Coverage Report

Created: 2025-09-17 06:55

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}