diff options
-rw-r--r-- | lib/unicorn/socket_helper.rb | 36 | ||||
-rw-r--r-- | test/unit/test_socket_helper.rb | 24 |
2 files changed, 53 insertions, 7 deletions
diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb index 9a4266d..8677f70 100644 --- a/lib/unicorn/socket_helper.rb +++ b/lib/unicorn/socket_helper.rb @@ -12,6 +12,14 @@ module Unicorn # from /usr/include/linux/tcp.h TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT) + # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+ + # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9 + # This change shouldn't affect Unicorn users behind nginx (a + # value of 1 remains an optimization), but Rainbows! users may + # want to use a higher value on Linux 2.6.32+ to protect against + # denial-of-service attacks + TCP_DEFER_ACCEPT_DEFAULT = 1 + # do not send out partial frames (Linux) TCP_CORK = 3 unless defined?(TCP_CORK) when /freebsd(([1-4]\..{1,2})|5\.[0-4])/ @@ -25,10 +33,16 @@ module Unicorn # The struct made by pack() is defined in /usr/include/sys/socket.h # as accept_filter_arg unless `/sbin/sysctl -nq net.inet.accf.http`.empty? - # set set the "httpready" accept filter in FreeBSD if available - # if other protocols are to be supported, this may be - # String#replace-d with "dataready" arguments instead - FILTER_ARG = ['httpready', nil].pack('a16a240') + # struct accept_filter_arg { + # char af_name[16]; + # char af_arg[240]; + # }; + # + # +af_name+ is either "httpready" or "dataready", + # though other filters may be supported by FreeBSD + def accf_arg(af_name) + [ af_name, nil ].pack('a16a240') + end end end @@ -49,10 +63,18 @@ module Unicorn end # No good reason to ever have deferred accepts off + # (except maybe benchmarking) if defined?(TCP_DEFER_ACCEPT) - sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) - elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG) - sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG) + # this differs from nginx, since nginx doesn't allow us to + # configure the the timeout... + tmp = { :tcp_defer_accept => true }.update(opt) + seconds = tmp[:tcp_defer_accept] + seconds = TCP_DEFER_ACCEPT_DEFAULT if seconds == true + seconds and sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds) + elsif defined?(SO_ACCEPTFILTER) && respond_to?(:accf_arg) + tmp = { :accept_filter => 'httpready' }.update(opt) + name = tmp[:accept_filter] and + sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name)) end end diff --git a/test/unit/test_socket_helper.rb b/test/unit/test_socket_helper.rb index 36b2dc2..bbce359 100644 --- a/test/unit/test_socket_helper.rb +++ b/test/unit/test_socket_helper.rb @@ -146,4 +146,28 @@ class TestSocketHelper < Test::Unit::TestCase sock_name(@unix_server) end + def test_tcp_defer_accept_default + port = unused_port @test_addr + name = "#@test_addr:#{port}" + sock = bind_listen(name) + cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] + assert cur >= 1 + end if defined?(TCP_DEFER_ACCEPT) + + def test_tcp_defer_accept_disable + port = unused_port @test_addr + name = "#@test_addr:#{port}" + sock = bind_listen(name, :tcp_defer_accept => false) + cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] + assert_equal 0, cur + end if defined?(TCP_DEFER_ACCEPT) + + def test_tcp_defer_accept_nr + port = unused_port @test_addr + name = "#@test_addr:#{port}" + sock = bind_listen(name, :tcp_defer_accept => 60) + cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] + assert cur > 1 + end if defined?(TCP_DEFER_ACCEPT) + end |