summaryrefslogtreecommitdiff
path: root/jni/ruby/ext/socket/udpsocket.c
blob: eb605cafc1f11887767c4100affb3722f887af88 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
/************************************************

  udpsocket.c -

  created at: Thu Mar 31 12:21:29 JST 1994

  Copyright (C) 1993-2007 Yukihiro Matsumoto

************************************************/

#include "rubysocket.h"

/*
 * call-seq:
 *   UDPSocket.new([address_family]) => socket
 *
 * Creates a new UDPSocket object.
 *
 * _address_family_ should be an integer, a string or a symbol:
 * Socket::AF_INET, "AF_INET", :INET, etc.
 *
 *   UDPSocket.new                   #=> #<UDPSocket:fd 3>
 *   UDPSocket.new(Socket::AF_INET6) #=> #<UDPSocket:fd 4>
 *
 */
static VALUE
udp_init(int argc, VALUE *argv, VALUE sock)
{
    VALUE arg;
    int family = AF_INET;
    int fd;

    rb_secure(3);
    if (rb_scan_args(argc, argv, "01", &arg) == 1) {
        family = rsock_family_arg(arg);
    }
    fd = rsock_socket(family, SOCK_DGRAM, 0);
    if (fd < 0) {
        rb_sys_fail("socket(2) - udp");
    }

    return rsock_init_sock(sock, fd);
}

struct udp_arg
{
    struct rb_addrinfo *res;
    int fd;
};

static VALUE
udp_connect_internal(struct udp_arg *arg)
{
    int fd = arg->fd;
    struct addrinfo *res;

    for (res = arg->res->ai; res; res = res->ai_next) {
        if (rsock_connect(fd, res->ai_addr, res->ai_addrlen, 0) >= 0) {
            return Qtrue;
        }
    }
    return Qfalse;
}

/*
 * call-seq:
 *   udpsocket.connect(host, port) => 0
 *
 * Connects _udpsocket_ to _host_:_port_.
 *
 * This makes possible to send without destination address.
 *
 *   u1 = UDPSocket.new
 *   u1.bind("127.0.0.1", 4913)
 *   u2 = UDPSocket.new
 *   u2.connect("127.0.0.1", 4913)
 *   u2.send "uuuu", 0
 *   p u1.recvfrom(10) #=> ["uuuu", ["AF_INET", 33230, "localhost", "127.0.0.1"]]
 *
 */
static VALUE
udp_connect(VALUE sock, VALUE host, VALUE port)
{
    rb_io_t *fptr;
    struct udp_arg arg;
    VALUE ret;

    rb_secure(3);
    arg.res = rsock_addrinfo(host, port, SOCK_DGRAM, 0);
    GetOpenFile(sock, fptr);
    arg.fd = fptr->fd;
    ret = rb_ensure(udp_connect_internal, (VALUE)&arg,
                    rsock_freeaddrinfo, (VALUE)arg.res);
    if (!ret) rsock_sys_fail_host_port("connect(2)", host, port);
    return INT2FIX(0);
}

/*
 * call-seq:
 *   udpsocket.bind(host, port) #=> 0
 *
 * Binds _udpsocket_ to _host_:_port_.
 *
 *   u1 = UDPSocket.new
 *   u1.bind("127.0.0.1", 4913)
 *   u1.send "message-to-self", 0, "127.0.0.1", 4913
 *   p u1.recvfrom(10) #=> ["message-to", ["AF_INET", 4913, "localhost", "127.0.0.1"]]
 *
 */
static VALUE
udp_bind(VALUE sock, VALUE host, VALUE port)
{
    rb_io_t *fptr;
    struct rb_addrinfo *res0;
    struct addrinfo *res;

    rb_secure(3);
    res0 = rsock_addrinfo(host, port, SOCK_DGRAM, 0);
    GetOpenFile(sock, fptr);
    for (res = res0->ai; res; res = res->ai_next) {
        if (bind(fptr->fd, res->ai_addr, res->ai_addrlen) < 0) {
            continue;
        }
        rb_freeaddrinfo(res0);
        return INT2FIX(0);
    }
    rb_freeaddrinfo(res0);

    rsock_sys_fail_host_port("bind(2)", host, port);

    return INT2FIX(0);
}

/*
 * call-seq:
 *   udpsocket.send(mesg, flags, host, port)  => numbytes_sent
 *   udpsocket.send(mesg, flags, sockaddr_to) => numbytes_sent
 *   udpsocket.send(mesg, flags)              => numbytes_sent
 *
 * Sends _mesg_ via _udpsocket_.
 *
 * _flags_ should be a bitwise OR of Socket::MSG_* constants.
 *
 *   u1 = UDPSocket.new
 *   u1.bind("127.0.0.1", 4913)
 *
 *   u2 = UDPSocket.new
 *   u2.send "hi", 0, "127.0.0.1", 4913
 *
 *   mesg, addr = u1.recvfrom(10)
 *   u1.send mesg, 0, addr[3], addr[1]
 *
 *   p u2.recv(100) #=> "hi"
 *
 */
static VALUE
udp_send(int argc, VALUE *argv, VALUE sock)
{
    VALUE flags, host, port;
    rb_io_t *fptr;
    int n;
    struct rb_addrinfo *res0;
    struct addrinfo *res;
    struct rsock_send_arg arg;

    if (argc == 2 || argc == 3) {
        return rsock_bsock_send(argc, argv, sock);
    }
    rb_scan_args(argc, argv, "4", &arg.mesg, &flags, &host, &port);

    StringValue(arg.mesg);
    res0 = rsock_addrinfo(host, port, SOCK_DGRAM, 0);
    GetOpenFile(sock, fptr);
    arg.fd = fptr->fd;
    arg.flags = NUM2INT(flags);
    for (res = res0->ai; res; res = res->ai_next) {
      retry:
        arg.to = res->ai_addr;
        arg.tolen = res->ai_addrlen;
        rsock_maybe_fd_writable(arg.fd);
        n = (int)BLOCKING_REGION_FD(rsock_sendto_blocking, &arg);
        if (n >= 0) {
            rb_freeaddrinfo(res0);
            return INT2FIX(n);
        }
        if (rb_io_wait_writable(fptr->fd)) {
            goto retry;
        }
    }
    rb_freeaddrinfo(res0);
    rsock_sys_fail_host_port("sendto(2)", host, port);
    return INT2FIX(n);
}

/*
 * call-seq:
 *   udpsocket.recvfrom_nonblock(maxlen) => [mesg, sender_inet_addr]
 *   udpsocket.recvfrom_nonblock(maxlen, flags) => [mesg, sender_inet_addr]
 *
 * Receives up to _maxlen_ bytes from +udpsocket+ using recvfrom(2) after
 * O_NONBLOCK is set for the underlying file descriptor.
 * If _maxlen_ is omitted, its default value is 65536.
 * _flags_ is zero or more of the +MSG_+ options.
 * The first element of the results, _mesg_, is the data received.
 * The second element, _sender_inet_addr_, is an array to represent the sender address.
 *
 * When recvfrom(2) returns 0,
 * Socket#recvfrom_nonblock returns an empty string as data.
 * It means an empty packet.
 *
 * === Parameters
 * * +maxlen+ - the number of bytes to receive from the socket
 * * +flags+ - zero or more of the +MSG_+ options
 *
 * === Example
 *      require 'socket'
 *      s1 = UDPSocket.new
 *      s1.bind("127.0.0.1", 0)
 *      s2 = UDPSocket.new
 *      s2.bind("127.0.0.1", 0)
 *      s2.connect(*s1.addr.values_at(3,1))
 *      s1.connect(*s2.addr.values_at(3,1))
 *      s1.send "aaa", 0
 *      begin # emulate blocking recvfrom
 *        p s2.recvfrom_nonblock(10)  #=> ["aaa", ["AF_INET", 33302, "localhost.localdomain", "127.0.0.1"]]
 *      rescue IO::WaitReadable
 *        IO.select([s2])
 *        retry
 *      end
 *
 * Refer to Socket#recvfrom for the exceptions that may be thrown if the call
 * to _recvfrom_nonblock_ fails.
 *
 * UDPSocket#recvfrom_nonblock may raise any error corresponding to recvfrom(2) failure,
 * including Errno::EWOULDBLOCK.
 *
 * If the exception is Errno::EWOULDBLOCK or Errno::AGAIN,
 * it is extended by IO::WaitReadable.
 * So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
 *
 * === See
 * * Socket#recvfrom
 */
static VALUE
udp_recvfrom_nonblock(int argc, VALUE *argv, VALUE sock)
{
    return rsock_s_recvfrom_nonblock(sock, argc, argv, RECV_IP);
}

void
rsock_init_udpsocket(void)
{
    /*
     * Document-class: UDPSocket < IPSocket
     *
     * UDPSocket represents a UDP/IP socket.
     *
     */
    rb_cUDPSocket = rb_define_class("UDPSocket", rb_cIPSocket);
    rb_define_method(rb_cUDPSocket, "initialize", udp_init, -1);
    rb_define_method(rb_cUDPSocket, "connect", udp_connect, 2);
    rb_define_method(rb_cUDPSocket, "bind", udp_bind, 2);
    rb_define_method(rb_cUDPSocket, "send", udp_send, -1);
    rb_define_method(rb_cUDPSocket, "recvfrom_nonblock", udp_recvfrom_nonblock, -1);
}