diff options
author | Hongjun Ni <hongjun.ni@intel.com> | 2019-09-09 19:03:46 +0800 |
---|---|---|
committer | Hongjun Ni <hongjun.ni@intel.com> | 2019-09-09 19:03:46 +0800 |
commit | 2234c30a625ec2c38f7fb9d0c7e7ddd02a0f038f (patch) | |
tree | 087fbe5a528d96cdc548f36dd576c61d317b4a58 /src | |
parent | 3b09645d2b0259962d7f1e8a0adf76ab4b00fc1c (diff) |
Add initial code
The inital code is from this patch:
https://gerrit.fd.io/r/#/c/vpp/+/16580/
It is contributed by Intel and Travelping.
Jerome Tollet, Ed Warnicke, Dave Barach, Damjan Marion from Cisco
and other guys have gave lots of useful comments on it.
The initial code is used for identifying layer-7 applications for HTTPS/TLS traffic
- Below is the brief design info.
- Provides a default APPID database for scan.
- Dynamically create new SW mapping has been supported now.
- Support TCP connection state tracking.
- 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.
- Support Hyperscan Stream mode, which can scan rules straddling into different 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.
- 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.
SW entry 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 set flow-offload hw <interface-name> rx <flow-id> [del]",
"dpi set ip4 flow-bypass <interface> [del]",
- 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.
- 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.
Ticket:
Type: feature
Signed-off-by: Hongjun Ni <hongjun.ni@intel.com>
Signed-off-by: Andreas Schultz <andreas.schultz@travelping.com>
Signed-off-by: Mathias Gumz <mathias.gumz@travelping.com>
Change-Id: I839cc70ddc80ea594d22a04e2cabf92a7b9c74e7
Diffstat (limited to 'src')
-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: + */ |