From 5d28c7afbc0abd172d0053768b2ebe37b7a6c348 Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Tue, 4 Sep 2018 03:55:45 -0700 Subject: [PATCH] NAT: add support for configurable port range (VPP-1346) Change-Id: I6882b6daa05db866fe6e78a62b380ec331507f74 Signed-off-by: Matus Fabian --- src/plugins/nat/nat.api | 55 ++++++++++++++++++++++++++ src/plugins/nat/nat.c | 84 +++++++++++++++++++++++++++++++++++++++ src/plugins/nat/nat.h | 19 +++++++++ src/plugins/nat/nat44_cli.c | 60 +++++++++++++++++++++++++++- src/plugins/nat/nat_api.c | 95 ++++++++++++++++++++++++++++++++++++++++++++- test/test_nat.py | 35 +++++++++++++++-- test/vpp_papi_provider.py | 30 ++++++++++++++ 7 files changed, 372 insertions(+), 6 deletions(-) diff --git a/src/plugins/nat/nat.api b/src/plugins/nat/nat.api index f1c95b2d65b..1ff288dba2a 100644 --- a/src/plugins/nat/nat.api +++ b/src/plugins/nat/nat.api @@ -277,6 +277,61 @@ define nat_get_timeouts_reply { u32 icmp; }; +/** \brief Set address and port assignment algorithm + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param alg - address and port assignment algorithm: + 0 - default, 1 - MAP-E, 2 - port range + (see nat_addr_and_port_alloc_alg_t in nat.h) + @param psid_offset - number of offset bits (valid only for MAP-E alg) + @param psid_length - length of PSID (valid only for MAP-E alg) + @param psid - Port Set Identifier (PSID) value (valid only for MAP-E alg) + @param start_port - beginning of the port range + @param end_port - end of the port range +*/ +autoreply define nat_set_addr_and_port_alloc_alg { + u32 client_index; + u32 context; + u8 alg; + u8 psid_offset; + u8 psid_length; + u16 psid; + u16 start_port; + u16 end_port; +}; + +/** \brief Get address and port assignment algorithm + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat_get_addr_and_port_alloc_alg { + u32 client_index; + u32 context; +}; + +/** \brief Get address and port assignment algorithm reply + @param context - sender context, to match reply w/ request + @param retval - return code + @param alg - address and port assignment algorithm: + 0 - default, 1 - MAP-E, 2 - port range + (see nat_addr_and_port_alloc_alg_t in nat.h) + @param psid_offset - number of offset bits (valid only for MAP-E alg) + @param psid_length - length of PSID (valid only for MAP-E alg) + @param psid - Port Set Identifier (PSID) value (valid only for MAP-E alg) + @param start_port - beginning of the port range + @param end_port - end of the port range +*/ +define nat_get_addr_and_port_alloc_alg_reply { + u32 context; + i32 retval; + u8 alg; + u8 psid_offset; + u8 psid_length; + u16 psid; + u16 start_port; + u16 end_port; +}; + /* * NAT44 APIs */ diff --git a/src/plugins/nat/nat.c b/src/plugins/nat/nat.c index 0ce1a60c976..fe6d6cae82f 100755 --- a/src/plugins/nat/nat.c +++ b/src/plugins/nat/nat.c @@ -2062,6 +2062,7 @@ static clib_error_t * snat_init (vlib_main_t * vm) sm->tcp_transitory_timeout = SNAT_TCP_TRANSITORY_TIMEOUT; sm->icmp_timeout = SNAT_ICMP_TIMEOUT; sm->alloc_addr_and_port = nat_alloc_addr_and_port_default; + sm->addr_and_port_alloc_alg = NAT_ADDR_AND_PORT_ALLOC_ALG_DEFAULT; sm->forwarding_enabled = 0; sm->log_class = vlib_log_register_class ("nat", 0); error_drop_node = vlib_get_node_by_name (vm, (u8 *) "error-drop"); @@ -2466,6 +2467,57 @@ exhausted: return 1; } +static int +nat_alloc_addr_and_port_range (snat_address_t * addresses, + u32 fib_index, + u32 thread_index, + snat_session_key_t * k, + u32 * address_indexp, + u16 port_per_thread, + u32 snat_thread_index) +{ + snat_main_t *sm = &snat_main; + snat_address_t *a = addresses; + u16 portnum, ports; + + ports = sm->end_port - sm->start_port + 1; + + if (!vec_len (addresses)) + goto exhausted; + + switch (k->protocol) + { +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + if (a->busy_##n##_ports < ports) \ + { \ + while (1) \ + { \ + portnum = snat_random_port(sm->start_port, sm->end_port); \ + if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, portnum)) \ + continue; \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, portnum, 1); \ + a->busy_##n##_ports++; \ + k->addr = a->addr; \ + k->port = clib_host_to_net_u16 (portnum); \ + *address_indexp = i; \ + return 0; \ + } \ + } \ + break; + foreach_snat_protocol +#undef _ + default: + nat_log_info ("unknown protocol"); + return 1; + } + +exhausted: + /* Totally out of translations to use... */ + snat_ipfix_logging_addresses_exhausted(0); + return 1; +} + void nat44_add_del_address_dpo (ip4_address_t addr, u8 is_add) { @@ -2522,6 +2574,25 @@ format_snat_protocol (u8 * s, va_list * args) return s; } +u8 * +format_nat_addr_and_port_alloc_alg (u8 * s, va_list * args) +{ + u32 i = va_arg (*args, u32); + u8 *t = 0; + + switch (i) + { +#define _(v, N, s) case NAT_ADDR_AND_PORT_ALLOC_ALG_##N: t = (u8 *) s; break; + foreach_nat_addr_and_port_alloc_alg +#undef _ + default: + s = format (s, "unknown"); + return s; + } + s = format (s, "%s", t); + return s; +} + u8 * format_snat_key (u8 * s, va_list * args); u8 * format_static_mapping_key (u8 * s, va_list * args); @@ -3550,17 +3621,30 @@ nat_set_alloc_addr_and_port_mape (u16 psid, u16 psid_offset, u16 psid_length) { snat_main_t *sm = &snat_main; + sm->addr_and_port_alloc_alg = NAT_ADDR_AND_PORT_ALLOC_ALG_MAPE; sm->alloc_addr_and_port = nat_alloc_addr_and_port_mape; sm->psid = psid; sm->psid_offset = psid_offset; sm->psid_length = psid_length; } +void +nat_set_alloc_addr_and_port_range (u16 start_port, u16 end_port) +{ + snat_main_t *sm = &snat_main; + + sm->addr_and_port_alloc_alg = NAT_ADDR_AND_PORT_ALLOC_ALG_RANGE; + sm->alloc_addr_and_port = nat_alloc_addr_and_port_range; + sm->start_port = start_port; + sm->end_port = end_port; +} + void nat_set_alloc_addr_and_port_default (void) { snat_main_t *sm = &snat_main; + sm->addr_and_port_alloc_alg = NAT_ADDR_AND_PORT_ALLOC_ALG_DEFAULT; sm->alloc_addr_and_port = nat_alloc_addr_and_port_default; } diff --git a/src/plugins/nat/nat.h b/src/plugins/nat/nat.h index 660fb4c2965..d8d0b2a9c4d 100644 --- a/src/plugins/nat/nat.h +++ b/src/plugins/nat/nat.h @@ -96,6 +96,16 @@ typedef struct { }; } snat_user_key_t; +#define foreach_nat_addr_and_port_alloc_alg \ + _(0, DEFAULT, "default") \ + _(1, MAPE, "map-e") \ + _(2, RANGE, "port-range") + +typedef enum { +#define _(v, N, s) NAT_ADDR_AND_PORT_ALLOC_ALG_##N = v, + foreach_nat_addr_and_port_alloc_alg +#undef _ +} nat_addr_and_port_alloc_alg_t; #define foreach_snat_protocol \ _(UDP, 0, udp, "udp") \ @@ -361,10 +371,17 @@ typedef struct snat_main_s { /* Vector of outside addresses */ snat_address_t * addresses; + /* Address and port allocation function */ nat_alloc_out_addr_and_port_function_t *alloc_addr_and_port; + /* Address and port allocation type */ + nat_addr_and_port_alloc_alg_t addr_and_port_alloc_alg; + /* Port set parameters (MAP-E) */ u8 psid_offset; u8 psid_length; u16 psid; + /* Port range parameters */ + u16 start_port; + u16 end_port; /* vector of outside fibs */ nat_outside_fib_t * outside_fibs; @@ -631,6 +648,7 @@ int snat_add_interface_address(snat_main_t *sm, u32 sw_if_index, int is_del, u8 twice_nat); uword unformat_snat_protocol(unformat_input_t * input, va_list * args); u8 * format_snat_protocol(u8 * s, va_list * args); +u8 * format_nat_addr_and_port_alloc_alg(u8 * s, va_list * args); int nat44_add_del_lb_static_mapping (ip4_address_t e_addr, u16 e_port, snat_protocol_t proto, nat44_lb_addr_port_t *locals, u8 is_add, @@ -651,6 +669,7 @@ snat_session_t * nat_ed_session_alloc (snat_main_t *sm, snat_user_t *u, u32 thread_index); void nat_set_alloc_addr_and_port_mape (u16 psid, u16 psid_offset, u16 psid_length); +void nat_set_alloc_addr_and_port_range (u16 start_port, u16 end_port); void nat_set_alloc_addr_and_port_default (void); int nat44_i2o_ed_is_idle_session_cb (clib_bihash_kv_16_8_t *kv, void *arg); int nat44_o2i_ed_is_idle_session_cb (clib_bihash_kv_16_8_t *kv, void *arg); diff --git a/src/plugins/nat/nat44_cli.c b/src/plugins/nat/nat44_cli.c index 3847502ca7e..17a3827dee6 100644 --- a/src/plugins/nat/nat44_cli.c +++ b/src/plugins/nat/nat44_cli.c @@ -214,7 +214,7 @@ nat44_set_alloc_addr_and_port_alg_command_fn (vlib_main_t * vm, unformat_input_t _line_input, *line_input = &_line_input; snat_main_t *sm = &snat_main; clib_error_t *error = 0; - u32 psid, psid_offset, psid_length; + u32 psid, psid_offset, psid_length, port_start, port_end; if (sm->deterministic) return clib_error_return (0, UNSUPPORTED_IN_DET_MODE_STR); @@ -233,6 +233,20 @@ nat44_set_alloc_addr_and_port_alg_command_fn (vlib_main_t * vm, &psid_offset, &psid_length)) nat_set_alloc_addr_and_port_mape ((u16) psid, (u16) psid_offset, (u16) psid_length); + else + if (unformat + (line_input, "port-range %d - %d", &port_start, &port_end)) + { + if (port_end <= port_start) + { + error = + clib_error_return (0, + "The end-port must be greater than start-port"); + goto done; + } + nat_set_alloc_addr_and_port_range ((u16) port_start, + (u16) port_end); + } else { error = clib_error_return (0, "unknown input '%U'", @@ -247,6 +261,36 @@ done: return error; }; +static clib_error_t * +nat44_show_alloc_addr_and_port_alg_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + + if (sm->deterministic) + return clib_error_return (0, UNSUPPORTED_IN_DET_MODE_STR); + + vlib_cli_output (vm, "NAT address and port: %U", + format_nat_addr_and_port_alloc_alg, + sm->addr_and_port_alloc_alg); + switch (sm->addr_and_port_alloc_alg) + { + case NAT_ADDR_AND_PORT_ALLOC_ALG_MAPE: + vlib_cli_output (vm, " psid %d psid-offset %d psid-len %d", sm->psid, + sm->psid_offset, sm->psid_length); + break; + case NAT_ADDR_AND_PORT_ALLOC_ALG_RANGE: + vlib_cli_output (vm, " start-port %d end-port %d", sm->start_port, + sm->end_port); + break; + default: + break; + } + + return 0; +} + static clib_error_t * add_address_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) @@ -1634,6 +1678,8 @@ VLIB_CLI_COMMAND (snat_ipfix_logging_enable_disable_command, static) = { * Set address and port assignment algorithm * For the MAP-E CE limit port choice based on PSID use: * vpp# nat addr-port-assignment-alg map-e psid 10 psid-offset 6 psid-len 6 + * For port range use: + * vpp# nat addr-port-assignment-alg port-range - * To set standard (default) address and port assignment algorithm use: * vpp# nat addr-port-assignment-alg default * @cliexend @@ -1644,6 +1690,18 @@ VLIB_CLI_COMMAND (nat44_set_alloc_addr_and_port_alg_command, static) = { .function = nat44_set_alloc_addr_and_port_alg_command_fn, }; +/*? + * @cliexpar + * @cliexstart{show nat addr-port-assignment-alg} + * Show address and port assignment algorithm + * @cliexend +?*/ +VLIB_CLI_COMMAND (nat44_show_alloc_addr_and_port_alg_command, static) = { + .path = "show nat addr-port-assignment-alg", + .short_help = "show nat addr-port-assignment-alg", + .function = nat44_show_alloc_addr_and_port_alg_command_fn, +}; + /*? * @cliexpar * @cliexstart{show nat44 hash tables} diff --git a/src/plugins/nat/nat_api.c b/src/plugins/nat/nat_api.c index 17009c93561..2994f8001f4 100644 --- a/src/plugins/nat/nat_api.c +++ b/src/plugins/nat/nat_api.c @@ -484,6 +484,95 @@ vl_api_nat_get_timeouts_t_print (vl_api_nat_get_timeouts_t * mp, void *handle) FINISH; } +static void + vl_api_nat_set_addr_and_port_alloc_alg_t_handler + (vl_api_nat_set_addr_and_port_alloc_alg_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_set_addr_and_port_alloc_alg_reply_t *rmp; + int rv = 0; + u16 port_start, port_end; + + if (sm->deterministic) + { + rv = VNET_API_ERROR_UNSUPPORTED; + goto send_reply; + } + + switch (mp->alg) + { + case NAT_ADDR_AND_PORT_ALLOC_ALG_DEFAULT: + nat_set_alloc_addr_and_port_default (); + break; + case NAT_ADDR_AND_PORT_ALLOC_ALG_MAPE: + nat_set_alloc_addr_and_port_mape (ntohs (mp->psid), mp->psid_offset, + mp->psid_length); + break; + case NAT_ADDR_AND_PORT_ALLOC_ALG_RANGE: + port_start = ntohs (mp->start_port); + port_end = ntohs (mp->end_port); + if (port_end <= port_start) + { + rv = VNET_API_ERROR_INVALID_VALUE; + goto send_reply; + } + nat_set_alloc_addr_and_port_range (port_start, port_end); + break; + default: + rv = VNET_API_ERROR_INVALID_VALUE; + break; + } + +send_reply: + REPLY_MACRO (VL_API_NAT_SET_ADDR_AND_PORT_ALLOC_ALG_REPLY); +} + +static void *vl_api_nat_set_addr_and_port_alloc_alg_t_print + (vl_api_nat_set_addr_and_port_alloc_alg_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_set_addr_and_port_alloc_alg "); + s = format (s, "alg %d psid_offset %d psid_length %d psid %d start_port %d " + "end_port %d\n", + ntohl (mp->alg), ntohl (mp->psid_offset), + ntohl (mp->psid_length), ntohs (mp->psid), + ntohs (mp->start_port), ntohs (mp->end_port)); + + FINISH; +} + +static void + vl_api_nat_get_addr_and_port_alloc_alg_t_handler + (vl_api_nat_get_addr_and_port_alloc_alg_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_get_addr_and_port_alloc_alg_reply_t *rmp; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT_GET_ADDR_AND_PORT_ALLOC_ALG_REPLY, + ({ + rmp->alg = sm->addr_and_port_alloc_alg; + rmp->psid_offset = sm->psid_offset; + rmp->psid_length = sm->psid_length; + rmp->psid = htons (sm->psid); + rmp->start_port = htons (sm->start_port); + rmp->end_port = htons (sm->end_port); + })) + /* *INDENT-ON* */ +} + +static void *vl_api_nat_get_addr_and_port_alloc_alg_t_print + (vl_api_nat_get_addr_and_port_alloc_alg_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_get_addr_and_port_alloc_alg"); + + FINISH; +} + /*************/ /*** NAT44 ***/ /*************/ @@ -2990,8 +3079,10 @@ _(NAT_IPFIX_ENABLE_DISABLE, nat_ipfix_enable_disable) \ _(NAT_SET_REASS, nat_set_reass) \ _(NAT_GET_REASS, nat_get_reass) \ _(NAT_REASS_DUMP, nat_reass_dump) \ -_(NAT_SET_TIMEOUTS, nat_set_timeouts) \ -_(NAT_GET_TIMEOUTS, nat_get_timeouts) \ +_(NAT_SET_TIMEOUTS, nat_set_timeouts) \ +_(NAT_GET_TIMEOUTS, nat_get_timeouts) \ +_(NAT_SET_ADDR_AND_PORT_ALLOC_ALG, nat_set_addr_and_port_alloc_alg) \ +_(NAT_GET_ADDR_AND_PORT_ALLOC_ALG, nat_get_addr_and_port_alloc_alg) \ _(NAT44_ADD_DEL_ADDRESS_RANGE, nat44_add_del_address_range) \ _(NAT44_INTERFACE_ADD_DEL_FEATURE, nat44_interface_add_del_feature) \ _(NAT44_ADD_DEL_STATIC_MAPPING, nat44_add_del_static_mapping) \ diff --git a/test/test_nat.py b/test/test_nat.py index 73e414ad1ae..6152a7eadf4 100644 --- a/test/test_nat.py +++ b/test/test_nat.py @@ -138,6 +138,7 @@ class MethodHolder(VppTestCase): self.vapi.nat_set_reass(is_ip6=1) self.verify_no_nat44_user() self.vapi.nat_set_timeouts() + self.vapi.nat_set_addr_and_port_alloc_alg() def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0', local_port=0, external_port=0, vrf_id=0, @@ -3098,8 +3099,10 @@ class TestNAT44(MethodHolder): self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index) self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index, is_inside=0) - self.vapi.cli("nat addr-port-assignment-alg map-e psid 10 " - "psid-offset 6 psid-len 6") + self.vapi.nat_set_addr_and_port_alloc_alg(alg=1, + psid_offset=6, + psid_length=6, + psid=10) p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) / @@ -3122,6 +3125,31 @@ class TestNAT44(MethodHolder): self.logger.error(ppp("Unexpected or invalid packet:", p)) raise + def test_port_range(self): + """ External address port range """ + self.nat44_add_address(self.nat_addr) + self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index) + self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index, + is_inside=0) + self.vapi.nat_set_addr_and_port_alloc_alg(alg=2, + start_port=1025, + end_port=1027) + + pkts = [] + for port in range(0, 5): + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) / + TCP(sport=1125 + port)) + pkts.append(p) + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg1.get_capture(3) + for p in capture: + tcp = p[TCP] + self.assertGreaterEqual(tcp.sport, 1025) + self.assertLessEqual(tcp.sport, 1027) + def test_ipfix_max_frags(self): """ IPFIX logging maximum fragments pending reassembly exceeded """ self.nat44_add_address(self.nat_addr) @@ -3290,7 +3318,8 @@ class TestNAT44(MethodHolder): self.logger.info(self.vapi.cli("show nat virtual-reassembly")) self.logger.info(self.vapi.cli("show nat44 hash tables detail")) self.logger.info(self.vapi.cli("show nat timeouts")) - self.vapi.cli("nat addr-port-assignment-alg default") + self.logger.info( + self.vapi.cli("show nat addr-port-assignment-alg")) self.clear_nat44() self.vapi.cli("clear logging") diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index e0d55c16cee..9a3a9741ed9 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1797,6 +1797,36 @@ class VppPapiProvider(object): """ return self.api(self.papi.nat_get_timeouts, {}) + def nat_set_addr_and_port_alloc_alg( + self, + alg=0, + psid_offset=0, + psid_length=0, + psid=0, + start_port=0, + end_port=0): + """Set address and port assignment algorithm + + :param alg: algorithm: 0 - default, 1 - MAP-E, 2 - port range + :param psid_offset: number of offset bits (valid only for MAP-E alg) + :param psid_length: length of PSID (valid only for MAP-E alg) + :param psid: Port Set Identifier value (valid only for MAP-E alg) + :param start_port: beginning of the port range + :param end_port: end of the port range + """ + return self.api( + self.papi.nat_set_addr_and_port_alloc_alg, + {'alg': alg, + 'psid_offset': psid_offset, + 'psid_length': psid_length, + 'psid': psid, + 'start_port': start_port, + 'end_port': end_port}) + + def nat_get_addr_and_port_alloc_alg(self): + """Get address and port assignment algorithm""" + return self.api(self.papi.nat_get_addr_and_port_alloc_alg, {}) + def nat_det_close_session_out( self, out_addr, -- 2.16.6