Class: TunnelRb::Server::ControlServer

Inherits:
Object
  • Object
show all
Defined in:
lib/tunnel_rb/server/control_server.rb

Overview

Owns the control plane: port 7777 listener, handshake handler, the IO.select-based read loop that consumes pong/disconnect events, and the ping loop that evicts unresponsive clients.

Constant Summary collapse

PING_INTERVAL =

seconds; keeps NAT mappings alive

30
PONG_MISSES =
3
HANDSHAKE_POOL_SIZE =
64
HANDSHAKE_QUEUE_SIZE =
64
READ_CHUNK =
4096
LINE_MAX =

drop clients that send oversized lines without n

16 * 1024
SELECT_TIMEOUT =
1.0
IDLE_SLEEP =

when no clients are connected

0.5

Instance Method Summary collapse

Constructor Details

#initialize(port:, domain:, registry:, token_store:, pending_connections:, logger:, url_port: nil, tls_context: nil) ⇒ ControlServer

Returns a new instance of ControlServer.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/tunnel_rb/server/control_server.rb', line 25

def initialize(port:, domain:, registry:, token_store:, pending_connections:, logger:, url_port: nil, tls_context: nil)
  @port = port
  @domain = domain
  @url_port = url_port
  @registry = registry
  @token_store = token_store
  @pending_connections = pending_connections
  @tls_context = tls_context
  @logger = logger

  @pool = ThreadPool.new(HANDSHAKE_POOL_SIZE, max_queue: HANDSHAKE_QUEUE_SIZE)
  @stopping = false
  @server = nil
end

Instance Method Details

#ping_loopObject



84
85
86
87
88
# File 'lib/tunnel_rb/server/control_server.rb', line 84

def ping_loop
  while interruptible_sleep(PING_INTERVAL)
    tick_pings
  end
end

#read_loopObject

IO.select over all registered control sockets. One thread serves all clients, so the count of long-lived ping/pong loops doesn’t grow with the number of registered tunnels.



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/tunnel_rb/server/control_server.rb', line 63

def read_loop
  until @stopping
    sockets = @registry.sockets
    if sockets.empty?
      sleep IDLE_SLEEP
      next
    end

    begin
      readable, = IO.select(sockets, nil, nil, SELECT_TIMEOUT)
    rescue IOError, Errno::EBADF
      # A socket was closed (e.g. shutdown) while we were waiting on it.
      # Drop it from our snapshot and re-loop to rebuild the set.
      next
    end
    next unless readable

    readable.each { |socket| handle_readable(socket) }
  end
end

#startObject

Blocking accept loop. Returns when stop is called.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/tunnel_rb/server/control_server.rb', line 45

def start
  tcp_server = TCPServer.new("0.0.0.0", @port)
  @server = tls? ? TLS.wrap_listener(tcp_server, @tls_context) : tcp_server
  @logger.info "🎧 Control server listening for clients on #{@port}"

  loop do
    socket = @server.accept
    @pool.submit { handle_connection(socket) }
  rescue IOError, Errno::EBADF
    break if @stopping

    raise
  end
end

#stopObject



90
91
92
93
94
95
# File 'lib/tunnel_rb/server/control_server.rb', line 90

def stop
  @stopping = true
  @server&.close rescue nil
  @pool.shutdown
  @registry.sockets.each { |s| s.close rescue nil }
end

#tls?Boolean

Returns:

  • (Boolean)


40
41
42
# File 'lib/tunnel_rb/server/control_server.rb', line 40

def tls?
  !@tls_context.nil?
end