From e3ad5aa68a3a8338c7ee27eaf0b45fa7e56841f4 Mon Sep 17 00:00:00 2001 From: Alexander Skorichenko Date: Thu, 12 Sep 2024 09:32:46 +0200 Subject: [PATCH] snort: API functions for plugin Also, made disconnect-instance and delete-instance functions available via cli. Type: feature Change-Id: I7939d27867959cb871b1cc7205b94410b53906fd Signed-off-by: Alexander Skorichenko --- src/plugins/snort/CMakeLists.txt | 4 + src/plugins/snort/cli.c | 205 +++++++++++++++- src/plugins/snort/dequeue.c | 2 +- src/plugins/snort/enqueue.c | 2 +- src/plugins/snort/main.c | 172 ++++++++++--- src/plugins/snort/snort.api | 226 ++++++++++++++++++ src/plugins/snort/snort.h | 22 +- src/plugins/snort/snort_api.c | 398 +++++++++++++++++++++++++++++++ test/test_snort.py | 98 ++++++++ 9 files changed, 1082 insertions(+), 47 deletions(-) create mode 100644 src/plugins/snort/snort.api create mode 100644 src/plugins/snort/snort_api.c diff --git a/src/plugins/snort/CMakeLists.txt b/src/plugins/snort/CMakeLists.txt index bd9dcdc4fdd..3fc2bd625a4 100644 --- a/src/plugins/snort/CMakeLists.txt +++ b/src/plugins/snort/CMakeLists.txt @@ -7,6 +7,10 @@ add_vpp_plugin(snort dequeue.c main.c cli.c + snort_api.c + + API_FILES + snort.api MULTIARCH_SOURCES enqueue.c diff --git a/src/plugins/snort/cli.c b/src/plugins/snort/cli.c index 08740f41b37..4b6dbc742a7 100644 --- a/src/plugins/snort/cli.c +++ b/src/plugins/snort/cli.c @@ -25,6 +25,7 @@ snort_create_instance_command_fn (vlib_main_t *vm, unformat_input_t *input, u8 *name = 0; u32 queue_size = 1024; u8 drop_on_diconnect = 1; + int rv = 0; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) @@ -60,8 +61,30 @@ snort_create_instance_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } - err = snort_instance_create (vm, (char *) name, min_log2 (queue_size), - drop_on_diconnect); + rv = snort_instance_create (vm, (char *) name, min_log2 (queue_size), + drop_on_diconnect); + + switch (rv) + { + case 0: + break; + case VNET_API_ERROR_ENTRY_ALREADY_EXISTS: + err = clib_error_return (0, "instance '%s' already exists", name); + break; + case VNET_API_ERROR_SYSCALL_ERROR_1: + err = clib_error_return (0, "memory fd failure: %U", format_clib_error, + clib_mem_get_last_error ()); + break; + case VNET_API_ERROR_SYSCALL_ERROR_2: + err = clib_error_return (0, "ftruncate failure"); + break; + case VNET_API_ERROR_SYSCALL_ERROR_3: + err = clib_error_return (0, "mmap failure"); + break; + default: + err = clib_error_return (0, "snort_instance_create returned %d", rv); + break; + } done: vec_free (name); @@ -76,6 +99,118 @@ VLIB_CLI_COMMAND (snort_create_instance_command, static) = { .function = snort_create_instance_command_fn, }; +static clib_error_t * +snort_disconnect_instance_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; + u8 *name = 0; + snort_instance_t *si; + int rv = 0; + + if (!unformat_user (input, unformat_line_input, line_input)) + return clib_error_return (0, "please specify instance name"); + + if (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + unformat (line_input, "%s", &name); + + if (!name) + { + err = clib_error_return (0, "please specify instance name"); + goto done; + } + + si = snort_get_instance_by_name ((char *) name); + if (!si) + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + else + rv = snort_instance_disconnect (vm, si->index); + + switch (rv) + { + case 0: + break; + case VNET_API_ERROR_NO_SUCH_ENTRY: + err = clib_error_return (0, "unknown instance '%s'", name); + break; + case VNET_API_ERROR_FEATURE_DISABLED: + err = clib_error_return (0, "instance '%s' is not connected", name); + break; + case VNET_API_ERROR_INVALID_VALUE: + err = clib_error_return (0, "failed to disconnect a broken client"); + break; + default: + err = clib_error_return (0, "snort_instance_disconnect returned %d", rv); + break; + } + +done: + vec_free (name); + unformat_free (line_input); + return err; +} + +VLIB_CLI_COMMAND (snort_disconnect_instance_command, static) = { + .path = "snort disconnect instance", + .short_help = "snort disconnect instance ", + .function = snort_disconnect_instance_command_fn, +}; + +static clib_error_t * +snort_delete_instance_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; + u8 *name = 0; + int rv = 0; + + if (!unformat_user (input, unformat_line_input, line_input)) + return clib_error_return (0, "please specify instance name"); + + if (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + unformat (line_input, "%s", &name); + + if (!name) + { + err = clib_error_return (0, "please specify instance name"); + goto done; + } + + snort_instance_t *si = snort_get_instance_by_name ((char *) name); + if (!si) + err = clib_error_return (0, "unknown instance '%s' requested", name); + else + rv = snort_instance_delete (vm, si->index); + + switch (rv) + { + case 0: + break; + case VNET_API_ERROR_NO_SUCH_ENTRY: + err = clib_error_return (0, "instance '%s' deletion failure", name); + break; + case VNET_API_ERROR_INSTANCE_IN_USE: + err = clib_error_return (0, "instance '%s' has connected client", name); + break; + default: + err = clib_error_return (0, "snort_instance_delete returned %d", rv); + break; + } + +done: + vec_free (name); + unformat_free (line_input); + return err; +} + +VLIB_CLI_COMMAND (snort_delete_instance_command, static) = { + .path = "snort delete instance", + .short_help = "snort delete instance ", + .function = snort_delete_instance_command_fn, +}; + static clib_error_t * snort_attach_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) @@ -86,6 +221,7 @@ snort_attach_command_fn (vlib_main_t *vm, unformat_input_t *input, u8 *name = 0; u32 sw_if_index = ~0; snort_attach_dir_t dir = SNORT_INOUT; + int rv = 0; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) @@ -124,8 +260,29 @@ snort_attach_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } - err = - snort_interface_enable_disable (vm, (char *) name, sw_if_index, 1, dir); + rv = snort_interface_enable_disable (vm, (char *) name, sw_if_index, 1, dir); + + switch (rv) + { + case 0: + break; + case VNET_API_ERROR_FEATURE_ALREADY_ENABLED: + /* already attached to same instance */ + break; + case VNET_API_ERROR_INSTANCE_IN_USE: + err = clib_error_return (0, + "interface %U already assigned to " + "an instance", + format_vnet_sw_if_index_name, vnm, sw_if_index); + break; + case VNET_API_ERROR_NO_SUCH_ENTRY: + err = clib_error_return (0, "unknown instance '%s'", name); + break; + default: + err = clib_error_return (0, "snort_interface_enable_disable returned %d", + rv); + break; + } done: vec_free (name); @@ -148,6 +305,7 @@ snort_detach_command_fn (vlib_main_t *vm, unformat_input_t *input, vnet_main_t *vnm = vnet_get_main (); clib_error_t *err = 0; u32 sw_if_index = ~0; + int rv = 0; /* Get a line of input. */ if (!unformat_user (input, unformat_line_input, line_input)) @@ -172,7 +330,23 @@ snort_detach_command_fn (vlib_main_t *vm, unformat_input_t *input, goto done; } - err = snort_interface_enable_disable (vm, 0, sw_if_index, 0, SNORT_INOUT); + rv = snort_interface_enable_disable (vm, 0, sw_if_index, 0, SNORT_INOUT); + + switch (rv) + { + case 0: + break; + case VNET_API_ERROR_INVALID_INTERFACE: + err = clib_error_return (0, + "interface %U is not assigned to snort " + "instance!", + format_vnet_sw_if_index_name, vnm, sw_if_index); + break; + default: + err = clib_error_return (0, "snort_interface_enable_disable returned %d", + rv); + break; + } done: unformat_free (line_input); @@ -213,7 +387,7 @@ snort_show_interfaces_command_fn (vlib_main_t *vm, unformat_input_t *input, snort_instance_t *si; u32 *index; - vlib_cli_output (vm, "interface\tsnort instance"); + vlib_cli_output (vm, "interface\t\tsnort instance"); vec_foreach (index, sm->instance_by_sw_if_index) { if (index[0] != ~0) @@ -237,7 +411,18 @@ snort_show_clients_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) { snort_main_t *sm = &snort_main; - vlib_cli_output (vm, "number of clients: %d", pool_elts (sm->clients)); + u32 n_clients = pool_elts (sm->clients); + snort_client_t *c; + snort_instance_t *si; + + vlib_cli_output (vm, "number of clients: %d", n_clients); + if (n_clients) + vlib_cli_output (vm, "client snort instance"); + pool_foreach (c, sm->clients) + { + si = vec_elt_at_index (sm->instances, c->instance_index); + vlib_cli_output (vm, "%6d %s", c - sm->clients, si->name); + } return 0; } @@ -251,14 +436,16 @@ static clib_error_t * snort_mode_polling_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) { - return snort_set_node_mode (vm, VLIB_NODE_STATE_POLLING); + snort_set_node_mode (vm, VLIB_NODE_STATE_POLLING); + return 0; } static clib_error_t * snort_mode_interrupt_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t *cmd) { - return snort_set_node_mode (vm, VLIB_NODE_STATE_INTERRUPT); + snort_set_node_mode (vm, VLIB_NODE_STATE_INTERRUPT); + return 0; } VLIB_CLI_COMMAND (snort_mode_polling_command, static) = { diff --git a/src/plugins/snort/dequeue.c b/src/plugins/snort/dequeue.c index 31745de404c..bc301f6888b 100644 --- a/src/plugins/snort/dequeue.c +++ b/src/plugins/snort/dequeue.c @@ -307,7 +307,7 @@ snort_deq_node_polling (vlib_main_t *vm, vlib_node_runtime_t *node, snort_qpair_t *qp; snort_instance_t *si; - vec_foreach (si, sm->instances) + pool_foreach (si, sm->instances) { qp = vec_elt_at_index (si->qpairs, vm->thread_index); u32 ready = __atomic_load_n (&qp->ready, __ATOMIC_ACQUIRE); diff --git a/src/plugins/snort/enqueue.c b/src/plugins/snort/enqueue.c index 409c0e49078..ce4f34491ec 100644 --- a/src/plugins/snort/enqueue.c +++ b/src/plugins/snort/enqueue.c @@ -133,7 +133,7 @@ snort_enq_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node, nexts, n_processed); } - vec_foreach (si, sm->instances) + pool_foreach (si, sm->instances) { u32 head, freelist_len, n_pending, n_enq, mask; u64 ctr = 1; diff --git a/src/plugins/snort/main.c b/src/plugins/snort/main.c index 2430fcdc5c2..50bff027a13 100644 --- a/src/plugins/snort/main.c +++ b/src/plugins/snort/main.c @@ -3,10 +3,24 @@ */ #include +#include #include #include #include +#include +#include + +#include +#include + +#include + +#include + +#include +#include + #include snort_main_t snort_main; @@ -18,6 +32,12 @@ VLIB_REGISTER_LOG_CLASS (snort_log, static) = { #define log_debug(fmt, ...) vlib_log_debug (snort_log.class, fmt, __VA_ARGS__) #define log_err(fmt, ...) vlib_log_err (snort_log.class, fmt, __VA_ARGS__) +snort_main_t * +snort_get_main () +{ + return &snort_main; +} + static void snort_client_disconnect (clib_file_t *uf) { @@ -45,7 +65,38 @@ snort_client_disconnect (clib_file_t *uf) pool_put (sm->clients, c); } -static snort_instance_t * +int +snort_instance_disconnect (vlib_main_t *vm, u32 instance_index) +{ + snort_main_t *sm = &snort_main; + snort_instance_t *si; + snort_client_t *client; + clib_file_main_t *fm = &file_main; + clib_file_t *uf = 0; + int rv = 0; + + si = snort_get_instance_by_index (instance_index); + if (!si) + return VNET_API_ERROR_NO_SUCH_ENTRY; + if (si->client_index == ~0) + return VNET_API_ERROR_FEATURE_DISABLED; + + client = pool_elt_at_index (sm->clients, si->client_index); + uf = clib_file_get (fm, client->file_index); + if (uf) + snort_client_disconnect (uf); + else + { + log_err ("failed to disconnect a broken client from" + "instance '%s'", + si->name); + rv = VNET_API_ERROR_INVALID_VALUE; + } + + return rv; +} + +snort_instance_t * snort_get_instance_by_name (char *name) { snort_main_t *sm = &snort_main; @@ -54,7 +105,16 @@ snort_get_instance_by_name (char *name) return 0; return vec_elt_at_index (sm->instances, p[0]); - ; +} + +snort_instance_t * +snort_get_instance_by_index (u32 instance_index) +{ + snort_main_t *sm = &snort_main; + + if (pool_is_free_index (sm->instances, instance_index)) + return 0; + return pool_elt_at_index (sm->instances, instance_index); } static clib_error_t * @@ -110,6 +170,8 @@ snort_conn_fd_read_ready (clib_file_t *uf) snort_client_disconnect (uf); return 0; } + snort_freelist_init (qp->freelist); + *qp->enq_head = *qp->deq_head = qp->next_desc = 0; } base = (u8 *) si->shm_base; @@ -281,14 +343,13 @@ snort_listener_init (vlib_main_t *vm) return 0; } -clib_error_t * +int snort_instance_create (vlib_main_t *vm, char *name, u8 log2_queue_sz, u8 drop_on_disconnect) { vlib_thread_main_t *tm = vlib_get_thread_main (); snort_main_t *sm = &snort_main; snort_instance_t *si; - clib_error_t *err = 0; u32 index, i; u8 *base = CLIB_MEM_VM_MAP_FAILED; u32 size; @@ -296,9 +357,10 @@ snort_instance_create (vlib_main_t *vm, char *name, u8 log2_queue_sz, u32 qpair_mem_sz = 0; u32 qsz = 1 << log2_queue_sz; u8 align = CLIB_CACHE_LINE_BYTES; + int rv = 0; if (snort_get_instance_by_name (name)) - return clib_error_return (0, "instance already exists"); + return VNET_API_ERROR_ENTRY_ALREADY_EXISTS; /* descriptor table */ qpair_mem_sz += round_pow2 (qsz * sizeof (daq_vpp_desc_t), align); @@ -316,14 +378,13 @@ snort_instance_create (vlib_main_t *vm, char *name, u8 log2_queue_sz, if (fd == -1) { - err = clib_error_return (0, "memory fd failure: %U", format_clib_error, - clib_mem_get_last_error ()); + rv = VNET_API_ERROR_SYSCALL_ERROR_1; goto done; } if ((ftruncate (fd, size)) == -1) { - err = clib_error_return (0, "ftruncate failure"); + rv = VNET_API_ERROR_SYSCALL_ERROR_2; goto done; } @@ -331,7 +392,7 @@ snort_instance_create (vlib_main_t *vm, char *name, u8 log2_queue_sz, if (base == CLIB_MEM_VM_MAP_FAILED) { - err = clib_error_return (0, "mmap failure"); + rv = VNET_API_ERROR_SYSCALL_ERROR_3; goto done; } @@ -399,17 +460,17 @@ snort_instance_create (vlib_main_t *vm, char *name, u8 log2_queue_sz, sm->input_mode); done: - if (err) + if (rv) { if (base != CLIB_MEM_VM_MAP_FAILED) clib_mem_vm_unmap (base); if (fd != -1) close (fd); } - return err; + return rv; } -clib_error_t * +int snort_interface_enable_disable (vlib_main_t *vm, char *instance_name, u32 sw_if_index, int is_enable, snort_attach_dir_t snort_dir) @@ -417,16 +478,16 @@ snort_interface_enable_disable (vlib_main_t *vm, char *instance_name, snort_main_t *sm = &snort_main; vnet_main_t *vnm = vnet_get_main (); snort_instance_t *si; - clib_error_t *err = 0; u64 fa_data; u32 index; + int rv = 0; if (is_enable) { if ((si = snort_get_instance_by_name (instance_name)) == 0) { - err = clib_error_return (0, "unknown instance '%s'", instance_name); - goto done; + log_err ("unknown instance '%s'", instance_name); + return VNET_API_ERROR_NO_SUCH_ENTRY; } vec_validate_init_empty (sm->instance_by_sw_if_index, sw_if_index, ~0); @@ -434,12 +495,13 @@ snort_interface_enable_disable (vlib_main_t *vm, char *instance_name, index = sm->instance_by_sw_if_index[sw_if_index]; if (index != ~0) { + if (index == si->index) + rv = VNET_API_ERROR_FEATURE_ALREADY_ENABLED; + else + rv = VNET_API_ERROR_INSTANCE_IN_USE; si = vec_elt_at_index (sm->instances, index); - err = clib_error_return (0, - "interface %U already assgined to " - "instance '%s'", - format_vnet_sw_if_index_name, vnm, - sw_if_index, si->name); + log_err ("interface %U already assgined to instance '%s'", + format_vnet_sw_if_index_name, vnm, sw_if_index, si->name); goto done; } @@ -462,11 +524,9 @@ snort_interface_enable_disable (vlib_main_t *vm, char *instance_name, if (sw_if_index >= vec_len (sm->instance_by_sw_if_index) || sm->instance_by_sw_if_index[sw_if_index] == ~0) { - err = - clib_error_return (0, - "interface %U is not assigned to snort " - "instance!", - format_vnet_sw_if_index_name, vnm, sw_if_index); + rv = VNET_API_ERROR_INVALID_INTERFACE; + log_err ("interface %U is not assigned to snort instance!", + format_vnet_sw_if_index_name, vnm, sw_if_index); goto done; } index = sm->instance_by_sw_if_index[sw_if_index]; @@ -488,12 +548,66 @@ snort_interface_enable_disable (vlib_main_t *vm, char *instance_name, } done: - if (err) - log_err ("%U", format_clib_error, err); - return 0; + return rv; } -clib_error_t * +static int +snort_strip_instance_interfaces (vlib_main_t *vm, u32 instance_index) +{ + snort_main_t *sm = &snort_main; + u32 *index; + int rv = 0; + + vec_foreach (index, sm->instance_by_sw_if_index) + { + if (*index == instance_index) + rv = snort_interface_enable_disable ( + vm, NULL, index - sm->instance_by_sw_if_index, 0, 0); + if (rv) + break; + } + + return rv; +} + +int +snort_instance_delete (vlib_main_t *vm, u32 instance_index) +{ + snort_main_t *sm = &snort_main; + snort_instance_t *si; + snort_qpair_t *qp; + int rv = 0; + + si = snort_get_instance_by_index (instance_index); + if (!si) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + if (si->client_index != ~0) + return VNET_API_ERROR_INSTANCE_IN_USE; + + if ((rv = snort_strip_instance_interfaces (vm, si->index))) + return rv; + + hash_unset_mem (sm->instance_by_name, si->name); + + clib_mem_vm_unmap (si->shm_base); + close (si->shm_fd); + + vec_foreach (qp, si->qpairs) + { + clib_file_del_by_index (&file_main, qp->deq_fd_file_index); + } + + log_debug ("deleting instance '%s'", si->name); + + vec_free (si->qpairs); + vec_free (si->name); + pool_put (sm->instances, si); + + return rv; +} + +int snort_set_node_mode (vlib_main_t *vm, u32 mode) { int i; diff --git a/src/plugins/snort/snort.api b/src/plugins/snort/snort.api new file mode 100644 index 00000000000..5c65f79e68a --- /dev/null +++ b/src/plugins/snort/snort.api @@ -0,0 +1,226 @@ +option version = "1.0.0"; + +import "vnet/interface_types.api"; +import "vnet/ip/ip_types.api"; + +define snort_instance_create { + u32 client_index; + u32 context; + u32 queue_size; + u8 drop_on_disconnect; + string name[]; +}; + +define snort_instance_create_reply { + u32 context; + i32 retval; + u32 instance_index; +}; + +define snort_instance_delete { + u32 client_index; + u32 context; + u32 instance_index; +}; + +define snort_instance_delete_reply { + u32 context; + i32 retval; +}; + +define snort_client_disconnect { + u32 client_index; + u32 context; + u32 snort_client_index; +}; + +define snort_client_disconnect_reply { + u32 context; + i32 retval; +}; + +define snort_instance_disconnect { + u32 client_index; + u32 context; + u32 instance_index; +}; + +define snort_instance_disconnect_reply { + u32 context; + i32 retval; +}; + +define snort_interface_attach { + u32 client_index; + u32 context; + u32 instance_index; + u32 sw_if_index; + u8 snort_dir; +}; + +define snort_interface_attach_reply { + u32 context; + i32 retval; +}; + +define snort_interface_detach { + u32 client_index; + u32 context; + u32 sw_if_index; +}; + +define snort_interface_detach_reply { + u32 context; + i32 retval; +}; + +define snort_input_mode_get { + u32 client_index; + u32 context; +}; + +define snort_input_mode_get_reply { + u32 context; + i32 retval; + u32 snort_mode; +}; + +define snort_input_mode_set { + u32 client_index; + u32 context; + u8 input_mode; +}; + +define snort_input_mode_set_reply { + u32 context; + i32 retval; +}; + +service { + rpc snort_instance_get returns snort_instance_get_reply + stream snort_instance_details; +}; + +/** \brief Get snort instance(s). + @param client_index - opaque cookie to identify the sender. + @param context - sender context + @param cursor - current iterator value (all requested). + @param instance_index - instance index (~0 for all). +*/ +define snort_instance_get +{ + u32 client_index; + u32 context; + u32 cursor; + u32 instance_index; +}; + +/** \brief Reply for snort instance(s). + @param context - sender context + @param retval - return code for the request. + @param cursor - iterator value to continue with (if there is more). +*/ +define snort_instance_get_reply +{ + u32 context; + i32 retval; + u32 cursor; +}; + +/** \brief Details of a snort instance. + @param context - sender context + @param instance - snort instance info. +*/ +define snort_instance_details { + u32 context; + u32 instance_index; + u32 shm_size; + u32 shm_fd; + u8 drop_on_disconnect; + u32 snort_client_index; + string name[]; +}; + +service { + rpc snort_interface_get returns snort_interface_get_reply + stream snort_interface_details; +}; + +/** \brief Get snort interface(s). + @param client_index - opaque cookie to identify the sender. + @param context - sender context + @param cursor - current iterator value (all requested). + @param sw_if_index - sw if index (~0 for all). +*/ +define snort_interface_get +{ + u32 client_index; + u32 context; + u32 cursor; + u32 sw_if_index; +}; + +/** \brief Reply for snort interface(s). + @param context - sender context + @param retval - return code for the request. + @param cursor - iterator value to continue with (if there is more). +*/ +define snort_interface_get_reply +{ + u32 context; + i32 retval; + u32 cursor; +}; + +/** \brief Details of a snort interface. + @param context - sender context + @param sw_if_index - interface index + @param instance_index - snort instance the interface is attached to. +*/ +define snort_interface_details { + u32 context; + u32 sw_if_index; + u32 instance_index; +}; + +service { + rpc snort_client_get returns snort_client_get_reply + stream snort_client_details; +}; + +/** \brief Get snort clients. + @param client_index - opaque cookie to identify the sender. + @param context - sender context + @param cursor - current iterator value (all requested). + @param client_index (~0 for all). +*/ +define snort_client_get +{ + u32 client_index; + u32 context; + u32 cursor; + u32 snort_client_index; +}; + +/** \brief Reply for snort clients. + @param context - sender context + @param retval - return code for the request. + @param cursor - iterator value to continue with (if there is more). +*/ +define snort_client_get_reply +{ + u32 context; + i32 retval; + u32 cursor; +}; + +/** \brief Details of a snort client. + @param context - sender context + @param client index + @param instance_index - snort instance of the client. +*/ +define snort_client_details { + u32 context; + u32 client_index; + u32 instance_index; +}; diff --git a/src/plugins/snort/snort.h b/src/plugins/snort/snort.h index 79299aa6d91..c7e856c0127 100644 --- a/src/plugins/snort/snort.h +++ b/src/plugins/snort/snort.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -78,8 +79,11 @@ typedef struct snort_per_thread_data_t *per_thread_data; u32 input_mode; u8 *socket_name; + /* API message ID base */ + u16 msg_id_base; } snort_main_t; +extern clib_file_main_t file_main; extern snort_main_t snort_main; extern vlib_node_registration_t snort_enq_node; extern vlib_node_registration_t snort_deq_node; @@ -103,13 +107,17 @@ typedef enum } /* functions */ -clib_error_t *snort_instance_create (vlib_main_t *vm, char *name, - u8 log2_queue_sz, u8 drop_on_disconnect); -clib_error_t *snort_interface_enable_disable (vlib_main_t *vm, - char *instance_name, - u32 sw_if_index, int is_enable, - snort_attach_dir_t dir); -clib_error_t *snort_set_node_mode (vlib_main_t *vm, u32 mode); +snort_main_t *snort_get_main (); +snort_instance_t *snort_get_instance_by_index (u32 instance_index); +snort_instance_t *snort_get_instance_by_name (char *name); +int snort_instance_create (vlib_main_t *vm, char *name, u8 log2_queue_sz, + u8 drop_on_disconnect); +int snort_interface_enable_disable (vlib_main_t *vm, char *instance_name, + u32 sw_if_index, int is_enable, + snort_attach_dir_t dir); +int snort_set_node_mode (vlib_main_t *vm, u32 mode); +int snort_instance_delete (vlib_main_t *vm, u32 instance_index); +int snort_instance_disconnect (vlib_main_t *vm, u32 instance_index); always_inline void snort_freelist_init (u32 *fl) diff --git a/src/plugins/snort/snort_api.c b/src/plugins/snort/snort_api.c new file mode 100644 index 00000000000..334a84b4341 --- /dev/null +++ b/src/plugins/snort/snort_api.c @@ -0,0 +1,398 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +/** + * Base message ID fot the plugin + */ +static u32 snort_base_msg_id; +#define REPLY_MSG_ID_BASE snort_base_msg_id + +#include + +#include + +#include +#include + +VLIB_REGISTER_LOG_CLASS (snort_log, static) = { + .class_name = "snort", +}; + +#define log_debug(fmt, ...) vlib_log_debug (snort_log.class, fmt, __VA_ARGS__) +#define log_err(fmt, ...) vlib_log_err (snort_log.class, fmt, __VA_ARGS__) + +static void +vl_api_snort_instance_create_t_handler (vl_api_snort_instance_create_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_snort_instance_create_reply_t *rmp; + char *name = vl_api_from_api_to_new_c_string (&mp->name); + u32 queue_sz = clib_net_to_host_u32 (mp->queue_size); + u8 drop_on_disconnect = mp->drop_on_disconnect; + int rv = 0; + u32 instance_index = ~0; + snort_instance_t *si; + + rv = + snort_instance_create (vm, name, min_log2 (queue_sz), drop_on_disconnect); + + if ((si = snort_get_instance_by_name (name))) + { + instance_index = si->index; + } + + REPLY_MACRO2 (VL_API_SNORT_INSTANCE_CREATE_REPLY, ({ + rmp->instance_index = clib_host_to_net_u32 (instance_index); + })); +} + +static void +vl_api_snort_instance_delete_t_handler (vl_api_snort_instance_delete_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_snort_instance_delete_reply_t *rmp; + u32 instance_index = clib_net_to_host_u32 (mp->instance_index); + int rv; + + rv = snort_instance_delete (vm, instance_index); + + REPLY_MACRO (VL_API_SNORT_INSTANCE_DELETE_REPLY); +} + +static void +vl_api_snort_interface_attach_t_handler (vl_api_snort_interface_attach_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_snort_interface_attach_reply_t *rmp; + u32 instance_index = clib_net_to_host_u32 (mp->instance_index); + snort_instance_t *instance = 0; + u32 sw_if_index = clib_net_to_host_u32 (mp->sw_if_index); + u8 snort_dir = mp->snort_dir; + int rv = VNET_API_ERROR_NO_SUCH_ENTRY; + + instance = snort_get_instance_by_index (instance_index); + if (instance) + rv = snort_interface_enable_disable ( + vm, (char *) instance->name, sw_if_index, 1 /* is_enable */, snort_dir); + + REPLY_MACRO (VL_API_SNORT_INTERFACE_ATTACH_REPLY); +} + +static void +send_snort_instance_details (const snort_instance_t *instance, + vl_api_registration_t *rp, u32 context) +{ + vl_api_snort_instance_details_t *rmp; + u32 name_len = vec_len (instance->name); + + REPLY_MACRO_DETAILS5 ( + VL_API_SNORT_INSTANCE_DETAILS, name_len, rp, context, ({ + rmp->instance_index = clib_host_to_net_u32 (instance->index); + vl_api_vec_to_api_string (instance->name, &rmp->name); + rmp->snort_client_index = clib_host_to_net_u32 (instance->client_index); + rmp->shm_size = clib_host_to_net_u32 (instance->shm_size); + rmp->shm_fd = clib_host_to_net_u32 (instance->shm_fd); + rmp->drop_on_disconnect = instance->drop_on_disconnect; + })); +} + +static void +vl_api_snort_instance_get_t_handler (vl_api_snort_instance_get_t *mp) +{ + snort_main_t *sm = snort_get_main (); + snort_instance_t *instance = 0; + vl_api_snort_instance_get_reply_t *rmp; + u32 instance_index; + int rv = 0; + + instance_index = clib_net_to_host_u32 (mp->instance_index); + + if (instance_index == INDEX_INVALID) + { + /* clang-format off */ + REPLY_AND_DETAILS_MACRO ( + VL_API_SNORT_INSTANCE_GET_REPLY, sm->instances, ({ + instance = pool_elt_at_index (sm->instances, cursor); + send_snort_instance_details (instance, rp, mp->context); + })); + /* clang-format on */ + } + else + { + instance = snort_get_instance_by_index (instance_index); + + if (instance) + { + vl_api_registration_t *rp = + vl_api_client_index_to_registration (mp->client_index); + + if (rp == NULL) + { + return; + } + + send_snort_instance_details (instance, rp, mp->context); + } + else + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + } + + /* clang-format off */ + REPLY_MACRO2 (VL_API_SNORT_INSTANCE_GET_REPLY, ({ + rmp->cursor = INDEX_INVALID; + })); + /* clang-format on */ + } +} + +static void +send_snort_interface_details (u32 sw_if_index, u32 instance_index, + vl_api_registration_t *rp, u32 context) +{ + vl_api_snort_interface_details_t *rmp; + + if (instance_index != ~0) + { + REPLY_MACRO_DETAILS4 (VL_API_SNORT_INTERFACE_DETAILS, rp, context, ({ + rmp->instance_index = + clib_host_to_net_u32 (instance_index); + rmp->sw_if_index = + clib_host_to_net_u32 (sw_if_index); + })); + } +} + +static void +vl_api_snort_interface_get_t_handler (vl_api_snort_interface_get_t *mp) +{ + snort_main_t *sm = snort_get_main (); + vl_api_snort_interface_get_reply_t *rmp; + u32 sw_if_index; + u32 *index; + int rv = 0; + + sw_if_index = clib_net_to_host_u32 (mp->sw_if_index); + + if (sw_if_index == INDEX_INVALID) + { + /* clang-format off */ + if (vec_len (sm->instance_by_sw_if_index) == 0) + { + REPLY_MACRO2 (VL_API_SNORT_INTERFACE_GET_REPLY, ({ rmp->cursor = ~0; })); + return; + } + + REPLY_AND_DETAILS_VEC_MACRO( + VL_API_SNORT_INTERFACE_GET_REPLY, + sm->instance_by_sw_if_index, + mp, rmp, rv, ({ + index = vec_elt_at_index (sm->instance_by_sw_if_index, cursor); + send_snort_interface_details (cursor, *index, rp, mp->context); + })) + /* clang-format on */ + } + else + { + index = vec_elt_at_index (sm->instance_by_sw_if_index, sw_if_index); + if (snort_get_instance_by_index (index[0])) + { + vl_api_registration_t *rp = + vl_api_client_index_to_registration (mp->client_index); + + if (rp == NULL) + { + return; + } + + send_snort_interface_details (sw_if_index, *index, rp, mp->context); + } + else + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + } + + /* clang-format off */ + REPLY_MACRO2 (VL_API_SNORT_INTERFACE_GET_REPLY, ({ + rmp->cursor = INDEX_INVALID; + })); + /* clang-format on */ + } +} + +static void +send_snort_client_details (const snort_client_t *client, + vl_api_registration_t *rp, u32 context) +{ + snort_main_t *sm = snort_get_main (); + vl_api_snort_client_details_t *rmp; + snort_instance_t *instance; + + if (client->instance_index == ~0) + { + return; + } + + instance = pool_elt_at_index (sm->instances, client->instance_index); + if (instance) + { + REPLY_MACRO_DETAILS4 (VL_API_SNORT_CLIENT_DETAILS, rp, context, ({ + rmp->instance_index = + clib_host_to_net_u32 (client->instance_index); + rmp->client_index = + clib_host_to_net_u32 (client - sm->clients); + })); + } +} + +static void +vl_api_snort_client_get_t_handler (vl_api_snort_client_get_t *mp) +{ + snort_main_t *sm = snort_get_main (); + snort_client_t *client; + vl_api_snort_client_get_reply_t *rmp; + u32 client_index; + int rv = 0; + + client_index = clib_net_to_host_u32 (mp->snort_client_index); + + if (client_index == INDEX_INVALID) + { + /* clang-format off */ + REPLY_AND_DETAILS_MACRO ( + VL_API_SNORT_CLIENT_GET_REPLY, sm->clients, ({ + client = pool_elt_at_index (sm->clients, cursor); + send_snort_client_details (client, rp, mp->context); + })); + /* clang-format on */ + } + else + { + client = pool_elt_at_index (sm->clients, client_index); + + if (client) + { + vl_api_registration_t *rp = + vl_api_client_index_to_registration (mp->client_index); + + if (rp == NULL) + { + return; + } + + send_snort_client_details (client, rp, mp->context); + } + else + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + } + + /* clang-format off */ + REPLY_MACRO2 (VL_API_SNORT_CLIENT_GET_REPLY, ({ + rmp->cursor = INDEX_INVALID; + })); + /* clang-format on */ + } +} + +static void +vl_api_snort_client_disconnect_t_handler (vl_api_snort_client_disconnect_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + snort_main_t *sm = snort_get_main (); + snort_client_t *client; + vl_api_snort_client_disconnect_reply_t *rmp; + u32 client_index = clib_net_to_host_u32 (mp->snort_client_index); + int rv = 0; + + if (pool_is_free_index (sm->clients, client_index)) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + } + else + { + client = pool_elt_at_index (sm->clients, client_index); + rv = snort_instance_disconnect (vm, client->instance_index); + } + + REPLY_MACRO (VL_API_SNORT_CLIENT_DISCONNECT_REPLY); +} + +static void +vl_api_snort_instance_disconnect_t_handler ( + vl_api_snort_instance_disconnect_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_snort_instance_disconnect_reply_t *rmp; + u32 instance_index = clib_net_to_host_u32 (mp->instance_index); + int rv = snort_instance_disconnect (vm, instance_index); + + REPLY_MACRO (VL_API_SNORT_INSTANCE_DISCONNECT_REPLY); +} + +static void +vl_api_snort_interface_detach_t_handler (vl_api_snort_interface_detach_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_snort_interface_detach_reply_t *rmp; + u32 sw_if_index = clib_net_to_host_u32 (mp->sw_if_index); + int rv; + + rv = snort_interface_enable_disable (vm, NULL, sw_if_index, + 0 /* is_enable */, 0); + + REPLY_MACRO (VL_API_SNORT_INTERFACE_DETACH_REPLY); +} + +static void +vl_api_snort_input_mode_get_t_handler (vl_api_snort_input_mode_get_t *mp) +{ + snort_main_t *sm = &snort_main; + vl_api_snort_input_mode_get_reply_t *rmp; + int rv = 0; + + REPLY_MACRO2 (VL_API_SNORT_INPUT_MODE_GET_REPLY, ({ + rmp->snort_mode = clib_host_to_net_u32 (sm->input_mode); + })); +} + +static void +vl_api_snort_input_mode_set_t_handler (vl_api_snort_input_mode_set_t *mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_snort_input_mode_set_reply_t *rmp; + u8 mode = mp->input_mode; + int rv = 0; + + if (mode != VLIB_NODE_STATE_INTERRUPT && mode != VLIB_NODE_STATE_POLLING) + { + clib_error_return (0, "invalid input mode %u", mode); + } + snort_set_node_mode (vm, mode); + + REPLY_MACRO (VL_API_SNORT_INPUT_MODE_SET_REPLY); +} + +/* API definitions */ +#include + +clib_error_t * +snort_init_api (vlib_main_t *vm) +{ + /* Add our API messages to the global name_crc hash table */ + snort_base_msg_id = setup_message_id_table (); + + return NULL; +} + +VLIB_INIT_FUNCTION (snort_init_api); diff --git a/test/test_snort.py b/test/test_snort.py index 352eaa315a9..19401cb7b85 100644 --- a/test/test_snort.py +++ b/test/test_snort.py @@ -30,6 +30,7 @@ class TestSnort(VppTestCase): def test_snort_cli(self): # TODO: add a test with packets # { cli command : part of the expected reply } + print("TEST SNORT CLI") commands_replies = { "snort create-instance name snortTest queue-size 16 on-disconnect drop": "", "snort create-instance name snortTest2 queue-size 16 on-disconnect pass": "", @@ -43,6 +44,7 @@ class TestSnort(VppTestCase): "snort mode interrupt": "", "snort detach interface pg0": "", "snort detach interface pg1": "", + "snort delete instance snortTest": "", } for command, reply in commands_replies.items(): @@ -50,5 +52,101 @@ class TestSnort(VppTestCase): self.assertIn(reply, actual_reply) +@unittest.skipIf("snort" in config.excluded_plugins, "Exclude snort plugin test") +class TestSnortVapi(VppTestCase): + """Snort plugin test [VAPI]""" + + @classmethod + def setUpClass(cls): + super(TestSnortVapi, cls).setUpClass() + try: + cls.create_pg_interfaces(range(2)) + for i in cls.pg_interfaces: + i.config_ip4() + i.resolve_arp() + i.admin_up() + except Exception: + cls.tearDownClass() + raise + + @classmethod + def tearDownClass(cls): + for i in cls.pg_interfaces: + i.unconfig_ip4() + i.admin_down() + super(TestSnortVapi, cls).tearDownClass() + + def test_snort_01_modes_set_interrupt(self): + """Set mode to interrupt""" + self.vapi.snort_input_mode_set(input_mode=1) + reply = self.vapi.snort_input_mode_get() + self.assertEqual(reply.snort_mode, 1) + reply = self.vapi.cli("show snort mode") + self.assertIn("interrupt", reply) + + def test_snort_02_modes_set_polling(self): + """Set mode to polling""" + self.vapi.snort_input_mode_set(input_mode=0) + reply = self.vapi.snort_input_mode_get() + self.assertEqual(reply.snort_mode, 0) + + def test_snort_03_create(self): + """Create two snort instances""" + reply = self.vapi.snort_instance_create( + queue_size=8, drop_on_disconnect=0, name="snortTest0" + ) + self.assertEqual(reply.instance_index, 0) + reply = self.vapi.snort_instance_create( + queue_size=32, drop_on_disconnect=1, name="snortTest1" + ) + self.assertEqual(reply.instance_index, 1) + reply = self.vapi.cli("show snort instances") + self.assertIn("snortTest0", reply) + self.assertIn("snortTest1", reply) + + def test_snort_04_attach_if(self): + """Interfaces can be attached""" + reply = self.vapi.snort_interface_attach( + instance_index=0, sw_if_index=1, snort_dir=1 + ) + try: + reply = self.vapi.snort_interface_attach( + instance_index=1, sw_if_index=1, snort_dir=1 + ) + except: + pass + else: + self.assertNotEqual(reply.retval, 0) + + reply = self.vapi.snort_interface_attach( + instance_index=1, sw_if_index=2, snort_dir=3 + ) + reply = self.vapi.cli("show snort interfaces") + self.assertIn("snortTest0", reply) + self.assertIn("snortTest1", reply) + + def test_snort_05_delete_instance(self): + """Instances can be deleted""" + reply = self.vapi.snort_instance_delete(instance_index=0) + reply = self.vapi.cli("show snort interfaces") + self.assertNotIn("snortTest0", reply) + self.assertIn("snortTest1", reply) + reply = self.vapi.cli("show snort interfaces") + self.assertNotIn("pg0", reply) + self.assertIn("pg1", reply) + + def test_snort_06_detach_if(self): + """Interfaces can be detached""" + try: + reply = self.vapi.snort_interface_detach(sw_if_index=1) + except: + pass + else: + self.assertNotEqual(reply.retval, 0) + reply = self.vapi.snort_interface_detach(sw_if_index=2) + reply = self.vapi.cli("show snort interfaces") + self.assertNotIn("pg1", reply) + + if __name__ == "__main__": unittest.main(testRunner=VppTestRunner)