Currently, if multiple distributed gateway ports (DGP) are configured
on a logical router, NAT is disabled as part of commit 15348b7
(northd: Multiple distributed gateway port support.)
This patch adds a new column called "gateway_port" in NAT table that
references a distributed gateway port in the Logical_Router_Port
table. A NAT rule is only applied on matching packets entering or
leaving the DGP configured for the rule, when a router has
multiple DGPs.
If a router has a single DGP, NAT rules are applied at the DGP even if
the "gateway_port" column is not set. It is an error to not set this
column for a NAT rule when the router has multiple DGPs.
This patch also updates the NAT commands in ovn-nbctl to support the
new column.
Signed-off-by: Abhiram Sangana <sangana.abhi...@nutanix.com>
---
NEWS | 1 +
northd/northd.c | 182 +++++++++++++++++++++++---------------
northd/ovn-northd.8.xml | 27 +++---
ovn-architecture.7.xml | 6 +-
ovn-nb.ovsschema | 10 ++-
ovn-nb.xml | 37 +++++++-
tests/ovn-nbctl.at | 172 +++++++++++++++++++++--------------
tests/ovn-northd.at | 166 +++++++++++++++++++++++++++++++++-
tests/ovn.at | 2 +-
utilities/ovn-nbctl.8.xml | 32 ++++---
utilities/ovn-nbctl.c | 151 ++++++++++++++++++++++++++-----
11 files changed, 592 insertions(+), 194 deletions(-)
diff --git a/NEWS b/NEWS
index 9648f6cb2..764b2a03e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,6 @@
Post v22.03.0
-------------
+ - Support NAT with multiple distributed gateway ports on a logical router.
OVN v22.03.0 - XX XXX XXXX
--------------------------
diff --git a/northd/northd.c b/northd/northd.c
index 294a59bd7..351f41134 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -604,11 +604,11 @@ struct ovn_datapath {
/* Applies to only logical router datapath.
* True if logical router is a gateway router. i.e options:chassis is set.
- * If this is true, then 'l3dgw_port' will be ignored. */
+ * If this is true, then 'l3dgw_ports' will be ignored. */
bool is_gw_router;
- /* OVN northd only needs to know about the logical router gateway port for
- * NAT on a distributed router. The "distributed gateway ports" are
+ /* OVN northd only needs to know about logical router gateway ports for
+ * NAT/LB on a distributed router. The "distributed gateway ports" are
* populated only when there is a gateway chassis or ha chassis group
* specified for some of the ports on the logical router. Otherwise this
* will be NULL. */
@@ -761,16 +761,6 @@ init_nat_entries(struct ovn_datapath *od)
return;
}
- if (od->n_l3dgw_ports > 1) {
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
- VLOG_WARN_RL(&rl, "NAT is configured on logical router %s, which has %"
- PRIuSIZE" distributed gateway ports. NAT is not supported"
- " yet when there is more than one distributed gateway "
- "port on the router.",
- od->nbr->name, od->n_l3dgw_ports);
- return;
- }
-
od->nat_entries = xmalloc(od->nbr->n_nat * sizeof *od->nat_entries);
for (size_t i = 0; i < od->nbr->n_nat; i++) {
@@ -2704,8 +2694,9 @@ join_logical_ports(struct northd_input *input_data,
* by one or more IP addresses, and if the port is a distributed gateway
* port, followed by 'is_chassis_resident("LPORT_NAME")', where the
* LPORT_NAME is the name of the L3 redirect port or the name of the
- * logical_port specified in a NAT rule. These strings include the
- * external IP addresses of all NAT rules defined on that router, and all
+ * logical_port specified in a NAT rule. These strings include the
+ * external IP addresses of NAT rules defined on that router which have
+ * gateway_port not set or have gateway_port as the router port 'op', and all
* of the IP addresses used in load balancer VIPs defined on that router.
*
* The caller must free each of the n returned strings with free(),
@@ -2718,8 +2709,7 @@ get_nat_addresses(const struct ovn_port *op, size_t *n,
bool routable_only,
struct eth_addr mac;
if (!op || !op->nbrp || !op->od || !op->od->nbr
|| (!op->od->nbr->n_nat && !op->od->has_lb_vip)
- || !eth_addr_from_string(op->nbrp->mac, &mac)
- || op->od->n_l3dgw_ports > 1) {
+ || !eth_addr_from_string(op->nbrp->mac, &mac)) {
*n = n_nats;
return NULL;
}
@@ -2748,6 +2738,12 @@ get_nat_addresses(const struct ovn_port *op, size_t *n,
bool routable_only,
continue;
}
+ /* Not including external IP of NAT rules whose gateway_port is
+ * not 'op'. */
+ if (nat->gateway_port && nat->gateway_port != op->nbrp) {
+ continue;
+ }
+
/* Determine whether this NAT rule satisfies the conditions for
* distributed NAT processing. */
if (op->od->n_l3dgw_ports && !strcmp(nat->type, "dnat_and_snat")
@@ -2818,9 +2814,9 @@ get_nat_addresses(const struct ovn_port *op, size_t *n,
bool routable_only,
if (central_ip_address) {
/* Gratuitous ARP for centralized NAT rules on distributed gateway
* ports should be restricted to the gateway chassis. */
- if (op->od->n_l3dgw_ports) {
+ if (is_l3dgw_port(op)) {
ds_put_format(&c_addresses, " is_chassis_resident(%s)",
- op->od->l3dgw_ports[0]->cr_port->json_key);
+ op->cr_port->json_key);
}
addresses[n_nats++] = ds_steal_cstr(&c_addresses);
@@ -3453,9 +3449,12 @@ ovn_port_update_sbrec(struct northd_input *input_data,
}
if (op->peer->od->n_l3dgw_ports) {
+ const struct ovn_port *l3dgw_port = (
+ is_l3dgw_port(op->peer)
+ ? op->peer
+ : op->peer->od->l3dgw_ports[0]);
ds_put_format(&garp_info, " is_chassis_resident(%s)",
- op->peer->od->l3dgw_ports[0]
- ->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
}
n_nats++;
@@ -10278,6 +10277,12 @@ build_lrouter_port_nat_arp_nd_flow(struct ovn_port *op,
const struct nbrec_nat *nat = nat_entry->nb;
struct ds match = DS_EMPTY_INITIALIZER;
+ /* ARP/ND should be sent from distributed gateway port specified in
+ * the NAT rule. */
+ if (nat->gateway_port && nat->gateway_port != op->nbrp) {
+ return;
+ }
+
/* Mac address to use when replying to ARP/NS. */
const char *mac_s = REG_INPORT_ETH_ADDR;
struct eth_addr mac;
@@ -10301,10 +10306,9 @@ 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->n_l3dgw_ports) {
- ds_put_format(&match, "is_chassis_resident(%s)",
- op->od->l3dgw_ports[0]->cr_port->json_key);
- }
+ ovs_assert(is_l3dgw_port(op));
+ ds_put_format(&match, "is_chassis_resident(%s)",
+ op->cr_port->json_key);
}
/* Respond to ARP/NS requests on the chassis that binds the gw
@@ -12007,7 +12011,7 @@ build_ipv6_input_flows_for_lrouter_port(
struct ds *match, struct ds *actions,
const struct shash *meter_groups)
{
- if (op->nbrp && (!op->l3dgw_port)) {
+ if (op->nbrp && !is_cr_port(op)) {
/* No ingress packets are accepted on a chassisredirect
* port, so no need to program flows for that port. */
if (op->lrp_networks.n_ipv6_addrs) {
@@ -12136,7 +12140,7 @@ build_ipv6_input_flows_for_lrouter_port(
ds_clear(match);
ds_clear(actions);
ds_clear(&ip_ds);
- if (op->od->n_l3dgw_ports && op->od->l3dgw_ports[0] == op) {
+ if (is_l3dgw_port(op)) {
ds_put_cstr(&ip_ds, "ip6.dst <-> ip6.src");
} else {
ds_put_format(&ip_ds, "ip6.dst = ip6.src; ip6.src = %s",
@@ -12226,7 +12230,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
{
/* No ingress packets are accepted on a chassisredirect
* port, so no need to program flows for that port. */
- if (op->nbrp && (!op->l3dgw_port)) {
+ if (op->nbrp && !is_cr_port(op)) {
if (op->lrp_networks.n_ipv4_addrs) {
/* L3 admission control: drop packets that originate from an
* IPv4 address owned by the router or a broadcast address
@@ -12268,7 +12272,7 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
ds_clear(match);
ds_clear(actions);
ds_clear(&ip_ds);
- if (op->od->n_l3dgw_ports && op->od->l3dgw_ports[0] == op) {
+ if (is_l3dgw_port(op)) {
ds_put_cstr(&ip_ds, "ip4.dst <-> ip4.src");
} else {
ds_put_format(&ip_ds, "ip4.dst = ip4.src; ip4.src = %s",
@@ -12513,7 +12517,8 @@ build_lrouter_ipv4_ip_input(struct ovn_port *op,
static void
build_lrouter_in_unsnat_flow(struct hmap *lflows, struct ovn_datapath *od,
const struct nbrec_nat *nat, struct ds *match,
- struct ds *actions, bool distributed, bool is_v6)
+ struct ds *actions, bool distributed, bool is_v6,
+ struct ovn_port *l3dgw_port)
{
/* Ingress UNSNAT table: It is for already established connections'
* reverse traffic. i.e., SNAT has already been done in egress
@@ -12552,12 +12557,12 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows,
struct ovn_datapath *od,
ds_clear(actions);
ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
"flags.loopback == 0", is_v6 ? "6" : "4",
- nat->external_ip, od->l3dgw_ports[0]->json_key);
+ nat->external_ip, l3dgw_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->l3dgw_ports[0]->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
}
if (!strcmp(nat->type, "dnat_and_snat") && stateless) {
@@ -12577,12 +12582,12 @@ build_lrouter_in_unsnat_flow(struct hmap *lflows,
struct ovn_datapath *od,
ds_put_format(match, "ip && ip%s.dst == %s && inport == %s && "
"flags.loopback == 1 && flags.use_snat_zone == 1",
is_v6 ? "6" : "4", nat->external_ip,
- od->l3dgw_ports[0]->json_key);
+ l3dgw_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->l3dgw_ports[0]->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
}
ds_put_cstr(actions, "ct_snat;");
ovn_lflow_add_with_hint(lflows, od, S_ROUTER_IN_UNSNAT,
@@ -12596,7 +12601,8 @@ static void
build_lrouter_in_dnat_flow(struct hmap *lflows, struct ovn_datapath *od,
const struct nbrec_nat *nat, struct ds *match,
struct ds *actions, bool distributed,
- ovs_be32 mask, bool is_v6)
+ ovs_be32 mask, bool is_v6,
+ struct ovn_port *l3dgw_port)
{
/* Ingress DNAT table: Packets enter the pipeline with destination
* IP address that needs to be DNATted from a external IP address
@@ -12648,12 +12654,12 @@ build_lrouter_in_dnat_flow(struct hmap *lflows,
struct ovn_datapath *od,
ds_clear(match);
ds_put_format(match, "ip && ip%s.dst == %s && inport == %s",
is_v6 ? "6" : "4", nat->external_ip,
- od->l3dgw_ports[0]->json_key);
+ l3dgw_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->l3dgw_ports[0]->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
}
ds_clear(actions);
if (nat->allowed_ext_ips || nat->exempted_ext_ips) {
@@ -12683,7 +12689,8 @@ static void
build_lrouter_out_undnat_flow(struct hmap *lflows, struct ovn_datapath *od,
const struct nbrec_nat *nat, struct ds *match,
struct ds *actions, bool distributed,
- struct eth_addr mac, bool is_v6)
+ struct eth_addr mac, bool is_v6,
+ struct ovn_port *l3dgw_port)
{
/* Egress UNDNAT table: It is for already established connections'
* reverse traffic. i.e., DNAT has already been done in ingress
@@ -12700,12 +12707,12 @@ build_lrouter_out_undnat_flow(struct hmap *lflows,
struct ovn_datapath *od,
ds_clear(match);
ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
is_v6 ? "6" : "4", nat->logical_ip,
- od->l3dgw_ports[0]->json_key);
+ l3dgw_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->l3dgw_ports[0]->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
}
ds_clear(actions);
if (distributed) {
@@ -12731,7 +12738,7 @@ static void
build_lrouter_out_is_dnat_local(struct hmap *lflows, struct ovn_datapath *od,
const struct nbrec_nat *nat, struct ds *match,
struct ds *actions, bool distributed,
- bool is_v6)
+ bool is_v6, struct ovn_port *l3dgw_port)
{
/* Note that this only applies for NAT on a distributed router.
*/
@@ -12746,7 +12753,7 @@ build_lrouter_out_is_dnat_local(struct hmap *lflows,
struct ovn_datapath *od,
ds_put_format(match, "is_chassis_resident(\"%s\")",
nat->logical_port);
} else {
ds_put_format(match, "is_chassis_resident(%s)",
- od->l3dgw_ports[0]->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
}
ds_clear(actions);
@@ -12762,7 +12769,8 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct
ovn_datapath *od,
const struct nbrec_nat *nat, struct ds *match,
struct ds *actions, bool distributed,
struct eth_addr mac, ovs_be32 mask,
- int cidr_bits, bool is_v6)
+ int cidr_bits, bool is_v6,
+ struct ovn_port *l3dgw_port)
{
/* Egress SNAT table: Packets enter the egress pipeline with
* source ip address that needs to be SNATted to a external ip
@@ -12809,7 +12817,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct
ovn_datapath *od,
ds_clear(match);
ds_put_format(match, "ip && ip%s.src == %s && outport == %s",
is_v6 ? "6" : "4", nat->logical_ip,
- od->l3dgw_ports[0]->json_key);
+ l3dgw_port->json_key);
if (od->n_l3dgw_ports) {
if (distributed) {
ovs_assert(nat->logical_port);
@@ -12821,7 +12829,7 @@ build_lrouter_out_snat_flow(struct hmap *lflows, struct
ovn_datapath *od,
* programmed on the gateway chassis. */
priority += 128;
ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3dgw_ports[0]->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
}
}
ds_clear(actions);
@@ -12880,13 +12888,13 @@ build_lrouter_ingress_nat_check_pkt_len(struct hmap
*lflows,
const struct nbrec_nat *nat,
struct ovn_datapath *od, bool is_v6,
struct ds *match, struct ds *actions,
- int mtu,
+ int mtu, struct ovn_port *l3dgw_port,
const struct shash *meter_groups)
{
ds_clear(match);
ds_put_format(match, "inport == %s && "REGBIT_PKT_LARGER
" && "REGBIT_EGRESS_LOOPBACK" == 0",
- od->l3dgw_ports[0]->json_key);
+ l3dgw_port->json_key);
ds_clear(actions);
if (!is_v6) {
@@ -12907,7 +12915,7 @@ build_lrouter_ingress_nat_check_pkt_len(struct hmap
*lflows,
"outport = %s; flags.loopback = 1; output; };",
nat->external_mac,
nat->external_ip,
- mtu, od->l3dgw_ports[0]->json_key);
+ mtu, l3dgw_port->json_key);
ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_IP_INPUT, 160,
ds_cstr(match), ds_cstr(actions),
NULL,
@@ -12934,7 +12942,7 @@ build_lrouter_ingress_nat_check_pkt_len(struct hmap
*lflows,
"outport = %s; flags.loopback = 1; output; };",
nat->external_mac,
nat->external_ip,
- mtu, od->l3dgw_ports[0]->json_key);
+ mtu, l3dgw_port->json_key);
ovn_lflow_add_with_hint__(lflows, od, S_ROUTER_IN_IP_INPUT, 160,
ds_cstr(match), ds_cstr(actions),
NULL,
@@ -12951,13 +12959,14 @@ build_lrouter_ingress_flow(struct hmap *lflows,
struct ovn_datapath *od,
const struct nbrec_nat *nat, struct ds *match,
struct ds *actions, struct eth_addr mac,
bool distributed, bool is_v6,
+ struct ovn_port *l3dgw_port,
const struct shash *meter_groups)
{
if (od->n_l3dgw_ports && !strcmp(nat->type, "snat")) {
ds_clear(match);
ds_put_format(
match, "inport == %s && %s == %s",
- od->l3dgw_ports[0]->json_key,
+ l3dgw_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;",
@@ -12973,32 +12982,33 @@ build_lrouter_ingress_flow(struct hmap *lflows,
struct ovn_datapath *od,
* This will save us from having to match on inport further
* down in the pipeline.
*/
- int gw_mtu = smap_get_int(&od->l3dgw_ports[0]->nbrp->options,
+ int gw_mtu = smap_get_int(&l3dgw_port->nbrp->options,
"gateway_mtu", 0);
ds_clear(match);
ds_put_format(match,
"eth.dst == "ETH_ADDR_FMT" && inport == %s"
" && is_chassis_resident(\"%s\")",
ETH_ADDR_ARGS(mac),
- od->l3dgw_ports[0]->json_key,
+ l3dgw_port->json_key,
nat->logical_port);
- build_gateway_mtu_flow(lflows, od->l3dgw_ports[0],
+ build_gateway_mtu_flow(lflows, l3dgw_port,
S_ROUTER_IN_ADMISSION, 50, 55,
match, actions, &nat->header_,
REG_INPORT_ETH_ADDR " = %s; next;",
- od->l3dgw_ports[0]->lrp_networks.ea_s);
+ l3dgw_port->lrp_networks.ea_s);
if (gw_mtu) {
build_lrouter_ingress_nat_check_pkt_len(lflows, nat, od, is_v6,
match, actions, gw_mtu,
- meter_groups);
+ l3dgw_port, meter_groups);
}
}
}
static int
lrouter_check_nat_entry(struct ovn_datapath *od, const struct nbrec_nat *nat,
- ovs_be32 *mask, bool *is_v6, int *cidr_bits,
- struct eth_addr *mac, bool *distributed)
+ const struct hmap *ports, ovs_be32 *mask,
+ bool *is_v6, int *cidr_bits, struct eth_addr *mac,
+ bool *distributed, struct ovn_port **nat_l3dgw_port)
{
struct in6_addr ipv6, mask_v6, v6_exact = IN6ADDR_EXACT_INIT;
ovs_be32 ip;
@@ -13032,6 +13042,32 @@ lrouter_check_nat_entry(struct ovn_datapath *od, const
struct nbrec_nat *nat,
*is_v6 = true;
}
+ /* Validate gateway_port of NAT rule. */
+ *nat_l3dgw_port = NULL;
+ if (nat->gateway_port == NULL) {
+ if (od->n_l3dgw_ports > 1) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "NAT configured on logical router: %s with"
+ "multiple distributed gateway ports needs to specify"
+ "valid gateway_port.", od->nbr->name);
+ return -EINVAL;
+ } else if (od->n_l3dgw_ports) {
+ *nat_l3dgw_port = od->l3dgw_ports[0];
+ }
+ } else {
+ *nat_l3dgw_port = ovn_port_find(ports, nat->gateway_port->name);
+
+ if (!(*nat_l3dgw_port) || (*nat_l3dgw_port)->od != od ||
+ !is_l3dgw_port(*nat_l3dgw_port)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "gateway_port: %s of NAT configured on "
+ "logical router: %s is not a valid distributed "
+ "gateway port on that router",
+ nat->gateway_port->name, od->nbr->name);
+ return -EINVAL;
+ }
+ }
+
/* Check the validity of nat->logical_ip. 'logical_ip' can
* be a subnet when the type is "snat". */
if (*is_v6) {
@@ -13131,7 +13167,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath
*od, struct hmap *lflows,
ovn_lflow_add(lflows, od, S_ROUTER_OUT_SNAT, 120, "nd_ns", "next;");
/* NAT rules are only valid on Gateway routers and routers with
- * l3dgw_port (router has a port with gateway chassis
+ * l3dgw_ports (router has port(s) with gateway chassis
* specified). */
if (!od->is_gw_router && !od->n_l3dgw_ports) {
return;
@@ -13150,18 +13186,19 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath
*od, struct hmap *lflows,
bool is_v6, distributed;
ovs_be32 mask;
int cidr_bits;
+ struct ovn_port *l3dgw_port;
- if (lrouter_check_nat_entry(od, nat, &mask, &is_v6, &cidr_bits,
- &mac, &distributed) < 0) {
+ if (lrouter_check_nat_entry(od, nat, ports, &mask, &is_v6, &cidr_bits,
+ &mac, &distributed, &l3dgw_port) < 0) {
continue;
}
/* S_ROUTER_IN_UNSNAT */
build_lrouter_in_unsnat_flow(lflows, od, nat, match, actions,
distributed,
- is_v6);
+ is_v6, l3dgw_port);
/* S_ROUTER_IN_DNAT */
build_lrouter_in_dnat_flow(lflows, od, nat, match, actions,
distributed,
- mask, is_v6);
+ mask, is_v6, l3dgw_port);
/* ARP resolve for NAT IPs. */
if (od->is_gw_router) {
@@ -13174,14 +13211,14 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath
*od, struct hmap *lflows,
ds_clear(match);
ds_put_format(
match, "outport == %s && %s == %s",
- od->l3dgw_ports[0]->json_key,
+ l3dgw_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_ports[0]->lrp_networks.ea_s);
+ l3dgw_port->lrp_networks.ea_s);
ovn_lflow_add_with_hint(lflows, od,
S_ROUTER_IN_ARP_RESOLVE,
100, ds_cstr(match),
@@ -13193,18 +13230,19 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath
*od, struct hmap *lflows,
/* S_ROUTER_OUT_DNAT_LOCAL */
build_lrouter_out_is_dnat_local(lflows, od, nat, match, actions,
- distributed, is_v6);
+ distributed, is_v6, l3dgw_port);
/* S_ROUTER_OUT_UNDNAT */
build_lrouter_out_undnat_flow(lflows, od, nat, match, actions,
distributed,
- mac, is_v6);
+ mac, is_v6, l3dgw_port);
/* S_ROUTER_OUT_SNAT */
build_lrouter_out_snat_flow(lflows, od, nat, match, actions,
distributed,
- mac, mask, cidr_bits, is_v6);
+ mac, mask, cidr_bits, is_v6, l3dgw_port);
/* S_ROUTER_IN_ADMISSION - S_ROUTER_IN_IP_INPUT */
- build_lrouter_ingress_flow(lflows, od, nat, match, actions,
- mac, distributed, is_v6, meter_groups);
+ build_lrouter_ingress_flow(lflows, od, nat, match, actions, mac,
+ distributed, is_v6, l3dgw_port,
+ meter_groups);
/* Ingress Gateway Redirect Table: For NAT on a distributed
* router, add flows that are specific to a NAT rule. These
@@ -13221,7 +13259,7 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath
*od, struct hmap *lflows,
ds_put_format(match,
"ip%s.src == %s && outport == %s",
is_v6 ? "6" : "4", nat->logical_ip,
- od->l3dgw_ports[0]->json_key);
+ l3dgw_port->json_key);
/* Add a rule to drop traffic from a distributed NAT if
* the virtual port has not claimed yet becaused otherwise
* the traffic will be centralized misconfiguring the TOR switch.
@@ -13254,10 +13292,10 @@ build_lrouter_nat_defrag_and_lb(struct ovn_datapath
*od, struct hmap *lflows,
ds_put_format(match, "ip%s.dst == %s && outport == %s",
is_v6 ? "6" : "4",
nat->external_ip,
- od->l3dgw_ports[0]->json_key);
+ l3dgw_port->json_key);
if (!distributed) {
ds_put_format(match, " && is_chassis_resident(%s)",
- od->l3dgw_ports[0]->cr_port->json_key);
+ l3dgw_port->cr_port->json_key);
} else {
ds_put_format(match, " && is_chassis_resident(\"%s\")",
nat->logical_port);
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index e495db46a..6adec765b 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -2943,7 +2943,8 @@ icmp6 {
<code>ip &&
ip6.dst == <var>B</var> && inport == <var>GW</var>
&& flags.loopback == 0</code>
- where <var>GW</var> is the logical router gateway port, with an
+ where <var>GW</var> is the distributed gateway port
+ specified in the NAT rule, with an
action <code>ct_snat_in_czone;</code> to unSNAT in the common
zone. If the NAT rule is of type dnat_and_snat and has
<code>stateless=true</code> in the options, then the action
@@ -2968,7 +2969,8 @@ icmp6 {
ip6.dst == <var>B</var> && inport == <var>GW</var>
&& flags.loopback == 0 &&
flags.use_snat_zone == 1</code>
- where <var>GW</var> is the logical router gateway port, with an
+ where <var>GW</var> is the distributed gateway port
+ specified in the NAT rule, with an
action <code>ct_snat;</code> to unSNAT in the snat zone. If the
NAT rule is of type dnat_and_snat and has
<code>stateless=true</code> in the options, then the action
@@ -3249,9 +3251,10 @@ icmp6 {
to change the destination IP address of a packet from <var>A</var>
to
<var>B</var>, a priority-100 flow matches <code>ip &&
ip4.dst == <var>B</var> && inport == <var>GW</var></code>,
- where <var>GW</var> is the logical router gateway port, with an
- action <code>ct_dnat(<var>B</var>);</code>. The match will
- include <code>ip6.dst == <var>B</var></code> in the IPv6 case.
+ where <var>GW</var> is the logical router gateway port configured
+ for the NAT rule, with an action
+ <code>ct_dnat(<var>B</var>);</code>. The match will include
+ <code>ip6.dst == <var>B</var></code> in the IPv6 case.
If the NAT rule is of type dnat_and_snat and has
<code>stateless=true</code> in the options, then the action
would be <code>ip4/6.dst=(<var>B</var>)</code>.
@@ -4061,10 +4064,11 @@ icmp6 {
flow with match <code>ip4.src == <var>B</var> &&
outport == <var>GW</var></code> &&
is_chassis_resident(<var>P</var>), where <var>GW</var> is
- the logical router distributed gateway port and <var>P</var>
- is the NAT logical port. IP traffic matching the above rule
- will be managed locally setting <code>reg1</code> to <var>C</var>
- and <code>eth.src</code> to <var>D</var>, where <var>C</var> is NAT
+ the distributed gateway port specified in the
+ NAT rule and <var>P</var> is the NAT logical port. IP traffic
+ matching the above rule will be managed locally setting
+ <code>reg1</code> to <var>C</var> and
+ <code>eth.src</code> to <var>D</var>, where <var>C</var> is NAT
external ip and <var>D</var> is NAT external mac.
</li>
@@ -4531,8 +4535,9 @@ nd_ns {
outport == <var>GW</var> &&
is_chassis_resident(<var>P</var>)</code>, where <var>E</var> is the
external IP address specified in the NAT rule, <var>GW</var>
- is the logical router distributed gateway port. For dnat_and_snat
- NAT rule, <var>P</var> is the logical port specified in the NAT rule.
+ is the distributed gateway port specified in the NAT rule.
+ For dnat_and_snat NAT rule, <var>P</var> is the logical port
+ specified in the NAT rule.
If <ref column="logical_port"
table="NAT" db="OVN_Northbound"/> column of
<ref table="NAT" db="OVN_Northbound"/> table is NOT set, then
diff --git a/ovn-architecture.7.xml b/ovn-architecture.7.xml
index ef8d669a2..a48757761 100644
--- a/ovn-architecture.7.xml
+++ b/ovn-architecture.7.xml
@@ -742,9 +742,9 @@
<p>
A logical router can have multiple distributed gateway ports, each
- connecting different external networks. However, some features, such as NAT
- and load balancers, are not supported yet for logical routers with more
- than one distributed gateway port configured.
+ connecting different external networks. Load balancing is not yet
+ supported for logical routers with more than one distributed gateway
+ port configured.
</p>
<h4>Physical VLAN MTU Issues</h4>
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 80b830629..1a342c473 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "6.1.0",
- "cksum": "4010776751 31237",
+ "version": "6.2.0",
+ "cksum": "3707848838 31535",
"tables": {
"NB_Global": {
"columns": {
@@ -479,6 +479,12 @@
"refType": "strong"},
"min": 0,
"max": 1}},
+ "gateway_port": {
+ "type": {"key": {"type": "uuid",
+ "refTable": "Logical_Router_Port",
+ "refType": "strong"},
+ "min": 0,
+ "max": 1}},
"options": {"type": {"key": "string", "value": "string",
"min": 0, "max": "unlimited"}},
"external_ids": {
diff --git a/ovn-nb.xml b/ovn-nb.xml
index beb3ded79..fc2b917ac 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -2590,8 +2590,8 @@
<p>
There can be more than one distributed gateway ports configured
on each logical router, each connecting to different L2 segments.
- However, features such as NAT and load-balancer are not supported
- on logical routers with more than one distributed gateway ports.
+ Load-balancing is not yet supported on logical routers with more
+ than one distributed gateway ports.
</p>
<p>
@@ -3323,6 +3323,39 @@
</p>
</column>
+ <column name="gateway_port">
+ <p>
+ A distributed gateway port in the <ref table="Logical_Router_Port"/>
+ table where the NAT rule needs to be applied.
+ </p>
+
+ <p>
+ This column needs to be set when multiple distributed gateway ports
+ are configured on a <ref table="Logical_Router"/> for the NAT rule to
+ be applied. If logical router has a single distributed gateway port,
+ NAT rule is applied at the distributed gateway port even if this
+ column is not set.
+ </p>
+
+ <p>
+ When multiple distributed gateway ports are configured on a
+ <ref table="Logical_Router"/>, applying a NAT rule at each of the
+ distributed gateway ports might not be desired. Consider the case
+ where a logical router has 2 distributed gateway port, one with
+ <ref column="networks" table="Logical_Router_Port"/>
+ <code>50.0.0.10/24</code> and the other with
+ <ref column="networks" table="Logical_Router_Port"/>
+ <code>60.0.0.10/24</code>. If the logical router has a
+ NAT rule of <ref column="type"/> <code>snat</code>,
+ <ref column="logical_ip"/> <code>10.1.1.0/24</code> and
+ <ref column="external_ip"/> <code>50.1.1.20/24</code>, the rule needs
+ to be selectively applied on matching packets entering/leaving
+ through the distributed gateway port with
+ <ref column="networks" table="Logical_Router_Port"/>
+ <code>50.0.0.10/24</code>.
+ </p>
+ </column>
+
<column name="options" key="stateless">
Indicates if a dnat_and_snat rule should lead to connection
tracking state or not.
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 539a121c0..b4acb2da6 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -510,64 +510,64 @@ AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat fd01::1
fd11::2])
AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.2 192.168.1.3 lp0
00:00:00:01:02:03])
AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat fd01::2 fd11::3 lp0
00:00:00:01:02:03])
AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP
EXTERNAL_MAC LOGICAL_PORT
-dnat 30.0.0.1 192.168.1.2
-dnat fd01::1 fd11::2
-dnat_and_snat 30.0.0.1 192.168.1.2
-dnat_and_snat 30.0.0.2 192.168.1.3
00:00:00:01:02:03 lp0
-dnat_and_snat fd01::1 fd11::2
-dnat_and_snat fd01::2 fd11::3
00:00:00:01:02:03 lp0
-snat 30.0.0.1 192.168.1.0/24
-snat fd01::1 fd11::/64
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+dnat 30.0.0.1
192.168.1.2
+dnat fd01::1
fd11::2
+dnat_and_snat 30.0.0.1
192.168.1.2
+dnat_and_snat 30.0.0.2
192.168.1.3 00:00:00:01:02:03 lp0
+dnat_and_snat fd01::1
fd11::2
+dnat_and_snat fd01::2
fd11::3 00:00:00:01:02:03 lp0
+snat 30.0.0.1
192.168.1.0/24
+snat fd01::1
fd11::/64
])
AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and
logical_ip already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.0/24, : a NAT with this external_ip,
logical_ip and gateway_port already exists
])
AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.1 192.168.1.10/24], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.0/24: a NAT with this external_ip and
logical_ip already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.0/24, : a NAT with this external_ip,
logical_ip and gateway_port already exists
])
AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 snat 30.0.0.1 192.168.1.0/24])
AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 30.0.0.2 192.168.1.0/24], [1], [],
-[ovn-nbctl: a NAT with this type (snat) and logical_ip (192.168.1.0/24)
already exists
+[ovn-nbctl: a NAT with this type (snat), logical_ip (192.168.1.0/24) and
gateway_port () already exists
])
AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2], [1], [],
-[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip
already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.2, : a NAT with this external_ip, logical_ip
and gateway_port already exists
])
AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat 30.0.0.1 192.168.1.2])
AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 30.0.0.1 192.168.1.3], [1], [],
-[ovn-nbctl: a NAT with this type (dnat) and external_ip (30.0.0.1) already
exists
+[ovn-nbctl: a NAT with this type (dnat), external_ip (30.0.0.1) and
gateway_port () already exists
])
AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.2], [1],
[],
-[ovn-nbctl: 30.0.0.1, 192.168.1.2: a NAT with this external_ip and logical_ip
already exists
+[ovn-nbctl: 30.0.0.1, 192.168.1.2, : a NAT with this external_ip, logical_ip
and gateway_port already exists
])
AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.1
192.168.1.2])
AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 30.0.0.1 192.168.1.3], [1],
[],
-[ovn-nbctl: a NAT with this type (dnat_and_snat) and external_ip (30.0.0.1)
already exists
+[ovn-nbctl: a NAT with this type (dnat_and_snat), external_ip (30.0.0.1) and
gateway_port () already exists
])
AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2
192.168.1.3 lp0 00:00:00:04:05:06])
AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP
EXTERNAL_MAC LOGICAL_PORT
-dnat 30.0.0.1 192.168.1.2
-dnat fd01::1 fd11::2
-dnat_and_snat 30.0.0.1 192.168.1.2
-dnat_and_snat 30.0.0.2 192.168.1.3
00:00:00:04:05:06 lp0
-dnat_and_snat fd01::1 fd11::2
-dnat_and_snat fd01::2 fd11::3
00:00:00:01:02:03 lp0
-snat 30.0.0.1 192.168.1.0/24
-snat fd01::1 fd11::/64
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+dnat 30.0.0.1
192.168.1.2
+dnat fd01::1
fd11::2
+dnat_and_snat 30.0.0.1
192.168.1.2
+dnat_and_snat 30.0.0.2
192.168.1.3 00:00:00:04:05:06 lp0
+dnat_and_snat fd01::1
fd11::2
+dnat_and_snat fd01::2
fd11::3 00:00:00:01:02:03 lp0
+snat 30.0.0.1
192.168.1.0/24
+snat fd01::1
fd11::/64
])
AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat 30.0.0.2
192.168.1.3])
AT_CHECK([ovn-nbctl --may-exist lr-nat-add lr0 dnat_and_snat fd01::2 fd11::3])
AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP
EXTERNAL_MAC LOGICAL_PORT
-dnat 30.0.0.1 192.168.1.2
-dnat fd01::1 fd11::2
-dnat_and_snat 30.0.0.1 192.168.1.2
-dnat_and_snat 30.0.0.2 192.168.1.3
-dnat_and_snat fd01::1 fd11::2
-dnat_and_snat fd01::2 fd11::3
-snat 30.0.0.1 192.168.1.0/24
-snat fd01::1 fd11::/64
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+dnat 30.0.0.1
192.168.1.2
+dnat fd01::1
fd11::2
+dnat_and_snat 30.0.0.1
192.168.1.2
+dnat_and_snat 30.0.0.2
192.168.1.3
+dnat_and_snat fd01::1
fd11::2
+dnat_and_snat fd01::2
fd11::3
+snat 30.0.0.1
192.168.1.0/24
+snat fd01::1
fd11::/64
])
check_row_count nb:NAT 0 options:stateless=true
@@ -598,40 +598,31 @@ AT_CHECK([ovn-nbctl --stateless lr-nat-add lr0
dnat_and_snat 40.0.0.3 192.168.1.
])
dnl Deletes the NATs
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3], [1], [],
-[ovn-nbctl: no matching NAT with the type (dnat_and_snat) and external_ip
(30.0.0.3)
-])
-AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.2], [1], [],
-[ovn-nbctl: no matching NAT with the type (dnat) and external_ip (30.0.0.2)
-])
-AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 192.168.10.0/24], [1], [],
-[ovn-nbctl: no matching NAT with the type (snat) and logical_ip
(192.168.10.0/24)
-])
-AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 192.168.10.0/24])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.3])
AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat 30.0.0.1])
AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat_and_snat fd01::1])
AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP
EXTERNAL_MAC LOGICAL_PORT
-dnat 30.0.0.1 192.168.1.2
-dnat fd01::1 fd11::2
-dnat_and_snat 30.0.0.2 192.168.1.3
-dnat_and_snat 40.0.0.2 192.168.1.4
-dnat_and_snat fd01::2 fd11::3
-snat 30.0.0.1 192.168.1.0/24
-snat 40.0.0.3 192.168.1.6
-snat fd01::1 fd11::/64
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+dnat 30.0.0.1
192.168.1.2
+dnat fd01::1
fd11::2
+dnat_and_snat 30.0.0.2
192.168.1.3
+dnat_and_snat 40.0.0.2
192.168.1.4
+dnat_and_snat fd01::2
fd11::3
+snat 30.0.0.1
192.168.1.0/24
+snat 40.0.0.3
192.168.1.6
+snat fd01::1
fd11::/64
])
AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])
AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP
EXTERNAL_MAC LOGICAL_PORT
-dnat_and_snat 30.0.0.2 192.168.1.3
-dnat_and_snat 40.0.0.2 192.168.1.4
-dnat_and_snat fd01::2 fd11::3
-snat 30.0.0.1 192.168.1.0/24
-snat 40.0.0.3 192.168.1.6
-snat fd01::1 fd11::/64
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+dnat_and_snat 30.0.0.2
192.168.1.3
+dnat_and_snat 40.0.0.2
192.168.1.4
+dnat_and_snat fd01::2
fd11::3
+snat 30.0.0.1
192.168.1.0/24
+snat 40.0.0.3
192.168.1.6
+snat fd01::1
fd11::/64
])
AT_CHECK([ovn-nbctl lr-nat-del lr0])
@@ -702,12 +693,12 @@ AT_CHECK([ovn-nbctl show lr0 | grep -C2 'external port(s):
"1"' | uuidfilt], [0]
])
AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
-TYPE EXTERNAL_IP EXTERNAL_PORT LOGICAL_IP
EXTERNAL_MAC LOGICAL_PORT
-dnat 40.0.0.4 1-3000 192.168.1.7
-dnat 40.0.0.5 1 192.168.1.10
-dnat_and_snat 40.0.0.5 1-3000 192.168.1.8
-dnat_and_snat 40.0.0.6 1-3000 192.168.1.9
00:00:00:04:05:06 lp0
-snat 40.0.0.3 21-65535 192.168.1.6
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+dnat 40.0.0.4 1-3000
192.168.1.7
+dnat 40.0.0.5 1
192.168.1.10
+dnat_and_snat 40.0.0.5 1-3000
192.168.1.8
+dnat_and_snat 40.0.0.6 1-3000
192.168.1.9 00:00:00:04:05:06 lp0
+snat 40.0.0.3 21-65535
192.168.1.6
])
AT_CHECK([ovn-nbctl lr-nat-del lr0])
@@ -755,7 +746,54 @@ AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat
192.168.16 allowed_range], [1]
[ovn-nbctl: 192.168.16: Invalid IP address or CIDR
])
-AT_CHECK([ovn-nbctl lr-nat-del lr0])])
+AT_CHECK([ovn-nbctl lr-nat-del lr0])
+
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp00 00:00:00:01:02:03 172.64.0.10/24])
+AT_CHECK([ovn-nbctl lrp-add lr0 lrp01 00:00:00:01:02:04 172.64.1.10/24])
+AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp00 chassis1])
+AT_CHECK([ovn-nbctl lr-add lr1])
+AT_CHECK([ovn-nbctl lrp-add lr1 lrp10 00:00:00:01:02:05 172.64.2.10/24])
+
+AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 snat 172.64.0.10
20.0.0.10])
+AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 172.64.1.10
20.0.0.10], [1], [],
+[ovn-nbctl: lrp01 is not a distributed gateway router port.
+])
+AT_CHECK([ovn-nbctl --gateway-port=lrp10 lr-nat-add lr0 dnat_and_snat
172.64.2.10 20.0.0.10], [1], [],
+[ovn-nbctl: lrp10 is not a router port of logical router: lr0.
+])
+
+AT_CHECK([ovn-nbctl lrp-set-gateway-chassis lrp01 chassis2])
+
+AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 172.64.1.10 20.0.0.10], [1], [],
+[ovn-nbctl: logical router: lr0 has multiple distributed gateway ports. NAT
rule needs to specify gateway_port.
+])
+AT_CHECK([ovn-nbctl --gateway-port=lrp00 lr-nat-add lr0 dnat 30.0.0.10
20.0.0.10])
+AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 30.0.0.10
20.0.0.10])
+AT_CHECK([ovn-nbctl --gateway-port=lrp01 lr-nat-add lr0 dnat 30.0.0.10
20.0.0.20], [1], [],
+[ovn-nbctl: a NAT with this type (dnat), external_ip (30.0.0.10) and
gateway_port (lrp01) already exists
+])
+
+AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+dnat lrp00 30.0.0.10
20.0.0.10
+dnat lrp01 30.0.0.10
20.0.0.10
+snat lrp00 172.64.0.10
20.0.0.10
+])
+
+AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.10])
+AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], [dnl
+TYPE GATEWAY_PORT EXTERNAL_IP EXTERNAL_PORT
LOGICAL_IP EXTERNAL_MAC LOGICAL_PORT
+snat lrp00 172.64.0.10
20.0.0.10
+])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat 30.0.0.10 lrp00], [1], [],
+[ovn-nbctl: no matching NAT with the type (dnat), external_ip (30.0.0.10) and
gateway_port (lrp00)
+])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0.10 lrp01], [1], [],
+[ovn-nbctl: no matching NAT with the type (snat), logical_ip (20.0.0.10) and
gateway_port (lrp01)
+])
+AT_CHECK([ovn-nbctl --if-exists lr-nat-del lr0 snat 20.0.0.10 lrp01])
+AT_CHECK([ovn-nbctl lr-nat-del lr0 snat 20.0.0.10 lrp00])
+])
dnl ---------------------------------------------------------------------
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index fe2786973..166e1e0e9 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -4296,8 +4296,6 @@ check ovn-nbctl lsp-set-options lrp1-attachment
router-port=lrp1
check ovn-nbctl lr-nat-add lr0 dnat 42.42.42.42 192.168.0.2
check ovn-nbctl --wait=sb sync
-ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02 && ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64'
-
AT_CHECK([ovn-trace --minimal 'inport == "sw1-port1" && eth.src == 50:54:00:00:00:03 && eth.dst == 00:00:00:00:ff:02
&& ip4.dst == 42.42.42.42 && ip4.src == 11.0.0.2 && ip.ttl == 64' | grep "output(\"sw0-port1\")"], [0],
[ignore])
dnl If we remove the DNAT entry we will be unable to trace to the DNAT address
@@ -6249,3 +6247,167 @@ check_log_flows_count 0 in
AT_CLEANUP
])
+
+AT_SETUP([ovn-northd -- lr multiple gw ports NAT])
+AT_KEYWORDS([multiple-l3dgw-ports])
+ovn_start
+
+# Logical network:
+# 1 Logical Router, 3 bridged Logical Switches,
+# 1 gateway chassis attached to each corresponding LRP.
+#
+# | S1 (gw1)
+# |
+# ls ---- DR -- S3 (gw3)
+# (20.0.0.0/24) |
+# | S2 (gw2)
+#
+# Validate SNAT, DNAT and DNAT_AND_SNAT behavior with multiple
+# distributed gateway LRPs.
+
+check ovn-sbctl chassis-add gw1 geneve 127.0.0.1
+check ovn-sbctl chassis-add gw2 geneve 128.0.0.1
+check ovn-sbctl chassis-add gw3 geneve 129.0.0.1
+
+check ovn-nbctl lr-add DR
+check ovn-nbctl lrp-add DR DR-S1 02:ac:10:01:00:01 172.16.1.1/24
+check ovn-nbctl lrp-add DR DR-S2 03:ac:10:01:00:01 10.0.0.1/24
+check ovn-nbctl lrp-add DR DR-S3 04:ac:10:01:00:01 192.168.0.1/24
+check ovn-nbctl lrp-add DR DR-ls 05:ac:10:01:00:01 20.0.0.1/24
+
+check ovn-nbctl ls-add S1
+check ovn-nbctl lsp-add S1 S1-DR
+check ovn-nbctl lsp-set-type S1-DR router
+check ovn-nbctl lsp-set-addresses S1-DR router
+check ovn-nbctl --wait=sb lsp-set-options S1-DR router-port=DR-S1
+
+check ovn-nbctl ls-add S2
+check ovn-nbctl lsp-add S2 S2-DR
+check ovn-nbctl lsp-set-type S2-DR router
+check ovn-nbctl lsp-set-addresses S2-DR router
+check ovn-nbctl --wait=sb lsp-set-options S2-DR router-port=DR-S2
+
+check ovn-nbctl ls-add S3
+check ovn-nbctl lsp-add S3 S3-DR
+check ovn-nbctl lsp-set-type S3-DR router
+check ovn-nbctl lsp-set-addresses S3-DR router
+check ovn-nbctl --wait=sb lsp-set-options S3-DR router-port=DR-S3
+
+check ovn-nbctl ls-add ls
+check ovn-nbctl lsp-add ls ls-DR
+check ovn-nbctl lsp-set-type ls-DR router
+check ovn-nbctl lsp-set-addresses ls-DR router
+check ovn-nbctl --wait=sb lsp-set-options ls-DR router-port=DR-ls
+
+check ovn-nbctl lrp-set-gateway-chassis DR-S1 gw1
+check ovn-nbctl lrp-set-gateway-chassis DR-S2 gw2
+check ovn-nbctl lrp-set-gateway-chassis DR-S3 gw3
+
+check ovn-nbctl --wait=sb sync
+
+# Configure SNAT
+check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR snat 172.16.1.10
20.0.0.10
+check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR snat 10.0.0.10
20.0.0.10
+check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR snat 192.168.0.10
20.0.0.10
+
+ovn-sbctl dump-flows DR > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+check_lr_in_arp_nat_flows() {
+ AT_CHECK([grep lr_in_ip_input lrflows | grep arp | grep -e 172.16.1.10 -e
10.0.0.10 -e 192.168.0.10 | sed 's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_in_ip_input ), priority=90 , match=(arp.op == 1 && arp.tpa ==
10.0.0.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply */
arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport;
flags.loopback = 1; output;)
+ table=??(lr_in_ip_input ), priority=90 , match=(arp.op == 1 && arp.tpa ==
172.16.1.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply
*/ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport;
flags.loopback = 1; output;)
+ table=??(lr_in_ip_input ), priority=90 , match=(arp.op == 1 && arp.tpa ==
192.168.0.10), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2; /* ARP reply
*/ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport;
flags.loopback = 1; output;)
+ table=??(lr_in_ip_input ), priority=91 , match=(inport == "DR-S1" && arp.op ==
1 && arp.tpa == 172.16.1.10), action=(drop;)
+ table=??(lr_in_ip_input ), priority=91 , match=(inport == "DR-S2" && arp.op ==
1 && arp.tpa == 10.0.0.10), action=(drop;)
+ table=??(lr_in_ip_input ), priority=91 , match=(inport == "DR-S3" && arp.op ==
1 && arp.tpa == 192.168.0.10), action=(drop;)
+ table=??(lr_in_ip_input ), priority=92 , match=(inport == "DR-S1" && arp.op == 1 && arp.tpa ==
172.16.1.10 && is_chassis_resident("cr-DR-S1")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op =
2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1;
output;)
+ table=??(lr_in_ip_input ), priority=92 , match=(inport == "DR-S2" && arp.op == 1 && arp.tpa ==
10.0.0.10 && is_chassis_resident("cr-DR-S2")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op = 2;
/* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1;
output;)
+ table=??(lr_in_ip_input ), priority=92 , match=(inport == "DR-S3" && arp.op == 1 && arp.tpa ==
192.168.0.10 && is_chassis_resident("cr-DR-S3")), action=(eth.dst = eth.src; eth.src = xreg0[[0..47]]; arp.op =
2; /* ARP reply */ arp.tha = arp.sha; arp.sha = xreg0[[0..47]]; arp.tpa <-> arp.spa; outport = inport; flags.loopback = 1;
output;)
+])
+}
+
+check_lr_in_unsnat_flows() {
+ AT_CHECK([grep lr_in_unsnat lrflows | grep ct_snat | sed
's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_in_unsnat ), priority=100 , match=(ip && ip4.dst == 10.0.0.10 && inport == "DR-S2"
&& flags.loopback == 0 && is_chassis_resident("cr-DR-S2")), action=(ct_snat_in_czone;)
+ table=??(lr_in_unsnat ), priority=100 , match=(ip && ip4.dst == 10.0.0.10 && inport == "DR-S2"
&& flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S2")), action=(ct_snat;)
+ table=??(lr_in_unsnat ), priority=100 , match=(ip && ip4.dst == 172.16.1.10 && inport ==
"DR-S1" && flags.loopback == 0 && is_chassis_resident("cr-DR-S1")), action=(ct_snat_in_czone;)
+ table=??(lr_in_unsnat ), priority=100 , match=(ip && ip4.dst == 172.16.1.10 && inport == "DR-S1"
&& flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S1")), action=(ct_snat;)
+ table=??(lr_in_unsnat ), priority=100 , match=(ip && ip4.dst == 192.168.0.10 && inport ==
"DR-S3" && flags.loopback == 0 && is_chassis_resident("cr-DR-S3")), action=(ct_snat_in_czone;)
+ table=??(lr_in_unsnat ), priority=100 , match=(ip && ip4.dst == 192.168.0.10 && inport == "DR-S3"
&& flags.loopback == 1 && flags.use_snat_zone == 1 && is_chassis_resident("cr-DR-S3")), action=(ct_snat;)
+])
+}
+
+check_lr_out_snat_flows() {
+ AT_CHECK([grep lr_out_snat lrflows | grep ct_snat | sed
's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport ==
"DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_snat_in_czone(172.16.1.10);)
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport ==
"DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_snat_in_czone(10.0.0.10);)
+ table=??(lr_out_snat ), priority=161 , match=(ip && ip4.src == 20.0.0.10 && outport ==
"DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_snat_in_czone(192.168.0.10);)
+ table=??(lr_out_snat ), priority=162 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S1"
&& is_chassis_resident("cr-DR-S1") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(172.16.1.10);)
+ table=??(lr_out_snat ), priority=162 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S2"
&& is_chassis_resident("cr-DR-S2") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(10.0.0.10);)
+ table=??(lr_out_snat ), priority=162 , match=(ip && ip4.src == 20.0.0.10 && outport == "DR-S3"
&& is_chassis_resident("cr-DR-S3") && reg9[[4]] == 1), action=(reg9[[4]] = 0; ct_snat(192.168.0.10);)
+])
+}
+
+check_lr_in_unsnat_flows
+check_lr_out_snat_flows
+check_lr_in_arp_nat_flows
+
+check ovn-nbctl lr-nat-del DR snat 20.0.0.10
+AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_unsnat -e lr_out_snat | grep
ct_snat | wc -l], [0], [0
+])
+
+# Configure DNAT
+check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR dnat 172.16.1.10
20.0.0.10
+check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR dnat 10.0.0.10
20.0.0.10
+check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR dnat 192.168.0.10
20.0.0.10
+
+ovn-sbctl dump-flows DR > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+check_lr_in_dnat_flows() {
+ AT_CHECK([grep lr_in_dnat lrflows | grep ct_dnat | sed
's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_in_dnat ), priority=100 , match=(ip && ip4.dst == 10.0.0.10 && inport ==
"DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_dnat_in_czone(20.0.0.10);)
+ table=??(lr_in_dnat ), priority=100 , match=(ip && ip4.dst == 172.16.1.10 && inport ==
"DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_dnat_in_czone(20.0.0.10);)
+ table=??(lr_in_dnat ), priority=100 , match=(ip && ip4.dst == 192.168.0.10 && inport ==
"DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_dnat_in_czone(20.0.0.10);)
+])
+}
+
+check_lr_out_undnat_flows() {
+ AT_CHECK([grep lr_out_undnat lrflows | grep ct_dnat | sed
's/table=../table=??/' | sort], [0], [dnl
+ table=??(lr_out_undnat ), priority=100 , match=(ip && ip4.src == 20.0.0.10 && outport ==
"DR-S1" && is_chassis_resident("cr-DR-S1")), action=(ct_dnat_in_czone;)
+ table=??(lr_out_undnat ), priority=100 , match=(ip && ip4.src == 20.0.0.10 && outport ==
"DR-S2" && is_chassis_resident("cr-DR-S2")), action=(ct_dnat_in_czone;)
+ table=??(lr_out_undnat ), priority=100 , match=(ip && ip4.src == 20.0.0.10 && outport ==
"DR-S3" && is_chassis_resident("cr-DR-S3")), action=(ct_dnat_in_czone;)
+])
+}
+
+check_lr_in_dnat_flows
+check_lr_out_undnat_flows
+check_lr_in_arp_nat_flows
+
+check ovn-nbctl lr-nat-del DR dnat
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_dnat -e lr_out_undnat | grep
ct_dnat | wc -l], [0], [0
+])
+
+# Configure DNAT_AND_SNAT
+check ovn-nbctl --gateway-port=DR-S1 lr-nat-add DR dnat_and_snat 172.16.1.10
20.0.0.10
+check ovn-nbctl --gateway-port=DR-S2 lr-nat-add DR dnat_and_snat 10.0.0.10
20.0.0.10
+check ovn-nbctl --gateway-port=DR-S3 lr-nat-add DR dnat_and_snat 192.168.0.10
20.0.0.10
+
+ovn-sbctl dump-flows DR > lrflows
+AT_CAPTURE_FILE([lrflows])
+
+check_lr_in_unsnat_flows
+check_lr_out_snat_flows
+check_lr_in_dnat_flows
+check_lr_out_undnat_flows
+check_lr_in_arp_nat_flows
+
+check ovn-nbctl lr-nat-del DR dnat_and_snat
+
+AT_CHECK([ovn-sbctl dump-flows DR | grep -e lr_in_unsnat -e lr_out_snat -e
lr_in_dnat -e lr_out_undnat | grep ct_snat| wc -l], [0], [0
+])
+
+AT_CLEANUP
+])
diff --git a/tests/ovn.at b/tests/ovn.at
index 69270601a..62b6ed7db 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -29272,7 +29272,7 @@ as hv2 check ovn-appctl -t ovn-controller recompute
AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2], [1])
# Enable dnat_and_snat on lr, and now hv2 should see flows for lsp1.
-AT_CHECK([ovn-nbctl --wait=hv lr-nat-add lr dnat_and_snat 192.168.0.1 10.0.1.3
lsp1 f0:00:00:00:00:03])
+AT_CHECK([ovn-nbctl --wait=hv --gateway-port=lrp_lr_ls1 lr-nat-add lr
dnat_and_snat 192.168.0.1 10.0.1.3 lsp1 f0:00:00:00:00:03])
AT_CHECK([as hv2 ovs-ofctl dump-flows br-int table=24 | grep 10.0.1.2], [0],
[ignore])
OVN_CLEANUP([hv1],[hv2])
diff --git a/utilities/ovn-nbctl.8.xml b/utilities/ovn-nbctl.8.xml
index 545f3bf27..1f75c3ebc 100644
--- a/utilities/ovn-nbctl.8.xml
+++ b/utilities/ovn-nbctl.8.xml
@@ -1135,7 +1135,7 @@
<h2>NAT Commands</h2>
<dl>
- <dt>[<code>--may-exist</code>] [<code>--stateless</code>]<code>lr-nat-add</code> <var>router</var> <var>type</var>
<var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>
+ <dt>[<code>--may-exist</code>] [<code>--stateless</code>] [<code>--gateway_port</code>=<var>GATEWAY_PORT</var>] <code>lr-nat-add</code>
<var>router</var> <var>type</var> <var>external_ip</var> <var>logical_ip</var> [<var>logical_port</var> <var>external_mac</var>]</dt>
<dd>
<p>
Adds the specified NAT to <var>router</var>.
@@ -1151,7 +1151,6 @@
The <var>logical_port</var> is the name of an existing logical
switch port where the <var>logical_ip</var> resides.
The <var>external_mac</var> is an Ethernet address.
- The <var>--stateless</var>
</p>
<p>
When <code>--stateless</code> is specified then it implies that
@@ -1162,6 +1161,16 @@
with any other NAT rule.
</p>
+ <p>
+ <code>--gateway-port</code> option allows specifying the distributed
+ gateway port of <var>router</var> where the NAT rule needs to be
+ applied. <var>GATEWAY_PORT</var> should reference a
+ <ref table="Logical_Router_Port"/> row that is a distributed gateway
+ port of <var>router</var>. When <var>router</var> has multiple
+ distributed gateway ports, it is an error to not specify the
+ <var>GATEWAY_PORT</var>.
+ </p>
+
<p>
When <var>type</var> is <code>dnat</code>, the externally
visible IP address <var>external_ip</var> is DNATted to the
@@ -1194,30 +1203,33 @@
<p>
It is an error if a NAT already exists with the same values
of <var>router</var>, <var>type</var>, <var>external_ip</var>,
- and <var>logical_ip</var>, unless <code>--may-exist</code> is
- specified. When <code>--may-exist</code>,
+ <var>logical_ip</var> and <var>GATEWAY_PORT</var> (in case of
+ multiple distributed gateway ports), unless <code>--may-exist</code>
+ is specified. When <code>--may-exist</code>,
<var>logical_port</var>, and <var>external_mac</var> are all
specified, the existing values of <var>logical_port</var> and
<var>external_mac</var> are overwritten.
</p>
</dd>
- <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var> [<var>ip</var>]]</dt>
+ <dt>[<code>--if-exists</code>] <code>lr-nat-del</code> <var>router</var> [<var>type</var>
[<var>ip</var> [<var>gateway_port</var>]]]</dt>
<dd>
<p>
Deletes NATs from <var>router</var>. If only <var>router</var>
is supplied, all the NATs from the logical router are
deleted. If <var>type</var> is also specified, then all the
NATs that match the <var>type</var> will be deleted from the logical
- router. If all the fields are given, then a single NAT rule
- that matches all the fields will be deleted. When <var>type</var>
- is <code>snat</code>, the <var>ip</var> should be logical_ip.
- When <var>type</var> is <code>dnat</code> or
+ router. If <var>ip</var> is also specified, then all the
+ NATs that match the <var>type</var> and <var>ip</var> will be
+ deleted from the logical router. If all the fields are given, then
+ a single NAT rule that matches all the fields will be deleted.
+ When <var>type</var> is <code>snat</code>, the <var>ip</var> should
+ be logical_ip. When <var>type</var> is <code>dnat</code> or
<code>dnat_and_snat</code>, the <var>ip</var> shoud be external_ip.
</p>
<p>
- It is an error if <var>ip</var> is specified and there
+ It is an error if <var>gateway_port</var> is specified and there
is no matching NAT entry, unless <code>--if-exists</code> is
specified.
</p>
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index adb08c6c9..163e64b67 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -378,10 +378,11 @@ NAT commands:\n\
[--stateless]\n\
[--portrange]\n\
[--add-route]\n\
+ [--gateway-port=GATEWAY_PORT]\n\
lr-nat-add ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]\n\
[EXTERNAL_PORT_RANGE]\n\
add a NAT to ROUTER\n\
- lr-nat-del ROUTER [TYPE [IP]]\n\
+ lr-nat-del ROUTER [TYPE [IP [GATEWAY_PORT]]]\n\
remove NATs from ROUTER\n\
lr-nat-list ROUTER print NATs for ROUTER\n\
\n\
@@ -4557,6 +4558,7 @@ nbctl_pre_lr_nat_add(struct ctl_context *ctx)
{
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_name);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_nat);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_col_ports);
ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
@@ -4565,7 +4567,14 @@ nbctl_pre_lr_nat_add(struct ctl_context *ctx)
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_type);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_options);
+
+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
+ ovsdb_idl_add_column(ctx->idl,
+ &nbrec_logical_router_port_col_gateway_chassis);
+ ovsdb_idl_add_column(ctx->idl,
+ &nbrec_logical_router_port_col_ha_chassis_group);
}
static void
@@ -4700,6 +4709,52 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
ctl_error(ctx, "routes cannot be added for snat types.");
goto cleanup;
}
+
+ const char *dgw_port_name = shash_find_data(&ctx->options,
+ "--gateway-port");
+ const struct nbrec_logical_router_port *dgw_port = NULL;
+ if (dgw_port_name) {
+ error = lrp_by_name_or_uuid(ctx, dgw_port_name,
+ true, &dgw_port);
+ if (error) {
+ ctx->error = error;
+ goto cleanup;
+ }
+
+ bool nat_lr_port = false;
+ for (size_t i = 0; i < lr->n_ports; i++) {
+ const struct nbrec_logical_router_port *lrp = lr->ports[i];
+ if (lrp == dgw_port) {
+ nat_lr_port = true;
+ }
+ }
+ if (!nat_lr_port) {
+ ctl_error(ctx, "%s is not a router port of logical router: %s.",
+ dgw_port_name, ctx->argv[1]);
+ goto cleanup;
+ }
+
+ if (!dgw_port->ha_chassis_group && !dgw_port->n_gateway_chassis) {
+ ctl_error(ctx, "%s is not a distributed gateway router port.",
+ dgw_port_name);
+ goto cleanup;
+ }
+ } else {
+ size_t num_l3dgw_ports = 0;
+ for (size_t i = 0; i < lr->n_ports; i++) {
+ const struct nbrec_logical_router_port *lrp = lr->ports[i];
+ if (lrp->ha_chassis_group || lrp->n_gateway_chassis) {
+ num_l3dgw_ports++;
+ }
+ }
+ if (num_l3dgw_ports > 1) {
+ ctl_error(ctx, "logical router: %s has multiple distributed "
+ "gateway ports. NAT rule needs to specify "
+ "gateway_port.", ctx->argv[1]);
+ goto cleanup;
+ }
+ }
+
for (size_t i = 0; i < lr->n_nat; i++) {
const struct nbrec_nat *nat = lr->nat[i];
@@ -4716,7 +4771,8 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
continue;
}
- if (!strcmp(nat_type, nat->type)) {
+ if (!strcmp(nat_type, nat->type) &&
+ dgw_port == nat->gateway_port) {
if (!strcmp(is_snat ? new_logical_ip : new_external_ip,
is_snat ? old_logical_ip : old_external_ip)) {
if (!strcmp(is_snat ? new_external_ip : new_logical_ip,
@@ -4728,18 +4784,20 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
nbrec_nat_set_external_mac(nat, external_mac);
should_return = true;
} else {
- ctl_error(ctx, "%s, %s: a NAT with this "
- "external_ip and logical_ip already "
- "exists", new_external_ip,
- new_logical_ip);
+ ctl_error(ctx, "%s, %s, %s: a NAT with this "
+ "external_ip, logical_ip and "
+ "gateway_port already exists",
+ new_external_ip, new_logical_ip,
+ dgw_port ? dgw_port->name : "");
should_return = true;
}
} else {
- ctl_error(ctx, "a NAT with this type (%s) and %s (%s) "
- "already exists",
+ ctl_error(ctx, "a NAT with this type (%s), %s (%s) "
+ "and gateway_port (%s) already exists",
nat_type,
is_snat ? "logical_ip" : "external_ip",
- is_snat ? new_logical_ip : new_external_ip);
+ is_snat ? new_logical_ip : new_external_ip,
+ dgw_port ? dgw_port->name : "");
should_return = true;
}
}
@@ -4783,6 +4841,10 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
if (add_route) {
smap_add(&nat_options, "add_route", "true");
}
+
+ if (dgw_port) {
+ nbrec_nat_update_gateway_port_addvalue(nat, dgw_port);
+ }
nbrec_nat_set_options(nat, &nat_options);
smap_destroy(&nat_options);
@@ -4804,6 +4866,9 @@ nbctl_pre_lr_nat_del(struct ctl_context *ctx)
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_type);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_ip);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_ip);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
+
+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
}
static void
@@ -4850,7 +4915,32 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
}
int is_snat = !strcmp("snat", nat_type);
- /* Remove the matching NAT. */
+ if (ctx->argc == 4) {
+ /* Remove NAT rules matching the type and IP (based on type). */
+ for (size_t i = 0; i < lr->n_nat; i++) {
+ struct nbrec_nat *nat = lr->nat[i];
+ char *old_ip = normalize_prefix_str(is_snat
+ ? nat->logical_ip
+ : nat->external_ip);
+ if (!old_ip) {
+ continue;
+ }
+ if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
+ nbrec_logical_router_update_nat_delvalue(lr, nat);
+ }
+ free(old_ip);
+ }
+ goto cleanup;
+ }
+
+ const struct nbrec_logical_router_port *dgw_port = NULL;
+ error = lrp_by_name_or_uuid(ctx, ctx->argv[4], true, &dgw_port);
+ if (error) {
+ ctx->error = error;
+ goto cleanup;
+ }
+
+ /* Remove matching NAT. */
for (size_t i = 0; i < lr->n_nat; i++) {
struct nbrec_nat *nat = lr->nat[i];
bool should_return = false;
@@ -4860,19 +4950,21 @@ nbctl_lr_nat_del(struct ctl_context *ctx)
if (!old_ip) {
continue;
}
- if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip)) {
+ if (!strcmp(nat_type, nat->type) && !strcmp(nat_ip, old_ip) &&
+ nat->gateway_port == dgw_port) {
nbrec_logical_router_update_nat_delvalue(lr, nat);
should_return = true;
}
free(old_ip);
- if (should_return) {
+ if (should_return)
goto cleanup;
- }
}
if (must_exist) {
- ctl_error(ctx, "no matching NAT with the type (%s) and %s (%s)",
- nat_type, is_snat ? "logical_ip" : "external_ip", nat_ip);
+ ctl_error(ctx, "no matching NAT with the type (%s), %s (%s) and "
+ "gateway_port (%s)", nat_type,
+ is_snat ? "logical_ip" : "external_ip", nat_ip,
+ ctx->argv[4]);
}
cleanup:
@@ -4891,6 +4983,9 @@ nbctl_pre_lr_nat_list(struct ctl_context *ctx)
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_ip);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_external_mac);
ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_logical_port);
+ ovsdb_idl_add_column(ctx->idl, &nbrec_nat_col_gateway_port);
+
+ ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name);
}
static void
@@ -4906,11 +5001,18 @@ nbctl_lr_nat_list(struct ctl_context *ctx)
struct smap lr_nats = SMAP_INITIALIZER(&lr_nats);
for (size_t i = 0; i < lr->n_nat; i++) {
const struct nbrec_nat *nat = lr->nat[i];
- char *key = xasprintf("%-17.13s%s", nat->type, nat->external_ip);
+ char *key = xasprintf("%-17.13s%-22.18s%s",
+ nat->type,
+ nat->gateway_port
+ ? nat->gateway_port->name
+ : "",
+ nat->external_ip);
if (nat->external_mac && nat->logical_port) {
- smap_add_format(&lr_nats, key, "%-17.13s%-22.18s%-21.17s%s",
+ smap_add_format(&lr_nats, key,
+ "%-17.13s%-20.16s%-21.17s%s",
nat->external_port_range,
- nat->logical_ip, nat->external_mac,
+ nat->logical_ip,
+ nat->external_mac,
nat->logical_port);
} else {
smap_add_format(&lr_nats, key, "%-17.13s%s",
@@ -4923,12 +5025,12 @@ nbctl_lr_nat_list(struct ctl_context *ctx)
const struct smap_node **nodes = smap_sort(&lr_nats);
if (nodes) {
ds_put_format(&ctx->output,
- "%-17.13s%-19.15s%-17.13s%-22.18s%-21.17s%s\n",
- "TYPE", "EXTERNAL_IP", "EXTERNAL_PORT", "LOGICAL_IP",
- "EXTERNAL_MAC", "LOGICAL_PORT");
+ "%-17.13s%-22.18s%-19.15s%-17.13s%-20.16s%-21.17s%s\n",
+ "TYPE", "GATEWAY_PORT", "EXTERNAL_IP", "EXTERNAL_PORT",
+ "LOGICAL_IP", "EXTERNAL_MAC", "LOGICAL_PORT");
for (size_t i = 0; i < smap_count(&lr_nats); i++) {
const struct smap_node *node = nodes[i];
- ds_put_format(&ctx->output, "%-36.32s%s\n",
+ ds_put_format(&ctx->output, "%-58.54s%s\n",
node->key, node->value);
}
free(nodes);
@@ -7099,8 +7201,9 @@ static const struct ctl_command_syntax nbctl_commands[] =
{
"ROUTER TYPE EXTERNAL_IP LOGICAL_IP"
"[LOGICAL_PORT EXTERNAL_MAC] [EXTERNAL_PORT_RANGE]",
nbctl_pre_lr_nat_add, nbctl_lr_nat_add,
- NULL, "--may-exist,--stateless,--portrange,--add-route", RW },
- { "lr-nat-del", 1, 3, "ROUTER [TYPE [IP]]",
+ NULL, "--may-exist,--stateless,--portrange,--add-route,"
+ "--gateway-port=", RW },
+ { "lr-nat-del", 1, 4, "ROUTER [TYPE [IP [GATEWAY_PORT]]]",
nbctl_pre_lr_nat_del, nbctl_lr_nat_del, NULL, "--if-exists", RW },
{ "lr-nat-list", 1, 1, "ROUTER", nbctl_pre_lr_nat_list,
nbctl_lr_nat_list, NULL, "", RO },