On Thu, Jan 17, 2019 at 10:57 PM Han Zhou <zhou...@gmail.com> wrote:

> On Tue, Jan 15, 2019 at 10:24 AM Numan Siddique <nusid...@redhat.com>
> wrote:
> >
> >
> >
> > On Tue, Jan 15, 2019 at 5:42 AM Han Zhou <zhou...@gmail.com> wrote:
> >>
> >> Hi Numan,
> >>
> >> The feature looks very good overall. I have some more comments inlined.
> >
> >
> > Thank Han for the review and the comments.
> >
> > You are right. The logical port's mac flaps too. I had to read the
> commit message again to
> > recall :)
> >
> >
> >>
> >> On Thu, Jan 10, 2019 at 12:37 PM <nusid...@redhat.com> wrote:
> >> >
> >> > From: Numan Siddique <nusid...@redhat.com>
> >> >
> >> > In the case of OpenStack + OVN, when the VMs are booted on
> >> > hypervisors supporting SR-IOV nics, there are no OVS ports
> >> > for these VMs. When these VMs sends DHCPv4, DHPCv6 or IPv6
> >> > Router Solicitation requests, the local ovn-controller
> >> > cannot reply to these packets. OpenStack Neutron dhcp agent
> >> > service needs to be run to serve these requests.
> >> >
> >> > With the new logical port type - 'external', OVN itself can
> >> > handle these requests avoiding the need to deploy any
> >> > external services like neutron dhcp agent.
> >> >
> >> > To make use of this feature, CMS has to
> >> >  - create a logical port for such VMs
> >> >  - set the type to 'external'
> >> >  - set requested-chassis="<chassis-name>" in the options
> >> >    column.
> >> >  - create a localnet port for the logical switch
> >> >  - configure the ovn-bridge-mappings option in the OVS db.
> >> >
> >> > When the ovn-controller running in that 'chassis', detects
> >> > the Port_Binding row, it adds the necessary DHCPv4/v6 OF
> >> > flows. Since the packet enters the logical switch pipeline
> >> > via the localnet port, the inport register (reg14) is set
> >> > to the tunnel key of localnet port in the match conditions.
> >> >
> >> > In case the chassis goes down for some reason, it is the
> >> > responsibility of CMS to change the 'requested-chassis'
> >> > option to some other active chassis, so that it can serve
> >> > these requests.
> >> >
> >> > When the VM with the external port, sends an ARP request for
> >> > the router ips, only the chassis which has claimed the port,
> >> > will reply to the ARP requests. Rest of the chassis on
> >> > receiving these packets drop them in the ingress switch
> >> > datapath stage - S_SWITCH_IN_EXTERNAL_PORT which is just
> >> > before S_SWITCH_IN_L2_LKUP.
> >> >
> >> > This would guarantee that only the chassis which has claimed
> >> > the external ports will run the router datapath pipeline.
> >> >
> >> > Signed-off-by: Numan Siddique <nusid...@redhat.com>
> >> > ---
> >> >
> >> > v3 -> v4
> >> > ------
> >> >   * Updated the documention as per Han Zhou's suggestion.
> >> >
> >> > v2 -> v3
> >> > -------
> >> >   * Rebased
> >> >
> >> >  ovn/controller/binding.c        |  15 +-
> >> >  ovn/controller/lflow.c          |  41 ++-
> >> >  ovn/controller/lflow.h          |   2 +
> >> >  ovn/controller/lport.c          |  26 ++
> >> >  ovn/controller/lport.h          |   5 +
> >> >  ovn/controller/ovn-controller.c |   6 +
> >> >  ovn/lib/ovn-util.c              |   1 +
> >> >  ovn/northd/ovn-northd.8.xml     |  52 +++-
> >> >  ovn/northd/ovn-northd.c         | 123 ++++++--
> >> >  ovn/ovn-architecture.7.xml      |  76 +++++
> >> >  ovn/ovn-nb.xml                  |  44 +++
> >> >  tests/ovn.at                    | 530
> +++++++++++++++++++++++++++++++-
> >> >  12 files changed, 889 insertions(+), 32 deletions(-)
> >> >
> >> > diff --git a/ovn/controller/binding.c b/ovn/controller/binding.c
> >> > index 021ecddcf..ee396c93d 100644
> >> > --- a/ovn/controller/binding.c
> >> > +++ b/ovn/controller/binding.c
> >> > @@ -471,13 +471,26 @@ consider_local_datapath(struct ovsdb_idl_txn
> *ovnsb_idl_txn,
> >> >           * for them. */
> >> >          sset_add(local_lports, binding_rec->logical_port);
> >> >          our_chassis = false;
> >> > +    } else if (!strcmp(binding_rec->type, "external")) {
> >> > +        const char *chassis_id = smap_get(&binding_rec->options,
> >> > +                                          "requested-chassis");
> >> > +        our_chassis = chassis_id && (
> >> > +            !strcmp(chassis_id, chassis_rec->name) ||
> >> > +            !strcmp(chassis_id, chassis_rec->hostname));
> >> > +        if (our_chassis) {
> >> > +            add_local_datapath(sbrec_datapath_binding_by_key,
> >> > +                               sbrec_port_binding_by_datapath,
> >> > +                               sbrec_port_binding_by_name,
> >> > +                               binding_rec->datapath, true,
> local_datapaths);
> >> > +        }
> >> >      }
> >> >
> >> >      if (our_chassis
> >> >          || !strcmp(binding_rec->type, "patch")
> >> >          || !strcmp(binding_rec->type, "localport")
> >> >          || !strcmp(binding_rec->type, "vtep")
> >> > -        || !strcmp(binding_rec->type, "localnet")) {
> >> > +        || !strcmp(binding_rec->type, "localnet")
> >> > +        || !strcmp(binding_rec->type, "external")) {
> >>
> >> Why calling update_local_lport_ids() when the type is external? I
> >> think "our_chassis" is enough as the condition for "external" port to
> >> be considered.
> >
> >
> > Agree. I will address in v5.
> >
> >
> >>
> >>
> >> >          update_local_lport_ids(local_lport_ids, binding_rec);
> >> >      }
> >> >
> >> > diff --git a/ovn/controller/lflow.c b/ovn/controller/lflow.c
> >> > index 8db81927e..98e8ed3b9 100644
> >> > --- a/ovn/controller/lflow.c
> >> > +++ b/ovn/controller/lflow.c
> >> > @@ -52,7 +52,10 @@ lflow_init(void)
> >> >  struct lookup_port_aux {
> >> >      struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath;
> >> >      struct ovsdb_idl_index *sbrec_port_binding_by_name;
> >> > +    struct ovsdb_idl_index *sbrec_port_binding_by_type;
> >> > +    struct ovsdb_idl_index *sbrec_datapath_binding_by_key;
> >> >      const struct sbrec_datapath_binding *dp;
> >> > +    const struct sbrec_chassis *chassis;
> >> >  };
> >> >
> >> >  struct condition_aux {
> >> > @@ -66,6 +69,8 @@ static void consider_logical_flow(
> >> >      struct ovsdb_idl_index *sbrec_chassis_by_name,
> >> >      struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
> >> >      struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >> > +    struct ovsdb_idl_index *sbrec_port_binding_by_type,
> >> > +    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> >> >      const struct sbrec_logical_flow *,
> >> >      const struct hmap *local_datapaths,
> >> >      const struct sbrec_chassis *,
> >> > @@ -89,8 +94,24 @@ lookup_port_cb(const void *aux_, const char
> *port_name, unsigned int *portp)
> >> >      const struct sbrec_port_binding *pb
> >> >          = lport_lookup_by_name(aux->sbrec_port_binding_by_name,
> port_name);
> >> >      if (pb && pb->datapath == aux->dp) {
> >> > -        *portp = pb->tunnel_key;
> >> > -        return true;
> >> > +        if (strcmp(pb->type, "external")) {
> >> > +            *portp = pb->tunnel_key;
> >> > +            return true;
> >> > +        }
> >> > +        const char *chassis_id = smap_get(&pb->options,
> >> > +                                          "requested-chassis");
> >> > +        if (chassis_id && (!strcmp(chassis_id, aux->chassis->name) ||
> >> > +                           !strcmp(chassis_id,
> aux->chassis->hostname))) {
> >> > +            const struct sbrec_port_binding *localnet_pb
> >> > +                =
> lport_lookup_by_type(aux->sbrec_datapath_binding_by_key,
> >> > +
>  aux->sbrec_port_binding_by_type,
> >> > +                                       aux->dp->tunnel_key,
> "localnet");
> >> > +            if (localnet_pb) {
> >> > +                *portp = localnet_pb->tunnel_key;
> >> > +                return true;
> >> > +            }
> >> > +        }
> >> > +        return false;
> >> >      }
> >> >
> >> >      const struct sbrec_multicast_group *mg =
> mcgroup_lookup_by_dp_name(
> >> > @@ -144,6 +165,8 @@ add_logical_flows(
> >> >      struct ovsdb_idl_index *sbrec_chassis_by_name,
> >> >      struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
> >> >      struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >> > +    struct ovsdb_idl_index *sbrec_port_binding_by_type,
> >> > +    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> >> >      const struct sbrec_dhcp_options_table *dhcp_options_table,
> >> >      const struct sbrec_dhcpv6_options_table *dhcpv6_options_table,
> >> >      const struct sbrec_logical_flow_table *logical_flow_table,
> >> > @@ -183,6 +206,8 @@ add_logical_flows(
> >> >          consider_logical_flow(sbrec_chassis_by_name,
> >> >                                sbrec_multicast_group_by_name_datapath,
> >> >                                sbrec_port_binding_by_name,
> >> > +                              sbrec_port_binding_by_type,
> >> > +                              sbrec_datapath_binding_by_key,
> >> >                                lflow, local_datapaths,
> >> >                                chassis, &dhcp_opts, &dhcpv6_opts,
> &nd_ra_opts,
> >> >                                addr_sets, port_groups, active_tunnels,
> >> > @@ -200,6 +225,8 @@ consider_logical_flow(
> >> >      struct ovsdb_idl_index *sbrec_chassis_by_name,
> >> >      struct ovsdb_idl_index *sbrec_multicast_group_by_name_datapath,
> >> >      struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >> > +    struct ovsdb_idl_index *sbrec_port_binding_by_type,
> >> > +    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> >> >      const struct sbrec_logical_flow *lflow,
> >> >      const struct hmap *local_datapaths,
> >> >      const struct sbrec_chassis *chassis,
> >> > @@ -292,7 +319,10 @@ consider_logical_flow(
> >> >          .sbrec_multicast_group_by_name_datapath
> >> >              = sbrec_multicast_group_by_name_datapath,
> >> >          .sbrec_port_binding_by_name = sbrec_port_binding_by_name,
> >> > -        .dp = lflow->logical_datapath
> >> > +        .sbrec_port_binding_by_type = sbrec_port_binding_by_type,
> >> > +        .sbrec_datapath_binding_by_key =
> sbrec_datapath_binding_by_key,
> >> > +        .dp = lflow->logical_datapath,
> >> > +        .chassis = chassis
> >> >      };
> >> >      struct condition_aux cond_aux = {
> >> >          .sbrec_chassis_by_name = sbrec_chassis_by_name,
> >> > @@ -463,6 +493,8 @@ void
> >> >  lflow_run(struct ovsdb_idl_index *sbrec_chassis_by_name,
> >> >            struct ovsdb_idl_index
> *sbrec_multicast_group_by_name_datapath,
> >> >            struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >> > +          struct ovsdb_idl_index *sbrec_port_binding_by_type,
> >> > +          struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> >> >            const struct sbrec_dhcp_options_table *dhcp_options_table,
> >> >            const struct sbrec_dhcpv6_options_table
> *dhcpv6_options_table,
> >> >            const struct sbrec_logical_flow_table *logical_flow_table,
> >> > @@ -481,7 +513,8 @@ lflow_run(struct ovsdb_idl_index
> *sbrec_chassis_by_name,
> >> >
> >> >      add_logical_flows(sbrec_chassis_by_name,
> >> >                        sbrec_multicast_group_by_name_datapath,
> >> > -                      sbrec_port_binding_by_name, dhcp_options_table,
> >> > +                      sbrec_port_binding_by_name,
> sbrec_port_binding_by_type,
> >> > +                      sbrec_datapath_binding_by_key,
> dhcp_options_table,
> >> >                        dhcpv6_options_table, logical_flow_table,
> >> >                        local_datapaths, chassis, addr_sets,
> port_groups,
> >> >                        active_tunnels, local_lport_ids, flow_table,
> group_table,
> >> > diff --git a/ovn/controller/lflow.h b/ovn/controller/lflow.h
> >> > index d19338140..b2911e0eb 100644
> >> > --- a/ovn/controller/lflow.h
> >> > +++ b/ovn/controller/lflow.h
> >> > @@ -68,6 +68,8 @@ void lflow_init(void);
> >> >  void lflow_run(struct ovsdb_idl_index *sbrec_chassis_by_name,
> >> >                 struct ovsdb_idl_index
> *sbrec_multicast_group_by_name_datapath,
> >> >                 struct ovsdb_idl_index *sbrec_port_binding_by_name,
> >> > +               struct ovsdb_idl_index *sbrec_port_binding_by_type,
> >> > +               struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> >> >                 const struct sbrec_dhcp_options_table *,
> >> >                 const struct sbrec_dhcpv6_options_table *,
> >> >                 const struct sbrec_logical_flow_table *,
> >> > diff --git a/ovn/controller/lport.c b/ovn/controller/lport.c
> >> > index cc5c5fbb2..9c827d9b0 100644
> >> > --- a/ovn/controller/lport.c
> >> > +++ b/ovn/controller/lport.c
> >> > @@ -64,6 +64,32 @@ lport_lookup_by_key(struct ovsdb_idl_index
> *sbrec_datapath_binding_by_key,
> >> >      return retval;
> >> >  }
> >> >
> >> > +const struct sbrec_port_binding *
> >> > +lport_lookup_by_type(struct ovsdb_idl_index
> *sbrec_datapath_binding_by_key,
> >> > +                     struct ovsdb_idl_index
> *sbrec_port_binding_by_type,
> >> > +                     uint64_t dp_key, const char *port_type)
> >> > +{
> >> > +    /* Lookup datapath corresponding to dp_key. */
> >> > +    const struct sbrec_datapath_binding *db = datapath_lookup_by_key(
> >> > +        sbrec_datapath_binding_by_key, dp_key);
> >> > +    if (!db) {
> >> > +        return NULL;
> >> > +    }
> >> > +
> >> > +    /* Build key for an indexed lookup. */
> >> > +    struct sbrec_port_binding *pb =
> sbrec_port_binding_index_init_row(
> >> > +            sbrec_port_binding_by_type);
> >> > +    sbrec_port_binding_index_set_datapath(pb, db);
> >> > +    sbrec_port_binding_index_set_type(pb, port_type);
> >>
> >> The index sbrec_port_binding_by_type was defined with only one column
> >> (type), but here it is set with 2 columns. Would it get wrong result?
> >
> >
> > Not sure I completely get your point. If you see the function
> 'lport_lookup_by_key' here
> > -
> https://github.com/openvswitch/ovs/blob/master/ovn/controller/lport.c#L42
> > it uses the index - sbrec_port_binding_by_key the same way.
> > The usage seems fine to me.
> >
>
> In the example, sbrec_port_binding_by_key is created as an index with
> two columns:
> https://github.com/openvswitch/ovs/blob/master/ovn/controller/ovn-controller.c#L612
> .
> So it is appropriate. But in this case, sbrec_port_binding_by_type was
> created (in ovn-controller.c) with only 1 column. So I think we should
> create it with 2 columns, both datapath_binding and port_type.
>
>
Thanks. It's now clear to me. I will address it in v6. I will wait for your
comments in v5 before
sending v6.

Thanks
Numan


> >
> >>
> >> > +
> >> > +    const struct sbrec_port_binding *retval =
> sbrec_port_binding_index_find(
> >> > +            sbrec_port_binding_by_type, pb);
> >> > +
> >> > +    sbrec_port_binding_index_destroy_row(pb);
> >> > +
> >> > +    return retval;
> >> > +}
> >> > +
> >> >  const struct sbrec_datapath_binding *
> >> >  datapath_lookup_by_key(struct ovsdb_idl_index
> *sbrec_datapath_binding_by_key,
> >> >                         uint64_t dp_key)
> >> > diff --git a/ovn/controller/lport.h b/ovn/controller/lport.h
> >> > index 7dcd5bee0..2d49792f6 100644
> >> > --- a/ovn/controller/lport.h
> >> > +++ b/ovn/controller/lport.h
> >> > @@ -42,6 +42,11 @@ const struct sbrec_port_binding
> *lport_lookup_by_key(
> >> >      struct ovsdb_idl_index *sbrec_port_binding_by_key,
> >> >      uint64_t dp_key, uint64_t port_key);
> >> >
> >> > +const struct sbrec_port_binding *lport_lookup_by_type(
> >> > +    struct ovsdb_idl_index *sbrec_datapath_binding_by_key,
> >> > +    struct ovsdb_idl_index *sbrec_port_binding_by_type,
> >> > +    uint64_t dp_key, const char *port_type);
> >> > +
> >> >  const struct sbrec_datapath_binding *datapath_lookup_by_key(
> >> >      struct ovsdb_idl_index *sbrec_datapath_binding_by_key, uint64_t
> dp_key);
> >> >
> >> > diff --git a/ovn/controller/ovn-controller.c
> b/ovn/controller/ovn-controller.c
> >> > index 4e9a5865f..5aab9142f 100644
> >> > --- a/ovn/controller/ovn-controller.c
> >> > +++ b/ovn/controller/ovn-controller.c
> >> > @@ -145,6 +145,7 @@ update_sb_monitors(struct ovsdb_idl *ovnsb_idl,
> >> >       * ports that have a Gateway_Chassis that point's to our own
> >> >       * chassis */
> >> >      sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ,
> "chassisredirect");
> >> > +    sbrec_port_binding_add_clause_type(&pb, OVSDB_F_EQ, "external");
> >> >      if (chassis) {
> >> >          /* This should be mostly redundant with the other clauses
> for port
> >> >           * bindings, but it allows us to catch any ports that are
> assigned to
> >> > @@ -616,6 +617,9 @@ main(int argc, char *argv[])
> >> >      struct ovsdb_idl_index *sbrec_port_binding_by_datapath
> >> >          = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
> >> >                                    &sbrec_port_binding_col_datapath);
> >> > +    struct ovsdb_idl_index *sbrec_port_binding_by_type
> >> > +        = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
> >> > +                                  &sbrec_port_binding_col_type);
> >> >      struct ovsdb_idl_index *sbrec_datapath_binding_by_key
> >> >          = ovsdb_idl_index_create1(ovnsb_idl_loop.idl,
> >> >
> &sbrec_datapath_binding_col_tunnel_key);
> >> > @@ -743,6 +747,8 @@ main(int argc, char *argv[])
> >> >                              sbrec_chassis_by_name,
> >> >                              sbrec_multicast_group_by_name_datapath,
> >> >                              sbrec_port_binding_by_name,
> >> > +                            sbrec_port_binding_by_type,
> >> > +                            sbrec_datapath_binding_by_key,
> >> >
> sbrec_dhcp_options_table_get(ovnsb_idl_loop.idl),
> >> >
> sbrec_dhcpv6_options_table_get(ovnsb_idl_loop.idl),
> >> >
> sbrec_logical_flow_table_get(ovnsb_idl_loop.idl),
> >> > diff --git a/ovn/lib/ovn-util.c b/ovn/lib/ovn-util.c
> >> > index e9464e926..0e4439c5d 100644
> >> > --- a/ovn/lib/ovn-util.c
> >> > +++ b/ovn/lib/ovn-util.c
> >> > @@ -311,6 +311,7 @@ static const char *OVN_NB_LSP_TYPES[] = {
> >> >      "localport",
> >> >      "router",
> >> >      "vtep",
> >> > +    "external",
> >> >  };
> >> >
> >> >  bool
> >> > diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
> >> > index f52699bd3..c133969ed 100644
> >> > --- a/ovn/northd/ovn-northd.8.xml
> >> > +++ b/ovn/northd/ovn-northd.8.xml
> >> > @@ -113,6 +113,26 @@
> >> >          logical ports on which port security is not enabled, these
> advance all
> >> >          packets that match the <code>inport</code>.
> >> >        </li>
> >> > +
> >> > +      <li>
> >> > +        <p>
> >> > +          For each logical port of type <code>external</code> with
> port
> >> > +          security enabled and the logical switch to which it
> belongs, has a
> >> > +          localnet port, a priority 90 flow is added which matches
> on the
> >> > +          <code>inport</code> of localnet port and the valid
> >> > +          <code>eth.src</code> address(es) of the
> <code>external</code>
> >> > +          logical port and sets the
> <code>REGBIT_EXT_PORT_NOT_RESIDENT</code>
> >> > +          flag if the logical port doesn't reside on a chassis and
> advances the
> >> > +          packet to the next flow table.
> >> > +        </p>
> >> > +
> >> > +        <p>
> >> > +          On the chassis where the <code>external</code> port
> resides doesn't
> >> > +          add the above flow. The priority 50 flow with the match on
> the
> >> > +          <code>inport</code> of the localnet port takes care of
> forwarding
> >> > +          the packet to the next flow table.
> >> > +        </p>
> >> > +      </li>
> >> >      </ul>
> >> >
> >> >      <p>
> >> > @@ -626,7 +646,8 @@ nd_na_router {
> >> >      <p>
> >> >        This table adds the DHCPv4 options to a DHCPv4 packet from the
> >> >        logical ports configured with IPv4 address(es) and DHCPv4
> options,
> >> > -      and similarly for DHCPv6 options.
> >> > +      and similarly for DHCPv6 options. This table also adds flows
> for the
> >> > +      logical ports of type <code>external</code>.
> >> >      </p>
> >> >
> >> >      <ul>
> >> > @@ -827,7 +848,34 @@ output;
> >> >        </li>
> >> >      </ul>
> >> >
> >> > -    <h3>Ingress Table 16 Destination Lookup</h3>
> >> > +    <h3>Ingress table 16 External ports</h3>
> >> > +
> >> > +    <p>
> >> > +      Traffic from the <code>external</code> logical ports enter the
> ingress
> >> > +      datapath pipeline via the <code>localnet</code> port. This
> table adds the
> >> > +      below logical flows to handle the traffic from these ports.
> >> > +    </p>
> >> > +
> >> > +    <ul>
> >> > +      <li>
> >> > +        <p>
> >> > +          For each router port IP address <code>A</code> which
> belongs to the
> >> > +          logical switch, A priority-100 flow is added which matches
> >> > +          <code>REGBIT_EXT_PORT_NOT_RESIDENT &amp;&amp; arp.tpa ==
> <var>A</var>
> >> > +          &amp;&amp; arp.op == 1</code> (ARP request to the router
> >> > +          IP) with the action to <code>drop</code> the packet.
> >> > +        </p>
> >> > +
> >> > +        <p>
> >> > +          These flows guarantees that the ARP/NS request to the
> router IP
> >> > +          address from the external ports is responded by only the
> chassis
> >> > +          which has claimed these external ports. All the other
> chassis,
> >> > +          drops these packets.
> >> > +        </p>
> >> > +      </li>
> >> > +    </ul>
> >> > +
> >> > +    <h3>Ingress Table 17 Destination Lookup</h3>
> >> >
> >> >      <p>
> >> >        This table implements switching behavior.  It contains these
> logical
> >> > diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
> >> > index 2de9fb38d..24203b50d 100644
> >> > --- a/ovn/northd/ovn-northd.c
> >> > +++ b/ovn/northd/ovn-northd.c
> >> > @@ -119,7 +119,8 @@ enum ovn_stage {
> >> >      PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 13,
> "ls_in_dhcp_response") \
> >> >      PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    14,
> "ls_in_dns_lookup")    \
> >> >      PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  15,
> "ls_in_dns_response")  \
> >> > -    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       16, "ls_in_l2_lkup")
>      \
> >> > +    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 16,
> "ls_in_external_port") \
> >> > +    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       17, "ls_in_l2_lkup")
>      \
> >> >
>       \
> >> >      /* Logical switch egress stages. */
>      \
> >> >      PIPELINE_STAGE(SWITCH, OUT, PRE_LB,       0, "ls_out_pre_lb")
>      \
> >> > @@ -166,12 +167,13 @@ enum ovn_stage {
> >> >  #define OVN_ACL_PRI_OFFSET 1000
> >> >
> >> >  /* Register definitions specific to switches. */
> >> > -#define REGBIT_CONNTRACK_DEFRAG  "reg0[0]"
> >> > -#define REGBIT_CONNTRACK_COMMIT  "reg0[1]"
> >> > -#define REGBIT_CONNTRACK_NAT     "reg0[2]"
> >> > -#define REGBIT_DHCP_OPTS_RESULT  "reg0[3]"
> >> > -#define REGBIT_DNS_LOOKUP_RESULT "reg0[4]"
> >> > -#define REGBIT_ND_RA_OPTS_RESULT "reg0[5]"
> >> > +#define REGBIT_CONNTRACK_DEFRAG         "reg0[0]"
> >> > +#define REGBIT_CONNTRACK_COMMIT         "reg0[1]"
> >> > +#define REGBIT_CONNTRACK_NAT            "reg0[2]"
> >> > +#define REGBIT_DHCP_OPTS_RESULT         "reg0[3]"
> >> > +#define REGBIT_DNS_LOOKUP_RESULT        "reg0[4]"
> >> > +#define REGBIT_ND_RA_OPTS_RESULT        "reg0[5]"
> >> > +#define REGBIT_EXT_PORT_NOT_RESIDENT    "reg0[6]"
> >> >
> >> >  /* Register definitions for switches and routers. */
> >> >  #define REGBIT_NAT_REDIRECT     "reg9[0]"
> >> > @@ -452,6 +454,8 @@ struct ovn_datapath {
> >> >
> >> >      /* Port groups related to the datapath, used only when nbs is
> NOT NULL. */
> >> >      struct hmap nb_pgs;
> >> > +
> >> > +    bool has_external_ports;
> >> >  };
> >> >
> >> >  struct macam_node {
> >> > @@ -1610,6 +1614,10 @@ join_logical_ports(struct northd_context *ctx,
> >> >                     od->localnet_port = op;
> >> >                  }
> >> >
> >> > +                if (!strcmp(nbsp->type, "external")) {
> >> > +                   od->has_external_ports = true;
> >> > +                }
> >> > +
> >> >                  op->lsp_addrs
> >> >                      = xmalloc(sizeof *op->lsp_addrs *
> nbsp->n_addresses);
> >> >                  for (size_t j = 0; j < nbsp->n_addresses; j++) {
> >> > @@ -2905,6 +2913,12 @@ lsp_is_up(const struct
> nbrec_logical_switch_port *lsp)
> >> >      return !lsp->up || *lsp->up;
> >> >  }
> >> >
> >> > +static bool
> >> > +lsp_is_external(const struct nbrec_logical_switch_port *nbsp)
> >> > +{
> >> > +    return !strcmp(nbsp->type, "external");
> >> > +}
> >> > +
> >> >  static bool
> >> >  build_dhcpv4_action(struct ovn_port *op, ovs_be32 offer_ip,
> >> >                      struct ds *options_action, struct ds
> *response_action,
> >> > @@ -4086,9 +4100,24 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >              continue;
> >> >          }
> >> >
> >> > +        bool is_external = lsp_is_external(op->nbsp);
> >> > +        if (is_external && (!op->od->localnet_port ||
> !op->n_ps_addrs)) {
> >> > +            /* If the lsp is external with no port securty addresses
> then,
> >> > +             * we don't need to add any port security rules.
> >> > +             * The packets from external ports is received on
> localnet port
> >> > +             * and we allow the traffic from localnet ports.
> >> > +             *
> >> > +             * We also need to ignore these ports if the logical
> switch
> >> > +             * doesn't have a localnet port.
> >> > +             */
> >> > +            continue;
> >> > +        }
> >> > +
> >> >          ds_clear(&match);
> >> >          ds_clear(&actions);
> >> > -        ds_put_format(&match, "inport == %s", op->json_key);
> >> > +        ds_put_format(
> >> > +            &match, "inport == %s",
> >> > +            is_external ? op->od->localnet_port->json_key :
> op->json_key);
> >> >          build_port_security_l2("eth.src", op->ps_addrs,
> op->n_ps_addrs,
> >> >                                 &match);
> >>
> >> It seems matching eth.src depends on setting port-security on the
> >> external port. If port-security is not enabled on the port, eth.src
> >> won't be in match condition, and the following code would generate a
> >> logical flow that sets REGBIT_EXT_PORT_NOT_RESIDENT = 1 for all
> >> packets coming in from localnet port. Did I misunderstand something
> >> here?
> >>
> > Actually if the lport doesn't have port security enabled we have
> 'continue' just above it.
> > So we will not have the flow to set  'REGBIT_EXT_PORT_NOT_RESIDENT'.
> >
> >> Maybe port-security stage is not the right place to set the flag?
> >
> >
> > I agree with you.
> > In this patch I set the 'REGBIT_EXT_PORT_NOT_RESIDENT' to 1
> > if the match is "inport == localnet_port_key && eth.src ==
> <external_port_mac> && !is_chassis_resident(<HV>)"
> >
> > And later this register flag is set in table 16 -
> 'S_SWITCH_IN_EXTERNAL_PORT' to drop the packet
> > if its an ARP request for the router IP.
> >
> > I am using the port security field here to figure out if the packet is
> coming from the external port or not.
> > Probably this is not the right place.
> >
> > In v5, I will remove the usage of register
> 'REGBIT_EXT_PORT_NOT_RESIDENT' and instead add
> > a flow to drop the arp request with the match -
> >  "inport == external_port_key && eth.src == <external_port_mac> &&
> !is_chassis_resident(<HV>) && arp.tpa == <A> && arp.op == 1"
> > in the table 16 'S_SWITCH_IN_EXTERNAL_PORT' for each external port.
> >
> >
> >
> >>
> >> >
> >> > @@ -4096,11 +4125,21 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >          if (queue_id) {
> >> >              ds_put_format(&actions, "set_queue(%s); ", queue_id);
> >> >          }
> >> > +
> >> > +        if (is_external) {
> >> > +            /* Set REGBIT_EXT_PORT_NOT_RESIDENT bit if the external
> port
> >> > +             * doesn't reside on a chassis. */
> >> > +            ds_put_format(&match, " && !is_chassis_resident(%s)",
> >> > +                          op->json_key);
> >> > +            ds_put_cstr(&actions, REGBIT_EXT_PORT_NOT_RESIDENT" = 1;
> ");
> >> > +        }
> >> > +
> >> >          ds_put_cstr(&actions, "next;");
> >> > -        ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2, 50,
> >> > +        ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2,
> >> > +                      is_external ? 90 : 50,
> >> >                        ds_cstr(&match), ds_cstr(&actions));
> >> >
> >> > -        if (op->nbsp->n_port_security) {
> >> > +        if (op->nbsp->n_port_security && !is_external) {
> >> >              build_port_security_ip(P_IN, op, lflows);
> >> >              build_port_security_nd(op, lflows);
> >> >          }
> >> > @@ -4148,7 +4187,7 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >           *  - port type is localport
> >> >           */
> >> >          if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router")
> &&
> >> > -            strcmp(op->nbsp->type, "localport")) {
> >> > +            strcmp(op->nbsp->type, "localport") &&
> lsp_is_external(op->nbsp)) {
> >> >              continue;
> >> >          }
> >> >
> >> > @@ -4260,6 +4299,13 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >              continue;
> >> >          }
> >> >
> >> > +        bool is_external = lsp_is_external(op->nbsp);
> >> > +        if (is_external && !op->od->localnet_port) {
> >> > +            /* If it's an external port and there is no localnet port
> >> > +             * ignore it. */
> >> > +            continue;
> >> > +        }
> >> > +
> >> >          for (size_t i = 0; i < op->n_lsp_addrs; i++) {
> >> >              for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs;
> j++) {
> >> >                  struct ds options_action = DS_EMPTY_INITIALIZER;
> >> > @@ -4272,8 +4318,8 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >                      ds_put_format(
> >> >                          &match, "inport == %s && eth.src == %s && "
> >> >                          "ip4.src == 0.0.0.0 && ip4.dst ==
> 255.255.255.255 && "
> >> > -                        "udp.src == 68 && udp.dst == 67",
> op->json_key,
> >> > -                        op->lsp_addrs[i].ea_s);
> >> > +                        "udp.src == 68 && udp.dst == 67",
> >> > +                        op->json_key, op->lsp_addrs[i].ea_s);
> >> >
> >> >                      ovn_lflow_add(lflows, op->od,
> S_SWITCH_IN_DHCP_OPTIONS,
> >> >                                    100, ds_cstr(&match),
> >> > @@ -4378,7 +4424,8 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >      /* Ingress table 12 and 13: DHCP options and response, by
> default goto
> >> >       * next. (priority 0).
> >> >       * Ingress table 14 and 15: DNS lookup and response, by default
> goto next.
> >> > -     * (priority 0).*/
> >> > +     * (priority 0).
> >> > +     * Ingress table 16 - External port handling */
> >> >
> >> >      HMAP_FOR_EACH (od, key_node, datapaths) {
> >> >          if (!od->nbs) {
> >> > @@ -4389,9 +4436,47 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1",
> "next;");
> >> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_LOOKUP, 0, "1",
> "next;");
> >> >          ovn_lflow_add(lflows, od, S_SWITCH_IN_DNS_RESPONSE, 0, "1",
> "next;");
> >> > +        ovn_lflow_add(lflows, od, S_SWITCH_IN_EXTERNAL_PORT, 0, "1",
> "next;");
> >> > +
> >> > +        if (od->has_external_ports) {
> >> > +            /* Table 16: External port. Drop ARP request for router
> ips from
> >> > +             * external ports if REGBIT_EXT_PORT_NOT_RESIDENT is set.
> >> > +             * This makes the router pipeline to be run only the
> chassis
> >>
> >> s/only the chassis/only on the chassis/
> >
> >
> > Will do.
> >
> > Thanks
> > Numan
> >
> >>
> >>
> >> > +             * binding the external ports. */
> >> > +            for (size_t i = 0; i < od->n_router_ports; i++) {
> >> > +                struct ovn_port *rp = od->router_ports[i];
> >> > +                for (size_t j = 0; j < rp->n_lsp_addrs; j++) {
> >> > +                    for (size_t k = 0; k <
> rp->lsp_addrs[j].n_ipv4_addrs;
> >> > +                         k++) {
> >> > +                        ds_clear(&match);
> >> > +                        ds_put_format(
> >> > +                            &match, REGBIT_EXT_PORT_NOT_RESIDENT""
> >> > +                            " && arp.tpa == %s && arp.op == 1",
> >> > +                            rp->lsp_addrs[j].ipv4_addrs[k].addr_s);
> >> > +                        ds_clear(&actions);
> >> > +                        ovn_lflow_add(lflows, od,
> S_SWITCH_IN_EXTERNAL_PORT,
> >> > +                                      100, ds_cstr(&match), "drop;");
> >> > +                    }
> >> > +                    for (size_t k = 0; k <
> rp->lsp_addrs[j].n_ipv6_addrs;
> >> > +                         k++) {
> >> > +                        ds_clear(&match);
> >> > +                        ds_put_format(
> >> > +                            &match, REGBIT_EXT_PORT_NOT_RESIDENT""
> >> > +                            " && nd_ns && ip6.dst == {%s, %s} && "
> >> > +                            "nd.target == %s",
> >> > +                            rp->lsp_addrs[j].ipv6_addrs[k].addr_s,
> >> > +                            rp->lsp_addrs[j].ipv6_addrs[k].sn_addr_s,
> >> > +                            rp->lsp_addrs[j].ipv6_addrs[k].addr_s);
> >> > +                        ds_clear(&actions);
> >> > +                        ovn_lflow_add(lflows, od,
> S_SWITCH_IN_EXTERNAL_PORT,
> >> > +                                      100, ds_cstr(&match), "drop;");
> >> > +                    }
> >> > +                }
> >> > +            }
> >> > +        }
> >> >      }
> >> >
> >> > -    /* Ingress table 16: Destination lookup, broadcast and multicast
> handling
> >> > +    /* Ingress table 17: Destination lookup, broadcast and multicast
> handling
> >> >       * (priority 100). */
> >> >      HMAP_FOR_EACH (op, key_node, ports) {
> >> >          if (!op->nbsp) {
> >> > @@ -4411,9 +4496,9 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >                        "outport = \""MC_FLOOD"\"; output;");
> >> >      }
> >> >
> >> > -    /* Ingress table 16: Destination lookup, unicast handling
> (priority 50), */
> >> > +    /* Ingress table 17: Destination lookup, unicast handling
> (priority 50), */
> >> >      HMAP_FOR_EACH (op, key_node, ports) {
> >> > -        if (!op->nbsp) {
> >> > +        if (!op->nbsp || lsp_is_external(op->nbsp)) {
> >> >              continue;
> >> >          }
> >> >
> >> > @@ -4530,7 +4615,7 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >          }
> >> >      }
> >> >
> >> > -    /* Ingress table 16: Destination lookup for unknown MACs
> (priority 0). */
> >> > +    /* Ingress table 17: Destination lookup for unknown MACs
> (priority 0). */
> >> >      HMAP_FOR_EACH (od, key_node, datapaths) {
> >> >          if (!od->nbs) {
> >> >              continue;
> >> > @@ -4565,7 +4650,7 @@ build_lswitch_flows(struct hmap *datapaths,
> struct hmap *ports,
> >> >       * Priority 150 rules drop packets to disabled logical ports, so
> that they
> >> >       * don't even receive multicast or broadcast packets. */
> >> >      HMAP_FOR_EACH (op, key_node, ports) {
> >> > -        if (!op->nbsp) {
> >> > +        if (!op->nbsp || lsp_is_external(op->nbsp)) {
> >> >              continue;
> >> >          }
> >> >
> >> > diff --git a/ovn/ovn-architecture.7.xml b/ovn/ovn-architecture.7.xml
> >> > index 3936e6016..71bdb64df 100644
> >> > --- a/ovn/ovn-architecture.7.xml
> >> > +++ b/ovn/ovn-architecture.7.xml
> >> > @@ -1678,6 +1678,82 @@
> >> >      </li>
> >> >    </ol>
> >> >
> >> > +  <h2>Native OVN services for external logical ports</h2>
> >> > +
> >> > +  <p>
> >> > +    To support OVN native services (like DHCP/IPv6 RA/DNS lookup) to
> the
> >> > +    cloud resources which are external, OVN supports
> <code>external</code>
> >> > +    logical ports.
> >> > +  </p>
> >> > +
> >> > +  <p>
> >> > +    Below are some of the use cases where <code>external</code>
> ports can be
> >> > +    used.
> >> > +  </p>
> >> > +
> >> > +  <ul>
> >> > +    <li>
> >> > +      VMs connected to SR-IOV nics - Traffic from these VMs by
> passes the
> >> > +      kernel stack and local <code>ovn-controller</code> do not bind
> these
> >> > +      ports and cannot serve the native services.
> >> > +    </li>
> >> > +    <li>
> >> > +      When CMS supports provisioning baremetal servers.
> >> > +    </li>
> >> > +  </ul>
> >> > +
> >> > +  <p>
> >> > +    OVN will provide the native services if CMS has done the below
> >> > +    configuration in the <dfn>OVN Northbound Database</dfn>.
> >> > +  </p>
> >> > +
> >> > +  <ul>
> >> > +    <li>
> >> > +      A row is created in <code>Logical_Switch_Port</code>,
> configuring the
> >> > +      <ref column="addresses" table="Logical_Switch_Port"
> db="OVN_NB"/> column
> >> > +      and setting the <ref column="type" table="Logical_Switch_Port"
> >> > +      db="OVN_NB"/> to <code>external</code>.
> >> > +    </li>
> >> > +
> >> > +    <li>
> >> > +      <ref column="options:requested-chassis"
> table="Logical_Switch_Port"
> >> > +      db="OVN_NB"/> column is configured to a desired chassis.
> >> > +    </li>
> >> > +
> >> > +    <li>
> >> > +      The chassis on which this logical port is requested has the
> >> > +      <code>ovn-bridge-mappings</code> configured and has proper L2
> >> > +      connectivity so that it can receive the DHCP and other related
> request
> >> > +      packets from these external resources.
> >> > +    </li>
> >> > +
> >> > +    <li>
> >> > +      The Logical_Switch of this port has a <code>localnet</code>
> port.
> >> > +    </li>
> >> > +
> >> > +    <li>
> >> > +      Native OVN services are enabled by configuring the DHCP and
> other
> >> > +      options like the way it is done for the normal logical ports.
> >> > +    </li>
> >> > +  </ul>
> >> > +
> >> > +  <p>
> >> > +    OVN doesn't support HA for these <code>external</code> ports. In
> case
> >> > +    the <code>ovn-controller</code> running on the requested chassis
> goes down,
> >> > +    it is the responsiblity of CMS, to reschedule these
> <code>external</code>
> >> > +    ports to other active chassis.
> >> > +  </p>
> >> > +
> >> > +  <p>
> >> > +    It is recommended to request the same chassis for all the
> external ports.
> >> > +    Otherwise, the physical switch might see MAC flap issue when
> different
> >> > +    chassis provide the native services. For example when supporting
> native
> >> > +    DHCPv4 service, DHCPv4 server mac (configured in
> >> > +    <ref column="options:server_mac" table="DHCP_Options"
> db="OVN_NB"/> column
> >> > +    in table <ref table="DHCP_Options"/>)
> >> > +    originating from different ports can cause MAC flap issue.
> >> > +  </p>
> >> > +
> >> >    <h1>Security</h1>
> >> >
> >> >    <h2>Role-Based Access Controls for the Soutbound DB</h2>
> >> > diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
> >> > index 4141751f8..e45d07f1d 100644
> >> > --- a/ovn/ovn-nb.xml
> >> > +++ b/ovn/ovn-nb.xml
> >> > @@ -348,6 +348,50 @@
> >> >            <dd>
> >> >              A port to a logical switch on a VTEP gateway.
> >> >            </dd>
> >> > +
> >> > +          <dt><code>external</code></dt>
> >> > +          <dd>
> >> > +            <p>
> >> > +              Represents a logical port which is external and not
> having
> >> > +              an OVS port in the integration bridge.
> >> > +              <code>OVN</code> will never receive any traffic from
> this port or
> >> > +              send any traffic to this port. <code>OVN</code> can
> support
> >> > +              native services like DHCPv4/DHCPv6/DNS for this port.
> >> > +              If <ref column="options:requested-chassis"/> is
> defined,
> >> > +              <code>ovn-controller</code> running in that chassis
> will bind
> >> > +              this port to provide these native services. It is
> expected that
> >> > +              this port belong to a bridged logical switch
> >> > +              (with a <code>localnet</code> port).
> >> > +            </p>
> >> > +
> >> > +            <p>
> >> > +              It is recommended to request the same chassis for all
> the
> >> > +              external ports. Otherwise, the physical switch might
> see
> >> > +              MAC flap issue when different chassis provide the
> native
> >> > +              services. For example when supporting native DHCPv4
> >> > +              service, DHCPv4 server mac (configured in
> >> > +              <ref column="options:server_mac" table="DHCP_Options"
> >> > +              db="OVN_NB"/> column in table <ref
> table="DHCP_Options"/>)
> >> > +              originating from different ports can cause MAC flap
> issue.
> >> > +            </p>
> >> > +
> >> > +            <p>
> >> > +              Below are some of the use cases where
> <code>external</code>
> >> > +              ports can be used.
> >> > +            </p>
> >> > +
> >> > +            <ul>
> >> > +              <li>
> >> > +                VMs connected to SR-IOV nics - Traffic from these
> VMs by passes
> >> > +                the kernel stack and local
> <code>ovn-controller</code> do not
> >> > +                bind these ports and cannot serve the native
> services.
> >> > +              </li>
> >> > +
> >> > +              <li>
> >> > +                When CMS supports provisioning baremetal servers.
> >> > +              </li>
> >> > +            </ul>
> >> > +          </dd>
> >> >          </dl>
> >> >        </column>
> >> >      </group>
> >> > diff --git a/tests/ovn.at b/tests/ovn.at
> >> > index 2db3f675a..9598a81ae 100644
> >> > --- a/tests/ovn.at
> >> > +++ b/tests/ovn.at
> >> > @@ -9456,9 +9456,9 @@ AT_CHECK([as hv2 ovs-ofctl dump-flows br-int
> table=32 | grep active_backup | gre
> >> >  sleep 3 # let BFD sessions settle so we get the right flows on the
> right chassis
> >> >
> >> >  # make sure that flows for handling the outside router port reside
> on gw1
> >> > -AT_CHECK([as gw1 ovs-ofctl dump-flows br-int table=24 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[1
> >> > +AT_CHECK([as gw1 ovs-ofctl dump-flows br-int table=25 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[1
> >> >  ]])
> >> > -AT_CHECK([as gw2 ovs-ofctl dump-flows br-int table=24 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[0
> >> > +AT_CHECK([as gw2 ovs-ofctl dump-flows br-int table=25 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[0
> >> >  ]])
> >> >
> >> >  # make sure ARP responder flows for outside router port reside on
> gw1 too
> >> > @@ -9548,9 +9548,9 @@ AT_CHECK([ovs-vsctl --bare --columns bfd find
> Interface name=ovn-hv1-0],[0],
> >> >  sleep 3  # let BFD sessions settle so we get the right flows on the
> right chassis
> >> >
> >> >  # make sure that flows for handling the outside router port reside
> on gw2 now
> >> > -AT_CHECK([as gw2 ovs-ofctl dump-flows br-int table=24 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[1
> >> > +AT_CHECK([as gw2 ovs-ofctl dump-flows br-int table=25 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[1
> >> >  ]])
> >> > -AT_CHECK([as gw1 ovs-ofctl dump-flows br-int table=24 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[0
> >> > +AT_CHECK([as gw1 ovs-ofctl dump-flows br-int table=25 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[0
> >> >  ]])
> >> >
> >> >  # disconnect GW2 from the network, GW1 should take over
> >> > @@ -9562,9 +9562,9 @@ sleep 4
> >> >  bfd_dump
> >> >
> >> >  # make sure that flows for handling the outside router port reside
> on gw2 now
> >> > -AT_CHECK([as gw1 ovs-ofctl dump-flows br-int table=24 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[1
> >> > +AT_CHECK([as gw1 ovs-ofctl dump-flows br-int table=25 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[1
> >> >  ]])
> >> > -AT_CHECK([as gw2 ovs-ofctl dump-flows br-int table=24 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[0
> >> > +AT_CHECK([as gw2 ovs-ofctl dump-flows br-int table=25 | grep
> 00:00:02:01:02:04 | wc -l], [0], [[0
> >> >  ]])
> >> >
> >> >  # check that the chassis redirect port has been reclaimed by the gw1
> chassis
> >> > @@ -11431,6 +11431,524 @@ as hv2 start_daemon ovn-controller
> >> >  OVN_CLEANUP([hv1],[hv2])
> >> >  AT_CLEANUP
> >> >
> >> > +AT_SETUP([ovn -- external logical port])
> >> > +AT_SKIP_IF([test $HAVE_PYTHON = no])
> >> > +ovn_start
> >> > +
> >> > +net_add n1
> >> > +sim_add hv1
> >> > +sim_add hv2
> >> > +
> >> > +ovn-nbctl ls-add ls1
> >> > +ovn-nbctl lsp-add ls1 ls1-lp1 \
> >> > +-- lsp-set-addresses ls1-lp1 "f0:00:00:00:00:01 10.0.0.4 ae70::4"
> >> > +
> >> > +# Add a couple of external logical port
> >> > +ovn-nbctl lsp-add ls1 ls1-lp_ext1 \
> >> > +-- lsp-set-addresses ls1-lp_ext1 "f0:00:00:00:00:03 10.0.0.6 ae70::6"
> >> > +ovn-nbctl lsp-set-port-security ls1-lp_ext1 \
> >> > +"f0:00:00:00:00:03 10.0.0.6 ae70::6"
> >> > +ovn-nbctl lsp-set-type ls1-lp_ext1 external
> >> > +
> >> > +ovn-nbctl lsp-add ls1 ls1-lp_ext2 \
> >> > +-- lsp-set-addresses ls1-lp_ext2 "f0:00:00:00:00:04 10.0.0.7 ae70::7"
> >> > +ovn-nbctl lsp-set-port-security ls1-lp_ext2 \
> >> > +"f0:00:00:00:00:04 10.0.0.7 ae70::8"
> >> > +ovn-nbctl lsp-set-type ls1-lp_ext2 external
> >> > +
> >> > +d1="$(ovn-nbctl create DHCP_Options cidr=10.0.0.0/24 \
> >> > +options="\"server_id\"=\"10.0.0.1\"
> \"server_mac\"=\"ff:10:00:00:00:01\" \
> >> > +\"lease_time\"=\"3600\" \"router\"=\"10.0.0.1\"")"
> >> > +
> >> > +d2="$(ovn-nbctl create DHCP_Options cidr="ae70\:\:/64" \
> >> > +options="\"server_id\"=\"00:00:00:10:00:01\"")"
> >> > +
> >> > +ovn-nbctl lsp-set-dhcpv4-options ls1-lp1 ${d1}
> >> > +ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext1 ${d1}
> >> > +ovn-nbctl lsp-set-dhcpv4-options ls1-lp_ext2 ${d1}
> >> > +
> >> > +ovn-nbctl lsp-set-dhcpv6-options ls1-lp1 ${d2}
> >> > +ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext1 ${d2}
> >> > +ovn-nbctl lsp-set-dhcpv6-options ls1-lp_ext2 ${d2}
> >> > +
> >> > +# Create a logical router and connect it to ls1
> >> > +ovn-nbctl lr-add lr0
> >> > +ovn-nbctl lrp-add lr0 lr0-ls1 a0:10:00:00:00:01 10.0.0.1/24
> >> > +ovn-nbctl lsp-add ls1 ls1-lr0
> >> > +ovn-nbctl set Logical_Switch_Port ls1-lr0 type=router \
> >> > +    options:router-port=lr0-ls1 addresses=router
> >> > +
> >> > +as hv1
> >> > +ovs-vsctl add-br br-phys
> >> > +ovn_attach n1 br-phys 192.168.0.1
> >> > +ovs-vsctl -- add-port br-phys hv1-ext1 -- \
> >> > +    set interface hv1-ext1 options:tx_pcap=hv1/ext1-tx.pcap \
> >> > +    options:rxq_pcap=hv1/ext1-rx.pcap \
> >> > +    ofport-request=2
> >> > +ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
> >> > +
> >> > +as hv2
> >> > +ovs-vsctl add-br br-phys
> >> > +ovn_attach n1 br-phys 192.168.0.2
> >> > +ovs-vsctl -- add-port br-phys hv2-ext2 -- \
> >> > +    set interface hv2-ext2 options:tx_pcap=hv2/ext2-tx.pcap \
> >> > +    options:rxq_pcap=hv2/ext2-rx.pcap \
> >> > +    ofport-request=2
> >> > +ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
> >> > +
> >> > +ovn-sbctl dump-flows > lflows_n.txt
> >> > +
> >> > +# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6
> in hv1 and
> >> > +# hv2 as requested-chassis option is not set and no localnet port
> added to ls1.
> >> > +AT_CHECK([ovn-sbctl dump-flows ls1 | grep "offerip = 10.0.0.6" | \
> >> > +wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +
> >> > +hv1_uuid=$(ovn-sbctl list chassis hv1 | grep uuid | awk '{print $3}')
> >> > +
> >> > +# The port_binding row for ls1-lp_ext1 should have empty chassis
> >> > +chassis=$(ovn-sbctl list port_binding ls1-lp_ext1 | grep -v gateway
> | \
> >> > +grep -v requested | grep chassis | awk '{print $3}')
> >> > +
> >> > +AT_CHECK([test $chassis == "[[]]"], [0], [])
> >> > +
> >> > +# Set the requested-chassis option for ls1-lp_ext1
> >> > +ovn-nbctl --wait=hv lsp-set-options ls1-lp_ext1 requested-chassis=hv1
> >> > +
> >> > +# No DHCPv4/v6 flows for the external port - ls1-lp_ext1 - 10.0.0.6
> in hv1 and hv2
> >> > +# as no localnet port added to ls1 yet.
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +
> >> > +# Add the localnet port to the logical switch ls1
> >> > +ovn-nbctl lsp-add ls1 ln-public
> >> > +ovn-nbctl lsp-set-addresses ln-public unknown
> >> > +ovn-nbctl lsp-set-type ln-public localnet
> >> > +ovn-nbctl --wait=hv lsp-set-options ln-public network_name=phys
> >> > +
> >> > +ln_public_key=$(ovn-sbctl list port_binding ln-public | grep
> tunnel_key | \
> >> > +awk '{print $3}')
> >> > +
> >> > +# The ls1-lp_ext1 should be bound to hv1
> >> > +chassis=$(ovn-sbctl list port_binding ls1-lp_ext1 | grep -v gateway
> | \
> >> > +grep -v requested | grep chassis | awk '{print $3}')
> >> > +AT_CHECK([test $chassis == "$hv1_uuid"], [0], [])
> >> > +
> >> > +# There should be DHCPv4/v6 OF flows for the ls1-lp_ext1 port in hv1
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key |
> \
> >> > +wc -l], [0], [3
> >> > +])
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
> >> > +grep reg14=0x$ln_public_key | wc -l], [0], [1
> >> > +])
> >> > +
> >> > +# There should ne no DHCPv4/v6 flows for ls1-lp_ext1 on hv2
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +
> >> > +# No DHCPv4/v6 flows for the external port - ls1-lp_ext2 - 10.0.0.7
> in hv1 and
> >> > +# hv2 as requested-chassis option is not set.
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.07" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.07" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.07" | wc -l], [0], [0
> >> > +])
> >> > +
> >> > +as hv1
> >> > +ovs-vsctl show
> >> > +
> >> > +# This shell function sends a DHCP request packet
> >> > +# test_dhcp INPORT SRC_MAC DHCP_TYPE OFFER_IP ...
> >> > +test_dhcp() {
> >> > +    local inport=$1 src_mac=$2 dhcp_type=$3 offer_ip=$4 use_ip=$5
> >> > +    shift; shift; shift; shift; shift;
> >> > +    if test $use_ip != 0; then
> >> > +        src_ip=$1
> >> > +        dst_ip=$2
> >> > +        shift; shift;
> >> > +    else
> >> > +        src_ip=`ip_to_hex 0 0 0 0`
> >> > +        dst_ip=`ip_to_hex 255 255 255 255`
> >> > +    fi
> >> > +    local
> request=ffffffffffff${src_mac}0800451001100000000080110000${src_ip}${dst_ip}
> >> > +    # udp header and dhcp header
> >> > +    request=${request}0044004300fc0000
> >> > +
> request=${request}010106006359aa760000000000000000000000000000000000000000${src_mac}
> >> > +    # client hardware padding
> >> > +    request=${request}00000000000000000000
> >> > +    # server hostname
> >> > +
> request=${request}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> request=${request}0000000000000000000000000000000000000000000000000000000000000000
> >> > +    # boot file name
> >> > +
> request=${request}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> request=${request}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> request=${request}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> request=${request}0000000000000000000000000000000000000000000000000000000000000000
> >> > +    # dhcp magic cookie
> >> > +    request=${request}63825363
> >> > +    # dhcp message type
> >> > +    request=${request}3501${dhcp_type}ff
> >> > +
> >> > +    local srv_mac=$1 srv_ip=$2 expected_dhcp_opts=$3
> >> > +    # total IP length will be the IP length of the request packet
> >> > +    # (which is 272 in our case) + 8 (padding bytes) +
> (expected_dhcp_opts / 2)
> >> > +    ip_len=`expr 280 + ${#expected_dhcp_opts} / 2`
> >> > +    udp_len=`expr $ip_len - 20`
> >> > +    ip_len=$(printf "%x" $ip_len)
> >> > +    udp_len=$(printf "%x" $udp_len)
> >> > +    # $ip_len var will be in 3 digits i.e 134. So adding a '0'
> before $ip_len
> >> > +    local
> reply=${src_mac}${srv_mac}080045100${ip_len}000000008011XXXX${srv_ip}${offer_ip}
> >> > +    # udp header and dhcp header.
> >> > +    # $udp_len var will be in 3 digits. So adding a '0' before
> $udp_len
> >> > +
> reply=${reply}004300440${udp_len}0000020106006359aa760000000000000000
> >> > +    # your ip address
> >> > +    reply=${reply}${offer_ip}
> >> > +    # next server ip address, relay agent ip address, client mac
> address
> >> > +    reply=${reply}0000000000000000${src_mac}
> >> > +    # client hardware padding
> >> > +    reply=${reply}00000000000000000000
> >> > +    # server hostname
> >> > +
> reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
> >> > +    # boot file name
> >> > +
> reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
> >> > +
> reply=${reply}0000000000000000000000000000000000000000000000000000000000000000
> >> > +    # dhcp magic cookie
> >> > +    reply=${reply}63825363
> >> > +    # dhcp message type
> >> > +    local dhcp_reply_type=02
> >> > +    if test $dhcp_type = 03; then
> >> > +        dhcp_reply_type=05
> >> > +    fi
> >> > +
> reply=${reply}3501${dhcp_reply_type}${expected_dhcp_opts}00000000ff00000000
> >> > +    echo $reply >> ext1_v4.expected
> >> > +
> >> > +    as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport}
> $request
> >> > +}
> >> > +
> >> > +
> >> > +trim_zeros() {
> >> > +    sed 's/\(00\)\{1,\}$//'
> >> > +}
> >> > +
> >> > +# This shell function sends a DHCPv6 request packet
> >> > +# test_dhcpv6 INPORT SRC_MAC SRC_LLA DHCPv6_MSG_TYPE OFFER_IP
> OUTPORT...
> >> > +# The OUTPORTs (zero or more) list the VIFs on which the original
> DHCPv6
> >> > +# packet should be received twice (one from ovn-controller and the
> other
> >> > +# from the "ovs-ofctl monitor br-int resume"
> >> > +test_dhcpv6() {
> >> > +    local inport=$1 src_mac=$2 src_lla=$3 msg_code=$4 offer_ip=$5
> >> > +    local req_pkt_in_expected=$6
> >> > +    local
> request=ffffffffffff${src_mac}86dd00000000002a1101${src_lla}
> >> > +    # dst ip ff02::1:2
> >> > +    request=${request}ff020000000000000000000000010002
> >> > +    # udp header and dhcpv6 header
> >> > +    request=${request}02220223002affff${msg_code}010203
> >> > +    # Client identifier
> >> > +    request=${request}0001000a00030001${src_mac}
> >> > +    # IA-NA (Identity Association for Non Temporary Address)
> >> > +    request=${request}0003000c0102030400000e1000001518
> >> > +    shift; shift; shift; shift; shift;
> >> > +
> >> > +    local server_mac=000000100001
> >> > +    local server_lla=fe80000000000000020000fffe100001
> >> > +    local reply_code=07
> >> > +    if test $msg_code = 01; then
> >> > +        reply_code=02
> >> > +    fi
> >> > +    local msg_len=54
> >> > +    if test $offer_ip = 1; then
> >> > +        msg_len=28
> >> > +    fi
> >> > +    local reply=${src_mac}${server_mac}86dd0000000000${msg_len}1101
> >> > +    reply=${reply}${server_lla}${src_lla}
> >> > +
> >> > +    # udp header and dhcpv6 header
> >> > +    reply=${reply}0223022200${msg_len}ffff${reply_code}010203
> >> > +    # Client identifier
> >> > +    reply=${reply}0001000a00030001${src_mac}
> >> > +    # IA-NA
> >> > +    if test $offer_ip != 1; then
> >> > +
> reply=${reply}0003002801020304ffffffffffffffff00050018${offer_ip}
> >> > +        reply=${reply}ffffffffffffffff
> >> > +    fi
> >> > +    # Server identifier
> >> > +    reply=${reply}0002000a00030001${server_mac}
> >> > +
> >> > +    echo $reply | trim_zeros >> ext${inport}_v6.expected
> >> > +    # The inport also receives the request packet since it is
> connected
> >> > +    # to the br-phys.
> >> > +    #echo $request >> ext${inport}_v6.expected
> >> > +
> >> > +    as hv1 ovs-appctl netdev-dummy/receive hv${inport}-ext${inport}
> $request
> >> > +}
> >> > +
> >> > +reset_pcap_file() {
> >> > +    local iface=$1
> >> > +    local pcap_file=$2
> >> > +    ovs-vsctl -- set Interface $iface options:tx_pcap=dummy-tx.pcap \
> >> > +options:rxq_pcap=dummy-rx.pcap
> >> > +    rm -f ${pcap_file}*.pcap
> >> > +    ovs-vsctl -- set Interface $iface
> options:tx_pcap=${pcap_file}-tx.pcap \
> >> > +options:rxq_pcap=${pcap_file}-rx.pcap
> >> > +}
> >> > +
> >> > +ip_to_hex() {
> >> > +    printf "%02x%02x%02x%02x" "$@"
> >> > +}
> >> > +
> >> > +AT_CAPTURE_FILE([ofctl_monitor0_hv1.log])
> >> > +as hv1 ovs-ofctl monitor br-int resume --detach --no-chdir \
> >> > +--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv1.log
> >> > +
> >> > +AT_CAPTURE_FILE([ofctl_monitor0_hv2.log])
> >> > +as hv2 ovs-ofctl monitor br-int resume --detach --no-chdir \
> >> > +--pidfile=ovs-ofctl0.pid 2> ofctl_monitor0_hv2.log
> >> > +
> >> > +# Send DHCPDISCOVER.
> >> > +offer_ip=`ip_to_hex 10 0 0 6`
> >> > +server_ip=`ip_to_hex 10 0 0 1`
> >> > +server_mac=ff1000000001
> >> > +expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> >> > +test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
> >> > +$expected_dhcp_opts
> >> > +
> >> > +# NXT_RESUMEs should be 1 in hv1.
> >> > +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv1.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +# NXT_RESUMEs should be 0 in hv2.
> >> > +OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap >
> ext1_v4.packets
> >> > +cat ext1_v4.expected | cut -c -48 > expout
> >> > +AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
> >> > +# Skipping the IPv4 checksum.
> >> > +cat ext1_v4.expected | cut -c 53- > expout
> >> > +AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
> >> > +
> >> > +# ovs-ofctl also resumes the packets and this causes other ports to
> receive
> >> > +# the DHCP request packet. So reset the pcap files so that its
> easier to test.
> >> > +reset_pcap_file hv1-ext1 hv1/ext1
> >> > +rm -f ext1_v4.expected
> >> > +rm -f ext1_v4.packets
> >> > +
> >> > +# Send DHCPv6 request
> >> > +src_mac=f00000000003
> >> > +src_lla=fe80000000000000f20000fffe000003
> >> > +offer_ip=ae700000000000000000000000000006
> >> > +test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip
> >> > +
> >> > +# NXT_RESUMEs should be 2 in hv1.
> >> > +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +# NXT_RESUMEs should be 0 in hv2.
> >> > +OVS_WAIT_UNTIL([test 0 = `cat ofctl_monitor0_hv2.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
> >> > +sort > ext1_v6.packets
> >> > +cat ext1_v6.expected | cut -c -120 > expout
> >> > +AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
> >> > +# Skipping the UDP checksum
> >> > +cat ext1_v6.expected | cut -c 125- > expout
> >> > +AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
> >> > +
> >> > +rm -f ext1_v6.expected
> >> > +rm -f ext1_v6.packets
> >> > +reset_pcap_file hv1-ext1 hv1/ext1
> >> > +
> >> > +# Change the requested-chassis option for ls1-lp_ext1 from hv1 to hv2
> >> > +ovn-nbctl --wait=hv lsp-set-options ls1-lp_ext1 requested-chassis=hv2
> >> > +
> >> > +hv2_uuid=$(ovn-sbctl list chassis hv2 | grep uuid | awk '{print $3}')
> >> > +
> >> > +# The ls1-lp_ext1 should be bound to hv2
> >> > +chassis=$(ovn-sbctl list port_binding ls1-lp_ext1 | grep -v gateway
> | \
> >> > +grep -v requested | grep chassis | awk '{print $3}')
> >> > +AT_CHECK([test $chassis == "$hv2_uuid"], [0], [])
> >> > +
> >> > +# There should be OF flows for DHCP4/v6 for the ls1-lp_ext1 port in
> hv2
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | grep reg14=0x$ln_public_key |
> \
> >> > +wc -l], [0], [3
> >> > +])
> >> > +AT_CHECK([as hv2 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
> >> > +grep reg14=0x$ln_public_key | wc -l], [0], [1
> >> > +])
> >> > +
> >> > +# There should ne no DHCPv4/v6 flows for ls1-lp_ext1 on hv1
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep "0a.00.00.06" | wc -l], [0], [0
> >> > +])
> >> > +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep table=20 | \
> >> > +grep controller | grep tp_src=546 | grep \
> >> > +"ae.70.00.00.00.00.00.00.00.00.00.00.00.00.00.06" | \
> >> > +grep reg14=0x$ln_public_key | wc -l], [0], [0
> >> > +])
> >> > +
> >> > +# Send DHCPDISCOVER again for hv1/ext1. The DHCP response should
> come from
> >> > +# hv2 ovn-controller. Due to the test setup, the port hv1/ext1 is
> also
> >> > +# receiving the expected packet.
> >> > +offer_ip=`ip_to_hex 10 0 0 6`
> >> > +server_ip=`ip_to_hex 10 0 0 1`
> >> > +server_mac=ff1000000001
> >> > +expected_dhcp_opts=330400000e100104ffffff0003040a00000136040a000001
> >> > +test_dhcp 1 f00000000003 01 $offer_ip 0 $server_mac $server_ip \
> >> > +$expected_dhcp_opts
> >> > +
> >> > +# NXT_RESUMEs should be 2 in hv1.
> >> > +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +# NXT_RESUMEs should be 1 in hv2.
> >> > +OVS_WAIT_UNTIL([test 1 = `cat ofctl_monitor0_hv2.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap >
> ext1_v4.packets
> >> > +cat ext1_v4.expected | cut -c -48 > expout
> >> > +AT_CHECK([cat ext1_v4.packets | cut -c -48], [0], [expout])
> >> > +# Skipping the IPv4 checksum.
> >> > +cat ext1_v4.expected | cut -c 53- > expout
> >> > +AT_CHECK([cat ext1_v4.packets | cut -c 53-], [0], [expout])
> >> > +
> >> > +# ovs-ofctl also resumes the packets and this causes other ports to
> receive
> >> > +# the DHCP request packet. So reset the pcap files so that its
> easier to test.
> >> > +reset_pcap_file hv1-ext1 hv1/ext1
> >> > +rm -f ext1_v4.expected
> >> > +
> >> > +# Send DHCPv6 request again
> >> > +src_mac=f00000000003
> >> > +src_lla=fe80000000000000f20000fffe000003
> >> > +offer_ip=ae700000000000000000000000000006
> >> > +test_dhcpv6 1 $src_mac $src_lla 01 $offer_ip 1
> >> > +
> >> > +# NXT_RESUMEs should be 2 in hv1.
> >> > +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv1.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +# NXT_RESUMEs should be 2 in hv2.
> >> > +OVS_WAIT_UNTIL([test 2 = `cat ofctl_monitor0_hv2.log | grep -c
> NXT_RESUME`])
> >> > +
> >> > +as hv1
> >> > +ovs-vsctl show
> >> > +ovs-ofctl dump-flows br-int
> >> > +
> >> > +as hv2
> >> > +ovs-vsctl show
> >> > +ovs-ofctl dump-flows br-int
> >> > +
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap | \
> >> > +sort > ext1_v6.packets
> >> > +cat ext1_v6.expected | cut -c -120 > expout
> >> > +AT_CHECK([cat ext1_v6.packets | cut -c -120], [0], [expout])
> >> > +# Skipping the UDP checksum
> >> > +cat ext1_v6.expected | cut -c 125- > expout
> >> > +AT_CHECK([cat ext1_v6.packets | cut -c 125-], [0], [expout])
> >> > +
> >> > +rm -f ext1_v6.expected
> >> > +rm -f ext1_v6.packets
> >> > +
> >> > +as hv1
> >> > +ovs-vsctl show
> >> > +reset_pcap_file hv1-ext1 hv1/ext1
> >> > +reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >> > +reset_pcap_file br-phys hv1/br-phys
> >> > +
> >> > +as hv2
> >> > +ovs-vsctl show
> >> > +reset_pcap_file hv2-ext2 hv2/ext2
> >> > +reset_pcap_file br-phys_n1 hv2/br-phys_n1
> >> > +reset_pcap_file br-phys hv2/br-phys
> >> > +
> >> > +# From  ls1-lp_ext1, send ARP request for the router ip. The ARP
> >> > +# response should come from the router pipeline of hv2.
> >> > +ext1_mac=f00000000003
> >> > +router_mac=a01000000001
> >> > +ext1_ip=`ip_to_hex 10 0 0 6`
> >> > +router_ip=`ip_to_hex 10 0 0 1`
> >> >
> +arp_request=ffffffffffff${ext1_mac}08060001080006040001${ext1_mac}${ext1_ip}000000000000${router_ip}
> >> > +
> >> > +as hv1 ovs-appctl netdev-dummy/receive hv1-ext1 $arp_request
> >> >
> +expected_response=${src_mac}${router_mac}08060001080006040002${router_mac}${router_ip}${ext1_mac}${ext1_ip}
> >> > +echo $expected_response > expout
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap >
> ext1_arp_resp
> >> > +AT_CHECK([cat ext1_arp_resp], [0], [expout])
> >> > +
> >> > +# Verify that the response came from hv2
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap
> > ext1_arp_resp
> >> > +AT_CHECK([cat ext1_arp_resp], [0], [expout])
> >> > +
> >> > +
> >> > +# # Change the requested-chassis option for ls1-lp_ext1 from hv2 to
> hv1
> >> > +ovn-nbctl --wait=hv lsp-set-options ls1-lp_ext1 requested-chassis=hv1
> >> > +
> >> > +as hv1
> >> > +ovs-vsctl show
> >> > +reset_pcap_file hv1-ext1 hv1/ext1
> >> > +reset_pcap_file br-phys_n1 hv1/br-phys_n1
> >> > +reset_pcap_file br-phys hv1/br-phys
> >> > +
> >> > +as hv2
> >> > +ovs-vsctl show
> >> > +reset_pcap_file hv2-ext2 hv2/ext2
> >> > +reset_pcap_file br-phys_n1 hv2/br-phys_n1
> >> > +reset_pcap_file br-phys hv2/br-phys
> >> > +
> >> > +as hv1 ovs-appctl netdev-dummy/receive hv1-ext1 $arp_request
> >> > +
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/ext1-tx.pcap >
> ext1_arp_resp
> >> > +AT_CHECK([cat ext1_arp_resp], [0], [expout])
> >> > +
> >> > +# Verify that the response didn't come from hv2
> >> > +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/br-phys_n1-tx.pcap
> > ext1_arp_resp
> >> > +AT_CHECK([cat ext1_arp_resp], [0], [])
> >> > +
> >> > +OVN_CLEANUP([hv1],[hv2])
> >> > +AT_CLEANUP
> >> > +
> >> >  AT_SETUP([ovn -- ovn-controller restart])
> >> >  AT_SKIP_IF([test $HAVE_PYTHON = no])
> >> >  ovn_start
> >> > --
> >> > 2.20.1
> >> >
> >> > _______________________________________________
> >> > dev mailing list
> >> > d...@openvswitch.org
> >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to