1/************************************************
2
3  ipsocket.c -
4
5  created at: Thu Mar 31 12:21:29 JST 1994
6
7  Copyright (C) 1993-2007 Yukihiro Matsumoto
8
9************************************************/
10
11#include "rubysocket.h"
12
13struct inetsock_arg
14{
15    VALUE sock;
16    struct {
17	VALUE host, serv;
18	struct addrinfo *res;
19    } remote, local;
20    int type;
21    int fd;
22};
23
24static VALUE
25inetsock_cleanup(struct inetsock_arg *arg)
26{
27    if (arg->remote.res) {
28	freeaddrinfo(arg->remote.res);
29	arg->remote.res = 0;
30    }
31    if (arg->local.res) {
32	freeaddrinfo(arg->local.res);
33	arg->local.res = 0;
34    }
35    if (arg->fd >= 0) {
36	close(arg->fd);
37    }
38    return Qnil;
39}
40
41static VALUE
42init_inetsock_internal(struct inetsock_arg *arg)
43{
44    int type = arg->type;
45    struct addrinfo *res;
46    int fd, status = 0;
47    const char *syscall = 0;
48
49    arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, SOCK_STREAM,
50				    (type == INET_SERVER) ? AI_PASSIVE : 0);
51    /*
52     * Maybe also accept a local address
53     */
54
55    if (type != INET_SERVER && (!NIL_P(arg->local.host) || !NIL_P(arg->local.serv))) {
56	arg->local.res = rsock_addrinfo(arg->local.host, arg->local.serv, SOCK_STREAM, 0);
57    }
58
59    arg->fd = fd = -1;
60    for (res = arg->remote.res; res; res = res->ai_next) {
61#if !defined(INET6) && defined(AF_INET6)
62	if (res->ai_family == AF_INET6)
63	    continue;
64#endif
65	status = rsock_socket(res->ai_family,res->ai_socktype,res->ai_protocol);
66	syscall = "socket(2)";
67	fd = status;
68	if (fd < 0) {
69	    continue;
70	}
71	arg->fd = fd;
72	if (type == INET_SERVER) {
73#if !defined(_WIN32) && !defined(__CYGWIN__)
74	    status = 1;
75	    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
76		       (char*)&status, (socklen_t)sizeof(status));
77#endif
78	    status = bind(fd, res->ai_addr, res->ai_addrlen);
79	    syscall = "bind(2)";
80	}
81	else {
82	    if (arg->local.res) {
83		status = bind(fd, arg->local.res->ai_addr, arg->local.res->ai_addrlen);
84		syscall = "bind(2)";
85	    }
86
87	    if (status >= 0) {
88		status = rsock_connect(fd, res->ai_addr, res->ai_addrlen,
89				       (type == INET_SOCKS));
90		syscall = "connect(2)";
91	    }
92	}
93
94	if (status < 0) {
95	    close(fd);
96	    arg->fd = fd = -1;
97	    continue;
98	} else
99	    break;
100    }
101    if (status < 0) {
102	rb_sys_fail(syscall);
103    }
104
105    arg->fd = -1;
106
107    if (type == INET_SERVER) {
108	status = listen(fd, SOMAXCONN);
109	if (status < 0) {
110	    close(fd);
111            rb_sys_fail("listen(2)");
112	}
113    }
114
115    /* create new instance */
116    return rsock_init_sock(arg->sock, fd);
117}
118
119VALUE
120rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv,
121	            VALUE local_host, VALUE local_serv, int type)
122{
123    struct inetsock_arg arg;
124    arg.sock = sock;
125    arg.remote.host = remote_host;
126    arg.remote.serv = remote_serv;
127    arg.remote.res = 0;
128    arg.local.host = local_host;
129    arg.local.serv = local_serv;
130    arg.local.res = 0;
131    arg.type = type;
132    arg.fd = -1;
133    return rb_ensure(init_inetsock_internal, (VALUE)&arg,
134		     inetsock_cleanup, (VALUE)&arg);
135}
136
137static ID id_numeric, id_hostname;
138
139int
140rsock_revlookup_flag(VALUE revlookup, int *norevlookup)
141{
142#define return_norevlookup(x) {*norevlookup = (x); return 1;}
143    ID id;
144
145    switch (revlookup) {
146      case Qtrue:  return_norevlookup(0);
147      case Qfalse: return_norevlookup(1);
148      case Qnil: break;
149      default:
150	Check_Type(revlookup, T_SYMBOL);
151	id = SYM2ID(revlookup);
152	if (id == id_numeric) return_norevlookup(1);
153	if (id == id_hostname) return_norevlookup(0);
154	rb_raise(rb_eArgError, "invalid reverse_lookup flag: :%s", rb_id2name(id));
155    }
156    return 0;
157#undef return_norevlookup
158}
159
160/*
161 * call-seq:
162 *   ipsocket.addr([reverse_lookup]) => [address_family, port, hostname, numeric_address]
163 *
164 * Returns the local address as an array which contains
165 * address_family, port, hostname and numeric_address.
166 *
167 * If +reverse_lookup+ is +true+ or +:hostname+,
168 * hostname is obtained from numeric_address using reverse lookup.
169 * Or if it is +false+, or +:numeric+,
170 * hostname is same as numeric_address.
171 * Or if it is +nil+ or ommitted, obeys to +ipsocket.do_not_reverse_lookup+.
172 * See +Socket.getaddrinfo+ also.
173 *
174 *   TCPSocket.open("www.ruby-lang.org", 80) {|sock|
175 *     p sock.addr #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
176 *     p sock.addr(true)  #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
177 *     p sock.addr(false) #=> ["AF_INET", 49429, "192.168.0.128", "192.168.0.128"]
178 *     p sock.addr(:hostname)  #=> ["AF_INET", 49429, "hal", "192.168.0.128"]
179 *     p sock.addr(:numeric)   #=> ["AF_INET", 49429, "192.168.0.128", "192.168.0.128"]
180 *   }
181 *
182 */
183static VALUE
184ip_addr(int argc, VALUE *argv, VALUE sock)
185{
186    rb_io_t *fptr;
187    struct sockaddr_storage addr;
188    socklen_t len = (socklen_t)sizeof addr;
189    int norevlookup;
190
191    GetOpenFile(sock, fptr);
192
193    if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
194	norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
195    if (getsockname(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
196	rb_sys_fail("getsockname(2)");
197    return rsock_ipaddr((struct sockaddr*)&addr, norevlookup);
198}
199
200/*
201 * call-seq:
202 *   ipsocket.peeraddr([reverse_lookup]) => [address_family, port, hostname, numeric_address]
203 *
204 * Returns the remote address as an array which contains
205 * address_family, port, hostname and numeric_address.
206 * It is defined for connection oriented socket such as TCPSocket.
207 *
208 * If +reverse_lookup+ is +true+ or +:hostname+,
209 * hostname is obtained from numeric_address using reverse lookup.
210 * Or if it is +false+, or +:numeric+,
211 * hostname is same as numeric_address.
212 * Or if it is +nil+ or ommitted, obeys to +ipsocket.do_not_reverse_lookup+.
213 * See +Socket.getaddrinfo+ also.
214 *
215 *   TCPSocket.open("www.ruby-lang.org", 80) {|sock|
216 *     p sock.peeraddr #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
217 *     p sock.peeraddr(true)  #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
218 *     p sock.peeraddr(false) #=> ["AF_INET", 80, "221.186.184.68", "221.186.184.68"]
219 *     p sock.peeraddr(:hostname) #=> ["AF_INET", 80, "carbon.ruby-lang.org", "221.186.184.68"]
220 *     p sock.peeraddr(:numeric)  #=> ["AF_INET", 80, "221.186.184.68", "221.186.184.68"]
221 *   }
222 *
223 */
224static VALUE
225ip_peeraddr(int argc, VALUE *argv, VALUE sock)
226{
227    rb_io_t *fptr;
228    struct sockaddr_storage addr;
229    socklen_t len = (socklen_t)sizeof addr;
230    int norevlookup;
231
232    GetOpenFile(sock, fptr);
233
234    if (argc < 1 || !rsock_revlookup_flag(argv[0], &norevlookup))
235	norevlookup = fptr->mode & FMODE_NOREVLOOKUP;
236    if (getpeername(fptr->fd, (struct sockaddr*)&addr, &len) < 0)
237	rb_sys_fail("getpeername(2)");
238    return rsock_ipaddr((struct sockaddr*)&addr, norevlookup);
239}
240
241/*
242 * call-seq:
243 *   ipsocket.recvfrom(maxlen)        => [mesg, ipaddr]
244 *   ipsocket.recvfrom(maxlen, flags) => [mesg, ipaddr]
245 *
246 * Receives a message and return the message as a string and
247 * an address which the message come from.
248 *
249 * _maxlen_ is the maximum number of bytes to receive.
250 *
251 * _flags_ should be a bitwise OR of Socket::MSG_* constants.
252 *
253 * ipaddr is same as IPSocket#{peeraddr,addr}.
254 *
255 *   u1 = UDPSocket.new
256 *   u1.bind("127.0.0.1", 4913)
257 *   u2 = UDPSocket.new
258 *   u2.send "uuuu", 0, "127.0.0.1", 4913
259 *   p u1.recvfrom(10) #=> ["uuuu", ["AF_INET", 33230, "localhost", "127.0.0.1"]]
260 *
261 */
262static VALUE
263ip_recvfrom(int argc, VALUE *argv, VALUE sock)
264{
265    return rsock_s_recvfrom(sock, argc, argv, RECV_IP);
266}
267
268/*
269 * call-seq:
270 *   IPSocket.getaddress(host)        => ipaddress
271 *
272 * Lookups the IP address of _host_.
273 *
274 *   IPSocket.getaddress("localhost")     #=> "127.0.0.1"
275 *   IPSocket.getaddress("ip6-localhost") #=> "::1"
276 *
277 */
278static VALUE
279ip_s_getaddress(VALUE obj, VALUE host)
280{
281    struct sockaddr_storage addr;
282    struct addrinfo *res = rsock_addrinfo(host, Qnil, SOCK_STREAM, 0);
283
284    /* just take the first one */
285    memcpy(&addr, res->ai_addr, res->ai_addrlen);
286    freeaddrinfo(res);
287
288    return rsock_make_ipaddr((struct sockaddr*)&addr);
289}
290
291void
292rsock_init_ipsocket(void)
293{
294    /*
295     * Document-class: IPSocket < BasicSocket
296     *
297     * IPSocket is the super class of TCPSocket and UDPSocket.
298     */
299    rb_cIPSocket = rb_define_class("IPSocket", rb_cBasicSocket);
300    rb_define_method(rb_cIPSocket, "addr", ip_addr, -1);
301    rb_define_method(rb_cIPSocket, "peeraddr", ip_peeraddr, -1);
302    rb_define_method(rb_cIPSocket, "recvfrom", ip_recvfrom, -1);
303    rb_define_singleton_method(rb_cIPSocket, "getaddress", ip_s_getaddress, 1);
304    rb_undef_method(rb_cIPSocket, "getpeereid");
305
306    id_numeric = rb_intern_const("numeric");
307    id_hostname = rb_intern_const("hostname");
308}
309