Message ID | 20190110203644.22792-1-nusiddiq@redhat.com |
---|---|
State | Superseded |
Headers | show |
Series | [ovs-dev,v4] ovn: Support a new Logical_Switch_Port.type - 'external' | expand |
Hi Numan, The feature looks very good overall. I have some more comments inlined. On Thu, Jan 10, 2019 at 12:37 PM <nusiddiq@redhat.com> wrote: > > From: Numan Siddique <nusiddiq@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 <nusiddiq@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. > 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? > + > + 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? Maybe port-security stage is not the right place to set the flag? > > @@ -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/ > + * 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 > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
On Tue, Jan 15, 2019 at 5:42 AM Han Zhou <zhouhan@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 <nusiddiq@redhat.com> wrote: > > > > From: Numan Siddique <nusiddiq@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 <nusiddiq@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. > > + > > + 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 > > dev@openvswitch.org > > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
On Tue, Jan 15, 2019 at 10:24 AM Numan Siddique <nusiddiq@redhat.com> wrote: > > > > On Tue, Jan 15, 2019 at 5:42 AM Han Zhou <zhouhan@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 <nusiddiq@redhat.com> wrote: >> > >> > From: Numan Siddique <nusiddiq@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 <nusiddiq@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. > >> >> > + >> > + 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 >> > dev@openvswitch.org >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
On Thu, Jan 17, 2019 at 10:57 PM Han Zhou <zhouhan@gmail.com> wrote: > On Tue, Jan 15, 2019 at 10:24 AM Numan Siddique <nusiddiq@redhat.com> > wrote: > > > > > > > > On Tue, Jan 15, 2019 at 5:42 AM Han Zhou <zhouhan@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 <nusiddiq@redhat.com> wrote: > >> > > >> > From: Numan Siddique <nusiddiq@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 <nusiddiq@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 && 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 > >> > dev@openvswitch.org > >> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev >
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")) { 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); + + 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); @@ -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 + * 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