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
実装して気がついたのだが、recvmsg を使うと、パケットの長さを事前に知らなくてもどうにかなるな。
MSG_PEEK で試して、MSG_TRUNC/MSG_CTRUNC が出なくなるまでバッファを大きくしていけばいい。
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 側で可能であってもおかしくはないのだが。
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 から返事が返ってきている。
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 の拡張になるようちょっと変えた)
FreeBSD の IP_SENDSRCADDR は 2002 年になって実装が出てきたもののようだ。(残念なことに) 古いものではない。
それに対し、IP_RECVDSTADDR は BSD 4.4 Lite の時点で存在する。
cmsg を使った機能だから、導入されたのは早くても 4.3BSD Reno である。[RFC3542]
やっぱ getifaddrs で得たアドレス毎に別々にソケットを作るのがましかなぁ。
ネットワークを抜いて、
% ./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 を安全に中断できるか、というのを考えよう。
あー、なんですかねこれは。どうしようもない?
かすかな光明としては以下が判明した
しかし前者はどうやったら使われるのか不明
後者は、スレッドを新しく作ってその中でやるのかなぁ
ふむ。次の 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; }
おっと、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">]
が、Debian GNU/Linux (etch) では Socket::IPV6_RECVPKTINFO が定義されない。
んー。IPV6_RECVPKTINFO の定義は /usr/include/linux/in6.h にしかなくて、include してないな。
sid や lenny だと /usr/include/bits/in.h にもあって問題ない。
いや、::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>]
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 */ };
というもので、インターフェースとアドレスの組合せというのはそうなのだが、アドレスがふたつある。
ちょっと観察する限りではこれらのアドレスは同じものになるのだが、異なるものになるのはどういう状況か?
<URL:http://osdir.com/ml/java.openjdk.net.devel/2008-02/msg00010.html> によれば、broadcast で起こるらしい
ntp で、アドレスごとにソケットを作るのでなく IP_PKTINFO を使ってほしいというリクエストが以前あったようだ。
<URL:http://lists.ntp.isc.org/pipermail/bugs/2004-June/001256.html>
そうすれば、アドレス (インターフェース) が動的に増えたり減ったりするのを扱える、というのが意図らしい。
もちろん反論としてポータビリティの話もあったが、ひとつ面白かった話として、個々に作っておけば使わないと決めたアドレスへのパケットをカーネルが弾いてくれるというのがあった。
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 は受け取ったインターフェースについているアドレスである。
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 になっている。
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 なようなのでそこのを使ってみた)
考えてみると、UDP パケットを受け取るというのは、あるフォーマットにしたがったデータが、あるネットワークインターフェースに到着するという事象である。
その事象の中で、recv で知ることができるのは、データ中のヘッダを除いたデータ (ペイロードっていうんだっけ?) である。ヘッダやインターフェースを知ることはできない。
recvfrom を使えば、ヘッダの中の送信元を知ることはできる。だが、その他の部分は分からない。
IPV6_RECVPKTINFO を使えば、ヘッダの中の宛先とインターフェースを知ることができる。
IP_PKTINFO の addr と ifindex も同じである。
では、IP_PKTINFO の spec_dst は何かというと、それはマシンが受け取る前には存在しなかったものである。受け取った後に、マシンの中でなんか処理をして生成されるものであろう。そうすると、ちょっと付加的な感じがする。
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 の扱いは書いてないがきっとヘッダに (送信元として) 載るのであろう。あまりに明らか過ぎて書いてないのかも。
む、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>]
というようにサーバで受け取る。
マシンの各 IP アドレスに bind したソケットを用意した場合、パケットを受け取ったときにわかるのはどのソケットから受け取ったか、ということであり、IP アドレスが判明するというのは、ソケットと IP アドレスが (bind により) 対応しているためである。
とすると、ヘッダに載っているアドレスが分かるというよりは、受け取ったところについていたアドレスがわかるという、spec_dst っぽい感じがする。
では、broadcast や multicast で何が起きるか?
multicast はそもそも狙って受け取らないといけないので受け取る気がない、一般の UDP サーバには関係ない。
broadcast は... えーと、bind すると受け取れない感じ? まぁ、受け取れなくてぜんぜん問題ないと思うけれど。
結局、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 なりをいれておくわけだ。
おぉ、OpenBSD は IPV6_V6ONLY は常に有効で、無効にできなくしてあるのか。すばらしい。[ip6(4)]
IPV6_V6ONLY が常に有効であることがなんですばらしいかというと、API デザインにおける制約となるからである。(セキュリティの話もないというわけではないが)
ポータビリティと OpenBSD とプロトコル非依存性を考えると、IPV6_V6ONLY を無効にしてソケットひとつで済ますという可能性が排除される。
なお、Windows XP でも IPV6_V6ONLY は常に有効らしい。これも同じ都合ですばらしい。
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 を始めた時刻とは関係無く、これが処理の順序が崩れる理由だろう。
プロトコル非依存な UDP client というのはどう書くべきか。
えーと、一般的なことはいえない? アプリケーションによる?
TCP client は名前解決していくつかのアドレスが出てきたら順に connect して成功するまで試す。
だが、UDP client は connect に成功したからといって、相手と通信できることが確認できたわけではない。connect するとローカルなルーティングテーブルの段階で宛先がない場合は失敗するようだが、通信経路や、相手のサーバの IPv4/IPv6 のどちらでサービスがおこなわれているかというのはわからない。
これを調べるにはなにか送って返事が返ってくるか確かめるしかないが、送るものというのはアプリケーションに依存する。なので、ライブラリにホスト名とポート番号を与えると、ソケットがアプリケーションに返される、という API はうまくいかない。
ライブラリにするとしたら、もっと違う形態の API が必要である。
6to4 なるものであれば、IPv4 グローバルアドレスさえあれば、とくになにか登録とかする必要もなく IPv6 でつなげるということを知って、試してみる。
<URL:http://tldp.org/HOWTO/Linux+IPv6-HOWTO/configuring-ipv6to4-tunnels.html>
まず b-mobile でつないで IPv4 アドレスを得て、それで IPv6 につないでみた。
とりあえず kame は踊るのは見れて、ipv6.google.com にもつながった。
まぁ、ちょっと実験するにはいいかな。
風邪
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 が想定できる。
<URL:http://en.wikipedia.org/wiki/User_Datagram_Protocol>
あぁ、UDP はストリーム配信でも使うか。これはまた要件が違うな。ひとつパケットが届かなかったからといってやりなおすことはないだろうし。
あとオンラインゲーム?
NFS の mkdir について検索してみた
「東あずま」という駅があることを知って、漢字にすると「東東」になるのだろうか、と思ったが、そうではないらしい。
How Projects Really Work という有名な cartoon があるが、バージョンアップしているようだ。
<URL:http://www.projectcartoon.com/gallery/>
しかし、最初のくらいでちょうどよかった気もする。
CSS で max-width: 30em としてみる。
一行の中の文字数が多すぎると次の行に視線を移すのが難しくなるという理屈には説得力がある。
まぁ、w3m には効かないが...
そういや、max-width: で文字数を指定して行長を制約するのってエラスティックレイアウトっていうんだよね、と思い出して検索してみると、一般に em で指定するものをエラスティックレイアウトと呼んでいるのが散見されるな。
たとえば「エラスティックレイアウトって何?:ITpro」とか。
RFC 5014 は struct addrinfo の要素を増やすのか。
そんなことして ABI は大丈夫なのか、と思って検索すると、やはり懸念が示されている。
Re: Revised address selection preference API draft-chakrabarti-ipv6-addrselect-api-04
スレッドを読むと反論はされているが、微妙。
そういや Java はどうしてんのかな、と思って探すと、ネットワーク IPv6 ユーザーガイド (JDK/JRE 5.0) というのが見つかった。
IPv4/IPv6 の違いをちゃんと隠蔽しているらしい。えらい。
だが、UDP クライアントをどうしてるのかは、読んでもはっきりしない。
[latest]