Coverage Report

Created: 2025-08-29 06:29

/src/ndpi/src/lib/protocols/raknet.c
Line
Count
Source (jump to first uncovered line)
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
2.24k
{
31
2.24k
  NDPI_LOG_INFO(ndpi_struct, "found RakNet\n");
32
2.24k
  ndpi_set_detected_protocol(ndpi_struct, flow,
33
2.24k
                             NDPI_PROTOCOL_RAKNET,
34
2.24k
                             NDPI_PROTOCOL_UNKNOWN,
35
2.24k
                             NDPI_CONFIDENCE_DPI);
36
2.24k
}
37
38
static size_t raknet_dissect_ip(struct ndpi_packet_struct * const packet, size_t offset)
39
76.7k
{
40
76.7k
  if (offset + 1 >= packet->payload_packet_len ||
41
76.7k
      (packet->payload[offset] != 0x04 /* IPv4 */ &&
42
50.9k
       packet->payload[offset] != 0x06 /* IPv6 */))
43
73.0k
  {
44
73.0k
    return 0;
45
73.0k
  }
46
47
3.69k
  return (packet->payload[offset] == 0x04 ? 4 : 16);
48
76.7k
}
49
50
static int is_custom_version(struct ndpi_detection_module_struct *ndpi_struct)
51
518k
{
52
518k
  struct ndpi_packet_struct *packet = &ndpi_struct->packet;
53
518k
  unsigned char magic[] = { 0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
54
518k
                            0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78 };
55
56
518k
  if (packet->payload_packet_len >= 1200) /* Full MTU packet */
57
29.2k
  {
58
    /* Offset 32 has been found only in the traces; the other ones are present
59
       also in the Raknet heuristic in Wireshark */
60
29.2k
    if (memcmp(magic, &packet->payload[1], sizeof(magic)) == 0 ||
61
29.2k
        memcmp(magic, &packet->payload[9], sizeof(magic)) == 0 ||
62
29.2k
        memcmp(magic, &packet->payload[17], sizeof(magic)) == 0 ||
63
29.2k
        memcmp(magic, &packet->payload[32], sizeof(magic)) == 0)
64
498
    {
65
498
      return 1;
66
498
    }
67
29.2k
  }
68
518k
  return 0;
69
518k
}
70
71
static void exclude_proto(struct ndpi_detection_module_struct *ndpi_struct,
72
                          struct ndpi_flow_struct *flow)
73
512k
{
74
512k
  if (flow->l4.udp.raknet_custom == 1)
75
429
  {
76
429
    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
429
    ndpi_int_raknet_add_connection(ndpi_struct, flow);
81
512k
  } else {
82
512k
    NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow);
83
512k
  }
84
512k
}
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
532k
{
90
532k
  struct ndpi_packet_struct * const packet = &ndpi_struct->packet;
91
532k
  u_int8_t op, ip_addr_offset, required_packets = 3;
92
93
532k
  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
532k
  if (flow->packet_counter == 1)
105
518k
  {
106
518k
    flow->l4.udp.raknet_custom = is_custom_version(ndpi_struct);
107
518k
  }
108
109
532k
  if (packet->payload_packet_len < 7)
110
37.5k
  {
111
37.5k
    exclude_proto(ndpi_struct, flow);
112
37.5k
    return;
113
37.5k
  }
114
115
495k
  op = packet->payload[0];
116
117
495k
  switch (op)
118
495k
  {
119
88.5k
    case 0x00: /* Connected Ping */
120
88.5k
      if (packet->payload_packet_len != 8)
121
79.6k
      {
122
79.6k
        exclude_proto(ndpi_struct, flow);
123
79.6k
        return;
124
79.6k
      }
125
8.93k
      required_packets = 6;
126
8.93k
      break;
127
128
25.2k
    case 0x01: /* Unconnected Ping */
129
90.7k
    case 0x02: /* Unconnected Ping */
130
90.7k
      if (packet->payload_packet_len != 32)
131
87.1k
      {
132
87.1k
        exclude_proto(ndpi_struct, flow);
133
87.1k
        return;
134
87.1k
      }
135
3.63k
      required_packets = 6;
136
3.63k
      break;
137
138
2.17k
    case 0x03: /* Connected Pong */
139
2.17k
      if (packet->payload_packet_len != 16)
140
1.93k
      {
141
1.93k
        exclude_proto(ndpi_struct, flow);
142
1.93k
        return;
143
1.93k
      }
144
237
      required_packets = 6;
145
237
      break;
146
147
5.12k
    case 0x05: /* Open Connection Request 1 */
148
5.12k
      if (packet->payload_packet_len < 18 ||
149
5.12k
          packet->payload[17] > 10 /* maximum supported protocol version */)
150
2.84k
      {
151
2.84k
        exclude_proto(ndpi_struct, flow);
152
2.84k
        return;
153
2.84k
      }
154
2.28k
      required_packets = 6;
155
2.28k
      break;
156
157
1.98k
    case 0x06: /* Open Connection Reply 1 */
158
1.98k
      if (packet->payload_packet_len != 28 ||
159
1.98k
          packet->payload[25] > 0x01 /* connection uses encryption: bool -> 0x00 or 0x01 */)
160
1.64k
      {
161
1.64k
        exclude_proto(ndpi_struct, flow);
162
1.64k
        return;
163
1.64k
      }
164
165
338
      {
166
338
        u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 26));
167
338
        if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
168
51
        {
169
51
          exclude_proto(ndpi_struct, flow);
170
51
          return;
171
51
        }
172
338
      }
173
287
      required_packets = 4;
174
287
      break;
175
176
2.04k
    case 0x07: /* Open Connection Request 2 */
177
2.04k
      ip_addr_offset = raknet_dissect_ip(packet, 17);
178
2.04k
      if (ip_addr_offset == 0 ||
179
2.04k
          !((ip_addr_offset == 16 && packet->payload_packet_len == 46) ||
180
652
            (ip_addr_offset == 4 && packet->payload_packet_len == 34)))
181
1.59k
      {
182
1.59k
        exclude_proto(ndpi_struct, flow);
183
1.59k
        return;
184
1.59k
      }
185
186
455
      {
187
455
          u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 20 + ip_addr_offset));
188
455
          if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
189
72
          {
190
72
            exclude_proto(ndpi_struct, flow);
191
72
            return;
192
72
          }
193
455
      }
194
383
      break;
195
196
6.92k
    case 0x08: /* Open Connection Reply 2 */
197
6.92k
      ip_addr_offset = raknet_dissect_ip(packet, 25);
198
6.92k
      if (ip_addr_offset == 0 ||
199
6.92k
          !((ip_addr_offset == 16 && packet->payload_packet_len == 47) ||
200
1.91k
            (ip_addr_offset == 4 && packet->payload_packet_len == 35)))
201
5.36k
      {
202
5.36k
        exclude_proto(ndpi_struct, flow);
203
5.36k
        return;
204
5.36k
      }
205
206
1.56k
      {
207
1.56k
          u_int16_t mtu_size = ntohs(get_u_int16_t(packet->payload, 28 + ip_addr_offset));
208
1.56k
          if (mtu_size > 1500 /* Max. supported MTU, see: http://www.jenkinssoftware.com/raknet/manual/programmingtips.html */)
209
283
          {
210
283
            exclude_proto(ndpi_struct, flow);
211
283
            return;
212
283
          }
213
1.56k
      }
214
1.28k
      break;
215
216
2.32k
    case 0x10: /* Connection Request Accepted */
217
6.15k
    case 0x13: /* New Incoming Connection */
218
6.15k
      {
219
6.15k
  size_t i;
220
221
6.15k
        ip_addr_offset = 4 + raknet_dissect_ip(packet, 0);
222
6.15k
        if (op == 0x10)
223
2.32k
        {
224
2.32k
          ip_addr_offset += 2; // System Index
225
2.32k
        }
226
67.7k
        for (i = 0; i < 10; ++i)
227
61.5k
        {
228
61.5k
          ip_addr_offset += 3 + raknet_dissect_ip(packet, ip_addr_offset);
229
61.5k
        }
230
6.15k
        ip_addr_offset += 16;
231
6.15k
        if (ip_addr_offset != packet->payload_packet_len)
232
5.99k
        {
233
5.99k
          exclude_proto(ndpi_struct, flow);
234
5.99k
          return;
235
5.99k
        }
236
6.15k
      }
237
159
      break;
238
239
    /* Check for Frame Set Packet's */
240
14.3k
    case 0x80:
241
17.4k
    case 0x81:
242
18.9k
    case 0x82:
243
19.7k
    case 0x83:
244
23.3k
    case 0x84:
245
25.8k
    case 0x85:
246
26.4k
    case 0x86:
247
27.7k
    case 0x87:
248
28.2k
    case 0x88:
249
29.0k
    case 0x89:
250
30.8k
    case 0x8a:
251
31.3k
    case 0x8b:
252
31.8k
    case 0x8c:
253
32.5k
    case 0x8d:
254
32.5k
      {
255
32.5k
        size_t frame_offset = 4;
256
257
41.2k
        do {
258
41.2k
          u_int8_t msg_flags = get_u_int8_t(packet->payload, frame_offset);
259
41.2k
          if ((msg_flags & 0x0F) != 0)
260
20.3k
          {
261
20.3k
            exclude_proto(ndpi_struct, flow);
262
20.3k
            return;
263
20.3k
          }
264
265
20.9k
          u_int16_t msg_size = ntohs(get_u_int16_t(packet->payload, frame_offset + 1));
266
20.9k
          msg_size /= 8;
267
20.9k
          if (msg_size == 0)
268
4.60k
          {
269
4.60k
            exclude_proto(ndpi_struct, flow);
270
4.60k
            break;
271
4.60k
          }
272
273
16.3k
          u_int8_t reliability_type = (msg_flags & 0xE0) >> 5;
274
16.3k
          if (reliability_type >= 2 && reliability_type <= 4 /* is reliable? */)
275
1.50k
          {
276
1.50k
            frame_offset += 3;
277
1.50k
          }
278
16.3k
          if (reliability_type == 1 || reliability_type == 4 /* is sequenced? */)
279
832
          {
280
832
            frame_offset += 3;
281
832
          }
282
16.3k
          if (reliability_type == 3 || reliability_type == 7 /* is ordered? */)
283
973
          {
284
973
            frame_offset += 4;
285
973
          }
286
16.3k
          if ((msg_flags & 0x10) != 0 /* is fragmented? */)
287
758
          {
288
758
            frame_offset += 10;
289
758
          }
290
291
16.3k
          frame_offset += msg_size + 3;
292
16.3k
        } while (frame_offset + 3 <= packet->payload_packet_len);
293
294
        /* We've dissected enough to be sure. */
295
12.2k
        if (frame_offset == packet->payload_packet_len)
296
1.53k
        {
297
          /* This packet might also be a RTP/RTCP one: give precedence to RTP/RTCP dissector */
298
1.53k
          if(flow->rtp_stage == 0 && flow->rtcp_stage == 0)
299
337
            ndpi_int_raknet_add_connection(ndpi_struct, flow);
300
10.6k
        } else {
301
10.6k
          exclude_proto(ndpi_struct, flow);
302
10.6k
        }
303
12.2k
        return;
304
32.5k
      }
305
306
985
    case 0x09: /* Connection Request */
307
985
      if (packet->payload_packet_len != 16)
308
849
      {
309
849
        exclude_proto(ndpi_struct, flow);
310
849
        return;
311
849
      }
312
136
      required_packets = 6;
313
136
      break;
314
315
2.83k
    case 0x15: /* Disconnect */
316
2.83k
      required_packets = 8;
317
2.83k
      break;
318
319
805
    case 0x19: /* Incompatible Protocol */
320
805
      if (packet->payload_packet_len != 25 ||
321
805
          packet->payload[17] > 10)
322
701
      {
323
701
        exclude_proto(ndpi_struct, flow);
324
701
        return;
325
701
      }
326
104
      break;
327
328
1.18k
    case 0x1c: /* Unconnected Pong */
329
1.18k
      if (packet->payload_packet_len < 35)
330
181
      {
331
181
        exclude_proto(ndpi_struct, flow);
332
181
        return;
333
181
      }
334
335
1.00k
      {
336
1.00k
        u_int16_t motd_len = ntohs(get_u_int16_t(packet->payload, 33));
337
338
1.00k
        if (motd_len == 0 || motd_len + 35 != packet->payload_packet_len)
339
833
        {
340
833
          exclude_proto(ndpi_struct, flow);
341
833
          return;
342
833
        }
343
1.00k
      }
344
173
      break;
345
346
1.60k
    case 0xa0: /* NACK */
347
3.13k
    case 0xc0: /* ACK */
348
3.13k
      {
349
3.13k
        u_int16_t record_count = ntohs(get_u_int16_t(packet->payload, 1));
350
3.13k
        size_t record_index = 0, record_offset = 3;
351
352
10.1k
        do {
353
10.1k
          if (packet->payload[record_offset] == 0x00 /* Range */)
354
5.70k
          {
355
5.70k
            record_offset += 7;
356
5.70k
          } else if (packet->payload[record_offset] == 0x01 /* No Range */)
357
2.46k
          {
358
2.46k
            record_offset += 4;
359
2.46k
          } else {
360
1.98k
            exclude_proto(ndpi_struct, flow);
361
1.98k
            return;
362
1.98k
          }
363
10.1k
        } while (++record_index < record_count &&
364
8.16k
                 record_offset + 4 <= packet->payload_packet_len);
365
366
1.15k
        if (record_index == record_count && record_offset == packet->payload_packet_len)
367
294
        {
368
          /* This packet might also be a RTP/RTCP one: give precedence to RTP/RTCP dissector */
369
294
          if(flow->rtp_stage == 0 && flow->rtcp_stage == 0)
370
257
            ndpi_int_raknet_add_connection(ndpi_struct, flow);
371
860
        } else {
372
860
          exclude_proto(ndpi_struct, flow);
373
860
        }
374
1.15k
        return;
375
3.13k
      }
376
377
2.51k
    case 0xfe: /* Game Packet */
378
2.51k
      required_packets = 8;
379
2.51k
      break;
380
381
247k
    default: /* Invalid RakNet packet */
382
247k
      exclude_proto(ndpi_struct, flow);
383
247k
      return;
384
495k
  }
385
386
22.9k
  if (flow->packet_counter < required_packets)
387
21.7k
  {
388
21.7k
    return;
389
21.7k
  }
390
391
1.22k
  ndpi_int_raknet_add_connection(ndpi_struct, flow);
392
1.22k
}
393
394
void init_raknet_dissector(struct ndpi_detection_module_struct *ndpi_struct)
395
15.4k
{
396
15.4k
  register_dissector("RakNet", ndpi_struct,
397
15.4k
                     ndpi_search_raknet,
398
15.4k
                     NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD,
399
15.4k
                     1, NDPI_PROTOCOL_RAKNET);
400
15.4k
}