class Socket::AncillaryData - Documentation for Ruby 3.3

# 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"));
    }