diff options
Diffstat (limited to 'lib/yahns/socket_helper.rb')
-rw-r--r-- | lib/yahns/socket_helper.rb | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/lib/yahns/socket_helper.rb b/lib/yahns/socket_helper.rb new file mode 100644 index 0000000..61f2b0f --- /dev/null +++ b/lib/yahns/socket_helper.rb @@ -0,0 +1,117 @@ +# -*- encoding: binary -*- +# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors +# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt) +# this is only meant for Yahns::Server +module Yahns::SocketHelper # :nodoc: + def set_server_sockopt(sock, opt) + opt = {backlog: 1024}.merge!(opt) if opt + + TCPSocket === sock and sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1) + sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1) + + if opt[:rcvbuf] || opt[:sndbuf] + log_buffer_sizes(sock, "before: ") + { SO_RCVBUF: :rcvbuf, SO_SNDBUF: :sndbuf }.each do |optname,cfgname| + val = opt[cfgname] and sock.setsockopt(:SOL_SOCKET, optname, val) + end + log_buffer_sizes(sock, " after: ") + end + sock.listen(opt[:backlog]) + rescue => e + Yahns::Log.exception(@logger, "#{sock_name(sock)} #{opt.inspect}", e) + end + + def log_buffer_sizes(sock, pfx = '') + rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int + sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int + @logger.info("#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}") + end + + # creates a new server, socket. address may be a HOST:PORT or + # an absolute path to a UNIX socket. address can even be a Socket + # object in which case it is immediately returned + def bind_listen(address, opt) + return address unless String === address + opt ||= {} + + sock = if address[0] == ?/ + if File.exist?(address) + if File.socket?(address) + 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 + else + raise ArgumentError, + "socket=#{address} specified but it is not a socket!" + end + end + old_umask = File.umask(opt[:umask] || 0) + begin + Kgio::UNIXServer.new(address) + ensure + File.umask(old_umask) + end + elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address + new_ipv6_server($1, $2.to_i, opt) + elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address + Kgio::TCPServer.new($1, $2.to_i) + else + raise ArgumentError, "Don't know how to bind: #{address}" + end + set_server_sockopt(sock, opt) + sock + end + + def new_ipv6_server(addr, port, opt) + opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port) + sock = Socket.new(:AF_INET6, :SOCK_STREAM, 0) + sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0) + sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1) + sock.bind(Socket.pack_sockaddr_in(port, addr)) + sock.autoclose = false + Kgio::TCPServer.for_fd(sock.fileno) + end + + # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6 + def tcp_name(sock) + port, addr = Socket.unpack_sockaddr_in(sock.getsockname) + /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}" + end + + # Returns the configuration name of a socket as a string. sock may + # be a string value, in which case it is returned as-is + # Warning: TCP sockets may not always return the name given to it. + def sock_name(sock) + case sock + when String then sock + when UNIXServer + Socket.unpack_sockaddr_un(sock.getsockname) + when TCPServer + tcp_name(sock) + when Socket + begin + tcp_name(sock) + rescue ArgumentError + Socket.unpack_sockaddr_un(sock.getsockname) + end + else + raise ArgumentError, "Unhandled class #{sock.class}: #{sock.inspect}" + end + end + + # casts a given Socket to be a TCPServer or UNIXServer + def server_cast(sock) + sock.autoclose = false + begin + Socket.unpack_sockaddr_in(sock.getsockname) + Kgio::TCPServer.for_fd(sock.fileno) + rescue ArgumentError + Kgio::UNIXServer.for_fd(sock.fileno) + end + end +end |