tftp 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. tftp-0.0.0/.gitignore +31 -0
  2. tftp-0.0.0/CHANGELOG.md +101 -0
  3. tftp-0.0.0/LICENSE +21 -0
  4. tftp-0.0.0/PKG-INFO +260 -0
  5. tftp-0.0.0/README.md +218 -0
  6. tftp-0.0.0/RELEASENOTES.md +70 -0
  7. tftp-0.0.0/docs/api/aio.md +7 -0
  8. tftp-0.0.0/docs/api/backends.md +9 -0
  9. tftp-0.0.0/docs/api/capture.md +13 -0
  10. tftp-0.0.0/docs/api/client.md +3 -0
  11. tftp-0.0.0/docs/api/errors.md +5 -0
  12. tftp-0.0.0/docs/api/netascii.md +3 -0
  13. tftp-0.0.0/docs/api/options.md +13 -0
  14. tftp-0.0.0/docs/api/packet.md +3 -0
  15. tftp-0.0.0/docs/api/path.md +5 -0
  16. tftp-0.0.0/docs/api/relay.md +7 -0
  17. tftp-0.0.0/docs/api/server.md +7 -0
  18. tftp-0.0.0/docs/api/transfer.md +3 -0
  19. tftp-0.0.0/docs/api/uri.md +3 -0
  20. tftp-0.0.0/docs/async.md +48 -0
  21. tftp-0.0.0/docs/backends.md +52 -0
  22. tftp-0.0.0/docs/capture.md +64 -0
  23. tftp-0.0.0/docs/changelog.md +1 -0
  24. tftp-0.0.0/docs/index.md +55 -0
  25. tftp-0.0.0/docs/paths.md +36 -0
  26. tftp-0.0.0/docs/protocol.md +96 -0
  27. tftp-0.0.0/docs/relay.md +61 -0
  28. tftp-0.0.0/docs/serving.md +124 -0
  29. tftp-0.0.0/examples/dynamic_boot_menu.py +35 -0
  30. tftp-0.0.0/examples/fetch.py +21 -0
  31. tftp-0.0.0/examples/serve_directory.py +20 -0
  32. tftp-0.0.0/mkdocs.yml +71 -0
  33. tftp-0.0.0/pyproject.toml +90 -0
  34. tftp-0.0.0/src/tftp/AGENTS.md +761 -0
  35. tftp-0.0.0/src/tftp/__init__.py +180 -0
  36. tftp-0.0.0/src/tftp/__main__.py +6 -0
  37. tftp-0.0.0/src/tftp/_sockets.py +36 -0
  38. tftp-0.0.0/src/tftp/aio/__init__.py +22 -0
  39. tftp-0.0.0/src/tftp/aio/bridge.py +214 -0
  40. tftp-0.0.0/src/tftp/aio/client.py +426 -0
  41. tftp-0.0.0/src/tftp/aio/server.py +314 -0
  42. tftp-0.0.0/src/tftp/backends/__init__.py +19 -0
  43. tftp-0.0.0/src/tftp/backends/http.py +142 -0
  44. tftp-0.0.0/src/tftp/backends/memory.py +93 -0
  45. tftp-0.0.0/src/tftp/backends/pipe.py +192 -0
  46. tftp-0.0.0/src/tftp/backends/proxy.py +162 -0
  47. tftp-0.0.0/src/tftp/capture/__init__.py +74 -0
  48. tftp-0.0.0/src/tftp/capture/events.py +161 -0
  49. tftp-0.0.0/src/tftp/capture/filters.py +149 -0
  50. tftp-0.0.0/src/tftp/capture/flows.py +290 -0
  51. tftp-0.0.0/src/tftp/capture/frames.py +189 -0
  52. tftp-0.0.0/src/tftp/capture/live.py +54 -0
  53. tftp-0.0.0/src/tftp/capture/pcap.py +225 -0
  54. tftp-0.0.0/src/tftp/cli/__init__.py +56 -0
  55. tftp-0.0.0/src/tftp/cli/capture.py +130 -0
  56. tftp-0.0.0/src/tftp/cli/common.py +240 -0
  57. tftp-0.0.0/src/tftp/cli/handlers.py +147 -0
  58. tftp-0.0.0/src/tftp/cli/relay.py +117 -0
  59. tftp-0.0.0/src/tftp/cli/serve.py +237 -0
  60. tftp-0.0.0/src/tftp/cli/transfer.py +174 -0
  61. tftp-0.0.0/src/tftp/client.py +769 -0
  62. tftp-0.0.0/src/tftp/errors.py +205 -0
  63. tftp-0.0.0/src/tftp/listing.py +123 -0
  64. tftp-0.0.0/src/tftp/netascii.py +149 -0
  65. tftp-0.0.0/src/tftp/options/__init__.py +89 -0
  66. tftp-0.0.0/src/tftp/options/base.py +175 -0
  67. tftp-0.0.0/src/tftp/options/builtin.py +318 -0
  68. tftp-0.0.0/src/tftp/options/negotiate.py +134 -0
  69. tftp-0.0.0/src/tftp/options/policy.py +96 -0
  70. tftp-0.0.0/src/tftp/options/profiles.py +74 -0
  71. tftp-0.0.0/src/tftp/options/registry.py +64 -0
  72. tftp-0.0.0/src/tftp/packet/__init__.py +40 -0
  73. tftp-0.0.0/src/tftp/packet/codec.py +194 -0
  74. tftp-0.0.0/src/tftp/packet/enums.py +32 -0
  75. tftp-0.0.0/src/tftp/path/__init__.py +34 -0
  76. tftp-0.0.0/src/tftp/path/_stream.py +195 -0
  77. tftp-0.0.0/src/tftp/path/local.py +221 -0
  78. tftp-0.0.0/src/tftp/path/uri.py +94 -0
  79. tftp-0.0.0/src/tftp/py.typed +0 -0
  80. tftp-0.0.0/src/tftp/relay/__init__.py +24 -0
  81. tftp-0.0.0/src/tftp/relay/core.py +429 -0
  82. tftp-0.0.0/src/tftp/relay/routing.py +148 -0
  83. tftp-0.0.0/src/tftp/relay/session.py +165 -0
  84. tftp-0.0.0/src/tftp/result.py +88 -0
  85. tftp-0.0.0/src/tftp/server/__init__.py +20 -0
  86. tftp-0.0.0/src/tftp/server/base.py +321 -0
  87. tftp-0.0.0/src/tftp/server/core.py +430 -0
  88. tftp-0.0.0/src/tftp/server/handler.py +290 -0
  89. tftp-0.0.0/src/tftp/server/listener.py +198 -0
  90. tftp-0.0.0/src/tftp/server/policy.py +63 -0
  91. tftp-0.0.0/src/tftp/server/session.py +320 -0
  92. tftp-0.0.0/src/tftp/server/stats.py +63 -0
  93. tftp-0.0.0/src/tftp/transfer/__init__.py +31 -0
  94. tftp-0.0.0/src/tftp/transfer/base.py +241 -0
  95. tftp-0.0.0/src/tftp/transfer/receiver.py +212 -0
  96. tftp-0.0.0/src/tftp/transfer/sender.py +175 -0
  97. tftp-0.0.0/src/tftp/uri.py +95 -0
  98. tftp-0.0.0/tests/conftest.py +70 -0
  99. tftp-0.0.0/tests/firmware_boot.py +228 -0
  100. tftp-0.0.0/tests/smoke_installed.py +45 -0
  101. tftp-0.0.0/tests/test_addresses.py +162 -0
  102. tftp-0.0.0/tests/test_aio.py +308 -0
  103. tftp-0.0.0/tests/test_backends.py +216 -0
  104. tftp-0.0.0/tests/test_capture.py +293 -0
  105. tftp-0.0.0/tests/test_cli.py +273 -0
  106. tftp-0.0.0/tests/test_client_server.py +358 -0
  107. tftp-0.0.0/tests/test_deploy.py +162 -0
  108. tftp-0.0.0/tests/test_engine.py +286 -0
  109. tftp-0.0.0/tests/test_interop.py +57 -0
  110. tftp-0.0.0/tests/test_interop_peers.py +228 -0
  111. tftp-0.0.0/tests/test_listing.py +139 -0
  112. tftp-0.0.0/tests/test_netascii.py +64 -0
  113. tftp-0.0.0/tests/test_option_registry.py +198 -0
  114. tftp-0.0.0/tests/test_options.py +110 -0
  115. tftp-0.0.0/tests/test_packet.py +91 -0
  116. tftp-0.0.0/tests/test_path.py +120 -0
  117. tftp-0.0.0/tests/test_protocol.py +278 -0
  118. tftp-0.0.0/tests/test_relay.py +261 -0
  119. tftp-0.0.0/tests/test_robustness.py +494 -0
  120. tftp-0.0.0/tests/test_uri.py +65 -0
tftp-0.0.0/.gitignore ADDED
@@ -0,0 +1,31 @@
1
+ # Dotfiles are local by default; re-include the ones that are shared.
2
+ /.*
3
+ !/.gitignore
4
+ !/.gitattributes
5
+ !/.github/
6
+
7
+ # Agent configs / private notes -- never tracked in this repo.
8
+ # `.agents` is deliberately slashless: it is usually a symlink, which a
9
+ # directory-only `.agents/` pattern would not match.
10
+ .agents
11
+ *.local.*
12
+ CLAUDE*
13
+ .claude
14
+
15
+ # Python
16
+ __pycache__/
17
+ *.py[cod]
18
+ .pytest_cache/
19
+ .hypothesis/
20
+ .mypy_cache/
21
+ .ruff_cache/
22
+ .coverage*
23
+ htmlcov/
24
+ *.egg-info/
25
+ .venv/
26
+ .pyvenv/
27
+
28
+ # Build / docs output
29
+ build/
30
+ dist/
31
+ site/
@@ -0,0 +1,101 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.0.0] - 2026-10-03
11
+
12
+ ### Added
13
+
14
+ - `Client`: download and upload over IPv4 and IPv6, to and from paths, binary
15
+ files or memory, with `blksize`, `windowsize`, `tsize` and `timeout`
16
+ negotiation (and the `utimeout`, `rollover`, `blksize2` and `cookie`
17
+ extensions on request), progress and `on_negotiated` callbacks, exponential
18
+ backoff of retransmissions, an optional time limit, and a one-time retry
19
+ without options when a server refuses them (ERROR 8). `blksize="mtu"` sizes
20
+ blocks to the route's interface MTU; hosts may carry a port
21
+ (`"[::1]:6969"`).
22
+ - `Server`: one event-loop thread serving any number of transfers, each on its
23
+ own socket bound to the address the request was sent to (pktinfo); dual-stack
24
+ `::` listening by default; a negotiation policy (`ServerOptions`: maximum
25
+ block and window size, window memory bound, allowed and refused options,
26
+ fitting `blksize` to the arrival interface's MTU); resource limits
27
+ (`ServerLimits`: request size, filename length, option count and length,
28
+ sessions per client, transfer duration); ignoring requests sent to broadcast
29
+ or multicast addresses; blocking handlers opened in worker threads;
30
+ dallying on the last ACK of an upload; `on_complete` for every transfer;
31
+ counters for metrics (`stats`, `stats_snapshot()`).
32
+ - `FileSystemHandler`: serves a directory with containment against `..`,
33
+ symlinks and Windows device names; backslash separators; atomic uploads;
34
+ `create`/`overwrite` policy; `max_upload` and free-space checks from `tsize`.
35
+ - Handler protocol (`open_read`/`open_write`) for generated content; sources
36
+ and sinks may report "not ready yet" (`WouldBlock`) and wake the transfer
37
+ from another thread, so slow backends apply backpressure instead of
38
+ blocking the server.
39
+ - Backends: `MemoryHandler`; `HttpHandler`, a TFTP-to-HTTP(S) gateway (GET and
40
+ PUT, HTTP status codes mapped to TFTP errors, `Content-Length` as `tsize`);
41
+ `UpstreamHandler`, a terminating proxy to another TFTP server whose two sides
42
+ negotiate independently; `Pipe` for writing such handlers.
43
+ - `Relay`: a transparent relay forwarding requests byte for byte to upstream
44
+ servers and datagrams unchanged between the two transfer IDs, with routing
45
+ by client subnet, filename prefix or arrival interface, idle and lifetime
46
+ limits, per-transfer summaries and counters.
47
+ - `tftp.aio`: `AsyncClient` (including `stream()`, an async generator with
48
+ backpressure) and `AsyncServer` (async handlers and streams, pktinfo on every
49
+ event loop including Windows' Proactor loop).
50
+ - Option negotiation through a registry of option handlers, so applications can
51
+ add their own; compatibility profiles `STRICT`, `DEFAULT`, `PXE`, `HPA` and
52
+ `LEGACY`.
53
+ - Capture and debugging: `trace=` hooks on the client, server and relay
54
+ reporting every datagram as a `PacketEvent`; `PcapWriter` (Wireshark-readable
55
+ pcap); pcap/pcapng reading from files or live pipes with IP fragment
56
+ reassembly; `FlowTracker`/`analyze()` reconstructing transfers and their
57
+ files from a capture; a filter language; live capture on Linux.
58
+ - `tftp://` URLs (RFC 3617): `parse_url`, `format_url`, `download_url`,
59
+ `upload_url`.
60
+ - `Client.size()`: a file's size without transferring it (a `tsize` probe the
61
+ server counts as declined, not failed).
62
+ - `path` extra: `TftpPath`, a pathlib-next `Path` bound to a client
63
+ (`client.path(...)`), and `TftpUriPath`, the `tftp://` scheme for
64
+ pathlib-next's `UriPath`; whole-file streaming reads and writes, `stat()`
65
+ without a transfer, `copy()`/`move()` across schemes.
66
+ - Typed errors: `RemoteError` raised as `FileNotFound`, `AccessViolation`,
67
+ `DiskFull`, `IllegalOperation`, `UnknownTransferId`, `FileAlreadyExists`,
68
+ `NoSuchUser` or `OptionNegotiationError`; `TransferAborted`.
69
+ - Transfer engine (`Sender`/`Receiver`) with RFC 7440 windowing, the
70
+ Sorcerer's Apprentice fix, unknown-TID handling, tolerance of repeated
71
+ OACKs and unlimited file size via block-number rollover, usable without
72
+ sockets.
73
+ - Streaming netascii in both directions; packet encode/decode for every RFC
74
+ 1350 and RFC 2347 packet, keeping a request's raw bytes for forwarding.
75
+ - `pytftp` command (`cli` extra): `get` and `put` (also by URL), `serve` (a
76
+ directory, `--http` gateway or `--upstream` proxy), `relay`, `capture`;
77
+ `--compat` profiles, `--trace` and `--pcap` on every command that moves
78
+ packets, `--json` output.
79
+ - `port_range=` on `Server`, `AsyncServer` and `Relay` (`PortRange`):
80
+ transfer sockets take their ports from a range a firewall can allow;
81
+ `--port-range` on `pytftp serve` and `relay`.
82
+ - `pytftp serve --per-client` (serve `ROOT/<client address>/` when it exists),
83
+ `--ignore-case` and `--remap REGEX=REPLACEMENT`.
84
+ - `mstfwindow`, Windows bootmgr's variable-window option, as an extension a
85
+ server may allow (fixed window of 4).
86
+ - pytftp's `x-list` and `x-mtime` extensions (`LISTING_OPTIONS`): directory
87
+ listings (`tftp.listing`) and modification times. `Client.stat()`,
88
+ `Client.listdir()` (async too), `pytftp ls`, `pytftp serve --listing`, and
89
+ `iterdir`/`walk`/`glob`/`is_dir`/`st_mtime` on `TftpPath` and `TftpUriPath`.
90
+ - Hosts, networks and interfaces accepted as objects (`ipaddress` types,
91
+ `netimps.Host`, `netimps.Interface`) wherever an address is taken, typed
92
+ with netimps' aliases.
93
+ - `interface=` on `Server`, `AsyncServer` and `Relay` (and `--interface` on
94
+ `pytftp serve`/`relay`): listen on one network adapter, by name,
95
+ `netimps.Interface`, MAC or address.
96
+ - `RequestContext.interface`: the arrival interface as a `netimps.Interface`.
97
+ - Loopback throughput benchmark (`benchmarks/run.py`).
98
+ - Licensed under MIT.
99
+
100
+ [Unreleased]: https://github.com/jose-pr/pytftp/compare/v0.0.0...HEAD
101
+ [0.0.0]: https://github.com/jose-pr/pytftp/releases/tag/v0.0.0
tftp-0.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jose-pr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
tftp-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,260 @@
1
+ Metadata-Version: 2.5
2
+ Name: tftp
3
+ Version: 0.0.0
4
+ Summary: Pure-Python TFTP client and server for IPv4 and IPv6: RFC 1350, 2347, 2348, 2349 and 7440
5
+ Project-URL: Homepage, https://github.com/jose-pr/pytftp/
6
+ Project-URL: Documentation, https://jose-pr.github.io/pytftp/
7
+ Project-URL: Issues, https://github.com/jose-pr/pytftp/issues
8
+ Author-email: jose-pr <jose-pr@coqui.dev>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Internet
22
+ Classifier: Topic :: System :: Boot
23
+ Classifier: Topic :: System :: Networking
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.9
26
+ Requires-Dist: netimps<0.4,>=0.3.4
27
+ Provides-Extra: cli
28
+ Requires-Dist: duho<0.7,>=0.6.0; extra == 'cli'
29
+ Provides-Extra: dev
30
+ Requires-Dist: black; extra == 'dev'
31
+ Requires-Dist: build; extra == 'dev'
32
+ Requires-Dist: duho<0.7,>=0.6.0; extra == 'dev'
33
+ Requires-Dist: pathlib-next[uri]<0.10,>=0.9.0; extra == 'dev'
34
+ Requires-Dist: pytest; extra == 'dev'
35
+ Provides-Extra: docs
36
+ Requires-Dist: mkdocs; extra == 'docs'
37
+ Requires-Dist: mkdocs-material; extra == 'docs'
38
+ Requires-Dist: mkdocstrings[python]; extra == 'docs'
39
+ Provides-Extra: path
40
+ Requires-Dist: pathlib-next[uri]<0.10,>=0.9.0; extra == 'path'
41
+ Description-Content-Type: text/markdown
42
+
43
+ # tftp
44
+
45
+ [![Version](https://img.shields.io/pypi/v/tftp.svg)](https://pypi.org/project/tftp/)
46
+ [![Python versions](https://img.shields.io/pypi/pyversions/tftp.svg)](https://pypi.org/project/tftp/)
47
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jose-pr/pytftp/blob/main/LICENSE)
48
+ [![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://jose-pr.github.io/pytftp/)
49
+ [![CI](https://img.shields.io/github/actions/workflow/status/jose-pr/pytftp/test.yml)](https://github.com/jose-pr/pytftp/actions/workflows/test.yml)
50
+
51
+ A **pure-Python TFTP client, server, relay and capture decoder for IPv4 and
52
+ IPv6** that implements the whole of modern TFTP: RFC 1350, the option
53
+ extension (RFC 2347) with `blksize`, `timeout`, `tsize` and `windowsize`
54
+ (RFC 2348, 2349, 7440), tftp-hpa's extensions, block-number rollover and
55
+ netascii. Built as infrastructure for network boot: it interoperates with
56
+ iPXE, tftp-hpa, BusyBox, dnsmasq and curl, serves files, HTTP or another TFTP
57
+ server, and shows you what crossed the wire.
58
+
59
+ ```python
60
+ import tftp
61
+
62
+ tftp.Client("192.0.2.1").download("pxelinux.0", "pxelinux.0")
63
+
64
+ with tftp.Server("/srv/tftp") as server: # IPv6 + IPv4, port 69
65
+ server.serve_forever()
66
+ ```
67
+
68
+ ## Features
69
+
70
+ - **Complete option support** — `blksize` up to 65464, RFC 7440 windows,
71
+ `tsize` both ways, `timeout`, tftp-hpa's `utimeout`, `rollover`,
72
+ `blksize2`, `cookie`, and Windows bootmgr's `mstfwindow`. Unlimited file size. A registry for your own options,
73
+ and profiles (`pxe`, `hpa`, `legacy`, `strict`) for quirky peers.
74
+ - **Fast** — windowed transfers move hundreds of MiB/s over loopback in pure
75
+ Python (see [benchmarks](https://github.com/jose-pr/pytftp/blob/main/benchmarks/README.md)); the hot path reads into
76
+ preallocated buffers and keeps a sent window in memory instead of re-reading.
77
+ - **Correct under loss** — Sorcerer's Apprentice fix, exponential backoff,
78
+ gap detection inside a window, repeated OACKs tolerated, ERROR 5 for stray
79
+ packets without disturbing the transfer, dallying on the last ACK. The
80
+ engine is tested over a simulated lossy link and fuzzed.
81
+ - **IPv4 and IPv6** — dual-stack listening by default; v4 clients are served
82
+ from plain v4 sockets.
83
+ - **Replies from the request's address** — on a multi-homed host or a VIP,
84
+ each transfer answers from the address the request was sent to (pktinfo,
85
+ via [netimps](https://github.com/jose-pr/netimps)); tested on Linux and
86
+ Windows.
87
+ - **Safe file serving** — no path escapes `root` (`..`, symlinks, Windows
88
+ device names), uploads are atomic (a failed upload leaves nothing), quotas
89
+ and disk-space checks from `tsize`.
90
+ - **Pluggable backends** — a handler is two methods, so boot menus or images
91
+ can be generated per client; built in: a directory, memory, an HTTP(S)
92
+ gateway, and a terminating proxy to another TFTP server. Slow backends apply
93
+ backpressure instead of blocking other transfers.
94
+ - **Transparent relay** — forwards requests byte for byte to upstream servers
95
+ (routed by subnet, filename or interface), so unknown extensions still work.
96
+ - **Capture and debugging** — trace every datagram from the client, server or
97
+ relay; write Wireshark-readable pcaps; read pcap/pcapng files or a live
98
+ `tcpdump` pipe, reconstruct each transfer and extract its file.
99
+ - **Directory listings** — an opt-in extension between pytftp peers: `pytftp ls`,
100
+ `Client.listdir()`, and `iterdir`/`walk`/`glob` on paths; other servers
101
+ simply ignore it.
102
+ - **Deployment** — transfer ports pinned to a range for firewalls; per-client
103
+ roots, case-insensitive names and filename remapping in `pytftp serve`.
104
+ - **asyncio** — `AsyncClient` and `AsyncServer` with async handlers and streams.
105
+ - **pathlib** — `client.path("boot/x").read_bytes()`, and `tftp://` URLs in
106
+ [pathlib-next](https://github.com/jose-pr/pathlib-next), so
107
+ `UriPath("tftp://h/x").copy("s3://bucket/x")` just works (optional extra).
108
+ - **Bounded** — limits on requests, sessions per client, window memory and
109
+ transfer time; counters for metrics.
110
+ - **CLI** — `pytftp get|put|serve|relay|capture` with `--json`, `--trace` and
111
+ `--pcap` (optional extra).
112
+
113
+ ## Installation
114
+
115
+ ```bash
116
+ pip install tftp # library
117
+ pip install "tftp[cli]" # plus the pytftp command
118
+ ```
119
+
120
+ | Extra | Adds | Needed for |
121
+ | --- | --- | --- |
122
+ | `cli` | `duho` | the `pytftp` command and `python -m tftp` |
123
+ | `path` | `pathlib-next[uri]` | `TftpPath`, and `tftp://` URLs in `pathlib_next.uri.UriPath` |
124
+
125
+ Requires Python 3.9+. The one required dependency, `netimps`, has no
126
+ dependencies of its own.
127
+
128
+ ## Quick start
129
+
130
+ Client:
131
+
132
+ ```python
133
+ import tftp
134
+
135
+ client = tftp.Client("boot.example.net", blksize=1428, windowsize=16)
136
+ result = client.download("images/vmlinuz", "vmlinuz")
137
+ print(result.bytes, result.duration, result.negotiated)
138
+
139
+ data = client.get("pxelinux.cfg/default") # into memory
140
+ client.upload("logs/boot.txt", b"ok\n") # needs a writable server
141
+ ```
142
+
143
+ Server with uploads, a bounded window and a completion hook:
144
+
145
+ ```python
146
+ import tftp
147
+
148
+ server = tftp.Server(
149
+ "/srv/tftp",
150
+ "::",
151
+ 69,
152
+ writable=True,
153
+ options=tftp.ServerOptions(max_blksize=8192, max_windowsize=32),
154
+ on_complete=lambda r: print(r.operation, r.filename, r.peer[0], r.error or "ok"),
155
+ )
156
+ server.serve_forever()
157
+ ```
158
+
159
+ Generated content:
160
+
161
+ ```python
162
+ import io
163
+ import tftp
164
+
165
+ class Menu(tftp.FileSystemHandler):
166
+ def open_read(self, context):
167
+ if context.filename == "menu.cfg":
168
+ return io.BytesIO(b"client %s\n" % context.peer[0].encode())
169
+ return super().open_read(context)
170
+
171
+ tftp.Server(Menu("/srv/tftp")).serve_forever()
172
+ ```
173
+
174
+ Command line:
175
+
176
+ ```bash
177
+ pytftp get 192.0.2.1 pxelinux.0 -b 1428 -w 16
178
+ pytftp put 192.0.2.1 firmware.bin
179
+ pytftp serve /srv/tftp --port 6969 --write --json
180
+ ```
181
+
182
+ Relay, capture and asyncio:
183
+
184
+ ```python
185
+ import tftp
186
+ from tftp.relay import Relay, RouteTable, by_subnet
187
+ from tftp.capture import PcapWriter, analyze
188
+
189
+ # Forward requests to per-subnet boot servers, recording everything.
190
+ with PcapWriter("relay.pcap") as pcap:
191
+ route = RouteTable([by_subnet({"10.1.0.0/16": "10.1.0.5"})], default="10.0.0.20")
192
+ Relay(route, trace=pcap).serve_forever()
193
+
194
+ # What happened in a capture (yours, or tcpdump's)?
195
+ for transfer in analyze("boot.pcapng").transfers:
196
+ print(transfer, transfer.missing_blocks)
197
+
198
+ # asyncio
199
+ async def fetch():
200
+ async for chunk in tftp.aio.AsyncClient("192.0.2.1").stream("vmlinuz"):
201
+ ...
202
+ ```
203
+
204
+ ```bash
205
+ pytftp serve --http https://images.example.com/pxe/ --compat pxe
206
+ pytftp relay 10.0.0.20 --route-prefix windows/=wds.lan --trace
207
+ tcpdump -i eth0 -U -w - udp | pytftp capture - --filter "op=RRQ,ERROR"
208
+ ```
209
+
210
+ ## API overview
211
+
212
+ | Module | Purpose |
213
+ | --- | --- |
214
+ | `tftp.client` | `Client`, `download`, `upload` |
215
+ | `tftp.server` | `Server`, `ServerLimits`, `FileSystemHandler`, `AtomicWriter`, `RequestContext` |
216
+ | `tftp.backends` | `MemoryHandler`, `HttpHandler`, `UpstreamHandler` (proxy), `Pipe` |
217
+ | `tftp.relay` | `Relay` and routing helpers |
218
+ | `tftp.aio` | `AsyncClient`, `AsyncServer` |
219
+ | `tftp.capture` | trace events, `PcapWriter`, pcap/pcapng reading, `analyze`, filters |
220
+ | `tftp.options` | `ServerOptions`, option registry, profiles, `Negotiated` |
221
+ | `tftp.packet` | `Opcode`, `ErrorCode`, packet types, `encode_*`/`decode` |
222
+ | `tftp.transfer` | I/O-free `Sender`/`Receiver` engine |
223
+ | `tftp.uri` | `tftp://` URLs |
224
+ | `tftp.path` | `TftpPath`, `TftpUriPath` (`path` extra) |
225
+ | `tftp.netascii` | streaming netascii translation |
226
+ | `tftp.errors` | `TftpError` and the typed `RemoteError` subclasses |
227
+ | `tftp.cli` | the `pytftp` command (`cli` extra) |
228
+
229
+ Everything is also importable from `tftp` directly. The complete reference,
230
+ with every signature and gotcha, is [`src/tftp/AGENTS.md`](https://github.com/jose-pr/pytftp/blob/main/src/tftp/AGENTS.md),
231
+ which ships inside the package.
232
+
233
+ ## Development
234
+
235
+ ```bash
236
+ py -3.14 -m venv .venv/3.14-nt-arm64 # or python3.14 -m venv .venv/3.14-posix-x86_64
237
+ .venv/3.14-nt-arm64/Scripts/pip install -e ".[dev]"
238
+ .venv/3.14-nt-arm64/Scripts/python -m pytest -q
239
+ .venv/3.14-nt-arm64/Scripts/python -m black --check src tests benchmarks examples
240
+ python benchmarks/run.py
241
+ ```
242
+
243
+ `pytest -m "not slow"` skips the rollover tests; tests marked `interop` run
244
+ against curl, tftp-hpa, BusyBox and dnsmasq where installed (their servers need
245
+ passwordless `sudo`). `sudo python tests/firmware_boot.py ipxe` network-boots
246
+ iPXE in QEMU against the server (its `uefi` scenario does not pass yet). See
247
+ [AGENTS.md](https://github.com/jose-pr/pytftp/blob/main/AGENTS.md) for the layout and conventions.
248
+
249
+ ### Releasing
250
+
251
+ This project follows [Semantic Versioning](https://semver.org/) and keeps a
252
+ [`CHANGELOG.md`](https://github.com/jose-pr/pytftp/blob/main/CHANGELOG.md). Pushing a tag matching `v*` triggers the release
253
+ workflow: test gate → build (checking the tag names the version built) → a strict
254
+ docs build as a gate → GitHub release → publish. The release workflow never deploys
255
+ the docs site itself: for a final release its last job dispatches the docs workflow
256
+ at the tag, which owns every Pages deploy.
257
+
258
+ ## License
259
+
260
+ MIT — see [LICENSE](https://github.com/jose-pr/pytftp/blob/main/LICENSE).
tftp-0.0.0/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # tftp
2
+
3
+ [![Version](https://img.shields.io/pypi/v/tftp.svg)](https://pypi.org/project/tftp/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/tftp.svg)](https://pypi.org/project/tftp/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jose-pr/pytftp/blob/main/LICENSE)
6
+ [![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://jose-pr.github.io/pytftp/)
7
+ [![CI](https://img.shields.io/github/actions/workflow/status/jose-pr/pytftp/test.yml)](https://github.com/jose-pr/pytftp/actions/workflows/test.yml)
8
+
9
+ A **pure-Python TFTP client, server, relay and capture decoder for IPv4 and
10
+ IPv6** that implements the whole of modern TFTP: RFC 1350, the option
11
+ extension (RFC 2347) with `blksize`, `timeout`, `tsize` and `windowsize`
12
+ (RFC 2348, 2349, 7440), tftp-hpa's extensions, block-number rollover and
13
+ netascii. Built as infrastructure for network boot: it interoperates with
14
+ iPXE, tftp-hpa, BusyBox, dnsmasq and curl, serves files, HTTP or another TFTP
15
+ server, and shows you what crossed the wire.
16
+
17
+ ```python
18
+ import tftp
19
+
20
+ tftp.Client("192.0.2.1").download("pxelinux.0", "pxelinux.0")
21
+
22
+ with tftp.Server("/srv/tftp") as server: # IPv6 + IPv4, port 69
23
+ server.serve_forever()
24
+ ```
25
+
26
+ ## Features
27
+
28
+ - **Complete option support** — `blksize` up to 65464, RFC 7440 windows,
29
+ `tsize` both ways, `timeout`, tftp-hpa's `utimeout`, `rollover`,
30
+ `blksize2`, `cookie`, and Windows bootmgr's `mstfwindow`. Unlimited file size. A registry for your own options,
31
+ and profiles (`pxe`, `hpa`, `legacy`, `strict`) for quirky peers.
32
+ - **Fast** — windowed transfers move hundreds of MiB/s over loopback in pure
33
+ Python (see [benchmarks](https://github.com/jose-pr/pytftp/blob/main/benchmarks/README.md)); the hot path reads into
34
+ preallocated buffers and keeps a sent window in memory instead of re-reading.
35
+ - **Correct under loss** — Sorcerer's Apprentice fix, exponential backoff,
36
+ gap detection inside a window, repeated OACKs tolerated, ERROR 5 for stray
37
+ packets without disturbing the transfer, dallying on the last ACK. The
38
+ engine is tested over a simulated lossy link and fuzzed.
39
+ - **IPv4 and IPv6** — dual-stack listening by default; v4 clients are served
40
+ from plain v4 sockets.
41
+ - **Replies from the request's address** — on a multi-homed host or a VIP,
42
+ each transfer answers from the address the request was sent to (pktinfo,
43
+ via [netimps](https://github.com/jose-pr/netimps)); tested on Linux and
44
+ Windows.
45
+ - **Safe file serving** — no path escapes `root` (`..`, symlinks, Windows
46
+ device names), uploads are atomic (a failed upload leaves nothing), quotas
47
+ and disk-space checks from `tsize`.
48
+ - **Pluggable backends** — a handler is two methods, so boot menus or images
49
+ can be generated per client; built in: a directory, memory, an HTTP(S)
50
+ gateway, and a terminating proxy to another TFTP server. Slow backends apply
51
+ backpressure instead of blocking other transfers.
52
+ - **Transparent relay** — forwards requests byte for byte to upstream servers
53
+ (routed by subnet, filename or interface), so unknown extensions still work.
54
+ - **Capture and debugging** — trace every datagram from the client, server or
55
+ relay; write Wireshark-readable pcaps; read pcap/pcapng files or a live
56
+ `tcpdump` pipe, reconstruct each transfer and extract its file.
57
+ - **Directory listings** — an opt-in extension between pytftp peers: `pytftp ls`,
58
+ `Client.listdir()`, and `iterdir`/`walk`/`glob` on paths; other servers
59
+ simply ignore it.
60
+ - **Deployment** — transfer ports pinned to a range for firewalls; per-client
61
+ roots, case-insensitive names and filename remapping in `pytftp serve`.
62
+ - **asyncio** — `AsyncClient` and `AsyncServer` with async handlers and streams.
63
+ - **pathlib** — `client.path("boot/x").read_bytes()`, and `tftp://` URLs in
64
+ [pathlib-next](https://github.com/jose-pr/pathlib-next), so
65
+ `UriPath("tftp://h/x").copy("s3://bucket/x")` just works (optional extra).
66
+ - **Bounded** — limits on requests, sessions per client, window memory and
67
+ transfer time; counters for metrics.
68
+ - **CLI** — `pytftp get|put|serve|relay|capture` with `--json`, `--trace` and
69
+ `--pcap` (optional extra).
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ pip install tftp # library
75
+ pip install "tftp[cli]" # plus the pytftp command
76
+ ```
77
+
78
+ | Extra | Adds | Needed for |
79
+ | --- | --- | --- |
80
+ | `cli` | `duho` | the `pytftp` command and `python -m tftp` |
81
+ | `path` | `pathlib-next[uri]` | `TftpPath`, and `tftp://` URLs in `pathlib_next.uri.UriPath` |
82
+
83
+ Requires Python 3.9+. The one required dependency, `netimps`, has no
84
+ dependencies of its own.
85
+
86
+ ## Quick start
87
+
88
+ Client:
89
+
90
+ ```python
91
+ import tftp
92
+
93
+ client = tftp.Client("boot.example.net", blksize=1428, windowsize=16)
94
+ result = client.download("images/vmlinuz", "vmlinuz")
95
+ print(result.bytes, result.duration, result.negotiated)
96
+
97
+ data = client.get("pxelinux.cfg/default") # into memory
98
+ client.upload("logs/boot.txt", b"ok\n") # needs a writable server
99
+ ```
100
+
101
+ Server with uploads, a bounded window and a completion hook:
102
+
103
+ ```python
104
+ import tftp
105
+
106
+ server = tftp.Server(
107
+ "/srv/tftp",
108
+ "::",
109
+ 69,
110
+ writable=True,
111
+ options=tftp.ServerOptions(max_blksize=8192, max_windowsize=32),
112
+ on_complete=lambda r: print(r.operation, r.filename, r.peer[0], r.error or "ok"),
113
+ )
114
+ server.serve_forever()
115
+ ```
116
+
117
+ Generated content:
118
+
119
+ ```python
120
+ import io
121
+ import tftp
122
+
123
+ class Menu(tftp.FileSystemHandler):
124
+ def open_read(self, context):
125
+ if context.filename == "menu.cfg":
126
+ return io.BytesIO(b"client %s\n" % context.peer[0].encode())
127
+ return super().open_read(context)
128
+
129
+ tftp.Server(Menu("/srv/tftp")).serve_forever()
130
+ ```
131
+
132
+ Command line:
133
+
134
+ ```bash
135
+ pytftp get 192.0.2.1 pxelinux.0 -b 1428 -w 16
136
+ pytftp put 192.0.2.1 firmware.bin
137
+ pytftp serve /srv/tftp --port 6969 --write --json
138
+ ```
139
+
140
+ Relay, capture and asyncio:
141
+
142
+ ```python
143
+ import tftp
144
+ from tftp.relay import Relay, RouteTable, by_subnet
145
+ from tftp.capture import PcapWriter, analyze
146
+
147
+ # Forward requests to per-subnet boot servers, recording everything.
148
+ with PcapWriter("relay.pcap") as pcap:
149
+ route = RouteTable([by_subnet({"10.1.0.0/16": "10.1.0.5"})], default="10.0.0.20")
150
+ Relay(route, trace=pcap).serve_forever()
151
+
152
+ # What happened in a capture (yours, or tcpdump's)?
153
+ for transfer in analyze("boot.pcapng").transfers:
154
+ print(transfer, transfer.missing_blocks)
155
+
156
+ # asyncio
157
+ async def fetch():
158
+ async for chunk in tftp.aio.AsyncClient("192.0.2.1").stream("vmlinuz"):
159
+ ...
160
+ ```
161
+
162
+ ```bash
163
+ pytftp serve --http https://images.example.com/pxe/ --compat pxe
164
+ pytftp relay 10.0.0.20 --route-prefix windows/=wds.lan --trace
165
+ tcpdump -i eth0 -U -w - udp | pytftp capture - --filter "op=RRQ,ERROR"
166
+ ```
167
+
168
+ ## API overview
169
+
170
+ | Module | Purpose |
171
+ | --- | --- |
172
+ | `tftp.client` | `Client`, `download`, `upload` |
173
+ | `tftp.server` | `Server`, `ServerLimits`, `FileSystemHandler`, `AtomicWriter`, `RequestContext` |
174
+ | `tftp.backends` | `MemoryHandler`, `HttpHandler`, `UpstreamHandler` (proxy), `Pipe` |
175
+ | `tftp.relay` | `Relay` and routing helpers |
176
+ | `tftp.aio` | `AsyncClient`, `AsyncServer` |
177
+ | `tftp.capture` | trace events, `PcapWriter`, pcap/pcapng reading, `analyze`, filters |
178
+ | `tftp.options` | `ServerOptions`, option registry, profiles, `Negotiated` |
179
+ | `tftp.packet` | `Opcode`, `ErrorCode`, packet types, `encode_*`/`decode` |
180
+ | `tftp.transfer` | I/O-free `Sender`/`Receiver` engine |
181
+ | `tftp.uri` | `tftp://` URLs |
182
+ | `tftp.path` | `TftpPath`, `TftpUriPath` (`path` extra) |
183
+ | `tftp.netascii` | streaming netascii translation |
184
+ | `tftp.errors` | `TftpError` and the typed `RemoteError` subclasses |
185
+ | `tftp.cli` | the `pytftp` command (`cli` extra) |
186
+
187
+ Everything is also importable from `tftp` directly. The complete reference,
188
+ with every signature and gotcha, is [`src/tftp/AGENTS.md`](https://github.com/jose-pr/pytftp/blob/main/src/tftp/AGENTS.md),
189
+ which ships inside the package.
190
+
191
+ ## Development
192
+
193
+ ```bash
194
+ py -3.14 -m venv .venv/3.14-nt-arm64 # or python3.14 -m venv .venv/3.14-posix-x86_64
195
+ .venv/3.14-nt-arm64/Scripts/pip install -e ".[dev]"
196
+ .venv/3.14-nt-arm64/Scripts/python -m pytest -q
197
+ .venv/3.14-nt-arm64/Scripts/python -m black --check src tests benchmarks examples
198
+ python benchmarks/run.py
199
+ ```
200
+
201
+ `pytest -m "not slow"` skips the rollover tests; tests marked `interop` run
202
+ against curl, tftp-hpa, BusyBox and dnsmasq where installed (their servers need
203
+ passwordless `sudo`). `sudo python tests/firmware_boot.py ipxe` network-boots
204
+ iPXE in QEMU against the server (its `uefi` scenario does not pass yet). See
205
+ [AGENTS.md](https://github.com/jose-pr/pytftp/blob/main/AGENTS.md) for the layout and conventions.
206
+
207
+ ### Releasing
208
+
209
+ This project follows [Semantic Versioning](https://semver.org/) and keeps a
210
+ [`CHANGELOG.md`](https://github.com/jose-pr/pytftp/blob/main/CHANGELOG.md). Pushing a tag matching `v*` triggers the release
211
+ workflow: test gate → build (checking the tag names the version built) → a strict
212
+ docs build as a gate → GitHub release → publish. The release workflow never deploys
213
+ the docs site itself: for a final release its last job dispatches the docs workflow
214
+ at the tag, which owns every Pages deploy.
215
+
216
+ ## License
217
+
218
+ MIT — see [LICENSE](https://github.com/jose-pr/pytftp/blob/main/LICENSE).