/src/ndpi/src/lib/protocols/rtp.c
Line | Count | Source |
1 | | /* |
2 | | * rtp.c |
3 | | * |
4 | | * Copyright (C) 2009-11 - ipoque GmbH |
5 | | * Copyright (C) 2011-26 - ntop.org |
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_RTP |
28 | | |
29 | | #include "ndpi_api.h" |
30 | | #include "ndpi_private.h" |
31 | | |
32 | 0 | #define RTP_MIN_HEADER 12 |
33 | 0 | #define RTCP_MIN_HEADER 8 |
34 | | |
35 | | static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct, |
36 | | struct ndpi_flow_struct *flow); |
37 | | |
38 | | /* *************************************************************** */ |
39 | | |
40 | | /* https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */ |
41 | | int is_valid_rtp_payload_type(uint8_t type) |
42 | 0 | { |
43 | 0 | if(!(type <= 34 || (type >= 96 && type <= 127))) |
44 | 0 | return 0; |
45 | 0 | return 1; |
46 | 0 | } |
47 | | |
48 | | /* *************************************************************** */ |
49 | | |
50 | | u_int8_t rtp_get_stream_type(u_int8_t payloadType, u_int8_t *s_type, u_int16_t sub_proto) |
51 | 0 | { |
52 | | /* General, from IANA */ |
53 | 0 | switch(payloadType) { |
54 | 0 | case 0: /* G.711 u-Law */ |
55 | 0 | case 3: /* GSM 6.10 */ |
56 | 0 | case 4: /* G.723.1 */ |
57 | 0 | case 5: /* DVI4 */ |
58 | 0 | case 6: /* DVI4 */ |
59 | 0 | case 7: /* LPC */ |
60 | 0 | case 8: /* G.711 A-Law */ |
61 | 0 | case 9: /* G.722 */ |
62 | 0 | case 10: /* L16 */ |
63 | 0 | case 11: /* L16 */ |
64 | 0 | case 12: /* QCELP */ |
65 | 0 | case 13: /* Comfort Noise */ |
66 | 0 | case 14: /* MPA */ |
67 | 0 | case 15: /* G728 */ |
68 | 0 | case 16: /* DVI4 */ |
69 | 0 | case 17: /* DVI4 */ |
70 | 0 | case 18: /* G729 */ |
71 | 0 | *s_type |= ndpi_multimedia_audio_flow; |
72 | 0 | return(1); |
73 | | |
74 | 0 | case 25: /* CelB */ |
75 | 0 | case 26: /* JPEG */ |
76 | 0 | case 28: /* nv */ |
77 | 0 | case 31: /* H261 */ |
78 | 0 | case 32: /* MPV */ |
79 | 0 | case 34: /* H263 */ |
80 | 0 | *s_type |= ndpi_multimedia_video_flow; |
81 | 0 | return(1); |
82 | 0 | } |
83 | | |
84 | | /* Microsoft; from https://learn.microsoft.com/en-us/openspecs/office_protocols/ms-rtp/3b8dc3c6-34b8-4827-9b38-3b00154f471c */ |
85 | 0 | if(sub_proto == NDPI_PROTOCOL_MSTEAMS_CALL) { |
86 | 0 | switch(payloadType) { |
87 | 0 | case 103: /* SILK Narrowband */ |
88 | 0 | case 104: /* SILK Wideband */ |
89 | 0 | case 106: /* OPUS */ |
90 | 0 | case 111: /* Siren */ |
91 | 0 | case 112: /* G.722.1 */ |
92 | 0 | case 114: /* RT Audio Wideband */ |
93 | 0 | case 115: /* RT Audio Narrowband */ |
94 | 0 | case 116: /* G.726 */ |
95 | 0 | case 117: /* G.722 */ |
96 | 0 | case 118: /* Comfort Noise Wideband */ |
97 | 0 | *s_type |= ndpi_multimedia_audio_flow; |
98 | 0 | return(1); |
99 | | |
100 | 0 | case 34: /* H.263 [MS-H26XPF] */ |
101 | 0 | case 121: /* RT Video */ |
102 | 0 | case 122: /* H.264 [MS-H264PF] */ |
103 | 0 | case 123: /* H.264 FEC [MS-H264PF] */ |
104 | 0 | *s_type |= ndpi_multimedia_video_flow; |
105 | 0 | return(1); |
106 | | |
107 | 0 | default: |
108 | 0 | *s_type |= ndpi_multimedia_unknown_flow; |
109 | 0 | return(0); |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | /* Dynamic PTs are... dynamic... :D |
114 | | * Looking at some traces, it seems specific applications keep using |
115 | | * always the same PT for audio/video... |
116 | | * TODO: something better? |
117 | | * Bottom line: checking only PT is very fast/easy, but we might have |
118 | | * false positives/negatives |
119 | | */ |
120 | | |
121 | 0 | if(sub_proto == NDPI_PROTOCOL_GOOGLE_CALL) { |
122 | 0 | switch(payloadType) { |
123 | 0 | case 111: |
124 | 0 | *s_type |= ndpi_multimedia_audio_flow; |
125 | 0 | return(1); |
126 | | |
127 | 0 | case 96: |
128 | 0 | case 100: |
129 | 0 | *s_type |= ndpi_multimedia_video_flow; |
130 | 0 | return(1); |
131 | | |
132 | 0 | default: |
133 | 0 | *s_type |= ndpi_multimedia_unknown_flow; |
134 | 0 | return(0); |
135 | 0 | } |
136 | 0 | } |
137 | | |
138 | 0 | if(sub_proto == NDPI_PROTOCOL_WHATSAPP_CALL) { |
139 | 0 | switch(payloadType) { |
140 | 0 | case 120: |
141 | 0 | *s_type |= ndpi_multimedia_audio_flow; |
142 | 0 | return(1); |
143 | | |
144 | 0 | case 97: |
145 | 0 | case 102: |
146 | 0 | *s_type |= ndpi_multimedia_video_flow; |
147 | 0 | return(1); |
148 | | |
149 | 0 | default: |
150 | 0 | *s_type |= ndpi_multimedia_unknown_flow; |
151 | 0 | return(0); |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | 0 | if(sub_proto == NDPI_PROTOCOL_FACEBOOK_VOIP) { |
156 | 0 | switch(payloadType) { |
157 | 0 | case 96: |
158 | 0 | case 97: |
159 | 0 | case 101: |
160 | 0 | case 109: |
161 | 0 | *s_type |= ndpi_multimedia_audio_flow; |
162 | 0 | return(1); |
163 | | |
164 | 0 | case 127: |
165 | 0 | *s_type |= ndpi_multimedia_video_flow; |
166 | 0 | return(1); |
167 | | |
168 | 0 | default: |
169 | 0 | *s_type |= ndpi_multimedia_unknown_flow; |
170 | 0 | return(0); |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | 0 | if(sub_proto == NDPI_PROTOCOL_TELEGRAM_VOIP) { |
175 | 0 | switch(payloadType) { |
176 | 0 | case 111: |
177 | 0 | *s_type |= ndpi_multimedia_audio_flow; |
178 | 0 | return(1); |
179 | | |
180 | 0 | case 106: |
181 | 0 | *s_type |= ndpi_multimedia_video_flow; |
182 | 0 | return(1); |
183 | | |
184 | 0 | default: |
185 | 0 | *s_type |= ndpi_multimedia_unknown_flow; |
186 | 0 | return(0); |
187 | 0 | } |
188 | 0 | } |
189 | | |
190 | 0 | if(sub_proto == NDPI_PROTOCOL_SIGNAL_VOIP) { |
191 | 0 | switch(payloadType) { |
192 | 0 | case 102: |
193 | 0 | *s_type |= ndpi_multimedia_audio_flow; |
194 | 0 | return(1); |
195 | | |
196 | 0 | case 108: |
197 | 0 | case 120: |
198 | 0 | *s_type |= ndpi_multimedia_video_flow; |
199 | 0 | return(1); |
200 | | |
201 | 0 | default: |
202 | 0 | *s_type |= ndpi_multimedia_unknown_flow; |
203 | 0 | return(0); |
204 | 0 | } |
205 | 0 | } |
206 | | |
207 | 0 | *s_type |= ndpi_multimedia_unknown_flow; |
208 | 0 | return(0); |
209 | 0 | } |
210 | | |
211 | | /* *************************************************************** */ |
212 | | |
213 | 0 | static int is_valid_rtcp_payload_type(uint8_t type) { |
214 | 0 | return (type >= 192 && type <= 213); |
215 | 0 | } |
216 | | |
217 | | /* *************************************************************** */ |
218 | | |
219 | | int is_rtp_or_rtcp(struct ndpi_detection_module_struct *ndpi_struct, |
220 | | const u_int8_t *payload, u_int16_t payload_len, u_int16_t *seq) |
221 | 0 | { |
222 | 0 | u_int8_t csrc_count, ext_header; |
223 | 0 | u_int16_t ext_len; |
224 | 0 | u_int32_t min_len; |
225 | |
|
226 | 0 | if(payload_len < 2) |
227 | 0 | return NO_RTP_RTCP; |
228 | | |
229 | 0 | if((payload[0] & 0xC0) != 0x80) { /* Version 2 */ |
230 | 0 | NDPI_LOG_DBG(ndpi_struct, "Not version 2\n"); |
231 | 0 | return NO_RTP_RTCP; |
232 | 0 | } |
233 | | |
234 | 0 | if(is_valid_rtp_payload_type(payload[1] & 0x7F) && |
235 | 0 | payload_len >= RTP_MIN_HEADER) { |
236 | | /* RTP */ |
237 | 0 | csrc_count = payload[0] & 0x0F; |
238 | 0 | ext_header = !!(payload[0] & 0x10); |
239 | 0 | min_len = RTP_MIN_HEADER + 4 * csrc_count + 4 * ext_header; |
240 | 0 | if(ext_header) { |
241 | 0 | if(min_len > payload_len) { |
242 | 0 | NDPI_LOG_DBG(ndpi_struct, "Too short (a) %d vs %d\n", min_len, payload_len); |
243 | 0 | return NO_RTP_RTCP; |
244 | 0 | } |
245 | 0 | ext_len = ntohs(*(unsigned short *)&payload[min_len - 2]); |
246 | 0 | min_len += ext_len * 4; |
247 | 0 | } |
248 | 0 | if(min_len > payload_len) { |
249 | 0 | NDPI_LOG_DBG(ndpi_struct, "Too short (b) %d vs %d\n", min_len, payload_len); |
250 | 0 | return NO_RTP_RTCP; |
251 | 0 | } |
252 | | /* Check on padding doesn't work because: |
253 | | * we may have multiple RTP packets in the same TCP/UDP datagram |
254 | | * with SRTP, padding_length field is encrypted */ |
255 | 0 | if(seq) |
256 | 0 | *seq = ntohs(*(unsigned short *)&payload[2]); |
257 | 0 | return IS_RTP; |
258 | 0 | } else if(is_valid_rtcp_payload_type(payload[1]) && |
259 | 0 | payload_len >= RTCP_MIN_HEADER) { |
260 | 0 | min_len = (ntohs(*(unsigned short *)&payload[2]) + 1) * 4; |
261 | 0 | if(min_len > payload_len) { |
262 | 0 | NDPI_LOG_DBG(ndpi_struct, "Too short (c) %d vs %d\n", min_len, payload_len); |
263 | 0 | return NO_RTP_RTCP; |
264 | 0 | } |
265 | 0 | return IS_RTCP; |
266 | 0 | } |
267 | 0 | NDPI_LOG_DBG(ndpi_struct, "not RTP/RTCP\n"); |
268 | 0 | return NO_RTP_RTCP; |
269 | 0 | } |
270 | | |
271 | | /* ************************************************************ */ |
272 | | |
273 | | static int get_rtp_info(struct ndpi_detection_module_struct *ndpi_struct, |
274 | | struct ndpi_flow_struct *flow, |
275 | | const u_int8_t *payload, |
276 | 0 | u_int16_t payload_len) { |
277 | 0 | u_int8_t packet_direction = current_pkt_from_client_to_server(ndpi_struct, flow) ? 0 : 1; |
278 | |
|
279 | 0 | if(flow->rtp[packet_direction].payload_detected == false) { |
280 | 0 | flow->rtp[packet_direction].payload_type = payload[1] & 0x7F; |
281 | 0 | flow->rtp[packet_direction].payload_detected = true; |
282 | | |
283 | | /* printf("********* [direction: %d] payload_type=%u\n", packet_direction, flow->protos.rtp[packet_direction].payload_type); */ |
284 | |
|
285 | 0 | if(((flow->rtp[packet_direction].payload_type == 126 /* Enhanced Voice Services (EVS) */) |
286 | 0 | || (flow->rtp[packet_direction].payload_type == 127 /* Enhanced Voice Services (EVS) */)) |
287 | 0 | && (payload_len > 12 /* RTP header */)) { |
288 | 0 | const u_int8_t *evs = &payload[12]; |
289 | 0 | u_int packet_len = payload_len - 12; |
290 | 0 | u_int num_bits = packet_len * 8; |
291 | |
|
292 | 0 | flow->flow_multimedia_types = ndpi_multimedia_audio_flow; |
293 | | /* printf("********* %02X [bits %u]\n", evs[0], num_bits); */ |
294 | |
|
295 | 0 | if(num_bits == 56) { |
296 | | /* A.2.1.3 Special case for 56 bit payload size (EVS Primary or EVS AMR-WB IO SID) */ |
297 | |
|
298 | 0 | if((evs[0] & 0x80) == 0) |
299 | 0 | flow->rtp[packet_direction].evs_subtype = evs[0] & 0xF; |
300 | 0 | else |
301 | 0 | flow->rtp[packet_direction].evs_subtype = evs[1] & 0xF; |
302 | 0 | } else { |
303 | | |
304 | | /* See ndpi_rtp_payload_type2str() */ |
305 | 0 | switch(num_bits) { |
306 | 0 | case 48: |
307 | 0 | case 136: |
308 | 0 | case 144: |
309 | 0 | case 160: |
310 | 0 | case 184: |
311 | 0 | case 192: |
312 | 0 | case 256: |
313 | 0 | case 264: |
314 | 0 | case 288: |
315 | 0 | case 320: |
316 | 0 | case 328: |
317 | 0 | case 368: |
318 | 0 | case 400: |
319 | 0 | case 464: |
320 | 0 | case 480: |
321 | 0 | case 488: |
322 | 0 | case 640: |
323 | 0 | case 960: |
324 | 0 | case 1280: |
325 | 0 | case 1920: |
326 | 0 | case 2560: |
327 | 0 | flow->rtp[packet_direction].evs_subtype = num_bits; |
328 | 0 | break; |
329 | | |
330 | 0 | default: |
331 | 0 | if((evs[0] >> 7) == 1) { |
332 | | /* EVS Codec Mode Request (EVS-CMR) */ |
333 | 0 | u_int8_t d_bits = evs[0] & 0X0F; |
334 | |
|
335 | 0 | flow->rtp[packet_direction].evs_subtype = d_bits + 30 /* dummy offset */; |
336 | 0 | } |
337 | 0 | break; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | } |
341 | 0 | } |
342 | 0 | return 0; |
343 | 0 | } |
344 | | |
345 | | /* ************************************************************ */ |
346 | | |
347 | 0 | static int keep_extra_dissection(struct ndpi_flow_struct *flow) { |
348 | 0 | return ((flow->rtp[0].payload_detected && flow->rtp[1].payload_detected) ? false :true); |
349 | 0 | } |
350 | | |
351 | | /* ************************************************************ */ |
352 | | |
353 | | static int rtp_search_again(struct ndpi_detection_module_struct *ndpi_struct, |
354 | 0 | struct ndpi_flow_struct *flow) { |
355 | 0 | NDPI_LOG_DBG2(ndpi_struct, "Again\n"); |
356 | |
|
357 | 0 | ndpi_rtp_search(ndpi_struct, flow); |
358 | |
|
359 | 0 | return keep_extra_dissection(flow); |
360 | 0 | } |
361 | | |
362 | | /* *************************************************************** */ |
363 | | |
364 | | static void ndpi_int_rtp_add_connection(struct ndpi_detection_module_struct *ndpi_struct, |
365 | | struct ndpi_flow_struct *flow, |
366 | | u_int16_t proto) |
367 | 0 | { |
368 | 0 | ndpi_set_detected_protocol(ndpi_struct, flow, |
369 | 0 | NDPI_PROTOCOL_UNKNOWN, proto, |
370 | 0 | NDPI_CONFIDENCE_DPI); |
371 | 0 | if(ndpi_struct->cfg.rtp_search_for_stun) { |
372 | | /* It makes sense to look for STUN only if we didn't capture the entire flow, |
373 | | from the beginning */ |
374 | 0 | if(!(flow->l4_proto == IPPROTO_TCP && ndpi_seen_flow_beginning(flow))) { |
375 | 0 | NDPI_LOG_DBG(ndpi_struct, "Enabling (STUN) extra dissection\n"); |
376 | 0 | switch_extra_dissection_to_stun(ndpi_struct, flow, 1); |
377 | 0 | } |
378 | 0 | } else if(proto == NDPI_PROTOCOL_RTP) { |
379 | 0 | if(!flow->extra_packets_func && |
380 | 0 | keep_extra_dissection(flow) && |
381 | 0 | ndpi_struct->cfg.rtp_max_packets_extra_dissection > 0) { |
382 | 0 | NDPI_LOG_DBG(ndpi_struct, "Enabling extra dissection\n"); |
383 | 0 | flow->max_extra_packets_to_check = ndpi_struct->cfg.rtp_max_packets_extra_dissection; |
384 | 0 | flow->extra_packets_func = rtp_search_again; |
385 | 0 | } |
386 | 0 | } |
387 | 0 | } |
388 | | |
389 | | /* *************************************************************** */ |
390 | | |
391 | | static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct, |
392 | 0 | struct ndpi_flow_struct *flow) { |
393 | 0 | u_int8_t is_rtp; |
394 | 0 | struct ndpi_packet_struct *packet = &ndpi_struct->packet; |
395 | 0 | const u_int8_t *payload = packet->payload; |
396 | 0 | u_int16_t payload_len = packet->payload_packet_len; |
397 | 0 | u_int16_t seq; |
398 | |
|
399 | 0 | if(packet->payload_packet_len == 0 || packet->tcp_retransmission) |
400 | 0 | return; |
401 | | |
402 | 0 | if(packet->tcp != NULL) { |
403 | 0 | if (payload_len < 2) { |
404 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
405 | 0 | return; |
406 | 0 | } |
407 | 0 | payload += 2; /* Skip the length field */ |
408 | 0 | payload_len -= 2; |
409 | 0 | } |
410 | 0 | NDPI_LOG_DBG(ndpi_struct, "search RTP (stage %d/%d)\n", flow->rtp_stage, flow->rtcp_stage); |
411 | | |
412 | | /* * Let some "unknown" packets at the beginning: |
413 | | * search for 3/4 consecutive RTP/RTCP packets. |
414 | | * Wait a little longer (4 vs 3 pkts) for RTCP to try to tell if there are only |
415 | | * RTCP packets in the flow or if RTP/RTCP are multiplexed together */ |
416 | |
|
417 | 0 | if(flow->packet_counter > 3 && |
418 | 0 | flow->rtp_stage == 0 && |
419 | 0 | flow->rtcp_stage == 0) { |
420 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
421 | 0 | return; |
422 | 0 | } |
423 | | |
424 | 0 | is_rtp = is_rtp_or_rtcp(ndpi_struct, payload, payload_len, &seq); |
425 | |
|
426 | 0 | if(is_rtp == IS_RTP) { |
427 | 0 | if(flow->rtp_stage == 2) { |
428 | 0 | if(flow->l4_proto == IPPROTO_UDP && |
429 | 0 | flow->l4.udp.line_pkts[0] >= 2 && flow->l4.udp.line_pkts[1] >= 2) { |
430 | | /* It seems that it is a LINE stuff; let its dissector to evaluate */ |
431 | 0 | } else if(flow->l4_proto == IPPROTO_UDP && flow->l4.udp.epicgames_stage > 0) { |
432 | | /* It seems that it is a EpicGames stuff; let its dissector to evaluate */ |
433 | 0 | } else if(flow->rtp_seq_set[packet->packet_direction] && |
434 | 0 | flow->rtp_seq[packet->packet_direction] == seq) { |
435 | | /* Simple heuristic to avoid false positives. Tradeoff between: |
436 | | - consecutive RTP packets should have different sequence number |
437 | | - we should handle duplicated traffic */ |
438 | 0 | NDPI_LOG_DBG(ndpi_struct, "Same seq on consecutive pkts\n"); |
439 | 0 | flow->rtp_stage = 0; |
440 | 0 | flow->rtcp_stage = 0; |
441 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
442 | 0 | } else { |
443 | 0 | get_rtp_info(ndpi_struct, flow, payload, payload_len); |
444 | 0 | rtp_get_stream_type(flow->rtp[packet->packet_direction].payload_type, |
445 | 0 | &flow->flow_multimedia_types, NDPI_PROTOCOL_UNKNOWN); |
446 | |
|
447 | 0 | NDPI_LOG_INFO(ndpi_struct, "Found RTP\n"); |
448 | 0 | ndpi_int_rtp_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_RTP); |
449 | 0 | } |
450 | 0 | return; |
451 | 0 | } |
452 | 0 | if(flow->rtp_stage == 0) { |
453 | 0 | flow->rtp_seq[packet->packet_direction] = seq; |
454 | 0 | flow->rtp_seq_set[packet->packet_direction] = 1; |
455 | 0 | } |
456 | 0 | flow->rtp_stage += 1; |
457 | 0 | } else if(is_rtp == IS_RTCP && flow->rtp_stage > 0) { |
458 | | /* RTCP after (some) RTP. Keep looking for RTP */ |
459 | 0 | } else if(is_rtp == IS_RTCP && flow->rtp_stage == 0) { |
460 | 0 | if(flow->rtcp_stage == 3) { |
461 | 0 | NDPI_LOG_INFO(ndpi_struct, "Found RTCP\n"); |
462 | 0 | ndpi_int_rtp_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_RTCP); |
463 | 0 | return; |
464 | 0 | } |
465 | 0 | flow->rtcp_stage += 1; |
466 | 0 | } else { |
467 | 0 | if(flow->rtp_stage || flow->rtcp_stage) { |
468 | 0 | u_int32_t unused; |
469 | 0 | u_int16_t app_proto = NDPI_PROTOCOL_UNKNOWN; |
470 | 0 | ndpi_protocol_category_t category; |
471 | | |
472 | | /* TODO: we should switch to the demultiplexing-code in stun dissector */ |
473 | 0 | if(is_stun(ndpi_struct, flow, &app_proto, &category) != 0 && |
474 | 0 | !is_dtls(packet->payload, packet->payload_packet_len, &unused)) { |
475 | 0 | flow->rtp_stage = 0; |
476 | 0 | flow->rtcp_stage = 0; |
477 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
478 | 0 | } |
479 | 0 | } |
480 | 0 | } |
481 | 0 | } |
482 | | |
483 | | /* *************************************************************** */ |
484 | | /* https://datatracker.ietf.org/doc/html/rfc4571 |
485 | | * message format for RTP/RTCP over TCP: |
486 | | * 0 1 2 3 |
487 | | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
488 | | * --------------------------------------------------------------- |
489 | | * | LENGTH | RTP or RTCP packet ... | |
490 | | * --------------------------------------------------------------- |
491 | | */ |
492 | | static void ndpi_search_rtp_tcp(struct ndpi_detection_module_struct *ndpi_struct, |
493 | | struct ndpi_flow_struct *flow) |
494 | 0 | { |
495 | 0 | struct ndpi_packet_struct *packet = &ndpi_struct->packet; |
496 | 0 | const u_int8_t *payload = packet->payload; |
497 | |
|
498 | 0 | if(packet->payload_packet_len < 4){ /* (2) len field + (2) min rtp/rtcp*/ |
499 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
500 | 0 | return; |
501 | 0 | } |
502 | | |
503 | 0 | u_int16_t len = ntohs(get_u_int16_t(payload, 0)); |
504 | 0 | if(len + sizeof(len) != packet->payload_packet_len) { /*fragmented packets are not handled*/ |
505 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
506 | 0 | } else { |
507 | 0 | ndpi_rtp_search(ndpi_struct, flow); |
508 | 0 | } |
509 | |
|
510 | 0 | } |
511 | | |
512 | | /* *************************************************************** */ |
513 | | static void ndpi_search_rtp_udp(struct ndpi_detection_module_struct *ndpi_struct, |
514 | | struct ndpi_flow_struct *flow) |
515 | 0 | { |
516 | 0 | struct ndpi_packet_struct *packet = &ndpi_struct->packet; |
517 | 0 | u_int16_t source = ntohs(packet->udp->source); |
518 | 0 | u_int16_t dest = ntohs(packet->udp->dest); |
519 | | /* |
520 | | * XXX: not sure if rtp/rtcp over tcp will also mix with Ethereum |
521 | | * for now, will not add it unitl we have a false positive. |
522 | | */ |
523 | 0 | if((source == 30303) || (dest == 30303 /* Avoid to mix it with Ethereum that looks alike */) |
524 | 0 | || (dest == 5355 /* LLMNR_PORT */) |
525 | 0 | || (dest == 5353 /* MDNS_PORT */) |
526 | 0 | || (dest == 9600 /* FINS_PORT */) |
527 | 0 | || (dest <= 1023)){ |
528 | 0 | NDPI_EXCLUDE_DISSECTOR(ndpi_struct, flow); |
529 | 0 | return; |
530 | 0 | } |
531 | 0 | ndpi_rtp_search(ndpi_struct, flow); |
532 | 0 | } |
533 | | |
534 | | /* *************************************************************** */ |
535 | | static void ndpi_search_rtp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) |
536 | 0 | { |
537 | 0 | struct ndpi_packet_struct *packet = &ndpi_struct->packet; |
538 | 0 | if(packet->tcp != NULL) { |
539 | 0 | ndpi_search_rtp_tcp(ndpi_struct, flow); |
540 | 0 | } else { |
541 | 0 | ndpi_search_rtp_udp(ndpi_struct, flow); |
542 | 0 | } |
543 | 0 | } |
544 | | |
545 | | /* *************************************************************** */ |
546 | | |
547 | 0 | void init_rtp_dissector(struct ndpi_detection_module_struct *ndpi_struct) { |
548 | 0 | ndpi_register_dissector("RT(C)P", ndpi_struct, |
549 | 0 | ndpi_search_rtp, |
550 | 0 | NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, |
551 | 0 | 2, NDPI_PROTOCOL_RTP, NDPI_PROTOCOL_RTCP); |
552 | 0 | } |