diff options
-rw-r--r-- | src/CMakeLists.txt | 44 | ||||
-rw-r--r-- | src/dpi.api | 42 | ||||
-rw-r--r-- | src/dpi.c | 723 | ||||
-rw-r--r-- | src/dpi.h | 330 | ||||
-rw-r--r-- | src/dpi_api.c | 160 | ||||
-rw-r--r-- | src/dpi_app_match.h | 124 | ||||
-rw-r--r-- | src/dpi_cli.c | 357 | ||||
-rw-r--r-- | src/dpi_node.c | 1042 | ||||
-rw-r--r-- | src/dpi_plugin_doc.md | 107 | ||||
-rw-r--r-- | src/protocols/dpi_ssl.c | 247 |
10 files changed, 3176 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..5eb40d3 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,44 @@ +# Copyright (c) 2018 Intel 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. + +message(STATUS "Looking for Hyperscan") +find_path(HYPERSCAN_INCLUDE_DIR NAMES hs/hs.h) +find_library(HYPERSCAN_LIB1 NAMES hs) +find_library(HYPERSCAN_LIB2 NAMES hs_runtime) +set (HYPERSCAN_LIB ${HYPERSCAN_LIB1} ${HYPERSCAN_LIB2}) + +if(HYPERSCAN_INCLUDE_DIR AND HYPERSCAN_LIB) + include_directories(${HYPERSCAN_INCLUDE_DIR}) + add_vpp_plugin(dpi + SOURCES + dpi.c + dpi_api.c + dpi_cli.c + dpi_node.c + protocols/dpi_ssl.c + + API_FILES + dpi.api + + INSTALL_HEADERS + dpi_app_match.h + dpi.h + + LINK_LIBRARIES + ${HYPERSCAN_LIB} + ) + message(STATUS "Found Hyperscan in ${HYPERSCAN_INCLUDE_DIR}") +else() + message(WARNING "-- Hyperscan not found - dpi_plugin disabled") +endif() + diff --git a/src/dpi.api b/src/dpi.api new file mode 100644 index 0000000..b9fa99e --- /dev/null +++ b/src/dpi.api @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Intel, Travelping and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +option version = "1.0.0"; + +define dpi_flow_add_del { + u32 client_index; + u32 context; + u8 is_add; + u8 is_ipv6; + u8 src_ip[16]; + u8 dst_ip[16]; + u16 src_port; + u16 dst_port; + u32 vrf_id; + u8 protocol; +}; + +define dpi_flow_add_del_reply +{ + u32 context; + i32 retval; + u32 flow_id; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/dpi.c b/src/dpi.c new file mode 100644 index 0000000..1cb482e --- /dev/null +++ b/src/dpi.c @@ -0,0 +1,723 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 Intel, Travelping 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 <stdint.h> +#include <string.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <inttypes.h> + +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/fib/fib_entry.h> +#include <vnet/fib/fib_table.h> +#include <vnet/mfib/mfib_table.h> +#include <vnet/adj/adj_mcast.h> +#include <vnet/dpo/dpo.h> +#include <vnet/plugin/plugin.h> +#include <vpp/app/version.h> +#include <vnet/flow/flow.h> + +#include <dpi/dpi.h> + +dpi_main_t dpi_main; +dpi_entry_t *dpi_dbs = NULL; + +#if CLIB_DEBUG > 0 +#define dpi_debug clib_warning +#else +#define dpi_debug(...) \ + do { } while (0) +#endif + +/* Here rules are extracted from below link with BSD License + * https://rules.emergingthreats.net/open-nogpl/snort-2.9.0/emerging-all.rules */ + +dpi_app_match_rule app_match_rules[] = { + {"www.cisco.com", NULL, "Cisco", DPI_APP_CISCO} + , + {"*.google.com", NULL, "Google", DPI_APP_GOOGLE} + , + {"www.bing.com", NULL, "Bing", DPI_APP_BING} + , + {"www.msn.com", NULL, "MSN", DPI_APP_MSN} + , + {"www.yahoo.com", NULL, "", DPI_APP_YAHOO} + , + {"mail.yahoo.com", NULL, "YahooMail", DPI_APP_YAHOOMAIL} + , + {"www.intel.com", NULL, "Intel", DPI_APP_INTEL} + , + {"*.amazon.com", NULL, "Amazon", DPI_APP_AMAZON} + , + {"*.amd.com", NULL, "AMD", DPI_APP_AMD} + , + {"*.baidu.com", NULL, "Baidu", DPI_APP_BAIDU} + , + {"*.apple.com", NULL, "Apple", DPI_APP_APPLE} + , + {"*.facebook.com", NULL, "Facebook", DPI_APP_FACEBOOK} + , + {"*.ebay.com", NULL, "Ebay", DPI_APP_EBAY} + , + {"*.github.com", NULL, "GitHub", DPI_APP_GITHUB} + , + {"*.gmail.com", NULL, "Gmail", DPI_APP_GMAIL} + , + {"*.qq.com", NULL, "QQ", DPI_APP_QQ} + , + {"weixin.qq.com", NULL, "Wechat", DPI_APP_WECHAT} + , + {"*.pinterest.com", NULL, "", DPI_APP_PINTEREST} + , + {"*.lenovo.com", NULL, "Levono", DPI_APP_LENOVO} + , + {"*.linkedin.com", NULL, "LinkedIn", DPI_APP_LINKEDIN} + , + {"*.skype.com", NULL, "Skype", DPI_APP_SKYPE} + , + {"*.microsoft.com", NULL, "Microsoft", DPI_APP_MICROSOFT} + , + {"*.netflix.com", NULL, "Netflix", DPI_APP_NETFLIX} + , + {"*.nokia.com", NULL, "Nokia", DPI_APP_NOKIA} + , + {"*.nvidia.com", NULL, "nVIDIA", DPI_APP_NVIDIA} + , + {"*.office365.com", NULL, "Office", DPI_APP_OFFICE} + , + {"*.oracle.com", NULL, "Oracle", DPI_APP_ORACLE} + , + {"*.Outlook.com", NULL, "Outlook", DPI_APP_OUTLOOK} + , + {"*.pandora.com", NULL, "Pandora", DPI_APP_PANDORA} + , + {"*.paypal.com", NULL, "Paypal", DPI_APP_PAYPAL} + , + {"*.sina.com", NULL, "Sina", DPI_APP_SINA} + , + {"*.sogou.com", NULL, "Sogou", DPI_APP_SOGOU} + , + {"*.symantec.com", NULL, "Symantec", DPI_APP_SYMANTEC} + , + {"*.taobao.com", NULL, "Taobao", DPI_APP_TAOBAO} + , + {"*.twitter.com", NULL, "Twitter", DPI_APP_TWITTER} + , + {"*.ups.com", NULL, "UPS", DPI_APP_UPS} + , + {"*.visa.com", NULL, "VISA", DPI_APP_VISA} + , + {"*.mcafee.com", NULL, "Mcafee", DPI_APP_MCAFEE} + , + {"*.vmware.com", NULL, "VMWare", DPI_APP_VMWARE} + , + {"*.wordpress.com", NULL, "Wordpress", DPI_APP_WORDPRESS} + , + {"www.adobe.com", NULL, "Adobe", DPI_APP_ADOBE} + , + {"www.akamai.com", NULL, "Akamai", DPI_APP_AKAMAI} + , + {"*.alienvault.com", NULL, "Alienvault", DPI_APP_ALIENVAULT} + , + {"www.bitcomet.com", NULL, "Bitcomet", DPI_APP_BITCOMET} + , + {"www.checkpoint.com", NULL, "Checkpoint", DPI_APP_CHECKPOINT} + , + {"*.bloomberg.com", NULL, "Bloomberg", DPI_APP_BLOOMBERG} + , + {"www.dell.com", NULL, "DELL", DPI_APP_DELL} + , + {"www.f5.com", NULL, "F5", DPI_APP_F5} + , + {"www.fireeye.com", NULL, "Fireeye", DPI_APP_FIREEYE} + , + {"*.dropbox.com", NULL, "", DPI_APP_DROPBOX} + , + + {NULL, NULL, NULL, 0} +}; + +int +dpi_event_handler (unsigned int id, unsigned long long from, + unsigned long long to, unsigned int flags, void *ctx) +{ + (void) from; + (void) to; + (void) flags; + + dpi_cb_args_t *args = (dpi_cb_args_t *) ctx; + + args->res = 1; + args->id = id; + + return 0; +} + +int +dpi_search_host_protocol (dpi_flow_info_t * flow, + char *str_to_match, + u32 str_to_match_len, + u16 master_protocol_id, u32 * host_protocol_id) +{ + dpi_main_t *dm = &dpi_main; + dpi_entry_t entry = dm->default_db; + dpi_cb_args_t args = { }; + int ret; + + /* First search default database */ + ret = hs_scan_stream (flow->stream, + (const char *) str_to_match, str_to_match_len, 0, + entry.scratch, dpi_event_handler, (void *) &args); + if ((ret != HS_SUCCESS) && (ret != HS_SCAN_TERMINATED)) + { + return DPI_PROTOCOL_UNKNOWN; + } + else + { + flow->app_id = args.id; + flow->detect_done = 1; + goto done; + } + +done: + if (flow->app_id != ~0) + { + /* Move the protocol to right position */ + flow->detected_protocol[1] = master_protocol_id, + flow->detected_protocol[0] = flow->app_id; + *host_protocol_id = flow->app_id; + + return (flow->detected_protocol[0]); + } + + return DPI_PROTOCOL_UNKNOWN; +} + +char * +host2hex (const char *str) +{ + int len, i; + char *hexbuf, *buf; + + len = strlen (str); + hexbuf = (char *) malloc (len * 4 + 1); + if (!hexbuf) + return (NULL); + + for (i = 0, buf = hexbuf; i < len; i++, buf += 4) + { + snprintf (buf, 5, "\\x%02x", (const char) str[i]); + } + *buf = '\0'; + + return hexbuf; +} + +int +dpi_create_db_entry (dpi_entry_t * entry, u32 num, u32 mode) +{ + hs_compile_error_t *compile_err; + + if (hs_compile_multi + ((const char **) entry->expressions, entry->flags, entry->ids, + num, mode, NULL, &entry->database, &compile_err) != HS_SUCCESS) + { + return -1; + } + + if (hs_alloc_scratch (entry->database, &entry->scratch) != HS_SUCCESS) + { + hs_free_database (entry->database); + entry->database = NULL; + return -1; + } + + return 0; +} + + +int +dpi_flow_add_del (dpi_add_del_flow_args_t * a, u32 * flow_idp) +{ + dpi_main_t *dm = &dpi_main; + vnet_main_t *vnm = dm->vnet_main; + dpi4_flow_key_t key4; + dpi6_flow_key_t key6; + dpi_flow_entry_t *p; + u32 is_ip6 = a->is_ipv6; + u32 flow_id; + dpi_flow_entry_t *flow; + + int not_found; + if (!is_ip6) + { + key4.key[0] = a->src_ip.ip4.as_u32 + | (((u64) a->dst_ip.ip4.as_u32) << 32); + key4.key[1] = (((u64) a->protocol) << 32) + | ((u32) clib_host_to_net_u16 (a->src_port) << 16) + | clib_host_to_net_u16 (a->dst_port); + key4.key[2] = (u64) a->fib_index; + + not_found = + clib_bihash_search_inline_24_8 (&dm->dpi4_flow_by_key, &key4); + p = (void *) &key4.value; + } + else + { + key6.key[0] = a->src_ip.ip6.as_u64[0]; + key6.key[1] = a->src_ip.ip6.as_u64[1]; + key6.key[2] = a->dst_ip.ip6.as_u64[0]; + key6.key[3] = a->dst_ip.ip6.as_u64[1]; + key6.key[4] = (((u64) a->protocol) << 32) + | ((u32) clib_host_to_net_u16 (a->src_port) << 16) + | clib_host_to_net_u16 (a->dst_port); + key6.key[5] = (u64) a->fib_index; + + not_found = + clib_bihash_search_inline_48_8 (&dm->dpi6_flow_by_key, &key6); + p = (void *) &key6.value; + } + + if (not_found) + p = 0; + + if (a->is_add) + { + + /* adding a flow entry: entry must not already exist */ + if (p) + return VNET_API_ERROR_TUNNEL_EXIST; + + pool_get_aligned (dm->dpi_flows, flow, CLIB_CACHE_LINE_BYTES); + clib_memset (flow, 0, sizeof (*flow)); + flow_id = flow - dm->dpi_flows; + + /* copy from arg structure */ +#define _(x) flow->key.x = a->x; + foreach_copy_field; +#undef _ + + flow->next_index = DPI_INPUT_NEXT_IP4_LOOKUP; + flow->flow_index = ~0; + + pool_get_aligned (dm->dpi_infos, flow->info, CLIB_CACHE_LINE_BYTES); + clib_memset (flow->info, 0, sizeof (*flow->info)); + flow->info->app_id = ~0; + + int add_failed; + if (is_ip6) + { + key6.value = (u64) flow_id; + add_failed = clib_bihash_add_del_48_8 (&dm->dpi6_flow_by_key, + &key6, 1 /*add */ ); + } + else + { + key4.value = (u64) flow_id; + add_failed = clib_bihash_add_del_24_8 (&dm->dpi4_flow_by_key, + &key4, 1 /*add */ ); + } + + if (add_failed) + { + pool_put (dm->dpi_infos, flow->info); + pool_put (dm->dpi_flows, flow); + return VNET_API_ERROR_INVALID_REGISTRATION; + } + + /* Open a Hyperscan stream for each flow */ + hs_error_t err = hs_open_stream (dm->default_db.database, 0, + &(flow->info->stream)); + if (err != HS_SUCCESS) + { + pool_put (dm->dpi_infos, flow->info); + pool_put (dm->dpi_flows, flow); + return VNET_API_ERROR_INVALID_REGISTRATION; + } + } + else + { + /* deleting a flow: flow must exist */ + if (!p) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + flow_id = is_ip6 ? key6.value : key4.value; + flow_id = (u32) (flow_id & (u32) (~0)); + flow = pool_elt_at_index (dm->dpi_flows, flow_id); + + if (!is_ip6) + clib_bihash_add_del_24_8 (&dm->dpi4_flow_by_key, &key4, 0 /*del */ ); + else + clib_bihash_add_del_48_8 (&dm->dpi6_flow_by_key, &key6, 0 /*del */ ); + + if (flow->flow_index != ~0) + vnet_flow_del (vnm, flow->flow_index); + + /* Close the Hyperscan stream for each flow */ + hs_error_t err = hs_close_stream (flow->info->stream, NULL, + NULL, NULL); + if (err != HS_SUCCESS) + { + return VNET_API_ERROR_INVALID_REGISTRATION; + } + + pool_put (dm->dpi_infos, flow->info); + pool_put (dm->dpi_flows, flow); + } + + if (flow_idp) + *flow_idp = flow_id; + + return 0; +} + +int +dpi_reverse_flow_add_del (dpi_add_del_flow_args_t * a, u32 flow_id) +{ + dpi_main_t *dm = &dpi_main; + vnet_main_t *vnm = dm->vnet_main; + dpi4_flow_key_t key4; + dpi6_flow_key_t key6; + dpi_flow_entry_t *p; + u32 is_ip6 = a->is_ipv6; + dpi_flow_entry_t *flow; + + int not_found; + if (!is_ip6) + { + key4.key[0] = a->dst_ip.ip4.as_u32 + | (((u64) a->src_ip.ip4.as_u32) << 32); + key4.key[1] = (((u64) a->protocol) << 32) + | ((u32) clib_host_to_net_u16 (a->dst_port) << 16) + | clib_host_to_net_u16 (a->src_port); + key4.key[2] = (u64) a->fib_index; + + not_found = + clib_bihash_search_inline_24_8 (&dm->dpi4_flow_by_key, &key4); + p = (void *) &key4.value; + } + else + { + key6.key[0] = a->dst_ip.ip6.as_u64[0]; + key6.key[1] = a->dst_ip.ip6.as_u64[1]; + key6.key[2] = a->dst_ip.ip6.as_u64[0]; + key6.key[3] = a->dst_ip.ip6.as_u64[1]; + key6.key[4] = (((u64) a->protocol) << 32) + | ((u32) a->dst_port << 16) | (a->src_port); + key6.key[5] = (u64) a->fib_index; + + not_found = + clib_bihash_search_inline_48_8 (&dm->dpi6_flow_by_key, &key6); + p = (void *) &key6.value; + } + + if (not_found) + p = 0; + + if (a->is_add) + { + + /* adding a flow entry: entry must not already exist */ + if (p) + return VNET_API_ERROR_TUNNEL_EXIST; + + int add_failed; + if (is_ip6) + { + key6.value = (u64) flow_id | ((u64) 1 << 63); + add_failed = clib_bihash_add_del_48_8 (&dm->dpi6_flow_by_key, + &key6, 1 /*add */ ); + } + else + { + key4.value = (u64) flow_id | ((u64) 1 << 63); + add_failed = clib_bihash_add_del_24_8 (&dm->dpi4_flow_by_key, + &key4, 1 /*add */ ); + } + + if (add_failed) + { + return VNET_API_ERROR_INVALID_REGISTRATION; + } + } + else + { + /* deleting a flow: flow must exist */ + if (!p) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + flow_id = is_ip6 ? key6.value : key4.value; + flow = pool_elt_at_index (dm->dpi_flows, flow_id); + + if (!is_ip6) + clib_bihash_add_del_24_8 (&dm->dpi4_flow_by_key, &key4, 0 /*del */ ); + else + clib_bihash_add_del_48_8 (&dm->dpi6_flow_by_key, &key6, 0 /*del */ ); + + if (flow->flow_index != ~0) + vnet_flow_del (vnm, flow->flow_index); + + pool_put (dm->dpi_flows, flow); + } + + return 0; +} + +int +dpi_tcp_reass (tcp_reass_args_t * a) +{ + dpi_main_t *dm = &dpi_main; + dpi_flow_entry_t *flow; + + flow = pool_elt_at_index (dm->dpi_flows, a->flow_id); + if (flow == NULL) + return -1; + + flow->reass_en = a->reass_en; + flow->reass_dir = a->reass_dir; + return 0; +} + +int +dpi_add_del_rx_flow (u32 hw_if_index, u32 flow_id, int is_add, u32 is_ipv6) +{ + dpi_main_t *dm = &dpi_main; + vnet_main_t *vnm = dm->vnet_main; + dpi_flow_entry_t *dpi_flow; + vnet_flow_t *vent_flow; + + ip_port_and_mask_t src_port; + ip_port_and_mask_t dst_port; + + + dpi_flow = pool_elt_at_index (dm->dpi_flows, flow_id); + + src_port.port = dpi_flow->key.dst_port; + src_port.mask = ~0; + dst_port.port = dpi_flow->key.dst_port; + dst_port.mask = ~0; + + if (is_add) + { + if (dpi_flow->flow_index == ~0) + { + if (!is_ipv6) + { + ip4_address_and_mask_t src_addr4; + ip4_address_and_mask_t dst_addr4; + src_addr4.addr = dpi_flow->key.src_ip.ip4; + src_addr4.mask.as_u32 = ~0; + dst_addr4.addr = dpi_flow->key.dst_ip.ip4; + dst_addr4.mask.as_u32 = ~0; + + vnet_flow_t flow4 = { + .actions = + VNET_FLOW_ACTION_REDIRECT_TO_NODE | VNET_FLOW_ACTION_MARK, + .mark_flow_id = flow_id + dm->flow_id_start, + .redirect_node_index = 0, + .type = VNET_FLOW_TYPE_IP4_N_TUPLE, + .ip4_n_tuple = { + .src_addr = src_addr4, + .dst_addr = dst_addr4, + .src_port = src_port, + .dst_port = dst_port, + .protocol = dpi_flow->key.protocol, + } + , + }; + vent_flow = &flow4; + } + else + { + ip6_address_and_mask_t src_addr6; + ip6_address_and_mask_t dst_addr6; + src_addr6.addr.as_u64[0] = dpi_flow->key.src_ip.ip6.as_u64[0]; + src_addr6.addr.as_u64[1] = dpi_flow->key.src_ip.ip6.as_u64[1]; + src_addr6.mask.as_u64[0] = ~0; + src_addr6.mask.as_u64[1] = ~0; + dst_addr6.addr.as_u64[0] = dpi_flow->key.dst_ip.ip6.as_u64[0]; + dst_addr6.addr.as_u64[1] = dpi_flow->key.dst_ip.ip6.as_u64[1]; + dst_addr6.mask.as_u64[0] = ~0; + dst_addr6.mask.as_u64[1] = ~0; + + vnet_flow_t flow6 = { + .actions = + VNET_FLOW_ACTION_REDIRECT_TO_NODE | VNET_FLOW_ACTION_MARK, + .mark_flow_id = flow_id + dm->flow_id_start, + .redirect_node_index = 0, + .type = VNET_FLOW_TYPE_IP6_N_TUPLE, + .ip6_n_tuple = { + .src_addr = src_addr6, + .dst_addr = dst_addr6, + .src_port = src_port, + .dst_port = dst_port, + .protocol = dpi_flow->key.protocol, + } + , + }; + vent_flow = &flow6; + } + vnet_flow_add (vnm, vent_flow, &(dpi_flow->flow_index)); + } + return vnet_flow_enable (vnm, dpi_flow->flow_index, hw_if_index); + } + + /* flow index is removed when the flow is deleted */ + return vnet_flow_disable (vnm, dpi_flow->flow_index, hw_if_index); +} + +void +dpi_flow_bypass_mode (u32 sw_if_index, u8 is_ip6, u8 is_enable) +{ + if (is_ip6) + vnet_feature_enable_disable ("ip6-unicast", "dpi6-input", + sw_if_index, is_enable, 0, 0); + else + vnet_feature_enable_disable ("ip4-unicast", "dpi4-input", + sw_if_index, is_enable, 0, 0); +} + +int +dpi_init_hs_database (dpi_entry_t * entry) +{ + u32 i, j; + u32 rule_num = 0; + unsigned char *free_list; + int rv; + + for (i = 0; + (app_match_rules[i].host != NULL + || app_match_rules[i].pattern != NULL); i++) + { + rule_num++; + } + + entry->expressions = (regex_t *) calloc (sizeof (char *), rule_num + 1); + if (entry->expressions == NULL) + return -1; + + entry->ids = (u32 *) calloc (sizeof (u32), rule_num + 1); + if (entry->ids == NULL) + { + free (entry->expressions); + return -1; + } + + entry->flags = (u32 *) calloc (sizeof (u32), rule_num + 1); + if (entry->ids == NULL) + { + free (entry->expressions); + free (entry->ids); + return -1; + } + + free_list = (unsigned char *) calloc (sizeof (unsigned char), rule_num + 1); + if (free_list == NULL) + { + free (entry->expressions); + free (entry->ids); + free (entry->flags); + return -1; + } + + /* first choose pattern, otherwise choose host */ + for (i = 0, j = 0; + (app_match_rules[i].host != NULL + || app_match_rules[i].pattern != NULL); i++) + { + if (app_match_rules[i].pattern) + { + entry->expressions[j] = (regex_t) (app_match_rules[i].pattern); + entry->ids[j] = app_match_rules[i].app_id; + entry->flags[j] = HS_FLAG_SINGLEMATCH; + free_list[j] = 0; + ++j; + } + else + { + /* need to allocate additional buffer for rules */ + entry->expressions[j] = + (regex_t) host2hex (app_match_rules[i].host); + if (entry->expressions[j] != NULL) + { + entry->ids[j] = app_match_rules[i].app_id; + entry->flags[j] = HS_FLAG_SINGLEMATCH; + free_list[j] = 1; + ++j; + } + } + } + + rv = dpi_create_db_entry (entry, j, HS_MODE_STREAM); + + /* Need to free additional buffers */ + for (i = 0; i < j; ++i) + { + if (free_list[i]) + free (entry->expressions[i]); + } + + free (entry->expressions); + free (entry->ids); + free (entry->flags); + free (free_list); + return rv; +} + +#define DPI_HASH_NUM_BUCKETS (2 * 1024) +#define DPI_HASH_MEMORY_SIZE (1 << 20) + +clib_error_t * +dpi_init (vlib_main_t * vm) +{ + dpi_main_t *dm = &dpi_main; + + dm->vnet_main = vnet_get_main (); + dm->vlib_main = vm; + + vnet_flow_get_range (dm->vnet_main, "dpi", 1024 * 1024, &dm->flow_id_start); + + /* initialize the flow hash */ + clib_bihash_init_24_8 (&dm->dpi4_flow_by_key, "dpi4", + DPI_HASH_NUM_BUCKETS, DPI_HASH_MEMORY_SIZE); + clib_bihash_init_48_8 (&dm->dpi6_flow_by_key, "dpi6", + DPI_HASH_NUM_BUCKETS, DPI_HASH_MEMORY_SIZE); + + /* Init default Hyperscan database */ + dpi_init_hs_database (&dm->default_db); + + return 0; +} + +VLIB_INIT_FUNCTION (dpi_init); + +/* *INDENT-OFF* */ +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "Deep Packet Inspection", +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/dpi.h b/src/dpi.h new file mode 100644 index 0000000..e3d0add --- /dev/null +++ b/src/dpi.h @@ -0,0 +1,330 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 Intel, Travelping and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef included_vnet_dpi_h +#define included_vnet_dpi_h + +#include <vppinfra/lock.h> +#include <vppinfra/error.h> +#include <vppinfra/hash.h> +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/ip/ip4_packet.h> +#include <vnet/ip/ip6_packet.h> +#include <vnet/udp/udp.h> +#include <vnet/dpo/dpo.h> +#include <vppinfra/bihash_8_8.h> +#include <vppinfra/bihash_24_8.h> +#include <vppinfra/bihash_48_8.h> + +#include <hs/hs.h> +#include <hs/hs_common.h> +#include <hs/hs_compile.h> +#include <hs/hs_runtime.h> +#include "dpi_app_match.h" + +typedef u8 *regex_t; + +typedef struct +{ + u8 *name; + regex_t *expressions; + u32 *flags; + u32 *ids; + hs_database_t *database; + hs_scratch_t *scratch; + u32 ref_cnt; +} dpi_entry_t; + +typedef struct +{ + int res; + u32 id; +} dpi_cb_args_t; + +typedef struct +{ + union + { + struct + { + ip46_address_t src_ip; + ip46_address_t dst_ip; + u16 src_port; + u16 dst_port; + u8 protocol; + u32 fib_index; + }; + u64 key[6]; + }; +} dpi_flow_key_t; + +typedef clib_bihash_kv_24_8_t dpi4_flow_key_t; +typedef clib_bihash_kv_48_8_t dpi6_flow_key_t; + +typedef struct +{ + /* SSL */ + u8 ssl_stage; + u8 ssl_got_server_cert; +} dpi_flow_tcp_t; + +typedef struct +{ + /* TBD */ +} dpi_flow_udp_t; + +typedef struct segment_ +{ + u32 send_sn; + u8 *data; + u32 len; + u32 bi; /* vlib buffer index */ + struct segment_ *next; +} segment; + +typedef struct tcp_stream_ +{ + u32 send_sn; /* expected segment sn */ + u32 ack_sn; /* acked segment sn */ + segment *seg_queue; /* store out-of-order segments */ +} tcp_stream_t; + +typedef struct dpi_flow_info +{ + /* Required for pool_get_aligned */ + CLIB_CACHE_LINE_ALIGN_MARK (cacheline0); + + u16 detected_protocol[2]; + u16 protocol_stack_info; + u16 pkt_num; + u16 pkt_direct_counter[2]; + + hs_stream_t *stream; + u8 detect_begin; + u8 detect_done; + u32 app_id; /* L7 APP ID */ + + u16 guessed_protocol_id; + u16 guessed_host_protocol_id; + + u8 max_more_pkts_to_check; + + int (*more_pkts_func) (u8 * payload, u32 payload_len, + struct dpi_flow_info * flow); + + u16 dst_port; + u8 l4_protocol; + union + { + dpi_flow_tcp_t tcp; + dpi_flow_udp_t udp; + } l4; + + u8 ssl_cert_detected:4; + u8 ssl_cert_num_checks:4; + + union + { + struct + { + char server_cert[64]; + } ssl; + /* TBD: Add more protocols */ + } protos; +} dpi_flow_info_t; + +typedef enum +{ + TCP_STATE_SYN = 1, + TCP_STATE_SYN_ACK = 2, + TCP_STATE_ACK = 3, + TCP_STATE_ESTABLISH = 4, + TCP_STATE_FIN1 = 5, + TCP_STATE_CLOSE = 6, +} tcp_state_t; + +/* tcp packet direction */ +#define DIR_C2S 0 +#define DIR_S2C 1 + +/* tcp reassembly side */ +#define REASS_C2S 0 +#define REASS_S2C 1 +#define REASS_BOTH 2 + +/* Macros to handle sequence numbers */ +#define SN_LT(a,b) ((int)((a) - (b)) < 0) +#define SN_GT(a,b) ((int)((a) - (b)) > 0) +#define SN_EQ(a,b) ((int)((a) - (b)) == 0) + +typedef struct +{ + /* Required for pool_get_aligned */ + CLIB_CACHE_LINE_ALIGN_MARK (cacheline0); + + u32 flow_index; /* infra flow index */ + u32 next_index; + + u8 check_more_pkts:1; + u8 pkt_dir:1; + u8 forward_is_c2s:1; + u8 consumed:1; + u8 reass_en:1; + u8 reass_dir:2; + + dpi_flow_key_t key; + dpi_flow_info_t *info; + + /* TCP stream reassembly */ + tcp_state_t tcp_state; + tcp_stream_t c2s; + tcp_stream_t s2c; + segment *first_seg; + +} dpi_flow_entry_t; + +typedef struct +{ + u32 flow_id; + u8 reass_en; + u8 reass_dir; +} tcp_reass_args_t; + +typedef struct +{ + u8 is_add; + u8 is_ipv6; + ip46_address_t src_ip; + ip46_address_t dst_ip; + u16 src_port; + u16 dst_port; + u8 protocol; + u32 fib_index; +} dpi_add_del_flow_args_t; + +typedef struct +{ + u32 app_id; + u32 db_id; +} dpi_adr_t; + +typedef struct +{ + /* lookup tunnel by key */ + clib_bihash_24_8_t dpi4_flow_by_key; + clib_bihash_48_8_t dpi6_flow_by_key; + + /* vector of dpi flow instances */ + dpi_flow_entry_t *dpi_flows; + u32 flow_id_start; + dpi_flow_info_t *dpi_infos; + segment *seg_pool; + + /* Default hyperscan database */ + dpi_entry_t default_db; + + /* graph node state */ + uword *bm_ip4_bypass_enabled_by_sw_if; + uword *bm_ip6_bypass_enabled_by_sw_if; + + /* API message ID base */ + u16 msg_id_base; + + /* convenience */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; +} dpi_main_t; + +extern dpi_main_t dpi_main; + +#define foreach_copy_field \ +_(src_ip) \ +_(dst_ip) \ +_(src_port) \ +_(dst_port) \ +_(protocol) \ +_(fib_index) + + +#define dpi_enqueue_tcp_segments(seg,vm,node,next_index,to_next,n_left_to_next,bi0,next0) \ +while(seg) \ + { \ + bi0 = seg->bi; \ + to_next[0] = bi0; \ + to_next++; \ + n_left_to_next--; \ + next0 = flow0->next_index; \ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, \ + to_next, n_left_to_next, \ + bi0, next0); \ + prev_seg = seg; \ + seg=seg->next; \ + pool_put(dm->seg_pool, prev_seg); \ + } + + +#define get_u16_t(X,O) (*(u16 *)(((u8 *)X) + O)) +#define DPI_MAX_SSL_REQUEST_SIZE 10000 + +int dpi_flow_add_del (dpi_add_del_flow_args_t * a, u32 * flow_idp); +int dpi_reverse_flow_add_del (dpi_add_del_flow_args_t * a, u32 flow_id); +int dpi_add_del_rx_flow (u32 hw_if_index, u32 flow_id, int is_add, + u32 is_ipv6); +int dpi_tcp_reass (tcp_reass_args_t * a); +void dpi_flow_bypass_mode (u32 sw_if_index, u8 is_ip6, u8 is_enable); +int dpi_search_host_protocol (dpi_flow_info_t * flow, + char *str_to_match, + u32 str_to_match_len, + u16 master_protocol_id, u32 * host_protocol_id); +void dpi_search_tcp_ssl (u8 * payload, u32 payload_len, + dpi_flow_info_t * flow); + +void dpi_detect_application (u8 * payload, u32 payload_len, + dpi_flow_info_t * flow); + +typedef enum +{ + DPI_PROTOCOL_UNKNOWN = 0, + DPI_PROTOCOL_SSL = 1, + DPI_PROTOCOL_SSL_NO_CERT = 2, + DPI_N_PROTOCOL +} dpi_protocol_id_t; + +#define foreach_dpi_input_next \ +_(DROP, "error-drop") \ +_(IP4_LOOKUP, "ip4-lookup") + +typedef enum +{ +#define _(s,n) DPI_INPUT_NEXT_##s, + foreach_dpi_input_next +#undef _ + DPI_INPUT_N_NEXT, +} dpi_input_next_t; + +#endif /* included_vnet_dpi_h */ + + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/dpi_api.c b/src/dpi_api.c new file mode 100644 index 0000000..540d1a2 --- /dev/null +++ b/src/dpi_api.c @@ -0,0 +1,160 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 Intel, Travelping 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 <vnet/interface.h> +#include <vnet/api_errno.h> +#include <vnet/feature/feature.h> +#include <vnet/fib/fib_table.h> + +#include <vppinfra/byte_order.h> +#include <vlibmemory/api.h> + +#include <dpi/dpi.h> + + +#define vl_msg_id(n,h) n, +typedef enum +{ +#include <dpi/dpi.api.h> + /* We'll want to know how many messages IDs we need... */ + VL_MSG_FIRST_AVAILABLE, +} vl_msg_id_t; +#undef vl_msg_id + +/* define message structures */ +#define vl_typedefs +#include <dpi/dpi.api.h> +#undef vl_typedefs + +/* define generated endian-swappers */ +#define vl_endianfun +#include <dpi/dpi.api.h> +#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 <dpi/dpi.api.h> +#undef vl_printfun + +/* Get the API version number */ +#define vl_api_version(n,v) static u32 api_version=(v); +#include <dpi/dpi.api.h> +#undef vl_api_version + +#define vl_msg_name_crc_list +#include <dpi/dpi.api.h> +#undef vl_msg_name_crc_list + +#define REPLY_MSG_ID_BASE dm->msg_id_base +#include <vlibapi/api_helper_macros.h> + +static void +setup_message_id_table (dpi_main_t * dm, api_main_t * am) +{ +#define _(id,n,crc) \ + vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id + dm->msg_id_base); + foreach_vl_msg_name_crc_dpi; +#undef _ +} + +#define foreach_dpi_plugin_api_msg \ +_(DPI_FLOW_ADD_DEL, dpi_flow_add_del) + + +/* API message handler */ +static void +vl_api_dpi_flow_add_del_t_handler (vl_api_dpi_flow_add_del_t * mp) +{ + vl_api_dpi_flow_add_del_reply_t *rmp = NULL; + dpi_main_t *dm = &dpi_main; + int rv = 0; + u32 fib_index; + u32 flow_id = ~0; + + fib_index = fib_table_find (fib_ip_proto (mp->is_ipv6), ntohl (mp->vrf_id)); + if (fib_index == ~0) + { + rv = VNET_API_ERROR_NO_SUCH_FIB; + goto out; + } + + dpi_add_del_flow_args_t a = { + .is_add = mp->is_add, + .is_ipv6 = mp->is_ipv6, + .src_ip = to_ip46 (mp->is_ipv6, mp->src_ip), + .dst_ip = to_ip46 (mp->is_ipv6, mp->dst_ip), + .src_port = ntohs (mp->src_port), + .dst_port = ntohs (mp->dst_port), + .protocol = mp->protocol, + .fib_index = fib_index, + }; + + /* Check src ip and dst ip are different */ + if (ip46_address_cmp (&a.dst_ip, &a.src_ip) == 0) + { + rv = VNET_API_ERROR_SAME_SRC_DST; + goto out; + } + + rv = dpi_flow_add_del (&a, &flow_id); + +out: + /* *INDENT-OFF* */ + REPLY_MACRO2(VL_API_DPI_FLOW_ADD_DEL_REPLY, + ({ + rmp->flow_id = htonl (flow_id); + })); + /* *INDENT-ON* */ +} + +static clib_error_t * +dpi_api_hookup (vlib_main_t * vm) +{ + dpi_main_t *dm = &dpi_main; + + u8 *name = format (0, "dpi_%08x%c", api_version, 0); + dm->msg_id_base = vl_msg_api_get_msg_ids + ((char *) name, VL_MSG_FIRST_AVAILABLE); + +#define _(N,n) \ + vl_msg_api_set_handlers((VL_API_##N + dm->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_dpi_plugin_api_msg; +#undef _ + + /* Add our API messages to the global name_crc hash table */ + setup_message_id_table (dm, &api_main); + + return 0; +} + +VLIB_API_INIT_FUNCTION (dpi_api_hookup); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/dpi_app_match.h b/src/dpi_app_match.h new file mode 100644 index 0000000..dde4857 --- /dev/null +++ b/src/dpi_app_match.h @@ -0,0 +1,124 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2019 Intel, Travelping 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. + *------------------------------------------------------------------ + */ + +/* +#************************************************************* +# Copyright (c) 2003-2017, Emerging Threats +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +# following disclaimer in the documentation and/or other materials provided with the distribution. +# * Neither the name of the nor the names of its contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +#************************************************************* +*/ + +#ifndef dpi_app_match_h +#define dpi_app_match_h + +typedef enum +{ + DPI_APP_CISCO = 1, + DPI_APP_GOOGLE = 2, + DPI_APP_BING = 3, + DPI_APP_MSN = 4, + DPI_APP_YAHOO = 5, + DPI_APP_YAHOOMAIL = 6, + DPI_APP_INTEL = 7, + DPI_APP_AMAZON = 8, + DPI_APP_AMD = 9, + DPI_APP_BAIDU = 10, + DPI_APP_APPLE = 11, + DPI_APP_FACEBOOK = 12, + DPI_APP_EBAY = 13, + DPI_APP_GITHUB = 14, + DPI_APP_GMAIL = 15, + DPI_APP_QQ = 16, + DPI_APP_WECHAT = 17, + DPI_APP_PINTEREST = 18, + DPI_APP_LENOVO = 19, + DPI_APP_LINKEDIN = 20, + DPI_APP_SKYPE = 21, + DPI_APP_MICROSOFT = 22, + DPI_APP_NETFLIX = 23, + DPI_APP_NOKIA = 24, + DPI_APP_NVIDIA = 25, + DPI_APP_OFFICE = 26, + DPI_APP_ORACLE = 27, + DPI_APP_OUTLOOK = 28, + DPI_APP_PANDORA = 29, + DPI_APP_PAYPAL = 30, + DPI_APP_SINA = 31, + DPI_APP_SOGOU = 32, + DPI_APP_SYMANTEC = 33, + DPI_APP_TAOBAO = 34, + DPI_APP_TWITTER = 35, + DPI_APP_UPS = 36, + DPI_APP_VISA = 37, + DPI_APP_MCAFEE = 38, + DPI_APP_VMWARE = 39, + DPI_APP_WORDPRESS = 40, + DPI_APP_ADOBE = 41, + DPI_APP_AKAMAI = 42, + DPI_APP_ALIENVAULT = 43, + DPI_APP_BITCOMET = 44, + DPI_APP_CHECKPOINT = 45, + DPI_APP_BLOOMBERG = 46, + DPI_APP_DELL = 47, + DPI_APP_F5 = 48, + DPI_APP_FIREEYE = 49, + DPI_APP_DROPBOX = 50, + + /* last app ID */ + DPI_N_APPLICATIONS = 51, +} dpi_application_id_t; + +typedef struct dpi_app_match_rule_ +{ + char *host; + char *pattern; + char *app_name; + u32 app_id; +} dpi_app_match_rule; + +#define DPI_MAX_APP_NUM DPI_N_APPLICATIONS +extern dpi_app_match_rule app_match_rules[]; + + +#endif /* dpi_app_match_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/dpi_cli.c b/src/dpi_cli.c new file mode 100644 index 0000000..6b3afc7 --- /dev/null +++ b/src/dpi_cli.c @@ -0,0 +1,357 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2018 Intel, Travelping 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 <stdint.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <inttypes.h> + +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/plugin/plugin.h> +#include <vnet/fib/fib_entry.h> +#include <vnet/fib/fib_table.h> +#include <vpp/app/version.h> +#include <dpi/dpi.h> + + +extern dpi_main_t dpi_main; +extern dpi_entry_t *dpi_dbs; + +static clib_error_t * +dpi_flow_add_del_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd_arg) +{ + unformat_input_t _line_input, *line_input = &_line_input; + ip46_address_t src_ip = ip46_address_initializer; + ip46_address_t dst_ip = ip46_address_initializer; + u16 src_port = 0, dst_port = 0; + u8 is_add = 0; + u8 ipv4_set = 0; + u8 ipv6_set = 0; + u32 tmp; + int rv; + u8 protocol = 0; + u32 table_id; + u32 fib_index = 0; + u32 dpi_flow_id; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "add")) + is_add = 1; + else if (unformat (line_input, "del")) + is_add = 0; + else if (unformat (line_input, "src-ip %U", unformat_ip46_address, + &src_ip, IP46_TYPE_ANY)) + { + ip46_address_is_ip4 (&src_ip) ? (ipv4_set = 1) : (ipv6_set = 1); + } + else if (unformat (line_input, "dst-ip %U", unformat_ip46_address, + &dst_ip, IP46_TYPE_ANY)) + { + ip46_address_is_ip4 (&dst_ip) ? (ipv4_set = 1) : (ipv6_set = 1); + } + else if (unformat (line_input, "src-port %d", &tmp)) + src_port = (u16) tmp; + else if (unformat (line_input, "dst-port %d", &tmp)) + dst_port = (u16) tmp; + else + if (unformat (line_input, "protocol %U", unformat_ip_protocol, &tmp)) + protocol = (u8) tmp; + else if (unformat (line_input, "protocol %u", &tmp)) + protocol = (u8) tmp; + else if (unformat (line_input, "vrf-id %d", &table_id)) + { + fib_index = fib_table_find (fib_ip_proto (ipv6_set), table_id); + } + else + return clib_error_return (0, "parse error: '%U'", + format_unformat_error, line_input); + } + + unformat_free (line_input); + + if (ipv4_set && ipv6_set) + return clib_error_return (0, "both IPv4 and IPv6 addresses specified"); + + dpi_add_del_flow_args_t a = {.is_add = is_add, + .is_ipv6 = ipv6_set, +#define _(x) .x = x, + foreach_copy_field +#undef _ + }; + + /* Add normal flow */ + rv = dpi_flow_add_del (&a, &dpi_flow_id); + if (rv < 0) + return clib_error_return (0, "flow error: %d", rv); + + /* Add reverse flow */ + rv = dpi_reverse_flow_add_del (&a, dpi_flow_id); + if (rv < 0) + return clib_error_return (0, "reverse flow error: %d", rv); + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dpi_flow_add_del_command, static) = { + .path = "dpi flow", + .short_help = "dpi flow [add | del] " + "[src-ip <ip-addr>] [dst-ip <ip-addr>] " + "[src-port <port>] [dst-port <port>] " + "[protocol <protocol>] [vrf-id <nn>]", + .function = dpi_flow_add_del_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dpi_tcp_reass_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd_arg) +{ + unformat_input_t _line_input, *line_input = &_line_input; + u32 flow_id = ~0; + u8 reass_en = 0; + u8 reass_dir = 0; + int rv; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "flow_id %d", &flow_id)) + ; + else if (unformat (line_input, "enable")) + { + reass_en = 1; + } + else if (unformat (line_input, "disable")) + { + reass_en = 0; + } + else if (unformat (line_input, "client")) + { + reass_dir = REASS_C2S; + } + else if (unformat (line_input, "server")) + { + reass_dir = REASS_S2C; + } + else if (unformat (line_input, "both")) + { + reass_dir = REASS_BOTH; + } + else + return clib_error_return (0, "parse error: '%U'", + format_unformat_error, line_input); + } + + unformat_free (line_input); + + tcp_reass_args_t a = {.flow_id = flow_id, + .reass_en = reass_en, + .reass_dir = reass_dir, + }; + + rv = dpi_tcp_reass (&a); + if (rv < 0) + return clib_error_return (0, "flow error: %d", rv); + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dpi_tcp_reass_command, static) = { + .path = "dpi tcp reass", + .short_help = "dpi tcp reass flow_id <nn> <enable|disable> " + "[ <client | server | both> ]", + .function = dpi_tcp_reass_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dpi_flow_offload_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + dpi_main_t *dm = &dpi_main; + vnet_main_t *vnm = dm->vnet_main; + u32 rx_flow_id = ~0; + u32 hw_if_index = ~0; + int is_add = 1; + u32 is_ipv6 = 0; + dpi_flow_entry_t *flow; + vnet_hw_interface_t *hw_if; + u32 rx_fib_index = ~0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "hw %U", unformat_vnet_hw_interface, vnm, + &hw_if_index)) + continue; + if (unformat (line_input, "rx %d", &rx_flow_id)) + continue; + if (unformat (line_input, "del")) + { + is_add = 0; + continue; + } + return clib_error_return (0, "unknown input `%U'", + format_unformat_error, line_input); + } + + if (rx_flow_id == ~0) + return clib_error_return (0, "missing rx flow"); + if (hw_if_index == ~0) + return clib_error_return (0, "missing hw interface"); + + flow = pool_elt_at_index (dm->dpi_flows, rx_flow_id); + + hw_if = vnet_get_hw_interface (vnm, hw_if_index); + + is_ipv6 = ip46_address_is_ip4 (&(flow->key.src_ip)) ? 0 : 1; + + if (is_ipv6) + { + ip6_main_t *im6 = &ip6_main; + rx_fib_index = + vec_elt (im6->fib_index_by_sw_if_index, hw_if->sw_if_index); + } + else + { + ip4_main_t *im4 = &ip4_main; + rx_fib_index = + vec_elt (im4->fib_index_by_sw_if_index, hw_if->sw_if_index); + } + + if (flow->key.fib_index != rx_fib_index) + return clib_error_return (0, "interface/flow fib mismatch"); + + if (dpi_add_del_rx_flow (hw_if_index, rx_flow_id, is_add, is_ipv6)) + return clib_error_return (0, "error %s flow", + is_add ? "enabling" : "disabling"); + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dpi_flow_offload_command, static) = { + .path = "dpi set flow-offload", + .short_help = + "dpi set flow-offload hw <interface-name> rx <flow-id> [del]", + .function = dpi_flow_offload_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dpi_set_flow_bypass (u32 is_ip6, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + vnet_main_t *vnm = vnet_get_main (); + clib_error_t *error = 0; + u32 sw_if_index, is_enable; + + sw_if_index = ~0; + is_enable = 1; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat_user (line_input, unformat_vnet_sw_interface, vnm, + &sw_if_index)) + ; + else if (unformat (line_input, "del")) + is_enable = 0; + else + { + error = unformat_parse_error (line_input); + goto done; + } + } + + if (~0 == sw_if_index) + { + error = clib_error_return (0, "unknown interface `%U'", + format_unformat_error, line_input); + goto done; + } + + dpi_flow_bypass_mode (sw_if_index, is_ip6, is_enable); + +done: + unformat_free (line_input); + + return error; +} + +static clib_error_t * +dpi_set_ip4_flow_bypass_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + return dpi_set_flow_bypass (0, input, cmd); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dpi_set_ip4_flow_bypass_command, static) = +{ + .path = "dpi set ip4 flow-bypass", + .short_help = "dpi set ip4 flow-bypass <interface> [del]", + .function = dpi_set_ip4_flow_bypass_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dpi_set_ip6_flow_bypass_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + return dpi_set_flow_bypass (0, input, cmd); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dpi_set_ip6_flow_bypass_command, static) = +{ + .path = "dpi set ip6 flow-bypass", + .short_help = "dpi set ip6 flow-bypass <interface> [del]", + .function = dpi_set_ip6_flow_bypass_command_fn, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/dpi_node.c b/src/dpi_node.c new file mode 100644 index 0000000..2e86c65 --- /dev/null +++ b/src/dpi_node.c @@ -0,0 +1,1042 @@ +/* + * Copyright (c) 2019 Intel and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <vlib/vlib.h> +#include <vnet/pg/pg.h> +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vppinfra/bihash_48_8.h> +#include <vppinfra/dlist.h> +#include <vppinfra/pool.h> +#include <vppinfra/vec.h> +#include <vnet/plugin/plugin.h> +#include <vpp/app/version.h> +#include <vnet/flow/flow.h> +#include <vnet/tcp/tcp_packet.h> + +#include <dpi/dpi.h> + +vlib_node_registration_t dpi4_input_node; +vlib_node_registration_t dpi6_input_node; +vlib_node_registration_t dpi4_flow_input_node; +vlib_node_registration_t dpi6_flow_input_node; + + +#define foreach_dpi_input_error \ + _(NONE, "no error") \ + _(NO_SUCH_FLOW, "flow not existed") + +typedef enum +{ +#define _(sym,str) DPI_INPUT_ERROR_##sym, + foreach_dpi_input_error +#undef _ + DPI_INPUT_N_ERROR, +} dpi_input_error_t; + +static char *dpi_input_error_strings[] = { +#define _(sym,string) string, + foreach_dpi_input_error +#undef _ +}; + +typedef struct +{ + u32 next_index; + u32 flow_id; + u32 app_id; + u32 error; +} dpi_rx_trace_t; + +/* *INDENT-OFF* */ +VNET_FEATURE_INIT (dpi4_input, static) = +{ + .arc_name = "ip4-unicast", + .node_name = "dpi4-input", + .runs_before = VNET_FEATURES ("ip4-lookup"), +}; + +VNET_FEATURE_INIT (dpi6_input, static) = +{ + .arc_name = "ip6-unicast", + .node_name = "dpi6-input", + .runs_before = VNET_FEATURES ("ip6-lookup"), +}; +/* *INDENT-on* */ + +static u8 * +format_dpi_rx_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 *); + dpi_rx_trace_t *t = va_arg (*args, dpi_rx_trace_t *); + + if (t->flow_id == ~0) + return format (s, "DPI error - flow %d does not exist", + t->flow_id); + + return format (s, "DPI from flow %d app_id %d next %d error %d", + t->flow_id, t->app_id, t->next_index, t->error); +} + + + +static inline int +parse_ip4_packet_and_lookup (ip4_header_t * ip4, u32 fib_index, + dpi4_flow_key_t * key4, + int * not_found, u64 * flow_id) +{ + dpi_main_t *dm = &dpi_main; + u8 protocol = ip4_is_fragment (ip4) ? 0xfe : ip4->protocol; + u16 src_port = 0; + u16 dst_port = 0; + dpi_flow_entry_t *flow; + + key4->key[0] = ip4->src_address.as_u32 + | (((u64) ip4->dst_address.as_u32) << 32); + + if (protocol == IP_PROTOCOL_UDP || protocol == IP_PROTOCOL_TCP) + { + /* tcp and udp ports have the same offset */ + udp_header_t * udp = ip4_next_header (ip4); + src_port = udp->src_port; + dst_port = udp->dst_port; + } + + key4->key[1] = (((u64) protocol) << 32) | ((u32) src_port << 16) | dst_port; + key4->key[2] = (u64) fib_index; + + key4->value = ~0; + *not_found = clib_bihash_search_inline_24_8 (&dm->dpi4_flow_by_key, key4); + *flow_id = key4->value; + + /* not found, then create new SW flow dynamically */ + if (*not_found) + { + int add_failed; + pool_get_aligned(dm->dpi_flows, flow, CLIB_CACHE_LINE_BYTES); + clib_memset(flow, 0, sizeof(*flow)); + *flow_id = flow - dm->dpi_flows; + + flow->next_index = DPI_INPUT_NEXT_IP4_LOOKUP; + flow->flow_index = ~0; + + pool_get_aligned(dm->dpi_infos, flow->info, CLIB_CACHE_LINE_BYTES); + clib_memset(flow->info, 0, sizeof(*flow->info)); + flow->info->app_id = ~0; + + /* Add forwarding flow entry */ + key4->value = *flow_id; + add_failed = clib_bihash_add_del_24_8 (&dm->dpi4_flow_by_key, key4, + 1 /*add */); + + if (add_failed) + { + pool_put(dm->dpi_infos, flow->info); + pool_put(dm->dpi_flows, flow); + return -1; + } + + /* Add reverse flow entry*/ + key4->key[0] = ip4->dst_address.as_u32 + | (((u64) ip4->src_address.as_u32) << 32); + key4->key[1] = (((u64) protocol) << 32) | ((u32) dst_port << 16) + | src_port; + key4->key[2] = (u64) fib_index; + key4->value = (u64) flow_id | ((u64) 1 << 63); + add_failed = clib_bihash_add_del_24_8 (&dm->dpi4_flow_by_key, key4, + 1 /*add */); + + if (add_failed) + { + pool_put(dm->dpi_infos, flow->info); + pool_put(dm->dpi_flows, flow); + return -1; + } + + /* Open a Hyperscan stream for each flow */ + hs_error_t err = hs_open_stream (dm->default_db.database, 0, + &(flow->info->stream)); + + if (err != HS_SUCCESS) + { + pool_put(dm->dpi_infos, flow->info); + pool_put(dm->dpi_flows, flow); + return -1; + } + } + + return 0; +} + +static inline int +parse_ip6_packet_and_lookup (ip6_header_t * ip6, u32 fib_index, + dpi6_flow_key_t * key6, + int * not_found, u64 * flow_id) +{ + dpi_main_t *dm = &dpi_main; + u8 protocol = ip6->protocol; + u16 src_port = 0; + u16 dst_port = 0; + dpi_flow_entry_t *flow; + + key6->key[0] = ip6->src_address.as_u64[0]; + key6->key[1] = ip6->src_address.as_u64[1]; + key6->key[2] = ip6->dst_address.as_u64[0]; + key6->key[3] = ip6->dst_address.as_u64[1]; + + if (protocol == IP_PROTOCOL_UDP || protocol == IP_PROTOCOL_TCP) + { + /* tcp and udp ports have the same offset */ + udp_header_t * udp = ip6_next_header(ip6); + src_port = udp->src_port; + dst_port = udp->dst_port; + } + + key6->key[4] = (((u64) protocol) << 32) + | ((u32) src_port << 16) + | dst_port; + key6->key[5] = (u64) fib_index; + + key6->value = ~0; + *not_found = clib_bihash_search_inline_48_8 (&dm->dpi6_flow_by_key, key6); + *flow_id = key6->value; + + /* not found, then create new SW flow dynamically */ + if (*not_found) + { + int add_failed; + pool_get_aligned(dm->dpi_flows, flow, CLIB_CACHE_LINE_BYTES); + clib_memset(flow, 0, sizeof(*flow)); + *flow_id = flow - dm->dpi_flows; + + flow->next_index = DPI_INPUT_NEXT_IP4_LOOKUP; + flow->flow_index = ~0; + + pool_get_aligned(dm->dpi_infos, flow->info, CLIB_CACHE_LINE_BYTES); + clib_memset(flow->info, 0, sizeof(*flow->info)); + flow->info->app_id = ~0; + + /* Add forwarding flow entry */ + key6->value = (u64) flow_id; + add_failed = clib_bihash_add_del_48_8 (&dm->dpi6_flow_by_key, + key6, 1 /*add */ ); + if (add_failed) + { + pool_put(dm->dpi_infos, flow->info); + pool_put(dm->dpi_flows, flow); + return -1; + } + + /* Add reverse flow entry*/ + key6->key[0] = ip6->dst_address.as_u64[0]; + key6->key[1] = ip6->dst_address.as_u64[1]; + key6->key[2] = ip6->src_address.as_u64[0]; + key6->key[3] = ip6->src_address.as_u64[1]; + key6->key[4] = (((u64) protocol) << 32) + | ((u32) dst_port << 16) + | src_port; + key6->key[5] = (u64) fib_index; + key6->value = (u64) flow_id | ((u64) 1 << 63); + add_failed = clib_bihash_add_del_48_8 (&dm->dpi6_flow_by_key, + key6, 1 /*add */ ); + + if (add_failed) + { + pool_put(dm->dpi_infos, flow->info); + pool_put(dm->dpi_flows, flow); + return -1; + } + + /* Open a Hyperscan stream for each flow */ + hs_error_t err = hs_open_stream (dm->default_db.database, 0, + &(flow->info->stream)); + + if (err != HS_SUCCESS) + { + pool_put(dm->dpi_infos, flow->info); + pool_put(dm->dpi_flows, flow); + return -1; + } + } + + return 0; +} + +static inline void +dpi_trim_overlap(u32 left_sn, segment *seg) +{ + int overlap_len; + + overlap_len = left_sn - seg->send_sn; + /* trim leading overlap bytes */ + seg->data += overlap_len; + seg->len -= overlap_len; + seg->send_sn += overlap_len; + + /* trim the right overlap bytes */ + if( seg->next + && (seg->send_sn+seg->len) > (seg->next->send_sn) ) + { + overlap_len = (seg->send_sn+seg->len) - (seg->next->send_sn); + if(seg->len > overlap_len) + { + seg->len -= overlap_len; + } + } +} + +/* + * re-order out-of-order segments, and handle overlap segments. + * */ +static inline void +dpi_handle_tcp_segments (dpi_flow_entry_t *flow, tcp_stream_t *stream, + u32 bi, u8 *pkt, u32 payload_len) +{ + dpi_main_t *dm = &dpi_main; + u32 send_sn; + u32 ack_sn; + u32 next_sn; + u32 left_sn; + segment *first_seg = 0; + segment *seg = 0; + segment *new_seg = 0; + tcp_header_t *tcp = (tcp_header_t *)pkt; + u8 *payload = pkt + tcp_doff(tcp) * 4; + + if((tcp->flags & TCP_FLAG_ACK) == TCP_FLAG_ACK) + { + ack_sn = clib_net_to_host_u32(tcp->ack_number); + if(ack_sn != stream->ack_sn) + { + stream->ack_sn = ack_sn; + } + } + + send_sn = clib_net_to_host_u32(tcp->seq_number); + next_sn = send_sn + payload_len; + + /* Handle fully overlapping segments */ + if(SN_GT(stream->send_sn, next_sn)) + { + flow->consumed = 1; + return; + } + + if(SN_LT(stream->send_sn, send_sn)) + { + /* Store out-of-order segments to segment queue */ + for(seg=stream->seg_queue; seg; seg=seg->next) + { + if (send_sn < seg->send_sn ) + break; + } + + pool_get_aligned (dm->seg_pool, new_seg, CLIB_CACHE_LINE_BYTES); + new_seg->bi = bi; + new_seg->send_sn = send_sn; + new_seg->data = payload; + new_seg->len = payload_len; + + /* Insert new segment to right position of segment queue */ + if(seg == stream->seg_queue) + { + new_seg->next = stream->seg_queue; + stream->seg_queue = new_seg; + stream->send_sn = seg->send_sn; + left_sn = stream->send_sn; + } + else + { + new_seg->next = seg->next; + seg->next = new_seg; + left_sn = seg->send_sn; + } + + /* trim overlapped packet */ + dpi_trim_overlap(left_sn, new_seg); + + flow->consumed = 1; + } + else + { + pool_get_aligned(dm->seg_pool, first_seg, CLIB_CACHE_LINE_BYTES); + first_seg->bi = bi; + first_seg->send_sn = send_sn; + first_seg->data = payload; + first_seg->len = payload_len; + first_seg->next = stream->seg_queue; + + /* trim overlapped packet */ + dpi_trim_overlap (stream->send_sn, first_seg); + + /* reassemble continuous segments and move forward to scan */ + for (seg = first_seg; seg->next; seg = seg->next) + { + if (seg->send_sn + seg->len != seg->next->send_sn) + break; + } + + /* left non-continuous segments */ + stream->seg_queue = seg->next; + stream->send_sn = seg->send_sn + seg->len; + + flow->first_seg = first_seg; + seg->next = 0; + + /* scan ordered segments */ + for (seg = first_seg; seg; seg = seg->next) + { + /* detect layer 7 application for single segment */ + dpi_detect_application (seg->data, seg->len, flow->info); + if(flow->info->detect_done) + break; + } + } +} + +static inline int +dpi_handle_tcp_stream (dpi_flow_entry_t *flow, u32 bi, + u8 *pkt, u32 payload_len, u8 is_reverse) +{ + tcp_header_t *tcp; + tcp_stream_t *stream; + + tcp = (tcp_header_t *)pkt; + if((tcp->flags & (TCP_FLAG_SYN|TCP_FLAG_ACK)) == TCP_FLAG_SYN) + { + flow->c2s.send_sn = clib_net_to_host_u32(tcp->seq_number) + 1; + flow->pkt_dir = DIR_C2S; + flow->forward_is_c2s = !is_reverse; + flow->tcp_state = TCP_STATE_SYN; + } + else + { + /* + forward_is_c2s | is_reverse + 0 1 + 0 s2c(1) c2s(0) + 1 c2s(0) s2c(1) + */ + flow->pkt_dir = (flow->forward_is_c2s == is_reverse); + } + + switch(flow->tcp_state) + { + case TCP_STATE_SYN: + { + if(flow->pkt_dir != DIR_S2C) + break; + + if((tcp->flags & (TCP_FLAG_SYN|TCP_FLAG_ACK)) + != (TCP_FLAG_SYN|TCP_FLAG_ACK)) + break; + + flow->s2c.send_sn = clib_net_to_host_u32(tcp->seq_number) + 1; + flow->s2c.ack_sn = clib_net_to_host_u32(tcp->ack_number) + 1; + flow->tcp_state = TCP_STATE_SYN_ACK; + break; + } + + case TCP_STATE_SYN_ACK: + { + if(flow->pkt_dir != DIR_C2S) + break; + + flow->c2s.ack_sn = clib_net_to_host_u32(tcp->ack_number) + 1; + flow->tcp_state = TCP_STATE_ESTABLISH; + break; + } + + case TCP_STATE_ACK: + case TCP_STATE_ESTABLISH: + case TCP_STATE_FIN1: + { + stream = (flow->pkt_dir == DIR_C2S)? &(flow->c2s) : &(flow->s2c); + if( (flow->reass_dir == REASS_BOTH) + || ((flow->pkt_dir==DIR_C2S) && (flow->reass_dir==REASS_C2S)) + || ((flow->pkt_dir==DIR_S2C) && (flow->reass_dir==REASS_S2C)) ) + { + dpi_handle_tcp_segments(flow, stream, bi, pkt, payload_len); + } + + break; + } + + case TCP_STATE_CLOSE: + { + /* Free all segments in the queue */ + break; + } + } + + return 0; +} + +void +dpi_detect_application (u8 *payload, u32 payload_len, + dpi_flow_info_t *flow) +{ + + /* detect if payload is SSL's payload for default port */ + dpi_search_tcp_ssl(payload, payload_len, flow); + + /* TBD: add detect if is SSL's payload with non default port*/ + +} + +always_inline uword +dpi_input_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame, u32 is_ip4) +{ + dpi_main_t *dm = &dpi_main; + u32 *from, *to_next, n_left_from, n_left_to_next, next_index; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0, next0 = 0; + vlib_buffer_t *b0; + ip4_header_t *ip40; + ip6_header_t *ip60; + tcp_header_t *tcp0; + udp_header_t *udp0; + dpi4_flow_key_t key40; + dpi6_flow_key_t key60; + u32 fib_index0 = ~0; + u64 flow_id0 = ~0; + u32 flow_index0 = ~0; + int not_found0 = 0; + u8 is_reverse0 = 0; + dpi_flow_entry_t *flow0 = 0; + u32 ip_len0, l4_len0, payload_len0; + u8 protocol0; + u8 *l4_pkt0, *payload0; + u16 dst_port = 0; + segment *seg = 0; + segment *prev_seg = 0; + int rv; + + bi0 = to_next[0] = from[0]; + b0 = vlib_get_buffer (vm, bi0); + ip_len0 = vlib_buffer_length_in_chain (vm, b0); + + if (is_ip4) + { + ip40 = vlib_buffer_get_current (b0); + ip4_main_t *im4 = &ip4_main; + fib_index0 = vec_elt (im4->fib_index_by_sw_if_index, + vnet_buffer(b0)->sw_if_index[VLIB_RX]); + rv = + parse_ip4_packet_and_lookup(ip40, fib_index0, &key40, + ¬_found0, &flow_id0); + } + else + { + ip60 = vlib_buffer_get_current (b0); + ip6_main_t *im6 = &ip6_main; + fib_index0 = vec_elt (im6->fib_index_by_sw_if_index, + vnet_buffer(b0)->sw_if_index[VLIB_RX]); + rv = + parse_ip6_packet_and_lookup(ip60, fib_index0, &key60, + ¬_found0, &flow_id0); + } + + if (!rv) + goto enqueue0; + + is_reverse0 = (u8)((flow_id0 >> 63) & 0x1); + flow_index0 = (u32)(flow_id0 & (u32)(~0)); + flow0 = pool_elt_at_index (dm->dpi_flows, flow_index0); + + /* have detected successfully, directly return */ + if(flow0->info->detect_done) + goto enqueue0; + + /* check layer4 */ + if (is_ip4) + { + l4_pkt0 = (u8 *)(ip40 + 1); + l4_len0 = ip_len0 - sizeof(ip4_header_t); + protocol0 = ip40->protocol; + } + else + { + l4_pkt0 = (u8 *)(ip60 + 1); + l4_len0 = ip_len0 - sizeof(ip6_header_t); + protocol0 = ip60->protocol; + } + + if((protocol0 == IP_PROTOCOL_TCP) && (l4_len0 >= 20)) + { + tcp0 = (tcp_header_t *)l4_pkt0; + payload_len0 = l4_len0 - tcp_doff(tcp0) * 4; + payload0 = l4_pkt0 + tcp_doff(tcp0) * 4; + dst_port = tcp0->dst_port; + } + else if ((protocol0 == IP_PROTOCOL_UDP) && (l4_len0 >= 8)) + { + udp0 = (udp_header_t *)l4_pkt0; + payload_len0 = l4_len0 - sizeof(udp_header_t); + payload0 = l4_pkt0 + sizeof(udp_header_t); + dst_port = udp0->dst_port; + } + else + { + payload_len0 = l4_len0; + payload0 = l4_pkt0; + } + + flow0->info->l4_protocol = protocol0; + flow0->info->dst_port = dst_port; + + /* TCP stream reassembly and detect a protocol pdu */ + if((protocol0 == IP_PROTOCOL_TCP) && (flow0->reass_en)) + { + dpi_handle_tcp_stream(flow0, bi0, l4_pkt0, payload_len0, is_reverse0); + + /* This packet has been consumed, retrieve next packet */ + if(flow0->consumed) + goto trace0; + + /* send out continuous scanned segments */ + seg=flow0->first_seg; + dpi_enqueue_tcp_segments(seg,vm,node,next_index,to_next,n_left_to_next,bi0,next0); + flow0->first_seg = 0; + + /* Here detected successfully, send out remaining segments in seg_queue */ + if(flow0->info->detect_done) + { + seg=flow0->c2s.seg_queue; + dpi_enqueue_tcp_segments(seg,vm,node,next_index,to_next,n_left_to_next,bi0,next0); + flow0->c2s.seg_queue = 0; + + seg=flow0->s2c.seg_queue; + dpi_enqueue_tcp_segments(seg,vm,node,next_index,to_next,n_left_to_next,bi0,next0); + flow0->s2c.seg_queue = 0; + } + goto trace0; + } + else + { + /* detect layer 7 application for single packet */ + dpi_detect_application (payload0, payload_len0, flow0->info); + } + +enqueue0: + to_next[0] = bi0; + to_next++; + n_left_to_next--; + next0 = flow0->next_index; + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + +trace0: + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dpi_rx_trace_t *tr + = vlib_add_trace (vm, node, b0, sizeof (*tr)); + tr->app_id = flow0->info->app_id; + tr->next_index = next0; + tr->error = b0->error; + tr->flow_id = flow_index0; + } + + from += 1; + n_left_from -= 1; + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + return frame->n_vectors; +} + +VLIB_NODE_FN (dpi4_input_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return dpi_input_inline (vm, node, frame, /* is_ip4 */ 1); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dpi4_input_node) = +{ + .name = "dpi4-input", + .vector_size = sizeof (u32), + .n_errors = DPI_INPUT_N_ERROR, + .error_strings = dpi_input_error_strings, + .n_next_nodes = DPI_INPUT_N_NEXT, + .next_nodes = { +#define _(s,n) [DPI_INPUT_NEXT_##s] = n, + foreach_dpi_input_next +#undef _ + }, + .format_trace = format_dpi_rx_trace, +}; + +/* *INDENT-ON* */ + +/* Dummy init function to get us linked in. */ +static clib_error_t * +dpi4_input_init (vlib_main_t * vm) +{ + return 0; +} + +VLIB_INIT_FUNCTION (dpi4_input_init); + + +VLIB_NODE_FN (dpi6_input_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return dpi_input_inline (vm, node, frame, /* is_ip4 */ 0); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dpi6_input_node) = +{ + .name = "dpi6-input", + .vector_size = sizeof (u32), + .n_errors = DPI_INPUT_N_ERROR, + .error_strings = dpi_input_error_strings, + .n_next_nodes = DPI_INPUT_N_NEXT, + .next_nodes = { +#define _(s,n) [DPI_INPUT_NEXT_##s] = n, + foreach_dpi_input_next +#undef _ + }, + .format_trace = format_dpi_rx_trace, +}; +/* *INDENT-ON* */ + +/* Dummy init function to get us linked in. */ +static clib_error_t * +dpi6_input_init (vlib_main_t * vm) +{ + return 0; +} + +VLIB_INIT_FUNCTION (dpi6_input_init); + + + +#define foreach_dpi_flow_input_next \ +_(DROP, "error-drop") \ +_(IP4_LOOKUP, "ip4-lookup") + +typedef enum +{ +#define _(s,n) DPI_FLOW_NEXT_##s, + foreach_dpi_flow_input_next +#undef _ + DPI_FLOW_N_NEXT, +} dpi_flow_input_next_t; + +#define foreach_dpi_flow_error \ + _(NONE, "no error") \ + _(IP_CHECKSUM_ERROR, "Rx ip checksum errors") \ + _(IP_HEADER_ERROR, "Rx ip header errors") \ + _(UDP_CHECKSUM_ERROR, "Rx udp checksum errors") \ + _(UDP_LENGTH_ERROR, "Rx udp length errors") + +typedef enum +{ +#define _(f,s) DPI_FLOW_ERROR_##f, + foreach_dpi_flow_error +#undef _ + DPI_FLOW_N_ERROR, +} dpi_flow_error_t; + +static char *dpi_flow_error_strings[] = { +#define _(n,s) s, + foreach_dpi_flow_error +#undef _ +}; + +static_always_inline u8 +dpi_check_ip4 (ip4_header_t * ip4, u16 payload_len) +{ + u16 ip_len = clib_net_to_host_u16 (ip4->length); + return ip_len > payload_len || ip4->ttl == 0 + || ip4->ip_version_and_header_length != 0x45; +} + +static_always_inline u8 +dpi_check_ip6 (ip6_header_t * ip6, u16 payload_len) +{ + u16 ip_len = clib_net_to_host_u16 (ip6->payload_length); + return ip_len > (payload_len - sizeof (ip6_header_t)) + || ip6->hop_limit == 0 + || (ip6->ip_version_traffic_class_and_flow_label >> 28) != 0x6; +} + +always_inline uword +dpi_flow_input_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame, u32 is_ip4) +{ + dpi_main_t *dm = &dpi_main; + u32 *from, *to_next, n_left_from, n_left_to_next, next_index; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0, next0 = DPI_FLOW_NEXT_IP4_LOOKUP; + vlib_buffer_t *b0; + ip4_header_t *ip40; + ip6_header_t *ip60; + tcp_header_t *tcp0; + udp_header_t *udp0; + u32 flow_id0 = ~0; + u32 flow_index0 = ~0; + u32 is_reverse0 = 0; + dpi_flow_entry_t *flow0; + u32 ip_len0, l4_len0, payload_len0; + u8 protocol0; + u8 *l4_pkt0, *payload0; + u16 dst_port = 0; + segment *seg = 0; + segment *prev_seg = 0; + + bi0 = to_next[0] = from[0]; + b0 = vlib_get_buffer (vm, bi0); + ip_len0 = vlib_buffer_length_in_chain (vm, b0); + + if (is_ip4) + { + ip40 = vlib_buffer_get_current (b0); + dpi_check_ip4 (ip40, ip_len0); + } + else + { + ip60 = vlib_buffer_get_current (b0); + dpi_check_ip6 (ip60, ip_len0); + } + + ASSERT (b0->flow_id != 0); + flow_id0 = b0->flow_id - dm->flow_id_start; + + is_reverse0 = (u32) ((flow_id0 >> 31) & 0x1); + flow_index0 = (u32) (flow_id0 & (u32) (~(1 << 31))); + flow0 = pool_elt_at_index (dm->dpi_flows, flow_index0); + + /* have detected successfully, directly return */ + if (flow0->info->detect_done) + goto enqueue0; + + /* check layer4 */ + if (is_ip4) + { + l4_pkt0 = (u8 *) (ip40 + 1); + l4_len0 = ip_len0 - sizeof (ip4_header_t); + protocol0 = ip40->protocol; + } + else + { + l4_pkt0 = (u8 *) (ip60 + 1); + l4_len0 = ip_len0 - sizeof (ip6_header_t); + protocol0 = ip60->protocol; + } + + if ((protocol0 == IP_PROTOCOL_TCP) && (l4_len0 >= 20)) + { + tcp0 = (tcp_header_t *) l4_pkt0; + payload_len0 = l4_len0 - tcp_doff (tcp0) * 4; + payload0 = l4_pkt0 + tcp_doff (tcp0) * 4; + dst_port = tcp0->dst_port; + } + else if ((protocol0 == IP_PROTOCOL_UDP) && (l4_len0 >= 8)) + { + udp0 = (udp_header_t *) l4_pkt0; + payload_len0 = l4_len0 - sizeof (udp_header_t); + payload0 = l4_pkt0 + sizeof (udp_header_t); + dst_port = udp0->dst_port; + } + else + { + payload_len0 = l4_len0; + payload0 = l4_pkt0; + } + + flow0->info->l4_protocol = protocol0; + flow0->info->dst_port = dst_port; + + /* TCP stream reassembly and detect a protocol pdu */ + if ((protocol0 == IP_PROTOCOL_TCP) && (flow0->reass_en)) + { + dpi_handle_tcp_stream (flow0, bi0, l4_pkt0, payload_len0, + is_reverse0); + + /* This packet has been consumed, retrieve next packet */ + if (flow0->consumed) + goto trace0; + + /* send out continuous scanned segments */ + seg = flow0->first_seg; + dpi_enqueue_tcp_segments (seg, vm, node, next_index, to_next, + n_left_to_next, bi0, next0); + flow0->first_seg = 0; + + /* Here detected successfully, send out remaining segments in seg_queue */ + if (flow0->info->detect_done) + { + seg = flow0->c2s.seg_queue; + dpi_enqueue_tcp_segments (seg, vm, node, next_index, + to_next, n_left_to_next, bi0, + next0); + flow0->c2s.seg_queue = 0; + + seg = flow0->s2c.seg_queue; + dpi_enqueue_tcp_segments (seg, vm, node, next_index, + to_next, n_left_to_next, bi0, + next0); + flow0->s2c.seg_queue = 0; + } + goto trace0; + } + else + { + /* detect layer 7 application for single packet */ + dpi_detect_application (payload0, payload_len0, flow0->info); + } + +enqueue0: + to_next[0] = bi0; + to_next++; + n_left_to_next--; + next0 = flow0->next_index; + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + +trace0: + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dpi_rx_trace_t *tr + = vlib_add_trace (vm, node, b0, sizeof (*tr)); + tr->app_id = flow0->info->app_id; + tr->next_index = next0; + tr->error = b0->error; + tr->flow_id = flow_index0; + } + + from += 1; + n_left_from -= 1; + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + return frame->n_vectors; +} + + +VLIB_NODE_FN (dpi4_flow_input_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return dpi_flow_input_inline (vm, node, frame, /* is_ip4 */ 1); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dpi4_flow_input_node) = { + .name = "dpi4-flow-input", + .type = VLIB_NODE_TYPE_INTERNAL, + .vector_size = sizeof (u32), + + .format_trace = format_dpi_rx_trace, + + .n_errors = DPI_FLOW_N_ERROR, + .error_strings = dpi_flow_error_strings, + + .n_next_nodes = DPI_FLOW_N_NEXT, + .next_nodes = { +#define _(s,n) [DPI_FLOW_NEXT_##s] = n, + foreach_dpi_flow_input_next +#undef _ + }, +}; +/* *INDENT-ON* */ + +/* Dummy init function to get us linked in. */ +static clib_error_t * +dpi4_flow_input_init (vlib_main_t * vm) +{ + return 0; +} + +VLIB_INIT_FUNCTION (dpi4_flow_input_init); + +VLIB_NODE_FN (dpi6_flow_input_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return dpi_flow_input_inline (vm, node, frame, /* is_ip4 */ 0); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dpi6_flow_input_node) = { + .name = "dpi6-flow-input", + .type = VLIB_NODE_TYPE_INTERNAL, + .vector_size = sizeof (u32), + + .format_trace = format_dpi_rx_trace, + + .n_errors = DPI_FLOW_N_ERROR, + .error_strings = dpi_flow_error_strings, + + .n_next_nodes = DPI_FLOW_N_NEXT, + .next_nodes = { +#define _(s,n) [DPI_FLOW_NEXT_##s] = n, + foreach_dpi_flow_input_next +#undef _ + }, +}; +/* *INDENT-ON* */ + +/* Dummy init function to get us linked in. */ +static clib_error_t * +dpi6_flow_input_init (vlib_main_t * vm) +{ + return 0; +} + +VLIB_INIT_FUNCTION (dpi6_flow_input_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/dpi_plugin_doc.md b/src/dpi_plugin_doc.md new file mode 100644 index 0000000..fc069c0 --- /dev/null +++ b/src/dpi_plugin_doc.md @@ -0,0 +1,107 @@ +# DPI plugin for VPP {#dpi_plugin_doc} + +## Overview + +DPI plugin can identify and analyze the traffic running on networks in real time. +It can be used on many use cases, such as Web Application Firewall, +Policy based routing, Intrusion Detection System, Intrusion Prevention System, etc. + +The main use case for current approach would be identification of cooperating traffic +for an established TCP connection (i.e. traffic that is not intentionally disguised) +to support application-based QoS. + + +## Design + +The DPI plugin leverage Hyperscan to perform regex matching. + +Hyperscan is a high-performance multiple regex matching library. +Please refer to below for details: +http://intel.github.io/dpi/dev-reference/ + +Below is the brief design: + +1. Provides a default APPID database for detection. + +2. Support TCP connection state tracking. + +3. Support TCP segments reassembly on the fly, which handles out-of-order tcp segments and overlapping segments. + It means that we do not need to reassembly segments first, then dedect applicaion, + and then fragment segments again, which helps to improve performance. + +4. Support Hyperscan Stream mode, which can detect one rule straddling into some tcp segments. + It means that if there is a rule "abcde", then "abc" can be in packet 1, + and "de" can be in packet 2. + +5. Configure static dpi flows with 5-tuple and VRF-aware, and supports both ipv4 and ipv6 flows. + These flows will first try to HW offload to NIC based on DPDK rte_flow mechanism + and vpp/vnet/flow infrastructure. + If failed, then will create static SW flow mappings. + Each flow configuration will create two HW or SW flow mappings, i.e. for forward and reverse traffic. + And both flow mappings will be mapped to the same dpi flow. + Dynamically create new SW mapping and aging out mechanism will be added later. + + "dpi flow [add | del] " + "[src-ip <ip-addr>] [dst-ip <ip-addr>] " + "[src-port <port>] [dst-port <port>] " + "[protocol <protocol>] [vrf-id <nn>]", + + "dpi tcp reass flow_id <nn> <enable|disable> " + "[ <client | server | both> ]", + + "dpi set flow-offload hw <interface-name> rx <flow-id> [del]", + + "dpi set ip4 flow-bypass <interface> [del]", + +6. When HW flow offload matched, packets will be redirected to DPI plugin with dpi flow_id in packet descriptor. + If not, packets will be bypassed to DPI plugin from ip-input, and then lookup SW flow mapping table. + +7. Then will detect layer 7 applications. + This first patch only detect sub protocls within SSL/TLS. + 1). Identify SSL/TLS certificate message and subsequent segments. + 2). Scan SSL/TLS certificate message through hyperscan, and get application id if matched. + 3). If maximum packets for this flow are checked and not found matched application, the detection will end up. + + +## Hyperscan Installation + +Hyperscan can be installed from packages directly on below OS: + Ubuntu 16.04.03 + Ubuntu 18.04 and later version + Fedora 27 and later version + openSUSE rolling-release Tumbleweed and later version + +If you cannot install Hyperscan from packages directly, +you can build and install it from the source code. + +Below are steps to build and install Hyperscan on Ubuntu 16.04: +1).Install binary prerequisites +apt-get install cmake ragel +apt-get install libboost-dev +apt-get install python-dev libbz2-dev + +2).Download Hyperscan sources +wget https://github.com/intel/hyperscan/archive/v5.0.0.tar.gz +tar -xf v5.0.0.tar.gz + +3).Download boost headers +wget https://dl.bintray.com/boostorg/release/1.68.0/source/boost_1_68_0.tar.gz +tar -xf boost_1_68_0.tar.gz +cp -r boost_1_68_0/boost hyperscan-5.0.0/include + +4).Build and install Hyperscan shared library. + Just follow the instruction from here. Compilation can take a long time. +cd hyperscan-5.0.0 +mkdir build +cd build +cmake -DBUILD_SHARED_LIBS=true .. +make +make install + +## Multi-Thread Support +Since generated bytecode database is read only, you can run multiple cores +to utilize the byte database to scale. + + + + diff --git a/src/protocols/dpi_ssl.c b/src/protocols/dpi_ssl.c new file mode 100644 index 0000000..7257ca5 --- /dev/null +++ b/src/protocols/dpi_ssl.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2019 Intel 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 <stdint.h> +#include <string.h> +#include <net/if.h> +#include <sys/ioctl.h> +#include <inttypes.h> + +#include <vlib/vlib.h> +#include <vlib/unix/unix.h> + +#include <dpi/dpi.h> + +typedef enum +{ + State_Initial = 0, + State_Client_Hello = 1, + State_Server_Hello = 2, + State_Certificate = 3, +} ssl_state; + +enum +{ + MAJOR_TLS = 0x3, +}; + +enum +{ + MINOR_SSL30 = 0, + MINOR_TLS10 = 0x1, + MINOR_TLS11 = 0x2, + MINOR_TLS12 = 0x3, +}; + +typedef enum +{ + change_cipher_spec = 20, + alert = 21, + handshake = 22, + application_data = 23, +} ContentType; + +typedef struct +{ + u8 major; + u8 minor; +} ProtocolVersion; + +typedef struct +{ + u8 type; + ProtocolVersion version; + u16 length; +} __attribute__ ((packed)) ssl_header; + +typedef enum +{ + hello_request = 0, + client_hello = 1, + server_hello = 2, + certificate = 11, + server_key_exchange = 12, + certificate_request = 13, + server_hello_done = 14, + certificate_verify = 15, + client_key_exchange = 16, + finished = 20, +} HandshakeType; + +typedef struct +{ + u8 msg_type; /* handshake type */ + u8 length[3]; /* bytes in message */ +} Handshake_header; + +int dpi_ssl_detect_protocol_from_cert (u8 * payload, u32 payload_len, + dpi_flow_info_t * flow); + +#define dpi_isprint(ch) ((ch) >= 0x20 && (ch) <= 0x7e) +#define dpi_isalpha(ch) (((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z')) +#define dpi_isdigit(ch) ((ch) >= '0' && (ch) <= '9') +#define dpi_isspace(ch) (((ch) >= '\t' && (ch) <= '\r') || ((ch) == ' ')) +#define dpi_min(a,b) ((a < b) ? a : b) + +static void +dpi_set_detected_protocol (dpi_flow_info_t * flow, + u32 upper_protocol, u32 lower_protocol) +{ + + if ((upper_protocol == DPI_PROTOCOL_UNKNOWN) + && (lower_protocol != DPI_PROTOCOL_UNKNOWN)) + upper_protocol = lower_protocol; + + if (upper_protocol == lower_protocol) + lower_protocol = DPI_PROTOCOL_UNKNOWN; + + if ((upper_protocol != DPI_PROTOCOL_UNKNOWN) + && (lower_protocol == DPI_PROTOCOL_UNKNOWN)) + { + if ((flow->guessed_host_protocol_id != DPI_PROTOCOL_UNKNOWN) + && (upper_protocol != flow->guessed_host_protocol_id)) + { + lower_protocol = upper_protocol; + upper_protocol = flow->guessed_host_protocol_id; + } + } + + flow->detected_protocol[0] = upper_protocol; + flow->detected_protocol[1] = lower_protocol; +} + +static u32 +dpi_ssl_refine_master_protocol (dpi_flow_info_t * flow, u32 protocol) +{ + + if (flow->l4.tcp.ssl_got_server_cert == 1) + protocol = DPI_PROTOCOL_SSL; + else + protocol = DPI_PROTOCOL_SSL_NO_CERT; + + return protocol; +} + +int +dpi_ssl_detect_protocol_from_cert (u8 * payload, u32 payload_len, + dpi_flow_info_t * flow) +{ + u32 host_protocol = DPI_PROTOCOL_UNKNOWN; + int rv = 0; + + /* Only check SSL handshake packets. + * Check first segment and subsequent segments. */ + if (((payload_len > (sizeof (ssl_header) + sizeof (Handshake_header))) + && (payload[0] == handshake)) || (flow->detect_begin)) + { + if ((flow->detected_protocol[0] == DPI_PROTOCOL_UNKNOWN) + || (flow->detected_protocol[0] == DPI_PROTOCOL_SSL)) + { + rv = dpi_search_host_protocol (flow, (char *) payload, payload_len, + DPI_PROTOCOL_SSL, &host_protocol); + + if (host_protocol != DPI_PROTOCOL_UNKNOWN) + { + dpi_set_detected_protocol (flow, host_protocol, + dpi_ssl_refine_master_protocol (flow, + DPI_PROTOCOL_SSL)); + return rv; + } + } + } + return 0; +} + + +void +dpi_search_tcp_ssl (u8 * payload, u32 payload_len, dpi_flow_info_t * flow) +{ + u32 cur_len = payload_len; + u32 cur_len2; + u8 handshake_type; + + /* Check first segment of SSL Certificate message */ + if ((payload_len > (sizeof (ssl_header) + sizeof (Handshake_header))) + && (payload[0] == handshake)) + { + handshake_type = payload[5]; + + if (handshake_type == client_hello) + { + flow->l4.tcp.ssl_stage = State_Client_Hello; + return; + } + else if (handshake_type == server_hello) + { + cur_len = ntohs (get_u16_t (payload, 3)) + sizeof (ssl_header); + + /* This packet only contains Server Hello message */ + if (cur_len == payload_len) + { + flow->l4.tcp.ssl_stage = State_Server_Hello; + return; + } + + /* This packet contains Server Hello, Certificate and more messages */ + if (payload_len >= cur_len + sizeof (ssl_header) + && payload[cur_len] == handshake + && payload[cur_len + 1] == MAJOR_TLS) + { + cur_len2 = ntohs (get_u16_t (payload, cur_len + 3)) + + sizeof (ssl_header); + if (payload[cur_len + 5] == certificate) + { + flow->l4.tcp.ssl_stage = State_Certificate; + flow->detect_begin = 1; + /* Scan segments of certificate message */ + if (dpi_ssl_detect_protocol_from_cert (&payload[cur_len], + cur_len2, flow) > 0) + return; + } + } + } + else if (handshake_type == certificate) + { + cur_len = ntohs (get_u16_t (payload, 3)) + sizeof (ssl_header); + + /* This packet contains first segment of certificate message */ + if (cur_len == payload_len) + { + flow->l4.tcp.ssl_stage = State_Certificate; + flow->detect_begin = 1; + /* Scan segments of certificate message */ + if (dpi_ssl_detect_protocol_from_cert (payload, cur_len, flow) > + 0) + return; + } + } + else if (flow->detect_begin) + { + /* Check subsequent segments of SSL Certificate message */ + if (dpi_ssl_detect_protocol_from_cert (payload, cur_len, flow) > 0) + return; + } + } + + return; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ |