From bf55e9931ce203049385fbf55dde291ead556679 Mon Sep 17 00:00:00 2001 From: Neale Ranns Date: Thu, 4 Oct 2018 06:40:30 -0700 Subject: mss_clamp: TCP MSS clamping plugin Type: feature Configure TCP MSS clamping on an interface as follows: set interface tcp-mss-clamp [rx|tx] ip4 [enable|disable|rx|tx] ip4-mss ip6 [enable|disable|rx|tx] ip6-mss Change-Id: I45b04e50a0b70a33e14a9066f981c651292ebffb Signed-off-by: Neale Ranns Signed-off-by: Paul Vinciguerra Signed-off-by: Miklos Tirpak Signed-off-by: Matthew Smith --- src/plugins/mss_clamp/CMakeLists.txt | 30 ++ src/plugins/mss_clamp/mss_clamp.api | 112 ++++++++ src/plugins/mss_clamp/mss_clamp.c | 292 +++++++++++++++++++ src/plugins/mss_clamp/mss_clamp.h | 55 ++++ src/plugins/mss_clamp/mss_clamp_api.c | 136 +++++++++ src/plugins/mss_clamp/mss_clamp_node.c | 411 +++++++++++++++++++++++++++ src/plugins/mss_clamp/test/test_mss_clamp.py | 295 +++++++++++++++++++ 7 files changed, 1331 insertions(+) create mode 100644 src/plugins/mss_clamp/CMakeLists.txt create mode 100644 src/plugins/mss_clamp/mss_clamp.api create mode 100644 src/plugins/mss_clamp/mss_clamp.c create mode 100644 src/plugins/mss_clamp/mss_clamp.h create mode 100644 src/plugins/mss_clamp/mss_clamp_api.c create mode 100644 src/plugins/mss_clamp/mss_clamp_node.c create mode 100644 src/plugins/mss_clamp/test/test_mss_clamp.py (limited to 'src/plugins/mss_clamp') diff --git a/src/plugins/mss_clamp/CMakeLists.txt b/src/plugins/mss_clamp/CMakeLists.txt new file mode 100644 index 00000000000..d650d7fc528 --- /dev/null +++ b/src/plugins/mss_clamp/CMakeLists.txt @@ -0,0 +1,30 @@ + +# Copyright (c) +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_vpp_plugin(mss_clamp + SOURCES + mss_clamp.c + mss_clamp_api.c + mss_clamp_node.c + + + MULTIARCH_SOURCES + mss_clamp_node.c + + API_FILES + mss_clamp.api + + INSTALL_HEADERS + mss_clamp.h +) diff --git a/src/plugins/mss_clamp/mss_clamp.api b/src/plugins/mss_clamp/mss_clamp.api new file mode 100644 index 00000000000..e1b9fdea6cb --- /dev/null +++ b/src/plugins/mss_clamp/mss_clamp.api @@ -0,0 +1,112 @@ +/* Hey Emacs use -*- mode: C -*- */ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +option version = "1.0.0"; +import "vnet/interface_types.api"; + +/** \brief TCP MSS Clamping direction flag + */ +enumflag mss_clamp_dir : u8 { + MSS_CLAMP_DIR_NONE = 0x0, + MSS_CLAMP_DIR_RX = 0x1, + MSS_CLAMP_DIR_TX = 0x2, +}; + +/** \brief Enable/Disable TCP MSS Clamping feature on an interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - interface index on which clamping will be applied + @param ipv4_mss - Maximum Segment Size for IPv4/TCP + @param ipv6_mss - Maximum Segment Size for IPv6/TCP + @param ipv4_direction - Direction clamping is enabled on (IPv4/TCP) + @param ipv6_direction - Direction clamping is enabled on (IPv6/TCP) + */ +autoreply define mss_clamp_enable_disable { + u32 client_index; + u32 context; + vl_api_interface_index_t sw_if_index; + u16 ipv4_mss; + u16 ipv6_mss; + vl_api_mss_clamp_dir_t ipv4_direction; + vl_api_mss_clamp_dir_t ipv6_direction; +}; + + +/** \brief Get the list of configured mss values + @param client_index - opaque cookie to identify the sender +*/ +service { + rpc mss_clamp_get returns mss_clamp_get_reply + stream mss_clamp_details; +}; + +/** \brief Get the TCP MSS Clamping feature settings + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param cursor - cursor to continue when there is more to read + @param sw_if_index - interface index to filter the result, + ~0 means no filter + */ +define mss_clamp_get { + u32 client_index; + u32 context; + u32 cursor; + vl_api_interface_index_t sw_if_index; +}; + +/** \brief Reply for get TCP MSS Clamping feature settings request + @param context - returned sender context, to match reply w/ request + @param retval - return code + @param cursor - cursor to continue when there is more to read + */ +define mss_clamp_get_reply { + u32 context; + i32 retval; + u32 cursor; +}; + +/** \brief Configured MSS values on an interface + @param context - returned sender context, to match reply w/ request + @param sw_if_index - interface index on which clamping is applied + @param ipv4_mss - Maximum Segment Size for IPv4/TCP + @param ipv6_mss - Maximum Segment Size for IPv6/TCP + @param ipv4_direction - Direction clamping is enabled on (IPv4/TCP) + @param ipv6_direction - Direction clamping is enabled on (IPv6/TCP) + */ +define mss_clamp_details { + u32 context; + vl_api_interface_index_t sw_if_index; + u16 ipv4_mss; + u16 ipv6_mss; + vl_api_mss_clamp_dir_t ipv4_direction; + vl_api_mss_clamp_dir_t ipv6_direction; +}; + +counters mss_clamp { + clamped { + severity info; + type counter64; + units "packets"; + description "packets clamped"; + }; +}; +paths { + "/err/tcp-mss-clamping-ip4-in" "mss-clamp"; + "/err/tcp-mss-clamping-ip4-out" "mss-clamp"; + "/err/tcp-mss-clamping-ip6-in" "mss-clamp"; + "/err/tcp-mss-clamping-ip6-out" "mss-clamp"; +}; + diff --git a/src/plugins/mss_clamp/mss_clamp.c b/src/plugins/mss_clamp/mss_clamp.c new file mode 100644 index 00000000000..cdac5456641 --- /dev/null +++ b/src/plugins/mss_clamp/mss_clamp.c @@ -0,0 +1,292 @@ +/* + * mss_clamp.c - TCP MSS clamping plug-in + * + * Copyright (c) 2018 Cisco and/or its affiliates + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +mssc_main_t mssc_main; + +/* Action function shared between message handler and debug CLI */ + +static void +mssc_enable_disable_feat (u32 sw_if_index, u8 dir4, u8 dir6, int enable) +{ + if (dir4 == MSS_CLAMP_DIR_NONE && dir6 == MSS_CLAMP_DIR_NONE) + return; + + // ip4 + if ((dir4 & MSS_CLAMP_DIR_RX) != MSS_CLAMP_DIR_NONE) + vnet_feature_enable_disable ("ip4-unicast", "tcp-mss-clamping-ip4-in", + sw_if_index, enable, 0, 0); + if ((dir4 & MSS_CLAMP_DIR_TX) != MSS_CLAMP_DIR_NONE) + vnet_feature_enable_disable ("ip4-output", "tcp-mss-clamping-ip4-out", + sw_if_index, enable, 0, 0); + // ip6 + if ((dir6 & MSS_CLAMP_DIR_RX) != MSS_CLAMP_DIR_NONE) + vnet_feature_enable_disable ("ip6-unicast", "tcp-mss-clamping-ip6-in", + sw_if_index, enable, 0, 0); + if ((dir6 & MSS_CLAMP_DIR_TX) != MSS_CLAMP_DIR_NONE) + vnet_feature_enable_disable ("ip6-output", "tcp-mss-clamping-ip6-out", + sw_if_index, enable, 0, 0); +} + +int +mssc_enable_disable (u32 sw_if_index, u8 dir4, u8 dir6, u16 mss4, u16 mss6) +{ + mssc_main_t *cm = &mssc_main; + u8 *dir_enabled4, *dir_enabled6; + int rv = 0; + + if (dir4 == MSS_CLAMP_DIR_NONE) + mss4 = MSS_CLAMP_UNSET; + if (dir6 == MSS_CLAMP_DIR_NONE) + mss6 = MSS_CLAMP_UNSET; + + vec_validate_init_empty (cm->dir_enabled4, sw_if_index, MSS_CLAMP_DIR_NONE); + vec_validate_init_empty (cm->dir_enabled6, sw_if_index, MSS_CLAMP_DIR_NONE); + vec_validate_init_empty (cm->max_mss4, sw_if_index, MSS_CLAMP_UNSET); + vec_validate_init_empty (cm->max_mss6, sw_if_index, MSS_CLAMP_UNSET); + + cm->max_mss4[sw_if_index] = mss4; + cm->max_mss6[sw_if_index] = mss6; + dir_enabled4 = &cm->dir_enabled4[sw_if_index]; + dir_enabled6 = &cm->dir_enabled6[sw_if_index]; + + // Disable the directions that are no longer needed + mssc_enable_disable_feat (sw_if_index, (*dir_enabled4) & ~dir4, + (*dir_enabled6) & ~dir6, 0); + // Enable the new directions + mssc_enable_disable_feat (sw_if_index, ~(*dir_enabled4) & dir4, + ~(*dir_enabled6) & dir6, 1); + + *dir_enabled4 = dir4; + *dir_enabled6 = dir6; + + return rv; +} + +int +mssc_get_mss (u32 sw_if_index, u8 *dir4, u8 *dir6, u16 *mss4, u16 *mss6) +{ + mssc_main_t *cm = &mssc_main; + int rv = VNET_API_ERROR_FEATURE_DISABLED; + + if (vec_len (cm->dir_enabled4) > sw_if_index && + MSS_CLAMP_DIR_NONE != cm->dir_enabled4[sw_if_index]) + { + *mss4 = cm->max_mss4[sw_if_index]; + *dir4 = cm->dir_enabled4[sw_if_index]; + rv = 0; + } + else + { + *mss4 = MSS_CLAMP_DIR_NONE; + *dir4 = 0; + } + + if (vec_len (cm->dir_enabled6) > sw_if_index && + MSS_CLAMP_DIR_NONE != cm->dir_enabled6[sw_if_index]) + { + *mss6 = cm->max_mss6[sw_if_index]; + *dir6 = cm->dir_enabled6[sw_if_index]; + rv = 0; + } + else + { + *mss6 = MSS_CLAMP_DIR_NONE; + *dir6 = 0; + } + return rv; +} + +static uword +unformat_mssc_dir (unformat_input_t *input, va_list *args) +{ + u8 *result = va_arg (*args, u8 *); + u8 dir = MSS_CLAMP_DIR_RX | MSS_CLAMP_DIR_TX; + + if (unformat (input, "disable")) + dir = MSS_CLAMP_DIR_NONE; + else if (unformat (input, "enable")) + dir = MSS_CLAMP_DIR_RX | MSS_CLAMP_DIR_TX; + else if (unformat (input, "rx")) + dir = MSS_CLAMP_DIR_RX; + else if (unformat (input, "tx")) + dir = MSS_CLAMP_DIR_TX; + else + return 0; + + *result = dir; + return 1; +} + +static clib_error_t * +mssc_enable_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + u32 sw_if_index = ~0; + u8 dir4 = ~0, dir6 = ~0; + u32 mss4 = ~0, mss6 = ~0; + int rv; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "ip4 %U", unformat_mssc_dir, &dir4)) + ; + else if (unformat (input, "ip6 %U", unformat_mssc_dir, &dir6)) + ; + else if (unformat (input, "ip4-mss %d", &mss4)) + ; + else if (unformat (input, "ip6-mss %d", &mss6)) + ; + else if (unformat (input, "%U", unformat_vnet_sw_interface, + vnet_get_main (), &sw_if_index)) + ; + else + break; + } + + if (sw_if_index == ~0) + return clib_error_return (0, "Please specify an interface"); + + if (dir4 == (u8) ~0 || dir6 == (u8) ~0) + return clib_error_return ( + 0, "Please specify the MSS clamping direction for ip4 and ip6"); + + if (dir4 != MSS_CLAMP_DIR_NONE) + { + if (mss4 == ~0) + return clib_error_return ( + 0, "Please specify the Max Segment Size for ip4"); + if (mss4 >= MSS_CLAMP_UNSET) + return clib_error_return (0, "Invalid Max Segment Size"); + } + if (dir6 != MSS_CLAMP_DIR_NONE) + { + if (mss6 == ~0) + return clib_error_return ( + 0, "Please specify the Max Segment Size for ip6"); + if (mss6 >= MSS_CLAMP_UNSET) + return clib_error_return (0, "Invalid Max Segment Size"); + } + + rv = mssc_enable_disable (sw_if_index, dir4, dir6, mss4, mss6); + + if (rv) + return clib_error_return (0, "Failed: %d = %U", rv, format_vnet_api_errno, + rv); + + return (NULL); +} + +VLIB_CLI_COMMAND (mssc_enable_disable_command, static) = { + .path = "set interface tcp-mss-clamp", + .short_help = "set interface tcp-mss-clamp " + "ip4 [enable|disable|rx|tx] ip4-mss " + "ip6 [enable|disable|rx|tx] ip6-mss ", + .function = mssc_enable_command_fn, +}; + +static u8 * +format_mssc_clamping (u8 *s, va_list *args) +{ + u8 dir = va_arg (*args, u32); + u16 mss = va_arg (*args, u32); +#define DIR2S(d) \ + (((d) == (MSS_CLAMP_DIR_RX | MSS_CLAMP_DIR_TX)) ? \ + "" : \ + (((d) == MSS_CLAMP_DIR_RX) ? " [RX]" : " [TX]")) + + if (MSS_CLAMP_DIR_NONE == dir) + { + return format (s, "disabled"); + } + u32 mss_u32 = mss; + return format (s, "%d%s", mss_u32, DIR2S (dir)); +} + +static clib_error_t * +mssc_show_command_fn (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + mssc_main_t *cm = &mssc_main; + u32 sw_if_index = ~0; + u32 ii; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "%U", unformat_vnet_sw_interface, vnet_get_main (), + &sw_if_index)) + ; + else + break; + } + + if (sw_if_index == ~0) + { + vec_foreach_index (ii, cm->dir_enabled4) + { + u8 dir4 = cm->dir_enabled4[ii]; + u8 dir6 = cm->dir_enabled6[ii]; + if (MSS_CLAMP_DIR_NONE != dir4 || MSS_CLAMP_DIR_NONE != dir6) + { + u16 mss4 = cm->max_mss4[ii]; + u16 mss6 = cm->max_mss6[ii]; + vlib_cli_output (vm, "%U: ip4: %U ip6: %U", + format_vnet_sw_if_index_name, vnet_get_main (), + ii, format_mssc_clamping, dir4, mss4, + format_mssc_clamping, dir6, mss6); + } + } + } + else + { + u16 mss4, mss6; + u8 dir4, dir6; + mssc_get_mss (sw_if_index, &dir4, &dir6, &mss4, &mss6); + vlib_cli_output (vm, "%U: ip4: %U ip6: %U", format_vnet_sw_if_index_name, + vnet_get_main (), sw_if_index, format_mssc_clamping, + dir4, mss4, format_mssc_clamping, dir6, mss6); + } + + return (NULL); +} + +VLIB_CLI_COMMAND (mssc_show_command, static) = { + .path = "show interface tcp-mss-clamp", + .short_help = "show interface tcp-mss-clamp [interface-name]", + .long_help = "show TCP MSS clamping configurations", + .function = mssc_show_command_fn, +}; + +static clib_error_t * +mssc_init (vlib_main_t *vm) +{ + return NULL; +} + +VLIB_INIT_FUNCTION (mssc_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/mss_clamp/mss_clamp.h b/src/plugins/mss_clamp/mss_clamp.h new file mode 100644 index 00000000000..46aa12aa81f --- /dev/null +++ b/src/plugins/mss_clamp/mss_clamp.h @@ -0,0 +1,55 @@ +/* + * mss_clamp.h - TCP MSS clamping plug-in header file + * + * Copyright (c) 2018 Cisco and/or its affiliates + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __included_mss_clamp_h__ +#define __included_mss_clamp_h__ + +#include /* for bool in .api */ +#include + +extern int mssc_enable_disable (u32 sw_if_index, u8 dir4, u8 dir6, u16 mss4, + u16 mss6); +extern int mssc_get_mss (u32 sw_if_index, u8 *dir4, u8 *dir6, u16 *mss4, + u16 *mss6); + +typedef struct +{ + /* Maximum segment size per interface for IPv4/IPv6 */ + u16 *max_mss4; + u16 *max_mss6; + + /* Direction the feature is enabled for IPv4/IPv6 (rx, tx, both) */ + u8 *dir_enabled4; + u8 *dir_enabled6; + + /* API message ID base */ + u16 msg_id_base; +} mssc_main_t; + +extern mssc_main_t mssc_main; + +#define MSS_CLAMP_UNSET 0xffff + +#endif /* __included_mss_clamp_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/mss_clamp/mss_clamp_api.c b/src/plugins/mss_clamp/mss_clamp_api.c new file mode 100644 index 00000000000..cb47cd6ce06 --- /dev/null +++ b/src/plugins/mss_clamp/mss_clamp_api.c @@ -0,0 +1,136 @@ +/* + * mss_clamp_api.c - TCP MSS clamping plug-in + * + * Copyright (c) + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include /* VLIB_PLUGIN_REGISTER */ +#include +#include +#include +#include +#include +#include /* VPP_BUILD_VER */ + +#define REPLY_MSG_ID_BASE cm->msg_id_base +#include + +/* API message handler */ +static void +vl_api_mss_clamp_enable_disable_t_handler ( + vl_api_mss_clamp_enable_disable_t *mp) +{ + mssc_main_t *cm = &mssc_main; + vl_api_mss_clamp_enable_disable_reply_t *rmp; + int rv; + + VALIDATE_SW_IF_INDEX (mp); + + rv = mssc_enable_disable (ntohl (mp->sw_if_index), mp->ipv4_direction, + mp->ipv6_direction, ntohs (mp->ipv4_mss), + ntohs (mp->ipv6_mss)); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_MSS_CLAMP_ENABLE_DISABLE_REPLY); +} + +static void +send_mss_clamp_details (u32 sw_if_index, vl_api_registration_t *rp, + u32 context) +{ + mssc_main_t *cm = &mssc_main; + vl_api_mss_clamp_details_t *rmp; + u16 mss4, mss6; + u8 dir4, dir6; + int rv; + + mss4 = mss6 = 0; + dir4 = dir6 = MSS_CLAMP_DIR_NONE; + rv = mssc_get_mss (sw_if_index, &dir4, &dir6, &mss4, &mss6); + if (rv == VNET_API_ERROR_FEATURE_DISABLED) + return; + + REPLY_MACRO_DETAILS4 (VL_API_MSS_CLAMP_DETAILS, rp, context, ({ + rmp->sw_if_index = htonl (sw_if_index); + rmp->ipv4_mss = htons (mss4); + rmp->ipv6_mss = htons (mss6); + rmp->ipv4_direction = dir4; + rmp->ipv6_direction = dir6; + })); +} + +static void +vl_api_mss_clamp_get_t_handler (vl_api_mss_clamp_get_t *mp) +{ + mssc_main_t *cm = &mssc_main; + vl_api_mss_clamp_get_reply_t *rmp; + int rv = 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + if (sw_if_index == ~0) + { + if (vec_len (cm->dir_enabled4) == 0) + { + REPLY_MACRO2 (VL_API_MSS_CLAMP_GET_REPLY, ({ rmp->cursor = ~0; })); + return; + } + + REPLY_AND_DETAILS_MACRO ( + VL_API_MSS_CLAMP_GET_REPLY, cm->dir_enabled4, + ({ send_mss_clamp_details (cursor, reg, mp->context); })); + } + else + { + VALIDATE_SW_IF_INDEX (mp); + send_mss_clamp_details (sw_if_index, reg, mp->context); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO2 (VL_API_MSS_CLAMP_GET_REPLY, ({ rmp->cursor = ~0; })); + } +} + +/* API definitions */ +#include +#include + +/* Set up the API message handling tables */ +static clib_error_t * +mssc_api_hookup (vlib_main_t *vm) +{ + mssc_main_t *cm = &mssc_main; + + cm->msg_id_base = setup_message_id_table (); + return 0; +} + +VLIB_API_INIT_FUNCTION (mssc_api_hookup); + +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "TCP MSS clamping plugin", +}; + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/mss_clamp/mss_clamp_node.c b/src/plugins/mss_clamp/mss_clamp_node.c new file mode 100644 index 00000000000..4a40b2329e1 --- /dev/null +++ b/src/plugins/mss_clamp/mss_clamp_node.c @@ -0,0 +1,411 @@ +/* + * mss_clamp_node.c - Node implementing TCP MSS clamping + * + * Copyright (c) 2018 Cisco and/or its affiliates + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern vlib_node_registration_t mssc_ip4_in_node, mssc_ip4_out_node; +extern vlib_node_registration_t mssc_ip6_in_node, mssc_ip6_out_node; + +typedef struct mssc_trace_t_ +{ + u32 max_mss; + u32 clamped; +} mssc_trace_t; + +/* packet trace format function */ +static u8 * +format_mssc_trace (u8 *s, va_list *args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + mssc_trace_t *t = va_arg (*args, mssc_trace_t *); + + s = format (s, "max mss: %d clamped: %d", t->max_mss, t->clamped); + return s; +} + +typedef enum +{ + MSSC_NEXT_DROP, + MSSC_N_NEXT, +} mssc_next_t; + +/* + * fixup the maximum segment size if it's a syn packet + * return 1 if the mss was changed otherwise 0 + */ +always_inline u32 +mssc_mss_fixup (vlib_buffer_t *b0, tcp_header_t *tcp0, u16 max_mss0) +{ + ip_csum_t sum0; + + if (PREDICT_FALSE (tcp_syn (tcp0))) + { + u8 opt_len, opts_len, kind; + const u8 *data; + u16 mss0, new_mss0; + + opts_len = (tcp_doff (tcp0) << 2) - sizeof (tcp_header_t); + data = (const u8 *) (tcp0 + 1); + + for (; opts_len > 0; opts_len -= opt_len, data += opt_len) + { + kind = data[0]; + + /* Get options length */ + if (kind == TCP_OPTION_EOL) + break; + else if (kind == TCP_OPTION_NOOP) + { + opt_len = 1; + continue; + } + else + { + /* broken options */ + if (opts_len < 2) + return 0; + opt_len = data[1]; + + /* weird option length */ + if (opt_len < 2 || opt_len > opts_len) + return 0; + } + + if (kind == TCP_OPTION_MSS) + { + mss0 = *(u16 *) (data + 2); + if (clib_net_to_host_u16 (mss0) > max_mss0) + { + new_mss0 = clib_host_to_net_u16 (max_mss0); + *((u16 *) (data + 2)) = new_mss0; + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, mss0, new_mss0, tcp_header_t, + checksum); + tcp0->checksum = ip_csum_fold (sum0); + return 1; + } + } + } + } + + return 0; +} + +always_inline uword +mssc_inline (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, + vlib_dir_t dir, fib_protocol_t fproto) +{ + mssc_main_t *cm = &mssc_main; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; + u16 nexts[VLIB_FRAME_SIZE], *next; + u32 n_left, *from; + u32 pkts_clamped = 0; + + from = vlib_frame_vector_args (frame); + n_left = frame->n_vectors; + b = bufs; + next = nexts; + + vlib_get_buffers (vm, from, bufs, n_left); + + while (n_left >= 4) + { + u32 sw_if_index0, sw_if_index1; + const u8 *h0, *h1; + u32 clamped0, clamped1; + + /* Prefetch next iteration. */ + { + vlib_prefetch_buffer_header (b[2], LOAD); + vlib_prefetch_buffer_header (b[3], LOAD); + vlib_prefetch_buffer_data (b[2], LOAD); + vlib_prefetch_buffer_data (b[3], LOAD); + } + + sw_if_index0 = vnet_buffer (b[0])->sw_if_index[dir]; + sw_if_index1 = vnet_buffer (b[1])->sw_if_index[dir]; + clamped0 = clamped1 = 0; + + /* speculatively enqueue b0 to the current next frame */ + vnet_feature_next_u16 (&next[0], b[0]); + vnet_feature_next_u16 (&next[1], b[1]); + + h0 = (u8 *) vlib_buffer_get_current (b[0]); + h1 = (u8 *) vlib_buffer_get_current (b[1]); + if (VLIB_TX == dir) + { + h0 += vnet_buffer (b[0])->ip.save_rewrite_length; + h1 += vnet_buffer (b[1])->ip.save_rewrite_length; + } + + if (FIB_PROTOCOL_IP4 == fproto) + { + ip4_header_t *ip0 = (ip4_header_t *) h0; + ip4_header_t *ip1 = (ip4_header_t *) h1; + + if (IP_PROTOCOL_TCP == ip0->protocol) + { + clamped0 = mssc_mss_fixup (b[0], ip4_next_header (ip0), + cm->max_mss4[sw_if_index0]); + } + if (IP_PROTOCOL_TCP == ip1->protocol) + { + clamped1 = mssc_mss_fixup (b[1], ip4_next_header (ip1), + cm->max_mss4[sw_if_index1]); + } + } + else if (FIB_PROTOCOL_IP6 == fproto) + { + ip6_header_t *ip0 = (ip6_header_t *) h0; + ip6_header_t *ip1 = (ip6_header_t *) h1; + + if (IP_PROTOCOL_TCP == ip0->protocol) + { + clamped0 = mssc_mss_fixup (b[0], ip6_next_header (ip0), + cm->max_mss6[sw_if_index0]); + } + if (IP_PROTOCOL_TCP == ip1->protocol) + { + clamped1 = mssc_mss_fixup (b[1], ip6_next_header (ip1), + cm->max_mss6[sw_if_index1]); + } + } + + pkts_clamped += clamped0 + clamped1; + + if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE)) + { + if (b[0]->flags & VLIB_BUFFER_IS_TRACED) + { + mssc_trace_t *t; + + t = vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->max_mss = (FIB_PROTOCOL_IP4 == fproto) ? + cm->max_mss4[sw_if_index0] : + cm->max_mss6[sw_if_index0]; + t->clamped = clamped0; + } + if (b[1]->flags & VLIB_BUFFER_IS_TRACED) + { + mssc_trace_t *t; + + t = vlib_add_trace (vm, node, b[1], sizeof (*t)); + t->max_mss = (FIB_PROTOCOL_IP4 == fproto) ? + cm->max_mss4[sw_if_index1] : + cm->max_mss6[sw_if_index1]; + t->clamped = clamped1; + } + } + + b += 2; + next += 2; + n_left -= 2; + } + + while (n_left > 0) + { + u32 sw_if_index0; + const u8 *h0; + u32 clamped0; + + sw_if_index0 = vnet_buffer (b[0])->sw_if_index[dir]; + clamped0 = 0; + + /* speculatively enqueue b0 to the current next frame */ + vnet_feature_next_u16 (&next[0], b[0]); + + h0 = (u8 *) vlib_buffer_get_current (b[0]); + if (VLIB_TX == dir) + h0 += vnet_buffer (b[0])->ip.save_rewrite_length; + + if (FIB_PROTOCOL_IP4 == fproto) + { + ip4_header_t *ip0 = (ip4_header_t *) h0; + + if (IP_PROTOCOL_TCP == ip0->protocol) + { + clamped0 = mssc_mss_fixup (b[0], ip4_next_header (ip0), + cm->max_mss4[sw_if_index0]); + } + } + else if (FIB_PROTOCOL_IP6 == fproto) + { + ip6_header_t *ip0 = (ip6_header_t *) h0; + + if (IP_PROTOCOL_TCP == ip0->protocol) + { + clamped0 = mssc_mss_fixup (b[0], ip6_next_header (ip0), + cm->max_mss6[sw_if_index0]); + } + } + + pkts_clamped += clamped0; + + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) && + (b[0]->flags & VLIB_BUFFER_IS_TRACED))) + { + mssc_trace_t *t; + + t = vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->max_mss = (FIB_PROTOCOL_IP4 == fproto) ? + cm->max_mss4[sw_if_index0] : + cm->max_mss6[sw_if_index0]; + t->clamped = clamped0; + } + + b++; + next++; + n_left--; + } + + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + vlib_node_increment_counter (vm, node->node_index, MSS_CLAMP_ERROR_CLAMPED, + pkts_clamped); + + return frame->n_vectors; +} + +static uword +mssc_ip4_in (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return (mssc_inline (vm, node, frame, VLIB_RX, FIB_PROTOCOL_IP4)); +} + +static uword +mssc_ip4_out (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return (mssc_inline (vm, node, frame, VLIB_TX, FIB_PROTOCOL_IP4)); +} + +static uword +mssc_ip6_in (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return (mssc_inline (vm, node, frame, VLIB_RX, FIB_PROTOCOL_IP6)); +} + +static uword +mssc_ip6_out (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + return (mssc_inline (vm, node, frame, VLIB_TX, FIB_PROTOCOL_IP6)); +} + +VLIB_REGISTER_NODE (mssc_ip4_in_node) = +{ + .function = mssc_ip4_in, + .name = "tcp-mss-clamping-ip4-in", + .vector_size = sizeof (u32), + .format_trace = format_mssc_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = MSS_CLAMP_N_ERROR, + .error_counters = mss_clamp_error_counters, + + .n_next_nodes = MSSC_N_NEXT, + .next_nodes = { + [MSSC_NEXT_DROP] = "error-drop", + }, +}; + +VLIB_REGISTER_NODE (mssc_ip4_out_node) = +{ + .function = mssc_ip4_out, + .name = "tcp-mss-clamping-ip4-out", + .vector_size = sizeof (u32), + .format_trace = format_mssc_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = MSS_CLAMP_N_ERROR, + .error_counters = mss_clamp_error_counters, + + .n_next_nodes = MSSC_N_NEXT, + .next_nodes = { + [MSSC_NEXT_DROP] = "error-drop", + }, +}; + +VLIB_REGISTER_NODE (mssc_ip6_in_node) = +{ + .function = mssc_ip6_in, + .name = "tcp-mss-clamping-ip6-in", + .vector_size = sizeof (u32), + .format_trace = format_mssc_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = MSS_CLAMP_N_ERROR, + .error_counters = mss_clamp_error_counters, + + .n_next_nodes = MSSC_N_NEXT, + .next_nodes = { + [MSSC_NEXT_DROP] = "error-drop", + }, +}; + +VLIB_REGISTER_NODE (mssc_ip6_out_node) = +{ + .function = mssc_ip6_out, + .name = "tcp-mss-clamping-ip6-out", + .vector_size = sizeof (u32), + .format_trace = format_mssc_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = MSS_CLAMP_N_ERROR, + .error_counters = mss_clamp_error_counters, + + .n_next_nodes = MSSC_N_NEXT, + .next_nodes = { + [MSSC_NEXT_DROP] = "error-drop", + }, +}; + +VNET_FEATURE_INIT (mssc_ip4_in_feat, static) = { + .arc_name = "ip4-unicast", + .node_name = "tcp-mss-clamping-ip4-in", + .runs_after = VNET_FEATURES ("ip4-policer-classify"), +}; + +VNET_FEATURE_INIT (mssc_ip4_out_feat, static) = { + .arc_name = "ip4-output", + .node_name = "tcp-mss-clamping-ip4-out", +}; + +VNET_FEATURE_INIT (mssc_ip6_in_feat, static) = { + .arc_name = "ip6-unicast", + .node_name = "tcp-mss-clamping-ip6-in", + .runs_after = VNET_FEATURES ("ip6-policer-classify"), +}; + +VNET_FEATURE_INIT (mssc_ip6_out_feat, static) = { + .arc_name = "ip6-output", + .node_name = "tcp-mss-clamping-ip6-out", +}; + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/mss_clamp/test/test_mss_clamp.py b/src/plugins/mss_clamp/test/test_mss_clamp.py new file mode 100644 index 00000000000..23495b6050b --- /dev/null +++ b/src/plugins/mss_clamp/test/test_mss_clamp.py @@ -0,0 +1,295 @@ +#!/usr/bin/env python3 + +import unittest + +from framework import VppTestCase, VppTestRunner + +from scapy.layers.inet import IP, TCP +from scapy.layers.inet6 import IPv6 +from scapy.layers.l2 import Ether +from scapy.packet import Raw + + +class TestMSSClamp(VppTestCase): + """ TCP MSS Clamping Test Case """ + + def setUp(self): + super(TestMSSClamp, self).setUp() + + # create 2 pg interfaces + self.create_pg_interfaces(range(2)) + + for i in self.pg_interfaces: + i.admin_up() + i.config_ip4() + i.resolve_arp() + i.config_ip6() + i.resolve_ndp() + + def tearDown(self): + for i in self.pg_interfaces: + i.unconfig_ip4() + i.unconfig_ip6() + i.admin_down() + super(TestMSSClamp, self).tearDown() + + def verify_pkt(self, rx, expected_mss): + # check that the MSS size equals the expected value + # and the IP and TCP checksums are correct + tcp = rx[TCP] + tcp_csum = tcp.chksum + del tcp.chksum + ip_csum = 0 + if (rx.haslayer(IP)): + ip_csum = rx[IP].chksum + del rx[IP].chksum + + opt = tcp.options + self.assertEqual(opt[0][0], 'MSS') + self.assertEqual(opt[0][1], expected_mss) + # recalculate checksums + rx = rx.__class__(bytes(rx)) + tcp = rx[TCP] + self.assertEqual(tcp_csum, tcp.chksum) + if (rx.haslayer(IP)): + self.assertEqual(ip_csum, rx[IP].chksum) + + def send_and_verify_ip4(self, src_pg, dst_pg, mss, expected_mss): + # IPv4 TCP packet with the requested MSS option. + # from a host on src_pg to a host on dst_pg. + p = (Ether(dst=src_pg.local_mac, + src=src_pg.remote_mac) / + IP(src=src_pg.remote_ip4, + dst=dst_pg.remote_ip4) / + TCP(sport=1234, dport=1234, + flags="S", + options=[('MSS', (mss)), ('EOL', None)]) / + Raw('\xa5' * 100)) + + rxs = self.send_and_expect(src_pg, p * 65, dst_pg) + + for rx in rxs: + self.verify_pkt(rx, expected_mss) + + def send_and_verify_ip6(self, src_pg, dst_pg, mss, expected_mss): + # + # IPv6 TCP packet with the requested MSS option. + # from a host on src_pg to a host on dst_pg. + # + p = (Ether(dst=src_pg.local_mac, + src=src_pg.remote_mac) / + IPv6(src=src_pg.remote_ip6, + dst=dst_pg.remote_ip6) / + TCP(sport=1234, dport=1234, + flags="S", + options=[('MSS', (mss)), ('EOL', None)]) / + Raw('\xa5' * 100)) + + rxs = self.send_and_expect(src_pg, p * 65, dst_pg) + + for rx in rxs: + self.verify_pkt(rx, expected_mss) + + def test_tcp_mss_clamping_ip4_tx(self): + """ IP4 TCP MSS Clamping TX """ + + # enable the TCP MSS clamping feature to lower the MSS to 1424. + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=1424, ipv6_mss=0, + ipv4_direction=3, ipv6_direction=0) + + # Verify that the feature is enabled. + rv, reply = self.vapi.mss_clamp_get(sw_if_index=self.pg1.sw_if_index) + self.assertEqual(reply[0].ipv4_mss, 1424) + self.assertEqual(reply[0].ipv4_direction, 3) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip4(self.pg0, self.pg1, 1460, 1424) + + # check the stats + stats = self.statistics.get_counter( + '/err/tcp-mss-clamping-ip4-out/clamped') + self.assertEqual(sum(stats), 65) + + # Send syn packets with small enough MSS values and verify they are + # unchanged. + self.send_and_verify_ip4(self.pg0, self.pg1, 1400, 1400) + + # enable the the feature only in TX direction + # and change the max MSS value + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=1420, ipv6_mss=0, + ipv4_direction=2, ipv6_direction=0) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip4(self.pg0, self.pg1, 1460, 1420) + + # enable the the feature only in RX direction + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=1424, ipv6_mss=0, + ipv4_direction=1, ipv6_direction=0) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip4(self.pg0, self.pg1, 1460, 1460) + + # disable the feature + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=0, + ipv4_direction=0, ipv6_direction=0) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip4(self.pg0, self.pg1, 1460, 1460) + + def test_tcp_mss_clamping_ip4_rx(self): + """ IP4 TCP MSS Clamping RX """ + + # enable the TCP MSS clamping feature to lower the MSS to 1424. + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=1424, ipv6_mss=0, + ipv4_direction=3, ipv6_direction=0) + + # Verify that the feature is enabled. + rv, reply = self.vapi.mss_clamp_get(sw_if_index=self.pg1.sw_if_index) + self.assertEqual(reply[0].ipv4_mss, 1424) + self.assertEqual(reply[0].ipv4_direction, 3) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip4(self.pg1, self.pg0, 1460, 1424) + + # check the stats + stats = self.statistics.get_counter( + '/err/tcp-mss-clamping-ip4-in/clamped') + self.assertEqual(sum(stats), 65) + + # Send syn packets with small enough MSS values and verify they are + # unchanged. + self.send_and_verify_ip4(self.pg1, self.pg0, 1400, 1400) + + # enable the the feature only in RX direction + # and change the max MSS value + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=1420, ipv6_mss=0, + ipv4_direction=1, ipv6_direction=0) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip4(self.pg1, self.pg0, 1460, 1420) + + # enable the the feature only in TX direction + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=1424, ipv6_mss=0, + ipv4_direction=2, ipv6_direction=0) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip4(self.pg1, self.pg0, 1460, 1460) + + # disable the feature + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=0, + ipv4_direction=0, ipv6_direction=0) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip4(self.pg1, self.pg0, 1460, 1460) + + def test_tcp_mss_clamping_ip6_tx(self): + """ IP6 TCP MSS Clamping TX """ + + # enable the TCP MSS clamping feature to lower the MSS to 1424. + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=1424, + ipv4_direction=0, ipv6_direction=3) + + # Verify that the feature is enabled. + rv, reply = self.vapi.mss_clamp_get(sw_if_index=self.pg1.sw_if_index) + self.assertEqual(reply[0].ipv6_mss, 1424) + self.assertEqual(reply[0].ipv6_direction, 3) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip6(self.pg0, self.pg1, 1460, 1424) + + # check the stats + stats = self.statistics.get_counter( + '/err/tcp-mss-clamping-ip6-out/clamped') + self.assertEqual(sum(stats), 65) + + # Send syn packets with small enough MSS values and verify they are + # unchanged. + self.send_and_verify_ip6(self.pg0, self.pg1, 1400, 1400) + + # enable the the feature only in TX direction + # and change the max MSS value + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=1420, + ipv4_direction=0, ipv6_direction=2) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip6(self.pg0, self.pg1, 1460, 1420) + + # enable the the feature only in RX direction + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=1424, + ipv4_direction=0, ipv6_direction=1) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip6(self.pg0, self.pg1, 1460, 1460) + + # disable the feature + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=0, + ipv4_direction=0, ipv6_direction=0) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip6(self.pg0, self.pg1, 1460, 1460) + + def test_tcp_mss_clamping_ip6_rx(self): + """ IP6 TCP MSS Clamping RX """ + + # enable the TCP MSS clamping feature to lower the MSS to 1424. + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=1424, + ipv4_direction=0, ipv6_direction=3) + + # Verify that the feature is enabled. + rv, reply = self.vapi.mss_clamp_get(sw_if_index=self.pg1.sw_if_index) + self.assertEqual(reply[0].ipv6_mss, 1424) + self.assertEqual(reply[0].ipv6_direction, 3) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip6(self.pg1, self.pg0, 1460, 1424) + + # check the stats + stats = self.statistics.get_counter( + '/err/tcp-mss-clamping-ip6-in/clamped') + self.assertEqual(sum(stats), 65) + + # Send syn packets with small enough MSS values and verify they are + # unchanged. + self.send_and_verify_ip6(self.pg1, self.pg0, 1400, 1400) + + # enable the the feature only in RX direction + # and change the max MSS value + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=1420, + ipv4_direction=0, ipv6_direction=1) + + # Send syn packets and verify that the MSS value is lowered. + self.send_and_verify_ip6(self.pg1, self.pg0, 1460, 1420) + + # enable the the feature only in TX direction + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=1424, + ipv4_direction=0, ipv6_direction=2) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip6(self.pg1, self.pg0, 1460, 1460) + + # disable the feature + self.vapi.mss_clamp_enable_disable(self.pg1.sw_if_index, + ipv4_mss=0, ipv6_mss=0, + ipv4_direction=0, ipv6_direction=0) + + # Send the packets again and ensure they are unchanged. + self.send_and_verify_ip6(self.pg1, self.pg0, 1460, 1460) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg