Protocol coverage
| Spec | What |
|---|---|
| RFC 1350 | RRQ, WRQ, DATA, ACK, ERROR; octet and netascii modes (mail is refused) |
| RFC 1123 §4.2.3.1 | Sorcerer's Apprentice fix |
| RFC 1123 §4.2.3.2 | Exponential backoff of retransmissions |
| RFC 1123 §4.2.3.5 | Requests sent to broadcast or multicast addresses ignored |
| RFC 2347 | Option extension, OACK, ERROR 8 |
| RFC 2348 | blksize, 8 to 65464 |
| RFC 2349 | timeout (whole seconds) and tsize |
| RFC 7440 | windowsize, 1 to 65535 |
| tftp-hpa | utimeout, rollover, blksize2, cookie -- off unless a server allows them |
| Microsoft | mstfwindow, Windows bootmgr's variable window -- off unless allowed |
| pytftp | x-list and x-mtime: directory listings and modification times -- off unless allowed |
| RFC 3617 | tftp:// URLs |
RFC 2090 (multicast) and PXE MTFTP are not implemented.
Tested against: iPXE (booting in QEMU), tftp-hpa 5.3 (server and client), BusyBox 1.37 (server and client), dnsmasq 2.92, curl. What tftp-hpa 5.3's server answered to 22 requests is recorded, and every test run replays it with no peer installed; the differences from tftp-hpa are listed in the README, one recorded case each.
Negotiation
The client asks; the server answers with an OACK holding only what it
accepts. Unknown options and unusable values are ignored rather than
refused, as RFC 2347 requires, and the transfer runs on the default for
anything not acknowledged. The server clamps blksize and windowsize to
its TFTPServerOptions limits; the client refuses (ERROR 8) an OACK that grants
more than it asked for, an option it never requested, a timeout or utimeout
other than the one it asked for, or a number that is not ASCII digits.
A client whose request is answered with ERROR 8, 4 or 0 retries once with no
options (fallback=True), for servers that reject what they do not understand.
tsize is answered for any file whose size is known, except an empty one:
curl rejects tsize 0 in an OACK, and the transfer shows the size anyway. A
netascii read request's tsize is left out: its size needs the whole file
read, which a server does not do before it answers (tftp-hpa does the same).
Extensions and profiles
Options are handlers in a registry, so an application can add its own. A
server acknowledges the RFC options by default; tftp-hpa's extensions only
when allowed (TFTPServerOptions(allowed=...)), and any option can be refused
outright for firmware that asks for it and then mishandles it
(refused={"windowsize"}). Profiles bundle coherent settings: pxe (fit
blksize to the interface MTU so boot ROMs never see IP fragments), hpa,
legacy, strict.
mstfwindow
Windows Boot Manager (Windows 8 and later) offers mstfwindow=31416; a WDS
server answers 27182 and both start with a window of 4 blocks. The client
can then resize the window through its ACKs, in a format Microsoft has not
published, so this library keeps the window at 4 (bytes after an ACK's block
number are ignored). Allow it with --allow mstfwindow or
TFTPServerOptions(allowed=STANDARD_OPTIONS | {"mstfwindow"}). Recent bootmgr
also negotiates the standard windowsize, which wins when both are
acknowledged.
Listing and modification times
TFTP has no way to list a directory or learn a file's time. A pytftp server
that allows LISTING_OPTIONS (pytftp serve --listing) answers an RRQ for a
directory carrying x-list=1 with a text listing, acknowledging x-list in
its OACK, and puts a file's modification time in the OACK when asked for
x-mtime. A server that does not know them ignores them (RFC 2347): a
directory then reads as "file not found". TFTPClient.stat(), TFTPClient.listdir(),
pytftp ls and TFTPPath.iterdir()/walk()/glob() build on this.
Under loss
- Retransmission is driven only by timeouts on the sending side, backing
off exponentially; a duplicate ACK never triggers a resend when
windowsizeis 1. - Repeated OACKs (the server's answer when ACK 0 or DATA 1 was lost) are tolerated: a downloading client re-acknowledges, an uploading one waits for its own timeout.
- Windows: the receiver acknowledges every
windowsizeblocks. On a gap it acknowledges the last block it has in order, twice at most per gap (the first report may move the sender's window on; the second asks for the blocks from the hole), and once for a run of blocks it already has. An ACK that moves the window sends only the blocks that now fit; a duplicate ACK or a timeout resends the blocks in flight, and an ACK resends them at most once per two windows of progress, so one duplicated, lost or late ACK costs one window of DATA and not every window after it. The sender keeps the window in memory, so a resend never re-reads the source. - Stray packets: anything from an address or port other than the peer's gets ERROR 5 and is otherwise ignored.
- Dallying: the server keeps a finished upload open for one timeout to
re-acknowledge a repeated last block (the client can do the same with
dally=True). - Rollover: block numbers are tracked as unbounded integers and mapped to
16 bits on the wire, so file size is unlimited. A receiver that did not
negotiate
rolloverfollows a sender that wraps to 1.
Addresses
The server listens on 0.0.0.0 and :: by default, two sockets on one port. Each transfer
gets its own socket bound to the address the request was sent to, read from
pktinfo, so on a multi-homed host or a virtual IP the client hears back from
the address it used.