10 /* Ruby 1.8.6+ macros (for compatibility with Ruby 1.9) */
12 # define RSTRING_LEN(s) (RSTRING(s)->len)
15 /* partial emulation of the 1.9 rb_thread_blocking_region under 1.8 */
16 #ifndef HAVE_RB_THREAD_BLOCKING_REGION
18 typedef void rb_unblock_function_t(void *);
19 typedef VALUE
rb_blocking_function_t(void *);
21 rb_thread_blocking_region(
22 rb_blocking_function_t
*func
, void *data1
,
23 rb_unblock_function_t
*ubf
, void *data2
)
33 #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
37 #include <sys/socket.h>
38 #include <sys/types.h>
42 #include <asm/types.h>
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
45 #include <netinet/tcp.h>
46 #include <linux/netlink.h>
47 #include <linux/rtnetlink.h>
48 #include <linux/inet_diag.h>
50 static size_t page_size
;
51 static unsigned g_seq
;
52 static VALUE cListenStats
, cIDSock
;
57 uint32_t listener_p
:1;
61 #define OPLEN (sizeof(struct inet_diag_bc_op) + \
62 sizeof(struct inet_diag_hostcond) + \
63 sizeof(struct sockaddr_storage))
67 struct iovec iov
[3]; /* last iov holds inet_diag bytecode */
68 struct listen_stats stats
;
74 * Raindrops::InetDiagSocket.new -> Socket
76 * Creates a new Socket object for the netlink inet_diag facility
78 static VALUE
ids_s_new(VALUE klass
)
81 argv
[0] = INT2NUM(AF_NETLINK
);
82 argv
[1] = INT2NUM(SOCK_RAW
);
83 argv
[2] = INT2NUM(NETLINK_INET_DIAG
);
85 return rb_call_super(3, argv
);
88 /* creates a Ruby ListenStats Struct based on our internal listen_stats */
89 static VALUE
rb_listen_stats(struct listen_stats
*stats
)
91 VALUE active
= UINT2NUM(stats
->active
);
92 VALUE queued
= UINT2NUM(stats
->queued
);
94 return rb_struct_new(cListenStats
, active
, queued
);
97 static int st_free_data(st_data_t key
, st_data_t value
, st_data_t ignored
)
100 xfree((void *)value
);
105 static int st_to_hash(st_data_t key
, st_data_t value
, VALUE hash
)
107 struct listen_stats
*stats
= (struct listen_stats
*)value
;
109 if (stats
->listener_p
) {
110 VALUE k
= rb_str_new2((const char *)key
);
111 VALUE v
= rb_listen_stats(stats
);
114 rb_hash_aset(hash
, k
, v
);
116 return st_free_data(key
, value
, 0);
119 static int st_AND_hash(st_data_t key
, st_data_t value
, VALUE hash
)
121 struct listen_stats
*stats
= (struct listen_stats
*)value
;
123 if (stats
->listener_p
) {
124 VALUE k
= rb_str_new2((const char *)key
);
126 if (rb_hash_lookup(hash
, k
) == Qtrue
) {
127 VALUE v
= rb_listen_stats(stats
);
129 rb_hash_aset(hash
, k
, v
);
132 return st_free_data(key
, value
, 0);
135 static const char *addr_any(sa_family_t family
)
137 static const char ipv4
[] = "0.0.0.0";
138 static const char ipv6
[] = "[::]";
140 if (family
== AF_INET
)
142 assert(family
== AF_INET6
&& "unknown family");
146 static void bug_warn(void)
148 fprintf(stderr
, "Please report how you produced this at "\
149 "raindrops@librelist.com\n");
153 static struct listen_stats
*stats_for(st_table
*table
, struct inet_diag_msg
*r
)
155 char *key
, *port
, *old_key
;
157 struct listen_stats
*stats
;
159 size_t portlen
= sizeof("65535");
160 struct sockaddr_storage ss
= { 0 };
161 socklen_t len
= sizeof(struct sockaddr_storage
);
163 int flags
= NI_NUMERICHOST
| NI_NUMERICSERV
;
165 switch ((ss
.ss_family
= r
->idiag_family
)) {
167 struct sockaddr_in
*in
= (struct sockaddr_in
*)&ss
;
169 in
->sin_port
= r
->id
.idiag_sport
;
170 in
->sin_addr
.s_addr
= r
->id
.idiag_src
[0];
171 keylen
= INET_ADDRSTRLEN
;
172 alloca_len
= keylen
+ 1 + portlen
;
173 key
= alloca(alloca_len
);
174 key
[keylen
] = 0; /* will be ':' later */
175 port
= key
+ keylen
+ 1;
176 rc
= getnameinfo((struct sockaddr
*)&ss
, len
,
177 key
, keylen
, port
, portlen
, flags
);
181 struct sockaddr_in6
*in6
= (struct sockaddr_in6
*)&ss
;
182 in6
->sin6_port
= r
->id
.idiag_sport
;
183 memcpy(&in6
->sin6_addr
.in6_u
.u6_addr32
,
184 &r
->id
.idiag_src
, sizeof(__be32
[4]));
185 keylen
= INET6_ADDRSTRLEN
;
187 alloca_len
= 1 + keylen
+ 1 + 1 + portlen
;
188 key
= alloca(alloca_len
);
190 key
[1 + keylen
+ 1] = 0; /* will be ':' later */
191 port
= 1 + key
+ keylen
+ 1 + 1;
192 rc
= getnameinfo((struct sockaddr
*)&ss
, len
,
193 key
+ 1, keylen
, port
, portlen
, flags
);
197 assert(0 && "unsupported address family, could that be IPv7?!");
200 fprintf(stderr
, "BUG: getnameinfo: %s\n", gai_strerror(rc
));
205 keylen
= strlen(key
);
206 portlen
= strlen(port
);
208 switch (ss
.ss_family
) {
211 memmove(key
+ keylen
+ 1, port
, portlen
+ 1);
215 key
[keylen
+ 1] = ':';
216 memmove(key
+ keylen
+ 2, port
, portlen
+ 1);
220 assert(0 && "unsupported address family, could that be IPv7?!");
223 if (st_lookup(table
, (st_data_t
)key
, (st_data_t
*)&stats
))
228 if (r
->idiag_state
== TCP_ESTABLISHED
) {
229 int n
= snprintf(key
, alloca_len
, "%s:%u",
230 addr_any(ss
.ss_family
),
231 ntohs(r
->id
.idiag_sport
));
233 fprintf(stderr
, "BUG: snprintf: %d\n", n
);
236 if (st_lookup(table
, (st_data_t
)key
, (st_data_t
*)&stats
))
243 key
= xmalloc(n
+ 1);
244 memcpy(key
, old_key
, n
+ 1);
247 key
= xmalloc(keylen
+ 1 + portlen
+ 1);
248 memcpy(key
, old_key
, keylen
+ 1 + portlen
+ 1);
250 stats
= xcalloc(1, sizeof(struct listen_stats
));
251 st_insert(table
, (st_data_t
)key
, (st_data_t
)stats
);
255 static void table_incr_active(st_table
*table
, struct inet_diag_msg
*r
)
257 struct listen_stats
*stats
= stats_for(table
, r
);
261 static void table_set_queued(st_table
*table
, struct inet_diag_msg
*r
)
263 struct listen_stats
*stats
= stats_for(table
, r
);
264 stats
->listener_p
= 1;
265 stats
->queued
= r
->idiag_rqueue
;
268 /* inner loop of inet_diag, called for every socket returned by netlink */
269 static inline void r_acc(struct nogvl_args
*args
, struct inet_diag_msg
*r
)
272 * inode == 0 means the connection is still in the listen queue
273 * and has not yet been accept()-ed by the server. The
274 * inet_diag bytecode cannot filter this for us.
276 if (r
->idiag_inode
== 0)
278 if (r
->idiag_state
== TCP_ESTABLISHED
) {
280 table_incr_active(args
->table
, r
);
282 args
->stats
.active
++;
283 } else { /* if (r->idiag_state == TCP_LISTEN) */
285 table_set_queued(args
->table
, r
);
287 args
->stats
.queued
= r
->idiag_rqueue
;
290 * we wont get anything else because of the idiag_states filter
294 static const char err_sendmsg
[] = "sendmsg";
295 static const char err_recvmsg
[] = "recvmsg";
296 static const char err_nlmsg
[] = "nlmsg";
300 struct inet_diag_req r
;
303 static void prep_msghdr(
305 struct nogvl_args
*args
,
306 struct sockaddr_nl
*nladdr
,
309 memset(msg
, 0, sizeof(struct msghdr
));
310 msg
->msg_name
= (void *)nladdr
;
311 msg
->msg_namelen
= sizeof(struct sockaddr_nl
);
312 msg
->msg_iov
= args
->iov
;
313 msg
->msg_iovlen
= iovlen
;
316 static void prep_diag_args(
317 struct nogvl_args
*args
,
318 struct sockaddr_nl
*nladdr
,
320 struct diag_req
*req
,
323 memset(req
, 0, sizeof(struct diag_req
));
324 memset(nladdr
, 0, sizeof(struct sockaddr_nl
));
326 nladdr
->nl_family
= AF_NETLINK
;
328 req
->nlh
.nlmsg_len
= sizeof(struct diag_req
) +
329 RTA_LENGTH(args
->iov
[2].iov_len
);
330 req
->nlh
.nlmsg_type
= TCPDIAG_GETSOCK
;
331 req
->nlh
.nlmsg_flags
= NLM_F_ROOT
| NLM_F_MATCH
| NLM_F_REQUEST
;
332 req
->nlh
.nlmsg_pid
= getpid();
333 req
->r
.idiag_states
= (1<<TCP_ESTABLISHED
) | (1<<TCP_LISTEN
);
334 rta
->rta_type
= INET_DIAG_REQ_BYTECODE
;
335 rta
->rta_len
= RTA_LENGTH(args
->iov
[2].iov_len
);
337 args
->iov
[0].iov_base
= req
;
338 args
->iov
[0].iov_len
= sizeof(struct diag_req
);
339 args
->iov
[1].iov_base
= rta
;
340 args
->iov
[1].iov_len
= sizeof(struct rtattr
);
342 prep_msghdr(msg
, args
, nladdr
, 3);
345 static void prep_recvmsg_buf(struct nogvl_args
*args
)
347 /* reuse buffer that was allocated for bytecode */
348 args
->iov
[0].iov_len
= page_size
;
349 args
->iov
[0].iov_base
= args
->iov
[2].iov_base
;
352 /* does the inet_diag stuff with netlink(), this is called w/o GVL */
353 static VALUE
diag(void *ptr
)
355 struct nogvl_args
*args
= ptr
;
356 struct sockaddr_nl nladdr
;
360 const char *err
= NULL
;
361 unsigned seq
= ++g_seq
;
363 prep_diag_args(args
, &nladdr
, &rta
, &req
, &msg
);
364 req
.nlh
.nlmsg_seq
= seq
;
366 if (sendmsg(args
->fd
, &msg
, 0) < 0) {
371 prep_recvmsg_buf(args
);
376 struct nlmsghdr
*h
= (struct nlmsghdr
*)args
->iov
[0].iov_base
;
378 prep_msghdr(&msg
, args
, &nladdr
, 1);
379 readed
= recvmsg(args
->fd
, &msg
, 0);
389 for ( ; NLMSG_OK(h
, r
); h
= NLMSG_NEXT(h
, r
)) {
390 if (h
->nlmsg_seq
!= seq
)
392 if (h
->nlmsg_type
== NLMSG_DONE
)
394 if (h
->nlmsg_type
== NLMSG_ERROR
) {
398 r_acc(args
, NLMSG_DATA(h
));
403 int save_errno
= errno
;
404 if (err
&& args
->table
) {
405 st_foreach(args
->table
, st_free_data
, 0);
406 st_free_table(args
->table
);
413 /* populates sockaddr_storage struct by parsing +addr+ */
414 static void parse_addr(struct sockaddr_storage
*inet
, VALUE addr
)
419 char *rbracket
= NULL
;
426 Check_Type(addr
, T_STRING
);
427 host_ptr
= StringValueCStr(addr
);
428 host_len
= RSTRING_LEN(addr
);
429 if (*host_ptr
== '[') { /* ipv6 address format (rfc2732) */
430 struct sockaddr_in6
*in6
= (struct sockaddr_in6
*)inet
;
431 rbracket
= memchr(host_ptr
+ 1, ']', host_len
- 1);
433 if (rbracket
== NULL
)
434 rb_raise(rb_eArgError
, "']' not found in IPv6 addr=%s",
436 if (rbracket
[1] != ':')
437 rb_raise(rb_eArgError
, "':' not found in IPv6 addr=%s",
439 colon
= rbracket
+ 1;
442 inet
->ss_family
= af
= AF_INET6
;
443 dst
= &in6
->sin6_addr
;
444 portdst
= &in6
->sin6_port
;
446 struct sockaddr_in
*in
= (struct sockaddr_in
*)inet
;
447 colon
= memchr(host_ptr
, ':', host_len
);
448 inet
->ss_family
= af
= AF_INET
;
450 portdst
= &in
->sin_port
;
454 rb_raise(rb_eArgError
, "port not found in: `%s'", host_ptr
);
455 port
= strtoul(colon
+ 1, &check
, 10);
457 rc
= inet_pton(af
, host_ptr
, dst
);
459 if (rbracket
) *rbracket
= ']';
460 if (*check
|| ((uint16_t)port
!= port
))
461 rb_raise(rb_eArgError
, "invalid port: %s", colon
+ 1);
463 rb_raise(rb_eArgError
, "inet_pton failed for: `%s' with %d",
465 *portdst
= ntohs((uint16_t)port
);
468 /* generates inet_diag bytecode to match all addrs */
469 static void gen_bytecode_all(struct iovec
*iov
)
471 struct inet_diag_bc_op
*op
;
472 struct inet_diag_hostcond
*cond
;
474 /* iov_len was already set and base allocated in a parent function */
475 assert(iov
->iov_len
== OPLEN
&& iov
->iov_base
&& "iov invalid");
477 op
->code
= INET_DIAG_BC_S_COND
;
479 op
->no
= sizeof(struct inet_diag_bc_op
) + OPLEN
;
480 cond
= (struct inet_diag_hostcond
*)(op
+ 1);
481 cond
->family
= AF_UNSPEC
;
483 cond
->prefix_len
= 0;
486 /* generates inet_diag bytecode to match a single addr */
487 static void gen_bytecode(struct iovec
*iov
, struct sockaddr_storage
*inet
)
489 struct inet_diag_bc_op
*op
;
490 struct inet_diag_hostcond
*cond
;
492 /* iov_len was already set and base allocated in a parent function */
493 assert(iov
->iov_len
== OPLEN
&& iov
->iov_base
&& "iov invalid");
495 op
->code
= INET_DIAG_BC_S_COND
;
497 op
->no
= sizeof(struct inet_diag_bc_op
) + OPLEN
;
499 cond
= (struct inet_diag_hostcond
*)(op
+ 1);
500 cond
->family
= inet
->ss_family
;
501 switch (inet
->ss_family
) {
503 struct sockaddr_in
*in
= (struct sockaddr_in
*)inet
;
505 cond
->port
= ntohs(in
->sin_port
);
506 cond
->prefix_len
= in
->sin_addr
.s_addr
== 0 ? 0 :
507 sizeof(in
->sin_addr
.s_addr
) * CHAR_BIT
;
508 *cond
->addr
= in
->sin_addr
.s_addr
;
512 struct sockaddr_in6
*in6
= (struct sockaddr_in6
*)inet
;
514 cond
->port
= ntohs(in6
->sin6_port
);
515 cond
->prefix_len
= memcmp(&in6addr_any
, &in6
->sin6_addr
,
516 sizeof(struct in6_addr
)) == 0 ?
517 0 : sizeof(in6
->sin6_addr
) * CHAR_BIT
;
518 memcpy(&cond
->addr
, &in6
->sin6_addr
, sizeof(struct in6_addr
));
522 assert(0 && "unsupported address family, could that be IPv7?!");
526 static void nl_errcheck(VALUE r
)
528 const char *err
= (const char *)r
;
531 if (err
== err_nlmsg
)
532 rb_raise(rb_eRuntimeError
, "NLMSG_ERROR");
538 static VALUE
tcp_stats(struct nogvl_args
*args
, VALUE addr
)
540 struct sockaddr_storage query_addr
;
542 parse_addr(&query_addr
, addr
);
543 gen_bytecode(&args
->iov
[2], &query_addr
);
545 memset(&args
->stats
, 0, sizeof(struct listen_stats
));
546 nl_errcheck(rb_thread_blocking_region(diag
, args
, 0, 0));
548 return rb_listen_stats(&args
->stats
);
553 * Raindrops::Linux.tcp_listener_stats([addrs[, sock]]) => hash
555 * If specified, +addr+ may be a string or array of strings representing
556 * listen addresses to filter for. Returns a hash with given addresses as
557 * keys and ListenStats objects as the values or a hash of all addresses.
559 * addrs = %w(0.0.0.0:80 127.0.0.1:8080)
561 * If +addr+ is nil or not specified, all (IPv4) addresses are returned.
562 * If +sock+ is specified, it should be a Raindrops::InetDiagSock object.
564 static VALUE
tcp_listener_stats(int argc
, VALUE
*argv
, VALUE self
)
568 VALUE rv
= rb_hash_new();
569 struct nogvl_args args
;
572 rb_scan_args(argc
, argv
, "02", &addrs
, &sock
);
575 * allocating page_size instead of OP_LEN since we'll reuse the
576 * buffer for recvmsg() later, we already checked for
577 * OPLEN <= page_size at initialization
579 args
.iov
[2].iov_len
= OPLEN
;
580 args
.iov
[2].iov_base
= alloca(page_size
);
583 sock
= rb_funcall(cIDSock
, id_new
, 0);
584 args
.fd
= my_fileno(sock
);
586 switch (TYPE(addrs
)) {
588 rb_hash_aset(rv
, addrs
, tcp_stats(&args
, addrs
));
591 ary
= RARRAY_PTR(addrs
);
592 i
= RARRAY_LEN(addrs
);
594 rb_hash_aset(rv
, *ary
, tcp_stats(&args
, *ary
));
597 for (; --i
>= 0; ary
++) {
598 struct sockaddr_storage check
;
600 parse_addr(&check
, *ary
);
601 rb_hash_aset(rv
, *ary
, Qtrue
);
605 args
.table
= st_init_strtable();
606 gen_bytecode_all(&args
.iov
[2]);
609 rb_raise(rb_eArgError
,
610 "addr must be an array of strings, a string, or nil");
613 nl_errcheck(rb_thread_blocking_region(diag
, &args
, NULL
, 0));
615 st_foreach(args
.table
, NIL_P(addrs
) ? st_to_hash
: st_AND_hash
, rv
);
616 st_free_table(args
.table
);
618 /* let GC deal with corner cases */
619 if (argc
< 2) rb_io_close(sock
);
623 void Init_raindrops_linux_inet_diag(void)
625 VALUE cRaindrops
= rb_const_get(rb_cObject
, rb_intern("Raindrops"));
626 VALUE mLinux
= rb_define_module_under(cRaindrops
, "Linux");
628 rb_require("socket");
629 cIDSock
= rb_const_get(rb_cObject
, rb_intern("Socket"));
630 id_new
= rb_intern("new");
633 * Document-class: Raindrops::InetDiagSocket
635 * This is a subclass of +Socket+ specifically for talking
636 * to the inet_diag facility of Netlink.
638 cIDSock
= rb_define_class_under(cRaindrops
, "InetDiagSocket", cIDSock
);
639 rb_define_singleton_method(cIDSock
, "new", ids_s_new
, 0);
641 cListenStats
= rb_const_get(cRaindrops
, rb_intern("ListenStats"));
643 rb_define_module_function(mLinux
, "tcp_listener_stats",
644 tcp_listener_stats
, -1);
646 page_size
= getpagesize();
648 assert(OPLEN
<= page_size
&& "bytecode OPLEN is not <= PAGE_SIZE");
650 #endif /* __linux__ */