[#37679] [FEATURE:trunk] EncDet again — "Yugui (Yuki Sonoda)" <yugui@...>

Yuguiです。

23 messages 2009/01/03

[#37748] $LOAD_PATHとバージョンの運用の関係 — akira yamada / やまだあきら <akira@...>

1.9系でのバージョンの運用と$LOAD_PATHの値について質問です。

12 messages 2009/01/09
[#37758] Re: $LOAD_PATHとバージョンの運用の関係 — "NARUSE, Yui" <naruse@...> 2009/01/11

成瀬です。

[ruby-dev:37692] AddrInfo

From: Tanaka Akira <akr@...>
Date: 2009-01-04 07:44:20 UTC
List: ruby-dev #37692
提案なのですが、ext/socket で AddrInfo クラスを新設するのはどうでしょうか。

AddrInfo は基本的には getaddrinfo(3) の struct addrinfo と対
応する情報をもつものとします。

なんでこれが欲しいのかという理由はいろいろとありますが、すぐ
に思い浮かぶ理由だけでも以下のようなものがあります。

* Socket.getaddrinfo の返り値がわかりにくい
* Socket#{accept,recvfrom} が返す送信元がわかりにくい
* BasicSocket#{getsockname,getpeername} が返すものがわかりに
  くい
* Socket.sockaddr_in(port, host) は引数の順番が逆で戸惑う
* Socket.getaddrinfo の結果をプロトコル非依存に connect/bind
  できない

まず Socket.getaddrinfo の返り値がわかりにくいという話ですが、
最近の [ruby-dev:37674] で指摘した問題の

% ./ruby -rpp -rsocket -ve 'pp Socket.getaddrinfo("www.ruby-lang.org", 80)'
ruby 1.9.1 (2009-01-01 patchlevel-5000 trunk 21253) [i686-linux]
[["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68", 2, 2, 17]]

という例でいうと、表示しても意味がよくわからない値がいくつか
ついています。しかし、struct addrinfo に対応するクラスがあり、
その inspectが適切に定義されていれば、もっとわかりやすい表示
を行うことができます。AddrInfo.getaddrinfo を定義することに
して、AddrInfo の配列を返すとすれば、以下のように、TCP とか
UDP といったわかりやすい表示をすることが出来ます。

% ./ruby -rpp -rsocket -e 'pp AddrInfo.getaddrinfo("www.ruby-lang.org", 80)'
[#<AddrInfo: 221.186.184.68:80 TCP (www.ruby-lang.org:80)>,
 #<AddrInfo: 221.186.184.68:80 UDP (www.ruby-lang.org:80)>]

また、AddrInfo は struct sockaddr を中に持っているので、
Socket クラスを使うときに必要となる、「ソケットアドレス構造
体を pack した文字列」の代わりに使うことが出来ます。

Socket#{accept,recvfrom} が返す送信元がわかりにくいというの
は、送信元としてソケットアドレス構造体を返すからですが、これ
を AddrInfo に変えることによりわかりやすくすることができます。
なお Socket#{accept_nonblock,recvfrom_nonblock} も同様です。

ただしこの変更は非互換になります。とはいえ、ソケットアドレス
構造体を受け付けるところでは AddrInfo を受け付けるようにして
あるので、それほど大きな問題は起きないことを期待します。また、
AddrInfo#to_sockaddr で AddrInfo からソケットアドレス構造体
を取り出せますので、この非互換性で問題が出たときにはそれが使
えます。

また、BasicSocket#{getsockname,getpeername} もソケットアドレス
構造体を返すのでわかりにくいのですが、
BasicSocket#{local_address,remote_address} というメソッドを
新設してそっちは AddrInfo を返すようにしてみました。
こっちあは新設なので非互換性はありません。また、getsockname
より local_address という名前のほうがわかりやすいと思います。

BasicSocket につけたので TCPSocket にも使えて、例えば、以下
のように動きます。

% ./ruby -rsocket -e '                                                                             
TCPSocket.open("localhost", 80) {|s| p [s.local_address, s.remote_address] }'
[#<AddrInfo: 127.0.0.1:58231 TCP>, #<AddrInfo: 127.0.0.1:80 TCP>]

IPSocket では addr, peeraddr がありますが、これまた順序を覚
えられないという問題があります。

% ./ruby -rsocket -e '
TCPSocket.open("localhost", 80) {|s| p s.addr }'
["AF_INET", 58985, "localhost", "127.0.0.1"]

さらに、addr, peeraddr は第3要素を求めるのに逆引きを行うのが
厄介です。BasicSocket.do_not_reverse_lookup = true で抑制は
出来ますが、AddrInfo#inspect はそもそも逆引きを行いません。
逆引きを行わない代わりに、AddrInfo は生成時に inspect 用の名
前を記録できます。正引きでアドレスを求めた時には、元になった
名前を記録しておくので、
  #<AddrInfo: 221.186.184.68:80 TCP (www.ruby-lang.org:80)>
というように、カッコの中に名前が出てきます。

AddrInfo を作る方法はいくつか用意しましたが、その中で
AddrInfo.tcp(host, port) と AddrInfo.udp(host, port) は
Socket.sockaddr_in(port, host) の引数順のわかりにくさを解決
しています。なお、tcp/udp の区別をしないといけないのは
AddrInfo がソケットアドレス構造体に加えて、ソケットを生成す
るための情報 (SOCK_STREAM/SOCK_DGRAM とか) も持っているから
です。

その情報を持っているので、AddrInfo が与えられれば、ソケット
を生成して connect/bind することができます。

ai = AddrInfo.getaddrinfo("localhost", 80)[0] # 最初のやつを使う (本当は良くない)
sock = Socket.new(ai.pfamily, ai.socktype, ai.protocol)
sock.connect(ai)

これに対し、Socket.getaddrinfo を使うと、プロトコル非依存な
形では記述できません。

ai = Socket.getaddrinfo("localhost", 80)[0]
sock = Socket.new(ai[4], ai[5], ai[6])
sock.connect(Socket.sockaddr_in(ai[1], ai[3]))

ここで、Socket.sockaddr_in を使っているので、これは
IPv4/IPv6 に依存した書き方になっています。

まぁ、実際に getaddrinfo が IPv4/IPv6 以外に使えるかというと
微妙なところですが、C レベルの getaddrinfo(3) で実現できてい
るプロトコル非依存性を Ruby ではいくらか失っているのはたしか
です。

また、先の話としては、
* IPv6 では sendmsg/recvmsg の補助データを活用しているような
  んですが、recvmsg では recvfrom と同様に送信元を返すのでやっ
  ぱり AddrInfo を使いたい
* プロトコル非依存に書きたいとはいってもやっぱりアドレスに対
  する操作はたまに必要になるので AddrInfo#ipv4_private? とか
  操作を加えたい
とか、いろいろ考えられます。

というわけでどうでしょうか。非互換性は、上で述べた
Socket#{accept,recvfrom,accept_nonblock,recvfrom_nonblock}
だけなので、悪くないんじゃないかと思うんですが。

Index: ext/socket/socket.c
===================================================================
--- ext/socket/socket.c	(revision 21278)
+++ ext/socket/socket.c	(working copy)
@@ -88,6 +88,7 @@ VALUE rb_cUNIXSocket;
 VALUE rb_cUNIXServer;
 #endif
 VALUE rb_cSocket;
+static VALUE rb_cAddrInfo;
 
 static VALUE rb_eSocket;
 
@@ -572,6 +573,88 @@ bsock_getpeername(VALUE sock)
     return rb_str_new(buf, len);
 }
 
+static VALUE addrinfo_new(struct sockaddr *, socklen_t, int, int, int, VALUE, VALUE);
+
+static VALUE
+fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len)
+{
+    /* assumes protocol family and address family are identical */
+    int family = addr->sa_family;
+    int socktype;
+    int ret;
+    socklen_t optlen = sizeof(socktype);
+
+    ret = getsockopt(fd, SOL_SOCKET, SO_TYPE, (void*)&socktype, &optlen);
+    if (ret == -1) {
+	rb_sys_fail("getsockopt(SO_TYPE)");
+    }
+
+    return addrinfo_new(addr, len, family, socktype, 0, Qnil, Qnil);
+}
+
+static VALUE
+io_socket_addrinfo(VALUE io, struct sockaddr *addr, socklen_t len)
+{
+    rb_io_t *fptr;
+
+    switch (TYPE(io)) {
+      case T_FIXNUM:
+        return fd_socket_addrinfo(FIX2INT(io), addr, len);
+
+      case T_BIGNUM:
+        return fd_socket_addrinfo(NUM2INT(io), addr, len);
+
+      case T_FILE:
+        GetOpenFile(io, fptr);
+        return fd_socket_addrinfo(fptr->fd, addr, len);
+
+      default:
+	rb_raise(rb_eTypeError, "neither IO nor file descriptor");
+    }
+}
+
+/*
+ * call-seq:
+ *   bsock.local_address => addrinfo
+ *
+ * returns an AddrInfo object for local address obtained by getsockname.
+ *
+ * Note that addrinfo.protocol is filled by 0.
+ */
+static VALUE
+bsock_local_address(VALUE sock)
+{
+    char buf[1024];
+    socklen_t len = sizeof buf;
+    rb_io_t *fptr;
+
+    GetOpenFile(sock, fptr);
+    if (getsockname(fptr->fd, (struct sockaddr*)buf, &len) < 0)
+	rb_sys_fail("getsockname(2)");
+    return fd_socket_addrinfo(fptr->fd, (struct sockaddr *)buf, len);
+}
+
+/*
+ * call-seq:
+ *   bsock.remote_address => addrinfo
+ *
+ * returns an AddrInfo object for remote address obtained by getpeername.
+ *
+ * Note that addrinfo.protocol is filled by 0.
+ */
+static VALUE
+bsock_remote_address(VALUE sock)
+{
+    char buf[1024];
+    socklen_t len = sizeof buf;
+    rb_io_t *fptr;
+
+    GetOpenFile(sock, fptr);
+    if (getpeername(fptr->fd, (struct sockaddr*)buf, &len) < 0)
+	rb_sys_fail("getpeername(2)");
+    return fd_socket_addrinfo(fptr->fd, (struct sockaddr *)buf, len);
+}
+
 struct send_arg {
     int fd, flags;
     VALUE mesg;
@@ -744,7 +827,7 @@ s_recvfrom(VALUE sock, int argc, VALUE *
         return rb_assoc_new(str, unixaddr((struct sockaddr_un*)arg.buf, arg.alen));
 #endif
       case RECV_SOCKET:
-	return rb_assoc_new(str, rb_str_new(arg.buf, arg.alen));
+	return rb_assoc_new(str, io_socket_addrinfo(sock, (struct sockaddr*)arg.buf, arg.alen));
       default:
 	rb_bug("s_recvfrom called with bad value");
     }
@@ -804,7 +887,7 @@ s_recvfrom_nonblock(VALUE sock, int argc
         break;
 
       case RECV_SOCKET:
-        addr = rb_str_new(buf, alen);
+        addr = io_socket_addrinfo(sock, (struct sockaddr*)buf, alen);
         break;
 
       default:
@@ -2971,7 +3054,7 @@ sock_accept(VALUE sock)
     GetOpenFile(sock, fptr);
     sock2 = s_accept(rb_cSocket,fptr->fd,(struct sockaddr*)buf,&len);
 
-    return rb_assoc_new(sock2, rb_str_new(buf, len));
+    return rb_assoc_new(sock2, io_socket_addrinfo(sock2, (struct sockaddr*)buf, len));
 }
 
 /*
@@ -3032,7 +3115,7 @@ sock_accept_nonblock(VALUE sock)
 
     GetOpenFile(sock, fptr);
     sock2 = s_accept_nonblock(rb_cSocket, fptr, (struct sockaddr *)buf, &len);
-    return rb_assoc_new(sock2, rb_str_new(buf, len));
+    return rb_assoc_new(sock2, io_socket_addrinfo(sock2, (struct sockaddr*)buf, len));
 }
 
 /*
@@ -3085,7 +3168,7 @@ sock_sysaccept(VALUE sock)
     GetOpenFile(sock, fptr);
     sock2 = s_accept(0,fptr->fd,(struct sockaddr*)buf,&len);
 
-    return rb_assoc_new(sock2, rb_str_new(buf, len));
+    return rb_assoc_new(sock2, io_socket_addrinfo(sock2, (struct sockaddr*)buf, len));
 }
 
 #ifdef HAVE_GETHOSTNAME
@@ -3516,6 +3599,864 @@ sock_s_unpack_sockaddr_un(VALUE self, VA
 }
 #endif
 
+typedef struct {
+    VALUE inspectname;
+    VALUE canonname;
+    int pfamily;
+    int socktype;
+    int protocol;
+    struct sockaddr_storage addr;
+} rb_addrinfo_t;
+
+static void
+addrinfo_mark(rb_addrinfo_t *rai)
+{
+    if (rai) {
+        rb_gc_mark(rai->inspectname);
+        rb_gc_mark(rai->canonname);
+    }
+}
+
+static void
+addrinfo_free(rb_addrinfo_t *rai)
+{
+    xfree(rai);
+}
+
+static socklen_t
+sockaddr_size(int family)
+{
+    switch (family) {
+      case AF_INET: return sizeof(struct sockaddr_in);
+#ifdef INET6
+      case AF_INET6: return sizeof(struct sockaddr_in6);
+#endif
+#ifdef HAVE_SYS_UN_H
+      case AF_UNIX: return sizeof(struct sockaddr_un);
+#endif
+      default: return sizeof(struct sockaddr);
+    }
+}
+
+static VALUE
+addrinfo_s_allocate(VALUE klass)
+{
+    return Data_Wrap_Struct(klass, addrinfo_mark, addrinfo_free, 0);
+}
+
+#define IS_ADDRINFO(obj) (RDATA(obj)->dmark == (RUBY_DATA_FUNC)addrinfo_mark)
+static rb_addrinfo_t *
+check_addrinfo(VALUE self)
+{
+    Check_Type(self, T_DATA);
+    if (!IS_ADDRINFO(self)) {
+        rb_raise(rb_eTypeError, "wrong argument type %s (expected AddrInfo)",
+                 rb_class2name(CLASS_OF(self)));
+    }
+    return DATA_PTR(self);
+}
+
+static rb_addrinfo_t *
+get_addrinfo(VALUE self)
+{
+    rb_addrinfo_t *rai = check_addrinfo(self);
+
+    if (!rai) {
+        rb_raise(rb_eTypeError, "uninitialized socket address");
+    }
+    return rai;
+}
+
+
+static rb_addrinfo_t *
+alloc_addrinfo()
+{
+    rb_addrinfo_t *rai = ALLOC(rb_addrinfo_t);
+    memset(rai, 0, sizeof(rb_addrinfo_t));
+    rai->inspectname = Qnil;
+    rai->canonname = Qnil;
+    return rai;
+}
+
+static void
+init_addrinfo(rb_addrinfo_t *rai, struct sockaddr *sa, size_t len,
+              int pfamily, int socktype, int protocol,
+              VALUE canonname, VALUE inspectname)
+{
+    if (sizeof(rai->addr) < len)
+        rb_raise(rb_eArgError, "sockaddr string too big");
+    memcpy((void *)&rai->addr, (void *)sa, len);
+
+    rai->pfamily = pfamily;
+    rai->socktype = socktype;
+    rai->protocol = protocol;
+    rai->canonname = canonname;
+    rai->inspectname = inspectname;
+}
+
+static VALUE
+addrinfo_new(struct sockaddr *addr, socklen_t len,
+             int family, int socktype, int protocol,
+             VALUE canonname, VALUE inspectname)
+{
+    VALUE a;
+    rb_addrinfo_t *rai;
+
+    a = addrinfo_s_allocate(rb_cAddrInfo);
+    DATA_PTR(a) = rai = alloc_addrinfo();
+    init_addrinfo(rai, addr, len, family, socktype, protocol, canonname, inspectname);
+    return a;
+}
+
+static struct addrinfo *
+call_getaddrinfo(VALUE node, VALUE service,
+                 VALUE family, VALUE socktype, VALUE protocol, VALUE flags)
+{
+    struct addrinfo hints, *res;
+
+    MEMZERO(&hints, struct addrinfo, 1);
+    hints.ai_family = NIL_P(family) ? PF_UNSPEC : family_arg(family);
+
+    if (!NIL_P(socktype)) {
+	hints.ai_socktype = socktype_arg(socktype);
+    }
+    if (!NIL_P(protocol)) {
+	hints.ai_protocol = NUM2INT(protocol);
+    }
+    if (!NIL_P(flags)) {
+	hints.ai_flags = NUM2INT(flags);
+    }
+    res = sock_getaddrinfo(node, service, &hints, 0);
+
+    if (res == NULL)
+	rb_raise(rb_eSocket, "host not found");
+    return res;
+}
+
+static void
+init_addrinfo_getaddrinfo(rb_addrinfo_t *rai, VALUE node, VALUE service,
+                          VALUE family, VALUE socktype, VALUE protocol, VALUE flags,
+                          VALUE inspectname)
+{
+    struct addrinfo *res = call_getaddrinfo(node, service, family, socktype, protocol, flags);
+    VALUE canonname;
+
+    canonname = Qnil;
+    if (res->ai_canonname) {
+        canonname = rb_tainted_str_new_cstr(res->ai_canonname);
+        OBJ_FREEZE(canonname);
+    }
+
+    init_addrinfo(rai, res->ai_addr, res->ai_addrlen,
+                  NUM2INT(family), NUM2INT(socktype), NUM2INT(protocol),
+                  canonname, inspectname);
+
+    freeaddrinfo(res);
+}
+
+static VALUE
+make_inspectname(VALUE node, VALUE service)
+{
+    VALUE inspectname = Qnil;
+    if (TYPE(node) == T_STRING) {
+        inspectname = rb_str_dup(node);
+    }
+    if (TYPE(service) == T_STRING) {
+        if (NIL_P(inspectname))
+            inspectname = rb_sprintf(":%s", StringValueCStr(service));
+        else
+            rb_str_catf(inspectname, ":%s", StringValueCStr(service));
+    }
+    else if (TYPE(service) == T_FIXNUM && FIX2INT(service) != 0)
+    {
+        if (NIL_P(inspectname))
+            inspectname = rb_sprintf(":%d", FIX2INT(service));
+        else
+            rb_str_catf(inspectname, ":%d", FIX2INT(service));
+    }
+    if (!NIL_P(inspectname)) {
+        OBJ_INFECT(inspectname, node);
+        OBJ_INFECT(inspectname, service);
+        OBJ_FREEZE(inspectname);
+    }
+    return inspectname;
+}
+
+static VALUE
+addrinfo_firstonly_new(VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE protocol, VALUE flags)
+{
+    VALUE ret;
+    VALUE canonname;
+    VALUE inspectname;
+
+    struct addrinfo *res = call_getaddrinfo(node, service, family, socktype, protocol, flags);
+
+    inspectname = make_inspectname(node, service);
+
+    canonname = Qnil;
+    if (res->ai_canonname) {
+        canonname = rb_tainted_str_new_cstr(res->ai_canonname);
+        OBJ_FREEZE(canonname);
+    }
+
+    ret = addrinfo_new(res->ai_addr, res->ai_addrlen,
+                       res->ai_family, res->ai_socktype, res->ai_protocol,
+                       canonname, inspectname);
+
+    freeaddrinfo(res);
+    return ret;
+}
+
+static VALUE
+addrinfo_list_new(VALUE node, VALUE service, VALUE family, VALUE socktype, VALUE protocol, VALUE flags)
+{
+    VALUE ret;
+    struct addrinfo *r;
+    VALUE inspectname;
+
+    struct addrinfo *res = call_getaddrinfo(node, service, family, socktype, protocol, flags);
+
+    inspectname = make_inspectname(node, service);
+
+    ret = rb_ary_new();
+    for (r = res; r; r = r->ai_next) {
+        VALUE addr;
+        VALUE canonname = Qnil;
+
+        if (r->ai_canonname) {
+            canonname = rb_tainted_str_new_cstr(r->ai_canonname);
+            OBJ_FREEZE(canonname);
+        }
+
+        addr = addrinfo_new(r->ai_addr, r->ai_addrlen,
+                            r->ai_family, r->ai_socktype, r->ai_protocol,
+                            canonname, inspectname);
+
+        rb_ary_push(ret, addr);
+    }
+
+    freeaddrinfo(res);
+    return ret;
+}
+
+
+#ifdef HAVE_SYS_UN_H
+static VALUE
+init_unix_addrinfo(rb_addrinfo_t *rai, VALUE path)
+{
+    struct sockaddr_un un;
+    VALUE addr;
+
+    StringValue(path);
+
+    if (sizeof(un.sun_path) <= RSTRING_LEN(path))
+        rb_raise(rb_eArgError, "too long unix socket path (max: %dbytes)",
+            (int)sizeof(un.sun_path)-1);
+
+    MEMZERO(&un, struct sockaddr_un, 1);
+
+    un.sun_family = AF_UNIX;
+    memcpy((void*)&un.sun_path, RSTRING_PTR(path), RSTRING_LEN(path));
+   
+    init_addrinfo(rai, (struct sockaddr *)&un, sizeof(un), AF_UNIX, SOCK_STREAM, 0, Qnil, Qnil);
+
+    return addr;
+}
+#endif
+
+/*
+ * call-seq:
+ *   AddrInfo.new(sockaddr)
+ *   AddrInfo.new(sockaddr, family)
+ *   AddrInfo.new(sockaddr, family, socktype)
+ *   AddrInfo.new(sockaddr, family, socktype, protocol)
+ *
+ * returns a new instance of AddrInfo.
+ * It the instnace contains sockaddr, family, socktype, protocol.
+ * sockaddr means struct sockaddr which can be used for connect(2), etc.
+ * family, socktype and protocol are integers which is used for arguments of socket(2).
+ *
+ * sockaddr is specified as an array or a string.
+ * The array should be compatible to the value of IPSocket#addr or UNIXSocket#addr.
+ * The string should be struct sockaddr as generated by
+ * Socket.sockaddr_in or Socket.unpack_sockaddr_un.
+ *
+ * sockaddr examples:
+ * - ["AF_INET", 46102, "localhost.localdomain", "127.0.0.1"] 
+ * - ["AF_INET6", 42304, "ip6-localhost", "::1"] 
+ * - ["AF_UNIX", "/tmp/sock"] 
+ * - Socket.sockaddr_in("smtp", "2001:DB8::1")
+ * - Socket.sockaddr_in(80, "172.18.22.42")
+ * - Socket.sockaddr_in(80, "www.ruby-lang.org")
+ * - Socket.sockaddr_un("/tmp/sock")
+ *
+ * In an AF_INET/AF_INET6 sockaddr array, the 4th element,
+ * numeric IP address, is used to construct socket address in the AddrInfo instance.
+ * The 3rd element, textual host name, is also recorded but only used for AddrInfo#inspect.
+ *
+ * family is specified as an integer to specify the protocol family such as Socket::PF_INET.
+ * It can be a symbol or a string which is the constant name
+ * with or without PF_ prefix such as :INET, :INET6, :UNIX, "PF_INET", etc.
+ * If ommitted, PF_UNSPEC is assumed.
+ *
+ * socktype is specified as an integer to specify the socket type such as Socket::SOCK_STREAM.
+ * It can be a symbol or a string which is the constant name
+ * with or without SOCK_ prefix such as :STREAM, :DGRAM, :RAW, "SOCK_STREAM", etc.
+ * If ommitted, 0 is assumed.
+ *
+ * protocol is specified as an integer to specify the protocol such as Socket::IPPROTO_TCP.
+ * It must be an integer, unlike family and socktype.
+ * If ommitted, 0 is assumed.
+ * Note that 0 is reasonable value for most protocols, except raw socket.
+ *
+ */
+static VALUE
+addrinfo_initialize(int argc, VALUE *argv, VALUE self)
+{
+    rb_addrinfo_t *rai;
+    VALUE sockaddr_arg, sockaddr_ary, pfamily, socktype, protocol;
+    int i_pfamily, i_socktype, i_protocol;
+    struct sockaddr *sockaddr_ptr;
+    size_t sockaddr_len;
+    VALUE canonname = Qnil, inspectname = Qnil;
+
+    if (check_addrinfo(self))
+        rb_raise(rb_eTypeError, "already initialized socket address");
+    DATA_PTR(self) = rai = alloc_addrinfo();
+
+    rb_scan_args(argc, argv, "13", &sockaddr_arg, &pfamily, &socktype, &protocol);
+
+    i_pfamily = NIL_P(pfamily) ? PF_UNSPEC : family_arg(pfamily);
+    i_socktype = NIL_P(socktype) ? 0 : socktype_arg(socktype);
+    i_protocol = NIL_P(protocol) ? 0 : NUM2INT(protocol);
+
+    sockaddr_ary = rb_check_array_type(sockaddr_arg);
+    if (!NIL_P(sockaddr_ary)) {
+        VALUE afamily = rb_ary_entry(sockaddr_ary, 0);
+        int af;
+        StringValue(afamily);
+        if (family_to_int(RSTRING_PTR(afamily), RSTRING_LEN(afamily), &af) == -1)
+	    rb_raise(rb_eSocket, "unknown address family: %s", StringValueCStr(afamily));
+        switch (af) {
+          case AF_INET: /* ["AF_INET", 46102, "localhost.localdomain", "127.0.0.1"] */
+#ifdef INET6
+          case AF_INET6: /* ["AF_INET6", 42304, "ip6-localhost", "::1"] */
+#endif
+          {
+            VALUE service = rb_ary_entry(sockaddr_ary, 1);
+            VALUE nodename = rb_ary_entry(sockaddr_ary, 2);
+            VALUE numericnode = rb_ary_entry(sockaddr_ary, 3);
+
+            service = INT2NUM(NUM2INT(service));
+            if (!NIL_P(nodename))
+                StringValue(nodename);
+            StringValue(numericnode);
+
+            init_addrinfo_getaddrinfo(rai, numericnode, service,
+                    INT2NUM(i_pfamily ? i_pfamily : af), INT2NUM(i_socktype), INT2NUM(i_protocol),
+                    INT2NUM(AI_NUMERICHOST|AI_NUMERICSERV),
+                    rb_str_equal(numericnode, nodename) ? Qnil : make_inspectname(nodename, service));
+            break;
+          }
+
+#ifdef HAVE_SYS_UN_H
+          case AF_UNIX: /* ["AF_UNIX", "/tmp/sock"] */
+          {
+            VALUE path = rb_ary_entry(sockaddr_ary, 1);
+            StringValue(path);
+            init_unix_addrinfo(rai, path);
+            break;
+          }
+#endif
+
+          default:
+            rb_raise(rb_eSocket, "unexpected address family");
+        }
+    }
+    else {
+        StringValue(sockaddr_arg);
+        sockaddr_ptr = (struct sockaddr *)RSTRING_PTR(sockaddr_arg);
+        sockaddr_len = RSTRING_LEN(sockaddr_arg);
+        init_addrinfo(rai, sockaddr_ptr, sockaddr_len,
+                      i_pfamily, i_socktype, i_protocol,
+                      canonname, inspectname);
+    }
+
+    return self;
+}
+
+/*
+ * call-seq:
+ *   addrinfo.inspect
+ *
+ * returns a string which shows addrinfo in human-readable form.
+ *
+ *   AddrInfo.tcp("localhost", 80).inspect #=> "#<AddrInfo: 127.0.0.1:80 TCP (localhost:80)>"
+ *   AddrInfo.unix("/tmp/sock").inspect    #=> "#<AddrInfo: /tmp/sock SOCK_STREAM>"
+ *
+ */
+static VALUE
+addrinfo_inspect(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    int internet_p;
+    VALUE ret;
+
+    ret = rb_sprintf("#<%s: ", rb_obj_classname(self));
+
+    switch (rai->addr.ss_family) {
+      case AF_INET:
+      {
+        struct sockaddr_in *addr = (struct sockaddr_in *)&rai->addr;
+        int port;
+        rb_str_catf(ret, "%d.%d.%d.%d",
+                    ((unsigned char*)&addr->sin_addr)[0],
+                    ((unsigned char*)&addr->sin_addr)[1],
+                    ((unsigned char*)&addr->sin_addr)[2],
+                    ((unsigned char*)&addr->sin_addr)[3]);
+        port = ntohs(addr->sin_port);
+        if (port)
+            rb_str_catf(ret, ":%d", port);
+        break;
+      }
+
+#ifdef INET6
+      case AF_INET6:
+      {
+        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&rai->addr;
+        char hbuf[1024];
+        int port;
+        int error;
+        /* use getnameinfo for scope_id.
+         * RFC 4007: IPv6 Scoped Address Architecture
+         * draft-ietf-ipv6-scope-api-00.txt: Scoped Address Extensions to the IPv6 Basic Socket API
+         */
+        error = getnameinfo((struct sockaddr *)&rai->addr, sizeof(struct sockaddr_in6),
+                            hbuf, sizeof(hbuf), NULL, 0,
+                            NI_NUMERICHOST|NI_NUMERICSERV);
+        if (error) {
+            raise_socket_error("getnameinfo", error);
+        }
+        rb_str_catf(ret, "[%s]", hbuf);
+        port = ntohs(addr->sin6_port);
+        if (port)
+            rb_str_catf(ret, ":%d", port);
+        break;
+      }
+#endif
+
+#ifdef HAVE_SYS_UN_H
+      case AF_UNIX:
+      {
+        struct sockaddr_un *addr = (struct sockaddr_un *)&rai->addr;
+        if (addr->sun_path[0] == '/') { /* show absolute path as-is */
+            rb_str_catf(ret, "%.*s", sizeof(addr->sun_path), addr->sun_path);
+        }
+        else {
+            char *s, *e;
+            rb_str_cat2(ret, "AF_UNIX");
+            s = addr->sun_path;
+            e = addr->sun_path + sizeof(addr->sun_path);
+            while (s < e && *(e-1) == '\0')
+                e--;
+            while (s < e)
+                rb_str_catf(ret, ":%02x", (unsigned char)*s++);
+        }
+
+        break;
+      }
+#endif
+
+      default:
+      {
+        ID id = intern_family(rai->addr.ss_family);
+        if (id == 0)
+            rb_str_catf(ret, "unknown address family %d", rai->addr.ss_family);
+        else
+            rb_str_catf(ret, "%s address format unknown", rb_id2name(id));
+        break;
+      }
+    }
+
+    if (rai->pfamily && rai->addr.ss_family != rai->pfamily) {
+        ID id = intern_protocol_family(rai->pfamily);
+        if (id)
+            rb_str_catf(ret, " %s", rb_id2name(id));
+        else
+            rb_str_catf(ret, " PF_\?\?\?(%d)", rai->pfamily);
+    }
+
+    internet_p = rai->pfamily == PF_INET;
+#ifdef INET6
+    internet_p = internet_p || rai->pfamily == PF_INET6;
+#endif
+    if (internet_p && rai->socktype == SOCK_STREAM &&
+        (rai->protocol == 0 || rai->protocol == IPPROTO_TCP)) {
+        rb_str_cat2(ret, " TCP");
+    }
+    else if (internet_p && rai->socktype == SOCK_DGRAM &&
+        (rai->protocol == 0 || rai->protocol == IPPROTO_UDP)) {
+        rb_str_cat2(ret, " UDP");
+    }
+    else {
+        if (rai->socktype) {
+            ID id = intern_socktype(rai->socktype);
+            if (id)
+                rb_str_catf(ret, " %s", rb_id2name(id));
+            else
+                rb_str_catf(ret, " SOCK_\?\?\?(%d)", rai->socktype);
+        }
+
+        if (rai->protocol) {
+            if (internet_p) {
+                ID id = intern_ipproto(rai->protocol);
+                if (id)
+                    rb_str_catf(ret, " %s", rb_id2name(id));
+                else
+                    goto unknown_protocol;
+            }
+            else {
+              unknown_protocol:
+                rb_str_catf(ret, " UNKNOWN_PROTOCOL(%d)", rai->protocol);
+            }
+        }
+    }
+
+    if (!NIL_P(rai->canonname)) {
+        VALUE name = rai->canonname;
+        rb_str_catf(ret, " %s", StringValueCStr(name));
+    }
+
+    if (!NIL_P(rai->inspectname)) {
+        VALUE name = rai->inspectname;
+        rb_str_catf(ret, " (%s)", StringValueCStr(name));
+    }
+
+    rb_str_buf_cat2(ret, ">");
+    return ret;
+}
+
+/*
+ * call-seq:
+ *   addrinfo.afamily
+ *
+ * returns the address family as an integer.
+ *
+ *   AddrInfo.tcp("localhost", 80).afamily == Socket::AF_INET #=> true
+ *
+ */
+static VALUE
+addrinfo_afamily(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    return INT2NUM(rai->addr.ss_family);
+}
+
+/*
+ * call-seq:
+ *   addrinfo.pfamily
+ *
+ * returns the protocol family as an integer.
+ *
+ *   AddrInfo.tcp("localhost", 80).pfamily == Socket::PF_INET #=> true
+ *
+ */
+static VALUE
+addrinfo_pfamily(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    return INT2NUM(rai->pfamily);
+}
+
+/*
+ * call-seq:
+ *   addrinfo.socktype
+ *
+ * returns the socket type as an integer.
+ *
+ *   AddrInfo.tcp("localhost", 80).socktype == Socket::SOCK_STREAM #=> true
+ *
+ */
+static VALUE
+addrinfo_socktype(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    return INT2NUM(rai->socktype);
+}
+
+/*
+ * call-seq:
+ *   addrinfo.protocol
+ *
+ * returns the socket type as an integer.
+ *
+ *   AddrInfo.tcp("localhost", 80).protocol == Socket::IPPROTO_TCP #=> true
+ *
+ */
+static VALUE
+addrinfo_protocol(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    return INT2NUM(rai->protocol);
+}
+
+/*
+ * call-seq:
+ *   addrinfo.to_sockaddr
+ *
+ * returns the socket address as packed struct sockaddr string.
+ *
+ *   AddrInfo.tcp("localhost", 80).to_sockaddr                    
+ *   #=> "\x02\x00\x00P\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
+ *
+ */
+static VALUE
+addrinfo_to_sockaddr(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    VALUE ret;
+    ret = rb_str_new((char*)&rai->addr, sockaddr_size(rai->addr.ss_family));
+    OBJ_INFECT(ret, self);
+    return ret;
+}
+
+/*
+ * call-seq:
+ *   addrinfo.canonname
+ *
+ * returns the canonical name as an string.
+ *
+ * The canonical name is set by AddrInfo.getaddrinfo when AI_CANONNAME is specified.
+ *
+ *   list = AddrInfo.getaddrinfo("www.ruby-lang.org", 80, :INET, :STREAM, nil, Socket::AI_CANONNAME)
+ *   p list[0] #=> #<AddrInfo: 221.186.184.68:80 TCP carbon.ruby-lang.org (www.ruby-lang.org:80)>
+ *   p list[0].canonname #=> "carbon.ruby-lang.org"
+ *
+ */
+static VALUE
+addrinfo_canonname(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    return rai->canonname;
+}
+
+/*
+ * call-seq:
+ *   addrinfo.inet?
+ *
+ * returns true if addrinfo is internet (IPv4/IPv6) address.
+ *
+ *   AddrInfo.tcp("127.0.0.1", 80).inet? #=> true
+ *   AddrInfo.tcp("::1", 80).inet?       #=> true
+ *   AddrInfo.unix("/tmp/sock").inet?    #=> false
+ *
+ */
+static VALUE
+addrinfo_inet_p(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    return rai->addr.ss_family == AF_INET
+#ifdef AF_INET6
+        || rai->addr.ss_family == AF_INET6
+#endif
+        ? Qtrue : Qfalse;
+    return Qfalse;
+}
+
+/*
+ * call-seq:
+ *   addrinfo.ipv4?
+ *
+ * returns true if addrinfo is IPv4 address.
+ *
+ *   AddrInfo.tcp("127.0.0.1", 80).ipv4? #=> true
+ *   AddrInfo.tcp("::1", 80).ipv4?       #=> false
+ *   AddrInfo.unix("/tmp/sock").ipv4?    #=> false
+ *
+ */
+static VALUE
+addrinfo_ipv4_p(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    return rai->addr.ss_family == AF_INET ? Qtrue : Qfalse;
+}
+
+/*
+ * call-seq:
+ *   addrinfo.ipv6?
+ *
+ * returns true if addrinfo is IPv6 address.
+ *
+ *   AddrInfo.tcp("127.0.0.1", 80).ipv6? #=> false
+ *   AddrInfo.tcp("::1", 80).ipv6?       #=> true
+ *   AddrInfo.unix("/tmp/sock").ipv6?    #=> false
+ *
+ */
+static VALUE
+addrinfo_ipv6_p(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+#ifdef AF_INET6
+    return rai->addr.ss_family == AF_INET6 ? Qtrue : Qfalse;
+#else
+    return Qfalse;
+#endif
+}
+
+/*
+ * call-seq:
+ *   addrinfo.unix?
+ *
+ * returns true if addrinfo is UNIX address.
+ *
+ *   AddrInfo.tcp("127.0.0.1", 80).ipv6? #=> false
+ *   AddrInfo.tcp("::1", 80).ipv6?       #=> false
+ *   AddrInfo.unix("/tmp/sock").ipv6?    #=> true
+ *
+ */
+static VALUE
+addrinfo_unix_p(VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+#ifdef AF_UNIX
+    return rai->addr.ss_family == AF_UNIX ? Qtrue : Qfalse;
+#else
+    return Qfalse;
+#endif
+}
+
+/*
+ * call-seq:
+ *   addrinfo.getnameinfo        => [nodename, service]
+ *   addrinfo.getnameinfo(flags) => [nodename, service]
+ *
+ * returns nodename and service as a pair of strings.
+ * This converts struct sockaddr in addrinfo to textual representation.
+ *
+ * flags should be bitwise OR of Socket::NI_??? constants.
+ *
+ *   AddrInfo.tcp("127.0.0.1", 80).getnameinfo #=> ["localhost", "www"]
+ *
+ *   AddrInfo.tcp("127.0.0.1", 80).getnameinfo(Socket::NI_NUMERICSERV)
+ *   #=> ["localhost", "80"]
+ */
+static VALUE
+addrinfo_getnameinfo(int argc, VALUE *argv, VALUE self)
+{
+    rb_addrinfo_t *rai = get_addrinfo(self);
+    VALUE vflags;
+    char hbuf[1024], pbuf[1024];
+    int flags, error;
+
+    rb_scan_args(argc, argv, "01", &vflags);
+
+    flags = NIL_P(vflags) ? 0 : NUM2INT(vflags);
+
+    error = getnameinfo((struct sockaddr *)&rai->addr, sizeof(struct sockaddr_storage),
+                        hbuf, sizeof(hbuf), pbuf, sizeof(pbuf),
+                        flags);
+    if (error) {
+        raise_socket_error("getnameinfo", error);
+    }
+
+    return rb_assoc_new(rb_str_new2(hbuf), rb_str_new2(pbuf));
+}
+
+/*
+ * call-seq:
+ *   AddrInfo.getaddrinfo(nodename, service, family, socktype, protocol, flags) => [addrinfo, ...]
+ *   AddrInfo.getaddrinfo(nodename, service, family, socktype, protocol)        => [addrinfo, ...]
+ *   AddrInfo.getaddrinfo(nodename, service, family, socktype)                  => [addrinfo, ...]
+ *   AddrInfo.getaddrinfo(nodename, service, family)                            => [addrinfo, ...]
+ *   AddrInfo.getaddrinfo(nodename, service)                                    => [addrinfo, ...]
+ *
+ * returns a list of addrinfo objects as an array.
+ *
+ * This method converts nodename (hostname) and service (port) to addrinfo.
+ * Since the conversion is not unique, the result is a list of addrinfo objects.
+ *
+ * nodename or service can be nil if no conversion intended.
+ *
+ * family, socktype and protocol are hint for prefered protocol.
+ * If the result will be used for a socket with SOCK_STREAM, 
+ * SOCK_STREAM should be specified as socktype.
+ * If so, AddrInfo.getaddrinfo returns addrinfo list appropriate for SOCK_STREAM.
+ * If they are omitted or nil is given, the result is not restricted.
+ * 
+ * Similary, PF_INET6 as family restricts for IPv6.
+ *
+ * flags should be bitwise OR of Socket::AI_??? constants.
+ *
+ *   AddrInfo.getaddrinfo("www.kame.net", 80, nil, :STREAM)
+ *   #=> [#<AddrInfo: 203.178.141.194:80 TCP (www.kame.net:80)>,
+ *   #    #<AddrInfo: [2001:200:0:8002:203:47ff:fea5:3085]:80 TCP (www.kame.net:80)>]
+ *
+ */
+static VALUE
+addrinfo_s_getaddrinfo(int argc, VALUE *argv, VALUE self)
+{
+    VALUE node, service, family, socktype, protocol, flags;
+
+    rb_scan_args(argc, argv, "24", &node, &service, &family, &socktype, &protocol, &flags);
+    return addrinfo_list_new(node, service, family, socktype, protocol, flags);
+}
+
+
+/*
+ * call-seq:
+ *   AddrInfo.tcp(host, port) => addrinfo
+ *
+ * returns an addrinfo object for TCP address.
+ *
+ *   AddrInfo.tcp("localhost", "smtp") #=> #<AddrInfo: 127.0.0.1:25 TCP (localhost:smtp)>
+ */
+static VALUE
+addrinfo_s_tcp(VALUE self, VALUE host, VALUE port)
+{
+    return addrinfo_firstonly_new(host, port,
+            INT2NUM(PF_UNSPEC), INT2NUM(SOCK_STREAM), INT2FIX(IPPROTO_TCP), INT2FIX(0));
+}
+
+/*
+ * call-seq:
+ *   AddrInfo.udp(host, port) => addrinfo
+ *
+ * returns an addrinfo object for UDP address.
+ *
+ *   AddrInfo.udp("localhost", "daytime") #=> #<AddrInfo: 127.0.0.1:13 UDP (localhost:daytime)>
+ */
+static VALUE
+addrinfo_s_udp(VALUE self, VALUE host, VALUE port)
+{
+    return addrinfo_firstonly_new(host, port,
+            INT2NUM(PF_UNSPEC), INT2NUM(SOCK_DGRAM), INT2FIX(IPPROTO_UDP), INT2FIX(0));
+}
+
+#ifdef HAVE_SYS_UN_H
+
+/*
+ * call-seq:
+ *   AddrInfo.udp(host, port) => addrinfo
+ *
+ * returns an addrinfo object for UNIX socket address.
+ *
+ *   AddrInfo.unix("/tmp/sock") #=> #<AddrInfo: /tmp/sock SOCK_STREAM>
+ */
+static VALUE
+addrinfo_s_unix(VALUE self, VALUE path)
+{
+    VALUE addr;
+    rb_addrinfo_t *rai;
+
+    addr = addrinfo_s_allocate(rb_cAddrInfo);
+    DATA_PTR(addr) = rai = alloc_addrinfo();
+    init_unix_addrinfo(rai, path);
+    OBJ_INFECT(addr, path);
+    return addr;
+}
+
+#endif
+
 static void
 sock_define_const(const char *name, int value, VALUE mConst)
 {
@@ -3578,6 +4519,8 @@ Init_socket()
     rb_define_method(rb_cBasicSocket, "getsockopt", bsock_getsockopt, 2);
     rb_define_method(rb_cBasicSocket, "getsockname", bsock_getsockname, 0);
     rb_define_method(rb_cBasicSocket, "getpeername", bsock_getpeername, 0);
+    rb_define_method(rb_cBasicSocket, "local_address", bsock_local_address, 0);
+    rb_define_method(rb_cBasicSocket, "remote_address", bsock_remote_address, 0);
     rb_define_method(rb_cBasicSocket, "send", bsock_send, -1);
     rb_define_method(rb_cBasicSocket, "recv", bsock_recv, -1);
     rb_define_method(rb_cBasicSocket, "recv_nonblock", bsock_recv_nonblock, -1);
@@ -3668,6 +4611,33 @@ Init_socket()
     rb_define_singleton_method(rb_cSocket, "unpack_sockaddr_un", sock_s_unpack_sockaddr_un, 1);
 #endif
 
+    rb_cAddrInfo = rb_define_class("AddrInfo", rb_cData);
+    rb_define_alloc_func(rb_cAddrInfo, addrinfo_s_allocate);
+    rb_define_method(rb_cAddrInfo, "initialize", addrinfo_initialize, -1);
+    rb_define_method(rb_cAddrInfo, "inspect", addrinfo_inspect, 0);
+    rb_define_singleton_method(rb_cAddrInfo, "getaddrinfo", addrinfo_s_getaddrinfo, -1);
+    rb_define_singleton_method(rb_cAddrInfo, "tcp", addrinfo_s_tcp, 2);
+    rb_define_singleton_method(rb_cAddrInfo, "udp", addrinfo_s_udp, 2);
+#ifdef HAVE_SYS_UN_H
+    rb_define_singleton_method(rb_cAddrInfo, "unix", addrinfo_s_unix, 1);
+#endif
+
+    rb_define_method(rb_cAddrInfo, "afamily", addrinfo_afamily, 0);
+    rb_define_method(rb_cAddrInfo, "pfamily", addrinfo_pfamily, 0);
+    rb_define_method(rb_cAddrInfo, "socktype", addrinfo_socktype, 0);
+    rb_define_method(rb_cAddrInfo, "protocol", addrinfo_protocol, 0);
+    rb_define_method(rb_cAddrInfo, "canonname", addrinfo_canonname, 0);
+
+    rb_define_method(rb_cAddrInfo, "inet?", addrinfo_inet_p, 0);
+    rb_define_method(rb_cAddrInfo, "ipv4?", addrinfo_ipv4_p, 0);
+    rb_define_method(rb_cAddrInfo, "ipv6?", addrinfo_ipv6_p, 0);
+    rb_define_method(rb_cAddrInfo, "unix?", addrinfo_unix_p, 0);
+
+    rb_define_method(rb_cAddrInfo, "to_sockaddr", addrinfo_to_sockaddr, 0);
+    rb_define_method(rb_cAddrInfo, "to_str", addrinfo_to_sockaddr, 0); /* for compatibility */
+
+    rb_define_method(rb_cAddrInfo, "getnameinfo", addrinfo_getnameinfo, -1);
+
     /* constants */
     mConst = rb_define_module_under(rb_cSocket, "Constants");
     init_constants(mConst);
Index: ext/socket/mkconstants.rb
===================================================================
--- ext/socket/mkconstants.rb	(revision 21278)
+++ ext/socket/mkconstants.rb	(working copy)
@@ -172,6 +172,9 @@ def def_intern(func_name, pat, prefix_op
 end
 
 def_intern('intern_family',  /\AAF_/)
+def_intern('intern_protocol_family',  /\APF_/)
+def_intern('intern_socktype',  /\ASOCK_/)
+def_intern('intern_ipproto',  /\AIPPROTO_/)
 
 result << ERB.new(<<'EOS', nil, '%').result(binding)
 
Index: test/socket/test_socket.rb
===================================================================
--- test/socket/test_socket.rb	(revision 21278)
+++ test/socket/test_socket.rb	(working copy)
@@ -102,7 +102,7 @@ class TestSocket < Test::Unit::TestCase
     c = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
     c.connect(serv.getsockname)
     fd, peeraddr = serv.sysaccept
-    assert_equal(c.getsockname, peeraddr)
+    assert_equal(c.getsockname, peeraddr.to_sockaddr)
   ensure
     serv.close if serv
     c.close if c
-- 
[田中 哲][たなか あきら][Tanaka Akira]

In This Thread

Prev Next