Skip to content

Commit 85ebda4

Browse files
committed
OpenVPN, Wireguard: improve sub-classification
Allow sub-classification of OpenVPN/Wireguard flows using their server IP. That is useful to detect the specific VPN application/app used. At the moment, the supported protocols are: Mullvad, NordVPN, ProtonVPN. This feature is configurable.
1 parent f350379 commit 85ebda4

File tree

9 files changed

+68
-21
lines changed

9 files changed

+68
-21
lines changed

doc/configuration_parameters.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,7 @@ TODO
4949
| "ookla" | "dpi.aggressiveness", | 0x01 | 0x00 | 0x01 | Detection aggressiveness for Ookla. The value is a bitmask. Values: 0x0 = disabled; 0x01 = enable heuristic for detection over TLS (via Ookla LRU cache) |
5050
| "zoom" | "max_packets_extra_dissection" | 4 | 0 | 255 | After a flow has been classified has Zoom, nDPI might analyse more packets to look for a sub-classification or for metadata. This parameter set the upper limit on the number of these packets |
5151
| "rtp" | "search_for_stun" | disable | NULL | NULL | After a flow has been classified as RTP or RTCP, nDPI might analyse more packets to look for STUN/DTLS packets, i.e. to try to tell if this flow is a "pure" RTP/RTCP flow or if the RTP/RTCP packets are multiplexed with STUN/DTLS. Useful for proper (sub)classification when the beginning of the flows are not captured or if there are lost packets in the the captured traffic. If enabled, nDPI requires more packets to process for each RTP/RTCP flow. |
52-
| $PROTO_NAME | "log" | disable | NULL | NULL | Enable/disable logging/debug for specific protocol. Use "any" as protocol name if you want to easily enable/disable logging/debug for all protocols |
53-
| $PROTO_NAME | "ip_list.load" | 1 | NULL | NULL | Enable/disable loading of internal list of IP addresses (used for (sub)classification) specific to that protocol. Use "any" as protocol name if you want to easily enable/disable all lists. This knob is valid only for the following protocols: Alibaba, Amazon AWS, Apple, Avast, Bloomberg, Cachefly, Cloudflare, Discord, Disney+, Dropbox, Edgecast, EpicGames, Ethereum, Facebook, Github, Google, Google Cloud, GoTo, Hotspot Shield, Hulu, Line, Microsoft 365, Microsoft Azure, Microsoft One Drive, Microsoft Outlook, Mullvad, Netflix, NordVPN, Nvidia, OpenDNS, ProtonVPN, RiotGames, Roblox, Skype/Teams, Starcraft, Steam, Teamviewer, Telegram, Tencent, Threema, TOR, Twitch, Twitter, UbuntuONE, VK, Yandex, Yandex Cloud, Webex, Whatsapp, Zoom |
52+
| "openvpn" | "subclassification_by_ip" | enable | NULL | NULL | Enable/disable sub-classification of OpenVPN flows using server IP. Useful to detect the specific VPN application/app. At the moment, this knob allows to identify: Mullvad, NordVPN, ProtonVPN. |
53+
| "wireguard" | "subclassification_by_ip" | enable | NULL | NULL | Enable/disable sub-classification of Wireguard flows using server IP. Useful to detect the specific VPN application/app. At the moment, this knob allows to identify: Mullvad, NordVPN, ProtonVPN. |
54+
| $PROTO_NAME | "log" | disable | NULL | NULL | Enable/disable logging/debug for specific protocol. Use "any" as protocol name if you want to easily enable/disable logging/debug for all protocols |
55+
| $PROTO_NAME | "ip_list.load" | 1 | NULL | NULL | Enable/disable loading of internal list of IP addresses (used for (sub)classification) specific to that protocol. Use "any" as protocol name if you want to easily enable/disable all lists. This knob is valid only for the following protocols: Alibaba, Amazon AWS, Apple, Avast, Bloomberg, Cachefly, Cloudflare, Discord, Disney+, Dropbox, Edgecast, EpicGames, Ethereum, Facebook, Github, Google, Google Cloud, GoTo, Hotspot Shield, Hulu, Line, Microsoft 365, Microsoft Azure, Microsoft One Drive, Microsoft Outlook, Mullvad, Netflix, NordVPN, Nvidia, OpenDNS, ProtonVPN, RiotGames, Roblox, Skype/Teams, Starcraft, Steam, Teamviewer, Telegram, Tencent, Threema, TOR, Twitch, Twitter, UbuntuONE, VK, Yandex, Yandex Cloud, Webex, Whatsapp, Zoom |

fuzz/fuzz_config.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
239239
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
240240
ndpi_set_config(ndpi_info_mod, "rtp", "search_for_stun", cfg_value);
241241
}
242+
if(fuzzed_data.ConsumeBool()) {
243+
value = fuzzed_data.ConsumeIntegralInRange(0, 0x01 + 1);
244+
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
245+
ndpi_set_config(ndpi_info_mod, "openvpn", "subclassification_by_ip", cfg_value);
246+
}
247+
if(fuzzed_data.ConsumeBool()) {
248+
value = fuzzed_data.ConsumeIntegralInRange(0, 0x01 + 1);
249+
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
250+
ndpi_set_config(ndpi_info_mod, "wireguard", "subclassification_by_ip", cfg_value);
251+
}
242252
if(fuzzed_data.ConsumeBool()) {
243253
value = fuzzed_data.ConsumeIntegralInRange(0, 1 + 1);
244254
snprintf(cfg_value, sizeof(cfg_value), "%d", value);

src/include/ndpi_private.h

+4
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,10 @@ struct ndpi_detection_module_config_struct {
268268

269269
int rtp_search_for_stun;
270270

271+
int openvpn_subclassification_by_ip;
272+
273+
int wireguard_subclassification_by_ip;
274+
271275
NDPI_PROTOCOL_BITMASK debug_bitmask;
272276
NDPI_PROTOCOL_BITMASK ip_list_bitmask;
273277

src/lib/ndpi_main.c

+4
Original file line numberDiff line numberDiff line change
@@ -11430,6 +11430,10 @@ static const struct cfg_param {
1143011430

1143111431
{ "rtp", "search_for_stun", "disable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(rtp_search_for_stun), NULL },
1143211432

11433+
{ "openvpn", "subclassification_by_ip", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(openvpn_subclassification_by_ip), NULL },
11434+
11435+
{ "wireguard", "subclassification_by_ip", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(wireguard_subclassification_by_ip), NULL },
11436+
1143311437
{ "$PROTO_NAME_OR_ID", "log", "disable", NULL, NULL, CFG_PARAM_PROTOCOL_ENABLE_DISABLE, __OFF(debug_bitmask), NULL },
1143411438
{ "$PROTO_NAME_OR_ID", "ip_list.load", "1", NULL, NULL, CFG_PARAM_PROTOCOL_ENABLE_DISABLE, __OFF(ip_list_bitmask), NULL },
1143511439

src/lib/protocols/openvpn.c

+13-3
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@
6060
#define P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size) (P_HARD_RESET_PACKET_ID_OFFSET(hmac_size) + 8 * (!!(hmac_size)))
6161

6262

63+
static void ndpi_int_openvpn_add_connection(struct ndpi_detection_module_struct * const ndpi_struct,
64+
struct ndpi_flow_struct * const flow)
65+
{
66+
if(ndpi_struct->cfg.openvpn_subclassification_by_ip &&
67+
ndpi_struct->proto_defaults[flow->guessed_protocol_id_by_ip].protoCategory == NDPI_PROTOCOL_CATEGORY_VPN) {
68+
ndpi_set_detected_protocol(ndpi_struct, flow, flow->guessed_protocol_id_by_ip, NDPI_PROTOCOL_OPENVPN, NDPI_CONFIDENCE_DPI);
69+
} else {
70+
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
71+
}
72+
}
6373

6474
static int is_opcode_valid(u_int8_t opcode)
6575
{
@@ -193,14 +203,14 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
193203
flow->packet_direction_counter[!dir] >= 2) {
194204
/* (2) */
195205
NDPI_LOG_INFO(ndpi_struct,"found openvpn (session ids match on both direction)\n");
196-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
206+
ndpi_int_openvpn_add_connection(ndpi_struct, flow);
197207
return;
198208
}
199209
if(flow->packet_direction_counter[dir] >= 4 &&
200210
flow->packet_direction_counter[!dir] == 0) {
201211
/* (3) */
202212
NDPI_LOG_INFO(ndpi_struct,"found openvpn (asymmetric)\n");
203-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
213+
ndpi_int_openvpn_add_connection(ndpi_struct, flow);
204214
return;
205215
}
206216
} else {
@@ -231,7 +241,7 @@ static void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct
231241

232242
if(memcmp(flow->ovpn_session_id[!dir], session_remote, 8) == 0) {
233243
NDPI_LOG_INFO(ndpi_struct,"found openvpn\n");
234-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
244+
ndpi_int_openvpn_add_connection(ndpi_struct, flow);
235245
return;
236246
} else {
237247
NDPI_LOG_DBG2(ndpi_struct, "key mismatch 0x%lx\n", ndpi_ntohll(*(u_int64_t *)session_remote));

src/lib/protocols/wireguard.c

+20-5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@ enum wg_message_type {
4040
WG_TYPE_TRANSPORT_DATA = 4
4141
};
4242

43+
static void ndpi_int_wireguard_add_connection(struct ndpi_detection_module_struct * const ndpi_struct,
44+
struct ndpi_flow_struct * const flow,
45+
u_int16_t app_protocol)
46+
{
47+
if(ndpi_struct->cfg.wireguard_subclassification_by_ip &&
48+
ndpi_struct->proto_defaults[flow->guessed_protocol_id_by_ip].protoCategory == NDPI_PROTOCOL_CATEGORY_VPN) {
49+
ndpi_set_detected_protocol(ndpi_struct, flow, flow->guessed_protocol_id_by_ip, NDPI_PROTOCOL_WIREGUARD, NDPI_CONFIDENCE_DPI);
50+
} else if(app_protocol != NDPI_PROTOCOL_UNKNOWN) {
51+
ndpi_set_detected_protocol(ndpi_struct, flow, app_protocol, NDPI_PROTOCOL_WIREGUARD, NDPI_CONFIDENCE_DPI);
52+
} else {
53+
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WIREGUARD, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
54+
}
55+
}
56+
57+
4358
static void ndpi_search_wireguard(struct ndpi_detection_module_struct *ndpi_struct,
4459
struct ndpi_flow_struct *flow)
4560
{
@@ -109,7 +124,7 @@ static void ndpi_search_wireguard(struct ndpi_detection_module_struct *ndpi_stru
109124

110125
if(flow->num_processed_pkts > 1) {
111126
/* This looks like a retransmission and probably this communication is blocked hence let's stop here */
112-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WIREGUARD, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
127+
ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN);
113128
return;
114129
}
115130
/* need more packets before deciding */
@@ -125,9 +140,9 @@ static void ndpi_search_wireguard(struct ndpi_detection_module_struct *ndpi_stru
125140

126141
if (receiver_index == flow->l4.udp.wireguard_peer_index[1 - packet->packet_direction]) {
127142
if(packet->payload_packet_len == 100)
128-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TUNNELBEAR, NDPI_PROTOCOL_WIREGUARD, NDPI_CONFIDENCE_DPI);
143+
ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TUNNELBEAR);
129144
else
130-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WIREGUARD, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
145+
ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN);
131146
} else {
132147
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
133148
}
@@ -143,7 +158,7 @@ static void ndpi_search_wireguard(struct ndpi_detection_module_struct *ndpi_stru
143158
if (flow->l4.udp.wireguard_stage == 2 - packet->packet_direction) {
144159
u_int32_t receiver_index = get_u_int32_t(payload, 4);
145160
if (receiver_index == flow->l4.udp.wireguard_peer_index[1 - packet->packet_direction]) {
146-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WIREGUARD, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
161+
ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN);
147162
} else {
148163
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
149164
}
@@ -171,7 +186,7 @@ static void ndpi_search_wireguard(struct ndpi_detection_module_struct *ndpi_stru
171186
/* need more packets before deciding */
172187
} else if (flow->l4.udp.wireguard_stage == 5) {
173188
if (receiver_index == flow->l4.udp.wireguard_peer_index[packet->packet_direction]) {
174-
ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WIREGUARD, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI);
189+
ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN);
175190
} else {
176191
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
177192
}

tests/cfgs/default/pcap/openvpn.pcap

8.34 KB
Binary file not shown.

tests/cfgs/default/result/mullvad_wireguard.pcap.out

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ Patricia risk IPv6: 0/0 (search/found)
2020
Patricia protocols: 1/1 (search/found)
2121
Patricia protocols IPv6: 0/0 (search/found)
2222

23-
WireGuard 10 1924 1
23+
Mullvad 10 1924 1
2424

2525
Acceptable 10 1924 1
2626

27-
1 UDP 192.168.122.11:22595 <-> 198.54.131.98:5060 [proto: 206/WireGuard][IP: 348/Mullvad][Encrypted][Confidence: DPI][FPC: 348/Mullvad, Confidence: IP address][DPI packets: 3][cat: VPN/2][6 pkts/828 bytes <-> 4 pkts/1096 bytes][Goodput ratio: 69/85][0.97 sec][bytes ratio: -0.139 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/234 193/239 470/248 177/6][Pkt Len c2s/s2c min/avg/max/stddev: 122/122 138/274 202/714 29/254][Risk: ** Known Proto on Non Std Port **][Risk Score: 50][Risk Info: Expected on port 51820][Plen Bins: 0,0,60,20,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
27+
1 UDP 192.168.122.11:22595 <-> 198.54.131.98:5060 [proto: 206.348/WireGuard.Mullvad][IP: 348/Mullvad][Encrypted][Confidence: DPI][FPC: 348/Mullvad, Confidence: IP address][DPI packets: 3][cat: VPN/2][6 pkts/828 bytes <-> 4 pkts/1096 bytes][Goodput ratio: 69/85][0.97 sec][bytes ratio: -0.139 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/234 193/239 470/248 177/6][Pkt Len c2s/s2c min/avg/max/stddev: 122/122 138/274 202/714 29/254][Risk: ** Known Proto on Non Std Port **][Risk Score: 50][Risk Info: Expected on port 51820][Plen Bins: 0,0,60,20,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

0 commit comments

Comments
 (0)