On Tue, Jan 15, 2019 at 10:24 AM Numan Siddique <[email protected]> wrote: > > > > On Tue, Jan 15, 2019 at 5:42 AM Han Zhou <[email protected]> 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 <[email protected]> wrote: >> > >> > From: Numan Siddique <[email protected]> >> > >> > 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 <[email protected]> >> > --- >> > >> > 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. > >> >> > + >> > + 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 && arp.tpa == >> > <var>A</var> >> > + && 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 >> > [email protected] >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
