tftp
A pure-Python TFTP client, server, relay and capture decoder for IPv4 and
IPv6 that implements the whole of modern TFTP: RFC 1350, the option
extension (RFC 2347) with blksize, timeout, tsize and windowsize
(RFC 2348, 2349, 7440), tftp-hpa's extensions, block-number rollover and
netascii. Built as infrastructure for network boot: it interoperates with
iPXE, tftp-hpa, BusyBox, dnsmasq and curl, serves files, HTTP or another TFTP
server, and shows you what crossed the wire.
import tftp
tftp.Client("192.0.2.1").download("pxelinux.0", "pxelinux.0")
with tftp.Server("/srv/tftp") as server: # IPv6 + IPv4, port 69
server.serve_forever()
Features
- Complete option support —
blksizeup to 65464, RFC 7440 windows,tsizeboth ways,timeout, tftp-hpa'sutimeout,rollover,blksize2,cookie, and Windows bootmgr'smstfwindow. Unlimited file size. A registry for your own options, and profiles (pxe,hpa,legacy,strict) for quirky peers. - Fast — windowed transfers move hundreds of MiB/s over loopback in pure Python (see benchmarks); the hot path reads into preallocated buffers and keeps a sent window in memory instead of re-reading.
- Correct under loss — Sorcerer's Apprentice fix, exponential backoff, gap detection inside a window, repeated OACKs tolerated, ERROR 5 for stray packets without disturbing the transfer, dallying on the last ACK. The engine is tested over a simulated lossy link and fuzzed.
- IPv4 and IPv6 — dual-stack listening by default; v4 clients are served from plain v4 sockets.
- Replies from the request's address — on a multi-homed host or a VIP, each transfer answers from the address the request was sent to (pktinfo, via netimps); tested on Linux and Windows.
- Safe file serving — no path escapes
root(.., symlinks, Windows device names), uploads are atomic (a failed upload leaves nothing), quotas and disk-space checks fromtsize. - Pluggable backends — a handler is two methods, so boot menus or images can be generated per client; built in: a directory, memory, an HTTP(S) gateway, and a terminating proxy to another TFTP server. Slow backends apply backpressure instead of blocking other transfers.
- Transparent relay — forwards requests byte for byte to upstream servers (routed by subnet, filename or interface), so unknown extensions still work.
- Capture and debugging — trace every datagram from the client, server or
relay; write Wireshark-readable pcaps; read pcap/pcapng files or a live
tcpdumppipe, reconstruct each transfer and extract its file. - Directory listings — an opt-in extension between pytftp peers:
pytftp ls,Client.listdir(), anditerdir/walk/globon paths; other servers simply ignore it. - Deployment — transfer ports pinned to a range for firewalls; per-client
roots, case-insensitive names and filename remapping in
pytftp serve. - asyncio —
AsyncClientandAsyncServerwith async handlers and streams. - pathlib —
client.path("boot/x").read_bytes(), andtftp://URLs in pathlib-next, soUriPath("tftp://h/x").copy("s3://bucket/x")just works (optional extra). - Bounded — limits on requests, sessions per client, window memory and transfer time; counters for metrics.
- CLI —
pytftp get|put|serve|relay|capturewith--json,--traceand--pcap(optional extra).
Installation
pip install tftp # library
pip install "tftp[cli]" # plus the pytftp command
| Extra | Adds | Needed for |
|---|---|---|
cli |
duho |
the pytftp command and python -m tftp |
path |
pathlib-next[uri] |
TftpPath, and tftp:// URLs in pathlib_next.uri.UriPath |
Requires Python 3.9+. The one required dependency, netimps, has no
dependencies of its own.
Quick start
Client:
import tftp
client = tftp.Client("boot.example.net", blksize=1428, windowsize=16)
result = client.download("images/vmlinuz", "vmlinuz")
print(result.bytes, result.duration, result.negotiated)
data = client.get("pxelinux.cfg/default") # into memory
client.upload("logs/boot.txt", b"ok\n") # needs a writable server
Server with uploads, a bounded window and a completion hook:
import tftp
server = tftp.Server(
"/srv/tftp",
"::",
69,
writable=True,
options=tftp.ServerOptions(max_blksize=8192, max_windowsize=32),
on_complete=lambda r: print(r.operation, r.filename, r.peer[0], r.error or "ok"),
)
server.serve_forever()
Generated content:
import io
import tftp
class Menu(tftp.FileSystemHandler):
def open_read(self, context):
if context.filename == "menu.cfg":
return io.BytesIO(b"client %s\n" % context.peer[0].encode())
return super().open_read(context)
tftp.Server(Menu("/srv/tftp")).serve_forever()
Command line:
pytftp get 192.0.2.1 pxelinux.0 -b 1428 -w 16
pytftp put 192.0.2.1 firmware.bin
pytftp serve /srv/tftp --port 6969 --write --json
Relay, capture and asyncio:
import tftp
from tftp.relay import Relay, RouteTable, by_subnet
from tftp.capture import PcapWriter, analyze
# Forward requests to per-subnet boot servers, recording everything.
with PcapWriter("relay.pcap") as pcap:
route = RouteTable([by_subnet({"10.1.0.0/16": "10.1.0.5"})], default="10.0.0.20")
Relay(route, trace=pcap).serve_forever()
# What happened in a capture (yours, or tcpdump's)?
for transfer in analyze("boot.pcapng").transfers:
print(transfer, transfer.missing_blocks)
# asyncio
async def fetch():
async for chunk in tftp.aio.AsyncClient("192.0.2.1").stream("vmlinuz"):
...
pytftp serve --http https://images.example.com/pxe/ --compat pxe
pytftp relay 10.0.0.20 --route-prefix windows/=wds.lan --trace
tcpdump -i eth0 -U -w - udp | pytftp capture - --filter "op=RRQ,ERROR"
API overview
| Module | Purpose |
|---|---|
tftp.client |
Client, download, upload |
tftp.server |
Server, ServerLimits, FileSystemHandler, AtomicWriter, RequestContext |
tftp.backends |
MemoryHandler, HttpHandler, UpstreamHandler (proxy), Pipe |
tftp.relay |
Relay and routing helpers |
tftp.aio |
AsyncClient, AsyncServer |
tftp.capture |
trace events, PcapWriter, pcap/pcapng reading, analyze, filters |
tftp.options |
ServerOptions, option registry, profiles, Negotiated |
tftp.packet |
Opcode, ErrorCode, packet types, encode_*/decode |
tftp.transfer |
I/O-free Sender/Receiver engine |
tftp.uri |
tftp:// URLs |
tftp.path |
TftpPath, TftpUriPath (path extra) |
tftp.netascii |
streaming netascii translation |
tftp.errors |
TftpError and the typed RemoteError subclasses |
tftp.cli |
the pytftp command (cli extra) |
Everything is also importable from tftp directly. The complete reference,
with every signature and gotcha, is src/tftp/AGENTS.md,
which ships inside the package.
Development
py -3.14 -m venv .venv/3.14-nt-arm64 # or python3.14 -m venv .venv/3.14-posix-x86_64
.venv/3.14-nt-arm64/Scripts/pip install -e ".[dev]"
.venv/3.14-nt-arm64/Scripts/python -m pytest -q
.venv/3.14-nt-arm64/Scripts/python -m black --check src tests benchmarks examples
python benchmarks/run.py
pytest -m "not slow" skips the rollover tests; tests marked interop run
against curl, tftp-hpa, BusyBox and dnsmasq where installed (their servers need
passwordless sudo). sudo python tests/firmware_boot.py ipxe network-boots
iPXE in QEMU against the server (its uefi scenario does not pass yet). See
AGENTS.md for the layout and conventions.
Releasing
This project follows Semantic Versioning and keeps a
CHANGELOG.md. Pushing a tag matching v* triggers the release
workflow: test gate → build (checking the tag names the version built) → a strict
docs build as a gate → GitHub release → publish. The release workflow never deploys
the docs site itself: for a final release its last job dispatches the docs workflow
at the tag, which owns every Pages deploy.
License
MIT — see LICENSE.
Metadata
Release files for tftp 0.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tftp-0.0.0.tar.gz | 176.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tftp-0.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 338.5 kB
Release files / tftp-0.0.0.tar.gz
| Download URL | tftp-0.0.0.tar.gz |
|---|---|
| Size | 176.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e85bb0ae569448e8253eefe5c526c0204f074947a587dfc125c4bafe42580145
|
|
BLAKE2b-256 checksum How to use checksums |
a4097ef1ebe39db45d722b84ce42223198bb67541749fe8aaba6d6116f7bde9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.
Transparency logRelease files / tftp-0.0.0-py3-none-any.whl
| Download URL | tftp-0.0.0-py3-none-any.whl |
|---|---|
| Size | 161.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dfa72f8f9e268c6ef990376ede67aa6afa1b6f14920cee6a617ed774cca5797b
|
|
BLAKE2b-256 checksum How to use checksums |
09135487008104473047a9eaa689aee29703bb9dc39e80de6b62042a734a6720
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 3, 2026.
Transparency log