From a55df1081762b4e40698ef7d9196551851be646a Mon Sep 17 00:00:00 2001 From: Dave Barach Date: Wed, 6 Mar 2019 11:14:03 -0500 Subject: [PATCH] ipv6 connection tracking plugin A security feature: drop unsolicited global unicast traffic. Change-Id: I421da7d52e08b7acf40c62a1f6e2a6caac349e7e Signed-off-by: Dave Barach --- src/plugins/ct6/CMakeLists.txt | 35 +++ src/plugins/ct6/ct6.api | 27 ++ src/plugins/ct6/ct6.c | 573 ++++++++++++++++++++++++++++++++++++++++ src/plugins/ct6/ct6.h | 179 +++++++++++++ src/plugins/ct6/ct6_all_api_h.h | 19 ++ src/plugins/ct6/ct6_in2out.c | 362 +++++++++++++++++++++++++ src/plugins/ct6/ct6_msg_enum.h | 31 +++ src/plugins/ct6/ct6_out2in.c | 278 +++++++++++++++++++ src/plugins/ct6/ct6_test.c | 202 ++++++++++++++ 9 files changed, 1706 insertions(+) create mode 100644 src/plugins/ct6/CMakeLists.txt create mode 100644 src/plugins/ct6/ct6.api create mode 100644 src/plugins/ct6/ct6.c create mode 100644 src/plugins/ct6/ct6.h create mode 100644 src/plugins/ct6/ct6_all_api_h.h create mode 100644 src/plugins/ct6/ct6_in2out.c create mode 100644 src/plugins/ct6/ct6_msg_enum.h create mode 100644 src/plugins/ct6/ct6_out2in.c create mode 100644 src/plugins/ct6/ct6_test.c diff --git a/src/plugins/ct6/CMakeLists.txt b/src/plugins/ct6/CMakeLists.txt new file mode 100644 index 00000000000..069b33ad2d0 --- /dev/null +++ b/src/plugins/ct6/CMakeLists.txt @@ -0,0 +1,35 @@ + +# Copyright (c) +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +add_vpp_plugin(ct6 + SOURCES + ct6.c + ct6_in2out.c + ct6_out2in.c + ct6.h + + MULTIARCH_SOURCES + ct6_in2out.c + ct6_out2in.c + + API_FILES + ct6.api + + INSTALL_HEADERS + ct6_all_api_h.h + ct6_msg_enum.h + + API_TEST_SOURCES + ct6_test.c +) diff --git a/src/plugins/ct6/ct6.api b/src/plugins/ct6/ct6.api new file mode 100644 index 00000000000..67af42fd9fe --- /dev/null +++ b/src/plugins/ct6/ct6.api @@ -0,0 +1,27 @@ + +/* Define a simple enable-disable binary API to control the feature */ + +define ct6_enable_disable { + /* Client identifier, set from api_main.my_client_index */ + u32 client_index; + + /* Arbitrary context, so client can match reply to request */ + u32 context; + + /* Enable / disable the feature */ + u8 enable_disable; + + /* Inside or outside interface */ + u8 is_inside; + + /* Interface handle */ + u32 sw_if_index; +}; + +define ct6_enable_disable_reply { + /* From the request */ + u32 context; + + /* Return value, zero means all OK */ + i32 retval; +}; diff --git a/src/plugins/ct6/ct6.c b/src/plugins/ct6/ct6.c new file mode 100644 index 00000000000..f0ad67a80c9 --- /dev/null +++ b/src/plugins/ct6/ct6.c @@ -0,0 +1,573 @@ +/* + * ct6.c - skeleton vpp engine plug-in + * + * Copyright (c) + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include + +/* define message IDs */ +#include + +/* define message structures */ +#define vl_typedefs +#include +#undef vl_typedefs + +/* define generated endian-swappers */ +#define vl_endianfun +#include +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include +#undef vl_printfun + +/* Get the API version number */ +#define vl_api_version(n,v) static u32 api_version=(v); +#include +#undef vl_api_version + +#define REPLY_MSG_ID_BASE cmp->msg_id_base +#include + +ct6_main_t ct6_main; + +/* List of message types that this plugin understands */ + +#define foreach_ct6_plugin_api_msg \ +_(CT6_ENABLE_DISABLE, ct6_enable_disable) + +/* Action function shared between message handler and debug CLI */ + +static void +ct6_feature_init (ct6_main_t * cmp) +{ + u32 nworkers = vlib_num_workers (); + + if (cmp->feature_initialized) + return; + + clib_bihash_init_48_8 (&cmp->session_hash, "ct6 session table", + cmp->session_hash_buckets, cmp->session_hash_memory); + cmp->feature_initialized = 1; + vec_validate (cmp->sessions, nworkers); + vec_validate_init_empty (cmp->first_index, nworkers, ~0); + vec_validate_init_empty (cmp->last_index, nworkers, ~0); +} + +int +ct6_in2out_enable_disable (ct6_main_t * cmp, u32 sw_if_index, + int enable_disable) +{ + vnet_sw_interface_t *sw; + int rv = 0; + + ct6_feature_init (cmp); + + /* Utterly wrong? */ + if (pool_is_free_index (cmp->vnet_main->interface_main.sw_interfaces, + sw_if_index)) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + /* Not a physical port? */ + sw = vnet_get_sw_interface (cmp->vnet_main, sw_if_index); + if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + vnet_feature_enable_disable ("interface-output", "ct6-in2out", + sw_if_index, enable_disable, 0, 0); + + return rv; +} + +int +ct6_out2in_enable_disable (ct6_main_t * cmp, u32 sw_if_index, + int enable_disable) +{ + vnet_sw_interface_t *sw; + int rv = 0; + + ct6_feature_init (cmp); + + /* Utterly wrong? */ + if (pool_is_free_index (cmp->vnet_main->interface_main.sw_interfaces, + sw_if_index)) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + /* Not a physical port? */ + sw = vnet_get_sw_interface (cmp->vnet_main, sw_if_index); + if (sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE) + return VNET_API_ERROR_INVALID_SW_IF_INDEX; + + vnet_feature_enable_disable ("ip6-unicast", "ct6-out2in", + sw_if_index, enable_disable, 0, 0); + + return rv; +} + +static clib_error_t * +set_ct6_enable_disable_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ct6_main_t *cmp = &ct6_main; + u32 sw_if_index = ~0; + int enable_disable = 1; + u32 inside = ~0; + int rv; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "disable")) + enable_disable = 0; + else if (unformat (input, "%U", unformat_vnet_sw_interface, + cmp->vnet_main, &sw_if_index)) + ; + else if (unformat (input, "inside") || unformat (input, "in")) + inside = 1; + else if (unformat (input, "outside") || unformat (input, "out")) + inside = 0; + else + break; + } + + if (inside == ~0) + return clib_error_return (0, "Please specify inside or outside"); + + if (sw_if_index == ~0) + return clib_error_return (0, "Please specify an interface..."); + + if (inside == 1) + rv = ct6_in2out_enable_disable (cmp, sw_if_index, enable_disable); + else + rv = ct6_out2in_enable_disable (cmp, sw_if_index, enable_disable); + + switch (rv) + { + case 0: + break; + + case VNET_API_ERROR_INVALID_SW_IF_INDEX: + return clib_error_return + (0, "Invalid interface, only works on physical ports"); + break; + + default: + return clib_error_return (0, "ct6_enable_disable returned %d", rv); + } + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (set_ct6_command, static) = +{ + .path = "set ct6", + .short_help = + "set ct6 [inside|outside] [disable]", + .function = set_ct6_enable_disable_command_fn, +}; +/* *INDENT-ON* */ + +/* API message handler */ +static void vl_api_ct6_enable_disable_t_handler + (vl_api_ct6_enable_disable_t * mp) +{ + vl_api_ct6_enable_disable_reply_t *rmp; + ct6_main_t *cmp = &ct6_main; + int rv; + + if (mp->is_inside) + rv = ct6_in2out_enable_disable (cmp, ntohl (mp->sw_if_index), + (int) (mp->enable_disable)); + else + rv = ct6_out2in_enable_disable (cmp, ntohl (mp->sw_if_index), + (int) (mp->enable_disable)); + + REPLY_MACRO (VL_API_CT6_ENABLE_DISABLE_REPLY); +} + +/* Set up the API message handling tables */ +static clib_error_t * +ct6_plugin_api_hookup (vlib_main_t * vm) +{ + ct6_main_t *cmp = &ct6_main; +#define _(N,n) \ + vl_msg_api_set_handlers((VL_API_##N + cmp->msg_id_base), \ + #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_ct6_plugin_api_msg; +#undef _ + + return 0; +} + +#define vl_msg_name_crc_list +#include +#undef vl_msg_name_crc_list + +static void +setup_message_id_table (ct6_main_t * cmp, api_main_t * am) +{ +#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n #crc, id + cmp->msg_id_base); + foreach_vl_msg_name_crc_ct6; +#undef _ +} + +static clib_error_t * +ct6_init (vlib_main_t * vm) +{ + ct6_main_t *cmp = &ct6_main; + clib_error_t *error = 0; + u8 *name; + + cmp->vlib_main = vm; + cmp->vnet_main = vnet_get_main (); + + name = format (0, "ct6_%08x%c", api_version, 0); + + /* Ask for a correctly-sized block of API message decode slots */ + cmp->msg_id_base = vl_msg_api_get_msg_ids + ((char *) name, VL_MSG_FIRST_AVAILABLE); + + error = ct6_plugin_api_hookup (vm); + + /* Add our API messages to the global name_crc hash table */ + setup_message_id_table (cmp, &api_main); + + vec_free (name); + + /* + * Set default parameters... + * 256K sessions + * 64K buckets + * 2 minute inactivity timer + * 10000 concurrent sessions + */ + cmp->session_hash_memory = 16ULL << 20; + cmp->session_hash_buckets = 64 << 10; + cmp->session_timeout_interval = 120.0; + cmp->max_sessions_per_worker = 10000; + + /* ... so the packet generator can feed the in2out node ... */ + ethernet_setup_node (vm, ct6_in2out_node.index); + return error; +} + +VLIB_INIT_FUNCTION (ct6_init); + +/* *INDENT-OFF* */ +VNET_FEATURE_INIT (ct6out2in, static) = +{ + .arc_name = "ip6-unicast", + .node_name = "ct6-out2in", + .runs_before = VNET_FEATURES ("ip6-lookup"), +}; +/* *INDENT-ON */ + +/* *INDENT-OFF* */ +VNET_FEATURE_INIT (ct6in2out, static) = +{ + .arc_name = "interface-output", + .node_name = "ct6-in2out", + .runs_before = VNET_FEATURES ("interface-output"), +}; +/* *INDENT-ON */ + +/* *INDENT-OFF* */ +VLIB_PLUGIN_REGISTER () = +{ + .version = VPP_BUILD_VER, + .description = "ipv6 connection tracker", +}; +/* *INDENT-ON* */ + +u8 * +format_ct6_session (u8 * s, va_list * args) +{ + ct6_main_t *cmp = va_arg (*args, ct6_main_t *); + int i = va_arg (*args, int); + ct6_session_t *s0 = va_arg (*args, ct6_session_t *); + int verbose = va_arg (*args, int); + clib_bihash_kv_48_8_t kvp0; + + if (s0 == 0) + { + s = format (s, "\n%6s%6s%20s%6s%20s%6s", + "Sess", "Prot", "Src", "Sport", "Dst", "Dport"); + return s; + } + + s = format (s, "\n%6d%6d%20U%6u%20U%6u", + s0 - cmp->sessions[i], s0->key.proto, + format_ip6_address, &s0->key.src, + clib_net_to_host_u16 (s0->key.sport), + format_ip6_address, &s0->key.dst, + clib_net_to_host_u16 (s0->key.dport)); + + clib_memcpy_fast (&kvp0, s0, sizeof (ct6_session_key_t)); + + if (clib_bihash_search_48_8 (&cmp->session_hash, &kvp0, &kvp0) < 0) + { + s = format (s, " LOOKUP FAIL!"); + } + else + { + if (kvp0.value == s0 - cmp->sessions[s0->thread_index]) + { + s = format (s, " OK"); + if (verbose > 1) + { + s = format (s, " next %d prev %d", s0->next_index, + s0->prev_index); + s = format (s, " hits %d expires %.2f", s0->hits, s0->expires); + } + } + else + s = format (s, " BOGUS LOOKUP RESULT!"); + } + + return s; +} + +static clib_error_t * +show_ct6_command_fn_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ct6_main_t *cmp = &ct6_main; + ct6_session_t *s0; + int verbose = 0; + u8 *s = 0; + int i; + + if (!cmp->feature_initialized) + return clib_error_return (0, "ip6 connection tracking not enabled..."); + + if (unformat (input, "verbose %d", &verbose)) + ; + else if (unformat (input, "verbose")) + verbose = 1; + + for (i = 0; i < vec_len (cmp->sessions); i++) + { + s = format (s, "Thread %d: %d sessions\n", i, + pool_elts (cmp->sessions[i])); + + if (verbose == 0) + continue; + + s = + format (s, "%U", format_ct6_session, cmp, + 0 /* pool */ , 0 /* header */ , verbose); + + /* *INDENT-OFF* */ + pool_foreach (s0, cmp->sessions[i], + ({ + s = format (s, "%U", format_ct6_session, cmp, i, s0, verbose); + })); + /* *INDENT-ON* */ + } + vlib_cli_output (cmp->vlib_main, "%v", s); + vec_free (s); + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_ct6_command_fn_command, static) = +{ + .path = "show ip6 connection-tracker", + .short_help = "show ip6 connection-tracker", + .function = show_ct6_command_fn_command_fn, +}; +/* *INDENT-ON* */ + +static void +increment_v6_address (ip6_address_t * a) +{ + u64 v0, v1; + + v0 = clib_net_to_host_u64 (a->as_u64[0]); + v1 = clib_net_to_host_u64 (a->as_u64[1]); + + v1 += 1; + if (v1 == 0) + v0 += 1; + a->as_u64[0] = clib_net_to_host_u64 (v0); + a->as_u64[1] = clib_net_to_host_u64 (v1); +} + + +static clib_error_t * +test_ct6_command_fn_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ct6_main_t *cmp = &ct6_main; + clib_bihash_kv_48_8_t kvp0; + ct6_session_key_t *key0; + ct6_session_t *s0; + u8 src[16], dst[16]; + u32 recycled = 0, created = 0; + int i, num_sessions = 5; + u32 midpt_index; + u8 *s = 0; + + cmp->max_sessions_per_worker = 4; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "num-sessions %d", &num_sessions)) + ; + else + if (unformat + (input, "max-sessions %d", &cmp->max_sessions_per_worker)) + ; + else + break; + } + + ct6_feature_init (cmp); + + /* Set up starting src/dst addresses */ + memset (src, 0, sizeof (src)); + memset (dst, 0, sizeof (dst)); + + src[0] = 0xdb; + dst[0] = 0xbe; + + src[15] = 1; + dst[15] = 1; + + /* + * See if we know about this flow. + * Key set up for the out2in path, the performant case + */ + key0 = (ct6_session_key_t *) & kvp0; + memset (&kvp0, 0, sizeof (kvp0)); + + for (i = 0; i < num_sessions; i++) + { + clib_memcpy_fast (&key0->src, src, sizeof (src)); + clib_memcpy_fast (&key0->dst, dst, sizeof (dst)); + key0->as_u64[4] = 0; + key0->as_u64[5] = 0; + key0->sport = clib_host_to_net_u16 (1234); + key0->dport = clib_host_to_net_u16 (4321); + key0->proto = 17; /* udp, fwiw */ + + s0 = ct6_create_or_recycle_session + (cmp, &kvp0, 3.0 /* now */ , 0 /* thread index */ , + &recycled, &created); + + s = format (s, "%U (%d, %d)", format_ct6_session, cmp, + 0 /* thread index */ , s0, 1 /* verbose */ , + recycled, created); + vlib_cli_output (vm, "%v", s); + vec_free (s); + increment_v6_address ((ip6_address_t *) src); + recycled = 0; + created = 0; + } + + /* *INDENT-OFF* */ + pool_foreach (s0, cmp->sessions[0], + ({ + s = format (s, "%U", format_ct6_session, cmp, 0, s0, 1 /* verbose */); + })); + /* *INDENT-ON* */ + + vlib_cli_output (vm, "\nEnd state: first index %d last index %d\n%v", + cmp->first_index[0], cmp->last_index[0], s); + + vec_free (s); + + midpt_index = cmp->max_sessions_per_worker / 3; + + s0 = pool_elt_at_index (cmp->sessions[0], midpt_index); + vlib_cli_output (vm, "\nSimulate LRU hit on session %d", + s0 - cmp->sessions[0]); + + ct6_update_session_hit (cmp, s0, 234.0); + + /* *INDENT-OFF* */ + pool_foreach (s0, cmp->sessions[0], + ({ + s = format (s, "%U", format_ct6_session, cmp, 0, s0, 1 /* verbose */); + })); + /* *INDENT-ON* */ + + vlib_cli_output (vm, "\nEnd state: first index %d last index %d\n%v", + cmp->first_index[0], cmp->last_index[0], s); + + vec_free (s); + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (test_ct6_command_fn_command, static) = +{ + .path = "test ip6 connection-tracker", + .short_help = "test ip6 connection-tracker", + .function = test_ct6_command_fn_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +ct6_config (vlib_main_t * vm, unformat_input_t * input) +{ + ct6_main_t *cmp = &ct6_main; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "session-hash-buckets %u", + &cmp->session_hash_buckets)) + ; + else if (unformat (input, "session-hash-memory %U", + unformat_memory_size, &cmp->session_hash_memory)) + ; + else if (unformat (input, "session-timeout %f", + &cmp->session_timeout_interval)) + ; + else + { + return clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + } + } + return 0; +} + +VLIB_CONFIG_FUNCTION (ct6_config, "ct6"); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ct6/ct6.h b/src/plugins/ct6/ct6.h new file mode 100644 index 00000000000..534534f5c99 --- /dev/null +++ b/src/plugins/ct6/ct6.h @@ -0,0 +1,179 @@ + +/* + * ct6.h - skeleton vpp engine plug-in header file + * + * Copyright (c) + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef __included_ct6_h__ +#define __included_ct6_h__ + +#include +#include +#include +#include + +#include +#include + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct +{ + union + { + struct + { + /* out2in */ + ip6_address_t src; + ip6_address_t dst; + u16 sport; + u16 dport; + u8 proto; /* byte 37 */ + }; + u64 as_u64[6]; + }; +}) ct6_session_key_t; +/* *INDENT-ON* */ + +typedef struct +{ + ct6_session_key_t key; + u32 thread_index; + u32 next_index; + u32 prev_index; + u32 hits; + f64 expires; +} ct6_session_t; + +typedef struct +{ + /* API message ID base */ + u16 msg_id_base; + + /* session lookup table */ + clib_bihash_48_8_t session_hash; + u8 feature_initialized; + + /* per_thread session pools */ + ct6_session_t **sessions; + u32 *first_index; + u32 *last_index; + + /* Config parameters */ + f64 session_timeout_interval; + uword session_hash_memory; + u32 session_hash_buckets; + u32 max_sessions_per_worker; + + /* convenience */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; + ethernet_main_t *ethernet_main; +} ct6_main_t; + +extern ct6_main_t ct6_main; + +extern vlib_node_registration_t ct6_out2in_node; +extern vlib_node_registration_t ct6_in2out_node; + +format_function_t format_ct6_session; + +ct6_session_t *ct6_create_or_recycle_session (ct6_main_t * cmp, + clib_bihash_kv_48_8_t * kvpp, + f64 now, u32 my_thread_index, + u32 * recyclep, u32 * createp); + +static inline void +ct6_lru_remove (ct6_main_t * cmp, ct6_session_t * s0) +{ + ct6_session_t *next_sess, *prev_sess; + u32 thread_index; + u32 s0_index; + + thread_index = s0->thread_index; + + s0_index = s0 - cmp->sessions[thread_index]; + + /* Deal with list heads */ + if (s0_index == cmp->first_index[thread_index]) + cmp->first_index[thread_index] = s0->next_index; + if (s0_index == cmp->last_index[thread_index]) + cmp->last_index[thread_index] = s0->prev_index; + + /* Fix next->prev */ + if (s0->next_index != ~0) + { + next_sess = pool_elt_at_index (cmp->sessions[thread_index], + s0->next_index); + next_sess->prev_index = s0->prev_index; + } + /* Fix prev->next */ + if (s0->prev_index != ~0) + { + prev_sess = pool_elt_at_index (cmp->sessions[thread_index], + s0->prev_index); + prev_sess->next_index = s0->next_index; + } +} + +static inline void +ct6_lru_add (ct6_main_t * cmp, ct6_session_t * s0, f64 now) +{ + ct6_session_t *next_sess; + u32 thread_index; + u32 s0_index; + + s0->hits++; + s0->expires = now + cmp->session_timeout_interval; + thread_index = s0->thread_index; + + s0_index = s0 - cmp->sessions[thread_index]; + + /* + * Re-add at the head of the forward LRU list, + * tail of the reverse LRU list + */ + if (cmp->first_index[thread_index] != ~0) + { + next_sess = pool_elt_at_index (cmp->sessions[thread_index], + cmp->first_index[thread_index]); + next_sess->prev_index = s0_index; + } + + s0->prev_index = ~0; + + /* s0 now the new head of the LRU forward list */ + s0->next_index = cmp->first_index[thread_index]; + cmp->first_index[thread_index] = s0_index; + + /* single session case: also the tail of the reverse LRU list */ + if (cmp->last_index[thread_index] == ~0) + cmp->last_index[thread_index] = s0_index; +} + +static inline void +ct6_update_session_hit (ct6_main_t * cmp, ct6_session_t * s0, f64 now) +{ + ct6_lru_remove (cmp, s0); + ct6_lru_add (cmp, s0, now); +} + +#endif /* __included_ct6_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ct6/ct6_all_api_h.h b/src/plugins/ct6/ct6_all_api_h.h new file mode 100644 index 00000000000..3642216cefa --- /dev/null +++ b/src/plugins/ct6/ct6_all_api_h.h @@ -0,0 +1,19 @@ + +/* + * ct6_all_api_h.h - skeleton vpp engine plug-in api #include file + * + * Copyright (c) + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* Include the generated file, see BUILT_SOURCES in Makefile.am */ +#include diff --git a/src/plugins/ct6/ct6_in2out.c b/src/plugins/ct6/ct6_in2out.c new file mode 100644 index 00000000000..b28d349dd29 --- /dev/null +++ b/src/plugins/ct6/ct6_in2out.c @@ -0,0 +1,362 @@ +/* + * ct6_in2out.c - ip6 connection tracker, inside-to-outside path + * + * Copyright (c) 2019 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +typedef struct +{ + u32 sw_if_index; + u32 next_index; + u32 session_index; +} ct6_in2out_trace_t; + +#ifndef CLIB_MARCH_VARIANT + +/* packet trace format function */ +static u8 * +format_ct6_in2out_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 *); + ct6_in2out_trace_t *t = va_arg (*args, ct6_in2out_trace_t *); + + s = format (s, "CT6_IN2OUT: sw_if_index %d, next index %d session %d\n", + t->sw_if_index, t->next_index, t->session_index); + return s; +} + +vlib_node_registration_t ct6_in2out_node; + +#endif /* CLIB_MARCH_VARIANT */ + +#define foreach_ct6_in2out_error \ +_(PROCESSED, "ct6 packets processed") \ +_(CREATED, "ct6 sessions created") \ +_(RECYCLED, "ct6 sessions recycled") + +typedef enum +{ +#define _(sym,str) CT6_IN2OUT_ERROR_##sym, + foreach_ct6_in2out_error +#undef _ + CT6_IN2OUT_N_ERROR, +} ct6_in2out_error_t; + +#ifndef CLIB_MARCH_VARIANT +static char *ct6_in2out_error_strings[] = { +#define _(sym,string) string, + foreach_ct6_in2out_error +#undef _ +}; +#endif /* CLIB_MARCH_VARIANT */ + +typedef enum +{ + CT6_IN2OUT_NEXT_DROP, + CT6_IN2OUT_N_NEXT, +} ct6_next_t; + +#ifndef CLIB_MARCH_VARIANT +ct6_session_t * +ct6_create_or_recycle_session (ct6_main_t * cmp, + clib_bihash_kv_48_8_t * kvpp, f64 now, + u32 my_thread_index, u32 * recyclep, + u32 * createp) +{ + ct6_session_t *s0; + + /* Empty arena? */ + if (PREDICT_FALSE (cmp->last_index[my_thread_index] == ~0)) + goto alloc0; + + /* Look at the least-recently-used session */ + s0 = pool_elt_at_index (cmp->sessions[my_thread_index], + cmp->last_index[my_thread_index]); + + if (s0->expires < now) + clib_warning ("session %d expired %.2f time now %.2f", + s0 - cmp->sessions[my_thread_index], s0->expires, now); + + if (pool_elts (cmp->sessions[my_thread_index]) >= + cmp->max_sessions_per_worker) + clib_warning ("recycle session %d have %d max %d", + s0 - cmp->sessions[my_thread_index], + pool_elts (cmp->sessions[my_thread_index]), + cmp->max_sessions_per_worker); + + /* Session expired, or we have as many sessions as is allowed by law? */ + if ((s0->expires < now) || (pool_elts (cmp->sessions[my_thread_index]) + >= cmp->max_sessions_per_worker)) + { + /* recycle the session */ + if (clib_bihash_add_del_48_8 (&cmp->session_hash, + (clib_bihash_kv_48_8_t *) s0, + 0 /* is_add */ ) < 0) + clib_warning ("session %d not found in hash?", + s0 - cmp->sessions[my_thread_index]); + + ct6_lru_remove (cmp, s0); + *recyclep += 1; + } + else + { + alloc0: + /* Allocate a fresh session */ + pool_get (cmp->sessions[my_thread_index], s0); + *createp += 1; + } + + /* Sesison setup */ + memset (s0, 0, sizeof (*s0)); + clib_memcpy_fast (s0, kvpp, sizeof (ct6_session_key_t)); + s0->thread_index = my_thread_index; + s0->expires = now + cmp->session_timeout_interval; + kvpp->value = s0 - cmp->sessions[my_thread_index]; + clib_bihash_add_del_48_8 (&cmp->session_hash, kvpp, 1 /* is_add */ ); + ct6_lru_add (cmp, s0, now); + return s0; +} +#endif /* CLIB_MARCH_VARIANT */ + +always_inline uword +ct6_in2out_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, vlib_frame_t * frame, + int is_trace) +{ + u32 n_left_from, *from; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; + u16 nexts[VLIB_FRAME_SIZE], *next; + ct6_main_t *cmp = &ct6_main; + u32 my_thread_index = vm->thread_index; + f64 now = vlib_time_now (vm); + u32 created = 0; + u32 recycled = 0; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + + vlib_get_buffers (vm, from, bufs, n_left_from); + b = bufs; + next = nexts; + +#if 0 + while (n_left_from >= 4) + { + /* Prefetch next iteration. */ + if (PREDICT_TRUE (n_left_from >= 8)) + { + vlib_prefetch_buffer_header (b[4], STORE); + vlib_prefetch_buffer_header (b[5], STORE); + vlib_prefetch_buffer_header (b[6], STORE); + vlib_prefetch_buffer_header (b[7], STORE); + CLIB_PREFETCH (b[4]->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (b[5]->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (b[6]->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (b[7]->data, CLIB_CACHE_LINE_BYTES, STORE); + } + + /* $$$$ process 4x pkts right here */ + next[0] = 0; + next[1] = 0; + next[2] = 0; + next[3] = 0; + + if (is_trace) + { + if (b[0]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->next_index = next[0]; + t->sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX]; + } + if (b[1]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[1], sizeof (*t)); + t->next_index = next[1]; + t->sw_if_index = vnet_buffer (b[1])->sw_if_index[VLIB_RX]; + } + if (b[2]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[2], sizeof (*t)); + t->next_index = next[2]; + t->sw_if_index = vnet_buffer (b[2])->sw_if_index[VLIB_RX]; + } + if (b[3]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[3], sizeof (*t)); + t->next_index = next[3]; + t->sw_if_index = vnet_buffer (b[3])->sw_if_index[VLIB_RX]; + } + } + + b += 4; + next += 4; + n_left_from -= 4; + } +#endif + + while (n_left_from > 0) + { + clib_bihash_kv_48_8_t kvp0; + ct6_session_key_t *key0; + ct6_session_t *s0; + u32 session_index0 = ~0; + u32 next0, delta0; + ethernet_header_t *e0; + + ip6_header_t *ip0; + udp_header_t *udp0; + + /* $$$ Set to 0 for pg testing */ + if (1) + { + vnet_feature_next (&next0, b[0]); + next[0] = next0; + } + else + next[0] = CT6_IN2OUT_NEXT_DROP; + + /* + * This is an output feature which runs at the last possible + * moment. Assume an ethernet header. + */ + + e0 = vlib_buffer_get_current (b[0]); + delta0 = sizeof (*e0); + delta0 += (e0->type == clib_net_to_host_u16 (ETHERNET_TYPE_VLAN)) + ? 4 : 0; + delta0 += (e0->type == clib_net_to_host_u16 (ETHERNET_TYPE_DOT1AD)) + ? 8 : 0; + + ip0 = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + delta0); + + /* + * Pass non-global unicast traffic + */ + if (PREDICT_FALSE (!ip6_address_is_global_unicast (&ip0->src_address) + || + !ip6_address_is_global_unicast (&ip0->src_address))) + goto trace0; + /* Pass non-udp, non-tcp traffic */ + if (PREDICT_FALSE (ip0->protocol != IP_PROTOCOL_TCP && + ip0->protocol != IP_PROTOCOL_UDP)) + goto trace0; + + udp0 = ip6_next_header (ip0); + + /* + * See if we know about this flow. + * Key set up for the out2in path, the performant case + */ + key0 = (ct6_session_key_t *) & kvp0; + clib_memcpy_fast (&key0->src, &ip0->dst_address, + sizeof (ip6_address_t)); + clib_memcpy_fast (&key0->dst, &ip0->src_address, + sizeof (ip6_address_t)); + key0->as_u64[4] = 0; + key0->as_u64[5] = 0; + key0->sport = udp0->dst_port; + key0->dport = udp0->src_port; + key0->proto = ip0->protocol; + + /* Need to create a new session? */ + if (clib_bihash_search_48_8 (&cmp->session_hash, &kvp0, &kvp0) < 0) + { + s0 = + ct6_create_or_recycle_session (cmp, &kvp0, now, my_thread_index, + &recycled, &created); + session_index0 = kvp0.value; + } + else + { + s0 = pool_elt_at_index (cmp->sessions[my_thread_index], kvp0.value); + session_index0 = kvp0.value; + ct6_update_session_hit (cmp, s0, now); + } + + trace0: + if (is_trace) + { + if (b[0]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_in2out_trace_t *t = + vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->next_index = next[0]; + t->sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX]; + t->session_index = session_index0; + } + } + + b += 1; + next += 1; + n_left_from -= 1; + } + + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + + vlib_node_increment_counter (vm, node->node_index, + CT6_IN2OUT_ERROR_PROCESSED, frame->n_vectors); + vlib_node_increment_counter (vm, node->node_index, + CT6_IN2OUT_ERROR_CREATED, created); + vlib_node_increment_counter (vm, node->node_index, + CT6_IN2OUT_ERROR_RECYCLED, recycled); + + return frame->n_vectors; +} + +VLIB_NODE_FN (ct6_in2out_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE)) + return ct6_in2out_inline (vm, node, frame, 1 /* is_trace */ ); + else + return ct6_in2out_inline (vm, node, frame, 0 /* is_trace */ ); +} + +/* *INDENT-OFF* */ +#ifndef CLIB_MARCH_VARIANT +VLIB_REGISTER_NODE (ct6_in2out_node) = +{ + .name = "ct6-in2out", + .vector_size = sizeof (u32), + .format_trace = format_ct6_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(ct6_in2out_error_strings), + .error_strings = ct6_in2out_error_strings, + + .n_next_nodes = CT6_IN2OUT_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [CT6_IN2OUT_NEXT_DROP] = "error-drop", + }, + .unformat_buffer = unformat_ethernet_header, +}; +#endif /* CLIB_MARCH_VARIANT */ +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ct6/ct6_msg_enum.h b/src/plugins/ct6/ct6_msg_enum.h new file mode 100644 index 00000000000..05b1751550d --- /dev/null +++ b/src/plugins/ct6/ct6_msg_enum.h @@ -0,0 +1,31 @@ + +/* + * ct6_msg_enum.h - skeleton vpp engine plug-in message enumeration + * + * Copyright (c) + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef included_ct6_msg_enum_h +#define included_ct6_msg_enum_h + +#include + +#define vl_msg_id(n,h) n, +typedef enum { +#include + /* We'll want to know how many messages IDs we need... */ + VL_MSG_FIRST_AVAILABLE, +} vl_msg_id_t; +#undef vl_msg_id + +#endif /* included_ct6_msg_enum_h */ diff --git a/src/plugins/ct6/ct6_out2in.c b/src/plugins/ct6/ct6_out2in.c new file mode 100644 index 00000000000..eac4c155aa4 --- /dev/null +++ b/src/plugins/ct6/ct6_out2in.c @@ -0,0 +1,278 @@ +/* + * ct6_out2in.c - ip6 connection tracker, inside-to-outside path + * + * Copyright (c) 2019 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +typedef struct +{ + u32 sw_if_index; + u32 next_index; + u32 session_index; +} ct6_out2in_trace_t; + +#ifndef CLIB_MARCH_VARIANT + +/* packet trace format function */ +static u8 * +format_ct6_out2in_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 *); + ct6_out2in_trace_t *t = va_arg (*args, ct6_out2in_trace_t *); + + s = format (s, "CT6_OUT2IN: sw_if_index %d, next index %d session %d\n", + t->sw_if_index, t->next_index, t->session_index); + return s; +} + +vlib_node_registration_t ct6_out2in_node; + +#endif /* CLIB_MARCH_VARIANT */ + +#define foreach_ct6_out2in_error \ +_(PROCESSED, "ct6 packets processed") \ +_(NO_SESSION, "ct6 no session drops") + + +typedef enum +{ +#define _(sym,str) CT6_OUT2IN_ERROR_##sym, + foreach_ct6_out2in_error +#undef _ + CT6_OUT2IN_N_ERROR, +} ct6_out2in_error_t; + +#ifndef CLIB_MARCH_VARIANT +static char *ct6_out2in_error_strings[] = { +#define _(sym,string) string, + foreach_ct6_out2in_error +#undef _ +}; +#endif /* CLIB_MARCH_VARIANT */ + +typedef enum +{ + CT6_OUT2IN_NEXT_DROP, + CT6_OUT2IN_N_NEXT, +} ct6_next_t; + +always_inline uword +ct6_out2in_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, vlib_frame_t * frame, + int is_trace) +{ + u32 n_left_from, *from; + vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b; + u16 nexts[VLIB_FRAME_SIZE], *next; + ct6_main_t *cmp = &ct6_main; + u32 my_thread_index = vm->thread_index; + f64 now = vlib_time_now (vm); + u32 dropped = 0; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + + vlib_get_buffers (vm, from, bufs, n_left_from); + b = bufs; + next = nexts; + +#if 0 + while (n_left_from >= 4) + { + /* Prefetch next iteration. */ + if (PREDICT_TRUE (n_left_from >= 8)) + { + vlib_prefetch_buffer_header (b[4], STORE); + vlib_prefetch_buffer_header (b[5], STORE); + vlib_prefetch_buffer_header (b[6], STORE); + vlib_prefetch_buffer_header (b[7], STORE); + CLIB_PREFETCH (b[4]->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (b[5]->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (b[6]->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (b[7]->data, CLIB_CACHE_LINE_BYTES, STORE); + } + + /* $$$$ process 4x pkts right here */ + next[0] = 0; + next[1] = 0; + next[2] = 0; + next[3] = 0; + + if (is_trace) + { + if (b[0]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->next_index = next[0]; + t->sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX]; + } + if (b[1]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[1], sizeof (*t)); + t->next_index = next[1]; + t->sw_if_index = vnet_buffer (b[1])->sw_if_index[VLIB_RX]; + } + if (b[2]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[2], sizeof (*t)); + t->next_index = next[2]; + t->sw_if_index = vnet_buffer (b[2])->sw_if_index[VLIB_RX]; + } + if (b[3]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_trace_t *t = vlib_add_trace (vm, node, b[3], sizeof (*t)); + t->next_index = next[3]; + t->sw_if_index = vnet_buffer (b[3])->sw_if_index[VLIB_RX]; + } + } + + b += 4; + next += 4; + n_left_from -= 4; + } +#endif + + while (n_left_from > 0) + { + clib_bihash_kv_48_8_t kvp0; + ct6_session_key_t *key0; + ct6_session_t *s0; + u32 session_index0 = ~0; + u32 next0; + + ip6_header_t *ip0; + udp_header_t *udp0; + + /* Are we having fun yet? */ + vnet_feature_next (&next0, b[0]); + next[0] = next0; + + ip0 = vlib_buffer_get_current (b[0]); + + /* + * Pass non-global unicast traffic + */ + if (PREDICT_FALSE (!ip6_address_is_global_unicast (&ip0->src_address) + || + !ip6_address_is_global_unicast (&ip0->src_address))) + goto trace0; + /* Pass non-udp, non-tcp traffic */ + if (PREDICT_FALSE (ip0->protocol != IP_PROTOCOL_TCP && + ip0->protocol != IP_PROTOCOL_UDP)) + goto trace0; + + udp0 = ip6_next_header (ip0); + + /* + * See if we know about this flow. + */ + key0 = (ct6_session_key_t *) & kvp0; + clib_memcpy_fast (&key0->src, &ip0->src_address, + sizeof (ip6_address_t)); + clib_memcpy_fast (&key0->dst, &ip0->dst_address, + sizeof (ip6_address_t)); + key0->as_u64[4] = 0; + key0->as_u64[5] = 0; + key0->sport = udp0->src_port; + key0->dport = udp0->dst_port; + key0->proto = ip0->protocol; + + /* Do we know about this session? */ + if (clib_bihash_search_48_8 (&cmp->session_hash, &kvp0, &kvp0) < 0) + { + /* Bad engineer, no donut for you... */ + next[0] = CT6_OUT2IN_NEXT_DROP; + b[0]->error = node->errors[CT6_OUT2IN_ERROR_NO_SESSION]; + dropped++; + goto trace0; + } + else + { + s0 = pool_elt_at_index (cmp->sessions[my_thread_index], kvp0.value); + session_index0 = kvp0.value; + ct6_update_session_hit (cmp, s0, now); + } + + trace0: + if (is_trace) + { + if (b[0]->flags & VLIB_BUFFER_IS_TRACED) + { + ct6_out2in_trace_t *t = + vlib_add_trace (vm, node, b[0], sizeof (*t)); + t->next_index = next[0]; + t->sw_if_index = vnet_buffer (b[0])->sw_if_index[VLIB_RX]; + t->session_index = session_index0; + } + } + + b += 1; + next += 1; + n_left_from -= 1; + } + + vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors); + + vlib_node_increment_counter (vm, node->node_index, + CT6_OUT2IN_ERROR_PROCESSED, frame->n_vectors); + vlib_node_increment_counter (vm, node->node_index, + CT6_OUT2IN_ERROR_NO_SESSION, dropped); + + return frame->n_vectors; +} + +VLIB_NODE_FN (ct6_out2in_node) (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + if (PREDICT_FALSE (node->flags & VLIB_NODE_FLAG_TRACE)) + return ct6_out2in_inline (vm, node, frame, 1 /* is_trace */ ); + else + return ct6_out2in_inline (vm, node, frame, 0 /* is_trace */ ); +} + +/* *INDENT-OFF* */ +#ifndef CLIB_MARCH_VARIANT +VLIB_REGISTER_NODE (ct6_out2in_node) = +{ + .name = "ct6-out2in", + .vector_size = sizeof (u32), + .format_trace = format_ct6_out2in_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(ct6_out2in_error_strings), + .error_strings = ct6_out2in_error_strings, + + .n_next_nodes = CT6_OUT2IN_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [CT6_OUT2IN_NEXT_DROP] = "error-drop", + }, +}; +#endif /* CLIB_MARCH_VARIANT */ +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/ct6/ct6_test.c b/src/plugins/ct6/ct6_test.c new file mode 100644 index 00000000000..507620eaf0b --- /dev/null +++ b/src/plugins/ct6/ct6_test.c @@ -0,0 +1,202 @@ +/* + * ct6.c - skeleton vpp-api-test plug-in + * + * Copyright (c) + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include + +uword unformat_sw_if_index (unformat_input_t * input, va_list * args); + +/* Declare message IDs */ +#include + +/* define message structures */ +#define vl_typedefs +#include +#undef vl_typedefs + +/* declare message handlers for each api */ + +#define vl_endianfun /* define message structures */ +#include +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) +#define vl_printfun +#include +#undef vl_printfun + +/* Get the API version number. */ +#define vl_api_version(n,v) static u32 api_version=(v); +#include +#undef vl_api_version + + +typedef struct +{ + /* API message ID base */ + u16 msg_id_base; + vat_main_t *vat_main; +} ct6_test_main_t; + +ct6_test_main_t ct6_test_main; + +#define __plugin_msg_base ct6_test_main.msg_id_base +#include + +#define foreach_standard_reply_retval_handler \ +_(ct6_enable_disable_reply) + +#define _(n) \ + static void vl_api_##n##_t_handler \ + (vl_api_##n##_t * mp) \ + { \ + vat_main_t * vam = ct6_test_main.vat_main; \ + i32 retval = ntohl(mp->retval); \ + if (vam->async_mode) { \ + vam->async_errors += (retval < 0); \ + } else { \ + vam->retval = retval; \ + vam->result_ready = 1; \ + } \ + } +foreach_standard_reply_retval_handler; +#undef _ + +/* + * Table of message reply handlers, must include boilerplate handlers + * we just generated + */ +#define foreach_vpe_api_reply_msg \ +_(CT6_ENABLE_DISABLE_REPLY, ct6_enable_disable_reply) + + +static int +api_ct6_enable_disable (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + int enable_disable = 1; + u32 sw_if_index = ~0; + vl_api_ct6_enable_disable_t *mp; + u32 inside = ~0; + int ret; + + /* Parse args required to build the message */ + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index)) + ; + else if (unformat (i, "sw_if_index %d", &sw_if_index)) + ; + else if (unformat (i, "disable")) + enable_disable = 0; + else if (unformat (i, "inside") || unformat (i, "in")) + inside = 1; + else if (unformat (i, "outside") || unformat (i, "out")) + inside = 0; + else + break; + } + + if (inside == ~0) + { + errmsg ("must specify inside or outside"); + return -99; + } + + if (sw_if_index == ~0) + { + errmsg ("missing interface name / explicit sw_if_index number \n"); + return -99; + } + + /* Construct the API message */ + M (CT6_ENABLE_DISABLE, mp); + mp->sw_if_index = ntohl (sw_if_index); + mp->enable_disable = enable_disable; + mp->is_inside = (u8) inside; + + /* send it... */ + S (mp); + + /* Wait for a reply... */ + W (ret); + return ret; +} + +/* + * List of messages that the api test plugin sends, + * and that the data plane plugin processes + */ +#define foreach_vpe_api_msg \ +_(ct6_enable_disable, " [disable]") + +static void +ct6_api_hookup (vat_main_t * vam) +{ + ct6_test_main_t *ctmp = &ct6_test_main; + /* Hook up handlers for replies from the data plane plug-in */ +#define _(N,n) \ + vl_msg_api_set_handlers((VL_API_##N + ctmp->msg_id_base), \ + #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_vpe_api_reply_msg; +#undef _ + + /* API messages we can send */ +#define _(n,h) hash_set_mem (vam->function_by_name, #n, api_##n); + foreach_vpe_api_msg; +#undef _ + + /* Help strings */ +#define _(n,h) hash_set_mem (vam->help_by_name, #n, h); + foreach_vpe_api_msg; +#undef _ +} + +clib_error_t * +vat_plugin_register (vat_main_t * vam) +{ + ct6_test_main_t *ctmp = &ct6_test_main; + u8 *name; + + ctmp->vat_main = vam; + + /* Ask the vpp engine for the first assigned message-id */ + name = format (0, "ct6_%08x%c", api_version, 0); + ctmp->msg_id_base = vl_client_get_first_plugin_msg_id ((char *) name); + + if (ctmp->msg_id_base != (u16) ~ 0) + ct6_api_hookup (vam); + + vec_free (name); + + return 0; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ -- 2.16.6