Changelog
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.2.2 - 2026-08-16
Fixed
resolve_system(timeout=)now bounds wall time. The lookup ran inside awith ThreadPoolExecutor(...)block, whose__exit__joins the worker still blocked ingetaddrinfo-- so the timeout changed what was raised but not when, and a 30s resolver hang still cost the caller 30s despitetimeout=5.0.resolve()'s chain consequently never reachednslookupat the promised deadline either.interface=is honoured for IPv6 multicast. The spec was reduced to an address and then passed toif_nametoindex(), which always fails for an address string, so theIPV6_JOIN_GROUPindex silently stayed0-- "kernel's choice", the exact defaultinterface=exists to override.IPV6_MULTICAST_IFwas never set at all, so sends left by the default route while joins listened elsewhere. Both now resolve the adapter to its interface index. IPv4 behaviour is unchanged.ping(hostname, ipv6=True)is no longer always false. The reply address was resolved with the IPv4-onlygethostbyname, so a v6 reply was checked against a v4 expectation and never matched. The expectation now comes fromgetaddrinfohonouringipv6=, and a name resolving to several addresses counts as answered if the reply came from any of them.ping(..., method="tcp"/"udp")reaches IPv6 destinations. Both probes openedAF_INETsockets unconditionally, so a v6 destination failed insideconnect/sendtoand was reported as unreachable -- a wrong falsy answer rather than an error.ipv6=now applies to all three methods.ping(..., method="udp")detects ICMP port-unreachable on POSIX. The probe socket was never connected, and POSIX delivers asynchronous ICMP errors only to connected UDP sockets -- so the documented "host answered, nothing listening" signal worked on Windows alone and the probe just timed out on Linux/macOS.ECONNREFUSEDandECONNRESETboth now count.
Documentation
- The shipped API header named
PingResult.sourceandRoute.source; both attributes are spelled.src(andPingResult.hostwas unlisted). - Recorded the per-family multicast interface selection,
ping'sipv6=reach across all three methods, andresolve_system's real wall-time deadline in the shipped header. - Added the known, still-unverified macOS/BSD
ping6reply-shape gap (from <addr>,rather thanfrom <addr>:) to the header rather than guessing a parser change without a macOS runner.
0.2.1 - 2026-07-30
Added
AddressLike, a new type alias (str | IPv4Address | IPv6Address | IPv4Interface | IPv6Interface) accepted by everydst-typed parameter:ping,tcp_check,wait_for_port,get_route,hop_count,get_pmtu,discover_mtu,get_tcp_mss,scan_ports(host),get_ip,UdpEndpoint.send, andresolve's (and its backends')query. AnIPv4Interface/IPv6Interfaceunwraps to its.ip-- previously passing one stringified with its/prefixintact, which every consumer (subprocess argument, socket call, DNS query) read as garbage. A network (IPv4Network/IPv6Network) raisesTypeError, since it has no single address to use.resolve()(and all three backends) auto-selectrdtype. It now defaults toNone, which picks"ptr"whenqueryis an address literal and"a"otherwise --resolve("8.8.8.8")now returns['dns.google']instead of attempting a nonsensical A lookup on a literal address. Pass an explicitrdtypeto opt out.resolve_system()gains"ptr"support (viasocket.gethostbyaddr()) to make this work across every backend.
Fixed
ping(src=...)crashed withNameErrorinstead of returning a falsy result whensrcnamed an interface with no usable address (e.g. an unknown adapter name, or a MAC not currently present) -- a leftover reference to an undefinedhostnamevariable instead ofdst. Found via amypypass while auditing type annotations; a regression test now covers the path.
Changed
- Public functions across the package now carry complete parameter and
return type annotations (previously missing on, among others,
collapse,subtract,get_ip,PingResult,Route,scan_hosts,is_multicast,join_group/leave_group,multicast_socket,UdpEndpoint, andbind). The recurring "loose interface spec" parameter (ping(src=),bind(interface=),discover_mtu(src=),multicast_socket(interface=), etc.) now shares one internal type alias instead of being unannotated at each call site.
0.2.0 - 2026-07-29
Added
- Three independently callable DNS backends, plus
resolve()chaining them:resolve_dnspython()(the originaldnspython-backed implementation, every record type),resolve_system()(socket.getaddrinfo()-- hosts file, NSS, OS resolver cache, address records only), andresolve_nslookup()(shells out tonslookup, parses both BIND-style and Windows-style output, address records only).resolve()now tries["dnspython", "system", "nslookup"]in order by default and returns the first definitive answer, skipping/falling through backends that can't serve the request (non-addressrdtype, missing binary,dnspythonnot installed). A custom order/subset is available viaresolve(..., backends=[...])(or a single name as a plain string). resolve()(and all three backends) gain asearchparameter for the system resolver's search list (resolv.conf'ssearch/domaindirective, or the Windows per-adapter DNS suffix list). It defaults toTrue, so an unqualified name likeresolve("db1")is expanded the wayping db1would be;search=Falselooks the name up literally (as a fully-qualified name, so the OS resolver's own search-list logic doesn't kick in either), and a list of domain names tries exactly those suffixes instead of the system list.dnspythonis now an optional dependency (pip install netimps[dns]), sinceresolve()can fall back toresolve_system()/resolve_nslookup()without it. The CLI'sresolvesubcommand now reports a missing-backend failure as a clean CLI error rather than an uncaught exception.
Changed
resolve()'s default behavior for unqualified names. Previously an unqualifiedquerywas only ever looked up as-is; it now also tries the system resolver's search list first (seesearchabove). Passsearch=Falseto keep the old literal-only behavior.ns=Nonealready used the system resolver's nameservers; an invalidns=now raises before any query is attempted rather than silently falling back to the system default.resolve()is no longer purelydnspython-backed. Behavior should be unchanged for existing callers whendnspythonis installed (it's still tried first), but a lookup that previously raised or returned[]becausednspythonfailed for a reason unrelated to the DNS answer itself (e.g. a malformed system resolver config) may now succeed via thesystemornslookupfallback instead.
0.1.0 - 2026-07-25
Added
- Complete local-interface membership lookups.
interfaces_for()yields every adapter matching an interface, exact address/IPInterface, network, orMACAddress, whileinterface_for()keeps the first-match scalar contract.is_local_address()distinguishes an assigned or loopback address from one that is merely private, link-local, on-link or reachable. - Static parser contracts.
TypeFormoverloads preserve union and concrete result types, callable builders, and explicittry_parse(default=...)fallbacks.IPInterfaceLikenow complements the existing input aliases.
Changed
interface_for()accepts networks and MAC addresses, including MAC text and 6-byte packed values. Integer MACs remain explicitMACAddressvalues. Its legacystrict=Falsesynthetic fallback remains address-only because a missing network or MAC has no honest single-interface representation.- The IP input aliases now include packed bytes plus the exact stdlib
two-tuple and existing-interface forms accepted by the interface/network
factories.
is_valid()is documented as a boolean convertibility check rather than an unsound type guard for the original object.
0.0.2 - 2026-07-23
Added
ws/wssin the built-in scheme→port table (80/443). WebSocket schemes (RFC 6455) ride the HTTP/HTTPS ports but are absent from/etc/services, soget_default_port("wss")previously returnedNoneand every websocket consumer had toregister_portthem.http/httpsremain canonical for 80/443.
0.0.1 - 2026-07-22
Added
- Command-line interface (
netimps .../python -m netimps), built on duho and installed by the newcliextra. Eleven subcommands cover the diagnostic surface:interfaces,ping,resolve,check,route,mtu,scan,addr,source,port,split. Every one takes--json, and exit codes distinguish success from "the answer was no" from a caller error.duhois CLI-only -- importing the library does not require it.
0.0.0 - 2026-07-22
Initial release.
Earlier version numbers appear in this project's git history but were never tagged or published, so there is no upgrade path to describe -- everything below is simply what the package contains.
Added
- Interface discovery --
get_interfaces()reports adapter names, MACs, MTU and real prefix lengths on Linux, macOS/BSD and Windows, viactypesbindings togetifaddrs(3)andGetAdaptersAddresses. No third-party dependency.Interface.is_loopbackis derived from the addresses rather than the name, sincelo,lo0andLoopback Pseudo-Interface 1share no spelling.Interface.primary_ip()picks one entry;iter_addresses()is the flattened per-address view. - Types and parsing --
IPAddress/IPInterface/IPNetworkunion aliases to annotate with, and oneparse(value, type, **kwargs)entry point with non-raisingtry_parseand booleanis_validsiblings. Concrete types are strict about family; networks are non-strict about host bits by default. MACAddress-- colon/hyphen/dot/bare plusint/bytes, hashable and ordered, with.packed,.oui,.is_multicast,.is_localand case-selectable rendering. A value type exposing.packed, not abytessubclass, matching howipaddressmodels addresses.- Socket helpers --
bind(),bind_error_hint(),interface_for(),get_source_ip(),get_free_port(),tcp_check(),wait_for_port(). UdpEndpoint-- UDP receive reporting which interface a datagram arrived on viaIP_PKTINFO, degrading whererecvmsgdoes not exist.- Routing and MTU --
get_route()(first hop, unprivileged),hop_count()(raw sockets or a traceroute fallback, so it works without elevation),discover_mtu()(measures the real path --method="icmp"with DF-flagged pings,"udp"with datagrams, or"tcp"deriving from the negotiated MSS since TCP cannot be probed),get_pmtu()(the kernel's cached answer, usuallyNone),get_tcp_mss(), andInterface.mtu. Header arithmetic is family-aware: IPv6 adds 20 bytes over IPv4, and assuming v4 on a v6 path under-reports by exactly that. - CIDR maths and host parsing --
collapse(),subtract()(absent fromipaddress), andnormalize_host(), which keeps"::1"an address rather than host"::"port1. - Scheme/port registry --
get_default_port(),get_default_scheme(),register_port(). - DNS --
resolve()returning native types (A/AAAAasipaddressobjects),[]on a genuine lookup failure, andValueErrorfor a malformed query rather than a silent empty result. ping()-- returns aPingResultwith round-trip time and TTL that stays truthy.method="icmp"|"tcp"|"udp"reaches hosts through firewalls that drop echo; all three ask "is the host up?", so a TCP refusal or an ICMP port-unreachable counts as success.tcp_checkremains the "is the service up?" question, where a refusal is a failure.ttl=behaves identically on every platform, because Windowspingexits 0 for "TTL expired in transit" and the reply address is verified instead of the exit code.- Scanning -- concurrent
scan_ports()/scan_hosts(), ports addressable by scheme name. - Multicast --
multicast_socket(),join_group(),leave_group(), wrapping a setup whose failure modes are otherwise silent. Host,retry()/backoff_delays(), and the named networksAPIPA,LOOPBACK_V4,LOOPBACK_V6,LINK_LOCAL_V6.