github DanielAdolfsson/ndppd 1.0.0-rc1
ndppd 1.0.0-rc1

pre-release2 hours ago

The first release candidate for ndppd 1.0.0 — and the first GitHub Release this project has ever published, for any version.

ndppd 1.0 is a ground-up C99 rewrite of the 0.2.x C++ daemon. It was started in 2019, went quiet in June 2020, and was finished this year after @totoCZ put it into production behind a Juniper switch, found everything that was wrong with it, and sent the fixes back (#93). It has since gained a test suite and the fixes that suite turned up.

Why this instead of 0.2.x

It sees unicast NUD probes. Once the upstream router has a neighbor entry for a proxied address, it refreshes that entry with a unicast NS sent straight to the proxy's MAC — no multicast involved. 0.2.x listened on an all-multicast socket and never saw those frames, so entries went STALE and then FAILED every twenty or thirty minutes under sustained traffic, recovering only when something put the link back into multicast. This is why "it works while tcpdump is running" is such a common report: tcpdump enables promiscuous mode as a side effect. ndppd 1.0 enables it on purpose.

The session state machine is correct. One session per (target, proxy) pair, moving INCOMPLETE → VALID → STALE → INVALID with RFC 4861 semantics at each edge. Solicitations arriving during INCOMPLETE are queued rather than answered, because an answer there is a claim the daemon cannot yet back up, and a late advertisement still satisfies every queued subscriber. In auto mode an incoming NS re-checks the routing table, which is what lets a container started after the first solicitation still be reached.

It is strict about what it answers. Hop limit 255 is enforced on receive (§6.1.1), the ICMPv6 checksum is verified, multicast and solicited-node targets are rejected, and advertisements carry the OVERRIDE flag and an explicit source address rather than leaving the choice to the kernel.

Routing table integration is through Netlink on Linux and AF_ROUTE on the BSDs, rather than four fork//bin/sh invocations per session transition.

Verified

228 unit assertions and 55 integration assertions, both emitting TAP 13, run by make check. The integration suite stands up a three-node topology — an upstream that believes the whole prefix is on-link, a router running ndppd, and a target behind it — injects exact frames, and asserts on exactly what comes back on the wire. On Linux it uses network namespaces and needs no root; on FreeBSD it uses VNET jails and epairs. Every change is additionally built with gcc and clang in release and debug, and the unit suite is run under AddressSanitizer and UndefinedBehaviorSanitizer.

Upgrading from 0.2.x — read this first

Configuration files are not portable between the two. 1.0 reorganised the configuration, and it rejects an unknown keyword rather than ignoring it, so a 0.2.x file will not load unmodified. timeout, ttl, retries, promiscuous, autovia, deadtime, route-ttl and address-ttl are 0.2.x spellings with no 1.0 equivalent. Start from ndppd.conf-dist or the README's Configuration section rather than adapting what you have.

0.2.x is not going away: it remains on the 0.2.x branch in maintenance, taking fixes for crashes and memory errors but no features.

Known limitations

None of these is a regression, and all are tracked:

  • MLD proxying is not implemented (#55). On a segment with MLD snooping, solicited-node multicast never reaches the proxy host and ndppd is bypassed entirely. src/mld.c exists but is a stub.
  • Ethernet only (#72). The packet path assumes a link-layer header and a hardware address, so tun, wg and other point-to-point devices do not work — and worse, they fail silently: the daemon starts cleanly, logs the interface with an all-zero MAC, and then does nothing. Detecting and refusing a non-Ethernet interface at open time is the next step here.
  • Interfaces must exist at startup. ndppd exits if a configured interface is missing, which at boot is a race against Docker and libvirt bridges. Order the unit after whatever creates them, or set Restart=on-failure.
  • It does not route, and it does not send router advertisements. ndppd answers discovery; moving the packets afterwards needs a route and net.ipv6.conf.all.forwarding=1, and downstream machines still need an RA daemon or static addressing.

Building

make && sudo make install

On FreeBSD and other BSDs use gmake — the Makefile is GNU make syntax, and the base system's make fails on the first line. Man pages are built with Asciidoctor; make ndppd builds only the binary.

Why a release candidate

The code is running in production and the suite covers it, but it has been in front of a small number of real networks so far, and the behaviours that matter most here are the ones only a real network exercises. If you run it, please report what you find — particularly anything involving a switch doing MLD snooping, a provider that filters on source address, or hardware the test matrix does not cover. 1.0.0 final follows once this has had time in the field.

Thanks to @totoCZ, without whom this branch would still be an abandoned rewrite.

Don't miss a new ndppd release

NewReleases is sending notifications on new releases.