bbaaef
From 036599bdfa4c10d69c1bcae407fb75ea841fa834 Mon Sep 17 00:00:00 2001
bbaaef
From: Numan Siddique <nusiddiq@redhat.com>
bbaaef
Date: Thu, 5 Sep 2019 00:15:38 +0530
bbaaef
Subject: [PATCH 3/4] Learn the mac binding only if required
bbaaef
bbaaef
OVN has the actions - put_arp and put_nd to learn the mac bindings from the
bbaaef
ARP/ND packets. These actions update the Southbound MAC_Binding table.
bbaaef
These actions translates to controller actions. Whenever pinctrl thread
bbaaef
receives such packets, it wakes up the main ovn-controller thread.
bbaaef
If the MAC_Binding table is already upto date, this results
bbaaef
in unnecessary CPU cyles. There are some security implications as well.
bbaaef
A rogue VM can flood broadcast ARP request/reply packets and this
bbaaef
could cause DoS issues. A physical switch may send periodic GARPs
bbaaef
and these packets hit ovn-controllers.
bbaaef
bbaaef
This patch solves these problems by learning the mac bindings only if
bbaaef
required. There is no need to apply the put_arp/put_nd action if the
bbaaef
Southbound MAC_Binding row is upto date.
bbaaef
bbaaef
New actions - lookup_arp and lookup_nd are added which looks up the
bbaaef
IP, MAC pair in the mac_binding table and stores the result in a
bbaaef
register. 1 if lookup is successful, 0 otherwise.
bbaaef
bbaaef
ovn-northd adds 2 new stages - LOOKUP_NEIGHBOR and LEARN_NEIGHBOR before
bbaaef
IP_INPUT in the router ingress pipeline.c. The LOOKUP_NEIGHBOR stage
bbaaef
adds flows to do the lookup in the mac_binding table and the LEARN_NEIGHBOR
bbaaef
adds flows to learn the neighbors only if require.
bbaaef
bbaaef
The lflow module of ovn-controller adds OF flows in table 67 (OFTABLE_MAC_LOOKUP)
bbaaef
for each mac_binding entry with the match reg0 = ip && eth.src = mac with
bbaaef
the action - load:1->reg10[6]
bbaaef
bbaaef
Eg:
bbaaef
table=31, priority=100,arp,reg0=0xaca8006f,reg14=0x3,metadata=0x3,dl_src=00:44:00:00:00:04
bbaaef
          actions=load:1->NXM_NX_REG10[6]
bbaaef
bbaaef
This patch should also address the issue reported in 'Reported-at'
bbaaef
bbaaef
Reported-at: https://bugzilla.redhat.com/1729846
bbaaef
Reported-by: Haidong Li <haili@redhat.com>
bbaaef
CC: Han ZHou <hzhou8@ebay.com>
bbaaef
CC: Dumitru Ceara <dceara@redhat.com>
bbaaef
Acked-by: Han ZHou <hzhou8@ebay.com>
bbaaef
Tested-by: Dumitru Ceara <dceara@redhat.com>
bbaaef
Signed-off-by: Numan Siddique <nusiddiq@redhat.com>
bbaaef
---
bbaaef
 include/ovn/actions.h        |  13 ++
bbaaef
 include/ovn/logical-fields.h |   4 +
bbaaef
 ovn/controller/lflow.c       |  37 ++++-
bbaaef
 ovn/controller/lflow.h       |   1 +
bbaaef
 ovn/lib/actions.c            | 114 ++++++++++++++
bbaaef
 ovn/northd/ovn-northd.8.xml  | 212 ++++++++++++++++---------
bbaaef
 ovn/northd/ovn-northd.c      | 205 ++++++++++++++-----------
bbaaef
 ovn/ovn-architecture.7.xml   |  18 +++
bbaaef
 ovn/ovn-sb.xml               |  57 +++++++
bbaaef
 ovn/utilities/ovn-trace.c    |  69 +++++++++
bbaaef
 tests/ovn.at                 | 290 ++++++++++++++++++++++++++++++++++-
bbaaef
 tests/test-ovn.c             |   1 +
bbaaef
 12 files changed, 844 insertions(+), 177 deletions(-)
bbaaef
bbaaef
diff --git a/include/ovn/actions.h b/include/ovn/actions.h
bbaaef
index 145f27f25..4e2f4d28d 100644
bbaaef
--- a/include/ovn/actions.h
bbaaef
+++ b/include/ovn/actions.h
bbaaef
@@ -73,8 +73,10 @@ struct ovn_extend_table;
bbaaef
     OVNACT(ND_NA_ROUTER,      ovnact_nest)            \
bbaaef
     OVNACT(GET_ARP,           ovnact_get_mac_bind)    \
bbaaef
     OVNACT(PUT_ARP,           ovnact_put_mac_bind)    \
bbaaef
+    OVNACT(LOOKUP_ARP,        ovnact_lookup_mac_bind) \
bbaaef
     OVNACT(GET_ND,            ovnact_get_mac_bind)    \
bbaaef
     OVNACT(PUT_ND,            ovnact_put_mac_bind)    \
bbaaef
+    OVNACT(LOOKUP_ND,         ovnact_lookup_mac_bind) \
bbaaef
     OVNACT(PUT_DHCPV4_OPTS,   ovnact_put_opts)        \
bbaaef
     OVNACT(PUT_DHCPV6_OPTS,   ovnact_put_opts)        \
bbaaef
     OVNACT(SET_QUEUE,         ovnact_set_queue)       \
bbaaef
@@ -266,6 +268,15 @@ struct ovnact_put_mac_bind {
bbaaef
     struct expr_field mac;      /* 48-bit Ethernet address. */
bbaaef
 };
bbaaef
 
bbaaef
+/* OVNACT_LOOKUP_ARP, OVNACT_LOOKUP_ND. */
bbaaef
+struct ovnact_lookup_mac_bind {
bbaaef
+    struct ovnact ovnact;
bbaaef
+    struct expr_field dst;      /* 1-bit destination field. */
bbaaef
+    struct expr_field port;     /* Logical port name. */
bbaaef
+    struct expr_field ip;       /* 32-bit or 128-bit IP address. */
bbaaef
+    struct expr_field mac;      /* 48-bit Ethernet address. */
bbaaef
+};
bbaaef
+
bbaaef
 struct ovnact_gen_option {
bbaaef
     const struct gen_opts_map *option;
bbaaef
     struct expr_constant_set value;
bbaaef
@@ -628,6 +639,8 @@ struct ovnact_encode_params {
bbaaef
     uint8_t output_ptable;      /* OpenFlow table for 'output' to resubmit. */
bbaaef
     uint8_t mac_bind_ptable;    /* OpenFlow table for 'get_arp'/'get_nd' to
bbaaef
                                    resubmit. */
bbaaef
+    uint8_t mac_lookup_ptable;  /* OpenFlow table for
bbaaef
+                                   'lookup_arp'/'lookup_nd' to resubmit. */
bbaaef
 };
bbaaef
 
bbaaef
 void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
bbaaef
diff --git a/include/ovn/logical-fields.h b/include/ovn/logical-fields.h
bbaaef
index 9bac8e027..9b7c34fb7 100644
bbaaef
--- a/include/ovn/logical-fields.h
bbaaef
+++ b/include/ovn/logical-fields.h
bbaaef
@@ -56,6 +56,7 @@ enum mff_log_flags_bits {
bbaaef
     MLF_FORCE_SNAT_FOR_LB_BIT = 3,
bbaaef
     MLF_LOCAL_ONLY_BIT = 4,
bbaaef
     MLF_NESTED_CONTAINER_BIT = 5,
bbaaef
+    MLF_LOOKUP_MAC_BIT = 6,
bbaaef
 };
bbaaef
 
bbaaef
 /* MFF_LOG_FLAGS_REG flag assignments */
bbaaef
@@ -84,6 +85,9 @@ enum mff_log_flags {
bbaaef
 
bbaaef
     /* Indicate that a packet was received from a nested container. */
bbaaef
     MLF_NESTED_CONTAINER = (1 << MLF_NESTED_CONTAINER_BIT),
bbaaef
+
bbaaef
+    /* Indicate that the lookup in the mac binding table was successful. */
bbaaef
+    MLF_LOOKUP_MAC = (1 << MLF_LOOKUP_MAC_BIT),
bbaaef
 };
bbaaef
 
bbaaef
 /* OVN logical fields
bbaaef
diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
bbaaef
index 1aafafb33..733564fd1 100644
bbaaef
--- a/ovn/controller/lflow.c
bbaaef
+++ b/ovn/controller/lflow.c
bbaaef
@@ -687,6 +687,7 @@ consider_logical_flow(
bbaaef
         .egress_ptable = OFTABLE_LOG_EGRESS_PIPELINE,
bbaaef
         .output_ptable = output_ptable,
bbaaef
         .mac_bind_ptable = OFTABLE_MAC_BINDING,
bbaaef
+        .mac_lookup_ptable = OFTABLE_MAC_LOOKUP,
bbaaef
     };
bbaaef
     ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts);
bbaaef
     ovnacts_free(ovnacts.data, ovnacts.size);
bbaaef
@@ -777,7 +778,9 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
bbaaef
         return;
bbaaef
     }
bbaaef
 
bbaaef
-    struct match match = MATCH_CATCHALL_INITIALIZER;
bbaaef
+    struct match get_arp_match = MATCH_CATCHALL_INITIALIZER;
bbaaef
+    struct match lookup_arp_match = MATCH_CATCHALL_INITIALIZER;
bbaaef
+
bbaaef
     if (strchr(b->ip, '.')) {
bbaaef
         ovs_be32 ip;
bbaaef
         if (!ip_parse(b->ip, &ip)) {
bbaaef
@@ -785,7 +788,9 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
bbaaef
             VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
bbaaef
             return;
bbaaef
         }
bbaaef
-        match_set_reg(&match, 0, ntohl(ip));
bbaaef
+        match_set_reg(&get_arp_match, 0, ntohl(ip));
bbaaef
+        match_set_reg(&lookup_arp_match, 0, ntohl(ip));
bbaaef
+        match_set_dl_type(&lookup_arp_match, htons(ETH_TYPE_ARP));
bbaaef
     } else {
bbaaef
         struct in6_addr ip6;
bbaaef
         if (!ipv6_parse(b->ip, &ip6)) {
bbaaef
@@ -795,17 +800,35 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
bbaaef
         }
bbaaef
         ovs_be128 value;
bbaaef
         memcpy(&value, &ip6, sizeof(value));
bbaaef
-        match_set_xxreg(&match, 0, ntoh128(value));
bbaaef
+        match_set_xxreg(&get_arp_match, 0, ntoh128(value));
bbaaef
+
bbaaef
+        match_set_xxreg(&lookup_arp_match, 0, ntoh128(value));
bbaaef
+        match_set_dl_type(&lookup_arp_match, htons(ETH_TYPE_IPV6));
bbaaef
+        match_set_nw_proto(&lookup_arp_match, 58);
bbaaef
+        match_set_icmp_code(&lookup_arp_match, 0);
bbaaef
     }
bbaaef
 
bbaaef
-    match_set_metadata(&match, htonll(pb->datapath->tunnel_key));
bbaaef
-    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key);
bbaaef
+    match_set_metadata(&get_arp_match, htonll(pb->datapath->tunnel_key));
bbaaef
+    match_set_reg(&get_arp_match, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key);
bbaaef
+
bbaaef
+    match_set_metadata(&lookup_arp_match, htonll(pb->datapath->tunnel_key));
bbaaef
+    match_set_reg(&lookup_arp_match, MFF_LOG_INPORT - MFF_REG0,
bbaaef
+                  pb->tunnel_key);
bbaaef
 
bbaaef
     uint64_t stub[1024 / 8];
bbaaef
     struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
bbaaef
     put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
bbaaef
-    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &match, &ofpacts,
bbaaef
-                    &b->header_.uuid);
bbaaef
+    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &get_arp_match,
bbaaef
+                    &ofpacts, &b->header_.uuid);
bbaaef
+
bbaaef
+    ofpbuf_clear(&ofpacts);
bbaaef
+    uint8_t value = 1;
bbaaef
+    put_load(&value, sizeof value, MFF_LOG_FLAGS, MLF_LOOKUP_MAC_BIT, 1,
bbaaef
+             &ofpacts);
bbaaef
+    match_set_dl_src(&lookup_arp_match, mac);
bbaaef
+    ofctrl_add_flow(flow_table, OFTABLE_MAC_LOOKUP, 100, 0, &lookup_arp_match,
bbaaef
+                    &ofpacts, &b->header_.uuid);
bbaaef
+
bbaaef
     ofpbuf_uninit(&ofpacts);
bbaaef
 }
bbaaef
 
bbaaef
diff --git a/ovn/controller/lflow.h b/ovn/controller/lflow.h
bbaaef
index 4e1086eb6..9cb2afb86 100644
bbaaef
--- a/ovn/controller/lflow.h
bbaaef
+++ b/ovn/controller/lflow.h
bbaaef
@@ -65,6 +65,7 @@ struct uuid;
bbaaef
 #define OFTABLE_SAVE_INPORT          64
bbaaef
 #define OFTABLE_LOG_TO_PHY           65
bbaaef
 #define OFTABLE_MAC_BINDING          66
bbaaef
+#define OFTABLE_MAC_LOOKUP           67
bbaaef
 
bbaaef
 /* The number of tables for the ingress and egress pipelines. */
bbaaef
 #define LOG_PIPELINE_LEN 24
bbaaef
diff --git a/ovn/lib/actions.c b/ovn/lib/actions.c
bbaaef
index 0bb76cb27..45f8715cf 100644
bbaaef
--- a/ovn/lib/actions.c
bbaaef
+++ b/ovn/lib/actions.c
bbaaef
@@ -1607,6 +1607,112 @@ ovnact_put_mac_bind_free(struct ovnact_put_mac_bind *put_mac OVS_UNUSED)
bbaaef
 {
bbaaef
 }
bbaaef
 
bbaaef
+static void format_lookup_mac(const struct ovnact_lookup_mac_bind *lookup_mac,
bbaaef
+                              struct ds *s, const char *name)
bbaaef
+{
bbaaef
+    expr_field_format(&lookup_mac->dst, s);
bbaaef
+    ds_put_format(s, " = %s(", name);
bbaaef
+    expr_field_format(&lookup_mac->port, s);
bbaaef
+    ds_put_cstr(s, ", ");
bbaaef
+    expr_field_format(&lookup_mac->ip, s);
bbaaef
+    ds_put_cstr(s, ", ");
bbaaef
+    expr_field_format(&lookup_mac->mac, s);
bbaaef
+    ds_put_cstr(s, ");");
bbaaef
+}
bbaaef
+
bbaaef
+static void
bbaaef
+format_LOOKUP_ARP(const struct ovnact_lookup_mac_bind *lookup_mac,
bbaaef
+                         struct ds *s)
bbaaef
+{
bbaaef
+    format_lookup_mac(lookup_mac, s, "lookup_arp");
bbaaef
+}
bbaaef
+
bbaaef
+static void
bbaaef
+format_LOOKUP_ND(const struct ovnact_lookup_mac_bind *lookup_mac,
bbaaef
+                        struct ds *s)
bbaaef
+{
bbaaef
+    format_lookup_mac(lookup_mac, s, "lookup_nd");
bbaaef
+}
bbaaef
+
bbaaef
+static void
bbaaef
+encode_lookup_mac(const struct ovnact_lookup_mac_bind *lookup_mac,
bbaaef
+                  enum mf_field_id ip_field,
bbaaef
+                  const struct ovnact_encode_params *ep,
bbaaef
+                  struct ofpbuf *ofpacts)
bbaaef
+{
bbaaef
+    const struct arg args[] = {
bbaaef
+        { expr_resolve_field(&lookup_mac->port), MFF_LOG_INPORT },
bbaaef
+        { expr_resolve_field(&lookup_mac->ip), ip_field },
bbaaef
+        { expr_resolve_field(&lookup_mac->mac),  MFF_ETH_SRC},
bbaaef
+    };
bbaaef
+
bbaaef
+    encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
bbaaef
+
bbaaef
+    struct mf_subfield dst = expr_resolve_field(&lookup_mac->dst);
bbaaef
+    ovs_assert(dst.field);
bbaaef
+
bbaaef
+    put_load(0, MFF_LOG_FLAGS, MLF_LOOKUP_MAC_BIT, 1, ofpacts);
bbaaef
+    emit_resubmit(ofpacts, ep->mac_lookup_ptable);
bbaaef
+
bbaaef
+    struct ofpact_reg_move *orm = ofpact_put_REG_MOVE(ofpacts);
bbaaef
+    orm->dst = dst;
bbaaef
+    orm->src.field = mf_from_id(MFF_LOG_FLAGS);
bbaaef
+    orm->src.ofs = MLF_LOOKUP_MAC_BIT;
bbaaef
+    orm->src.n_bits = 1;
bbaaef
+
bbaaef
+    encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
bbaaef
+}
bbaaef
+
bbaaef
+static void
bbaaef
+encode_LOOKUP_ARP(const struct ovnact_lookup_mac_bind *lookup_mac,
bbaaef
+                  const struct ovnact_encode_params *ep,
bbaaef
+                  struct ofpbuf *ofpacts)
bbaaef
+{
bbaaef
+    encode_lookup_mac(lookup_mac, MFF_REG0, ep, ofpacts);
bbaaef
+}
bbaaef
+
bbaaef
+static void
bbaaef
+encode_LOOKUP_ND(const struct ovnact_lookup_mac_bind *lookup_mac,
bbaaef
+                        const struct ovnact_encode_params *ep,
bbaaef
+                        struct ofpbuf *ofpacts)
bbaaef
+{
bbaaef
+    encode_lookup_mac(lookup_mac, MFF_XXREG0, ep, ofpacts);
bbaaef
+}
bbaaef
+
bbaaef
+static void
bbaaef
+parse_lookup_mac_bind(struct action_context *ctx,
bbaaef
+                      const struct expr_field *dst,
bbaaef
+                      int width,
bbaaef
+                      struct ovnact_lookup_mac_bind *lookup_mac)
bbaaef
+{
bbaaef
+    /* Validate that the destination is a 1-bit, modifiable field. */
bbaaef
+    char *error = expr_type_check(dst, 1, true);
bbaaef
+    if (error) {
bbaaef
+        lexer_error(ctx->lexer, "%s", error);
bbaaef
+        free(error);
bbaaef
+        return;
bbaaef
+    }
bbaaef
+
bbaaef
+    lexer_get(ctx->lexer); /* Skip lookup_arp/lookup_nd. */
bbaaef
+    lexer_get(ctx->lexer); /* Skip '('. * */
bbaaef
+
bbaaef
+    action_parse_field(ctx, 0, false, &lookup_mac->port);
bbaaef
+    lexer_force_match(ctx->lexer, LEX_T_COMMA);
bbaaef
+    action_parse_field(ctx, width, false, &lookup_mac->ip);
bbaaef
+    lexer_force_match(ctx->lexer, LEX_T_COMMA);
bbaaef
+    action_parse_field(ctx, 48, false, &lookup_mac->mac);
bbaaef
+    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
bbaaef
+    lookup_mac->dst = *dst;
bbaaef
+}
bbaaef
+
bbaaef
+static void
bbaaef
+ovnact_lookup_mac_bind_free(
bbaaef
+    struct ovnact_lookup_mac_bind *lookup_mac OVS_UNUSED)
bbaaef
+{
bbaaef
+
bbaaef
+}
bbaaef
+
bbaaef
+
bbaaef
 static void
bbaaef
 parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o,
bbaaef
               const struct hmap *gen_opts, const char *opts_type)
bbaaef
@@ -2722,6 +2828,14 @@ parse_set_action(struct action_context *ctx)
bbaaef
                 && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
bbaaef
             parse_check_pkt_larger(ctx, &lhs,
bbaaef
                                    ovnact_put_CHECK_PKT_LARGER(ctx->ovnacts));
bbaaef
+        } else if (!strcmp(ctx->lexer->token.s, "lookup_arp")
bbaaef
+                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
bbaaef
+            parse_lookup_mac_bind(ctx, &lhs, 32,
bbaaef
+                                  ovnact_put_LOOKUP_ARP(ctx->ovnacts));
bbaaef
+        } else if (!strcmp(ctx->lexer->token.s, "lookup_nd")
bbaaef
+                && lexer_lookahead(ctx->lexer) == LEX_T_LPAREN) {
bbaaef
+            parse_lookup_mac_bind(ctx, &lhs, 128,
bbaaef
+                                  ovnact_put_LOOKUP_ND(ctx->ovnacts));
bbaaef
         } else {
bbaaef
             parse_assignment_action(ctx, false, &lhs;;
bbaaef
         }
bbaaef
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
bbaaef
index ec2b6454c..ec9020d2a 100644
bbaaef
--- a/ovn/northd/ovn-northd.8.xml
bbaaef
+++ b/ovn/northd/ovn-northd.8.xml
bbaaef
@@ -1223,7 +1223,126 @@ output;
bbaaef
       Other packets are implicitly dropped.
bbaaef
     

bbaaef
 
bbaaef
-    

Ingress Table 1: IP Input

bbaaef
+    

Ingress Table 1: Neighbor lookup

bbaaef
+
bbaaef
+    

bbaaef
+      For ARP and IPv6 Neighbor Discovery packets, this table looks into the
bbaaef
+      <ref db="OVN_Southbound" table="MAC_Binding"/> records to determine
bbaaef
+      if OVN needs to learn the mac bindings. Following flows are added:
bbaaef
+    

bbaaef
+
bbaaef
+    
    bbaaef
    +      
  • bbaaef
    +        

    bbaaef
    +          For each router port P that owns IP address A,
    bbaaef
    +          which belongs to subnet S with prefix length L,
    bbaaef
    +          a priority-100 flow is added which matches
    bbaaef
    +          inport == P &&
    bbaaef
    +          arp.spa == S/L && arp.op == 1
    bbaaef
    +          (ARP request) with the
    bbaaef
    +          following actions:
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +reg9[4] = lookup_arp(inport, arp.spa, arp.sha);
    bbaaef
    +next;
    bbaaef
    +        
    bbaaef
    +
    bbaaef
    +        

    bbaaef
    +          If the logical router port P is a distributed gateway
    bbaaef
    +          router port, additional match
    bbaaef
    +          is_chassis_resident(cr-P) is added so that
    bbaaef
    +          the resident gateway chassis handles the neighbor lookup.
    bbaaef
    +        

    bbaaef
    +      
    bbaaef
    +
    bbaaef
    +      
  • bbaaef
    +        

    bbaaef
    +          A priority-100 flow which matches on ARP reply packets and applies
    bbaaef
    +          the actions:
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +reg9[4] = lookup_arp(inport, arp.spa, arp.sha);
    bbaaef
    +next;
    bbaaef
    +        
    bbaaef
    +      
    bbaaef
    +
    bbaaef
    +      
  • bbaaef
    +        

    bbaaef
    +          A priority-100 flow which matches on IPv6 Neighbor Discovery
    bbaaef
    +          advertisement packet and applies the actions:
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +reg9[4] = lookup_nd(inport, nd.target, nd.tll);
    bbaaef
    +next;
    bbaaef
    +        
    bbaaef
    +      
    bbaaef
    +
    bbaaef
    +      
  • bbaaef
    +        

    bbaaef
    +          A priority-100 flow which matches on IPv6 Neighbor Discovery
    bbaaef
    +          solicitation packet and applies the actions:
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +reg9[4] = lookup_nd(inport, ip6.src, nd.sll);
    bbaaef
    +next;
    bbaaef
    +        
    bbaaef
    +      
    bbaaef
    +
    bbaaef
    +      
  • bbaaef
    +        A priority-0 fallback flow that matches all packets and applies
    bbaaef
    +        the action reg9[5] = 1; next;
    bbaaef
    +        advancing the packet to the next table.
    bbaaef
    +      
    bbaaef
    +    
    bbaaef
    +
    bbaaef
    +    

    Ingress Table 2: Neighbor learning

    bbaaef
    +
    bbaaef
    +    

    bbaaef
    +      This table adds flows to learn the mac bindings from the ARP and
    bbaaef
    +      IPv6 Neighbor Solicitation/Advertisement packets if ARP/ND lookup
    bbaaef
    +      failed in the previous table.
    bbaaef
    +    

    bbaaef
    +
    bbaaef
    +    

    bbaaef
    +      reg9[4] will be 1 if the lookup_arp/lookup_nd
    bbaaef
    +      in the previous table was successful.
    bbaaef
    +    

    bbaaef
    +
    bbaaef
    +    

    bbaaef
    +      reg9[5] will be 1 if there was no need to do the lookup.
    bbaaef
    +    

    bbaaef
    +
    bbaaef
    +    
      bbaaef
      +      
    • bbaaef
      +        A priority-100 flow with the match
      bbaaef
      +        reg9[4] == 1 || reg9[5] == 1 and advances the packet
      bbaaef
      +        to the next table as there is no need to learn the neighbor.
      bbaaef
      +      
      bbaaef
      +
      bbaaef
      +      
    • bbaaef
      +        A priority-90 flow with the match arp and
      bbaaef
      +        applies the action
      bbaaef
      +        put_arp(inport, arp.spa, arp.sha); next;
      bbaaef
      +      
      bbaaef
      +
      bbaaef
      +      
    • bbaaef
      +        A priority-90 flow with the match nd_na and
      bbaaef
      +        applies the action
      bbaaef
      +        put_nd(inport, nd.target, nd.tll); next;
      bbaaef
      +      
      bbaaef
      +
      bbaaef
      +      
    • bbaaef
      +        A priority-90 flow with the match nd_ns and
      bbaaef
      +        applies the action
      bbaaef
      +        put_nd(inport, ip6.src, nd.sll); next;
      bbaaef
      +      
      bbaaef
      +    
      bbaaef
      +
      bbaaef
      +    

      Ingress Table 3: IP Input

      bbaaef
       
      bbaaef
           

      bbaaef
             This table is the core of the logical router datapath functionality.  It
      bbaaef
      @@ -1320,8 +1439,7 @@ next;
      bbaaef
               

      bbaaef
       
      bbaaef
               

      bbaaef
      -          These flows reply to ARP requests for the router's own IP address
      bbaaef
      -          and populates mac binding table of the logical router port.
      bbaaef
      +          These flows reply to ARP requests for the router's own IP address.
      bbaaef
                 The ARP requests are handled only if the requestor's IP belongs
      bbaaef
                 to the same subnets of the logical router port.
      bbaaef
                 For each router port P that owns IP address A,
      bbaaef
      @@ -1334,7 +1452,6 @@ next;
      bbaaef
               

      bbaaef
       
      bbaaef
               
      bbaaef
      -put_arp(inport, arp.spa, arp.sha);
      bbaaef
       eth.dst = eth.src;
      bbaaef
       eth.src = E;
      bbaaef
       arp.op = 2; /* ARP reply. */
      bbaaef
      @@ -1370,17 +1487,6 @@ output;
      bbaaef
               

      bbaaef
             
      bbaaef
       
      bbaaef
      -      
    • bbaaef
      -        

      bbaaef
      -          These flows handles ARP requests not for router's own IP address.
      bbaaef
      -          They use the SPA and SHA to populate the logical router port's
      bbaaef
      -          mac binding table, with priority 80.  The typical use case of
      bbaaef
      -          these flows are GARP requests handling.  For the gateway port
      bbaaef
      -          on a distributed logical router, these flows are only programmed
      bbaaef
      -          on the gateway port instance on the redirect-chassis.
      bbaaef
      -        

      bbaaef
      -      
      bbaaef
      -
      bbaaef
             
    • bbaaef
               

      bbaaef
                 These flows reply to ARP requests for the virtual IP addresses
      bbaaef
      @@ -1451,36 +1557,6 @@ arp.sha = external_mac;
      bbaaef
               
      bbaaef
             
      bbaaef
       
      bbaaef
      -      
    • bbaaef
      -        

      bbaaef
      -          ARP reply handling.  Following flows are added to handle ARP replies.
      bbaaef
      -        

      bbaaef
      -
      bbaaef
      -        

      bbaaef
      -          For each distributed gateway logical router port a priority-92 flow
      bbaaef
      -          with match inport == P &&
      bbaaef
      -          is_chassis_resident(cr-P) && eth.bcast &&
      bbaaef
      -          arp.op == 2 && arp.spa == I with the
      bbaaef
      -          action put_arp(inport, arp.spa, arp.sha); so that the
      bbaaef
      -          resident gateway chassis can learn the GARP reply, where
      bbaaef
      -          P is the distributed gateway router port name,
      bbaaef
      -          I is the logical router port's network address.
      bbaaef
      -        

      bbaaef
      -
      bbaaef
      -        

      bbaaef
      -          For each distributed gateway logical router port a priority-92 flow
      bbaaef
      -          with match inport == P &&
      bbaaef
      -          !is_chassis_resident(cr-P) && eth.bcast &&
      bbaaef
      -          arp.op == 2 && arp.spa == I with the action
      bbaaef
      -          drop; so that other chassis drop this packet.
      bbaaef
      -        

      bbaaef
      -
      bbaaef
      -        

      bbaaef
      -          A priority-90 flow with match arp.op == 2 has actions
      bbaaef
      -          put_arp(inport, arp.spa, arp.sha);.
      bbaaef
      -        

      bbaaef
      -      
      bbaaef
      -
      bbaaef
             
    • bbaaef
               

      bbaaef
                 Reply to IPv6 Neighbor Solicitations.  These flows reply to
      bbaaef
      @@ -1499,7 +1575,6 @@ arp.sha = external_mac;
      bbaaef
               

      bbaaef
       
      bbaaef
               
      bbaaef
      -put_nd(inport, ip6.src, nd.sll);
      bbaaef
       nd_na_router {
      bbaaef
           eth.src = E;
      bbaaef
           ip6.src = A;
      bbaaef
      @@ -1521,7 +1596,6 @@ nd_na_router {
      bbaaef
               

      bbaaef
       
      bbaaef
               
      bbaaef
      -put_nd(inport, ip6.src, nd.sll);
      bbaaef
       nd_na {
      bbaaef
           eth.src = E;
      bbaaef
           ip6.src = A;
      bbaaef
      @@ -1546,20 +1620,8 @@ nd_na {
      bbaaef
             
      bbaaef
       
      bbaaef
             
    • bbaaef
      -        IPv6 neighbor advertisement handling.  This flow uses neighbor
      bbaaef
      -        advertisements to populate the logical router's mac binding
      bbaaef
      -        table.  A priority-90 flow with match nd_na
      bbaaef
      -        has actions put_nd(inport, nd.target, nd.tll);.
      bbaaef
      -      
      bbaaef
      -
      bbaaef
      -      
    • bbaaef
      -        IPv6 neighbor solicitation for non-hosted addresses handling.
      bbaaef
      -        This flow uses neighbor solicitations to populate the logical
      bbaaef
      -        router's mac binding table (ones that were directed at the
      bbaaef
      -        logical router would have matched the priority-90 neighbor
      bbaaef
      -        solicitation flow already).  A priority-80 flow with match
      bbaaef
      -        nd_ns has actions
      bbaaef
      -        put_nd(inport, ip6.src, nd.sll);.
      bbaaef
      +        Priority-85 flows which drops the ARP and IPv6 Neighbor Discovery
      bbaaef
      +        packets.
      bbaaef
             
      bbaaef
       
      bbaaef
             
    • bbaaef
      @@ -1675,7 +1737,7 @@ icmp6 {
      bbaaef
             
      bbaaef
           
      bbaaef
       
      bbaaef
      -    

      Ingress Table 2: DEFRAG

      bbaaef
      +    

      Ingress Table 4: DEFRAG

      bbaaef
       
      bbaaef
           

      bbaaef
             This is to send packets to connection tracker for tracking and
      bbaaef
      @@ -1733,7 +1795,7 @@ icmp6 {
      bbaaef
             
      bbaaef
           
      bbaaef
       
      bbaaef
      -    

      Ingress Table 3: UNSNAT on Distributed Routers

      bbaaef
      +    

      Ingress Table 5: UNSNAT on Distributed Routers

      bbaaef
       
      bbaaef
           
        bbaaef
               
      • bbaaef
        @@ -1772,7 +1834,7 @@ icmp6 {
        bbaaef
               
        bbaaef
             
        bbaaef
         
        bbaaef
        -    

        Ingress Table 4: DNAT

        bbaaef
        +    

        Ingress Table 6: DNAT

        bbaaef
         
        bbaaef
             

        bbaaef
               Packets enter the pipeline with destination IP address that needs to
        bbaaef
        @@ -1780,7 +1842,7 @@ icmp6 {
        bbaaef
               in the reverse direction needs to be unDNATed.
        bbaaef
             

        bbaaef
         
        bbaaef
        -    

        Ingress Table 4: Load balancing DNAT rules

        bbaaef
        +    

        Ingress Table 6: Load balancing DNAT rules

        bbaaef
         
        bbaaef
             

        bbaaef
               Following load balancing DNAT flows are added for Gateway router or
        bbaaef
        @@ -1851,7 +1913,7 @@ icmp6 {
        bbaaef
               
        bbaaef
             
        bbaaef
         
        bbaaef
        -    

        Ingress Table 4: DNAT on Gateway Routers

        bbaaef
        +    

        Ingress Table 6: DNAT on Gateway Routers

        bbaaef
         
        bbaaef
             
          bbaaef
                 
        • bbaaef
          @@ -1877,7 +1939,7 @@ icmp6 {
          bbaaef
                 
          bbaaef
               
          bbaaef
           
          bbaaef
          -    

          Ingress Table 4: DNAT on Distributed Routers

          bbaaef
          +    

          Ingress Table 6: DNAT on Distributed Routers

          bbaaef
           
          bbaaef
               

          bbaaef
                 On distributed routers, the DNAT table only handles packets
          bbaaef
          @@ -1924,7 +1986,7 @@ icmp6 {
          bbaaef
                 
          bbaaef
               
          bbaaef
           
          bbaaef
          -    

          Ingress Table 5: IPv6 ND RA option processing

          bbaaef
          +    

          Ingress Table 7: IPv6 ND RA option processing

          bbaaef
           
          bbaaef
               
            bbaaef
                   
          • bbaaef
            @@ -1954,7 +2016,7 @@ reg0[5] = put_nd_ra_opts(options);next;
            bbaaef
                   
            bbaaef
                 
            bbaaef
             
            bbaaef
            -    

            Ingress Table 6: IPv6 ND RA responder

            bbaaef
            +    

            Ingress Table 8: IPv6 ND RA responder

            bbaaef
             
            bbaaef
                 

            bbaaef
                   This table implements IPv6 ND RA responder for the IPv6 ND RA replies
            bbaaef
            @@ -1999,7 +2061,7 @@ output;
            bbaaef
                   
            bbaaef
                 
            bbaaef
             
            bbaaef
            -    

            Ingress Table 7: IP Routing

            bbaaef
            +    

            Ingress Table 9: IP Routing

            bbaaef
             
            bbaaef
                 

            bbaaef
                   A packet that arrives at this table is an IP packet that should be
            bbaaef
            @@ -2149,7 +2211,7 @@ next;
            bbaaef
                   
            bbaaef
                 
            bbaaef
             
            bbaaef
            -    

            Ingress Table 8: ARP/ND Resolution

            bbaaef
            +    

            Ingress Table 10: ARP/ND Resolution

            bbaaef
             
            bbaaef
                 

            bbaaef
                   Any packet that reaches this table is an IP packet whose next-hop
            bbaaef
            @@ -2284,7 +2346,7 @@ next;
            bbaaef
                   
            bbaaef
                 
            bbaaef
             
            bbaaef
            -    

            Ingress Table 9: Check packet length

            bbaaef
            +    

            Ingress Table 11: Check packet length

            bbaaef
             
            bbaaef
                 

            bbaaef
                   For distributed logical routers with distributed gateway port configured
            bbaaef
            @@ -2314,7 +2376,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(L); next;
            bbaaef
                   and advances to the next table.
            bbaaef
                 

            bbaaef
             
            bbaaef
            -    

            Ingress Table 10: Handle larger packets

            bbaaef
            +    

            Ingress Table 12: Handle larger packets

            bbaaef
             
            bbaaef
                 

            bbaaef
                   For distributed logical routers with distributed gateway port configured
            bbaaef
            @@ -2363,7 +2425,7 @@ icmp4 {
            bbaaef
                   and advances to the next table.
            bbaaef
                 

            bbaaef
             
            bbaaef
            -    

            Ingress Table 11: Gateway Redirect

            bbaaef
            +    

            Ingress Table 13: Gateway Redirect

            bbaaef
             
            bbaaef
                 

            bbaaef
                   For distributed logical routers where one of the logical router
            bbaaef
            @@ -2425,7 +2487,7 @@ icmp4 {
            bbaaef
                   
            bbaaef
                 
            bbaaef
             
            bbaaef
            -    

            Ingress Table 12: ARP Request

            bbaaef
            +    

            Ingress Table 14: ARP Request

            bbaaef
             
            bbaaef
                 

            bbaaef
                   In the common case where the Ethernet destination has been resolved, this
            bbaaef
            diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
            bbaaef
            index 83a7ec14f..2f6826f17 100644
            bbaaef
            --- a/ovn/northd/ovn-northd.c
            bbaaef
            +++ b/ovn/northd/ovn-northd.c
            bbaaef
            @@ -144,20 +144,22 @@ enum ovn_stage {
            bbaaef
                 PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2,  9, "ls_out_port_sec_l2")    \
            bbaaef
                                                                                   \
            bbaaef
                 /* Logical router ingress stages. */                              \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  ADMISSION,      0, "lr_in_admission")    \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,       1, "lr_in_ip_input")     \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,         2, "lr_in_defrag")       \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,         3, "lr_in_unsnat")       \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  DNAT,           4, "lr_in_dnat")         \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,  5, "lr_in_nd_ra_options") \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE, 6, "lr_in_nd_ra_response") \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,     7, "lr_in_ip_routing")   \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  POLICY,         8, "lr_in_policy")       \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,    9, "lr_in_arp_resolve")  \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN   , 10, "lr_in_chk_pkt_len")   \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,    11,"lr_in_larger_pkts")   \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,    12, "lr_in_gw_redirect")  \
            bbaaef
            -    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,    13, "lr_in_arp_request")  \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  ADMISSION,       0, "lr_in_admission")    \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  LOOKUP_NEIGHBOR, 1, "lr_in_lookup_neighbor") \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  LEARN_NEIGHBOR,  2, "lr_in_learn_neighbor") \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,        3, "lr_in_ip_input")     \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,          4, "lr_in_defrag")       \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,          5, "lr_in_unsnat")       \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  DNAT,            6, "lr_in_dnat")         \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,   7, "lr_in_nd_ra_options") \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE,  8, "lr_in_nd_ra_response") \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,      9, "lr_in_ip_routing")   \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  POLICY,          10, "lr_in_policy")       \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,     11, "lr_in_arp_resolve")  \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN   ,  12, "lr_in_chk_pkt_len")   \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,     13,"lr_in_larger_pkts")   \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,     14, "lr_in_gw_redirect")  \
            bbaaef
            +    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,     15, "lr_in_arp_request")  \
            bbaaef
                                                                                   \
            bbaaef
                 /* Logical router egress stages. */                               \
            bbaaef
                 PIPELINE_STAGE(ROUTER, OUT, UNDNAT,    0, "lr_out_undnat")        \
            bbaaef
            @@ -196,6 +198,8 @@ enum ovn_stage {
            bbaaef
             #define REGBIT_DISTRIBUTED_NAT  "reg9[2]"
            bbaaef
             /* Register to store the result of check_pkt_larger action. */
            bbaaef
             #define REGBIT_PKT_LARGER        "reg9[3]"
            bbaaef
            +#define REGBIT_LOOKUP_NEIGHBOR_RESULT "reg9[4]"
            bbaaef
            +#define REGBIT_SKIP_LOOKUP_NEIGHBOR "reg9[5]"
            bbaaef
             
            bbaaef
             /* Returns an "enum ovn_stage" built from the arguments. */
            bbaaef
             static enum ovn_stage
            bbaaef
            @@ -6420,7 +6424,96 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
            bbaaef
                                   ds_cstr(&match), "next;");
            bbaaef
                 }
            bbaaef
             
            bbaaef
            -    /* Logical router ingress table 1: IP Input. */
            bbaaef
            +    /* Logical router ingress table 1: LOOKUP_NEIGHBOR and
            bbaaef
            +     * table 2: LEARN_NEIGHBOR. */
            bbaaef
            +    HMAP_FOR_EACH (od, key_node, datapaths) {
            bbaaef
            +        if (!od->nbr) {
            bbaaef
            +            continue;
            bbaaef
            +        }
            bbaaef
            +
            bbaaef
            +        /* Learn MAC bindings from ARP/IPv6 ND.
            bbaaef
            +         *
            bbaaef
            +         * For ARP packets, table LOOKUP_NEIGHBOR does a lookup for the
            bbaaef
            +         * (arp.spa, arp.sha) in the mac binding table using the 'lookup_arp'
            bbaaef
            +         * action and stores the result in REGBIT_LOOKUP_NEIGHBOR_RESULT bit.
            bbaaef
            +         *
            bbaaef
            +         * For IPv6 ND NA packets, table LOOKUP_NEIGHBOR does a lookup
            bbaaef
            +         * for the (nd.target, nd.tll) in the mac binding table using the
            bbaaef
            +         * 'lookup_nd' action and stores the result in
            bbaaef
            +         * REGBIT_LOOKUP_NEIGHBOR_RESULT bit.
            bbaaef
            +         *
            bbaaef
            +         * For IPv6 ND NS packets, table LOOKUP_NEIGHBOR does a lookup
            bbaaef
            +         * for the (ip6.src, nd.sll) in the mac binding table using the
            bbaaef
            +         * 'lookup_nd' action and stores the result in
            bbaaef
            +         * REGBIT_LOOKUP_NEIGHBOR_RESULT bit.
            bbaaef
            +         *
            bbaaef
            +         * Table LEARN_NEIGHBOR learns the mac-binding using the action
            bbaaef
            +         * - 'put_arp/put_nd' only if REGBIT_LOOKUP_NEIGHBOR_RESULT bit
            bbaaef
            +         * is not set.
            bbaaef
            +         *
            bbaaef
            +         * */
            bbaaef
            +
            bbaaef
            +        /* Flows for LOOKUP_NEIGHBOR. */
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
            bbaaef
            +                      "arp.op == 2",
            bbaaef
            +                      REGBIT_LOOKUP_NEIGHBOR_RESULT" = "
            bbaaef
            +                      "lookup_arp(inport, arp.spa, arp.sha); next;");
            bbaaef
            +
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_na",
            bbaaef
            +                      REGBIT_LOOKUP_NEIGHBOR_RESULT" = "
            bbaaef
            +                      "lookup_nd(inport, nd.target, nd.tll); next;");
            bbaaef
            +
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100, "nd_ns",
            bbaaef
            +                      REGBIT_LOOKUP_NEIGHBOR_RESULT" = "
            bbaaef
            +                      "lookup_nd(inport, ip6.src, nd.sll); next;");
            bbaaef
            +
            bbaaef
            +        /* For other packet types, we can skip neighbor learning.
            bbaaef
            +         * So set REGBIT_SKIP_LOOKUP_NEIGHBOR to 1. */
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 0, "1",
            bbaaef
            +                      REGBIT_SKIP_LOOKUP_NEIGHBOR" = 1; next;");
            bbaaef
            +
            bbaaef
            +        /* Flows for LEARN_NEIGHBOR. */
            bbaaef
            +        /* Skip Neighbor learning if not required. */
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 100,
            bbaaef
            +                      REGBIT_SKIP_LOOKUP_NEIGHBOR" == 1 || "
            bbaaef
            +                      REGBIT_LOOKUP_NEIGHBOR_RESULT" == 1", "next;");
            bbaaef
            +
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
            bbaaef
            +                      "arp", "put_arp(inport, arp.spa, arp.sha); next;");
            bbaaef
            +
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
            bbaaef
            +                      "nd_na", "put_nd(inport, nd.target, nd.tll); next;");
            bbaaef
            +
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LEARN_NEIGHBOR, 90,
            bbaaef
            +                      "nd_ns", "put_nd(inport, ip6.src, nd.sll); next;");
            bbaaef
            +    }
            bbaaef
            +
            bbaaef
            +    HMAP_FOR_EACH (op, key_node, ports) {
            bbaaef
            +        if (!op->nbrp) {
            bbaaef
            +            continue;
            bbaaef
            +        }
            bbaaef
            +
            bbaaef
            +        /* Check if we need to learn mac-binding from ARP requests. */
            bbaaef
            +        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
            bbaaef
            +            ds_clear(&match);
            bbaaef
            +            ds_put_format(&match,
            bbaaef
            +                          "inport == %s && arp.spa == %s/%u && arp.op == 1",
            bbaaef
            +                          op->json_key,
            bbaaef
            +                          op->lrp_networks.ipv4_addrs[i].network_s,
            bbaaef
            +                          op->lrp_networks.ipv4_addrs[i].plen);
            bbaaef
            +            if (op->od->l3dgw_port && op == op->od->l3dgw_port
            bbaaef
            +                && op->od->l3redirect_port) {
            bbaaef
            +                ds_put_format(&match, " && is_chassis_resident(%s)",
            bbaaef
            +                              op->od->l3redirect_port->json_key);
            bbaaef
            +            }
            bbaaef
            +            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_LOOKUP_NEIGHBOR, 100,
            bbaaef
            +                          ds_cstr(&match),
            bbaaef
            +                          REGBIT_LOOKUP_NEIGHBOR_RESULT" = "
            bbaaef
            +                          "lookup_arp(inport, arp.spa, arp.sha); next;");
            bbaaef
            +        }
            bbaaef
            +    }
            bbaaef
            +
            bbaaef
            +    /* Logical router ingress table 3: IP Input. */
            bbaaef
                 HMAP_FOR_EACH (od, key_node, datapaths) {
            bbaaef
                     if (!od->nbr) {
            bbaaef
                         continue;
            bbaaef
            @@ -6442,10 +6535,13 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
            bbaaef
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 95, "ip4.mcast",
            bbaaef
                                   od->mcast_info.rtr.relay ? "next;" : "drop;");
            bbaaef
             
            bbaaef
            -        /* ARP reply handling.  Use ARP replies to populate the logical
            bbaaef
            -         * router's ARP table. */
            bbaaef
            -        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "arp.op == 2",
            bbaaef
            -                      "put_arp(inport, arp.spa, arp.sha);");
            bbaaef
            +        /* Drop ARP packets (priority 85). ARP request packets for router's own
            bbaaef
            +         * IPs are handled with priority-90 flows.
            bbaaef
            +         * Drop IPv6 ND packets (priority 85). ND NA packets for router's own
            bbaaef
            +         * IPs are handled with priority-90 flows.
            bbaaef
            +         */
            bbaaef
            +        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 85,
            bbaaef
            +                      "arp || nd", "drop;");
            bbaaef
             
            bbaaef
                     /* Drop Ethernet local broadcast.  By definition this traffic should
            bbaaef
                      * not be forwarded.*/
            bbaaef
            @@ -6458,23 +6554,12 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
            bbaaef
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
            bbaaef
                                   ds_cstr(&match), "drop;");
            bbaaef
             
            bbaaef
            -        /* ND advertisement handling.  Use advertisements to populate
            bbaaef
            -         * the logical router's ARP/ND table. */
            bbaaef
            -        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "nd_na",
            bbaaef
            -                      "put_nd(inport, nd.target, nd.tll);");
            bbaaef
            -
            bbaaef
            -        /* Lean from neighbor solicitations that were not directed at
            bbaaef
            -         * us.  (A priority-90 flow will respond to requests to us and
            bbaaef
            -         * learn the sender's mac address. */
            bbaaef
            -        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 80, "nd_ns",
            bbaaef
            -                      "put_nd(inport, ip6.src, nd.sll);");
            bbaaef
            -
            bbaaef
                     /* Pass other traffic not already handled to the next table for
            bbaaef
                      * routing. */
            bbaaef
                     ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
            bbaaef
                 }
            bbaaef
             
            bbaaef
            -    /* Logical router ingress table 1: IP Input for IPv4. */
            bbaaef
            +    /* Logical router ingress table 3: IP Input for IPv4. */
            bbaaef
                 HMAP_FOR_EACH (op, key_node, ports) {
            bbaaef
                     if (!op->nbrp) {
            bbaaef
                         continue;
            bbaaef
            @@ -6584,7 +6669,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
            bbaaef
             
            bbaaef
                         ds_clear(&actions);
            bbaaef
                         ds_put_format(&actions,
            bbaaef
            -                "put_arp(inport, arp.spa, arp.sha); "
            bbaaef
                             "eth.dst = eth.src; "
            bbaaef
                             "eth.src = %s; "
            bbaaef
                             "arp.op = 2; /* ARP reply */ "
            bbaaef
            @@ -6603,62 +6687,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
            bbaaef
                                       ds_cstr(&match), ds_cstr(&actions));
            bbaaef
                     }
            bbaaef
             
            bbaaef
            -        /* Learn from ARP requests that were not directed at us. A typical
            bbaaef
            -         * use case is GARP request handling.  (A priority-90 flow will
            bbaaef
            -         * respond to request to us and learn the sender's mac address.) */
            bbaaef
            -        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
            bbaaef
            -            ds_clear(&match);
            bbaaef
            -            ds_put_format(&match,
            bbaaef
            -                          "inport == %s && arp.spa == %s/%u && arp.op == 1",
            bbaaef
            -                          op->json_key,
            bbaaef
            -                          op->lrp_networks.ipv4_addrs[i].network_s,
            bbaaef
            -                          op->lrp_networks.ipv4_addrs[i].plen);
            bbaaef
            -            if (op->od->l3dgw_port && op == op->od->l3dgw_port
            bbaaef
            -                && op->od->l3redirect_port) {
            bbaaef
            -                ds_put_format(&match, " && is_chassis_resident(%s)",
            bbaaef
            -                              op->od->l3redirect_port->json_key);
            bbaaef
            -            }
            bbaaef
            -            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80,
            bbaaef
            -                          ds_cstr(&match),
            bbaaef
            -                          "put_arp(inport, arp.spa, arp.sha);");
            bbaaef
            -
            bbaaef
            -        }
            bbaaef
            -
            bbaaef
            -        /* Handle GARP reply packets received on a distributed router gateway
            bbaaef
            -         * port. GARP reply broadcast packets could be sent by external
            bbaaef
            -         * switches. We don't want them to be handled by all the
            bbaaef
            -         * ovn-controllers if they receive it. So add a priority-92 flow to
            bbaaef
            -         * apply the put_arp action on a redirect chassis and drop it on
            bbaaef
            -         * other chassis.
            bbaaef
            -         * Note that we are already adding a priority-90 logical flow in the
            bbaaef
            -         * table S_ROUTER_IN_IP_INPUT to apply the put_arp action if
            bbaaef
            -         * arp.op == 2.
            bbaaef
            -         * */
            bbaaef
            -        if (op->od->l3dgw_port && op == op->od->l3dgw_port
            bbaaef
            -                && op->od->l3redirect_port) {
            bbaaef
            -            for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
            bbaaef
            -                ds_clear(&match);
            bbaaef
            -                ds_put_format(&match,
            bbaaef
            -                              "inport == %s && is_chassis_resident(%s) && "
            bbaaef
            -                              "eth.bcast && arp.op == 2 && arp.spa == %s/%u",
            bbaaef
            -                              op->json_key, op->od->l3redirect_port->json_key,
            bbaaef
            -                              op->lrp_networks.ipv4_addrs[i].network_s,
            bbaaef
            -                              op->lrp_networks.ipv4_addrs[i].plen);
            bbaaef
            -                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 92,
            bbaaef
            -                              ds_cstr(&match),
            bbaaef
            -                              "put_arp(inport, arp.spa, arp.sha);");
            bbaaef
            -                ds_clear(&match);
            bbaaef
            -                ds_put_format(&match,
            bbaaef
            -                              "inport == %s && !is_chassis_resident(%s) && "
            bbaaef
            -                              "eth.bcast && arp.op == 2 && arp.spa == %s/%u",
            bbaaef
            -                              op->json_key, op->od->l3redirect_port->json_key,
            bbaaef
            -                              op->lrp_networks.ipv4_addrs[i].network_s,
            bbaaef
            -                              op->lrp_networks.ipv4_addrs[i].plen);
            bbaaef
            -                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 92,
            bbaaef
            -                              ds_cstr(&match), "drop;");
            bbaaef
            -            }
            bbaaef
            -        }
            bbaaef
            -
            bbaaef
                     /* A set to hold all load-balancer vips that need ARP responses. */
            bbaaef
                     struct sset all_ips = SSET_INITIALIZER(&all_ips);
            bbaaef
                     int addr_family;
            bbaaef
            @@ -6969,7 +6997,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
            bbaaef
             
            bbaaef
                         ds_clear(&actions);
            bbaaef
                         ds_put_format(&actions,
            bbaaef
            -                          "put_nd(inport, ip6.src, nd.sll); "
            bbaaef
                                       "nd_na_router { "
            bbaaef
                                       "eth.src = %s; "
            bbaaef
                                       "ip6.src = %s; "
            bbaaef
            diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml
            bbaaef
            index c4099f25a..2a375c8b3 100644
            bbaaef
            --- a/ovn/ovn-architecture.7.xml
            bbaaef
            +++ b/ovn/ovn-architecture.7.xml
            bbaaef
            @@ -970,6 +970,24 @@
            bbaaef
                         this temporary use.)
            bbaaef
                       

            bbaaef
                     
            bbaaef
            +
            bbaaef
            +        
            R = lookup_arp(P, A, M);
            bbaaef
            +        
            R = lookup_nd(P, A, M);
            bbaaef
            +        
            bbaaef
            +          

            bbaaef
            +            Implemented by storing arguments into OpenFlow fields, then
            bbaaef
            +            resubmitting to table 67, which ovn-controller
            bbaaef
            +            populates with flows generated from the MAC_Binding
            bbaaef
            +            table in the OVN Southbound database.  If there is a match in table
            bbaaef
            +            67, then its actions set the logical flow flag MLF_LOOKUP_MAC.
            bbaaef
            +          

            bbaaef
            +
            bbaaef
            +          

            bbaaef
            +            (The OpenFlow actions save and restore the OpenFlow fields used for
            bbaaef
            +            the arguments, so that the OVN actions do not have to be aware of
            bbaaef
            +            this temporary use.)
            bbaaef
            +          

            bbaaef
            +        
            bbaaef
                   
            bbaaef
                 
            bbaaef
             
            bbaaef
            diff --git a/ovn/ovn-sb.xml b/ovn/ovn-sb.xml
            bbaaef
            index 477e7bc7a..e5fb51a9d 100644
            bbaaef
            --- a/ovn/ovn-sb.xml
            bbaaef
            +++ b/ovn/ovn-sb.xml
            bbaaef
            @@ -1397,6 +1397,35 @@
            bbaaef
                       

            Example: put_arp(inport, arp.spa, arp.sha);

            bbaaef
                     
            bbaaef
             
            bbaaef
            +        
            bbaaef
            +          R = lookup_arp(P, A, M);
            bbaaef
            +        
            bbaaef
            +
            bbaaef
            +        
            bbaaef
            +          

            bbaaef
            +            Parameters: logical port string field P, 32-bit
            bbaaef
            +            IP address field A, 48-bit MAC address field
            bbaaef
            +            M.
            bbaaef
            +          

            bbaaef
            +
            bbaaef
            +          

            bbaaef
            +            Result: stored to a 1-bit subfield R.
            bbaaef
            +          

            bbaaef
            +
            bbaaef
            +          

            bbaaef
            +            Looks up A and M in P's mac
            bbaaef
            +            binding table. If an entry is found, stores 1 in
            bbaaef
            +            the 1-bit subfield R, else 0.
            bbaaef
            +          

            bbaaef
            +
            bbaaef
            +          

            bbaaef
            +            Example:
            bbaaef
            +            
            bbaaef
            +              reg0[0] = lookup_arp(inport, arp.spa, arp.sha);
            bbaaef
            +            
            bbaaef
            +          

            bbaaef
            +        
            bbaaef
            +
            bbaaef
                     
            nd_ns { action; ... };
            bbaaef
                     
            bbaaef
                       

            bbaaef
            @@ -1553,6 +1582,34 @@
            bbaaef
                       

            Example: put_nd(inport, nd.target, nd.tll);

            bbaaef
                     
            bbaaef
             
            bbaaef
            +        
            R = lookup_nd(P, A, M);
            bbaaef
            +        
            bbaaef
            +
            bbaaef
            +        
            bbaaef
            +          

            bbaaef
            +            Parameters: logical port string field P, 128-bit
            bbaaef
            +            IP address field A, 48-bit MAC address field
            bbaaef
            +            M.
            bbaaef
            +          

            bbaaef
            +
            bbaaef
            +          

            bbaaef
            +            Result: stored to a 1-bit subfield R.
            bbaaef
            +          

            bbaaef
            +
            bbaaef
            +          

            bbaaef
            +            Looks up A and M in P's mac
            bbaaef
            +            binding table. If an entry is found, stores 1 in
            bbaaef
            +            the 1-bit subfield R, else 0.
            bbaaef
            +          

            bbaaef
            +
            bbaaef
            +          

            bbaaef
            +            Example:
            bbaaef
            +            
            bbaaef
            +              reg0[0] = lookup_nd(inport, ip6.src, eth.src);
            bbaaef
            +            
            bbaaef
            +          

            bbaaef
            +        
            bbaaef
            +
            bbaaef
                     
            bbaaef
                       R = put_dhcp_opts(D1 = V1, D2 = V2, ..., Dn = Vn);
            bbaaef
                     
            bbaaef
            diff --git a/ovn/utilities/ovn-trace.c b/ovn/utilities/ovn-trace.c
            bbaaef
            index b532b8eaf..103b25891 100644
            bbaaef
            --- a/ovn/utilities/ovn-trace.c
            bbaaef
            +++ b/ovn/utilities/ovn-trace.c
            bbaaef
            @@ -556,6 +556,22 @@ ovntrace_mac_binding_find(const struct ovntrace_datapath *dp,
            bbaaef
                 return NULL;
            bbaaef
             }
            bbaaef
             
            bbaaef
            +static const struct ovntrace_mac_binding *
            bbaaef
            +ovntrace_mac_binding_find_mac_ip(const struct ovntrace_datapath *dp,
            bbaaef
            +                                 uint16_t port_key, const struct in6_addr *ip,
            bbaaef
            +                                 struct eth_addr mac)
            bbaaef
            +{
            bbaaef
            +    const struct ovntrace_mac_binding *bind;
            bbaaef
            +    HMAP_FOR_EACH_WITH_HASH (bind, node, hash_mac_binding(port_key, ip),
            bbaaef
            +                             &dp->mac_bindings) {
            bbaaef
            +        if (bind->port_key == port_key && ipv6_addr_equals(ip, &bind->ip)
            bbaaef
            +            && eth_addr_equals(bind->mac, mac)) {
            bbaaef
            +            return bind;
            bbaaef
            +        }
            bbaaef
            +    }
            bbaaef
            +    return NULL;
            bbaaef
            +}
            bbaaef
            +
            bbaaef
             /* If 's' ends with a UUID, returns a copy of it with the UUID truncated to
            bbaaef
              * just the first 6 characters; otherwise, returns a copy of 's'. */
            bbaaef
             static char *
            bbaaef
            @@ -1704,6 +1720,51 @@ execute_get_mac_bind(const struct ovnact_get_mac_bind *bind,
            bbaaef
                                      ETH_ADDR_ARGS(uflow->dl_dst));
            bbaaef
             }
            bbaaef
             
            bbaaef
            +static void
            bbaaef
            +execute_lookup_mac(const struct ovnact_lookup_mac_bind *bind OVS_UNUSED,
            bbaaef
            +                   const struct ovntrace_datapath *dp OVS_UNUSED,
            bbaaef
            +                   struct flow *uflow OVS_UNUSED,
            bbaaef
            +                   struct ovs_list *super OVS_UNUSED)
            bbaaef
            +{
            bbaaef
            +    /* Get logical port number.*/
            bbaaef
            +    struct mf_subfield port_sf = expr_resolve_field(&bind->port);
            bbaaef
            +    ovs_assert(port_sf.n_bits == 32);
            bbaaef
            +    uint32_t port_key = mf_get_subfield(&port_sf, uflow);
            bbaaef
            +
            bbaaef
            +    /* Get IP address. */
            bbaaef
            +    struct mf_subfield ip_sf = expr_resolve_field(&bind->ip);
            bbaaef
            +    ovs_assert(ip_sf.n_bits == 32 || ip_sf.n_bits == 128);
            bbaaef
            +    union mf_subvalue ip_sv;
            bbaaef
            +    mf_read_subfield(&ip_sf, uflow, &ip_sv);
            bbaaef
            +    struct in6_addr ip = (ip_sf.n_bits == 32
            bbaaef
            +                          ? in6_addr_mapped_ipv4(ip_sv.ipv4)
            bbaaef
            +                          : ip_sv.ipv6);
            bbaaef
            +
            bbaaef
            +    /* Get MAC. */
            bbaaef
            +    struct mf_subfield mac_sf = expr_resolve_field(&bind->mac);
            bbaaef
            +    ovs_assert(mac_sf.n_bits == 48);
            bbaaef
            +    union mf_subvalue mac_sv;
            bbaaef
            +    mf_read_subfield(&mac_sf, uflow, &mac_sv);
            bbaaef
            +
            bbaaef
            +    const struct ovntrace_mac_binding *binding
            bbaaef
            +        = ovntrace_mac_binding_find_mac_ip(dp, port_key, &ip, mac_sv.mac);
            bbaaef
            +
            bbaaef
            +    struct mf_subfield dst = expr_resolve_field(&bind->dst);
            bbaaef
            +    uint8_t val = 0;
            bbaaef
            +
            bbaaef
            +    if (binding) {
            bbaaef
            +        val = 1;
            bbaaef
            +        ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
            bbaaef
            +                             "/* MAC binding to "ETH_ADDR_FMT" found. */",
            bbaaef
            +                             ETH_ADDR_ARGS(uflow->dl_dst));
            bbaaef
            +    } else {
            bbaaef
            +        ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
            bbaaef
            +                             "/* lookup failed - No MAC binding. */");
            bbaaef
            +    }
            bbaaef
            +    union mf_subvalue sv = { .u8_val = val };
            bbaaef
            +    mf_write_subfield_flow(&dst, &sv, uflow);
            bbaaef
            +}
            bbaaef
            +
            bbaaef
             static void
            bbaaef
             execute_put_opts(const struct ovnact_put_opts *po,
            bbaaef
                              const char *name, struct flow *uflow,
            bbaaef
            @@ -2072,6 +2133,14 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
            bbaaef
                         /* Nothing to do for tracing. */
            bbaaef
                         break;
            bbaaef
             
            bbaaef
            +        case OVNACT_LOOKUP_ARP:
            bbaaef
            +            execute_lookup_mac(ovnact_get_LOOKUP_ARP(a), dp, uflow, super);
            bbaaef
            +            break;
            bbaaef
            +
            bbaaef
            +        case OVNACT_LOOKUP_ND:
            bbaaef
            +            execute_lookup_mac(ovnact_get_LOOKUP_ND(a), dp, uflow, super);
            bbaaef
            +            break;
            bbaaef
            +
            bbaaef
                     case OVNACT_PUT_DHCPV4_OPTS:
            bbaaef
                         execute_put_dhcp_opts(ovnact_get_PUT_DHCPV4_OPTS(a),
            bbaaef
                                               "put_dhcp_opts", uflow, super);
            bbaaef
            diff --git a/tests/ovn.at b/tests/ovn.at
            bbaaef
            index df41a7549..d52c97541 100644
            bbaaef
            --- a/tests/ovn.at
            bbaaef
            +++ b/tests/ovn.at
            bbaaef
            @@ -2231,6 +2231,33 @@ put_arp(inport, arp.spa, arp.sha);
            bbaaef
                 encodes as push:NXM_NX_REG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],pop:NXM_NX_REG0[],pop:NXM_OF_ETH_SRC[],controller(userdata=00.00.00.01.00.00.00.00),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[]
            bbaaef
                 has prereqs eth.type == 0x806 && eth.type == 0x806
            bbaaef
             
            bbaaef
            +# lookup_arp
            bbaaef
            +reg0[0] = lookup_arp(inport, ip4.dst, eth.src);
            bbaaef
            +    encodes as push:NXM_NX_REG0[],push:NXM_OF_IP_DST[],pop:NXM_NX_REG0[],set_field:0/0x40->reg10,resubmit(,67),move:NXM_NX_REG10[6]->NXM_NX_XXREG0[96],pop:NXM_NX_REG0[]
            bbaaef
            +    has prereqs eth.type == 0x800
            bbaaef
            +reg1[1] = lookup_arp(inport, arp.spa, arp.sha);
            bbaaef
            +    encodes as push:NXM_NX_REG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ARP_SHA[],push:NXM_OF_ARP_SPA[],pop:NXM_NX_REG0[],pop:NXM_OF_ETH_SRC[],set_field:0/0x40->reg10,resubmit(,67),move:NXM_NX_REG10[6]->NXM_NX_XXREG0[65],pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[]
            bbaaef
            +    has prereqs eth.type == 0x806 && eth.type == 0x806
            bbaaef
            +
            bbaaef
            +lookup_arp;
            bbaaef
            +    Syntax error at `lookup_arp' expecting action.
            bbaaef
            +reg0[0] = lookup_arp;
            bbaaef
            +    Syntax error at `lookup_arp' expecting field name.
            bbaaef
            +reg0[0] = lookup_arp();
            bbaaef
            +    Syntax error at `)' expecting field name.
            bbaaef
            +reg0[0] = lookup_arp(inport);
            bbaaef
            +    Syntax error at `)' expecting `,'.
            bbaaef
            +reg0[0] = lookup_arp(inport ip4.dst);
            bbaaef
            +    Syntax error at `ip4.dst' expecting `,'.
            bbaaef
            +reg0[0] = lookup_arp(inport, ip4.dst;
            bbaaef
            +    Syntax error at `;' expecting `,'.
            bbaaef
            +reg0[0] = lookup_arp(inport, ip4.dst, eth.src;
            bbaaef
            +    Syntax error at `;' expecting `)'.
            bbaaef
            +reg0[0] = lookup_arp(inport, eth.dst);
            bbaaef
            +    Cannot use 48-bit field eth.dst[0..47] where 32-bit field is required.
            bbaaef
            +reg0[0] = lookup_arp(inport, ip4.src, ip4.dst);
            bbaaef
            +    Cannot use 32-bit field ip4.dst[0..31] where 48-bit field is required.
            bbaaef
            +
            bbaaef
             # put_dhcp_opts
            bbaaef
             reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
            bbaaef
                 encodes as controller(userdata=00.00.00.02.00.00.00.00.00.01.de.10.00.00.00.40.01.02.03.04.03.04.0a.00.00.01,pause)
            bbaaef
            @@ -2331,6 +2358,35 @@ reg1[0] = put_dhcpv6_opts(ia_addr="ae70::4");
            bbaaef
             reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, domain_search=ae70::1);
            bbaaef
                 DHCPv6 option domain_search requires string value.
            bbaaef
             
            bbaaef
            +# lookup_nd
            bbaaef
            +reg2[0] = lookup_nd(inport, ip6.dst, eth.src);
            bbaaef
            +    encodes as push:NXM_NX_XXREG0[],push:NXM_NX_IPV6_DST[],pop:NXM_NX_XXREG0[],set_field:0/0x40->reg10,resubmit(,67),move:NXM_NX_REG10[6]->NXM_NX_XXREG0[32],pop:NXM_NX_XXREG0[]
            bbaaef
            +    has prereqs eth.type == 0x86dd
            bbaaef
            +reg3[0] = lookup_nd(inport, nd.target, nd.tll);
            bbaaef
            +    encodes as push:NXM_NX_XXREG0[],push:NXM_OF_ETH_SRC[],push:NXM_NX_ND_TLL[],push:NXM_NX_ND_TARGET[],pop:NXM_NX_XXREG0[],pop:NXM_OF_ETH_SRC[],set_field:0/0x40->reg10,resubmit(,67),move:NXM_NX_REG10[6]->NXM_NX_XXREG0[0],pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[]
            bbaaef
            +    has prereqs (icmp6.type == 0x87 || icmp6.type == 0x88) && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.type == 0x88 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && icmp6.code == 0 && eth.type == 0x86dd && ip.proto == 0x3a && (eth.type == 0x800 || eth.type == 0x86dd) && ip.ttl == 0xff && (eth.type == 0x800 || eth.type == 0x86dd)
            bbaaef
            +
            bbaaef
            +lookup_nd;
            bbaaef
            +    Syntax error at `lookup_nd' expecting action.
            bbaaef
            +reg0[0] = lookup_nd;
            bbaaef
            +    Syntax error at `lookup_nd' expecting field name.
            bbaaef
            +reg0[0] = lookup_nd();
            bbaaef
            +    Syntax error at `)' expecting field name.
            bbaaef
            +reg0[0] = lookup_nd(inport);
            bbaaef
            +    Syntax error at `)' expecting `,'.
            bbaaef
            +reg0[0] = lookup_nd(inport ip6.dst);
            bbaaef
            +    Syntax error at `ip6.dst' expecting `,'.
            bbaaef
            +reg0[0] = lookup_nd(inport, ip6.dst;
            bbaaef
            +    Syntax error at `;' expecting `,'.
            bbaaef
            +reg0[0] = lookup_nd(inport, ip6.dst, eth.src;
            bbaaef
            +    Syntax error at `;' expecting `)'.
            bbaaef
            +reg0[0] = lookup_nd(inport, eth.dst);
            bbaaef
            +    Cannot use 48-bit field eth.dst[0..47] where 128-bit field is required.
            bbaaef
            +reg0[0] = lookup_nd(inport, ip4.src, ip4.dst);
            bbaaef
            +    Cannot use 32-bit field ip4.src[0..31] where 128-bit field is required.
            bbaaef
            +reg0[0] = lookup_nd(inport, ip6.src, ip6.dst);
            bbaaef
            +    Cannot use 128-bit field ip6.dst[0..127] where 48-bit field is required.
            bbaaef
            +
            bbaaef
             # set_queue
            bbaaef
             set_queue(0);
            bbaaef
                 encodes as set_queue:0
            bbaaef
            @@ -15608,7 +15664,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
            bbaaef
             # Since the sw0-vir is not claimed by any chassis, eth.dst should be set to
            bbaaef
             # zero if the ip4.dst is the virtual ip in the router pipeline.
            bbaaef
             AT_CHECK([cat lflows.txt], [0], [dnl
            bbaaef
            -  table=9 (lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
            bbaaef
            +  table=11(lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
            bbaaef
             ])
            bbaaef
             
            bbaaef
             ip_to_hex() {
            bbaaef
            @@ -15644,7 +15700,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
            bbaaef
             # There should be an arp resolve flow to resolve the virtual_ip with the
            bbaaef
             # sw0-p1's MAC.
            bbaaef
             AT_CHECK([cat lflows.txt], [0], [dnl
            bbaaef
            -  table=9 (lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
            bbaaef
            +  table=11(lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
            bbaaef
             ])
            bbaaef
             
            bbaaef
             # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir
            bbaaef
            @@ -15667,7 +15723,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
            bbaaef
             # There should be an arp resolve flow to resolve the virtual_ip with the
            bbaaef
             # sw0-p2's MAC.
            bbaaef
             AT_CHECK([cat lflows.txt], [0], [dnl
            bbaaef
            -  table=9 (lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
            bbaaef
            +  table=11(lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
            bbaaef
             ])
            bbaaef
             
            bbaaef
             # Now send arp reply from sw0-p1. hv1 should claim sw0-vir
            bbaaef
            @@ -15688,7 +15744,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
            bbaaef
             > lflows.txt
            bbaaef
             
            bbaaef
             AT_CHECK([cat lflows.txt], [0], [dnl
            bbaaef
            -  table=9 (lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
            bbaaef
            +  table=11(lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:03; next;)
            bbaaef
             ])
            bbaaef
             
            bbaaef
             # Delete hv1-vif1 port. hv1 should release sw0-vir
            bbaaef
            @@ -15706,7 +15762,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
            bbaaef
             > lflows.txt
            bbaaef
             
            bbaaef
             AT_CHECK([cat lflows.txt], [0], [dnl
            bbaaef
            -  table=9 (lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
            bbaaef
            +  table=11(lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 00:00:00:00:00:00; next;)
            bbaaef
             ])
            bbaaef
             
            bbaaef
             # Now send arp reply from sw0-p2. hv2 should claim sw0-vir
            bbaaef
            @@ -15727,7 +15783,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
            bbaaef
             > lflows.txt
            bbaaef
             
            bbaaef
             AT_CHECK([cat lflows.txt], [0], [dnl
            bbaaef
            -  table=9 (lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
            bbaaef
            +  table=11(lr_in_arp_resolve  ), priority=100  , match=(outport == "lr0-sw0" && reg0 == 10.0.0.10), action=(eth.dst = 50:54:00:00:00:04; next;)
            bbaaef
             ])
            bbaaef
             
            bbaaef
             # Delete sw0-p2 logical port
            bbaaef
            @@ -16332,3 +16388,225 @@ OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
            bbaaef
             
            bbaaef
             OVN_CLEANUP([hv1], [hv2])
            bbaaef
             AT_CLEANUP
            bbaaef
            +
            bbaaef
            +AT_SETUP([ovn -- ARP lookup before learning])
            bbaaef
            +AT_KEYWORDS([virtual ports])
            bbaaef
            +AT_SKIP_IF([test $HAVE_PYTHON = no])
            bbaaef
            +ovn_start
            bbaaef
            +
            bbaaef
            +send_garp() {
            bbaaef
            +    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
            bbaaef
            +    local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
            bbaaef
            +    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
            bbaaef
            +}
            bbaaef
            +
            bbaaef
            +send_arp_reply() {
            bbaaef
            +    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
            bbaaef
            +    local request=${eth_dst}${eth_src}08060001080006040002${eth_src}${spa}${eth_dst}${tpa}
            bbaaef
            +    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
            bbaaef
            +}
            bbaaef
            +
            bbaaef
            +net_add n1
            bbaaef
            +
            bbaaef
            +sim_add hv1
            bbaaef
            +as hv1
            bbaaef
            +ovs-vsctl add-br br-phys
            bbaaef
            +ovn_attach n1 br-phys 192.168.0.1
            bbaaef
            +ovs-vsctl -- add-port br-int hv1-vif1 -- \
            bbaaef
            +    set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
            bbaaef
            +    options:tx_pcap=hv1/vif1-tx.pcap \
            bbaaef
            +    options:rxq_pcap=hv1/vif1-rx.pcap \
            bbaaef
            +    ofport-request=1
            bbaaef
            +ovs-vsctl -- add-port br-int hv1-vif2 -- \
            bbaaef
            +    set interface hv1-vif2 external-ids:iface-id=sw0-p3 \
            bbaaef
            +    options:tx_pcap=hv1/vif2-tx.pcap \
            bbaaef
            +    options:rxq_pcap=hv1/vif2-rx.pcap \
            bbaaef
            +    ofport-request=2
            bbaaef
            +
            bbaaef
            +sim_add hv2
            bbaaef
            +as hv2
            bbaaef
            +ovs-vsctl add-br br-phys
            bbaaef
            +ovn_attach n1 br-phys 192.168.0.2
            bbaaef
            +ovs-vsctl -- add-port br-int hv2-vif1 -- \
            bbaaef
            +    set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
            bbaaef
            +    options:tx_pcap=hv2/vif1-tx.pcap \
            bbaaef
            +    options:rxq_pcap=hv2/vif1-rx.pcap \
            bbaaef
            +    ofport-request=1
            bbaaef
            +
            bbaaef
            +ovn-nbctl ls-add sw0
            bbaaef
            +
            bbaaef
            +ovn-nbctl lsp-add sw0 sw0-p1
            bbaaef
            +ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03"
            bbaaef
            +
            bbaaef
            +# Create the second logical switch with one port
            bbaaef
            +ovn-nbctl ls-add sw1
            bbaaef
            +ovn-nbctl lsp-add sw1 sw1-p1
            bbaaef
            +ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
            bbaaef
            +ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
            bbaaef
            +
            bbaaef
            +# Create a logical router and attach both logical switches
            bbaaef
            +ovn-nbctl lr-add lr0
            bbaaef
            +ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
            bbaaef
            +ovn-nbctl lsp-add sw0 sw0-lr0
            bbaaef
            +ovn-nbctl lsp-set-type sw0-lr0 router
            bbaaef
            +ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
            bbaaef
            +ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
            bbaaef
            +
            bbaaef
            +ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
            bbaaef
            +ovn-nbctl lsp-add sw1 sw1-lr0
            bbaaef
            +ovn-nbctl lsp-set-type sw1-lr0 router
            bbaaef
            +ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
            bbaaef
            +ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
            bbaaef
            +
            bbaaef
            +OVN_POPULATE_ARP
            bbaaef
            +ovn-nbctl --wait=hv sync
            bbaaef
            +
            bbaaef
            +as hv1 ovs-appctl -t ovn-controller vlog/set dbg
            bbaaef
            +
            bbaaef
            +ip_to_hex() {
            bbaaef
            +    printf "%02x%02x%02x%02x" "$@"
            bbaaef
            +}
            bbaaef
            +
            bbaaef
            +# From sw0-p1 send GARP for 10.0.0.30.
            bbaaef
            +# ovn-controller should learn the
            bbaaef
            +#   mac_binding entry
            bbaaef
            +#     port - lr0-sw0
            bbaaef
            +#     ip - 10.0.0.30
            bbaaef
            +#     mac - 50:54:00:00:00:03
            bbaaef
            +
            bbaaef
            +AT_CHECK([test 0 = `ovn-sbctl list mac_binding | wc -l`])
            bbaaef
            +eth_src=505400000003
            bbaaef
            +eth_dst=ffffffffffff
            bbaaef
            +spa=$(ip_to_hex 10 0 0 30)
            bbaaef
            +tpa=$(ip_to_hex 10 0 0 30)
            bbaaef
            +send_garp 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +
            bbaaef
            +OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`])
            bbaaef
            +
            bbaaef
            +AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
            bbaaef
            +list mac_binding], [0], [lr0-sw0
            bbaaef
            +10.0.0.30
            bbaaef
            +50:54:00:00:00:03
            bbaaef
            +])
            bbaaef
            +
            bbaaef
            +AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
            bbaaef
            +grep controller | grep -v n_packets=0 | wc -l`])
            bbaaef
            +
            bbaaef
            +# Wait for an entry in table=67
            bbaaef
            +OVS_WAIT_UNTIL(
            bbaaef
            +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_packets=0 \
            bbaaef
            +| wc -l`]
            bbaaef
            +)
            bbaaef
            +
            bbaaef
            +# Send garp again. This time the packet should not be sent to ovn-controller.
            bbaaef
            +send_garp 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +# Wait for an entry in table=67
            bbaaef
            +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep n_packets=1 | wc -l`])
            bbaaef
            +
            bbaaef
            +# The packet should not be sent to ovn-controller. The packet
            bbaaef
            +count should be 1 only.
            bbaaef
            +AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
            bbaaef
            +grep controller | grep -v n_packets=0 | wc -l`])
            bbaaef
            +
            bbaaef
            +# Now send garp packet with different mac.
            bbaaef
            +eth_src=505400000013
            bbaaef
            +eth_dst=ffffffffffff
            bbaaef
            +spa=$(ip_to_hex 10 0 0 30)
            bbaaef
            +tpa=$(ip_to_hex 10 0 0 30)
            bbaaef
            +send_garp 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +
            bbaaef
            +# The garp packet should be sent to ovn-controller and the mac_binding entry
            bbaaef
            +# should be updated.
            bbaaef
            +OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +
            bbaaef
            +AT_CHECK([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`])
            bbaaef
            +
            bbaaef
            +AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
            bbaaef
            +list mac_binding], [0], [lr0-sw0
            bbaaef
            +10.0.0.30
            bbaaef
            +50:54:00:00:00:13
            bbaaef
            +])
            bbaaef
            +
            bbaaef
            +# Send ARP request to lrp - lr0-sw1 (20.0.0.1) using src mac 50:54:00:00:00:33
            bbaaef
            +# and src ip - 10.0.0.50.from sw0-p1.
            bbaaef
            +# ovn-controller should add the mac_binding entry
            bbaaef
            +#   logical_port - lr0
            bbaaef
            +#   IP           - 10.0.0.50
            bbaaef
            +#   MAC          - 50:54:00:00:00:33
            bbaaef
            +eth_src=505400000033
            bbaaef
            +eth_dst=ffffffffffff
            bbaaef
            +spa=$(ip_to_hex 10 0 0 50)
            bbaaef
            +tpa=$(ip_to_hex 20 0 0 1)
            bbaaef
            +
            bbaaef
            +send_garp 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +
            bbaaef
            +# The garp packet should be sent to ovn-controller and the mac_binding entry
            bbaaef
            +# should be updated.
            bbaaef
            +OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +
            bbaaef
            +OVS_WAIT_UNTIL(
            bbaaef
            +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:33 \
            bbaaef
            +| wc -l`]
            bbaaef
            +)
            bbaaef
            +
            bbaaef
            +AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
            bbaaef
            +find mac_binding ip=10.0.0.50], [0], [lr0-sw0
            bbaaef
            +10.0.0.50
            bbaaef
            +50:54:00:00:00:33
            bbaaef
            +])
            bbaaef
            +
            bbaaef
            +# Send the same packet again.
            bbaaef
            +send_garp 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +
            bbaaef
            +OVS_WAIT_UNTIL(
            bbaaef
            +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:33 \
            bbaaef
            +| grep n_packets=1 | wc -l`]
            bbaaef
            +)
            bbaaef
            +
            bbaaef
            +AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +
            bbaaef
            +# Now send ARP reply packet with IP - 10.0.0.40 and mac 505400000023
            bbaaef
            +eth_src=505400000023
            bbaaef
            +eth_dst=ffffffffffff
            bbaaef
            +spa=$(ip_to_hex 10 0 0 40)
            bbaaef
            +tpa=$(ip_to_hex 10 0 0 50)
            bbaaef
            +send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +
            bbaaef
            +# ovn-controller should add the
            bbaaef
            +#   mac_binding entry
            bbaaef
            +#     port - lr0-sw0
            bbaaef
            +#     ip - 10.0.0.40
            bbaaef
            +#     mac - 50:54:00:00:00:23
            bbaaef
            +
            bbaaef
            +# The garp packet should be sent to ovn-controller and the mac_binding entry
            bbaaef
            +# should be updated.
            bbaaef
            +OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +
            bbaaef
            +# Wait for an entry in table=67 for the learnt mac_binding entry.
            bbaaef
            +
            bbaaef
            +OVS_WAIT_UNTIL(
            bbaaef
            +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:23 \
            bbaaef
            +| wc -l`]
            bbaaef
            +)
            bbaaef
            +
            bbaaef
            +# Send the same garp reply. This time it should not be sent to ovn-controller.
            bbaaef
            +send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +OVS_WAIT_UNTIL(
            bbaaef
            +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:23 \
            bbaaef
            +| grep n_packets=1 | wc -l`]
            bbaaef
            +)
            bbaaef
            +
            bbaaef
            +AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +
            bbaaef
            +send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
            bbaaef
            +OVS_WAIT_UNTIL(
            bbaaef
            +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=67 | grep dl_src=50:54:00:00:00:23 \
            bbaaef
            +| grep n_packets=2 | wc -l`]
            bbaaef
            +)
            bbaaef
            +
            bbaaef
            +AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
            bbaaef
            +
            bbaaef
            +OVN_CLEANUP([hv1], [hv2])
            bbaaef
            +AT_CLEANUP
            bbaaef
            diff --git a/tests/test-ovn.c b/tests/test-ovn.c
            bbaaef
            index cf1bc5432..a7adc1c9a 100644
            bbaaef
            --- a/tests/test-ovn.c
            bbaaef
            +++ b/tests/test-ovn.c
            bbaaef
            @@ -1297,6 +1297,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
            bbaaef
                             .egress_ptable = 40,
            bbaaef
                             .output_ptable = 64,
            bbaaef
                             .mac_bind_ptable = 65,
            bbaaef
            +                .mac_lookup_ptable = 67,
            bbaaef
                         };
            bbaaef
                         struct ofpbuf ofpacts;
            bbaaef
                         ofpbuf_init(&ofpacts, 0);
            bbaaef
            -- 
            bbaaef
            2.21.0
            bbaaef