session: rules tables

This introduces 5-tuple lookup tables that may be used to implement
custom session layer actions at connection establishment time (session
layer perspective).

The rules table build mask-match-action lookup trees that for a given
5-tuple key return the action for the first longest match. If rules
overlap, ordering is established by tuple longest match with the
following descending priority: remote ip, local ip, remote port, local
port.

At this time, the only match action supported is to forward packets to
the application identified by the action.

Change-Id: Icbade6fac720fa3979820d50cd7d6137f8b635c3
Signed-off-by: Florin Coras <fcoras@cisco.com>
diff --git a/src/vnet/session/session_test.c b/src/vnet/session/session_test.c
index 986837c..bdd4f05 100644
--- a/src/vnet/session/session_test.c
+++ b/src/vnet/session/session_test.c
@@ -17,6 +17,7 @@
 #include <vnet/session/application_interface.h>
 #include <vnet/session/application.h>
 #include <vnet/session/session.h>
+#include <vnet/session/session_rules_table.h>
 
 #define SESSION_TEST_I(_cond, _comment, _args...)		\
 ({								\
@@ -438,6 +439,463 @@
   return 0;
 }
 
+static int
+session_test_rule_table (vlib_main_t * vm, unformat_input_t * input)
+{
+  session_rules_table_t _srt, *srt = &_srt;
+  u16 lcl_port = 1234, rmt_port = 4321;
+  u32 action_index = 1, res;
+  ip4_address_t lcl_lkup, rmt_lkup;
+  clib_error_t *error;
+  int verbose = 0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "verbose"))
+	verbose = 1;
+      else
+	{
+	  vlib_cli_output (vm, "parse error: '%U'", format_unformat_error,
+			   input);
+	  return -1;
+	}
+    }
+
+  memset (srt, 0, sizeof (*srt));
+  session_rules_table_init (srt);
+
+  ip4_address_t lcl_ip = {
+    .as_u32 = clib_host_to_net_u32 (0x01020304),
+  };
+  ip4_address_t rmt_ip = {
+    .as_u32 = clib_host_to_net_u32 (0x05060708),
+  };
+  ip4_address_t lcl_ip2 = {
+    .as_u32 = clib_host_to_net_u32 (0x02020202),
+  };
+  ip4_address_t rmt_ip2 = {
+    .as_u32 = clib_host_to_net_u32 (0x06060606),
+  };
+  ip4_address_t lcl_ip3 = {
+    .as_u32 = clib_host_to_net_u32 (0x03030303),
+  };
+  ip4_address_t rmt_ip3 = {
+    .as_u32 = clib_host_to_net_u32 (0x07070707),
+  };
+  fib_prefix_t lcl_pref = {
+    .fp_addr.ip4.as_u32 = lcl_ip.as_u32,
+    .fp_len = 16,
+    .fp_proto = FIB_PROTOCOL_IP4,
+  };
+  fib_prefix_t rmt_pref = {
+    .fp_addr.ip4.as_u32 = rmt_ip.as_u32,
+    .fp_len = 16,
+    .fp_proto = FIB_PROTOCOL_IP4,
+  };
+
+  session_rule_table_add_del_args_t args = {
+    .lcl = lcl_pref,
+    .rmt = rmt_pref,
+    .lcl_port = lcl_port,
+    .rmt_port = rmt_port,
+    .action_index = action_index++,
+    .is_add = 1,
+  };
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/16 1234 5.6.7.8/16 4321 action %d",
+		action_index - 1);
+
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 1),
+		"Lookup 1.2.3.4 1234 5.6.7.8 4321, action should " "be 1: %d",
+		res);
+
+  /*
+   * Add 1.2.3.4/24 1234 5.6.7.8/16 4321 and 1.2.3.4/24 1234 5.6.7.8/24 4321
+   */
+  args.lcl.fp_addr.ip4 = lcl_ip;
+  args.lcl.fp_len = 24;
+  args.action_index = action_index++;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/24 1234 5.6.7.8/16 4321 action %d",
+		action_index - 1);
+  args.rmt.fp_addr.ip4 = rmt_ip;
+  args.rmt.fp_len = 24;
+  args.action_index = action_index++;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/24 1234 5.6.7.8/24 4321 action %d",
+		action_index - 1);
+
+  /*
+   * Add 2.2.2.2/24 1234 6.6.6.6/16 4321 and 3.3.3.3/24 1234 7.7.7.7/16 4321
+   */
+  args.lcl.fp_addr.ip4 = lcl_ip2;
+  args.lcl.fp_len = 24;
+  args.rmt.fp_addr.ip4 = rmt_ip2;
+  args.rmt.fp_len = 16;
+  args.action_index = action_index++;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 2.2.2.2/24 1234 6.6.6.6/16 4321 action %d",
+		action_index - 1);
+  args.lcl.fp_addr.ip4 = lcl_ip3;
+  args.rmt.fp_addr.ip4 = rmt_ip3;
+  args.action_index = action_index++;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 3.3.3.3/24 1234 7.7.7.7/16 4321 action %d",
+		action_index - 1);
+
+  /*
+   * Add again 3.3.3.3/24 1234 7.7.7.7/16 4321
+   */
+  args.lcl.fp_addr.ip4 = lcl_ip3;
+  args.rmt.fp_addr.ip4 = rmt_ip3;
+  args.action_index = action_index++;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "overwrite 3.3.3.3/24 1234 7.7.7.7/16 4321 "
+		"action %d", action_index - 1);
+
+  /*
+   * Lookup 1.2.3.4/32 1234 5.6.7.8/32 4321, 1.2.2.4/32 1234 5.6.7.9/32 4321
+   * and  3.3.3.3 1234 7.7.7.7 4321
+   */
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 3),
+		"Lookup 1.2.3.4 1234 5.6.7.8 4321 action " "should be 3: %d",
+		res);
+
+  lcl_lkup.as_u32 = clib_host_to_net_u32 (0x01020204);
+  rmt_lkup.as_u32 = clib_host_to_net_u32 (0x05060709);
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_lkup,
+				 &rmt_lkup, lcl_port, rmt_port);
+  SESSION_TEST ((res == 1),
+		"Lookup 1.2.2.4 1234 5.6.7.9 4321, action " "should be 1: %d",
+		res);
+
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip3, &rmt_ip3,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 6),
+		"Lookup 3.3.3.3 1234 7.7.7.7 4321, action "
+		"should be 6 (updated): %d", res);
+
+  /*
+   * Add 1.2.3.4/24 * 5.6.7.8/24 *
+   * Lookup 1.2.3.4 1234 5.6.7.8 4321 and 1.2.3.4 1235 5.6.7.8 4321
+   */
+  args.lcl.fp_addr.ip4 = lcl_ip;
+  args.rmt.fp_addr.ip4 = rmt_ip;
+  args.lcl.fp_len = 24;
+  args.rmt.fp_len = 24;
+  args.lcl_port = 0;
+  args.rmt_port = 0;
+  args.action_index = action_index++;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/24 * 5.6.7.8/24 * action %d",
+		action_index - 1);
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 7),
+		"Lookup 1.2.3.4 1234 5.6.7.8 4321, action should"
+		" be 7 (lpm dst): %d", res);
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port + 1, rmt_port);
+  SESSION_TEST ((res == 7),
+		"Lookup 1.2.3.4 1235 5.6.7.8 4321, action should " "be 7: %d",
+		res);
+
+  /*
+   * Del 1.2.3.4/24 * 5.6.7.8/24 *
+   * Add 1.2.3.4/16 * 5.6.7.8/16 * and 1.2.3.4/24 1235 5.6.7.8/24 4321
+   * Lookup 1.2.3.4 1234 5.6.7.8 4321, 1.2.3.4 1235 5.6.7.8 4321 and
+   * 1.2.3.4 1235 5.6.7.8 4322
+   */
+  args.is_add = 0;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Del 1.2.3.4/24 * 5.6.7.8/24 *");
+
+  args.lcl.fp_addr.ip4 = lcl_ip;
+  args.rmt.fp_addr.ip4 = rmt_ip;
+  args.lcl.fp_len = 16;
+  args.rmt.fp_len = 16;
+  args.lcl_port = 0;
+  args.rmt_port = 0;
+  args.action_index = action_index++;
+  args.is_add = 1;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/16 * 5.6.7.8/16 * action %d",
+		action_index - 1);
+
+  args.lcl.fp_addr.ip4 = lcl_ip;
+  args.rmt.fp_addr.ip4 = rmt_ip;
+  args.lcl.fp_len = 24;
+  args.rmt.fp_len = 24;
+  args.lcl_port = lcl_port + 1;
+  args.rmt_port = rmt_port;
+  args.action_index = action_index++;
+  args.is_add = 1;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/24 1235 5.6.7.8/24 4321 action %d",
+		action_index - 1);
+
+  if (verbose)
+    session_rules_table_cli_dump (vm, srt, FIB_PROTOCOL_IP4,
+				  TRANSPORT_PROTO_TCP);
+
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 3),
+		"Lookup 1.2.3.4 1234 5.6.7.8 4321, action should " "be 3: %d",
+		res);
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port + 1, rmt_port);
+  SESSION_TEST ((res == 9),
+		"Lookup 1.2.3.4 1235 5.6.7.8 4321, action should " "be 9: %d",
+		res);
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port + 1, rmt_port + 1);
+  SESSION_TEST ((res == 8),
+		"Lookup 1.2.3.4 1235 5.6.7.8 4322, action should " "be 8: %d",
+		res);
+
+  /*
+   * Delete 1.2.0.0/16 1234 5.6.0.0/16 4321 and 1.2.0.0/16 * 5.6.0.0/16 *
+   * Lookup 1.2.3.4 1234 5.6.7.8 4321
+   */
+  args.lcl_port = 1234;
+  args.rmt_port = 4321;
+  args.lcl.fp_len = 16;
+  args.rmt.fp_len = 16;
+  args.is_add = 0;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Del 1.2.0.0/16 1234 5.6.0.0/16 4321");
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 3),
+		"Lookup 1.2.3.4 1234 5.6.7.8 4321, action should " "be 3: %d",
+		res);
+
+  args.lcl_port = 0;
+  args.rmt_port = 0;
+  args.is_add = 0;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Del 1.2.0.0/16 * 5.6.0.0/16 *");
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 3),
+		"Lookup 1.2.3.4 1234 5.6.7.8 4321, action should " "be 3: %d",
+		res);
+
+  /*
+   * Delete 1.2.3.4/24 1234 5.6.7.5/24
+   */
+  args.lcl.fp_addr.ip4 = lcl_ip;
+  args.rmt.fp_addr.ip4 = rmt_ip;
+  args.lcl.fp_len = 24;
+  args.rmt.fp_len = 24;
+  args.lcl_port = 1234;
+  args.rmt_port = 4321;
+  args.is_add = 0;
+  error = session_rules_table_add_del (srt, &args);
+  SESSION_TEST ((error == 0), "Del 1.2.3.4/24 1234 5.6.7.5/24");
+  res =
+    session_rules_table_lookup4 (srt, TRANSPORT_PROTO_TCP, &lcl_ip, &rmt_ip,
+				 lcl_port, rmt_port);
+  SESSION_TEST ((res == 2), "Action should be 2: %d", res);
+
+  return 0;
+}
+
+static int
+session_test_rules (vlib_main_t * vm, unformat_input_t * input)
+{
+  session_endpoint_t server_sep = SESSION_ENDPOINT_NULL;
+  u64 options[SESSION_OPTIONS_N_OPTIONS];
+  u16 lcl_port = 1234, rmt_port = 4321;
+  u32 server_index, app_index;
+  u32 dummy_server_api_index = ~0;
+  transport_connection_t *tc;
+  u32 dummy_port = 1111;
+  clib_error_t *error = 0;
+  u8 segment_name[128];
+  stream_session_t *listener, *s;
+  app_namespace_t *default_ns = app_namespace_get_default ();
+  u32 local_ns_index = default_ns->local_table_index;
+
+  server_sep.is_ip4 = 1;
+  server_sep.port = dummy_port;
+  memset (options, 0, sizeof (options));
+
+  vnet_app_attach_args_t attach_args = {
+    .api_client_index = ~0,
+    .options = options,
+    .namespace_id = 0,
+    .session_cb_vft = &dummy_session_cbs,
+    .segment_name = segment_name,
+  };
+
+  vnet_bind_args_t bind_args = {
+    .sep = server_sep,
+    .app_index = 0,
+  };
+
+  /*
+   * Attach server with global and local default scope
+   */
+  options[APP_OPTIONS_FLAGS] = APP_OPTIONS_FLAGS_BUILTIN_APP;
+  options[APP_OPTIONS_FLAGS] |= APP_OPTIONS_FLAGS_ACCEPT_REDIRECT;
+  options[APP_OPTIONS_FLAGS] |= APP_OPTIONS_FLAGS_USE_GLOBAL_SCOPE;
+  options[APP_OPTIONS_FLAGS] |= APP_OPTIONS_FLAGS_USE_LOCAL_SCOPE;
+  attach_args.namespace_id = 0;
+  attach_args.api_client_index = dummy_server_api_index;
+  error = vnet_application_attach (&attach_args);
+  SESSION_TEST ((error == 0), "server attached");
+  server_index = attach_args.app_index;
+
+  bind_args.app_index = server_index;
+  error = vnet_bind (&bind_args);
+  SESSION_TEST ((error == 0), "server bound to %U/%d", format_ip46_address,
+		&server_sep.ip, 1, server_sep.port);
+  listener = listen_session_get_from_handle (bind_args.handle);
+  ip4_address_t lcl_ip = {
+    .as_u32 = clib_host_to_net_u32 (0x01020304),
+  };
+  ip4_address_t rmt_ip = {
+    .as_u32 = clib_host_to_net_u32 (0x05060708),
+  };
+  fib_prefix_t lcl_pref = {
+    .fp_addr.ip4.as_u32 = lcl_ip.as_u32,
+    .fp_len = 16,
+    .fp_proto = FIB_PROTOCOL_IP4,
+  };
+  fib_prefix_t rmt_pref = {
+    .fp_addr.ip4.as_u32 = rmt_ip.as_u32,
+    .fp_len = 16,
+    .fp_proto = FIB_PROTOCOL_IP4,
+  };
+
+  tc = session_lookup_connection_wt4 (0, &lcl_pref.fp_addr.ip4,
+				      &rmt_pref.fp_addr.ip4, lcl_port,
+				      rmt_port, TRANSPORT_PROTO_TCP, 0);
+  SESSION_TEST ((tc == 0), "optimized lookup should not work (port)");
+
+  /*
+   * Add 1.2.3.4/16 1234 5.6.7.8/16 4321 action server_index
+   */
+  session_rule_add_del_args_t args = {
+    .table_args.lcl = lcl_pref,
+    .table_args.rmt = rmt_pref,
+    .table_args.lcl_port = lcl_port,
+    .table_args.rmt_port = rmt_port,
+    .table_args.action_index = server_index,
+    .table_args.is_add = 1,
+    .appns_index = 0,
+  };
+  error = vnet_session_rule_add_del (&args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/16 1234 5.6.7.8/16 4321 action %d",
+		args.table_args.action_index);
+
+  tc = session_lookup_connection4 (0, &lcl_pref.fp_addr.ip4,
+				   &rmt_pref.fp_addr.ip4, lcl_port, rmt_port,
+				   TRANSPORT_PROTO_TCP);
+  SESSION_TEST ((tc->c_index == listener->connection_index),
+		"optimized lookup should return the listener");
+  tc = session_lookup_connection_wt4 (0, &lcl_pref.fp_addr.ip4,
+				      &rmt_pref.fp_addr.ip4, lcl_port,
+				      rmt_port, TRANSPORT_PROTO_TCP, 0);
+  SESSION_TEST ((tc->c_index == listener->connection_index),
+		"lookup should return the listener");
+  s = session_lookup_safe4 (0, &lcl_pref.fp_addr.ip4, &rmt_pref.fp_addr.ip4,
+			    lcl_port, rmt_port, TRANSPORT_PROTO_TCP);
+  SESSION_TEST ((s->connection_index == listener->connection_index),
+		"safe lookup should return the listener");
+  session_endpoint_t sep = {
+    .ip = rmt_pref.fp_addr,
+    .is_ip4 = 1,
+    .port = rmt_port,
+    .transport_proto = TRANSPORT_PROTO_TCP,
+  };
+  app_index = session_lookup_local_session_endpoint (local_ns_index, &sep);
+  SESSION_TEST ((app_index != server_index), "local session endpoint lookup "
+		"should not work (global scope)");
+
+  tc = session_lookup_connection_wt4 (0, &lcl_pref.fp_addr.ip4,
+				      &rmt_pref.fp_addr.ip4, lcl_port + 1,
+				      rmt_port, TRANSPORT_PROTO_TCP, 0);
+  SESSION_TEST ((tc == 0),
+		"optimized lookup for wrong lcl port + 1 should not" " work");
+
+  /*
+   * Add 1.2.3.4/16 * 5.6.7.8/16 4321
+   */
+  args.table_args.lcl_port = 0;
+  args.scope = SESSION_RULE_SCOPE_LOCAL | SESSION_RULE_SCOPE_GLOBAL;
+  error = vnet_session_rule_add_del (&args);
+  SESSION_TEST ((error == 0), "Add 1.2.3.4/16 * 5.6.7.8/16 4321 action %d",
+		args.table_args.action_index);
+  tc = session_lookup_connection_wt4 (0, &lcl_pref.fp_addr.ip4,
+				      &rmt_pref.fp_addr.ip4, lcl_port + 1,
+				      rmt_port, TRANSPORT_PROTO_TCP, 0);
+  SESSION_TEST ((tc->c_index == listener->connection_index),
+		"optimized lookup for lcl port + 1 should work");
+  app_index = session_lookup_local_session_endpoint (local_ns_index, &sep);
+  SESSION_TEST ((app_index != server_index), "local session endpoint lookup "
+		"should not work (constrained lcl ip)");
+
+  /*
+   * Add local scope rule for 0/0 * 5.6.7.8/16 4321 action server_index
+   */
+  args.table_args.lcl.fp_len = 0;
+  error = vnet_session_rule_add_del (&args);
+  SESSION_TEST ((error == 0), "Add * * 5.6.7.8/16 4321 action %d",
+		args.table_args.action_index);
+  app_index = session_lookup_local_session_endpoint (local_ns_index, &sep);
+  SESSION_TEST ((app_index == server_index), "local session endpoint lookup "
+		"should work");
+
+  /*
+   * Delete 0/0 * 5.6.7.8/16 4321, 1.2.3.4/16 * 5.6.7.8/16 4321 and
+   * 1.2.3.4/16 1234 5.6.7.8/16 4321
+   */
+  args.table_args.is_add = 0;
+  error = vnet_session_rule_add_del (&args);
+  SESSION_TEST ((error == 0), "Del 0/0 * 5.6.7.8/16 4321");
+  app_index = session_lookup_local_session_endpoint (local_ns_index, &sep);
+  SESSION_TEST ((app_index != server_index), "local session endpoint lookup "
+		"should not work (removed)");
+
+  args.table_args.is_add = 0;
+  args.table_args.lcl = lcl_pref;
+  error = vnet_session_rule_add_del (&args);
+  SESSION_TEST ((error == 0), "Del 1.2.3.4/16 * 5.6.7.8/16 4321");
+  tc = session_lookup_connection_wt4 (0, &lcl_pref.fp_addr.ip4,
+				      &rmt_pref.fp_addr.ip4, lcl_port + 1,
+				      rmt_port, TRANSPORT_PROTO_TCP, 0);
+  SESSION_TEST ((tc == 0), "optimized lookup for lcl port + 1 should not "
+		"work (del)");
+
+  args.table_args.is_add = 0;
+  args.table_args.lcl_port = 1234;
+  error = vnet_session_rule_add_del (&args);
+  SESSION_TEST ((error == 0), "Del 1.2.3.4/16 1234 5.6.7.8/16 4321");
+  tc = session_lookup_connection_wt4 (0, &lcl_pref.fp_addr.ip4,
+				      &rmt_pref.fp_addr.ip4, lcl_port,
+				      rmt_port, TRANSPORT_PROTO_TCP, 0);
+  SESSION_TEST ((tc == 0), "optimized lookup should not work (del)");
+  return 0;
+}
+
 static clib_error_t *
 session_test (vlib_main_t * vm,
 	      unformat_input_t * input, vlib_cli_command_t * cmd_arg)
@@ -452,6 +910,10 @@
 	{
 	  res = session_test_namespace (vm, input);
 	}
+      else if (unformat (input, "rules-table"))
+	res = session_test_rule_table (vm, input);
+      else if (unformat (input, "rules"))
+	res = session_test_rules (vm, input);
       else
 	break;
     }