socket_helper: cleanup FreeBSD accf_* detection
[unicorn.git] / lib / unicorn / socket_helper.rb
blob08b3d0616632768cfc5056b42c38a6920c3ad309
1 # -*- encoding: binary -*-
3 require 'socket'
5 module Unicorn
6   module SocketHelper
7     include Socket::Constants
9     # :stopdoc:
10     # internal interface, only used by Rainbows!/Zbatery
11     DEFAULTS = {
12       # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
13       # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
14       # This change shouldn't affect Unicorn users behind nginx (a
15       # value of 1 remains an optimization), but Rainbows! users may
16       # want to use a higher value on Linux 2.6.32+ to protect against
17       # denial-of-service attacks
18       :tcp_defer_accept => 1,
20       # FreeBSD, we need to override this to 'dataready' when we
21       # eventually get HTTPS support
22       :accept_filter => 'httpready',
23     }
24     #:startdoc:
26     # configure platform-specific options (only tested on Linux 2.6 so far)
27     case RUBY_PLATFORM
28     when /linux/
29       # from /usr/include/linux/tcp.h
30       TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
32       # do not send out partial frames (Linux)
33       TCP_CORK = 3 unless defined?(TCP_CORK)
34     when /freebsd/
35       # do not send out partial frames (FreeBSD)
36       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
38       def accf_arg(af_name)
39         [ af_name, nil ].pack('a16a240')
40       end if defined?(SO_ACCEPTFILTER)
41     end
43     def set_tcp_sockopt(sock, opt)
45       # highly portable, but off by default because we don't do keepalive
46       if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
47         sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
48       end
50       unless (val = opt[:tcp_nopush]).nil?
51         val = val ? 1 : 0
52         if defined?(TCP_CORK) # Linux
53           sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
54         elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
55           sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
56         end
57       end
59       # No good reason to ever have deferred accepts off
60       # (except maybe benchmarking)
61       if defined?(TCP_DEFER_ACCEPT)
62         # this differs from nginx, since nginx doesn't allow us to
63         # configure the the timeout...
64         tmp = DEFAULTS.merge(opt)
65         seconds = tmp[:tcp_defer_accept]
66         seconds = DEFAULTS[:tcp_defer_accept] if seconds == true
67         seconds = 0 unless seconds # nil/false means disable this
68         sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
69       elsif respond_to?(:accf_arg)
70         tmp = DEFAULTS.merge(opt)
71         if name = tmp[:accept_filter]
72           begin
73             sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
74           rescue => e
75             logger.error("#{sock_name(sock)} " \
76                          "failed to set accept_filter=#{name} (#{e.inspect})")
77           end
78         end
79       end
80     end
82     def set_server_sockopt(sock, opt)
83       opt ||= {}
85       TCPSocket === sock and set_tcp_sockopt(sock, opt)
87       if opt[:rcvbuf] || opt[:sndbuf]
88         log_buffer_sizes(sock, "before: ")
89         sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
90         sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
91         log_buffer_sizes(sock, " after: ")
92       end
93       sock.listen(opt[:backlog] || 1024)
94       rescue => e
95         logger.error "error setting socket options: #{e.inspect}"
96         logger.error e.backtrace.join("\n")
97     end
99     def log_buffer_sizes(sock, pfx = '')
100       rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
101       sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
102       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
103     end
105     # creates a new server, socket. address may be a HOST:PORT or
106     # an absolute path to a UNIX socket.  address can even be a Socket
107     # object in which case it is immediately returned
108     def bind_listen(address = '0.0.0.0:8080', opt = {})
109       return address unless String === address
111       sock = if address[0] == ?/
112         if File.exist?(address)
113           if File.socket?(address)
114             logger.info "unlinking existing socket=#{address}"
115             File.unlink(address)
116           else
117             raise ArgumentError,
118                   "socket=#{address} specified but it is not a socket!"
119           end
120         end
121         old_umask = File.umask(opt[:umask] || 0)
122         begin
123           UNIXServer.new(address)
124         ensure
125           File.umask(old_umask)
126         end
127       elsif address =~ /^(\d+\.\d+\.\d+\.\d+):(\d+)$/
128         TCPServer.new($1, $2.to_i)
129       else
130         raise ArgumentError, "Don't know how to bind: #{address}"
131       end
132       set_server_sockopt(sock, opt)
133       sock
134     end
136     # Returns the configuration name of a socket as a string.  sock may
137     # be a string value, in which case it is returned as-is
138     # Warning: TCP sockets may not always return the name given to it.
139     def sock_name(sock)
140       case sock
141       when String then sock
142       when UNIXServer
143         Socket.unpack_sockaddr_un(sock.getsockname)
144       when TCPServer
145         Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
146       when Socket
147         begin
148           Socket.unpack_sockaddr_in(sock.getsockname).reverse!.join(':')
149         rescue ArgumentError
150           Socket.unpack_sockaddr_un(sock.getsockname)
151         end
152       else
153         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
154       end
155     end
157     module_function :sock_name
159     # casts a given Socket to be a TCPServer or UNIXServer
160     def server_cast(sock)
161       begin
162         Socket.unpack_sockaddr_in(sock.getsockname)
163         TCPServer.for_fd(sock.fileno)
164       rescue ArgumentError
165         UNIXServer.for_fd(sock.fileno)
166       end
167     end
169   end # module SocketHelper
170 end # module Unicorn