configurator: add :ipv6only directive
[unicorn.git] / lib / unicorn / socket_helper.rb
blob85482761d1dd98af2967b924fba6c8ad8e278937
1 # -*- encoding: binary -*-
2 # :enddoc:
3 require 'socket'
5 module Unicorn
6   module SocketHelper
7     # :stopdoc:
8     include Socket::Constants
10     # prevents IO objects in here from being GC-ed
11     IO_PURGATORY = []
13     # internal interface, only used by Rainbows!/Zbatery
14     DEFAULTS = {
15       # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
16       # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
17       # This change shouldn't affect Unicorn users behind nginx (a
18       # value of 1 remains an optimization), but Rainbows! users may
19       # want to use a higher value on Linux 2.6.32+ to protect against
20       # denial-of-service attacks
21       :tcp_defer_accept => 1,
23       # FreeBSD, we need to override this to 'dataready' if we
24       # eventually get HTTPS support
25       :accept_filter => 'httpready',
27       # same default value as Mongrel
28       :backlog => 1024,
30       # since we don't do keepalive, we'll always flush-on-close and
31       # this saves packets for everyone.
32       :tcp_nopush => true,
33     }
34     #:startdoc:
36     # configure platform-specific options (only tested on Linux 2.6 so far)
37     case RUBY_PLATFORM
38     when /linux/
39       # from /usr/include/linux/tcp.h
40       TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
42       # do not send out partial frames (Linux)
43       TCP_CORK = 3 unless defined?(TCP_CORK)
44     when /freebsd/
45       # do not send out partial frames (FreeBSD)
46       TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
48       def accf_arg(af_name)
49         [ af_name, nil ].pack('a16a240')
50       end if defined?(SO_ACCEPTFILTER)
51     end
53     def set_tcp_sockopt(sock, opt)
54       # highly portable, but off by default because we don't do keepalive
55       if defined?(TCP_NODELAY)
56         val = opt[:tcp_nodelay]
57         val = DEFAULTS[:tcp_nodelay] if nil == val
58         sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
59       end
61       val = opt[:tcp_nopush]
62       val = DEFAULTS[:tcp_nopush] if nil == val
63       val = val ? 1 : 0
64       if defined?(TCP_CORK) # Linux
65         sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
66       elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
67         sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
68       end
70       # No good reason to ever have deferred accepts off
71       # (except maybe benchmarking)
72       if defined?(TCP_DEFER_ACCEPT)
73         # this differs from nginx, since nginx doesn't allow us to
74         # configure the the timeout...
75         seconds = opt[:tcp_defer_accept]
76         seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
77         seconds = 0 unless seconds # nil/false means disable this
78         sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
79       elsif respond_to?(:accf_arg)
80         name = opt[:accept_filter]
81         name = DEFAULTS[:accept_filter] if nil == name
82         begin
83           sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
84         rescue => e
85           logger.error("#{sock_name(sock)} " \
86                        "failed to set accept_filter=#{name} (#{e.inspect})")
87         end
88       end
89     end
91     def set_server_sockopt(sock, opt)
92       opt = DEFAULTS.merge(opt || {})
94       TCPSocket === sock and set_tcp_sockopt(sock, opt)
96       if opt[:rcvbuf] || opt[:sndbuf]
97         log_buffer_sizes(sock, "before: ")
98         sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
99         sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
100         log_buffer_sizes(sock, " after: ")
101       end
102       sock.listen(opt[:backlog])
103       rescue => e
104         logger.error "error setting socket options: #{e.inspect}"
105         logger.error e.backtrace.join("\n")
106     end
108     def log_buffer_sizes(sock, pfx = '')
109       rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
110       sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
111       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
112     end
114     # creates a new server, socket. address may be a HOST:PORT or
115     # an absolute path to a UNIX socket.  address can even be a Socket
116     # object in which case it is immediately returned
117     def bind_listen(address = '0.0.0.0:8080', opt = {})
118       return address unless String === address
120       sock = if address[0] == ?/
121         if File.exist?(address)
122           if File.socket?(address)
123             begin
124               UNIXSocket.new(address).close
125               # fall through, try to bind(2) and fail with EADDRINUSE
126               # (or succeed from a small race condition we can't sanely avoid).
127             rescue Errno::ECONNREFUSED
128               logger.info "unlinking existing socket=#{address}"
129               File.unlink(address)
130             end
131           else
132             raise ArgumentError,
133                   "socket=#{address} specified but it is not a socket!"
134           end
135         end
136         old_umask = File.umask(opt[:umask] || 0)
137         begin
138           Kgio::UNIXServer.new(address)
139         ensure
140           File.umask(old_umask)
141         end
142       elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
143         new_ipv6_server($1, $2.to_i, opt)
144       elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
145         Kgio::TCPServer.new($1, $2.to_i)
146       else
147         raise ArgumentError, "Don't know how to bind: #{address}"
148       end
149       set_server_sockopt(sock, opt)
150       sock
151     end
153     def new_ipv6_server(addr, port, opt)
154       opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port)
155       defined?(IPV6_V6ONLY) or
156         abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
157       sock = Socket.new(AF_INET6, SOCK_STREAM, 0)
158       sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
159       sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
160       sock.bind(Socket.pack_sockaddr_in(port, addr))
161       IO_PURGATORY << sock
162       Kgio::TCPServer.for_fd(sock.fileno)
163     end
165     # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
166     def tcp_name(sock)
167       port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
168       /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
169     end
170     module_function :tcp_name
172     # Returns the configuration name of a socket as a string.  sock may
173     # be a string value, in which case it is returned as-is
174     # Warning: TCP sockets may not always return the name given to it.
175     def sock_name(sock)
176       case sock
177       when String then sock
178       when UNIXServer
179         Socket.unpack_sockaddr_un(sock.getsockname)
180       when TCPServer
181         tcp_name(sock)
182       when Socket
183         begin
184           tcp_name(sock)
185         rescue ArgumentError
186           Socket.unpack_sockaddr_un(sock.getsockname)
187         end
188       else
189         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
190       end
191     end
193     module_function :sock_name
195     # casts a given Socket to be a TCPServer or UNIXServer
196     def server_cast(sock)
197       begin
198         Socket.unpack_sockaddr_in(sock.getsockname)
199         Kgio::TCPServer.for_fd(sock.fileno)
200       rescue ArgumentError
201         Kgio::UNIXServer.for_fd(sock.fileno)
202       end
203     end
205   end # module SocketHelper
206 end # module Unicorn