Repository navigation
Releases: jose-pr/pytftp
Releases · jose-pr/pytftp
Release list
v0.2.0
Changed
tftp.pathis built onpathapianduripath. Thepathextra installs
pathapi>=0.1.0,<0.2andpathapi-uri>=0.1.0,<0.2where it installedpathlib-next[uri],
which cannot be installed beside them. To upgrade, importLocalPath,MemPathandFileStat
frompathapiinstead ofpathlib_next, and spell the URI path
from uripath import URIPath; URIPath("tftp://host/file")where it was
pathlib_next.uri.UriPath.TFTPPathandTFTPURIPathkeep their names, arguments and
methods, andURIPath("tftp://...")findsTFTPURIPathwithout an import, now through the
uripath.schemesentry-point group.unlink()always raisesNotImplementedError.unlink(missing_ok=True)used to return
without doing anything, so thatcopy(overwrite=True)could clear its target.pathapino
longer asks a target that cannot be removed to do that, and the write that follows still replaces
the file, so a caller ofcopy()changes nothing. A program that calledunlink(missing_ok=True)
itself must stop, or askpathapi.protocols.supports(path, Remove)first.move()from a TFTP path raisesNotImplementedErrorbefore it copies anything. It used to
copy the file and then raise because the source could not be deleted, which left a stray copy at
the destination. Checksupports(path, Remove)to know in advance.stat()says when the server gave no size. A server that does not sendtsize(and a file
longer than one block) used to givest_size == 0as if it were known; the result now has
size_known == False, andst_sizeis still 0. A caller that needs the size reads the file or
uses a server that reports it.
Added
pathapi.protocols.supports(path, Listing)answers for the server: onex-listprobe of its root
on the first question, kept for the life of the client,Falsefor a server without the
extension. The classes implementStat,BinaryOpen,Listing,IdentityandCopy, and
capabilities()of either says so.
Fixed
- A failed or abandoned upload no longer replaces the server's file. A
withblock over
open("w")that raised, a text write that could not be encoded (write_text("\u00e9", encoding="ascii")) and a file collected unclosed all uploaded what had been written, even an
empty file. Each now sends nothing and the server keeps the file it had; a stream collected
unclosed emits oneResourceWarning. Closing a stream normally still uploads.
Full Changelog: v0.1.1...v0.2.0
v0.1.1
Changed
- The
cliextra requiresduho>=0.7.4,<0.8. With it every error of the settings file is one
line and status 2: a key or a table nobody has, and a key a command does not have, no longer print the
usage lines before the line that names the file and the key. Aconfigkey in the file is refused by
duho's own check, in its words (pytftp: error: duho: config file <file>: unknown key 'config').
Full Changelog: v0.1.0...v0.1.1
v0.1.0
This release breaks the documented API: names, public module paths, constructor signatures and
what malformed input raises. Old names are not kept as aliases. "Renamed" is the lookup from an old
name to its new one, "Removed" lists what is gone, "Changed" has the detail of each behaviour that
differs and "Fixed" the defects corrected on the way.
Added
tftp.relay.AsyncTFTPRelayandAsyncRouteFunction:TFTPRelayon an asyncio event loop. A
sibling over the same rules, with the same arguments, ERROR texts, counters and bounds, run on
Windows' default Proactor loop as well; its route is anasync def(a plain function is a
TypeErrorwhen the relay is built), and a route or a name lookup that outlastsidle_timeout
answers the client ERROR 0 "relay error". Stopping ends every transfer in flight with ERROR 0
"relay shutting down" to both ends. Importingtftp.relaystill imports no asyncio.TransferResult.to_dict()andRelaySummary.to_dict(): the JSON-ready objects the
command line prints for--json, one per transfer.TFTPServerOptions.replace(**changes): a copy with the named fields changed, built
through the constructor asdataclasses.replacedoes.tftp.capture.combine_hooks(*hooks): onetrace=hook from several; a hook that
raises does not keep the others from the event.tftp.capture.trace_to(writer): a trace hook that appends every event to a writer, and
DatagramWriter, theProtocolit takes (write(time, source, destination, payload)).
pktcap.PcapWriterandpktcap.PcapngWriterare both one, so a run can be recorded as
pcapng too.tftp.capture.dissect_tftp,TFTPLayerandregister_tftp_dissector(registry=None, *, ports=(69,)): TFTP as a layer for pktcap's dissection, sopktcap.read_dissected
gives aTFTPLayeron the datagrams to the request port,pktcap.frame_recordwrites
it and the filterproto=tftpselects it. Nothing registers on import; the call registers
in the registry given, or in pktcap's default one.TFTPLayer.sessionandTFTPLayer.summary(), andtftp.capture.follow_transfers(frames, tracker):
pktcap dissects TFTP by port, so only a datagram to or from the request port had a layer and a
transfer's DATA, ACK and OACK, which run between other ports, had none.follow_transfersfeeds
the dissected frames to aFlowTrackerand gives each datagram it attributes to a transfer its
layer, with the transfer's id insession, so thatpktcap's filterop=DATA and tftp.session=c3
selects one transfer's data and a record carries the id. A datagram a snap length cut and one that
is not TFTP are left as they were.summary()is the line pktcap'stextoutput prints for the
layer ([c3] RRQ 'boot.efi' octet).pktcap_pluginregisters the filter keysession.tftp.capture.pktcap_plugin(registry): the hook pktcap loads by name, soPKTCAP_LOAD=tftp.capture pktcap convert -i boot.pcap -f "op=RRQ and file=*.efi"filters TFTP with the keysop,file,block
andcode(oprefuses a name that is none of the six opcodes when the filter is compiled). It
registers the layer andregister_tftp_dissector(registry)and, when that raises, nothing. Importing
tftp.capturestill imports neither pktcap nor asyncio and registers nothing.tftp.capture.replay_transfers(source, host, port=69, *, ports, writes, speed, max_delay, limit, timeout, retries)andpytftp replay --input FILE --to HOST: ask a server you name for each transfer a
capture holds again, paced by the capture's own times (speed,max_delayandlimitas
pktcap.replay_schedulehas them). It is not a replay of datagrams, and it sends nothing to an
address in the capture. Reads only by default;writes=True/--writesalso uploads what the
capture holds of each write, which overwrites a file on the server, and a write the capture
holds only partly is never replayed. It returnsReplayedTransfers(results, skipped).
pytftp replayis pktcap'sReplaycommand subclassed (ReplayCmd(pktcap.cli.Replay), with the plugin
tftp.capturealways loaded):--input/-i FILEand--to HOST[:PORT](port 69 when left out),
--request-port,--writes,--speed,--max-delay,--limit(counts transfers),--timeout/-t,
--retries/-r,--json,--no-delay,--filter/-f(which frames reach the tracker) and--load.
--source-portand--broadcastare refused by the parser: no captured datagram is sent, a client asks
again.- pktcap is an optional dependency, the
pktcapextra (pip install "tftp[pktcap]");dependencies
namesnetimpsalone. Importingtftp,tftp.captureandtftp.cli, transferring, serving and
relaying need no pktcap. A function that reads, dissects, replays or writes a capture (analyzeof a
path or a stream,replay_transfers,dissect_tftp,register_tftp_dissector,pktcap_plugin,
trace_to) raisesImportErrorwith the linecaptures need the 'pktcap' extra: pip install "tftp[pktcap]"when it is missing;analyzeof datagrams needs none.pytftp capture,
pytftp replayand--pcaponget,put,serveandrelayprint that line on standard error
and end with status 1 before a socket is bound or a file created;pytftp --helpstill lists
captureandreplay.devinstallstftp[cli,path,pktcap]. CapturedTransfer.write_to(directory)writes a transfer's file underdirectory
whatever the capture called it, replacing the example in the capture guide that opened
the peer's file name and the command's own private copy.by_prefixandby_subnettake"KEY=HOST[:PORT]"text in a sequence, in place of
a pair.pytftp serve --max-duration SECONDS(the longest a transfer may run),
--max-upload BYTES(the largest upload accepted; a directory only) and
--per-client-only(a client with no directory of its own is refused, where
--per-clientserves it the whole root, other clients' directories included; the
help of--per-clientsays it is not isolation).tftp.transfer.Requester(send, server, opcode, filename, retries, now, *, ...): the client's
opening exchange as an engine class that does no I/O, besideSenderandReceiver. It sends the
request, repeats it with back-off, decides which datagram is the first answer, classifies it
(OACK, DATA 1, ACK 0 or an ERROR) and says whether the request may be made again without options.
TFTPClientandAsyncTFTPClientdrive it, and so can a client on another transport.- Documentation: a command-line guide in the docs site (checked against each command's
--help), an API
page fortftp.listing, and the shipped API header split into a top header,tftp/AGENTS.md, and one
header per package (tftp/client/AGENTS.md,server,options,backends,relay,capture,
packet,path,transferandcli), with the environment variables and the loggers listed; a test
checks every signature a header prints against the live object. - A settings file for the
pytftpcommand:--config/-c FILEbefore the command's name, else
PYTFTP_CONFIG, else the user's ownpytftp/pytftp.ini(under$XDG_CONFIG_HOME, else~/.config, or
%APPDATA%; absent is no error). INI, JSON, TOML and YAML as duho reads them: top-level keys are the root's
options and a table named for a command ([serve],[capture]) sets that command's options by field name,
the argument over the file. An empty path (--config "",PYTFTP_CONFIGset empty) means no file, the
user's own included. A key or table nobody has (duho's strict check: the usage lines, then one line that names
the key, asserve.bogus, and never a value), aconfigkey, an invalid document, a file over 65,536 octets
and (POSIX) a default file that is another user's or everyone may write are refused with status 2 and a line
that names the file and holds no text of it; without thepktcapextra a[capture]or[replay]table is
refused with the line that names the extra. The library reads no settings file. An on/off option now also has its opposite
(--no-json,--no-trace,--no-write, ... and--tsize,--options,--createand
--packetsfor the options that were--no-X), so the command line can switch off what a file switched on.
Changed
- The servers and the relays listen where a netimps listen specification says.
TFTPServer,
AsyncTFTPServer,TFTPRelayandAsyncTFTPRelaytakelisten=(netimps.ListenLike, bound by
netimps.bind_listen) in place ofhost=andinterface=;port=stays and is the port an entry
without one gets. An entry is an address ("192.0.2.10","[fe80::1%eth0]:6969"), an adapter name,
netimps.Interfaceor MAC (the IPv4 wildcard socket limited to that adapter, which also hears its
broadcasts, asinterface=did not on Linux),"*", or several as a list or joined by commas, and each
is one socket. With nolistenthe server listens on every address of both families, as"*"and"::":
two IPv6-only-clean sockets that share the port chosen forport=0, the IPv6 one left out on a host with no
IPv6, where there was one dual-stack socket. A request is answered, refused and traced on the socket it
arrived on, from the address it was sent to as before;max_sessionsand the other limits count across
all the sockets; a datagram that reaches an adapter-limited socket from another adapter is dropped before it
is read as a request. A host name is no longer resolved for listening:listen="localhost"is an adapter
name andbind()refuses it.server_addressis the first bound address;server_addresseslists them all. pytftp serveandpytftp relayread--listenin that grammar, repeatable or comma-joined, with-p
the default port; a specification that names nothing bindable is one line and status 2. The settings file's
listenkey is a list o...
v0.0.0
Added
Client: download and upload over IPv4 and IPv6, to and from paths, binary
files or memory, withblksize,windowsize,tsizeandtimeout
negotiation (and theutimeout,rollover,blksize2andcookie
extensions on request), progress andon_negotiatedcallbacks, exponential
backoff of retransmissions, an optional time limit, and a one-time retry
without options when a server refuses them (ERROR 8).blksize="mtu"sizes
blocks to the route's interface MTU; hosts may carry a port
("[::1]:6969").Server: one event-loop thread serving any number of transfers, each on its
own socket bound to the address the request was sent to (pktinfo); dual-stack
::listening by default; a negotiation policy (ServerOptions: maximum
block and window size, window memory bound, allowed and refused options,
fittingblksizeto the arrival interface's MTU); resource limits
(ServerLimits: request size, filename length, option count and length,
sessions per client, transfer duration); ignoring requests sent to broadcast
or multicast addresses; blocking handlers opened in worker threads;
dallying on the last ACK of an upload;on_completefor every transfer;
counters for metrics (stats,stats_snapshot()).FileSystemHandler: serves a directory with containment against..,
symlinks and Windows device names; backslash separators; atomic uploads;
create/overwritepolicy;max_uploadand free-space checks fromtsize.- Handler protocol (
open_read/open_write) for generated content; sources
and sinks may report "not ready yet" (WouldBlock) and wake the transfer
from another thread, so slow backends apply backpressure instead of
blocking the server. - Backends:
MemoryHandler;HttpHandler, a TFTP-to-HTTP(S) gateway (GET and
PUT, HTTP status codes mapped to TFTP errors,Content-Lengthastsize);
UpstreamHandler, a terminating proxy to another TFTP server whose two sides
negotiate independently;Pipefor writing such handlers. Relay: a transparent relay forwarding requests byte for byte to upstream
servers and datagrams unchanged between the two transfer IDs, with routing
by client subnet, filename prefix or arrival interface, idle and lifetime
limits, per-transfer summaries and counters.tftp.aio:AsyncClient(includingstream(), an async generator with
backpressure) andAsyncServer(async handlers and streams, pktinfo on every
event loop including Windows' Proactor loop).- Option negotiation through a registry of option handlers, so applications can
add their own; compatibility profilesSTRICT,DEFAULT,PXE,HPAand
LEGACY. - Capture and debugging:
trace=hooks on the client, server and relay
reporting every datagram as aPacketEvent;PcapWriter(Wireshark-readable
pcap); pcap/pcapng reading from files or live pipes with IP fragment
reassembly;FlowTracker/analyze()reconstructing transfers and their
files from a capture; a filter language; live capture on Linux. tftp://URLs (RFC 3617):parse_url,format_url,download_url,
upload_url.Client.size(): a file's size without transferring it (atsizeprobe the
server counts as declined, not failed).pathextra:TftpPath, a pathlib-nextPathbound to a client
(client.path(...)), andTftpUriPath, thetftp://scheme for
pathlib-next'sUriPath; whole-file streaming reads and writes,stat()
without a transfer,copy()/move()across schemes.- Typed errors:
RemoteErrorraised asFileNotFound,AccessViolation,
DiskFull,IllegalOperation,UnknownTransferId,FileAlreadyExists,
NoSuchUserorOptionNegotiationError;TransferAborted. - Transfer engine (
Sender/Receiver) with RFC 7440 windowing, the
Sorcerer's Apprentice fix, unknown-TID handling, tolerance of repeated
OACKs and unlimited file size via block-number rollover, usable without
sockets. - Streaming netascii in both directions; packet encode/decode for every RFC
1350 and RFC 2347 packet, keeping a request's raw bytes for forwarding. pytftpcommand (cliextra):getandput(also by URL),serve(a
directory,--httpgateway or--upstreamproxy),relay,capture;
--compatprofiles,--traceand--pcapon every command that moves
packets,--jsonoutput.port_range=onServer,AsyncServerandRelay(PortRange):
transfer sockets take their ports from a range a firewall can allow;
--port-rangeonpytftp serveandrelay.pytftp serve --per-client(serveROOT/<client address>/when it exists),
--ignore-caseand--remap REGEX=REPLACEMENT.mstfwindow, Windows bootmgr's variable-window option, as an extension a
server may allow (fixed window of 4).- pytftp's
x-listandx-mtimeextensions (LISTING_OPTIONS): directory
listings (tftp.listing) and modification times.Client.stat(),
Client.listdir()(async too),pytftp ls,pytftp serve --listing, and
iterdir/walk/glob/is_dir/st_mtimeonTftpPathandTftpUriPath. - Hosts, networks and interfaces accepted as objects (
ipaddresstypes,
netimps.Host,netimps.Interface) wherever an address is taken, typed
with netimps' aliases. interface=onServer,AsyncServerandRelay(and--interfaceon
pytftp serve/relay): listen on one network adapter, by name,
netimps.Interface, MAC or address.RequestContext.interface: the arrival interface as anetimps.Interface.- Loopback throughput benchmark (
benchmarks/run.py). - Licensed under MIT.
Full Changelog: https://github.com/jose-pr/pytftp/commits/v0.0.0