class TFTP::Server::Base

Basic server utilizing threads for handling sessions.

It lacks a mutex around access to @clients, in case you'd want to stress test it for 10K or something.

@attr handler [Handler] Session handler @attr address [String] Address to listen to @attr port [Integer] Session dispatcher port @attr clients [Hash] Current sessions

Attributes

address[R]
clients[R]
handler[R]
port[R]

Public Class Methods

new(handler, opts = {}) click to toggle source

Initialize the server.

Options:

- :address => address to listen to (default: '0.0.0.0')
- :port    => dispatcher port (default: 69)
- :logger  => logger instance

@param handler [Handler] Initialized session handler @param opts [Hash] Options

# File lib/tftp/tftp.rb, line 299
def initialize(handler, opts = {})
  @handler = handler

  @address = opts[:address] || '0.0.0.0'
  @port    = opts[:port] || 69
  @logger  = opts[:logger]

  @clients = Hash.new
  @run = false
end

Public Instance Methods

run!() click to toggle source

Run the main server loop.

This is obviously blocking.

# File lib/tftp/tftp.rb, line 313
def run!
  log :info, "UDP server loop at #{@address}:#{@port}"
  @run = true
  Socket.udp_server_loop(@address, @port) do |msg, src|
    break unless @run

    addr = src.remote_address
    tag = "[#{addr.ip_address}:#{addr.ip_port.to_s.ljust(5)}]"
    log :info, "#{tag} New initial packet received"

    begin
      pkt = Packet.parse(msg)
    rescue ParseError => e
      log :warn, "#{tag} Packet parse error: #{e.to_s}"
      next
    end

    log :debug, "#{tag} -> PKT: #{pkt.inspect}"
    tid = get_tid
    tag = "[#{addr.ip_address}:#{addr.ip_port.to_s.ljust(5)}:#{tid.to_s.ljust(5)}]"
    sock = addr.connect_from(@address, tid)
    @clients[tid] = tag

    unless pkt.is_a?(Packet::RRQ) || pkt.is_a?(Packet::WRQ)
      log :warn, "#{tag} Bad initial packet: #{pkt.class}"
      sock.send(Packet::ERROR.new(4, 'Illegal TFTP operation.').encode, 0)
      sock.close
      next
    end

    Thread.new do
      @handler.run!(tag, pkt, sock, src)
      @clients.delete(tid)
      log :info, "#{tag} Session ended"
    end
  end
  log :info, 'UDP server loop has stopped'
end
stop() click to toggle source

Stop the main server loop.

This will allow the currently pending sessions to finish.

# File lib/tftp/tftp.rb, line 355
def stop
  log :info, 'Stopping UDP server loop'
  @run = false
  UDPSocket.new.send('break', 0, @address, @port)
end

Private Instance Methods

get_tid() click to toggle source

Get the server's TID.

The TID is basically a random port number we will use for a session. This actually tries to get a unique TID per session. It uses only ports 1024 - 65535 as not to require root.

# File lib/tftp/tftp.rb, line 367
def get_tid
  tid = 1024 + rand(64512)
  tid = 1024 + rand(64512) while @clients.has_key? tid
  tid
end
log(level, msg) click to toggle source
# File lib/tftp/tftp.rb, line 373
def log(level, msg)
  @logger.send(level, msg) if @logger
end