diff --git a/ChangeLog b/ChangeLog index 1cff55f8c9..a927a442f3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,69 @@ +Wed Jan 7 22:24:12 2009 Tanaka Akira + + * ext/socket/socket.c (rb_cAddrInfo): new class AddrInfo. + (s_recvfrom): return AddrInfo as address. + (s_recvfrom_nonblock): ditto. + (sock_accept): ditto. + (sock_accept_nonblock): ditto. + (sock_sysaccept): ditto. + (bsock_send): accept AddrInfo as address argument. + (sock_connect): ditto. + (sock_connect_nonblock): ditto. + (sock_bind): ditto. + (sock_s_unpack_sockaddr_in): ditto. + (sock_s_unpack_sockaddr_un): ditto. + (bsock_local_address): new method BasicSocket#local_address. + (bsock_remote_address): new method BasicSocket#remote_address. + (addrinfo_initialize): new method AddrInfo#initialize. + (addrinfo_inspect): new method AddrInfo#inspect. + (addrinfo_afamily): new method AddrInfo#afamily. + (addrinfo_pfamily): new method AddrInfo#pfamily. + (addrinfo_socktype): new method AddrInfo#socktype. + (addrinfo_protocol): new method AddrInfo#protocol. + (addrinfo_to_sockaddr): new method AddrInfo#to_sockaddr. + (addrinfo_canonname): new method AddrInfo#canonname. + (addrinfo_ip_p): new method AddrInfo#ip?. + (addrinfo_ipv4_p): new method AddrInfo#ipv4?. + (addrinfo_ipv6_p): new method AddrInfo#ipv6?. + (addrinfo_unix_p): new method AddrInfo#unix?. + (addrinfo_getnameinfo): new method AddrInfo#getnameinfo. + (addrinfo_s_getaddrinfo): new method AddrInfo.getaddrinfo. + (addrinfo_s_tcp): new method AddrInfo.tcp. + (addrinfo_s_udp): new method AddrInfo.udp. + (addrinfo_s_unix): new method AddrInfo.unix. + (Init_socket): define new class and methods. + (sock_getaddrinfo): apply socktype hack regardless of ai_flags. + (addrinfo_new): defined. + (get_afamily): ditto. + (fd_socket_addrinfo): ditto. + (io_socket_addrinfo): ditto. + (SockAddrStringValue): ditto. + (SockAddrStringValuePtr): ditto. + (sockaddr_string_value): ditto. + (sockaddr_string_value_ptr): ditto. + (rb_addrinfo_t): ditto. + (addrinfo_mark): ditto. + (addrinfo_free): ditto. + (addrinfo_s_allocate): ditto. + (IS_ADDRINFO): ditto. + (check_addrinfo): ditto. + (get_addrinfo): ditto. + (alloc_addrinfo): ditto. + (init_addrinfo): ditto. + (addrinfo_new): ditto. + (call_getaddrinfo): ditto. + (init_addrinfo_getaddrinfo): ditto. + (make_inspectname): ditto. + (addrinfo_firstonly_new): ditto. + (addrinfo_list_new): ditto. + (init_unix_addrinfo): ditto. + (ai_get_afamily): ditto. + + * ext/socket/mkconstants.rb: generate intern_protocol_family, + intern_socktype and intern_ipproto. + + [ruby-dev:37692] + Wed Jan 7 22:13:03 2009 Nobuyoshi Nakada * instruby.rb (man, gem): fixed errors. diff --git a/ext/socket/mkconstants.rb b/ext/socket/mkconstants.rb index da15fb4c79..2b126b17e7 100644 --- a/ext/socket/mkconstants.rb +++ b/ext/socket/mkconstants.rb @@ -172,6 +172,9 @@ def def_intern(func_name, pat, prefix_optional=nil) 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) diff --git a/ext/socket/socket.c b/ext/socket/socket.c index adeabacfc7..12b6fda877 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -88,6 +88,7 @@ VALUE rb_cUNIXSocket; VALUE rb_cUNIXServer; #endif VALUE rb_cSocket; +static VALUE rb_cAddrInfo; static VALUE rb_eSocket; @@ -640,6 +641,99 @@ bsock_getpeername(VALUE sock) return rb_str_new(buf, len); } +static VALUE addrinfo_new(struct sockaddr *, socklen_t, int, int, int, VALUE, VALUE); + +static int +get_afamily(struct sockaddr *addr, socklen_t len) +{ + if ((char*)&addr->sa_family + sizeof(addr->sa_family) - (char*)addr <= len) + return addr->sa_family; + else + return AF_UNSPEC; +} + +static VALUE +fd_socket_addrinfo(int fd, struct sockaddr *addr, socklen_t len) +{ + int family; + int socktype; + int ret; + socklen_t optlen = sizeof(socktype); + + /* assumes protocol family and address family are identical */ + family = get_afamily(addr, len); + + 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; @@ -665,6 +759,11 @@ send_blocking(void *data) arg->flags); } +#define SockAddrStringValue(v) sockaddr_string_value(&(v)) +#define SockAddrStringValuePtr(v) sockaddr_string_value_ptr(&(v)) +static VALUE sockaddr_string_value(volatile VALUE *); +static char *sockaddr_string_value_ptr(volatile VALUE *); + static VALUE bsock_send(int argc, VALUE *argv, VALUE sock) { @@ -679,7 +778,7 @@ bsock_send(int argc, VALUE *argv, VALUE sock) StringValue(arg.mesg); if (!NIL_P(to)) { - StringValue(to); + SockAddrStringValue(to); to = rb_str_new4(to); arg.to = (struct sockaddr *)RSTRING_PTR(to); arg.tolen = RSTRING_LEN(to); @@ -812,7 +911,7 @@ s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from) 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"); } @@ -872,7 +971,7 @@ s_recvfrom_nonblock(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from) break; case RECV_SOCKET: - addr = rb_str_new(buf, alen); + addr = io_socket_addrinfo(sock, (struct sockaddr*)buf, alen); break; default: @@ -1077,7 +1176,7 @@ sock_getaddrinfo(VALUE host, VALUE port, struct addrinfo *hints, int socktype_ha hostp = host_str(host, hbuf, sizeof(hbuf)); portp = port_str(port, pbuf, sizeof(pbuf)); - if (socktype_hack && hints->ai_socktype == 0 && hints->ai_flags == 0 && str_isnumber(portp)) { + if (socktype_hack && hints->ai_socktype == 0 && str_isnumber(portp)) { hints->ai_socktype = SOCK_DGRAM; } @@ -2044,7 +2143,6 @@ udp_send(int argc, VALUE *argv, VALUE sock) static VALUE udp_recvfrom_nonblock(int argc, VALUE *argv, VALUE sock) { - return s_recvfrom_nonblock(sock, argc, argv, RECV_IP); } @@ -2608,7 +2706,7 @@ sock_connect(VALUE sock, VALUE addr) rb_io_t *fptr; int fd, n; - StringValue(addr); + SockAddrStringValue(addr); addr = rb_str_new4(addr); GetOpenFile(sock, fptr); fd = fptr->fd; @@ -2664,7 +2762,7 @@ sock_connect_nonblock(VALUE sock, VALUE addr) rb_io_t *fptr; int n; - StringValue(addr); + SockAddrStringValue(addr); addr = rb_str_new4(addr); GetOpenFile(sock, fptr); rb_io_set_nonblock(fptr); @@ -2760,7 +2858,7 @@ sock_bind(VALUE sock, VALUE addr) { rb_io_t *fptr; - StringValue(addr); + SockAddrStringValue(addr); GetOpenFile(sock, fptr); if (bind(fptr->fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_LEN(addr)) < 0) rb_sys_fail("bind(2)"); @@ -3039,7 +3137,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)); } /* @@ -3100,7 +3198,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)); } /* @@ -3153,7 +3251,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 @@ -3518,7 +3616,7 @@ sock_s_unpack_sockaddr_in(VALUE self, VALUE addr) struct sockaddr_in * sockaddr; VALUE host; - sockaddr = (struct sockaddr_in*)StringValuePtr(addr); + sockaddr = (struct sockaddr_in*)SockAddrStringValuePtr(addr); if (((struct sockaddr *)sockaddr)->sa_family != AF_INET #ifdef INET6 && ((struct sockaddr *)sockaddr)->sa_family != AF_INET6 @@ -3564,7 +3662,7 @@ sock_s_unpack_sockaddr_un(VALUE self, VALUE addr) const char *sun_path; VALUE path; - sockaddr = (struct sockaddr_un*)StringValuePtr(addr); + sockaddr = (struct sockaddr_un*)SockAddrStringValuePtr(addr); if (((struct sockaddr *)sockaddr)->sa_family != AF_UNIX) { rb_raise(rb_eArgError, "not an AF_UNIX sockaddr"); } @@ -3584,6 +3682,932 @@ sock_s_unpack_sockaddr_un(VALUE self, VALUE addr) } #endif +typedef struct { + VALUE inspectname; + VALUE canonname; + int pfamily; + int socktype; + int protocol; + size_t sockaddr_len; + 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 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, RUBY_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->sockaddr_len = 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, + int socktype_hack) +{ + 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, socktype_hack); + + 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, 1); + 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, 0); + + 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, 0); + + 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; +} + +static int +ai_get_afamily(rb_addrinfo_t *rai) +{ + return get_afamily((struct sockaddr *)&rai->addr, rai->sockaddr_len); +} + +/* + * call-seq: + * addrinfo.inspect + * + * returns a string which shows addrinfo in human-readable form. + * + * AddrInfo.tcp("localhost", 80).inspect #=> "#" + * AddrInfo.unix("/tmp/sock").inspect #=> "#" + * + */ +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)); + + if (rai->sockaddr_len == 0) { + rb_str_cat2(ret, "empty-sockaddr"); + } + else if (rai->sockaddr_len < ((char*)&rai->addr.ss_family + sizeof(rai->addr.ss_family)) - (char*)&rai->addr) + rb_str_cat2(ret, "too-short-sockaddr"); + else { + switch (rai->addr.ss_family) { + case AF_INET: + { + struct sockaddr_in *addr; + int port; + if (rai->sockaddr_len < sizeof(struct sockaddr_in)) { + rb_str_cat2(ret, "too-short-AF_INET-sockaddr"); + } + else { + addr = (struct sockaddr_in *)&rai->addr; + 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); + if (sizeof(struct sockaddr_in) < rai->sockaddr_len) + rb_str_catf(ret, "(sockaddr %d bytes too long)", (int)(rai->sockaddr_len - sizeof(struct sockaddr_in))); + } + break; + } + +#ifdef INET6 + case AF_INET6: + { + struct sockaddr_in6 *addr; + char hbuf[1024]; + int port; + int error; + if (rai->sockaddr_len < sizeof(struct sockaddr_in6)) { + rb_str_cat2(ret, "too-short-AF_INET6-sockaddr"); + } + else { + addr = (struct sockaddr_in6 *)&rai->addr; + /* 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, rai->sockaddr_len, + hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST|NI_NUMERICSERV); + if (error) { + raise_socket_error("getnameinfo", error); + } + if (addr->sin6_port == 0) { + rb_str_cat2(ret, hbuf); + } + else { + port = ntohs(addr->sin6_port); + rb_str_catf(ret, "[%s]:%d", hbuf, port); + } + if (sizeof(struct sockaddr_in6) < rai->sockaddr_len) + rb_str_catf(ret, "(sockaddr %d bytes too long)", (int)(rai->sockaddr_len - sizeof(struct sockaddr_in6))); + } + break; + } +#endif + +#ifdef HAVE_SYS_UN_H + case AF_UNIX: + { + struct sockaddr_un *addr = (struct sockaddr_un *)&rai->addr; + char *p, *s, *t, *e; + s = addr->sun_path; + e = (char*)addr + rai->sockaddr_len; + if (e < s) + rb_str_cat2(ret, "too-short-AF_UNIX-sockaddr"); + else if (s == e) + rb_str_cat2(ret, "empty-path-AF_UNIX-sockaddr"); + else { + int printable_only = 1; + p = s; + while (p < e && *p != '\0') { + printable_only = printable_only && ISPRINT(*p) && !ISSPACE(*p); + p++; + } + t = p; + while (p < e && *p == '\0') + p++; + if (printable_only && /* only printable, no space */ + t < e && /* NUL terminated */ + p == e) { /* no data after NUL */ + if (s == t) + rb_str_cat2(ret, "empty-path-AF_UNIX-sockaddr"); + else if (s[0] == '/') /* absolute path */ + rb_str_cat2(ret, s); + else + rb_str_catf(ret, "AF_UNIX %s", s); + } + else { + rb_str_cat2(ret, "AF_UNIX"); + e = (char *)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++); + } + if (addr->sun_path + sizeof(addr->sun_path) < (char*)&rai->addr + rai->sockaddr_len) + rb_str_catf(ret, "(sockaddr %d bytes too long)", + (int)(rai->sockaddr_len - (addr->sun_path + sizeof(addr->sun_path) - (char*)&rai->addr))); + } + 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 && ai_get_afamily(rai) != 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(ai_get_afamily(rai)); +} + +/* + * 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, rai->sockaddr_len); + 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] #=> # + * 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.ip? + * + * returns true if addrinfo is internet (IPv4/IPv6) address. + * returns false otherwise. + * + * AddrInfo.tcp("127.0.0.1", 80).ip? #=> true + * AddrInfo.tcp("::1", 80).ip? #=> true + * AddrInfo.unix("/tmp/sock").ip? #=> false + * + */ +static VALUE +addrinfo_ip_p(VALUE self) +{ + rb_addrinfo_t *rai = get_addrinfo(self); + int family = ai_get_afamily(rai); + return family == AF_INET +#ifdef AF_INET6 + || family == AF_INET6 +#endif + ? Qtrue : Qfalse; + return Qfalse; +} + +/* + * call-seq: + * addrinfo.ipv4? + * + * returns true if addrinfo is IPv4 address. + * returns false otherwise. + * + * 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 ai_get_afamily(rai) == AF_INET ? Qtrue : Qfalse; +} + +/* + * call-seq: + * addrinfo.ipv6? + * + * returns true if addrinfo is IPv6 address. + * returns false otherwise. + * + * 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 ai_get_afamily(rai) == AF_INET6 ? Qtrue : Qfalse; +#else + return Qfalse; +#endif +} + +/* + * call-seq: + * addrinfo.unix? + * + * returns true if addrinfo is UNIX address. + * returns false otherwise. + * + * AddrInfo.tcp("127.0.0.1", 80).unix? #=> false + * AddrInfo.tcp("::1", 80).unix? #=> false + * AddrInfo.unix("/tmp/sock").unix? #=> true + * + */ +static VALUE +addrinfo_unix_p(VALUE self) +{ + rb_addrinfo_t *rai = get_addrinfo(self); +#ifdef AF_UNIX + return ai_get_afamily(rai) == 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, rai->sockaddr_len, + 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) + * #=> [#, + * # #] + * + */ +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") #=> # + */ +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") #=> # + */ +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") #=> # + */ +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 VALUE +sockaddr_string_value(volatile VALUE *v) +{ + VALUE val = *v; + if (TYPE(val) == RUBY_T_DATA && IS_ADDRINFO(val)) { + *v = addrinfo_to_sockaddr(val); + } + StringValue(*v); + return *v; +} + +static char * +sockaddr_string_value_ptr(volatile VALUE *v) +{ + sockaddr_string_value(v); + return RSTRING_PTR(*v); +} + static void sock_define_const(const char *name, int value, VALUE mConst) { @@ -3646,6 +4670,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); @@ -3736,6 +4762,32 @@ 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, "ip?", addrinfo_ip_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, "getnameinfo", addrinfo_getnameinfo, -1); + /* constants */ mConst = rb_define_module_under(rb_cSocket, "Constants"); init_constants(mConst); diff --git a/test/socket/test_socket.rb b/test/socket/test_socket.rb index c94861b06c..4055c825c9 100644 --- a/test/socket/test_socket.rb +++ b/test/socket/test_socket.rb @@ -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