Skip to content

Releases: jose-pr/pytftp

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 07:54

Changed

  • tftp.path is built on pathapi and uripath. The path extra installs
    pathapi>=0.1.0,<0.2 and pathapi-uri>=0.1.0,<0.2 where it installed pathlib-next[uri],
    which cannot be installed beside them. To upgrade, import LocalPath, MemPath and FileStat
    from pathapi instead of pathlib_next, and spell the URI path
    from uripath import URIPath; URIPath("tftp://host/file") where it was
    pathlib_next.uri.UriPath. TFTPPath and TFTPURIPath keep their names, arguments and
    methods, and URIPath("tftp://...") finds TFTPURIPath without an import, now through the
    uripath.schemes entry-point group.
  • unlink() always raises NotImplementedError. unlink(missing_ok=True) used to return
    without doing anything, so that copy(overwrite=True) could clear its target. pathapi no
    longer asks a target that cannot be removed to do that, and the write that follows still replaces
    the file, so a caller of copy() changes nothing. A program that called unlink(missing_ok=True)
    itself must stop, or ask pathapi.protocols.supports(path, Remove) first.
  • move() from a TFTP path raises NotImplementedError before 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. Check supports(path, Remove) to know in advance.
  • stat() says when the server gave no size. A server that does not send tsize (and a file
    longer than one block) used to give st_size == 0 as if it were known; the result now has
    size_known == False, and st_size is 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: one x-list probe of its root
    on the first question, kept for the life of the client, False for a server without the
    extension. The classes implement Stat, BinaryOpen, Listing, Identity and Copy, and
    capabilities() of either says so.

Fixed

  • A failed or abandoned upload no longer replaces the server's file. A with block 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 one ResourceWarning. Closing a stream normally still uploads.

Full Changelog: v0.1.1...v0.2.0

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 14 Oct 04:13

Changed

  • The cli extra requires duho>=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. A config key 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

Choose a tag to compare

@github-actions github-actions released this 12 Oct 13:07

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.AsyncTFTPRelay and AsyncRouteFunction: TFTPRelay on 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 an async def (a plain function is a
    TypeError when the relay is built), and a route or a name lookup that outlasts idle_timeout
    answers the client ERROR 0 "relay error". Stopping ends every transfer in flight with ERROR 0
    "relay shutting down" to both ends. Importing tftp.relay still imports no asyncio.
  • TransferResult.to_dict() and RelaySummary.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 as dataclasses.replace does.
  • tftp.capture.combine_hooks(*hooks): one trace= 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, the Protocol it takes (write(time, source, destination, payload)).
    pktcap.PcapWriter and pktcap.PcapngWriter are both one, so a run can be recorded as
    pcapng too.
  • tftp.capture.dissect_tftp, TFTPLayer and register_tftp_dissector(registry=None, *, ports=(69,)): TFTP as a layer for pktcap's dissection, so pktcap.read_dissected
    gives a TFTPLayer on the datagrams to the request port, pktcap.frame_record writes
    it and the filter proto=tftp selects it. Nothing registers on import; the call registers
    in the registry given, or in pktcap's default one.
  • TFTPLayer.session and TFTPLayer.summary(), and tftp.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_transfers feeds
    the dissected frames to a FlowTracker and gives each datagram it attributes to a transfer its
    layer, with the transfer's id in session, so that pktcap's filter op=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's text output prints for the
    layer ([c3] RRQ 'boot.efi' octet). pktcap_plugin registers the filter key session.
  • tftp.capture.pktcap_plugin(registry): the hook pktcap loads by name, so PKTCAP_LOAD=tftp.capture pktcap convert -i boot.pcap -f "op=RRQ and file=*.efi" filters TFTP with the keys op, file, block
    and code (op refuses a name that is none of the six opcodes when the filter is compiled). It
    registers the layer and register_tftp_dissector(registry) and, when that raises, nothing. Importing
    tftp.capture still imports neither pktcap nor asyncio and registers nothing.
  • tftp.capture.replay_transfers(source, host, port=69, *, ports, writes, speed, max_delay, limit, timeout, retries) and pytftp 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_delay and limit as
    pktcap.replay_schedule has them). It is not a replay of datagrams, and it sends nothing to an
    address in the capture. Reads only by default; writes=True / --writes also 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 returns ReplayedTransfers(results, skipped).
    pytftp replay is pktcap's Replay command subclassed (ReplayCmd(pktcap.cli.Replay), with the plugin
    tftp.capture always loaded): --input/-i FILE and --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-port and --broadcast are refused by the parser: no captured datagram is sent, a client asks
    again.
  • pktcap is an optional dependency, the pktcap extra (pip install "tftp[pktcap]"); dependencies
    names netimps alone. Importing tftp, tftp.capture and tftp.cli, transferring, serving and
    relaying need no pktcap. A function that reads, dissects, replays or writes a capture (analyze of a
    path or a stream, replay_transfers, dissect_tftp, register_tftp_dissector, pktcap_plugin,
    trace_to) raises ImportError with the line captures need the 'pktcap' extra: pip install "tftp[pktcap]" when it is missing; analyze of datagrams needs none. pytftp capture,
    pytftp replay and --pcap on get, put, serve and relay print that line on standard error
    and end with status 1 before a socket is bound or a file created; pytftp --help still lists
    capture and replay. dev installs tftp[cli,path,pktcap].
  • CapturedTransfer.write_to(directory) writes a transfer's file under directory
    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_prefix and by_subnet take "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-client serves it the whole root, other clients' directories included; the
    help of --per-client says 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, beside Sender and Receiver. 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.
    TFTPClient and AsyncTFTPClient drive 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 for tftp.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, transfer and cli), 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 pytftp command: --config/-c FILE before the command's name, else
    PYTFTP_CONFIG, else the user's own pytftp/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_CONFIG set 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, as serve.bogus, and never a value), a config key, 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 the pktcap extra 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, --create and
    --packets for 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, TFTPRelay and AsyncTFTPRelay take listen= (netimps.ListenLike, bound by
    netimps.bind_listen) in place of host= and interface=; 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.Interface or MAC (the IPv4 wildcard socket limited to that adapter, which also hears its
    broadcasts, as interface= did not on Linux), "*", or several as a list or joined by commas, and each
    is one socket. With no listen the server listens on every address of both families, as "*" and "::":
    two IPv6-only-clean sockets that share the port chosen for port=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_sessions and 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 and bind() refuses it. server_address is the first bound address; server_addresses lists them all.
  • pytftp serve and pytftp relay read --listen in 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
    listen key is a list o...
Read more

v0.0.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 07:46

Added

  • Client: download and upload over IPv4 and IPv6, to and from paths, binary
    files or memory, with blksize, windowsize, tsize and timeout
    negotiation (and the utimeout, rollover, blksize2 and cookie
    extensions on request), progress and on_negotiated callbacks, 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,
    fitting blksize to 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_complete for 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/overwrite policy; max_upload and free-space checks from tsize.
  • 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-Length as tsize);
    UpstreamHandler, a terminating proxy to another TFTP server whose two sides
    negotiate independently; Pipe for 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 (including stream(), an async generator with
    backpressure) and AsyncServer (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 profiles STRICT, DEFAULT, PXE, HPA and
    LEGACY.
  • Capture and debugging: trace= hooks on the client, server and relay
    reporting every datagram as a PacketEvent; 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 (a tsize probe the
    server counts as declined, not failed).
  • path extra: TftpPath, a pathlib-next Path bound to a client
    (client.path(...)), and TftpUriPath, the tftp:// scheme for
    pathlib-next's UriPath; whole-file streaming reads and writes, stat()
    without a transfer, copy()/move() across schemes.
  • Typed errors: RemoteError raised as FileNotFound, AccessViolation,
    DiskFull, IllegalOperation, UnknownTransferId, FileAlreadyExists,
    NoSuchUser or OptionNegotiationError; 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.
  • pytftp command (cli extra): get and put (also by URL), serve (a
    directory, --http gateway or --upstream proxy), relay, capture;
    --compat profiles, --trace and --pcap on every command that moves
    packets, --json output.
  • port_range= on Server, AsyncServer and Relay (PortRange):
    transfer sockets take their ports from a range a firewall can allow;
    --port-range on pytftp serve and relay.
  • pytftp serve --per-client (serve ROOT/<client address>/ when it exists),
    --ignore-case and --remap REGEX=REPLACEMENT.
  • mstfwindow, Windows bootmgr's variable-window option, as an extension a
    server may allow (fixed window of 4).
  • pytftp's x-list and x-mtime extensions (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_mtime on TftpPath and TftpUriPath.
  • Hosts, networks and interfaces accepted as objects (ipaddress types,
    netimps.Host, netimps.Interface) wherever an address is taken, typed
    with netimps' aliases.
  • interface= on Server, AsyncServer and Relay (and --interface on
    pytftp serve/relay): listen on one network adapter, by name,
    netimps.Interface, MAC or address.
  • RequestContext.interface: the arrival interface as a netimps.Interface.
  • Loopback throughput benchmark (benchmarks/run.py).
  • Licensed under MIT.

Full Changelog: https://github.com/jose-pr/pytftp/commits/v0.0.0