about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2017-03-18 01:32:19 +0000
committerEric Wong <e@80x24.org>2017-03-18 01:32:19 +0000
commit0b8ecbc71cd6aba009e5e3a99a8e0e8f140e2e5f (patch)
tree0ff0289ddd6f6de1e0fa22f8cfcc39087f5fcf72
parent33a2540fb12cec9052f9b92810f2a9aa5b395911 (diff)
parentbf2fb0a16091201a9b2798ebdea54e03c1c3e61b (diff)
downloadraindrops-0b8ecbc71cd6aba009e5e3a99a8e0e8f140e2e5f.tar.gz
* origin/freebsd:
  define Raindrops::TCP hash for TCP states
  tcp_info: support this struct under FreeBSD
-rw-r--r--ext/raindrops/extconf.rb105
-rw-r--r--ext/raindrops/linux_tcp_info.c196
-rw-r--r--ext/raindrops/raindrops.c8
-rw-r--r--ext/raindrops/tcp_info.c216
-rw-r--r--test/test_tcp_info.rb (renamed from test/test_linux_tcp_info.rb)40
5 files changed, 356 insertions, 209 deletions
diff --git a/ext/raindrops/extconf.rb b/ext/raindrops/extconf.rb
index 79d212c..86c7d78 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,110 @@ 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 = <<EOF
+static VALUE #{cfunc}(VALUE self)
+{
+        struct tcp_info *info = DATA_PTR(self);
+        return UINT2NUM((uint32_t)info->#{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
+  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")
 have_func('rb_thread_blocking_region')
 have_func('rb_thread_io_blocking_region')
@@ -53,4 +155,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 <ruby.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <linux/tcp.h>
-#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 9090839..837084c 100644
--- a/ext/raindrops/raindrops.c
+++ b/ext/raindrops/raindrops.c
@@ -340,7 +340,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
@@ -445,6 +447,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..3e241a1
--- /dev/null
+++ b/ext/raindrops/tcp_info.c
@@ -0,0 +1,216 @@
+#include <ruby.h>
+#include "extconf.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#if defined(HAVE_LINUX_TCP_H)
+#  include <linux/tcp.h>
+#else
+#  if defined(HAVE_NETINET_TCP_H)
+#    include <netinet/tcp.h>
+#  endif
+#  if defined(HAVE_NETINET_TCP_FSM_H)
+#    include <netinet/tcp_fsm.h>
+#  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;
+
+#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_linux_tcp_info.rb b/test/test_tcp_info.rb
index c947211..b107565 100644
--- a/test/test_linux_tcp_info.rb
+++ b/test/test_tcp_info.rb
@@ -5,15 +5,15 @@ require 'raindrops'
 require 'socket'
 require 'pp'
 $stderr.sync = $stdout.sync = true
-class TestLinuxTCP_Info < Test::Unit::TestCase
+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
+  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]
@@ -29,10 +29,8 @@ class TestLinuxTCP_Info < Test::Unit::TestCase
     tmp.get!(s)
     assert_equal before, tmp.object_id
 
-    ensure
-      c.close if c
-      a.close if a
-      s.close
+  ensure
+    [ c, a, s ].compact.each(&:close)
   end
 
   def test_accessors
@@ -42,12 +40,14 @@ class TestLinuxTCP_Info < Test::Unit::TestCase
     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
-    ensure
-      s.close
+    assert tmp.respond_to?(:state), 'every OS knows about TCP state, right?'
+  ensure
+    s.close
   end
 
   def test_tcp_server_delayed
@@ -65,4 +65,24 @@ class TestLinuxTCP_Info < Test::Unit::TestCase
       a.close if a
       s.close
   end
-end if RUBY_PLATFORM =~ /linux/
+
+  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
+    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)
+    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