From 17d57231c4d9d3dfb486b6d37b85d57e6065958e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 16 Mar 2017 03:16:51 +0000 Subject: tcp_info: support this struct under FreeBSD Of course these fields are not portable between Linux and FreeBSD, but they should remain ABI-compatible for future versions of each OS. Tested on FreeBSD 10.3-RELEASE i386 TCP state names will be another problem... --- ext/raindrops/extconf.rb | 78 +++++++++++++++- ext/raindrops/linux_tcp_info.c | 196 ----------------------------------------- ext/raindrops/raindrops.c | 8 +- ext/raindrops/tcp_info.c | 195 ++++++++++++++++++++++++++++++++++++++++ test/test_linux_tcp_info.rb | 68 -------------- test/test_tcp_info.rb | 85 ++++++++++++++++++ 6 files changed, 363 insertions(+), 267 deletions(-) delete mode 100644 ext/raindrops/linux_tcp_info.c create mode 100644 ext/raindrops/tcp_info.c delete mode 100644 test/test_linux_tcp_info.rb create mode 100644 test/test_tcp_info.rb diff --git a/ext/raindrops/extconf.rb b/ext/raindrops/extconf.rb index 79d212c..5273b74 100644 --- a/ext/raindrops/extconf.rb +++ b/ext/raindrops/extconf.rb @@ -1,4 +1,5 @@ require 'mkmf' +require 'shellwords' dir_config('atomic_ops') have_func('mmap', 'sys/mman.h') or abort 'mmap() not found' @@ -6,9 +7,83 @@ have_func('munmap', 'sys/mman.h') or abort 'munmap() not found' $CPPFLAGS += " -D_GNU_SOURCE " have_func('mremap', 'sys/mman.h') -have_header('linux/tcp.h') +headers = %w(sys/types.h netdb.h string.h sys/socket.h netinet/in.h) +if have_header('linux/tcp.h') + headers << 'linux/tcp.h' +else + %w(netinet/tcp.h netinet/tcp_fsm.h).each { |h| + have_header(h, headers) and headers << h + } +end $CPPFLAGS += " -D_BSD_SOURCE " + +if have_type("struct tcp_info", headers) + %w( + tcpi_state + tcpi_ca_state + tcpi_retransmits + tcpi_probes + tcpi_backoff + tcpi_options + tcpi_snd_wscale + tcpi_rcv_wscale + tcpi_rto + tcpi_ato + tcpi_snd_mss + tcpi_rcv_mss + tcpi_unacked + tcpi_sacked + tcpi_lost + tcpi_retrans + tcpi_fackets + tcpi_last_data_sent + tcpi_last_ack_sent + tcpi_last_data_recv + tcpi_last_ack_recv + tcpi_pmtu + tcpi_rcv_ssthresh + tcpi_rtt + tcpi_rttvar + tcpi_snd_ssthresh + tcpi_snd_cwnd + tcpi_advmss + tcpi_reordering + tcpi_rcv_rtt + tcpi_rcv_space + tcpi_total_retrans + tcpi_snd_wnd + tcpi_snd_bwnd + tcpi_snd_nxt + tcpi_rcv_nxt + tcpi_toe_tid + tcpi_snd_rexmitpack + tcpi_rcv_ooopack + tcpi_snd_zerowin + ).each do |field| + cfunc = "tcp_info_#{field}" + if have_struct_member('struct tcp_info', field, headers) + func_body = <#{field}); +} +EOF + func_body.delete!("\n") + $defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}" + else + func_body = "static inline void #{cfunc}(void) {}" + $defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}" + cfunc = 'rb_f_notimplement'.freeze + end + rbmethod = %Q("#{field.sub(/\Atcpi_/, ''.freeze)}") + $defs << "-DDEFINE_METHOD_tcp_info_#{field}=" \ + "#{Shellwords.shellescape( + %Q[rb_define_method(cTCP_Info,#{rbmethod},#{cfunc},0)])}" + end +end + have_func("getpagesize", "unistd.h") have_func('rb_thread_blocking_region') have_func('rb_thread_io_blocking_region') @@ -53,4 +128,5 @@ Users of Debian-based distros may run: apt-get install libatomic-ops-dev SRC +create_header # generate extconf.h to avoid excessively long command-line create_makefile('raindrops_ext') diff --git a/ext/raindrops/linux_tcp_info.c b/ext/raindrops/linux_tcp_info.c deleted file mode 100644 index 83001a5..0000000 --- a/ext/raindrops/linux_tcp_info.c +++ /dev/null @@ -1,196 +0,0 @@ -#if defined(__linux__) && defined(HAVE_LINUX_TCP_H) -#include -#include -#include -#include -#ifdef TCP_INFO -#include "my_fileno.h" - -#define TCPI_ATTR_READER(x) \ -static VALUE tcp_info_##x(VALUE self) \ -{ \ - struct tcp_info *info = DATA_PTR(self); \ - return UINT2NUM((uint32_t)info->tcpi_##x); \ -} - -TCPI_ATTR_READER(state) -TCPI_ATTR_READER(ca_state) -TCPI_ATTR_READER(retransmits) -TCPI_ATTR_READER(probes) -TCPI_ATTR_READER(backoff) -TCPI_ATTR_READER(options) -TCPI_ATTR_READER(snd_wscale) -TCPI_ATTR_READER(rcv_wscale) -TCPI_ATTR_READER(rto) -TCPI_ATTR_READER(ato) -TCPI_ATTR_READER(snd_mss) -TCPI_ATTR_READER(rcv_mss) -TCPI_ATTR_READER(unacked) -TCPI_ATTR_READER(sacked) -TCPI_ATTR_READER(lost) -TCPI_ATTR_READER(retrans) -TCPI_ATTR_READER(fackets) -TCPI_ATTR_READER(last_data_sent) -TCPI_ATTR_READER(last_ack_sent) -TCPI_ATTR_READER(last_data_recv) -TCPI_ATTR_READER(last_ack_recv) -TCPI_ATTR_READER(pmtu) -TCPI_ATTR_READER(rcv_ssthresh) -TCPI_ATTR_READER(rtt) -TCPI_ATTR_READER(rttvar) -TCPI_ATTR_READER(snd_ssthresh) -TCPI_ATTR_READER(snd_cwnd) -TCPI_ATTR_READER(advmss) -TCPI_ATTR_READER(reordering) -TCPI_ATTR_READER(rcv_rtt) -TCPI_ATTR_READER(rcv_space) -TCPI_ATTR_READER(total_retrans) - -static size_t tcpi_memsize(const void *ptr) -{ - return sizeof(struct tcp_info); -} - -static const rb_data_type_t tcpi_type = { - "tcp_info", - { NULL, RUBY_TYPED_DEFAULT_FREE, tcpi_memsize, /* reserved */ }, - /* parent, data, [ flags ] */ -}; - -static VALUE alloc(VALUE klass) -{ - struct tcp_info *info; - - return TypedData_Make_Struct(klass, struct tcp_info, &tcpi_type, info); -} - -/* - * call-seq: - * - * Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object - * - * Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7) - * manpage and /usr/include/linux/tcp.h for more details. - */ -static VALUE init(VALUE self, VALUE io) -{ - int fd = my_fileno(io); - struct tcp_info *info = DATA_PTR(self); - socklen_t len = (socklen_t)sizeof(struct tcp_info); - int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len); - - if (rc != 0) - rb_sys_fail("getsockopt"); - - return self; -} - -void Init_raindrops_linux_tcp_info(void) -{ - VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject); - VALUE cTCP_Info; - - /* - * Document-class: Raindrops::TCP_Info - * - * This is used to wrap "struct tcp_info" as described in tcp(7) - * and /usr/include/linux/tcp.h. The following readers methods - * are defined corresponding to the "tcpi_" fields in the - * tcp_info struct. - * - * In particular, the +last_data_recv+ field is useful for measuring - * the amount of time a client spent in the listen queue before - * +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the - * listen socket (it is on by default in Unicorn). - * - * - state - * - ca_state - * - retransmits - * - probes - * - backoff - * - options - * - snd_wscale - * - rcv_wscale - * - rto - * - ato - * - snd_mss - * - rcv_mss - * - unacked - * - sacked - * - lost - * - retrans - * - fackets - * - last_data_sent - * - last_ack_sent - * - last_data_recv - * - last_ack_recv - * - pmtu - * - rcv_ssthresh - * - rtt - * - rttvar - * - snd_ssthresh - * - snd_cwnd - * - advmss - * - reordering - * - rcv_rtt - * - rcv_space - * - total_retrans - * - * https://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html - */ - cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject); - rb_define_alloc_func(cTCP_Info, alloc); - rb_define_private_method(cTCP_Info, "initialize", init, 1); - - /* - * Document-method: Raindrops::TCP_Info#get! - * - * call-seq: - * - * info = Raindrops::TCP_Info.new(tcp_socket) - * info.get!(tcp_socket) - * - * Update an existing TCP_Info objects with the latest stats - * from the given socket. This even allows sharing TCP_Info - * objects between different sockets to avoid garbage. - */ - rb_define_method(cTCP_Info, "get!", init, 1); - -#define TCPI_DEFINE_METHOD(x) \ - rb_define_method(cTCP_Info, #x, tcp_info_##x, 0) - - TCPI_DEFINE_METHOD(state); - TCPI_DEFINE_METHOD(ca_state); - TCPI_DEFINE_METHOD(retransmits); - TCPI_DEFINE_METHOD(probes); - TCPI_DEFINE_METHOD(backoff); - TCPI_DEFINE_METHOD(options); - TCPI_DEFINE_METHOD(snd_wscale); - TCPI_DEFINE_METHOD(rcv_wscale); - TCPI_DEFINE_METHOD(rto); - TCPI_DEFINE_METHOD(ato); - TCPI_DEFINE_METHOD(snd_mss); - TCPI_DEFINE_METHOD(rcv_mss); - TCPI_DEFINE_METHOD(unacked); - TCPI_DEFINE_METHOD(sacked); - TCPI_DEFINE_METHOD(lost); - TCPI_DEFINE_METHOD(retrans); - TCPI_DEFINE_METHOD(fackets); - TCPI_DEFINE_METHOD(last_data_sent); - TCPI_DEFINE_METHOD(last_ack_sent); - TCPI_DEFINE_METHOD(last_data_recv); - TCPI_DEFINE_METHOD(last_ack_recv); - TCPI_DEFINE_METHOD(pmtu); - TCPI_DEFINE_METHOD(rcv_ssthresh); - TCPI_DEFINE_METHOD(rtt); - TCPI_DEFINE_METHOD(rttvar); - TCPI_DEFINE_METHOD(snd_ssthresh); - TCPI_DEFINE_METHOD(snd_cwnd); - TCPI_DEFINE_METHOD(advmss); - TCPI_DEFINE_METHOD(reordering); - TCPI_DEFINE_METHOD(rcv_rtt); - TCPI_DEFINE_METHOD(rcv_space); - TCPI_DEFINE_METHOD(total_retrans); -} -#endif /* TCP_INFO */ -#endif /* defined(__linux__) && defined(HAVE_LINUX_TCP_H) */ diff --git a/ext/raindrops/raindrops.c b/ext/raindrops/raindrops.c index 390b8b8..0ea3e32 100644 --- a/ext/raindrops/raindrops.c +++ b/ext/raindrops/raindrops.c @@ -336,7 +336,9 @@ static VALUE aref(VALUE self, VALUE index) #ifdef __linux__ void Init_raindrops_linux_inet_diag(void); -void Init_raindrops_linux_tcp_info(void); +#endif +#ifdef HAVE_TYPE_STRUCT_TCP_INFO +void Init_raindrops_tcp_info(void); #endif #ifndef _SC_NPROCESSORS_CONF @@ -441,6 +443,8 @@ void Init_raindrops_ext(void) #ifdef __linux__ Init_raindrops_linux_inet_diag(); - Init_raindrops_linux_tcp_info(); +#endif +#ifdef HAVE_TYPE_STRUCT_TCP_INFO + Init_raindrops_tcp_info(); #endif } diff --git a/ext/raindrops/tcp_info.c b/ext/raindrops/tcp_info.c new file mode 100644 index 0000000..dc615f7 --- /dev/null +++ b/ext/raindrops/tcp_info.c @@ -0,0 +1,195 @@ +#include +#include "extconf.h" +#include +#include +#if defined(HAVE_LINUX_TCP_H) +# include +#else +# if defined(HAVE_NETINET_TCP_H) +# include +# endif +# if defined(HAVE_NETINET_TCP_FSM_H) +# include +# endif +#endif + +#ifdef HAVE_TYPE_STRUCT_TCP_INFO +#include "my_fileno.h" + +CFUNC_tcp_info_tcpi_state +CFUNC_tcp_info_tcpi_ca_state +CFUNC_tcp_info_tcpi_retransmits +CFUNC_tcp_info_tcpi_probes +CFUNC_tcp_info_tcpi_backoff +CFUNC_tcp_info_tcpi_options +CFUNC_tcp_info_tcpi_snd_wscale +CFUNC_tcp_info_tcpi_rcv_wscale +CFUNC_tcp_info_tcpi_rto +CFUNC_tcp_info_tcpi_ato +CFUNC_tcp_info_tcpi_snd_mss +CFUNC_tcp_info_tcpi_rcv_mss +CFUNC_tcp_info_tcpi_unacked +CFUNC_tcp_info_tcpi_sacked +CFUNC_tcp_info_tcpi_lost +CFUNC_tcp_info_tcpi_retrans +CFUNC_tcp_info_tcpi_fackets +CFUNC_tcp_info_tcpi_last_data_sent +CFUNC_tcp_info_tcpi_last_ack_sent +CFUNC_tcp_info_tcpi_last_data_recv +CFUNC_tcp_info_tcpi_last_ack_recv +CFUNC_tcp_info_tcpi_pmtu +CFUNC_tcp_info_tcpi_rcv_ssthresh +CFUNC_tcp_info_tcpi_rtt +CFUNC_tcp_info_tcpi_rttvar +CFUNC_tcp_info_tcpi_snd_ssthresh +CFUNC_tcp_info_tcpi_snd_cwnd +CFUNC_tcp_info_tcpi_advmss +CFUNC_tcp_info_tcpi_reordering +CFUNC_tcp_info_tcpi_rcv_rtt +CFUNC_tcp_info_tcpi_rcv_space +CFUNC_tcp_info_tcpi_total_retrans + +static size_t tcpi_memsize(const void *ptr) +{ + return sizeof(struct tcp_info); +} + +static const rb_data_type_t tcpi_type = { + "tcp_info", + { NULL, RUBY_TYPED_DEFAULT_FREE, tcpi_memsize, /* reserved */ }, + /* parent, data, [ flags ] */ +}; + +static VALUE alloc(VALUE klass) +{ + struct tcp_info *info; + + return TypedData_Make_Struct(klass, struct tcp_info, &tcpi_type, info); +} + +/* + * call-seq: + * + * Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object + * + * Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7) + * manpage and /usr/include/linux/tcp.h for more details. + */ +static VALUE init(VALUE self, VALUE io) +{ + int fd = my_fileno(io); + struct tcp_info *info = DATA_PTR(self); + socklen_t len = (socklen_t)sizeof(struct tcp_info); + int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len); + + if (rc != 0) + rb_sys_fail("getsockopt"); + + return self; +} + +void Init_raindrops_tcp_info(void) +{ + VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject); + VALUE cTCP_Info; + + /* + * Document-class: Raindrops::TCP_Info + * + * This is used to wrap "struct tcp_info" as described in tcp(7) + * and /usr/include/linux/tcp.h. The following readers methods + * are defined corresponding to the "tcpi_" fields in the + * tcp_info struct. + * + * In particular, the +last_data_recv+ field is useful for measuring + * the amount of time a client spent in the listen queue before + * +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the + * listen socket (it is on by default in Unicorn). + * + * - state + * - ca_state + * - retransmits + * - probes + * - backoff + * - options + * - snd_wscale + * - rcv_wscale + * - rto + * - ato + * - snd_mss + * - rcv_mss + * - unacked + * - sacked + * - lost + * - retrans + * - fackets + * - last_data_sent + * - last_ack_sent + * - last_data_recv + * - last_ack_recv + * - pmtu + * - rcv_ssthresh + * - rtt + * - rttvar + * - snd_ssthresh + * - snd_cwnd + * - advmss + * - reordering + * - rcv_rtt + * - rcv_space + * - total_retrans + * + * https://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html + */ + cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject); + rb_define_alloc_func(cTCP_Info, alloc); + rb_define_private_method(cTCP_Info, "initialize", init, 1); + + /* + * Document-method: Raindrops::TCP_Info#get! + * + * call-seq: + * + * info = Raindrops::TCP_Info.new(tcp_socket) + * info.get!(tcp_socket) + * + * Update an existing TCP_Info objects with the latest stats + * from the given socket. This even allows sharing TCP_Info + * objects between different sockets to avoid garbage. + */ + rb_define_method(cTCP_Info, "get!", init, 1); + + DEFINE_METHOD_tcp_info_tcpi_state; + DEFINE_METHOD_tcp_info_tcpi_ca_state; + DEFINE_METHOD_tcp_info_tcpi_retransmits; + DEFINE_METHOD_tcp_info_tcpi_probes; + DEFINE_METHOD_tcp_info_tcpi_backoff; + DEFINE_METHOD_tcp_info_tcpi_options; + DEFINE_METHOD_tcp_info_tcpi_snd_wscale; + DEFINE_METHOD_tcp_info_tcpi_rcv_wscale; + DEFINE_METHOD_tcp_info_tcpi_rto; + DEFINE_METHOD_tcp_info_tcpi_ato; + DEFINE_METHOD_tcp_info_tcpi_snd_mss; + DEFINE_METHOD_tcp_info_tcpi_rcv_mss; + DEFINE_METHOD_tcp_info_tcpi_unacked; + DEFINE_METHOD_tcp_info_tcpi_sacked; + DEFINE_METHOD_tcp_info_tcpi_lost; + DEFINE_METHOD_tcp_info_tcpi_retrans; + DEFINE_METHOD_tcp_info_tcpi_fackets; + DEFINE_METHOD_tcp_info_tcpi_last_data_sent; + DEFINE_METHOD_tcp_info_tcpi_last_ack_sent; + DEFINE_METHOD_tcp_info_tcpi_last_data_recv; + DEFINE_METHOD_tcp_info_tcpi_last_ack_recv; + DEFINE_METHOD_tcp_info_tcpi_pmtu; + DEFINE_METHOD_tcp_info_tcpi_rcv_ssthresh; + DEFINE_METHOD_tcp_info_tcpi_rtt; + DEFINE_METHOD_tcp_info_tcpi_rttvar; + DEFINE_METHOD_tcp_info_tcpi_snd_ssthresh; + DEFINE_METHOD_tcp_info_tcpi_snd_cwnd; + DEFINE_METHOD_tcp_info_tcpi_advmss; + DEFINE_METHOD_tcp_info_tcpi_reordering; + DEFINE_METHOD_tcp_info_tcpi_rcv_rtt; + DEFINE_METHOD_tcp_info_tcpi_rcv_space; + DEFINE_METHOD_tcp_info_tcpi_total_retrans; +} +#endif /* HAVE_STRUCT_TCP_INFO */ diff --git a/test/test_linux_tcp_info.rb b/test/test_linux_tcp_info.rb deleted file mode 100644 index c947211..0000000 --- a/test/test_linux_tcp_info.rb +++ /dev/null @@ -1,68 +0,0 @@ -# -*- encoding: binary -*- -require 'test/unit' -require 'tempfile' -require 'raindrops' -require 'socket' -require 'pp' -$stderr.sync = $stdout.sync = true -class TestLinuxTCP_Info < Test::Unit::TestCase - - TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' - - # Linux kernel commit 5ee3afba88f5a79d0bff07ddd87af45919259f91 - TCP_INFO_useful_listenq = `uname -r`.strip >= '2.6.24' - - - def test_tcp_server - s = TCPServer.new(TEST_ADDR, 0) - rv = Raindrops::TCP_Info.new s - c = TCPSocket.new TEST_ADDR, s.addr[1] - tmp = Raindrops::TCP_Info.new s - TCP_INFO_useful_listenq and assert_equal 1, tmp.unacked - - assert_equal 0, rv.unacked - a = s.accept - tmp = Raindrops::TCP_Info.new s - assert_equal 0, tmp.unacked - before = tmp.object_id - - tmp.get!(s) - assert_equal before, tmp.object_id - - ensure - c.close if c - a.close if a - s.close - end - - def test_accessors - s = TCPServer.new TEST_ADDR, 0 - tmp = Raindrops::TCP_Info.new s - tcp_info_methods = tmp.methods - Object.new.methods - assert tcp_info_methods.size >= 32 - tcp_info_methods.each do |m| - next if m.to_sym == :get! - val = tmp.__send__ m - assert_kind_of Integer, val - assert val >= 0 - end - ensure - s.close - end - - def test_tcp_server_delayed - delay = 0.010 - delay_ms = (delay * 1000).to_i - s = TCPServer.new(TEST_ADDR, 0) - c = TCPSocket.new TEST_ADDR, s.addr[1] - c.syswrite "." - sleep(delay * 1.2) - a = s.accept - i = Raindrops::TCP_Info.new(a) - assert i.last_data_recv >= delay_ms, "#{i.last_data_recv} < #{delay_ms}" - ensure - c.close if c - a.close if a - s.close - end -end if RUBY_PLATFORM =~ /linux/ diff --git a/test/test_tcp_info.rb b/test/test_tcp_info.rb new file mode 100644 index 0000000..15df087 --- /dev/null +++ b/test/test_tcp_info.rb @@ -0,0 +1,85 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'tempfile' +require 'raindrops' +require 'socket' +require 'pp' +$stderr.sync = $stdout.sync = true +class TestTCP_Info < Test::Unit::TestCase + + TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' + + # Linux kernel commit 5ee3afba88f5a79d0bff07ddd87af45919259f91 + TCP_INFO_useful_listenq = `uname -r`.strip >= '2.6.24' + + def test_tcp_server_unacked + return if RUBY_PLATFORM !~ /linux/ # unacked not implemented on others... + s = TCPServer.new(TEST_ADDR, 0) + rv = Raindrops::TCP_Info.new s + c = TCPSocket.new TEST_ADDR, s.addr[1] + tmp = Raindrops::TCP_Info.new s + TCP_INFO_useful_listenq and assert_equal 1, tmp.unacked + + assert_equal 0, rv.unacked + a = s.accept + tmp = Raindrops::TCP_Info.new s + assert_equal 0, tmp.unacked + before = tmp.object_id + + tmp.get!(s) + assert_equal before, tmp.object_id + + ensure + [ c, a, s ].compact.each(&:close) + end + + def test_accessors + s = TCPServer.new TEST_ADDR, 0 + tmp = Raindrops::TCP_Info.new s + tcp_info_methods = tmp.methods - Object.new.methods + assert tcp_info_methods.size >= 32 + tcp_info_methods.each do |m| + next if m.to_sym == :get! + next if ! tmp.respond_to?(m) + val = tmp.__send__ m + assert_kind_of Integer, val + assert val >= 0 + end + assert tmp.respond_to?(:state), 'every OS knows about TCP state, right?' + ensure + s.close + end + + def test_tcp_server_delayed + delay = 0.010 + delay_ms = (delay * 1000).to_i + s = TCPServer.new(TEST_ADDR, 0) + c = TCPSocket.new TEST_ADDR, s.addr[1] + c.syswrite "." + sleep(delay * 1.2) + a = s.accept + i = Raindrops::TCP_Info.new(a) + assert i.last_data_recv >= delay_ms, "#{i.last_data_recv} < #{delay_ms}" + ensure + c.close if c + a.close if a + s.close + end + + def test_tcp_server_state_closed + s = TCPServer.new(TEST_ADDR, 0) + c = TCPSocket.new(TEST_ADDR, s.addr[1]) + i = Raindrops::TCP_Info.allocate + a = s.accept + i.get!(a) + state = i.state + c = c.close + sleep(0.01) # wait for kernel to update state + i.get!(a) + assert_not_equal state, i.state + ensure + s.close if s + c.close if c + a.close if a + end +end if defined? Raindrops::TCP_Info -- cgit v1.2.3-24-ge0c7 From bf2fb0a16091201a9b2798ebdea54e03c1c3e61b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 16 Mar 2017 03:16:52 +0000 Subject: define Raindrops::TCP hash for TCP states FreeBSD not only uses different values than Linux for TCP states, but different names, too. To ease writing portable code between the OSes, do more CPP metaprogramming via extconf.rb and define a common hash supported on both OSes. Putting all this in a hash allows for easy dumping and mapping in an OS-neutral way, since the actual TCP states are OS-independent. --- ext/raindrops/extconf.rb | 27 +++++++++++++++++++++++++++ ext/raindrops/tcp_info.c | 21 +++++++++++++++++++++ test/test_tcp_info.rb | 3 +++ 3 files changed, 51 insertions(+) diff --git a/ext/raindrops/extconf.rb b/ext/raindrops/extconf.rb index 5273b74..86c7d78 100644 --- a/ext/raindrops/extconf.rb +++ b/ext/raindrops/extconf.rb @@ -82,6 +82,33 @@ EOF "#{Shellwords.shellescape( %Q[rb_define_method(cTCP_Info,#{rbmethod},#{cfunc},0)])}" end + tcp_state_map = { + ESTABLISHED: %w(TCP_ESTABLISHED TCPS_ESTABLISHED), + SYN_SENT: %w(TCP_SYN_SENT TCPS_SYN_SENT), + SYN_RECV: %w(TCP_SYN_RECV TCPS_SYN_RECEIVED), + FIN_WAIT1: %w(TCP_FIN_WAIT1 TCPS_FIN_WAIT_1), + FIN_WAIT2: %w(TCP_FIN_WAIT2 TCPS_FIN_WAIT_2), + TIME_WAIT: %w(TCP_TIME_WAIT TCPS_TIME_WAIT), + CLOSE: %w(TCP_CLOSE TCPS_CLOSED), + CLOSE_WAIT: %w(TCP_CLOSE_WAIT TCPS_CLOSE_WAIT), + LAST_ACK: %w(TCP_LAST_ACK TCPS_LAST_ACK), + LISTEN: %w(TCP_LISTEN TCPS_LISTEN), + CLOSING: %w(TCP_CLOSING TCPS_CLOSING), + } + nstate = 0 + tcp_state_map.each do |state, try| + try.each do |os_name| + have_const(os_name, headers) or next + tcp_state_map[state] = os_name + nstate += 1 + end + end + if nstate == tcp_state_map.size + $defs << '-DRAINDROPS_TCP_STATES_ALL_KNOWN=1' + tcp_state_map.each do |state, name| + $defs << "-DRAINDROPS_TCP_#{state}=#{name}" + end + end end have_func("getpagesize", "unistd.h") diff --git a/ext/raindrops/tcp_info.c b/ext/raindrops/tcp_info.c index dc615f7..3e241a1 100644 --- a/ext/raindrops/tcp_info.c +++ b/ext/raindrops/tcp_info.c @@ -191,5 +191,26 @@ void Init_raindrops_tcp_info(void) DEFINE_METHOD_tcp_info_tcpi_rcv_rtt; DEFINE_METHOD_tcp_info_tcpi_rcv_space; DEFINE_METHOD_tcp_info_tcpi_total_retrans; + +#ifdef RAINDROPS_TCP_STATES_ALL_KNOWN + { +#define TCPSET(n,v) rb_hash_aset(tcp, ID2SYM(rb_intern(#n)), INT2NUM(v)) + VALUE tcp = rb_hash_new(); + TCPSET(ESTABLISHED, RAINDROPS_TCP_ESTABLISHED); + TCPSET(SYN_SENT, RAINDROPS_TCP_SYN_SENT); + TCPSET(SYN_RECV, RAINDROPS_TCP_SYN_RECV); + TCPSET(FIN_WAIT1, RAINDROPS_TCP_FIN_WAIT1); + TCPSET(FIN_WAIT2, RAINDROPS_TCP_FIN_WAIT2); + TCPSET(TIME_WAIT, RAINDROPS_TCP_TIME_WAIT); + TCPSET(CLOSE, RAINDROPS_TCP_CLOSE); + TCPSET(CLOSE_WAIT, RAINDROPS_TCP_CLOSE_WAIT); + TCPSET(LAST_ACK, RAINDROPS_TCP_LAST_ACK); + TCPSET(LISTEN, RAINDROPS_TCP_LISTEN); + TCPSET(CLOSING, RAINDROPS_TCP_CLOSING); +#undef TCPSET + OBJ_FREEZE(tcp); + rb_define_const(cRaindrops, "TCP", tcp); + } +#endif } #endif /* HAVE_STRUCT_TCP_INFO */ diff --git a/test/test_tcp_info.rb b/test/test_tcp_info.rb index 15df087..b107565 100644 --- a/test/test_tcp_info.rb +++ b/test/test_tcp_info.rb @@ -73,6 +73,9 @@ class TestTCP_Info < Test::Unit::TestCase a = s.accept i.get!(a) state = i.state + if Raindrops.const_defined?(:TCP) + assert_equal state, Raindrops::TCP[:ESTABLISHED] + end c = c.close sleep(0.01) # wait for kernel to update state i.get!(a) -- cgit v1.2.3-24-ge0c7