about summary refs log tree commit homepage
path: root/lib/unicorn/socket_helper.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/unicorn/socket_helper.rb')
-rw-r--r--lib/unicorn/socket_helper.rb78
1 files changed, 51 insertions, 27 deletions
diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb
index 9a4266d..7364937 100644
--- a/lib/unicorn/socket_helper.rb
+++ b/lib/unicorn/socket_helper.rb
@@ -1,11 +1,28 @@
 # -*- encoding: binary -*-
-
+# :enddoc:
 require 'socket'
 
 module Unicorn
   module SocketHelper
     include Socket::Constants
 
+    # :stopdoc:
+    # internal interface, only used by Rainbows!/Zbatery
+    DEFAULTS = {
+      # 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 => 1,
+
+      # FreeBSD, we need to override this to 'dataready' when we
+      # eventually get HTTPS support
+      :accept_filter => 'httpready',
+    }
+    #:startdoc:
+
     # configure platform-specific options (only tested on Linux 2.6 so far)
     case RUBY_PLATFORM
     when /linux/
@@ -14,22 +31,13 @@ module Unicorn
 
       # do not send out partial frames (Linux)
       TCP_CORK = 3 unless defined?(TCP_CORK)
-    when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
-      # Do nothing for httpready, just closing a bug when freebsd <= 5.4
-      TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH) # :nodoc:
     when /freebsd/
       # do not send out partial frames (FreeBSD)
       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
 
-      # Use the HTTP accept filter if available.
-      # 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')
-      end
+      def accf_arg(af_name)
+        [ af_name, nil ].pack('a16a240')
+      end if defined?(SO_ACCEPTFILTER)
     end
 
     def set_tcp_sockopt(sock, opt)
@@ -49,10 +57,25 @@ 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 = DEFAULTS.merge(opt)
+        seconds = tmp[:tcp_defer_accept]
+        seconds = DEFAULTS[:tcp_defer_accept] if seconds == true
+        seconds = 0 unless seconds # nil/false means disable this
+        sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
+      elsif respond_to?(:accf_arg)
+        tmp = DEFAULTS.merge(opt)
+        if name = tmp[:accept_filter]
+          begin
+            sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
+          rescue => e
+            logger.error("#{sock_name(sock)} " \
+                         "failed to set accept_filter=#{name} (#{e.inspect})")
+          end
+        end
       end
     end
 
@@ -69,14 +92,11 @@ module Unicorn
       end
       sock.listen(opt[:backlog] || 1024)
       rescue => e
-        if respond_to?(:logger)
-          logger.error "error setting socket options: #{e.inspect}"
-          logger.error e.backtrace.join("\n")
-        end
+        logger.error "error setting socket options: #{e.inspect}"
+        logger.error e.backtrace.join("\n")
     end
 
     def log_buffer_sizes(sock, pfx = '')
-      respond_to?(:logger) or return
       rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
       sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
@@ -91,10 +111,14 @@ module Unicorn
       sock = if address[0] == ?/
         if File.exist?(address)
           if File.socket?(address)
-            if self.respond_to?(:logger)
+            begin
+              UNIXSocket.new(address).close
+              # fall through, try to bind(2) and fail with EADDRINUSE
+              # (or succeed from a small race condition we can't sanely avoid).
+            rescue Errno::ECONNREFUSED
               logger.info "unlinking existing socket=#{address}"
+              File.unlink(address)
             end
-            File.unlink(address)
           else
             raise ArgumentError,
                   "socket=#{address} specified but it is not a socket!"
@@ -102,12 +126,12 @@ module Unicorn
         end
         old_umask = File.umask(opt[:umask] || 0)
         begin
-          UNIXServer.new(address)
+          Kgio::UNIXServer.new(address)
         ensure
           File.umask(old_umask)
         end
       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
-        TCPServer.new($1, $2.to_i)
+        Kgio::TCPServer.new($1, $2.to_i)
       else
         raise ArgumentError, "Don't know how to bind: #{address}"
       end
@@ -142,9 +166,9 @@ module Unicorn
     def server_cast(sock)
       begin
         Socket.unpack_sockaddr_in(sock.getsockname)
-        TCPServer.for_fd(sock.fileno)
+        Kgio::TCPServer.for_fd(sock.fileno)
       rescue ArgumentError
-        UNIXServer.for_fd(sock.fileno)
+        Kgio::UNIXServer.for_fd(sock.fileno)
       end
     end