天泣記

2009-01-01 (Thu)

#1

IP_PKTINFO を試してみたい。

のだが、こいつを試すには recvmesg を使う必要があり、ruby では使えないので面倒くさい。

さて、C で直接書くか、Ruby に sendmsg/recvmesg を実装するか。

後者でやってみよう... やってみた。

で、試してみた。

% ./ruby -rsocket -rpp -e '
u = UDPSocket.new
u.setsockopt(Socket::IPPROTO_IP, Socket::IP_PKTINFO, 1)
u.bind("0.0.0.0", 2222)

v = UDPSocket.new
v.send("foo", 0, "127.0.0.1", 2222)
pp x = u.recvmsg
p Socket.unpack_sockaddr_in(x.last)
puts

v.send("bar", 0, "192.168.0.128", 2222)
pp x = u.recvmsg
p Socket.unpack_sockaddr_in(x.last)
puts
'
["foo",
 [[0, 8, "\x01\x00\x00\x00\x7F\x00\x00\x01\x7F\x00\x00\x01"]],
 0,
 "\x02\x00\x8E\xBE\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"]
[36542, "127.0.0.1"]

["bar",
 [[0, 8, "\x01\x00\x00\x00\xC0\xA8\x00\x80\xC0\xA8\x00\x80"]],
 0,
 "\x02\x00\x8E\xBE\xC0\xA8\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00"]
[36542, "192.168.0.128"]

補助データの level, type の 0, 8 というのは IPPROTO_IP, IP_PKTINFO である。

で、中身の "\x01\x00\x00\x00\x7F\x00\x00\x01\x7F\x00\x00\x01" の中の、\x7F\x00\x00\x01 が 127.0.0.1 で宛先を意味している、のだろう。ふたつあるのだが、どういう違いがあるのかはよくわからない。(マニュアル ip(7) に書いてあるのだが読んでもいまひとつよくわからない)

とりあえずの実装:

% svn diff --diff-cmd diff -x '-u -p'
Index: ext/socket/mkconstants.rb
===================================================================
--- ext/socket/mkconstants.rb   (revision 21217)
+++ ext/socket/mkconstants.rb   (working copy)
@@ -191,6 +191,36 @@ IP_DROP_MEMBERSHIP
 IP_DEFAULT_MULTICAST_TTL
 IP_DEFAULT_MULTICAST_LOOP
 IP_MAX_MEMBERSHIPS
+IP_ROUTER_ALERT
+IP_PKTINFO
+IP_PKTOPTIONS
+IP_MTU_DISCOVER
+IP_RECVERR
+IP_RECVTTL
+IP_RECVTOS
+IP_MTU
+IP_FREEBIND
+IP_IPSEC_POLICY
+IP_XFRM_POLICY
+IP_PASSSEC
+IP_PMTUDISC_DONT
+IP_PMTUDISC_WANT
+IP_PMTUDISC_DO
+IP_UNBLOCK_SOURCE
+IP_BLOCK_SOURCE
+IP_ADD_SOURCE_MEMBERSHIP
+IP_DROP_SOURCE_MEMBERSHIP
+IP_MSFILTER
+
+MCAST_JOIN_GROUP
+MCAST_BLOCK_SOURCE
+MCAST_UNBLOCK_SOURCE
+MCAST_LEAVE_GROUP
+MCAST_JOIN_SOURCE_GROUP
+MCAST_LEAVE_SOURCE_GROUP
+MCAST_MSFILTER
+MCAST_EXCLUDE
+MCAST_INCLUDE

 SO_DEBUG
 SO_REUSEADDR
@@ -308,3 +338,6 @@ IPV6_USE_MIN_MTU

 INET_ADDRSTRLEN
 INET6_ADDRSTRLEN
+
+SCM_RIGHTS
+SCM_CREDENTIALS
Index: ext/socket/socket.c
===================================================================
--- ext/socket/socket.c (revision 21217)
+++ ext/socket/socket.c (working copy)
@@ -789,6 +789,390 @@ bsock_recv_nonblock(int argc, VALUE *arg
     return s_recvfrom_nonblock(sock, argc, argv, RECV_RECV);
 }

+#if defined(HAVE_SENDMSG) && defined(HAVE_ST_MSG_CONTROL)
+struct sendmsg_args_struct {
+    int fd;
+    const struct msghdr *msg;
+    int flags;
+};
+
+static VALUE
+internal_sendmsg_func(void *ptr)
+{
+    struct sendmsg_args_struct *args = ptr;
+    return sendmsg(args->fd, args->msg, args->flags);
+}
+
+static ssize_t
+rb_sendmsg(int fd, const struct msghdr *msg, int flags)
+{
+    struct sendmsg_args_struct args;
+    args.fd = fd;
+    args.msg = msg;
+    args.flags = flags;
+    return rb_thread_blocking_region(internal_sendmsg_func, &args, RUBY_UBF_IO, 0);
+}
+
+static VALUE
+bsock_sendmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
+{
+    rb_io_t *fptr;
+    VALUE data, controls, vflags, dest_sockaddr;
+    struct msghdr mh;
+    struct iovec iov;
+    long i;
+    volatile VALUE controls_str = 0;
+    int flags;
+    ssize_t ss;
+
+    rb_secure(4);
+
+    rb_scan_args(argc, argv, "13", &data, &controls, &vflags, &dest_sockaddr);
+
+    StringValue(data);
+
+    if (!NIL_P(controls)) {
+        controls_str = rb_str_tmp_new(0);
+        controls = rb_convert_type(controls, T_ARRAY, "Array", "to_ary");
+        for (i = 0; i < RARRAY_LEN(controls); i++) {
+            VALUE elt = RARRAY_PTR(controls)[i];
+            int level, type;
+            VALUE cdata;
+            long oldlen;
+            struct cmsghdr *cmh;
+            size_t cspace;
+            elt = rb_convert_type(elt, T_ARRAY, "Array", "to_ary");
+            if (RARRAY_LEN(elt) != 3)
+                rb_raise(rb_eArgError, "an element of controls should be 3-elements array");
+            level = NUM2INT(rb_ary_entry(elt, 0));
+            type = NUM2INT(rb_ary_entry(elt, 1));
+            cdata = rb_ary_entry(elt, 2);
+            StringValue(cdata);
+            oldlen = RSTRING_LEN(controls_str);
+            cspace = CMSG_SPACE(RSTRING_LEN(cdata));
+            rb_str_resize(controls_str, oldlen + cspace);
+            cmh = (struct cmsghdr *)(RSTRING_PTR(controls_str)+oldlen);
+            memset((char *)cmh, 0, cspace);
+            cmh->cmsg_level = level;
+            cmh->cmsg_type = type;
+            cmh->cmsg_len = CMSG_DATA(cmh) + RSTRING_LEN(cdata) - (unsigned char *)cmh;
+            MEMCPY(CMSG_DATA(cmh), RSTRING_PTR(cdata), char, RSTRING_LEN(cdata));
+        }
+    }
+
+    flags = NIL_P(vflags) ? 0 : NUM2INT(vflags);
+
+    if (!NIL_P(dest_sockaddr))
+        StringValue(dest_sockaddr);
+
+    GetOpenFile(sock, fptr);
+
+  retry:
+    memset(&mh, 0, sizeof(mh));
+    mh.msg_iovlen = 1;
+    mh.msg_iov = &iov;
+    iov.iov_base = RSTRING_PTR(data);
+    iov.iov_len = RSTRING_LEN(data);
+    if (controls_str) {
+        mh.msg_control = RSTRING_PTR(controls_str);
+        mh.msg_controllen = RSTRING_LEN(controls_str);
+    }
+    else {
+        mh.msg_control = NULL;
+        mh.msg_controllen = 0;
+    }
+
+    if (nonblock)
+        rb_io_set_nonblock(fptr);
+
+    ss = rb_sendmsg(fptr->fd, &mh, flags);
+
+    if (!nonblock && rb_io_wait_writable(fptr->fd)) {
+        rb_io_check_closed(fptr);
+        goto retry;
+    }
+
+    if (ss == -1)
+       rb_sys_fail("sendmsg(2)");
+
+    return SSIZET2NUM(ss);
+}
+#else
+static VALUE
+bsock_sendmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
+{
+    rb_notimplement();
+}
+#endif
+
+/*
+ * call-seq:
+ *    basicsocket.sendmsg(str, controls=nil, flags=0, dest_sockaddr=nil) => sent_len
+ *
+ * sendmsg sends a message using sendmsg(2) system call in blocking manner.
+ *
+ * str is data to send.
+ *
+ * controls is ancillary data which is an array of 3-elements arrays such as:
+ *
+ *   controls = [[Socket::SOL_SOCKET, Socket::SCM_RIGHTS, [io.fileno].pack("i!")]]
+ *
+ * flags is bitwise OR of MSG_* constants such as Socket::MSG_OOB.
+ *
+ * dest_sockaddr is a destination socket address for connection-less socket.
+ * It should be a sockaddr such as a result of Socket.sockaddr_in.
+ *
+ * The return value, sent_len, is an integer which is the number of bytes sent.
+ *
+ * sendmsg can be used to implement send_io as follows:
+ *
+ *   sock.sendmsg("\0", [[Socket::SOL_SOCKET, Socket::SCM_RIGHTS, [io.fileno].pack("i!")]])
+ *
+ */
+static VALUE
+bsock_sendmsg(int argc, VALUE *argv, VALUE sock)
+{
+    return bsock_sendmsg_internal(argc, argv, sock, 0);
+}
+
+/*
+ * call-seq:
+ *    basicsocket.sendmsg_nonblock(str, controls=nil, flags=0, dest_sockaddr=nil) => sent_len
+ *
+ * sendmsg sends a message using sendmsg(2) system call in non-blocking manner.
+ *
+ * It is similar to BasicSocket#sendmsg
+ * but non-blocking flag is set before the system call
+ * and it doesn't retry the system call.
+ *
+ */
+static VALUE
+bsock_sendmsg_nonblock(int argc, VALUE *argv, VALUE sock)
+{
+    return bsock_sendmsg_internal(argc, argv, sock, 1);
+}
+
+#if defined(HAVE_RECVMSG) && defined(HAVE_ST_MSG_CONTROL)
+struct recvmsg_args_struct {
+    int fd;
+    struct msghdr *msg;
+    int flags;
+};
+
+static VALUE
+internal_recvmsg_func(void *ptr)
+{
+    struct recvmsg_args_struct *args = ptr;
+    return recvmsg(args->fd, args->msg, args->flags);
+}
+
+static ssize_t
+rb_recvmsg(int fd, struct msghdr *msg, int flags)
+{
+    struct recvmsg_args_struct args;
+    args.fd = fd;
+    args.msg = msg;
+    args.flags = flags;
+    return rb_thread_blocking_region(internal_recvmsg_func, &args, RUBY_UBF_IO, 0);
+}
+
+static VALUE
+bsock_recvmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
+{
+    rb_io_t *fptr;
+    VALUE vmaxdatlen, vmaxctllen, vflags;
+    int grow_buffer;
+    size_t maxdatlen, maxctllen;
+    int flags, orig_flags;
+    struct msghdr mh;
+    struct iovec iov;
+    struct cmsghdr *cmh;
+    char namebuf[1024];
+    char datbuf0[4096], *datbuf;
+    char ctlbuf0[4096], *ctlbuf;
+    VALUE dat_str = Qnil;
+    VALUE ctl_str = Qnil;
+    VALUE controls;
+    ssize_t ss;
+
+    rb_secure(4);
+
+    rb_scan_args(argc, argv, "03", &vmaxdatlen, &vmaxctllen, &vflags);
+
+    maxdatlen = NIL_P(vmaxdatlen) ? sizeof(datbuf0) : NUM2SIZET(vmaxdatlen);
+    maxctllen = NIL_P(vmaxctllen) ? sizeof(ctlbuf0) : NUM2SIZET(vmaxctllen);
+    orig_flags = flags = NIL_P(vflags) ? 0 : NUM2INT(vflags);
+
+    grow_buffer = NIL_P(vmaxdatlen) || NIL_P(vmaxctllen);
+
+    GetOpenFile(sock, fptr);
+
+  retry:
+    if (maxdatlen <= sizeof(datbuf0))
+        datbuf = datbuf0;
+    else {
+        if (NIL_P(dat_str))
+            dat_str = rb_str_tmp_new(maxdatlen);
+        else
+            rb_str_resize(dat_str, maxdatlen);
+        datbuf = RSTRING_PTR(dat_str);
+    }
+
+    if (maxctllen <= sizeof(ctlbuf0))
+        ctlbuf = ctlbuf0;
+    else {
+        if (NIL_P(ctl_str))
+            ctl_str = rb_str_tmp_new(maxctllen);
+        else
+            rb_str_resize(ctl_str, maxctllen);
+        ctlbuf = RSTRING_PTR(ctl_str);
+    }
+
+    memset(&mh, 0, sizeof(mh));
+
+    memset(namebuf, 0, sizeof(namebuf));
+    mh.msg_name = namebuf;
+    mh.msg_namelen = sizeof(namebuf);
+
+    mh.msg_iov = &iov;
+    mh.msg_iovlen = 1;
+    iov.iov_base = datbuf;
+    iov.iov_len = maxdatlen;
+
+    mh.msg_control = ctlbuf;
+    mh.msg_controllen = maxctllen;
+
+    if (grow_buffer)
+        flags |= MSG_PEEK;
+
+    if (nonblock)
+        rb_io_set_nonblock(fptr);
+
+    ss = rb_recvmsg(fptr->fd, &mh, flags);
+
+    if (!nonblock && rb_io_wait_readable(fptr->fd)) {
+        rb_io_check_closed(fptr);
+        goto retry;
+    }
+
+    if (grow_buffer) {
+        if ((NIL_P(vmaxdatlen) && (mh.msg_flags & MSG_TRUNC)) ||
+            (NIL_P(vmaxctllen) && (mh.msg_flags & MSG_CTRUNC))) {
+            if (NIL_P(vmaxdatlen) && (mh.msg_flags & MSG_TRUNC))
+                maxdatlen *= 2;
+            if (NIL_P(vmaxctllen) && (mh.msg_flags & MSG_CTRUNC))
+                maxctllen *= 2;
+            goto retry;
+        }
+        else {
+            grow_buffer = 0;
+            if (flags != orig_flags) {
+                flags = orig_flags;
+                goto retry;
+            }
+        }
+    }
+
+    if (ss == -1)
+       rb_sys_fail("recvmsg(2)");
+
+    controls = rb_ary_new();
+    for (cmh = CMSG_FIRSTHDR(&mh); cmh != NULL; cmh = CMSG_NXTHDR(&mh, cmh)) {
+        VALUE ctl;
+        size_t clen = (char*)cmh + cmh->cmsg_len - (char*)CMSG_DATA(cmh);
+        ctl = rb_ary_new3(3, INT2NUM(cmh->cmsg_level),
+                             INT2NUM(cmh->cmsg_type), 
+                             rb_str_new((char*)CMSG_DATA(cmh), clen));
+        rb_ary_push(controls, ctl);
+    }
+
+    if (NIL_P(dat_str))
+        dat_str = rb_str_new(datbuf, ss);
+    else {
+        rb_str_resize(dat_str, ss);
+        RBASIC(dat_str)->klass = rb_cString;
+    }
+
+    return rb_ary_new3(4, dat_str,
+                          controls,
+                          INT2NUM(mh.msg_flags),
+                          rb_str_new(mh.msg_name, mh.msg_namelen));
+}
+#else
+static VALUE
+bsock_recvmsg_internal(int argc, VALUE *argv, VALUE sock, int nonblock)
+{
+    rb_notimplement();
+}
+#endif
+
+/*
+ * call-seq:
+ *    basicsocket.recvmsg(maxdatalen=nil, maxcontrollen=nil, flags=0) => [data, controls, rflags, sender_sockaddr]
+ *
+ * recvmsg receives a message using recvmsg(2) system call in blocking manner.
+ *
+ * maxdatalen is the maximum length of data to receive.
+ *
+ * maxcontrolslen is the maximum length of controls (ancillary data) to receive.
+ *
+ * flags is bitwise OR of MSG_* constants such as Socket::MSG_PEEK.
+ *
+ * The return value is 4-elements array.
+ *
+ * data is a string.
+ *
+ * controls is ancillary data which is an array of 3-elements arrays such as:
+ *
+ *   [[1, 1, "\a\x00\x00\x00"]]
+ *   # Socket::SOL_SOCKET = 1
+ *   # Socket::SCM_RIGHTS = 1
+ *   # "\a\x00\x00\x00".unpack("i!") = 7
+ *
+ * rflags is a flags on received message which is bitwise OR of MSG_* constants such as Socket::MSG_TRUNC.
+ *
+ * sender_sockaddr is a sender socket address for connection-less socket.
+ * It is a sockaddr such as a result of Socket.sockaddr_in.
+ * For connection-oriented socket, sender_sockaddr is unspecified.
+ *
+ * maxdatalen and maxcontrolslen can be nil.
+ * In that case, the buffer will be grown until the message is not truncated.
+ * Internally, MSG_PEEK is used and MSG_TRUNC/MSG_CTRUNC are checked.
+ *
+ * sendmsg can be used to implement recv_io as follows:
+ *
+ *   data, controls, rflags, sender_sockaddr = sock.recvmsg
+ *   controls.each {|level, type, cdata|
+ *     if level == Socket::SOL_SOCKET && Socket::SCM_RIGHTS
+ *       fd = cdata.unpack("i!")
+ *       return IO.new(fd)
+ *     end
+ *   }
+ *
+ */
+static VALUE
+bsock_recvmsg(int argc, VALUE *argv, VALUE sock)
+{
+    return bsock_recvmsg_internal(argc, argv, sock, 0);
+}
+
+/*
+ * call-seq:
+ *    basicsocket.recvmsg_nonblock(maxdatalen=nil, maxcontrollen=nil, flags=0) => [data, controls, rflags, sender_sockaddr]
+ *
+ * recvmsg receives a message using recvmsg(2) system call in non-blocking manner.
+ *
+ * It is similar to BasicSocket#recvmsg
+ * but non-blocking flag is set before the system call
+ * and it doesn't retry the system call.
+ *
+ */
+static VALUE
+bsock_recvmsg_nonblock(int argc, VALUE *argv, VALUE sock)
+{
+    return bsock_recvmsg_internal(argc, argv, sock, 1);
+}
+
 static VALUE
 bsock_do_not_rev_lookup(void)
 {
@@ -3646,6 +4030,11 @@ Init_socket()
     rb_define_method(rb_cBasicSocket, "do_not_reverse_lookup", bsock_do_not_reverse_lookup, 0);
     rb_define_method(rb_cBasicSocket, "do_not_reverse_lookup=", bsock_do_not_reverse_lookup_set, 1);

+    rb_define_method(rb_cBasicSocket, "sendmsg", bsock_sendmsg, -1);
+    rb_define_method(rb_cBasicSocket, "sendmsg_nonblock", bsock_sendmsg_nonblock, -1);
+    rb_define_method(rb_cBasicSocket, "recvmsg", bsock_recvmsg, -1);
+    rb_define_method(rb_cBasicSocket, "recvmsg_nonblock", bsock_recvmsg_nonblock, -1);
+
     rb_cIPSocket = rb_define_class("IPSocket", rb_cBasicSocket);
     rb_define_method(rb_cIPSocket, "addr", ip_addr, 0);
     rb_define_method(rb_cIPSocket, "peeraddr", ip_peeraddr, 0);
Index: test/socket/test_unix.rb
===================================================================
--- test/socket/test_unix.rb    (revision 21217)
+++ test/socket/test_unix.rb    (working copy)
@@ -30,6 +30,52 @@ class TestUNIXSocket < Test::Unit::TestC
     end
   end

+  def test_sendmsg
+    return if !Socket.const_defined?(:SCM_RIGHTS)
+    IO.pipe {|r1, w|
+      UNIXSocket.pair {|s1, s2|
+        begin
+          ret = s1.sendmsg("\0", [[Socket::SOL_SOCKET, Socket::SCM_RIGHTS, [r1.fileno].pack("i!")]])
+        rescue NotImplementedError
+          return
+        end
+        assert_equal(1, ret)
+        r2 = s2.recv_io
+        begin
+          assert(File.identical?(r1, r2))
+        ensure
+          r2.close
+        end
+      }
+    }
+  end
+
+  def test_recvmsg
+    return if !Socket.const_defined?(:SCM_RIGHTS)
+    IO.pipe {|r1, w|
+      UNIXSocket.pair {|s1, s2|
+        s1.send_io(r1)
+        data, ctls, flags, srcaddr = s2.recvmsg
+        assert_equal("\0", data)
+        assert_instance_of(Array, ctls)
+        assert_equal(1, ctls.length)
+        assert_equal(3, ctls[0].length)
+        assert_equal(Socket::SOL_SOCKET, ctls[0][0])
+        assert_equal(Socket::SCM_RIGHTS, ctls[0][1])
+        assert_instance_of(String, ctls[0][2])
+        fd, rest = ctls[0][2].unpack("i!a*")
+        assert_equal("", rest)
+        assert_equal(0, flags)
+        r2 = IO.new(fd)
+        begin
+          assert(File.identical?(r1, r2))
+        ensure
+          r2.close
+        end
+      }
+    }
+  end
+
   def bound_unix_socket(klass)
     tmpfile = Tempfile.new("testrubysock")
     path = tmpfile.path
#2

実装して気がついたのだが、recvmsg を使うと、パケットの長さを事前に知らなくてもどうにかなるな。

MSG_PEEK で試して、MSG_TRUNC/MSG_CTRUNC が出なくなるまでバッファを大きくしていけばいい。

2009-01-02 (Fri)

#1

SO_REUSEADDR はきょーれつ。

ひとつのポートから複数の通信先に connect できるとは。

% ./ruby -rsocket -e '
s1 = Socket.new(:AF_INET, :SOCK_STREAM, 0)
s1.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
s1.bind(Socket.sockaddr_in(3333, "127.0.0.1"))
s1.connect(Socket.sockaddr_in(8888, "127.0.0.1"))
Thread.new { loop { p s1.readline } }

s2 = Socket.new(:AF_INET, :SOCK_STREAM, 0)
s2.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
s2.bind(Socket.sockaddr_in(3333, "127.0.0.1"))
s2.connect(Socket.sockaddr_in(8889, "127.0.0.1"))
Thread.new { loop { p s2.readline } }.join
'

たしかに accept 側ではひとつのポートで複数の相手と通信できるんだから、connect 側で可能であってもおかしくはないのだが。

#2

UDP で、受信したところから返事をするにはどうしたらいいか。

まず、UDP で簡単な echo server を作る。

% ./ruby -rsocket -e '
u = UDPSocket.new
u.bind(Socket::INADDR_ANY, 3333)

loop {
  data, sender = u.recvfrom(4096)
  u.send(data, 0, sender[3], sender[1])
}
'

この echo server は UDP の 3333番ポートでパケットを待ち、パケットが来たら送信元に送り返す。

INADDR_ANY を指定しているので、このホストが持っている任意の IPv4 アドレスに届いたパケットを受け取れる。

このホストは 192.168.0.128 と 127.0.0.1 というふたつのアドレスを持っている。まぁ、普通である。本当に面白いのは外につながっているインターフェースが複数ある multi home な場合だが、ホスト内で考えればこのホストも multi home といえなくもない。(いや、いわないか...) まぁ 127.0.0.1 は外からはつながらないので、ここでは server/client ともに同一ホストで動作させることにしよう。

echo client はたとえば以下のように動く。

% ./ruby -rsocket -e '
u = UDPSocket.new
u.send "a", 0, "127.0.0.1", 3333
p u.recvfrom(4096)
'
["a", ["AF_INET", 3333, "localhost", "127.0.0.1"]]

ここで、127.0.0.1:3333 に送って、127.0.0.1:3333 から返ってきている。これは期待された動作である。

192.168.0.128:3333 に送ることもできる。

% ./ruby -rsocket -e '
u = UDPSocket.new
u.send "a", 0, "192.168.0.128", 3333
p u.recvfrom(4096)
'
["a", ["AF_INET", 3333, "nute.local", "192.168.0.128"]]

この場合は、192.168.0.128:3333 から返ってきて、やはり送ったところから返ってくるので期待された動作になっている。

しかし、以下のようにするとうまくいかない。

% ./ruby -rsocket -e '
u = UDPSocket.new
u.bind "192.168.0.128", 2222
u.send "a", 0, "127.0.0.1", 3333
p u.recvfrom(4096)
'
["a", ["AF_INET", 3333, "nute.local", "192.168.0.128"]]

ここでは、192.168.0.128:2222 に bind しておいて、127.0.0.1:3333 に送っている。すると、192.168.0.128:3333 から返事が返ってくる。つまり、送ったところから返って来ない。返事は違うところから返ってくる。

アプリケーションが返事がどこから返ってくるか検査して、変なところからきたのを落としていたりすると、返事が落とされてしまう。

というわけでサーバでは届いたところから返事を出したい。

それには問題が 2つある。

ではどうしたらいいかというと、IPv6 用には RFC 3542 でやりかたが示されている。setsockopt で IPV6_PKTINFO というのを有効にしておくと、recvmsg で返ってくる補助データに宛先が入って来る。そして、sendmsg にそのデータをそのままつけておけば、その宛先が送信元として使われるというものである。

残念なことに、IPv4 では標準化された方法は見当たらない。だが、GNU/Linux では IPv6 と同様な IP_PKTINFO があって、これが使える。

これを使って UDP echo server を書いてみよう。

% ./ruby -rsocket -e '
u = UDPSocket.new
u.bind(Socket::INADDR_ANY, 4444)
u.setsockopt(:IP, :PKTINFO, 1)
loop {
  data, ctls, rflags, sender = u.recvmsg
  u.sendmsg data, ctls, 0, sender
}
'

送ってみる。

% ./ruby -rsocket -e '
u = UDPSocket.new
u.bind "192.168.0.128", 2222
u.send "a", 0, "127.0.0.1", 4444
p u.recvfrom(4096)
'
["a", ["AF_INET", 4444, "localhost", "127.0.0.1"]]

今度はうまくいった。つまり、192.168.0.128:2222 に bind したソケットから 127.0.0.1:4444 に送っても、送った場所の 127.0.0.1:4444 から返事が返ってきている。

2009-01-05 (Mon)

#1

FreeBSD では、IP_RECVDSTADDR で受け取ったときの宛先を得て、IP_SENDSRCADDR で指定すればできるようだ。(IP_PKTINFO はない)

% ./ruby -rsocket -e '
u = UDPSocket.new
u.bind(Socket::INADDR_ANY, 4444)
u.setsockopt(:IP, :RECVDSTADDR, 1)
loop {
  data, sender, rflags, srcaddr = u.recvmsg
  u.sendmsg data, 0, sender, [:IP, :SENDSRCADDR, srcaddr[2]]
}
'

(sendmsg/recvmsg の引数・返り値の順序は send/recvfrom の拡張になるようちょっと変えた)

#2

FreeBSD の IP_SENDSRCADDR は 2002 年になって実装が出てきたもののようだ。(残念なことに) 古いものではない。

それに対し、IP_RECVDSTADDR は BSD 4.4 Lite の時点で存在する。

cmsg を使った機能だから、導入されたのは早くても 4.3BSD Reno である。[RFC3542]

#3

やっぱ getifaddrs で得たアドレス毎に別々にソケットを作るのがましかなぁ。

2009-01-06 (Tue)

#1

ネットワークを抜いて、

% ./ruby -rsocket -ve 'p Socket.sockaddr_in(80, "www.ruby-lang.org")'
ruby 1.9.1 (2009-01-04 patchlevel-5000 trunk 21315) [i686-linux]
^C^C^C^C

死なねー。サイテー

1.8 も同様。

しかし調べてみると、なんか厄介である。

まず、Socket.sockaddr_in の中身では getaddrinfo を使っている。

ruby 固有の話は気にしないことにして、まず SUSv3 的に getaddrinfo を安全に中断できるか、というのを考えよう。

あー、なんですかねこれは。どうしようもない?

かすかな光明としては以下が判明した

しかし前者はどうやったら使われるのか不明

後者は、スレッドを新しく作ってその中でやるのかなぁ

2009-01-09 (Fri)

#1

ふむ。次の POSIX では getaddrinfo/getnameinfo を cancelation point にしてもいい、ってことになるかんじ?

やはり cancel が POSIX 道か。

実際に試して見ると、Debian GNU/Linux (etch), OpenBSD 4,4, NetBSD 4.0_RC5 では cancel できるようだ。(現在の規格からいえば、この挙動は違反であり、それは次の規格で解消される)

だが、FreeBSD だと (6.3-RC1 という古くて中途半端な奴だが) では cancel できないようだ。(現在の規格でも、次の規格でも、この挙動は規格に反していない)

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>

void *start(void *arg)
{
  struct addrinfo *res;
  int err;
  struct addrinfo hints;

  memset(&hints, 0, sizeof(hints));
  hints.ai_socktype = SOCK_STREAM;

  fprintf(stderr, "getaddrinfo start\n");
  err = getaddrinfo("www.example.org", "80", &hints, &res);
  if (err) {
      fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(err));
      exit(1);
  }
  else {
      fprintf(stderr, "getaddrinfo finished\n");
  }
  return NULL;
}

int main(int argc, char *argv[])
{
  pthread_t th;
  int err;

  err = pthread_create(&th, NULL, start, NULL);
  if (err) {
      fprintf(stderr, "pthread_create: %s\n", strerror(err));
      exit(1);
  }

  sleep(1);

  err = pthread_cancel(th);
  if (err) {
      fprintf(stderr, "pthread_cancel: %s\n", strerror(err));
      exit(1);
  }
  fprintf(stderr, "pthread_cancel finished\n");

  err = pthread_join(th, NULL);
  if (err) {
    fprintf(stderr, "pthread_join: %s\n", strerror(err));
    exit(1);
  }

  return 0;
}

2009-01-12 (Mon)

#1

おっと、IPV6_PKTINFO は setsockopt には使わないように変わってるのか。

RFC 3542 では、setsockopt に IPV6_RECVPKTINFO を使い、recvmsg では IPV6_PKTINFO で返ってくるのだな。

だからこうしないといけない。

% ./ruby -rsocket -rpp -e '       
s = Socket.new(Socket::AF_INET6, Socket::SOCK_DGRAM, 0)
s.bind(AddrInfo.udp("::1", 8888))
s.setsockopt(:IPV6, :RECVPKTINFO, 1)
pp s.recvmsg
'
["a",
 #<AddrInfo: [::1]:51887 UDP>,
 0,
 #<Socket::AncillaryData: IPV6 PKTINFO "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x04\x00\x00\x00">]
#2

が、Debian GNU/Linux (etch) では Socket::IPV6_RECVPKTINFO が定義されない。

んー。IPV6_RECVPKTINFO の定義は /usr/include/linux/in6.h にしかなくて、include してないな。

sid や lenny だと /usr/include/bits/in.h にもあって問題ない。

2009-01-13 (Tue)

#1

いや、::1 に bind したら宛先が ::1 なのは当り前ではないか。bind は :: にしないとね。

% ./ruby -rsocket -rpp -e '
s = Socket.new(Socket::AF_INET6, Socket::SOCK_DGRAM, 0)
s.bind(AddrInfo.udp("::", 8888))
s.setsockopt(:IPV6, :RECVPKTINFO, 1)
loop { pp s.recvmsg }'
["a",
 #<AddrInfo: [::1]:41120 UDP>,
 128,
 #<Socket::AncillaryData: IPV6 PKTINFO ::1 lo0>]
["a",
 #<AddrInfo: [fe80::1%lo0]:4742 UDP>,
 128,
 #<Socket::AncillaryData: IPV6 PKTINFO fe80::1 lo0>]

2009-01-14 (Wed)

#1

IPV6_PKTINFO で得られるデータは宛先アドレスとインターフェースである。

struct in6_pktinfo {
  struct in6_addr ipi6_addr;    /* src/dst IPv6 address */
  unsigned int    ipi6_ifindex; /* send/recv interface index */
};

それに対し GNU/Linux にある (IPv4 用の) IP_PKTINFO で得られるデータはというと、

struct in_pktinfo {
    unsigned int   ipi_ifindex;  /* Interface index */
    struct in_addr ipi_spec_dst; /* Local address */
    struct in_addr ipi_addr;     /* Header Destination
                                    address */
};

というもので、インターフェースとアドレスの組合せというのはそうなのだが、アドレスがふたつある。

ちょっと観察する限りではこれらのアドレスは同じものになるのだが、異なるものになるのはどういう状況か?

#2

<URL:http://osdir.com/ml/java.openjdk.net.devel/2008-02/msg00010.html> によれば、broadcast で起こるらしい

#3

ntp で、アドレスごとにソケットを作るのでなく IP_PKTINFO を使ってほしいというリクエストが以前あったようだ。

<URL:http://lists.ntp.isc.org/pipermail/bugs/2004-June/001256.html>

そうすれば、アドレス (インターフェース) が動的に増えたり減ったりするのを扱える、というのが意図らしい。

もちろん反論としてポータビリティの話もあったが、ひとつ面白かった話として、個々に作っておけば使わないと決めたアドレスへのパケットをカーネルが弾いてくれるというのがあった。

2009-01-15 (Thu)

#1

broadcast を試してみる。

クライアントから

client% ./ruby -rsocket -e '     
s = Socket.new(:INET, :DGRAM, 0)
s.setsockopt(:SOCKET, :BROADCAST, 1)
s.send "a", 0, AddrInfo.udp("192.168.0.255", 8888)'

というように送るのを、サーバで

server% ./ruby -rsocket -rpp -e '
AddrInfo.udp("0.0.0.0", 8888).bind {|s|
  s.setsockopt(:IP, :PKTINFO, 1)
  loop { pp s.recvmsg }
}'
["a",
 #<AddrInfo: 192.168.0.128:32771 UDP>,
 0,
 #<Socket::AncillaryData: IP PKTINFO spec_dst:192.168.0.130 addr:192.168.0.255 eth0>]

というように待ち構えて表示するとたしかに spec_dst と addr が違う。

まぁ、マニュアルに書いてあるとおりではあるが、addr はパケットに書いてある宛先でこの場合 broadcast adress になっている。それに対し、spec_dst は受け取ったインターフェースについているアドレスである。

#2

multicast も試してみる。

client% ./ruby -rsocket -e '
AddrInfo.udp("239.255.99.81", 8887).connect {|s|
  s.send "a", 0
}'

というクライアントから、

server% ./ruby -rsocket -rpp -e '
AddrInfo.udp("0.0.0.0", 8887).bind {|s|
  s.setsockopt(:IP, :ADD_MEMBERSHIP, [239,255,99,81, 192,168,0,130, 2].pack("CCCCCCCCI!"))
  s.setsockopt(:IP, :PKTINFO, 1)
  loop { pp s.recvmsg }
}'
["a",
 #<AddrInfo: 192.168.0.128:32773 UDP>,
 0,
 #<Socket::AncillaryData: IP PKTINFO spec_dst:192.168.0.130 addr:239.255.99.81 eth0>]

というようにサーバへ届くと、addr は client で指定した multicast address になっている。

2009-01-16 (Fri)

#1

IPv6 の multicast も試してみる。

client% ./ruby -rsocket -e '
AddrInfo.udp("ff12::1%eth0", 8888).connect {|s|
  s.send "a", 0
}'

というクライアントから、

server% ./ruby -rsocket -rpp -e '
AddrInfo.udp("::", 8888).bind {|s|
  s.setsockopt(:IPV6, :RECVPKTINFO, 1)
  s.setsockopt(:IPV6, :ADD_MEMBERSHIP, [0xff12,0,0,0,0,0,0,1, 2].pack("nnnnnnnnI!"))
  loop { pp s.recvmsg }
}'
["a",
 #<AddrInfo: [fe80::20b:97ff:fe2b:8539%eth0]:1029 UDP>,
 0,
 #<Socket::AncillaryData: IPV6 PKTINFO ff12::1 eth0>]

というようにサーバへ届くと、multicast address と受け取ったインターフェースが得られる。

(RFC 4291 を読むと、ff12::/16 は、link-local で non-permanently-assigned な multicast addresss なようなのでそこのを使ってみた)

#2

考えてみると、UDP パケットを受け取るというのは、あるフォーマットにしたがったデータが、あるネットワークインターフェースに到着するという事象である。

その事象の中で、recv で知ることができるのは、データ中のヘッダを除いたデータ (ペイロードっていうんだっけ?) である。ヘッダやインターフェースを知ることはできない。

recvfrom を使えば、ヘッダの中の送信元を知ることはできる。だが、その他の部分は分からない。

IPV6_RECVPKTINFO を使えば、ヘッダの中の宛先とインターフェースを知ることができる。

IP_PKTINFO の addr と ifindex も同じである。

では、IP_PKTINFO の spec_dst は何かというと、それはマシンが受け取る前には存在しなかったものである。受け取った後に、マシンの中でなんか処理をして生成されるものであろう。そうすると、ちょっと付加的な感じがする。

#3

GNU/Linux の ip(7) の IP_PKTINFO のところを読み直してみる

IP_PKTINFO
       Pass an IP_PKTINFO ancillary message  that  contains  a  pktinfo
       structure  that  supplies  some  information  about the incoming
       packet.  This only works for  datagram  oriented  sockets.   The
       argument  is a flag that tells the socket whether the IP_PKTINFO
       message should be passed or not.  The message itself can only be
       sent/retrieved as control message with a packet using recvmsg(2)
       or sendmsg(2).

         struct in_pktinfo {
             unsigned int   ipi_ifindex;  /* Interface index */
             struct in_addr ipi_spec_dst; /* Local address */
             struct in_addr ipi_addr;     /* Header Destination
                                             address */
         };

ここまでは書いてあるとおりで疑問はない

ipi_ifindex is the unique index of the interface the packet was
received on.

ipi_ifindex も、受け取ったときの挙動に疑問はない

ipi_spec_dst is the local address of the packet and ipi_addr is
the destination address in the packet header.

ipi_addr の挙動に疑問はない。だが ipi_spec_dst が the local address of the packet というのはあまりはっきりしない。

If IP_PKTINFO is passed to sendmsg(2) and ipi_spec_dst is not zero,
then it is used as the local source address for the routing table lookup and
for setting up IP source route options.

この文からは送るときの話である。ipi_spec_dst は送るときに使われるようだ。

When ipi_ifindex is not zero the primary local address of the
interface specified by the index overwrites ipi_spec_dst for the routing table lookup.

ipi_ifindex も使われるようだ。

ipi_addr の扱いは書いてないがきっとヘッダに (送信元として) 載るのであろう。あまりに明らか過ぎて書いてないのかも。

#4

む、GNU/Linux の ip(7) に載っている IPV6_ADD_MEMBERSHIP は RFC 2133 にはあるが、RFC 2553, 3493 では IPV6_JOIN_GROUP に変わっているのか。

ということは、こうだな。

client% ./ruby -rsocket -e '
AddrInfo.udp("ff12::1%eth0", 8888).connect {|s|
  s.send "a", 0
}'

というようにクライアントから送って

server% ./ruby -rsocket -rpp -e '
AddrInfo.udp("::", 8888).bind {|s|
  s.setsockopt(:IPV6, :RECVPKTINFO, 1)
  s.setsockopt(:IPV6, :JOIN_GROUP, [0xff12,0,0,0,0,0,0,1, 2].pack("nnnnnnnnI!")) 
  loop { pp s.recvmsg }
}'
["a",
 #<AddrInfo: [fe80::20b:97ff:fe2b:8539%eth0]:1029 UDP>,
 0,
 #<Socket::AncillaryData: IPV6 PKTINFO ff12::1 eth0>]

というようにサーバで受け取る。

2009-01-17 (Sat)

#1

マシンの各 IP アドレスに bind したソケットを用意した場合、パケットを受け取ったときにわかるのはどのソケットから受け取ったか、ということであり、IP アドレスが判明するというのは、ソケットと IP アドレスが (bind により) 対応しているためである。

とすると、ヘッダに載っているアドレスが分かるというよりは、受け取ったところについていたアドレスがわかるという、spec_dst っぽい感じがする。

では、broadcast や multicast で何が起きるか?

multicast はそもそも狙って受け取らないといけないので受け取る気がない、一般の UDP サーバには関係ない。

broadcast は... えーと、bind すると受け取れない感じ? まぁ、受け取れなくてぜんぜん問題ないと思うけれど。

#2

結局、IPv4 は IP アドレスごとに bind したソケットを使って、IPv6 は IPV6_PKTINFO を使うのがいいような気がする。

では、そのような実装の詳細を隠蔽した UDP サーバライブラリの API はどうなるか。

受け取ったパケットをライブラリからアプリケーションに渡すときに、どこに到着したかという情報も渡さなければならない。その情報は、IPv4 では受け取ったソケットであり、IPv6 では struct in6_pktinfo である。だが、アプリケーションはそんな中身は気にしたくない。やりたいのは単に受け取ったところから返事をしたいというだけである。

とすると、

Socket.udp_server_loop(host, port) {|msg, client_info|
  client_info.send "reply"
}

あたりかな。

client_info に、ソケットなり、struct in6_pktinfo なりをいれておくわけだ。

#3

おぉ、OpenBSD は IPV6_V6ONLY は常に有効で、無効にできなくしてあるのか。すばらしい。[ip6(4)]

2009-01-18 (Sun)

#1

IPV6_V6ONLY が常に有効であることがなんですばらしいかというと、API デザインにおける制約となるからである。(セキュリティの話もないというわけではないが)

ポータビリティと OpenBSD とプロトコル非依存性を考えると、IPV6_V6ONLY を無効にしてソケットひとつで済ますという可能性が排除される。

なお、Windows XP でも IPV6_V6ONLY は常に有効らしい。これも同じ都合ですばらしい。

2009-01-19 (Mon)

#1

listen の backlog というのはよくわからない引数である。

まぁ、キューの長さであるという説明はよくあるが、いまひとつふにおちない。

というわけで、試してみよう。

まず、サーバを用意する。

TCP で接続を受け付けて、送られてきたデータを読み捨てるだけのサーバである。ただし、並行処理はせず、ひとつの接続からデータを読み終わるまでは次の接続は accept しない。

listen の引数の backlog は 30 にしてある。

server% ruby -rsocket -e '
AddrInfo.tcp("0.0.0.0", 8888).bind {|s|
  s.listen(30)
  loop {
    t, addr = s.accept
    begin
      t.read
    rescue Errno::ECONNRESET
      puts $!
    end
    t.close
  }
}'

これに対して、クライアントは並行にやる。

0.1秒間隔でスレッドを起動し、各スレッドはサーバにつないで 3秒接続を保持した後に終了する。で、connect, sleep, close のプログラム開始時からの相対時刻を測る。

client% ruby -rsocket -e '
ts = []
t0 = Time.now
100.times {|i|
  ts << Thread.new {
    begin
      t1 = Time.now - t0
      AddrInfo.tcp("127.0.0.1", 8888).connect {|s|
        t2 = Time.now - t0
        sleep 3
        t3 = Time.now - t0
        s.close
        t4 = Time.now - t0
        line = " " * t4.ceil
        line[t1.ceil, (t2-t1).ceil] = "c"*(t2-t1).ceil
        line[t2.ceil, (t3-t2).ceil] = "s"*(t3-t2).ceil
        line[t4.ceil, (t4-t3).ceil] = "x"*(t4-t3).ceil
        printf "%02d %s\n", i, line
      }
    rescue
      p [i, $!]
    end
  }
  sleep 0.1
}
ts.each {|t| t.join }
'
00  sssx
01  sssx
02  sssx
03  sssx
04  sssx
05  sssx
06  sssx
07  sssx
08  sssx
09  sssx
10   sssx
11   sssx
12   sssx
13   sssx
14   sssx
15   sssx
16   sssx
17   sssx
18   sssx
19   sssx
20    sssx
21    sssx
22    sssx
23    sssx
24    sssx
25    sssx
26    sssx
27    sssx
28    sssx
29     sssx
30     sssx
31     sssx
32     sssx
33     sssx
34     sssx
35     sssx
36     sssx
37     sssx
38     sssx
39      sssx
40      sssx
41      sssx
42      sssx
43      sssx
44      sssx
45      sssx
46      sssx
47      sssx
48      sssx
49       sssx
50       sssx
51       sssx
52       sssx
53       sssx
54       sssx
55       sssx
56       sssx
57       sssx
58        sssx
59        sssx
60        sssx
61        sssx
62        sssx
63        sssx
64        sssx
65        sssx
66        sssx
67        sssx
68         sssx
69         sssx
70         sssx
71         sssx
72         sssx
73         sssx
74         sssx
75         sssx
76         sssx
77          sssx
78          sssx
79          sssx
80          sssx
81          sssx
82          sssx
83          sssx
84          sssx
85          sssx
86          sssx
87           sssx
88           sssx
89           sssx
90           sssx
91           sssx
92           sssx
93           sssx
94           sssx
95           sssx
96           sssx
97            sssx
98            sssx
99            sssx

結果の、先頭の数値は何番目に開始したスレッドかを示しており、結果を表示するのはスレッドが終わるときである。00 から 99 まで順に表示されているので、スレッドが始まった順番に終わったことを意味している。

s と x は sleep と close をやっていたタイミングである。1文字1秒。(ceil しているので、一瞬で終わっても 1文字にはなる)

connect のタイミングは c だが、この例では s に書きつぶされて出ていない。

ところで、この結果を見ると、クライアント側からはサーバが並行動作しているように見える。クライアントはいくつものスレッドが同時に接続を開いて sleep している。まぁ、アプリケーションが accept しなくても、接続を確立するところまではカーネルがやってくれるのであろう。(GNU/Linux)

さて、backlog を 10 にすると、結果は次のようになる。

00  sssx
01  sssx
02  sssx
03  sssx
04  sssx
05  sssx
06  sssx
07  sssx
08  sssx
09  sssx
10   sssx
11   sssx
12   sssx
13   sssx
29     sssx
30     sssx
31     sssx
32     sssx
33     sssx
34     sssx
35     sssx
36     sssx
37     sssx
38     sssx
39      sssx
40      sssx
20    cccsssx
24    cccsssx
58        sssx
59        sssx
60        sssx
61        sssx
62        sssx
63        sssx
64        sssx
65        sssx
66        sssx
67        sssx
68         sssx
69         sssx
87           sssx
88           sssx
89           sssx
90           sssx
91           sssx
92           sssx
93           sssx
94           sssx
95           sssx
96           sssx
97            sssx
98            sssx
70         cccsssx
99            sssx
41      cccccccccsssx
42      cccccccccsssx
43      cccccccccsssx
44      cccccccccsssx
45      cccccccccsssx
46      cccccccccsssx
47      cccccccccsssx
48      cccccccccsssx
49       cccccccccsssx
50       cccccccccsssx
51       cccccccccsssx
52       cccccccccsssx
71         cccccccccsssx
72         cccccccccsssx
73         cccccccccsssx
74         cccccccccsssx
75         cccccccccsssx
76         cccccccccsssx
77          cccccccccsssx
78          cccccccccsssx
79          cccccccccsssx
80          cccccccccsssx
81          cccccccccsssx
82          cccccccccsssx
14   cccccccccccccccccccccsssx
15   cccccccccccccccccccccsssx
16   cccccccccccccccccccccsssx
17   cccccccccccccccccccccsssx
18   cccccccccccccccccccccsssx
19   cccccccccccccccccccccsssx
21    cccccccccccccccccccccsssx
22    cccccccccccccccccccccsssx
23    cccccccccccccccccccccsssx
25    cccccccccccccccccccccsssx
26    cccccccccccccccccccccsssx
27    cccccccccccccccccccccsssx
53       cccccccccccccccccccccsssx
54       cccccccccccccccccccccsssx
55       cccccccccccccccccccccsssx
56       cccccccccccccccccccccsssx
57       cccccccccccccccccccccsssx
83          cccccccccccccccccccccsssx
84          cccccccccccccccccccccsssx
85          cccccccccccccccccccccsssx
86          cccccccccccccccccccccsssx
28    cccccccccccccccccccccccccccccccccccccccccccccsssx

今度は、後ろにいくにしたがって connect にかかる時間が増えていき、また、スレッドが終わる順番も変わっている。

スレッドが始まった順番にソートしてみよう。

00  sssx
01  sssx
02  sssx
03  sssx
04  sssx
05  sssx
06  sssx
07  sssx
08  sssx
09  sssx
10   sssx
11   sssx
12   sssx
13   sssx
14   cccccccccccccccccccccsssx
15   cccccccccccccccccccccsssx
16   cccccccccccccccccccccsssx
17   cccccccccccccccccccccsssx
18   cccccccccccccccccccccsssx
19   cccccccccccccccccccccsssx
20    cccsssx
21    cccccccccccccccccccccsssx
22    cccccccccccccccccccccsssx
23    cccccccccccccccccccccsssx
24    cccsssx
25    cccccccccccccccccccccsssx
26    cccccccccccccccccccccsssx
27    cccccccccccccccccccccsssx
28    cccccccccccccccccccccccccccccccccccccccccccccsssx
29     sssx
30     sssx
31     sssx
32     sssx
33     sssx
34     sssx
35     sssx
36     sssx
37     sssx
38     sssx
39      sssx
40      sssx
41      cccccccccsssx
42      cccccccccsssx
43      cccccccccsssx
44      cccccccccsssx
45      cccccccccsssx
46      cccccccccsssx
47      cccccccccsssx
48      cccccccccsssx
49       cccccccccsssx
50       cccccccccsssx
51       cccccccccsssx
52       cccccccccsssx
53       cccccccccccccccccccccsssx
54       cccccccccccccccccccccsssx
55       cccccccccccccccccccccsssx
56       cccccccccccccccccccccsssx
57       cccccccccccccccccccccsssx
58        sssx
59        sssx
60        sssx
61        sssx
62        sssx
63        sssx
64        sssx
65        sssx
66        sssx
67        sssx
68         sssx
69         sssx
70         cccsssx
71         cccccccccsssx
72         cccccccccsssx
73         cccccccccsssx
74         cccccccccsssx
75         cccccccccsssx
76         cccccccccsssx
77          cccccccccsssx
78          cccccccccsssx
79          cccccccccsssx
80          cccccccccsssx
81          cccccccccsssx
82          cccccccccsssx
83          cccccccccccccccccccccsssx
84          cccccccccccccccccccccsssx
85          cccccccccccccccccccccsssx
86          cccccccccccccccccccccsssx
87           sssx
88           sssx
89           sssx
90           sssx
91           sssx
92           sssx
93           sssx
94           sssx
95           sssx
96           sssx
97            sssx
98            sssx
99            sssx

これをみると、最初のうちはすぐに connect は終わるが、接続が増えてくると、connect に時間がかかるようになる。

おそらく、これが「キューが溢れた」状況なのであろう。

この結果では、その状態からしばらく経過すると、またすぐに connect が終わるようになる。

これは、3秒経過すると、接続中のクライアントが (sleep が終わって) 終了するので、キューに入っているのが accept され、キューに空きができるからであろう。キューに空きがあれば、connect はすぐに終了する。その時に、どの接続要求がキューに入るかは connect を始めた時刻とは関係無く、これが処理の順序が崩れる理由だろう。

2009-01-20 (Tue)

#1

プロトコル非依存な UDP client というのはどう書くべきか。

えーと、一般的なことはいえない? アプリケーションによる?

TCP client は名前解決していくつかのアドレスが出てきたら順に connect して成功するまで試す。

だが、UDP client は connect に成功したからといって、相手と通信できることが確認できたわけではない。connect するとローカルなルーティングテーブルの段階で宛先がない場合は失敗するようだが、通信経路や、相手のサーバの IPv4/IPv6 のどちらでサービスがおこなわれているかというのはわからない。

これを調べるにはなにか送って返事が返ってくるか確かめるしかないが、送るものというのはアプリケーションに依存する。なので、ライブラリにホスト名とポート番号を与えると、ソケットがアプリケーションに返される、という API はうまくいかない。

ライブラリにするとしたら、もっと違う形態の API が必要である。

2009-01-21 (Wed)

#1

6to4 なるものであれば、IPv4 グローバルアドレスさえあれば、とくになにか登録とかする必要もなく IPv6 でつなげるということを知って、試してみる。

<URL:http://tldp.org/HOWTO/Linux+IPv6-HOWTO/configuring-ipv6to4-tunnels.html>

まず b-mobile でつないで IPv4 アドレスを得て、それで IPv6 につないでみた。

とりあえず kame は踊るのは見れて、ipv6.google.com にもつながった。

まぁ、ちょっと実験するにはいいかな。

2009-01-23 (Fri)

#1

風邪

2009-01-25 (Sun)

#1

UDP を使うサービスとしては NTP, DNS, NFS あたりがすぐに思い浮かぶ。DHCP もあるが、DHCP サーバを選択するのに悩むということはないはずなのでここでは考えない。

NTP, DNS は基本的にクライアントからのリクエストでサーバの情報は変わらない (と思う)。したがって、クライアントからのリクエストがどこかに消えても、大きな害はない。クライアントが再挑戦すればいいだけのことである。クライアントからすれば、複数のリクエストを同時に送るという戦略もありうる。

NFS はファイルシステムという情報がサーバ側に保持されていて、それがクライアントからのリクエストで書き換えられる。したがって、リクエストやリプライが消えるとクライアントとしては書き換えが起こったのかどうかわからなくて悩ましい。

ちょっと RFC 1094 (NFS) を覗いてみると、リカバリを単純にするためには操作を可能な限り idempotent にするのが基本だと書いてある。それはたしかにそうである。操作が idempotent であれば、操作を 2回以上行ってしまっても 1回だけ行ったのと同じ結果が得られるので、安心してリクエストを再挑戦できる。

idempotent でない操作については、サーバが結果をちょっとキャッシュしておいて、同じリクエストに対して前回のリプライを返せばだいたいの問題は解決できる、とある。

idempotent でないかもしれない操作というのは、Remove File, Rename File, Create Link to File, Create Directory, Remove Directory があげられている。

まぁ、たしかにそれなりに解決できる気はする。

もちろん、解決できない問題はあって、即座に排他制御が思い浮かぶ。Create Directory がそういう挙動になってると、mkdir は排他制御に使えない。

さて、UDP のライブラリとしては、まずは簡単でお薦めなところから支援すべきである。とすると、idempotent なやつから始めるべきだろう。

idempotent というのは NFS だけじゃなくて、NTP, DNS で何回リクエストを送っても問題ないというのも idempotent な性質であるし。(サーバからすると、0回でも結果は同じというところは idempotent よりも強いが)

idempotent とすると、複数のアドレスにいっぺんに送るとか、適当な間隔で順に送るとかといった API が想定できる。

#2

<URL:http://en.wikipedia.org/wiki/User_Datagram_Protocol>

あぁ、UDP はストリーム配信でも使うか。これはまた要件が違うな。ひとつパケットが届かなかったからといってやりなおすことはないだろうし。

あとオンラインゲーム?

2009-01-26 (Mon)

#1

NFS の mkdir について検索してみた

2009-01-27 (Tue)

#1

「東あずま」という駅があることを知って、漢字にすると「東東」になるのだろうか、と思ったが、そうではないらしい。

2009-01-28 (Wed)

#1

How Projects Really Work という有名な cartoon があるが、バージョンアップしているようだ。

<URL:http://www.projectcartoon.com/gallery/>

しかし、最初のくらいでちょうどよかった気もする。

2009-01-31 (Sat)

#1

CSS で max-width: 30em としてみる。

一行の中の文字数が多すぎると次の行に視線を移すのが難しくなるという理屈には説得力がある。

まぁ、w3m には効かないが...

#2

そういや、max-width: で文字数を指定して行長を制約するのってエラスティックレイアウトっていうんだよね、と思い出して検索してみると、一般に em で指定するものをエラスティックレイアウトと呼んでいるのが散見されるな。

たとえば「エラスティックレイアウトって何?:ITpro」とか。

#3

RFC 5014 は struct addrinfo の要素を増やすのか。

そんなことして ABI は大丈夫なのか、と思って検索すると、やはり懸念が示されている。

Re: Revised address selection preference API draft-chakrabarti-ipv6-addrselect-api-04

スレッドを読むと反論はされているが、微妙。

#4

そういや Java はどうしてんのかな、と思って探すと、ネットワーク IPv6 ユーザーガイド (JDK/JRE 5.0) というのが見つかった。

IPv4/IPv6 の違いをちゃんと隠蔽しているらしい。えらい。

だが、UDP クライアントをどうしてるのかは、読んでもはっきりしない。


[latest]


田中哲