From patchwork Fri Oct 4 20:13:48 2019
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Ankur Sharma
--may-exist
] lr-nat-add
router type external_ip logical_ip [logical_port external_mac]--may-exist
] [--stateless
]lr-nat-add
router type external_ip logical_ip [logical_port external_mac]Adds the specified NAT to router. @@ -681,8 +681,18 @@ The logical_port is the name of an existing logical switch port where the logical_ip resides. The external_mac is an Ethernet address. + The --stateless
+ When --stateless
is specified then it implies that
+ we will be not use connection tracker, i.e internal ip and external
+ ip are 1:1 mapped. This implies that --stateless
is
+ applicable only to dnat_and_snat type NAT rules.
+ An external ip with --stateless
NAT cannot be shared
+ with any other NAT rule.
+
When type is dnat
, the externally
visible IP address external_ip is DNATted to the
IP address logical_ip in the logical space.
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index a89a9cb..7a3ac6e 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -691,6 +691,7 @@ Policy commands:\n\
lr-policy-list ROUTER print policies for ROUTER\n\
\n\
NAT commands:\n\
+ [--stateless]\n\
lr-nat-add ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]\n\
add a NAT to ROUTER\n\
lr-nat-del ROUTER [TYPE [IP]]\n\
@@ -3926,6 +3927,13 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
}
bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+ bool is_stateless = shash_find(&ctx->options, "--stateless") != NULL;
+
+ if (strcmp(nat_type, "dnat_and_snat") && is_stateless) {
+ ctl_error(ctx, "is_stateless is not applicable to dnat or snat types");
+ return;
+ }
+
int is_snat = !strcmp("snat", nat_type);
for (size_t i = 0; i < lr->n_nat; i++) {
const struct nbrec_nat *nat = lr->nat[i];
@@ -3957,10 +3965,25 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
return;
}
}
+
+ }
+ if (!strcmp(nat_type, "dnat_and_snat") ||
+ !strcmp(nat->type, "dnat_and_snat")) {
+
+ if (!strcmp(nat->external_ip, external_ip)) {
+ struct smap nat_options = SMAP_INITIALIZER(&nat_options);
+ if (!strcmp(smap_get(&nat->options, "is_stateless"),
+ "true") || is_stateless) {
+ ctl_error(ctx, "%s, %s: External ip cannot be shared "
+ "across stateless and stateful NATs",
+ external_ip, new_logical_ip);
+ }
+ }
}
}
/* Create the NAT. */
+ struct smap nat_options = SMAP_INITIALIZER(&nat_options);
struct nbrec_nat *nat = nbrec_nat_insert(ctx->txn);
nbrec_nat_set_type(nat, nat_type);
nbrec_nat_set_external_ip(nat, external_ip);
@@ -3969,7 +3992,12 @@ nbctl_lr_nat_add(struct ctl_context *ctx)
nbrec_nat_set_logical_port(nat, logical_port);
nbrec_nat_set_external_mac(nat, external_mac);
}
+
+ smap_add(&nat_options, "is_stateless", is_stateless ? "true":"false");
+ nbrec_nat_set_options(nat, &nat_options);
+
free(new_logical_ip);
+ smap_destroy(&nat_options);
/* Insert the NAT into the logical router. */
nbrec_logical_router_verify_nat(lr);
@@ -5689,7 +5717,7 @@ static const struct ctl_command_syntax nbctl_commands[] = {
/* NAT commands. */
{ "lr-nat-add", 4, 6,
"ROUTER TYPE EXTERNAL_IP LOGICAL_IP [LOGICAL_PORT EXTERNAL_MAC]", NULL,
- nbctl_lr_nat_add, NULL, "--may-exist", RW },
+ nbctl_lr_nat_add, NULL, "--may-exist,--stateless", RW },
{ "lr-nat-del", 1, 3, "ROUTER [TYPE [IP]]", NULL,
nbctl_lr_nat_del, NULL, "--if-exists", RW },
{ "lr-nat-list", 1, 1, "ROUTER", NULL, nbctl_lr_nat_list, NULL, "", RO },
From patchwork Fri Oct 4 20:13:50 2019
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Patchwork-Submitter: Ankur Sharma ip &&
ip4.dst == B
with an action
- ct_snat;
.
+ ct_snat;
. If the NAT rule is of type dnat_and_snat
+ and has is_stateless=true
in the options, then the action
+ would be replace_dst_ip(B)
.
@@ -1738,7 +1740,10 @@ icmp6 {
B, a priority-100 flow matches ip &&
ip4.dst == B && inport == GW
,
where GW is the logical router gateway port, with an
- action ct_snat;
.
+ action ct_snat;
. If the NAT rule is of type
+ dnat_and_snat and has is_stateless=true
in the
+ options, then the action would be replace_dst_ip
+ (B)
.
@@ -1858,7 +1863,10 @@ icmp6 {
Gateway router is configured to force SNAT any DNATed packet,
the above action will be replaced by
flags.force_snat_for_dnat = 1; flags.loopback = 1;
- ct_dnat(B);
.
+ ct_dnat(B);. If the NAT rule is of type
+ dnat_and_snat and has is_stateless=true
in the
+ options, then the action would be replace_dst_ip
+ (B)
.
ip &&
ip4.dst == B && inport == GW
,
where GW is the logical router gateway port, with an
- action ct_dnat(B);
.
+ action ct_dnat(B);
. If the NAT rule is of
+ type dnat_and_snat and has is_stateless=true
in the
+ options, then the action would be replace_dst_ip
+ (B)
.
@@ -2553,7 +2564,10 @@ nd_ns {
matches ip && ip4.src == B
&& outport == GW
, where GW
is the logical router gateway port, with an action
- ct_dnat;
.
+ ct_dnat;
. If the NAT rule is of type
+ dnat_and_snat and has is_stateless=true
in the
+ options, then the action would be replace_src_ip
+ (B)
.
@@ -2611,7 +2625,10 @@ nd_ns {
ip && ip4.src == A
with an action
ct_snat(B);
. The priority of the flow
is calculated based on the mask of A, with matches
- having larger masks getting higher priorities.
+ having larger masks getting higher priorities. If the NAT rule is
+ of type dnat_and_snat and has is_stateless=true
in the
+ options, then the action would be replace_src_ip
+ (B)
.
A priority-0 logical flow with match 1
has actions
@@ -2634,7 +2651,10 @@ nd_ns {
logical router gateway port, with an action
ct_snat(B);
. The priority of the flow
is calculated based on the mask of A, with matches
- having larger masks getting higher priorities.
+ having larger masks getting higher priorities. If the NAT rule
+ is of type dnat_and_snat and has is_stateless=true
+ in the options, then the action would be replace_src_ip
+ (B)
.
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c index f393ceb..4036392 100644 --- a/northd/ovn-northd.c +++ b/northd/ovn-northd.c @@ -6314,6 +6314,18 @@ copy_ra_to_sb(struct ovn_port *op, const char *address_mode) smap_destroy(&options); } +static inline bool +lrouter_nat_is_stateless(const struct nbrec_nat *nat) +{ + const char *is_stateless = smap_get(&nat->options, "is_stateless"); + + if (is_stateless && !strcmp(is_stateless, "true")) { + return true; + } + + return false; +} + static void build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, struct hmap *lflows, struct shash *meter_groups) @@ -7052,6 +7064,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, nat = od->nbr->nat[i]; ovs_be32 ip, mask; + bool is_stateless = lrouter_nat_is_stateless(nat); char *error = ip_parse_masked(nat->external_ip, &ip, &mask); if (error || mask != OVS_BE32_MAX) { @@ -7117,15 +7130,26 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, if (!od->l3dgw_port) { /* Gateway router. */ ds_clear(&match); + ds_clear(&actions); ds_put_format(&match, "ip && ip4.dst == %s", nat->external_ip); + + if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) { + ds_put_format(&actions, "ip4.dst=%s; next;", + nat->logical_ip); + } else { + ds_put_cstr(&actions, "ct_snat;"); + } + ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 90, - ds_cstr(&match), "ct_snat;"); + ds_cstr(&match), ds_cstr(&actions)); } else { /* Distributed router. */ /* Traffic received on l3dgw_port is subject to NAT. */ ds_clear(&match); + ds_clear(&actions); + ds_put_format(&match, "ip && ip4.dst == %s" " && inport == %s", nat->external_ip, @@ -7136,8 +7160,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_put_format(&match, " && is_chassis_resident(%s)", od->l3redirect_port->json_key); } + + if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) { + ds_put_format(&actions, "ip4.dst=%s; next;", + nat->logical_ip); + } else { + ds_put_cstr(&actions, "ct_snat;"); + } + ovn_lflow_add(lflows, od, S_ROUTER_IN_UNSNAT, 100, - ds_cstr(&match), "ct_snat;"); + ds_cstr(&match), ds_cstr(&actions)); /* Traffic received on other router ports must be * redirected to the central instance of the l3dgw_port @@ -7172,8 +7204,16 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_put_format(&actions, "flags.force_snat_for_dnat = 1; "); } - ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);", - nat->logical_ip); + + if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) { + ds_put_format(&actions, "flags.loopback = 1; " + "ip4.dst=%s; next;", + nat->logical_ip); + } else { + ds_put_format(&actions, "flags.loopback = 1; ct_dnat(%s);", + nat->logical_ip); + } + ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100, ds_cstr(&match), ds_cstr(&actions)); } else { @@ -7192,8 +7232,15 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, od->l3redirect_port->json_key); } ds_clear(&actions); - ds_put_format(&actions, "ct_dnat(%s);", - nat->logical_ip); + + if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) { + ds_put_format(&actions, "ip4.dst=%s; next;", + nat->logical_ip); + } else { + ds_put_format(&actions, "ct_dnat(%s);", + nat->logical_ip); + } + ovn_lflow_add(lflows, od, S_ROUTER_IN_DNAT, 100, ds_cstr(&match), ds_cstr(&actions)); @@ -7235,7 +7282,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", ETH_ADDR_ARGS(mac)); } - ds_put_format(&actions, "ct_dnat;"); + + if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) { + ds_put_format(&actions, "ip4.src=%s; next;", + nat->external_ip); + } else { + ds_put_format(&actions, "ct_dnat;"); + } + ovn_lflow_add(lflows, od, S_ROUTER_OUT_UNDNAT, 100, ds_cstr(&match), ds_cstr(&actions)); } @@ -7251,7 +7305,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_put_format(&match, "ip && ip4.src == %s", nat->logical_ip); ds_clear(&actions); - ds_put_format(&actions, "ct_snat(%s);", nat->external_ip); + + if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) { + ds_put_format(&actions, "ip4.src=%s; next;", + nat->external_ip); + } else { + ds_put_format(&actions, "ct_snat(%s);", + nat->external_ip); + } /* The priority here is calculated such that the * nat->logical_ip with the longest mask gets a higher @@ -7280,7 +7341,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_put_format(&actions, "eth.src = "ETH_ADDR_FMT"; ", ETH_ADDR_ARGS(mac)); } - ds_put_format(&actions, "ct_snat(%s);", nat->external_ip); + + if (!strcmp(nat->type, "dnat_and_snat") && is_stateless) { + ds_put_format(&actions, "ip4.src=%s; next;", + nat->external_ip); + } else { + ds_put_format(&actions, "ct_snat(%s);", + nat->external_ip); + } /* The priority here is calculated such that the * nat->logical_ip with the longest mask gets a higher diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 42033d5..64511a9 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -966,3 +966,53 @@ OVS_WAIT_UNTIL([ovn-sbctl get Port_Binding ${uuid} options:redirect-type], [0], ]) AT_CLEANUP + +AT_SETUP([ovn -- check stateless dnat_and_snat rule]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +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 ls-add S1 +ovn-nbctl lsp-add S1 S1-R1 +ovn-nbctl lsp-set-type S1-R1 router +ovn-nbctl lsp-set-addresses S1-R1 router +ovn-nbctl --wait=sb lsp-set-options S1-R1 router-port=R1-S1 + +ovn-nbctl lrp-set-gateway-chassis R1-S1 gw1 + +uuid=`ovn-sbctl --columns=_uuid --bare find Port_Binding logical_port=cr-R1-S1` +echo "CR-LRP UUID is: " $uuid + +ovn-nbctl lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [2 +]) + +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [2 +]) + +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [0 +]) + +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [0 +]) + +ovn-nbctl lr-nat-del R1 dnat_and_snat 172.16.1.1 + +ovn-nbctl --stateless lr-nat-add R1 dnat_and_snat 172.16.1.1 50.0.0.11 +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_snat | wc -l], [0], [0 +]) + +AT_CHECK([ovn-sbctl dump-flows R1 | grep ct_dnat | wc -l], [0], [0 +]) + +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.dst=| wc -l], [0], [2 +]) + +AT_CHECK([ovn-sbctl dump-flows R1 | grep ip4.src=| wc -l], [0], [2 +]) + +AT_CLEANUP