raindrops-maintained 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.document +7 -0
  3. data/.gitattributes +4 -0
  4. data/.gitignore +16 -0
  5. data/.manifest +62 -0
  6. data/.olddoc.yml +16 -0
  7. data/COPYING +165 -0
  8. data/GIT-VERSION-FILE +1 -0
  9. data/GIT-VERSION-GEN +40 -0
  10. data/GNUmakefile +4 -0
  11. data/LATEST +9 -0
  12. data/LICENSE +16 -0
  13. data/NEWS +384 -0
  14. data/README +101 -0
  15. data/TODO +3 -0
  16. data/archive/.gitignore +3 -0
  17. data/archive/slrnpull.conf +4 -0
  18. data/examples/linux-listener-stats.rb +122 -0
  19. data/examples/middleware.ru +5 -0
  20. data/examples/watcher.ru +4 -0
  21. data/examples/watcher_demo.ru +13 -0
  22. data/examples/yahns.conf.rb +30 -0
  23. data/examples/zbatery.conf.rb +16 -0
  24. data/ext/raindrops/extconf.rb +163 -0
  25. data/ext/raindrops/linux_inet_diag.c +713 -0
  26. data/ext/raindrops/my_fileno.h +16 -0
  27. data/ext/raindrops/raindrops.c +487 -0
  28. data/ext/raindrops/raindrops_atomic.h +23 -0
  29. data/ext/raindrops/tcp_info.c +245 -0
  30. data/lib/raindrops/aggregate/last_data_recv.rb +94 -0
  31. data/lib/raindrops/aggregate/pmq.rb +245 -0
  32. data/lib/raindrops/aggregate.rb +8 -0
  33. data/lib/raindrops/last_data_recv.rb +102 -0
  34. data/lib/raindrops/linux.rb +77 -0
  35. data/lib/raindrops/middleware/proxy.rb +40 -0
  36. data/lib/raindrops/middleware.rb +153 -0
  37. data/lib/raindrops/struct.rb +62 -0
  38. data/lib/raindrops/watcher.rb +428 -0
  39. data/lib/raindrops.rb +72 -0
  40. data/pkg.mk +151 -0
  41. data/raindrops-maintained.gemspec +1 -0
  42. data/raindrops.gemspec +26 -0
  43. data/setup.rb +1586 -0
  44. data/test/ipv6_enabled.rb +9 -0
  45. data/test/rack_unicorn.rb +11 -0
  46. data/test/test_aggregate_pmq.rb +65 -0
  47. data/test/test_inet_diag_socket.rb +16 -0
  48. data/test/test_last_data_recv.rb +57 -0
  49. data/test/test_last_data_recv_unicorn.rb +69 -0
  50. data/test/test_linux.rb +281 -0
  51. data/test/test_linux_all_tcp_listen_stats.rb +66 -0
  52. data/test/test_linux_all_tcp_listen_stats_leak.rb +43 -0
  53. data/test/test_linux_ipv6.rb +166 -0
  54. data/test/test_linux_middleware.rb +64 -0
  55. data/test/test_linux_reuseport_tcp_listen_stats.rb +51 -0
  56. data/test/test_middleware.rb +128 -0
  57. data/test/test_middleware_unicorn.rb +37 -0
  58. data/test/test_middleware_unicorn_ipv6.rb +37 -0
  59. data/test/test_raindrops.rb +207 -0
  60. data/test/test_raindrops_gc.rb +38 -0
  61. data/test/test_struct.rb +54 -0
  62. data/test/test_tcp_info.rb +88 -0
  63. data/test/test_watcher.rb +186 -0
  64. metadata +193 -0
@@ -0,0 +1,713 @@
1
+ #include <ruby.h>
2
+ #include <stdarg.h>
3
+ #include <ruby/st.h>
4
+ #include "my_fileno.h"
5
+ #ifdef __linux__
6
+
7
+ #ifdef HAVE_RB_THREAD_IO_BLOCKING_REGION
8
+ /* Ruby 1.9.3 and 2.0.0 */
9
+ VALUE rb_thread_io_blocking_region(rb_blocking_function_t *, void *, int);
10
+ # define rd_fd_region(fn,data,fd) \
11
+ rb_thread_io_blocking_region((fn),(data),(fd))
12
+ #elif defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && \
13
+ defined(HAVE_RUBY_THREAD_H) && HAVE_RUBY_THREAD_H
14
+ /* in case Ruby 2.0+ ever drops rb_thread_io_blocking_region: */
15
+ # include <ruby/thread.h>
16
+ # define COMPAT_FN (void *(*)(void *))
17
+ # define rd_fd_region(fn,data,fd) \
18
+ rb_thread_call_without_gvl(COMPAT_FN(fn),(data),RUBY_UBF_IO,NULL)
19
+ #else
20
+ # error Ruby <= 1.8 not supported
21
+ #endif
22
+
23
+ #include <assert.h>
24
+ #include <errno.h>
25
+ #include <sys/socket.h>
26
+ #include <sys/types.h>
27
+ #include <netdb.h>
28
+ #include <unistd.h>
29
+ #include <fcntl.h>
30
+ #include <string.h>
31
+ #include <asm/types.h>
32
+ #include <netinet/in.h>
33
+ #include <arpa/inet.h>
34
+ #include <netinet/tcp.h>
35
+ #include <linux/netlink.h>
36
+ #include <linux/rtnetlink.h>
37
+ #include <linux/inet_diag.h>
38
+
39
+ union any_addr {
40
+ struct sockaddr_storage ss;
41
+ struct sockaddr sa;
42
+ struct sockaddr_in in;
43
+ struct sockaddr_in6 in6;
44
+ };
45
+
46
+ static size_t page_size;
47
+ static unsigned g_seq;
48
+ static VALUE cListenStats, cIDSock;
49
+ static ID id_new;
50
+
51
+ struct listen_stats {
52
+ uint32_t active;
53
+ uint32_t queued;
54
+ uint32_t listener_p;
55
+ };
56
+
57
+ #define OPLEN (sizeof(struct inet_diag_bc_op) + \
58
+ sizeof(struct inet_diag_hostcond) + \
59
+ sizeof(struct sockaddr_storage))
60
+
61
+ struct nogvl_args {
62
+ st_table *table;
63
+ struct iovec iov[3]; /* last iov holds inet_diag bytecode */
64
+ struct listen_stats stats;
65
+ int fd;
66
+ };
67
+
68
+ #ifdef SOCK_CLOEXEC
69
+ # define my_SOCK_RAW (SOCK_RAW|SOCK_CLOEXEC)
70
+ # define FORCE_CLOEXEC(v) (v)
71
+ #else
72
+ # define my_SOCK_RAW SOCK_RAW
73
+ static VALUE FORCE_CLOEXEC(VALUE io)
74
+ {
75
+ int fd = my_fileno(io);
76
+ int flags = fcntl(fd, F_SETFD, FD_CLOEXEC);
77
+ if (flags == -1)
78
+ rb_sys_fail("fcntl(F_SETFD, FD_CLOEXEC)");
79
+ return io;
80
+ }
81
+ #endif
82
+
83
+ /*
84
+ * call-seq:
85
+ * Raindrops::InetDiagSocket.new -> Socket
86
+ *
87
+ * Creates a new Socket object for the netlink inet_diag facility
88
+ */
89
+ static VALUE ids_s_new(VALUE klass)
90
+ {
91
+ VALUE argv[3];
92
+
93
+ argv[0] = INT2NUM(AF_NETLINK);
94
+ argv[1] = INT2NUM(my_SOCK_RAW);
95
+ argv[2] = INT2NUM(NETLINK_INET_DIAG);
96
+
97
+ return FORCE_CLOEXEC(rb_call_super(3, argv));
98
+ }
99
+
100
+ /* creates a Ruby ListenStats Struct based on our internal listen_stats */
101
+ static VALUE rb_listen_stats(struct listen_stats *stats)
102
+ {
103
+ VALUE active = UINT2NUM(stats->active);
104
+ VALUE queued = UINT2NUM(stats->queued);
105
+
106
+ return rb_struct_new(cListenStats, active, queued);
107
+ }
108
+
109
+ static int st_free_data(st_data_t key, st_data_t value, st_data_t ignored)
110
+ {
111
+ xfree((void *)key);
112
+ xfree((void *)value);
113
+
114
+ return ST_DELETE;
115
+ }
116
+
117
+ /*
118
+ * call-seq:
119
+ * remove_scope_id(ip_address)
120
+ *
121
+ * Returns copy of IP address with Scope ID removed,
122
+ * if address has it (only IPv6 actually may have it).
123
+ */
124
+ static VALUE remove_scope_id(const char *addr)
125
+ {
126
+ VALUE rv = rb_str_new2(addr);
127
+ long len = RSTRING_LEN(rv);
128
+ char *ptr = RSTRING_PTR(rv);
129
+ char *pct = memchr(ptr, '%', len);
130
+
131
+ /*
132
+ * remove scoped portion
133
+ * Ruby equivalent: rv.sub!(/%([^\]]*)\]/, "]")
134
+ */
135
+ if (pct) {
136
+ size_t newlen = pct - ptr;
137
+ char *rbracket = memchr(pct, ']', len - newlen);
138
+
139
+ if (rbracket) {
140
+ size_t move = len - (rbracket - ptr);
141
+
142
+ memmove(pct, rbracket, move);
143
+ newlen += move;
144
+
145
+ rb_str_set_len(rv, newlen);
146
+ } else {
147
+ rb_raise(rb_eArgError,
148
+ "']' not found in IPv6 addr=%s", ptr);
149
+ }
150
+ }
151
+ return rv;
152
+ }
153
+
154
+ static int st_to_hash(st_data_t key, st_data_t value, VALUE hash)
155
+ {
156
+ struct listen_stats *stats = (struct listen_stats *)value;
157
+
158
+ if (stats->listener_p) {
159
+ VALUE k = remove_scope_id((const char *)key);
160
+ VALUE v = rb_listen_stats(stats);
161
+
162
+ OBJ_FREEZE(k);
163
+ rb_hash_aset(hash, k, v);
164
+ }
165
+ return st_free_data(key, value, 0);
166
+ }
167
+
168
+ static int st_AND_hash(st_data_t key, st_data_t value, VALUE hash)
169
+ {
170
+ struct listen_stats *stats = (struct listen_stats *)value;
171
+
172
+ if (stats->listener_p) {
173
+ VALUE k = remove_scope_id((const char *)key);
174
+
175
+ if (rb_hash_lookup(hash, k) == Qtrue) {
176
+ VALUE v = rb_listen_stats(stats);
177
+ OBJ_FREEZE(k);
178
+ rb_hash_aset(hash, k, v);
179
+ }
180
+ }
181
+ return st_free_data(key, value, 0);
182
+ }
183
+
184
+ static const char *addr_any(sa_family_t family)
185
+ {
186
+ static const char ipv4[] = "0.0.0.0";
187
+ static const char ipv6[] = "[::]";
188
+
189
+ if (family == AF_INET)
190
+ return ipv4;
191
+ assert(family == AF_INET6 && "unknown family");
192
+ return ipv6;
193
+ }
194
+
195
+ #ifdef __GNUC__
196
+ static void bug_warn_nogvl(const char *, ...)
197
+ __attribute__((format(printf,1,2)));
198
+ #endif
199
+ static void bug_warn_nogvl(const char *fmt, ...)
200
+ {
201
+ va_list ap;
202
+
203
+ va_start(ap, fmt);
204
+ vfprintf(stderr, fmt, ap);
205
+ va_end(ap);
206
+
207
+ fprintf(stderr, "Please report how you produced this at "\
208
+ "raindrops-public@yhbt.net\n");
209
+ fflush(stderr);
210
+ }
211
+
212
+ static struct listen_stats *stats_for(st_table *table, struct inet_diag_msg *r)
213
+ {
214
+ char *host, *key, *port, *old_key;
215
+ struct listen_stats *stats;
216
+ socklen_t hostlen;
217
+ socklen_t portlen = (socklen_t)sizeof("65535");
218
+ int n;
219
+ const void *src = r->id.idiag_src;
220
+ char buf[INET6_ADDRSTRLEN];
221
+ size_t buf_len;
222
+
223
+ switch (r->idiag_family) {
224
+ case AF_INET: {
225
+ hostlen = INET_ADDRSTRLEN;
226
+ buf_len = hostlen + portlen;
227
+ host = key = buf;
228
+ break;
229
+ }
230
+ case AF_INET6: {
231
+ hostlen = INET6_ADDRSTRLEN;
232
+ buf_len = 1 + hostlen + 1 + portlen;
233
+ key = buf;
234
+ host = key + 1;
235
+ break;
236
+ }
237
+ default:
238
+ assert(0 && "unsupported address family, could that be IPv7?!");
239
+ }
240
+ if (!inet_ntop(r->idiag_family, src, host, hostlen)) {
241
+ bug_warn_nogvl("BUG: inet_ntop: %s\n", strerror(errno));
242
+ *key = '\0';
243
+ *host = '\0';
244
+ }
245
+ hostlen = (socklen_t)strlen(host);
246
+ switch (r->idiag_family) {
247
+ case AF_INET:
248
+ host[hostlen] = ':';
249
+ port = host + hostlen + 1;
250
+ break;
251
+ case AF_INET6:
252
+ key[0] = '[';
253
+ host[hostlen] = ']';
254
+ host[hostlen + 1] = ':';
255
+ port = host + hostlen + 2;
256
+ break;
257
+ default:
258
+ assert(0 && "unsupported address family, could that be IPv7?!");
259
+ }
260
+
261
+ n = snprintf(port, portlen, "%u", ntohs(r->id.idiag_sport));
262
+ if (n <= 0) {
263
+ bug_warn_nogvl("BUG: snprintf port: %d\n", n);
264
+ *key = '\0';
265
+ }
266
+
267
+ if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats))
268
+ return stats;
269
+
270
+ old_key = key;
271
+
272
+ if (r->idiag_state == TCP_ESTABLISHED) {
273
+ n = snprintf(key, buf_len, "%s:%u",
274
+ addr_any(r->idiag_family),
275
+ ntohs(r->id.idiag_sport));
276
+ if (n <= 0) {
277
+ bug_warn_nogvl("BUG: snprintf: %d\n", n);
278
+ *key = '\0';
279
+ }
280
+ if (st_lookup(table, (st_data_t)key, (st_data_t *)&stats))
281
+ return stats;
282
+ if (n <= 0) {
283
+ key = xmalloc(1);
284
+ *key = '\0';
285
+ } else {
286
+ old_key = key;
287
+ key = xmalloc(n + 1);
288
+ memcpy(key, old_key, n + 1);
289
+ }
290
+ } else {
291
+ size_t old_len = strlen(old_key) + 1;
292
+ key = xmalloc(old_len);
293
+ memcpy(key, old_key, old_len);
294
+ }
295
+ stats = xcalloc(1, sizeof(struct listen_stats));
296
+ st_insert(table, (st_data_t)key, (st_data_t)stats);
297
+ return stats;
298
+ }
299
+
300
+ static void table_incr_active(st_table *table, struct inet_diag_msg *r)
301
+ {
302
+ struct listen_stats *stats = stats_for(table, r);
303
+ ++stats->active;
304
+ }
305
+
306
+ static void table_set_queued(st_table *table, struct inet_diag_msg *r)
307
+ {
308
+ struct listen_stats *stats = stats_for(table, r);
309
+ stats->listener_p = 1;
310
+ stats->queued += r->idiag_rqueue;
311
+ }
312
+
313
+ /* inner loop of inet_diag, called for every socket returned by netlink */
314
+ static inline void r_acc(struct nogvl_args *args, struct inet_diag_msg *r)
315
+ {
316
+ /*
317
+ * inode == 0 means the connection is still in the listen queue
318
+ * and has not yet been accept()-ed by the server. The
319
+ * inet_diag bytecode cannot filter this for us.
320
+ */
321
+ if (r->idiag_inode == 0)
322
+ return;
323
+ if (r->idiag_state == TCP_ESTABLISHED) {
324
+ if (args->table)
325
+ table_incr_active(args->table, r);
326
+ else
327
+ args->stats.active++;
328
+ } else { /* if (r->idiag_state == TCP_LISTEN) */
329
+ if (args->table)
330
+ table_set_queued(args->table, r);
331
+ else
332
+ args->stats.queued += r->idiag_rqueue;
333
+ }
334
+ /*
335
+ * we wont get anything else because of the idiag_states filter
336
+ */
337
+ }
338
+
339
+ static const char err_sendmsg[] = "sendmsg";
340
+ static const char err_recvmsg[] = "recvmsg";
341
+ static const char err_nlmsg[] = "nlmsg";
342
+
343
+ struct diag_req {
344
+ struct nlmsghdr nlh;
345
+ struct inet_diag_req r;
346
+ };
347
+
348
+ static void prep_msghdr(
349
+ struct msghdr *msg,
350
+ struct nogvl_args *args,
351
+ struct sockaddr_nl *nladdr,
352
+ size_t iovlen)
353
+ {
354
+ memset(msg, 0, sizeof(struct msghdr));
355
+ msg->msg_name = (void *)nladdr;
356
+ msg->msg_namelen = sizeof(struct sockaddr_nl);
357
+ msg->msg_iov = args->iov;
358
+ msg->msg_iovlen = iovlen;
359
+ }
360
+
361
+ static void prep_diag_args(
362
+ struct nogvl_args *args,
363
+ struct sockaddr_nl *nladdr,
364
+ struct rtattr *rta,
365
+ struct diag_req *req,
366
+ struct msghdr *msg)
367
+ {
368
+ memset(req, 0, sizeof(struct diag_req));
369
+ memset(nladdr, 0, sizeof(struct sockaddr_nl));
370
+
371
+ nladdr->nl_family = AF_NETLINK;
372
+
373
+ req->nlh.nlmsg_len = (unsigned int)(sizeof(struct diag_req) +
374
+ RTA_LENGTH(args->iov[2].iov_len));
375
+ req->nlh.nlmsg_type = TCPDIAG_GETSOCK;
376
+ req->nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST;
377
+ req->nlh.nlmsg_pid = getpid();
378
+ req->r.idiag_states = (1<<TCP_ESTABLISHED) | (1<<TCP_LISTEN);
379
+ rta->rta_type = INET_DIAG_REQ_BYTECODE;
380
+ rta->rta_len = RTA_LENGTH(args->iov[2].iov_len);
381
+
382
+ args->iov[0].iov_base = req;
383
+ args->iov[0].iov_len = sizeof(struct diag_req);
384
+ args->iov[1].iov_base = rta;
385
+ args->iov[1].iov_len = sizeof(struct rtattr);
386
+
387
+ prep_msghdr(msg, args, nladdr, 3);
388
+ }
389
+
390
+ static void prep_recvmsg_buf(struct nogvl_args *args)
391
+ {
392
+ /* reuse buffer that was allocated for bytecode */
393
+ args->iov[0].iov_len = page_size;
394
+ args->iov[0].iov_base = args->iov[2].iov_base;
395
+ }
396
+
397
+ /* does the inet_diag stuff with netlink(), this is called w/o GVL */
398
+ static VALUE diag(void *ptr)
399
+ {
400
+ struct nogvl_args *args = ptr;
401
+ struct sockaddr_nl nladdr;
402
+ struct rtattr rta;
403
+ struct diag_req req;
404
+ struct msghdr msg;
405
+ const char *err = NULL;
406
+ unsigned seq = ++g_seq;
407
+
408
+ prep_diag_args(args, &nladdr, &rta, &req, &msg);
409
+ req.nlh.nlmsg_seq = seq;
410
+
411
+ if (sendmsg(args->fd, &msg, 0) < 0) {
412
+ err = err_sendmsg;
413
+ goto out;
414
+ }
415
+
416
+ prep_recvmsg_buf(args);
417
+
418
+ while (1) {
419
+ ssize_t readed;
420
+ size_t r;
421
+ struct nlmsghdr *h = (struct nlmsghdr *)args->iov[0].iov_base;
422
+
423
+ prep_msghdr(&msg, args, &nladdr, 1);
424
+ readed = recvmsg(args->fd, &msg, 0);
425
+ if (readed < 0) {
426
+ if (errno == EINTR)
427
+ continue;
428
+ err = err_recvmsg;
429
+ goto out;
430
+ }
431
+ if (readed == 0)
432
+ goto out;
433
+ r = (size_t)readed;
434
+ for ( ; NLMSG_OK(h, r); h = NLMSG_NEXT(h, r)) {
435
+ if (h->nlmsg_seq != seq)
436
+ continue;
437
+ if (h->nlmsg_type == NLMSG_DONE)
438
+ goto out;
439
+ if (h->nlmsg_type == NLMSG_ERROR) {
440
+ err = err_nlmsg;
441
+ goto out;
442
+ }
443
+ r_acc(args, NLMSG_DATA(h));
444
+ }
445
+ }
446
+ out:
447
+ /* prepare to raise, free memory before reacquiring GVL */
448
+ if (err && args->table) {
449
+ int save_errno = errno;
450
+
451
+ st_foreach(args->table, st_free_data, 0);
452
+ st_free_table(args->table);
453
+ errno = save_errno;
454
+ }
455
+ return (VALUE)err;
456
+ }
457
+
458
+ /* populates sockaddr_storage struct by parsing +addr+ */
459
+ static void parse_addr(union any_addr *inet, VALUE addr)
460
+ {
461
+ char *host_ptr;
462
+ char *check;
463
+ char *colon = NULL;
464
+ char *rbracket = NULL;
465
+ void *dst;
466
+ long host_len;
467
+ int af, rc;
468
+ uint16_t *portdst;
469
+ unsigned long port;
470
+
471
+ Check_Type(addr, T_STRING);
472
+ host_ptr = StringValueCStr(addr);
473
+ host_len = RSTRING_LEN(addr);
474
+ if (*host_ptr == '[') { /* ipv6 address format (rfc2732) */
475
+ rbracket = memchr(host_ptr + 1, ']', host_len - 1);
476
+
477
+ if (rbracket == NULL)
478
+ rb_raise(rb_eArgError, "']' not found in IPv6 addr=%s",
479
+ host_ptr);
480
+ if (rbracket[1] != ':')
481
+ rb_raise(rb_eArgError, "':' not found in IPv6 addr=%s",
482
+ host_ptr);
483
+ colon = rbracket + 1;
484
+ host_ptr++;
485
+ *rbracket = 0;
486
+ inet->ss.ss_family = af = AF_INET6;
487
+ dst = &inet->in6.sin6_addr;
488
+ portdst = &inet->in6.sin6_port;
489
+ } else { /* ipv4 */
490
+ colon = memchr(host_ptr, ':', host_len);
491
+ inet->ss.ss_family = af = AF_INET;
492
+ dst = &inet->in.sin_addr;
493
+ portdst = &inet->in.sin_port;
494
+ }
495
+
496
+ if (!colon)
497
+ rb_raise(rb_eArgError, "port not found in: `%s'", host_ptr);
498
+ port = strtoul(colon + 1, &check, 10);
499
+ *colon = 0;
500
+ rc = inet_pton(af, host_ptr, dst);
501
+ *colon = ':';
502
+ if (rbracket) *rbracket = ']';
503
+ if (*check || ((uint16_t)port != port))
504
+ rb_raise(rb_eArgError, "invalid port: %s", colon + 1);
505
+ if (rc != 1)
506
+ rb_raise(rb_eArgError, "inet_pton failed for: `%s' with %d",
507
+ host_ptr, rc);
508
+ *portdst = ntohs((uint16_t)port);
509
+ }
510
+
511
+ /* generates inet_diag bytecode to match all addrs */
512
+ static void gen_bytecode_all(struct iovec *iov)
513
+ {
514
+ struct inet_diag_bc_op *op;
515
+ struct inet_diag_hostcond *cond;
516
+
517
+ /* iov_len was already set and base allocated in a parent function */
518
+ assert(iov->iov_len == OPLEN && iov->iov_base && "iov invalid");
519
+ op = iov->iov_base;
520
+ op->code = INET_DIAG_BC_S_COND;
521
+ op->yes = OPLEN;
522
+ op->no = sizeof(struct inet_diag_bc_op) + OPLEN;
523
+ cond = (struct inet_diag_hostcond *)(op + 1);
524
+ cond->family = AF_UNSPEC;
525
+ cond->port = -1;
526
+ cond->prefix_len = 0;
527
+ }
528
+
529
+ /* generates inet_diag bytecode to match a single addr */
530
+ static void gen_bytecode(struct iovec *iov, union any_addr *inet)
531
+ {
532
+ struct inet_diag_bc_op *op;
533
+ struct inet_diag_hostcond *cond;
534
+
535
+ /* iov_len was already set and base allocated in a parent function */
536
+ assert(iov->iov_len == OPLEN && iov->iov_base && "iov invalid");
537
+ op = iov->iov_base;
538
+ op->code = INET_DIAG_BC_S_COND;
539
+ op->yes = OPLEN;
540
+ op->no = sizeof(struct inet_diag_bc_op) + OPLEN;
541
+
542
+ cond = (struct inet_diag_hostcond *)(op + 1);
543
+ cond->family = inet->ss.ss_family;
544
+ switch (inet->ss.ss_family) {
545
+ case AF_INET: {
546
+ cond->port = ntohs(inet->in.sin_port);
547
+ cond->prefix_len = inet->in.sin_addr.s_addr == 0 ? 0 :
548
+ sizeof(inet->in.sin_addr.s_addr) * CHAR_BIT;
549
+ *cond->addr = inet->in.sin_addr.s_addr;
550
+ }
551
+ break;
552
+ case AF_INET6: {
553
+ cond->port = ntohs(inet->in6.sin6_port);
554
+ cond->prefix_len = memcmp(&in6addr_any, &inet->in6.sin6_addr,
555
+ sizeof(struct in6_addr)) == 0 ?
556
+ 0 : sizeof(inet->in6.sin6_addr) * CHAR_BIT;
557
+ memcpy(&cond->addr, &inet->in6.sin6_addr,
558
+ sizeof(struct in6_addr));
559
+ }
560
+ break;
561
+ default:
562
+ assert(0 && "unsupported address family, could that be IPv7?!");
563
+ }
564
+ }
565
+
566
+ /*
567
+ * n.b. we may safely raise here because an error will cause diag()
568
+ * to free args->table
569
+ */
570
+ static void nl_errcheck(VALUE r)
571
+ {
572
+ const char *err = (const char *)r;
573
+
574
+ if (err) {
575
+ if (err == err_nlmsg)
576
+ rb_raise(rb_eRuntimeError, "NLMSG_ERROR");
577
+ else
578
+ rb_sys_fail(err);
579
+ }
580
+ }
581
+
582
+ static VALUE tcp_stats(struct nogvl_args *args, VALUE addr)
583
+ {
584
+ union any_addr query_addr;
585
+
586
+ parse_addr(&query_addr, addr);
587
+ gen_bytecode(&args->iov[2], &query_addr);
588
+
589
+ memset(&args->stats, 0, sizeof(struct listen_stats));
590
+ nl_errcheck(rd_fd_region(diag, args, args->fd));
591
+
592
+ return rb_listen_stats(&args->stats);
593
+ }
594
+
595
+ static int drop_placeholders(st_data_t k, st_data_t v, st_data_t ign)
596
+ {
597
+ if ((VALUE)v == Qtrue)
598
+ return ST_DELETE;
599
+ return ST_CONTINUE;
600
+ }
601
+
602
+ /*
603
+ * call-seq:
604
+ * Raindrops::Linux.tcp_listener_stats([addrs[, sock]]) => hash
605
+ *
606
+ * If specified, +addr+ may be a string or array of strings representing
607
+ * listen addresses to filter for. Returns a hash with given addresses as
608
+ * keys and ListenStats objects as the values or a hash of all addresses.
609
+ *
610
+ * addrs = %w(0.0.0.0:80 127.0.0.1:8080)
611
+ *
612
+ * If +addr+ is nil or not specified, all (IPv4) addresses are returned.
613
+ * If +sock+ is specified, it should be a Raindrops::InetDiagSock object.
614
+ */
615
+ static VALUE tcp_listener_stats(int argc, VALUE *argv, VALUE self)
616
+ {
617
+ VALUE rv = rb_hash_new();
618
+ struct nogvl_args args;
619
+ VALUE addrs, sock, buf;
620
+
621
+ rb_scan_args(argc, argv, "02", &addrs, &sock);
622
+
623
+ /*
624
+ * allocating page_size instead of OP_LEN since we'll reuse the
625
+ * buffer for recvmsg() later, we already checked for
626
+ * OPLEN <= page_size at initialization
627
+ */
628
+ buf = rb_str_buf_new(page_size);
629
+ args.iov[2].iov_len = OPLEN;
630
+ args.iov[2].iov_base = RSTRING_PTR(buf);
631
+ args.table = NULL;
632
+ sock = NIL_P(sock) ? rb_funcall(cIDSock, id_new, 0)
633
+ : rb_io_get_io(sock);
634
+ args.fd = my_fileno(sock);
635
+
636
+ switch (TYPE(addrs)) {
637
+ case T_STRING:
638
+ rb_hash_aset(rv, addrs, tcp_stats(&args, addrs));
639
+ goto out;
640
+ case T_ARRAY: {
641
+ long i;
642
+ long len = RARRAY_LEN(addrs);
643
+
644
+ if (len == 1) {
645
+ VALUE cur = rb_ary_entry(addrs, 0);
646
+
647
+ rb_hash_aset(rv, cur, tcp_stats(&args, cur));
648
+ goto out;
649
+ }
650
+ for (i = 0; i < len; i++) {
651
+ union any_addr check;
652
+ VALUE cur = rb_ary_entry(addrs, i);
653
+
654
+ parse_addr(&check, cur);
655
+ rb_hash_aset(rv, cur, Qtrue /* placeholder */);
656
+ }
657
+ /* fall through */
658
+ }
659
+ case T_NIL:
660
+ args.table = st_init_strtable();
661
+ gen_bytecode_all(&args.iov[2]);
662
+ break;
663
+ default:
664
+ if (argc < 2) rb_io_close(sock);
665
+ rb_raise(rb_eArgError,
666
+ "addr must be an array of strings, a string, or nil");
667
+ }
668
+
669
+ nl_errcheck(rd_fd_region(diag, &args, args.fd));
670
+
671
+ st_foreach(args.table, NIL_P(addrs) ? st_to_hash : st_AND_hash, rv);
672
+ st_free_table(args.table);
673
+
674
+ if (RHASH_SIZE(rv) > 1)
675
+ rb_hash_foreach(rv, drop_placeholders, Qfalse);
676
+
677
+ out:
678
+ /* let GC deal with corner cases */
679
+ rb_str_resize(buf, 0);
680
+ if (argc < 2) rb_io_close(sock);
681
+ return rv;
682
+ }
683
+
684
+ void Init_raindrops_linux_inet_diag(void)
685
+ {
686
+ VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
687
+ VALUE mLinux = rb_define_module_under(cRaindrops, "Linux");
688
+ VALUE Socket;
689
+
690
+ rb_require("socket");
691
+ Socket = rb_const_get(rb_cObject, rb_intern("Socket"));
692
+ id_new = rb_intern("new");
693
+
694
+ /*
695
+ * Document-class: Raindrops::InetDiagSocket
696
+ *
697
+ * This is a subclass of +Socket+ specifically for talking
698
+ * to the inet_diag facility of Netlink.
699
+ */
700
+ cIDSock = rb_define_class_under(cRaindrops, "InetDiagSocket", Socket);
701
+ rb_define_singleton_method(cIDSock, "new", ids_s_new, 0);
702
+
703
+ cListenStats = rb_const_get(cRaindrops, rb_intern("ListenStats"));
704
+ rb_gc_register_mark_object(cListenStats); /* pin */
705
+
706
+ rb_define_module_function(mLinux, "tcp_listener_stats",
707
+ tcp_listener_stats, -1);
708
+
709
+ page_size = getpagesize();
710
+
711
+ assert(OPLEN <= page_size && "bytecode OPLEN is not <= PAGE_SIZE");
712
+ }
713
+ #endif /* __linux__ */
@@ -0,0 +1,16 @@
1
+ #include <ruby.h>
2
+ #include <ruby/io.h>
3
+
4
+ #ifdef HAVE_RB_IO_DESCRIPTOR
5
+ # define my_fileno(io) rb_io_descriptor(io)
6
+ #else /* Ruby <3.1 */
7
+ static int my_fileno(VALUE io)
8
+ {
9
+ rb_io_t *fptr;
10
+
11
+ GetOpenFile(io, fptr);
12
+ rb_io_check_closed(fptr);
13
+
14
+ return fptr->fd;
15
+ }
16
+ #endif /* Ruby <3.1 !HAVE_RB_IO_DESCRIPTOR */