mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			403 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			403 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/************************************************
 | 
						|
 | 
						|
  ipsocket.c -
 | 
						|
 | 
						|
  created at: Thu Mar 31 12:21:29 JST 1994
 | 
						|
 | 
						|
  Copyright (C) 1993-2007 Yukihiro Matsumoto
 | 
						|
 | 
						|
************************************************/
 | 
						|
 | 
						|
#include "rubysocket.h"
 | 
						|
 | 
						|
struct inetsock_arg
 | 
						|
{
 | 
						|
    VALUE sock;
 | 
						|
    struct {
 | 
						|
	VALUE host, serv;
 | 
						|
	struct rb_addrinfo *res;
 | 
						|
    } remote, local;
 | 
						|
    int type;
 | 
						|
    int fd;
 | 
						|
    VALUE resolv_timeout;
 | 
						|
    VALUE connect_timeout;
 | 
						|
};
 | 
						|
 | 
						|
static VALUE
 | 
						|
inetsock_cleanup(VALUE v)
 | 
						|
{
 | 
						|
    struct inetsock_arg *arg = (void *)v;
 | 
						|
    if (arg->remote.res) {
 | 
						|
	rb_freeaddrinfo(arg->remote.res);
 | 
						|
	arg->remote.res = 0;
 | 
						|
    }
 | 
						|
    if (arg->local.res) {
 | 
						|
	rb_freeaddrinfo(arg->local.res);
 | 
						|
	arg->local.res = 0;
 | 
						|
    }
 | 
						|
    if (arg->fd >= 0) {
 | 
						|
	close(arg->fd);
 | 
						|
    }
 | 
						|
    return Qnil;
 | 
						|
}
 | 
						|
 | 
						|
static VALUE
 | 
						|
init_inetsock_internal(VALUE v)
 | 
						|
{
 | 
						|
    struct inetsock_arg *arg = (void *)v;
 | 
						|
    int error = 0;
 | 
						|
    int type = arg->type;
 | 
						|
    struct addrinfo *res, *lres;
 | 
						|
    int fd, status = 0, local = 0;
 | 
						|
    int family = AF_UNSPEC;
 | 
						|
    const char *syscall = 0;
 | 
						|
    VALUE connect_timeout = arg->connect_timeout;
 | 
						|
    struct timeval tv_storage;
 | 
						|
    struct timeval *tv = NULL;
 | 
						|
 | 
						|
    if (!NIL_P(connect_timeout)) {
 | 
						|
        tv_storage = rb_time_interval(connect_timeout);
 | 
						|
        tv = &tv_storage;
 | 
						|
    }
 | 
						|
 | 
						|
    arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv,
 | 
						|
				     family, SOCK_STREAM,
 | 
						|
				     (type == INET_SERVER) ? AI_PASSIVE : 0);
 | 
						|
 | 
						|
 | 
						|
    /*
 | 
						|
     * Maybe also accept a local address
 | 
						|
     */
 | 
						|
 | 
						|
    if (type != INET_SERVER && (!NIL_P(arg->local.host) || !NIL_P(arg->local.serv))) {
 | 
						|
	arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv,
 | 
						|
					family, SOCK_STREAM, 0);
 | 
						|
    }
 | 
						|
 | 
						|
    arg->fd = fd = -1;
 | 
						|
    for (res = arg->remote.res->ai; res; res = res->ai_next) {
 | 
						|
#if !defined(INET6) && defined(AF_INET6)
 | 
						|
	if (res->ai_family == AF_INET6)
 | 
						|
	    continue;
 | 
						|
#endif
 | 
						|
        lres = NULL;
 | 
						|
        if (arg->local.res) {
 | 
						|
            for (lres = arg->local.res->ai; lres; lres = lres->ai_next) {
 | 
						|
                if (lres->ai_family == res->ai_family)
 | 
						|
                    break;
 | 
						|
            }
 | 
						|
            if (!lres) {
 | 
						|
                if (res->ai_next || status < 0)
 | 
						|
                    continue;
 | 
						|
                /* Use a different family local address if no choice, this
 | 
						|
                 * will cause EAFNOSUPPORT. */
 | 
						|
                lres = arg->local.res->ai;
 | 
						|
            }
 | 
						|
        }
 | 
						|
	status = rsock_socket(res->ai_family,res->ai_socktype,res->ai_protocol);
 | 
						|
	syscall = "socket(2)";
 | 
						|
	fd = status;
 | 
						|
	if (fd < 0) {
 | 
						|
	    error = errno;
 | 
						|
	    continue;
 | 
						|
	}
 | 
						|
	arg->fd = fd;
 | 
						|
	if (type == INET_SERVER) {
 | 
						|
#if !defined(_WIN32) && !defined(__CYGWIN__)
 | 
						|
	    status = 1;
 | 
						|
	    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
 | 
						|
		       (char*)&status, (socklen_t)sizeof(status));
 | 
						|
#endif
 | 
						|
	    status = bind(fd, res->ai_addr, res->ai_addrlen);
 | 
						|
	    syscall = "bind(2)";
 | 
						|
	}
 | 
						|
	else {
 | 
						|
	    if (lres) {
 | 
						|
#if !defined(_WIN32) && !defined(__CYGWIN__)
 | 
						|
                status = 1;
 | 
						|
                setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
 | 
						|
                           (char*)&status, (socklen_t)sizeof(status));
 | 
						|
#endif
 | 
						|
		status = bind(fd, lres->ai_addr, lres->ai_addrlen);
 | 
						|
		local = status;
 | 
						|
		syscall = "bind(2)";
 | 
						|
	    }
 | 
						|
 | 
						|
	    if (status >= 0) {
 | 
						|
		status = rsock_connect(fd, res->ai_addr, res->ai_addrlen,
 | 
						|
				       (type == INET_SOCKS), tv);
 | 
						|
		syscall = "connect(2)";
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
	if (status < 0) {
 | 
						|
	    error = errno;
 | 
						|
	    close(fd);
 | 
						|
	    arg->fd = fd = -1;
 | 
						|
	    continue;
 | 
						|
	} else
 | 
						|
	    break;
 | 
						|
    }
 | 
						|
    if (status < 0) {
 | 
						|
	VALUE host, port;
 | 
						|
 | 
						|
	if (local < 0) {
 | 
						|
	    host = arg->local.host;
 | 
						|
	    port = arg->local.serv;
 | 
						|
	} else {
 | 
						|
	    host = arg->remote.host;
 | 
						|
	    port = arg->remote.serv;
 | 
						|
	}
 | 
						|
 | 
						|
	rsock_syserr_fail_host_port(error, syscall, host, port);
 | 
						|
    }
 | 
						|
 | 
						|
    arg->fd = -1;
 | 
						|
 | 
						|
    if (type == INET_SERVER) {
 | 
						|
	status = listen(fd, SOMAXCONN);
 | 
						|
	if (status < 0) {
 | 
						|
	    error = errno;
 | 
						|
	    close(fd);
 | 
						|
	    rb_syserr_fail(error, "listen(2)");
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    /* create new instance */
 | 
						|
    return rsock_init_sock(arg->sock, fd);
 | 
						|
}
 | 
						|
 | 
						|
VALUE
 | 
						|
rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv,
 | 
						|
	            VALUE local_host, VALUE local_serv, int type,
 | 
						|
		    VALUE resolv_timeout, VALUE connect_timeout)
 | 
						|
{
 | 
						|
    struct inetsock_arg arg;
 | 
						|
    arg.sock = sock;
 | 
						|
    arg.remote.host = remote_host;
 | 
						|
    arg.remote.serv = remote_serv;
 | 
						|
    arg.remote.res = 0;
 | 
						|
    arg.local.host = local_host;
 | 
						|
    arg.local.serv = local_serv;
 | 
						|
    arg.local.res = 0;
 | 
						|
    arg.type = type;
 | 
						|
    arg.fd = -1;
 | 
						|
    arg.resolv_timeout = resolv_timeout;
 | 
						|
    arg.connect_timeout = connect_timeout;
 | 
						|
    return rb_ensure(init_inetsock_internal, (VALUE)&arg,
 | 
						|
		     inetsock_cleanup, (VALUE)&arg);
 | 
						|
}
 | 
						|
 | 
						|
static ID id_numeric, id_hostname;
 | 
						|
 | 
						|
int
 | 
						|
rsock_revlookup_flag(VALUE revlookup, int *norevlookup)
 | 
						|
{
 | 
						|
#define return_norevlookup(x) {*norevlookup = (x); return 1;}
 | 
						|
    ID id;
 | 
						|
 | 
						|
    switch (revlookup) {
 | 
						|
      case Qtrue:  return_norevlookup(0);
 | 
						|
      case Qfalse: return_norevlookup(1);
 | 
						|
      case Qnil: break;
 | 
						|
      default:
 | 
						|
	Check_Type(revlookup, T_SYMBOL);
 | 
						|
	id = SYM2ID(revlookup);
 | 
						|
	if (id == id_numeric) return_norevlookup(1);
 | 
						|
	if (id == id_hostname) return_norevlookup(0);
 | 
						|
	rb_raise(rb_eArgError, "invalid reverse_lookup flag: :%s", rb_id2name(id));
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
#undef return_norevlookup
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * call-seq:
 | 
						|
 *   ipsocket.inspect   -> string
 | 
						|
 *
 | 
						|
 * Return a string describing this IPSocket object.
 | 
						|
 */
 | 
						|
static VALUE
 | 
						|
ip_inspect(VALUE sock)
 | 
						|
{
 | 
						|
    VALUE str = rb_call_super(0, 0);
 | 
						|
    rb_io_t *fptr = RFILE(sock)->fptr;
 | 
						|
    union_sockaddr addr;
 | 
						|
    socklen_t len = (socklen_t)sizeof addr;
 | 
						|
    ID id;
 | 
						|
    if (fptr && fptr->fd >= 0 &&
 | 
						|
	getsockname(fptr->fd, &addr.addr, &len) >= 0 &&
 | 
						|
	(id = rsock_intern_family(addr.addr.sa_family)) != 0) {
 | 
						|
	VALUE family = rb_id2str(id);
 | 
						|
	char hbuf[1024], pbuf[1024];
 | 
						|
	long slen = RSTRING_LEN(str);
 | 
						|
	const char last = (slen > 1 && RSTRING_PTR(str)[slen - 1] == '>') ?
 | 
						|
	    (--slen, '>') : 0;
 | 
						|
	str = rb_str_subseq(str, 0, slen);
 | 
						|
	rb_str_cat_cstr(str, ", ");
 | 
						|
	rb_str_append(str, family);
 | 
						|
	if (!rb_getnameinfo(&addr.addr, len, hbuf, sizeof(hbuf),
 | 
						|
			    pbuf, sizeof(pbuf), NI_NUMERICHOST | NI_NUMERICSERV)) {
 | 
						|
	    rb_str_cat_cstr(str, ", ");
 | 
						|
	    rb_str_cat_cstr(str, hbuf);
 | 
						|
	    rb_str_cat_cstr(str, ", ");
 | 
						|
	    rb_str_cat_cstr(str, pbuf);
 | 
						|
	}
 | 
						|
	if (last) rb_str_cat(str, &last, 1);
 | 
						|
    }
 | 
						|
    return str;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * call-seq:
 | 
						|
 *   ipsocket.addr([reverse_lookup]) => [address_family, port, hostname, numeric_address]
 | 
						|
 *
 | 
						|
 * Returns the local address as an array which contains
 | 
						|
 * address_family, port, hostname and numeric_address.
 | 
						|
 *
 | 
						|
 * If +reverse_lookup+ is +true+ or +:hostname+,
 | 
						|
 * hostname is obtained from numeric_address using reverse lookup.
 | 
						|
 * Or if it is +false+, or +:numeric+,
 | 
						|
 * hostname is the same as numeric_address.
 | 
						|
 * Or if it is +nil+ or omitted, obeys to +ipsocket.do_not_reverse_lookup+.
 | 
						|
 * See +Socket.getaddrinfo+ also.
 | 
						|
 *
 | 
						|
 *   TCPSocket.open("www.ruby-lang.org", 80) {|sock|
 | 
						|
 *     p sock.addr #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
 | 
						|
 *     p sock.addr(true)  #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
 | 
						|
 *     p sock.addr(false) #=> ["AF_INET", 49429, "192.168.0.128", "192.168.0.128"]
 | 
						|
 *     p sock.addr(:hostname)  #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
 | 
						|
 *     p sock.addr(:numeric)   #=> ["AF_INET", 49429, "192.168.0.128", "192.168.0.128"]
 | 
						|
 *   }
 | 
						|
 *
 | 
						|
 */
 | 
						|
static VALUE
 | 
						|
ip_addr(int argc, VALUE *argv, VALUE sock)
 | 
						|
{
 | 
						|
    rb_io_t *fptr;
 | 
						|
    union_sockaddr addr;
 | 
						|
    socklen_t len = (socklen_t)sizeof addr;
 | 
						|
    int norevlookup;
 | 
						|
 | 
						|
    GetOpenFile(sock, fptr);
 | 
						|
 | 
						|
    if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
 | 
						|
	norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
 | 
						|
    if (getsockname(fptr->fd, &addr.addr, &len) < 0)
 | 
						|
	rb_sys_fail("getsockname(2)");
 | 
						|
    return rsock_ipaddr(&addr.addr, len, norevlookup);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * call-seq:
 | 
						|
 *   ipsocket.peeraddr([reverse_lookup]) => [address_family, port, hostname, numeric_address]
 | 
						|
 *
 | 
						|
 * Returns the remote address as an array which contains
 | 
						|
 * address_family, port, hostname and numeric_address.
 | 
						|
 * It is defined for connection oriented socket such as TCPSocket.
 | 
						|
 *
 | 
						|
 * If +reverse_lookup+ is +true+ or +:hostname+,
 | 
						|
 * hostname is obtained from numeric_address using reverse lookup.
 | 
						|
 * Or if it is +false+, or +:numeric+,
 | 
						|
 * hostname is the same as numeric_address.
 | 
						|
 * Or if it is +nil+ or omitted, obeys to +ipsocket.do_not_reverse_lookup+.
 | 
						|
 * See +Socket.getaddrinfo+ also.
 | 
						|
 *
 | 
						|
 *   TCPSocket.open("www.ruby-lang.org", 80) {|sock|
 | 
						|
 *     p sock.peeraddr #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
 | 
						|
 *     p sock.peeraddr(true)  #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
 | 
						|
 *     p sock.peeraddr(false) #=> ["AF_INET", 80, "221.186.184.68", "221.186.184.68"]
 | 
						|
 *     p sock.peeraddr(:hostname) #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
 | 
						|
 *     p sock.peeraddr(:numeric)  #=> ["AF_INET", 80, "221.186.184.68", "221.186.184.68"]
 | 
						|
 *   }
 | 
						|
 *
 | 
						|
 */
 | 
						|
static VALUE
 | 
						|
ip_peeraddr(int argc, VALUE *argv, VALUE sock)
 | 
						|
{
 | 
						|
    rb_io_t *fptr;
 | 
						|
    union_sockaddr addr;
 | 
						|
    socklen_t len = (socklen_t)sizeof addr;
 | 
						|
    int norevlookup;
 | 
						|
 | 
						|
    GetOpenFile(sock, fptr);
 | 
						|
 | 
						|
    if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
 | 
						|
	norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
 | 
						|
    if (getpeername(fptr->fd, &addr.addr, &len) < 0)
 | 
						|
	rb_sys_fail("getpeername(2)");
 | 
						|
    return rsock_ipaddr(&addr.addr, len, norevlookup);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * call-seq:
 | 
						|
 *   ipsocket.recvfrom(maxlen)        => [mesg, ipaddr]
 | 
						|
 *   ipsocket.recvfrom(maxlen, flags) => [mesg, ipaddr]
 | 
						|
 *
 | 
						|
 * Receives a message and return the message as a string and
 | 
						|
 * an address which the message come from.
 | 
						|
 *
 | 
						|
 * _maxlen_ is the maximum number of bytes to receive.
 | 
						|
 *
 | 
						|
 * _flags_ should be a bitwise OR of Socket::MSG_* constants.
 | 
						|
 *
 | 
						|
 * ipaddr is the same as IPSocket#{peeraddr,addr}.
 | 
						|
 *
 | 
						|
 *   u1 = UDPSocket.new
 | 
						|
 *   u1.bind("127.0.0.1", 4913)
 | 
						|
 *   u2 = UDPSocket.new
 | 
						|
 *   u2.send "uuuu", 0, "127.0.0.1", 4913
 | 
						|
 *   p u1.recvfrom(10) #=> ["uuuu", ["AF_INET", 33230, "localhost", "127.0.0.1"]]
 | 
						|
 *
 | 
						|
 */
 | 
						|
static VALUE
 | 
						|
ip_recvfrom(int argc, VALUE *argv, VALUE sock)
 | 
						|
{
 | 
						|
    return rsock_s_recvfrom(sock, argc, argv, RECV_IP);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * call-seq:
 | 
						|
 *   IPSocket.getaddress(host)        => ipaddress
 | 
						|
 *
 | 
						|
 * Lookups the IP address of _host_.
 | 
						|
 *
 | 
						|
 *   require 'socket'
 | 
						|
 *
 | 
						|
 *   IPSocket.getaddress("localhost")     #=> "127.0.0.1"
 | 
						|
 *   IPSocket.getaddress("ip6-localhost") #=> "::1"
 | 
						|
 *
 | 
						|
 */
 | 
						|
static VALUE
 | 
						|
ip_s_getaddress(VALUE obj, VALUE host)
 | 
						|
{
 | 
						|
    union_sockaddr addr;
 | 
						|
    struct rb_addrinfo *res = rsock_addrinfo(host, Qnil, AF_UNSPEC, SOCK_STREAM, 0);
 | 
						|
    socklen_t len = res->ai->ai_addrlen;
 | 
						|
 | 
						|
    /* just take the first one */
 | 
						|
    memcpy(&addr, res->ai->ai_addr, len);
 | 
						|
    rb_freeaddrinfo(res);
 | 
						|
 | 
						|
    return rsock_make_ipaddr(&addr.addr, len);
 | 
						|
}
 | 
						|
 | 
						|
void
 | 
						|
rsock_init_ipsocket(void)
 | 
						|
{
 | 
						|
    /*
 | 
						|
     * Document-class: IPSocket < BasicSocket
 | 
						|
     *
 | 
						|
     * IPSocket is the super class of TCPSocket and UDPSocket.
 | 
						|
     */
 | 
						|
    rb_cIPSocket = rb_define_class("IPSocket", rb_cBasicSocket);
 | 
						|
    rb_define_method(rb_cIPSocket, "inspect", ip_inspect, 0);
 | 
						|
    rb_define_method(rb_cIPSocket, "addr", ip_addr, -1);
 | 
						|
    rb_define_method(rb_cIPSocket, "peeraddr", ip_peeraddr, -1);
 | 
						|
    rb_define_method(rb_cIPSocket, "recvfrom", ip_recvfrom, -1);
 | 
						|
    rb_define_singleton_method(rb_cIPSocket, "getaddress", ip_s_getaddress, 1);
 | 
						|
    rb_undef_method(rb_cIPSocket, "getpeereid");
 | 
						|
 | 
						|
    id_numeric = rb_intern_const("numeric");
 | 
						|
    id_hostname = rb_intern_const("hostname");
 | 
						|
}
 |