Skip to content

Commit

Permalink
STUN: improve detection of Telegram calls (#2671)
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanNardi authored Jan 14, 2025
1 parent af011e3 commit 252be78
Show file tree
Hide file tree
Showing 9 changed files with 129 additions and 51 deletions.
2 changes: 1 addition & 1 deletion src/include/ndpi_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ int search_into_bittorrent_cache(struct ndpi_detection_module_struct *ndpi_struc
int is_stun(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
u_int16_t *app_proto);
void switch_extra_dissection_to_stun(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow);
void switch_extra_dissection_to_stun(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, int std_callback);

/* TPKT */
int tpkt_verify_hdr(const struct ndpi_packet_struct * const packet);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/protocols/rtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ static void ndpi_int_rtp_add_connection(struct ndpi_detection_module_struct *ndp
from the beginning */
if(!(flow->l4_proto == IPPROTO_TCP && ndpi_seen_flow_beginning(flow))) {
NDPI_LOG_DBG(ndpi_struct, "Enabling (STUN) extra dissection\n");
switch_extra_dissection_to_stun(ndpi_struct, flow);
switch_extra_dissection_to_stun(ndpi_struct, flow, 1);
}
}
}
Expand Down
78 changes: 72 additions & 6 deletions src/lib/protocols/stun.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ static int stun_search_again(struct ndpi_detection_module_struct *ndpi_struct,


/* Valid classifications:
* STUN, DTLS, STUN/RTP, DTLS/SRTP, RTP or RTCP (the last two, only from RTP dissector)
* STUN, DTLS, STUN/RTP, DTLS/SRTP, RTP or RTCP (only from RTP dissector)
and TELEGRAM (only from Telegram dissector, note that TELEGRAM != TELEGRAM_VOIP!!)
* STUN/APP, DTLS/APP, SRTP/APP ["real" sub-classification]
The idea is:
* the specific "real" application (WA/FB/Signal/...), if present, should
Expand All @@ -79,7 +80,8 @@ static int is_subclassification_real_by_proto(u_int16_t proto)
proto == NDPI_PROTOCOL_RTP ||
proto == NDPI_PROTOCOL_RTCP ||
proto == NDPI_PROTOCOL_SRTP ||
proto == NDPI_PROTOCOL_DTLS)
proto == NDPI_PROTOCOL_DTLS ||
proto == NDPI_PROTOCOL_TELEGRAM)
return 0;
return 1;
}
Expand Down Expand Up @@ -493,6 +495,9 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct,
*app_proto = NDPI_PROTOCOL_SIGNAL_VOIP;
}

if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_TELEGRAM)
*app_proto = NDPI_PROTOCOL_TELEGRAM_VOIP;

off = STUN_HDR_LEN;
while(off + 4 < payload_length) {
u_int16_t attribute = ntohs(*((u_int16_t *)&payload[off]));
Expand Down Expand Up @@ -783,7 +788,8 @@ static u_int32_t __get_master(struct ndpi_flow_struct *flow) {

if(flow->detected_protocol_stack[1] != NDPI_PROTOCOL_UNKNOWN)
return flow->detected_protocol_stack[1];
if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN)
if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN &&
flow->detected_protocol_stack[0] != NDPI_PROTOCOL_TELEGRAM)
return flow->detected_protocol_stack[0];
return NDPI_PROTOCOL_STUN;
}
Expand Down Expand Up @@ -1039,6 +1045,62 @@ static int stun_search_again(struct ndpi_detection_module_struct *ndpi_struct,

/* ************************************************************ */

static int stun_telegram_search_again(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
{
struct ndpi_packet_struct *packet = &ndpi_struct->packet;
const u_int8_t *orig_payload;
u_int16_t orig_payload_length;
char pattern[12] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
u_int16_t length;

NDPI_LOG_DBG2(ndpi_struct, "[T] Packet counter %d protos %d/%d Monitoring? %d\n",
flow->packet_counter,
flow->detected_protocol_stack[0], flow->detected_protocol_stack[1],
flow->monitoring);

/* For SOME of its STUN flows, Telegram uses a custom encapsulation
There is no documentation. It seems:
* some unknown packets (especially at the beginning/end of the flow) have a bunch of 0xFF
* the other packets encapsulate standard STUN/DTLS/RTP payload at offset 24
(with a previous field containing the payload length)
*/

if(packet->payload_packet_len <= 28) {
NDPI_LOG_DBG(ndpi_struct, "Malformed custom Telegram packet (too short)\n");
return keep_extra_dissection(ndpi_struct, flow);
}

if(memcmp(&packet->payload[16], pattern, sizeof(pattern)) == 0) {
NDPI_LOG_DBG(ndpi_struct, "Custom/Unknown Telegram packet\n");
return keep_extra_dissection(ndpi_struct, flow);
}

/* It should be STUN/DTLS/RTP */

length = ntohs(*(u_int16_t *)&packet->payload[22]);
if(24 + length > packet->payload_packet_len) {
NDPI_LOG_DBG(ndpi_struct, "Malformed custom Telegram packet (too long: %d %d)\n",
length, packet->payload_packet_len);
return keep_extra_dissection(ndpi_struct, flow);
}

orig_payload = packet->payload;
orig_payload_length = packet->payload_packet_len ;
packet->payload = packet->payload + 24;
packet->payload_packet_len = length;

stun_search_again(ndpi_struct, flow);

packet->payload = orig_payload;
packet->payload_packet_len = orig_payload_length;

return keep_extra_dissection(ndpi_struct, flow);
}

/* ************************************************************ */

static u_int64_t get_stun_lru_key(struct ndpi_flow_struct *flow, u_int8_t rev) {
if(rev) {
if(flow->is_ipv6)
Expand Down Expand Up @@ -1150,19 +1212,23 @@ static void ndpi_int_stun_add_connection(struct ndpi_detection_module_struct *nd
}
}

switch_extra_dissection_to_stun(ndpi_struct, flow);
switch_extra_dissection_to_stun(ndpi_struct, flow, 1);
}

/* ************************************************************ */

void switch_extra_dissection_to_stun(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
struct ndpi_flow_struct *flow,
int std_callback)
{
if(!flow->extra_packets_func) {
if(keep_extra_dissection(ndpi_struct, flow)) {
NDPI_LOG_DBG(ndpi_struct, "Enabling extra dissection\n");
flow->max_extra_packets_to_check = ndpi_struct->cfg.stun_max_packets_extra_dissection;
flow->extra_packets_func = stun_search_again;
if(std_callback)
flow->extra_packets_func = stun_search_again;
else
flow->extra_packets_func = stun_telegram_search_again;
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/lib/protocols/telegram.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ static void ndpi_search_telegram(struct ndpi_detection_module_struct *ndpi_struc

if(found == 12) {
ndpi_int_telegram_add_connection(ndpi_struct, flow, NDPI_CONFIDENCE_DPI);
/* It seems this kind of traffic is used:
* for "normal" stuff (at least years ago... and now? TODO)
* for calls, as a custom encapsulation of STUN/DTLS/RTP packets
Since we are not able to tell the former from the latter, always
switch to STUN dissection. If we find STUN/DTLS/RTP stuff we will
update the classification to something like STUN/Telegram_voip,
otherwise it will remain Telegram */
switch_extra_dissection_to_stun(ndpi_struct, flow, 0);
return;
}
}
Expand Down
Loading

0 comments on commit 252be78

Please sign in to comment.