-
Notifications
You must be signed in to change notification settings - Fork 23
infra: send GARP and NA on MAC address change #473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
📝 WalkthroughWalkthroughAdds a new interface event constant 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. Comment |
There was a problem hiding this 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 emitsGR_EVENT_IFACE_MAC_CHANGEon success, but the infraiface_eventhandler doesn’t subscribe to (or forward) this event toiface->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 portsDuring VLAN reconfig, when
GR_VLAN_SET_MACis set you doiface_del_eth_addr(cur_parent, &cur->mac)(Line 49-53). With the newiface_vlan_set_eth_addr()“inherit from parent” path,cur->maccan 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
📒 Files selected for processing (7)
modules/infra/api/gr_infra.hmodules/infra/control/bond.cmodules/infra/control/iface.cmodules/infra/control/port.cmodules/infra/control/vlan.cmodules/ip/control/address.cmodules/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 setsx = NULL. There is no risk of double free.ec_node_*()functions consume theirec_nodearguments. No leaks on error.rte_node->ctxis 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.hmodules/infra/control/iface.cmodules/infra/control/bond.cmodules/infra/control/vlan.cmodules/ip6/control/address.cmodules/ip/control/address.cmodules/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.hmodules/infra/control/iface.cmodules/ip6/control/address.cmodules/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 addedGR_EVENT_IFACE_MAC_CHANGEhandling/subscription.Also applies to: 473-481
modules/infra/control/port.c (1)
333-334: No critical issues spotted in the switch toiface_set_eth_addr()for port MAC reconfig.modules/infra/control/bond.c (1)
356-367: No critical issues spotted in the switch toiface_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 addingGR_EVENT_IFACE_MAC_CHANGE.
9424ace to
cc677f9
Compare
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>
cc677f9 to
cceea80
Compare
There was a problem hiding this 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
📒 Files selected for processing (11)
modules/infra/api/gr_infra.hmodules/infra/api/iface.cmodules/infra/cli/iface.cmodules/infra/control/bond.cmodules/infra/control/iface.cmodules/infra/control/port.cmodules/infra/control/port_test.cmodules/infra/control/vlan.cmodules/infra/control/worker_test.cmodules/ip/control/address.cmodules/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 setsx = NULL. There is no risk of double free.ec_node_*()functions consume theirec_nodearguments. No leaks on error.rte_node->ctxis 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.cmodules/infra/control/iface.cmodules/infra/cli/iface.cmodules/infra/api/iface.cmodules/ip/control/address.cmodules/infra/control/vlan.cmodules/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.cmodules/infra/control/iface.cmodules/infra/cli/iface.cmodules/infra/api/iface.cmodules/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
| case GR_EVENT_IFACE_MAC_CHANGE: | ||
| str = "MAC_CHANGE"; | ||
| gr_vec_foreach (struct iface *s, iface->subinterfaces) | ||
| gr_event_push(event, s); | ||
| break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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).
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 -->
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
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.