On 10/5/26 10:30 AM, Ales Musil wrote:
> On Fri, Oct 2, 2026 at 7:31 PM Dumitru Ceara via dev <
> [email protected]> wrote:
> 
>> Currently, build_lswitch_learn_fdb_op() only generates
>> put_fdb(inport, eth.src) flows, so only eth.src is ever learned
>> into the FDB.  In VRRP gratuitous ARP packets, eth.src is the
>> physical MAC while arp.sha is the virtual MAC (VMAC).  Since
>> put_fdb only records eth.src, the VMAC is never learned and
>> traffic destined to it falls through to MC_UNKNOWN flooding.
>>
>> The same gap exists for IPv6 Neighbor Discovery: unsolicited
>> NAs carry the VMAC in nd.tll, and NSes carry it in nd.sll.
>>
>> Add higher-priority (110) flows in both the lookup_fdb and
>> put_fdb stages for ARP, ND NA, and ND NS packets.  The lookup
>> flows check both eth.src and the inner MAC field (arp.sha,
>> nd.tll, nd.sll) and store the inner result in a new regbit
>> REGBIT_LKUP_FDB_DL_INNER (reg0[22]).  The put flows fire when
>> either MAC is unknown and learn both via two put_fdb calls.
>> When both MACs are already known, no put_fdb flow matches and
>> no packet-in is generated.
>>
>> ARP/ND packets may lack the arp.sha/SLL/TLL, in which case
>> arp.sha/nd.sll/nd.tll is all-zeros.  Higher priority (120)
>> flows for ARP/ND flows prevent learning a zero MAC into the
>> FDB.
>>
> 
> Hi Dumitru,
> 

Hi Ales,

> thank you for the patch. Few small comments down below.
> 

Thanks for the review!

> 
>>
>> Reported-at: https://redhat.atlassian.net/browse/FDP-4286
> 
> 
> nit: This should only be in the 2/2 patch.
> 

TBH I chose myself to add it there, it wasn't AI. :)

But I can remove it.

> 
>>
>> Assisted-by: Claude Opus 4.6, opencode
>> Signed-off-by: Dumitru Ceara <[email protected]>
>> ---
>>  NEWS                |   5 ++
>>  northd/northd.c     | 190 +++++++++++++++++++++++++++++++++++++++++---
>>  ovn-nb.xml          |  23 ++++--
>>  tests/ovn-ic.at     |  72 +++++++++++++++++
>>  tests/ovn-northd.at | 118 +++++++++++++++++++++++++++
>>  tests/ovn.at        | 145 +++++++++++++++++++++++++++++++--
>>  6 files changed, 532 insertions(+), 21 deletions(-)
>>
>> diff --git a/NEWS b/NEWS
>> index 7f94d0b14f..d025581c9a 100644
>> --- a/NEWS
>> +++ b/NEWS
>> @@ -1,5 +1,10 @@
>>  Post v26.09.0
>>  --------------
>> +   - Learn ARP sender hardware address (arp.sha) and IPv6 Neighbor
>> +     Discovery link-layer addresses (nd.tll, nd.sll) into the FDB
>> +     table in addition to eth.src.  This allows VRRP virtual MACs to
>> +     be learned from gratuitous ARP packets and unsolicited Neighbor
>> +     Advertisements.
>>     - Added a new "options:ttl" key on the NB DNS table to make the TTL of
>>       DNS replies from OVN's native DNS resolver configurable per row.
>>     - Removed implementations of the commit_ecmp_nh, chk_ecmp_nh, and
>> diff --git a/northd/northd.c b/northd/northd.c
>> index f37040b57f..d33b60cd76 100644
>> --- a/northd/northd.c
>> +++ b/northd/northd.c
>> @@ -142,6 +142,7 @@ static bool vxlan_mode;
>>  #define REGBIT_IP_FRAG            "reg0[19]"
>>  #define REGBIT_ACL_PERSIST_ID     "reg0[20]"
>>  #define REGBIT_ACL_HINT_ALLOW_PERSISTED "reg0[21]"
>> +#define REGBIT_LKUP_FDB_DL_INNER  "reg0[22]"
>>
>>  /* Register definitions for switches and routers. */
>>
>> @@ -6635,7 +6636,21 @@ build_lswitch_learn_fdb_op(
>>          return;
>>      }
>>
>> +    /* For non-ARP, non-ND Ethernet packets lookup their eth.src and learn
>> +     * it if not already known.
>> +     *
>> +     * For ARP packets, also look up arp.sha in the FDB.  For ND packets,
>> +     * look up nd.tll (NA) or nd.sll (NS).  They may differ from eth.src
>> +     * in specific cases.
>> +     *
>> +     * ND packets may lack the optional SLLAO/TLLAO option, in which case
>> +     * nd.sll/nd.tll is all-zeros so they need even more special
>> attention.
>> +     */
>>      bool remote = lsp_is_remote(op->nbsp);
>> +    const struct ovn_stage *lkup_stage = remote ? S_SWITCH_OUT_LOOKUP_FDB
>> +                                                : S_SWITCH_IN_LOOKUP_FDB;
>> +    const struct ovn_stage *put_stage = remote ? S_SWITCH_OUT_PUT_FDB
>> +                                               : S_SWITCH_IN_PUT_FDB;
>>
>>      if (remote || (!strcmp(op->nbsp->type, "") &&
>> lsp_can_learn_mac(op->nbsp))
>>          || lsp_is_switch(op->nbsp)
>> @@ -6646,20 +6661,177 @@ build_lswitch_learn_fdb_op(
>>          if (lsp_is_localnet(op->nbsp)) {
>>              ds_put_cstr(actions, "flags.localnet = 1; ");
>>          }
>> +        size_t match_len = match->length;
>> +        size_t actions_len = actions->length;
>> +
>> +        /* Lookup: Prio: 120, match: arp.sha == 0,
>> +         *                    action: REGBIT_LKUP_FDB =
>> lookup_fdb(eth.src);
>> +         *                            REGBIT_LKUP_FDB_DL_INNER = 1;
>> +         * Put:    Prio: 120, match: arp.sha == 0 && REGBIT_LKUP_FDB == 0,
>> +         *                    action: put_fdb(eth.src).
>> +         */
>> +        ds_put_cstr(match, " && arp.sha == 00:00:00:00:00:00");
>> +        ds_put_format(actions,
>> +                      REGBIT_LKUP_FDB " = lookup_fdb(inport, eth.src); "
>> +                      REGBIT_LKUP_FDB_DL_INNER " = 1; next;");
>> +        ovn_lflow_add(lflows, op->od, lkup_stage, 120,
>> +                      ds_cstr(match), ds_cstr(actions), op->lflow_ref,
>> +                      WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>> +
>> +        ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
>> +        ovn_lflow_add(lflows, op->od, put_stage, 120,
>> +                      ds_cstr(match), "put_fdb(inport, eth.src); next;",
>> +                      op->lflow_ref, WITH_IO_PORT(op->key),
>> +                      WITH_HINT(&op->nbsp->header_));
>> +
>> +        /* Lookup: Prio: 110, match: arp (i.e, arp.sha != 0)
>> +         *                    action: REGBIT_LKUP_FDB =
>> lookup_fdb(eth.src);
>> +         *                            REGBIT_LKUP_FDB_DL_INNER
>> +         *                              = lookup_fdb(arp.sha);
>> +         * Put:    Prio: 110, match: arp && (REGBIT_LKUP_FDB == 0
>> +         *                                   || REGBIT_LKUP_FDB_DL_INNER
>> == 0)
>> +         *                    action: put_fdb(eth.src); put_fdb(arp.sha).
>> +         */
>> +        ds_truncate(match, match_len);
>> +        ds_truncate(actions, actions_len);
>> +        ds_put_cstr(match, " && arp");
>> +        ds_put_format(actions,
>> +                      REGBIT_LKUP_FDB " = lookup_fdb(inport, eth.src); "
>> +                      REGBIT_LKUP_FDB_DL_INNER
>> +                        " = lookup_fdb(inport, arp.sha); next;");
>> +        ovn_lflow_add(lflows, op->od, lkup_stage, 110,
>> +                      ds_cstr(match), ds_cstr(actions), op->lflow_ref,
>> +                      WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>> +
>> +        ds_put_cstr(match, " && ("REGBIT_LKUP_FDB" == 0 "
>> +                                 "|| " REGBIT_LKUP_FDB_DL_INNER " == 0)");
>> +        ovn_lflow_add(lflows, op->od, put_stage, 110,
>> +                      ds_cstr(match), "put_fdb(inport, eth.src); "
>> +                                      "put_fdb(inport, arp.sha); "
>> +                                      "next;",
>> +                      op->lflow_ref, WITH_IO_PORT(op->key),
>> +                      WITH_HINT(&op->nbsp->header_));
>> +
>> +        /* Lookup: Prio: 120, match: nd.tll == 0,
>> +         *                    action: REGBIT_LKUP_FDB =
>> lookup_fdb(eth.src).
>> +         *                            REGBIT_LKUP_FDB_DL_INNER = 1;
>> +         * Put:    Prio: 120, match: nd.tll == 0 && REGBIT_LKUP_FDB == 0,
>> +         *                    action: put_fdb(eth.src).
>> +         */
>> +        ds_truncate(match, match_len);
>> +        ds_truncate(actions, actions_len);
>> +        ds_put_cstr(match, " && nd.tll == 00:00:00:00:00:00");
>> +        ds_put_format(actions,
>> +                      REGBIT_LKUP_FDB " = lookup_fdb(inport, eth.src); "
>> +                      REGBIT_LKUP_FDB_DL_INNER " = 1; next;");
>> +        ovn_lflow_add(lflows, op->od, lkup_stage, 120,
>> +                      ds_cstr(match), ds_cstr(actions), op->lflow_ref,
>> +                      WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>> +
>> +        ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
>> +        ovn_lflow_add(lflows, op->od, put_stage, 120,
>> +                      ds_cstr(match), "put_fdb(inport, eth.src); next;",
>> +                      op->lflow_ref, WITH_IO_PORT(op->key),
>> +                      WITH_HINT(&op->nbsp->header_));
>> +
>> +        /* Lookup: Prio: 110, match: nd_na (i.e, nd_na.tll != 0)
>> +         *                    action: REGBIT_LKUP_FDB =
>> lookup_fdb(eth.src);
>> +         *                            REGBIT_LKUP_FDB_DL_INNER
>> +         *                              = lookup_fdb(nd_na.tll);
>> +         * Put:    Prio: 110, match: nd_na
>> +         *                              && (REGBIT_LKUP_FDB == 0
>> +         *                                  || REGBIT_LKUP_FDB_DL_INNER
>> == 0)
>> +         *                    action: put_fdb(eth.src);
>> put_fdb(nd_na.tll).
>> +         */
>> +        ds_truncate(match, match_len);
>> +        ds_truncate(actions, actions_len);
>> +        ds_put_cstr(match, " && nd_na");
>> +        ds_put_format(actions,
>> +                      REGBIT_LKUP_FDB " = lookup_fdb(inport, eth.src); "
>> +                      REGBIT_LKUP_FDB_DL_INNER
>> +                        "= lookup_fdb(inport, nd.tll); next;");
>> +        ovn_lflow_add(lflows, op->od, lkup_stage, 110,
>> +                      ds_cstr(match), ds_cstr(actions), op->lflow_ref,
>> +                      WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>> +
>> +        ds_put_cstr(match, " && ("REGBIT_LKUP_FDB" == 0 "
>> +                                 "|| " REGBIT_LKUP_FDB_DL_INNER " == 0)");
>> +        ovn_lflow_add(lflows, op->od, put_stage, 110,
>> +                      ds_cstr(match), "put_fdb(inport, eth.src); "
>> +                                      "put_fdb(inport, nd.tll); "
>> +                                      "next;",
>> +                      op->lflow_ref, WITH_IO_PORT(op->key),
>> +                      WITH_HINT(&op->nbsp->header_));
>> +
>> +        /* Lookup: Prio: 120, match: nd.sll == 0,
>> +         *                    action: REGBIT_LKUP_FDB =
>> lookup_fdb(eth.src).
>> +         *                            REGBIT_LKUP_FDB_DL_INNER = 1;
>> +         * Put:    Prio: 120, match: nd.sll == 0 && REGBIT_LKUP_FDB == 0,
>> +         *                    action: put_fdb(eth.src).
>> +         */
>> +        ds_truncate(match, match_len);
>> +        ds_truncate(actions, actions_len);
>> +        ds_put_cstr(match, " && nd.sll == 00:00:00:00:00:00");
>> +        ds_put_format(actions,
>> +                      REGBIT_LKUP_FDB " = lookup_fdb(inport, eth.src); "
>> +                      REGBIT_LKUP_FDB_DL_INNER " = 1; next;");
>> +        ovn_lflow_add(lflows, op->od, lkup_stage, 120,
>> +                      ds_cstr(match), ds_cstr(actions), op->lflow_ref,
>> +                      WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>> +
>> +        ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
>> +        ovn_lflow_add(lflows, op->od, put_stage, 120,
>> +                      ds_cstr(match), "put_fdb(inport, eth.src); next;",
>> +                      op->lflow_ref, WITH_IO_PORT(op->key),
>> +                      WITH_HINT(&op->nbsp->header_));
>> +
>> +        /* Lookup: Prio: 110, match: nd_ns (i.e, nd.sll != 0)
>> +         *                    action: REGBIT_LKUP_FDB =
>> lookup_fdb(eth.src);
>> +         *                            REGBIT_LKUP_FDB_DL_INNER
>> +         *                              = lookup_fdb(nd.sll);
>> +         * Put:    Prio: 110, match: nd_ns
>> +         *                              && (REGBIT_LKUP_FDB == 0
>> +         *                                  || REGBIT_LKUP_FDB_DL_INNER
>> == 0)
>> +         *                    action: put_fdb(eth.src); put_fdb(nd.sll).
>> +         */
>> +        ds_truncate(match, match_len);
>> +        ds_truncate(actions, actions_len);
>> +        ds_put_cstr(match, " && nd_ns");
>> +        ds_put_format(actions,
>> +                      REGBIT_LKUP_FDB " = lookup_fdb(inport, eth.src); "
>> +                      REGBIT_LKUP_FDB_DL_INNER
>> +                        "= lookup_fdb(inport, nd.sll); next;");
>> +        ovn_lflow_add(lflows, op->od, lkup_stage, 110,
>> +                      ds_cstr(match), ds_cstr(actions), op->lflow_ref,
>> +                      WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>> +
>> +        ds_put_cstr(match, " && ("REGBIT_LKUP_FDB" == 0 "
>> +                                 "|| " REGBIT_LKUP_FDB_DL_INNER " == 0)");
>> +        ovn_lflow_add(lflows, op->od, put_stage, 110,
>> +                      ds_cstr(match), "put_fdb(inport, eth.src); "
>> +                                      "put_fdb(inport, nd.sll); "
>> +                                      "next;",
>> +                      op->lflow_ref, WITH_IO_PORT(op->key),
>> +                      WITH_HINT(&op->nbsp->header_));
>> +
>> +        /* Lookup: Prio: 100, match: any eth packet,
>> +         *                    action: REGBIT_LKUP_FDB =
>> lookup_fdb(eth.src).
>> +         * Put:    Prio: 100, match: any eth packet && REGBIT_LKUP_FDB ==
>> 0,
>> +         *                    action: put_fdb(eth.src).
>> +         */
>> +        ds_truncate(match, match_len);
>> +        ds_truncate(actions, actions_len);
>>          ds_put_format(actions, REGBIT_LKUP_FDB
>>                        " = lookup_fdb(inport, eth.src); next;");
>> -        ovn_lflow_add(lflows, op->od, remote ? S_SWITCH_OUT_LOOKUP_FDB
>> -                                             : S_SWITCH_IN_LOOKUP_FDB,
>> -                      100, ds_cstr(match), ds_cstr(actions),
>> op->lflow_ref,
>> +        ovn_lflow_add(lflows, op->od, lkup_stage, 100,
>> +                      ds_cstr(match), ds_cstr(actions), op->lflow_ref,
>>                        WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>>
>>          ds_put_cstr(match, " && "REGBIT_LKUP_FDB" == 0");
>> -        ds_clear(actions);
>> -        ds_put_cstr(actions, "put_fdb(inport, eth.src); next;");
>> -        ovn_lflow_add(lflows, op->od, remote ? S_SWITCH_OUT_PUT_FDB
>> -                                             : S_SWITCH_IN_PUT_FDB,
>> -                      100, ds_cstr(match), ds_cstr(actions),
>> op->lflow_ref,
>> -                      WITH_IO_PORT(op->key),
>> WITH_HINT(&op->nbsp->header_));
>> +        ovn_lflow_add(lflows, op->od, put_stage, 100,
>> +                      ds_cstr(match), "put_fdb(inport, eth.src); next;",
>> +                      op->lflow_ref, WITH_IO_PORT(op->key),
>> +                      WITH_HINT(&op->nbsp->header_));
>>      }
>>  }
>>
>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>> index 003bbf4146..feea50a25f 100644
>> --- a/ovn-nb.xml
>> +++ b/ovn-nb.xml
>> @@ -1965,12 +1965,23 @@
>>
>>            <dt><code>unknown</code></dt>
>>            <dd>
>> -            This indicates that the logical port has an unknown set of
>> Ethernet
>> -            addresses.  When an OVN logical switch processes a unicast
>> Ethernet
>> -            frame whose destination MAC address is not in any logical
>> port's
>> -            <ref column="addresses"/> column, it delivers it to the port
>> (or
>> -            ports) whose <ref column="addresses"/> columns include
>> -            <code>unknown</code>.
>> +            <p>
>> +              This indicates that the logical port has an unknown set of
>> +              Ethernet addresses.  When an OVN logical switch processes a
>> +              unicast Ethernet frame whose destination MAC address is not
>> in
>> +              any logical port's <ref column="addresses"/> column, it
>> delivers
>> +              it to the port (or ports) whose <ref column="addresses"/>
>> +              columns include <code>unknown</code>.
>> +            </p>
>> +            <p>
>> +              When <code>unknown</code> is present, FDB learning is
>> enabled
>> +              for this port: source MACs are recorded via
>> <code>put_fdb</code>
>> +              and destination MACs are resolved via <code>get_fdb</code>.
>> +              For ARP packets, the sender hardware address is also
>> learned.
>> +              For IPv6 Neighbor Advertisements, the target link-layer
>> address
>> +              is learned; for Neighbor Solicitations, the source
>> link-layer
>> +              address is learned.
>> +            </p>
>>            </dd>
>>
>>            <dt><code>dynamic</code></dt>
>> diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at
>> index 638d96f795..67f2b1a65f 100644
>> --- a/tests/ovn-ic.at
>> +++ b/tests/ovn-ic.at
>> @@ -4038,20 +4038,92 @@ dnl Check that FDB learning is enabled for remote
>> ports in the egress pipeline.
>>  AT_CHECK([grep -E "ls_out.*fdb.*spine-to-" az1/sbflows |
>> ovn_strip_lflows], [0], [dnl
>>    table=??(ls_out_lookup_fdb  ), priority=100  , match=(inport ==
>> "spine-to-ls2"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>>    table=??(ls_out_lookup_fdb  ), priority=100  , match=(inport ==
>> "spine-to-ls3"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls2" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls3" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls2" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls3" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_out_put_fdb     ), priority=100  , match=(inport ==
>> "spine-to-ls2" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>>    table=??(ls_out_put_fdb     ), priority=100  , match=(inport ==
>> "spine-to-ls3" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls2" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls3" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls2" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls3" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>>  ])
>>  AT_CHECK([grep -E "ls_out.*fdb.*spine-to-" az2/sbflows |
>> ovn_strip_lflows], [0], [dnl
>>    table=??(ls_out_lookup_fdb  ), priority=100  , match=(inport ==
>> "spine-to-ls1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>>    table=??(ls_out_lookup_fdb  ), priority=100  , match=(inport ==
>> "spine-to-ls3"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls1" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls3" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls1" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls3" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_out_put_fdb     ), priority=100  , match=(inport ==
>> "spine-to-ls1" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>>    table=??(ls_out_put_fdb     ), priority=100  , match=(inport ==
>> "spine-to-ls3" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls1" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls3" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls3" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls1" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls3" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls3" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>>  ])
>>  AT_CHECK([grep -E "ls_out.*fdb.*spine-to-" az3/sbflows |
>> ovn_strip_lflows], [0], [dnl
>>    table=??(ls_out_lookup_fdb  ), priority=100  , match=(inport ==
>> "spine-to-ls1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>>    table=??(ls_out_lookup_fdb  ), priority=100  , match=(inport ==
>> "spine-to-ls2"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls1" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls2" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls1" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls2" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_out_lookup_fdb  ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_out_put_fdb     ), priority=100  , match=(inport ==
>> "spine-to-ls1" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>>    table=??(ls_out_put_fdb     ), priority=100  , match=(inport ==
>> "spine-to-ls2" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls1" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls1" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls2" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=110  , match=(inport ==
>> "spine-to-ls2" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls1" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls1" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls2" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_out_put_fdb     ), priority=120  , match=(inport ==
>> "spine-to-ls2" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>>  ])
>>
>>  check ovn-ic-nbctl --wait=sb sync
>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> index f8c1449182..11d181e6bb 100644
>> --- a/tests/ovn-northd.at
>> +++ b/tests/ovn-northd.at
>> @@ -8204,7 +8204,19 @@ AT_CAPTURE_FILE([S1flows])
>>  AT_CHECK([grep -e "ls_in_l2_lkup.*S1-vm1" S1flows | grep -e
>> "match=(eth.dst == 50:54:00:00:00:01)"], [1], [])
>>  AT_CHECK([grep -e "ls_in_.*_fdb.*S1-vm1" S1flows | ovn_strip_lflows],
>> [0], [dnl
>>    table=??(ls_in_lookup_fdb   ), priority=100  , match=(inport ==
>> "S1-vm1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "S1-vm1" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "S1-vm1" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "S1-vm1" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "S1-vm1" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "S1-vm1" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "S1-vm1" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_in_put_fdb      ), priority=100  , match=(inport ==
>> "S1-vm1" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "S1-vm1" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "S1-vm1" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "S1-vm1" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "S1-vm1" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "S1-vm1" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "S1-vm1" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>>  ])
>>
>>  #Verify the flows for a non-default port type (localnet port)
>> @@ -8217,6 +8229,46 @@ AT_CLEANUP
>>  ])
>>
>>
>> +OVN_FOR_EACH_NORTHD_NO_HV([
>> +AT_SETUP([FDB learning includes ARP/ND inner MAC addresses])
>> +AT_KEYWORDS([ovn fdb arp nd])
>> +ovn_start
>> +
>> +check ovn-nbctl ls-add ls0
>> +check ovn-nbctl lsp-add ls0 p1
>> +check ovn-nbctl lsp-set-addresses p1 "fa:16:3e:00:00:01 10.0.0.1" unknown
>> +check ovn-nbctl --wait=sb sync
>> +
>> +ovn-sbctl dump-flows ls0 > ls0flows
>> +AT_CAPTURE_FILE([ls0flows])
>> +
>> +dnl Standard lookup/put flows at priority 100 for eth.src.
>> +AT_CHECK([grep -e "ls_in_lookup_fdb" ls0flows | grep "p1" | grep -q
>> "priority=100"])
>> +AT_CHECK([grep -e "ls_in_put_fdb" ls0flows | grep "p1" | grep -q
>> "priority=100"])
>> +
>> +dnl ARP-specific lookup flow at priority 110 with lookup_fdb for
>> +dnl both eth.src and arp.sha.
>> +AT_CHECK([grep -e "ls_in_lookup_fdb" ls0flows | grep "p1" | grep
>> "priority=110" | grep -q "arp.sha"])
>> +
>> +dnl ARP-specific put_fdb flow at priority 110 that learns both
>> +dnl eth.src and arp.sha, guarded by the OR of both regbits.
>> +AT_CHECK([grep -e "ls_in_put_fdb" ls0flows | grep "p1" | grep
>> "priority=110" | grep "arp.sha" | grep -q "reg0\[[22\]]"])
>> +
>> +dnl ND NA lookup and put flows at priority 110 for nd.tll.
>> +dnl The flows must include a guard against all-zero nd.tll (no TLLAO).
>> +AT_CHECK([grep -e "ls_in_lookup_fdb" ls0flows | grep "p1" | grep
>> "priority=120" | grep -q "nd.tll == 00:00:00:00:00:00"])
>> +AT_CHECK([grep -e "ls_in_put_fdb" ls0flows | grep "p1" | grep
>> "priority=120" | grep -q "nd.tll == 00:00:00:00:00:00"])
>> +
>> +dnl ND NS lookup and put flows at priority 110 for nd.sll.
>> +dnl The flows must include a guard against all-zero nd.sll (no SLLAO).
>> +AT_CHECK([grep -e "ls_in_lookup_fdb" ls0flows | grep "p1" | grep
>> "priority=120" | grep -q "nd.sll == 00:00:00:00:00:00"])
>> +AT_CHECK([grep -e "ls_in_put_fdb" ls0flows | grep "p1" | grep
>> "priority=120" | grep -q "nd.sll == 00:00:00:00:00:00"])
>> +
>> +OVN_CLEANUP_NORTHD
>> +AT_CLEANUP
>> +])
>> +
>> +
>>  OVN_FOR_EACH_NORTHD_NO_HV([
>>  AT_SETUP([check options:pkt_clone_type for LSP])
>>  ovn_start
>> @@ -8288,7 +8340,19 @@ AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|unknown"
>> S1flows | ovn_strip_lflows], [0],
>>  dnl Check that FDB learning is enabled for the switch port.
>>  AT_CHECK([grep -E "ls_.*fdb.*S1-" S1flows | ovn_strip_lflows], [0], [dnl
>>    table=??(ls_in_lookup_fdb   ), priority=100  , match=(inport ==
>> "S1-S2"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S1-S2"
>> && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] =
>> lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S1-S2"
>> && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]]=
>> lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S1-S2"
>> && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]]=
>> lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S1-S2"
>> && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S1-S2"
>> && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S1-S2"
>> && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_in_put_fdb      ), priority=100  , match=(inport == "S1-S2"
>> && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S1-S2"
>> && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S1-S2"
>> && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S1-S2"
>> && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S1-S2"
>> && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S1-S2"
>> && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S1-S2"
>> && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>>  ])
>>
>>  ovn-sbctl dump-flows S2 > S2flows
>> @@ -8305,7 +8369,19 @@ AT_CHECK([grep -E "ls_in_l2_lkup.*S2-|unknown"
>> S2flows | ovn_strip_lflows], [0],
>>  dnl Check that FDB learning is enabled for the switch port.
>>  AT_CHECK([grep -E "ls_.*fdb.*S2-" S2flows | ovn_strip_lflows], [0], [dnl
>>    table=??(ls_in_lookup_fdb   ), priority=100  , match=(inport ==
>> "S2-S1"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S2-S1"
>> && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] =
>> lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S2-S1"
>> && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]]=
>> lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S2-S1"
>> && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]]=
>> lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S2-S1"
>> && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S2-S1"
>> && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S2-S1"
>> && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_in_put_fdb      ), priority=100  , match=(inport == "S2-S1"
>> && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S2-S1"
>> && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S2-S1"
>> && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S2-S1"
>> && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S2-S1"
>> && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S2-S1"
>> && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S2-S1"
>> && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>>  ])
>>
>>  dnl Add an explicit address to S1-S2 indicating that the port with
>> @@ -8326,7 +8402,19 @@ AT_CHECK([grep -E "ls_in_l2_lkup.*S1-|unknown"
>> S1flows2 | ovn_strip_lflows], [0]
>>  dnl Check that FDB learning is still enabled for the switch port.
>>  AT_CHECK([grep -E "ls_.*fdb.*S1-" S1flows | ovn_strip_lflows], [0], [dnl
>>    table=??(ls_in_lookup_fdb   ), priority=100  , match=(inport ==
>> "S1-S2"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S1-S2"
>> && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] =
>> lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S1-S2"
>> && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]]=
>> lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "S1-S2"
>> && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]]=
>> lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S1-S2"
>> && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S1-S2"
>> && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "S1-S2"
>> && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_in_put_fdb      ), priority=100  , match=(inport == "S1-S2"
>> && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S1-S2"
>> && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S1-S2"
>> && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport == "S1-S2"
>> && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)), action=(put_fdb(inport,
>> eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S1-S2"
>> && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S1-S2"
>> && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport == "S1-S2"
>> && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>>  ])
>>
>>  OVN_CLEANUP_NORTHD
>> @@ -10681,9 +10769,21 @@ AT_CHECK([ovn-nbctl --wait=sb lsp-set-options
>> ln_port localnet_learn_fdb=true])
>>  AT_CHECK([ovn-sbctl dump-flows ls0 | grep -e 'ls_in_\(put\|lookup\)_fdb'
>> | ovn_strip_lflows], [0], [dnl
>>    table=??(ls_in_lookup_fdb   ), priority=0    , match=(1), action=(next;)
>>    table=??(ls_in_lookup_fdb   ), priority=100  , match=(inport ==
>> "ln_port"), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport,
>> eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "ln_port" && arp), action=(flags.localnet = 1; reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = lookup_fdb(inport, arp.sha);
>> next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "ln_port" && nd_na), action=(flags.localnet = 1; reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "ln_port" && nd_ns), action=(flags.localnet = 1; reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "ln_port" && arp.sha == 00:00:00:00:00:00), action=(flags.localnet = 1;
>> reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "ln_port" && nd.sll == 00:00:00:00:00:00), action=(flags.localnet = 1;
>> reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "ln_port" && nd.tll == 00:00:00:00:00:00), action=(flags.localnet = 1;
>> reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_in_lookup_fdb   ), priority=50   , match=(inport ==
>> "ln_port"), action=(flags.localnet = 1; next;)
>>    table=??(ls_in_put_fdb      ), priority=0    , match=(1), action=(next;)
>>    table=??(ls_in_put_fdb      ), priority=100  , match=(inport ==
>> "ln_port" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "ln_port" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "ln_port" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "ln_port" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "ln_port" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "ln_port" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "ln_port" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>>  ])
>>
>>  # Disable 'localnet_learn_fdb' and check the flows
>> @@ -11104,6 +11204,12 @@ dnl priority 50 fallback.
>>  AT_CHECK([ovn-sbctl dump-flows ls1 | grep -e 'ls_in_lookup_fdb' |
>> ovn_strip_lflows], [0], [dnl
>>    table=??(ls_in_lookup_fdb   ), priority=0    , match=(1), action=(next;)
>>    table=??(ls_in_lookup_fdb   ), priority=100  , match=(inport == "ln1"),
>> action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "ln1"
>> && arp), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "ln1"
>> && nd_na), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport == "ln1"
>> && nd_ns), action=(flags.localnet = 1; reg0[[11]] = lookup_fdb(inport,
>> eth.src); reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "ln1"
>> && arp.sha == 00:00:00:00:00:00), action=(flags.localnet = 1; reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "ln1"
>> && nd.sll == 00:00:00:00:00:00), action=(flags.localnet = 1; reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport == "ln1"
>> && nd.tll == 00:00:00:00:00:00), action=(flags.localnet = 1; reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_in_lookup_fdb   ), priority=50   , match=(inport == "ln1"),
>> action=(flags.localnet = 1; next;)
>>  ])
>>
>> @@ -23715,9 +23821,21 @@ AT_CAPTURE_FILE([ls1flows])
>>
>>  AT_CHECK([grep "ls1-to-spine" ls1flows | ovn_strip_lflows | sort], [0],
>> [dnl
>>    table=??(ls_in_lookup_fdb   ), priority=100  , match=(inport ==
>> "ls1-to-spine"), action=(reg0[[11]] = lookup_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "ls1-to-spine" && arp), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]] = lookup_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "ls1-to-spine" && nd_na), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , match=(inport ==
>> "ls1-to-spine" && nd_ns), action=(reg0[[11]] = lookup_fdb(inport, eth.src);
>> reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "ls1-to-spine" && arp.sha == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "ls1-to-spine" && nd.sll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , match=(inport ==
>> "ls1-to-spine" && nd.tll == 00:00:00:00:00:00), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>    table=??(ls_in_pre_acl      ), priority=110  , match=(ip && inport ==
>> "ls1-to-spine"), action=(next;)
>>    table=??(ls_in_pre_lb       ), priority=110  , match=(ip && inport ==
>> "ls1-to-spine"), action=(next;)
>>    table=??(ls_in_put_fdb      ), priority=100  , match=(inport ==
>> "ls1-to-spine" && reg0[[11]] == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "ls1-to-spine" && arp && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "ls1-to-spine" && nd_na && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , match=(inport ==
>> "ls1-to-spine" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] == 0)),
>> action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "ls1-to-spine" && arp.sha == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "ls1-to-spine" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , match=(inport ==
>> "ls1-to-spine" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] == 0),
>> action=(put_fdb(inport, eth.src); next;)
>>    table=??(ls_out_pre_acl     ), priority=110  , match=(ip && outport ==
>> "ls1-to-spine"), action=(next;)
>>    table=??(ls_out_pre_lb      ), priority=110  , match=(ip && outport ==
>> "ls1-to-spine"), action=(next;)
>>  ])
>> diff --git a/tests/ovn.at b/tests/ovn.at
>> index 136b825f98..e095e50bff 100644
>> --- a/tests/ovn.at
>> +++ b/tests/ovn.at
>> @@ -34090,6 +34090,30 @@ match=(1), action=(next;)
>>  match=(inport == "sw0-p1"), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); next;)
>>    table=??(ls_in_lookup_fdb   ), priority=100  , dnl
>>  match=(inport == "sw0-p3"), action=(reg0[[11]] = lookup_fdb(inport,
>> eth.src); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , dnl
>> +match=(inport == "sw0-p1" && arp), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = lookup_fdb(inport, arp.sha);
>> next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , dnl
>> +match=(inport == "sw0-p1" && nd_na), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , dnl
>> +match=(inport == "sw0-p1" && nd_ns), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , dnl
>> +match=(inport == "sw0-p3" && arp), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]] = lookup_fdb(inport, arp.sha);
>> next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , dnl
>> +match=(inport == "sw0-p3" && nd_na), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]]= lookup_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=110  , dnl
>> +match=(inport == "sw0-p3" && nd_ns), action=(reg0[[11]] =
>> lookup_fdb(inport, eth.src); reg0[[22]]= lookup_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , dnl
>> +match=(inport == "sw0-p1" && arp.sha == 00:00:00:00:00:00),
>> action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , dnl
>> +match=(inport == "sw0-p1" && nd.sll == 00:00:00:00:00:00),
>> action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , dnl
>> +match=(inport == "sw0-p1" && nd.tll == 00:00:00:00:00:00),
>> action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , dnl
>> +match=(inport == "sw0-p3" && arp.sha == 00:00:00:00:00:00),
>> action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , dnl
>> +match=(inport == "sw0-p3" && nd.sll == 00:00:00:00:00:00),
>> action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>> +  table=??(ls_in_lookup_fdb   ), priority=120  , dnl
>> +match=(inport == "sw0-p3" && nd.tll == 00:00:00:00:00:00),
>> action=(reg0[[11]] = lookup_fdb(inport, eth.src); reg0[[22]] = 1; next;)
>>  ])
>>
>>  AT_CHECK([grep "ls_in_put_fdb" sw0flows | ovn_strip_lflows], [0], [dnl
>> @@ -34099,6 +34123,30 @@ match=(1), action=(next;)
>>  match=(inport == "sw0-p1" && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>>    table=??(ls_in_put_fdb      ), priority=100  , dnl
>>  match=(inport == "sw0-p3" && reg0[[11]] == 0), action=(put_fdb(inport,
>> eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , dnl
>> +match=(inport == "sw0-p1" && arp && (reg0[[11]] == 0 || reg0[[22]] ==
>> 0)), action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , dnl
>> +match=(inport == "sw0-p1" && nd_na && (reg0[[11]] == 0 || reg0[[22]] ==
>> 0)), action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , dnl
>> +match=(inport == "sw0-p1" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] ==
>> 0)), action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , dnl
>> +match=(inport == "sw0-p3" && arp && (reg0[[11]] == 0 || reg0[[22]] ==
>> 0)), action=(put_fdb(inport, eth.src); put_fdb(inport, arp.sha); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , dnl
>> +match=(inport == "sw0-p3" && nd_na && (reg0[[11]] == 0 || reg0[[22]] ==
>> 0)), action=(put_fdb(inport, eth.src); put_fdb(inport, nd.tll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=110  , dnl
>> +match=(inport == "sw0-p3" && nd_ns && (reg0[[11]] == 0 || reg0[[22]] ==
>> 0)), action=(put_fdb(inport, eth.src); put_fdb(inport, nd.sll); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , dnl
>> +match=(inport == "sw0-p1" && arp.sha == 00:00:00:00:00:00 && reg0[[11]]
>> == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , dnl
>> +match=(inport == "sw0-p1" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] ==
>> 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , dnl
>> +match=(inport == "sw0-p1" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] ==
>> 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , dnl
>> +match=(inport == "sw0-p3" && arp.sha == 00:00:00:00:00:00 && reg0[[11]]
>> == 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , dnl
>> +match=(inport == "sw0-p3" && nd.sll == 00:00:00:00:00:00 && reg0[[11]] ==
>> 0), action=(put_fdb(inport, eth.src); next;)
>> +  table=??(ls_in_put_fdb      ), priority=120  , dnl
>> +match=(inport == "sw0-p3" && nd.tll == 00:00:00:00:00:00 && reg0[[11]] ==
>> 0), action=(put_fdb(inport, eth.src); next;)
>>  ])
>>
>>  # Send a packet from sw0-p1 with a different mac not present
>> @@ -34440,6 +34488,88 @@ OVN_CLEANUP([hv1], [hv2], [hv3])
>>  AT_CLEANUP
>>  ])
>>
>> +OVN_FOR_EACH_NORTHD([
>> +AT_SETUP([ovn -- FDB learning from ARP sender hardware address])
>>
> 
> Let's also extend this with ND? Just to make sure the
> flows work for IPv6 too.
> 

OK, I'll do that.

> +AT_KEYWORDS([fdb arp])
>> +CHECK_SCAPY
>> +ovn_start
>> +
>> +dnl Three ports on a logical switch.  p1 and p2 have "unknown" to enable
>> +dnl FDB learning.  pr is a regular client.
>> +check ovn-nbctl ls-add ls0
>> +check ovn-nbctl lsp-add ls0 p1
>> +check ovn-nbctl lsp-set-addresses p1 "fa:16:3e:00:00:01 10.0.0.1" unknown
>> +check ovn-nbctl lsp-add ls0 p2
>> +check ovn-nbctl lsp-set-addresses p2 "fa:16:3e:00:00:02 10.0.0.2" unknown
>> +check ovn-nbctl lsp-add ls0 p3
>> +check ovn-nbctl lsp-set-addresses p3 "fa:16:3e:00:00:03 10.0.0.3"
>> +
>> +net_add n1
>> +sim_add hv1
>> +as hv1
>> +check ovs-vsctl add-br br-phys
>> +ovn_attach n1 br-phys 192.168.0.1
>> +
>> +check ovs-vsctl -- add-port br-int vif1 -- \
>> +    set interface vif1 external-ids:iface-id=p1 \
>> +    options:tx_pcap=hv1/vif1-tx.pcap \
>> +    options:rxq_pcap=hv1/vif1-rx.pcap
>> +check ovs-vsctl -- add-port br-int vif2 -- \
>> +    set interface vif2 external-ids:iface-id=p2 \
>> +    options:tx_pcap=hv1/vif2-tx.pcap \
>> +    options:rxq_pcap=hv1/vif2-rx.pcap
>> +check ovs-vsctl -- add-port br-int vif3 -- \
>> +    set interface vif3 external-ids:iface-id=p3 \
>> +    options:tx_pcap=hv1/vif3-tx.pcap \
>> +    options:rxq_pcap=hv1/vif3-rx.pcap
>> +
>> +wait_for_ports_up
>> +check ovn-nbctl --wait=hv sync
>> +
>> +dnl p1 sends a GARP with eth.src=phys_mac, arp.sha=other_mac.
>> +dnl where phys_mac=fa:16:3e:00:00:01 and other_mac=fa:16:3e:aa:bb:cc
>> +dnl Both MACs should be learned into the FDB for p1.
>> +
>> +garp=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff', src='fa:16:3e:00:00:01')/ \
>> +                ARP(op=1, hwsrc='fa:16:3e:aa:bb:cc', psrc='10.0.0.100', \
>> +                    hwdst='00:00:00:00:00:00', pdst='10.0.0.100')")
>> +as hv1 ovs-appctl netdev-dummy/receive vif1 $garp
>> +
>> +dnl Wait for arp.sha (other_mac) to appear in the FDB table.
>> +OVS_WAIT_UNTIL([ovn-sbctl find fdb mac='"fa:16:3e:aa:bb:cc"' | grep -q
>> port_key])
>> +
>> +dnl Verify both MACs are in the FDB.  Each put_fdb generates a separate
>> +dnl packet-in with an independent random delay, so both need a wait.
>> +wait_row_count fdb 1 mac='"fa:16:3e:00:00:01"'
>> +wait_row_count fdb 1 mac='"fa:16:3e:aa:bb:cc"'
>> +
>> +dnl Send unicast traffic from p2 to other_mac.  The FDB should
>> +dnl only direct it to p1.
>> +as hv1 reset_pcap_file vif1 hv1/vif1
>> +as hv1 reset_pcap_file vif2 hv1/vif2
>> +
>> +packet=$(fmt_pkt "Ether(dst='fa:16:3e:aa:bb:cc',
>> src='fa:16:3e:00:00:03')/ \
>> +                  IP(src='10.0.0.3', dst='10.0.0.100')/ \
>> +                  UDP(sport=12345, dport=5678)/ \
>> +                  Raw(b'to_other_mac')")
>> +echo $packet > p1.expected
>> +as hv1 ovs-appctl netdev-dummy/receive vif3 $packet
>> +
>> +packet=$(fmt_pkt "Ether(dst='fa:16:3e:00:00:01',
>> src='fa:16:3e:00:00:03')/ \
>> +                  IP(src='10.0.0.3', dst='10.0.0.101')/ \
>> +                  UDP(sport=12345, dport=5678)/ \
>> +                  Raw(b'to_other_mac')")
>> +echo $packet >> p1.expected
>> +as hv1 ovs-appctl netdev-dummy/receive vif3 $packet
>> +
>> +> p2.expected
>> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [p1.expected])
>> +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [p2.expected])
>> +
>> +OVN_CLEANUP([hv1])
>> +AT_CLEANUP
>> +])
>> +
>>  OVN_FOR_EACH_NORTHD([
>>  AT_SETUP([container port changed to normal port and then deleted])
>>  ovn_start
>> @@ -41116,12 +41246,12 @@ sleep_sb
>>  sleep_controller hv2
>>
>>  AS_BOX([$(date +%H:%M:%S.%03N) Sending ARP 14 -> 24])
>> -arp_req=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff',
>> src='00:00:00:00:10:14')/ARP(op=1, pdst='192.168.10.24',
>> psrc='192.168.10.14')")
>> +arp_req=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff',
>> src='00:00:00:00:10:14')/ARP(op=1, hwsrc='00:00:00:00:10:14',
>> pdst='192.168.10.24', psrc='192.168.10.14')")
>>  as hv1 ovs-appctl netdev-dummy/receive vif14 $arp_req
>>  ensure_controller_run hv1
>>
>>  AS_BOX([$(date +%H:%M:%S.%03N) Sending ARP 24 -> 14])
>> -arp_req=$(fmt_pkt "Ether(dst='00:00:00:00:10:14',
>> src='00:00:00:00:10:24')/ARP(op=2, pdst='192.168.10.14',
>> psrc='192.168.10.24')")
>> +arp_req=$(fmt_pkt "Ether(dst='00:00:00:00:10:14',
>> src='00:00:00:00:10:24')/ARP(op=2, hwsrc='00:00:00:00:10:24',
>> hwdst='00:00:00:00:10:14', pdst='192.168.10.14', psrc='192.168.10.24')")
>>  as hv2 ovs-appctl netdev-dummy/receive vif24 $arp_req
>>  sleep 1
>>
>> @@ -41149,12 +41279,12 @@ sleep_sb
>>  sleep_controller hv2
>>
>>  AS_BOX([$(date +%H:%M:%S.%03N) Sending ARP 15 -> 25])
>> -arp_req=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff',
>> src='00:00:00:00:10:15')/ARP(op=1, pdst='192.168.10.25',
>> psrc='192.168.10.15')")
>> +arp_req=$(fmt_pkt "Ether(dst='ff:ff:ff:ff:ff:ff',
>> src='00:00:00:00:10:15')/ARP(op=1, hwsrc='00:00:00:00:10:15',
>> pdst='192.168.10.25', psrc='192.168.10.15')")
>>  as hv1 ovs-appctl netdev-dummy/receive vif15 $arp_req
>>  ensure_controller_run hv1
>>
>>  AS_BOX([$(date +%H:%M:%S.%03N) Sending ARP 25 -> 15])
>> -arp_req=$(fmt_pkt "Ether(dst='00:00:00:00:10:15',
>> src='00:00:00:00:10:25')/ARP(op=2, pdst='192.168.10.15',
>> psrc='192.168.10.25')")
>> +arp_req=$(fmt_pkt "Ether(dst='00:00:00:00:10:15',
>> src='00:00:00:00:10:25')/ARP(op=2, hwsrc='00:00:00:00:10:25',
>> hwdst='00:00:00:00:10:15', pdst='192.168.10.15', psrc='192.168.10.25')")
>>  as hv2 ovs-appctl netdev-dummy/receive vif25 $arp_req
>>
>>  sleep 1
>> @@ -41178,7 +41308,10 @@ expected_flow_count=$(($expected_flow_count + 4))
>>  check_flow_count hv1 $expected_flow_count
>>  check_flow_count hv2 $expected_flow_count
>>
>> -AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc
>> -l`])
>> +# 5 original packet-ins + 3 extra for arp.sha learning
>> +# (one per ARP sent from hv1: vif14->vif24, vif15->vif25,
>> +#  vif99->unknown).
>> +AT_CHECK([test 8 = `cat hv1/ovs-vswitchd.log | grep NXT_PACKET_IN2 | wc
>> -l`])
>>  # Do not check number of PACKET_IN2 on hv2, as vif14 => 99 as well as
>> vif15 => vif25
>>  # might have caused PACKET_IN2, but without any guarantee.
>>  # e.g. (1) packet => hv1 ovs, (2) PACKET_IN2 => hv1 ovn, (3) FDB => sb,
>> @@ -41190,7 +41323,7 @@ AT_CHECK([test 5 = `cat hv1/ovs-vswitchd.log |
>> grep NXT_PACKET_IN2 | wc -l`])
>>  # hv1 is expected to be stable.
>>
>>  AS_BOX([$(date +%H:%M:%S.%03N) Packet from external])
>> -arp_req=$(fmt_pkt "Ether(dst='00:00:00:00:10:50',
>> src='00:00:00:00:10:40')/ARP(op=2, pdst='192.168.10.50',
>> psrc='192.168.10.40')")
>> +arp_req=$(fmt_pkt "Ether(dst='00:00:00:00:10:50',
>> src='00:00:00:00:10:40')/ARP(op=2, hwsrc='00:00:00:00:10:40',
>> hwdst='00:00:00:00:10:50', pdst='192.168.10.50', psrc='192.168.10.40')")
>>  as hv2 ovs-appctl netdev-dummy/receive ext0 $arp_req
>>  wait_column "$ln_port_key" fdb port_key mac='"00:00:00:00:10:40"'
>>
>> --
>> 2.55.0
>>
>> _______________________________________________
>> dev mailing list
>> [email protected]
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
>>
> Aren't we missing an update to ovn-logical-flows.7.rst?
> 

We are, I still dislike having to do that TBH but I will.

Thanks again for the review!

Regards,
Dumitru

> Regards,
> Ales
> 

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to