Coverage Report

Created: 2025-11-02 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/ndpi/src/lib/protocols/raknet.c
Line
Count
Source
1
/*
2
 * raknet.c
3
 *
4
 * Copyright (C) 2011-25 - ntop.org
5
 *
6
 * nDPI is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * nDPI is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 */
20
21
#include "ndpi_protocol_ids.h"
22
23
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_RAKNET
24
25
#include "ndpi_api.h"
26
#include "ndpi_private.h"
27
28
static void ndpi_int_raknet_add_connection(struct ndpi_detection_module_struct * const ndpi_struct,
29
                                           struct ndpi_flow_struct * const flow)
30
0
{
31
0
  NDPI_LOG_INFO(ndpi_struct, "found RakNet\n");
32
0
  ndpi_set_detected_protocol(ndpi_struct, flow,
33
0
                             NDPI_PROTOCOL_RAKNET,
34
0
                             NDPI_PROTOCOL_UNKNOWN,
35
0
                             NDPI_CONFIDENCE_DPI);
36
0
}
37
38
static size_t raknet_dissect_ip(struct ndpi_packet_struct * const packet, size_t offset)
39
0
{
40
0
  if (offset + 1 >= packet->payload_packet_len ||
41
0
      (packet->payload[offset] != 0x04 /* IPv4 */ &&
42
0
       packet->payload[offset] != 0x06 /* IPv6 */))
43
0
  {
44
0
    return 0;
45
0
  }
46
47
0
  return (packet->payload[offset] == 0x04 ? 4 : 16);
48
0
}
49
50
static int is_custom_version(struct ndpi_detection_module_struct *ndpi_struct)
51
0
{
52
0
  struct ndpi_packet_struct *packet = &ndpi_struct->packet;
53
0
  unsigned char magic[] = { 0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
54
0
                            0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78 };
55
56
0
  if (packet->payload_packet_len >= 1200) /* Full MTU packet */
57
0
  {
58
    /* Offset 32 has been found only in the traces; the other ones are present
59
       also in the Raknet heuristic in Wireshark */
60
0
    if (memcmp(magic, &packet->payload[1], sizeof(magic)) == 0 ||
61
0
        memcmp(magic, &packet->payload[9], sizeof(magic)) == 0 ||
62
0
        memcmp(magic, &packet->payload[17], sizeof(magic)) == 0 ||
63
0
        memcmp(magic, &packet->payload[32], sizeof(magic)) == 0)
64
0
    {
65
0
      return 1;
66
0
    }
67
0
  }
68
0
  return 0;
69
0
}
70
71
static void exclude_proto(struct ndpi_detection_module_struct *ndpi_struct,
72
                          struct ndpi_flow_struct *flow)
73
0
{
74
0
  if (flow->l4.udp.raknet_custom == 1)
75
0
  {
76
0
    NDPI_LOG_INFO(ndpi_struct, "found RakNet (custom version)\n");
77
    /* Classify as Raknet or as Roblox?
78
       This pattern ha been observed with Roblox games but it might be used by
79
       other protocols too. Keep the generic classification, for the time being */
80
0
    ndpi_int_raknet_add_connection(ndpi_struct, flow);
81
0
  } else {
82
0
    NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
83
0
  }
84
0
}
85
86
/* Reference: https://wiki.vg/Raknet_Protocol */
87
static void ndpi_search_raknet(struct ndpi_detection_module_struct *ndpi_struct,
88
                               struct ndpi_flow_struct *flow)
89
0
{
90
0
  struct ndpi_packet_struct * const packet = &ndpi_struct->packet;
91
0
  u_int8_t op, ip_addr_offset, required_packets = 3;
92
93
0
  NDPI_LOG_DBG(ndpi_struct, "search RakNet\n");
94
95
  /* There are two "versions" of Raknet:
96
     * plaintext one: we need multiple packets for classification and for extracting metadata
97
     * custom/encrypted one: an extension used by Roblox games (and others?).
98
       Only the first pkt is required.
99
     The main issue is that these two versions "overlap", i.e. some plaintext flows might be wrongly
100
     identified as encrypted one (losing their metadata).
101
     Solution: check for the custoom/encrypted version, cache the result and use it only if/when the
102
     standard detection ends.
103
  */
104
0
  if (flow->packet_counter == 1)
105
0
  {
106
0
    flow->l4.udp.raknet_custom = is_custom_version(ndpi_struct);
107
0
  }
108
109
0
  if (packet->payload_packet_len < 7)
110
0
  {
111
0
    exclude_proto(ndpi_struct, flow);
112
0
    return;
113
0
  }
114
115
0
  op = packet->payload[0];
116
117
0
  switch (op)
118
0
  {
119
0
    case 0x00: /* Connected Ping */
120
0
      if (packet->payload_packet_len != 8)
121
0
      {
122
0
        exclude_proto(ndpi_struct, flow);
123
0
        return;
124
0
      }
125
0
      required_packets = 6;
126
0
      break;
127
128
0
    case 0x01: /* Unconnected Ping */
129
0
    case 0x02: /* Unconnected Ping */
130
0
      if (packet->payload_packet_len != 32)
131
0
      {
132
0
        exclude_proto(ndpi_struct, flow);
133
0
        return;
134
0
      }
135
0
      required_packets = 6;
136
0
      break;
137
138
0
    case 0x03: /* Connected Pong */
139
0
      if (packet->payload_packet_len != 16)
140
0
      {
141
0
        exclude_proto(ndpi_struct, flow);
142
0
        return;
143
0
      }
144
0
      required_packets = 6;
145
0
      break;
146
147
0
    case 0x05: /* Open Connection Request 1 */
148
0
      if (packet->payload_packet_len < 18 ||
149
0
          packet->payload[17] > 10 /* maximum supported protocol version */)
150
0
      {
151
0
        exclude_proto(ndpi_struct, flow);
152
0
        return;
153
0
      }
154
0
      required_packets = 6;
155
0
      break;
156
157
0
    case 0x06: /* Open Connection Reply 1 */
158
0
      if (packet->payload_packet_len != 28 ||
159
0
          packet->payload[25] > 0x01 /* connection uses encryption: bool -> 0x00 or 0x01 */)
160
0
      {
161
0
        exclude_proto(ndpi_struct, flow);
162
0
        return;
163
0
      }
164
165
0
      {
166
0
        u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 26));
167
0
        if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
168
0
        {
169
0
          exclude_proto(ndpi_struct, flow);
170
0
          return;
171
0
        }
172
0
      }
173
0
      required_packets = 4;
174
0
      break;
175
176
0
    case 0x07: /* Open Connection Request 2 */
177
0
      ip_addr_offset = raknet_dissect_ip(packet, 17);
178
0
      if (ip_addr_offset == 0 ||
179
0
          !((ip_addr_offset == 16 && packet->payload_packet_len == 46) ||
180
0
            (ip_addr_offset == 4 && packet->payload_packet_len == 34)))
181
0
      {
182
0
        exclude_proto(ndpi_struct, flow);
183
0
        return;
184
0
      }
185
186
0
      {
187
0
          u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 20 + ip_addr_offset));
188
0
          if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
189
0
          {
190
0
            exclude_proto(ndpi_struct, flow);
191
0
            return;
192
0
          }
193
0
      }
194
0
      break;
195
196
0
    case 0x08: /* Open Connection Reply 2 */
197
0
      ip_addr_offset = raknet_dissect_ip(packet, 25);
198
0
      if (ip_addr_offset == 0 ||
199
0
          !((ip_addr_offset == 16 && packet->payload_packet_len == 47) ||
200
0
            (ip_addr_offset == 4 && packet->payload_packet_len == 35)))
201
0
      {
202
0
        exclude_proto(ndpi_struct, flow);
203
0
        return;
204
0
      }
205
206
0
      {
207
0
          u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 28 + ip_addr_offset));
208
0
          if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
209
0
          {
210
0
            exclude_proto(ndpi_struct, flow);
211
0
            return;
212
0
          }
213
0
      }
214
0
      break;
215
216
0
    case 0x10: /* Connection Request Accepted */
217
0
    case 0x13: /* New Incoming Connection */
218
0
      {
219
0
  size_t i;
220
221
0
        ip_addr_offset = 4 + raknet_dissect_ip(packet, 0);
222
0
        if (op == 0x10)
223
0
        {
224
0
          ip_addr_offset += 2; // System Index
225
0
        }
226
0
        for (i = 0; i < 10; ++i)
227
0
        {
228
0
          ip_addr_offset += 3 + raknet_dissect_ip(packet, ip_addr_offset);
229
0
        }
230
0
        ip_addr_offset += 16;
231
0
        if (ip_addr_offset != packet->payload_packet_len)
232
0
        {
233
0
          exclude_proto(ndpi_struct, flow);
234
0
          return;
235
0
        }
236
0
      }
237
0
      break;
238
239
    /* Check for Frame Set Packet's */
240
0
    case 0x80:
241
0
    case 0x81:
242
0
    case 0x82:
243
0
    case 0x83:
244
0
    case 0x84:
245
0
    case 0x85:
246
0
    case 0x86:
247
0
    case 0x87:
248
0
    case 0x88:
249
0
    case 0x89:
250
0
    case 0x8a:
251
0
    case 0x8b:
252
0
    case 0x8c:
253
0
    case 0x8d:
254
0
      {
255
0
        size_t frame_offset = 4;
256
257
0
        do {
258
0
          u_int8_t msg_flags = get_u_int8_t(packet->payload, frame_offset);
259
0
          if ((msg_flags & 0x0F) != 0)
260
0
          {
261
0
            exclude_proto(ndpi_struct, flow);
262
0
            return;
263
0
          }
264
265
0
          u_int16_t msg_size = ntohs(get_u_int16_t(packet->payload, frame_offset + 1));
266
0
          msg_size /= 8;
267
0
          if (msg_size == 0)
268
0
          {
269
0
            exclude_proto(ndpi_struct, flow);
270
0
            break;
271
0
          }
272
273
0
          u_int8_t reliability_type = (msg_flags & 0xE0) >> 5;
274
0
          if (reliability_type >= 2 && reliability_type <= 4 /* is reliable? */)
275
0
          {
276
0
            frame_offset += 3;
277
0
          }
278
0
          if (reliability_type == 1 || reliability_type == 4 /* is sequenced? */)
279
0
          {
280
0
            frame_offset += 3;
281
0
          }
282
0
          if (reliability_type == 3 || reliability_type == 7 /* is ordered? */)
283
0
          {
284
0
            frame_offset += 4;
285
0
          }
286
0
          if ((msg_flags & 0x10) != 0 /* is fragmented? */)
287
0
          {
288
0
            frame_offset += 10;
289
0
          }
290
291
0
          frame_offset += msg_size + 3;
292
0
        } while (frame_offset + 3 <= packet->payload_packet_len);
293
294
        /* We've dissected enough to be sure. */
295
0
        if (frame_offset == packet->payload_packet_len)
296
0
        {
297
          /* This packet might also be a RTP/RTCP one: give precedence to RTP/RTCP dissector */
298
0
          if(flow->rtp_stage == 0 && flow->rtcp_stage == 0)
299
0
            ndpi_int_raknet_add_connection(ndpi_struct, flow);
300
0
        } else {
301
0
          exclude_proto(ndpi_struct, flow);
302
0
        }
303
0
        return;
304
0
      }
305
306
0
    case 0x09: /* Connection Request */
307
0
      if (packet->payload_packet_len != 16)
308
0
      {
309
0
        exclude_proto(ndpi_struct, flow);
310
0
        return;
311
0
      }
312
0
      required_packets = 6;
313
0
      break;
314
315
0
    case 0x15: /* Disconnect */
316
0
      required_packets = 8;
317
0
      break;
318
319
0
    case 0x19: /* Incompatible Protocol */
320
0
      if (packet->payload_packet_len != 25 ||
321
0
          packet->payload[17] > 10)
322
0
      {
323
0
        exclude_proto(ndpi_struct, flow);
324
0
        return;
325
0
      }
326
0
      break;
327
328
0
    case 0x1c: /* Unconnected Pong */
329
0
      if (packet->payload_packet_len < 35)
330
0
      {
331
0
        exclude_proto(ndpi_struct, flow);
332
0
        return;
333
0
      }
334
335
0
      {
336
0
        u_int16_t motd_len = ntohs(get_u_int16_t(packet->payload, 33));
337
338
0
        if (motd_len == 0 || motd_len + 35 != packet->payload_packet_len)
339
0
        {
340
0
          exclude_proto(ndpi_struct, flow);
341
0
          return;
342
0
        }
343
0
      }
344
0
      break;
345
346
0
    case 0xa0: /* NACK */
347
0
    case 0xc0: /* ACK */
348
0
      {
349
0
        u_int16_t record_count = ntohs(get_u_int16_t(packet->payload, 1));
350
0
        size_t record_index = 0, record_offset = 3;
351
352
0
        do {
353
0
          if (packet->payload[record_offset] == 0x00 /* Range */)
354
0
          {
355
0
            record_offset += 7;
356
0
          } else if (packet->payload[record_offset] == 0x01 /* No Range */)
357
0
          {
358
0
            record_offset += 4;
359
0
          } else {
360
0
            exclude_proto(ndpi_struct, flow);
361
0
            return;
362
0
          }
363
0
        } while (++record_index < record_count &&
364
0
                 record_offset + 4 <= packet->payload_packet_len);
365
366
0
        if (record_index == record_count && record_offset == packet->payload_packet_len)
367
0
        {
368
          /* This packet might also be a RTP/RTCP one: give precedence to RTP/RTCP dissector */
369
0
          if(flow->rtp_stage == 0 && flow->rtcp_stage == 0)
370
0
            ndpi_int_raknet_add_connection(ndpi_struct, flow);
371
0
        } else {
372
0
          exclude_proto(ndpi_struct, flow);
373
0
        }
374
0
        return;
375
0
      }
376
377
0
    case 0xfe: /* Game Packet */
378
0
      required_packets = 8;
379
0
      break;
380
381
0
    default: /* Invalid RakNet packet */
382
0
      exclude_proto(ndpi_struct, flow);
383
0
      return;
384
0
  }
385
386
0
  if (flow->packet_counter < required_packets)
387
0
  {
388
0
    return;
389
0
  }
390
391
0
  ndpi_int_raknet_add_connection(ndpi_struct, flow);
392
0
}
393
394
void init_raknet_dissector(struct ndpi_detection_module_struct *ndpi_struct)
395
0
{
396
0
  register_dissector("RakNet", ndpi_struct,
397
0
                     ndpi_search_raknet,
398
0
                     NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD,
399
0
                     1, NDPI_PROTOCOL_RAKNET);
400
0
}