From a88bed858dfa20b5131b631739b340da9dceae99 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Sat, 3 Jul 2010 09:30:57 +0000 Subject: socket_helper: tunables for tcp_defer_accept/accept_filter Under Linux, this allows users to tune the time (in seconds) to defer connections before allowing them to be accepted. The behavior of TCP_DEFER_ACCEPT changed with Linux 2.6.32 and idle connections may still be accept()-ed after the specified value in seconds. A small value of '1' remains the default for Unicorn as Unicorn does not worry about slow clients. Higher values provide better DoS protection for Rainbows! but also increases kernel memory usage. Allowing "dataready" for FreeBSD accept filters will allow SSL sockets to be used in the future for HTTPS, too. (cherry picked from commit 646cc762cc9297510102fc094f3af8a5a9e296c7) --- lib/unicorn/socket_helper.rb | 36 +++++++++++++++++++++++++++++------- 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 -- cgit v1.2.3-24-ge0c7