/src/ndpi/src/lib/protocols/wireguard.c
Line | Count | Source |
1 | | /* |
2 | | * wireguard.c |
3 | | * |
4 | | * Copyright (C) 2019 - ntop.org |
5 | | * Copyright (C) 2019 - Yağmur Oymak |
6 | | * |
7 | | * This file is part of nDPI, an open source deep packet inspection |
8 | | * library based on the OpenDPI and PACE technology by ipoque GmbH |
9 | | * |
10 | | * nDPI is free software: you can redistribute it and/or modify |
11 | | * it under the terms of the GNU Lesser General Public License as published by |
12 | | * the Free Software Foundation, either version 3 of the License, or |
13 | | * (at your option) any later version. |
14 | | * |
15 | | * nDPI is distributed in the hope that it will be useful, |
16 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | | * GNU Lesser General Public License for more details. |
19 | | * |
20 | | * You should have received a copy of the GNU Lesser General Public License |
21 | | * along with nDPI. If not, see <http://www.gnu.org/licenses/>. |
22 | | * |
23 | | */ |
24 | | |
25 | | #include "ndpi_protocol_ids.h" |
26 | | |
27 | | #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_WIREGUARD |
28 | | |
29 | | #include "ndpi_api.h" |
30 | | #include "ndpi_private.h" |
31 | | |
32 | | /* |
33 | | * See https://www.wireguard.com/protocol/ for protocol reference. |
34 | | */ |
35 | | |
36 | | enum wg_message_type { |
37 | | WG_TYPE_HANDSHAKE_INITIATION = 1, |
38 | | WG_TYPE_HANDSHAKE_RESPONSE = 2, |
39 | | WG_TYPE_COOKIE_REPLY = 3, |
40 | | WG_TYPE_TRANSPORT_DATA = 4 |
41 | | }; |
42 | | |
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 | 0 | { |
47 | 0 | if(ndpi_struct->cfg.wireguard_subclassification_by_ip && |
48 | 0 | ndpi_struct->proto_defaults[flow->guessed_protocol_id_by_ip].protoCategory == NDPI_PROTOCOL_CATEGORY_VPN) { |
49 | 0 | ndpi_set_detected_protocol(ndpi_struct, flow, flow->guessed_protocol_id_by_ip, NDPI_PROTOCOL_WIREGUARD, NDPI_CONFIDENCE_DPI); |
50 | 0 | } else if(app_protocol != NDPI_PROTOCOL_UNKNOWN) { |
51 | 0 | ndpi_set_detected_protocol(ndpi_struct, flow, app_protocol, NDPI_PROTOCOL_WIREGUARD, NDPI_CONFIDENCE_DPI); |
52 | 0 | } else { |
53 | 0 | ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WIREGUARD, NDPI_PROTOCOL_UNKNOWN, NDPI_CONFIDENCE_DPI); |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | | |
58 | | static void ndpi_search_wireguard(struct ndpi_detection_module_struct *ndpi_struct, |
59 | | struct ndpi_flow_struct *flow) |
60 | 0 | { |
61 | 0 | struct ndpi_packet_struct *packet = &ndpi_struct->packet; |
62 | 0 | const u_int8_t *payload = packet->payload; |
63 | 0 | u_int8_t message_type = payload[0]; |
64 | |
|
65 | 0 | NDPI_LOG_DBG(ndpi_struct, "search WireGuard\n"); |
66 | | |
67 | | /* |
68 | | * First, try some easy ways to rule out the protocol. |
69 | | * The packet size and the reserved bytes in the header are good candidates. |
70 | | */ |
71 | | |
72 | | /* |
73 | | * A transport packet contains at minimum the following fields: |
74 | | * u8 message_type |
75 | | * u8 reserved_zero[3] |
76 | | * u32 receiver_index |
77 | | * u64 counter |
78 | | * u8 encrypted_encapsulated_packet[] |
79 | | * In the case of a keepalive message, the encapsulated packet will have |
80 | | * zero length, but will still have a 16 byte poly1305 authentication tag. |
81 | | * Thus, packet->payload will be at least 32 bytes in size. |
82 | | * Note that handshake packets have a slightly different structure, but they are larger. |
83 | | */ |
84 | 0 | if (packet->payload_packet_len < 32) { |
85 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
86 | 0 | return; |
87 | 0 | } |
88 | | /* |
89 | | * The next three bytes after the message type are reserved and set to zero. |
90 | | */ |
91 | 0 | if (payload[1] != 0 || payload[2] != 0 || payload[3] != 0) { |
92 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
93 | 0 | return; |
94 | 0 | } |
95 | | |
96 | | /* |
97 | | * Below we make a deeper analysis; possibly inspecting multiple packets to |
98 | | * look for consistent sender/receiver index fields. We also exploit the fact |
99 | | * that handshake messages always have a fixed size. |
100 | | * |
101 | | * Stages 1-2 means we are processing a handshake sequence. |
102 | | * Stages 3-4 means we are processing a transport packet sequence. |
103 | | * |
104 | | * Message type can be one of the following: |
105 | | * 1) Handshake Initiation (148 bytes) |
106 | | * 2) Handshake Response (92 bytes) |
107 | | * 3) Cookie Reply (64 bytes) |
108 | | * 4) Transport Data (variable length, min 32 bytes) |
109 | | * |
110 | | * |
111 | | * TunnelBear VPN uses slightly different handshake packets: the format seems the same, |
112 | | * but the length is different (204/100). Not sure why and I don't know if it is some |
113 | | * kind of generic "obfuscation" attempt, used also by other apps. For the time being, |
114 | | * classify this kind of traffic as Wireguard/TunnelBear |
115 | | */ |
116 | 0 | if (message_type == WG_TYPE_HANDSHAKE_INITIATION && |
117 | 0 | (packet->payload_packet_len == 148 || packet->payload_packet_len == 204)) { |
118 | 0 | u_int32_t sender_index = get_u_int32_t(payload, 4); |
119 | | /* |
120 | | * We always start a new detection stage on a handshake initiation. |
121 | | */ |
122 | 0 | flow->l4.udp.wireguard_stage = 1 + packet->packet_direction; |
123 | 0 | flow->l4.udp.wireguard_peer_index[packet->packet_direction] = sender_index; |
124 | |
|
125 | 0 | if(flow->num_processed_pkts > 1) { |
126 | | /* This looks like a retransmission and probably this communication is blocked hence let's stop here */ |
127 | 0 | ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); |
128 | 0 | return; |
129 | 0 | } |
130 | | /* need more packets before deciding */ |
131 | 0 | } else if (message_type == WG_TYPE_HANDSHAKE_RESPONSE && |
132 | 0 | (packet->payload_packet_len == 92 || packet->payload_packet_len == 100)) { |
133 | 0 | if (flow->l4.udp.wireguard_stage == 2 - packet->packet_direction) { |
134 | | /* |
135 | | * This means we are probably processing a handshake response to a handshake |
136 | | * initiation that we've just processed, so we check if the receiver index |
137 | | * matches the index in the handshake initiation. |
138 | | */ |
139 | 0 | u_int32_t receiver_index = get_u_int32_t(payload, 8); |
140 | |
|
141 | 0 | if (receiver_index == flow->l4.udp.wireguard_peer_index[1 - packet->packet_direction]) { |
142 | 0 | if(packet->payload_packet_len == 100 && |
143 | 0 | ndpi_struct->cfg.wireguard_subclassification_by_ip /* TODO: the right option? */) |
144 | 0 | ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TUNNELBEAR); |
145 | 0 | else |
146 | 0 | ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); |
147 | 0 | } else { |
148 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
149 | 0 | } |
150 | 0 | } |
151 | | /* need more packets before deciding */ |
152 | 0 | } else if (message_type == WG_TYPE_COOKIE_REPLY && packet->payload_packet_len == 64) { |
153 | | /* |
154 | | * A cookie reply is sent as response to a handshake initiation when under load, |
155 | | * for DoS mitigation. If we have just seen a handshake initiation before |
156 | | * this cookie reply packet, we check if the receiver index in this packet |
157 | | * matches the sender index in that handshake initiation packet. |
158 | | */ |
159 | 0 | if (flow->l4.udp.wireguard_stage == 2 - packet->packet_direction) { |
160 | 0 | u_int32_t receiver_index = get_u_int32_t(payload, 4); |
161 | 0 | if (receiver_index == flow->l4.udp.wireguard_peer_index[1 - packet->packet_direction]) { |
162 | 0 | ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); |
163 | 0 | } else { |
164 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
165 | 0 | } |
166 | 0 | } |
167 | | /* need more packets before deciding */ |
168 | 0 | } else if (message_type == WG_TYPE_TRANSPORT_DATA) { |
169 | | /* |
170 | | * For detecting transport data packets, we save the peer |
171 | | * indices in both directions first. This requires at least one packet in each |
172 | | * direction (stages 3-4). The third packet that we process will be checked |
173 | | * against the appropriate index for a match (stage 5). |
174 | | */ |
175 | 0 | u_int32_t receiver_index = get_u_int32_t(payload, 4); |
176 | | |
177 | | /* We speculate this is wireguard, so let's remember it */ |
178 | 0 | flow->fast_callback_protocol_id = NDPI_PROTOCOL_WIREGUARD; |
179 | | |
180 | 0 | if (flow->l4.udp.wireguard_stage == 0) { |
181 | 0 | flow->l4.udp.wireguard_stage = 3 + packet->packet_direction; |
182 | 0 | flow->l4.udp.wireguard_peer_index[packet->packet_direction] = receiver_index; |
183 | | /* need more packets before deciding */ |
184 | 0 | } else if (flow->l4.udp.wireguard_stage == 4 - packet->packet_direction) { |
185 | 0 | flow->l4.udp.wireguard_peer_index[packet->packet_direction] = receiver_index; |
186 | 0 | flow->l4.udp.wireguard_stage = 5; |
187 | | /* need more packets before deciding */ |
188 | 0 | } else if (flow->l4.udp.wireguard_stage == 5) { |
189 | 0 | if (receiver_index == flow->l4.udp.wireguard_peer_index[packet->packet_direction]) { |
190 | 0 | ndpi_int_wireguard_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); |
191 | 0 | } else { |
192 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
193 | 0 | } |
194 | 0 | } |
195 | | /* need more packets before deciding */ |
196 | 0 | } else { |
197 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
198 | 0 | } |
199 | 0 | } |
200 | | |
201 | | void init_wireguard_dissector(struct ndpi_detection_module_struct *ndpi_struct) |
202 | 1 | { |
203 | 1 | register_dissector("WireGuard", ndpi_struct, |
204 | 1 | ndpi_search_wireguard, |
205 | 1 | NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD, |
206 | 1 | 1, NDPI_PROTOCOL_WIREGUARD); |
207 | 1 | } |