ip_session_redirect: add session redirect plugin 55/33455/35
authorBenoît Ganne <bganne@cisco.com>
Tue, 10 Aug 2021 14:23:36 +0000 (16:23 +0200)
committerDave Wallace <dwallacelf@gmail.com>
Tue, 16 May 2023 13:29:04 +0000 (13:29 +0000)
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 <bganne@cisco.com>
15 files changed:
MAINTAINERS
docs/developer/plugins/index.rst
docs/developer/plugins/ip_session_redirect_doc.rst [new symlink]
src/plugins/ip_session_redirect/CMakeLists.txt [new file with mode: 0644]
src/plugins/ip_session_redirect/FEATURE.yaml [new file with mode: 0644]
src/plugins/ip_session_redirect/api.c [new file with mode: 0644]
src/plugins/ip_session_redirect/ip_session_redirect.api [new file with mode: 0644]
src/plugins/ip_session_redirect/ip_session_redirect.h [new file with mode: 0644]
src/plugins/ip_session_redirect/ip_session_redirect_doc.rst [new file with mode: 0644]
src/plugins/ip_session_redirect/punt_redirect.vpp [new file with mode: 0644]
src/plugins/ip_session_redirect/redirect.c [new file with mode: 0644]
src/plugins/ip_session_redirect/test_api.c [new file with mode: 0644]
src/vnet/fib/fib_api.c
src/vnet/fib/fib_api.h
test/test_ip_session_redirect.py [new file with mode: 0644]

index a9b8a1e..71ae16d 100644 (file)
@@ -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 <bganne@cisco.com>
+F:     src/plugins/ip_session_redirect/
+
 Plugin - linux-cp
 I:     linux-cp
 M:     Neale Ranns <neale@graphiant.com>
index e96a74a..4af8c4c 100644 (file)
@@ -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 (symlink)
index 0000000..b8e42cf
--- /dev/null
@@ -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 (file)
index 0000000..f77500f
--- /dev/null
@@ -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 (file)
index 0000000..d5cca46
--- /dev/null
@@ -0,0 +1,9 @@
+---
+name: IP session redirect
+maintainer: Benoît Ganne <bganne@cisco.com>
+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 (file)
index 0000000..1d17d55
--- /dev/null
@@ -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 <vlib/vlib.h>
+#include <vnet/fib/fib_api.h>
+#include <vnet/ip/ip_format_fns.h>
+#include <vlibmemory/api.h>
+#include <vlibapi/api.h>
+
+#define REPLY_MSG_ID_BASE vl_api_ip_sesion_redirect_msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+#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 (file)
index 0000000..2bf2373
--- /dev/null
@@ -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 <index> match <match> via <path>";
+  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 <index> match <match> via <path>";
+  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 <index> table <index> match <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 (file)
index 0000000..45f64ee
--- /dev/null
@@ -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 <vnet/fib/fib_node.h>
+
+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 (file)
index 0000000..aad8716
--- /dev/null
@@ -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 (file)
index 0000000..e3594cd
--- /dev/null
@@ -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 (file)
index 0000000..d925d12
--- /dev/null
@@ -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 <vlib/vlib.h>
+#include <vnet/fib/fib_path_list.h>
+#include <vnet/classify/vnet_classify.h>
+#include <vnet/classify/in_out_acl.h>
+#include <vnet/plugin/plugin.h>
+#include <vpp/app/version.h>
+#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 <table-index>] "
+               "[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 <index> match <match> "
+               "via <path> | del table <index> match <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 (file)
index 0000000..e4026a6
--- /dev/null
@@ -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 <vlib/vlib.h>
+#include <vnet/fib/fib_api.h>
+#include <vnet/ip/ip_format_fns.h>
+#include <vnet/classify/vnet_classify.h>
+#include <vat/vat.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#define __plugin_msg_base ip_session_redirect_test_main.msg_id_base
+#include <vlibapi/vat_helper_macros.h>
+/* 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:
+ */
index 75a17cf..c8511c0 100644 (file)
@@ -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)
 {
index 7fd7d16..0c59531 100644 (file)
@@ -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 (file)
index 0000000..620b216
--- /dev/null
@@ -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)