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:

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

Parameters:

  • handler (Handler)

    Initialized session handler

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

    Options



299
300
301
302
303
304
305
306
307
308
# 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

Instance Attribute Details

#addressString

Address to listen to

Returns:

  • (String)

    the current value of address



286
287
288
# File 'lib/tftp/tftp.rb', line 286

def address
  @address
end

#clientsHash

Current sessions

Returns:

  • (Hash)

    the current value of clients



286
287
288
# File 'lib/tftp/tftp.rb', line 286

def clients
  @clients
end

#handlerHandler

Session handler

Returns:

  • (Handler)

    the current value of handler



286
287
288
# File 'lib/tftp/tftp.rb', line 286

def handler
  @handler
end

#portInteger

Session dispatcher port

Returns:

  • (Integer)

    the current value of port



286
287
288
# File 'lib/tftp/tftp.rb', line 286

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.



367
368
369
370
371
# 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) ⇒ Object (private)



373
374
375
# File 'lib/tftp/tftp.rb', line 373

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

#run!Object

Run the main server loop.

This is obviously blocking.



313
314
315
316
317
318
319
320
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
# 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

#stopObject

Stop the main server loop.

This will allow the currently pending sessions to finish.



355
356
357
358
359
# 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