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


282
283
284
285
286
287
288
289
290
291
# File 'lib/tftp/tftp.rb', line 282

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


269
270
271
# File 'lib/tftp/tftp.rb', line 269

def clients
  @clients
end

#handlerHandler

Session handler

Returns:

  • (Handler)

    the current value of handler


269
270
271
# File 'lib/tftp/tftp.rb', line 269

def handler
  @handler
end

#hostString

Host the sockets bind to

Returns:

  • (String)

    the current value of host


269
270
271
# File 'lib/tftp/tftp.rb', line 269

def host
  @host
end

#portInteger

Session dispatcher port

Returns:

  • (Integer)

    the current value of port


269
270
271
# File 'lib/tftp/tftp.rb', line 269

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.


350
351
352
353
354
# File 'lib/tftp/tftp.rb', line 350

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

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


356
357
358
# File 'lib/tftp/tftp.rb', line 356

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

#run!Object

Run the main server loop.

This is obviously blocking.


296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/tftp/tftp.rb', line 296

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.


338
339
340
341
342
# File 'lib/tftp/tftp.rb', line 338

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