ruby/ext/socket/ipsocket.c

309 строки
8.4 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 addrinfo *res;
} remote, local;
int type;
int fd;
};
static VALUE
inetsock_cleanup(struct inetsock_arg *arg)
{
if (arg->remote.res) {
freeaddrinfo(arg->remote.res);
arg->remote.res = 0;
}
if (arg->local.res) {
freeaddrinfo(arg->local.res);
arg->local.res = 0;
}
if (arg->fd >= 0) {
close(arg->fd);
}
return Qnil;
}
static VALUE
init_inetsock_internal(struct inetsock_arg *arg)
{
int type = arg->type;
struct addrinfo *res;
int fd, status = 0;
const char *syscall = 0;
arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, 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, SOCK_STREAM, 0);
}
arg->fd = fd = -1;
for (res = arg->remote.res; res; res = res->ai_next) {
#if !defined(INET6) && defined(AF_INET6)
if (res->ai_family == AF_INET6)
continue;
#endif
status = rsock_socket(res->ai_family,res->ai_socktype,res->ai_protocol);
syscall = "socket(2)";
fd = status;
if (fd < 0) {
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 (arg->local.res) {
status = bind(fd, arg->local.res->ai_addr, arg->local.res->ai_addrlen);
syscall = "bind(2)";
}
if (status >= 0) {
status = rsock_connect(fd, res->ai_addr, res->ai_addrlen,
(type == INET_SOCKS));
syscall = "connect(2)";
}
}
if (status < 0) {
close(fd);
arg->fd = fd = -1;
continue;
} else
break;
}
if (status < 0) {
rb_sys_fail(syscall);
}
arg->fd = -1;
if (type == INET_SERVER) {
status = listen(fd, 5);
if (status < 0) {
close(fd);
syscall = "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)
{
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;
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.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 same as numeric_address.
* Or if it is +nil+ or ommitted, 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;
struct sockaddr_storage 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, (struct sockaddr*)&addr, &len) < 0)
rb_sys_fail("getsockname(2)");
return rsock_ipaddr((struct sockaddr*)&addr, 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 same as numeric_address.
* Or if it is +nil+ or ommitted, 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, "221.186.184.68", "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;
struct sockaddr_storage 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, (struct sockaddr*)&addr, &len) < 0)
rb_sys_fail("getpeername(2)");
return rsock_ipaddr((struct sockaddr*)&addr, 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 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_.
*
* IPSocket.getaddress("localhost") #=> "127.0.0.1"
* IPSocket.getaddress("ip6-localhost") #=> "::1"
*
*/
static VALUE
ip_s_getaddress(VALUE obj, VALUE host)
{
struct sockaddr_storage addr;
struct addrinfo *res = rsock_addrinfo(host, Qnil, SOCK_STREAM, 0);
/* just take the first one */
memcpy(&addr, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
return rsock_make_ipaddr((struct sockaddr*)&addr);
}
/*
* Document-class: ::IPSocket < BasicSocket
*
* IPSocket is the super class of TCPSocket and UDPSocket.
*/
void
rsock_init_ipsocket(void)
{
rb_cIPSocket = rb_define_class("IPSocket", rb_cBasicSocket);
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");
}