Skip to content

Conversation

@rjarry
Copy link
Collaborator

@rjarry rjarry commented Jan 9, 2026

Add a GR_EVENT_IFACE_MAC_CHANGE event that is pushed when an interface MAC address is successfully changed via iface_set_eth_addr().

Refactor bond and port reconfig handlers to use iface_set_eth_addr() instead of calling the type-specific callback directly. Implement set_eth_addr for VLAN interfaces.

Subscribe IPv4 and IPv6 address handlers to the new event so that gratuitous ARP and neighbor advertisements are sent when the MAC changes. This allows neighbors to update their caches without waiting for stale entries to expire.

Summary by CodeRabbit

  • New Features

    • Interface event for MAC address changes added so MAC updates are announced system-wide.
  • Bug Fixes

    • MAC updates now apply via the unified interface setter and propagate to dependent components (including IP/IPv6 paths) for consistent behavior.
    • VLAN/port/bond MAC handling streamlined to use the generic setter.
  • Tests

    • Added test helpers/no-op setters to exercise MAC-setting paths.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Adds a new interface event constant GR_EVENT_IFACE_MAC_CHANGE. Introduces iface_set_eth_addr() (with no-op stubs in tests) and updates bond, port, and VLAN reconfiguration paths to call it; VLAN implements iface_vlan_set_eth_addr() and wires it into its iface_type. iface_set_eth_addr() emits GR_EVENT_IFACE_MAC_CHANGE on success and the iface event handling now propagates MAC changes to subinterfaces. API/CLI/serializers and IPv4/IPv6 modules subscribe to and handle the new event.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
modules/infra/control/iface.c (1)

296-313: Potential functional gap: MAC change event won’t reach subinterfaces (VLAN, etc.)

iface_set_eth_addr() now emits GR_EVENT_IFACE_MAC_CHANGE on success, but the infra iface_event handler doesn’t subscribe to (or forward) this event to iface->subinterfaces. If a parent’s MAC change affects neighbors’ reachability for IPs hosted on subinterfaces, they may not send GARP/NA, defeating the PR’s goal for those IPs.

Proposed fix (subscribe + forward MAC_CHANGE to subinterfaces)
 static void iface_event(uint32_t event, const void *obj) {
 	const struct iface *iface = obj;
 	char *str = "";
 	switch (event) {
+	case GR_EVENT_IFACE_MAC_CHANGE:
+		str = "MAC_CHANGE";
+		gr_vec_foreach (struct iface *s, iface->subinterfaces)
+			gr_event_push(event, s);
+		break;
 	case GR_EVENT_IFACE_ADD:
 		str = "ADD";
 		break;
 	...
 	}
 	LOG(DEBUG, "iface event [0x%08x] %s triggered for iface %s.", event, str, iface->name);
 }

 static struct gr_event_subscription iface_event_handler = {
 	.callback = iface_event,
-	.ev_count = 7,
+	.ev_count = 8,
 	.ev_types = {
 		GR_EVENT_IFACE_ADD,
 		GR_EVENT_IFACE_POST_ADD,
 		GR_EVENT_IFACE_PRE_REMOVE,
 		GR_EVENT_IFACE_REMOVE,
 		GR_EVENT_IFACE_POST_RECONFIG,
 		GR_EVENT_IFACE_STATUS_UP,
 		GR_EVENT_IFACE_STATUS_DOWN,
+		GR_EVENT_IFACE_MAC_CHANGE,
 	},
 };
modules/infra/control/vlan.c (1)

34-98: Critical: VLAN MAC reconfig can end up deleting a bond parent’s primary MAC from member ports

During VLAN reconfig, when GR_VLAN_SET_MAC is set you do iface_del_eth_addr(cur_parent, &cur->mac) (Line 49-53). With the new iface_vlan_set_eth_addr() “inherit from parent” path, cur->mac can be the parent’s primary MAC. If the parent is a bond, bond_mac_del() will remove that MAC from all member ports, potentially breaking the bond.

Minimal mitigation: don’t delete the parent’s primary MAC
 	if (reconfig) {
 		if ((cur_parent = iface_from_id(cur->parent_id)) == NULL)
 			return -errno;
 		if (set_attrs & GR_VLAN_SET_MAC) {
 			// reconfig, *not initial config*
-			// remove previous mac filter (ignore errors)
-			iface_del_eth_addr(cur_parent, &cur->mac);
+			// remove previous mac filter (ignore errors), but never delete parent's primary MAC
+			struct rte_ether_addr parent_mac;
+			if (iface_get_eth_addr(cur_parent, &parent_mac) == 0
+			    && rte_is_same_ether_addr(&parent_mac, &cur->mac)) {
+				/* inherited/primary MAC: nothing to delete */
+			} else {
+				iface_del_eth_addr(cur_parent, &cur->mac);
+			}
 		}
 	} else {
 		cur_parent = NULL;
 	}

This still leaves an edge case if the VLAN was previously “inherited” but cached an old parent MAC (e.g., parent MAC changed); the robust fix is to track whether the VLAN MAC was explicitly added to the parent (e.g., a boolean in the VLAN private info) and only delete in that case.

Also applies to: 153-168

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c10bc6 and 9424ace.

📒 Files selected for processing (7)
  • modules/infra/api/gr_infra.h
  • modules/infra/control/bond.c
  • modules/infra/control/iface.c
  • modules/infra/control/port.c
  • modules/infra/control/vlan.c
  • modules/ip/control/address.c
  • modules/ip6/control/address.c
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{c,h}

⚙️ CodeRabbit configuration file

**/*.{c,h}: - gr_vec_*() functions cannot fail. No need to check their return value.

  • gr_vec_free(x) always sets x = NULL. There is no risk of double free.
  • ec_node_*() functions consume their ec_node arguments. No leaks on error.
  • rte_node->ctx is an uint8_t array of size 16, not a pointer.
  • Never suggest to replace assert() with graceful error checking.
  • We compile with -std=gnu2x. Unnamed parameters in function definitions are valid.

Files:

  • modules/infra/api/gr_infra.h
  • modules/infra/control/iface.c
  • modules/infra/control/bond.c
  • modules/infra/control/vlan.c
  • modules/ip6/control/address.c
  • modules/ip/control/address.c
  • modules/infra/control/port.c
🧠 Learnings (3)
📓 Common learnings
Learnt from: christophefontaine
Repo: DPDK/grout PR: 466
File: modules/srv6/datapath/l2_encap.c:26-32
Timestamp: 2025-12-17T17:32:21.746Z
Learning: The GR_MBUF_PRIV_DATA_TYPE macro in modules/infra/datapath/gr_mbuf.h automatically adds `const struct iface *iface` as the first field to any structure defined with it. All types defined using this macro (e.g., srv6_dx2_mbuf_data, mbuf_data, queue_mbuf_data) will have the iface field available, followed by any custom fields provided as the second macro argument.
📚 Learning: 2025-12-17T17:32:21.746Z
Learnt from: christophefontaine
Repo: DPDK/grout PR: 466
File: modules/srv6/datapath/l2_encap.c:26-32
Timestamp: 2025-12-17T17:32:21.746Z
Learning: The GR_MBUF_PRIV_DATA_TYPE macro in modules/infra/datapath/gr_mbuf.h automatically adds `const struct iface *iface` as the first field to any structure defined with it. All types defined using this macro (e.g., srv6_dx2_mbuf_data, mbuf_data, queue_mbuf_data) will have the iface field available, followed by any custom fields provided as the second macro argument.

Applied to files:

  • modules/infra/api/gr_infra.h
  • modules/infra/control/iface.c
  • modules/ip6/control/address.c
  • modules/ip/control/address.c
📚 Learning: 2025-10-21T15:42:43.874Z
Learnt from: rjarry
Repo: DPDK/grout PR: 350
File: modules/ip/control/address.c:214-216
Timestamp: 2025-10-21T15:42:43.874Z
Learning: In C code compiled with `-std=gnu2x`, the gr_vec_foreach macro supports inline variable declarations (e.g., `gr_vec_foreach (struct nexthop *nh, vector)`). This is valid C2x syntax and does not require pre-declaring the loop variable.

Applied to files:

  • modules/ip6/control/address.c
🧬 Code graph analysis (3)
modules/infra/control/iface.c (3)
modules/infra/control/gr_iface.h (1)
  • iface (16-25)
modules/infra/control/port_test.c (1)
  • gr_event_push (24-24)
modules/infra/control/worker_test.c (1)
  • gr_event_push (37-37)
modules/infra/control/vlan.c (1)
modules/infra/control/iface.c (2)
  • iface_set_eth_addr (296-313)
  • iface_from_id (256-263)
modules/infra/control/port.c (2)
modules/infra/control/iface.c (1)
  • iface_set_eth_addr (296-313)
modules/infra/control/gr_iface.h (1)
  • iface (16-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: build-and-tests (clang-15, none, debugoptimized, ubuntu-22.04, false)
  • GitHub Check: build-and-tests (clang-18, none, debugoptimized, ubuntu-24.04, false)
  • GitHub Check: build-and-tests (gcc-13, none, debugoptimized, ubuntu-24.04, -Dfrr=enabled, false)
  • GitHub Check: build-and-tests (clang-16, none, debugoptimized, ubuntu-24.04, false)
  • GitHub Check: build-and-tests (gcc-14, address, debug, ubuntu-24.04, -Dfrr=enabled, true)
  • GitHub Check: rpm
  • GitHub Check: deb
🔇 Additional comments (5)
modules/ip6/control/address.c (1)

421-428: No critical issues spotted in the added GR_EVENT_IFACE_MAC_CHANGE handling/subscription.

Also applies to: 473-481

modules/infra/control/port.c (1)

333-334: No critical issues spotted in the switch to iface_set_eth_addr() for port MAC reconfig.

modules/infra/control/bond.c (1)

356-367: No critical issues spotted in the switch to iface_set_eth_addr() for bond MAC reconfig.

modules/ip/control/address.c (1)

271-275: No critical issues spotted in the updated IPv4 iface event subscription/serializer counts.

Also applies to: 276-280

modules/infra/api/gr_infra.h (1)

205-216: No critical issues spotted in adding GR_EVENT_IFACE_MAC_CHANGE.

When an interface MAC address changes, neighbors must be notified so
they can update their caches without waiting for stale entries to
expire.

Introduce GR_EVENT_IFACE_MAC_CHANGE which is pushed from
iface_set_eth_addr() on success. Have bond and port reconfig handlers
call the generic iface_set_eth_addr() wrapper instead of their
type-specific callbacks. Implement set_eth_addr for VLAN interfaces.

Subscribe IPv4 and IPv6 address handlers to this event to trigger
gratuitous ARP and unsolicited neighbor advertisements respectively.

Signed-off-by: Robin Jarry <rjarry@redhat.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @modules/infra/control/vlan.c:
- Around line 97-100: The VLAN MAC handling currently copies the parent MAC and
loses the “inherit” semantics, and lacks NULL checks; add a boolean inherit_mac
(or similar) to struct iface_info_vlan, set it instead of copying when the
requested MAC is 00:00:00:00:00:00 in iface_vlan_set_eth_addr(), validate that
parent and mac pointers are non-NULL before using them, update
iface_vlan_get_eth_addr() to return the parent’s current MAC dynamically when
inherit_mac==true (only return stored vlan->mac when inherit_mac==false), and
ensure iface_vlan_fini() and the reconfig delete path only call
iface_del_eth_addr(parent, &vlan->mac) when inherit_mac==false so you never try
to delete an inherited/parent primary address.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc677f9 and cceea80.

📒 Files selected for processing (11)
  • modules/infra/api/gr_infra.h
  • modules/infra/api/iface.c
  • modules/infra/cli/iface.c
  • modules/infra/control/bond.c
  • modules/infra/control/iface.c
  • modules/infra/control/port.c
  • modules/infra/control/port_test.c
  • modules/infra/control/vlan.c
  • modules/infra/control/worker_test.c
  • modules/ip/control/address.c
  • modules/ip6/control/address.c
🚧 Files skipped from review as they are similar to previous changes (4)
  • modules/infra/control/port_test.c
  • modules/infra/api/gr_infra.h
  • modules/infra/control/worker_test.c
  • modules/infra/control/port.c
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{c,h}

⚙️ CodeRabbit configuration file

**/*.{c,h}: - gr_vec_*() functions cannot fail. No need to check their return value.

  • gr_vec_free(x) always sets x = NULL. There is no risk of double free.
  • ec_node_*() functions consume their ec_node arguments. No leaks on error.
  • rte_node->ctx is an uint8_t array of size 16, not a pointer.
  • Never suggest to replace assert() with graceful error checking.
  • We compile with -std=gnu2x. Unnamed parameters in function definitions are valid.

Files:

  • modules/ip6/control/address.c
  • modules/infra/control/iface.c
  • modules/infra/cli/iface.c
  • modules/infra/api/iface.c
  • modules/ip/control/address.c
  • modules/infra/control/vlan.c
  • modules/infra/control/bond.c
🧠 Learnings (3)
📓 Common learnings
Learnt from: christophefontaine
Repo: DPDK/grout PR: 466
File: modules/srv6/datapath/l2_encap.c:26-32
Timestamp: 2025-12-17T17:32:21.746Z
Learning: The GR_MBUF_PRIV_DATA_TYPE macro in modules/infra/datapath/gr_mbuf.h automatically adds `const struct iface *iface` as the first field to any structure defined with it. All types defined using this macro (e.g., srv6_dx2_mbuf_data, mbuf_data, queue_mbuf_data) will have the iface field available, followed by any custom fields provided as the second macro argument.
Learnt from: maxime-leroy
Repo: DPDK/grout PR: 372
File: smoke/cross_vrf_forward_test.sh:18-18
Timestamp: 2025-11-05T13:55:26.189Z
Learning: In the DPDK/grout codebase, VRF interfaces (named gr-vrf<id>) are automatically created when an interface is added to a non-existing VRF using port_add. The VRF creation is handled automatically by the event system in vrf_netlink.c, so no explicit VRF interface creation commands are needed in test scripts.
📚 Learning: 2025-12-17T17:32:21.746Z
Learnt from: christophefontaine
Repo: DPDK/grout PR: 466
File: modules/srv6/datapath/l2_encap.c:26-32
Timestamp: 2025-12-17T17:32:21.746Z
Learning: The GR_MBUF_PRIV_DATA_TYPE macro in modules/infra/datapath/gr_mbuf.h automatically adds `const struct iface *iface` as the first field to any structure defined with it. All types defined using this macro (e.g., srv6_dx2_mbuf_data, mbuf_data, queue_mbuf_data) will have the iface field available, followed by any custom fields provided as the second macro argument.

Applied to files:

  • modules/ip6/control/address.c
  • modules/infra/control/iface.c
  • modules/infra/cli/iface.c
  • modules/infra/api/iface.c
  • modules/ip/control/address.c
📚 Learning: 2025-10-21T15:42:43.874Z
Learnt from: rjarry
Repo: DPDK/grout PR: 350
File: modules/ip/control/address.c:214-216
Timestamp: 2025-10-21T15:42:43.874Z
Learning: In C code compiled with `-std=gnu2x`, the gr_vec_foreach macro supports inline variable declarations (e.g., `gr_vec_foreach (struct nexthop *nh, vector)`). This is valid C2x syntax and does not require pre-declaring the loop variable.

Applied to files:

  • modules/ip6/control/address.c
🧬 Code graph analysis (3)
modules/infra/control/iface.c (1)
modules/infra/control/gr_iface.h (1)
  • iface (16-25)
modules/infra/control/vlan.c (3)
modules/infra/control/iface.c (5)
  • iface_get_eth_addr (265-277)
  • iface_del_eth_addr (329-341)
  • iface_set_eth_addr (296-313)
  • iface_from_id (256-263)
  • iface_add_eth_addr (315-327)
modules/infra/control/port_test.c (1)
  • iface_set_eth_addr (31-33)
modules/infra/control/worker_test.c (2)
  • iface_set_eth_addr (60-62)
  • iface_from_id (45-47)
modules/infra/control/bond.c (3)
modules/infra/control/iface.c (1)
  • iface_set_eth_addr (296-313)
modules/infra/control/port_test.c (1)
  • iface_set_eth_addr (31-33)
modules/infra/control/gr_iface.h (1)
  • iface (16-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: build-and-tests (clang-16, none, debugoptimized, ubuntu-24.04, false)
  • GitHub Check: build-and-tests (gcc-13, none, debugoptimized, ubuntu-24.04, -Dfrr=enabled, false)
  • GitHub Check: build-and-tests (clang-18, none, debugoptimized, ubuntu-24.04, false)
  • GitHub Check: build-and-tests (clang-15, none, debugoptimized, ubuntu-22.04, false)
  • GitHub Check: build-and-tests (gcc-14, address, debug, ubuntu-24.04, -Dfrr=enabled, true)
  • GitHub Check: rpm
  • GitHub Check: deb

Comment on lines +520 to +524
case GR_EVENT_IFACE_MAC_CHANGE:
str = "MAC_CHANGE";
gr_vec_foreach (struct iface *s, iface->subinterfaces)
gr_event_push(event, s);
break;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

MAC_CHANGE propagation can trigger GARP/NA with a stale L2 source on inheriting subinterfaces (e.g., VLAN).

GR_EVENT_IFACE_MAC_CHANGE is forwarded to iface->subinterfaces (Line 520-524), but nothing here ensures subinterfaces that inherit the parent MAC refresh their effective MAC before IPv4/IPv6 handlers react. With the current VLAN implementation, iface_vlan_get_eth_addr() returns a cached vlan->mac, so a parent MAC change can cause neighbors to be “updated” with the old MAC (defeating the PR’s goal and potentially blackholing traffic).

Fix needs to ensure “inheriting” subifaces resolve the current parent MAC when handling MAC_CHANGE (either by making their get_eth_addr dynamic when inheriting, or by refreshing their cached MAC before emitting/handling the event).

Comment on lines 97 to 100
if (set_attrs & GR_VLAN_SET_MAC) {
struct iface *parent = iface_from_id(cur->parent_id);
if (rte_is_zero_ether_addr(&next->mac)) {
if ((ret = iface_get_eth_addr(parent, &cur->mac)) < 0)
return ret;
} else {
if ((ret = iface_add_eth_addr(parent, &next->mac)) < 0)
return ret;
cur->mac = next->mac;
}
if ((ret = iface_set_eth_addr(iface, &next->mac)) < 0)
return ret;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

VLAN MAC inheritance is cached, so parent MAC changes can make VLAN advertise the wrong MAC (plus missing NULL guards).

iface_vlan_set_eth_addr() (Line 158-173) copies the parent MAC into vlan->mac when “inherit” is requested (00:00:00:00:00:00). That loses the “inherit” intent and makes iface_vlan_get_eth_addr() return stale data after a parent MAC change—exactly when this PR now triggers GARP/NA.

Also, parent and mac aren’t validated here; if this is ever called with a missing parent or a NULL mac, behavior degrades to wrong errno or a crash.

A robust fix is to persist “inherit” vs “explicit” in VLAN private state and make get_eth_addr dynamic when inheriting (and only add/del parent filters when explicit).

Proposed direction (requires adding an inherit flag in struct iface_info_vlan)
 static int iface_vlan_get_eth_addr(const struct iface *iface, struct rte_ether_addr *mac) {
 	const struct iface_info_vlan *vlan = iface_info_vlan(iface);
-	*mac = vlan->mac;
-	return 0;
+	if (vlan->inherit_mac) {
+		const struct iface *parent = iface_from_id(vlan->parent_id);
+		return iface_get_eth_addr(parent, mac);
+	}
+	*mac = vlan->mac;
+	return 0;
 }
 
 static int iface_vlan_set_eth_addr(struct iface *iface, const struct rte_ether_addr *mac) {
 	struct iface_info_vlan *vlan = iface_info_vlan(iface);
 	struct iface *parent = iface_from_id(vlan->parent_id);
 	int ret;
 
+	if (mac == NULL)
+		return errno_set(EINVAL);
+	if (parent == NULL)
+		return -errno; /* preserve ENODEV from iface_from_id */
+
 	if (rte_is_zero_ether_addr(mac)) {
-		if ((ret = iface_get_eth_addr(parent, &vlan->mac)) < 0)
-			return ret;
+		vlan->inherit_mac = true;
+		/* keep vlan->mac as last-known cache if you want, but don't rely on it */
+		return 0;
 	} else {
+		vlan->inherit_mac = false;
 		if ((ret = iface_add_eth_addr(parent, mac)) < 0)
 			return ret;
 		vlan->mac = *mac;
 	}
 
 	return 0;
 }

You’ll also want to ensure iface_vlan_fini() and the reconfig delete path only call iface_del_eth_addr(parent, &vlan->mac) when inherit_mac == false (otherwise you risk trying to delete the parent’s primary/inherited MAC).

</details>




Also applies to: 158-173, 212-213

<details>
<summary>🤖 Prompt for AI Agents</summary>

In @modules/infra/control/vlan.c around lines 97 - 100, The VLAN MAC handling
currently copies the parent MAC and loses the “inherit” semantics, and lacks
NULL checks; add a boolean inherit_mac (or similar) to struct iface_info_vlan,
set it instead of copying when the requested MAC is 00:00:00:00:00:00 in
iface_vlan_set_eth_addr(), validate that parent and mac pointers are non-NULL
before using them, update iface_vlan_get_eth_addr() to return the parent’s
current MAC dynamically when inherit_mac==true (only return stored vlan->mac
when inherit_mac==false), and ensure iface_vlan_fini() and the reconfig delete
path only call iface_del_eth_addr(parent, &vlan->mac) when inherit_mac==false so
you never try to delete an inherited/parent primary address.


</details>

<!-- This is an auto-generated comment by CodeRabbit -->

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant