Skip to content

Zebra local addresses same network 2#22086

Draft
pguibert6WIND wants to merge 3 commits into
FRRouting:masterfrom
pguibert6WIND:zebra_local_addresses_same_network_2
Draft

Zebra local addresses same network 2#22086
pguibert6WIND wants to merge 3 commits into
FRRouting:masterfrom
pguibert6WIND:zebra_local_addresses_same_network_2

Conversation

@pguibert6WIND
Copy link
Copy Markdown
Member

No description provided.

@frrbot frrbot Bot added bugfix tests Topotests, make check, etc zebra labels May 27, 2026
@pguibert6WIND pguibert6WIND marked this pull request as draft May 27, 2026 15:28
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 27, 2026

Greptile Summary

This PR fixes zebra's handling of multiple connected addresses on the same network by tracking the kernel's promote_secondaries sysctl per-interface and using it to decide whether to retain connected-prefix and local /32 routes when a primary address is deleted.

  • zebra/connected.c: connected_up() and connected_down() are refactored to use install_prefix/remove_prefix flags instead of early returns, so the local-route path is still reached when a prefix-route skip is needed. connected_down() also clears ZEBRA_IFA_SECONDARY on the surviving secondary when the primary is removed and promotion is active.
  • zebra/if_netlink.c / zebra/interface.c: A new parser reads promote_secondaries from RTM_NEWLINK IFLA_AF_SPEC and stores it in the dplane context; zebra_if_dplane_ifp_handling syncs the value into ZIF_FLAG_IPV4_PROMOTE_SECONDARIES on the per-interface zebra_if.
  • Tests: Two new topotests cover the promote-secondaries-on and promote-secondaries-off scenarios end-to-end.

Confidence Score: 3/5

Two real defects in interface.c need fixing before merging: an always-true debug guard that floods logs, and a missing UNSET_FLAG branch that prevents the promote-secondaries flag from ever being cleared.

The always-true if (1 || IS_ZEBRA_DEBUG_KERNEL) guard causes every RTM_NEWLINK to emit a log line in production. More critically, SET_FLAG is called unconditionally on any state change, so toggling promote_secondaries back to 0 via sysctl is never reflected in zebra — the flag stays set permanently, leading to stale connected-route decisions.

zebra/interface.c — the promote_secondaries sync block at line ~2250 needs both the always-true guard and the missing UNSET_FLAG path corrected before this is safe to merge.

Important Files Changed

Filename Overview
zebra/interface.c Adds promote_secondaries tracking and display; contains two bugs: always-true debug guard and missing UNSET_FLAG path so ZIF_FLAG_IPV4_PROMOTE_SECONDARIES can never be cleared.
zebra/connected.c Refactors connected_up/down to skip prefix-route install/removal when other same-network addresses remain; adds secondary-flag promotion logic on primary deletion. Contains an unused zif variable in connected_up().
zebra/if_netlink.c Adds local IPV4_DEVCONF enum and a new function to parse promote_secondaries from RTM_NEWLINK IFLA_AF_SPEC; uses bare magic number 1 for IFLA_INET_CONF.
zebra/zebra_dplane.c Adds promote_secondaries field to dplane_intf_info with standard setter/getter pair; straightforward and correct.
zebra/zebra_dplane.h Declares the two new promote_secondaries dplane accessor prototypes; no issues.
zebra/interface.h Adds ZIF_FLAG_IPV4_PROMOTE_SECONDARIES (bit 7) to zebra_if_flags enum; clean change.
tests/topotests/zebra_multiple_connected/test_zebra_multiple_connected.py Adds two end-to-end topotests covering secondary local /32 route lifecycle with and without promote_secondaries; well structured.

Sequence Diagram

sequenceDiagram
    participant K as Linux Kernel
    participant NL as if_netlink.c
    participant DP as zebra_dplane
    participant IC as interface.c
    participant ZIF as zebra_if (flags)
    participant CD as connected.c

    K->>NL: RTM_NEWLINK (IFLA_AF_SPEC with IFLA_INET_CONF)
    NL->>NL: netlink_interface_promote_secondaries_update()
    NL->>DP: dplane_ctx_set_ifp_promote_secondaries(ctx, val)
    DP->>IC: zebra_if_dplane_ifp_handling(ctx)
    IC->>DP: dplane_ctx_intf_is_promote_secondaries(ctx)
    IC->>ZIF: SET/UNSET ZIF_FLAG_IPV4_PROMOTE_SECONDARIES

    K->>NL: RTM_DELADDR (primary address deleted)
    NL->>CD: connected_down(ifp, ifc)
    CD->>ZIF: CHECK_FLAG(ZIF_FLAG_IPV4_PROMOTE_SECONDARIES)
    alt promote_secondaries enabled AND secondary exists
        CD->>CD: UNSET ZEBRA_IFA_SECONDARY on surviving addr
        CD->>CD: "remove_prefix = false (keep connected route)"
        CD->>CD: remove local /32 for deleted address
    else no surviving address / promotion disabled
        CD->>CD: "remove_prefix = true"
        CD->>CD: rib_delete connected + local routes
    end
Loading
Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
zebra/interface.c:2252-2253
**Debug guard always evaluates to true**

`if (1 || IS_ZEBRA_DEBUG_KERNEL)` short-circuits unconditionally, so this `zlog_debug` fires on every interface link-change event regardless of debug settings. This will flood the system log on any deployment that receives frequent RTM_NEWLINK notifications.

### Issue 2 of 4
zebra/interface.c:2250-2257
**`UNSET_FLAG` path missing for promote_secondaries**

When `promote_secondaries` is `false` and `ZIF_FLAG_IPV4_PROMOTE_SECONDARIES` is currently set, the condition is true (they differ) but the code unconditionally calls `SET_FLAG`, re-setting the flag instead of clearing it. Once the flag is set on an interface it can never be cleared, and zebra will continue treating that interface as having `promote_secondaries` enabled even after the sysctl is disabled — causing incorrect connected-route behavior.

```suggestion
			if (promote_secondaries !=
			    !!CHECK_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES)) {
				if (IS_ZEBRA_DEBUG_KERNEL)
					zlog_debug("Intf %s(%u) has promote secondaries turned %s",
						   name, ifp->ifindex,
						   promote_secondaries ? "on" : "off");
				if (promote_secondaries)
					SET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
				else
					UNSET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
			}
```

### Issue 3 of 4
zebra/connected.c:238-240
**Unused variable `zif` in `connected_up()`**

`zif` is declared but never assigned or referenced in `connected_up()`. It is likely a leftover from a copy-paste from `connected_down()`. While benign, it adds noise and may confuse future readers.

```suggestion
	bool install_local = true;
	bool install_prefix = true;
```

### Issue 4 of 4
zebra/if_netlink.c:812
**Magic number for inner netlink attribute type**

`j->rta_type == 1` refers to `IFLA_INET_CONF` (value 1 in the kernel's `ifla_inet_attr` enum). Defining a named constant alongside the `IPV4_DEVCONF_*` enum already introduced in this PR would make the intent self-documenting.

Reviews (1): Last reviewed commit: "zebra: upon deletion, promote secondarie..." | Re-trigger Greptile

Comment thread zebra/interface.c Outdated
Comment on lines +2252 to +2253
if (1 || IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("Intf %s(%u) has promote secondaries turned %s",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Debug guard always evaluates to true

if (1 || IS_ZEBRA_DEBUG_KERNEL) short-circuits unconditionally, so this zlog_debug fires on every interface link-change event regardless of debug settings. This will flood the system log on any deployment that receives frequent RTM_NEWLINK notifications.

Prompt To Fix With AI
This is a comment left during a code review.
Path: zebra/interface.c
Line: 2252-2253

Comment:
**Debug guard always evaluates to true**

`if (1 || IS_ZEBRA_DEBUG_KERNEL)` short-circuits unconditionally, so this `zlog_debug` fires on every interface link-change event regardless of debug settings. This will flood the system log on any deployment that receives frequent RTM_NEWLINK notifications.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread zebra/interface.c Outdated
Comment on lines +2250 to +2257
if (promote_secondaries !=
!!CHECK_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES)) {
if (1 || IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("Intf %s(%u) has promote secondaries turned %s",
name, ifp->ifindex,
promote_secondaries ? "on" : "off");
SET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 UNSET_FLAG path missing for promote_secondaries

When promote_secondaries is false and ZIF_FLAG_IPV4_PROMOTE_SECONDARIES is currently set, the condition is true (they differ) but the code unconditionally calls SET_FLAG, re-setting the flag instead of clearing it. Once the flag is set on an interface it can never be cleared, and zebra will continue treating that interface as having promote_secondaries enabled even after the sysctl is disabled — causing incorrect connected-route behavior.

Suggested change
if (promote_secondaries !=
!!CHECK_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES)) {
if (1 || IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("Intf %s(%u) has promote secondaries turned %s",
name, ifp->ifindex,
promote_secondaries ? "on" : "off");
SET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
}
if (promote_secondaries !=
!!CHECK_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES)) {
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("Intf %s(%u) has promote secondaries turned %s",
name, ifp->ifindex,
promote_secondaries ? "on" : "off");
if (promote_secondaries)
SET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
else
UNSET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: zebra/interface.c
Line: 2250-2257

Comment:
**`UNSET_FLAG` path missing for promote_secondaries**

When `promote_secondaries` is `false` and `ZIF_FLAG_IPV4_PROMOTE_SECONDARIES` is currently set, the condition is true (they differ) but the code unconditionally calls `SET_FLAG`, re-setting the flag instead of clearing it. Once the flag is set on an interface it can never be cleared, and zebra will continue treating that interface as having `promote_secondaries` enabled even after the sysctl is disabled — causing incorrect connected-route behavior.

```suggestion
			if (promote_secondaries !=
			    !!CHECK_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES)) {
				if (IS_ZEBRA_DEBUG_KERNEL)
					zlog_debug("Intf %s(%u) has promote secondaries turned %s",
						   name, ifp->ifindex,
						   promote_secondaries ? "on" : "off");
				if (promote_secondaries)
					SET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
				else
					UNSET_FLAG(zif->flags, ZIF_FLAG_IPV4_PROMOTE_SECONDARIES);
			}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread zebra/connected.c Outdated
Comment on lines +238 to +240
bool install_local = true;
bool install_prefix = true;
struct zebra_if *zif;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unused variable zif in connected_up()

zif is declared but never assigned or referenced in connected_up(). It is likely a leftover from a copy-paste from connected_down(). While benign, it adds noise and may confuse future readers.

Suggested change
bool install_local = true;
bool install_prefix = true;
struct zebra_if *zif;
bool install_local = true;
bool install_prefix = true;
Prompt To Fix With AI
This is a comment left during a code review.
Path: zebra/connected.c
Line: 238-240

Comment:
**Unused variable `zif` in `connected_up()`**

`zif` is declared but never assigned or referenced in `connected_up()`. It is likely a leftover from a copy-paste from `connected_down()`. While benign, it adds noise and may confuse future readers.

```suggestion
	bool install_local = true;
	bool install_prefix = true;
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread zebra/if_netlink.c Outdated
continue;
for (j = RTA_DATA(i), rem2 = RTA_PAYLOAD(i); RTA_OK(j, rem2);
j = RTA_NEXT(j, rem2)) {
if (j->rta_type == 1) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Magic number for inner netlink attribute type

j->rta_type == 1 refers to IFLA_INET_CONF (value 1 in the kernel's ifla_inet_attr enum). Defining a named constant alongside the IPV4_DEVCONF_* enum already introduced in this PR would make the intent self-documenting.

Prompt To Fix With AI
This is a comment left during a code review.
Path: zebra/if_netlink.c
Line: 812

Comment:
**Magic number for inner netlink attribute type**

`j->rta_type == 1` refers to `IFLA_INET_CONF` (value 1 in the kernel's `ifla_inet_attr` enum). Defining a named constant alongside the `IPV4_DEVCONF_*` enum already introduced in this PR would make the intent self-documenting.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@pguibert6WIND pguibert6WIND force-pushed the zebra_local_addresses_same_network_2 branch 2 times, most recently from eaf5ed0 to 33941a4 Compare May 27, 2026 15:55
@github-actions
Copy link
Copy Markdown

This pull request has conflicts, please resolve those before we can evaluate the pull request.

On a setup with 4.4.4.4/24 address present, adding 4.4.4.40/24 address
results in unsyncing local entry 4.4.4.40/32.

The output on vtysh differs from the output on shell.

> # vtysh -c "show ip route" | grep 4.4.4.
> C>* 4.4.4.0/24 is directly connected, loo1, weight 1, 00:00:08
> L>* 4.4.4.4/32 is directly connected, loo1, weight 1, 00:00:08

> # ip route show table 255 | grep 4.4.4.
> local 4.4.4.4 dev loo1 proto kernel scope host src 4.4.4.4
> local 4.4.4.40 dev loo1 proto kernel scope host src 4.4.4.4
> broadcast 4.4.4.255 dev loo1 proto kernel scope link src 4.4.4.4

Consequently, using the secondary address for routing purposes may be
mishandled by ZEBRA. Fix this by keeping all the local entries.

Fixes: d4aa24b ("*: Introduce Local Host Routes to FRR")

Signed-off-by: Philippe Guibert <philippe.guibert@6wind.com>
When deleting a primary address on an interface with promoted
secondaries turned on, the move of the address from secondary to primary
is shared to other daemons by an address delete/add notification.

> # ip a a 192.168.0.1/24 dev loo2
> 2026/05/28 10:21:54 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo2(5), addr 192.168.0.1/24
> 2026/05/28 10:21:54 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 192.168.0.1/24 on loo2 vrf default(0)
> # ip a a 192.168.0.2/24 dev loo2
> 2026/05/28 10:21:57 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo2(5), addr 192.168.0.2/24
> 2026/05/28 10:21:57 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 192.168.0.2/24 on loo2 vrf default(0)
> # ip a a 192.168.0.3/24 dev loo2
> 2026/05/28 10:21:59 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo2(5), addr 192.168.0.3/24
> 2026/05/28 10:21:59 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 192.168.0.3/24 on loo2 vrf default(0)
> # ip a a 192.168.0.4/24 dev loo2
> 2026/05/28 10:22:04 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo2(5), addr 192.168.0.4/24
> 2026/05/28 10:22:04 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 192.168.0.4/24 on loo2 vrf default(0)
> # ip a d 192.168.0.3/24 dev loo2
> 2026/05/28 10:22:15 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_DEL: ifindex loo2(5), addr 192.168.0.3/24
> 2026/05/28 10:22:15 ZEBRA: [XN0NB-2NSYE] MESSAGE: ZEBRA_INTERFACE_ADDRESS_DELETE 192.168.0.3/24 on loo2 vrf default(0)
> # ip a d 192.168.0.1/24 dev loo2
> 2026/05/28 10:22:27 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_DEL: ifindex loo2(5), addr 192.168.0.1/24
> 2026/05/28 10:22:27 ZEBRA: [XN0NB-2NSYE] MESSAGE: ZEBRA_INTERFACE_ADDRESS_DELETE 192.168.0.1/24 on loo2 vrf default(0)
> 2026/05/28 10:22:27 ZEBRA: [XN0NB-2NSYE] MESSAGE: ZEBRA_INTERFACE_ADDRESS_DELETE 192.168.0.2/24 on loo2 vrf default(0)
> 2026/05/28 10:22:27 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 192.168.0.2/24 on loo2 vrf default(0)
> 2026/05/28 10:22:27 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo2(5), addr 192.168.0.2/24
>

When such interface has promoted secondaries flag turned off, the
addresses deletion are being notified and comply with expected behavior.
Note the order of events: address deletion notify the secondary first.

> # ip a a 172.31.0.1/24 dev loo1
> 2026/05/28 10:20:48 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo1(4), addr 172.31.0.1/24
> 2026/05/28 10:20:48 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 172.31.0.1/24 on loo1 vrf default(0)
> # ip a a 172.31.0.2/24 dev loo1
> 2026/05/28 10:20:50 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo1(4), addr 172.31.0.2/24
> 2026/05/28 10:20:50 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 172.31.0.2/24 on loo1 vrf default(0)
> # ip a a 172.31.0.3/24 dev loo1
> 2026/05/28 10:20:54 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_ADD: ifindex loo1(4), addr 172.31.0.3/24
> 2026/05/28 10:20:54 ZEBRA: [NBGDS-5ZDQ9] MESSAGE: ZEBRA_INTERFACE_ADDRESS_ADD 172.31.0.3/24 on loo1 vrf default(0)
> # ip a d 172.31.0.3/24 dev loo1
> 2026/05/28 10:21:13 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_DEL: ifindex loo1(4), addr 172.31.0.3/24
> 2026/05/28 10:21:13 ZEBRA: [XN0NB-2NSYE] MESSAGE: ZEBRA_INTERFACE_ADDRESS_DELETE 172.31.0.3/24 on loo1 vrf default(0)
> # ip a d 172.31.0.1/24 dev loo1
> 2026/05/28 10:21:20 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_DEL: ifindex loo1(4), addr 172.31.0.2/24
> 2026/05/28 10:21:20 ZEBRA: [XN0NB-2NSYE] MESSAGE: ZEBRA_INTERFACE_ADDRESS_DELETE 172.31.0.2/24 on loo1 vrf default(0)
> 2026/05/28 10:21:20 ZEBRA: [MZPZA-W042K] zebra_if_addr_update_ctx: INTF_ADDR_DEL: ifindex loo1(4), addr 172.31.0.1/24
> 2026/05/28 10:21:20 ZEBRA: [XN0NB-2NSYE] MESSAGE: ZEBRA_INTERFACE_ADDRESS_DELETE 172.31.0.1/24 on loo1 vrf default(0)

This behavior is correct, and does not require any further changes with
the kernel. Modify the comment, remove the XXX.

Fixes: 02b4805 ("zebra: don't change connected state from zebra/interface.c")

Signed-off-by: Philippe Guibert <philippe.guibert@6wind.com>
@pguibert6WIND pguibert6WIND force-pushed the zebra_local_addresses_same_network_2 branch from 33941a4 to 38d259e Compare May 28, 2026 08:44
Add two test cases to test_zebra_multiple_connected.py exercising
secondary IPv4 address local routes RIB addition handling at runtime
on an interface that is already UP:

- test_zebra_secondary_address_local_route: with
  promote_secondaries=1, verify both the primary and secondary local
  /32 routes are present after add, and that the deleted primary's
  /32 is removed (while the surviving secondary's /32 stays) after
  the kernel promotes it.

- test_zebra_secondary_address_no_promote: set promote_secondaries=0.
  After the primary is deleted the kernel removes the secondary too,
  and zebra's RIB must end up with neither /32.

These exercised the two zebra connected.c fixes from the preceding
commits.

Signed-off-by: Nelson Lako Tangala <nelson.lako_tangala@6wind.com>
@github-actions github-actions Bot added size/L and removed size/S labels May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants