Class: TFTP::Server::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/tftp/tftp.rb

Overview

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.

Direct Known Subclasses

RWSimple

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handler, opts = {}) ⇒ Base

Initialize the server.

Options:

  • :host => host to bind to (default: 127.0.0.1)
  • :port => dispatcher port (default: 69)
  • :logger => logger instance

Parameters:

  • handler (Handler)

    Initialized session handler

  • opts (Hash) (defaults to: {})

    Options



307
308
309
310
311
312
313
314
315
316
# File 'lib/tftp/tftp.rb', line 307

def initialize(handler, opts = {})
  @handler = handler

  @host = opts[:host] || '127.0.0.1'
  @port = opts[:port] || 69
  @logger = opts[:logger]

  @clients = Hash.new
  @run = false
end

Instance Attribute Details

#clientsHash

Current sessions

Returns:

  • (Hash)

    the current value of clients



294
295
296
# File 'lib/tftp/tftp.rb', line 294

def clients
  @clients
end

#handlerHandler

Session handler

Returns:

  • (Handler)

    the current value of handler



294
295
296
# File 'lib/tftp/tftp.rb', line 294

def handler
  @handler
end

#hostString

Host the sockets bind to

Returns:

  • (String)

    the current value of host



294
295
296
# File 'lib/tftp/tftp.rb', line 294

def host
  @host
end

#portInteger

Session dispatcher port

Returns:

  • (Integer)

    the current value of port



294
295
296
# File 'lib/tftp/tftp.rb', line 294

def port
  @port
end

Instance Method Details

#get_tidObject (private)

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.



375
376
377
378
379
# File 'lib/tftp/tftp.rb', line 375

def get_tid
  tid = 1024 + rand(64512)
  tid = 1024 + rand(64512) while @clients.has_key? tid
  tid
end

#log(level, msg) ⇒ Object (private)



381
382
383
# File 'lib/tftp/tftp.rb', line 381

def log(level, msg)
  @logger.send(level, msg) if @logger
end

#run!Object

Run the main server loop.

This is obviously blocking.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/tftp/tftp.rb', line 321

def run!
  log :info, "UDP server loop at #{@host}:#{@port}"
  @run = true
  Socket.udp_server_loop(@host, @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(@host, 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

#stopObject

Stop the main server loop.

This will allow the currently pending sessions to finish.



363
364
365
366
367
# File 'lib/tftp/tftp.rb', line 363

def stop
  log :info, 'Stopping UDP server loop'
  @run = false
  UDPSocket.new.send('break', 0, @host, @port)
end