Message ID | 1599512205-11041-2-git-send-email-svc.mail.git@nutanix.com |
---|---|
State | Accepted |
Headers | show |
Series | External IP based NAT | expand |
Bleep bloop. Greetings Ankur Sharma, I am a robot and I have tried out your patch. Thanks for your contribution. I encountered some error that I wasn't expecting. See the details below. checkpatch: ERROR: Improper whitespace around control block #199 FILE: utilities/ovn-nbctl.c:861: NBREC_ADDRESS_SET_FOR_EACH(iter, ctx->idl) { Lines checked: 316, Warnings: 0, Errors: 1 Please check this out. If you feel there has been an error, please email aconole@redhat.com Thanks, 0-day Robot
On Tue, Sep 8, 2020 at 2:28 AM Ankur Sharma <svc.mail.git@nutanix.com> wrote: > From: Ankur Sharma <ankur.sharma@nutanix.com> > > This patch adds following columns to NAT table. > > a. allowed_ext_ips: > Represents Address Set of External IPs for which > a NAT rule is applicable. > > b. exempted_ext_ips: > Represents Address Set of External IPs for which > a NAT rule is NOT applicable. > > Additionally, patch adds nbctl cli to set these column values. > ovn-nbctl [--is-exempted] lr-nat-update-ext-ip > > Signed-off-by: Ankur Sharma <ankur.sharma@nutanix.com> > Thanks Ankur. I applied this patch series to master. I think this requires updating the 'NEWS' file. Can you submit a follow up patch to add a news entry for it ? Thanks Numan --- > ovn-nb.ovsschema | 14 +++++- > ovn-nb.xml | 48 +++++++++++++++++++++ > tests/ovn-nbctl.at | 44 ++++++++++++++++++- > utilities/ovn-nbctl.c | 116 > +++++++++++++++++++++++++++++++++++++++++++++++++- > 4 files changed, 218 insertions(+), 4 deletions(-) > > diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema > index 0c939b7..62abf15 100644 > --- a/ovn-nb.ovsschema > +++ b/ovn-nb.ovsschema > @@ -1,7 +1,7 @@ > { > "name": "OVN_Northbound", > - "version": "5.25.0", > - "cksum": "1354137211 26116", > + "version": "5.26.0", > + "cksum": "1072595492 26575", > "tables": { > "NB_Global": { > "columns": { > @@ -403,6 +403,16 @@ > "snat", > > "dnat_and_snat" > ]]}}}, > + "allowed_ext_ips": {"type": { > + "key": {"type": "uuid", "refTable": "Address_Set", > + "refType": "strong"}, > + "min": 0, > + "max": 1}}, > + "exempted_ext_ips": {"type": { > + "key": {"type": "uuid", "refTable": "Address_Set", > + "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 1f2dbb9..09beae9 100644 > --- a/ovn-nb.xml > +++ b/ovn-nb.xml > @@ -2716,6 +2716,54 @@ > </p> > </column> > > + <column name="allowed_ext_ips"> > + It represents Address Set of external ips that NAT rule is > applicable to. > + For SNAT type NAT rules, this refers to destination addresses. > + For DNAT type NAT rules, this refers to source addresses. > + > + <p> > + This configuration overrides the default NAT behavior of applying > a > + rule solely based on internal IP. Without this configuration, NAT > + happens without considering the external IP (i.e dest/source for > + snat/dnat type rule). With this configuration NAT rule is applied > + ONLY if external ip is in the input Address Set. > + </p> > + </column> > + > + <column name="exempted_ext_ips"> > + It represents Address Set of external ips that NAT rule is NOT > + applicable to. > + For SNAT type NAT rules, this refers to destination addresses. > + For DNAT type NAT rules, this refers to source addresses. > + > + <p> > + This configuration overrides the default NAT behavior of applying > a > + rule solely based on internal IP. Without this configuration, NAT > + happens without considering the external IP (i.e dest/source for > + snat/dnat type rule). With this configuration NAT rule is NOT > applied > + if external ip is in the input Address Set. > + </p> > + > + <p> > + If there are NAT rules in a logical router with overlapping IP > prefixes > + (including /32), then usage of <var>exempted_ext_ips</var> should > be > + avoided in following scenario. > + a. SNAT rule (let us say RULE1) with logical_ip PREFIX/MASK > + (let us say 50.0.0.0/24). > + b. SNAT rule (let us say RULE2) with logical_ip PREFIX/MASK+1 > + (let us say 50.0.0.0/25). > + c. Now, if exempted_ext_ips is associated with RULE2, then a > logical > + ip which matches both 50.0.0.0/24 and 50.0.0.0/25 may get the > RULE2 > + applied to it instead of RULE1. > + </p> > + > + <p> > + <var>allowed_ext_ips</var> and <var>exempted_ext_ips</var> are > mutually > + exclusive to each other. If both Address Sets are set for a rule, > + then the NAT rule is not considered. > + </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 619051d..baf7a87 100644 > --- a/tests/ovn-nbctl.at > +++ b/tests/ovn-nbctl.at > @@ -685,7 +685,49 @@ snat 40.0.0.3 21-65535 > 192.168.1.6 > AT_CHECK([ovn-nbctl lr-nat-del lr0]) > AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], []) > AT_CHECK([ovn-nbctl lr-nat-del lr0]) > -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])]) > +AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat]) > + > +AT_CHECK([ovn-nbctl lr-nat-del lr0]) > + > +ovn-nbctl create Address_Set name=allowed_range addresses=\"1.1.1.1\" > +ovn-nbctl create Address_Set name=disallowed_range addresses=\"2.2.2.2\" > +AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 40.0.0.3 192.168.1.6]) > +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.1.6 > allowed_range]) > +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 snat > 192.168.1.6 disallowed_range]) > +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.1.6 > allowed_range_tmp], [1], [], > +[ovn-nbctl: allowed_range_tmp: Address Set name not found > +]) > +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 snat > 192.168.1.6 disallowed_range_tmp], [1], [], > +[ovn-nbctl: disallowed_range_tmp: Address Set name not found > +]) > + > +AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 40.0.0.4 192.168.1.7]) > +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat 40.0.0.4 allowed_range]) > +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 > disallowed_range]) > +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat 40.0.0.4 > allowed_range_tmp], [1], [], > +[ovn-nbctl: allowed_range_tmp: Address Set name not found > +]) > +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 > disallowed_range_tmp], [1], [], > +[ovn-nbctl: disallowed_range_tmp: Address Set name not found > +]) > + > +AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 40.0.0.5 192.168.1.8]) > +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat_and_snat 40.0.0.5 > allowed_range]) > +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 > disallowed_range]) > +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat 40.0.0.4 > allowed_range_tmp], [1], [], > +[ovn-nbctl: allowed_range_tmp: Address Set name not found > +]) > +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 > disallowed_range_tmp], [1], [], > +[ovn-nbctl: disallowed_range_tmp: Address Set name not found > +]) > +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.1.6], [1], [], > +[ovn-nbctl: 'lr-nat-update-ext-ip' command requires at least 4 arguments > +]) > +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])]) > > dnl --------------------------------------------------------------------- > > diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c > index 542cbf7..42dcad7 100644 > --- a/utilities/ovn-nbctl.c > +++ b/utilities/ovn-nbctl.c > @@ -839,6 +839,46 @@ lr_by_name_or_uuid(struct ctl_context *ctx, const > char *id, > return NULL; > } > > +/* Find an Address Set given its id. */ > +static char * OVS_WARN_UNUSED_RESULT > +address_set_by_name_or_uuid(struct ctl_context *ctx, > + const char *id, bool must_exist, > + const struct nbrec_address_set **addr_set_p) > +{ > + const struct nbrec_address_set *addr_set = NULL; > + bool is_uuid = false; > + struct uuid addr_set_uuid; > + > + *addr_set_p = NULL; > + if (uuid_from_string(&addr_set_uuid, id)) { > + is_uuid = true; > + addr_set = nbrec_address_set_get_for_uuid(ctx->idl, > &addr_set_uuid); > + } > + > + if (!addr_set) { > + const struct nbrec_address_set *iter; > + > + NBREC_ADDRESS_SET_FOR_EACH(iter, ctx->idl) { > + if (strcmp(iter->name, id)) { > + continue; > + } > + if (addr_set) { > + return xasprintf("Multiple Address Sets named '%s'. " > + "Use a UUID.", id); > + } > + addr_set = iter; > + } > + } > + > + if (!addr_set && must_exist) { > + return xasprintf("%s: Address Set %s not found", > + id, is_uuid ? "UUID" : "name"); > + } > + > + *addr_set_p = addr_set; > + return NULL; > +} > + > static char * OVS_WARN_UNUSED_RESULT > ls_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool > must_exist, > const struct nbrec_logical_switch **ls_p) > @@ -4486,6 +4526,79 @@ nbctl_lr_nat_list(struct ctl_context *ctx) > smap_destroy(&lr_nats); > } > > +static void > +nbctl_lr_nat_set_ext_ips(struct ctl_context *ctx) > +{ > + const struct nbrec_logical_router *lr = NULL; > + const struct nbrec_address_set *addr_set = NULL; > + bool is_exempted = shash_find(&ctx->options, "--is-exempted"); > + bool nat_found = false; > + > + if (ctx->argc < 5) { > + ctl_error(ctx, "Incomplete input, Required arguments are: " > + "ROUTER TYPE IP ADDRESS_SET"); > + return; > + } > + > + char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr); > + if (error) { > + ctx->error = error; > + return; > + } > + > + const char *nat_type = ctx->argv[2]; > + if (strcmp(nat_type, "dnat") && strcmp(nat_type, "snat") > + && strcmp(nat_type, "dnat_and_snat")) { > + ctl_error(ctx, "%s: type must be one of \"dnat\", \"snat\" and " > + "\"dnat_and_snat\".", nat_type); > + return; > + } > + > + error = address_set_by_name_or_uuid(ctx, ctx->argv[4], true, > &addr_set); > + if (error) { > + ctx->error = error; > + return; > + } > + > + char *nat_ip = normalize_prefix_str(ctx->argv[3]); > + if (!nat_ip) { > + ctl_error(ctx, "%s: Invalid IP address or CIDR", ctx->argv[3]); > + return; > + } > + > + int is_snat = !strcmp("snat", nat_type); > + > + /* Update the matching NAT. */ > + 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)) { > + nat_found = true; > + nbrec_logical_router_verify_nat(lr); > + if (is_exempted) { > + nbrec_nat_set_exempted_ext_ips(nat, addr_set); > + } else { > + nbrec_nat_set_allowed_ext_ips(nat, addr_set); > + } > + return; > + } > + } > + > + if (!nat_found) { > + ctl_error(ctx, "%s: Could not locate nat rule for: %s.", > + nat_type, nat_ip); > + } > + > + free(nat_ip); > +} > + > > static char * OVS_WARN_UNUSED_RESULT > lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool > must_exist, > @@ -6402,7 +6515,8 @@ static const struct ctl_command_syntax > nbctl_commands[] = { > { "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 }, > - > + { "lr-nat-update-ext-ip", 4, 4, "ROUTER TYPE IP ADDRESS_SET", NULL, > + nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW}, > /* load balancer commands. */ > { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL, > nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW }, > -- > 1.8.3.1 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > https://mail.openvswitch.org/mailman/listinfo/ovs-dev > >
Hi Numan, Thanks a lot for review and applying the patch. Sure, just sent out the patch for 'NEWS '. Regards, Ankur
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema index 0c939b7..62abf15 100644 --- a/ovn-nb.ovsschema +++ b/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.25.0", - "cksum": "1354137211 26116", + "version": "5.26.0", + "cksum": "1072595492 26575", "tables": { "NB_Global": { "columns": { @@ -403,6 +403,16 @@ "snat", "dnat_and_snat" ]]}}}, + "allowed_ext_ips": {"type": { + "key": {"type": "uuid", "refTable": "Address_Set", + "refType": "strong"}, + "min": 0, + "max": 1}}, + "exempted_ext_ips": {"type": { + "key": {"type": "uuid", "refTable": "Address_Set", + "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 1f2dbb9..09beae9 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -2716,6 +2716,54 @@ </p> </column> + <column name="allowed_ext_ips"> + It represents Address Set of external ips that NAT rule is applicable to. + For SNAT type NAT rules, this refers to destination addresses. + For DNAT type NAT rules, this refers to source addresses. + + <p> + This configuration overrides the default NAT behavior of applying a + rule solely based on internal IP. Without this configuration, NAT + happens without considering the external IP (i.e dest/source for + snat/dnat type rule). With this configuration NAT rule is applied + ONLY if external ip is in the input Address Set. + </p> + </column> + + <column name="exempted_ext_ips"> + It represents Address Set of external ips that NAT rule is NOT + applicable to. + For SNAT type NAT rules, this refers to destination addresses. + For DNAT type NAT rules, this refers to source addresses. + + <p> + This configuration overrides the default NAT behavior of applying a + rule solely based on internal IP. Without this configuration, NAT + happens without considering the external IP (i.e dest/source for + snat/dnat type rule). With this configuration NAT rule is NOT applied + if external ip is in the input Address Set. + </p> + + <p> + If there are NAT rules in a logical router with overlapping IP prefixes + (including /32), then usage of <var>exempted_ext_ips</var> should be + avoided in following scenario. + a. SNAT rule (let us say RULE1) with logical_ip PREFIX/MASK + (let us say 50.0.0.0/24). + b. SNAT rule (let us say RULE2) with logical_ip PREFIX/MASK+1 + (let us say 50.0.0.0/25). + c. Now, if exempted_ext_ips is associated with RULE2, then a logical + ip which matches both 50.0.0.0/24 and 50.0.0.0/25 may get the RULE2 + applied to it instead of RULE1. + </p> + + <p> + <var>allowed_ext_ips</var> and <var>exempted_ext_ips</var> are mutually + exclusive to each other. If both Address Sets are set for a rule, + then the NAT rule is not considered. + </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 619051d..baf7a87 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at @@ -685,7 +685,49 @@ snat 40.0.0.3 21-65535 192.168.1.6 AT_CHECK([ovn-nbctl lr-nat-del lr0]) AT_CHECK([ovn-nbctl lr-nat-list lr0], [0], []) AT_CHECK([ovn-nbctl lr-nat-del lr0]) -AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat])]) +AT_CHECK([ovn-nbctl lr-nat-del lr0 dnat]) + +AT_CHECK([ovn-nbctl lr-nat-del lr0]) + +ovn-nbctl create Address_Set name=allowed_range addresses=\"1.1.1.1\" +ovn-nbctl create Address_Set name=disallowed_range addresses=\"2.2.2.2\" +AT_CHECK([ovn-nbctl lr-nat-add lr0 snat 40.0.0.3 192.168.1.6]) +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.1.6 allowed_range]) +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 snat 192.168.1.6 disallowed_range]) +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.1.6 allowed_range_tmp], [1], [], +[ovn-nbctl: allowed_range_tmp: Address Set name not found +]) +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 snat 192.168.1.6 disallowed_range_tmp], [1], [], +[ovn-nbctl: disallowed_range_tmp: Address Set name not found +]) + +AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat 40.0.0.4 192.168.1.7]) +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat 40.0.0.4 allowed_range]) +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 disallowed_range]) +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat 40.0.0.4 allowed_range_tmp], [1], [], +[ovn-nbctl: allowed_range_tmp: Address Set name not found +]) +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 disallowed_range_tmp], [1], [], +[ovn-nbctl: disallowed_range_tmp: Address Set name not found +]) + +AT_CHECK([ovn-nbctl lr-nat-add lr0 dnat_and_snat 40.0.0.5 192.168.1.8]) +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat_and_snat 40.0.0.5 allowed_range]) +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 disallowed_range]) +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 dnat 40.0.0.4 allowed_range_tmp], [1], [], +[ovn-nbctl: allowed_range_tmp: Address Set name not found +]) +AT_CHECK([ovn-nbctl --is-exempted lr-nat-update-ext-ip lr0 dnat 40.0.0.4 disallowed_range_tmp], [1], [], +[ovn-nbctl: disallowed_range_tmp: Address Set name not found +]) +AT_CHECK([ovn-nbctl lr-nat-update-ext-ip lr0 snat 192.168.1.6], [1], [], +[ovn-nbctl: 'lr-nat-update-ext-ip' command requires at least 4 arguments +]) +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])]) dnl --------------------------------------------------------------------- diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index 542cbf7..42dcad7 100644 --- a/utilities/ovn-nbctl.c +++ b/utilities/ovn-nbctl.c @@ -839,6 +839,46 @@ lr_by_name_or_uuid(struct ctl_context *ctx, const char *id, return NULL; } +/* Find an Address Set given its id. */ +static char * OVS_WARN_UNUSED_RESULT +address_set_by_name_or_uuid(struct ctl_context *ctx, + const char *id, bool must_exist, + const struct nbrec_address_set **addr_set_p) +{ + const struct nbrec_address_set *addr_set = NULL; + bool is_uuid = false; + struct uuid addr_set_uuid; + + *addr_set_p = NULL; + if (uuid_from_string(&addr_set_uuid, id)) { + is_uuid = true; + addr_set = nbrec_address_set_get_for_uuid(ctx->idl, &addr_set_uuid); + } + + if (!addr_set) { + const struct nbrec_address_set *iter; + + NBREC_ADDRESS_SET_FOR_EACH(iter, ctx->idl) { + if (strcmp(iter->name, id)) { + continue; + } + if (addr_set) { + return xasprintf("Multiple Address Sets named '%s'. " + "Use a UUID.", id); + } + addr_set = iter; + } + } + + if (!addr_set && must_exist) { + return xasprintf("%s: Address Set %s not found", + id, is_uuid ? "UUID" : "name"); + } + + *addr_set_p = addr_set; + return NULL; +} + static char * OVS_WARN_UNUSED_RESULT ls_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, const struct nbrec_logical_switch **ls_p) @@ -4486,6 +4526,79 @@ nbctl_lr_nat_list(struct ctl_context *ctx) smap_destroy(&lr_nats); } +static void +nbctl_lr_nat_set_ext_ips(struct ctl_context *ctx) +{ + const struct nbrec_logical_router *lr = NULL; + const struct nbrec_address_set *addr_set = NULL; + bool is_exempted = shash_find(&ctx->options, "--is-exempted"); + bool nat_found = false; + + if (ctx->argc < 5) { + ctl_error(ctx, "Incomplete input, Required arguments are: " + "ROUTER TYPE IP ADDRESS_SET"); + return; + } + + char *error = lr_by_name_or_uuid(ctx, ctx->argv[1], true, &lr); + if (error) { + ctx->error = error; + return; + } + + const char *nat_type = ctx->argv[2]; + if (strcmp(nat_type, "dnat") && strcmp(nat_type, "snat") + && strcmp(nat_type, "dnat_and_snat")) { + ctl_error(ctx, "%s: type must be one of \"dnat\", \"snat\" and " + "\"dnat_and_snat\".", nat_type); + return; + } + + error = address_set_by_name_or_uuid(ctx, ctx->argv[4], true, &addr_set); + if (error) { + ctx->error = error; + return; + } + + char *nat_ip = normalize_prefix_str(ctx->argv[3]); + if (!nat_ip) { + ctl_error(ctx, "%s: Invalid IP address or CIDR", ctx->argv[3]); + return; + } + + int is_snat = !strcmp("snat", nat_type); + + /* Update the matching NAT. */ + 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)) { + nat_found = true; + nbrec_logical_router_verify_nat(lr); + if (is_exempted) { + nbrec_nat_set_exempted_ext_ips(nat, addr_set); + } else { + nbrec_nat_set_allowed_ext_ips(nat, addr_set); + } + return; + } + } + + if (!nat_found) { + ctl_error(ctx, "%s: Could not locate nat rule for: %s.", + nat_type, nat_ip); + } + + free(nat_ip); +} + static char * OVS_WARN_UNUSED_RESULT lrp_by_name_or_uuid(struct ctl_context *ctx, const char *id, bool must_exist, @@ -6402,7 +6515,8 @@ static const struct ctl_command_syntax nbctl_commands[] = { { "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 }, - + { "lr-nat-update-ext-ip", 4, 4, "ROUTER TYPE IP ADDRESS_SET", NULL, + nbctl_lr_nat_set_ext_ips, NULL, "--is-exempted", RW}, /* load balancer commands. */ { "lb-add", 3, 4, "LB VIP[:PORT] IP[:PORT]... [PROTOCOL]", NULL, nbctl_lb_add, NULL, "--may-exist,--add-duplicate", RW },