summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2010-07-03 09:30:57 +0000
committerEric Wong <normalperson@yhbt.net>2010-07-06 14:39:40 -0700
commita88bed858dfa20b5131b631739b340da9dceae99 (patch)
treec5bbfa682f5c0944ff412bd901798e0431cc9596
parent85d55f6450f3546d3211be247919a2dae03a1110 (diff)
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)
-rw-r--r--lib/unicorn/socket_helper.rb36
-rw-r--r--test/unit/test_socket_helper.rb24
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