*Synopsis:* OpenBGPD incorrectly retains an ARP-associated host route (/32)
in its internal connected-route FIB and originates it through BGP.
Restarting bgpd clears the anomaly.

*Category:* user
Environment

   -

   OpenBSD 7.8, GENERIC.MP #54, amd64
   -

   OpenBGPD 8.9
   -

   KVM virtual machine, VirtIO network interfaces
   -

   Two OpenBSD nodes in a CARP HA pair, with iBGP between them

*Privacy note:* This report is sanitized. All IP addresses are RFC 5737
documentation addresses. Hostnames, ASNs, interface names and service
identities have been anonymized or omitted. Route priorities, flags, prefix
lengths, timestamps and BGP attributes have been preserved.
Description

On the CARP BACKUP node, OpenBGPD unexpectedly originates a single /32
belonging to a connected /28 as though it were a genuine connected route.

The corresponding kernel route is an ordinary incomplete/expired ARP entry
with HOST,DONE,LLINFO,CLONED flags. Other host entries in the same /28 have
identical kernel route flags but are not imported into OpenBGPD's internal
connected FIB.

The erroneously originated /32 is advertised over iBGP to the CARP MASTER,
which receives and selects the route. Since this route is more specific
than the intended /28, it can cause traffic for the individual host to be
redirected toward the BACKUP instead of the MASTER.

The issue has occurred multiple times. Restarting bgpd on the BACKUP clears
the invalid /32 from its internal FIB and originated networks, while the
kernel ARP entry remains.

The BACKUP's core-facing BGP neighbors are Idle because they depend on CARP
state, while its inter-node iBGP session remains Established.
Relevant configuration

Sanitized bgpd.conf excerpt:

AS 64512
router-id 198.51.100.31

network inet connected set { localpref 150 }

neighbor 198.51.100.32 {
    remote-as 64512
    set localpref 10
    route-reflector
}

match to any set localpref 120
allow from any
allow to any

Other network statements, filters and peers are omitted.
Before restart — October 26, 2026, 09:22:26 UTC

*CARP status*

carpX: BACKUP
inet 192.0.2.225 netmask 0xfffffff0

*OpenBGPD locally originated networks*

S  0  192.0.2.224/28  0.0.0.0  [expected]
S  0  192.0.2.230/32  0.0.0.0  [UNEXPECTED]

*OpenBGPD internal connected FIB*

C   19  192.0.2.224/28  link#6  [expected]
C  146  192.0.2.230/32  link#6  [UNEXPECTED]

The targeted FIB lookup confirms the erroneous entry:

$ bgpctl show fib 192.0.2.230

C  146  192.0.2.230/32  link#6

*Locally originated BGP route*

BGP routing table entry for 192.0.2.230/32
    Nexthop 0.0.0.0 (via 0.0.0.0)
    Neighbor LOCAL (198.51.100.31)
    Origin IGP, metric 0, localpref 150,
    weight 0, internal, valid, best, announced
    Last update: 1d14h53m ago

*Advertisement to the MASTER*

$ bgpctl show rib out neighbor 198.51.100.32 192.0.2.230/32

AI* N-? 192.0.2.230/32 198.51.100.31 120 0 i

*Kernel route for the affected host*

$ route -n get 192.0.2.230

destination: 192.0.2.230
mask: 255.255.255.255
interface: carpX
if address: 192.0.2.225
priority: 18 ()
flags: <HOST,DONE,LLINFO,CLONED>

*Control sample from the same subnet*

$ route -n get 192.0.2.229

destination: 192.0.2.229
interface: carpX
priority: 18 ()
flags: <HOST,DONE,LLINFO,CLONED>

Only .230 appears in OpenBGPD's internal connected FIB.

*ARP state*

192.0.2.230 (incomplete) carpX expired

State observed on the CARP MASTER

At approximately October 26, 2026, 08:43:50 UTC, the MASTER was receiving
the unexpected /32 from the BACKUP:

BGP routing table entry for 192.0.2.230/32
    Nexthop 198.51.100.31 (via 198.51.100.31)
    Neighbor 198.51.100.31 (198.51.100.31)
    Origin IGP, localpref 10,
    internal, valid, best
    Originator Id: 198.51.100.31
    Cluster Id List: 198.51.100.32

The /32 was not locally originated on the MASTER.
Routing-socket observations

A route -n monitor -inet capture was started *after* the original event
that caused the erroneous BGP route.

At October 26, 2026, 09:28:41 UTC, the kernel emitted both RTM_DELETE and
RTM_ADD notifications for the affected host:

RTM_DELETE:
    priority 146, table 0, if# 6,
    pid 0, errno 0
    flags: <HOST,DONE,LLINFO,CLONED>
    destination: 192.0.2.230
    gateway: link#6

RTM_ADD:
    priority 146, table 0, if# 6,
    pid 0, errno 0
    flags: <HOST,DONE,LLINFO,CLONED>
    destination: 192.0.2.230
    gateway: link#6

Both events include RTF_LLINFO and should be excluded by OpenBGPD's ARP
filtering.

Similar notifications were recorded for other hosts in the same subnet.

At 09:30:45 UTC, the route monitor reported RTM_DESYNC during heavy BGP
route churn. This indicates event loss on the monitoring socket, not
necessarily on OpenBGPD's own routing socket.

The reported priority 146 is consistent with priority 18 plus RTP_DOWN
(0x80).

The route(8) utility rejects an explicit -priority 146 argument as too
large. This does not contradict the priority value observed in kernel
routing notifications and OpenBGPD's internal FIB.
After restarting bgpd

At 09:30:50 and 09:31:12 UTC:

*Locally originated network*

$ bgpctl network show inet | grep -F '192.0.2.230'
[no output]

*Internal connected FIB*

$ bgpctl show fib connected | grep -F '192.0.2.230'
[no output]

*BGP RIB*

$ bgpctl show rib detail 192.0.2.230/32

[Only the covering 192.0.2.224/28 is returned.
 No /32 route remains.]

*Kernel routing table*

$ route -n get 192.0.2.230

destination: 192.0.2.230
mask: 255.255.255.255
interface: carpX
if address: 192.0.2.225
priority: 18 ()
flags: <HOST,DONE,LLINFO,CLONED>

*ARP state*

192.0.2.230 (incomplete) carpX expired

This confirms that the erroneous BGP origination disappears after
restarting bgpd, even though the ordinary kernel ARP host entry remains.
Timeline correlation

The reported BGP last-update age places the most recent update around
October 24, 2026, 18:28–18:29 UTC.

Application logs show several concurrent backend health-check failures
between 18:28:40 and 18:28:44 UTC.

The timing is potentially relevant, but the direction of causality is
unknown. Application and service identities have been deliberately omitted.
How to repeat

The issue is not yet deterministically reproducible.

The observed environment consists of:

   1.

   OpenBSD 7.8 running OpenBGPD 8.9.
   2.

   CARP HA with an IPv4 /28 subnet.
   3.

   network inet connected configured in bgpd.conf.
   4.

   A permanently established iBGP session between MASTER and BACKUP.
   5.

   ARP host entries created and modified within the CARP-connected subnet.

Intermittently, one ARP-associated /32 appears as a connected route in
OpenBGPD's internal FIB and is advertised through BGP.

The erroneous entry persists through subsequent normal ARP notifications,
but is removed by restarting bgpd.
Suspected regression compared with OpenBSD 7.0

A source-code comparison identified a potentially relevant difference in
usr.sbin/bgpd/kroute.c.

OpenBSD 7.0 ignores an RTM_CHANGE notification when the corresponding route
is absent from its internal FIB.

In OpenBSD 7.8, the newer kr_fib_change() implementation may instead insert
the route using kroute_insert().

This behavior was introduced during the June 13, 2022 refactor:

https://github.com/openbsd/src/commit/db879ab8e4d2a60465f754e43e0fa382566b31fe

One possible failure sequence is:

   1.

   An ARP-associated host route generates an RTM_CHANGE notification
   without RTF_LLINFO.
   2.

   OpenBGPD accepts the notification and inserts the /32 as connected.
   3.

   A subsequent notification includes RTF_LLINFO.
   4.

   The notification is ignored by the route-message parser.
   5.

   The internal FIB entry persists and remains redistributed through BGP.

*This is a hypothesis, not a confirmed reproduction.* The original
routing-socket notification was not captured.

Our environment also runs OpenBSD 7.0 on a large number of systems using
similar CARP/BGP designs, without having observed this issue over an
extended period. This operational observation motivated the historical
comparison, but does not independently establish the regression.
Potential applicability to OpenBSD 7.9

The problem has only been observed on OpenBSD 7.8 (OpenBGPD 8.9).

However, source comparison indicates that the relevant kernel-route
handling in usr.sbin/bgpd/kroute.c is unchanged in OpenBSD 7.9 (OpenBGPD
9.1).

In particular, the handling of unknown RTM_CHANGE notifications introduced
in June 2022 remains present.

We therefore suspect that OpenBSD 7.9 could also be affected, although this
has not been experimentally verified.
Workaround / fix

Restarting bgpd on the BACKUP removes the incorrectly originated /32
without removing the ordinary kernel ARP entry.

A targeted outbound BGP filter preventing more-specific host routes from
being advertised out of the CARP-connected subnet is a possible operational
mitigation.

No source-level fix has been identified or verified.
Supporting evidence

A separate sanitized technical evidence extract is provided below. It
contains the relevant kernel routing messages, CARP status, OpenBGPD FIB
and RIB output, and before/after observations.

Full original system logs, infrastructure configuration, hardware
identifiers, internal addressing and unrelated application logs are
intentionally excluded.

OPENBGPD 8.9 / OPENBSD 7.8
SANITIZED ROUTING EVIDENCE

PRIVACY
All addresses are RFC 5737 documentation addresses.
Real IPs, hostnames, ASNs, MAC addresses, service names,
process identifiers and unrelated messages are withheld.
Interface carpX is a placeholder.

SYSTEM
OpenBSD 7.8 GENERIC.MP #54, amd64
OpenBGPD 8.9
KVM virtual machine, VirtIO interfaces

TOPOLOGY
BACKUP:      198.51.100.31
MASTER:      198.51.100.32
iBGP:        BACKUP -> MASTER
CARP subnet: 192.0.2.224/28
CARP VIP:    192.0.2.225
Bad prefix:  192.0.2.230/32


========================================================
1. BEFORE BGPD RESTART - BACKUP
2026-10-26 09:22:26 UTC
========================================================

$ ifconfig carpX

carp: BACKUP
inet 192.0.2.225 netmask 0xfffffff0


$ bgpctl network show inet
[Relevant entries only]

S 0 192.0.2.224/28 0.0.0.0
S 0 192.0.2.230/32 0.0.0.0


$ bgpctl show fib connected
[Relevant entries only]

C  19 192.0.2.224/28 link#6
C 146 192.0.2.230/32 link#6


$ bgpctl show fib 192.0.2.230

C 146 192.0.2.230/32 link#6


$ bgpctl show rib detail 192.0.2.230/32

BGP routing table entry for 192.0.2.230/32
  Nexthop 0.0.0.0 (via 0.0.0.0)
  Neighbor LOCAL (198.51.100.31)
  Origin IGP, metric 0, localpref 150,
  weight 0, internal, valid, best, announced
  Last update: 1d14h53m ago


$ bgpctl show rib out neighbor 198.51.100.32 \
    192.0.2.230/32

flags  vs  destination       gateway        lpref med aspath origin
AI*    N-? 192.0.2.230/32    198.51.100.31  120  0          i


$ route -n get 192.0.2.230

destination: 192.0.2.230
mask: 255.255.255.255
interface: carpX
if address: 192.0.2.225
priority: 18 ()
flags: <HOST,DONE,LLINFO,CLONED>


$ route -n get 192.0.2.229
[Control sample]

destination: 192.0.2.229
interface: carpX
priority: 18 ()
flags: <HOST,DONE,LLINFO,CLONED>


$ arp -an
[Filtered to affected host]

192.0.2.230 (incomplete) carpX expired


========================================================
2. BEFORE RESTART - MASTER
2026-10-26 approximately 08:43:50 UTC
========================================================

$ bgpctl show rib detail 192.0.2.230/32

BGP routing table entry for 192.0.2.230/32
  Nexthop 198.51.100.31 (via 198.51.100.31)
  Neighbor 198.51.100.31 (198.51.100.31)
  Origin IGP, localpref 10,
  internal, valid, best
  Originator Id: 198.51.100.31
  Cluster Id List: 198.51.100.32


========================================================
3. KERNEL ROUTING-SOCKET EVENTS - BACKUP
2026-10-26 09:28:41 UTC
========================================================

RTM_DELETE: Delete Route
  len 192
  priority 146
  table 0
  if# 6
  name carpX
  pid 0
  seq 0
  errno 0

flags:<HOST,DONE,LLINFO,CLONED>
sockaddrs:<DST,GATEWAY,IFP,IFA>

192.0.2.230 link#6 [MAC OMITTED] 192.0.2.225


RTM_ADD: Add Route
  len 192
  priority 146
  table 0
  if# 6
  name carpX
  pid 0
  seq 0
  errno 0

flags:<HOST,DONE,LLINFO,CLONED>
sockaddrs:<DST,GATEWAY,IFP,IFA>

192.0.2.230 link#6 [MAC OMITTED] 192.0.2.225


NOTES
- Other ARP entries in the same subnet produced
  comparable RTF_LLINFO notifications.
- Monitoring started AFTER the original event
  responsible for the erroneous BGP origination.
- These captured events contain RTF_LLINFO.
- This does not establish the original trigger.


========================================================
4. ROUTING MONITOR DESYNCHRONIZATION
2026-10-26 09:30:45 UTC
========================================================

RTM_DESYNC: route socket overflow

NOTE:
Occurred during heavy BGP route churn.
This was observed by route(8)'s monitoring socket.
It does not prove that bgpd's own routing socket
experienced an overflow.


========================================================
5. AFTER BGPD RESTART - BACKUP
2026-10-26 09:30:50 AND 09:31:12 UTC
========================================================

$ bgpctl network show inet | grep -F '192.0.2.230'

[no output]


$ bgpctl show fib connected | grep -F '192.0.2.230'

[no output]


$ bgpctl show rib detail 192.0.2.230/32

[Only covering route 192.0.2.224/28 returned]
[No 192.0.2.230/32 entry]


$ route -n get 192.0.2.230

destination: 192.0.2.230
mask: 255.255.255.255
interface: carpX
if address: 192.0.2.225
priority: 18 ()
flags: <HOST,DONE,LLINFO,CLONED>


$ arp -an
[Filtered to affected host]

192.0.2.230 (incomplete) carpX expired


========================================================
6. APPROXIMATE EVENT CORRELATION
2026-10-24 18:28-18:29 UTC
========================================================

The BGP path's reported Last update age points to
this approximate time window.

Multiple backend health checks failed concurrently
between 18:28:40 and 18:28:44 UTC.

This is correlation only.
The original route-message flags were not captured.
---


Kind regards,
Priit

Reply via email to