Post-0.5.0 review pass. The crate's public surface stays
source-compatible — every fix below tightens existing semantics
(correctness, performance, robustness against kernel edge cases)
without breaking the API. The minor bump rather than a patch reflects
the breadth of behavioral changes (Linux RPDB-aware best-local
selection, lazy nexthop dump, BSD source-route filtering, Windows
forwarding-table walk, etc.) that callers exercising edge cases may
notice.
- Lazy
RTM_GETNEXTHOPdump innetlink_walk_routes,netlink_best_local_addrs_into, andrt_generic_addrs(the gateway walker). The dump only runs if the route walk actually encounters anRTA_NH_IDreference. Hosts withoutip nexthop-managed routes (the common case) skip the round-trip entirely; transientEINTR/NLM_F_DUMP_INTRfrom unrelated nexthop-subsystem churn no longer fails ordinaryroute_table()/best_local_*()/gateway_addrs*()calls. - Per-family route walk —
route_table_by_filterissues separateAF_INETandAF_INET6dumps instead of a singleAF_UNSPECrequest. Some kernel versions return only IPv4 forAF_UNSPECroute dumps; the per-family walk avoids silent IPv6 loss on dual-stack hosts. RT_TABLE_DEFAULT(253) accepted alongsideRT_TABLE_MAIN(254) andRT_TABLE_LOCAL(255). Hosts with a fallback default route installed viaip route add default ... table defaultnow surface it correctly.- RPDB precedence in best-local selection — candidate key is now
(table_rank, metric, pref_rank)lexicographic. Lower table rank wins regardless of metric (matching the kernel's rule chainlocal < main < default); within a table, lower metric wins; RFC 4191 router preference (RTA_PREF) breaks equal-metric IPv6 ties withHIGH < MEDIUM < LOW. - ECMP correctness — equal-key default candidates now extend
best_oifsinstead of replacing it, so addresses from every winning interface surface inbest_local_*. RTA_VIAcross-family gateways are dropped, documented —IpRouteonly models same-family(dest, gateway)pairs, so a route whose next-hop family differs from the destination family used to silently emit asgateway = None(on-link). They now skip cleanly; the omission is noted inroute_table's rustdoc.- Source-prefix and policy-table filtering — routes with
rtm_src_len != 0orRTA_SRCset, plus routes from custom policy tables, are filtered out.route_table/best_local_*rustdoc carries a "best-effort" caveat for policy-routed hosts. - Nexthop-group missing-member handling —
resolve_nh_idnow returnsNonewhen a group leaf is missing from the nexthop snapshot (triggering the deferred-retry →EINTRpath), instead of silently dropping the leg. - Malformed-attribute defence —
dump_nexthopsfilters nexthops with malformedNHA_GATEWAYpayloads instead of emitting them asgw = None. Same defence forRTA_DSTin best-local selection. - Receive-buffer size — route / nexthop walkers now use a 32 KiB
buffer (matching
iproute2) instead of one OS page. Routes with largeRTA_MULTIPATHECMP lists or deepNHA_GROUPpayloads no longer silently truncate. NLM_F_DUMP_INTR/ family-unavailable handling inrt_generic_addrs— the gateway walker now mirrors the route walker. Interrupted snapshots returnEINTR; unsupported-family errors collapse to an empty result.- Big-endian byte order —
rt_generic_addrs's IPv4 gateway decode usesIpv4Addr::from([u8; 4])instead ofu32::from_ne_bytes(...).swap_bytes(). The previous form happened to work on little-endian Linux but produced byte-reversed addresses on big-endian targets (powerpc64, riscv64gc, etc.) that recently joined the cross-target CI matrix.
fetch()truncation fix — thesysctl(NET_RT_*)wrapper truncates the buffer to the kernel-written length before parsing. NetBSD and OpenBSDNET_RT_IFLISTdumps no longer surface asErr(InvalidData "invalid message")on the trailing zero-padding.- Source-specific routes filtered — NetBSD's
RTF_SRCflag and OpenBSD'sRTAX_SRC/RTAX_SRCMASKslot bits are respected. - OpenBSD
rtm_priorityfor best-local — used as the best-default selection key. Other BSDs have no documented per-route priority; on those targets every default route ties and addresses from all default-route interfaces are returned (documented as best-effort). - Per-family union APIs —
best_local_addrs/route_table_by_filterwalkAF_INETandAF_INET6separately so single-stack hosts don't lose the populated family onEAFNOSUPPORT/EPROTONOSUPPORT/EOPNOTSUPP. - End-of-stream sentinel —
walk_route_table,best_local_addrs_in, andrt_generic_addrs_intreat a zero-length record header as end-of-stream padding instead of an error. - Defensive netmask parsing —
interface_addr_table_intoskips individual addresses with non-canonicalRTAX_NETMASKinstead of failing the whole walk (helps point-to-point / tunnel interfaces that emit peer-address bytes in the mask slot). - Big-endian IPv4 gateway fix — same
swap_bytes()byte-order bug as the Linux walker, fixed identically. compat::RtMsghdr/RtMetricslayout assertions — every BSDRtMsghdrand per-platformrt_metricsstruct now has compile-timesize_ofandoffset_of!checks against the kernel ABI for every field we read. Catches version-skew silently breaking selection on a future libc bump.- DragonFly multicast —
multicast_addrs()returnsErr(ErrorKind::Unsupported)instead of pretending success with an empty list (the DragonFly kernel has noNET_RT_IFMALIST).
route_table()support —route_table_by_filterwalksGetIpForwardTable2and emitsIpRouteentries. Filters expired rows (ValidLifetime == 0), multicast / broadcast destinations, loopback rows, and per-subnet directed-broadcast/32housekeeping rows.- Interface-keyed broadcast filter — directed-broadcast suppression
set is
HashSet<(InterfaceIndex, Ipv4Addr)>derived fromGetUnicastIpAddressTable. Multihomed hosts with a legitimate/32host route to an address coincidentally equal to another adapter's directed broadcast no longer get dropped. RFC 3021/31prefixes excluded; suppression only applies toPrefixLength == 32 && gw.is_none()rows. best_local_*via documented forwarding-table walk — replaces the prior undocumentedGetBestRoute2(NULL, 0, ...)/GetBestInterfaceEx(unspec)calls. WalksGetIpForwardTable2forPrefixLength == 0 && ValidLifetime > 0 && !Loopbackrows, joins per-interfaceConnected+MetricfromGetIpInterfaceTable, picks smallest effective metric (route metric + interface metric).- Equal-cost defaults preserved —
best_default_route_interfacereturnsSmallVec<u32>instead ofOption<u32>. Multi-homed hosts with equal-cost defaults across two adapters now surface addresses from every winning interface (matching Linux/BSD). Connected = FALSEfilter — stale defaults pinned to a disconnected VPN / unplugged NIC can no longer win the metric race.
- Bulk path with per-interface fallback —
get_ip_mtu/get_ipv4_mtu/get_ipv6_mtufirst try a singleinterface_addrs()dump, then fall back to per-interface iteration if the bulk dump fails. One unrelated malformed kernel message no longer aborts the whole lookup.
- New
routebenchmark suite (benches/route.rs) coveringroute_ipv4_table,route_ipv6_table,route_table, androute_table_by_filter(default_only)on Linux / macOS / Windows. - Linux gateway operations regained their pre-branch latency after
the lazy nexthop fix:
gateway_ipv4_addrs: 19.9 µs (was 30.4 µs mid-branch, 18.4 µs at0.5.0)gateway_ipv6_addrs: 16.1 µs (was 26.7 µs mid-branch, 14.3 µs at0.5.0)gateway_addrs: 24.1 µs (was 34.4 µs mid-branch, 22.4 µs at0.5.0)
- Cross-target compile matrix now covers Linux glibc/musl across aarch64 / i686 / powerpc64 / riscv64gc, FreeBSD x86_64 / i686, NetBSD x86_64, and Windows MinGW / MSVC i686 / x86_64.
- Runtime BSD VMs —
test-freebsd,test-netbsd,test-openbsd,test-dragonflybsdjobs run insidevmactions/*-vm@v1. NetBSD and DragonFly usecargo test --lib(doctests + integration tests skipped); per-test cfg-gates intests/interfaces.rsandtests/filter_variants.rsdocument the platform-specific gaps. - Coverage on all four BSDs —
coverage-freebsd,coverage-netbsd,coverage-openbsd,coverage-dragonflybsduse cargo-tarpaulin inside vmactions VMs (replacing the prior cargo-llvm-cov plumbing). - Dedicated MSRV CI job removed —
rust-version = "1.85.0"is the declared contract; users on Rust 1.85 follow the documentedcargo update -p smol_str --precise 0.3.2/cargo update -p criterion --precise 0.7.0workaround. The DragonFly CI job uses these pins because pkg-shipped Rust there is 1.85.1. criteriondev-dep range relaxed from^0.8to>=0.7, <0.9so the DragonFly CI'scargo update --precise 0.7.0satisfies the constraint while normal users still resolve to the latest 0.8.x.
- Fix the inverted
ConvertInterfaceLuidToIndexcheck on Windows that caused the crate to discard the LUID-derived interface index and returnIpv6IfIndexon success (or0on failure) instead. Affectedinterface_table,interface_addr_table,interface_multiaddr_table, andbest_local_addrs_in. - Harden the Linux netlink attribute parser so malformed kernel
messages can no longer panic: IPv4
IFA_ADDRESSpayload length guard,IFLA_MTUempty-payload guard, safe bounded read forIFLA_IFNAME(replacesCStr::from_ptrwhich scanned past the attribute on a non-null-terminated value), and clamp ofrta_align_of()against the remaining buffer so an unaligned last attribute cannot overflow. Added the missing bounds check in theRTM_NEWROUTEmetric/oif parser. - Fix FreeBSD build
(#32):
libcdoes not exposert_msghdr,ifa_msghdr, orNET_RT_IFLIST2on non-Apple BSDs.getifsnow ships absd_like::compatmodule with local#[repr(C)]definitions for FreeBSD, DragonFly, NetBSD, and OpenBSD, and gatesNET_RT_IFLIST2/if_msghdr2/ifma_msghdr2to Apple only. - Fix NetBSD build:
IfaMsghdrcompat for NetBSD/OpenBSD (whose layouts differ from FreeBSD), and castif_data.ifi_mtutou32since NetBSD defines it asuint64_t.
- Windows:
Informationno longer materializes aSmallVec<IP_ADAPTER_ADDRESSES_LH>of ~400-byte struct copies. A zero-copyAdapterIter<'_>walks the kernel's linked list in place. - BSD routing: replace the O(n²)
results.contains(&addr)dedup with an O(1)HashSet<(index, IpAddr)>so large routing tables scale linearly. - Linux: drop the per-line
MediumVec<&str>allocation in/proc/net/igmpand/proc/net/igmp6parsing — walk the whitespace-separated iterator directly. - Linux: switch
ifindex_to_nametorustix::net::netdevice::index_to_name_inlined(rustix 1.1). The returned stack-allocatedInlinedNamecombined withSmolStr's inline-up-to-23-bytes optimization makes the call allocation-free on the happy path. - Extract a shared
adapter_indexhelper on Windows, deduplicating three copies of the LUID→index resolution block.
- MSRV bumped from 1.63.0 to 1.64.0.
- Bump
rustixrequirement from 1 to 1.1 (required forindex_to_name_inlined). - Bump
smallvec-wrapperrequirement from 0.3 to 0.4. - Bump
criteriondev-dependency from 0.7 to 0.8. - Drop the unused
eitherdependency (was declared both at the top level and under the Linux-specific target).
- Refresh the crate description.
- Add a benchmark CI workflow with Criterion benches for
interfaces,local_ip_address, andgateway. - Bump
windows-sysrequirement from 0.60 to 0.61. - Bump
linux-raw-sys,which, andcriteriondev-dependency versions.
- Bump up versions
- Fix #8
- Fix #6
- Add
ifname_to_v6_iface,ifname_to_v4_iface, andifname_to_iface.
- Remove
libcon Linux platform, implement netlink by usingrustix
- Refactor
IfNetand addIfv4Net,Ifv6Net,IfAddr,Ifv4AddrandIfv6Addr - Add
interface_addrs,interface_ipv4_addrs,interface_ipv6_addrs,interface_addrs_by_filter,interface_ipv4_addrs_by_filter, andinterface_ipv6_addrs_by_filter - Add
interface_multicast_addrs,interface_multicast_ipv4_addrs,interface_multicast_ipv6_addrs,interface_multicast_addrs_by_filter,interface_multcast_ipv4_addrs_by_filterandinterface_multcast_ipv6_addrs_by_filter - Add
gateway_addrs,gateway_ipv4_addrs,gateway_ipv6_addrs,gateway_addrs_by_filter,gateway_ipv4_addrs_by_filterandgateway_ipv6_addrs_by_filter - Add
local_addrs,local_ipv4_addrs,local_ipv6_addrs,local_addrs_by_filter,local_ipv4_addrs_by_filterandlocal_ipv6_addrs_by_filter - Add
private_addrs,private_ipv4_addrs,private_ipv6_addrs,private_addrs_by_filter,private_ipv4_addrs_by_filter,private_ipv6_addrs_by_filter - Add
public_addrs,public_ipv4_addrs,public_ipv6_addrs,public_addrs_by_filter,public_ipv4_addrs_by_filter, andpublic_ipv6_addrs_by_filter - Add
ifindex_to_nameandifname_to_index