NAT: TCP MSS clamping 97/14797/1
authorMatus Fabian <matfabia@cisco.com>
Thu, 13 Sep 2018 09:36:25 +0000 (02:36 -0700)
committerMatus Fabian <matfabia@cisco.com>
Thu, 13 Sep 2018 09:36:25 +0000 (02:36 -0700)
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 <matfabia@cisco.com>
src/plugins/nat/dslite_in2out.c
src/plugins/nat/in2out.c
src/plugins/nat/nat.api
src/plugins/nat/nat.c
src/plugins/nat/nat.h
src/plugins/nat/nat44_cli.c
src/plugins/nat/nat64_in2out.c
src/plugins/nat/nat_api.c
src/plugins/nat/nat_inlines.h
test/test_nat.py
test/vpp_papi_provider.py

index bf51ca2..8f5a0c0 100644 (file)
@@ -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
index 22a3468..661d8c1 100755 (executable)
@@ -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);
                 }
             }
index 1ff288d..6e2d8e5 100644 (file)
@@ -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
  */
index ac81a99..03e264e 100755 (executable)
@@ -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)
index d8d0b2a..06a6162 100644 (file)
@@ -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;
 
index 17a3827..4f02896 100644 (file)
@@ -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 <mss-value>|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}
index ddbf585..e843fcf 100644 (file)
@@ -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);
     }
 
index 2994f80..4c532ed 100644 (file)
@@ -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)           \
index adfb1d5..8922c05 100644 (file)
@@ -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__ */
 
 /*
index 5baadd8..a705bd9 100644 (file)
@@ -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:
index 13d0d93..61d80b2 100644 (file)
@@ -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,