tftp.relay
tftp.relay
Transparent TFTP relay with pluggable routing.
See :class:TFTPRelay and its asyncio twin :class:AsyncTFTPRelay. For a proxy that
terminates both sessions (each side with its own options) use
:class:tftp.backends.UpstreamBackend with a TFTPServer instead.
AsyncTFTPRelay(route, *, listen=None, port=69, idle_timeout=IDLE_TIMEOUT, max_duration=MAX_DURATION, linger=LINGER, upstream_src=None, limits=None, max_sessions=DEFAULT_MAX_SESSIONS, ignore_broadcast=True, reply_from_request_address=True, trace=None, on_session_end=None, port_range=None)
Bases: AsyncioService, RelayBase
:class:tftp.relay.TFTPRelay for asyncio; the same arguments, but route is a coroutine.
route is an upstream (as for TFTPRelay) or an async def
route(request, context) -> upstream | None; a plain function is a
TypeError. The route and the lookup of an upstream's name (in the
loop's executor, unless it is an address) are bounded together by
idle_timeout and count against max_sessions; one that outlasts it
gets the client ERROR 0 "relay error". Use as async with
AsyncTFTPRelay(...) as relay: await relay.serve_forever(), or await
relay.start() ... await relay.aclose().
The lifecycle is :class:tftp.AsyncTFTPServer's: bind() is plain,
shutdown() never blocks and is safe from any thread, await
wait_closed() returns once serving has stopped and await aclose() is
final. Stopping ends every transfer in flight, both ends getting ERROR 0
"relay shutting down".
Source code in src/tftp/relay/_asyncio.py
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
RelaySummary
Bases: NamedTuple
What :class:TFTPRelay reports when a relayed transfer ends.
reason is "complete", "error" (an ERROR passed through, see
error), "idle", "lifetime" or "shutdown".
to_dict()
The summary as a dictionary of JSON types: client and upstream are [host, port].
Source code in src/tftp/relay/_session.py
45 46 47 48 49 50 | |
RouteTable(routes, default=None)
Try routes in order; the first that returns an upstream wins.
default (optional) answers when none does.
Source code in src/tftp/relay/_routing.py
193 194 195 | |
TFTPRelay(route, *, listen=None, port=69, idle_timeout=IDLE_TIMEOUT, max_duration=MAX_DURATION, linger=LINGER, upstream_src=None, limits=None, max_sessions=DEFAULT_MAX_SESSIONS, ignore_broadcast=True, reply_from_request_address=True, trace=None, on_session_end=None, port_range=None)
Bases: SelectorService, RelayBase
Forward TFTP requests to upstream servers, transparently.
:param route: an upstream ("host", "host:port", (host, port),
:class:Upstream) for every request, or a route callable
route(request, context) -> upstream | None (see
:mod:tftp.relay). None refuses with ERROR 2. The callable is a
plain function: a coroutine function is a TypeError, which
:class:AsyncTFTPRelay takes.
:param listen, port: where to listen for requests (as for TFTPServer).
:param idle_timeout: end a transfer after this long without traffic.
Keep it above the largest timeout a peer may negotiate times its
retries.
:param max_duration: end any transfer after this long.
:param linger: after a recognised final DATA/ACK exchange (or an ERROR,
up to 1 s), keep forwarding this long for retransmissions.
:param upstream_src: local address to send upstream from (a string,
an ipaddress address or a netimps.Host).
:param max_sessions: concurrent relayed transfers; requests beyond it get
ERROR 0 "relay busy". The default is 250 on every platform (two
sockets each); None is unlimited, except on Windows, where it is
250 and a number above 255 is a ValueError (select()'s limit).
:param limits, ignore_broadcast, reply_from_request_address:
as for TFTPServer.
:param trace: trace(PacketEvent) for every datagram received or
sent, with role="relay" and leg "client"/"upstream".
:param on_session_end: on_session_end(RelaySummary) when a relayed
transfer ends.
:param port_range: ports for both sockets of each relayed transfer (the
client side and the upstream side), as for TFTPServer.
The constructor opens nothing: :meth:bind does, and :meth:start,
:meth:serve_forever and with call it. The lifecycle is
:class:TFTPServer's: shutdown() never blocks, wait_closed()
returns once serving has stopped, close() is final.
Source code in src/tftp/relay/_sync.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | |
Upstream(host, port=69)
dataclass
Where to forward: an upstream server's host and request port.
An immutable value that equals only another Upstream. host is
whatever was given: a name or address string, an ipaddress address, or
a netimps.Host; port is an int in 1..65535 (TypeError for
another type, :class:TFTPValueError outside the range).
parse(value)
classmethod
An :class:Upstream from "host", "host:port", "[v6]:port",
an address or netimps.Host (port 69 unless its text has one),
(host, port) or an :class:Upstream.
:raises TypeError: a host that is none of those. :raises ValueError: host text netimps cannot read, or a bad port.
Source code in src/tftp/relay/_routing.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | |
__getattr__(name)
Bind :class:AsyncTFTPRelay on first use, so importing the package does not import asyncio.
Source code in src/tftp/relay/__init__.py
43 44 45 46 47 48 49 50 | |
by_interface(table)
Route by arrival: an interface index (int), a netimps.Interface,
or the local address the request was sent to (an address string or
object, an ipaddress interface, a netimps.Host).
Needs pktinfo; without it neither is known and nothing matches.
Source code in src/tftp/relay/_routing.py
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
by_prefix(table)
Route by filename prefix: {"windows/": "wds.lan", "": "default.lan"}.
The longest matching prefix wins; leading / and \ are ignored
and \ counts as /. A sequence may hold "PREFIX=HOST[:PORT]"
text in place of a pair.
:raises TFTPValueError: text without = or without a target.
Source code in src/tftp/relay/_routing.py
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | |
by_subnet(table)
Route by the client's address: {"10.1.0.0/16": "10.1.0.5", ...}.
Keys are anything netimps.parse(..., IPNetwork) takes: CIDR strings,
ipaddress networks, interfaces (their network) or addresses (a /32 or
/128). The most specific (longest prefix) matching network wins. A v4
client seen as ::ffff:a.b.c.d matches v4 networks. A sequence may hold
"CIDR=HOST[:PORT]" text in place of a pair.
:raises TFTPValueError: text without = or without a target.
Source code in src/tftp/relay/_routing.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | |