[ovs-dev] [PATCH ovn] Learn the mac binding only if required

Dumitru Ceara dceara at redhat.com
Mon Sep 16 09:31:41 UTC 2019


On Wed, Sep 11, 2019 at 10:05 PM <nusiddiq at redhat.com> wrote:
>
> From: Numan Siddique <nusiddiq at redhat.com>
>
> OVN has the actions - put_arp and put_nd to learn the mac bindings from the
> ARP/ND packets. These actions update the Southbound MAC_Binding table.
> These actions translates to controller actions. Whenever pinctrl thread
> receives such packets, it wakes up the main ovn-controller thread.
> If the MAC_Binding table is already upto date, this results
> in unnecessary CPU cyles. There are some security implications as well.
> A rogue VM can flood broadcast ARP request/reply packets and this
> could cause DoS issues. A physical switch may send periodic GARPs
> and these packets hit ovn-controllers.
>
> This patch solves these problems by learning the mac bindings only if
> required. There is no need to apply the put_arp/put_nd action if the
> Southbound MAC_Binding row is upto date.
>
> A new action - lookup_arp and lookup_nd is added which looks up the
> IP, MAC pair in the mac_binding table and updates the eth.dst if
> the entry is present, else eth.dst is set to 00:00:00:00:00:00.
>
> ovn-northd adds 2 new stages - lookup_arp and put_arp before ip_input
> in the router ingress pipeline.
>
> The logical flows looks something like:
>
> table=1 (lr_in_lookup_arp), priority=100  , match=(arp),
>          action=(xxreg1[0..47] = eth.dst;
>          lookup_arp(inport, arp.spa, arp.sha);
>          xxreg0[0..47] = eth.dst; eth.dst = xxreg1[0..47]; next;)
>
> table=1 (lr_in_lookup_arp), priority=0    , match=(1), action=(next;)
> ...
> table=2 (lr_in_put_arp   ), priority=100  ,
>          match=(arp.op == 2 && xxreg0[0..47] == 00:00:00:00:00:00),
>          action=(put_arp(inport, arp.spa, arp.sha);)
> table=2 (lr_in_put_arp   ), priority=90   , match=(arp.op == 2), action=(drop;)
> table=2 (lr_in_put_arp   ), priority=0    , match=(1), action=(next;)
>
> The lflow module of ovn-controller adds OF flows in table 31 (OFTABLE_MAC_LOOKUP)
> for each mac_binding entry with the match reg0 = ip && eth.src = mac with
> the action - eth.dst = mac
>
> Eg:
> table=31, priority=100,arp,reg0=0xaca8006f,reg14=0x3,metadata=0x3,dl_src=00:44:00:00:00:04
>           actions=mod_dl_dst:00:44:00:00:00:04
>
> This patch should also address the issue reported in 'Reported-at'
>
> Reported-at: https://bugzilla.redhat.com/1729846
> Reported-by: Haidong Li <haili at redhat.com>
> CC: Han ZHou <hzhou8 at ebay.com>
> CC: Dumitru Ceara <dceara at redhat.com>
> Signed-off-by: Numan Siddique <nusiddiq at redhat.com>

Hi Numan,

Thanks for taking care of this!
The code looks good to me but I'll let Han review it thoroughly as
he's more knowledgeable than me in this area. I tried it out on my
setup and it looks fine for the normal use cases.

I could still hog the CPU with a custom script that sends ARP packets
and varies the eth addresses but as I mentioned on the other thread
that can only be mitigated with rate limiting.

Tested-by: Dumitru Ceara <dceara at redhat.com>

Thanks,
Dumitru

> ---
>  controller/lflow.c      |  33 ++++-
>  controller/lflow.h      |   1 +
>  include/ovn/actions.h   |  12 ++
>  lib/actions.c           |  87 ++++++++++++
>  northd/ovn-northd.8.xml | 261 +++++++++++++++++++++++++-----------
>  northd/ovn-northd.c     | 210 ++++++++++++++++-------------
>  ovn-sb.xml              |  46 +++++++
>  tests/ovn.at            | 286 +++++++++++++++++++++++++++++++++++++++-
>  tests/test-ovn.c        |   1 +
>  utilities/ovn-trace.c   |  67 ++++++++++
>  10 files changed, 824 insertions(+), 180 deletions(-)
>
> diff --git a/controller/lflow.c b/controller/lflow.c
> index d0335a83a..45c4d725a 100644
> --- a/controller/lflow.c
> +++ b/controller/lflow.c
> @@ -687,6 +687,7 @@ consider_logical_flow(
>          .egress_ptable = OFTABLE_LOG_EGRESS_PIPELINE,
>          .output_ptable = output_ptable,
>          .mac_bind_ptable = OFTABLE_MAC_BINDING,
> +        .mac_lookup_ptable = OFTABLE_MAC_LOOKUP,
>      };
>      ovnacts_encode(ovnacts.data, ovnacts.size, &ep, &ofpacts);
>      ovnacts_free(ovnacts.data, ovnacts.size);
> @@ -777,7 +778,9 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>          return;
>      }
>
> -    struct match match = MATCH_CATCHALL_INITIALIZER;
> +    struct match get_arp_match = MATCH_CATCHALL_INITIALIZER;
> +    struct match lookup_arp_match = MATCH_CATCHALL_INITIALIZER;
> +
>      if (strchr(b->ip, '.')) {
>          ovs_be32 ip;
>          if (!ip_parse(b->ip, &ip)) {
> @@ -785,7 +788,9 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>              VLOG_WARN_RL(&rl, "bad 'ip' %s", b->ip);
>              return;
>          }
> -        match_set_reg(&match, 0, ntohl(ip));
> +        match_set_reg(&get_arp_match, 0, ntohl(ip));
> +        match_set_reg(&lookup_arp_match, 0, ntohl(ip));
> +        match_set_dl_type(&lookup_arp_match, htons(ETH_TYPE_ARP));
>      } else {
>          struct in6_addr ip6;
>          if (!ipv6_parse(b->ip, &ip6)) {
> @@ -795,17 +800,31 @@ consider_neighbor_flow(struct ovsdb_idl_index *sbrec_port_binding_by_name,
>          }
>          ovs_be128 value;
>          memcpy(&value, &ip6, sizeof(value));
> -        match_set_xxreg(&match, 0, ntoh128(value));
> +        match_set_xxreg(&get_arp_match, 0, ntoh128(value));
> +
> +        match_set_xxreg(&lookup_arp_match, 0, ntoh128(value));
> +        match_set_dl_type(&lookup_arp_match, htons(ETH_TYPE_IPV6));
> +        match_set_nw_proto(&lookup_arp_match, 58);
> +        match_set_icmp_code(&lookup_arp_match, 0);
>      }
>
> -    match_set_metadata(&match, htonll(pb->datapath->tunnel_key));
> -    match_set_reg(&match, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key);
> +    match_set_metadata(&get_arp_match, htonll(pb->datapath->tunnel_key));
> +    match_set_reg(&get_arp_match, MFF_LOG_OUTPORT - MFF_REG0, pb->tunnel_key);
> +
> +    match_set_metadata(&lookup_arp_match, htonll(pb->datapath->tunnel_key));
> +    match_set_reg(&lookup_arp_match, MFF_LOG_INPORT - MFF_REG0,
> +                  pb->tunnel_key);
>
>      uint64_t stub[1024 / 8];
>      struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(stub);
>      put_load(mac.ea, sizeof mac.ea, MFF_ETH_DST, 0, 48, &ofpacts);
> -    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &match, &ofpacts,
> -                    &b->header_.uuid);
> +    ofctrl_add_flow(flow_table, OFTABLE_MAC_BINDING, 100, 0, &get_arp_match,
> +                    &ofpacts, &b->header_.uuid);
> +
> +    match_set_dl_src(&lookup_arp_match, mac);
> +    ofctrl_add_flow(flow_table, OFTABLE_MAC_LOOKUP, 100, 0, &lookup_arp_match,
> +                    &ofpacts, &b->header_.uuid);
> +
>      ofpbuf_uninit(&ofpacts);
>  }
>
> diff --git a/controller/lflow.h b/controller/lflow.h
> index 54da00b49..d6d18978a 100644
> --- a/controller/lflow.h
> +++ b/controller/lflow.h
> @@ -58,6 +58,7 @@ struct uuid;
>   * you make any changes. */
>  #define OFTABLE_PHY_TO_LOG            0
>  #define OFTABLE_LOG_INGRESS_PIPELINE  8 /* First of LOG_PIPELINE_LEN tables. */
> +#define OFTABLE_MAC_LOOKUP           31
>  #define OFTABLE_REMOTE_OUTPUT        32
>  #define OFTABLE_LOCAL_OUTPUT         33
>  #define OFTABLE_CHECK_LOOPBACK       34
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index 145f27f25..37f74e281 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -73,8 +73,10 @@ struct ovn_extend_table;
>      OVNACT(ND_NA_ROUTER,      ovnact_nest)            \
>      OVNACT(GET_ARP,           ovnact_get_mac_bind)    \
>      OVNACT(PUT_ARP,           ovnact_put_mac_bind)    \
> +    OVNACT(LOOKUP_ARP,        ovnact_lookup_mac_bind) \
>      OVNACT(GET_ND,            ovnact_get_mac_bind)    \
>      OVNACT(PUT_ND,            ovnact_put_mac_bind)    \
> +    OVNACT(LOOKUP_ND,         ovnact_lookup_mac_bind) \
>      OVNACT(PUT_DHCPV4_OPTS,   ovnact_put_opts)        \
>      OVNACT(PUT_DHCPV6_OPTS,   ovnact_put_opts)        \
>      OVNACT(SET_QUEUE,         ovnact_set_queue)       \
> @@ -266,6 +268,14 @@ struct ovnact_put_mac_bind {
>      struct expr_field mac;      /* 48-bit Ethernet address. */
>  };
>
> +/* OVNACT_LOOKUP_ARP, OVNACT_LOOKUP_ND. */
> +struct ovnact_lookup_mac_bind {
> +    struct ovnact ovnact;
> +    struct expr_field port;     /* Logical port name. */
> +    struct expr_field ip;       /* 32-bit or 128-bit IP address. */
> +    struct expr_field mac;      /* 48-bit Ethernet address. */
> +};
> +
>  struct ovnact_gen_option {
>      const struct gen_opts_map *option;
>      struct expr_constant_set value;
> @@ -628,6 +638,8 @@ struct ovnact_encode_params {
>      uint8_t output_ptable;      /* OpenFlow table for 'output' to resubmit. */
>      uint8_t mac_bind_ptable;    /* OpenFlow table for 'get_arp'/'get_nd' to
>                                     resubmit. */
> +    uint8_t mac_lookup_ptable;  /* OpenFlow table for
> +                                   'lookup_arp'/'lookup_nd' to resubmit. */
>  };
>
>  void ovnacts_encode(const struct ovnact[], size_t ovnacts_len,
> diff --git a/lib/actions.c b/lib/actions.c
> index 6a5907e1b..9cb05e34c 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -1607,6 +1607,89 @@ ovnact_put_mac_bind_free(struct ovnact_put_mac_bind *put_mac OVS_UNUSED)
>  {
>  }
>
> +static void format_lookup_mac(const struct ovnact_lookup_mac_bind *lookup_mac,
> +                              struct ds *s, const char *name)
> +{
> +    ds_put_format(s, "%s(", name);
> +    expr_field_format(&lookup_mac->port, s);
> +    ds_put_cstr(s, ", ");
> +    expr_field_format(&lookup_mac->ip, s);
> +    ds_put_cstr(s, ", ");
> +    expr_field_format(&lookup_mac->mac, s);
> +    ds_put_cstr(s, ");");
> +}
> +
> +static void
> +format_LOOKUP_ARP(const struct ovnact_lookup_mac_bind *lookup_mac,
> +                         struct ds *s)
> +{
> +    format_lookup_mac(lookup_mac, s, "lookup_arp");
> +}
> +
> +static void
> +format_LOOKUP_ND(const struct ovnact_lookup_mac_bind *lookup_mac,
> +                        struct ds *s)
> +{
> +    format_lookup_mac(lookup_mac, s, "lookup_nd");
> +}
> +
> +static void
> +encode_lookup_mac(const struct ovnact_lookup_mac_bind *lookup_mac,
> +                  enum mf_field_id ip_field,
> +                  const struct ovnact_encode_params *ep,
> +                  struct ofpbuf *ofpacts)
> +{
> +    const struct arg args[] = {
> +        { expr_resolve_field(&lookup_mac->port), MFF_LOG_INPORT },
> +        { expr_resolve_field(&lookup_mac->ip), ip_field },
> +        { expr_resolve_field(&lookup_mac->mac),  MFF_ETH_SRC},
> +    };
> +
> +    encode_setup_args(args, ARRAY_SIZE(args), ofpacts);
> +
> +    put_load(0, MFF_ETH_DST, 0, 48, ofpacts);
> +    emit_resubmit(ofpacts, ep->mac_lookup_ptable);
> +
> +    encode_restore_args(args, ARRAY_SIZE(args), ofpacts);
> +}
> +
> +static void
> +encode_LOOKUP_ARP(const struct ovnact_lookup_mac_bind *lookup_mac,
> +                  const struct ovnact_encode_params *ep,
> +                  struct ofpbuf *ofpacts)
> +{
> +    encode_lookup_mac(lookup_mac, MFF_REG0, ep, ofpacts);
> +}
> +
> +static void
> +encode_LOOKUP_ND(const struct ovnact_lookup_mac_bind *lookup_mac,
> +                        const struct ovnact_encode_params *ep,
> +                        struct ofpbuf *ofpacts)
> +{
> +    encode_lookup_mac(lookup_mac, MFF_XXREG0, ep, ofpacts);
> +}
> +
> +static void
> +parse_lookup_mac_bind(struct action_context *ctx, int width,
> +                      struct ovnact_lookup_mac_bind *lookup_mac)
> +{
> +    lexer_force_match(ctx->lexer, LEX_T_LPAREN);
> +    action_parse_field(ctx, 0, false, &lookup_mac->port);
> +    lexer_force_match(ctx->lexer, LEX_T_COMMA);
> +    action_parse_field(ctx, width, false, &lookup_mac->ip);
> +    lexer_force_match(ctx->lexer, LEX_T_COMMA);
> +    action_parse_field(ctx, 48, false, &lookup_mac->mac);
> +    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
> +}
> +
> +static void
> +ovnact_lookup_mac_bind_free(
> +    struct ovnact_lookup_mac_bind *lookup_mac OVS_UNUSED)
> +{
> +
> +}
> +
> +
>  static void
>  parse_gen_opt(struct action_context *ctx, struct ovnact_gen_option *o,
>                const struct hmap *gen_opts, const char *opts_type)
> @@ -2784,10 +2867,14 @@ parse_action(struct action_context *ctx)
>          parse_get_mac_bind(ctx, 32, ovnact_put_GET_ARP(ctx->ovnacts));
>      } else if (lexer_match_id(ctx->lexer, "put_arp")) {
>          parse_put_mac_bind(ctx, 32, ovnact_put_PUT_ARP(ctx->ovnacts));
> +    } else if (lexer_match_id(ctx->lexer, "lookup_arp")) {
> +        parse_lookup_mac_bind(ctx, 32, ovnact_put_LOOKUP_ARP(ctx->ovnacts));
>      } else if (lexer_match_id(ctx->lexer, "get_nd")) {
>          parse_get_mac_bind(ctx, 128, ovnact_put_GET_ND(ctx->ovnacts));
>      } else if (lexer_match_id(ctx->lexer, "put_nd")) {
>          parse_put_mac_bind(ctx, 128, ovnact_put_PUT_ND(ctx->ovnacts));
> +    } else if (lexer_match_id(ctx->lexer, "lookup_nd")) {
> +        parse_lookup_mac_bind(ctx, 128, ovnact_put_LOOKUP_ND(ctx->ovnacts));
>      } else if (lexer_match_id(ctx->lexer, "set_queue")) {
>          parse_SET_QUEUE(ctx);
>      } else if (lexer_match_id(ctx->lexer, "log")) {
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index b34ef687a..e411705cd 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -1218,7 +1218,174 @@ output;
>        Other packets are implicitly dropped.
>      </p>
>
> -    <h3>Ingress Table 1: IP Input</h3>
> +    <h3>Ingress Table 1: ARP/ND lookup</h3>
> +
> +    <p>
> +      For ARP and Neighbor Discovery packets, this table looks into the
> +      <ref db="OVN_Southbound" table="MAC_Binding"/> records to determine
> +      if OVN needs to learn the mac bindings. Following flows are added:
> +    </p>
> +
> +    <ul>
> +      <li>
> +        <p>
> +          A priority-100 flow which matches on IPv4 ARP packet and applies
> +          the actions:
> +        </p>
> +
> +        <pre>
> +xxreg1[0..47] = eth.dst;;
> +lookup_arp(inport, arp.spa, arp.sha);
> +xxreg0[0..47] = eth.dst;
> +eth.dst = xxreg1[0..47];
> +next;
> +        </pre>
> +      </li>
> +
> +      <li>
> +        <p>
> +          A priority-100 flow which matches on IPv6 Neighbor Discovery
> +          advertisement packet and applies the actions:
> +        </p>
> +
> +        <pre>
> +xxreg1[0..47] = eth.dst;;
> +lookup_nd(inport, nd.target, nd.tll);
> +xxreg0[0..47] = eth.dst;
> +eth.dst = xxreg1[0..47];
> +next;
> +        </pre>
> +      </li>
> +
> +      <li>
> +        <p>
> +          A priority-100 flow which matches on IPv6 Neighbor Discovery
> +          solicitation packet and applies the actions:
> +        </p>
> +
> +        <pre>
> +xxreg1[0..47] = eth.dst;;
> +lookup_nd(inport, ip6.src, nd.sll);
> +xxreg0[0..47] = eth.dst;
> +eth.dst = xxreg1[0..47];
> +next;
> +        </pre>
> +      </li>
> +
> +      <li>
> +        A priority-0 fallback flow that matches all packets
> +        and advances to the next table.
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 2: MAC learning</h3>
> +
> +    <p>
> +      This table adds flows to learn the mac bindings from the ARP and
> +      IPv6 Neighbor Solicitation/Advertisement packets if ARP/ND lookup
> +      failed in the previous table.
> +    </p>
> +
> +    <p>
> +      xxreg0[0..47] will be <code>00:00:00:00:00:00</code> if the
> +      <code>lookup_arp/lookup_nd</code> in the previous table failed the
> +      lookup in the mac binding table.
> +    </p>
> +
> +    <ul>
> +      <li>
> +        A priority-100 flow with the match <code>arp.op == 2 &amp;&amp;
> +        xxreg0[0..47] == 00:00:00:00:00:00</code> and applies the
> +        action <code>put_arp(inport, arp.spa, arp.sha);</code>
> +      </li>
> +
> +      <li>
> +        A priority-90 flow with the match <code>arp.op == 2</code> and
> +        applies the action <code>drop;</code>
> +      </li>
> +
> +      <li>
> +        <p>
> +          MAC learning from ARP requests.
> +        </p>
> +
> +        <p>
> +          These flows populates the mac binding table of the logical router
> +          port from the ARP request packets for the router's own IP address.
> +          The ARP requests are handled only if the requestor's IP belongs
> +          to the same subnets of the logical router port.
> +          For each router port <var>P</var> that owns IP address <var>A</var>,
> +          which belongs to subnet <var>S</var> with prefix length <var>L</var>,
> +          and Ethernet address <var>E</var>, a priority-90 flow matches
> +          <code>inport == <var>P</var> &amp;&amp;
> +          arp.spa == <var>S</var>/<var>L</var> &amp;&amp; arp.op == 1
> +          &amp;&amp; arp.tpa == <var>A</var> &amp;&amp;
> +          xxreg0[0..47] == 00:00:00:00:00:00</code> (ARP request) with the
> +          following actions:
> +        </p>
> +
> +        <pre>
> +put_arp(inport, arp.spa, arp.sha);
> +next;
> +        </pre>
> +      </li>
> +
> +      <li>
> +        <p>
> +          MAC learning from ARP requests not redirected to router IPs.
> +        </p>
> +
> +        <p>
> +          For each router port <var>P</var> that owns IP address
> +          <var>A</var>, which belongs to subnet <var>S</var> with prefix length
> +          <var>L</var>, and Ethernet address <var>E</var>, a priority-90 flow
> +          matches <code>inport == <var>P</var> &amp;&amp;
> +          arp.spa == <var>S</var>/<var>L</var> &amp;&amp; arp.op == 1
> +          &amp;&amp; xxreg0[0..47] == 00:00:00:00:00:00</code> (ARP request)
> +          with the action <code>put_arp(inport, arp.spa, arp.sha);</code>.
> +        </p>
> +
> +        <p>
> +          If the logical router port <var>P</var> is a distributed gateway
> +          router port, additional match
> +          <code>is_chassis_resident(cr-<var>P</var>)</code> is added so that
> +          the resident gateway chassis handles such ARP packets.
> +        </p>
> +      </li>
> +
> +      <li>
> +        <p>
> +          MAC learning from IPv6 Neighbor Solicitation packets.
> +        </p>
> +
> +        <p>
> +          A priority-100 flow with the match <code>nd_ns &amp;&amp;
> +          xxreg0[0..47] == 00:00:00:00:00:00</code> and applies the
> +          below actions and advancing the packet to the next table.
> +        </p>
> +
> +        <pre>
> +put_nd(inport, ip6.src, nd.sll);
> +next;
> +        </pre>
> +      </li>
> +
> +      <li>
> +        <p>
> +          MAC learning from IPv6 Neighbor Advertisement packets.
> +          This flow uses Neighbor Advertisements to populate the
> +          logical router's mac binding table.
> +        </p>
> +
> +        <p>
> +          A priority-100 flow with the match <code>nd_na &amp;&amp;
> +          xxreg0[0..47] == 00:00:00:00:00:00</code> and applies the
> +          action <code>put_nd(inport, nd.target, nd.tll);</code>
> +        </p>
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 3: IP Input</h3>
>
>      <p>
>        This table is the core of the logical router datapath functionality.  It
> @@ -1315,8 +1482,7 @@ next;
>          </p>
>
>          <p>
> -          These flows reply to ARP requests for the router's own IP address
> -          and populates mac binding table of the logical router port.
> +          These flows reply to ARP requests for the router's own IP address.
>            The ARP requests are handled only if the requestor's IP belongs
>            to the same subnets of the logical router port.
>            For each router port <var>P</var> that owns IP address <var>A</var>,
> @@ -1329,7 +1495,6 @@ next;
>          </p>
>
>          <pre>
> -put_arp(inport, arp.spa, arp.sha);
>  eth.dst = eth.src;
>  eth.src = <var>E</var>;
>  arp.op = 2; /* ARP reply. */
> @@ -1365,17 +1530,6 @@ output;
>          </p>
>        </li>
>
> -      <li>
> -        <p>
> -          These flows handles ARP requests not for router's own IP address.
> -          They use the SPA and SHA to populate the logical router port's
> -          mac binding table, with priority 80.  The typical use case of
> -          these flows are GARP requests handling.  For the gateway port
> -          on a distributed logical router, these flows are only programmed
> -          on the gateway port instance on the <code>redirect-chassis</code>.
> -        </p>
> -      </li>
> -
>        <li>
>          <p>
>            These flows reply to ARP requests for the virtual IP addresses
> @@ -1446,36 +1600,6 @@ arp.sha = <var>external_mac</var>;
>          </ul>
>        </li>
>
> -      <li>
> -        <p>
> -          ARP reply handling.  Following flows are added to handle ARP replies.
> -        </p>
> -
> -        <p>
> -          For each distributed gateway logical router port a priority-92 flow
> -          with match <code>inport == <var>P</var> &amp;&amp;
> -          is_chassis_resident(cr-<var>P</var>) &amp;&amp; eth.bcast &amp;&amp;
> -          arp.op == 2 &amp;&amp; arp.spa == <var>I</var></code> with the
> -          action <code>put_arp(inport, arp.spa, arp.sha);</code> so that the
> -          resident gateway chassis can learn the GARP reply, where
> -          <var>P</var> is the distributed gateway router port name,
> -          <var>I</var> is the logical router port's network address.
> -        </p>
> -
> -        <p>
> -          For each distributed gateway logical router port a priority-92 flow
> -          with match <code>inport == <var>P</var> &amp;&amp;
> -          !is_chassis_resident(cr-<var>P</var>) &amp;&amp; eth.bcast &amp;&amp;
> -          arp.op == 2 &amp;&amp; arp.spa == <var>I</var></code> with the action
> -          <code>drop;</code> so that other chassis drop this packet.
> -        </p>
> -
> -        <p>
> -          A priority-90 flow with match <code>arp.op == 2</code> has actions
> -          <code>put_arp(inport, arp.spa, arp.sha);</code>.
> -        </p>
> -      </li>
> -
>        <li>
>          <p>
>            Reply to IPv6 Neighbor Solicitations.  These flows reply to
> @@ -1494,7 +1618,6 @@ arp.sha = <var>external_mac</var>;
>          </p>
>
>          <pre>
> -put_nd(inport, ip6.src, nd.sll);
>  nd_na_router {
>      eth.src = <var>E</var>;
>      ip6.src = <var>A</var>;
> @@ -1516,7 +1639,6 @@ nd_na_router {
>          </p>
>
>          <pre>
> -put_nd(inport, ip6.src, nd.sll);
>  nd_na {
>      eth.src = <var>E</var>;
>      ip6.src = <var>A</var>;
> @@ -1540,23 +1662,6 @@ nd_na {
>          </p>
>        </li>
>
> -      <li>
> -        IPv6 neighbor advertisement handling.  This flow uses neighbor
> -        advertisements to populate the logical router's mac binding
> -        table.  A priority-90 flow with match <code>nd_na</code>
> -        has actions <code>put_nd(inport, nd.target, nd.tll);</code>.
> -      </li>
> -
> -      <li>
> -        IPv6 neighbor solicitation for non-hosted addresses handling.
> -        This flow uses neighbor solicitations to populate the logical
> -        router's mac binding table (ones that were directed at the
> -        logical router would have matched the priority-90 neighbor
> -        solicitation flow already).  A priority-80 flow with match
> -        <code>nd_ns</code> has actions
> -        <code>put_nd(inport, ip6.src, nd.sll);</code>.
> -      </li>
> -
>        <li>
>          <p>
>            UDP port unreachable.  Priority-80 flows generate ICMP port
> @@ -1670,7 +1775,7 @@ icmp6 {
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 2: DEFRAG</h3>
> +    <h3>Ingress Table 4: DEFRAG</h3>
>
>      <p>
>        This is to send packets to connection tracker for tracking and
> @@ -1728,7 +1833,7 @@ icmp6 {
>        </li>
>      </ul>
>
> -    <p>Ingress Table 3: UNSNAT on Distributed Routers</p>
> +    <p>Ingress Table 5: UNSNAT on Distributed Routers</p>
>
>      <ul>
>        <li>
> @@ -1767,7 +1872,7 @@ icmp6 {
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 4: DNAT</h3>
> +    <h3>Ingress Table 6: DNAT</h3>
>
>      <p>
>        Packets enter the pipeline with destination IP address that needs to
> @@ -1775,7 +1880,7 @@ icmp6 {
>        in the reverse direction needs to be unDNATed.
>      </p>
>
> -    <p>Ingress Table 4: Load balancing DNAT rules</p>
> +    <p>Ingress Table 6: Load balancing DNAT rules</p>
>
>      <p>
>        Following load balancing DNAT flows are added for Gateway router or
> @@ -1836,7 +1941,7 @@ icmp6 {
>        </li>
>      </ul>
>
> -    <p>Ingress Table 4: DNAT on Gateway Routers</p>
> +    <p>Ingress Table 6: DNAT on Gateway Routers</p>
>
>      <ul>
>        <li>
> @@ -1862,7 +1967,7 @@ icmp6 {
>        </li>
>      </ul>
>
> -    <p>Ingress Table 4: DNAT on Distributed Routers</p>
> +    <p>Ingress Table 6: DNAT on Distributed Routers</p>
>
>      <p>
>        On distributed routers, the DNAT table only handles packets
> @@ -1909,7 +2014,7 @@ icmp6 {
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 5: IPv6 ND RA option processing</h3>
> +    <h3>Ingress Table 7: IPv6 ND RA option processing</h3>
>
>      <ul>
>        <li>
> @@ -1939,7 +2044,7 @@ reg0[5] = put_nd_ra_opts(<var>options</var>);next;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 6: IPv6 ND RA responder</h3>
> +    <h3>Ingress Table 8: IPv6 ND RA responder</h3>
>
>      <p>
>        This table implements IPv6 ND RA responder for the IPv6 ND RA replies
> @@ -1984,7 +2089,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 7: IP Routing</h3>
> +    <h3>Ingress Table 9: IP Routing</h3>
>
>      <p>
>        A packet that arrives at this table is an IP packet that should be
> @@ -2134,7 +2239,7 @@ next;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 8: ARP/ND Resolution</h3>
> +    <h3>Ingress Table 10: ARP/ND Resolution</h3>
>
>      <p>
>        Any packet that reaches this table is an IP packet whose next-hop
> @@ -2281,7 +2386,7 @@ next;
>
>      </ul>
>
> -    <h3>Ingress Table 9: Check packet length</h3>
> +    <h3>Ingress Table 11: Check packet length</h3>
>
>      <p>
>        For distributed logical routers with distributed gateway port configured
> @@ -2311,7 +2416,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>); next;
>        and advances to the next table.
>      </p>
>
> -    <h3>Ingress Table 10: Handle larger packets</h3>
> +    <h3>Ingress Table 12: Handle larger packets</h3>
>
>      <p>
>        For distributed logical routers with distributed gateway port configured
> @@ -2360,7 +2465,7 @@ icmp4 {
>        and advances to the next table.
>      </p>
>
> -    <h3>Ingress Table 11: Gateway Redirect</h3>
> +    <h3>Ingress Table 13: Gateway Redirect</h3>
>
>      <p>
>        For distributed logical routers where one of the logical router
> @@ -2422,7 +2527,7 @@ icmp4 {
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 12: ARP Request</h3>
> +    <h3>Ingress Table 14: ARP Request</h3>
>
>      <p>
>        In the common case where the Ethernet destination has been resolved, this
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index c24e4d864..9100d6719 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -145,19 +145,21 @@ enum ovn_stage {
>                                                                        \
>      /* Logical router ingress stages. */                              \
>      PIPELINE_STAGE(ROUTER, IN,  ADMISSION,      0, "lr_in_admission")    \
> -    PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,       1, "lr_in_ip_input")     \
> -    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,         2, "lr_in_defrag")       \
> -    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,         3, "lr_in_unsnat")       \
> -    PIPELINE_STAGE(ROUTER, IN,  DNAT,           4, "lr_in_dnat")         \
> -    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,  5, "lr_in_nd_ra_options") \
> -    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE, 6, "lr_in_nd_ra_response") \
> -    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,     7, "lr_in_ip_routing")   \
> -    PIPELINE_STAGE(ROUTER, IN,  POLICY,         8, "lr_in_policy")       \
> -    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,    9, "lr_in_arp_resolve")  \
> -    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN   , 10, "lr_in_chk_pkt_len")   \
> -    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,    11,"lr_in_larger_pkts")   \
> -    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,    12, "lr_in_gw_redirect")  \
> -    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,    13, "lr_in_arp_request")  \
> +    PIPELINE_STAGE(ROUTER, IN,  LOOKUP_ARP,     1, "lr_in_lookup_arp") \
> +    PIPELINE_STAGE(ROUTER, IN,  PUT_ARP,        2, "lr_in_put_arp") \
> +    PIPELINE_STAGE(ROUTER, IN,  IP_INPUT,       3, "lr_in_ip_input")     \
> +    PIPELINE_STAGE(ROUTER, IN,  DEFRAG,         4, "lr_in_defrag")       \
> +    PIPELINE_STAGE(ROUTER, IN,  UNSNAT,         5, "lr_in_unsnat")       \
> +    PIPELINE_STAGE(ROUTER, IN,  DNAT,           6, "lr_in_dnat")         \
> +    PIPELINE_STAGE(ROUTER, IN,  ND_RA_OPTIONS,  7, "lr_in_nd_ra_options") \
> +    PIPELINE_STAGE(ROUTER, IN,  ND_RA_RESPONSE, 8, "lr_in_nd_ra_response") \
> +    PIPELINE_STAGE(ROUTER, IN,  IP_ROUTING,     9, "lr_in_ip_routing")   \
> +    PIPELINE_STAGE(ROUTER, IN,  POLICY,         10, "lr_in_policy")       \
> +    PIPELINE_STAGE(ROUTER, IN,  ARP_RESOLVE,    11, "lr_in_arp_resolve")  \
> +    PIPELINE_STAGE(ROUTER, IN,  CHK_PKT_LEN   , 12, "lr_in_chk_pkt_len")   \
> +    PIPELINE_STAGE(ROUTER, IN,  LARGER_PKTS,    13,"lr_in_larger_pkts")   \
> +    PIPELINE_STAGE(ROUTER, IN,  GW_REDIRECT,    14, "lr_in_gw_redirect")  \
> +    PIPELINE_STAGE(ROUTER, IN,  ARP_REQUEST,    15, "lr_in_arp_request")  \
>                                                                        \
>      /* Logical router egress stages. */                               \
>      PIPELINE_STAGE(ROUTER, OUT, UNDNAT,    0, "lr_out_undnat")        \
> @@ -6367,7 +6369,111 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                        ds_cstr(&match), "next;");
>      }
>
> -    /* Logical router ingress table 1: IP Input. */
> +    /* Logical router ingress table 1: LOOKUP_ARP and table 2: PUT_ARP. */
> +    HMAP_FOR_EACH (od, key_node, datapaths) {
> +        if (!od->nbr) {
> +            continue;
> +        }
> +
> +        /* Learn from ARP requests and ARP replies. A typical
> +         * use case is GARP request handling.
> +         * Table LOOKUP_ARP does a lookup for the (arp.spa, arp.sha)
> +         * in the mac binding table using the 'lookup_arp' action.
> +         * If it is present, then this action stores the mac in the eth.dst
> +         * of the packet. Before calling 'lookup_arp' we store
> +         * eth.dst in xxreg1. After 'lookup_arp' action is applied
> +         * we store the searched mac - eth.dst in xxreg0 and restore
> +         * eth.dst to its original value.
> +         *
> +         * Table PUT_ARP learns the mac using the action - 'put_arp'
> +         * only if xxreg0 is 00:00:00:00:00:00. There is no need to learn
> +         * the mac otherwise.
> +         *
> +         * The same thing will be done for IPv6 ND/NS packets.
> +         * */
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_ARP, 100, "arp",
> +                      "xxreg1[0..47] = eth.dst; "
> +                      "lookup_arp(inport, arp.spa, arp.sha); "
> +                      "xxreg0[0..47] = eth.dst; "
> +                      "eth.dst = xxreg1[0..47]; next;");
> +
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_PUT_ARP, 100,
> +                      "arp.op == 2 && xxreg0[0..47] == 00:00:00:00:00:00",
> +                      "put_arp(inport, arp.spa, arp.sha);");
> +
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_PUT_ARP, 90, "arp.op == 2",
> +                      "drop;");
> +
> +        /* IPv6 ND/NS handling. */
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_ARP, 100, "nd_na",
> +                      "xxreg1[0..47] = eth.dst; "
> +                      "lookup_nd(inport, nd.target, nd.tll); "
> +                      "xxreg0[0..47] = eth.dst; "
> +                      "eth.dst = xxreg1[0..47]; next;");
> +
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_ARP, 100, "nd_ns",
> +                      "xxreg1[0..47] = eth.dst; "
> +                      "lookup_nd(inport, ip6.src, nd.sll); "
> +                      "xxreg0[0..47] = eth.dst; "
> +                      "eth.dst = xxreg1[0..47]; next;");
> +
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_PUT_ARP, 100,
> +                      "nd_na && xxreg0[0..47] == 00:00:00:00:00:00",
> +                      "put_nd(inport, nd.target, nd.tll);");
> +
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_PUT_ARP, 100,
> +                      "nd_ns && xxreg0[0..47] == 00:00:00:00:00:00",
> +                      "put_nd(inport, ip6.src, nd.sll); next;");
> +
> +        /* Pass other traffic not already handled to the next table for
> +         * routing. */
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_LOOKUP_ARP, 0, "1", "next;");
> +        ovn_lflow_add(lflows, od, S_ROUTER_IN_PUT_ARP, 0, "1", "next;");
> +    }
> +
> +    HMAP_FOR_EACH (op, key_node, ports) {
> +        if (!op->nbrp) {
> +            continue;
> +        }
> +
> +        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> +            ds_clear(&match);
> +            ds_put_format(&match,
> +                          "inport == %s && arp.spa == %s/%u && arp.tpa == %s"
> +                          " && arp.op == 1 && "
> +                          "xxreg0[0..47] == 00:00:00:00:00:00",
> +                          op->json_key,
> +                          op->lrp_networks.ipv4_addrs[i].network_s,
> +                          op->lrp_networks.ipv4_addrs[i].plen,
> +                          op->lrp_networks.ipv4_addrs[i].addr_s);
> +            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_PUT_ARP, 100,
> +                          ds_cstr(&match),
> +                          "put_arp(inport, arp.spa, arp.sha); next; ");
> +        }
> +
> +        /* Learn from ARP requests that were not directed at us. A typical
> +         * use case is GARP request handling.  (A priority-90 flow will
> +         * respond to request to us and learn the sender's mac address.) */
> +        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> +            ds_clear(&match);
> +            ds_put_format(&match,
> +                          "inport == %s && arp.spa == %s/%u && arp.op == 1 && "
> +                          "xxreg0[0..47] == 00:00:00:00:00:00",
> +                          op->json_key,
> +                          op->lrp_networks.ipv4_addrs[i].network_s,
> +                          op->lrp_networks.ipv4_addrs[i].plen);
> +            if (op->od->l3dgw_port && op == op->od->l3dgw_port
> +                && op->od->l3redirect_port) {
> +                ds_put_format(&match, " && is_chassis_resident(%s)",
> +                              op->od->l3redirect_port->json_key);
> +            }
> +            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_PUT_ARP, 90,
> +                          ds_cstr(&match),
> +                          "put_arp(inport, arp.spa, arp.sha);");
> +        }
> +    }
> +
> +    /* Logical router ingress table 3: IP Input. */
>      HMAP_FOR_EACH (od, key_node, datapaths) {
>          if (!od->nbr) {
>              continue;
> @@ -6389,11 +6495,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>          ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 95, "ip4.mcast",
>                        od->mcast_info.rtr.relay ? "next;" : "drop;");
>
> -        /* ARP reply handling.  Use ARP replies to populate the logical
> -         * router's ARP table. */
> -        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "arp.op == 2",
> -                      "put_arp(inport, arp.spa, arp.sha);");
> -
>          /* Drop Ethernet local broadcast.  By definition this traffic should
>           * not be forwarded.*/
>          ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 50,
> @@ -6405,23 +6506,12 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>          ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 30,
>                        ds_cstr(&match), "drop;");
>
> -        /* ND advertisement handling.  Use advertisements to populate
> -         * the logical router's ARP/ND table. */
> -        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 90, "nd_na",
> -                      "put_nd(inport, nd.target, nd.tll);");
> -
> -        /* Lean from neighbor solicitations that were not directed at
> -         * us.  (A priority-90 flow will respond to requests to us and
> -         * learn the sender's mac address. */
> -        ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 80, "nd_ns",
> -                      "put_nd(inport, ip6.src, nd.sll);");
> -
>          /* Pass other traffic not already handled to the next table for
>           * routing. */
>          ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_INPUT, 0, "1", "next;");
>      }
>
> -    /* Logical router ingress table 1: IP Input for IPv4. */
> +    /* Logical router ingress table 4: IP Input for IPv4. */
>      HMAP_FOR_EACH (op, key_node, ports) {
>          if (!op->nbrp) {
>              continue;
> @@ -6531,7 +6621,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>
>              ds_clear(&actions);
>              ds_put_format(&actions,
> -                "put_arp(inport, arp.spa, arp.sha); "
>                  "eth.dst = eth.src; "
>                  "eth.src = %s; "
>                  "arp.op = 2; /* ARP reply */ "
> @@ -6550,62 +6639,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>                            ds_cstr(&match), ds_cstr(&actions));
>          }
>
> -        /* Learn from ARP requests that were not directed at us. A typical
> -         * use case is GARP request handling.  (A priority-90 flow will
> -         * respond to request to us and learn the sender's mac address.) */
> -        for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> -            ds_clear(&match);
> -            ds_put_format(&match,
> -                          "inport == %s && arp.spa == %s/%u && arp.op == 1",
> -                          op->json_key,
> -                          op->lrp_networks.ipv4_addrs[i].network_s,
> -                          op->lrp_networks.ipv4_addrs[i].plen);
> -            if (op->od->l3dgw_port && op == op->od->l3dgw_port
> -                && op->od->l3redirect_port) {
> -                ds_put_format(&match, " && is_chassis_resident(%s)",
> -                              op->od->l3redirect_port->json_key);
> -            }
> -            ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 80,
> -                          ds_cstr(&match),
> -                          "put_arp(inport, arp.spa, arp.sha);");
> -
> -        }
> -
> -        /* Handle GARP reply packets received on a distributed router gateway
> -         * port. GARP reply broadcast packets could be sent by external
> -         * switches. We don't want them to be handled by all the
> -         * ovn-controllers if they receive it. So add a priority-92 flow to
> -         * apply the put_arp action on a redirect chassis and drop it on
> -         * other chassis.
> -         * Note that we are already adding a priority-90 logical flow in the
> -         * table S_ROUTER_IN_IP_INPUT to apply the put_arp action if
> -         * arp.op == 2.
> -         * */
> -        if (op->od->l3dgw_port && op == op->od->l3dgw_port
> -                && op->od->l3redirect_port) {
> -            for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
> -                ds_clear(&match);
> -                ds_put_format(&match,
> -                              "inport == %s && is_chassis_resident(%s) && "
> -                              "eth.bcast && arp.op == 2 && arp.spa == %s/%u",
> -                              op->json_key, op->od->l3redirect_port->json_key,
> -                              op->lrp_networks.ipv4_addrs[i].network_s,
> -                              op->lrp_networks.ipv4_addrs[i].plen);
> -                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 92,
> -                              ds_cstr(&match),
> -                              "put_arp(inport, arp.spa, arp.sha);");
> -                ds_clear(&match);
> -                ds_put_format(&match,
> -                              "inport == %s && !is_chassis_resident(%s) && "
> -                              "eth.bcast && arp.op == 2 && arp.spa == %s/%u",
> -                              op->json_key, op->od->l3redirect_port->json_key,
> -                              op->lrp_networks.ipv4_addrs[i].network_s,
> -                              op->lrp_networks.ipv4_addrs[i].plen);
> -                ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 92,
> -                              ds_cstr(&match), "drop;");
> -            }
> -        }
> -
>          /* A set to hold all load-balancer vips that need ARP responses. */
>          struct sset all_ips = SSET_INITIALIZER(&all_ips);
>          int addr_family;
> @@ -6916,7 +6949,6 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>
>              ds_clear(&actions);
>              ds_put_format(&actions,
> -                          "put_nd(inport, ip6.src, nd.sll); "
>                            "nd_na_router { "
>                            "eth.src = %s; "
>                            "ip6.src = %s; "
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 477e7bc7a..dd733c8df 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -1397,6 +1397,29 @@
>            <p><b>Example:</b> <code>put_arp(inport, arp.spa, arp.sha);</code></p>
>          </dd>
>
> +        <dt>
> +          <code>lookup_arp(<var>P</var>, <var>A</var>, <var>M</var>);</code>
> +        </dt>
> +
> +        <dd>
> +          <p>
> +            <b>Parameters</b>: logical port string field <var>P</var>, 32-bit
> +            IP address field <var>A</var>, 48-bit MAC address field
> +            <var>M</var>.
> +          </p>
> +
> +          <p>
> +            Looks up <var>A</var> and <var>M</var> in <var>P</var>'s mac
> +            binding table. If an entry is found, stores <var>M</var> in
> +            <code>eth.dst</code>, otherwise stores
> +            <code>00:00:00:00:00:00</code> in <code>eth.dst</code>.
> +          </p>
> +
> +          <p>
> +            <b>Example:</b> <code>lookup_arp(inport, arp.spa, arp.sha);</code>
> +          </p>
> +        </dd>
> +
>          <dt><code>nd_ns { <var>action</var>; </code>...<code> };</code></dt>
>          <dd>
>            <p>
> @@ -1553,6 +1576,29 @@
>            <p><b>Example:</b> <code>put_nd(inport, nd.target, nd.tll);</code></p>
>          </dd>
>
> +        <dt>
> +          <code>lookup_nd(<var>P</var>, <var>A</var>, <var>M</var>);</code>
> +        </dt>
> +
> +        <dd>
> +          <p>
> +            <b>Parameters</b>: logical port string field <var>P</var>, 128-bit
> +            IP address field <var>A</var>, 48-bit MAC address field
> +            <var>M</var>.
> +          </p>
> +
> +          <p>
> +            Looks up <var>A</var> and <var>M</var> in <var>P</var>'s mac
> +            binding table. If an entry is found, stores <var>M</var> in
> +            <code>eth.dst</code>, otherwise stores
> +            <code>00:00:00:00:00:00</code> in <code>eth.dst</code>.
> +          </p>
> +
> +          <p>
> +            <b>Example:</b> <code>lookup_nd(inport, ip6.src, eth.src);</code>
> +          </p>
> +        </dd>
> +
>          <dt>
>            <code><var>R</var> = put_dhcp_opts(<var>D1</var> = <var>V1</var>, <var>D2</var> = <var>V2</var>, ..., <var>Dn</var> = <var>Vn</var>);</code>
>          </dt>
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 2a35b4e15..6db63b595 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -1117,6 +1117,31 @@ put_arp(inport, arp.spa, arp.sha);
>      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[]
>      has prereqs eth.type == 0x806 && eth.type == 0x806
>
> +# lookup_arp
> +lookup_arp(inport, ip4.dst, eth.src);
> +    encodes as push:NXM_NX_REG0[],push:NXM_OF_IP_DST[],pop:NXM_NX_REG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,31),pop:NXM_NX_REG0[]
> +    has prereqs eth.type == 0x800
> +lookup_arp(inport, arp.spa, arp.sha);
> +    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:00:00:00:00:00:00->eth_dst,resubmit(,31),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_REG0[]
> +    has prereqs eth.type == 0x806 && eth.type == 0x806
> +
> +lookup_arp;
> +    Syntax error at `;' expecting `('.
> +lookup_arp();
> +    Syntax error at `)' expecting field name.
> +lookup_arp(inport);
> +    Syntax error at `)' expecting `,'.
> +lookup_arp(inport ip4.dst);
> +    Syntax error at `ip4.dst' expecting `,'.
> +lookup_arp(inport, ip4.dst;
> +    Syntax error at `;' expecting `,'.
> +lookup_arp(inport, ip4.dst, eth.src;
> +    Syntax error at `;' expecting `)'.
> +lookup_arp(inport, eth.dst);
> +    Cannot use 48-bit field eth.dst[0..47] where 32-bit field is required.
> +lookup_arp(inport, ip4.src, ip4.dst);
> +    Cannot use 32-bit field ip4.dst[0..31] where 48-bit field is required.
> +
>  # put_dhcp_opts
>  reg1[0] = put_dhcp_opts(offerip = 1.2.3.4, router = 10.0.0.1);
>      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)
> @@ -1217,6 +1242,33 @@ reg1[0] = put_dhcpv6_opts(ia_addr="ae70::4");
>  reg1[0] = put_dhcpv6_opts(ia_addr=ae70::4, domain_search=ae70::1);
>      DHCPv6 option domain_search requires string value.
>
> +# lookup_nd
> +lookup_nd(inport, ip6.dst, eth.src);
> +    encodes as push:NXM_NX_XXREG0[],push:NXM_NX_IPV6_DST[],pop:NXM_NX_XXREG0[],set_field:00:00:00:00:00:00->eth_dst,resubmit(,31),pop:NXM_NX_XXREG0[]
> +    has prereqs eth.type == 0x86dd
> +lookup_nd(inport, nd.target, nd.tll);
> +    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:00:00:00:00:00:00->eth_dst,resubmit(,31),pop:NXM_OF_ETH_SRC[],pop:NXM_NX_XXREG0[]
> +    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)
> +
> +lookup_nd;
> +    Syntax error at `;' expecting `('.
> +lookup_nd();
> +    Syntax error at `)' expecting field name.
> +lookup_nd(inport);
> +    Syntax error at `)' expecting `,'.
> +lookup_nd(inport ip6.dst);
> +    Syntax error at `ip6.dst' expecting `,'.
> +lookup_nd(inport, ip6.dst;
> +    Syntax error at `;' expecting `,'.
> +lookup_nd(inport, ip6.dst, eth.src;
> +    Syntax error at `;' expecting `)'.
> +lookup_nd(inport, eth.dst);
> +    Cannot use 48-bit field eth.dst[0..47] where 128-bit field is required.
> +lookup_nd(inport, ip4.src, ip4.dst);
> +    Cannot use 32-bit field ip4.src[0..31] where 128-bit field is required.
> +lookup_nd(inport, ip6.src, ip6.dst);
> +    Cannot use 128-bit field ip6.dst[0..127] where 48-bit field is required.
> +
>  # set_queue
>  set_queue(0);
>      encodes as set_queue:0
> @@ -14502,7 +14554,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
>  # Since the sw0-vir is not claimed by any chassis, eth.dst should be set to
>  # zero if the ip4.dst is the virtual ip in the router pipeline.
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  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;)
> +  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;)
>  ])
>
>  ip_to_hex() {
> @@ -14538,7 +14590,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
>  # There should be an arp resolve flow to resolve the virtual_ip with the
>  # sw0-p1's MAC.
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  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;)
> +  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;)
>  ])
>
>  # send the garp from sw0-p2 (in hv2). hv2 should claim sw0-vir
> @@ -14561,7 +14613,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
>  # There should be an arp resolve flow to resolve the virtual_ip with the
>  # sw0-p2's MAC.
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  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;)
> +  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;)
>  ])
>
>  # Now send arp reply from sw0-p1. hv1 should claim sw0-vir
> @@ -14582,7 +14634,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
>  > lflows.txt
>
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  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;)
> +  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;)
>  ])
>
>  # Delete hv1-vif1 port. hv1 should release sw0-vir
> @@ -14600,7 +14652,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
>  > lflows.txt
>
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  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;)
> +  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;)
>  ])
>
>  # Now send arp reply from sw0-p2. hv2 should claim sw0-vir
> @@ -14621,7 +14673,7 @@ ovn-sbctl dump-flows lr0 | grep lr_in_arp_resolve | grep "reg0 == 10.0.0.10" \
>  > lflows.txt
>
>  AT_CHECK([cat lflows.txt], [0], [dnl
> -  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;)
> +  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;)
>  ])
>
>  # Delete sw0-p2 logical port
> @@ -15811,3 +15863,225 @@ as hv4 ovs-appctl fdb/show br-phys
>  OVN_CLEANUP([hv1],[hv2],[hv3],[hv4])
>
>  AT_CLEANUP
> +
> +AT_SETUP([ovn -- ARP lookup before learning])
> +AT_KEYWORDS([virtual ports])
> +AT_SKIP_IF([test $HAVE_PYTHON = no])
> +ovn_start
> +
> +send_garp() {
> +    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
> +    local request=${eth_dst}${eth_src}08060001080006040001${eth_src}${spa}${eth_dst}${tpa}
> +    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
> +}
> +
> +send_arp_reply() {
> +    local hv=$1 inport=$2 eth_src=$3 eth_dst=$4 spa=$5 tpa=$6
> +    local request=${eth_dst}${eth_src}08060001080006040002${eth_src}${spa}${eth_dst}${tpa}
> +    as hv$hv ovs-appctl netdev-dummy/receive hv${hv}-vif$inport $request
> +}
> +
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.1
> +ovs-vsctl -- add-port br-int hv1-vif1 -- \
> +    set interface hv1-vif1 external-ids:iface-id=sw0-p1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +ovs-vsctl -- add-port br-int hv1-vif2 -- \
> +    set interface hv1-vif2 external-ids:iface-id=sw0-p3 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=2
> +
> +sim_add hv2
> +as hv2
> +ovs-vsctl add-br br-phys
> +ovn_attach n1 br-phys 192.168.0.2
> +ovs-vsctl -- add-port br-int hv2-vif1 -- \
> +    set interface hv2-vif1 external-ids:iface-id=sw1-p1 \
> +    options:tx_pcap=hv2/vif1-tx.pcap \
> +    options:rxq_pcap=hv2/vif1-rx.pcap \
> +    ofport-request=1
> +
> +ovn-nbctl ls-add sw0
> +
> +ovn-nbctl lsp-add sw0 sw0-p1
> +ovn-nbctl lsp-set-addresses sw0-p1 "50:54:00:00:00:03"
> +
> +# Create the second logical switch with one port
> +ovn-nbctl ls-add sw1
> +ovn-nbctl lsp-add sw1 sw1-p1
> +ovn-nbctl lsp-set-addresses sw1-p1 "40:54:00:00:00:03 20.0.0.3"
> +ovn-nbctl lsp-set-port-security sw1-p1 "40:54:00:00:00:03 20.0.0.3"
> +
> +# Create a logical router and attach both logical switches
> +ovn-nbctl lr-add lr0
> +ovn-nbctl lrp-add lr0 lr0-sw0 00:00:00:00:ff:01 10.0.0.1/24
> +ovn-nbctl lsp-add sw0 sw0-lr0
> +ovn-nbctl lsp-set-type sw0-lr0 router
> +ovn-nbctl lsp-set-addresses sw0-lr0 00:00:00:00:ff:01
> +ovn-nbctl lsp-set-options sw0-lr0 router-port=lr0-sw0
> +
> +ovn-nbctl lrp-add lr0 lr0-sw1 00:00:00:00:ff:02 20.0.0.1/24
> +ovn-nbctl lsp-add sw1 sw1-lr0
> +ovn-nbctl lsp-set-type sw1-lr0 router
> +ovn-nbctl lsp-set-addresses sw1-lr0 00:00:00:00:ff:02
> +ovn-nbctl lsp-set-options sw1-lr0 router-port=lr0-sw1
> +
> +OVN_POPULATE_ARP
> +ovn-nbctl --wait=hv sync
> +
> +as hv1 ovs-appctl -t ovn-controller vlog/set dbg
> +
> +ip_to_hex() {
> +    printf "%02x%02x%02x%02x" "$@"
> +}
> +
> +# From sw0-p1 send GARP for 10.0.0.30.
> +# ovn-controller should learn the
> +#   mac_binding entry
> +#     port - lr0-sw0
> +#     ip - 10.0.0.30
> +#     mac - 50:54:00:00:00:03
> +
> +AT_CHECK([test 0 = `ovn-sbctl list mac_binding | wc -l`])
> +eth_src=505400000003
> +eth_dst=ffffffffffff
> +spa=$(ip_to_hex 10 0 0 30)
> +tpa=$(ip_to_hex 10 0 0 30)
> +send_garp 1 1 $eth_src $eth_dst $spa $tpa
> +
> +OVS_WAIT_UNTIL([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`])
> +
> +AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
> +list mac_binding], [0], [lr0-sw0
> +10.0.0.30
> +50:54:00:00:00:03
> +])
> +
> +AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
> +grep controller | grep -v n_packets=0 | wc -l`])
> +
> +# Wait for an entry in table=31
> +OVS_WAIT_UNTIL(
> +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=31 | grep n_packets=0 \
> +| wc -l`]
> +)
> +
> +# Send garp again. This time the packet should not be sent to ovn-controller.
> +send_garp 1 1 $eth_src $eth_dst $spa $tpa
> +# Wait for an entry in table=31
> +OVS_WAIT_UNTIL([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=31 | grep n_packets=1 | wc -l`])
> +
> +# The packet should not be sent to ovn-controller. The packet
> +count should be 1 only.
> +AT_CHECK([test 1 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +AT_CHECK([test 1 = `as hv1 ovs-ofctl dump-flows br-int table=10 | grep arp | \
> +grep controller | grep -v n_packets=0 | wc -l`])
> +
> +# Now send garp packet with different mac.
> +eth_src=505400000013
> +eth_dst=ffffffffffff
> +spa=$(ip_to_hex 10 0 0 30)
> +tpa=$(ip_to_hex 10 0 0 30)
> +send_garp 1 1 $eth_src $eth_dst $spa $tpa
> +
> +# The garp packet should be sent to ovn-controller and the mac_binding entry
> +# should be updated.
> +OVS_WAIT_UNTIL([test 2 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +
> +AT_CHECK([test 1 = `ovn-sbctl --bare --columns _uuid list mac_binding | wc -l`])
> +
> +AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
> +list mac_binding], [0], [lr0-sw0
> +10.0.0.30
> +50:54:00:00:00:13
> +])
> +
> +# Send ARP request to lrp - lr0-sw1 (20.0.0.1) using src mac 50:54:00:00:00:33
> +# and src ip - 10.0.0.50.from sw0-p1.
> +# ovn-controller should add the mac_binding entry
> +#   logical_port - lr0
> +#   IP           - 10.0.0.50
> +#   MAC          - 50:54:00:00:00:33
> +eth_src=505400000033
> +eth_dst=ffffffffffff
> +spa=$(ip_to_hex 10 0 0 50)
> +tpa=$(ip_to_hex 20 0 0 1)
> +
> +send_garp 1 1 $eth_src $eth_dst $spa $tpa
> +
> +# The garp packet should be sent to ovn-controller and the mac_binding entry
> +# should be updated.
> +OVS_WAIT_UNTIL([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +
> +OVS_WAIT_UNTIL(
> +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=31 | grep dl_src=50:54:00:00:00:33 \
> +| wc -l`]
> +)
> +
> +AT_CHECK([ovn-sbctl --format=csv --bare --columns logical_port,ip,mac \
> +find mac_binding ip=10.0.0.50], [0], [lr0-sw0
> +10.0.0.50
> +50:54:00:00:00:33
> +])
> +
> +# Send the same packet again.
> +send_garp 1 1 $eth_src $eth_dst $spa $tpa
> +
> +OVS_WAIT_UNTIL(
> +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=31 | grep dl_src=50:54:00:00:00:33 \
> +| grep n_packets=1 | wc -l`]
> +)
> +
> +AT_CHECK([test 3 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +
> +# Now send ARP reply packet with IP - 10.0.0.40 and mac 505400000023
> +eth_src=505400000023
> +eth_dst=ffffffffffff
> +spa=$(ip_to_hex 10 0 0 40)
> +tpa=$(ip_to_hex 10 0 0 50)
> +send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
> +
> +# ovn-controller should add the
> +#   mac_binding entry
> +#     port - lr0-sw0
> +#     ip - 10.0.0.40
> +#     mac - 50:54:00:00:00:23
> +
> +# The garp packet should be sent to ovn-controller and the mac_binding entry
> +# should be updated.
> +OVS_WAIT_UNTIL([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +
> +# Wait for an entry in table=31 for the learnt mac_binding entry.
> +
> +OVS_WAIT_UNTIL(
> +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=31 | grep dl_src=50:54:00:00:00:23 \
> +| wc -l`]
> +)
> +
> +# Send the same garp reply. This time it should not be sent to ovn-controller.
> +send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
> +OVS_WAIT_UNTIL(
> +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=31 | grep dl_src=50:54:00:00:00:23 \
> +| grep n_packets=1 | wc -l`]
> +)
> +
> +AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +
> +send_arp_reply 1 1 $eth_src $eth_dst $spa $tpa
> +OVS_WAIT_UNTIL(
> +    [test 1 = `as hv1 ovs-ofctl dump-flows br-int table=31 | grep dl_src=50:54:00:00:00:23 \
> +| grep n_packets=2 | wc -l`]
> +)
> +
> +AT_CHECK([test 4 = `cat hv1/ovn-controller.log | grep NXT_PACKET_IN2 | wc -l`])
> +
> +OVN_CLEANUP([hv1], [hv2])
> +AT_CLEANUP
> diff --git a/tests/test-ovn.c b/tests/test-ovn.c
> index 8462c21b6..e96321bd6 100644
> --- a/tests/test-ovn.c
> +++ b/tests/test-ovn.c
> @@ -1297,6 +1297,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx OVS_UNUSED)
>                  .egress_ptable = 40,
>                  .output_ptable = 64,
>                  .mac_bind_ptable = 65,
> +                .mac_lookup_ptable = 31,
>              };
>              struct ofpbuf ofpacts;
>              ofpbuf_init(&ofpacts, 0);
> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
> index 0583610b9..2bd0f906b 100644
> --- a/utilities/ovn-trace.c
> +++ b/utilities/ovn-trace.c
> @@ -556,6 +556,22 @@ ovntrace_mac_binding_find(const struct ovntrace_datapath *dp,
>      return NULL;
>  }
>
> +static const struct ovntrace_mac_binding *
> +ovntrace_mac_binding_find_mac_ip(const struct ovntrace_datapath *dp,
> +                                 uint16_t port_key, const struct in6_addr *ip,
> +                                 struct eth_addr mac)
> +{
> +    const struct ovntrace_mac_binding *bind;
> +    HMAP_FOR_EACH_WITH_HASH (bind, node, hash_mac_binding(port_key, ip),
> +                             &dp->mac_bindings) {
> +        if (bind->port_key == port_key && ipv6_addr_equals(ip, &bind->ip)
> +            && eth_addr_equals(bind->mac, mac)) {
> +            return bind;
> +        }
> +    }
> +    return NULL;
> +}
> +
>  /* If 's' ends with a UUID, returns a copy of it with the UUID truncated to
>   * just the first 6 characters; otherwise, returns a copy of 's'. */
>  static char *
> @@ -1704,6 +1720,49 @@ execute_get_mac_bind(const struct ovnact_get_mac_bind *bind,
>                           ETH_ADDR_ARGS(uflow->dl_dst));
>  }
>
> +static void
> +execute_lookup_mac(const struct ovnact_lookup_mac_bind *bind OVS_UNUSED,
> +                   const struct ovntrace_datapath *dp OVS_UNUSED,
> +                   struct flow *uflow OVS_UNUSED,
> +                   struct ovs_list *super OVS_UNUSED)
> +{
> +    /* Get logical port number.*/
> +    struct mf_subfield port_sf = expr_resolve_field(&bind->port);
> +    ovs_assert(port_sf.n_bits == 32);
> +    uint32_t port_key = mf_get_subfield(&port_sf, uflow);
> +
> +    /* Get IP address. */
> +    struct mf_subfield ip_sf = expr_resolve_field(&bind->ip);
> +    ovs_assert(ip_sf.n_bits == 32 || ip_sf.n_bits == 128);
> +    union mf_subvalue ip_sv;
> +    mf_read_subfield(&ip_sf, uflow, &ip_sv);
> +    struct in6_addr ip = (ip_sf.n_bits == 32
> +                          ? in6_addr_mapped_ipv4(ip_sv.ipv4)
> +                          : ip_sv.ipv6);
> +
> +    /* Get MAC. */
> +    struct mf_subfield mac_sf = expr_resolve_field(&bind->mac);
> +    ovs_assert(mac_sf.n_bits == 48);
> +    union mf_subvalue mac_sv;
> +    mf_read_subfield(&mac_sf, uflow, &mac_sv);
> +
> +    const struct ovntrace_mac_binding *binding
> +        = ovntrace_mac_binding_find_mac_ip(dp, port_key, &ip, mac_sv.mac);
> +
> +    uflow->dl_dst = binding ? binding->mac : eth_addr_zero;
> +    if (binding) {
> +        ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
> +                             "/* MAC binding to "ETH_ADDR_FMT" found. */",
> +                             ETH_ADDR_ARGS(uflow->dl_dst));
> +    } else {
> +        ovntrace_node_append(super, OVNTRACE_NODE_ACTION,
> +                             "/* lookup failed - No MAC binding. */");
> +    }
> +    ovntrace_node_append(super, OVNTRACE_NODE_MODIFY,
> +                         "eth.dst = "ETH_ADDR_FMT,
> +                         ETH_ADDR_ARGS(uflow->dl_dst));
> +}
> +
>  static void
>  execute_put_opts(const struct ovnact_put_opts *po,
>                   const char *name, struct flow *uflow,
> @@ -2072,6 +2131,14 @@ trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
>              /* Nothing to do for tracing. */
>              break;
>
> +        case OVNACT_LOOKUP_ARP:
> +            execute_lookup_mac(ovnact_get_LOOKUP_ARP(a), dp, uflow, super);
> +            break;
> +
> +        case OVNACT_LOOKUP_ND:
> +            execute_lookup_mac(ovnact_get_LOOKUP_ND(a), dp, uflow, super);
> +            break;
> +
>          case OVNACT_PUT_DHCPV4_OPTS:
>              execute_put_dhcp_opts(ovnact_get_PUT_DHCPV4_OPTS(a),
>                                    "put_dhcp_opts", uflow, super);
> --
> 2.21.0
>


More information about the dev mailing list