On 8/6/26 8:40 AM, Ales Musil via dev wrote:
> When always_learn_from_arp_request=false we still allow to learn from
> requests targeted to the LRP own address. This was working for IPv4,
> but not for IPv6. Add missing flows to make sure it works for both
> IP families.
>
> Fixes: 783337b24c3f ("Learn the mac binding only if required")
> Reported-at: https://redhat.atlassian.net/browse/FDP-4201
> Assisted-by: Claude Opus 4.6, OpenCode
> Signed-off-by: Ales Musil <[email protected]>
> ---
Hi Ales,
Thanks for the fix!
> northd/northd.c | 62 +++++++++++++++++----
> tests/ovn-northd.at | 10 +++-
> tests/ovn.at | 130 ++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 189 insertions(+), 13 deletions(-)
>
> diff --git a/northd/northd.c b/northd/northd.c
> index 21eb70141..8cd85a328 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -15017,15 +15017,6 @@ build_neigh_learning_flows_for_lrouter(
> ds_cstr(actions), lflow_ref);
> }
>
> - ds_clear(actions);
> - ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
> - " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
> - learn_from_arp_request ? "" :
> - REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
> - " = lookup_nd_ip(inport, ip6.src); ");
> - ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns",
> - ds_cstr(actions), lflow_ref);
> -
> /* For other packet types, we can skip neighbor learning.
> * So set REGBIT_LOOKUP_NEIGHBOR_RESULT to 1. */
> ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1",
> @@ -15089,7 +15080,7 @@ build_neigh_learning_flows_for_lrouter_port(
> "always_learn_from_arp_request", true);
>
> /* Check if we need to learn mac-binding from ARP requests. */
> - for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> + for (size_t i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> if (!learn_from_arp_request) {
> /* ARP request to this address should always get learned,
> * so add a priority-110 flow to set
> @@ -15135,6 +15126,57 @@ build_neigh_learning_flows_for_lrouter_port(
> WITH_HINT(&op->nbrp->header_));
> }
>
> + /* Check if we need to learn mac-binding from ND NS. */
> + for (size_t i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
> + if (in6_is_lla(&op->lrp_networks.ipv6_addrs[i].addr)) {
> + continue;
> + }
> +
> + if (!learn_from_arp_request) {
> + /* ND NS request to this address should always get learned,
> + * so add a priority-110 flow to set
> + * REGBIT_LOOKUP_NEIGHBOR_IP_RESULT to 1. */
> + ds_clear(match);
> + ds_put_format(match,
> + "inport == %s && ip6.src == %s/%u && "
> + "nd.target == %s && nd_ns",
> + op->json_key,
> + op->lrp_networks.ipv6_addrs[i].network_s,
> + op->lrp_networks.ipv6_addrs[i].plen,
> + op->lrp_networks.ipv6_addrs[i].addr_s);
> + if (lrp_is_l3dgw(op)) {
> + ds_put_format(match, " && is_chassis_resident(%s)",
> + op->cr_port->json_key);
> + }
> + const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
> + " = lookup_nd(inport, ip6.src, nd.sll); "
> + REGBIT_LOOKUP_NEIGHBOR_IP_RESULT" = 1;"
> + " next;";
Nit: the indentation is a bit weird here but it matches what we have for
IPv4, let's keep it I guess.
> + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 110,
> + ds_cstr(match), actions_s, lflow_ref,
> + WITH_HINT(&op->nbrp->header_));
> + }
> + ds_clear(match);
> + ds_put_format(match,
> + "inport == %s && ip6.src == %s/%u && nd_ns",
> + op->json_key,
> + op->lrp_networks.ipv6_addrs[i].network_s,
> + op->lrp_networks.ipv6_addrs[i].plen);
> + if (lrp_is_l3dgw(op)) {
> + ds_put_format(match, " && is_chassis_resident(%s)",
> + op->cr_port->json_key);
> + }
> + ds_clear(actions);
> + ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
> + " = lookup_nd(inport, ip6.src, nd.sll); %snext;",
> + learn_from_arp_request ? "" :
> + REGBIT_LOOKUP_NEIGHBOR_IP_RESULT
> + " = lookup_nd_ip(inport, ip6.src); ");
> + ovn_lflow_add(lflows, op->od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
> + ds_cstr(match), ds_cstr(actions), lflow_ref,
> + WITH_HINT(&op->nbrp->header_));
> + }
> +
> if (lrp_is_l3dgw(op)) {
> ds_clear(match);
> ds_put_format(match, "inport == %s && (nd_na || nd_ns) && "
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index d3d9de3b3..13400ff5e 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -9184,7 +9184,7 @@ AT_SETUP([LR neighbor lookup and learning flows])
> ovn_start
>
> # Create logical routers
> -check ovn-nbctl --wait=sb lr-add lr0
> +check ovn-nbctl --wait=sb lr-add lr0 -- lrp-add lr0 lrp 00:00:00:00:00:01
> 192.168.0.1/24 fd10::1/96
>
> ovn-sbctl dump-flows lr0 > lrflows
> AT_CAPTURE_FILE([lrflows])
> @@ -9199,8 +9199,9 @@ AT_CHECK([cat lrflows | grep -e lr_in_lookup_neighbor
> -e lr_in_learn_neighbor |
> table=??(lr_in_learn_neighbor), priority=95 , match=(nd_ns && (ip6.src
> == 0 || nd.sll == 0)), action=(next;)
> table=??(lr_in_lookup_neighbor), priority=0 , match=(1),
> action=(reg9[[2]] = 1; next;)
> table=??(lr_in_lookup_neighbor), priority=100 , match=(arp.op == 2),
> action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); next;)
> + table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" &&
> arp.spa == 192.168.0.0/24 && arp.op == 1), action=(reg9[[2]] =
> lookup_arp(inport, arp.spa, arp.sha); next;)
> + table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" &&
> ip6.src == fd10::/96 && nd_ns), action=(reg9[[2]] = lookup_nd(inport,
> ip6.src, nd.sll); next;)
> table=??(lr_in_lookup_neighbor), priority=100 , match=(nd_na),
> action=(reg9[[2]] = lookup_nd(inport, nd.target, nd.tll); next;)
> - table=??(lr_in_lookup_neighbor), priority=100 , match=(nd_ns),
> action=(reg9[[2]] = lookup_nd(inport, ip6.src, nd.sll); next;)
> table=??(lr_in_lookup_neighbor), priority=105 , match=(nd_na && nd.tll ==
> 0), action=(reg9[[2]] = lookup_nd(inport, nd.target, eth.src); next;)
> ])
>
> @@ -9219,9 +9220,12 @@ AT_CHECK([cat lrflows | grep -e lr_in_lookup_neighbor
> -e lr_in_learn_neighbor |
> table=??(lr_in_learn_neighbor), priority=95 , match=(nd_ns && (ip6.src
> == 0 || nd.sll == 0)), action=(next;)
> table=??(lr_in_lookup_neighbor), priority=0 , match=(1),
> action=(reg9[[2]] = 1; next;)
> table=??(lr_in_lookup_neighbor), priority=100 , match=(arp.op == 2),
> action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); reg9[[3]] = 1;
> next;)
> + table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" &&
> arp.spa == 192.168.0.0/24 && arp.op == 1), action=(reg9[[2]] =
> lookup_arp(inport, arp.spa, arp.sha); reg9[[3]] = lookup_arp_ip(inport,
> arp.spa); next;)
> + table=??(lr_in_lookup_neighbor), priority=100 , match=(inport == "lrp" &&
> ip6.src == fd10::/96 && nd_ns), action=(reg9[[2]] = lookup_nd(inport,
> ip6.src, nd.sll); reg9[[3]] = lookup_nd_ip(inport, ip6.src); next;)
> table=??(lr_in_lookup_neighbor), priority=100 , match=(nd_na),
> action=(reg9[[2]] = lookup_nd(inport, nd.target, nd.tll); reg9[[3]] = 1;
> next;)
> - table=??(lr_in_lookup_neighbor), priority=100 , match=(nd_ns),
> action=(reg9[[2]] = lookup_nd(inport, ip6.src, nd.sll); reg9[[3]] =
> lookup_nd_ip(inport, ip6.src); next;)
> table=??(lr_in_lookup_neighbor), priority=105 , match=(nd_na && nd.tll ==
> 0), action=(reg9[[2]] = lookup_nd(inport, nd.target, eth.src); reg9[[3]] = 1;
> next;)
> + table=??(lr_in_lookup_neighbor), priority=110 , match=(inport == "lrp" &&
> arp.spa == 192.168.0.0/24 && arp.tpa == 192.168.0.1 && arp.op == 1),
> action=(reg9[[2]] = lookup_arp(inport, arp.spa, arp.sha); reg9[[3]] = 1;
> next;)
> + table=??(lr_in_lookup_neighbor), priority=110 , match=(inport == "lrp" &&
> ip6.src == fd10::/96 && nd.target == fd10::1 && nd_ns), action=(reg9[[2]] =
> lookup_nd(inport, ip6.src, nd.sll); reg9[[3]] = 1; next;)
> table=??(lr_in_lookup_neighbor), priority=110 , match=(nd_na && ip6.src
> == fe80::/10 && ip6.dst == ff00::/8), action=(reg9[[2]] = lookup_nd(inport,
> nd.target, nd.tll); reg9[[3]] = lookup_nd_ip(inport, nd.target); next;)
> table=??(lr_in_lookup_neighbor), priority=115 , match=(nd_na && nd.tll ==
> 0 && ip6.src == fe80::/10 && ip6.dst == ff00::/8), action=(reg9[[2]] =
> lookup_nd(inport, nd.target, eth.src); reg9[[3]] = lookup_nd_ip(inport,
> nd.target); next;)
> ])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 4324c2244..272a22db6 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -5740,6 +5740,136 @@ OVN_CLEANUP([hv1], [hv2])
> AT_CLEANUP
> ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([MAC binding learning from ARP request and ND NS])
> +CHECK_SCAPY
> +ovn_start
> +
> +dnl Logical network:
> +dnl
> +dnl One logical router lr0 connected to two logical switches.
> +dnl ls-ext has a localnet port ln0 (physnet0) and a router attachment
> +dnl for lrp0. ls-int has one VIF lsp1 on hv1.
> +dnl
> +dnl lrp0 is a distributed gateway port (172.18.0.3/16, fd00::3/64),
> +dnl resident on hv1. On hv2 the LS ARP/ND responder for lrp0 is
> +dnl conditioned with is_chassis_resident("cr-lrp0"), so ARP/NS
> +dnl injected via the localnet on hv2 bypasses the LS responder and
> +dnl reaches the router pipeline where lr_in_lookup_neighbor runs.
> +
> +check ovn-nbctl lr-add lr0
> +check ovn-nbctl ls-add ls-ext
> +check ovn-nbctl ls-add ls-int
> +
> +check ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:ff:01 \
> + 172.18.0.3/16 fd00::3/64
> +check ovn-nbctl lsp-add-router-port ls-ext lrp0-attach lrp0
> +
> +check ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:ff:02 192.168.1.1/24
> +check ovn-nbctl lsp-add-router-port ls-int lrp1-attach lrp1
> +
> +check ovn-nbctl lsp-add ls-int lsp1 \
> + -- lsp-set-addresses lsp1 "f0:00:00:00:00:01 192.168.1.10"
> +
> +check ovn-nbctl lsp-add-localnet-port ls-ext ln0 physnet0
> +check ovn-nbctl lrp-set-gateway-chassis lrp0 hv1 20
> +
> +net_add n1
> +for i in 1 2; do
> + sim_add hv$i
> + as hv$i
> + ovs-vsctl add-br br-phys
> + ovn_attach n1 br-phys 192.168.0.$i
> + as hv$i ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=physnet0:br-phys
> +done
> +
> +as hv1 ovs-vsctl \
> + -- add-port br-int vif1 \
> + -- set Interface vif1 external_ids:iface-id=lsp1
Nit: missing check calls for a bunch of these.
> +
> +dnl Add a dummy port on hv2's br-phys to inject external packets.
> +as hv2 ovs-vsctl \
> + -- add-port br-phys ext1 \
> + -- set Interface ext1 type=internal
> +
> +OVN_POPULATE_ARP
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +
> +dnl External device's addresses.
> +sha=f0:00:00:00:10:01
> +spa=172.18.0.100
> +spa6=fd00::100
> +
> +dnl Router port addresses (target of ARP request / ND NS).
> +router_ip=172.18.0.3
> +router_ip6=fd00::3
> +
> +AS_BOX([always_learn_from_arp_request=false])
> +check ovn-nbctl --wait=hv set logical_router lr0 \
> + options:always_learn_from_arp_request=false
> +
> +AS_BOX([ARP request targeting router own IPv4])
> +packet=$(dump_arp 1 $sha ff:ff:ff:ff:ff:ff $spa $router_ip 00:00:00:00:00:00)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha\"
> +
> +check ovn-sbctl --all destroy mac_binding
> +check ovn-nbctl --wait=hv sync
> +
> +AS_BOX([ND NS targeting router own IPv6])
> +packet=$(dump_ns 33:33:ff:00:00:03 $sha ff02::1:ff00:3 $spa6 $router_ip6)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
> +
> +check ovn-sbctl --all destroy mac_binding
> +check ovn-nbctl --wait=hv sync
> +
> +AS_BOX([GARP for unknown IP - no binding expected])
> +unknown_ip=172.18.0.200
> +packet=$(dump_arp 1 $sha ff:ff:ff:ff:ff:ff $unknown_ip $unknown_ip
> 00:00:00:00:00:00)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +check ovn-nbctl --wait=hv sync
> +sleep 1
> +check_row_count MAC_Binding 0 ip="$unknown_ip"
> +
> +AS_BOX([ND NS for unknown IPv6 - no binding expected])
> +unknown_ip6=fd00::200
> +packet=$(dump_ns 33:33:ff:00:02:00 $sha ff02::1:ff00:200 $spa6 $unknown_ip6)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +check ovn-nbctl --wait=hv sync
> +sleep 1
> +check_row_count MAC_Binding 0 ip=\"$spa6\"
> +
> +AS_BOX([Update existing ARP binding with new MAC])
> +check ovn-sbctl --all destroy mac_binding
> +check ovn-nbctl --wait=hv sync
> +
> +packet=$(dump_arp 1 $sha ff:ff:ff:ff:ff:ff $spa $router_ip 00:00:00:00:00:00)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha\"
> +check ovn-nbctl --wait=hv sync
> +
> +sha2=f0:00:00:00:10:02
> +packet=$(dump_arp 1 $sha2 ff:ff:ff:ff:ff:ff $spa $router_ip
> 00:00:00:00:00:00)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +wait_row_count MAC_Binding 1 ip="$spa" mac=\"$sha2\"
> +
> +AS_BOX([Update existing ND binding with new MAC])
> +packet=$(dump_ns 33:33:ff:00:00:03 $sha ff02::1:ff00:3 $spa6 $router_ip6)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha\"
> +check ovn-nbctl --wait=hv sync
> +
> +packet=$(dump_ns 33:33:ff:00:00:03 $sha2 ff02::1:ff00:3 $spa6 $router_ip6)
> +as hv2 ovs-appctl netdev-dummy/receive ext1 $packet
> +wait_row_count MAC_Binding 1 ip=\"$spa6\" mac=\"$sha2\"
> +
> +OVN_CLEANUP([hv1], [hv2])
> +
> +AT_CLEANUP
> +])
> +
> # 3 hypervisors, one logical switch, 3 logical ports per hypervisor
> OVN_FOR_EACH_NORTHD([
> AT_SETUP([portsecurity : 3 HVs, 1 LS, 3 lports/HV])
I took care of the minor issues in the test and applied the patch to
main, 26.03 and 25.09.
Regards,
Dumitru
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev