[ovs-dev] [PATCH RFC ovn] Support logical switches with multiple localnet ports
Maciej Jozefczyk
mjozefcz at redhat.com
Thu Apr 23 11:53:35 UTC 2020
Hey,
I tested this patch with multi node devstack environment - more detailed
information: [1]
I verified that those things are working:
* DHCP and metadata for instances in all segments (VM1, VM2, VM3)
* traffic from VM1 to VM3 (same segment, different chassis)
* traffic from host1 to VM1 (from provider network segment-1 to VM using
OVN and vice versa)
* traffic from host2 to VM2 (from provider network segment-2 to VM using
OVN and vice versa)
* Security groups for traffic within OVN (VM1<>VM3) and outside
(VM1<>host1, VM2<>host2).
* ARP responder (tested between VM1 and VM3).
Things to check:
* possibility of binding localnet ports on bridge mappings change or
recomputation (only)
Tested-By: Maciej Jozefczyk <mjozefcz at redhat.com>
[1]
https://github.com/mjozefcz/vagrants/tree/master/ovn-routed-provider-networks-devstack
On Wed, Apr 15, 2020 at 3:45 AM Ihar Hrachyshka <ihrachys at redhat.com> wrote:
> Assuming only a single localnet port is actually plugged mapped on
> each chassis, this allows to maintain disjoint networks plugged to the
> same switch. This is useful to simplify resource management for
> OpenStack "routed provider networks" feature [1] where a single
> "network" (which traditionally maps to logical switches in OVN) is
> comprised of multiple L2 segments and assumes external L3 routing
> implemented between the segments.
>
> TODO: consider E-W routing between localnet vlan tagged LSs
> (ovn-chassis-mac-mappings).
>
> Note: the test requires [2] to actually validate packets.
>
> [1]:
> https://docs.openstack.org/ocata/networking-guide/config-routed-networks.html
> [2]: https://patchwork.ozlabs.org/project/openvswitch/list/?series=169291
>
> Signed-off-by: Ihar Hrachyshka <ihrachys at redhat.com>
> ---
> controller/binding.c | 16 ++
> controller/patch.c | 24 ++-
> northd/ovn-northd.c | 351 ++++++++++++++++++++++-------------------
> ovn-architecture.7.xml | 25 ++-
> ovn-nb.xml | 21 +--
> ovn-sb.xml | 21 ++-
> tests/ovn.at | 112 +++++++++++++
> 7 files changed, 373 insertions(+), 197 deletions(-)
>
> diff --git a/controller/binding.c b/controller/binding.c
> index 5ea12a8be..f4ae42806 100644
> --- a/controller/binding.c
> +++ b/controller/binding.c
> @@ -680,12 +680,28 @@ add_localnet_egress_interface_mappings(
> }
> }
>
> +static bool
> +is_network_plugged(const struct sbrec_port_binding *binding_rec,
> + struct shash *bridge_mappings)
> +{
> + const char *network = smap_get(&binding_rec->options, "network_name");
> + if (!network) {
> + return false;
> + }
> + return shash_find_data(bridge_mappings, network);
> +}
> +
> static void
> consider_localnet_port(const struct sbrec_port_binding *binding_rec,
> struct shash *bridge_mappings,
> struct sset *egress_ifaces,
> struct hmap *local_datapaths)
> {
> + /* Ignore localnet ports for unplugged networks. */
> + if (!is_network_plugged(binding_rec, bridge_mappings)) {
> + return;
> + }
> +
> add_localnet_egress_interface_mappings(binding_rec,
> bridge_mappings, egress_ifaces);
>
> diff --git a/controller/patch.c b/controller/patch.c
> index 349faae17..52255cc3a 100644
> --- a/controller/patch.c
> +++ b/controller/patch.c
> @@ -198,9 +198,9 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
> continue;
> }
>
> - const char *patch_port_id;
> + bool is_localnet = false;
> if (!strcmp(binding->type, "localnet")) {
> - patch_port_id = "ovn-localnet-port";
> + is_localnet = true;
> } else if (!strcmp(binding->type, "l2gateway")) {
> if (!binding->chassis
> || strcmp(chassis->name, binding->chassis->name)) {
> @@ -208,7 +208,6 @@ add_bridge_mappings(struct ovsdb_idl_txn *ovs_idl_txn,
> * so we should not create any patch ports for it. */
> continue;
> }
> - patch_port_id = "ovn-l2gateway-port";
> } else {
> /* not a localnet or L2 gateway port. */
> continue;
> @@ -224,12 +223,25 @@ add_bridge_mappings(struct ovsdb_idl_txn
> *ovs_idl_txn,
> struct ovsrec_bridge *br_ln = shash_find_data(&bridge_mappings,
> network);
> if (!br_ln) {
> static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> - VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' "
> - "with network name '%s'",
> - binding->type, binding->logical_port, network);
> + if (!is_localnet) {
> + VLOG_ERR_RL(&rl, "bridge not found for %s port '%s' "
> + "with network name '%s'",
> + binding->type, binding->logical_port, network);
> + } else {
> + VLOG_INFO_RL(&rl, "bridge not found for localnet port
> '%s' "
> + "with network name '%s'; skipping",
> + binding->logical_port, network);
> + }
> continue;
> }
>
> + const char *patch_port_id;
> + if (is_localnet) {
> + patch_port_id = "ovn-localnet-port";
> + } else {
> + patch_port_id = "ovn-l2gateway-port";
> + }
> +
> char *name1 = patch_port_name(br_int->name,
> binding->logical_port);
> char *name2 = patch_port_name(binding->logical_port,
> br_int->name);
> create_patch_port(ovs_idl_txn, patch_port_id,
> binding->logical_port,
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index 076278197..91730f7f3 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -543,7 +543,9 @@ struct ovn_datapath {
> /* The "derived" OVN port representing the instance of l3dgw_port on
> * the "redirect-chassis". */
> struct ovn_port *l3redirect_port;
> - struct ovn_port *localnet_port;
> +
> + struct ovn_port **localnet_ports;
> + size_t n_localnet_ports;
>
> struct ovs_list lr_list; /* In list of logical router datapaths. */
> /* The logical router group to which this datapath belongs.
> @@ -611,6 +613,7 @@ ovn_datapath_destroy(struct hmap *datapaths, struct
> ovn_datapath *od)
> ovn_destroy_tnlids(&od->port_tnlids);
> bitmap_free(od->ipam_info.allocated_ipv4s);
> free(od->router_ports);
> + free(od->localnet_ports);
> ovn_ls_port_group_destroy(&od->nb_pgs);
> destroy_mcast_info_for_datapath(od);
>
> @@ -2053,7 +2056,11 @@ join_logical_ports(struct northd_context *ctx,
> }
>
> if (!strcmp(nbsp->type, "localnet")) {
> - od->localnet_port = op;
> + od->localnet_ports = xrealloc(
> + od->localnet_ports,
> + sizeof *od->localnet_ports * (od->n_localnet_ports
> + 1)
> + );
> + od->localnet_ports[od->n_localnet_ports++] = op;
> }
>
> op->lsp_addrs
> @@ -2974,7 +2981,7 @@ ovn_port_update_sbrec(struct northd_context *ctx,
> "reside-on-redirect-chassis", false) ||
> op->peer == op->peer->od->l3dgw_port)) {
> add_router_port_garp = true;
> - } else if (chassis && op->od->localnet_port) {
> + } else if (chassis && op->od->localnet_ports) {
> add_router_port_garp = true;
> }
>
> @@ -4662,23 +4669,25 @@ build_pre_acls(struct ovn_datapath *od, struct
> hmap *lflows)
> ds_destroy(&match_in);
> ds_destroy(&match_out);
> }
> - if (od->localnet_port) {
> - struct ds match_in = DS_EMPTY_INITIALIZER;
> - struct ds match_out = DS_EMPTY_INITIALIZER;
> + if (od->localnet_ports) {
> + for (size_t i = 0; i < od->n_localnet_ports; i++) {
> + struct ds match_in = DS_EMPTY_INITIALIZER;
> + struct ds match_out = DS_EMPTY_INITIALIZER;
>
> - ds_put_format(&match_in, "ip && inport == %s",
> - od->localnet_port->json_key);
> - ds_put_format(&match_out, "ip && outport == %s",
> - od->localnet_port->json_key);
> - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_ACL, 110,
> - ds_cstr(&match_in), "next;",
> - &od->localnet_port->nbsp->header_);
> - ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_PRE_ACL, 110,
> - ds_cstr(&match_out), "next;",
> - &od->localnet_port->nbsp->header_);
> + ds_put_format(&match_in, "ip && inport == %s",
> + od->localnet_ports[i]->json_key);
> + ds_put_format(&match_out, "ip && outport == %s",
> + od->localnet_ports[i]->json_key);
> + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_PRE_ACL,
> 110,
> + ds_cstr(&match_in), "next;",
> +
> &od->localnet_ports[i]->nbsp->header_);
> + ovn_lflow_add_with_hint(lflows, od, S_SWITCH_OUT_PRE_ACL,
> 110,
> + ds_cstr(&match_out), "next;",
> +
> &od->localnet_ports[i]->nbsp->header_);
>
> - ds_destroy(&match_in);
> - ds_destroy(&match_out);
> + ds_destroy(&match_in);
> + ds_destroy(&match_out);
> + }
> }
>
> /* Ingress and Egress Pre-ACL Table (Priority 110).
> @@ -5919,9 +5928,11 @@ build_lswitch_rport_arp_req_flow_for_ip(struct sset
> *ips,
> /* Send a the packet only to the router pipeline and skip flooding it
> * in the broadcast domain (except for the localnet port).
> */
> - if (od->localnet_port) {
> - ds_put_format(&actions, "clone { outport = %s; output; }; ",
> - od->localnet_port->json_key);
> + if (od->localnet_ports) {
> + for (size_t i = 0; i < od->n_localnet_ports; i++) {
> + ds_put_format(&actions, "clone { outport = %s; output; }; ",
> + od->localnet_ports[i]->json_key);
> + }
> }
> ds_put_format(&actions, "outport = %s; output;", patch_op->json_key);
> ovn_lflow_add_with_hint(lflows, od, S_SWITCH_IN_L2_LKUP, priority,
> @@ -6323,9 +6334,9 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
> }
>
> bool is_external = lsp_is_external(op->nbsp);
> - if (is_external && (!op->od->localnet_port ||
> + if (is_external && (!op->od->localnet_ports ||
> !op->nbsp->ha_chassis_group)) {
> - /* If it's an external port and there is no localnet port
> + /* If it's an external port and there are no localnet ports
> * and if it doesn't belong to an HA chassis group ignore it.
> */
> continue;
> }
> @@ -6339,84 +6350,93 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
> stage_hint = NULL;
> }
>
> - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
> - struct ds options_action = DS_EMPTY_INITIALIZER;
> - struct ds response_action = DS_EMPTY_INITIALIZER;
> - struct ds ipv4_addr_match = DS_EMPTY_INITIALIZER;
> - if (build_dhcpv4_action(
> - op, op->lsp_addrs[i].ipv4_addrs[j].addr,
> - &options_action, &response_action,
> &ipv4_addr_match)) {
> - ds_clear(&match);
> - 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",
> - is_external ? op->od->localnet_port->json_key :
> - op->json_key,
> - op->lsp_addrs[i].ea_s);
> -
> - if (is_external) {
> - ds_put_format(&match, " &&
> is_chassis_resident(%s)",
> - op->json_key);
> - }
> + size_t jmax = is_external? op->od->n_localnet_ports: 1;
> + for (size_t j = 0; j < jmax; j++) {
> + for (size_t k = 0; k < op->lsp_addrs[i].n_ipv4_addrs;
> k++) {
> + struct ds options_action = DS_EMPTY_INITIALIZER;
> + struct ds response_action = DS_EMPTY_INITIALIZER;
> + struct ds ipv4_addr_match = DS_EMPTY_INITIALIZER;
> + if (build_dhcpv4_action(
> + op, op->lsp_addrs[i].ipv4_addrs[k].addr,
> + &options_action, &response_action,
> + &ipv4_addr_match)) {
> + ds_clear(&match);
> + 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",
> + is_external ?
> op->od->localnet_ports[j]->json_key :
> + op->json_key,
> + op->lsp_addrs[i].ea_s);
>
> - ovn_lflow_add_with_hint(lflows, op->od,
> - S_SWITCH_IN_DHCP_OPTIONS, 100,
> - ds_cstr(&match),
> - ds_cstr(&options_action),
> - stage_hint);
> - ds_clear(&match);
> - /* Allow ip4.src = OFFER_IP and
> - * ip4.dst = {SERVER_IP, 255.255.255.255} for the
> below
> - * cases
> - * - When the client wants to renew the IP by
> sending
> - * the DHCPREQUEST to the server ip.
> - * - When the client wants to renew the IP by
> - * broadcasting the DHCPREQUEST.
> - */
> - ds_put_format(
> - &match, "inport == %s && eth.src == %s && "
> - "%s && udp.src == 68 && udp.dst == 67",
> - is_external ? op->od->localnet_port->json_key :
> - op->json_key,
> - op->lsp_addrs[i].ea_s, ds_cstr(&ipv4_addr_match));
> + if (is_external) {
> + ds_put_format(
> + &match, " && is_chassis_resident(%s)",
> + op->json_key);
> + }
>
> - if (is_external) {
> - ds_put_format(&match, " &&
> is_chassis_resident(%s)",
> - op->json_key);
> - }
> + ovn_lflow_add_with_hint(lflows, op->od,
> + S_SWITCH_IN_DHCP_OPTIONS,
> 100,
> + ds_cstr(&match),
> + ds_cstr(&options_action),
> + stage_hint);
> + ds_clear(&match);
> + /* Allow ip4.src = OFFER_IP and
> + * ip4.dst = {SERVER_IP, 255.255.255.255} for the
> below
> + * cases
> + * - When the client wants to renew the IP by
> sending
> + * the DHCPREQUEST to the server ip.
> + * - When the client wants to renew the IP by
> + * broadcasting the DHCPREQUEST.
> + */
> + ds_put_format(
> + &match, "inport == %s && eth.src == %s && "
> + "%s && udp.src == 68 && udp.dst == 67",
> + is_external ?
> op->od->localnet_ports[j]->json_key :
> + op->json_key,
> + op->lsp_addrs[i].ea_s,
> ds_cstr(&ipv4_addr_match));
> +
> + if (is_external) {
> + ds_put_format(
> + &match, " && is_chassis_resident(%s)",
> + op->json_key);
> + }
>
> - ovn_lflow_add_with_hint(lflows, op->od,
> - S_SWITCH_IN_DHCP_OPTIONS, 100,
> - ds_cstr(&match),
> - ds_cstr(&options_action),
> - stage_hint);
> - ds_clear(&match);
> + ovn_lflow_add_with_hint(lflows, op->od,
> + S_SWITCH_IN_DHCP_OPTIONS,
> 100,
> + ds_cstr(&match),
> + ds_cstr(&options_action),
> + stage_hint);
> + ds_clear(&match);
>
> - /* If REGBIT_DHCP_OPTS_RESULT is set, it means the
> - * put_dhcp_opts action is successful. */
> - ds_put_format(
> - &match, "inport == %s && eth.src == %s && "
> - "ip4 && udp.src == 68 && udp.dst == 67"
> - " && "REGBIT_DHCP_OPTS_RESULT,
> - is_external ? op->od->localnet_port->json_key :
> - op->json_key,
> - op->lsp_addrs[i].ea_s);
> -
> - if (is_external) {
> - ds_put_format(&match, " &&
> is_chassis_resident(%s)",
> - op->json_key);
> - }
> + /* If REGBIT_DHCP_OPTS_RESULT is set, it means the
> + * put_dhcp_opts action is successful. */
> + ds_put_format(
> + &match, "inport == %s && eth.src == %s && "
> + "ip4 && udp.src == 68 && udp.dst == 67"
> + " && "REGBIT_DHCP_OPTS_RESULT,
> + is_external ?
> op->od->localnet_ports[j]->json_key :
> + op->json_key,
> + op->lsp_addrs[i].ea_s);
>
> - ovn_lflow_add_with_hint(lflows, op->od,
> - S_SWITCH_IN_DHCP_RESPONSE,
> 100,
> - ds_cstr(&match),
> - ds_cstr(&response_action),
> - stage_hint);
> - ds_destroy(&options_action);
> - ds_destroy(&response_action);
> - ds_destroy(&ipv4_addr_match);
> - break;
> + if (is_external) {
> + ds_put_format(
> + &match, " && is_chassis_resident(%s)",
> + op->json_key);
> + }
> +
> + ovn_lflow_add_with_hint(lflows, op->od,
> +
> S_SWITCH_IN_DHCP_RESPONSE, 100,
> + ds_cstr(&match),
> + ds_cstr(&response_action),
> + stage_hint);
> + ds_destroy(&options_action);
> + ds_destroy(&response_action);
> + ds_destroy(&ipv4_addr_match);
> + break;
> + }
> }
> }
>
> @@ -6426,43 +6446,46 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
> stage_hint = NULL;
> }
>
> - for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
> - struct ds options_action = DS_EMPTY_INITIALIZER;
> - struct ds response_action = DS_EMPTY_INITIALIZER;
> - if (build_dhcpv6_action(
> - op, &op->lsp_addrs[i].ipv6_addrs[j].addr,
> - &options_action, &response_action)) {
> - ds_clear(&match);
> - ds_put_format(
> - &match, "inport == %s && eth.src == %s"
> - " && ip6.dst == ff02::1:2 && udp.src == 546 &&"
> - " udp.dst == 547",
> - is_external ? op->od->localnet_port->json_key :
> - op->json_key,
> - op->lsp_addrs[i].ea_s);
> -
> - if (is_external) {
> - ds_put_format(&match, " &&
> is_chassis_resident(%s)",
> - op->json_key);
> - }
> + for (size_t j = 0; j < jmax; j++) {
> + for (size_t k = 0; k < op->lsp_addrs[i].n_ipv6_addrs;
> k++) {
> + struct ds options_action = DS_EMPTY_INITIALIZER;
> + struct ds response_action = DS_EMPTY_INITIALIZER;
> + if (build_dhcpv6_action(
> + op, &op->lsp_addrs[i].ipv6_addrs[k].addr,
> + &options_action, &response_action)) {
> + ds_clear(&match);
> + ds_put_format(
> + &match, "inport == %s && eth.src == %s"
> + " && ip6.dst == ff02::1:2 && udp.src == 546
> &&"
> + " udp.dst == 547",
> + is_external ?
> op->od->localnet_ports[j]->json_key :
> + op->json_key,
> + op->lsp_addrs[i].ea_s);
>
> - ovn_lflow_add_with_hint(lflows, op->od,
> - S_SWITCH_IN_DHCP_OPTIONS, 100,
> - ds_cstr(&match),
> - ds_cstr(&options_action),
> - stage_hint);
> + if (is_external) {
> + ds_put_format(
> + &match, " && is_chassis_resident(%s)",
> + op->json_key);
> + }
>
> - /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it means
> the
> - * put_dhcpv6_opts action is successful */
> - ds_put_cstr(&match, " && "REGBIT_DHCP_OPTS_RESULT);
> - ovn_lflow_add_with_hint(lflows, op->od,
> - S_SWITCH_IN_DHCP_RESPONSE,
> 100,
> - ds_cstr(&match),
> - ds_cstr(&response_action),
> - stage_hint);
> - ds_destroy(&options_action);
> - ds_destroy(&response_action);
> - break;
> + ovn_lflow_add_with_hint(lflows, op->od,
> + S_SWITCH_IN_DHCP_OPTIONS,
> 100,
> + ds_cstr(&match),
> + ds_cstr(&options_action),
> + stage_hint);
> +
> + /* If REGBIT_DHCP_OPTS_RESULT is set to 1, it
> means the
> + * put_dhcpv6_opts action is successful */
> + ds_put_cstr(&match, " &&
> "REGBIT_DHCP_OPTS_RESULT);
> + ovn_lflow_add_with_hint(lflows, op->od,
> +
> S_SWITCH_IN_DHCP_RESPONSE, 100,
> + ds_cstr(&match),
> + ds_cstr(&response_action),
> + stage_hint);
> + ds_destroy(&options_action);
> + ds_destroy(&response_action);
> + break;
> + }
> }
> }
> }
> @@ -6521,7 +6544,7 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
>
> HMAP_FOR_EACH (op, key_node, ports) {
> if (!op->nbsp || !lsp_is_external(op->nbsp) ||
> - !op->od->localnet_port) {
> + !op->od->localnet_ports) {
> continue;
> }
>
> @@ -6536,36 +6559,42 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
> for (size_t k = 0; k < rp->n_lsp_addrs; k++) {
> for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv4_addrs;
> l++) {
> - ds_clear(&match);
> - ds_put_format(
> - &match, "inport == %s && eth.src == %s"
> - " && !is_chassis_resident(%s)"
> - " && arp.tpa == %s && arp.op == 1",
> - op->od->localnet_port->json_key,
> - op->lsp_addrs[i].ea_s, op->json_key,
> - rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
> - ovn_lflow_add_with_hint(lflows, op->od,
> - S_SWITCH_IN_EXTERNAL_PORT,
> - 100, ds_cstr(&match),
> "drop;",
> - &op->nbsp->header_);
> + for (size_t m = 0; m < op->od->n_localnet_ports;
> m++) {
> + ds_clear(&match);
> + ds_put_format(
> + &match, "inport == %s && eth.src == %s"
> + " && !is_chassis_resident(%s)"
> + " && arp.tpa == %s && arp.op == 1",
> + op->od->localnet_ports[m]->json_key,
> + op->lsp_addrs[i].ea_s, op->json_key,
> + rp->lsp_addrs[k].ipv4_addrs[l].addr_s);
> + ovn_lflow_add_with_hint(
> + lflows, op->od,
> + S_SWITCH_IN_EXTERNAL_PORT, 100,
> + ds_cstr(&match), "drop;",
> + &op->nbsp->header_);
> + }
> }
> for (size_t l = 0; l < rp->lsp_addrs[k].n_ipv6_addrs;
> l++) {
> - ds_clear(&match);
> - ds_put_format(
> - &match, "inport == %s && eth.src == %s"
> - " && !is_chassis_resident(%s)"
> - " && nd_ns && ip6.dst == {%s, %s} && "
> - "nd.target == %s",
> - op->od->localnet_port->json_key,
> - op->lsp_addrs[i].ea_s, op->json_key,
> - rp->lsp_addrs[k].ipv6_addrs[l].addr_s,
> - rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s,
> - rp->lsp_addrs[k].ipv6_addrs[l].addr_s);
> - ovn_lflow_add_with_hint(lflows, op->od,
> -
> S_SWITCH_IN_EXTERNAL_PORT, 100,
> - ds_cstr(&match), "drop;",
> - &op->nbsp->header_);
> + for (size_t m = 0; m < op->od->n_localnet_ports;
> m++) {
> + ds_clear(&match);
> + ds_put_format(
> + &match, "inport == %s && eth.src == %s"
> + " && !is_chassis_resident(%s)"
> + " && nd_ns && ip6.dst == {%s, %s} && "
> + "nd.target == %s",
> + op->od->localnet_ports[m]->json_key,
> + op->lsp_addrs[i].ea_s, op->json_key,
> + rp->lsp_addrs[k].ipv6_addrs[l].addr_s,
> + rp->lsp_addrs[k].ipv6_addrs[l].sn_addr_s,
> + rp->lsp_addrs[k].ipv6_addrs[l].addr_s);
> + ovn_lflow_add_with_hint(
> + lflows, op->od,
> + S_SWITCH_IN_EXTERNAL_PORT, 100,
> + ds_cstr(&match), "drop;",
> + &op->nbsp->header_);
> + }
> }
> }
> }
> @@ -6787,7 +6816,7 @@ build_lswitch_flows(struct hmap *datapaths, struct
> hmap *ports,
> ETH_ADDR_ARGS(mac));
> if (op->peer->od->l3dgw_port
> && op->peer->od->l3redirect_port
> - && op->od->localnet_port) {
> + && op->od->localnet_ports) {
> bool add_chassis_resident_check = false;
> if (op->peer == op->peer->od->l3dgw_port) {
> /* The peer of this port represents a distributed
> @@ -8084,7 +8113,7 @@ build_lrouter_flows(struct hmap *datapaths, struct
> hmap *ports,
> op->lrp_networks.ipv4_addrs[i].addr_s);
>
> if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
> - && op->peer->od->localnet_port) {
> + && op->peer->od->localnet_ports) {
> bool add_chassis_resident_check = false;
> if (op == op->od->l3dgw_port) {
> /* Traffic with eth.src =
> l3dgw_port->lrp_networks.ea_s
> diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
> index 533ae716d..88edb6f32 100644
> --- a/ovn-architecture.7.xml
> +++ b/ovn-architecture.7.xml
> @@ -441,9 +441,8 @@
>
> <p>
> A <code>localnet</code> logical switch port bridges a logical switch
> to a
> - physical VLAN. Any given logical switch should have no more than one
> - <code>localnet</code> port. Such a logical switch is used in two
> - scenarios:
> + physical VLAN. A logical switch may have one or more
> <code>localnet</code>
> + ports. Such a logical switch is used in two scenarios:
> </p>
>
> <ul>
> @@ -1895,13 +1894,13 @@
> <ol>
> <li>
> The packet first enters the ingress pipeline, and then egress
> pipeline of
> - the source localnet logical switch datapath and is sent out via the
> + the source localnet logical switch datapath and is sent out via a
> localnet port of the source localnet logical switch (instead of
> sending
> it to router pipeline).
> </li>
>
> <li>
> - The gateway chassis receives the packet via the localnet port of the
> + The gateway chassis receives the packet via a localnet port of the
> source localnet logical switch and sends it to the integration
> bridge.
> The packet then enters the ingress pipeline, and then egress
> pipeline of
> the source localnet logical switch datapath and enters the ingress
> @@ -1916,11 +1915,11 @@
> From the router datapath, packet enters the ingress pipeline and
> then
> egress pipeline of the destination localnet logical switch datapath.
> It then goes out of the integration bridge to the provider bridge (
> - belonging to the destination logical switch) via the localnet port.
> + belonging to the destination logical switch) via a localnet port.
> </li>
>
> <li>
> - The destination chassis receives the packet via the localnet port
> and
> + The destination chassis receives the packet via a localnet port and
> sends it to the integration bridge. The packet enters the
> ingress pipeline and then egress pipeline of the destination
> localnet
> logical switch and finally delivered to the destination VM port.
> @@ -1935,13 +1934,13 @@
> <ol>
> <li>
> The packet first enters the ingress pipeline, and then egress
> pipeline of
> - the source localnet logical switch datapath and is sent out via the
> + the source localnet logical switch datapath and is sent out via a
> localnet port of the source localnet logical switch (instead of
> sending
> it to router pipeline).
> </li>
>
> <li>
> - The gateway chassis receives the packet via the localnet port of the
> + The gateway chassis receives the packet via a localnet port of the
> source localnet logical switch and sends it to the integration
> bridge.
> The packet then enters the ingress pipeline, and then egress
> pipeline of
> the source localnet logical switch datapath and enters the ingress
> @@ -1957,7 +1956,7 @@
> egress pipeline of the localnet logical switch datapath which
> provides
> external connectivity. It then goes out of the integration bridge
> to the
> provider bridge (belonging to the logical switch which provides
> external
> - connectivity) via the localnet port.
> + connectivity) via a localnet port.
> </li>
> </ol>
>
> @@ -1967,7 +1966,7 @@
>
> <ol>
> <li>
> - The gateway chassis receives the packet from the localnet port of
> + The gateway chassis receives the packet from a localnet port of
> the logical switch which provides external connectivity. The packet
> then
> enters the ingress pipeline and then egress pipeline of the localnet
> logical switch (which provides external connectivity). The packet
> then
> @@ -1978,12 +1977,12 @@
> The ingress pipeline of the logical router datapath applies the
> unNATting
> rules. The packet then enters the ingress pipeline and then egress
> pipeline of the source localnet logical switch. Since the source VM
> - doesn't reside in the gateway chassis, the packet is sent out via
> the
> + doesn't reside in the gateway chassis, the packet is sent out via a
> localnet port of the source logical switch.
> </li>
>
> <li>
> - The source chassis receives the packet via the localnet port and
> + The source chassis receives the packet via a localnet port and
> sends it to the integration bridge. The packet enters the
> ingress pipeline and then egress pipeline of the source localnet
> logical switch and finally gets delivered to the source VM port.
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 541ec20c1..6f84c427c 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -244,14 +244,14 @@
> <p>
> There are two kinds of logical switches, that is, ones that fully
> virtualize the network (overlay logical switches) and ones that
> provide
> - simple connectivity to a physical network (bridged logical
> switches).
> + simple connectivity to physical networks (bridged logical switches).
> They work in the same way when providing connectivity between
> logical
> - ports on same chasis, but differently when connecting remote logical
> + ports on same chassis, but differently when connecting remote
> logical
> ports. Overlay logical switches connect remote logical ports by
> tunnels,
> while bridged logical switches provide connectivity to remote ports
> by
> - bridging the packets to directly connected physical L2 segment with
> the
> + bridging the packets to directly connected physical L2 segments
> with the
> help of <code>localnet</code> ports. Each bridged logical switch
> has
> - one and only one <code>localnet</code> port, which has only one
> special
> + one or more <code>localnet</code> ports, which have only one special
> address <code>unknown</code>.
> </p>
>
> @@ -527,10 +527,13 @@
>
> <dt><code>localnet</code></dt>
> <dd>
> - A connection to a locally accessible network from each
> - <code>ovn-controller</code> instance. A logical switch can
> only
> - have a single <code>localnet</code> port attached. This is
> used
> - to model direct connectivity to an existing network.
> + A connection to a locally accessible network from
> + <code>ovn-controller</code> instances that have corresponding
> + bridge mapping. A logical switch can have multiple
> + <code>localnet</code> ports attached, as long as each
> + <code>ovn-controller</code> is plugged to a single local
> network
> + only. In this case, each hypervisor implements part of switch
> + external network connectivity.
> </dd>
>
> <dt><code>localport</code></dt>
> @@ -721,7 +724,7 @@
> Required. The name of the network to which the
> <code>localnet</code>
> port is connected. Each hypervisor, via
> <code>ovn-controller</code>,
> uses its local configuration to determine exactly how to
> connect to
> - this locally accessible network.
> + this locally accessible network, if at all.
> </column>
> </group>
>
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 3ae9d4f92..2d2d08027 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2606,10 +2606,13 @@ tcp.flags = RST;
>
> <dt><code>localnet</code></dt>
> <dd>
> - A connection to a locally accessible network from each
> - <code>ovn-controller</code> instance. A logical switch can
> only
> - have a single <code>localnet</code> port attached. This is
> used
> - to model direct connectivity to an existing network.
> + A connection to a locally accessible network from some or all
> + <code>ovn-controller</code> instances. This is used
> + to model direct connectivity to existing networks. A logical
> + switch can have multiple <code>localnet</code> ports
> attached, as
> + long as each <code>ovn-controller</code> is plugged to a
> single
> + local network only. In this case, each hypervisor implements
> part
> + of switch external network connectivity.
> </dd>
>
> <dt><code>localport</code></dt>
> @@ -2754,10 +2757,12 @@ tcp.flags = RST;
> <p>
> When a logical switch has a <code>localnet</code> port attached,
> every chassis that may have a local vif attached to that logical
> - switch must have a bridge mapping configured to reach that
> - <code>localnet</code>. Traffic that arrives on a
> - <code>localnet</code> port is never forwarded over a tunnel to
> - another chassis.
> + switch that needs this external connectivity must have a bridge
> + mapping configured to reach that <code>localnet</code>. If the
> + mapping is missing, the vif won't be plugged to this network.
> It may
> + still reach the other network if routing is implemented by
> fabric.
> + Traffic that arrives on a <code>localnet</code> port is never
> + forwarded over a tunnel to another chassis.
> </p>
> </column>
>
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 013583826..42089763a 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -2438,6 +2438,118 @@ OVN_CLEANUP([hv1],[hv2])
>
> AT_CLEANUP
>
> +AT_SETUP([ovn -- 2 HVs, multiple localnet ports])
> +ovn_start
> +
> +# In this test case we create a single switch connected to two physical
> +# networks via multiple localnet ports. Then we create two hypervisors,
> with 2
> +# ports on each. Each pair of adjecent ports belong to the same network
> segment
> +# and assume interconnectivity. There is no direct interconnectivity
> between
> +# ports located on chassis attached to different segments. (It is assumed
> that
> +# in real life external fabric L3 routing will deliver packets between the
> +# segments as needed.)
> +ovn-nbctl ls-add ls1
> +for tag in 10 20; do
> + ln_port_name=ln-$tag
> + ovn-nbctl lsp-add ls1 $ln_port_name "" $tag
> + ovn-nbctl lsp-set-addresses $ln_port_name unknown
> + ovn-nbctl lsp-set-type $ln_port_name localnet
> + ovn-nbctl lsp-set-options $ln_port_name network_name=phys-$tag
> +done
> +
> +for tag in 10 20; do
> + net_add n-$tag
> +done
> +
> +for tag in 10 20; do
> + for i in 1 2; do
> + sim_add hv-$tag-$i
> + as hv-$tag-$i
> + ovs-vsctl add-br br-phys
> + ovs-vsctl set open .
> external-ids:ovn-bridge-mappings=phys-$tag:br-phys
> + ovn_attach n-$tag br-phys 192.168.$i.$tag
> +
> + ovs-vsctl add-port br-int vif-$tag-$i -- \
> + set Interface vif-$tag-$i external-ids:iface-id=lp-$tag-$i \
> +
> options:tx_pcap=hv-$tag-$i/vif-$tag-$i-tx.pcap \
> +
> options:rxq_pcap=hv-$tag-$i/vif-$tag-$i-rx.pcap \
> + ofport-request=$tag$i
> +
> + lsp_name=lp-$tag-$i
> + ovn-nbctl lsp-add ls1 $lsp_name
> + ovn-nbctl lsp-set-addresses $lsp_name f0:00:00:00:0$i:$tag
> + ovn-nbctl lsp-set-port-security $lsp_name f0:00:00:00:0$i:$tag
> +
> + OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up $lsp_name` = xup])
> + done
> +done
> +ovn-nbctl --wait=sb sync
> +ovn-sbctl dump-flows
> +
> +for tag in 10 20; do
> + for i in 1 2; do
> + : > $tag-$i.expected
> + done
> +done
> +
> +vif_to_hv() {
> + echo hv-$1
> +}
> +
> +test_packet() {
> + local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6
> +
> + # First try tracing the packet.
> + uflow="inport==\"lp-$inport\" && eth.dst==$dst && eth.src==$src &&
> eth.type==0x$eth"
> + echo "output(\"$lout\");" > expout
> + AT_CAPTURE_FILE([trace])
> + AT_CHECK([ovn-trace --all ls1 "$uflow" | tee trace | sed '1,/Minimal
> trace/d'], [0], [expout])
> +
> + # Then actually send a packet, for an end-to-end test.
> + local packet=$(echo $dst$src | sed 's/://g')${eth}
> + hv=`vif_to_hv $inport`
> + vif=vif-$inport
> + as $hv ovs-appctl netdev-dummy/receive $vif $packet
> + if test $eth = 1002 -o $eth = 2002; then
> + echo $packet >> ${eout#lp-}.expected
> + fi
> +}
> +
> +# should fail
> +test_packet 10-1 f0:00:00:00:01:20 f0:00:00:00:01:10 1001 lp-20-1 lp-20-1
> +test_packet 20-1 f0:00:00:00:01:10 f0:00:00:00:01:20 2001 lp-10-1 lp-10-1
> +
> +# should pass
> +test_packet 10-1 f0:00:00:00:02:10 f0:00:00:00:01:10 1002 lp-10-2 lp-10-2
> +test_packet 20-1 f0:00:00:00:02:20 f0:00:00:00:01:20 2002 lp-20-2 lp-20-2
> +
> +# Dump a bunch of info helpful for debugging if there's a failure.
> +
> +echo "------ OVN dump ------"
> +ovn-nbctl show
> +ovn-sbctl show
> +
> +for tag in 10 20; do
> + for i in 1 2; do
> + hv=hv-$tag-$i
> + echo "------ $hv dump ------"
> + as $hv ovs-vsctl show
> + as $hv ovs-ofctl -O OpenFlow13 dump-flows br-int
> + done
> +done
> +
> +# Now check the packets actually received against the ones expected.
> +for tag in 10 20; do
> + for i in 1 2; do
> + echo "hv = $tag-$i"
> +
> OVN_CHECK_PACKETS_REMOVE_BROADCAST([hv-$tag-$i/vif-$tag-$i-tx.pcap],
> [$tag-$i.expected])
> + done
> +done
> +
> +OVN_CLEANUP([hv-10-1],[hv-10-2],[hv-20-1],[hv-20-2])
> +
> +AT_CLEANUP
> +
> AT_SETUP([ovn -- vtep: 3 HVs, 1 VIFs/HV, 1 GW, 1 LS])
> AT_KEYWORDS([vtep])
> ovn_start
> --
> 2.25.2
>
>
> _______________________________________________
> dev mailing list
> dev at openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
>
--
Best regards,
Maciej Józefczyk
More information about the dev
mailing list