tftp.server
tftp.server
The TFTP servers (blocking and asyncio), their request handlers, and the per-transfer session.
AsyncTFTPHandler
Bases: Protocol
What an :class:~tftp.AsyncTFTPServer needs from a handler: coroutine hooks.
async open_read(context) returns an :class:AsyncTFTPReader and
async open_write(context, size) an :class:AsyncTFTPWriter. Both run as
tasks on the server's event loop and may raise what :class:TFTPHandler's
hooks may. A synchronous handler is given to the server through
:class:ThreadedHandler.
AsyncTFTPReader
Bases: Protocol
An asynchronous source an :class:~tftp.AsyncTFTPServer reads a download from.
Required: async read(size) returning b"" at the end, and
async close(). Optional: size, the total length, which answers
tsize. The server reads ahead into a bounded buffer, so a slow source
slows the transfer instead of filling memory.
AsyncTFTPServer(root_or_handler, *, listen=None, port=69, writable=False, create=True, overwrite=False, timeout=1.0, retries=5, options=None, max_sessions=DEFAULT_MAX_SESSIONS, reply_from_request_address=True, dally=True, on_complete=None, limits=None, ignore_broadcast=True, backoff=2.0, max_timeout=None, trace=None, port_range=None)
Bases: AsyncioService, ServerBase
:class:tftp.TFTPServer for asyncio; same arguments except open_in_thread/workers.
root_or_handler is a directory, a handler with coroutine hooks
(:class:AsyncTFTPHandler) or a synchronous handler wrapped in
:class:ThreadedHandler; a bare synchronous handler raises TypeError.
Use as async with AsyncTFTPServer(...) as server: await server.serve_forever(),
or await server.start() ... await server.aclose().
The constructor opens nothing: :meth:bind does, and :meth:start,
:meth:serve_forever and async with call it. shutdown() never
blocks and is safe from any thread; await wait_closed() returns once
serving has stopped; await aclose() shuts down, waits and releases the
sockets, and is final: :meth:start or :meth:serve_forever afterwards,
or while serving, raises RuntimeError.
Source code in src/tftp/server/_asyncio.py
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | |
AsyncTFTPWriter
Bases: Protocol
An asynchronous sink an :class:~tftp.AsyncTFTPServer writes an upload into.
Required: async write(data) and async close(), which is awaited
before the last block is acknowledged, so a failing close reaches the
client as an ERROR. The server buffers a bounded amount ahead of the sink;
data is always bytes. An abandoned upload cancels the write in
progress and does not call close.
AtomicWriter(path, *, overwrite=True, mode=None)
Writes to a temporary file beside path and renames it on close.
A partial upload therefore never replaces or appears as path;
:meth:abort deletes the temporary file. The temporary file is private
(mode 0600) unless mode is given: then it is created with that mode
less the process's umask, and takes the permissions of the file it
replaces. A process killed mid-transfer leaves .name.*.part beside
path. On Windows a refused rename is tried again for half a second.
Source code in src/tftp/server/_handler.py
336 337 338 339 340 341 342 343 344 345 346 347 348 349 | |
PortRange(low, high)
dataclass
The UDP ports transfer sockets may use, low to high inclusive.
Each transfer needs a port of its own; pinning them to a range lets a
firewall allow them (tftp-hpa -R, dnsmasq --tftp-port-range).
An immutable value: len() counts the ports, iteration and in
cover them in order, str() is LOW:HIGH and PortRange.parse
reads it back. Which port a server takes next is the server's own
:class:PortAllocator.
:raises TypeError: a bound that is not an int.
:raises TFTPValueError: not 1 <= low <= high <= 65535.
parse(text)
classmethod
LOW:HIGH (LOW-HIGH too), ASCII digits, as str(range) writes it.
:raises TypeError: text is not a str.
:raises TFTPValueError: anything else.
Source code in src/tftp/server/_session.py
93 94 95 96 97 98 99 100 101 102 103 104 105 | |
try_parse(text, default=None)
classmethod
:meth:parse, or default for text that is not a port range.
Still raises :class:TypeError when text is not a str.
Source code in src/tftp/server/_session.py
107 108 109 110 111 112 113 114 115 116 | |
TFTPChunkReader
Bases: Protocol
A :class:TFTPReader that has read(size) in place of readinto.
TFTPHandler
Bases: Protocol
What a synchronous server needs from a handler.
Both hooks run one request at a time, in a worker thread unless the handler
has opens_fast = True (an optional attribute: opening is quick and never
blocks, so it runs inline on the server's thread). open_read returns a
:class:TFTPReader or :class:TFTPChunkReader; open_write returns a
:class:TFTPWriter, given the client's announced tsize as size (or
None).
Either hook may raise :class:TFTPError to refuse with a specific code, or
OSError, which is mapped by errno (ENOENT -> file not found,
EACCES -> access violation, ENOSPC -> disk full...).
A hook is a plain function: a handler whose hooks are async def belongs
to :class:~tftp.AsyncTFTPServer, and the synchronous server refuses it.
TFTPReader
Bases: Protocol
A binary source a synchronous server reads a download from.
Required: readinto(buffer) and close(). A source with read(size)
instead is a :class:TFTPChunkReader.
Optional members, each read once per transfer when present:
size-- the total length, an integer, which answerstsize;mtime-- the modification time in seconds, which answersx-mtime;set_wakeup(callback)-- stores a callable the source calls, from any thread, when areadintothat raised :class:tftp.WouldBlockcan make progress;lists_directories--Trueon a source that holds a directory listing, which is what makes the server acknowledgex-list.
readinto may raise :class:tftp.WouldBlock for "no data yet".
TFTPRequestContext(request, peer, *, local_address=None, interface_index=0)
Everything known about a request when a handler is asked to open it.
:ivar request: the parsed RRQ/WRQ (filename, mode, options).
:ivar peer: the client's (host, port, ...).
:ivar local_address: the address the request was sent to, when the
platform reports it (see TFTPServer.has_pktinfo), else None.
:ivar interface_index: the interface it arrived on, or 0.
:ivar interface: that interface as a netimps.Interface (name,
addresses, MTU), or None when unknown.
:ivar listing: an RRQ asking for a directory listing (x-list) that
the server's policy allows; a handler may then answer a directory
with a :class:tftp.listing.DirectoryListing.
Source code in src/tftp/server/_handler.py
62 63 64 65 66 67 68 69 70 71 72 73 74 75 | |
with_filename(filename)
A copy of this context for a request naming filename instead.
Everything else is carried over (the listing flag, the interface), so a wrapper that rewrites names does not have to know the fields.
Source code in src/tftp/server/_handler.py
77 78 79 80 81 82 83 84 85 | |
TFTPServer(root_or_handler, *, listen=None, port=69, writable=False, create=True, overwrite=False, timeout=1.0, retries=5, options=None, max_sessions=DEFAULT_MAX_SESSIONS, reply_from_request_address=True, dally=True, on_complete=None, limits=None, ignore_broadcast=True, backoff=2.0, max_timeout=None, trace=None, open_in_thread=None, workers=8, port_range=None)
Bases: SelectorService, ServerBase
A TFTP server.
:param root_or_handler: a directory to serve (wrapped in
:class:FilesystemBackend with writable, create and
overwrite), or a handler object.
:param listen: where to listen, as netimps reads it (netimps.ListenLike):
an address, "host:port", "[v6]:port", an adapter name or MAC (a
wildcard socket limited to that adapter), several of them in a list or
joined by commas. None (the default) is every address of both
families: "*" and "::", two sockets, the IPv6 one left out on a
host with no IPv6. A host name is never resolved.
:param port: the port a listen entry that names none gets; 0 picks a
free one (with listen=None both sockets share it; see
:attr:server_address and :attr:server_addresses, valid after :meth:bind).
:param timeout: retransmission timeout, unless a client negotiates its own.
:param retries: retransmissions of one packet before abandoning a transfer.
:param options: what the server negotiates (:class:TFTPServerOptions).
:param max_sessions: concurrent transfers; requests beyond it get
ERROR 0 "server busy". The default is 500 on every platform; None
is unlimited, except on Windows, where select() caps it at 510
(a larger number there is a ValueError).
:param reply_from_request_address: answer each request from the address
it was sent to (pktinfo). When the platform cannot report it, replies
come from the listening address, or the routing table's choice when
listening on a wildcard.
:param dally: after acknowledging the last block of an upload, keep the
transfer open for one timeout to re-acknowledge a retransmitted last
block (RFC 1350 section 6). Costs nothing but a socket for that long.
:param on_complete: called with a :class:TransferResult after every
transfer, failed ones included (result.error is set).
:param limits: request, per-client and duration bounds
(:class:TFTPServerLimits).
:param ignore_broadcast: silently drop requests sent to a broadcast or
multicast address (RFC 1123 4.2.3.5). Needs pktinfo to see the
destination; without it every request looks unicast.
:param backoff: each consecutive retransmission waits this many times
longer (RFC 1123 4.2.3.2); progress resets it.
:param max_timeout: ceiling for the backed-off wait; None is eight
times the timeout.
:param trace: trace(PacketEvent) for every datagram of every
transfer, received and sent (role="server", one session
id per transfer). Requests refused before a transfer exists are
not traced. :func:tftp.capture.trace_to makes a hook of a pktcap writer.
:param open_in_thread: call the handler's open_read/open_write in
a worker thread, so a handler that blocks (an HTTP request, an
upstream server) never stalls other transfers. None decides from
the handler: those with opens_fast = True (the built-in
file and memory handlers) open inline, everything else in a worker.
:param workers: size of that worker pool.
:param port_range: a :class:PortRange, a (low, high) pair
(inclusive), a range or "LOW:HIGH" text: transfer sockets take
their ports from it, round-robin from a position this server owns, so a
firewall can allow them. A request arriving while every port is in
use gets ERROR 0 "server busy". None lets the OS choose.
Source code in src/tftp/server/_sync.py
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 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 334 | |
TFTPServerLimits(*, max_request_size=1024, max_filename_length=512, max_options=16, max_option_length=255, max_sessions_per_client=None, max_duration=None, max_idle=60.0)
Bounds on what one request, one client and one transfer may use.
:param max_request_size: bytes in an RRQ/WRQ datagram. RFC 2347 keeps a
request within 512 octets; the default allows twice that.
:param max_filename_length: characters in a requested filename.
:param max_options: options in one request.
:param max_option_length: characters in one option name or value.
:param max_sessions_per_client: concurrent transfers from one client
address (any port); None is unlimited.
:param max_duration: seconds one transfer may take; None is unlimited.
:param max_idle: seconds a transfer may go with no datagram from its
peer before it ends; a timeout the client negotiated cannot extend
it. Time the server spends waiting on its own handler does not count.
None is unbounded.
A request over a limit is answered with ERROR 4 (ERROR 0 "server busy"
for the per-client cap). Window memory is bounded separately, by
:class:TFTPServerOptions max_window_bytes.
Source code in src/tftp/server/_policy.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | |
check(request)
Raise :class:TFTPError (4) if request exceeds a limit.
Source code in src/tftp/server/_policy.py
79 80 81 82 83 84 85 86 87 | |
TFTPStats(*names)
Monotonic counters, safe to read from any thread.
Server counters: requests (RRQ/WRQ seen on the listening port),
refused (answered with an ERROR before a transfer started, or
dropped by a limit), started, completed, failed,
declined (the client refused the OACK before any data: a size probe),
bytes_sent, bytes_received, retransmits. A relay counts
requests, refused, started, completed, failed,
bytes_to_clients, bytes_from_clients. snapshot() returns them
all, plus active from the owner.
Source code in src/tftp/server/_stats.py
24 25 26 | |
TFTPWriter
Bases: Protocol
A binary sink a synchronous server writes an upload into.
Required: write(data) and close(). close is called once the last
block is written and before it is acknowledged, so a failing close
reaches the client as an ERROR; it may raise :class:tftp.WouldBlock
while the data is still on its way elsewhere.
Optional members:
abort()-- called instead ofclosewhen the transfer fails;copies_writes--Truewhenwritecopies what it is given. Without itwritereceivesbytes, because the argument would otherwise be a view of a receive buffer that the next packet reuses (standard file objects always count as copying);set_wakeup(callback)-- as for :class:TFTPReader.
ThreadedHandler(handler, *, executor=None)
Gives a synchronous handler to :class:~tftp.AsyncTFTPServer.
open_read and open_write of handler run in executor (the
loop's default one when None), so a handler that blocks never stalls
the loop, unless the handler has opens_fast = True, when they run on
the loop. The streams handler returns are synchronous and are read and
written on the loop thread, as a synchronous server's are on its own.
Source code in src/tftp/server/_handler.py
263 264 265 266 267 268 269 270 271 272 | |