> Add a new pipeline stage ls_in_arp_nd_pre_lookup (table 26)
> and EVPN suppression response flows in ls_in_arp_rsp for
> logical switches with EVPN enabled (dynamic-routing-vni set).
>
> The pre-lookup stage calls chk_evpn_arp(arp.tpa) for broadcast
> ARP requests and chk_evpn_arp(nd.target) for multicast ND
> solicitations. When the EVPN ARP side table has a matching
> entry, the resolved MAC is stored in eth.dst and reg9[5] is
> set.
>
> The response flows in ls_in_arp_rsp at priority 40 match when
> reg9[5] == 1 and generate proxy ARP replies or ND NA replies
> using the MAC from eth.dst. This prevents unnecessary
> flooding of ARP/ND requests to remote VTEPs for EVPN-learned
> addresses.
>
> Key design points:
> - MAC stored in eth.dst (loaded by the EVPN ARP side table).
> On a miss, eth.dst is left unchanged.
> - ARP response uses eth.dst <-> eth.src swap to put the
> resolved MAC into eth.src and the original sender's MAC
> into eth.dst for the reply.
> - ND response relies on nd_na{} reading eth.dst for the
> NA source MAC (nd.tll).
> - ARP match: arp.op == 1 && reg9[5] == 1.
> - ND match: nd_ns && reg9[5] == 1.
> - COPP_ND_NA meter on the ND NA response flow.
>
> Reported-at: https://redhat.atlassian.net/browse/FDP-3429
> Assisted-by: Claude Opus 4.6, Claude Code
> Signed-off-by: Ales Musil <[email protected]>
Acked-by: Lorenzo Bianconi <[email protected]>
> ---
> Documentation/ref/ovn-logical-flows.7.rst | 72 ++++++++++++++------
> NEWS | 6 ++
> lib/ovn-util.c | 4 +-
> lib/ovn-util.h | 2 +-
> northd/northd.c | 83 +++++++++++++++++++++++
> northd/northd.h | 18 ++---
> ovn-sb.ovsschema | 6 +-
> tests/ovn-northd.at | 39 +++++++++++
> tests/ovn.at | 4 +-
> tests/system-ovn.at | 77 +++++++++++++++++++++
> 10 files changed, 275 insertions(+), 36 deletions(-)
>
> diff --git a/Documentation/ref/ovn-logical-flows.7.rst
> b/Documentation/ref/ovn-logical-flows.7.rst
> index ce4dd5355..2c13478a7 100644
> --- a/Documentation/ref/ovn-logical-flows.7.rst
> +++ b/Documentation/ref/ovn-logical-flows.7.rst
> @@ -717,7 +717,7 @@ Ingress Table 19: Hairpin
> - If logical switch has attached logical switch port of *vtep* type, then a
> priority-1000 flow that matches on ``reg0[14]`` register bit for the
> traffic
> received from HW VTEP (ramp) ports. This traffic is passed to ingress
> table
> - :ref:`Destination Lookup <ls-in-32>`.
> + :ref:`Destination Lookup <ls-in-33>`.
>
> - A priority-1 flow that hairpins traffic matched by non-default flows in the
> :ref:`Pre-Hairpin <ls-in-17>` table. Hairpinning is done at L2, Ethernet
> @@ -978,7 +978,28 @@ refer to either the parent or child ports as applicable
> to this logical switch.
>
> .. _ls-in-26:
>
> -Ingress Table 26: ARP/ND responder
> +Ingress Table 26: ARP/ND Pre-Lookup
> +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> +
> +For logical switches with EVPN enabled (``dynamic-routing-vni`` is set),
> +this table performs a pre-lookup in the EVPN ARP side table using the
> +``chk_evpn_arp()`` action. If the target IP address matches an
> +EVPN-learned entry, the resolved MAC is loaded into ``eth.dst``
> +and a regbit is set so that the ARP/ND responder table can generate a
> +proxy reply.
> +
> +- Priority-5 flows match broadcast ARP requests
> + (``arp.op == 1 && eth.bcast``) and multicast ND
> + solicitations (``nd_ns_mcast``), and call ``chk_evpn_arp(arp.tpa)``
> + or ``chk_evpn_arp(nd.target)`` respectively.
> +
> +- A priority-0 fallback flow advances to the next table.
> +
> +For switches without EVPN, only the priority-0 fallback flow is present.
> +
> +.. _ls-in-27:
> +
> +Ingress Table 27: ARP/ND responder
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> This table implements ARP/ND responder in a logical switch for known IPs.
> The
> @@ -1208,12 +1229,23 @@ proxy ARP/ND behavior. It contains these logical
> flows:
> These flows are required to respond to an ARP request if an ARP request is
> sent for the IP *vip*.
>
> +- For logical switches with EVPN enabled, priority-40 flows provide ARP/ND
> + suppression for EVPN-learned addresses. These flows match when the EVPN
> + ARP pre-lookup (table 26) found a hit (``reg9[5] == 1``):
> +
> + - An ARP suppression flow matches ``arp.op == 1 && reg9[5] == 1`` and
> + generates an ARP reply using the MAC from ``eth.dst`` (loaded by
> + ``chk_evpn_arp()`` in the pre-lookup stage).
> +
> + - An ND suppression flow matches ``nd_ns && reg9[5] == 1`` and
> + generates an ND NA reply using the MAC from ``eth.dst``.
> +
> - One priority-0 fallback flow that matches all packets and advances to the
> next
> table.
>
> -.. _ls-in-27:
> +.. _ls-in-28:
>
> -Ingress Table 27: DHCP option processing
> +Ingress Table 28: DHCP option processing
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> This table adds the DHCPv4 options to a DHCPv4 packet from the logical ports
> @@ -1246,9 +1278,9 @@ options. This table also adds flows for the logical
> ports of type ``external``.
>
> - A priority-0 flow that matches all packets to advances to table 16.
>
> -.. _ls-in-28:
> +.. _ls-in-29:
>
> -Ingress Table 28: DHCP responses
> +Ingress Table 29: DHCP responses
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> This table implements DHCP responder for the DHCP replies generated by the
> @@ -1301,9 +1333,9 @@ previous table.
>
> - A priority-0 flow that matches all packets to advances to table 17.
>
> -.. _ls-in-29:
> +.. _ls-in-30:
>
> -Ingress Table 29 DNS Lookup
> +Ingress Table 30 DNS Lookup
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> This table looks up and resolves the DNS names to the corresponding
> configured
> @@ -1321,9 +1353,9 @@ IP address(es).
> other kinds of packets, it just stores 0 into reg0[4]. Either way, it
> continues to the next table.
>
> -.. _ls-in-30:
> +.. _ls-in-31:
>
> -Ingress Table 30 DNS Responses
> +Ingress Table 31 DNS Responses
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> This table implements DNS responder for the DNS replies generated by the
> @@ -1346,9 +1378,9 @@ previous table.
> (This terminates ingress packet processing; the packet does not go to the
> next
> ingress table.)
>
> -.. _ls-in-31:
> +.. _ls-in-32:
>
> -Ingress table 31 External ports
> +Ingress table 32 External ports
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> Traffic from the ``external`` logical ports enter the ingress datapath
> pipeline
> @@ -1373,9 +1405,9 @@ traffic from these ports.
>
> - A priority-0 flow that matches all packets to advances to table 20.
>
> -.. _ls-in-32:
> +.. _ls-in-33:
>
> -Ingress Table 32 Destination Lookup
> +Ingress Table 33 Destination Lookup
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> This table implements switching behavior. It contains these logical flows:
> @@ -1532,9 +1564,9 @@ This table implements switching behavior. It contains
> these logical flows:
> If there is no entry for ``eth.dst`` in the MAC learning table, then it
> stores
> ``none`` in the ``outport``.
>
> -.. _ls-in-33:
> +.. _ls-in-34:
>
> -Ingress Table 33 Destination unknown
> +Ingress Table 34 Destination unknown
> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> This table handles the packets whose destination was not found or and looked
> up
> @@ -1695,12 +1727,12 @@ In addition, the following flows are added.
>
> - A priority 34000 logical flow is added for each logical port which has
> DHCPv4
> options defined to allow the DHCPv4 reply packet and which has DHCPv6
> options
> - defined to allow the DHCPv6 reply packet from :ref:`Ingress Table 28: DHCP
> - responses <ls-in-28>`. This is indicated by setting the allow bit.
> + defined to allow the DHCPv6 reply packet from :ref:`Ingress Table 29: DHCP
> + responses <ls-in-29>`. This is indicated by setting the allow bit.
>
> - A priority 34000 logical flow is added for each logical switch datapath
> configured with DNS records with the match ``udp.dst = 53`` to allow the
> DNS
> - reply packet from :ref:`Ingress Table 30: DNS responses <ls-in-30>`. This
> is
> + reply packet from :ref:`Ingress Table 31: DNS responses <ls-in-31>`. This
> is
> indicated by setting the allow bit.
>
> - A priority 34000 logical flow is added for each logical switch datapath
> with
> @@ -1843,7 +1875,7 @@ in ``ct_label.nf_id`` during request processing.
> function group, a priority-99 flow matches ``reg8[21] == 1 && reg8[22] ==
> 1 &&
> reg0[22..29] == id`` and sets ``outport=P; reg8[23] = 1;
> next(pipeline=ingress, table=T)`` where *P* is the ``outport`` of that
> network
> - function and *T* is the ingress table :ref:`Destination Lookup <ls-in-32>`.
> + function and *T* is the ingress table :ref:`Destination Lookup <ls-in-33>`.
> This redirects request packets matching ``to-lport`` ACLs with
> network_function_group to the specific network function selected by the Pre
> Network Function stage. The packets are injected back to the ingress
> pipeline
> diff --git a/NEWS b/NEWS
> index 748ae30eb..d426d671a 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -15,6 +15,12 @@ Post v26.03.0
> * Add ECMP/multi-homing support for EVPN FDB entries. FDB entries
> backed by a kernel nexthop group are load-balanced via OpenFlow
> select groups with weighted buckets.
> + * Add EVPN ARP/ND suppression for logical switches. When a
> + broadcast ARP request or multicast ND solicitation targets
> + an IP address that was learned via EVPN, ovn-northd now
> + generates proxy-reply flows using a dedicated side table
> + and the new chk_evpn_arp() action, preventing unnecessary
> + flooding to remote VTEPs.
> - Added "override-connected" option to Logical Router Static Routes to
> mark
> static routes as higher-priority than connected routes, which in turn
> led
> to changes in administrative distance for specific route types. Please
> see
> diff --git a/lib/ovn-util.c b/lib/ovn-util.c
> index cc5431a11..90ab27fc6 100644
> --- a/lib/ovn-util.c
> +++ b/lib/ovn-util.c
> @@ -1007,8 +1007,8 @@ ip_address_and_port_from_lb_key(const char *key, char
> **ip_address,
> *
> * NOTE: If OVN_NORTHD_PIPELINE_CSUM is updated make sure to double check
> * whether an update of OVN_INTERNAL_MINOR_VER is required. */
> -#define OVN_NORTHD_PIPELINE_CSUM "951247664 11305"
> -#define OVN_INTERNAL_MINOR_VER 14
> +#define OVN_NORTHD_PIPELINE_CSUM "3951531131 11381"
> +#define OVN_INTERNAL_MINOR_VER 15
>
> /* Returns the OVN version. The caller must free the returned value. */
> char *
> diff --git a/lib/ovn-util.h b/lib/ovn-util.h
> index bfca178e4..4d1761dc4 100644
> --- a/lib/ovn-util.h
> +++ b/lib/ovn-util.h
> @@ -340,7 +340,7 @@ BUILD_ASSERT_DECL(
> #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0)
>
> /* The number of tables for the ingress and egress pipelines. */
> -#define LOG_PIPELINE_INGRESS_LEN 34
> +#define LOG_PIPELINE_INGRESS_LEN 35
> #define LOG_PIPELINE_EGRESS_LEN 16
>
> static inline uint32_t
> diff --git a/northd/northd.c b/northd/northd.c
> index f5aa5cca3..a5534e89c 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -204,6 +204,7 @@ BUILD_ASSERT_DECL(ACL_OBS_STAGE_MAX < (1 << 2));
> #define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[2]"
> #define REGBIT_LOOKUP_NEIGHBOR_IP_RESULT "reg9[3]"
> #define REGBIT_DST_NAT_IP_LOCAL "reg9[4]"
> +#define REGBIT_EVPN_LOOKUP_MAC "reg9[5]"
> #define REGBIT_KNOWN_LB_SESSION "reg9[6]"
> #define REGBIT_DHCP_RELAY_REQ_CHK "reg9[7]"
> #define REGBIT_DHCP_RELAY_RESP_CHK "reg9[8]"
> @@ -10754,6 +10755,85 @@ build_lswitch_arp_nd_responder_default(struct
> ovn_datapath *od,
> lflow_ref);
> }
>
> +/* Ingress table ls_in_arp_nd_pre_lookup: EVPN ARP/ND pre-lookup.
> + *
> + * For EVPN-enabled switches, calls chk_evpn_arp() to look up the
> + * IP in the EVPN ARP side table. On a hit, the resolved MAC is
> + * stored in eth.dst and REGBIT_EVPN_LOOKUP_MAC is set. The
> + * response flow in ls_in_arp_rsp reads the MAC from eth.dst.
> + */
> +static void
> +build_lswitch_arp_nd_evpn_lookup(struct ovn_datapath *od,
> + struct lflow_table *lflows,
> + struct lflow_ref *lflow_ref)
> +{
> + ovs_assert(od->nbs);
> +
> + /* Default: pass through. */
> + ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_PRE_LOOKUP, 0, "1",
> + "next;", lflow_ref);
> +
> + if (!od->has_evpn_vni) {
> + return;
> + }
> +
> + /* IPv4: broadcast ARP requests only. */
> + ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_PRE_LOOKUP, 5,
> + "arp.op == 1 && eth.bcast",
> + REGBIT_EVPN_LOOKUP_MAC " = chk_evpn_arp(arp.tpa); next;",
> + lflow_ref);
> +
> + /* IPv6: multicast ND solicitations only. */
> + ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_PRE_LOOKUP, 5,
> + "nd_ns_mcast",
> + REGBIT_EVPN_LOOKUP_MAC " = chk_evpn_arp(nd.target); next;",
> + lflow_ref);
> +}
> +
> +/* Ingress table ls_in_arp_rsp: EVPN ARP/ND suppression response flows.
> + *
> + * For EVPN-enabled switches, adds flows that proxy-reply to ARP/ND
> + * when chk_evpn_arp found a match (REGBIT_EVPN_LOOKUP_MAC == 1).
> + * The resolved MAC is already in eth.dst (loaded by the side table).
> + */
> +static void
> +build_lswitch_arp_nd_evpn_response(struct ovn_datapath *od,
> + struct lflow_table *lflows,
> + const struct shash *meter_groups,
> + struct lflow_ref *lflow_ref)
> +{
> + ovs_assert(od->nbs);
> +
> + if (!od->has_evpn_vni) {
> + return;
> + }
> +
> + /* ARP reply (priority 40): ARP request with EVPN hit.
> + * eth.dst holds the resolved MAC (loaded by chk_evpn_arp). */
> + ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 40,
> + "arp.op == 1 && " REGBIT_EVPN_LOOKUP_MAC " == 1",
> + "eth.dst <-> eth.src; "
> + "arp.op = 2; "
> + "arp.tha = arp.sha; "
> + "arp.sha = eth.src; "
> + "arp.tpa <-> arp.spa; "
> + "outport = inport; "
> + "flags.loopback = 1; "
> + "output;",
> + lflow_ref);
> +
> + /* ND NA reply (priority 40): ND NS with EVPN hit.
> + * eth.dst holds the resolved MAC (loaded by chk_evpn_arp);
> + * compose_nd_na() uses ip_flow->dl_dst for eth.src and nd.tll. */
> + ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 40,
> + "nd_ns && " REGBIT_EVPN_LOOKUP_MAC " == 1",
> + "nd_na { outport = inport; flags.loopback = 1; output; };",
> + lflow_ref,
> + WITH_CTRL_METER(copp_meter_get(COPP_ND_NA,
> + od->nbs->copp,
> + meter_groups)));
> +}
> +
> /* Ingress table 24: ARP/ND responder for service monitor source ip.
> * (priority 110)*/
> static void
> @@ -19525,7 +19605,10 @@ build_lswitch_and_lrouter_iterate_by_ls(struct
> ovn_datapath *od,
> build_fwd_group_lflows(od, lsi->lflows, NULL);
> build_lswitch_lflows_admission_control(od, lsi->lflows, NULL);
> build_lswitch_learn_fdb_od(od, lsi->lflows, NULL);
> + build_lswitch_arp_nd_evpn_lookup(od, lsi->lflows, NULL);
> build_lswitch_arp_nd_responder_default(od, lsi->lflows, NULL);
> + build_lswitch_arp_nd_evpn_response(od, lsi->lflows, lsi->meter_groups,
> + NULL);
> build_lswitch_dns_lookup_and_response(od, lsi->lflows, lsi->meter_groups,
> NULL);
> build_lswitch_dhcp_and_dns_defaults(od, lsi->lflows, NULL);
> diff --git a/northd/northd.h b/northd/northd.h
> index 726a416e4..f713017b5 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -541,14 +541,16 @@ ls_has_localnet_port(const struct ovn_datapath *od)
> "ls_in_pre_network_function") \
> PIPELINE_STAGE(SWITCH, IN, STATEFUL, 24, "ls_in_stateful") \
> PIPELINE_STAGE(SWITCH, IN, NF, 25, "ls_in_network_function")
> \
> - PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 26, "ls_in_arp_rsp") \
> - PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 27, "ls_in_dhcp_options") \
> - PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 28, "ls_in_dhcp_response") \
> - PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 29, "ls_in_dns_lookup") \
> - PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 30, "ls_in_dns_response") \
> - PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 31, "ls_in_external_port") \
> - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 32, "ls_in_l2_lkup") \
> - PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 33, "ls_in_l2_unknown") \
> + PIPELINE_STAGE(SWITCH, IN, ARP_ND_PRE_LOOKUP, 26, \
> + "ls_in_arp_nd_pre_lookup") \
> + PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 27, "ls_in_arp_rsp") \
> + PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 28, "ls_in_dhcp_options") \
> + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 29, "ls_in_dhcp_response") \
> + PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 30, "ls_in_dns_lookup") \
> + PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 31, "ls_in_dns_response") \
> + PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 32, "ls_in_external_port") \
> + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 33, "ls_in_l2_lkup") \
> + PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 34, "ls_in_l2_unknown") \
> \
> /* Logical switch egress stages. */ \
> PIPELINE_STAGE(SWITCH, OUT, LOOKUP_FDB, 0, "ls_out_lookup_fdb")
> \
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index d9a91739c..d38992ad8 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
> {
> "name": "OVN_Southbound",
> - "version": "21.8.0",
> - "cksum": "614397313 36713",
> + "version": "21.9.0",
> + "cksum": "3029208442 36713",
> "tables": {
> "SB_Global": {
> "columns": {
> @@ -103,7 +103,7 @@
> "egress"]]}}},
> "table_id": {"type": {"key": {"type": "integer",
> "minInteger": 0,
> - "maxInteger": 33}}},
> + "maxInteger": 34}}},
> "priority": {"type": {"key": {"type": "integer",
> "minInteger": 0,
> "maxInteger": 65535}}},
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 7f4a88d4e..763c3265b 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -19502,6 +19502,45 @@ OVN_CLEANUP_NORTHD
> AT_CLEANUP
> ])
>
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([LS EVPN ARP/ND suppression flows])
> +AT_KEYWORDS([dynamic-routing evpn])
> +ovn_start
> +
> +AS_BOX([EVPN switch])
> +check ovn-nbctl --wait=sb \
> + -- ls-add ls-evpn \
> + -- set logical_switch ls-evpn other_config:dynamic-routing-vni=10
> +
> +dnl Verify ls_in_arp_nd_pre_lookup flows use chk_evpn_arp.
> +AT_CHECK([ovn-sbctl lflow-list ls-evpn | grep ls_in_arp_nd_pre_lookup |
> ovn_strip_lflows], [0], [dnl
> + table=??(ls_in_arp_nd_pre_lookup), priority=0 , match=(1),
> action=(next;)
> + table=??(ls_in_arp_nd_pre_lookup), priority=5 , match=(arp.op == 1 &&
> eth.bcast), action=(reg9[[5]] = chk_evpn_arp(arp.tpa); next;)
> + table=??(ls_in_arp_nd_pre_lookup), priority=5 , match=(nd_ns_mcast),
> action=(reg9[[5]] = chk_evpn_arp(nd.target); next;)
> +])
> +
> +dnl Verify ls_in_arp_rsp EVPN ARP suppression flow at priority 40.
> +AT_CHECK([ovn-sbctl lflow-list ls-evpn | grep ls_in_arp_rsp | grep
> 'priority=40' | ovn_strip_lflows], [0], [dnl
> + table=??(ls_in_arp_rsp ), priority=40 , match=(arp.op == 1 &&
> reg9[[5]] == 1), action=(eth.dst <-> eth.src; arp.op = 2; arp.tha = arp.sha;
> arp.sha = eth.src; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1;
> output;)
> + table=??(ls_in_arp_rsp ), priority=40 , match=(nd_ns && reg9[[5]]
> == 1), action=(nd_na { outport = inport; flags.loopback = 1; output; };)
> +])
> +
> +AS_BOX([Non-EVPN switch])
> +check ovn-nbctl --wait=sb \
> + -- ls-add ls-plain
> +
> +dnl Non-EVPN switch should have only default next; in pre_lookup.
> +AT_CHECK([ovn-sbctl lflow-list ls-plain | grep ls_in_arp_nd_pre_lookup |
> ovn_strip_lflows], [0], [dnl
> + table=??(ls_in_arp_nd_pre_lookup), priority=0 , match=(1),
> action=(next;)
> +])
> +
> +dnl Non-EVPN switch should have no EVPN suppression flows (no priority 40
> with EVPN regbit).
> +AT_CHECK([ovn-sbctl lflow-list ls-plain | grep ls_in_arp_rsp | grep
> 'priority=40'], [1])
> +
> +OVN_CLEANUP_NORTHD
> +AT_CLEANUP
> +])
> +
> OVN_FOR_EACH_NORTHD_NO_HV([
> AT_SETUP([LS EVPN conntrack skip with stateful ACLs and LBs])
> AT_KEYWORDS([dynamic-routing])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 2d2dcb385..495a6ed7f 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -867,8 +867,8 @@ next();
> Syntax error at `)' expecting "pipeline" or "table".
> next(10;
> Syntax error at `;' expecting `)'.
> -next(34);
> - "next" action cannot advance beyond table 34.
> +next(35);
> + "next" action cannot advance beyond table 35.
>
> next(table=lflow_table);
> formats as next;
> diff --git a/tests/system-ovn.at b/tests/system-ovn.at
> index 1f0f17cb7..c6e6b61fd 100644
> --- a/tests/system-ovn.at
> +++ b/tests/system-ovn.at
> @@ -18015,6 +18015,7 @@ OVN_FOR_EACH_NORTHD([
> AT_SETUP([dynamic-routing - EVPN $1 naming])
> AT_KEYWORDS([dynamic-routing])
>
> +AT_SKIP_IF([test $HAVE_SCAPY = no])
> CHECK_VRF()
> CHECK_CONNTRACK()
> CHECK_CONNTRACK_NAT()
> @@ -18588,6 +18589,82 @@ check ip -6 neigh add dev $BR_NAME 172:16::70 lladdr
> f0:00:0f:16:10:70 nud noarp
> OVS_WAIT_UNTIL([test "$(ovs-ofctl dump-flows br-int
> table=OFTABLE_EVPN_ARP_LOOKUP | \
> grep -c priority)" = "6"])
>
> +AS_BOX([EVPN ARP/ND suppression logical flows])
> +dnl Verify northd generates chk_evpn_arp flows in ls_in_arp_nd_pre_lookup.
> +AT_CHECK([ovn-sbctl lflow-list ls-evpn | grep ls_in_arp_nd_pre_lookup | grep
> -q 'chk_evpn_arp'])
> +
> +dnl Verify ls_in_arp_rsp has EVPN suppression response flows (priority 40).
> +AT_CHECK([ovn-sbctl lflow-list ls-evpn | grep ls_in_arp_rsp | grep
> 'priority=40' | \
> + grep -q 'reg9\[[5\]] == 1'])
> +
> +AS_BOX([EVPN ARP/ND suppression traffic])
> +
> +dnl Record initial packet counts on both EVPN suppression flows.
> +evpn_arp_pkt_before=$(ovs-ofctl dump-flows br-int table=$(ovn-debug
> lflow-stage-to-oftable ls_in_arp_rsp) | \
> + grep "priority=40.*arp" | sed -n 's/.*n_packets=\([[0-9]]*\).*/\1/p')
> +evpn_nd_pkt_before=$(ovs-ofctl dump-flows br-int table=$(ovn-debug
> lflow-stage-to-oftable ls_in_arp_rsp) | \
> + grep "priority=40.*icmp6" | sed -n 's/.*n_packets=\([[0-9]]*\).*/\1/p')
> +
> +dnl --- Broadcast ARP: should be suppressed (local proxy reply) ---
> +ip netns exec workload1 scapy -H <<-EOF
> +p = Ether(src='f0:00:0f:16:01:10', dst='ff:ff:ff:ff:ff:ff') / \
> + ARP(op=1, hwsrc='f0:00:0f:16:01:10', psrc='172.16.1.10',
> + hwdst='00:00:00:00:00:00', pdst='172.16.1.50')
> +sendp(p, iface='workload1', verbose=False)
> +EOF
> +
> +dnl Verify the EVPN suppression flow handled the broadcast ARP.
> +OVS_WAIT_UNTIL([
> + evpn_arp_pkt_after=$(ovs-ofctl dump-flows br-int table=$(ovn-debug
> lflow-stage-to-oftable ls_in_arp_rsp) | \
> + grep "priority=40.*arp" | sed -n 's/.*n_packets=\([[0-9]]*\).*/\1/p')
> + test "$evpn_arp_pkt_after" -gt "$evpn_arp_pkt_before"
> +])
> +
> +dnl --- Unicast ARP: must NOT be suppressed ---
> +ip netns exec workload1 scapy -H <<-EOF
> +p = Ether(src='f0:00:0f:16:01:10', dst='f0:00:0f:16:10:50') / \
> + ARP(op=1, hwsrc='f0:00:0f:16:01:10', psrc='172.16.1.10',
> + hwdst='00:00:00:00:00:00', pdst='172.16.1.50')
> +sendp(p, iface='workload1', verbose=False)
> +EOF
> +
> +dnl The unicast ARP must NOT hit the suppression flow.
> +evpn_arp_pkt_unicast=$(ovs-ofctl dump-flows br-int table=$(ovn-debug
> lflow-stage-to-oftable ls_in_arp_rsp) | \
> + grep "priority=40.*arp" | sed -n 's/.*n_packets=\([[0-9]]*\).*/\1/p')
> +AT_CHECK([test "$evpn_arp_pkt_unicast" = "$evpn_arp_pkt_after"], [0])
> +
> +dnl --- Multicast ND NS: should be suppressed (local proxy NA) ---
> +NS_CHECK_EXEC([workload1], [ip -6 neigh flush dev workload1], [0], [ignore],
> [ignore])
> +
> +ip netns exec workload1 scapy -H <<-EOF
> +p = Ether(src='f0:00:0f:16:01:10', dst='33:33:ff:00:00:50') / \
> + IPv6(src='172:16::10', dst='ff02::1:ff00:50') / \
> + ICMPv6ND_NS(tgt='172:16::50') / \
> + ICMPv6NDOptSrcLLAddr(lladdr='f0:00:0f:16:01:10')
> +sendp(p, iface='workload1', verbose=False)
> +EOF
> +
> +dnl Verify the EVPN ND suppression flow handled the multicast NS.
> +OVS_WAIT_UNTIL([
> + evpn_nd_pkt_after=$(ovs-ofctl dump-flows br-int table=$(ovn-debug
> lflow-stage-to-oftable ls_in_arp_rsp) | \
> + grep "priority=40.*icmp6" | sed -n
> 's/.*n_packets=\([[0-9]]*\).*/\1/p')
> + test "$evpn_nd_pkt_after" -gt "$evpn_nd_pkt_before"
> +])
> +
> +dnl --- Unicast ND NS: must NOT be suppressed ---
> +ip netns exec workload1 scapy -H <<-EOF
> +p = Ether(src='f0:00:0f:16:01:10', dst='f0:00:0f:16:10:50') / \
> + IPv6(src='172:16::10', dst='172:16::50') / \
> + ICMPv6ND_NS(tgt='172:16::50') / \
> + ICMPv6NDOptSrcLLAddr(lladdr='f0:00:0f:16:01:10')
> +sendp(p, iface='workload1', verbose=False)
> +EOF
> +
> +dnl The unicast ND NS must NOT hit the suppression flow.
> +evpn_nd_pkt_unicast=$(ovs-ofctl dump-flows br-int table=$(ovn-debug
> lflow-stage-to-oftable ls_in_arp_rsp) | \
> + grep "priority=40.*icmp6" | sed -n 's/.*n_packets=\([[0-9]]*\).*/\1/p')
> +AT_CHECK([test "$evpn_nd_pkt_unicast" = "$evpn_nd_pkt_after"], [0])
> +
> # Remove remote workload ARP entries and check ovn-controller's state.
> check ip neigh del dev $BR_NAME 172.16.1.50
> check ip neigh del dev $BR_NAME 172.16.1.60
> --
> 2.54.0
>
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev