From bb4e022502dd7f76d4f1cd705a7bac628d8c098c Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 13 Sep 2018 02:36:25 -0700 Subject: [PATCH] NAT: TCP MSS clamping NAT plugin changes the MSS value in TCP SYN packets to avoid fragmentation. If the negotiated MSS value is greater than the configured value it is changed to the configured value. If the negotiated MSS value is smaller than the configured value it remains unchanged. Change-Id: Ic3c4f94a2f1b76e2bf79f50f3ad36a4097f3f188 Signed-off-by: Matus Fabian --- src/plugins/nat/dslite_in2out.c | 1 + src/plugins/nat/in2out.c | 11 ++++++ src/plugins/nat/nat.api | 35 +++++++++++++++++++ src/plugins/nat/nat.c | 1 + src/plugins/nat/nat.h | 4 +++ src/plugins/nat/nat44_cli.c | 76 +++++++++++++++++++++++++++++++++++++++++ src/plugins/nat/nat64_in2out.c | 1 + src/plugins/nat/nat_api.c | 59 ++++++++++++++++++++++++++++++++ src/plugins/nat/nat_inlines.h | 48 ++++++++++++++++++++++++++ test/test_nat.py | 52 ++++++++++++++++++++++++++++ test/vpp_papi_provider.py | 14 ++++++++ 11 files changed, 302 insertions(+) diff --git a/src/plugins/nat/dslite_in2out.c b/src/plugins/nat/dslite_in2out.c index bf51ca23051..8f5a0c0e248 100644 --- a/src/plugins/nat/dslite_in2out.c +++ b/src/plugins/nat/dslite_in2out.c @@ -377,6 +377,7 @@ dslite_in2out_node_fn_inline (vlib_main_t * vm, vlib_node_runtime_t * node, sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t, length); + mss_clamping (&snat_main, tcp0, &sum0); tcp0->checksum = ip_csum_fold (sum0); } else diff --git a/src/plugins/nat/in2out.c b/src/plugins/nat/in2out.c index 22a346855c6..661d8c1bef9 100755 --- a/src/plugins/nat/in2out.c +++ b/src/plugins/nat/in2out.c @@ -1336,6 +1336,7 @@ snat_in2out_node_fn_inline (vlib_main_t * vm, sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t /* cheat */, length /* changed member */); + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); } else @@ -1496,6 +1497,7 @@ snat_in2out_node_fn_inline (vlib_main_t * vm, sum1 = ip_csum_update (sum1, old_port1, new_port1, ip4_header_t /* cheat */, length /* changed member */); + mss_clamping (sm, tcp1, &sum1); tcp1->checksum = ip_csum_fold(sum1); } else @@ -1693,6 +1695,7 @@ snat_in2out_node_fn_inline (vlib_main_t * vm, sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t /* cheat */, length /* changed member */); + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); } else @@ -3354,6 +3357,7 @@ nat44_ed_in2out_node_fn_inline (vlib_main_t * vm, tcp0->dst_port = s0->ext_host_port; ip0->dst_address.as_u32 = s0->ext_host_addr.as_u32; } + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); if (nat44_set_tcp_session_state_i2o (sm, s0, tcp0, thread_index)) goto trace00; @@ -3539,6 +3543,7 @@ nat44_ed_in2out_node_fn_inline (vlib_main_t * vm, ip1->dst_address.as_u32 = s1->ext_host_addr.as_u32; } tcp1->checksum = ip_csum_fold(sum1); + mss_clamping (sm, tcp1, &sum1); if (nat44_set_tcp_session_state_i2o (sm, s1, tcp1, thread_index)) goto trace01; } @@ -3751,6 +3756,7 @@ nat44_ed_in2out_node_fn_inline (vlib_main_t * vm, tcp0->dst_port = s0->ext_host_port; ip0->dst_address.as_u32 = s0->ext_host_addr.as_u32; } + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); if (nat44_set_tcp_session_state_i2o (sm, s0, tcp0, thread_index)) goto trace0; @@ -4122,6 +4128,7 @@ snat_det_in2out_node_fn (vlib_main_t * vm, sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t /* cheat */, length /* changed member */); + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); } else @@ -4272,6 +4279,7 @@ snat_det_in2out_node_fn (vlib_main_t * vm, sum1 = ip_csum_update (sum1, old_port1, new_port1, ip4_header_t /* cheat */, length /* changed member */); + mss_clamping (sm, tcp1, &sum1); tcp1->checksum = ip_csum_fold(sum1); } else @@ -4458,6 +4466,7 @@ snat_det_in2out_node_fn (vlib_main_t * vm, sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t /* cheat */, length /* changed member */); + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); } else @@ -5354,6 +5363,7 @@ snat_in2out_fast_static_map_fn (vlib_main_t * vm, sum0 = ip_csum_update (sum0, old_port0, new_port0, ip4_header_t /* cheat */, length /* changed member */); + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); } else @@ -5371,6 +5381,7 @@ snat_in2out_fast_static_map_fn (vlib_main_t * vm, sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, dst_address /* changed member */); + mss_clamping (sm, tcp0, &sum0); tcp0->checksum = ip_csum_fold(sum0); } } diff --git a/src/plugins/nat/nat.api b/src/plugins/nat/nat.api index 1ff288dba2a..6e2d8e57853 100644 --- a/src/plugins/nat/nat.api +++ b/src/plugins/nat/nat.api @@ -332,6 +332,41 @@ define nat_get_addr_and_port_alloc_alg_reply { u16 end_port; }; +/** \brief Set TCP MSS rewriting configuration + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param mss_value - MSS value to be used for MSS rewriting + @param enable - disable(0)/enable(1) MSS rewriting feature +*/ +autoreply define nat_set_mss_clamping { + u32 client_index; + u32 context; + u16 mss_value; + u8 enable; +}; + +/** \brief Get TCP MSS rewriting configuration + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat_get_mss_clamping { + u32 client_index; + u32 context; +}; + +/** \brief Get TCP MSS rewriting configuration reply + @param context - sender context, to match reply w/ request + @param retval - return code + @param mss_value - MSS value to be used for MSS rewriting + @param enable - disable(0)/enable(1) MSS rewriting feature +*/ +define nat_get_mss_clamping_reply { + u32 context; + i32 retval; + u16 mss_value; + u8 enable; +}; + /* * NAT44 APIs */ diff --git a/src/plugins/nat/nat.c b/src/plugins/nat/nat.c index ac81a9930dc..03e264eda3b 100755 --- a/src/plugins/nat/nat.c +++ b/src/plugins/nat/nat.c @@ -2078,6 +2078,7 @@ static clib_error_t * snat_init (vlib_main_t * vm) sm->log_class = vlib_log_register_class ("nat", 0); error_drop_node = vlib_get_node_by_name (vm, (u8 *) "error-drop"); sm->error_node_index = error_drop_node->index; + sm->mss_clamping = 0; p = hash_get_mem (tm->thread_registrations_by_name, "workers"); if (p) diff --git a/src/plugins/nat/nat.h b/src/plugins/nat/nat.h index d8d0b2a9c4d..06a61626333 100644 --- a/src/plugins/nat/nat.h +++ b/src/plugins/nat/nat.h @@ -439,6 +439,10 @@ typedef struct snat_main_s { u32 tcp_transitory_timeout; u32 icmp_timeout; + /* TCP MSS clamping */ + u16 mss_clamping; + u16 mss_value_net; + /* API message ID base */ u16 msg_id_base; diff --git a/src/plugins/nat/nat44_cli.c b/src/plugins/nat/nat44_cli.c index 17a3827dee6..4f02896f4b9 100644 --- a/src/plugins/nat/nat44_cli.c +++ b/src/plugins/nat/nat44_cli.c @@ -291,6 +291,56 @@ nat44_show_alloc_addr_and_port_alg_command_fn (vlib_main_t * vm, return 0; } +static clib_error_t * +nat_set_mss_clamping_command_fn (vlib_main_t * vm, unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + snat_main_t *sm = &snat_main; + clib_error_t *error = 0; + u32 mss; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "disable")) + sm->mss_clamping = 0; + else if (unformat (line_input, "%d", &mss)) + { + sm->mss_clamping = (u16) mss; + sm->mss_value_net = clib_host_to_net_u16 (sm->mss_clamping); + } + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + +done: + unformat_free (line_input); + + return error; +} + +static clib_error_t * +nat_show_mss_clamping_command_fn (vlib_main_t * vm, unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + + if (sm->mss_clamping) + vlib_cli_output (vm, "mss-clamping %d", sm->mss_clamping); + else + vlib_cli_output (vm, "mss-clamping disabled"); + + return 0; +} + static clib_error_t * add_address_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) @@ -1702,6 +1752,32 @@ VLIB_CLI_COMMAND (nat44_show_alloc_addr_and_port_alg_command, static) = { .function = nat44_show_alloc_addr_and_port_alg_command_fn, }; +/*? + * @cliexpar + * @cliexstart{nat mss-clamping} + * Set TCP MSS rewriting configuration + * To enable TCP MSS rewriting use: + * vpp# nat mss-clamping 1452 + * To disbale TCP MSS rewriting use: + * vpp# nat mss-clamping disable +?*/ +VLIB_CLI_COMMAND (nat_set_mss_clamping_command, static) = { + .path = "nat mss-clamping", + .short_help = "nat mss-clamping |disable", + .function = nat_set_mss_clamping_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{nat mss-clamping} + * Show TCP MSS rewriting configuration +?*/ +VLIB_CLI_COMMAND (nat_show_mss_clamping_command, static) = { + .path = "show nat mss-clamping", + .short_help = "show nat mss-clamping", + .function = nat_show_mss_clamping_command_fn, +}; + /*? * @cliexpar * @cliexstart{show nat44 hash tables} diff --git a/src/plugins/nat/nat64_in2out.c b/src/plugins/nat/nat64_in2out.c index ddbf5850c7c..e843fcf6730 100644 --- a/src/plugins/nat/nat64_in2out.c +++ b/src/plugins/nat/nat64_in2out.c @@ -239,6 +239,7 @@ nat64_in2out_tcp_udp_set_cb (ip6_header_t * ip6, ip4_header_t * ip4, checksum = &tcp->checksum; csum = ip_csum_sub_even (*checksum, sport); csum = ip_csum_add_even (csum, udp->src_port); + mss_clamping (nm->sm, tcp, &csum); *checksum = ip_csum_fold (csum); } diff --git a/src/plugins/nat/nat_api.c b/src/plugins/nat/nat_api.c index 2994f8001f4..4c532ed7e2b 100644 --- a/src/plugins/nat/nat_api.c +++ b/src/plugins/nat/nat_api.c @@ -573,6 +573,63 @@ static void *vl_api_nat_get_addr_and_port_alloc_alg_t_print FINISH; } +static void +vl_api_nat_set_mss_clamping_t_handler (vl_api_nat_set_mss_clamping_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_set_mss_clamping_reply_t *rmp; + int rv = 0; + + if (mp->enable) + { + sm->mss_clamping = ntohs (mp->mss_value); + sm->mss_value_net = mp->mss_value; + } + else + sm->mss_clamping = 0; + + REPLY_MACRO (VL_API_NAT_SET_MSS_CLAMPING_REPLY); +} + +static void * +vl_api_nat_set_mss_clamping_t_print (vl_api_nat_set_mss_clamping_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_set_mss_clamping enable %d mss_value %d\n", + mp->enable, ntohs (mp->mss_value)); + + FINISH; +} + +static void +vl_api_nat_get_mss_clamping_t_handler (vl_api_nat_get_mss_clamping_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_get_mss_clamping_reply_t *rmp; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT_GET_MSS_CLAMPING_REPLY, + ({ + rmp->enable = sm->mss_clamping ? 1 : 0; + rmp->mss_value = htons (sm->mss_clamping); + })) + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_get_mss_clamping_t_print (vl_api_nat_get_mss_clamping_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_get_mss_clamping"); + + FINISH; +} + /*************/ /*** NAT44 ***/ /*************/ @@ -3083,6 +3140,8 @@ _(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) \ +_(NAT_SET_MSS_CLAMPING, nat_set_mss_clamping) \ +_(NAT_GET_MSS_CLAMPING, nat_get_mss_clamping) \ _(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/src/plugins/nat/nat_inlines.h b/src/plugins/nat/nat_inlines.h index adfb1d51954..8922c05c393 100644 --- a/src/plugins/nat/nat_inlines.h +++ b/src/plugins/nat/nat_inlines.h @@ -328,6 +328,54 @@ make_sm_kv (clib_bihash_kv_8_8_t * kv, ip4_address_t * addr, u8 proto, kv->value = ~0ULL; } +always_inline void +mss_clamping (snat_main_t * sm, tcp_header_t * tcp, ip_csum_t * sum) +{ + u8 *data; + u8 opt_len, opts_len, kind; + u16 mss; + + if (!(sm->mss_clamping && tcp_syn (tcp))) + return; + + opts_len = (tcp_doff (tcp) << 2) - sizeof (tcp_header_t); + data = (u8 *) (tcp + 1); + for (; opts_len > 0; opts_len -= opt_len, data += opt_len) + { + kind = data[0]; + + if (kind == TCP_OPTION_EOL) + break; + else if (kind == TCP_OPTION_NOOP) + { + opt_len = 1; + continue; + } + else + { + if (opts_len < 2) + return; + opt_len = data[1]; + + if (opt_len < 2 || opt_len > opts_len) + return; + } + + if (kind == TCP_OPTION_MSS) + { + mss = *(u16 *) (data + 2); + if (clib_net_to_host_u16 (mss) > sm->mss_clamping) + { + *sum = + ip_csum_update (*sum, mss, sm->mss_value_net, ip4_header_t, + length); + clib_memcpy (data + 2, &sm->mss_value_net, 2); + } + return; + } + } +} + #endif /* __included_nat_inlines_h__ */ /* diff --git a/test/test_nat.py b/test/test_nat.py index 5baadd89863..a705bd98fc0 100644 --- a/test/test_nat.py +++ b/test/test_nat.py @@ -139,6 +139,7 @@ class MethodHolder(VppTestCase): self.verify_no_nat44_user() self.vapi.nat_set_timeouts() self.vapi.nat_set_addr_and_port_alloc_alg() + self.vapi.nat_set_mss_clamping() def nat44_add_static_mapping(self, local_ip, external_ip='0.0.0.0', local_port=0, external_port=0, vrf_id=0, @@ -1002,6 +1003,21 @@ class MethodHolder(VppTestCase): # sourceIPv4Address self.assertEqual(src_addr, record[8]) + def verify_mss_value(self, pkt, mss): + """ + Verify TCP MSS value + + :param pkt: + :param mss: + """ + if not pkt.haslayer(IP) or not pkt.haslayer(TCP): + raise TypeError("Not a TCP/IP packet") + + for option in pkt[TCP].options: + if option[0] == 'MSS': + self.assertEqual(option[1], mss) + self.assert_tcp_checksum_valid(pkt) + class TestNAT44(MethodHolder): """ NAT44 Test Cases """ @@ -3295,6 +3311,42 @@ class TestNAT44(MethodHolder): nsessions = nsessions + user.nsessions self.assertLess(nsessions, 2 * max_sessions) + def test_mss_clamping(self): + """ TCP MSS clamping """ + 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) + + p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) / + TCP(sport=self.tcp_port_in, dport=self.tcp_external_port, + flags="S", options=[('MSS', 1400)])) + + self.vapi.nat_set_mss_clamping(enable=1, mss_value=1000) + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg1.get_capture(1) + # Negotiated MSS value greater than configured - changed + self.verify_mss_value(capture[0], 1000) + + self.vapi.nat_set_mss_clamping(enable=0) + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg1.get_capture(1) + # MSS clamping disabled - negotiated MSS unchanged + self.verify_mss_value(capture[0], 1400) + + self.vapi.nat_set_mss_clamping(enable=1, mss_value=1500) + self.pg0.add_stream(p) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + capture = self.pg1.get_capture(1) + # Negotiated MSS value smaller than configured - unchanged + self.verify_mss_value(capture[0], 1400) + def tearDown(self): super(TestNAT44, self).tearDown() if not self.vpp_dead: diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 13d0d933ea1..61d80b21fb5 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -1827,6 +1827,20 @@ class VppPapiProvider(object): """Get address and port assignment algorithm""" return self.api(self.papi.nat_get_addr_and_port_alloc_alg, {}) + def nat_set_mss_clamping(self, enable=0, mss_value=1500): + """Set TCP MSS rewriting configuration + + :param enable: disable(0)/enable(1) MSS rewriting feature + :param mss_value: MSS value to be used for MSS rewriting + """ + return self.api( + self.papi.nat_set_mss_clamping, + {'enable': enable, 'mss_value': mss_value}) + + def nat_get_mss_clamping(self): + """Get TCP MSS rewriting configuration""" + return self.api(self.papi.nat_get_mss_clamping, {}) + def nat_det_close_session_out( self, out_addr, -- 2.16.6