[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