* [PATCH 1/2] tcp_info: support this struct under FreeBSD
@ 2017-03-15 22:07 Eric Wong
2017-03-15 22:07 ` [PATCH 2/2] define Raindrops::TCP hash for TCP states Eric Wong
0 siblings, 1 reply; 2+ messages in thread
From: Eric Wong @ 2017-03-15 22:07 UTC (permalink / raw)
To: spew
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/raindrops.c | 8 +-
ext/raindrops/{linux_tcp_info.c => tcp_info.c} | 159 +++++++++++-----------
test/{test_linux_tcp_info.rb => test_tcp_info.rb} | 37 +++--
4 files changed, 189 insertions(+), 93 deletions(-)
rename ext/raindrops/{linux_tcp_info.c => tcp_info.c} (51%)
rename test/{test_linux_tcp_info.rb => test_tcp_info.rb} (66%)
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 @@
$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
+end
+
have_func("getpagesize", "unistd.h")
have_func('rb_thread_blocking_region')
have_func('rb_thread_io_blocking_region')
@@ -53,4 +128,5 @@
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/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/linux_tcp_info.c b/ext/raindrops/tcp_info.c
similarity index 51%
rename from ext/raindrops/linux_tcp_info.c
rename to ext/raindrops/tcp_info.c
index 83001a5..dc615f7 100644
--- a/ext/raindrops/linux_tcp_info.c
+++ b/ext/raindrops/tcp_info.c
@@ -1,50 +1,53 @@
-#if defined(__linux__) && defined(HAVE_LINUX_TCP_H)
#include <ruby.h>
+#include "extconf.h"
#include <sys/socket.h>
#include <netinet/in.h>
-#include <linux/tcp.h>
-#ifdef TCP_INFO
-#include "my_fileno.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
-#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); \
-}
+#ifdef HAVE_TYPE_STRUCT_TCP_INFO
+#include "my_fileno.h"
-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)
+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)
{
@@ -85,7 +88,7 @@ static VALUE init(VALUE self, VALUE io)
return self;
}
-void Init_raindrops_linux_tcp_info(void)
+void Init_raindrops_tcp_info(void)
{
VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
VALUE cTCP_Info;
@@ -156,41 +159,37 @@ void Init_raindrops_linux_tcp_info(void)
*/
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);
+ 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 /* TCP_INFO */
-#endif /* defined(__linux__) && defined(HAVE_LINUX_TCP_H) */
+#endif /* HAVE_STRUCT_TCP_INFO */
diff --git a/test/test_linux_tcp_info.rb b/test/test_tcp_info.rb
similarity index 66%
rename from test/test_linux_tcp_info.rb
rename to test/test_tcp_info.rb
index c947211..15df087 100644
--- a/test/test_linux_tcp_info.rb
+++ b/test/test_tcp_info.rb
@@ -5,15 +5,15 @@
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 @@ def test_tcp_server
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 @@ def test_accessors
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,21 @@ def test_tcp_server_delayed
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
+ 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
--
EW
^ permalink raw reply related [flat|nested] 2+ messages in thread
* [PATCH 2/2] define Raindrops::TCP hash for TCP states
2017-03-15 22:07 [PATCH 1/2] tcp_info: support this struct under FreeBSD Eric Wong
@ 2017-03-15 22:07 ` Eric Wong
0 siblings, 0 replies; 2+ messages in thread
From: Eric Wong @ 2017-03-15 22:07 UTC (permalink / raw)
To: spew
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 @@
"#{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 @@ def test_tcp_server_state_closed
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)
--
EW
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2017-03-15 22:07 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-03-15 22:07 [PATCH 1/2] tcp_info: support this struct under FreeBSD Eric Wong
2017-03-15 22:07 ` [PATCH 2/2] define Raindrops::TCP hash for TCP states Eric Wong
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).