aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Nardi <12729895+IvanNardi@users.noreply.github.com>2024-09-16 18:38:26 +0200
committerGitHub <noreply@github.com>2024-09-16 18:38:26 +0200
commit0ddbda1f829a2d1b27d7e6519900201111702823 (patch)
tree58caea96101eeb330a9490b3565ab3d751af0035 /src
parent47ea30fdaa4eb33d8150bbb0e7d57f9d92c41821 (diff)
Add an heuristic to detect encrypted/obfuscated OpenVPN flows (#2547)
Based on the paper: "OpenVPN is Open to VPN Fingerprinting" See: https://www.usenix.org/conference/usenixsecurity22/presentation/xue-diwen Basic idea: * the distribution of the first byte of the messages (i.e. the distribution of the op-codes) is quite unique * this fingerprint might be still detectable even if the OpenVPN packets are somehow fully encrypted/obfuscated The heuristic is disabled by default.
Diffstat (limited to 'src')
-rw-r--r--src/include/ndpi_private.h4
-rw-r--r--src/include/ndpi_typedefs.h19
-rw-r--r--src/lib/ndpi_main.c35
-rw-r--r--src/lib/ndpi_utils.c7
-rw-r--r--src/lib/protocols/openvpn.c282
5 files changed, 315 insertions, 32 deletions
diff --git a/src/include/ndpi_private.h b/src/include/ndpi_private.h
index b9f197ad6..809d6c7b3 100644
--- a/src/include/ndpi_private.h
+++ b/src/include/ndpi_private.h
@@ -269,6 +269,8 @@ struct ndpi_detection_module_config_struct {
int rtp_search_for_stun;
+ int openvpn_heuristics;
+ int openvpn_heuristics_num_msgs;
int openvpn_subclassification_by_ip;
int wireguard_subclassification_by_ip;
@@ -609,6 +611,8 @@ u_int ndpi_search_tcp_or_udp_raw(struct ndpi_detection_module_struct *ndpi_struc
char* ndpi_intoav4(unsigned int addr, char* buf, u_int16_t bufLen);
+int is_flow_addr_informative(const struct ndpi_flow_struct *flow);
+
u_int16_t icmp4_checksum(u_int8_t const * const buf, size_t len);
ndpi_risk_enum ndpi_network_risk_ptree_match(struct ndpi_detection_module_struct *ndpi_str,
diff --git a/src/include/ndpi_typedefs.h b/src/include/ndpi_typedefs.h
index 9086e456e..263d81dc5 100644
--- a/src/include/ndpi_typedefs.h
+++ b/src/include/ndpi_typedefs.h
@@ -169,7 +169,8 @@ typedef enum {
NDPI_MALWARE_HOST_CONTACTED, /* Flow client contacted a malware host */
NDPI_BINARY_DATA_TRANSFER, /* Attempt to transfer something in binary format */
NDPI_PROBING_ATTEMPT, /* Probing attempt (e.g. TCP connection with no data exchanged or unidirection traffic for bidirectional flows such as SSH) */
-
+ NDPI_OBFUSCATED_TRAFFIC,
+
/* Leave this as last member */
NDPI_MAX_RISK /* must be <= 63 due to (**) */
} ndpi_risk_enum;
@@ -791,6 +792,10 @@ struct ndpi_lru_cache {
/* Ookla */
#define NDPI_AGGRESSIVENESS_OOKLA_TLS 0x01 /* Enable detection over TLS (using ookla cache) */
+/* OpenVPN */
+#define NDPI_HEURISTICS_OPENVPN_OPCODE 0x01 /* Enable heuristic based on opcode frequency */
+
+
/* ************************************************** */
struct ndpi_flow_tcp_struct {
@@ -1520,6 +1525,14 @@ struct ndpi_flow_struct {
/* NDPI_PROTOCOL_OPENVPN */
u_int8_t ovpn_session_id[2][8];
+ u_int8_t ovpn_alg_standard_state : 2;
+ u_int8_t ovpn_alg_heur_opcode_state : 2;
+ u_int8_t ovpn_heur_opcode__codes_num : 4;
+ u_int8_t ovpn_heur_opcode__num_msgs;
+#define OPENVPN_HEUR_MAX_NUM_OPCODES 4
+ u_int8_t ovpn_heur_opcode__codes[OPENVPN_HEUR_MAX_NUM_OPCODES];
+ u_int8_t ovpn_heur_opcode__resets[2];
+ u_int16_t ovpn_heur_opcode__missing_bytes[2];
/* NDPI_PROTOCOL_TINC */
u_int8_t tinc_state;
@@ -1549,8 +1562,8 @@ struct ndpi_flow_struct {
_Static_assert(sizeof(((struct ndpi_flow_struct *)0)->protos) <= 264,
"Size of the struct member protocols increased to more than 264 bytes, "
"please check if this change is necessary.");
-_Static_assert(sizeof(struct ndpi_flow_struct) <= 1136,
- "Size of the flow struct increased to more than 1136 bytes, "
+_Static_assert(sizeof(struct ndpi_flow_struct) <= 1152,
+ "Size of the flow struct increased to more than 1152 bytes, "
"please check if this change is necessary.");
#endif
#endif
diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c
index a61a8e804..808075515 100644
--- a/src/lib/ndpi_main.c
+++ b/src/lib/ndpi_main.c
@@ -198,6 +198,7 @@ static ndpi_risk_info ndpi_known_risks[] = {
{ NDPI_MALWARE_HOST_CONTACTED, NDPI_RISK_SEVERE, CLIENT_HIGH_RISK_PERCENTAGE, NDPI_CLIENT_ACCOUNTABLE },
{ NDPI_BINARY_DATA_TRANSFER, NDPI_RISK_MEDIUM, CLIENT_FAIR_RISK_PERCENTAGE, NDPI_CLIENT_ACCOUNTABLE },
{ NDPI_PROBING_ATTEMPT, NDPI_RISK_MEDIUM, CLIENT_FAIR_RISK_PERCENTAGE, NDPI_CLIENT_ACCOUNTABLE },
+ { NDPI_OBFUSCATED_TRAFFIC, NDPI_RISK_HIGH, CLIENT_HIGH_RISK_PERCENTAGE, NDPI_BOTH_ACCOUNTABLE },
/* Leave this as last member */
{ NDPI_MAX_RISK, NDPI_RISK_LOW, CLIENT_FAIR_RISK_PERCENTAGE, NDPI_NO_ACCOUNTABILITY }
@@ -438,6 +439,38 @@ void ndpi_set_proto_category(struct ndpi_detection_module_struct *ndpi_str, u_in
/* ********************************************************************************** */
+int is_flow_addr_informative(const struct ndpi_flow_struct *flow)
+{
+ /* The ideas is to tell if the address itself carries some useful information or not.
+ Examples:
+ a flow to a Facebook address is quite likely related to some Facebook apps
+ a flow to an AWS address might be potentially anything
+ */
+
+ switch(flow->guessed_protocol_id_by_ip) {
+ case NDPI_PROTOCOL_UNKNOWN:
+ /* This is basically the list of cloud providers supported by nDPI */
+ case NDPI_PROTOCOL_TENCENT:
+ case NDPI_PROTOCOL_EDGECAST:
+ case NDPI_PROTOCOL_ALIBABA:
+ case NDPI_PROTOCOL_YANDEX_CLOUD:
+ case NDPI_PROTOCOL_AMAZON_AWS:
+ case NDPI_PROTOCOL_MICROSOFT_AZURE:
+ case NDPI_PROTOCOL_CACHEFLY:
+ case NDPI_PROTOCOL_CLOUDFLARE:
+ case NDPI_PROTOCOL_GOOGLE_CLOUD:
+ return 0;
+ /* This is basically the list of VPNs (with **entry** addresses) supported by nDPI */
+ case NDPI_PROTOCOL_NORDVPN:
+ case NDPI_PROTOCOL_PROTONVPN:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+/* ********************************************************************************** */
+
/*
There are some (master) protocols that are informative, meaning that it shows
what is the subprotocol about, but also that the subprotocol isn't a real protocol.
@@ -11439,6 +11472,8 @@ static const struct cfg_param {
{ "rtp", "search_for_stun", "disable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(rtp_search_for_stun), NULL },
+ { "openvpn", "dpi.heuristics", "0x00", "0", "0x01", CFG_PARAM_INT, __OFF(openvpn_heuristics), NULL },
+ { "openvpn", "dpi.heuristics.num_messages", "10", "0", "255", CFG_PARAM_INT, __OFF(openvpn_heuristics_num_msgs), NULL },
{ "openvpn", "subclassification_by_ip", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(openvpn_subclassification_by_ip), NULL },
{ "wireguard", "subclassification_by_ip", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(wireguard_subclassification_by_ip), NULL },
diff --git a/src/lib/ndpi_utils.c b/src/lib/ndpi_utils.c
index 012c49baa..27dd8c7db 100644
--- a/src/lib/ndpi_utils.c
+++ b/src/lib/ndpi_utils.c
@@ -2097,6 +2097,9 @@ const char* ndpi_risk2str(ndpi_risk_enum risk) {
case NDPI_PROBING_ATTEMPT:
return("Probing Attempt");
+ case NDPI_OBFUSCATED_TRAFFIC:
+ return("Obfuscated Traffic");
+
default:
ndpi_snprintf(buf, sizeof(buf), "%d", (int)risk);
return(buf);
@@ -2221,6 +2224,8 @@ const char* ndpi_risk2code(ndpi_risk_enum risk) {
return STRINGIFY(NDPI_BINARY_DATA_TRANSFER);
case NDPI_PROBING_ATTEMPT:
return STRINGIFY(NDPI_PROBING_ATTEMPT);
+ case NDPI_OBFUSCATED_TRAFFIC:
+ return STRINGIFY(NDPI_OBFUSCATED_TRAFFIC);
default:
return("Unknown risk");
@@ -2342,6 +2347,8 @@ ndpi_risk_enum ndpi_code2risk(const char* risk) {
return(NDPI_BINARY_DATA_TRANSFER);
else if(strcmp(STRINGIFY(NDPI_PROBING_ATTEMPT), risk) == 0)
return(NDPI_PROBING_ATTEMPT);
+ else if(strcmp(STRINGIFY(NDPI_OBFUSCATED_TRAFFIC), risk) == 0)
+ return(NDPI_OBFUSCATED_TRAFFIC);
else
return(NDPI_MAX_RISK);
}
diff --git a/src/lib/protocols/openvpn.c b/src/lib/protocols/openvpn.c
index a56af25be..1c63f5ecd 100644
--- a/src/lib/protocols/openvpn.c
+++ b/src/lib/protocols/openvpn.c
@@ -61,13 +61,14 @@
static void ndpi_int_openvpn_add_connection(struct ndpi_detection_module_struct * const ndpi_struct,
- struct ndpi_flow_struct * const flow)
+ struct ndpi_flow_struct * const flow,
+ ndpi_confidence_t confidence)
{
if(ndpi_struct->cfg.openvpn_subclassification_by_ip &&
ndpi_struct->proto_defaults[flow->guessed_protocol_id_by_ip].protoCategory == NDPI_PROTOCOL_CATEGORY_VPN) {
- ndpi_set_detected_protocol(ndpi_struct, flow, flow->guessed_protocol_id_by_ip, NDPI_PROTOCOL_OPENVPN, NDPI_CONFIDENCE_DPI);
+ ndpi_set_detected_protocol(ndpi_struct, flow, flow->guessed_protocol_id_by_ip, NDPI_PROTOCOL_OPENVPN, confidence);
} else {
- ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
+ ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, confidence);
}
}
@@ -132,8 +133,8 @@ static int8_t detect_hmac_size(const u_int8_t *payload, int payload_len) {
return(-1);
}
-static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct,
- struct ndpi_flow_struct* flow) {
+static int search_standard(struct ndpi_detection_module_struct* ndpi_struct,
+ struct ndpi_flow_struct* flow) {
struct ndpi_packet_struct* packet = &ndpi_struct->packet;
const u_int8_t * ovpn_payload = packet->payload;
const u_int8_t * session_remote;
@@ -151,24 +152,21 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
*/
if(ovpn_payload_len < 14 + 2 * (packet->tcp != NULL)) {
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
- return;
+ return 1; /* Exclude */
}
-
+
/* Skip openvpn TCP transport packet size */
if(packet->tcp != NULL)
ovpn_payload += 2, ovpn_payload_len -= 2;
opcode = ovpn_payload[0] & P_OPCODE_MASK;
if(!is_opcode_valid(opcode)) {
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
- return;
+ return 1; /* Exclude */
}
/* Maybe a strong assumption... */
if((ovpn_payload[0] & ~P_OPCODE_MASK) != 0) {
NDPI_LOG_DBG2(ndpi_struct, "Invalid key id\n");
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
- return;
+ return 1; /* Exclude */
}
if(flow->packet_direction_counter[dir] == 1 &&
!(opcode == P_CONTROL_HARD_RESET_CLIENT_V1 ||
@@ -177,15 +175,23 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
opcode == P_CONTROL_HARD_RESET_SERVER_V2 ||
opcode == P_CONTROL_HARD_RESET_CLIENT_V3)) {
NDPI_LOG_DBG2(ndpi_struct, "Invalid first packet\n");
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
- return;
+ return 1; /* Exclude */
+ }
+ /* Resets are small packets */
+ if(packet->payload_packet_len >= 1200 &&
+ (opcode == P_CONTROL_HARD_RESET_CLIENT_V1 ||
+ opcode == P_CONTROL_HARD_RESET_CLIENT_V2 ||
+ opcode == P_CONTROL_HARD_RESET_SERVER_V1 ||
+ opcode == P_CONTROL_HARD_RESET_SERVER_V2 ||
+ opcode == P_CONTROL_HARD_RESET_CLIENT_V3)) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid len first pkt (QUIC collision)\n");
+ return 1; /* Exclude */
}
if(flow->packet_direction_counter[dir] == 1 &&
packet->tcp &&
ntohs(*(u_int16_t *)(packet->payload)) != ovpn_payload_len) {
NDPI_LOG_DBG2(ndpi_struct, "Invalid tcp len on reset\n");
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
- return;
+ return 1; /* Exclude */
}
NDPI_LOG_DBG2(ndpi_struct, "[packets %d/%d][opcode: %u][len: %u]\n",
@@ -196,22 +202,19 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
if(flow->packet_direction_counter[dir] > 1) {
if(memcmp(flow->ovpn_session_id[dir], ovpn_payload + 1, 8) != 0) {
NDPI_LOG_DBG2(ndpi_struct, "Invalid session id on two consecutive pkts in the same dir\n");
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
- return;
+ return 1; /* Exclude */
}
if(flow->packet_direction_counter[dir] >= 2 &&
flow->packet_direction_counter[!dir] >= 2) {
/* (2) */
NDPI_LOG_INFO(ndpi_struct,"found openvpn (session ids match on both direction)\n");
- ndpi_int_openvpn_add_connection(ndpi_struct, flow);
- return;
- }
+ return 2; /* Found */
+ }
if(flow->packet_direction_counter[dir] >= 4 &&
flow->packet_direction_counter[!dir] == 0) {
/* (3) */
NDPI_LOG_INFO(ndpi_struct,"found openvpn (asymmetric)\n");
- ndpi_int_openvpn_add_connection(ndpi_struct, flow);
- return;
+ return 2; /* Found */
}
} else {
memcpy(flow->ovpn_session_id[dir], ovpn_payload + 1, 8);
@@ -241,9 +244,8 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
if(memcmp(flow->ovpn_session_id[!dir], session_remote, 8) == 0) {
NDPI_LOG_INFO(ndpi_struct,"found openvpn\n");
- ndpi_int_openvpn_add_connection(ndpi_struct, flow);
- return;
- } else {
+ return 2; /* Found */
+ } else {
NDPI_LOG_DBG2(ndpi_struct, "key mismatch 0x%lx\n", ndpi_ntohll(*(u_int64_t *)session_remote));
}
}
@@ -254,11 +256,233 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
}
}
- if(failed)
+ if(failed || flow->packet_counter > 5)
+ return 1; /* Exclude */
+ return 0; /* Continue */
+}
+
+/* Heuristic to detect encrypted/obfusctaed OpenVPN flows, based on
+ https://www.usenix.org/conference/usenixsecurity22/presentation/xue-diwen.
+ Main differences between the paper and our implementation:
+ * only op-code fingerprint
+
+ Core idea: even if the OpenVPN packets are somehow encrypted to avoid trivial
+ detection, the distibution of the first byte of the messages (i.e. the
+ distribution of the op-codes) might still be unique
+*/
+
+static int search_heur_opcode_common(struct ndpi_detection_module_struct* ndpi_struct,
+ struct ndpi_flow_struct* flow,
+ u_int8_t first_byte) {
+ u_int8_t opcode, found = 0, i;
+ int dir = ndpi_struct->packet.packet_direction;
+
+ opcode = first_byte & P_OPCODE_MASK;
+
+ /* Handshake:
+ * 2 different resets
+ * up to 3 different opcodes (ack, control, wkc)
+ * 1 data (v1 or v2)
+ So, other than the resets:
+ * at least 2 different opcodes (ack, control)
+ * no more than 4 (i.e. OPENVPN_HEUR_MAX_NUM_OPCODES) different opcodes
+ */
+
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: [packets %d/%d msgs %d, dir %d][first byte 0x%x][opcode: 0x%x]\n",
+ flow->packet_direction_counter[0],
+ flow->packet_direction_counter[1],
+ flow->ovpn_heur_opcode__num_msgs,
+ dir, first_byte, opcode);
+
+ flow->ovpn_heur_opcode__num_msgs++;
+
+ if(flow->packet_direction_counter[dir] == 1) {
+ flow->ovpn_heur_opcode__resets[dir] = opcode;
+ if(flow->packet_direction_counter[!dir] > 0 &&
+ opcode == flow->ovpn_heur_opcode__resets[!dir]) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: same resets\n");
+ return 1; /* Exclude */
+ }
+ return 0; /* Continue */
+ }
+
+ if(opcode == flow->ovpn_heur_opcode__resets[dir]) {
+ if(flow->ovpn_heur_opcode__codes_num > 0) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: resets after other opcodes\n");
+ return 1; /* Exclude */
+ }
+ return 0; /* Continue */
+ }
+ if(flow->packet_direction_counter[!dir] > 0 &&
+ opcode == flow->ovpn_heur_opcode__resets[!dir]) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: same resets\n");
+ return 1; /* Exclude */
+ }
+
+ if(flow->packet_direction_counter[!dir] == 0) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: opcode different than reset but not reset in the other direction\n");
+ return 1; /* Exclude */
+ }
+
+ if(flow->ovpn_heur_opcode__codes_num == OPENVPN_HEUR_MAX_NUM_OPCODES &&
+ opcode != flow->ovpn_heur_opcode__codes[OPENVPN_HEUR_MAX_NUM_OPCODES - 1]) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: once data we can't have other opcode\n");
+ /* TODO: this check assumes that the "data" opcode is the 4th one (after the resets).
+ * But we usually have only ack + control + data... */
+ return 1; /* Exclude */
+ }
+
+ for(i = 0; i < flow->ovpn_heur_opcode__codes_num; i++) {
+ if(flow->ovpn_heur_opcode__codes[i] == opcode)
+ found = 1;
+ }
+ if(found == 0) {
+ if(flow->ovpn_heur_opcode__codes_num == OPENVPN_HEUR_MAX_NUM_OPCODES) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: too many opcodes. Early exclude\n");
+ return 1; /* Exclude */
+ }
+ flow->ovpn_heur_opcode__codes[flow->ovpn_heur_opcode__codes_num++] = opcode;
+ }
+
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: Resets 0x%x,0x%x Num %d\n",
+ flow->ovpn_heur_opcode__resets[0],
+ flow->ovpn_heur_opcode__resets[1],
+ flow->ovpn_heur_opcode__codes_num);
+
+ if(flow->ovpn_heur_opcode__num_msgs < ndpi_struct->cfg.openvpn_heuristics_num_msgs)
+ return 0; /* Continue */
+
+ /* Done. Check what we have found...*/
+
+ if(flow->packet_direction_counter[0] == 0 ||
+ flow->packet_direction_counter[1] == 0) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: excluded because asymmetric traffic\n");
+ return 1; /* Exclude */
+ }
+
+ if(flow->ovpn_heur_opcode__codes_num >= 2) {
+ NDPI_LOG_INFO(ndpi_struct,"found openvpn (Heur-opcode)\n");
+ return 2; /* Found */
+ }
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: excluded\n");
+ return 1; /* Exclude */
+}
+
+static int search_heur_opcode(struct ndpi_detection_module_struct* ndpi_struct,
+ struct ndpi_flow_struct* flow) {
+ struct ndpi_packet_struct* packet = &ndpi_struct->packet;
+ const u_int8_t *ovpn_payload = packet->payload;
+ u_int16_t ovpn_payload_len = packet->payload_packet_len;
+ int dir = packet->packet_direction;
+ u_int16_t pdu_len;
+ int rc, iter, offset;
+
+ /* To reduce false positives number, trigger the heuristic only for flows to
+ suspicious/unknown addresses */
+ if(is_flow_addr_informative(flow)) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: flow to informative address. Exclude\n");
+ return 1; /* Exclude */
+ }
+
+ if(packet->tcp != NULL) {
+ /* Two bytes field with pdu length */
+
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: TCP length %d (remaining %d)\n",
+ ovpn_payload_len,
+ flow->ovpn_heur_opcode__missing_bytes[dir]);
+
+ /* We might need to "reassemble" the OpenVPN messages.
+ Luckily, we are not interested in the message itself, but only in the first byte
+ (after the length field), so as state we only need to know the "missing bytes"
+ of the latest pdu (from the previous TCP packets) */
+ if(flow->ovpn_heur_opcode__missing_bytes[dir] > 0) {
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: TCP, remaining bytes to ignore %d length %d\n",
+ flow->ovpn_heur_opcode__missing_bytes[dir], ovpn_payload_len);
+ if(flow->ovpn_heur_opcode__missing_bytes[dir] >= ovpn_payload_len) {
+ flow->ovpn_heur_opcode__missing_bytes[dir] -= ovpn_payload_len;
+ return 0; /* Continue */
+ } else {
+ offset = flow->ovpn_heur_opcode__missing_bytes[dir];
+ flow->ovpn_heur_opcode__missing_bytes[dir] = 0;
+ }
+ } else {
+ offset = 0;
+ }
+
+ iter = 0;
+ rc = 1; /* Exclude */
+ while(offset + 2 + 1 /* The first byte is the opcode */ <= ovpn_payload_len) {
+ pdu_len = ntohs((*(u_int16_t *)(ovpn_payload + offset)));
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: TCP, iter %d offset %d pdu_length %d\n",
+ iter, offset, pdu_len);
+ if(pdu_len < 14)
+ return 1; /* Exclude */
+ rc = search_heur_opcode_common(ndpi_struct, flow, *(ovpn_payload + offset + 2));
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: TCP, rc %d\n", rc);
+ if(rc > 0) /* Exclude || Found --> stop */
+ return rc;
+
+ if(offset + 2 + pdu_len <= ovpn_payload_len) {
+ offset += 2 + pdu_len;
+ } else {
+ flow->ovpn_heur_opcode__missing_bytes[dir] = pdu_len - (ovpn_payload_len - (offset + 2));
+ NDPI_LOG_DBG2(ndpi_struct, "Heur-opcode: TCP, missing %d bytes\n",
+ flow->ovpn_heur_opcode__missing_bytes[dir]);
+ return 0; /* Continue */
+ }
+ iter++;
+ }
+ return rc;
+ } else {
+ if(ovpn_payload_len < 14)
+ return 1; /* Exclude */
+ return search_heur_opcode_common(ndpi_struct, flow, ovpn_payload[0]);
+ }
+}
+
+
+static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct,
+ struct ndpi_flow_struct* flow) {
+ struct ndpi_packet_struct* packet = &ndpi_struct->packet;
+
+ NDPI_LOG_DBG(ndpi_struct, "Search opnvpn\n");
+
+ if(packet->payload_packet_len > 10 &&
+ ntohl(*(u_int32_t *)&packet->payload[4 + 2 * (packet->tcp != NULL)]) == 0x2112A442) {
+ NDPI_LOG_DBG2(ndpi_struct, "Avoid collision with STUN\n");
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+
+ NDPI_LOG_DBG2(ndpi_struct, "States (before): %d %d\n",
+ flow->ovpn_alg_standard_state,
+ flow->ovpn_alg_heur_opcode_state);
+
+ if(flow->ovpn_alg_standard_state == 0) {
+ flow->ovpn_alg_standard_state = search_standard(ndpi_struct, flow);
+ }
+ if(ndpi_struct->cfg.openvpn_heuristics & NDPI_HEURISTICS_OPENVPN_OPCODE) {
+ if(flow->ovpn_alg_heur_opcode_state == 0) {
+ flow->ovpn_alg_heur_opcode_state = search_heur_opcode(ndpi_struct, flow);
+ }
+ } else {
+ flow->ovpn_alg_heur_opcode_state = 1;
+ }
+
+ NDPI_LOG_DBG2(ndpi_struct, "States (after): %d %d\n",
+ flow->ovpn_alg_standard_state,
+ flow->ovpn_alg_heur_opcode_state);
+
+ if(flow->ovpn_alg_standard_state == 2) {
+ ndpi_int_openvpn_add_connection(ndpi_struct, flow, NDPI_CONFIDENCE_DPI);
+ } else if (flow->ovpn_alg_heur_opcode_state == 2) {
+ ndpi_int_openvpn_add_connection(ndpi_struct, flow, NDPI_CONFIDENCE_DPI_AGGRESSIVE);
+ ndpi_set_risk(flow, NDPI_OBFUSCATED_TRAFFIC, "Obfuscated OpenVPN");
+ } else if(flow->ovpn_alg_standard_state == 1 &&
+ flow->ovpn_alg_heur_opcode_state == 1) {
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ }
- if(flow->packet_counter > 5)
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
}
void init_openvpn_dissector(struct ndpi_detection_module_struct *ndpi_struct,