diff options
author | Ivan Nardi <12729895+IvanNardi@users.noreply.github.com> | 2024-04-04 18:16:40 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-04 18:16:40 +0200 |
commit | c0d3f8a92eb229a42c99373864059558483e7fbd (patch) | |
tree | 4dd0e8f17f266ab772cbb4a229b5a44397f12c6d | |
parent | 6e61368cd609899048560405ad792705fffb1f1a (diff) |
STUN: rework sub-classification (#2361)
The main goal is to have the "real" application (if any; i.e.
Signal/Whatsapp/Telegram/...) always as "application" protocol and not
as "master" one
-rw-r--r-- | src/lib/protocols/stun.c | 157 | ||||
-rw-r--r-- | tests/cfgs/default/result/stun_classic.pcap.out | 6 | ||||
-rw-r--r-- | tests/cfgs/default/result/stun_dtls_rtp_unidir.pcapng.out | 2 | ||||
-rw-r--r-- | tests/cfgs/stun_extra_dissection/result/stun_dtls_rtp_unidir.pcapng.out | 10 |
4 files changed, 120 insertions, 55 deletions
diff --git a/src/lib/protocols/stun.c b/src/lib/protocols/stun.c index d0397a590..4e77cc532 100644 --- a/src/lib/protocols/stun.c +++ b/src/lib/protocols/stun.c @@ -55,11 +55,73 @@ static u_int64_t get_stun_lru_key_raw4(u_int32_t ip, u_int16_t port); static u_int64_t get_stun_lru_key_raw6(u_int8_t *ip, u_int16_t port); static void ndpi_int_stun_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, - u_int app_proto); + u_int16_t app_proto, + u_int16_t master_proto); static int stun_search_again(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); +/* Valid classifications: + * STUN, DTLS, STUN/RTP, DTLS/SRTP + * STUN/APP, DTLS/APP, SRTP/APP ["real" sub-classification] + The idea is: + * the specific "real" application (WA/FB/Signal/...), if present, should + be always set as "app" protocol, with STUN or DTLS or SRTP as "master" protocol + * every "real" application that we handle, if it uses RTP, it is + encrypted --> SRTP + * keep STUN/RTP for the generic case without sub-classification [because + nDPI uses SRTP only when it is sure that there is encryption] +*/ + +static int is_subclassification_real_by_proto(u_int16_t proto) +{ + if(proto == NDPI_PROTOCOL_UNKNOWN || + proto == NDPI_PROTOCOL_STUN || + proto == NDPI_PROTOCOL_RTP || + proto == NDPI_PROTOCOL_SRTP || + proto == NDPI_PROTOCOL_DTLS) + return 0; + return 1; +} + +static int is_subclassification_real(struct ndpi_flow_struct *flow) +{ + /* No previous subclassification */ + if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN) + return 0; + return is_subclassification_real_by_proto(flow->detected_protocol_stack[0]); +} + +static int is_new_subclassification_better(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + u_int16_t new_app_proto) +{ + NDPI_LOG_DBG(ndpi_struct, "%d/%d -> %d\n", + flow->detected_protocol_stack[1], flow->detected_protocol_stack[0], + new_app_proto); + + /* If we don't have a real subclassification, we might want to lookup into the cache again + (even if new_app_proto == NDPI_PROTOCOL_UNKNOWN) */ + + if(is_subclassification_real(flow) && + new_app_proto == NDPI_PROTOCOL_UNKNOWN) + return 0; + + /* Debug */ + if(new_app_proto != NDPI_PROTOCOL_UNKNOWN && + is_subclassification_real(flow) && + new_app_proto != flow->detected_protocol_stack[0]) { + NDPI_LOG_ERR(ndpi_struct, "Incoherent sub-classification change %d/%d->%d \n", + flow->detected_protocol_stack[1], + flow->detected_protocol_stack[0], new_app_proto); + } + + if(new_app_proto != flow->detected_protocol_stack[0]) + return 1; + return 0; +} + + static u_int16_t search_into_cache(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { @@ -111,11 +173,7 @@ static void add_to_caches(struct ndpi_detection_module_struct *ndpi_struct, { u_int64_t key, key_rev; - if(ndpi_struct->stun_cache && - app_proto != NDPI_PROTOCOL_STUN && - app_proto != NDPI_PROTOCOL_UNKNOWN) { - /* No sense to add STUN, but only subprotocols */ - + if(ndpi_struct->stun_cache) { key = get_stun_lru_key(flow, 0); ndpi_lru_add_to_cache(ndpi_struct->stun_cache, key, app_proto, ndpi_get_current_time(flow)); key_rev = get_stun_lru_key(flow, 1); @@ -292,8 +350,8 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct, inet_ntop(AF_INET, &ip, buf, sizeof(buf)), port, flow->detected_protocol_stack[0]); - if(1 /* TODO: enable/disable */ && - ndpi_struct->stun_cache) { + if(ndpi_struct->stun_cache && + is_subclassification_real(flow)) { u_int64_t key = get_stun_lru_key_raw4(ip, port); ndpi_lru_add_to_cache(ndpi_struct->stun_cache, key, @@ -316,8 +374,8 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct, inet_ntop(AF_INET6, &ip, buf, sizeof(buf)), port, flow->detected_protocol_stack[0]); - if(1 /* TODO: enable/disable */ && - ndpi_struct->stun_cache) { + if(ndpi_struct->stun_cache && + is_subclassification_real(flow)) { u_int64_t key = get_stun_lru_key_raw6((u_int8_t *)ip, port); ndpi_lru_add_to_cache(ndpi_struct->stun_cache, key, @@ -436,14 +494,9 @@ int is_stun(struct ndpi_detection_module_struct *ndpi_struct, static int keep_extra_dissection(struct ndpi_flow_struct *flow) { - if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN /* No subclassification */) + if(!is_subclassification_real(flow)) return 1; - /* We have a sub-classification */ - - if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_RTP) - return 0; - /* 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)) @@ -486,9 +539,10 @@ static int stun_search_again(struct ndpi_detection_module_struct *ndpi_struct, /* RFC9443 */ if(first_byte <= 3) { NDPI_LOG_DBG(ndpi_struct, "Still STUN\n"); - if(is_stun(ndpi_struct, flow, &app_proto) /* To extract other metadata */ && - flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN /* No previous subclassification */) { - ndpi_int_stun_add_connection(ndpi_struct, flow, app_proto); + if(is_stun(ndpi_struct, flow, &app_proto)) { /* To extract other metadata */ + if(is_new_subclassification_better(ndpi_struct, flow, app_proto)) { + ndpi_int_stun_add_connection(ndpi_struct, flow, app_proto, __get_master(flow)); + } } } else if(first_byte <= 15) { NDPI_LOG_DBG(ndpi_struct, "DROP range. Unexpected\n"); @@ -562,10 +616,10 @@ static int stun_search_again(struct ndpi_detection_module_struct *ndpi_struct, flow->stun.maybe_dtls = 0; flow->max_extra_packets_to_check -= 10; - - NDPI_LOG_DBG(ndpi_struct, "(%d/%d)\n", - flow->detected_protocol_stack[0], flow->detected_protocol_stack[1]); } + + NDPI_LOG_DBG(ndpi_struct, "(%d/%d)\n", + flow->detected_protocol_stack[0], flow->detected_protocol_stack[1]); } } } @@ -613,23 +667,25 @@ static int stun_search_again(struct ndpi_detection_module_struct *ndpi_struct, rtp_get_stream_type(packet->payload[1] & 0x7F, &flow->flow_multimedia_type); - if(flow->detected_protocol_stack[1] != NDPI_PROTOCOL_UNKNOWN) { - if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_DTLS) { - /* Keep DTLS/SUBPROTO since we already wrote to flow->protos.tls_quic */ + if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_RTP && + flow->detected_protocol_stack[1] != NDPI_PROTOCOL_SRTP) { + + if(flow->detected_protocol_stack[1] != NDPI_PROTOCOL_UNKNOWN) { + if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_DTLS) { + /* Keep DTLS/SUBPROTO since we already wrote to flow->protos.tls_quic */ + } else { + /* STUN/SUBPROTO -> SRTP/SUBPROTO */ + ndpi_int_stun_add_connection(ndpi_struct, flow, + flow->detected_protocol_stack[0], NDPI_PROTOCOL_SRTP); + } } else { - /* STUN/SUBPROTO -> SUBPROTO/RTP */ - ndpi_set_detected_protocol(ndpi_struct, flow, - NDPI_PROTOCOL_RTP, flow->detected_protocol_stack[0], - NDPI_CONFIDENCE_DPI); + /* STUN -> STUN/RTP, or + DTLS -> DTLS/SRTP */ + ndpi_int_stun_add_connection(ndpi_struct, flow, + __get_master(flow) == NDPI_PROTOCOL_STUN ? NDPI_PROTOCOL_RTP: NDPI_PROTOCOL_SRTP, + __get_master(flow)); } - } else { - /* STUN -> STUN/RTP, or - DTLS -> DTLS/RTP */ - ndpi_set_detected_protocol(ndpi_struct, flow, - NDPI_PROTOCOL_RTP, __get_master(flow), - NDPI_CONFIDENCE_DPI); } - return 0; /* Stop */ } else if(rtp_rtcp == IS_RTCP) { NDPI_LOG_DBG(ndpi_struct, "RTCP\n"); } else { @@ -700,8 +756,12 @@ int stun_search_into_zoom_cache(struct ndpi_detection_module_struct *ndpi_struct static void ndpi_int_stun_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, - u_int app_proto) { + u_int16_t app_proto, + u_int16_t master_proto) { ndpi_confidence_t confidence = NDPI_CONFIDENCE_DPI; + u_int16_t new_app_proto; + + NDPI_LOG_DBG(ndpi_struct, "Wanting %d/%d\n", master_proto, app_proto); if(app_proto == NDPI_PROTOCOL_UNKNOWN) { /* https://support.google.com/a/answer/1279090?hl=en */ @@ -734,23 +794,28 @@ static void ndpi_int_stun_add_connection(struct ndpi_detection_module_struct *nd } } - if(app_proto == NDPI_PROTOCOL_UNKNOWN) { - app_proto = search_into_cache(ndpi_struct, flow); - if(app_proto != NDPI_PROTOCOL_UNKNOWN) + if(!is_subclassification_real_by_proto(app_proto)) { + new_app_proto = search_into_cache(ndpi_struct, flow); + if(new_app_proto != NDPI_PROTOCOL_UNKNOWN) { confidence = NDPI_CONFIDENCE_DPI_CACHE; + if(app_proto == NDPI_PROTOCOL_RTP) + master_proto = NDPI_PROTOCOL_SRTP; /* STUN/RTP --> SRTP/APP */ + app_proto = new_app_proto; + } } - if(app_proto != NDPI_PROTOCOL_UNKNOWN) + /* Adding only real subclassifications */ + if(is_subclassification_real_by_proto(app_proto)) add_to_caches(ndpi_struct, flow, app_proto); if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN || app_proto != NDPI_PROTOCOL_UNKNOWN) { - NDPI_LOG_DBG(ndpi_struct, "Setting %d\n", app_proto); - ndpi_set_detected_protocol(ndpi_struct, flow, app_proto, __get_master(flow), confidence); + NDPI_LOG_DBG(ndpi_struct, "Setting %d/%d\n", master_proto, app_proto); + ndpi_set_detected_protocol(ndpi_struct, flow, app_proto, master_proto, confidence); /* In "normal" data-path the generic code in `ndpi_internal_detection_process_packet()` takes care of setting the category */ if(flow->extra_packets_func) { - ndpi_protocol ret = { __get_master(flow), app_proto, NDPI_PROTOCOL_UNKNOWN /* unused */, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NULL}; + ndpi_protocol ret = { master_proto, app_proto, NDPI_PROTOCOL_UNKNOWN /* unused */, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NULL}; flow->category = ndpi_get_proto_category(ndpi_struct, ret); } } @@ -762,7 +827,7 @@ static void ndpi_int_stun_add_connection(struct ndpi_detection_module_struct *nd (to find all XOR-PEER-ADDRESS attributes) */ if(!flow->extra_packets_func) { - if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN /* No-subclassification */ || + 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; @@ -791,7 +856,7 @@ static void ndpi_search_stun(struct ndpi_detection_module_struct *ndpi_struct, s } if(is_stun(ndpi_struct, flow, &app_proto)) { - ndpi_int_stun_add_connection(ndpi_struct, flow, app_proto); + ndpi_int_stun_add_connection(ndpi_struct, flow, app_proto, __get_master(flow)); return; } diff --git a/tests/cfgs/default/result/stun_classic.pcap.out b/tests/cfgs/default/result/stun_classic.pcap.out index 904c92789..af05263d9 100644 --- a/tests/cfgs/default/result/stun_classic.pcap.out +++ b/tests/cfgs/default/result/stun_classic.pcap.out @@ -1,10 +1,10 @@ -DPI Packets (UDP): 3 (3.00 pkts/flow) +DPI Packets (UDP): 5 (5.00 pkts/flow) Confidence DPI : 1 (flows) Num dissector calls: 6 (6.00 diss/flow) LRU cache ookla: 0/0/0 (insert/search/found) LRU cache bittorrent: 0/0/0 (insert/search/found) LRU cache zoom: 0/0/0 (insert/search/found) -LRU cache stun: 0/4/0 (insert/search/found) +LRU cache stun: 0/6/0 (insert/search/found) LRU cache tls_cert: 0/0/0 (insert/search/found) LRU cache mining: 0/0/0 (insert/search/found) LRU cache msteams: 0/0/0 (insert/search/found) @@ -25,4 +25,4 @@ RTP 22 1624 1 Acceptable 22 1624 1 - 1 UDP 172.16.63.224:55050 <-> 172.16.63.21:13958 [proto: 78.87/STUN.RTP][IP: 0/Unknown][ClearText][Confidence: DPI][DPI packets: 3][cat: Network/14][9 pkts/662 bytes <-> 13 pkts/962 bytes][Goodput ratio: 43/43][0.23 sec][bytes ratio: -0.185 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 4/0 32/17 101/42 32/11][Pkt Len c2s/s2c min/avg/max/stddev: 70/74 74/74 74/74 1/0][Risk: ** Known Proto on Non Std Port **][Risk Score: 50][Plen Bins: 4,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + 1 UDP 172.16.63.224:55050 <-> 172.16.63.21:13958 [proto: 78.87/STUN.RTP][IP: 0/Unknown][ClearText][Confidence: DPI][DPI packets: 5][cat: Media/1][9 pkts/662 bytes <-> 13 pkts/962 bytes][Goodput ratio: 43/43][0.23 sec][bytes ratio: -0.185 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 4/0 32/17 101/42 32/11][Pkt Len c2s/s2c min/avg/max/stddev: 70/74 74/74 74/74 1/0][Risk: ** Known Proto on Non Std Port **][Risk Score: 50][Plen Bins: 4,95,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] diff --git a/tests/cfgs/default/result/stun_dtls_rtp_unidir.pcapng.out b/tests/cfgs/default/result/stun_dtls_rtp_unidir.pcapng.out index 30fb52b8b..784028f5a 100644 --- a/tests/cfgs/default/result/stun_dtls_rtp_unidir.pcapng.out +++ b/tests/cfgs/default/result/stun_dtls_rtp_unidir.pcapng.out @@ -4,7 +4,7 @@ Num dissector calls: 12 (6.00 diss/flow) LRU cache ookla: 0/0/0 (insert/search/found) LRU cache bittorrent: 0/0/0 (insert/search/found) LRU cache zoom: 0/0/0 (insert/search/found) -LRU cache stun: 6/28/0 (insert/search/found) +LRU cache stun: 0/28/0 (insert/search/found) LRU cache tls_cert: 0/0/0 (insert/search/found) LRU cache mining: 0/0/0 (insert/search/found) LRU cache msteams: 0/0/0 (insert/search/found) diff --git a/tests/cfgs/stun_extra_dissection/result/stun_dtls_rtp_unidir.pcapng.out b/tests/cfgs/stun_extra_dissection/result/stun_dtls_rtp_unidir.pcapng.out index 5ced24b59..9d690016f 100644 --- a/tests/cfgs/stun_extra_dissection/result/stun_dtls_rtp_unidir.pcapng.out +++ b/tests/cfgs/stun_extra_dissection/result/stun_dtls_rtp_unidir.pcapng.out @@ -1,10 +1,10 @@ -DPI Packets (UDP): 36 (18.00 pkts/flow) +DPI Packets (UDP): 43 (21.50 pkts/flow) Confidence DPI : 2 (flows) Num dissector calls: 12 (6.00 diss/flow) LRU cache ookla: 0/0/0 (insert/search/found) LRU cache bittorrent: 0/0/0 (insert/search/found) LRU cache zoom: 0/0/0 (insert/search/found) -LRU cache stun: 32/98/0 (insert/search/found) +LRU cache stun: 0/120/0 (insert/search/found) LRU cache tls_cert: 0/5/0 (insert/search/found) LRU cache mining: 0/0/0 (insert/search/found) LRU cache msteams: 0/0/0 (insert/search/found) @@ -21,7 +21,7 @@ Patricia risk IPv6: 0/0 (search/found) Patricia protocols: 4/0 (search/found) Patricia protocols IPv6: 0/0 (search/found) -RTP 43 10358 2 +SRTP 43 10358 2 Acceptable 43 10358 2 @@ -30,5 +30,5 @@ JA3 Host Stats: 1 10.10.0.1 1 - 1 UDP 10.1.0.3:5853 -> 10.10.0.1:2808 [proto: 30.87/DTLS.RTP][IP: 0/Unknown][Encrypted][Confidence: DPI][DPI packets: 15][cat: Network/14][18 pkts/5384 bytes -> 0 pkts/0 bytes][Goodput ratio: 86/0][7.17 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 386/0 4001/0 979/0][Pkt Len c2s/s2c min/avg/max/stddev: 102/0 299/0 750/0 221/0][Risk: ** Self-signed Cert **][Risk Score: 100][Risk Info: CN=8][DTLSv1.0][JA3S: 1cfcbe58451407e23669f1dd08565519][Issuer: CN=8][Subject: CN=8][Certificate SHA-1: 94:8C:6F:C3:00:6A:A1:63:F1:52:7E:7F:1F:A7:93:90:46:3B:B1:2D][Validity: 2015-12-10 05:41:43 - 2016-01-10 05:41:43][Cipher: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA][PLAIN TEXT (Coturn)][Plen Bins: 0,5,5,5,34,22,0,0,0,5,0,0,0,0,0,5,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] - 2 UDP 10.10.0.1:65226 -> 10.1.0.3:57730 [proto: 30.87/DTLS.RTP][IP: 0/Unknown][Encrypted][Confidence: DPI][DPI packets: 21][cat: Network/14][25 pkts/4974 bytes -> 0 pkts/0 bytes][Goodput ratio: 79/0][7.16 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 324/0 4001/0 904/0][Pkt Len c2s/s2c min/avg/max/stddev: 78/0 199/0 478/0 92/0][DTLSv1.0][JA3C: fd8faf73d274d5614a51dae82304be0a][JA4: t00d250500_c70d7c76d4be_255c854b9f77][PLAIN TEXT (username1)][Plen Bins: 0,8,16,16,32,0,4,8,0,12,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + 1 UDP 10.1.0.3:5853 -> 10.10.0.1:2808 [proto: 30.338/DTLS.SRTP][IP: 0/Unknown][Encrypted][Confidence: DPI][DPI packets: 18][cat: Media/1][18 pkts/5384 bytes -> 0 pkts/0 bytes][Goodput ratio: 86/0][7.17 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 386/0 4001/0 979/0][Pkt Len c2s/s2c min/avg/max/stddev: 102/0 299/0 750/0 221/0][Risk: ** Self-signed Cert **** Unidirectional Traffic **][Risk Score: 110][Risk Info: No server to client traffic / CN=8][DTLSv1.0][JA3S: 1cfcbe58451407e23669f1dd08565519][Issuer: CN=8][Subject: CN=8][Certificate SHA-1: 94:8C:6F:C3:00:6A:A1:63:F1:52:7E:7F:1F:A7:93:90:46:3B:B1:2D][Validity: 2015-12-10 05:41:43 - 2016-01-10 05:41:43][Cipher: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA][PLAIN TEXT (Coturn)][Plen Bins: 0,5,5,5,34,22,0,0,0,5,0,0,0,0,0,5,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + 2 UDP 10.10.0.1:65226 -> 10.1.0.3:57730 [proto: 30.338/DTLS.SRTP][IP: 0/Unknown][Encrypted][Confidence: DPI][DPI packets: 25][cat: Media/1][25 pkts/4974 bytes -> 0 pkts/0 bytes][Goodput ratio: 79/0][7.16 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 324/0 4001/0 904/0][Pkt Len c2s/s2c min/avg/max/stddev: 78/0 199/0 478/0 92/0][Risk: ** Unidirectional Traffic **][Risk Score: 10][Risk Info: No server to client traffic][DTLSv1.0][JA3C: fd8faf73d274d5614a51dae82304be0a][JA4: t00d250500_c70d7c76d4be_255c854b9f77][PLAIN TEXT (username1)][Plen Bins: 0,8,16,16,32,0,4,8,0,12,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] |