nobodd.tftpd
Defines several classes for the purposes of constructing TFTP servers. The most
useful are TFTPBaseHandler
and TFTPBaseServer
which are
abstract base classes for the construction of a TFTP server with an arbitrary
source of files (these are used by nobodd’s main
module). In
addition, TFTPSimplerHandler
and TFTPSimplerServer
are
provided as a trivial example implementation of a straight-forward TFTP file
server.
For example, to start a TFTP server which will serve files from the current directory on (unprivileged) port 1069:
>>> from nobodd.tftpd import SimpleTFTPServer
>>> server = SimpleTFTPServer(('0.0.0.0', 1069), '.')
>>> server.serve_forever()
Handler Classes
- class nobodd.tftpd.TFTPBaseHandler(request, client_address, server)[source]
A abstract base handler for building TFTP servers.
Implements
do_RRQ()
to handle the initialRRQPacket
of a transfer. This calls the abstractresolve_path()
to obtain thePath
-like object representing the requested file. Descendents must (at a minimum) overrideresolve_path()
to implement a TFTP server.- do_ERROR(packet)[source]
Handles
ERRORPacket
by ignoring it. The only way this should appear on the main port is at the start of a transfer, which would imply we’re not going to start a transfer anyway.
- do_RRQ(packet)[source]
Handles packet, the initial
RRQPacket
of a connection.If option negotiation succeeds, and
resolve_path()
returns a validPath
-like object, this method will spin up aTFTPSubServer
instance in a background thread (seeTFTPSubServers
) on an ephemeral port to handle all further interaction with this client.
- resolve_path(filename)[source]
Given filename, as requested by a TFTP client, returns a
Path
-like object.In the base class, this is an abstract method which raises
NotImplementedError
. Descendents must override this method to return aPath
-like object, specifically one with a workingopen()
method, representing the file requested, or raise anOSError
(e.g.FileNotFoundError
) if the requested filename is invalid.
- class nobodd.tftpd.SimpleTFTPHandler(request, client_address, server)[source]
An implementation of
TFTPBaseHandler
that overrides usesSimpleTFTPServer.base_path
forresolve_path()
.- resolve_path(filename)[source]
Resolves filename against
SimpleTFTPServer.base_path
.
Server Classes
- class nobodd.tftpd.TFTPBaseServer(address, handler_class, bind_and_activate=True)[source]
A abstract base for building TFTP servers.
To build a concrete TFTP server, make a descendent of
TFTPBaseHandler
that overridesresolve_path()
, then make a descendent of this class that callssuper().__init__
with the overridden handler class. SeeSimpleTFTPHandler
andSimpleTFTPServer
for examples.Note
While it is common to combine classes like
UDPServer
andTCPServer
with the threading or fork-based mixins there is little point in doing so withTFTPBaseServer
.Only the initial packet of a TFTP transaction arrives on the “main” port; every packet after this is handled by a background thread with its own ephemeral port. Thus, multi-threading or multi-processing of the initial connection only applies to a single (minimal) packet.
Command Line Use
Just as http.server
can be invoked from the command line as a standalone
server using the interpreter’s -m
option, so nobodd.tftpd
can
too. To serve the current directory as a TFTP server:
python -m nobodd.tftpd
The server listens to port 6969 by default. This is not the registered port 69 of TFTP, but as that port requires root privileges by default on UNIX platforms, a safer default was selected (the security provenance of this code is largely unknown, and certainly untested at higher privilege levels). The default port can be overridden by passed the desired port number as an argument:
python -m nobodd.tftpd 1069
By default, the server binds to all interfaces. The option -b/--bind
specifies an address to which it should bind instead. Both IPv4 and IPv6
addresses are supported. For example, the following command causes the server
to bind to localhost only:
python -m nobodd.tftpd --bind 127.0.0.1
By default, the server uses the current directory. The option
-d/--directory
specifies a directory from which it should serve files
instead. For example:
python -m nobodd.tftpd --directory /tmp/
Internal Classes and Exceptions
The following classes and exceptions are entirely for internal use and should never be needed (directly) by applications.
- class nobodd.tftpd.TFTPClientState(address, path, mode='octet')[source]
Represents the state of a single transfer with a client. Constructed with the client’s address (format varies according to family), the path of the file to transfer (must be a
Path
-like object, specifically one with a functioningopen()
method), and the mode of the transfer (must be eitherTFTP_BINARY
orTFTP_NETASCII
).- address
The address of the client.
- blocks
An internal mapping of block numbers to blocks. This caches blocks that have been read, transmitted, but not yet acknowledged. As
ACK
packets are received, blocks are removed from this cache.
- block_size
The size, in bytes, of blocks to transfer to the client.
- mode
The transfer mode. One of
TFTP_BINARY
orTFTP_NETASCII
.
- source
The file-like object opened from the specified path.
- timeout
The timeout, in nano-seconds, to use before re-transmitting packets to the client.
- ack(block_num)[source]
Specifies that block_num has been acknowledged by the client and can be removed from
blocks
, the internal block cache.
- close()[source]
Closes the source file associated with the client state. This method is idempotent.
- get_block(block_num)[source]
Returns the
bytes
of the specified block_num.If the block_num has not been read yet, this will cause the
source
to be read. Otherwise, it will be returned from the as-yet unacknowledged block cache (inblocks
). If the block has already been acknowledged, which may happen asynchronously, this will raiseAlreadyAcknowledged
.A
ValueError
is raised if an invalid block is requested.
- get_size()[source]
Attempts to calculate the size of the transfer. This is used when negotiating the
tsize
option.At first,
os.fstat()
is attempted on the open file; if this fails (e.g. because there’s no validfileno
), the routine will attempt toseek()
to the end of the file briefly to determine its size. RaisesOSError
in the case that the size cannot be determined.
- negotiate(options)[source]
Called with options, a mapping of option names to values (both
str
) that the client wishes to negotiate.Currently supported options are defined in
nobodd.tftp.TFTP_OPTIONS
. The original options mapping is left unchanged. Returns a new options mapping containing only those options that we understand and accept, and with values adjusted to those that we can support.Raises
BadOptions
in the case that the client requests pathologically silly or dangerous options.
- property finished
Indicates whether the transfer has completed or not. A transfer is considered complete when the final (under-sized) block has been sent to the client and acknowledged.
- property transferred
Returns the number of bytes transferred to client and successfully acknowledged.
- class nobodd.tftpd.TFTPHandler(request, client_address, server)[source]
Abstract base handler for TFTP transfers.
This handles decoding TFTP packets with the classes defined in
nobodd.tftp
. If the decoding is successful, it attempts to call a correspondingdo_
method (e.g.do_RRQ()
,do_ACK()
) with the decoded packet. The handler must return anobodd.tftp.Packet
in response.This base class defines no
do_
methods itself; seeTFTPBaseHandler
andTFTPSubHandler
.- finish()[source]
Overridden to send the response written to
wfile
. Returns the number of bytes written.Note
In contrast to the usual DatagramRequestHandler, this method does not send an empty packet in the event that
wfile
has no content, as that confused several TFTP clients.
- class nobodd.tftpd.TFTPSubHandler(request, client_address, server)[source]
Handler for all client interaction after the initial
RRQPacket
.Only the initial packet goes to the “main” TFTP port (69). After that, each transfer communicates between the client’s original port (presumably in the ephemeral range) and an ephemeral server port, specific to that transfer. This handler is spawned by the main handler (a descendent of
TFTPBaseHandler
) and deals with all further client communication. In practice this means it only handlesACKPacket
andERRORPacket
.- do_ACK(packet)[source]
Handles
ACKPacket
by callingTFTPClientState.ack()
. Terminates the thread for this sub-handler if the transfer is complete, and otherwise sends the nextDATAPacket
in response.
- do_ERROR(packet)[source]
Handles
ERRORPacket
by terminating the transfer (in accordance with the spec.)
- class nobodd.tftpd.TFTPSubServer(main_server, client_state)[source]
The server class associated with
TFTPSubHandler
.You should never need to instantiate this class yourself. The base handler should create an instance of this to handle all communication with the client after the initial
RRQ
packet.
- class nobodd.tftpd.TFTPSubServers[source]
Manager class for the threads running
TFTPSubServer
.TFTPBaseServer
creates an instance of this to keep track of the background threads that are running transfers withTFTPSubServer
.- add(server)[source]
Add server, a
TFTPSubServer
instance, as a new background thread to be tracked.
- exception nobodd.tftpd.TransferDone[source]
Exception raised internally to signal that a transfer has been completed.