aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Deri <lucaderi@users.noreply.github.com>2020-08-22 16:30:29 +0200
committerGitHub <noreply@github.com>2020-08-22 16:30:29 +0200
commit519ba7a9d5d97c7bcc2c4270617b8d11afc45d8e (patch)
tree86d33ea644de44046f487ed3e60534ddd9f11890
parentfef199ad450d451d88d143d395dfcfd7906deefc (diff)
parentb23cfd6b8444ec44dcc1b349ed0ee0659df8447d (diff)
Merge pull request #989 from IvanNardi/quic
Improve QUIC detection
-rw-r--r--configure.seed6
-rw-r--r--example/Makefile.in2
-rw-r--r--example/ndpiReader.c4
-rw-r--r--fuzz/Makefile.am6
-rw-r--r--src/include/ndpi_api.h.in9
-rw-r--r--src/include/ndpi_typedefs.h1
-rw-r--r--src/lib/ndpi_main.c32
-rw-r--r--src/lib/ndpi_serializer.c4
-rw-r--r--src/lib/protocols/quic.c1275
-rw-r--r--src/lib/protocols/tls.c32
-rw-r--r--tests/pcap/quic-23.pcapbin0 -> 7535 bytes
-rw-r--r--tests/pcap/quic-24.pcapbin0 -> 8264 bytes
-rw-r--r--tests/pcap/quic-27.pcapbin0 -> 13231 bytes
-rw-r--r--tests/pcap/quic-28.pcapngbin0 -> 256428 bytes
-rw-r--r--tests/pcap/quic-29.pcapngbin0 -> 11356 bytes
-rw-r--r--tests/pcap/quic-mvfst-22.pcapbin0 -> 296167 bytes
-rw-r--r--tests/pcap/quic-mvfst-22_decryption_error.pcapbin0 -> 406162 bytes
-rw-r--r--tests/pcap/quic-mvfst-27.pcapbin0 -> 13231 bytes
-rw-r--r--tests/pcap/quic_q39.pcapbin0 -> 25169 bytes
-rw-r--r--tests/pcap/quic_q43.pcapbin0 -> 1520 bytes
-rw-r--r--tests/pcap/quic_q46.pcapbin0 -> 21585 bytes
-rw-r--r--tests/pcap/quic_q46_b.pcapbin0 -> 7364 bytes
-rw-r--r--tests/pcap/quic_q50.pcapbin0 -> 20778 bytes
-rw-r--r--tests/result/http_ipv6.pcap.out19
-rw-r--r--tests/result/quic-23.pcap.out8
-rw-r--r--tests/result/quic-24.pcap.out8
-rw-r--r--tests/result/quic-27.pcap.out8
-rw-r--r--tests/result/quic-mvfst-22.pcap.out8
-rw-r--r--tests/result/quic-mvfst-22_decryption_error.pcap.out3
-rw-r--r--tests/result/quic-mvfst-27.pcap.out8
-rw-r--r--tests/result/quic.pcap.out21
-rw-r--r--tests/result/quic_q39.pcap.out3
-rw-r--r--tests/result/quic_q43.pcap.out3
-rw-r--r--tests/result/quic_q46.pcap.out3
-rw-r--r--tests/result/quic_q46_b.pcap.out3
-rw-r--r--tests/result/quic_q50.pcap.out3
-rw-r--r--tests/result/starcraft_battle.pcap.out2
-rw-r--r--tests/result/viber.pcap.out2
-rw-r--r--tests/result/weibo.pcap.out4
39 files changed, 1322 insertions, 155 deletions
diff --git a/configure.seed b/configure.seed
index ecde0579b..aed5c3529 100644
--- a/configure.seed
+++ b/configure.seed
@@ -169,6 +169,12 @@ AM_CONDITIONAL([HAS_FUZZLDFLAGS], [test "x$has_sanitizefuzzer" = "xyes"])
AC_CHECK_LIB(pthread, pthread_setaffinity_np, AC_DEFINE_UNQUOTED(HAVE_PTHREAD_SETAFFINITY_NP, 1, [libc has pthread_setaffinity_np]))
+AC_ARG_ENABLE([gcrypt],
+ [AS_HELP_STRING([--disable-gcrypt], [Avoid compiling with libgcrypt/libgpg-error, even if they are present. QUIC sub-classification may be missing])],
+ [:],
+ [AC_CHECK_LIB(gcrypt, gcry_cipher_checktag)
+ AC_CHECK_LIB(gpg-error, gpg_strerror_r)])
+
dnl> PCRE
AC_ARG_WITH(pcre, [ --with-pcre Enable nDPI build with libpcre])
if test "${with_pcre+set}" = set; then :
diff --git a/example/Makefile.in b/example/Makefile.in
index df7885166..32e36677d 100644
--- a/example/Makefile.in
+++ b/example/Makefile.in
@@ -3,7 +3,7 @@ CXX=@CXX@
SRCHOME=../src
CFLAGS=-g -fPIC -DPIC -I$(SRCHOME)/include @CFLAGS@
LIBNDPI=$(SRCHOME)/lib/libndpi.a
-LDFLAGS=$(LIBNDPI) @PCAP_LIB@ @ADDITIONAL_LIBS@ -lpthread -lm @LDFLAGS@
+LDFLAGS=$(LIBNDPI) @PCAP_LIB@ @LIBS@ @ADDITIONAL_LIBS@ -lpthread -lm @LDFLAGS@
HEADERS=intrusion_detection.h reader_util.h $(SRCHOME)/include/ndpi_api.h \
$(SRCHOME)/include/ndpi_typedefs.h $(SRCHOME)/include/ndpi_protocol_ids.h
OBJS=ndpiReader.o reader_util.o intrusion_detection.o
diff --git a/example/ndpiReader.c b/example/ndpiReader.c
index 5202c8b78..ed56c2114 100644
--- a/example/ndpiReader.c
+++ b/example/ndpiReader.c
@@ -3699,6 +3699,10 @@ int orginal_main(int argc, char **argv) {
"------------------------------------------------------------\n\n");
printf("Using nDPI (%s) [%d thread(s)]\n", ndpi_revision(), num_threads);
+
+ const char *gcrypt_ver = ndpi_get_gcrypt_version();
+ if(gcrypt_ver)
+ printf("Using libgcrypt version %s\n", gcrypt_ver);
}
signal(SIGINT, sigproc);
diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am
index 959bedda8..b70eae2d8 100644
--- a/fuzz/Makefile.am
+++ b/fuzz/Makefile.am
@@ -3,7 +3,7 @@ bin_PROGRAMS = fuzz_process_packet fuzz_ndpi_reader fuzz_ndpi_reader_with_main
fuzz_process_packet_SOURCES = fuzz_process_packet.c
fuzz_process_packet_CFLAGS =
fuzz_process_packet_LDADD = ../src/lib/libndpi.a
-fuzz_process_packet_LDFLAGS = $(ADDITIONAL_LIBS)
+fuzz_process_packet_LDFLAGS = $(ADDITIONAL_LIBS) $(LIBS)
if HAS_FUZZLDFLAGS
fuzz_process_packet_CFLAGS += $(LIB_FUZZING_ENGINE)
fuzz_process_packet_LDFLAGS += $(LIB_FUZZING_ENGINE)
@@ -16,7 +16,7 @@ fuzz_process_packet_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
fuzz_ndpi_reader_SOURCES = fuzz_ndpi_reader.c
fuzz_ndpi_reader_CFLAGS = -I../example/
fuzz_ndpi_reader_LDADD = ../src/lib/libndpi.a
-fuzz_ndpi_reader_LDFLAGS = ../example/libndpiReader.a $(PCAP_LIB) $(ADDITIONAL_LIBS)
+fuzz_ndpi_reader_LDFLAGS = ../example/libndpiReader.a $(PCAP_LIB) $(ADDITIONAL_LIBS) $(LIBS)
if HAS_FUZZLDFLAGS
fuzz_ndpi_reader_CFLAGS += $(LIB_FUZZING_ENGINE)
fuzz_ndpi_reader_LDFLAGS += $(LIB_FUZZING_ENGINE)
@@ -29,7 +29,7 @@ fuzz_ndpi_reader_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
fuzz_ndpi_reader_with_main_SOURCES = fuzz_ndpi_reader.c
fuzz_ndpi_reader_with_main_CFLAGS = -I../example/ -DBUILD_MAIN
fuzz_ndpi_reader_with_main_LDADD = ../src/lib/libndpi.a
-fuzz_ndpi_reader_with_main_LDFLAGS = ../example/libndpiReader.a $(PCAP_LIB) $(ADDITIONAL_LIBS)
+fuzz_ndpi_reader_with_main_LDFLAGS = ../example/libndpiReader.a $(PCAP_LIB) $(ADDITIONAL_LIBS) $(LIBS)
# force usage of CXX for linker
fuzz_ndpi_reader_with_main_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CXX) $(AM_CXXFLAGS) $(CXXFLAGS) \
diff --git a/src/include/ndpi_api.h.in b/src/include/ndpi_api.h.in
index e5d2ffad3..de3f90885 100644
--- a/src/include/ndpi_api.h.in
+++ b/src/include/ndpi_api.h.in
@@ -182,6 +182,10 @@ extern "C" {
* hosts and do other things. As soon as you are ready to use
* it do not forget to call first ndpi_finalize_initalization()
*
+ * You can call this function multiple times, (i.e. to create multiple
+ * indipendent detection contexts) but all these calls MUST NOT run
+ * in parallel
+ *
* @par prefs = load preferences
* @return the initialized detection module
*
@@ -879,6 +883,7 @@ extern "C" {
//void * ndpi_calloc(unsigned long count, size_t size);
//void ndpi_free(void *ptr);
u_int16_t ndpi_get_api_version(void);
+ const char *ndpi_get_gcrypt_version(void);
/* https://github.com/corelight/community-id-spec */
int ndpi_flowv4_flow_hash(u_int8_t l4_proto, u_int32_t src_ip, u_int32_t dst_ip, u_int16_t src_port, u_int16_t dst_port,
@@ -930,6 +935,10 @@ extern "C" {
int ndpi_ptree_match_addr(ndpi_ptree_t *tree, const ndpi_ip_addr_t *addr, u_int32_t *user_data);
void ndpi_ptree_destroy(ndpi_ptree_t *tree);
+ /* General purpose utilities */
+ u_int64_t ndpi_htonll(u_int64_t v);
+ u_int64_t ndpi_ntohll(u_int64_t v);
+
/* DGA */
int ndpi_check_dga_name(struct ndpi_detection_module_struct *ndpi_str,
struct ndpi_flow_struct *flow,
diff --git a/src/include/ndpi_typedefs.h b/src/include/ndpi_typedefs.h
index d585ccd23..b4d9b3dd5 100644
--- a/src/include/ndpi_typedefs.h
+++ b/src/include/ndpi_typedefs.h
@@ -1412,6 +1412,7 @@ typedef enum
{
ndpi_no_prefs = 0,
ndpi_dont_load_tor_hosts,
+ ndpi_dont_init_libgcrypt,
} ndpi_prefs;
typedef struct {
diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c
index a2b9b7d42..4c9d5d37c 100644
--- a/src/lib/ndpi_main.c
+++ b/src/lib/ndpi_main.c
@@ -32,6 +32,10 @@
#include "ahocorasick.h"
#include "libcache.h"
+#ifdef HAVE_LIBGCRYPT
+#include <gcrypt.h>
+#endif
+
#include <time.h>
#ifndef WIN32
#include <unistd.h>
@@ -1979,6 +1983,24 @@ struct ndpi_detection_module_struct *ndpi_init_detection_module(ndpi_init_prefs
NDPI_BITMASK_RESET(ndpi_str->debug_bitmask);
#endif /* NDPI_ENABLE_DEBUG_MESSAGES */
+#ifdef HAVE_LIBGCRYPT
+ if(!(prefs & ndpi_dont_init_libgcrypt)) {
+ if(!gcry_control (GCRYCTL_INITIALIZATION_FINISHED_P)) {
+ const char *gcrypt_ver = gcry_check_version(NULL);
+ if (!gcrypt_ver) {
+ NDPI_LOG_ERR(ndpi_str, "Error initializing libgcrypt\n");
+ ndpi_free(ndpi_str);
+ return NULL;
+ }
+ NDPI_LOG_DBG(ndpi_str, "Libgcrypt %s\n", gcrypt_ver);
+ /* Tell Libgcrypt that initialization has completed. */
+ gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+ }
+ } else {
+ NDPI_LOG_DBG(ndpi_str, "Libgcrypt initialization skipped\n");
+ }
+#endif
+
if((ndpi_str->protocols_ptree = ndpi_New_Patricia(32 /* IPv4 */)) != NULL)
ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, host_protocol_list, prefs & ndpi_dont_load_tor_hosts);
@@ -6211,7 +6233,8 @@ void ndpi_free_flow(struct ndpi_flow_struct *flow) {
if(flow->kerberos_buf.pktbuf)
ndpi_free(flow->kerberos_buf.pktbuf);
- if(flow_is_proto(flow, NDPI_PROTOCOL_TLS)) {
+ if(flow_is_proto(flow, NDPI_PROTOCOL_TLS) ||
+ flow_is_proto(flow, NDPI_PROTOCOL_QUIC)) {
if(flow->protos.stun_ssl.ssl.server_names)
ndpi_free(flow->protos.stun_ssl.ssl.server_names);
@@ -6311,6 +6334,13 @@ u_int16_t ndpi_get_api_version() {
return(NDPI_API_VERSION);
}
+const char *ndpi_get_gcrypt_version(void) {
+#ifdef HAVE_LIBGCRYPT
+ return gcry_check_version(NULL);
+#endif
+ return NULL;
+}
+
ndpi_proto_defaults_t *ndpi_get_proto_defaults(struct ndpi_detection_module_struct *ndpi_str) {
return(ndpi_str->proto_defaults);
}
diff --git a/src/lib/ndpi_serializer.c b/src/lib/ndpi_serializer.c
index 87b2c06a8..83365e58c 100644
--- a/src/lib/ndpi_serializer.c
+++ b/src/lib/ndpi_serializer.c
@@ -40,7 +40,7 @@
/* ********************************** */
-static u_int64_t ndpi_htonll(u_int64_t v) {
+u_int64_t ndpi_htonll(u_int64_t v) {
union { u_int32_t lv[2]; u_int64_t llv; } u;
u.lv[0] = htonl(v >> 32);
@@ -51,7 +51,7 @@ static u_int64_t ndpi_htonll(u_int64_t v) {
/* ********************************** */
-static u_int64_t ndpi_ntohll(u_int64_t v) {
+u_int64_t ndpi_ntohll(u_int64_t v) {
union { u_int32_t lv[2]; u_int64_t llv; } u;
u.llv = v;
diff --git a/src/lib/protocols/quic.c b/src/lib/protocols/quic.c
index 6beac5443..f3d46e89c 100644
--- a/src/lib/protocols/quic.c
+++ b/src/lib/protocols/quic.c
@@ -27,142 +27,1193 @@
#endif
#include "ndpi_protocol_ids.h"
-
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_QUIC
-
#include "ndpi_api.h"
-static int quic_ports(u_int16_t sport, u_int16_t dport)
-{
- if ((sport == 443 || dport == 443 || sport == 80 || dport == 80) &&
- (sport != 123 && dport != 123))
- return 1;
+#ifdef HAVE_LIBGCRYPT
+#include <gcrypt.h>
+#endif
+
+// #define DEBUG_CRYPT
+/* This dissector handles GQUIC and IETF-QUIC both.
+ Main references:
+ * https://groups.google.com/a/chromium.org/g/proto-quic/c/wVHBir-uRU0?pli=1
+ * https://groups.google.com/a/chromium.org/g/proto-quic/c/OAVgFqw2fko/m/jCbjP0AVAAAJ
+ * https://groups.google.com/a/chromium.org/g/proto-quic/c/OAVgFqw2fko/m/-NYxlh88AgAJ
+ * https://docs.google.com/document/d/1FcpCJGTDEMblAs-Bm5TYuqhHyUqeWpqrItw2vkMFsdY/edit
+ * https://tools.ietf.org/html/draft-ietf-quic-tls-29
+ * https://tools.ietf.org/html/draft-ietf-quic-transport-29
+*/
+
+extern int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow, int is_quic);
+
+/* Versions */
+#define V_Q024 0x51303234
+#define V_Q025 0x51303235
+#define V_Q030 0x51303330
+#define V_Q033 0x51303333
+#define V_Q034 0x51303334
+#define V_Q035 0x51303335
+#define V_Q037 0x51303337
+#define V_Q039 0x51303339
+#define V_Q043 0x51303433
+#define V_Q046 0x51303436
+#define V_Q050 0x51303530
+#define V_MVFST_22 0xfaceb001
+#define V_MVFST_27 0xfaceb002
+
+#define QUIC_MAX_CID_LENGTH 20
+
+static int is_version_gquic(uint32_t version)
+{
+ return ((version & 0xFFFFFF00) == 0x51303500) /* Q05X */ ||
+ ((version & 0xFFFFFF00) == 0x51303400) /* Q04X */ ||
+ ((version & 0xFFFFFF00) == 0x51303300) /* Q03X */ ||
+ ((version & 0xFFFFFF00) == 0x51303200) /* Q02X */;
+}
+static int is_version_quic(uint32_t version)
+{
+ return ((version & 0xFFFFFF00) == 0xFF000000) /* IETF */ ||
+ ((version & 0xFFFFF000) == 0xfaceb000) /* Facebook */;
+}
+static int is_version_valid(uint32_t version)
+{
+ return is_version_gquic(version) || is_version_quic(version);
+}
+static uint8_t get_u8_quic_ver(uint32_t version)
+{
+ if((version >> 8) == 0xff0000)
+ return (uint8_t)version;
return 0;
}
+#ifdef HAVE_LIBGCRYPT
+static int is_quic_ver_less_than(uint32_t version, uint8_t max_version)
+{
+ uint8_t u8_ver = get_u8_quic_ver(version);
+ return u8_ver && u8_ver <= max_version;
+}
+#endif
+static int is_quic_ver_greater_than(uint32_t version, uint8_t min_version)
+{
+ return get_u8_quic_ver(version) >= min_version;
+}
+static uint8_t get_u8_gquic_ver(uint32_t version)
+{
+ if(is_version_gquic(version)) {
+ version = ntohl(((uint16_t)version) << 16);
+ return atoi((char *)&version);
+ }
+ return 0;
+}
+static int is_gquic_ver_less_than(uint32_t version, uint8_t max_version)
+{
+ uint8_t u8_ver = get_u8_gquic_ver(version);
+ return u8_ver && u8_ver <= max_version;
+}
+static int is_version_supported(uint32_t version)
+{
+ return (version == V_Q024 ||
+ version == V_Q025 ||
+ version == V_Q030 ||
+ version == V_Q033 ||
+ version == V_Q034 ||
+ version == V_Q035 ||
+ version == V_Q037 ||
+ version == V_Q039 ||
+ version == V_Q043 ||
+ version == V_Q046 ||
+ version == V_Q050 ||
+ version == V_MVFST_22 ||
+ version == V_MVFST_27 ||
+ is_quic_ver_greater_than(version, 23));
+}
+static int is_version_with_encrypted_header(uint32_t version)
+{
+ return is_version_quic(version) ||
+ ((version & 0xFFFFFF00) == 0x51303500) /* Q05X */;
+}
-/* ***************************************************************** */
-
-static int quic_len(u_int8_t l) {
- switch(l) {
+static int quic_len(const uint8_t *buf, uint64_t *value)
+{
+ *value = buf[0];
+ switch((*value) >> 6) {
case 0:
- return(1);
- break;
+ (*value) &= 0x3F;
+ return 1;
case 1:
- return(2);
- break;
+ *value = ntohs(*(uint16_t *)buf) & 0x3FFF;
+ return 2;
case 2:
- return(4);
- break;
+ *value = ntohl(*(uint32_t *)buf) & 0x3FFFFFFF;
+ return 4;
case 3:
- return(8);
+ *value = ndpi_ntohll(*(uint64_t *)buf) & 0x3FFFFFFFFFFFFFFF;
+ return 8;
+ default: /* No Possible */
+ return 0;
+ }
+}
+
+static uint16_t gquic_get_u16(const uint8_t *buf, uint32_t version)
+{
+ if(version >= V_Q039)
+ return ntohs(*(uint16_t *)buf);
+ return (*(uint16_t *)buf);
+}
+
+
+#ifdef HAVE_LIBGCRYPT
+
+#ifdef DEBUG_CRYPT
+char *__gcry_err(gpg_error_t err, char *buf, size_t buflen)
+{
+#ifdef HAVE_LIBGPG_ERROR
+ gpg_strerror_r(err, buf, buflen);
+ /* I am not sure if the string will be always null-terminated...
+ Better safe than sorry */
+ if(buflen > 0)
+ buf[buflen - 1] = '\0';
+#else
+ if(buflen > 0)
+ buf[0] = '\0';
+#endif
+ return buf;
+}
+#endif /* DEBUG_CRYPT */
+
+static uint64_t pntoh64(const void *p)
+{
+ return (uint64_t)*((const uint8_t *)(p)+0)<<56|
+ (uint64_t)*((const uint8_t *)(p)+1)<<48|
+ (uint64_t)*((const uint8_t *)(p)+2)<<40|
+ (uint64_t)*((const uint8_t *)(p)+3)<<32|
+ (uint64_t)*((const uint8_t *)(p)+4)<<24|
+ (uint64_t)*((const uint8_t *)(p)+5)<<16|
+ (uint64_t)*((const uint8_t *)(p)+6)<<8|
+ (uint64_t)*((const uint8_t *)(p)+7)<<0;
+}
+static void phton64(uint8_t *p, uint64_t v)
+{
+ p[0] = (uint8_t)(v >> 56);
+ p[1] = (uint8_t)(v >> 48);
+ p[2] = (uint8_t)(v >> 40);
+ p[3] = (uint8_t)(v >> 32);
+ p[4] = (uint8_t)(v >> 24);
+ p[5] = (uint8_t)(v >> 16);
+ p[6] = (uint8_t)(v >> 8);
+ p[7] = (uint8_t)(v >> 0);
+}
+
+static void *memdup(const uint8_t *orig, size_t len)
+{
+ void *dest = ndpi_malloc(len);
+ if(dest)
+ memcpy(dest, orig, len);
+ return dest;
+}
+
+
+/*
+ * Generic Wireshark definitions
+ */
+
+#define HASH_SHA2_256_LENGTH 32
+#define TLS13_AEAD_NONCE_LENGTH 12
+
+typedef struct _StringInfo {
+ unsigned char *data; /* Backing storage which may be larger than data_len */
+ unsigned int data_len; /* Length of the meaningful part of data */
+} StringInfo;
+
+/* QUIC decryption context. */
+typedef struct quic_cipher {
+ gcry_cipher_hd_t hp_cipher; /* Header protection cipher. */
+ gcry_cipher_hd_t pp_cipher; /* Packet protection cipher. */
+ uint8_t pp_iv[TLS13_AEAD_NONCE_LENGTH];
+} quic_cipher;
+
+typedef struct quic_decrypt_result {
+ uint8_t *data; /* Decrypted result on success (file-scoped). */
+ uint32_t data_len; /* Size of decrypted data. */
+} quic_decrypt_result_t;
+
+
+/*
+ * From wsutil/wsgcrypt.{c,h}
+ */
+
+static gcry_error_t ws_hmac_buffer(int algo, void *digest, const void *buffer,
+ size_t length, const void *key, size_t keylen)
+{
+ gcry_md_hd_t hmac_handle;
+ gcry_error_t result = gcry_md_open(&hmac_handle, algo, GCRY_MD_FLAG_HMAC);
+ if(result) {
+ return result;
+ }
+ result = gcry_md_setkey(hmac_handle, key, keylen);
+ if(result) {
+ gcry_md_close(hmac_handle);
+ return result;
+ }
+ gcry_md_write(hmac_handle, buffer, length);
+ memcpy(digest, gcry_md_read(hmac_handle, 0), gcry_md_get_algo_dlen(algo));
+ gcry_md_close(hmac_handle);
+ return GPG_ERR_NO_ERROR;
+}
+static gcry_error_t hkdf_expand(int hashalgo, const uint8_t *prk, uint32_t prk_len,
+ const uint8_t *info, uint32_t info_len,
+ uint8_t *out, uint32_t out_len)
+{
+ /* Current maximum hash output size: 48 bytes for SHA-384. */
+ uint8_t lastoutput[48];
+ gcry_md_hd_t h;
+ gcry_error_t err;
+ const unsigned int hash_len = gcry_md_get_algo_dlen(hashalgo);
+
+ /* Some sanity checks */
+ if(!(out_len > 0 && out_len <= 255 * hash_len) ||
+ !(hash_len > 0 && hash_len <= sizeof(lastoutput))) {
+ return GPG_ERR_INV_ARG;
+ }
+
+ err = gcry_md_open(&h, hashalgo, GCRY_MD_FLAG_HMAC);
+ if(err) {
+ return err;
+ }
+
+ for(uint32_t offset = 0; offset < out_len; offset += hash_len) {
+ gcry_md_reset(h);
+ gcry_md_setkey(h, prk, prk_len); /* Set PRK */
+ if(offset > 0) {
+ gcry_md_write(h, lastoutput, hash_len); /* T(1..N) */
+ }
+ gcry_md_write(h, info, info_len); /* info */
+ gcry_md_putc(h, (uint8_t) (offset / hash_len + 1)); /* constant 0x01..N */
+
+ memcpy(lastoutput, gcry_md_read(h, hashalgo), hash_len);
+ memcpy(out + offset, lastoutput, MIN(hash_len, out_len - offset));
+ }
+
+ gcry_md_close(h);
+ return 0;
+}
+/*
+ * Calculate HKDF-Extract(salt, IKM) -> PRK according to RFC 5869.
+ * Caller MUST ensure that 'prk' is large enough to store the digest from hash
+ * algorithm 'hashalgo' (e.g. 32 bytes for SHA-256).
+ */
+static gcry_error_t hkdf_extract(int hashalgo, const uint8_t *salt, size_t salt_len,
+ const uint8_t *ikm, size_t ikm_len, uint8_t *prk)
+{
+ /* PRK = HMAC-Hash(salt, IKM) where salt is key, and IKM is input. */
+ return ws_hmac_buffer(hashalgo, prk, ikm, ikm_len, salt, salt_len);
+}
+
+
+/*
+ * From epan/dissectors/packet-tls-utils.c
+ */
+
+/*
+ * Computes HKDF-Expand-Label(Secret, Label, Hash(context_value), Length) with a
+ * custom label prefix. If "context_hash" is NULL, then an empty context is
+ * used. Otherwise it must have the same length as the hash algorithm output.
+ */
+static int tls13_hkdf_expand_label_context(int md, const StringInfo *secret,
+ const char *label_prefix, const char *label,
+ const uint8_t *context_hash, uint8_t context_length,
+ uint16_t out_len, uint8_t **out)
+{
+ /* RFC 8446 Section 7.1:
+ * HKDF-Expand-Label(Secret, Label, Context, Length) =
+ * HKDF-Expand(Secret, HkdfLabel, Length)
+ * struct {
+ * uint16 length = Length;
+ * opaque label<7..255> = "tls13 " + Label; // "tls13 " is label prefix.
+ * opaque context<0..255> = Context;
+ * } HkdfLabel;
+ *
+ * RFC 5869 HMAC-based Extract-and-Expand Key Derivation Function (HKDF):
+ * HKDF-Expand(PRK, info, L) -> OKM
+ */
+ gcry_error_t err;
+ const unsigned int label_prefix_length = (unsigned int)strlen(label_prefix);
+ const unsigned label_length = (unsigned int)strlen(label);
+#ifdef DEBUG_CRYPT
+ char buferr[128];
+#endif
+
+ /* Some sanity checks */
+ if(!(label_length > 0 && label_prefix_length + label_length <= 255)) {
+#ifdef DEBUG_CRYPT
+ printf("Failed sanity checks\n");
+#endif
+ return 0;
+ }
+
+ /* info = HkdfLabel { length, label, context } */
+ /* Keep original Wireshark code as reference */
+#if 0
+ GByteArray *info = g_byte_array_new();
+ const uint16_t length = htons(out_len);
+ g_byte_array_append(info, (const guint8 *)&length, sizeof(length));
+
+ const uint8_t label_vector_length = label_prefix_length + label_length;
+ g_byte_array_append(info, &label_vector_length, 1);
+ g_byte_array_append(info, (const uint8_t *)label_prefix, label_prefix_length);
+ g_byte_array_append(info, (const uint8_t *)label, label_length);
+
+ g_byte_array_append(info, &context_length, 1);
+ if (context_length) {
+ g_byte_array_append(info, context_hash, context_length);
+ }
+#else
+ uint32_t info_len = 0;
+ uint8_t *info_data = (uint8_t *)ndpi_malloc(1024);
+ if(!info_data)
+ return 0;
+ const uint16_t length = htons(out_len);
+ memcpy(&info_data[info_len], &length, sizeof(length));
+ info_len += sizeof(length);
+
+ const uint8_t label_vector_length = label_prefix_length + label_length;
+ memcpy(&info_data[info_len], &label_vector_length, 1);
+ info_len += 1;
+ memcpy(&info_data[info_len], (const uint8_t *)label_prefix, label_prefix_length);
+ info_len += label_prefix_length;
+ memcpy(&info_data[info_len], (const uint8_t *)label, label_length);
+ info_len += label_length;
+
+ memcpy(&info_data[info_len], &context_length, 1);
+ info_len += 1;
+ if(context_length) {
+ memcpy(&info_data[info_len], context_hash, context_length);
+ info_len += context_length;
+ }
+#endif
+
+ *out = (uint8_t *)ndpi_malloc(out_len);
+ if(!*out)
+ return 0;
+ err = hkdf_expand(md, secret->data, secret->data_len, info_data, info_len, *out, out_len);
+ ndpi_free(info_data);
+
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Failed hkdf_expand: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(*out);
+ *out = NULL;
+ return 0;
+ }
+
+ return 1;
+}
+static int tls13_hkdf_expand_label(int md, const StringInfo *secret,
+ const char *label_prefix, const char *label,
+ uint16_t out_len, unsigned char **out)
+{
+ return tls13_hkdf_expand_label_context(md, secret, label_prefix, label, NULL, 0, out_len, out);
+}
+
+
+/*
+ * From epan/dissectors/packet-quic.c
+ */
+
+static int quic_hkdf_expand_label(int hash_algo, uint8_t *secret, uint32_t secret_len,
+ const char *label, uint8_t *out, uint32_t out_len)
+{
+ const StringInfo secret_si = { secret, secret_len };
+ uint8_t *out_mem = NULL;
+ if(tls13_hkdf_expand_label(hash_algo, &secret_si, "tls13 ", label, out_len, &out_mem)) {
+ memcpy(out, out_mem, out_len);
+ ndpi_free(out_mem);
+ return 1;
+ }
+ return 0;
+}
+static void quic_cipher_reset(quic_cipher *cipher)
+{
+ gcry_cipher_close(cipher->hp_cipher);
+ gcry_cipher_close(cipher->pp_cipher);
+#if 0
+ memset(cipher, 0, sizeof(*cipher));
+#endif
+}
+/**
+ * Expands the secret (length MUST be the same as the "hash_algo" digest size)
+ * and initialize cipher with the new key.
+ */
+static int quic_cipher_init(quic_cipher *cipher, int hash_algo,
+ uint8_t key_length, uint8_t *secret)
+{
+ uint8_t write_key[256/8]; /* Maximum key size is for AES256 cipher. */
+ uint8_t hp_key[256/8];
+ uint32_t hash_len = gcry_md_get_algo_dlen(hash_algo);
+
+ if(key_length > sizeof(write_key)) {
+ return 0;
+ }
+
+ if(!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic key", write_key, key_length) ||
+ !quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic iv", cipher->pp_iv, sizeof(cipher->pp_iv)) ||
+ !quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic hp", hp_key, key_length)) {
+ return 1;
+ }
+
+ return gcry_cipher_setkey(cipher->hp_cipher, hp_key, key_length) == 0 &&
+ gcry_cipher_setkey(cipher->pp_cipher, write_key, key_length) == 0;
+}
+/**
+ * Maps a Packet Protection cipher to the Packet Number protection cipher.
+ * See https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.3
+ */
+static int quic_get_pn_cipher_algo(int cipher_algo, int *hp_cipher_mode)
+{
+ switch (cipher_algo) {
+ case GCRY_CIPHER_AES128:
+ case GCRY_CIPHER_AES256:
+ *hp_cipher_mode = GCRY_CIPHER_MODE_ECB;
+ return 1;
+ default:
+ return 0;
+ }
+}
+/*
+ * (Re)initialize the PNE/PP ciphers using the given cipher algorithm.
+ * If the optional base secret is given, then its length MUST match the hash
+ * algorithm output.
+ */
+static int quic_cipher_prepare(quic_cipher *cipher, int hash_algo, int cipher_algo,
+ int cipher_mode, uint8_t *secret)
+{
+#if 0
+ /* Clear previous state (if any). */
+ quic_cipher_reset(cipher);
+#endif
+
+ int hp_cipher_mode;
+ if(!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) {
+#ifdef DEBUG_CRYPT
+ printf("Unsupported cipher algorithm\n");
+#endif
+ return 0;
+ }
+
+ if(gcry_cipher_open(&cipher->hp_cipher, cipher_algo, hp_cipher_mode, 0) ||
+ gcry_cipher_open(&cipher->pp_cipher, cipher_algo, cipher_mode, 0)) {
+ quic_cipher_reset(cipher);
+#ifdef DEBUG_CRYPT
+ printf("Failed to create ciphers\n");
+#endif
+ return 0;
+ }
+
+ if(secret) {
+ uint32_t cipher_keylen = (uint8_t)gcry_cipher_get_algo_keylen(cipher_algo);
+ if(!quic_cipher_init(cipher, hash_algo, cipher_keylen, secret)) {
+ quic_cipher_reset(cipher);
+#ifdef DEBUG_CRYPT
+ printf("Failed to derive key material for cipher\n");
+#endif
+ return 0;
+ }
+ }
+
+ return 1;
+}
+/**
+ * Given a header protection cipher, a buffer and the packet number offset,
+ * return the unmasked first byte and packet number.
+ */
+static int quic_decrypt_header(const uint8_t *packet_payload,
+ uint32_t pn_offset, gcry_cipher_hd_t hp_cipher,
+ int hp_cipher_algo, uint8_t *first_byte, uint32_t *pn)
+{
+ gcry_cipher_hd_t h = hp_cipher;
+ if(!hp_cipher) {
+ /* Need to know the cipher */
+ return 0;
+ }
+
+ /* Sample is always 16 bytes and starts after PKN (assuming length 4).
+ https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.2 */
+ uint8_t sample[16];
+ memcpy(sample, packet_payload + pn_offset + 4, 16);
+
+ uint8_t mask[5] = { 0 };
+ switch (hp_cipher_algo) {
+ case GCRY_CIPHER_AES128:
+ case GCRY_CIPHER_AES256:
+ /* Encrypt in-place with AES-ECB and extract the mask. */
+ if(gcry_cipher_encrypt(h, sample, sizeof(sample), NULL, 0)) {
+ return 0;
+ }
+ memcpy(mask, sample, sizeof(mask));
break;
+ default:
+ return 0;
+ }
+
+ /* https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.1 */
+ uint8_t packet0 = packet_payload[0];
+ if((packet0 & 0x80) == 0x80) {
+ /* Long header: 4 bits masked */
+ packet0 ^= mask[0] & 0x0f;
+ } else {
+ /* Short header: 5 bits masked */
+ packet0 ^= mask[0] & 0x1f;
}
+ uint32_t pkn_len = (packet0 & 0x03) + 1;
+ /* printf("packet0 0x%x pkn_len %d\n", packet0, pkn_len); */
- return(0); /* NOTREACHED */
+ uint8_t pkn_bytes[4];
+ memcpy(pkn_bytes, packet_payload + pn_offset, pkn_len);
+ uint32_t pkt_pkn = 0;
+ for(uint32_t i = 0; i < pkn_len; i++) {
+ pkt_pkn |= (uint32_t)(pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i));
+ }
+ *first_byte = packet0;
+ *pn = pkt_pkn;
+ return 1;
}
+/**
+ * Given a QUIC message (header + non-empty payload), the actual packet number,
+ * try to decrypt it using the cipher.
+ * As the header points to the original buffer with an encrypted packet number,
+ * the (encrypted) packet number length is also included.
+ *
+ * The actual packet number must be constructed according to
+ * https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-12.3
+ */
+static void quic_decrypt_message(quic_cipher *cipher, const uint8_t *packet_payload, uint32_t packet_payload_len,
+ uint32_t header_length, uint8_t first_byte, uint32_t pkn_len,
+ uint64_t packet_number, quic_decrypt_result_t *result)
+{
+ gcry_error_t err;
+ uint8_t *header;
+ uint8_t nonce[TLS13_AEAD_NONCE_LENGTH];
+ uint8_t *buffer;
+ uint8_t atag[16];
+ uint32_t buffer_length;
+#ifdef DEBUG_CRYPT
+ char buferr[128];
+#endif
-/* ***************************************************************** */
+ if(!(cipher != NULL) ||
+ !(cipher->pp_cipher != NULL) ||
+ !(pkn_len < header_length) ||
+ !(1 <= pkn_len && pkn_len <= 4)) {
+#ifdef DEBUG_CRYPT
+ printf("Failed sanity checks\n");
+#endif
+ return;
+ }
+ /* Copy header, but replace encrypted first byte and PKN by plaintext. */
+ header = (uint8_t *)memdup(packet_payload, header_length);
+ if(!header)
+ return;
+ header[0] = first_byte;
+ for(uint32_t i = 0; i < pkn_len; i++) {
+ header[header_length - 1 - i] = (uint8_t)(packet_number >> (8 * i));
+ }
-void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
- struct ndpi_flow_struct *flow) {
+ /* Input is "header || ciphertext (buffer) || auth tag (16 bytes)" */
+ buffer_length = packet_payload_len - (header_length + 16);
+ if(buffer_length == 0) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption not possible, ciphertext is too short\n");
+#endif
+ ndpi_free(header);
+ return;
+ }
+ buffer = (uint8_t *)memdup(packet_payload + header_length, buffer_length);
+ if(!buffer) {
+ ndpi_free(header);
+ return;
+ }
+ memcpy(atag, packet_payload + header_length + buffer_length, 16);
+
+ memcpy(nonce, cipher->pp_iv, TLS13_AEAD_NONCE_LENGTH);
+ /* Packet number is left-padded with zeroes and XORed with write_iv */
+ phton64(nonce + sizeof(nonce) - 8, pntoh64(nonce + sizeof(nonce) - 8) ^ packet_number);
+
+ gcry_cipher_reset(cipher->pp_cipher);
+ err = gcry_cipher_setiv(cipher->pp_cipher, nonce, TLS13_AEAD_NONCE_LENGTH);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (setiv) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(header);
+ ndpi_free(buffer);
+ return;
+ }
+
+ /* associated data (A) is the contents of QUIC header */
+ err = gcry_cipher_authenticate(cipher->pp_cipher, header, header_length);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (authenticate) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(header);
+ ndpi_free(buffer);
+ return;
+ }
+
+ ndpi_free(header);
+
+ /* Output ciphertext (C) */
+ err = gcry_cipher_decrypt(cipher->pp_cipher, buffer, buffer_length, NULL, 0);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (decrypt) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(buffer);
+ return;
+ }
+
+ err = gcry_cipher_checktag(cipher->pp_cipher, atag, 16);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (checktag) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(buffer);
+ return;
+ }
+
+ result->data = buffer;
+ result->data_len = buffer_length;
+}
+/**
+ * Compute the client and server initial secrets given Connection ID "cid".
+ */
+static int quic_derive_initial_secrets(uint32_t version,
+ const uint8_t *cid, uint8_t cid_len,
+ uint8_t client_initial_secret[HASH_SHA2_256_LENGTH])
+{
+ /*
+ * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2
+ *
+ * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id)
+ *
+ * client_initial_secret = HKDF-Expand-Label(initial_secret,
+ * "client in", "", Hash.length)
+ *
+ * Hash for handshake packets is SHA-256 (output size 32).
+ */
+ static const uint8_t handshake_salt_draft_22[20] = {
+ 0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a,
+ 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a
+ };
+ static const uint8_t handshake_salt_draft_23[20] = {
+ 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7,
+ 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02,
+ };
+ static const uint8_t handshake_salt_draft_29[20] = {
+ 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97,
+ 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99
+ };
+ static const uint8_t hanshake_salt_draft_q50[20] = {
+ 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94,
+ 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45
+ };
+
+ gcry_error_t err;
+ uint8_t secret[HASH_SHA2_256_LENGTH];
+#ifdef DEBUG_CRYPT
+ char buferr[128];
+#endif
+
+ if(version == V_Q050) {
+ err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_q50,
+ sizeof(hanshake_salt_draft_q50),
+ cid, cid_len, secret);
+ } else if(is_quic_ver_less_than(version, 22) ||
+ version == V_MVFST_22) {
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_22,
+ sizeof(handshake_salt_draft_22),
+ cid, cid_len, secret);
+ } else if(is_quic_ver_less_than(version, 28) ||
+ version == V_MVFST_27) {
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_23,
+ sizeof(handshake_salt_draft_23),
+ cid, cid_len, secret);
+ } else {
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29,
+ sizeof(handshake_salt_draft_29),
+ cid, cid_len, secret);
+ }
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Failed to extract secrets: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ return -1;
+ }
+
+ if(!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "client in",
+ client_initial_secret, HASH_SHA2_256_LENGTH)) {
+#ifdef DEBUG_CRYPT
+ printf("Key expansion (client) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * End Wireshark code
+ */
+
+
+static uint8_t *decrypt_initial_packet(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ const uint8_t *dest_conn_id, uint8_t dest_conn_id_len,
+ uint8_t source_conn_id_len, uint32_t version,
+ uint32_t *clear_payload_len)
+{
+ uint64_t token_length, payload_length, packet_number;
struct ndpi_packet_struct *packet = &flow->packet;
- u_int32_t udp_len = packet->payload_packet_len;
- u_int version_len = ((packet->payload[0] & 0x01) == 0) ? 0 : 4;
- u_int cid_len = quic_len((packet->payload[0] & 0x0C) >> 2);
- u_int seq_len = quic_len((packet->payload[0] & 0x30) >> 4);
- u_int quic_hlen = 1 /* flags */ + version_len + seq_len + cid_len;
-
- NDPI_LOG_DBG(ndpi_struct, "search QUIC\n");
-
- if(packet->udp != NULL
- && (udp_len > (quic_hlen+4 /* QXXX */))
- // && ((packet->payload[0] & 0xC2) == 0x00)
- && (quic_ports(ntohs(packet->udp->source), ntohs(packet->udp->dest)))
- ) {
- int i;
-
- if((packet->payload[1] == 'Q')
- && (packet->payload[2] == '0')
- && (packet->payload[3] == '4')
- && (packet->payload[4] == '6')) {
-
- /*
- TODO: Better handle Q046
- https://tools.ietf.org/html/draft-ietf-quic-invariants-04
- */
- quic_hlen = 18;
- } else {
- u_int16_t potential_stun_len = ntohs((*((u_int16_t*)&packet->payload[2])));
-
- if((version_len > 0) && (packet->payload[1+cid_len] != 'Q'))
- goto no_quic;
-
- if((version_len == 0) && ((packet->payload[0] & 0xC3 /* ignore CID len/packet number */) != 0))
- goto no_quic;
-
-
- /* Heuristic to see if this packet could be a STUN packet */
- if((potential_stun_len /* STUN message len */ < udp_len)
- && ((potential_stun_len+25 /* Attribute header overhead we assume is max */) /* STUN message len */ > udp_len))
- return; /* This could be STUN, let's skip this packet */
-
- NDPI_LOG_INFO(ndpi_struct, "found QUIC\n");
- ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, NDPI_PROTOCOL_UNKNOWN);
-
- if((udp_len > quic_hlen + 12) && (packet->payload[quic_hlen+12] != 0xA0))
- quic_hlen++;
+ uint8_t first_byte;
+ uint32_t pkn32, pn_offset, pkn_len, offset;
+ quic_cipher cipher = {0}; /* Client initial cipher */
+ quic_decrypt_result_t decryption = {0};
+ uint8_t client_secret[HASH_SHA2_256_LENGTH];
+
+ if(quic_derive_initial_secrets(version, dest_conn_id, dest_conn_id_len,
+ client_secret) != 0) {
+ NDPI_LOG_DBG(ndpi_struct, "Error quic_derive_initial_secrets\n");
+ return NULL;
+ }
+
+ /* Packet numbers are protected with AES128-CTR,
+ Initial packets are protected with AEAD_AES_128_GCM. */
+ if(!quic_cipher_prepare(&cipher, GCRY_MD_SHA256,
+ GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM,
+ client_secret)) {
+ NDPI_LOG_DBG(ndpi_struct, "Error quic_cipher_prepare\n");
+ return NULL;
+ }
+
+ /* Type(1) + version(4) + DCIL + DCID + SCIL + SCID */
+ pn_offset = 1 + 4 + 1 + dest_conn_id_len + 1 + source_conn_id_len;
+ pn_offset += quic_len(&packet->payload[pn_offset], &token_length);
+ pn_offset += token_length;
+ /* Checks: quic_len reads 8 bytes, at most; quic_decrypt_header reads other 20 bytes */
+ if(pn_offset + 8 + (4 + 16) >= packet->payload_packet_len)
+ return NULL;
+ pn_offset += quic_len(&packet->payload[pn_offset], &payload_length);
+
+ NDPI_LOG_DBG2(ndpi_struct, "pn_offset %d token_length %d payload_length %d\n",
+ pn_offset, token_length, payload_length);
+
+ if(!quic_decrypt_header(&packet->payload[0], pn_offset, cipher.hp_cipher,
+ GCRY_CIPHER_AES128, &first_byte, &pkn32)) {
+ quic_cipher_reset(&cipher);
+ return NULL;
+ }
+ NDPI_LOG_DBG2(ndpi_struct, "first_byte 0x%x pkn32 0x%x\n", first_byte, pkn32);
+
+ pkn_len = (first_byte & 3) + 1;
+ /* TODO: is it always true in Initial Packets? */
+ packet_number = pkn32;
+
+ offset = pn_offset + pkn_len;
+ quic_decrypt_message(&cipher, &packet->payload[0], packet->payload_packet_len,
+ offset, first_byte, pkn_len, packet_number, &decryption);
+
+ quic_cipher_reset(&cipher);
+
+ if(decryption.data_len) {
+ *clear_payload_len = decryption.data_len;
+ return decryption.data;
+ }
+ return NULL;
+}
+
+#endif /* HAVE_LIBGCRYPT */
+
+
+static const uint8_t *get_crypto_data(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ uint32_t version,
+ u_int8_t *clear_payload, uint32_t clear_payload_len,
+ uint64_t *crypto_data_len)
+{
+ const u_int8_t *crypto_data;
+ uint32_t counter;
+ uint8_t first_nonzero_payload_byte, offset_len;
+ uint64_t unused;
+
+ counter = 0;
+ while(clear_payload[counter] == 0 && counter < clear_payload_len)
+ counter += 1;
+ if(counter >= clear_payload_len)
+ return NULL;
+ first_nonzero_payload_byte = clear_payload[counter];
+ NDPI_LOG_DBG2(ndpi_struct, "first_nonzero_payload_byte 0x%x\n", first_nonzero_payload_byte);
+ if(is_gquic_ver_less_than(version, 46)) {
+ if(first_nonzero_payload_byte == 0x40 ||
+ first_nonzero_payload_byte == 0x60) {
+ /* Probably an ACK/NACK frame: this CHLO is not the first one but try
+ decoding it nonetheless */
+ counter += (first_nonzero_payload_byte == 0x40) ? 6 : 9;
+ if(counter >= clear_payload_len)
+ return NULL;
+ first_nonzero_payload_byte = clear_payload[counter];
}
-
- if(udp_len > (quic_hlen + 16 + 4)) {
- if(!strncmp((char*)&packet->payload[quic_hlen+16], "CHLO" /* Client Hello */, 4)) {
- /* Check if SNI (Server Name Identification) is present */
- for(i=quic_hlen+12; i<udp_len-3; i++) {
- if((packet->payload[i] == 'S')
- && (packet->payload[i+1] == 'N')
- && (packet->payload[i+2] == 'I')
- && (packet->payload[i+3] == 0)) {
- u_int32_t offset = (*((u_int32_t*)&packet->payload[i+4]));
- u_int32_t prev_offset = (*((u_int32_t*)&packet->payload[i-4]));
-
- if(offset > prev_offset) {
- u_int32_t len = offset - prev_offset;
- u_int32_t sni_offset = i+prev_offset+1;
-
- if(len < udp_len) {
- while((sni_offset < udp_len) && (packet->payload[sni_offset] == '-'))
- sni_offset++;
-
- if((sni_offset+len) < udp_len) {
- u_int32_t max_len = sizeof(flow->host_server_name)-1, j = 0;
- ndpi_protocol_match_result ret_match;
-
- if(len > max_len) len = max_len;
-
- while((len > 0) && (sni_offset < udp_len)) {
- flow->host_server_name[j++] = packet->payload[sni_offset];
- sni_offset++, len--;
- }
-
- ndpi_match_host_subprotocol(ndpi_struct, flow,
- (char *)flow->host_server_name,
- strlen((const char*)flow->host_server_name),
- &ret_match,
- NDPI_PROTOCOL_QUIC);
- }
-
- break;
- }
- }
- }
- }
+ if((first_nonzero_payload_byte != 0xA0) &&
+ (first_nonzero_payload_byte != 0xA4)) {
+ NDPI_LOG_DBG(ndpi_struct, "Unexpected frame 0x%x version 0x%x\n",\
+ first_nonzero_payload_byte, version);
+ return NULL;
+ }
+ offset_len = (first_nonzero_payload_byte & 0x1C) >> 2;
+ if(offset_len > 0)
+ offset_len += 1;
+ if(counter + 2 + offset_len + 2 /*gquic_get_u16 reads 2 bytes */ > clear_payload_len)
+ return NULL;
+ if(clear_payload[counter + 1] != 0x01) {
+ NDPI_LOG_ERR(ndpi_struct, "Unexpected stream ID version 0x%x\n", version);
+ return NULL;
+ }
+ counter += 2 + offset_len;
+ *crypto_data_len = gquic_get_u16(&clear_payload[counter], version);
+ counter += 2;
+ crypto_data = &clear_payload[counter];
+
+ } else if(version == V_Q050) {
+ if(first_nonzero_payload_byte == 0x40 ||
+ first_nonzero_payload_byte == 0x60) {
+ /* Probably an ACK/NACK frame: this CHLO is not the first one but try
+ decoding it nonetheless */
+ counter += (first_nonzero_payload_byte == 0x40) ? 6 : 9;
+ if(counter >= clear_payload_len)
+ return NULL;
+ first_nonzero_payload_byte = clear_payload[counter];
+ }
+ if(first_nonzero_payload_byte != 0x08) {
+ NDPI_LOG_DBG(ndpi_struct, "Unexpected frame 0x%x\n", first_nonzero_payload_byte);
+ return NULL;
+ }
+ counter += 1;
+ if(counter + 8 + 8 >= clear_payload_len) /* quic_len reads 8 bytes, at most */
+ return NULL;
+ counter += quic_len(&clear_payload[counter], &unused);
+ counter += quic_len(&clear_payload[counter], crypto_data_len);
+ crypto_data = &clear_payload[counter];
+
+ } else { /* All other versions */
+ if(first_nonzero_payload_byte != 0x06) {
+ if(first_nonzero_payload_byte != 0x02 &&
+ first_nonzero_payload_byte != 0x1C) {
+ NDPI_LOG_ERR(ndpi_struct, "Unexpected frame 0x%x\n", first_nonzero_payload_byte);
+ } else {
+ NDPI_LOG_DBG(ndpi_struct, "Unexpected ACK/CC frame\n");
}
+ return NULL;
+ }
+ if(counter + 2 + 8 >= clear_payload_len) /* quic_len reads 8 bytes, at most */
+ return NULL;
+ if(clear_payload[counter + 1] != 0x00) {
+ NDPI_LOG_ERR(ndpi_struct, "Unexpected crypto stream offset 0x%x\n",
+ clear_payload[counter + 1]);
+ return NULL;
+ }
+ counter += 2;
+ counter += quic_len(&clear_payload[counter], crypto_data_len);
+ crypto_data = &clear_payload[counter];
+ }
+
+ if(*crypto_data_len + counter > clear_payload_len) {
+ NDPI_LOG_ERR(ndpi_struct, "Invalid length %lu + %d > %d version 0x%x\n",
+ *crypto_data_len, counter, clear_payload_len, version);
+ return NULL;
+ }
+ return crypto_data;
+}
+
+static uint8_t *get_clear_payload(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ uint32_t version, uint32_t *clear_payload_len)
+{
+ struct ndpi_packet_struct *packet = &flow->packet;
+ u_int8_t *clear_payload;
+ u_int8_t dest_conn_id_len, source_conn_id_len;
+
+ if(is_gquic_ver_less_than(version, 43)) {
+ clear_payload = (uint8_t *)&packet->payload[26];
+ *clear_payload_len = packet->payload_packet_len - 26;
+ /* Skip Private-flag field for version for < Q34 */
+ if(is_gquic_ver_less_than(version, 33)) {
+ clear_payload += 1;
+ (*clear_payload_len) -= 1;
}
+ } else if(version == V_Q046) {
+ if(packet->payload[5] != 0x50) {
+ NDPI_LOG_DBG(ndpi_struct, "Q46 invalid conn id len 0x%x\n",
+ packet->payload[5]);
+ return NULL;
+ }
+ clear_payload = (uint8_t *)&packet->payload[30];
+ *clear_payload_len = packet->payload_packet_len - 30;
+ } else {
+ dest_conn_id_len = packet->payload[5];
+ if(dest_conn_id_len == 0 ||
+ dest_conn_id_len > QUIC_MAX_CID_LENGTH) {
+ NDPI_LOG_DBG(ndpi_struct, "Packet 0x%x with dest_conn_id_len %d\n",
+ version, dest_conn_id_len);
+ return NULL;
+ }
+ source_conn_id_len = packet->payload[6 + dest_conn_id_len];
+ if(source_conn_id_len > QUIC_MAX_CID_LENGTH) {
+ NDPI_LOG_DBG(ndpi_struct, "Packet 0x%x with source_conn_id_len %d\n",
+ version, source_conn_id_len);
+ return NULL;
+ }
+#ifdef HAVE_LIBGCRYPT
+ const u_int8_t *dest_conn_id = &packet->payload[6];
+ clear_payload = decrypt_initial_packet(ndpi_struct, flow,
+ dest_conn_id, dest_conn_id_len,
+ source_conn_id_len, version,
+ clear_payload_len);
+#else
+ clear_payload = NULL;
+#endif
+ }
+
+ return clear_payload;
+}
+static void process_tls(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ const u_int8_t *crypto_data, uint32_t crypto_data_len)
+{
+ struct ndpi_packet_struct *packet = &flow->packet;
+
+ /* Overwriting packet payload */
+ u_int16_t p_len;
+ const u_int8_t *p;
+ p = packet->payload;
+ p_len = packet->payload_packet_len;
+ packet->payload = crypto_data;
+ packet->payload_packet_len = crypto_data_len;
+
+ processClientServerHello(ndpi_struct, flow, 1);
+
+ /* Restore */
+ packet->payload = p;
+ packet->payload_packet_len = p_len;
+
+ /* ServerHello is not needed to sub-classified QUIC, so we ignore it:
+ this way we lose JA3S and negotiated ciphers...
+ Negotiated version is only present in the ServerHello message too, but
+ fortunately, QUIC always uses TLS version 1.3 */
+ flow->protos.stun_ssl.ssl.ssl_version = 0x0304;
+}
+static void process_chlo(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ const u_int8_t *crypto_data, uint32_t crypto_data_len)
+{
+ const uint8_t *tag;
+ uint32_t i;
+ uint16_t num_tags;
+ uint32_t prev_offset;
+ uint32_t tag_offset_start, offset, len, sni_len;
+ ndpi_protocol_match_result ret_match;
+
+ if(crypto_data_len < 6)
+ return;
+ if(memcmp(crypto_data, "CHLO", 4) != 0) {
+ NDPI_LOG_ERR(ndpi_struct, "Unexpected handshake message");
return;
}
+ num_tags = (*(uint16_t *)&crypto_data[4]);
+
+ tag_offset_start = 8 + 8 * num_tags;
+ prev_offset = 0;
+ for(i = 0; i < num_tags; i++) {
+ if(8 + 8 * i + 8 >= crypto_data_len)
+ break;
+ tag = &crypto_data[8 + 8 * i];
+ offset = *((u_int32_t *)&crypto_data[8 + 8 * i + 4]);
+ if(prev_offset > offset)
+ break;
+ len = offset - prev_offset;
+ if(tag_offset_start + prev_offset + len > crypto_data_len)
+ break;
+#if 0
+ printf("crypto_data_len %u prev_offset %u offset %u len %d\n",
+ crypto_data_len, prev_offset, offset, len);
+#endif
+ if((memcmp(tag, "SNI\0", 4) == 0) &&
+ (tag_offset_start + prev_offset + len < crypto_data_len)) {
+ sni_len = MIN(len, sizeof(flow->host_server_name) - 1);
+ memcpy(flow->host_server_name,
+ &crypto_data[tag_offset_start + prev_offset], sni_len);
+
+ NDPI_LOG_DBG2(ndpi_struct, "SNI: [%s]\n", flow->host_server_name);
+
+ ndpi_match_host_subprotocol(ndpi_struct, flow,
+ (char *)flow->host_server_name,
+ strlen((const char*)flow->host_server_name),
+ &ret_match, NDPI_PROTOCOL_QUIC);
+ return;
+ }
+
+ prev_offset = offset;
+ }
+ if(i != num_tags)
+ NDPI_LOG_DBG(ndpi_struct, "Something went wrong in tags iteration\n");
+}
- no_quic:
- NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+
+static int may_be_initial_pkt(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ uint32_t *version)
+{
+ struct ndpi_packet_struct *packet = &flow->packet;
+ u_int8_t first_byte;
+ u_int8_t pub_bit1, pub_bit2, pub_bit3, pub_bit4, pub_bit5, pub_bit7, pub_bit8;
+
+ /* According to draft-ietf-quic-transport-29: "Clients MUST ensure that UDP
+ datagrams containing Initial packets have UDP payloads of at least 1200
+ bytes". Similar limit exists for previous versions */
+ if(packet->payload_packet_len < 1200) {
+ return 0;
+ }
+
+ first_byte = packet->payload[0];
+ pub_bit1 = ((first_byte & 0x80) != 0);
+ pub_bit2 = ((first_byte & 0x40) != 0);
+ pub_bit3 = ((first_byte & 0x20) != 0);
+ pub_bit4 = ((first_byte & 0x10) != 0);
+ pub_bit5 = ((first_byte & 0x08) != 0);
+ pub_bit7 = ((first_byte & 0x02) != 0);
+ pub_bit8 = ((first_byte & 0x01) != 0);
+
+ *version = 0;
+ if(pub_bit1) {
+ *version = ntohl(*((u_int32_t *)&packet->payload[1]));
+ } else if(pub_bit5 && !pub_bit2) {
+ if(!pub_bit8) {
+ NDPI_LOG_DBG2(ndpi_struct, "Packet without version\n")
+ } else {
+ *version = ntohl(*((u_int32_t *)&packet->payload[9]));
+ }
+ }
+ if(!is_version_valid(*version)) {
+ NDPI_LOG_DBG2(ndpi_struct, "Invalid version 0x%x\n", *version);
+ return 0;
+ }
+
+ if(is_gquic_ver_less_than(*version, 43) &&
+ (!pub_bit5 || pub_bit3 != 0 || pub_bit4 != 0)) {
+ NDPI_LOG_ERR(ndpi_struct, "Version 0x%x invalid flags 0x%x\n",
+ *version, first_byte);
+ return 0;
+ }
+ if((*version == V_Q046) &&
+ (pub_bit7 != 1 || pub_bit8 != 1)) {
+ NDPI_LOG_ERR(ndpi_struct, "Q46 invalid flag 0x%x\n", first_byte);
+ return 0;
+ }
+ if((is_version_quic(*version) || (*version == V_Q046) || (*version == V_Q050)) &&
+ (pub_bit3 != 0 || pub_bit4 != 0)) {
+ NDPI_LOG_DBG2(ndpi_struct, "Version 0x%x not Initial Packet\n", *version);
+ return 0;
+ }
+
+ /* TODO: add some other checks to avoid false positives */
+
+ return 1;
+}
+
+/* ***************************************************************** */
+
+void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow)
+{
+ u_int32_t version;
+ u_int8_t *clear_payload;
+ uint32_t clear_payload_len;
+ const u_int8_t *crypto_data;
+ uint64_t crypto_data_len;
+ int is_quic;
+
+ NDPI_LOG_DBG2(ndpi_struct, "search QUIC\n");
+
+ /* Buffers: packet->payload ---> clear_payload ---> crypto_data */
+
+ /*
+ * 1) (Very) basic heuristic to check if it is a QUIC packet.
+ * The first packet of each QUIC session should contain a valid
+ * CHLO/ClientHello message and we need (only) it to sub-classify
+ * the flow.
+ * Detecting QUIC sessions where the first captured packet is not a
+ * CHLO/CH is VERY hard. Let's try avoiding it and let's see if
+ * anyone complains...
+ */
+
+ is_quic = may_be_initial_pkt(ndpi_struct, flow, &version);
+ if(!is_quic) {
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+
+ /*
+ * 2) Ok, this packet seems to be QUIC
+ */
+
+ NDPI_LOG_INFO(ndpi_struct, "found QUIC\n");
+ ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, NDPI_PROTOCOL_UNKNOWN);
+
+ /*
+ * 3) Skip not supported versions
+ */
+
+ if(!is_version_supported(version)) {
+ NDPI_LOG_ERR(ndpi_struct, "Unsupported version 0x%x\n", version)
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+
+ /*
+ * 4) Extract the Payload from Initial Packets
+ */
+ clear_payload = get_clear_payload(ndpi_struct, flow, version, &clear_payload_len);
+ if(!clear_payload) {
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ return;
+ }
+
+ /*
+ * 5) Extract Crypto Data from the Payload
+ */
+ crypto_data = get_crypto_data(ndpi_struct, flow, version,
+ clear_payload, clear_payload_len,
+ &crypto_data_len);
+ if(!crypto_data) {
+ NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ if(is_version_with_encrypted_header(version)) {
+ ndpi_free(clear_payload);
+ }
+ return;
+ }
+
+ /*
+ * 6) Process ClientHello/CHLO from the Crypto Data
+ */
+ if(is_version_gquic(version)) {
+ process_chlo(ndpi_struct, flow, crypto_data, crypto_data_len);
+ } else {
+ process_tls(ndpi_struct, flow, crypto_data, crypto_data_len);
+ }
+ if(is_version_with_encrypted_header(version)) {
+ ndpi_free(clear_payload);
+ }
}
/* ***************************************************************** */
diff --git a/src/lib/protocols/tls.c b/src/lib/protocols/tls.c
index 883de7666..aa3836442 100644
--- a/src/lib/protocols/tls.c
+++ b/src/lib/protocols/tls.c
@@ -31,7 +31,7 @@
extern char *strptime(const char *s, const char *format, struct tm *tm);
extern int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
- struct ndpi_flow_struct *flow);
+ struct ndpi_flow_struct *flow, int is_quic);
// #define DEBUG_TLS_MEMORY 1
// #define DEBUG_TLS 1
@@ -616,7 +616,7 @@ static int processTLSBlock(struct ndpi_detection_module_struct *ndpi_struct,
switch(packet->payload[0] /* block type */) {
case 0x01: /* Client Hello */
case 0x02: /* Server Hello */
- processClientServerHello(ndpi_struct, flow);
+ processClientServerHello(ndpi_struct, flow, 0);
flow->l4.tcp.tls.hello_processed = 1;
ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TLS);
break;
@@ -864,7 +864,7 @@ struct ja3_info {
/* **************************************** */
int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
- struct ndpi_flow_struct *flow) {
+ struct ndpi_flow_struct *flow, int is_quic) {
struct ndpi_packet_struct *packet = &flow->packet;
struct ja3_info ja3;
u_int8_t invalid_ja3 = 0;
@@ -876,6 +876,7 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
u_int16_t total_len;
u_int8_t handshake_type;
char buffer[64] = { '\0' };
+ int is_dtls = packet->udp && (!is_quic);
#ifdef DEBUG_TLS
printf("SSL %s() called\n", __FUNCTION__);
@@ -893,9 +894,9 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
/* At least "magic" 3 bytes, null for string end, otherwise no need to waste cpu cycles */
if(total_len > 4) {
- u_int16_t base_offset = packet->tcp ? 38 : 46;
- u_int16_t version_offset = packet->tcp ? 4 : 12;
- u_int16_t offset = packet->tcp ? 38 : 46, extension_len, j;
+ u_int16_t base_offset = (!is_dtls) ? 38 : 46;
+ u_int16_t version_offset = (!is_dtls) ? 4 : 12;
+ u_int16_t offset = (!is_dtls) ? 38 : 46, extension_len, j;
u_int8_t session_id_len = 0;
if (base_offset < total_len)
session_id_len = packet->payload[base_offset];
@@ -1029,7 +1030,7 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
if((session_id_len+base_offset+3) > packet->payload_packet_len)
return(0); /* Not found */
- if(packet->tcp) {
+ if(!is_dtls) {
cipher_len = packet->payload[session_id_len+base_offset+2] + (packet->payload[session_id_len+base_offset+1] << 8);
cipher_offset = base_offset + session_id_len + 3;
} else {
@@ -1079,7 +1080,7 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
u_int16_t compression_len;
u_int16_t extensions_len;
- offset += packet->tcp ? 1 : 2;
+ offset += (!is_dtls) ? 1 : 2;
compression_len = packet->payload[offset];
offset++;
@@ -1149,9 +1150,16 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
snprintf(flow->protos.stun_ssl.ssl.client_requested_server_name,
sizeof(flow->protos.stun_ssl.ssl.client_requested_server_name),
"%s", buffer);
-
- if(ndpi_match_hostname_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TLS, buffer, strlen(buffer)))
- flow->l4.tcp.tls.subprotocol_detected = 1;
+#ifdef DEBUG_TLS
+ printf("[TLS] SNI: [%s]\n", buffer);
+#endif
+ if(!is_quic) {
+ if(ndpi_match_hostname_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TLS, buffer, strlen(buffer)))
+ flow->l4.tcp.tls.subprotocol_detected = 1;
+ } else {
+ if(ndpi_match_hostname_protocol(ndpi_struct, flow, NDPI_PROTOCOL_QUIC, buffer, strlen(buffer)))
+ flow->l4.tcp.tls.subprotocol_detected = 1;
+ }
ndpi_check_dga_name(ndpi_struct, flow, flow->protos.stun_ssl.ssl.client_requested_server_name);
} else {
@@ -1289,7 +1297,7 @@ int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
#ifdef DEBUG_TLS
printf("Client SSL [TLS version: %s/0x%04X]\n",
- ndpi_ssl_version2str(NULL, tls_version, &unknown_tls_version), tls_version);
+ ndpi_ssl_version2str(flow, tls_version, &unknown_tls_version), tls_version);
#endif
if((version_str_len+8) < sizeof(version_str)) {
diff --git a/tests/pcap/quic-23.pcap b/tests/pcap/quic-23.pcap
new file mode 100644
index 000000000..1e8df1101
--- /dev/null
+++ b/tests/pcap/quic-23.pcap
Binary files differ
diff --git a/tests/pcap/quic-24.pcap b/tests/pcap/quic-24.pcap
new file mode 100644
index 000000000..a2637de56
--- /dev/null
+++ b/tests/pcap/quic-24.pcap
Binary files differ
diff --git a/tests/pcap/quic-27.pcap b/tests/pcap/quic-27.pcap
new file mode 100644
index 000000000..d6804d874
--- /dev/null
+++ b/tests/pcap/quic-27.pcap
Binary files differ
diff --git a/tests/pcap/quic-28.pcapng b/tests/pcap/quic-28.pcapng
new file mode 100644
index 000000000..733d665d7
--- /dev/null
+++ b/tests/pcap/quic-28.pcapng
Binary files differ
diff --git a/tests/pcap/quic-29.pcapng b/tests/pcap/quic-29.pcapng
new file mode 100644
index 000000000..c0034b2c9
--- /dev/null
+++ b/tests/pcap/quic-29.pcapng
Binary files differ
diff --git a/tests/pcap/quic-mvfst-22.pcap b/tests/pcap/quic-mvfst-22.pcap
new file mode 100644
index 000000000..2188a5f2e
--- /dev/null
+++ b/tests/pcap/quic-mvfst-22.pcap
Binary files differ
diff --git a/tests/pcap/quic-mvfst-22_decryption_error.pcap b/tests/pcap/quic-mvfst-22_decryption_error.pcap
new file mode 100644
index 000000000..73e8312bc
--- /dev/null
+++ b/tests/pcap/quic-mvfst-22_decryption_error.pcap
Binary files differ
diff --git a/tests/pcap/quic-mvfst-27.pcap b/tests/pcap/quic-mvfst-27.pcap
new file mode 100644
index 000000000..d6804d874
--- /dev/null
+++ b/tests/pcap/quic-mvfst-27.pcap
Binary files differ
diff --git a/tests/pcap/quic_q39.pcap b/tests/pcap/quic_q39.pcap
new file mode 100644
index 000000000..e2a90775b
--- /dev/null
+++ b/tests/pcap/quic_q39.pcap
Binary files differ
diff --git a/tests/pcap/quic_q43.pcap b/tests/pcap/quic_q43.pcap
new file mode 100644
index 000000000..862a49842
--- /dev/null
+++ b/tests/pcap/quic_q43.pcap
Binary files differ
diff --git a/tests/pcap/quic_q46.pcap b/tests/pcap/quic_q46.pcap
new file mode 100644
index 000000000..4d13dca85
--- /dev/null
+++ b/tests/pcap/quic_q46.pcap
Binary files differ
diff --git a/tests/pcap/quic_q46_b.pcap b/tests/pcap/quic_q46_b.pcap
new file mode 100644
index 000000000..15ed06e7b
--- /dev/null
+++ b/tests/pcap/quic_q46_b.pcap
Binary files differ
diff --git a/tests/pcap/quic_q50.pcap b/tests/pcap/quic_q50.pcap
new file mode 100644
index 000000000..5964f452e
--- /dev/null
+++ b/tests/pcap/quic_q50.pcap
Binary files differ
diff --git a/tests/result/http_ipv6.pcap.out b/tests/result/http_ipv6.pcap.out
index f2f89081d..39c96b63f 100644
--- a/tests/result/http_ipv6.pcap.out
+++ b/tests/result/http_ipv6.pcap.out
@@ -1,8 +1,8 @@
+Unknown 3 502 1
ntop 80 36401 4
TLS 26 3245 7
Facebook 22 10202 2
Google 62 15977 1
-QUIC 3 502 1
JA3 Host Stats:
IP Address # JA3C
@@ -17,10 +17,13 @@ JA3 Host Stats:
6 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:53132 <-> [2a02:26f0:ad:197::236]:443 [proto: 91.119/TLS.Facebook][cat: SocialNetwork/6][7 pkts/960 bytes <-> 5 pkts/4227 bytes][Goodput ratio: 36/90][0.06 sec][ALPN: http/1.1;spdy/3.1;h2-14;h2][bytes ratio: -0.630 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 3/3 8/7 3/3][Pkt Len c2s/s2c min/avg/max/stddev: 86/86 137/845 310/2942 83/1078][TLSv1.2][Client: s-static.ak.facebook.com][JA3C: d3e627f423a33ea41841c19b8af79293][ServerNames: *.ak.fbcdn.net,s-static.ak.fbcdn.net,igsonar.com,*.igsonar.com,ak.facebook.com,*.ak.facebook.com,*.s-static.ak.facebook.com,connect.facebook.net,s-static.ak.facebook.com][JA3S: b898351eb5e266aefd3723d466935494][Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance CA-3][Subject: C=US, ST=CA, L=Menlo Park, O=Facebook, Inc., CN=*.ak.fbcdn.net][Certificate SHA-1: E7:62:76:74:8D:09:F7:E9:69:05:B8:1A:37:A1:30:2D:FF:3B:BC:0A][Validity: 2015-08-12 00:00:00 - 2015-12-31 12:00:00][Cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256][Plen Bins: 0,0,0,20,0,0,0,40,0,0,0,0,0,0,0,0,0,0,0,0,0,20,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,20]
7 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:53134 <-> [2a02:26f0:ad:197::236]:443 [proto: 91.119/TLS.Facebook][cat: SocialNetwork/6][6 pkts/874 bytes <-> 4 pkts/4141 bytes][Goodput ratio: 40/91][0.06 sec][ALPN: http/1.1;spdy/3.1;h2-14;h2][bytes ratio: -0.651 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/1 12/5 43/8 16/3][Pkt Len c2s/s2c min/avg/max/stddev: 86/86 146/1035 310/3633 86/1503][TLSv1.2][Client: s-static.ak.facebook.com][JA3C: d3e627f423a33ea41841c19b8af79293][ServerNames: *.ak.fbcdn.net,s-static.ak.fbcdn.net,igsonar.com,*.igsonar.com,ak.facebook.com,*.ak.facebook.com,*.s-static.ak.facebook.com,connect.facebook.net,s-static.ak.facebook.com][JA3S: b898351eb5e266aefd3723d466935494][Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert High Assurance CA-3][Subject: C=US, ST=CA, L=Menlo Park, O=Facebook, Inc., CN=*.ak.fbcdn.net][Certificate SHA-1: E7:62:76:74:8D:09:F7:E9:69:05:B8:1A:37:A1:30:2D:FF:3B:BC:0A][Validity: 2015-08-12 00:00:00 - 2015-12-31 12:00:00][Cipher: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256][Plen Bins: 0,0,0,25,0,0,0,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,25]
8 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:41776 <-> [2a00:1450:4001:803::1017]:443 [proto: 91/TLS][cat: Web/5][7 pkts/860 bytes <-> 7 pkts/1353 bytes][Goodput ratio: 30/55][0.12 sec][bytes ratio: -0.223 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 11/6 30/30 13/12][Pkt Len c2s/s2c min/avg/max/stddev: 86/86 123/193 268/592 62/172][Plen Bins: 0,57,0,0,0,28,0,0,0,0,0,0,0,0,0,14,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]
- 9 UDP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:55145 <-> [2a00:1450:400b:c02::5f]:443 [proto: 188/QUIC][cat: Web/5][2 pkts/359 bytes <-> 1 pkts/143 bytes][Goodput ratio: 65/56][0.07 sec][Plen Bins: 0,33,33,0,0,0,33,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]
- 10 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:33062 <-> [2a00:1450:400b:c02::9a]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.04 sec][Plen Bins: 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,0,0]
- 11 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:40308 <-> [2a03:2880:1010:3f20:face:b00c::25de]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.13 sec][Plen Bins: 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,0,0]
- 12 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:40526 <-> [2a00:1450:4006:804::200e]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.02 sec][Plen Bins: 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,0,0]
- 13 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:58660 <-> [2a00:1450:4006:803::2008]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.02 sec][Plen Bins: 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,0,0]
- 14 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:59690 <-> [2a00:1450:4001:803::1012]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.02 sec][Plen Bins: 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,0,0]
- 15 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:60124 <-> [2a02:26f0:ad:1a1::eed]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.01 sec][Plen Bins: 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,0,0]
+ 9 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:33062 <-> [2a00:1450:400b:c02::9a]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.04 sec][Plen Bins: 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,0,0]
+ 10 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:40308 <-> [2a03:2880:1010:3f20:face:b00c::25de]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.13 sec][Plen Bins: 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,0,0]
+ 11 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:40526 <-> [2a00:1450:4006:804::200e]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.02 sec][Plen Bins: 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,0,0]
+ 12 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:58660 <-> [2a00:1450:4006:803::2008]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.02 sec][Plen Bins: 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,0,0]
+ 13 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:59690 <-> [2a00:1450:4001:803::1012]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.02 sec][Plen Bins: 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,0,0]
+ 14 TCP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:60124 <-> [2a02:26f0:ad:1a1::eed]:443 [proto: 91/TLS][cat: Web/5][1 pkts/86 bytes <-> 1 pkts/86 bytes][Goodput ratio: 0/0][0.01 sec][Plen Bins: 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,0,0]
+
+
+Undetected flows:
+ 1 UDP [2a00:d40:1:3:7aac:c0ff:fea7:d4c]:55145 <-> [2a00:1450:400b:c02::5f]:443 [proto: 0/Unknown][2 pkts/359 bytes <-> 1 pkts/143 bytes][Goodput ratio: 65/56][0.07 sec][Plen Bins: 0,33,33,0,0,0,33,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/result/quic-23.pcap.out b/tests/result/quic-23.pcap.out
new file mode 100644
index 000000000..2b83a61cb
--- /dev/null
+++ b/tests/result/quic-23.pcap.out
@@ -0,0 +1,8 @@
+QUIC 20 7191 1
+
+JA3 Host Stats:
+ IP Address # JA3C
+ 1 2e4a:774d:26fd:7f9b:785b:2d1b:4f8a:63c7 1
+
+
+ 1 UDP [2e4a:774d:26fd:7f9b:785b:2d1b:4f8a:63c7]:50339 <-> [3bcc:9991:faba:bae1:cd2a:e2fd:b3be:c5ab]:443 [proto: 188/QUIC][cat: Web/5][10 pkts/2613 bytes <-> 10 pkts/4578 bytes][Goodput ratio: 76/86][0.11 sec][ALPN: h3-22][TLS Supported Versions: TLSv1.3][bytes ratio: -0.273 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 6/8 38/34 12/15][Pkt Len c2s/s2c min/avg/max/stddev: 92/94 261/458 1342/1342 373/458][TLSv1.3][Client: quic.aiortc.org][JA3C: d9e7bdb15af8e499820ca74a68affd78][Plen Bins: 5,35,15,10,5,0,0,5,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,5,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0]
diff --git a/tests/result/quic-24.pcap.out b/tests/result/quic-24.pcap.out
new file mode 100644
index 000000000..f91558186
--- /dev/null
+++ b/tests/result/quic-24.pcap.out
@@ -0,0 +1,8 @@
+QUIC 15 8000 1
+
+JA3 Host Stats:
+ IP Address # JA3C
+ 1 10.9.0.1 1
+
+
+ 1 UDP 10.9.0.1:41436 <-> 10.9.0.2:443 [proto: 188/QUIC][cat: Web/5][7 pkts/4672 bytes <-> 8 pkts/3328 bytes][Goodput ratio: 94/90][30.04 sec][ALPN: h3-24][TLS Supported Versions: TLSv1.3][bytes ratio: 0.168 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 1/0 6006/4 30007/12 12000/4][Pkt Len c2s/s2c min/avg/max/stddev: 82/81 667/416 1294/1294 562/406][TLSv1.3][Client: localhost][JA3C: b3e43d74f4b790abca2f5fe7dd06e7cf][PLAIN TEXT (Udwn.wf)][Plen Bins: 0,34,0,6,6,0,0,0,6,0,6,0,0,0,0,6,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,27,0,0,0,0,0,0,0,0]
diff --git a/tests/result/quic-27.pcap.out b/tests/result/quic-27.pcap.out
new file mode 100644
index 000000000..83e2fba11
--- /dev/null
+++ b/tests/result/quic-27.pcap.out
@@ -0,0 +1,8 @@
+Google 20 12887 1
+
+JA3 Host Stats:
+ IP Address # JA3C
+ 1 3ef4:2194:f4a6:3503:40cd:714:57:c4e4 1
+
+
+ 1 UDP [3ef4:2194:f4a6:3503:40cd:714:57:c4e4]:64229 <-> [2f3d:64d1:9d59:549b::200e]:443 [proto: 188.126/QUIC.Google][cat: Web/5][9 pkts/6081 bytes <-> 11 pkts/6806 bytes][Goodput ratio: 91/90][8.46 sec][ALPN: h3-27][TLS Supported Versions: TLSv1.3][bytes ratio: -0.056 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 10/0 1198/938 8168/8161 2846/2554][Pkt Len c2s/s2c min/avg/max/stddev: 95/87 676/619 1392/1392 622/598][TLSv1.3][Client: play.google.com][JA3C: 1e022f87823477abd6a79c31d70062d7][Plen Bins: 20,30,0,0,5,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,0,0,0,0,0,5,5,0,30,0,0,0,0,0,0]
diff --git a/tests/result/quic-mvfst-22.pcap.out b/tests/result/quic-mvfst-22.pcap.out
new file mode 100644
index 000000000..067082b84
--- /dev/null
+++ b/tests/result/quic-mvfst-22.pcap.out
@@ -0,0 +1,8 @@
+Facebook 490 288303 1
+
+JA3 Host Stats:
+ IP Address # JA3C
+ 1 10.0.2.15 1
+
+
+ 1 UDP 10.0.2.15:35601 <-> 31.13.86.8:443 [proto: 188.119/QUIC.Facebook][cat: SocialNetwork/6][188 pkts/80544 bytes <-> 302 pkts/207759 bytes][Goodput ratio: 90/94][115.21 sec][ALPN: h3-fb-05;h1q-fb][TLS Supported Versions: TLSv1.3;TLSv1.3 (draft)][bytes ratio: -0.441 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 276/385 10173/64999 1046/4119][Pkt Len c2s/s2c min/avg/max/stddev: 73/66 428/688 1274/1294 478/546][TLSv1.3][Client: graph.facebook.com][JA3C: a3795d067fbf6f44c8657f9e9cbae493][PLAIN TEXT (rPnDAD)][Plen Bins: 21,26,1,0,0,0,0,0,0,2,0,2,3,1,3,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,32,0,0,0,0,0,0,0,0]
diff --git a/tests/result/quic-mvfst-22_decryption_error.pcap.out b/tests/result/quic-mvfst-22_decryption_error.pcap.out
new file mode 100644
index 000000000..d467f3859
--- /dev/null
+++ b/tests/result/quic-mvfst-22_decryption_error.pcap.out
@@ -0,0 +1,3 @@
+QUIC 353 400490 1
+
+ 1 UDP 10.230.40.168:62196 <-> 94.97.225.146:443 [proto: 188/QUIC][cat: Web/5][43 pkts/13029 bytes <-> 310 pkts/387461 bytes][Goodput ratio: 91/98][0.20 sec][bytes ratio: -0.935 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 1/0 15/43 3/3][Pkt Len c2s/s2c min/avg/max/stddev: 59/66 303/1250 1260/1280 452/176][PLAIN TEXT (FSboeS)][Plen Bins: 4,4,1,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,85,0,0,0,0,0,0,0,0]
diff --git a/tests/result/quic-mvfst-27.pcap.out b/tests/result/quic-mvfst-27.pcap.out
new file mode 100644
index 000000000..83e2fba11
--- /dev/null
+++ b/tests/result/quic-mvfst-27.pcap.out
@@ -0,0 +1,8 @@
+Google 20 12887 1
+
+JA3 Host Stats:
+ IP Address # JA3C
+ 1 3ef4:2194:f4a6:3503:40cd:714:57:c4e4 1
+
+
+ 1 UDP [3ef4:2194:f4a6:3503:40cd:714:57:c4e4]:64229 <-> [2f3d:64d1:9d59:549b::200e]:443 [proto: 188.126/QUIC.Google][cat: Web/5][9 pkts/6081 bytes <-> 11 pkts/6806 bytes][Goodput ratio: 91/90][8.46 sec][ALPN: h3-27][TLS Supported Versions: TLSv1.3][bytes ratio: -0.056 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 10/0 1198/938 8168/8161 2846/2554][Pkt Len c2s/s2c min/avg/max/stddev: 95/87 676/619 1392/1392 622/598][TLSv1.3][Client: play.google.com][JA3C: 1e022f87823477abd6a79c31d70062d7][Plen Bins: 20,30,0,0,5,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,0,0,0,0,0,5,5,0,30,0,0,0,0,0,0]
diff --git a/tests/result/quic.pcap.out b/tests/result/quic.pcap.out
index cbe4856ec..f54c50fc4 100644
--- a/tests/result/quic.pcap.out
+++ b/tests/result/quic.pcap.out
@@ -1,18 +1,15 @@
-Unknown 6 7072 1
GMail 413 254874 1
YouTube 85 76193 5
Google 14 10427 3
+QUIC 6 7072 1
1 UDP 192.168.1.109:57833 <-> 216.58.212.101:443 [proto: 188.122/QUIC.GMail][cat: Email/3][161 pkts/23930 bytes <-> 252 pkts/230944 bytes][Goodput ratio: 72/95][37.93 sec][Host: mail.google.com][bytes ratio: -0.812 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 303/161 21144/21225 1960/1485][Pkt Len c2s/s2c min/avg/max/stddev: 67/61 149/916 1392/1392 207/581][PLAIN TEXT (mail.google.com)][Plen Bins: 4,37,1,5,3,0,3,0,0,0,0,1,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,40,0,0,0,0,0]
2 UDP 192.168.1.109:35236 <-> 216.58.210.206:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][25 pkts/5276 bytes <-> 44 pkts/53157 bytes][Goodput ratio: 80/97][1.00 sec][Host: www.youtube.com][bytes ratio: -0.819 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 52/26 803/828 183/134][Pkt Len c2s/s2c min/avg/max/stddev: 79/61 211/1208 1392/1392 358/430][PLAIN TEXT (www.youtube.com)][Plen Bins: 1,35,1,0,0,0,0,0,0,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,57,0,0,0,0,0]
- 3 UDP 192.168.1.105:34438 <-> 216.58.210.238:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][4 pkts/3682 bytes <-> 3 pkts/2863 bytes][Goodput ratio: 95/96][0.10 sec][Host: www.youtube.com][bytes ratio: 0.125 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 22/20 33/10 52/20 13/10][Pkt Len c2s/s2c min/avg/max/stddev: 82/79 920/954 1392/1392 538/619][PLAIN TEXT (www.youtube.com)][Plen Bins: 0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57,0,0,0,0,0]
- 4 UDP 192.168.1.105:40030 <-> 216.58.201.227:443 [proto: 188.126/QUIC.Google][cat: Web/5][3 pkts/2866 bytes <-> 3 pkts/2863 bytes][Goodput ratio: 96/96][0.10 sec][Host: fonts.gstatic.com][bytes ratio: 0.001 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 25/21 50/10 74/21 24/10][Pkt Len c2s/s2c min/avg/max/stddev: 82/79 955/954 1392/1392 618/619][PLAIN TEXT (fonts.gstatic.com)][Plen Bins: 0,33,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,66,0,0,0,0,0]
- 5 UDP 192.168.1.105:55934 <-> 216.58.201.238:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][2 pkts/2784 bytes <-> 2 pkts/2784 bytes][Goodput ratio: 97/97][0.09 sec][Host: s.ytimg.com][PLAIN TEXT (s.ytimg.com)][Plen Bins: 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,100,0,0,0,0,0]
- 6 UDP 192.168.1.105:45669 <-> 172.217.16.4:443 [proto: 188.126/QUIC.Google][cat: Web/5][3 pkts/1550 bytes <-> 2 pkts/2784 bytes][Goodput ratio: 92/97][0.16 sec][Host: www.google.com][PLAIN TEXT (www.google.comO)][Plen Bins: 0,40,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,60,0,0,0,0,0]
- 7 UDP 192.168.1.105:48445 <-> 216.58.214.110:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][2 pkts/1471 bytes <-> 1 pkts/1392 bytes][Goodput ratio: 94/97][0.10 sec][Host: i.ytimg.com][PLAIN TEXT (i.ytimg.com)][Plen Bins: 0,33,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,66,0,0,0,0,0]
- 8 UDP 192.168.1.105:53817 <-> 216.58.210.225:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][1 pkts/1392 bytes <-> 1 pkts/1392 bytes][Goodput ratio: 97/97][0.08 sec][Host: yt3.ggpht.com][PLAIN TEXT (yt3.ggpht.com)][Plen Bins: 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,100,0,0,0,0,0]
- 9 UDP 192.168.1.105:40461 <-> 172.217.16.3:443 [proto: 188.126/QUIC.Google][cat: Web/5][2 pkts/241 bytes <-> 1 pkts/123 bytes][Goodput ratio: 65/65][0.09 sec][Plen Bins: 0,33,33,33,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]
-
-
-Undetected flows:
- 1 UDP 10.0.0.4:40134 -> 10.0.0.3:6121 [proto: 0/Unknown][6 pkts/7072 bytes -> 0 pkts/0 bytes][Goodput ratio: 96/0][4.00 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 150/0 800/0 1749/0 595/0][Pkt Len c2s/s2c min/avg/max/stddev: 112/0 1179/0 1392/0 477/0][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,83,0,0,0,0,0]
+ 3 UDP 10.0.0.4:40134 -> 10.0.0.3:6121 [proto: 188/QUIC][cat: Web/5][6 pkts/7072 bytes -> 0 pkts/0 bytes][Goodput ratio: 96/0][4.00 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 150/0 800/0 1749/0 595/0][Pkt Len c2s/s2c min/avg/max/stddev: 112/0 1179/0 1392/0 477/0][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,83,0,0,0,0,0]
+ 4 UDP 192.168.1.105:34438 <-> 216.58.210.238:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][4 pkts/3682 bytes <-> 3 pkts/2863 bytes][Goodput ratio: 95/96][0.10 sec][Host: www.youtube.com][bytes ratio: 0.125 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 22/20 33/10 52/20 13/10][Pkt Len c2s/s2c min/avg/max/stddev: 82/79 920/954 1392/1392 538/619][PLAIN TEXT (www.youtube.com)][Plen Bins: 0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,57,0,0,0,0,0]
+ 5 UDP 192.168.1.105:40030 <-> 216.58.201.227:443 [proto: 188.126/QUIC.Google][cat: Web/5][3 pkts/2866 bytes <-> 3 pkts/2863 bytes][Goodput ratio: 96/96][0.10 sec][Host: fonts.gstatic.com][bytes ratio: 0.001 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 25/21 50/10 74/21 24/10][Pkt Len c2s/s2c min/avg/max/stddev: 82/79 955/954 1392/1392 618/619][PLAIN TEXT (fonts.gstatic.com)][Plen Bins: 0,33,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,66,0,0,0,0,0]
+ 6 UDP 192.168.1.105:55934 <-> 216.58.201.238:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][2 pkts/2784 bytes <-> 2 pkts/2784 bytes][Goodput ratio: 97/97][0.09 sec][Host: s.ytimg.com][PLAIN TEXT (s.ytimg.com)][Plen Bins: 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,100,0,0,0,0,0]
+ 7 UDP 192.168.1.105:45669 <-> 172.217.16.4:443 [proto: 188.126/QUIC.Google][cat: Web/5][3 pkts/1550 bytes <-> 2 pkts/2784 bytes][Goodput ratio: 92/97][0.16 sec][Host: www.google.com][PLAIN TEXT (www.google.comO)][Plen Bins: 0,40,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,60,0,0,0,0,0]
+ 8 UDP 192.168.1.105:48445 <-> 216.58.214.110:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][2 pkts/1471 bytes <-> 1 pkts/1392 bytes][Goodput ratio: 94/97][0.10 sec][Host: i.ytimg.com][PLAIN TEXT (i.ytimg.com)][Plen Bins: 0,33,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,66,0,0,0,0,0]
+ 9 UDP 192.168.1.105:53817 <-> 216.58.210.225:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][1 pkts/1392 bytes <-> 1 pkts/1392 bytes][Goodput ratio: 97/97][0.08 sec][Host: yt3.ggpht.com][PLAIN TEXT (yt3.ggpht.com)][Plen Bins: 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,100,0,0,0,0,0]
+ 10 UDP 192.168.1.105:40461 <-> 172.217.16.3:443 [proto: 126/Google][cat: Web/5][2 pkts/241 bytes <-> 1 pkts/123 bytes][Goodput ratio: 65/65][0.09 sec][Plen Bins: 0,33,33,33,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/result/quic_q39.pcap.out b/tests/result/quic_q39.pcap.out
new file mode 100644
index 000000000..d15fd32fd
--- /dev/null
+++ b/tests/result/quic_q39.pcap.out
@@ -0,0 +1,3 @@
+YouTube 60 24185 1
+
+ 1 UDP 170.216.16.209:38620 <-> 21.157.183.227:443 [proto: 188.124/QUIC.YouTube][cat: Media/1][27 pkts/20099 bytes <-> 33 pkts/4086 bytes][Goodput ratio: 94/66][48.95 sec][Host: s.youtube.com][bytes ratio: 0.662 (Upload)][IAT c2s/s2c min/avg/max/stddev: 1/0 2239/1370 14326/14805 3925/3576][Pkt Len c2s/s2c min/avg/max/stddev: 65/60 744/124 1392/1392 569/228][PLAIN TEXT (s.youtube.com)][Plen Bins: 24,47,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,5,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,16,0,0,0,0,0]
diff --git a/tests/result/quic_q43.pcap.out b/tests/result/quic_q43.pcap.out
new file mode 100644
index 000000000..72fa15459
--- /dev/null
+++ b/tests/result/quic_q43.pcap.out
@@ -0,0 +1,3 @@
+DoH_DoT 2 1464 1
+
+ 1 UDP 51.120.20.202:49241 <-> 72.119.217.29:443 [proto: 188.196/QUIC.DoH_DoT][cat: Network/14][1 pkts/1392 bytes <-> 1 pkts/72 bytes][Goodput ratio: 97/41][0.05 sec][Host: dns.google.com][PLAIN TEXT (dns.google.com)][Plen Bins: 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,50,0,0,0,0,0]
diff --git a/tests/result/quic_q46.pcap.out b/tests/result/quic_q46.pcap.out
new file mode 100644
index 000000000..42b7ba8ea
--- /dev/null
+++ b/tests/result/quic_q46.pcap.out
@@ -0,0 +1,3 @@
+Google 20 21241 1
+
+ 1 UDP 172.29.42.236:38292 <-> 153.20.183.203:443 [proto: 188.126/QUIC.Google][cat: Web/5][5 pkts/1675 bytes <-> 15 pkts/19566 bytes][Goodput ratio: 87/97][0.31 sec][Host: play.google.com][bytes ratio: -0.842 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 6/20 17/224 8/59][Pkt Len c2s/s2c min/avg/max/stddev: 70/78 335/1304 1392/1392 529/328][PLAIN TEXT (play.google.comL)][Plen Bins: 20,5,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,75,0,0,0,0,0]
diff --git a/tests/result/quic_q46_b.pcap.out b/tests/result/quic_q46_b.pcap.out
new file mode 100644
index 000000000..79d716033
--- /dev/null
+++ b/tests/result/quic_q46_b.pcap.out
@@ -0,0 +1,3 @@
+YouTubeUpload 20 7020 1
+
+ 1 UDP 172.27.69.216:45530 <-> 110.231.134.35:443 [proto: 188.136/QUIC.YouTubeUpload][cat: Media/1][6 pkts/2916 bytes <-> 14 pkts/4104 bytes][Goodput ratio: 81/69][3.09 sec][Host: upload.youtube.com][bytes ratio: -0.169 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 24/0 200/218 384/1017 128/277][Pkt Len c2s/s2c min/avg/max/stddev: 118/106 486/293 1440/1440 466/345][PLAIN TEXT (upload.youtube.comx)][Plen Bins: 45,15,0,0,0,0,0,0,0,0,20,0,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,10,0,0,0,0,0]
diff --git a/tests/result/quic_q50.pcap.out b/tests/result/quic_q50.pcap.out
new file mode 100644
index 000000000..5f8779ed1
--- /dev/null
+++ b/tests/result/quic_q50.pcap.out
@@ -0,0 +1,3 @@
+QUIC 20 20434 1
+
+ 1 UDP 248.144.129.147:39203 <-> 184.151.193.237:443 [proto: 188/QUIC][cat: Web/5][6 pkts/3579 bytes <-> 14 pkts/16855 bytes][Goodput ratio: 93/97][0.47 sec][Host: www.googletagmanager.com][bytes ratio: -0.650 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 85/27 210/221 80/63][Pkt Len c2s/s2c min/avg/max/stddev: 75/67 596/1204 1392/1392 588/461][PLAIN TEXT (x.GdrZY)][Plen Bins: 5,20,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,70,0,0,0,0,0]
diff --git a/tests/result/starcraft_battle.pcap.out b/tests/result/starcraft_battle.pcap.out
index 212f31532..fe6fdc45c 100644
--- a/tests/result/starcraft_battle.pcap.out
+++ b/tests/result/starcraft_battle.pcap.out
@@ -30,7 +30,7 @@ Starcraft 236 51494 6
20 TCP 192.168.1.100:3427 <-> 80.239.208.193:1119 [proto: 213/Starcraft][cat: Game/8][6 pkts/376 bytes <-> 7 pkts/526 bytes][Goodput ratio: 14/22][10.56 sec][bytes ratio: -0.166 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 2624/2614 6381/6342 2711/2730][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 63/75 74/155 9/33][Plen Bins: 80,0,0,20,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]
21 TCP 192.168.1.100:3512 <-> 12.129.222.54:80 [proto: 7.76/HTTP.WorldOfWarcraft][cat: Game/8][5 pkts/367 bytes <-> 4 pkts/513 bytes][Goodput ratio: 23/53][0.60 sec][Host: us.scan.worldofwarcraft.com][bytes ratio: -0.166 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 9/0 148/102 198/203 80/102][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 73/128 139/327 33/115][URL: us.scan.worldofwarcraft.com/update/Launcher.txt][StatusCode: 200][Content-Type: text/plain][PLAIN TEXT (GET /update/Launcher.txt HTTP/1)][Plen Bins: 0,0,50,0,0,0,0,0,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]
22 UDP 192.168.1.100:55468 <-> 192.168.1.254:53 [proto: 5/DNS][cat: Network/14][2 pkts/168 bytes <-> 2 pkts/388 bytes][Goodput ratio: 50/78][0.09 sec][Host: bnetcmsus-a.akamaihd.net][2.228.46.112][PLAIN TEXT (bnetcmsus)][Plen Bins: 0,50,0,0,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]
- 23 UDP 173.194.40.22:443 <-> 192.168.1.100:53568 [proto: 188.126/QUIC.Google][cat: Web/5][3 pkts/243 bytes <-> 3 pkts/232 bytes][Goodput ratio: 48/45][28.94 sec][bytes ratio: 0.023 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 13855/13940 14457/14457 15059/14974 602/517][Pkt Len c2s/s2c min/avg/max/stddev: 77/66 81/77 83/83 3/8][Plen Bins: 16,83,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]
+ 23 UDP 173.194.40.22:443 <-> 192.168.1.100:53568 [proto: 126/Google][cat: Web/5][3 pkts/243 bytes <-> 3 pkts/232 bytes][Goodput ratio: 48/45][28.94 sec][bytes ratio: 0.023 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 13855/13940 14457/14457 15059/14974 602/517][Pkt Len c2s/s2c min/avg/max/stddev: 77/66 81/77 83/83 3/8][Plen Bins: 16,83,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]
24 UDP 192.168.1.100:58851 <-> 192.168.1.254:53 [proto: 5/DNS][cat: Network/14][2 pkts/173 bytes <-> 2 pkts/282 bytes][Goodput ratio: 51/70][0.05 sec][Host: 110.212.58.216.in-addr.arpa][::][Plen Bins: 0,50,25,25,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]
25 UDP 192.168.1.100:60026 <-> 192.168.1.254:53 [proto: 5/DNS][cat: Network/14][2 pkts/154 bytes <-> 2 pkts/288 bytes][Goodput ratio: 45/71][0.08 sec][Host: llnw.blizzard.com][87.248.221.254][PLAIN TEXT (blizzard)][Plen Bins: 0,50,0,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]
26 UDP 192.168.1.100:58818 <-> 192.168.1.254:53 [proto: 5/DNS][cat: Network/14][2 pkts/172 bytes <-> 2 pkts/260 bytes][Goodput ratio: 51/67][0.06 sec][Host: 100.1.168.192.in-addr.arpa][::][PLAIN TEXT (dynect)][Plen Bins: 0,50,25,25,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/result/viber.pcap.out b/tests/result/viber.pcap.out
index 044b1f39e..236bbab7f 100644
--- a/tests/result/viber.pcap.out
+++ b/tests/result/viber.pcap.out
@@ -33,7 +33,7 @@ JA3 Host Stats:
18 UDP 192.168.0.17:44376 <-> 192.168.0.15:53 [proto: 5/DNS][cat: Network/14][1 pkts/82 bytes <-> 1 pkts/183 bytes][Goodput ratio: 48/77][0.03 sec][Host: venetia.iad.appboy.com][151.101.1.130][PLAIN TEXT (venetia)][Plen Bins: 0,50,0,0,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]
19 UDP 192.168.0.17:37418 <-> 192.168.0.15:53 [proto: 5.144/DNS.Viber][cat: Chat/9][1 pkts/79 bytes <-> 1 pkts/185 bytes][Goodput ratio: 46/77][0.12 sec][Host: media.cdn.viber.com][54.230.93.96][PLAIN TEXT (cloudfront)][Plen Bins: 0,50,0,0,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]
20 UDP 192.168.0.17:40445 <-> 192.168.0.15:53 [proto: 5.144/DNS.Viber][cat: Chat/9][1 pkts/78 bytes <-> 1 pkts/185 bytes][Goodput ratio: 46/77][0.03 sec][Host: dl-media.viber.com][54.230.93.53][PLAIN TEXT (cloudfront)][Plen Bins: 0,50,0,0,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]
- 21 UDP 192.168.0.17:41993 <-> 172.217.23.106:443 [proto: 188.126/QUIC.Google][cat: Web/5][2 pkts/130 bytes <-> 1 pkts/64 bytes][Goodput ratio: 35/34][0.00 sec][Plen Bins: 100,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,0]
+ 21 UDP 192.168.0.17:41993 <-> 172.217.23.106:443 [proto: 126/Google][cat: Web/5][2 pkts/130 bytes <-> 1 pkts/64 bytes][Goodput ratio: 35/34][0.00 sec][Plen Bins: 100,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,0]
22 UDP 192.168.0.17:35331 <-> 192.168.0.15:53 [proto: 5/DNS][cat: Network/14][1 pkts/79 bytes <-> 1 pkts/95 bytes][Goodput ratio: 46/55][0.02 sec][Host: app-measurement.com][172.217.23.78][PLAIN TEXT (measurement)][Plen Bins: 0,100,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]
23 UDP 192.168.0.17:50097 <-> 192.168.0.15:53 [proto: 5.126/DNS.Google][cat: Web/5][1 pkts/74 bytes <-> 1 pkts/90 bytes][Goodput ratio: 43/53][0.00 sec][Host: www.google.com][216.58.205.100][PLAIN TEXT (google)][Plen Bins: 0,100,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]
24 ICMPV6 [fe80::3207:4dff:fea3:5fa7]:0 -> [ff02::2]:0 [proto: 102/ICMPV6][cat: Network/14][2 pkts/140 bytes -> 0 pkts/0 bytes][Goodput ratio: 11/0][< 1 sec][Plen Bins: 100,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,0]
diff --git a/tests/result/weibo.pcap.out b/tests/result/weibo.pcap.out
index 19f20a90c..d5e20b164 100644
--- a/tests/result/weibo.pcap.out
+++ b/tests/result/weibo.pcap.out
@@ -17,8 +17,8 @@ JA3 Host Stats:
5 TCP 192.168.1.105:35805 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][21 pkts/2323 bytes <-> 20 pkts/20922 bytes][Goodput ratio: 37/94][1.37 sec][Host: img.t.sinajs.cn][bytes ratio: -0.800 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 72/75 375/438 116/123][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 111/1046 525/1502 127/557][URL: img.t.sinajs.cn/t6/skin/default/skin.css?version=201605130537][StatusCode: 200][Content-Type: text/css][User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36][Risk: ** HTTP Suspicious User-Agent **][PLAIN TEXT (GET /t6/skin/default/skin.css)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,5,10,5,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,58,0,0,0]
6 TCP 192.168.1.105:35809 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][18 pkts/1681 bytes <-> 17 pkts/20680 bytes][Goodput ratio: 28/95][0.56 sec][Host: img.t.sinajs.cn][bytes ratio: -0.850 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 32/38 252/181 64/51][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 93/1216 539/1502 108/526][URL: img.t.sinajs.cn/t6/style/images/common/font/wbficon.woff?id=201605111746][StatusCode: 200][Content-Type: application/x-font-woff][User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36][Risk: ** HTTP Suspicious User-Agent **][PLAIN TEXT (GET /t6/style/images/common/fon)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,12,6,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,81,0,0,0]
7 TCP 192.168.1.105:35806 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][7 pkts/946 bytes <-> 6 pkts/3755 bytes][Goodput ratio: 49/89][0.23 sec][Host: img.t.sinajs.cn][bytes ratio: -0.598 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/1 45/42 163/160 63/68][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 135/626 530/1502 161/505][URL: img.t.sinajs.cn/t6/style/images/global_nav/WB_logo_b.png][StatusCode: 200][Content-Type: image/png][User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36][Risk: ** HTTP Suspicious User-Agent **][PLAIN TEXT (GET /t6/style/images/global)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,20,0,20,0,0,0,0,20,0,0,0,0,0,0,0,0,20,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,20,0,0,0]
- 8 UDP 192.168.1.105:53656 <-> 216.58.210.227:443 [proto: 188.126/QUIC.Google][cat: Web/5][8 pkts/1301 bytes <-> 6 pkts/873 bytes][Goodput ratio: 74/71][1.60 sec][bytes ratio: 0.197 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 76/2 266/14 1385/29 503/13][Pkt Len c2s/s2c min/avg/max/stddev: 67/74 163/146 406/433 122/129][Plen Bins: 21,35,14,0,0,7,0,0,7,0,0,7,7,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]
- 9 UDP 216.58.210.14:443 <-> 192.168.1.105:49361 [proto: 188.126/QUIC.Google][cat: Web/5][5 pkts/963 bytes <-> 4 pkts/981 bytes][Goodput ratio: 78/83][0.69 sec][bytes ratio: -0.009 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 171/228 626/662 264/307][Pkt Len c2s/s2c min/avg/max/stddev: 77/85 193/245 353/660 93/241][Plen Bins: 0,33,11,11,11,11,0,0,0,11,0,0,0,0,0,0,0,0,0,11,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]
+ 8 UDP 192.168.1.105:53656 <-> 216.58.210.227:443 [proto: 126/Google][cat: Web/5][8 pkts/1301 bytes <-> 6 pkts/873 bytes][Goodput ratio: 74/71][1.60 sec][bytes ratio: 0.197 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 76/2 266/14 1385/29 503/13][Pkt Len c2s/s2c min/avg/max/stddev: 67/74 163/146 406/433 122/129][Plen Bins: 21,35,14,0,0,7,0,0,7,0,0,7,7,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]
+ 9 UDP 216.58.210.14:443 <-> 192.168.1.105:49361 [proto: 126/Google][cat: Web/5][5 pkts/963 bytes <-> 4 pkts/981 bytes][Goodput ratio: 78/83][0.69 sec][bytes ratio: -0.009 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 171/228 626/662 264/307][Pkt Len c2s/s2c min/avg/max/stddev: 77/85 193/245 353/660 93/241][Plen Bins: 0,33,11,11,11,11,0,0,0,11,0,0,0,0,0,0,0,0,0,11,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]
10 TCP 192.168.1.105:59119 <-> 114.134.80.162:80 [proto: 7/HTTP][cat: Web/5][5 pkts/736 bytes <-> 4 pkts/863 bytes][Goodput ratio: 61/73][1.05 sec][Host: weibo.com][bytes ratio: -0.079 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/347 176/348 353/348 174/0][Pkt Len c2s/s2c min/avg/max/stddev: 54/54 147/216 500/689 177/273][URL: weibo.com/login.php?lang=en-us][StatusCode: 301][Content-Type: text/html][User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36][Risk: ** HTTP Suspicious User-Agent **][PLAIN TEXT (GET /login.php)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,50,0,0,0,0,0,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]
11 TCP 192.168.1.105:35811 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][3 pkts/604 bytes <-> 2 pkts/140 bytes][Goodput ratio: 66/0][0.46 sec][Host: js.t.sinajs.cn][URL: js.t.sinajs.cn/t5/register/js/v6/pl/base.js?version=201605130537][StatusCode: 0][User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36][Risk: ** HTTP Suspicious User-Agent **][PLAIN TEXT (KGET /t)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,100,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]
12 TCP 192.168.1.105:42275 <-> 222.73.28.96:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][3 pkts/610 bytes <-> 1 pkts/66 bytes][Goodput ratio: 70/0][0.38 sec][Host: u1.img.mobile.sina.cn][URL: u1.img.mobile.sina.cn/public/files/image/620x300_img5653d57c6dab2.png][StatusCode: 0][User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36][Risk: ** HTTP Suspicious User-Agent **][PLAIN TEXT (GET /public/files/image/620)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,100,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]