# class Socket::AncillaryData

Socket::AncillaryData represents the ancillary data (control information) used by sendmsg and recvmsg system call. It contains socket family, control message (cmsg) level, cmsg type and cmsg data.

### Public Class Methods

Socket::AncillaryData.int(family, cmsg_level, cmsg_type, integer) → ancillarydata click to toggle source

Creates a new Socket::AncillaryData object which contains a int as data.

The size and endian is dependent on the host.

    require 'socket'

    p Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, STDERR.fileno)
    #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 2>

    static VALUE
    ancillary_s_int(VALUE klass, VALUE vfamily, VALUE vlevel, VALUE vtype, VALUE integer)
    {
        int family = rsock_family_arg(vfamily);
        int level = rsock_level_arg(family, vlevel);
        int type = rsock_cmsg_type_arg(family, level, vtype);
        int i = NUM2INT(integer);
        return ancdata_new(family, level, type, rb_str_new((char*)&i, sizeof(i)));
    }

Socket::AncillaryData.ip_pktinfo(addr, ifindex) → ancdata click to toggle source
Socket::AncillaryData.ip_pktinfo(addr, ifindex, spec_dst) → ancdata

Returns new ancillary data for IP_PKTINFO.

If spec_dst is not given, addr is used.

IP_PKTINFO is not standard.

Supported platform: GNU/Linux

    addr = Addrinfo.ip("127.0.0.1")
    ifindex = 0
    spec_dst = Addrinfo.ip("127.0.0.1")
    p Socket::AncillaryData.ip_pktinfo(addr, ifindex, spec_dst)
    #=> #<Socket::AncillaryData: INET IP PKTINFO 127.0.0.1 ifindex:0 spec_dst:127.0.0.1>

    static VALUE
    ancillary_s_ip_pktinfo(int argc, VALUE *argv, VALUE self)
    {
        VALUE v_addr, v_ifindex, v_spec_dst;
        unsigned int ifindex;
        struct sockaddr_in sa;
        struct in_pktinfo pktinfo;

        rb_scan_args(argc, argv, "21", &v_addr, &v_ifindex, &v_spec_dst);

        SockAddrStringValue(v_addr);
        ifindex = NUM2UINT(v_ifindex);
        if (NIL_P(v_spec_dst))
            v_spec_dst = v_addr;
        else
            SockAddrStringValue(v_spec_dst);

        memset(&pktinfo, 0, sizeof(pktinfo));

        memset(&sa, 0, sizeof(sa));
        if (RSTRING_LEN(v_addr) != sizeof(sa))
            rb_raise(rb_eArgError, "addr size different to AF_INET sockaddr");
        memcpy(&sa, RSTRING_PTR(v_addr), sizeof(sa));
        if (sa.sin_family != AF_INET)
            rb_raise(rb_eArgError, "addr is not AF_INET sockaddr");
        memcpy(&pktinfo.ipi_addr, &sa.sin_addr, sizeof(pktinfo.ipi_addr));

        pktinfo.ipi_ifindex = ifindex;

        memset(&sa, 0, sizeof(sa));
        if (RSTRING_LEN(v_spec_dst) != sizeof(sa))
            rb_raise(rb_eArgError, "spec_dat size different to AF_INET sockaddr");
        memcpy(&sa, RSTRING_PTR(v_spec_dst), sizeof(sa));
        if (sa.sin_family != AF_INET)
            rb_raise(rb_eArgError, "spec_dst is not AF_INET sockaddr");
        memcpy(&pktinfo.ipi_spec_dst, &sa.sin_addr, sizeof(pktinfo.ipi_spec_dst));

        return ancdata_new(AF_INET, IPPROTO_IP, IP_PKTINFO, rb_str_new((char *)&pktinfo, sizeof(pktinfo)));
    }

Socket::AncillaryData.ipv6_pktinfo(addr, ifindex) → ancdata click to toggle source

Returns new ancillary data for IPV6_PKTINFO.

IPV6_PKTINFO is defined by RFC 3542.

    addr = Addrinfo.ip("::1")
    ifindex = 0
    p Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
    #=> #<Socket::AncillaryData: INET6 IPV6 PKTINFO ::1 ifindex:0>

    static VALUE
    ancillary_s_ipv6_pktinfo(VALUE self, VALUE v_addr, VALUE v_ifindex)
    {
        unsigned int ifindex;
        struct sockaddr_in6 sa;
        struct in6_pktinfo pktinfo;

        SockAddrStringValue(v_addr);
        ifindex = NUM2UINT(v_ifindex);

        memset(&pktinfo, 0, sizeof(pktinfo));

        memset(&sa, 0, sizeof(sa));
        if (RSTRING_LEN(v_addr) != sizeof(sa))
            rb_raise(rb_eArgError, "addr size different to AF_INET6 sockaddr");
        memcpy(&sa, RSTRING_PTR(v_addr), sizeof(sa));
        if (sa.sin6_family != AF_INET6)
            rb_raise(rb_eArgError, "addr is not AF_INET6 sockaddr");
        memcpy(&pktinfo.ipi6_addr, &sa.sin6_addr, sizeof(pktinfo.ipi6_addr));

        pktinfo.ipi6_ifindex = ifindex;

        return ancdata_new(AF_INET6, IPPROTO_IPV6, IPV6_PKTINFO, rb_str_new((char *)&pktinfo, sizeof(pktinfo)));
    }

Socket::AncillaryData.new(family, cmsg_level, cmsg_type, cmsg_data) → ancillarydata click to toggle source

family should be an integer, a string or a symbol.

- Socket::AF_INET, “AF_INET”, “INET”, :AF_INET, :INET

- Socket::AF_UNIX, “AF_UNIX”, “UNIX”, :AF_UNIX, :UNIX

- etc.

cmsg_level should be an integer, a string or a symbol.

- Socket::SOL_SOCKET, “SOL_SOCKET”, “SOCKET”, :SOL_SOCKET and :SOCKET

- Socket::IPPROTO_IP, “IP” and :IP

- Socket::IPPROTO_IPV6, “IPV6” and :IPV6

- Socket::IPPROTO_TCP, “TCP” and :TCP

- etc.

cmsg_type should be an integer, a string or a symbol. If a string/symbol is specified, it is interpreted depend on cmsg_level.

- Socket::SCM_RIGHTS, “SCM_RIGHTS”, “RIGHTS”, :SCM_RIGHTS, :RIGHTS for SOL_SOCKET

- Socket::IP_RECVTTL, “RECVTTL” and :RECVTTL for IPPROTO_IP

- Socket::IPV6_PKTINFO, “PKTINFO” and :PKTINFO for IPPROTO_IPV6

- etc.

cmsg_data should be a string.

    p Socket::AncillaryData.new(:INET, :TCP, :NODELAY, "")
    #=> #<Socket::AncillaryData: INET TCP NODELAY "">

    p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "")
    #=> #<Socket::AncillaryData: INET6 IPV6 PKTINFO "">

    static VALUE
    ancillary_initialize(VALUE self, VALUE vfamily, VALUE vlevel, VALUE vtype, VALUE data)
    {
        int family = rsock_family_arg(vfamily);
        int level = rsock_level_arg(family, vlevel);
        int type = rsock_cmsg_type_arg(family, level, vtype);
        StringValue(data);
        rb_ivar_set(self, rb_intern("family"), INT2NUM(family));
        rb_ivar_set(self, rb_intern("level"), INT2NUM(level));
        rb_ivar_set(self, rb_intern("type"), INT2NUM(type));
        rb_ivar_set(self, rb_intern("data"), data);
        return self;
    }

Socket::AncillaryData.unix_rights(io1, io2, ...) → ancillarydata click to toggle source

Creates a new Socket::AncillaryData object which contains file descriptors as data.

    p Socket::AncillaryData.unix_rights(STDERR)
    #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 2>

    static VALUE
    ancillary_s_unix_rights(int argc, VALUE *argv, VALUE klass)
    {
        VALUE result, str, ary;
        int i;

        ary = rb_ary_new();

        for (i = 0 ; i < argc; i++) {
            VALUE obj = argv[i];
            if (!RB_TYPE_P(obj, T_FILE)) {
                rb_raise(rb_eTypeError, "IO expected");
            }
            rb_ary_push(ary, obj);
        }

        str = rb_str_buf_new(sizeof(int) * argc);

        for (i = 0 ; i < argc; i++) {
            VALUE obj = RARRAY_AREF(ary, i);
            rb_io_t *fptr;
            int fd;
            GetOpenFile(obj, fptr);
            fd = fptr->fd;
            rb_str_buf_cat(str, (char *)&fd, sizeof(int));
        }

        result = ancdata_new(AF_UNIX, SOL_SOCKET, SCM_RIGHTS, str);
        rb_ivar_set(result, rb_intern("unix_rights"), ary);
        return result;
    }

### Public Instance Methods

cmsg_is?(level, type) → true or false click to toggle source

tests the level and type of ancillarydata.

    ancdata = Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "")
    ancdata.cmsg_is?(Socket::IPPROTO_IPV6, Socket::IPV6_PKTINFO) #=> true
    ancdata.cmsg_is?(:IPV6, :PKTINFO)       #=> true
    ancdata.cmsg_is?(:IP, :PKTINFO)         #=> false
    ancdata.cmsg_is?(:SOCKET, :RIGHTS)      #=> false

    static VALUE
    ancillary_cmsg_is_p(VALUE self, VALUE vlevel, VALUE vtype)
    {
        int family = ancillary_family(self);
        int level = rsock_level_arg(family, vlevel);
        int type = rsock_cmsg_type_arg(family, level, vtype);

        if (ancillary_level(self) == level &&
            ancillary_type(self) == type)
            return Qtrue;
        else
            return Qfalse;
    }

data → string click to toggle source

returns the cmsg data as a string.

    p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").data
    #=> ""

    static VALUE
    ancillary_data(VALUE self)
    {
        VALUE v = rb_attr_get(self, rb_intern("data"));
        StringValue(v);
        return v;
    }

family → integer click to toggle source

returns the socket family as an integer.

    p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").family
    #=> 10

    static VALUE
    ancillary_family_m(VALUE self)
    {
        return INT2NUM(ancillary_family(self));
    }

inspect → string click to toggle source

returns a string which shows ancillarydata in human-readable form.

    p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").inspect
    #=> "#<Socket::AncillaryData: INET6 IPV6 PKTINFO \"\">"

    static VALUE
    ancillary_inspect(VALUE self)
    {
        VALUE ret;
        int family, level, type;
        VALUE data;
        ID family_id, level_id, type_id;
        VALUE vtype;
        int inspected;

        family = ancillary_family(self);
        level = ancillary_level(self);
        type = ancillary_type(self);
        data = ancillary_data(self);

        ret = rb_sprintf("#<%s:", rb_obj_classname(self));

        family_id = rsock_intern_family_noprefix(family);
        if (family_id)
            rb_str_catf(ret, " %s", rb_id2name(family_id));
        else
            rb_str_catf(ret, " family:%d", family);

        if (level == SOL_SOCKET) {
            rb_str_cat2(ret, " SOCKET");

            type_id = rsock_intern_scm_optname(type);
            if (type_id)
                rb_str_catf(ret, " %s", rb_id2name(type_id));
            else
                rb_str_catf(ret, " cmsg_type:%d", type);
        }
        else if (IS_IP_FAMILY(family)) {
            level_id = rsock_intern_iplevel(level);
            if (level_id)
                rb_str_catf(ret, " %s", rb_id2name(level_id));
            else
                rb_str_catf(ret, " cmsg_level:%d", level);

            vtype = ip_cmsg_type_to_sym(level, type);
            if (SYMBOL_P(vtype))
                rb_str_catf(ret, " %"PRIsVALUE, rb_sym2str(vtype));
            else
                rb_str_catf(ret, " cmsg_type:%d", type);
        }
        else {
            rb_str_catf(ret, " cmsg_level:%d", level);
            rb_str_catf(ret, " cmsg_type:%d", type);
        }

        inspected = 0;

        if (level == SOL_SOCKET)
            family = AF_UNSPEC;

        switch (family) {
          case AF_UNSPEC:
            switch (level) {
    #        if defined(SOL_SOCKET)
              case SOL_SOCKET:
                switch (type) {
    #            if defined(SCM_TIMESTAMP) /* GNU/Linux, FreeBSD, NetBSD, OpenBSD, MacOS X, Solaris */
                  case SCM_TIMESTAMP: inspected = inspect_timeval_as_abstime(level, type, data, ret); break;
    #            endif
    #            if defined(SCM_TIMESTAMPNS) /* GNU/Linux */
                  case SCM_TIMESTAMPNS: inspected = inspect_timespec_as_abstime(level, type, data, ret); break;
    #            endif
    #            if defined(SCM_BINTIME) /* FreeBSD */
                  case SCM_BINTIME: inspected = inspect_bintime_as_abstime(level, type, data, ret); break;
    #            endif
    #            if defined(SCM_RIGHTS) /* 4.4BSD */
                  case SCM_RIGHTS: inspected = anc_inspect_socket_rights(level, type, data, ret); break;
    #            endif
    #            if defined(SCM_CREDENTIALS) /* GNU/Linux */
                  case SCM_CREDENTIALS: inspected = anc_inspect_passcred_credentials(level, type, data, ret); break;
    #            endif
    #            if defined(INSPECT_SCM_CREDS) /* NetBSD */
                  case SCM_CREDS: inspected = anc_inspect_socket_creds(level, type, data, ret); break;
    #            endif
                }
                break;
    #        endif
            }
            break;

          case AF_INET:
    #ifdef INET6
          case AF_INET6:
    #endif
            switch (level) {
    #        if defined(IPPROTO_IP)
              case IPPROTO_IP:
                switch (type) {
    #            if defined(IP_RECVDSTADDR) /* 4.4BSD */
                  case IP_RECVDSTADDR: inspected = anc_inspect_ip_recvdstaddr(level, type, data, ret); break;
    #            endif
    #            if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO_IPI_SPEC_DST) /* GNU/Linux */
                  case IP_PKTINFO: inspected = anc_inspect_ip_pktinfo(level, type, data, ret); break;
    #            endif
                }
                break;
    #        endif

    #        if defined(IPPROTO_IPV6)
              case IPPROTO_IPV6:
                switch (type) {
    #            if defined(IPV6_PKTINFO) && defined(HAVE_TYPE_STRUCT_IN6_PKTINFO) /* RFC 3542 */
                  case IPV6_PKTINFO: inspected = anc_inspect_ipv6_pktinfo(level, type, data, ret); break;
    #            endif
                }
                break;
    #        endif
            }
            break;
        }

        if (!inspected) {
            rb_str_cat2(ret, " ");
            rb_str_append(ret, rb_str_dump(data));
        }

        rb_str_cat2(ret, ">");

        return ret;
    }

int → integer click to toggle source

Returns the data in ancillarydata as an int.

The size and endian is dependent on the host.

    ancdata = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, STDERR.fileno)
    p ancdata.int #=> 2

    static VALUE
    ancillary_int(VALUE self)
    {
        VALUE data;
        int i;
        data = ancillary_data(self);
        if (RSTRING_LEN(data) != sizeof(int))
            rb_raise(rb_eTypeError, "size differ.  expected as sizeof(int)=%d but %ld", (int)sizeof(int), (long)RSTRING_LEN(data));
        memcpy((char*)&i, RSTRING_PTR(data), sizeof(int));
        return INT2NUM(i);
    }

ip_pktinfo → [addr, ifindex, spec_dst] click to toggle source

Extracts addr, ifindex and spec_dst from IP_PKTINFO ancillary data.

IP_PKTINFO is not standard.

Supported platform: GNU/Linux

    addr = Addrinfo.ip("127.0.0.1")
    ifindex = 0
    spec_dest = Addrinfo.ip("127.0.0.1")
    ancdata = Socket::AncillaryData.ip_pktinfo(addr, ifindex, spec_dest)
    p ancdata.ip_pktinfo
    #=> [#<Addrinfo: 127.0.0.1>, 0, #<Addrinfo: 127.0.0.1>]

    static VALUE
    ancillary_ip_pktinfo(VALUE self)
    {
        int level, type;
        VALUE data;
        struct in_pktinfo pktinfo;
        struct sockaddr_in sa;
        VALUE v_spec_dst, v_addr;

        level = ancillary_level(self);
        type = ancillary_type(self);
        data = ancillary_data(self);

        if (level != IPPROTO_IP || type != IP_PKTINFO ||
            RSTRING_LEN(data) != sizeof(struct in_pktinfo)) {
            rb_raise(rb_eTypeError, "IP_PKTINFO ancillary data expected");
        }

        memcpy(&pktinfo, RSTRING_PTR(data), sizeof(struct in_pktinfo));
        memset(&sa, 0, sizeof(sa));

        sa.sin_family = AF_INET;
        memcpy(&sa.sin_addr, &pktinfo.ipi_addr, sizeof(sa.sin_addr));
        v_addr = rsock_addrinfo_new((struct sockaddr *)&sa, sizeof(sa), PF_INET, 0, 0, Qnil, Qnil);

        sa.sin_family = AF_INET;
        memcpy(&sa.sin_addr, &pktinfo.ipi_spec_dst, sizeof(sa.sin_addr));
        v_spec_dst = rsock_addrinfo_new((struct sockaddr *)&sa, sizeof(sa), PF_INET, 0, 0, Qnil, Qnil);

        return rb_ary_new3(3, v_addr, UINT2NUM(pktinfo.ipi_ifindex), v_spec_dst);
    }

ipv6_pktinfo → [addr, ifindex] click to toggle source

Extracts addr and ifindex from IPV6_PKTINFO ancillary data.

IPV6_PKTINFO is defined by RFC 3542.

    addr = Addrinfo.ip("::1")
    ifindex = 0
    ancdata = Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
    p ancdata.ipv6_pktinfo #=> [#<Addrinfo: ::1>, 0]

    static VALUE
    ancillary_ipv6_pktinfo(VALUE self)
    {
        struct in6_pktinfo pktinfo;
        struct sockaddr_in6 sa;
        VALUE v_addr;

        extract_ipv6_pktinfo(self, &pktinfo, &sa);
        v_addr = rsock_addrinfo_new((struct sockaddr *)&sa, (socklen_t)sizeof(sa), PF_INET6, 0, 0, Qnil, Qnil);
        return rb_ary_new3(2, v_addr, UINT2NUM(pktinfo.ipi6_ifindex));
    }

ipv6_pktinfo_addr → addr click to toggle source

Extracts addr from IPV6_PKTINFO ancillary data.

IPV6_PKTINFO is defined by RFC 3542.

    addr = Addrinfo.ip("::1")
    ifindex = 0
    ancdata = Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
    p ancdata.ipv6_pktinfo_addr #=> #<Addrinfo: ::1>

    static VALUE
    ancillary_ipv6_pktinfo_addr(VALUE self)
    {
        struct in6_pktinfo pktinfo;
        struct sockaddr_in6 sa;
        extract_ipv6_pktinfo(self, &pktinfo, &sa);
        return rsock_addrinfo_new((struct sockaddr *)&sa, (socklen_t)sizeof(sa), PF_INET6, 0, 0, Qnil, Qnil);
    }

ipv6_pktinfo_ifindex → addr click to toggle source

Extracts ifindex from IPV6_PKTINFO ancillary data.

IPV6_PKTINFO is defined by RFC 3542.

    addr = Addrinfo.ip("::1")
    ifindex = 0
    ancdata = Socket::AncillaryData.ipv6_pktinfo(addr, ifindex)
    p ancdata.ipv6_pktinfo_ifindex #=> 0

    static VALUE
    ancillary_ipv6_pktinfo_ifindex(VALUE self)
    {
        struct in6_pktinfo pktinfo;
        struct sockaddr_in6 sa;
        extract_ipv6_pktinfo(self, &pktinfo, &sa);
        return UINT2NUM(pktinfo.ipi6_ifindex);
    }

level → integer click to toggle source

returns the cmsg level as an integer.

    p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").level
    #=> 41

    static VALUE
    ancillary_level_m(VALUE self)
    {
        return INT2NUM(ancillary_level(self));
    }

timestamp → time click to toggle source

returns the timestamp as a time object.

ancillarydata should be one of following type:

- SOL_SOCKET/SCM_TIMESTAMP (microsecond) GNU/Linux, FreeBSD, NetBSD, OpenBSD, Solaris, MacOS X

- SOL_SOCKET/SCM_TIMESTAMPNS (nanosecond) GNU/Linux

- SOL_SOCKET/SCM_BINTIME (2**(-64) second) FreeBSD

  Addrinfo.udp(“127.0.0.1”, 0).bind {|s1|

      Addrinfo.udp("127.0.0.1", 0).bind {|s2|
        s1.setsockopt(:SOCKET, :TIMESTAMP, true)
        s2.send "a", 0, s1.local_address
        ctl = s1.recvmsg.last
        p ctl    #=> #<Socket::AncillaryData: INET SOCKET TIMESTAMP 2009-02-24 17:35:46.775581>
        t = ctl.timestamp
        p t      #=> 2009-02-24 17:35:46 +0900
        p t.usec #=> 775581
        p t.nsec #=> 775581000
      }

  }

    static VALUE
    ancillary_timestamp(VALUE self)
    {
        int level, type;
        VALUE data;
        VALUE result = Qnil;

        level = ancillary_level(self);
        type = ancillary_type(self);
        data = ancillary_data(self);

    # ifdef SCM_TIMESTAMP
        if (level == SOL_SOCKET && type == SCM_TIMESTAMP &&
            RSTRING_LEN(data) == sizeof(struct timeval)) {
            struct timeval tv;
            memcpy((char*)&tv, RSTRING_PTR(data), sizeof(tv));
            result = rb_time_new(tv.tv_sec, tv.tv_usec);
        }
    # endif

    # ifdef SCM_TIMESTAMPNS
        if (level == SOL_SOCKET && type == SCM_TIMESTAMPNS &&
            RSTRING_LEN(data) == sizeof(struct timespec)) {
            struct timespec ts;
            memcpy((char*)&ts, RSTRING_PTR(data), sizeof(ts));
            result = rb_time_nano_new(ts.tv_sec, ts.tv_nsec);
        }
    # endif

    #define add(x,y) (rb_funcall((x), '+', 1, (y)))
    #define mul(x,y) (rb_funcall((x), '*', 1, (y)))
    #define quo(x,y) (rb_funcall((x), rb_intern("quo"), 1, (y)))

    # ifdef SCM_BINTIME
        if (level == SOL_SOCKET && type == SCM_BINTIME &&
            RSTRING_LEN(data) == sizeof(struct bintime)) {
            struct bintime bt;
            VALUE d, timev;
            memcpy((char*)&bt, RSTRING_PTR(data), sizeof(bt));
            d = ULL2NUM(0x100000000ULL);
            d = mul(d,d);
            timev = add(TIMET2NUM(bt.sec), quo(ULL2NUM(bt.frac), d));
            result = rb_time_num_new(timev, Qnil);
        }
    # endif

        if (result == Qnil)
            rb_raise(rb_eTypeError, "timestamp ancillary data expected");

        return result;
    }

type → integer click to toggle source

returns the cmsg type as an integer.

    p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").type
    #=> 2

    static VALUE
    ancillary_type_m(VALUE self)
    {
        return INT2NUM(ancillary_type(self));
    }

unix_rights → array-of-IOs or nil click to toggle source

returns the array of IO objects for SCM_RIGHTS control message in UNIX domain socket.

The class of the IO objects in the array is IO or Socket.

The array is attached to ancillarydata when it is instantiated. For example, BasicSocket#recvmsg attach the array when receives a SCM_RIGHTS control message and :scm_rights=>true option is given.

    # recvmsg needs :scm_rights=>true for unix_rights
    s1, s2 = UNIXSocket.pair
    p s1                                         #=> #<UNIXSocket:fd 3>
    s1.sendmsg "stdin and a socket", 0, nil, Socket::AncillaryData.unix_rights(STDIN, s1)
    _, _, _, ctl = s2.recvmsg(:scm_rights=>true)
    p ctl                                        #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 6 7>
    p ctl.unix_rights                            #=> [#<IO:fd 6>, #<Socket:fd 7>]
    p File.identical?(STDIN, ctl.unix_rights[0]) #=> true
    p File.identical?(s1, ctl.unix_rights[1])    #=> true

    # If :scm_rights=>true is not given, unix_rights returns nil
    s1, s2 = UNIXSocket.pair
    s1.sendmsg "stdin and a socket", 0, nil, Socket::AncillaryData.unix_rights(STDIN, s1)
    _, _, _, ctl = s2.recvmsg
    p ctl #=> #<Socket::AncillaryData: UNIX SOCKET RIGHTS 6 7>
    p ctl.unix_rights #=> nil

    static VALUE
    ancillary_unix_rights(VALUE self)
    {
        int level, type;

        level = ancillary_level(self);
        type = ancillary_type(self);

        if (level != SOL_SOCKET || type != SCM_RIGHTS)
            rb_raise(rb_eTypeError, "SCM_RIGHTS ancillary data expected");

        return rb_attr_get(self, rb_intern("unix_rights"));
    }