about summary refs log tree commit homepage
path: root/lib/yahns/socket_helper.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/yahns/socket_helper.rb')
-rw-r--r--lib/yahns/socket_helper.rb117
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