sfdp_services: plugin with basic SFDP services 73/43873/8
authorMohammed Hawari <[email protected]>
Fri, 10 Oct 2025 06:45:39 +0000 (08:45 +0200)
committerMohammed HAWARI <[email protected]>
Tue, 21 Oct 2025 19:35:42 +0000 (19:35 +0000)
Change-Id: Id33e9f40fcc20d38c9749aada26a1b345b3ad027
Type: feature
Signed-off-by: Mohammed Hawari <[email protected]>
42 files changed:
MAINTAINERS
src/plugins/sfdp_services/CMakeLists.txt [new file with mode: 0644]
src/plugins/sfdp_services/acl/acl_sample.c [new file with mode: 0644]
src/plugins/sfdp_services/acl/acl_sample.h [new file with mode: 0644]
src/plugins/sfdp_services/acl/cli.c [new file with mode: 0644]
src/plugins/sfdp_services/acl/node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/interface_input/api.c [new file with mode: 0644]
src/plugins/sfdp_services/base/interface_input/cli.c [new file with mode: 0644]
src/plugins/sfdp_services/base/interface_input/interface_input.api [new file with mode: 0644]
src/plugins/sfdp_services/base/interface_input/interface_input.c [new file with mode: 0644]
src/plugins/sfdp_services/base/interface_input/interface_input.h [new file with mode: 0644]
src/plugins/sfdp_services/base/interface_input/node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/l4-lifecycle/node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/api.c [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/cli.c [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/external_input_node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/fastpath_node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/format.c [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/nat.api [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/nat.c [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/nat.h [new file with mode: 0644]
src/plugins/sfdp_services/base/nat/slowpath_node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/sample/node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/tcp-check/api.c [new file with mode: 0644]
src/plugins/sfdp_services/base/tcp-check/cli.c [new file with mode: 0644]
src/plugins/sfdp_services/base/tcp-check/format.c [new file with mode: 0644]
src/plugins/sfdp_services/base/tcp-check/node.c [new file with mode: 0644]
src/plugins/sfdp_services/base/tcp-check/tcp_check.api [new file with mode: 0644]
src/plugins/sfdp_services/base/tcp-check/tcp_check.c [new file with mode: 0644]
src/plugins/sfdp_services/base/tcp-check/tcp_check.h [new file with mode: 0644]
src/plugins/sfdp_services/dot1q/dot1q.c [new file with mode: 0644]
src/plugins/sfdp_services/geneve/api.c [new file with mode: 0644]
src/plugins/sfdp_services/geneve/cli.c [new file with mode: 0644]
src/plugins/sfdp_services/geneve/gateway.api [new file with mode: 0644]
src/plugins/sfdp_services/geneve/gateway.c [new file with mode: 0644]
src/plugins/sfdp_services/geneve/gateway.h [new file with mode: 0644]
src/plugins/sfdp_services/geneve/geneve_input/node.c [new file with mode: 0644]
src/plugins/sfdp_services/geneve/geneve_output/node.c [new file with mode: 0644]
src/plugins/sfdp_services/reass/full_reass_node.c [moved from src/vnet/sfdp/lookup/full_reass_node.c with 86% similarity]
src/plugins/sfdp_services/reass/reass.c [moved from src/vnet/sfdp/lookup/reass.c with 71% similarity]
src/plugins/sfdp_services/reass/reass.h [moved from src/vnet/sfdp/lookup/reass.h with 100% similarity]
src/plugins/sfdp_services/reass/sv_reass_node.c [moved from src/vnet/sfdp/lookup/sv_reass_node.c with 85% similarity]

index 6ba9e56..18e9ac3 100644 (file)
@@ -223,6 +223,11 @@ M:      Mohammed Hawari <[email protected]>
 M:      Ole Troan <[email protected]>
 F:      src/vnet/sfdp/
 
+Plugin StateFul Data Plane Services
+I:      sfdp_services
+M:      Mohammed Hawari <[email protected]>
+F:      src/plugins/sfdp_services
+
 Plugin - Crypto - native
 I:     crypto-native
 M:     Damjan Marion <[email protected]>
diff --git a/src/plugins/sfdp_services/CMakeLists.txt b/src/plugins/sfdp_services/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c8806d0
--- /dev/null
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2025 Cisco Systems, Inc.
+#
+
+#include_directories(${CMAKE_SOURCE_DIR})
+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
+
+add_vpp_plugin(sfdp_services
+  SOURCES
+  base/tcp-check/node.c
+  base/tcp-check/tcp_check.c
+  base/tcp-check/format.c
+  base/tcp-check/cli.c
+  base/tcp-check/api.c
+
+  base/l4-lifecycle/node.c
+
+  base/nat/nat.c
+  base/nat/cli.c
+  base/nat/api.c
+  base/nat/format.c
+  base/nat/fastpath_node.c
+  base/nat/slowpath_node.c
+  base/nat/external_input_node.c
+
+  base/interface_input/interface_input.c
+  base/interface_input/cli.c
+  base/interface_input/api.c
+  base/interface_input/node.c
+
+  acl/acl_sample.c
+  acl/cli.c
+  acl/node.c
+
+  dot1q/dot1q.c
+
+  geneve/gateway.c
+  geneve/cli.c
+  geneve/api.c
+  geneve/geneve_input/node.c
+  geneve/geneve_output/node.c
+
+  reass/reass.c
+  reass/sv_reass_node.c
+  #reass/full_reass_node.c
+
+  MULTIARCH_SOURCES
+  base/tcp-check/node.c
+  base/l4-lifecycle/node.c
+  base/interface_input/node.c
+  acl/node.c
+  geneve/geneve_input/node.c
+  geneve/geneve_output/node.c
+  dot1q/dot1q.c
+  reass/sv_reass_node.c
+  #reass/full_reass_node.c
+
+  API_FILES
+  base/nat/nat.api
+  base/tcp-check/tcp_check.api
+  base/interface_input/interface_input.api
+  geneve/gateway.api
+)
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/acl/acl_sample.c b/src/plugins/sfdp_services/acl/acl_sample.c
new file mode 100644 (file)
index 0000000..a91ae4c
--- /dev/null
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <acl/acl_sample.h>
+
+static void
+acl_sample_validate_tenant_lc (u16 tenant_idx)
+{
+  sfdp_acl_main_t *vam = &sfdp_acl_main;
+  sfdp_main_t *sfdp = &sfdp_main;
+  sfdp_tenant_t *tenant = sfdp_tenant_at_index (sfdp, tenant_idx);
+  u32 lc;
+
+  if (vec_elt_at_index (vam->lc_by_tenant_idx, tenant_idx)[0] != ~0)
+    return;
+
+  lc = acl_plugin.get_lookup_context_index (vam->acl_user_id,
+                                           tenant->tenant_id, 0);
+  vec_elt_at_index (vam->lc_by_tenant_idx, tenant_idx)[0] = lc;
+};
+
+clib_error_t *
+sfdp_acl_sample_tenant_set_acl (sfdp_acl_main_t *vam, u64 tenant_id,
+                               u32 acl_index, bool disable)
+{
+  sfdp_main_t *sfdp = &sfdp_main;
+  clib_error_t *err = 0;
+  clib_bihash_kv_8_8_t kv = { .key = tenant_id, .value = 0 };
+  u16 tenant_idx;
+  u32 lc;
+  u32 *acl_vec = 0;
+  if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+    {
+      err = clib_error_return (0, "Invalid tenant id: %llu", tenant_id);
+      return err;
+    }
+  tenant_idx = kv.value;
+  acl_sample_validate_tenant_lc (tenant_idx);
+  lc = vam->lc_by_tenant_idx[tenant_idx];
+  if (!disable)
+    vec_add1 (acl_vec, acl_index);
+  acl_plugin.set_acl_vec_for_context (lc, acl_vec);
+  vec_free (acl_vec);
+  return err;
+}
+
+static clib_error_t *
+acl_sample_init (vlib_main_t *vm)
+{
+  sfdp_acl_main_t *vam = &sfdp_acl_main;
+  sfdp_main_t *sfdp = &sfdp_main;
+  clib_error_t *err = acl_plugin_exports_init (&acl_plugin);
+  int i;
+  if (err)
+    return err;
+
+  vam->acl_user_id =
+    acl_plugin.register_user_module ("sfdp ACL plugin", "tenant id", NULL);
+
+  vec_validate (vam->lc_by_tenant_idx, (1ULL << sfdp->log2_tenants) - 1);
+
+  vec_foreach_index (i, vam->lc_by_tenant_idx)
+    vam->lc_by_tenant_idx[i] = ~0;
+
+  return 0;
+}
+
+VLIB_MAIN_LOOP_ENTER_FUNCTION (acl_sample_init) = {
+
+};
+
+sfdp_acl_main_t sfdp_acl_main;
+acl_plugin_methods_t acl_plugin;
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/acl/acl_sample.h b/src/plugins/sfdp_services/acl/acl_sample.h
new file mode 100644 (file)
index 0000000..f84d46b
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef __included_acl_sample_h__
+#define __included_acl_sample_h__
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+#include <acl/exports.h>
+
+typedef struct
+{
+  u32 acl_user_id;
+  u32 *lc_by_tenant_idx; /* vec */
+} sfdp_acl_main_t;
+
+clib_error_t *sfdp_acl_sample_tenant_set_acl (sfdp_acl_main_t *vam,
+                                             u64 tenant_id, u32 acl_index,
+                                             bool disable);
+
+extern sfdp_acl_main_t sfdp_acl_main;
+extern acl_plugin_methods_t acl_plugin;
+#endif
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/acl/cli.c b/src/plugins/sfdp_services/acl/cli.c
new file mode 100644 (file)
index 0000000..20591ca
--- /dev/null
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <acl/acl_sample.h>
+
+static clib_error_t *
+sfdp_acl_sample_set_fn (vlib_main_t *vm, unformat_input_t *input,
+                       vlib_cli_command_t *cmd)
+{
+  sfdp_acl_main_t *vam = &sfdp_acl_main;
+  unformat_input_t line_input_, *line_input = &line_input_;
+
+  clib_error_t *err = 0;
+  u32 sw_if_index = ~0;
+  u32 tenant_id = ~0;
+  u32 acl_index = ~0;
+  u8 disable = 0;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "tenant %d", &tenant_id))
+       ;
+
+      else if (unformat (line_input, "acl_index %d", &acl_index))
+       ;
+      else if (unformat (line_input, "disable"))
+       disable = 1;
+      else if (unformat (line_input, "%U", unformat_vnet_sw_interface,
+                        vnet_get_main (), &sw_if_index))
+       ;
+      else
+       {
+         err = unformat_parse_error (line_input);
+         goto done;
+       }
+    }
+  err = sfdp_acl_sample_tenant_set_acl (vam, tenant_id, acl_index, disable);
+done:
+  unformat_free (line_input);
+  return err;
+}
+
+VLIB_CLI_COMMAND (sfdp_acl_sample_set_cmd, static) = {
+  .path = "set sfdp acl",
+  .short_help =
+    "set sfdp acl tenant <tenant-id> acl_index <acl_index> [disable]",
+  .function = sfdp_acl_sample_set_fn,
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/acl/node.c b/src/plugins/sfdp_services/acl/node.c
new file mode 100644 (file)
index 0000000..aba7f60
--- /dev/null
@@ -0,0 +1,195 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <acl/acl_sample.h>
+#include <vnet/sfdp/service.h>
+#include <vnet/sfdp/sfdp_funcs.h>
+
+#define foreach_sfdp_acl_sample_error _ (DROP, "drop")
+
+typedef enum
+{
+#define _(sym, str) SFDP_ACL_SAMPLE_ERROR_##sym,
+  foreach_sfdp_acl_sample_error
+#undef _
+    SFDP_ACL_SAMPLE_N_ERROR,
+} sfdp_acl_sample_error_t;
+
+static char *sfdp_acl_sample_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_acl_sample_error
+#undef _
+};
+
+typedef struct
+{
+  u32 thread_index;
+  u32 flow_id;
+  u8 matched;
+  u8 action;
+} sfdp_acl_sample_trace_t;
+
+static u8 *
+format_sfdp_acl_sample_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sfdp_acl_sample_trace_t *t = va_arg (*args, sfdp_acl_sample_trace_t *);
+  const char *action_str[] = { "deny", "permit", "permit+reflect" };
+
+  s = format (
+    s, "sfdp-acl-sample: flow-id %u (session %u, %s) status: %s, action: %s\n",
+    t->flow_id, t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward",
+    t->matched ? "matched" : "unmatched",
+    t->matched ? action_str[t->action] : "<none>");
+  return s;
+}
+
+SFDP_SERVICE_DECLARE (drop)
+SFDP_SERVICE_DECLARE (sfdp_acl_sample)
+static_always_inline void
+sfdp_acl_sample_process_one (sfdp_acl_main_t *vam, sfdp_session_t *session,
+                            u8 dir, u16 *to_next, vlib_buffer_t **b,
+                            u8 *matched, u8 *action)
+{
+  u16 tenant_idx = session->tenant_idx;
+  u32 lc_index = vam->lc_by_tenant_idx[tenant_idx];
+  fa_5tuple_opaque_t fa_5tuple;
+  u32 match_acl_index = ~0;
+  u32 match_acl_pos = ~0;
+  u32 match_rule_index = ~0;
+  u32 trace_bitmap = 0;
+
+  if (lc_index == ~0)
+    goto end_of_packet;
+
+  acl_plugin_fill_5tuple_inline (acl_plugin.p_acl_main, lc_index, b[0], 0, 1,
+                                0, &fa_5tuple);
+
+  if (acl_plugin_match_5tuple_inline (
+       acl_plugin.p_acl_main, lc_index, &fa_5tuple, 0, action, &match_acl_pos,
+       &match_acl_index, &match_rule_index, &trace_bitmap))
+    {
+      matched[0] = 1;
+      if (action[0] == 0)
+       {
+         /* Drop this flow, in this direction, forever */
+         session->bitmaps[dir] |= SFDP_SERVICE_MASK (drop);
+         sfdp_buffer (b[0])->service_bitmap = SFDP_SERVICE_MASK (drop);
+       }
+      else if (action[0] == 1)
+       /* Allow this packet only */
+       ;
+      else
+       {
+         /* Allow this flow and cache the decision in forward and reverse
+          * direction */
+         session->bitmaps[SFDP_FLOW_FORWARD] &=
+           ~SFDP_SERVICE_MASK (sfdp_acl_sample);
+         session->bitmaps[SFDP_FLOW_REVERSE] &=
+           ~SFDP_SERVICE_MASK (sfdp_acl_sample);
+       }
+    }
+  else
+    {
+      matched[0] = 0;
+    }
+
+end_of_packet:
+  sfdp_next (b[0], to_next);
+  return;
+}
+
+static_always_inline u16
+sfdp_acl_sample_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
+                       vlib_frame_t *frame)
+{
+
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  sfdp_acl_main_t *vam = &sfdp_acl_main;
+  u32 thread_index = vlib_get_thread_index ();
+  sfdp_session_t *session;
+  u32 session_idx;
+  u8 dir;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+  u8 matched[VLIB_FRAME_SIZE], action[VLIB_FRAME_SIZE];
+  u8 *m = matched;
+  u8 *a = action;
+  vlib_get_buffers (vm, from, bufs, n_left);
+  while (n_left > 0)
+    {
+      session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+      dir = sfdp_direction_from_flow_index (b[0]->flow_id);
+      session = sfdp_session_at_index (session_idx);
+
+      sfdp_acl_sample_process_one (vam, session, dir, to_next, b, m, a);
+      n_left -= 1;
+      b += 1;
+      to_next += 1;
+      m += 1;
+      a += 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      int i;
+      b = bufs;
+      m = matched;
+      a = action;
+      n_left = frame->n_vectors;
+      for (i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sfdp_acl_sample_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             t->flow_id = b[0]->flow_id;
+             t->thread_index = thread_index;
+             t->matched = m[0];
+             t->action = a[0];
+             b++;
+             m++;
+             a++;
+           }
+         else
+           break;
+       }
+    }
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (sfdp_acl_sample_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return sfdp_acl_sample_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (sfdp_acl_sample_node) = {
+  .name = "sfdp-acl-sample",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_acl_sample_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_acl_sample_error_strings),
+  .error_strings = sfdp_acl_sample_error_strings
+};
+
+SFDP_SERVICE_DEFINE (sfdp_acl_sample) = {
+  .node_name = "sfdp-acl-sample",
+  .runs_before = SFDP_SERVICES ("sfdp-geneve-output", "ip4-lookup"),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle",
+                              "sfdp-tcp-check"),
+  .is_terminal = 0
+};
+
+SFDP_SERVICE_DEFINE (ip4_lookup) = {
+  .node_name = "ip4-lookup",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle",
+                              "sfdp-tcp-check"),
+  .is_terminal = 1
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/interface_input/api.c b/src/plugins/sfdp_services/base/interface_input/api.c
new file mode 100644 (file)
index 0000000..0811bae
--- /dev/null
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/sfdp/sfdp.h>
+
+#include <sfdp_services/base/interface_input/interface_input.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vnet/ip/ip_types_api.h>
+
+#include <vnet/format_fns.h>
+#include <sfdp_services/base/interface_input/interface_input.api_enum.h>
+#include <sfdp_services/base/interface_input/interface_input.api_types.h>
+
+#define REPLY_MSG_ID_BASE vim->msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+static void
+vl_api_sfdp_interface_input_set_t_handler (
+  vl_api_sfdp_interface_input_set_t *mp)
+{
+  sfdp_interface_input_main_t *vim = &sfdp_interface_input_main;
+  u32 sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);
+  u32 tenant_id = clib_net_to_host_u32 (mp->tenant_id);
+  u8 unset = mp->is_disable;
+  clib_error_t *err =
+    sfdp_interface_input_set_tenant (vim, sw_if_index, tenant_id, unset);
+  int rv = err ? -1 : 0;
+  vl_api_sfdp_interface_input_set_reply_t *rmp;
+  REPLY_MACRO (VL_API_SFDP_INTERFACE_INPUT_SET_REPLY);
+}
+
+#include <sfdp_services/base/interface_input/interface_input.api.c>
+static clib_error_t *
+sfdp_interface_input_api_hookup (vlib_main_t *vm)
+{
+  sfdp_interface_input_main_t *vim = &sfdp_interface_input_main;
+  vim->msg_id_base = setup_message_id_table ();
+  return 0;
+}
+VLIB_API_INIT_FUNCTION (sfdp_interface_input_api_hookup);
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/sfdp_services/base/interface_input/cli.c b/src/plugins/sfdp_services/base/interface_input/cli.c
new file mode 100644 (file)
index 0000000..3de9c24
--- /dev/null
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <sfdp_services/base/interface_input/interface_input.h>
+
+static clib_error_t *
+sfdp_interface_input_set_unset_fn (vlib_main_t *vm, unformat_input_t *input,
+                                  vlib_cli_command_t *cmd)
+{
+  unformat_input_t line_input_, *line_input = &line_input_;
+
+  clib_error_t *err = 0;
+  u32 sw_if_index = ~0;
+  u32 tenant_id = ~0;
+  u8 unset = 0;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "tenant %d", &tenant_id))
+       ;
+      else if (unformat (line_input, "disable"))
+       unset = 1;
+      else if (unformat (line_input, "%U", unformat_vnet_sw_interface,
+                        vnet_get_main (), &sw_if_index))
+       ;
+      else
+       {
+         err = unformat_parse_error (line_input);
+         goto done;
+       }
+    }
+  err = sfdp_interface_input_set_tenant (&sfdp_interface_input_main,
+                                        sw_if_index, tenant_id, unset);
+done:
+  unformat_free (line_input);
+  return err;
+}
+
+VLIB_CLI_COMMAND (sfdp_nat_external_interface_set_unset, static) = {
+  .path = "set sfdp interface-input",
+  .short_help =
+    "set sfdp interface-input <interface> tenant <tenant-id> [disable]",
+  .function = sfdp_interface_input_set_unset_fn,
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/interface_input/interface_input.api b/src/plugins/sfdp_services/base/interface_input/interface_input.api
new file mode 100644 (file)
index 0000000..9cec606
--- /dev/null
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+option version = "0.0.1";
+
+import "vnet/ip/ip_types.api";
+import "vnet/interface_types.api";
+
+autoreply define sfdp_interface_input_set
+{
+  u32 client_index;
+  u32 context;
+
+  vl_api_interface_index_t sw_if_index;
+  u32 tenant_id;
+  u8 is_disable;
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/interface_input/interface_input.c b/src/plugins/sfdp_services/base/interface_input/interface_input.c
new file mode 100644 (file)
index 0000000..63f5278
--- /dev/null
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/sfdp/sfdp.h>
+#include <sfdp_services/base/interface_input/interface_input.h>
+#include <vppinfra/pool.h>
+
+clib_error_t *
+sfdp_interface_input_set_tenant (sfdp_interface_input_main_t *vim,
+                                u32 sw_if_index, u32 tenant_id, u8 unset)
+{
+  sfdp_main_t *sfdp = &sfdp_main;
+  clib_bihash_kv_8_8_t kv = { .key = tenant_id, .value = 0 };
+  vnet_main_t *vnm = vnet_get_main ();
+  u16 *config;
+
+  if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+    return clib_error_return (0, "Tenant with id %d not found");
+
+  vec_validate (vim->tenant_idx_by_sw_if_idx, sw_if_index);
+  config = vim->tenant_idx_by_sw_if_idx + sw_if_index;
+
+  if (config[0] == ((u16) ~0) && unset)
+    return clib_error_return (
+      0, "Outside tenant %d is not configured on interface %U", tenant_id,
+      format_vnet_sw_if_index_name, vnm, sw_if_index);
+
+  if (config[0] != (u16) (~0) && !unset)
+    return clib_error_return (0, "Interface %U is already configured",
+                             format_vnet_sw_if_index_name, vnm, sw_if_index);
+
+  if (!unset)
+    {
+      vnet_feature_enable_disable ("ip4-unicast", "sfdp-interface-input",
+                                  sw_if_index, 1, 0, 0);
+      config[0] = kv.value;
+    }
+
+  else
+    {
+      vnet_feature_enable_disable ("ip4-unicast", "sfdp-interface-input",
+                                  sw_if_index, 0, 0, 0);
+      config[0] = (u16) (~0);
+    }
+
+  return 0;
+}
+
+clib_error_t *
+sfdp_interface_input_add_del_sw_interface (vnet_main_t *vnm, u32 sw_if_index,
+                                          u32 is_add)
+{
+  sfdp_interface_input_main_t *vim = &sfdp_interface_input_main;
+  uword old_size = vec_len (vim->tenant_idx_by_sw_if_idx);
+  if (sw_if_index >= old_size)
+    {
+      vec_validate (vim->tenant_idx_by_sw_if_idx, sw_if_index);
+      for (int i = old_size; i <= sw_if_index; i++)
+       vim->tenant_idx_by_sw_if_idx[i] = (u16) (~0);
+    }
+  return 0;
+}
+
+VNET_SW_INTERFACE_ADD_DEL_FUNCTION (sfdp_interface_input_add_del_sw_interface);
+sfdp_interface_input_main_t sfdp_interface_input_main;
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/interface_input/interface_input.h b/src/plugins/sfdp_services/base/interface_input/interface_input.h
new file mode 100644 (file)
index 0000000..153c7ed
--- /dev/null
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef __included_nat_h__
+#define __included_nat_h__
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+
+typedef struct
+{
+  u16 *tenant_idx_by_sw_if_idx; /* vec */
+  u16 msg_id_base;
+} sfdp_interface_input_main_t;
+
+extern sfdp_interface_input_main_t sfdp_interface_input_main;
+
+clib_error_t *
+sfdp_interface_input_set_tenant (sfdp_interface_input_main_t *nat,
+                                u32 sw_if_index, u32 tenant_id, u8 unset);
+#endif
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/interface_input/node.c b/src/plugins/sfdp_services/base/interface_input/node.c
new file mode 100644 (file)
index 0000000..1b0e633
--- /dev/null
@@ -0,0 +1,129 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/feature/feature.h>
+#include <sfdp_services/base/interface_input/interface_input.h>
+#include <vnet/sfdp/sfdp.h>
+#include <vnet/sfdp/common.h>
+typedef struct
+{
+  u32 tenant_id;
+  u32 sw_if_index;
+} sfdp_interface_input_trace_t;
+
+static u8 *
+format_sfdp_interface_input_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 *);
+  sfdp_interface_input_trace_t *t =
+    va_arg (*args, sfdp_interface_input_trace_t *);
+
+  s = format (s, "sfdp-interface-input: sw_if_index %d, tenant %d\n",
+             t->sw_if_index, t->tenant_id);
+
+  return s;
+}
+
+#define foreach_sfdp_interface_input_next  _ (LOOKUP, "sfdp-lookup-ip4")
+#define foreach_sfdp_interface_input_error _ (NOERROR, "No error")
+
+typedef enum
+{
+#define _(sym, str) SFDP_INTERFACE_INPUT_ERROR_##sym,
+  foreach_sfdp_interface_input_error
+#undef _
+    SFDP_INTERFACE_INPUT_N_ERROR,
+} sfdp_interface_input_error_t;
+
+static char *sfdp_interface_input_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_interface_input_error
+#undef _
+};
+
+typedef enum
+{
+#define _(s, n) SFDP_INTERFACE_INPUT_NEXT_##s,
+  foreach_sfdp_interface_input_next
+#undef _
+    SFDP_INTERFACE_INPUT_N_NEXT
+} sfdp_interface_input_next_t;
+
+static_always_inline uword
+sfdp_interface_input_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
+                            vlib_frame_t *frame)
+{
+  /*
+   * use VNI as tenant ID
+   * tenant_id -> tenant index
+   * drop unknown tenants
+   * store tenant_id into opaque1
+   * advance current data to beginning of IP packet
+   */
+  sfdp_main_t *sfdp = &sfdp_main;
+  sfdp_interface_input_main_t *vim = &sfdp_interface_input_main;
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  vlib_combined_counter_main_t *cm =
+    &sfdp->tenant_data_ctr[SFDP_TENANT_DATA_COUNTER_INCOMING];
+
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *current_next;
+  uword thread_index = vlib_get_thread_index ();
+  vlib_get_buffers (vm, from, bufs, n_left);
+  b = bufs;
+  current_next = next_indices;
+
+  while (n_left)
+    {
+      u32 len = vlib_buffer_length_in_chain (vm, b[0]);
+      u32 rx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
+      u32 tenant_idx = vim->tenant_idx_by_sw_if_idx[rx_sw_if_index];
+      sfdp_tenant_t *tenant;
+      if (tenant_idx == ~0)
+       {
+         vnet_feature_next_u16 (current_next, b[0]);
+         goto end_of_packet;
+       }
+      tenant = sfdp_tenant_at_index (sfdp, tenant_idx);
+      b[0]->flow_id = tenant->context_id;
+      sfdp_buffer (b[0])->tenant_index = tenant_idx;
+      current_next[0] = SFDP_INTERFACE_INPUT_NEXT_LOOKUP;
+
+      vlib_increment_combined_counter (cm, thread_index, tenant_idx, 1, len);
+    end_of_packet:
+      b += 1;
+      current_next += 1;
+      n_left -= 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (sfdp_interface_input_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return sfdp_interface_input_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (sfdp_interface_input_node) = {
+  .name = "sfdp-interface-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_interface_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_interface_input_error_strings),
+  .error_strings = sfdp_interface_input_error_strings,
+  .n_next_nodes = SFDP_INTERFACE_INPUT_N_NEXT,
+  .next_nodes = {
+          [SFDP_INTERFACE_INPUT_NEXT_LOOKUP] = "sfdp-lookup-ip4",
+  },
+};
+
+VNET_FEATURE_INIT (sfdp_interface_input_feat, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "sfdp-interface-input",
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/l4-lifecycle/node.c b/src/plugins/sfdp_services/base/l4-lifecycle/node.c
new file mode 100644 (file)
index 0000000..8deda32
--- /dev/null
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+#include <vnet/sfdp/service.h>
+#include <vnet/sfdp/timer/timer.h>
+#define foreach_sfdp_l4_lifecycle_error _ (DROP, "drop")
+
+typedef enum
+{
+#define _(sym, str) SFDP_L4_LIFECYCLE_ERROR_##sym,
+  foreach_sfdp_l4_lifecycle_error
+#undef _
+    SFDP_L4_LIFECYCLE_N_ERROR,
+} sfdp_l4_lifecycle_error_t;
+
+static char *sfdp_l4_lifecycle_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_l4_lifecycle_error
+#undef _
+};
+
+typedef struct
+{
+  u32 flow_id;
+  u8 new_state;
+} sfdp_l4_lifecycle_trace_t;
+
+static u8 *
+format_sfdp_l4_lifecycle_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sfdp_l4_lifecycle_trace_t *t = va_arg (*args, sfdp_l4_lifecycle_trace_t *);
+
+  s = format (
+    s, "sfdp-l4-lifecycle: flow-id %u (session %u, %s) new_state: %U",
+    t->flow_id, t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward",
+    format_sfdp_session_state, t->new_state);
+  return s;
+}
+
+SFDP_SERVICE_DECLARE (tcp_check)
+SFDP_SERVICE_DECLARE (l4_lifecycle)
+VLIB_NODE_FN (sfdp_l4_lifecycle_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  sfdp_main_t *sfdp = &sfdp_main;
+
+  u32 thread_index = vm->thread_index;
+  sfdp_timer_per_thread_data_t *tptd =
+    vec_elt_at_index (sfdp_timer_main.per_thread_data, thread_index);
+
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+
+  while (n_left)
+    {
+      u32 session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+      u16 tenant_idx = sfdp_buffer (b[0])->tenant_index;
+      sfdp_session_t *session = sfdp_session_at_index (session_idx);
+      sfdp_tenant_t *tenant = sfdp_tenant_at_index (sfdp, tenant_idx);
+      u8 direction = sfdp_direction_from_flow_index (b[0]->flow_id);
+      /* TODO: prefetch, 4-loop, remove ifs and do state-transition-timer LUT?
+       */
+      if (session->proto == IP_PROTOCOL_TCP)
+       {
+         session->bitmaps[SFDP_FLOW_FORWARD] &=
+           ~SFDP_SERVICE_MASK (l4_lifecycle);
+         session->bitmaps[SFDP_FLOW_REVERSE] &=
+           ~SFDP_SERVICE_MASK (l4_lifecycle);
+         sfdp_buffer (b[0])->service_bitmap |= SFDP_SERVICE_MASK (tcp_check);
+         session->bitmaps[SFDP_FLOW_FORWARD] |= SFDP_SERVICE_MASK (tcp_check);
+         session->bitmaps[SFDP_FLOW_REVERSE] |= SFDP_SERVICE_MASK (tcp_check);
+       }
+      else
+       {
+         if (session->state == SFDP_SESSION_STATE_FSOL &&
+             direction == SFDP_FLOW_REVERSE)
+           /*Establish the session*/
+           session->state = SFDP_SESSION_STATE_ESTABLISHED;
+
+         if (session->state == SFDP_SESSION_STATE_ESTABLISHED)
+           {
+             /* TODO: must be configurable per tenant */
+             sfdp_session_timer_update (
+               &tptd->wheel, SFDP_SESSION_TIMER (session), tptd->current_time,
+               tenant->timeouts[SFDP_TIMEOUT_ESTABLISHED]);
+           }
+       }
+      sfdp_next (b[0], to_next);
+
+      b++;
+      to_next++;
+      n_left--;
+    }
+
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      n_left = frame->n_vectors;
+      b = bufs;
+      for (int i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sfdp_l4_lifecycle_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             u32 session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+             sfdp_session_t *session = sfdp_session_at_index (session_idx);
+             u16 state = session->state;
+             t->flow_id = b[0]->flow_id;
+             t->new_state = state;
+             b++;
+           }
+         else
+           break;
+       }
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  return frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (sfdp_l4_lifecycle_node) = {
+  .name = "sfdp-l4-lifecycle",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_l4_lifecycle_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_l4_lifecycle_error_strings),
+  .error_strings = sfdp_l4_lifecycle_error_strings,
+};
+
+SFDP_SERVICE_DEFINE (l4_lifecycle) = {
+  .node_name = "sfdp-l4-lifecycle",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES ("sfdp-drop"),
+  .is_terminal = 0
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/nat/api.c b/src/plugins/sfdp_services/base/nat/api.c
new file mode 100644 (file)
index 0000000..08b0df1
--- /dev/null
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/sfdp/sfdp.h>
+
+#include <sfdp_services/base/nat/nat.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vnet/ip/ip_types_api.h>
+
+#include <vnet/format_fns.h>
+#include <sfdp_services/base/nat/nat.api_enum.h>
+#include <sfdp_services/base/nat/nat.api_types.h>
+
+#define REPLY_MSG_ID_BASE nat->msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+static void
+vl_api_sfdp_nat_set_external_interface_t_handler (
+  vl_api_sfdp_nat_set_external_interface_t *mp)
+{
+  nat_main_t *nat = &nat_main;
+  u32 sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);
+  u32 tenant_id = clib_net_to_host_u32 (mp->tenant_id);
+  u8 unset = mp->is_disable;
+  clib_error_t *err =
+    nat_external_interface_set_tenant (nat, sw_if_index, tenant_id, unset);
+  int rv = err ? -1 : 0;
+  vl_api_sfdp_nat_set_external_interface_reply_t *rmp;
+  REPLY_MACRO (VL_API_SFDP_NAT_SET_EXTERNAL_INTERFACE_REPLY);
+}
+
+static void
+vl_api_sfdp_nat_alloc_pool_add_del_t_handler (
+  vl_api_sfdp_nat_alloc_pool_add_del_t *mp)
+{
+  nat_main_t *nat = &nat_main;
+  u32 alloc_pool_id = clib_net_to_host_u32 (mp->alloc_pool_id);
+  u8 is_del = mp->is_del;
+  uword n_addr = clib_net_to_host_u32 (mp->n_addr);
+  ip4_address_t *addrs = 0;
+  clib_error_t *err;
+  int rv;
+  vl_api_sfdp_nat_alloc_pool_add_del_reply_t *rmp;
+  vec_resize (addrs, n_addr);
+  for (int i = 0; i < n_addr; i++)
+    ip4_address_decode (mp->addr[i], addrs + i);
+
+  err = nat_alloc_pool_add_del (nat, alloc_pool_id, is_del, addrs);
+  vec_free (addrs);
+  rv = err ? -1 : 0;
+  REPLY_MACRO (VL_API_SFDP_NAT_ALLOC_POOL_ADD_DEL_REPLY);
+}
+
+static void
+vl_api_sfdp_nat_snat_set_unset_t_handler (vl_api_sfdp_nat_snat_set_unset_t *mp)
+{
+  nat_main_t *nat = &nat_main;
+  u32 tenant_id = clib_net_to_host_u32 (mp->tenant_id);
+  u32 outside_tenant_id = clib_net_to_host_u32 (mp->outside_tenant_id);
+  u32 table_id = clib_net_to_host_u32 (mp->table_id);
+  u32 alloc_pool_id = clib_net_to_host_u32 (mp->alloc_pool_id);
+  u8 unset = mp->is_disable;
+  clib_error_t *err;
+  int rv;
+  vl_api_sfdp_nat_alloc_pool_add_del_reply_t *rmp;
+
+  err = nat_tenant_set_snat (nat, tenant_id, outside_tenant_id, table_id,
+                            alloc_pool_id, unset);
+  rv = err ? -1 : 0;
+  REPLY_MACRO (VL_API_SFDP_NAT_SNAT_SET_UNSET_REPLY);
+}
+
+#include <sfdp_services/base/nat/nat.api.c>
+static clib_error_t *
+sfdp_nat_api_hookup (vlib_main_t *vm)
+{
+  nat_main_t *nat = &nat_main;
+  nat->msg_id_base = setup_message_id_table ();
+  return 0;
+}
+VLIB_API_INIT_FUNCTION (sfdp_nat_api_hookup);
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/sfdp_services/base/nat/cli.c b/src/plugins/sfdp_services/base/nat/cli.c
new file mode 100644 (file)
index 0000000..bd2aa4c
--- /dev/null
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <sfdp_services/base/nat/nat.h>
+
+static clib_error_t *
+sfdp_nat_external_interface_set_unset_fn (vlib_main_t *vm,
+                                         unformat_input_t *input,
+                                         vlib_cli_command_t *cmd)
+{
+  unformat_input_t line_input_, *line_input = &line_input_;
+
+  clib_error_t *err = 0;
+  u32 sw_if_index = ~0;
+  u32 tenant_id = ~0;
+  u8 unset = 0;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "tenant %d", &tenant_id))
+       ;
+      else if (unformat (line_input, "disable"))
+       unset = 1;
+      else if (unformat (line_input, "%U", unformat_vnet_sw_interface,
+                        vnet_get_main (), &sw_if_index))
+       ;
+      else
+       {
+         err = unformat_parse_error (line_input);
+         goto done;
+       }
+    }
+  err = nat_external_interface_set_tenant (&nat_main, sw_if_index, tenant_id,
+                                          unset);
+done:
+  unformat_free (line_input);
+  return err;
+}
+
+VLIB_CLI_COMMAND (sfdp_nat_external_interface_set_unset, static) = {
+  .path = "set sfdp nat external-interface",
+  .short_help =
+    "set sfdp nat external-interface <interface> tenant <tenant-id> [disable]",
+  .function = sfdp_nat_external_interface_set_unset_fn,
+};
+
+static clib_error_t *
+sfdp_nat_alloc_pool_add_del_fn (vlib_main_t *vm, unformat_input_t *input,
+                               vlib_cli_command_t *cmd)
+{
+  unformat_input_t line_input_, *line_input = &line_input_;
+
+  clib_error_t *err = 0;
+  u8 is_del = 0;
+  u32 alloc_pool_id = ~0;
+  ip4_address_t tmp;
+  ip4_address_t *addr = 0;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "add %d", &alloc_pool_id))
+       is_del = 0;
+      else if (unformat (line_input, "del %d", &alloc_pool_id))
+       is_del = 1;
+      else if (unformat (line_input, "%U", unformat_ip4_address, &tmp))
+       vec_add1 (addr, tmp);
+      else
+       {
+         err = unformat_parse_error (line_input);
+         goto done;
+       }
+    }
+  nat_alloc_pool_add_del (&nat_main, alloc_pool_id, is_del, addr);
+done:
+  unformat_free (line_input);
+  vec_free (addr);
+  return err;
+}
+
+VLIB_CLI_COMMAND (sfdp_nat_alloc_pool_add_del, static) = {
+  .path = "sfdp nat alloc-pool",
+  .short_help = "sfdp nat alloc-pool [add|del] <alloc-pool-id> <ip-addr>+",
+  .function = sfdp_nat_alloc_pool_add_del_fn,
+};
+
+static clib_error_t *
+sfdp_nat_snat_set_unset_fn (vlib_main_t *vm, unformat_input_t *input,
+                           vlib_cli_command_t *cmd)
+{
+  unformat_input_t line_input_, *line_input = &line_input_;
+
+  clib_error_t *err = 0;
+  u32 tenant_id = ~0;
+  u32 outside_tenant_id = ~0;
+  u32 table_id = ~0;
+  u32 alloc_pool_id = ~0;
+  u8 unset = 0;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "tenant %d", &tenant_id))
+       ;
+      else if (unformat (line_input, "outside-tenant %d", &outside_tenant_id))
+       ;
+      else if (unformat (line_input, "table %d", &table_id))
+       ;
+      else if (unformat (line_input, "alloc-pool %d", &alloc_pool_id))
+       ;
+      else if (unformat (line_input, "disable"))
+       unset = 1;
+      else
+       {
+         err = unformat_parse_error (line_input);
+         goto done;
+       }
+    }
+  err = nat_tenant_set_snat (&nat_main, tenant_id, outside_tenant_id, table_id,
+                            alloc_pool_id, unset);
+done:
+  unformat_free (line_input);
+  return err;
+}
+
+VLIB_CLI_COMMAND (sfdp_nat_snat_set_unset, static) = {
+  .path = "set sfdp nat snat",
+  .short_help =
+    "set sfdp nat snat tenant <tenant-id> outside-tenant <tenant-id> table "
+    "<table-id> alloc-pool <alloc-pool-id> [disable]",
+  .function = sfdp_nat_snat_set_unset_fn,
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/nat/external_input_node.c b/src/plugins/sfdp_services/base/nat/external_input_node.c
new file mode 100644 (file)
index 0000000..a162d08
--- /dev/null
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/feature/feature.h>
+#include <sfdp_services/base/nat/nat.h>
+#include <vnet/sfdp/sfdp.h>
+#include <vnet/sfdp/common.h>
+typedef struct
+{
+  u32 tenant_id;
+  u32 sw_if_index;
+} nat_external_input_trace_t;
+
+static u8 *
+format_nat_external_input_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 *);
+  nat_external_input_trace_t *t = va_arg (*args, nat_external_input_trace_t *);
+
+  s = format (s, "nat-external-input: sw_if_index %d, tenant %d\n",
+             t->sw_if_index, t->tenant_id);
+
+  return s;
+}
+
+#define foreach_nat_external_input_next         _ (LOOKUP, "sfdp-lookup-ip4")
+#define foreach_nat_external_input_error _ (NOERROR, "No error")
+
+typedef enum
+{
+#define _(sym, str) NAT_EXTERNAL_INPUT_ERROR_##sym,
+  foreach_nat_external_input_error
+#undef _
+    NAT_EXTERNAL_INPUT_N_ERROR,
+} nat_external_input_error_t;
+
+static char *nat_external_input_error_strings[] = {
+#define _(sym, string) string,
+  foreach_nat_external_input_error
+#undef _
+};
+
+typedef enum
+{
+#define _(s, n) NAT_EXTERNAL_INPUT_NEXT_##s,
+  foreach_nat_external_input_next
+#undef _
+    NAT_EXTERNAL_INPUT_N_NEXT
+} nat_external_input_next_t;
+
+static_always_inline uword
+nat_external_input_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
+                          vlib_frame_t *frame)
+{
+  /*
+   * use VNI as tenant ID
+   * tenant_id -> tenant index
+   * drop unknown tenants
+   * store tenant_id into opaque1
+   * advance current data to beginning of IP packet
+   */
+  sfdp_main_t *sfdp = &sfdp_main;
+  nat_main_t *nat = &nat_main;
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  vlib_combined_counter_main_t *cm =
+    &sfdp->tenant_data_ctr[SFDP_TENANT_DATA_COUNTER_INCOMING];
+
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *current_next;
+  uword thread_index = vlib_get_thread_index ();
+  vlib_get_buffers (vm, from, bufs, n_left);
+  b = bufs;
+  current_next = next_indices;
+
+  while (n_left)
+    {
+      u32 len = vlib_buffer_length_in_chain (vm, b[0]);
+      u32 rx_sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX];
+      u32 tenant_idx = nat->tenant_idx_by_sw_if_idx[rx_sw_if_index];
+      sfdp_tenant_t *tenant;
+      if (tenant_idx == NAT_INVALID_TENANT_IDX)
+       {
+         vnet_feature_next_u16 (current_next, b[0]);
+         goto end_of_packet;
+       }
+      tenant = sfdp_tenant_at_index (sfdp, tenant_idx);
+      b[0]->flow_id = tenant->context_id;
+      sfdp_buffer (b[0])->tenant_index = tenant_idx;
+      current_next[0] = NAT_EXTERNAL_INPUT_NEXT_LOOKUP;
+
+      vlib_increment_combined_counter (cm, thread_index, tenant_idx, 1, len);
+    end_of_packet:
+      b += 1;
+      current_next += 1;
+      n_left -= 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (nat_external_input_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return nat_external_input_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (nat_external_input_node) = {
+  .name = "nat-external-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_nat_external_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (nat_external_input_error_strings),
+  .error_strings = nat_external_input_error_strings,
+  .n_next_nodes = NAT_EXTERNAL_INPUT_N_NEXT,
+  .next_nodes = {
+          [NAT_EXTERNAL_INPUT_NEXT_LOOKUP] = "sfdp-lookup-ip4",
+  },
+};
+
+VNET_FEATURE_INIT (nat_external_input_feat, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "nat-external-input",
+};
diff --git a/src/plugins/sfdp_services/base/nat/fastpath_node.c b/src/plugins/sfdp_services/base/nat/fastpath_node.c
new file mode 100644 (file)
index 0000000..5beb147
--- /dev/null
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <sfdp_services/base/nat/nat.h>
+#include <vnet/sfdp/service.h>
+#include <vnet/sfdp/sfdp_funcs.h>
+
+#define foreach_sfdp_nat_fastpath_error _ (DROP, "drop")
+
+#define foreach_sfdp_nat_terminal_next                                        \
+  _ (DROP, "error-drop")                                                      \
+  _ (IP4_LOOKUP, "ip4-lookup")
+
+typedef enum
+{
+#define _(n, x) SFDP_NAT_TERMINAL_NEXT_##n,
+  foreach_sfdp_nat_terminal_next
+#undef _
+    SFDP_NAT_TERMINAL_N_NEXT
+} sfdp_nat_terminal_next_t;
+
+typedef enum
+{
+#define _(sym, str) SFDP_NAT_FASTPATH_ERROR_##sym,
+  foreach_sfdp_nat_fastpath_error
+#undef _
+    SFDP_NAT_FASTPATH_N_ERROR,
+} sfdp_nat_fastpath_error_t;
+
+static char *sfdp_nat_fastpath_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_nat_fastpath_error
+#undef _
+};
+
+typedef struct
+{
+  u32 thread_index;
+  u32 flow_id;
+} sfdp_nat_fastpath_trace_t;
+
+static u8 *
+format_sfdp_nat_fastpath_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sfdp_nat_fastpath_trace_t *t = va_arg (*args, sfdp_nat_fastpath_trace_t *);
+  nat_main_t *nm = &nat_main;
+  nat_rewrite_data_t *rewrite = vec_elt_at_index (nm->flows, t->flow_id);
+  s = format (
+    s, "sfdp-nat-fastpath: flow-id %u (session %u, %s) rewrite: %U\n",
+    t->flow_id, t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward",
+    format_sfdp_nat_rewrite, rewrite);
+
+  return s;
+}
+
+SFDP_SERVICE_DECLARE (drop)
+
+static_always_inline void
+nat_fastpath_process_one (nat_rewrite_data_t *nat_session,
+                         sfdp_session_t *session, u16 *to_next,
+                         vlib_buffer_t **b, u8 is_terminal)
+{
+  u8 *data = vlib_buffer_get_current (b[0]);
+  u8 proto = nat_session->rewrite.proto;
+  u32 ops;
+  ip4_header_t *ip4 = (void *) data;
+  ip_csum_t ip_sum = 0, tcp_sum = 0, udp_sum = 0, icmp_sum = 0;
+  tcp_header_t *tcp;
+  udp_header_t *udp;
+  icmp46_header_t *icmp;
+  u16 *icmp_id;
+
+  if (session->session_version != nat_session->version)
+    {
+      sfdp_buffer (b[0])->service_bitmap = SFDP_SERVICE_MASK (drop);
+      goto end_of_packet;
+    }
+
+  ops = nat_session->ops;
+
+  ip_sum = ip4->checksum;
+  ip_sum = ip_csum_sub_even (ip_sum, nat_session->l3_csum_delta);
+  ip_sum = ip_csum_fold (ip_sum);
+  ip4->checksum = ip_sum;
+
+  if (ops & NAT_REWRITE_OP_SADDR)
+    ip4->src_address = nat_session->rewrite.saddr;
+
+  if (ops & NAT_REWRITE_OP_DADDR)
+    ip4->dst_address = nat_session->rewrite.daddr;
+
+  if (proto == IP_PROTOCOL_TCP)
+    {
+      tcp = ip4_next_header (ip4);
+      tcp_sum = tcp->checksum;
+      tcp_sum = ip_csum_sub_even (tcp_sum, nat_session->l3_csum_delta);
+      tcp_sum = ip_csum_sub_even (tcp_sum, nat_session->l4_csum_delta);
+      tcp_sum = ip_csum_fold (tcp_sum);
+      tcp->checksum = tcp_sum;
+
+      if (ops & NAT_REWRITE_OP_SPORT)
+       tcp->src_port = nat_session->rewrite.sport;
+
+      if (ops & NAT_REWRITE_OP_DPORT)
+       tcp->dst_port = nat_session->rewrite.dport;
+    }
+  else if (proto == IP_PROTOCOL_UDP)
+    {
+      udp = ip4_next_header (ip4);
+      udp_sum = udp->checksum;
+      udp_sum = ip_csum_sub_even (udp_sum, nat_session->l3_csum_delta);
+      udp_sum = ip_csum_sub_even (udp_sum, nat_session->l4_csum_delta);
+      udp_sum = ip_csum_fold (udp_sum);
+      udp->checksum = udp_sum;
+
+      if (ops & NAT_REWRITE_OP_SPORT)
+       udp->src_port = nat_session->rewrite.sport;
+
+      if (ops & NAT_REWRITE_OP_DPORT)
+       udp->dst_port = nat_session->rewrite.dport;
+    }
+  else if (proto == IP_PROTOCOL_ICMP)
+    {
+      icmp = ip4_next_header (ip4);
+      icmp_sum = icmp->checksum;
+      icmp_id = (u16 *) (icmp + 1);
+      icmp_sum = ip_csum_sub_even (icmp_sum, nat_session->l4_csum_delta);
+      icmp_sum = ip_csum_fold (icmp_sum);
+      icmp->checksum = icmp_sum;
+      if (ops & NAT_REWRITE_OP_ICMP_ID)
+       *icmp_id = nat_session->rewrite.icmp_id;
+    }
+  else
+    {
+      /*FIXME, must be done at the beginning!*/
+      sfdp_buffer (b[0])->service_bitmap = SFDP_SERVICE_MASK (drop);
+      goto end_of_packet;
+    }
+
+  if (ops & NAT_REWRITE_OP_TXFIB)
+    vnet_buffer (b[0])->sw_if_index[VLIB_TX] = nat_session->rewrite.fib_index;
+
+  if (is_terminal)
+    {
+      to_next[0] = SFDP_NAT_TERMINAL_NEXT_IP4_LOOKUP;
+      return;
+    }
+
+end_of_packet:
+  sfdp_next (b[0], to_next);
+  return;
+}
+
+static_always_inline u16
+sfdp_nat_fastpath_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
+                         vlib_frame_t *frame, u8 is_terminal)
+{
+
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  nat_main_t *nat = &nat_main;
+  u32 thread_index = vlib_get_thread_index ();
+
+  sfdp_session_t *session;
+  u32 session_idx;
+  nat_rewrite_data_t *nat_rewrite;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+  while (n_left > 0)
+    {
+      session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+      session = sfdp_session_at_index (session_idx);
+      nat_rewrite = vec_elt_at_index (nat->flows, b[0]->flow_id);
+
+      nat_fastpath_process_one (nat_rewrite, session, to_next, b, is_terminal);
+      n_left -= 1;
+      b += 1;
+      to_next += 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      int i;
+      b = bufs;
+      n_left = frame->n_vectors;
+      for (i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sfdp_nat_fastpath_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             t->flow_id = b[0]->flow_id;
+             t->thread_index = thread_index;
+             b++;
+           }
+         else
+           break;
+       }
+    }
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (sfdp_nat_early_rewrite_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return sfdp_nat_fastpath_inline (vm, node, frame, 0);
+}
+
+VLIB_NODE_FN (sfdp_nat_late_rewrite_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return sfdp_nat_fastpath_inline (vm, node, frame, 1);
+}
+
+VLIB_REGISTER_NODE (sfdp_nat_early_rewrite_node) = {
+  .name = "sfdp-nat-early-rewrite",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_nat_fastpath_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_nat_fastpath_error_strings),
+  .error_strings = sfdp_nat_fastpath_error_strings
+};
+
+VLIB_REGISTER_NODE (sfdp_nat_late_rewrite_node) = {
+  .name = "sfdp-nat-late-rewrite",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_nat_fastpath_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_nat_fastpath_error_strings),
+  .error_strings = sfdp_nat_fastpath_error_strings,
+  .n_next_nodes = SFDP_NAT_TERMINAL_N_NEXT,
+  .next_nodes = {
+#define _(n, x) [SFDP_NAT_TERMINAL_NEXT_##n] = x,
+          foreach_sfdp_nat_terminal_next
+#undef _
+  }
+
+};
+
+SFDP_SERVICE_DEFINE (nat_late_rewrite) = {
+  .node_name = "sfdp-nat-late-rewrite",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle",
+                              "sfdp-tcp-check", "sfdp-nat-output"),
+  .is_terminal = 1
+};
+
+SFDP_SERVICE_DEFINE (nat_early_rewrite) = {
+  .node_name = "sfdp-nat-early-rewrite",
+  .runs_before = SFDP_SERVICES ("sfdp-geneve-output"),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle",
+                              "sfdp-tcp-check"),
+  .is_terminal = 0
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/nat/format.c b/src/plugins/sfdp_services/base/nat/format.c
new file mode 100644 (file)
index 0000000..b300866
--- /dev/null
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+#include <sfdp_services/base/nat/nat.h>
+
+static u8 *
+format_sfdp_nat_rewrite_SADDR (u8 *s, va_list *args)
+{
+  nat_rewrite_data_t *rewrite = va_arg (*args, nat_rewrite_data_t *);
+  s = format (s, "%U", format_ip4_address, &rewrite->rewrite.saddr);
+  return s;
+}
+
+static u8 *
+format_sfdp_nat_rewrite_SPORT (u8 *s, va_list *args)
+{
+  nat_rewrite_data_t *rewrite = va_arg (*args, nat_rewrite_data_t *);
+  s = format (s, "%u", clib_net_to_host_u16 (rewrite->rewrite.sport));
+  return s;
+}
+
+static u8 *
+format_sfdp_nat_rewrite_DADDR (u8 *s, va_list *args)
+{
+  nat_rewrite_data_t *rewrite = va_arg (*args, nat_rewrite_data_t *);
+  s = format (s, "%U", format_ip4_address, &rewrite->rewrite.daddr);
+  return s;
+}
+static u8 *
+format_sfdp_nat_rewrite_DPORT (u8 *s, va_list *args)
+{
+  nat_rewrite_data_t *rewrite = va_arg (*args, nat_rewrite_data_t *);
+  s = format (s, "%u", clib_net_to_host_u16 (rewrite->rewrite.dport));
+  return s;
+}
+static u8 *
+format_sfdp_nat_rewrite_ICMP_ID (u8 *s, va_list *args)
+{
+  nat_rewrite_data_t *rewrite = va_arg (*args, nat_rewrite_data_t *);
+  s = format (s, "%u", rewrite->rewrite.icmp_id);
+  return s;
+}
+static u8 *
+format_sfdp_nat_rewrite_TXFIB (u8 *s, va_list *args)
+{
+  nat_rewrite_data_t *rewrite = va_arg (*args, nat_rewrite_data_t *);
+  s = format (s, "fib-index %u", rewrite->rewrite.fib_index);
+  return s;
+}
+
+u8 *
+format_sfdp_nat_rewrite (u8 *s, va_list *args)
+{
+  nat_rewrite_data_t *rewrite = va_arg (*args, nat_rewrite_data_t *);
+#define _(sym, x, str)                                                        \
+  if (rewrite->ops & NAT_REWRITE_OP_##sym)                                    \
+    s = format (s, "rewrite %s (to %U),", str, format_sfdp_nat_rewrite_##sym, \
+               rewrite);
+  foreach_nat_rewrite_op
+#undef _
+    // if (s && s[vec_len (s) - 1] == ',') vec_resize (s, vec_len (s) - 1);
+    return s;
+}
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/nat/nat.api b/src/plugins/sfdp_services/base/nat/nat.api
new file mode 100644 (file)
index 0000000..41378c8
--- /dev/null
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+option version = "0.0.1";
+
+import "vnet/ip/ip_types.api";
+import "vnet/interface_types.api";
+
+autoreply define sfdp_nat_set_external_interface
+{
+  u32 client_index;
+  u32 context;
+
+  vl_api_interface_index_t sw_if_index;
+  u32 tenant_id;
+  u8 is_disable;
+};
+
+autoreply define sfdp_nat_alloc_pool_add_del
+{
+  u32 client_index;
+  u32 context;
+
+  u32 alloc_pool_id;
+  u8 is_del;
+  u32 n_addr;
+  vl_api_ip4_address_t addr[n_addr];
+};
+
+autoreply define sfdp_nat_snat_set_unset
+{
+  u32 client_index;
+  u32 context;
+
+  u32 tenant_id;
+  u32 outside_tenant_id;
+  u32 table_id;
+  u32 alloc_pool_id;
+  u8 is_disable;
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/nat/nat.c b/src/plugins/sfdp_services/base/nat/nat.c
new file mode 100644 (file)
index 0000000..7b0f9e3
--- /dev/null
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/sfdp/sfdp.h>
+#include <sfdp_services/base/nat/nat.h>
+#include <vppinfra/pool.h>
+
+clib_error_t *
+nat_external_interface_set_tenant (nat_main_t *nat, u32 sw_if_index,
+                                  u32 tenant_id, u8 unset)
+{
+  sfdp_main_t *sfdp = &sfdp_main;
+  clib_bihash_kv_8_8_t kv = { .key = tenant_id, .value = 0 };
+  vnet_main_t *vnm = vnet_get_main ();
+  u16 *config;
+
+  if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+    return clib_error_return (0, "Tenant with id %d not found");
+
+  vec_validate (nat->tenants, kv.value);
+  vec_validate (nat->tenant_idx_by_sw_if_idx, sw_if_index);
+  config = nat->tenant_idx_by_sw_if_idx + sw_if_index;
+
+  if (config[0] == NAT_INVALID_TENANT_IDX && unset)
+    return clib_error_return (
+      0, "Outside tenant %d is not configured on interface %U", tenant_id,
+      format_vnet_sw_if_index_name, vnm, sw_if_index);
+
+  if (config[0] != NAT_INVALID_TENANT_IDX && !unset)
+    return clib_error_return (0, "Interface %U is already configured",
+                             format_vnet_sw_if_index_name, vnm, sw_if_index);
+
+  if (!unset)
+    {
+      vnet_feature_enable_disable ("ip4-unicast", "nat-external-input",
+                                  sw_if_index, 1, 0, 0);
+      config[0] = kv.value;
+    }
+
+  else
+    {
+      vnet_feature_enable_disable ("ip4-unicast", "nat-external-input",
+                                  sw_if_index, 0, 0, 0);
+      config[0] = NAT_INVALID_TENANT_IDX;
+    }
+
+  return 0;
+}
+
+clib_error_t *
+nat_alloc_pool_add_del (nat_main_t *nat, u32 alloc_pool_id, u8 is_del,
+                       ip4_address_t *addr)
+{
+  u16 alloc_pool_idx;
+  uword *val = hash_get (nat->alloc_pool_idx_by_id, alloc_pool_id);
+
+  if (!val && is_del)
+    return clib_error_return (0, "Allocation pool %d does not exist",
+                             alloc_pool_id);
+  if (val && !is_del)
+    return clib_error_return (0, "Existing allocation pool with id %d",
+                             alloc_pool_id);
+
+  if (is_del)
+    {
+      pool_put_index (nat->alloc_pool, val[0]);
+      hash_unset (nat->alloc_pool_idx_by_id, alloc_pool_id);
+    }
+  else
+    {
+      nat_alloc_pool_t *alloc_pool;
+      uword num = vec_len (addr);
+      uword num_static = clib_min (num, NAT_ALLOC_POOL_ARRAY_SZ);
+      pool_get_zero (nat->alloc_pool, alloc_pool);
+      alloc_pool->num = num;
+      alloc_pool_idx = alloc_pool - nat->alloc_pool;
+      num -= num_static;
+      for (int i = 0; i < num_static; i++)
+       alloc_pool->addr[i] = addr[i];
+      if (num > 0)
+       {
+         addr += num_static;
+         vec_validate (alloc_pool->remaining, num);
+         for (int i = 0; i < num; i++)
+           alloc_pool->remaining[i] = addr[i];
+       }
+      hash_set (nat->alloc_pool_idx_by_id, alloc_pool_id, alloc_pool_idx);
+    }
+  return 0;
+}
+
+clib_error_t *
+nat_tenant_set_snat (nat_main_t *nat, u32 tenant_id, u32 outside_tenant_id,
+                    u32 table_id, u32 alloc_pool_id, u8 unset)
+{
+  ip4_main_t *im = &ip4_main;
+  uword *fib_index = hash_get (im->fib_index_by_table_id, table_id);
+  uword *out_alloc_pool_idx =
+    hash_get (nat->alloc_pool_idx_by_id, alloc_pool_id);
+  clib_bihash_kv_8_8_t kv = { .key = tenant_id, .value = 0 };
+  sfdp_main_t *sfdp = &sfdp_main;
+  nat_tenant_t *tenant;
+  sfdp_tenant_t *outside_tenant;
+  uword tenant_idx;
+  uword outside_tenant_idx;
+
+  if (!unset && !fib_index)
+    return clib_error_return (0, "Unknown table %d", table_id);
+
+  if (!unset && !out_alloc_pool_idx)
+    return clib_error_return (0, "Unknown allocation pool %d", alloc_pool_id);
+
+  if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+    return clib_error_return (0, "Unknown tenant %d", tenant_id);
+
+  tenant_idx = kv.value;
+  kv.key = outside_tenant_id;
+  kv.value = 0;
+  if (!unset && clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+    return clib_error_return (0, "Unknown tenant %d", tenant_id);
+  else
+    outside_tenant_idx = kv.value;
+
+  vec_validate (nat->tenants, tenant_idx);
+  tenant = vec_elt_at_index (nat->tenants, tenant_idx);
+
+  if (unset && !(tenant->flags & NAT_TENANT_FLAG_SNAT))
+    return clib_error_return (0, "SNAT is not set on tenant %d", tenant_id);
+
+  if (!unset && (tenant->flags & NAT_TENANT_FLAG_SNAT))
+    return clib_error_return (0, "SNAT is already set on tenant %d",
+                             tenant_id);
+
+  if (unset)
+    {
+      tenant->flags &= ~NAT_TENANT_FLAG_SNAT;
+      tenant->fib_index = ~0;
+      tenant->out_alloc_pool_idx = ~0;
+      tenant->reverse_context = ~0;
+    }
+  else
+    {
+      outside_tenant = sfdp_tenant_at_index (sfdp, outside_tenant_idx);
+      tenant->flags |= NAT_TENANT_FLAG_SNAT;
+      tenant->fib_index = fib_index[0];
+      tenant->out_alloc_pool_idx = out_alloc_pool_idx[0];
+      tenant->reverse_context = outside_tenant->context_id;
+    }
+  return 0;
+}
+
+static clib_error_t *
+nat_init (vlib_main_t *vm)
+{
+  nat_main_t *nat = &nat_main;
+  sfdp_main_t *sfdp = &sfdp_main;
+
+  nat->alloc_pool_idx_by_id = hash_create (0, sizeof (uword));
+  vec_validate (nat->flows, (2ULL << sfdp->log2_sessions) - 1);
+
+  return 0;
+}
+VLIB_INIT_FUNCTION (nat_init);
+
+clib_error_t *
+nat_add_del_sw_interface (vnet_main_t *vnm, u32 sw_if_index, u32 is_add)
+{
+  nat_main_t *nat = &nat_main;
+  uword old_size = vec_len (nat->tenant_idx_by_sw_if_idx);
+  if (sw_if_index >= old_size)
+    {
+      vec_validate (nat->tenant_idx_by_sw_if_idx, sw_if_index);
+      for (int i = old_size; i <= sw_if_index; i++)
+       nat->tenant_idx_by_sw_if_idx[i] = NAT_INVALID_TENANT_IDX;
+    }
+  return 0;
+}
+
+VNET_SW_INTERFACE_ADD_DEL_FUNCTION (nat_add_del_sw_interface);
+nat_main_t nat_main;
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/nat/nat.h b/src/plugins/sfdp_services/base/nat/nat.h
new file mode 100644 (file)
index 0000000..2084750
--- /dev/null
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef __included_nat_h__
+#define __included_nat_h__
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+#include <vnet/ip/ip46_address.h>
+
+#define NAT_INVALID_TENANT_IDX (u16) (~0)
+#define NAT_ALLOC_POOL_ARRAY_SZ 13
+
+#define foreach_nat_tenant_flag _ (SNAT, 0x1, "snat")
+
+enum
+{
+#define _(name, x, str) NAT_TENANT_FLAG_##name = (x),
+  foreach_nat_tenant_flag
+#undef _
+    NAT_TENANT_N_FLAGS
+};
+
+typedef struct
+{
+  u16 flags;
+  u32 reverse_context;
+  uword out_alloc_pool_idx;
+  uword fib_index;
+} nat_tenant_t;
+
+#define foreach_nat_rewrite_op                                                \
+  _ (SADDR, 0x1, "src-addr")                                                  \
+  _ (SPORT, 0x2, "src-port")                                                  \
+  _ (DADDR, 0x4, "dst-addr")                                                  \
+  _ (DPORT, 0x8, "dst-port")                                                  \
+  _ (ICMP_ID, 0x10, "icmp-id")                                                \
+  _ (TXFIB, 0x20, "tx-fib")
+
+typedef enum
+{
+#define _(sym, x, s) NAT_REWRITE_OP_##sym = x,
+  foreach_nat_rewrite_op
+#undef _
+} nat_rewrite_op_t;
+typedef struct
+{
+  CLIB_CACHE_LINE_ALIGN_MARK (cache0);
+  struct
+  {
+    ip4_address_t saddr, daddr;
+    u16 sport;
+    u16 dport;
+    u32 fib_index;
+    u16 icmp_id;
+    u8 proto;
+  } rewrite;
+  u32 ops; /* see nat_rewrite_op_t */
+  uword l3_csum_delta;
+  uword l4_csum_delta;
+  session_version_t version;
+} nat_rewrite_data_t;
+STATIC_ASSERT_SIZEOF (nat_rewrite_data_t, CLIB_CACHE_LINE_BYTES);
+
+typedef struct
+{
+  CLIB_CACHE_LINE_ALIGN_MARK (cache0);
+  u16 flags;
+  u16 num;
+  ip4_address_t addr[NAT_ALLOC_POOL_ARRAY_SZ];
+  ip4_address_t *remaining;
+} nat_alloc_pool_t;
+STATIC_ASSERT_SIZEOF (nat_alloc_pool_t, CLIB_CACHE_LINE_BYTES);
+
+typedef struct
+{
+  u16 *tenant_idx_by_sw_if_idx; /* vec */
+  nat_tenant_t *tenants;       /* vec */
+  nat_alloc_pool_t *alloc_pool; /* pool of allocation pools */
+  nat_rewrite_data_t *flows;   /* by flow_index */
+  uword *alloc_pool_idx_by_id; /* hash */
+  u16 msg_id_base;
+} nat_main_t;
+
+extern nat_main_t nat_main;
+
+clib_error_t *nat_external_interface_set_tenant (nat_main_t *nat,
+                                                u32 sw_if_index,
+                                                u32 tenant_id, u8 unset);
+
+clib_error_t *nat_alloc_pool_add_del (nat_main_t *nat, u32 alloc_pool_id,
+                                     u8 is_del, ip4_address_t *addr);
+
+clib_error_t *nat_tenant_set_snat (nat_main_t *nat, u32 tenant_id,
+                                  u32 outside_tenant_id, u32 table_id,
+                                  u32 alloc_pool_id, u8 unset);
+format_function_t format_sfdp_nat_rewrite;
+#endif
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/nat/slowpath_node.c b/src/plugins/sfdp_services/base/nat/slowpath_node.c
new file mode 100644 (file)
index 0000000..952051d
--- /dev/null
@@ -0,0 +1,289 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <sfdp_services/base/nat/nat.h>
+#include <vnet/sfdp/service.h>
+#include <vnet/sfdp/sfdp_funcs.h>
+#define foreach_sfdp_nat_slowpath_error _ (DROP, "drop")
+
+typedef enum
+{
+#define _(sym, str) SFDP_NAT_SLOWPATH_ERROR_##sym,
+  foreach_sfdp_nat_slowpath_error
+#undef _
+    SFDP_NAT_SLOWPATH_N_ERROR,
+} sfdp_nat_slowpath_error_t;
+
+static char *sfdp_nat_slowpath_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_nat_slowpath_error
+#undef _
+};
+
+typedef struct
+{
+  u32 flow_id;
+  u32 thread_index;
+} sfdp_nat_slowpath_trace_t;
+
+format_function_t format_sfdp_bitmap;
+
+SFDP_SERVICE_DECLARE (drop)
+static u8 *
+format_sfdp_nat_slowpath_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sfdp_nat_slowpath_trace_t *t = va_arg (*args, sfdp_nat_slowpath_trace_t *);
+  /* FIXME: This is a scam, the session-idx can be invalid at format time!*/
+  sfdp_session_t *session = sfdp_session_at_index (t->flow_id >> 1);
+  u32 scope_index = session->scope_index;
+
+  s = format (s, "sfdp-nat-output: flow-id %u (session %u, %s)\n", t->flow_id,
+             t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward");
+  s = format (s, "  new forward service chain: %U\n", format_sfdp_bitmap,
+             scope_index, session->bitmaps[SFDP_FLOW_FORWARD]);
+  s = format (s, "  new reverse service chain: %U\n", format_sfdp_bitmap,
+             scope_index, session->bitmaps[SFDP_FLOW_REVERSE]);
+
+  return s;
+}
+SFDP_SERVICE_DECLARE (nat_late_rewrite)
+SFDP_SERVICE_DECLARE (nat_early_rewrite)
+SFDP_SERVICE_DECLARE (nat_output)
+static_always_inline void
+nat_slow_path_process_one (sfdp_main_t *sfdp, u32 *fib_index_by_sw_if_index,
+                          u16 thread_index, nat_main_t *nm,
+                          nat_tenant_t *tenant, u32 session_index,
+                          nat_rewrite_data_t *nat_session,
+                          sfdp_session_t *session, u16 *to_next,
+                          vlib_buffer_t **b)
+{
+  uword l3_sum_delta_forward = 0;
+  uword l4_sum_delta_forward = 0;
+  uword l3_sum_delta_reverse = 0;
+  uword l4_sum_delta_reverse = 0;
+  sfdp_session_ip46_key_t new_key46 = { 0 };
+  sfdp_session_ip4_key_t *new_key = &new_key46.key4;
+  new_key46.key4 = session->keys[SFDP_SESSION_KEY_PRIMARY].key4;
+  u8 pseudo_dir = session->pseudo_dir[SFDP_SESSION_KEY_PRIMARY];
+  u8 proto = session->proto;
+  u8 n_retries = 0;
+  u32 *ip4_key_src_addr =
+    pseudo_dir ? &new_key->ip4_key.ip_addr_hi : &new_key->ip4_key.ip_addr_lo;
+  u32 ip4_old_src_addr;
+  u32 ip4_new_src_addr;
+  u16 *ip4_key_src_port;
+  u16 *ip4_key_dst_port;
+  u16 ip4_old_port;
+  u16 ip4_new_port;
+
+  nat_alloc_pool_t *pool;
+  u32 src_addr_index;
+  u64 h;
+  u32 pseudo_flow_index;
+  u32 old_fib_index;
+
+  if (PREDICT_FALSE (!(tenant->flags & NAT_TENANT_FLAG_SNAT)))
+    {
+      sfdp_buffer (b[0])->service_bitmap = SFDP_SERVICE_MASK (drop);
+      goto end_of_packet;
+    }
+
+  pool = pool_elt_at_index (nm->alloc_pool, tenant->out_alloc_pool_idx);
+
+  if (PREDICT_FALSE (session->session_version == nat_session->version))
+    {
+      /* NAT State is already created, certainly a packet in flight. Refresh
+       * bitmap */
+      sfdp_buffer (b[0])->service_bitmap =
+       session->bitmaps[b[0]->flow_id & 0x1];
+      goto end_of_packet;
+    }
+
+  /* TODO: handle case with many addresses in pool (slowpath) */
+  if (PREDICT_FALSE (pool->num > NAT_ALLOC_POOL_ARRAY_SZ))
+    ASSERT (0);
+
+  new_key->context_id = tenant->reverse_context;
+
+  /* Allocate a new source */
+  ip4_old_src_addr = *ip4_key_src_addr;
+  src_addr_index = ip4_old_src_addr % pool->num;
+  ip4_new_src_addr = pool->addr[src_addr_index].as_u32;
+  *ip4_key_src_addr = ip4_new_src_addr;
+  pseudo_dir = sfdp_renormalise_ip4_key (new_key, pseudo_dir);
+  pseudo_flow_index = (session_index << 1) | (pseudo_dir & 0x1);
+  /* Allocate a new port */
+  ip4_key_src_port =
+    pseudo_dir ? &new_key->ip4_key.port_hi : &new_key->ip4_key.port_lo;
+  ip4_key_dst_port =
+    pseudo_dir ? &new_key->ip4_key.port_lo : &new_key->ip4_key.port_hi;
+  ip4_old_port = *ip4_key_src_port;
+
+  /* First try with original src port */
+  ip4_new_port = ip4_old_port;
+  while ((++n_retries) < 5 && sfdp_session_try_add_secondary_key (
+                               sfdp, thread_index, pseudo_flow_index,
+                               &new_key46, IP46_TYPE_IP4, &h))
+    {
+      /* Use h to try a different port */
+      u32 h2 = h;
+      u64 reduced = h2;
+      reduced *= 64512ULL;
+      reduced >>= 32;
+      ip4_new_port = clib_host_to_net_u16 (1024 + reduced);
+      *ip4_key_src_port = ip4_new_port;
+      if (PREDICT_FALSE (proto == IP_PROTOCOL_ICMP))
+       *ip4_key_dst_port = ip4_new_port;
+    }
+
+  if (n_retries == 5)
+    {
+      /* Port allocation failure */
+      /* TODO: do the sensible thing, drop the packet + increase counters */
+      sfdp_buffer (b[0])->service_bitmap = SFDP_SERVICE_MASK (drop);
+      goto end_of_packet;
+    }
+
+  /* Build the rewrites in both directions */
+  l3_sum_delta_forward =
+    ip_csum_add_even (l3_sum_delta_forward, ip4_new_src_addr);
+  l3_sum_delta_forward =
+    ip_csum_sub_even (l3_sum_delta_forward, ip4_old_src_addr);
+
+  l4_sum_delta_forward = ip_csum_add_even (l4_sum_delta_forward, ip4_new_port);
+  l4_sum_delta_forward = ip_csum_sub_even (l4_sum_delta_forward, ip4_old_port);
+
+  l3_sum_delta_reverse =
+    ip_csum_add_even (l3_sum_delta_reverse, ip4_old_src_addr);
+  l3_sum_delta_reverse =
+    ip_csum_sub_even (l3_sum_delta_reverse, ip4_new_src_addr);
+
+  l4_sum_delta_reverse = ip_csum_add_even (l4_sum_delta_reverse, ip4_old_port);
+  l4_sum_delta_reverse = ip_csum_sub_even (l4_sum_delta_reverse, ip4_new_port);
+
+  old_fib_index = vec_elt (fib_index_by_sw_if_index,
+                          vnet_buffer (b[0])->sw_if_index[VLIB_RX]);
+  nat_session[0].version = session->session_version;
+  nat_session[1].version = session->session_version;
+
+  if (PREDICT_TRUE (proto != IP_PROTOCOL_ICMP))
+    {
+      nat_session[0].ops =
+       NAT_REWRITE_OP_SADDR | NAT_REWRITE_OP_SPORT | NAT_REWRITE_OP_TXFIB;
+      nat_session[0].rewrite.sport = ip4_new_port;
+      nat_session[1].ops =
+       NAT_REWRITE_OP_DADDR | NAT_REWRITE_OP_DPORT | NAT_REWRITE_OP_TXFIB;
+      nat_session[1].rewrite.dport = ip4_old_port;
+    }
+  else
+    {
+      nat_session[0].ops =
+       NAT_REWRITE_OP_SADDR | NAT_REWRITE_OP_ICMP_ID | NAT_REWRITE_OP_TXFIB;
+      nat_session[0].rewrite.icmp_id = ip4_new_port;
+      nat_session[1].ops =
+       NAT_REWRITE_OP_DADDR | NAT_REWRITE_OP_ICMP_ID | NAT_REWRITE_OP_TXFIB;
+      nat_session[1].rewrite.icmp_id = ip4_old_port;
+    }
+
+  nat_session[0].rewrite.saddr.as_u32 = ip4_new_src_addr;
+  nat_session[0].rewrite.fib_index = tenant->fib_index;
+  nat_session[0].rewrite.proto = proto;
+  nat_session[0].l3_csum_delta = l3_sum_delta_forward;
+  nat_session[0].l4_csum_delta = l4_sum_delta_forward;
+
+  nat_session[1].rewrite.daddr.as_u32 = ip4_old_src_addr;
+  nat_session[1].rewrite.fib_index = old_fib_index;
+  nat_session[1].rewrite.proto = proto;
+  nat_session[1].l3_csum_delta = l3_sum_delta_reverse;
+  nat_session[1].l4_csum_delta = l4_sum_delta_reverse;
+
+  sfdp_buffer (b[0])->service_bitmap |= SFDP_SERVICE_MASK (nat_late_rewrite);
+  session->bitmaps[SFDP_FLOW_FORWARD] &= ~SFDP_SERVICE_MASK (nat_output);
+  session->bitmaps[SFDP_FLOW_REVERSE] &= ~SFDP_SERVICE_MASK (nat_output);
+  session->bitmaps[SFDP_FLOW_FORWARD] |= SFDP_SERVICE_MASK (nat_late_rewrite);
+  session->bitmaps[SFDP_FLOW_REVERSE] |= SFDP_SERVICE_MASK (nat_early_rewrite);
+
+end_of_packet:
+  sfdp_next (b[0], to_next);
+  return;
+}
+
+VLIB_NODE_FN (sfdp_nat_slowpath_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  sfdp_main_t *sfdp = &sfdp_main;
+  ip4_main_t *im = &ip4_main;
+  nat_main_t *nat = &nat_main;
+  u32 thread_index = vlib_get_thread_index ();
+  sfdp_session_t *session;
+  nat_tenant_t *tenant;
+  u32 session_idx;
+  u32 tenant_idx;
+  nat_rewrite_data_t *nat_rewrites; /* rewrite data in both directions */
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+  while (n_left > 0)
+    {
+      session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+      session = sfdp_session_at_index (session_idx);
+      tenant_idx = sfdp_buffer (b[0])->tenant_index;
+      nat_rewrites = vec_elt_at_index (nat->flows, session_idx << 1);
+      tenant = vec_elt_at_index (nat->tenants, tenant_idx);
+
+      // nat_slow_path_process_one (tenant, nat_rewrites, session, to_next, b);
+      nat_slow_path_process_one (sfdp, im->fib_index_by_sw_if_index,
+                                thread_index, nat, tenant, session_idx,
+                                nat_rewrites, session, to_next, b);
+      n_left -= 1;
+      b += 1;
+      to_next += 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      int i;
+      b = bufs;
+      n_left = frame->n_vectors;
+      for (i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sfdp_nat_slowpath_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             t->flow_id = b[0]->flow_id;
+             t->thread_index = thread_index;
+             b++;
+           }
+         else
+           break;
+       }
+    }
+  return frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (sfdp_nat_slowpath_node) = {
+  .name = "sfdp-nat-output",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_nat_slowpath_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_nat_slowpath_error_strings),
+  .error_strings = sfdp_nat_slowpath_error_strings
+};
+
+SFDP_SERVICE_DEFINE (nat_output) = {
+  .node_name = "sfdp-nat-output",
+  .runs_before = SFDP_SERVICES ("sfdp-geneve-output", "sfdp-nat-late-rewrite"),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle",
+                              "sfdp-tcp-check"),
+  .is_terminal = 0
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/sample/node.c b/src/plugins/sfdp_services/base/sample/node.c
new file mode 100644 (file)
index 0000000..5cd28f7
--- /dev/null
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+#include <vnet/sfdp/service.h>
+#define foreach_sample_terminal_error _ (DROP, "drop")
+
+typedef enum
+{
+#define _(sym, str) SAMPLE_TERMINAL_ERROR_##sym,
+  foreach_sample_terminal_error
+#undef _
+    SAMPLE_TERMINAL_N_ERROR,
+} sample_terminal_error_t;
+
+static char *sample_terminal_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sample_terminal_error
+#undef _
+};
+
+#define foreach_sample_terminal_next _ (DROP, "error-drop")
+
+typedef enum
+{
+#define _(n, x) SAMPLE_TERMINAL_NEXT_##n,
+  foreach_sample_terminal_next
+#undef _
+    SAMPLE_TERMINAL_N_NEXT
+} sample_terminal_next_t;
+
+typedef struct
+{
+  u32 flow_id;
+} sample_terminal_trace_t;
+
+static u8 *
+format_sample_terminal_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sample_terminal_trace_t *t = va_arg (*args, sample_terminal_trace_t *);
+
+  s =
+    format (s, "sample-terminal-drop: flow-id %u (session %u, %s)", t->flow_id,
+           t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward");
+  return s;
+}
+
+VLIB_NODE_FN (sample_terminal_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+
+  vlib_buffer_enqueue_to_single_next (vm, node, from,
+                                     SAMPLE_TERMINAL_NEXT_DROP, n_left);
+  vlib_node_increment_counter (vm, node->node_index,
+                              SAMPLE_TERMINAL_ERROR_DROP, n_left);
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      int i;
+      vlib_get_buffers (vm, from, bufs, n_left);
+      b = bufs;
+      for (i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sample_terminal_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             t->flow_id = b[0]->flow_id;
+             b++;
+           }
+         else
+           break;
+       }
+    }
+  return frame->n_vectors;
+}
+
+#define foreach_sample_non_terminal_error _ (DROP, "drop")
+
+typedef enum
+{
+#define _(sym, str) SAMPLE_NON_TERMINAL_ERROR_##sym,
+  foreach_sample_non_terminal_error
+#undef _
+    SAMPLE_NON_TERMINAL_N_ERROR,
+} sample_non_terminal_error_t;
+
+static char *sample_non_terminal_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sample_non_terminal_error
+#undef _
+};
+
+typedef struct
+{
+  u32 flow_id;
+  u8 new_state;
+} sample_non_terminal_trace_t;
+
+static u8 *
+format_sample_non_terminal_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sample_non_terminal_trace_t *t =
+    va_arg (*args, sample_non_terminal_trace_t *);
+
+  s = format (
+    s, "sample-non-terminal: flow-id %u (session %u, %s) new_state: %U",
+    t->flow_id, t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward",
+    format_sfdp_session_state, t->new_state);
+  return s;
+}
+
+VLIB_NODE_FN (sample_non_terminal_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  sfdp_main_t *sfdp = &sfdp_main;
+
+  u32 thread_index = vm->thread_index;
+  sfdp_per_thread_data_t *ptd =
+    vec_elt_at_index (sfdp->per_thread_data, thread_index);
+
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+
+  while (n_left)
+    {
+      u32 session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+      u16 tenant_idx = sfdp_buffer (b[0])->tenant_index;
+      sfdp_session_t *session = sfdp_session_at_index (session_idx);
+      sfdp_tenant_t *tenant = sfdp_tenant_at_index (sfdp, tenant_idx);
+      CLIB_UNUSED (u8 direction) =
+       sfdp_direction_from_flow_index (b[0]->flow_id);
+      /* Set the state of the session to established */
+      session->state = SFDP_SESSION_STATE_ESTABLISHED;
+      /* Rearm the session timeout to
+       * tenant->timeouts[SFDP_TIMEOUT_ESTABLISHED] from now */
+      sfdp_session_timer_update (&ptd->wheel, &session->timer,
+                                ptd->current_time,
+                                tenant->timeouts[SFDP_TIMEOUT_ESTABLISHED]);
+      /* Next service in chain for this packet */
+      sfdp_next (b[0], to_next);
+
+      b++;
+      to_next++;
+      n_left--;
+    }
+
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      n_left = frame->n_vectors;
+      b = bufs;
+      for (int i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sample_non_terminal_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             u32 session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+             sfdp_session_t *session =
+               sfdp_session_at_index (ptd, session_idx);
+             u16 state = session->state;
+             t->flow_id = b[0]->flow_id;
+             t->new_state = state;
+             b++;
+           }
+         else
+           break;
+       }
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  return frame->n_vectors;
+}
+
+/* This service is a terminal service, i.e., its next nodes are outside
+   of sfdp (here for example, error-drop) */
+
+VLIB_REGISTER_NODE (sample_terminal_node) = {
+  .name = "sample-terminal",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sample_terminal_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sample_terminal_error_strings),
+  .error_strings = sample_terminal_error_strings,
+
+  .n_next_nodes = SAMPLE_TERMINAL_N_NEXT,
+  .next_nodes = {
+#define _(n, x) [SAMPLE_TERMINAL_NEXT_##n] = x,
+          foreach_sample_terminal_next
+#undef _
+  }
+
+};
+
+SFDP_SERVICE_DEFINE (sample) = {
+  .node_name = "sample-terminal",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES (0),
+  .is_terminal = 1,
+};
+
+/* This service is a nonterminal service, i.e., next nodes cannot be specified
+ */
+
+VLIB_REGISTER_NODE (sample_non_terminal_node) = {
+  .name = "sample-non-terminal",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sample_non_terminal_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sample_non_terminal_error_strings),
+  .error_strings = sample_non_terminal_error_strings,
+
+};
+
+SFDP_SERVICE_DEFINE (sample_non_terminal) = {
+  .node_name = "sample-non-terminal",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES ("sfdp-drop"),
+  .is_terminal = 0
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/tcp-check/api.c b/src/plugins/sfdp_services/base/tcp-check/api.c
new file mode 100644 (file)
index 0000000..c4cd9a8
--- /dev/null
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/sfdp/sfdp.h>
+#include <sfdp_services/base/tcp-check/tcp_check.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vnet/ip/ip_types_api.h>
+#include <vnet/format_fns.h>
+#include <sfdp_services/base/tcp-check/tcp_check.api_enum.h>
+#include <sfdp_services/base/tcp-check/tcp_check.api_types.h>
+#include <vlibapi/api_helper_macros.h>
+
+#include <vnet/sfdp/sfdp_types_funcs.h>
+
+static u32
+sfdp_tcp_check_session_flags_encode (u32 x)
+{
+  return clib_host_to_net_u32 (x);
+};
+
+static void
+sfdp_tcp_send_session_details (vl_api_registration_t *rp, u32 context,
+                              u32 session_index, u32 thread_index,
+                              sfdp_session_t *session,
+                              sfdp_tcp_check_session_state_t *tcp_session)
+{
+  sfdp_main_t *sfdp = &sfdp_main;
+  sfdp_tcp_check_main_t *tcp = &sfdp_tcp;
+  vl_api_sfdp_tcp_session_details_t *mp;
+  sfdp_session_ip46_key_t skey;
+  sfdp_tenant_t *tenant;
+  u32 tenant_id;
+  size_t msg_size;
+  u8 n_keys = sfdp_session_n_keys (session);
+  tenant = sfdp_tenant_at_index (sfdp, session->tenant_idx);
+  tenant_id = tenant->tenant_id;
+  msg_size = sizeof (*mp) + sizeof (mp->keys[0]) * n_keys;
+
+  mp = vl_msg_api_alloc_zero (msg_size);
+  mp->_vl_msg_id = ntohs (VL_API_SFDP_TCP_SESSION_DETAILS + tcp->msg_id_base);
+
+  /* fill in the message */
+  mp->context = context;
+  mp->session_id = clib_host_to_net_u64 (session->session_id);
+  mp->thread_index = clib_host_to_net_u32 (thread_index);
+  mp->tenant_id = clib_host_to_net_u32 (tenant_id);
+  mp->session_idx = clib_host_to_net_u32 (session_index);
+  mp->session_type = sfdp_session_type_encode (session->type);
+  mp->flags = sfdp_tcp_check_session_flags_encode (tcp_session->flags);
+  mp->n_keys = n_keys;
+  for (int i = 0; i < n_keys; i++)
+    {
+      if ((i == 0 &&
+          session->key_flags & SFDP_SESSION_KEY_FLAG_PRIMARY_VALID_IP4) ||
+         (i == 1 &&
+          session->key_flags & SFDP_SESSION_KEY_FLAG_SECONDARY_VALID_IP4))
+       {
+         sfdp_normalise_ip4_key (session, &skey.key4, i);
+         sfdp_session_ip46_key_encode (&skey, IP46_TYPE_IP4, &mp->keys[i]);
+       }
+      if ((i == 0 &&
+          session->key_flags & SFDP_SESSION_KEY_FLAG_PRIMARY_VALID_IP6) ||
+         (i == 1 &&
+          session->key_flags & SFDP_SESSION_KEY_FLAG_SECONDARY_VALID_IP6))
+       {
+         sfdp_normalise_ip6_key (session, &skey.key6, i);
+         sfdp_session_ip46_key_encode (&skey, IP46_TYPE_IP6, &mp->keys[i]);
+       }
+    }
+  vl_api_send_msg (rp, (u8 *) mp);
+}
+
+static void
+vl_api_sfdp_tcp_session_dump_t_handler (vl_api_sfdp_tcp_session_dump_t *mp)
+{
+  sfdp_main_t *sfdp = &sfdp_main;
+  sfdp_tcp_check_main_t *tcp = &sfdp_tcp;
+  sfdp_session_t *session;
+  sfdp_tcp_check_session_state_t *tcp_session;
+  uword session_index;
+  vl_api_registration_t *rp;
+  rp = vl_api_client_index_to_registration (mp->client_index);
+  if (rp == 0)
+    return;
+
+  sfdp_foreach_session (sfdp, session_index, session)
+  {
+    if (session->proto != IP_PROTOCOL_TCP)
+      continue;
+    tcp_session = vec_elt_at_index (tcp->state, session_index);
+    sfdp_tcp_send_session_details (rp, mp->context, session_index,
+                                  session->owning_thread_index, session,
+                                  tcp_session);
+  }
+}
+#include <sfdp_services/base/tcp-check/tcp_check.api.c>
+static clib_error_t *
+sfdp_tcp_check_plugin_api_hookup (vlib_main_t *vm)
+{
+  sfdp_tcp_check_main_t *tcp = &sfdp_tcp;
+  tcp->msg_id_base = setup_message_id_table ();
+  return 0;
+}
+VLIB_API_INIT_FUNCTION (sfdp_tcp_check_plugin_api_hookup);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/plugins/sfdp_services/base/tcp-check/cli.c b/src/plugins/sfdp_services/base/tcp-check/cli.c
new file mode 100644 (file)
index 0000000..aafe056
--- /dev/null
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+#include <sfdp_services/base/tcp-check/tcp_check.h>
+
+static clib_error_t *
+sfdp_tcp_check_show_sessions_command_fn (vlib_main_t *vm,
+                                        unformat_input_t *input,
+                                        vlib_cli_command_t *cmd)
+{
+  unformat_input_t line_input_, *line_input = &line_input_;
+  clib_error_t *err = 0;
+  sfdp_main_t *sfdp = &sfdp_main;
+  sfdp_tcp_check_main_t *vtcm = &sfdp_tcp;
+  sfdp_session_t *session;
+  sfdp_tcp_check_session_state_t *tcp_session;
+  sfdp_tenant_t *tenant;
+  u32 session_index;
+  u32 tenant_id = ~0;
+
+  if (unformat_user (input, unformat_line_input, line_input))
+    {
+      while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+       {
+         if (unformat (line_input, "tenant %d", &tenant_id))
+           ;
+         else
+           {
+             err = unformat_parse_error (line_input);
+             break;
+           }
+       }
+      unformat_free (line_input);
+    }
+
+  if (!err)
+    {
+      table_t session_table_ = {}, *session_table = &session_table_;
+      u32 n = 0;
+      table_add_header_col (session_table, 8, "id", "tenant", "index", "type",
+                           "context", "ingress", "egress", "flags");
+      sfdp_foreach_session (sfdp, session_index, session)
+      {
+       tenant = sfdp_tenant_at_index (sfdp, session->tenant_idx);
+       if (tenant_id != ~0 && tenant_id != tenant->tenant_id)
+         continue;
+       if (session->proto != IP_PROTOCOL_TCP)
+         continue;
+       tcp_session = vec_elt_at_index (vtcm->state, session_index);
+       n = sfdp_table_format_insert_tcp_check_session (
+         session_table, n, sfdp, session_index, session, tcp_session);
+      }
+      vlib_cli_output (vm, "%U", format_table, session_table);
+      table_free (session_table);
+    }
+
+  return err;
+}
+
+VLIB_CLI_COMMAND (show_sfdp_tcp_check_sessions_command, static) = {
+  .path = "show sfdp tcp session-table",
+  .short_help = "show sfdp tcp session-table [tenant <tenant-id>]",
+  .function = sfdp_tcp_check_show_sessions_command_fn,
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/tcp-check/format.c b/src/plugins/sfdp_services/base/tcp-check/format.c
new file mode 100644 (file)
index 0000000..f87a8f9
--- /dev/null
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+#include <vnet/sfdp/lookup/parser.h>
+#include <sfdp_services/base/tcp-check/tcp_check.h>
+
+u8 *
+format_sfdp_tcp_check_session_flags (u8 *s, va_list *args)
+{
+  u32 flags = va_arg (*args, u32);
+#define _(name, x, str)                                                       \
+  if (flags & SFDP_TCP_CHECK_SESSION_FLAG_##name)                             \
+    s = format (s, "%s", (str));
+  foreach_sfdp_tcp_check_session_flag
+#undef _
+
+    return s;
+}
+
+u32
+sfdp_table_format_insert_tcp_check_session (
+  table_t *t, u32 n, sfdp_main_t *sfdp, u32 session_index,
+  sfdp_session_t *session, sfdp_tcp_check_session_state_t *tcp_session)
+{
+  sfdp_parser_main_t *pm = &sfdp_parser_main;
+  u64 session_net = clib_host_to_net_u64 (session->session_id);
+  sfdp_tenant_t *tenant = sfdp_tenant_at_index (sfdp, session->tenant_idx);
+  sfdp_session_ip46_key_t skey;
+  __clib_aligned (CLIB_CACHE_LINE_BYTES)
+  u8 kdata[SFDP_PARSER_MAX_KEY_SIZE];
+  sfdp_parser_data_t *parser;
+  /* Session id */
+  table_format_cell (t, n, 0, "0x%U", format_hex_bytes, &session_net,
+                    sizeof (session_net));
+  /* Tenant id */
+  table_format_cell (t, n, 1, "%d", tenant->tenant_id);
+  /* Session index */
+  table_format_cell (t, n, 2, "%d", session_index);
+  /* Session type */
+  table_format_cell (t, n, 3, "%U", format_sfdp_session_type, session->type,
+                    session->parser_index[SFDP_SESSION_KEY_PRIMARY]);
+  /* Session flags */
+  table_format_cell (t, n, 4, "%U", format_sfdp_tcp_check_session_flags,
+                    tcp_session->flags);
+  if (session->key_flags & SFDP_SESSION_KEY_FLAG_PRIMARY_VALID_IP4)
+    {
+      sfdp_normalise_ip4_key (session, &skey.key4, SFDP_SESSION_KEY_PRIMARY);
+      table_format_cell (t, n, 5, "%U", format_sfdp_ipv4_context_id,
+                        &skey.key4);
+      table_format_cell (t, n, 6, "%U", format_sfdp_ipv4_ingress, &skey.key4);
+      table_format_cell (t, n, 7, "%U", format_sfdp_ipv4_egress, &skey.key4);
+    }
+  else if (session->key_flags & SFDP_SESSION_KEY_FLAG_PRIMARY_VALID_IP6)
+    {
+      sfdp_normalise_ip6_key (session, &skey.key6, SFDP_SESSION_KEY_PRIMARY);
+      table_format_cell (t, n, 5, "%U", format_sfdp_ipv6_context_id,
+                        &skey.key6);
+      table_format_cell (t, n, 6, "%U", format_sfdp_ipv6_ingress, &skey.key6);
+      table_format_cell (t, n, 7, "%U", format_sfdp_ipv6_egress, &skey.key6);
+    }
+  else if (session->key_flags & SFDP_SESSION_KEY_FLAG_PRIMARY_VALID_USER)
+    {
+      parser = vec_elt_at_index (
+       pm->parsers, session->parser_index[SFDP_SESSION_KEY_PRIMARY]);
+      parser->normalize_key_fn (session, kdata, SFDP_SESSION_KEY_PRIMARY);
+      table_format_cell (
+       t, n, 5, "%U", parser->format_fn[SFDP_PARSER_FORMAT_FUNCTION_CONTEXT],
+       kdata);
+      table_format_cell (
+       t, n, 6, "%U", parser->format_fn[SFDP_PARSER_FORMAT_FUNCTION_INGRESS],
+       kdata);
+      table_format_cell (t, n, 7, "%U",
+                        parser->format_fn[SFDP_PARSER_FORMAT_FUNCTION_EGRESS],
+                        kdata);
+    }
+  n += 1;
+  if (session->key_flags & SFDP_SESSION_KEY_FLAG_SECONDARY_VALID_IP4)
+    {
+      sfdp_normalise_ip4_key (session, &skey.key4, SFDP_SESSION_KEY_SECONDARY);
+      table_format_cell (t, n, 5, "%U", format_sfdp_ipv4_context_id,
+                        &skey.key4);
+      table_format_cell (t, n, 6, "%U", format_sfdp_ipv4_ingress, &skey.key4);
+      table_format_cell (t, n, 7, "%U", format_sfdp_ipv4_egress, &skey.key4);
+      n += 1;
+    }
+  else if (session->key_flags & SFDP_SESSION_KEY_FLAG_SECONDARY_VALID_IP6)
+    {
+      sfdp_normalise_ip6_key (session, &skey.key6, SFDP_SESSION_KEY_SECONDARY);
+      table_format_cell (t, n, 5, "%U", format_sfdp_ipv6_context_id,
+                        &skey.key6);
+      table_format_cell (t, n, 6, "%U", format_sfdp_ipv6_ingress, &skey.key6);
+      table_format_cell (t, n, 7, "%U", format_sfdp_ipv6_egress, &skey.key6);
+      n += 1;
+    }
+  else if (session->key_flags & SFDP_SESSION_KEY_FLAG_SECONDARY_VALID_USER)
+    {
+      parser = vec_elt_at_index (
+       pm->parsers, session->parser_index[SFDP_SESSION_KEY_SECONDARY]);
+      parser->normalize_key_fn (session, kdata, SFDP_SESSION_KEY_SECONDARY);
+      table_format_cell (
+       t, n, 5, "%U", parser->format_fn[SFDP_PARSER_FORMAT_FUNCTION_CONTEXT],
+       kdata);
+      table_format_cell (
+       t, n, 6, "%U", parser->format_fn[SFDP_PARSER_FORMAT_FUNCTION_INGRESS],
+       kdata);
+      table_format_cell (t, n, 7, "%U",
+                        parser->format_fn[SFDP_PARSER_FORMAT_FUNCTION_EGRESS],
+                        kdata);
+      n += 1;
+    }
+  return n;
+}
diff --git a/src/plugins/sfdp_services/base/tcp-check/node.c b/src/plugins/sfdp_services/base/tcp-check/node.c
new file mode 100644 (file)
index 0000000..6c395e5
--- /dev/null
@@ -0,0 +1,310 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <sfdp_services/base/tcp-check/tcp_check.h>
+#include <vnet/sfdp/service.h>
+#include <vnet/sfdp/timer/timer.h>
+
+#define foreach_sfdp_tcp_check_error _ (DROP, "drop")
+
+typedef enum
+{
+#define _(sym, str) SFDP_TCP_CHECK_ERROR_##sym,
+  foreach_sfdp_tcp_check_error
+#undef _
+    SFDP_TCP_CHECK_N_ERROR,
+} sfdp_tcp_check_error_t;
+
+static char *sfdp_tcp_check_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_tcp_check_error
+#undef _
+};
+
+typedef struct
+{
+  u32 flow_id;
+  u32 old_state_flags;
+  u32 new_state_flags;
+} sfdp_tcp_check_trace_t;
+
+static u8 *
+format_sfdp_tcp_check_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sfdp_tcp_check_trace_t *t = va_arg (*args, sfdp_tcp_check_trace_t *);
+  u32 indent = format_get_indent (s);
+  indent += 2;
+  s = format (s, "sfdp-tcp-check: flow-id %u (session %u, %s)\n", t->flow_id,
+             t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward");
+  s = format (s, "%Uold session flags: %U\n", format_white_space, indent,
+             format_sfdp_tcp_check_session_flags, t->old_state_flags);
+  s = format (s, "%Unew session flags: %U\n", format_white_space, indent,
+             format_sfdp_tcp_check_session_flags, t->new_state_flags);
+  return s;
+}
+
+SFDP_SERVICE_DECLARE (drop)
+static_always_inline void
+update_state_one_pkt (sfdp_tw_t *tw, sfdp_tenant_t *tenant,
+                     sfdp_tcp_check_session_state_t *tcp_session,
+                     sfdp_session_t *session, f64 current_time, u8 dir,
+                     u16 *to_next, vlib_buffer_t **b, u32 *sf, u32 *nsf)
+{
+  /* Parse the packet */
+  /* TODO: !!! Broken with IP options !!! */
+  u8 *data = vlib_buffer_get_current (b[0]);
+  tcp_header_t *tcph =
+    (void *) (data + (session->type == SFDP_SESSION_TYPE_IP4 ?
+                       sizeof (ip4_header_t) :
+                       sizeof (ip6_header_t)));
+  ip4_header_t *ip4 = (void *) data;
+  ip6_header_t *ip6 = (void *) data;
+  /* Ignore non first fragments */
+  if (session->type == SFDP_SESSION_TYPE_IP4 &&
+      ip4->flags_and_fragment_offset &
+       clib_host_to_net_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS - 1))
+    {
+      sfdp_next (b[0], to_next);
+      return;
+    }
+
+  if (session->type == SFDP_SESSION_TYPE_IP6 && ip6_ext_hdr (ip6->protocol))
+    {
+      ip6_ext_hdr_chain_t chain = { 0 };
+      int res = ip6_ext_header_walk (b[0], ip6, IP_PROTOCOL_IPV6_FRAGMENTATION,
+                                    &chain);
+      if (res >= 0 && chain.eh[res].protocol == IP_PROTOCOL_IPV6_FRAGMENTATION)
+       {
+         ip6_frag_hdr_t *frag =
+           ip6_ext_next_header_offset (ip6, chain.eh[res].offset);
+         if (ip6_frag_hdr_offset (frag))
+           {
+             sfdp_next (b[0], to_next);
+             return;
+           }
+       }
+      tcph =
+       ip6_ext_next_header_offset (ip6, chain.eh[chain.length - 1].offset);
+    }
+
+  u8 flags = tcph->flags & SFDP_TCP_CHECK_TCP_FLAGS_MASK;
+  u32 acknum = clib_net_to_host_u32 (tcph->ack_number);
+  u32 seqnum = clib_net_to_host_u32 (tcph->seq_number);
+  u32 next_timeout = 0;
+  u8 remove_session = 0;
+  if (PREDICT_FALSE (tcp_session->version != session->session_version))
+    {
+      tcp_session->version = session->session_version;
+      tcp_session->flags = 0;
+      tcp_session->as_u64_0 = 0;
+      if (flags != SFDP_TCP_CHECK_TCP_FLAGS_SYN)
+       {
+         /* Abnormal, put the session in blocked state */
+         session->bitmaps[SFDP_FLOW_FORWARD] = SFDP_SERVICE_MASK (drop);
+         session->bitmaps[SFDP_FLOW_REVERSE] = SFDP_SERVICE_MASK (drop);
+         sfdp_buffer (b[0])->service_bitmap = SFDP_SERVICE_MASK (drop);
+         tcp_session->flags = SFDP_TCP_CHECK_SESSION_FLAG_BLOCKED;
+       }
+    }
+  nsf[0] = (sf[0] = tcp_session->flags);
+  if (dir == SFDP_FLOW_FORWARD)
+    {
+      if (sf[0] & SFDP_TCP_CHECK_SESSION_FLAG_BLOCKED)
+       goto out;
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_SYN)
+       {
+         /* New session, must be a SYN otherwise bad */
+         if (sf[0] == 0)
+           nsf[0] = SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_SYN |
+                    SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_ACK_TO_SYN;
+         else
+           {
+             remove_session = 1;
+             goto out;
+           }
+       }
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_ACK)
+       {
+         /* Either ACK to SYN */
+         if (sf[0] & SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_INIT_ACK_TO_SYN)
+           nsf[0] &= ~SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_INIT_ACK_TO_SYN;
+         /* Or ACK to FIN */
+         if (sf[0] & SFDP_TCP_CHECK_SESSION_FLAG_SEEN_FIN_RESP &&
+             acknum == tcp_session->fin_num[SFDP_FLOW_REVERSE])
+           nsf[0] |= SFDP_TCP_CHECK_SESSION_FLAG_SEEN_ACK_TO_FIN_INIT;
+         /* Or regular ACK */
+       }
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_FIN)
+       {
+         /*If we were up, we are not anymore */
+         nsf[0] &= ~SFDP_TCP_CHECK_SESSION_FLAG_ESTABLISHED;
+         /*Seen our FIN, wait for the other FIN and for an ACK*/
+         tcp_session->fin_num[SFDP_FLOW_FORWARD] = seqnum + 1;
+         nsf[0] |= SFDP_TCP_CHECK_SESSION_FLAG_SEEN_FIN_INIT;
+       }
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_RST)
+       {
+         /* Reason to kill the connection */
+         remove_session = 1;
+         goto out;
+       }
+    }
+  if (dir == SFDP_FLOW_REVERSE)
+    {
+      if (sf[0] & SFDP_TCP_CHECK_SESSION_FLAG_BLOCKED)
+       goto out;
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_SYN)
+       {
+         if (sf[0] & SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_SYN)
+           nsf[0] ^= SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_SYN |
+                     SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_INIT_ACK_TO_SYN;
+       }
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_ACK)
+       {
+         /* Either ACK to SYN */
+         if (sf[0] & SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_ACK_TO_SYN)
+           nsf[0] &= ~SFDP_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_ACK_TO_SYN;
+         /* Or ACK to FIN */
+         if (sf[0] & SFDP_TCP_CHECK_SESSION_FLAG_SEEN_FIN_INIT &&
+             acknum == tcp_session->fin_num[SFDP_FLOW_FORWARD])
+           nsf[0] |= SFDP_TCP_CHECK_SESSION_FLAG_SEEN_ACK_TO_FIN_RESP;
+         /* Or regular ACK */
+       }
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_FIN)
+       {
+         /*If we were up, we are not anymore */
+         nsf[0] &= ~SFDP_TCP_CHECK_SESSION_FLAG_ESTABLISHED;
+         /* Seen our FIN, wait for the other FIN and for an ACK */
+         tcp_session->fin_num[SFDP_FLOW_REVERSE] = seqnum + 1;
+         nsf[0] |= SFDP_TCP_CHECK_SESSION_FLAG_SEEN_FIN_RESP;
+       }
+      if (flags & SFDP_TCP_CHECK_TCP_FLAGS_RST)
+       {
+         /* Reason to kill the connection */
+         nsf[0] = SFDP_TCP_CHECK_SESSION_FLAG_REMOVING;
+         remove_session = 1;
+         goto out;
+       }
+    }
+  /* If all flags are cleared connection is established! */
+  if (nsf[0] == 0)
+    {
+      nsf[0] = SFDP_TCP_CHECK_SESSION_FLAG_ESTABLISHED;
+      session->state = SFDP_SESSION_STATE_ESTABLISHED;
+    }
+
+  /* If all FINs are ACKED, game over */
+  if ((nsf[0] & (SFDP_TCP_CHECK_SESSION_FLAG_SEEN_ACK_TO_FIN_INIT)) &&
+      (nsf[0] & SFDP_TCP_CHECK_SESSION_FLAG_SEEN_ACK_TO_FIN_RESP))
+    {
+      nsf[0] = SFDP_TCP_CHECK_SESSION_FLAG_REMOVING;
+      remove_session = 1;
+    }
+out:
+  tcp_session->flags = nsf[0];
+  if (remove_session)
+    next_timeout = 0;
+  else if (nsf[0] & SFDP_TCP_CHECK_SESSION_FLAG_ESTABLISHED)
+    next_timeout = tenant->timeouts[SFDP_TIMEOUT_TCP_ESTABLISHED];
+  else if (nsf[0] & SFDP_TCP_CHECK_SESSION_FLAG_BLOCKED)
+    next_timeout = tenant->timeouts[SFDP_TIMEOUT_SECURITY];
+  else
+    next_timeout = tenant->timeouts[SFDP_TIMEOUT_EMBRYONIC];
+
+  sfdp_session_timer_update_maybe_past (tw, SFDP_SESSION_TIMER (session),
+                                       current_time, next_timeout);
+  sfdp_next (b[0], to_next);
+  return;
+}
+
+VLIB_NODE_FN (sfdp_tcp_check_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  sfdp_main_t *sfdp = &sfdp_main;
+  sfdp_timer_main_t *sfdpt = &sfdp_timer_main;
+  sfdp_tcp_check_main_t *vtcm = &sfdp_tcp;
+  u32 thread_index = vlib_get_thread_index ();
+  sfdp_timer_per_thread_data_t *timer_ptd =
+    vec_elt_at_index (sfdpt->per_thread_data, thread_index);
+
+  sfdp_session_t *session;
+  sfdp_tenant_t *tenant;
+  u32 session_idx;
+  sfdp_tcp_check_session_state_t *tcp_session;
+  sfdp_tw_t *tw = &timer_ptd->wheel;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+  u32 state_flags[VLIB_FRAME_SIZE], *sf = state_flags;
+  u32 new_state_flags[VLIB_FRAME_SIZE], *nsf = new_state_flags;
+  f64 current_time = timer_ptd->current_time;
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+  while (n_left > 0)
+    {
+      session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+      session = sfdp_session_at_index (session_idx);
+      tcp_session = vec_elt_at_index (vtcm->state, session_idx);
+      tenant = sfdp_tenant_at_index (sfdp, sfdp_buffer (b[0])->tenant_index);
+      if (sfdp_direction_from_flow_index (b[0]->flow_id) == SFDP_FLOW_FORWARD)
+       update_state_one_pkt (tw, tenant, tcp_session, session, current_time,
+                             SFDP_FLOW_FORWARD, to_next, b, sf, nsf);
+      else
+       update_state_one_pkt (tw, tenant, tcp_session, session, current_time,
+                             SFDP_FLOW_REVERSE, to_next, b, sf, nsf);
+      n_left -= 1;
+      b += 1;
+      to_next += 1;
+      sf += 1;
+      nsf += 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      int i;
+      b = bufs;
+      sf = state_flags;
+      nsf = new_state_flags;
+      n_left = frame->n_vectors;
+      for (i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sfdp_tcp_check_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             t->flow_id = b[0]->flow_id;
+             t->old_state_flags = sf[0];
+             t->new_state_flags = nsf[0];
+             b++;
+             sf++;
+             nsf++;
+           }
+         else
+           break;
+       }
+    }
+  return frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (sfdp_tcp_check_node) = {
+  .name = "sfdp-tcp-check",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_tcp_check_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_tcp_check_error_strings),
+  .error_strings = sfdp_tcp_check_error_strings
+};
+
+SFDP_SERVICE_DEFINE (tcp_check) = {
+  .node_name = "sfdp-tcp-check",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle"),
+  .is_terminal = 0
+};
diff --git a/src/plugins/sfdp_services/base/tcp-check/tcp_check.api b/src/plugins/sfdp_services/base/tcp-check/tcp_check.api
new file mode 100644 (file)
index 0000000..93144bb
--- /dev/null
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+option version = "0.0.1";
+import "vnet/ip/ip_types.api";
+import "vnet/sfdp/sfdp_types.api";
+
+enumflag sfdp_tcp_check_session_flags : u32
+{
+  SFDP_API_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_SYN = 0x1,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_WAIT_FOR_INIT_ACK_TO_SYN,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_WAIT_FOR_RESP_ACK_TO_SYN = 0x4,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_SEEN_FIN_INIT = 0x8,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_SEEN_FIN_RESP = 0x10,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_SEEN_ACK_TO_FIN_INIT = 0x20,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_SEEN_ACK_TO_FIN_RESP = 0x40,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_ESTABLISHED = 0x80,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_REMOVING = 0x100,
+  SFDP_API_TCP_CHECK_SESSION_FLAG_BLOCKED = 0x200,
+};
+
+define sfdp_tcp_session_dump
+{
+  u32 client_index;
+  u32 context;
+};
+
+define sfdp_tcp_session_details
+{
+  u32 context;
+
+  u64 session_id;
+  u32 thread_index;
+  u32 tenant_id;
+  u32 session_idx;
+  vl_api_sfdp_session_type_t session_type;
+  vl_api_sfdp_tcp_check_session_flags_t flags;
+  u8 n_keys;
+  vl_api_sfdp_session_key_t keys[n_keys];
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/base/tcp-check/tcp_check.c b/src/plugins/sfdp_services/base/tcp-check/tcp_check.c
new file mode 100644 (file)
index 0000000..37f353f
--- /dev/null
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <sfdp_services/base/tcp-check/tcp_check.h>
+
+sfdp_tcp_check_main_t sfdp_tcp;
+
+static clib_error_t *
+sfdp_tcp_check_init (vlib_main_t *vm)
+{
+  sfdp_tcp_check_main_t *vtcm = &sfdp_tcp;
+  vec_validate (vtcm->state, sfdp_num_sessions ());
+  return 0;
+};
+
+VLIB_INIT_FUNCTION (sfdp_tcp_check_init);
diff --git a/src/plugins/sfdp_services/base/tcp-check/tcp_check.h b/src/plugins/sfdp_services/base/tcp-check/tcp_check.h
new file mode 100644 (file)
index 0000000..3918ce6
--- /dev/null
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef __included_sfdp_tcp_check_h__
+#define __included_sfdp_tcp_check_h__
+
+#include <vlib/vlib.h>
+#include <vnet/sfdp/sfdp.h>
+/* Convention: uppercase relates to responder lowercase to initiator */
+#define foreach_sfdp_tcp_check_session_flag                                   \
+  _ (WAIT_FOR_RESP_SYN, 0, "S")                                               \
+  _ (WAIT_FOR_INIT_ACK_TO_SYN, 1, "a")                                        \
+  _ (WAIT_FOR_RESP_ACK_TO_SYN, 2, "A")                                        \
+  _ (SEEN_FIN_INIT, 3, "f")                                                   \
+  _ (SEEN_FIN_RESP, 4, "F")                                                   \
+  _ (SEEN_ACK_TO_FIN_INIT, 5, "r")                                            \
+  _ (SEEN_ACK_TO_FIN_RESP, 6, "R")                                            \
+  _ (ESTABLISHED, 7, "U")                                                     \
+  _ (REMOVING, 8, "D")                                                        \
+  _ (BLOCKED, 9, "X")
+
+typedef enum
+{
+#define _(name, x, str) SFDP_TCP_CHECK_SESSION_FLAG_##name = (1 << (x)),
+  foreach_sfdp_tcp_check_session_flag SFDP_TCP_CHECK_SESSION_N_FLAG
+#undef _
+} sfdp_tcp_check_session_flag_t;
+
+#define SFDP_TCP_CHECK_TCP_FLAGS_MASK (0x17)
+#define SFDP_TCP_CHECK_TCP_FLAGS_FIN  (0x1)
+#define SFDP_TCP_CHECK_TCP_FLAGS_SYN  (0x2)
+#define SFDP_TCP_CHECK_TCP_FLAGS_RST  (0x4)
+#define SFDP_TCP_CHECK_TCP_FLAGS_ACK  (0x10)
+/* transitions are labelled with TCP Flags encoded in u8 as
+   0 0 0 ACK 0 RST SYN FIN
+   Transition table for each direction is 32x9
+   result of the lookup is (what is set, what is cleared)
+ */
+/*#define foreach_sfdp_tcp_check_forward_transition \
+_(WAIT_FOR_INIT_ACK_TO_SYN, ACK, )*/
+
+typedef struct
+{
+  CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
+  u32 flags;
+  union
+  {
+    u32 fin_num[SFDP_FLOW_F_B_N];
+    u64 as_u64_0;
+  };
+  session_version_t version;
+} sfdp_tcp_check_session_state_t;
+
+typedef struct
+{
+  sfdp_tcp_check_session_state_t *state; /* vec indexed by session-index */
+  u16 msg_id_base;
+} sfdp_tcp_check_main_t;
+
+extern sfdp_tcp_check_main_t sfdp_tcp;
+
+format_function_t format_sfdp_tcp_check_session_flags;
+u32 sfdp_table_format_insert_tcp_check_session (
+  table_t *t, u32 n, sfdp_main_t *sfdp, u32 session_index,
+  sfdp_session_t *session, sfdp_tcp_check_session_state_t *tcp_session);
+#endif /* __included_sfdp_tcp_check_h__ */
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/dot1q/dot1q.c b/src/plugins/sfdp_services/dot1q/dot1q.c
new file mode 100644 (file)
index 0000000..b0995bb
--- /dev/null
@@ -0,0 +1,296 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/feature/feature.h>
+#include <vnet/sfdp/sfdp.h>
+#include <vnet/sfdp/common.h>
+#include <vnet/sfdp/service.h>
+typedef struct
+{
+  u32 next_index;
+  u32 sw_if_index;
+} sfdp_dummy_dot1q_input_trace_t;
+
+static u8 *
+format_sfdp_dummy_dot1q_input_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 *);
+  CLIB_UNUSED (sfdp_dummy_dot1q_input_trace_t * t) =
+    va_arg (*args, sfdp_dummy_dot1q_input_trace_t *);
+
+  /*s = format (s, "snort-enq: sw_if_index %d, next index %d\n",
+     t->sw_if_index, t->next_index);*/
+
+  return s;
+}
+
+#define foreach_sfdp_dummy_dot1q_input_next                                   \
+  _ (LOOKUP_IP4, "sfdp-lookup-ip4")                                           \
+  _ (LOOKUP_IP6, "sfdp-lookup-ip6")
+#define foreach_sfdp_dummy_dot1q_input_error _ (NOERROR, "No error")
+
+typedef enum
+{
+#define _(sym, str) SFDP_DUMMY_DOT1Q_ERROR_##sym,
+  foreach_sfdp_dummy_dot1q_input_error
+#undef _
+    SFDP_DUMMY_DOT1Q_N_ERROR,
+} sfdp_dummy_dot1q_input_error_t;
+
+static char *sfdp_dummy_dot1q_input_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_dummy_dot1q_input_error
+#undef _
+};
+
+typedef enum
+{
+#define _(s, n) SFDP_DUMMY_DOT1Q_INPUT_NEXT_##s,
+  foreach_sfdp_dummy_dot1q_input_next
+#undef _
+    SFDP_DUMMY_DOT1Q_INPUT_N_NEXT
+} sfdp_dummy_dot1q_input_next_t;
+
+/*-----------------------------*/
+
+typedef struct
+{
+  u32 next_index;
+  u32 sw_if_index;
+} sfdp_dummy_dot1q_output_trace_t;
+
+static u8 *
+format_sfdp_dummy_dot1q_output_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 *);
+  CLIB_UNUSED (sfdp_dummy_dot1q_output_trace_t * t) =
+    va_arg (*args, sfdp_dummy_dot1q_output_trace_t *);
+
+  /*s = format (s, "snort-enq: sw_if_index %d, next index %d\n",
+     t->sw_if_index, t->next_index);*/
+
+  return s;
+}
+
+#define foreach_sfdp_dummy_dot1q_output_next                                  \
+  _ (LOOKUP_IP4, "sfdp-lookup-ip4")                                           \
+  _ (LOOKUP_IP6, "sfdp-lookup-ip6")
+#define foreach_sfdp_dummy_dot1q_output_error _ (NOERROR, "No error")
+
+typedef enum
+{
+#define _(sym, str) SFDP_DUMMY_DOT1Q_OUTPUT_ERROR_##sym,
+  foreach_sfdp_dummy_dot1q_output_error
+#undef _
+    SFDP_DUMMY_DOT1Q_OUTPUT_N_ERROR,
+} sfdp_dummy_dot1q_output_error_t;
+
+static char *sfdp_dummy_dot1q_output_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_dummy_dot1q_output_error
+#undef _
+};
+
+typedef enum
+{
+#define _(s, n) SFDP_DUMMY_DOT1Q_OUTPUT_NEXT_##s,
+  foreach_sfdp_dummy_dot1q_output_next
+#undef _
+    SFDP_DUMMY_DOT1Q_OUTPUT_N_NEXT
+} sfdp_dummy_dot1q_output_next_t;
+
+static_always_inline void
+process_one_pkt (vlib_main_t *vm, sfdp_main_t *sfdp,
+                vlib_combined_counter_main_t *cm, u32 thread_index,
+                vlib_buffer_t **b, u16 *current_next)
+{
+  sfdp_tenant_t *tenant;
+  clib_bihash_kv_8_8_t kv = { 0 };
+  u8 *data = vlib_buffer_get_current (b[0]);
+  u32 orig_len = vlib_buffer_length_in_chain (vm, b[0]);
+  ethernet_header_t *eth = (void *) data;
+  u32 tenant_id = 0;
+  u32 off = sizeof (eth[0]);
+  u16 type = clib_net_to_host_u16 (eth->type);
+  u16 tenant_idx;
+  if (type == ETHERNET_TYPE_VLAN)
+    {
+      ethernet_vlan_header_t *vlan = (void *) (data + sizeof (eth[0]));
+      tenant_id = clib_net_to_host_u16 (vlan->priority_cfi_and_id) & 0xfff;
+      type = clib_net_to_host_u16 (vlan->type);
+      off += sizeof (vlan[0]);
+    }
+  if (type != ETHERNET_TYPE_IP4 && type != ETHERNET_TYPE_IP6)
+    {
+      vnet_feature_next_u16 (current_next, b[0]);
+      return;
+    }
+  /* Tenant-id lookup */
+  kv.key = (u64) tenant_id;
+  if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+    {
+      /* Not found */
+      vnet_feature_next_u16 (current_next, b[0]);
+      return;
+    }
+  tenant_idx = kv.value;
+  tenant = sfdp_tenant_at_index (sfdp, tenant_idx);
+  b[0]->flow_id = tenant->context_id;
+  sfdp_buffer (b[0])->tenant_index = tenant_idx;
+  vnet_buffer (b[0])->l2_hdr_offset = b[0]->current_data;
+  vnet_buffer (b[0])->l3_hdr_offset = b[0]->current_data + off;
+  b[0]->flags |=
+    VNET_BUFFER_F_L2_HDR_OFFSET_VALID | VNET_BUFFER_F_L3_HDR_OFFSET_VALID;
+  current_next[0] = type == ETHERNET_TYPE_IP4 ?
+                     SFDP_DUMMY_DOT1Q_INPUT_NEXT_LOOKUP_IP4 :
+                     SFDP_DUMMY_DOT1Q_INPUT_NEXT_LOOKUP_IP6;
+  vlib_increment_combined_counter (cm, thread_index, tenant_idx, 1,
+                                  orig_len - off);
+  vlib_buffer_advance (b[0], off);
+}
+
+static_always_inline uword
+sfdp_dummy_dot1q_input_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
+                              vlib_frame_t *frame)
+{
+  /*
+   * use VNI as tenant ID
+   * tenant_id -> tenant index
+   * drop unknown tenants
+   * store tenant_id into opaque1
+   * advance current data to beginning of IP packet
+   */
+  sfdp_main_t *sfdp = &sfdp_main;
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *current_next;
+  u32 thread_index = vlib_get_thread_index ();
+  vlib_combined_counter_main_t *cm =
+    &sfdp->tenant_data_ctr[SFDP_TENANT_DATA_COUNTER_INCOMING];
+  vlib_get_buffers (vm, from, bufs, n_left);
+  b = bufs;
+  current_next = next_indices;
+
+  while (n_left)
+    {
+      process_one_pkt (vm, sfdp, cm, thread_index, b, current_next);
+      b += 1;
+      current_next += 1;
+      n_left -= 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (sfdp_dummy_dot1q_input_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return sfdp_dummy_dot1q_input_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (sfdp_dummy_dot1q_input_node) = {
+  .name = "sfdp-dummy-dot1q-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_dummy_dot1q_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_dummy_dot1q_input_error_strings),
+  .error_strings = sfdp_dummy_dot1q_input_error_strings,
+  .n_next_nodes = SFDP_DUMMY_DOT1Q_INPUT_N_NEXT,
+  .next_nodes = {
+          [SFDP_DUMMY_DOT1Q_INPUT_NEXT_LOOKUP_IP4] = "sfdp-lookup-ip4",
+          [SFDP_DUMMY_DOT1Q_INPUT_NEXT_LOOKUP_IP6] = "sfdp-lookup-ip6",
+  },
+};
+
+VNET_FEATURE_INIT (sfdp_dummy_dot1q_input_feat, static) = {
+  .arc_name = "device-input",
+  .node_name = "sfdp-dummy-dot1q-input",
+};
+
+#define SFDP_PREFETCH_SIZE 8
+VLIB_NODE_FN (sfdp_dummy_dot1q_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  sfdp_main_t *sfdp = &sfdp_main;
+  vlib_combined_counter_main_t *cm =
+    &sfdp->tenant_data_ctr[SFDP_TENANT_DATA_COUNTER_OUTGOING];
+  u32 thread_index = vlib_get_thread_index ();
+  u16 tenant_idx[SFDP_PREFETCH_SIZE];
+  u32 orig_len[SFDP_PREFETCH_SIZE];
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+
+  while (n_left > SFDP_PREFETCH_SIZE)
+    {
+      word l2_len[SFDP_PREFETCH_SIZE];
+      if (n_left > 2 * SFDP_PREFETCH_SIZE)
+       for (int i = 0; i < SFDP_PREFETCH_SIZE; i++)
+         vlib_prefetch_buffer_header (b[0], STORE);
+
+      for (int i = 0; i < SFDP_PREFETCH_SIZE; i++)
+       {
+         orig_len[i] = vlib_buffer_length_in_chain (vm, b[i]);
+         tenant_idx[i] = sfdp_buffer (b[i])->tenant_index;
+         vlib_increment_combined_counter (cm, thread_index, tenant_idx[i], 1,
+                                          orig_len[i]);
+         l2_len[i] = vnet_buffer (b[i])->l3_hdr_offset;
+         l2_len[i] -= vnet_buffer (b[i])->l2_hdr_offset;
+         vlib_buffer_advance (b[i], -l2_len[i]);
+         vnet_feature_next_u16 (to_next + i, b[i]);
+       }
+
+      b += SFDP_PREFETCH_SIZE;
+      to_next += SFDP_PREFETCH_SIZE;
+      n_left -= SFDP_PREFETCH_SIZE;
+    }
+  while (n_left)
+    {
+      word l2_len;
+      u32 orig_len = vlib_buffer_length_in_chain (vm, b[0]);
+      u16 tenant_idx = sfdp_buffer (b[0])->tenant_index;
+      vlib_increment_combined_counter (cm, thread_index, tenant_idx, 1,
+                                      orig_len);
+      l2_len = vnet_buffer (b[0])->l3_hdr_offset;
+      l2_len -= vnet_buffer (b[0])->l2_hdr_offset;
+      vlib_buffer_advance (b[0], -l2_len);
+      vnet_feature_next_u16 (to_next, b[0]);
+
+      b += 1;
+      to_next += 1;
+      n_left -= 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  return frame->n_vectors;
+}
+#undef SFDP_PREFETCH_SIZE
+
+VLIB_REGISTER_NODE (sfdp_dummy_dot1q_output_node) = {
+  .name = "sfdp-dummy-dot1q-output",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_dummy_dot1q_output_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_dummy_dot1q_output_error_strings),
+  .error_strings = sfdp_dummy_dot1q_output_error_strings,
+
+  .sibling_of = "sfdp-dummy-dot1q-input"
+};
+
+SFDP_SERVICE_DEFINE (dummy_dot1q_output) = {
+  .node_name = "sfdp-dummy-dot1q-output",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle",
+                              "sfdp-tcp-check"),
+  .is_terminal = 1
+};
diff --git a/src/plugins/sfdp_services/geneve/api.c b/src/plugins/sfdp_services/geneve/api.c
new file mode 100644 (file)
index 0000000..94e52d2
--- /dev/null
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vnet/sfdp/sfdp.h>
+
+#include <sfdp_services/geneve/gateway.h>
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+#include <vnet/ip/ip_types_api.h>
+#include <vnet/ethernet/ethernet_types_api.h>
+
+#include <vnet/format_fns.h>
+#include <sfdp_services/geneve/gateway.api_enum.h>
+#include <sfdp_services/geneve/gateway.api_types.h>
+
+#define REPLY_MSG_ID_BASE gw->msg_id_base
+#include <vlibapi/api_helper_macros.h>
+
+static void
+vl_api_sfdp_gateway_set_geneve_output_t_handler (
+  vl_api_sfdp_gateway_set_geneve_output_t *mp)
+{
+  gw_main_t *gw = &gateway_main;
+  int rv;
+  vl_api_sfdp_gateway_set_geneve_output_reply_t *rmp;
+  gw_set_geneve_output_args_t args;
+  args.tenant_id = clib_net_to_host_u32 (mp->tenant_id);
+  ASSERT (mp->src.af == ADDRESS_IP4);
+  ASSERT (mp->dst.af == ADDRESS_IP4);
+  ip4_address_decode (mp->src.un.ip4, &args.src_addr);
+  ip4_address_decode (mp->dst.un.ip4, &args.dst_addr);
+  args.src_port = mp->src_port;
+  args.dst_port = mp->dst_port;
+  args.direction = mp->dir;
+  args.static_mac = mp->static_mac;
+  args.output_tenant_id = clib_net_to_host_u32 (mp->output_tenant_id);
+  mac_address_decode (mp->src_mac, &args.src_mac);
+  mac_address_decode (mp->dst_mac, &args.dst_mac);
+  gw_set_geneve_output (&args);
+  rv = args.err ? -1 : 0;
+  REPLY_MACRO (VL_API_SFDP_GATEWAY_SET_GENEVE_OUTPUT_REPLY);
+}
+
+static void
+vl_api_sfdp_gateway_geneve_input_enable_disable_t_handler (
+  vl_api_sfdp_gateway_geneve_input_enable_disable_t *mp)
+{
+  gw_main_t *gw = &gateway_main;
+  int rv;
+  vl_api_sfdp_gateway_geneve_input_enable_disable_reply_t *rmp;
+  gw_enable_disable_geneve_input_args_t args;
+  args.enable_disable = mp->is_enable;
+  args.sw_if_index = clib_net_to_host_u32 (mp->sw_if_index);
+  gw_enable_disable_geneve_input (&args);
+  rv = args.err ? -1 : 0;
+  REPLY_MACRO (VL_API_SFDP_GATEWAY_GENEVE_INPUT_ENABLE_DISABLE_REPLY);
+}
+
+#include <sfdp_services/geneve/gateway.api.c>
+static clib_error_t *
+sfdp_gateway_api_hookup (vlib_main_t *vm)
+{
+  gw_main_t *gw = &gateway_main;
+  gw->msg_id_base = setup_message_id_table ();
+  return 0;
+}
+VLIB_API_INIT_FUNCTION (sfdp_gateway_api_hookup);
diff --git a/src/plugins/sfdp_services/geneve/cli.c b/src/plugins/sfdp_services/geneve/cli.c
new file mode 100644 (file)
index 0000000..e1af6a4
--- /dev/null
@@ -0,0 +1,151 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <sfdp_services/geneve/gateway.h>
+#include <vnet/plugin/plugin.h>
+#include <vnet/vnet.h>
+
+/*
+ * add CLI:
+ * set gateway geneve-output tenant <tenant-id> src <src ip> dst <dst ip>
+ *      src-port <src-port> dst-port <dst-port> <forward|reverse>
+ *
+ * it sets the geneve output data in each direction
+ */
+
+static clib_error_t *
+gateway_set_output_command_fn (vlib_main_t *vm, unformat_input_t *input,
+                              vlib_cli_command_t *cmd)
+{
+  unformat_input_t line_input_, *line_input = &line_input_;
+  gw_set_geneve_output_args_t args = { .tenant_id = ~0,
+                                      .src_addr = { .as_u32 = ~0 },
+                                      .dst_addr = { .as_u32 = ~0 },
+                                      .src_port = ~0,
+                                      .dst_port = ~0,
+                                      .direction = ~0,
+                                      .output_tenant_id = ~0,
+                                      .src_mac = { .bytes = { 0 } },
+                                      .dst_mac = { .bytes = { 0 } } };
+  clib_error_t *err = 0;
+  u32 tmp;
+
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "tenant %d", &args.tenant_id))
+       ;
+      else if (unformat (line_input, "output-tenant %d",
+                        &args.output_tenant_id))
+       ;
+      else if (unformat (line_input, "src %U", unformat_ip4_address,
+                        &args.src_addr))
+       ;
+      else if (unformat (line_input, "dst %U", unformat_ip4_address,
+                        &args.dst_addr))
+       ;
+      else if (unformat (line_input, "src-port %d", &tmp))
+       args.src_port = clib_host_to_net_u16 (tmp);
+      else if (unformat (line_input, "dst-port %d", &tmp))
+       args.dst_port = clib_host_to_net_u16 (tmp);
+      else if (unformat (line_input, "src-mac %U dst-mac %U",
+                        unformat_mac_address, &args.src_mac,
+                        unformat_mac_address, &args.dst_mac))
+       args.static_mac = 1;
+      else if (unformat (line_input, "forward"))
+       args.direction = SFDP_FLOW_FORWARD;
+      else if (unformat (line_input, "reverse"))
+       args.direction = SFDP_FLOW_REVERSE;
+      else
+       {
+         err = unformat_parse_error (line_input);
+         goto done;
+       }
+    }
+  if (args.tenant_id == ~0 || args.src_addr.as_u32 == ~0 ||
+      args.dst_addr.as_u32 == ~0 || args.src_port == (u16) ~0 ||
+      args.dst_port == (u16) ~0 || args.direction == (u8) ~0)
+    {
+      err = clib_error_return (0, "missing geneve output parameters");
+      goto done;
+    }
+  gw_set_geneve_output (&args);
+  err = args.err;
+done:
+  unformat_free (line_input);
+  return err;
+}
+
+VLIB_CLI_COMMAND (gateway_set_output_command, static) = {
+  .path = "set sfdp gateway geneve-output",
+  .short_help = "set sfdp gateway geneve-output tenant <tenant-id> "
+               "src <src ip> dst <dst ip> "
+               "src-port <src-port> dst-port <dst-port> "
+               "[output-tenant <tenant-id>] "
+               "[src-mac <src-mac-address> dst-mac <dst-mac-address>]"
+               "<forward|reverse>",
+  .function = gateway_set_output_command_fn,
+};
+
+/*
+ * Add CLI:
+ *  gateway geneve-input interface <ifname> <enable-disable>
+ *
+ */
+
+static clib_error_t *
+gateway_geneve_enable_disable_command_fn (vlib_main_t *vm,
+                                         unformat_input_t *input,
+                                         vlib_cli_command_t *cmd)
+{
+  int enable_disable = -1;
+  gw_enable_disable_geneve_input_args_t args;
+  unformat_input_t line_input_, *line_input = &line_input_;
+  clib_error_t *err = 0;
+  args.sw_if_index = ~0;
+  if (!unformat_user (input, unformat_line_input, line_input))
+    return 0;
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (line_input, "interface %U", unformat_vnet_sw_interface,
+                   vnet_get_main (), &args.sw_if_index))
+       ;
+      else if (unformat (line_input, "enable"))
+       enable_disable = 1;
+      else if (unformat (line_input, "disable"))
+       enable_disable = 0;
+      else
+       {
+         err = unformat_parse_error (line_input);
+         goto done;
+       }
+    }
+  if (enable_disable == -1)
+    {
+      err = clib_error_return (0, "enable or disable?");
+      goto done;
+    }
+  if (args.sw_if_index == ~0)
+    {
+      err = clib_error_return (0, "valid interface name required");
+      goto done;
+    }
+  args.enable_disable = enable_disable;
+  gw_enable_disable_geneve_input (&args);
+  err = args.err;
+
+done:
+  unformat_free (line_input);
+  return err;
+}
+
+VLIB_CLI_COMMAND (gateway_geneve_input_enable_disable_command, static) = {
+  .path = "sfdp gateway geneve-input",
+  .short_help =
+    "sfdp gateway geneve-input interface <ifname> <enable|disable>",
+  .function = gateway_geneve_enable_disable_command_fn,
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/geneve/gateway.api b/src/plugins/sfdp_services/geneve/gateway.api
new file mode 100644 (file)
index 0000000..e8ffbbd
--- /dev/null
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+option version = "0.0.1";
+
+import "vnet/ip/ip_types.api";
+import "vnet/ethernet/ethernet_types.api";
+import "vnet/interface_types.api";
+
+enum sfdp_session_direction : u8
+{
+  SFDP_API_FORWARD = 0,
+  SFDP_API_REVERSE = 1,
+};
+
+autoreply define sfdp_gateway_set_geneve_output
+{
+  u32 client_index;
+  u32 context;
+
+  u32 tenant_id;
+  u32 output_tenant_id;
+  vl_api_address_t src;
+  vl_api_address_t dst;
+  u16 src_port;
+  u16 dst_port;
+  u8 static_mac;
+  vl_api_mac_address_t src_mac;
+  vl_api_mac_address_t dst_mac;
+  vl_api_sfdp_session_direction_t dir;
+};
+
+autoreply define sfdp_gateway_geneve_input_enable_disable
+{
+  u32 client_index;
+  u32 context;
+
+  vl_api_interface_index_t sw_if_index;
+  u8 is_enable;
+};
diff --git a/src/plugins/sfdp_services/geneve/gateway.c b/src/plugins/sfdp_services/geneve/gateway.c
new file mode 100644 (file)
index 0000000..719210c
--- /dev/null
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#define _GNU_SOURCE
+#include <sys/mman.h>
+
+#include <sfdp_services/geneve/gateway.h>
+
+#include <vnet/plugin/plugin.h>
+#include <vnet/vnet.h>
+
+#include <vlibapi/api.h>
+#include <vlibmemory/api.h>
+
+gw_main_t gateway_main;
+
+static void
+gateway_init_main_if_needed (gw_main_t *gm)
+{
+  static u32 done = 0;
+  sfdp_main_t *sfdp = &sfdp_main;
+
+  if (done)
+    return;
+
+  vec_validate (gm->output, sfdp_num_sessions () << 1);
+  vec_validate (gm->tenants, 1ULL << sfdp->log2_tenants);
+
+  done = 1;
+}
+
+static clib_error_t *
+gateway_init (vlib_main_t *vm)
+{
+  return 0;
+}
+
+void
+gw_enable_disable_geneve_input (gw_enable_disable_geneve_input_args_t *args)
+{
+  gw_main_t *gm = &gateway_main;
+  int rv = 0;
+  gateway_init_main_if_needed (gm);
+  rv = vnet_feature_enable_disable ("ip4-unicast", "sfdp-geneve-input",
+                                   args->sw_if_index, args->enable_disable, 0,
+                                   0);
+  args->rv = rv;
+  if (rv)
+    args->err = clib_error_return (
+      0, "Failed vnet_feature_enable_disable with error %d : %U", rv,
+      format_vnet_api_errno, rv);
+  else
+    args->err = 0;
+}
+
+void
+gw_set_geneve_output (gw_set_geneve_output_args_t *args)
+{
+  gw_main_t *gm = &gateway_main;
+  sfdp_main_t *sfdp = &sfdp_main;
+  sfdp_tenant_t *vt;
+  gw_tenant_t *gt;
+  clib_bihash_kv_8_8_t kv = {};
+  u8 dir = !!args->direction;
+  kv.key = args->tenant_id;
+  if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+    {
+      args->rv = -1;
+      args->err =
+       clib_error_return (0, "tenant-id %d not found", args->tenant_id);
+      return;
+    }
+  vt = sfdp_tenant_at_index (sfdp, kv.value);
+  gt = gw_tenant_at_index (gm, kv.value);
+
+  /* Caching tenant id in gt */
+  gt->output_tenant_id =
+    args->output_tenant_id == ~0 ? vt->tenant_id : args->output_tenant_id;
+  gt->flags = GW_TENANT_F_OUTPUT_DATA_SET;
+  gt->geneve_src_ip[dir] = args->src_addr;
+  gt->geneve_dst_ip[dir] = args->dst_addr;
+  gt->geneve_src_port[dir] = args->src_port;
+  gt->geneve_dst_port[dir] = args->dst_port;
+  if (args->static_mac)
+    {
+      gt->flags |= GW_TENANT_F_STATIC_MAC;
+      gt->src_mac[dir] = args->src_mac;
+      gt->dst_mac[dir] = args->dst_mac;
+    }
+  args->rv = 0;
+  args->err = 0;
+}
+
+VLIB_INIT_FUNCTION (gateway_init);
+VLIB_PLUGIN_REGISTER () = {
+  .version = SFDP_GW_PLUGIN_BUILD_VER,
+  .description = "sfdp Gateway Plugin",
+};
\ No newline at end of file
diff --git a/src/plugins/sfdp_services/geneve/gateway.h b/src/plugins/sfdp_services/geneve/gateway.h
new file mode 100644 (file)
index 0000000..49fd93e
--- /dev/null
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef __included_gateway_h__
+#define __included_gateway_h__
+
+#include <vnet/vnet.h>
+#include <vnet/ip/ip.h>
+#include <vnet/ethernet/ethernet.h>
+
+#include <vppinfra/hash.h>
+#include <vppinfra/error.h>
+#include <vppinfra/elog.h>
+
+#include <vppinfra/bihash_24_8.h>
+#include <vppinfra/bihash_8_8.h>
+
+#include <vppinfra/bihash_template.h>
+
+#include <vnet/sfdp/sfdp.h>
+
+#define foreach_gw_tenant_flag                                                \
+  _ (OUTPUT_DATA_SET, "output-data-set", 0)                                   \
+  _ (STATIC_MAC, "static-mac", 1)
+
+typedef enum
+{
+#define _(a, b, c) GW_TENANT_F_##a = (1 << (c)),
+  foreach_gw_tenant_flag
+#undef _
+} gw_tenant_flags_t;
+
+typedef struct
+{
+  /* Here goes the geneve rewrite */
+  session_version_t session_version;
+  u16 encap_size;
+  u8 encap_data[124];
+} gw_geneve_output_data_t;
+STATIC_ASSERT (sizeof (gw_geneve_output_data_t) == 128, "");
+
+typedef struct
+{
+  u32 output_tenant_id;
+  u32 flags;
+
+  /* Geneve output spec for forward/reverse packets */
+  ip4_address_t geneve_src_ip[SFDP_FLOW_F_B_N];
+  ip4_address_t geneve_dst_ip[SFDP_FLOW_F_B_N];
+  u16 geneve_src_port[SFDP_FLOW_F_B_N];
+  u16 geneve_dst_port[SFDP_FLOW_F_B_N];
+  mac_address_t src_mac[SFDP_FLOW_F_B_N];
+  mac_address_t dst_mac[SFDP_FLOW_F_B_N];
+
+} gw_tenant_t;
+
+typedef struct
+{
+  /* pool of tenants */
+  gw_tenant_t *tenants;
+  gw_geneve_output_data_t *output; /* by flow_index */
+  u16 msg_id_base;
+} gw_main_t;
+
+typedef struct
+{
+  int rv;
+  clib_error_t *err;
+  u32 sw_if_index;
+  u8 enable_disable;
+} gw_enable_disable_geneve_input_args_t;
+
+typedef struct
+{
+  int rv;
+  clib_error_t *err;
+  u32 tenant_id;
+  ip4_address_t src_addr;
+  ip4_address_t dst_addr;
+  u16 src_port; /*network order*/
+  u16 dst_port; /*network order*/
+  u8 direction; /* 0 is forward, 1 is reverse */
+  u8 static_mac;
+  u32 output_tenant_id; /* ~0 means output on the same tenant as input */
+  mac_address_t src_mac;
+  mac_address_t dst_mac;
+} gw_set_geneve_output_args_t;
+
+extern gw_main_t gateway_main;
+
+static_always_inline gw_tenant_t *
+gw_tenant_at_index (gw_main_t *gm, u32 idx)
+{
+  return vec_elt_at_index (gm->tenants, idx);
+}
+
+void
+gw_enable_disable_geneve_input (gw_enable_disable_geneve_input_args_t *args);
+void gw_set_geneve_output (gw_set_geneve_output_args_t *args);
+
+#define SFDP_GW_PLUGIN_BUILD_VER "1.0"
+
+#endif /* __included_gateway_h__ */
diff --git a/src/plugins/sfdp_services/geneve/geneve_input/node.c b/src/plugins/sfdp_services/geneve/geneve_input/node.c
new file mode 100644 (file)
index 0000000..6132a01
--- /dev/null
@@ -0,0 +1,164 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <vnet/feature/feature.h>
+#include <sfdp_services/geneve/gateway.h>
+#include <vnet/sfdp/common.h>
+typedef struct
+{
+  u32 next_index;
+  u32 sw_if_index;
+} sfdp_geneve_input_trace_t;
+
+static u8 *
+format_sfdp_geneve_input_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 *);
+  sfdp_geneve_input_trace_t *t = va_arg (*args, sfdp_geneve_input_trace_t *);
+
+  s = format (s, "snort-enq: sw_if_index %d, next index %d\n", t->sw_if_index,
+             t->next_index);
+
+  return s;
+}
+
+#define foreach_sfdp_geneve_input_next                                        \
+  _ (LOOKUP_IP4, "sfdp-lookup-ip4")                                           \
+  _ (LOOKUP_IP6, "sfdp-lookup-ip6")
+#define foreach_sfdp_geneve_input_error _ (NOERROR, "No error")
+
+typedef enum
+{
+#define _(sym, str) SFDP_GENEVE_INPUT_ERROR_##sym,
+  foreach_sfdp_geneve_input_error
+#undef _
+    SFDP_GENEVE_INPUT_N_ERROR,
+} sfdp_geneve_input_error_t;
+
+static char *sfdp_geneve_input_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_geneve_input_error
+#undef _
+};
+
+typedef enum
+{
+#define _(s, n) SFDP_GENEVE_INPUT_NEXT_##s,
+  foreach_sfdp_geneve_input_next
+#undef _
+    SFDP_GENEVE_INPUT_N_NEXT
+} sfdp_geneve_input_next_t;
+
+static_always_inline uword
+sfdp_geneve_input_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
+                         vlib_frame_t *frame)
+{
+  /*
+   * use VNI as tenant ID
+   * tenant_id -> tenant index
+   * drop unknown tenants
+   * store tenant_id into opaque1
+   * advance current data to beginning of IP packet
+   */
+  sfdp_main_t *sfdp = &sfdp_main;
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b;
+  vlib_combined_counter_main_t *cm =
+    &sfdp->tenant_data_ctr[SFDP_TENANT_DATA_COUNTER_INCOMING];
+  sfdp_tenant_t *tenant;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+  u16 next_indices[VLIB_FRAME_SIZE], *current_next;
+  u32 thread_index = vlib_get_thread_index ();
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+  b = bufs;
+  current_next = next_indices;
+
+  while (n_left)
+    {
+      ip4_header_t *ip4 = vlib_buffer_get_current (b[0]);
+      udp_header_t *udp;
+      u32 *gnv;
+      u32 tenant_id;
+      u16 tenant_idx;
+      clib_bihash_kv_8_8_t kv = {};
+      u16 off = 0;
+      u32 len = vlib_buffer_length_in_chain (vm, b[0]);
+      u16 ethtype;
+      if (ip4->protocol != IP_PROTOCOL_UDP)
+       {
+         vnet_feature_next_u16 (current_next, b[0]);
+         goto end_of_packet;
+       }
+      off += ip4_header_bytes (ip4);
+      udp = (udp_header_t *) (b[0]->data + b[0]->current_data + off);
+      if (udp->dst_port != 0xC117)
+       {
+         vnet_feature_next_u16 (current_next, b[0]);
+         goto end_of_packet;
+       }
+      off += sizeof (udp[0]);
+      gnv = (u32 *) (b[0]->data + b[0]->current_data + off);
+
+      /* Extract VNI */
+      tenant_id = clib_net_to_host_u32 (gnv[1]) >> 8;
+      kv.key = (u64) tenant_id;
+      if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv))
+       {
+         /* Not found */
+         vnet_feature_next_u16 (current_next, b[0]);
+         goto end_of_packet;
+       }
+
+      /* Store tenant_id as flow_id (to simplify the future lookup) */
+      tenant_idx = kv.value;
+      tenant = sfdp_tenant_at_index (sfdp, tenant_idx);
+      b[0]->flow_id = tenant->context_id;
+      sfdp_buffer (b[0])->tenant_index = tenant_idx;
+      ethtype = *(u16 *) (b[0]->data + b[0]->current_data + off + 20);
+      ethtype = clib_net_to_host_u16 (ethtype);
+      current_next[0] = ethtype == ETHERNET_TYPE_IP6 ?
+                         SFDP_GENEVE_INPUT_NEXT_LOOKUP_IP6 :
+                         SFDP_GENEVE_INPUT_NEXT_LOOKUP_IP4;
+      off +=
+       8 /* geneve header no options */ + 14 /* ethernet header, no tag*/;
+      vlib_buffer_advance (b[0], off);
+      vlib_increment_combined_counter (cm, thread_index, tenant_idx, 1,
+                                      len - off);
+    end_of_packet:
+      b += 1;
+      current_next += 1;
+      n_left -= 1;
+    }
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+  return frame->n_vectors;
+}
+
+VLIB_NODE_FN (sfdp_geneve_input_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  return sfdp_geneve_input_inline (vm, node, frame);
+}
+
+VLIB_REGISTER_NODE (sfdp_geneve_input_node) = {
+  .name = "sfdp-geneve-input",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_geneve_input_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_geneve_input_error_strings),
+  .error_strings = sfdp_geneve_input_error_strings,
+  .n_next_nodes = SFDP_GENEVE_INPUT_N_NEXT,
+  .next_nodes = {
+          [SFDP_GENEVE_INPUT_NEXT_LOOKUP_IP4] = "sfdp-lookup-ip4",
+          [SFDP_GENEVE_INPUT_NEXT_LOOKUP_IP6] = "sfdp-lookup-ip6",
+  },
+};
+
+VNET_FEATURE_INIT (sfdp_geneve_input_feat, static) = {
+  .arc_name = "ip4-unicast",
+  .node_name = "sfdp-geneve-input",
+};
diff --git a/src/plugins/sfdp_services/geneve/geneve_output/node.c b/src/plugins/sfdp_services/geneve/geneve_output/node.c
new file mode 100644 (file)
index 0000000..6b3f52e
--- /dev/null
@@ -0,0 +1,313 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vlib/vlib.h>
+#include <sfdp_services/geneve/gateway.h>
+#include <vnet/sfdp/common.h>
+#include <vnet/sfdp/service.h>
+#define SFDP_GENEVE_OPTION_CLASS          ((u16) 0xDEAD)
+#define SFDP_GENEVE_OPTION_TYPE_SESSION_ID ((u8) 0xBE)
+#define SFDP_GENEVE_OPTION_SESSION_ID_SIZE ((u8) 0x2)
+#define SFDP_GENEVE_OPTION_SESSION_ID_FIRST_WORD                              \
+  (SFDP_GENEVE_OPTION_CLASS << 16) |                                          \
+    (SFDP_GENEVE_OPTION_TYPE_SESSION_ID << 8) |                               \
+    (SFDP_GENEVE_OPTION_SESSION_ID_SIZE << 0)
+#define SFDP_GENEVE_OPTION_LEN (12)
+#define SFDP_GENEVE_TOTAL_LEN  (8 + SFDP_GENEVE_OPTION_LEN)
+
+#define foreach_sfdp_geneve_output_error _ (NO_OUTPUT, "no output data")
+
+typedef enum
+{
+#define _(sym, str) SFDP_GENEVE_OUTPUT_ERROR_##sym,
+  foreach_sfdp_geneve_output_error
+#undef _
+    SFDP_GENEVE_OUTPUT_N_ERROR,
+} sfdp_geneve_output_error_t;
+
+static char *sfdp_geneve_output_error_strings[] = {
+#define _(sym, string) string,
+  foreach_sfdp_geneve_output_error
+#undef _
+};
+
+#define foreach_sfdp_geneve_output_next                                       \
+  _ (DROP, "error-drop")                                                      \
+  _ (IP4_LOOKUP, "ip4-lookup")
+
+typedef enum
+{
+#define _(n, x) SFDP_GENEVE_OUTPUT_NEXT_##n,
+  foreach_sfdp_geneve_output_next
+#undef _
+    SFDP_GENEVE_OUTPUT_N_NEXT
+} sfdp_geneve_output_next_t;
+
+typedef struct
+{
+  u32 flow_id;
+  u16 encap_size;
+  u8 encap_data[124];
+} sfdp_geneve_output_trace_t;
+
+static u8 *
+format_sfdp_geneve_output_trace (u8 *s, va_list *args)
+{
+  vlib_main_t __clib_unused *vm = va_arg (*args, vlib_main_t *);
+  vlib_node_t __clib_unused *node = va_arg (*args, vlib_node_t *);
+  sfdp_geneve_output_trace_t *t = va_arg (*args, sfdp_geneve_output_trace_t *);
+  u32 indent = format_get_indent (s);
+  s =
+    format (s, "sfdp-geneve_output: flow-id %u (session %u, %s)\n", t->flow_id,
+           t->flow_id >> 1, t->flow_id & 0x1 ? "reverse" : "forward");
+  s = format (s, "%U", format_white_space, indent);
+  s = format (s, "encap-data: %U", format_hex_bytes, t->encap_data,
+             t->encap_size);
+  return s;
+}
+
+static_always_inline int
+sfdp_geneve_output_load_data (gw_main_t *gm,
+                             gw_geneve_output_data_t *geneve_out,
+                             sfdp_session_t *session, vlib_buffer_t *b)
+{
+  u32 tenant_idx = sfdp_buffer (b)->tenant_index;
+  gw_tenant_t *tenant = gw_tenant_at_index (gm, tenant_idx);
+  u8 direction = b->flow_id & 0x1;
+  ip4_header_t *ip4 = (void *) geneve_out->encap_data;
+  udp_header_t *udp;
+  ethernet_header_t *eth;
+  u32 *gnv;
+  if (PREDICT_FALSE (!(tenant->flags & GW_TENANT_F_OUTPUT_DATA_SET)))
+    return -1;
+  geneve_out->session_version = session->session_version;
+  geneve_out->encap_size = 0;
+  /* Start with IP header */
+  ip4->src_address = tenant->geneve_src_ip[direction];
+  ip4->dst_address = tenant->geneve_dst_ip[direction];
+  ip4->protocol = IP_PROTOCOL_UDP;
+  ip4->ip_version_and_header_length = 0x45;
+  ip4->tos = IP_DSCP_CS0;
+  ip4->ttl = 0xff;
+  ip4->flags_and_fragment_offset = 0;
+  ip4->length = 0;
+  ip4->checksum = ip4_header_checksum (ip4);
+  ip4->length = /* stored in host byte order, incremented and swapped
+                  later */
+    sizeof (ip4_header_t) + sizeof (udp_header_t) + SFDP_GENEVE_TOTAL_LEN +
+    sizeof (ethernet_header_t);
+  geneve_out->encap_size += sizeof (*ip4);
+  udp = (void *) (geneve_out->encap_data + geneve_out->encap_size);
+  udp->src_port = tenant->geneve_src_port[direction];
+  udp->dst_port = tenant->geneve_dst_port[direction];
+  udp->checksum = 0;
+  udp->length = /* stored in host byte order, incremented and swapped
+                  later */
+    sizeof (udp_header_t) + SFDP_GENEVE_TOTAL_LEN + sizeof (ethernet_header_t);
+  geneve_out->encap_size += sizeof (*udp);
+  gnv = (void *) (geneve_out->encap_data + geneve_out->encap_size);
+  gnv[0] =
+    // Not sure if 0x0C or 0x03 (number of bytes or of 4B-words???)
+    clib_host_to_net_u32 (0x03006558); /*3 words of option geneve version 0*/
+  gnv[1] = clib_host_to_net_u32 (tenant->output_tenant_id << 8);
+  gnv[2] = clib_host_to_net_u32 (SFDP_GENEVE_OPTION_SESSION_ID_FIRST_WORD);
+  gnv[3] =
+    clib_host_to_net_u32 (session->session_id >> 32); /* session id high  */
+  gnv[4] = clib_host_to_net_u32 (session->session_id |
+                                direction); /* session id low */
+  geneve_out->encap_size += SFDP_GENEVE_TOTAL_LEN;
+  eth = (void *) (geneve_out->encap_data + geneve_out->encap_size);
+  if (tenant->flags & GW_TENANT_F_STATIC_MAC)
+    {
+      clib_memcpy_fast (eth->src_address, tenant->src_mac[direction].bytes,
+                       sizeof (mac_address_t));
+      clib_memcpy_fast (eth->dst_address, tenant->dst_mac[direction].bytes,
+                       sizeof (mac_address_t));
+      eth->type = clib_host_to_net_u16 (ETHERNET_TYPE_IP4);
+    }
+  else
+    clib_memcpy_fast (eth, b->data + b->current_data - sizeof (*eth),
+                     sizeof (*eth));
+
+  geneve_out->encap_size += sizeof (*eth);
+  ASSERT (geneve_out->encap_size < sizeof (geneve_out->encap_data));
+  return 0;
+}
+
+static_always_inline void
+geneve_output_rewrite_one (vlib_main_t *vm, vlib_node_runtime_t *node,
+                          gw_main_t *gm, vlib_combined_counter_main_t *cm,
+                          gw_geneve_output_data_t *geneve_out,
+                          sfdp_session_t *session, u32 thread_index,
+                          u32 session_idx, u16 *to_next, vlib_buffer_t **b)
+{
+  if (PREDICT_FALSE (
+       geneve_out->session_version != session->session_version &&
+       sfdp_geneve_output_load_data (gm, geneve_out, session, b[0])))
+    {
+      to_next[0] = SFDP_GENEVE_OUTPUT_NEXT_DROP;
+      vlib_node_increment_counter (vm, node->node_index,
+                                  SFDP_GENEVE_OUTPUT_ERROR_NO_OUTPUT, 1);
+    }
+  else
+    {
+      ip4_header_t *ip;
+      udp_header_t *udp;
+      ip_csum_t csum;
+      u8 *data;
+      u16 orig_len = vlib_buffer_length_in_chain (vm, b[0]);
+      b[0]->flags |=
+       (VNET_BUFFER_F_IS_IP4 | VNET_BUFFER_F_L3_HDR_OFFSET_VALID |
+        VNET_BUFFER_F_L4_HDR_OFFSET_VALID);
+      vnet_buffer (b[0])->oflags |=
+       VNET_BUFFER_OFFLOAD_F_UDP_CKSUM | VNET_BUFFER_OFFLOAD_F_IP_CKSUM;
+      vlib_buffer_advance (b[0], -geneve_out->encap_size);
+      data = vlib_buffer_get_current (b[0]);
+      vnet_buffer (b[0])->l3_hdr_offset = b[0]->current_data;
+      vnet_buffer (b[0])->l4_hdr_offset =
+       b[0]->current_data + sizeof (ip4_header_t);
+      clib_memcpy_fast (data, geneve_out->encap_data, geneve_out->encap_size);
+      /* fixup */
+      ip = (void *) data;
+      ip->length = clib_net_to_host_u16 (ip->length + orig_len);
+      csum = ip->checksum;
+      csum = ip_csum_update (csum, 0, ip->length, ip4_header_t, length);
+      ip->checksum = ip_csum_fold (csum);
+      udp = (void *) (data + sizeof (ip4_header_t));
+      udp->length = clib_net_to_host_u16 (udp->length + orig_len);
+      to_next[0] = SFDP_GENEVE_OUTPUT_NEXT_IP4_LOOKUP;
+      vlib_increment_combined_counter (cm, thread_index, session->tenant_idx,
+                                      1, orig_len);
+    }
+}
+
+#define vlib_prefetch_buffer_data_with_offset(b, type, offset)                \
+  CLIB_PREFETCH (b->data + b->current_data + (offset), CLIB_CACHE_LINE_BYTES, \
+                type)
+VLIB_NODE_FN (sfdp_geneve_output_node)
+(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
+{
+  vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
+  gw_main_t *gm = &gateway_main;
+  sfdp_main_t *sfdp = &sfdp_main;
+  vlib_combined_counter_main_t *cm =
+    &sfdp->tenant_data_ctr[SFDP_TENANT_DATA_COUNTER_OUTGOING];
+  u32 thread_index = vm->thread_index;
+
+  u16 next_indices[VLIB_FRAME_SIZE], *to_next = next_indices;
+  u32 *from = vlib_frame_vector_args (frame);
+  u32 n_left = frame->n_vectors;
+
+  vlib_get_buffers (vm, from, bufs, n_left);
+
+  /* Pipeline load buffer data -> load session_data + geneve_output_data
+   * ->process */
+#define SFDP_PREFETCH_SIZE 2
+  while (n_left >= SFDP_PREFETCH_SIZE)
+    {
+      u32 si[SFDP_PREFETCH_SIZE * 2];
+      sfdp_session_t *session[SFDP_PREFETCH_SIZE];
+      gw_geneve_output_data_t *geneve_out[SFDP_PREFETCH_SIZE];
+      if (n_left >= SFDP_PREFETCH_SIZE * 3)
+       {
+         for (int i = 0; i < SFDP_PREFETCH_SIZE; i++)
+           {
+             vlib_prefetch_buffer_header (b[2 * SFDP_PREFETCH_SIZE + i],
+                                          STORE);
+             vlib_prefetch_buffer_data_with_offset (
+               b[2 * SFDP_PREFETCH_SIZE + i], STORE, -64);
+           }
+       }
+      if (n_left >= SFDP_PREFETCH_SIZE * 2)
+       {
+         for (int i = 0; i < SFDP_PREFETCH_SIZE; i++)
+           {
+             si[SFDP_PREFETCH_SIZE + i] = sfdp_session_from_flow_index (
+               b[SFDP_PREFETCH_SIZE + i]->flow_id);
+             CLIB_PREFETCH (sfdp->sessions + si[SFDP_PREFETCH_SIZE + i],
+                            CLIB_CACHE_LINE_BYTES, LOAD);
+             CLIB_PREFETCH (gm->output + b[SFDP_PREFETCH_SIZE + i]->flow_id,
+                            2 * CLIB_CACHE_LINE_BYTES, LOAD);
+           }
+       }
+      for (int i = 0; i < SFDP_PREFETCH_SIZE; i++)
+       {
+         si[i] = sfdp_session_from_flow_index (b[i]->flow_id);
+         session[i] = sfdp_session_at_index (si[i]);
+         geneve_out[i] = vec_elt_at_index (gm->output, b[i]->flow_id);
+         geneve_output_rewrite_one (vm, node, gm, cm, geneve_out[i],
+                                    session[i], thread_index, si[i],
+                                    to_next + i, b + i);
+       }
+      to_next += SFDP_PREFETCH_SIZE;
+      b += SFDP_PREFETCH_SIZE;
+      n_left -= SFDP_PREFETCH_SIZE;
+    }
+
+  while (n_left)
+    {
+      u32 session_idx = sfdp_session_from_flow_index (b[0]->flow_id);
+      sfdp_session_t *session = sfdp_session_at_index (session_idx);
+      gw_geneve_output_data_t *geneve_out =
+       vec_elt_at_index (gm->output, b[0]->flow_id);
+
+      geneve_output_rewrite_one (vm, node, gm, cm, geneve_out, session,
+                                thread_index, session_idx, to_next, b);
+      to_next++;
+      b++;
+      n_left--;
+    }
+
+  vlib_buffer_enqueue_to_next (vm, node, from, next_indices, frame->n_vectors);
+
+  if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
+    {
+      int i;
+      n_left = frame->n_vectors;
+      b = bufs;
+      for (i = 0; i < n_left; i++)
+       {
+         if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
+           {
+             sfdp_geneve_output_trace_t *t =
+               vlib_add_trace (vm, node, b[0], sizeof (*t));
+             t->flow_id = b[0]->flow_id;
+             t->encap_size = gm->output[b[0]->flow_id].encap_size;
+             clib_memcpy_fast (t->encap_data,
+                               gm->output[b[0]->flow_id].encap_data,
+                               gm->output[b[0]->flow_id].encap_size);
+             b++;
+           }
+         else
+           break;
+       }
+    }
+  return frame->n_vectors;
+}
+
+VLIB_REGISTER_NODE (sfdp_geneve_output_node) = {
+  .name = "sfdp-geneve-output",
+  .vector_size = sizeof (u32),
+  .format_trace = format_sfdp_geneve_output_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = ARRAY_LEN (sfdp_geneve_output_error_strings),
+  .error_strings = sfdp_geneve_output_error_strings,
+
+  .n_next_nodes = SFDP_GENEVE_OUTPUT_N_NEXT,
+  .next_nodes = {
+#define _(n, x) [SFDP_GENEVE_OUTPUT_NEXT_##n] = x,
+          foreach_sfdp_geneve_output_next
+#undef _
+  }
+
+};
+
+SFDP_SERVICE_DEFINE (geneve_output) = {
+  .node_name = "sfdp-geneve-output",
+  .runs_before = SFDP_SERVICES (0),
+  .runs_after = SFDP_SERVICES ("sfdp-drop", "sfdp-l4-lifecycle",
+                              "sfdp-tcp-check"),
+  .is_terminal = 1
+};
\ No newline at end of file
similarity index 86%
rename from src/vnet/sfdp/lookup/full_reass_node.c
rename to src/plugins/sfdp_services/reass/full_reass_node.c
index bbd3e20..50ea597 100644 (file)
@@ -1,23 +1,12 @@
-/*
- * Copyright (c) 2024 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.
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
  */
 
 #include <vlib/vlib.h>
 #include <vnet/vnet.h>
 #include <vnet/sfdp/common.h>
 #include <vnet/sfdp/sfdp.h>
-#include <vnet/sfdp/lookup/reass.h>
+#include <sfdp_services/lookup/reass.h>
 
 typedef struct
 {
similarity index 71%
rename from src/vnet/sfdp/lookup/reass.c
rename to src/plugins/sfdp_services/reass/reass.c
index a1966a2..4270a68 100644 (file)
@@ -1,24 +1,13 @@
-/*
- * Copyright (c) 2024 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.
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
  */
 
 #include <vnet/ip/reass/ip4_full_reass.h>
 #include <vnet/ip/reass/ip6_full_reass.h>
 #include <vnet/ip/reass/ip4_sv_reass.h>
 #include <vnet/ip/reass/ip6_sv_reass.h>
-#include <vnet/sfdp/lookup/reass.h>
 #include <vnet/sfdp/sfdp.h>
+#include <sfdp_services/reass/reass.h>
 
 sfdp_reass_main_t sfdp_reass_main;
 
@@ -32,18 +21,19 @@ sfdp_reass_main_init (vlib_main_t *vm)
   vrm->ip6_sv_reass_next_index =
     ip6_sv_reass_custom_context_register_next_node (
       sfdp_lookup_ip6_node.index);
-  vrm->ip4_full_reass_next_index =
+  /*vrm->ip4_full_reass_next_index =
     ip4_full_reass_custom_context_register_next_node (
       sfdp_lookup_ip4_node.index);
   vrm->ip6_full_reass_next_index =
     ip6_full_reass_custom_context_register_next_node (
       sfdp_lookup_ip6_node.index);
   vrm->ip4_full_reass_err_next_index = ip4_full_reass_get_error_next_index ();
-  vrm->ip6_full_reass_err_next_index = ip6_full_reass_get_error_next_index ();
+  vrm->ip6_full_reass_err_next_index = ip6_full_reass_get_error_next_index
+  ();*/
   return 0;
 }
 
-void
+/*void
 sfdp_ip4_full_reass_custom_context_register_next_node (u16 node_index)
 {
   sfdp_reass_main.ip4_full_reass_next_index =
@@ -70,5 +60,5 @@ sfdp_ip6_full_reass_custom_context_register_next_err_node (u16 node_index)
   sfdp_reass_main.ip6_full_reass_err_next_index =
     ip6_full_reass_custom_context_register_next_node (node_index);
 }
-
+*/
 VLIB_INIT_FUNCTION (sfdp_reass_main_init);
similarity index 85%
rename from src/vnet/sfdp/lookup/sv_reass_node.c
rename to src/plugins/sfdp_services/reass/sv_reass_node.c
index e8d6ce8..fead7b3 100644 (file)
@@ -1,23 +1,12 @@
-/*
- * Copyright (c) 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.
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2025 Cisco Systems, Inc.
  */
 
 #include <vlib/vlib.h>
 #include <vnet/vnet.h>
 #include <vnet/sfdp/common.h>
 #include <vnet/sfdp/sfdp.h>
-#include <vnet/sfdp/lookup/reass.h>
+#include <sfdp_services/reass/reass.h>
 
 typedef struct
 {