Skip to content

Commit

Permalink
HTTP, QUIC, TLS: allow to disable sub-classification (#2533)
Browse files Browse the repository at this point in the history
  • Loading branch information
IvanNardi authored Sep 3, 2024
1 parent 2d04024 commit 338eedd
Show file tree
Hide file tree
Showing 18 changed files with 187 additions and 8 deletions.
3 changes: 3 additions & 0 deletions doc/configuration_parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ TODO
| "tls" | "metadata.ja3c_fingerprint" | enable | NULL | NULL | Enable/disable computation and export of JA3C fingerprint for TLS flows. Note that if it is disable, the flow risk `NDPI_MALICIOUS_JA3` is not checked |
| "tls" | "metadata.ja3s_fingerprint" | enable | NULL | NULL | Enable/disable computation and export of JA3S fingerprint for TLS flows |
| "tls" | "metadata.ja4c_fingerprint" | enable | NULL | NULL | Enable/disable computation and export of JA4C fingerprint for TLS flows |
| "tls" | "subclassification" | enable | NULL | NULL | Enable/disable sub-classification of TLS/DTLS flows |
| "quic" | "subclassification" | enable | NULL | NULL | Enable/disable sub-classification of QUIC flows |
| "smtp" | "tls_dissection" | enable | NULL | NULL | Enable/disable dissection of TLS packets in cleartext SMTP flows (because of opportunistic TLS, via STARTTLS msg) |
| "imap" | "tls_dissection" | enable | NULL | NULL | Enable/disable dissection of TLS packets in cleartext IMAP flows (because of opportunistic TLS, via STARTTLS msg) |
| "pop" | "tls_dissection" | enable | NULL | NULL | Enable/disable dissection of TLS packets in cleartext POP flows (because of opportunistic TLS, via STARTTLS msg) |
Expand All @@ -43,6 +45,7 @@ TODO
| "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 |
| "http" | "subclassification" | enable | NULL | NULL | Enable/disable sub-classification of HTTP flows |
| "ookla" | "dpi.aggressiveness", | 0x01 | 0x00 | 0x01 | Detection aggressiveness for Ookla. The value is a bitmask. Values: 0x0 = disabled; 0x01 = enable heuristic for detection over TLS (via Ookla LRU cache) |
| "zoom" | "max_packets_extra_dissection" | 4 | 0 | 255 | After a flow has been classified has Zoom, 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 |
| "rtp" | "search_for_stun" | disable | NULL | NULL | After a flow has been classified as RTP or RTCP, nDPI might analyse more packets to look for STUN/DTLS packets, i.e. to try to tell if this flow is a "pure" RTP/RTCP flow or if the RTP/RTCP packets are multiplexed with STUN/DTLS. Useful for proper (sub)classification when the beginning of the flows are not captured or if there are lost packets in the the captured traffic. If enabled, nDPI requires more packets to process for each RTP/RTCP flow. |
Expand Down
15 changes: 15 additions & 0 deletions fuzz/fuzz_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
ndpi_set_config(ndpi_info_mod, "tls", "metadata.ja4c_fingerprint", cfg_value);
}
if(fuzzed_data.ConsumeBool()) {
value = fuzzed_data.ConsumeIntegralInRange(0, 1 + 1);
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
ndpi_set_config(ndpi_info_mod, "tls", "subclassification", cfg_value);
}
if(fuzzed_data.ConsumeBool()) {
value = fuzzed_data.ConsumeIntegralInRange(0, 1 + 1);
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
ndpi_set_config(ndpi_info_mod, "quic", "subclassification", cfg_value);
}
if(fuzzed_data.ConsumeBool()) {
value = fuzzed_data.ConsumeIntegralInRange(0, 1 + 1);
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
Expand Down Expand Up @@ -208,6 +218,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
ndpi_set_config(ndpi_info_mod, "http", "process_response", cfg_value);
}
if(fuzzed_data.ConsumeBool()) {
value = fuzzed_data.ConsumeIntegralInRange(0, 1 + 1);
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
ndpi_set_config(ndpi_info_mod, "http", "subclassification", cfg_value);
}
if(fuzzed_data.ConsumeBool()) {
value = fuzzed_data.ConsumeIntegralInRange(0, 0x01 + 1);
snprintf(cfg_value, sizeof(cfg_value), "%d", value);
Expand Down
4 changes: 4 additions & 0 deletions src/include/ndpi_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,9 @@ struct ndpi_detection_module_config_struct {
int tls_ja3c_fingerprint_enabled;
int tls_ja3s_fingerprint_enabled;
int tls_ja4c_fingerprint_enabled;
int tls_subclassification_enabled;

int quic_subclassification_enabled;

int smtp_opportunistic_tls_enabled;

Expand All @@ -257,6 +260,7 @@ struct ndpi_detection_module_config_struct {
int dns_parse_response_enabled;

int http_parse_response_enabled;
int http_subclassification_enabled;

int ookla_aggressiveness;

Expand Down
4 changes: 4 additions & 0 deletions src/lib/ndpi_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -11386,6 +11386,9 @@ static const struct cfg_param {
{ "tls", "metadata.ja3c_fingerprint", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_ja3c_fingerprint_enabled), NULL },
{ "tls", "metadata.ja3s_fingerprint", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_ja3s_fingerprint_enabled), NULL },
{ "tls", "metadata.ja4c_fingerprint", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_ja4c_fingerprint_enabled), NULL },
{ "tls", "subclassification", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(tls_subclassification_enabled), NULL },

{ "quic", "subclassification", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(quic_subclassification_enabled), NULL },

{ "smtp", "tls_dissection", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(smtp_opportunistic_tls_enabled), NULL },

Expand All @@ -11407,6 +11410,7 @@ static const struct cfg_param {
{ "dns", "process_response", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(dns_parse_response_enabled), NULL },

{ "http", "process_response", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(http_parse_response_enabled), NULL },
{ "http", "subclassification", "enable", NULL, NULL, CFG_PARAM_ENABLE_DISABLE, __OFF(http_subclassification_enabled), NULL },

{ "ookla", "dpi.aggressiveness", "0x01", "0", "1", CFG_PARAM_INT, __OFF(ookla_aggressiveness), NULL },

Expand Down
5 changes: 5 additions & 0 deletions src/lib/protocols/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,11 @@ static void ndpi_http_parse_subprotocol(struct ndpi_detection_module_struct *ndp
u_int16_t master_protocol;
struct ndpi_packet_struct *packet = &ndpi_struct->packet;

if(!ndpi_struct->cfg.http_subclassification_enabled) {
NDPI_LOG_DBG2(ndpi_struct, "Skip sub-protocol check because subclassification is disabled\n");
return;
}

master_protocol = NDPI_PROTOCOL_HTTP;
if(flow->detected_protocol_stack[1] != NDPI_PROTOCOL_UNKNOWN)
master_protocol = flow->detected_protocol_stack[1];
Expand Down
23 changes: 16 additions & 7 deletions src/lib/protocols/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ static void checkTLSSubprotocol(struct ndpi_detection_module_struct *ndpi_struct
int is_from_client) {
struct ndpi_packet_struct *packet = &ndpi_struct->packet;

if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN) {
if(ndpi_struct->cfg.tls_subclassification_enabled &&
flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN) {
/* Subprotocol not yet set */

if(ndpi_struct->tls_cert_cache) {
Expand Down Expand Up @@ -689,11 +690,13 @@ void processCertificateElements(struct ndpi_detection_module_struct *ndpi_struct
}
}

if(!flow->protos.tls_quic.subprotocol_detected)
if(ndpi_struct->cfg.tls_subclassification_enabled &&
!flow->protos.tls_quic.subprotocol_detected) {
if(ndpi_match_hostname_protocol(ndpi_struct, flow, __get_master(ndpi_struct, flow), dNSName, dNSName_len)) {
flow->protos.tls_quic.subprotocol_detected = 1;
ndpi_unset_risk(flow, NDPI_NUMERIC_IP_HOST);
}
}

i += len;
} else {
Expand Down Expand Up @@ -726,7 +729,8 @@ void processCertificateElements(struct ndpi_detection_module_struct *ndpi_struct
if(rdn_len && (flow->protos.tls_quic.subjectDN == NULL)) {
flow->protos.tls_quic.subjectDN = ndpi_strdup(rdnSeqBuf);

if(flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN) {
if(ndpi_struct->cfg.tls_subclassification_enabled &&
flow->detected_protocol_stack[1] == NDPI_PROTOCOL_UNKNOWN) {
/* No idea what is happening behind the scenes: let's check the certificate */
u_int32_t val;
int rc = ndpi_match_string_value(ndpi_struct->tls_cert_subject_automa.ac_automa,
Expand Down Expand Up @@ -2325,10 +2329,12 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
}

if(!is_quic) {
if(ndpi_match_hostname_protocol(ndpi_struct, flow, __get_master(ndpi_struct, flow), sni, sni_len))
if(ndpi_struct->cfg.tls_subclassification_enabled &&
ndpi_match_hostname_protocol(ndpi_struct, flow, __get_master(ndpi_struct, flow), sni, sni_len))
flow->protos.tls_quic.subprotocol_detected = 1;
} else {
if(ndpi_match_hostname_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, sni, sni_len))
if(ndpi_struct->cfg.quic_subclassification_enabled &&
ndpi_match_hostname_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, sni, sni_len))
flow->protos.tls_quic.subprotocol_detected = 1;
}

Expand Down Expand Up @@ -2614,8 +2620,11 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
/* Without SNI matching we can try to sub-classify the flow via ALPN.
Note that this happens only on very rare cases, not the common ones
("h2", "http/1.1", ...). Usefull for asymmetric traffic */
if(!flow->protos.tls_quic.subprotocol_detected)
tls_subclassify_by_alpn(ndpi_struct, flow);
if(!flow->protos.tls_quic.subprotocol_detected) {
if((is_quic && ndpi_struct->cfg.quic_subclassification_enabled) ||
(!is_quic && ndpi_struct->cfg.tls_subclassification_enabled))
tls_subclassify_by_alpn(ndpi_struct, flow);
}
}
}

Expand Down
1 change: 0 additions & 1 deletion tests/cfgs/dns_subclassification_disable/config.txt

This file was deleted.

1 change: 1 addition & 0 deletions tests/cfgs/subclassification_disable/config.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--cfg=dns,subclassification,0 --cfg=http,subclassification,0 --cfg=quic,subclassification,0 --cfg=tls,subclassification,0
1 change: 1 addition & 0 deletions tests/cfgs/subclassification_disable/pcap/anydesk.pcapng
1 change: 1 addition & 0 deletions tests/cfgs/subclassification_disable/pcap/http.pcapng
1 change: 1 addition & 0 deletions tests/cfgs/subclassification_disable/pcap/tls_ech.pcapng
44 changes: 44 additions & 0 deletions tests/cfgs/subclassification_disable/result/anydesk.pcapng.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
DPI Packets (TCP): 34 (6.80 pkts/flow)
DPI Packets (UDP): 4 (2.00 pkts/flow)
Confidence DPI : 7 (flows)
Num dissector calls: 15 (2.14 diss/flow)
LRU cache ookla: 0/0/0 (insert/search/found)
LRU cache bittorrent: 0/0/0 (insert/search/found)
LRU cache stun: 0/0/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)
LRU cache fpc_dns: 0/4/0 (insert/search/found)
Automa host: 0/0 (search/found)
Automa domain: 0/0 (search/found)
Automa tls cert: 0/0 (search/found)
Automa risk mask: 2/0 (search/found)
Automa common alpns: 1/0 (search/found)
Patricia risk mask: 14/0 (search/found)
Patricia risk mask IPv6: 0/0 (search/found)
Patricia risk: 0/0 (search/found)
Patricia risk IPv6: 0/0 (search/found)
Patricia protocols: 12/2 (search/found)
Patricia protocols IPv6: 0/0 (search/found)

DNS 4 392 2
TLS 170 45725 5

Safe 170 45725 5
Acceptable 4 392 2

JA3 Host Stats:
IP Address # JA3C
1 192.168.1.178 1
2 192.168.1.187 1
3 192.168.1.128 1
4 192.168.149.129 1


1 TCP 192.168.149.129:43535 <-> 51.83.238.219:80 [proto: 91/TLS][IP: 252/AnyDesk][Encrypted][Confidence: DPI][FPC: 252/AnyDesk, Confidence: IP address][DPI packets: 8][cat: Web/5][19 pkts/6843 bytes <-> 22 pkts/9152 bytes][Goodput ratio: 85/86][10.60 sec][bytes ratio: -0.144 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 624/488 7028/7028 1803/1610][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 360/416 1514/1514 525/549][Risk: ** Known Proto on Non Std Port **** TLS (probably) Not Carrying HTTPS **** Missing SNI TLS Extn **][Risk Score: 110][Risk Info: No ALPN / SNI should always be present / Expected on port 443][TLSv1.2][JA3C: 201999283915cc31cee6b15472ef3332][JA4: t12d640500_9197985d2161_a1e935682795][JA3S: 107030a763c7224285717ff1569a17f3][Issuer: CN=AnyNet Root CA, O=philandro Software GmbH, C=DE][Subject: C=DE, O=philandro Software GmbH, CN=AnyNet Relay][Certificate SHA-1: 9E:08:D2:58:A9:02:CD:4F:E2:4A:26:B8:48:5C:43:0B:81:29:99:E3][Firefox][Validity: 2018-11-18 02:14:23 - 2028-11-15 02:14:23][Cipher: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384][Plen Bins: 4,13,13,9,9,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,4,4,0,4,0,0,9,0,0,0,0,18,0,0]
2 TCP 192.168.1.128:48260 <-> 195.181.174.176:443 [proto: 91/TLS][IP: 0/Unknown][Encrypted][Confidence: DPI][FPC: 0/Unknown, Confidence: Unknown][DPI packets: 8][cat: Web/5][27 pkts/7693 bytes <-> 27 pkts/4853 bytes][Goodput ratio: 77/63][58.81 sec][(Advertised) ALPNs: anydesk/6.2.0/linux][bytes ratio: 0.226 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 2284/1898 10210/10228 4074/3857][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 285/180 1514/1514 460/331][Risk: ** Missing SNI TLS Extn **** Uncommon TLS ALPN **][Risk Score: 100][Risk Info: anydesk/6.2.0/linu / SNI should always be present][TLSv1.2][JA3C: 29b5a018fa5992fe23560c16af0dc9fc][JA4: t12d6406an_9197985d2161_a1e935682795][JA3S: e58f0b3c1e9eefb8ee4f92aeceee5858][Issuer: CN=AnyNet Root CA, O=philandro Software GmbH, C=DE][Subject: C=DE, O=philandro Software GmbH, CN=AnyNet Relay][Certificate SHA-1: 9E:08:D2:58:A9:02:CD:4F:E2:4A:26:B8:48:5C:43:0B:81:29:99:E3][Firefox][Validity: 2018-11-18 02:14:23 - 2028-11-15 02:14:23][Cipher: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384][Plen Bins: 0,35,20,0,10,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,0,0,0,5,0,0,0,0,0,0,15,0,0]
3 TCP 192.168.1.187:54164 <-> 192.168.1.178:7070 [proto: 91/TLS][IP: 0/Unknown][Encrypted][Confidence: DPI][FPC: 0/Unknown, Confidence: Unknown][DPI packets: 7][cat: Web/5][19 pkts/7324 bytes <-> 21 pkts/3951 bytes][Goodput ratio: 86/69][7.29 sec][bytes ratio: 0.299 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 481/137 2966/1753 831/422][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 385/188 3980/1514 894/354][Risk: ** Known Proto on Non Std Port **** TLS (probably) Not Carrying HTTPS **** Missing SNI TLS Extn **][Risk Score: 110][Risk Info: No ALPN / SNI should always be present][TLSv1.2][JA3C: 3f2fba0262b1a22b739126dfb2fe7a7d][JA4: t12d550500_168bb377f8c8_a1e935682795][JA3S: ee644a8a34c434abca4b737ec1d9efad][Subject: CN=AnyDesk Client, CN=AnyDesk Client][Certificate SHA-1: F8:4E:27:4E:F9:33:35:2F:1A:69:71:D5:02:6B:B8:72:EF:B7:BA:B0][Firefox][Cipher: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384][Plen Bins: 0,45,15,5,5,0,0,5,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,5,0,0,0,0,5,0,5]
4 TCP 192.168.1.178:52039 <-> 192.168.1.187:7070 [proto: 91/TLS][IP: 0/Unknown][Encrypted][Confidence: DPI][FPC: 0/Unknown, Confidence: Unknown][DPI packets: 6][cat: Web/5][8 pkts/2035 bytes <-> 7 pkts/2157 bytes][Goodput ratio: 76/82][0.56 sec][bytes ratio: -0.029 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 92/40 406/85 150/33][Pkt Len c2s/s2c min/avg/max/stddev: 60/54 254/308 1340/968 419/387][Risk: ** Known Proto on Non Std Port **** Weak TLS Cipher **** TLS (probably) Not Carrying HTTPS **** Missing SNI TLS Extn **][Risk Score: 210][Risk Info: No ALPN / SNI should always be present / Cipher TLS_RSA_WITH_AES_256_GCM_SHA384][TLSv1.2][JA3C: 201999283915cc31cee6b15472ef3332][JA4: t12d640500_9197985d2161_a1e935682795][JA3S: 4b505adfb4a921c5a3a39d293b0811e1 (WEAK)][Subject: CN=AnyDesk Client, CN=AnyDesk Client][Certificate SHA-1: 86:4F:2A:9F:24:71:FD:0D:6A:35:56:AC:D8:7B:3A:19:E8:03:CA:2E][Firefox][Cipher: TLS_RSA_WITH_AES_256_GCM_SHA384][Plen Bins: 0,20,0,0,0,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,0,0,20,0,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0]
5 TCP 192.168.149.129:36351 <-> 51.83.239.144:80 [proto: 91/TLS][IP: 252/AnyDesk][Encrypted][Confidence: DPI][FPC: 91/TLS, Confidence: DPI][DPI packets: 5][cat: Web/5][10 pkts/792 bytes <-> 10 pkts/925 bytes][Goodput ratio: 32/38][45.83 sec][bytes ratio: -0.077 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 32/31 5700/5700 15000/15001 7162/7162][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 79/92 105/213 25/45][Risk: ** Known Proto on Non Std Port **][Risk Score: 50][Risk Info: Expected on port 443][Plen Bins: 0,90,0,0,10,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]
6 UDP 192.168.1.187:55376 <-> 192.168.1.1:53 [proto: 5/DNS][IP: 0/Unknown][ClearText][Confidence: DPI][FPC: 5/DNS, Confidence: DPI][DPI packets: 2][cat: Network/14][1 pkts/90 bytes <-> 1 pkts/106 bytes][Goodput ratio: 53/60][0.01 sec][Hostname/SNI: relay-9b6827f2.net.anydesk.com][138.199.36.115][PLAIN TEXT (anydesk)][Plen Bins: 0,50,50,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]
7 UDP 192.168.1.187:59511 <-> 192.168.1.1:53 [proto: 5/DNS][IP: 0/Unknown][ClearText][Confidence: DPI][FPC: 5/DNS, Confidence: DPI][DPI packets: 2][cat: Network/14][1 pkts/90 bytes <-> 1 pkts/106 bytes][Goodput ratio: 53/60][0.01 sec][Hostname/SNI: relay-3185a847.net.anydesk.com][37.61.223.15][PLAIN TEXT (anydesk)][Plen Bins: 0,50,50,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]
Loading

0 comments on commit 338eedd

Please sign in to comment.