On Wed, Oct 8, 2025 at 4:49 PM Mark Michelson via dev <
[email protected]> wrote:

> By default, northd does not install any responder flows for unicast ARP.
> These are intended to be forwarded to the destination so that the
> destination can respond appropriately. For VIF ports, this tends to work
> as expected in most scenarios.
>
> When proxy ARP is configured on a logical switch port conntected to a
> logical router, then we install low priority flows to ensure that
> ARPs for the configured proxy addresses is responded to by the logical
> switch. These proxy ARP flows are hit when unicast ARP requests are sent
> for the VIF ports on the logical switch. We therefore end up responding
> incorrectly to unicast ARP requests with the proxy ARP MAC instead of
> forwarding the ARP request to the proper VIF port.
>
> This commit fixes the issue by installing explicit "next;" actions for
> unicast ARP requests directed towards VIFs so that the proxy ARP flows
> will not be hit. These flows are only installed if proxy ARP is
> configured, since they are unnecessary otherwise.
>
> Reported-at: https://issues.redhat.com/browse/FDP-1646
> Signed-off-by: Mark Michelson <[email protected]>
> ---
>
 northd/northd.c     | 30 ++++++++++++++++-----
>  tests/ovn-northd.at | 65 +++++++++++++++++++++++++++++++++++++++++++++
>  tests/ovn.at        | 64 ++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 153 insertions(+), 6 deletions(-)
>
> diff --git a/northd/northd.c b/northd/northd.c
> index 4a3463a8e..993073e68 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -9940,15 +9940,33 @@ build_lswitch_arp_nd_responder_known_ips(struct
> ovn_port *op,
>          for (size_t i = 0; i < op->n_lsp_addrs; i++) {
>              for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
>                  ds_clear(match);
> -                /* Do not reply on unicast ARPs, forward them to the
> target
> -                 * to have ability to monitor target liveness via unicast
> -                 * ARP requests.
> -                */
>                  ds_put_format(match,
>                      "arp.tpa == %s && "
> -                    "arp.op == 1 && "
> -                    "eth.dst == ff:ff:ff:ff:ff:ff",
> +                    "arp.op == 1",
>                      op->lsp_addrs[i].ipv4_addrs[j].addr_s);
> +
> +                /* Do not reply on unicast ARPs, forward them to the
> target
> +                 * to have ability to monitor target liveness via unicast
> +                 * ARP requests. If proxy arp is configured, then we need
> +                 * to set up flows to forward the packets. Otherwise, we
> +                 * could end up replying with the proxy ARP erroneously.
> +                 * Without proxy arp configured, these flows are
> +                 * unnecessary since the packets will hit the default
> +                 * "next" flow at priority 0.
> +                 */
> +                if (op->od->has_arp_proxy_port) {
> +                    size_t match_len = match->length;
> +                    ds_put_format(match, " && eth.dst == %s",
> +                                  op->lsp_addrs[i].ea_s);
> +                    ovn_lflow_add_with_hint(lflows, op->od,
> +                                            S_SWITCH_IN_ARP_ND_RSP, 50,
> +                                            ds_cstr(match),
> +                                            "next;", &op->nbsp->header_,
> +                                            op->lflow_ref);
> +                    ds_truncate(match, match_len);
> +                }
> +                ds_put_cstr(match, " && eth.dst == ff:ff:ff:ff:ff:ff");
> +
>                  ds_clear(actions);
>                  ds_put_format(actions,
>                      "eth.dst = eth.src; "
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index b1aee0008..6c5a4203a 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -18716,3 +18716,68 @@ AT_CHECK(
>
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([Unicast ARP flows])
> +ovn_start
> +
> +# Typically on logical switches, the ARP responder stage installs nothing
> +# for unicast ARPs towards VIF MACs. Instead, we rely on the default
> priority
> +# 1 "next;" action to move these ARPs through the pipeline, eventually
> resulting
> +# in the ARP reaching the destination. When proxy ARP is configured, we
> need
> +# to install explicit flows for the unicast ARPs at a higher priority.
> Otherwise
> +# the proxy ARP responder may respond with incorrect information.
> +#
> +# In this test, we are ensuring that the unicast flows in the ARP
> responder stage
> +# are only installed when proxy ARP is enabled.
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl lsp-add ls1 vm1 -- \
> +      lsp-set-addresses vm1 "00:00:00:00:00:02 192.168.0.2"
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 192.168.0.1
> +check ovn-nbctl lsp-add ls1 ls1-lr1 -- \
> +      lsp-set-addresses ls1-lr1 router -- \
> +      lsp-set-type ls1-lr1 router -- \
> +      lsp-set-options ls1-lr1 router-port=lr1-ls1
> +
> +check ovn-nbctl --wait=sb sync
> +
> +# If we check the ARP responder flows in ls1, we should not see any
> unicast
> +# flows for vm1 (00:00:00:00:00:02).
> +
> +AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_arp_rsp | grep "eth.dst
> == 00:00:00:00:00:02" | ovn_strip_lflows], [0], [dnl
> +])
> +
> +AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_arp_rsp | grep "eth.dst
> == 00:00:00:00:00:01" | ovn_strip_lflows], [0], [dnl
> +])
> +
> +# Add ARP proxy configuration on the router port.
> +check ovn-nbctl set logical_switch_port ls1-lr1
> options:arp_proxy="10.0.0.1 10.0.0.2"
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl lflow-list lr1
> +
> +# Now that we have ARP proxy configured, we should see a flow for
> +# vm1's MAC.
> +AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_arp_rsp | grep "eth.dst
> == 00:00:00:00:00:02" | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_arp_rsp      ), priority=50   , match=(arp.tpa ==
> 192.168.0.2 && arp.op == 1 && eth.dst == 00:00:00:00:00:02), action=(next;)
> +])
> +
> +# We should also see a flow for ls1-lr1's MAC.
> +AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_arp_rsp | grep "eth.dst
> == 00:00:00:00:00:01" | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_arp_rsp      ), priority=50   , match=(arp.tpa ==
> 192.168.0.1 && arp.op == 1 && eth.dst == 00:00:00:00:00:01), action=(next;)
> +])
> +
> +check ovn-nbctl remove logical_switch_port ls1-lr1 options arp_proxy
> +check ovn-nbctl --wait=sb sync
> +
> +# We have removed ARP proxy, so we should no longer see a unicast ARP
> flow.
> +AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_arp_rsp | grep "eth.dst
> == 00:00:00:00:00:02" | ovn_strip_lflows], [0], [dnl
> +])
> +AT_CHECK([ovn-sbctl lflow-list ls1 | grep ls_in_arp_rsp | grep "eth.dst
> == 00:00:00:00:00:01" | ovn_strip_lflows], [0], [dnl
> +])
> +
> +AT_CLEANUP
> +])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 16270151f..18b745033 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -44436,3 +44436,67 @@ check ovn-nbctl --wait=hv sync
>  OVN_CLEANUP([hv1],[hv2],[hv3])
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Unicast ARP when proxy ARP is configured])
> +ovn_start
> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl lsp-add ls1 vm1 -- \
> +      lsp-set-addresses vm1 "00:00:00:00:00:02 10.0.0.2"
> +check ovn-nbctl lsp-add ls1 vm2 -- \
> +      lsp-set-addresses vm2 "00:00:00:00:00:03 10.0.0.3"
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lr1-ls1 00:00:00:00:00:01 10.0.0.1
> +check ovn-nbctl lsp-add ls1 ls1-lr1 -- \
> +      lsp-set-addresses ls1-lr1 router -- \
> +      lsp-set-type ls1-lr1 router -- \
> +      lsp-set-options ls1-lr1 router-port=lr1-ls1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +net_add n1
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +ovs-vsctl add-port br-int vif1 -- set Interface vif1
> external-ids:iface-id=vm1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +ovs-vsctl add-port br-int vif2 -- set Interface vif2
> external-ids:iface-id=vm2 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=2
> +
> +OVN_POPULATE_ARP
> +
> +wait_for_ports_up
> +
> +# If we send a unicast ARP from vm2 towards vm1, the ARP should be
> forwarded
> +# to vm1 by ls1.
> +packet=$(fmt_pkt "Ether(dst='00:00:00:00:00:02',
> src='00:00:00:00:00:03')/ \
> +                  ARP(hwsrc='00:00:00:00:00:03',
> hwdst='00:00:00:00:00:02',
> +                      psrc='10.0.0.3', pdst='10.0.0.2')")
> +
> +as hv1 ovs-appctl netdev-dummy/receive vif2 $packet
> +
> +echo $packet > expected
> +
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], expected)
> +
> +# Add ARP proxy configuration on the router port. The subnet for the ARP
> proxy
> +# addresses overlaps with the VIF addresses for vm1 and vm2.
> +check ovn-nbctl set logical_switch_port ls1-lr1 options:arp_proxy="
> 10.0.0.0/8"
> +check ovn-nbctl --wait=hv sync
> +
> +# Sending a unicast ARP from vm2 towards vm1 should still result in the
> ARP being
> +# forwarded to vm1 by ls1.
> +as hv1 ovs-appctl netdev-dummy/receive vif2 $packet
> +echo $packet >> expected
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], expected)
> +
> +OVN_CLEANUP([hv1])
> +AT_CLEANUP
> +])
> --
> 2.50.1
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
Looks good to me, thanks.
Acked-by: Ales Musil <[email protected]>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to