From 83e73709c33ca4188a04f355ccb13bde13d63271 Mon Sep 17 00:00:00 2001 From: Benoît Ganne Date: Tue, 10 Aug 2021 16:23:36 +0200 Subject: ip_session_redirect: add session redirect plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This feature enables the use of the classifier and ip-in-out-acl nodes to redirect matching sessions via arbitrary fib paths instead of relying on additional VRFs. Type: feature Change-Id: Ia59d35481c2555aec96c806b62bf29671abb295a Signed-off-by: Benoît Ganne --- MAINTAINERS | 5 + docs/developer/plugins/index.rst | 1 + docs/developer/plugins/ip_session_redirect_doc.rst | 1 + src/plugins/ip_session_redirect/CMakeLists.txt | 24 ++ src/plugins/ip_session_redirect/FEATURE.yaml | 9 + src/plugins/ip_session_redirect/api.c | 124 ++++++ .../ip_session_redirect/ip_session_redirect.api | 106 +++++ .../ip_session_redirect/ip_session_redirect.h | 33 ++ .../ip_session_redirect_doc.rst | 42 ++ src/plugins/ip_session_redirect/punt_redirect.vpp | 48 +++ src/plugins/ip_session_redirect/redirect.c | 463 +++++++++++++++++++++ src/plugins/ip_session_redirect/test_api.c | 195 +++++++++ src/vnet/fib/fib_api.c | 4 +- src/vnet/fib/fib_api.h | 2 + test/test_ip_session_redirect.py | 229 ++++++++++ 15 files changed, 1284 insertions(+), 2 deletions(-) create mode 120000 docs/developer/plugins/ip_session_redirect_doc.rst create mode 100644 src/plugins/ip_session_redirect/CMakeLists.txt create mode 100644 src/plugins/ip_session_redirect/FEATURE.yaml create mode 100644 src/plugins/ip_session_redirect/api.c create mode 100644 src/plugins/ip_session_redirect/ip_session_redirect.api create mode 100644 src/plugins/ip_session_redirect/ip_session_redirect.h create mode 100644 src/plugins/ip_session_redirect/ip_session_redirect_doc.rst create mode 100644 src/plugins/ip_session_redirect/punt_redirect.vpp create mode 100644 src/plugins/ip_session_redirect/redirect.c create mode 100644 src/plugins/ip_session_redirect/test_api.c create mode 100644 test/test_ip_session_redirect.py diff --git a/MAINTAINERS b/MAINTAINERS index a9b8a1e3b5b..71ae16d28a4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -771,6 +771,11 @@ I: geneve M: community vpp-dev@lists.fd.io F: src/plugins/geneve/ +Plugin - IP session redirect +I: ip_session_redirect +M: Benoît Ganne +F: src/plugins/ip_session_redirect/ + Plugin - linux-cp I: linux-cp M: Neale Ranns diff --git a/docs/developer/plugins/index.rst b/docs/developer/plugins/index.rst index e96a74a33e6..4af8c4c67be 100644 --- a/docs/developer/plugins/index.rst +++ b/docs/developer/plugins/index.rst @@ -40,3 +40,4 @@ For more on plugins please refer to :ref:`add_plugin`. acl_hash_lookup acl_lookup_context bufmon_doc + ip_session_redirect_doc diff --git a/docs/developer/plugins/ip_session_redirect_doc.rst b/docs/developer/plugins/ip_session_redirect_doc.rst new file mode 120000 index 00000000000..b8e42cf2c21 --- /dev/null +++ b/docs/developer/plugins/ip_session_redirect_doc.rst @@ -0,0 +1 @@ +../../../src/plugins/ip_session_redirect/ip_session_redirect_doc.rst \ No newline at end of file diff --git a/src/plugins/ip_session_redirect/CMakeLists.txt b/src/plugins/ip_session_redirect/CMakeLists.txt new file mode 100644 index 00000000000..f77500fc9fc --- /dev/null +++ b/src/plugins/ip_session_redirect/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) 2021-2022 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. + +add_vpp_plugin(ip_session_redirect + SOURCES + api.c + redirect.c + + API_FILES + ip_session_redirect.api + + API_TEST_SOURCES + test_api.c +) diff --git a/src/plugins/ip_session_redirect/FEATURE.yaml b/src/plugins/ip_session_redirect/FEATURE.yaml new file mode 100644 index 00000000000..d5cca4673d6 --- /dev/null +++ b/src/plugins/ip_session_redirect/FEATURE.yaml @@ -0,0 +1,9 @@ +--- +name: IP session redirect +maintainer: Benoît Ganne +features: + - use the classifier ACL infrastructure to redirect sessions via arbitrary + fib paths +description: "IP session redirect plugin" +state: experimental +properties: [CLI, STATS, MULTITHREAD, API] diff --git a/src/plugins/ip_session_redirect/api.c b/src/plugins/ip_session_redirect/api.c new file mode 100644 index 00000000000..1d17d55b5b4 --- /dev/null +++ b/src/plugins/ip_session_redirect/api.c @@ -0,0 +1,124 @@ +/* Copyright (c) 2021-2022 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 + +#define REPLY_MSG_ID_BASE vl_api_ip_sesion_redirect_msg_id_base +#include + +#include "ip_session_redirect.api_enum.h" +#include "ip_session_redirect.api_types.h" + +#include "ip_session_redirect.h" + +static u16 vl_api_ip_sesion_redirect_msg_id_base; + +static int +vl_api_ip_session_redirect_add (u32 table_index, u32 opaque_index, + vl_api_fib_path_nh_proto_t proto, int is_punt, + u8 *match, int match_len, + vl_api_fib_path_t *paths, int n_paths) +{ + vlib_main_t *vm = vlib_get_main (); + fib_route_path_t *paths_ = 0; + dpo_proto_t proto_; + u8 *match_ = 0; + int rv = 0; + + if (n_paths <= 0) + { + rv = VNET_API_ERROR_NO_PATHS_IN_ROUTE; + goto err0; + } + + for (int i = 0; i < n_paths; i++) + { + fib_route_path_t path; + if ((rv = fib_api_path_decode (&paths[i], &path))) + goto err1; + vec_add1 (paths_, path); + } + + if (~0 == proto) + proto_ = paths_[0].frp_proto; + else + fib_api_path_nh_proto_to_dpo (ntohl (proto), &proto_); + + vec_add (match_, match, match_len); + rv = ip_session_redirect_add (vm, ntohl (table_index), ntohl (opaque_index), + proto_, is_punt, match_, paths_); + vec_free (match_); + +err1: + vec_free (paths_); +err0: + return rv; +} + +static void +vl_api_ip_session_redirect_add_t_handler (vl_api_ip_session_redirect_add_t *mp) +{ + vl_api_ip_session_redirect_add_reply_t *rmp; + int rv = vl_api_ip_session_redirect_add ( + mp->table_index, mp->opaque_index, ~0 /* proto */, mp->is_punt, mp->match, + mp->match_len, mp->paths, mp->n_paths); + REPLY_MACRO (VL_API_IP_SESSION_REDIRECT_ADD_REPLY) +} + +static void +vl_api_ip_session_redirect_add_v2_t_handler ( + vl_api_ip_session_redirect_add_v2_t *mp) +{ + vl_api_ip_session_redirect_add_v2_reply_t *rmp; + int rv = vl_api_ip_session_redirect_add ( + mp->table_index, mp->opaque_index, mp->proto, mp->is_punt, mp->match, + mp->match_len, mp->paths, mp->n_paths); + REPLY_MACRO (VL_API_IP_SESSION_REDIRECT_ADD_V2_REPLY) +} + +static void +vl_api_ip_session_redirect_del_t_handler (vl_api_ip_session_redirect_del_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_ip_session_redirect_del_reply_t *rmp; + u8 *match = 0; + int rv; + + vec_add (match, mp->match, mp->match_len); + rv = ip_session_redirect_del (vm, ntohl (mp->table_index), match); + vec_free (match); + + REPLY_MACRO (VL_API_IP_SESSION_REDIRECT_DEL_REPLY); +} + +#include "ip_session_redirect.api.c" +static clib_error_t * +ip_session_redirect_plugin_api_hookup (vlib_main_t *vm) +{ + vl_api_ip_sesion_redirect_msg_id_base = setup_message_id_table (); + return 0; +} + +VLIB_API_INIT_FUNCTION (ip_session_redirect_plugin_api_hookup); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ip_session_redirect/ip_session_redirect.api b/src/plugins/ip_session_redirect/ip_session_redirect.api new file mode 100644 index 00000000000..2bf2373dbd2 --- /dev/null +++ b/src/plugins/ip_session_redirect/ip_session_redirect.api @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021-2022 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 = "0.3.0"; +import "vnet/interface_types.api"; +import "vnet/fib/fib_types.api"; + +/** \brief Add or update a session redirection + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param table_index - classifier table index + @param opaque_index - classifier session opaque index + @param match_len - classifier session match length in bytes (max is 80-bytes) + @param match - classifier session match + @param is_punt - true = punted traffic, false = forwarded traffic + @param n_paths - number of paths + @param paths - the paths of the redirect +*/ + +autoreply define ip_session_redirect_add +{ + option deprecated; + u32 client_index; + u32 context; + + u32 table_index; + u8 match_len; + u8 match[80]; + u32 opaque_index [default=0xffffffff]; + bool is_punt; + u8 n_paths; + vl_api_fib_path_t paths[n_paths]; + + option vat_help = "table match via "; + option status="in_progress"; +}; + +/** \brief Add or update a session redirection - version 2 + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param table_index - classifier table index + @param opaque_index - classifier session opaque index + @param proto - protocol of forwarded packets (default autodetect from path nh) + @param is_punt - true = punted traffic, false = forwarded traffic + @param match_len - classifier session match length in bytes (max is 80-bytes) + @param match - classifier session match + @param n_paths - number of paths + @param paths - the paths of the redirect +*/ + +autoreply define ip_session_redirect_add_v2 +{ + u32 client_index; + u32 context; + + u32 table_index; + u32 opaque_index [default=0xffffffff]; + vl_api_fib_path_nh_proto_t proto [default=0xffffffff]; + bool is_punt; + u8 match_len; + u8 match[80]; + u8 n_paths; + vl_api_fib_path_t paths[n_paths]; + + option vat_help = "table match via "; + option status="in_progress"; +}; + +/** \brief Delete a session redirection + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param table_index - classifier table index + @param match_len - classifier session match length in bytes (max is 80-bytes) + @param match - classifier session match +*/ + +autoreply define ip_session_redirect_del +{ + u32 client_index; + u32 context; + + u32 table_index; + u8 match_len; + u8 match[match_len]; + + option vat_help = "session-index table match "; + option status="in_progress"; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ip_session_redirect/ip_session_redirect.h b/src/plugins/ip_session_redirect/ip_session_redirect.h new file mode 100644 index 00000000000..45f64eebba1 --- /dev/null +++ b/src/plugins/ip_session_redirect/ip_session_redirect.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2021-2022 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 IP_SESSION_REDIRECT_H_ +#define IP_SESSION_REDIRECT_H_ + +#include + +int ip_session_redirect_add (vlib_main_t *vm, u32 table_index, + u32 opaque_index, dpo_proto_t proto, int is_punt, + const u8 *match, const fib_route_path_t *rpaths); +int ip_session_redirect_del (vlib_main_t *vm, u32 table_index, + const u8 *match); + +#endif /* IP_SESSION_REDIRECT_H_ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ip_session_redirect/ip_session_redirect_doc.rst b/src/plugins/ip_session_redirect/ip_session_redirect_doc.rst new file mode 100644 index 00000000000..aad87166f8f --- /dev/null +++ b/src/plugins/ip_session_redirect/ip_session_redirect_doc.rst @@ -0,0 +1,42 @@ +IP session redirect +=================== + +This plugin allows to steer packet via different paths based on the +classifier. +It leverages the VPP classifier ACL infrastructure (classifier, in_out_acl +etc), extending its capabilities to redirect traffic without having to +resort on additional VRFs. +It also allows to steer punted packets using the same mechanism. + +Maturity level +-------------- + +Under development: it should work, but has not been thoroughly tested. + +Features +-------- + +- steer regular or/and punt traffic using the classifier +- API + +Quickstart +---------- + +1. configure punting + +:: + + ~# vppctl set punt ipv4 udp all + +2. create the classifier table and uses it for punt ACL + +:: + + ~# vppctl classify table miss-next drop mask l3 ip4 src l4 udp src_port buckets 100000 + ~# vppctl set interface input acl intfc local0 ip4-punt-table 0 + +3. add session to steer punted packets + +:: + + ~# vppctl ip session redirect table 0 match l3 ip4 src 10.10.10.10 l4 src_port 1234 via 10.10.0.10 pg1 diff --git a/src/plugins/ip_session_redirect/punt_redirect.vpp b/src/plugins/ip_session_redirect/punt_redirect.vpp new file mode 100644 index 00000000000..e3594cd71d9 --- /dev/null +++ b/src/plugins/ip_session_redirect/punt_redirect.vpp @@ -0,0 +1,48 @@ +create packet-generator interface pg0 +set int ip addr pg0 10.10.10.1/24 + +create packet-generator interface pg1 +set int ip addr pg1 10.10.0.1/24 +set ip neighbor pg1 10.10.0.10 4.5.6 + +set punt ipv4 udp all + +classify table miss-next drop mask l3 ip4 src l4 udp src_port buckets 100000 +set interface input acl intfc local0 ip4-punt-table 0 +ip session redirect punt table 0 match l3 ip4 src 10.10.10.10 l4 src_port 1234 via 10.10.0.10 pg1 + +set int st pg0 up +set int st pg1 up + +comment { punt because of no udp listener for 53667, redirected } +packet-generator new { \ + name ok \ + limit 1 \ + node ethernet-input \ + source pg0 \ + size 100-100 \ + data { \ + IP4: 5.6.7 -> 2.3.4 \ + UDP: 10.10.10.10 -> 10.10.10.1 \ + UDP: 1234 -> 53667 \ + incrementing 1 \ + } \ +} + +comment { punt because of no udp listener for 53668, dropped } +packet-generator new { \ + name nok \ + limit 1 \ + node ethernet-input \ + source pg0 \ + size 100-100 \ + data { \ + IP4: 5.6.7 -> 2.3.4 \ + UDP: 10.10.10.10 -> 10.10.10.1 \ + UDP: 1235 -> 53668 \ + incrementing 1 \ + } \ +} + +trace add pg-input 10 +pa en diff --git a/src/plugins/ip_session_redirect/redirect.c b/src/plugins/ip_session_redirect/redirect.c new file mode 100644 index 00000000000..d925d12cde7 --- /dev/null +++ b/src/plugins/ip_session_redirect/redirect.c @@ -0,0 +1,463 @@ +/* Copyright (c) 2021-2022 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 "ip_session_redirect.h" + +typedef struct +{ + u8 *match_and_table_index; + dpo_id_t dpo; /* forwarding dpo */ + fib_node_t node; /* linkage into the FIB graph */ + fib_node_index_t pl; + u32 sibling; + u32 parent_node_index; + u32 opaque_index; + u32 table_index; + fib_forward_chain_type_t payload_type; + u8 is_punt : 1; + u8 is_ip6 : 1; +} ip_session_redirect_t; + +typedef struct +{ + ip_session_redirect_t *pool; + u32 *session_by_match_and_table_index; + fib_node_type_t fib_node_type; +} ip_session_redirect_main_t; + +static ip_session_redirect_main_t ip_session_redirect_main; + +static int +ip_session_redirect_stack (ip_session_redirect_t *ipr) +{ + dpo_id_t dpo = DPO_INVALID; + + fib_path_list_contribute_forwarding (ipr->pl, ipr->payload_type, + fib_path_list_is_popular (ipr->pl) ? + FIB_PATH_LIST_FWD_FLAG_NONE : + FIB_PATH_LIST_FWD_FLAG_COLLAPSE, + &dpo); + dpo_stack_from_node (ipr->parent_node_index, &ipr->dpo, &dpo); + dpo_reset (&dpo); + + /* update session with new next_index */ + return vnet_classify_add_del_session ( + &vnet_classify_main, ipr->table_index, ipr->match_and_table_index, + ipr->dpo.dpoi_next_node /* hit_next_index */, ipr->opaque_index, + 0 /* advance */, CLASSIFY_ACTION_SET_METADATA, + ipr->dpo.dpoi_index /* metadata */, 1 /* is_add */); +} + +static ip_session_redirect_t * +ip_session_redirect_find (ip_session_redirect_main_t *im, u32 table_index, + const u8 *match) +{ + /* we are adding the table index at the end of the match string so we + * can disambiguiate identical matches in different tables in + * im->session_by_match_and_table_index */ + u8 *match_and_table_index = vec_dup (match); + vec_add (match_and_table_index, (void *) &table_index, 4); + uword *p = + hash_get_mem (im->session_by_match_and_table_index, match_and_table_index); + vec_free (match_and_table_index); + if (!p) + return 0; + return pool_elt_at_index (im->pool, p[0]); +} + +int +ip_session_redirect_add (vlib_main_t *vm, u32 table_index, u32 opaque_index, + dpo_proto_t proto, int is_punt, const u8 *match, + const fib_route_path_t *rpaths) +{ + ip_session_redirect_main_t *im = &ip_session_redirect_main; + fib_forward_chain_type_t payload_type; + ip_session_redirect_t *ipr; + const char *pname; + + payload_type = fib_forw_chain_type_from_dpo_proto (proto); + switch (payload_type) + { + case FIB_FORW_CHAIN_TYPE_UNICAST_IP4: + pname = is_punt ? "ip4-punt-acl" : "ip4-inacl"; + break; + case FIB_FORW_CHAIN_TYPE_UNICAST_IP6: + pname = is_punt ? "ip6-punt-acl" : "ip6-inacl"; + break; + default: + return VNET_API_ERROR_INVALID_ADDRESS_FAMILY; + } + + ipr = ip_session_redirect_find (im, table_index, match); + if (ipr) + { + /* update to an existing session */ + fib_path_list_child_remove (ipr->pl, ipr->sibling); + dpo_reset (&ipr->dpo); + } + else + { + /* allocate a new entry */ + pool_get (im->pool, ipr); + fib_node_init (&ipr->node, im->fib_node_type); + ipr->match_and_table_index = vec_dup ((u8 *) match); + /* we are adding the table index at the end of the match string so we + * can disambiguiate identical matches in different tables in + * im->session_by_match_and_table_index */ + vec_add (ipr->match_and_table_index, (void *) &table_index, 4); + ipr->table_index = table_index; + hash_set_mem (im->session_by_match_and_table_index, + ipr->match_and_table_index, ipr - im->pool); + } + + ipr->payload_type = payload_type; + ipr->pl = fib_path_list_create ( + FIB_PATH_LIST_FLAG_SHARED | FIB_PATH_LIST_FLAG_NO_URPF, rpaths); + ipr->sibling = + fib_path_list_child_add (ipr->pl, im->fib_node_type, ipr - im->pool); + ipr->parent_node_index = vlib_get_node_by_name (vm, (u8 *) pname)->index; + ipr->opaque_index = opaque_index; + ipr->is_punt = is_punt; + ipr->is_ip6 = payload_type == FIB_FORW_CHAIN_TYPE_UNICAST_IP6; + + return ip_session_redirect_stack (ipr); +} + +int +ip_session_redirect_del (vlib_main_t *vm, u32 table_index, const u8 *match) +{ + ip_session_redirect_main_t *im = &ip_session_redirect_main; + vnet_classify_main_t *cm = &vnet_classify_main; + ip_session_redirect_t *ipr; + int rv; + + ipr = ip_session_redirect_find (im, table_index, match); + if (!ipr) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + rv = vnet_classify_add_del_session ( + cm, ipr->table_index, ipr->match_and_table_index, 0 /* hit_next_index */, + 0 /* opaque_index */, 0 /* advance */, 0 /* action */, 0 /* metadata */, + 0 /* is_add */); + if (rv) + return rv; + + hash_unset_mem (im->session_by_match_and_table_index, + ipr->match_and_table_index); + vec_free (ipr->match_and_table_index); + fib_path_list_child_remove (ipr->pl, ipr->sibling); + dpo_reset (&ipr->dpo); + pool_put (im->pool, ipr); + return 0; +} + +static int +ip_session_redirect_show_yield (vlib_main_t *vm, f64 *start) +{ + /* yields for 2 clock ticks every 1 tick to avoid blocking the main thread + * when dumping huge data structures */ + f64 now = vlib_time_now (vm); + if (now - *start > 11e-6) + { + vlib_process_suspend (vm, 21e-6); + *start = vlib_time_now (vm); + return 1; + } + + return 0; +} + +static u8 * +format_ip_session_redirect (u8 *s, va_list *args) +{ + const ip_session_redirect_main_t *im = &ip_session_redirect_main; + const ip_session_redirect_t *ipr = + va_arg (*args, const ip_session_redirect_t *); + index_t ipri = ipr - im->pool; + const char *type = ipr->is_punt ? "[punt]" : "[acl]"; + const char *ip = ipr->is_ip6 ? "[ip6]" : "[ip4]"; + s = + format (s, "[%u] %s %s table %d key %U opaque_index 0x%x\n", ipri, type, + ip, ipr->table_index, format_hex_bytes, ipr->match_and_table_index, + vec_len (ipr->match_and_table_index) - 4, ipr->opaque_index); + s = format (s, " via:\n"); + s = format (s, " %U", format_fib_path_list, ipr->pl, 2); + s = format (s, " forwarding\n"); + s = format (s, " %U", format_dpo_id, &ipr->dpo, 0); + return s; +} + +static clib_error_t * +ip_session_redirect_show_cmd (vlib_main_t *vm, unformat_input_t *main_input, + vlib_cli_command_t *cmd) +{ + ip_session_redirect_main_t *im = &ip_session_redirect_main; + unformat_input_t _line_input, *line_input = &_line_input; + vnet_classify_main_t *cm = &vnet_classify_main; + ip_session_redirect_t *ipr; + clib_error_t *error = 0; + u32 table_index = ~0; + int is_punt = -1; + int is_ip6 = -1; + u8 *match = 0; + int max = 50; + u8 *s = 0; + + if (unformat_is_eof (main_input)) + unformat_init (line_input, 0, + 0); /* support straight "sh ip session redirect" */ + else if (!unformat_user (main_input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "all")) + ; + else if (unformat (line_input, "punt")) + is_punt = 1; + else if (unformat (line_input, "acl")) + is_punt = 0; + else if (unformat (line_input, "ip4")) + is_ip6 = 0; + else if (unformat (line_input, "ip6")) + is_ip6 = 1; + else if (unformat (line_input, "table %u", &table_index)) + ; + else if (unformat (line_input, "match %U", unformat_classify_match, cm, + &match, table_index)) + ; + else if (unformat (line_input, "max %d", &max)) + ; + else + { + error = unformat_parse_error (line_input); + goto out; + } + } + + if (match) + { + ipr = ip_session_redirect_find (im, table_index, match); + if (!ipr) + vlib_cli_output (vm, "none"); + else + vlib_cli_output (vm, "%U", format_ip_session_redirect, ipr); + } + else + { + f64 start = vlib_time_now (vm); + ip_session_redirect_t *iprs = im->pool; + int n = 0; + pool_foreach (ipr, iprs) + { + if (n >= max) + { + n = -1; /* signal overflow */ + break; + } + if ((~0 == table_index || ipr->table_index == table_index) && + (-1 == is_punt || ipr->is_punt == is_punt) && + (-1 == is_ip6 || ipr->is_ip6 == is_ip6)) + { + s = format (s, "%U\n", format_ip_session_redirect, ipr); + n++; + } + if (ip_session_redirect_show_yield (vm, &start)) + { + /* we must reload the pool as it might have moved */ + u32 ii = ipr - iprs; + iprs = im->pool; + ipr = iprs + ii; + } + } + vec_add1 (s, 0); + vlib_cli_output (vm, (char *) s); + vec_free (s); + if (-1 == n) + { + vlib_cli_output ( + vm, + "\nPlease note: only the first %d entries displayed. " + "To display more, specify max.", + max); + } + } + +out: + vec_free (match); + unformat_free (line_input); + return error; +} + +VLIB_CLI_COMMAND (ip_session_redirect_show_command, static) = { + .path = "show ip session redirect", + .function = ip_session_redirect_show_cmd, + .short_help = "show ip session redirect [all|[table ] " + "[punt|acl] [ip4|ip6] [match]]", +}; + +static clib_error_t * +ip_session_redirect_cmd (vlib_main_t *vm, unformat_input_t *main_input, + vlib_cli_command_t *cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + vnet_classify_main_t *cm = &vnet_classify_main; + dpo_proto_t proto = DPO_PROTO_IP4; + fib_route_path_t *rpaths = 0, rpath; + clib_error_t *error = 0; + u32 opaque_index = ~0; + u32 table_index = ~0; + int is_punt = 0; + int is_add = 1; + u8 *match = 0; + int rv; + + if (!unformat_user (main_input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "del")) + is_add = 0; + else if (unformat (line_input, "add")) + is_add = 1; + else if (unformat (line_input, "punt")) + is_punt = 1; + else if (unformat (line_input, "table %u", &table_index)) + ; + else if (unformat (line_input, "opaque-index %u", &opaque_index)) + ; + else if (unformat (line_input, "match %U", unformat_classify_match, cm, + &match, table_index)) + ; + else if (unformat (line_input, "via %U", unformat_fib_route_path, &rpath, + &proto)) + vec_add1 (rpaths, rpath); + else + { + error = unformat_parse_error (line_input); + goto out; + } + } + + if (~0 == table_index || 0 == match) + { + error = clib_error_create ("missing table index or match"); + goto out; + } + + if (is_add) + { + if (0 == rpaths) + { + error = clib_error_create ("missing path"); + goto out; + } + rv = ip_session_redirect_add (vm, table_index, opaque_index, proto, + is_punt, match, rpaths); + } + else + { + rv = ip_session_redirect_del (vm, table_index, match); + } + + if (rv) + error = clib_error_create ("failed with error %d", rv); + +out: + vec_free (rpaths); + vec_free (match); + unformat_free (line_input); + return error; +} + +VLIB_CLI_COMMAND (ip_session_redirect_command, static) = { + .path = "ip session redirect", + .function = ip_session_redirect_cmd, + .short_help = "ip session redirect [add] [punt] table match " + "via | del table match " +}; + +static fib_node_t * +ip_session_redirect_get_node (fib_node_index_t index) +{ + ip_session_redirect_main_t *im = &ip_session_redirect_main; + ip_session_redirect_t *ipr = pool_elt_at_index (im->pool, index); + return &ipr->node; +} + +static ip_session_redirect_t * +ip_session_redirect_get_from_node (fib_node_t *node) +{ + return ( + ip_session_redirect_t *) (((char *) node) - + STRUCT_OFFSET_OF (ip_session_redirect_t, node)); +} + +static void +ip_session_redirect_last_lock_gone (fib_node_t *node) +{ + /* the lifetime of the entry is managed by the table. */ + ASSERT (0); +} + +/* A back walk has reached this entry */ +static fib_node_back_walk_rc_t +ip_session_redirect_back_walk_notify (fib_node_t *node, + fib_node_back_walk_ctx_t *ctx) +{ + int rv; + ip_session_redirect_t *ipr = ip_session_redirect_get_from_node (node); + rv = ip_session_redirect_stack (ipr); + ASSERT (0 == rv); + if (rv) + clib_warning ("ip_session_redirect_stack() error %d", rv); + return FIB_NODE_BACK_WALK_CONTINUE; +} + +static const fib_node_vft_t ip_session_redirect_vft = { + .fnv_get = ip_session_redirect_get_node, + .fnv_last_lock = ip_session_redirect_last_lock_gone, + .fnv_back_walk = ip_session_redirect_back_walk_notify, +}; + +static clib_error_t * +ip_session_redirect_init (vlib_main_t *vm) +{ + ip_session_redirect_main_t *im = &ip_session_redirect_main; + im->session_by_match_and_table_index = + hash_create_vec (0, sizeof (u8), sizeof (u32)); + im->fib_node_type = fib_node_register_new_type ("ip-session-redirect", + &ip_session_redirect_vft); + return 0; +} + +VLIB_INIT_FUNCTION (ip_session_redirect_init); + +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "IP session redirect", +}; + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ip_session_redirect/test_api.c b/src/plugins/ip_session_redirect/test_api.c new file mode 100644 index 00000000000..e4026a673ff --- /dev/null +++ b/src/plugins/ip_session_redirect/test_api.c @@ -0,0 +1,195 @@ +/* Copyright (c) 2021-2022 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 +#define __plugin_msg_base ip_session_redirect_test_main.msg_id_base +#include +/* declare message IDs */ +#include "ip_session_redirect.api_enum.h" +#include "ip_session_redirect.api_types.h" +#include "ip_session_redirect.h" + +typedef struct +{ + /* API message ID base */ + u16 msg_id_base; + vat_main_t *vat_main; +} ip_session_redirect_test_main_t; + +ip_session_redirect_test_main_t ip_session_redirect_test_main; + +static int +api_ip_session_redirect_add_parse (vat_main_t *vam, u32 *table_index, + u32 *opaque_index, dpo_proto_t *proto, + int *is_punt, u8 **match, + fib_route_path_t **paths) +{ + vnet_classify_main_t *cm = &vnet_classify_main; + fib_route_path_t path; + + *table_index = ~0; + *opaque_index = ~0; + *proto = DPO_PROTO_IP4; + *is_punt = 0; + *match = 0; + *paths = 0; + + while (unformat_check_input (vam->input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (vam->input, "punt")) + *is_punt = 1; + else if (unformat (vam->input, "table %u", table_index)) + ; + else if (unformat (vam->input, "opaque-index %u", opaque_index)) + ; + else if (unformat (vam->input, "match %U", unformat_classify_match, cm, + match, *table_index)) + ; + else if (unformat (vam->input, "via %U", unformat_fib_route_path, &path, + proto)) + vec_add1 (*paths, path); + else + { + clib_warning ("unknown input `%U'", format_unformat_error, + vam->input); + return -99; + } + } + + return 0; +} + +static int +api_ip_session_redirect_add (vat_main_t *vam) +{ + vl_api_ip_session_redirect_add_t *mp; + fib_route_path_t *paths; + dpo_proto_t proto; + u32 opaque_index; + u32 table_index; + int is_punt; + int ret, i; + u8 *match; + + ret = api_ip_session_redirect_add_parse (vam, &table_index, &opaque_index, + &proto, &is_punt, &match, &paths); + if (ret) + goto err; + + M2 (IP_SESSION_REDIRECT_ADD, mp, vec_len (paths) * sizeof (mp->paths[0])); + + mp->table_index = htonl (table_index); + mp->opaque_index = htonl (opaque_index); + mp->is_punt = is_punt; + memcpy_s (mp->match, sizeof (mp->match), match, vec_len (match)); + mp->n_paths = vec_len (paths); + vec_foreach_index (i, paths) + fib_api_path_encode (&paths[i], &mp->paths[i]); + + S (mp); + W (ret); + +err: + vec_free (match); + vec_free (paths); + return ret; +} + +static int +api_ip_session_redirect_add_v2 (vat_main_t *vam) +{ + vl_api_ip_session_redirect_add_v2_t *mp; + fib_route_path_t *paths; + dpo_proto_t proto; + u32 opaque_index; + u32 table_index; + int is_punt; + int ret, i; + u8 *match; + + ret = api_ip_session_redirect_add_parse (vam, &table_index, &opaque_index, + &proto, &is_punt, &match, &paths); + if (ret) + goto err; + + M2 (IP_SESSION_REDIRECT_ADD_V2, mp, vec_len (paths) * sizeof (mp->paths[0])); + + mp->table_index = htonl (table_index); + mp->opaque_index = htonl (opaque_index); + mp->proto = fib_api_path_dpo_proto_to_nh (proto); + mp->is_punt = is_punt; + memcpy_s (mp->match, sizeof (mp->match), match, vec_len (match)); + mp->n_paths = vec_len (paths); + vec_foreach_index (i, paths) + fib_api_path_encode (&paths[i], &mp->paths[i]); + + S (mp); + W (ret); + +err: + vec_free (match); + vec_free (paths); + return ret; +} + +static int +api_ip_session_redirect_del (vat_main_t *vam) +{ + vnet_classify_main_t *cm = &vnet_classify_main; + vl_api_ip_session_redirect_del_t *mp; + u32 table_index = ~0; + u8 *match = 0; + int ret; + + while (unformat_check_input (vam->input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (vam->input, "table %u", &table_index)) + ; + else if (unformat (vam->input, "match %U", unformat_classify_match, cm, + &match, table_index)) + ; + else + { + clib_warning ("unknown input '%U'", format_unformat_error, + vam->input); + return -99; + } + } + + M2 (IP_SESSION_REDIRECT_DEL, mp, vec_len (match)); + + mp->table_index = htonl (table_index); + mp->match_len = htonl (vec_len (match)); + clib_memcpy (mp->match, match, vec_len (match)); + + S (mp); + W (ret); + + return ret; +} + +#include "ip_session_redirect.api_test.c" + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/fib/fib_api.c b/src/vnet/fib/fib_api.c index 75a17cfca02..c8511c08eba 100644 --- a/src/vnet/fib/fib_api.c +++ b/src/vnet/fib/fib_api.c @@ -69,7 +69,7 @@ fib_api_next_hop_decode (const vl_api_fib_path_t *in, *out = to_ip46 (FIB_API_PATH_NH_PROTO_IP6 == in->proto, (void *)&in->nh.address); } -static vl_api_fib_path_nh_proto_t +vl_api_fib_path_nh_proto_t fib_api_path_dpo_proto_to_nh (dpo_proto_t dproto) { switch (dproto) @@ -108,7 +108,7 @@ fib_api_next_hop_encode (const fib_route_path_t *rpath, sizeof (rpath->frp_addr.ip6)); } -static int +int fib_api_path_nh_proto_to_dpo (vl_api_fib_path_nh_proto_t pp, dpo_proto_t *dproto) { diff --git a/src/vnet/fib/fib_api.h b/src/vnet/fib/fib_api.h index 7fd7d16cb33..0c59531b438 100644 --- a/src/vnet/fib/fib_api.h +++ b/src/vnet/fib/fib_api.h @@ -29,6 +29,8 @@ struct _vl_api_fib_prefix; /** * Encode and decode functions from the API types to internal types */ +extern vl_api_fib_path_nh_proto_t fib_api_path_dpo_proto_to_nh (dpo_proto_t dproto); +extern int fib_api_path_nh_proto_to_dpo (vl_api_fib_path_nh_proto_t pp, dpo_proto_t *dproto); extern void fib_api_path_encode(const fib_route_path_t * api_rpath, vl_api_fib_path_t *out); extern int fib_api_path_decode(vl_api_fib_path_t *in, diff --git a/test/test_ip_session_redirect.py b/test/test_ip_session_redirect.py new file mode 100644 index 00000000000..620b21626a9 --- /dev/null +++ b/test/test_ip_session_redirect.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 + +import unittest + +import socket + +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP +from scapy.layers.inet6 import IPv6 + +from vpp_papi import VppEnum +from vpp_ip_route import VppRoutePath + +from framework import VppTestCase + + +class TestIpSessionRedirect(VppTestCase): + """IP session redirect Test Case""" + + @classmethod + def setUpClass(cls): + super(TestIpSessionRedirect, cls).setUpClass() + itfs = cls.create_pg_interfaces(range(3)) + for itf in itfs: + itf.admin_up() + itf.config_ip4() + itf.resolve_arp() + itf.config_ip6() + itf.resolve_ndp() + + def __build_mask(self, ip, src_port, match_n_vectors): + # UDP: udp src port (2 bytes) + udp = src_port.to_bytes(2, byteorder="big") + match = ip + udp + # skip the remainer + match += b"\x00" * (match_n_vectors * 16 - len(match)) + return match + + def build_mask4(self, proto, src_ip, src_port): + proto = proto.to_bytes(1, byteorder="big") + # IP: skip 9 bytes | proto (1 byte) | skip checksum (2 bytes) | src IP + # (4 bytes) | skip dst IP (4 bytes) + ip = b"\x00" * 9 + proto + b"\x00" * 2 + src_ip + b"\x00" * 4 + return self.__build_mask(ip, src_port, 2) + + def build_mask6(self, proto, src_ip, src_port): + nh = proto.to_bytes(1, byteorder="big") + # IPv6: skip 6 bytes | nh (1 byte) | skip hl (1 byte) | src IP (16 + # bytes) | skip dst IP (16 bytes) + ip = b"\x00" * 6 + nh + b"\x00" + src_ip + b"\x00" * 16 + return self.__build_mask(ip, src_port, 4) + + def build_match(self, src_ip, src_port, is_ip6): + if is_ip6: + return self.build_mask6( + 0x11, socket.inet_pton(socket.AF_INET6, src_ip), src_port + ) + else: + return self.build_mask4( + 0x11, socket.inet_pton(socket.AF_INET, src_ip), src_port + ) + + def create_table(self, is_ip6): + if is_ip6: + mask = self.build_mask6(0xFF, b"\xff" * 16, 0xFFFF) + match_n_vectors = 4 + else: + mask = self.build_mask4(0xFF, b"\xff" * 4, 0xFFFF) + match_n_vectors = 2 + r = self.vapi.classify_add_del_table( + is_add=True, + match_n_vectors=match_n_vectors, + miss_next_index=0, # drop + current_data_flag=1, # match on current header (ip) + mask_len=len(mask), + mask=mask, + ) + return r.new_table_index + + def __test_redirect(self, sport, dport, is_punt, is_ip6): + if is_ip6: + af = VppEnum.vl_api_address_family_t.ADDRESS_IP6 + nh1 = self.pg1.remote_ip6 + nh2 = self.pg2.remote_ip6 + # note: nh3 is using a v4 adj to forward ipv6 packets + nh3 = self.pg2.remote_ip4 + src = self.pg0.remote_ip6 + dst = self.pg0.local_ip6 + IP46 = IPv6 + proto = VppEnum.vl_api_fib_path_nh_proto_t.FIB_API_PATH_NH_PROTO_IP6 + else: + af = VppEnum.vl_api_address_family_t.ADDRESS_IP4 + nh1 = self.pg1.remote_ip4 + nh2 = self.pg2.remote_ip4 + # note: nh3 is using a v6 adj to forward ipv4 packets + nh3 = self.pg2.remote_ip6 + src = self.pg0.remote_ip4 + dst = self.pg0.local_ip4 + IP46 = IP + proto = VppEnum.vl_api_fib_path_nh_proto_t.FIB_API_PATH_NH_PROTO_IP4 + + if is_punt: + # punt udp packets to dport + self.vapi.set_punt( + is_add=1, + punt={ + "type": VppEnum.vl_api_punt_type_t.PUNT_API_TYPE_L4, + "punt": { + "l4": { + "af": af, + "protocol": VppEnum.vl_api_ip_proto_t.IP_API_PROTO_UDP, + "port": dport, + } + }, + }, + ) + + pkts = [ + ( + Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) + / IP46(src=src, dst=dst) + / UDP(sport=sport, dport=dport) + / Raw("\x17" * 100) + ) + ] * 2 + + # create table and configure ACL + table_index = self.create_table(is_ip6) + ip4_tid, ip6_tid = ( + (0xFFFFFFFF, table_index) if is_ip6 else (table_index, 0xFFFFFFFF) + ) + + if is_punt: + self.vapi.punt_acl_add_del( + is_add=1, ip4_table_index=ip4_tid, ip6_table_index=ip6_tid + ) + else: + self.vapi.input_acl_set_interface( + is_add=1, + ip4_table_index=ip4_tid, + ip6_table_index=ip6_tid, + l2_table_index=0xFFFFFFFF, + sw_if_index=self.pg0.sw_if_index, + ) + + # add a session redirect rule but not matching the stream: expect to + # drop + paths = [VppRoutePath(nh1, 0xFFFFFFFF).encode()] + match1 = self.build_match(src, sport + 10, is_ip6) + r = self.vapi.ip_session_redirect_add_v2( + table_index=table_index, + match_len=len(match1), + match=match1, + is_punt=is_punt, + n_paths=1, + paths=paths, + ) + self.send_and_assert_no_replies(self.pg0, pkts) + + # redirect a session matching the stream: expect to pass + match2 = self.build_match(src, sport, is_ip6) + self.vapi.ip_session_redirect_add_v2( + table_index=table_index, + match_len=len(match2), + match=match2, + is_punt=is_punt, + n_paths=1, + paths=paths, + ) + self.send_and_expect_only(self.pg0, pkts, self.pg1) + + # update the matching entry so it redirects to pg2 + # nh3 is using a v4 adj for v6 and vice-versa, hence we must specify + # the payload proto with v2 api + paths = [VppRoutePath(nh3, 0xFFFFFFFF).encode()] + self.vapi.ip_session_redirect_add_v2( + table_index=table_index, + match_len=len(match2), + match=match2, + is_punt=is_punt, + n_paths=1, + paths=paths, + proto=proto, + ) + self.send_and_expect_only(self.pg0, pkts, self.pg2) + + # we still have only 2 sessions, not 3 + t = self.vapi.classify_table_info(table_id=table_index) + self.assertEqual(t.active_sessions, 2) + + # cleanup + self.vapi.ip_session_redirect_del(table_index, len(match2), match2) + self.vapi.ip_session_redirect_del(table_index, len(match1), match1) + t = self.vapi.classify_table_info(table_id=table_index) + self.assertEqual(t.active_sessions, 0) + + if is_punt: + self.vapi.punt_acl_add_del( + is_add=0, ip4_table_index=ip4_tid, ip6_table_index=ip6_tid + ) + else: + self.vapi.input_acl_set_interface( + is_add=0, + ip4_table_index=ip4_tid, + ip6_table_index=ip6_tid, + l2_table_index=0xFFFFFFFF, + sw_if_index=self.pg0.sw_if_index, + ) + + def test_punt_redirect_ipv4(self): + """IPv4 punt session redirect test""" + return self.__test_redirect(sport=6754, dport=17923, is_punt=True, is_ip6=False) + + def test_punt_redirect_ipv6(self): + """IPv6 punt session redirect test""" + return self.__test_redirect(sport=28447, dport=4035, is_punt=True, is_ip6=True) + + def test_redirect_ipv4(self): + """IPv4 session redirect test""" + return self.__test_redirect(sport=834, dport=1267, is_punt=False, is_ip6=False) + + def test_redirect_ipv6(self): + """IPv6 session redirect test""" + return self.__test_redirect(sport=9999, dport=32768, is_punt=False, is_ip6=True) + + +if __name__ == "__main__": + unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg