Skip to content

Commit

Permalink
STUN: improve extraction of Mapped-Address metadata (#2370)
Browse files Browse the repository at this point in the history
Enable parsing of Mapped-Address attribute for all STUN flows: that
means that STUN classification might require more packets.

Add a configuration knob to enable/disable this feature.

Note that we can have (any) STUN metadata also for flows *not*
classified as STUN (because of DTLS).

Add support for ipv6.

Restore the correct extra dissection logic for Telegram flows.
  • Loading branch information
IvanNardi authored Apr 8, 2024
1 parent f5905a6 commit 1b3ef7d
Show file tree
Hide file tree
Showing 35 changed files with 409 additions and 248 deletions.
1 change: 1 addition & 0 deletions doc/configuration_parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ TODO
| "ftp" | "tls_dissection" | enable | NULL | NULL | Enable/disable dissection of TLS packets in cleartext FTP flows (because of opportunistic TLS, via AUTH TLS msg) |
| "stun" | "max_packets_extra_dissection" | 4 | 0 | 255 | After a flow has been classified has STUN, nDPI might analyse more packets to look for a sub-classification or for metadata. This parameter set the upper limit on the number of these packets |
| "stun" | "tls_dissection" | enable | NULL | NULL | Enable/disable dissection of TLS packets multiplexed into STUN flows |
| "stun" | "metadata.attribute.mapped_address" | enable | NULL | NULL | Enable/disable extraction of (xor)-mapped-address attribute for STUN flows. If it is disabled, STUN classification might be significant faster |
| "dns" | "subclassification" | enable | NULL | NULL | Enable/disable sub-classification of DNS flows (via query/response domain name). If disabled, some flow risks are not checked |
| "dns" | "process_response" | enable | NULL | NULL | Enable/disable processing of DNS responses. By default, DNS flows are fully classified after the first request/response pair (or after the first response, if the request is missing). If this parameter is disabled, the flows are fully classified after the first packet, i.e. usually after the first request; in that case, some flow risks are not checked and some metadata are not exported |
| "http" | "process_response" | enable | NULL | NULL | Enable/disable processing of HTTP responses. By default, HTTP flows are usually fully classified after the first request/response pair. If this parameter is disabled, the flows are fully classified after the first request (or after the first response, if the request is missing); in that case, some flow risks are not checked and some metadata are not exported |
Expand Down
49 changes: 11 additions & 38 deletions example/ndpiReader.c
Original file line number Diff line number Diff line change
Expand Up @@ -1457,38 +1457,6 @@ static void parseOptions(int argc, char **argv) {

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

/**
* @brief A faster replacement for inet_ntoa().
*/
char* intoaV4(u_int32_t addr, char* buf, u_int16_t bufLen) {
char *cp;
int n;

cp = &buf[bufLen];
*--cp = '\0';

n = 4;
do {
u_int byte = addr & 0xff;

*--cp = byte % 10 + '0';
byte /= 10;
if(byte > 0) {
*--cp = byte % 10 + '0';
byte /= 10;
if(byte > 0)
*--cp = byte + '0';
}
if(n > 1)
*--cp = '.';
addr >>= 8;
} while (--n > 0);

return(cp);
}

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

static char* print_cipher(ndpi_cipher_weakness c) {
switch(c) {
case ndpi_cipher_insecure:
Expand Down Expand Up @@ -1903,14 +1871,19 @@ static void printFlow(u_int32_t id, struct ndpi_flow_info *flow, u_int16_t threa
}
}

if(flow->stun.mapped_address.ipv4 != 0) {
char buf[32];

if(flow->stun.mapped_address.port != 0) {
char buf[INET6_ADDRSTRLEN];

if(flow->stun.mapped_address.is_ipv6) {
inet_ntop(AF_INET6, &flow->stun.mapped_address.address, buf, sizeof(buf));
} else {
inet_ntop(AF_INET, &flow->stun.mapped_address.address, buf, sizeof(buf));
}
fprintf(out, "[Mapped IP/Port: %s:%u]",
intoaV4(flow->stun.mapped_address.ipv4, buf, sizeof(buf)),
flow->stun.mapped_address.port);
buf,
flow->stun.mapped_address.port);
}

if(flow->http.url[0] != '\0') {
ndpi_risk_enum risk = ndpi_validate_url(flow->http.url);

Expand Down
9 changes: 6 additions & 3 deletions example/reader_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -1309,9 +1309,12 @@ void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_fl
ndpi_inc_bin(&flow->payload_len_bin, plen2slot(len), 1);
}
}
} else if(is_ndpi_proto(flow, NDPI_PROTOCOL_STUN)) {
flow->stun.mapped_address.ipv4 = flow->ndpi_flow->stun.mapped_address.ipv4,
flow->stun.mapped_address.port = flow->ndpi_flow->stun.mapped_address.port;
}

if(flow->ndpi_flow->stun.mapped_address.port) {
memcpy(&flow->stun.mapped_address.address, &flow->ndpi_flow->stun.mapped_address.address, 16);
flow->stun.mapped_address.port = flow->ndpi_flow->stun.mapped_address.port;
flow->stun.mapped_address.is_ipv6 = flow->ndpi_flow->stun.mapped_address.is_ipv6;
}

flow->multimedia_flow_type = flow->ndpi_flow->flow_multimedia_type;
Expand Down
6 changes: 5 additions & 1 deletion example/reader_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,12 @@ typedef struct ndpi_flow_info {

struct {
struct {
u_int32_t ipv4;
union {
u_int32_t v4;
u_int8_t v6[16];
} address;
u_int16_t port;
u_int16_t is_ipv6: 1, _pad: 15;
} mapped_address;
} stun;

Expand Down
5 changes: 5 additions & 0 deletions fuzz/fuzz_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
sprintf(cfg_value, "%d", value);
ndpi_set_config(ndpi_info_mod, "stun", "max_packets_extra_dissection", cfg_value);
}
if(fuzzed_data.ConsumeBool()) {
value = fuzzed_data.ConsumeIntegralInRange(0, 1 + 1);
sprintf(cfg_value, "%d", value);
ndpi_set_config(ndpi_info_mod, "stun", "metadata.attribute.mapped_address", cfg_value);
}
if(fuzzed_data.ConsumeBool()) {
value = fuzzed_data.ConsumeIntegralInRange(0, 1 + 1);
sprintf(cfg_value, "%d", value);
Expand Down
1 change: 1 addition & 0 deletions src/include/ndpi_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ struct ndpi_detection_module_config_struct {

int stun_opportunistic_tls_enabled;
int stun_max_packets_extra_dissection;
int stun_mapped_address_enabled;

int dns_subclassification_enabled;
int dns_parse_response_enabled;
Expand Down
10 changes: 7 additions & 3 deletions src/include/ndpi_typedefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1287,8 +1287,12 @@ struct ndpi_flow_struct {
struct {
u_int8_t maybe_dtls : 1, is_turn : 1, pad : 6;
struct {
u_int32_t ipv4;
union {
u_int32_t v4;
u_int8_t v6[16];
} address; /* Network-order */
u_int16_t port;
u_int16_t is_ipv6: 1, _pad: 15;
} mapped_address;
} stun;

Expand Down Expand Up @@ -1513,8 +1517,8 @@ struct ndpi_flow_struct {
_Static_assert(sizeof(((struct ndpi_flow_struct *)0)->protos) <= 256,
"Size of the struct member protocols increased to more than 256 bytes, "
"please check if this change is necessary.");
_Static_assert(sizeof(struct ndpi_flow_struct) <= 1024,
"Size of the flow struct increased to more than 1024 bytes, "
_Static_assert(sizeof(struct ndpi_flow_struct) <= 1032,
"Size of the flow struct increased to more than 1032 bytes, "
"please check if this change is necessary.");
#endif
#endif
Expand Down
1 change: 1 addition & 0 deletions src/lib/ndpi_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -11216,6 +11216,7 @@ static const struct cfg_param {

{ "stun", "tls_dissection", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(stun_opportunistic_tls_enabled), NULL },
{ "stun", "max_packets_extra_dissection", "4", "0", "255", CFG_PARAM_INT, __OFF(stun_max_packets_extra_dissection), NULL },
{ "stun", "metadata.attribute.mapped_address", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(stun_mapped_address_enabled), NULL },

{ "dns", "subclassification", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(dns_subclassification_enabled), NULL },
{ "dns", "process_response", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(dns_parse_response_enabled), NULL },
Expand Down
67 changes: 46 additions & 21 deletions src/lib/protocols/stun.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,7 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct,
case 0x4007:
/* These are the only messages apparently whatsapp voice can use */
*app_proto = NDPI_PROTOCOL_WHATSAPP_CALL;
flow->max_extra_packets_to_check = ndpi_struct->cfg.stun_max_packets_extra_dissection;
flow->extra_packets_func = stun_search_again;
return 1;
break;

case 0x0014: /* Realm */
if(flow->host_server_name[0] == '\0') {
Expand Down Expand Up @@ -442,7 +440,7 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct,
case 0x8070: /* MS Implementation Version */
case 0x8055: /* MS Service Quality */
*app_proto = NDPI_PROTOCOL_SKYPE_TEAMS_CALL;
return 1;
break;

case 0xFF03:
*app_proto = NDPI_PROTOCOL_GOOGLE_CALL;
Expand All @@ -466,7 +464,8 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct,
break;

case 0x0020: /* XOR-MAPPED-ADDRESS */
if(real_len <= payload_length - off - 12) {
if(ndpi_struct->cfg.stun_mapped_address_enabled &&
real_len <= payload_length - off - 12) {
u_int8_t protocol_family = payload[off+5];

if(protocol_family == 0x01 /* IPv4 */) {
Expand All @@ -475,8 +474,22 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct,
u_int16_t port_xor = (magic_cookie >> 16) & 0xFFFF;

flow->stun.mapped_address.port = xored_port ^ port_xor;
flow->stun.mapped_address.ipv4 = xored_ip ^ magic_cookie;
flow->extra_packets_func = NULL; /* We're good now */
flow->stun.mapped_address.address.v4 = htonl(xored_ip ^ magic_cookie);
flow->stun.mapped_address.is_ipv6 = 0;
} else if(protocol_family == 0x02 /* IPv6 */ &&
real_len <= payload_length - off - 24) {
u_int32_t ip[4];
u_int16_t port;

port = ntohs(*((u_int16_t *)&payload[off + 6])) ^ (magic_cookie >> 16);
ip[0] = *((u_int32_t *)&payload[off + 8]) ^ htonl(magic_cookie);
ip[1] = *((u_int32_t *)&payload[off + 12]) ^ htonl(transaction_id[0]);
ip[2] = *((u_int32_t *)&payload[off + 16]) ^ htonl(transaction_id[1]);
ip[3] = *((u_int32_t *)&payload[off + 20]) ^ htonl(transaction_id[2]);

flow->stun.mapped_address.port = port;
memcpy(&flow->stun.mapped_address.address, &ip, 16);
flow->stun.mapped_address.is_ipv6 = 1;
}
}
break;
Expand All @@ -492,16 +505,25 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct,
return 1;
}

static int keep_extra_dissection(struct ndpi_flow_struct *flow)
static int keep_extra_dissection(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow)
{
if(!is_subclassification_real(flow))
return 1;

/* Looking for XOR-PEER-ADDRESS metadata; TODO: other protocols? */
if((flow->detected_protocol_stack[0] == NDPI_PROTOCOL_TELEGRAM_VOIP)
|| (flow->detected_protocol_stack[0] == NDPI_PROTOCOL_WHATSAPP_CALL))
/* See the comment at the end of ndpi_int_stun_add_connection()
where we set the extra dissection */

if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_TELEGRAM_VOIP)
return 1;
return 0;

if(ndpi_struct->cfg.stun_mapped_address_enabled &&
flow->stun.mapped_address.port)
return 0;
if(!ndpi_struct->cfg.stun_mapped_address_enabled)
return 0;

return 1;
}

static u_int32_t __get_master(struct ndpi_flow_struct *flow) {
Expand Down Expand Up @@ -694,7 +716,7 @@ static int stun_search_again(struct ndpi_detection_module_struct *ndpi_struct,
} else {
NDPI_LOG_DBG(ndpi_struct, "QUIC range. Unexpected\n");
}
return keep_extra_dissection(flow);
return keep_extra_dissection(ndpi_struct, flow);
}

/* ************************************************************ */
Expand Down Expand Up @@ -822,16 +844,19 @@ static void ndpi_int_stun_add_connection(struct ndpi_detection_module_struct *nd

/* We want extra dissection for:
* sub-classification
* metadata extraction or looking for RTP
The latter is enabled only without sub-classification or for Telegram
(to find all XOR-PEER-ADDRESS attributes)
* metadata extraction (XOR-PEER-ADDRESS/XOR-MAPPED-ADDRESS) or looking for RTP
At the moment:
* it seems ZOOM doens't have any meaningful attributes
* we want XOR-MAPPED-ADDRESS only for Telegram -> we can stop after (the first)
XOR-MAPPED-ADDRESS for all the other sub-protocols
*/
if(!flow->extra_packets_func) {
if(!is_subclassification_real(flow) ||
flow->detected_protocol_stack[0] == NDPI_PROTOCOL_TELEGRAM_VOIP /* Metadata. TODO: other protocols? */) {
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(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_ZOOM) {
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;
}
}
}
}
Expand Down
Loading

0 comments on commit 1b3ef7d

Please sign in to comment.