One of the most common objections I hear to SLAAC, and frankly one of the most legitimate ones, is accountability. “If the host picks its own address, how do I know who had 3fff:db8:10::5c1e:9aff:fe3b:21aa last Tuesday at 21:00?” With DHCPv6 IA_NA you get a lease log - this is what everyone is used to with legacy IP - and why so many enterprises ask for DHCPv6 support. With SLAAC, and especially with temporary/privacy addresses rotating underneath you, you get… whatever you can scrape out of neighbor caches before they age out. We talked about exactly this visibility gap on IPv6 Buzz 208 , and it’s a real blocker for a lot of security and compliance teams.
RFC 9686 (Registering Self-Generated IPv6 Addresses Using DHCPv6) is the standards-track answer: a host that builds its own address tells a DHCPv6 server about it with an ADDR-REG-INFORM message, and the server logs it - operators get an analog to what they had with legacy IP, for the most part. The problem is that the client side barely exists at this time. Kea has server support, Android rollout has been announced, systemd-networkd has an open PR, and macOS and Windows don’t have it at all. Seeing a significant missing piece here, I built ipv6-slaac-logger to close that gap now, instead of waiting for every OS vendor to catch up. Do I expect anyone to roll this out in production at scale? Probably not. However, it is a working implementation that they can point to and tell their vendors “we need that as a native feature, here is an example”.
The goal is simple to state: for any IPv6 address seen during a time window, answer which DUID, MAC(s) and hostname held it, from when to when, and what evidence each claim rests on. It’s written in Go, pure Go with CGO_ENABLED=0, and ships as two binaries.
So there are three ways an address ends up in the database:
Native RFC 9686 clients ─┐
Agents (mac/win/linux) ──┼── multicast ff02::1:2:547 ──┐
├── unicast collector:547 ────┼──> collector ──> SQLite (truth) ──> syslog/rsyslog
Remote links ── relay ───┴── Relay-Forward ────────────┘ ^
All hosts (incl. IoT) ── ND/RA on the wire ── SPAN/trunk ── sniffer
The collector accepts direct multicast, direct unicast, and relayed registrations, so it works on a single flat lab network or behind DHCPv6 relays on a campus. Full disclosure: relay needs tested. If you test and this is working, please do let me know. If it has problems, let me know that too. I’ll get around to testing it eventually.
This is a forensic tool, so the output has to fit into what people already run. SQLite is the source of truth, with 400 days of retention by default, and every event is forwarded as JSON with an @cee: prefix so rsyslog’s mmjsonparse can handle it, and from there your SIEM or whatever. A registration looks like this:
{"ts":"2026-11-08T21:14:03.412Z","event":"reg.add","addr":"3fff:db8:10::5c1e:9aff:fe3b:21aa",
"duid":"00:03:00:01:5c:1e:9a:3b:21:aa","duid_type":"LL","mac":"5c:1e:9a:3b:21:aa",
"mac_src":"duid","origin":"agent","host":"lab-mbp","via":"multicast","link":"en0/3fff:db8:10::/64",
"preferred_lft":14400,"valid_lft":86400,"xid":"a1b2c3"}
Events cover registrations (reg.add, reg.change, reg.expire, reg.conflict, reg.reject), sniffer sightings (nd.new, nd.conflict, nd.expire), and router advertisements (ra.seen, ra.change). That last pair turns out to be a nice side benefit: you get a log entry whenever a new router shows up on a link or an existing one flips its M/O flags or prefix set. That’s useful for rogue RA detection in its own right.
Repeat sightings and refreshes only update last_seen in the database. They never generate syslog lines, so a busy link doesn’t bury the events you actually care about, or as a mentor once taught me “never alarm on non-actionable stuff”. I try to keep that ethos alive.
Answering the “who had this address” question is one command:
slaac-logger-collector query -addr 3fff:db8:10::5 -at 2026-11-08T21:00:00Z
Getting the agent going is pretty painless:
slaac-logger-agent list # show local addresses and which would be registered
slaac-logger-agent -config agent.toml install # install as a service
slaac-logger-agent start
It runs as a systemd unit with DynamicUser=yes and only CAP_NET_BIND_SERVICE on Linux, a launchd daemon on macOS, and a Windows Service as LocalService. It tries to reuse the OS’s own DHCPv6 DUID (from the Windows registry, macOS’s DUID_IA.plist, or dhclient’s leases) so that its records line up with any existing DHCPv6 server logs. If it can’t, it generates a DUID-LLT and persists it, so the identity survives restarts and MAC randomization. That last part matters a lot when every phone and laptop on the network is rotating its MAC.
A few deliberate choices worth calling out:
last_seen plus lifetime.origin with that in mind.Give a collector interface the sniff role and it watches DAD, NS, NA, and RS traffic to learn MAC ↔ address pairs. A few things I learned building it that are worth passing along:
AF_PACKET with PACKET_AUXDATA), so VLANs are recorded correctly, plus one in-frame tag for QinQ (not fully tested).nd.conflict with the previous MAC attached.stats event, so you can tell “nothing happened” apart from “the sniffer can’t see anything.”This is new, and I want to be upfront about the edges:
responder role and set the O flag in your RAs. Don’t run the responder on the same link as a Kea server that also answers registrations, or you’ll get two servers answering.duid in the config if you need it to match.slaac. Both are still registered.CGO_ENABLED=0 go build -o bin/slaac-logger-collector ./cmd/collector
CGO_ENABLED=0 go build -o bin/slaac-logger-agent ./cmd/agent
There’s a unit test suite, a Linux network-namespace end-to-end test, and a CI interop job that runs the agent against ISC Kea 3.0, so it isn’t only tested against itself. As with some of my other recent projects, LLM tooling, specifically Claude was used during development to help me make my own code “suck less”, and to aid in some of the more complex things I am still learning, and a human reviewed all of it - I want to learn as much as I can from “companion coding”; the README says so plainly.
For me, the larger point is that “we can’t track SLAAC addresses” has been a reason, or at least an excuse, to keep DHCPv6-only or IPv4-only policies around for far too long. RFC 9686 is the right long-term answer, and as native client support lands this tool should mostly fade into the background as a plain RFC 9686 collector. Until then, an agent plus a sniffer plus a sane log format gets you most of the way there today.
The code is at github.com/buraglio/ipv6-slaac-logger . Kick the tires, break it, and open issues. I’m especially interested in hearing from anyone testing native Android registrations or the systemd-networkd client.