t/lib.perl: fix Perl integration tests w/o installation
[unicorn.git] / lib / unicorn / socket_helper.rb
blob986932f28876e102b603623d7dc4d66dd1b05a70
1 # -*- encoding: binary -*-
2 # frozen_string_literal: false
3 # :enddoc:
4 require 'socket'
6 module Unicorn
7   module SocketHelper
9     # internal interface
10     DEFAULTS = {
11       # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
12       # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
13       # This change shouldn't affect unicorn users behind nginx (a
14       # value of 1 remains an optimization).
15       :tcp_defer_accept => 1,
17       # FreeBSD, we need to override this to 'dataready' if we
18       # eventually support non-HTTP/1.x
19       :accept_filter => 'httpready',
21       # same default value as Mongrel
22       :backlog => 1024,
24       # favor latency over bandwidth savings
25       :tcp_nopush => nil,
26       :tcp_nodelay => true,
27     }
29     # configure platform-specific options (only tested on Linux 2.6 so far)
30     def accf_arg(af_name)
31       [ af_name, nil ].pack('a16a240')
32     end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
34     def set_tcp_sockopt(sock, opt)
35       # just in case, even LANs can break sometimes.  Linux sysadmins
36       # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
37       Socket.const_defined?(:SO_KEEPALIVE) and
38         sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
40       if Socket.const_defined?(:TCP_NODELAY)
41         val = opt[:tcp_nodelay]
42         val = DEFAULTS[:tcp_nodelay] if val.nil?
43         sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
44       end
46       val = opt[:tcp_nopush]
47       unless val.nil?
48         if Socket.const_defined?(:TCP_CORK) # Linux
49           sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
50         elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
51           sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
52         end
53       end
55       # No good reason to ever have deferred accepts off in single-threaded
56       # servers (except maybe benchmarking)
57       if Socket.const_defined?(:TCP_DEFER_ACCEPT)
58         # this differs from nginx, since nginx doesn't allow us to
59         # configure the the timeout...
60         seconds = opt[:tcp_defer_accept]
61         seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
62         seconds = 0 unless seconds # nil/false means disable this
63         sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
64       elsif respond_to?(:accf_arg)
65         name = opt[:accept_filter]
66         name = DEFAULTS[:accept_filter] if name.nil?
67         sock.listen(opt[:backlog])
68         got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
69         arg = accf_arg(name)
70         begin
71           sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
72         rescue => e
73           logger.error("#{sock_name(sock)} " \
74                        "failed to set accept_filter=#{name} (#{e.inspect})")
75           logger.error("perhaps accf_http(9) needs to be loaded".freeze)
76         end if arg != got
77       end
78     end
80     def set_server_sockopt(sock, opt)
81       opt = DEFAULTS.merge(opt || {})
83       set_tcp_sockopt(sock, opt) if sock.local_address.ip?
85       rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
86       if rcvbuf || sndbuf
87         log_buffer_sizes(sock, "before: ")
88         sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
89         sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
90         log_buffer_sizes(sock, " after: ")
91       end
92       sock.listen(opt[:backlog])
93     rescue => e
94       Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
95     end
97     def log_buffer_sizes(sock, pfx = '')
98       rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
99       sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
100       logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
101     end
103     # creates a new server, socket. address may be a HOST:PORT or
104     # an absolute path to a UNIX socket.  address can even be a Socket
105     # object in which case it is immediately returned
106     def bind_listen(address = '0.0.0.0:8080', opt = {})
107       return address unless String === address
109       sock = if address.start_with?('/')
110         if File.exist?(address)
111           if File.socket?(address)
112             begin
113               UNIXSocket.new(address).close
114               # fall through, try to bind(2) and fail with EADDRINUSE
115               # (or succeed from a small race condition we can't sanely avoid).
116             rescue Errno::ECONNREFUSED
117               logger.info "unlinking existing socket=#{address}"
118               File.unlink(address)
119             end
120           else
121             raise ArgumentError,
122                   "socket=#{address} specified but it is not a socket!"
123           end
124         end
125         old_umask = File.umask(opt[:umask] || 0)
126         begin
127           s = Socket.new(:UNIX, :STREAM)
128           s.bind(Socket.sockaddr_un(address))
129           s
130         ensure
131           File.umask(old_umask)
132         end
133       elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
134         new_tcp_server($1, $2.to_i, opt.merge(:ipv6=>true))
135       elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
136         new_tcp_server($1, $2.to_i, opt)
137       else
138         raise ArgumentError, "Don't know how to bind: #{address}"
139       end
140       set_server_sockopt(sock, opt)
141       sock
142     end
144     def new_tcp_server(addr, port, opt)
145       # n.b. we set FD_CLOEXEC in the workers
146       sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
147       if opt.key?(:ipv6only)
148         Socket.const_defined?(:IPV6_V6ONLY) or
149           abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
150         sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
151       end
152       sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
153       if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
154         sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
155       end
156       sock.bind(Socket.pack_sockaddr_in(port, addr))
157       sock
158     end
160     # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
161     def tcp_name(sock)
162       port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
163       addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
164     end
165     module_function :tcp_name
167     # Returns the configuration name of a socket as a string.  sock may
168     # be a string value, in which case it is returned as-is
169     # Warning: TCP sockets may not always return the name given to it.
170     def sock_name(sock)
171       case sock
172       when String then sock
173       when Socket
174         begin
175           tcp_name(sock)
176         rescue ArgumentError
177           Socket.unpack_sockaddr_un(sock.getsockname)
178         end
179       else
180         raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}"
181       end
182     end
184     module_function :sock_name
185   end # module SocketHelper
186 end # module Unicorn