Hi, thanks for your report, it seems to be yet another instance of a well-known bug where intermittent network unreachability on start gets misinterpreted and not rectified afterwards. We’ll add this to the list of symptoms, internal reference #192.

Have a nice day!
Maria

On Fri, Oct 09, 2026 at 02:32:17PM -0400, qipeng ding wrote:

Bug description When BIRD starts while a native link-local address is still tentative, BIRD2.19.3 can leave a valid static external IPv6 route configured at NSSA ASBR startup unpublished beyond convergence after the interface and adjacency become usable. Initial export logs “Cannot find forwarding address for NSSA-LSA”. The local route remains present, both routers become Full and ordinary OSPF routes converge, but the receiver gets no target Type7 or external route during a further40-second observation. A normal outbound reload, with the same peer PID and configuration bytes, immediately publishes Type7 and installs the receiver route. This is ordinary native configuration and startup, not malformed traffic or an alleged security effect.

Software version Tested BIRD2.19.3, source commit5c6376d3075a58d1d378f6d6a7fd50fc0750a72a, executable SHA256302cdf17722d7601a15aa06d821c8cb92e5b309324a56d19021394e78f2e9793. Linux openEuler24.03 under WSL2. Both endpoints run this same unmodified binary. Each independent run used CPUQuota50%, MemoryMax256M, MemorySwapMax0, TasksMax32 and a 180-second process bound. No source patch was applied. Results are limited to this fixed binary and topology; no current-master runtime or FRR execution is claimed.

How to reproduce Requirements: Linux root, Python3, iproute2, util-linux, tcpdump and BIRD2.19.3; birdc is useful for the equivalent manual steps. The attached normal_nssa_startup_export.py is self-contained and imports only Python standard-library modules. Its exact verified source SHA256 is 0d214e2218925520f02ff04e82f09cbf2f6109f85b7df81093ebdf54c38b15c7. Choose a previously nonexistent short output directory and an absolute BIRD executable path. Run the following command, preserving the attached source unchanged: sudo systemd-run –unit=bird-nssa-startup-review-a –collect –wait –pipe -p CPUQuota=50% -p MemoryMax=256M -p MemorySwapMax=0 -p TasksMax=32 timeout 180 unshare –net – python3 /absolute/path/normal_nssa_startup_export.py –bird /absolute/path/bird –output /tmp/snssa-review-a Repeat with another unused output directory and unit name. Both exact final-source natural-startup executions were independently verified. As a third positive control, run once with –wait-link-local: the corrected program verifies addr_info tentative/dadfailed Boolean fields, and this ready-address control publishes normally without a reload. The failure requires the ordinary startup where BIRD begins before native link-local DAD completes. The complete equivalent textual sequence is: inside a fresh outer network namespace containing only lo, start two owned network namespaces with unshare –net sleep 240 and retain their PIDs. Create one veth pair named sn_dut/sn_peer, move each endpoint into its respective namespace with ip link set … netns PID, bring lo and the endpoint up, and assign 2001:db8:55::1/64 to DUT and2001:db8:55::2/64 to peer using ip -6 addr add … nodad. Verify there are no default routes. For the ready-address control, wait until each native link-local address has no tentative flag. Do not disable DAD globally. Configure DUT as: log stderr all; router id 10.0.0.1; protocol device bc_device { scan time 1; } protocol ospf v3 osp { debug all; ipv6 { import all; export filter { reject; }; }; area 1 { nssa; summary yes; interface “sn_dut” { type ptp; hello 1; dead 4; retransmit 2; transmit delay 1; }; }; } Configure peer with router id 1.1.1.1, device bc_device, the same OSPF area/interface settings on sn_peer, and IPv6 export filter { if source = RTS_STATIC then accept; else reject; }. Include the static protocol from the start: protocol static origin { ipv6; route 2001:db8:55:feed::/64 blackhole { ospf_metric2=20; ospf_tag=16909060; }; } The attached startup-dut.conf/startup-peer.conf files provide the exact whitespace-correct full configurations used. Validate both with bird -p -c CONFIG. Start tcpdump on DUT sn_dut before starting either bird -f -c CONFIG -s ROLE.ctl -P ROLE.pid in its own namespace. Use birdc -s ROLE.ctl (or the program’s Unix control-socket client) to poll show ospf neighbors until both are Full and show route 2001:db8:55::/64 all until the ordinary OSPF route is present. Confirm show route 2001:db8:55:feed::/64 all on peer shows the origin static route. Record actual IPv6 interface addresses, peer PID and peer configuration hash. Poll the target on DUT for 40 additional seconds and capture show route all/show ospf lsadb. Observe missing target and the startup FA errors. Execute reload out osp only on peer, without changing its configuration, PID or interface addresses. Require DUT target route and exact Type7 LSDB identity, corroborated by the native capture. Remove only the peer static protocol, configure normally and verify target withdrawal plus both Full. Stop only the owned captures/processes and namespaces. The program performs all these steps, records every command/CLI reply/config/log/capture and verifies cleanup; it never attaches to an external network or exports kernel routes.

Expected behavior Initial suppression is reasonable while no valid NSSA forwarding address is available. Once an ordinary global address and OSPF interface are usable, the configured, export-accepted static route should be reevaluated and originated as Type7 without manual reload. RFC3101 sections2.3/2.4 require a valid nonzero FA for propagated NSSA external routes, and RFC5340 sections4.4.3.7/A.4.8 require an IPv6 global FA. The same unchanged route/configuration successfully publishes with P1 and FA2001:db8:55::2 on outbound reload. The initial error should not permanently lose this export work. This report does not challenge suppression while FA is genuinely unavailable.

Actual behavior Two final-source independent natural-startup executions reproduce the missing export. A separate exact-source control waits for confirmed non-tentative link-local addresses before starting BIRD and publishes the route normally without reload. After Full and positive ordinary-route control, absence persists for 40.187s and 40.158s. First valid Type7 appears 0.023955s and 0.015342s after the respective reload trigger. In each run source PID/configuration are unchanged. Native Type7 is area1, ASBR1.1.1.1, prefix2001:db8:55:feed::/64, type2 metric20, tag0x01020304, P1, FA2001:db8:55::2. OSPF pseudoheader and LSA Fletcher checksums independently verify; capture kernel drops are zero. The receiving external route/LSDB appear, and subsequent ordinary withdrawal succeeds with both routers Full.

Additional information Two earlier three-role NSSA startup attempts also failed to establish the same ordinary external-route baseline. Their exact failed sources, polls, logs and captures remain archived. A later Totally NSSA policy test adds its external control after Full and passes; that setup workaround neither fixes nor dismisses this startup finding. The two earlier standalone executions used an otherwise valid protocol identifier that emitted a nonfatal keyword-shadow warning; those results remain preserved. The attached final source uses bc_device. The initial ready-address check then proved incorrect because ip -j uses an addr_info tentative Boolean. Those preliminary controls are explicitly invalidated as ready-address claims and retained. The final source fixes this Boolean gate; two natural-startup reproductions and a genuinely ready-address positive control were rerun cleanly. No corpus PASS is inferred from these standalone observations. Primary RFC texts and all10 associated errata records were reviewed: the one RFC3101 erratum4767 is rejected path-preference wording; RFC5340 verified2078/3350/3351/3352/3357, held1582/1583 and rejected2046/7649 do not amend this FA/export lifecycle. Verified3352 R-bit constraints concern route processing; normal reachable routers are used here. A separate default-route-policy question is not part of this report.

Code location and suggested fix Fixed-source links: https://gitlab.nic.cz/labs/bird/-/blob/5c6376d3075a58d1d378f6d6a7fd50fc0750a72a/proto/ospf/topology.c (ospf_rt_notify, find_surrogate_fwaddr, ospf_originate_ext_lsa, ospf_feed_begin/end), https://gitlab.nic.cz/labs/bird/-/blob/5c6376d3075a58d1d378f6d6a7fd50fc0750a72a/proto/ospf/iface.c (ospf_ifa_notify3, ospf_iface_new), and https://gitlab.nic.cz/labs/bird/-/blob/5c6376d3075a58d1d378f6d6a7fd50fc0750a72a/nest/proto.c (proto_cmd_reload, channel_request_feeding). Source facts: ospf_rt_notify chooses a surrogate FA for a single-NSSA export; if none is found it logs and returns before installing external_rte or originating Type7. find_surrogate_fwaddr scans p->iface_list for the NSSA and admissible nonsecondary, nonpeer addresses above link scope. ospf_ifa_notify3 creates OSPF interfaces on link-local-address notification, while global-address notifications schedule Link/Router LSAs; those paths do not explicitly refeed failed external exports. ospf_iface_new adds the interface to the list. Manual reload out reaches proto_cmd_reload -> channel_request_feeding -> export notification again, now allowing ordinary Type7 origination. Logs show early export errors before the OSPF interface/link-local notification path; actual Type7 appears only after reload. The source-supported root-cause explanation is lost/deferred export work when initial feed and the first device/static update precede usable NSSA interface state, followed by later link-local availability without export retry. The ready-address control shows an initial FA error can recover automatically if interface construction is already available during the initial device/static-update sequence; the delayed-address case misses that opportunity. Native internal callback ordering has not been instrumented, so the precise scheduling attribution remains a hypothesis, while the failed export and manual-refeed recovery are directly observed facts. Suggested concrete design: track pending NSSA exports when FA selection is temporarily unavailable, and coalesce a deferred outbound channel refeed when an eligible NSSA interface/global address becomes available. Hook the availability transitions in ospf_iface_new/ospf_ifa_notify3, with channel-state and pending-work guards; reuse channel_request_feeding rather than invoking export notification recursively inside address callbacks. Preserve route withdrawal and FA changes, avoid repeated full refeeds on every Hello/SPF and audit down/removal transitions. Regress both ready-address and naturally delayed-address startup, valid reload control and withdrawal. This is a suggested design, not an authored, built or tested patch.

Checklist Fixed tested version/source and binary hash recorded; complete textual native steps and independently executable exact source attached; exact final source clean-run in two natural-startup reproductions plus a genuinely ready-address positive control; actual initial absence, positive Full/RIB controls, same PID/config reload recovery, raw valid Type7 and receiver LSDB/RIB, zero capture drops, ordinary withdrawal and ownership cleanup verified; all failed/earlier attempts retained; RFC and all applicable errata reviewed; source facts, causal hypothesis and untested fix clearly separated; no inherited FRR/corpus credit, current-master runtime claim or speculative security claim. Maintainer acceptance is not claimed.

–
Maria Matejka (she/her) | BIRD Team Leader | CZ.NIC, z.s.p.o.