@@ -588,6 +588,19 @@ ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
&mcast_info->group_tnlid_hint);
}
+struct ovn_datapath_l3dgw_port {
+
+ /* OVN northd only needs to know about the logical router gateway port for
+ * NAT on a distributed router. This "distributed gateway port" is
+ * populated only when there is a gateway chassis specified for one of
+ * the ports on the logical router. Otherwise this will be NULL. */
+ struct ovn_port *dgw_port;
+
+ /* The "derived" OVN port representing the instance of l3dgw_port on
+ * the gateway chassis. */
+ struct ovn_port *redirect_port;
+};
+
/* The 'key' comes from nbs->header_.uuid or nbr->header_.uuid or
* sb->external_ids:logical-switch. */
struct ovn_datapath {
@@ -617,14 +630,9 @@ struct ovn_datapath {
/* Multicast data. */
struct mcast_info mcast_info;
- /* OVN northd only needs to know about the logical router gateway port for
- * NAT on a distributed router. This "distributed gateway port" is
- * populated only when there is a gateway chassis specified for one of
- * the ports on the logical router. Otherwise this will be NULL. */
- struct ovn_port *l3dgw_port;
- /* The "derived" OVN port representing the instance of l3dgw_port on
- * the gateway chassis. */
- struct ovn_port *l3redirect_port;
+ /* L3 distributed gateway ports */
+ struct ovn_datapath_l3dgw_port *l3dgw_ports;
+ size_t n_l3dgw_ports;
/* NAT entries configured on the router. */
struct ovn_nat *nat_entries;
@@ -843,6 +851,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->l3dgw_ports);
destroy_nat_entries(od);
free(od->nat_entries);
free(od->localnet_ports);
@@ -1490,6 +1499,94 @@ struct ovn_port {
struct ovs_list list; /* In list of similar records. */
};
+/* Get the l3dgw port corresponding to a logical router port.*/
+static inline struct ovn_datapath_l3dgw_port*
+ovn_get_l3dgw_port_from_lrp(const struct ovn_port *op)
+{
+ struct ovn_datapath *od = op->od;
+
+ if (!op || !op->nbrp) {
+ return NULL;
+ }
+
+ for (int iter = 0; iter < od->n_l3dgw_ports; iter++) {
+ struct ovn_datapath_l3dgw_port* l3dgw_port = &(od->l3dgw_ports[iter]);
+ if (op == l3dgw_port->dgw_port) {
+ return l3dgw_port;
+ }
+ }
+
+ return NULL;
+}
+
+/* Get the l3dgw port corresponding to a logical router port
+ * with input ip */
+static struct ovn_datapath_l3dgw_port*
+ovn_get_l3dgw_port_from_ip(struct ovn_datapath *od, char *ip_s, bool is_v6)
+{
+ ovs_be32 ip4, mask4;
+ struct in6_addr ip6, mask6;
+
+ char *error = NULL;
+
+ if (!od || !od->nbr) {
+ return NULL;
+ }
+
+ if (is_v6) {
+ error = ipv6_parse_masked(ip_s, &ip6, &mask6);
+ } else {
+ error = ip_parse_masked(ip_s, &ip4, &mask4);
+ ip4 = ntohl(ip4);
+ }
+
+ if (error) {
+ free(error);
+ return NULL;
+ }
+
+ for (int iter = 0; iter < od->n_l3dgw_ports; iter++) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ &(od->l3dgw_ports[iter]);
+ struct ovn_port *op = l3dgw_port->dgw_port;
+ struct lport_addresses lrp_networks;
+
+ if (!extract_lrp_networks(op->nbrp, &lrp_networks)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+ VLOG_WARN_RL(&rl, "Extract addresses failed.");
+ continue;
+ }
+
+ if (is_v6) {
+ for (int iter2 = 0; iter2 < lrp_networks.n_ipv6_addrs; iter2++) {
+ struct ipv6_netaddr *lrp6_addr =
+ &(lrp_networks.ipv6_addrs[iter2]);
+ struct in6_addr ip6_mask = ipv6_addr_bitand(&lrp6_addr->mask,
+ &ip6);
+
+ if (ipv6_addr_equals(&ip6_mask, &(lrp6_addr->network))) {
+ return l3dgw_port;
+ }
+ }
+ } else {
+ for (int iter2 = 0; iter2 < lrp_networks.n_ipv4_addrs; iter2++) {
+ struct ipv4_netaddr *lrp4_addr =
+ &(lrp_networks.ipv4_addrs[iter2]);
+ ovs_be32 addr = ntohl(lrp4_addr->addr);
+ ovs_be32 network = ntohl(lrp4_addr->network);
+ mask4 = ntohl(lrp4_addr->mask);
+ ovs_be32 bcast = addr | ~mask4;
+
+ if (ip4 >= network && ip4 < bcast) {
+ return l3dgw_port;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
static void
ovn_port_set_nb(struct ovn_port *op,
const struct nbrec_logical_switch_port *nbsp,
@@ -2478,13 +2575,12 @@ join_logical_ports(struct northd_context *ctx,
"on L3 gateway router", nbrp->name);
continue;
}
- if (od->l3dgw_port || od->l3redirect_port) {
+ if (od->n_l3dgw_ports) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(1, 1);
- VLOG_WARN_RL(&rl, "Bad configuration: multiple "
- "distributed gateway ports on logical "
- "router %s", od->nbr->name);
- continue;
+ VLOG_DBG_RL(&rl, "Multiple ports with "
+ "redirect-chassis on same "
+ "logical router %s", od->nbr->name);
}
char *redirect_name =
@@ -2506,8 +2602,12 @@ join_logical_ports(struct northd_context *ctx,
/* Set l3dgw_port and l3redirect_port in od, for later
* use during flow creation. */
- od->l3dgw_port = op;
- od->l3redirect_port = crp;
+ od->l3dgw_ports = xrealloc(od->l3dgw_ports,
+ sizeof *od->l3dgw_ports *
+ (od->n_l3dgw_ports + 1));
+ (od->l3dgw_ports[od->n_l3dgw_ports]).dgw_port = op;
+ (od->l3dgw_ports[od->n_l3dgw_ports]).redirect_port = crp;
+ od->n_l3dgw_ports++;
}
}
}
@@ -2663,7 +2763,7 @@ get_nat_addresses(const struct ovn_port *op, size_t *n)
/* Determine whether this NAT rule satisfies the conditions for
* distributed NAT processing. */
- if (op->od->l3redirect_port && !strcmp(nat->type, "dnat_and_snat")
+ if (op->od->n_l3dgw_ports && !strcmp(nat->type, "dnat_and_snat")
&& nat->logical_port && nat->external_mac) {
/* Distributed NAT rule. */
if (eth_addr_from_string(nat->external_mac, &mac)) {
@@ -2723,11 +2823,13 @@ get_nat_addresses(const struct ovn_port *op, size_t *n)
sset_destroy(&all_ips_v6);
if (central_ip_address) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
/* Gratuitous ARP for centralized NAT rules on distributed gateway
* ports should be restricted to the gateway chassis. */
- if (op->od->l3redirect_port) {
+ if (l3dgw_port) {
ds_put_format(&c_addresses, " is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
addresses[n_nats++] = ds_steal_cstr(&c_addresses);
@@ -3220,7 +3322,7 @@ ovn_port_update_sbrec(struct northd_context *ctx,
char **nats = NULL;
if (nat_addresses && !strcmp(nat_addresses, "router")) {
if (op->peer && op->peer->od
- && (chassis || op->peer->od->l3redirect_port)) {
+ && (chassis || op->peer->od->n_l3dgw_ports)) {
nats = get_nat_addresses(op->peer, &n_nats);
}
/* Only accept manual specification of ethernet address
@@ -3256,11 +3358,11 @@ ovn_port_update_sbrec(struct northd_context *ctx,
* sending the GARPs for the router port IPs.
* */
bool add_router_port_garp = false;
- if (op->peer && op->peer->nbrp && op->peer->od->l3dgw_port &&
- op->peer->od->l3redirect_port &&
+ struct ovn_datapath_l3dgw_port *l3dgw_port = NULL;
+ if (op->peer && op->peer->nbrp && op->peer->od->n_l3dgw_ports &&
(smap_get_bool(&op->peer->nbrp->options,
"reside-on-redirect-chassis", false) ||
- op->peer == op->peer->od->l3dgw_port)) {
+ (l3dgw_port = ovn_get_l3dgw_port_from_lrp(op->peer)))) {
add_router_port_garp = true;
} else if (chassis && op->od->n_localnet_ports) {
add_router_port_garp = true;
@@ -3275,9 +3377,12 @@ ovn_port_update_sbrec(struct northd_context *ctx,
op->peer->lrp_networks.ipv4_addrs[i].addr_s);
}
- if (op->peer->od->l3redirect_port) {
+ if (op->peer->od->n_l3dgw_ports) {
+ if (!l3dgw_port) {
+ l3dgw_port = &(op->peer->od->l3dgw_ports[0]);
+ }
ds_put_format(&garp_info, " is_chassis_resident(%s)",
- op->peer->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
n_nats++;
@@ -4869,7 +4974,6 @@ build_lswitch_input_port_sec_op(
struct ovn_port *op, struct hmap *lflows,
struct ds *actions, struct ds *match)
{
-
if (!op->nbsp) {
return;
}
@@ -6169,13 +6273,15 @@ build_lrouter_groups__(struct hmap *ports, struct ovn_datapath *od)
{
ovs_assert((od && od->nbr && od->lr_group));
- if (od->l3dgw_port && od->l3redirect_port) {
+ for (int i = 0; i < od->n_l3dgw_ports; i++) {
+ struct ovn_datapath_l3dgw_port* l3dgw_port = &(od->l3dgw_ports[i]);
+
/* It's a logical router with gateway port. If it
* has HA_Chassis_Group associated to it in SB DB, then store the
* ha chassis group name. */
- if (od->l3redirect_port->sb->ha_chassis_group) {
+ if (l3dgw_port->redirect_port->sb->ha_chassis_group) {
sset_add(&od->lr_group->ha_chassis_groups,
- od->l3redirect_port->sb->ha_chassis_group->name);
+ l3dgw_port->redirect_port->sb->ha_chassis_group->name);
}
}
@@ -7414,17 +7520,18 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op,
ds_clear(match);
ds_put_format(match, "eth.dst == "ETH_ADDR_FMT,
ETH_ADDR_ARGS(mac));
- if (op->peer->od->l3dgw_port
- && op->peer->od->l3redirect_port
- && op->od->n_localnet_ports) {
+ if (op->peer->od->n_l3dgw_ports &&
+ op->od->n_localnet_ports) {
bool add_chassis_resident_check = false;
- if (op->peer == op->peer->od->l3dgw_port) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op->peer);
+ if (l3dgw_port) {
/* The peer of this port represents a distributed
* gateway port. The destination lookup flow for the
* router's distributed gateway port MAC address should
* only be programmed on the gateway chassis. */
add_chassis_resident_check = true;
- } else {
+ } else if (op->peer->od->n_l3dgw_ports == 1) {
/* Check if the option 'reside-on-redirect-chassis'
* is set to true on the peer port. If set to true
* and if the logical switch has a localnet port, it
@@ -7432,6 +7539,11 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op,
* this logical switch should be run on the chassis
* hosting the gateway port.
*/
+
+ /* reside-on-redirect-chassis is supported only for
+ * logical routers with single l3dgw port.
+ */
+ l3dgw_port = &(op->peer->od->l3dgw_ports[0]);
add_chassis_resident_check = smap_get_bool(
&op->peer->nbrp->options,
"reside-on-redirect-chassis", false);
@@ -7439,7 +7551,7 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op,
if (add_chassis_resident_check) {
ds_put_format(match, " && is_chassis_resident(%s)",
- op->peer->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
}
@@ -7452,8 +7564,7 @@ build_lswitch_ip_unicast_lookup(struct ovn_port *op,
/* Add ethernet addresses specified in NAT rules on
* distributed logical routers. */
- if (op->peer->od->l3dgw_port
- && op->peer == op->peer->od->l3dgw_port) {
+ if (ovn_get_l3dgw_port_from_lrp(op->peer)) {
for (int j = 0; j < op->peer->od->nbr->n_nat; j++) {
const struct nbrec_nat *nat
= op->peer->od->nbr->nat[j];
@@ -8589,34 +8700,44 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
build_empty_lb_event_flow(od, lflows, lb_vip, lb, S_ROUTER_IN_DNAT,
meter_groups);
- /* A match and actions for new connections. */
- char *new_match = xasprintf("ct.new && %s", ds_cstr(match));
- if (lb_force_snat_ip) {
- char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
- ds_cstr(actions));
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
- new_match, new_actions, &lb->header_);
- free(new_actions);
- } else {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
- new_match, ds_cstr(actions), &lb->header_);
- }
+ for (int i = 0; i < od->n_l3dgw_ports; i++) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port = &(od->l3dgw_ports[i]);
+
+ /* A match and actions for new connections. */
+ char *new_match = xasprintf("ct.new && %s && inport == %s && "
+ "is_chassis_resident(%s)", ds_cstr(match),
+ l3dgw_port->dgw_port->json_key,
+ l3dgw_port->redirect_port->json_key);
+ if (lb_force_snat_ip) {
+ char *new_actions = xasprintf("flags.force_snat_for_lb = 1; %s",
+ ds_cstr(actions));
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
+ new_match, new_actions, &lb->header_);
+ free(new_actions);
+ } else {
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
+ new_match, ds_cstr(actions), &lb->header_);
+ }
+
+ /* A match and actions for established connections. */
+ char *est_match = xasprintf("ct.est && %s && inport == %s && "
+ "is_chassis_resident(%s)", ds_cstr(match),
+ l3dgw_port->dgw_port->json_key,
+ l3dgw_port->redirect_port->json_key);
+ if (lb_force_snat_ip) {
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
+ est_match,
+ "flags.force_snat_for_lb = 1; ct_dnat;",
+ &lb->header_);
+ } else {
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
+ est_match, "ct_dnat;", &lb->header_);
+ }
- /* A match and actions for established connections. */
- char *est_match = xasprintf("ct.est && %s", ds_cstr(match));
- if (lb_force_snat_ip) {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
- est_match,
- "flags.force_snat_for_lb = 1; ct_dnat;",
- &lb->header_);
- } else {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_DNAT, priority,
- est_match, "ct_dnat;", &lb->header_);
+ free(new_match);
+ free(est_match);
}
- free(new_match);
- free(est_match);
-
const char *ip_match = NULL;
if (IN6_IS_ADDR_V4MAPPED(&lb_vip->vip)) {
ip_match = "ip4";
@@ -8650,49 +8771,55 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od,
ds_destroy(&unsnat_match);
}
- if (!od->l3dgw_port || !od->l3redirect_port || !lb_vip->n_backends) {
+ if (!od->n_l3dgw_ports || !lb_vip->n_backends) {
return;
}
- /* Add logical flows to UNDNAT the load balanced reverse traffic in
- * the router egress pipleine stage - S_ROUTER_OUT_UNDNAT if the logical
- * router has a gateway router port associated.
- */
- struct ds undnat_match = DS_EMPTY_INITIALIZER;
- ds_put_format(&undnat_match, "%s && (", ip_match);
+ for (int iter = 0; iter < od->n_l3dgw_ports; iter++) {
- for (size_t i = 0; i < lb_vip->n_backends; i++) {
- struct ovn_lb_backend *backend = &lb_vip->backends[i];
- ds_put_format(&undnat_match, "(%s.src == %s", ip_match,
- backend->ip_str);
+ /* Add logical flows to UNDNAT the load balanced reverse traffic in
+ * the router egress pipleine stage - S_ROUTER_OUT_UNDNAT if the logical
+ * router has a gateway router port associated.
+ */
+ struct ds undnat_match = DS_EMPTY_INITIALIZER;
+ ds_put_format(&undnat_match, "%s && (", ip_match);
+
+ struct ovn_datapath_l3dgw_port *l3dgw_port = &(od->l3dgw_ports[iter]);
- if (backend->port) {
- ds_put_format(&undnat_match, " && %s.src == %d) || ",
- proto, backend->port);
+ for (size_t i = 0; i < lb_vip->n_backends; i++) {
+ struct ovn_lb_backend *backend = &lb_vip->backends[i];
+ ds_put_format(&undnat_match, "(%s.src == %s", ip_match,
+ backend->ip_str);
+
+ if (backend->port) {
+ ds_put_format(&undnat_match, " && %s.src == %d) || ",
+ proto, backend->port);
+ } else {
+ ds_put_cstr(&undnat_match, ") || ");
+ }
+ }
+
+ ds_chomp(&undnat_match, ' ');
+ ds_chomp(&undnat_match, '|');
+ ds_chomp(&undnat_match, '|');
+ ds_chomp(&undnat_match, ' ');
+ ds_put_format(&undnat_match, ") && outport == %s && "
+ "is_chassis_resident(%s)",
+ l3dgw_port->dgw_port->json_key,
+ l3dgw_port->redirect_port->json_key);
+ if (lb_force_snat_ip) {
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
+ ds_cstr(&undnat_match),
+ "flags.force_snat_for_lb = 1; ct_dnat;",
+ &lb->header_);
} else {
- ds_put_cstr(&undnat_match, ") || ");
- }
- }
-
- ds_chomp(&undnat_match, ' ');
- ds_chomp(&undnat_match, '|');
- ds_chomp(&undnat_match, '|');
- ds_chomp(&undnat_match, ' ');
- ds_put_format(&undnat_match, ") && outport == %s && "
- "is_chassis_resident(%s)", od->l3dgw_port->json_key,
- od->l3redirect_port->json_key);
- if (lb_force_snat_ip) {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
- ds_cstr(&undnat_match),
- "flags.force_snat_for_lb = 1; ct_dnat;",
- &lb->header_);
- } else {
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
- ds_cstr(&undnat_match), "ct_dnat;",
- &lb->header_);
- }
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_OUT_UNDNAT, 120,
+ ds_cstr(&undnat_match), "ct_dnat;",
+ &lb->header_);
+ }
- ds_destroy(&undnat_match);
+ ds_destroy(&undnat_match);
+ }
}
#define ND_RA_MAX_INTERVAL_MAX 1800
@@ -8814,7 +8941,7 @@ lrouter_nat_add_ext_ip_match(struct ovn_datapath *od,
{
struct nbrec_address_set *allowed_ext_ips = nat->allowed_ext_ips;
struct nbrec_address_set *exempted_ext_ips = nat->exempted_ext_ips;
- bool is_gw_router = !od->l3dgw_port;
+ bool is_gw_router = !od->n_l3dgw_ports;
ovs_assert(allowed_ext_ips || exempted_ext_ips);
@@ -9029,9 +9156,11 @@ build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
* upstream MAC learning points to the gateway chassis.
* Also need to avoid generation of multiple ARP responses
* from different chassis. */
- if (op->od->l3redirect_port) {
+ if (op->od->n_l3dgw_ports) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
ds_put_format(&match, "is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
}
@@ -9218,6 +9347,8 @@ build_adm_ctrl_flows_for_lrouter_port(
struct ovn_port *op, struct hmap *lflows,
struct ds *match, struct ds *actions)
{
+ struct ovn_datapath_l3dgw_port *l3dgw_port = NULL;
+
if (op->nbrp) {
if (!lrport_is_enabled(op->nbrp)) {
/* Drop packets from disabled logical ports (since logical flow
@@ -9248,12 +9379,12 @@ build_adm_ctrl_flows_for_lrouter_port(
ds_clear(match);
ds_put_format(match, "eth.dst == %s && inport == %s",
op->lrp_networks.ea_s, op->json_key);
- if (op->od->l3dgw_port && op == op->od->l3dgw_port
- && op->od->l3redirect_port) {
+ l3dgw_port = ovn_get_l3dgw_port_from_lrp(op);
+ if (l3dgw_port) {
/* Traffic with eth.dst = l3dgw_port->lrp_networks.ea_s
* should only be received on the gateway chassis. */
ds_put_format(match, " && is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
ovn_lflow_add_with_hint(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
ds_cstr(match), ds_cstr(actions),
@@ -9373,6 +9504,8 @@ build_neigh_learning_flows_for_lrouter_port(
/* Check if we need to learn mac-binding from ARP requests. */
for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
if (!learn_from_arp_request) {
/* ARP request to this address should always get learned,
* so add a priority-110 flow to set
@@ -9385,10 +9518,9 @@ build_neigh_learning_flows_for_lrouter_port(
op->lrp_networks.ipv4_addrs[i].network_s,
op->lrp_networks.ipv4_addrs[i].plen,
op->lrp_networks.ipv4_addrs[i].addr_s);
- if (op->od->l3dgw_port && op == op->od->l3dgw_port
- && op->od->l3redirect_port) {
+ if (l3dgw_port) {
ds_put_format(match, " && is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
const char *actions_s = REGBIT_LOOKUP_NEIGHBOR_RESULT
" = lookup_arp(inport, arp.spa, arp.sha); "
@@ -9405,10 +9537,9 @@ build_neigh_learning_flows_for_lrouter_port(
op->json_key,
op->lrp_networks.ipv4_addrs[i].network_s,
op->lrp_networks.ipv4_addrs[i].plen);
- if (op->od->l3dgw_port && op == op->od->l3dgw_port
- && op->od->l3redirect_port) {
+ if (l3dgw_port) {
ds_put_format(match, " && is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
ds_clear(actions);
ds_put_format(actions, REGBIT_LOOKUP_NEIGHBOR_RESULT
@@ -9833,7 +9964,10 @@ build_arp_resolve_flows_for_lrouter_port(
}
}
- if (!op->derived && op->od->l3redirect_port) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
+
+ if (!op->derived && l3dgw_port) {
const char *redirect_type = smap_get(&op->nbrp->options,
"redirect-type");
if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
@@ -9846,7 +9980,7 @@ build_arp_resolve_flows_for_lrouter_port(
ds_clear(match);
ds_put_format(match, "outport == %s && "
"!is_chassis_resident(%s)", op->json_key,
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
ds_clear(actions);
ds_put_format(actions, "eth.dst = %s; next;",
op->lrp_networks.ea_s);
@@ -10157,11 +10291,14 @@ build_check_pkt_len_flows_for_lrouter(
ovn_lflow_add(lflows, od, S_ROUTER_IN_LARGER_PKTS, 0, "1",
"next;");
- if (od->l3dgw_port && od->l3redirect_port) {
+ for (int iter = 0; iter < od->n_l3dgw_ports; iter++) {
int gw_mtu = 0;
- if (od->l3dgw_port->nbrp) {
- gw_mtu = smap_get_int(&od->l3dgw_port->nbrp->options,
- "gateway_mtu", 0);
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ &(od->l3dgw_ports[iter]);
+
+ if (l3dgw_port->dgw_port->nbrp) {
+ gw_mtu = smap_get_int(&(l3dgw_port->dgw_port->nbrp->options),
+ "gateway_mtu", 0);
}
/* Add the flows only if gateway_mtu is configured. */
if (gw_mtu <= 0) {
@@ -10169,20 +10306,20 @@ build_check_pkt_len_flows_for_lrouter(
}
ds_clear(match);
- ds_put_format(match, "outport == %s", od->l3dgw_port->json_key);
-
+ ds_put_format(match, "outport == %s",
+ l3dgw_port->dgw_port->json_key);
ds_clear(actions);
ds_put_format(actions,
REGBIT_PKT_LARGER" = check_pkt_larger(%d);"
" next;", gw_mtu + VLAN_ETH_HEADER_LEN);
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_CHK_PKT_LEN, 50,
ds_cstr(match), ds_cstr(actions),
- &od->l3dgw_port->nbrp->header_);
+ &(l3dgw_port->dgw_port->nbrp->header_));
for (size_t i = 0; i < od->nbr->n_ports; i++) {
struct ovn_port *rp = ovn_port_find(ports,
od->nbr->ports[i]->name);
- if (!rp || rp == od->l3dgw_port) {
+ if (rp == l3dgw_port->dgw_port) {
continue;
}
@@ -10190,7 +10327,8 @@ build_check_pkt_len_flows_for_lrouter(
ds_clear(match);
ds_put_format(match, "inport == %s && outport == %s"
" && ip4 && "REGBIT_PKT_LARGER,
- rp->json_key, od->l3dgw_port->json_key);
+ rp->json_key,
+ l3dgw_port->dgw_port->json_key);
ds_clear(actions);
/* Set icmp4.frag_mtu to gw_mtu */
@@ -10219,7 +10357,8 @@ build_check_pkt_len_flows_for_lrouter(
ds_clear(match);
ds_put_format(match, "inport == %s && outport == %s"
" && ip6 && "REGBIT_PKT_LARGER,
- rp->json_key, od->l3dgw_port->json_key);
+ rp->json_key, l3dgw_port->dgw_port
+ ->json_key);
ds_clear(actions);
/* Set icmp6.frag_mtu to gw_mtu */
@@ -10261,11 +10400,14 @@ build_gateway_redirect_flows_for_lrouter(
struct ds *match, struct ds *actions)
{
if (od->nbr) {
- if (od->l3dgw_port && od->l3redirect_port) {
+ for (int iter = 0; iter < od->n_l3dgw_ports; iter++) {
const struct ovsdb_idl_row *stage_hint = NULL;
- if (od->l3dgw_port->nbrp) {
- stage_hint = &od->l3dgw_port->nbrp->header_;
+ struct ovn_port *l3dgw_port = (od->l3dgw_ports[iter]).dgw_port;
+ struct ovn_port *l3redirect_port =
+ (od->l3dgw_ports[iter]).redirect_port;
+ if (l3dgw_port->nbrp) {
+ stage_hint = &l3dgw_port->nbrp->header_;
}
/* For traffic with outport == l3dgw_port, if the
@@ -10273,13 +10415,12 @@ build_gateway_redirect_flows_for_lrouter(
* rule, then the traffic is redirected to the central
* instance of the l3dgw_port. */
ds_clear(match);
- ds_put_format(match, "outport == %s",
- od->l3dgw_port->json_key);
+ ds_put_format(match, "outport == %s", l3dgw_port->json_key);
ds_clear(actions);
ds_put_format(actions, "outport = %s; next;",
- od->l3redirect_port->json_key);
- ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT, 50,
- ds_cstr(match), ds_cstr(actions),
+ l3redirect_port->json_key);
+ ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_GW_REDIRECT,
+ 50, ds_cstr(match), ds_cstr(actions),
stage_hint);
}
@@ -10510,16 +10651,17 @@ build_ipv6_input_flows_for_lrouter_port(
/* ND reply. These flows reply to ND solicitations for the
* router's own IP address. */
for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
ds_clear(match);
- if (op->od->l3dgw_port && op == op->od->l3dgw_port
- && op->od->l3redirect_port) {
+ if (l3dgw_port) {
/* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
* should only be sent from the gateway chassi, so that
* upstream MAC learning points to the gateway chassis.
* Also need to avoid generation of multiple ND replies
* from different chassis. */
ds_put_format(match, "is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
build_lrouter_nd_flow(op->od, op, "nd_na_router",
@@ -10531,7 +10673,7 @@ build_ipv6_input_flows_for_lrouter_port(
/* UDP/TCP port unreachable */
if (!smap_get(&op->od->nbr->options, "chassis")
- && !op->od->l3dgw_port) {
+ && !op->od->n_l3dgw_ports) {
for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) {
ds_clear(match);
ds_put_format(match,
@@ -10735,10 +10877,12 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
op->lrp_networks.ipv4_addrs[i].network_s,
op->lrp_networks.ipv4_addrs[i].plen);
- if (op->od->l3dgw_port && op->od->l3redirect_port && op->peer
+ if (op->od->n_l3dgw_ports && op->peer
&& op->peer->od->n_localnet_ports) {
bool add_chassis_resident_check = false;
- if (op == op->od->l3dgw_port) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
+ if (l3dgw_port) {
/* Traffic with eth.src = l3dgw_port->lrp_networks.ea_s
* should only be sent from the gateway chassis, so that
* upstream MAC learning points to the gateway chassis.
@@ -10754,6 +10898,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
* hosting the gateway port and it should reply to the
* ARP requests for the router port IPs.
*/
+ l3dgw_port = &(op->od->l3dgw_ports[0]);
add_chassis_resident_check = smap_get_bool(
&op->nbrp->options,
"reside-on-redirect-chassis", false);
@@ -10761,7 +10906,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
if (add_chassis_resident_check) {
ds_put_format(match, " && is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
}
@@ -10779,9 +10924,11 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
const char *ip_address;
SSET_FOR_EACH (ip_address, &all_ips_v4) {
ds_clear(match);
- if (op == op->od->l3dgw_port) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
+ if (l3dgw_port) {
ds_put_format(match, "is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
build_lrouter_arp_flow(op->od, op,
@@ -10791,9 +10938,11 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
SSET_FOR_EACH (ip_address, &all_ips_v6) {
ds_clear(match);
- if (op == op->od->l3dgw_port) {
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ ovn_get_l3dgw_port_from_lrp(op);
+ if (l3dgw_port) {
ds_put_format(match, "is_chassis_resident(%s)",
- op->od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
build_lrouter_nd_flow(op->od, op, "nd_na",
@@ -10805,7 +10954,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
sset_destroy(&all_ips_v6);
if (!smap_get(&op->od->nbr->options, "chassis")
- && !op->od->l3dgw_port) {
+ && !op->od->n_l3dgw_ports) {
/* UDP/TCP port unreachable. */
for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
ds_clear(match);
@@ -10870,7 +11019,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
* exception is on the l3dgw_port where we might need to use a
* different ETH address.
*/
- if (op != op->od->l3dgw_port) {
+ if (!ovn_get_l3dgw_port_from_lrp(op)) {
return;
}
@@ -10935,7 +11084,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
/* NAT rules are only valid on Gateway routers and routers with
* l3dgw_port (router has a port with gateway chassis
* specified). */
- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
+ if (!smap_get(&od->nbr->options, "chassis") && !od->n_l3dgw_ports) {
return;
}
@@ -11022,11 +11171,26 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
}
}
+ struct ovn_datapath_l3dgw_port *l3dgw_port =
+ &((od)->l3dgw_ports[0]);
+ if (od->n_l3dgw_ports) {
+ /* Get the L3DGW port only for distributed router. */
+ l3dgw_port = ovn_get_l3dgw_port_from_ip(od, nat->external_ip,
+ is_v6);
+ if (!l3dgw_port) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "Could not map external ip: %s to a "
+ "gateway port "UUID_FMT"", nat->external_ip,
+ UUID_ARGS(&od->key));
+ continue;
+ }
+ }
+
/* For distributed router NAT, determine whether this NAT rule
* satisfies the conditions for distributed NAT processing. */
bool distributed = false;
struct eth_addr mac;
- if (od->l3dgw_port && !strcmp(nat->type, "dnat_and_snat") &&
+ if (od->n_l3dgw_ports && !strcmp(nat->type, "dnat_and_snat") &&
nat->logical_port && nat->external_mac) {
if (eth_addr_from_string(nat->external_mac, &mac)) {
distributed = true;
@@ -11050,7 +11214,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
* egress pipeline. */
if (!strcmp(nat->type, "snat")
|| !strcmp(nat->type, "dnat_and_snat")) {
- if (!od->l3dgw_port) {
+ if (!od->n_l3dgw_ports) {
/* Gateway router. */
ds_clear(match);
ds_clear(actions);
@@ -11078,12 +11242,12 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
" && inport == %s",
is_v6 ? "6" : "4",
nat->external_ip,
- od->l3dgw_port->json_key);
- if (!distributed && od->l3redirect_port) {
+ l3dgw_port->dgw_port->json_key);
+ if (!distributed && od->n_l3dgw_ports) {
/* Flows for NAT rules that are centralized are only
* programmed on the gateway chassis. */
ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
@@ -11105,7 +11269,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
* to a logical IP address. */
if (!strcmp(nat->type, "dnat")
|| !strcmp(nat->type, "dnat_and_snat")) {
- if (!od->l3dgw_port) {
+ if (!od->n_l3dgw_ports) {
/* Gateway router. */
/* Packet when it goes from the initiator to destination.
* We need to set flags.loopback because the router can
@@ -11155,12 +11319,12 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
" && inport == %s",
is_v6 ? "6" : "4",
nat->external_ip,
- od->l3dgw_port->json_key);
- if (!distributed && od->l3redirect_port) {
+ l3dgw_port->dgw_port->json_key);
+ if (!distributed && od->n_l3dgw_ports) {
/* Flows for NAT rules that are centralized are only
* programmed on the gateway chassis. */
ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
ds_clear(actions);
if (allowed_ext_ips || exempted_ext_ips) {
@@ -11187,12 +11351,12 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
}
/* ARP resolve for NAT IPs. */
- if (od->l3dgw_port) {
+ if (od->n_l3dgw_ports) {
if (!strcmp(nat->type, "snat")) {
ds_clear(match);
ds_put_format(
match, "inport == %s && %s == %s",
- od->l3dgw_port->json_key,
+ l3dgw_port->dgw_port->json_key,
is_v6 ? "ip6.src" : "ip4.src", nat->external_ip);
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_IP_INPUT,
120, ds_cstr(match), "next;",
@@ -11203,14 +11367,14 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
ds_clear(match);
ds_put_format(
match, "outport == %s && %s == %s",
- od->l3dgw_port->json_key,
+ l3dgw_port->dgw_port->json_key,
is_v6 ? REG_NEXT_HOP_IPV6 : REG_NEXT_HOP_IPV4,
nat->external_ip);
ds_clear(actions);
ds_put_format(
actions, "eth.dst = %s; next;",
distributed ? nat->external_mac :
- od->l3dgw_port->lrp_networks.ea_s);
+ l3dgw_port->dgw_port->lrp_networks.ea_s);
ovn_lflow_add_with_hint(lflows, od,
S_ROUTER_IN_ARP_RESOLVE,
100, ds_cstr(match),
@@ -11233,19 +11397,19 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
* Note that this only applies for NAT on a distributed router.
* Undo DNAT on a gateway router is done in the ingress DNAT
* pipeline stage. */
- if (od->l3dgw_port && (!strcmp(nat->type, "dnat")
+ if (od->n_l3dgw_ports && (!strcmp(nat->type, "dnat")
|| !strcmp(nat->type, "dnat_and_snat"))) {
ds_clear(match);
ds_put_format(match, "ip && ip%s.src == %s"
" && outport == %s",
is_v6 ? "6" : "4",
nat->logical_ip,
- od->l3dgw_port->json_key);
- if (!distributed && od->l3redirect_port) {
+ l3dgw_port->dgw_port->json_key);
+ if (!distributed && od->n_l3dgw_ports) {
/* Flows for NAT rules that are centralized are only
* programmed on the gateway chassis. */
ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
ds_clear(actions);
if (distributed) {
@@ -11270,7 +11434,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
* address. */
if (!strcmp(nat->type, "snat")
|| !strcmp(nat->type, "dnat_and_snat")) {
- if (!od->l3dgw_port) {
+ if (!od->n_l3dgw_ports) {
/* Gateway router. */
ds_clear(match);
ds_put_format(match, "ip && ip%s.src == %s",
@@ -11313,13 +11477,13 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
" && outport == %s",
is_v6 ? "6" : "4",
nat->logical_ip,
- od->l3dgw_port->json_key);
- if (!distributed && od->l3redirect_port) {
+ l3dgw_port->dgw_port->json_key);
+ if (!distributed && od->n_l3dgw_ports) {
/* Flows for NAT rules that are centralized are only
* programmed on the gateway chassis. */
priority += 128;
ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
}
ds_clear(actions);
@@ -11368,14 +11532,14 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
*/
ds_clear(actions);
ds_put_format(actions, REG_INPORT_ETH_ADDR " = %s; next;",
- od->l3dgw_port->lrp_networks.ea_s);
+ l3dgw_port->dgw_port->lrp_networks.ea_s);
ds_clear(match);
ds_put_format(match,
"eth.dst == "ETH_ADDR_FMT" && inport == %s"
" && is_chassis_resident(\"%s\")",
ETH_ADDR_ARGS(mac),
- od->l3dgw_port->json_key,
+ l3dgw_port->dgw_port->json_key,
nat->logical_port);
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_ADMISSION, 50,
ds_cstr(match), ds_cstr(actions),
@@ -11398,7 +11562,8 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
"ip%s.src == %s && outport == %s && "
"is_chassis_resident(\"%s\")",
is_v6 ? "6" : "4", nat->logical_ip,
- od->l3dgw_port->json_key, nat->logical_port);
+ l3dgw_port->dgw_port->json_key,
+ nat->logical_port);
ds_put_format(actions, "eth.src = %s; %s = %s; next;",
nat->external_mac,
is_v6 ? REG_SRC_IPV6 : REG_SRC_IPV4,
@@ -11413,16 +11578,16 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
* gateway port have ip.dst matching a NAT external IP, then
* loop a clone of the packet back to the beginning of the
* ingress pipeline with inport = outport. */
- if (od->l3dgw_port) {
+ if (od->n_l3dgw_ports) {
/* Distributed router. */
ds_clear(match);
ds_put_format(match, "ip%s.dst == %s && outport == %s",
is_v6 ? "6" : "4",
nat->external_ip,
- od->l3dgw_port->json_key);
+ l3dgw_port->dgw_port->json_key);
if (!distributed) {
ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3redirect_port->json_key);
+ l3dgw_port->redirect_port->json_key);
} else {
ds_put_format(match, " && is_chassis_resident(\"%s\")",
nat->logical_port);
@@ -11446,7 +11611,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
}
/* Handle force SNAT options set in the gateway router. */
- if (!od->l3dgw_port) {
+ if (!od->n_l3dgw_ports) {
if (dnat_force_snat_ip) {
if (od->dnat_force_snat_addrs.n_ipv4_addrs) {
build_lrouter_force_snat_flows(lflows, od, "4",
@@ -11485,7 +11650,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
/* Load balancing and packet defrag are only valid on
* Gateway routers or router with gateway port. */
- if (!smap_get(&od->nbr->options, "chassis") && !od->l3dgw_port) {
+ if (!smap_get(&od->nbr->options, "chassis") && !od->n_l3dgw_ports) {
sset_destroy(&nat_entries);
return;
}
@@ -11555,11 +11720,6 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath *od,
prio = 120;
}
- if (od->l3redirect_port &&
- (lb_vip->n_backends || !lb_vip->empty_backend_rej)) {
- ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3redirect_port->json_key);
- }
add_router_lb_flow(lflows, od, match, actions, prio,
lb_force_snat_ip, lb_vip, proto,
nb_lb, meter_groups, &nat_entries);
@@ -711,7 +711,7 @@ ovn_start
ovn-sbctl chassis-add gw1 geneve 127.0.0.1
ovn-nbctl lr-add R1
-ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
+ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24 3000::a/64
ovn-nbctl ls-add S1
ovn-nbctl lsp-add S1 S1-R1
@@ -752,13 +752,13 @@ ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1
echo
echo "IPv6: stateful"
-ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
+ovn-nbctl --wait=sb lr-nat-add R1 dnat_and_snat 3000::c 1000::3
check_flow_match_sets 2 2 2 0 0 0 0
-ovn-nbctl lr-nat-del R1 dnat_and_snat fd01::1
+ovn-nbctl lr-nat-del R1 dnat_and_snat 3000::c
echo
echo "IPv6: stateless"
-ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat fd01::1 fd11::2
+ovn-nbctl --wait=sb --stateless lr-nat-add R1 dnat_and_snat 3000::c 1000::3
check_flow_match_sets 2 0 0 0 0 2 2
AT_CLEANUP
@@ -769,7 +769,7 @@ ovn_start
ovn-sbctl chassis-add gw1 geneve 127.0.0.1
ovn-nbctl lr-add R1
-ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24
+ovn-nbctl lrp-add R1 R1-S1 02:ac:10:01:00:01 172.16.1.1/24 3000::a/64
ovn-nbctl ls-add S1
ovn-nbctl lsp-add S1 S1-R1
@@ -2385,3 +2385,346 @@ ovn-nbctl destroy bfd $uuid
check_row_count bfd 2
AT_CLEANUP
+
+AT_SETUP([ovn-northd -- lr multiple gw ports])
+ovn_start
+
+# Logical network:
+# 1 LR, 3 Logical Switches,
+# 1 gateway chassis attached to each corresponding LRP.
+#
+# | S1 (gw1)
+# |
+# ls ---- DR -- S3 (gw3)
+# (20.0.0.0/24) |
+# | S2 (gw2)
+#
+# We will validate basic LR logical flows.
+
+ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+ovn-sbctl chassis-add gw2 geneve 128.0.0.1
+ovn-sbctl chassis-add gw3 geneve 129.0.0.1
+
+ovn-nbctl lr-add DR
+ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+ovn-nbctl lrp-add DR DR-S2 03:ac:10:01:00:01 10.0.0.0/24
+ovn-nbctl lrp-add DR DR-S3 04:ac:10:01:00:01 192.168.0.0/24
+ovn-nbctl lrp-add DR DR-ls 04:ac:10:01:00:01 20.0.0.0/24
+
+ovn-nbctl ls-add S1
+ovn-nbctl lsp-add S1 S1-DR
+ovn-nbctl lsp-set-type S1-DR router
+ovn-nbctl lsp-set-addresses S1-DR router
+ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
+
+ovn-nbctl ls-add S2
+ovn-nbctl lsp-add S2 S2-DR
+ovn-nbctl lsp-set-type S2-DR router
+ovn-nbctl lsp-set-addresses S2-DR router
+ovn-nbctl --wait=sb lsp-set-options S2-DR router-port=DR-S2
+
+ovn-nbctl ls-add S3
+ovn-nbctl lsp-add S3 S3-DR
+ovn-nbctl lsp-set-type S3-DR router
+ovn-nbctl lsp-set-addresses S3-DR router
+ovn-nbctl --wait=sb lsp-set-options S3-DR router-port=DR-S3
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls ls-DR
+ovn-nbctl lsp-set-type ls-DR router
+ovn-nbctl lsp-set-addresses ls-DR router
+ovn-nbctl --wait=sb lsp-set-options ls-DR router-port=DR-ls
+
+ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1
+ovn-nbctl lrp-set-gateway-chassis DR-S2 gw2
+ovn-nbctl lrp-set-gateway-chassis DR-S3 gw3
+
+ovn-nbctl --wait=sb sync
+
+ovn-sbctl dump-flows DR
+
+# Check the flows in lr_in_lookup_neighbor stage
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_lookup_neighbor | grep cr-DR | wc -l], [0], [3
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_lookup_neighbor | grep cr-DR-S1 | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_lookup_neighbor | grep cr-DR-S2 | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_lookup_neighbor | grep cr-DR-S3 | wc -l], [0], [1
+])
+
+# Check the flows in lr_in_gw_redirect stage
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_gw_redirect | grep cr-DR | wc -l], [0], [3
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_gw_redirect | grep cr-DR-S1 | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_gw_redirect | grep cr-DR-S2 | wc -l], [0], [1
+])
+AT_CLEANUP
+
+AT_SETUP([ovn-northd -- lr multiple gw ports NAT])
+ovn_start
+
+# Logical network:
+# 1 LR, 3 Logical Switches,
+# 1 gateway chassis attached to each corresponding LRP.
+#
+# | S1 (gw1)
+# |
+# ls ---- DR -- S3 (gw3)
+# (20.0.0.0/24) |
+# | S2 (gw2)
+#
+# We will validate basic SNAT, DNAT and DNAT_AND_SNAT with
+# multiple distributed gateway LRPs.
+ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+ovn-sbctl chassis-add gw2 geneve 128.0.0.1
+ovn-sbctl chassis-add gw3 geneve 129.0.0.1
+
+ovn-nbctl lr-add DR
+ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+ovn-nbctl lrp-add DR DR-S2 03:ac:10:01:00:01 10.0.0.1/24
+ovn-nbctl lrp-add DR DR-S3 04:ac:10:01:00:01 192.168.0.1/24
+ovn-nbctl lrp-add DR DR-ls 04:ac:10:01:00:01 20.0.0.0/24
+
+ovn-nbctl ls-add S1
+ovn-nbctl lsp-add S1 S1-DR
+ovn-nbctl lsp-set-type S1-DR router
+ovn-nbctl lsp-set-addresses S1-DR router
+ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
+
+ovn-nbctl ls-add S2
+ovn-nbctl lsp-add S2 S2-DR
+ovn-nbctl lsp-set-type S2-DR router
+ovn-nbctl lsp-set-addresses S2-DR router
+ovn-nbctl --wait=sb lsp-set-options S2-DR router-port=DR-S2
+
+ovn-nbctl ls-add S3
+ovn-nbctl lsp-add S3 S3-DR
+ovn-nbctl lsp-set-type S3-DR router
+ovn-nbctl lsp-set-addresses S3-DR router
+ovn-nbctl --wait=sb lsp-set-options S3-DR router-port=DR-S3
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls ls-DR
+ovn-nbctl lsp-set-type ls-DR router
+ovn-nbctl lsp-set-addresses ls-DR router
+ovn-nbctl --wait=sb lsp-set-options ls-DR router-port=DR-ls
+
+ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1
+ovn-nbctl lrp-set-gateway-chassis DR-S2 gw2
+ovn-nbctl lrp-set-gateway-chassis DR-S3 gw3
+
+ovn-nbctl --wait=sb sync
+
+# Configure SNAT
+ovn-nbctl lr-nat-add DR snat 172.16.1.1 20.0.0.10
+ovn-nbctl lr-nat-add DR snat 10.0.0.1 20.0.0.10
+ovn-nbctl lr-nat-add DR snat 192.168.0.1 20.0.0.10
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep ct_snat| wc -l], [0], [3
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep ct_snat| wc -l], [0], [3
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep "ip4.dst == 172.16.1.1" | grep cr-DR-S1 | grep ct_snat | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S1 | grep "ct_snat(172.16.1.1)"| wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep "ip4.dst == 10.0.0.1" | grep cr-DR-S2 | grep ct_snat | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S2 | grep "ct_snat(10.0.0.1)"| wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep "ip4.dst == 192.168.0.1" | grep cr-DR-S3 | grep ct_snat | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S3 | grep "ct_snat(192.168.0.1)"| wc -l], [0], [1
+])
+
+ovn-nbctl lr-nat-del DR snat 20.0.0.10
+ovn-nbctl lr-nat-del DR snat 20.0.0.10
+ovn-nbctl lr-nat-del DR snat 20.0.0.10
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep ct_snat| wc -l], [0], [0
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep ct_snat| wc -l], [0], [0
+])
+
+# Configure DNAT
+ovn-nbctl lr-nat-add DR dnat 172.16.1.10 20.0.0.10
+ovn-nbctl lr-nat-add DR dnat 10.0.0.10 20.0.0.10
+ovn-nbctl lr-nat-add DR dnat 192.168.0.10 20.0.0.10
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct_dnat| wc -l], [0], [3
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep ct_dnat| wc -l], [0], [3
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.10" | grep cr-DR-S1 | grep "ct_dnat(20.0.0.10)" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S1 | grep ct_dnat | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 10.0.0.10" | grep cr-DR-S2 | grep "ct_dnat(20.0.0.10)" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S2 | grep ct_dnat | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 192.168.0.10" | grep cr-DR-S3 | grep "ct_dnat(20.0.0.10)" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S3 | grep ct_dnat | wc -l], [0], [1
+])
+
+ovn-nbctl lr-nat-del DR dnat 172.16.1.10
+ovn-nbctl lr-nat-del DR dnat 10.0.0.10
+ovn-nbctl lr-nat-del DR dnat 192.168.0.10
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct_dnat | wc -l], [0], [0
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep ct_dnat | wc -l], [0], [0
+])
+
+# Configure DNAT_AND_SNAT
+ovn-nbctl lr-nat-add DR dnat_and_snat 172.16.1.10 20.0.0.10
+ovn-nbctl lr-nat-add DR dnat_and_snat 10.0.0.10 20.0.0.10
+ovn-nbctl lr-nat-add DR dnat_and_snat 192.168.0.10 20.0.0.10
+
+ovn-sbctl dump-flows DR
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep ct_snat| wc -l], [0], [3
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep ct_snat| wc -l], [0], [3
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep "ip4.dst == 172.16.1.10" | grep cr-DR-S1 | grep ct_snat | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S1 | grep "ct_snat(172.16.1.10)"| wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep "ip4.dst == 10.0.0.10" | grep cr-DR-S2 | grep ct_snat | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S2 | grep "ct_snat(10.0.0.10)"| wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep "ip4.dst == 192.168.0.10" | grep cr-DR-S3 | grep ct_snat | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S3 | grep "ct_snat(192.168.0.10)"| wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct_dnat| wc -l], [0], [3
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep ct_dnat| wc -l], [0], [3
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 172.16.1.10" | grep cr-DR-S1 | grep "ct_dnat(20.0.0.10)" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S1 | grep ct_dnat | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 10.0.0.10" | grep cr-DR-S2 | grep "ct_dnat(20.0.0.10)" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S2 | grep ct_dnat | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep "ip4.dst == 192.168.0.10" | grep cr-DR-S3 | grep "ct_dnat(20.0.0.10)" | wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep "ip4.src == 20.0.0.10" | grep cr-DR-S3 | grep ct_dnat | wc -l], [0], [1
+])
+
+ovn-nbctl lr-nat-del DR dnat_and_snat 172.16.1.10
+ovn-nbctl lr-nat-del DR dnat_and_snat 10.0.0.10
+ovn-nbctl lr-nat-del DR dnat_and_snat 192.168.0.10
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_unsnat | grep ct_snat| wc -l], [0], [0
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_snat | grep ct_snat| wc -l], [0], [0
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct_dnat | wc -l], [0], [0
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep ct_dnat | wc -l], [0], [0
+])
+AT_CLEANUP
+
+AT_SETUP([ovn-northd -- lr multiple gw ports LB])
+ovn_start
+
+# Logical network:
+# 1 LR, 3 Logical Switches,
+# 1 gateway chassis attached to each corresponding LRP.
+#
+# | S1 (gw1)
+# |
+# ls ---- DR -- S3 (gw3)
+# (20.0.0.0/24) |
+# | S2 (gw2)
+#
+# We will validate LB with multiple distributed gateway LRPs.
+ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+ovn-sbctl chassis-add gw2 geneve 128.0.0.1
+ovn-sbctl chassis-add gw3 geneve 129.0.0.1
+
+ovn-nbctl lr-add DR
+ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+ovn-nbctl lrp-add DR DR-S2 03:ac:10:01:00:01 10.0.0.1/24
+ovn-nbctl lrp-add DR DR-S3 04:ac:10:01:00:01 192.168.0.1/24
+ovn-nbctl lrp-add DR DR-ls 04:ac:10:01:00:01 20.0.0.0/24
+
+ovn-nbctl ls-add S1
+ovn-nbctl lsp-add S1 S1-DR
+ovn-nbctl lsp-set-type S1-DR router
+ovn-nbctl lsp-set-addresses S1-DR router
+ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
+
+ovn-nbctl ls-add S2
+ovn-nbctl lsp-add S2 S2-DR
+ovn-nbctl lsp-set-type S2-DR router
+ovn-nbctl lsp-set-addresses S2-DR router
+ovn-nbctl --wait=sb lsp-set-options S2-DR router-port=DR-S2
+
+ovn-nbctl ls-add S3
+ovn-nbctl lsp-add S3 S3-DR
+ovn-nbctl lsp-set-type S3-DR router
+ovn-nbctl lsp-set-addresses S3-DR router
+ovn-nbctl --wait=sb lsp-set-options S3-DR router-port=DR-S3
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls ls-DR
+ovn-nbctl lsp-set-type ls-DR router
+ovn-nbctl lsp-set-addresses ls-DR router
+ovn-nbctl --wait=sb lsp-set-options ls-DR router-port=DR-ls
+
+ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1
+ovn-nbctl lrp-set-gateway-chassis DR-S2 gw2
+ovn-nbctl lrp-set-gateway-chassis DR-S3 gw3
+
+ovn-nbctl --wait=sb sync
+
+ovn-nbctl lb-add lb0 192.168.0.3:80 10.0.0.2:80,10.0.0.3:80
+ovn-nbctl lr-lb-add DR lb0
+
+ovn-sbctl dump-flows DR
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.est| wc -l], [0], [3
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.new| wc -l], [0], [3
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.est| grep DR-S1 | grep cr-DR-S1| wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.new| grep DR-S1| grep cr-DR-S1 | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.est| grep DR-S2 | grep cr-DR-S2| wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.new| grep DR-S2| grep cr-DR-S2 | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.est| grep DR-S3 | grep cr-DR-S3| wc -l], [0], [1
+])
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_in_dnat | grep ct.new| grep DR-S3| grep cr-DR-S3 | wc -l], [0], [1
+])
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep lr_out_undnat | grep priority=120| wc -l], [0], [3
+])
+
+AT_CLEANUP
@@ -19568,7 +19568,7 @@ AT_CAPTURE_FILE([sbflows2])
OVS_WAIT_FOR_OUTPUT(
[ovn-sbctl dump-flows > sbflows2
ovn-sbctl dump-flows lr0 | grep ct_lb | grep priority=120 | sed 's/table=..//'], 0,
- [ (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
+ [ (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_lb(backends=10.0.0.3:80,20.0.0.3:80; hash_fields="ip_dst,ip_src,tcp_dst,tcp_src");)
])
# get the svc monitor mac.
@@ -19609,8 +19609,8 @@ AT_CHECK(
AT_CAPTURE_FILE([sbflows4])
ovn-sbctl dump-flows lr0 > sbflows4
AT_CHECK([grep lr_in_dnat sbflows4 | grep priority=120 | sed 's/table=..//' | sort], [0], [dnl
- (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
- (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && is_chassis_resident("cr-lr0-public")), action=(drop;)
+ (lr_in_dnat ), priority=120 , match=(ct.est && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(ct_dnat;)
+ (lr_in_dnat ), priority=120 , match=(ct.new && ip && ip4.dst == 10.0.0.10 && tcp && tcp.dst == 80 && inport == "lr0-public" && is_chassis_resident("cr-lr0-public")), action=(drop;)
])
# Delete sw0-p1
@@ -23560,3 +23560,307 @@ as ovn-nb
OVS_APP_EXIT_AND_WAIT([ovsdb-server])
AT_CLEANUP
+
+AT_SETUP([ovn -- lr multiple gw ports])
+ovn_start
+
+# Logical network:
+# 1 LR, 3 Logical Switches,
+# 1 gateway chassis attached to each corresponding LRP.
+#
+# | S1 (gw1)
+# |
+# ls ---- DR -- S3 (gw3)
+# (20.0.0.0/24) |
+# | S2 (gw2)
+#
+# S1 - VLAN 1000
+# S2 - VLAN 2000
+# S3 - VLAN 3000
+#
+# 5 chassis(s), HV1----HV5
+#
+# HV1 - VIF11
+# HV2 - Gateway chassis gw1
+# HV3 - Gateway chassis gw2
+# HV4 - Gateway chassis gw3
+# HV5 - North endpoint
+
+ovn-nbctl lr-add DR
+ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+ovn-nbctl lrp-add DR DR-S2 08:ac:10:01:00:01 10.0.0.1/24
+ovn-nbctl lrp-add DR DR-S3 04:ac:10:01:00:01 192.168.0.1/24
+ovn-nbctl lrp-add DR DR-ls 06:ac:10:01:00:01 20.0.0.0/24
+
+ovn-nbctl ls-add S1
+ovn-nbctl lsp-add S1 S1-DR
+ovn-nbctl lsp-set-type S1-DR router
+ovn-nbctl lsp-set-addresses S1-DR router
+ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
+ovn-nbctl lsp-add S1 ln1 "" 1000
+ovn-nbctl lsp-set-addresses ln1 unknown
+ovn-nbctl lsp-set-type ln1 localnet
+ovn-nbctl lsp-set-options ln1 network_name=phys
+
+ovn-nbctl ls-add S2
+ovn-nbctl lsp-add S2 S2-DR
+ovn-nbctl lsp-set-type S2-DR router
+ovn-nbctl lsp-set-addresses S2-DR router
+ovn-nbctl --wait=sb lsp-set-options S2-DR router-port=DR-S2
+ovn-nbctl lsp-add S2 ln2 "" 2000
+ovn-nbctl lsp-set-addresses ln2 unknown
+ovn-nbctl lsp-set-type ln2 localnet
+ovn-nbctl lsp-set-options ln2 network_name=phys
+
+ovn-nbctl ls-add S3
+ovn-nbctl lsp-add S3 S3-DR
+ovn-nbctl lsp-set-type S3-DR router
+ovn-nbctl lsp-set-addresses S3-DR router
+ovn-nbctl --wait=sb lsp-set-options S3-DR router-port=DR-S3
+ovn-nbctl lsp-add S3 ln3 "" 3000
+ovn-nbctl lsp-set-addresses ln3 unknown
+ovn-nbctl lsp-set-type ln3 localnet
+ovn-nbctl lsp-set-options ln3 network_name=phys
+
+ovn-nbctl ls-add ls
+ovn-nbctl lsp-add ls ls-DR
+ovn-nbctl lsp-set-type ls-DR router
+ovn-nbctl lsp-set-addresses ls-DR router
+ovn-nbctl --wait=sb lsp-set-options ls-DR router-port=DR-ls
+
+# Add the lsp lp11 to ls. This will map to VIF11.
+ovn-nbctl lsp-add ls lp11
+ovn-nbctl lsp-set-addresses lp11 "f0:00:00:00:00:10 20.0.0.10"
+ovn-nbctl lsp-set-port-security lp11 f0:00:00:00:00:10
+
+# Add the Northbound endpoint, lp-north1
+ovn-nbctl ls-add ls-north1
+ovn-nbctl lsp-add ls-north1 ln4 "" 1000
+ovn-nbctl lsp-set-addresses ln4 unknown
+ovn-nbctl lsp-set-type ln4 localnet
+ovn-nbctl lsp-set-options ln4 network_name=phys
+
+ovn-nbctl lsp-add ls-north1 lp-north1
+ovn-nbctl lsp-set-addresses lp-north1 "f0:f0:00:00:00:11 172.16.1.10"
+ovn-nbctl lsp-set-port-security lp-north1 f0:f0:00:00:00:11
+
+# Add the Northbound endpoint, lp-north2
+ovn-nbctl ls-add ls-north2
+ovn-nbctl lsp-add ls-north2 ln5 "" 2000
+ovn-nbctl lsp-set-addresses ln5 unknown
+ovn-nbctl lsp-set-type ln5 localnet
+ovn-nbctl lsp-set-options ln5 network_name=phys
+
+ovn-nbctl lsp-add ls-north2 lp-north2
+ovn-nbctl lsp-set-addresses lp-north2 "f0:f0:00:00:00:22 10.0.0.10"
+ovn-nbctl lsp-set-port-security lp-north2 f0:f0:00:00:00:22
+
+# Add the Northbound endpoint, lp-north3
+ovn-nbctl ls-add ls-north3
+ovn-nbctl lsp-add ls-north3 ln6 "" 3000
+ovn-nbctl lsp-set-addresses ln6 unknown
+ovn-nbctl lsp-set-type ln6 localnet
+ovn-nbctl lsp-set-options ln6 network_name=phys
+
+ovn-nbctl lsp-add ls-north3 lp-north3
+ovn-nbctl lsp-set-addresses lp-north3 "f0:f0:00:00:00:33 192.168.0.10"
+ovn-nbctl lsp-set-port-security lp-north3 f0:f0:00:00:00:33
+
+# Add 5 chassis
+net_add n1
+for i in 1 2 3 4 5; do
+ sim_add hv$i
+ as hv$i
+ ovs-vsctl add-br br-phys
+ ovs-vsctl set open . external-ids:ovn-bridge-mappings=phys:br-phys
+ ovn_attach n1 br-phys 192.168.0.$i 24 $encap
+done
+
+# Add a vif on HV1
+as hv1 ovs-vsctl add-port br-int vif11 -- \
+ set Interface vif11 external-ids:iface-id=lp11 \
+ options:tx_pcap=hv1/vif11-tx.pcap \
+ options:rxq_pcap=hv1/vif11-rx.pcap \
+ ofport-request=11
+OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp11` = xup])
+
+as hv5 ovs-vsctl add-port br-int vif-north1 -- \
+ set Interface vif-north1 external-ids:iface-id=lp-north1 \
+ options:tx_pcap=hv5/vif-north1-tx.pcap \
+ options:rxq_pcap=hv5/vif-north1-rx.pcap \
+ ofport-request=44
+
+as hv5 ovs-vsctl add-port br-int vif-north2 -- \
+ set Interface vif-north2 external-ids:iface-id=lp-north2 \
+ options:tx_pcap=hv5/vif-north2-tx.pcap \
+ options:rxq_pcap=hv5/vif-north2-rx.pcap \
+ ofport-request=45
+
+as hv5 ovs-vsctl add-port br-int vif-north3 -- \
+ set Interface vif-north3 external-ids:iface-id=lp-north3 \
+ options:tx_pcap=hv5/vif-north3-tx.pcap \
+ options:rxq_pcap=hv5/vif-north3-rx.pcap \
+ ofport-request=46
+
+ovn-nbctl lrp-set-gateway-chassis DR-S1 hv2
+ovn-nbctl lrp-set-gateway-chassis DR-S2 hv3
+ovn-nbctl lrp-set-gateway-chassis DR-S3 hv4
+
+ovn-nbctl --wait=sb sync
+OVN_POPULATE_ARP
+
+vif_to_ls () {
+ case ${1} in dnl (
+ vif?[[11]]) echo ls ;; dnl (
+ vif-north1) echo ls-north1 ;; dnl (
+ vif-north2) echo ls-north2 ;; dnl (
+ vif-north3) echo ls-north3 ;; dnl (
+ *) AT_FAIL_IF([:]) ;;
+ esac
+}
+
+vif_to_hv () {
+ case ${1} in dnl (
+ vif[[1]]?) echo hv1 ;; dnl (
+ vif-north1) echo hv5 ;; dnl (
+ vif-north2) echo hv5 ;; dnl (
+ vif-north3) echo hv5 ;; dnl (
+ *) AT_FAIL_IF([:]) ;;
+ esac
+}
+
+vif_to_lrp () {
+ case ${1} in dnl (
+ vif?[[11]]) echo DR-ls ;; dnl (
+ *) AT_FAIL_IF([:]) ;;
+ esac
+
+}
+
+ip_to_hex() {
+ printf "%02x%02x%02x%02x" "${@}"
+}
+
+# test_arp INPORT SHA SPA TPA
+#
+# Causes a packet to be received on INPORT. The packet is an ARP
+# request with SHA, SPA, and TPA as specified.
+test_arp() {
+ local inport=$1 sha=$2 spa=$3 tpa=$4
+ local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa}
+ hv=`vif_to_hv $inport`
+ as $hv ovs-appctl netdev-dummy/receive $inport $request
+}
+
+
+test_ip() {
+ # This packet has bad checksums but logical L3 routing doesn't check.
+ local inport=${1} src_mac=${2} dst_mac=${3} src_ip=${4} dst_ip=${5} outport=${6}
+ local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000
+ shift; shift; shift; shift; shift
+ hv=`vif_to_hv $inport`
+ as $hv ovs-appctl netdev-dummy/receive $inport $packet
+ in_ls=`vif_to_ls $inport`
+ for outport; do
+ out_ls=`vif_to_ls $outport`
+ if test $in_ls = $out_ls; then
+ # Ports on the same logical switch receive exactly the same packet.
+ echo $packet
+ else
+ # Routing decrements TTL and updates source and dest MAC
+ # (and checksum).
+ # For North-South, packet will come via gateway chassis, i.e hv3
+ if test $inport = vif-north1; then
+ echo f0000000001006ac1001000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 >> $outport.expected
+ fi
+ if test $outport = vif-north1; then
+ echo f0f00000001102ac1001000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 >> $outport.expected
+ fi
+ if test $outport = vif-north2; then
+ echo f0f00000002208ac1001000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 >> $outport.expected
+ fi
+ if test $outport = vif-north3; then
+ echo f0f00000003304ac1001000108004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 >> $outport.expected
+ fi
+ fi >> $outport.expected
+ done
+}
+
+echo "------ OVN dump ------"
+ovn-nbctl show
+ovn-sbctl show
+ovn-sbctl list port_binding
+ovn-sbctl list mac_binding
+ovn-sbctl list datapath_binding
+
+ovn-sbctl dump-flows DR
+ovn-sbctl dump-flows S1
+ovn-sbctl dump-flows ls
+
+echo "------ hv1 dump ------"
+as hv1 ovs-vsctl show
+as hv1 ovs-vsctl list Open_Vswitch
+as hv1 ovs-ofctl dump-flows br-int
+
+echo "------ hv2 dump ------"
+as hv2 ovs-vsctl show
+as hv2 ovs-vsctl list Open_Vswitch
+as hv2 ovs-ofctl dump-flows br-int
+
+echo "------ hv3 dump ------"
+as hv3 ovs-vsctl show
+as hv3 ovs-vsctl list Open_Vswitch
+as hv3 ovs-ofctl dump-flows br-int
+
+echo "------ hv4 dump ------"
+as hv4 ovs-vsctl show
+as hv4 ovs-vsctl list Open_Vswitch
+as hv5 ovs-ofctl dump-flows br-int
+
+# N-S with lp-north1
+echo "Send Dummy ARP"
+sip=`ip_to_hex 172 16 1 10`
+tip=`ip_to_hex 172 16 1 50`
+test_arp vif-north1 f0f000000011 $sip $tip
+
+echo "Send traffic North to South"
+sip=`ip_to_hex 172 16 1 10`
+dip=`ip_to_hex 20 0 0 10`
+test_ip vif-north1 f0f000000011 02ac10010001 $sip $dip vif11
+# Confirm that North to south traffic works fine.
+OVN_CHECK_PACKETS([hv1/vif11-tx.pcap], [vif11.expected])
+
+echo "Send traffic South to North"
+sip=`ip_to_hex 20 0 0 10`
+dip=`ip_to_hex 172 16 1 10`
+test_ip vif11 f00000000010 06ac10010001 $sip $dip vif-north1
+# Confirm that South to North traffic works fine.
+OVN_CHECK_PACKETS([hv5/vif-north1-tx.pcap], [vif-north1.expected])
+
+# N-S with lp-north2
+echo "Send Dummy ARP"
+sip=`ip_to_hex 10 0 0 10`
+tip=`ip_to_hex 10 0 0 50`
+test_arp vif-north2 f0f000000022 $sip $tip
+
+echo "Send traffic South to North"
+sip=`ip_to_hex 20 0 0 10`
+dip=`ip_to_hex 10 0 0 10`
+test_ip vif11 f00000000010 06ac10010001 $sip $dip vif-north2
+# Confirm that South to North traffic works fine.
+OVN_CHECK_PACKETS([hv5/vif-north2-tx.pcap], [vif-north2.expected])
+
+# N-S with lp-north3
+echo "Send Dummy ARP"
+sip=`ip_to_hex 192 168 0 10`
+tip=`ip_to_hex 192 168 0 50`
+test_arp vif-north3 f0f000000033 $sip $tip
+
+echo "Send traffic South to North"
+sip=`ip_to_hex 20 0 0 10`
+dip=`ip_to_hex 192 168 0 10`
+test_ip vif11 f00000000010 06ac10010001 $sip $dip vif-north3
+# Confirm that South to North traffic works fine.
+OVN_CHECK_PACKETS([hv5/vif-north3-tx.pcap], [vif-north3.expected])
+
+AT_CLEANUP
@@ -4208,6 +4208,30 @@ done:
return ret;
}
+static bool
+is_nat_rule_conflict(const struct nbrec_logical_router *lr,
+ const char *logical_ip1,
+ const char *logical_ip2,
+ bool is_v6)
+{
+ int num_l3dgw_ports = 0;
+
+ /* TODO: Add a proper validation to confirm that multiple
+ * external ips for a logical ip do not belong to same router port. */
+ for (size_t i = 0; i < lr->n_ports; i++) {
+ const struct nbrec_logical_router_port *lrp = lr->ports[i];
+ if (lrp->n_gateway_chassis) {
+ num_l3dgw_ports++;
+ }
+ }
+
+ if (num_l3dgw_ports > 1) {
+ return false;
+ }
+
+ return true;
+}
+
static void
nbctl_lr_nat_add(struct ctl_context *ctx)
{
@@ -4369,12 +4393,15 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
should_return = true;
}
} else {
- ctl_error(ctx, "a NAT with this type (%s) and %s (%s) "
- "already exists",
- nat_type,
- is_snat ? "logical_ip" : "external_ip",
- is_snat ? new_logical_ip : new_external_ip);
- should_return = true;
+ if (is_nat_rule_conflict(lr, old_logical_ip, logical_ip,
+ is_v6)) {
+ ctl_error(ctx, "a NAT with this type (%s) and %s (%s) "
+ "already exists",
+ nat_type,
+ is_snat ? "logical_ip" : "external_ip",
+ is_snat ? new_logical_ip : new_external_ip);
+ should_return = true;
+ }
}
}
}