11 /* Ruby 1.8.6+ macros (for compatibility with Ruby 1.9) */
13 # define RSTRING_LEN(s) (RSTRING(s)->len)
16 /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
17 #if !defined(HAVE_RB_THREAD_BLOCKING_REGION) && \
18 !defined(HAVE_RB_THREAD_IO_BLOCKING_REGION)
20 # define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
21 typedef void rb_unblock_function_t(void *);
22 typedef VALUE
rb_blocking_function_t(void *);
24 rb_thread_blocking_region(
25 rb_blocking_function_t
*func
, void *data1
,
26 rb_unblock_function_t
*ubf
, void *data2
)
36 #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
38 #ifdef HAVE_RB_THREAD_IO_BLOCKING_REGION
39 VALUE
rb_thread_io_blocking_region(rb_blocking_function_t
*, void *, int);
41 # define rb_thread_io_blocking_region(fn,data,fd) \
42 rb_thread_blocking_region((fn),(data),RUBY_UBF_IO,0)
43 #endif /* HAVE_RB_THREAD_IO_BLOCKING_REGION */
47 #include <sys/socket.h>
48 #include <sys/types.h>
53 #include <asm/types.h>
54 #include <netinet/in.h>
55 #include <arpa/inet.h>
56 #include <netinet/tcp.h>
57 #include <linux/netlink.h>
58 #include <linux/rtnetlink.h>
59 #include <linux/inet_diag.h>
62 struct sockaddr_storage ss
;
64 struct sockaddr_in in
;
65 struct sockaddr_in6 in6
;
68 static size_t page_size
;
69 static unsigned g_seq
;
70 static VALUE cListenStats
, cIDSock
;
79 #define OPLEN (sizeof(struct inet_diag_bc_op) + \
80 sizeof(struct inet_diag_hostcond) + \
81 sizeof(struct sockaddr_storage))
85 struct iovec iov
[3]; /* last iov holds inet_diag bytecode */
86 struct listen_stats stats
;
91 # define my_SOCK_RAW (SOCK_RAW|SOCK_CLOEXEC)
92 # define FORCE_CLOEXEC(v) (v)
94 # define my_SOCK_RAW SOCK_RAW
95 static VALUE
FORCE_CLOEXEC(VALUE io
)
97 int fd
= my_fileno(io
);
98 int flags
= fcntl(fd
, F_SETFD
, FD_CLOEXEC
);
100 rb_sys_fail("fcntl(F_SETFD, FD_CLOEXEC)");
107 * Raindrops::InetDiagSocket.new -> Socket
109 * Creates a new Socket object for the netlink inet_diag facility
111 static VALUE
ids_s_new(VALUE klass
)
115 argv
[0] = INT2NUM(AF_NETLINK
);
116 argv
[1] = INT2NUM(my_SOCK_RAW
);
117 argv
[2] = INT2NUM(NETLINK_INET_DIAG
);
119 return FORCE_CLOEXEC(rb_call_super(3, argv
));
122 /* creates a Ruby ListenStats Struct based on our internal listen_stats */
123 static VALUE
rb_listen_stats(struct listen_stats
*stats
)
125 VALUE active
= UINT2NUM(stats
->active
);
126 VALUE queued
= UINT2NUM(stats
->queued
);
128 return rb_struct_new(cListenStats
, active
, queued
);
131 static int st_free_data(st_data_t key
, st_data_t value
, st_data_t ignored
)
134 xfree((void *)value
);
141 * remove_scope_id(ip_address)
143 * Returns copy of IP address with Scope ID removed,
144 * if address has it (only IPv6 actually may have it).
146 static VALUE
remove_scope_id(const char *addr
)
148 VALUE rv
= rb_str_new2(addr
);
149 long len
= RSTRING_LEN(rv
);
150 char *ptr
= RSTRING_PTR(rv
);
151 char *pct
= memchr(ptr
, '%', len
);
154 * remove scoped portion
155 * Ruby equivalent: rv.sub!(/%([^\]]*)\]/, "]")
158 size_t newlen
= pct
- ptr
;
159 char *rbracket
= memchr(pct
, ']', len
- newlen
);
162 size_t move
= len
- (rbracket
- ptr
);
164 memmove(pct
, rbracket
, move
);
167 rb_str_set_len(rv
, newlen
);
169 rb_raise(rb_eArgError
,
170 "']' not found in IPv6 addr=%s", ptr
);
176 static int st_to_hash(st_data_t key
, st_data_t value
, VALUE hash
)
178 struct listen_stats
*stats
= (struct listen_stats
*)value
;
180 if (stats
->listener_p
) {
181 VALUE k
= remove_scope_id((const char *)key
);
182 VALUE v
= rb_listen_stats(stats
);
185 rb_hash_aset(hash
, k
, v
);
187 return st_free_data(key
, value
, 0);
190 static int st_AND_hash(st_data_t key
, st_data_t value
, VALUE hash
)
192 struct listen_stats
*stats
= (struct listen_stats
*)value
;
194 if (stats
->listener_p
) {
195 VALUE k
= remove_scope_id((const char *)key
);
197 if (rb_hash_lookup(hash
, k
) == Qtrue
) {
198 VALUE v
= rb_listen_stats(stats
);
200 rb_hash_aset(hash
, k
, v
);
203 return st_free_data(key
, value
, 0);
206 static const char *addr_any(sa_family_t family
)
208 static const char ipv4
[] = "0.0.0.0";
209 static const char ipv6
[] = "[::]";
211 if (family
== AF_INET
)
213 assert(family
== AF_INET6
&& "unknown family");
218 static void bug_warn_nogvl(const char *, ...)
219 __attribute__((format(printf
,1,2)));
221 static void bug_warn_nogvl(const char *fmt
, ...)
226 vfprintf(stderr
, fmt
, ap
);
229 fprintf(stderr
, "Please report how you produced this at "\
230 "raindrops-public@bogomips.org\n");
234 static struct listen_stats
*stats_for(st_table
*table
, struct inet_diag_msg
*r
)
236 char *host
, *key
, *port
, *old_key
;
238 struct listen_stats
*stats
;
240 socklen_t portlen
= (socklen_t
)sizeof("65535");
242 const void *src
= r
->id
.idiag_src
;
244 switch (r
->idiag_family
) {
246 hostlen
= INET_ADDRSTRLEN
;
247 alloca_len
= hostlen
+ portlen
;
248 host
= key
= alloca(alloca_len
);
252 hostlen
= INET6_ADDRSTRLEN
;
253 alloca_len
= 1 + hostlen
+ 1 + portlen
;
254 key
= alloca(alloca_len
);
259 assert(0 && "unsupported address family, could that be IPv7?!");
261 if (!inet_ntop(r
->idiag_family
, src
, host
, hostlen
)) {
262 bug_warn_nogvl("BUG: inet_ntop: %s\n", strerror(errno
));
266 hostlen
= (socklen_t
)strlen(host
);
267 switch (r
->idiag_family
) {
270 port
= host
+ hostlen
+ 1;
275 host
[hostlen
+ 1] = ':';
276 port
= host
+ hostlen
+ 2;
279 assert(0 && "unsupported address family, could that be IPv7?!");
282 n
= snprintf(port
, portlen
, "%u", ntohs(r
->id
.idiag_sport
));
284 bug_warn_nogvl("BUG: snprintf port: %d\n", n
);
288 if (st_lookup(table
, (st_data_t
)key
, (st_data_t
*)&stats
))
293 if (r
->idiag_state
== TCP_ESTABLISHED
) {
294 n
= snprintf(key
, alloca_len
, "%s:%u",
295 addr_any(r
->idiag_family
),
296 ntohs(r
->id
.idiag_sport
));
298 bug_warn_nogvl("BUG: snprintf: %d\n", n
);
301 if (st_lookup(table
, (st_data_t
)key
, (st_data_t
*)&stats
))
308 key
= xmalloc(n
+ 1);
309 memcpy(key
, old_key
, n
+ 1);
312 size_t old_len
= strlen(old_key
) + 1;
313 key
= xmalloc(old_len
);
314 memcpy(key
, old_key
, old_len
);
316 stats
= xcalloc(1, sizeof(struct listen_stats
));
317 st_insert(table
, (st_data_t
)key
, (st_data_t
)stats
);
321 static void table_incr_active(st_table
*table
, struct inet_diag_msg
*r
)
323 struct listen_stats
*stats
= stats_for(table
, r
);
327 static void table_set_queued(st_table
*table
, struct inet_diag_msg
*r
)
329 struct listen_stats
*stats
= stats_for(table
, r
);
330 stats
->listener_p
= 1;
331 stats
->queued
= r
->idiag_rqueue
;
334 /* inner loop of inet_diag, called for every socket returned by netlink */
335 static inline void r_acc(struct nogvl_args
*args
, struct inet_diag_msg
*r
)
338 * inode == 0 means the connection is still in the listen queue
339 * and has not yet been accept()-ed by the server. The
340 * inet_diag bytecode cannot filter this for us.
342 if (r
->idiag_inode
== 0)
344 if (r
->idiag_state
== TCP_ESTABLISHED
) {
346 table_incr_active(args
->table
, r
);
348 args
->stats
.active
++;
349 } else { /* if (r->idiag_state == TCP_LISTEN) */
351 table_set_queued(args
->table
, r
);
353 args
->stats
.queued
= r
->idiag_rqueue
;
356 * we wont get anything else because of the idiag_states filter
360 static const char err_sendmsg
[] = "sendmsg";
361 static const char err_recvmsg
[] = "recvmsg";
362 static const char err_nlmsg
[] = "nlmsg";
366 struct inet_diag_req r
;
369 static void prep_msghdr(
371 struct nogvl_args
*args
,
372 struct sockaddr_nl
*nladdr
,
375 memset(msg
, 0, sizeof(struct msghdr
));
376 msg
->msg_name
= (void *)nladdr
;
377 msg
->msg_namelen
= sizeof(struct sockaddr_nl
);
378 msg
->msg_iov
= args
->iov
;
379 msg
->msg_iovlen
= iovlen
;
382 static void prep_diag_args(
383 struct nogvl_args
*args
,
384 struct sockaddr_nl
*nladdr
,
386 struct diag_req
*req
,
389 memset(req
, 0, sizeof(struct diag_req
));
390 memset(nladdr
, 0, sizeof(struct sockaddr_nl
));
392 nladdr
->nl_family
= AF_NETLINK
;
394 req
->nlh
.nlmsg_len
= (unsigned int)(sizeof(struct diag_req
) +
395 RTA_LENGTH(args
->iov
[2].iov_len
));
396 req
->nlh
.nlmsg_type
= TCPDIAG_GETSOCK
;
397 req
->nlh
.nlmsg_flags
= NLM_F_ROOT
| NLM_F_MATCH
| NLM_F_REQUEST
;
398 req
->nlh
.nlmsg_pid
= getpid();
399 req
->r
.idiag_states
= (1<<TCP_ESTABLISHED
) | (1<<TCP_LISTEN
);
400 rta
->rta_type
= INET_DIAG_REQ_BYTECODE
;
401 rta
->rta_len
= RTA_LENGTH(args
->iov
[2].iov_len
);
403 args
->iov
[0].iov_base
= req
;
404 args
->iov
[0].iov_len
= sizeof(struct diag_req
);
405 args
->iov
[1].iov_base
= rta
;
406 args
->iov
[1].iov_len
= sizeof(struct rtattr
);
408 prep_msghdr(msg
, args
, nladdr
, 3);
411 static void prep_recvmsg_buf(struct nogvl_args
*args
)
413 /* reuse buffer that was allocated for bytecode */
414 args
->iov
[0].iov_len
= page_size
;
415 args
->iov
[0].iov_base
= args
->iov
[2].iov_base
;
418 /* does the inet_diag stuff with netlink(), this is called w/o GVL */
419 static VALUE
diag(void *ptr
)
421 struct nogvl_args
*args
= ptr
;
422 struct sockaddr_nl nladdr
;
426 const char *err
= NULL
;
427 unsigned seq
= ++g_seq
;
429 prep_diag_args(args
, &nladdr
, &rta
, &req
, &msg
);
430 req
.nlh
.nlmsg_seq
= seq
;
432 if (sendmsg(args
->fd
, &msg
, 0) < 0) {
437 prep_recvmsg_buf(args
);
442 struct nlmsghdr
*h
= (struct nlmsghdr
*)args
->iov
[0].iov_base
;
444 prep_msghdr(&msg
, args
, &nladdr
, 1);
445 readed
= recvmsg(args
->fd
, &msg
, 0);
455 for ( ; NLMSG_OK(h
, r
); h
= NLMSG_NEXT(h
, r
)) {
456 if (h
->nlmsg_seq
!= seq
)
458 if (h
->nlmsg_type
== NLMSG_DONE
)
460 if (h
->nlmsg_type
== NLMSG_ERROR
) {
464 r_acc(args
, NLMSG_DATA(h
));
468 /* prepare to raise, free memory before reacquiring GVL */
469 if (err
&& args
->table
) {
470 int save_errno
= errno
;
472 st_foreach(args
->table
, st_free_data
, 0);
473 st_free_table(args
->table
);
479 /* populates sockaddr_storage struct by parsing +addr+ */
480 static void parse_addr(union any_addr
*inet
, VALUE addr
)
485 char *rbracket
= NULL
;
492 Check_Type(addr
, T_STRING
);
493 host_ptr
= StringValueCStr(addr
);
494 host_len
= RSTRING_LEN(addr
);
495 if (*host_ptr
== '[') { /* ipv6 address format (rfc2732) */
496 rbracket
= memchr(host_ptr
+ 1, ']', host_len
- 1);
498 if (rbracket
== NULL
)
499 rb_raise(rb_eArgError
, "']' not found in IPv6 addr=%s",
501 if (rbracket
[1] != ':')
502 rb_raise(rb_eArgError
, "':' not found in IPv6 addr=%s",
504 colon
= rbracket
+ 1;
507 inet
->ss
.ss_family
= af
= AF_INET6
;
508 dst
= &inet
->in6
.sin6_addr
;
509 portdst
= &inet
->in6
.sin6_port
;
511 colon
= memchr(host_ptr
, ':', host_len
);
512 inet
->ss
.ss_family
= af
= AF_INET
;
513 dst
= &inet
->in
.sin_addr
;
514 portdst
= &inet
->in
.sin_port
;
518 rb_raise(rb_eArgError
, "port not found in: `%s'", host_ptr
);
519 port
= strtoul(colon
+ 1, &check
, 10);
521 rc
= inet_pton(af
, host_ptr
, dst
);
523 if (rbracket
) *rbracket
= ']';
524 if (*check
|| ((uint16_t)port
!= port
))
525 rb_raise(rb_eArgError
, "invalid port: %s", colon
+ 1);
527 rb_raise(rb_eArgError
, "inet_pton failed for: `%s' with %d",
529 *portdst
= ntohs((uint16_t)port
);
532 /* generates inet_diag bytecode to match all addrs */
533 static void gen_bytecode_all(struct iovec
*iov
)
535 struct inet_diag_bc_op
*op
;
536 struct inet_diag_hostcond
*cond
;
538 /* iov_len was already set and base allocated in a parent function */
539 assert(iov
->iov_len
== OPLEN
&& iov
->iov_base
&& "iov invalid");
541 op
->code
= INET_DIAG_BC_S_COND
;
543 op
->no
= sizeof(struct inet_diag_bc_op
) + OPLEN
;
544 cond
= (struct inet_diag_hostcond
*)(op
+ 1);
545 cond
->family
= AF_UNSPEC
;
547 cond
->prefix_len
= 0;
550 /* generates inet_diag bytecode to match a single addr */
551 static void gen_bytecode(struct iovec
*iov
, union any_addr
*inet
)
553 struct inet_diag_bc_op
*op
;
554 struct inet_diag_hostcond
*cond
;
556 /* iov_len was already set and base allocated in a parent function */
557 assert(iov
->iov_len
== OPLEN
&& iov
->iov_base
&& "iov invalid");
559 op
->code
= INET_DIAG_BC_S_COND
;
561 op
->no
= sizeof(struct inet_diag_bc_op
) + OPLEN
;
563 cond
= (struct inet_diag_hostcond
*)(op
+ 1);
564 cond
->family
= inet
->ss
.ss_family
;
565 switch (inet
->ss
.ss_family
) {
567 cond
->port
= ntohs(inet
->in
.sin_port
);
568 cond
->prefix_len
= inet
->in
.sin_addr
.s_addr
== 0 ? 0 :
569 sizeof(inet
->in
.sin_addr
.s_addr
) * CHAR_BIT
;
570 *cond
->addr
= inet
->in
.sin_addr
.s_addr
;
574 cond
->port
= ntohs(inet
->in6
.sin6_port
);
575 cond
->prefix_len
= memcmp(&in6addr_any
, &inet
->in6
.sin6_addr
,
576 sizeof(struct in6_addr
)) == 0 ?
577 0 : sizeof(inet
->in6
.sin6_addr
) * CHAR_BIT
;
578 memcpy(&cond
->addr
, &inet
->in6
.sin6_addr
,
579 sizeof(struct in6_addr
));
583 assert(0 && "unsupported address family, could that be IPv7?!");
588 * n.b. we may safely raise here because an error will cause diag()
589 * to free args->table
591 static void nl_errcheck(VALUE r
)
593 const char *err
= (const char *)r
;
596 if (err
== err_nlmsg
)
597 rb_raise(rb_eRuntimeError
, "NLMSG_ERROR");
603 static VALUE
tcp_stats(struct nogvl_args
*args
, VALUE addr
)
605 union any_addr query_addr
;
607 parse_addr(&query_addr
, addr
);
608 gen_bytecode(&args
->iov
[2], &query_addr
);
610 memset(&args
->stats
, 0, sizeof(struct listen_stats
));
611 nl_errcheck(rb_thread_io_blocking_region(diag
, args
, args
->fd
));
613 return rb_listen_stats(&args
->stats
);
616 static int drop_placeholders(st_data_t k
, st_data_t v
, st_data_t ign
)
618 if ((VALUE
)v
== Qtrue
)
625 * Raindrops::Linux.tcp_listener_stats([addrs[, sock]]) => hash
627 * If specified, +addr+ may be a string or array of strings representing
628 * listen addresses to filter for. Returns a hash with given addresses as
629 * keys and ListenStats objects as the values or a hash of all addresses.
631 * addrs = %w(0.0.0.0:80 127.0.0.1:8080)
633 * If +addr+ is nil or not specified, all (IPv4) addresses are returned.
634 * If +sock+ is specified, it should be a Raindrops::InetDiagSock object.
636 static VALUE
tcp_listener_stats(int argc
, VALUE
*argv
, VALUE self
)
638 VALUE rv
= rb_hash_new();
639 struct nogvl_args args
;
642 rb_scan_args(argc
, argv
, "02", &addrs
, &sock
);
645 * allocating page_size instead of OP_LEN since we'll reuse the
646 * buffer for recvmsg() later, we already checked for
647 * OPLEN <= page_size at initialization
649 args
.iov
[2].iov_len
= OPLEN
;
650 args
.iov
[2].iov_base
= alloca(page_size
);
653 sock
= rb_funcall(cIDSock
, id_new
, 0);
654 args
.fd
= my_fileno(sock
);
656 switch (TYPE(addrs
)) {
658 rb_hash_aset(rv
, addrs
, tcp_stats(&args
, addrs
));
662 long len
= RARRAY_LEN(addrs
);
665 VALUE cur
= rb_ary_entry(addrs
, 0);
667 rb_hash_aset(rv
, cur
, tcp_stats(&args
, cur
));
670 for (i
= 0; i
< len
; i
++) {
671 union any_addr check
;
672 VALUE cur
= rb_ary_entry(addrs
, i
);
674 parse_addr(&check
, cur
);
675 rb_hash_aset(rv
, cur
, Qtrue
/* placeholder */);
680 args
.table
= st_init_strtable();
681 gen_bytecode_all(&args
.iov
[2]);
684 rb_raise(rb_eArgError
,
685 "addr must be an array of strings, a string, or nil");
688 nl_errcheck(rb_thread_io_blocking_region(diag
, &args
, args
.fd
));
690 st_foreach(args
.table
, NIL_P(addrs
) ? st_to_hash
: st_AND_hash
, rv
);
691 st_free_table(args
.table
);
693 if (RHASH_SIZE(rv
) > 1)
694 rb_hash_foreach(rv
, drop_placeholders
, Qfalse
);
696 /* let GC deal with corner cases */
697 if (argc
< 2) rb_io_close(sock
);
701 void Init_raindrops_linux_inet_diag(void)
703 VALUE cRaindrops
= rb_define_class("Raindrops", rb_cObject
);
704 VALUE mLinux
= rb_define_module_under(cRaindrops
, "Linux");
707 rb_require("socket");
708 Socket
= rb_const_get(rb_cObject
, rb_intern("Socket"));
709 id_new
= rb_intern("new");
712 * Document-class: Raindrops::InetDiagSocket
714 * This is a subclass of +Socket+ specifically for talking
715 * to the inet_diag facility of Netlink.
717 cIDSock
= rb_define_class_under(cRaindrops
, "InetDiagSocket", Socket
);
718 rb_define_singleton_method(cIDSock
, "new", ids_s_new
, 0);
720 cListenStats
= rb_const_get(cRaindrops
, rb_intern("ListenStats"));
722 rb_define_module_function(mLinux
, "tcp_listener_stats",
723 tcp_listener_stats
, -1);
725 page_size
= getpagesize();
727 assert(OPLEN
<= page_size
&& "bytecode OPLEN is not <= PAGE_SIZE");
729 #endif /* __linux__ */