--- /home/maxx/dfbsd/dfbsd/sys/netinet6/nd6.c 2026-06-29 12:51:19.000000000 +0000 +++ nd6.c 2026-09-10 23:43:38.749415625 +0000 @@ -497,6 +497,16 @@ case ND6_LLINFO_WAITDELETE: next = nd6_free(rt); break; + case ND6_LLINFO_NOSTATE: + /* + * DF-3331: created by route cloning (RTM_RESOLVE) + * but never moved to INCOMPLETE because the output + * path does not do neighbor resolution (e.g. gif). + * Its creation grace (nd6_delay) has expired, and + * nothing else will ever free it -- reap it here. + */ + next = nd6_free(rt); + break; case ND6_LLINFO_INCOMPLETE: if (ln->ln_asked++ >= nd6_mmaxtries) { struct mbuf *m = ln->ln_hold; @@ -1314,9 +1324,15 @@ /* * When req == RTM_RESOLVE, rt is created and * initialized in rtrequest(), so rt_expire is 0. + * + * DF-3331: give the entry a grace period. On + * ND-resolving links nd6_resolve() moves it to + * INCOMPLETE synchronously in the same output call; + * on other links (e.g. gif) nothing ever does, and + * nd6_timer() now reaps it when the grace expires. */ ln->ln_state = ND6_LLINFO_NOSTATE; - ln->ln_expire = time_uptime; + ln->ln_expire = time_uptime + nd6_delay; } rt->rt_flags |= RTF_LLINFO; ln->ln_next = llinfo_nd6.ln_next; @@ -1709,11 +1725,15 @@ rt = nd6_lookup(from, 0, ifp); if (!rt) { -#if 0 - /* nothing must be done if there's no lladdr */ + /* + * DF-3331: do not create a neighbor cache entry when the + * ND message carries no link-layer address option + * (RFC 4861 7.2.3: nothing to record). Entries created + * here are ND6_LLINFO_NOSTATE, and see the timer change + * below for why that mattered. + */ if (!lladdr || !lladdrlen) return NULL; -#endif rt = nd6_lookup(from, 1, ifp); is_newentry = 1;