From 1b3ef7d7b2dde9d58cb217d3d7fb6b14d6281153 Mon Sep 17 00:00:00 2001
From: Ivan Nardi <12729895+IvanNardi@users.noreply.github.com>
Date: Mon, 8 Apr 2024 10:24:51 +0200
Subject: STUN: improve extraction of Mapped-Address metadata (#2370)

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.
---
 src/include/ndpi_private.h  |  1 +
 src/include/ndpi_typedefs.h | 10 +++++--
 src/lib/ndpi_main.c         |  1 +
 src/lib/protocols/stun.c    | 67 +++++++++++++++++++++++++++++++--------------
 4 files changed, 55 insertions(+), 24 deletions(-)

(limited to 'src')

diff --git a/src/include/ndpi_private.h b/src/include/ndpi_private.h
index b26803a3c..92afa1d24 100644
--- a/src/include/ndpi_private.h
+++ b/src/include/ndpi_private.h
@@ -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;
diff --git a/src/include/ndpi_typedefs.h b/src/include/ndpi_typedefs.h
index 58c9d7df5..4f1935776 100644
--- a/src/include/ndpi_typedefs.h
+++ b/src/include/ndpi_typedefs.h
@@ -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;
 
@@ -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
diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c
index e0150fe6c..c2583accc 100644
--- a/src/lib/ndpi_main.c
+++ b/src/lib/ndpi_main.c
@@ -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 },
diff --git a/src/lib/protocols/stun.c b/src/lib/protocols/stun.c
index 4e77cc532..cb0b3f001 100644
--- a/src/lib/protocols/stun.c
+++ b/src/lib/protocols/stun.c
@@ -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') {
@@ -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;
@@ -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 */) {
@@ -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;
@@ -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) {
@@ -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);
 }
 
 /* ************************************************************ */
@@ -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;
+      }
     }
   }
 }
-- 
cgit v1.2.3