From 9d620d160c02c607ee8abf780cf3e3d9c5ce9062 Mon Sep 17 00:00:00 2001 From: Paul Aitken Date: Tue, 7 Jan 2020 13:56:59 +0000 Subject: [PATCH 01/11] Remove libndpi-wireshark package Remove the libndpi-wireshark package in order to remove the build-dependency on libwireshark-dev >= 3.0 introduced in cce737a. Part of VRVDR-47719 --- debian/control | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/debian/control b/debian/control index 6db015d..6ad8ee5 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,6 @@ Build-Depends: dpkg-dev (>= 1.16.1.1), libjson-c-dev, libpcap-dev, - libwireshark-dev (>= 3.0~), pkg-config, Standards-Version: 4.4.1 Homepage: http://www.ntop.org/products/ndpi/ @@ -73,24 +72,3 @@ Description: extensible deep packet inspection library - ndpiReader (e.g. detect http non ports other than 80), and also the opposite. . This package contains the ndpiReader binary. - -Package: libndpi-wireshark -Architecture: any -Pre-Depends: ${misc:Pre-Depends} -Depends: libndpi-bin (>= ${binary:Version}), ${misc:Depends} -Multi-Arch: same -Description: extensible deep packet inspection library - wireshark dissector - nDPI is a ntop-maintained superset of the popular OpenDPI library. Released - under the LGPL license, its goal is to extend the original library by adding - new protocols that are otherwise available only on the paid version of - OpenDPI. - . - nDPI has also been modified to be suitable for traffic monitoring - applications, by disabling specific features that slow down the DPI engine - while being them un-necessary for network traffic monitoring. - . - With nDPI, it is possible to both detect known protocols on non-standard ports - (e.g. detect http non ports other than 80), and also the opposite. - . - This package contains the wireshark dissector based on nDPI. The dissector is - installed in the plugin directory for the current wireshark version. From 7a7e92b13eedd67248419b4104c63c1efc9b1747 Mon Sep 17 00:00:00 2001 From: Paul Aitken Date: Wed, 15 Jan 2020 08:44:08 +0000 Subject: [PATCH 02/11] Change nDPI names to suit the vyatta CLI debian patch to make the nDPI names more suitable for the vyatta CLI. Part of VRVDR-47719 --- debian/patches/series | 1 + debian/patches/vyatta-app-names.patch | 299 ++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 debian/patches/vyatta-app-names.patch diff --git a/debian/patches/series b/debian/patches/series index ab5d28c..4022f83 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -3,3 +3,4 @@ add-minor-version-to-soname.patch define-have-json-c.patch ndpireader-dynamic-link.patch additional-cppflags.patch +vyatta-app-names.patch diff --git a/debian/patches/vyatta-app-names.patch b/debian/patches/vyatta-app-names.patch new file mode 100644 index 0000000..102a9c1 --- /dev/null +++ b/debian/patches/vyatta-app-names.patch @@ -0,0 +1,299 @@ +From: Paul Aitken +Date: Wed, 15 Jan 2020 11:23:06 +0000 +Subject: Rename some applications to suit the vyatta CLI and for consistency. + +viz: Sina(Weibo) -> Weibo + MsSQL-TDS -> MsSQL_TDS + Whois-DAS -> Whois_DAS + Targus Dataspeed -> Targus_Dataspeed + Download-FileTransfer-FileSharing -> Download +--- + src/lib/ndpi_content_match.c.inc | 4 +-- + src/lib/ndpi_main.c | 8 +++--- + tests/result/bittorrent.pcap.out | 48 +++++++++++++++++------------------ + tests/result/bittorrent_ip.pcap.out | 4 +-- + tests/result/bittorrent_utp.pcap.out | 2 +- + tests/result/bt_search.pcap.out | 2 +- + tests/result/ftp.pcap.out | 4 +-- + tests/result/mssql_tds.pcap.out | 26 +++++++++---------- + tests/result/smpp_in_general.pcap.out | 2 +- + tests/result/weibo.pcap.out | 34 ++++++++++++------------- + tests/result/whatsappfiles.pcap.out | 4 +-- + 11 files changed, 69 insertions(+), 69 deletions(-) + +diff --git a/src/lib/ndpi_content_match.c.inc b/src/lib/ndpi_content_match.c.inc +index 0eed29d..e76ed4e 100644 +--- a/src/lib/ndpi_content_match.c.inc ++++ b/src/lib/ndpi_content_match.c.inc +@@ -8627,8 +8627,8 @@ static ndpi_protocol_match host_match[] = { + { ".qq.com", NULL, "\\.qq" TLD, "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".gtimg.com", NULL, "\\.gtimg" TLD, "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + +- { ".weibo.com", NULL, "\\.weibo" TLD, "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, +- { ".weibo.cn", NULL, NULL, "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, ++ { ".weibo.com", NULL, "\\.weibo" TLD, "Weibo", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, ++ { ".weibo.cn", NULL, NULL, "Weibo", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sinaimg.cn", NULL, "\\.sinaimg" TLD, "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sinajs.cn", NULL, "\\.sinajs" TLD, "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sina.cn", NULL, "\\.sina" TLD, "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, +diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c +index a72917b..1e50ac3 100644 +--- a/src/lib/ndpi_main.c ++++ b/src/lib/ndpi_main.c +@@ -1381,7 +1381,7 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MSSQL_TDS, + 0 /* can_have_a_subprotocol */, no_master, +- no_master, "MsSQL-TDS", NDPI_PROTOCOL_CATEGORY_DATABASE, ++ no_master, "MsSQL_TDS", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 1433, 1434, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_PPTP, +@@ -1588,7 +1588,7 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHOIS_DAS, + 0 /* can_have_a_subprotocol */, no_master, +- no_master, "Whois-DAS", NDPI_PROTOCOL_CATEGORY_NETWORK, ++ no_master, "Whois_DAS", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 43, 4343, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_COLLECTD, +@@ -1738,7 +1738,7 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TARGUS_GETDATA, + 0 /* can_have_a_subprotocol */, no_master, +- no_master, "Targus Dataspeed", NDPI_PROTOCOL_CATEGORY_NETWORK, ++ no_master, "Targus_Dataspeed", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 5001, 5201, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5001, 5201, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AMAZON_VIDEO, +@@ -1991,7 +1991,7 @@ static const char* categories[] = { + "DataTransfer", + "Web", + "SocialNetwork", +- "Download-FileTransfer-FileSharing", ++ "Download", + "Game", + "Chat", + "VoIP", +diff --git a/tests/result/bittorrent.pcap.out b/tests/result/bittorrent.pcap.out +index 750a8e8..df3d373 100644 +--- a/tests/result/bittorrent.pcap.out ++++ b/tests/result/bittorrent.pcap.out +@@ -1,26 +1,26 @@ + BitTorrent 299 305728 24 + +- 1 TCP 192.168.1.3:52915 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][17 pkts/2745 bytes <-> 193 pkts/282394 bytes][bytes ratio: -0.981 (Download)][IAT c2s/s2c min/avg/max/stddev: 12/0 319.4/29.6 779/919 241.2/94.6][Pkt Len c2s/s2c min/avg/max/stddev: 83/80 161.5/1463.2 242/1506 58.0/217.8][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 2 TCP 192.168.1.3:52895 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][4 pkts/583 bytes <-> 4 pkts/975 bytes][bytes ratio: -0.252 (Download)][IAT c2s/s2c min/avg/max/stddev: 132/72 958.7/2027.0 1966/3982 759.5/1955.0][Pkt Len c2s/s2c min/avg/max/stddev: 80/73 145.8/243.8 198/648 44.2/234.9][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 3 TCP 192.168.1.3:52914 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][4 pkts/640 bytes <-> 3 pkts/910 bytes][bytes ratio: -0.174 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 489/661 1178.3/883.0 1943/1105 596.0/222.0][Pkt Len c2s/s2c min/avg/max/stddev: 75/113 160.0/303.3 241/650 62.0/245.5][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 4 TCP 192.168.1.3:52907 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][2 pkts/583 bytes <-> 2 pkts/818 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 5 TCP 192.168.1.3:52927 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/582 bytes <-> 2 pkts/796 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 6 TCP 192.168.1.3:52897 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/510 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 7 TCP 192.168.1.3:52903 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/410 bytes <-> 3 pkts/851 bytes][bytes ratio: -0.350 (Download)][IAT c2s/s2c min/avg/max/stddev: 320/159 407.0/297.5 494/436 87.0/138.5][Pkt Len c2s/s2c min/avg/max/stddev: 80/80 136.7/283.7 196/601 47.4/227.4][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 8 TCP 192.168.1.3:52917 <-> 151.15.48.189:47001 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/455 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 9 TCP 192.168.1.3:52911 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/442 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 10 TCP 192.168.1.3:52921 <-> 95.234.159.16:41205 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/440 bytes <-> 2 pkts/772 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 11 TCP 192.168.1.3:52906 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 12 TCP 192.168.1.3:52922 <-> 95.237.193.34:11321 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 13 TCP 192.168.1.3:52887 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/430 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 14 TCP 192.168.1.3:52896 <-> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/409 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 15 TCP 192.168.1.3:52926 <-> 93.65.249.100:31336 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes <-> 2 pkts/796 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 16 TCP 192.168.1.3:52888 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes <-> 1 pkts/624 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 17 TCP 192.168.1.3:52902 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][2 pkts/349 bytes <-> 2 pkts/265 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 18 TCP 192.168.1.3:52912 <-> 151.72.255.163:59928 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/455 bytes <-> 1 pkts/157 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 19 TCP 192.168.1.3:52893 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 20 TCP 192.168.1.3:52894 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 21 TCP 192.168.1.3:52908 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 22 TCP 192.168.1.3:52909 -> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 23 TCP 192.168.1.3:52910 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +- 24 TCP 192.168.1.3:52925 -> 93.65.227.100:19116 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 1 TCP 192.168.1.3:52915 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download/7][17 pkts/2745 bytes <-> 193 pkts/282394 bytes][bytes ratio: -0.981 (Download)][IAT c2s/s2c min/avg/max/stddev: 12/0 319.4/29.6 779/919 241.2/94.6][Pkt Len c2s/s2c min/avg/max/stddev: 83/80 161.5/1463.2 242/1506 58.0/217.8][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 2 TCP 192.168.1.3:52895 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download/7][4 pkts/583 bytes <-> 4 pkts/975 bytes][bytes ratio: -0.252 (Download)][IAT c2s/s2c min/avg/max/stddev: 132/72 958.7/2027.0 1966/3982 759.5/1955.0][Pkt Len c2s/s2c min/avg/max/stddev: 80/73 145.8/243.8 198/648 44.2/234.9][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 3 TCP 192.168.1.3:52914 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download/7][4 pkts/640 bytes <-> 3 pkts/910 bytes][bytes ratio: -0.174 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 489/661 1178.3/883.0 1943/1105 596.0/222.0][Pkt Len c2s/s2c min/avg/max/stddev: 75/113 160.0/303.3 241/650 62.0/245.5][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 4 TCP 192.168.1.3:52907 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download/7][2 pkts/583 bytes <-> 2 pkts/818 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 5 TCP 192.168.1.3:52927 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download/7][3 pkts/582 bytes <-> 2 pkts/796 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 6 TCP 192.168.1.3:52897 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download/7][3 pkts/510 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 7 TCP 192.168.1.3:52903 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download/7][3 pkts/410 bytes <-> 3 pkts/851 bytes][bytes ratio: -0.350 (Download)][IAT c2s/s2c min/avg/max/stddev: 320/159 407.0/297.5 494/436 87.0/138.5][Pkt Len c2s/s2c min/avg/max/stddev: 80/80 136.7/283.7 196/601 47.4/227.4][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 8 TCP 192.168.1.3:52917 <-> 151.15.48.189:47001 [proto: 37/BitTorrent][cat: Download/7][3 pkts/455 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 9 TCP 192.168.1.3:52911 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download/7][3 pkts/442 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 10 TCP 192.168.1.3:52921 <-> 95.234.159.16:41205 [proto: 37/BitTorrent][cat: Download/7][3 pkts/440 bytes <-> 2 pkts/772 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 11 TCP 192.168.1.3:52906 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 12 TCP 192.168.1.3:52922 <-> 95.237.193.34:11321 [proto: 37/BitTorrent][cat: Download/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 13 TCP 192.168.1.3:52887 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download/7][3 pkts/430 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 14 TCP 192.168.1.3:52896 <-> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download/7][3 pkts/409 bytes <-> 2 pkts/771 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 15 TCP 192.168.1.3:52926 <-> 93.65.249.100:31336 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes <-> 2 pkts/796 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 16 TCP 192.168.1.3:52888 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes <-> 1 pkts/624 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 17 TCP 192.168.1.3:52902 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download/7][2 pkts/349 bytes <-> 2 pkts/265 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 18 TCP 192.168.1.3:52912 <-> 151.72.255.163:59928 [proto: 37/BitTorrent][cat: Download/7][3 pkts/455 bytes <-> 1 pkts/157 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 19 TCP 192.168.1.3:52893 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 20 TCP 192.168.1.3:52894 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 21 TCP 192.168.1.3:52908 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 22 TCP 192.168.1.3:52909 -> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 23 TCP 192.168.1.3:52910 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] ++ 24 TCP 192.168.1.3:52925 -> 93.65.227.100:19116 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)] +diff --git a/tests/result/bittorrent_ip.pcap.out b/tests/result/bittorrent_ip.pcap.out +index ecce10c..9102528 100644 +--- a/tests/result/bittorrent_ip.pcap.out ++++ b/tests/result/bittorrent_ip.pcap.out +@@ -1,4 +1,4 @@ + BitTorrent 479 508018 2 + +- 1 TCP 77.222.174.20:2866 <-> 10.0.0.14:46610 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][305 pkts/461770 bytes <-> 126 pkts/8316 bytes][bytes ratio: 0.965 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 5.2/5.7 879/56 54.4/10.5][Pkt Len c2s/s2c min/avg/max/stddev: 1514/66 1514.0/66.0 1514/66 0.0/0.0][PLAIN TEXT (n.m Hh)] +- 2 TCP 185.56.20.36:53646 <-> 10.0.0.14:35030 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][25 pkts/36414 bytes <-> 23 pkts/1518 bytes][bytes ratio: 0.920 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 10.4/10.7 83/83 23.3/24.4][Pkt Len c2s/s2c min/avg/max/stddev: 78/66 1456.6/66.0 1514/66 281.4/0.0][PLAIN TEXT (@RgmZT)] ++ 1 TCP 77.222.174.20:2866 <-> 10.0.0.14:46610 [proto: 37/BitTorrent][cat: Download/7][305 pkts/461770 bytes <-> 126 pkts/8316 bytes][bytes ratio: 0.965 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 5.2/5.7 879/56 54.4/10.5][Pkt Len c2s/s2c min/avg/max/stddev: 1514/66 1514.0/66.0 1514/66 0.0/0.0][PLAIN TEXT (n.m Hh)] ++ 2 TCP 185.56.20.36:53646 <-> 10.0.0.14:35030 [proto: 37/BitTorrent][cat: Download/7][25 pkts/36414 bytes <-> 23 pkts/1518 bytes][bytes ratio: 0.920 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 10.4/10.7 83/83 23.3/24.4][Pkt Len c2s/s2c min/avg/max/stddev: 78/66 1456.6/66.0 1514/66 281.4/0.0][PLAIN TEXT (@RgmZT)] +diff --git a/tests/result/bittorrent_utp.pcap.out b/tests/result/bittorrent_utp.pcap.out +index 0f2229f..84182fb 100644 +--- a/tests/result/bittorrent_utp.pcap.out ++++ b/tests/result/bittorrent_utp.pcap.out +@@ -1,3 +1,3 @@ + BitTorrent 86 41489 1 + +- 1 UDP 82.243.113.43:64969 <-> 192.168.1.5:40959 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][47 pkts/36653 bytes <-> 39 pkts/4836 bytes][bytes ratio: 0.767 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/11 222.5/425.0 4392/4641 700.8/934.4][Pkt Len c2s/s2c min/avg/max/stddev: 62/62 779.9/124.0 1514/519 608.8/123.2][PLAIN TEXT (hash20)] ++ 1 UDP 82.243.113.43:64969 <-> 192.168.1.5:40959 [proto: 37/BitTorrent][cat: Download/7][47 pkts/36653 bytes <-> 39 pkts/4836 bytes][bytes ratio: 0.767 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/11 222.5/425.0 4392/4641 700.8/934.4][Pkt Len c2s/s2c min/avg/max/stddev: 62/62 779.9/124.0 1514/519 608.8/123.2][PLAIN TEXT (hash20)] +diff --git a/tests/result/bt_search.pcap.out b/tests/result/bt_search.pcap.out +index 4aed6f1..918618a 100644 +--- a/tests/result/bt_search.pcap.out ++++ b/tests/result/bt_search.pcap.out +@@ -1,3 +1,3 @@ + BitTorrent 2 322 1 + +- 1 UDP 192.168.0.102:6771 -> 239.192.152.143:6771 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][2 pkts/322 bytes -> 0 pkts/0 bytes][PLAIN TEXT (SEARCH )] ++ 1 UDP 192.168.0.102:6771 -> 239.192.152.143:6771 [proto: 37/BitTorrent][cat: Download/7][2 pkts/322 bytes -> 0 pkts/0 bytes][PLAIN TEXT (SEARCH )] +diff --git a/tests/result/ftp.pcap.out b/tests/result/ftp.pcap.out +index 1e3d860..f80778d 100644 +--- a/tests/result/ftp.pcap.out ++++ b/tests/result/ftp.pcap.out +@@ -2,8 +2,8 @@ Unknown 1115 1122198 1 + FTP_CONTROL 68 5571 1 + FTP_DATA 9 1819 1 + +- 1 TCP 192.168.1.212:50694 <-> 90.130.70.73:21 [proto: 1/FTP_CONTROL][cat: Download-FileTransfer-FileSharing/7][41 pkts/2892 bytes <-> 27 pkts/2679 bytes][bytes ratio: 0.038 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 235.6/108.5 4743/1377 848.9/304.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 70.5/99.2 96/307 6.7/44.9][PLAIN TEXT (vsFTPd 3.0.3)] +- 2 TCP 192.168.1.212:50695 <-> 90.130.70.73:25685 [proto: 175/FTP_DATA][cat: Download-FileTransfer-FileSharing/7][5 pkts/342 bytes <-> 4 pkts/1477 bytes][bytes ratio: -0.624 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/28 14.2/28.5 29/29 14.3/0.5][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 68.4/369.2 78/1271 4.8/520.6][PLAIN TEXT ( 1 0 0 1073741)] ++ 1 TCP 192.168.1.212:50694 <-> 90.130.70.73:21 [proto: 1/FTP_CONTROL][cat: Download/7][41 pkts/2892 bytes <-> 27 pkts/2679 bytes][bytes ratio: 0.038 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 235.6/108.5 4743/1377 848.9/304.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 70.5/99.2 96/307 6.7/44.9][PLAIN TEXT (vsFTPd 3.0.3)] ++ 2 TCP 192.168.1.212:50695 <-> 90.130.70.73:25685 [proto: 175/FTP_DATA][cat: Download/7][5 pkts/342 bytes <-> 4 pkts/1477 bytes][bytes ratio: -0.624 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/28 14.2/28.5 29/29 14.3/0.5][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 68.4/369.2 78/1271 4.8/520.6][PLAIN TEXT ( 1 0 0 1073741)] + + + Undetected flows: +diff --git a/tests/result/mssql_tds.pcap.out b/tests/result/mssql_tds.pcap.out +index 347cb1a..da19b27 100644 +--- a/tests/result/mssql_tds.pcap.out ++++ b/tests/result/mssql_tds.pcap.out +@@ -1,14 +1,14 @@ +-MsSQL-TDS 38 16260 12 ++MsSQL_TDS 38 16260 12 + +- 1 TCP 10.111.111.111:6666 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][7 pkts/8717 bytes -> 0 pkts/0 bytes][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 0.0/0.0 0/0 0.0/0.0][Pkt Len c2s/s2c min/avg/max/stddev: 393/0 1245.3/0.0 1514/0 435.7/0.0] +- 2 TCP 10.111.111.111:5555 <-> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][10 pkts/1552 bytes <-> 7 pkts/1521 bytes][bytes ratio: 0.010 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 31/28 866.9/1024.0 1890/2071 763.3/864.5][Pkt Len c2s/s2c min/avg/max/stddev: 60/88 155.2/217.3 307/492 89.6/168.6][PLAIN TEXT (first )] +- 3 TCP 10.111.111.111:1111 <-> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][2 pkts/614 bytes <-> 2 pkts/524 bytes] +- 4 TCP 10.111.111.111:4444 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/1136 bytes -> 0 pkts/0 bytes] +- 5 TCP 10.111.111.111:7777 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/425 bytes -> 0 pkts/0 bytes] +- 6 TCP 10.111.111.111:33333 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/374 bytes -> 0 pkts/0 bytes] +- 7 TCP 10.111.111.111:22222 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/322 bytes -> 0 pkts/0 bytes] +- 8 TCP 10.111.111.111:9999 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/272 bytes -> 0 pkts/0 bytes][PLAIN TEXT (ABCDEFGHIJKLMNOPQ)] +- 9 TCP 10.111.111.111:11111 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/253 bytes -> 0 pkts/0 bytes] +- 10 TCP 10.111.111.111:3333 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/239 bytes -> 0 pkts/0 bytes] +- 11 TCP 10.111.111.111:2222 <-> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/98 bytes <-> 1 pkts/71 bytes] +- 12 TCP 10.111.111.111:8888 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/142 bytes -> 0 pkts/0 bytes] ++ 1 TCP 10.111.111.111:6666 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][7 pkts/8717 bytes -> 0 pkts/0 bytes][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 0.0/0.0 0/0 0.0/0.0][Pkt Len c2s/s2c min/avg/max/stddev: 393/0 1245.3/0.0 1514/0 435.7/0.0] ++ 2 TCP 10.111.111.111:5555 <-> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][10 pkts/1552 bytes <-> 7 pkts/1521 bytes][bytes ratio: 0.010 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 31/28 866.9/1024.0 1890/2071 763.3/864.5][Pkt Len c2s/s2c min/avg/max/stddev: 60/88 155.2/217.3 307/492 89.6/168.6][PLAIN TEXT (first )] ++ 3 TCP 10.111.111.111:1111 <-> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][2 pkts/614 bytes <-> 2 pkts/524 bytes] ++ 4 TCP 10.111.111.111:4444 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/1136 bytes -> 0 pkts/0 bytes] ++ 5 TCP 10.111.111.111:7777 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/425 bytes -> 0 pkts/0 bytes] ++ 6 TCP 10.111.111.111:33333 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/374 bytes -> 0 pkts/0 bytes] ++ 7 TCP 10.111.111.111:22222 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/322 bytes -> 0 pkts/0 bytes] ++ 8 TCP 10.111.111.111:9999 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/272 bytes -> 0 pkts/0 bytes][PLAIN TEXT (ABCDEFGHIJKLMNOPQ)] ++ 9 TCP 10.111.111.111:11111 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/253 bytes -> 0 pkts/0 bytes] ++ 10 TCP 10.111.111.111:3333 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/239 bytes -> 0 pkts/0 bytes] ++ 11 TCP 10.111.111.111:2222 <-> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/98 bytes <-> 1 pkts/71 bytes] ++ 12 TCP 10.111.111.111:8888 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/142 bytes -> 0 pkts/0 bytes] +diff --git a/tests/result/smpp_in_general.pcap.out b/tests/result/smpp_in_general.pcap.out +index bec09eb..c900bd7 100644 +--- a/tests/result/smpp_in_general.pcap.out ++++ b/tests/result/smpp_in_general.pcap.out +@@ -1,3 +1,3 @@ + SMPP 17 1144 1 + +- 1 TCP 10.226.202.118:1770 <-> 10.226.202.53:9000 [proto: 207/SMPP][cat: Download-FileTransfer-FileSharing/7][10 pkts/670 bytes <-> 7 pkts/474 bytes][bytes ratio: 0.171 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 3848.2/7230.0 28802/28906 9451.2/12514.6][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 67.0/67.7 104/79 17.3/7.3][PLAIN TEXT (password)] ++ 1 TCP 10.226.202.118:1770 <-> 10.226.202.53:9000 [proto: 207/SMPP][cat: Download/7][10 pkts/670 bytes <-> 7 pkts/474 bytes][bytes ratio: 0.171 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 3848.2/7230.0 28802/28906 9451.2/12514.6][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 67.0/67.7 104/79 17.3/7.3][PLAIN TEXT (password)] +diff --git a/tests/result/weibo.pcap.out b/tests/result/weibo.pcap.out +index 34bd430..39e27e1 100644 +--- a/tests/result/weibo.pcap.out ++++ b/tests/result/weibo.pcap.out +@@ -3,33 +3,33 @@ HTTP 19 2275 5 + TLS 15 1234 10 + Google 33 4778 7 + Amazon 2 132 1 +-Sina(Weibo) 419 258077 16 ++Weibo 419 258077 16 + + JA3 Host Stats: + IP Address # JA3C + 1 192.168.1.105 1 + + +- 1 TCP 192.168.1.105:35803 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][52 pkts/5367 bytes <-> 54 pkts/71536 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.860 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 29.0/29.3 400/372 66.4/64.0][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 103.2/1324.7 533/4374 116.5/822.8][URL: img.t.sinajs.cn/t6/style/css/module/base/frame.css?version=201605130537][StatusCode: 200][PLAIN TEXT (GET /t6/style/css/module/base/f)] +- 2 TCP 192.168.1.105:35804 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][32 pkts/3624 bytes <-> 40 pkts/50657 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.866 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 47.7/38.7 314/338 88.7/81.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 113.2/1266.4 549/2938 132.2/620.2][URL: img.t.sinajs.cn/t6/style/css/module/combination/comb_login.css?version=201605130537][StatusCode: 200][PLAIN TEXT (GET /t6/style/css/module/combin)] +- 3 TCP 192.168.1.105:51698 <-> 93.188.134.137:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][40 pkts/3462 bytes <-> 39 pkts/34030 bytes][Host: www.weibo.com][bytes ratio: -0.815 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 24.9/22.7 482/454 83.8/80.1][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 86.6/872.6 516/2938 69.2/915.2][URL: www.weibo.com/login.php?lang=en-us][StatusCode: 0][PLAIN TEXT (GET /login.php)] +- 4 TCP 192.168.1.105:35807 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][27 pkts/2298 bytes <-> 26 pkts/34170 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.874 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 23.0/21.8 183/162 50.2/47.0][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 85.1/1314.2 550/1502 91.2/448.1][URL: img.t.sinajs.cn/t6/style/images/growth/login/sprite_login.png?13434210384389][StatusCode: 200][PLAIN TEXT (GET /t6/style/images/growth/log)] +- 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][Host: img.t.sinajs.cn][bytes ratio: -0.800 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 71.8/74.7 375/438 115.7/123.1][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 110.6/1046.1 525/1502 126.8/556.9][URL: img.t.sinajs.cn/t6/skin/default/skin.css?version=201605130537][StatusCode: 200][PLAIN TEXT (GET /t6/skin/default/skin.css)] +- 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][Host: img.t.sinajs.cn][bytes ratio: -0.850 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 32.1/37.9 252/181 64.0/50.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 93.4/1216.5 539/1502 108.1/525.5][URL: img.t.sinajs.cn/t6/style/images/common/font/wbficon.woff?id=201605111746][StatusCode: 200][PLAIN TEXT (GET /t6/style/images/common/fon)] +- 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][Host: img.t.sinajs.cn][bytes ratio: -0.598 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/1 45.4/41.5 163/160 63.4/68.4][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 135.1/625.8 530/1502 161.3/505.1][URL: img.t.sinajs.cn/t6/style/images/global_nav/WB_logo_b.png][StatusCode: 200][PLAIN TEXT (GET /t6/style/images/global)] ++ 1 TCP 192.168.1.105:35803 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][52 pkts/5367 bytes <-> 54 pkts/71536 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.860 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 29.0/29.3 400/372 66.4/64.0][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 103.2/1324.7 533/4374 116.5/822.8][URL: img.t.sinajs.cn/t6/style/css/module/base/frame.css?version=201605130537][StatusCode: 200][PLAIN TEXT (GET /t6/style/css/module/base/f)] ++ 2 TCP 192.168.1.105:35804 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][32 pkts/3624 bytes <-> 40 pkts/50657 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.866 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 47.7/38.7 314/338 88.7/81.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 113.2/1266.4 549/2938 132.2/620.2][URL: img.t.sinajs.cn/t6/style/css/module/combination/comb_login.css?version=201605130537][StatusCode: 200][PLAIN TEXT (GET /t6/style/css/module/combin)] ++ 3 TCP 192.168.1.105:51698 <-> 93.188.134.137:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][40 pkts/3462 bytes <-> 39 pkts/34030 bytes][Host: www.weibo.com][bytes ratio: -0.815 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 24.9/22.7 482/454 83.8/80.1][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 86.6/872.6 516/2938 69.2/915.2][URL: www.weibo.com/login.php?lang=en-us][StatusCode: 0][PLAIN TEXT (GET /login.php)] ++ 4 TCP 192.168.1.105:35807 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][27 pkts/2298 bytes <-> 26 pkts/34170 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.874 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 23.0/21.8 183/162 50.2/47.0][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 85.1/1314.2 550/1502 91.2/448.1][URL: img.t.sinajs.cn/t6/style/images/growth/login/sprite_login.png?13434210384389][StatusCode: 200][PLAIN TEXT (GET /t6/style/images/growth/log)] ++ 5 TCP 192.168.1.105:35805 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][21 pkts/2323 bytes <-> 20 pkts/20922 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.800 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 71.8/74.7 375/438 115.7/123.1][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 110.6/1046.1 525/1502 126.8/556.9][URL: img.t.sinajs.cn/t6/skin/default/skin.css?version=201605130537][StatusCode: 200][PLAIN TEXT (GET /t6/skin/default/skin.css)] ++ 6 TCP 192.168.1.105:35809 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][18 pkts/1681 bytes <-> 17 pkts/20680 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.850 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 32.1/37.9 252/181 64.0/50.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 93.4/1216.5 539/1502 108.1/525.5][URL: img.t.sinajs.cn/t6/style/images/common/font/wbficon.woff?id=201605111746][StatusCode: 200][PLAIN TEXT (GET /t6/style/images/common/fon)] ++ 7 TCP 192.168.1.105:35806 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][7 pkts/946 bytes <-> 6 pkts/3755 bytes][Host: img.t.sinajs.cn][bytes ratio: -0.598 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/1 45.4/41.5 163/160 63.4/68.4][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 135.1/625.8 530/1502 161.3/505.1][URL: img.t.sinajs.cn/t6/style/images/global_nav/WB_logo_b.png][StatusCode: 200][PLAIN TEXT (GET /t6/style/images/global)] + 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][bytes ratio: 0.197 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 76/2 266.5/14.2 1385/29 502.8/13.3][Pkt Len c2s/s2c min/avg/max/stddev: 67/74 162.6/145.5 406/433 122.4/129.3] + 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][bytes ratio: -0.009 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 171.2/228.0 626/662 263.7/307.0][Pkt Len c2s/s2c min/avg/max/stddev: 77/85 192.6/245.2 353/660 93.4/241.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][Host: weibo.com][bytes ratio: -0.079 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/347 175.8/347.5 353/348 174.3/0.5][Pkt Len c2s/s2c min/avg/max/stddev: 54/54 147.2/215.8 500/689 176.6/273.3][PLAIN TEXT (GET /login.php)] +- 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][Host: js.t.sinajs.cn][URL: js.t.sinajs.cn/t5/register/js/v6/pl/base.js?version=201605130537][StatusCode: 0][PLAIN TEXT (KGET /t)] +- 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][Host: u1.img.mobile.sina.cn][URL: u1.img.mobile.sina.cn/public/files/image/620x300_img5653d57c6dab2.png][StatusCode: 0][PLAIN TEXT (GET /public/files/image/620)] ++ 11 TCP 192.168.1.105:35811 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][3 pkts/604 bytes <-> 2 pkts/140 bytes][Host: js.t.sinajs.cn][URL: js.t.sinajs.cn/t5/register/js/v6/pl/base.js?version=201605130537][StatusCode: 0][PLAIN TEXT (KGET /t)] ++ 12 TCP 192.168.1.105:42275 <-> 222.73.28.96:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][3 pkts/610 bytes <-> 1 pkts/66 bytes][Host: u1.img.mobile.sina.cn][URL: u1.img.mobile.sina.cn/public/files/image/620x300_img5653d57c6dab2.png][StatusCode: 0][PLAIN TEXT (GET /public/files/image/620)] + 13 TCP 192.168.1.105:50827 <-> 47.89.65.229:443 [proto: 91/TLS][cat: Web/5][3 pkts/382 bytes <-> 1 pkts/66 bytes][TLSv1][Client: g.alicdn.com][JA3C: 58e7f64db6e4fe4941dd9691d421196c][PLAIN TEXT (g.alicdn.com)] +- 14 UDP 192.168.1.105:53543 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/75 bytes <-> 1 pkts/191 bytes][Host: img.t.sinajs.cn] +- 15 UDP 192.168.1.105:41352 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/74 bytes <-> 1 pkts/190 bytes][Host: js.t.sinajs.cn] ++ 14 UDP 192.168.1.105:53543 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/75 bytes <-> 1 pkts/191 bytes][Host: img.t.sinajs.cn] ++ 15 UDP 192.168.1.105:41352 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/74 bytes <-> 1 pkts/190 bytes][Host: js.t.sinajs.cn] + 16 UDP 192.168.1.105:51440 <-> 192.168.1.1:53 [proto: 5/DNS][cat: Network/14][1 pkts/72 bytes <-> 1 pkts/171 bytes][Host: g.alicdn.com][PLAIN TEXT (alicdn)] + 17 UDP 192.168.1.105:33822 <-> 192.168.1.1:53 [proto: 5/DNS][cat: Network/14][1 pkts/76 bytes <-> 1 pkts/166 bytes][Host: login.taobao.com][PLAIN TEXT (taobao)] +- 18 UDP 192.168.1.105:18035 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/81 bytes <-> 1 pkts/159 bytes][Host: u1.img.mobile.sina.cn][PLAIN TEXT (mobile)] ++ 18 UDP 192.168.1.105:18035 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/81 bytes <-> 1 pkts/159 bytes][Host: u1.img.mobile.sina.cn][PLAIN TEXT (mobile)] + 19 UDP 192.168.1.105:50640 <-> 192.168.1.1:53 [proto: 5/DNS][cat: Network/14][1 pkts/77 bytes <-> 1 pkts/157 bytes][Host: acjstb.aliyun.com][PLAIN TEXT (alibabadns)] +- 20 UDP 192.168.1.105:7148 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/73 bytes <-> 1 pkts/142 bytes][Host: www.weibo.com] ++ 20 UDP 192.168.1.105:7148 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/73 bytes <-> 1 pkts/142 bytes][Host: www.weibo.com] + 21 TCP 192.168.1.105:35808 <-> 93.188.134.246:80 [proto: 7/HTTP][cat: Web/5][2 pkts/140 bytes <-> 1 pkts/74 bytes] + 22 TCP 192.168.1.105:50831 <-> 47.89.65.229:443 [proto: 91/TLS][cat: Web/5][2 pkts/128 bytes <-> 1 pkts/66 bytes] + 23 TCP 192.168.1.105:59120 <-> 114.134.80.162:80 [proto: 7/HTTP][cat: Web/5][2 pkts/128 bytes <-> 1 pkts/66 bytes] +@@ -42,7 +42,7 @@ JA3 Host Stats: + 30 TCP 192.168.1.105:40440 <-> 54.225.163.210:443 [proto: 91.178/TLS.Amazon][cat: Web/5][1 pkts/66 bytes <-> 1 pkts/66 bytes] + 31 TCP 192.168.1.105:58480 <-> 216.58.214.78:443 [proto: 91.126/TLS.Google][cat: Web/5][1 pkts/66 bytes <-> 1 pkts/66 bytes] + 32 TCP 192.168.1.105:58481 <-> 216.58.214.78:443 [proto: 91.126/TLS.Google][cat: Web/5][1 pkts/66 bytes <-> 1 pkts/66 bytes] +- 33 UDP 192.168.1.105:11798 -> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/77 bytes -> 0 pkts/0 bytes][Host: account.weibo.com][PLAIN TEXT (account)] ++ 33 UDP 192.168.1.105:11798 -> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/77 bytes -> 0 pkts/0 bytes][Host: account.weibo.com][PLAIN TEXT (account)] + 34 TCP 192.168.1.105:42280 -> 222.73.28.96:80 [proto: 7/HTTP][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes] + 35 TCP 192.168.1.105:47721 -> 140.205.170.63:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes] + 36 TCP 192.168.1.105:47723 -> 140.205.170.63:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes] +@@ -52,5 +52,5 @@ JA3 Host Stats: + 40 TCP 192.168.1.105:52271 -> 42.156.184.19:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes] + 41 TCP 192.168.1.105:52272 -> 42.156.184.19:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes] + 42 TCP 192.168.1.105:52274 -> 42.156.184.19:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes] +- 43 UDP 192.168.1.105:50533 -> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/74 bytes -> 0 pkts/0 bytes][Host: data.weibo.com] +- 44 UDP 192.168.1.105:16804 -> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/70 bytes -> 0 pkts/0 bytes][Host: c.weibo.cn] ++ 43 UDP 192.168.1.105:50533 -> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/74 bytes -> 0 pkts/0 bytes][Host: data.weibo.com] ++ 44 UDP 192.168.1.105:16804 -> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/70 bytes -> 0 pkts/0 bytes][Host: c.weibo.cn] +diff --git a/tests/result/whatsappfiles.pcap.out b/tests/result/whatsappfiles.pcap.out +index 1be8a6f..9c7e025 100644 +--- a/tests/result/whatsappfiles.pcap.out ++++ b/tests/result/whatsappfiles.pcap.out +@@ -5,5 +5,5 @@ JA3 Host Stats: + 1 192.168.2.29 2 + + +- 1 TCP 192.168.2.29:49698 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download-FileTransfer-FileSharing/7][132 pkts/9906 bytes <-> 178 pkts/237405 bytes][bytes ratio: -0.920 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 61.6/47.4 5775/5834 571.5/481.0][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 75.0/1333.7 583/1464 51.0/391.7][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 4e1a414c4f4c99097edd2a9a98e336c8][JA3S: 96681175a9547081bf3d417f1a572091][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256] +- 2 TCP 192.168.2.29:49674 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download-FileTransfer-FileSharing/7][161 pkts/189194 bytes <-> 149 pkts/15728 bytes][bytes ratio: 0.846 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 322.0/659.4 24639/64743 2277.8/6018.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/60 1175.1/105.6 1464/1464 540.1/167.3][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 107144b88827da5da9ed42d8776ccdc5][Server: *.whatsapp.net][JA3S: 2d1eb5817ece335c24904f516ad5da12][Organization: Facebook, Inc.][Certificate SHA-1: 10:54:EB:4A:A2:2A:42:2F:A6:1C:E7:9C:F4:84:10:7E:30:2E:56:BB][Validity: 2017-04-26 00:00:00 - 2018-05-01 12:00:00][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256] ++ 1 TCP 192.168.2.29:49698 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download/7][132 pkts/9906 bytes <-> 178 pkts/237405 bytes][bytes ratio: -0.920 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 61.6/47.4 5775/5834 571.5/481.0][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 75.0/1333.7 583/1464 51.0/391.7][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 4e1a414c4f4c99097edd2a9a98e336c8][JA3S: 96681175a9547081bf3d417f1a572091][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256] ++ 2 TCP 192.168.2.29:49674 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download/7][161 pkts/189194 bytes <-> 149 pkts/15728 bytes][bytes ratio: 0.846 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 322.0/659.4 24639/64743 2277.8/6018.6][Pkt Len c2s/s2c min/avg/max/stddev: 66/60 1175.1/105.6 1464/1464 540.1/167.3][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 107144b88827da5da9ed42d8776ccdc5][Server: *.whatsapp.net][JA3S: 2d1eb5817ece335c24904f516ad5da12][Organization: Facebook, Inc.][Certificate SHA-1: 10:54:EB:4A:A2:2A:42:2F:A6:1C:E7:9C:F4:84:10:7E:30:2E:56:BB][Validity: 2017-04-26 00:00:00 - 2018-05-01 12:00:00][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256] From be7dce47288215bac70304dcce78628f57f4c9a8 Mon Sep 17 00:00:00 2001 From: Paul Aitken Date: Wed, 8 Jan 2020 13:40:16 +0000 Subject: [PATCH 03/11] Add nDPI packages for application names and types Add nDPI packages for application names and types. Part of VRVDR-47719 --- debian/control | 21 +++++++++++++++++++ ...tta-ndpi-application-names-v1-yang.install | 1 + ...tta-ndpi-application-types-v1-yang.install | 1 + debian/vyatta-ndpi-show-name.install | 1 + 4 files changed, 24 insertions(+) create mode 100644 debian/vyatta-ndpi-application-names-v1-yang.install create mode 100644 debian/vyatta-ndpi-application-types-v1-yang.install create mode 100644 debian/vyatta-ndpi-show-name.install diff --git a/debian/control b/debian/control index 6ad8ee5..4effd03 100644 --- a/debian/control +++ b/debian/control @@ -72,3 +72,24 @@ Description: extensible deep packet inspection library - ndpiReader (e.g. detect http non ports other than 80), and also the opposite. . This package contains the ndpiReader binary. + +Package: vyatta-ndpi-application-names-v1-yang +Architecture: all +Section: contrib/admin +Depends: ${misc:Depends}, ${yang:Depends} +Description: ndpi application name yang definitions + The nDPI application name YANG definitions + +Package: vyatta-ndpi-application-types-v1-yang +Architecture: all +Section: contrib/admin +Depends: ${misc:Depends}, ${yang:Depends} +Description: ndpi application type yang definitions + The nDPI application type YANG definitions + +Package: vyatta-ndpi-show-name +Architecture: any +Section: contrib/admin +Depends: ${misc:Depends}, ${shlibs:Depends}, +Description: show information about nDPI names + Show nDPI name/type mapping information diff --git a/debian/vyatta-ndpi-application-names-v1-yang.install b/debian/vyatta-ndpi-application-names-v1-yang.install new file mode 100644 index 0000000..4f5530b --- /dev/null +++ b/debian/vyatta-ndpi-application-names-v1-yang.install @@ -0,0 +1 @@ +yang/vyatta-ndpi-application-names-v1.yang usr/share/configd/yang diff --git a/debian/vyatta-ndpi-application-types-v1-yang.install b/debian/vyatta-ndpi-application-types-v1-yang.install new file mode 100644 index 0000000..ea76bf4 --- /dev/null +++ b/debian/vyatta-ndpi-application-types-v1-yang.install @@ -0,0 +1 @@ +yang/vyatta-ndpi-application-types-v1.yang usr/share/configd/yang diff --git a/debian/vyatta-ndpi-show-name.install b/debian/vyatta-ndpi-show-name.install new file mode 100644 index 0000000..ea5126d --- /dev/null +++ b/debian/vyatta-ndpi-show-name.install @@ -0,0 +1 @@ +generation/ndpi_show_name /usr/lib/ndpi From 2ec1b973409c4446abd4ecb077c4b603d4f964f9 Mon Sep 17 00:00:00 2001 From: Paul Aitken Date: Wed, 15 Jan 2020 11:40:52 +0000 Subject: [PATCH 04/11] Vyatta nDPI Patches to add vyatta nDPI generation scripts and generated outputs. --- debian/patches/series | 2 + debian/patches/vyatta-generated.patch | 425 ++++++++++++++++++ debian/patches/vyatta-generation.patch | 582 +++++++++++++++++++++++++ 3 files changed, 1009 insertions(+) create mode 100644 debian/patches/vyatta-generated.patch create mode 100644 debian/patches/vyatta-generation.patch diff --git a/debian/patches/series b/debian/patches/series index 4022f83..354cb9c 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -4,3 +4,5 @@ define-have-json-c.patch ndpireader-dynamic-link.patch additional-cppflags.patch vyatta-app-names.patch +vyatta-generation.patch +vyatta-generated.patch diff --git a/debian/patches/vyatta-generated.patch b/debian/patches/vyatta-generated.patch new file mode 100644 index 0000000..b36c162 --- /dev/null +++ b/debian/patches/vyatta-generated.patch @@ -0,0 +1,425 @@ +From: Paul Aitken +Date: Wed, 15 Jan 2020 11:23:06 +0000 +Subject: Add generated vyatta yang for nDPI + +--- + yang/vyatta-ndpi-application-names-v1.yang | 312 +++++++++++++++++++++++++++++ + yang/vyatta-ndpi-application-types-v1.yang | 90 +++++++++ + 2 files changed, 402 insertions(+) + create mode 100644 yang/vyatta-ndpi-application-names-v1.yang + create mode 100644 yang/vyatta-ndpi-application-types-v1.yang + +diff --git a/yang/vyatta-ndpi-application-names-v1.yang b/yang/vyatta-ndpi-application-names-v1.yang +new file mode 100644 +index 0000000..e1290fd +--- /dev/null ++++ b/yang/vyatta-ndpi-application-names-v1.yang +@@ -0,0 +1,312 @@ ++module vyatta-ndpi-application-names-v1 { ++ namespace "urn:vyatta.com:mgmt:vyatta-ndpi-application-names:1"; ++ prefix vyatta-ndpi-application-names-v1; ++ ++ organization "AT&T Inc."; ++ contact "AT&T ++ Postal: 208 S. Akard Street ++ Dallas, TX 75202, USA ++ Web: www.att.com"; ++ ++ description ++ "Copyright (c) 2020, AT&T Intellectual Property ++ All rights reserved. ++ ++ Redistribution and use in source and binary forms, ++ with or without modification, are permitted provided ++ that the following conditions are met: ++ ++ 1. Redistributions of source code must retain the ++ above copyright notice, this list of conditions and ++ the following disclaimer. ++ 2. Redistributions in binary form must reproduce ++ the above copyright notice, this list of conditions ++ and the following disclaimer in the documentation ++ and/or other materials provided with the distribution. ++ 3. Neither the name of the copyright holder nor the ++ names of its contributors may be used to endorse or ++ promote products derived from this software without ++ specific prior written permission. ++ ++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ++ AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED ++ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ++ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ++ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, ++ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ++ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS ++ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER ++ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE ++ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ OF SUCH DAMAGE. ++ ++ SPDX-License-Identifier: BSD-3-Clause ++ ++ Application names associated with the nDPI engine. ++ ++ This file was auto-generated with ++ generation/application_names_yang.sh"; ++ ++ revision 2020-01-06 { ++ description "Initial revision based on nDPI 3.0.0."; ++ } ++ ++ typedef application-names { ++ /* New enums MUST only be added to the end of this list. ++ * Old enums MAY be deprecated or obsoleted, but must not be removed. ++ * It's not important to preserve alphabetical order. ++ */ ++ type enumeration { ++ enum "104"; ++ enum "afp"; ++ enum "aimini"; ++ enum "ajp"; ++ enum "amazon"; ++ enum "amazonvideo"; ++ enum "amqp"; ++ enum "apple"; ++ enum "appleicloud"; ++ enum "appleitunes"; ++ enum "applejuice"; ++ enum "applepush"; ++ enum "applestore"; ++ enum "armagetron"; ++ enum "ayiya"; ++ enum "battlefield"; ++ enum "bgp"; ++ enum "bittorrent"; ++ enum "bjnp"; ++ enum "checkmk"; ++ enum "ciscoskinny"; ++ enum "ciscovpn"; ++ enum "citrix"; ++ enum "cloudflare"; ++ enum "cnn"; ++ enum "coap"; ++ enum "collectd"; ++ enum "corba"; ++ enum "crossfire"; ++ enum "csgo"; ++ enum "datasaver"; ++ enum "dce_rpc"; ++ enum "deezer"; ++ enum "dhcp"; ++ enum "dhcpv6"; ++ enum "diameter"; ++ enum "direct_download_link"; ++ enum "directconnect"; ++ enum "dnp3"; ++ enum "dns"; ++ enum "dnscrypt"; ++ enum "dnsoverhttps"; ++ enum "dofus"; ++ enum "drda"; ++ enum "dropbox"; ++ enum "eaq"; ++ enum "ebay"; ++ enum "edonkey"; ++ enum "egp"; ++ enum "facebook"; ++ enum "facebookzero"; ++ enum "fasttrack"; ++ enum "fiesta"; ++ enum "fix"; ++ enum "florensia"; ++ enum "ftp_control"; ++ enum "ftp_data"; ++ enum "git"; ++ enum "github"; ++ enum "gmail"; ++ enum "gnutella"; ++ enum "google"; ++ enum "googledocs"; ++ enum "googledrive"; ++ enum "googlehangoutduo"; ++ enum "googlemaps"; ++ enum "googleplus"; ++ enum "googleservices"; ++ enum "gre"; ++ enum "gtp"; ++ enum "guildwars"; ++ enum "h323"; ++ enum "halflife2"; ++ enum "hotmail"; ++ enum "hotspotshield"; ++ enum "http"; ++ enum "http_activesync"; ++ enum "http_connect"; ++ enum "http_download"; ++ enum "http_proxy"; ++ enum "hulu"; ++ enum "iax"; ++ enum "icecast"; ++ enum "icmp"; ++ enum "icmpv6"; ++ enum "iflix"; ++ enum "igmp"; ++ enum "imap"; ++ enum "imaps"; ++ enum "imo"; ++ enum "instagram"; ++ enum "ip_in_ip"; ++ enum "ipp"; ++ enum "ipsec"; ++ enum "irc"; ++ enum "kakaotalk"; ++ enum "kakaotalk_voice"; ++ enum "kerberos"; ++ enum "kontiki"; ++ enum "lastfm"; ++ enum "ldap"; ++ enum "line"; ++ enum "linkedin"; ++ enum "lisp"; ++ enum "llmnr"; ++ enum "lotusnotes"; ++ enum "maplestory"; ++ enum "mdns"; ++ enum "megaco"; ++ enum "memcached"; ++ enum "messenger"; ++ enum "mgcp"; ++ enum "microsoft"; ++ enum "mining"; ++ enum "modbus"; ++ enum "mpeg_ts"; ++ enum "mqtt"; ++ enum "ms_onedrive"; ++ enum "msn"; ++ enum "mssql_tds"; ++ enum "mysql"; ++ enum "nestlogsink"; ++ enum "netbios"; ++ enum "netflix"; ++ enum "netflow"; ++ enum "nfs"; ++ enum "nintendo"; ++ enum "noe"; ++ enum "ntop"; ++ enum "ntp"; ++ enum "ocs"; ++ enum "office365"; ++ enum "ookla"; ++ enum "opendns"; ++ enum "openft"; ++ enum "openvpn"; ++ enum "oracle"; ++ enum "oscar"; ++ enum "ospf"; ++ enum "pando_media_booster"; ++ enum "pandora"; ++ enum "pastebin"; ++ enum "pcanywhere"; ++ enum "playstation"; ++ enum "playstore"; ++ enum "pop3"; ++ enum "pops"; ++ enum "postgresql"; ++ enum "pplive"; ++ enum "ppstream"; ++ enum "pptp"; ++ enum "ps_vue"; ++ enum "qq"; ++ enum "qqlive"; ++ enum "quic"; ++ enum "radius"; ++ enum "rdp"; ++ enum "redis"; ++ enum "remotescan"; ++ enum "rsync"; ++ enum "rtcp"; ++ enum "rtmp"; ++ enum "rtp"; ++ enum "rtsp"; ++ enum "rx"; ++ enum "sap"; ++ enum "sctp"; ++ enum "sflow"; ++ enum "shoutcast"; ++ enum "signal"; ++ enum "sip"; ++ enum "skype"; ++ enum "skypecall"; ++ enum "slack"; ++ enum "smbv1"; ++ enum "smbv23"; ++ enum "smpp"; ++ enum "smtp"; ++ enum "smtps"; ++ enum "snapchat"; ++ enum "snmp"; ++ enum "socks"; ++ enum "someip"; ++ enum "sopcast"; ++ enum "soulseek"; ++ enum "soundcloud"; ++ enum "spotify"; ++ enum "ssdp"; ++ enum "ssh"; ++ enum "starcraft"; ++ enum "stealthnet"; ++ enum "steam"; ++ enum "stun"; ++ enum "syslog"; ++ enum "targus_dataspeed"; ++ enum "teamspeak"; ++ enum "teamviewer"; ++ enum "telegram"; ++ enum "telnet"; ++ enum "teredo"; ++ enum "tftp"; ++ enum "thunder"; ++ enum "tiktok"; ++ enum "tinc"; ++ enum "tls"; ++ enum "tor"; ++ enum "truphone"; ++ enum "tuenti"; ++ enum "tvants"; ++ enum "tvuplayer"; ++ enum "twitch"; ++ enum "twitter"; ++ enum "ubntac2"; ++ enum "ubuntuone"; ++ enum "unencrypted_jabber"; ++ enum "unknown"; ++ enum "upnp"; ++ enum "usenet"; ++ enum "vevo"; ++ enum "vhua"; ++ enum "viber"; ++ enum "vmware"; ++ enum "vnc"; ++ enum "vrrp"; ++ enum "warcraft3"; ++ enum "waze"; ++ enum "webex"; ++ enum "wechat"; ++ enum "weibo"; ++ enum "whatsapp"; ++ enum "whatsappcall"; ++ enum "whatsappfiles"; ++ enum "whois_das"; ++ enum "wikipedia"; ++ enum "windowsupdate"; ++ enum "wireguard"; ++ enum "worldofkungfu"; ++ enum "worldofwarcraft"; ++ enum "xbox"; ++ enum "xdmcp"; ++ enum "yahoo"; ++ enum "youtube"; ++ enum "youtubeupload"; ++ enum "zattoo"; ++ enum "zeromq"; ++ enum "zoom"; ++ } ++ } ++} +diff --git a/yang/vyatta-ndpi-application-types-v1.yang b/yang/vyatta-ndpi-application-types-v1.yang +new file mode 100644 +index 0000000..1bad8ea +--- /dev/null ++++ b/yang/vyatta-ndpi-application-types-v1.yang +@@ -0,0 +1,90 @@ ++module vyatta-ndpi-application-types-v1 { ++ namespace "urn:vyatta.com:mgmt:vyatta-ndpi-application-types:1"; ++ prefix vyatta-ndpi-application-types-v1; ++ ++ organization "AT&T Inc."; ++ contact "AT&T ++ Postal: 208 S. Akard Street ++ Dallas, TX 75202, USA ++ Web: www.att.com"; ++ ++ description ++ "Copyright (c) 2020, AT&T Intellectual Property ++ All rights reserved. ++ ++ Redistribution and use in source and binary forms, ++ with or without modification, are permitted provided ++ that the following conditions are met: ++ ++ 1. Redistributions of source code must retain the ++ above copyright notice, this list of conditions and ++ the following disclaimer. ++ 2. Redistributions in binary form must reproduce ++ the above copyright notice, this list of conditions ++ and the following disclaimer in the documentation ++ and/or other materials provided with the distribution. ++ 3. Neither the name of the copyright holder nor the ++ names of its contributors may be used to endorse or ++ promote products derived from this software without ++ specific prior written permission. ++ ++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ++ AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED ++ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ++ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ++ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, ++ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ++ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS ++ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER ++ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE ++ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ OF SUCH DAMAGE. ++ ++ SPDX-License-Identifier: BSD-3-Clause ++ ++ Application types associated with the nDPI engine. ++ ++ This file was auto-generated with ++ generation/application_types_yang.sh"; ++ ++ revision 2020-01-06 { ++ description "Initial revision based on nDPI 3.0.0."; ++ } ++ ++ typedef application-types { ++ /* New enums MUST only be added to the end of this list. ++ * Old enums MAY be deprecated or obsoleted, but must not be removed. ++ * It's not important to preserve alphabetical order. ++ */ ++ type enumeration { ++ enum "chat"; ++ enum "cloud"; ++ enum "collaborative"; ++ enum "database"; ++ enum "datatransfer"; ++ enum "download"; ++ enum "email"; ++ enum "filesharing"; ++ enum "game"; ++ enum "media"; ++ enum "music"; ++ enum "network"; ++ enum "productivity"; ++ enum "remoteaccess"; ++ enum "rpc"; ++ enum "shopping"; ++ enum "socialnetwork"; ++ enum "softwareupdate"; ++ enum "streaming"; ++ enum "system"; ++ enum "video"; ++ enum "voip"; ++ enum "vpn"; ++ enum "web"; ++ } ++ } ++} diff --git a/debian/patches/vyatta-generation.patch b/debian/patches/vyatta-generation.patch new file mode 100644 index 0000000..f7883b8 --- /dev/null +++ b/debian/patches/vyatta-generation.patch @@ -0,0 +1,582 @@ +From: Paul Aitken +Date: Wed, 15 Jan 2020 11:23:06 +0000 +Subject: Add vyatta nDPI generation scripts + +--- + Makefile.am | 2 +- + configure.seed | 2 +- + generation/Makefile.in | 11 +++ + generation/README.md | 25 +++++++ + generation/application_names_yang.sh | 113 +++++++++++++++++++++++++++++ + generation/application_types_yang.sh | 113 +++++++++++++++++++++++++++++ + generation/applications_list.c | 135 +++++++++++++++++++++++++++++++++++ + generation/ndpi_show_name.c | 104 +++++++++++++++++++++++++++ + 8 files changed, 503 insertions(+), 2 deletions(-) + create mode 100644 generation/Makefile.in + create mode 100644 generation/README.md + create mode 100755 generation/application_names_yang.sh + create mode 100755 generation/application_types_yang.sh + create mode 100644 generation/applications_list.c + create mode 100644 generation/ndpi_show_name.c + +diff --git a/Makefile.am b/Makefile.am +index 4090817..502856d 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -1,5 +1,5 @@ + ACLOCAL_AMFLAGS = -I m4 +-SUBDIRS = src/lib example tests ++SUBDIRS = src/lib generation example tests + + pkgconfigdir = $(libdir)/pkgconfig + pkgconfig_DATA = libndpi.pc +diff --git a/configure.seed b/configure.seed +index a3cc646..5fc70f3 100644 +--- a/configure.seed ++++ b/configure.seed +@@ -141,7 +141,7 @@ AC_ARG_ENABLE([debug-messages], + + AC_CHECK_LIB(pthread, pthread_setaffinity_np, AC_DEFINE_UNQUOTED(HAVE_PTHREAD_SETAFFINITY_NP, 1, [libc has pthread_setaffinity_np])) + +-AC_CONFIG_FILES([Makefile example/Makefile example/Makefile.dpdk tests/Makefile libndpi.pc src/include/ndpi_define.h src/lib/Makefile python/Makefile]) ++AC_CONFIG_FILES([Makefile generation/Makefile example/Makefile example/Makefile.dpdk tests/Makefile libndpi.pc src/include/ndpi_define.h src/lib/Makefile python/Makefile]) + AC_CONFIG_HEADERS(src/include/ndpi_config.h) + AC_SUBST(GIT_RELEASE) + AC_SUBST(NDPI_MAJOR) +diff --git a/generation/Makefile.in b/generation/Makefile.in +new file mode 100644 +index 0000000..886e17e +--- /dev/null ++++ b/generation/Makefile.in +@@ -0,0 +1,11 @@ ++CC=@CC@ ++CFLAGS=-g -I../src/include @CFLAGS@ ++LIBNDPI=../src/lib/libndpi.a ++LDFLAGS=$(LIBNDPI) @PCAP_LIB@ -lpthread -lm @LDFLAGS@ ++ ++all: applications_list ndpi_show_name ++ ++%: %.c Makefile ++ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) ++ ++install: ; +diff --git a/generation/README.md b/generation/README.md +new file mode 100644 +index 0000000..c61e395 +--- /dev/null ++++ b/generation/README.md +@@ -0,0 +1,25 @@ ++# Generation scripts ++ ++Scripts to generate yang files and show-command scripts. ++ ++## Usage ++ ++The scripts are `application_names_yang.sh`, `application_types_yang.sh`. ++ ++These accept the `OLD_YANG_FILE` environment variable to use an old yang ++file's "header" information (description, copyright, revisions, etc) rather ++than starting from fresh. ++ ++These will prompt the user via stderr for revision notes, which are ++pulled from /dev/stdin, so can be piped in. ++ ++## Examples ++ ++Generate a new nDPI application names yang file, using `/path/base.yang` as a base, ++"Revision notes" as the notes for the revision, and writing it to ++`new_applications.yang`. ++`application_types_yang.sh` is invoked similarly. ++ ++``` ++echo "Revision Notes" | OLD_YANG_FILE=/path/base.yang ./application_names_yang.sh > new_applications.yang ++``` +diff --git a/generation/application_names_yang.sh b/generation/application_names_yang.sh +new file mode 100755 +index 0000000..770d65c +--- /dev/null ++++ b/generation/application_names_yang.sh +@@ -0,0 +1,113 @@ ++#!/bin/bash ++ ++# Copyright (c) 2020, AT&T Intellectual Property. All rights reserved. ++ ++# Create a new/updated vyatta-ndpi-applications-names.yang file, ++# writing to stdout. ++# ./application_list must exist. ++# ++# If OLD_YANG_FILE is set, the file will be read in, have the existing names ++# removed and used instead of a fresh file. ++# ++# Revision notes will be taken from stdin and added to the resulting yang with ++# today's date as a new revision. ++ ++# If OLD_YANG_FILE set, read it, cut out the pre-existing names and use the ++# remainder as the header. ++# Otherwise, use the heredoc. ++heading= ++if [ -z "${OLD_YANG_FILE}" ]; then ++ read -r -d '' heading << EndOfHeader ++module vyatta-ndpi-application-names-v1 { ++ namespace "urn:vyatta.com:mgmt:vyatta-ndpi-application-names:1"; ++ prefix vyatta-ndpi-application-names-v1; ++ ++ organization "AT&T Inc."; ++ contact "AT&T ++ Postal: 208 S. Akard Street ++ Dallas, TX 75202, USA ++ Web: www.att.com"; ++ ++ description ++ "Copyright (c) 2020, AT&T Intellectual Property ++ All rights reserved. ++ ++ Redistribution and use in source and binary forms, ++ with or without modification, are permitted provided ++ that the following conditions are met: ++ ++ 1. Redistributions of source code must retain the ++ above copyright notice, this list of conditions and ++ the following disclaimer. ++ 2. Redistributions in binary form must reproduce ++ the above copyright notice, this list of conditions ++ and the following disclaimer in the documentation ++ and/or other materials provided with the distribution. ++ 3. Neither the name of the copyright holder nor the ++ names of its contributors may be used to endorse or ++ promote products derived from this software without ++ specific prior written permission. ++ ++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ++ AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED ++ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ++ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ++ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, ++ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ++ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS ++ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER ++ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE ++ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ OF SUCH DAMAGE. ++ ++ SPDX-License-Identifier: BSD-3-Clause ++ ++ Application names associated with the nDPI engine. ++ ++ This file was auto-generated with ++ generation/application_names_yang.sh"; ++EndOfHeader ++else ++ heading=$(sed '1,/typedef application-names /!d' ${OLD_YANG_FILE} | head -n -1) ++fi ++ ++# Get the all names from applications_list ++new_list=$(./applications_list --names | sort -u) ++ ++# Get the names from the old yang file, which may be empty ++if [ -n "$OLD_YANG_FILE" ]; then ++ old_list=$(sed '1,/typedef application-names/d' ${OLD_YANG_FILE} | grep -o 'enum ".*"' | cut -d '"' -f 2) ++ ++ # Extract the new names and append them to the old list ++ TMP=/tmp/app_name_yang.tmp ++ echo "$old_list" > $TMP ++ apps=$(echo "${old_list}" && comm -13 $TMP - <<< $new_list | sed '/^[[:blank:]]*$/d') ++ rm -f $TMP ++else ++ apps=$new_list ++fi ++ ++# Convert each name into an application_names enum variant with correct indentation. ++yang="$(awk -F, '{print "\t\t\t" "enum \"" $1 "\";"}' <<< $apps)" ++ ++# Prompt the user for revision notes. ++>&2 echo "Enter revision notes:" ++changes=$( $TMP ++ apps=$(echo "${old_list}" && comm -13 $TMP - <<< $new_list | sed '/^[[:blank:]]*$/d') ++ rm -f $TMP ++else ++ apps=$new_list ++fi ++ ++# Convert each type into an application_types enum variant with correct indentation. ++yang="$(awk -F, '{print "\t\t\t" "enum \"" $1 "\";"}' <<< $apps)" ++ ++# Prompt the user for revision notes. ++>&2 echo "Enter revision notes:" ++changes=$( ++#include ++#include "ndpi_api.h" ++ ++ ++char *strlwr(const char *str) ++{ ++ char *dup = strdup(str); ++ ++ for (char *p = dup; *p; ++p) *p = tolower(*p); ++ ++ return dup; ++} ++ ++ ++void show_categories(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ for (int i = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED + 1; ++ i < NDPI_PROTOCOL_NUM_CATEGORIES; ++ i++) { ++ const char *category_name = ndpi_category_get_name(ndpi_info_mod, i); ++ if ( (i >= CUSTOM_CATEGORY_MINING && ++ i <= CUSTOM_CATEGORY_ANTIMALWARE) || ++ ndpi_is_custom_category(i) || ++ (category_name == "") ) { ++ continue; ++ } ++ ++ printf("%s\n", strlwr(category_name)); ++ } ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void show_protocols(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); ++ ++ for (int i = NDPI_PROTOCOL_UNKNOWN; ++ i < num_protocols; ++ i++) { ++ const char *proto_name = ndpi_get_proto_name(ndpi_info_mod, i); ++ if (proto_name == "") { ++ continue; ++ } ++ ++ printf("%s\n", strlwr(proto_name)); ++ } ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void show_mapping(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); ++ ++ ndpi_proto_defaults_t *pd = ndpi_get_proto_defaults(ndpi_info_mod); ++ ++ for (int i = NDPI_PROTOCOL_UNKNOWN; i < num_protocols; i++) { ++ const char *proto_name = ndpi_get_proto_name(ndpi_info_mod, i); ++ if (proto_name == "") { ++ continue; ++ } ++ ++ ndpi_protocol_category_t category = pd[i].protoCategory; ++ const char *category_name = ndpi_category_get_name(ndpi_info_mod, category); ++ printf("%s %s\n", strlwr(proto_name), strlwr(category_name)); ++ } ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void dump_protocols(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ ndpi_dump_protocols(ndpi_info_mod); ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void usage(char *name) ++{ ++ printf("Usage: %s [--names | --types | --mapping | --dump | --revision]\n", name); ++} ++ ++ ++int main(int argc, char **argv) ++{ ++ if (argc < 2) { ++ usage(argv[0]); ++ return -1; ++ } ++ ++ if (!strcmp(argv[1], "--revision")) ++ printf("Revision: %s\n", ndpi_revision()); ++ ++ else if (!strcmp(argv[1], "--types")) ++ show_categories(); ++ ++ else if (!strcmp(argv[1], "--names")) ++ show_protocols(); ++ ++ else if (!strcmp(argv[1], "--mapping")) ++ show_mapping(); ++ ++ else if (!strcmp(argv[1], "--dump")) ++ dump_protocols(); ++ ++ else { ++ usage(argv[0]); ++ return -1; ++ } ++} +diff --git a/generation/ndpi_show_name.c b/generation/ndpi_show_name.c +new file mode 100644 +index 0000000..f243e19 +--- /dev/null ++++ b/generation/ndpi_show_name.c +@@ -0,0 +1,104 @@ ++/* ++ * Copyright (c) 2020, AT&T Intellectual Property. All rights reserved. ++ * All rights reserved. ++ * ++ * SPDX-License-Identifier: LGPL-2.1-only ++ */ ++ ++#include ++#include ++#include "ndpi_api.h" ++ ++ ++char *strlwr(const char *str) ++{ ++ char *dup = strdup(str); ++ ++ for (char *p = dup; *p; ++p) *p = tolower(*p); ++ ++ return dup; ++} ++ ++ ++/* ++ * Show the application names associated with the given application type. ++ */ ++void show_type(char *type) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ int id = ndpi_get_category_id(ndpi_info_mod, type); ++ if (id == -1) return; ++ ++ u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); ++ ++ ndpi_proto_defaults_t *pd = ndpi_get_proto_defaults(ndpi_info_mod); ++ ++ printf("%s includes the following applications:\n", type); ++ ++ for (int i = NDPI_PROTOCOL_UNKNOWN; i < num_protocols; i++) { ++ ndpi_protocol_category_t category = pd[i].protoCategory; ++ ++ if (category == id) { ++ const char *proto_name = strlwr(ndpi_get_proto_name(ndpi_info_mod, i)); ++ if (proto_name == "") { ++ continue; ++ } ++ ++ printf("%s ", proto_name); ++ } ++ } ++ ++ printf("\n"); ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++/* ++ * Show the application type associated with the given application name. ++ */ ++void show_name(char *name) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ int id = ndpi_get_protocol_id(ndpi_info_mod, name); ++ if (id == -1) return; ++ ++ ndpi_proto_defaults_t *pd = ndpi_get_proto_defaults(ndpi_info_mod); ++ ++ ndpi_protocol_category_t category = pd[id].protoCategory; ++ const char *category_name = strlwr(ndpi_category_get_name(ndpi_info_mod, category)); ++ ++ printf("%s is included in the following application types:\n%s\n", ++ name, category_name); ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void usage(char *name) ++{ ++ printf("Usage: %s [name | type] \n", name); ++} ++ ++ ++int main(int argc, char **argv) ++{ ++ if (argc != 3) { ++ usage(argv[0]); ++ return -1; ++ } ++ ++ if (!strcmp(argv[1], "type")) { ++ show_type(argv[2]); ++ } ++ else if (!strcmp(argv[1], "name")) { ++ show_name(argv[2]); ++ } ++ else { ++ usage(argv[0]); ++ return -1; ++ } ++} From 41d5376564375e03714d1df5c6f4c8c9bd4feb45 Mon Sep 17 00:00:00 2001 From: Paul Aitken Date: Wed, 24 Jun 2020 10:35:32 +0100 Subject: [PATCH 05/11] patch to increase NDPI_NUM_BITS from 512 to 1024 Increase NDPI_NUM_BITS from 512 to 1024 because it's used to derive NDPI_MAX_NUM_CUSTOM_PROTOCOLS which limits the number of applications which can be added. --- debian/patches/series | 1 + .../patches/vyatta-increase-ndpi-num-bits.patch | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 debian/patches/vyatta-increase-ndpi-num-bits.patch diff --git a/debian/patches/series b/debian/patches/series index 354cb9c..899cf90 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -6,3 +6,4 @@ additional-cppflags.patch vyatta-app-names.patch vyatta-generation.patch vyatta-generated.patch +vyatta-increase-ndpi-num-bits.patch diff --git a/debian/patches/vyatta-increase-ndpi-num-bits.patch b/debian/patches/vyatta-increase-ndpi-num-bits.patch new file mode 100644 index 0000000..8c1169e --- /dev/null +++ b/debian/patches/vyatta-increase-ndpi-num-bits.patch @@ -0,0 +1,15 @@ +--- + src/include/ndpi_define.h.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/include/ndpi_define.h.in ++++ b/src/include/ndpi_define.h.in +@@ -252,7 +252,7 @@ + #define NDPI_COMPARE_IPV6_ADDRESS_STRUCTS(x,y) \ + ((x.u6_addr.u6_addr64[0] < y.u6_addr.u6_addr64[0]) || ((x.u6_addr.u6_addr64[0] == y.u6_addr.u6_addr64[0]) && (x.u6_addr.u6_addr64[1] < y.u6_addr.u6_addr64[1]))) + +-#define NDPI_NUM_BITS 512 ++#define NDPI_NUM_BITS 1024 + + #define NDPI_BITS /* 32 */ (sizeof(ndpi_ndpi_mask) * 8 /* number of bits in a byte */) /* bits per mask */ + #define howmanybits(x, y) (((x)+((y)-1))/(y)) From fd9a57adfd13be975ba6390ca1b6126ae1a20996 Mon Sep 17 00:00:00 2001 From: Paul Aitken Date: Wed, 24 Jun 2020 11:29:46 +0100 Subject: [PATCH 06/11] add category name to category file The category file consists of tuples, where "id" is the numeric id of the desired category. IDs aren't intuitive to use, so this patch extends the existing functionality with an id-to-name lookup allowing to be used in the tuples. This is similar to the protocols file, where rules end in a protocol name. --- debian/patches/series | 1 + debian/patches/vyatta-category-file.patch | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 debian/patches/vyatta-category-file.patch diff --git a/debian/patches/series b/debian/patches/series index 899cf90..da40d6b 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -7,3 +7,4 @@ vyatta-app-names.patch vyatta-generation.patch vyatta-generated.patch vyatta-increase-ndpi-num-bits.patch +vyatta-category-file.patch diff --git a/debian/patches/vyatta-category-file.patch b/debian/patches/vyatta-category-file.patch new file mode 100644 index 0000000..5e12eff --- /dev/null +++ b/debian/patches/vyatta-category-file.patch @@ -0,0 +1,21 @@ +--- + src/lib/ndpi_main.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +--- a/src/lib/ndpi_main.c ++++ b/src/lib/ndpi_main.c +@@ -2734,9 +2734,12 @@ int ndpi_load_categories_file(struct ndp + + if(name) { + category = strtok_r(NULL, "\t", &saveptr); ++ ndpi_protocol_category_t category_val = atoi(category); ++ if (category_val == 0) ++ category_val = ndpi_get_category_id(ndpi_str, category); + +- if(category) +- ndpi_load_category(ndpi_str, name, (ndpi_protocol_category_t) atoi(category)); ++ if(category_val > 0) ++ ndpi_load_category(ndpi_str, name, category_val); + } + } + From 61553ac7497205e45cfab3b0ef0ea2ea5610f859 Mon Sep 17 00:00:00 2001 From: Paul Aitken Date: Mon, 3 Aug 2020 11:26:03 +0100 Subject: [PATCH 07/11] 3.0-2 --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index 22ee190..9048383 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +ndpi (3.0-2) unstable; urgency=medium + + * Remove libndpi-wireshark package + * Change nDPI names to suit the vyatta CLI + * Add nDPI packages for application names and types + * Vyatta nDPI + * patch to increase NDPI_NUM_BITS from 512 to 1024 + * add category name to category file + + -- Paul Aitken Mon, 03 Aug 2020 11:25:50 +0100 + ndpi (3.0-1) unstable; urgency=medium * New upstream version 3.0 From d0dad080abd42f4f3de0fe3ddd041a4250d058c0 Mon Sep 17 00:00:00 2001 From: Subhajit Chatterjee Date: Tue, 10 Nov 2020 10:47:06 +0000 Subject: [PATCH 08/11] Update the repository with nDPI version 3.4 (stable) Signed-off-by: Subhajit Chatterjee --- CHANGELOG.md | 151 +- Makefile.am | 11 +- README.md | 17 +- README.nDPI | 9 +- autogen.sh | 18 +- configure.seed | 222 +- debian/changelog | 25 + debian/control | 4 +- debian/libndpi3.4.docs | 1 + debian/libndpi3.4.install | 1 + debian/libndpi3.4.symbols | 696 ++ ...or-version-to-soname-ndpi-3.4-stable.patch | 50 + .../additional-cppflags-ndpi-3.4-stable.patch | 53 + .../define-have-json-c-ndpi-3.4-stable.patch | 25 + .../fix-makefile-prefix-ndpi-3.4-stable.patch | 48 + ...ireader-dynamic-link-ndpi-3.4-stable.patch | 25 + debian/patches/series | 20 +- .../vyatta-app-names-ndpi-3.4-stable.patch | 298 + ...vyatta-category-file-ndpi-3.4-stable.patch | 32 + .../vyatta-generated-ndpi-3.4-changes.patch | 239 + .../patches/vyatta-generation-modified.patch | 556 + .../vyatta-generation-ndpi-3.4-changes.patch | 75 + ...crease-ndpi-num-bits-ndpi-3.4-stable.patch | 26 + debian/rules | 24 +- doc/nDPI_QuickStartGuide.pages | Bin 128922 -> 133048 bytes doc/nDPI_QuickStartGuide.pdf | Bin 184164 -> 228485 bytes example/Makefile.dpdk.in | 4 +- example/Makefile.in | 47 +- example/intrusion_detection.c | 462 + example/intrusion_detection.h | 69 + example/ndpi2timeline.py | 136 + example/ndpiReader.c | 2221 ++-- example/ndpiSimpleIntegration.c | 1118 ++ example/protos.txt | 31 +- example/reader_util.c | 908 +- example/reader_util.h | 86 +- .../fuzz_ndpi_reader-fuzz_ndpi_reader.Po | 1 + ..._ndpi_reader_with_main-fuzz_ndpi_reader.Po | 1 + ...fuzz_process_packet-fuzz_process_packet.Po | 1 + fuzz/Makefile | 777 ++ fuzz/Makefile.am | 43 + fuzz/fuzz_ndpi_reader.c | 163 + fuzz/fuzz_process_packet.c | 28 + libndpi.pc.in | 2 +- m4/ax_check_compile_flag.m4 | 53 + ndpiReader.1 | 78 + packages/openwrt/Makefile | 14 +- packages/openwrt/README | 25 + packages/rpm/Makefile.in | 4 +- packages/rpm/configure | 12 + packages/rpm/configure.in | 11 + packages/rpm/ndpi.spec.in | 4 + python/Makefile.in | 6 +- python/README | 7 - python/README.rst | 43 + python/flow_printer.py | 38 + python/ndpi.py | 1459 +++ python/ndpi_example.py | 151 +- python/ndpi_typestruct.py | 809 +- src/include/ndpi_api.h | 693 +- src/include/ndpi_classify.h | 27 +- src/include/ndpi_define.h.in | 28 +- src/include/ndpi_includes.h | 8 +- src/include/ndpi_includes_OpenBSD.h | 35 + src/include/ndpi_main.h | 9 +- src/include/ndpi_protocol_ids.h | 65 +- src/include/ndpi_protocols.h | 179 +- src/include/ndpi_typedefs.h | 360 +- src/include/ndpi_win32.h | 25 +- src/lib/Makefile.in | 21 +- src/lib/ndpi_analyze.c | 654 +- src/lib/ndpi_classify.c | 948 +- src/lib/ndpi_community_id.c | 380 + src/lib/ndpi_content_match.c.inc | 1812 ++-- src/lib/ndpi_main.c | 5906 +++++----- src/lib/ndpi_serializer.c | 1571 ++- src/lib/ndpi_utils.c | 1114 +- src/lib/protocols/afp.c | 2 +- src/lib/protocols/aimini.c | 2 +- src/lib/protocols/ajp.c | 2 +- src/lib/protocols/amazon_video.c | 43 +- src/lib/protocols/amqp.c | 2 +- src/lib/protocols/applejuice.c | 2 +- src/lib/protocols/armagetron.c | 2 +- src/lib/protocols/attic/flash.c | 2 +- src/lib/protocols/attic/ftp.c | 2 +- src/lib/protocols/attic/manolito.c | 2 +- src/lib/protocols/attic/popo.c | 2 +- src/lib/protocols/attic/secondlife.c | 2 +- src/lib/protocols/ayiya.c | 4 +- src/lib/protocols/battlefield.c | 126 - src/lib/protocols/bgp.c | 2 +- src/lib/protocols/bittorrent.c | 71 +- src/lib/protocols/btlib.c | 10 +- src/lib/protocols/capwap.c | 125 + src/lib/protocols/checkmk.c | 2 +- src/lib/protocols/ciscovpn.c | 84 +- src/lib/protocols/citrix.c | 4 +- src/lib/protocols/collectd.c | 2 +- src/lib/protocols/crossfire.c | 2 +- src/lib/protocols/csgo.c | 79 +- src/lib/protocols/dhcp.c | 67 +- src/lib/protocols/dhcpv6.c | 2 +- src/lib/protocols/directconnect.c | 34 +- src/lib/protocols/directdownloadlink.c | 4 +- src/lib/protocols/dnp3.c | 48 +- src/lib/protocols/dns.c | 304 +- src/lib/protocols/dnscrypt.c | 69 + src/lib/protocols/dofus.c | 2 +- src/lib/protocols/drda.c | 2 +- src/lib/protocols/eaq.c | 2 +- src/lib/protocols/edonkey.c | 14 +- src/lib/protocols/fasttrack.c | 79 +- src/lib/protocols/fbzero.c | 5 +- src/lib/protocols/fiesta.c | 2 +- src/lib/protocols/fix.c | 4 +- src/lib/protocols/florensia.c | 2 +- src/lib/protocols/ftp_control.c | 772 +- src/lib/protocols/ftp_data.c | 2 +- src/lib/protocols/git.c | 2 +- src/lib/protocols/gnutella.c | 22 +- src/lib/protocols/gtp.c | 2 +- src/lib/protocols/guildwars.c | 2 +- src/lib/protocols/h323.c | 117 +- src/lib/protocols/halflife2_and_mods.c | 2 +- src/lib/protocols/hangout.c | 4 +- src/lib/protocols/http.c | 824 +- src/lib/protocols/http_activesync.c | 2 +- src/lib/protocols/iax.c | 13 +- src/lib/protocols/icecast.c | 4 +- src/lib/protocols/iec60870-5-104.c | 59 +- src/lib/protocols/imo.c | 2 +- src/lib/protocols/ipp.c | 2 +- src/lib/protocols/irc.c | 31 +- src/lib/protocols/jabber.c | 20 +- src/lib/protocols/kakaotalk_voice.c | 2 +- src/lib/protocols/kerberos.c | 412 +- src/lib/protocols/kontiki.c | 2 +- src/lib/protocols/ldap.c | 2 +- src/lib/protocols/line.c | 70 - src/lib/protocols/lisp.c | 2 +- src/lib/protocols/lotus_notes.c | 2 +- src/lib/protocols/mail_imap.c | 139 +- src/lib/protocols/mail_pop.c | 317 +- src/lib/protocols/mail_smtp.c | 282 +- src/lib/protocols/maplestory.c | 2 +- src/lib/protocols/mdns_proto.c | 155 - src/lib/protocols/megaco.c | 2 +- src/lib/protocols/memcached.c | 5 +- src/lib/protocols/mgcp.c | 2 +- src/lib/protocols/mining.c | 69 +- src/lib/protocols/modbus.c | 3 +- src/lib/protocols/mpegts.c | 2 +- src/lib/protocols/mqtt.c | 6 +- src/lib/protocols/msn.c | 526 - src/lib/protocols/mssql_tds.c | 9 +- src/lib/protocols/mysql.c | 4 +- src/lib/protocols/nats.c | 82 + src/lib/protocols/nest_log_sink.c | 2 +- src/lib/protocols/netbios.c | 274 +- src/lib/protocols/netflow.c | 21 +- src/lib/protocols/nfs.c | 2 +- src/lib/protocols/noe.c | 23 +- src/lib/protocols/non_tcp_udp.c | 2 +- src/lib/protocols/ntp.c | 4 +- src/lib/protocols/openft.c | 2 +- src/lib/protocols/openvpn.c | 100 +- src/lib/protocols/oracle.c | 2 +- src/lib/protocols/oscar.c | 816 -- src/lib/protocols/pando.c | 167 - src/lib/protocols/pcanywhere.c | 67 - src/lib/protocols/postgres.c | 6 +- src/lib/protocols/pplive.c | 232 - src/lib/protocols/ppstream.c | 2 +- src/lib/protocols/pptp.c | 2 +- src/lib/protocols/quic.c | 1365 ++- src/lib/protocols/radius.c | 14 +- src/lib/protocols/rdp.c | 2 +- src/lib/protocols/redis_net.c | 2 +- src/lib/protocols/rtcp.c | 5 +- src/lib/protocols/rtmp.c | 18 +- src/lib/protocols/rtp.c | 44 +- src/lib/protocols/rtsp.c | 6 +- src/lib/protocols/rx.c | 7 +- src/lib/protocols/s7comm.c | 57 + src/lib/protocols/sflow.c | 4 +- src/lib/protocols/shoutcast.c | 2 +- src/lib/protocols/sip.c | 226 +- src/lib/protocols/skype.c | 26 +- src/lib/protocols/smb.c | 25 +- src/lib/protocols/smpp.c | 4 +- src/lib/protocols/snmp_proto.c | 2 +- src/lib/protocols/soap.c | 70 + src/lib/protocols/socks45.c | 2 +- src/lib/protocols/someip.c | 14 +- src/lib/protocols/sopcast.c | 2 +- src/lib/protocols/soulseek.c | 55 +- src/lib/protocols/spotify.c | 2 +- src/lib/protocols/ssdp.c | 2 +- src/lib/protocols/ssh.c | 365 +- src/lib/protocols/starcraft.c | 2 +- src/lib/protocols/stealthnet.c | 2 +- src/lib/protocols/steam.c | 3 +- src/lib/protocols/stun.c | 174 +- src/lib/protocols/syslog.c | 2 +- src/lib/protocols/tcp_udp.c | 2 +- src/lib/protocols/teamspeak.c | 68 +- src/lib/protocols/teamviewer.c | 2 +- src/lib/protocols/telegram.c | 77 +- src/lib/protocols/telnet.c | 148 +- src/lib/protocols/teredo.c | 2 +- src/lib/protocols/tftp.c | 35 +- src/lib/protocols/thunder.c | 15 +- src/lib/protocols/tinc.c | 6 +- src/lib/protocols/tls.c | 2333 ++-- src/lib/protocols/tor.c | 43 +- src/lib/protocols/tvants.c | 85 - src/lib/protocols/tvuplayer.c | 2 +- src/lib/protocols/ubntac2.c | 8 +- src/lib/protocols/usenet.c | 2 +- src/lib/protocols/vhua.c | 2 +- src/lib/protocols/vmware.c | 2 +- src/lib/protocols/vnc.c | 2 +- src/lib/protocols/warcraft3.c | 2 +- src/lib/protocols/websocket.c | 131 + src/lib/protocols/whoisdas.c | 14 +- src/lib/protocols/world_of_kung_fu.c | 2 +- src/lib/protocols/world_of_warcraft.c | 2 +- src/lib/protocols/xbox.c | 2 +- src/lib/protocols/xdmcp.c | 2 +- src/lib/protocols/yahoo.c | 406 - src/lib/protocols/zabbix.c | 63 + src/lib/protocols/zattoo.c | 17 +- src/lib/protocols/zeromq.c | 2 +- src/lib/third_party/include/MurmurHash3.h | 8 + src/lib/third_party/include/actypes.h | 4 +- src/lib/third_party/include/ahocorasick.h | 13 +- src/lib/third_party/include/hll.h | 27 + src/lib/third_party/include/libinjection.h | 65 + .../third_party/include/libinjection_html5.h | 54 + .../third_party/include/libinjection_sqli.h | 294 + .../include/libinjection_sqli_data.h | 9652 +++++++++++++++++ .../third_party/include/libinjection_xss.h | 21 + src/lib/third_party/include/ndpi_patricia.h | 22 +- src/lib/third_party/include/rce_injection.h | 618 ++ src/lib/third_party/src/ahocorasick.c | 250 +- src/lib/third_party/src/hll/MurmurHash3.c | 61 + src/lib/third_party/src/hll/hll.c | 161 + src/lib/third_party/src/libinjection_html5.c | 850 ++ src/lib/third_party/src/libinjection_sqli.c | 2325 ++++ src/lib/third_party/src/libinjection_xss.c | 532 + src/lib/third_party/src/ndpi_patricia.c | 245 +- src/lib/third_party/src/ndpi_sha1.c | 7 + src/lib/third_party/src/node.c | 12 +- src/lib/third_party/src/strptime.c | 440 + tests/Makefile.am | 5 +- tests/do-unit.sh | 32 + tests/do.sh | 23 +- tests/ossfuzz.sh | 32 + tests/pcap/443-chrome.pcap | Bin 0 -> 1546 bytes tests/pcap/443-curl.pcap | Bin 0 -> 75750 bytes tests/pcap/443-firefox.pcap | Bin 0 -> 468763 bytes tests/pcap/443-git.pcap | Bin 0 -> 38333 bytes tests/pcap/443-opvn.pcap | Bin 0 -> 12333 bytes tests/pcap/443-safari.pcap | Bin 0 -> 20609 bytes tests/pcap/4in4tunnel.pcap | Bin 0 -> 954 bytes tests/pcap/4in6tunnel.pcap | Bin 0 -> 2276 bytes tests/pcap/6in6tunnel.pcap | Bin 0 -> 268 bytes tests/pcap/KakaoTalk_chat.pcap | Bin 77824 -> 77512 bytes tests/pcap/KakaoTalk_talk.pcap | Bin 487424 -> 487064 bytes tests/pcap/WebattackRCE.pcap | Bin 0 -> 203779 bytes tests/pcap/WebattackSQLinj.pcap | Bin 0 -> 31536 bytes tests/pcap/WebattackXSS.pcap | Bin 0 -> 4871156 bytes tests/pcap/android.pcap | Bin 0 -> 141554 bytes tests/pcap/anydesk.pcap | Bin 0 -> 2906892 bytes tests/pcap/bad-dns-traffic.pcap | Bin 0 -> 105510 bytes tests/pcap/badpackets.pcap | Bin 0 -> 50972 bytes tests/pcap/capwap.pcap | Bin 0 -> 109690 bytes tests/pcap/dnp3.pcap | Bin 0 -> 56812 bytes tests/pcap/dns-tunnel-iodine.pcap | Bin 0 -> 77524 bytes tests/pcap/dns_doh.pcap | Bin 0 -> 22658 bytes tests/pcap/dns_dot.pcap | Bin 0 -> 6277 bytes tests/pcap/dns_exfiltration.pcap | Bin 0 -> 78369 bytes tests/pcap/dns_long_domainname.pcap | Bin 0 -> 318 bytes .../pcap/dnscrypt-v1-and-resolver-pings.pcap | Bin 0 -> 330594 bytes tests/pcap/dnscrypt-v2-doh.pcap | Bin 0 -> 225839 bytes tests/pcap/dos_win98_smb_netbeui.pcap | Bin 0 -> 26256 bytes tests/pcap/dtls.pcap | Bin 0 -> 450 bytes tests/pcap/encrypted_sni.pcap | Bin 0 -> 2382 bytes tests/pcap/ethereum.pcap | Bin 147293 -> 248135 bytes tests/pcap/exe_download_as_png.pcap | Bin 0 -> 538017 bytes tests/pcap/fbzero-missing-lengthcheck.pcap | Bin 0 -> 215 bytes tests/pcap/fuzz-2006-09-29-28586.pcap | Bin 0 -> 35746 bytes tests/pcap/fuzz-2020-02-16-11740.pcap | Bin 0 -> 188444 bytes tests/pcap/googledns_android10.pcap | Bin 0 -> 141430 bytes tests/pcap/h323-overflow.pcap | Bin 0 -> 98 bytes .../pcap/http-crash-content-disposition.pcap | Bin 0 -> 3496 bytes tests/pcap/http-lines-split.pcap | Bin 0 -> 2751 bytes tests/pcap/iec60780-5-104.pcap | Bin 0 -> 11409 bytes tests/pcap/imaps.pcap | Bin 0 -> 5540 bytes tests/pcap/instagram.pcap | Bin 3043172 -> 2981793 bytes tests/pcap/iphone.pcap | Bin 0 -> 229926 bytes tests/pcap/ipv6_in_gtp.pcap | Bin 0 -> 372 bytes tests/pcap/ja3_lots_of_cipher_suites.pcap | Bin 0 -> 5332 bytes .../ja3_lots_of_cipher_suites_2_anon.pcap | Bin 0 -> 7422 bytes tests/pcap/kerberos.pcap | Bin 0 -> 29547 bytes tests/pcap/malformed_dns.pcap | Bin 0 -> 5980 bytes tests/pcap/malformed_icmp.pcap | Bin 0 -> 82 bytes tests/pcap/mysql-8.pcap | Bin 0 -> 455 bytes tests/pcap/nats.pcap | Bin 0 -> 2916 bytes tests/pcap/netbios.pcap | Bin 0 -> 28866 bytes tests/pcap/netbios_wildcard_dns_query.pcap | Bin 0 -> 132 bytes tests/pcap/netflow-fritz.pcap | Bin 0 -> 262 bytes .../pcap/quic-mvfst-22_decryption_error.pcap | Bin 0 -> 406162 bytes tests/pcap/quic046.pcap | Bin 0 -> 92921 bytes tests/pcap/quic_q39.pcap | Bin 0 -> 25169 bytes tests/pcap/quic_q43.pcap | Bin 0 -> 1520 bytes tests/pcap/quic_q46.pcap | Bin 0 -> 21585 bytes tests/pcap/quic_q46_b.pcap | Bin 0 -> 7364 bytes tests/pcap/s7comm.pcap | Bin 0 -> 6164 bytes tests/pcap/selfsigned.pcap | Bin 0 -> 4110 bytes tests/pcap/simple-dnscrypt.pcap | Bin 0 -> 46476 bytes tests/pcap/smb_deletefile.pcap | Bin 0 -> 32388 bytes tests/pcap/teams.pcap | Bin 0 -> 1535303 bytes tests/pcap/teamspeak3.pcap | Bin 0 -> 2143 bytes tests/pcap/telegram.pcap | Bin 0 -> 361905 bytes tests/pcap/tftp_rrq.pcap | Bin 0 -> 31463 bytes tests/pcap/tls-esni-fuzzed.pcap | Bin 0 -> 2382 bytes tests/pcap/tls-rdn-extract.pcap | Bin 0 -> 7325 bytes tests/pcap/tls_esni_sni_both.pcap | Bin 0 -> 16531 bytes tests/pcap/tls_long_cert.pcap | Bin 0 -> 120537 bytes tests/pcap/tls_verylong_certificate.pcap | Bin 0 -> 23021 bytes tests/pcap/wa_video.pcap | Bin 0 -> 986081 bytes tests/pcap/wa_voice.pcap | Bin 0 -> 182100 bytes tests/pcap/websocket.pcap | Bin 0 -> 545 bytes tests/pcap/zabbix.pcap | Bin 0 -> 899 bytes tests/result/1kxun.pcap.out | 266 +- tests/result/443-chrome.pcap.out | 3 + tests/result/443-curl.pcap.out | 8 + tests/result/443-firefox.pcap.out | 8 + tests/result/443-git.pcap.out | 8 + tests/result/443-opvn.pcap.out | 3 + tests/result/443-safari.pcap.out | 8 + tests/result/4in4tunnel.pcap.out | 6 + tests/result/4in6tunnel.pcap.out | 8 + tests/result/6in4tunnel.pcap.out | 20 +- tests/result/6in6tunnel.pcap.out | 6 + tests/result/BGP_Cisco_hdlc_slarp.pcap.out | 2 +- tests/result/BGP_redist.pcap.out | 4 +- tests/result/EAQ.pcap.out | 62 +- tests/result/KakaoTalk_chat.pcap.out | 76 +- tests/result/KakaoTalk_talk.pcap.out | 40 +- tests/result/NTPv2.pcap.out | 2 +- tests/result/NTPv3.pcap.out | 2 +- tests/result/NTPv4.pcap.out | 2 +- tests/result/Oscar.pcap.out | 4 +- tests/result/WebattackRCE.pcap.out | 800 ++ tests/result/WebattackSQLinj.pcap.out | 11 + tests/result/WebattackXSS.pcap.out | 663 ++ tests/result/ajp.pcap.out | 8 +- tests/result/amqp.pcap.out | 6 +- tests/result/android.pcap.out | 83 + tests/result/anyconnect-vpn.pcap.out | 146 +- tests/result/anydesk.pcap.out | 9 + tests/result/bad-dns-traffic.pcap.out | 5 + tests/result/badpackets.pcap.out | 0 tests/result/bitcoin.pcap.out | 12 +- tests/result/bittorrent.pcap.out | 48 +- tests/result/bittorrent_ip.pcap.out | 4 +- tests/result/bittorrent_utp.pcap.out | 2 +- tests/result/bt_search.pcap.out | 2 +- tests/result/capwap.pcap.out | 8 + tests/result/check_mk_new.pcap.out | 2 +- tests/result/coap_mqtt.pcap.out | 32 +- tests/result/diameter.pcap.out | 2 +- tests/result/dnp3.pcap.out | 10 + tests/result/dns-tunnel-iodine.pcap.out | 3 + tests/result/dns_doh.pcap.out | 8 + tests/result/dns_dot.pcap.out | 8 + tests/result/dns_exfiltration.pcap.out | 3 + tests/result/dns_long_domainname.pcap.out | 3 + .../dnscrypt-v1-and-resolver-pings.pcap.out | 248 + tests/result/dnscrypt-v2-doh.pcap.out | 41 + tests/result/dos_win98_smb_netbeui.pcap.out | 8 + tests/result/drda_db2.pcap.out | 2 +- tests/result/dropbox.pcap.out | 30 +- tests/result/dtls.pcap.out | 8 + tests/result/encrypted_sni.pcap.out | 10 + tests/result/ethereum.pcap.out | 79 +- tests/result/exe_download.pcap.out | 3 + tests/result/exe_download_as_png.pcap.out | 3 + tests/result/facebook.pcap.out | 4 +- .../fbzero-missing-lengthcheck.pcap.out | 3 + tests/result/fix.pcap.out | 24 +- tests/result/ftp.pcap.out | 6 +- tests/result/ftp_failed.pcap.out | 3 + tests/result/fuzz-2006-06-26-2594.pcap.out | 264 + tests/result/fuzz-2006-09-29-28586.pcap.out | 47 + tests/result/fuzz-2020-02-16-11740.pcap.out | 84 + tests/result/git.pcap.out | 2 +- tests/result/google_ssl.pcap.out | 6 +- tests/result/googledns_android10.pcap.out | 16 + tests/result/gquic.pcap.out | 3 + tests/result/h323-overflow.pcap.out | 3 + tests/result/hangout.pcap.out | 2 +- .../http-crash-content-disposition.pcap.out | 3 + tests/result/http-lines-split.pcap.out | 3 + tests/result/http_ipv6.pcap.out | 35 +- tests/result/iec60780-5-104.pcap.out | 8 + tests/result/imaps.pcap.out | 8 + tests/result/instagram.pcap.out | 75 +- tests/result/iphone.pcap.out | 72 + tests/result/ipv6_in_gtp.pcap.out | 8 + .../result/ja3_lots_of_cipher_suites.pcap.out | 8 + .../ja3_lots_of_cipher_suites_2_anon.pcap.out | 8 + tests/result/kerberos.pcap.out | 44 + tests/result/malformed_dns.pcap.out | 3 + tests/result/malformed_icmp.pcap.out | 3 + tests/result/malware.pcap.out | 14 +- tests/result/modbus.pcap.out | 2 +- tests/result/monero.pcap.out | 4 +- tests/result/mpeg.pcap.out | 2 +- tests/result/mpegts.pcap.out | 2 +- tests/result/mssql_tds.pcap.out | 24 +- tests/result/mysql-8.pcap.out | 3 + tests/result/nats.pcap.out | 4 + tests/result/nest_log_sink.pcap.out | 31 +- tests/result/netbios.pcap.out | 18 + .../netbios_wildcard_dns_query.pcap.out | 3 + tests/result/netflix.pcap.out | 122 +- tests/result/netflow-fritz.pcap.out | 3 + tests/result/netflowv9.pcap.out | 2 +- tests/result/nintendo.pcap.out | 46 +- tests/result/ocs.pcap.out | 40 +- tests/result/ookla.pcap.out | 4 +- tests/result/openvpn.pcap.out | 6 +- tests/result/pps.pcap.out | 214 +- tests/result/ps_vue.pcap.out | 16 +- tests/result/quic-23.pcap.out | 8 + tests/result/quic-24.pcap.out | 8 + tests/result/quic-27.pcap.out | 8 + tests/result/quic-28.pcap.out | 8 + tests/result/quic-29.pcap.out | 8 + tests/result/quic-mvfst-22.pcap.out | 8 + .../quic-mvfst-22_decryption_error.pcap.out | 3 + tests/result/quic-mvfst-27.pcap.out | 8 + tests/result/quic-mvfst-exp.pcap.out | 8 + tests/result/quic.pcap.out | 25 +- tests/result/quic046.pcap.out | 3 + tests/result/quic_q39.pcap.out | 3 + tests/result/quic_q43.pcap.out | 3 + tests/result/quic_q46.pcap.out | 3 + tests/result/quic_q46_b.pcap.out | 3 + tests/result/quic_q50.pcap.out | 3 + tests/result/quic_t50.pcap.out | 8 + tests/result/quic_t51.pcap.out | 8 + tests/result/quickplay.pcap.out | 42 +- tests/result/rdp.pcap.out | 2 +- tests/result/rx.pcap.out | 10 +- tests/result/s7comm.pcap.out | 3 + tests/result/selfsigned.pcap.out | 8 + tests/result/signal.pcap.out | 38 +- tests/result/simple-dnscrypt.pcap.out | 11 + tests/result/sip.pcap.out | 8 +- tests/result/skype-conference-call.pcap.out | 2 +- tests/result/skype.pcap.out | 595 +- tests/result/skype_no_unknown.pcap.out | 545 +- tests/result/smb_deletefile.pcap.out | 3 + tests/result/smbv1.pcap.out | 2 +- tests/result/smpp_in_general.pcap.out | 2 +- tests/result/snapchat.pcap.out | 6 +- tests/result/ssdp-m-search.pcap.out | 2 +- tests/result/ssh.pcap.out | 2 +- tests/result/starcraft_battle.pcap.out | 104 +- tests/result/steam.pcap.out | 110 +- tests/result/teams.pcap.out | 107 + tests/result/teamspeak3.pcap.out | 3 + tests/result/telegram.pcap.out | 66 + tests/result/teredo.pcap.out | 10 +- tests/result/tftp_rrq.pcap.out | 5 + tests/result/tinc.pcap.out | 8 +- tests/result/tls-esni-fuzzed.pcap.out | 10 + tests/result/tls-rdn-extract.pcap.out | 8 + tests/result/tls_esni_sni_both.pcap.out | 9 + tests/result/tls_long_cert.pcap.out | 8 + .../result/tls_verylong_certificate.pcap.out | 8 + tests/result/tor.pcap.out | 28 +- tests/result/ubntac2.pcap.out | 16 +- tests/result/upnp.pcap.out | 4 +- tests/result/viber.pcap.out | 52 +- tests/result/vnc.pcap.out | 4 +- tests/result/wa_video.pcap.out | 21 + tests/result/wa_voice.pcap.out | 48 + tests/result/waze.pcap.out | 66 +- tests/result/webex.pcap.out | 114 +- tests/result/websocket.pcap.out | 3 + tests/result/wechat.pcap.out | 209 +- tests/result/weibo.pcap.out | 88 +- tests/result/whatsapp_login_call.pcap.out | 123 +- tests/result/whatsapp_login_chat.pcap.out | 27 +- .../whatsapp_voice_and_message.pcap.out | 29 +- tests/result/whatsappfiles.pcap.out | 4 +- tests/result/wireguard.pcap.out | 2 +- tests/result/youtube_quic.pcap.out | 6 +- tests/result/youtubeupload.pcap.out | 6 +- tests/result/zabbix.pcap.out | 3 + tests/result/zcash.pcap.out | 2 +- tests/result/zoom.pcap.out | 70 +- tests/unit/unit.c | 261 + tests/vagrind_test.sh | 35 + utils/bitcoinnodes.sh | 8 + utils/toripaddr2list.py | 16 +- windows/README.md | 4 + windows/nDPI.vcxproj | 390 + wireshark/download-fuzz-traces.sh | 10 + wireshark/ndpi.lua | 8 +- 516 files changed, 45965 insertions(+), 15634 deletions(-) create mode 100644 debian/libndpi3.4.docs create mode 100644 debian/libndpi3.4.install create mode 100644 debian/libndpi3.4.symbols create mode 100644 debian/patches/add-minor-version-to-soname-ndpi-3.4-stable.patch create mode 100644 debian/patches/additional-cppflags-ndpi-3.4-stable.patch create mode 100644 debian/patches/define-have-json-c-ndpi-3.4-stable.patch create mode 100644 debian/patches/fix-makefile-prefix-ndpi-3.4-stable.patch create mode 100644 debian/patches/ndpireader-dynamic-link-ndpi-3.4-stable.patch create mode 100644 debian/patches/vyatta-app-names-ndpi-3.4-stable.patch create mode 100644 debian/patches/vyatta-category-file-ndpi-3.4-stable.patch create mode 100644 debian/patches/vyatta-generated-ndpi-3.4-changes.patch create mode 100644 debian/patches/vyatta-generation-modified.patch create mode 100644 debian/patches/vyatta-generation-ndpi-3.4-changes.patch create mode 100644 debian/patches/vyatta-increase-ndpi-num-bits-ndpi-3.4-stable.patch create mode 100644 example/intrusion_detection.c create mode 100644 example/intrusion_detection.h create mode 100755 example/ndpi2timeline.py create mode 100644 example/ndpiSimpleIntegration.c create mode 100644 fuzz/.deps/fuzz_ndpi_reader-fuzz_ndpi_reader.Po create mode 100644 fuzz/.deps/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po create mode 100644 fuzz/.deps/fuzz_process_packet-fuzz_process_packet.Po create mode 100644 fuzz/Makefile create mode 100644 fuzz/Makefile.am create mode 100644 fuzz/fuzz_ndpi_reader.c create mode 100644 fuzz/fuzz_process_packet.c create mode 100644 m4/ax_check_compile_flag.m4 create mode 100644 ndpiReader.1 create mode 100644 packages/openwrt/README delete mode 100644 python/README create mode 100644 python/README.rst create mode 100644 python/flow_printer.py create mode 100644 python/ndpi.py create mode 100644 src/include/ndpi_includes_OpenBSD.h create mode 100644 src/lib/ndpi_community_id.c delete mode 100644 src/lib/protocols/battlefield.c create mode 100644 src/lib/protocols/capwap.c create mode 100644 src/lib/protocols/dnscrypt.c delete mode 100644 src/lib/protocols/line.c delete mode 100644 src/lib/protocols/mdns_proto.c delete mode 100644 src/lib/protocols/msn.c create mode 100644 src/lib/protocols/nats.c delete mode 100644 src/lib/protocols/oscar.c delete mode 100644 src/lib/protocols/pando.c delete mode 100644 src/lib/protocols/pcanywhere.c delete mode 100644 src/lib/protocols/pplive.c create mode 100644 src/lib/protocols/s7comm.c create mode 100644 src/lib/protocols/soap.c delete mode 100644 src/lib/protocols/tvants.c create mode 100644 src/lib/protocols/websocket.c delete mode 100644 src/lib/protocols/yahoo.c create mode 100644 src/lib/protocols/zabbix.c create mode 100644 src/lib/third_party/include/MurmurHash3.h create mode 100644 src/lib/third_party/include/hll.h create mode 100644 src/lib/third_party/include/libinjection.h create mode 100644 src/lib/third_party/include/libinjection_html5.h create mode 100644 src/lib/third_party/include/libinjection_sqli.h create mode 100644 src/lib/third_party/include/libinjection_sqli_data.h create mode 100644 src/lib/third_party/include/libinjection_xss.h create mode 100644 src/lib/third_party/include/rce_injection.h create mode 100644 src/lib/third_party/src/hll/MurmurHash3.c create mode 100644 src/lib/third_party/src/hll/hll.c create mode 100644 src/lib/third_party/src/libinjection_html5.c create mode 100644 src/lib/third_party/src/libinjection_sqli.c create mode 100644 src/lib/third_party/src/libinjection_xss.c create mode 100644 src/lib/third_party/src/strptime.c create mode 100755 tests/do-unit.sh create mode 100644 tests/ossfuzz.sh create mode 100644 tests/pcap/443-chrome.pcap create mode 100644 tests/pcap/443-curl.pcap create mode 100644 tests/pcap/443-firefox.pcap create mode 100644 tests/pcap/443-git.pcap create mode 100644 tests/pcap/443-opvn.pcap create mode 100644 tests/pcap/443-safari.pcap create mode 100644 tests/pcap/4in4tunnel.pcap create mode 100644 tests/pcap/4in6tunnel.pcap create mode 100644 tests/pcap/6in6tunnel.pcap create mode 100644 tests/pcap/WebattackRCE.pcap create mode 100644 tests/pcap/WebattackSQLinj.pcap create mode 100644 tests/pcap/WebattackXSS.pcap create mode 100644 tests/pcap/android.pcap create mode 100644 tests/pcap/anydesk.pcap create mode 100644 tests/pcap/bad-dns-traffic.pcap create mode 100644 tests/pcap/badpackets.pcap create mode 100644 tests/pcap/capwap.pcap create mode 100644 tests/pcap/dnp3.pcap create mode 100644 tests/pcap/dns-tunnel-iodine.pcap create mode 100644 tests/pcap/dns_doh.pcap create mode 100644 tests/pcap/dns_dot.pcap create mode 100644 tests/pcap/dns_exfiltration.pcap create mode 100644 tests/pcap/dns_long_domainname.pcap create mode 100644 tests/pcap/dnscrypt-v1-and-resolver-pings.pcap create mode 100644 tests/pcap/dnscrypt-v2-doh.pcap create mode 100644 tests/pcap/dos_win98_smb_netbeui.pcap create mode 100644 tests/pcap/dtls.pcap create mode 100644 tests/pcap/encrypted_sni.pcap create mode 100644 tests/pcap/exe_download_as_png.pcap create mode 100644 tests/pcap/fbzero-missing-lengthcheck.pcap create mode 100644 tests/pcap/fuzz-2006-09-29-28586.pcap create mode 100644 tests/pcap/fuzz-2020-02-16-11740.pcap create mode 100644 tests/pcap/googledns_android10.pcap create mode 100644 tests/pcap/h323-overflow.pcap create mode 100644 tests/pcap/http-crash-content-disposition.pcap create mode 100644 tests/pcap/http-lines-split.pcap create mode 100644 tests/pcap/iec60780-5-104.pcap create mode 100644 tests/pcap/imaps.pcap create mode 100644 tests/pcap/iphone.pcap create mode 100644 tests/pcap/ipv6_in_gtp.pcap create mode 100644 tests/pcap/ja3_lots_of_cipher_suites.pcap create mode 100644 tests/pcap/ja3_lots_of_cipher_suites_2_anon.pcap create mode 100644 tests/pcap/kerberos.pcap create mode 100644 tests/pcap/malformed_dns.pcap create mode 100644 tests/pcap/malformed_icmp.pcap create mode 100644 tests/pcap/mysql-8.pcap create mode 100644 tests/pcap/nats.pcap create mode 100644 tests/pcap/netbios.pcap create mode 100644 tests/pcap/netbios_wildcard_dns_query.pcap create mode 100644 tests/pcap/netflow-fritz.pcap create mode 100644 tests/pcap/quic-mvfst-22_decryption_error.pcap create mode 100644 tests/pcap/quic046.pcap create mode 100644 tests/pcap/quic_q39.pcap create mode 100644 tests/pcap/quic_q43.pcap create mode 100644 tests/pcap/quic_q46.pcap create mode 100644 tests/pcap/quic_q46_b.pcap create mode 100644 tests/pcap/s7comm.pcap create mode 100644 tests/pcap/selfsigned.pcap create mode 100644 tests/pcap/simple-dnscrypt.pcap create mode 100644 tests/pcap/smb_deletefile.pcap create mode 100644 tests/pcap/teams.pcap create mode 100644 tests/pcap/teamspeak3.pcap create mode 100644 tests/pcap/telegram.pcap create mode 100644 tests/pcap/tftp_rrq.pcap create mode 100644 tests/pcap/tls-esni-fuzzed.pcap create mode 100644 tests/pcap/tls-rdn-extract.pcap create mode 100644 tests/pcap/tls_esni_sni_both.pcap create mode 100644 tests/pcap/tls_long_cert.pcap create mode 100644 tests/pcap/tls_verylong_certificate.pcap create mode 100644 tests/pcap/wa_video.pcap create mode 100644 tests/pcap/wa_voice.pcap create mode 100644 tests/pcap/websocket.pcap create mode 100644 tests/pcap/zabbix.pcap create mode 100644 tests/result/443-chrome.pcap.out create mode 100644 tests/result/443-curl.pcap.out create mode 100644 tests/result/443-firefox.pcap.out create mode 100644 tests/result/443-git.pcap.out create mode 100644 tests/result/443-opvn.pcap.out create mode 100644 tests/result/443-safari.pcap.out create mode 100644 tests/result/4in4tunnel.pcap.out create mode 100644 tests/result/4in6tunnel.pcap.out create mode 100644 tests/result/6in6tunnel.pcap.out create mode 100644 tests/result/WebattackRCE.pcap.out create mode 100644 tests/result/WebattackSQLinj.pcap.out create mode 100644 tests/result/WebattackXSS.pcap.out create mode 100644 tests/result/android.pcap.out create mode 100644 tests/result/anydesk.pcap.out create mode 100644 tests/result/bad-dns-traffic.pcap.out create mode 100644 tests/result/badpackets.pcap.out create mode 100644 tests/result/capwap.pcap.out create mode 100644 tests/result/dnp3.pcap.out create mode 100644 tests/result/dns-tunnel-iodine.pcap.out create mode 100644 tests/result/dns_doh.pcap.out create mode 100644 tests/result/dns_dot.pcap.out create mode 100644 tests/result/dns_exfiltration.pcap.out create mode 100644 tests/result/dns_long_domainname.pcap.out create mode 100644 tests/result/dnscrypt-v1-and-resolver-pings.pcap.out create mode 100644 tests/result/dnscrypt-v2-doh.pcap.out create mode 100644 tests/result/dos_win98_smb_netbeui.pcap.out create mode 100644 tests/result/dtls.pcap.out create mode 100644 tests/result/encrypted_sni.pcap.out create mode 100644 tests/result/exe_download.pcap.out create mode 100644 tests/result/exe_download_as_png.pcap.out create mode 100644 tests/result/fbzero-missing-lengthcheck.pcap.out create mode 100644 tests/result/ftp_failed.pcap.out create mode 100644 tests/result/fuzz-2006-06-26-2594.pcap.out create mode 100644 tests/result/fuzz-2006-09-29-28586.pcap.out create mode 100644 tests/result/fuzz-2020-02-16-11740.pcap.out create mode 100644 tests/result/googledns_android10.pcap.out create mode 100644 tests/result/gquic.pcap.out create mode 100644 tests/result/h323-overflow.pcap.out create mode 100644 tests/result/http-crash-content-disposition.pcap.out create mode 100644 tests/result/http-lines-split.pcap.out create mode 100644 tests/result/iec60780-5-104.pcap.out create mode 100644 tests/result/imaps.pcap.out create mode 100644 tests/result/iphone.pcap.out create mode 100644 tests/result/ipv6_in_gtp.pcap.out create mode 100644 tests/result/ja3_lots_of_cipher_suites.pcap.out create mode 100644 tests/result/ja3_lots_of_cipher_suites_2_anon.pcap.out create mode 100644 tests/result/kerberos.pcap.out create mode 100644 tests/result/malformed_dns.pcap.out create mode 100644 tests/result/malformed_icmp.pcap.out create mode 100644 tests/result/mysql-8.pcap.out create mode 100644 tests/result/nats.pcap.out create mode 100644 tests/result/netbios.pcap.out create mode 100644 tests/result/netbios_wildcard_dns_query.pcap.out create mode 100644 tests/result/netflow-fritz.pcap.out create mode 100644 tests/result/quic-23.pcap.out create mode 100644 tests/result/quic-24.pcap.out create mode 100644 tests/result/quic-27.pcap.out create mode 100644 tests/result/quic-28.pcap.out create mode 100644 tests/result/quic-29.pcap.out create mode 100644 tests/result/quic-mvfst-22.pcap.out create mode 100644 tests/result/quic-mvfst-22_decryption_error.pcap.out create mode 100644 tests/result/quic-mvfst-27.pcap.out create mode 100644 tests/result/quic-mvfst-exp.pcap.out create mode 100644 tests/result/quic046.pcap.out create mode 100644 tests/result/quic_q39.pcap.out create mode 100644 tests/result/quic_q43.pcap.out create mode 100644 tests/result/quic_q46.pcap.out create mode 100644 tests/result/quic_q46_b.pcap.out create mode 100644 tests/result/quic_q50.pcap.out create mode 100644 tests/result/quic_t50.pcap.out create mode 100644 tests/result/quic_t51.pcap.out create mode 100644 tests/result/s7comm.pcap.out create mode 100644 tests/result/selfsigned.pcap.out create mode 100644 tests/result/simple-dnscrypt.pcap.out create mode 100644 tests/result/smb_deletefile.pcap.out create mode 100644 tests/result/teams.pcap.out create mode 100644 tests/result/teamspeak3.pcap.out create mode 100644 tests/result/telegram.pcap.out create mode 100644 tests/result/tftp_rrq.pcap.out create mode 100644 tests/result/tls-esni-fuzzed.pcap.out create mode 100644 tests/result/tls-rdn-extract.pcap.out create mode 100644 tests/result/tls_esni_sni_both.pcap.out create mode 100644 tests/result/tls_long_cert.pcap.out create mode 100644 tests/result/tls_verylong_certificate.pcap.out create mode 100644 tests/result/wa_video.pcap.out create mode 100644 tests/result/wa_voice.pcap.out create mode 100644 tests/result/websocket.pcap.out create mode 100644 tests/result/zabbix.pcap.out create mode 100644 tests/unit/unit.c create mode 100755 tests/vagrind_test.sh create mode 100755 utils/bitcoinnodes.sh create mode 100644 windows/README.md create mode 100644 windows/nDPI.vcxproj create mode 100755 wireshark/download-fuzz-traces.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index b9af753..fd88d1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,145 @@ # CHANGELOG +#### nDPI 3.4 (October 2020) + +## New Features +* Completely reworked and extended QUIC dissector +* Added flow risk concept to move nDPI towards result interpretation +* Added ndpi_dpi2json() API call +* Added DGA risk for names that look like a DGA +* Added HyperLogLog cardinality estimator API calls +* Added ndpi_bin_XXX API calls to handle bin handling +* Fully fuzzy tested code that has greatly improved reliability and robustness + +## New Supported Protocols and Services +* QUIC +* SMBv1 +* WebSocket +* TLS: added ESNI support +* SOAP +* DNScrypt + +## Improvements +* Python CFFI bindings +* Various TLS extensions and fixes including extendede metadata support +* Added various pcap files for testing corner cases in protocols +* Various improvements in JSON/Binary data serialization +* CiscoVPN +* H323 +* MDNS +* MySQL 8 +* IEC 60870-5-104 +* DoH/DoT dissection improvements +* Office365 renamed to Microsoft365 +* Major protocol dissection improvement in particular with unknwon traffic +* Improvement in Telegram v6 protocol support +* HTTP improvements to detect file download/upload and binary files +* BitTorrent and WhatsApp dissection improvement +* Spotify +* Added detection of malformed packets +* Fuzzy testing support has been greatly improved +* SSH code cleanup + +## Fixes +* Fixed various memory leaks and race conditions in protocol decoding +* NATS, CAPWAP dissector +* Removed HyperScan support that greatly simplified the code +* ARM platform fixes on memory alignment +* Wireshark extcap support +* DPDK support +* OpenWRT, OpenBSD support +* MINGW compiler support + +## MISC +* Created demo app for nDPI newcomers +* Removed obsolete pplive and pando protocols + +#### nDPI 3.2 (February 2020) + +## New Features +* New API calls + * Protocol detection: ndpi_is_protocol_detected + * Categories: ndpi_load_categories_file / ndpi_load_category + * JSON/TLV serialization: ndpi_serialize_string_boolean / ndpi_serialize_uint32_boolean + * Patricia tree: ndpi_load_ipv4_ptree + * Module initialization: ndpi_init_detection_module / ndpi_finalize_initalization + * Base64 encoding: ndpi_base64_encode + * JSON exprot: ndpi_flow2json + * Print protocol: ndpi_get_l4_proto_name / ndpi_get_l4_proto_info +* Libfuzz integration +* Implemented Community ID hash (API call ndpi_flowv6_flow_hash and ndpi_flowv4_flow_hash) +* Detection of RCE in HTTP GET requests via PCRE +* Integration of the libinjection library to detect SQL injections and XSS type attacks in HTTP requests + +## New Supported Protocols and Services +* TLS + * Added ALPN support + * Added export of supported version in TLS header +* Added Telnet dissector with metadata extraction +* Added Zabbix dissector +* Added POP3/IMAP metadata extraction +* Added FTP user/password extraction +* Added NetBIOS metadata extraction +* Added Kerberos metadata extraction +* Implemented SQL Injection and XSS attack detection +* Host-based detection improvements and changes + * Added Microsoft range + * Added twitch.tv website + * Added brasilbandalarga.com.br and .eaqbr.com.br as EAQ + * Added 20.180.0.0/14, 20.184.0.0/13 range as Skype + * Added 52.84.0.0/14 range as Amazon + * Added ^pastebin.com + * Changed 13.64.0.0/11 range from Skype to Microsoft + * Refreshed Whatsapp server list, added *whatsapp-*.fbcdn.net IPs +* Added public DNSoverHTTPS servers + +## Improvements +* Reworked and improved the TLS dissector +* Reworked Kerberos dissector +* Improved DNS response decoding +* Support for DNS continuous flow dissection +* Improved Python bindings +* Improved Ethereum support +* Improved categories detection with streaming and HTTP +* Support for IP-based detection to compute the application protocol +* Renamed protocol 104 to IEC60870 (more meaningful) +* Added failed authentication support with FTP +* Renamed DNSoverHTTPS to handle bot DoH and DoT +* Implemented stacked DPI decoding +* Improvements for CapWAP and Bloomberg +* Improved SMB dissection +* Improved SSH dissection +* Added capwap support +* Modified API signatures for ndpi_ssl_version2str / ndpi_detection_giveup +* Removed ndpi_pref_http_dont_dissect_response / ndpi_pref_dns_dont_dissect_response (replaced by ndpi_extra_dissection_possible) + +## Fixes +* Fixed memory invalid access in SMTP and leaks in TLS +* Fixed a few memory leaks +* Fixrd invalid memory access in a few protocol dissectors (HTTP, memcached, Citrix, STUN, DNS, Amazon Video, TLS, Viber) +* Fixed IPv6 address format across the various platforms/distributions +* Fixed infinite loop in ndpi_workflow_process_packet +* Fixed SHA1 certificate detection +* Fixed custom protocol detection +* Fixed SMTP dissection (including email) +* Fixed Telnet dissection and invalid password report +* Fixed invalid category matching in HTTP +* Fixed Skype and STUN false positives +* Fixed SQL Injection detection +* Fixed invalid SMBv1 detection +* Fixed SSH dissection +* Fixed ndpi_ssl_version2str +* Fixed ndpi_extra_dissection_possible +* Fixed out of bounds read in ndpi_match_custom_category + +## Misc +* ndpiReader + * CSV output enhancements + * Added tunnelling decapsulation + * Improved HTTP reporting + +------------------------------------------------------------------------ + #### nDPI 3.0 (October 2019) ## New Features @@ -67,7 +207,6 @@ * Fix DNS rsp_addr missing in some tiny responses * Various hardening fixes - ------------------------------------------------------------------------ #### nDPI 2.8 (March 2019) @@ -143,12 +282,12 @@ ## Other * Deb and RPM packages: ndpi with shared libraries and binaries, ndpi-dev with headers and static libraries -* Protocols now have an optional subprotocol: Spotify cannot have subprotocols, DNS can (DNS.Spotify) +* Protocols now have an optional subprotocol: Spotify cannot have subprotocols, DNS can (DNS.Spotify) * New API functions: - - ndpi_fill_ip_protocol_category to handle ICMP flows category - - ndpi_flowv4_flow_hash and ndpi_flowv6_flow_hash to support the Community ID Flow Hashing (https://github.com/corelight/community-id-spec) - - ndpi_protocol2id to print the protocol as ID - - ndpi_get_custom_category_match to search host in custom categories + * ndpi_fill_ip_protocol_category to handle ICMP flows category + * ndpi_flowv4_flow_hash and ndpi_flowv6_flow_hash to support the Community ID Flow Hashing (https://github.com/corelight/community-id-spec) + * ndpi_protocol2id to print the protocol as ID + * ndpi_get_custom_category_match to search host in custom categories * Changed ndpi_detection_giveup API: guess is now part of the call * Added DPDK support to ndpiReader * Removed Musical.ly protocol (service no longer used) diff --git a/Makefile.am b/Makefile.am index 4090817..1e86ebf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,14 @@ ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = src/lib example tests +SUBDIRS = src/lib @EXTRA_TARGETS@ + +if BUILD_FUZZTARGETS +SUBDIRS += fuzz +endif pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = libndpi.pc -EXTRA_DIST = autogen.sh +EXTRA_DIST = README.md CHANGELOG.md CONTRIBUTING.md README.protocols autogen.sh configure.seed wireshark python windows utils packages doc/nDPI_QuickStartGuide.pages doc/nDPI_QuickStartGuide.pdf example/MacOS example/Win32 + +changelog: + git log --since={`curl -s https://github.com/ntop/ndpi/releases | grep datetime | head -n1 | egrep -o "[0-9]+\-[0-9]+\-[0-9]+"`} --name-only --pretty=format:" - %s" | grep "^ " > Changelog.latest diff --git a/README.md b/README.md index 2b062c1..e90800a 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,11 @@ [![Build Status](https://travis-ci.org/ntop/nDPI.png?branch=dev)](https://travis-ci.org/ntop/nDPI) [![Code Quality: Cpp](https://img.shields.io/lgtm/grade/cpp/g/ntop/nDPI.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ntop/nDPI/context:cpp) [![Total Alerts](https://img.shields.io/lgtm/alerts/g/ntop/nDPI.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ntop/nDPI/alerts) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/ndpi.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:ndpi) ## What is nDPI ? -nDPI is an open source LGPLv3 library for deep-packet inspection. Based on OpenDPI it includes ntop extensions. We have tried to push them into the OpenDPI source tree but nobody answered emails so we have decided to create our own source tree +nDPI® is an open source LGPLv3 library for deep-packet inspection. Based on OpenDPI it includes ntop extensions. We have tried to push them into the OpenDPI source tree but nobody answered emails so we have decided to create our own source tree ### How To Compile nDPI @@ -41,21 +42,15 @@ The entire procedure of adding new protocols in detail: 10. make check ### How to use nDPI to Block Selected Traffic - You can use nDPI to selectively block selected Internet traffic by embedding it onto an application (remember that nDPI is just a library). Both [ntopng](https://github.com/ntop/ntopng) and [nProbe cento](http://www.ntop.org/products/netflow/nprobe-cento/) can do this. +### nDPI-Related Projects +- [nfstream](https://github.com/aouinizied/nfstream) + ### DISCLAIMER While we do our best to detect network protocols, we cannot guarantee that our software is error free and 100% accurate in protocol detection. Please make sure that you respect the privacy of users and you have proper authorization to listen, capture and inspect network traffic. -### Creating A Source File Tar Ball - -If you want to distribute a source tar file of nDPI do: - -- make dist - -To ensure that a tar file includes all necessary files and to run tests on distribution do: - -- make distcheck +nDPI is a registered trademark in the US and EU. [ntopng_logo]: https://camo.githubusercontent.com/0f789abcef232035c05e0d2e82afa3cc3be46485/687474703a2f2f7777772e6e746f702e6f72672f77702d636f6e74656e742f75706c6f6164732f323031312f30382f6e746f706e672d69636f6e2d313530783135302e706e67 diff --git a/README.nDPI b/README.nDPI index 773f807..42d2d25 100644 --- a/README.nDPI +++ b/README.nDPI @@ -5,17 +5,18 @@ Prerequisites - GNU autotools/libtool - libpcap or PF_RING (optional but recommended) - apt-get install libjson-c-dev (for JSON support) +- libgcrypt20-dev (for QUIC support) On Ubuntu/Debian - apt-get install build-essential -- apt-get install git autoconf automake autogen libpcap-dev libtool +- apt-get install git autoconf automake autogen libpcap-dev libtool libgcrypt20-dev On Fedora/CentOS - yum groupinstall "Development tools" -- yum install git autoconf automake autogen libpcap-devel libtool +- yum install git autoconf automake autogen libpcap-devel libtool libgcrypt-devel On MacOSX (using http://brew.sh) -brew install autoconf automake libtool git +brew install autoconf automake libtool git libgcrypt On FreeBSD -- pkg install autoconf automake libtool gmake git +- pkg install autoconf automake libtool gmake git libgcrypt diff --git a/autogen.sh b/autogen.sh index 4967754..df734ce 100755 --- a/autogen.sh +++ b/autogen.sh @@ -1,7 +1,7 @@ #!/bin/sh NDPI_MAJOR="3" -NDPI_MINOR="0" +NDPI_MINOR="4" NDPI_PATCH="0" NDPI_VERSION_SHORT="$NDPI_MAJOR.$NDPI_MINOR.$NDPI_PATCH" @@ -12,10 +12,17 @@ AUTOMAKE=$(command -v automake) LIBTOOL=$(command -v libtool) LIBTOOLIZE=$(command -v libtoolize) AUTORECONF=$(command -v autoreconf) +PKG_CONFIG=$(command -v pkg-config) +FUZZY= if test -z $AUTOCONF; then echo "autoconf is missing: please install it and try again" exit +else + V=`autoconf --version | head -1 | cut -d ' ' -f 4` + if [ "$V" = '2.63' ]; then + FUZZY="dnl> " + fi fi if test -z $AUTOMAKE; then @@ -33,11 +40,17 @@ if test -z $AUTORECONF; then exit fi +if test -z $PKG_CONFIG; then + echo "pkg-config is missing: please install it (apt-get install pkg-config) and try again" + exit +fi + cat configure.seed | sed \ -e "s/@NDPI_MAJOR@/$NDPI_MAJOR/g" \ -e "s/@NDPI_MINOR@/$NDPI_MINOR/g" \ -e "s/@NDPI_PATCH@/$NDPI_PATCH/g" \ -e "s/@NDPI_VERSION_SHORT@/$NDPI_VERSION_SHORT/g" \ + -e "s/@FUZZY@/$FUZZY/g" \ > configure.ac autoreconf -ivf @@ -45,4 +58,5 @@ cat configure | sed "s/#define PACKAGE/#define NDPI_PACKAGE/g" | sed "s/#define cat configure.tmp > configure chmod +x configure -./configure $* +./configure $@ + diff --git a/configure.seed b/configure.seed index a3cc646..b333110 100644 --- a/configure.seed +++ b/configure.seed @@ -1,15 +1,39 @@ AC_INIT([libndpi], [@NDPI_VERSION_SHORT@]) +AC_CONFIG_AUX_DIR([.]) AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE([foreign subdir-objects]) +EXTRA_TARGETS="example tests tests/unit" +AC_ARG_WITH(only-libndpi, AS_HELP_STRING([--with-only-libndpi], [Build only libndpi (no examples, tests etc)])) +AS_IF([test "${with_only_libndpi+set}" = set],[ + EXTRA_TARGETS="" +]) + +AC_ARG_WITH(sanitizer, AS_HELP_STRING([--with-sanitizer], [Build with support for address, undefined and leak sanitizer])) +AC_ARG_ENABLE(fuzztargets, AS_HELP_STRING([--enable-fuzztargets], [Enable fuzz targets]),[enable_fuzztargets=$enableval],[enable_fuzztargets=no]) +AM_CONDITIONAL([BUILD_FUZZTARGETS], [test "x$enable_fuzztargets" = "xyes"]) + +AS_IF([test "${with_sanitizer+set}" = set],[ + CFLAGS="${CFLAGS} -g3 -O0 -Wno-unused-value -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fsanitize=leak -fno-omit-frame-pointer" + LDFLAGS="${LDFLAGS} -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fsanitize=leak" +]) + LT_INIT -AC_PROG_CC -AM_PROG_CC_C_O -AC_PROG_CXX -AC_PROG_CC_STDC +SYSTEM=`uname -s` +if test $SYSTEM = "Darwin"; then +dnl> AC_PROG_CC(clang gcc) + AM_PROG_CC_C_O(clang gcc) + AC_PROG_CXX(clang++ g++) + AC_PROG_CC_STDC(clang gcc) +else +dnl> AC_PROG_CC + AM_PROG_CC_C_O + AC_PROG_CXX + AC_PROG_CC_STDC +fi AC_LANG_WERROR AX_PTHREAD @@ -22,38 +46,52 @@ AC_DEFINE_UNQUOTED(NDPI_MAJOR_RELEASE, "${NDPI_MAJOR}", [nDPI major release]) AC_DEFINE_UNQUOTED(NDPI_MINOR_RELEASE, "${NDPI_MINOR}", [nDPI minor release]) AC_DEFINE_UNQUOTED(NDPI_PATCH_LEVEL, "${NDPI_PATCH}", [nDPI patch level]) -if test -d ".git"; then : +# .git as directory in a cloned repo +# .git as file in submodule based integration +if test -d ".git" || test -f ".git" ; then : GIT_TAG=`git log -1 --format=%h` GIT_DATE=`git log -1 --format=%cd` # # On CentOS 6 `git rev-list HEAD --count` does not work - # + # # GIT_NUM=`git log --pretty=oneline | wc -l | tr -d '[[:space:]]'` GIT_RELEASE="${PACKAGE_VERSION}-${GIT_NUM}-${GIT_TAG}" + + A=`git log src/include/ndpi_typedefs.h|wc -l` + B=`git log src/include/ndpi_protocol_ids.h|wc -l` + C=`git log src/include/ndpi_api.h.in|wc -l` + NDPI_API_VERSION=$((A+B+C)) else GIT_RELEASE="${PACKAGE_VERSION}" GIT_DATE=`date -u -r CHANGELOG.md` + NDPI_API_VERSION=`date +%s | cut -c7-10` fi +NDPI_API_VERSION=`echo $NDPI_API_VERSION | sed 's/^0*//'` + AC_DEFINE_UNQUOTED(NDPI_GIT_RELEASE, "${GIT_RELEASE}", [GIT Release]) AC_DEFINE_UNQUOTED(NDPI_GIT_DATE, "${GIT_DATE}", [Last GIT change]) -AC_CHECK_HEADERS([netinet/in.h stdint.h stdlib.h string.h unistd.h]) +dnl> used by json-c +JSONC_INC=`pkg-config --cflags json-c` +CFLAGS="${CFLAGS} ${JSONC_INC}" + +AC_CHECK_HEADERS([netinet/in.h stdint.h stdlib.h string.h unistd.h json.h]) +ADDITIONAL_LIBS= PCAP_HOME=$HOME/PF_RING/userland DPDK_TARGET= +AC_MSG_CHECKING([DPDK (used by ndpiReader)]) if test -d $HOME/DPDK; then : - echo "Enabling DPDK support in ndpiReader" + AC_MSG_RESULT(yes) DPDK_TARGET=dpdk else - echo "DPDK support disabled (missing $HOME/DPDK)" + AC_MSG_RESULT([no (missing $HOME/DPDK)]) fi -if test -d $PCAP_HOME; then : - echo -n "" -else +if ! test -d $PCAP_HOME; then : PCAP_HOME=`pwd`/../../PF_RING/userland fi SHORT_MACHINE=`uname -m | cut -b1-3` @@ -64,96 +102,114 @@ else fi MACHINE=`uname -m` -SYSTEM=`uname -s` -if test $SYSTEM = "Darwin"; then - CC=clang -fi - - -AC_ARG_WITH(hyperscan, [ --with-hyperscan Enable nDPI build with Intel Hyperscan]) - -AS_IF([test "${with_hyperscan+set}" = set],[ - BKP=$LIBS - LIBS="$LIBS -lstdc++ -lm" - AC_CHECK_LIB([hs], [hs_compile_multi], AC_DEFINE_UNQUOTED(HAVE_HYPERSCAN, 1, [Intel Hyperscan is present])) - LIBS=$BKP - - AS_IF([test "x$ac_cv_lib_hs_hs_compile_multi" = xyes],[ - AC_CHECK_LIB([m], [pow]) - AC_CHECK_LIB([stdc++], [main]) - PKG_CHECK_MODULES([HS],[libhs]) - LDFLAGS="$LDFLAGS $HS_LIBS" - AC_MSG_RESULT([compiling with Intel Hyperscan]) - ],[ - AC_MSG_RESULT([Intel Hyperscan not found, exiting. See https://github.com/intel/hyperscan/blob/master/doc/dev-reference/getting_started.rst for install/build instructions]) - exit 1 - ]) -]) -if test -f $PCAP_HOME/libpcap/libpcap.a; then : - echo "Using libpcap from $PCAP_HOME" - PCAP_INC="-I $PCAP_HOME/libpcap" - PCAP_LIB="$PCAP_HOME/libpcap/libpcap.a $PCAP_HOME/lib/libpfring.a $LIBNUMA `$PCAP_HOME/lib/pfring_config --libs`" - - AC_CHECK_LIB([rt], [clock_gettime], [PCAP_LIB="$PCAP_LIB -lrt"]) - AC_CHECK_LIB([nl], [nl_handle_alloc], [PCAP_LIB="$PCAP_LIB -lnl"]) - # The dlopen() function is in libdl on GLIBC-based systems - # and in the C library for *BSD systems - AC_CHECK_LIB([dl], [dlopen, dlsym], [DL_LIB="-ldl"], - [AC_CHECK_LIB([c], [dlopen, dlsym], [DL_LIB="-lc"], - [AC_MSG_ERROR([unable to find the dlopen(), dlsym() functions]) ]) ]) -else - AC_CHECK_LIB([pcap], [pcap_open_live], [PCAP_LIB="-lpcap"]) - - if test $ac_cv_lib_pcap_pcap_open_live = "no"; then : - echo "" - echo "ERROR: Missing libpcap(-dev) library required to compile the example application" - echo "ERROR: Please install it and try again" - exit - fi +CUSTOM_NDPI= + +if test -d ../nDPI-custom; then : + CUSTOM_NDPI="-DCUSTOM_NDPI_PROTOCOLS" + AC_MSG_RESULT([Compiling with custom nDPI protocols]) fi -dnl> https://github.com/json-c/json-c -AC_ARG_ENABLE([json-c], - AS_HELP_STRING([--disable-json-c], [Disable json-c support])) - -AS_IF([test "x$enable_json_c" != "xno"], [ - PKG_CHECK_MODULES([JSONC],[json-c], - [ - CFLAGS="$CFLAGS $JSONC_CFLAGS" - LDFLAGS="$LDFLAGS $JSONC_LIBS" - AC_CHECK_LIB(json-c, json_object_new_object, AC_DEFINE_UNQUOTED(HAVE_JSON_C, 1, [The JSON-C library is present])) - ], - [ - JSONC_HOME="$HOME/json-c" - if test -d "$JSONC_HOME"; then : - CFLAGS="$CFLAGS -I $JSONC_HOME" - LDFLAGS="$LDFLAGS $JSONC_HOME/.libs/libjson-c.a" - AC_MSG_RESULT([Found json-c in $JSONC_HOME]) - AC_DEFINE_UNQUOTED(HAVE_JSON_C, 1, [The JSON-C library is present]) - fi - ]) - ]) +case "$host" in + *-*-mingw32*|*-*-msys) + CFLAGS="${CFLAGS} -DOS_WIN32" + LDFLAGS="${LDFLAGS} -lws2_32 -lucrtbase" + BUILD_MINGW=1 + ;; + *) + if test -f $PCAP_HOME/libpcap/libpcap.a; then : + echo "Using libpcap from $PCAP_HOME" + PCAP_INC="-I $PCAP_HOME/libpcap" + PCAP_LIB="$PCAP_HOME/libpcap/libpcap.a $PCAP_HOME/lib/libpfring.a $LIBNUMA `$PCAP_HOME/lib/pfring_config --libs`" + AC_CHECK_LIB([rt], [clock_gettime], [PCAP_LIB="$PCAP_LIB -lrt"]) + AC_CHECK_LIB([nl], [nl_handle_alloc], [PCAP_LIB="$PCAP_LIB -lnl"]) + # The dlopen() function is in libdl on GLIBC-based systems + # and in the C library for *BSD systems + AC_CHECK_LIB([dl], [dlopen, dlsym], [DL_LIB="-ldl"],[AC_CHECK_LIB([c], [dlopen, dlsym], [DL_LIB="-lc"],[AC_MSG_ERROR([unable to find the dlopen(), dlsym() functions]) ]) ]) + else + AC_CHECK_LIB([pcap], [pcap_open_live], [PCAP_LIB="-lpcap"]) + if test $ac_cv_lib_pcap_pcap_open_live = "no"; then : + echo "" + echo "ERROR: Missing libpcap(-dev) library required to compile the example application" + echo "ERROR: Please install it and try again" + exit + fi + fi + ;; +esac AC_ARG_ENABLE([debug-messages], AS_HELP_STRING([--enable-debug-messages], [Define NDPI_ENABLE_DEBUG_MESSAGES=1]), [ AC_DEFINE(NDPI_ENABLE_DEBUG_MESSAGES, 1, [Enable ndpi_debug_messages]) ]) +@FUZZY@ AS_IF([test "x$enable_fuzztargets" = "xyes"], [ +@FUZZY@ AC_PROG_CXX +@FUZZY@ AC_LANG_PUSH(C++) +@FUZZY@ tmp_saved_flags=$[]_AC_LANG_PREFIX[]FLAGS +@FUZZY@ AX_CHECK_COMPILE_FLAG([-fsanitize=fuzzer],, +@FUZZY@ [AC_MSG_ERROR([--enable-fuzztargets requires -fsanitize=fuzzer which is only supported by LLVM])], +@FUZZY@ [-Werror]) +@FUZZY@ AS_IF([test "x$LIB_FUZZING_ENGINE" = "x"], [ +@FUZZY@ LIB_FUZZING_ENGINE=-fsanitize=fuzzer +@FUZZY@ AC_SUBST(LIB_FUZZING_ENGINE) +@FUZZY@ ]) +@FUZZY@ _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $LIB_FUZZING_ENGINE" +@FUZZY@ AC_MSG_CHECKING([whether $CXX accepts $LIB_FUZZING_ENGINE]) +@FUZZY@ AC_LINK_IFELSE([AC_LANG_SOURCE([[ +@FUZZY@ #include +@FUZZY@ extern "C" int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size); +@FUZZY@ extern "C" int LLVMFuzzerTestOneInput(const unsigned char *Data, size_t Size) { +@FUZZY@ (void)Data; +@FUZZY@ (void)Size; +@FUZZY@ return 0; +@FUZZY@ } +@FUZZY@ ]])], +@FUZZY@ [ AC_MSG_RESULT(yes) +@FUZZY@ has_sanitizefuzzer=yes], +@FUZZY@ [ AC_MSG_RESULT(no) ] +@FUZZY@ ) +@FUZZY@ _AC_LANG_PREFIX[]FLAGS=$tmp_saved_flags +@FUZZY@ AC_LANG_POP() +@FUZZY@ ]) +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_CONFIG_FILES([Makefile example/Makefile example/Makefile.dpdk tests/Makefile libndpi.pc src/include/ndpi_define.h src/lib/Makefile python/Makefile]) +dnl> GCRYPT +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)]) +if test "x$ac_cv_lib_gcrypt_gcry_cipher_checktag" = xyes; then : + ADDITIONAL_LIBS="${ADDITIONAL_LIBS} -lgcrypt" +fi + +dnl> PCRE +AC_ARG_WITH(pcre, [ --with-pcre Enable nDPI build with libpcre]) +if test "${with_pcre+set}" = set; then : + AC_CHECK_LIB(pcre, pcre_compile, AC_DEFINE_UNQUOTED(HAVE_PCRE, 1, [libpcre(-dev) is present])) + if test "x$ac_cv_lib_pcre_pcre_compile" = xyes; then : + ADDITIONAL_LIBS="${ADDITIONAL_LIBS} -lpcre" + fi +fi + + +AC_CONFIG_FILES([Makefile example/Makefile example/Makefile.dpdk tests/Makefile tests/unit/Makefile libndpi.pc src/include/ndpi_define.h src/lib/Makefile python/Makefile fuzz/Makefile src/include/ndpi_api.h]) AC_CONFIG_HEADERS(src/include/ndpi_config.h) AC_SUBST(GIT_RELEASE) AC_SUBST(NDPI_MAJOR) AC_SUBST(NDPI_MINOR) AC_SUBST(NDPI_PATCH) AC_SUBST(NDPI_VERSION_SHORT) -AC_SUBST(SVN_DATE) -AC_SUBST(JSON_C_LIB) AC_SUBST(PCAP_INC) AC_SUBST(PCAP_LIB) +AC_SUBST(ADDITIONAL_LIBS) AC_SUBST(DL_LIB) AC_SUBST(DPDK_TARGET) AC_SUBST(HAVE_PTHREAD_SETAFFINITY_NP) - +AC_SUBST(CUSTOM_NDPI) +AC_SUBST(NDPI_API_VERSION) +AC_SUBST(EXTRA_TARGETS) +AC_SUBST(BUILD_MINGW) AC_OUTPUT diff --git a/debian/changelog b/debian/changelog index 9048383..02583e6 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,28 @@ +ndpi (3.4-1) stable; urgency=medium + + * Changes in debian folder for version 3.4 + - refresh patch files for latest ndpi version (also kept old files) + - Update series file + - Update rules file ( disable tests with a broken pcap file ) + - Update control file + - Update changelog file + - Added docs, symbols and install files for ndpi3.4 (kept old files) + - Added libndpi3.4 in libndpi3.4.symbols file + * Updated with latest nDPI code + * Removed obsolete protocol files from nDPI src/lib/protocols + - battlefield.c + - line.c + - mdns_proto.c + - msn.c + - oscar.c + - pando.c + - pcanywhere.c + - pplive.c + - tvants.c + - yahoo.c + + -- Subhajit Chatterjee Tue, 10 Nov 2020 14:25:50 +0100 + ndpi (3.0-2) unstable; urgency=medium * Remove libndpi-wireshark package diff --git a/debian/control b/debian/control index 4effd03..e312ba6 100644 --- a/debian/control +++ b/debian/control @@ -17,7 +17,7 @@ Vcs-Browser: https://salsa.debian.org/debian/ndpi Package: libndpi-dev Section: libdevel Architecture: any -Depends: libndpi3.0 (= ${binary:Version}), ${misc:Depends} +Depends: libndpi3.4 (= ${binary:Version}), ${misc:Depends} Description: extensible deep packet inspection library - development files nDPI is a ntop-maintained superset of the popular OpenDPI library. Released under the LGPL license, its goal is to extend the original library by adding @@ -33,7 +33,7 @@ Description: extensible deep packet inspection library - development files . This package contains the static library and compilation headers. -Package: libndpi3.0 +Package: libndpi3.4 Architecture: any Pre-Depends: ${misc:Pre-Depends} Depends: ${shlibs:Depends}, ${misc:Depends} diff --git a/debian/libndpi3.4.docs b/debian/libndpi3.4.docs new file mode 100644 index 0000000..48749cf --- /dev/null +++ b/debian/libndpi3.4.docs @@ -0,0 +1 @@ +README.protocols diff --git a/debian/libndpi3.4.install b/debian/libndpi3.4.install new file mode 100644 index 0000000..1f82b25 --- /dev/null +++ b/debian/libndpi3.4.install @@ -0,0 +1 @@ +usr/lib/*/libndpi*.so.* diff --git a/debian/libndpi3.4.symbols b/debian/libndpi3.4.symbols new file mode 100644 index 0000000..9a19161 --- /dev/null +++ b/debian/libndpi3.4.symbols @@ -0,0 +1,696 @@ +libndpi.so.3.4 libndpi3.4 #MINVER# + MurmurHash3_x86_32@Base 3.4 + NDPI_BITMASK_COMPARE@Base 3.4 + NDPI_PROTOCOL_IP_clear@Base 3.4 + SHA1Final@Base 3.4 + SHA1Init@Base 3.4 + SHA1Transform@Base 3.4 + SHA1Update@Base 3.4 + _hll_hash@Base 3.4 + ac_automata_add@Base 3.4 + ac_automata_display@Base 3.4 + ac_automata_finalize@Base 3.4 + ac_automata_init@Base 3.4 + ac_automata_release@Base 3.4 + ac_automata_search@Base 3.4 + bt_decode@Base 3.4 + bt_parse_debug@Base 3.4 + cache_add@Base 3.4 + cache_contains@Base 3.4 + cache_entry_map_new@Base 3.4 + cache_entry_new@Base 3.4 + cache_free@Base 3.4 + cache_new@Base 3.4 + cache_remove@Base 3.4 + cache_touch_entry@Base 3.4 + cb_data@Base 3.4 + check_ndpi_other_flow_func@Base 3.4 + get_stun_lru_key@Base 3.4 + hll_add@Base 3.4 + hll_count@Base 3.4 + hll_destroy@Base 3.4 + hll_init@Base 3.4 + hll_reset@Base 3.4 + ht_create@Base 3.4 + ht_free@Base 3.4 + ht_get@Base 3.4 + ht_hash@Base 3.4 + ht_newpair@Base 3.4 + ht_set@Base 3.4 + http_process_user_agent@Base 3.4 + init_104_dissector@Base 3.4 + init_afp_dissector@Base 3.4 + init_aimini_dissector@Base 3.4 + init_ajp_dissector@Base 3.4 + init_amazon_video_dissector@Base 3.4 + init_amqp_dissector@Base 3.4 + init_apple_push_dissector@Base 3.4 + init_applejuice_dissector@Base 3.4 + init_armagetron_dissector@Base 3.4 + init_ayiya_dissector@Base 3.4 + init_bgp_dissector@Base 3.4 + init_bittorrent_dissector@Base 3.4 + init_bjnp_dissector@Base 3.4 + init_capwap_dissector@Base 3.4 + init_checkmk_dissector@Base 3.4 + init_ciscovpn_dissector@Base 3.4 + init_citrix_dissector@Base 3.4 + init_coap_dissector@Base 3.4 + init_corba_dissector@Base 3.4 + init_crossfire_dissector@Base 3.4 + init_csgo_dissector@Base 3.4 + init_dcerpc_dissector@Base 3.4 + init_dhcp_dissector@Base 3.4 + init_dhcpv6_dissector@Base 3.4 + init_diameter_dissector@Base 3.4 + init_directconnect_dissector@Base 3.4 + init_directdownloadlink_dissector@Base 3.4 + init_dnp3_dissector@Base 3.4 + init_dns_dissector@Base 3.4 + init_dnscrypt_dissector@Base 3.4 + init_dofus_dissector@Base 3.4 + init_drda_dissector@Base 3.4 + init_dropbox_dissector@Base 3.4 + init_eaq_dissector@Base 3.4 + init_edonkey_dissector@Base 3.4 + init_fasttrack_dissector@Base 3.4 + init_fbzero_dissector@Base 3.4 + init_fiesta_dissector@Base 3.4 + init_fix_dissector@Base 3.4 + init_florensia_dissector@Base 3.4 + init_ftp_control_dissector@Base 3.4 + init_ftp_data_dissector@Base 3.4 + init_git_dissector@Base 3.4 + init_gnutella_dissector@Base 3.4 + init_gtp_dissector@Base 3.4 + init_guildwars_dissector@Base 3.4 + init_h323_dissector@Base 3.4 + init_halflife2_dissector@Base 3.4 + init_hangout_dissector@Base 3.4 + init_http_activesync_dissector@Base 3.4 + init_http_dissector@Base 3.4 + init_iax_dissector@Base 3.4 + init_icecast_dissector@Base 3.4 + init_imo_dissector@Base 3.4 + init_ipp_dissector@Base 3.4 + init_irc_dissector@Base 3.4 + init_jabber_dissector@Base 3.4 + init_kakaotalk_voice_dissector@Base 3.4 + init_kerberos_dissector@Base 3.4 + init_kontiki_dissector@Base 3.4 + init_ldap_dissector@Base 3.4 + init_lisp_dissector@Base 3.4 + init_lotus_notes_dissector@Base 3.4 + init_mail_imap_dissector@Base 3.4 + init_mail_pop_dissector@Base 3.4 + init_mail_smtp_dissector@Base 3.4 + init_maplestory_dissector@Base 3.4 + init_megaco_dissector@Base 3.4 + init_memcached_dissector@Base 3.4 + init_mgpc_dissector@Base 3.4 + init_mining_dissector@Base 3.4 + init_modbus_dissector@Base 3.4 + init_mpegts_dissector@Base 3.4 + init_mqtt_dissector@Base 3.4 + init_mssql_tds_dissector@Base 3.4 + init_mysql_dissector@Base 3.4 + init_nats_dissector@Base 3.4 + init_nest_log_sink_dissector@Base 3.4 + init_netbios_dissector@Base 3.4 + init_netflow_dissector@Base 3.4 + init_nfs_dissector@Base 3.4 + init_nintendo_dissector@Base 3.4 + init_noe_dissector@Base 3.4 + init_non_tcp_udp_dissector@Base 3.4 + init_ntp_dissector@Base 3.4 + init_ookla_dissector@Base 3.4 + init_openft_dissector@Base 3.4 + init_openvpn_dissector@Base 3.4 + init_oracle_dissector@Base 3.4 + init_postgres_dissector@Base 3.4 + init_ppstream_dissector@Base 3.4 + init_pptp_dissector@Base 3.4 + init_qq_dissector@Base 3.4 + init_quic_dissector@Base 3.4 + init_radius_dissector@Base 3.4 + init_rdp_dissector@Base 3.4 + init_redis_dissector@Base 3.4 + init_rsync_dissector@Base 3.4 + init_rtcp_dissector@Base 3.4 + init_rtmp_dissector@Base 3.4 + init_rtp_dissector@Base 3.4 + init_rtsp_dissector@Base 3.4 + init_rx_dissector@Base 3.4 + init_s7comm_dissector@Base 3.4 + init_sflow_dissector@Base 3.4 + init_shoutcast_dissector@Base 3.4 + init_sip_dissector@Base 3.4 + init_skinny_dissector@Base 3.4 + init_skype_dissector@Base 3.4 + init_smb_dissector@Base 3.4 + init_smpp_dissector@Base 3.4 + init_snmp_dissector@Base 3.4 + init_soap_dissector@Base 3.4 + init_socks_dissector@Base 3.4 + init_someip_dissector@Base 3.4 + init_sopcast_dissector@Base 3.4 + init_soulseek_dissector@Base 3.4 + init_spotify_dissector@Base 3.4 + init_ssdp_dissector@Base 3.4 + init_ssh_dissector@Base 3.4 + init_starcraft_dissector@Base 3.4 + init_stealthnet_dissector@Base 3.4 + init_steam_dissector@Base 3.4 + init_stun_dissector@Base 3.4 + init_syslog_dissector@Base 3.4 + init_targus_getdata_dissector@Base 3.4 + init_teamspeak_dissector@Base 3.4 + init_teamviewer_dissector@Base 3.4 + init_telegram_dissector@Base 3.4 + init_telnet_dissector@Base 3.4 + init_teredo_dissector@Base 3.4 + init_tftp_dissector@Base 3.4 + init_thunder_dissector@Base 3.4 + init_tinc_dissector@Base 3.4 + init_tls_dissector@Base 3.4 + init_tor_dissector@Base 3.4 + init_tvuplayer_dissector@Base 3.4 + init_ubntac2_dissector@Base 3.4 + init_upnp_dissector@Base 3.4 + init_usenet_dissector@Base 3.4 + init_vhua_dissector@Base 3.4 + init_viber_dissector@Base 3.4 + init_vmware_dissector@Base 3.4 + init_vnc_dissector@Base 3.4 + init_warcraft3_dissector@Base 3.4 + init_websocket_dissector@Base 3.4 + init_whatsapp_dissector@Base 3.4 + init_whois_das_dissector@Base 3.4 + init_wireguard_dissector@Base 3.4 + init_world_of_kung_fu_dissector@Base 3.4 + init_world_of_warcraft_dissector@Base 3.4 + init_xbox_dissector@Base 3.4 + init_xdmcp_dissector@Base 3.4 + init_zabbix_dissector@Base 3.4 + init_zattoo_dissector@Base 3.4 + init_zmq_dissector@Base 3.4 + is_diameter@Base 3.4 + is_udp_guessable_protocol@Base 3.4 + is_version_with_var_int_transport_params@Base 3.4 + jenkins_one_at_a_time_hash@Base 3.4 + libinjection_h5_init@Base 3.4 + libinjection_h5_next@Base 3.4 + libinjection_is_sqli@Base 3.4 + libinjection_is_xss@Base 3.4 + libinjection_sqli@Base 3.4 + libinjection_sqli_blacklist@Base 3.4 + libinjection_sqli_callback@Base 3.4 + libinjection_sqli_check_fingerprint@Base 3.4 + libinjection_sqli_fingerprint@Base 3.4 + libinjection_sqli_fold@Base 3.4 + libinjection_sqli_get_token@Base 3.4 + libinjection_sqli_init@Base 3.4 + libinjection_sqli_lookup_word@Base 3.4 + libinjection_sqli_not_whitelist@Base 3.4 + libinjection_sqli_reset@Base 3.4 + libinjection_sqli_tokenize@Base 3.4 + libinjection_version@Base 3.4 + libinjection_xss@Base 3.4 + ndpi_Clear_Patricia@Base 3.4 + ndpi_Destroy_Patricia@Base 3.4 + ndpi_MD5Final@Base 3.4 + ndpi_MD5Init@Base 3.4 + ndpi_MD5Update@Base 3.4 + ndpi_New_Patricia@Base 3.4 + ndpi_add_string_to_automa@Base 3.4 + ndpi_add_string_value_to_automa@Base 3.4 + ndpi_alloc_data_analysis@Base 3.4 + ndpi_apply_flow_protocol_to_packet@Base 3.4 + ndpi_base64_decode@Base 3.4 + ndpi_base64_encode@Base 3.4 + ndpi_bin_similarity@Base 3.4 + ndpi_build_default_ports@Base 3.4 + ndpi_bytestream_dec_or_hex_to_number64@Base 3.4 + ndpi_bytestream_to_ipv4@Base 3.4 + ndpi_bytestream_to_number64@Base 3.4 + ndpi_bytestream_to_number@Base 3.4 + ndpi_calloc@Base 3.4 + ndpi_category_get_name@Base 3.4 + ndpi_category_set_name@Base 3.4 + ndpi_check_dga_name@Base 3.4 + ndpi_check_flow_func@Base 3.4 + ndpi_check_for_email_address@Base 3.4 + ndpi_check_punycode_string@Base 3.4 + ndpi_check_rx@Base 3.4 + ndpi_check_starcraft_tcp@Base 3.4 + ndpi_check_starcraft_udp@Base 3.4 + ndpi_cipher2str@Base 3.4 + ndpi_classify@Base 3.4 + ndpi_clone_bin@Base 3.4 + ndpi_cluster_bins@Base 3.4 + ndpi_connection_tracking@Base 3.4 + ndpi_data_add_value@Base 3.4 + ndpi_data_average@Base 3.4 + ndpi_data_entropy@Base 3.4 + ndpi_data_last@Base 3.4 + ndpi_data_max@Base 3.4 + ndpi_data_min@Base 3.4 + ndpi_data_print_window_values@Base 3.4 + ndpi_data_ratio2str@Base 3.4 + ndpi_data_ratio@Base 3.4 + ndpi_data_stddev@Base 3.4 + ndpi_data_variance@Base 3.4 + ndpi_data_window_average@Base 3.4 + ndpi_data_window_stddev@Base 3.4 + ndpi_data_window_variance@Base 3.4 + ndpi_debug_printf@Base 3.4 + ndpi_default_ports_tree_node_t_walker@Base 3.4 + ndpi_deserialize_clone_all@Base 3.4 + ndpi_deserialize_clone_item@Base 3.4 + ndpi_deserialize_get_format@Base 3.4 + ndpi_deserialize_get_item_type@Base 3.4 + ndpi_deserialize_key_string@Base 3.4 + ndpi_deserialize_key_uint32@Base 3.4 + ndpi_deserialize_next@Base 3.4 + ndpi_deserialize_value_float@Base 3.4 + ndpi_deserialize_value_int32@Base 3.4 + ndpi_deserialize_value_int64@Base 3.4 + ndpi_deserialize_value_string@Base 3.4 + ndpi_deserialize_value_uint32@Base 3.4 + ndpi_deserialize_value_uint64@Base 3.4 + ndpi_detection_get_l4@Base 3.4 + ndpi_detection_get_sizeof_ndpi_flow_struct@Base 3.4 + ndpi_detection_get_sizeof_ndpi_flow_tcp_struct@Base 3.4 + ndpi_detection_get_sizeof_ndpi_flow_udp_struct@Base 3.4 + ndpi_detection_get_sizeof_ndpi_id_struct@Base 3.4 + ndpi_detection_giveup@Base 3.4 + ndpi_detection_process_packet@Base 3.4 + ndpi_dpi2json@Base 3.4 + ndpi_dump_protocols@Base 3.4 + ndpi_enable_loaded_categories@Base 3.4 + ndpi_exclude_protocol@Base 3.4 + ndpi_exit_detection_module@Base 3.4 + ndpi_extra_dissection_possible@Base 3.4 + ndpi_extra_search_mail_pop_tcp@Base 3.4 + ndpi_extra_search_mail_smtp_tcp@Base 3.4 + ndpi_fill_ip_protocol_category@Base 3.4 + ndpi_fill_protocol_category@Base 3.4 + ndpi_finalize_automa@Base 3.4 + ndpi_finalize_initalization@Base 3.4 + ndpi_flow2json@Base 3.4 + ndpi_flow_free@Base 3.4 + ndpi_flow_malloc@Base 3.4 + ndpi_flowv4_flow_hash@Base 3.4 + ndpi_flowv6_flow_hash@Base 3.4 + ndpi_free@Base 3.4 + ndpi_free_automa@Base 3.4 + ndpi_free_bin@Base 3.4 + ndpi_free_data_analysis@Base 3.4 + ndpi_free_flow@Base 3.4 + ndpi_get_api_version@Base 3.4 + ndpi_get_bin_value@Base 3.4 + ndpi_get_category_id@Base 3.4 + ndpi_get_custom_category_match@Base 3.4 + ndpi_get_flow_masterprotocol@Base 3.4 + ndpi_get_gcrypt_version@Base 3.4 + ndpi_get_http_content_type@Base 3.4 + ndpi_get_http_method@Base 3.4 + ndpi_get_http_url@Base 3.4 + ndpi_get_ip_string@Base 3.4 + ndpi_get_l4_proto_info@Base 3.4 + ndpi_get_l4_proto_name@Base 3.4 + ndpi_get_lower_proto@Base 3.4 + ndpi_get_mc_rep_times@Base 3.4 + ndpi_get_ndpi_detection_module_size@Base 3.4 + ndpi_get_ndpi_num_custom_protocols@Base 3.4 + ndpi_get_ndpi_num_supported_protocols@Base 3.4 + ndpi_get_num_supported_protocols@Base 3.4 + ndpi_get_proto_breed@Base 3.4 + ndpi_get_proto_breed_name@Base 3.4 + ndpi_get_proto_by_id@Base 3.4 + ndpi_get_proto_by_name@Base 3.4 + ndpi_get_proto_category@Base 3.4 + ndpi_get_proto_defaults@Base 3.4 + ndpi_get_proto_name@Base 3.4 + ndpi_get_protocol_id@Base 3.4 + ndpi_get_protocol_id_master_proto@Base 3.4 + ndpi_guess_host_protocol_id@Base 3.4 + ndpi_guess_protocol_id@Base 3.4 + ndpi_guess_undetected_protocol@Base 3.4 + ndpi_handle_ipv6_extension_headers@Base 3.4 + ndpi_handle_rule@Base 3.4 + ndpi_has_human_readeable_string@Base 3.4 + ndpi_hll_add@Base 3.4 + ndpi_hll_add_number@Base 3.4 + ndpi_hll_count@Base 3.4 + ndpi_hll_destroy@Base 3.4 + ndpi_hll_init@Base 3.4 + ndpi_hll_reset@Base 3.4 + ndpi_htonll@Base 3.4 + ndpi_http_method2str@Base 3.4 + ndpi_http_str2method@Base 3.4 + ndpi_inc_bin@Base 3.4 + ndpi_init_automa@Base 3.4 + ndpi_init_bin@Base 3.4 + ndpi_init_data_analysis@Base 3.4 + ndpi_init_deserializer@Base 3.4 + ndpi_init_deserializer_buf@Base 3.4 + ndpi_init_detection_module@Base 3.4 + ndpi_init_protocol_match@Base 3.4 + ndpi_init_serializer@Base 3.4 + ndpi_init_serializer_ll@Base 3.4 + ndpi_int_change_category@Base 3.4 + ndpi_int_change_flow_protocol@Base 3.4 + ndpi_int_change_packet_protocol@Base 3.4 + ndpi_int_change_protocol@Base 3.4 + ndpi_int_reset_packet_protocol@Base 3.4 + ndpi_int_reset_protocol@Base 3.4 + ndpi_int_stun_add_connection@Base 3.4 + ndpi_ips_match@Base 3.4 + ndpi_is_custom_category@Base 3.4 + ndpi_is_ipv6@Base 3.4 + ndpi_is_proto@Base 3.4 + ndpi_is_protocol_detected@Base 3.4 + ndpi_is_safe_ssl_cipher@Base 3.4 + ndpi_is_subprotocol_informative@Base 3.4 + ndpi_is_tls_tor@Base 3.4 + ndpi_is_tor_flow@Base 3.4 + ndpi_load_categories_file@Base 3.4 + ndpi_load_category@Base 3.4 + ndpi_load_hostname_category@Base 3.4 + ndpi_load_ip_category@Base 3.4 + ndpi_load_ipv4_ptree@Base 3.4 + ndpi_load_protocols_file@Base 3.4 + ndpi_log_timestamp@Base 3.4 + ndpi_lru_add_to_cache@Base 3.4 + ndpi_lru_cache_init@Base 3.4 + ndpi_lru_find_cache@Base 3.4 + ndpi_lru_free_cache@Base 3.4 + ndpi_malloc@Base 3.4 + ndpi_match_bigram@Base 3.4 + ndpi_match_content_subprotocol@Base 3.4 + ndpi_match_custom_category@Base 3.4 + ndpi_match_host_subprotocol@Base 3.4 + ndpi_match_hostname_protocol@Base 3.4 + ndpi_match_prefix@Base 3.4 + ndpi_match_string@Base 3.4 + ndpi_match_string_protocol_id@Base 3.4 + ndpi_match_string_subprotocol@Base 3.4 + ndpi_match_string_value@Base 3.4 + ndpi_md5@Base 3.4 + ndpi_merge_splt_arrays@Base 3.4 + ndpi_net_match@Base 3.4 + ndpi_netbios_name_interpret@Base 3.4 + ndpi_network_port_ptree_match@Base 3.4 + ndpi_network_ptree_match@Base 3.4 + ndpi_normalize_bin@Base 3.4 + ndpi_ntohll@Base 3.4 + ndpi_packet_dst_ip_eql@Base 3.4 + ndpi_packet_dst_ip_get@Base 3.4 + ndpi_packet_src_ip_eql@Base 3.4 + ndpi_packet_src_ip_get@Base 3.4 + ndpi_parameters_bd@Base 3.4 + ndpi_parameters_splt@Base 3.4 + ndpi_parse_ip_string@Base 3.4 + ndpi_parse_packet_line_info@Base 3.4 + ndpi_parse_packet_line_info_any@Base 3.4 + ndpi_patchIPv6Address@Base 3.4 + ndpi_patricia_lookup@Base 3.4 + ndpi_patricia_process@Base 3.4 + ndpi_patricia_remove@Base 3.4 + ndpi_patricia_search_best2@Base 3.4 + ndpi_patricia_search_best@Base 3.4 + ndpi_patricia_search_exact@Base 3.4 + ndpi_patricia_walk_inorder@Base 3.4 + ndpi_print_bin@Base 3.4 + ndpi_process_extra_packet@Base 3.4 + ndpi_protocol2id@Base 3.4 + ndpi_protocol2name@Base 3.4 + ndpi_ptree_create@Base 3.4 + ndpi_ptree_destroy@Base 3.4 + ndpi_ptree_insert@Base 3.4 + ndpi_ptree_match_addr@Base 3.4 + ndpi_quick_16_byte_hash@Base 3.4 + ndpi_realloc@Base 3.4 + ndpi_reset_bin@Base 3.4 + ndpi_reset_data_analysis@Base 3.4 + ndpi_reset_serializer@Base 3.4 + ndpi_revision@Base 3.4 + ndpi_risk2str@Base 3.4 + ndpi_search_activesync@Base 3.4 + ndpi_search_afp@Base 3.4 + ndpi_search_aimini@Base 3.4 + ndpi_search_ajp@Base 3.4 + ndpi_search_amazon_video@Base 3.4 + ndpi_search_amqp@Base 3.4 + ndpi_search_apple_push@Base 3.4 + ndpi_search_applejuice_tcp@Base 3.4 + ndpi_search_armagetron_udp@Base 3.4 + ndpi_search_ayiya@Base 3.4 + ndpi_search_bgp@Base 3.4 + ndpi_search_bittorrent@Base 3.4 + ndpi_search_bjnp@Base 3.4 + ndpi_search_capwap@Base 3.4 + ndpi_search_checkmk@Base 3.4 + ndpi_search_ciscovpn@Base 3.4 + ndpi_search_citrix@Base 3.4 + ndpi_search_coap@Base 3.4 + ndpi_search_collectd@Base 3.4 + ndpi_search_corba@Base 3.4 + ndpi_search_crossfire_tcp_udp@Base 3.4 + ndpi_search_csgo@Base 3.4 + ndpi_search_dcerpc@Base 3.4 + ndpi_search_dhcp_udp@Base 3.4 + ndpi_search_dhcpv6_udp@Base 3.4 + ndpi_search_diameter@Base 3.4 + ndpi_search_direct_download_link_tcp@Base 3.4 + ndpi_search_directconnect@Base 3.4 + ndpi_search_dnp3_tcp@Base 3.4 + ndpi_search_dnscrypt@Base 3.4 + ndpi_search_dofus@Base 3.4 + ndpi_search_drda@Base 3.4 + ndpi_search_dropbox@Base 3.4 + ndpi_search_eaq@Base 3.4 + ndpi_search_edonkey@Base 3.4 + ndpi_search_fasttrack_tcp@Base 3.4 + ndpi_search_fbzero@Base 3.4 + ndpi_search_fiesta@Base 3.4 + ndpi_search_fix@Base 3.4 + ndpi_search_florensia@Base 3.4 + ndpi_search_ftp_control@Base 3.4 + ndpi_search_ftp_data@Base 3.4 + ndpi_search_git@Base 3.4 + ndpi_search_gnutella@Base 3.4 + ndpi_search_gtp@Base 3.4 + ndpi_search_guildwars_tcp@Base 3.4 + ndpi_search_h323@Base 3.4 + ndpi_search_halflife2@Base 3.4 + ndpi_search_hangout@Base 3.4 + ndpi_search_iax@Base 3.4 + ndpi_search_icecast_tcp@Base 3.4 + ndpi_search_iec60870_tcp@Base 3.4 + ndpi_search_imo@Base 3.4 + ndpi_search_in_non_tcp_udp@Base 3.4 + ndpi_search_ipp@Base 3.4 + ndpi_search_irc_ssl_detect_ninety_percent_but_very_fast@Base 3.4 + ndpi_search_irc_tcp@Base 3.4 + ndpi_search_jabber_tcp@Base 3.4 + ndpi_search_kakaotalk_voice@Base 3.4 + ndpi_search_kerberos@Base 3.4 + ndpi_search_kontiki@Base 3.4 + ndpi_search_ldap@Base 3.4 + ndpi_search_lisp@Base 3.4 + ndpi_search_lotus_notes@Base 3.4 + ndpi_search_mail_imap_tcp@Base 3.4 + ndpi_search_mail_pop_tcp@Base 3.4 + ndpi_search_mail_smtp_tcp@Base 3.4 + ndpi_search_maplestory@Base 3.4 + ndpi_search_megaco@Base 3.4 + ndpi_search_memcached@Base 3.4 + ndpi_search_mgcp@Base 3.4 + ndpi_search_mining_tcp@Base 3.4 + ndpi_search_mining_udp@Base 3.4 + ndpi_search_modbus_tcp@Base 3.4 + ndpi_search_mpegts@Base 3.4 + ndpi_search_mqtt@Base 3.4 + ndpi_search_mssql_tds@Base 3.4 + ndpi_search_mysql_tcp@Base 3.4 + ndpi_search_nats_tcp@Base 3.4 + ndpi_search_nest_log_sink@Base 3.4 + ndpi_search_netbios@Base 3.4 + ndpi_search_netflow@Base 3.4 + ndpi_search_nfs@Base 3.4 + ndpi_search_nintendo@Base 3.4 + ndpi_search_noe@Base 3.4 + ndpi_search_ntp_udp@Base 3.4 + ndpi_search_ookla@Base 3.4 + ndpi_search_openft_tcp@Base 3.4 + ndpi_search_openvpn@Base 3.4 + ndpi_search_oracle@Base 3.4 + ndpi_search_postgres_tcp@Base 3.4 + ndpi_search_ppstream@Base 3.4 + ndpi_search_pptp@Base 3.4 + ndpi_search_qq@Base 3.4 + ndpi_search_quic@Base 3.4 + ndpi_search_radius@Base 3.4 + ndpi_search_rdp@Base 3.4 + ndpi_search_redis@Base 3.4 + ndpi_search_rsync@Base 3.4 + ndpi_search_rtcp@Base 3.4 + ndpi_search_rtmp@Base 3.4 + ndpi_search_rtp@Base 3.4 + ndpi_search_rtsp_tcp_udp@Base 3.4 + ndpi_search_rx@Base 3.4 + ndpi_search_s7comm_tcp@Base 3.4 + ndpi_search_sflow@Base 3.4 + ndpi_search_shoutcast_tcp@Base 3.4 + ndpi_search_sip@Base 3.4 + ndpi_search_skinny@Base 3.4 + ndpi_search_skype@Base 3.4 + ndpi_search_smb_tcp@Base 3.4 + ndpi_search_smpp_tcp@Base 3.4 + ndpi_search_snmp@Base 3.4 + ndpi_search_soap@Base 3.4 + ndpi_search_socks@Base 3.4 + ndpi_search_someip@Base 3.4 + ndpi_search_sopcast@Base 3.4 + ndpi_search_soulseek_tcp@Base 3.4 + ndpi_search_spotify@Base 3.4 + ndpi_search_ssdp@Base 3.4 + ndpi_search_starcraft@Base 3.4 + ndpi_search_stealthnet@Base 3.4 + ndpi_search_steam@Base 3.4 + ndpi_search_stun@Base 3.4 + ndpi_search_syslog@Base 3.4 + ndpi_search_targus_getdata@Base 3.4 + ndpi_search_tcp_or_udp@Base 3.4 + ndpi_search_tcp_or_udp_raw@Base 3.4 + ndpi_search_teamspeak@Base 3.4 + ndpi_search_teamview@Base 3.4 + ndpi_search_telegram@Base 3.4 + ndpi_search_telnet_tcp@Base 3.4 + ndpi_search_teredo@Base 3.4 + ndpi_search_tftp@Base 3.4 + ndpi_search_thunder@Base 3.4 + ndpi_search_tinc@Base 3.4 + ndpi_search_tls_tcp_memory@Base 3.4 + ndpi_search_tor@Base 3.4 + ndpi_search_tvuplayer@Base 3.4 + ndpi_search_ubntac2@Base 3.4 + ndpi_search_upnp@Base 3.4 + ndpi_search_usenet_tcp@Base 3.4 + ndpi_search_vhua@Base 3.4 + ndpi_search_viber@Base 3.4 + ndpi_search_vmware@Base 3.4 + ndpi_search_vnc_tcp@Base 3.4 + ndpi_search_warcraft3@Base 3.4 + ndpi_search_websocket@Base 3.4 + ndpi_search_whatsapp@Base 3.4 + ndpi_search_whois_das@Base 3.4 + ndpi_search_wireguard@Base 3.4 + ndpi_search_world_of_kung_fu@Base 3.4 + ndpi_search_worldofwarcraft@Base 3.4 + ndpi_search_xbox@Base 3.4 + ndpi_search_xdmcp@Base 3.4 + ndpi_search_zabbix@Base 3.4 + ndpi_search_zattoo@Base 3.4 + ndpi_search_zmq@Base 3.4 + ndpi_self_check_host_match@Base 3.4 + ndpi_serialize_binary_binary@Base 3.4 + ndpi_serialize_binary_float@Base 3.4 + ndpi_serialize_binary_int32@Base 3.4 + ndpi_serialize_binary_int64@Base 3.4 + ndpi_serialize_binary_uint32@Base 3.4 + ndpi_serialize_binary_uint64@Base 3.4 + ndpi_serialize_end_of_block@Base 3.4 + ndpi_serialize_end_of_list@Base 3.4 + ndpi_serialize_end_of_record@Base 3.4 + ndpi_serialize_raw_record@Base 3.4 + ndpi_serialize_risk@Base 3.4 + ndpi_serialize_start_of_block@Base 3.4 + ndpi_serialize_start_of_block_binary@Base 3.4 + ndpi_serialize_start_of_list@Base 3.4 + ndpi_serialize_start_of_list_binary@Base 3.4 + ndpi_serialize_string_binary@Base 3.4 + ndpi_serialize_string_boolean@Base 3.4 + ndpi_serialize_string_float@Base 3.4 + ndpi_serialize_string_int32@Base 3.4 + ndpi_serialize_string_int64@Base 3.4 + ndpi_serialize_string_raw@Base 3.4 + ndpi_serialize_string_string@Base 3.4 + ndpi_serialize_string_uint32@Base 3.4 + ndpi_serialize_string_uint32_format@Base 3.4 + ndpi_serialize_string_uint64@Base 3.4 + ndpi_serialize_uint32_boolean@Base 3.4 + ndpi_serialize_uint32_float@Base 3.4 + ndpi_serialize_uint32_int32@Base 3.4 + ndpi_serialize_uint32_int64@Base 3.4 + ndpi_serialize_uint32_string@Base 3.4 + ndpi_serialize_uint32_uint32@Base 3.4 + ndpi_serialize_uint32_uint64@Base 3.4 + ndpi_serializer_create_snapshot@Base 3.4 + ndpi_serializer_get_buffer@Base 3.4 + ndpi_serializer_get_buffer_len@Base 3.4 + ndpi_serializer_get_format@Base 3.4 + ndpi_serializer_get_header@Base 3.4 + ndpi_serializer_get_internal_buffer_size@Base 3.4 + ndpi_serializer_rollback_snapshot@Base 3.4 + ndpi_serializer_set_buffer_len@Base 3.4 + ndpi_serializer_set_csv_separator@Base 3.4 + ndpi_set_bin@Base 3.4 + ndpi_set_bitmask_protocol_detection@Base 3.4 + ndpi_set_debug_bitmask@Base 3.4 + ndpi_set_detected_protocol@Base 3.4 + ndpi_set_detection_preferences@Base 3.4 + ndpi_set_log_level@Base 3.4 + ndpi_set_proto_breed@Base 3.4 + ndpi_set_proto_category@Base 3.4 + ndpi_set_proto_defaults@Base 3.4 + ndpi_set_protocol_detection_bitmask2@Base 3.4 + ndpi_ssl_version2str@Base 3.4 + ndpi_strdup@Base 3.4 + ndpi_string_sha1_hash@Base 3.4 + ndpi_strncasestr@Base 3.4 + ndpi_strnstr@Base 3.4 + ndpi_tdelete@Base 3.4 + ndpi_tdestroy@Base 3.4 + ndpi_term_serializer@Base 3.4 + ndpi_tfind@Base 3.4 + ndpi_timer_clear@Base 3.4 + ndpi_timer_eq@Base 3.4 + ndpi_timer_lt@Base 3.4 + ndpi_timer_sub@Base 3.4 + ndpi_timeval_to_microseconds@Base 3.4 + ndpi_timeval_to_milliseconds@Base 3.4 + ndpi_tsearch@Base 3.4 + ndpi_tunnel2str@Base 3.4 + ndpi_twalk@Base 3.4 + ndpi_update_params@Base 3.4 + ndpi_user_pwd_payload_copy@Base 3.4 + ndpi_validate_url@Base 3.4 + node_assign_id@Base 3.4 + node_create@Base 3.4 + node_create_next@Base 3.4 + node_edge_compare@Base 3.4 + node_find_next@Base 3.4 + node_findbs_next@Base 3.4 + node_has_matchstr@Base 3.4 + node_init@Base 3.4 + node_register_matchstr@Base 3.4 + node_register_outgoing@Base 3.4 + node_release@Base 3.4 + node_sort_edges@Base 3.4 + ntohs_ndpi_bytestream_to_number@Base 3.4 + processCertificate@Base 3.4 + processClientServerHello@Base 3.4 + quic_len@Base 3.4 + quic_len_buffer_still_required@Base 3.4 + sc2_match_logon_ip@Base 3.4 + search_ddl_domains@Base 3.4 + set_ndpi_debug_function@Base 3.4 + set_ndpi_flow_free@Base 3.4 + set_ndpi_flow_malloc@Base 3.4 + set_ndpi_free@Base 3.4 + set_ndpi_malloc@Base 3.4 + sha1_compress@Base 3.4 + sort@Base 3.4 diff --git a/debian/patches/add-minor-version-to-soname-ndpi-3.4-stable.patch b/debian/patches/add-minor-version-to-soname-ndpi-3.4-stable.patch new file mode 100644 index 0000000..8fac0d8 --- /dev/null +++ b/debian/patches/add-minor-version-to-soname-ndpi-3.4-stable.patch @@ -0,0 +1,50 @@ +From f19a97a2dad74928789141d6634d63b666b8bca4 Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 06:35:54 +0000 +Subject: [PATCH] add-minor-version-to-soname changes for ndpi-3.4 (stable) + +--- + src/lib/Makefile.in | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/src/lib/Makefile.in b/src/lib/Makefile.in +index 6c69485..ff66b59 100644 +--- a/src/lib/Makefile.in ++++ b/src/lib/Makefile.in +@@ -21,6 +21,7 @@ RANLIB = ranlib + OBJECTS = $(patsubst protocols/%.c, protocols/%.o, $(wildcard protocols/*.c)) $(patsubst third_party/src/%.c, third_party/src/%.o, $(wildcard third_party/src/*.c)) $(patsubst ./%.c, ./%.o, $(wildcard ./*.c)) + HEADERS = $(wildcard ../include/*.h) + NDPI_VERSION_MAJOR = @NDPI_MAJOR@ ++NDPI_VERSION_MINOR = @NDPI_MINOR@ + NDPI_LIB_STATIC = libndpi.a + NDPI_LIB_SHARED_BASE = libndpi.so + NDPI_LIB_SHARED = $(NDPI_LIB_SHARED_BASE).@NDPI_VERSION_SHORT@ +@@ -39,7 +40,7 @@ ifneq ($(BUILD_MINGW),) + NDPI_LIB_SHARED_BASE = libndpi + NDPI_LIB_SHARED = $(NDPI_LIB_SHARED_BASE)-@NDPI_VERSION_SHORT@.dll + else +-SONAME_FLAG=-Wl,-soname,$(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR) ++SONAME_FLAG=-Wl,-soname,$(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR).$(NDPI_VERSION_MINOR) + endif + endif + +@@ -54,7 +55,7 @@ $(NDPI_LIB_STATIC): $(OBJECTS) + $(NDPI_LIB_SHARED): $(OBJECTS) + $(CC) -shared -fPIC $(SONAME_FLAG) -o $@ $(OBJECTS) $(LDFLAGS) + ln -fs $(NDPI_LIB_SHARED) $(NDPI_LIB_SHARED_BASE) +- ln -fs $(NDPI_LIB_SHARED) $(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR) ++ ln -fs $(NDPI_LIB_SHARED) $(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR).$(NDPI_VERSION_MINOR) + + %.o: %.c $(HEADERS) Makefile + $(CC) $(CFLAGS) -c $< -o $@ +@@ -78,6 +79,6 @@ install: $(NDPI_LIBS) + mkdir -p $(DESTDIR)$(libdir) + cp $(NDPI_LIBS) $(DESTDIR)$(libdir)/ + cp -P $(NDPI_LIB_SHARED_BASE) $(DESTDIR)$(libdir)/ +- cp -P $(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR) $(DESTDIR)$(libdir)/ ++ cp -P $(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR).$(NDPI_VERSION_MINOR) $(DESTDIR)$(libdir)/ + mkdir -p $(DESTDIR)$(includedir) + cp ../include/*.h $(DESTDIR)$(includedir) +-- +2.20.1 + diff --git a/debian/patches/additional-cppflags-ndpi-3.4-stable.patch b/debian/patches/additional-cppflags-ndpi-3.4-stable.patch new file mode 100644 index 0000000..c398774 --- /dev/null +++ b/debian/patches/additional-cppflags-ndpi-3.4-stable.patch @@ -0,0 +1,53 @@ +From c67f8cdb6be7cf15605abdb4aac843a177653ede Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 07:14:58 +0000 +Subject: [PATCH] cpp flags added + +--- + example/Makefile.in | 6 +++--- + src/lib/Makefile.in | 4 ++-- + 2 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/example/Makefile.in b/example/Makefile.in +index ec44acb..129861a 100644 +--- a/example/Makefile.in ++++ b/example/Makefile.in +@@ -25,13 +25,13 @@ libndpiReader.a: $(COMMON_SOURCES:%.c=%.o) $(LIBNDPI) + ar rsv libndpiReader.a $(COMMON_SOURCES:%.c=%.o) + + ndpiReader: libndpiReader.a $(LIBNDPI) ndpiReader.o +- $(CC) $(CFLAGS) ndpiReader.o libndpiReader.a -o $@ $(LDFLAGS) ++ $(CC) $(CPPFLAGS) $(CFLAGS) ndpiReader.o libndpiReader.a -o $@ $(LDFLAGS) + + ndpiSimpleIntegration: ndpiSimpleIntegration.o +- $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) ++ $(CC) $(CPPFLAGS) $(CFLAGS) $< -o $@ $(LDFLAGS) + + %.o: %.c $(HEADERS) Makefile +- $(CC) $(CFLAGS) -c $< -o $@ ++ $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + + install: ndpiReader + mkdir -p $(DESTDIR)$(PREFIX)/bin/ +diff --git a/src/lib/Makefile.in b/src/lib/Makefile.in +index 6c69485..86316ac 100644 +--- a/src/lib/Makefile.in ++++ b/src/lib/Makefile.in +@@ -52,12 +52,12 @@ $(NDPI_LIB_STATIC): $(OBJECTS) + $(RANLIB) $@ + + $(NDPI_LIB_SHARED): $(OBJECTS) +- $(CC) -shared -fPIC $(SONAME_FLAG) -o $@ $(OBJECTS) $(LDFLAGS) ++ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -shared -fPIC $(SONAME_FLAG) -o $@ $(OBJECTS) + ln -fs $(NDPI_LIB_SHARED) $(NDPI_LIB_SHARED_BASE) + ln -fs $(NDPI_LIB_SHARED) $(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR).$(NDPI_VERSION_MINOR) + + %.o: %.c $(HEADERS) Makefile +- $(CC) $(CFLAGS) -c $< -o $@ ++ $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ + + clean: + /bin/rm -f $(NDPI_LIB_STATIC) $(OBJECTS) *.o *.so *.lo libndpi.so* +-- +2.20.1 + diff --git a/debian/patches/define-have-json-c-ndpi-3.4-stable.patch b/debian/patches/define-have-json-c-ndpi-3.4-stable.patch new file mode 100644 index 0000000..6bedc86 --- /dev/null +++ b/debian/patches/define-have-json-c-ndpi-3.4-stable.patch @@ -0,0 +1,25 @@ +From 5416aafbc68b97a1a53c4489a1e618b3227120db Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 06:42:16 +0000 +Subject: [PATCH] define have-json-c flag in ndpi-3.4 + +--- + example/Makefile.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/example/Makefile.in b/example/Makefile.in +index ec44acb..3a74dd0 100644 +--- a/example/Makefile.in ++++ b/example/Makefile.in +@@ -2,7 +2,7 @@ CC=@CC@ + CXX=@CXX@ + BUILD_MINGW=@BUILD_MINGW@ + SRCHOME=../src +-CFLAGS=-g -fPIC -DPIC -I$(SRCHOME)/include @PCAP_INC@ @CFLAGS@ ++CFLAGS=-g -fPIC -DPIC -I$(SRCHOME)/include -DHAVE_JSON_C @PCAP_INC@ @CFLAGS@ + LIBNDPI=$(SRCHOME)/lib/libndpi.a + LDFLAGS=$(LIBNDPI) @PCAP_LIB@ @LIBS@ @ADDITIONAL_LIBS@ -lpthread -lm @LDFLAGS@ + HEADERS=intrusion_detection.h reader_util.h $(SRCHOME)/include/ndpi_api.h \ +-- +2.20.1 + diff --git a/debian/patches/fix-makefile-prefix-ndpi-3.4-stable.patch b/debian/patches/fix-makefile-prefix-ndpi-3.4-stable.patch new file mode 100644 index 0000000..8d2340c --- /dev/null +++ b/debian/patches/fix-makefile-prefix-ndpi-3.4-stable.patch @@ -0,0 +1,48 @@ +From d68a23f6f1ac364267e9feb38d1c93ab2fbd86b0 Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 06:25:07 +0000 +Subject: [PATCH] fix makefile prefix changes for ndpi-3.4 (stable) + +--- + configure.seed | 4 ++-- + src/lib/Makefile.in | 2 +- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/configure.seed b/configure.seed +index b333110..abe252d 100644 +--- a/configure.seed ++++ b/configure.seed +@@ -5,7 +5,7 @@ AC_CONFIG_MACRO_DIR([m4]) + + AM_INIT_AUTOMAKE([foreign subdir-objects]) + +-EXTRA_TARGETS="example tests tests/unit" ++EXTRA_TARGETS="generation example tests tests/unit" + AC_ARG_WITH(only-libndpi, AS_HELP_STRING([--with-only-libndpi], [Build only libndpi (no examples, tests etc)])) + AS_IF([test "${with_only_libndpi+set}" = set],[ + EXTRA_TARGETS="" +@@ -195,7 +195,7 @@ if test "${with_pcre+set}" = set; then : + fi + + +-AC_CONFIG_FILES([Makefile example/Makefile example/Makefile.dpdk tests/Makefile tests/unit/Makefile libndpi.pc src/include/ndpi_define.h src/lib/Makefile python/Makefile fuzz/Makefile src/include/ndpi_api.h]) ++AC_CONFIG_FILES([Makefile generation/Makefile example/Makefile example/Makefile.dpdk tests/Makefile tests/unit/Makefile libndpi.pc src/include/ndpi_define.h src/lib/Makefile python/Makefile fuzz/Makefile src/include/ndpi_api.h]) + AC_CONFIG_HEADERS(src/include/ndpi_config.h) + AC_SUBST(GIT_RELEASE) + AC_SUBST(NDPI_MAJOR) +diff --git a/src/lib/Makefile.in b/src/lib/Makefile.in +index 6c69485..e847c14 100644 +--- a/src/lib/Makefile.in ++++ b/src/lib/Makefile.in +@@ -11,7 +11,7 @@ + # Installation directories + # + prefix = @prefix@ +-libdir = ${prefix}/lib ++libdir = ${prefix}/lib/$(DEB_TARGET_MULTIARCH) + includedir = ${prefix}/include/ndpi + CC = @CC@ + CFLAGS += -fPIC -DPIC -I../include -Ithird_party/include -DNDPI_LIB_COMPILATION -Wall @CFLAGS@ @CUSTOM_NDPI@ +-- +2.20.1 + diff --git a/debian/patches/ndpireader-dynamic-link-ndpi-3.4-stable.patch b/debian/patches/ndpireader-dynamic-link-ndpi-3.4-stable.patch new file mode 100644 index 0000000..1ad10ac --- /dev/null +++ b/debian/patches/ndpireader-dynamic-link-ndpi-3.4-stable.patch @@ -0,0 +1,25 @@ +From 554f2459a59fbd066710c38a9e9afe33562d2d7b Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 07:08:56 +0000 +Subject: [PATCH] ndpireader-dynamic-link.patch updated + +--- + example/Makefile.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/example/Makefile.in b/example/Makefile.in +index ec44acb..e1bff38 100644 +--- a/example/Makefile.in ++++ b/example/Makefile.in +@@ -5,7 +5,7 @@ BUILD_MINGW=@BUILD_MINGW@ + SRCHOME=../src + CFLAGS=-g -fPIC -DPIC -I$(SRCHOME)/include -DHAVE_JSON_C @PCAP_INC@ @CFLAGS@ + LIBNDPI=$(SRCHOME)/lib/libndpi.a +-LDFLAGS=$(LIBNDPI) @PCAP_LIB@ @LIBS@ @ADDITIONAL_LIBS@ -lpthread -lm @LDFLAGS@ ++LDFLAGS=-L../src/lib -lndpi @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 +-- +2.20.1 + diff --git a/debian/patches/series b/debian/patches/series index da40d6b..ff6b69c 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,10 +1,12 @@ -fix-makefile-prefix -add-minor-version-to-soname.patch -define-have-json-c.patch -ndpireader-dynamic-link.patch -additional-cppflags.patch -vyatta-app-names.patch -vyatta-generation.patch +fix-makefile-prefix-ndpi-3.4-stable.patch +add-minor-version-to-soname-ndpi-3.4-stable.patch +define-have-json-c-ndpi-3.4-stable.patch +ndpireader-dynamic-link-ndpi-3.4-stable.patch +additional-cppflags-ndpi-3.4-stable.patch +vyatta-app-names-ndpi-3.4-stable.patch +vyatta-generation-modified.patch vyatta-generated.patch -vyatta-increase-ndpi-num-bits.patch -vyatta-category-file.patch +vyatta-increase-ndpi-num-bits-ndpi-3.4-stable.patch +vyatta-category-file-ndpi-3.4-stable.patch +vyatta-generation-ndpi-3.4-changes.patch +vyatta-generated-ndpi-3.4-changes.patch diff --git a/debian/patches/vyatta-app-names-ndpi-3.4-stable.patch b/debian/patches/vyatta-app-names-ndpi-3.4-stable.patch new file mode 100644 index 0000000..d4a174b --- /dev/null +++ b/debian/patches/vyatta-app-names-ndpi-3.4-stable.patch @@ -0,0 +1,298 @@ +From 846e27302b6e450e927932c1e5ebd61c58ff5203 Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 09:49:21 +0000 +Subject: [PATCH] added application name rename patch for ndpi-3.4(stable) + +--- + src/lib/ndpi_content_match.c.inc | 4 +-- + src/lib/ndpi_main.c | 8 ++--- + tests/result/bittorrent.pcap.out | 48 +++++++++++++-------------- + tests/result/bittorrent_ip.pcap.out | 4 +-- + tests/result/bittorrent_utp.pcap.out | 2 +- + tests/result/bt_search.pcap.out | 2 +- + tests/result/ftp.pcap.out | 4 +-- + tests/result/mssql_tds.pcap.out | 26 +++++++-------- + tests/result/smpp_in_general.pcap.out | 2 +- + tests/result/weibo.pcap.out | 34 +++++++++---------- + tests/result/whatsappfiles.pcap.out | 4 +-- + 11 files changed, 69 insertions(+), 69 deletions(-) + +diff --git a/src/lib/ndpi_content_match.c.inc b/src/lib/ndpi_content_match.c.inc +index caa8e73..7fe7db8 100644 +--- a/src/lib/ndpi_content_match.c.inc ++++ b/src/lib/ndpi_content_match.c.inc +@@ -8984,8 +8984,8 @@ static ndpi_protocol_match host_match[] = + { ".qq.com", "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".gtimg.com", "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + +- { ".weibo.com", "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, +- { ".weibo.cn", "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, ++ { ".weibo.com", "Weibo", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, ++ { ".weibo.cn", "Weibo", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sinaimg.cn", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sinajs.cn", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sina.cn", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, +diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c +index 4cbe722..f70ddf8 100644 +--- a/src/lib/ndpi_main.c ++++ b/src/lib/ndpi_main.c +@@ -1158,7 +1158,7 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MSSQL_TDS, 0 /* can_have_a_subprotocol */, +- no_master, no_master, "MsSQL-TDS", NDPI_PROTOCOL_CATEGORY_DATABASE, ++ no_master, no_master, "MsSQL_TDS", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 1433, 1434, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_PPTP, 0 /* can_have_a_subprotocol */, +@@ -1332,7 +1332,7 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHOIS_DAS, 0 /* can_have_a_subprotocol */, +- no_master, no_master, "Whois-DAS", NDPI_PROTOCOL_CATEGORY_NETWORK, ++ no_master, no_master, "Whois_DAS", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 43, 4343, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_COLLECTD, 0 /* can_have_a_subprotocol */, +@@ -1452,7 +1452,7 @@ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndp + ndpi_build_default_ports(ports_a, 8009, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TARGUS_GETDATA, +- 0 /* can_have_a_subprotocol */, no_master, no_master, "Targus Dataspeed", ++ 0 /* can_have_a_subprotocol */, no_master, no_master, "Targus_Dataspeed", + NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 5001, 5201, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5001, 5201, 0, 0, 0) /* UDP */); +@@ -1865,7 +1865,7 @@ static const char *categories[] = { + "DataTransfer", + "Web", + "SocialNetwork", +- "Download-FileTransfer-FileSharing", ++ "Download", + "Game", + "Chat", + "VoIP", +diff --git a/tests/result/bittorrent.pcap.out b/tests/result/bittorrent.pcap.out +index 4a0c5b0..7eef666 100644 +--- a/tests/result/bittorrent.pcap.out ++++ b/tests/result/bittorrent.pcap.out +@@ -1,26 +1,26 @@ + BitTorrent 299 305728 24 + +- 1 TCP 192.168.1.3:52915 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][17 pkts/2745 bytes <-> 193 pkts/282394 bytes][Goodput ratio: 59/95][5.77 sec][bytes ratio: -0.981 (Download)][IAT c2s/s2c min/avg/max/stddev: 12/0 319/30 779/919 241/95][Pkt Len c2s/s2c min/avg/max/stddev: 83/80 161/1463 242/1506 58/1457][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 2,0,0,0,3,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,93,0,0] +- 2 TCP 192.168.1.3:52895 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][4 pkts/583 bytes <-> 4 pkts/975 bytes][Goodput ratio: 55/73][4.11 sec][bytes ratio: -0.252 (Download)][IAT c2s/s2c min/avg/max/stddev: 132/72 959/2027 1966/3982 760/1955][Pkt Len c2s/s2c min/avg/max/stddev: 80/73 146/244 198/648 44/235][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 25,12,25,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] +- 3 TCP 192.168.1.3:52914 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][4 pkts/640 bytes <-> 3 pkts/910 bytes][Goodput ratio: 59/78][3.54 sec][bytes ratio: -0.174 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 489/661 1178/883 1943/1105 596/222][Pkt Len c2s/s2c min/avg/max/stddev: 75/113 160/303 241/650 62/246][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 14,14,28,14,0,14,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,0,0,0,0,0,0,0,0,0,0,0,0] +- 4 TCP 192.168.1.3:52907 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][2 pkts/583 bytes <-> 2 pkts/818 bytes][Goodput ratio: 77/84][1.89 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,25,25,0,0,0,0,0,0,0,0,25,0,0,0,0,0,0,0,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] +- 5 TCP 192.168.1.3:52927 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/582 bytes <-> 2 pkts/796 bytes][Goodput ratio: 66/83][0.92 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,40,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0] +- 6 TCP 192.168.1.3:52897 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/510 bytes <-> 2 pkts/771 bytes][Goodput ratio: 61/83][0.92 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,0,20,60,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,0,0,0,0,0,0] +- 7 TCP 192.168.1.3:52903 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/410 bytes <-> 3 pkts/851 bytes][Goodput ratio: 52/77][0.81 sec][bytes ratio: -0.350 (Download)][IAT c2s/s2c min/avg/max/stddev: 320/159 407/298 494/436 87/139][Pkt Len c2s/s2c min/avg/max/stddev: 80/80 137/284 196/601 47/227][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 34,0,16,16,16,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] +- 8 TCP 192.168.1.3:52917 <-> 151.15.48.189:47001 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/455 bytes <-> 2 pkts/771 bytes][Goodput ratio: 56/83][0.09 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,40,0,0,20,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,0,0,0,0,0] +- 9 TCP 192.168.1.3:52911 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/442 bytes <-> 2 pkts/771 bytes][Goodput ratio: 55/83][0.94 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] +- 10 TCP 192.168.1.3:52921 <-> 95.234.159.16:41205 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/440 bytes <-> 2 pkts/772 bytes][Goodput ratio: 55/83][0.27 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] +- 11 TCP 192.168.1.3:52906 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][Goodput ratio: 54/83][0.36 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] +- 12 TCP 192.168.1.3:52922 <-> 95.237.193.34:11321 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][Goodput ratio: 54/83][0.26 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] +- 13 TCP 192.168.1.3:52887 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/430 bytes <-> 2 pkts/771 bytes][Goodput ratio: 54/83][0.45 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] +- 14 TCP 192.168.1.3:52896 <-> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/409 bytes <-> 2 pkts/771 bytes][Goodput ratio: 51/83][0.25 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,40,0,20,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,0,0,0,0,0] +- 15 TCP 192.168.1.3:52926 <-> 93.65.249.100:31336 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes <-> 2 pkts/796 bytes][Goodput ratio: 50/83][0.23 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,0,33,33,0,0,0,0,0,0,0,0,0,0,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] +- 16 TCP 192.168.1.3:52888 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes <-> 1 pkts/624 bytes][Goodput ratio: 50/89][0.22 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,0,50,0,0,0,0,0,0,0,0,0,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] +- 17 TCP 192.168.1.3:52902 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][2 pkts/349 bytes <-> 2 pkts/265 bytes][Goodput ratio: 62/50][1.91 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 25,0,25,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] +- 18 TCP 192.168.1.3:52912 <-> 151.72.255.163:59928 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][3 pkts/455 bytes <-> 1 pkts/157 bytes][Goodput ratio: 56/58][0.15 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 25,0,50,0,0,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] +- 19 TCP 192.168.1.3:52893 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] +- 20 TCP 192.168.1.3:52894 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] +- 21 TCP 192.168.1.3:52908 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] +- 22 TCP 192.168.1.3:52909 -> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] +- 23 TCP 192.168.1.3:52910 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] +- 24 TCP 192.168.1.3:52925 -> 93.65.227.100:19116 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] ++ 1 TCP 192.168.1.3:52915 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download/7][17 pkts/2745 bytes <-> 193 pkts/282394 bytes][Goodput ratio: 59/95][5.77 sec][bytes ratio: -0.981 (Download)][IAT c2s/s2c min/avg/max/stddev: 12/0 319/30 779/919 241/95][Pkt Len c2s/s2c min/avg/max/stddev: 83/80 161/1463 242/1506 58/1457][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 2,0,0,0,3,0,0,0,0,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,0,0,0,0,0,0,0,0,0,0,93,0,0] ++ 2 TCP 192.168.1.3:52895 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download/7][4 pkts/583 bytes <-> 4 pkts/975 bytes][Goodput ratio: 55/73][4.11 sec][bytes ratio: -0.252 (Download)][IAT c2s/s2c min/avg/max/stddev: 132/72 959/2027 1966/3982 760/1955][Pkt Len c2s/s2c min/avg/max/stddev: 80/73 146/244 198/648 44/235][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 25,12,25,12,12,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ++ 3 TCP 192.168.1.3:52914 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download/7][4 pkts/640 bytes <-> 3 pkts/910 bytes][Goodput ratio: 59/78][3.54 sec][bytes ratio: -0.174 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 489/661 1178/883 1943/1105 596/222][Pkt Len c2s/s2c min/avg/max/stddev: 75/113 160/303 241/650 62/246][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 14,14,28,14,0,14,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,0,0,0,0,0,0,0,0,0,0,0,0] ++ 4 TCP 192.168.1.3:52907 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download/7][2 pkts/583 bytes <-> 2 pkts/818 bytes][Goodput ratio: 77/84][1.89 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,25,25,0,0,0,0,0,0,0,0,25,0,0,0,0,0,0,0,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] ++ 5 TCP 192.168.1.3:52927 <-> 83.216.184.241:51413 [proto: 37/BitTorrent][cat: Download/7][3 pkts/582 bytes <-> 2 pkts/796 bytes][Goodput ratio: 66/83][0.92 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,40,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ++ 6 TCP 192.168.1.3:52897 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download/7][3 pkts/510 bytes <-> 2 pkts/771 bytes][Goodput ratio: 61/83][0.92 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,0,20,60,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,0,0,0,0,0,0] ++ 7 TCP 192.168.1.3:52903 <-> 198.100.146.9:60163 [proto: 37/BitTorrent][cat: Download/7][3 pkts/410 bytes <-> 3 pkts/851 bytes][Goodput ratio: 52/77][0.81 sec][bytes ratio: -0.350 (Download)][IAT c2s/s2c min/avg/max/stddev: 320/159 407/298 494/436 87/139][Pkt Len c2s/s2c min/avg/max/stddev: 80/80 137/284 196/601 47/227][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 34,0,16,16,16,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ++ 8 TCP 192.168.1.3:52917 <-> 151.15.48.189:47001 [proto: 37/BitTorrent][cat: Download/7][3 pkts/455 bytes <-> 2 pkts/771 bytes][Goodput ratio: 56/83][0.09 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,40,0,0,20,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,0,0,0,0,0] ++ 9 TCP 192.168.1.3:52911 <-> 151.26.95.30:22673 [proto: 37/BitTorrent][cat: Download/7][3 pkts/442 bytes <-> 2 pkts/771 bytes][Goodput ratio: 55/83][0.94 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] ++ 10 TCP 192.168.1.3:52921 <-> 95.234.159.16:41205 [proto: 37/BitTorrent][cat: Download/7][3 pkts/440 bytes <-> 2 pkts/772 bytes][Goodput ratio: 55/83][0.27 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] ++ 11 TCP 192.168.1.3:52906 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][Goodput ratio: 54/83][0.36 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] ++ 12 TCP 192.168.1.3:52922 <-> 95.237.193.34:11321 [proto: 37/BitTorrent][cat: Download/7][3 pkts/434 bytes <-> 2 pkts/771 bytes][Goodput ratio: 54/83][0.26 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] ++ 13 TCP 192.168.1.3:52887 <-> 82.57.97.83:53137 [proto: 37/BitTorrent][cat: Download/7][3 pkts/430 bytes <-> 2 pkts/771 bytes][Goodput ratio: 54/83][0.45 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,20,20,20,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,0,0,0,0,0,0] ++ 14 TCP 192.168.1.3:52896 <-> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download/7][3 pkts/409 bytes <-> 2 pkts/771 bytes][Goodput ratio: 51/83][0.25 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 20,0,40,0,20,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,0,0,0,0,0] ++ 15 TCP 192.168.1.3:52926 <-> 93.65.249.100:31336 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes <-> 2 pkts/796 bytes][Goodput ratio: 50/83][0.23 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,0,33,33,0,0,0,0,0,0,0,0,0,0,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] ++ 16 TCP 192.168.1.3:52888 <-> 82.58.216.115:38305 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes <-> 1 pkts/624 bytes][Goodput ratio: 50/89][0.22 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 0,0,50,0,0,0,0,0,0,0,0,0,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] ++ 17 TCP 192.168.1.3:52902 <-> 190.103.195.56:46633 [proto: 37/BitTorrent][cat: Download/7][2 pkts/349 bytes <-> 2 pkts/265 bytes][Goodput ratio: 62/50][1.91 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 25,0,25,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] ++ 18 TCP 192.168.1.3:52912 <-> 151.72.255.163:59928 [proto: 37/BitTorrent][cat: Download/7][3 pkts/455 bytes <-> 1 pkts/157 bytes][Goodput ratio: 56/58][0.15 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 25,0,50,0,0,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] ++ 19 TCP 192.168.1.3:52893 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] ++ 20 TCP 192.168.1.3:52894 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] ++ 21 TCP 192.168.1.3:52908 -> 79.55.129.22:12097 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] ++ 22 TCP 192.168.1.3:52909 -> 79.53.228.2:14627 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] ++ 23 TCP 192.168.1.3:52910 -> 120.62.33.241:39332 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] ++ 24 TCP 192.168.1.3:52925 -> 93.65.227.100:19116 [proto: 37/BitTorrent][cat: Download/7][1 pkts/134 bytes -> 0 pkts/0 bytes][Goodput ratio: 50/0][< 1 sec][BT Hash: dcfcdccfb9e670ccc3dd40c78c161f2bea243126][PLAIN TEXT (BitTorrent protocol)][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] +diff --git a/tests/result/bittorrent_ip.pcap.out b/tests/result/bittorrent_ip.pcap.out +index b651b2e..d87ec31 100644 +--- a/tests/result/bittorrent_ip.pcap.out ++++ b/tests/result/bittorrent_ip.pcap.out +@@ -1,4 +1,4 @@ + BitTorrent 479 508018 2 + +- 1 TCP 77.222.174.20:2866 <-> 10.0.0.14:46610 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][305 pkts/461770 bytes <-> 126 pkts/8316 bytes][Goodput ratio: 96/0][2.45 sec][bytes ratio: 0.965 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 5/6 879/56 54/10][Pkt Len c2s/s2c min/avg/max/stddev: 1514/66 1514/66 1514/66 1504/0][PLAIN TEXT (n.m Hh)][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,100,0,0] +- 2 TCP 185.56.20.36:53646 <-> 10.0.0.14:35030 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][25 pkts/36414 bytes <-> 23 pkts/1518 bytes][Goodput ratio: 95/0][0.21 sec][bytes ratio: 0.920 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 10/11 83/83 23/24][Pkt Len c2s/s2c min/avg/max/stddev: 78/66 1457/66 1514/66 281/0][PLAIN TEXT (@RgmZT)][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,100,0,0] ++ 1 TCP 77.222.174.20:2866 <-> 10.0.0.14:46610 [proto: 37/BitTorrent][cat: Download/7][305 pkts/461770 bytes <-> 126 pkts/8316 bytes][Goodput ratio: 96/0][2.45 sec][bytes ratio: 0.965 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 5/6 879/56 54/10][Pkt Len c2s/s2c min/avg/max/stddev: 1514/66 1514/66 1514/66 1504/0][PLAIN TEXT (n.m Hh)][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,100,0,0] ++ 2 TCP 185.56.20.36:53646 <-> 10.0.0.14:35030 [proto: 37/BitTorrent][cat: Download/7][25 pkts/36414 bytes <-> 23 pkts/1518 bytes][Goodput ratio: 95/0][0.21 sec][bytes ratio: 0.920 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 10/11 83/83 23/24][Pkt Len c2s/s2c min/avg/max/stddev: 78/66 1457/66 1514/66 281/0][PLAIN TEXT (@RgmZT)][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,100,0,0] +diff --git a/tests/result/bittorrent_utp.pcap.out b/tests/result/bittorrent_utp.pcap.out +index b98b10d..053a79a 100644 +--- a/tests/result/bittorrent_utp.pcap.out ++++ b/tests/result/bittorrent_utp.pcap.out +@@ -1,3 +1,3 @@ + BitTorrent 86 41489 1 + +- 1 UDP 82.243.113.43:64969 <-> 192.168.1.5:40959 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][47 pkts/36653 bytes <-> 39 pkts/4836 bytes][Goodput ratio: 95/66][19.22 sec][bytes ratio: 0.767 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/11 223/425 4392/4641 701/934][Pkt Len c2s/s2c min/avg/max/stddev: 62/62 780/124 1514/519 609/123][PLAIN TEXT (hash20)][Plen Bins: 52,1,2,4,0,1,1,1,0,0,5,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,6,0,0,0,6,0,0,0,8,0] ++ 1 UDP 82.243.113.43:64969 <-> 192.168.1.5:40959 [proto: 37/BitTorrent][cat: Download/7][47 pkts/36653 bytes <-> 39 pkts/4836 bytes][Goodput ratio: 95/66][19.22 sec][bytes ratio: 0.767 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/11 223/425 4392/4641 701/934][Pkt Len c2s/s2c min/avg/max/stddev: 62/62 780/124 1514/519 609/123][PLAIN TEXT (hash20)][Plen Bins: 52,1,2,4,0,1,1,1,0,0,5,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,6,0,0,0,6,0,0,0,8,0] +diff --git a/tests/result/bt_search.pcap.out b/tests/result/bt_search.pcap.out +index 532f094..e759a23 100644 +--- a/tests/result/bt_search.pcap.out ++++ b/tests/result/bt_search.pcap.out +@@ -1,3 +1,3 @@ + BitTorrent 2 322 1 + +- 1 UDP 192.168.0.102:6771 -> 239.192.152.143:6771 [proto: 37/BitTorrent][cat: Download-FileTransfer-FileSharing/7][2 pkts/322 bytes -> 0 pkts/0 bytes][Goodput ratio: 74/0][300.03 sec][PLAIN TEXT (SEARCH )][Plen Bins: 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,0,0,0,0,0,0,0,0,0] ++ 1 UDP 192.168.0.102:6771 -> 239.192.152.143:6771 [proto: 37/BitTorrent][cat: Download/7][2 pkts/322 bytes -> 0 pkts/0 bytes][Goodput ratio: 74/0][300.03 sec][PLAIN TEXT (SEARCH )][Plen Bins: 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,0,0,0,0,0,0,0,0,0] +diff --git a/tests/result/ftp.pcap.out b/tests/result/ftp.pcap.out +index 584cbc8..857b507 100644 +--- a/tests/result/ftp.pcap.out ++++ b/tests/result/ftp.pcap.out +@@ -2,8 +2,8 @@ Unknown 1115 1122198 1 + FTP_CONTROL 68 5571 1 + FTP_DATA 9 1819 1 + +- 1 TCP 192.168.1.212:50694 <-> 90.130.70.73:21 [proto: 1/FTP_CONTROL][cat: Download-FileTransfer-FileSharing/7][41 pkts/2892 bytes <-> 27 pkts/2679 bytes][Goodput ratio: 6/33][8.48 sec][User: anonymous][Pwd: NcFTP@][bytes ratio: 0.038 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 236/108 4743/1377 849/305][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 71/99 96/307 7/45][Risk: ** Unsafe Protocol **][PLAIN TEXT (vsFTPd 3.0.3)][Plen Bins: 74,18,5,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] +- 2 TCP 192.168.1.212:50695 <-> 90.130.70.73:25685 [proto: 175/FTP_DATA][cat: Download-FileTransfer-FileSharing/7][5 pkts/342 bytes <-> 4 pkts/1477 bytes][Goodput ratio: 0/82][0.09 sec][bytes ratio: -0.624 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/28 14/28 29/29 14/1][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 68/369 78/1271 5/521][PLAIN TEXT ( 1 0 0 1073741)][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,100,0,0,0,0,0,0,0,0,0,0] ++ 1 TCP 192.168.1.212:50694 <-> 90.130.70.73:21 [proto: 1/FTP_CONTROL][cat: Download/7][41 pkts/2892 bytes <-> 27 pkts/2679 bytes][Goodput ratio: 6/33][8.48 sec][User: anonymous][Pwd: NcFTP@][bytes ratio: 0.038 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 236/108 4743/1377 849/305][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 71/99 96/307 7/45][Risk: ** Unsafe Protocol **][PLAIN TEXT (vsFTPd 3.0.3)][Plen Bins: 74,18,5,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ++ 2 TCP 192.168.1.212:50695 <-> 90.130.70.73:25685 [proto: 175/FTP_DATA][cat: Download/7][5 pkts/342 bytes <-> 4 pkts/1477 bytes][Goodput ratio: 0/82][0.09 sec][bytes ratio: -0.624 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/28 14/28 29/29 14/1][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 68/369 78/1271 5/521][PLAIN TEXT ( 1 0 0 1073741)][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,100,0,0,0,0,0,0,0,0,0,0] + + + Undetected flows: +diff --git a/tests/result/mssql_tds.pcap.out b/tests/result/mssql_tds.pcap.out +index e7b3dea..55ec645 100644 +--- a/tests/result/mssql_tds.pcap.out ++++ b/tests/result/mssql_tds.pcap.out +@@ -1,14 +1,14 @@ +-MsSQL-TDS 38 16260 12 ++MsSQL_TDS 38 16260 12 + +- 1 TCP 10.111.111.111:6666 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][7 pkts/8717 bytes -> 0 pkts/0 bytes][Goodput ratio: 96/0][< 1 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 0/0 0/0 0/0][Pkt Len c2s/s2c min/avg/max/stddev: 393/0 1245/0 1514/0 436/0][Plen Bins: 0,0,0,0,0,0,0,0,0,0,14,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,0,0,0,0,0,0,71,0,0] +- 2 TCP 10.111.111.111:5555 <-> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][10 pkts/1552 bytes <-> 7 pkts/1521 bytes][Goodput ratio: 64/75][7.22 sec][bytes ratio: 0.010 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 31/28 867/1024 1890/2071 763/864][Pkt Len c2s/s2c min/avg/max/stddev: 60/88 155/217 307/492 90/169][PLAIN TEXT (first )][Plen Bins: 0,42,7,14,0,7,0,14,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,0,0] +- 3 TCP 10.111.111.111:1111 <-> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][2 pkts/614 bytes <-> 2 pkts/524 bytes][Goodput ratio: 78/75][0.14 sec][Plen Bins: 0,25,0,0,0,25,0,0,0,25,0,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] +- 4 TCP 10.111.111.111:4444 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/1136 bytes -> 0 pkts/0 bytes][Goodput ratio: 95/0][< 1 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,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0] +- 5 TCP 10.111.111.111:7777 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/425 bytes -> 0 pkts/0 bytes][Goodput ratio: 87/0][< 1 sec][Plen Bins: 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,0] +- 6 TCP 10.111.111.111:33333 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/374 bytes -> 0 pkts/0 bytes][Goodput ratio: 85/0][< 1 sec][Plen Bins: 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,0,0] +- 7 TCP 10.111.111.111:22222 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/322 bytes -> 0 pkts/0 bytes][Goodput ratio: 83/0][< 1 sec][Plen Bins: 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,0,0,0,0] +- 8 TCP 10.111.111.111:9999 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/272 bytes -> 0 pkts/0 bytes][Goodput ratio: 80/0][< 1 sec][PLAIN TEXT (ABCDEFGHIJKLMNOPQ)][Plen Bins: 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,0,0,0,0,0,0] +- 9 TCP 10.111.111.111:11111 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/253 bytes -> 0 pkts/0 bytes][Goodput ratio: 78/0][< 1 sec][Plen Bins: 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,0,0,0,0,0,0] +- 10 TCP 10.111.111.111:3333 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/239 bytes -> 0 pkts/0 bytes][Goodput ratio: 77/0][< 1 sec][Plen Bins: 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,0,0,0,0,0,0,0] +- 11 TCP 10.111.111.111:2222 <-> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/98 bytes <-> 1 pkts/71 bytes][Goodput ratio: 44/24][< 1 sec][Plen Bins: 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,0] +- 12 TCP 10.111.111.111:8888 -> 10.0.0.1:1433 [proto: 114/MsSQL-TDS][cat: Database/11][1 pkts/142 bytes -> 0 pkts/0 bytes][Goodput ratio: 62/0][< 1 sec][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] ++ 1 TCP 10.111.111.111:6666 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][7 pkts/8717 bytes -> 0 pkts/0 bytes][Goodput ratio: 96/0][< 1 sec][bytes ratio: 1.000 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 0/0 0/0 0/0][Pkt Len c2s/s2c min/avg/max/stddev: 393/0 1245/0 1514/0 436/0][Plen Bins: 0,0,0,0,0,0,0,0,0,0,14,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,0,0,0,0,0,0,71,0,0] ++ 2 TCP 10.111.111.111:5555 <-> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][10 pkts/1552 bytes <-> 7 pkts/1521 bytes][Goodput ratio: 64/75][7.22 sec][bytes ratio: 0.010 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 31/28 867/1024 1890/2071 763/864][Pkt Len c2s/s2c min/avg/max/stddev: 60/88 155/217 307/492 90/169][PLAIN TEXT (first )][Plen Bins: 0,42,7,14,0,7,0,14,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,0,0] ++ 3 TCP 10.111.111.111:1111 <-> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][2 pkts/614 bytes <-> 2 pkts/524 bytes][Goodput ratio: 78/75][0.14 sec][Plen Bins: 0,25,0,0,0,25,0,0,0,25,0,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] ++ 4 TCP 10.111.111.111:4444 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/1136 bytes -> 0 pkts/0 bytes][Goodput ratio: 95/0][< 1 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,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ++ 5 TCP 10.111.111.111:7777 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/425 bytes -> 0 pkts/0 bytes][Goodput ratio: 87/0][< 1 sec][Plen Bins: 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,0] ++ 6 TCP 10.111.111.111:33333 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/374 bytes -> 0 pkts/0 bytes][Goodput ratio: 85/0][< 1 sec][Plen Bins: 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,0,0] ++ 7 TCP 10.111.111.111:22222 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/322 bytes -> 0 pkts/0 bytes][Goodput ratio: 83/0][< 1 sec][Plen Bins: 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,0,0,0,0] ++ 8 TCP 10.111.111.111:9999 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/272 bytes -> 0 pkts/0 bytes][Goodput ratio: 80/0][< 1 sec][PLAIN TEXT (ABCDEFGHIJKLMNOPQ)][Plen Bins: 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,0,0,0,0,0,0] ++ 9 TCP 10.111.111.111:11111 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/253 bytes -> 0 pkts/0 bytes][Goodput ratio: 78/0][< 1 sec][Plen Bins: 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,0,0,0,0,0,0] ++ 10 TCP 10.111.111.111:3333 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/239 bytes -> 0 pkts/0 bytes][Goodput ratio: 77/0][< 1 sec][Plen Bins: 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,0,0,0,0,0,0,0] ++ 11 TCP 10.111.111.111:2222 <-> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/98 bytes <-> 1 pkts/71 bytes][Goodput ratio: 44/24][< 1 sec][Plen Bins: 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,0] ++ 12 TCP 10.111.111.111:8888 -> 10.0.0.1:1433 [proto: 114/MsSQL_TDS][cat: Database/11][1 pkts/142 bytes -> 0 pkts/0 bytes][Goodput ratio: 62/0][< 1 sec][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0] +diff --git a/tests/result/smpp_in_general.pcap.out b/tests/result/smpp_in_general.pcap.out +index 75cb5e0..42f0711 100644 +--- a/tests/result/smpp_in_general.pcap.out ++++ b/tests/result/smpp_in_general.pcap.out +@@ -1,3 +1,3 @@ + SMPP 17 1144 1 + +- 1 TCP 10.226.202.118:1770 <-> 10.226.202.53:9000 [proto: 207/SMPP][cat: Download-FileTransfer-FileSharing/7][10 pkts/670 bytes <-> 7 pkts/474 bytes][Goodput ratio: 18/16][30.95 sec][bytes ratio: 0.171 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 3848/7230 28802/28906 9451/12515][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 67/68 104/79 17/7][PLAIN TEXT (password)][Plen Bins: 75,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,0,0] ++ 1 TCP 10.226.202.118:1770 <-> 10.226.202.53:9000 [proto: 207/SMPP][cat: Download/7][10 pkts/670 bytes <-> 7 pkts/474 bytes][Goodput ratio: 18/16][30.95 sec][bytes ratio: 0.171 (Mixed)][IAT c2s/s2c min/avg/max/stddev: 0/0 3848/7230 28802/28906 9451/12515][Pkt Len c2s/s2c min/avg/max/stddev: 54/60 67/68 104/79 17/7][PLAIN TEXT (password)][Plen Bins: 75,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,0,0] +diff --git a/tests/result/weibo.pcap.out b/tests/result/weibo.pcap.out +index a27eb27..c53e993 100644 +--- a/tests/result/weibo.pcap.out ++++ b/tests/result/weibo.pcap.out +@@ -3,33 +3,33 @@ HTTP 19 2275 5 + TLS 15 1234 10 + Google 33 4778 7 + Amazon 2 132 1 +-Sina(Weibo) 419 258077 16 ++Weibo 419 258077 16 + + JA3 Host Stats: + IP Address # JA3C + 1 192.168.1.105 1 + + +- 1 TCP 192.168.1.105:35803 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][52 pkts/5367 bytes <-> 54 pkts/71536 bytes][Goodput ratio: 33/95][1.44 sec][Host: img.t.sinajs.cn][bytes ratio: -0.860 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 29/29 400/372 66/64][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 103/1325 533/4374 117/1466][URL: img.t.sinajs.cn/t6/style/css/module/base/frame.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][PLAIN TEXT (GET /t6/style/css/module/base/f)][Plen Bins: 0,0,0,0,1,0,0,0,0,0,0,1,3,5,3,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,70,0,0,5] +- 2 TCP 192.168.1.105:35804 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][32 pkts/3624 bytes <-> 40 pkts/50657 bytes][Goodput ratio: 38/95][1.33 sec][Host: img.t.sinajs.cn][bytes ratio: -0.866 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 48/39 314/338 89/82][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 113/1266 549/2938 132/620][URL: img.t.sinajs.cn/t6/style/css/module/combination/comb_login.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][PLAIN TEXT (GET /t6/style/css/module/combin)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,2,5,2,2,5,0,0,0,0,0,0,0,0,7,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,65,0,0,5] +- 3 TCP 192.168.1.105:51698 <-> 93.188.134.137:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][40 pkts/3462 bytes <-> 39 pkts/34030 bytes][Goodput ratio: 13/92][0.82 sec][Host: www.weibo.com][bytes ratio: -0.815 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 25/23 482/454 84/80][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 87/873 516/2938 69/915][URL: www.weibo.com/login.php?lang=en-us][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][PLAIN TEXT (GET /login.php)][Plen Bins: 38,0,0,5,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,5,2,0,0,0,0,5,0,0,0,0,0,0,0,24,0,0,10] +- 4 TCP 192.168.1.105:35807 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Sina(Weibo)][cat: SocialNetwork/6][27 pkts/2298 bytes <-> 26 pkts/34170 bytes][Goodput ratio: 21/95][0.53 sec][Host: img.t.sinajs.cn][bytes ratio: -0.874 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 23/22 183/162 50/47][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 85/1314 550/1502 91/448][URL: img.t.sinajs.cn/t6/style/images/growth/login/sprite_login.png?13434210384389][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][PLAIN TEXT (GET /t6/style/images/growth/log)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,0,0,0] +- 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][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][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][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] ++ 1 TCP 192.168.1.105:35803 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][52 pkts/5367 bytes <-> 54 pkts/71536 bytes][Goodput ratio: 33/95][1.44 sec][Host: img.t.sinajs.cn][bytes ratio: -0.860 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 29/29 400/372 66/64][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 103/1325 533/4374 117/1466][URL: img.t.sinajs.cn/t6/style/css/module/base/frame.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][PLAIN TEXT (GET /t6/style/css/module/base/f)][Plen Bins: 0,0,0,0,1,0,0,0,0,0,0,1,3,5,3,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,70,0,0,5] ++ 2 TCP 192.168.1.105:35804 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][32 pkts/3624 bytes <-> 40 pkts/50657 bytes][Goodput ratio: 38/95][1.33 sec][Host: img.t.sinajs.cn][bytes ratio: -0.866 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 48/39 314/338 89/82][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 113/1266 549/2938 132/620][URL: img.t.sinajs.cn/t6/style/css/module/combination/comb_login.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][PLAIN TEXT (GET /t6/style/css/module/combin)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,2,5,2,2,5,0,0,0,0,0,0,0,0,7,0,0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,65,0,0,5] ++ 3 TCP 192.168.1.105:51698 <-> 93.188.134.137:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][40 pkts/3462 bytes <-> 39 pkts/34030 bytes][Goodput ratio: 13/92][0.82 sec][Host: www.weibo.com][bytes ratio: -0.815 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 25/23 482/454 84/80][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 87/873 516/2938 69/915][URL: www.weibo.com/login.php?lang=en-us][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][PLAIN TEXT (GET /login.php)][Plen Bins: 38,0,0,5,0,0,0,2,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,2,0,0,0,0,0,0,5,2,0,0,0,0,5,0,0,0,0,0,0,0,24,0,0,10] ++ 4 TCP 192.168.1.105:35807 <-> 93.188.134.246:80 [proto: 7.200/HTTP.Weibo][cat: SocialNetwork/6][27 pkts/2298 bytes <-> 26 pkts/34170 bytes][Goodput ratio: 21/95][0.53 sec][Host: img.t.sinajs.cn][bytes ratio: -0.874 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/2 23/22 183/162 50/47][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 85/1314 550/1502 91/448][URL: img.t.sinajs.cn/t6/style/images/growth/login/sprite_login.png?13434210384389][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][PLAIN TEXT (GET /t6/style/images/growth/log)][Plen Bins: 0,0,0,0,0,0,0,0,0,0,0,0,0,8,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,88,0,0,0] ++ 5 TCP 192.168.1.105:35805 <-> 93.188.134.246:80 [proto: 7.200/HTTP.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][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.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][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.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][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: 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/1][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][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][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][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] ++ 11 TCP 192.168.1.105:35811 <-> 93.188.134.246:80 [proto: 7.200/HTTP.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][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.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][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] + 13 TCP 192.168.1.105:50827 <-> 47.89.65.229:443 [proto: 91/TLS][cat: Web/5][3 pkts/382 bytes <-> 1 pkts/66 bytes][Goodput ratio: 52/0][0.16 sec][ALPN: h2;spdy/3.1;http/1.1][TLSv1.2][Client: g.alicdn.com][JA3C: 58e7f64db6e4fe4941dd9691d421196c][PLAIN TEXT (g.alicdn.com)][Plen Bins: 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,0,0,0,0,0,0] +- 14 UDP 192.168.1.105:53543 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/75 bytes <-> 1 pkts/191 bytes][Goodput ratio: 43/78][0.11 sec][Host: img.t.sinajs.cn][93.188.134.246][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] +- 15 UDP 192.168.1.105:41352 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/74 bytes <-> 1 pkts/190 bytes][Goodput ratio: 43/77][0.54 sec][Host: js.t.sinajs.cn][93.188.134.246][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] ++ 14 UDP 192.168.1.105:53543 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/75 bytes <-> 1 pkts/191 bytes][Goodput ratio: 43/78][0.11 sec][Host: img.t.sinajs.cn][93.188.134.246][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] ++ 15 UDP 192.168.1.105:41352 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/74 bytes <-> 1 pkts/190 bytes][Goodput ratio: 43/77][0.54 sec][Host: js.t.sinajs.cn][93.188.134.246][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] + 16 UDP 192.168.1.105:51440 <-> 192.168.1.1:53 [proto: 5/DNS][cat: Network/14][1 pkts/72 bytes <-> 1 pkts/171 bytes][Goodput ratio: 41/75][0.19 sec][Host: g.alicdn.com][47.89.65.229][PLAIN TEXT (alicdn)][Plen Bins: 50,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,0,0,0,0] + 17 UDP 192.168.1.105:33822 <-> 192.168.1.1:53 [proto: 5/DNS][cat: Network/14][1 pkts/76 bytes <-> 1 pkts/166 bytes][Goodput ratio: 44/74][0.47 sec][Host: login.taobao.com][140.205.170.63][PLAIN TEXT (taobao)][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] +- 18 UDP 192.168.1.105:18035 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/81 bytes <-> 1 pkts/159 bytes][Goodput ratio: 48/73][0.11 sec][Host: u1.img.mobile.sina.cn][222.73.28.96][PLAIN TEXT (mobile)][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] ++ 18 UDP 192.168.1.105:18035 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/81 bytes <-> 1 pkts/159 bytes][Goodput ratio: 48/73][0.11 sec][Host: u1.img.mobile.sina.cn][222.73.28.96][PLAIN TEXT (mobile)][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] + 19 UDP 192.168.1.105:50640 <-> 192.168.1.1:53 [proto: 5/DNS][cat: Network/14][1 pkts/77 bytes <-> 1 pkts/157 bytes][Goodput ratio: 45/73][0.47 sec][Host: acjstb.aliyun.com][42.156.184.19][PLAIN TEXT (alibabadns)][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] +- 20 UDP 192.168.1.105:7148 <-> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/73 bytes <-> 1 pkts/142 bytes][Goodput ratio: 42/70][0.06 sec][Host: www.weibo.com][93.188.134.137][Plen Bins: 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,0] ++ 20 UDP 192.168.1.105:7148 <-> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/73 bytes <-> 1 pkts/142 bytes][Goodput ratio: 42/70][0.06 sec][Host: www.weibo.com][93.188.134.137][Plen Bins: 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,0] + 21 TCP 192.168.1.105:35808 <-> 93.188.134.246:80 [proto: 7/HTTP][cat: Web/5][2 pkts/140 bytes <-> 1 pkts/74 bytes][Goodput ratio: 0/0][0.06 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] + 22 TCP 192.168.1.105:50831 <-> 47.89.65.229:443 [proto: 91/TLS][cat: Web/5][2 pkts/128 bytes <-> 1 pkts/66 bytes][Goodput ratio: 0/0][0.22 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] + 23 TCP 192.168.1.105:59120 <-> 114.134.80.162:80 [proto: 7/HTTP][cat: Web/5][2 pkts/128 bytes <-> 1 pkts/66 bytes][Goodput ratio: 0/0][0.36 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] +@@ -42,7 +42,7 @@ JA3 Host Stats: + 30 TCP 192.168.1.105:40440 <-> 54.225.163.210:443 [proto: 91.178/TLS.Amazon][cat: Web/5][1 pkts/66 bytes <-> 1 pkts/66 bytes][Goodput ratio: 0/0][0.14 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] + 31 TCP 192.168.1.105:58480 <-> 216.58.214.78:443 [proto: 91.126/TLS.Google][cat: Web/5][1 pkts/66 bytes <-> 1 pkts/66 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] + 32 TCP 192.168.1.105:58481 <-> 216.58.214.78:443 [proto: 91.126/TLS.Google][cat: Web/5][1 pkts/66 bytes <-> 1 pkts/66 bytes][Goodput ratio: 0/0][0.05 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] +- 33 UDP 192.168.1.105:11798 -> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/77 bytes -> 0 pkts/0 bytes][Goodput ratio: 45/0][< 1 sec][Host: account.weibo.com][::][PLAIN TEXT (account)][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] ++ 33 UDP 192.168.1.105:11798 -> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/77 bytes -> 0 pkts/0 bytes][Goodput ratio: 45/0][< 1 sec][Host: account.weibo.com][::][PLAIN TEXT (account)][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] + 34 TCP 192.168.1.105:42280 -> 222.73.28.96:80 [proto: 7/HTTP][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][< 1 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] + 35 TCP 192.168.1.105:47721 -> 140.205.170.63:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][< 1 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] + 36 TCP 192.168.1.105:47723 -> 140.205.170.63:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][< 1 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] +@@ -52,5 +52,5 @@ JA3 Host Stats: + 40 TCP 192.168.1.105:52271 -> 42.156.184.19:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][< 1 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] + 41 TCP 192.168.1.105:52272 -> 42.156.184.19:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][< 1 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] + 42 TCP 192.168.1.105:52274 -> 42.156.184.19:443 [proto: 91/TLS][cat: Web/5][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 0/0][< 1 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] +- 43 UDP 192.168.1.105:50533 -> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 43/0][< 1 sec][Host: data.weibo.com][::][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] +- 44 UDP 192.168.1.105:16804 -> 192.168.1.1:53 [proto: 5.200/DNS.Sina(Weibo)][cat: SocialNetwork/6][1 pkts/70 bytes -> 0 pkts/0 bytes][Goodput ratio: 39/0][< 1 sec][Host: c.weibo.cn][::][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] ++ 43 UDP 192.168.1.105:50533 -> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/74 bytes -> 0 pkts/0 bytes][Goodput ratio: 43/0][< 1 sec][Host: data.weibo.com][::][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] ++ 44 UDP 192.168.1.105:16804 -> 192.168.1.1:53 [proto: 5.200/DNS.Weibo][cat: SocialNetwork/6][1 pkts/70 bytes -> 0 pkts/0 bytes][Goodput ratio: 39/0][< 1 sec][Host: c.weibo.cn][::][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/whatsappfiles.pcap.out b/tests/result/whatsappfiles.pcap.out +index 2be63e8..30ecc09 100644 +--- a/tests/result/whatsappfiles.pcap.out ++++ b/tests/result/whatsappfiles.pcap.out +@@ -5,5 +5,5 @@ JA3 Host Stats: + 1 192.168.2.29 2 + + +- 1 TCP 192.168.2.29:49698 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download-FileTransfer-FileSharing/7][132 pkts/9906 bytes <-> 178 pkts/237405 bytes][Goodput ratio: 12/95][7.27 sec][ALPN: h2;h2-16;h2-15;h2-14;spdy/3.1;spdy/3;http/1.1][bytes ratio: -0.920 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 62/47 5775/5834 571/481][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 75/1334 583/1464 51/1384][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 4e1a414c4f4c99097edd2a9a98e336c8][JA3S: 96681175a9547081bf3d417f1a572091][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,0,0,0,0] +- 2 TCP 192.168.2.29:49674 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download-FileTransfer-FileSharing/7][161 pkts/189194 bytes <-> 149 pkts/15728 bytes][Goodput ratio: 94/32][110.02 sec][ALPN: h2;h2-16;h2-15;h2-14;spdy/3.1;spdy/3;http/1.1][bytes ratio: 0.846 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 322/659 24639/64743 2278/6045][Pkt Len c2s/s2c min/avg/max/stddev: 66/60 1175/106 1464/1464 1272/167][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 107144b88827da5da9ed42d8776ccdc5][ServerNames: *.cdn.whatsapp.net,*.snr.whatsapp.net,*.whatsapp.com,*.whatsapp.net,whatsapp.com,whatsapp.net][JA3S: 2d1eb5817ece335c24904f516ad5da12][Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance Server CA][Subject: C=US, ST=California, L=Menlo Park, O=Facebook, Inc., CN=*.whatsapp.net][Certificate SHA-1: 10:54:EB:4A:A2:2A:42:2F:A6:1C:E7:9C:F4:84:10:7E:30:2E:56:BB][Validity: 2017-04-26 00:00:00 - 2018-05-01 12:00:00][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256][Plen Bins: 0,5,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,87,0,0,0,0] ++ 1 TCP 192.168.2.29:49698 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download/7][132 pkts/9906 bytes <-> 178 pkts/237405 bytes][Goodput ratio: 12/95][7.27 sec][ALPN: h2;h2-16;h2-15;h2-14;spdy/3.1;spdy/3;http/1.1][bytes ratio: -0.920 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 62/47 5775/5834 571/481][Pkt Len c2s/s2c min/avg/max/stddev: 66/66 75/1334 583/1464 51/1384][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 4e1a414c4f4c99097edd2a9a98e336c8][JA3S: 96681175a9547081bf3d417f1a572091][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256][Plen Bins: 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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,0,0,0,0] ++ 2 TCP 192.168.2.29:49674 <-> 185.60.216.53:443 [proto: 91.242/TLS.WhatsAppFiles][cat: Download/7][161 pkts/189194 bytes <-> 149 pkts/15728 bytes][Goodput ratio: 94/32][110.02 sec][ALPN: h2;h2-16;h2-15;h2-14;spdy/3.1;spdy/3;http/1.1][bytes ratio: 0.846 (Upload)][IAT c2s/s2c min/avg/max/stddev: 0/0 322/659 24639/64743 2278/6045][Pkt Len c2s/s2c min/avg/max/stddev: 66/60 1175/106 1464/1464 1272/167][TLSv1.2][Client: mmg-fna.whatsapp.net][JA3C: 107144b88827da5da9ed42d8776ccdc5][ServerNames: *.cdn.whatsapp.net,*.snr.whatsapp.net,*.whatsapp.com,*.whatsapp.net,whatsapp.com,whatsapp.net][JA3S: 2d1eb5817ece335c24904f516ad5da12][Issuer: C=US, O=DigiCert Inc, OU=www.digicert.com, CN=DigiCert SHA2 High Assurance Server CA][Subject: C=US, ST=California, L=Menlo Park, O=Facebook, Inc., CN=*.whatsapp.net][Certificate SHA-1: 10:54:EB:4A:A2:2A:42:2F:A6:1C:E7:9C:F4:84:10:7E:30:2E:56:BB][Validity: 2017-04-26 00:00:00 - 2018-05-01 12:00:00][Cipher: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256][Plen Bins: 0,5,0,1,0,0,2,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,87,0,0,0,0] +-- +2.20.1 + diff --git a/debian/patches/vyatta-category-file-ndpi-3.4-stable.patch b/debian/patches/vyatta-category-file-ndpi-3.4-stable.patch new file mode 100644 index 0000000..0143cd5 --- /dev/null +++ b/debian/patches/vyatta-category-file-ndpi-3.4-stable.patch @@ -0,0 +1,32 @@ +From e7e9aa7ba7e2a420bb9ed0bbea5ab6dd36f1c23a Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 09:58:22 +0000 +Subject: [PATCH] new vyatta-category-file added for ndpi-3.4(stable) + +--- + src/lib/ndpi_main.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c +index 4cbe722..0b42c1c 100644 +--- a/src/lib/ndpi_main.c ++++ b/src/lib/ndpi_main.c +@@ -2715,9 +2715,13 @@ int ndpi_load_categories_file(struct ndpi_detection_module_struct *ndpi_str, con + + if(name) { + category = strtok_r(NULL, "\t", &saveptr); ++ ndpi_protocol_category_t category_val = atoi(category); ++ if (category_val == 0) ++ category_val = ndpi_get_category_id(ndpi_str, category); + +- if(category) { +- int rc = ndpi_load_category(ndpi_str, name, (ndpi_protocol_category_t) atoi(category)); ++ ++ if(category_val > 0) { ++ int rc = ndpi_load_category(ndpi_str, name, category_val); + + if(rc >= 0) + num++; +-- +2.20.1 + diff --git a/debian/patches/vyatta-generated-ndpi-3.4-changes.patch b/debian/patches/vyatta-generated-ndpi-3.4-changes.patch new file mode 100644 index 0000000..9373263 --- /dev/null +++ b/debian/patches/vyatta-generated-ndpi-3.4-changes.patch @@ -0,0 +1,239 @@ +From 2874bfa9d6d3c0e8d8142008f545936cb12bf11f Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Thu, 5 Nov 2020 15:42:29 +0000 +Subject: [PATCH] nDPI-3.4 related changes for yang files + +--- + yang/vyatta-ndpi-application-names-v1.yang | 44 +++++++++++++--------- + yang/vyatta-ndpi-application-types-v1.yang | 8 ++-- + 2 files changed, 31 insertions(+), 21 deletions(-) + +diff --git a/yang/vyatta-ndpi-application-names-v1.yang b/yang/vyatta-ndpi-application-names-v1.yang +index e1290fd..e648884 100644 +--- a/yang/vyatta-ndpi-application-names-v1.yang ++++ b/yang/vyatta-ndpi-application-names-v1.yang +@@ -51,8 +51,8 @@ module vyatta-ndpi-application-names-v1 { + This file was auto-generated with + generation/application_names_yang.sh"; + +- revision 2020-01-06 { +- description "Initial revision based on nDPI 3.0.0."; ++ revision 2020-11-02 { ++ description "Revision based on nDPI version 3.4.0 (Stable)"; + } + + typedef application-names { +@@ -61,13 +61,13 @@ module vyatta-ndpi-application-names-v1 { + * It's not important to preserve alphabetical order. + */ + type enumeration { +- enum "104"; + enum "afp"; + enum "aimini"; + enum "ajp"; + enum "amazon"; + enum "amazonvideo"; + enum "amqp"; ++ enum "anydesk"; + enum "apple"; + enum "appleicloud"; + enum "appleitunes"; +@@ -76,10 +76,11 @@ module vyatta-ndpi-application-names-v1 { + enum "applestore"; + enum "armagetron"; + enum "ayiya"; +- enum "battlefield"; + enum "bgp"; + enum "bittorrent"; + enum "bjnp"; ++ enum "bloomberg"; ++ enum "capwap"; + enum "checkmk"; + enum "ciscoskinny"; + enum "ciscovpn"; +@@ -97,13 +98,14 @@ module vyatta-ndpi-application-names-v1 { + enum "dhcp"; + enum "dhcpv6"; + enum "diameter"; +- enum "direct_download_link"; + enum "directconnect"; ++ enum "direct_download_link"; ++ enum "discord"; + enum "dnp3"; + enum "dns"; + enum "dnscrypt"; +- enum "dnsoverhttps"; + enum "dofus"; ++ enum "doh_dot"; + enum "drda"; + enum "dropbox"; + enum "eaq"; +@@ -116,6 +118,12 @@ module vyatta-ndpi-application-names-v1 { + enum "fiesta"; + enum "fix"; + enum "florensia"; ++ enum "free183"; ++ enum "free_205"; ++ enum "free53"; ++ enum "free69"; ++ enum "free71"; ++ enum "free_90"; + enum "ftp_control"; + enum "ftp_data"; + enum "git"; +@@ -146,6 +154,7 @@ module vyatta-ndpi-application-names-v1 { + enum "icecast"; + enum "icmp"; + enum "icmpv6"; ++ enum "iec60870"; + enum "iflix"; + enum "igmp"; + enum "imap"; +@@ -162,7 +171,6 @@ module vyatta-ndpi-application-names-v1 { + enum "kontiki"; + enum "lastfm"; + enum "ldap"; +- enum "line"; + enum "linkedin"; + enum "lisp"; + enum "llmnr"; +@@ -174,14 +182,15 @@ module vyatta-ndpi-application-names-v1 { + enum "messenger"; + enum "mgcp"; + enum "microsoft"; ++ enum "microsoft365"; + enum "mining"; + enum "modbus"; + enum "mpeg_ts"; + enum "mqtt"; + enum "ms_onedrive"; +- enum "msn"; +- enum "mssql_tds"; ++ enum "mssql-tds"; + enum "mysql"; ++ enum "nats"; + enum "nestlogsink"; + enum "netbios"; + enum "netflix"; +@@ -192,24 +201,19 @@ module vyatta-ndpi-application-names-v1 { + enum "ntop"; + enum "ntp"; + enum "ocs"; +- enum "office365"; + enum "ookla"; + enum "opendns"; + enum "openft"; + enum "openvpn"; + enum "oracle"; +- enum "oscar"; + enum "ospf"; +- enum "pando_media_booster"; + enum "pandora"; + enum "pastebin"; +- enum "pcanywhere"; + enum "playstation"; + enum "playstore"; + enum "pop3"; + enum "pops"; + enum "postgresql"; +- enum "pplive"; + enum "ppstream"; + enum "pptp"; + enum "ps_vue"; +@@ -226,11 +230,13 @@ module vyatta-ndpi-application-names-v1 { + enum "rtp"; + enum "rtsp"; + enum "rx"; ++ enum "s7comm"; + enum "sap"; + enum "sctp"; + enum "sflow"; + enum "shoutcast"; + enum "signal"; ++ enum "sina(weibo)"; + enum "sip"; + enum "skype"; + enum "skypecall"; +@@ -242,6 +248,7 @@ module vyatta-ndpi-application-names-v1 { + enum "smtps"; + enum "snapchat"; + enum "snmp"; ++ enum "soap"; + enum "socks"; + enum "someip"; + enum "sopcast"; +@@ -255,7 +262,8 @@ module vyatta-ndpi-application-names-v1 { + enum "steam"; + enum "stun"; + enum "syslog"; +- enum "targus_dataspeed"; ++ enum "targus dataspeed"; ++ enum "teams"; + enum "teamspeak"; + enum "teamviewer"; + enum "telegram"; +@@ -269,7 +277,6 @@ module vyatta-ndpi-application-names-v1 { + enum "tor"; + enum "truphone"; + enum "tuenti"; +- enum "tvants"; + enum "tvuplayer"; + enum "twitch"; + enum "twitter"; +@@ -288,12 +295,12 @@ module vyatta-ndpi-application-names-v1 { + enum "warcraft3"; + enum "waze"; + enum "webex"; ++ enum "websocket"; + enum "wechat"; +- enum "weibo"; + enum "whatsapp"; + enum "whatsappcall"; + enum "whatsappfiles"; +- enum "whois_das"; ++ enum "whois-das"; + enum "wikipedia"; + enum "windowsupdate"; + enum "wireguard"; +@@ -304,6 +311,7 @@ module vyatta-ndpi-application-names-v1 { + enum "yahoo"; + enum "youtube"; + enum "youtubeupload"; ++ enum "zabbix"; + enum "zattoo"; + enum "zeromq"; + enum "zoom"; +diff --git a/yang/vyatta-ndpi-application-types-v1.yang b/yang/vyatta-ndpi-application-types-v1.yang +index 1bad8ea..1366a41 100644 +--- a/yang/vyatta-ndpi-application-types-v1.yang ++++ b/yang/vyatta-ndpi-application-types-v1.yang +@@ -51,8 +51,8 @@ module vyatta-ndpi-application-types-v1 { + This file was auto-generated with + generation/application_types_yang.sh"; + +- revision 2020-01-06 { +- description "Initial revision based on nDPI 3.0.0."; ++ revision 2020-11-02 { ++ description "Revision based on nDPI version 3.4.0 (Stable)"; + } + + typedef application-types { +@@ -64,12 +64,14 @@ module vyatta-ndpi-application-types-v1 { + enum "chat"; + enum "cloud"; + enum "collaborative"; ++ enum "connectivitycheck"; + enum "database"; + enum "datatransfer"; +- enum "download"; ++ enum "download"; + enum "email"; + enum "filesharing"; + enum "game"; ++ enum "iot-scada"; + enum "media"; + enum "music"; + enum "network"; +-- +2.20.1 + diff --git a/debian/patches/vyatta-generation-modified.patch b/debian/patches/vyatta-generation-modified.patch new file mode 100644 index 0000000..5eeda4d --- /dev/null +++ b/debian/patches/vyatta-generation-modified.patch @@ -0,0 +1,556 @@ +From: Subhajit Chatterjee +Date: Fri, 06 Nov 2020 22:40:06 +0000 +Subject: Add vyatta nDPI generation scripts (modified for ndpi-3.4) + +--- + generation/Makefile.in | 11 +++ + generation/README.md | 25 +++++++ + generation/application_names_yang.sh | 113 +++++++++++++++++++++++++++++ + generation/application_types_yang.sh | 113 +++++++++++++++++++++++++++++ + generation/applications_list.c | 135 +++++++++++++++++++++++++++++++++++ + generation/ndpi_show_name.c | 104 +++++++++++++++++++++++++++ + 8 files changed, 503 insertions(+), 2 deletions(-) + create mode 100644 generation/Makefile.in + create mode 100644 generation/README.md + create mode 100755 generation/application_names_yang.sh + create mode 100755 generation/application_types_yang.sh + create mode 100644 generation/applications_list.c + create mode 100644 generation/ndpi_show_name.c + +diff --git a/generation/Makefile.in b/generation/Makefile.in +new file mode 100644 +index 0000000..886e17e +--- /dev/null ++++ b/generation/Makefile.in +@@ -0,0 +1,11 @@ ++CC=@CC@ ++CFLAGS=-g -I../src/include @CFLAGS@ ++LIBNDPI=../src/lib/libndpi.a ++LDFLAGS=$(LIBNDPI) @PCAP_LIB@ -lpthread -lm @LDFLAGS@ ++ ++all: applications_list ndpi_show_name ++ ++%: %.c Makefile ++ $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) ++ ++install: ; +diff --git a/generation/README.md b/generation/README.md +new file mode 100644 +index 0000000..c61e395 +--- /dev/null ++++ b/generation/README.md +@@ -0,0 +1,25 @@ ++# Generation scripts ++ ++Scripts to generate yang files and show-command scripts. ++ ++## Usage ++ ++The scripts are `application_names_yang.sh`, `application_types_yang.sh`. ++ ++These accept the `OLD_YANG_FILE` environment variable to use an old yang ++file's "header" information (description, copyright, revisions, etc) rather ++than starting from fresh. ++ ++These will prompt the user via stderr for revision notes, which are ++pulled from /dev/stdin, so can be piped in. ++ ++## Examples ++ ++Generate a new nDPI application names yang file, using `/path/base.yang` as a base, ++"Revision notes" as the notes for the revision, and writing it to ++`new_applications.yang`. ++`application_types_yang.sh` is invoked similarly. ++ ++``` ++echo "Revision Notes" | OLD_YANG_FILE=/path/base.yang ./application_names_yang.sh > new_applications.yang ++``` +diff --git a/generation/application_names_yang.sh b/generation/application_names_yang.sh +new file mode 100755 +index 0000000..770d65c +--- /dev/null ++++ b/generation/application_names_yang.sh +@@ -0,0 +1,113 @@ ++#!/bin/bash ++ ++# Copyright (c) 2020, AT&T Intellectual Property. All rights reserved. ++ ++# Create a new/updated vyatta-ndpi-applications-names.yang file, ++# writing to stdout. ++# ./application_list must exist. ++# ++# If OLD_YANG_FILE is set, the file will be read in, have the existing names ++# removed and used instead of a fresh file. ++# ++# Revision notes will be taken from stdin and added to the resulting yang with ++# today's date as a new revision. ++ ++# If OLD_YANG_FILE set, read it, cut out the pre-existing names and use the ++# remainder as the header. ++# Otherwise, use the heredoc. ++heading= ++if [ -z "${OLD_YANG_FILE}" ]; then ++ read -r -d '' heading << EndOfHeader ++module vyatta-ndpi-application-names-v1 { ++ namespace "urn:vyatta.com:mgmt:vyatta-ndpi-application-names:1"; ++ prefix vyatta-ndpi-application-names-v1; ++ ++ organization "AT&T Inc."; ++ contact "AT&T ++ Postal: 208 S. Akard Street ++ Dallas, TX 75202, USA ++ Web: www.att.com"; ++ ++ description ++ "Copyright (c) 2020, AT&T Intellectual Property ++ All rights reserved. ++ ++ Redistribution and use in source and binary forms, ++ with or without modification, are permitted provided ++ that the following conditions are met: ++ ++ 1. Redistributions of source code must retain the ++ above copyright notice, this list of conditions and ++ the following disclaimer. ++ 2. Redistributions in binary form must reproduce ++ the above copyright notice, this list of conditions ++ and the following disclaimer in the documentation ++ and/or other materials provided with the distribution. ++ 3. Neither the name of the copyright holder nor the ++ names of its contributors may be used to endorse or ++ promote products derived from this software without ++ specific prior written permission. ++ ++ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ++ AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED ++ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ++ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ++ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ++ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, ++ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED ++ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS ++ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER ++ IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE ++ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY ++ OF SUCH DAMAGE. ++ ++ SPDX-License-Identifier: BSD-3-Clause ++ ++ Application names associated with the nDPI engine. ++ ++ This file was auto-generated with ++ generation/application_names_yang.sh"; ++EndOfHeader ++else ++ heading=$(sed '1,/typedef application-names /!d' ${OLD_YANG_FILE} | head -n -1) ++fi ++ ++# Get the all names from applications_list ++new_list=$(./applications_list --names | sort -u) ++ ++# Get the names from the old yang file, which may be empty ++if [ -n "$OLD_YANG_FILE" ]; then ++ old_list=$(sed '1,/typedef application-names/d' ${OLD_YANG_FILE} | grep -o 'enum ".*"' | cut -d '"' -f 2) ++ ++ # Extract the new names and append them to the old list ++ TMP=/tmp/app_name_yang.tmp ++ echo "$old_list" > $TMP ++ apps=$(echo "${old_list}" && comm -13 $TMP - <<< $new_list | sed '/^[[:blank:]]*$/d') ++ rm -f $TMP ++else ++ apps=$new_list ++fi ++ ++# Convert each name into an application_names enum variant with correct indentation. ++yang="$(awk -F, '{print "\t\t\t" "enum \"" $1 "\";"}' <<< $apps)" ++ ++# Prompt the user for revision notes. ++>&2 echo "Enter revision notes:" ++changes=$( $TMP ++ apps=$(echo "${old_list}" && comm -13 $TMP - <<< $new_list | sed '/^[[:blank:]]*$/d') ++ rm -f $TMP ++else ++ apps=$new_list ++fi ++ ++# Convert each type into an application_types enum variant with correct indentation. ++yang="$(awk -F, '{print "\t\t\t" "enum \"" $1 "\";"}' <<< $apps)" ++ ++# Prompt the user for revision notes. ++>&2 echo "Enter revision notes:" ++changes=$( ++#include ++#include "ndpi_api.h" ++ ++ ++char *strlwr(const char *str) ++{ ++ char *dup = strdup(str); ++ ++ for (char *p = dup; *p; ++p) *p = tolower(*p); ++ ++ return dup; ++} ++ ++ ++void show_categories(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ for (int i = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED + 1; ++ i < NDPI_PROTOCOL_NUM_CATEGORIES; ++ i++) { ++ const char *category_name = ndpi_category_get_name(ndpi_info_mod, i); ++ if ( (i >= CUSTOM_CATEGORY_MINING && ++ i <= CUSTOM_CATEGORY_ANTIMALWARE) || ++ ndpi_is_custom_category(i) || ++ (category_name == "") ) { ++ continue; ++ } ++ ++ printf("%s\n", strlwr(category_name)); ++ } ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void show_protocols(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); ++ ++ for (int i = NDPI_PROTOCOL_UNKNOWN; ++ i < num_protocols; ++ i++) { ++ const char *proto_name = ndpi_get_proto_name(ndpi_info_mod, i); ++ if (proto_name == "") { ++ continue; ++ } ++ ++ printf("%s\n", strlwr(proto_name)); ++ } ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void show_mapping(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); ++ ++ ndpi_proto_defaults_t *pd = ndpi_get_proto_defaults(ndpi_info_mod); ++ ++ for (int i = NDPI_PROTOCOL_UNKNOWN; i < num_protocols; i++) { ++ const char *proto_name = ndpi_get_proto_name(ndpi_info_mod, i); ++ if (proto_name == "") { ++ continue; ++ } ++ ++ ndpi_protocol_category_t category = pd[i].protoCategory; ++ const char *category_name = ndpi_category_get_name(ndpi_info_mod, category); ++ printf("%s %s\n", strlwr(proto_name), strlwr(category_name)); ++ } ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void dump_protocols(void) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ ndpi_dump_protocols(ndpi_info_mod); ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void usage(char *name) ++{ ++ printf("Usage: %s [--names | --types | --mapping | --dump | --revision]\n", name); ++} ++ ++ ++int main(int argc, char **argv) ++{ ++ if (argc < 2) { ++ usage(argv[0]); ++ return -1; ++ } ++ ++ if (!strcmp(argv[1], "--revision")) ++ printf("Revision: %s\n", ndpi_revision()); ++ ++ else if (!strcmp(argv[1], "--types")) ++ show_categories(); ++ ++ else if (!strcmp(argv[1], "--names")) ++ show_protocols(); ++ ++ else if (!strcmp(argv[1], "--mapping")) ++ show_mapping(); ++ ++ else if (!strcmp(argv[1], "--dump")) ++ dump_protocols(); ++ ++ else { ++ usage(argv[0]); ++ return -1; ++ } ++} +diff --git a/generation/ndpi_show_name.c b/generation/ndpi_show_name.c +new file mode 100644 +index 0000000..f243e19 +--- /dev/null ++++ b/generation/ndpi_show_name.c +@@ -0,0 +1,104 @@ ++/* ++ * Copyright (c) 2020, AT&T Intellectual Property. All rights reserved. ++ * All rights reserved. ++ * ++ * SPDX-License-Identifier: LGPL-2.1-only ++ */ ++ ++#include ++#include ++#include "ndpi_api.h" ++ ++ ++char *strlwr(const char *str) ++{ ++ char *dup = strdup(str); ++ ++ for (char *p = dup; *p; ++p) *p = tolower(*p); ++ ++ return dup; ++} ++ ++ ++/* ++ * Show the application names associated with the given application type. ++ */ ++void show_type(char *type) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ int id = ndpi_get_category_id(ndpi_info_mod, type); ++ if (id == -1) return; ++ ++ u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); ++ ++ ndpi_proto_defaults_t *pd = ndpi_get_proto_defaults(ndpi_info_mod); ++ ++ printf("%s includes the following applications:\n", type); ++ ++ for (int i = NDPI_PROTOCOL_UNKNOWN; i < num_protocols; i++) { ++ ndpi_protocol_category_t category = pd[i].protoCategory; ++ ++ if (category == id) { ++ const char *proto_name = strlwr(ndpi_get_proto_name(ndpi_info_mod, i)); ++ if (proto_name == "") { ++ continue; ++ } ++ ++ printf("%s ", proto_name); ++ } ++ } ++ ++ printf("\n"); ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++/* ++ * Show the application type associated with the given application name. ++ */ ++void show_name(char *name) ++{ ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ if (ndpi_info_mod == NULL) return; ++ ++ int id = ndpi_get_protocol_id(ndpi_info_mod, name); ++ if (id == -1) return; ++ ++ ndpi_proto_defaults_t *pd = ndpi_get_proto_defaults(ndpi_info_mod); ++ ++ ndpi_protocol_category_t category = pd[id].protoCategory; ++ const char *category_name = strlwr(ndpi_category_get_name(ndpi_info_mod, category)); ++ ++ printf("%s is included in the following application types:\n%s\n", ++ name, category_name); ++ ++ ndpi_exit_detection_module(ndpi_info_mod); ++} ++ ++ ++void usage(char *name) ++{ ++ printf("Usage: %s [name | type] \n", name); ++} ++ ++ ++int main(int argc, char **argv) ++{ ++ if (argc != 3) { ++ usage(argv[0]); ++ return -1; ++ } ++ ++ if (!strcmp(argv[1], "type")) { ++ show_type(argv[2]); ++ } ++ else if (!strcmp(argv[1], "name")) { ++ show_name(argv[2]); ++ } ++ else { ++ usage(argv[0]); ++ return -1; ++ } ++} diff --git a/debian/patches/vyatta-generation-ndpi-3.4-changes.patch b/debian/patches/vyatta-generation-ndpi-3.4-changes.patch new file mode 100644 index 0000000..566e3e7 --- /dev/null +++ b/debian/patches/vyatta-generation-ndpi-3.4-changes.patch @@ -0,0 +1,75 @@ +From c9afb998c5a35117ec8ba1dcea39a1a108a5a524 Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Thu, 5 Nov 2020 15:36:33 +0000 +Subject: [PATCH] Added nDPI-3.4 related changes in Vyatta Generation Patch + +--- + generation/applications_list.c | 8 ++++---- + generation/ndpi_show_name.c | 4 ++-- + 2 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/generation/applications_list.c b/generation/applications_list.c +index 0332d8e..ed7399d 100644 +--- a/generation/applications_list.c ++++ b/generation/applications_list.c +@@ -22,7 +22,7 @@ char *strlwr(const char *str) + + void show_categories(void) + { +- struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(ndpi_no_prefs); + if (ndpi_info_mod == NULL) return; + + for (int i = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED + 1; +@@ -45,7 +45,7 @@ void show_categories(void) + + void show_protocols(void) + { +- struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(ndpi_no_prefs); + if (ndpi_info_mod == NULL) return; + + u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); +@@ -67,7 +67,7 @@ void show_protocols(void) + + void show_mapping(void) + { +- struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(ndpi_no_prefs); + if (ndpi_info_mod == NULL) return; + + u_int num_protocols = ndpi_get_ndpi_num_supported_protocols(ndpi_info_mod); +@@ -91,7 +91,7 @@ void show_mapping(void) + + void dump_protocols(void) + { +- struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(ndpi_no_prefs); + if (ndpi_info_mod == NULL) return; + + ndpi_dump_protocols(ndpi_info_mod); +diff --git a/generation/ndpi_show_name.c b/generation/ndpi_show_name.c +index f243e19..cca6daf 100644 +--- a/generation/ndpi_show_name.c ++++ b/generation/ndpi_show_name.c +@@ -25,7 +25,7 @@ char *strlwr(const char *str) + */ + void show_type(char *type) + { +- struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(ndpi_no_prefs); + if (ndpi_info_mod == NULL) return; + + int id = ndpi_get_category_id(ndpi_info_mod, type); +@@ -60,7 +60,7 @@ void show_type(char *type) + */ + void show_name(char *name) + { +- struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(); ++ struct ndpi_detection_module_struct *ndpi_info_mod = ndpi_init_detection_module(ndpi_no_prefs); + if (ndpi_info_mod == NULL) return; + + int id = ndpi_get_protocol_id(ndpi_info_mod, name); +-- +2.20.1 + diff --git a/debian/patches/vyatta-increase-ndpi-num-bits-ndpi-3.4-stable.patch b/debian/patches/vyatta-increase-ndpi-num-bits-ndpi-3.4-stable.patch new file mode 100644 index 0000000..2493000 --- /dev/null +++ b/debian/patches/vyatta-increase-ndpi-num-bits-ndpi-3.4-stable.patch @@ -0,0 +1,26 @@ +From 71bb7be5b01f96cfa197c8837369716bc34f088a Mon Sep 17 00:00:00 2001 +From: Subhajit Chatterjee +Date: Fri, 6 Nov 2020 09:52:19 +0000 +Subject: [PATCH] new vyatta-increase-ndpi-num-bits patch added for + ndpi-3.4(stable) + +--- + src/include/ndpi_define.h.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/include/ndpi_define.h.in b/src/include/ndpi_define.h.in +index fdfe998..d65c718 100644 +--- a/src/include/ndpi_define.h.in ++++ b/src/include/ndpi_define.h.in +@@ -256,7 +256,7 @@ + #define NDPI_COMPARE_IPV6_ADDRESS_STRUCTS(x,y) \ + ((x.u6_addr.u6_addr64[0] < y.u6_addr.u6_addr64[0]) || ((x.u6_addr.u6_addr64[0] == y.u6_addr.u6_addr64[0]) && (x.u6_addr.u6_addr64[1] < y.u6_addr.u6_addr64[1]))) + +-#define NDPI_NUM_BITS 512 ++#define NDPI_NUM_BITS 1024 + + #define NDPI_BITS /* 32 */ (sizeof(ndpi_ndpi_mask) * 8 /* number of bits in a byte */) /* bits per mask */ + #define howmanybits(x, y) (((x)+((y)-1))/(y)) +-- +2.20.1 + diff --git a/debian/rules b/debian/rules index d1917aa..cb0e8f1 100755 --- a/debian/rules +++ b/debian/rules @@ -18,7 +18,7 @@ override_dh_auto_configure: override_dh_auto_install: dh_auto_install - rm $(CURDIR)/debian/tmp/usr/sbin/ndpi/*.txt + rm $(CURDIR)/debian/tmp/usr/share/ndpi/*.txt override_dh_auto_test: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) @@ -30,6 +30,24 @@ ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) rm -f tests/pcap/skype.pcap rm -f tests/pcap/skype_no_unknown.pcap rm -f tests/pcap/1kxun.pcap + rm -f tests/pcap/quic_q50.pcap + rm -f tests/pcap/quic_t50.pcap + rm -f tests/pcap/quic_t51.pcap + rm -f tests/pcap/quic-mvfst-exp.pcap + rm -f tests/pcap/quic-mvfst-27.pcap + rm -f tests/pcap/quic-mvfst-22.pcap + rm -f tests/pcap/quic-23.pcap + rm -f tests/pcap/quic-24.pcap + rm -f tests/pcap/quic-27.pcap + rm -f tests/pcap/quic-28.pcap + rm -f tests/pcap/quic-29.pcap + rm -f tests/pcap/gquic.pcap + rm -f tests/pcap/dnscrypt.pcap + rm -f tests/pcap/exe_download.pcap + rm -f tests/pcap/ftp_failed.pcap + rm -f tests/pcap/fuzz-2006-06-26-2594.pcap + rm -f tests/pcap/msnms.pcap + rm -f tests/pcap/quic-mvfst-exp.pcap cd tests && LD_LIBRARY_PATH=$(CURDIR)/src/lib ./do.sh endif @@ -37,4 +55,8 @@ override_dh_strip: dh_strip --dbgsym-migration='libndpi-dbg (<< 1.8-1~)' +#override_dh_shlibdeps: +# dh_shlibdeps -l$(CURDIR)/src/lib --dpkg-shlibdeps-params=--ignore-missing-info + + .PHONY: override_dh_auto_configure override_dh_strip diff --git a/doc/nDPI_QuickStartGuide.pages b/doc/nDPI_QuickStartGuide.pages index 0f0db9ae259ef7b393e493b6fcf45aad96b0fbf8..7751951d3872995155be615f2493c1486e4945bc 100644 GIT binary patch literal 133048 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 Cll9!p&$_wQ!xdMaXkXR#5Ja z&B|Y{oHx|k9mspTPr_8sZ_ni)4>1|lnDd9y`#m>zK=P@V8mgbf&M-3DpO!7W?fBi1 zYO(NDY&}i=#Zv9>Gt9j*dq2RR$MzmPaBxW0w=?AFboji!>>Q(Dp?tz{=riqS_aPaY zXT+V-D6a)<1=m;F)n z{WoJqx(DUz={6E~!hin$d|f@-bvA9v)#3N}Rj{!PYx?$W-)C7W8|Y)&zCssOc|oi& zukU^L>#rSZzMWn*cMQ;PrRBK&vM!Y0yA7ZKG5PR){LcMtwd4V(?|IQj{C8WSUs67d zb)m#95_XYh1ZJ%(o~|{S*Zw^082QAETH|$Ss$=^yvFf=k8k}8jD}tPAzP)$|UH=$y zFW*e|q$fFjMQ^9ahd6Jq+@$*#HAnb4V|jOChV~Kf4AfmUKcw`z^CpUX;@zt7Rh4#m zR4KWj1Z3-}9=Ch!O+RpI$!c@awKtcGt$2s7Uqicq8SxP_DRz{ZEt7NJkIFL2`GC+3 zmiQh1fz=fzuYdP?v)-E*>G?)1v`!R!^hvvz+Q~TAx6=0xOFh)8l})^B6NWmzsq&}W zdbcU)M;Xk3TrP@B{4rVZROx>q3L|Lrs&>8ixz&%`}EoB^>fZs@5`XRweaPMz(}!x z?IiWjQ!!*Q-3o;o*H30TYc{F7O<^uc!8;3s1%CG<%o6Hjge&cA!mNJZzTpP@ znBomXEb<734VK?iE&L^doisA8tJQ)}{!Mus+PXGpSSg3g9G*O!?yiVE5|MNQ=SjTi za1Qc&u7sLMhF^_^o<$ z!$#QR!gJ5-4MKO1r?K@5&RW+8Qn8X5inbrqccm!Y8ZnW{hCM4{eoR^X7V-B7II#ZbTQSwWLv)pmR325{^DG)`TBP^sisRcn>PCPlX=f)wCvF*t!K+6f!|R|Y)h1OLr}x?aI2SrwMY$>)o&WY!bQA5K9i?DPJd$Y?*cE;GjIZWB<+APc z6)hXuRx8&(cX)EFym{pT)xD!fL;-J8JisTv`R}sNnucypNH_Nvq?@BN&0Ol7e5Z=^ zC%T)e_B;#fok3+K(QjS69I`v5qgvDHd8_r?p+3xlXC0%9>SVe*X}`?f9h=Q5SU$$a zA;)m|fM>InQQ_0-!#Z1(1n8@g-fiQCTe&*^Z}hg#+BZBsf;N2A@(}7cucV%AlKfWx zvZwgLPuxQrCi8YB?Nj*vp0~m0`#pr=+nLhGTV_aD+B`_-Gaj2pc%{TD0Y^qGUK zkpSIWe^yvBAhrGc@#>nEeXaJnvumRZ><hJC5AAxOs5fg`}A<83k)Whdh(pGHqu*0a4X7g049)XfyVKYkaS2{ug$aXdKbr(U)B{HmLlt!`7C zZFJUpv!fPu=+|L~gtJ+n-`>)?SR&VdHNCLz^#|#(&6|ZsHemA=S17G)Sw?y8^m$zg zI#%C81#Pu4|BOMmPiVgJl2B>Y36E{lj!%r0HFr0iMg^WpM~6JrDAIo=We43gf8)I; z)30xCGAD8P%wb)JANvX|3qDA12s-Azb?oK$(&lzTwURBI91EX?By(>ir@OM-p5!>Y zf6dIieI}#CQy4kouzgvx;fcZof$8&IQiUrCiJq;>H%YlG>S{CWvwnQPwyi}x=>YpT z`n6eRm$)x!^6(}<^HI;P4#}Dy;x{@_@A_N1^sZ!u2VBZBRw0BQ8q>Gq=-(3?Y&??c z+2t;_i;%Rw*V8DW;cAyaLX7h%_2b4{L8&oOQ2Ytv%HOpL4|g)W-=l`^uGpqt$KDci zcw5ZHAy!c;TI~jYoyE^6?|^BC@XOZ2b`$rvCgZM@`u88${H)&m;SxD&6T71g?&`b! zp)qEBEONDKBt7HUy;tgAAN4HVQRyEn{@gFMpzn$2iX`X{he1vuuWPPK368dGuCyZ~uYU31?CJ)}?n7(mbLE z?!kL2Fl*!n2!r=GJDH*@2`_5KxT}qKeHfRh^ZqfMyx+#&JnaVUen4d$Qky9U^X8Rq z{YnrE3q`8NZ;XUIP;_|GdJBKdz#=rVd&}P4HNMw%HaIN1TzjtORFT+3G1O5lFY548 z)-dyD4z`_;h&Jzozg@Qs`(UHKM#cdrtwfBOnRkMsdc(FVOcXcWV+JSW$R|Wx&Ss*c z35^D`9aqFA`-?8@kbfP29u98XplhQb`9^02N-wGbHb`E=o85hjqAz;3#ms>wGiUXlxVlf1VlmRiFj!c>U}mI+tb>66q|>pSZWDROx7z z895|)N$;TE$X@HqdNm)SCTd?C+%jbSVt3EV$y-p>pL}y|D%tuAXZ_iWoP_nV;~^8I zTKT7Go%U`Tz7Csw(*tCtJ^S5{_$E*CdT04bUw=)zvAY{wlpSKi(?a(JJRnyTWzL0Z z_OHF9>*N;QwnJKD6V>T--c3=6Q}3#}<<4fY*3JWt42Mk(n5BcK3-IO&>?v=eBPh+Fb@=TO^hmF8N`p3=&`H0P&MZny4R#wwY2DTmulD^Yh?MgqKK zaK8P8rX!`h0*;sIr|WfonO?foQ6%Kv z)~)L%$KpaQ2-!tNqP}N?7t`jl8P=oEzXit0PgqnK?)WxW^YV*eN|n~4#!jtyZcVAY%%#E z_1RzuJvgU+e(8iHi!$byf$LVtJO;6*Km2krC;9d`_~_+oD36J zDJSQIcGmC7I#0JjW@CR0`1V>YyQTB=zLflRsl&VOEA0KkE!`p?rCjj+)++nV9wY?6 zRb}J~B**f}m3cR!Yp~+cVaH->lz55LGDUf0)2x%y?K?6DZ=R4S&-1l(cx}Q zq-G!PMv{|F+K9u^jn%0`+pYRve$;Q*E_F>xXZ14-cJ*K`xNXEsT|Z~7VO;p$4wYUI zr&EPa(Av>zPi{Ep?C+{u#jLLSnu>m1yrXhYUc~LaO(q%vuZ41)_5Sx&V#>_ref!)# zjfL$LI4@uRUQ_)tLvW@Z7T9g%$3Om!GS1$ziXAz|M5j5_%Xf7w`xf!FeccSR19_cw zGtQiRAY<+Ppq)1C<{6=|02ABFX#<3Y`2Q{m-gK8E0q;FyAwl z@9lR@ZFkxdn@0W7I1H^hv#{heJ4O9&ZFyKlnopm!WMZBkJ0xK|$R?zIBcx|g`T*%V z{lF^7}lNkaVr~D&!75mdPZHH zaTn@wP=nNxv^E87#L3L=!ZwgB10PK}6~Przn!gy942hXIx_|S!OB=mzJSe9MmsYjl`#(CTox=A%!yPMvkVUk7mAee z9xT3y73RkhD2u!Li+C_z0_VcpgC|MgY*_wS0!C9@(ht(40Pz0=21l$PEQaGmYA)7M z0Yt!J8;YNkJ{n9#9}T9V4+a(Jqrn7V0yz?thjU?UFb9-`2kE21!1`z~q&^yqr;i4s znZTydzX204k>C`@VmSX+`mYvRR$?CE+yG{zm6$URi<1%e3}*>}O7L4)0bKkN8JuTu zxByF$5VI4o;bkf~52h$w0Gc~UT0nmxf+H0``R6&sBF7yKx^zc7^FRW+b4P=|KrT4k z9SwQ`wM>!EViO;DCG;O~@sb^{;xqWfbfY1-nMUE>W;eEDU=9oB^JIEs{9M4wl0u!7z-WIfDamg#Rja zN`aT?2}QKsK>H$=0G#P_1C~i)3d#L}h$mb798@&qiJb+CXf6ji`WEV*1B&4w4o=CU8b z;sF6V&dxfKEg(m8c6O#kcEPv+n4k-CeXx`*_|c*TND?(hVC;qsCShT3VH_4mHW3P^ zVFGc93@vIFmZVC4_KJ#%ibcU%Nhnx51spTL@e&GNU5J9|B`Dao0tKr+LZL^ZZ?&K> zt4EGr=t7}gm@a)N{7S8rD|NM3u3R~SLa(&deTx$NTcSFRl31w)D{R-*eU$U)@+=B% z>*XGc#uLz%UN#8IQUxnvW99CIpxjkr(F6k8+R_F=Tf~@;Br5%E?v$nIuy2^Q5gL+$QyoQXos~v*I$}djR+YL0ZwsW(?))#ntdxM!>mPfI; zU~g$6X`*q(OTh-yLb8DT(6|S3e6W?o!f4zR5-f&!3KFOjm}eK@8Q?jf8}I_~5-Wz8 zlY@0@ZYv~#3vMf10%;qm0EWNAPz~AU4-7@S+r*=w=mx73s0?bMRT`?j?Sxe}3Ko;g zM?sK`RS{O~vt1d;Mw{P9K{Y7&DGKUC$!>7l<-`=S1P(0GE@wSGeO>M@XFInMlo(sc zqcaUZqhO#U+EDRGo|$`MKeZAuBHl+sVWLm4KIkjp5q$lc_-l$Ydtl>3x&N(H5oQbnnzJfJ+J)KF?E zk0^DN$CT&fL2^B%fzn86qBK)lD6Nz>N;{>4@`Un~(n;x}Jfl3P3{hWGhp8jfQR*0V zi1?Z~OdKJO632++#0lag@eOf`_?D;-OA$`X$z!cJOl~N!tURK-91dQ#@cWRt>cYYy zb2|L)rbeI6Gv1DL;ftQe3bmppHJEFr_fjoo(-~Tl17=yi!DYk4=_|~R$7|V$BcXz3%;HH%22&Iplwc{)eZmK$4D_lHBlsPPS3h8 z3ksB^X{w`UOmlkm{G-MCFX{qw)#r(u%;_zstw4d{_;!DD)v_ofb9z4Pyj~jv(;5`) z%~j9jkjx$2VU1{W)!LU9=JYoOWBM)-HdgUEQPZ6M)-7bQv-jR%=BinZE6nLtImU~f zb=$a`tIlqpXV8y5y=tZ%3t?Rp@0&hk(C_c8(l>h?5{hg|KJ}t#&{$ zrF5W}fpV|?5XG{}vK2CyRCD)4K@@l%tEP&h(LmHd8EZXjq3;X1i%@pHkc$&4#TSC( ztT-quP-cU=;tMex{^m{p=B@tb4HsL1o)+6^p?3O0a)^Wc1{svRC4=wyq~1(*^(4WZ z{{6I$`I`1NCt!oh1W5?GXib5!a{4Ho6!7n`mTu=$H-_rzQ_|-2q8nP99xrn;l(c`! zP<@|E2YO#>#@OQo=}t~eFjTJ>0$rRqetgpaXmhwL++6kb+MVX~y6un57Tb)E1(>UL zbpl<3!5o|*TR&X&oK#vgWDz(9=}x@~HdjqMLk5b!tO9CjUL%{#Ro4$rm?gFebYb15 zMqmc72DUTkG0_mH*WI62!%+3UE5V?fjEzTu7X5vF4An26`3(B6pFfFC7$s3OEZI7QP{4t_vGMQF;($27U~tiRX_oZC0`=~ z-%|)qt^;lWiU7qhR^uiS_@i5k0q<>4QUWLi+yRt<%kC1b(71cxtWS1~>~i2RIK% z2V?*;0a<`-zy-iXKn@@ma0$d ze;4Fmg8V(e|Ih?f1?sB-4*(AVHGo>cBS0PCG2l7u2n~W`J?N(a92)^mfM!4opcT*t zXa{rvo&cT#IssjPXTVUNQ-CQ9Q9<$=lnetFXE_REX=Bt!;>&@`#w{bE2~ai(cmtS% zu@Y~Ik0f9vXb419|Kl=^F`_gODhMDB%gCllYN2Ex5^jiyNa22J;}H&46LkTRN8>Ia zMJSLU2`Jp8E`mm3Ah}}sgJc;6@VO6?O(a9|)FluVAO~0qpaE6@mIIan6ahql3_uzn z1&{>D0w@47fCNC`B}X-vHzV`NH{=WQ9yuu=BOfpS3u&M>Qk$sF)E4S26Z3czGW*?ib<< zg)Fugz&cPAjFoDZ#+;M~so_ca6cEleP%}Wx> z?rh&GBwz}8Bwz}8r1?P1KYoh?F&cwNHt7`UI4O}77aypbPO72SQXf(4 zsE?@&CV?2BqfI(cTudMoXZiO?PF(zapKYn5af$@=bDRY9Gw7wGm`?IVfjjuyYO$s5 ze^UeUwSY%}I>2MV!hhg@we!+>^!Elv7B!I_6G+QOZliG72$J|;cW@~XV@P|8bRjvU zEYdmB3Hc;jJ<~R%f?7$fqE=HMP!r`5T6O?LcF~45835rLKuQ1P95{B>rb9X4m<4$2 zWuVxG_@cpG{HsY4P~X3(0QpKl6`&gMz}5hhC|{fYpAzVQHTE+2$E}Q-7@(jdV6?x? z8=(I2TVjCHnn7M6-N+N96^WCVZNX{uA}^^u)Lv>IwLd06-vIUZ4kGVEC&AUNwQ0~R zaO?)hCx96*1BqV57ZVkTfnI{i^#FPSeSm&jgFXML_+L{R15?uawD|ny6A_rlC*pjX z*gt-Y)5K`;q$%Vz(uefO|0ZaRA#K!lY6tZR^{Lfw!e84k`3*K_pztZcYe1i+4kn(o z{5L^j4DrQAf!a2p>UKZ};0fTVt@P)3WVWYxj*6K$t-e~Rad}Ve@2=P{ z81TFd_ehR`n_$#I1$U4#X8Pd5oto| z;5zc1#YvXI4}e?30+&<=TvFk`2M7P=lFt4=T~a-`_MfiEsR>MR%9%Rs(Pg=(%2Cm) zA|h~J4nh%>~W#9zc&;&0*{@egsHxIm0XVvtzmFcOEv zBeh_9Ux;9O)6BR3h&`lC5a_#<67rv;52+5B{~3OOM|(G+7%;n7z(1o8r+6^K<<{E8 z$fRRciT~QelyzyJzzl1_4BsryFp2aPIZ7Hwo+G)WA!HgkLOM_CL^_ZQqz_0M=_7K9 zlt3ClCXq9wHZa2^5}4svr5Na_l>HKDTVnV z{DXW(4wFWZr^rQ8Kk^BQA*GVqkrpJA^bScOjUpMO-^fd37&$>|1Pc9Qxe-Khb>Q~v zfS0cWvA`V=7~O>W!KjN7UJMv@F|cX}vs;WGGJyfS19d5YQ9uUZH{d0x1CfH$2{1cM zr@yCrsr{N`l5vN7T8ar^c8y?mZx?5G6iGyqkYmVk4y}*{|C< zS+Y)}Nsa;@uplzHi!O;NWP&E9fz&N^k_0tKt&-5~rhv_+frMN|;X0-9%HK}HLnE-} zdDIE=8FD>&o;pYEBp)GPBmW@PkgtNL2`|W>6k^H!HgB&OH0VPdd-4bJv_cm3t3o#Qo5BU^cZG}8KXN}5W)yxZV9gng|q!bX&xf*%#>yy&JJJ^ zT=^_6jmZt5+3ppwxI(rchedO;cXQffK(k@7_%t`Be<({t+sGC2S^gq6k4tl6`w74k z_rLw?2SDB+vza9jE>;<68|Y~pttB{wb0T33j zi4YbhErUNZKRv7>U_F_OgYp7t);unlgGfjq=xKuyMFL(x_}?+{hM+``hPUBGa5+3? z0L_CJF7Ri8VtsAe-&_0Vr0{D%3211|3*)mnY;F(@yBzYs>d1h;16WvPNMBnS+8_#M z(E?auJR0Ah$#-Wl16TqpJqTw&!!KKI1TGC*l!p_W%c8mR;Y*MZ>)m5W!@CKuN9XjW7RGGKT? zG-HCHHq8>~0v<5J>**5KXah0tVR3kTHgQ_fAH162h0*wuCuQ(@281#_i?x@}60liZf0h<4f<)OKR z2Jj+;S~NB=uYdry$g;{;#G`p}*?V<>Xv6nv)9k_pKof#6o&ayD8_Wcv3*!Z_1KF$q z>?c@XF^n0?qOnCZSX~D6$`;vHZUE!bL;_}DAlo0b<+4RQfs%_Hlh5a{{h78aaN1i~ zcn@8se=r-!G=L@wW((mD*oGengb6ex1Pz!X8WZR$oXchTvxGum z*I2F`mnDke2|^wAK#-Q@Qy_c4NZ1v_3V^+F;d~lQS*0$=4FW>brqK(?Ob!R!r*%O(S@Xwy95UWqUk zlPg(+0Wy-t@p;0;J#s)&emqgIq#v0U$^}MG^|M^5#pQA7LJ_b{&=+=>oRFr$(hkxl z8c1yL^mKzY;3%As##6edq)Fz|jC5CN*=|*xhqD!VKp{f5h_%)dlJN+Q8|PKs}Aa;|78I3+8bG zguqN(_gGeIfR)5&g4ZyCtO(2HI8m_BGS}8u7bqNvUkH>SU~-ISWY}DPPPkH~mI=X@ z8_WcqgV&GP2-rFPh6Fc&&pvTuN6;!CIbbQ^O-`8VB{EPHD?rC<6LXIuur4NF7|z+4 zzFSoSbgE(5gkf`)_Cfya5R5+)erXf|wl5|p65NBmryEUVc^t#xSU%eLRb`bn!O{=x z0-{Kz0XhG_I|7TXk7IL%%rf{cD@cVF$`Wu{9GZ})bQ=<}I^Y1M5TFIyJ}4l)8DcRv zL2P&yzYd}&+L{8>3Fl*&+dZuNF*_h+uIzFir(r z5Wo_Kf(5*JJtSu%;PL%US34Sx<{}|R&mk@^g(TOFHP_h&w6xD8}^TGBf0(%8Fh~Tb>FB%?BGztgXFH;0I6AsuM z19-C5csXEm5y333>Ngp`a5g7kd=K`41{e4OKd}5a@FHR|B*5Ab!NE*DXphuIJl4?m z{i=Hw24Be4&YsP7siG zxSuvKQynQh4$@&h#sNnW|5b|~z}f@Wn13iUh=m;(o|IwkVX`?>iOd*y0}fm$sfH11 z!?rToJVB67xwiz(kIfx+#OLAIEFtzM6f8T3#{{zDcVd8p*iHZrB5E5!jzIVu1JGsR zd&_N?z}Mw~W+G`qwiJON^@^a@k7IIk^${Wtp8`dyg1r{_C0ZDZC9=)JFxkWEWR`%n zJDgqn%qv>agh0UCyLi!*-T+6$;|O&)Y(E%pLBIzwBSJyy?@u6j0-Mj>1F=^<#Bf=; zwGc1&l7tAzm?;T`NtWjpV*+_nRNU}$$Y1t70UKyz#|q#Hm^3^-GCYhHB;bYf>$SnE zUVeyBe-++`6B6)vHKG!njhNwaU^ppDr$!?N%?8v@>jj8C=KYgeK$Od(x?CN!ZD2>t;yQ`JfBm5^=#TL1zj zo*kXK~}g#B@*~^4o|e` zqvoM7*gd}$;>q?)Vzg=4iu52!x%f4}dcoUtAOwz7tOO3c0z)?AVFw8o@UD`YNZk@3 zcwy}~ML!U`L;xEwLrc)vG}D-Ht^L4Y+5RjBZ5urhi|B$aov~vZT}U$x0AEblD#6%6 zpiy9%ZD9GC)~|$wu(~3LX3F95_(IsiX*XyZ00;(%0DC}{)K;Qd7VD~^QtOOK7lu#iMyo6wRn1G(ILE6kHN-+%M_R~C&Y6&F=cv-%!j1CA}BH@`~ z3pg;p0>=^wuYJbw1mhF%*YRHGbDAN%P&x!c@( z=Pu`-bM8H7uwQe}Yx){8)wMO%let}_&e5GXGoBEJF^qE;;jA>E4e?s+>Z6^whtl9| zoFx2eLf_P25jajJG3xu|;wHKvH`HkC$J2Lt&aRj3z@m-j2T6@k=)P=@I17m;nGVoe zD@vQH8}sBE!#$~>@XG5eNK%K3Grjg_7|7)}}GAsNoJuhdPA zbxpQ!(>l#N3dC2IHk3Gx3*$%??INnnE1IfpT{eV@7{cBsGXqOA^mR@3Ac`_hliK9j zx)WBqSc~#OWnkMbmFDTxa(z<`cbQZc^&kM8?CMV}HMMj33+ z$XaYR9hzza7A@(}Tf@qAwoY27?Cvx{=*VZu9wwGdw)JH00N&l+$MY!5ih{IS>uyIk zN`rQU{<&`>OPusZ^i|zxbs6Zaz%EuT3f&tG?U>n{pJ3G( zz{cd|YN?4=W0V@{VkvTjZLG8fh%nwwKj4ivHM1e4Heu9ddsT(uK7Cn+6DZ1ggXPv6 z!xa2XVE2L^S6GrIOrQ^Gid$J8)#Uxka>LyT%+=19SbAK?>E~T$zT!dRmnKZtCmS?u zZAG3*gYHP79|#tMhASxjd2cK>sW5F&lloFZ+tCds;Vda*s<~X!V9PL-%R((SmRUPQZtK4mNSc}fpp7HOe78C z!E!CEh}`9TObE?ln`)@upl^@`uq||nf$o-w^P^QvlCY2m5}Upoh+rNs^x(lT&{SS% z*YCvxJ=Ma=X=}n!=&Yj;mMVmvakgP$OuiFU7WS0`;5J=uXVA;UROb1;a%b5cL{uQx z34Yb<$@2%?UYC4#+L$GIvOl}9c);+xJ#yXH#)fQ90F2pWtlr^wj{+Cs1YtjFY)FHi zsGUfcQvGmI7+BD^bAQ4Iy&k5Lxz`>*JwB($x1{0EbO(GPeKKt+%AV4?G<}Wn zXn6CG`1E`sS#6S%jkJAYppcLT3v{Wk#Piuzz3C3KeKfe*AsCv5P@VDac&Qp zSVxQF?vg{5wT=SNMWDyWg4tH?K{i&7lj3;8%}JubrPybgZW-7 z=vaG|v)n0dO)$yjHMLEnE1B(lLsNrvO)|M%a@UbnjVTpa-8FQ9NH-?h8z;+^U@#|w zEtcNr$xzGdMq@QkCUdo0&w`7p28L{;+eK|-)wpum7}1f^YmL%;^?|9ho=?~74tziw zX+^wVXfF?F8fhpdBBN6mL0-TlGiuvXdtOTZgwpD!^3Ew)d2wWGJHAVPgC?x*43bVr zv5&5;9StsPGI(60)7rd`PwCQ?OE9F92`5%;J(`-*WiS?Yc|DNX5i9*z7YggHkV_y}$n(1bZU=y; z%Yg2To=fEet}MpN@QjxAdNevl(08dCP%xmoW{}qFa=E(D*9=7Te?cEnGv`Tt@^{I} zL*u3PSP&VXF`s3&^2AQBYgk8}WvPy2cqU4qFOoZ1gu%|`lA$;I>L#X`l+gQSQZh(I zNA44E^9~25`e|H%q#7rgUM2bK+`B)AD0Dr3M}|d1aCU7-1!J$HuIxO8>CKg zL>!zU8w=uxuD8sJ=lagO#B8;gtkT+8GRo3drv{r^PHU14N2ocTFCAA-6V$Aus(h@a zMP~~d=Sf5^-O0q!2XvZ_lG^O-NIVrJIa6%{9!-^<*+~oYLB*9fR=Q+RY#x^=I=*9w zRm_x&`wWca83#}W1K!m0khyRNY(q(=Z5YYSSebm?s&yh)_vtMnr+;GTq(+W0bU^~!SerH`l+MZ%>Br{rjci%%+Mwmt1Q$d?<)Gq`s{)g6 z6xEk2GRZPJOK-ai^nse*ZJ|rm^erW|4Qfa?y&#(>$-5cMMv-K3m@%GgI zmb@@@tY*WbPeh+siwh&ZLgIbkdYI9;hkwDdpp}q6lfA z@mhT8(&lIBFB;M}`O{cyo-E5AZ_s6ww77#>TfyjvTW3oJrvvr{n{r=cKF_$3$(@tq z&+&N~7c0xOOi)*iZz{LkBk*!gu3+LImlL?tn?SxQ>kn*hp6vE`oY)%b>Kf#1ImZ=r z20e1mk=dYzz)n$vMD04Zx;Fhv3J?Awso5lB=MxLdsb~W~`%wn{RN%3laE%^@SX$I- zY%e)Z6OsC>^?7nm(B(5HFcW~MWlx}mq=FMKDXuLkD6h|~lMF*xU#=sDV>Cmw9#7EY zGMF?&NPtVxFjy@`AQZ#3wPQ6pfk95U*O^U_F~_#kQbL#1O8X#MsQp zMuKL2TM`=xI4=^-4#R$Qmnaw?w&gJd>3)wHu`D)-8ZvkYIo-$8ncxn=5FP@MPS;R= zz#VjZ4dZn~)IL%4(ihB;^Hl|Gxl9kHZF`uEqpJkt17=>HSZdVU=sjtq{KT@R6dE5# zYPViCVS$%5jjNN6@YBttRQiWudP4XIj^>y^Al0QBe5|8p?+_oR^h7y(LTP<=&G8E{rhqu203+-krVF!WqSHXMU<%8t zD3`{LoJ=PQS`cTV%iz(LK~hs^{9QC&wOO7@w8aNQ^qW}i$Z`nfGlol#q-!CDsjjAB zi=;`$tdpxtn`%Z?a(UP|PIfr9R5~qimSbPBRe8nFM-Lv%5V`e@=)e3QeS@CoLq`|3 zfAi&`8U6P&Eg#I^6|MFK-R`v>Old)$cuHhh(+y-45M>nK~8DieyEMl<5y8}MMQ&&bLNj7f6-WU?- zH;Q8%oEC=+9`}{Qx*eSyHctA@uFSz5p57|m5H>fblUX;!Fq&cLLLKqaDB07RCEZY` z=UepPks&JE+K{{|wsgSOPb@2?Lpmytt}XqX?$h#<>Z+-#Jv|!=fU#QivHSU)6#=&CbhOw-prA9-%nKc?&91`kZ z+`YJKp7Fs%xqIIUzHETN_`~IemS6P7i}BKIt;7q&eqf3wi8L6i+cP0)9c3c4n*-t( z;}d#AzkYjDK^~6+g-~85E-O^#`%7P)DCB|u3dqwBM7~%;4bRBk3 z^E}^NJopXG1Y?Znr|8{k67;;sO>bfw@&d57Do!!R5U=M4Zu<+o-yP7LXE3(TkJK{| z9q3en85GR*nw2TwNgC6lV+R!0jEiwP2!=E0cUpO6Za6K>x8s<%!BT!yNxaT=F-Y|I zomJ5&sjZ4eikjO{Uy#)e3A`LVrIF$>f{bX*j2O)hPlnPI#ClTfBD4@hHRgRz@NLZy z&jb~aS(VC)TCg$Ilj#DJn&kJ9c0#J(xx6rKi)=vkc5-Wx4-OgOpTBxem5oc4iuhgAN4J%pBp9e17BG zZZ(cY91LzkN)t1rM>E*cGHP*>^2~4JKryg_8ls-|IxR_|`>0%^93MF=Q$%CA){!7x zOduoa*O_iVkW&|}F{q_acjR%rMJEZuSxMrl)n)>%AXgAIhiy<^9>D#~0Kp2+nRTAz#uy<%cPdYD!+#$VgmrEKN>v1F~canz3krJj4 zDKWlkQ3Ti}LWH-Af;m15(yp-$CE(_3tDBfqb(ScMqhqqOMwLvg2hmbqj|l2DB*p2= z%11VhE|ETw`HGA?Y$`p*s-mezy2I6dG!r(iH4HBt_93CxMB8X}IZiyf7cti}{3jae zi@q)p8G{9qn@S*C@p?)cW#L(6FiIMvs~z-roSgQ!EQfxj#TG5Nx^Z^NH*m0@qfx$J0a z$)Ome`5-QZsierQV(V(9wH=rn)>4ROafmiKO-DvakG3OX-d)5toXxNc2EMMovA#D+ zo05Ff=B_7MxO<{59@MK>=rXCZJQ_^JnOk|ygsOT_|HTRPcONfDY9RlhJ@|h2fl;;24yPt5C42TRY3H9n_hdel=-x>~o) zn^LWzk6NTJk~+VTI%{}ZuUYeHgN5lk^f%oTT1?xry20lyp6^M9j{cOV?h6T0=>|;m zq1l${`h};Y@?2?%X{#Z9gHCGJu%qkg#th?lle9T)<)>JA-#W+?qgwy`qW(LGdo3$> zqWM{TO+(}47fA-UXOO@=O?w4N$!QO37p3Q&?&md>dAcyGs;*MLQ?@r@p5$VhWM+VX z%=74Ani|uvs*biZ@bF9*vj7|FM*)^KG}_{(r;v(8W7+P(ZAIp%wuLksSUHn>OURtZ zG9?D*pl>|A3G=qbhge1<w(`2Tv; z-BlRGEEcpaa|0P<>7$`TT$Nm1UNah}Jhc@XFV1ypwyoD>ZXMZu8z)Ua&NP!=(&jv^ zSGITWYk9`o+?kBZxT1TAZK$N1gTewOzpzo;c0YiLYGW&W8w-cne^53a2WPh~7UJXRdaG>odR8rf1r8Uqh78Li#z zNrTz42F-@z!33g0+G01hYgchM8TT*d^-zbiR;fIpd>)Up)8s;T`l5W=KB$EVw7%B( zxvuj9)!seR$4W6N&ddCUn#ku6Usd(!%yWstubBJANy&+P>pfAO?}-A>GFTX!u_bgg z{2>8)rA>l%6y*N(^rl!4Kt!&&J?r!lFP4#ph z=+UYgb+TMJL%H_A{%d}l5ES+*v$KJHa#j!T79QB8y%2s z86tA+G(YkU&dL!sVybHzi398s_cjQ9D`j)fw6)S&ksal1Idkj{Qf6$<1S@%<-ndXp zKg%|rx|4qDwUqd|6}w*|4e6cz3imkslP=Ooz2{N2%_j9wlggC)6EznP-Bn8vK*i2T zKSrd_soC;|ZJK1Jvu*=@+lF&CDN#s{!x=$Cid!?Ul3w?ja8Y9dj#}x-EJc_m72H8P zcq8$))wX)NyK~@j4!tWoxxYa7_?SJG37x2q=Qil(D;?^mKUw>ygdOd5S<+V;=NgR( zw-XxbYHOqoX42kR?Ql=DZM=5Sy*YFcQq-COIT5EOyZb+N_h&Vf)HdMw*jXuxrL>2Q zzLJX!x{1;kLdpR(+k2=%oh1nF9JeUq1h2llBAwW-F;DBpS(WqE8ihN4v=eX!JIU!xKI`Z#Xw`R!qMVVa z?$xKOedIJ1WWP@c`$(3-omx^D2@mR;C8vx_AK&@2xq2|i|ENi6Y4yo+cBb4gwn{lC ztshIbrbs7wX=tH#RAu?7vGj;SMQrw%?;PHZo^WEJWQ#JBM(jc-)R&P<%F|D$lM*gN zf46J63~|(!;$Bf~hHT5kvgi}b-S2sbIpEZtwslO+xI>iXsp3?OVj)hQk|64+t-mHs zAH>$xOV7m8&ve{L99NK1u4r39Y2O|%s%R&Htr~T@nVVL$8bq70Txcg;Pk%wH+2xrW zi!Ska3G^7epU6tXiTlc+I5$_+<& z^xPW<32t1zZi~|}YE)XT8CCQ?(TwFog@r?Jumk%fJGE*IyI{M4&eepc-eGB{on9&I z?0-iTcXo2DabyPiBgH6|vTQxhSbUKm$*2><*-?$jQAw7HZl#7&;UoJ^F)T(+S$oWpBf}#v4+S1wOYN4B4(5HX@NUzYqNbn@xsO8Gk{Uf1XhAI~H z)#FJZo$2-Dk?B2)Ey-6dp8nc-F@}^`b{nJ&UEbZIB`0?64;Gw`2>kpgUXz^{6zB)B z^$gEuXF7FJnw6-db$(2Ee$13Es+J4Jm9c}4OYH@mKjKzIL+vQ4(UY<_DJji(Rvr-P z6)70ly-)G@$BqAt1+uQDUBa}J+p?bzXAt_XKpDZaQ!UjB`$sdz4+OeeWJ48noP&%C ztvInNw92@sGyP1nFkLzpn|4EYDpM^Q1!`=Lwe*ami#)naTBc5ZOJ;^Yyc3EJ;<+}+djO&r}}@~kG)moViFn{q?Fp;j6yaf9y<7{Ee!95L(0utYFErYnDVrHX`& zFB(j8rd&31T!v+;Xq+hu(e$u5;Tdu11I#|hij<64^FyBJqs@CXZZ@lQ)-)o#c~{^O zl6kM%nMNF$osWn(a^|{)x2Z<-Bh~?-!4a(HfdLYgS`r#iGN`y`ai1a1VWj0F%%LJD zDIHH_S}L-G>ZItN^q4DkBn}-)MmAMcl+(3Z+z^>GUC_&NX~&Ltq-!Dy7mpJokOkS{^8*ctUpUw}(ke{}N)wmXg*439)EJ&M!w zYhxvvGDb#S>td0540o0HFn#qiHOSS>?C*O`gQNo#;A$MegNp9ji%b={M%kgblZ!{L zqpVYmo?Wch^@TE7e%5S|(qA zJptJj&9-Iqry*_ExNmEhK?llo!h|OmnRWS4%@T$DR5LQ~umJ32Hj#_eREeJa1ed@? z@)=V?mf3!8(@A@hSwMEtWm9pZxxF%K&(J@@30kxylX}2zgVRxp(n0j z$KiH+9CujVoYWSf+!|lR)Y!jFMyBoU&>{a~`a&yB#2xC_?BBP;^N%SV_bJ-`8629M zf69i4eJKDd+5;%VvoRH(MQH#(Vpw;l!}B>*Y??$o5zUYMGmfS{BhpM&=P(^-glCQD~lrW0po7dmY9#DO3lLGn*d zB%gI6jH~4cKa!2`mTZLE9w*;If$lrJ?J6jrDUZ)^g?Qr9d7wIoAoM4cjB%W&?R}Rq z@9SILXxa_i&-veY(Bfyj0Bd{zn7H;2{qQ^)08q3OTk%>;a}ZdLz!@aJoQuG!Ai!C~ zoP!eF`5;2XXN>!GirSi2NXYHfnG(y zPu_`SXxgzq-Dyi9cj}%n9q595Z*v!92s;6l0_c4h%H zgA|PKjVX$GXJeuBC}QRq<1DK=V5<&5b zVhJwk>(>+^fW@m{+!Y?E2I^;bgJ%ws6{XRS;r`It)GzFggjW!84DLGG^jMFOqELBT zZ}1el=wCgNXIU=*=mB<_8)dgXHn~uOi+}{5U{q!SytPhPUL8qXx z+1Rl<`47ETIpPx3B(X*n;5qV=4n5aedh{{Yxv* z{Kp2M1mGC`x`FUOk5a#Q5Ipaotix~@5wUGBBA!6tB{=AUbV682Q)M7D0gM~BekJlm#W|h344&^PZlcHJBl-`S~;Euc{CDl%f3IpqVCTd_j_Bejn6^mdc6aIHw3v4Y8L>vKf153gh-vJolU;GTk08UwXlcHt@}mFz*_X*i{*Ul6%s zHCnbBo;9nPx7c+6kvGYDbmT|qLo51F$GyjHS;GVs3oqnXWL&ux<>LwRIvfU?U9b*K zdj)}4;a>YA;Q)S>_8){(Ccv9;if-`RaOhF?@H%EI`z}D_HF6dM{bgttUOyJE{?U4- ziNK@`yYwOS)iQXtKg8V0?f~Epq09v3)!Ee>m?ZW&06e*XJkROK3ux*P_Qi)W`Y%cA zHIb|2XOw%Pv=>G#w)`jd$Bj$@XT)&Ca#`$#O$H-*7~TQwiA~G^yoZik#x87TcCsza zm>J(m7d7NU=mN4Qpd+HmRk&+#*HQQS&5Xb8&F4IV7CwR4PZ9gT7W9kK=Sl>|T6qU@ z3{IK*%4nTIrVCq`ne4?a%sy@%yJjoXm8?aA^7N)h87aT{QO3iI(LC%Wm`L8ov6cmK zYS=D_kp{%6FURAzM;tj!Lvgf69GBR#88nO3Vqss>C10jwUP~_M=_XDuou&X^-vZ<^ zU8j*Ap8^palz=~TT3gsD+d0vZYeq)hpNR-E*+204G$z#hUd#jlsX)o{ZU(B`u% zzGP{(23vSvX`%-E6r{@sDYvM(giiw zSNc(nBHO@simwfvP<(CROU2g)K2dya;C;o{1`aE}HgHJswSgKn_r3(6mj;x(D6}=IQHv!NqQ9syTm1<+rW&phch5@9_R(#1)S0xU> zu7I*zzTF^bC4fl^@JO3QkO4FBgPa?H!KHHolo0ph2hYR*6kZ$t8tIgfMgfrR>;Dgq z2+%^!myReA0CsYcKCAfm2vDcAOOGplfc*dPtySVyAg==c@GVyA`ZpM_Bvhk_=55Ae z0fP{${Jh-uqf9EnQY>&b#BoMk*5|^;PD;?N07f8}n->{>;gNKK$6RRp*~!!(gyb`S z0Hg6M9upx8Kgjq#&rLRhxiuPJ;khj13yN=t;+wDd?pHuS0hs`&C&G>AjBj4%w!+!o zd<*V5gqyj=aJE0363&9V^}AmEh(7$v?$5~l=Dk{lnf7~JX@+6&ueTGiflbaNGmfDl)^oMv5j_tR8OujB145C{P z5?l|n7Rne#>oKSRdg$i<8V|bM4pIvVTaR;gIB=~7G~jbYE9}klEiQJh5j%aek_R{mxbr>}^py4az9| zBc8ji^eX71XY{8$=+EaEQGFgPir_i$B9^Kg&w2EF@{#ZaI{JgQ;!n51D+<&fQ#h4h zZ9gA4g$fVRM5W%(=}igacU6iMk^?PA(1vqqPqT9($$3)Rk^?PxwJ&M9L|(GZRv+sl z@^9`*`Ak zhbDyXq!+>Gv9C=^J{HHtu@@&Z$+S6+%*Sm9ws|^TEE;hue17w)ZRapSW665yB}y(e zf3xiz8u*y>0NwTywma<1+n(bJNVBw zD0Xcf=%T0bbgNlstfgd?#8JeYmyDFGmAYz_a;3X8+rAX;BkP+DAfA>})+m>@40`?O8nxi z4dhg)2gdxL=B@XD5so8!=~(mDuHaq$v}84CGTY840HEP}3}iva=b^%E$2Yg#^Gg zI*F_kB+%6xK*2taIAs=6@}$nH-VPOE<`-l0DmvNJa*e%2A+C+#uTZW;2HQbV?lFUg zs^=QTm==ey8u);+%Mv*+iK0u*$T=rdL$z}=pI{yl4xy_1TUc8$nci|u`c0>QNRhTY z0N(gx@RZ3wX>nwc?QyKdWb6fs%6%MY|72{{agiY84#`+NaS%-<14$=?kBY;DiNmr? z#*Pz*meIL-+Fy-C5fj?Z{z7S1;)y6QcyZeIjC+_R!ecDY!)yY%+R_w4+Z}kbHlE#j!(iZlF-Y2! z^ag9^O!jHRF$2&2!s;^neS$z{r#PKKeaz68nVg<n^k|A>k!NpTJ)LnEhZ`A z!c~;QQuQe4r%T2vZJT=fpe^KT@j!;Z93Sqtlgz2OTSYFlzDL6E!GfO6-8v*HZ%7m| z+oln{zrB5ECjYb^h798$Fpj%hYl(t#xnE13F1{xU#HC-$pNsF|PGlc24x12VjpH9M z!YI-WOW{O0PJ~q?Txp4+H;3qM{+r>Zq1EuaVVZF|{YWOi zg;w0Mq!n7Mh%U&q6DPIPjRyX=TQA5b@Hd@nRb+T)0I6uL%0KhWausQ9eUpUWRNCFK zb-60MT*Xc^79)Pvx#gf+BEq>lWi6Z@Ai#59M=Ox~aH za>6SKEeWqCyq55K!b;P0^9=J$^8@Bt=Go>sW=lj=?@th8%GZNH60|DpE3e=;3coFk zK*L&WdjjpL2nYpjG-B~x1vv)4D@|DK)Rl_slcEvVVrL*-MEp!-cWbetaD0g1EClmX zVINGO5ApUnX4t7rKp%deU^MY+*r|Lu4+hd+Q9z3VURA(r3V0oWU1y@y-zv{t(BzZeoAY6{O|n6^lD4bErS&o>O4rgUchlOtec5%{wXk`B zbK`6@*=8meG{YY+6Vt_1Z?4K}9Jxh>K}X0N@w+^cfG1QKl=7In zN4#2>nmw|*c4Rg*-Li`}1!0x*B0axI*GP((#!YTduBXV86LJJxxo(HAs3_i~MfMinfhxB0wJpI}697-qW^yx^0h6VMvaDO6oW$FjXl^$WG(IfELQ~qswXG zNHyN`jB_|46E}YX{ST?9?Y>;-jY2JPVigW|aVxQuKG~aOGt`w06@#>hCoR{N7W5`R zGXmGEpg3AGj!qq_#+|2K;;C81BRNJ+2m#XUVtcWXR;4UnJqQV;jOor#}#{vmUqp1{Z}SO4j6CX{YS zo@RINc?qS5{5d(1U@+GajuiMDzJhR}BUBXfI|7kl0fsb`>n-xf^S_z!Jo#Pdg|**H zoqlQ}VS2$5*yS;&i29?`7u?L6$5T?>1w{q84_@GKdy5JkKIrE-!XbaI!;>4pt>J*j zQ&?0K&u=hG9v9=>Pt?CJ|5rz*zA+`W&{gCLhg^P#w-6K2=Xd)Zp>QzAQQ-DQY&}h3 zkKY&1|2$?5QU7$KI9ph=ChQ63`hC8TqcD<-t4Gt$ZAwYSIt~?j{BB1mCxZ756-FGv!k`-~ z$LsSI6u81crH)^ePVV^V)$YQnHE#5i7q&(4re4g2px5sRy1cm#S8mAVD{uuPK9?t+ z|8-0+q8`wcR4w#c6YzzxBt2nAfhQbs_zJPUgXlH9M<|fv4n_(KycplBMZE|e%9k%u z?>zHoV+eXGZ&>D}=>kmZ& z0Y}L1$DkAzV8XaDVIpBqA^IRUQsBkRzNWnYCtsX#38zv6xvt!BZb87|VK&_3i-d~s zGI+op!BzTTP9TV<;`!^Sg6|OTA6K{rI4Rfd^LjDPKJ;)ngxMWHn}RtWGzZoCg28xx zvr z81lM&0Y@$_k~@5EjA5?Jj|Xzxxdq{%tFXL~Jfh5!{A)ium5Jza2f{@_bA{d_j~jHvcrY92QJ0{qC*QE5w|~rR=IseK@7xArHZR> zRE$8QT%kx|BnOBk6vEQS*u$4o;Bt5{3Ec(3aK!HoCGa27qZ;y(#TdKh8Us`XQ^8s> zD7F(tO~sfG3k<${i^7tqIh_AQ*l;*MGi-`7?q8oJLA6L_!W|jXC3M2(y34h{aWpU zwg1>m)kg-%!*QuM_@MS9LmdAl?JlxEN=BBqgETV3O24G%AJVu%)2B#Vw!2bHikcKY z2_VDl2z5N1)di~3eVMypw7-(jZy)P6jR7;qp#7T}ChG|X?8zR6yU-ni|Eflq`NsiU zn^EQW8Iy8X-+@)^au0L`^IZNsZ%#)SHoVW7xKLALWo^Bq5I0JlFimuU!C?-WrXq7y z;u5<5F*Ph%zQ?>r7NuA6{HO6eo#f5`G#(fowp{C89k4SL&mYN4C+uhOQYBA5)1>pe zI+nm(`B5eEi}=Xv@zO-<`ci53nHA?pt?Tt%=+B>USTpIVJ~Lp|R&nuovs<5V7+h68aX_Qe zG0Ha_?q|tG7)FEDbByZyxSybW!vUr}mn!V%(qsc13~W8pN5YX2ENm@LUcn?)y9magv9OBdH-(G7s|U~jj=wk)>6?956lTL3hKSP{O|^a} zqm|?qrW&FWl&ZT_O^BeWW_L2oxUw+@{^6^S&APnm^+$EuXI*V|#NDDWMyF?g6O=>Hv{Xi!lHD*Q&Z5c9JYY11v zwzAA+?n_)lsnvX$deo@Bsg>XDa63=_3pMCq&tZnB9i?d>2;dN zJ8oy}+&Z3g9#07498XezzC*htMt-2+cB1u#@g%TQHTWbUQHGPL4-{w*N%A{`ZYPJ3 zJYxXEGHa5?r@k{td)6pFUwu2V2#JgEAd3f|uhzDxh)6(^t< zx}g`YPtxuZWgJ{(M)Pk>)}9j18KTM!-6a0vLbN-3bbZpG?R`1%UOd*r z5~G}SX=kfs9AIVYG)ni0FZ@IT!x@-j+d7SrVugFHoC!YD6EhbIN=v6Id9@ow`Mt%= zR6%pDib<>LF_o~pW@)A>ofK`jt|V!H7iAoVC^-(PB8QY5?=99$ML$g?N`m%-SQ&={ zNLMP&+dPpW%z^U2D3y})-F2F&$5B>{mbj2956W(1N(VO$D6MJ8iys{7+F!YdS62T|#C2AQ6N~6E9GQq@TiYt>>C9h6i zlf1?Pyj)QsLzi<}04!&oVl%tE(BSp1Dg+8srZ7`U>c~_=Hr5%TX%%auS4=5k4d{U)5jMRVxuPU!sR9;IPG2u5c%ytIz3Ia+6 zKF2VxFc9sAu91}|esQfzKEl9oG46msDulDj)r@h{nq=U)xhk?mMb@csTf(wSMV?dP zvj8iT!Qgyh8PvvYty0jvC5u;UalPv7+p-piEeid;nvs)S=tt;NS@8i4iR0fj3J&C2 zW1*9IV#*k3f<#cUUas7!OauSwV7OF2b{3cr*ODZN&HsqxOr#U;;6$)rb?MEB)_LqjSwzT~kr0X1D_{!1zfsmqe07HJH#bp6UnwpT&)h;;1*O%B84|^WT;!JZECqF0 zHDqDu3sdv(*#^naZ^C661$RBAOA%qJMx}~Y>oKO*w;uRBR^Io?y9ZohU!D@{u1I`^USt9T24qy;@G8eA-kt%9J*RjmlK`V9$&cl6GI&Kg9&? z&-+gWO|v)bSr;Wg+w$Tj!Mg0 z{zjtBgDq)2_sKn@k?F?HgQcct-g$VwAn$0N{W_!?kM7^0`(#Ekv#tx%2x*{6yXWfR zcR17fS5C}eM*I2T+GKQm&|8E=|7Y#TFq;K5_{NvKf52q7BS{~QS zQJJ^Ct>oJ9!Hv~FZ2$U;d-6#4f#a8^C_Hul*hmx9cFME+L(hB+sgL5%Btrb-KBPru zQY`;CR%Db)G(EwH@w~Fce%Q2ZODCwl2HShLbUwX&VP}7NL>v)mf4)+JKx;&FGfo*^QqroJa~y} zd6SsZ`Vzr^=a-0H&mT{e;{1~aZUe3TfK}x9b)bK>F{VRI3M^SspTK(96HnmoKXnU& znq;=lVcx)9o(D{06#KgmSZ((z@;)n&=Qc}HtTma(9%CXYNxK!tYxxt-ZH#s$BN}-+ zUod?<=c6r;pf&S^)D!Dgee^h^U|Ug^^5zP}qO8v_*5|T5Dss~KD2enTKNrVE z^J-K39$U$tuT-t-s2=Jl@{RK)LZ+9h`@a*-gH-PE5@GdHO0-BUSUF+zPDT$hj2gu$ z4@Z$x&SfO+iXCh8#4B%{eq<@C?<}Uidj8b+S3usIqIxVRGd^HiuP|v>cs>8Eb54}} zAv$B06|;!mFY5QsT=P^HExk{)FJHXlx=tHw;!{$~S7K|Mhx@kC^j=Zz!MOpQjLuU> zX^E$`IqKc!s9|8FN%5-@`HiSrKZb_E^)>N3U-}V`+z2dF#a+ha8|h*w(SClVici@IN@W z;wxmfZgf7o0*b*QP&62NAOZ-#g=ZZImvS z#K2xXM~fuECiRnqSf<~F&LlH>hH7mJ`0oO@2Nn+> zTvNVtW|4rDRVIR%E+Ge#t-ZzcCAnr8VRm}9H4|6=ta<%EVyA_T{KKn{?D=@zg66Ln z?cOWjKK#}rr+z$%>vr|+#3Tr4nfus#LPY}UYf@cO@iUClPb&VR^OmvxtDNu#>2Lm? z^h2AA;QE?xl0Nt?hU*R23$7yMc@kI3gAL;UEuD7>kQWkE*~z;#Y(cC zWsON{eAPsK;la!vr_C?K&o-Vh|7bpEzF@v+zGS{^{>l8ad9L-m`K)=nevAH0{Q3B4 z`UfpDE&n#{zsPdkyfprrG~f7z=}Xfb<6PrBqnPSFsuK}Dj zyP|Sc;h0;-Y2DL7yoDa*16}5EqWaOqk}kN*GffY$MFD5xxjTLF%9+kg%UggCT9k`P z_;^5<9~kg@%7-kjGR!m;Gf&0zW0ZU62`3*aCF(0lzez1boAM2JoQy5+FWL0QjbP2CIC| zU_Kx|d4QIzgWC*;J3V!K*fBu6n?7#+xcR-Mo2#_X|G4FYi_14}e3Fs+iNpRGrIswB zzBnn`Y9cOTNfZtJzlzr5lUU>hmf6O>s(`luOz)%gH_e}O+e|0nyKct4ptdVU%GIJ7 z9Am1U&y}DeWm`SFv-!!3j264<{8z4g_VggBXmGrsw(ufrj1nM%$YW_go`h$;#`a)K z&$9j5>8taHvA}y|Bn#w=+f!C?*b7=)y>>H~fCe?jLT*0a_|oUZQt`GSRf`_NR&~QA zNzW#bS+rW+YwqhezdQHZTOTiHv`e z&rkiZ;l|BTh&}zo>HX&(-G~BSIREa0i+;ZJc(q++$BCj#h40yrG>nfV^UZEy)rErG zO#Nc(66;dyGV5~d3hPSiD(hVZ! zgVqGTWp3vzLaQ}BR+yM9TnWn}?`ZCl?$$y>(N288lu|bTl-rmmu6bOQnX*Im< zv5W1{7+@7gwa8K03yT#)yGS8ZFqPZ*p#Na3x~t9=4GV7X>%ovJE*0(;W7USC8nU~! z*|8n~TVAE3%fn5hV70qVIOK#8Z3;*e(?Q$_g|Z%M*j^*wrw2m?#7o!-_5Q z3zWdWnK`9SOXJ?C9Mq+_^uJN3!`)V^7iU0o;}lH&?^-kdsFjcQm83RK#Sv!MShch{fGW&c{P`qMpU|5mxdKbO1v&*ch#EqBA->r4Mmxj2;g zQ+xN^E>~d=Sm3e0^uy4=#c%&sx$Zxg`}FN{m1)ZIQGYF0N;R$gTjhHGT<$Zs%e~cJ z^_6wjzCV}C|FzsBpU*wEEb-@k{L`_W$`B&m0Gk@BQ!;ITVqB-%F(D2__A}aF%9EIRBd$}x5 z5@xHgH{#F+MMD4&ad;H&N(%uifdWfv7KiAR)+j=(NouweH;XTfvGMO3rqfHx(p-uy6i@@*5jk&8NYdj?E48YK zIfV8npM%*?67u8t$E%ZDsqo+b=cpi2(02cYabsp3dMF=-DNcs_(}3N^xDFVHygkA2 z3E#kn;O72wBBTTHMuD{g`n9P4`2NEv91)dU)CwxHMBn;?#h-EgsI|ksD@2Wo`#)>G zJq5cE(&<8e0$2(duepu$(=wx9HyzQ>vG5>GEf^eaG)TACagzYZN+IQr=;3ACymKsn zZo$Soa_0YyMc{S)WpnuHKi{3@{|kxVQES_)u$+qfKi};3=3pbF-K04F?Co+Dq!_0V zqqklNB!7*6U0UD7{(X#RP_To#MszXT)cJAco+K_J<((SgX*CZ@GjXYfPD{4T>h}2CGG< zfi#;)BE;Jagxe5tCm{O&QTHZrQC0o__&GCoU}hLDGnZk25yoM35JzWZ7b#}|jg*uC z&5XpvgwzxbmyAjgl@yoAM{x^NbIS!y%`JCx!zHyvGc&c#HgeBgrvLlQg>gXYsptEA zzyE%(*V*nl=bn4c=Wge`&u76id%{nUjXeIO#p^iO;(qnV?B8V_M)l&0FWOwTKENS_ zjXhC5`?%IaXhY!}fX$;6jS*&QGH9V(W8(JE03YzV%uSp}z_K}y%L)Svvl$iKuA_Y_FN{^X0n~FYp`1hTE>2YfC8^2oraeCl)J2r4g z=3i>J!EgLp{dOPt-LAH@-S}6&2q1;{8?XH$T=#7E&%X$df75UFU+8*Vy9RIAM0QW= zUIY$MY=fC&O>Wm$g76_@9va!|{~#J!EjOXGgGY$8N)4 zg4W#rFwg)Vt0-{=b~c6-7jv6GlfiDa@R3aZ2KYePNA2h5OfA5tjD1!qHcCUz1$&yp zJ{QfC>$4=5H)S8ebkz~f+X6GK*SI0Q#U7^inDM>M0yA*es5!eJPlI>N1l|n|vo_bL zZMh&%>vPN=-Nyp6`_`zHw;)eDaLh8?aekK`(cXxWWcI`B`&k< z8v;Is@w9hgr2S)vfoXy#-6Czr!NxB$yI?-MUxx_5e`sP^Ku5>oWDg^UkW2XAF|GU-+Fby@5PlK-+whCw z|1Su&1J~IJzYBgh{2q8FSI=bV_rdRnKLCFa{t!I)zy69fWJqnxT$Y)cqJTPW8E24& zdJKrHiHZ@FhvD_`9?!`1r~dv9qqCH zxXI&iz;0|QJa!(z{Pv&O&Yp$yX2UZz4|Cz?!T*WfY}dg5Xdn0z&aE3X8}Q#5>}}MC zWIhC8>KP5kmPhV*WHo%A8yof%)SC)F4Za-y4fyHsOu0ubmJM!~zc<|f#xwmMuDJ<* zGd%banVoeHQ>yi!W6x*!?>xUBq0PEymud9)ch7I#E&MyTs8;Z&@!J&O(wFg9ZqsCR z&+}Hjj^jVNT@@(%|KpZzz;)Se+X!EG>$V{M?{43J^E32UZsVVQhQ39A8+?jj3>2S^ zx*ro8BzqC>&xWgn6M;OW->%#?7|zGbgmZXyIJHB32b?z7Ox4%>>DMO&G=qK~wKQ5kI4 zlaBpiFR^gYfC8qy<&0KnqSV?=B(h+{Af~(Jh(-ui>S$>H;nqm&v*5;sNsgCb`Dqx| zkj$pEpjG+?G=*u)!r?fE)#(5wFXKC+@}N9esOg8y1%l928Kenp&MZzn2MxvjyU|s$ zbh4r#jHp>xRWl=f~LrZ&E z!iL0_9AiK*dc9Tb_4{J4w~4*p?&>w!k<*_>-VOR7)IQWx64Tkx?AQCs>Z-y z17%}aZ;#=MbQ(9`|Ec{cDXhak7a&R`GZDF-nHqMSMiwDLcV81bc(&kKcJ!U=h@0X6 z8?M9pdj$QBVf|f*3%UCHW#x9nS>&qN;nMWS7!_u_I!sPf{zPKXUv8rRQwwbDfgsm6XVG}j3E|1zKkBnvL08vdQ9fE|5EJmk79?XvnYjpg~;`Y&lNj-M~qI1 z{k|YZQ(4=rzXwzwN zptloQjNVQ{Z{t{R|0VWz1|kr5CW{hD1&dP1EEc7a*@#>(;~XSF)wyCcPmJEg_~Te# z*&`gs`Z_fy%SGC0tXEm&b+N1EEJ`77h+Un|DzmPZiSOBDL5TBHL&5pr0(}9~WX{aJ z@vo@5l=};2rJ-M8?u!uD!)GfG-l|OMNmU zb;yw6&=#K89X4BJUpBwb2+fc1BLVz;wN;@W1}Wz}VS$wbH3=R&r9tbE^cBOcuqBc~YsE=I-n80tD@tax1FinwtBti?IxsvT?+F=%SMJy}dl5bKq)s3&<% zJenj%Wnwg0jHZauR56-nM}y9P|vIbdVo>=~-7*%2f^aC={9zb9S{ulc|DcwWT?5__RTBWg*7lhG;+Xhg-pKWS@!AQw4_qz&sn3`Q?t45f!b2{Ky9sipx&>2 zpvbl*y-g<3!vLl^dzhd3Y$))G$CQHldV1CN!Sdstp|?gkR6C1f4z%EEyy00 z`3d=eMW2&{EV@7rvFJNe{f*pyjX)vX(yTbLWCA(BvLnc0L^P9J$m&g(%pk{E;}K-7 zSaw2;j)>96Vsx&@B37j;D}si7O*Up_kR{v5QL*g9yl3pAs5~YrAAni`3EHCj$hsbxjNBK|Bf6WcWYJEt zo|SDUAF<0_BWtp*QRrvii~;T2LH3E6$5<3W_Os|0vH}t0bWe(ztHfxD7%dm0cf@D| z21HBAr&(2W-wpDanE7tj^JL%eWEmp*Dfh9K-{@n{kkU`c=UI9}KPIPH^bz-kmQVJ# zr<3q>U+xY6-5sHa7tu!&438SVMk-DA8@_hTTqTf+71@W{*Ej}}-$4&B&XGv7#rie+g=IgaJE7{pG zti@TZ#TeG&o31>u-h$eCCllvW63glxPJ99>R_{Yso>=db+InBGbK_XO)2zKXR_}}} zPpo&ow%+$bjP8*;^7P!nxfi4FNy%@CP07(NFZgql{rR!RVEfSwO;a+5Wp+p6NMa5- z+7pS-SWYh_zHZr(%Nw=38*k$GrK=#{Q@}&n$aW1Zl~4o3%SYJ_-o~{W!K7?5e8{t( zP1euz{Vm717&4ER3Mk>Fj~S)%O9DPO=L4t`D+9LHVmdR=z_X<_gMHZgjX_30M6^4# zs0+A8yAC>Kp>j~k80(D zi}A$kxawfpGyq;vWU*oz#NJg3k57X74p?+~E)NEh z2lve@j1p3}x9nwMyWJU*?1w|KSD&27MBcD-RwvySQOhHHRian+MM0BfRq%L{mpGnq zTo<0Rq6`(X$5~{N+L9~W28nN&FQ?BmD zKwS+;Lx?}xDyk2+rirThcT~E(E8HXWivA0krzAk z(571KFuw|E!EaskG(nt2Ydw18h&>vQ772J1!sk&&0`c}Fnx<3Ikk&>fygoD;gua|C zyWCuWIHg}lW-6V3G*p{A$oN>^OEpu8?^G28B^>!v3CW^UVYV0)l4M56Xi#bbSVv>+`*LSQ4kd*0+c~3WE16G^75Up5yflVk@oq1 z)cPiOa8KfEBtpN5Q~Gp;%99a;6_dcL0CQ``E`ItYGa@|Xd9iN*%{@@@sT4Px%nzXQJ)tmdXEePF`K4|{@`rbA ztR?>x~kN^DVDN-Hx}sAnOw) zY1HkAl90qkOOVM5pWq`D@@&#q7svBqzQQ#HAD}So@)J@O`{(fL{*dK*mG%yrK-udx zm3l>_K*Ss3uFI*^k1uEkVZjCZ&wW)dDg%VXH`*CK_f_{&5~w5U??WtAOTcC>{NOX% z?gu?bD+K5{`c^O1sNR#9ed*uV18&NP@VGO9~y_eme9{*1VWP^%jNRt$o5Pe(HmienDxF z>O7@4zzcs%u=!~xO6Y9K?;4sw7f9$@3EhRoxrCgSr2VF*9ehW2yd@!h^W_pfg~D_H1Xe0rZOWc4gBrLt!v27T%~P%3t*;+m zlM*T=yBtPp-R)$u*~EE3w)#NU5*DvJfL&DQOd2Bw@X9TdUhV-c2W5Q??|e0Ip<{Wi<7{YNzQdz<+Lr1aeyUfM2BG(m zN6o|3`1U@f9O>}ml9Kj+?FUidAgNKR<~KcxFW+L{sp=(x%0MCIi$^uDMT`qn#=}4o z)3afDidTsmHn?3$!e&1?)39MMCM5;nOS99$@o%;x!kGjjhhXu@+Yqa$KBrzcMa_56 zlvX!2M*A)DQVCuuh5XAQe5a-^TVE=Pk)*)pmr80RX)NZ9Bnxo_rfAu{g*|l+@?oCtVmdy!v^O~wEyLIXmmF7Ara=t=}ULjlwb>@qa(h}dL3W;d@})hc?rgrB3Ro=ui-mdSU?xZ^U}X&HB3Cch-(F3Y%EGI_C=e4-b( zOTit)5A+HLd6gynehXP#u+c|17=|9MI5+yZ-HMGqgxVVkqBr_PKH2-c-Mr{Ghr{~9 z-LaN1{ODNj&0ZhHm_HgMcUVAxUKPMlx*H2b|b4{2YD0El%7!=qD@h*M$oWs0mVVJ{uH2SnjSs~+wc(i?r!U1U5~M@08n${rvhibnoTB8si?8v~8zFMsLhuukmO z<~{+JF#`J9+*d?`5m0JxB*0iBuLlfum}f==IIQnR9<{hA7Ij4sgRY_Bx9|oKl6w)GtZWp$0bMw zXLndh+@_Kjk+RWO`w{#8_(%DqC-7%oLCMz9IZWo%JC$E*=hgYT;r&KGo(D}0P+37Q z3*(;%f?}K560%%)JP#B3Wz&dHNiH>AeaMMvWiE}&t^Q1gxG0yZC}zy*xs+_SVUlp# zqU&!DD9_yI)b)3}bGrUy1K_3k`Ym>{12y!|yb;xhyzQ>gAc6YM)MpxXJ5ry4B+nl& zl$^@`|8*Cw?9aU?^iX0~TJJs6@S!iRP=uqMw(qj5y7nSb;k!l)x-n3~dBM-NIN$U7 z;i$IDwFuCy6fiyJl9HW62e_SK=m4@?EbiJ7#SKs0BbME3*GSo3VVhemHx^zC!cfou|(Owx$h$A4d9*}GNd0*O(ZeYkN4`2fMbD#1*E_3 zh1|%MudK40Yx@3dxBhE@#_&-y(sv+cE zEw``Bj`TiGf!4~;Q}0XE+u1+=K_~Rj&sr%F{JZTRXf~W_g~^NaOe?rVq?eQEyM(_J zUV8aiL;rj>uer-g3+jrJm8Ew{(YkC1j5#Y>xt*?}Ro^MJ8i+(is{-V^X1j)pR%|Nc zv>?+~(JC@4;d{F|=@p~Hy8q3NSN8I4;;LuRvWixm_e7z2KG|AwT%mg{kj({;E8K1o zOh3^0MgskDMdW(tdv^1e5tO!3Ie0*UIv9_e}Sw9ZN(24sZRh88rXh%f2q zzJQpxiAx^onSP{a7M0vSu3!UK$($?V3Rc81ntUwZC}x5h#}x%gy9kx6#1XsP3oP61 zbjWE!bH_7!xN6tZAN2EBcqt>r>kA52){nHWdULnCoFJ+@|7_iZ=jc@HR zZ#uW$Zk^S0`h%k{$^8#TFW%|@-O+zF08_`sJ-@eGC7*A1kA6EF{l3VCqvzP@8wPCj z`{Vc8&Fjn7*{zq-zkP7@Z$9%69epPCb(mkciJ8o@>~LpNi?pN`iBo#(lUk4+i~^)x zLkh?k1sVpRK0j&|&b7Dw0aF@ReK9qY>mk=i3)7=GlyI^`i z-opXX10TSCfDF*QObfu$1=a(yhvFWV1*;lvtiC<4`rrqUdLZS|&qDPJX@Hf7xUh0y z-}$qM8!n$kTyS~CEE1H&0RDq1n%no$a_o|`odXduH#bDg)eJFrl$dE9U@kwHVh)?C z78uBn{l=_99SwVX{%Xo~v6oCFfYYN^Gay~Xwhm+yt-4w;ed$zE3#D^=Lc^Au>tX3C zwu=$^X3cEkI~#iiM@n(I`@njFYX^d#dI|sbH;Ok%8kiULAARZj~qf?U*shXM3A+x{DEx@=kw5v#fh`c zc+~kHAoLeL3jsjy(*6=YRtbEb10PQa*M|xpDcIf;4e;Ds`u^!9o7mox2;HaLhwjgN zK=<;i8c4V;Xd#M=G4!5iJhBiit7Tht?W+M@ZHVT#SB_pnjFIxY8iwZkDPC*M(0tb> zoeR!a1I{g&@uHr2BlYjQjSPNDKAA`46gM>*M@=bEzlB&kyloAb-St zke`NiYyk4NzUA8fszdoZH9+|(_W$xv(Z*2zK8>RMZ~Bs%vX}oB%zw`{-6l~Cu5SqQ zJH@hSR@M;a|6MF=4Cd#Gh3&Kd6y`trTP@6=-$T|qzysn3>tJIL|5(?&o5XoexRGu* zla#P2w*c~P3R^Q}wnbVG;UCB5_9DVRu$-&H!lRtK=sCF`FJ2_!!HY}~=k);j zXNrpgF-$4vpcH3!5S~Z|@2eTSA844x*JEQgRPYVZ{@7ZyUw%`AU0I<0+Kc```_-Ch zS{I-5Fny?BEFR%q{{IH}k97n8jGM9%;J+eD(=%GT$P3qbv@j3$nJGwfg2L>>8Q2(WI+tlM_$l1nDTZ_4|t2vVpBX@KB&Cq;^oELhRF zzpm)$g6>@_3+lC&x}f_=O>Dgt#?`|0J|c9VD=_H(6S=V7r#^HasQN?bUJ(+)pnGiY zcKLVM{eZv**!^8q$_@XD41We^cYF}LPw%X)5W)Mbha@R5IwO=;YTXFFudD%r&nEQm za}^oD?MJ&-Jq)#X!TKY@YGM5s?xZZH6(WjXahK5i(pwZb{-lAx@yS+T^w8S8$2XLp z79dPC3FrI<+gvFAlm7!0e_$X`{LW`9CQ2$2pv2d=JJuCp=drH%PcVE3F#Ls|GYr3n z01O`iF#O0TxoLKD{;K8<>**#vGN<#Ye-+2~7IFMvORgz&D*~O@6mGZw8rE!CjRg8@ zShHQMvYRIj_jXv{+^oBD8Ch(lQWrMB=)1E5EM5>|0p|)WX!ri5+}E_B@UGYHCe?4t zSv@LA>CSHX9d8`5#Y`I+sumy(JU)ZgW@0s0mx*O>#VtA=r(!MVF%}(i>eh0uy0vS$ zYp%82)2t`a*A%VqUACLIElYG*$-9qV8G=kuh1_DKX>0Usj*&*fNYgODKwD4QsdAX7 z?o&CeukSET_jeOTBv52M1U_^8gQW4ikUL%FqQ>qa)oxPk^+KUY3N~uHzdY7HL6VEHT)Blbb8ylwt&S%&E8&8IK#mFWF61#a-%irwQ zDobnREy8GTGfHGex%-3!qXfq&(J)|?xL$q@8?9$;4(sj65f4(L7C^t??*h>Ov6PU# z5b7{r^y}-eUVe2x-jL5iEuR6t$~w}5++hS+)2<;wWQ-sU14fW7@f+;s&BJ%tt=q1@ zpBdTk$*2dD|6}Q~ZmQB@etj?&t`o+pYu}M;JVv}ozwRNe3nce9ta~WLB?JYrFUxxm zku_uIF|@qXL-m=xLf0KTDP_?4pfo_|elaRQ=T!H647aISc3%PneeuJ5Ra!j+edt5{ zsW|?dF1o2bTi5b-VL8W`yL5L_f|cAs%1?g`77MSYCDcOdF77V1{5o57HjD9Gbk+mZ zRUd;>?TI~F7n_={^Pc80ZrQc?eG>4i2?0HczU#BjeUhAgA{!ddcOZGm2jt*GD;+4= z^Z4Vsm%G++pmEf=^?;=LE;qNC&?8;Nn9UwJ5W!f@7;iRN@MtzEGE^`2Q@_R7xAc3$s!5orME*L$wJ-jgh{-H|X%{*z`6X4$~x zUt_cEUf!OK7ohR8m~781_sm7wf`ac{^YumwW;}2PYv)Kd0!E@^h!^t9LdjzNvQQXG zVIzo1LGZq3TlwR2n)@&EQVYtIK_l`k<8o3U(^KLK$GXB|7KV?>i9Pl9=P~9J6A#<1 z^Ir>f=p&xi?`p+f0-!6D`#uF$fpm2*tagRTTMsf2$xqB^EIYO;Apd4zLU zs{*7&WZm4N^aG1wPG?qx|E))n3t68&r!)J@p!qtd!v*W+AFkT9^N(E&TNi6Z4jZS5 zHGhA~%V9mSH3MMc`Z&w_xE^D2$d|k`qOeAxTjE01YZT5pw-pALY5D_!p(rOYrH@XR zr>}9%%Id;`A{Pa42nx7D3tB45dC--hCQ#&o)i=FSk&SYP7p_`>w2J^6#Y_y{BNH%s zje=bgL$B*05<2A64c%3{cIY*1>Lpf89Q@q(dyy=`P_81-S&g18>_N7K4YPh=7?BgX zVfaIK^N#Wl?AE)Bod#}5!x4{YIAYIs-RUZH@&9nRk!_wIXEz5OQ-BL$LbUU&|Dv=0 zq_m_uRQFzlyH7~4aZA~_>j#FRIg$NI@k8c4<(=%-H%87*MECKa*M$W`|A~Wc{oSok z9y0&9Xqnym>siyg8VtJx!=BAj#GCb0X*d6l6CqXl`;?zqtC0Ay}n9 z>tAo=&;Jug9y0Djo88>ysdoB9uQ{tjQ>sIIJfA~Ou=`!nuHnFCY~b|+Lw-){!>I?c zMPQ9_SkG>;*WGAw=mq)z$U$GTd}B9n^y-QM_O4O%SQpt5I^?mP>7jV?9qnIuUvITpRm49Vq8wtqm&tGY`q-?ApO>L|koqC$nmC z_xgjYLczRqMRhZTx!Im02}j|N;vAqIn;|qTn=Y2U>zW^U(3ze06`LQNc$&=*TDEqc zc-qqmC!QvAYi9+=8<`c{sokmfXtDrJoem)bwVAyD0D7Ln zS~^fimt#11i$58bKB`-Ny6n%-Q9W9i`%+4NPG|te(;$c()0{ZW6S@GBD1Ad_c(+(z z6<6>7FTN)Y{{Qv+hfbj{{M|8Wwht?YS`1g$)=mTtwlp+05dhF+$j@nP7Jz>`n|b{G zS-|s)A|2+ELK83xc+*kxrh|OW&uk%10i0EFZa1we&N3HM023nyk%*C0j5slp zA&T6*HP&I?uq)PK-LyG2vz7Z#@kbvJHZ6F7FwwX#SK~kW057!gfBe5}_Y;^Fzq|Gv zHX6Tw4m1&G3coZuQ^5VOtie>_kBE_VYUaNkKQ8c~`#+u}I3<;zN-D|NMyCb;ZjgN5 zJYjSt?|;g$@Q42=Mym#N%ow`)#pevqRMpN6{?+K*px)RMq(&}HTpzGcgOPj--uGfI zLqym)BbVh-s0esPX5_N6BD#j)swFY*@VKxcEWR@V8FqtGEEC#}4 z4s%Cr_%SFtvL60^yFTs?_H!`0*YeJjByhwtV7@*BJ%eQ#&-{JtT@99chdKEnm5ZbP zaR$A20b{>!_dkK&=@*KNR5`UnL)@KqP5C?g^C6#LOV*hD-$ve5B*{pVM-lt(w;A%@ zI^_li`1?J`&5XHX8!iRk?d9CY_~%J|{`p$m{U{R&TwJ@6`h{x)O590}wTin?&&1X3 zrM`cEBTDJJJ9Xb<_k~boYS@_2E3vU(e=jQjQ&9?~#x(H~Qf#d$M{5}~}5Tw1-CG0A!|$4fpRR2PB>DV|i}L(MJwwU0~isHY@kQ z@`4y=03KpN$GgzHQKI~)$QvO%C_)<6V(d9W42ON)Qlt}7!V@(2nNscxkn#gG#y^rE z&0F2&z65Cl{*8Z_1SvTv%+S1ZUj5&dAYI7+za>FB=e`8#?Th*cWk)-DGN4Qn7y}Aq zN7XLw6XB$<(Hd9FeBvoax-PZ}A1{=i;lXtBFP8|ZpKhw~7r0Jh>vNsx*9o4or44va zc#|12kDhEN)X2C_jBc(I!?@=1`*NkiG{5?Sr5zt+IngaN(sn70vz@fiio&Hrkbg*> za4Fw8RDXqscqu{lrHjy94B+x8uiaOPUeYMXoTbB%|GKfIrvvOPQLadN!8cGjCBkrQ-OM8ImvDH z58u^_#35<=FpSK`lF{w*ceOIA7I`TIY3hgq{t-xt2JiB-oEq3$OUf801>(wwvTue+%_+;1ex6%cISGKCpCt@)B;4JokMu`8jE3{O$B3{nP zaShzS$#H6z6eZu$gz3Y!W)-ja8_+nF2=9mPUtuC8`tM@ckNX>rO$RHUj>g;$6-1~Q zlSH&$yF{*RRe*FY({<^=fa$m{{@<0Y+)js_I<(kTyB003bTOgJSVbEKV+px#H|yiU z?r?H#cQB#vYK}knQuKdX$zg@A#08WeRygb2{KHr!M>i7a4=ZAe0f=V?8rZoI10d z^{n?@ax8|Cz;;*>xp9iYVeb2Jmctr3drRm~OgIeGc%2dJyQZFvvup{YRKoz{jyxWo z?l8ameze0n3>+gv{lKuP zSLE77Q4aIYRom>=#g|ME5TRgGuYcgMTYvRQKZkiNf6Z=<`NG!KfEOY?H?+s5USzuK zok(anb{QLc{lGAzSLET>fFc~91{9%sLKj19%LY$C!Hj>_;MwD!8*0l9jeK|uR;|)K zShc>tag6)-^l4P zqZgPkp<7ZZzob9ZB{EUjM9@C#Qa@=O#?SA>e__tsslpUFcI!%>)`T)78sZ!Ea(Mzk$?hA9$BL~?b z>i#4Zio#-DHl|@*IzyQqs;0u_r=SGk>Hw}di8ke1JQG=*lm#QI!T#_9Z~CljY-@|; z4QPynwZ=F6ja2cDTAHm3CH%#XoL?uy;;zIh{Q6Y$z7n}478Qgx0ZHU&$=FVvERsoD zWlNc)e4GG1;Q&K45U(u=KnaVKF=WGJBRw zlqMwyRcU!bz;Ni?1o@bL%mFqU*g0Iz^(1x)QH%_E;t5$&-V;wupd+DtU#XQP=dD~3 zP6uf_B_nqLmB}Y052kO=ml3W|W!RjJwNnsv@?-~3U_lv*L6!1@fjZwz<7j1w;8GpR zZ|WU{YcbWKw(q5KF1uGzx0HChtl~o-ezYRQ0ks(5`FJ;M4OnegQYB1h$;M|*A>3r@ zoJ#smC52svaK)AUFC)kTeU~9BMzO-3G0`ov5mkDxVeXBa&M)NG-9r z+p!snkQB85eJ{EF7Op1478-PZIVX=nHL2|wv6Vq3UfMBIQbyyCk*YvRAvkN^9my0> zNKKy(O->~ndZY1}f^8)7cF7JV$92CbwJ^gTBxn{s z6oT6-YG@6Tx16PtXHTWhuapD7QWm!4(kdh>3(1fd@jkFWzGUP#&{BvX_RIc9+nZi1NLSCI;UufH~KJ83bq4tXJu&%`+ z;G>l3@xWuE=S%t5u(sl#fI=mr%P8P&e)^&Psa7GwDSf{oD(LtMu#EO4+xgMHU3^7N zV|o3I%tWZ*QckOH?Wa=;stm85QoMcqa9`rUn`L;(mz47peYNToD1_}<`#Ljq?@VJK zEG%&94$h&zJBIqkgS8vjqXe=pv@guZs^xFl(y4ZnL>Z#=3VTFlj2C=uk5b*J$5k1! zo)T2l?dOvpE|U>}QO$aDu-4no9II7#z;$BLHoMj<@a*%k$k~bB?nDzxsASKk5=w`_ z5cV%6%SQUK%Pg>UqU5`Fok-jGPPA*e+`Fd~)kQbI6SYs1gp7g;GU>(QMYgBuNHre>~oqz`5PP1P+>czc{Ad3_WQ|?B5_b06IapZQ*l!oS%vsLe|e{~8ZzpVxS|D?!F>Tr_?n3iK2C z9`hv7_<7h*sPS_iuXXvy{m30w=PA@F{95YtAX8Xpa+eiZ*y$ENeTDzr96=W0oGyV| zFHrBS`wD4|M zf=iFBJJFqs1S=@1D?s0zpG(~?|8prB=UL`4BzCo{w!vQHTfxHX&Id#<3(?<`I|KEe zE<8|A#`oSOiO=N0bCAiqT?sNKu$CJuDE*^?*XjyZcXxiJcDwwq)MOGPT}-3RmDQc) zzB&@D1C#3t^yt}0rQ79?RFWy4WetWs)w67{m$T00gBsI3%4#}OYiD6qOJP|{VNpw= zvL!L`*%2fa@A*J@rp0oOb3g>#Mree*Y|G>$-h)mDrL0TdbGd_}Gx&k)z2%q{@FPN) zDo3p`3DbfUz9@n8XTbz^KGbMJg(SBl=}b|Gb40?i5eeyC137vy+x203z4z;qcq5NZ zd?W96CCDge-K;GLN7-qq=(0~s$r~PJ^-h{zSD?okcja!Ee^*XsF#5;3nmjtVX1({% z>xJwn;fW|`NTleZLn6scyqjcq_}8s!YqEAa)2PnEUAZ}3pbB$00eH*sD8WZTVs&UW zi9eKhVe#wqzr1$-7+%GG?(( z*A^tU){KbyyNKASgW^H5UxUNZhR@s>pNUVdCCcRYGzN6O&2bAC=4eDsLC!{C@0qwY!GS>>~$$|wGaSL`V1 zD8(o;iXTNs`HqrPuR=|0@~`aIB~WSnDAjNEalS5byL@ENXU6N}Q0Cn1<8~#ReaIEt zr&8@U3De`rM@Mji)>7WV=+nbb7jPNDHv|!rQoGMwNd%WH)$+oS)`D^ zlj+iT&QE(xCRZ^deEMmR$!T^s=k3h2+nF%`zB#)DGo_jmA}bNbb|Pf7b3bm!7F+wr z$zq}X_^2^qv!S`);O2)UhlUOK>MhEhoW4lcs+YKe#Y%?*j4Tb8yG$aN% z{W7QfWd_}h2yw*9-?N>{Alu4LWoWPYE6qwDKDWCOW&?~-#$-WbzuZ%Q!B^Eb`yI*` zWUCw0H@>|A#(UZI8Z#j^G zu;M@l?(!%64+^M~{JAN&Aj!3&k?}_|h>?GxCrRX=>lyMya=iSMR){jK%_e(F_fR9$ zjUUy*T&Hi;2SL%sWb{Tz*-!OX)aC=a)5}R8=K%^=dVtFN%cYj(*{Y*QE~W@)mZKxo zr}_ZpN9J(1sQEz1QSC{ryFS+H&MsHQBDM(K$@bEhD1R{9XjFQoV}X}DC^cTHxBFsV zz0z>En<^>U;v*P)an>x}&}*Q+moq{7LxR*9+^ZmYzqj8jkCkt`S%_yetB~py{u5RZDwM2t7E;kwqln8!u!B{KXO*J;-K8u~ z#!7dwR9ja1URb?SFIKvTl_s##flca_db83GSZO;G4%!f0 zEE}R^&q_ySO92WDjr>nOts&ms${5iFCM6153Vn9PjwpP|*zMW=Lxv3MJ7mc7X%dxw zk`BaOWjW*M~*#Z4>!OT(6FV8tc3DgqPW%+VL;&ES4izFP1#UaA2XD30|N6?ye6$w8&if1 z>Q~?|H3XzP{iSYK=r1LQZEw?QPkM2m25f!O3pevgFT;TJ_LYEIp7e6N-A|HFZIkm+ zsp13hlIS+Zk)b?NY|()KHPP|o1lJ5d3u+RF;z_%(<1sC}GbW`s|EWTbw>WjAm*LxV z%QlsBTp(Q8xWMekv=_$(=5JD{;^oH!w#EmBoMNYhDZQ4+uEi?C@$$7c*jnFP`id9FeH`|ZI#em?6rDDLnsgfSu4R#G z-4nz3hXT1rQ=G%}Cx_|DpxeXrc=7qt-3WzJES957ck7m^!ivJz29XoCZeC<}X%Xeu z1~m^?`g9x7*9vv{*#kJISp#J=QgW?qBxT)xLYX+>Ibzhc)@zLs zPv-S)S-9O#ZH!7V4%NM^NDG&mdTN{p{oHQgK|k`bZ8;@d%9c~^`+y7M)ZGURAC#AV zR509q{N6Zn-suR1D@Ew7w|na?UQNmOwiE^Ver}3FjaS`~Cw$mMju)J-ZlcADziIGF z{b$g1%x5Zs@HxOuILE5*E89uQB!O#c%ax{o;%&;)kd;?HftJ>pp`x2MGc-EeEB^d} z-Px9^m+-V!+1g3x7E!rH>1U7)6M~0jB}+|-8uCv2SBS#2;brH`TDaZ_BVL{#@iNZX zXe*-Awl!lK^4n_k@hbjSSZZ61X}?mu*9dFI1zK)|MIZ4dM{O_A9Y?&sKNy^nYsXr^U>pYu5tT*Y%L{2888jebsLiZG%54?!sIH;Mk_(Y|MohFBz@1}L9a zCbe9o@S3)q3LP{ftEfq3G8HMD=^D4Qr)%(ZY_bVF*;QJFulQLQorYd+Mc86~LWDd< z(Au|$GYc6escz>@P+qTd-Fv|YnK-)Hrc z{LrVfwrEA^n!SP_FK>$9^@zaKXH^ETZkDY@&LM%KJ2@nf(73@fUq?T-I4@v6kaahw`MdMi-32jgk=m%y%;dTvq z*1|a5X1m4@_70{kI9~n=; zlbNznTqT8WlUkL_8P#R^ZG@ly+XNIi}e+?OWkzXE6`qU@$N8W1T3T#Cia!k=C!jRP{3J4_ZLy_ zHtq2;#=QS!8c|M_-o7z0ZDGDfrXZUX3RQl-qJytM+A0*lm+}zi=TAa-w95j7F)^qj zpBsD(AtB!&%>VX|FG58}S*CpMXIkVO?P6r5yILs}YP8%@-&vJBUBy&NcU7w=fis7` zP(m*5FOhPqw6+pyx|f8+WqOf!UMS(plbt0}(d{Xb67o`sSO-A#Di;tv_16#=5Dm0E z_Z`kiTP6aF&IL53PRe678Vemjc+Yr$g>g z#*33yEhX`aGWOTctqx&I_agzDZ%GY$yzig^FO0~`|5yq&7@cLGc9!A)a!fknSXO%} zcPSwLFhy8#n8xM|&rHh88Ga%uFEua}5N1Mh-quSc$vCP^&O7nQCaP7Rzz27|4GW$l zrH5&d1Sl%LpBFR{n2&@5x@UG+(oWztjXFUVhn-kGk0|0!I1f>{ltYw^Kpc07Qz<6&eNMGk;WscM317&~w$YSqs~An?n9Gmd&7bs9$GB$lAN!b|RF3EX9f8xgan2F6W(38= zeFVMu5wy{U?^Q&dlL zIJ0y8orb$+PeRIdo{No&T8k-3p-)1eWQ*tQao{@&XLD&ybE$lQT#PmY;@F1&X!WQD{LtxD{Odcca^<7@*81{AUP;*^ra-k3q&J<*J7m$8Xx+*cmx#EdxQ8*vdF^u(XW6Q5G_2$g|JCFk;B?bfwhP zO5-f1aL03oG|;mf-r!16-yv~o38kcPg2a+vc-{~8wIOcT{) zedhI!<;fQR9?6ep4Ht?B&oV0=iH_=@i25?`EIIfDCCyLB`bn))^Oaqs7) zm#c3Sl66sIh;i#(c~IS^-97oqdoU6P99=gk#Ccbec{jS5ANh>EP}|E!aoh)5VRn!% zIwW0OW98&D{*q-6X_CK_hhY_gzgJ#5ru7j%&bE5EjcxU0jB}yN zv{2Prr&NF&xn~D8#V^i>oaU#6j_#_*Yw(5bEfc2m4mn%HW0KxN>6=UV$6@JDIMgI1 zQ*TQ0F{R3}zR1+Wm1gQO=i8vVh?sT%cjcLnmXeol*!K=}%dAjrShFEaI7D6vSU z=%XpG{MEIg=-Q;hW|d4%hVe&(bh5w*MfNGGOw)J5mz@8>-2Jf+_q8{-`c4T&*yU{; zagaa2gvG>9ZW{Ym2dt;w>JTlF^GPI)wvIhw2aetQ@z3j$BP*V^cyC&R@Y+rFHx_^X+uC;rWQH$4L;|(GHZaEIo&9 zlhNmBtnZ`8vERAS>aczt*xo)8Ip2Nzt=+tGg~b7r08{L~XPoCK-2QV^9w=XBZ->u! zupJBA=+W(H?6{L79Om=!y&cxQz4o*p*^Wkj^EDVNPM*Ln9!TnJN8!%3qanWqYvrpo zg5S&x^4^3DxpJ~(PFEmR@wEAmR9S1`VDOpi^|35Um3Jd|4YO_#yMDUO zVcvfHPF|8wa-EcU}d+$0N)*aUzZ#?VtqHsIBXmSQwWAmcq zR7=?DXzvAZ%!{g+`6b_oIprvH!u0?d*X_^d3}48 zYJUrkmEROBy9KhdlvD6im0iHg;AzH=ZO0EaM6|iLj5klC=;S`1wO+WEAxk_9q%zcrj zRz@P7iZ#SO#67T#Xl0Chkz_2r&SQ~s8%vcJ=`@rc5=((~2dXOb0GZV-M39I^d9_$> zstkIfh74_3dbqaKi?v20qw|;++@Wq_{xAF0^1S6_CEY_zmr@2=zzJtIwGS}ggyaUyu3cyI(}GdmC0L>@jkjv zj%boV@XqcJQp*o9k&bIN-AOxF(UQL?xC{yMZ31QT(5pel?3)pCD(L2FBCpLE;xJ!c zG{j-OGHZzDI=7tiBkavfBwvTh{q%S6bgBbm7j0f-H!t3{2yC4j5H#f#{t&%U2g-z! z?P_ZeNO0I7!J&69C8|S1k!&kV=m88`6yQ zyo6qY(kzLtAD`%}=0Efkd?8j9tq-qI`jOj3-3eE0-{0F0QcBL{By~B&laO}|@pNZ* zk_K_5x$GDcN{d*07nVF~^ViiLZ>^2@uo&l6*TyGn>-~T1y$M{6-P%9C?%J`Nw%u-! zL<4zuB&1P;29-o3A0M+H-rylIpO0Jm2i2X5!hhSM>IjdPWab{MJ{SWz6Mm7>0@Zyib z8$x*Wp-&$QWHb_}?Qr6Rp+n+Co$eSoxA;SMXZ+@fv|0!?0EXHu04j>y=S&Vr!hzqX#`9CK@ zt|(9wl#&QJ-GH%v3*?W(8dfF$)fztIK=Ehs{aL^%lj~REF@a)4sePJu!(cljjW5d3 zZcWj3+0i!S9R` zA{oE2SNJ*nAF~(QM8A_!unl7=ig4x}NR z9}eQ{|1ZiNpoz~v>1ts>|AEg}5ps@~Emui#9>tCNo9Jcy%UlvVgtElK-$uF}t4*o? z2-lFV0_D{m`SbP1nvF+BvaFRQuy7PTD$8j;n67T&TM#mXqjQYDH}J}m*tiop4vm>M zv}m|%Bu4Uv0U7t>V&wjX#JX0fMwG^RX}=FyXQc7Q)YeGr-HG+XJH~9!C8UqPlaQ5$ zxkP*?9x~gs)2(nZQclZsBdx4a)4jIg3T6H_G6mgrog6&65bTsROuSnkXJpdo386}H z$Jw~`%L_ySwdoI=TBA4nGupSsd<-=VuM9QP;*T}=!U8-m*_BZy)nvavlT%H|Y1k&q z^%c-1n&r>vm~zr~n6%n&-zGvl_?yU-GBi*QCo+F?0AQ!pcDOo214_iQD=m}FiHzl@ zW!sU6D@Y0T9d7<|%BB~>y70rnguX;(S|YjpBY+7AAjbs9Pf|*8y-^0T7&WX(7!vo0UA6%K(=g$~|0cF%2?}`14$T1fs)juNV&x{l) zwl~xmP-bta#YiZv+PfY0Dt_44%fZJ>>KX1U)wA|z4maR-7ur;vrB5kZ9P{JMVPxkg zz7;WvB3A2}-HZFzz;U8=hudFPjb$Xf7zsTI1zskbUK-7r?>x%r6yaGb5`|@_*rL{p z56{zn zP+NWt(p(y_YOd@f8x~JSa{nc_4NC(EIUE_J+=;d4opkMaWDpr_eko`wgHyRhYZ+z& zJ4~L(XV)u9g{WqUsEbR8;l65~PNB3UC*nSl6j$Iy-ZzlI3eTS;M3*d>!J`*kk2(^d z6nb9~1BW*Iw(cF%NDM}K=LD3c_ucZ=z2$u^^gdbF#|c;Ggqw7RNzomDoR5Z636I5Q z6q!M6+^s9YjZeN$WM7$)1Tn-Bt`@7)<)Oa(3Dg#>@PHcpY0rk%_A#q^_@yC~q_ab(i^C|VyA~URU&Y5GRHB){LrQQd1T)jR9FRJ~Z z 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%0A<8uFutg`-fz|Qy~=*3)k;e8jsX@nMO7O&5M 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 I*VkbDf8}xlL;wH) literal 184164 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 z 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

undd~3xbLr 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 diff --git a/example/Makefile.dpdk.in b/example/Makefile.dpdk.in index 523fcb7..8c452ae 100644 --- a/example/Makefile.dpdk.in +++ b/example/Makefile.dpdk.in @@ -17,11 +17,11 @@ include $(RTE_SDK)/mk/rte.vars.mk APP = ndpiReader.dpdk LIBNDPI = $(PWD)/../src/lib/libndpi.a -SRCS-y := reader_util.c ndpiReader.c +SRCS-y := reader_util.c intrusion_detection.c ndpiReader.c CFLAGS += -g CFLAGS += -Wno-strict-prototypes -Wno-missing-prototypes -Wno-missing-declarations -Wno-unused-parameter -I $(PWD)/../src/include @CFLAGS@ -DUSE_DPDK -LDLIBS = $(LIBNDPI) @PCAP_LIB@ -lpthread @LDFLAGS@ +LDLIBS = $(LIBNDPI) @PCAP_LIB@ @LIBS@ -lpthread -lm @LDFLAGS@ include $(RTE_SDK)/mk/rte.extapp.mk diff --git a/example/Makefile.in b/example/Makefile.in index 8d1bcd3..ec44acb 100644 --- a/example/Makefile.in +++ b/example/Makefile.in @@ -1,25 +1,44 @@ CC=@CC@ CXX=@CXX@ -CFLAGS=-g -I../src/include @CFLAGS@ -LIBNDPI=../src/lib/libndpi.a -LDFLAGS=$(LIBNDPI) @PCAP_LIB@ -lpthread -lm @LDFLAGS@ -OBJS=ndpiReader.o reader_util.o +BUILD_MINGW=@BUILD_MINGW@ +SRCHOME=../src +CFLAGS=-g -fPIC -DPIC -I$(SRCHOME)/include @PCAP_INC@ @CFLAGS@ +LIBNDPI=$(SRCHOME)/lib/libndpi.a +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 PREFIX?=@prefix@ +ifneq ($(BUILD_MINGW),) +all: + @echo 'Examples disabled due to mingw build.' + +else + all: ndpiReader @DPDK_TARGET@ -ndpiReader: $(OBJS) $(LIBNDPI) - $(CXX) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) +EXECUTABLE_SOURCES := ndpiReader.c ndpiSimpleIntegration.c +COMMON_SOURCES := $(filter-out $(EXECUTABLE_SOURCES),$(wildcard *.c )) + +libndpiReader.a: $(COMMON_SOURCES:%.c=%.o) $(LIBNDPI) + ar rsv libndpiReader.a $(COMMON_SOURCES:%.c=%.o) + +ndpiReader: libndpiReader.a $(LIBNDPI) ndpiReader.o + $(CC) $(CFLAGS) ndpiReader.o libndpiReader.a -o $@ $(LDFLAGS) + +ndpiSimpleIntegration: ndpiSimpleIntegration.o + $(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) %.o: %.c $(HEADERS) Makefile $(CC) $(CFLAGS) -c $< -o $@ -install: +install: ndpiReader mkdir -p $(DESTDIR)$(PREFIX)/bin/ - mkdir -p $(DESTDIR)$(PREFIX)/sbin/ndpi + mkdir -p $(DESTDIR)$(PREFIX)/share/ndpi cp ndpiReader $(DESTDIR)$(PREFIX)/bin/ - cp protos.txt $(DESTDIR)$(PREFIX)/sbin/ndpi/ndpiProtos.txt - cp mining_hosts.txt $(DESTDIR)$(PREFIX)/sbin/ndpi/ndpiCustomCategory.txt + cp protos.txt $(DESTDIR)$(PREFIX)/share/ndpi/ndpiProtos.txt + cp mining_hosts.txt $(DESTDIR)$(PREFIX)/share/ndpi/ndpiCustomCategory.txt [ -f build/app/ndpiReader.dpdk ] && cp build/app/ndpiReader.dpdk $(DESTDIR)$(PREFIX)/bin/ || true [ -f ndpiReader.dpdk ] && cp ndpiReader.dpdk $(DESTDIR)$(PREFIX)/bin/ || true @@ -27,7 +46,7 @@ dpdk: make -f Makefile.dpdk check: - cppcheck --template='{file}:{line}:{severity}:{message}' --quiet --enable=all --force -I../src/include -I/usr/local/include/json-c *.c + cppcheck --template='{file}:{line}:{severity}:{message}' --quiet --enable=all --force -I$(SRCHOME)/include *.c clean: /bin/rm -f *.o ndpiReader ndpiReader.dpdk @@ -35,6 +54,12 @@ clean: /bin/rm -f _install _postbuild _postinstall _preinstall /bin/rm -rf build +distdir: + cp categories.txt mining_hosts.txt protos.txt README.DPDK '$(distdir)/' + find . -maxdepth 1 -type f -name '*.c' -o -name '*.h' -o -name '*.py' | xargs -I'{}' cp '{}' '$(distdir)/{}' + distclean: clean /bin/rm -f Makefile.dpdk /bin/rm -f Makefile + +endif diff --git a/example/intrusion_detection.c b/example/intrusion_detection.c new file mode 100644 index 0000000..ef4189a --- /dev/null +++ b/example/intrusion_detection.c @@ -0,0 +1,462 @@ +/* + * intrusion_detection.c + * + * Copyright (C) 2011-20 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#include "intrusion_detection.h" + +double normalize(ndpi_norm_value* tresholds){ + if(tresholds->upper_bound != tresholds->lower_bound){ + tresholds->norm_value = (tresholds->value - tresholds->lower_bound) / (tresholds->upper_bound - tresholds->lower_bound); + }else{ + if(tresholds->value > tresholds->upper_bound){ + tresholds->norm_value = 1 + (tresholds->value - tresholds->lower_bound) / tresholds->upper_bound; + }else{ + tresholds->norm_value = 1 - (tresholds->value - tresholds->lower_bound) / tresholds->upper_bound; + } + + } + if(tresholds->norm_value >= 0){ + return tresholds->norm_value * tresholds->weight; + } + else{ + return (1 - tresholds->norm_value) * tresholds->weight; + } +} + +double get_flow_score(ndpi_norm_value* scores, int n_metrics){ + double flow_score = 0; + for(int i=0; ipktlen_c_to_s); + + /* pktlen_s_to_c_max */ + i++; + scores[i].lower_bound = 90.0; + scores[i].upper_bound = 2974.0; + scores[i].weight = 0.21073785073559176; + scores[i].value = ndpi_data_max(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_avg */ + i++; + scores[i].lower_bound = 72.7; + scores[i].upper_bound = 1130.4199999999996; + scores[i].weight = 0.21257330032661592; + scores[i].value = ndpi_data_average(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_stddev */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 906.0; + scores[i].weight = 0.20990954527912953; + scores[i].value = ndpi_data_stddev(flow->pktlen_s_to_c); + + /* fin */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.07710300166602348; + scores[i].value = flow->fin_count; + + /* s_to_c_fin */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.07710300166602348; + scores[i].value = flow->dst2src_fin_count; + + // sum = 1.0 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} + +double Dos_goldeneye_score(struct ndpi_flow_info* flow){ + int n_metrics = 6; + ndpi_norm_value* scores = malloc(n_metrics * sizeof(ndpi_norm_value)); + /* pktlen_s_to_c_max */ + int i = 0; + scores[i].lower_bound = 74.0; + scores[i].upper_bound = 3292.6699999999764; + scores[i].weight = 0.3123007140611667; + scores[i].value = ndpi_data_max(flow->pktlen_s_to_c); + /* pktlen_s_to_c_avg */ + i++; + scores[i].lower_bound = 68.7; + scores[i].upper_bound = 1354.0569999999987; + scores[i].weight = 0.23802038891633356; + scores[i].value = ndpi_data_average(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_stddev */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 959.4469999999993; + scores[i].weight = 0.3111779763775991; + scores[i].value = ndpi_data_stddev(flow->pktlen_s_to_c); + + /* syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.0464364305923564; + scores[i].value = flow->syn_count; + + /* c_to_s_syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 1.0; + scores[i].weight = 0.04562805946018772; + scores[i].value = flow->src2dst_syn_count; + + /* s_to_c_syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.0464364305923564; + scores[i].value = flow->dst2src_syn_count; + + // sum = 0.9999999999999998 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} + +double Dos_hulk_score(struct ndpi_flow_info* flow){ + double f = (double)flow->first_seen_ms/1000.0, l = (double)flow->last_seen_ms/1000.0; + int n_metrics = 6; + ndpi_norm_value* scores = malloc(n_metrics * sizeof(ndpi_norm_value)); + /* duration */ + int i = 0; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 539.40668006422; + scores[i].weight = 0.16666666666666666; + scores[i].value = (l - f); + + /* src2dst_packets */ + i++; + scores[i].lower_bound = 2.0; + scores[i].upper_bound = 41.0; + scores[i].weight = 0.16666666666666666; + scores[i].value = flow->src2dst_packets; + + /* dst2src_packets */ + i++; + scores[i].lower_bound = 2.0; + scores[i].upper_bound = 45.0; + scores[i].weight = 0.16666666666666666; + scores[i].value = flow->dst2src_packets; + + /* src2dst_bytes */ + i++; + scores[i].lower_bound = 146.0; + scores[i].upper_bound = 6306.300000000001; + scores[i].weight = 0.16666666666666666; + scores[i].value = flow->src2dst_bytes; + + /* ack */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 82.0; + scores[i].weight = 0.16666666666666666; + scores[i].value = flow->ack_count; + + /* syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.16666666666666666; + scores[i].value = flow->syn_count; + + // sum = 0.9999999999999999 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} + +double Dos_slow_score(struct ndpi_flow_info* flow){ + int n_metrics = 6; + ndpi_norm_value* scores = malloc(n_metrics * sizeof(ndpi_norm_value)); + /* pktlen_s_to_c_max */ + int i = 0; + scores[i].lower_bound = 90.0; + scores[i].upper_bound = 3135.0; + scores[i].weight = 0.1760747755022144; + scores[i].value = ndpi_data_max(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_avg */ + i++; + scores[i].lower_bound = 80.37100000000001; + scores[i].upper_bound = 1292.5900000000008; + scores[i].weight = 0.17600137023171597; + scores[i].value = ndpi_data_average(flow->pktlen_s_to_c); + + /* dst2src_bytes */ + i++; + scores[i].lower_bound = 262.0; + scores[i].upper_bound = 53227.80000000002; + scores[i].weight = 0.16919914849886225; + scores[i].value = flow->dst2src_bytes; + + /* syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.168000195747388; + scores[i].value = flow->syn_count; + + /* c_to_s_syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 1.0; + scores[i].weight = 0.14272431427243143; + scores[i].value = flow->src2dst_syn_count; + + /* s_to_c_syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.168000195747388; + scores[i].value = flow->dst2src_syn_count; + + // sum = 1.0 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} + +double Ftp_patator_score(struct ndpi_flow_info* flow){ + int n_metrics = 6; + ndpi_norm_value* scores = malloc(n_metrics * sizeof(ndpi_norm_value)); + /* iat_flow_min */ + int i = 0; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 24.0; + scores[i].weight = 0.002732919254658385; + scores[i].value = ndpi_data_min(flow->iat_flow); + + /* pktlen_s_to_c_max */ + i++; + scores[i].lower_bound = 90.0; + scores[i].upper_bound = 3393.0; + scores[i].weight = 0.007453416149068323; + scores[i].value = ndpi_data_max(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_avg */ + i++; + scores[i].lower_bound = 81.3; + scores[i].upper_bound = 1315.021; + scores[i].weight = 0.9833540372670807; + scores[i].value = ndpi_data_average(flow->pktlen_s_to_c); + + /* dst2src_bytes */ + i++; + scores[i].lower_bound = 256.0; + scores[i].upper_bound = 56434.0; + scores[i].weight = 0.0034782608695652175; + scores[i].value = flow->dst2src_bytes; + + /* fin */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.0014906832298136647; + scores[i].value = flow->fin_count; + + /* rst */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.0014906832298136647; + scores[i].value = flow->rst_count; + + // sum = 1.0 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} + +double Hearthbleed_score(struct ndpi_flow_info* flow){ + double f = (double)flow->first_seen_ms/1000.0, l = (double)flow->last_seen_ms/1000.0; + int n_metrics = 6; + ndpi_norm_value* scores = malloc(n_metrics * sizeof(ndpi_norm_value)); + /* iat_flow_max */ + int i = 0; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 595213.3999999999; + scores[i].weight = 0.16666666666666666; + scores[i].value = ndpi_data_max(flow->iat_flow); + + /* iat_flow_stddev */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 245377.74799999973; + scores[i].weight = 0.16666666666666666; + scores[i].value = ndpi_data_stddev(flow->iat_flow); + + /* pktlen_s_to_c_max */ + i++; + scores[i].lower_bound = 74.0; + scores[i].upper_bound = 3380.0; + scores[i].weight = 0.16666666666666666; + scores[i].value = ndpi_data_max(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_avg */ + i++; + scores[i].lower_bound = 70.0; + scores[i].upper_bound = 1344.6399999999996; + scores[i].weight = 0.16666666666666666; + scores[i].value = ndpi_data_average(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_stddev */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 944.6399999999996; + scores[i].weight = 0.16666666666666666; + scores[i].value = ndpi_data_stddev(flow->pktlen_s_to_c); + + /* duration */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 711.6677598000391; + scores[i].weight = 0.16666666666666666; + scores[i].value = (l - f); + + // sum = 0.9999999999999999 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} + +double Infiltration_score(struct ndpi_flow_info* flow){ + int n_metrics = 6; + ndpi_norm_value* scores = malloc(n_metrics * sizeof(ndpi_norm_value)); + /* pktlen_c_to_s_max */ + int i = 0; + scores[i].lower_bound = 72.0; + scores[i].upper_bound = 1840.739999999998; + scores[i].weight = 0.11937557392102846; + scores[i].value = ndpi_data_max(flow->pktlen_c_to_s); + + /* pktlen_c_to_s_avg */ + i++; + scores[i].lower_bound = 70.0; + scores[i].upper_bound = 296.56599999999816; + scores[i].weight = 0.12526782981328435; + scores[i].value = ndpi_data_average(flow->pktlen_c_to_s); + + /* pktlen_s_to_c_max */ + i++; + scores[i].lower_bound = 90.0; + scores[i].upper_bound = 3496.1399999999776; + scores[i].weight = 0.13927150290786652; + scores[i].value = ndpi_data_max(flow->pktlen_s_to_c); + + /* pktlen_s_to_c_avg */ + i++; + scores[i].lower_bound = 72.6; + scores[i].upper_bound = 1367.7959999999991; + scores[i].weight = 0.12182430364248545; + scores[i].value = ndpi_data_average(flow->pktlen_s_to_c); + + /* src2dst_bytes */ + i++; + scores[i].lower_bound = 144.0; + scores[i].upper_bound = 7847.69999999999; + scores[i].weight = 0.12059993878175697; + scores[i].value = flow->src2dst_bytes; + + /* dst2src_bytes */ + i++; + scores[i].lower_bound = 236.0; + scores[i].upper_bound = 74486.7799999998; + scores[i].weight = 0.3736608509335782; + scores[i].value = flow->dst2src_bytes; + + // sum = 1.0 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} + +double Ssh_patator_score(struct ndpi_flow_info* flow){ + int n_metrics = 6; + ndpi_norm_value* scores = malloc(n_metrics * sizeof(ndpi_norm_value)); + /* fin */ + int i = 0; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.0033738191632928477; + scores[i].value = flow->fin_count; + + /* psh */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 30.0; + scores[i].weight = 0.33076923076923076; + scores[i].value = flow->psh_count; + + /* c_to_s_syn */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 1.0; + scores[i].weight = 0.0004048582995951417; + scores[i].value = flow->src2dst_syn_count; + + /* c_to_s_psh */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 12.0; + scores[i].weight = 0.33130904183535764; + scores[i].value = flow->src2dst_psh_count; + + /* s_to_c_fin */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 2.0; + scores[i].weight = 0.0033738191632928477; + scores[i].value = flow->dst2src_fin_count; + + /* s_to_c_psh */ + i++; + scores[i].lower_bound = 0.0; + scores[i].upper_bound = 30.0; + scores[i].weight = 0.33076923076923076; + scores[i].value = flow->dst2src_psh_count; + + // sum = 1.0 + double flow_score = get_flow_score(scores, n_metrics); + free(scores); + return flow_score; +} diff --git a/example/intrusion_detection.h b/example/intrusion_detection.h new file mode 100644 index 0000000..d079654 --- /dev/null +++ b/example/intrusion_detection.h @@ -0,0 +1,69 @@ +/* + * intrusion_detection.h + * + * Copyright (C) 2011-19 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#ifndef _INTRUSION_DETECTION_H_ +#define _INTRUSION_DETECTION_H_ + +/* + Code to detect attacks reported in + + https://www.unb.ca/cic/datasets/ids-2017.html + https://www.unb.ca/cic/datasets/ids-2018.html +*/ + +#include +#include +#include "reader_util.h" +#include "ndpi_api.h" + +typedef struct norm_values{ + double upper_bound; + double lower_bound; + double weight; + double value; + double norm_value; +}ndpi_norm_value; + +double normalize(ndpi_norm_value* tresholds); + +double get_flow_score(ndpi_norm_value* scores, int n_metrics); + +/* ********************************** */ + +double Ddos_score(struct ndpi_flow_info* flow); + +double Dos_goldeneye_score(struct ndpi_flow_info* flow); + +double Dos_hulk_score(struct ndpi_flow_info* flow); + +double Dos_slow_score(struct ndpi_flow_info* flow); + +double Ftp_patator_score(struct ndpi_flow_info* flow); + +double Hearthbleed_score(struct ndpi_flow_info* flow); + +double Infiltration_score(struct ndpi_flow_info* flow); + +double Ssh_patator_score(struct ndpi_flow_info* flow); + +#endif /* _INTRUSION_DETECTION_H_ */ diff --git a/example/ndpi2timeline.py b/example/ndpi2timeline.py new file mode 100755 index 0000000..c7af7aa --- /dev/null +++ b/example/ndpi2timeline.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python +# +# Copyright (C) 2019 - ntop.org +# +# nDPI is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# nDPI is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with nDPI. If not, see . +# + +# +# Usage +# +# 1) Generate a CSV file using the ndpiReader tool. Example: +# ./ndpiReader -C sample.json -i sample.pcap +# +# 2) Open Google Chrome and type in the URL 'chrome://tracing/' +# +# 3) Inside Chrome click on 'Load' or drop sample.json in the +# Chrome window to visualize the output +# + +import sys +import json + +protos = {} +lastId = 1 + +def get_timestamp(seen): + tok = seen.split(".") + return int(tok[0]) * 1000 + int(tok[1]) + +def get_record(toks, csv_fields): + global protos + global lastId + + if len(toks) < 11: + return None + + record = dict() + ndpiProtocol = toks[10] + + ndpi_protos = ndpiProtocol.split(".") + if(len(ndpi_protos) == 1): + app_proto = ndpi_protos[0] + else: + app_proto = ndpi_protos[1] + + id = protos.get(ndpiProtocol) + if(id == None): + lastId = lastId + 1 + protos[ndpiProtocol] = lastId + id = lastId + #print(ndpiProtocol+"="+str(id)) + + ip_address = toks[5] + server_name = toks[11] + record["cat"] = "flow" + record["pid"] = ip_address + record["tid"] = ndpiProtocol # id + record["ts"] = get_timestamp(toks[2]) + record["ph"] = "X" + record["name"] = app_proto + + if(server_name == ""): + args = {} + else: + args = { "name": server_name } + record["args"] = args + record["dur"] = get_timestamp(toks[3]) - record["ts"] + + # if we do not have the legend we just return + if csv_fields is None: + return record + + # Otherwise we just add everything we find as a string + if(0): + idx = 0 + for tok in toks: + name = csv_fields[idx] + idx += 1 + record["args"][name] = str(tok) + + return record + +def get_record_dict(filename): + csv_fields = None + records = [] + fin = open(filename, "r"); + for line in fin: + line = line.replace("\n","") + + # Get the legend if present + if line[0] == '#': + csv_fields = [] + line = line.replace("#", "") + toks = line.split(",") + for tok in toks: + csv_fields.append(tok) + continue + + toks = line.split(",") + flow_id = int(toks[0]) + record = get_record(toks, csv_fields) + if record is None: + print("Error while parsing " + line) + continue + + records.append(record) + + json_dict = dict() + json_dict["traceEvents"] = records + + return json_dict + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("ndpi2json ") + sys.exit(0) + + record_dict = get_record_dict(sys.argv[1]) + #print(record_dict) + #json_string = json.dumps(json_dict) + #print(json_string) + + with open(sys.argv[2], 'w') as fp: + json.dump(record_dict, fp) + print("Written " + str(len(record_dict["traceEvents"])) + " records") diff --git a/example/ndpiReader.c b/example/ndpiReader.c index 0c0c2c8..ce027f1 100644 --- a/example/ndpiReader.c +++ b/example/ndpiReader.c @@ -1,7 +1,7 @@ /* * ndpiReader.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -21,7 +21,9 @@ #include "ndpi_config.h" #ifdef linux +#ifndef _GNU_SOURCE #define _GNU_SOURCE +#endif #include #endif #include @@ -52,11 +54,8 @@ #include #include -#ifdef HAVE_JSON_C -#include -#endif - #include "reader_util.h" +#include "intrusion_detection.h" /** Client parameters **/ @@ -68,28 +67,16 @@ static char *results_path = NULL; static char * bpfFilter = NULL; /**< bpf filter */ static char *_protoFilePath = NULL; /**< Protocol file path */ static char *_customCategoryFilePath= NULL; /**< Custom categories file path */ -#ifdef HAVE_JSON_C -static char *_statsFilePath = NULL; /**< Top stats file path */ -static char *_diagnoseFilePath = NULL; /**< Top stats file path */ -static char *_jsonFilePath = NULL; /**< JSON file path */ -static FILE *stats_fp = NULL; /**< for Top Stats JSON file */ -static json_object *jArray_known_flows = NULL, *jArray_unknown_flows = NULL; -static json_object *jArray_topStats = NULL; -#endif -static FILE *csv_fp = NULL; /**< for CSV export */ static u_int8_t live_capture = 0; static u_int8_t undetected_flows_deleted = 0; +FILE *csv_fp = NULL; /**< for CSV export */ /** User preferences **/ -u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0; -u_int8_t verbose = 0, json_flag = 0, enable_joy_stats = 0; +u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0, num_bin_clusters = 0; +u_int8_t verbose = 0, enable_joy_stats = 0; int nDPI_LogLevel = 0; char *_debug_protocols = NULL; -static u_int8_t stats_flag = 0, bpf_filter_flag = 0; -#ifdef HAVE_JSON_C -static u_int8_t file_first_time = 1; -#endif u_int8_t human_readeable_string_len = 5; -u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 10; +u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */; static u_int32_t pcap_analysis_duration = (u_int32_t)-1; static u_int16_t decode_tunnels = 0; static u_int16_t num_loops = 1; @@ -108,6 +95,7 @@ static struct ndpi_detection_module_struct *ndpi_info_mod = NULL; extern u_int32_t max_num_packets_per_flow, max_packet_payload_dissection, max_num_reported_top_payloads; extern u_int16_t min_pattern_len, max_pattern_len; +extern void ndpi_self_check_host_match(); /* Self check function */ struct flow_info { struct ndpi_flow_info *flow; @@ -184,6 +172,7 @@ struct ndpi_packet_trailer { }; static pcap_dumper_t *extcap_dumper = NULL; +static pcap_t *extcap_fifo_h = NULL; static char extcap_buf[16384]; static char *extcap_capture_fifo = NULL; static u_int16_t extcap_packet_filter = (u_int16_t)-1; @@ -230,6 +219,7 @@ FILE *trace = NULL; */ static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle); +#if 0 static void reduceBDbits(uint32_t *bd, unsigned int len) { int mask = 0; int shift = 0; @@ -250,6 +240,7 @@ static void reduceBDbits(uint32_t *bd, unsigned int len) { for(i = 0; i < len; i++) bd[i] = bd[i] >> shift; } +#endif /** * @brief Get flow byte distribution mean and variance @@ -257,7 +248,6 @@ static void reduceBDbits(uint32_t *bd, unsigned int len) { static void flowGetBDMeanandVariance(struct ndpi_flow_info* flow) { FILE *out = results_file ? results_file : stdout; - const uint32_t *array = NULL; uint32_t tmp[256], i; unsigned int num_bytes; @@ -309,6 +299,7 @@ flowGetBDMeanandVariance(struct ndpi_flow_info* flow) { } if(enable_joy_stats) { +#if 0 if(verbose > 1) { reduceBDbits(tmp, 256); array = tmp; @@ -319,15 +310,23 @@ flowGetBDMeanandVariance(struct ndpi_flow_info* flow) { fprintf(out, "%u]", (unsigned char)array[i]); } +#endif /* Output the mean */ if(num_bytes != 0) { double entropy = ndpi_flow_get_byte_count_entropy(array, num_bytes); - fprintf(out, "][byte_dist_mean: %f", mean); - fprintf(out, "][byte_dist_std: %f]", variance); - fprintf(out, "[entropy: %f]", entropy); - fprintf(out, "[total_entropy: %f]", entropy * num_bytes); + if(csv_fp) { + fprintf(csv_fp, ",%.3f,%.3f,%.3f,%.3f", mean, variance, entropy, entropy * num_bytes); + } else { + fprintf(out, "[byte_dist_mean: %f", mean); + fprintf(out, "][byte_dist_std: %f]", variance); + fprintf(out, "[entropy: %f]", entropy); + fprintf(out, "[total_entropy: %f]", entropy * num_bytes); + } + } else { + if(csv_fp) + fprintf(csv_fp, ",%.3f,%.3f,%.3f,%.3f", 0.0, 0.0, 0.0, 0.0); } } } @@ -336,13 +335,13 @@ flowGetBDMeanandVariance(struct ndpi_flow_info* flow) { * @brief Print help instructions */ static void help(u_int long_help) { - printf("Welcome to nDPI %s\n\n", ndpi_revision()); + printf("Welcome to nDPI %s (API rev.%u)\n\n", ndpi_revision(), ndpi_get_api_version()); printf("ndpiReader " #ifndef USE_DPDK "-i " #endif - "[-f ][-s ][-m ]\n" + "[-f ][-s ][-m ][-b ]\n" " [-p ][-l [-q][-d][-J][-h][-e ][-t][-v ]\n" " [-n ][-w ][-c ][-C ][-j ][-x ]\n" " [-T ][-U ]\n\n" @@ -356,7 +355,7 @@ static void help(u_int long_help) { " -l | Number of detection loops (test only)\n" " -n | Number of threads. Default: number of interfaces in -i.\n" " | Ignored with pcap files.\n" - " -j | Specify a file to write the content of packets in .json format\n" + " -b | Number of bin clusters\n" #ifdef linux " -g | Thread affinity mask (one core id per thread)\n" #endif @@ -385,10 +384,10 @@ static void help(u_int long_help) { " | 3 = port stats\n" " -V <1-4> | nDPI logging level\n" " | 1 - trace, 2 - debug, 3 - full debug\n" - " | >3 - full debug + dbg_proto = all\n" - " -b | Specify a file to write port based diagnose statistics\n" - " -x | Produce bpf filters for specified diagnose file. Use\n" - " | this option only for .json files generated with -b flag.\n" + " | >3 - full debug + log enabled for all protocols (i.e. '-u all')\n" + " -u all|proto|num[,...] | Enable logging only for such protocol(s)\n" + " | If this flag is present multiple times (directly, or via '-V'),\n" + " | only the last instance will be considered\n" " -T | Max number of TCP processed packets before giving up [default: %u]\n" " -U | Max number of UDP processed packets before giving up [default: %u]\n" , @@ -407,22 +406,22 @@ static void help(u_int long_help) { " --extcap-capture-filter\n" " --fifo \n" " --debug\n" - " --dbg-proto proto|num[,...]\n" ); #endif if(long_help) { NDPI_PROTOCOL_BITMASK all; - + printf("\n\nnDPI supported protocols:\n"); printf("%3s %-22s %-8s %-12s %s\n", "Id", "Protocol", "Layer_4", "Breed", "Category"); num_threads = 1; NDPI_BITMASK_SET_ALL(all); ndpi_set_protocol_detection_bitmask2(ndpi_info_mod, &all); - + ndpi_dump_protocols(ndpi_info_mod); } + exit(!long_help); } @@ -437,8 +436,6 @@ static struct option longopts[] = { { "capture", no_argument, NULL, '5'}, { "extcap-capture-filter", required_argument, NULL, '6'}, { "fifo", required_argument, NULL, '7'}, - { "debug", no_argument, NULL, '8'}, - { "dbg-proto", required_argument, NULL, 257}, { "ndpi-proto-filter", required_argument, NULL, '9'}, /* ndpiReader options */ @@ -455,10 +452,11 @@ static struct option longopts[] = { { "capture-duration", required_argument, NULL, 's'}, { "decode-tunnels", no_argument, NULL, 't'}, { "revision", no_argument, NULL, 'r'}, - { "verbose", no_argument, NULL, 'v'}, - { "version", no_argument, NULL, 'V'}, + { "verbose", required_argument, NULL, 'v'}, + { "version", no_argument, NULL, 'r'}, + { "ndpi-log-level", required_argument, NULL, 'V'}, + { "dbg-proto", required_argument, NULL, 'u'}, { "help", no_argument, NULL, 'h'}, - { "json", required_argument, NULL, 'j'}, { "joy", required_argument, NULL, 'J'}, { "payload-analysis", required_argument, NULL, 'P'}, { "result-path", required_argument, NULL, 'w'}, @@ -534,7 +532,7 @@ void extcap_config() { printf("arg {number=%d}{call=-i}{display=Pcap File to Analyze}{type=fileselect}" "{tooltip=The pcap file to analyze (if the interface is unspecified)}\n", argidx++); - protos = (struct ndpi_proto_sorter*)malloc(sizeof(struct ndpi_proto_sorter) * ndpi_num_supported_protocols); + protos = (struct ndpi_proto_sorter*)ndpi_malloc(sizeof(struct ndpi_proto_sorter) * ndpi_num_supported_protocols); if(!protos) exit(0); for(i=0; i<(int) ndpi_num_supported_protocols; i++) { @@ -553,7 +551,7 @@ void extcap_config() { printf("value {arg=%d}{value=%d}{display=%s (%d)}\n", argidx, protos[i].id, protos[i].name, protos[i].id); - free(protos); + ndpi_free(protos); exit(0); } @@ -565,7 +563,16 @@ void extcap_capture() { if(trace) fprintf(trace, " #### %s #### \n", __FUNCTION__); #endif - if((extcap_dumper = pcap_dump_open(pcap_open_dead(DLT_EN10MB, 16384 /* MTU */), + if((extcap_fifo_h = pcap_open_dead(DLT_EN10MB, 16384 /* MTU */)) == NULL) { + fprintf(stderr, "Error pcap_open_dead"); + +#ifdef DEBUG_TRACE + if(trace) fprintf(trace, "Error pcap_open_dead\n"); +#endif + return; + } + + if((extcap_dumper = pcap_dump_open(extcap_fifo_h, extcap_capture_fifo)) == NULL) { fprintf(stderr, "Unable to open the pcap dumper on %s", extcap_capture_fifo); @@ -586,10 +593,11 @@ void extcap_capture() { void printCSVHeader() { if(!csv_fp) return; - fprintf(csv_fp, "#flow_id,protocol,first_seen,last_seen,src_ip,src_port,dst_ip,dst_port,ndpi_proto_num,ndpi_proto,"); - fprintf(csv_fp, "src2dst_packets,src2dst_bytes,dst2src_packets,dst2src_bytes,"); - fprintf(csv_fp, "data_ratio,str_data_ratio,"); - + fprintf(csv_fp, "#flow_id,protocol,first_seen,last_seen,duration,src_ip,src_port,dst_ip,dst_port,ndpi_proto_num,ndpi_proto,server_name,"); + fprintf(csv_fp, "benign_score,dos_slow_score,dos_goldeneye_score,dos_hulk_score,ddos_score,hearthbleed_score,ftp_patator_score,ssh_patator_score,infiltration_score,"); + fprintf(csv_fp, "c_to_s_pkts,c_to_s_bytes,c_to_s_goodput_bytes,s_to_c_pkts,s_to_c_bytes,s_to_c_goodput_bytes,"); + fprintf(csv_fp, "data_ratio,str_data_ratio,c_to_s_goodput_ratio,s_to_c_goodput_ratio,"); + /* IAT (Inter Arrival Time) */ fprintf(csv_fp, "iat_flow_min,iat_flow_avg,iat_flow_max,iat_flow_stddev,"); fprintf(csv_fp, "iat_c_to_s_min,iat_c_to_s_avg,iat_c_to_s_max,iat_c_to_s_stddev,"); @@ -599,11 +607,31 @@ void printCSVHeader() { fprintf(csv_fp, "pktlen_c_to_s_min,pktlen_c_to_s_avg,pktlen_c_to_s_max,pktlen_c_to_s_stddev,"); fprintf(csv_fp, "pktlen_s_to_c_min,pktlen_s_to_c_avg,pktlen_s_to_c_max,pktlen_s_to_c_stddev,"); + /* TCP flags */ + fprintf(csv_fp, "cwr,ece,urg,ack,psh,rst,syn,fin,"); + + fprintf(csv_fp, "c_to_s_cwr,c_to_s_ece,c_to_s_urg,c_to_s_ack,c_to_s_psh,c_to_s_rst,c_to_s_syn,c_to_s_fin,"); + + fprintf(csv_fp, "s_to_c_cwr,s_to_c_ece,s_to_c_urg,s_to_c_ack,s_to_c_psh,s_to_c_rst,s_to_c_syn,s_to_c_fin,"); + + /* TCP window */ + fprintf(csv_fp, "c_to_s_init_win,s_to_c_init_win,"); + /* Flow info */ fprintf(csv_fp, "client_info,server_info,"); fprintf(csv_fp, "tls_version,ja3c,tls_client_unsafe,"); - fprintf(csv_fp, "tls_server_info,ja3s,tls_server_unsafe,"); - fprintf(csv_fp, "ssh_client_hassh,ssh_server_hassh"); + fprintf(csv_fp, "ja3s,tls_server_unsafe,"); + fprintf(csv_fp, "tls_alpn,tls_supported_versions,"); +#if 0 + fprintf(csv_fp, "tls_issuerDN,tls_subjectDN,"); +#endif + fprintf(csv_fp, "ssh_client_hassh,ssh_server_hassh,flow_info,plen_bins"); + + /* Joy */ + if(enable_joy_stats) { + fprintf(csv_fp, ",byte_dist_mean,byte_dist_std,entropy,total_entropy"); + } + fprintf(csv_fp, "\n"); } @@ -637,13 +665,18 @@ static void parseOptions(int argc, char **argv) { } #endif - while((opt = getopt_long(argc, argv, "e:c:C:df:g:i:hp:P:l:s:tv:V:n:j:Jrp:w:q0123:456:7:89:m:b:x:T:U:", + while((opt = getopt_long(argc, argv, "b:e:c:C:df:g:i:hp:P:l:s:tv:V:u:n:Jrp:w:q0123:456:7:89:m:T:U:", longopts, &option_idx)) != EOF) { #ifdef DEBUG_TRACE if(trace) fprintf(trace, " #### -%c [%s] #### \n", opt, optarg ? optarg : ""); #endif switch (opt) { + case 'b': + if((num_bin_clusters = atoi(optarg)) > 32) + num_bin_clusters = 32; + break; + case 'd': enable_protocol_guess = 0; break; @@ -657,28 +690,10 @@ static void parseOptions(int argc, char **argv) { _pcap_file[0] = optarg; break; - case 'b': -#ifndef HAVE_JSON_C - printf("WARNING: this copy of ndpiReader has been compiled without JSON-C: json export disabled\n"); -#else - _statsFilePath = optarg; - stats_flag = 1; -#endif - break; - case 'm': pcap_analysis_duration = atol(optarg); break; - case 'x': -#ifndef HAVE_JSON_C - printf("WARNING: this copy of ndpiReader has been compiled without JSON-C: json export disabled\n"); -#else - _diagnoseFilePath = optarg; - bpf_filter_flag = 1; -#endif - break; - case 'f': case '6': bpfFilter = optarg; @@ -707,8 +722,6 @@ static void parseOptions(int argc, char **argv) { case 'C': if((csv_fp = fopen(optarg, "w")) == NULL) printf("Unable to write on CSV file %s\n", optarg); - else - printCSVHeader(); break; case 's': @@ -730,13 +743,19 @@ static void parseOptions(int argc, char **argv) { case 'V': nDPI_LogLevel = atoi(optarg); - if(nDPI_LogLevel < 0) nDPI_LogLevel = 0; - if(nDPI_LogLevel > 3) { - nDPI_LogLevel = 3; - _debug_protocols = strdup("all"); + if(nDPI_LogLevel < NDPI_LOG_ERROR) nDPI_LogLevel = NDPI_LOG_ERROR; + if(nDPI_LogLevel > NDPI_LOG_DEBUG_EXTRA) { + nDPI_LogLevel = NDPI_LOG_DEBUG_EXTRA; + ndpi_free(_debug_protocols); + _debug_protocols = strdup("all"); } break; + case 'u': + ndpi_free(_debug_protocols); + _debug_protocols = strdup(optarg); + break; + case 'h': help(1); break; @@ -772,15 +791,6 @@ static void parseOptions(int argc, char **argv) { } break; - case 'j': -#ifndef HAVE_JSON_C - printf("WARNING: this copy of ndpiReader has been compiled without json-c: JSON export disabled\n"); -#else - _jsonFilePath = optarg; - json_flag = 1; -#endif - break; - case 'w': results_path = strdup(optarg); if((results_file = fopen(results_path, "w")) == NULL) { @@ -819,20 +829,11 @@ static void parseOptions(int argc, char **argv) { extcap_capture_fifo = strdup(optarg); break; - case '8': - nDPI_LogLevel = NDPI_LOG_DEBUG_EXTRA; - _debug_protocols = strdup("all"); - break; - case '9': extcap_packet_filter = ndpi_get_proto_by_name(ndpi_info_mod, optarg); if(extcap_packet_filter == NDPI_PROTOCOL_UNKNOWN) extcap_packet_filter = atoi(optarg); break; - case 257: - _debug_protocols = strdup(optarg); - break; - case 'T': max_num_tcp_dissected_pkts = atoi(optarg); if(max_num_tcp_dissected_pkts < 3) max_num_tcp_dissected_pkts = 3; @@ -849,30 +850,30 @@ static void parseOptions(int argc, char **argv) { } } + if(csv_fp) + printCSVHeader(); + #ifndef USE_DPDK - if(!bpf_filter_flag) { - if(do_capture) { - quiet_mode = 1; - extcap_capture(); - } + if(do_capture) { + quiet_mode = 1; + extcap_capture(); + } - // check parameters - if(!bpf_filter_flag && (_pcap_file[0] == NULL || strcmp(_pcap_file[0], "") == 0)) { - help(0); - } + if(_pcap_file[0] == NULL) + help(0); - if(strchr(_pcap_file[0], ',')) { /* multiple ingress interfaces */ - num_threads = 0; /* setting number of threads = number of interfaces */ - __pcap_file = strtok(_pcap_file[0], ","); - while(__pcap_file != NULL && num_threads < MAX_NUM_READER_THREADS) { - _pcap_file[num_threads++] = __pcap_file; - __pcap_file = strtok(NULL, ","); - } - } else { - if(num_threads > MAX_NUM_READER_THREADS) num_threads = MAX_NUM_READER_THREADS; - for(thread_id = 1; thread_id < num_threads; thread_id++) - _pcap_file[thread_id] = _pcap_file[0]; + if(strchr(_pcap_file[0], ',')) { /* multiple ingress interfaces */ + num_threads = 0; /* setting number of threads = number of interfaces */ + __pcap_file = strtok(_pcap_file[0], ","); + while(__pcap_file != NULL && num_threads < MAX_NUM_READER_THREADS) { + _pcap_file[num_threads++] = __pcap_file; + __pcap_file = strtok(NULL, ","); } + } else { + if(num_threads > MAX_NUM_READER_THREADS) num_threads = MAX_NUM_READER_THREADS; + for(thread_id = 1; thread_id < num_threads; thread_id++) + _pcap_file[thread_id] = _pcap_file[0]; + } #ifdef linux for(thread_id = 0; thread_id < num_threads; thread_id++) @@ -887,7 +888,6 @@ static void parseOptions(int argc, char **argv) { } } #endif - } #endif #ifdef DEBUG_TRACE @@ -998,269 +998,357 @@ static char* is_unsafe_cipher(ndpi_cipher_weakness c) { /* ********************************** */ +void print_bin(FILE *fout, const char *label, struct ndpi_bin *b) { + u_int8_t i; + FILE *out = results_file ? results_file : stdout; + const char *sep = label ? "," : ";"; + + ndpi_normalize_bin(b); + + if(label) fprintf(fout, "[%s: ", label); + + for(i=0; inum_bins; i++) { + switch(b->family) { + case ndpi_bin_family8: + fprintf(fout, "%s%u", (i > 0) ? sep : "", b->u.bins8[i]); + break; + case ndpi_bin_family16: + fprintf(fout, "%s%u", (i > 0) ? sep : "", b->u.bins16[i]); + break; + case ndpi_bin_family32: + fprintf(fout, "%s%u", (i > 0) ? sep : "", b->u.bins32[i]); + break; + } + } + + if(label) fprintf(fout, "]"); +} + +/* ********************************** */ + /** * @brief Print the flow */ static void printFlow(u_int16_t id, struct ndpi_flow_info *flow, u_int16_t thread_id) { -#ifdef HAVE_JSON_C - json_object *jObj; -#endif FILE *out = results_file ? results_file : stdout; u_int8_t known_tls; - + char buf[32], buf1[64]; + u_int i; + + double dos_ge_score; + double dos_slow_score; + double dos_hulk_score; + double ddos_score; + + double hearthbleed_score; + + double ftp_patator_score; + double ssh_patator_score; + + double inf_score; + if(csv_fp != NULL) { - char buf[32]; float data_ratio = ndpi_data_ratio(flow->src2dst_bytes, flow->dst2src_bytes); - float f = (float)flow->first_seen, l = (float)flow->last_seen; - + double f = (double)flow->first_seen_ms, l = (double)flow->last_seen_ms; + /* PLEASE KEEP IN SYNC WITH printCSVHeader() */ + dos_ge_score = Dos_goldeneye_score(flow); + + dos_slow_score = Dos_slow_score(flow); + dos_hulk_score = Dos_hulk_score(flow); + ddos_score = Ddos_score(flow); + + hearthbleed_score = Hearthbleed_score(flow); - fprintf(csv_fp, "%u,%u,%.3f,%.3f,%s,%u,%s,%u,", + ftp_patator_score = Ftp_patator_score(flow); + ssh_patator_score = Ssh_patator_score(flow); + + inf_score = Infiltration_score(flow); + + double benign_score = dos_ge_score < 1 && dos_slow_score < 1 && \ + dos_hulk_score < 1 && ddos_score < 1 && hearthbleed_score < 1 && \ + ftp_patator_score < 1 && ssh_patator_score < 1 && inf_score < 1 ? 1.1 : 0; + + fprintf(csv_fp, "%u,%u,%.3f,%.3f,%.3f,%s,%u,%s,%u,", flow->flow_id, flow->protocol, f/1000.0, l/1000.0, + (l-f)/1000.0, flow->src_name, ntohs(flow->src_port), flow->dst_name, ntohs(flow->dst_port) - ); + ); - fprintf(csv_fp, "%u.%u,%s,", - flow->detected_protocol.master_protocol, flow->detected_protocol.app_protocol, - ndpi_protocol2name(ndpi_thread_info[thread_id].workflow->ndpi_struct, - flow->detected_protocol, buf, sizeof(buf))); + fprintf(csv_fp, "%s,", + ndpi_protocol2id(ndpi_thread_info[thread_id].workflow->ndpi_struct, + flow->detected_protocol, buf, sizeof(buf))); - fprintf(csv_fp, "%u,%llu,", flow->src2dst_packets, (long long unsigned int) flow->src2dst_bytes); - fprintf(csv_fp, "%u,%llu,", flow->dst2src_packets, (long long unsigned int) flow->dst2src_bytes); + fprintf(csv_fp, "%s,%s,", + ndpi_protocol2name(ndpi_thread_info[thread_id].workflow->ndpi_struct, + flow->detected_protocol, buf, sizeof(buf)), + flow->host_server_name); + + fprintf(csv_fp, "%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,%.4lf,", \ + benign_score, dos_slow_score, dos_ge_score, dos_hulk_score, \ + ddos_score, hearthbleed_score, ftp_patator_score, \ + ssh_patator_score, inf_score); + + fprintf(csv_fp, "%u,%llu,%llu,", flow->src2dst_packets, + (long long unsigned int) flow->src2dst_bytes, (long long unsigned int) flow->src2dst_goodput_bytes); + fprintf(csv_fp, "%u,%llu,%llu,", flow->dst2src_packets, + (long long unsigned int) flow->dst2src_bytes, (long long unsigned int) flow->dst2src_goodput_bytes); fprintf(csv_fp, "%.3f,%s,", data_ratio, ndpi_data_ratio2str(data_ratio)); - + fprintf(csv_fp, "%.1f,%.1f,", 100.0*((float)flow->src2dst_goodput_bytes / (float)(flow->src2dst_bytes+1)), + 100.0*((float)flow->dst2src_goodput_bytes / (float)(flow->dst2src_bytes+1))); + /* IAT (Inter Arrival Time) */ fprintf(csv_fp, "%u,%.1f,%u,%.1f,", ndpi_data_min(flow->iat_flow), ndpi_data_average(flow->iat_flow), ndpi_data_max(flow->iat_flow), ndpi_data_stddev(flow->iat_flow)); fprintf(csv_fp, "%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,", - ndpi_data_min(flow->iat_c_to_s), ndpi_data_average(flow->iat_c_to_s), ndpi_data_max(flow->iat_c_to_s), ndpi_data_stddev(flow->iat_c_to_s), - ndpi_data_min(flow->iat_s_to_c), ndpi_data_average(flow->iat_s_to_c), ndpi_data_max(flow->iat_s_to_c), ndpi_data_stddev(flow->iat_s_to_c)); + ndpi_data_min(flow->iat_c_to_s), ndpi_data_average(flow->iat_c_to_s), ndpi_data_max(flow->iat_c_to_s), ndpi_data_stddev(flow->iat_c_to_s), + ndpi_data_min(flow->iat_s_to_c), ndpi_data_average(flow->iat_s_to_c), ndpi_data_max(flow->iat_s_to_c), ndpi_data_stddev(flow->iat_s_to_c)); /* Packet Length */ fprintf(csv_fp, "%u,%.1f,%u,%.1f,%u,%.1f,%u,%.1f,", - ndpi_data_min(flow->pktlen_c_to_s), ndpi_data_average(flow->pktlen_c_to_s), ndpi_data_max(flow->pktlen_c_to_s), ndpi_data_stddev(flow->pktlen_c_to_s), - ndpi_data_min(flow->pktlen_s_to_c), ndpi_data_average(flow->pktlen_s_to_c), ndpi_data_max(flow->pktlen_s_to_c), ndpi_data_stddev(flow->pktlen_s_to_c)); + ndpi_data_min(flow->pktlen_c_to_s), ndpi_data_average(flow->pktlen_c_to_s), ndpi_data_max(flow->pktlen_c_to_s), ndpi_data_stddev(flow->pktlen_c_to_s), + ndpi_data_min(flow->pktlen_s_to_c), ndpi_data_average(flow->pktlen_s_to_c), ndpi_data_max(flow->pktlen_s_to_c), ndpi_data_stddev(flow->pktlen_s_to_c)); + + /* TCP flags */ + fprintf(csv_fp, "%d,%d,%d,%d,%d,%d,%d,%d,", flow->cwr_count, flow->ece_count, flow->urg_count, flow->ack_count, flow->psh_count, flow->rst_count, flow->syn_count, flow->fin_count); + + fprintf(csv_fp, "%d,%d,%d,%d,%d,%d,%d,%d,", flow->src2dst_cwr_count, flow->src2dst_ece_count, flow->src2dst_urg_count, flow->src2dst_ack_count, flow->src2dst_psh_count, flow->src2dst_rst_count, flow->src2dst_syn_count, flow->src2dst_fin_count); + + fprintf(csv_fp, "%d,%d,%d,%d,%d,%d,%d,%d,", flow->dst2src_cwr_count, flow->ece_count, flow->urg_count, flow->ack_count, flow->psh_count, flow->rst_count, flow->syn_count, flow->fin_count); + + /* TCP window */ + fprintf(csv_fp, "%u,%u,", flow->c_to_s_init_win, flow->s_to_c_init_win); fprintf(csv_fp, "%s,%s,", - (flow->ssh_tls.client_info[0] != '\0') ? flow->ssh_tls.client_info : "", + (flow->ssh_tls.client_requested_server_name[0] != '\0') ? flow->ssh_tls.client_requested_server_name : "", (flow->ssh_tls.server_info[0] != '\0') ? flow->ssh_tls.server_info : ""); - - fprintf(csv_fp, "%s,%s,%s,", - (flow->ssh_tls.ssl_version != 0) ? ndpi_ssl_version2str(flow->ssh_tls.ssl_version, &known_tls) : "", + + fprintf(csv_fp, "%s,%s,%s,%s,%s,", + (flow->ssh_tls.ssl_version != 0) ? ndpi_ssl_version2str(flow->ndpi_flow, flow->ssh_tls.ssl_version, &known_tls) : "0", (flow->ssh_tls.ja3_client[0] != '\0') ? flow->ssh_tls.ja3_client : "", - (flow->ssh_tls.ja3_client[0] != '\0') ? is_unsafe_cipher(flow->ssh_tls.client_unsafe_cipher) : ""); + (flow->ssh_tls.ja3_client[0] != '\0') ? is_unsafe_cipher(flow->ssh_tls.client_unsafe_cipher) : "0", + (flow->ssh_tls.ja3_server[0] != '\0') ? flow->ssh_tls.ja3_server : "", + (flow->ssh_tls.ja3_server[0] != '\0') ? is_unsafe_cipher(flow->ssh_tls.server_unsafe_cipher) : "0"); fprintf(csv_fp, "%s,%s,", - (flow->ssh_tls.ja3_server[0] != '\0') ? flow->ssh_tls.ja3_server : "", - (flow->ssh_tls.ja3_server[0] != '\0') ? is_unsafe_cipher(flow->ssh_tls.server_unsafe_cipher) : ""); + flow->ssh_tls.tls_alpn ? flow->ssh_tls.tls_alpn : "", + flow->ssh_tls.tls_supported_versions ? flow->ssh_tls.tls_supported_versions : "" + ); + +#if 0 + fprintf(csv_fp, "%s,%s,", + flow->ssh_tls.tls_issuerDN ? flow->ssh_tls.tls_issuerDN : "", + flow->ssh_tls.tls_subjectDN ? flow->ssh_tls.tls_subjectDN : "" + ); +#endif fprintf(csv_fp, "%s,%s", (flow->ssh_tls.client_hassh[0] != '\0') ? flow->ssh_tls.client_hassh : "", (flow->ssh_tls.server_hassh[0] != '\0') ? flow->ssh_tls.server_hassh : "" ); - - fprintf(csv_fp, "\n"); + + fprintf(csv_fp, ",%s,", flow->info); + +#ifndef DIRECTION_BINS + print_bin(csv_fp, NULL, &flow->payload_len_bin); +#endif } - if((verbose != 1) && (verbose != 2)) + if((verbose != 1) && (verbose != 2)) { + if(csv_fp && enable_joy_stats) { + flowGetBDMeanandVariance(flow); + } + + if(csv_fp) + fprintf(csv_fp, "\n"); return; + } - if(!json_flag) { - u_int i; - - fprintf(out, "\t%u", id); + if(csv_fp || (verbose > 1)) { +#if 1 + fprintf(out, "\t%u", id); +#else + fprintf(out, "\t%u(%u)", id, flow->flow_id); +#endif - fprintf(out, "\t%s ", ipProto2Name(flow->protocol)); + fprintf(out, "\t%s ", ipProto2Name(flow->protocol)); - fprintf(out, "%s%s%s:%u %s %s%s%s:%u ", - (flow->ip_version == 6) ? "[" : "", - flow->src_name, (flow->ip_version == 6) ? "]" : "", ntohs(flow->src_port), - flow->bidirectional ? "<->" : "->", - (flow->ip_version == 6) ? "[" : "", - flow->dst_name, (flow->ip_version == 6) ? "]" : "", ntohs(flow->dst_port) - ); + fprintf(out, "%s%s%s:%u %s %s%s%s:%u ", + (flow->ip_version == 6) ? "[" : "", + flow->src_name, (flow->ip_version == 6) ? "]" : "", ntohs(flow->src_port), + flow->bidirectional ? "<->" : "->", + (flow->ip_version == 6) ? "[" : "", + flow->dst_name, (flow->ip_version == 6) ? "]" : "", ntohs(flow->dst_port) + ); - if(flow->vlan_id > 0) fprintf(out, "[VLAN: %u]", flow->vlan_id); - if(enable_payload_analyzer) fprintf(out, "[flowId: %u]", flow->flow_id); + if(flow->vlan_id > 0) fprintf(out, "[VLAN: %u]", flow->vlan_id); + if(enable_payload_analyzer) fprintf(out, "[flowId: %u]", flow->flow_id); + } - if(enable_joy_stats) { - /* Print entropy values for monitored flows. */ - flowGetBDMeanandVariance(flow); - fflush(out); - fprintf(out, "[score: %.4f]", flow->entropy.score); - } + if(enable_joy_stats) { + /* Print entropy values for monitored flows. */ + flowGetBDMeanandVariance(flow); + fflush(out); + fprintf(out, "[score: %.4f]", flow->entropy.score); + } - if(flow->detected_protocol.master_protocol) { - char buf[64]; + if(csv_fp) fprintf(csv_fp, "\n"); - fprintf(out, "[proto: %u.%u/%s]", - flow->detected_protocol.master_protocol, flow->detected_protocol.app_protocol, - ndpi_protocol2name(ndpi_thread_info[thread_id].workflow->ndpi_struct, - flow->detected_protocol, buf, sizeof(buf))); - } else - fprintf(out, "[proto: %u/%s]", - flow->detected_protocol.app_protocol, - ndpi_get_proto_name(ndpi_thread_info[thread_id].workflow->ndpi_struct, flow->detected_protocol.app_protocol)); - - if(flow->detected_protocol.category != 0) - fprintf(out, "[cat: %s/%u]", - ndpi_category_get_name(ndpi_thread_info[thread_id].workflow->ndpi_struct, - flow->detected_protocol.category), - (unsigned int)flow->detected_protocol.category); - - fprintf(out, "[%u pkts/%llu bytes ", flow->src2dst_packets, (long long unsigned int) flow->src2dst_bytes); - fprintf(out, "%s %u pkts/%llu bytes]", - (flow->dst2src_packets > 0) ? "<->" : "->", - flow->dst2src_packets, (long long unsigned int) flow->dst2src_bytes); - - if(flow->host_server_name[0] != '\0') fprintf(out, "[Host: %s]", flow->host_server_name); - - if(flow->info[0] != '\0') fprintf(out, "[%s]", flow->info); - - if((flow->src2dst_packets+flow->dst2src_packets) > 5) { - if(flow->iat_c_to_s && flow->iat_s_to_c) { - float data_ratio = ndpi_data_ratio(flow->src2dst_bytes, flow->dst2src_bytes); - - fprintf(out, "[bytes ratio: %.3f (%s)]", data_ratio, ndpi_data_ratio2str(data_ratio)); - - /* IAT (Inter Arrival Time) */ - fprintf(out, "[IAT c2s/s2c min/avg/max/stddev: %u/%u %.1f/%.1f %u/%u %.1f/%.1f]", - ndpi_data_min(flow->iat_c_to_s), ndpi_data_min(flow->iat_s_to_c), - (float)ndpi_data_average(flow->iat_c_to_s), (float)ndpi_data_average(flow->iat_s_to_c), - ndpi_data_max(flow->iat_c_to_s), ndpi_data_max(flow->iat_s_to_c), - (float)ndpi_data_stddev(flow->iat_c_to_s), (float)ndpi_data_stddev(flow->iat_s_to_c)); - - /* Packet Length */ - fprintf(out, "[Pkt Len c2s/s2c min/avg/max/stddev: %u/%u %.1f/%.1f %u/%u %.1f/%.1f]", - ndpi_data_min(flow->pktlen_c_to_s), ndpi_data_min(flow->pktlen_s_to_c), - ndpi_data_average(flow->pktlen_c_to_s), ndpi_data_average(flow->pktlen_s_to_c), - ndpi_data_max(flow->pktlen_c_to_s), ndpi_data_max(flow->pktlen_s_to_c), - ndpi_data_stddev(flow->pktlen_c_to_s), ndpi_data_stddev(flow->pktlen_s_to_c)); - } - } + fprintf(out, "[proto: "); + if(flow->tunnel_type != ndpi_no_tunnel) + fprintf(out, "%s:", ndpi_tunnel2str(flow->tunnel_type)); - if(flow->http.url[0] != '\0') - fprintf(out, "[URL: %s][StatusCode: %u]", - flow->http.url, flow->http.response_status_code); - - if(flow->ssh_tls.ssl_version != 0) fprintf(out, "[%s]", ndpi_ssl_version2str(flow->ssh_tls.ssl_version, &known_tls)); - if(flow->ssh_tls.client_info[0] != '\0') fprintf(out, "[Client: %s]", flow->ssh_tls.client_info); - if(flow->ssh_tls.client_hassh[0] != '\0') fprintf(out, "[HASSH-C: %s]", flow->ssh_tls.client_hassh); - - if(flow->ssh_tls.ja3_client[0] != '\0') fprintf(out, "[JA3C: %s%s]", flow->ssh_tls.ja3_client, - print_cipher(flow->ssh_tls.client_unsafe_cipher)); - - if(flow->ssh_tls.server_info[0] != '\0') fprintf(out, "[Server: %s]", flow->ssh_tls.server_info); - if(flow->ssh_tls.server_hassh[0] != '\0') fprintf(out, "[HASSH-S: %s]", flow->ssh_tls.server_hassh); - - if(flow->ssh_tls.ja3_server[0] != '\0') fprintf(out, "[JA3S: %s%s]", flow->ssh_tls.ja3_server, - print_cipher(flow->ssh_tls.server_unsafe_cipher)); - if(flow->ssh_tls.server_organization[0] != '\0') fprintf(out, "[Organization: %s]", flow->ssh_tls.server_organization); - - if((flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS) - || (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_TLS)) { - if((flow->ssh_tls.sha1_cert_fingerprint[0] == 0) - && (flow->ssh_tls.sha1_cert_fingerprint[1] == 0) - && (flow->ssh_tls.sha1_cert_fingerprint[2] == 0)) - ; /* Looks empty */ - else { - fprintf(out, "[Certificate SHA-1: "); - for(i=0; i<20; i++) - fprintf(out, "%s%02X", (i > 0) ? ":" : "", - flow->ssh_tls.sha1_cert_fingerprint[i] & 0xFF); - fprintf(out, "]"); - } - } - - if(flow->ssh_tls.notBefore && flow->ssh_tls.notAfter) { - char notBefore[32], notAfter[32]; - struct tm a, b; - struct tm *before = gmtime_r(&flow->ssh_tls.notBefore, &a); - struct tm *after = gmtime_r(&flow->ssh_tls.notAfter, &b); + fprintf(out, "%s/%s]", + ndpi_protocol2id(ndpi_thread_info[thread_id].workflow->ndpi_struct, + flow->detected_protocol, buf, sizeof(buf)), + ndpi_protocol2name(ndpi_thread_info[thread_id].workflow->ndpi_struct, + flow->detected_protocol, buf1, sizeof(buf1))); - strftime(notBefore, sizeof(notBefore), "%F %T", before); - strftime(notAfter, sizeof(notAfter), "%F %T", after); + if(flow->detected_protocol.category != 0) + fprintf(out, "[cat: %s/%u]", + ndpi_category_get_name(ndpi_thread_info[thread_id].workflow->ndpi_struct, + flow->detected_protocol.category), + (unsigned int)flow->detected_protocol.category); - fprintf(out, "[Validity: %s - %s]", notBefore, notAfter); - } + fprintf(out, "[%u pkts/%llu bytes ", flow->src2dst_packets, (long long unsigned int) flow->src2dst_bytes); + fprintf(out, "%s %u pkts/%llu bytes]", + (flow->dst2src_packets > 0) ? "<->" : "->", + flow->dst2src_packets, (long long unsigned int) flow->dst2src_bytes); - if(flow->ssh_tls.server_cipher != '\0') fprintf(out, "[Cipher: %s]", ndpi_cipher2str(flow->ssh_tls.server_cipher)); - if(flow->bittorent_hash[0] != '\0') fprintf(out, "[BT Hash: %s]", flow->bittorent_hash); - if(flow->dhcp_fingerprint[0] != '\0') fprintf(out, "[DHCP Fingerprint: %s]", flow->dhcp_fingerprint); + fprintf(out, "[Goodput ratio: %.0f/%.0f]", + 100.0*((float)flow->src2dst_goodput_bytes / (float)(flow->src2dst_bytes+1)), + 100.0*((float)flow->dst2src_goodput_bytes / (float)(flow->dst2src_bytes+1))); - if(flow->has_human_readeable_strings) fprintf(out, "[PLAIN TEXT (%s)]", flow->human_readeable_string_buffer); + if(flow->last_seen_ms > flow->first_seen_ms) + fprintf(out, "[%.2f sec]", ((float)(flow->last_seen_ms - flow->first_seen_ms))/(float)1000); + else + fprintf(out, "[< 1 sec]"); - fprintf(out, "\n"); - } else { -#ifdef HAVE_JSON_C - jObj = json_object_new_object(); + if(flow->telnet.username[0] != '\0') fprintf(out, "[Username: %s]", flow->telnet.username); + if(flow->telnet.password[0] != '\0') fprintf(out, "[Password: %s]", flow->telnet.password); + if(flow->host_server_name[0] != '\0') fprintf(out, "[Host: %s]", flow->host_server_name); - json_object_object_add(jObj,"protocol",json_object_new_string(ipProto2Name(flow->protocol))); - json_object_object_add(jObj,"host_a.name",json_object_new_string(flow->src_name)); - json_object_object_add(jObj,"host_a.port",json_object_new_int(ntohs(flow->src_port))); - json_object_object_add(jObj,"host_b.name",json_object_new_string(flow->dst_name)); - json_object_object_add(jObj,"host_b.port",json_object_new_int(ntohs(flow->dst_port))); + if(flow->info[0] != '\0') fprintf(out, "[%s]", flow->info); + if(flow->flow_extra_info[0] != '\0') fprintf(out, "[%s]", flow->flow_extra_info); - if(flow->detected_protocol.master_protocol) - json_object_object_add(jObj,"detected.master_protocol", - json_object_new_int(flow->detected_protocol.master_protocol)); + if((flow->src2dst_packets+flow->dst2src_packets) > 5) { + if(flow->iat_c_to_s && flow->iat_s_to_c) { + float data_ratio = ndpi_data_ratio(flow->src2dst_bytes, flow->dst2src_bytes); - json_object_object_add(jObj,"detected.app_protocol", - json_object_new_int(flow->detected_protocol.app_protocol)); + fprintf(out, "[bytes ratio: %.3f (%s)]", data_ratio, ndpi_data_ratio2str(data_ratio)); - if(flow->detected_protocol.master_protocol) { - char tmp[256]; + /* IAT (Inter Arrival Time) */ + fprintf(out, "[IAT c2s/s2c min/avg/max/stddev: %u/%u %.0f/%.0f %u/%u %.0f/%.0f]", + ndpi_data_min(flow->iat_c_to_s), ndpi_data_min(flow->iat_s_to_c), + (float)ndpi_data_average(flow->iat_c_to_s), (float)ndpi_data_average(flow->iat_s_to_c), + ndpi_data_max(flow->iat_c_to_s), ndpi_data_max(flow->iat_s_to_c), + (float)ndpi_data_stddev(flow->iat_c_to_s), (float)ndpi_data_stddev(flow->iat_s_to_c)); - snprintf(tmp, sizeof(tmp), "%s.%s", - ndpi_get_proto_name(ndpi_thread_info[thread_id].workflow->ndpi_struct, - flow->detected_protocol.master_protocol), - ndpi_get_proto_name(ndpi_thread_info[thread_id].workflow->ndpi_struct, - flow->detected_protocol.app_protocol)); + /* Packet Length */ + fprintf(out, "[Pkt Len c2s/s2c min/avg/max/stddev: %u/%u %.0f/%.0f %u/%u %.0f/%.0f]", + ndpi_data_min(flow->pktlen_c_to_s), ndpi_data_min(flow->pktlen_s_to_c), + ndpi_data_average(flow->pktlen_c_to_s), ndpi_data_average(flow->pktlen_s_to_c), + ndpi_data_max(flow->pktlen_c_to_s), ndpi_data_max(flow->pktlen_s_to_c), + ndpi_data_stddev(flow->pktlen_c_to_s), ndpi_data_stddev(flow->pktlen_s_to_c)); + } + } - json_object_object_add(jObj,"detected.protocol.name", - json_object_new_string(tmp)); - } else - json_object_object_add(jObj,"detected.protocol.name", - json_object_new_string(ndpi_get_proto_name(ndpi_thread_info[thread_id].workflow->ndpi_struct, - flow->detected_protocol.app_protocol))); + if(flow->http.url[0] != '\0') { + ndpi_risk_enum risk = ndpi_validate_url(flow->http.url); + + if(risk != NDPI_NO_RISK) + NDPI_SET_BIT(flow->risk, risk); + + fprintf(out, "[URL: %s][StatusCode: %u]", + flow->http.url, flow->http.response_status_code); + + if(flow->http.content_type[0] != '\0') + fprintf(out, "[Content-Type: %s]", flow->http.content_type); + } - json_object_object_add(jObj,"packets",json_object_new_int(flow->src2dst_packets + flow->dst2src_packets)); - json_object_object_add(jObj,"bytes",json_object_new_int(flow->src2dst_bytes + flow->dst2src_bytes)); + if(flow->http.user_agent[0] != '\0') + fprintf(out, "[User-Agent: %s]", flow->http.user_agent); - if(flow->host_server_name[0] != '\0') - json_object_object_add(jObj,"host.server.name",json_object_new_string(flow->host_server_name)); + if(flow->risk) { + u_int i; + + fprintf(out, "[Risk: "); + + for(i=0; irisk, i)) + fprintf(out, "** %s **", ndpi_risk2str(i)); + + fprintf(out, "]"); + } + + if(flow->ssh_tls.ssl_version != 0) fprintf(out, "[%s]", ndpi_ssl_version2str(flow->ndpi_flow, flow->ssh_tls.ssl_version, &known_tls)); + if(flow->ssh_tls.client_requested_server_name[0] != '\0') fprintf(out, "[Client: %s]", flow->ssh_tls.client_requested_server_name); + if(flow->ssh_tls.client_hassh[0] != '\0') fprintf(out, "[HASSH-C: %s]", flow->ssh_tls.client_hassh); - if((flow->ssh_tls.client_info[0] != '\0') || (flow->ssh_tls.server_info[0] != '\0')) { - json_object *sjObj = json_object_new_object(); + if(flow->ssh_tls.ja3_client[0] != '\0') fprintf(out, "[JA3C: %s%s]", flow->ssh_tls.ja3_client, + print_cipher(flow->ssh_tls.client_unsafe_cipher)); - if(flow->ssh_tls.ja3_server[0] != '\0') - json_object_object_add(jObj,"ja3s",json_object_new_string(flow->ssh_tls.ja3_server)); + if(flow->ssh_tls.server_info[0] != '\0') fprintf(out, "[Server: %s]", flow->ssh_tls.server_info); - if(flow->ssh_tls.ja3_client[0] != '\0') - json_object_object_add(jObj,"ja3c",json_object_new_string(flow->ssh_tls.ja3_client)); + if(flow->ssh_tls.server_names) fprintf(out, "[ServerNames: %s]", flow->ssh_tls.server_names); + if(flow->ssh_tls.server_hassh[0] != '\0') fprintf(out, "[HASSH-S: %s]", flow->ssh_tls.server_hassh); - if(flow->ssh_tls.ja3_server[0] != '\0') - json_object_object_add(jObj,"host.server.ja3",json_object_new_string(flow->ssh_tls.ja3_server)); + if(flow->ssh_tls.ja3_server[0] != '\0') fprintf(out, "[JA3S: %s%s]", flow->ssh_tls.ja3_server, + print_cipher(flow->ssh_tls.server_unsafe_cipher)); - if(flow->ssh_tls.client_info[0] != '\0') - json_object_object_add(sjObj, "client", json_object_new_string(flow->ssh_tls.client_info)); + if(flow->ssh_tls.tls_issuerDN) fprintf(out, "[Issuer: %s]", flow->ssh_tls.tls_issuerDN); + if(flow->ssh_tls.tls_subjectDN) fprintf(out, "[Subject: %s]", flow->ssh_tls.tls_subjectDN); - if(flow->ssh_tls.server_info[0] != '\0') - json_object_object_add(sjObj, "server", json_object_new_string(flow->ssh_tls.server_info)); + if(flow->ssh_tls.encrypted_sni.esni) { + fprintf(out, "[ESNI: %s]", flow->ssh_tls.encrypted_sni.esni); + fprintf(out, "[ESNI Cipher: %s]", ndpi_cipher2str(flow->ssh_tls.encrypted_sni.cipher_suite)); + } - json_object_object_add(jObj, "ssh_tls", sjObj); + if((flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS) + || (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_TLS)) { + if(flow->ssh_tls.sha1_cert_fingerprint_set) { + fprintf(out, "[Certificate SHA-1: "); + for(i=0; i<20; i++) + fprintf(out, "%s%02X", (i > 0) ? ":" : "", + flow->ssh_tls.sha1_cert_fingerprint[i] & 0xFF); + fprintf(out, "]"); } + } - if(json_flag == 1) - json_object_array_add(jArray_known_flows,jObj); - else if(json_flag == 2) - json_object_array_add(jArray_unknown_flows,jObj); -#endif + if(flow->ssh_tls.notBefore && flow->ssh_tls.notAfter) { + char notBefore[32], notAfter[32]; + struct tm a, b; + struct tm *before = gmtime_r(&flow->ssh_tls.notBefore, &a); + struct tm *after = gmtime_r(&flow->ssh_tls.notAfter, &b); + + strftime(notBefore, sizeof(notBefore), "%F %T", before); + strftime(notAfter, sizeof(notAfter), "%F %T", after); + + fprintf(out, "[Validity: %s - %s]", notBefore, notAfter); } + + if(flow->ssh_tls.server_cipher != '\0') fprintf(out, "[Cipher: %s]", + ndpi_cipher2str(flow->ssh_tls.server_cipher)); + if(flow->bittorent_hash[0] != '\0') fprintf(out, "[BT Hash: %s]", + flow->bittorent_hash); + if(flow->dhcp_fingerprint[0] != '\0') fprintf(out, "[DHCP Fingerprint: %s]", + flow->dhcp_fingerprint); + + if(flow->has_human_readeable_strings) fprintf(out, "[PLAIN TEXT (%s)]", + flow->human_readeable_string_buffer); + +#ifdef DIRECTION_BINS + print_bin(out, "Plen c2s", &flow->payload_len_bin_src2dst); + print_bin(out, "Plen s2c", &flow->payload_len_bin_dst2src); +#else + print_bin(out, "Plen Bins", &flow->payload_len_bin); +#endif + + fprintf(out, "\n"); } /* ********************************** */ @@ -1273,7 +1361,9 @@ static void node_print_unknown_proto_walker(const void *node, struct ndpi_flow_info *flow = *(struct ndpi_flow_info**)node; u_int16_t thread_id = *((u_int16_t*)user_data); - if(flow->detected_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN) return; + if((flow->detected_protocol.master_protocol != NDPI_PROTOCOL_UNKNOWN) + || (flow->detected_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN)) + return; if((which == ndpi_preorder) || (which == ndpi_leaf)) { /* Avoid walking the same node multiple times */ @@ -1292,7 +1382,9 @@ static void node_print_known_proto_walker(const void *node, struct ndpi_flow_info *flow = *(struct ndpi_flow_info**)node; u_int16_t thread_id = *((u_int16_t*)user_data); - if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UNKNOWN) return; + if((flow->detected_protocol.master_protocol == NDPI_PROTOCOL_UNKNOWN) + && (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UNKNOWN)) + return; if((which == ndpi_preorder) || (which == ndpi_leaf)) { /* Avoid walking the same node multiple times */ @@ -1308,21 +1400,23 @@ static void node_print_known_proto_walker(const void *node, */ static void node_proto_guess_walker(const void *node, ndpi_VISIT which, int depth, void *user_data) { struct ndpi_flow_info *flow = *(struct ndpi_flow_info **) node; - u_int16_t thread_id = *((u_int16_t *) user_data); + u_int16_t thread_id = *((u_int16_t *) user_data), proto; if((which == ndpi_preorder) || (which == ndpi_leaf)) { /* Avoid walking the same node multiple times */ if((!flow->detection_completed) && flow->ndpi_flow) { u_int8_t proto_guessed; - + flow->detected_protocol = ndpi_detection_giveup(ndpi_thread_info[0].workflow->ndpi_struct, flow->ndpi_flow, enable_protocol_guess, &proto_guessed); } - - process_ndpi_collected_info(ndpi_thread_info[thread_id].workflow, flow); - ndpi_thread_info[thread_id].workflow->stats.protocol_counter[flow->detected_protocol.app_protocol] += flow->src2dst_packets + flow->dst2src_packets; - ndpi_thread_info[thread_id].workflow->stats.protocol_counter_bytes[flow->detected_protocol.app_protocol] += flow->src2dst_bytes + flow->dst2src_bytes; - ndpi_thread_info[thread_id].workflow->stats.protocol_flows[flow->detected_protocol.app_protocol]++; + process_ndpi_collected_info(ndpi_thread_info[thread_id].workflow, flow, csv_fp); + + proto = flow->detected_protocol.app_protocol ? flow->detected_protocol.app_protocol : flow->detected_protocol.master_protocol; + + ndpi_thread_info[thread_id].workflow->stats.protocol_counter[proto] += flow->src2dst_packets + flow->dst2src_packets; + ndpi_thread_info[thread_id].workflow->stats.protocol_counter_bytes[proto] += flow->src2dst_bytes + flow->dst2src_bytes; + ndpi_thread_info[thread_id].workflow->stats.protocol_flows[proto]++; } } @@ -1336,17 +1430,17 @@ void updateScanners(struct single_flow_info **scanners, u_int32_t saddr, HASH_FIND_INT(*scanners, (int *)&saddr, f); if(f == NULL) { - f = (struct single_flow_info*)malloc(sizeof(struct single_flow_info)); + f = (struct single_flow_info*)ndpi_malloc(sizeof(struct single_flow_info)); if(!f) return; f->saddr = saddr; f->version = version; f->tot_flows = 1; f->ports = NULL; - p = (struct port_flow_info*)malloc(sizeof(struct port_flow_info)); + p = (struct port_flow_info*)ndpi_malloc(sizeof(struct port_flow_info)); if(!p) { - free(f); + ndpi_free(f); return; } else p->port = dport, p->num_flows = 1; @@ -1360,7 +1454,7 @@ void updateScanners(struct single_flow_info **scanners, u_int32_t saddr, HASH_FIND_INT(f->ports, (int *)&dport, pp); if(pp == NULL) { - pp = (struct port_flow_info*)malloc(sizeof(struct port_flow_info)); + pp = (struct port_flow_info*)ndpi_malloc(sizeof(struct port_flow_info)); if(!pp) return; pp->port = dport, pp->num_flows = 1; @@ -1392,7 +1486,7 @@ int updateIpTree(u_int32_t key, u_int8_t version, &(*rootp)->right; /* T4: follow right branch */ } - q = (addr_node *) malloc(sizeof(addr_node)); /* T5: key not found */ + q = (addr_node *) ndpi_malloc(sizeof(addr_node)); /* T5: key not found */ if(q != (addr_node *)0) { /* make new node */ *rootp = q; /* link new node to old */ @@ -1415,7 +1509,7 @@ void freeIpTree(addr_node *root) { freeIpTree(root->left); freeIpTree(root->right); - free(root); + ndpi_free(root); } /* *********************************************** */ @@ -1475,7 +1569,7 @@ static void updatePortStats(struct port_stats **stats, u_int32_t port, HASH_FIND_INT(*stats, &port, s); if(s == NULL) { - s = (struct port_stats*)calloc(1, sizeof(struct port_stats)); + s = (struct port_stats*)ndpi_calloc(1, sizeof(struct port_stats)); if(!s) return; s->port = port, s->num_pkts = num_pkts, s->num_bytes = num_bytes; @@ -1483,9 +1577,9 @@ static void updatePortStats(struct port_stats **stats, u_int32_t port, updateTopIpAddress(addr, version, proto, 1, s->top_ip_addrs, MAX_NUM_IP_ADDRESS); - s->addr_tree = (addr_node *) malloc(sizeof(addr_node)); + s->addr_tree = (addr_node *) ndpi_malloc(sizeof(addr_node)); if(!s->addr_tree) { - free(s); + ndpi_free(s); return; } @@ -1521,12 +1615,14 @@ static int acceptable(u_int32_t num_pkts){ /* *********************************************** */ +#if 0 static int receivers_sort(void *_a, void *_b) { struct receiver *a = (struct receiver *)_a; struct receiver *b = (struct receiver *)_b; return(b->num_pkts - a->num_pkts); } +#endif /* *********************************************** */ @@ -1541,21 +1637,21 @@ static int receivers_sort_asc(void *_a, void *_b) { /*@brief removes first (size - max) elements from hash table. * hash table is ordered in ascending order. */ -static struct receiver *cutBackTo(struct receiver **receivers, u_int32_t size, u_int32_t max) { +static struct receiver *cutBackTo(struct receiver **rcvrs, u_int32_t size, u_int32_t max) { struct receiver *r, *tmp; int i=0; int count; if(size < max) //return the original table - return *receivers; + return *rcvrs; count = size - max; - HASH_ITER(hh, *receivers, r, tmp) { + HASH_ITER(hh, *rcvrs, r, tmp) { if(i++ == count) return r; - HASH_DEL(*receivers, r); - free(r); + HASH_DEL(*rcvrs, r); + ndpi_free(r); } return(NULL); @@ -1574,7 +1670,7 @@ static void mergeTables(struct receiver **primary, struct receiver **secondary) HASH_ITER(hh, *primary, r, tmp) { HASH_FIND_INT(*secondary, (int *)&(r->addr), s); if(s == NULL){ - s = (struct receiver *)malloc(sizeof(struct receiver)); + s = (struct receiver *)ndpi_malloc(sizeof(struct receiver)); if(!s) return; s->addr = r->addr; @@ -1587,17 +1683,17 @@ static void mergeTables(struct receiver **primary, struct receiver **secondary) s->num_pkts += r->num_pkts; HASH_DEL(*primary, r); - free(r); + ndpi_free(r); } } /* *********************************************** */ -static void deleteReceivers(struct receiver *receivers) { +static void deleteReceivers(struct receiver *rcvrs) { struct receiver *current, *tmp; - HASH_ITER(hh, receivers, current, tmp) { - HASH_DEL(receivers, current); - free(current); + HASH_ITER(hh, rcvrs, current, tmp) { + HASH_DEL(rcvrs, current); + ndpi_free(current); } } @@ -1616,38 +1712,38 @@ static void deleteReceivers(struct receiver *receivers) { * else * update table1 */ -static void updateReceivers(struct receiver **receivers, u_int32_t dst_addr, +static void updateReceivers(struct receiver **rcvrs, u_int32_t dst_addr, u_int8_t version, u_int32_t num_pkts, - struct receiver **topReceivers) { + struct receiver **topRcvrs) { struct receiver *r; u_int32_t size; int a; - HASH_FIND_INT(*receivers, (int *)&dst_addr, r); + HASH_FIND_INT(*rcvrs, (int *)&dst_addr, r); if(r == NULL) { - if(((size = HASH_COUNT(*receivers)) < MAX_TABLE_SIZE_1) + if(((size = HASH_COUNT(*rcvrs)) < MAX_TABLE_SIZE_1) || ((a = acceptable(num_pkts)) != 0)){ - r = (struct receiver *)malloc(sizeof(struct receiver)); + r = (struct receiver *)ndpi_malloc(sizeof(struct receiver)); if(!r) return; r->addr = dst_addr; r->version = version; r->num_pkts = num_pkts; - HASH_ADD_INT(*receivers, addr, r); + HASH_ADD_INT(*rcvrs, addr, r); - if((size = HASH_COUNT(*receivers)) > MAX_TABLE_SIZE_2){ + if((size = HASH_COUNT(*rcvrs)) > MAX_TABLE_SIZE_2){ - HASH_SORT(*receivers, receivers_sort_asc); - *receivers = cutBackTo(receivers, size, MAX_TABLE_SIZE_1); - mergeTables(receivers, topReceivers); + HASH_SORT(*rcvrs, receivers_sort_asc); + *rcvrs = cutBackTo(rcvrs, size, MAX_TABLE_SIZE_1); + mergeTables(rcvrs, topRcvrs); - if((size = HASH_COUNT(*topReceivers)) > MAX_TABLE_SIZE_1){ - HASH_SORT(*topReceivers, receivers_sort_asc); - *topReceivers = cutBackTo(topReceivers, size, MAX_TABLE_SIZE_1); + if((size = HASH_COUNT(*topRcvrs)) > MAX_TABLE_SIZE_1){ + HASH_SORT(*topRcvrs, receivers_sort_asc); + *topRcvrs = cutBackTo(topRcvrs, size, MAX_TABLE_SIZE_1); } - *receivers = NULL; + *rcvrs = NULL; } } } @@ -1655,42 +1751,6 @@ static void updateReceivers(struct receiver **receivers, u_int32_t dst_addr, r->num_pkts += num_pkts; } -/* *********************************************** */ - -#ifdef HAVE_JSON_C -static void saveReceiverStats(json_object **jObj_group, - struct receiver **receivers, - u_int64_t total_pkt_count) { - - json_object *jArray_stats = json_object_new_array(); - struct receiver *r, *tmp; - int i = 0; - - HASH_ITER(hh, *receivers, r, tmp) { - json_object *jObj_stat = json_object_new_object(); - char addr_name[48]; - - if(r->version == IPVERSION) - inet_ntop(AF_INET, &(r->addr), addr_name, sizeof(addr_name)); - else - inet_ntop(AF_INET6, &(r->addr), addr_name, sizeof(addr_name)); - - - json_object_object_add(jObj_stat,"ip.address",json_object_new_string(addr_name)); - json_object_object_add(jObj_stat,"packets.number", json_object_new_int(r->num_pkts)); - json_object_object_add(jObj_stat,"packets.percent",json_object_new_double(((double)r->num_pkts) / total_pkt_count)); - - json_object_array_add(jArray_stats, jObj_stat); - - i++; - if(i >= 10) break; - } - - json_object_object_add(*jObj_group, "top.receiver.stats", jArray_stats); -} -#endif - - /* *********************************************** */ static void deleteScanners(struct single_flow_info *scanners) { @@ -1699,11 +1759,11 @@ static void deleteScanners(struct single_flow_info *scanners) { HASH_ITER(hh, scanners, s, tmp) { HASH_ITER(hh, s->ports, p, tmp2) { - HASH_DEL(s->ports, p); - free(p); + if(s->ports) HASH_DEL(s->ports, p); + ndpi_free(p); } HASH_DEL(scanners, s); - free(s); + ndpi_free(s); } } @@ -1715,7 +1775,7 @@ static void deletePortsStats(struct port_stats *stats) { HASH_ITER(hh, stats, current_port, tmp) { HASH_DEL(stats, current_port); freeIpTree(current_port->addr_tree); - free(current_port); + ndpi_free(current_port); } } @@ -1771,15 +1831,17 @@ static void node_idle_scan_walker(const void *node, ndpi_VISIT which, int depth, return; if((which == ndpi_preorder) || (which == ndpi_leaf)) { /* Avoid walking the same node multiple times */ - if(flow->last_seen + MAX_IDLE_TIME < ndpi_thread_info[thread_id].workflow->last_time) { + if(flow->last_seen_ms + MAX_IDLE_TIME < ndpi_thread_info[thread_id].workflow->last_time) { /* update stats */ node_proto_guess_walker(node, which, depth, user_data); + if(verbose == 3) + port_stats_walker(node, which, depth, user_data); if((flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UNKNOWN) && !undetected_flows_deleted) undetected_flows_deleted = 1; - ndpi_free_flow_info_half(flow); + ndpi_flow_info_free_data(flow); ndpi_thread_info[thread_id].workflow->stats.ndpi_flow_count--; /* adding to a queue (we can't delete it from the tree inline ) */ @@ -1809,9 +1871,7 @@ static void debug_printf(u_int32_t protocol, void *id_struct, ndpi_log_level_t log_level, const char *format, ...) { va_list va_ap; -#ifndef WIN32 struct tm result; -#endif if(log_level <= nDPI_LogLevel) { char buf[8192], out_buf[8192]; @@ -1829,7 +1889,7 @@ static void debug_printf(u_int32_t protocol, void *id_struct, extra_msg = "DEBUG: "; memset(buf, 0, sizeof(buf)); - strftime(theDate, 32, "%d/%b/%Y %H:%M:%S", localtime_r(&theTime,&result) ); + strftime(theDate, 32, "%d/%b/%Y %H:%M:%S", localtime_r(&theTime,&result)); vsnprintf(buf, sizeof(buf)-1, format, va_ap); snprintf(out_buf, sizeof(out_buf), "%s %s%s", theDate, extra_msg, buf); @@ -1860,11 +1920,6 @@ static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle) { ndpi_thread_info[thread_id].workflow = ndpi_workflow_init(&prefs, pcap_handle); /* Preferences */ - ndpi_set_detection_preferences(ndpi_thread_info[thread_id].workflow->ndpi_struct, - ndpi_pref_http_dont_dissect_response, 0); - ndpi_set_detection_preferences(ndpi_thread_info[thread_id].workflow->ndpi_struct, - ndpi_pref_dns_dont_dissect_response, 0); - ndpi_workflow_set_flow_detected_callback(ndpi_thread_info[thread_id].workflow, on_protocol_discovered, (void *)(uintptr_t)thread_id); @@ -1886,6 +1941,12 @@ static void setupDetection(u_int16_t thread_id, pcap_t * pcap_handle) { if(_customCategoryFilePath) ndpi_load_categories_file(ndpi_thread_info[thread_id].workflow->ndpi_struct, _customCategoryFilePath); + + ndpi_finalize_initalization(ndpi_thread_info[thread_id].workflow->ndpi_struct); + +#ifdef USE_TLS_LEN + ndpi_set_detection_preferences(ndpi_thread_info[thread_id].workflow->ndpi_struct, ndpi_pref_enable_tls_block_dissection, 1); +#endif } /* *********************************************** */ @@ -1954,68 +2015,6 @@ char* formatPackets(float numPkts, char *buf) { /* *********************************************** */ -/** - * @brief JSON function init - */ -#ifdef HAVE_JSON_C -static void json_init() { - jArray_known_flows = json_object_new_array(); - jArray_unknown_flows = json_object_new_array(); - jArray_topStats = json_object_new_array(); -} - -/* *********************************************** */ - -#ifdef HAVE_JSON_C -/** - * @brief JSON destroy function - */ -static void json_destroy() { - if(jArray_known_flows) { - json_object_put(jArray_known_flows); - jArray_known_flows = NULL; - } - - if(jArray_unknown_flows) { - json_object_put(jArray_unknown_flows); - jArray_unknown_flows = NULL; - } - - if(jArray_topStats) { - json_object_put(jArray_topStats); - jArray_topStats = NULL; - } -} -#endif - -/* *********************************************** */ - -static void json_open_stats_file() { - if((file_first_time && ((stats_fp = fopen(_statsFilePath,"w")) == NULL)) - || - (!file_first_time && (stats_fp = fopen(_statsFilePath,"a")) == NULL)) { - printf("Error creating/opening file %s\n", _statsFilePath); - stats_flag = 0; - } - else file_first_time = 0; -} - -/* *********************************************** */ - -static void json_close_stats_file() { - json_object *jObjFinal = json_object_new_object(); - - json_object_object_add(jObjFinal,"duration.in.seconds", - json_object_new_int(pcap_analysis_duration)); - json_object_object_add(jObjFinal,"statistics", jArray_topStats); - fprintf(stats_fp,"%s\n",json_object_to_json_string(jObjFinal)); - fclose(stats_fp); - json_object_put(jObjFinal); -} -#endif - -/* *********************************************** */ - /** * @brief Bytes stats format */ @@ -2055,26 +2054,6 @@ static int port_stats_sort(void *_a, void *_b) { /* *********************************************** */ -#ifdef HAVE_JSON_C -static int scanners_sort(void *_a, void *_b) { - struct single_flow_info *a = (struct single_flow_info *)_a; - struct single_flow_info *b = (struct single_flow_info *)_b; - - return(b->tot_flows - a->tot_flows); -} - -/* *********************************************** */ - -static int scanners_port_sort(void *_a, void *_b) { - struct port_flow_info *a = (struct port_flow_info *)_a; - struct port_flow_info *b = (struct port_flow_info *)_b; - - return(b->num_flows - a->num_flows); -} - -#endif -/* *********************************************** */ - static int info_pair_cmp (const void *_a, const void *_b) { struct info_pair *a = (struct info_pair *)_a; @@ -2085,173 +2064,8 @@ static int info_pair_cmp (const void *_a, const void *_b) /* *********************************************** */ -#ifdef HAVE_JSON_C -static int top_stats_sort(void *_a, void *_b) { - struct port_stats *a = (struct port_stats*)_a; - struct port_stats *b = (struct port_stats*)_b; - - return(b->num_addr - a->num_addr); -} - -/* *********************************************** */ - -/** - * @brief Get port based top statistics - */ -static int getTopStats(struct port_stats *stats) { - struct port_stats *sp, *tmp; - struct info_pair inf; - u_int64_t total_ip_addrs = 0; - - HASH_ITER(hh, stats, sp, tmp) { - qsort(sp->top_ip_addrs, MAX_NUM_IP_ADDRESS, sizeof(struct info_pair), info_pair_cmp); - inf = sp->top_ip_addrs[0]; - - if(((inf.count * 100.0)/sp->cumulative_addr) > AGGRESSIVE_PERCENT) { - sp->hasTopHost = 1; - sp->top_host = inf.addr; - sp->version = inf.version; - strncpy(sp->proto, inf.proto, sizeof(sp->proto)); - } else - sp->hasTopHost = 0; - - total_ip_addrs += sp->num_addr; - } - - return total_ip_addrs; -} - -/* *********************************************** */ - -static void saveScannerStats(json_object **jObj_group, struct single_flow_info **scanners) { - struct single_flow_info *s, *tmp; - struct port_flow_info *p, *tmp2; - char addr_name[48]; - int i = 0, j = 0; - - json_object *jArray_stats = json_object_new_array(); - - HASH_SORT(*scanners, scanners_sort); // FIX - - HASH_ITER(hh, *scanners, s, tmp) { - json_object *jObj_stat = json_object_new_object(); - json_object *jArray_ports = json_object_new_array(); - - if(s->version == IPVERSION) - inet_ntop(AF_INET, &(s->saddr), addr_name, sizeof(addr_name)); - else - inet_ntop(AF_INET6, &(s->saddr), addr_name, sizeof(addr_name)); - - json_object_object_add(jObj_stat,"ip.address",json_object_new_string(addr_name)); - json_object_object_add(jObj_stat,"total.flows.number",json_object_new_int(s->tot_flows)); - - HASH_SORT(s->ports, scanners_port_sort); - - HASH_ITER(hh, s->ports, p, tmp2) { - json_object *jObj_port = json_object_new_object(); - - json_object_object_add(jObj_port,"port",json_object_new_int(p->port)); - json_object_object_add(jObj_port,"flows.number",json_object_new_int(p->num_flows)); - - json_object_array_add(jArray_ports, jObj_port); - - j++; - if(j >= 10) break; - } - - json_object_object_add(jObj_stat,"top.dst.ports",jArray_ports); - json_object_array_add(jArray_stats, jObj_stat); - - j = 0; - i++; - if(i >= 10) break; - } - - json_object_object_add(*jObj_group, "top.scanner.stats", jArray_stats); -} - -#endif - -/* *********************************************** */ - -#ifdef HAVE_JSON_C -/* - * @brief Save Top Stats in json format - */ -static void saveTopStats(json_object **jObj_group, - struct port_stats **stats, - u_int8_t direction, - u_int64_t total_flow_count, - u_int64_t total_ip_addr) { - struct port_stats *s, *tmp; - char addr_name[48]; - int i = 0; - - json_object *jArray_stats = json_object_new_array(); - - - HASH_ITER(hh, *stats, s, tmp) { - - if((s->hasTopHost)) { - json_object *jObj_stat = json_object_new_object(); - - json_object_object_add(jObj_stat,"port",json_object_new_int(s->port)); - json_object_object_add(jObj_stat,"packets.number",json_object_new_int(s->num_pkts)); - json_object_object_add(jObj_stat,"flows.number",json_object_new_int(s->num_flows)); - json_object_object_add(jObj_stat,"flows.percent",json_object_new_double((s->num_flows*100.0)/total_flow_count)); - if(s->num_pkts) json_object_object_add(jObj_stat,"flows/packets", - json_object_new_double(((double)s->num_flows)/s->num_pkts)); - else json_object_object_add(jObj_stat,"flows.num_packets",json_object_new_double(0.0)); - - if(s->version == IPVERSION) { - inet_ntop(AF_INET, &(s->top_host), addr_name, sizeof(addr_name)); - } else { - inet_ntop(AF_INET6, &(s->top_host), addr_name, sizeof(addr_name)); - } - - json_object_object_add(jObj_stat,"aggressive.host",json_object_new_string(addr_name)); - json_object_object_add(jObj_stat,"host.app.protocol",json_object_new_string(s->proto)); - - json_object_array_add(jArray_stats, jObj_stat); - i++; - - if(i >= 10) break; - } - } - - json_object_object_add(*jObj_group, (direction == DIR_SRC) ? - "top.src.pkts.stats" : "top.dst.pkts.stats", jArray_stats); - - jArray_stats = json_object_new_array(); - i=0; - - /*sort top stats by ip addr count*/ - HASH_SORT(*stats, top_stats_sort); - - - HASH_ITER(hh, *stats, s, tmp) { - json_object *jObj_stat = json_object_new_object(); - - json_object_object_add(jObj_stat,"port",json_object_new_int(s->port)); - json_object_object_add(jObj_stat,"host.number",json_object_new_int64(s->num_addr)); - json_object_object_add(jObj_stat,"host.percent",json_object_new_double((s->num_addr*100.0)/total_ip_addr)); - json_object_object_add(jObj_stat,"flows.number",json_object_new_int(s->num_flows)); - - json_object_array_add(jArray_stats,jObj_stat); - i++; - - if(i >= 10) break; - } - - json_object_object_add(*jObj_group, (direction == DIR_SRC) ? - "top.src.host.stats" : "top.dst.host.stats", jArray_stats); -} -#endif - -/* *********************************************** */ - -void printPortStats(struct port_stats *stats) { - struct port_stats *s, *tmp; +void printPortStats(struct port_stats *stats) { + struct port_stats *s, *tmp; char addr_name[48]; int i = 0, j = 0; @@ -2286,18 +2100,18 @@ static void printFlowsStats() { int thread_id; u_int32_t total_flows = 0; FILE *out = results_file ? results_file : stdout; - + if(enable_payload_analyzer) ndpi_report_payload_stats(); for(thread_id = 0; thread_id < num_threads; thread_id++) total_flows += ndpi_thread_info[thread_id].workflow->num_allocated_flows; - - if((all_flows = (struct flow_info*)malloc(sizeof(struct flow_info)*total_flows)) == NULL) { + + if((all_flows = (struct flow_info*)ndpi_malloc(sizeof(struct flow_info)*total_flows)) == NULL) { fprintf(out, "Fatal error: not enough memory\n"); exit(-1); } - + if(verbose) { ndpi_host_ja3_fingerprints *ja3ByHostsHashT = NULL; // outer hash table ndpi_ja3_fingerprints_host *hostByJA3C_ht = NULL; // for client @@ -2310,7 +2124,7 @@ static void printFlowsStats() { unsigned int num_ja3_client; unsigned int num_ja3_server; - if(!json_flag) fprintf(out, "\n"); + fprintf(out, "\n"); num_flows = 0; for(thread_id = 0; thread_id < num_threads; thread_id++) { @@ -2332,14 +2146,14 @@ static void printFlowsStats() { //host ip -> ja3 if(ja3ByHostFound == NULL){ //adding the new host - ndpi_host_ja3_fingerprints *newHost = malloc(sizeof(ndpi_host_ja3_fingerprints)); + ndpi_host_ja3_fingerprints *newHost = ndpi_malloc(sizeof(ndpi_host_ja3_fingerprints)); newHost->host_client_info_hasht = NULL; newHost->host_server_info_hasht = NULL; newHost->ip_string = all_flows[i].flow->src_name; newHost->ip = all_flows[i].flow->src_ip; - newHost->dns_name = all_flows[i].flow->ssh_tls.client_info; + newHost->dns_name = all_flows[i].flow->ssh_tls.client_requested_server_name; - ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info)); + ndpi_ja3_info *newJA3 = ndpi_malloc(sizeof(ndpi_ja3_info)); newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_client; newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.client_unsafe_cipher; //adding the new ja3 fingerprint @@ -2355,7 +2169,7 @@ static void printFlowsStats() { all_flows[i].flow->ssh_tls.ja3_client, infoFound); if(infoFound == NULL){ - ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info)); + ndpi_ja3_info *newJA3 = ndpi_malloc(sizeof(ndpi_ja3_info)); newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_client; newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.client_unsafe_cipher; HASH_ADD_KEYPTR(hh, ja3ByHostFound->host_client_info_hasht, @@ -2366,13 +2180,13 @@ static void printFlowsStats() { //ja3 -> host ip HASH_FIND_STR(hostByJA3C_ht, all_flows[i].flow->ssh_tls.ja3_client, hostByJA3Found); if(hostByJA3Found == NULL){ - ndpi_ip_dns *newHost = malloc(sizeof(ndpi_ip_dns)); + ndpi_ip_dns *newHost = ndpi_malloc(sizeof(ndpi_ip_dns)); newHost->ip = all_flows[i].flow->src_ip; newHost->ip_string = all_flows[i].flow->src_name; - newHost->dns_name = all_flows[i].flow->ssh_tls.client_info;; + newHost->dns_name = all_flows[i].flow->ssh_tls.client_requested_server_name;; - ndpi_ja3_fingerprints_host *newElement = malloc(sizeof(ndpi_ja3_fingerprints_host)); + ndpi_ja3_fingerprints_host *newElement = ndpi_malloc(sizeof(ndpi_ja3_fingerprints_host)); newElement->ja3 = all_flows[i].flow->ssh_tls.ja3_client; newElement->unsafe_cipher = all_flows[i].flow->ssh_tls.client_unsafe_cipher; newElement->ipToDNS_ht = NULL; @@ -2384,10 +2198,10 @@ static void printFlowsStats() { ndpi_ip_dns *innerElement = NULL; HASH_FIND_INT(hostByJA3Found->ipToDNS_ht, &(all_flows[i].flow->src_ip), innerElement); if(innerElement == NULL){ - ndpi_ip_dns *newInnerElement = malloc(sizeof(ndpi_ip_dns)); + ndpi_ip_dns *newInnerElement = ndpi_malloc(sizeof(ndpi_ip_dns)); newInnerElement->ip = all_flows[i].flow->src_ip; newInnerElement->ip_string = all_flows[i].flow->src_name; - newInnerElement->dns_name = all_flows[i].flow->ssh_tls.client_info; + newInnerElement->dns_name = all_flows[i].flow->ssh_tls.client_requested_server_name; HASH_ADD_INT(hostByJA3Found->ipToDNS_ht, ip, newInnerElement); } } @@ -2398,14 +2212,14 @@ static void printFlowsStats() { HASH_FIND_INT(ja3ByHostsHashT, &(all_flows[i].flow->dst_ip), ja3ByHostFound); if(ja3ByHostFound == NULL){ //adding the new host in the hash table - ndpi_host_ja3_fingerprints *newHost = malloc(sizeof(ndpi_host_ja3_fingerprints)); + ndpi_host_ja3_fingerprints *newHost = ndpi_malloc(sizeof(ndpi_host_ja3_fingerprints)); newHost->host_client_info_hasht = NULL; newHost->host_server_info_hasht = NULL; newHost->ip_string = all_flows[i].flow->dst_name; newHost->ip = all_flows[i].flow->dst_ip; newHost->dns_name = all_flows[i].flow->ssh_tls.server_info; - ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info)); + ndpi_ja3_info *newJA3 = ndpi_malloc(sizeof(ndpi_ja3_info)); newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_server; newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.server_unsafe_cipher; //adding the new ja3 fingerprint @@ -2419,7 +2233,7 @@ static void printFlowsStats() { HASH_FIND_STR(ja3ByHostFound->host_server_info_hasht, all_flows[i].flow->ssh_tls.ja3_server, infoFound); if(infoFound == NULL){ - ndpi_ja3_info *newJA3 = malloc(sizeof(ndpi_ja3_info)); + ndpi_ja3_info *newJA3 = ndpi_malloc(sizeof(ndpi_ja3_info)); newJA3->ja3 = all_flows[i].flow->ssh_tls.ja3_server; newJA3->unsafe_cipher = all_flows[i].flow->ssh_tls.server_unsafe_cipher; HASH_ADD_KEYPTR(hh, ja3ByHostFound->host_server_info_hasht, @@ -2429,13 +2243,13 @@ static void printFlowsStats() { HASH_FIND_STR(hostByJA3S_ht, all_flows[i].flow->ssh_tls.ja3_server, hostByJA3Found); if(hostByJA3Found == NULL){ - ndpi_ip_dns *newHost = malloc(sizeof(ndpi_ip_dns)); + ndpi_ip_dns *newHost = ndpi_malloc(sizeof(ndpi_ip_dns)); newHost->ip = all_flows[i].flow->dst_ip; newHost->ip_string = all_flows[i].flow->dst_name; newHost->dns_name = all_flows[i].flow->ssh_tls.server_info;; - ndpi_ja3_fingerprints_host *newElement = malloc(sizeof(ndpi_ja3_fingerprints_host)); + ndpi_ja3_fingerprints_host *newElement = ndpi_malloc(sizeof(ndpi_ja3_fingerprints_host)); newElement->ja3 = all_flows[i].flow->ssh_tls.ja3_server; newElement->unsafe_cipher = all_flows[i].flow->ssh_tls.server_unsafe_cipher; newElement->ipToDNS_ht = NULL; @@ -2448,7 +2262,7 @@ static void printFlowsStats() { HASH_FIND_INT(hostByJA3Found->ipToDNS_ht, &(all_flows[i].flow->dst_ip), innerElement); if(innerElement == NULL){ - ndpi_ip_dns *newInnerElement = malloc(sizeof(ndpi_ip_dns)); + ndpi_ip_dns *newInnerElement = ndpi_malloc(sizeof(ndpi_ip_dns)); newInnerElement->ip = all_flows[i].flow->dst_ip; newInnerElement->ip_string = all_flows[i].flow->dst_name; newInnerElement->dns_name = all_flows[i].flow->ssh_tls.server_info; @@ -2478,7 +2292,7 @@ static void printFlowsStats() { num_ja3_server = HASH_COUNT(ja3ByHost_element->host_server_info_hasht); if(num_ja3_client > 0) { - fprintf(out, "\t%d\t %-24s \t %-7d\n", + fprintf(out, "\t%d\t %-24s \t %-7u\n", i, ja3ByHost_element->ip_string, num_ja3_client @@ -2601,34 +2415,38 @@ static void printFlowsStats() { //freeing the hash table HASH_ITER(hh, ja3ByHostsHashT, ja3ByHost_element, tmp) { HASH_ITER(hh, ja3ByHost_element->host_client_info_hasht, info_of_element, tmp2) { - HASH_DEL(ja3ByHost_element->host_client_info_hasht, info_of_element); - free(info_of_element); + if(ja3ByHost_element->host_client_info_hasht) + HASH_DEL(ja3ByHost_element->host_client_info_hasht, info_of_element); + ndpi_free(info_of_element); } HASH_ITER(hh, ja3ByHost_element->host_server_info_hasht, info_of_element, tmp2) { - HASH_DEL(ja3ByHost_element->host_server_info_hasht, info_of_element); - free(info_of_element); + if(ja3ByHost_element->host_server_info_hasht) + HASH_DEL(ja3ByHost_element->host_server_info_hasht, info_of_element); + ndpi_free(info_of_element); } HASH_DEL(ja3ByHostsHashT, ja3ByHost_element); - free(ja3ByHost_element); + ndpi_free(ja3ByHost_element); } HASH_ITER(hh, hostByJA3C_ht, hostByJA3Element, tmp3) { HASH_ITER(hh, hostByJA3C_ht->ipToDNS_ht, innerHashEl, tmp4) { - HASH_DEL(hostByJA3Element->ipToDNS_ht, innerHashEl); - free(innerHashEl); + if(hostByJA3Element->ipToDNS_ht) + HASH_DEL(hostByJA3Element->ipToDNS_ht, innerHashEl); + ndpi_free(innerHashEl); } HASH_DEL(hostByJA3C_ht, hostByJA3Element); - free(hostByJA3Element); + ndpi_free(hostByJA3Element); } hostByJA3Element = NULL; HASH_ITER(hh, hostByJA3S_ht, hostByJA3Element, tmp3) { HASH_ITER(hh, hostByJA3S_ht->ipToDNS_ht, innerHashEl, tmp4) { - HASH_DEL(hostByJA3Element->ipToDNS_ht, innerHashEl); - free(innerHashEl); + if(hostByJA3Element->ipToDNS_ht) + HASH_DEL(hostByJA3Element->ipToDNS_ht, innerHashEl); + ndpi_free(innerHashEl); } HASH_DEL(hostByJA3S_ht, hostByJA3Element); - free(hostByJA3Element); + ndpi_free(hostByJA3Element); } } } @@ -2638,20 +2456,90 @@ static void printFlowsStats() { qsort(all_flows, num_flows, sizeof(struct flow_info), cmpFlows); if(verbose > 1) { - for(i=0; ipayload_len_bin, sizeof(struct ndpi_bin)); + ndpi_normalize_bin(&bins[i]); + } +#endif + printFlow(i+1, all_flows[i].flow, all_flows[i].thread_id); - } + } - for(thread_id = 0; thread_id < num_threads; thread_id++) { - if(ndpi_thread_info[thread_id].workflow->stats.protocol_counter[0 /* 0 = Unknown */] > 0) { - if(!json_flag) { +#ifndef DIRECTION_BINS + if(bins && cluster_ids && (num_bin_clusters > 0)) { + char buf[64]; + u_int j; + struct ndpi_bin *centroids; + + if((centroids = (struct ndpi_bin*)ndpi_malloc(sizeof(struct ndpi_bin)*num_bin_clusters)) != NULL) { + for(i=0; i %s:%u\t[", + i, + ndpi_protocol2name(ndpi_thread_info[0].workflow->ndpi_struct, + all_flows[i].flow->detected_protocol, buf, sizeof(buf)), + all_flows[i].flow->src_name, + ntohs(all_flows[i].flow->src_port), + all_flows[i].flow->dst_name, + ntohs(all_flows[i].flow->dst_port)); + + print_bin(out, NULL, &bins[i]); + printf("][similarity: %f]", ndpi_bin_similarity(¢roids[j], &bins[i], 0)); + + if(all_flows[i].flow->ssh_tls.client_requested_server_name[0] != '\0') + fprintf(out, "[%s]", all_flows[i].flow->ssh_tls.client_requested_server_name); + + printf("\n"); + num_printed++; + } - fprintf(out, "\n\nUndetected flows:%s\n", - undetected_flows_deleted ? " (expired flows are not listed below)" : ""); + if(num_printed) printf("\n"); + } + + for(i=0; istats.protocol_counter[0 /* 0 = Unknown */] > 0) { + fprintf(out, "\n\nUndetected flows:%s\n", + undetected_flows_deleted ? " (expired flows are not listed below)" : ""); break; } } @@ -2681,10 +2569,10 @@ static void printFlowsStats() { } for(i=0; indpi_flows_root[i], node_proto_guess_walker, &thread_id); - if(verbose == 3 || stats_flag) ndpi_twalk(ndpi_thread_info[thread_id].workflow->ndpi_flows_root[i], - port_stats_walker, &thread_id); + if(verbose == 3) + ndpi_twalk(ndpi_thread_info[thread_id].workflow->ndpi_flows_root[i], + port_stats_walker, &thread_id); } /* Stats aggregation */ @@ -2758,7 +2642,6 @@ static void printResults(u_int64_t processing_time_usec, u_int64_t setup_time_us printf("\tSetup Time: %lu msec\n", (unsigned long)(setup_time_usec/1000)); printf("\tPacket Processing Time: %lu msec\n", (unsigned long)(processing_time_usec/1000)); - if(!json_flag) { printf("\nTraffic statistics:\n"); printf("\tEthernet bytes: %-13llu (includes ethernet CRC/IFC/trailer)\n", (long long unsigned int)cumulative_stats.total_wire_bytes); @@ -2793,17 +2676,22 @@ static void printResults(u_int64_t processing_time_usec, u_int64_t setup_time_us float t = (float)(cumulative_stats.ip_packet_count*1000000)/(float)processing_time_usec; float b = (float)(cumulative_stats.total_wire_bytes * 8 *1000000)/(float)processing_time_usec; float traffic_duration; + struct tm result; if(live_capture) traffic_duration = processing_time_usec; else traffic_duration = (pcap_end.tv_sec*1000000 + pcap_end.tv_usec) - (pcap_start.tv_sec*1000000 + pcap_start.tv_usec); printf("\tnDPI throughput: %s pps / %s/sec\n", formatPackets(t, buf), formatTraffic(b, 1, buf1)); - t = (float)(cumulative_stats.ip_packet_count*1000000)/(float)traffic_duration; - b = (float)(cumulative_stats.total_wire_bytes * 8 *1000000)/(float)traffic_duration; - - strftime(when, sizeof(when), "%d/%b/%Y %H:%M:%S", localtime(&pcap_start.tv_sec)); + if(traffic_duration != 0) { + t = (float)(cumulative_stats.ip_packet_count*1000000)/(float)traffic_duration; + b = (float)(cumulative_stats.total_wire_bytes * 8 *1000000)/(float)traffic_duration; + } else { + t = 0; + b = 0; + } + strftime(when, sizeof(when), "%d/%b/%Y %H:%M:%S", localtime_r(&pcap_start.tv_sec, &result)); printf("\tAnalysis begin: %s\n", when); - strftime(when, sizeof(when), "%d/%b/%Y %H:%M:%S", localtime(&pcap_end.tv_sec)); + strftime(when, sizeof(when), "%d/%b/%Y %H:%M:%S", localtime_r(&pcap_end.tv_sec, &result)); printf("\tAnalysis end: %s\n", when); printf("\tTraffic throughput: %s pps / %s/sec\n", formatPackets(t, buf), formatTraffic(b, 1, buf1)); printf("\tTraffic duration: %.3f sec\n", traffic_duration/1000000); @@ -2811,51 +2699,10 @@ static void printResults(u_int64_t processing_time_usec, u_int64_t setup_time_us if(enable_protocol_guess) printf("\tGuessed flow protos: %-13u\n", cumulative_stats.guessed_flow_protocols); - } } - if(json_flag) { -#ifdef HAVE_JSON_C - if(!strcmp(_jsonFilePath, "-")) - json_fp = stderr, dont_close_json_fp = 1; - else if((json_fp = fopen(_jsonFilePath,"w")) == NULL) { - printf("Error creating .json file %s\n", _jsonFilePath); - json_flag = 0; - } - if(json_flag) { - jObj_main = json_object_new_object(); - jObj_trafficStats = json_object_new_object(); - jArray_detProto = json_object_new_array(); - - json_object_object_add(jObj_trafficStats,"ethernet.bytes",json_object_new_int64(cumulative_stats.total_wire_bytes)); - json_object_object_add(jObj_trafficStats,"discarded.bytes",json_object_new_int64(cumulative_stats.total_discarded_bytes)); - json_object_object_add(jObj_trafficStats,"ip.packets",json_object_new_int64(cumulative_stats.ip_packet_count)); - json_object_object_add(jObj_trafficStats,"total.packets",json_object_new_int64(cumulative_stats.raw_packet_count)); - json_object_object_add(jObj_trafficStats,"ip.bytes",json_object_new_int64(cumulative_stats.total_ip_bytes)); - json_object_object_add(jObj_trafficStats,"avg.pkt.size",json_object_new_int(cumulative_stats.total_ip_bytes/cumulative_stats.raw_packet_count)); - json_object_object_add(jObj_trafficStats,"unique.flows",json_object_new_int(cumulative_stats.ndpi_flow_count)); - json_object_object_add(jObj_trafficStats,"tcp.pkts",json_object_new_int64(cumulative_stats.tcp_count)); - json_object_object_add(jObj_trafficStats,"udp.pkts",json_object_new_int64(cumulative_stats.udp_count)); - json_object_object_add(jObj_trafficStats,"vlan.pkts",json_object_new_int64(cumulative_stats.vlan_count)); - json_object_object_add(jObj_trafficStats,"mpls.pkts",json_object_new_int64(cumulative_stats.mpls_count)); - json_object_object_add(jObj_trafficStats,"pppoe.pkts",json_object_new_int64(cumulative_stats.pppoe_count)); - json_object_object_add(jObj_trafficStats,"fragmented.pkts",json_object_new_int64(cumulative_stats.fragmented_count)); - json_object_object_add(jObj_trafficStats,"max.pkt.size",json_object_new_int(cumulative_stats.max_packet_len)); - json_object_object_add(jObj_trafficStats,"pkt.len_min64",json_object_new_int64(cumulative_stats.packet_len[0])); - json_object_object_add(jObj_trafficStats,"pkt.len_64_128",json_object_new_int64(cumulative_stats.packet_len[1])); - json_object_object_add(jObj_trafficStats,"pkt.len_128_256",json_object_new_int64(cumulative_stats.packet_len[2])); - json_object_object_add(jObj_trafficStats,"pkt.len_256_1024",json_object_new_int64(cumulative_stats.packet_len[3])); - json_object_object_add(jObj_trafficStats,"pkt.len_1024_1500",json_object_new_int64(cumulative_stats.packet_len[4])); - json_object_object_add(jObj_trafficStats,"pkt.len_grt1500",json_object_new_int64(cumulative_stats.packet_len[5])); - json_object_object_add(jObj_trafficStats,"guessed.flow.protos",json_object_new_int(cumulative_stats.guessed_flow_protocols)); - - json_object_object_add(jObj_main,"traffic.statistics",jObj_trafficStats); - } -#endif - } - - if((!json_flag) && (!quiet_mode)) printf("\n\nDetected protocols:\n"); + if(!quiet_mode) printf("\n\nDetected protocols:\n"); for(i = 0; i <= ndpi_get_num_supported_protocols(ndpi_thread_info[0].workflow->ndpi_struct); i++) { ndpi_protocol_breed_t breed = ndpi_get_proto_breed(ndpi_thread_info[0].workflow->ndpi_struct, i); @@ -2869,34 +2716,20 @@ static void printResults(u_int64_t processing_time_usec, u_int64_t setup_time_us (long long unsigned int)cumulative_stats.protocol_counter_bytes[i], cumulative_stats.protocol_flows[i]); - if((!json_flag) && (!quiet_mode)) { + if((!quiet_mode)) { printf("\t%-20s packets: %-13llu bytes: %-13llu " "flows: %-13u\n", ndpi_get_proto_name(ndpi_thread_info[0].workflow->ndpi_struct, i), (long long unsigned int)cumulative_stats.protocol_counter[i], (long long unsigned int)cumulative_stats.protocol_counter_bytes[i], cumulative_stats.protocol_flows[i]); - } else { -#ifdef HAVE_JSON_C - if(json_fp) { - jObj = json_object_new_object(); - - json_object_object_add(jObj,"name",json_object_new_string(ndpi_get_proto_name(ndpi_thread_info[0].workflow->ndpi_struct, i))); - json_object_object_add(jObj,"breed",json_object_new_string(ndpi_get_proto_breed_name(ndpi_thread_info[0].workflow->ndpi_struct, breed))); - json_object_object_add(jObj,"packets",json_object_new_int64(cumulative_stats.protocol_counter[i])); - json_object_object_add(jObj,"bytes",json_object_new_int64(cumulative_stats.protocol_counter_bytes[i])); - json_object_object_add(jObj,"flows",json_object_new_int(cumulative_stats.protocol_flows[i])); - - json_object_array_add(jArray_detProto,jObj); - } -#endif } total_flow_bytes += cumulative_stats.protocol_counter_bytes[i]; } } - if((!json_flag) && (!quiet_mode)) { + if((!quiet_mode)) { printf("\n\nProtocol statistics:\n"); for(i=0; i < NUM_BREEDS; i++) { @@ -2912,25 +2745,10 @@ static void printResults(u_int64_t processing_time_usec, u_int64_t setup_time_us printFlowsStats(); - if(json_flag != 0) { -#ifdef HAVE_JSON_C - json_object_object_add(jObj_main,"detected.protos",jArray_detProto); - json_object_object_add(jObj_main,"known.flows",jArray_known_flows); - - if(json_object_array_length(jArray_unknown_flows) != 0) - json_object_object_add(jObj_main,"unknown.flows",jArray_unknown_flows); - - fprintf(json_fp,"%s\n",json_object_to_json_string(jObj_main)); - if(!dont_close_json_fp) fclose(json_fp); -#endif - } - - if(stats_flag || verbose == 3) { + if(verbose == 3) { HASH_SORT(srcStats, port_stats_sort); HASH_SORT(dstStats, port_stats_sort); - } - if(verbose == 3) { printf("\n\nSource Ports Stats:\n"); printPortStats(srcStats); @@ -2938,39 +2756,6 @@ static void printResults(u_int64_t processing_time_usec, u_int64_t setup_time_us printPortStats(dstStats); } - if(stats_flag) { -#ifdef HAVE_JSON_C - json_object *jObj_stats = json_object_new_object(); - char timestamp[64]; - int count; - - strftime(timestamp, sizeof(timestamp), "%FT%TZ", localtime(&pcap_start.tv_sec)); - json_object_object_add(jObj_stats, "time", json_object_new_string(timestamp)); - - saveScannerStats(&jObj_stats, &scannerHosts); - - if((count = HASH_COUNT(topReceivers)) == 0) { - HASH_SORT(receivers, receivers_sort); - saveReceiverStats(&jObj_stats, &receivers, cumulative_stats.ip_packet_count); - } - else{ - HASH_SORT(topReceivers, receivers_sort); - saveReceiverStats(&jObj_stats, &topReceivers, cumulative_stats.ip_packet_count); - } - - u_int64_t total_src_addr = getTopStats(srcStats); - u_int64_t total_dst_addr = getTopStats(dstStats); - - saveTopStats(&jObj_stats, &srcStats, DIR_SRC, - cumulative_stats.ndpi_flow_count, total_src_addr); - - saveTopStats(&jObj_stats, &dstStats, DIR_DST, - cumulative_stats.ndpi_flow_count, total_dst_addr); - - json_object_array_add(jArray_topStats, jObj_stats); -#endif - } - free_stats: if(scannerHosts) { deleteScanners(scannerHosts); @@ -3093,6 +2878,7 @@ static pcap_t * openPcapFileOrDevice(u_int16_t thread_id, const u_char * pcap_fi if(dpdk_port_init(dpdk_port_id, mbuf_pool) != 0) rte_exit(EXIT_FAILURE, "DPDK: Cannot init port %u: please see README.dpdk\n", dpdk_port_id); #else + /* Trying to open the interface */ if((pcap_handle = pcap_open_live((char*)pcap_file, snaplen, promisc, 500, pcap_error_buffer)) == NULL) { capture_for = capture_until = 0; @@ -3100,28 +2886,31 @@ static pcap_t * openPcapFileOrDevice(u_int16_t thread_id, const u_char * pcap_fi live_capture = 0; num_threads = 1; /* Open pcap files in single threads mode */ - /* trying to open a pcap file */ + /* Trying to open a pcap file */ if((pcap_handle = pcap_open_offline((char*)pcap_file, pcap_error_buffer)) == NULL) { char filename[256] = { 0 }; if(strstr((char*)pcap_file, (char*)".pcap")) printf("ERROR: could not open pcap file %s: %s\n", pcap_file, pcap_error_buffer); + + /* Trying to open as a playlist as last attempt */ else if((getNextPcapFileFromPlaylist(thread_id, filename, sizeof(filename)) != 0) || ((pcap_handle = pcap_open_offline(filename, pcap_error_buffer)) == NULL)) { - printf("ERROR: could not open playlist %s: %s\n", filename, pcap_error_buffer); + /* This probably was a bad interface name, printing a generic error */ + printf("ERROR: could not open %s: %s\n", filename, pcap_error_buffer); exit(-1); } else { - if((!json_flag) && (!quiet_mode)) + if((!quiet_mode)) printf("Reading packets from playlist %s...\n", pcap_file); } } else { - if((!json_flag) && (!quiet_mode)) + if((!quiet_mode)) printf("Reading packets from pcap file %s...\n", pcap_file); } } else { live_capture = 1; - if((!json_flag) && (!quiet_mode)) { + if((!quiet_mode)) { #ifdef USE_DPDK printf("Capturing from DPDK (port 0)...\n"); #else @@ -3134,7 +2923,7 @@ static pcap_t * openPcapFileOrDevice(u_int16_t thread_id, const u_char * pcap_fi #endif /* !DPDK */ if(capture_for > 0) { - if((!json_flag) && (!quiet_mode)) + if((!quiet_mode)) printf("Capturing traffic up to %u seconds\n", (unsigned int)capture_for); #ifndef WIN32 @@ -3156,10 +2945,13 @@ static void ndpi_process_packet(u_char *args, u_int16_t thread_id = *((u_int16_t*)args); /* allocate an exact size buffer to check overflows */ - uint8_t *packet_checked = malloc(header->caplen); + uint8_t *packet_checked = ndpi_malloc(header->caplen); + if(packet_checked == NULL){ + return ; + } memcpy(packet_checked, packet, header->caplen); - p = ndpi_workflow_process_packet(ndpi_thread_info[thread_id].workflow, header, packet_checked); + p = ndpi_workflow_process_packet(ndpi_thread_info[thread_id].workflow, header, packet_checked, csv_fp); if(!pcap_start.tv_sec) pcap_start.tv_sec = header->ts.tv_sec, pcap_start.tv_usec = header->ts.tv_usec; pcap_end.tv_sec = header->ts.tv_sec, pcap_end.tv_usec = header->ts.tv_usec; @@ -3262,7 +3054,10 @@ static void ndpi_process_packet(u_char *args, Leave the free as last statement to avoid crashes when ndpi_detection_giveup() is called above by printResults() */ - free(packet_checked); + if(packet_checked){ + ndpi_free(packet_checked); + packet_checked = NULL; + } } /** @@ -3270,7 +3065,8 @@ static void ndpi_process_packet(u_char *args, */ static void runPcapLoop(u_int16_t thread_id) { if((!shutdown_app) && (ndpi_thread_info[thread_id].workflow->pcap_handle != NULL)) - pcap_loop(ndpi_thread_info[thread_id].workflow->pcap_handle, -1, &ndpi_process_packet, (u_char*)&thread_id); + if(pcap_loop(ndpi_thread_info[thread_id].workflow->pcap_handle, -1, &ndpi_process_packet, (u_char*)&thread_id) < 0) + printf("Error while reading pcap file: '%s'\n", pcap_geterr(ndpi_thread_info[thread_id].workflow->pcap_handle)); } /** @@ -3290,11 +3086,11 @@ void * processing_thread(void *_thread_id) { if(pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) != 0) fprintf(stderr, "Error while binding thread %ld to core %d\n", thread_id, core_affinity[thread_id]); else { - if((!json_flag) && (!quiet_mode)) printf("Running thread %ld on core %d...\n", thread_id, core_affinity[thread_id]); + if((!quiet_mode)) printf("Running thread %ld on core %d...\n", thread_id, core_affinity[thread_id]); } } else #endif - if((!json_flag) && (!quiet_mode)) printf("Running thread %ld...\n", thread_id); + if((!quiet_mode)) printf("Running thread %ld...\n", thread_id); #ifdef USE_DPDK while(dpdk_run_capture) { @@ -3345,15 +3141,9 @@ void * processing_thread(void *_thread_id) { * @brief Begin, process, end detection process */ void test_lib() { - struct timeval end; u_int64_t processing_time_usec, setup_time_usec; long thread_id; -#ifdef HAVE_JSON_C - json_init(); - if(stats_flag) json_open_stats_file(); -#endif - #ifdef DEBUG_TRACE if(trace) fprintf(trace, "Num threads: %d\n", num_threads); #endif @@ -3397,6 +3187,10 @@ void test_lib() { } } +#ifdef USE_DPDK + dpdk_port_deinit(dpdk_port_id); +#endif + gettimeofday(&end, NULL); processing_time_usec = end.tv_sec*1000000 + end.tv_usec - (begin.tv_sec*1000000 + begin.tv_usec); setup_time_usec = begin.tv_sec*1000000 + begin.tv_usec - (startup_time.tv_sec*1000000 + startup_time.tv_usec); @@ -3404,287 +3198,222 @@ void test_lib() { /* Printing cumulative results */ printResults(processing_time_usec, setup_time_usec); - if(stats_flag) { -#ifdef HAVE_JSON_C - json_close_stats_file(); -#endif - } - for(thread_id = 0; thread_id < num_threads; thread_id++) { if(ndpi_thread_info[thread_id].workflow->pcap_handle != NULL) pcap_close(ndpi_thread_info[thread_id].workflow->pcap_handle); terminateDetection(thread_id); } - -#ifdef HAVE_JSON_C - json_destroy(); -#endif } /* *********************************************** */ -void automataUnitTest() { - void *automa; - - assert((automa = ndpi_init_automa())); - assert(ndpi_add_string_to_automa(automa, "hello") == 0); - assert(ndpi_add_string_to_automa(automa, "world") == 0); - ndpi_finalize_automa(automa); - assert(ndpi_match_string(automa, "This is the wonderful world of nDPI") == 1); - ndpi_free_automa(automa); -} +static void binUnitTest() { + struct ndpi_bin *bins, b0, b1; + u_int8_t versbose = 0; + u_int8_t num_bins = 32; + u_int8_t num_points = 24; + u_int32_t i, j; + u_int8_t num_clusters = 3; + u_int16_t cluster_ids[256]; + char out_buf[128]; -/* *********************************************** */ + srand(time(NULL)); -void serializerUnitTest() { - ndpi_serializer serializer, deserializer; - int i; - u_int8_t trace = 0; + assert((bins = (struct ndpi_bin*)ndpi_malloc(sizeof(struct ndpi_bin)*num_bins)) != NULL); - assert(ndpi_init_serializer(&serializer, ndpi_serialization_format_tlv) != -1); + for(i=0; i= 4, <= 16 */ + u_int32_t i; - if(src_host_array[0] != NULL) { - if(port_array[0] != INIT_VAL) - l += snprintf(&filter[l], sizeof(filter)-l, " and not (src "); - else - l += snprintf(&filter[l], sizeof(filter)-l, "not (src "); + assert(ndpi_hll_init(&h, bits) == 0); - i = 0; + for(i=0; i<21320; i++) + ndpi_hll_add_number(&h, i); - while(i < sh_size && src_host_array[i] != NULL) { - if(i+1 == sh_size || src_host_array[i+1] == NULL) - l += snprintf(&filter[l], sizeof(filter)-l, "%s", src_host_array[i]); - else - l += snprintf(&filter[l], sizeof(filter)-l, "%s or ", src_host_array[i]); + /* printf("Count estimate: %f\n", ndpi_hll_count(&h)); */ - i++; - } + ndpi_hll_destroy(&h); +} - l += snprintf(&filter[l], sizeof(filter)-l, "%s", ")"); - produced = 1; - } +/* *********************************************** */ - if(dst_host_array[0] != NULL) { - if(port_array[0] != INIT_VAL || src_host_array[0] != NULL) - l += snprintf(&filter[l], sizeof(filter)-l, " and not (dst "); - else - l += snprintf(&filter[l], sizeof(filter)-l, "not (dst "); +static void bitmapUnitTest() { + u_int32_t val, i, j; - i=0; + for(i=0; i<32; i++) { + NDPI_ZERO_BIT(val); + NDPI_SET_BIT(val, i); - while(i < dh_size && dst_host_array[i] != NULL) { - if(i+1 == dh_size || dst_host_array[i+1] == NULL) - l += snprintf(&filter[l], sizeof(filter)-l, "%s", dst_host_array[i]); - else - l += snprintf(&filter[l], sizeof(filter)-l, "%s or ", dst_host_array[i]); + assert(NDPI_ISSET_BIT(val, i)); - i++; + for(j=0; j<32; j++) { + if(j != i) { + assert(!NDPI_ISSET_BIT(val, j)); + } } - - l += snprintf(&filter[l], sizeof(filter)-l, "%s", ")"); - produced = 1; } +} - if(produced) - json_object_object_add(*jObj_bpfFilter, "pkt.peak.filter", json_object_new_string(filter)); - else - json_object_object_add(*jObj_bpfFilter, "pkt.peak.filter", json_object_new_string("")); +/* *********************************************** */ + +void automataUnitTest() { + void *automa = ndpi_init_automa(); + + assert(automa); + assert(ndpi_add_string_to_automa(automa, "hello") == 0); + assert(ndpi_add_string_to_automa(automa, "world") == 0); + ndpi_finalize_automa(automa); + assert(ndpi_match_string(automa, "This is the wonderful world of nDPI") == 1); + ndpi_free_automa(automa); } -#endif /* *********************************************** */ -/** - * @brief Produce bpf filter to filter ports and hosts - * in order to remove a peak in terms of number of source - * addresses. - */ -#ifdef HAVE_JSON_C -void bpf_filter_host_peak_filter(json_object **jObj_bpfFilter, - const char *host_array[16], - int h_size) { - char filter[2048]; - int produced = 0; - int i = 0; +// #define RUN_DATA_ANALYSIS_THEN_QUIT 1 - if(host_array[0] != NULL) { - int l; +void analyzeUnitTest() { + struct ndpi_analyze_struct *s = ndpi_alloc_data_analysis(32); + u_int32_t i; - strcpy(filter, "not (dst "); + for(i=0; i<256; i++) { + ndpi_data_add_value(s, rand()*i); + // ndpi_data_add_value(s, i+1); + } - while(i < h_size && host_array[i] != NULL) { - l = strlen(filter); + // ndpi_data_print_window_values(s); - if(i+1 == h_size || host_array[i+1] == NULL) - snprintf(&filter[l], sizeof(filter)-l, "%s", host_array[i]); - else - snprintf(&filter[l], sizeof(filter)-l, "%s or ", host_array[i]); +#ifdef RUN_DATA_ANALYSIS_THEN_QUIT + printf("Average: [all: %f][window: %f]\n", + ndpi_data_average(s), ndpi_data_window_average(s)); + printf("Entropy: %f\n", ndpi_data_entropy(s)); - i++; - } + printf("Min/Max: %u/%u\n", + ndpi_data_min(s), ndpi_data_max(s)); +#endif - l = strlen(filter); - snprintf(&filter[l], sizeof(filter)-l, "%s", ")"); - produced = 1; - } + ndpi_free_data_analysis(s); - if(produced) - json_object_object_add(*jObj_bpfFilter, "host.peak.filter", json_object_new_string(filter)); - else - json_object_object_add(*jObj_bpfFilter, "host.peak.filter", json_object_new_string("")); -} +#ifdef RUN_DATA_ANALYSIS_THEN_QUIT + exit(0); #endif +} /* *********************************************** */ /** @@ -3750,373 +3479,28 @@ void bpf_filter_port_array_add(int filter_array[], int size, int port) { exit(-1); } - -/* *********************************************** */ -#ifdef HAVE_JSON_C -/* - * @brief returns average value for a given field - */ -float getAverage(struct json_object *jObj_stat, char *field) { - json_object *field_stat; - json_bool res; - float sum = 0; - int r; - int j = 0; - - if((r = strcmp(field, "top.scanner.stats")) == 0) { - for(j=0; j FLOWS_PACKETS_THRESHOLD) - && (flows_percent >= FLOWS_PERCENT_THRESHOLD) - && packets_number >= threshold) { - if((res = json_object_object_get_ex(src_pkts_stat, "port", &jObj_port)) == 0) { - fprintf(stderr, "ERROR: can't get \"port\", use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - int port = json_object_get_int(jObj_port); - - bpf_filter_port_array_add(srcPortArray, size, port); - } - } -} -#endif - -/* *********************************************** */ - -#ifdef HAVE_JSON_C -void getReceiverHosts(struct json_object *jObj_stat, const char *dstHostArray[16], int size) { - int j; - - for(j=0; j PKTS_PERCENT_THRESHOLD) { - if((res = json_object_object_get_ex(scanner_stat, "ip.address", &jObj_host_address)) == 0) { - fprintf(stderr, "ERROR: can't get \"ip.address, use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - const char *host_address = json_object_get_string(jObj_host_address); - - bpf_filter_host_array_add(dstHostArray, size, host_address); - } - } -} -#endif - /* *********************************************** */ -#ifdef HAVE_JSON_C -void getScannerHosts(struct json_object *jObj_stat, int duration, - const char *srcHostArray[48], int size, - float threshold) { - int j; - - for(j=0; j FLOWS_THRESHOLD) && tot_flows_number > threshold) { - if((res = json_object_object_get_ex(scanner_stat, "ip.address", &jObj_host_address)) == 0) { - fprintf(stderr, "ERROR: can't get \"ip.address\", use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - const char *host_address = json_object_get_string(jObj_host_address); - - bpf_filter_host_array_add(srcHostArray, size, host_address); - - } - } -} -#endif - -/* *********************************************** */ - -#ifdef HAVE_JSON_C -void getDestinationHosts(struct json_object *jObj_stat, int duration, - const char *dstHostArray[16], int size) { - int j; - - for(j=0; j FLOWS_PERCENT_THRESHOLD_2) { - if((res = json_object_object_get_ex(scanner_stat, "aggressive.host", &jObj_host_address)) == 0) { - fprintf(stderr, "ERROR: can't get \"aggressive.host\", use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - const char *host_address = json_object_get_string(jObj_host_address); - - bpf_filter_host_array_add(dstHostArray, size, host_address); - - } - } -} -#endif - -/* *********************************************** */ - -#ifdef HAVE_JSON_C -static void produceBpfFilter(char *filePath) { - json_object *jObj; /* entire json object from file */ - json_object *jObj_duration; - json_object *jObj_statistics; /* json array */ - json_bool res; - int filterSrcPorts[PORT_ARRAY_SIZE]; - const char *filterSrcHosts[48]; - const char *filterDstHosts[48]; - const char *filterPktDstHosts[48]; - struct stat statbuf; - FILE *fp = NULL; - char _filterFilePath[1024]; - json_object *jObj_bpfFilter; - void *fmap; - int fsock; - float average; - float deviation; - int duration; - int typeCheck; - int array_len; - int i; - - if((fsock = open(filePath, O_RDONLY)) == -1) { - fprintf(stderr,"error opening file %s\n", filePath); - exit(-1); - } - - if(fstat(fsock, &statbuf) == -1) { - fprintf(stderr,"error getting file stat\n"); - exit(-1); - } - - if((fmap = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fsock, 0)) == MAP_FAILED) { - fprintf(stderr,"error mmap is failed\n"); - exit(-1); - } - - if((jObj = json_tokener_parse(fmap)) == NULL) { - fprintf(stderr,"ERROR: invalid json file. Use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - - - if((res = json_object_object_get_ex(jObj, "duration.in.seconds", &jObj_duration)) == 0) { - fprintf(stderr,"ERROR: can't get \"duration.in.seconds\", use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - duration = json_object_get_int(jObj_duration); - - - if((res = json_object_object_get_ex(jObj, "statistics", &jObj_statistics)) == 0) { - fprintf(stderr,"ERROR: can't get \"statistics\", use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - - if((typeCheck = json_object_is_type(jObj_statistics, json_type_array)) == 0) { - fprintf(stderr,"ERROR: invalid json file. Use -x flag only with .json files generated by ndpiReader -b flag.\n"); - exit(-1); - } - array_len = json_object_array_length(jObj_statistics); - - - bpf_filter_port_array_init(filterSrcPorts, PORT_ARRAY_SIZE); - bpf_filter_host_array_init(filterSrcHosts, HOST_ARRAY_SIZE); - bpf_filter_host_array_init(filterDstHosts, HOST_ARRAY_SIZE); - bpf_filter_host_array_init(filterPktDstHosts, HOST_ARRAY_SIZE/2); - - for(i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_FLOW_ROOTS_PER_THREAD 2048 +#define MAX_IDLE_FLOWS_PER_THREAD 64 +#define TICK_RESOLUTION 1000 +#define MAX_READER_THREADS 4 +#define IDLE_SCAN_PERIOD 10000 /* msec */ +#define MAX_IDLE_TIME 300000 /* msec */ +#define INITIAL_THREAD_HASH 0x03dd018b + +#ifndef ETH_P_IP +#define ETH_P_IP 0x0800 +#endif + +#ifndef ETH_P_IPV6 +#define ETH_P_IPV6 0x86DD +#endif + +#ifndef ETH_P_ARP +#define ETH_P_ARP 0x0806 +#endif + +enum nDPI_l3_type { + L3_IP, L3_IP6 +}; + +struct nDPI_flow_info { + uint32_t flow_id; + unsigned long long int packets_processed; + uint64_t first_seen; + uint64_t last_seen; + uint64_t hashval; + + enum nDPI_l3_type l3_type; + union { + struct { + uint32_t src; + uint32_t dst; + } v4; + struct { + uint64_t src[2]; + uint64_t dst[2]; + } v6; + } ip_tuple; + + unsigned long long int total_l4_data_len; + uint16_t src_port; + uint16_t dst_port; + + uint8_t is_midstream_flow:1; + uint8_t flow_fin_ack_seen:1; + uint8_t flow_ack_seen:1; + uint8_t detection_completed:1; + uint8_t tls_client_hello_seen:1; + uint8_t tls_server_hello_seen:1; + uint8_t reserved_00:2; + uint8_t l4_protocol; + + struct ndpi_proto detected_l7_protocol; + struct ndpi_proto guessed_protocol; + + struct ndpi_flow_struct * ndpi_flow; + struct ndpi_id_struct * ndpi_src; + struct ndpi_id_struct * ndpi_dst; +}; + +struct nDPI_workflow { + pcap_t * pcap_handle; + + uint8_t error_or_eof:1; + uint8_t reserved_00:7; + uint8_t reserved_01[3]; + + unsigned long long int packets_captured; + unsigned long long int packets_processed; + unsigned long long int total_l4_data_len; + unsigned long long int detected_flow_protocols; + + uint64_t last_idle_scan_time; + uint64_t last_time; + + void ** ndpi_flows_active; + unsigned long long int max_active_flows; + unsigned long long int cur_active_flows; + unsigned long long int total_active_flows; + + void ** ndpi_flows_idle; + unsigned long long int max_idle_flows; + unsigned long long int cur_idle_flows; + unsigned long long int total_idle_flows; + + struct ndpi_detection_module_struct * ndpi_struct; +}; + +struct nDPI_reader_thread { + struct nDPI_workflow * workflow; + pthread_t thread_id; + int array_index; +}; + +static struct nDPI_reader_thread reader_threads[MAX_READER_THREADS] = {}; +static int reader_thread_count = MAX_READER_THREADS; +static int main_thread_shutdown = 0; +static uint32_t flow_id = 0; + +static void free_workflow(struct nDPI_workflow ** const workflow); + +static struct nDPI_workflow * init_workflow(char const * const file_or_device) +{ + char pcap_error_buffer[PCAP_ERRBUF_SIZE]; + struct nDPI_workflow * workflow = (struct nDPI_workflow *)ndpi_calloc(1, sizeof(*workflow)); + + if (workflow == NULL) { + return NULL; + } + + if (access(file_or_device, R_OK) != 0 && errno == ENOENT) { + workflow->pcap_handle = pcap_open_live(file_or_device, /* 1536 */ 65535, 1, 250, pcap_error_buffer); + } else { + workflow->pcap_handle = pcap_open_offline_with_tstamp_precision(file_or_device, PCAP_TSTAMP_PRECISION_MICRO, + pcap_error_buffer); + } + + if (workflow->pcap_handle == NULL) { + fprintf(stderr, "pcap_open_live / pcap_open_offline_with_tstamp_precision: %.*s\n", + (int) PCAP_ERRBUF_SIZE, pcap_error_buffer); + free_workflow(&workflow); + return NULL; + } + + ndpi_init_prefs init_prefs = ndpi_no_prefs; + workflow->ndpi_struct = ndpi_init_detection_module(init_prefs); + if (workflow->ndpi_struct == NULL) { + free_workflow(&workflow); + return NULL; + } + + workflow->total_active_flows = 0; + workflow->max_active_flows = MAX_FLOW_ROOTS_PER_THREAD; + workflow->ndpi_flows_active = (void **)ndpi_calloc(workflow->max_active_flows, sizeof(void *)); + if (workflow->ndpi_flows_active == NULL) { + free_workflow(&workflow); + return NULL; + } + + workflow->total_idle_flows = 0; + workflow->max_idle_flows = MAX_IDLE_FLOWS_PER_THREAD; + workflow->ndpi_flows_idle = (void **)ndpi_calloc(workflow->max_idle_flows, sizeof(void *)); + if (workflow->ndpi_flows_idle == NULL) { + free_workflow(&workflow); + return NULL; + } + + NDPI_PROTOCOL_BITMASK protos; + NDPI_BITMASK_SET_ALL(protos); + ndpi_set_protocol_detection_bitmask2(workflow->ndpi_struct, &protos); + ndpi_finalize_initalization(workflow->ndpi_struct); + + return workflow; +} + +static void ndpi_flow_info_freer(void * const node) +{ + struct nDPI_flow_info * const flow = (struct nDPI_flow_info *)node; + + ndpi_free(flow->ndpi_dst); + ndpi_free(flow->ndpi_src); + ndpi_flow_free(flow->ndpi_flow); + ndpi_free(flow); +} + +static void free_workflow(struct nDPI_workflow ** const workflow) +{ + struct nDPI_workflow * const w = *workflow; + + if (w == NULL) { + return; + } + + if (w->pcap_handle != NULL) { + pcap_close(w->pcap_handle); + w->pcap_handle = NULL; + } + + if (w->ndpi_struct != NULL) { + ndpi_exit_detection_module(w->ndpi_struct); + } + for(size_t i = 0; i < w->max_active_flows; i++) { + ndpi_tdestroy(w->ndpi_flows_active[i], ndpi_flow_info_freer); + } + ndpi_free(w->ndpi_flows_active); + ndpi_free(w->ndpi_flows_idle); + ndpi_free(w); + *workflow = NULL; +} + +static char * get_default_pcapdev(char *errbuf) +{ + char * ifname; + pcap_if_t * all_devices = NULL; + + if (pcap_findalldevs(&all_devices, errbuf) != 0) + { + return NULL; + } + + ifname = strdup(all_devices[0].name); + pcap_freealldevs(all_devices); + + return ifname; +} + +static int setup_reader_threads(char const * const file_or_device) +{ + char * file_or_default_device; + char pcap_error_buffer[PCAP_ERRBUF_SIZE]; + + if (reader_thread_count > MAX_READER_THREADS) { + return 1; + } + + if (file_or_device == NULL) { + file_or_default_device = get_default_pcapdev(pcap_error_buffer); + if (file_or_default_device == NULL) { + fprintf(stderr, "pcap_findalldevs: %.*s\n", (int) PCAP_ERRBUF_SIZE, pcap_error_buffer); + return 1; + } + } else { + file_or_default_device = strdup(file_or_device); + if (file_or_default_device == NULL) { + return 1; + } + } + + for (int i = 0; i < reader_thread_count; ++i) { + reader_threads[i].workflow = init_workflow(file_or_default_device); + if (reader_threads[i].workflow == NULL) + { + free(file_or_default_device); + return 1; + } + } + + free(file_or_default_device); + return 0; +} + +static int ip_tuple_to_string(struct nDPI_flow_info const * const flow, + char * const src_addr_str, size_t src_addr_len, + char * const dst_addr_str, size_t dst_addr_len) +{ + switch (flow->l3_type) { + case L3_IP: + return inet_ntop(AF_INET, (struct sockaddr_in *)&flow->ip_tuple.v4.src, + src_addr_str, src_addr_len) != NULL && + inet_ntop(AF_INET, (struct sockaddr_in *)&flow->ip_tuple.v4.dst, + dst_addr_str, dst_addr_len) != NULL; + case L3_IP6: + return inet_ntop(AF_INET6, (struct sockaddr_in6 *)&flow->ip_tuple.v6.src[0], + src_addr_str, src_addr_len) != NULL && + inet_ntop(AF_INET6, (struct sockaddr_in6 *)&flow->ip_tuple.v6.dst[0], + dst_addr_str, dst_addr_len) != NULL; + } + + return 0; +} + +#ifdef VERBOSE +static void print_packet_info(struct nDPI_reader_thread const * const reader_thread, + struct pcap_pkthdr const * const header, + uint32_t l4_data_len, + struct nDPI_flow_info const * const flow) +{ + struct nDPI_workflow const * const workflow = reader_thread->workflow; + char src_addr_str[INET6_ADDRSTRLEN+1] = {0}; + char dst_addr_str[INET6_ADDRSTRLEN+1] = {0}; + char buf[256]; + int used = 0, ret; + + ret = snprintf(buf, sizeof(buf), "[%8llu, %d, %4u] %4u bytes: ", + workflow->packets_captured, reader_thread->array_index, + flow->flow_id, header->caplen); + if (ret > 0) { + used += ret; + } + + if (ip_tuple_to_string(flow, src_addr_str, sizeof(src_addr_str), dst_addr_str, sizeof(dst_addr_str)) != 0) { + ret = snprintf(buf + used, sizeof(buf) - used, "IP[%s -> %s]", src_addr_str, dst_addr_str); + } else { + ret = snprintf(buf + used, sizeof(buf) - used, "IP[ERROR]"); + } + if (ret > 0) { + used += ret; + } + + switch (flow->l4_protocol) { + case IPPROTO_UDP: + ret = snprintf(buf + used, sizeof(buf) - used, " -> UDP[%u -> %u, %u bytes]", + flow->src_port, flow->dst_port, l4_data_len); + break; + case IPPROTO_TCP: + ret = snprintf(buf + used, sizeof(buf) - used, " -> TCP[%u -> %u, %u bytes]", + flow->src_port, flow->dst_port, l4_data_len); + break; + case IPPROTO_ICMP: + ret = snprintf(buf + used, sizeof(buf) - used, " -> ICMP"); + break; + case IPPROTO_ICMPV6: + ret = snprintf(buf + used, sizeof(buf) - used, " -> ICMP6"); + break; + case IPPROTO_HOPOPTS: + ret = snprintf(buf + used, sizeof(buf) - used, " -> ICMP6 Hop-By-Hop"); + break; + default: + ret = snprintf(buf + used, sizeof(buf) - used, " -> Unknown[0x%X]", flow->l4_protocol); + break; + } + if (ret > 0) { + used += ret; + } + + printf("%.*s\n", used, buf); +} +#endif + +static int ip_tuples_equal(struct nDPI_flow_info const * const A, + struct nDPI_flow_info const * const B) +{ + if (A->l3_type == L3_IP && B->l3_type == L3_IP6) { + return A->ip_tuple.v4.src == B->ip_tuple.v4.src && + A->ip_tuple.v4.dst == B->ip_tuple.v4.dst; + } else if (A->l3_type == L3_IP6 && B->l3_type == L3_IP6) { + return A->ip_tuple.v6.src[0] == B->ip_tuple.v6.src[0] && + A->ip_tuple.v6.src[1] == B->ip_tuple.v6.src[1] && + A->ip_tuple.v6.dst[0] == B->ip_tuple.v6.dst[0] && + A->ip_tuple.v6.dst[1] == B->ip_tuple.v6.dst[1]; + } + return 0; +} + +static int ip_tuples_compare(struct nDPI_flow_info const * const A, + struct nDPI_flow_info const * const B) +{ + if (A->l3_type == L3_IP && B->l3_type == L3_IP6) { + if (A->ip_tuple.v4.src < B->ip_tuple.v4.src || + A->ip_tuple.v4.dst < B->ip_tuple.v4.dst) + { + return -1; + } + if (A->ip_tuple.v4.src > B->ip_tuple.v4.src || + A->ip_tuple.v4.dst > B->ip_tuple.v4.dst) + { + return 1; + } + } else if (A->l3_type == L3_IP6 && B->l3_type == L3_IP6) { + if ((A->ip_tuple.v6.src[0] < B->ip_tuple.v6.src[0] && + A->ip_tuple.v6.src[1] < B->ip_tuple.v6.src[1]) || + (A->ip_tuple.v6.dst[0] < B->ip_tuple.v6.dst[0] && + A->ip_tuple.v6.dst[1] < B->ip_tuple.v6.dst[1])) + { + return -1; + } + if ((A->ip_tuple.v6.src[0] > B->ip_tuple.v6.src[0] && + A->ip_tuple.v6.src[1] > B->ip_tuple.v6.src[1]) || + (A->ip_tuple.v6.dst[0] > B->ip_tuple.v6.dst[0] && + A->ip_tuple.v6.dst[1] > B->ip_tuple.v6.dst[1])) + { + return 1; + } + } + if (A->src_port < B->src_port || + A->dst_port < B->dst_port) + { + return -1; + } else if (A->src_port > B->src_port || + A->dst_port > B->dst_port) + { + return 1; + } + return 0; +} + +static void ndpi_idle_scan_walker(void const * const A, ndpi_VISIT which, int depth, void * const user_data) +{ + struct nDPI_workflow * const workflow = (struct nDPI_workflow *)user_data; + struct nDPI_flow_info * const flow = *(struct nDPI_flow_info **)A; + + (void)depth; + + if (workflow == NULL || flow == NULL) { + return; + } + + if (workflow->cur_idle_flows == MAX_IDLE_FLOWS_PER_THREAD) { + return; + } + + if (which == ndpi_preorder || which == ndpi_leaf) { + if ((flow->flow_fin_ack_seen == 1 && flow->flow_ack_seen == 1) || + flow->last_seen + MAX_IDLE_TIME < workflow->last_time) + { + char src_addr_str[INET6_ADDRSTRLEN+1]; + char dst_addr_str[INET6_ADDRSTRLEN+1]; + ip_tuple_to_string(flow, src_addr_str, sizeof(src_addr_str), dst_addr_str, sizeof(dst_addr_str)); + workflow->ndpi_flows_idle[workflow->cur_idle_flows++] = flow; + workflow->total_idle_flows++; + } + } +} + +static int ndpi_workflow_node_cmp(void const * const A, void const * const B) { + struct nDPI_flow_info const * const flow_info_a = (struct nDPI_flow_info *)A; + struct nDPI_flow_info const * const flow_info_b = (struct nDPI_flow_info *)B; + + if (flow_info_a->hashval < flow_info_b->hashval) { + return(-1); + } else if (flow_info_a->hashval > flow_info_b->hashval) { + return(1); + } + + /* Flows have the same hash */ + if (flow_info_a->l4_protocol < flow_info_b->l4_protocol) { + return(-1); + } else if (flow_info_a->l4_protocol > flow_info_b->l4_protocol) { + return(1); + } + + if (ip_tuples_equal(flow_info_a, flow_info_b) != 0 && + flow_info_a->src_port == flow_info_b->src_port && + flow_info_a->dst_port == flow_info_b->dst_port) + { + return(0); + } + + return ip_tuples_compare(flow_info_a, flow_info_b); +} + +static void check_for_idle_flows(struct nDPI_workflow * const workflow) +{ + if (workflow->last_idle_scan_time + IDLE_SCAN_PERIOD < workflow->last_time) { + for (size_t idle_scan_index = 0; idle_scan_index < workflow->max_active_flows; ++idle_scan_index) { + ndpi_twalk(workflow->ndpi_flows_active[idle_scan_index], ndpi_idle_scan_walker, workflow); + + while (workflow->cur_idle_flows > 0) { + struct nDPI_flow_info * const f = + (struct nDPI_flow_info *)workflow->ndpi_flows_idle[--workflow->cur_idle_flows]; + if (f->flow_fin_ack_seen == 1) { + printf("Free fin flow with id %u\n", f->flow_id); + } else { + printf("Free idle flow with id %u\n", f->flow_id); + } + ndpi_tdelete(f, &workflow->ndpi_flows_active[idle_scan_index], + ndpi_workflow_node_cmp); + ndpi_flow_info_freer(f); + workflow->cur_active_flows--; + } + } + + workflow->last_idle_scan_time = workflow->last_time; + } +} + +static void ndpi_process_packet(uint8_t * const args, + struct pcap_pkthdr const * const header, + uint8_t const * const packet) +{ + struct nDPI_reader_thread * const reader_thread = + (struct nDPI_reader_thread *)args; + struct nDPI_workflow * workflow; + struct nDPI_flow_info flow = {}; + + size_t hashed_index; + void * tree_result; + struct nDPI_flow_info * flow_to_process; + + int direction_changed = 0; + struct ndpi_id_struct * ndpi_src; + struct ndpi_id_struct * ndpi_dst; + + const struct ndpi_ethhdr * ethernet; + const struct ndpi_iphdr * ip; + struct ndpi_ipv6hdr * ip6; + + uint64_t time_ms; + const uint16_t eth_offset = 0; + uint16_t ip_offset; + uint16_t ip_size; + + const uint8_t * l4_ptr = NULL; + uint16_t l4_len = 0; + + uint16_t type; + int thread_index = INITIAL_THREAD_HASH; // generated with `dd if=/dev/random bs=1024 count=1 |& hd' + + if (reader_thread == NULL) { + return; + } + workflow = reader_thread->workflow; + + if (workflow == NULL) { + return; + } + + workflow->packets_captured++; + time_ms = ((uint64_t) header->ts.tv_sec) * TICK_RESOLUTION + header->ts.tv_usec / (1000000 / TICK_RESOLUTION); + workflow->last_time = time_ms; + + check_for_idle_flows(workflow); + + /* process datalink layer */ + switch (pcap_datalink(workflow->pcap_handle)) { + case DLT_NULL: + if (ntohl(*((uint32_t *)&packet[eth_offset])) == 0x00000002) { + type = ETH_P_IP; + } else { + type = ETH_P_IPV6; + } + ip_offset = 4 + eth_offset; + break; + case DLT_EN10MB: + if (header->len < sizeof(struct ndpi_ethhdr)) { + fprintf(stderr, "[%8llu, %d] Ethernet packet too short - skipping\n", + workflow->packets_captured, reader_thread->array_index); + return; + } + ethernet = (struct ndpi_ethhdr *) &packet[eth_offset]; + ip_offset = sizeof(struct ndpi_ethhdr) + eth_offset; + type = ntohs(ethernet->h_proto); + switch (type) { + case ETH_P_IP: /* IPv4 */ + if (header->len < sizeof(struct ndpi_ethhdr) + sizeof(struct ndpi_iphdr)) { + fprintf(stderr, "[%8llu, %d] IP packet too short - skipping\n", + workflow->packets_captured, reader_thread->array_index); + return; + } + break; + case ETH_P_IPV6: /* IPV6 */ + if (header->len < sizeof(struct ndpi_ethhdr) + sizeof(struct ndpi_ipv6hdr)) { + fprintf(stderr, "[%8llu, %d] IP6 packet too short - skipping\n", + workflow->packets_captured, reader_thread->array_index); + return; + } + break; + case ETH_P_ARP: /* ARP */ + return; + default: + fprintf(stderr, "[%8llu, %d] Unknown Ethernet packet with type 0x%X - skipping\n", + workflow->packets_captured, reader_thread->array_index, type); + return; + } + break; + default: + fprintf(stderr, "[%8llu, %d] Captured non IP/Ethernet packet with datalink type 0x%X - skipping\n", + workflow->packets_captured, reader_thread->array_index, pcap_datalink(workflow->pcap_handle)); + return; + } + + if (type == ETH_P_IP) { + ip = (struct ndpi_iphdr *)&packet[ip_offset]; + ip6 = NULL; + } else if (type == ETH_P_IPV6) { + ip = NULL; + ip6 = (struct ndpi_ipv6hdr *)&packet[ip_offset]; + } else { + fprintf(stderr, "[%8llu, %d] Captured non IPv4/IPv6 packet with type 0x%X - skipping\n", + workflow->packets_captured, reader_thread->array_index, type); + return; + } + ip_size = header->len - ip_offset; + + if (type == ETH_P_IP && header->len >= ip_offset) { + if (header->caplen < header->len) { + fprintf(stderr, "[%8llu, %d] Captured packet size is smaller than packet size: %u < %u\n", + workflow->packets_captured, reader_thread->array_index, header->caplen, header->len); + } + } + + /* process layer3 e.g. IPv4 / IPv6 */ + if (ip != NULL && ip->version == 4) { + if (ip_size < sizeof(*ip)) { + fprintf(stderr, "[%8llu, %d] Packet smaller than IP4 header length: %u < %zu\n", + workflow->packets_captured, reader_thread->array_index, ip_size, sizeof(*ip)); + return; + } + + flow.l3_type = L3_IP; + if (ndpi_detection_get_l4((uint8_t*)ip, ip_size, &l4_ptr, &l4_len, + &flow.l4_protocol, NDPI_DETECTION_ONLY_IPV4) != 0) + { + fprintf(stderr, "[%8llu, %d] nDPI IPv4/L4 payload detection failed, L4 length: %zu\n", + workflow->packets_captured, reader_thread->array_index, ip_size - sizeof(*ip)); + return; + } + + flow.ip_tuple.v4.src = ip->saddr; + flow.ip_tuple.v4.dst = ip->daddr; + uint32_t min_addr = (flow.ip_tuple.v4.src > flow.ip_tuple.v4.dst ? + flow.ip_tuple.v4.dst : flow.ip_tuple.v4.src); + thread_index = min_addr + ip->protocol; + } else if (ip6 != NULL) { + if (ip_size < sizeof(ip6->ip6_hdr)) { + fprintf(stderr, "[%8llu, %d] Packet smaller than IP6 header length: %u < %zu\n", + workflow->packets_captured, reader_thread->array_index, ip_size, sizeof(ip6->ip6_hdr)); + return; + } + + flow.l3_type = L3_IP6; + if (ndpi_detection_get_l4((uint8_t*)ip6, ip_size, &l4_ptr, &l4_len, + &flow.l4_protocol, NDPI_DETECTION_ONLY_IPV6) != 0) + { + fprintf(stderr, "[%8llu, %d] nDPI IPv6/L4 payload detection failed, L4 length: %zu\n", + workflow->packets_captured, reader_thread->array_index, ip_size - sizeof(*ip6)); + return; + } + + flow.ip_tuple.v6.src[0] = ip6->ip6_src.u6_addr.u6_addr64[0]; + flow.ip_tuple.v6.src[1] = ip6->ip6_src.u6_addr.u6_addr64[1]; + flow.ip_tuple.v6.dst[0] = ip6->ip6_dst.u6_addr.u6_addr64[0]; + flow.ip_tuple.v6.dst[1] = ip6->ip6_dst.u6_addr.u6_addr64[1]; + uint64_t min_addr[2]; + if (flow.ip_tuple.v6.src[0] > flow.ip_tuple.v6.dst[0] && + flow.ip_tuple.v6.src[1] > flow.ip_tuple.v6.dst[1]) + { + min_addr[0] = flow.ip_tuple.v6.dst[0]; + min_addr[1] = flow.ip_tuple.v6.dst[0]; + } else { + min_addr[0] = flow.ip_tuple.v6.src[0]; + min_addr[1] = flow.ip_tuple.v6.src[0]; + } + thread_index = min_addr[0] + min_addr[1] + ip6->ip6_hdr.ip6_un1_nxt; + } else { + fprintf(stderr, "[%8llu, %d] Non IP/IPv6 protocol detected: 0x%X\n", + workflow->packets_captured, reader_thread->array_index, type); + return; + } + + /* process layer4 e.g. TCP / UDP */ + if (flow.l4_protocol == IPPROTO_TCP) { + const struct ndpi_tcphdr * tcp; + + if (header->len < (l4_ptr - packet) + sizeof(struct ndpi_tcphdr)) { + fprintf(stderr, "[%8llu, %d] Malformed TCP packet, packet size smaller than expected: %u < %zu\n", + workflow->packets_captured, reader_thread->array_index, + header->len, (l4_ptr - packet) + sizeof(struct ndpi_tcphdr)); + return; + } + tcp = (struct ndpi_tcphdr *)l4_ptr; + flow.is_midstream_flow = (tcp->syn == 0 ? 1 : 0); + flow.flow_fin_ack_seen = (tcp->fin == 1 && tcp->ack == 1 ? 1 : 0); + flow.flow_ack_seen = tcp->ack; + flow.src_port = ntohs(tcp->source); + flow.dst_port = ntohs(tcp->dest); + } else if (flow.l4_protocol == IPPROTO_UDP) { + const struct ndpi_udphdr * udp; + + if (header->len < (l4_ptr - packet) + sizeof(struct ndpi_udphdr)) { + fprintf(stderr, "[%8llu, %d] Malformed UDP packet, packet size smaller than expected: %u < %zu\n", + workflow->packets_captured, reader_thread->array_index, + header->len, (l4_ptr - packet) + sizeof(struct ndpi_udphdr)); + return; + } + udp = (struct ndpi_udphdr *)l4_ptr; + flow.src_port = ntohs(udp->source); + flow.dst_port = ntohs(udp->dest); + } + + /* distribute flows to threads while keeping stability (same flow goes always to same thread) */ + thread_index += (flow.src_port < flow.dst_port ? flow.dst_port : flow.src_port); + thread_index %= reader_thread_count; + if (thread_index != reader_thread->array_index) { + return; + } + workflow->packets_processed++; + workflow->total_l4_data_len += l4_len; + +#ifdef VERBOSE + print_packet_info(reader_thread, header, l4_data_len, &flow); +#endif + + /* calculate flow hash for btree find, search(insert) */ + if (flow.l3_type == L3_IP) { + if (ndpi_flowv4_flow_hash(flow.l4_protocol, flow.ip_tuple.v4.src, flow.ip_tuple.v4.dst, + flow.src_port, flow.dst_port, 0, 0, + (uint8_t *)&flow.hashval, sizeof(flow.hashval)) != 0) + { + flow.hashval = flow.ip_tuple.v4.src + flow.ip_tuple.v4.dst; // fallback + } + } else if (flow.l3_type == L3_IP6) { + if (ndpi_flowv6_flow_hash(flow.l4_protocol, &ip6->ip6_src, &ip6->ip6_dst, + flow.src_port, flow.dst_port, 0, 0, + (uint8_t *)&flow.hashval, sizeof(flow.hashval)) != 0) + { + flow.hashval = flow.ip_tuple.v6.src[0] + flow.ip_tuple.v6.src[1]; + flow.hashval += flow.ip_tuple.v6.dst[0] + flow.ip_tuple.v6.dst[1]; + } + } + flow.hashval += flow.l4_protocol + flow.src_port + flow.dst_port; + + hashed_index = flow.hashval % workflow->max_active_flows; + tree_result = ndpi_tfind(&flow, &workflow->ndpi_flows_active[hashed_index], ndpi_workflow_node_cmp); + if (tree_result == NULL) { + /* flow not found in btree: switch src <-> dst and try to find it again */ + uint64_t orig_src_ip[2] = { flow.ip_tuple.v6.src[0], flow.ip_tuple.v6.src[1] }; + uint64_t orig_dst_ip[2] = { flow.ip_tuple.v6.dst[0], flow.ip_tuple.v6.dst[1] }; + uint16_t orig_src_port = flow.src_port; + uint16_t orig_dst_port = flow.dst_port; + + flow.ip_tuple.v6.src[0] = orig_dst_ip[0]; + flow.ip_tuple.v6.src[1] = orig_dst_ip[1]; + flow.ip_tuple.v6.dst[0] = orig_src_ip[0]; + flow.ip_tuple.v6.dst[1] = orig_src_ip[1]; + flow.src_port = orig_dst_port; + flow.dst_port = orig_src_port; + + tree_result = ndpi_tfind(&flow, &workflow->ndpi_flows_active[hashed_index], ndpi_workflow_node_cmp); + if (tree_result != NULL) { + direction_changed = 1; + } + + flow.ip_tuple.v6.src[0] = orig_src_ip[0]; + flow.ip_tuple.v6.src[1] = orig_src_ip[1]; + flow.ip_tuple.v6.dst[0] = orig_dst_ip[0]; + flow.ip_tuple.v6.dst[1] = orig_dst_ip[1]; + flow.src_port = orig_src_port; + flow.dst_port = orig_dst_port; + } + + if (tree_result == NULL) { + /* flow still not found, must be new */ + if (workflow->cur_active_flows == workflow->max_active_flows) { + fprintf(stderr, "[%8llu, %d] max flows to track reached: %llu, idle: %llu\n", + workflow->packets_captured, reader_thread->array_index, + workflow->max_active_flows, workflow->cur_idle_flows); + return; + } + + flow_to_process = (struct nDPI_flow_info *)ndpi_malloc(sizeof(*flow_to_process)); + if (flow_to_process == NULL) { + fprintf(stderr, "[%8llu, %d] Not enough memory for flow info\n", + workflow->packets_captured, reader_thread->array_index); + return; + } + + workflow->cur_active_flows++; + workflow->total_active_flows++; + memcpy(flow_to_process, &flow, sizeof(*flow_to_process)); + flow_to_process->flow_id = flow_id++; + + flow_to_process->ndpi_flow = (struct ndpi_flow_struct *)ndpi_flow_malloc(SIZEOF_FLOW_STRUCT); + if (flow_to_process->ndpi_flow == NULL) { + fprintf(stderr, "[%8llu, %d, %4u] Not enough memory for flow struct\n", + workflow->packets_captured, reader_thread->array_index, flow_to_process->flow_id); + return; + } + memset(flow_to_process->ndpi_flow, 0, SIZEOF_FLOW_STRUCT); + + flow_to_process->ndpi_src = (struct ndpi_id_struct *)ndpi_calloc(1, SIZEOF_ID_STRUCT); + if (flow_to_process->ndpi_src == NULL) { + fprintf(stderr, "[%8llu, %d, %4u] Not enough memory for src id struct\n", + workflow->packets_captured, reader_thread->array_index, flow_to_process->flow_id); + return; + } + + flow_to_process->ndpi_dst = (struct ndpi_id_struct *)ndpi_calloc(1, SIZEOF_ID_STRUCT); + if (flow_to_process->ndpi_dst == NULL) { + fprintf(stderr, "[%8llu, %d, %4u] Not enough memory for dst id struct\n", + workflow->packets_captured, reader_thread->array_index, flow_to_process->flow_id); + return; + } + + printf("[%8llu, %d, %4u] new %sflow\n", workflow->packets_captured, thread_index, + flow_to_process->flow_id, + (flow_to_process->is_midstream_flow != 0 ? "midstream-" : "")); + if (ndpi_tsearch(flow_to_process, &workflow->ndpi_flows_active[hashed_index], ndpi_workflow_node_cmp) == NULL) { + /* Possible Leak, but should not happen as we'd abort earlier. */ + return; + } + + ndpi_src = flow_to_process->ndpi_src; + ndpi_dst = flow_to_process->ndpi_dst; + } else { + flow_to_process = *(struct nDPI_flow_info **)tree_result; + + if (direction_changed != 0) { + ndpi_src = flow_to_process->ndpi_dst; + ndpi_dst = flow_to_process->ndpi_src; + } else { + ndpi_src = flow_to_process->ndpi_src; + ndpi_dst = flow_to_process->ndpi_dst; + } + } + + flow_to_process->packets_processed++; + flow_to_process->total_l4_data_len += l4_len; + /* update timestamps, important for timeout handling */ + if (flow_to_process->first_seen == 0) { + flow_to_process->first_seen = time_ms; + } + flow_to_process->last_seen = time_ms; + /* current packet is an TCP-ACK? */ + flow_to_process->flow_ack_seen = flow.flow_ack_seen; + + /* TCP-FIN: indicates that at least one side wants to end the connection */ + if (flow.flow_fin_ack_seen != 0 && flow_to_process->flow_fin_ack_seen == 0) { + flow_to_process->flow_fin_ack_seen = 1; + printf("[%8llu, %d, %4u] end of flow\n", workflow->packets_captured, thread_index, + flow_to_process->flow_id); + return; + } + + /* + * This example tries to use maximum supported packets for detection: + * for uint8: 0xFF + */ + if (flow_to_process->ndpi_flow->num_processed_pkts == 0xFF) { + return; + } else if (flow_to_process->ndpi_flow->num_processed_pkts == 0xFE) { + /* last chance to guess something, better then nothing */ + uint8_t protocol_was_guessed = 0; + flow_to_process->guessed_protocol = + ndpi_detection_giveup(workflow->ndpi_struct, + flow_to_process->ndpi_flow, + 1, &protocol_was_guessed); + if (protocol_was_guessed != 0) { + printf("[%8llu, %d, %4d][GUESSED] protocol: %s | app protocol: %s | category: %s\n", + workflow->packets_captured, + reader_thread->array_index, + flow_to_process->flow_id, + ndpi_get_proto_name(workflow->ndpi_struct, flow_to_process->guessed_protocol.master_protocol), + ndpi_get_proto_name(workflow->ndpi_struct, flow_to_process->guessed_protocol.app_protocol), + ndpi_category_get_name(workflow->ndpi_struct, flow_to_process->guessed_protocol.category)); + } else { + printf("[%8llu, %d, %4d][FLOW NOT CLASSIFIED]\n", + workflow->packets_captured, reader_thread->array_index, flow_to_process->flow_id); + } + } + + flow_to_process->detected_l7_protocol = + ndpi_detection_process_packet(workflow->ndpi_struct, flow_to_process->ndpi_flow, + ip != NULL ? (uint8_t *)ip : (uint8_t *)ip6, + ip_size, time_ms, ndpi_src, ndpi_dst); + + if (ndpi_is_protocol_detected(workflow->ndpi_struct, + flow_to_process->detected_l7_protocol) != 0 && + flow_to_process->detection_completed == 0) + { + if (flow_to_process->detected_l7_protocol.master_protocol != NDPI_PROTOCOL_UNKNOWN || + flow_to_process->detected_l7_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN) { + flow_to_process->detection_completed = 1; + workflow->detected_flow_protocols++; + printf("[%8llu, %d, %4d][DETECTED] protocol: %s | app protocol: %s | category: %s\n", + workflow->packets_captured, + reader_thread->array_index, + flow_to_process->flow_id, + ndpi_get_proto_name(workflow->ndpi_struct, flow_to_process->detected_l7_protocol.master_protocol), + ndpi_get_proto_name(workflow->ndpi_struct, flow_to_process->detected_l7_protocol.app_protocol), + ndpi_category_get_name(workflow->ndpi_struct, flow_to_process->detected_l7_protocol.category)); + } + } + + if (flow_to_process->ndpi_flow->num_extra_packets_checked <= + flow_to_process->ndpi_flow->max_extra_packets_to_check) + { + /* + * Your business logic starts here. + * + * This example does print some information about + * TLS client and server hellos if available. + * + * You could also use nDPI's built-in json serialization + * and send it to a high-level application for further processing. + * + * EoE - End of Example + */ + + if (flow_to_process->detected_l7_protocol.master_protocol == NDPI_PROTOCOL_TLS || + flow_to_process->detected_l7_protocol.app_protocol == NDPI_PROTOCOL_TLS) + { + if (flow_to_process->tls_client_hello_seen == 0 && + flow_to_process->ndpi_flow->l4.tcp.tls.hello_processed != 0) + { + uint8_t unknown_tls_version = 0; + printf("[%8llu, %d, %4d][TLS-CLIENT-HELLO] version: %s | sni: %s | alpn: %s\n", + workflow->packets_captured, + reader_thread->array_index, + flow_to_process->flow_id, + ndpi_ssl_version2str(flow_to_process->ndpi_flow, + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.ssl_version, + &unknown_tls_version), + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.client_requested_server_name, + (flow_to_process->ndpi_flow->protos.stun_ssl.ssl.alpn != NULL ? + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.alpn : "-")); + flow_to_process->tls_client_hello_seen = 1; + } + if (flow_to_process->tls_server_hello_seen == 0 && + flow_to_process->ndpi_flow->l4.tcp.tls.certificate_processed != 0) + { + uint8_t unknown_tls_version = 0; + printf("[%8llu, %d, %4d][TLS-SERVER-HELLO] version: %s | common-name(s): %.*s | " + "issuer: %s | subject: %s\n", + workflow->packets_captured, + reader_thread->array_index, + flow_to_process->flow_id, + ndpi_ssl_version2str(flow_to_process->ndpi_flow, + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.ssl_version, + &unknown_tls_version), + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.server_names_len, + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.server_names, + (flow_to_process->ndpi_flow->protos.stun_ssl.ssl.issuerDN != NULL ? + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.issuerDN : "-"), + (flow_to_process->ndpi_flow->protos.stun_ssl.ssl.subjectDN != NULL ? + flow_to_process->ndpi_flow->protos.stun_ssl.ssl.subjectDN : "-")); + flow_to_process->tls_server_hello_seen = 1; + } + } + } +} + +static void run_pcap_loop(struct nDPI_reader_thread const * const reader_thread) +{ + if (reader_thread->workflow != NULL && + reader_thread->workflow->pcap_handle != NULL) { + + if (pcap_loop(reader_thread->workflow->pcap_handle, -1, + &ndpi_process_packet, (uint8_t *)reader_thread) == PCAP_ERROR) { + + fprintf(stderr, "Error while reading pcap file: '%s'\n", + pcap_geterr(reader_thread->workflow->pcap_handle)); + reader_thread->workflow->error_or_eof = 1; + } + } +} + +static void break_pcap_loop(struct nDPI_reader_thread * const reader_thread) +{ + if (reader_thread->workflow != NULL && + reader_thread->workflow->pcap_handle != NULL) + { + pcap_breakloop(reader_thread->workflow->pcap_handle); + } +} + +static void * processing_thread(void * const ndpi_thread_arg) +{ + struct nDPI_reader_thread const * const reader_thread = + (struct nDPI_reader_thread *)ndpi_thread_arg; + + printf("Starting ThreadID %d\n", reader_thread->array_index); + run_pcap_loop(reader_thread); + reader_thread->workflow->error_or_eof = 1; + return NULL; +} + +static int processing_threads_error_or_eof(void) +{ + for (int i = 0; i < reader_thread_count; ++i) { + if (reader_threads[i].workflow->error_or_eof == 0) { + return 0; + } + } + return 1; +} + +static int start_reader_threads(void) +{ + sigset_t thread_signal_set, old_signal_set; + + sigfillset(&thread_signal_set); + sigdelset(&thread_signal_set, SIGINT); + sigdelset(&thread_signal_set, SIGTERM); + if (pthread_sigmask(SIG_BLOCK, &thread_signal_set, &old_signal_set) != 0) { + fprintf(stderr, "pthread_sigmask: %s\n", strerror(errno)); + return 1; + } + + for (int i = 0; i < reader_thread_count; ++i) { + reader_threads[i].array_index = i; + + if (reader_threads[i].workflow == NULL) { + /* no more threads should be started */ + break; + } + + if (pthread_create(&reader_threads[i].thread_id, NULL, + processing_thread, &reader_threads[i]) != 0) + { + fprintf(stderr, "pthread_create: %s\n", strerror(errno)); + return 1; + } + } + + if (pthread_sigmask(SIG_BLOCK, &old_signal_set, NULL) != 0) { + fprintf(stderr, "pthread_sigmask: %s\n", strerror(errno)); + return 1; + } + + return 0; +} + +static int stop_reader_threads(void) +{ + unsigned long long int total_packets_processed = 0; + unsigned long long int total_l4_data_len = 0; + unsigned long long int total_flows_captured = 0; + unsigned long long int total_flows_idle = 0; + unsigned long long int total_flows_detected = 0; + + for (int i = 0; i < reader_thread_count; ++i) { + break_pcap_loop(&reader_threads[i]); + } + + printf("------------------------------------ Stopping reader threads\n"); + + for (int i = 0; i < reader_thread_count; ++i) { + if (reader_threads[i].workflow == NULL) { + continue; + } + + total_packets_processed += reader_threads[i].workflow->packets_processed; + total_l4_data_len += reader_threads[i].workflow->total_l4_data_len; + total_flows_captured += reader_threads[i].workflow->total_active_flows; + total_flows_idle += reader_threads[i].workflow->total_idle_flows; + total_flows_detected += reader_threads[i].workflow->detected_flow_protocols; + + printf("Stopping Thread %d, processed %10llu packets, %12llu bytes, total flows: %8llu, " + "idle flows: %8llu, detected flows: %8llu\n", + reader_threads[i].array_index, reader_threads[i].workflow->packets_processed, + reader_threads[i].workflow->total_l4_data_len, reader_threads[i].workflow->total_active_flows, + reader_threads[i].workflow->total_idle_flows, reader_threads[i].workflow->detected_flow_protocols); + } + /* total packets captured: same value for all threads as packet2thread distribution happens later */ + printf("Total packets captured.: %llu\n", + reader_threads[0].workflow->packets_captured); + printf("Total packets processed: %llu\n", total_packets_processed); + printf("Total layer4 data size.: %llu\n", total_l4_data_len); + printf("Total flows captured...: %llu\n", total_flows_captured); + printf("Total flows timed out..: %llu\n", total_flows_idle); + printf("Total flows detected...: %llu\n", total_flows_detected); + + for (int i = 0; i < reader_thread_count; ++i) { + if (reader_threads[i].workflow == NULL) { + continue; + } + + if (pthread_join(reader_threads[i].thread_id, NULL) != 0) { + fprintf(stderr, "pthread_join: %s\n", strerror(errno)); + } + + free_workflow(&reader_threads[i].workflow); + } + + return 0; +} + +static void sighandler(int signum) +{ + fprintf(stderr, "Received SIGNAL %d\n", signum); + + if (main_thread_shutdown == 0) { + main_thread_shutdown = 1; + if (stop_reader_threads() != 0) { + fprintf(stderr, "Failed to stop reader threads!\n"); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "Reader threads are already shutting down, please be patient.\n"); + } +} + +int main(int argc, char ** argv) +{ + if (argc == 0) { + return 1; + } + + printf("usage: %s [PCAP-FILE-OR-INTERFACE]\n" + "----------------------------------\n" + "nDPI version: %s\n" + " API version: %u\n" + "----------------------------------\n", + argv[0], + ndpi_revision(), ndpi_get_api_version()); + + if (setup_reader_threads((argc >= 2 ? argv[1] : NULL)) != 0) { + fprintf(stderr, "%s: setup_reader_threads failed\n", argv[0]); + return 1; + } + + if (start_reader_threads() != 0) { + fprintf(stderr, "%s: start_reader_threads\n", argv[0]); + return 1; + } + + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); + while (main_thread_shutdown == 0 && processing_threads_error_or_eof() == 0) { + sleep(1); + } + + if (main_thread_shutdown == 0 && stop_reader_threads() != 0) { + fprintf(stderr, "%s: stop_reader_threads\n", argv[0]); + return 1; + } + + return 0; +} diff --git a/example/protos.txt b/example/protos.txt index a840c85..b472d27 100644 --- a/example/protos.txt +++ b/example/protos.txt @@ -10,19 +10,32 @@ tcp:3000@ntop # Format: # host:"",host:"",.....@ -host:"googlesyndication.com"@Google -host:"venere.com"@Venere -host:"kataweb.it",host:"repubblica.it"@Repubblica -host:"ntop"@ntop -host:"atv-ext.amazon.com",host:"*.api.amazon.com",host:"*.api.amazonvideo.com"@AmazonVideo -host:"*.amazonaws.com"@AmazonVideo -host:"*.netflix.com"@Netflix +host:"disneyplus.com"host:"cdn.registerdisney.go.com",host:"disney-portal.my.onetrust.com",host:"disneyplus.bn5x.net",host:"disney-plus.net"@DisneyPlus host:"*.lvlt.dash.us.aiv-cdn.net.c.footprint.net"@AmazonVideo host:"api-global.netflix.com"@Netflix # IP based Subprotocols # Format: # ip:,ip:,.....@ -ip:213.75.170.11@CustomProtocol -ip:8.248.73.247@AmazonPrime +# +# NOTES +# 1) the port of a custom protocol is optional but if +# specified it must match the port. +# 2) you can specify up to 1 port per IP address +# 3) if you specify a custom ip:: rule, +# even if the doesn't match the +# (if best match during the search) will +# have priority as best match. Example if +# you specify a : and +# in your traffic have match for such IP but +# with a port other than 9999, the IP address +# begin a best match will hve preference over +# so this protocol will not be +# detected as .Google but only +# as +# + +ip:213.75.170.11/32:443@CustomProtocol +ip:8.248.73.247:443@AmazonPrime ip:54.80.47.130@AmazonPrime + diff --git a/example/reader_util.c b/example/reader_util.c index 8848f6f..630a820 100644 --- a/example/reader_util.c +++ b/example/reader_util.c @@ -1,7 +1,7 @@ /* * reader_util.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -69,13 +69,19 @@ /* mask for Bad FCF presence */ #define BAD_FCS 0x50 /* 0101 0000 */ -#define GTP_U_V1_PORT 2152 +#define GTP_U_V1_PORT 2152 +#define NDPI_CAPWAP_DATA_PORT 5247 #define TZSP_PORT 37008 #ifndef DLT_LINUX_SLL #define DLT_LINUX_SLL 113 #endif +#define PLEN_MAX 1504 +#define PLEN_BIN_LEN 32 +#define PLEN_NUM_BINS 48 /* 47*32 = 1504 */ +#define MAX_NUM_BIN_PKTS 256 + #include "ndpi_main.h" #include "reader_util.h" #include "ndpi_classify.h" @@ -83,7 +89,6 @@ extern u_int8_t enable_protocol_guess, enable_joy_stats, enable_payload_analyzer; extern u_int8_t verbose, human_readeable_string_len; extern u_int8_t max_num_udp_dissected_pkts /* 8 */, max_num_tcp_dissected_pkts /* 10 */; - static u_int32_t flow_id = 0; /* ****************************************************** */ @@ -123,7 +128,6 @@ void ndpi_analyze_payload(struct ndpi_flow_info *flow, u_int16_t payload_len, u_int32_t packet_id) { struct payload_stats *ret; - u_int i; struct flow_id_stats *f; struct packet_id_stats *p; @@ -135,11 +139,11 @@ void ndpi_analyze_payload(struct ndpi_flow_info *flow, HASH_FIND(hh, pstats, payload, payload_len, ret); if(ret == NULL) { - if((ret = (struct payload_stats*)calloc(1, sizeof(struct payload_stats))) == NULL) + if((ret = (struct payload_stats*)ndpi_calloc(1, sizeof(struct payload_stats))) == NULL) return; /* OOM */ - if((ret->pattern = (u_int8_t*)malloc(payload_len)) == NULL) { - free(ret); + if((ret->pattern = (u_int8_t*)ndpi_malloc(payload_len)) == NULL) { + ndpi_free(ret); return; } @@ -159,7 +163,7 @@ void ndpi_analyze_payload(struct ndpi_flow_info *flow, HASH_FIND_INT(ret->flows, &flow->flow_id, f); if(f == NULL) { - if((f = (struct flow_id_stats*)calloc(1, sizeof(struct flow_id_stats))) == NULL) + if((f = (struct flow_id_stats*)ndpi_calloc(1, sizeof(struct flow_id_stats))) == NULL) return; /* OOM */ f->flow_id = flow->flow_id; @@ -168,7 +172,7 @@ void ndpi_analyze_payload(struct ndpi_flow_info *flow, HASH_FIND_INT(ret->packets, &packet_id, p); if(p == NULL) { - if((p = (struct packet_id_stats*)calloc(1, sizeof(struct packet_id_stats))) == NULL) + if((p = (struct packet_id_stats*)ndpi_calloc(1, sizeof(struct packet_id_stats))) == NULL) return; /* OOM */ p->packet_id = packet_id; @@ -185,7 +189,7 @@ void ndpi_payload_analyzer(struct ndpi_flow_info *flow, u_int16_t i, j; u_int16_t scan_len = ndpi_min(max_packet_payload_dissection, payload_len); - if((flow->entropy.src2dst_pkt_count+flow->entropy.dst2src_pkt_count) <= max_num_packets_per_flow) { + if((flow->src2dst_packets+flow->dst2src_packets) <= max_num_packets_per_flow) { #ifdef DEBUG_PAYLOAD printf("[hashval: %u][proto: %u][vlan: %u][%s:%u <-> %s:%u][direction: %s][payload_len: %u]\n", flow->hashval, flow->protocol, flow->vlan_id, @@ -280,14 +284,14 @@ void ndpi_report_payload_stats() { if(num <= max_num_reported_top_payloads) print_payload_stat(p); - free(p->pattern); + ndpi_free(p->pattern); { struct flow_id_stats *p1, *tmp1; HASH_ITER(hh, p->flows, p1, tmp1) { HASH_DEL(p->flows, p1); - free(p1); + ndpi_free(p1); } } @@ -296,18 +300,16 @@ void ndpi_report_payload_stats() { HASH_ITER(hh, p->packets, p1, tmp1) { HASH_DEL(p->packets, p1); - free(p1); + ndpi_free(p1); } } HASH_DEL(pstats, p); - free(p); + ndpi_free(p); num++; } } - - /* ***************************************************** */ void ndpi_free_flow_info_half(struct ndpi_flow_info *flow) { @@ -321,15 +323,15 @@ void ndpi_free_flow_info_half(struct ndpi_flow_info *flow) { extern u_int32_t current_ndpi_memory, max_ndpi_memory; /** - * @brief malloc wrapper function + * @brief ndpi_malloc wrapper function */ -static void *malloc_wrapper(size_t size) { +static void *ndpi_malloc_wrapper(size_t size) { current_ndpi_memory += size; if(current_ndpi_memory > max_ndpi_memory) max_ndpi_memory = current_ndpi_memory; - return malloc(size); + return(malloc(size)); /* Don't change to ndpi_malloc !!!!! */ } /* ***************************************************** */ @@ -338,7 +340,7 @@ static void *malloc_wrapper(size_t size) { * @brief free wrapper function */ static void free_wrapper(void *freeable) { - free(freeable); + free(freeable); /* Don't change to ndpi_free !!!!! */ } /* ***************************************************** */ @@ -409,20 +411,20 @@ struct ndpi_workflow* ndpi_workflow_init(const struct ndpi_workflow_prefs * pref struct ndpi_detection_module_struct * module; struct ndpi_workflow * workflow; - set_ndpi_malloc(malloc_wrapper), set_ndpi_free(free_wrapper); + set_ndpi_malloc(ndpi_malloc_wrapper), set_ndpi_free(free_wrapper); set_ndpi_flow_malloc(NULL), set_ndpi_flow_free(NULL); - /* TODO: just needed here to init ndpi malloc wrapper */ - module = ndpi_init_detection_module(); + /* TODO: just needed here to init ndpi ndpi_malloc wrapper */ + module = ndpi_init_detection_module(ndpi_no_prefs); if(module == NULL) { - NDPI_LOG(0, NULL, NDPI_LOG_ERROR, "global structure initialization failed\n"); + LOG(NDPI_LOG_ERROR, "global structure initialization failed\n"); exit(-1); } workflow = ndpi_calloc(1, sizeof(struct ndpi_workflow)); if(workflow == NULL) { - NDPI_LOG(0, NULL, NDPI_LOG_ERROR, "global structure initialization failed\n"); + LOG(NDPI_LOG_ERROR, "global structure initialization failed\n"); ndpi_free(module); exit(-1); } @@ -438,13 +440,8 @@ struct ndpi_workflow* ndpi_workflow_init(const struct ndpi_workflow_prefs * pref exit(-1); _debug_protocols_ok = 1; } - -#ifdef NDPI_ENABLE_DEBUG_MESSAGES - NDPI_BITMASK_RESET(module->debug_bitmask); - if(_debug_protocols_ok) - module->debug_bitmask = debug_bitmask; -#endif + ndpi_set_debug_bitmask(module, debug_bitmask); workflow->ndpi_flows_root = ndpi_calloc(workflow->prefs.num_roots, sizeof(void *)); @@ -456,8 +453,48 @@ struct ndpi_workflow* ndpi_workflow_init(const struct ndpi_workflow_prefs * pref void ndpi_flow_info_freer(void *node) { struct ndpi_flow_info *flow = (struct ndpi_flow_info*)node; - ndpi_free_flow_info_half(flow); + ndpi_flow_info_free_data(flow); + ndpi_free(flow); +} +/* ***************************************************** */ + +static void ndpi_free_flow_tls_data(struct ndpi_flow_info *flow) { + + if(flow->ssh_tls.server_names) { + ndpi_free(flow->ssh_tls.server_names); + flow->ssh_tls.server_names = NULL; + } + + if(flow->ssh_tls.tls_alpn) { + ndpi_free(flow->ssh_tls.tls_alpn); + flow->ssh_tls.tls_alpn = NULL; + } + + if(flow->ssh_tls.tls_supported_versions) { + ndpi_free(flow->ssh_tls.tls_supported_versions); + flow->ssh_tls.tls_supported_versions = NULL; + } + + if(flow->ssh_tls.tls_issuerDN) { + ndpi_free(flow->ssh_tls.tls_issuerDN); + flow->ssh_tls.tls_issuerDN = NULL; + } + + if(flow->ssh_tls.tls_subjectDN) { + ndpi_free(flow->ssh_tls.tls_subjectDN); + flow->ssh_tls.tls_subjectDN = NULL; + } + + if(flow->ssh_tls.encrypted_sni.esni) { + ndpi_free(flow->ssh_tls.encrypted_sni.esni); + flow->ssh_tls.encrypted_sni.esni = NULL; + } +} + +/* ***************************************************** */ + +static void ndpi_free_flow_data_analysis(struct ndpi_flow_info *flow) { if(flow->iat_c_to_s) ndpi_free_data_analysis(flow->iat_c_to_s); if(flow->iat_s_to_c) ndpi_free_data_analysis(flow->iat_s_to_c); @@ -465,8 +502,22 @@ void ndpi_flow_info_freer(void *node) { if(flow->pktlen_s_to_c) ndpi_free_data_analysis(flow->pktlen_s_to_c); if(flow->iat_flow) ndpi_free_data_analysis(flow->iat_flow); +} - ndpi_free(flow); +/* ***************************************************** */ + +void ndpi_flow_info_free_data(struct ndpi_flow_info *flow) { + + ndpi_free_flow_info_half(flow); + ndpi_free_flow_data_analysis(flow); + ndpi_free_flow_tls_data(flow); + +#ifdef DIRECTION_BINS + ndpi_free_bin(&flow->payload_len_bin_src2dst); + ndpi_free_bin(&flow->payload_len_bin_dst2src); +#else + ndpi_free_bin(&flow->payload_len_bin); +#endif } /* ***************************************************** */ @@ -478,8 +529,8 @@ void ndpi_workflow_free(struct ndpi_workflow * workflow) { ndpi_tdestroy(workflow->ndpi_flows_root[i], ndpi_flow_info_freer); ndpi_exit_detection_module(workflow->ndpi_struct); - free(workflow->ndpi_flows_root); - free(workflow); + ndpi_free(workflow->ndpi_flows_root); + ndpi_free(workflow); } /* ***************************************************** */ @@ -532,10 +583,6 @@ int ndpi_workflow_node_cmp(const void *a, const void *b) { static void ndpi_flow_update_byte_count(struct ndpi_flow_info *flow, const void *x, unsigned int len, u_int8_t src_to_dst_direction) { - const unsigned char *data = x; - u_int32_t i; - u_int32_t current_count = 0; - /* * implementation note: The spec says that 4000 octets is enough of a * sample size to accurately reflect the byte distribution. Also, to avoid @@ -545,6 +592,8 @@ ndpi_flow_update_byte_count(struct ndpi_flow_info *flow, const void *x, if((flow->entropy.src2dst_pkt_count+flow->entropy.dst2src_pkt_count) <= max_num_packets_per_flow) { /* octet count was already incremented before processing this payload */ + u_int32_t current_count; + if(src_to_dst_direction) { current_count = flow->entropy.src2dst_l4_bytes - len; } else { @@ -552,6 +601,9 @@ ndpi_flow_update_byte_count(struct ndpi_flow_info *flow, const void *x, } if(current_count < ETTA_MIN_OCTETS) { + u_int32_t i; + const unsigned char *data = x; + for(i=0; ientropy.src2dst_byte_count[data[i]]++; @@ -580,11 +632,13 @@ static void ndpi_flow_update_byte_dist_mean_var(ndpi_flow_info_t *flow, const void *x, unsigned int len, u_int8_t src_to_dst_direction) { const unsigned char *data = x; - double delta; - unsigned int i; if((flow->entropy.src2dst_pkt_count+flow->entropy.dst2src_pkt_count) <= max_num_packets_per_flow) { + unsigned int i; + for(i=0; ientropy.src2dst_num_bytes += 1; delta = ((double)data[i] - flow->entropy.src2dst_bd_mean); @@ -606,33 +660,16 @@ float ndpi_flow_get_byte_count_entropy(const uint32_t byte_count[256], unsigned int num_bytes) { int i; - float tmp, sum = 0.0; + float sum = 0.0; for(i=0; i<256; i++) { - tmp = (float) byte_count[i] / (float) num_bytes; + float tmp = (float) byte_count[i] / (float) num_bytes; + if(tmp > FLT_EPSILON) { sum -= tmp * logf(tmp); } } - return sum / logf(2.0); -} - -/* ***************************************************** */ - -static void patchIPv6Address(char *str) { - int i = 0, j = 0; - - while(str[i] != '\0') { - if((str[i] == ':') - && (str[i+1] == '0') - && (str[i+2] == ':')) { - str[j++] = ':'; - str[j++] = ':'; - i += 3; - } else - str[j++] = str[i++]; - } - if(str[j] != '\0') str[j] = '\0'; + return(sum / logf(2.0)); } /* ***************************************************** */ @@ -640,6 +677,7 @@ static void patchIPv6Address(char *str) { static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow, const u_int8_t version, u_int16_t vlan_id, + ndpi_packet_tunnel tunnel_type, const struct ndpi_iphdr *iph, const struct ndpi_ipv6hdr *iph6, u_int16_t ip_offset, @@ -654,7 +692,7 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow u_int8_t **payload, u_int16_t *payload_len, u_int8_t *src_to_dst_direction, - struct timeval when) { + pkt_timeval when) { u_int32_t idx, l4_offset, hashval; struct ndpi_flow_info flow; void *ret; @@ -677,8 +715,13 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow l3 = (const u_int8_t*)iph; } else { l4_offset = sizeof(struct ndpi_ipv6hdr); + if(sizeof(struct ndpi_ipv6hdr) > ipsize) + return NULL; + l3 = (const u_int8_t*)iph6; } + if(ipsize < l4_offset + l4_packet_len) + return NULL; *proto = iph->protocol; @@ -694,16 +737,16 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow workflow->stats.packet_len[4]++; else if(l4_packet_len >= 1500) workflow->stats.packet_len[5]++; - + if(l4_packet_len > workflow->stats.max_packet_len) workflow->stats.max_packet_len = l4_packet_len; - l4 = ((const u_int8_t *) l3 + l4_offset); + l4 =& ((const u_int8_t *) l3)[l4_offset]; if(*proto == IPPROTO_TCP && l4_packet_len >= sizeof(struct ndpi_tcphdr)) { u_int tcp_len; - // tcp + // TCP workflow->stats.tcp_count++; *tcph = (struct ndpi_tcphdr *)l4; *sport = ntohs((*tcph)->source), *dport = ntohs((*tcph)->dest); @@ -712,8 +755,7 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow *payload_len = ndpi_max(0, l4_packet_len-4*(*tcph)->doff); l4_data_len = l4_packet_len - sizeof(struct ndpi_tcphdr); } else if(*proto == IPPROTO_UDP && l4_packet_len >= sizeof(struct ndpi_udphdr)) { - // udp - + // UDP workflow->stats.udp_count++; *udph = (struct ndpi_udphdr *)l4; *sport = ntohs((*udph)->source), *dport = ntohs((*udph)->dest); @@ -769,15 +811,15 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow if(ret == NULL) { if(workflow->stats.ndpi_flow_count == workflow->prefs.max_ndpi_flows) { - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_ERROR, + LOG(NDPI_LOG_ERROR, "maximum flow count (%u) has been exceeded\n", workflow->prefs.max_ndpi_flows); exit(-1); } else { - struct ndpi_flow_info *newflow = (struct ndpi_flow_info*)malloc(sizeof(struct ndpi_flow_info)); + struct ndpi_flow_info *newflow = (struct ndpi_flow_info*)ndpi_malloc(sizeof(struct ndpi_flow_info)); if(newflow == NULL) { - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_ERROR, "[NDPI] %s(1): not enough memory\n", __FUNCTION__); + LOG(NDPI_LOG_ERROR, "[NDPI] %s(1): not enough memory\n", __FUNCTION__); return(NULL); } else workflow->num_allocated_flows++; @@ -785,6 +827,7 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow memset(newflow, 0, sizeof(struct ndpi_flow_info)); newflow->flow_id = flow_id++; newflow->hashval = hashval; + newflow->tunnel_type = tunnel_type; newflow->protocol = iph->protocol, newflow->vlan_id = vlan_id; newflow->src_ip = iph->saddr, newflow->dst_ip = iph->daddr; newflow->src_port = htons(*sport), newflow->dst_port = htons(*dport); @@ -793,8 +836,15 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow newflow->iat_s_to_c = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW); newflow->pktlen_c_to_s = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW), newflow->pktlen_s_to_c = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW), - newflow->iat_flow = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW);; + newflow->iat_flow = ndpi_alloc_data_analysis(DATA_ANALUYSIS_SLIDING_WINDOW); +#ifdef DIRECTION_BINS + ndpi_init_bin(&newflow->payload_len_bin_src2dst, ndpi_bin_family8, PLEN_NUM_BINS); + ndpi_init_bin(&newflow->payload_len_bin_dst2src, ndpi_bin_family8, PLEN_NUM_BINS); +#else + ndpi_init_bin(&newflow->payload_len_bin, ndpi_bin_family8, PLEN_NUM_BINS); +#endif + if(version == IPVERSION) { inet_ntop(AF_INET, &newflow->src_ip, newflow->src_name, sizeof(newflow->src_name)); inet_ntop(AF_INET, &newflow->dst_ip, newflow->dst_name, sizeof(newflow->dst_name)); @@ -802,26 +852,41 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow inet_ntop(AF_INET6, &iph6->ip6_src, newflow->src_name, sizeof(newflow->src_name)); inet_ntop(AF_INET6, &iph6->ip6_dst, newflow->dst_name, sizeof(newflow->dst_name)); /* For consistency across platforms replace :0: with :: */ - patchIPv6Address(newflow->src_name), patchIPv6Address(newflow->dst_name); + ndpi_patchIPv6Address(newflow->src_name), ndpi_patchIPv6Address(newflow->dst_name); } if((newflow->ndpi_flow = ndpi_flow_malloc(SIZEOF_FLOW_STRUCT)) == NULL) { - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_ERROR, "[NDPI] %s(2): not enough memory\n", __FUNCTION__); - free(newflow); + LOG(NDPI_LOG_ERROR, "[NDPI] %s(2): not enough memory\n", __FUNCTION__); +#ifdef DIRECTION_BINS + ndpi_free_bin(&newflow->payload_len_bin_src2dst), ndpi_free_bin(&newflow->payload_len_bin_dst2src); +#else + ndpi_free_bin(&newflow->payload_len_bin); +#endif + ndpi_free(newflow); return(NULL); } else memset(newflow->ndpi_flow, 0, SIZEOF_FLOW_STRUCT); if((newflow->src_id = ndpi_malloc(SIZEOF_ID_STRUCT)) == NULL) { - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_ERROR, "[NDPI] %s(3): not enough memory\n", __FUNCTION__); - free(newflow); + LOG(NDPI_LOG_ERROR, "[NDPI] %s(3): not enough memory\n", __FUNCTION__); +#ifdef DIRECTION_BINS + ndpi_free_bin(&newflow->payload_len_bin_src2dst), ndpi_free_bin(&newflow->payload_len_bin_dst2src); +#else + ndpi_free_bin(&newflow->payload_len_bin); +#endif + ndpi_free(newflow); return(NULL); } else memset(newflow->src_id, 0, SIZEOF_ID_STRUCT); if((newflow->dst_id = ndpi_malloc(SIZEOF_ID_STRUCT)) == NULL) { - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_ERROR, "[NDPI] %s(4): not enough memory\n", __FUNCTION__); - free(newflow); + LOG(NDPI_LOG_ERROR, "[NDPI] %s(4): not enough memory\n", __FUNCTION__); +#ifdef DIRECTION_BINS + ndpi_free_bin(&newflow->payload_len_bin_src2dst), ndpi_free_bin(&newflow->payload_len_bin_dst2src); +#else + ndpi_free_bin(&newflow->payload_len_bin); +#endif + ndpi_free(newflow); return(NULL); } else memset(newflow->dst_id, 0, SIZEOF_ID_STRUCT); @@ -832,12 +897,12 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow *src = newflow->src_id, *dst = newflow->dst_id; newflow->entropy.src2dst_pkt_len[newflow->entropy.src2dst_pkt_count] = l4_data_len; newflow->entropy.src2dst_pkt_time[newflow->entropy.src2dst_pkt_count] = when; - if (newflow->entropy.src2dst_pkt_count == 0) { + if(newflow->entropy.src2dst_pkt_count == 0) { newflow->entropy.src2dst_start = when; } newflow->entropy.src2dst_pkt_count++; // Non zero app data. - if (l4_data_len != 0XFEEDFACE && l4_data_len != 0) { + if(l4_data_len != 0XFEEDFACE && l4_data_len != 0) { newflow->entropy.src2dst_opackets++; newflow->entropy.src2dst_l4_bytes += l4_data_len; } @@ -866,33 +931,33 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow else *src = rflow->dst_id, *dst = rflow->src_id, *src_to_dst_direction = 0, rflow->bidirectional = 1; } - if (src_to_dst_direction) { - if (rflow->entropy.src2dst_pkt_count < max_num_packets_per_flow) { + if(src_to_dst_direction) { + if(rflow->entropy.src2dst_pkt_count < max_num_packets_per_flow) { rflow->entropy.src2dst_pkt_len[rflow->entropy.src2dst_pkt_count] = l4_data_len; rflow->entropy.src2dst_pkt_time[rflow->entropy.src2dst_pkt_count] = when; rflow->entropy.src2dst_l4_bytes += l4_data_len; rflow->entropy.src2dst_pkt_count++; } // Non zero app data. - if (l4_data_len != 0XFEEDFACE && l4_data_len != 0) { + if(l4_data_len != 0XFEEDFACE && l4_data_len != 0) { rflow->entropy.src2dst_opackets++; } } else { - if (rflow->entropy.dst2src_pkt_count < max_num_packets_per_flow) { + if(rflow->entropy.dst2src_pkt_count < max_num_packets_per_flow) { rflow->entropy.dst2src_pkt_len[rflow->entropy.dst2src_pkt_count] = l4_data_len; rflow->entropy.dst2src_pkt_time[rflow->entropy.dst2src_pkt_count] = when; - if (rflow->entropy.dst2src_pkt_count == 0) { + if(rflow->entropy.dst2src_pkt_count == 0) { rflow->entropy.dst2src_start = when; } rflow->entropy.dst2src_l4_bytes += l4_data_len; rflow->entropy.dst2src_pkt_count++; } // Non zero app data. - if (l4_data_len != 0XFEEDFACE && l4_data_len != 0) { + if(l4_data_len != 0XFEEDFACE && l4_data_len != 0) { rflow->entropy.dst2src_opackets++; } } - + return(rflow); } } @@ -901,8 +966,10 @@ static struct ndpi_flow_info *get_ndpi_flow_info(struct ndpi_workflow * workflow static struct ndpi_flow_info *get_ndpi_flow_info6(struct ndpi_workflow * workflow, u_int16_t vlan_id, + ndpi_packet_tunnel tunnel_type, const struct ndpi_ipv6hdr *iph6, u_int16_t ip_offset, + u_int16_t ipsize, struct ndpi_tcphdr **tcph, struct ndpi_udphdr **udph, u_int16_t *sport, u_int16_t *dport, @@ -912,23 +979,23 @@ static struct ndpi_flow_info *get_ndpi_flow_info6(struct ndpi_workflow * workflo u_int8_t **payload, u_int16_t *payload_len, u_int8_t *src_to_dst_direction, - struct timeval when) { + pkt_timeval when) { struct ndpi_iphdr iph; memset(&iph, 0, sizeof(iph)); iph.version = IPVERSION; iph.saddr = iph6->ip6_src.u6_addr.u6_addr32[2] + iph6->ip6_src.u6_addr.u6_addr32[3]; iph.daddr = iph6->ip6_dst.u6_addr.u6_addr32[2] + iph6->ip6_dst.u6_addr.u6_addr32[3]; - iph.protocol = iph6->ip6_hdr.ip6_un1_nxt; - - if(iph.protocol == IPPROTO_DSTOPTS /* IPv6 destination option */) { - const u_int8_t *options = (const u_int8_t*)iph6 + sizeof(const struct ndpi_ipv6hdr); - - iph.protocol = options[0]; + u_int8_t l4proto = iph6->ip6_hdr.ip6_un1_nxt; + u_int16_t ip_len = ntohs(iph6->ip6_hdr.ip6_un1_plen); + const u_int8_t *l4ptr = (((const u_int8_t *) iph6) + sizeof(struct ndpi_ipv6hdr)); + if(ndpi_handle_ipv6_extension_headers(NULL, &l4ptr, &ip_len, &l4proto) != 0) { + return(NULL); } + iph.protocol = l4proto; - return(get_ndpi_flow_info(workflow, 6, vlan_id, &iph, iph6, ip_offset, - sizeof(struct ndpi_ipv6hdr), + return(get_ndpi_flow_info(workflow, 6, vlan_id, tunnel_type, + &iph, iph6, ip_offset, ipsize, ntohs(iph6->ip6_hdr.ip6_un1_plen), tcph, udph, sport, dport, src, dst, proto, payload, @@ -937,17 +1004,54 @@ static struct ndpi_flow_info *get_ndpi_flow_info6(struct ndpi_workflow * workflo /* ****************************************************** */ -void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_flow_info *flow) { +static u_int8_t is_ndpi_proto(struct ndpi_flow_info *flow, u_int16_t id) { + if((flow->detected_protocol.master_protocol == id) + || (flow->detected_protocol.app_protocol == id)) + return(1); + else + return(0); +} + +/* ****************************************************** */ + +void correct_csv_data_field(char* data) { + /* Replace , with ; to avoid issues with CSVs */ + for(u_int i=0; data[i] != '\0'; i++) if(data[i] == ',') data[i] = ';'; +} + +/* ****************************************************** */ + +u_int8_t plen2slot(u_int16_t plen) { + /* + Slots [32 bytes lenght] + 0..31, 32..63 ... + */ + + if(plen > PLEN_MAX) + return(PLEN_NUM_BINS-1); + else + return(plen/PLEN_BIN_LEN); +} + +/* ****************************************************** */ + +void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_flow_info *flow, FILE * csv_fp) { + u_int i; if(!flow->ndpi_flow) return; snprintf(flow->host_server_name, sizeof(flow->host_server_name), "%s", flow->ndpi_flow->host_server_name); - if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_DHCP) { + snprintf(flow->flow_extra_info, sizeof(flow->flow_extra_info), "%s", + flow->ndpi_flow->flow_extra_info); + + flow->risk = flow->ndpi_flow->risk; + + if(is_ndpi_proto(flow, NDPI_PROTOCOL_DHCP)) { snprintf(flow->dhcp_fingerprint, sizeof(flow->dhcp_fingerprint), "%s", flow->ndpi_flow->protos.dhcp.fingerprint); - } else if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_BITTORRENT) { - u_int i, j, n = 0; + } else if(is_ndpi_proto(flow, NDPI_PROTOCOL_BITTORRENT)) { + u_int j, n = 0; for(i=0, j = 0; j < sizeof(flow->bittorent_hash)-1; i++) { sprintf(&flow->bittorent_hash[j], "%02x", @@ -958,68 +1062,171 @@ void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_fl if(n == 0) flow->bittorent_hash[0] = '\0'; } + /* DNS */ + else if(is_ndpi_proto(flow, NDPI_PROTOCOL_DNS)) { + if(flow->ndpi_flow->protos.dns.rsp_type == 0x1) + inet_ntop(AF_INET, &flow->ndpi_flow->protos.dns.rsp_addr.ipv4, flow->info, sizeof(flow->info)); + else { + inet_ntop(AF_INET6, &flow->ndpi_flow->protos.dns.rsp_addr.ipv6, flow->info, sizeof(flow->info)); + + /* For consistency across platforms replace :0: with :: */ + ndpi_patchIPv6Address(flow->info); + } + } /* MDNS */ - else if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_MDNS) { - snprintf(flow->info, sizeof(flow->info), "%s", flow->ndpi_flow->protos.mdns.answer); + else if(is_ndpi_proto(flow, NDPI_PROTOCOL_MDNS)) { + char *name = (char*)flow->ndpi_flow->host_server_name; /* Trick to avoid warning(s) */ + snprintf(flow->info, sizeof(flow->info), "%s", name); } /* UBNTAC2 */ - else if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UBNTAC2) { + else if(is_ndpi_proto(flow, NDPI_PROTOCOL_UBNTAC2)) { snprintf(flow->info, sizeof(flow->info), "%s", flow->ndpi_flow->protos.ubntac2.version); } + /* FTP */ + else if((is_ndpi_proto(flow, NDPI_PROTOCOL_FTP_CONTROL)) + || /* IMAP */ is_ndpi_proto(flow, NDPI_PROTOCOL_MAIL_IMAP) + || /* POP */ is_ndpi_proto(flow, NDPI_PROTOCOL_MAIL_POP) + || /* SMTP */ is_ndpi_proto(flow, NDPI_PROTOCOL_MAIL_SMTP)) { + if(flow->ndpi_flow->protos.ftp_imap_pop_smtp.username[0] != '\0') + snprintf(flow->info, sizeof(flow->info), "User: %s][Pwd: %s%s", + flow->ndpi_flow->protos.ftp_imap_pop_smtp.username, + flow->ndpi_flow->protos.ftp_imap_pop_smtp.password, + flow->ndpi_flow->protos.ftp_imap_pop_smtp.auth_failed ? "][Auth Failed" : ""); + } /* KERBEROS */ - else if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_KERBEROS) { - if(flow->ndpi_flow->protos.kerberos.cname[0] != '\0') { - snprintf(flow->info, sizeof(flow->info), "%s (%s)", - flow->ndpi_flow->protos.kerberos.cname, - flow->ndpi_flow->protos.kerberos.realm); - } + else if(is_ndpi_proto(flow, NDPI_PROTOCOL_KERBEROS)) { + if((flow->ndpi_flow->protos.kerberos.hostname[0] != '\0') + || (flow->ndpi_flow->protos.kerberos.username[0] != '\0')) { + snprintf(flow->info, sizeof(flow->info), "%s%s%s%s", + flow->ndpi_flow->protos.kerberos.domain /* = realm */, + flow->ndpi_flow->protos.kerberos.domain[0] != '\0' ? "\\" : "", + flow->ndpi_flow->protos.kerberos.hostname, + flow->ndpi_flow->protos.kerberos.username); + } else if(flow->ndpi_flow->protos.kerberos.domain[0] != '\0') + snprintf(flow->info, sizeof(flow->info), "%s", + flow->ndpi_flow->protos.kerberos.domain); + +#if 0 + if(flow->info[0] != '\0') + printf("->> (%d) [%s][%s][%s]<<--\n", + htons(flow->src_port), + flow->ndpi_flow->protos.kerberos.domain, + flow->ndpi_flow->protos.kerberos.hostname, + flow->ndpi_flow->protos.kerberos.username); +#endif } /* HTTP */ - else if(flow->detected_protocol.master_protocol == NDPI_PROTOCOL_HTTP) { + else if((flow->detected_protocol.master_protocol == NDPI_PROTOCOL_HTTP) + || is_ndpi_proto(flow, NDPI_PROTOCOL_HTTP)) { if(flow->ndpi_flow->http.url != NULL) { snprintf(flow->http.url, sizeof(flow->http.url), "%s", flow->ndpi_flow->http.url); flow->http.response_status_code = flow->ndpi_flow->http.response_status_code; + snprintf(flow->http.content_type, sizeof(flow->http.content_type), "%s", flow->ndpi_flow->http.content_type ? flow->ndpi_flow->http.content_type : ""); + snprintf(flow->http.user_agent, sizeof(flow->http.user_agent), "%s", flow->ndpi_flow->http.user_agent ? flow->ndpi_flow->http.user_agent : ""); } + } else if(is_ndpi_proto(flow, NDPI_PROTOCOL_TELNET)) { + snprintf(flow->telnet.username, sizeof(flow->telnet.username), "%s", flow->ndpi_flow->protos.telnet.username); + snprintf(flow->telnet.password, sizeof(flow->telnet.password), "%s", flow->ndpi_flow->protos.telnet.password); + } else if(is_ndpi_proto(flow, NDPI_PROTOCOL_SSH)) { + snprintf(flow->ssh_tls.client_requested_server_name, + sizeof(flow->ssh_tls.client_requested_server_name), "%s", + flow->ndpi_flow->protos.ssh.client_signature); + snprintf(flow->ssh_tls.server_info, sizeof(flow->ssh_tls.server_info), "%s", + flow->ndpi_flow->protos.ssh.server_signature); + snprintf(flow->ssh_tls.client_hassh, sizeof(flow->ssh_tls.client_hassh), "%s", + flow->ndpi_flow->protos.ssh.hassh_client); + snprintf(flow->ssh_tls.server_hassh, sizeof(flow->ssh_tls.server_hassh), "%s", + flow->ndpi_flow->protos.ssh.hassh_server); } - else if(flow->detected_protocol.app_protocol != NDPI_PROTOCOL_DNS) { - /* SSH */ - if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_SSH) { - snprintf(flow->ssh_tls.client_info, sizeof(flow->ssh_tls.client_info), "%s", - flow->ndpi_flow->protos.ssh.client_signature); - snprintf(flow->ssh_tls.server_info, sizeof(flow->ssh_tls.server_info), "%s", - flow->ndpi_flow->protos.ssh.server_signature); - snprintf(flow->ssh_tls.client_hassh, sizeof(flow->ssh_tls.client_hassh), "%s", - flow->ndpi_flow->protos.ssh.hassh_client); - snprintf(flow->ssh_tls.server_hassh, sizeof(flow->ssh_tls.server_hassh), "%s", - flow->ndpi_flow->protos.ssh.hassh_server); - } - /* TLS */ - else if((flow->detected_protocol.app_protocol == NDPI_PROTOCOL_TLS) - || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS) - || (flow->ndpi_flow->protos.stun_ssl.ssl.ja3_client[0] != '\0') - ) { - flow->ssh_tls.ssl_version = flow->ndpi_flow->protos.stun_ssl.ssl.ssl_version; - snprintf(flow->ssh_tls.client_info, sizeof(flow->ssh_tls.client_info), "%s", - flow->ndpi_flow->protos.stun_ssl.ssl.client_certificate); - snprintf(flow->ssh_tls.server_info, sizeof(flow->ssh_tls.server_info), "%s", - flow->ndpi_flow->protos.stun_ssl.ssl.server_certificate); - snprintf(flow->ssh_tls.server_organization, sizeof(flow->ssh_tls.server_organization), "%s", - flow->ndpi_flow->protos.stun_ssl.ssl.server_organization); - flow->ssh_tls.notBefore = flow->ndpi_flow->protos.stun_ssl.ssl.notBefore; - flow->ssh_tls.notAfter = flow->ndpi_flow->protos.stun_ssl.ssl.notAfter; - snprintf(flow->ssh_tls.ja3_client, sizeof(flow->ssh_tls.ja3_client), "%s", - flow->ndpi_flow->protos.stun_ssl.ssl.ja3_client); - snprintf(flow->ssh_tls.ja3_server, sizeof(flow->ssh_tls.ja3_server), "%s", - flow->ndpi_flow->protos.stun_ssl.ssl.ja3_server); - flow->ssh_tls.server_unsafe_cipher = flow->ndpi_flow->protos.stun_ssl.ssl.server_unsafe_cipher; - flow->ssh_tls.server_cipher = flow->ndpi_flow->protos.stun_ssl.ssl.server_cipher; + /* TLS */ + else if((is_ndpi_proto(flow, NDPI_PROTOCOL_TLS)) + || ((is_ndpi_proto(flow, NDPI_PROTOCOL_QUIC))) + || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS) + || (flow->ndpi_flow->protos.stun_ssl.ssl.ja3_client[0] != '\0') + ) { + flow->ssh_tls.ssl_version = flow->ndpi_flow->protos.stun_ssl.ssl.ssl_version; + snprintf(flow->ssh_tls.client_requested_server_name, + sizeof(flow->ssh_tls.client_requested_server_name), "%s", + flow->ndpi_flow->protos.stun_ssl.ssl.client_requested_server_name); + + snprintf(flow->http.user_agent, sizeof(flow->http.user_agent), "%s", flow->ndpi_flow->http.user_agent ? flow->ndpi_flow->http.user_agent : ""); + + if(flow->ndpi_flow->protos.stun_ssl.ssl.server_names_len > 0 && flow->ndpi_flow->protos.stun_ssl.ssl.server_names) + flow->ssh_tls.server_names = ndpi_strdup(flow->ndpi_flow->protos.stun_ssl.ssl.server_names); + flow->ssh_tls.notBefore = flow->ndpi_flow->protos.stun_ssl.ssl.notBefore; + flow->ssh_tls.notAfter = flow->ndpi_flow->protos.stun_ssl.ssl.notAfter; + snprintf(flow->ssh_tls.ja3_client, sizeof(flow->ssh_tls.ja3_client), "%s", + flow->ndpi_flow->protos.stun_ssl.ssl.ja3_client); + snprintf(flow->ssh_tls.ja3_server, sizeof(flow->ssh_tls.ja3_server), "%s", + flow->ndpi_flow->protos.stun_ssl.ssl.ja3_server); + flow->ssh_tls.server_unsafe_cipher = flow->ndpi_flow->protos.stun_ssl.ssl.server_unsafe_cipher; + flow->ssh_tls.server_cipher = flow->ndpi_flow->protos.stun_ssl.ssl.server_cipher; + + if(flow->ndpi_flow->l4.tcp.tls.fingerprint_set) { memcpy(flow->ssh_tls.sha1_cert_fingerprint, - flow->ndpi_flow->l4.tcp.tls_sha1_certificate_fingerprint, 20); + flow->ndpi_flow->l4.tcp.tls.sha1_certificate_fingerprint, 20); + flow->ssh_tls.sha1_cert_fingerprint_set = 1; + } + + if(flow->ndpi_flow->protos.stun_ssl.ssl.alpn) { + if((flow->ssh_tls.tls_alpn = ndpi_strdup(flow->ndpi_flow->protos.stun_ssl.ssl.alpn)) != NULL) + correct_csv_data_field(flow->ssh_tls.tls_alpn); + } + + if(flow->ndpi_flow->protos.stun_ssl.ssl.issuerDN) + flow->ssh_tls.tls_issuerDN = strdup(flow->ndpi_flow->protos.stun_ssl.ssl.issuerDN); + + if(flow->ndpi_flow->protos.stun_ssl.ssl.subjectDN) + flow->ssh_tls.tls_subjectDN = strdup(flow->ndpi_flow->protos.stun_ssl.ssl.subjectDN); + + if(flow->ndpi_flow->protos.stun_ssl.ssl.encrypted_sni.esni) { + flow->ssh_tls.encrypted_sni.esni = strdup(flow->ndpi_flow->protos.stun_ssl.ssl.encrypted_sni.esni); + flow->ssh_tls.encrypted_sni.cipher_suite = flow->ndpi_flow->protos.stun_ssl.ssl.encrypted_sni.cipher_suite; + } + + if(flow->ssh_tls.tls_supported_versions) { + if((flow->ssh_tls.tls_supported_versions = ndpi_strdup(flow->ndpi_flow->protos.stun_ssl.ssl.tls_supported_versions)) != NULL) + correct_csv_data_field(flow->ssh_tls.tls_supported_versions); + } + + if(flow->ndpi_flow->protos.stun_ssl.ssl.alpn + && flow->ndpi_flow->protos.stun_ssl.ssl.tls_supported_versions) { + correct_csv_data_field(flow->ndpi_flow->protos.stun_ssl.ssl.alpn); + correct_csv_data_field(flow->ndpi_flow->protos.stun_ssl.ssl.tls_supported_versions); + + if(csv_fp) + snprintf(flow->info, sizeof(flow->info), "%s", + flow->ndpi_flow->protos.stun_ssl.ssl.alpn); + else + snprintf(flow->info, sizeof(flow->info), "ALPN: %s][TLS Supported Versions: %s", + flow->ndpi_flow->protos.stun_ssl.ssl.alpn, + flow->ndpi_flow->protos.stun_ssl.ssl.tls_supported_versions); + } + else if(flow->ndpi_flow->protos.stun_ssl.ssl.alpn) { + correct_csv_data_field(flow->ndpi_flow->protos.stun_ssl.ssl.alpn); + + if(csv_fp) + snprintf(flow->info, sizeof(flow->info), "%s,", + flow->ndpi_flow->protos.stun_ssl.ssl.alpn); + else + snprintf(flow->info, sizeof(flow->info), "ALPN: %s", + flow->ndpi_flow->protos.stun_ssl.ssl.alpn); + } + +#ifdef USE_TLS_LEN + /* For TLS we use TLS block lenght instead of payload lenght */ + ndpi_reset_bin(&flow->payload_len_bin); + + for(i=0; indpi_flow->l4.tcp.tls.num_tls_blocks; i++) { + u_int16_t len = abs(flow->ndpi_flow->l4.tcp.tls.tls_application_blocks_len[i]); + printf("%u\n", len); + ndpi_inc_bin(&flow->payload_len_bin, plen2slot(len), 1); } +#endif } if(flow->detection_completed && (!flow->check_extra_packets)) { - if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UNKNOWN) { + if(is_ndpi_proto(flow, NDPI_PROTOCOL_UNKNOWN)) { if(workflow->__flow_giveup_callback != NULL) workflow->__flow_giveup_callback(workflow, flow, workflow->__flow_giveup_udata); } else { @@ -1037,14 +1244,48 @@ void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_fl * @brief Clear entropy stats if it meets prereq. */ static void -ndpi_clear_entropy_stats(struct ndpi_flow_info *flow) -{ +ndpi_clear_entropy_stats(struct ndpi_flow_info *flow) { if(flow->entropy.src2dst_pkt_count + flow->entropy.dst2src_pkt_count == max_num_packets_per_flow) { memcpy(&flow->last_entropy, &flow->entropy, sizeof(struct ndpi_entropy)); memset(&flow->entropy, 0x00, sizeof(struct ndpi_entropy)); } } +void update_tcp_flags_count(struct ndpi_flow_info* flow, struct ndpi_tcphdr* tcp, u_int8_t src_to_dst_direction){ + if(tcp->cwr){ + flow->cwr_count++; + src_to_dst_direction ? flow->src2dst_cwr_count++ : flow->dst2src_cwr_count++; + } + if(tcp->ece){ + flow->ece_count++; + src_to_dst_direction ? flow->src2dst_ece_count++ : flow->dst2src_ece_count++; + } + if(tcp->rst){ + flow->rst_count++; + src_to_dst_direction ? flow->src2dst_rst_count++ : flow->dst2src_rst_count++; + } + if(tcp->ack){ + flow->ack_count++; + src_to_dst_direction ? flow->src2dst_ack_count++ : flow->dst2src_ack_count++; + } + if(tcp->fin){ + flow->fin_count++; + src_to_dst_direction ? flow->src2dst_fin_count++ : flow->dst2src_fin_count++; + } + if(tcp->syn){ + flow->syn_count++; + src_to_dst_direction ? flow->src2dst_syn_count++ : flow->dst2src_syn_count++; + } + if(tcp->psh){ + flow->psh_count++; + src_to_dst_direction ? flow->src2dst_psh_count++ : flow->dst2src_psh_count++; + } + if(tcp->urg){ + flow->urg_count++; + src_to_dst_direction ? flow->src2dst_urg_count++ : flow->dst2src_urg_count++; + } +} + /* ****************************************************** */ /** Function to process the packet: @@ -1054,68 +1295,86 @@ ndpi_clear_entropy_stats(struct ndpi_flow_info *flow) @Note: ipsize = header->len - ip_offset ; rawsize = header->len */ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, - const u_int64_t time, + const u_int64_t time_ms, u_int16_t vlan_id, + ndpi_packet_tunnel tunnel_type, const struct ndpi_iphdr *iph, struct ndpi_ipv6hdr *iph6, u_int16_t ip_offset, u_int16_t ipsize, u_int16_t rawsize, const struct pcap_pkthdr *header, const u_char *packet, - struct timeval when) { + pkt_timeval when, + FILE * csv_fp) { struct ndpi_id_struct *src, *dst; struct ndpi_flow_info *flow = NULL; struct ndpi_flow_struct *ndpi_flow = NULL; u_int8_t proto; struct ndpi_tcphdr *tcph = NULL; struct ndpi_udphdr *udph = NULL; - u_int16_t sport, dport, payload_len; + u_int16_t sport, dport, payload_len = 0; u_int8_t *payload; u_int8_t src_to_dst_direction = 1; u_int8_t begin_or_end_tcp = 0; - struct ndpi_proto nproto = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN }; + struct ndpi_proto nproto = NDPI_PROTOCOL_NULL; if(iph) - flow = get_ndpi_flow_info(workflow, IPVERSION, vlan_id, iph, NULL, + flow = get_ndpi_flow_info(workflow, IPVERSION, vlan_id, + tunnel_type, iph, NULL, ip_offset, ipsize, ntohs(iph->tot_len) - (iph->ihl * 4), &tcph, &udph, &sport, &dport, &src, &dst, &proto, &payload, &payload_len, &src_to_dst_direction, when); else - flow = get_ndpi_flow_info6(workflow, vlan_id, iph6, ip_offset, + flow = get_ndpi_flow_info6(workflow, vlan_id, + tunnel_type, iph6, ip_offset, ipsize, &tcph, &udph, &sport, &dport, &src, &dst, &proto, &payload, &payload_len, &src_to_dst_direction, when); if(flow != NULL) { - struct timeval tdiff; + pkt_timeval tdiff; workflow->stats.ip_packet_count++; workflow->stats.total_wire_bytes += rawsize + 24 /* CRC etc */, workflow->stats.total_ip_bytes += rawsize; ndpi_flow = flow->ndpi_flow; + if(tcph != NULL){ + update_tcp_flags_count(flow, tcph, src_to_dst_direction); + if(tcph->syn && !flow->src2dst_bytes){ + flow->c_to_s_init_win = rawsize; + }else if(tcph->syn && tcph->ack && flow->src2dst_bytes == flow->c_to_s_init_win){ + flow->s_to_c_init_win = rawsize; + } + } + if((tcph != NULL) && (tcph->fin || tcph->rst || tcph->syn)) begin_or_end_tcp = 1; if(flow->entropy.flow_last_pkt_time.tv_sec) { ndpi_timer_sub(&when, &flow->entropy.flow_last_pkt_time, &tdiff); - - if(flow->iat_flow) { + + if(flow->iat_flow + && (tdiff.tv_sec >= 0) /* Discard backward time */ + ) { u_int32_t ms = ndpi_timeval_to_milliseconds(tdiff); if(ms > 0) ndpi_data_add_value(flow->iat_flow, ms); } } - memcpy(&flow->entropy.flow_last_pkt_time, &when, sizeof(when)); - if(src_to_dst_direction) { + memcpy(&flow->entropy.flow_last_pkt_time, &when, sizeof(when)); + + if(src_to_dst_direction) { if(flow->entropy.src2dst_last_pkt_time.tv_sec) { ndpi_timer_sub(&when, &flow->entropy.src2dst_last_pkt_time, &tdiff); - if(flow->iat_c_to_s) { + if(flow->iat_c_to_s + && (tdiff.tv_sec >= 0) /* Discard backward time */ + ) { u_int32_t ms = ndpi_timeval_to_milliseconds(tdiff); ndpi_data_add_value(flow->iat_c_to_s, ms); @@ -1123,9 +1382,14 @@ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, } ndpi_data_add_value(flow->pktlen_c_to_s, rawsize); - flow->src2dst_packets++, flow->src2dst_bytes += rawsize; + flow->src2dst_packets++, flow->src2dst_bytes += rawsize, flow->src2dst_goodput_bytes += payload_len; memcpy(&flow->entropy.src2dst_last_pkt_time, &when, sizeof(when)); - } else { + +#ifdef DIRECTION_BINS + if(payload_len && (flow->src2dst_packets < MAX_NUM_BIN_PKTS)) + ndpi_inc_bin(&flow->payload_len_bin_src2dst, plen2slot(payload_len)); +#endif + } else { if(flow->entropy.dst2src_last_pkt_time.tv_sec && (!begin_or_end_tcp)) { ndpi_timer_sub(&when, &flow->entropy.dst2src_last_pkt_time, &tdiff); @@ -1135,11 +1399,25 @@ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, ndpi_data_add_value(flow->iat_s_to_c, ms); } } - ndpi_data_add_value(flow->pktlen_s_to_c, rawsize); - flow->dst2src_packets++, flow->dst2src_bytes += rawsize; + flow->dst2src_packets++, flow->dst2src_bytes += rawsize, flow->dst2src_goodput_bytes += payload_len; memcpy(&flow->entropy.dst2src_last_pkt_time, &when, sizeof(when)); + +#ifdef DIRECTION_BINS + if(payload_len && (flow->dst2src_packets < MAX_NUM_BIN_PKTS)) + ndpi_inc_bin(&flow->payload_len_bin_dst2src, plen2slot(payload_len)); +#endif + } + +#ifndef DIRECTION_BINS + if(payload_len && ((flow->src2dst_packets+flow->dst2src_packets) < MAX_NUM_BIN_PKTS)) { +#if 0 + /* Discard packets until the protocol is detected */ + if(flow->detected_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN) +#endif + ndpi_inc_bin(&flow->payload_len_bin, plen2slot(payload_len), 1); } +#endif if(enable_payload_analyzer && (payload_len > 0)) ndpi_payload_analyzer(flow, src_to_dst_direction, @@ -1154,28 +1432,28 @@ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, if((flow->entropy.src2dst_pkt_count+flow->entropy.dst2src_pkt_count) <= max_num_packets_per_flow) { if(flow->bidirectional) flow->entropy.score = ndpi_classify(flow->entropy.src2dst_pkt_len, flow->entropy.src2dst_pkt_time, - flow->entropy.dst2src_pkt_len, flow->entropy.dst2src_pkt_time, - flow->entropy.src2dst_start, flow->entropy.dst2src_start, - max_num_packets_per_flow, flow->src_port, flow->dst_port, - flow->src2dst_packets, flow->dst2src_packets, - flow->entropy.src2dst_opackets, flow->entropy.dst2src_opackets, - flow->entropy.src2dst_l4_bytes, flow->entropy.dst2src_l4_bytes, 1, - flow->entropy.src2dst_byte_count, flow->entropy.dst2src_byte_count); - else - flow->entropy.score = ndpi_classify(flow->entropy.src2dst_pkt_len, flow->entropy.src2dst_pkt_time, - NULL, NULL, flow->entropy.src2dst_start, flow->entropy.src2dst_start, - max_num_packets_per_flow, flow->src_port, flow->dst_port, - flow->src2dst_packets, 0, - flow->entropy.src2dst_opackets, 0, - flow->entropy.src2dst_l4_bytes, 0, 1, - flow->entropy.src2dst_byte_count, NULL); + flow->entropy.dst2src_pkt_len, flow->entropy.dst2src_pkt_time, + flow->entropy.src2dst_start, flow->entropy.dst2src_start, + max_num_packets_per_flow, flow->src_port, flow->dst_port, + flow->src2dst_packets, flow->dst2src_packets, + flow->entropy.src2dst_opackets, flow->entropy.dst2src_opackets, + flow->entropy.src2dst_l4_bytes, flow->entropy.dst2src_l4_bytes, 1, + flow->entropy.src2dst_byte_count, flow->entropy.dst2src_byte_count); + else + flow->entropy.score = ndpi_classify(flow->entropy.src2dst_pkt_len, flow->entropy.src2dst_pkt_time, + NULL, NULL, flow->entropy.src2dst_start, flow->entropy.src2dst_start, + max_num_packets_per_flow, flow->src_port, flow->dst_port, + flow->src2dst_packets, 0, + flow->entropy.src2dst_opackets, 0, + flow->entropy.src2dst_l4_bytes, 0, 1, + flow->entropy.src2dst_byte_count, NULL); } } - if(flow->first_seen == 0) - flow->first_seen = time; + if(flow->first_seen_ms == 0) + flow->first_seen_ms = time_ms; - flow->last_seen = time; + flow->last_seen_ms = time_ms; /* Copy packets entropy if num packets count == 10 */ ndpi_clear_entropy_stats(flow); @@ -1185,9 +1463,9 @@ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, if((proto == IPPROTO_TCP) && ( - (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_TLS) + is_ndpi_proto(flow, NDPI_PROTOCOL_TLS) || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS) - || (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_SSH) + || is_ndpi_proto(flow, NDPI_PROTOCOL_SSH) || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_SSH)) ) { if((flow->src2dst_packets+flow->dst2src_packets) < 10 /* MIN_NUM_ENCRYPT_SKIP_PACKETS */) @@ -1204,9 +1482,9 @@ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, } else { if((proto == IPPROTO_TCP) && ( - (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_TLS) + is_ndpi_proto(flow, NDPI_PROTOCOL_TLS) || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS) - || (flow->detected_protocol.app_protocol == NDPI_PROTOCOL_SSH) + || is_ndpi_proto(flow, NDPI_PROTOCOL_SSH) || (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_SSH)) ) flow->has_human_readeable_strings = 0; @@ -1221,31 +1499,36 @@ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, (((proto == IPPROTO_UDP) && ((flow->src2dst_packets + flow->dst2src_packets) > max_num_udp_dissected_pkts)) || ((proto == IPPROTO_TCP) && ((flow->src2dst_packets + flow->dst2src_packets) > max_num_tcp_dissected_pkts))) ? 1 : 0; +#if 0 + printf("%s()\n", __FUNCTION__); +#endif + flow->detected_protocol = ndpi_detection_process_packet(workflow->ndpi_struct, ndpi_flow, iph ? (uint8_t *)iph : (uint8_t *)iph6, - ipsize, time, src, dst); - + ipsize, time_ms, src, dst); + if(enough_packets || (flow->detected_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN)) { if((!enough_packets) - && (flow->detected_protocol.master_protocol == NDPI_PROTOCOL_TLS) - && (!flow->ndpi_flow->l4.tcp.tls_srv_cert_fingerprint_processed)) + && ndpi_extra_dissection_possible(workflow->ndpi_struct, ndpi_flow)) ; /* Wait for certificate fingerprint */ else { /* New protocol detected or give up */ flow->detection_completed = 1; +#if 0 /* Check if we should keep checking extra packets */ if(ndpi_flow && ndpi_flow->check_extra_packets) flow->check_extra_packets = 1; +#endif if(flow->detected_protocol.app_protocol == NDPI_PROTOCOL_UNKNOWN) { u_int8_t proto_guessed; - + flow->detected_protocol = ndpi_detection_giveup(workflow->ndpi_struct, flow->ndpi_flow, enable_protocol_guess, &proto_guessed); } - - process_ndpi_collected_info(workflow, flow); + + process_ndpi_collected_info(workflow, flow, csv_fp); } } } @@ -1257,7 +1540,8 @@ static struct ndpi_proto packet_processing(struct ndpi_workflow * workflow, struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, const struct pcap_pkthdr *header, - const u_char *packet) { + const u_char *packet, + FILE * csv_fp) { /* * Declare pointers to packet headers */ @@ -1285,7 +1569,8 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, /** --- IPv6 header --- **/ struct ndpi_ipv6hdr *iph6; - struct ndpi_proto nproto = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN }; + struct ndpi_proto nproto = NDPI_PROTOCOL_NULL; + ndpi_packet_tunnel tunnel_type = ndpi_no_tunnel; /* lengths and offsets */ u_int16_t eth_offset = 0; @@ -1295,10 +1580,10 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, int wifi_len = 0; int pyld_eth_len = 0; int check; - u_int64_t time; + u_int64_t time_ms; u_int16_t ip_offset = 0, ip_len; u_int16_t frag_off = 0, vlan_id = 0; - u_int8_t proto = 0; + u_int8_t proto = 0, recheck_type; /*u_int32_t label;*/ /* counters */ @@ -1308,15 +1593,15 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, workflow->stats.raw_packet_count++; /* setting time */ - time = ((uint64_t) header->ts.tv_sec) * TICK_RESOLUTION + header->ts.tv_usec / (1000000 / TICK_RESOLUTION); + time_ms = ((uint64_t) header->ts.tv_sec) * TICK_RESOLUTION + header->ts.tv_usec / (1000000 / TICK_RESOLUTION); /* safety check */ - if(workflow->last_time > time) { + if(workflow->last_time > time_ms) { /* printf("\nWARNING: timestamp bug in the pcap file (ts delta: %llu, repairing)\n", ndpi_thread_info[thread_id].last_time - time); */ - time = workflow->last_time; + time_ms = workflow->last_time; } /* update last time value */ - workflow->last_time = time; + workflow->last_time = time_ms; /*** check Data Link type ***/ int datalink_type; @@ -1327,7 +1612,11 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, datalink_type = (int)pcap_datalink(workflow->pcap_handle); #endif + datalink_check: + if(header->caplen < eth_offset + 40) + return(nproto); /* Too short */ + switch(datalink_type) { case DLT_NULL: if(ntohl(*((u_int32_t*)&packet[eth_offset])) == 2) @@ -1395,6 +1684,10 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, return(nproto); } + if(header->caplen < (eth_offset + radio_len + sizeof(struct ndpi_wifi_header))) + return(nproto); + + /* Calculate 802.11 header length (variable) */ wifi = (struct ndpi_wifi_header*)( packet + eth_offset + radio_len); fc = wifi->fc; @@ -1408,6 +1701,8 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, break; /* Check ether_type from LLC */ + if(header->caplen < (eth_offset + wifi_len + radio_len + sizeof(struct ndpi_llc_header_snap))) + return(nproto); llc = (struct ndpi_llc_header_snap*)(packet + eth_offset + wifi_len + radio_len); if(llc->dsap == SNAP) type = ntohs(llc->snap.proto_ID); @@ -1425,6 +1720,9 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, return(nproto); } + ether_type_check: + recheck_type = 0; + /* check ether type */ switch(type) { case VLAN: @@ -1432,13 +1730,16 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, type = (packet[ip_offset+2] << 8) + packet[ip_offset+3]; ip_offset += 4; vlan_packet = 1; + // double tagging for 802.1Q - if(type == 0x8100) { + while((type == 0x8100) && (((bpf_u_int32)ip_offset) < header->caplen)) { vlan_id = ((packet[ip_offset] << 8) + packet[ip_offset+1]) & 0xFFF; type = (packet[ip_offset+2] << 8) + packet[ip_offset+3]; ip_offset += 4; } + recheck_type = 1; break; + case MPLS_UNI: case MPLS_MULTI: mpls.u32 = *((uint32_t *) &packet[ip_offset]); @@ -1446,25 +1747,35 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, workflow->stats.mpls_count++; type = ETH_P_IP, ip_offset += 4; - while(!mpls.mpls.s) { + while(!mpls.mpls.s && (((bpf_u_int32)ip_offset) + 4 < header->caplen)) { mpls.u32 = *((uint32_t *) &packet[ip_offset]); mpls.u32 = ntohl(mpls.u32); ip_offset += 4; } + recheck_type = 1; break; + case PPPoE: workflow->stats.pppoe_count++; type = ETH_P_IP; ip_offset += 8; + recheck_type = 1; break; + default: break; } + if(recheck_type) + goto ether_type_check; + workflow->stats.vlan_count += vlan_packet; iph_check: /* Check and set IP header size and total packet length */ + if(header->caplen < ip_offset + sizeof(struct ndpi_iphdr)) + return(nproto); /* Too short for next IP header*/ + iph = (struct ndpi_iphdr *) &packet[ip_offset]; /* just work on Ethernet packets that contain IP */ @@ -1477,7 +1788,8 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, if(cap_warning_used == 0) { if(!workflow->prefs.quiet_mode) - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_DEBUG, "\n\nWARNING: packet capture size is smaller than packet size, DETECTION MIGHT NOT WORK CORRECTLY\n\n"); + LOG(NDPI_LOG_DEBUG, + "\n\nWARNING: packet capture size is smaller than packet size, DETECTION MIGHT NOT WORK CORRECTLY\n\n"); cap_warning_used = 1; } } @@ -1487,9 +1799,10 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, ip_len = ((u_int16_t)iph->ihl * 4); iph6 = NULL; - if(iph->protocol == IPPROTO_IPV6) { + if(iph->protocol == IPPROTO_IPV6 || iph->protocol == IPPROTO_IPIP) { ip_offset += ip_len; - goto iph_check; + if(ip_len > 0) + goto iph_check; } if((frag_off & 0x1FFF) != 0) { @@ -1498,7 +1811,7 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, if(ipv4_frags_warning_used == 0) { if(!workflow->prefs.quiet_mode) - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_DEBUG, "\n\nWARNING: IPv4 fragments are not handled by this demo (nDPI supports them)\n"); + LOG(NDPI_LOG_DEBUG, "\n\nWARNING: IPv4 fragments are not handled by this demo (nDPI supports them)\n"); ipv4_frags_warning_used = 1; } @@ -1506,14 +1819,23 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, return(nproto); } } else if(iph->version == 6) { + if(header->caplen < ip_offset + sizeof(struct ndpi_ipv6hdr)) + return(nproto); /* Too short for IPv6 header*/ iph6 = (struct ndpi_ipv6hdr *)&packet[ip_offset]; proto = iph6->ip6_hdr.ip6_un1_nxt; - ip_len = sizeof(struct ndpi_ipv6hdr); + ip_len = ntohs(iph6->ip6_hdr.ip6_un1_plen); + if(header->caplen < (ip_offset + sizeof(struct ndpi_ipv6hdr) + ntohs(iph6->ip6_hdr.ip6_un1_plen))) + return(nproto); /* Too short for IPv6 payload*/ - if(proto == IPPROTO_DSTOPTS /* IPv6 destination option */) { - u_int8_t *options = (u_int8_t*)&packet[ip_offset+ip_len]; - proto = options[0]; - ip_len += 8 * (options[1] + 1); + const u_int8_t *l4ptr = (((const u_int8_t *) iph6) + sizeof(struct ndpi_ipv6hdr)); + if(ndpi_handle_ipv6_extension_headers(NULL, &l4ptr, &ip_len, &proto) != 0) { + return(nproto); + } + if(proto == IPPROTO_IPV6 || proto == IPPROTO_IPIP) { + if(l4ptr > packet) { /* Better safe than sorry */ + ip_offset = (l4ptr - packet); + goto iph_check; + } } iph = NULL; @@ -1523,7 +1845,7 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, v4_warning: if(ipv4_warning_used == 0) { if(!workflow->prefs.quiet_mode) - NDPI_LOG(0, workflow->ndpi_struct, NDPI_LOG_DEBUG, + LOG(NDPI_LOG_DEBUG, "\n\nWARNING: only IPv4/IPv6 packets are supported in this demo (nDPI supports both IPv4 and IPv6), all other packets will be discarded\n\n"); ipv4_warning_used = 1; } @@ -1532,65 +1854,106 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, } if(workflow->prefs.decode_tunnels && (proto == IPPROTO_UDP)) { - struct ndpi_udphdr *udp = (struct ndpi_udphdr *)&packet[ip_offset+ip_len]; - u_int16_t sport = ntohs(udp->source), dport = ntohs(udp->dest); + if(header->caplen < ip_offset + ip_len + sizeof(struct ndpi_udphdr)) + return(nproto); /* Too short for UDP header*/ + else { + struct ndpi_udphdr *udp = (struct ndpi_udphdr *)&packet[ip_offset+ip_len]; + u_int16_t sport = ntohs(udp->source), dport = ntohs(udp->dest); + + if((sport == GTP_U_V1_PORT) || (dport == GTP_U_V1_PORT)) { + /* Check if it's GTPv1 */ + u_int offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr); + u_int8_t flags = packet[offset]; + u_int8_t message_type = packet[offset+1]; + + tunnel_type = ndpi_gtp_tunnel; + + if((((flags & 0xE0) >> 5) == 1 /* GTPv1 */) && + (message_type == 0xFF /* T-PDU */)) { + + ip_offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr)+8; /* GTPv1 header len */ + if(flags & 0x04) ip_offset += 1; /* next_ext_header is present */ + if(flags & 0x02) ip_offset += 4; /* sequence_number is present (it also includes next_ext_header and pdu_number) */ + if(flags & 0x01) ip_offset += 1; /* pdu_number is present */ + + if(ip_offset < header->caplen) { + iph = (struct ndpi_iphdr *)&packet[ip_offset]; + if(iph->version == 6) { + iph6 = (struct ndpi_ipv6hdr *)&packet[ip_offset]; + iph = NULL; + } else if(iph->version != IPVERSION) { + // printf("WARNING: not good (packet_id=%u)!\n", (unsigned int)workflow->stats.raw_packet_count); + goto v4_warning; + } + } + } + } else if((sport == TZSP_PORT) || (dport == TZSP_PORT)) { + /* https://en.wikipedia.org/wiki/TZSP */ + if(header->caplen < ip_offset + ip_len + sizeof(struct ndpi_udphdr) + 4) + return(nproto); /* Too short for TZSP*/ + + u_int offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr); + u_int8_t version = packet[offset]; + u_int8_t ts_type = packet[offset+1]; + u_int16_t encapsulates = ntohs(*((u_int16_t*)&packet[offset+2])); + + tunnel_type = ndpi_tzsp_tunnel; + + if((version == 1) && (ts_type == 0) && (encapsulates == 1)) { + u_int8_t stop = 0; + + offset += 4; + + while((!stop) && (offset < header->caplen)) { + u_int8_t tag_type = packet[offset]; + u_int8_t tag_len; + + switch(tag_type) { + case 0: /* PADDING Tag */ + tag_len = 1; + break; + case 1: /* END Tag */ + tag_len = 1, stop = 1; + break; + default: + tag_len = packet[offset+1]; + break; + } + + offset += tag_len; + + if(offset >= header->caplen) + return(nproto); /* Invalid packet */ + else { + eth_offset = offset; + goto datalink_check; + } + } + } + } else if((sport == NDPI_CAPWAP_DATA_PORT) || (dport == NDPI_CAPWAP_DATA_PORT)) { + /* We dissect ONLY CAPWAP traffic */ + u_int offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr); - if((sport == GTP_U_V1_PORT) || (dport == GTP_U_V1_PORT)) { - /* Check if it's GTPv1 */ - u_int offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr); - u_int8_t flags = packet[offset]; - u_int8_t message_type = packet[offset+1]; + if((offset+1) < header->caplen) { + uint8_t preamble = packet[offset]; - if((((flags & 0xE0) >> 5) == 1 /* GTPv1 */) && - (message_type == 0xFF /* T-PDU */)) { + if((preamble & 0x0F) == 0) { /* CAPWAP header */ + u_int16_t msg_len = (packet[offset+1] & 0xF8) >> 1; - ip_offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr)+8; /* GTPv1 header len */ - if(flags & 0x04) ip_offset += 1; /* next_ext_header is present */ - if(flags & 0x02) ip_offset += 4; /* sequence_number is present (it also includes next_ext_header and pdu_number) */ - if(flags & 0x01) ip_offset += 1; /* pdu_number is present */ + offset += msg_len; - iph = (struct ndpi_iphdr *) &packet[ip_offset]; + if((offset + 32 < header->caplen) && (packet[offset] == 0x02)) { + /* IEEE 802.11 Data */ - if(iph->version != IPVERSION) { - // printf("WARNING: not good (packet_id=%u)!\n", (unsigned int)workflow->stats.raw_packet_count); - goto v4_warning; - } - } - } else if((sport == TZSP_PORT) || (dport == TZSP_PORT)) { - /* https://en.wikipedia.org/wiki/TZSP */ - u_int offset = ip_offset+ip_len+sizeof(struct ndpi_udphdr); - u_int8_t version = packet[offset]; - u_int8_t ts_type = packet[offset+1]; - u_int16_t encapsulates = ntohs(*((u_int16_t*)&packet[offset+2])); - - if((version == 1) && (ts_type == 0) && (encapsulates == 1)) { - u_int8_t stop = 0; - - offset += 4; - - while((!stop) && (offset < header->caplen)) { - u_int8_t tag_type = packet[offset]; - u_int8_t tag_len; - - switch(tag_type) { - case 0: /* PADDING Tag */ - tag_len = 1; - break; - case 1: /* END Tag */ - tag_len = 1, stop = 1; - break; - default: - tag_len = packet[offset+1]; - break; - } + offset += 24; + /* LLC header is 8 bytes */ + type = ntohs((u_int16_t)*((u_int16_t*)&packet[offset+6])); - offset += tag_len; + ip_offset = offset + 8; - if(offset >= header->caplen) - return(nproto); /* Invalid packet */ - else { - eth_offset = offset; - goto datalink_check; + tunnel_type = ndpi_capwap_tunnel; + goto iph_check; + } } } } @@ -1598,9 +1961,10 @@ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, } /* process the packet */ - return(packet_processing(workflow, time, vlan_id, iph, iph6, + return(packet_processing(workflow, time_ms, vlan_id, tunnel_type, iph, iph6, ip_offset, header->caplen - ip_offset, - header->caplen, header, packet, header->ts)); + header->caplen, header, packet, header->ts, + csv_fp)); } /* ********************************************************** */ @@ -1656,8 +2020,15 @@ u_int32_t ethernet_crc32(const void* data, size_t n_bytes) { #ifdef USE_DPDK +#include +#include + static const struct rte_eth_conf port_conf_default = { +#if(RTE_VERSION < RTE_VERSION_NUM(19, 8, 0, 0)) .rxmode = { .max_rx_pkt_len = ETHER_MAX_LEN } +#else + .rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN } +#endif }; /* ************************************ */ @@ -1696,4 +2067,9 @@ int dpdk_port_init(int port, struct rte_mempool *mbuf_pool) { return 0; } +int dpdk_port_deinit(int port) { + rte_eth_dev_stop(port); + rte_eth_dev_close(port); +} + #endif diff --git a/example/reader_util.h b/example/reader_util.h index 3374f99..4dba29d 100644 --- a/example/reader_util.h +++ b/example/reader_util.h @@ -31,7 +31,9 @@ #include "uthash.h" #include +#include "ndpi_includes.h" #include "ndpi_classify.h" +#include "ndpi_typedefs.h" #ifdef USE_DPDK #include @@ -49,6 +51,7 @@ #define PREFETCH_OFFSET 3 extern int dpdk_port_init(int port, struct rte_mempool *mbuf_pool); +extern int dpdk_port_deinit(int port); #endif /* ETTA Spec defiintions for feature readiness */ @@ -93,7 +96,7 @@ typedef struct ndpi_ja3_info { // external hash table (host ip -> ) // used to aggregate ja3 fingerprints by hosts -typedef struct ndpi_host_ja3_fingerprints{ +typedef struct ndpi_host_ja3_fingerprints { u_int32_t ip; char *ip_string; char *dns_name; @@ -126,13 +129,13 @@ struct flow_metrics { struct ndpi_entropy { // Entropy fields - struct timeval src2dst_last_pkt_time, dst2src_last_pkt_time, flow_last_pkt_time; + pkt_timeval src2dst_last_pkt_time, dst2src_last_pkt_time, flow_last_pkt_time; u_int16_t src2dst_pkt_len[MAX_NUM_PKTS]; /*!< array of packet appdata lengths */ - struct timeval src2dst_pkt_time[MAX_NUM_PKTS]; /*!< array of arrival times */ + pkt_timeval src2dst_pkt_time[MAX_NUM_PKTS]; /*!< array of arrival times */ u_int16_t dst2src_pkt_len[MAX_NUM_PKTS]; /*!< array of packet appdata lengths */ - struct timeval dst2src_pkt_time[MAX_NUM_PKTS]; /*!< array of arrival times */ - struct timeval src2dst_start; /*!< first packet arrival time */ - struct timeval dst2src_start; /*!< first packet arrival time */ + pkt_timeval dst2src_pkt_time[MAX_NUM_PKTS]; /*!< array of arrival times */ + pkt_timeval src2dst_start; /*!< first packet arrival time */ + pkt_timeval dst2src_start; /*!< first packet arrival time */ u_int32_t src2dst_opackets; /*!< non-zero packet counts */ u_int32_t dst2src_opackets; /*!< non-zero packet counts */ u_int16_t src2dst_pkt_count; /*!< packet counts */ @@ -160,49 +163,78 @@ typedef struct ndpi_flow_info { u_int16_t dst_port; u_int8_t detection_completed, protocol, bidirectional, check_extra_packets; u_int16_t vlan_id; + ndpi_packet_tunnel tunnel_type; struct ndpi_flow_struct *ndpi_flow; char src_name[48], dst_name[48]; u_int8_t ip_version; - u_int64_t first_seen, last_seen; + u_int32_t cwr_count, src2dst_cwr_count, dst2src_cwr_count; + u_int32_t ece_count, src2dst_ece_count, dst2src_ece_count; + u_int32_t urg_count, src2dst_urg_count, dst2src_urg_count; + u_int32_t ack_count, src2dst_ack_count, dst2src_ack_count; + u_int32_t psh_count, src2dst_psh_count, dst2src_psh_count; + u_int32_t syn_count, src2dst_syn_count, dst2src_syn_count; + u_int32_t fin_count, src2dst_fin_count, dst2src_fin_count; + u_int32_t rst_count, src2dst_rst_count, dst2src_rst_count; + u_int32_t c_to_s_init_win, s_to_c_init_win; + u_int64_t first_seen_ms, last_seen_ms; u_int64_t src2dst_bytes, dst2src_bytes; + u_int64_t src2dst_goodput_bytes, dst2src_goodput_bytes; u_int32_t src2dst_packets, dst2src_packets; u_int32_t has_human_readeable_strings; char human_readeable_string_buffer[32]; - + // result only, not used for flow identification ndpi_protocol detected_protocol; // Flow data analysis struct ndpi_analyze_struct *iat_c_to_s, *iat_s_to_c, *iat_flow, *pktlen_c_to_s, *pktlen_s_to_c; - - char info[96]; - char host_server_name[256]; + + char info[160]; + char flow_extra_info[16]; + char host_server_name[240]; char bittorent_hash[41]; char dhcp_fingerprint[48]; - + ndpi_risk risk; + struct { u_int16_t ssl_version; - char client_info[64], server_info[64], - client_hassh[33], server_hassh[33], - server_organization[64], + char client_requested_server_name[64], server_info[64], + client_hassh[33], server_hassh[33], *server_names, + *tls_alpn, *tls_supported_versions, + *tls_issuerDN, *tls_subjectDN, ja3_client[33], ja3_server[33], sha1_cert_fingerprint[20]; + u_int8_t sha1_cert_fingerprint_set; + struct { + u_int16_t cipher_suite; + char *esni; + } encrypted_sni; time_t notBefore, notAfter; u_int16_t server_cipher; - ndpi_cipher_weakness client_unsafe_cipher, server_unsafe_cipher; + ndpi_cipher_weakness client_unsafe_cipher, server_unsafe_cipher; } ssh_tls; struct { - char url[256]; + char url[256], content_type[64], user_agent[128]; u_int response_status_code; } http; - + + struct { + char username[32], password[32]; + } telnet; + void *src_id, *dst_id; struct ndpi_entropy entropy; struct ndpi_entropy last_entropy; - + + /* Payload lenght bins */ +#ifdef DIRECTION_BINS + struct ndpi_bin payload_len_bin_src2dst, payload_len_bin_dst2src; +#else + struct ndpi_bin payload_len_bin; +#endif } ndpi_flow_info_t; @@ -277,7 +309,8 @@ void ndpi_free_flow_info_half(struct ndpi_flow_info *flow); /* Process a packet and update the workflow */ struct ndpi_proto ndpi_workflow_process_packet(struct ndpi_workflow * workflow, const struct pcap_pkthdr *header, - const u_char *packet); + const u_char *packet, + FILE * csv_fp); /* flow callbacks for complete detected flow @@ -296,12 +329,23 @@ static inline void ndpi_workflow_set_flow_giveup_callback(struct ndpi_workflow * /* compare two nodes in workflow */ int ndpi_workflow_node_cmp(const void *a, const void *b); -void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_flow_info *flow); +void process_ndpi_collected_info(struct ndpi_workflow * workflow, struct ndpi_flow_info *flow, FILE * csv_fp); u_int32_t ethernet_crc32(const void* data, size_t n_bytes); +void ndpi_flow_info_free_data(struct ndpi_flow_info *flow); void ndpi_flow_info_freer(void *node); const char* print_cipher_id(u_int32_t cipher); float ndpi_flow_get_byte_count_entropy(const uint32_t byte_count[256], unsigned int num_bytes); extern int nDPI_LogLevel; +#ifdef NDPI_ENABLE_DEBUG_MESSAGES + #define LOG(log_level, args...) \ + { \ + if(log_level <= nDPI_LogLevel) \ + printf(args); \ + } +#else + #define LOG(...) {} +#endif + #endif diff --git a/fuzz/.deps/fuzz_ndpi_reader-fuzz_ndpi_reader.Po b/fuzz/.deps/fuzz_ndpi_reader-fuzz_ndpi_reader.Po new file mode 100644 index 0000000..9ce06a8 --- /dev/null +++ b/fuzz/.deps/fuzz_ndpi_reader-fuzz_ndpi_reader.Po @@ -0,0 +1 @@ +# dummy diff --git a/fuzz/.deps/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po b/fuzz/.deps/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po new file mode 100644 index 0000000..9ce06a8 --- /dev/null +++ b/fuzz/.deps/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po @@ -0,0 +1 @@ +# dummy diff --git a/fuzz/.deps/fuzz_process_packet-fuzz_process_packet.Po b/fuzz/.deps/fuzz_process_packet-fuzz_process_packet.Po new file mode 100644 index 0000000..9ce06a8 --- /dev/null +++ b/fuzz/.deps/fuzz_process_packet-fuzz_process_packet.Po @@ -0,0 +1 @@ +# dummy diff --git a/fuzz/Makefile b/fuzz/Makefile new file mode 100644 index 0000000..e10fffe --- /dev/null +++ b/fuzz/Makefile @@ -0,0 +1,777 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# fuzz/Makefile. Generated from Makefile.in by configure. + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + + + + +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/libndpi +pkgincludedir = $(includedir)/libndpi +pkglibdir = $(libdir)/libndpi +pkglibexecdir = $(libexecdir)/libndpi +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = x86_64-pc-linux-gnu +host_triplet = x86_64-pc-linux-gnu +bin_PROGRAMS = fuzz_process_packet$(EXEEXT) fuzz_ndpi_reader$(EXEEXT) \ + fuzz_ndpi_reader_with_main$(EXEEXT) +#am__append_1 = $(LIB_FUZZING_ENGINE) +#am__append_2 = $(LIB_FUZZING_ENGINE) +#am__append_3 = $(LIB_FUZZING_ENGINE) +#am__append_4 = $(LIB_FUZZING_ENGINE) +subdir = fuzz +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \ + $(top_srcdir)/m4/ax_pthread.m4 $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/src/include/ndpi_config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +am_fuzz_ndpi_reader_OBJECTS = \ + fuzz_ndpi_reader-fuzz_ndpi_reader.$(OBJEXT) +fuzz_ndpi_reader_OBJECTS = $(am_fuzz_ndpi_reader_OBJECTS) +fuzz_ndpi_reader_DEPENDENCIES = ../src/lib/libndpi.a +am_fuzz_ndpi_reader_with_main_OBJECTS = \ + fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.$(OBJEXT) +fuzz_ndpi_reader_with_main_OBJECTS = \ + $(am_fuzz_ndpi_reader_with_main_OBJECTS) +fuzz_ndpi_reader_with_main_DEPENDENCIES = ../src/lib/libndpi.a +am_fuzz_process_packet_OBJECTS = \ + fuzz_process_packet-fuzz_process_packet.$(OBJEXT) +fuzz_process_packet_OBJECTS = $(am_fuzz_process_packet_OBJECTS) +fuzz_process_packet_DEPENDENCIES = ../src/lib/libndpi.a +AM_V_P = $(am__v_P_$(V)) +am__v_P_ = $(am__v_P_$(AM_DEFAULT_VERBOSITY)) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_$(V)) +am__v_GEN_ = $(am__v_GEN_$(AM_DEFAULT_VERBOSITY)) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_$(V)) +am__v_at_ = $(am__v_at_$(AM_DEFAULT_VERBOSITY)) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I. -I$(top_builddir)/src/include +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = \ + ./$(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Po \ + ./$(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po \ + ./$(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Po +am__mv = mv -f +AM_V_lt = $(am__v_lt_$(V)) +am__v_lt_ = $(am__v_lt_$(AM_DEFAULT_VERBOSITY)) +am__v_lt_0 = --silent +am__v_lt_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_$(V)) +am__v_CC_ = $(am__v_CC_$(AM_DEFAULT_VERBOSITY)) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_$(V)) +am__v_CCLD_ = $(am__v_CCLD_$(AM_DEFAULT_VERBOSITY)) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(fuzz_ndpi_reader_SOURCES) \ + $(fuzz_ndpi_reader_with_main_SOURCES) \ + $(fuzz_process_packet_SOURCES) +DIST_SOURCES = $(fuzz_ndpi_reader_SOURCES) \ + $(fuzz_ndpi_reader_with_main_SOURCES) \ + $(fuzz_process_packet_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = ${SHELL} /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/missing aclocal-1.16 +ADDITIONAL_LIBS = +AMTAR = $${TAR-tar} +AM_DEFAULT_VERBOSITY = 1 +AR = ar +AUTOCONF = ${SHELL} /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/missing autoconf +AUTOHEADER = ${SHELL} /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/missing autoheader +AUTOMAKE = ${SHELL} /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/missing automake-1.16 +AWK = mawk +BUILD_MINGW = +CC = gcc +CCDEPMODE = depmode=none +CFLAGS = -g -O2 -fdebug-prefix-map=/home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi=. -fstack-protector-strong -Wformat -Werror=format-security -I/usr/include/json-c +CPP = gcc -E +CPPFLAGS = -Wdate-time -D_FORTIFY_SOURCE=2 +CUSTOM_NDPI = +CXX = g++ +CXXCPP = +CXXDEPMODE = depmode=none +CXXFLAGS = -g -O2 -fdebug-prefix-map=/home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi=. -fstack-protector-strong -Wformat -Werror=format-security +CYGPATH_W = echo +DEFS = -DHAVE_CONFIG_H +DEPDIR = .deps +DLLTOOL = false +DL_LIB = +DPDK_TARGET = +DSYMUTIL = +DUMPBIN = +ECHO_C = +ECHO_N = -n +ECHO_T = +EGREP = /usr/bin/grep -E +EXEEXT = +EXTRA_TARGETS = generation example tests tests/unit +FGREP = /usr/bin/grep -F +GIT_RELEASE = 3.4.0-211-b1fb15a +GREP = /usr/bin/grep +HAVE_PTHREAD_SETAFFINITY_NP = +INSTALL = /usr/bin/install -c +INSTALL_DATA = ${INSTALL} -m 644 +INSTALL_PROGRAM = ${INSTALL} +INSTALL_SCRIPT = ${INSTALL} +INSTALL_STRIP_PROGRAM = $(install_sh) -c -s +LD = /usr/bin/ld -m elf_x86_64 +LDFLAGS = -Wl,-z,relro -Wl,-z,now +LIBOBJS = +LIBS = +LIBTOOL = $(SHELL) $(top_builddir)/libtool +LIB_FUZZING_ENGINE = +LIPO = +LN_S = ln -s +LTLIBOBJS = +LT_SYS_LIBRARY_PATH = +MAKEINFO = ${SHELL} /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/missing makeinfo +MANIFEST_TOOL = : +MKDIR_P = /usr/bin/mkdir -p +NDPI_API_VERSION = 124 +NDPI_MAJOR = 3 +NDPI_MINOR = 4 +NDPI_PATCH = 0 +NDPI_VERSION_SHORT = 3.4.0 +NM = /usr/bin/nm -B +NMEDIT = +OBJDUMP = objdump +OBJEXT = o +OTOOL = +OTOOL64 = +PACKAGE = libndpi +PACKAGE_BUGREPORT = +PACKAGE_NAME = libndpi +PACKAGE_STRING = libndpi 3.4.0 +PACKAGE_TARNAME = libndpi +PACKAGE_URL = +PACKAGE_VERSION = 3.4.0 +PATH_SEPARATOR = : +PCAP_INC = +PCAP_LIB = -lpcap +PTHREAD_CC = gcc +PTHREAD_CFLAGS = -pthread +PTHREAD_LIBS = +RANLIB = ranlib +SED = /usr/bin/sed +SET_MAKE = +SHELL = /bin/bash +STRIP = strip +VERSION = 3.4.0 +abs_builddir = /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/fuzz +abs_srcdir = /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/fuzz +abs_top_builddir = /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi +abs_top_srcdir = /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi +ac_ct_AR = ar +ac_ct_CC = gcc +ac_ct_CXX = g++ +ac_ct_DUMPBIN = +am__include = include +am__leading_dot = . +am__quote = +am__tar = $${TAR-tar} chof - "$$tardir" +am__untar = $${TAR-tar} xf - +ax_pthread_config = +bindir = ${exec_prefix}/bin +build = x86_64-pc-linux-gnu +build_alias = x86_64-linux-gnu +build_cpu = x86_64 +build_os = linux-gnu +build_vendor = pc +builddir = . +datadir = ${datarootdir} +datarootdir = ${prefix}/share +docdir = ${datarootdir}/doc/${PACKAGE_TARNAME} +dvidir = ${docdir} +exec_prefix = ${prefix} +host = x86_64-pc-linux-gnu +host_alias = +host_cpu = x86_64 +host_os = linux-gnu +host_vendor = pc +htmldir = ${docdir} +includedir = ${prefix}/include +infodir = ${prefix}/share/info +install_sh = ${SHELL} /home/shubham/vyatta_dpi/dpi/ndpi_cdot/ndpi/install-sh +libdir = ${prefix}/lib/x86_64-linux-gnu +libexecdir = ${exec_prefix}/libexec +localedir = ${datarootdir}/locale +localstatedir = /var +mandir = ${prefix}/share/man +mkdir_p = $(MKDIR_P) +oldincludedir = /usr/include +pdfdir = ${docdir} +prefix = /usr +program_transform_name = s,x,x, +psdir = ${docdir} +runstatedir = /run +sbindir = ${exec_prefix}/sbin +sharedstatedir = ${prefix}/com +srcdir = . +sysconfdir = /etc +target_alias = +top_build_prefix = ../ +top_builddir = .. +top_srcdir = .. +fuzz_process_packet_SOURCES = fuzz_process_packet.c +fuzz_process_packet_CFLAGS = $(am__append_1) +fuzz_process_packet_LDADD = ../src/lib/libndpi.a +fuzz_process_packet_LDFLAGS = $(ADDITIONAL_LIBS) $(LIBS) \ + $(am__append_2) +# force usage of CXX for linker +fuzz_process_packet_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXX) $(AM_CXXFLAGS) $(CXXFLAGS) \ + $(fuzz_process_packet_LDFLAGS) $(LDFLAGS) -o $@ + +fuzz_ndpi_reader_SOURCES = fuzz_ndpi_reader.c +fuzz_ndpi_reader_CFLAGS = -I../example/ $(am__append_3) +fuzz_ndpi_reader_LDADD = ../src/lib/libndpi.a +fuzz_ndpi_reader_LDFLAGS = ../example/libndpiReader.a $(PCAP_LIB) \ + $(ADDITIONAL_LIBS) $(LIBS) $(am__append_4) +# force usage of CXX for linker +fuzz_ndpi_reader_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXX) $(AM_CXXFLAGS) $(CXXFLAGS) \ + $(fuzz_ndpi_reader_LDFLAGS) $(LDFLAGS) -o $@ + +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) $(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) \ + $(fuzz_ndpi_reader_with_main_LDFLAGS) $(LDFLAGS) -o $@ + + +# required for Google oss-fuzz +# see https://github.com/google/oss-fuzz/tree/master/projects/ndpi +testpcaps := $(wildcard ../tests/pcap/*.pcap) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign fuzz/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign fuzz/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +fuzz_ndpi_reader$(EXEEXT): $(fuzz_ndpi_reader_OBJECTS) $(fuzz_ndpi_reader_DEPENDENCIES) $(EXTRA_fuzz_ndpi_reader_DEPENDENCIES) + @rm -f fuzz_ndpi_reader$(EXEEXT) + $(AM_V_GEN)$(fuzz_ndpi_reader_LINK) $(fuzz_ndpi_reader_OBJECTS) $(fuzz_ndpi_reader_LDADD) $(LIBS) + +fuzz_ndpi_reader_with_main$(EXEEXT): $(fuzz_ndpi_reader_with_main_OBJECTS) $(fuzz_ndpi_reader_with_main_DEPENDENCIES) $(EXTRA_fuzz_ndpi_reader_with_main_DEPENDENCIES) + @rm -f fuzz_ndpi_reader_with_main$(EXEEXT) + $(AM_V_GEN)$(fuzz_ndpi_reader_with_main_LINK) $(fuzz_ndpi_reader_with_main_OBJECTS) $(fuzz_ndpi_reader_with_main_LDADD) $(LIBS) + +fuzz_process_packet$(EXEEXT): $(fuzz_process_packet_OBJECTS) $(fuzz_process_packet_DEPENDENCIES) $(EXTRA_fuzz_process_packet_DEPENDENCIES) + @rm -f fuzz_process_packet$(EXEEXT) + $(AM_V_GEN)$(fuzz_process_packet_LINK) $(fuzz_process_packet_OBJECTS) $(fuzz_process_packet_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +#include ./$(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Po # am--include-marker +#include ./$(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po # am--include-marker +#include ./$(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Po # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +# $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +# $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +# $(am__mv) $$depbase.Tpo $$depbase.Po +# $(AM_V_CC)source='$<' object='$@' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(COMPILE) -c -o $@ $< + +.c.obj: +# $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +# $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +# $(am__mv) $$depbase.Tpo $$depbase.Po +# $(AM_V_CC)source='$<' object='$@' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +# $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +# $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +# $(am__mv) $$depbase.Tpo $$depbase.Plo +# $(AM_V_CC)source='$<' object='$@' libtool=yes +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(LTCOMPILE) -c -o $@ $< + +fuzz_ndpi_reader-fuzz_ndpi_reader.o: fuzz_ndpi_reader.c +# $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_CFLAGS) $(CFLAGS) -MT fuzz_ndpi_reader-fuzz_ndpi_reader.o -MD -MP -MF $(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Tpo -c -o fuzz_ndpi_reader-fuzz_ndpi_reader.o `test -f 'fuzz_ndpi_reader.c' || echo '$(srcdir)/'`fuzz_ndpi_reader.c +# $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Tpo $(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Po +# $(AM_V_CC)source='fuzz_ndpi_reader.c' object='fuzz_ndpi_reader-fuzz_ndpi_reader.o' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_CFLAGS) $(CFLAGS) -c -o fuzz_ndpi_reader-fuzz_ndpi_reader.o `test -f 'fuzz_ndpi_reader.c' || echo '$(srcdir)/'`fuzz_ndpi_reader.c + +fuzz_ndpi_reader-fuzz_ndpi_reader.obj: fuzz_ndpi_reader.c +# $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_CFLAGS) $(CFLAGS) -MT fuzz_ndpi_reader-fuzz_ndpi_reader.obj -MD -MP -MF $(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Tpo -c -o fuzz_ndpi_reader-fuzz_ndpi_reader.obj `if test -f 'fuzz_ndpi_reader.c'; then $(CYGPATH_W) 'fuzz_ndpi_reader.c'; else $(CYGPATH_W) '$(srcdir)/fuzz_ndpi_reader.c'; fi` +# $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Tpo $(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Po +# $(AM_V_CC)source='fuzz_ndpi_reader.c' object='fuzz_ndpi_reader-fuzz_ndpi_reader.obj' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_CFLAGS) $(CFLAGS) -c -o fuzz_ndpi_reader-fuzz_ndpi_reader.obj `if test -f 'fuzz_ndpi_reader.c'; then $(CYGPATH_W) 'fuzz_ndpi_reader.c'; else $(CYGPATH_W) '$(srcdir)/fuzz_ndpi_reader.c'; fi` + +fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.o: fuzz_ndpi_reader.c +# $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_with_main_CFLAGS) $(CFLAGS) -MT fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.o -MD -MP -MF $(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Tpo -c -o fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.o `test -f 'fuzz_ndpi_reader.c' || echo '$(srcdir)/'`fuzz_ndpi_reader.c +# $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Tpo $(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po +# $(AM_V_CC)source='fuzz_ndpi_reader.c' object='fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.o' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_with_main_CFLAGS) $(CFLAGS) -c -o fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.o `test -f 'fuzz_ndpi_reader.c' || echo '$(srcdir)/'`fuzz_ndpi_reader.c + +fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.obj: fuzz_ndpi_reader.c +# $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_with_main_CFLAGS) $(CFLAGS) -MT fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.obj -MD -MP -MF $(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Tpo -c -o fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.obj `if test -f 'fuzz_ndpi_reader.c'; then $(CYGPATH_W) 'fuzz_ndpi_reader.c'; else $(CYGPATH_W) '$(srcdir)/fuzz_ndpi_reader.c'; fi` +# $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Tpo $(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po +# $(AM_V_CC)source='fuzz_ndpi_reader.c' object='fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.obj' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_ndpi_reader_with_main_CFLAGS) $(CFLAGS) -c -o fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.obj `if test -f 'fuzz_ndpi_reader.c'; then $(CYGPATH_W) 'fuzz_ndpi_reader.c'; else $(CYGPATH_W) '$(srcdir)/fuzz_ndpi_reader.c'; fi` + +fuzz_process_packet-fuzz_process_packet.o: fuzz_process_packet.c +# $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_process_packet_CFLAGS) $(CFLAGS) -MT fuzz_process_packet-fuzz_process_packet.o -MD -MP -MF $(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Tpo -c -o fuzz_process_packet-fuzz_process_packet.o `test -f 'fuzz_process_packet.c' || echo '$(srcdir)/'`fuzz_process_packet.c +# $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Tpo $(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Po +# $(AM_V_CC)source='fuzz_process_packet.c' object='fuzz_process_packet-fuzz_process_packet.o' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_process_packet_CFLAGS) $(CFLAGS) -c -o fuzz_process_packet-fuzz_process_packet.o `test -f 'fuzz_process_packet.c' || echo '$(srcdir)/'`fuzz_process_packet.c + +fuzz_process_packet-fuzz_process_packet.obj: fuzz_process_packet.c +# $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_process_packet_CFLAGS) $(CFLAGS) -MT fuzz_process_packet-fuzz_process_packet.obj -MD -MP -MF $(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Tpo -c -o fuzz_process_packet-fuzz_process_packet.obj `if test -f 'fuzz_process_packet.c'; then $(CYGPATH_W) 'fuzz_process_packet.c'; else $(CYGPATH_W) '$(srcdir)/fuzz_process_packet.c'; fi` +# $(AM_V_at)$(am__mv) $(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Tpo $(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Po +# $(AM_V_CC)source='fuzz_process_packet.c' object='fuzz_process_packet-fuzz_process_packet.obj' libtool=no +# DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) + $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(fuzz_process_packet_CFLAGS) $(CFLAGS) -c -o fuzz_process_packet-fuzz_process_packet.obj `if test -f 'fuzz_process_packet.c'; then $(CYGPATH_W) 'fuzz_process_packet.c'; else $(CYGPATH_W) '$(srcdir)/fuzz_process_packet.c'; fi` + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Po + -rm -f ./$(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po + -rm -f ./$(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/fuzz_ndpi_reader-fuzz_ndpi_reader.Po + -rm -f ./$(DEPDIR)/fuzz_ndpi_reader_with_main-fuzz_ndpi_reader.Po + -rm -f ./$(DEPDIR)/fuzz_process_packet-fuzz_process_packet.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ + clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \ + ctags ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-binPROGRAMS \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-binPROGRAMS + +.PRECIOUS: Makefile + + +fuzz_ndpi_reader_seed_corpus.zip: $(testpcaps) + zip -r fuzz_ndpi_reader_seed_corpus.zip $(testpcaps) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/fuzz/Makefile.am b/fuzz/Makefile.am new file mode 100644 index 0000000..b70eae2 --- /dev/null +++ b/fuzz/Makefile.am @@ -0,0 +1,43 @@ +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) $(LIBS) +if HAS_FUZZLDFLAGS +fuzz_process_packet_CFLAGS += $(LIB_FUZZING_ENGINE) +fuzz_process_packet_LDFLAGS += $(LIB_FUZZING_ENGINE) +endif +# force usage of CXX for linker +fuzz_process_packet_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXX) $(AM_CXXFLAGS) $(CXXFLAGS) \ + $(fuzz_process_packet_LDFLAGS) $(LDFLAGS) -o $@ + +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) $(LIBS) +if HAS_FUZZLDFLAGS +fuzz_ndpi_reader_CFLAGS += $(LIB_FUZZING_ENGINE) +fuzz_ndpi_reader_LDFLAGS += $(LIB_FUZZING_ENGINE) +endif +# force usage of CXX for linker +fuzz_ndpi_reader_LINK=$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXX) $(AM_CXXFLAGS) $(CXXFLAGS) \ + $(fuzz_ndpi_reader_LDFLAGS) $(LDFLAGS) -o $@ + +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) $(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) \ + $(fuzz_ndpi_reader_with_main_LDFLAGS) $(LDFLAGS) -o $@ + +# required for Google oss-fuzz +# see https://github.com/google/oss-fuzz/tree/master/projects/ndpi +testpcaps := $(wildcard ../tests/pcap/*.pcap) + +fuzz_ndpi_reader_seed_corpus.zip: $(testpcaps) + zip -r fuzz_ndpi_reader_seed_corpus.zip $(testpcaps) diff --git a/fuzz/fuzz_ndpi_reader.c b/fuzz/fuzz_ndpi_reader.c new file mode 100644 index 0000000..9f73eb4 --- /dev/null +++ b/fuzz/fuzz_ndpi_reader.c @@ -0,0 +1,163 @@ +#include "reader_util.h" +#include "ndpi_api.h" + +#include + +#include +#include +#include + +struct ndpi_workflow_prefs *prefs = NULL; + +int nDPI_LogLevel = 0; +char *_debug_protocols = NULL; +u_int32_t current_ndpi_memory = 0, max_ndpi_memory = 0; +u_int8_t enable_protocol_guess = 1, enable_payload_analyzer = 0; +u_int8_t enable_joy_stats = 0; +u_int8_t human_readeable_string_len = 5; +u_int8_t max_num_udp_dissected_pkts = 16 /* 8 is enough for most protocols, Signal requires more */, max_num_tcp_dissected_pkts = 80 /* due to telnet */; + +int bufferToFile(const char * name, const uint8_t *Data, size_t Size) { + FILE * fd; + if (remove(name) != 0) { + if (errno != ENOENT) { + perror("remove failed"); + return -1; + } + } + fd = fopen(name, "wb"); + if (fd == NULL) { + perror("open failed"); + return -2; + } + if (fwrite (Data, 1, Size, fd) != Size) { + fclose(fd); + return -3; + } + fclose(fd); + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + pcap_t * pkts; + const u_char *pkt; + struct pcap_pkthdr *header; + int r; + char errbuf[PCAP_ERRBUF_SIZE]; + NDPI_PROTOCOL_BITMASK all; + char * pcap_path = tempnam("/tmp", "fuzz-ndpi-reader"); + + if (prefs == NULL) { + prefs = calloc(sizeof(struct ndpi_workflow_prefs), 1); + if (prefs == NULL) { + //should not happen + return 1; + } + prefs->decode_tunnels = 1; + prefs->num_roots = 16; + prefs->max_ndpi_flows = 1024; + prefs->quiet_mode = 0; + } + bufferToFile(pcap_path, Data, Size); + + pkts = pcap_open_offline(pcap_path, errbuf); + if (pkts == NULL) { + remove(pcap_path); + free(pcap_path); + return 0; + } + struct ndpi_workflow * workflow = ndpi_workflow_init(prefs, pkts); + // enable all protocols + NDPI_BITMASK_SET_ALL(all); + ndpi_set_protocol_detection_bitmask2(workflow->ndpi_struct, &all); + memset(workflow->stats.protocol_counter, 0, + sizeof(workflow->stats.protocol_counter)); + memset(workflow->stats.protocol_counter_bytes, 0, + sizeof(workflow->stats.protocol_counter_bytes)); + memset(workflow->stats.protocol_flows, 0, + sizeof(workflow->stats.protocol_flows)); + ndpi_finalize_initalization(workflow->ndpi_struct); + + r = pcap_next_ex(pkts, &header, &pkt); + while (r > 0) { + if(header->caplen >= 42 /* ARP+ size */) { + /* allocate an exact size buffer to check overflows */ + uint8_t *packet_checked = malloc(header->caplen); + + if(packet_checked) { + memcpy(packet_checked, pkt, header->caplen); + ndpi_workflow_process_packet(workflow, header, packet_checked, NULL); + free(packet_checked); + } + } + + r = pcap_next_ex(pkts, &header, &pkt); + } + ndpi_workflow_free(workflow); + pcap_close(pkts); + + remove(pcap_path); + free(pcap_path); + + return 0; +} + +#ifdef BUILD_MAIN +int main(int argc, char ** argv) +{ + FILE * pcap_file; + long pcap_file_size; + uint8_t * pcap_buffer; + int test_retval; + + if (argc != 2) { + fprintf(stderr, "usage: %s: [pcap-file]\n", + (argc > 0 ? argv[0] : "fuzz_ndpi_reader_with_main")); + return 1; + } + + pcap_file = fopen(argv[1], "r"); + if (pcap_file == NULL) { + perror("fopen failed"); + return 1; + } + + if (fseek(pcap_file, 0, SEEK_END) != 0) { + perror("fseek(SEEK_END) failed"); + fclose(pcap_file); + return 1; + } + + pcap_file_size = ftell(pcap_file); + if (pcap_file_size < 0) { + perror("ftell failed"); + fclose(pcap_file); + return 1; + } + + if (fseek(pcap_file, 0, SEEK_SET) != 0) { + perror("fseek(0, SEEK_SET) failed"); + fclose(pcap_file); + return 1; + } + + pcap_buffer = malloc(pcap_file_size); + if (pcap_buffer == NULL) { + perror("malloc failed"); + fclose(pcap_file); + return 1; + } + + if (fread(pcap_buffer, sizeof(*pcap_buffer), pcap_file_size, pcap_file) != pcap_file_size) { + perror("fread failed"); + fclose(pcap_file); + return 1; + } + + test_retval = LLVMFuzzerTestOneInput(pcap_buffer, pcap_file_size); + fclose(pcap_file); + free(pcap_buffer); + + return test_retval; +} +#endif diff --git a/fuzz/fuzz_process_packet.c b/fuzz/fuzz_process_packet.c new file mode 100644 index 0000000..5af15af --- /dev/null +++ b/fuzz/fuzz_process_packet.c @@ -0,0 +1,28 @@ +#include "ndpi_api.h" + +#include +#include + +struct ndpi_detection_module_struct *ndpi_info_mod = NULL; +struct ndpi_id_struct *src; +struct ndpi_id_struct *dst; + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if (ndpi_info_mod == NULL) { + ndpi_info_mod = ndpi_init_detection_module(ndpi_no_prefs); + NDPI_PROTOCOL_BITMASK all; + NDPI_BITMASK_SET_ALL(all); + ndpi_set_protocol_detection_bitmask2(ndpi_info_mod, &all); + src = ndpi_malloc(SIZEOF_ID_STRUCT); + dst = ndpi_malloc(SIZEOF_ID_STRUCT); + } + + struct ndpi_flow_struct *ndpi_flow = ndpi_flow_malloc(SIZEOF_FLOW_STRUCT); + memset(ndpi_flow, 0, SIZEOF_FLOW_STRUCT); + memset(src, 0, SIZEOF_ID_STRUCT); + memset(dst, 0, SIZEOF_ID_STRUCT); + ndpi_detection_process_packet(ndpi_info_mod, ndpi_flow, Data, Size, 0, src, dst); + ndpi_free_flow(ndpi_flow); + + return 0; +} diff --git a/libndpi.pc.in b/libndpi.pc.in index 5e1cde7..b67b2d7 100644 --- a/libndpi.pc.in +++ b/libndpi.pc.in @@ -6,5 +6,5 @@ includedir=@includedir@ Name: libndpi Description: deep packet inspection library Version: @VERSION@ -Libs: -L${libdir} -lndpi +Libs: -L${libdir} -lndpi @ADDITIONAL_LIBS@ Cflags: -I${includedir}/ndpi diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 new file mode 100644 index 0000000..bd753b3 --- /dev/null +++ b/m4/ax_check_compile_flag.m4 @@ -0,0 +1,53 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT]) +# +# DESCRIPTION +# +# Check whether the given FLAG works with the current language's compiler +# or gives an error. (Warnings, however, are ignored) +# +# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on +# success/failure. +# +# If EXTRA-FLAGS is defined, it is added to the current language's default +# flags (e.g. CFLAGS) when the check is done. The check is thus made with +# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to +# force the compiler to issue an error when a bad flag is given. +# +# INPUT gives an alternative input source to AC_COMPILE_IFELSE. +# +# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this +# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG. +# +# LICENSE +# +# Copyright (c) 2008 Guido U. Draheim +# Copyright (c) 2011 Maarten Bosmans +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 6 + +AC_DEFUN([AX_CHECK_COMPILE_FLAG], +[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF +AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl +AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ + ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS + _AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1" + AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])], + [AS_VAR_SET(CACHEVAR,[yes])], + [AS_VAR_SET(CACHEVAR,[no])]) + _AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags]) +AS_VAR_IF(CACHEVAR,yes, + [m4_default([$2], :)], + [m4_default([$3], :)]) +AS_VAR_POPDEF([CACHEVAR])dnl +])dnl AX_CHECK_COMPILE_FLAGS diff --git a/ndpiReader.1 b/ndpiReader.1 new file mode 100644 index 0000000..85adbd7 --- /dev/null +++ b/ndpiReader.1 @@ -0,0 +1,78 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" (C) Copyright 2014 Ludovico Cavedon , +.\" +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH NDPIREADER 1 "2014-08-15" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) +.SH NAME +ndpiReader \- example tool for libndpi +.SH SYNOPSIS +.B ndpiReader +.RB -i +.IR file.pcap|device +[\fIoptions\fR] +.SH DESCRIPTION +The +.B ndpiReader +command is an example tool that uses libndpi. +.B ndpiReader +is able to read from a pcap file or catpure traffic from a network interface +and process it with libndpi. +It implements only some basic features just to show what can be done with +libndpi. +.PP +.SH OPTIONS +.TP +.B \-i \fIfile.pcap|device\fR +Specify a pcap file/playlist to read packets from or a device for live capture +(comma-separated list). +.TP +.B \-f \fIbpf_filter\fR +Specify a BPF filter for filtering selected traffic. +.TP +.B \-s \fIduration\fR +Maximum capture duration in seconds (live traffic capture only). +.TP +.B \-p \fIfile.protos\fR +Specify a protocol file (eg. protos.txt). +.TP +.B \-l \fInum_loops\fR +Number of detection loops (test only). +.TP +.B \-n \fInum_threads\fR +Number of threads. Default: number of interfaces in \fB\-i\fR. Ignored with pcap +files. +.TP +.B \-j \fIfile.json\fR +Specify a file to write the content of packets in .json format. +.TP +.B \-g \fIid:id...\fR +Thread affinity mask (one core id per thread). +.TP +.B \-d +Disable protocol guess and use only DPI. +.TP +.B \-t +Dissect GTP tunnels. +.TP +.B \-h +Display a usage message. +.TP +.B \-v \fI1|2\fR +Verbose 'unknown protocol' packet print. 1=verbose, 2=very verbose. +.TP +.B \-V \fI1|2\fR +Verbose libndpi trace log print. 1=trace, 2=debug. diff --git a/packages/openwrt/Makefile b/packages/openwrt/Makefile index 4b8429b..5d56e18 100644 --- a/packages/openwrt/Makefile +++ b/packages/openwrt/Makefile @@ -1,22 +1,22 @@ # -# Copyright (C) 2018 - ntop.org +# Copyright (C) 2018-20 - ntop.org # include $(TOPDIR)/rules.mk PKG_NAME:=libndpi -PKG_VERSION:=1333.ab2f3ce +PKG_VERSION:=17022020 PKG_RELEASE:=1 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://github.com/ntop/nDPI.git -PKG_SOURCE_VERSION:=ab2f3cefc89017d73e67faa4eb4011e7e3f2044d +PKG_SOURCE_VERSION:=1f921562d1d7962f1d23ca5b59c25f9b65073460 PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) PKG_SOURCE_PROTO:=git -PKG_MAINTAINER:=Emanuele Faranda +PKG_MAINTAINER:=Luca Deri PKG_LICENSE:=GPL3 -PKG_BUILD_DEPENDS:=+libpcap +PKG_BUILD_DEPENDS:= PKG_BUILD_PARALLEL:=1 # autogen fix @@ -27,7 +27,6 @@ include $(INCLUDE_DIR)/package.mk define Package/libndpi SECTION:=network CATEGORY:=Network - #DEPENDS:=+libc +libjson-c +libpthread TITLE:=nDPI Deep Packet Inspection Library URL:=https://www.ntop.org endef @@ -37,8 +36,7 @@ define Package/libndpi/description endef CONFIGURE_ARGS += \ - --with-pic \ - --disable-json-c \ + --with-only-libndpi define Build/Prepare $(call Build/Prepare/Default) diff --git a/packages/openwrt/README b/packages/openwrt/README new file mode 100644 index 0000000..5a2cf27 --- /dev/null +++ b/packages/openwrt/README @@ -0,0 +1,25 @@ +Howto Compile lindpi on OpenWRT +------------------------------- + +cd myopenwrt_directory +mkdir package/network/services/libndpi +cd package/network/services/libndpi +cp ~/nDPI/packages/openwrt/Makefile . +cd myopenwrt_directory +make menuconfig + +Go under network and select + + libndpi.............................. nDPI Deep Packet Inspection Library + + +Build Commands +-------------- + +If you want to build just libndpi do: +make -j1 V=s package/network/services/libndpi/clean +make -j1 V=s package/network/services/libndpi/compile + +Other Documents +--------------- +https://openwrt.org/packages/pkgdata/libndpi \ No newline at end of file diff --git a/packages/rpm/Makefile.in b/packages/rpm/Makefile.in index 9546e44..88649cc 100644 --- a/packages/rpm/Makefile.in +++ b/packages/rpm/Makefile.in @@ -29,8 +29,8 @@ build-rpm: build-src cleanup-rpm tar cvfz $(HOME)/rpmbuild/SOURCES/ndpi-@PACKAGE_VERSION@.tgz ndpi-@PACKAGE_VERSION@ @rm -f $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(PACKAGE) @rpmbuild -bb ./$(APPL).spec --define "buildnumber $(GIT_REVISION)" - @if [[ $EUID -ne 0 ]]; then ./rpm-sign.exp $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(PACKAGE); fi - @if [[ $EUID -ne 0 ]]; then ./rpm-sign.exp $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(DEV_PACKAGE); fi + @if [[ $EUID -ne 0 ]]; then @RPM_SIGN_CMD@ $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(PACKAGE); fi + @if [[ $EUID -ne 0 ]]; then @RPM_SIGN_CMD@ $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(DEV_PACKAGE); fi @echo "" @echo "Package contents:" @rpm -qpl $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(PACKAGE) diff --git a/packages/rpm/configure b/packages/rpm/configure index f1d1084..6609f73 100755 --- a/packages/rpm/configure +++ b/packages/rpm/configure @@ -583,6 +583,7 @@ PACKAGE_URL='' ac_subst_vars='LTLIBOBJS LIBOBJS +RPM_SIGN_CMD MAJOR_RELEASE GIT_REVISION KERNEL @@ -1698,6 +1699,16 @@ PACKAGE_VERSION=`../version.sh --release` MAJOR_RELEASE=`../version.sh --major-release` GIT_REVISION=`../version.sh --revision` +CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 3|cut -d '.' -f 1` +if test $CENTOS_RELEASE = "release"; then + CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 4|cut -d '.' -f 1` +fi + +RPM_SIGN_CMD="rpm --addsign" +if test "$CENTOS_RELEASE" -ne 8; then + RPM_SIGN_CMD="./rpm-sign.exp" +fi + ac_config_files="$ac_config_files Makefile ndpi.spec" @@ -1710,6 +1721,7 @@ ac_config_files="$ac_config_files Makefile ndpi.spec" + cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure diff --git a/packages/rpm/configure.in b/packages/rpm/configure.in index 1eaa0ea..67f1b47 100644 --- a/packages/rpm/configure.in +++ b/packages/rpm/configure.in @@ -29,6 +29,16 @@ PACKAGE_VERSION=`../version.sh --release` MAJOR_RELEASE=`../version.sh --major-release` GIT_REVISION=`../version.sh --revision` +CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 3|cut -d '.' -f 1` +if test $CENTOS_RELEASE = "release"; then + CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 4|cut -d '.' -f 1` +fi + +RPM_SIGN_CMD="rpm --addsign" +if test "$CENTOS_RELEASE" -ne 8; then + RPM_SIGN_CMD="./rpm-sign.exp" +fi + AC_CONFIG_FILES([Makefile ndpi.spec]) AC_SUBST(PACKAGE_VERSION) @@ -39,6 +49,7 @@ AC_SUBST(DATE) AC_SUBST(KERNEL) AC_SUBST(GIT_REVISION) AC_SUBST(MAJOR_RELEASE) +AC_SUBST(RPM_SIGN_CMD) AC_OUTPUT diff --git a/packages/rpm/ndpi.spec.in b/packages/rpm/ndpi.spec.in index a177e37..295dac1 100644 --- a/packages/rpm/ndpi.spec.in +++ b/packages/rpm/ndpi.spec.in @@ -12,6 +12,8 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-root %{?el6:Requires: glibc >= 2.3.4 numactl coreutils } AutoReqProv: no +%define debug_package %{nil} + %description nDPI Open and Extensible LGPLv3 Deep Packet Inspection Library. @@ -75,6 +77,8 @@ rm -fr $RPM_BUILD_ROOT /usr/local/lib/libndpi.so.@MAJOR_RELEASE@ /usr/local/lib/libndpi.so.@PACKAGE_VERSION@ /usr/local/bin/ndpiReader +/usr/local/share/ndpi/ndpiCustomCategory.txt +/usr/local/share/ndpi/ndpiProtos.txt /etc/ld.so.conf.d/ndpi.conf %{libdir}/pkgconfig/libndpi.pc diff --git a/python/Makefile.in b/python/Makefile.in index 91b4230..497e689 100644 --- a/python/Makefile.in +++ b/python/Makefile.in @@ -1,8 +1,8 @@ CC=@CC@ -CFLAGS=-I. -I../src/include -I./src/lib/third_party/include -shared -Wl, +CFLAGS=-I. -I../src/include -I./src/lib/third_party/include @CFLAGS@ @CUSTOM_NDPI@ -shared #LIBNDPI=../src/lib/libndpi.so.@NDPI_VERSION_SHORT@ LIBNDPI=../src/lib/libndpi.a -LDFLAGS=$(CFILE) $(LIBNDPI) -lpcap +LDFLAGS=$(CFILE) $(LIBNDPI) -lpcap @ADDITIONAL_LIBS@ SHARE = -soname,ndpi_wrap SO=ndpi_wrap.so OBJS = ndpi_wrap.o @@ -19,7 +19,7 @@ endif all: $(SO) $(SO): $(CFILE) $(LIBNDPI) Makefile - $(CC) $(CFLAGS)$(SHARE) -o $@ $(PIC) $(LDFLAGS) + $(CC) $(CFLAGS) -Wl,$(SHARE) -o $@ $(PIC) $(LDFLAGS) # ln -s $(LIBNDPI) . clean: diff --git a/python/README b/python/README deleted file mode 100644 index 61a689a..0000000 --- a/python/README +++ /dev/null @@ -1,7 +0,0 @@ -This direcgory contains the Python bindings for nDPI - -Usage: -- python3 ndpi_example.py -- python3 ndpi_example.py - -Code courtesy of Massimo Puddu diff --git a/python/README.rst b/python/README.rst new file mode 100644 index 0000000..bb8e51a --- /dev/null +++ b/python/README.rst @@ -0,0 +1,43 @@ +nDPI Python bindings +-------------------- + +This directory contains the Python3 bindings for nDPI. We provide both cffi and ctypes based bindings. + +**cffi bindings** + +Files: + +* ndpi.py + +Example (using `nfstream `_ package): + +.. code-block:: bash + + pip3 install nfstream + python3 flow_printer.py + python3 flow_printer.py + +Code courtesy: + +* Zied Aouini + +**ctypes bindings** + +Files: + +* ndpi_typestruct.py +* ndpi_wrap.c +* Makefile.in + +Example: + +.. code-block:: bash + + pip3 install scapy + python3 ndpi_example.py + python3 ndpi_example.py + +Code courtesy: + +* Massimo Puddu +* Zied Aouini diff --git a/python/flow_printer.py b/python/flow_printer.py new file mode 100644 index 0000000..5579694 --- /dev/null +++ b/python/flow_printer.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +------------------------------------------------------------------------------------------------------------------------ +flow_printer.py +Copyright (C) 2019-20 - NFStream Developers +This file is part of NFStream, a Flexible Network Data Analysis Framework (https://www.nfstream.org/). +NFStream is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later +version. +NFStream is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +You should have received a copy of the GNU Lesser General Public License along with NFStream. +If not, see . +------------------------------------------------------------------------------------------------------------------------ +""" + +from nfstream import NFStreamer +import sys + +# Example must run with nfstream >= 6.1.1 +path = sys.argv[1] +flow_streamer = NFStreamer(source=path, statistical_analysis=False, performance_report=1) +result = {} +try: + for flow in flow_streamer: + print(flow) + try: + result[flow.application_name] += flow.bidirectional_packets + except KeyError: + result[flow.application_name] = flow.bidirectional_packets + print("\nSummary (Application Name: Packets):") + print(result) +except KeyboardInterrupt: + print("\nSummary (Application Name: Packets):") + print(result) + print("Terminated.") diff --git a/python/ndpi.py b/python/ndpi.py new file mode 100644 index 0000000..066ca2e --- /dev/null +++ b/python/ndpi.py @@ -0,0 +1,1459 @@ +""" +file: ndpi.py +This file is part of nfstream. + +Copyright (C) 2019-20 - nfstream.org + +nfstream is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +nfstream is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with nfstream. +If not, see . +""" + +from os.path import abspath, dirname +import cffi + +cc_ndpi_network_headers = """ +struct ptr_uint32 { + uint32_t value; +}; + +struct ndpi_chdlc +{ + uint8_t addr; /* 0x0F (Unicast) - 0x8F (Broadcast) */ + uint8_t ctrl; /* always 0x00 */ + uint16_t proto_code; /* protocol type (e.g. 0x0800 IP) */ +}; + +/* SLARP - Serial Line ARP http://tinyurl.com/qa54e95 */ +struct ndpi_slarp +{ + /* address requests (0x00) + address replies (0x01) + keep-alive (0x02) + */ + uint32_t slarp_type; + uint32_t addr_1; + uint32_t addr_2; +}; + +/* Cisco Discovery Protocol http://tinyurl.com/qa6yw9l */ +struct ndpi_cdp +{ + uint8_t version; + uint8_t ttl; + uint16_t checksum; + uint16_t type; + uint16_t length; +}; + +/* +++++++++++++++ Ethernet header (IEEE 802.3) +++++++++++++++ */ +struct ndpi_ethhdr +{ + uint8_t h_dest[6]; /* destination eth addr */ + uint8_t h_source[6]; /* source ether addr */ + uint16_t h_proto; /* data length (<= 1500) or type ID proto (>=1536) */ +}; + +/* +++++++++++++++ ARP header +++++++++++++++ */ +struct ndpi_arphdr { + uint16_t ar_hrd;/* Format of hardware address. */ + uint16_t ar_pro;/* Format of protocol address. */ + uint8_t ar_hln;/* Length of hardware address. */ + uint8_t ar_pln;/* Length of protocol address. */ + uint16_t ar_op;/* ARP opcode (command). */ + uint8_t arp_sha[6];/* sender hardware address */ + uint32_t arp_spa;/* sender protocol address */ + uint8_t arp_tha[6];/* target hardware address */ + uint32_t arp_tpa;/* target protocol address */ +}; + +/* +++++++++++++++ DHCP header +++++++++++++++ */ +struct ndpi_dhcphdr { + uint8_t msgType; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid;/* 4 */ + uint16_t secs;/* 8 */ + uint16_t flags; + uint32_t ciaddr;/* 12 */ + uint32_t yiaddr;/* 16 */ + uint32_t siaddr;/* 20 */ + uint32_t giaddr;/* 24 */ + uint8_t chaddr[16]; /* 28 */ + uint8_t sname[64]; /* 44 */ + uint8_t file[128]; /* 108 */ + uint32_t magic; /* 236 */ + uint8_t options[308]; +}; + +/* +++++++++++++++ MDNS rsp header +++++++++++++++ */ +struct ndpi_mdns_rsp_entry { + uint16_t rsp_type, rsp_class; + uint32_t ttl; + uint16_t data_len; +}; + +/* +++++++++++++++++++ LLC header (IEEE 802.2) ++++++++++++++++ */ +struct ndpi_snap_extension +{ + uint16_t oui; + uint8_t oui2; + uint16_t proto_ID; +}; + +struct ndpi_llc_header_snap +{ + uint8_t dsap; + uint8_t ssap; + uint8_t ctrl; + struct ndpi_snap_extension snap; +}; + +/* ++++++++++ RADIO TAP header (for IEEE 802.11) +++++++++++++ */ +struct ndpi_radiotap_header +{ + uint8_t version; /* set to 0 */ + uint8_t pad; + uint16_t len; + uint32_t present; + uint64_t MAC_timestamp; + uint8_t flags; +}; + +/* ++++++++++++ Wireless header (IEEE 802.11) ++++++++++++++++ */ +struct ndpi_wifi_header +{ + uint16_t fc; + uint16_t duration; + uint8_t rcvr[6]; + uint8_t trsm[6]; + uint8_t dest[6]; + uint16_t seq_ctrl; + /* uint64_t ccmp - for data encryption only - check fc.flag */ +}; + +/* +++++++++++++++++++++++ MPLS header +++++++++++++++++++++++ */ +struct ndpi_mpls_header +{ + /* Before using this strcut to parse an MPLS header, you will need to convert + * the 4-byte data to the correct endianess with ntohl(). */ + uint32_t ttl:8, s:1, exp:3, label:20; +}; + +extern union mpls { + uint32_t u32; + struct ndpi_mpls_header mpls; +} mpls; + +/* ++++++++++++++++++++++++ IP header ++++++++++++++++++++++++ */ +struct ndpi_iphdr { + uint8_t ihl:4, version:4; + uint8_t tos; + uint16_t tot_len; + uint16_t id; + uint16_t frag_off; + uint8_t ttl; + uint8_t protocol; + uint16_t check; + uint32_t saddr; + uint32_t daddr; +}; + +/* +++++++++++++++++++++++ IPv6 header +++++++++++++++++++++++ */ +/* rfc3542 */ +struct ndpi_in6_addr { + union { + uint8_t u6_addr8[16]; + uint16_t u6_addr16[8]; + uint32_t u6_addr32[4]; + uint64_t u6_addr64[2]; + } u6_addr; /* 128-bit IP6 address */ +}; + +struct ndpi_ip6_hdrctl { + uint32_t ip6_un1_flow; + uint16_t ip6_un1_plen; + uint8_t ip6_un1_nxt; + uint8_t ip6_un1_hlim; +}; + +struct ndpi_ipv6hdr { + struct ndpi_ip6_hdrctl ip6_hdr; + struct ndpi_in6_addr ip6_src; + struct ndpi_in6_addr ip6_dst; +}; + +/* +++++++++++++++++++++++ TCP header +++++++++++++++++++++++ */ +struct ndpi_tcphdr +{ + uint16_t source; + uint16_t dest; + uint32_t seq; + uint32_t ack_seq; + uint16_t res1:4, doff:4, fin:1, syn:1, rst:1, psh:1, ack:1, urg:1, ece:1, cwr:1; + uint16_t window; + uint16_t check; + uint16_t urg_ptr; +}; + +/* +++++++++++++++++++++++ UDP header +++++++++++++++++++++++ */ +struct ndpi_udphdr +{ + uint16_t source; + uint16_t dest; + uint16_t len; + uint16_t check; +}; +struct ndpi_dns_packet_header { + uint16_t tr_id; + uint16_t flags; + uint16_t num_queries; + uint16_t num_answers; + uint16_t authority_rrs; + uint16_t additional_rrs; +}; + +/* +++++++++++++++++++++++ ICMP header +++++++++++++++++++++++ */ +struct ndpi_icmphdr { + uint8_t type;/* message type */ + uint8_t code;/* type sub-code */ + uint16_t checksum; + union { + struct { + uint16_t id; + uint16_t sequence; + } echo; /* echo datagram */ + + uint32_t gateway; /* gateway address */ + struct { + uint16_t _unused; + uint16_t mtu; + } frag;/* path mtu discovery */ + } un; +}; + +/* +++++++++++++++++++++++ ICMP6 header +++++++++++++++++++++++ */ +struct ndpi_icmp6hdr { + uint8_t icmp6_type; /* type field */ + uint8_t icmp6_code; /* code field */ + uint16_t icmp6_cksum; /* checksum field */ + union { + uint32_t icmp6_un_data32[1]; /* type-specific field */ + uint16_t icmp6_un_data16[2]; /* type-specific field */ + uint8_t icmp6_un_data8[4]; /* type-specific field */ + } icmp6_dataun; +}; + +/* +++++++++++++++++++++++ VXLAN header +++++++++++++++++++++++ */ +struct ndpi_vxlanhdr { + uint16_t flags; + uint16_t groupPolicy; + uint32_t vni; +}; + +struct tinc_cache_entry { + uint32_t src_address; + uint32_t dst_address; + uint16_t dst_port; +}; +""" + +cc_ndpi_stuctures = """ + +#define NDPI_MAX_NUM_TLS_APPL_BLOCKS 8 + +typedef enum { + NDPI_LOG_ERROR, + NDPI_LOG_TRACE, + NDPI_LOG_DEBUG, + NDPI_LOG_DEBUG_EXTRA +} ndpi_log_level_t; + +typedef enum { + ndpi_l4_proto_unknown = 0, + ndpi_l4_proto_tcp_only, + ndpi_l4_proto_udp_only, + ndpi_l4_proto_tcp_and_udp, +} ndpi_l4_proto_info; + +typedef enum { + ndpi_no_tunnel = 0, + ndpi_gtp_tunnel, + ndpi_capwap_tunnel, + ndpi_tzsp_tunnel, + ndpi_l2tp_tunnel, +} ndpi_packet_tunnel; + +typedef enum { + NDPI_NO_RISK = 0, + NDPI_URL_POSSIBLE_XSS, + NDPI_URL_POSSIBLE_SQL_INJECTION, + NDPI_URL_POSSIBLE_RCE_INJECTION, + NDPI_BINARY_APPLICATION_TRANSFER, + NDPI_KNOWN_PROTOCOL_ON_NON_STANDARD_PORT, + NDPI_TLS_SELFSIGNED_CERTIFICATE, + NDPI_TLS_OBSOLETE_VERSION, + NDPI_TLS_WEAK_CIPHER, + NDPI_TLS_CERTIFICATE_EXPIRED, + NDPI_TLS_CERTIFICATE_MISMATCH, + NDPI_HTTP_SUSPICIOUS_USER_AGENT, + NDPI_HTTP_NUMERIC_IP_HOST, + NDPI_HTTP_SUSPICIOUS_URL, + NDPI_HTTP_SUSPICIOUS_HEADER, + NDPI_TLS_NOT_CARRYING_HTTPS, + NDPI_SUSPICIOUS_DGA_DOMAIN, + NDPI_MALFORMED_PACKET, + NDPI_SSH_OBSOLETE_CLIENT_VERSION_OR_CIPHER, + NDPI_SSH_OBSOLETE_SERVER_VERSION_OR_CIPHER, + NDPI_SMB_INSECURE_VERSION, + NDPI_TLS_SUSPICIOUS_ESNI_USAGE, + NDPI_UNSAFE_PROTOCOL, + NDPI_DNS_SUSPICIOUS_TRAFFIC, + NDPI_TLS_MISSING_SNI, + /* Leave this as last member */ + NDPI_MAX_RISK +} ndpi_risk_enum; + +typedef uint32_t ndpi_risk; + +/* NDPI_VISIT */ +typedef enum { + ndpi_preorder, + ndpi_postorder, + ndpi_endorder, + ndpi_leaf +} ndpi_VISIT; + +/* NDPI_NODE */ +typedef struct node_t { + char *key; + struct node_t *left, *right; +} ndpi_node; + +/* NDPI_MASK_SIZE */ +typedef uint32_t ndpi_ndpi_mask; + +/* NDPI_PROTO_BITMASK_STRUCT */ +typedef struct ndpi_protocol_bitmask_struct { + ndpi_ndpi_mask fds_bits[16]; +} NDPI_PROTOCOL_BITMASK; + +/* NDPI_PROTOCOL_BITTORRENT */ +typedef struct spinlock { + volatile int val; +} spinlock_t; + +typedef struct atomic { + volatile int counter; +} atomic_t; + +typedef long int time_t; + +struct hash_ip4p_node { + struct hash_ip4p_node *next, *prev; + time_t lchg; + uint16_t port,count:12,flag:4; + uint32_t ip; +}; + +struct hash_ip4p { + struct hash_ip4p_node *top; + spinlock_t lock; + size_t len; +}; + +struct hash_ip4p_table { + size_t size; + int ipv6; + spinlock_t lock; + atomic_t count; + struct hash_ip4p tbl; +}; + +struct bt_announce { // 192 bytes + uint32_t hash[5]; + uint32_t ip[4]; + uint32_t time; + uint16_t port; + uint8_t name_len, + name[149]; // 149 bytes +}; + +/* NDPI_PROTOCOL_TINC */ +#define TINC_CACHE_MAX_SIZE 10 + +typedef enum { + NDPI_HTTP_METHOD_UNKNOWN = 0, + NDPI_HTTP_METHOD_OPTIONS, + NDPI_HTTP_METHOD_GET, + NDPI_HTTP_METHOD_HEAD, + NDPI_HTTP_METHOD_PATCH, + NDPI_HTTP_METHOD_POST, + NDPI_HTTP_METHOD_PUT, + NDPI_HTTP_METHOD_DELETE, + NDPI_HTTP_METHOD_TRACE, + NDPI_HTTP_METHOD_CONNECT +} ndpi_http_method; + +struct ndpi_lru_cache_entry { + uint32_t key; /* Store the whole key to avoid ambiguities */ + uint32_t is_full:1, value:16, pad:15; +}; + +struct ndpi_lru_cache { + uint32_t num_entries; + struct ndpi_lru_cache_entry *entries; +}; + +typedef union +{ + uint32_t ipv4; + uint8_t ipv4_uint8_t[4]; + struct ndpi_in6_addr ipv6; +} ndpi_ip_addr_t; + +struct ndpi_id_struct { + /** + detected_protocol_bitmask: + access this bitmask to find out whether an id has used skype or not + if a flag is set here, it will not be reset + to compare this, use: + **/ + NDPI_PROTOCOL_BITMASK detected_protocol_bitmask; + /* NDPI_PROTOCOL_RTSP */ + ndpi_ip_addr_t rtsp_ip_address; + + /* NDPI_PROTOCOL_YAHOO */ + uint32_t yahoo_video_lan_timer; + + /* NDPI_PROTOCOL_IRC_MAXPORT % 2 must be 0 */ + /* NDPI_PROTOCOL_IRC */ +#define NDPI_PROTOCOL_IRC_MAXPORT 8 + uint16_t irc_port[NDPI_PROTOCOL_IRC_MAXPORT]; + uint32_t last_time_port_used[NDPI_PROTOCOL_IRC_MAXPORT]; + uint32_t irc_ts; + + /* NDPI_PROTOCOL_GNUTELLA */ + uint32_t gnutella_ts; + + /* NDPI_PROTOCOL_THUNDER */ + uint32_t thunder_ts; + + /* NDPI_PROTOCOL_RTSP */ + uint32_t rtsp_timer; + + /* NDPI_PROTOCOL_ZATTOO */ + uint32_t zattoo_ts; + + /* NDPI_PROTOCOL_UNENCRYPTED_JABBER */ + uint32_t jabber_stun_or_ft_ts; + + /* NDPI_PROTOCOL_DIRECTCONNECT */ + uint32_t directconnect_last_safe_access_time; + + /* NDPI_PROTOCOL_SOULSEEK */ + uint32_t soulseek_last_safe_access_time; + + /* NDPI_PROTOCOL_DIRECTCONNECT */ + uint16_t detected_directconnect_port; + uint16_t detected_directconnect_udp_port; + uint16_t detected_directconnect_ssl_port; + + /* NDPI_PROTOCOL_BITTORRENT */ +#define NDPI_BT_PORTS 8 + uint16_t bt_port_t[NDPI_BT_PORTS]; + uint16_t bt_port_u[NDPI_BT_PORTS]; + + /* NDPI_PROTOCOL_UNENCRYPTED_JABBER */ +#define JABBER_MAX_STUN_PORTS 6 + uint16_t jabber_voice_stun_port[JABBER_MAX_STUN_PORTS]; + uint16_t jabber_file_transfer_port[2]; + + /* NDPI_PROTOCOL_GNUTELLA */ + uint16_t detected_gnutella_port; + + /* NDPI_PROTOCOL_GNUTELLA */ + uint16_t detected_gnutella_udp_port1; + uint16_t detected_gnutella_udp_port2; + + /* NDPI_PROTOCOL_SOULSEEK */ + uint16_t soulseek_listen_port; + + /* NDPI_PROTOCOL_IRC */ + uint8_t irc_number_of_port; + + /* NDPI_PROTOCOL_UNENCRYPTED_JABBER */ + uint8_t jabber_voice_stun_used_ports; + + /* NDPI_PROTOCOL_SIP */ + /* NDPI_PROTOCOL_YAHOO */ + uint32_t yahoo_video_lan_dir:1; + + /* NDPI_PROTOCOL_YAHOO */ + uint32_t yahoo_conf_logged_in:1; + uint32_t yahoo_voice_conf_logged_in:1; + + /* NDPI_PROTOCOL_RTSP */ + uint32_t rtsp_ts_set:1; +}; + +struct ndpi_flow_tcp_struct { + /* NDPI_PROTOCOL_MAIL_SMTP */ + uint16_t smtp_command_bitmask; + + /* NDPI_PROTOCOL_MAIL_POP */ + uint16_t pop_command_bitmask; + + /* NDPI_PROTOCOL_QQ */ + uint16_t qq_nxt_len; + + /* NDPI_PROTOCOL_WHATSAPP */ + uint8_t wa_matched_so_far; + + /* NDPI_PROTOCOL_TDS */ + uint8_t tds_login_version; + + /* NDPI_PROTOCOL_IRC */ + uint8_t irc_stage; + uint8_t irc_port; + + /* NDPI_PROTOCOL_H323 */ + uint8_t h323_valid_packets; + + /* NDPI_PROTOCOL_GNUTELLA */ + uint8_t gnutella_msg_id[3]; + + /* NDPI_PROTOCOL_IRC */ + uint32_t irc_3a_counter:3; + uint32_t irc_stage2:5; + uint32_t irc_direction:2; + uint32_t irc_0x1000_full:1; + + /* NDPI_PROTOCOL_SOULSEEK */ + uint32_t soulseek_stage:2; + + /* NDPI_PROTOCOL_TDS */ + uint32_t tds_stage:3; + + /* NDPI_PROTOCOL_USENET */ + uint32_t usenet_stage:2; + + /* NDPI_PROTOCOL_IMESH */ + uint32_t imesh_stage:4; + + /* NDPI_PROTOCOL_HTTP */ + uint32_t http_setup_dir:2; + uint32_t http_stage:2; + uint32_t http_empty_line_seen:1; + uint32_t http_wait_for_retransmission:1; + + /* NDPI_PROTOCOL_GNUTELLA */ + uint32_t gnutella_stage:2; // 0 - 2 + + /* NDPI_CONTENT_MMS */ + uint32_t mms_stage:2; + + /* NDPI_PROTOCOL_YAHOO */ + uint32_t yahoo_sip_comm:1; + uint32_t yahoo_http_proxy_stage:2; + + /* NDPI_PROTOCOL_MSN */ + uint32_t msn_stage:3; + uint32_t msn_ssl_ft:2; + + /* NDPI_PROTOCOL_SSH */ + uint32_t ssh_stage:3; + + /* NDPI_PROTOCOL_VNC */ + uint32_t vnc_stage:2; // 0 - 3 + + /* NDPI_PROTOCOL_TELNET */ + uint32_t telnet_stage:2; // 0 - 2 + + struct { + struct { + uint8_t *buffer; + unsigned buffer_len, buffer_used; + } message; + + void* srv_cert_fingerprint_ctx; /* SHA-1 */ + + /* NDPI_PROTOCOL_TLS */ + uint8_t hello_processed:1, certificate_processed:1, subprotocol_detected:1, fingerprint_set:1, _pad:4; + uint8_t sha1_certificate_fingerprint[20], num_tls_blocks; + int16_t tls_application_blocks_len[NDPI_MAX_NUM_TLS_APPL_BLOCKS]; + } tls; + + /* NDPI_PROTOCOL_POSTGRES */ + uint32_t postgres_stage:3; + + /* NDPI_PROTOCOL_DIRECT_DOWNLOAD_LINK */ + uint32_t ddlink_server_direction:1; + uint32_t seen_syn:1; + uint32_t seen_syn_ack:1; + uint32_t seen_ack:1; + + /* NDPI_PROTOCOL_ICECAST */ + uint32_t icecast_stage:1; + + /* NDPI_PROTOCOL_DOFUS */ + uint32_t dofus_stage:1; + + /* NDPI_PROTOCOL_FIESTA */ + uint32_t fiesta_stage:2; + + /* NDPI_PROTOCOL_WORLDOFWARCRAFT */ + uint32_t wow_stage:2; + + /* NDPI_PROTOCOL_HTTP_APPLICATION_VEOHTV */ + uint32_t veoh_tv_stage:2; + + /* NDPI_PROTOCOL_SHOUTCAST */ + uint32_t shoutcast_stage:2; + + /* NDPI_PROTOCOL_RTP */ + uint32_t rtp_special_packets_seen:1; + + /* NDPI_PROTOCOL_MAIL_POP */ + uint32_t mail_pop_stage:2; + + /* NDPI_PROTOCOL_MAIL_IMAP */ + uint32_t mail_imap_stage:3, mail_imap_starttls:2; + + /* NDPI_PROTOCOL_SKYPE */ + uint8_t skype_packet_id; + + /* NDPI_PROTOCOL_CITRIX */ + uint8_t citrix_packet_id; + + /* NDPI_PROTOCOL_LOTUS_NOTES */ + uint8_t lotus_notes_packet_id; + + /* NDPI_PROTOCOL_TEAMVIEWER */ + uint8_t teamviewer_stage; + + /* NDPI_PROTOCOL_ZMQ */ + uint8_t prev_zmq_pkt_len; + uint8_t prev_zmq_pkt[10]; + + /* NDPI_PROTOCOL_PPSTREAM */ + uint32_t ppstream_stage:3; + + /* NDPI_PROTOCOL_MEMCACHED */ + uint8_t memcached_matches; + + /* NDPI_PROTOCOL_NEST_LOG_SINK */ + uint8_t nest_log_sink_matches; +}; + +struct ndpi_flow_udp_struct { + /* NDPI_PROTOCOL_SNMP */ + uint32_t snmp_msg_id; + + /* NDPI_PROTOCOL_SNMP */ + uint32_t snmp_stage:2; + + /* NDPI_PROTOCOL_PPSTREAM */ + uint32_t ppstream_stage:3; // 0 - 7 + + /* NDPI_PROTOCOL_HALFLIFE2 */ + uint32_t halflife2_stage:2; // 0 - 2 + + /* NDPI_PROTOCOL_TFTP */ + uint32_t tftp_stage:1; + + /* NDPI_PROTOCOL_AIMINI */ + uint32_t aimini_stage:5; + + /* NDPI_PROTOCOL_XBOX */ + uint32_t xbox_stage:1; + + /* NDPI_PROTOCOL_WINDOWS_UPDATE */ + uint32_t wsus_stage:1; + + /* NDPI_PROTOCOL_SKYPE */ + uint8_t skype_packet_id; + + /* NDPI_PROTOCOL_TEAMVIEWER */ + uint8_t teamviewer_stage; + + /* NDPI_PROTOCOL_EAQ */ + uint8_t eaq_pkt_id; + uint32_t eaq_sequence; + + /* NDPI_PROTOCOL_RX */ + uint32_t rx_conn_epoch; + uint32_t rx_conn_id; + + /* NDPI_PROTOCOL_MEMCACHED */ + uint8_t memcached_matches; + + /* NDPI_PROTOCOL_WIREGUARD */ + uint8_t wireguard_stage; + uint32_t wireguard_peer_index[2]; +}; + +struct ndpi_int_one_line_struct { + const uint8_t *ptr; + uint16_t len; +}; + +struct ndpi_packet_struct { + const struct ndpi_iphdr *iph; + const struct ndpi_ipv6hdr *iphv6; + const struct ndpi_tcphdr *tcp; + const struct ndpi_udphdr *udp; + const uint8_t *generic_l4_ptr; /* is set only for non tcp-udp traffic */ + const uint8_t *payload; + + uint64_t current_time_ms; + + uint16_t detected_protocol_stack[2]; + uint8_t detected_subprotocol_stack[2]; + uint16_t protocol_stack_info; + + struct ndpi_int_one_line_struct line[64]; + /* HTTP headers */ + struct ndpi_int_one_line_struct host_line; + struct ndpi_int_one_line_struct forwarded_line; + struct ndpi_int_one_line_struct referer_line; + struct ndpi_int_one_line_struct content_line; + struct ndpi_int_one_line_struct content_disposition_line; + struct ndpi_int_one_line_struct accept_line; + struct ndpi_int_one_line_struct user_agent_line; + struct ndpi_int_one_line_struct http_url_name; + struct ndpi_int_one_line_struct http_encoding; + struct ndpi_int_one_line_struct http_transfer_encoding; + struct ndpi_int_one_line_struct http_contentlen; + struct ndpi_int_one_line_struct http_cookie; + struct ndpi_int_one_line_struct http_origin; + struct ndpi_int_one_line_struct http_x_session_type; + struct ndpi_int_one_line_struct server_line; + struct ndpi_int_one_line_struct http_method; + struct ndpi_int_one_line_struct http_response; + uint8_t http_num_headers; /* number of found (valid) header lines in HTTP request or response */ + + uint16_t l3_packet_len; + uint16_t l4_packet_len; + uint16_t payload_packet_len; + uint16_t actual_payload_len; + uint16_t num_retried_bytes; + uint16_t parsed_lines; + uint16_t parsed_unix_lines; + uint16_t empty_line_position; + uint8_t tcp_retransmission; + uint8_t l4_protocol; + + uint8_t tls_certificate_detected:4, tls_certificate_num_checks:4; + uint8_t packet_lines_parsed_complete:1, + packet_direction:1, empty_line_position_set:1, pad:5; +}; + +struct ndpi_detection_module_struct; +struct ndpi_flow_struct; + +struct ndpi_call_function_struct { + NDPI_PROTOCOL_BITMASK detection_bitmask; + NDPI_PROTOCOL_BITMASK excluded_protocol_bitmask; + uint32_t ndpi_selection_bitmask; + void (*func) (struct ndpi_detection_module_struct *, struct ndpi_flow_struct *flow); + uint8_t detection_feature; +}; + +struct ndpi_subprotocol_conf_struct { + void (*func) (struct ndpi_detection_module_struct *, char *attr, char *value, int protocol_id); +}; + +typedef struct { + uint16_t port_low, port_high; +} ndpi_port_range; + +typedef enum { + NDPI_PROTOCOL_SAFE = 0, /* Surely doesn't provide risks for the network. (e.g., a news site) */ + NDPI_PROTOCOL_ACCEPTABLE, /* Probably doesn't provide risks, but could be malicious (e.g., Dropbox) */ + NDPI_PROTOCOL_FUN, /* Pure fun protocol, which may be prohibited by the user policy */ + NDPI_PROTOCOL_UNSAFE, /* Probably provides risks, but could be a normal traffic. Unencrypted protocols + with clear pass should be here (e.g., telnet) */ + NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, /* Possibly dangerous (ex. Tor). */ + NDPI_PROTOCOL_DANGEROUS, /* Surely is dangerous (ex. smbv1). Be prepared to troubles */ + NDPI_PROTOCOL_TRACKER_ADS, /* Trackers, Advertisements... */ + NDPI_PROTOCOL_UNRATED /* No idea, not implemented or impossible to classify */ +} ndpi_protocol_breed_t; + +#define NUM_BREEDS 8 + +/* Abstract categories to group the protocols. */ +typedef enum { + NDPI_PROTOCOL_CATEGORY_UNSPECIFIED = 0, /* For general services and unknown protocols */ + NDPI_PROTOCOL_CATEGORY_MEDIA, /* Multimedia and streaming */ + NDPI_PROTOCOL_CATEGORY_VPN, /* Virtual Private Networks */ + NDPI_PROTOCOL_CATEGORY_MAIL, /* Protocols to send/receive/sync emails */ + NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, /* AFS/NFS and similar protocols */ + NDPI_PROTOCOL_CATEGORY_WEB, /* Web/mobile protocols and services */ + NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, /* Social networks */ + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, /* Download, FTP, file transfer/sharing */ + NDPI_PROTOCOL_CATEGORY_GAME, /* Online games */ + NDPI_PROTOCOL_CATEGORY_CHAT, /* Instant messaging */ + NDPI_PROTOCOL_CATEGORY_VOIP, /* Real-time communications and conferencing */ + NDPI_PROTOCOL_CATEGORY_DATABASE, /* Protocols for database communication */ + NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, /* Remote access and control */ + NDPI_PROTOCOL_CATEGORY_CLOUD, /* Online cloud services */ + NDPI_PROTOCOL_CATEGORY_NETWORK, /* Network infrastructure protocols */ + NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, /* Software for collaborative development, including Webmail */ + NDPI_PROTOCOL_CATEGORY_RPC, /* High level network communication protocols */ + NDPI_PROTOCOL_CATEGORY_STREAMING, /* Streaming protocols */ + NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, /* System/Operating System level applications */ + NDPI_PROTOCOL_CATEGORY_SW_UPDATE, /* Software update */ + + /* See #define NUM_CUSTOM_CATEGORIES */ + NDPI_PROTOCOL_CATEGORY_CUSTOM_1, /* User custom category 1 */ + NDPI_PROTOCOL_CATEGORY_CUSTOM_2, /* User custom category 2 */ + NDPI_PROTOCOL_CATEGORY_CUSTOM_3, /* User custom category 3 */ + NDPI_PROTOCOL_CATEGORY_CUSTOM_4, /* User custom category 4 */ + NDPI_PROTOCOL_CATEGORY_CUSTOM_5, /* User custom category 5 */ + + /* Further categories... */ + NDPI_PROTOCOL_CATEGORY_MUSIC, + NDPI_PROTOCOL_CATEGORY_VIDEO, + NDPI_PROTOCOL_CATEGORY_SHOPPING, + NDPI_PROTOCOL_CATEGORY_PRODUCTIVITY, + NDPI_PROTOCOL_CATEGORY_FILE_SHARING, + + /* + The category below is used by sites who are used + to test connectivity + */ + NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK, + + /* Some custom categories */ + CUSTOM_CATEGORY_MINING = 99, + CUSTOM_CATEGORY_MALWARE = 100, + CUSTOM_CATEGORY_ADVERTISEMENT = 101, + CUSTOM_CATEGORY_BANNED_SITE = 102, + CUSTOM_CATEGORY_SITE_UNAVAILABLE = 103, + CUSTOM_CATEGORY_ALLOWED_SITE = 104, + /* + The category below is used to track communications made by + security applications (e.g. sophosxl.net, spamhaus.org) + to track malware, spam etc. + */ + CUSTOM_CATEGORY_ANTIMALWARE = 105, + + /* + IMPORTANT + Please keep in sync with + static const char* categories[] = { ..} + in ndpi_main.c + */ + + NDPI_PROTOCOL_NUM_CATEGORIES + /* + NOTE: Keep this as last member + Unused as value but useful to getting the number of elements + in this datastructure + */ +} ndpi_protocol_category_t; + +typedef enum { + ndpi_pref_direction_detect_disable = 0, + ndpi_pref_enable_tls_block_dissection +} ndpi_detection_preference; + +/* ntop extensions */ +typedef struct ndpi_proto_defaults { + char *protoName; + ndpi_protocol_category_t protoCategory; + uint8_t can_have_a_subprotocol; + uint16_t protoId, protoIdx; + uint16_t master_tcp_protoId[2], master_udp_protoId[2]; /* The main protocols on which this sub-protocol sits on */ + uint16_t tcp_default_ports[5], udp_default_ports[5]; + ndpi_protocol_breed_t protoBreed; + void (*func) (struct ndpi_detection_module_struct *, struct ndpi_flow_struct *flow); +} ndpi_proto_defaults_t; + +typedef struct ndpi_default_ports_tree_node { + ndpi_proto_defaults_t *proto; + uint8_t customUserProto; + uint16_t default_port; +} ndpi_default_ports_tree_node_t; + +typedef struct _ndpi_automa { + void *ac_automa; /* Real type is AC_AUTOMATA_t */ + uint8_t ac_automa_finalized; +} ndpi_automa; + +typedef struct ndpi_proto { + /* + Note + below we do not use ndpi_protocol_id_t as users can define their own + custom protocols and thus the typedef could be too short in size. + */ + uint16_t master_protocol /* e.g. HTTP */, app_protocol /* e.g. FaceBook */; + ndpi_protocol_category_t category; +} ndpi_protocol; + +#define NUM_CUSTOM_CATEGORIES 5 +#define CUSTOM_CATEGORY_LABEL_LEN 32 + + +struct ndpi_detection_module_struct { + NDPI_PROTOCOL_BITMASK detection_bitmask; + NDPI_PROTOCOL_BITMASK generic_http_packet_bitmask; + + uint32_t current_ts; + + uint32_t ticks_per_second; + + uint16_t num_tls_blocks_to_follow; + + char custom_category_labels[NUM_CUSTOM_CATEGORIES][CUSTOM_CATEGORY_LABEL_LEN]; + /* callback function buffer */ + struct ndpi_call_function_struct callback_buffer[250]; + uint32_t callback_buffer_size; + + struct ndpi_call_function_struct callback_buffer_tcp_no_payload[250]; + uint32_t callback_buffer_size_tcp_no_payload; + + struct ndpi_call_function_struct callback_buffer_tcp_payload[250]; + uint32_t callback_buffer_size_tcp_payload; + + struct ndpi_call_function_struct callback_buffer_udp[250]; + uint32_t callback_buffer_size_udp; + + struct ndpi_call_function_struct callback_buffer_non_tcp_udp[250]; + uint32_t callback_buffer_size_non_tcp_udp; + + ndpi_default_ports_tree_node_t *tcpRoot, *udpRoot; + + ndpi_log_level_t ndpi_log_level; /* default error */ + + /* misc parameters */ + uint32_t tcp_max_retransmission_window_size; + + uint32_t directconnect_connection_ip_tick_timeout; + + /* subprotocol registration handler */ + struct ndpi_subprotocol_conf_struct subprotocol_conf[250]; + + unsigned ndpi_num_supported_protocols; + unsigned ndpi_num_custom_protocols; + + /* HTTP/DNS/HTTPS host matching */ + ndpi_automa host_automa, /* Used for DNS/HTTPS */ + content_automa, /* Used for HTTP subprotocol_detection */ + subprotocol_automa, /* Used for HTTP subprotocol_detection */ + bigrams_automa, impossible_bigrams_automa; /* TOR */ + /* IMPORTANT: please update ndpi_finalize_initalization() whenever you add a new automa */ + + struct { + ndpi_automa hostnames, hostnames_shadow; + void *ipAddresses, *ipAddresses_shadow; /* Patricia */ + uint8_t categories_loaded; + } custom_categories; + + /* IP-based protocol detection */ + void *protocols_ptree; + + /* irc parameters */ + uint32_t irc_timeout; + /* gnutella parameters */ + uint32_t gnutella_timeout; + /* thunder parameters */ + uint32_t thunder_timeout; + /* SoulSeek parameters */ + uint32_t soulseek_connection_ip_tick_timeout; + /* rtsp parameters */ + uint32_t rtsp_connection_timeout; + /* rstp */ + uint32_t orb_rstp_ts_timeout; + /* yahoo */ + uint8_t yahoo_detect_http_connections; + uint32_t yahoo_lan_video_timeout; + uint32_t zattoo_connection_timeout; + uint32_t jabber_stun_timeout; + uint32_t jabber_file_transfer_timeout; + uint8_t ip_version_limit; + /* NDPI_PROTOCOL_BITTORRENT */ + struct hash_ip4p_table *bt_ht; + struct hash_ip4p_table *bt6_ht; + /* BT_ANNOUNCE */ + struct bt_announce *bt_ann; + int bt_ann_len; + + /* NDPI_PROTOCOL_OOKLA */ + struct ndpi_lru_cache *ookla_cache; + + /* NDPI_PROTOCOL_TINC */ + struct cache *tinc_cache; + + /* NDPI_PROTOCOL_STUN and subprotocols */ + struct ndpi_lru_cache *stun_cache; + + /* NDPI_PROTOCOL_MSTEAMS */ + struct ndpi_lru_cache *msteams_cache; + + ndpi_proto_defaults_t proto_defaults[512]; + + uint8_t direction_detect_disable:1, /* disable internal detection of packet direction */ + _pad:7; +}; + +#define NDPI_CIPHER_SAFE 0 +#define NDPI_CIPHER_WEAK 1 +#define NDPI_CIPHER_INSECURE 2 + +typedef enum { + ndpi_cipher_safe = NDPI_CIPHER_SAFE, + ndpi_cipher_weak = NDPI_CIPHER_WEAK, + ndpi_cipher_insecure = NDPI_CIPHER_INSECURE +} ndpi_cipher_weakness; + +struct ndpi_flow_struct { + uint16_t detected_protocol_stack[2]; + uint16_t protocol_stack_info; + /* init parameter, internal used to set up timestamp,... */ + uint16_t guessed_protocol_id, guessed_host_protocol_id, guessed_category, guessed_header_category; + uint8_t l4_proto, protocol_id_already_guessed:1, host_already_guessed:1, + init_finished:1, setup_packet_direction:1, packet_direction:1, check_extra_packets:1; + /* + if ndpi_struct->direction_detect_disable == 1 + tcp sequence number connection tracking + */ + uint32_t next_tcp_seq_nr[2]; + uint8_t max_extra_packets_to_check; + uint8_t num_extra_packets_checked; + uint8_t num_processed_pkts; /* <= WARNING it can wrap but we do expect people to giveup earlier */ + + int (*extra_packets_func) (struct ndpi_detection_module_struct *, struct ndpi_flow_struct *flow); + /* + the tcp / udp / other l4 value union + used to reduce the number of bytes for tcp or udp protocol states + */ + union { + struct ndpi_flow_tcp_struct tcp; + struct ndpi_flow_udp_struct udp; + } l4; + + /* Place textual flow info here */ + char flow_extra_info[16]; + + /* + Pointer to src or dst that identifies the + server of this connection + */ + struct ndpi_id_struct *server_id; + /* HTTP host or DNS query */ + uint8_t host_server_name[240]; + uint8_t initial_binary_bytes[8], initial_binary_bytes_len; + uint8_t risk_checked; + ndpi_risk risk; /* Issues found with this flow [bitmask of ndpi_risk] */ + + /* + This structure below will not stay inside the protos + structure below as HTTP is used by many subprotocols + such as Facebook, Google... so it is hard to know + when to use it or not. Thus we leave it outside for the + time being. + */ + struct { + ndpi_http_method method; + char *url, *content_type, *user_agent; + uint8_t num_request_headers, num_response_headers; + uint8_t request_version; /* 0=1.0 and 1=1.1. Create an enum for this? */ + uint16_t response_status_code; /* 200, 404, etc. */ + } http; + + /* + Put outside of the union to avoid issues in case the protocol + is remapped to somethign pther than Kerberos due to a faulty + dissector + */ + struct { + char *pktbuf; + uint16_t pktbuf_maxlen, pktbuf_currlen; + } kerberos_buf; + union { + /* the only fields useful for nDPI and ntopng */ + struct { + uint8_t num_queries, num_answers, reply_code, is_query; + uint16_t query_type, query_class, rsp_type; + ndpi_ip_addr_t rsp_addr; /* The first address in a DNS response packet */ + } dns; + + struct { + uint8_t request_code; + uint8_t version; + } ntp; + + struct { + char hostname[48], domain[48], username[48]; + } kerberos; + + struct { + struct { + char ssl_version_str[12]; + uint16_t ssl_version, server_names_len; + char client_requested_server_name[64], *server_names, + *alpn, *tls_supported_versions, *issuerDN, *subjectDN; + uint32_t notBefore, notAfter; + char ja3_client[33], ja3_server[33]; + uint16_t server_cipher; + struct { + uint16_t cipher_suite; + char *esni; + } encrypted_sni; + ndpi_cipher_weakness server_unsafe_cipher; + } ssl; + + struct { + uint8_t num_udp_pkts, num_processed_pkts, num_binding_requests; + } stun; + + /* We can have STUN over SSL/TLS thus they need to live together */ + } stun_ssl; + + struct { + char client_signature[48], server_signature[48]; + char hassh_client[33], hassh_server[33]; + } ssh; + + struct { + uint8_t last_one_byte_pkt, last_byte; + } imo; + + struct { + uint8_t username_detected:1, username_found:1, + password_detected:1, password_found:1, + pad:4; + uint8_t character_id; + char username[32], password[32]; + } telnet; + + struct { + char answer[96]; + } mdns; + + struct { + char version[32]; + } ubntac2; + + struct { + /* Via HTTP User-Agent */ + uint8_t detected_os[32]; + /* Via HTTP X-Forwarded-For */ + uint8_t nat_ip[24]; + } http; + + struct { + uint8_t auth_found:1, auth_failed:1, _pad:5; + char username[16], password[16]; + } ftp_imap_pop_smtp; + + struct { + /* Bittorrent hash */ + uint8_t hash[20]; + } bittorrent; + + struct { + char fingerprint[48]; + char class_ident[48]; + } dhcp; + } protos; + + /*** ALL protocol specific 64 bit variables here ***/ + + /* protocols which have marked a connection as this connection cannot be protocol XXX, multiple uint64_t */ + NDPI_PROTOCOL_BITMASK excluded_protocol_bitmask; + + ndpi_protocol_category_t category; + + /* NDPI_PROTOCOL_REDIS */ + uint8_t redis_s2d_first_char, redis_d2s_first_char; + + uint16_t packet_counter; // can be 0 - 65000 + uint16_t packet_direction_counter[2]; + uint16_t byte_counter[2]; + /* NDPI_PROTOCOL_BITTORRENT */ + uint8_t bittorrent_stage; // can be 0 - 255 + + /* NDPI_PROTOCOL_DIRECTCONNECT */ + uint8_t directconnect_stage:2; // 0 - 1 + + /* NDPI_PROTOCOL_YAHOO */ + uint8_t sip_yahoo_voice:1; + + /* NDPI_PROTOCOL_HTTP */ + uint8_t http_detected:1; + + /* NDPI_PROTOCOL_RTSP */ + uint8_t rtsprdt_stage:2, rtsp_control_flow:1; + + /* NDPI_PROTOCOL_YAHOO */ + uint8_t yahoo_detection_finished:2; + + /* NDPI_PROTOCOL_ZATTOO */ + uint8_t zattoo_stage:3; + + /* NDPI_PROTOCOL_QQ */ + uint8_t qq_stage:3; + + /* NDPI_PROTOCOL_THUNDER */ + uint8_t thunder_stage:2; // 0 - 3 + + /* NDPI_PROTOCOL_FLORENSIA */ + uint8_t florensia_stage:1; + + /* NDPI_PROTOCOL_SOCKS */ + uint8_t socks5_stage:2, socks4_stage:2; // 0 - 3 + + /* NDPI_PROTOCOL_EDONKEY */ + uint8_t edonkey_stage:2; // 0 - 3 + + /* NDPI_PROTOCOL_FTP_CONTROL */ + uint8_t ftp_control_stage:2; + + /* NDPI_PROTOCOL_RTMP */ + uint8_t rtmp_stage:2; + + /* NDPI_PROTOCOL_PANDO */ + uint8_t pando_stage:3; + + /* NDPI_PROTOCOL_STEAM */ + uint16_t steam_stage:3, steam_stage1:3, steam_stage2:2, steam_stage3:2; + + /* NDPI_PROTOCOL_PPLIVE */ + uint8_t pplive_stage1:3, pplive_stage2:2, pplive_stage3:2; + + /* NDPI_PROTOCOL_STARCRAFT */ + uint8_t starcraft_udp_stage : 3; // 0-7 + + /* NDPI_PROTOCOL_OPENVPN */ + uint8_t ovpn_session_id[8]; + uint8_t ovpn_counter; + + /* NDPI_PROTOCOL_TINC */ + uint8_t tinc_state; + struct tinc_cache_entry tinc_cache_entry; + + /* NDPI_PROTOCOL_CSGO */ + uint8_t csgo_strid[18],csgo_state,csgo_s2; + uint32_t csgo_id2; + /* internal structures to save functions calls */ + struct ndpi_packet_struct packet; + struct ndpi_flow_struct *flow; + struct ndpi_id_struct *src; + struct ndpi_id_struct *dst; +}; + +typedef struct { + char *string_to_match, *proto_name; + int protocol_id; + ndpi_protocol_category_t protocol_category; + ndpi_protocol_breed_t protocol_breed; +} ndpi_protocol_match; + +typedef struct { + char *string_to_match; + ndpi_protocol_category_t protocol_category; +} ndpi_category_match; + +typedef struct { + uint32_t network; + uint8_t cidr; + uint8_t value; +} ndpi_network; + +typedef uint32_t ndpi_init_prefs; + +typedef enum { + ndpi_no_prefs = 0, + ndpi_dont_load_tor_hosts, +} ndpi_prefs; + +typedef struct { + int protocol_id; + ndpi_protocol_category_t protocol_category; + ndpi_protocol_breed_t protocol_breed; +} ndpi_protocol_match_result; + +typedef struct { + char *str; + uint16_t str_len; +} ndpi_string; + +/* **************************************** */ + +struct ndpi_analyze_struct { + uint32_t *values; + uint32_t min_val, max_val, sum_total, num_data_entries, next_value_insert_index; + uint16_t num_values_array_len /* lenght of the values array */; + + struct { + float mu, q; + } stddev; +}; + +#define DEFAULT_SERIES_LEN 64 +#define MAX_SERIES_LEN 512 +#define MIN_SERIES_LEN 8 + +typedef struct ndpi_ptree ndpi_ptree_t; + +""" + +cc_ndpi_apis = """ +struct ndpi_detection_module_struct *ndpi_init_detection_module(void); +void *memset(void *str, int c, size_t n); +void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *ndpi_struct, + const NDPI_PROTOCOL_BITMASK * detection_bitmask); +ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + const unsigned char *packet, + const unsigned short packetlen, + const uint64_t current_tick, + struct ndpi_id_struct *src, + struct ndpi_id_struct *dst); +ndpi_protocol ndpi_detection_giveup(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + uint8_t enable_guess, + uint8_t *protocol_was_guessed); + +void * ndpi_malloc(size_t size); +void ndpi_free(void *ptr); +void * ndpi_flow_malloc(size_t size); +void ndpi_flow_free(void *ptr); +void ndpi_exit_detection_module(struct ndpi_detection_module_struct *ndpi_struct); +char* ndpi_protocol2name(struct ndpi_detection_module_struct *ndpi_mod, + ndpi_protocol proto, + char *buf, unsigned buf_len); +const char* ndpi_category_get_name(struct ndpi_detection_module_struct *ndpi_mod, ndpi_protocol_category_t category); +char* ndpi_revision(void); +void ndpi_finalize_initalization(struct ndpi_detection_module_struct *ndpi_str); +uint32_t ndpi_detection_get_sizeof_ndpi_flow_struct(void); +uint32_t ndpi_detection_get_sizeof_ndpi_id_struct(void); +uint32_t ndpi_detection_get_sizeof_ndpi_flow_tcp_struct(void); +uint32_t ndpi_detection_get_sizeof_ndpi_flow_udp_struct(void); +uint8_t ndpi_extra_dissection_possible(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); +""" + + +def check_structures_size(flow_struct_defined, flow_struct_loaded, + id_struct_defined, id_struct_loaded, + tcp_flow_struct_defined, tcp_flow_struct_loaded, + udp_flow_struct_defined, udp_flow_struct_loaded): + """ Function used to check loaded structures sizes againt defined ones """ + errors = [] + if flow_struct_defined != flow_struct_loaded: + errors.append('ndpi_flow_struct') + if id_struct_defined != id_struct_loaded: + errors.append('ndpi_id_struct') + if tcp_flow_struct_defined != tcp_flow_struct_loaded: + errors.append('ndpi_tcp_flow_struct') + if udp_flow_struct_defined != udp_flow_struct_loaded: + errors.append('ndpi_udp_flow_struct') + return errors + + +class NDPI(): + """ ndpi module main class """ + + def __init__(self, libpath=None, max_tcp_dissections=80, max_udp_dissections=16, enable_guess=True): + self._ffi = cffi.FFI() + if libpath is None: + self._ndpi = self._ffi.dlopen(dirname(abspath(__file__)) + '/libndpi.so') + else: + self._ndpi = self._ffi.dlopen(libpath) + self._ffi.cdef(cc_ndpi_network_headers, packed=True) + self._ffi.cdef(cc_ndpi_stuctures) + self._ffi.cdef(cc_ndpi_apis) + self._mod = self._ndpi.ndpi_init_detection_module() + ndpi_revision = self._ffi.string(self._ndpi.ndpi_revision()).decode('utf-8', errors='ignore') + if ndpi_revision[:3] >= '3.1': + self._ndpi.ndpi_finalize_initalization(self._mod) + all = self._ffi.new('NDPI_PROTOCOL_BITMASK*') + self._ndpi.memset(self._ffi.cast("char *", all), 0xFF, self._ffi.sizeof("NDPI_PROTOCOL_BITMASK")) + self._ndpi.ndpi_set_protocol_detection_bitmask2(self._mod, all) + errors = check_structures_size(self._ffi.sizeof("struct ndpi_flow_struct"), + self._ndpi.ndpi_detection_get_sizeof_ndpi_flow_struct(), + self._ffi.sizeof("struct ndpi_id_struct"), + self._ndpi.ndpi_detection_get_sizeof_ndpi_id_struct(), + self._ffi.sizeof("struct ndpi_flow_tcp_struct"), + self._ndpi.ndpi_detection_get_sizeof_ndpi_flow_tcp_struct(), + self._ffi.sizeof("struct ndpi_flow_udp_struct"), + self._ndpi.ndpi_detection_get_sizeof_ndpi_flow_udp_struct()) + if len(errors) != 0: + raise ValueError('nDPI error: mismatch in the headers of following structures{}'.format(', '.join(errors))) + else: + self.SIZEOF_FLOW_STRUCT = self._ffi.sizeof("struct ndpi_flow_struct") + self.SIZEOF_ID_STRUCT = self._ffi.sizeof("struct ndpi_id_struct") + self.NULL = self._ffi.NULL + self.max_tcp_dissections = max_tcp_dissections + self.max_udp_dissections = max_udp_dissections + self.enable_guess = enable_guess + + def new_ndpi_flow(self): + """ Create a new nDPI flow object """ + f = self._ffi.cast('struct ndpi_flow_struct*', self._ndpi.ndpi_flow_malloc(self.SIZEOF_FLOW_STRUCT)) + self._ndpi.memset(f, 0, self.SIZEOF_FLOW_STRUCT) + return f + + def new_ndpi_id(self): + """ Create a new nDPI id object """ + i = self._ffi.cast('struct ndpi_id_struct*', self._ndpi.ndpi_malloc(self.SIZEOF_ID_STRUCT)) + self._ndpi.memset(i, 0, self.SIZEOF_ID_STRUCT) + return i + + def ndpi_detection_process_packet(self, flow, packet, packetlen, current_tick, src, dst): + """ Main detection processing function """ + p = self._ndpi.ndpi_detection_process_packet(self._mod, flow, packet, packetlen, current_tick, src, dst) + return p + + def ndpi_detection_giveup(self, flow): + """ Giveup detection function """ + return self._ndpi.ndpi_detection_giveup(self._mod, flow, self.enable_guess, self._ffi.new("uint8_t*", 0)) + + def ndpi_flow_free(self, flow): + """ Free nDPI flow object """ + return self._ndpi.ndpi_flow_free(flow) + + def ndpi_free(self, ptr): + """ Free nDPI object """ + return self._ndpi.ndpi_free(ptr) + + def get_str_field(self, ptr): + """ Get fixed string size attribute """ + if ptr == self._ffi.NULL: + return '' + else: + return self._ffi.string(ptr).decode('utf-8', errors='ignore') + + def get_buffer_field(self, ptr, li): + """ Get variable string size attribute """ + if ptr == self._ffi.NULL: + return '' + else: + return self._ffi.string(ptr, li).decode('utf-8', errors='ignore') + + def ndpi_protocol2name(self, proto): + """ Convert nDPI protocol object to readable name """ + buf = self._ffi.new("char[32]") + self._ndpi.ndpi_protocol2name(self._mod, proto, buf, self._ffi.sizeof(buf)) + return self._ffi.string(buf).decode('utf-8', errors='ignore') + + def ndpi_category_get_name(self, category): + """ Convert nDPI protocol object to readable name """ + return self._ffi.string(self._ndpi.ndpi_category_get_name(self._mod, category)).decode('utf-8', errors='ignore') + + def ndpi_extra_dissection_possible(self, flow): + return self._ndpi.ndpi_extra_dissection_possible(self._mod, flow) + + def ndpi_exit_detection_module(self): + """ Exit function for nDPI module """ + self._ndpi.ndpi_exit_detection_module(self._mod) + self._ffi.dlclose(self._ndpi) diff --git a/python/ndpi_example.py b/python/ndpi_example.py index afbba43..d134d39 100755 --- a/python/ndpi_example.py +++ b/python/ndpi_example.py @@ -1,43 +1,44 @@ #!/usr/bin/env python3 -# -# ndpi_example.py -# -# Copyright (C) 2011-19 - ntop.org -# -# nDPI is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# nDPI is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with nDPI. If not, see . -# +# -*- coding: utf-8 -*- + +""" +file: ndpi_example.py +This file is part of nDPI. + +Copyright (C) 2011-19 - ntop.org +Copyright (C) 2019 - Zied Aouini (Incremental improvements) + +nDPI is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +nDPI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with nDPI. +If not, see . +""" from ndpi_typestruct import * +from ctypes import * from scapy.all import * - +import sys # ------- return type & pcapstruct to declare ------- -class workflow(Structure): +class WorkFlow(Structure): _fields_ = [("src_ip", c_uint32), ("dst_ip", c_uint32), ("src_port", c_uint16), ("dst_port", c_uint16), ("protocol", c_uint8), ("packets", c_uint32), - ("detected_protocol", ndpi_protocol), + ("detected_protocol", NDPIProtocol), ("detection_completed", c_uint8), ("id", c_uint32), - ("src_id", POINTER(ndpi_id_struct)), - ("dst_id", POINTER(ndpi_id_struct)), - ("flow", POINTER(ndpi_flow_struct))] + ("src_id", POINTER(NDPIIdStruct)), + ("dst_id", POINTER(NDPIIdStruct)), + ("flow", POINTER(NDPIFlowStruct))] CMCFUN = CFUNCTYPE(c_int, c_void_p, c_void_p) @@ -48,17 +49,19 @@ class workflow(Structure): def node_proto_guess_walker(nodo, which, depth, user_data): global ndpi_info_mod - flow = cast(nodo, POINTER(POINTER(workflow))).contents.contents - if which == 0 or which == 3: #execute only preorder operation of the tree + flow = cast(nodo, POINTER(POINTER(WorkFlow))).contents.contents + if which == 0 or which == 3: # execute only preorder operation of the tree if flow.detection_completed == 0: # order for tree operation - flow.detected_protocol = ndpi.ndpi_detection_giveup(ndpi_info_mod, flow.flow, flow.protocol, - int(socket.ntohl(flow.src_ip)), 1) + flow.detected_protocol = ndpi.ndpi_detection_giveup(ndpi_info_mod, + flow.flow, + 1, + cast(addressof(c_uint8(0)), POINTER(c_uint8))) count_protocol[flow.detected_protocol.app_protocol] += flow.packets def py_cmp_fun(a, b): - fa = cast(a, POINTER(workflow)) - fb = cast(b, POINTER(workflow)) + fa = cast(a, POINTER(WorkFlow)) + fb = cast(b, POINTER(WorkFlow)) if fa.contents.src_ip < fb.contents.src_ip: return -1 elif fa.contents.src_ip > fb.contents.src_ip: return 1 @@ -83,7 +86,7 @@ def freer(a): # -------------------------------------- -# number of anylized packet +# number of analyzed packet packet_number = 0 flow_count = 0 max_num_udp_dissected_pkts = 16 @@ -103,6 +106,8 @@ def freer(a): ndpi_info_mod = ndpi.ndpi_init_detection_module() if ndpi_info_mod is None: sys.exit(-1) +else: + ndpi_ndpi_finalize_initalization(ndpi_info_mod) def ip2int(ip): @@ -113,30 +118,34 @@ def ip2int(ip): return int(struct.unpack("!I", packedIP)[0]) -def get_flow(packet): +def get_flow(pkt): global flows_root global flows_root_ref global flow_count - ip_packet = packet[1] + ip_packet = pkt[1] ip_protocol = ip_packet.proto transport_packet = None - if ip_protocol == 6 or ip_protocol == 17: transport_packet = packet[2] + if ip_protocol == 6 or ip_protocol == 17: + transport_packet = pkt[2] if transport_packet is not None: # to avoid two nodes in one binary tree for a flow ip_src = ip2int(ip_packet.src) ip_dst = ip2int(ip_packet.dst) src_port = transport_packet.sport dst_port = transport_packet.dport - else: return None + else: + return None # set flows to correct type and data - ndpi_flow = pointer(ndpi_flow_struct()) - memset(ndpi_flow, 0, sizeof(ndpi_flow_struct)) + ndpi_flow = pointer(NDPIFlowStruct()) + memset(ndpi_flow, 0, sizeof(NDPIFlowStruct)) if ip_src > ip_dst: - flow = workflow(ip_src, ip_dst, src_port, dst_port, int(ip_packet.proto), 0, ndpi_protocol(), 0, 0, pointer(ndpi_id_struct()), pointer(ndpi_id_struct()), ndpi_flow) + flow = WorkFlow(ip_src, ip_dst, src_port, dst_port, int(ip_packet.proto), 0, NDPIProtocol(), 0, 0, + pointer(NDPIIdStruct()), pointer(NDPIIdStruct()), ndpi_flow) else: - flow = workflow(ip_dst, ip_src, dst_port, src_port, int(ip_packet.proto), 0, ndpi_protocol(), 0, 0, pointer(ndpi_id_struct()), pointer(ndpi_id_struct()), ndpi_flow) + flow = WorkFlow(ip_dst, ip_src, dst_port, src_port, int(ip_packet.proto), 0, NDPIProtocol(), 0, 0, + pointer(NDPIIdStruct()), pointer(NDPIIdStruct()), ndpi_flow) flow_ref = pointer(flow) res = ndpi.ndpi_tfind(flow_ref, flows_root_ref, cmp_func) if res is None: @@ -144,41 +153,45 @@ def get_flow(packet): lista.append(flow) flow_count += 1 return pointer(flow) - flow = cast(res, POINTER(POINTER(workflow))).contents + flow = cast(res, POINTER(POINTER(WorkFlow))).contents return flow -def packetcaptured(packet): +def packetcaptured(pkt): global packet_number - global start_time global ndpi_info_mod flow = None - h = pcap_pkthdr() + h = PcapPktHdr() - #getting flow + # getting flow try: - flow = get_flow(packet) + flow = get_flow(pkt) except AttributeError: - pass # ignore packet + pass # ignore packet if flow is None: return - #filling pcap_pkthdr - h.len = h.caplen = len(packet) - h.ts.tv_sec = int(packet["IP"].time/1000000) - h.ts.tv_usec = int(packet["IP"].time) + # filling pcap_pkthdr + h.len = h.caplen = len(pkt) + h.ts.tv_sec = int(pkt["IP"].time/1000000) + h.ts.tv_usec = int(pkt["IP"].time) # real work - if int(packet[1].frag) == 0: # not fragmented packet + if int(pkt[1].frag) == 0: # not fragmented packet flow.contents.packets += 1 packet_number += 1 # get ndpi_iphdr address - iphdr_addr = cast(c_char_p(packet[1].build()), c_void_p) + iphdr_addr = cast(c_char_p(pkt[1].build()), c_void_p) ndpi_flow = flow.contents.flow if flow.contents.detection_completed is 0: - flow.contents.detected_protocol = ndpi.ndpi_detection_process_packet(ndpi_info_mod, ndpi_flow, cast(iphdr_addr, POINTER(c_uint8)), - int(packet[1].len), h.ts.tv_usec, flow.contents.src_id, flow.contents.dst_id) + flow.contents.detected_protocol = ndpi.ndpi_detection_process_packet(ndpi_info_mod, + ndpi_flow, + cast(iphdr_addr, POINTER(c_uint8)), + int(pkt[1].len), + h.ts.tv_usec, + flow.contents.src_id, + flow.contents.dst_id) flow1 = flow.contents.detected_protocol @@ -187,27 +200,32 @@ def packetcaptured(packet): if flow.contents.protocol == 6: valid = flow.contents.packets > max_num_tcp_dissected_pkts elif flow.contents.protocol == 17: valid = flow.contents.packets > max_num_udp_dissected_pkts - #should we continue anylizing packet or not? + # should we continue anylizing packet or not? if valid or flow1.app_protocol is not 0: - if valid or flow1.master_protocol is not 91: #or # 91 is NDPI_PROTOCOL_TLS - flow.contents.detection_completed = 1 #protocol found + if valid or flow1.master_protocol is not 91: # or # 91 is NDPI_PROTOCOL_TLS + flow.contents.detection_completed = 1 # protocol found if flow1.app_protocol is 0: - flow.contents.detected_protocol = ndpi.ndpi_detection_giveup(ndpi_info_mod, ndpi_flow, 1) - + flow.contents.detected_protocol = ndpi.ndpi_detection_giveup(ndpi_info_mod, + ndpi_flow, + 1, + cast(addressof(c_uint8(0)), + POINTER(c_uint8))) def result(): global flows_root_ref global ndpi_info_mod - print('\nnumber of anylized packet ' + str(packet_number)) - print('number of flows ' + str(flow_count)) + print('\nnumber of analyzed packet: ' + str(packet_number)) + print('number of flows: ' + str(flow_count)) ndpi.ndpi_twalk(flows_root_ref.contents, guess_walker, None) print('\nDetected protocols:') for i in range(0, ndpi.ndpi_get_num_supported_protocols(ndpi_info_mod)): if count_protocol[i] > 0: - print(cast(ndpi.ndpi_get_proto_name(ndpi_info_mod, i), c_char_p).value + ': '.encode('UTF-8') + str(count_protocol[i]).encode('UTF-8')) + print("{}: {} packets".format( + cast(ndpi.ndpi_get_proto_name(ndpi_info_mod, i), c_char_p).value.decode('utf-8'), + str(count_protocol[i]))) def free(ndpi_struct): @@ -216,16 +234,16 @@ def free(ndpi_struct): def initialize(ndpi_struct): - all = NDPI_PROTOCOL_BITMASK() + all = NDPIProtocolBitMask() ndpi.ndpi_wrap_NDPI_BITMASK_SET_ALL(pointer(all)) ndpi.ndpi_set_protocol_detection_bitmask2(ndpi_struct, pointer(all)) -print('Using nDPI ' + str(cast(ndpi.ndpi_revision(), c_char_p).value)) +print('Using nDPI ' + cast(ndpi.ndpi_revision(), c_char_p).value.decode("utf-8")) initialize(ndpi_info_mod) -if(len(sys.argv) != 2): +if len(sys.argv) != 2: print("\nUsage: ndpi_example.py ") sys.exit(0) @@ -247,6 +265,9 @@ def initialize(ndpi_struct): sniff(iface=sys.argv[1], prn=packetcaptured) except KeyboardInterrupt: print('\nInterrupted\n') + except PermissionError: + sys.exit('\nRoot privilege required for live capture on interface: {}\n'.format(sys.argv[1])) + result() free(ndpi_info_mod) diff --git a/python/ndpi_typestruct.py b/python/ndpi_typestruct.py index 4f8c8c2..889257d 100644 --- a/python/ndpi_typestruct.py +++ b/python/ndpi_typestruct.py @@ -1,343 +1,319 @@ -# -# ndpi_typestruct.h -# -# Copyright (C) 2019 - ntop.org -# -# nDPI is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# nDPI is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with nDPI. If not, see . -# - - -import os -from ctypes import * - -ndpi = CDLL('./ndpi_wrap.so') - -# NDPI_SELECTION_BITMASK_PROTOCOL_SIZE = c_uint32 -# ndpi_protocol_category_t, ndpi_protocol_breed_t e ndpi_log_level_t are enum and are imported as c_int - -class ndpi_detection_module_struct(Structure): +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +file: ndpi_typestruct.py +This file is part of nDPI. + +Copyright (C) 2011-19 - ntop.org +Copyright (C) 2019 - Zied Aouini (Incremental improvements) + +nDPI is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License +as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +nDPI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty +of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with nDPI. +If not, see . +""" + +from ctypes import CDLL, Structure, c_uint16, c_int, c_ulong, c_uint32, CFUNCTYPE, c_void_p, POINTER, c_char_p, c_uint8 +from ctypes import c_char, c_uint, c_int16, c_longlong, c_size_t, Union, c_ubyte, c_uint64, c_int32, c_ushort, cast +from os.path import abspath, dirname +ndpi = CDLL(dirname(abspath(__file__)) + '/ndpi_wrap.so') + +# ----------------------------------------------- Structures ----------------------------------------------------------- + + +class NDPIDetectionModuleStruct(Structure): pass -class ndpi_flow_struct(Structure): + +class NDPIFlowStruct(Structure): pass -class ndpi_protocol(Structure): + +class NDPIProtocol(Structure): _fields_ = [ ("master_protocol", c_uint16), ("app_protocol", c_uint16), ("category", c_int) - ] -class timeval(Structure): + +class TimeVal(Structure): _fields_ = [("tv_sec", c_ulong), ("tv_usec", c_ulong)] -class pcap_pkthdr(Structure): - _fields_ = [("ts", timeval), ("caplen", c_uint32), ("len", c_uint32)] -#dal file ../src/include/ndpi_tydedefs.h -class ndpi_ndpi_mask(Structure): +class PcapPktHdr(Structure): + _fields_ = [("ts", TimeVal), ("caplen", c_uint32), ("len", c_uint32)] + + +class NDPIMask(Structure): _fields_ = [("fds_bits", c_uint32)] -class NDPI_PROTOCOL_BITMASK(Structure): - _fields_ = [("fds_bits", ndpi_ndpi_mask * ndpi.ndpi_wrap_ndpi_num_fds_bits())] -class ndpi_subprotocol_conf_struct(Structure): - _fields_ = [("func", CFUNCTYPE(c_void_p,POINTER(ndpi_detection_module_struct),c_char_p,c_char_p,c_int))] +class NDPIProtocolBitMask(Structure): + _fields_ = [("fds_bits", NDPIMask * ndpi.ndpi_wrap_ndpi_num_fds_bits())] + + +class NDPISubprotocolConfStruct(Structure): + _fields_ = [("func", CFUNCTYPE(c_void_p, POINTER(NDPIDetectionModuleStruct), c_char_p, c_char_p, c_int))] + -class ndpi_automa(Structure): +class NDPIAutoma(Structure): _fields_ = [ - ("ac_automa", c_void_p), #Real type is AC_AUTOMATA_t + ("ac_automa", c_void_p), ("ac_automa_finalized", c_uint8) ] -class struct_node_t(Structure): + +class NDPINode(Structure): pass -struct_node_t._fields_ = [ + + +NDPINode._fields_ = [ ('key', POINTER(c_char)), - ('left', POINTER(struct_node_t)), - ('right', POINTER(struct_node_t)), + ('left', POINTER(NDPINode)), + ('right', POINTER(NDPINode)), ] -class ndpi_call_function_struct(Structure): + +class NDPICallFunctionStruct(Structure): _fields_ = [ - ("detection_bitmask", NDPI_PROTOCOL_BITMASK), - ("excluded_protocol_bitmask",NDPI_PROTOCOL_BITMASK), + ("detection_bitmask", NDPIProtocolBitMask), + ("excluded_protocol_bitmask", NDPIProtocolBitMask), ("ndpi_selection_bitmask", c_uint32), - ("func", CFUNCTYPE(None, POINTER(ndpi_detection_module_struct), POINTER(ndpi_flow_struct))), + ("func", CFUNCTYPE(None, POINTER(NDPIDetectionModuleStruct), POINTER(NDPIFlowStruct))), ("detection_feature", c_uint8) ] -class ndpi_proto_defaults_t(Structure): + +class NDPIProtoDefaultsT(Structure): _fields_ = [ - ("protoName", POINTER(c_char)), - ("protoCategory",c_uint), + ("protoName", c_char_p), + ("protoCategory", c_uint), ("can_have_a_subprotocol", c_uint8), ("protoId", c_uint16), ("protoIdx", c_uint16), ("master_tcp_protoId", c_uint16 * 2), ("master_udp_protoId", c_uint16 * 2), ("protoBreed", c_uint), - ("func", CFUNCTYPE(None, POINTER(ndpi_detection_module_struct), POINTER(ndpi_flow_struct))), + ("func", CFUNCTYPE(None, POINTER(NDPIDetectionModuleStruct), POINTER(NDPIFlowStruct))), ] -class ndpi_default_ports_tree_node_t(Structure): + +class NDPIDefaultsPortsTreeNodeT(Structure): _fields_ = [ - ("proto", ndpi_proto_defaults_t), - ("customUserProto",c_uint8), + ("proto", NDPIProtoDefaultsT), + ("customUserProto", c_uint8), ("default_port", c_int16) ] -# NDPI_PROTOCOL_BITTORRENT -class spinlock_t(Structure): - _fields_ = [("val", c_int)] #missing volatile -class atomic_t(Structure): - _fields_ = [("counter", c_int)] #missing volatile +class SpinlockT(Structure): + _fields_ = [("val", c_int)] + + +class AtomicT(Structure): + _fields_ = [("counter", c_int)] + -class time_t(Structure): - _fields_ = [("counter", c_longlong)] # piattaform dependent +class TimeT(Structure): + _fields_ = [("counter", c_longlong)] -class hash_ip4p_node(Structure): + +class HashIp4pNode(Structure): pass -hash_ip4p_node._fields_ = [ - ("next", POINTER(hash_ip4p_node)), - ("prev", POINTER(hash_ip4p_node)), - ("lchg", time_t), + +HashIp4pNode._fields_ = [ + ("next", POINTER(HashIp4pNode)), + ("prev", POINTER(HashIp4pNode)), + ("lchg", TimeT), ("port", c_uint16), ("count", c_uint16, 12), ("flag", c_uint16, 4), ("ip", c_uint32) ] -class hash_ip4p(Structure): + +class HashIp4p(Structure): _fields_ = [ - ("top", POINTER(hash_ip4p_node)), - ("lock",spinlock_t), + ("top", POINTER(HashIp4pNode)), + ("lock",SpinlockT), ("len", c_size_t) ] -class hash_ip4p_table(Structure): + +class HashIp4pTable(Structure): _fields_ = [ ("size", c_size_t), - ("ipv6",c_int), - ("lock", spinlock_t), - ("count", atomic_t), - ("tbl", hash_ip4p) + ("ipv6", c_int), + ("lock", SpinlockT), + ("count", AtomicT), + ("tbl", HashIp4p) ] -class bt_announce(Structure): # 192 bytes + +class BtAnnounce(Structure): _fields_ = [ ("hash", c_uint32 * 5), ("ip", c_uint32 * 4), ("time", c_uint32), ("port", c_uint16), ("name_len", c_uint8), - ("name", c_uint8 * 149) # 149 bytes + ("name", c_uint8 * 149) + ] + + +class NDPILruCacheEntry(Structure): + _fields_ = [ + ("key", c_uint32), + ("is_full", c_uint32, 1), + ("value", c_uint32, 16), + ("pad", c_uint32, 15) ] -class ndpi_lru_cache(Structure): # 192 bytes + +class NDPILruCache(Structure): _fields_ = [ ("num_entries", c_uint32), - ("entries", POINTER(c_uint32)), + ("entries", POINTER(NDPILruCacheEntry)), ] -class cache_entry(Structure): + +class CacheEntry(Structure): pass -cache_entry._fields_ = [ + +CacheEntry._fields_ = [ ("item", c_void_p), ("item_size", c_uint32), - ("prev", POINTER(cache_entry)), - ("next", POINTER(cache_entry)) + ("prev", POINTER(CacheEntry)), + ("next", POINTER(CacheEntry)) ] -class cache_entry_map(Structure): + +class CacheEntryMap(Structure): pass -cache_entry_map._fields_ = [ - ("entry", POINTER(cache_entry)), - ("next", POINTER(cache_entry_map)), + +CacheEntryMap._fields_ = [ + ("entry", POINTER(CacheEntry)), + ("next", POINTER(CacheEntryMap)), ] -class cache(Structure): # 192 bytes + +class Cache(Structure): _fields_ = [ ("size", c_uint32), ("max_size", c_uint32), - ("head", POINTER(cache_entry)), - ("tail", POINTER(cache_entry)), - ("map", POINTER(POINTER(cache_entry_map))) - ] - -class custom_categories(Structure): - _fields_ =[ - #Hyperscam - #("hostnames", POINTER(hs)), - #("num_to_load", c_uint), - #("to_load", POINTER(hs_list)), - ("hostnames", ndpi_automa), - ("hostnames_shadow", ndpi_automa), - ("hostnames_hash", c_void_p), - ("ipAddresses", c_void_p), - ("ipAddresses_shadow", c_void_p), # Patricia - ("categories_loaded", c_uint8), + ("head", POINTER(CacheEntry)), + ("tail", POINTER(CacheEntry)), + ("map", POINTER(POINTER(CacheEntryMap))) ] -ndpi_detection_module_struct._fields_ = [ - ("detection_bitmask", NDPI_PROTOCOL_BITMASK), - ("generic_http_packet_bitmask", NDPI_PROTOCOL_BITMASK), - - ("current_ts", c_uint32), - - ("ticks_per_second", c_uint32), - - #("user_data", c_void_p), debug +class CustomCategories(Structure): + _fields_ = [ + ("hostnames", NDPIAutoma), + ("hostnames_shadow", NDPIAutoma), + ("ipAddresses", c_void_p), + ("ipAddresses_shadow", c_void_p), + ("categories_loaded", c_uint8), + ] - ("custom_category_labels", (c_char * ndpi.ndpi_wrap_num_custom_categories()) * ndpi.ndpi_wrap_custom_category_label_len()), - #callback function buffer - ("callback_buffer", ndpi_call_function_struct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), +NDPIDetectionModuleStruct._fields_ = [ + ("detection_bitmask", NDPIProtocolBitMask), + ("generic_http_packet_bitmask", NDPIProtocolBitMask), + ("current_ts", c_uint32), + ("ticks_per_second", c_uint32), + ("custom_category_labels", + (c_char * ndpi.ndpi_wrap_num_custom_categories()) * ndpi.ndpi_wrap_custom_category_label_len()), + ("callback_buffer", NDPICallFunctionStruct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), ("callback_buffer_size", c_uint32), - - ("callback_buffer_tcp_no_payload", ndpi_call_function_struct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), + ("callback_buffer_tcp_no_payload", NDPICallFunctionStruct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), ("callback_buffer_size_tcp_no_payload", c_uint32), - - ("callback_buffer_tcp_payload", ndpi_call_function_struct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), + ("callback_buffer_tcp_payload", NDPICallFunctionStruct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), ("callback_buffer_size_tcp_payload", c_uint32), - - ("callback_buffer_udp", ndpi_call_function_struct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), + ("callback_buffer_udp", NDPICallFunctionStruct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), ("callback_buffer_size_udp", c_uint32), - - ("callback_buffer_non_tcp_udp", ndpi_call_function_struct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), + ("callback_buffer_non_tcp_udp", NDPICallFunctionStruct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), ("callback_buffer_size_non_tcp_udp", c_uint32), - - ("tcpRoot", POINTER(ndpi_default_ports_tree_node_t)), - ("udpRoot", POINTER(ndpi_default_ports_tree_node_t)), - - ("ndpi_log_level", c_uint), #default error - - # ifdef NDPI_ENABLE_DEBUG_MESSAGES - #debug callback, only set whendebug is used * / - #ndpi_debug_function_ptr ndpi_debug_printf; - #const char * ndpi_debug_print_file; - #const char * ndpi_debug_print_function; - #u_int32_t ndpi_debug_print_line; - #NDPI_PROTOCOL_BITMASK debug_bitmask; - # endif - - #misc parameters - ("tcp_max_retransmission_window_size", c_uint32), - - ("directconnect_connection_ip_tick_timeout", c_uint32), - - #subprotocol registration handler - ("subprotocol_conf", ndpi_subprotocol_conf_struct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), - - ("ndpi_num_supported_protocols", c_uint), + ("tcpRoot", POINTER(NDPIDefaultsPortsTreeNodeT)), + ("udpRoot", POINTER(NDPIDefaultsPortsTreeNodeT)), + ("ndpi_log_level", c_uint), + ("tcp_max_retransmission_window_size", c_uint32), + ("directconnect_connection_ip_tick_timeout", c_uint32), + ("subprotocol_conf", NDPISubprotocolConfStruct * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + 1)), + ("ndpi_num_supported_protocols", c_uint), ("ndpi_num_custom_protocols", c_uint), - - #HTTP / DNS / HTTPS host matching * / - ("host_automa", ndpi_automa), #Used for DNS / HTTPS - ("content_automa", ndpi_automa), # Used for HTTP subprotocol_detection - ("subprotocol_automa", ndpi_automa), # Used for HTTP subprotocol_detection - ("bigrams_automa", ndpi_automa), #TOR - ("impossible_bigrams_automa", ndpi_automa), # TOR - - ("custom_categories", custom_categories), - #IP-based protocol detection - ("protocols_ptree", c_void_p), - - #irc parameters - ("irc_timeout", c_uint32), - #gnutella parameters - ("gnutella_timeout", c_uint32), - #battlefield parameters - ("battlefield_timeout", c_uint32), - # thunder parameters - ("thunder_timeout", c_uint32), - # SoulSeek parameters - ("soulseek_connection_ip_tick_timeout", c_uint32), - # rtsp parameters - ("rtsp_connection_timeout", c_uint32), - # tvants parameters - ("tvants_connection_timeout", c_uint32), - # rstp - ("orb_rstp_ts_timeout", c_uint32), - # yahoo - ("yahoo_detect_http_connections", c_uint8), + ("host_automa", NDPIAutoma), + ("content_automa", NDPIAutoma), + ("subprotocol_automa", NDPIAutoma), + ("bigrams_automa", NDPIAutoma), + ("impossible_bigrams_automa", NDPIAutoma), + ("custom_categories", CustomCategories), + ("protocols_ptree", c_void_p), + ("irc_timeout", c_uint32), + ("gnutella_timeout", c_uint32), + ("battlefield_timeout", c_uint32), + ("thunder_timeout", c_uint32), + ("soulseek_connection_ip_tick_timeout", c_uint32), + ("rtsp_connection_timeout", c_uint32), + ("tvants_connection_timeout", c_uint32), + ("orb_rstp_ts_timeout", c_uint32), + ("yahoo_detect_http_connections", c_uint8), ("yahoo_lan_video_timeout", c_uint32), ("zattoo_connection_timeout", c_uint32), ("jabber_stun_timeout", c_uint32), ("jabber_file_transfer_timeout", c_uint32), - - # ifdef NDPI_ENABLE_DEBUG_MESSAGES - # define NDPI_IP_STRING_SIZE 40 - #char ip_string[NDPI_IP_STRING_SIZE]; - # endif - - ("ip_version_limit", c_uint8), - #NDPI_PROTOCOL_BITTORRENT - ("bt_ht", POINTER(hash_ip4p_table)), - # ifdef NDPI_DETECTION_SUPPORT_IPV6 - ("bt6_ht", POINTER(hash_ip4p_table)), - # endif - - # BT_ANNOUNCE - ("bt_ann", POINTER(bt_announce)), + ("ip_version_limit", c_uint8), + ("bt_ht", POINTER(HashIp4pTable)), + ("bt6_ht", POINTER(HashIp4pTable)), + ("bt_ann", POINTER(BtAnnounce)), ("bt_ann_len", c_int), - - # NDPI_PROTOCOL_OOKLA - ("ookla_cache", POINTER(ndpi_lru_cache)), - - # NDPI_PROTOCOL_TINC - ("tinc_cache", POINTER(cache)), - - ("proto_defaults", ndpi_proto_defaults_t * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + ndpi.ndpi_wrap_ndpi_max_num_custom_protocols())), - - ("http_dont_dissect_response", c_uint8, 1), + ("ookla_cache", POINTER(NDPILruCache)), + ("tinc_cache", POINTER(Cache)), + ("proto_defaults", NDPIProtoDefaultsT * (ndpi.ndpi_wrap_ndpi_max_supported_protocols() + + ndpi.ndpi_wrap_ndpi_max_num_custom_protocols())), + ("http_dont_dissect_response", c_uint8, 1), ("dns_dont_dissect_response", c_uint8, 1), - ("direction_detect_disable", c_uint8, 1), # disable internal detection of packet direction - ("disable_metadata_export", c_uint8, 1), # No metadata is exported - ("enable_category_substring_match", c_uint8, 1), # Default is perfect match - - ("hyperscan", c_void_p) # Intel Hyperscan + ("direction_detect_disable", c_uint8, 1), + ("disable_metadata_export", c_uint8, 1), + ("hyperscan", c_void_p) ] -class u6_addr(Union): # 128-bit IP6 address + +class U6Addr(Union): _fields_ = [ - ("u6_addr8",c_uint8 * 16), - ("u6_addr16",c_uint16 * 8), - ("u6_addr32",c_uint32 * 4) + ("u6_addr8", c_uint8 * 16), + ("u6_addr16", c_uint16 * 8), + ("u6_addr32", c_uint32 * 4), + ("u6_addr64", c_uint64 * 2) ] -class ndpi_in6_addr(Structure): - _fields_ = [("u6_addr", u6_addr)] + +class NDPIIn6Addr(Structure): + _pack_ = 1 + _fields_ = [("u6_addr", U6Addr)] -class ndpi_ip_addr_t(Union): +class NDPIIpAddrT(Union): _fields_ = [ ('ipv4', c_uint32), ('ipv4_u_int8_t', c_uint8 * 4), - ('ipv6', ndpi_in6_addr), + ('ipv6', NDPIIn6Addr), ] -class ndpi_id_struct(Structure): + +class NDPIIdStruct(Structure): _fields_ = [ - ('detected_protocol_bitmask', NDPI_PROTOCOL_BITMASK), - ('rtsp_ip_address', ndpi_ip_addr_t), + ('detected_protocol_bitmask', NDPIProtocolBitMask), + ('rtsp_ip_address', NDPIIpAddrT), ('yahoo_video_lan_timer', c_uint32), ('irc_port', c_uint16 * 8), ('last_time_port_used', c_uint32 * 8), @@ -371,10 +347,10 @@ class ndpi_id_struct(Structure): ('rtsp_ts_set', c_uint32, 1), ] -#struct flow -class ndpi_flow_tcp_struct(Structure): + +class NDPIFlowTcpStruct(Structure): + _pack_ = 1 _fields_ = [ - # NDPI_PROTOCOL_MAIL_SMTP ('smtp_command_bitmask', c_uint16), ('pop_command_bitmask', c_uint16), ('qq_nxt_len', c_uint16), @@ -405,10 +381,16 @@ class ndpi_flow_tcp_struct(Structure): ('ssh_stage', c_uint32, 3), ('vnc_stage', c_uint32, 2), ('telnet_stage', c_uint32, 2), - ('ssl_seen_client_cert', c_uint8, 1), - ('ssl_seen_server_cert', c_uint8, 1), - ('ssl_seen_certificate', c_uint8, 1), - ('ssl_stage', c_uint8, 2), + ('tls_srv_cert_fingerprint_ctx', c_void_p), + ('tls_seen_client_cert', c_uint8, 1), + ('tls_seen_server_cert', c_uint8, 1), + ('tls_seen_certificate', c_uint8, 1), + ('tls_srv_cert_fingerprint_found', c_uint8, 1), + ('tls_srv_cert_fingerprint_processed', c_uint8, 1), + ('tls_stage', c_uint8, 2), + ('tls_record_offset', c_int16), + ('tls_fingerprint_len', c_int16), + ('tls_sha1_certificate_fingerprint', c_uint8 * 20), ('postgres_stage', c_uint32, 3), ('ddlink_server_direction', c_uint32, 1), ('seen_syn', c_uint32, 1), @@ -429,13 +411,15 @@ class ndpi_flow_tcp_struct(Structure): ('lotus_notes_packet_id', c_uint8), ('teamviewer_stage', c_uint8), ('prev_zmq_pkt_len', c_uint8), - ('prev_zmq_pkt', c_ubyte * 10), + ('prev_zmq_pkt', c_char * 10), ('ppstream_stage', c_uint32, 3), ('memcached_matches', c_uint8), ('nest_log_sink_matches', c_uint8), ] -class ndpi_flow_udp_struct(Structure): + +class NDPIFlowUdpStruct(Structure): + _pack_ = 1 _fields_ = [ ('battlefield_msg_id', c_uint32), ('snmp_msg_id', c_uint32), @@ -454,103 +438,152 @@ class ndpi_flow_udp_struct(Structure): ('rx_conn_epoch', c_uint32), ('rx_conn_id', c_uint32), ('memcached_matches', c_uint8), + ('wireguard_stage', c_uint8), + ('wireguard_peer_index', c_uint32 * 2), ] -# the tcp / udp / other l4 value union used to reduce the number of bytes for tcp or udp protocol states -class l4(Union): - _fields_ = [("tcp", ndpi_flow_tcp_struct),("udp", ndpi_flow_udp_struct)] -class http(Structure): +class L4(Union): + _fields_ = [("tcp", NDPIFlowTcpStruct), ("udp", NDPIFlowUdpStruct)] + + +class Http(Structure): _fields_ = [ ("method", c_int), ("url", c_char_p), ("content_type", c_char_p), - ("num_request_headers", c_uint8), ("num_response_headers", c_uint8), - ("request_version", c_uint8), # 0=1.0 and 1=1.1. Create an enum for this? - ("response_status_code", c_uint16), # 200, 404, etc. + ("num_request_headers", c_uint8), + ("num_response_headers", c_uint8), + ("request_version", c_uint8), + ("response_status_code", c_uint16), ] -class dns(Structure): # the only fields useful for nDPI and ntopng + +class Dns(Structure): _fields_ = [ - ("num_queries", c_uint8), ("num_answers", c_uint8), ("reply_code", c_uint8), - ("query_type", c_uint16), ("query_class", c_uint16), ("rsp_type", c_uint16), - ("rsp_addr", ndpi_ip_addr_t) # The first address in a DNS response packet + ("num_queries", c_uint8), + ("num_answers", c_uint8), + ("reply_code", c_uint8), + ("is_query", c_uint8), + ("query_type", c_uint16), + ("query_class", c_uint16), + ("rsp_type", c_uint16), + ("rsp_addr", NDPIIpAddrT) ] -class ntp(Structure): - _fields_ = [("request_code", c_uint8), ("version", c_uint8)] -class ssl(Structure): +class Ntp(Structure): + _fields_ = [("request_code", c_uint8), + ("version", c_uint8)] + + +class Kerberos(Structure): + _fields_ = [("cname", c_char * 24), + ("realm", c_char * 24)] + + +class Ssl(Structure): _fields_ = [ - ("ssl_version", c_uint8), - ("client_certificate", c_char * 64), ("server_certificate", c_char * 64), ("server_organization", c_char * 64), - ("ja3_client", c_char * 33), ("ja3_server", c_char * 33), + ("ssl_version", c_uint16), + ("client_certificate", c_char * 64), + ("server_certificate", c_char * 64), + ("server_organization", c_char * 64), + ('notBefore', c_uint32), + ('notAfter', c_uint32), + ("ja3_client", c_char * 33), + ("ja3_server", c_char * 33), ("server_cipher", c_uint16), ("server_unsafe_cipher", c_int) ] -class stun(Structure): + +class Stun(Structure): _fields_ = [ ("num_udp_pkts", c_uint8), ("num_processed_pkts", c_uint8), ("num_binding_requests", c_uint8), - ("is_skype", c_uint8) ] -class stun_ssl(Union): # We can have STUN over SSL thus they need to live together - _fields_ = [("ssl", ssl),("stun",stun)] -class ssh(Structure): - _fields_ = [("client_signature", c_char * 48), ("server_signature", c_char * 48)] +class StunSsl(Structure): + _fields_ = [("ssl", Ssl), ("stun", Stun)] + + +class Ssh(Structure): + _fields_ = [ + ("client_signature", c_char * 48), + ("server_signature", c_char * 48), + ("hassh_client", c_char * 33), + ("hassh_server", c_char * 33) + ] -class mdns(Structure): + +class Imo(Structure): + _fields_ = [ + ("last_one_byte_pkt", c_uint8), + ("last_byte", c_uint8) + ] + + +class Mdns(Structure): _fields_ = [("answer", c_char * 96)] -class ubntac2(Structure): - _fields_ = [("version", c_char * 96)] -class http2(Structure): +class Ubntac2(Structure): + _fields_ = [("version", c_char * 32)] + + +class Http2(Structure): _fields_ = [ - ("detected_os", c_ubyte * 32), #Via HTTP User-Agent - ("nat_ip", c_ubyte * 24) + ("detected_os", c_char * 32), + ("nat_ip", c_char * 24) ] -class bittorrent(Structure): # Bittorrent hash - _fields_ = [ ("hash", c_ubyte * 20) ] -class dhcp(Structure): +class Bittorrent(Structure): + _fields_ = [("hash", c_char * 20)] + + +class Dhcp(Structure): _fields_ = [ ("fingerprint", c_char * 48), - ("nat_ip", c_char * 48) + ("class_ident", c_char * 48) ] -class protos(Union): + +class Protos(Union): _fields_ = [ - ("dns", dns), - ("ntp", ntp), - ("stun_ssl", stun_ssl), - ("ssh", ssh), - ("mdns", mdns), - ("ubntac2", ubntac2), - ("http", http2), - ("bittorrent", bittorrent), - ("dhcp", dhcp) + ("dns", Dns), + ("kerberos", Kerberos), + ("stun_ssl", StunSsl), + ("ssh", Ssh), + ("imo", Imo), + ("mdns", Mdns), + ("ubntac2", Ubntac2), + ("http", Http2), + ("bittorrent", Bittorrent), + ("dhcp", Dhcp) ] -class tinc_cache_entry(Structure): + +class TincCacheEntry(Structure): + _pack_ = 1 _fields_ = [ ('src_address', c_uint32), ('dst_address', c_uint32), ('dst_port', c_uint16), ] -class struct_ndpi_int_one_line_struct(Structure): + +class NDPIIntOneLineStruct(Structure): _fields_ = [ ('ptr', POINTER(c_uint8)), ('len', c_uint16), ] -class struct_ndpi_iphdr_little_end(Structure): + +class NDPIIphdr(Structure): + _pack_ = 1 _fields_ = [ ('ihl', c_uint8, 4), ('version', c_uint8, 4), @@ -564,7 +597,9 @@ class struct_ndpi_iphdr_little_end(Structure): ('saddr', c_uint32), ('daddr', c_uint32)] -class struct_ndpi_ip6_hdrctl(Structure): + +class NDPIIp6Hdrctl(Structure): + _pack_ = 1 _fields_ = [ ('ip6_un1_flow', c_uint32), ('ip6_un1_plen', c_uint16), @@ -572,14 +607,18 @@ class struct_ndpi_ip6_hdrctl(Structure): ('ip6_un1_hlim', c_uint8), ] -class struct_ndpi_ipv6hdr(Structure): + +class NDPIIpv6hdr(Structure): + _pack_ = 1 _fields_ = [ - ('ip6_hdr', struct_ndpi_ip6_hdrctl), - ('ip6_src', ndpi_in6_addr), - ('ip6_dst', ndpi_in6_addr), + ('ip6_hdr', NDPIIp6Hdrctl), + ('ip6_src', NDPIIn6Addr), + ('ip6_dst', NDPIIn6Addr), ] -class struct_ndpi_tcphdr(Structure): + +class NDPITcpHdr(Structure): + _pack_ = 1 _fields_ = [ ('source', c_uint16), ('dest', c_uint16), @@ -600,7 +639,9 @@ class struct_ndpi_tcphdr(Structure): ('urg_ptr', c_uint16), ] -class struct_ndpi_udphdr(Structure): + +class NDPIUdpHdr(Structure): + _pack_ = 1 _fields_ = [ ('source', c_uint16), ('dest', c_uint16), @@ -608,36 +649,44 @@ class struct_ndpi_udphdr(Structure): ('check', c_uint16), ] -class ndpi_packet_struct(Structure): + +class NDPIPacketStructStack(Structure): + _pack_ = 1 + _fields_ = [ + ('detected_subprotocol_stack', c_uint8 * ndpi.ndpi_wrap_ndpi_procol_size()), + ('protocol_stack_info', c_uint16) + ] + + +class NDPIPacketStruct(Structure): _fields_ = [ - ('iph', POINTER(struct_ndpi_iphdr_little_end)), - ('iphv6', POINTER(struct_ndpi_ipv6hdr)), - ('tcp', POINTER(struct_ndpi_tcphdr)), - ('udp', POINTER(struct_ndpi_udphdr)), + ('iph', POINTER(NDPIIphdr)), + ('iphv6', POINTER(NDPIIpv6hdr)), + ('tcp', POINTER(NDPITcpHdr)), + ('udp', POINTER(NDPIUdpHdr)), ('generic_l4_ptr', POINTER(c_uint8)), ('payload', POINTER(c_uint8)), ('tick_timestamp', c_uint32), ('tick_timestamp_l', c_uint64), - ('detected_protocol_stack', c_uint16 * 2), - ('detected_subprotocol_stack', c_uint8 * 2), - ('protocol_stack_info', c_uint16), - ('line', struct_ndpi_int_one_line_struct * 64), - ('host_line', struct_ndpi_int_one_line_struct), - ('forwarded_line', struct_ndpi_int_one_line_struct), - ('referer_line', struct_ndpi_int_one_line_struct), - ('content_line', struct_ndpi_int_one_line_struct), - ('accept_line', struct_ndpi_int_one_line_struct), - ('user_agent_line', struct_ndpi_int_one_line_struct), - ('http_url_name', struct_ndpi_int_one_line_struct), - ('http_encoding', struct_ndpi_int_one_line_struct), - ('http_transfer_encoding', struct_ndpi_int_one_line_struct), - ('http_contentlen', struct_ndpi_int_one_line_struct), - ('http_cookie', struct_ndpi_int_one_line_struct), - ('http_origin', struct_ndpi_int_one_line_struct), - ('http_x_session_type', struct_ndpi_int_one_line_struct), - ('server_line', struct_ndpi_int_one_line_struct), - ('http_method', struct_ndpi_int_one_line_struct), - ('http_response', struct_ndpi_int_one_line_struct), + ('detected_protocol_stack', c_uint16 * ndpi.ndpi_wrap_ndpi_procol_size()), + ('ndpi_packet_stack', NDPIPacketStructStack), + ('line', NDPIIntOneLineStruct * 64), + ('host_line', NDPIIntOneLineStruct), + ('forwarded_line', NDPIIntOneLineStruct), + ('referer_line', NDPIIntOneLineStruct), + ('content_line', NDPIIntOneLineStruct), + ('accept_line', NDPIIntOneLineStruct), + ('user_agent_line', NDPIIntOneLineStruct), + ('http_url_name', NDPIIntOneLineStruct), + ('http_encoding', NDPIIntOneLineStruct), + ('http_transfer_encoding', NDPIIntOneLineStruct), + ('http_contentlen', NDPIIntOneLineStruct), + ('http_cookie', NDPIIntOneLineStruct), + ('http_origin', NDPIIntOneLineStruct), + ('http_x_session_type', NDPIIntOneLineStruct), + ('server_line', NDPIIntOneLineStruct), + ('http_method', NDPIIntOneLineStruct), + ('http_response', NDPIIntOneLineStruct), ('http_num_headers', c_uint8), ('l3_packet_len', c_uint16), ('l4_packet_len', c_uint16), @@ -654,58 +703,43 @@ class ndpi_packet_struct(Structure): ('packet_lines_parsed_complete', c_uint8, 1), ('packet_direction', c_uint8, 1), ('empty_line_position_set', c_uint8, 1), + ('pad', c_uint8, 5), + ] + + +class NDPIFlowStructStack(Structure): + _pack_ = 1 + _fields_ = [ + ("detected_protocol_stack", c_uint16 * ndpi.ndpi_wrap_ndpi_procol_size()), + ("protocol_stack_info", c_uint16) ] -ndpi_flow_struct._fields_ = [ - ("detected_protocol_stack", c_uint16 * ndpi.ndpi_wrap_ndpi_procol_size()), - ("protocol_stack_info", c_uint16), - # init parameter, internal used to set up timestamp,... +NDPIFlowStruct._fields_ = [ + ("ndpi_flow_stack", NDPIFlowStructStack), ("guessed_protocol_id", c_uint16), ("guessed_host_protocol_id", c_uint16), ("guessed_category", c_uint16), ("guessed_header_category", c_uint16), + ("l4_proto", c_uint8), ("protocol_id_already_guessed", c_uint8, 1), ("host_already_guessed", c_uint8, 1), ("init_finished", c_uint8, 1), ("setup_packet_direction", c_uint8, 1), ("packet_direction", c_uint8, 1), ("check_extra_packets", c_uint8, 1), - - # if ndpi_struct->direction_detect_disable == 1 tcp sequence number connection tracking ("next_tcp_seq_nr", c_uint32 * 2), - ("max_extra_packets_to_check", c_uint8), ("num_extra_packets_checked", c_uint8), - ("num_processed_pkts", c_uint8), # <= WARNING it can wrap but we do expect people to giveup earlier - - ("extra_packets_func", CFUNCTYPE(c_int,POINTER(ndpi_detection_module_struct),POINTER(ndpi_flow_struct))), - - ("l4", l4), - - # Pointer to src or dst that identifies the server of this connection - ("server_id", ndpi_id_struct), - # HTTP host or DNS query + ("num_processed_pkts", c_uint8), + ("extra_packets_func", CFUNCTYPE(c_int, POINTER(NDPIDetectionModuleStruct), POINTER(NDPIFlowStruct))), + ("l4", L4), + ("server_id", POINTER(NDPIIdStruct)), ("host_server_name", c_ubyte * 256), - - - # This structure below will not not stay inside the protos - # structure below as HTTP is used by many subprotocols - # such as FaceBook, Google... so it is hard to know - # when to use it or not. Thus we leave it outside for the - # time being. - - - ("http", http), - ("protos", protos), - - # ALL protocol specific 64 bit variables here - - # protocols which have marked a connection as this connection cannot be protocol XXX, multiple u_int64_t - ("excluded_protocol_bitmask", NDPI_PROTOCOL_BITMASK), - + ("http", Http), + ("protos", Protos), + ("excluded_protocol_bitmask", NDPIProtocolBitMask), ("category", c_int), - ('redis_s2d_first_char', c_uint8), ('redis_d2s_first_char', c_uint8), ('packet_counter', c_uint16), @@ -742,32 +776,89 @@ class ndpi_packet_struct(Structure): ('ovpn_session_id', c_uint8 * 8), ('ovpn_counter', c_uint8), ('tinc_state', c_uint8), - ('tinc_cache_entry', tinc_cache_entry), + ('TincCacheEntry', TincCacheEntry), ('csgo_strid', c_uint8 * 18), ('csgo_state', c_uint8), ('csgo_s2', c_uint8), ('csgo_id2', c_uint32), ('kxun_counter', c_uint16), ('iqiyi_counter', c_uint16), - ('packet', ndpi_packet_struct), - ('flow', POINTER(ndpi_flow_struct)), - ('src', POINTER(ndpi_id_struct)), - ('dst', POINTER(ndpi_id_struct)) + ('packet', NDPIPacketStruct), + ('flow', POINTER(NDPIFlowStruct)), + ('src', POINTER(NDPIIdStruct)), + ('dst', POINTER(NDPIIdStruct)) ] - +# ----------------------------------------------- nDPI APIs ------------------------------------------------------------ + +""" ndpi_detection_giveup: Function to be called before we give up with detection for a given flow. + This function reduces the NDPI_UNKNOWN_PROTOCOL detection. """ +ndpi.ndpi_detection_giveup.restype = NDPIProtocol +ndpi.ndpi_detection_giveup.argtypes = [POINTER(NDPIDetectionModuleStruct), + POINTER(NDPIFlowStruct), c_uint8, + POINTER(c_uint8)] + +""" ndpi_detection_process_packet: Processes one packet and returns the ID of the detected protocol. + This is the MAIN PACKET PROCESSING FUNCTION. """ +ndpi.ndpi_detection_process_packet.restype = NDPIProtocol +ndpi.ndpi_detection_process_packet.argtypes = [POINTER(NDPIDetectionModuleStruct), + POINTER(NDPIFlowStruct), + POINTER(c_ubyte), + c_ushort, + c_uint64, + POINTER(NDPIIdStruct), + POINTER(NDPIIdStruct)] + +""" ndpi_ssl_version2str : Converts ssl version to readable string """ +ndpi.ndpi_ssl_version2str.restype = c_char_p +ndpi.ndpi_ssl_version2str.argtypes = [c_int16, POINTER(c_uint8)] + +""" ndpi_init_detection_module: Returns a new initialized detection module. + Note that before you can use it you can still load hosts and do other things. As soon as you are ready to use + it do not forget to call first ndpi_finalize_initalization() """ +ndpi.ndpi_init_detection_module.restype = POINTER(NDPIDetectionModuleStruct) + + +def ndpi_ndpi_finalize_initalization(detection_module): + """ ndpi_finalize_initalization: Completes the initialization (ndpi_revision >= 3.1)""" + if cast(ndpi.ndpi_revision(), c_char_p).value.decode("utf-8")[:3] >= '3.1': + ndpi.ndpi_finalize_initalization.restype = c_void_p + ndpi.ndpi_finalize_initalization.argtypes = [POINTER(NDPIDetectionModuleStruct)] + return ndpi.ndpi_finalize_initalization(detection_module) + else: + # ignore it + return None + + +""" ndpi_tfind: find a node, or return 0. """ ndpi.ndpi_tfind.restype = c_void_p + +""" ndpi_tsearch: ftp://ftp.cc.uoc.gr/mirrors/OpenBSD/src/lib/libc/stdlib/tsearch.c + find or insert datum into search tree. """ ndpi.ndpi_tsearch.restype = c_void_p +ndpi.ndpi_tsearch.argtypes = [c_void_p, POINTER(c_void_p), CFUNCTYPE(c_int, c_void_p, c_void_p)] + +""" ndpi_revision: Get the nDPI version release. """ ndpi.ndpi_revision.restype = c_void_p + +""" ndpi_get_proto_name: Get the protocol name associated to the ID.""" ndpi.ndpi_get_proto_name.restype = c_void_p + +""" ndpi_category_get_name: Get protocol category as string.""" +ndpi.ndpi_category_get_name.restype = c_void_p + +""" ndpi_get_num_supported_protocols: Get the total number of the supported protocols.""" ndpi.ndpi_get_num_supported_protocols.restype = c_uint -ndpi.ndpi_detection_process_packet.restype = ndpi_protocol -ndpi.ndpi_init_detection_module.restype = POINTER(ndpi_detection_module_struct) -ndpi.ndpi_wrap_NDPI_BITMASK_SET_ALL.argtypes = [POINTER(NDPI_PROTOCOL_BITMASK)] -ndpi.ndpi_set_protocol_detection_bitmask2.argtypes = [POINTER(ndpi_detection_module_struct), POINTER(NDPI_PROTOCOL_BITMASK)] -ndpi.ndpi_tsearch.argtypes = [c_void_p, POINTER(c_void_p), CFUNCTYPE(c_int, c_void_p, c_void_p)] + +""" ndpi_wrap_NDPI_BITMASK_SET_ALL: memset((char *)(p), 0xFF, sizeof(*(p)))""" +ndpi.ndpi_wrap_NDPI_BITMASK_SET_ALL.argtypes = [POINTER(NDPIProtocolBitMask)] + +""" ndpi_set_protocol_detection_bitmask2: Sets the protocol bitmask2.""" +ndpi.ndpi_set_protocol_detection_bitmask2.argtypes = [POINTER(NDPIDetectionModuleStruct), + POINTER(NDPIProtocolBitMask)] + +""" ndpi_twalk: Walk the nodes of a tree. """ ndpi.ndpi_twalk.argtypes = [c_void_p, CFUNCTYPE(None, c_void_p, c_int32, c_int, c_void_p), c_void_p] -ndpi.ndpi_tdestroy.argtypes = [c_void_p, CFUNCTYPE(None, c_void_p)] -ndpi.ndpi_detection_giveup.restype = ndpi_protocol -ndpi.ndpi_detection_giveup.argtypes = [POINTER(ndpi_detection_module_struct), POINTER(ndpi_flow_struct), c_uint8] -ndpi.ndpi_detection_process_packet.argtypes = [POINTER(ndpi_detection_module_struct), POINTER(ndpi_flow_struct), POINTER(c_ubyte), c_ushort, c_uint64, POINTER(ndpi_id_struct), POINTER(ndpi_id_struct)] + +""" ndpi_tdestroy: node destroy. """ +ndpi.ndpi_tdestroy.argtypes = [c_void_p, CFUNCTYPE(None, c_void_p)] \ No newline at end of file diff --git a/src/include/ndpi_api.h b/src/include/ndpi_api.h index 3c30f1f..0f1b248 100644 --- a/src/include/ndpi_api.h +++ b/src/include/ndpi_api.h @@ -1,7 +1,7 @@ /* * ndpi_api.h * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -34,7 +34,7 @@ extern "C" { /* The #define below is used for apps that dynamically link with nDPI to make sure that datastructures and in sync across versions */ -#define NDPI_API_VERSION 1 +#define NDPI_API_VERSION 124 #define SIZEOF_ID_STRUCT ( sizeof(struct ndpi_id_struct) ) #define SIZEOF_FLOW_STRUCT ( sizeof(struct ndpi_flow_struct) ) @@ -78,9 +78,34 @@ extern "C" { */ u_int32_t ndpi_detection_get_sizeof_ndpi_id_struct(void); + + /** + * Get the size of the flow tcp struct + * + * @return the size of the flow tcp struct + * + */ + u_int32_t ndpi_detection_get_sizeof_ndpi_flow_tcp_struct(void); + + + /** + * Get the size of the flow udp struct + * + * @return the size of the flow udp struct + * + */ + u_int32_t ndpi_detection_get_sizeof_ndpi_flow_udp_struct(void); + + /* + Same as the API call above but used for matching raw id's added + via ndpi_add_string_value_to_automa() + */ + int ndpi_match_string_value(void *_automa, char *string_to_match, + u_int match_len, u_int32_t *num); + /** * nDPI personal allocation and free functions - **/ + **/ void * ndpi_malloc(size_t size); void * ndpi_calloc(unsigned long count, size_t size); void * ndpi_realloc(void *ptr, size_t old_size, size_t new_size); @@ -112,7 +137,7 @@ extern "C" { * NULL if the substring is not found * */ - char* ndpi_strncasestr(const char *s, const char *find, size_t slen); + const char* ndpi_strncasestr(const char *s, const char *find, size_t slen); /** * Returns the nDPI protocol id for IP-based protocol detection @@ -126,6 +151,21 @@ extern "C" { u_int16_t ndpi_network_ptree_match(struct ndpi_detection_module_struct *ndpi_struct, struct in_addr *pin); + /** + * Returns the nDPI protocol id for IP+port-based protocol detection + * + * @par ndpi_struct = the struct created for the protocol detection + * @par pin = IP host address (MUST BE in network byte order): + * See man(7) ip for details + * @par port = The port (MUST BE in network byte order) or + * 0 if ignored + * @return the nDPI protocol ID + * + */ + u_int16_t ndpi_network_port_ptree_match(struct ndpi_detection_module_struct *ndpi_struct, + struct in_addr *pin /* network byte order */, + u_int16_t port /* network byte order */); + /** * Init single protocol match * @@ -138,11 +178,27 @@ extern "C" { /** * Returns a new initialized detection module + * Note that before you can use it you can still load + * 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 * */ - struct ndpi_detection_module_struct *ndpi_init_detection_module(void); + struct ndpi_detection_module_struct *ndpi_init_detection_module(ndpi_init_prefs prefs); + + /** + * Completes the initialization (2nd step) + * + * @par ndpi_str = the struct created for the protocol detection + * + */ + void ndpi_finalize_initalization(struct ndpi_detection_module_struct *ndpi_str); /** * Frees the memory allocated in the specified flow @@ -206,19 +262,8 @@ extern "C" { */ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *ndpi_struct, const NDPI_PROTOCOL_BITMASK * detection_bitmask); - - /** - * Function to be called to see in case of unknown match to see if there is - * a partial match that has been prevented by the current nDPI preferences configuration - * - * @par ndpi_struct = the detection module - * @par flow = the flow given for the detection module - * @return the detected protocol even if the flow is not completed; - * - */ - ndpi_protocol ndpi_get_partial_detection(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow); - /** + + /** * Function to be called before we give up with detection for a given flow. * This function reduces the NDPI_UNKNOWN_PROTOCOL detection * @@ -239,13 +284,13 @@ extern "C" { * (like SSL getting both client and server certificate even if we already know after * seeing the client certificate what the protocol is) * - * @par ndpi_struct = the detection module - * @par flow = pointer to the connection state machine - * @par packet = unsigned char pointer to the Layer 3 (IP header) - * @par packetlen = the length of the packet - * @par current_tick = the current timestamp for the packet - * @par src = pointer to the source subscriber state machine - * @par dst = pointer to the destination subscriber state machine + * @par ndpi_struct = the detection module + * @par flow = pointer to the connection state machine + * @par packet = unsigned char pointer to the Layer 3 (IP header) + * @par packetlen = the length of the packet + * @par packet_time_ms = the current timestamp for the packet (expressed in msec) + * @par src = pointer to the source subscriber state machine + * @par dst = pointer to the destination subscriber state machine * @return void * */ @@ -253,7 +298,7 @@ extern "C" { struct ndpi_flow_struct *flow, const unsigned char *packet, const unsigned short packetlen, - const u_int64_t current_tick, + const u_int64_t packet_time_ms, struct ndpi_id_struct *src, struct ndpi_id_struct *dst); @@ -261,13 +306,13 @@ extern "C" { * Processes one packet and returns the ID of the detected protocol. * This is the MAIN PACKET PROCESSING FUNCTION. * - * @par ndpi_struct = the detection module - * @par flow = pointer to the connection state machine - * @par packet = unsigned char pointer to the Layer 3 (IP header) - * @par packetlen = the length of the packet - * @par current_tick = the current timestamp for the packet - * @par src = pointer to the source subscriber state machine - * @par dst = pointer to the destination subscriber state machine + * @par ndpi_struct = the detection module + * @par flow = pointer to the connection state machine + * @par packet = unsigned char pointer to the Layer 3 (IP header) + * @par packetlen = the length of the packet + * @par packet_time_ms = the current timestamp for the packet (expressed in msec) + * @par src = pointer to the source subscriber state machine + * @par dst = pointer to the destination subscriber state machine * @return the detected ID of the protocol * */ @@ -275,7 +320,7 @@ extern "C" { struct ndpi_flow_struct *flow, const unsigned char *packet, const unsigned short packetlen, - const u_int64_t current_tick, + const u_int64_t packet_time_ms, struct ndpi_id_struct *src, struct ndpi_id_struct *dst); /** @@ -289,7 +334,7 @@ extern "C" { */ u_int16_t ndpi_get_flow_masterprotocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); - + /** * API call that is called internally by ndpi_detection_process_packet or by apps * that want to avoid calling ndpi_detection_process_packet as they have already @@ -299,12 +344,14 @@ extern "C" { * @par ndpi_struct = the detection module * @par flow = the flow given for the detection module * @par ndpi_selection_bitmask = the protocol selected bitmask + * @return number of protocol dissector that have been tried (0 = no more dissectors) * */ - void ndpi_check_flow_func(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow, - NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet); + u_int32_t ndpi_check_flow_func(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet); + /** * Query the pointer to the layer 4 packet * @@ -390,7 +437,7 @@ extern "C" { char *string_to_match, u_int string_to_match_len, ndpi_protocol_match_result *ret_match, - u_int16_t master_protocol_id); + u_int16_t master_protocol_id); /** * Check if the string content passed match with a protocol @@ -505,6 +552,21 @@ extern "C" { u_int8_t ndpi_is_subprotocol_informative(struct ndpi_detection_module_struct *ndpi_mod, u_int16_t protoId); + /** + * Set hostname-based protocol + * + * @par ndpi_mod = the detection module + * @par flow = the flow to which this communication belongs to + * @par master_protocol = the master protocol for this flow + * @par name = the host name + * @par name_len = length of the host name + * + */ + int ndpi_match_hostname_protocol(struct ndpi_detection_module_struct *ndpi_mod, + struct ndpi_flow_struct *flow, + u_int16_t master_protocol, + char *name, u_int name_len); + /** * Get protocol category as string * @@ -726,7 +788,7 @@ extern "C" { * @return 0 in case of no error, or -1 if an error occurred. * */ - int ndpi_add_string_value_to_automa(void *_automa, char *str, unsigned long num); + int ndpi_add_string_value_to_automa(void *_automa, char *str, u_int32_t num); /** * Add a string to match to an automata. Same as ndpi_add_string_value_to_automa() with num set to 1 @@ -768,12 +830,13 @@ extern "C" { u_int32_t daddr, ndpi_protocol *ret); int ndpi_match_custom_category(struct ndpi_detection_module_struct *ndpi_struct, - char *name, u_int name_len, unsigned long *id); + char *name, u_int name_len, ndpi_protocol_category_t *id); void ndpi_fill_protocol_category(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, ndpi_protocol *ret); int ndpi_get_custom_category_match(struct ndpi_detection_module_struct *ndpi_struct, - char *name_or_ip, u_int name_len, unsigned long *id); + char *name_or_ip, u_int name_len, + ndpi_protocol_category_t *id); int ndpi_set_detection_preferences(struct ndpi_detection_module_struct *ndpi_mod, ndpi_detection_preference pref, int value); @@ -781,12 +844,13 @@ extern "C" { /* Tells to called on what l4 protocol given application protocol can be found */ ndpi_l4_proto_info ndpi_get_l4_proto_info(struct ndpi_detection_module_struct *ndpi_struct, u_int16_t ndpi_proto_id); const char* ndpi_get_l4_proto_name(ndpi_l4_proto_info proto); - + ndpi_proto_defaults_t* ndpi_get_proto_defaults(struct ndpi_detection_module_struct *ndpi_mod); u_int ndpi_get_ndpi_num_supported_protocols(struct ndpi_detection_module_struct *ndpi_mod); u_int ndpi_get_ndpi_num_custom_protocols(struct ndpi_detection_module_struct *ndpi_mod); u_int ndpi_get_ndpi_detection_module_size(void); void ndpi_set_log_level(struct ndpi_detection_module_struct *ndpi_mod, u_int l); + void ndpi_set_debug_bitmask(struct ndpi_detection_module_struct *ndpi_mod, NDPI_PROTOCOL_BITMASK debug_bitmask); /* LRU cache */ struct ndpi_lru_cache* ndpi_lru_cache_init(u_int32_t num_entries); @@ -794,18 +858,21 @@ extern "C" { u_int8_t ndpi_lru_find_cache(struct ndpi_lru_cache *c, u_int32_t key, u_int16_t *value, u_int8_t clean_key_when_found); void ndpi_lru_add_to_cache(struct ndpi_lru_cache *c, u_int32_t key, u_int16_t value); - + /** - * Add a string to match to an automata + * Find a protocol id associated with a string automata * * @par The automata initialized with ndpi_init_automa(); * @par The (sub)string to search * @par The (sub)string length - * @par The id associated with the matched string or 0 id not found. + * @par The protocol id associated with the matched string or 0 id not found. * @return 0 in case of match, or -1 if no match, or -2 if an error occurred. * */ - int ndpi_match_string_id(void *_automa, char *string_to_match, u_int match_len, unsigned long *id); + int ndpi_match_string_protocol_id(void *_automa, char *string_to_match, u_int match_len, + u_int16_t *protocol_id, + ndpi_protocol_category_t *category, + ndpi_protocol_breed_t *breed); /* Utility functions to set ndpi malloc/free/print wrappers */ void set_ndpi_malloc(void* (*__ndpi_malloc)(size_t size)); @@ -817,7 +884,8 @@ extern "C" { //void * ndpi_malloc(size_t size); //void * ndpi_calloc(unsigned long count, size_t size); //void ndpi_free(void *ptr); - u_int8_t ndpi_get_api_version(void); + 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, @@ -826,75 +894,454 @@ extern "C" { u_int16_t src_port, u_int16_t dst_port, u_int8_t icmp_type, u_int8_t icmp_code, u_char *hash_buf, u_int8_t hash_buf_len); u_int8_t ndpi_extra_dissection_possible(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow); + struct ndpi_flow_struct *flow); u_int8_t ndpi_is_safe_ssl_cipher(u_int32_t cipher); const char* ndpi_cipher2str(u_int32_t cipher); + const char* ndpi_tunnel2str(ndpi_packet_tunnel tt); u_int16_t ndpi_guess_host_protocol_id(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); int ndpi_has_human_readeable_string(struct ndpi_detection_module_struct *ndpi_struct, char *buffer, u_int buffer_size, u_int8_t min_string_match_len, /* Will return 0 if no string > min_string_match_len have been found */ char *outbuf, u_int outbuf_len); - char* ndpi_ssl_version2str(u_int16_t version, u_int8_t *unknown_tls_version); + char* ndpi_ssl_version2str(struct ndpi_flow_struct *flow, + u_int16_t version, u_int8_t *unknown_tls_version); + void ndpi_patchIPv6Address(char *str); + void ndpi_user_pwd_payload_copy(u_int8_t *dest, u_int dest_len, u_int offset, + const u_int8_t *src, u_int src_len); + u_char* ndpi_base64_decode(const u_char *src, size_t len, size_t *out_len); + char* ndpi_base64_encode(unsigned char const* bytes_to_encode, size_t in_len); /* NOTE: caller MUST free the returned pointer */ + void ndpi_string_sha1_hash(const uint8_t *message, size_t len, u_char *hash /* 20-bytes */); + + int ndpi_load_ipv4_ptree(struct ndpi_detection_module_struct *ndpi_str, + const char *path, u_int16_t protocol_id); + int ndpi_dpi2json(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + ndpi_protocol l7_protocol, + ndpi_serializer *serializer); + int ndpi_flow2json(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + u_int8_t ip_version, + u_int8_t l4_protocol, u_int16_t vlan_id, + u_int32_t src_v4, u_int32_t dst_v4, + struct ndpi_in6_addr *src_v6, struct ndpi_in6_addr *dst_v6, + u_int16_t src_port, u_int16_t dst_port, + ndpi_protocol l7_protocol, + ndpi_serializer *serializer); + + void ndpi_md5(const u_char *data, size_t data_len, u_char hash[16]); + + const char* ndpi_http_method2str(ndpi_http_method m); + ndpi_http_method ndpi_http_str2method(const char* method, ssize_t method_len); + + /* ptree (trie) API */ + ndpi_ptree_t* ndpi_ptree_create(void); + int ndpi_ptree_insert(ndpi_ptree_t *tree, const ndpi_ip_addr_t *addr, u_int8_t bits, u_int32_t user_data); + 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, + char *name, u_int8_t is_hostname); - /* Serializer */ - int ndpi_init_serializer_ll(ndpi_serializer *serializer, ndpi_serialization_format fmt, - u_int32_t buffer_size); + /* Serializer (supports JSON, TLV, CSV) */ + + /** + * Initialize a serializer handle (allocated by the caller). + * @param serializer The serializer handle + * @param fmt The serialization format (ndpi_serialization_format_json, ndpi_serialization_format_tlv, ndpi_serialization_format_csv) + * @return 0 on success, a negative number otherwise + */ int ndpi_init_serializer(ndpi_serializer *serializer, ndpi_serialization_format fmt); + + /** + * Initialize a serializer handle. Same as ndpi_init_serializer, but with some low-level settings. + * @param serializer The serializer handle + * @param fmt The serialization format (ndpi_serialization_format_json, ndpi_serialization_format_tlv, ndpi_serialization_format_csv) + * @param buffer_size The initial internal buffer_size + * @return 0 on success, a negative number otherwise + */ + int ndpi_init_serializer_ll(ndpi_serializer *serializer, ndpi_serialization_format fmt, u_int32_t buffer_size); + + /** + * Release all allocated data structure. + * @param serializer The serializer handle + */ void ndpi_term_serializer(ndpi_serializer *serializer); + + /** + * Reset the serializer (cleanup the internal buffer to start a new serialization) + * @param serializer The serializer handle + */ void ndpi_reset_serializer(ndpi_serializer *serializer); - int ndpi_serialize_string_int32(ndpi_serializer *serializer, - const char *key, int32_t value); - int ndpi_serialize_string_int64(ndpi_serializer *serializer, - const char *key, int64_t value); - int ndpi_serialize_uint32_uint32(ndpi_serializer *serializer, - u_int32_t key, u_int32_t value); - int ndpi_serialize_uint32_uint64(ndpi_serializer *serializer, - u_int32_t key, u_int64_t value); - int ndpi_serialize_uint32_int32(ndpi_serializer *serializer, - u_int32_t key, int32_t value); - int ndpi_serialize_uint32_int64(ndpi_serializer *serializer, - u_int32_t key, int64_t value); - int ndpi_serialize_uint32_float(ndpi_serializer *serializer, - u_int32_t key, float value, - const char *format /* e.f. "%.2f" */); - int ndpi_serialize_uint32_string(ndpi_serializer *serializer, - u_int32_t key, const char *value); - int ndpi_serialize_string_uint32(ndpi_serializer *serializer, - const char *key, u_int32_t value); - int ndpi_serialize_string_uint32_format(ndpi_serializer *serializer, - const char *key, u_int32_t value, - const char *format); - int ndpi_serialize_string_uint64(ndpi_serializer *serializer, - const char *key, u_int64_t value); - int ndpi_serialize_string_string(ndpi_serializer *serializer, - const char *key, const char *value); - int ndpi_serialize_string_binary(ndpi_serializer *_serializer, - const char *key, const char *_value, - u_int16_t vlen); - - int ndpi_serialize_string_float(ndpi_serializer *serializer, - const char *key, float value, - const char *format /* e.f. "%.2f" */); + + /** + * Serialize a 32-bit unsigned int key and a 32-bit unsigned int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_uint32_uint32(ndpi_serializer *serializer, u_int32_t key, u_int32_t value); + + /** + * Serialize a 32-bit unsigned int key and a 64-bit unsigned int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_uint32_uint64(ndpi_serializer *serializer, u_int32_t key, u_int64_t value); + + /** + * Serialize a 32-bit unsigned int key and a 32-bit signed int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_uint32_int32(ndpi_serializer *serializer, u_int32_t key, int32_t value); + + /** + * Serialize a 32-bit unsigned int key and a 64-bit signed int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_uint32_int64(ndpi_serializer *serializer, u_int32_t key, int64_t value); + + /** + * Serialize a 32-bit unsigned int key and a float value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @param format The float value format + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_uint32_float(ndpi_serializer *serializer, u_int32_t key, float value, const char *format /* e.f. "%.2f" */); + + /** + * Serialize a 32-bit unsigned int key and a string value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_uint32_string(ndpi_serializer *serializer, u_int32_t key, const char *value); + + /** + * Serialize a 32-bit unsigned int key and a boolean value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_uint32_boolean(ndpi_serializer *serializer, u_int32_t key, u_int8_t value); + + /** + * Serialize an unterminated string key and a 32-bit signed int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_binary_int32(ndpi_serializer *_serializer, const char *key, u_int16_t klen, int32_t value); + + /** + * Serialize a string key and a 32-bit signed int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_int32(ndpi_serializer *serializer, const char *key, int32_t value); + + /** + * Serialize an unterminated string key and a 64-bit signed int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_binary_int64(ndpi_serializer *_serializer, const char *key, u_int16_t klen, int64_t value); + + /** + * Serialize a string key and a 64-bit signed int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_int64(ndpi_serializer *serializer, const char *key, int64_t value); + + /** + * Serialize an unterminated string key and a 32-bit unsigned int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_binary_uint32(ndpi_serializer *_serializer, const char *key, u_int16_t klen, u_int32_t value); + + /** + * Serialize a string key and a 32-bit unsigned int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_uint32(ndpi_serializer *serializer, const char *key, u_int32_t value); + + /** + * Serialize a string key and a float value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @param format The float format + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_uint32_format(ndpi_serializer *serializer, const char *key, u_int32_t value, const char *format); + + /** + * Serialize an unterminated string key and a 64-bit unsigned int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_binary_uint64(ndpi_serializer *_serializer, const char *key, u_int16_t klen, u_int64_t value); + + /** + * Serialize a string key and a 64-bit unsigned int value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_uint64(ndpi_serializer *serializer, const char *key, u_int64_t value); + + /** + * Serialize an unterminated string key and an unterminated string value + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @param value The field value + * @param vlen The value length + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_binary_binary(ndpi_serializer *_serializer, const char *key, u_int16_t klen, const char *_value, u_int16_t vlen); + + /** + * Serialize a string key and a string value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_string(ndpi_serializer *serializer, const char *key, const char *value); + + /** + * Serialize a string key and an unterminated string value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @param vlen The value length + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_binary(ndpi_serializer *serializer, const char *key, const char *_value, u_int16_t vlen); + + /** + * Serialize a string key and a raw value (this is a string which is added to the JSON without any quote or escaping) + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @param vlen The value length + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_raw(ndpi_serializer *_serializer, const char *key, const char *_value, u_int16_t vlen); + + /** + * Serialize an unterminated string key and a float value + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @param value The field value + * @param format The float format + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_binary_float(ndpi_serializer *_serializer, const char *key, u_int16_t klen, float value, const char *format /* e.f. "%.2f" */); + + /** + * Serialize a string key and a a float value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @param format The float format + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_float(ndpi_serializer *serializer, const char *key, float value, const char *format /* e.f. "%.2f" */); + + /** + * Serialize a string key and a boolean value + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_string_boolean(ndpi_serializer *serializer, const char *key, u_int8_t value); + + /** + * Serialize a raw record in an array (this is a low-level function and its use is not recommended) + * @param serializer The serializer handle + * @param record The record value + * @param record_len The record length + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_raw_record(ndpi_serializer *_serializer, u_char *record, u_int32_t record_len); + + /** + * Serialize an End-Of-Record (the current object becomes is terminated and added to an array, + * and a new object is created where the next items will be added) + * @param serializer The serializer handle + * @return 0 on success, a negative number otherwise + */ int ndpi_serialize_end_of_record(ndpi_serializer *serializer); - int ndpi_serialize_start_of_block(ndpi_serializer *_serializer, - const char *key); - int ndpi_serialize_end_of_block(ndpi_serializer *_serializer); - char* ndpi_serializer_get_buffer(ndpi_serializer *_serializer, u_int32_t *buffer_len); - u_int32_t ndpi_serializer_get_buffer_len(ndpi_serializer *_serializer); - int ndpi_serializer_set_buffer_len(ndpi_serializer *_serializer, u_int32_t l); + + /** + * Serialize the start of a list with an unterminated string key, where the next serialized items + * will be added (note: keys for the new items are ignored) + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_start_of_list_binary(ndpi_serializer *_serializer, const char *key, u_int16_t klen); + + /** + * Serialize the start of a list, where the next serialized items will be added (note: keys for + * the new items are ignored) + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_start_of_list(ndpi_serializer *serializer, const char *key); + + /** + * Serialize the end of a list + * @param serializer The serializer handle + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_end_of_list(ndpi_serializer *serializer); + + /** + * Serialize the start of a block with an unterminated string key + * @param serializer The serializer handle + * @param key The field name or ID + * @param klen The key length + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_start_of_block_binary(ndpi_serializer *_serializer, const char *key, u_int16_t klen); + + /** + * Serialize the start of a block with a string key + * @param serializer The serializer handle + * @param key The field name or ID + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_start_of_block(ndpi_serializer *serializer, const char *key); + + /** + * Serialize the end of a block + * @param serializer The serializer handle + * @param key The field name or ID + * @param value The field value + * @return 0 on success, a negative number otherwise + */ + int ndpi_serialize_end_of_block(ndpi_serializer *serializer); + + /** + * Return the serialized buffer + * @param serializer The serializer handle + * @param buffer_len The buffer length (out) + * @return The buffer + */ + char* ndpi_serializer_get_buffer(ndpi_serializer *serializer, u_int32_t *buffer_len); + + /** + * Return the current serialized buffer length + * @param serializer The serializer handle + * @return The buffer length + */ + u_int32_t ndpi_serializer_get_buffer_len(ndpi_serializer *serializer); + + /** + * Return the real internal buffer size (containing the serialized buffer) + * @param serializer The serializer handle + * @return The internal buffer size + */ + u_int32_t ndpi_serializer_get_internal_buffer_size(ndpi_serializer *serializer); + + /** + * Change the serializer buffer length + * @param serializer The serializer handle + * @param l The new buffer length + * @return 0 on success, a negative number otherwise + */ + int ndpi_serializer_set_buffer_len(ndpi_serializer *serializer, u_int32_t l); + + /** + * Return the configured serialization format + * @param serializer The serializer handle + * @return The serialization format + */ + ndpi_serialization_format ndpi_serializer_get_format(ndpi_serializer *serializer); + + /** + * Set the CSV separator + * @param serializer The serializer handle + * @param separator The separator + */ void ndpi_serializer_set_csv_separator(ndpi_serializer *serializer, char separator); + /** + * Return the header automatically built from keys (CSV only) + * @param serializer The serializer handle + * @param buffer_len The buffer length (out) + * @return The header + */ + char* ndpi_serializer_get_header(ndpi_serializer *serializer, u_int32_t *buffer_len); + + /** + * Create a snapshot of the internal buffer for later rollback (ndpi_serializer_rollback_snapshot) + * @param serializer The serializer handle + */ void ndpi_serializer_create_snapshot(ndpi_serializer *serializer); + + /** + * Rollback to the latest snapshot + * @param serializer The serializer handle + */ void ndpi_serializer_rollback_snapshot(ndpi_serializer *serializer); - - /* Deserializer */ + + /* Deserializer (supports TLV only) */ + int ndpi_init_deserializer(ndpi_deserializer *deserializer, ndpi_serializer *serializer); int ndpi_init_deserializer_buf(ndpi_deserializer *deserializer, u_int8_t *serialized_buffer, u_int32_t serialized_buffer_len); - + ndpi_serialization_format ndpi_deserialize_get_format(ndpi_deserializer *_deserializer); ndpi_serialization_type ndpi_deserialize_get_item_type(ndpi_deserializer *deserializer, ndpi_serialization_type *key_type); int ndpi_deserialize_next(ndpi_deserializer *deserializer); @@ -916,21 +1363,69 @@ extern "C" { struct ndpi_analyze_struct* ndpi_alloc_data_analysis(u_int16_t _max_series_len); void ndpi_init_data_analysis(struct ndpi_analyze_struct *s, u_int16_t _max_series_len); void ndpi_free_data_analysis(struct ndpi_analyze_struct *d); + void ndpi_reset_data_analysis(struct ndpi_analyze_struct *d); void ndpi_data_add_value(struct ndpi_analyze_struct *s, const u_int32_t value); - float ndpi_data_average(struct ndpi_analyze_struct *s); + /* Sliding-window only */ float ndpi_data_window_average(struct ndpi_analyze_struct *s); - + float ndpi_data_window_variance(struct ndpi_analyze_struct *s); + float ndpi_data_window_stddev(struct ndpi_analyze_struct *s); + + /* All data */ + float ndpi_data_average(struct ndpi_analyze_struct *s); float ndpi_data_entropy(struct ndpi_analyze_struct *s); float ndpi_data_variance(struct ndpi_analyze_struct *s); float ndpi_data_stddev(struct ndpi_analyze_struct *s); + u_int32_t ndpi_data_last(struct ndpi_analyze_struct *s); u_int32_t ndpi_data_min(struct ndpi_analyze_struct *s); u_int32_t ndpi_data_max(struct ndpi_analyze_struct *s); float ndpi_data_ratio(u_int32_t sent, u_int32_t rcvd); - + const char* ndpi_data_ratio2str(float ratio); - + void ndpi_data_print_window_values(struct ndpi_analyze_struct *s); /* debug */ + + ndpi_risk_enum ndpi_validate_url(char *url); + + u_int8_t ndpi_is_protocol_detected(struct ndpi_detection_module_struct *ndpi_str, + ndpi_protocol proto); + void ndpi_serialize_risk(ndpi_serializer *serializer, struct ndpi_flow_struct *flow); + + const char* ndpi_risk2str(ndpi_risk_enum risk); + + /* ******************************* */ + + /* HyperLogLog cardinality estimator */ + + /* Memory lifecycle */ + int ndpi_hll_init(struct ndpi_hll *hll, u_int8_t bits); + void ndpi_hll_destroy(struct ndpi_hll *hll); + void ndpi_hll_reset(struct ndpi_hll *hll); + + /* Add values */ + void ndpi_hll_add(struct ndpi_hll *hll, const char *data, size_t data_len); + void ndpi_hll_add_number(struct ndpi_hll *hll, u_int32_t value) ; + + /* Get cardinality estimation */ + double ndpi_hll_count(struct ndpi_hll *hll); + + /* ******************************* */ + + int ndpi_init_bin(struct ndpi_bin *b, enum ndpi_bin_family f, u_int8_t num_bins); + void ndpi_free_bin(struct ndpi_bin *b); + struct ndpi_bin* ndpi_clone_bin(struct ndpi_bin *b); + void ndpi_inc_bin(struct ndpi_bin *b, u_int8_t slot_id, u_int32_t val); + void ndpi_set_bin(struct ndpi_bin *b, u_int8_t slot_id, u_int32_t value); + u_int32_t ndpi_get_bin_value(struct ndpi_bin *b, u_int8_t slot_id); + void ndpi_reset_bin(struct ndpi_bin *b); + void ndpi_normalize_bin(struct ndpi_bin *b); + char* ndpi_print_bin(struct ndpi_bin *b, u_int8_t normalize_first, char *out_buf, u_int out_buf_len); + float ndpi_bin_similarity(struct ndpi_bin *b1, struct ndpi_bin *b2, u_int8_t normalize_first); + int ndpi_cluster_bins(struct ndpi_bin *bins, u_int16_t num_bins, + u_int8_t num_clusters, u_int16_t *cluster_ids, + struct ndpi_bin *centroids); + + u_int32_t ndpi_quick_16_byte_hash(u_int8_t *in_16_bytes_long); #ifdef __cplusplus } #endif diff --git a/src/include/ndpi_classify.h b/src/include/ndpi_classify.h index 2793194..ab92128 100644 --- a/src/include/ndpi_classify.h +++ b/src/include/ndpi_classify.h @@ -43,8 +43,7 @@ #ifndef NDPI_CLASSIFY_H #define NDPI_CLASSIFY_H -#include -#include +#include "ndpi_includes.h" /* constants */ #define NUM_PARAMETERS_SPLT_LOGREG 208 @@ -67,27 +66,27 @@ extern float parameters_bd[NUM_PARAMETERS_BD_LOGREG]; extern float parameters_splt[NUM_PARAMETERS_SPLT_LOGREG]; /* Classifier functions */ -float ndpi_classify(const unsigned short *pkt_len, const struct timeval *pkt_time, - const unsigned short *pkt_len_twin, const struct timeval *pkt_time_twin, - struct timeval start_time, struct timeval start_time_twin, uint32_t max_num_pkt_len, +float ndpi_classify(const unsigned short *pkt_len, const pkt_timeval *pkt_time, + const unsigned short *pkt_len_twin, const pkt_timeval *pkt_time_twin, + pkt_timeval start_time, pkt_timeval start_time_twin, uint32_t max_num_pkt_len, uint16_t sp, uint16_t dp, uint32_t op, uint32_t ip, uint32_t np_o, uint32_t np_i, uint32_t ob, uint32_t ib, uint16_t use_bd, const uint32_t *bd, const uint32_t *bd_t); -void ndpi_merge_splt_arrays(const uint16_t *pkt_len, const struct timeval *pkt_time, - const uint16_t *pkt_len_twin, const struct timeval *pkt_time_twin, - struct timeval start_time, struct timeval start_time_twin, +void ndpi_merge_splt_arrays(const uint16_t *pkt_len, const pkt_timeval *pkt_time, + const uint16_t *pkt_len_twin, const pkt_timeval *pkt_time_twin, + pkt_timeval start_time, pkt_timeval start_time_twin, uint16_t s_idx, uint16_t r_idx, uint16_t *merged_lens, uint16_t *merged_times); void ndpi_update_params(classifier_type_codes_t param_type, const char *param_file); void ndpi_flow_info_freer(void *node); -unsigned int ndpi_timer_eq(const struct timeval *a, const struct timeval *b); -unsigned int ndpi_timer_lt(const struct timeval *a, const struct timeval *b); -void ndpi_timer_sub(const struct timeval *a, const struct timeval *b, struct timeval *result); -void ndpi_timer_clear(struct timeval *a); -unsigned int ndpi_timeval_to_milliseconds(struct timeval ts); -unsigned int ndpi_timeval_to_microseconds(struct timeval ts); +unsigned int ndpi_timer_eq(const pkt_timeval *a, const pkt_timeval *b); +unsigned int ndpi_timer_lt(const pkt_timeval *a, const pkt_timeval *b); +void ndpi_timer_sub(const pkt_timeval *a, const pkt_timeval *b, pkt_timeval *result); +void ndpi_timer_clear(pkt_timeval *a); +unsigned int ndpi_timeval_to_milliseconds(pkt_timeval ts); +unsigned int ndpi_timeval_to_microseconds(pkt_timeval ts); void ndpi_log_timestamp(char *log_ts, uint32_t log_ts_len); #endif /* NDPI_CLASSIFY_H */ diff --git a/src/include/ndpi_define.h.in b/src/include/ndpi_define.h.in index ce4c4cc..fdfe998 100644 --- a/src/include/ndpi_define.h.in +++ b/src/include/ndpi_define.h.in @@ -35,7 +35,9 @@ #include #define __BYTE_ORDER BYTE_ORDER #if BYTE_ORDER == LITTLE_ENDIAN +#ifndef __LITTLE_ENDIAN__ #define __LITTLE_ENDIAN__ +#endif /* __LITTLE_ENDIAN__ */ #else #define __BIG_ENDIAN__ #endif/* BYTE_ORDER */ @@ -179,6 +181,8 @@ #define NDPI_JABBER_FT_TIMEOUT 5 #define NDPI_SOULSEEK_CONNECTION_IP_TICK_TIMEOUT 600 +#include "ndpi_config.h" /* To have access to NDPI_ENABLE_DEBUG_MESSAGES */ + #ifdef NDPI_ENABLE_DEBUG_MESSAGES #define NDPI_LOG(proto, m, log_level, args...) \ { \ @@ -187,7 +191,7 @@ (*(mod->ndpi_debug_printf))(proto, mod, log_level, __FILE__, __FUNCTION__, __LINE__, args); \ } - /* We must define NDPI_CURRENT_PROTO before include ndpi_main.h !!! + /* We must define NDPI_CURRENT_PROTO before include ndpi_main.h !!! * * #include "ndpi_protocol_ids.h" * #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_XXXX @@ -196,7 +200,7 @@ */ #ifndef NDPI_CURRENT_PROTO - #define NDPI_CURRENT_PROTO NDPI_PROTO_UNKNOWN + #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_UNKNOWN #endif #define NDPI_LOG_ERR(mod, args...) \ @@ -274,6 +278,12 @@ #define NDPI_BITMASK_SET_ALL(a) NDPI_ONE(&a) #define NDPI_BITMASK_SET(a, b) { memcpy(&a, &b, sizeof(NDPI_PROTOCOL_BITMASK)); } +#define NDPI_SET_BIT(num, n) num |= 1UL << ( n ) +#define NDPI_CLR_BIT(num, n) num &= ~(1UL << ( n )) +#define NDPI_CLR_BIT(num, n) num &= ~(1UL << ( n )) +#define NDPI_ISSET_BIT(num, n) (num & (1UL << ( n ))) +#define NDPI_ZERO_BIT(num) num = 0 + /* this is a very very tricky macro *g*, * the compiler will remove all shifts here if the protocol is static... */ @@ -304,10 +314,10 @@ #define NDPI_ICMPV6_PROTOCOL_TYPE 0x3a /* the get_uXX will return raw network packet bytes !! */ -#define get_u_int8_t(X,O) (*(u_int8_t *)(((u_int8_t *)X) + O)) -#define get_u_int16_t(X,O) (*(u_int16_t *)(((u_int8_t *)X) + O)) -#define get_u_int32_t(X,O) (*(u_int32_t *)(((u_int8_t *)X) + O)) -#define get_u_int64_t(X,O) (*(u_int64_t *)(((u_int8_t *)X) + O)) +#define get_u_int8_t(X,O) (*(u_int8_t *)((&(((u_int8_t *)X)[O])))) +#define get_u_int16_t(X,O) (*(u_int16_t *)((&(((u_int8_t *)X)[O])))) +#define get_u_int32_t(X,O) (*(u_int32_t *)((&(((u_int8_t *)X)[O])))) +#define get_u_int64_t(X,O) (*(u_int64_t *)((&(((u_int8_t *)X)[O])))) /* new definitions to get little endian from network bytes */ #define get_ul8(X,O) get_u_int8_t(X,O) @@ -328,7 +338,7 @@ #define match_first_bytes(payload,st) (memcmp((payload),(st),(sizeof(st)-1))==0) #if defined(WIN32) && !defined(snprintf) -#define snprintf _snprintf +#define snprintf _snprintf #endif #define NDPI_MAX_DNS_REQUESTS 16 @@ -343,6 +353,10 @@ #define NDPI_CIPHER_WEAK 1 #define NDPI_CIPHER_INSECURE 2 +#define NDPI_OPTIMAL_HLL_NUM_BUCKETS 16 + +#define NDPI_MAX_NUM_TLS_APPL_BLOCKS 8 + #ifdef __APPLE__ #include diff --git a/src/include/ndpi_includes.h b/src/include/ndpi_includes.h index f8bde51..197f32f 100644 --- a/src/include/ndpi_includes.h +++ b/src/include/ndpi_includes.h @@ -57,7 +57,7 @@ #if defined __NetBSD__ || defined __OpenBSD__ #include -#ifdef __OpenBSD__ +#if defined __OpenBSD__ #include #endif @@ -67,4 +67,10 @@ #endif /* Win32 */ +#if defined __OpenBSD__ +#include "ndpi_includes_OpenBSD.h" +#else +typedef struct timeval pkt_timeval; +#endif /* __OpenBSD__ */ + #endif /* __NDPI_INCLUDES_H__ */ diff --git a/src/include/ndpi_includes_OpenBSD.h b/src/include/ndpi_includes_OpenBSD.h new file mode 100644 index 0000000..65716c8 --- /dev/null +++ b/src/include/ndpi_includes_OpenBSD.h @@ -0,0 +1,35 @@ +/* + * ndpi_includes_OpenBSD.h + * + * Copyright (C) 2011-16 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#ifndef __NDPI_INCLUDES_OPENBSD_H__ +#define __NDPI_INCLUDES_OPENBSD_H__ + +#ifndef IPPROTO_SCTP +#define IPPROTO_SCTP 132 +#endif /* IPPROTO_SCTP */ + +#include + +typedef struct bpf_timeval pkt_timeval; + +#endif /* __NDPI_INCLUDES_OPENBSD_H__ */ diff --git a/src/include/ndpi_main.h b/src/include/ndpi_main.h index 06aa8bc..f81e37c 100644 --- a/src/include/ndpi_main.h +++ b/src/include/ndpi_main.h @@ -1,7 +1,7 @@ /* * ndpi_main.h * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -114,6 +114,7 @@ extern "C" { extern int ndpi_parse_ip_string(const char *ip_str, ndpi_ip_addr_t *parsed_ip); extern char *ndpi_get_ip_string(const ndpi_ip_addr_t * ip, char *buf, u_int buf_len); + extern u_int8_t ndpi_is_ipv6(const ndpi_ip_addr_t *ip); extern char* ndpi_get_proto_by_id(struct ndpi_detection_module_struct *ndpi_mod, u_int id); u_int16_t ndpi_get_proto_by_name(struct ndpi_detection_module_struct *ndpi_mod, const char *name); @@ -131,7 +132,7 @@ extern "C" { u_int16_t** tcp_master_proto, u_int16_t** udp_master_proto); #/* NDPI_PROTOCOL_NETBIOS */ - int ndpi_netbios_name_interpret(char *in, char *out, u_int out_len); + int ndpi_netbios_name_interpret(char *in, size_t inlen, char *out, u_int out_len); #ifdef NDPI_ENABLE_DEBUG_MESSAGES void ndpi_debug_get_last_log_function_line(struct ndpi_detection_module_struct *ndpi_struct, @@ -149,6 +150,10 @@ extern "C" { #define ndpi_match_strprefix(payload, payload_len, str) \ ndpi_match_prefix((payload), (payload_len), (str), (sizeof(str)-1)) +#ifdef NDPI_DETECTION_SUPPORT_IPV6 + int ndpi_handle_ipv6_extension_headers(struct ndpi_detection_module_struct *ndpi_str, const u_int8_t ** l4ptr, u_int16_t * l4len, u_int8_t * nxt_hdr); +#endif + #ifdef __cplusplus } #endif diff --git a/src/include/ndpi_protocol_ids.h b/src/include/ndpi_protocol_ids.h index 05be138..261f97b 100644 --- a/src/include/ndpi_protocol_ids.h +++ b/src/include/ndpi_protocol_ids.h @@ -1,8 +1,7 @@ - /* * ndpi_protocol_ids.h * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -23,12 +22,8 @@ */ -#ifndef __NDPI_API_H__ - -#endif - -#ifndef __NDPI_PROTOCOLS_DEFAULT_H__ -#define __NDPI_PROTOCOLS_DEFAULT_H__ +#ifndef __NDPI_PROTOCOLS_IDS_H__ +#define __NDPI_PROTOCOLS_IDS_H__ #define NDPI_DETECTION_SUPPORT_IPV6 #define NDPI_PROTOCOL_SIZE 2 @@ -75,24 +70,24 @@ typedef enum { NDPI_PROTOCOL_SKYPE_CALL = 38, /* Skype call and videocalls */ NDPI_PROTOCOL_SIGNAL = 39, NDPI_PROTOCOL_MEMCACHED = 40, /* Memcached - Darryl Sokoloski */ - NDPI_PROTOCOL_SMBV23 = 41, /* SMB version 2/3 */ + NDPI_PROTOCOL_SMBV23 = 41, /* SMB version 2/3 */ NDPI_PROTOCOL_MINING = 42, /* Bitcoin, Ethereum, ZCash, Monero */ NDPI_PROTOCOL_NEST_LOG_SINK = 43, /* Nest Log Sink (Nest Protect) - Darryl Sokoloski */ NDPI_PROTOCOL_MODBUS = 44, /* Modbus */ NDPI_PROTOCOL_WHATSAPP_CALL = 45, /* WhatsApp video ad audio calls go here */ - NDPI_PROTOCOL_DATASAVER = 46, /* Protocols used to save data on Internet communications */ + NDPI_PROTOCOL_DATASAVER = 46, /* Protocols used to save data on Internet communications */ NDPI_PROTOCOL_XBOX = 47, NDPI_PROTOCOL_QQ = 48, NDPI_PROTOCOL_TIKTOK = 49, NDPI_PROTOCOL_RTSP = 50, NDPI_PROTOCOL_MAIL_IMAPS = 51, NDPI_PROTOCOL_ICECAST = 52, - NDPI_PROTOCOL_PPLIVE = 53, /* Tomasz Bujlow */ + NDPI_PROTOCOL_FREE_53 = 53, NDPI_PROTOCOL_PPSTREAM = 54, NDPI_PROTOCOL_ZATTOO = 55, NDPI_PROTOCOL_SHOUTCAST = 56, NDPI_PROTOCOL_SOPCAST = 57, - NDPI_PROTOCOL_TVANTS = 58, + NDPI_PROTOCOL_DISCORD = 58, NDPI_PROTOCOL_TVUPLAYER = 59, NDPI_PROTOCOL_HTTP_DOWNLOAD = 60, NDPI_PROTOCOL_QQLIVE = 61, @@ -102,10 +97,10 @@ typedef enum { NDPI_PROTOCOL_IRC = 65, NDPI_PROTOCOL_AYIYA = 66, NDPI_PROTOCOL_UNENCRYPTED_JABBER = 67, - NDPI_PROTOCOL_MSN = 68, - NDPI_PROTOCOL_OSCAR = 69, + NDPI_PROTOCOL_NATS = 68, + NDPI_PROTOCOL_FREE_69 = 69, /* Free */ NDPI_PROTOCOL_YAHOO = 70, - NDPI_PROTOCOL_BATTLEFIELD = 71, + NDPI_PROTOCOL_FREE_71 = 71, /* Free */ NDPI_PROTOCOL_GOOGLE_PLUS = 72, NDPI_PROTOCOL_IP_VRRP = 73, NDPI_PROTOCOL_STEAM = 74, /* Tomasz Bujlow */ @@ -124,7 +119,7 @@ typedef enum { NDPI_PROTOCOL_RTP = 87, NDPI_PROTOCOL_RDP = 88, NDPI_PROTOCOL_VNC = 89, - NDPI_PROTOCOL_PCANYWHERE = 90, + NDPI_PROTOCOL_FREE90 = 90, /* Free */ NDPI_PROTOCOL_TLS = 91, NDPI_PROTOCOL_SSH = 92, NDPI_PROTOCOL_USENET = 93, @@ -217,7 +212,7 @@ typedef enum { NDPI_PROTOCOL_CNN = 180, /* Tomasz Bujlow */ NDPI_PROTOCOL_MEGACO = 181, /* Gianluca Costa */ NDPI_PROTOCOL_REDIS = 182, - NDPI_PROTOCOL_PANDO = 183, /* Tomasz Bujlow */ + NDPI_PROTOCOL_FREE_183 = 183, NDPI_PROTOCOL_VHUA = 184, NDPI_PROTOCOL_TELEGRAM = 185, /* Gianluca Costa */ NDPI_PROTOCOL_VEVO = 186, @@ -230,19 +225,19 @@ typedef enum { NDPI_PROTOCOL_KAKAOTALK = 193, /* KakaoTalk Chat (no voice call) */ NDPI_PROTOCOL_KAKAOTALK_VOICE = 194, /* KakaoTalk Voice */ NDPI_PROTOCOL_TWITCH = 195, /* Edoardo Dominici */ - NDPI_PROTOCOL_DNS_OVER_HTTPS = 196, + NDPI_PROTOCOL_DOH_DOT = 196, /* DoH (DNS over HTTPS), DoT (DNS over TLS) */ NDPI_PROTOCOL_WECHAT = 197, NDPI_PROTOCOL_MPEGTS = 198, NDPI_PROTOCOL_SNAPCHAT = 199, NDPI_PROTOCOL_SINA = 200, - NDPI_PROTOCOL_HANGOUT_DUO = 201, /* Google Hangout ad Duo (merged as they are very similar) */ + NDPI_PROTOCOL_HANGOUT_DUO = 201, /* Google Hangout ad Duo (merged as they are very similar) */ NDPI_PROTOCOL_IFLIX = 202, /* www.vizuamatix.com R&D team & M.Mallawaarachchie */ NDPI_PROTOCOL_GITHUB = 203, NDPI_PROTOCOL_BJNP = 204, - NDPI_PROTOCOL_LINE = 205, /* https://en.wikipedia.org/wiki/Line_(software) */ + NDPI_PROTOCOL_FREE_205 = 205, NDPI_PROTOCOL_WIREGUARD = 206, NDPI_PROTOCOL_SMPP = 207, /* Damir Franusic */ - NDPI_PROTOCOL_DNSCRYPT = 208, + NDPI_PROTOCOL_DNSCRYPT = 208, /* Toni Uhlig */ NDPI_PROTOCOL_TINC = 209, /* William Guglielmo */ NDPI_PROTOCOL_DEEZER = 210, NDPI_PROTOCOL_INSTAGRAM = 211, /* Andrea Buscarinu */ @@ -253,7 +248,7 @@ typedef enum { NDPI_PROTOCOL_IMO = 216, NDPI_PROTOCOL_GOOGLE_DRIVE = 217, NDPI_PROTOCOL_OCS = 218, - NDPI_PROTOCOL_OFFICE_365 = 219, + NDPI_PROTOCOL_MICROSOFT_365 = 219, NDPI_PROTOCOL_CLOUDFLARE = 220, NDPI_PROTOCOL_MS_ONE_DRIVE = 221, NDPI_PROTOCOL_MQTT = 222, @@ -270,8 +265,8 @@ typedef enum { NDPI_PROTOCOL_LINKEDIN = 233, /* Paulo Angelo */ NDPI_PROTOCOL_SOUNDCLOUD = 234, NDPI_PROTOCOL_CSGO = 235, /* Counter-Strike Global Offensive, Dota = 2 */ - NDPI_PROTOCOL_LISP = 236, - NDPI_PROTOCOL_DIAMETER = 237, + NDPI_PROTOCOL_LISP = 236, + NDPI_PROTOCOL_DIAMETER = 237, NDPI_PROTOCOL_APPLE_PUSH = 238, NDPI_PROTOCOL_GOOGLE_SERVICES = 239, NDPI_PROTOCOL_AMAZON_VIDEO = 240, @@ -279,14 +274,27 @@ typedef enum { NDPI_PROTOCOL_WHATSAPP_FILES = 242, /* Videos, pictures, voice messages... */ NDPI_PROTOCOL_TARGUS_GETDATA = 243, NDPI_PROTOCOL_DNP3 = 244, - NDPI_PROTOCOL_104 = 245, -/* + NDPI_PROTOCOL_IEC60870 = 245, /* https://en.wikipedia.org/wiki/IEC_60870-5 */ + NDPI_PROTOCOL_BLOOMBERG = 246, + NDPI_PROTOCOL_CAPWAP = 247, + NDPI_PROTOCOL_ZABBIX = 248, + NDPI_PROTOCOL_S7COMM = 249, + NDPI_PROTOCOL_MSTEAMS = 250, + NDPI_PROTOCOL_WEBSOCKET = 251, /* Leonn Paiva */ + NDPI_PROTOCOL_ANYDESK = 252, /* Toni Uhlig */ + NDPI_PROTOCOL_SOAP = 253, /* Toni Uhlig */ + +#ifdef CUSTOM_NDPI_PROTOCOLS +#include "../../../nDPI-custom/custom_ndpi_protocol_ids.h" +#endif + + /* IMPORTANT before allocating a new identifier please fill up one of those named NDPI_PROTOCOL_FREE_XXX and not used (placeholders to avoid protocol renumbering) */ - + /* IMPORTANT:NDPI_LAST_IMPLEMENTED_PROTOCOL MUST BE THE LAST ELEMENT */ NDPI_LAST_IMPLEMENTED_PROTOCOL } ndpi_protocol_id_t; @@ -294,4 +302,5 @@ typedef enum { #define NDPI_PROTOCOL_NO_MASTER_PROTO NDPI_PROTOCOL_UNKNOWN #define NDPI_MAX_SUPPORTED_PROTOCOLS NDPI_LAST_IMPLEMENTED_PROTOCOL #define NDPI_MAX_NUM_CUSTOM_PROTOCOLS (NDPI_NUM_BITS-NDPI_LAST_IMPLEMENTED_PROTOCOL) -#endif + +#endif /* __NDPI_PROTOCOL_IDS_H__ */ diff --git a/src/include/ndpi_protocols.h b/src/include/ndpi_protocols.h index 17941d5..e93c326 100644 --- a/src/include/ndpi_protocols.h +++ b/src/include/ndpi_protocols.h @@ -1,7 +1,7 @@ /* * ndpi_protocols.h * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -51,165 +51,6 @@ u_int ndpi_search_tcp_or_udp_raw(struct ndpi_detection_module_struct *ndpi_struc void ndpi_search_tcp_or_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -/* Applications and other protocols. */ -void ndpi_search_diameter(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_bittorrent(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_lisp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_edonkey(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_fasttrack_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_gnutella(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_directconnect(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_applejuice_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_i23v5(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_socrates(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_msn(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_yahoo(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_oscar(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_jabber_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_sip(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_imo(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_direct_download_link_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mail_pop_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mail_smtp_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_http_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_http_subprotocol_conf(struct ndpi_detection_module_struct *ndpi_struct, char *attr, char *value, int protocol_id); -void ndpi_search_ftp_control(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ftp_data(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_usenet_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_rtsp_tcp_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_filetopia_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_vmware(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ssl_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mms_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_icecast_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_shoutcast_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_veohtv_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_openft_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_stun(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_tvants_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_sopcast(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_tvuplayer(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ppstream(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_pplive(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_iax(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mgcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_zattoo(struct ndpi_detection_module_struct*ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_qq(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_feidian(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ayiya(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_thunder(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_activesync(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_in_non_tcp_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_vnc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_dhcp_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_steam(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_halflife2(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_xbox(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_smb_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_telnet_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ntp_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_nfs(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_rtp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ssdp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_worldofwarcraft(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_postgres_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mysql_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_bgp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_quake(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_battlefield(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_secondlife(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_pcanywhere(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_rdp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_snmp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_kontiki(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_syslog(struct ndpi_detection_module_struct*ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mdns(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ipp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ldap(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_warcraft3(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_kerberos(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_xdmcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_tftp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mssql_tds(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_pptp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_stealthnet(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_dhcpv6_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_afp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_checkmk(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_aimini(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_florensia(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_maplestory(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_dofus(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_world_of_kung_fu(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_fiesta(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_crossfire_tcp_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_guildwars_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_armagetron_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_dropbox(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_citrix(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_dcerpc(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_netflow(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_sflow(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_radius(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_wsus(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_teamview(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_lotus_notes(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_gtp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_spotify(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_h323(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_openvpn(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_noe(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ciscovpn(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_viber(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_teamspeak(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_corba(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_collectd(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_oracle(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_rsync(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_rtcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_skinny(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_tor(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_whois_das(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_socks5(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_socks4(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_rtmp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_pando(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_megaco(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_redis(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_zmq(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_vhua(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_telegram(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_eaq(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_kakaotalk_voice(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mpegts(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_starcraft(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ubntac2(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_coap(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_mqtt (struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_someip (struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_rx(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_git(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_drda(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_bjnp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_smpp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_tinc(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_fix(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_csgo(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_ajp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_memcached(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_nest_log_sink(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_wireguard(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_targus_getdata(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_apple_push(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -void ndpi_search_amazon_video(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); -/* --- INIT FUNCTIONS --- */ void init_diameter_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_afp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_aimini_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); @@ -217,7 +58,6 @@ void init_applejuice_dissector(struct ndpi_detection_module_struct *ndpi_struct, void init_armagetron_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_ayiya_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_amqp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_battlefield_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_bgp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_bittorrent_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_lisp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); @@ -262,12 +102,11 @@ void init_mail_imap_dissector(struct ndpi_detection_module_struct *ndpi_struct, void init_mail_pop_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_mail_smtp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_maplestory_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_mdns_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_megaco_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_mgpc_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_mining_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_mms_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_msn_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); +void init_nats_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_mpegts_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_mssql_tds_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_mysql_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); @@ -280,11 +119,7 @@ void init_ntp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int3 void init_openft_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_openvpn_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_oracle_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_oscar_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_pando_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_pcanywhere_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_postgres_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_pplive_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_ppstream_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_pptp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_qq_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); @@ -326,7 +161,6 @@ void init_telnet_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_i void init_tftp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_thunder_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_tor_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_tvants_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_tvuplayer_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_usenet_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_upnp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); @@ -341,7 +175,6 @@ void init_world_of_warcraft_dissector(struct ndpi_detection_module_struct *ndpi_ void init_world_of_kung_fu_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_xbox_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_xdmcp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_yahoo_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_zattoo_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_zmq_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_stracraft_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); @@ -369,9 +202,15 @@ void init_memcached_dissector(struct ndpi_detection_module_struct *ndpi_struct, void init_nest_log_sink_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_ookla_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_modbus_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); -void init_line_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); +void init_capwap_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); +void init_zabbix_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_wireguard_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_targus_getdata_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_dnp3_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); void init_104_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); +void init_s7comm_dissector(struct ndpi_detection_module_struct *ndpi_struct,u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); +void init_websocket_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); +void init_soap_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); +void init_dnscrypt_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask); + #endif /* __NDPI_PROTOCOLS_H__ */ diff --git a/src/include/ndpi_typedefs.h b/src/include/ndpi_typedefs.h index 4366df5..12f8f97 100644 --- a/src/include/ndpi_typedefs.h +++ b/src/include/ndpi_typedefs.h @@ -1,7 +1,7 @@ /* * ndpi_typedefs.h * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -42,12 +42,60 @@ typedef enum { ndpi_l4_proto_tcp_and_udp, } ndpi_l4_proto_info; +typedef enum { + ndpi_no_tunnel = 0, + ndpi_gtp_tunnel, + ndpi_capwap_tunnel, + ndpi_tzsp_tunnel, + ndpi_l2tp_tunnel, +} ndpi_packet_tunnel; + +/* + NOTE + When the typedef below is modified don't forget to update + - ndpi_risk2str (in ndpi_utils.c) + - https://github.com/ntop/ntopng/blob/dev/scripts/lua/modules/flow_risk_utils.lua + - ndpi_risk_enum (in python/ndpi.py) + */ +typedef enum { + NDPI_NO_RISK = 0, + NDPI_URL_POSSIBLE_XSS, + NDPI_URL_POSSIBLE_SQL_INJECTION, + NDPI_URL_POSSIBLE_RCE_INJECTION, + NDPI_BINARY_APPLICATION_TRANSFER, + NDPI_KNOWN_PROTOCOL_ON_NON_STANDARD_PORT, + NDPI_TLS_SELFSIGNED_CERTIFICATE, + NDPI_TLS_OBSOLETE_VERSION, + NDPI_TLS_WEAK_CIPHER, + NDPI_TLS_CERTIFICATE_EXPIRED, + NDPI_TLS_CERTIFICATE_MISMATCH, /* 10 */ + NDPI_HTTP_SUSPICIOUS_USER_AGENT, + NDPI_HTTP_NUMERIC_IP_HOST, + NDPI_HTTP_SUSPICIOUS_URL, + NDPI_HTTP_SUSPICIOUS_HEADER, + NDPI_TLS_NOT_CARRYING_HTTPS, + NDPI_SUSPICIOUS_DGA_DOMAIN, + NDPI_MALFORMED_PACKET, + NDPI_SSH_OBSOLETE_CLIENT_VERSION_OR_CIPHER, + NDPI_SSH_OBSOLETE_SERVER_VERSION_OR_CIPHER, + NDPI_SMB_INSECURE_VERSION, /* 20 */ + NDPI_TLS_SUSPICIOUS_ESNI_USAGE, + NDPI_UNSAFE_PROTOCOL, + NDPI_DNS_SUSPICIOUS_TRAFFIC, + NDPI_TLS_MISSING_SNI, + + /* Leave this as last member */ + NDPI_MAX_RISK /* must be <= 31 due to (**) */ +} ndpi_risk_enum; + +typedef u_int32_t ndpi_risk; /* (**) */ + /* NDPI_VISIT */ typedef enum { - ndpi_preorder, - ndpi_postorder, - ndpi_endorder, - ndpi_leaf + ndpi_preorder, + ndpi_postorder, + ndpi_endorder, + ndpi_leaf } ndpi_VISIT; /* NDPI_NODE */ @@ -431,6 +479,10 @@ PACK_ON struct tinc_cache_entry { u_int16_t dst_port; } PACK_OFF; +/* + In case the typedef below is modified, please update + ndpi_http_method2str (ndpi_utils.c) +*/ typedef enum { NDPI_HTTP_METHOD_UNKNOWN = 0, NDPI_HTTP_METHOD_OPTIONS, @@ -478,18 +530,12 @@ struct ndpi_id_struct { /* NDPI_PROTOCOL_GNUTELLA */ u_int32_t gnutella_ts; - /* NDPI_PROTOCOL_BATTLEFIELD */ - u_int32_t battlefield_ts; - /* NDPI_PROTOCOL_THUNDER */ u_int32_t thunder_ts; /* NDPI_PROTOCOL_RTSP */ u_int32_t rtsp_timer; - /* NDPI_PROTOCOL_OSCAR */ - u_int32_t oscar_last_safe_access_time; - /* NDPI_PROTOCOL_ZATTOO */ u_int32_t zattoo_ts; @@ -530,9 +576,6 @@ struct ndpi_id_struct { /* NDPI_PROTOCOL_IRC */ u_int8_t irc_number_of_port; - /* NDPI_PROTOCOL_OSCAR */ - u_int8_t oscar_ssl_session_id[33]; - /* NDPI_PROTOCOL_UNENCRYPTED_JABBER */ u_int8_t jabber_voice_stun_used_ports; @@ -623,17 +666,20 @@ struct ndpi_flow_tcp_struct { /* NDPI_PROTOCOL_TELNET */ u_int32_t telnet_stage:2; // 0 - 2 - void* tls_srv_cert_fingerprint_ctx; + struct { + struct { + u_int8_t *buffer; + u_int buffer_len, buffer_used; + } message; + + void* srv_cert_fingerprint_ctx; /* SHA-1 */ - /* NDPI_PROTOCOL_TLS */ - u_int8_t tls_seen_client_cert:1, - tls_seen_server_cert:1, - tls_seen_certificate:1, - tls_srv_cert_fingerprint_found:1, - tls_srv_cert_fingerprint_processed:1, - tls_stage:2, _pad:1; // 0 - 5 - int16_t tls_record_offset, tls_fingerprint_len; /* Need to be signed */ - u_int8_t tls_sha1_certificate_fingerprint[20]; + /* NDPI_PROTOCOL_TLS */ + u_int8_t hello_processed:1, certificate_processed:1, subprotocol_detected:1, + fingerprint_set:1, _pad:4; + u_int8_t sha1_certificate_fingerprint[20], num_tls_blocks; + int16_t tls_application_blocks_len[NDPI_MAX_NUM_TLS_APPL_BLOCKS]; /* + = src->dst, - = dst->src */ + } tls; /* NDPI_PROTOCOL_POSTGRES */ u_int32_t postgres_stage:3; @@ -671,6 +717,9 @@ struct ndpi_flow_tcp_struct { /* NDPI_PROTOCOL_MAIL_IMAP */ u_int32_t mail_imap_stage:3, mail_imap_starttls:2; + /* NDPI_PROTOCOL_SOAP */ + u_int32_t soap_stage:1; + /* NDPI_PROTOCOL_SKYPE */ u_int8_t skype_packet_id; @@ -695,24 +744,14 @@ struct ndpi_flow_tcp_struct { /* NDPI_PROTOCOL_NEST_LOG_SINK */ u_int8_t nest_log_sink_matches; -} -#ifndef WIN32 - __attribute__ ((__packed__)) -#endif - ; +}; /* ************************************************** */ struct ndpi_flow_udp_struct { - /* NDPI_PROTOCOL_BATTLEFIELD */ - u_int32_t battlefield_msg_id; - /* NDPI_PROTOCOL_SNMP */ u_int32_t snmp_msg_id; - /* NDPI_PROTOCOL_BATTLEFIELD */ - u_int32_t battlefield_stage:3; - /* NDPI_PROTOCOL_SNMP */ u_int32_t snmp_stage:2; @@ -754,11 +793,7 @@ struct ndpi_flow_udp_struct { /* NDPI_PROTOCOL_WIREGUARD */ u_int8_t wireguard_stage; u_int32_t wireguard_peer_index[2]; -} -#ifndef WIN32 - __attribute__ ((__packed__)) -#endif - ; +}; /* ************************************************** */ @@ -777,15 +812,10 @@ struct ndpi_packet_struct { const u_int8_t *generic_l4_ptr; /* is set only for non tcp-udp traffic */ const u_int8_t *payload; - u_int32_t tick_timestamp; - u_int64_t tick_timestamp_l; + u_int64_t current_time_ms; u_int16_t detected_protocol_stack[NDPI_PROTOCOL_SIZE]; u_int8_t detected_subprotocol_stack[NDPI_PROTOCOL_SIZE]; - -#ifndef WIN32 - __attribute__ ((__packed__)) -#endif u_int16_t protocol_stack_info; struct ndpi_int_one_line_struct line[NDPI_MAX_PARSE_LINES_PER_PACKET]; @@ -794,6 +824,7 @@ struct ndpi_packet_struct { struct ndpi_int_one_line_struct forwarded_line; struct ndpi_int_one_line_struct referer_line; struct ndpi_int_one_line_struct content_line; + struct ndpi_int_one_line_struct content_disposition_line; struct ndpi_int_one_line_struct accept_line; struct ndpi_int_one_line_struct user_agent_line; struct ndpi_int_one_line_struct http_url_name; @@ -829,6 +860,7 @@ struct ndpi_detection_module_struct; struct ndpi_flow_struct; struct ndpi_call_function_struct { + u_int16_t ndpi_protocol_id; NDPI_PROTOCOL_BITMASK detection_bitmask; NDPI_PROTOCOL_BITMASK excluded_protocol_bitmask; NDPI_SELECTION_BITMASK_PROTOCOL_SIZE ndpi_selection_bitmask; @@ -893,6 +925,12 @@ typedef enum { NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_CATEGORY_PRODUCTIVITY, NDPI_PROTOCOL_CATEGORY_FILE_SHARING, + /* + The category below is used by sites who are used + to test connectivity + */ + NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK, + NDPI_PROTOCOL_CATEGORY_IOT_SCADA, /* Some custom categories */ CUSTOM_CATEGORY_MINING = 99, @@ -926,10 +964,8 @@ typedef enum { } ndpi_protocol_category_t; typedef enum { - ndpi_pref_http_dont_dissect_response = 0, - ndpi_pref_dns_dont_dissect_response, - ndpi_pref_direction_detect_disable, - ndpi_pref_disable_metadata_export, + ndpi_pref_direction_detect_disable = 0, + ndpi_pref_enable_tls_block_dissection } ndpi_detection_preference; /* ntop extensions */ @@ -939,6 +975,7 @@ typedef struct ndpi_proto_defaults { u_int8_t can_have_a_subprotocol; u_int16_t protoId, protoIdx; u_int16_t master_tcp_protoId[2], master_udp_protoId[2]; /* The main protocols on which this sub-protocol sits on */ + u_int16_t tcp_default_ports[MAX_DEFAULT_PORTS], udp_default_ports[MAX_DEFAULT_PORTS]; ndpi_protocol_breed_t protoBreed; void (*func) (struct ndpi_detection_module_struct *, struct ndpi_flow_struct *flow); } ndpi_proto_defaults_t; @@ -964,7 +1001,7 @@ typedef struct ndpi_proto { ndpi_protocol_category_t category; } ndpi_protocol; -#define NDPI_PROTOCOL_NULL { NDPI_PROTOCOL_UNKNOWN , NDPI_PROTOCOL_UNKNOWN } +#define NDPI_PROTOCOL_NULL { NDPI_PROTOCOL_UNKNOWN , NDPI_PROTOCOL_UNKNOWN , NDPI_PROTOCOL_CATEGORY_UNSPECIFIED } #define NUM_CUSTOM_CATEGORIES 5 #define CUSTOM_CATEGORY_LABEL_LEN 32 @@ -974,28 +1011,28 @@ typedef struct ndpi_proto { /* Needed to have access to HAVE_* defines */ #include "ndpi_config.h" -#ifdef HAVE_HYPERSCAN -#include - -struct hs_list { - char *expression; - unsigned int id; - struct hs_list *next; -}; +#ifdef HAVE_PCRE +#include -struct hs { - hs_database_t *database; - hs_scratch_t *scratch; +struct pcre_struct { + pcre *compiled; + pcre_extra *optimized; }; #endif +typedef enum { + ndpi_stun_cache, + ndpi_hangout_cache +} ndpi_lru_cache_type; + struct ndpi_detection_module_struct { NDPI_PROTOCOL_BITMASK detection_bitmask; NDPI_PROTOCOL_BITMASK generic_http_packet_bitmask; u_int32_t current_ts; u_int32_t ticks_per_second; - + u_int16_t num_tls_blocks_to_follow; + #ifdef NDPI_ENABLE_DEBUG_MESSAGES void *user_data; #endif @@ -1045,15 +1082,10 @@ struct ndpi_detection_module_struct { content_automa, /* Used for HTTP subprotocol_detection */ subprotocol_automa, /* Used for HTTP subprotocol_detection */ bigrams_automa, impossible_bigrams_automa; /* TOR */ - + /* IMPORTANT: please update ndpi_finalize_initalization() whenever you add a new automa */ + struct { -#ifdef HAVE_HYPERSCAN - struct hs *hostnames; - unsigned int num_to_load; - struct hs_list *to_load; -#else ndpi_automa hostnames, hostnames_shadow; -#endif void *ipAddresses, *ipAddresses_shadow; /* Patricia */ u_int8_t categories_loaded; } custom_categories; @@ -1065,16 +1097,12 @@ struct ndpi_detection_module_struct { u_int32_t irc_timeout; /* gnutella parameters */ u_int32_t gnutella_timeout; - /* battlefield parameters */ - u_int32_t battlefield_timeout; /* thunder parameters */ u_int32_t thunder_timeout; /* SoulSeek parameters */ u_int32_t soulseek_connection_ip_tick_timeout; /* rtsp parameters */ u_int32_t rtsp_connection_timeout; - /* tvants parameters */ - u_int32_t tvants_connection_timeout; /* rstp */ u_int32_t orb_rstp_ts_timeout; /* yahoo */ @@ -1103,14 +1131,18 @@ struct ndpi_detection_module_struct { /* NDPI_PROTOCOL_STUN and subprotocols */ struct ndpi_lru_cache *stun_cache; + /* NDPI_PROTOCOL_MSTEAMS */ + struct ndpi_lru_cache *msteams_cache; + ndpi_proto_defaults_t proto_defaults[NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS]; - u_int8_t http_dont_dissect_response:1, dns_dont_dissect_response:1, - direction_detect_disable:1, /* disable internal detection of packet direction */ - disable_metadata_export:1 /* No metadata is exported */ - ; + u_int8_t direction_detect_disable:1, /* disable internal detection of packet direction */ _pad:7; - void *hyperscan; /* Intel Hyperscan */ + void (*ndpi_notify_lru_add_handler_ptr)(ndpi_lru_cache_type cache_type, u_int32_t proto, u_int32_t app_proto); + +#ifdef CUSTOM_NDPI_PROTOCOLS + #include "../../../nDPI-custom/custom_ndpi_typedefs.h" +#endif }; #endif /* NDPI_LIB_COMPILATION */ @@ -1121,16 +1153,19 @@ typedef enum { ndpi_cipher_insecure = NDPI_CIPHER_INSECURE } ndpi_cipher_weakness; +/* + NOTE + When the struct below is modified don't forget to update + - ndpi_flow_struct (in python/ndpi.py) + */ struct ndpi_flow_struct { u_int16_t detected_protocol_stack[NDPI_PROTOCOL_SIZE]; -#ifndef WIN32 - __attribute__ ((__packed__)) -#endif u_int16_t protocol_stack_info; /* init parameter, internal used to set up timestamp,... */ u_int16_t guessed_protocol_id, guessed_host_protocol_id, guessed_category, guessed_header_category; - u_int8_t l4_proto, protocol_id_already_guessed:1, host_already_guessed:1, init_finished:1, setup_packet_direction:1, packet_direction:1, check_extra_packets:1; + u_int8_t l4_proto, protocol_id_already_guessed:1, host_already_guessed:1, fail_with_unknown:1, + init_finished:1, setup_packet_direction:1, packet_direction:1, check_extra_packets:1; /* if ndpi_struct->direction_detect_disable == 1 @@ -1153,14 +1188,20 @@ struct ndpi_flow_struct { struct ndpi_flow_udp_struct udp; } l4; + /* Place textual flow info here */ + char flow_extra_info[16]; + /* Pointer to src or dst that identifies the server of this connection */ struct ndpi_id_struct *server_id; /* HTTP host or DNS query */ - u_char host_server_name[256]; - + u_char host_server_name[240]; + u_int8_t initial_binary_bytes[8], initial_binary_bytes_len; + u_int8_t risk_checked; + ndpi_risk risk; /* Issues found with this flow [bitmask of ndpi_risk] */ + /* This structure below will not not stay inside the protos structure below as HTTP is used by many subprotocols @@ -1170,12 +1211,22 @@ struct ndpi_flow_struct { */ struct { ndpi_http_method method; - char *url, *content_type; + char *url, *content_type, *user_agent; u_int8_t num_request_headers, num_response_headers; u_int8_t request_version; /* 0=1.0 and 1=1.1. Create an enum for this? */ u_int16_t response_status_code; /* 200, 404, etc. */ } http; + /* + Put outside of the union to avoid issues in case the protocol + is remapped to somethign pther than Kerberos due to a faulty + dissector + */ + struct { + char *pktbuf; + u_int16_t pktbuf_maxlen, pktbuf_currlen; + } kerberos_buf; + union { /* the only fields useful for nDPI and ntopng */ struct { @@ -1190,16 +1241,23 @@ struct ndpi_flow_struct { } ntp; struct { - char cname[24], realm[24]; + char hostname[48], domain[48], username[48]; } kerberos; struct { struct { - u_int16_t ssl_version; - char client_certificate[64], server_certificate[64], server_organization[64]; + char ssl_version_str[12]; + u_int16_t ssl_version, server_names_len; + char client_requested_server_name[64], *server_names, + *alpn, *tls_supported_versions, *issuerDN, *subjectDN; u_int32_t notBefore, notAfter; char ja3_client[33], ja3_server[33]; u_int16_t server_cipher; + + struct { + u_int16_t cipher_suite; + char *esni; + } encrypted_sni; ndpi_cipher_weakness server_unsafe_cipher; } ssl; @@ -1220,9 +1278,13 @@ struct ndpi_flow_struct { } imo; struct { - char answer[96]; - } mdns; - + u_int8_t username_detected:1, username_found:1, + password_detected:1, password_found:1, + _pad:4; + u_int8_t character_id; + char username[32], password[32]; + } telnet; + struct { char version[32]; } ubntac2; @@ -1234,6 +1296,11 @@ struct ndpi_flow_struct { u_char nat_ip[24]; } http; + struct { + u_int8_t auth_found:1, auth_failed:1, _pad:5; + char username[16], password[16]; + } ftp_imap_pop_smtp; + struct { /* Bittorrent hash */ u_char hash[20]; @@ -1264,12 +1331,8 @@ struct ndpi_flow_struct { /* NDPI_PROTOCOL_DIRECTCONNECT */ u_int8_t directconnect_stage:2; // 0 - 1 - /* NDPI_PROTOCOL_YAHOO */ - u_int8_t sip_yahoo_voice:1; - /* NDPI_PROTOCOL_HTTP */ u_int8_t http_detected:1; - u_int16_t http_upper_protocol, http_lower_protocol; /* NDPI_PROTOCOL_RTSP */ u_int8_t rtsprdt_stage:2, rtsp_control_flow:1; @@ -1286,9 +1349,6 @@ struct ndpi_flow_struct { /* NDPI_PROTOCOL_THUNDER */ u_int8_t thunder_stage:2; // 0 - 3 - /* NDPI_PROTOCOL_OSCAR */ - u_int8_t oscar_ssl_voice_stage:3, oscar_video_voice:1; - /* NDPI_PROTOCOL_FLORENSIA */ u_int8_t florensia_stage:1; @@ -1304,15 +1364,9 @@ struct ndpi_flow_struct { /* NDPI_PROTOCOL_RTMP */ u_int8_t rtmp_stage:2; - /* NDPI_PROTOCOL_PANDO */ - u_int8_t pando_stage:3; - /* NDPI_PROTOCOL_STEAM */ u_int16_t steam_stage:3, steam_stage1:3, steam_stage2:2, steam_stage3:2; - /* NDPI_PROTOCOL_PPLIVE */ - u_int8_t pplive_stage1:3, pplive_stage2:2, pplive_stage3:2; - /* NDPI_PROTOCOL_STARCRAFT */ u_int8_t starcraft_udp_stage : 3; // 0-7 @@ -1327,10 +1381,7 @@ struct ndpi_flow_struct { /* NDPI_PROTOCOL_CSGO */ u_int8_t csgo_strid[18],csgo_state,csgo_s2; u_int32_t csgo_id2; - - /* NDPI_PROTOCOL_1KXUN || NDPI_PROTOCOL_IQIYI */ - u_int16_t kxun_counter, iqiyi_counter; - + /* internal structures to save functions calls */ struct ndpi_packet_struct packet; struct ndpi_flow_struct *flow; @@ -1339,14 +1390,14 @@ struct ndpi_flow_struct { }; typedef struct { - char *string_to_match, *string2_to_match, *pattern_to_match, *proto_name; + char *string_to_match, *proto_name; int protocol_id; ndpi_protocol_category_t protocol_category; ndpi_protocol_breed_t protocol_breed; } ndpi_protocol_match; typedef struct { - char *string_to_match, *hyperscan_string_to_match; + char *string_to_match; ndpi_protocol_category_t protocol_category; } ndpi_category_match; @@ -1356,6 +1407,15 @@ typedef struct { u_int8_t value; } ndpi_network; +typedef u_int32_t ndpi_init_prefs; + +typedef enum + { + ndpi_no_prefs = 0, + ndpi_dont_load_tor_hosts, + ndpi_dont_init_libgcrypt, + } ndpi_prefs; + typedef struct { int protocol_id; ndpi_protocol_category_t protocol_category; @@ -1369,8 +1429,9 @@ typedef enum { ndpi_serialization_format_csv } ndpi_serialization_format; -/* Note: key supports string and uint32 (compressed to uint8/uint16) only, - * this is also enforced by the API */ +/* Note: + * - up to 16 types (TLV encoding: "4 bit key type" << 4 | "4 bit value type") + * - key supports string and uint32 (compressed to uint8/uint16) only, this is also enforced by the API */ typedef enum { ndpi_serialization_unknown = 0, ndpi_serialization_end_of_record, @@ -1383,28 +1444,47 @@ typedef enum { ndpi_serialization_int32, ndpi_serialization_int64, ndpi_serialization_float, - ndpi_serialization_string + ndpi_serialization_string, + ndpi_serialization_start_of_block, + ndpi_serialization_end_of_block, + ndpi_serialization_start_of_list, + ndpi_serialization_end_of_list } ndpi_serialization_type; +#define NDPI_SERIALIZER_DEFAULT_HEADER_SIZE 1024 #define NDPI_SERIALIZER_DEFAULT_BUFFER_SIZE 8192 #define NDPI_SERIALIZER_DEFAULT_BUFFER_INCR 1024 -#define NDPI_SERIALIZER_STATUS_COMMA (1 << 0) -#define NDPI_SERIALIZER_STATUS_ARRAY (1 << 1) -#define NDPI_SERIALIZER_STATUS_EOR (1 << 2) -#define NDPI_SERIALIZER_STATUS_SOB (1 << 3) +#define NDPI_SERIALIZER_STATUS_COMMA (1 << 0) +#define NDPI_SERIALIZER_STATUS_ARRAY (1 << 1) +#define NDPI_SERIALIZER_STATUS_EOR (1 << 2) +#define NDPI_SERIALIZER_STATUS_SOB (1 << 3) +#define NDPI_SERIALIZER_STATUS_NOT_EMPTY (1 << 4) +#define NDPI_SERIALIZER_STATUS_LIST (1 << 5) +#define NDPI_SERIALIZER_STATUS_SOL (1 << 6) +#define NDPI_SERIALIZER_STATUS_HDR_DONE (1 << 7) typedef struct { - u_int32_t flags; u_int32_t size_used; +} ndpi_private_serializer_buffer_status; + +typedef struct { + u_int32_t flags; + ndpi_private_serializer_buffer_status buffer; + ndpi_private_serializer_buffer_status header; } ndpi_private_serializer_status; +typedef struct { + u_int32_t initial_size; + u_int32_t size; + u_int8_t *data; +} ndpi_private_serializer_buffer; + typedef struct { ndpi_private_serializer_status status; - u_int32_t initial_buffer_size; - u_int32_t buffer_size; + ndpi_private_serializer_buffer buffer; + ndpi_private_serializer_buffer header; ndpi_serialization_format fmt; - u_int8_t *buffer; char csv_separator[2]; u_int8_t has_snapshot; ndpi_private_serializer_status snapshot; @@ -1426,11 +1506,10 @@ typedef struct { struct ndpi_analyze_struct { u_int32_t *values; u_int32_t min_val, max_val, sum_total, num_data_entries, next_value_insert_index; - u_int16_t num_values_array_len /* lenght of the values array */; + u_int16_t num_values_array_len /* length of the values array */; struct { - /* https://www.johndcook.com/blog/standard_deviation/ */ - float mu, q; + u_int64_t sum_square_total; } stddev; }; @@ -1438,4 +1517,35 @@ struct ndpi_analyze_struct { #define MAX_SERIES_LEN 512 #define MIN_SERIES_LEN 8 +/* **************************************** */ + +typedef struct ndpi_ptree ndpi_ptree_t; + +/* **************************************** */ + +struct ndpi_hll { + u_int8_t bits; + size_t size; + u_int8_t *registers; +}; + +/* **************************************** */ + +enum ndpi_bin_family { + ndpi_bin_family8, + ndpi_bin_family16, + ndpi_bin_family32 +}; + +struct ndpi_bin { + u_int8_t num_bins, is_empty; + enum ndpi_bin_family family; + + union { + u_int8_t *bins8; /* num_bins bins */ + u_int16_t *bins16; /* num_bins bins */ + u_int32_t *bins32; /* num_bins bins */ + } u; +}; + #endif /* __NDPI_TYPEDEFS_H__ */ diff --git a/src/include/ndpi_win32.h b/src/include/ndpi_win32.h index db309fa..c3403c0 100644 --- a/src/include/ndpi_win32.h +++ b/src/include/ndpi_win32.h @@ -31,6 +31,8 @@ #define __mingw_forceinline __inline__ __attribute__((__always_inline__,__gnu_inline__)) #endif +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_WIN8 #include #include #include @@ -39,6 +41,7 @@ #include /* getopt from: http://www.pwilson.net/sample.html. */ #include /* for getpid() and the exec..() family */ #include +#include #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS @@ -48,6 +51,17 @@ #define IPVERSION 4 /* on *nix it is defined in netinet/ip.h */ +#ifndef MIN +#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) +#endif + +#ifndef IPPROTO_SCTP +#define IPPROTO_SCTP 132 +#endif + +#undef gettimeofday +#define gettimeofday mingw_gettimeofday + extern char* strsep(char **sp, char *sep); typedef unsigned char u_char; @@ -75,6 +89,15 @@ typedef unsigned __int64 u_int64_t; extern unsigned long waitForNextEvent(unsigned long ulDelay /* ms */); -#define sleep(a /* sec */) waitForNextEvent(1000*a /* ms */) +#define sleep(a /* sec */) waitForNextEvent(1000*a /* ms */) +#define strtok_r strtok_s +#define timegm _mkgmtime + +static inline struct tm * localtime_r(const time_t *timep, struct tm * result) +{ + struct tm *timeinfo; + timeinfo = localtime(timep); + return timeinfo; +} #endif /* __NDPI_WIN32_H__ */ diff --git a/src/lib/Makefile.in b/src/lib/Makefile.in index 1a884ac..6c69485 100644 --- a/src/lib/Makefile.in +++ b/src/lib/Makefile.in @@ -14,7 +14,8 @@ prefix = @prefix@ libdir = ${prefix}/lib includedir = ${prefix}/include/ndpi CC = @CC@ -CFLAGS += -fPIC -DPIC -I../include -Ithird_party/include -DNDPI_LIB_COMPILATION -O2 -g -Wall +CFLAGS += -fPIC -DPIC -I../include -Ithird_party/include -DNDPI_LIB_COMPILATION -Wall @CFLAGS@ @CUSTOM_NDPI@ +LDFLAGS = @LDFLAGS@ @ADDITIONAL_LIBS@ @LIBS@ RANLIB = ranlib OBJECTS = $(patsubst protocols/%.c, protocols/%.o, $(wildcard protocols/*.c)) $(patsubst third_party/src/%.c, third_party/src/%.o, $(wildcard third_party/src/*.c)) $(patsubst ./%.c, ./%.o, $(wildcard ./*.c)) @@ -28,13 +29,19 @@ NDPI_LIBS = $(NDPI_LIB_STATIC) $(NDPI_LIB_SHARED) ifneq ($(OS),Windows_NT) OS := $(shell uname) endif +BUILD_MINGW = @BUILD_MINGW@ ifeq ($(OS),Darwin) CC=clang SONAME_FLAG= else +ifneq ($(BUILD_MINGW),) +NDPI_LIB_SHARED_BASE = libndpi +NDPI_LIB_SHARED = $(NDPI_LIB_SHARED_BASE)-@NDPI_VERSION_SHORT@.dll +else SONAME_FLAG=-Wl,-soname,$(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR) endif +endif all: $(NDPI_LIBS) @@ -42,10 +49,10 @@ ndpi_main.c: ndpi_content_match.c.inc $(NDPI_LIB_STATIC): $(OBJECTS) ar rc $@ $(OBJECTS) - $(RANLIB) $@ + $(RANLIB) $@ $(NDPI_LIB_SHARED): $(OBJECTS) - $(CC) -shared -fPIC $(SONAME_FLAG) -o $@ $(OBJECTS) + $(CC) -shared -fPIC $(SONAME_FLAG) -o $@ $(OBJECTS) $(LDFLAGS) ln -fs $(NDPI_LIB_SHARED) $(NDPI_LIB_SHARED_BASE) ln -fs $(NDPI_LIB_SHARED) $(NDPI_LIB_SHARED_BASE).$(NDPI_VERSION_MAJOR) @@ -53,7 +60,13 @@ $(NDPI_LIB_SHARED): $(OBJECTS) $(CC) $(CFLAGS) -c $< -o $@ clean: - /bin/rm -f $(NDPI_LIB_STATIC) $(OBJECTS) *.o *.so *.lo $(NDPI_LIB_SHARED) + /bin/rm -f $(NDPI_LIB_STATIC) $(OBJECTS) *.o *.so *.lo libndpi.so* + +distdir: + cp ndpi_content_match.c.inc '$(distdir)/' + find . -type d | xargs -I'{}' mkdir -p '$(distdir)/{}' + find ../include -type f -name '*.h' | xargs -I'{}' cp '{}' '$(distdir)/{}' + find . -type f -name '*.c' -o -name '*.h' | xargs -I'{}' cp '{}' '$(distdir)/{}' distclean: clean /bin/rm -f Makefile diff --git a/src/lib/ndpi_analyze.c b/src/lib/ndpi_analyze.c index d1335bb..37e3147 100644 --- a/src/lib/ndpi_analyze.c +++ b/src/lib/ndpi_analyze.c @@ -40,7 +40,7 @@ void ndpi_init_data_analysis(struct ndpi_analyze_struct *ret, u_int16_t _max_ser u_int32_t len; memset(ret, 0, sizeof(struct ndpi_analyze_struct)); - + if(_max_series_len > MAX_SERIES_LEN) _max_series_len = MAX_SERIES_LEN; ret->num_values_array_len = _max_series_len; @@ -61,8 +61,8 @@ struct ndpi_analyze_struct* ndpi_alloc_data_analysis(u_int16_t _max_series_len) struct ndpi_analyze_struct *ret = ndpi_malloc(sizeof(struct ndpi_analyze_struct)); if(ret != NULL) - ndpi_init_data_analysis(ret, _max_series_len); - + ndpi_init_data_analysis(ret, _max_series_len); + return(ret); } @@ -75,12 +75,18 @@ void ndpi_free_data_analysis(struct ndpi_analyze_struct *d) { /* ********************************************************************************* */ +void ndpi_reset_data_analysis(struct ndpi_analyze_struct *d) { + memset(d, 0, sizeof(struct ndpi_analyze_struct)); + memset(d->values, 0, sizeof(u_int32_t)*d->num_values_array_len); + d->num_data_entries = 0; +} + +/* ********************************************************************************* */ + /* Add a new point to analyze */ void ndpi_data_add_value(struct ndpi_analyze_struct *s, const u_int32_t value) { - float tmp_mu; - if(s->sum_total == 0) s->min_val = s->max_val = value; else { @@ -89,18 +95,22 @@ void ndpi_data_add_value(struct ndpi_analyze_struct *s, const u_int32_t value) { } s->sum_total += value, s->num_data_entries++; - + if(s->num_values_array_len) { s->values[s->next_value_insert_index] = value; if(++s->next_value_insert_index == s->num_values_array_len) s->next_value_insert_index = 0; } - - /* Update stddev */ - tmp_mu = s->stddev.mu; - s->stddev.mu = ((s->stddev.mu * (s->num_data_entries - 1)) + value) / s->num_data_entries; - s->stddev.q = s->stddev.q + (value - tmp_mu)*(value - s->stddev.mu); + + /* + Optimized stddev calculation + + https://www.khanacademy.org/math/probability/data-distributions-a1/summarizing-spread-distributions/a/calculating-standard-deviation-step-by-step + https://math.stackexchange.com/questions/683297/how-to-calculate-standard-deviation-without-detailed-historical-data + http://mathcentral.uregina.ca/QQ/database/QQ.09.02/carlos1.html + */ + s->stddev.sum_square_total += value * value; } /* ********************************************************************************* */ @@ -112,6 +122,16 @@ float ndpi_data_average(struct ndpi_analyze_struct *s) { /* ********************************************************************************* */ +u_int32_t ndpi_data_last(struct ndpi_analyze_struct *s) { + if((s->num_data_entries == 0) || (s->sum_total == 0)) + return(0); + + if(s->next_value_insert_index == 0) + return(s->values[s->num_values_array_len-1]); + else + return(s->values[s->next_value_insert_index-1]); +} + /* Return min/max on all values */ u_int32_t ndpi_data_min(struct ndpi_analyze_struct *s) { return(s->min_val); } u_int32_t ndpi_data_max(struct ndpi_analyze_struct *s) { return(s->max_val); } @@ -120,11 +140,17 @@ u_int32_t ndpi_data_max(struct ndpi_analyze_struct *s) { return(s->max_val); } /* Compute the variance on all values */ float ndpi_data_variance(struct ndpi_analyze_struct *s) { - return(s->num_data_entries ? (s->stddev.q / s->num_data_entries) : 0); + return(s->num_data_entries ? (float)(s->stddev.sum_square_total - ((s->sum_total * s->sum_total) / (s->num_data_entries))) / (float)s->num_data_entries : 0); } /* ********************************************************************************* */ +/* + See the link below for "Population and sample standard deviation review" + https://www.khanacademy.org/math/statistics-probability/summarizing-quantitative-data/variance-standard-deviation-sample/a/population-and-sample-standard-deviation-review + + In nDPI we use an approximate stddev calculation to avoid storing all data in memory +*/ /* Compute the standard deviation on all values */ float ndpi_data_stddev(struct ndpi_analyze_struct *s) { return(sqrt(ndpi_data_variance(s))); @@ -137,10 +163,13 @@ float ndpi_data_window_average(struct ndpi_analyze_struct *s) { if(s->num_values_array_len) { float sum = 0.0; u_int16_t i, n = ndpi_min(s->num_data_entries, s->num_values_array_len); - + + if(n == 0) + return(0); + for(i=0; ivalues[i]; - + return((float)sum / (float)n); } else return(0); @@ -148,6 +177,32 @@ float ndpi_data_window_average(struct ndpi_analyze_struct *s) { /* ********************************************************************************* */ +/* Compute the variance only on the sliding window */ +float ndpi_data_window_variance(struct ndpi_analyze_struct *s) { + if(s->num_values_array_len) { + float sum = 0.0, avg = ndpi_data_window_average(s); + u_int16_t i, n = ndpi_min(s->num_data_entries, s->num_values_array_len); + + if(n == 0) + return(0); + + for(i=0; ivalues[i]-avg, 2); + + return((float)sum / (float)n); + } else + return(0); +} + +/* ********************************************************************************* */ + +/* Compute the variance only on the sliding window */ +float ndpi_data_window_stddev(struct ndpi_analyze_struct *s) { + return(sqrt(ndpi_data_window_variance(s))); +} + + /* ********************************************************************************* */ + /* Compute entropy on the last sliding window values */ @@ -155,17 +210,17 @@ float ndpi_data_entropy(struct ndpi_analyze_struct *s) { if(s->num_values_array_len) { int i; float sum = 0.0, total = 0.0; - + for(i=0; inum_values_array_len; i++) total += s->values[i]; - + for (i=0; inum_values_array_len; i++) { float tmp = (float)s->values[i] / (float)total; - + if(tmp > FLT_EPSILON) - sum -= tmp * logf(tmp); + sum -= tmp * logf(tmp); } - + return(sum / logf(2.0)); } else return(0); @@ -176,10 +231,10 @@ float ndpi_data_entropy(struct ndpi_analyze_struct *s) { void ndpi_data_print_window_values(struct ndpi_analyze_struct *s) { if(s->num_values_array_len) { u_int16_t i, n = ndpi_min(s->num_data_entries, s->num_values_array_len); - + for(i=0; ivalues[i]); - + printf("\n"); } } @@ -196,12 +251,567 @@ void ndpi_data_print_window_values(struct ndpi_analyze_struct *s) { float ndpi_data_ratio(u_int32_t sent, u_int32_t rcvd) { float s = (float)((int64_t)sent + (int64_t)rcvd); float d = (float)((int64_t)sent - (int64_t)rcvd); - + return((s == 0) ? 0 : (d/s)); } +/* ********************************************************************************* */ + const char* ndpi_data_ratio2str(float ratio) { if(ratio < -0.2) return("Download"); else if(ratio > 0.2) return("Upload"); else return("Mixed"); } + +/* ********************************************************************************* */ +/* ********************************************************************************* */ + +#include "third_party/src/hll/hll.c" +#include "third_party/src/hll/MurmurHash3.c" + +int ndpi_hll_init(struct ndpi_hll *hll, u_int8_t bits) { + return(hll_init(hll, bits)); +} + +void ndpi_hll_destroy(struct ndpi_hll *hll) { + hll_destroy(hll); +} + +void ndpi_hll_reset(struct ndpi_hll *hll) { + hll_reset(hll); +} + +void ndpi_hll_add(struct ndpi_hll *hll, const char *data, size_t data_len) { + hll_add(hll, (const void *)data, data_len); +} + +void ndpi_hll_add_number(struct ndpi_hll *hll, u_int32_t value) { + hll_add(hll, (const void *)&value, sizeof(value)); +} + +double ndpi_hll_count(struct ndpi_hll *hll) { + return(hll_count(hll)); +} + +/* ********************************************************************************* */ +/* ********************************************************************************* */ + +int ndpi_init_bin(struct ndpi_bin *b, enum ndpi_bin_family f, u_int8_t num_bins) { + b->num_bins = num_bins, b->family = f, b->is_empty = 1; + + switch(f) { + case ndpi_bin_family8: + if((b->u.bins8 = (u_int8_t*)ndpi_calloc(num_bins, sizeof(u_int8_t))) == NULL) + return(-1); + break; + + case ndpi_bin_family16: + if((b->u.bins16 = (u_int16_t*)ndpi_calloc(num_bins, sizeof(u_int16_t))) == NULL) + return(-1); + break; + + case ndpi_bin_family32: + if((b->u.bins32 = (u_int32_t*)ndpi_calloc(num_bins, sizeof(u_int32_t))) == NULL) + return(-1); + break; + } + + return(0); +} + +/* ********************************************************************************* */ + +void ndpi_free_bin(struct ndpi_bin *b) { + switch(b->family) { + case ndpi_bin_family8: + free(b->u.bins8); + break; + case ndpi_bin_family16: + free(b->u.bins16); + break; + case ndpi_bin_family32: + free(b->u.bins32); + break; + } +} + +/* ********************************************************************************* */ + +struct ndpi_bin* ndpi_clone_bin(struct ndpi_bin *b) { + struct ndpi_bin *out = (struct ndpi_bin*)ndpi_malloc(sizeof(struct ndpi_bin)); + + if(!out) return(NULL); + + out->num_bins = b->num_bins, out->family = b->family, out->is_empty = b->is_empty; + + switch(out->family) { + case ndpi_bin_family8: + if((out->u.bins8 = (u_int8_t*)ndpi_calloc(out->num_bins, sizeof(u_int8_t))) == NULL) { + free(out); + return(NULL); + } else + memcpy(out->u.bins8, b->u.bins8, out->num_bins*sizeof(u_int8_t)); + break; + + case ndpi_bin_family16: + if((out->u.bins16 = (u_int16_t*)ndpi_calloc(out->num_bins, sizeof(u_int16_t))) == NULL) { + free(out); + return(NULL); + } else + memcpy(out->u.bins16, b->u.bins16, out->num_bins*sizeof(u_int16_t)); + break; + + case ndpi_bin_family32: + if((out->u.bins32 = (u_int32_t*)ndpi_calloc(out->num_bins, sizeof(u_int32_t))) == NULL) { + free(out); + return(NULL); + } else + memcpy(out->u.bins32, b->u.bins32, out->num_bins*sizeof(u_int32_t)); + break; + } + + return(out); +} + +/* ********************************************************************************* */ + +void ndpi_set_bin(struct ndpi_bin *b, u_int8_t slot_id, u_int32_t val) { + if(slot_id >= b->num_bins) slot_id = 0; + + switch(b->family) { + case ndpi_bin_family8: + b->u.bins8[slot_id] = (u_int8_t)val; + break; + case ndpi_bin_family16: + b->u.bins16[slot_id] = (u_int16_t)val; + break; + case ndpi_bin_family32: + b->u.bins32[slot_id] = (u_int32_t)val; + break; + } +} + +/* ********************************************************************************* */ + +void ndpi_inc_bin(struct ndpi_bin *b, u_int8_t slot_id, u_int32_t val) { + b->is_empty = 0; + + if(slot_id >= b->num_bins) slot_id = 0; + + switch(b->family) { + case ndpi_bin_family8: + b->u.bins8[slot_id] += (u_int8_t)val; + break; + case ndpi_bin_family16: + b->u.bins16[slot_id] += (u_int16_t)val; + break; + case ndpi_bin_family32: + b->u.bins32[slot_id] += (u_int32_t)val; + break; + } +} + +/* ********************************************************************************* */ + +u_int32_t ndpi_get_bin_value(struct ndpi_bin *b, u_int8_t slot_id) { + if(slot_id >= b->num_bins) slot_id = 0; + + switch(b->family) { + case ndpi_bin_family8: + return(b->u.bins8[slot_id]); + break; + case ndpi_bin_family16: + return(b->u.bins16[slot_id]); + break; + case ndpi_bin_family32: + return(b->u.bins32[slot_id]); + break; + } + + return(0); +} + +/* ********************************************************************************* */ + +void ndpi_reset_bin(struct ndpi_bin *b) { + b->is_empty = 1; + + switch(b->family) { + case ndpi_bin_family8: + memset(b->u.bins8, 0, sizeof(u_int8_t)*b->num_bins); + break; + case ndpi_bin_family16: + memset(b->u.bins16, 0, sizeof(u_int16_t)*b->num_bins); + break; + case ndpi_bin_family32: + memset(b->u.bins32, 0, sizeof(u_int32_t)*b->num_bins); + break; + } +} +/* ********************************************************************************* */ + +/* + Each bin slot is transformed in a % with respect to the value total + */ +void ndpi_normalize_bin(struct ndpi_bin *b) { + u_int8_t i; + u_int32_t tot = 0; + + if(b->is_empty) return; + + switch(b->family) { + case ndpi_bin_family8: + for(i=0; inum_bins; i++) tot += b->u.bins8[i]; + + if(tot > 0) { + for(i=0; inum_bins; i++) + b->u.bins8[i] = (b->u.bins8[i]*100) / tot; + } + break; + case ndpi_bin_family16: + for(i=0; inum_bins; i++) tot += b->u.bins16[i]; + + if(tot > 0) { + for(i=0; inum_bins; i++) + b->u.bins16[i] = (b->u.bins16[i]*100) / tot; + } + break; + case ndpi_bin_family32: + for(i=0; inum_bins; i++) tot += b->u.bins32[i]; + + if(tot > 0) { + for(i=0; inum_bins; i++) + b->u.bins32[i] = (b->u.bins32[i]*100) / tot; + } + break; + } +} + +/* ********************************************************************************* */ + +char* ndpi_print_bin(struct ndpi_bin *b, u_int8_t normalize_first, char *out_buf, u_int out_buf_len) { + u_int8_t i; + u_int len = 0; + + if(!out_buf) return(out_buf); else out_buf[0] = '\0'; + + if(normalize_first) + ndpi_normalize_bin(b); + + switch(b->family) { + case ndpi_bin_family8: + for(i=0; inum_bins; i++) { + int rc = snprintf(&out_buf[len], out_buf_len-len, "%s%u", (i > 0) ? "," : "", b->u.bins8[i]); + + if(rc < 0) break; + len += rc; + } + break; + + case ndpi_bin_family16: + for(i=0; inum_bins; i++) { + int rc = snprintf(&out_buf[len], out_buf_len-len, "%s%u", (i > 0) ? "," : "", b->u.bins16[i]); + + if(rc < 0) break; + len += rc; + } + break; + + case ndpi_bin_family32: + for(i=0; inum_bins; i++) { + int rc = snprintf(&out_buf[len], out_buf_len-len, "%s%u", (i > 0) ? "," : "", b->u.bins32[i]); + + if(rc < 0) break; + len += rc; + } + break; + } + + return(out_buf); +} + +/* ********************************************************************************* */ + +// #define COSINE_SIMILARITY + +/* + Determines how similar are two bins + + Cosine Similiarity + 0 = Very differet + ... (gray zone) + 1 = Alike + + See https://en.wikipedia.org/wiki/Cosine_similarity for more details + + --- + Euclidean similarity + + 0 = alike + ... + the higher the more different +*/ +float ndpi_bin_similarity(struct ndpi_bin *b1, struct ndpi_bin *b2, u_int8_t normalize_first) { + u_int8_t i; + + if( + // (b1->family != b2->family) || + (b1->num_bins != b2->num_bins)) + return(-1); + + if(normalize_first) + ndpi_normalize_bin(b1), ndpi_normalize_bin(b2); + +#ifdef COSINE_SIMILARITY + { + u_int32_t sumxx = 0, sumxy = 0, sumyy = 0; + + for(i=0; inum_bins; i++) { + u_int32_t a = ndpi_get_bin_value(b1, i); + u_int32_t b = ndpi_get_bin_value(b2, i); + + sumxx += a*a, sumyy += b*b, sumxy += a*b; + } + + if((sumxx == 0) || (sumyy == 0)) + return(0); + else + return((float)sumxy / sqrt((float)(sumxx * sumyy))); + } +#else + { + u_int32_t sum = 0; + + for(i=0; inum_bins; i++) { + u_int32_t a = ndpi_get_bin_value(b1, i); + u_int32_t b = ndpi_get_bin_value(b2, i); + u_int32_t diff = (a > b) ? (a - b) : (b - a); + + if(a != b) sum += pow(diff, 2); + + // printf("[a: %u][b: %u][sum: %u]\n", a, b, sum); + } + + /* The lower the more similar */ + return(sqrt(sum)); + } +#endif +} + +/* ********************************************************************************* */ + +#define MAX_NUM_CLUSTERS 128 + +/* + Clusters bins into 'num_clusters' + - (in) bins: a vection 'num_bins' long of bins to cluster + - (in) 'num_clusters': number of desired clusters 0...(num_clusters-1) + - (out) 'cluster_ids': a vector 'num_bins' long containing the id's of each clustered bin + - (out) 'centroids': an optional 'num_clusters' long vector of (centroid) bins + See + - https://en.wikipedia.org/wiki/K-means_clustering + */ +int ndpi_cluster_bins(struct ndpi_bin *bins, u_int16_t num_bins, + u_int8_t num_clusters, u_int16_t *cluster_ids, + struct ndpi_bin *centroids) { + u_int16_t i, j, max_iterations = 25, num_iterations, num_moves; + u_int8_t verbose = 0, alloc_centroids = 0; + char out_buf[256]; + float *bin_score; + u_int16_t num_cluster_elems[MAX_NUM_CLUSTERS] = { 0 }; + + srand(time(NULL)); + + if(num_clusters > num_bins) num_clusters = num_bins; + if(num_clusters > MAX_NUM_CLUSTERS) num_clusters = MAX_NUM_CLUSTERS; + + if(verbose) + printf("Distributing %u bins over %u clusters\n", num_bins, num_clusters); + + if((bin_score = (float*)ndpi_calloc(num_bins, sizeof(float))) == NULL) + return(-2); + + if(centroids == NULL) { + alloc_centroids = 1; + + if((centroids = (struct ndpi_bin*)ndpi_malloc(sizeof(struct ndpi_bin)*num_clusters)) == NULL) { + ndpi_free(bin_score); + return(-2); + } else { + for(i=0; i best_similarity) { + cluster_id = j, best_similarity = similarity; + } +#else + if(similarity < best_similarity) { + cluster_id = j, best_similarity = similarity; + } +#endif + } + + if((best_similarity == current_similarity) && (num_cluster_elems[cluster_ids[i]] > 1)) { + /* + In case of identical similarity let's leave things as they are + this unless this is a cluster with only one element + */ + cluster_id = cluster_ids[i]; + } + + bin_score[i] = best_similarity; + + if(cluster_ids[i] != cluster_id) { + if(verbose) + printf("Moved bin %u from cluster %u -> %u [similarity: %f]\n", + i, cluster_ids[i], cluster_id, best_similarity); + + num_cluster_elems[cluster_ids[i]]--; + num_cluster_elems[cluster_id]++; + + cluster_ids[i] = cluster_id; + num_moves++; + } + } + + if(num_moves == 0) + break; + + if(verbose) { + for(j=0; j 1)) + score = bin_score[i], candidate = i; + } +#else + score = 0; + + for(i=0; i score) && (num_cluster_elems[cluster_ids[i]] > 1)) + score = bin_score[i], candidate = i; + } +#endif + + if(verbose) + printf("Rebalance: moving bin %u from cluster %u -> %u [similarity: %f]\n", + candidate, cluster_ids[candidate], j, score); + + num_cluster_elems[cluster_ids[candidate]]--; + num_cluster_elems[j]++; + cluster_ids[candidate] = j; + } + } +#endif + } /* while(...) */ + + if(alloc_centroids) { + for(i=0; i #include -#include +// #include #include #include #include #include "ndpi_main.h" #include "ndpi_classify.h" +#include "ndpi_includes.h" /** finds the minimum value between to inputs */ -#define min(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) +#ifndef min +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) +#endif + //bias (1) + w (207) //const float ndpi_parameters_splt[NUM_PARAMETERS_SPLT_LOGREG] = { float ndpi_parameters_splt[NUM_PARAMETERS_SPLT_LOGREG] = { - -2.088057846500587456e+00, 7.763936238952200239e-05, 4.404309737393306595e-05, -9.467385027293546973e-02, - 4.348947142638090457e-01, -2.091409170053043390e-04, -5.788902107267982974e-04, 4.481443450852441001e-10, - -3.136135459023654537e+00, -1.507730262127600751e+00, -1.204663669965535977e+00, -1.171839254318371104e+00, - 4.329302247232582057e-01, 8.310653628092458334e+00, 3.299246725156660176e+00, 0.000000000000000000e+00, - 1.847454931582027254e-02, -1.498024139966201096e+00, -7.660670007653060942e-01, -2.908130300830076731e+00, - -1.252564844610269734e+00, -1.910955328742287573e+00, 9.471710980110392697e-01, 2.352302758516665371e+00, - 2.982269972214651954e+00, 4.280736383314343918e+00, 4.633629909719495288e+00, -2.198052637823726840e+00, - -1.150759637168392580e+00, 3.420433363184381292e+00, 1.857878113059351077e-02, -1.559806674919653746e+00, - 4.197498183183401288e+00, 6.262186949633183453e+00, 1.100694844370524095e+01, 2.778688785515088000e+01, - 3.679948298336883195e+00, -2.432801394376875592e+00, 5.133442052706843617e-01, 2.181172654073517680e+00, - -8.577551729671881731e-01, 7.013844214023315926e-01, 3.138233436228588857e+00, 7.319940508466630247e-01, - 0.000000000000000000e+00, 3.529209394581482861e+00, 1.464585117707144413e+01, 8.506550226820598359e-01, - -9.060397326548508268e-01, 6.787474954688997641e+00, 8.125411068867387954e+00, 4.515740684104064151e+00, - 5.372135582950940069e+00, 9.210951196799497254e-01, 4.802177410869620466e+00, 2.945445016176073594e+01, - 1.575032253128311632e+00, -1.355276854364796946e-01, -3.322474764169629502e-01, 3.018397817188666732e+00, - 1.186503569922195744e+00, 0.000000000000000000e+00, 8.883242370198487503e-01, 7.248276146728496627e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, -4.831246718433664711e+00, 6.124136970173365002e-01, - 4.145693892559814686e-01, 2.683998941637626867e+00, 2.063906603639539039e+00, 2.989801217386735210e+00, - 2.262965767379551962e-01, 2.240332214649647380e+00, 5.984550782416063086e+00, 4.587011255338186544e+00, - 1.233118485315272039e+01, 1.115223490909697857e+00, -3.682686422016995476e+00, 6.096498453291562258e-01, - 1.119275528656461516e+00, 1.377886278915177731e-01, 3.828176805973048324e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 1.442927634029647344e+01, 0.000000000000000000e+00, 5.719118583309401593e-01, - 1.993632609731877392e-01, 3.047472271520709430e+00, 5.736784864911910198e+00, 6.677826247219391220e+00, - 6.307175478564531090e+00, 3.150295169417364249e+01, 3.738597740702392258e+00, 1.129754590514236234e+01, - 6.108506268573830056e+00, 1.605489516792866667e+00, 2.929631990348545489e+00, -2.832543082245212937e-02, - 1.358286530670594461e+00, 1.655932469853677924e+00, 6.701964773769768513e-01, 2.131182050917533211e+00, - 2.998351165769753468e+00, 7.772095996358327596e+00, 1.285014785269981141e+00, 4.407334784589962418e+00, - 1.719858214230612026e+00, -1.012765674651314063e+00, -5.749271123172469133e-01, -3.559614093795681278e+00, - -3.073088477387719397e+00, -4.492469521371540431e+00, -3.753286990415885427e+00, -3.219255423324282273e+00, - -2.806436518181075090e+00, -2.697305948568419875e+00, -7.879608430851776646e-01, 4.625507221739111330e+00, - 4.809280703883450414e+00, -3.435194026629848629e+00, -3.218943068168937049e+00, 3.335535704890596698e+00, - 2.071359212435486263e+00, 4.538992059175040339e+00, -2.770772323566738038e+01, 2.903047708571506735e+00, - -4.436143805989154032e+00, -2.647991280011542381e-01, 1.737252348126810064e+00, -4.121989655995259128e+00, - 3.209709099445720581e-01, 1.012758514896711759e+01, 3.313255624721038295e+00, 4.631467619785444967e+00, - 7.668642402146534032e+00, 6.780938812710099128e+00, -3.256164342602652972e+00, 6.749565128319576779e-01, - 0.000000000000000000e+00, -4.407265954524525853e+00, 0.000000000000000000e+00, -3.666522115024547901e+01, - -7.886029397826226273e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, -2.261283814517791058e+01, - -4.024317426178160240e+00, 3.213063737030031342e-01, 5.079805145796887800e+00, 1.326813226475260343e+00, - 1.233684078112145643e+00, 8.671852503871454232e+00, -2.041800256066371944e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, -1.607347800380474823e+01, -4.430790279223246309e+00, 1.177552465851384511e+00, - 6.342921220500139512e+00, -2.466913734548706327e-02, 3.451642566010713065e-01, -6.012767168531006234e+00, - 7.328146570137336724e+00, 7.500088131707050465e+00, 0.000000000000000000e+00, -3.547913249211809017e+01, - -3.130964814607208879e+00, 8.247326544297072237e-01, 3.757262485775580418e-01, -2.136528302027558723e+00, - -2.631627236037529793e-01, -2.016718799388414141e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, -7.708602132869285528e-01, -2.602868328868111814e+00, 1.435184800833797958e+00, - 0.000000000000000000e+00, -2.080420864280113413e+00, 1.169498351211070819e+00, -1.798334115637199560e+01, - -1.193885252696202670e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, 4.304089297965300709e+00, - -3.020893216686394656e+00, -1.234427481614708721e+00, 0.000000000000000000e+00, 1.853340741926325697e+00, - -2.686000064995862147e+01, -1.672275139058893600e+01, -2.826268691607605987e+01, 0.000000000000000000e+00, - 0.000000000000000000e+00, -1.547397429377200817e+00, -4.018181657009961327e+00, -7.289186736637049968e+00, - -7.458655219230571731e+00, -9.625538282761622710e+00, -1.103039457077456298e+01, -6.262675161142102809e+01, - -9.265912629799268885e+00, -8.961543476816615339e+00, -9.622764435629340696e+00, -1.097978292092879826e+01, + -2.088057846500587456e+00, 7.763936238952200239e-05, 4.404309737393306595e-05, -9.467385027293546973e-02, + 4.348947142638090457e-01, -2.091409170053043390e-04, -5.788902107267982974e-04, 4.481443450852441001e-10, + -3.136135459023654537e+00, -1.507730262127600751e+00, -1.204663669965535977e+00, -1.171839254318371104e+00, + 4.329302247232582057e-01, 8.310653628092458334e+00, 3.299246725156660176e+00, 0.000000000000000000e+00, + 1.847454931582027254e-02, -1.498024139966201096e+00, -7.660670007653060942e-01, -2.908130300830076731e+00, + -1.252564844610269734e+00, -1.910955328742287573e+00, 9.471710980110392697e-01, 2.352302758516665371e+00, + 2.982269972214651954e+00, 4.280736383314343918e+00, 4.633629909719495288e+00, -2.198052637823726840e+00, + -1.150759637168392580e+00, 3.420433363184381292e+00, 1.857878113059351077e-02, -1.559806674919653746e+00, + 4.197498183183401288e+00, 6.262186949633183453e+00, 1.100694844370524095e+01, 2.778688785515088000e+01, + 3.679948298336883195e+00, -2.432801394376875592e+00, 5.133442052706843617e-01, 2.181172654073517680e+00, + -8.577551729671881731e-01, 7.013844214023315926e-01, 3.138233436228588857e+00, 7.319940508466630247e-01, + 0.000000000000000000e+00, 3.529209394581482861e+00, 1.464585117707144413e+01, 8.506550226820598359e-01, + -9.060397326548508268e-01, 6.787474954688997641e+00, 8.125411068867387954e+00, 4.515740684104064151e+00, + 5.372135582950940069e+00, 9.210951196799497254e-01, 4.802177410869620466e+00, 2.945445016176073594e+01, + 1.575032253128311632e+00, -1.355276854364796946e-01, -3.322474764169629502e-01, 3.018397817188666732e+00, + 1.186503569922195744e+00, 0.000000000000000000e+00, 8.883242370198487503e-01, 7.248276146728496627e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, -4.831246718433664711e+00, 6.124136970173365002e-01, + 4.145693892559814686e-01, 2.683998941637626867e+00, 2.063906603639539039e+00, 2.989801217386735210e+00, + 2.262965767379551962e-01, 2.240332214649647380e+00, 5.984550782416063086e+00, 4.587011255338186544e+00, + 1.233118485315272039e+01, 1.115223490909697857e+00, -3.682686422016995476e+00, 6.096498453291562258e-01, + 1.119275528656461516e+00, 1.377886278915177731e-01, 3.828176805973048324e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 1.442927634029647344e+01, 0.000000000000000000e+00, 5.719118583309401593e-01, + 1.993632609731877392e-01, 3.047472271520709430e+00, 5.736784864911910198e+00, 6.677826247219391220e+00, + 6.307175478564531090e+00, 3.150295169417364249e+01, 3.738597740702392258e+00, 1.129754590514236234e+01, + 6.108506268573830056e+00, 1.605489516792866667e+00, 2.929631990348545489e+00, -2.832543082245212937e-02, + 1.358286530670594461e+00, 1.655932469853677924e+00, 6.701964773769768513e-01, 2.131182050917533211e+00, + 2.998351165769753468e+00, 7.772095996358327596e+00, 1.285014785269981141e+00, 4.407334784589962418e+00, + 1.719858214230612026e+00, -1.012765674651314063e+00, -5.749271123172469133e-01, -3.559614093795681278e+00, + -3.073088477387719397e+00, -4.492469521371540431e+00, -3.753286990415885427e+00, -3.219255423324282273e+00, + -2.806436518181075090e+00, -2.697305948568419875e+00, -7.879608430851776646e-01, 4.625507221739111330e+00, + 4.809280703883450414e+00, -3.435194026629848629e+00, -3.218943068168937049e+00, 3.335535704890596698e+00, + 2.071359212435486263e+00, 4.538992059175040339e+00, -2.770772323566738038e+01, 2.903047708571506735e+00, + -4.436143805989154032e+00, -2.647991280011542381e-01, 1.737252348126810064e+00, -4.121989655995259128e+00, + 3.209709099445720581e-01, 1.012758514896711759e+01, 3.313255624721038295e+00, 4.631467619785444967e+00, + 7.668642402146534032e+00, 6.780938812710099128e+00, -3.256164342602652972e+00, 6.749565128319576779e-01, + 0.000000000000000000e+00, -4.407265954524525853e+00, 0.000000000000000000e+00, -3.666522115024547901e+01, + -7.886029397826226273e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, -2.261283814517791058e+01, + -4.024317426178160240e+00, 3.213063737030031342e-01, 5.079805145796887800e+00, 1.326813226475260343e+00, + 1.233684078112145643e+00, 8.671852503871454232e+00, -2.041800256066371944e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, -1.607347800380474823e+01, -4.430790279223246309e+00, 1.177552465851384511e+00, + 6.342921220500139512e+00, -2.466913734548706327e-02, 3.451642566010713065e-01, -6.012767168531006234e+00, + 7.328146570137336724e+00, 7.500088131707050465e+00, 0.000000000000000000e+00, -3.547913249211809017e+01, + -3.130964814607208879e+00, 8.247326544297072237e-01, 3.757262485775580418e-01, -2.136528302027558723e+00, + -2.631627236037529793e-01, -2.016718799388414141e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, -7.708602132869285528e-01, -2.602868328868111814e+00, 1.435184800833797958e+00, + 0.000000000000000000e+00, -2.080420864280113413e+00, 1.169498351211070819e+00, -1.798334115637199560e+01, + -1.193885252696202670e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, 4.304089297965300709e+00, + -3.020893216686394656e+00, -1.234427481614708721e+00, 0.000000000000000000e+00, 1.853340741926325697e+00, + -2.686000064995862147e+01, -1.672275139058893600e+01, -2.826268691607605987e+01, 0.000000000000000000e+00, + 0.000000000000000000e+00, -1.547397429377200817e+00, -4.018181657009961327e+00, -7.289186736637049968e+00, + -7.458655219230571731e+00, -9.625538282761622710e+00, -1.103039457077456298e+01, -6.262675161142102809e+01, + -9.265912629799268885e+00, -8.961543476816615339e+00, -9.622764435629340696e+00, -1.097978292092879826e+01, }; //bias (1) + w (207) //const float ndpi_parameters_bd[NUM_PARAMETERS_BD_LOGREG] = { float ndpi_parameters_bd[NUM_PARAMETERS_BD_LOGREG] = { - -1.678134053325450292e+00, 1.048946534609769413e-04, 9.608725756967682636e-05, -7.515489355100658797e-02, - 2.089554874872663892e-01, -1.012058874142656513e-04, -2.917652723373885169e-04, 1.087540461196068741e-10, - -2.594688448425090055e+00, -2.071803573048482061e+00, -1.399303273236228939e+00, -2.089300736641718004e+00, - -8.842347826063630123e-01, 6.476433717022786141e+00, 3.114501282249810377e+00, -2.239127990932460399e+00, - -4.667574389646080291e-01, -2.200651610813817438e+00, -1.674926704401964894e+00, -3.894420410398949706e+00, - -1.232376502509682004e+00, -2.231027070413975189e+00, 7.691948448668822769e-01, 3.222335181407633531e+00, - 1.430983188964249919e+00, 2.144317250116257956e+00, 6.596745231472220361e+00, -2.464580889153460852e+00, - -1.923337901965658681e+00, 2.910328594745831943e+00, -3.123244869063500073e-01, -1.683345539896562659e+00, - 3.785795988845424898e+00, 5.235473328290667361e+00, 8.512526402199654285e+00, 1.393475907195251473e+01, - 1.673386027437856916e+00, -2.910729265724139925e+00, 2.969886703676111184e-01, 1.700051266957717466e+00, - -5.472121114836264733e-01, 1.716354591332415469e-01, 3.177884264837486317e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 1.924354871334499062e-01, 6.568439271753665487e+00, 2.102316342451608644e-01, - -1.132124603237853355e+00, 7.329625148148498859e+00, 6.606460464951361189e+00, 2.844223241371105271e+00, - 3.078771172794853683e+00, 0.000000000000000000e+00, 2.656884613648917703e+00, 1.779697712165259205e+01, - 0.000000000000000000e+00, -3.457017935109325535e-01, 2.157595478838472414e-01, 3.829196175023549031e+00, - 0.000000000000000000e+00, 1.650776974765602867e-01, 1.357223085191380796e-02, 3.946357663253555081e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, -2.155616432815957495e+00, 8.213633570666911687e-01, - 1.125480801049912050e-01, 2.684005418659722420e+00, 5.769541257304295900e-01, 1.060883870466023948e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 3.413708974045502664e+00, 2.275281553961784553e+00, - 5.176725998383044924e+00, 1.019445219242678835e+00, -1.848344450190015698e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 1.491820649409327126e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 9.379741891282449728e+00, 0.000000000000000000e+00, 5.444605374840002510e-01, - -9.654403640632221173e-02, 2.642171746731144744e+00, 4.626416118226488905e+00, 3.654642208477139498e+00, - 3.427412899258296619e+00, 1.490784083593987397e+01, 2.322393214516801141e+00, 6.511453713852694669e+00, - 6.949721651828602020e+00, 1.186838154505042375e+00, 2.072129970488261197e+00, 0.000000000000000000e+00, - 1.598928631178261561e+00, 5.926083912988970859e-01, -1.612886287403501873e-01, 9.452951868724716045e-01, - 2.145707914290207352e+00, 5.391610489831286657e+00, 8.454389313314318866e-01, 2.372736567215404602e+00, - -3.130110237826235764e-01, -2.994989290166069740e+00, -2.571950567149417832e+00, -5.018016256298333921e+00, - -4.851489154898488643e+00, -7.101788768628541249e+00, -5.227281714666618839e+00, -6.351346048086286444e+00, - -4.558191218464671124e+00, -5.293990544168526213e+00, -2.920034449434862345e-01, 5.166915658100844411e+00, - 4.642130303354632836e+00, -5.246106907306949951e-01, -3.120281208300208498e+00, 1.544764033379846691e+00, - 0.000000000000000000e+00, 3.721469736246234561e+00, -1.083434721745241625e+01, 2.901590918368040395e+00, - -3.602037909234679258e+00, 0.000000000000000000e+00, 2.736307835089097917e+00, -5.037400262764839987e+00, - -1.163050013241316849e+00, 6.565863507998260573e+00, 1.872406036485896097e+00, 2.249439295570562880e+00, - 3.276076277814265136e+00, 5.747730113795930684e+00, -2.084335807954610154e+00, 1.812930768433161921e+00, - 0.000000000000000000e+00, -4.068875727535363751e+00, -4.509432609364653205e-02, -1.424182063303933710e+01, - -1.743400430675688639e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, -8.986019040369217947e+00, - -2.005955598483518898e+00, 1.514163405869717538e+00, 4.060752357984299010e+00, 1.405971170124569403e+00, - 1.383171915541985708e+00, 4.654452090729912506e+00, -3.395023560174311950e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, -8.562968788250293173e+00, -1.939561462845156514e+00, 2.627499899415196793e+00, - 4.949794698120698833e+00, 4.355655772643094448e-01, 0.000000000000000000e+00, -1.055190553626396577e+00, - 4.757318838337171840e+00, 3.966536148163406938e+00, 0.000000000000000000e+00, -1.190662117721104352e+01, - -1.673945042186458121e+00, 0.000000000000000000e+00, -1.203943763219820356e-02, -1.411827841131889194e+00, - -7.623501643009024109e-01, -6.774873775798392117e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, -1.755294779557688090e+00, 1.542887322103192238e+00, - 0.000000000000000000e+00, -8.228978371972577310e-01, 0.000000000000000000e+00, -5.379142925264499553e+00, - -1.144060263986041326e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 4.731108583634047626e+00, - -1.569393147397664556e+00, -3.449886418134247568e-01, 0.000000000000000000e+00, 1.658412661295906920e+00, - -5.077151059809188460e+00, -7.326467579034271260e+00, -1.190177296658179840e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, -1.914781807241187739e+00, -5.438446604150855457e+00, - -5.988893208768400811e+00, -7.886849112491050029e+00, -9.355574940159534947e+00, -1.682361325340106006e+01, - -7.609538696398503888e+00, -7.363350786768400269e+00, -7.366039984795356155e+00, -7.051111570136543882e+00, - 2.337391373249395610e+00, -4.374845402801011574e+01, -3.610863365629191080e+00, 7.684297617701028571e+01, - 2.162851395732025139e+01, 1.066280518306870562e+01, 8.109257308306457901e+01, 5.149561395669890906e+00, - 0.000000000000000000e+00, 3.219993054481156136e+00, 0.000000000000000000e+00, 2.093519725422254396e+01, - -5.225298528278367272e+00, 0.000000000000000000e+00, 2.159597932230871820e+00, -5.205637201784965384e+01, - 1.601979388461561982e+01, 6.945290207097973401e+00, 8.036724740759808583e-01, 4.712266457087280536e+00, - 2.146353485778652370e+01, 3.470089369007970248e+01, 9.468591086256607170e+00, 9.760488656497257054e-01, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 9.008837970422939323e-01, - 0.000000000000000000e+00, 1.462843531299845168e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, - -1.179406942091425847e+01, 0.000000000000000000e+00, 1.642473653464513816e+01, 1.387228776263151175e+01, - 0.000000000000000000e+00, 1.613129141280310108e+01, 0.000000000000000000e+00, -1.077318890268341045e+00, - 4.189407459072477802e-01, 0.000000000000000000e+00, -1.570052145651456899e+00, 0.000000000000000000e+00, - 1.120834605828141939e+01, 4.286417457736029490e+01, 0.000000000000000000e+00, 2.938378293327098945e+01, - 1.194087082487160956e+01, 0.000000000000000000e+00, -9.951431855637998813e-02, 3.844291513997798448e-01, - 2.362333099868798669e+01, -1.002532136112976957e+01, 2.427817537309562823e+01, 0.000000000000000000e+00, - 1.076329692188489773e+01, 1.760895870067486157e+00, 2.080295785135324849e+01, -4.335217053626006134e+01, - -6.272369476984676062e-01, 5.165768790797590881e+00, -4.507215926635629311e-01, 0.000000000000000000e+00, - -4.242472062530233679e+00, -4.931831554080153168e+00, -2.806203935735193777e+00, -2.670377941558885126e+01, - 0.000000000000000000e+00, -2.124688439238133242e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, - 2.452415244698852970e+00, -1.173727222080745092e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - -1.458125295680756039e+01, -1.757703406512062827e+01, 0.000000000000000000e+00, 3.943626521423988951e+00, - 0.000000000000000000e+00, -4.006095410470026152e+00, 1.727171067402430538e+01, -3.412620901789366457e+01, - 0.000000000000000000e+00, 1.760073934312834254e+01, 3.266082201875645552e+01, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 1.514535424913179362e+01, 0.000000000000000000e+00, - 0.000000000000000000e+00, -3.100487758075622935e-01, 0.000000000000000000e+00, 2.387863228159451978e+01, - 1.237098847411416891e+01, 1.154430573879687560e-02, 7.976366278729441817e+00, 0.000000000000000000e+00, - -6.296727640787388447e-01, 1.406230674131906255e+01, 1.430275589872723430e+01, -2.231764570537816184e+00, - 0.000000000000000000e+00, 5.003869692542436631e+00, 0.000000000000000000e+00, -5.482127427587509594e+00, - -8.830547931126154992e+00, -5.376776036224484301e+01, -2.918517871695104304e+01, -1.009022417771788049e+01, - -4.811775051355994037e+00, -1.188016976215758547e+01, -2.055483647266791536e+01, -2.482333959706277327e+01, - -1.048392515070836950e+01, -3.837352144714887459e+01, 0.000000000000000000e+00, -9.298440675063780247e+00, - 0.000000000000000000e+00, 3.584086297861655890e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 1.184271790014085113e+00, - 1.594266439891793219e+01, 0.000000000000000000e+00, 8.473235161049382569e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 6.748879951595517568e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, -1.057534737660506430e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, -3.179879192807419841e+01, 0.000000000000000000e+00, 5.000324879565139824e+00, - 0.000000000000000000e+00, 1.229183419446936654e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 4.127983063177185663e+00, 6.616705680943091750e+00, 5.848245769217652601e+00, -1.818944631334333550e+01, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 2.694838778746875274e+00, 0.000000000000000000e+00, 1.463145767737777625e+01, -4.924734438569850603e+00, - 0.000000000000000000e+00, 1.877377621310543088e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 1.971941442729244764e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 1.732809836566829187e+00, 2.700285877421266534e+01, - 2.915978562591383216e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, -6.999629705176019456e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 1.089611710258455268e+01, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 2.121018958070171934e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - -7.416250358067024706e+00, -1.263327458973565065e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 2.241612733384156897e+01, - 0.000000000000000000e+00, 8.607688079645482659e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 1.750217629228628269e+01, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, -1.957769005108392690e+01, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, -3.242393079195928784e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, 0.000000000000000000e+00, 1.348338590741638932e+01, 0.000000000000000000e+00, - -2.000312276678208392e-02, -7.776608146776987640e-01, 0.000000000000000000e+00, 0.000000000000000000e+00, - 0.000000000000000000e+00, -5.387825733845168941e+00, 0.000000000000000000e+00, 2.153516224136292934e+01, - 0.000000000000000000e+00, 0.000000000000000000e+00, -9.635140703414636576e+00, 2.603288107669730511e+00, + -1.678134053325450292e+00, 1.048946534609769413e-04, 9.608725756967682636e-05, -7.515489355100658797e-02, + 2.089554874872663892e-01, -1.012058874142656513e-04, -2.917652723373885169e-04, 1.087540461196068741e-10, + -2.594688448425090055e+00, -2.071803573048482061e+00, -1.399303273236228939e+00, -2.089300736641718004e+00, + -8.842347826063630123e-01, 6.476433717022786141e+00, 3.114501282249810377e+00, -2.239127990932460399e+00, + -4.667574389646080291e-01, -2.200651610813817438e+00, -1.674926704401964894e+00, -3.894420410398949706e+00, + -1.232376502509682004e+00, -2.231027070413975189e+00, 7.691948448668822769e-01, 3.222335181407633531e+00, + 1.430983188964249919e+00, 2.144317250116257956e+00, 6.596745231472220361e+00, -2.464580889153460852e+00, + -1.923337901965658681e+00, 2.910328594745831943e+00, -3.123244869063500073e-01, -1.683345539896562659e+00, + 3.785795988845424898e+00, 5.235473328290667361e+00, 8.512526402199654285e+00, 1.393475907195251473e+01, + 1.673386027437856916e+00, -2.910729265724139925e+00, 2.969886703676111184e-01, 1.700051266957717466e+00, + -5.472121114836264733e-01, 1.716354591332415469e-01, 3.177884264837486317e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 1.924354871334499062e-01, 6.568439271753665487e+00, 2.102316342451608644e-01, + -1.132124603237853355e+00, 7.329625148148498859e+00, 6.606460464951361189e+00, 2.844223241371105271e+00, + 3.078771172794853683e+00, 0.000000000000000000e+00, 2.656884613648917703e+00, 1.779697712165259205e+01, + 0.000000000000000000e+00, -3.457017935109325535e-01, 2.157595478838472414e-01, 3.829196175023549031e+00, + 0.000000000000000000e+00, 1.650776974765602867e-01, 1.357223085191380796e-02, 3.946357663253555081e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, -2.155616432815957495e+00, 8.213633570666911687e-01, + 1.125480801049912050e-01, 2.684005418659722420e+00, 5.769541257304295900e-01, 1.060883870466023948e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 3.413708974045502664e+00, 2.275281553961784553e+00, + 5.176725998383044924e+00, 1.019445219242678835e+00, -1.848344450190015698e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 1.491820649409327126e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 9.379741891282449728e+00, 0.000000000000000000e+00, 5.444605374840002510e-01, + -9.654403640632221173e-02, 2.642171746731144744e+00, 4.626416118226488905e+00, 3.654642208477139498e+00, + 3.427412899258296619e+00, 1.490784083593987397e+01, 2.322393214516801141e+00, 6.511453713852694669e+00, + 6.949721651828602020e+00, 1.186838154505042375e+00, 2.072129970488261197e+00, 0.000000000000000000e+00, + 1.598928631178261561e+00, 5.926083912988970859e-01, -1.612886287403501873e-01, 9.452951868724716045e-01, + 2.145707914290207352e+00, 5.391610489831286657e+00, 8.454389313314318866e-01, 2.372736567215404602e+00, + -3.130110237826235764e-01, -2.994989290166069740e+00, -2.571950567149417832e+00, -5.018016256298333921e+00, + -4.851489154898488643e+00, -7.101788768628541249e+00, -5.227281714666618839e+00, -6.351346048086286444e+00, + -4.558191218464671124e+00, -5.293990544168526213e+00, -2.920034449434862345e-01, 5.166915658100844411e+00, + 4.642130303354632836e+00, -5.246106907306949951e-01, -3.120281208300208498e+00, 1.544764033379846691e+00, + 0.000000000000000000e+00, 3.721469736246234561e+00, -1.083434721745241625e+01, 2.901590918368040395e+00, + -3.602037909234679258e+00, 0.000000000000000000e+00, 2.736307835089097917e+00, -5.037400262764839987e+00, + -1.163050013241316849e+00, 6.565863507998260573e+00, 1.872406036485896097e+00, 2.249439295570562880e+00, + 3.276076277814265136e+00, 5.747730113795930684e+00, -2.084335807954610154e+00, 1.812930768433161921e+00, + 0.000000000000000000e+00, -4.068875727535363751e+00, -4.509432609364653205e-02, -1.424182063303933710e+01, + -1.743400430675688639e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, -8.986019040369217947e+00, + -2.005955598483518898e+00, 1.514163405869717538e+00, 4.060752357984299010e+00, 1.405971170124569403e+00, + 1.383171915541985708e+00, 4.654452090729912506e+00, -3.395023560174311950e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, -8.562968788250293173e+00, -1.939561462845156514e+00, 2.627499899415196793e+00, + 4.949794698120698833e+00, 4.355655772643094448e-01, 0.000000000000000000e+00, -1.055190553626396577e+00, + 4.757318838337171840e+00, 3.966536148163406938e+00, 0.000000000000000000e+00, -1.190662117721104352e+01, + -1.673945042186458121e+00, 0.000000000000000000e+00, -1.203943763219820356e-02, -1.411827841131889194e+00, + -7.623501643009024109e-01, -6.774873775798392117e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, -1.755294779557688090e+00, 1.542887322103192238e+00, + 0.000000000000000000e+00, -8.228978371972577310e-01, 0.000000000000000000e+00, -5.379142925264499553e+00, + -1.144060263986041326e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 4.731108583634047626e+00, + -1.569393147397664556e+00, -3.449886418134247568e-01, 0.000000000000000000e+00, 1.658412661295906920e+00, + -5.077151059809188460e+00, -7.326467579034271260e+00, -1.190177296658179840e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, -1.914781807241187739e+00, -5.438446604150855457e+00, + -5.988893208768400811e+00, -7.886849112491050029e+00, -9.355574940159534947e+00, -1.682361325340106006e+01, + -7.609538696398503888e+00, -7.363350786768400269e+00, -7.366039984795356155e+00, -7.051111570136543882e+00, + 2.337391373249395610e+00, -4.374845402801011574e+01, -3.610863365629191080e+00, 7.684297617701028571e+01, + 2.162851395732025139e+01, 1.066280518306870562e+01, 8.109257308306457901e+01, 5.149561395669890906e+00, + 0.000000000000000000e+00, 3.219993054481156136e+00, 0.000000000000000000e+00, 2.093519725422254396e+01, + -5.225298528278367272e+00, 0.000000000000000000e+00, 2.159597932230871820e+00, -5.205637201784965384e+01, + 1.601979388461561982e+01, 6.945290207097973401e+00, 8.036724740759808583e-01, 4.712266457087280536e+00, + 2.146353485778652370e+01, 3.470089369007970248e+01, 9.468591086256607170e+00, 9.760488656497257054e-01, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 9.008837970422939323e-01, + 0.000000000000000000e+00, 1.462843531299845168e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, + -1.179406942091425847e+01, 0.000000000000000000e+00, 1.642473653464513816e+01, 1.387228776263151175e+01, + 0.000000000000000000e+00, 1.613129141280310108e+01, 0.000000000000000000e+00, -1.077318890268341045e+00, + 4.189407459072477802e-01, 0.000000000000000000e+00, -1.570052145651456899e+00, 0.000000000000000000e+00, + 1.120834605828141939e+01, 4.286417457736029490e+01, 0.000000000000000000e+00, 2.938378293327098945e+01, + 1.194087082487160956e+01, 0.000000000000000000e+00, -9.951431855637998813e-02, 3.844291513997798448e-01, + 2.362333099868798669e+01, -1.002532136112976957e+01, 2.427817537309562823e+01, 0.000000000000000000e+00, + 1.076329692188489773e+01, 1.760895870067486157e+00, 2.080295785135324849e+01, -4.335217053626006134e+01, + -6.272369476984676062e-01, 5.165768790797590881e+00, -4.507215926635629311e-01, 0.000000000000000000e+00, + -4.242472062530233679e+00, -4.931831554080153168e+00, -2.806203935735193777e+00, -2.670377941558885126e+01, + 0.000000000000000000e+00, -2.124688439238133242e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, + 2.452415244698852970e+00, -1.173727222080745092e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + -1.458125295680756039e+01, -1.757703406512062827e+01, 0.000000000000000000e+00, 3.943626521423988951e+00, + 0.000000000000000000e+00, -4.006095410470026152e+00, 1.727171067402430538e+01, -3.412620901789366457e+01, + 0.000000000000000000e+00, 1.760073934312834254e+01, 3.266082201875645552e+01, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 1.514535424913179362e+01, 0.000000000000000000e+00, + 0.000000000000000000e+00, -3.100487758075622935e-01, 0.000000000000000000e+00, 2.387863228159451978e+01, + 1.237098847411416891e+01, 1.154430573879687560e-02, 7.976366278729441817e+00, 0.000000000000000000e+00, + -6.296727640787388447e-01, 1.406230674131906255e+01, 1.430275589872723430e+01, -2.231764570537816184e+00, + 0.000000000000000000e+00, 5.003869692542436631e+00, 0.000000000000000000e+00, -5.482127427587509594e+00, + -8.830547931126154992e+00, -5.376776036224484301e+01, -2.918517871695104304e+01, -1.009022417771788049e+01, + -4.811775051355994037e+00, -1.188016976215758547e+01, -2.055483647266791536e+01, -2.482333959706277327e+01, + -1.048392515070836950e+01, -3.837352144714887459e+01, 0.000000000000000000e+00, -9.298440675063780247e+00, + 0.000000000000000000e+00, 3.584086297861655890e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 1.184271790014085113e+00, + 1.594266439891793219e+01, 0.000000000000000000e+00, 8.473235161049382569e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 6.748879951595517568e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, -1.057534737660506430e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, -3.179879192807419841e+01, 0.000000000000000000e+00, 5.000324879565139824e+00, + 0.000000000000000000e+00, 1.229183419446936654e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 4.127983063177185663e+00, 6.616705680943091750e+00, 5.848245769217652601e+00, -1.818944631334333550e+01, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 2.694838778746875274e+00, 0.000000000000000000e+00, 1.463145767737777625e+01, -4.924734438569850603e+00, + 0.000000000000000000e+00, 1.877377621310543088e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 1.971941442729244764e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 1.732809836566829187e+00, 2.700285877421266534e+01, + 2.915978562591383216e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, -6.999629705176019456e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 1.089611710258455268e+01, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 2.121018958070171934e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + -7.416250358067024706e+00, -1.263327458973565065e+01, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 2.241612733384156897e+01, + 0.000000000000000000e+00, 8.607688079645482659e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 1.750217629228628269e+01, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, -1.957769005108392690e+01, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, -3.242393079195928784e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, 0.000000000000000000e+00, 1.348338590741638932e+01, 0.000000000000000000e+00, + -2.000312276678208392e-02, -7.776608146776987640e-01, 0.000000000000000000e+00, 0.000000000000000000e+00, + 0.000000000000000000e+00, -5.387825733845168941e+00, 0.000000000000000000e+00, 2.153516224136292934e+01, + 0.000000000000000000e+00, 0.000000000000000000e+00, -9.635140703414636576e+00, 2.603288107669730511e+00, }; /** - * \fn void ndpi_merge_splt_arrays (const uint16_t *pkt_len, const struct timeval *pkt_time, - const uint16_t *pkt_len_twin, const struct timeval *pkt_time_twin, - struct timeval start_time, struct timeval start_time_twin, - uint16_t s_idx, uint16_t r_idx, - uint16_t *merged_lens, uint16_t *merged_times, - uint32_t max_num_pkt_len, uint32_t max_merged_num_pkts) + * \fn void ndpi_merge_splt_arrays (const uint16_t *pkt_len, const pkt_timeval *pkt_time, + const uint16_t *pkt_len_twin, const pkt_timeval *pkt_time_twin, + pkt_timeval start_time, pkt_timeval start_time_twin, + uint16_t s_idx, uint16_t r_idx, + uint16_t *merged_lens, uint16_t *merged_times, + uint32_t max_num_pkt_len, uint32_t max_merged_num_pkts) * \param pkt_len length of the packet * \param pkt_time time of the packet * \param pkt_len_twin length of the twin packet @@ -260,170 +261,170 @@ float ndpi_parameters_bd[NUM_PARAMETERS_BD_LOGREG] = { * \return none */ void -ndpi_merge_splt_arrays (const uint16_t *pkt_len, const struct timeval *pkt_time, - const uint16_t *pkt_len_twin, const struct timeval *pkt_time_twin, - struct timeval start_time, struct timeval start_time_twin, +ndpi_merge_splt_arrays (const uint16_t *pkt_len, const pkt_timeval *pkt_time, + const uint16_t *pkt_len_twin, const pkt_timeval *pkt_time_twin, + pkt_timeval start_time, pkt_timeval start_time_twin, uint16_t s_idx, uint16_t r_idx, uint16_t *merged_lens, uint16_t *merged_times) { - int s,r; - struct timeval ts_start = { 0, 0 }; /* initialize to avoid spurious warnings */ - struct timeval tmp, tmp_r; - struct timeval start_m; - - if (r_idx + s_idx == 0) { - return ; - } else if (r_idx == 0) { - ts_start = pkt_time[0]; - tmp = pkt_time[0]; - ndpi_timer_sub(&tmp, &start_time, &start_m); - } else if (s_idx == 0) { - ts_start = pkt_time_twin[0]; - tmp = pkt_time_twin[0]; - ndpi_timer_sub(&tmp, &start_time_twin, &start_m); + int s,r; + pkt_timeval ts_start = { 0, 0 }; /* initialize to avoid spurious warnings */ + pkt_timeval tmp, tmp_r; + pkt_timeval start_m; + + if(r_idx + s_idx == 0) { + return ; + } else if(r_idx == 0) { + ts_start = pkt_time[0]; + tmp = pkt_time[0]; + ndpi_timer_sub(&tmp, &start_time, &start_m); + } else if(s_idx == 0) { + ts_start = pkt_time_twin[0]; + tmp = pkt_time_twin[0]; + ndpi_timer_sub(&tmp, &start_time_twin, &start_m); + } else { + if(ndpi_timer_lt(&start_time, &start_time_twin)) { + ts_start = pkt_time[0]; + tmp = pkt_time[0]; + ndpi_timer_sub(&tmp, &start_time, &start_m); } else { - if (ndpi_timer_lt(&start_time, &start_time_twin)) { - ts_start = pkt_time[0]; - tmp = pkt_time[0]; - ndpi_timer_sub(&tmp, &start_time, &start_m); - } else { - // ts_start = pkt_time_twin[0]; - tmp = pkt_time_twin[0]; - ndpi_timer_sub(&tmp, &start_time_twin, &start_m); - } + // ts_start = pkt_time_twin[0]; + tmp = pkt_time_twin[0]; + ndpi_timer_sub(&tmp, &start_time_twin, &start_m); } - s = r = 0; - while ((s < s_idx) || (r < r_idx)) { - if (s >= s_idx) { - merged_lens[s+r] = pkt_len_twin[r]; - tmp = pkt_time_twin[r]; - ndpi_timer_sub(&tmp, &ts_start, &tmp_r); - merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); - if (merged_times[s+r] == 0) - merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); - ts_start = tmp; - r++; - } else if (r >= r_idx) { - merged_lens[s+r] = pkt_len[s]; - tmp = pkt_time[s]; - ndpi_timer_sub(&tmp, &ts_start, &tmp_r); - merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); - if (merged_times[s+r] == 0) - merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); - ts_start = tmp; - s++; - } else { - if (ndpi_timer_lt(&pkt_time[s], &pkt_time_twin[r])) { - merged_lens[s+r] = pkt_len[s]; - tmp = pkt_time[s]; - ndpi_timer_sub(&tmp, &ts_start, &tmp_r); - merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); - if (merged_times[s+r] == 0) - merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); - ts_start = tmp; - s++; - } else { - merged_lens[s+r] = pkt_len_twin[r]; - tmp = pkt_time_twin[r]; - ndpi_timer_sub(&tmp, &ts_start, &tmp_r); - merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); - if (merged_times[s+r] == 0) - merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); - ts_start = tmp; - r++; - } - } + } + s = r = 0; + while ((s < s_idx) || (r < r_idx)) { + if(s >= s_idx) { + merged_lens[s+r] = pkt_len_twin[r]; + tmp = pkt_time_twin[r]; + ndpi_timer_sub(&tmp, &ts_start, &tmp_r); + merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); + if(merged_times[s+r] == 0) + merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); + ts_start = tmp; + r++; + } else if(r >= r_idx) { + merged_lens[s+r] = pkt_len[s]; + tmp = pkt_time[s]; + ndpi_timer_sub(&tmp, &ts_start, &tmp_r); + merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); + if(merged_times[s+r] == 0) + merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); + ts_start = tmp; + s++; + } else { + if(ndpi_timer_lt(&pkt_time[s], &pkt_time_twin[r])) { + merged_lens[s+r] = pkt_len[s]; + tmp = pkt_time[s]; + ndpi_timer_sub(&tmp, &ts_start, &tmp_r); + merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); + if(merged_times[s+r] == 0) + merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); + ts_start = tmp; + s++; + } else { + merged_lens[s+r] = pkt_len_twin[r]; + tmp = pkt_time_twin[r]; + ndpi_timer_sub(&tmp, &ts_start, &tmp_r); + merged_times[s+r] = ndpi_timeval_to_milliseconds(tmp_r); + if(merged_times[s+r] == 0) + merged_times[s+r] = ndpi_timeval_to_microseconds(tmp_r); + ts_start = tmp; + r++; + } } - merged_times[0] = ndpi_timeval_to_milliseconds(start_m); - if (merged_times[0] == 0) - merged_times[0] = ndpi_timeval_to_microseconds(start_m); + } + merged_times[0] = ndpi_timeval_to_milliseconds(start_m); + if(merged_times[0] == 0) + merged_times[0] = ndpi_timeval_to_microseconds(start_m); } /* transform lens array to Markov chain */ static void ndpi_get_mc_rep_lens (uint16_t *lens, float *length_mc, uint16_t num_packets) { - float row_sum; - int prev_packet_size = 0; - int cur_packet_size = 0; - int i, j; - - for (i = 0; i < MC_BINS_LEN*MC_BINS_LEN; i++) { // init to 0 - length_mc[i] = 0.0; + float row_sum; + int prev_packet_size = 0; + int cur_packet_size = 0; + int i, j; + + for (i = 0; i < MC_BINS_LEN*MC_BINS_LEN; i++) { // init to 0 + length_mc[i] = 0.0; + } + + if(num_packets == 0) { + // nothing to do + } else if(num_packets == 1) { + cur_packet_size = (int)min(lens[0]/(float)MC_BIN_SIZE_LEN,(uint16_t)MC_BINS_LEN-1); + length_mc[cur_packet_size + cur_packet_size*MC_BINS_LEN] = 1.0; + } else { + for (i = 1; i < num_packets; i++) { + prev_packet_size = (int)min((uint16_t)(lens[i-1]/(float)MC_BIN_SIZE_LEN),(uint16_t)MC_BINS_LEN-1); + cur_packet_size = (int)min((uint16_t)(lens[i]/(float)MC_BIN_SIZE_LEN),(uint16_t)MC_BINS_LEN-1); + length_mc[prev_packet_size*MC_BINS_LEN + cur_packet_size] += 1.0; } - - if (num_packets == 0) { - // nothing to do - } else if (num_packets == 1) { - cur_packet_size = (int)min(lens[0]/(float)MC_BIN_SIZE_LEN,(uint16_t)MC_BINS_LEN-1); - length_mc[cur_packet_size + cur_packet_size*MC_BINS_LEN] = 1.0; - } else { - for (i = 1; i < num_packets; i++) { - prev_packet_size = (int)min((uint16_t)(lens[i-1]/(float)MC_BIN_SIZE_LEN),(uint16_t)MC_BINS_LEN-1); - cur_packet_size = (int)min((uint16_t)(lens[i]/(float)MC_BIN_SIZE_LEN),(uint16_t)MC_BINS_LEN-1); - length_mc[prev_packet_size*MC_BINS_LEN + cur_packet_size] += 1.0; - } - // normalize rows of Markov chain - for (i = 0; i < MC_BINS_LEN; i++) { - // find sum - row_sum = 0.0; - for (j = 0; j < MC_BINS_LEN; j++) { - row_sum += length_mc[i*MC_BINS_LEN+j]; - } - if (row_sum != 0.0) { - for (j = 0; j < MC_BINS_LEN; j++) { - length_mc[i*MC_BINS_LEN+j] /= row_sum; - } - } - } + // normalize rows of Markov chain + for (i = 0; i < MC_BINS_LEN; i++) { + // find sum + row_sum = 0.0; + for (j = 0; j < MC_BINS_LEN; j++) { + row_sum += length_mc[i*MC_BINS_LEN+j]; + } + if(row_sum != 0.0) { + for (j = 0; j < MC_BINS_LEN; j++) { + length_mc[i*MC_BINS_LEN+j] /= row_sum; + } + } } + } } /* transform times array to Markov chain */ void ndpi_get_mc_rep_times (uint16_t *times, float *time_mc, uint16_t num_packets) { - float row_sum; - int prev_packet_time = 0; - int cur_packet_time = 0; - int i, j; - - for (i = 0; i < MC_BINS_TIME*MC_BINS_TIME; i++) { // init to 0 - time_mc[i] = 0.0; + float row_sum; + int prev_packet_time = 0; + int cur_packet_time = 0; + int i, j; + + for (i = 0; i < MC_BINS_TIME*MC_BINS_TIME; i++) { // init to 0 + time_mc[i] = 0.0; + } + if(num_packets == 0) { + // nothing to do + } else if(num_packets == 1) { + cur_packet_time = (int)min(times[0]/(float)MC_BIN_SIZE_TIME,(uint16_t)MC_BINS_TIME-1); + time_mc[cur_packet_time + cur_packet_time*MC_BINS_TIME] = 1.0; + } else { + for (i = 1; i < num_packets; i++) { + prev_packet_time = (int)min((uint16_t)(times[i-1]/(float)MC_BIN_SIZE_TIME),(uint16_t)MC_BINS_TIME-1); + cur_packet_time = (int)min((uint16_t)(times[i]/(float)MC_BIN_SIZE_TIME),(uint16_t)MC_BINS_TIME-1); + time_mc[prev_packet_time*MC_BINS_TIME + cur_packet_time] += 1.0; } - if (num_packets == 0) { - // nothing to do - } else if (num_packets == 1) { - cur_packet_time = (int)min(times[0]/(float)MC_BIN_SIZE_TIME,(uint16_t)MC_BINS_TIME-1); - time_mc[cur_packet_time + cur_packet_time*MC_BINS_TIME] = 1.0; - } else { - for (i = 1; i < num_packets; i++) { - prev_packet_time = (int)min((uint16_t)(times[i-1]/(float)MC_BIN_SIZE_TIME),(uint16_t)MC_BINS_TIME-1); - cur_packet_time = (int)min((uint16_t)(times[i]/(float)MC_BIN_SIZE_TIME),(uint16_t)MC_BINS_TIME-1); - time_mc[prev_packet_time*MC_BINS_TIME + cur_packet_time] += 1.0; - } - // normalize rows of Markov chain - for (i = 0; i < MC_BINS_TIME; i++) { - // find sum - row_sum = 0.0; - for (j = 0; j < MC_BINS_TIME; j++) { - row_sum += time_mc[i*MC_BINS_TIME+j]; - } - if (row_sum != 0.0) { - for (j = 0; j < MC_BINS_TIME; j++) { - time_mc[i*MC_BINS_TIME+j] /= row_sum; - } - } - } + // normalize rows of Markov chain + for (i = 0; i < MC_BINS_TIME; i++) { + // find sum + row_sum = 0.0; + for (j = 0; j < MC_BINS_TIME; j++) { + row_sum += time_mc[i*MC_BINS_TIME+j]; + } + if(row_sum != 0.0) { + for (j = 0; j < MC_BINS_TIME; j++) { + time_mc[i*MC_BINS_TIME+j] /= row_sum; + } + } } + } } /** - * \fn float classify (const unsigned short *pkt_len, const struct timeval *pkt_time, - const unsigned short *pkt_len_twin, const struct timeval *pkt_time_twin, - struct timeval start_time, struct timeval start_time_twin, uint32_t max_num_pkt_len, - uint16_t sp, uint16_t dp, uint32_t op, uint32_t ip, uint32_t np_o, uint32_t np_i, - uint32_t ob, uint32_t ib, uint16_t use_bd, const uint32_t *bd, const uint32_t *bd_t) + * \fn float classify (const unsigned short *pkt_len, const pkt_timeval *pkt_time, + const unsigned short *pkt_len_twin, const pkt_timeval *pkt_time_twin, + pkt_timeval start_time, pkt_timeval start_time_twin, uint32_t max_num_pkt_len, + uint16_t sp, uint16_t dp, uint32_t op, uint32_t ip, uint32_t np_o, uint32_t np_i, + uint32_t ob, uint32_t ib, uint16_t use_bd, const uint32_t *bd, const uint32_t *bd_t) * \param pkt_len length of the packet * \param pkt_time time of the packet * \param pkt_len_twin length of the packet twin @@ -445,94 +446,95 @@ ndpi_get_mc_rep_times (uint16_t *times, float *time_mc, uint16_t num_packets) * \return float score */ float -ndpi_classify (const unsigned short *pkt_len, const struct timeval *pkt_time, - const unsigned short *pkt_len_twin, const struct timeval *pkt_time_twin, - struct timeval start_time, struct timeval start_time_twin, uint32_t max_num_pkt_len, +ndpi_classify (const unsigned short *pkt_len, const pkt_timeval *pkt_time, + const unsigned short *pkt_len_twin, const pkt_timeval *pkt_time_twin, + pkt_timeval start_time, pkt_timeval start_time_twin, uint32_t max_num_pkt_len, uint16_t sp, uint16_t dp, uint32_t op, uint32_t ip, uint32_t np_o, uint32_t np_i, uint32_t ob, uint32_t ib, uint16_t use_bd, const uint32_t *bd, const uint32_t *bd_t) { - float features[NUM_PARAMETERS_BD_LOGREG] = {1.0}; - float mc_lens[MC_BINS_LEN*MC_BINS_LEN]; - float mc_times[MC_BINS_TIME*MC_BINS_TIME]; - uint32_t i; - float score = 0.0; - - uint32_t op_n = min(np_o, max_num_pkt_len); - uint32_t ip_n = min(np_i, max_num_pkt_len); - uint16_t *merged_lens = NULL; - uint16_t *merged_times = NULL; - - for (i = 1; i < NUM_PARAMETERS_BD_LOGREG; i++) { - features[i] = 0.0; - } - - merged_lens = calloc(1, sizeof(uint16_t)*(op_n + ip_n)); - merged_times = calloc(1, sizeof(uint16_t)*(op_n + ip_n)); - if (!merged_lens || !merged_times) { - free(merged_lens); - free(merged_times); - return(score); - } - - // fill out meta data - features[1] = (float)dp; // destination port - features[2] = (float)sp; // source port - features[3] = (float)ip; // inbound packets - features[4] = (float)op; // outbound packets - features[5] = (float)ib; // inbound bytes - features[6] = (float)ob; // outbound bytes - features[7] = 0.0;// skipping 7 until we process the pkt_time arrays - - // find the raw features - ndpi_merge_splt_arrays(pkt_len, pkt_time, pkt_len_twin, pkt_time_twin, start_time, start_time_twin, op_n, ip_n, - merged_lens, merged_times); - - // find new duration - for (i = 0; i < op_n+ip_n; i++) { - features[7] += (float)merged_times[i]; + float features[NUM_PARAMETERS_BD_LOGREG] = {1.0}; + float mc_lens[MC_BINS_LEN*MC_BINS_LEN]; + float mc_times[MC_BINS_TIME*MC_BINS_TIME]; + uint32_t i; + float score = 0.0; + + uint32_t op_n = min(np_o, max_num_pkt_len); + uint32_t ip_n = min(np_i, max_num_pkt_len); + uint16_t *merged_lens = NULL; + uint16_t *merged_times = NULL; + + for (i = 1; i < NUM_PARAMETERS_BD_LOGREG; i++) { + features[i] = 0.0; + } + + merged_lens = ndpi_calloc(1, sizeof(uint16_t)*(op_n + ip_n)); + merged_times = ndpi_calloc(1, sizeof(uint16_t)*(op_n + ip_n)); + + if(!merged_lens || !merged_times) { + ndpi_free(merged_lens); + ndpi_free(merged_times); + return(score); + } + + // fill out meta data + features[1] = (float)dp; // destination port + features[2] = (float)sp; // source port + features[3] = (float)ip; // inbound packets + features[4] = (float)op; // outbound packets + features[5] = (float)ib; // inbound bytes + features[6] = (float)ob; // outbound bytes + features[7] = 0.0;// skipping 7 until we process the pkt_time arrays + + // find the raw features + ndpi_merge_splt_arrays(pkt_len, pkt_time, pkt_len_twin, pkt_time_twin, start_time, start_time_twin, op_n, ip_n, + merged_lens, merged_times); + + // find new duration + for (i = 0; i < op_n+ip_n; i++) { + features[7] += (float)merged_times[i]; + } + + // get the Markov chain representation for the lengths + ndpi_get_mc_rep_lens(merged_lens, mc_lens, op_n+ip_n); + + // get the Markov chain representation for the times + ndpi_get_mc_rep_times(merged_times, mc_times, op_n+ip_n); + + // fill out lens/times in feature vector + for (i = 0; i < MC_BINS_LEN*MC_BINS_LEN; i++) { + features[i+8] = mc_lens[i]; // lengths + } + for (i = 0; i < MC_BINS_TIME*MC_BINS_TIME; i++) { + features[i+8+MC_BINS_LEN*MC_BINS_LEN] = mc_times[i]; // times + } + + // fill out byte distribution features + if(ob+ib > 100 && use_bd) { + for (i = 0; i < NUM_BD_VALUES; i++) { + if(pkt_len_twin != NULL) { + features[i+8+MC_BINS_LEN*MC_BINS_LEN+MC_BINS_TIME*MC_BINS_TIME] = (bd[i]+bd_t[i])/((float)(ob+ib)); + } else { + features[i+8+MC_BINS_LEN*MC_BINS_LEN+MC_BINS_TIME*MC_BINS_TIME] = bd[i]/((float)(ob)); + } } - // get the Markov chain representation for the lengths - ndpi_get_mc_rep_lens(merged_lens, mc_lens, op_n+ip_n); - - // get the Markov chain representation for the times - ndpi_get_mc_rep_times(merged_times, mc_times, op_n+ip_n); - - // fill out lens/times in feature vector - for (i = 0; i < MC_BINS_LEN*MC_BINS_LEN; i++) { - features[i+8] = mc_lens[i]; // lengths + score = ndpi_parameters_bd[0]; + for (i = 1; i < NUM_PARAMETERS_BD_LOGREG; i++) { + score += features[i]*ndpi_parameters_bd[i]; } - for (i = 0; i < MC_BINS_TIME*MC_BINS_TIME; i++) { - features[i+8+MC_BINS_LEN*MC_BINS_LEN] = mc_times[i]; // times + } else { + for (i = 0; i < NUM_PARAMETERS_SPLT_LOGREG; i++) { + score += features[i]*ndpi_parameters_splt[i]; } + } - // fill out byte distribution features - if (ob+ib > 100 && use_bd) { - for (i = 0; i < NUM_BD_VALUES; i++) { - if (pkt_len_twin != NULL) { - features[i+8+MC_BINS_LEN*MC_BINS_LEN+MC_BINS_TIME*MC_BINS_TIME] = (bd[i]+bd_t[i])/((float)(ob+ib)); - } else { - features[i+8+MC_BINS_LEN*MC_BINS_LEN+MC_BINS_TIME*MC_BINS_TIME] = bd[i]/((float)(ob)); - } - } - - score = ndpi_parameters_bd[0]; - for (i = 1; i < NUM_PARAMETERS_BD_LOGREG; i++) { - score += features[i]*ndpi_parameters_bd[i]; - } - } else { - for (i = 0; i < NUM_PARAMETERS_SPLT_LOGREG; i++) { - score += features[i]*ndpi_parameters_splt[i]; - } - } - - score = min(-score,500.0); // check b/c overflow + score = min(-score,500.0); // check b/c overflow - free(merged_lens); - free(merged_times); + ndpi_free(merged_lens); + ndpi_free(merged_times); - return 1.0/(1.0+exp(score)); + return 1.0/(1.0+exp(score)); } /** @@ -545,45 +547,45 @@ ndpi_classify (const unsigned short *pkt_len, const struct timeval *pkt_time, void ndpi_update_params (classifier_type_codes_t param_type, const char *param_file) { - float param; - FILE *fp; - int count = 0; - - switch (param_type) { - case (SPLT_PARAM_TYPE): - count = 0; - fp = fopen(param_file,"r"); - if (fp != NULL) { - while (fscanf(fp, "%f", ¶m) != EOF) { - ndpi_parameters_splt[count] = param; - count++; - if (count >= NUM_PARAMETERS_SPLT_LOGREG) { - break; - } - } - fclose(fp); - } - break; - - case (BD_PARAM_TYPE): - count = 0; - fp = fopen(param_file,"r"); - if (fp != NULL) { - while (fscanf(fp, "%f", ¶m) != EOF) { - ndpi_parameters_bd[count] = param; - count++; - if (count >= NUM_PARAMETERS_BD_LOGREG) { - break; - } - } - fclose(fp); - } - break; - - default: - printf("error: unknown paramerter type (%d)", param_type); - break; + float param; + FILE *fp; + int count = 0; + + switch (param_type) { + case (SPLT_PARAM_TYPE): + count = 0; + fp = fopen(param_file,"r"); + if(fp != NULL) { + while (fscanf(fp, "%f", ¶m) != EOF) { + ndpi_parameters_splt[count] = param; + count++; + if(count >= NUM_PARAMETERS_SPLT_LOGREG) { + break; + } + } + fclose(fp); + } + break; + + case (BD_PARAM_TYPE): + count = 0; + fp = fopen(param_file,"r"); + if(fp != NULL) { + while (fscanf(fp, "%f", ¶m) != EOF) { + ndpi_parameters_bd[count] = param; + count++; + if(count >= NUM_PARAMETERS_BD_LOGREG) { + break; + } + } + fclose(fp); } + break; + + default: + printf("error: unknown paramerter type (%d)", param_type); + break; + } } /* ********************************************************************* @@ -603,22 +605,22 @@ ndpi_update_params (classifier_type_codes_t param_type, const char *param_file) * \return 1 if equal, 0 otherwise */ unsigned int -ndpi_timer_eq(const struct timeval *a, - const struct timeval *b) +ndpi_timer_eq(const pkt_timeval *a, + const pkt_timeval *b) { - if (a->tv_sec == b->tv_sec && a->tv_usec == b->tv_usec) { - return 1; - } + if(a->tv_sec == b->tv_sec && a->tv_usec == b->tv_usec) { + return 1; + } - return 0; + return 0; } unsigned int -ndpi_timer_lt(const struct timeval *a, - const struct timeval *b) +ndpi_timer_lt(const pkt_timeval *a, + const pkt_timeval *b) { - return (a->tv_sec == b->tv_sec) ? - (a->tv_usec < b->tv_usec):(a->tv_sec < b->tv_sec); + return (a->tv_sec == b->tv_sec) ? + (a->tv_usec < b->tv_usec):(a->tv_sec < b->tv_sec); } /** @@ -629,16 +631,16 @@ ndpi_timer_lt(const struct timeval *a, * \return none */ void -ndpi_timer_sub(const struct timeval *a, - const struct timeval *b, - struct timeval *result) +ndpi_timer_sub(const pkt_timeval *a, + const pkt_timeval *b, + pkt_timeval *result) { - result->tv_sec = a->tv_sec - b->tv_sec; - result->tv_usec = a->tv_usec - b->tv_usec; - if (result->tv_usec < 0) { - --result->tv_sec; - result->tv_usec += 1000000; - } + result->tv_sec = a->tv_sec - b->tv_sec; + result->tv_usec = a->tv_usec - b->tv_usec; + if(result->tv_usec < 0) { + --result->tv_sec; + result->tv_usec += 1000000; + } } /** @@ -647,9 +649,9 @@ ndpi_timer_sub(const struct timeval *a, * \return none */ void -ndpi_timer_clear(struct timeval *a) +ndpi_timer_clear(pkt_timeval *a) { - a->tv_sec = a->tv_usec = 0; + a->tv_sec = a->tv_usec = 0; } /** @@ -658,10 +660,10 @@ ndpi_timer_clear(struct timeval *a) * \return unsigned int - Milliseconds */ unsigned int -ndpi_timeval_to_milliseconds(struct timeval ts) +ndpi_timeval_to_milliseconds(pkt_timeval ts) { - unsigned int result = ts.tv_usec / 1000 + ts.tv_sec * 1000; - return result; + unsigned int result = ts.tv_usec / 1000 + ts.tv_sec * 1000; + return result; } /** @@ -670,23 +672,23 @@ ndpi_timeval_to_milliseconds(struct timeval ts) * \return unsigned int - Milliseconds */ unsigned int -ndpi_timeval_to_microseconds(struct timeval ts) +ndpi_timeval_to_microseconds(pkt_timeval ts) { - unsigned int result = ts.tv_usec + ts.tv_sec * 1000 * 1000; - return result; + unsigned int result = ts.tv_usec + ts.tv_sec * 1000 * 1000; + return result; } void ndpi_log_timestamp(char *log_ts, uint32_t log_ts_len) { - struct timeval tv; - time_t nowtime; - struct tm nowtm_r; - char tmbuf[NDPI_TIMESTAMP_LEN]; - - gettimeofday(&tv, NULL); - nowtime = tv.tv_sec; - localtime_r(&nowtime, &nowtm_r); - strftime(tmbuf, NDPI_TIMESTAMP_LEN, "%H:%M:%S", &nowtm_r); - snprintf(log_ts, log_ts_len, "%s.%06ld", tmbuf, (long)tv.tv_usec); + pkt_timeval tv; + time_t nowtime; + struct tm nowtm_r; + char tmbuf[NDPI_TIMESTAMP_LEN]; + + gettimeofday(&tv, NULL); + nowtime = tv.tv_sec; + localtime_r(&nowtime, &nowtm_r); + strftime(tmbuf, NDPI_TIMESTAMP_LEN, "%H:%M:%S", &nowtm_r); + snprintf(log_ts, log_ts_len, "%s.%06ld", tmbuf, (long)tv.tv_usec); } diff --git a/src/lib/ndpi_community_id.c b/src/lib/ndpi_community_id.c new file mode 100644 index 0000000..cc84369 --- /dev/null +++ b/src/lib/ndpi_community_id.c @@ -0,0 +1,380 @@ +/* + * ndpi_community_id.c + * + * Copyright (C) 2011-20 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#ifdef HAVE_CONFIG_H +#include "ndpi_config.h" +#endif + +#include +#include +#include + +#include "ndpi_api.h" +#include "ndpi_config.h" +#include "ndpi_includes.h" + +#include +#ifndef WIN32 +#include +#endif + +#if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ +#include +#endif + +#include "ndpi_sha1.h" + +#define NDPI_ICMP6_ECHO_REQUEST 128 +#define NDPI_ICMP6_ECHO_REPLY 129 +#define NDPI_MLD_LISTENER_QUERY 130 +#define NDPI_MLD_LISTENER_REPORT 131 + +#define NDPI_ROUTER_SOLICIT 133 +#define NDPI_ROUTER_ADVERT 134 +#define NDPI_NEIGHBOR_SOLICIT 135 +#define NDPI_NEIGHBOR_ADVERT 136 + +#define NDPI_ICMP_ECHOREPLY 0 +#define NDPI_ICMP_ECHO 8 +#define NDPI_ICMP_ROUTERADVERT 9 +#define NDPI_ICMP_ROUTERSOLICIT 10 +#define NDPI_ICMP_TIMESTAMP 13 +#define NDPI_ICMP_TIMESTAMPREPLY 14 +#define NDPI_ICMP_INFO_REQUEST 15 +#define NDPI_ICMP_INFO_REPLY 16 +#define NDPI_ICMP_MASKREQ 17 +#define NDPI_ICMP_MASKREPLY 18 + +#define NDPI_ICMP6_WRUREQUEST 139 +#define NDPI_ICMP6_WRUREPLY 140 + +/* **************************************************** */ + +static ssize_t ndpi_community_id_buf_copy(u_int8_t * const dst, const void * const src, ssize_t len) { + if(src) + memcpy(dst, src, len); + else + memset(dst, 0, len); + + return len; +} + +/* **************************************************** */ + +/* + https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L56 +*/ +static u_int8_t ndpi_community_id_icmp_type_to_code_v4(u_int8_t icmp_type, u_int8_t icmp_code, int *is_one_way) { + *is_one_way = 0; + + switch(icmp_type) { + case NDPI_ICMP_ECHO: + return NDPI_ICMP_ECHOREPLY; + case NDPI_ICMP_ECHOREPLY: + return NDPI_ICMP_ECHO; + case NDPI_ICMP_TIMESTAMP: + return NDPI_ICMP_TIMESTAMPREPLY; + case NDPI_ICMP_TIMESTAMPREPLY: + return NDPI_ICMP_TIMESTAMP; + case NDPI_ICMP_INFO_REQUEST: + return NDPI_ICMP_INFO_REPLY; + case NDPI_ICMP_INFO_REPLY: + return NDPI_ICMP_INFO_REQUEST; + case NDPI_ICMP_ROUTERSOLICIT: + return NDPI_ICMP_ROUTERADVERT; + case NDPI_ICMP_ROUTERADVERT: + return NDPI_ICMP_ROUTERSOLICIT; + case NDPI_ICMP_MASKREQ: + return NDPI_ICMP_MASKREPLY; + case NDPI_ICMP_MASKREPLY: + return NDPI_ICMP_MASKREQ; + default: + *is_one_way = 1; + return icmp_code; + } +} + +/* **************************************************** */ + +/* + https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L83 +*/ +static u_int8_t ndpi_community_id_icmp_type_to_code_v6(u_int8_t icmp_type, u_int8_t icmp_code, int *is_one_way) { + *is_one_way = 0; + + switch(icmp_type) { + case NDPI_ICMP6_ECHO_REQUEST: + return NDPI_ICMP6_ECHO_REPLY; + case NDPI_ICMP6_ECHO_REPLY: + return NDPI_ICMP6_ECHO_REQUEST; + case NDPI_ROUTER_SOLICIT: + return NDPI_ROUTER_ADVERT; + case NDPI_ROUTER_ADVERT: + return NDPI_ROUTER_SOLICIT; + case NDPI_NEIGHBOR_SOLICIT: + return NDPI_NEIGHBOR_ADVERT; + case NDPI_NEIGHBOR_ADVERT: + return NDPI_NEIGHBOR_SOLICIT; + case NDPI_MLD_LISTENER_QUERY: + return NDPI_MLD_LISTENER_REPORT; + case NDPI_MLD_LISTENER_REPORT: + return NDPI_MLD_LISTENER_QUERY; + case NDPI_ICMP6_WRUREQUEST: + return NDPI_ICMP6_WRUREPLY; + case NDPI_ICMP6_WRUREPLY: + return NDPI_ICMP6_WRUREQUEST; + // Home Agent Address Discovery Request Message and reply + case 144: + return 145; + case 145: + return 144; + default: + *is_one_way = 1; + return icmp_code; + } +} + +/* **************************************************** */ + +/* + https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L164 +*/ +static int ndpi_community_id_peer_v4_is_less_than(u_int32_t ip1, u_int32_t ip2, u_int16_t p1, u_int16_t p2) { + int comp = memcmp(&ip1, &ip2, sizeof(u_int32_t)); + return comp < 0 || (comp == 0 && p1 < p2); +} + +/* **************************************************** */ + +static int ndpi_community_id_peer_v6_is_less_than(struct ndpi_in6_addr *ip1, struct ndpi_in6_addr *ip2, u_int16_t p1, u_int16_t p2) { + int comp = memcmp(ip1, ip2, sizeof(struct ndpi_in6_addr)); + + return comp < 0 || (comp == 0 && p1 < p2); +} + +/* **************************************************** */ + +void ndpi_string_sha1_hash(const uint8_t *message, size_t len, u_char *hash /* 20-bytes */) { + SHA1_CTX ctx; + + SHA1Init(&ctx); + SHA1Update(&ctx, message, len); + SHA1Final(hash, &ctx); +} + +/* **************************************************** */ + +/* +https://github.com/corelight/community-id-spec/blob/bda913f617389df07cdaa23606e11bbd318e265c/community-id.py#L285 +*/ +static int ndpi_community_id_finalize_and_compute_hash(u_int8_t *comm_buf, u_int16_t off, u_int8_t l4_proto, + u_int16_t src_port, u_int16_t dst_port, + char *hash_buf, u_int8_t hash_buf_len) { + u_int8_t pad = 0; + uint32_t hash[5]; + char *community_id; + + /* L4 proto */ + off += ndpi_community_id_buf_copy(&comm_buf[off], &l4_proto, sizeof(l4_proto)); + + /* Pad */ + off += ndpi_community_id_buf_copy(&comm_buf[off], &pad, sizeof(pad)); + + /* Source and destination ports */ + switch(l4_proto) { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + case IPPROTO_SCTP: + case IPPROTO_UDP: + case IPPROTO_TCP: + off += ndpi_community_id_buf_copy(&comm_buf[off], &src_port, sizeof(src_port)); + off += ndpi_community_id_buf_copy(&comm_buf[off], &dst_port, sizeof(dst_port)); + break; + } + + /* Compute SHA1 */ + ndpi_string_sha1_hash(comm_buf, off, (u_char*)hash); + + /* Base64 encoding */ + community_id = ndpi_base64_encode((u_int8_t*)hash, sizeof(hash)); + + if(community_id == NULL) + return -1; + +#if 0 /* Debug Info */ + printf("Hex output: "); + for(int i = 0; i < off; i++) + printf("%.2x ", comm_buf[i]); + printf("\n"); + + printf("Sha1 sum: "); + for(int i = 0; i < 5; i++) + printf("%.2x ", ntohl(hash[i])); + printf("\n"); + + printf("Base64: %s\n", community_id); +#endif + + if(hash_buf_len < 2 || hash_buf_len-2 < strlen(community_id)+1) { + ndpi_free(community_id); + return -1; + } + + /* Writing hash */ + hash_buf[0] = '1'; + hash_buf[1] = ':'; + strcpy(&hash_buf[2], community_id); + ndpi_free(community_id); + + return 0; +} + +/* **************************************************** */ + +/* + NOTE: + - Leave fields empty/zero when information is missing (e.g. with ICMP ports are zero) + - The hash_buf most be 30+1 bits or longer + - Return code: 0 = OK, -1 otherwise +*/ + +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, + u_int8_t icmp_type, u_int8_t icmp_code, + u_char *hash_buf, u_int8_t hash_buf_len) { + /* + Input buffer (40 bytes) + 2 - Seed + 16 - IPv6 src + 16 - IPv6 dst + 1 - L4 proto + 1 - Pad + 2 - Port src + 2 - Port dst + */ + u_int8_t comm_buf[40] = { 0 }; + u_int16_t off = 0; + u_int16_t seed = 0; + u_int32_t *ip_a_ptr, *ip_b_ptr; + u_int16_t port_a, port_b; + int icmp_one_way = 0; + + /* Adjust the ports according to the specs */ + switch(l4_proto) { + case IPPROTO_ICMP: + src_port = icmp_type; + dst_port = ndpi_community_id_icmp_type_to_code_v4(icmp_type, icmp_code, &icmp_one_way); + break; + case IPPROTO_SCTP: + case IPPROTO_UDP: + case IPPROTO_TCP: + /* src/dst port ok */ + break; + default: + src_port = dst_port = 0; + break; + } + + /* Convert tuple to NBO */ + src_ip = htonl(src_ip); + dst_ip = htonl(dst_ip); + src_port = htons(src_port); + dst_port = htons(dst_port); + + /* + The community id hash doesn't have the definition of client and server, it just sorts IP addresses + and ports to make sure the smaller ip address is the first. This performs this check and + possibly swap client ip and port. + */ + if(icmp_one_way || ndpi_community_id_peer_v4_is_less_than(src_ip, dst_ip, src_port, dst_port)) { + ip_a_ptr = &src_ip, ip_b_ptr = &dst_ip; + port_a = src_port, port_b = dst_port; + } else { + /* swap flow peers */ + ip_a_ptr = &dst_ip, ip_b_ptr = &src_ip; + port_a = dst_port, port_b = src_port; + } + + /* Seed */ + off = ndpi_community_id_buf_copy(&comm_buf[off], &seed, sizeof(seed)); + + /* Source and destination IPs */ + off += ndpi_community_id_buf_copy(&comm_buf[off], ip_a_ptr, sizeof(src_ip)); + off += ndpi_community_id_buf_copy(&comm_buf[off], ip_b_ptr, sizeof(dst_ip)); + + return ndpi_community_id_finalize_and_compute_hash(comm_buf, off, + l4_proto, port_a, port_b, (char*)hash_buf, hash_buf_len); +} + +/* **************************************************** */ + +int ndpi_flowv6_flow_hash(u_int8_t l4_proto, struct ndpi_in6_addr *src_ip, struct ndpi_in6_addr *dst_ip, + u_int16_t src_port, u_int16_t dst_port, + u_int8_t icmp_type, u_int8_t icmp_code, + u_char *hash_buf, u_int8_t hash_buf_len) { + u_int8_t comm_buf[40] = { 0 }; + u_int16_t off = 0; + u_int16_t seed = 0; + struct ndpi_in6_addr *ip_a_ptr, *ip_b_ptr; + u_int16_t port_a, port_b; + int icmp_one_way = 0; + + switch(l4_proto) { + case IPPROTO_ICMPV6: + src_port = icmp_type; + dst_port = ndpi_community_id_icmp_type_to_code_v6(icmp_type, icmp_code, &icmp_one_way); + break; + case IPPROTO_SCTP: + case IPPROTO_UDP: + case IPPROTO_TCP: + /* src/dst port ok */ + break; + default: + src_port = dst_port = 0; + break; + } + + /* Convert tuple to NBO */ + src_port = htons(src_port); + dst_port = htons(dst_port); + + if(icmp_one_way || ndpi_community_id_peer_v6_is_less_than(src_ip, dst_ip, src_port, dst_port)) { + ip_a_ptr = src_ip, ip_b_ptr = dst_ip; + port_a = src_port, port_b = dst_port; + } else { + ip_a_ptr = dst_ip, ip_b_ptr = src_ip; + port_a = dst_port, port_b = src_port; + } + + /* Seed */ + off = ndpi_community_id_buf_copy(&comm_buf[off], &seed, sizeof(seed)); + + /* Source and destination IPs */ + off += ndpi_community_id_buf_copy(&comm_buf[off], ip_a_ptr, sizeof(struct ndpi_in6_addr)); + off += ndpi_community_id_buf_copy(&comm_buf[off], ip_b_ptr, sizeof(struct ndpi_in6_addr)); + + return ndpi_community_id_finalize_and_compute_hash(comm_buf, off, + l4_proto, port_a, port_b, (char*)hash_buf, hash_buf_len); +} + +/* **************************************************** */ diff --git a/src/lib/ndpi_content_match.c.inc b/src/lib/ndpi_content_match.c.inc index 0eed29d..caa8e73 100644 --- a/src/lib/ndpi_content_match.c.inc +++ b/src/lib/ndpi_content_match.c.inc @@ -79,8 +79,67 @@ static ndpi_network host_protocol_list[] = { /* WhatsApp Inc. - List of the WhatsApp server IP addresses and ranges (https://developers.facebook.com/docs/whatsapp/network-debugging/ Dec 21, 2018) + List of the WhatsApp server IP addresses and ranges + https://developers.facebook.com/docs/whatsapp/network-debugging/ + Dec 1st, 2019 */ + { 0x1F0D4033 /* 31.13.64.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4035 /* 31.13.64.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4131 /* 31.13.65.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4132 /* 31.13.65.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4233 /* 31.13.66.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4238 /* 31.13.66.56/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4334 /* 31.13.67.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4430 /* 31.13.68.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4434 /* 31.13.68.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4631 /* 31.13.70.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4632 /* 31.13.70.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4731 /* 31.13.71.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4732 /* 31.13.71.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4830 /* 31.13.72.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4834 /* 31.13.72.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4934 /* 31.13.73.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4A34 /* 31.13.74.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4F35 /* 31.13.79.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4F36 /* 31.13.79.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5030 /* 31.13.80.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5035 /* 31.13.80.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5130 /* 31.13.81.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5135 /* 31.13.81.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5233 /* 31.13.82.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5237 /* 31.13.82.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5331 /* 31.13.83.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5333 /* 31.13.83.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5431 /* 31.13.84.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5433 /* 31.13.84.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5531 /* 31.13.85.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5533 /* 31.13.85.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5631 /* 31.13.86.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5633 /* 31.13.86.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5730 /* 31.13.87.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5733 /* 31.13.87.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5935 /* 31.13.89.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5936 /* 31.13.89.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5A31 /* 31.13.90.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5A33 /* 31.13.90.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5B31 /* 31.13.91.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5B33 /* 31.13.91.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5C30 /* 31.13.92.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5C34 /* 31.13.92.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5D35 /* 31.13.93.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5D36 /* 31.13.93.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5E34 /* 31.13.94.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5E36 /* 31.13.94.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5F32 /* 31.13.95.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5F3F /* 31.13.95.63/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x3216F0A0 /* 50.22.240.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0x32175A80 /* 50.23.90.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0x45ABEF0B /* 69.171.239.11/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x45ABFA34 /* 69.171.250.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x45ABFA36 /* 69.171.250.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x45ABFF0B /* 69.171.255.11/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x66846036 /* 102.132.96.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x66846136 /* 102.132.97.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, { 0x6CA8AE00 /* 108.168.174.0/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0x6CA8B0C0 /* 108.168.176.192/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, { 0x6CA8B100 /* 108.168.177.0/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, @@ -90,6 +149,17 @@ static ndpi_network host_protocol_list[] = { { 0x6CA8FFE3 /* 108.168.255.227/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF00135 /* 157.240.1.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF00136 /* 157.240.1.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00235 /* 157.240.2.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00236 /* 157.240.2.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00336 /* 157.240.3.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00635 /* 157.240.6.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00636 /* 157.240.6.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00735 /* 157.240.7.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00736 /* 157.240.7.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00835 /* 157.240.8.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00836 /* 157.240.8.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00935 /* 157.240.9.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF00936 /* 157.240.9.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF00A35 /* 157.240.10.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF00A36 /* 157.240.10.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF00B35 /* 157.240.11.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, @@ -104,141 +174,91 @@ static ndpi_network host_protocol_list[] = { { 0x9DF01234 /* 157.240.18.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01335 /* 157.240.19.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01336 /* 157.240.19.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00235 /* 157.240.2.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00236 /* 157.240.2.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01434 /* 157.240.20.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01534 /* 157.240.21.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01635 /* 157.240.22.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01636 /* 157.240.22.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01735 /* 157.240.23.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01736 /* 157.240.23.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF01836 /* 157.240.24.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF01936 /* 157.240.25.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF01A36 /* 157.240.26.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF01B36 /* 157.240.27.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF01C33 /* 157.240.28.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF01C37 /* 157.240.28.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01D35 /* 157.240.29.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0x9DF01D36 /* 157.240.29.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00635 /* 157.240.6.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00636 /* 157.240.6.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00735 /* 157.240.7.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00736 /* 157.240.7.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00835 /* 157.240.8.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00836 /* 157.240.8.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00935 /* 157.240.9.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00936 /* 157.240.9.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF01E36 /* 157.240.30.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C034 /* 157.240.192.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C037 /* 157.240.192.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C132 /* 157.240.193.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C137 /* 157.240.193.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C236 /* 157.240.194.54/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C336 /* 157.240.195.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C338 /* 157.240.195.56/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C43C /* 157.240.196.60/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C93C /* 157.240.201.60/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, { 0x9E550060 /* 158.85.0.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x9E55E0A0 /* 158.85.224.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x9E55E920 /* 158.85.233.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x9E55F980 /* 158.85.249.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x9E55FE40 /* 158.85.254.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0x9E5505C0 /* 158.85.5.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0x9E552E80 /* 158.85.46.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0x9E5530E0 /* 158.85.48.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x9E5505C0 /* 158.85.5.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x9E553A00 /* 158.85.58.0/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, + { 0x9E553A20 /* 158.85.58.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0x9E553A60 /* 158.85.58.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0x9E553DC0 /* 158.85.61.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92CA700 /* 169.44.167.0/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C2400 /* 169.44.36.0/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C3940 /* 169.44.57.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C3A40 /* 169.44.58.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C5000 /* 169.44.80.0/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C5280 /* 169.44.82.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C52C0 /* 169.44.82.192/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, + { 0x9E55E0A0 /* 158.85.224.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0x9E55E920 /* 158.85.233.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92C2400 /* 169.44.36.0/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92C5020 /* 169.44.80.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92C5260 /* 169.44.82.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C5300 /* 169.44.83.0/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C5380 /* 169.44.83.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C53C0 /* 169.44.83.192/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C5360 /* 169.44.83.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C5400 /* 169.44.84.0/24 */, 24, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C5540 /* 169.44.85.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92C57A0 /* 169.44.87.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92C5480 /* 169.44.84.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92D4720 /* 169.45.71.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92D5780 /* 169.45.87.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92DA9C0 /* 169.45.169.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92DB660 /* 169.45.182.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92DD240 /* 169.45.210.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92DD6E0 /* 169.45.214.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92DDBE0 /* 169.45.219.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92DEDC0 /* 169.45.237.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92DEE20 /* 169.45.238.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92DF2C0 /* 169.45.242.192/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92DF8A0 /* 169.45.248.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92DF860 /* 169.45.248.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92D4720 /* 169.45.71.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92D4760 /* 169.45.71.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92D5780 /* 169.45.87.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92E6F90 /* 169.46.111.144/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92E34E0 /* 169.46.52.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F8260 /* 169.47.130.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F9100 /* 169.47.145.0/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F9980 /* 169.47.153.128/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92FC0C0 /* 169.47.192.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92FC280 /* 169.47.194.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92FC680 /* 169.47.198.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92FD4A0 /* 169.47.212.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F2180 /* 169.47.33.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92DF8A0 /* 169.45.248.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92F05C0 /* 169.47.5.192/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92F0640 /* 169.47.6.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92F2320 /* 169.47.35.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F2580 /* 169.47.37.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92F2880 /* 169.47.40.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92F2A60 /* 169.47.42.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92F2AA0 /* 169.47.42.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA92F2AC0 /* 169.47.42.192/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F2A60 /* 169.47.42.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F2FA0 /* 169.47.47.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F05C0 /* 169.47.5.192/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA92F0640 /* 169.47.6.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA9307220 /* 169.48.114.32/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93079C0 /* 169.48.121.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA9307A40 /* 169.48.122.64/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA930D2B0 /* 169.48.210.176/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0xA930D2E0 /* 169.48.210.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA930D340 /* 169.48.211.64/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA930D480 /* 169.48.212.128/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, - { 0xA935FA80 /* 169.53.250.128/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA935FC40 /* 169.53.252.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA935FF40 /* 169.53.255.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA9351D80 /* 169.53.29.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA9353020 /* 169.53.48.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92F8260 /* 169.47.130.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92F9980 /* 169.47.153.128/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92FC680 /* 169.47.198.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA92FD4A0 /* 169.47.212.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA93547E0 /* 169.53.71.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA9355140 /* 169.53.81.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA936C1A0 /* 169.54.193.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93602A0 /* 169.54.2.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA936D200 /* 169.54.210.0/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA936DE80 /* 169.54.222.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA9362CE0 /* 169.54.44.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA9363320 /* 169.54.51.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA93637C0 /* 169.54.55.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93764A0 /* 169.55.100.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA9377E40 /* 169.55.126.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA937D260 /* 169.55.210.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA937EBA0 /* 169.55.235.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, + { 0xA936DE80 /* 169.54.222.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA9373C94 /* 169.55.60.148/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xA9373CAA /* 169.55.60.170/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xA93743E0 /* 169.55.67.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA9374580 /* 169.55.69.128/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, + { 0xA93745A0 /* 169.55.69.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA9374A20 /* 169.55.74.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA9374B60 /* 169.55.75.96/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93C8180 /* 169.60.129.128/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93C9300 /* 169.60.147.0/24 */, 24, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93CC540 /* 169.60.197.64/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93C4A70 /* 169.60.74.112/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93C4A80 /* 169.60.74.128/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93C4A20 /* 169.60.74.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA93C4B80 /* 169.60.75.128/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, { 0xA93C4F00 /* 169.60.79.0/24 */, 24, NDPI_PROTOCOL_WHATSAPP }, + { 0xA93C9300 /* 169.60.147.0/24 */, 24, NDPI_PROTOCOL_WHATSAPP }, { 0xA93D6500 /* 169.61.101.0/24 */, 24, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93D5190 /* 169.61.81.144/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93D51E0 /* 169.61.81.224/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93D5240 /* 169.61.82.64/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, - { 0xA93D5480 /* 169.61.84.128/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, + { 0xA93E9F10 /* 169.62.159.16/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, { 0xA93F4080 /* 169.63.64.128/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, { 0xA93F4920 /* 169.63.73.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xA93F4940 /* 169.63.73.64/26 */, 26, NDPI_PROTOCOL_WHATSAPP }, { 0xA93F4C00 /* 169.63.76.0/25 */, 25, NDPI_PROTOCOL_WHATSAPP }, { 0xADC0A220 /* 173.192.162.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xADC0DB80 /* 173.192.219.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xADC0DEA0 /* 173.192.222.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xADC1E680 /* 173.193.230.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xADC1E6C0 /* 173.193.230.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xADC1EF00 /* 173.193.239.0/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xAE24D080 /* 174.36.208.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xAE24D220 /* 174.36.210.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xAE24FBC0 /* 174.36.251.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xAE25F340 /* 174.37.243.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xAE25FB00 /* 174.37.251.0/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xB33CC030 /* 179.60.192.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xB33CC031 /* 179.60.192.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xB33CC033 /* 179.60.192.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xB33CC134 /* 179.60.193.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, @@ -250,7 +270,6 @@ static ndpi_network host_protocol_list[] = { { 0xB8AD9320 /* 184.173.147.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xB8ADA140 /* 184.173.161.64/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xB8ADAD74 /* 184.173.173.116/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0xB8ADB320 /* 184.173.179.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xB93CD835 /* 185.60.216.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xB93CD836 /* 185.60.216.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, { 0xB93CD935 /* 185.60.217.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, @@ -264,101 +283,7 @@ static ndpi_network host_protocol_list[] = { { 0xC60BFB20 /* 198.11.251.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xC6175000 /* 198.23.80.0/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, { 0xD02B73C0 /* 208.43.115.192/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0xD02B754F /* 208.43.117.79/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0xD02B7A80 /* 208.43.122.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4033 /* 31.13.64.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4035 /* 31.13.64.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4131 /* 31.13.65.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4132 /* 31.13.65.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4334 /* 31.13.67.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4430 /* 31.13.68.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4434 /* 31.13.68.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D45F0 /* 31.13.69.240/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4631 /* 31.13.70.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4632 /* 31.13.70.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4731 /* 31.13.71.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4732 /* 31.13.71.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4830 /* 31.13.72.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4834 /* 31.13.72.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4934 /* 31.13.73.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4A34 /* 31.13.74.52/31 */, 31, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4B30 /* 31.13.75.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4B34 /* 31.13.75.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4C51 /* 31.13.76.81/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4C52 /* 31.13.76.82/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4E35 /* 31.13.78.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4E37 /* 31.13.78.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5030 /* 31.13.80.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5035 /* 31.13.80.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5130 /* 31.13.81.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5135 /* 31.13.81.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5233 /* 31.13.82.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5237 /* 31.13.82.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5331 /* 31.13.83.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5333 /* 31.13.83.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5431 /* 31.13.84.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5433 /* 31.13.84.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5531 /* 31.13.85.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5533 /* 31.13.85.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5631 /* 31.13.86.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5633 /* 31.13.86.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5730 /* 31.13.87.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5733 /* 31.13.87.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5831 /* 31.13.88.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5834 /* 31.13.88.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5935 /* 31.13.89.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5936 /* 31.13.89.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5A31 /* 31.13.90.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5A33 /* 31.13.90.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5B31 /* 31.13.91.49/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5B33 /* 31.13.91.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5C30 /* 31.13.92.48/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5C34 /* 31.13.92.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5D35 /* 31.13.93.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5D36 /* 31.13.93.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5E34 /* 31.13.94.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5E36 /* 31.13.94.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5F32 /* 31.13.95.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D5F3F /* 31.13.95.63/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x3216C6CC /* 50.22.198.204/30 */, 30, NDPI_PROTOCOL_WHATSAPP }, - { 0x3216D280 /* 50.22.210.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x3216D220 /* 50.22.210.32/30 */, 30, NDPI_PROTOCOL_WHATSAPP }, - { 0x3216E140 /* 50.22.225.64/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x3216EBF8 /* 50.22.235.248/30 */, 30, NDPI_PROTOCOL_WHATSAPP }, - { 0x3216F0A0 /* 50.22.240.160/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x32175A80 /* 50.23.90.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x32613980 /* 50.97.57.128/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x4B7E0C70 /* 75.126.12.112/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0x4B7E7B40 /* 75.126.123.64/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0x4B7E8AA0 /* 75.126.138.160/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0x4B7E8D50 /* 75.126.141.80/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0x4B7E1430 /* 75.126.20.48/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0x4B7E2720 /* 75.126.39.32/27 */, 27, NDPI_PROTOCOL_WHATSAPP }, - { 0x4B7E51C0 /* 75.126.81.192/28 */, 28, NDPI_PROTOCOL_WHATSAPP }, - { 0x66846036 /* 102.132.96.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x66846136 /* 102.132.97.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00D36 /* 157.240.13.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00E34 /* 157.240.14.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01034 /* 157.240.16.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01234 /* 157.240.18.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF0C034 /* 157.240.192.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF0C132 /* 157.240.193.50/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF0C236 /* 157.240.194.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01434 /* 157.240.20.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01533 /* 157.240.21.51/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01534 /* 157.240.21.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01836 /* 157.240.24.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01936 /* 157.240.25.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01A36 /* 157.240.26.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01B36 /* 157.240.27.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF00336 /* 157.240.3.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x9DF01E36 /* 157.240.30.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0xB33CC134 /* 179.60.193.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4238 /* 31.13.66.56/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4334 /* 31.13.67.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4934 /* 31.13.73.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4A34 /* 31.13.74.52/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, - { 0x1F0D4F35 /* 31.13.79.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + /* Files */ { 0xB93CD835 /* 185.60.216.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP_FILES }, { 0xB93CD836 /* 185.60.216.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP_FILES }, @@ -369,6 +294,119 @@ static ndpi_network host_protocol_list[] = { { 0xB93CDB35 /* 185.60.219.53/32 */, 32, NDPI_PROTOCOL_WHATSAPP_FILES }, { 0xB93CDB36 /* 185.60.219.54/32 */, 32, NDPI_PROTOCOL_WHATSAPP_FILES }, + /* + WhatsApp Web + List of the WhatsApp Web server IP addresses and ranges + */ + { 0x9DF01337 /* 157.240.19.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0143D /* 157.240.20.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF080A0 /* 157.240.128.160/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08121 /* 157.240.129.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08221 /* 157.240.130.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08321 /* 157.240.131.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08421 /* 157.240.132.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08521 /* 157.240.133.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08621 /* 157.240.134.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08721 /* 157.240.135.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08821 /* 157.240.136.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08921 /* 157.240.137.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08A21 /* 157.240.138.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08B22 /* 157.240.139.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08C22 /* 157.240.140.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08D22 /* 157.240.141.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08E22 /* 157.240.142.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF08F22 /* 157.240.143.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09022 /* 157.240.144.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09122 /* 157.240.145.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09222 /* 157.240.146.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09322 /* 157.240.147.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09422 /* 157.240.148.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09722 /* 157.240.151.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09822 /* 157.240.152.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09BA2 /* 157.240.155.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09EA2 /* 157.240.158.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF09FA2 /* 157.240.159.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A0A2 /* 157.240.160.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A222 /* 157.240.162.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A262 /* 157.240.162.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A2A2 /* 157.240.162.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A2E2 /* 157.240.162.226/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A422 /* 157.240.164.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A462 /* 157.240.164.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A522 /* 157.240.165.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A562 /* 157.240.165.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A622 /* 157.240.166.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A662 /* 157.240.166.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A722 /* 157.240.167.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A762 /* 157.240.167.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A822 /* 157.240.168.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0A862 /* 157.240.168.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0ACA0 /* 157.240.172.160/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0AD22 /* 157.240.173.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0AEA2 /* 157.240.174.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0AF22 /* 157.240.175.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B0A2 /* 157.240.176.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B1A2 /* 157.240.177.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B2A2 /* 157.240.178.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B3A2 /* 157.240.179.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B4A2 /* 157.240.180.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B5A2 /* 157.240.181.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B622 /* 157.240.182.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B722 /* 157.240.183.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B822 /* 157.240.184.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B922 /* 157.240.185.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0B962 /* 157.240.185.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BA62 /* 157.240.186.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BB62 /* 157.240.187.98/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BCA2 /* 157.240.188.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BCE2 /* 157.240.188.226/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BD21 /* 157.240.189.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BD61 /* 157.240.189.97/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BDA2 /* 157.240.189.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BDE2 /* 157.240.189.226/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BE21 /* 157.240.190.33/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BE61 /* 157.240.190.97/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BEA2 /* 157.240.190.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BEE2 /* 157.240.190.226/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BF22 /* 157.240.191.34/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BFA2 /* 157.240.191.162/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0BFE2 /* 157.240.191.226/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C53C /* 157.240.197.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C53D /* 157.240.197.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C63C /* 157.240.198.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C63D /* 157.240.198.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C83C /* 157.240.200.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0C83D /* 157.240.200.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CB3C /* 157.240.203.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CB3D /* 157.240.203.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CC3C /* 157.240.204.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CC3D /* 157.240.204.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CE3C /* 157.240.206.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CE3D /* 157.240.206.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CF3C /* 157.240.207.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0CF3D /* 157.240.207.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D03C /* 157.240.208.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D03D /* 157.240.208.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D13C /* 157.240.209.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D13D /* 157.240.209.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D23C /* 157.240.210.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D23D /* 157.240.210.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D43C /* 157.240.212.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D43D /* 157.240.212.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D53C /* 157.240.213.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D53D /* 157.240.213.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D63C /* 157.240.214.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D63D /* 157.240.214.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D83C /* 157.240.216.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0D83D /* 157.240.216.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0DE3C /* 157.240.222.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x9DF0DE3D /* 157.240.222.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4239 /* 31.13.66.57/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4C3C /* 31.13.76.60/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D4C3D /* 31.13.76.61/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5C3E /* 31.13.92.62/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + { 0x1F0D5D37 /* 31.13.93.55/32 */, 32, NDPI_PROTOCOL_WHATSAPP }, + /* WeChat origin AS132203, AS132591, AS45090 @@ -488,6 +526,7 @@ static ndpi_network host_protocol_list[] = { { 0x344E0000 /* 52.78.0.0/16 */, 16, NDPI_PROTOCOL_AMAZON }, { 0x344F0000 /* 52.79.0.0/16 */, 16, NDPI_PROTOCOL_AMAZON }, { 0x34520000 /* 52.82.0.0/14 */, 14, NDPI_PROTOCOL_AMAZON }, + { 0x34540000 /* 52.84.0.0/14 */, 14, NDPI_PROTOCOL_AMAZON }, { 0x34580000 /* 52.88.0.0/13 */, 13, NDPI_PROTOCOL_AMAZON }, { 0x345A0000 /* 52.90.0.0/15 */, 15, NDPI_PROTOCOL_AMAZON }, { 0x345EE000 /* 52.94.224.0/19 */, 19, NDPI_PROTOCOL_AMAZON }, @@ -848,20 +887,19 @@ static ndpi_network host_protocol_list[] = { { 0xC71B8000 /* 199.27.128.0/21 */, 21, NDPI_PROTOCOL_CLOUDFLARE }, /* - OFFICE 365 + Microsoft 365 (Formerly known as Office 365) */ - - { 0x0D6B0100 /* 13.107.1.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0300 /* 13.107.3.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0400 /* 13.107.4.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0500 /* 13.107.5.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0600 /* 13.107.6.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0700 /* 13.107.7.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0900 /* 13.107.9.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0C00 /* 13.107.12.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0D00 /* 13.107.13.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B0F00 /* 13.107.15.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, - { 0x0D6B1000 /* 13.107.16.0 */, 24 , NDPI_PROTOCOL_OFFICE_365 }, + { 0x0D6B0100 /* 13.107.1.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0300 /* 13.107.3.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0400 /* 13.107.4.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0500 /* 13.107.5.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0600 /* 13.107.6.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0700 /* 13.107.7.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0900 /* 13.107.9.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0C00 /* 13.107.12.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0D00 /* 13.107.13.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0F00 /* 13.107.15.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B1000 /* 13.107.16.0 */, 24 , NDPI_PROTOCOL_MICROSOFT_365 }, /* OCS GO (Orange Cinéma Séries) @@ -871,7 +909,7 @@ static ndpi_network host_protocol_list[] = { /* Teamviewer 159.122.189.32-63 */ - { 0x9F7ABD30 /* 159.122.189.32 */, 21, NDPI_PROTOCOL_TEAMVIEWER }, + { 0x9F7ABD20 /* 159.122.189.32 */, 27, NDPI_PROTOCOL_TEAMVIEWER }, #if 0 /* @@ -894,6 +932,7 @@ static ndpi_network host_protocol_list[] = { { 0xD873D000 /* 216.115.208.0 */, 20, NDPI_PROTOCOL_CITRIX }, { 0xD8DB7000 /* 216.219.112.0 */, 20, NDPI_PROTOCOL_CITRIX }, + { 0xADC70000 /* 173.199.0.0 */, 18, NDPI_PROTOCOL_CITRIX }, /* Cisco Webex LLC @@ -967,7 +1006,6 @@ static ndpi_network host_protocol_list[] = { { 0x9D38C600 /* 157.56.198.0 */, 26, NDPI_PROTOCOL_SKYPE }, { 0x9D3C0000 /* 157.60.0.0 */, 16, NDPI_PROTOCOL_SKYPE }, { 0x9D360000 /* 157.54.0.0 */, 15, NDPI_PROTOCOL_SKYPE }, - { 0x0D400000 /* 13.64.0.0 */, 11, NDPI_PROTOCOL_SKYPE }, { 0x0D6B0380 /* 13.107.3.128 */, 32, NDPI_PROTOCOL_SKYPE }, { 0x0D6B0381 /* 13.107.3.129 */, 32, NDPI_PROTOCOL_SKYPE }, { 0x6FDD4000 /* 111.221.64.0 */, 18, NDPI_PROTOCOL_SKYPE }, @@ -977,6 +1015,8 @@ static ndpi_network host_protocol_list[] = { { 0x4237DF00 /* 65.55.223.0 */, 26, NDPI_PROTOCOL_SKYPE }, { 0x17600000 /* 23.96.0.0 */, 13, NDPI_PROTOCOL_SKYPE }, { 0x34724A05 /* 52.114.74.5 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x14B40000 /* 20.180.0.0 */, 14, NDPI_PROTOCOL_SKYPE }, + { 0x14B80000 /* 20.184.0.0 */, 13, NDPI_PROTOCOL_SKYPE }, /* Blizzard Entertainment, Inc @@ -1114,14 +1154,47 @@ static ndpi_network host_protocol_list[] = { { 0xB2A4F550 /* 178.164.245.80/32 */, 32, NDPI_PROTOCOL_BITTORRENT }, { 0xAE597B3E /* 174.89.123.62/32 */, 32, NDPI_PROTOCOL_BITTORRENT }, + /* + Ethereum + curl -s https://raw.githubusercontent.com/ethereum/go-ethereum/master/params/bootnodes.go | grep -v '^/' | grep ':' | cut -d '@' -f 2 | cut -d ':' -f 1 + */ + { 0x128A6C43 /* 18.138.108.67/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x03D12D4F /* 3.209.45.79/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x22FF1771 /* 34.255.23.113/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x239EF497 /* 35.158.244.151/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34BBCF1B /* 52.187.207.27/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0xBFEAA2C6 /* 191.234.162.198/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34E7A56C /* 52.231.165.108/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x682AD919 /* 104.42.217.25/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x050153E2 /* 5.1.83.226/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34B0070A /* 52.176.7.10/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34B0644D /* 52.176.100.77/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34E8F398 /* 52.232.243.152/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0xC051D0DF /* 192.81.208.223/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34A92A65 /* 52.169.42.101/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34039EB8 /* 52.3.158.184/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x9F591CD3 /* 159.89.28.211/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x338D4E35 /* 51.141.78.53/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x0D5D3689 /* 13.93.54.137/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x5EED3672 /* 94.237.54.114/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x34409B93 /* 52.64.155.147/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0xD5BA1052 /* 213.186.16.82/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x343888C8 /* 52.56.136.200/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x23B1E2A8 /* 35.177.226.168/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x287603DF /* 40.118.3.223/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x287603DF /* 40.118.3.223/32 */, 32, NDPI_PROTOCOL_MINING }, + { 0x287603DF /* 40.118.3.223/32 */, 32, NDPI_PROTOCOL_MINING }, + /* Tor - http://torstatus.blutmagie.de/ip_list_all.php/Tor_ip_list_ALL.csv + https://torstatus.rueckgr.at/ip_list_all.php/Tor_ip_list_ALL.csv From 09.09.2019 - + + https://panwdbl.appspot.com/lists/ettor.txt + Use utils/toripaddr2list.py to convert them */ - + { 0x0122a339 /* 1.34.163.57/32 */, 32, NDPI_PROTOCOL_TOR }, { 0x01EA0B8E /* 1.234.11.142/32 */, 32, NDPI_PROTOCOL_TOR }, { 0x01F4E33D /* 1.244.227.61/32 */, 32, NDPI_PROTOCOL_TOR }, @@ -8217,9 +8290,6 @@ static ndpi_network host_protocol_list[] = { { 0xD040C900 /* 208.64.201.0/22 */, 22, NDPI_PROTOCOL_STEAM }, { 0xD04EA400 /* 208.78.164.0/22 */, 22, NDPI_PROTOCOL_STEAM }, - /* Line */ - { 0x7DD1FC00 /* 125.209.252.0/24 */, 24, NDPI_PROTOCOL_LINE }, - /* Zoom video conference app. */ { 0x03501480 /* 3.80.20.128/25 */, 25, NDPI_PROTOCOL_ZOOM }, { 0x03787900 /* 3.120.121.0/25 */, 25, NDPI_PROTOCOL_ZOOM }, @@ -8307,6 +8377,231 @@ static ndpi_network host_protocol_list[] = { { 0xA7CEDA82 /* 167.206.218.130/32*/, 32, NDPI_PROTOCOL_PS_VUE }, { 0xA7CEDA8A /* 167.206.218.138/32*/, 32, NDPI_PROTOCOL_PS_VUE }, + /* Bloomberg */ + { 0xD086A100 /* 208.134.161.0/24 */, 24, NDPI_PROTOCOL_BLOOMBERG }, + { 0xCDB7F600 /* 205.183.246.0/24 */, 24, NDPI_PROTOCOL_BLOOMBERG }, + { 0xC769B000 /* 199.105.176.0/21 */, 21, NDPI_PROTOCOL_BLOOMBERG }, + { 0xC769B800 /* 199.105.184.0/23 */, 23, NDPI_PROTOCOL_BLOOMBERG }, + { 0x45B80000 /* 69.184.0.0/13 */, 13, NDPI_PROTOCOL_BLOOMBERG }, + { 0xA02B0000 /* 160.43.0.0/16 */, 24, NDPI_PROTOCOL_BLOOMBERG }, + { 0xCE9C3500 /* 206.156.53.0/24 */, 24, NDPI_PROTOCOL_BLOOMBERG }, + { 0xCDD87000 /* 205.216.112.0/24 */, 24, NDPI_PROTOCOL_BLOOMBERG }, + { 0xD0163800 /* 208.22.56.0/24 */, 24, NDPI_PROTOCOL_BLOOMBERG }, + { 0xD0163900 /* 208.22.57.0/24 */, 24, NDPI_PROTOCOL_BLOOMBERG }, + { 0x45BFC000 /* 69.191.192.0/18 */, 18, NDPI_PROTOCOL_BLOOMBERG }, + + /* AnyDesk */ + { 0x3353EF8E /* 51.83.239.142/31 */, 31, NDPI_PROTOCOL_ANYDESK }, + { 0x3353EF90 /* 51.83.239.144/31 */, 31, NDPI_PROTOCOL_ANYDESK }, + { 0x3353EEC8 /* 51.83.238.200/29 */, 29, NDPI_PROTOCOL_ANYDESK }, + { 0x3353EED0 /* 51.83.238.208/29 */, 29, NDPI_PROTOCOL_ANYDESK }, + { 0x3353EED8 /* 51.83.238.216/30 */, 30, NDPI_PROTOCOL_ANYDESK }, + { 0x3353EEDC /* 51.83.238.220/31 */, 31, NDPI_PROTOCOL_ANYDESK }, + + /* + Microsoft + + [JSON] https://endpoints.office.com/endpoints/worldwide?clientrequestid=b10c5ed1-bad1-445f-b386-b919946339a7 + [HTML] https://docs.microsoft.com/en-us/office365/enterprise/urls-and-ip-address-ranges + */ + { 0x0D6B0698 /* 13.107.6.152/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B120A /* 13.107.18.10/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B8000 /* 13.107.128.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x1767A000 /* 23.103.160.0/20 */, 20, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28600000 /* 40.96.0.0/13 */, 13, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28680000 /* 40.104.0.0/15 */, 15, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34600000 /* 52.96.0.0/14 */, 14, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x83FD21D7 /* 131.253.33.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x84F50000 /* 132.245.0.0/16 */, 16, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x96AB2000 /* 150.171.32.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xBFEA8C00 /* 191.234.140.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xCC4FC5D7 /* 204.79.197.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0698 /* 13.107.6.152/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B120A /* 13.107.18.10/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B8000 /* 13.107.128.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x1767A000 /* 23.103.160.0/20 */, 20, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28600000 /* 40.96.0.0/13 */, 13, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28680000 /* 40.104.0.0/15 */, 15, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x83FD21D7 /* 131.253.33.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x84F50000 /* 132.245.0.0/16 */, 16, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x96AB2000 /* 150.171.32.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xBFEA8C00 /* 191.234.140.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xCC4FC5D7 /* 204.79.197.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0698 /* 13.107.6.152/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B120A /* 13.107.18.10/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B8000 /* 13.107.128.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x1767A000 /* 23.103.160.0/20 */, 20, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28600000 /* 40.96.0.0/13 */, 13, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28680000 /* 40.104.0.0/15 */, 15, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x83FD21D7 /* 131.253.33.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x84F50000 /* 132.245.0.0/16 */, 16, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x96AB2000 /* 150.171.32.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xBFEA8C00 /* 191.234.140.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xCC4FC5D7 /* 204.79.197.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B0698 /* 13.107.6.152/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B120A /* 13.107.18.10/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B8000 /* 13.107.128.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x1767A000 /* 23.103.160.0/20 */, 20, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28600000 /* 40.96.0.0/13 */, 13, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28680000 /* 40.104.0.0/15 */, 15, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x83FD21D7 /* 131.253.33.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x84F50000 /* 132.245.0.0/16 */, 16, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x96AB2000 /* 150.171.32.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xBFEA8C00 /* 191.234.140.0/22 */, 22, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xCC4FC5D7 /* 204.79.197.215/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x285C0000 /* 40.92.0.0/15 */, 15, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x286B0000 /* 40.107.0.0/16 */, 16, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34640000 /* 52.100.0.0/14 */, 14, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34EE4E58 /* 52.238.78.88/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x682F0000 /* 104.47.0.0/17 */, 17, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x285C0000 /* 40.92.0.0/15 */, 15, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x286B0000 /* 40.107.0.0/16 */, 16, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34640000 /* 52.100.0.0/14 */, 14, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x682F0000 /* 104.47.0.0/17 */, 17, NDPI_PROTOCOL_MICROSOFT_365 }, + /* ** */ + { 0x0D6B8800 /* 13.107.136.0/22 */, 22, NDPI_PROTOCOL_MS_ONE_DRIVE }, + { 0x286C8000 /* 40.108.128.0/17 */, 17, NDPI_PROTOCOL_MS_ONE_DRIVE }, + { 0x34680000 /* 52.104.0.0/14 */, 14, NDPI_PROTOCOL_MS_ONE_DRIVE }, + { 0x68928000 /* 104.146.128.0/17 */, 17, NDPI_PROTOCOL_MS_ONE_DRIVE }, + { 0x96AB2800 /* 150.171.40.0/22 */, 22, NDPI_PROTOCOL_MS_ONE_DRIVE }, + /* ** */ + { 0x0D6B4000 /* 13.107.64.0/18 */, 18, NDPI_PROTOCOL_SKYPE }, + { 0x34700000 /* 52.112.0.0/14 */, 14, NDPI_PROTOCOL_SKYPE }, + { 0x0D4697D8 /* 13.70.151.216/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D477FC5 /* 13.71.127.197/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D48F573 /* 13.72.245.115/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D490178 /* 13.73.1.120/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D4B7EA9 /* 13.75.126.169/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D59F071 /* 13.89.240.113/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D6B0300 /* 13.107.3.0/24 */, 24, NDPI_PROTOCOL_SKYPE }, + { 0x0D6B4000 /* 13.107.64.0/18 */, 18, NDPI_PROTOCOL_SKYPE }, + { 0x338C9BEA /* 51.140.155.234/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x338CCBBE /* 51.140.203.190/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x338D334C /* 51.141.51.76/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34700000 /* 52.112.0.0/14 */, 14, NDPI_PROTOCOL_SKYPE }, + { 0x34A37ED7 /* 52.163.126.215/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34AA1543 /* 52.170.21.67/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34ACB912 /* 52.172.185.18/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34B25E02 /* 52.178.94.2/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34B2A18B /* 52.178.161.139/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34E41960 /* 52.228.25.96/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34EE778D /* 52.238.119.141/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34F217BD /* 52.242.23.189/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34F4A0CF /* 52.244.160.207/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x68D70B90 /* 104.215.11.144/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x68D73EC3 /* 104.215.62.195/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x8A5BEDED /* 138.91.237.237/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D4697D8 /* 13.70.151.216/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D477FC5 /* 13.71.127.197/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D48F573 /* 13.72.245.115/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D490178 /* 13.73.1.120/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D4B7EA9 /* 13.75.126.169/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D59F071 /* 13.89.240.113/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D6B0300 /* 13.107.3.0/24 */, 24, NDPI_PROTOCOL_SKYPE }, + { 0x0D6B4000 /* 13.107.64.0/18 */, 18, NDPI_PROTOCOL_SKYPE }, + { 0x338C9BEA /* 51.140.155.234/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x338CCBBE /* 51.140.203.190/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x338D334C /* 51.141.51.76/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34700000 /* 52.112.0.0/14 */, 14, NDPI_PROTOCOL_SKYPE }, + { 0x34A37ED7 /* 52.163.126.215/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34AA1543 /* 52.170.21.67/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34ACB912 /* 52.172.185.18/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34B25E02 /* 52.178.94.2/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34B2A18B /* 52.178.161.139/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34E41960 /* 52.228.25.96/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34EE778D /* 52.238.119.141/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34F217BD /* 52.242.23.189/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34F4A0CF /* 52.244.160.207/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x68D70B90 /* 104.215.11.144/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x68D73EC3 /* 104.215.62.195/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x8A5BEDED /* 138.91.237.237/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D4697D8 /* 13.70.151.216/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D477FC5 /* 13.71.127.197/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D48F573 /* 13.72.245.115/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D490178 /* 13.73.1.120/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D4B7EA9 /* 13.75.126.169/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D59F071 /* 13.89.240.113/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x0D6B0300 /* 13.107.3.0/24 */, 24, NDPI_PROTOCOL_SKYPE }, + { 0x0D6B4000 /* 13.107.64.0/18 */, 18, NDPI_PROTOCOL_SKYPE }, + { 0x338C9BEA /* 51.140.155.234/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x338CCBBE /* 51.140.203.190/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x338D334C /* 51.141.51.76/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34700000 /* 52.112.0.0/14 */, 14, NDPI_PROTOCOL_SKYPE }, + { 0x34A37ED7 /* 52.163.126.215/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34AA1543 /* 52.170.21.67/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34ACB912 /* 52.172.185.18/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34B25E02 /* 52.178.94.2/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34B2A18B /* 52.178.161.139/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34E41960 /* 52.228.25.96/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34EE778D /* 52.238.119.141/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34F217BD /* 52.242.23.189/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x34F4A0CF /* 52.244.160.207/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x68D70B90 /* 104.215.11.144/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x68D73EC3 /* 104.215.62.195/32 */, 32, NDPI_PROTOCOL_SKYPE }, + { 0x8A5BEDED /* 138.91.237.237/32 */, 32, NDPI_PROTOCOL_SKYPE }, + /* ** */ + { 0x0D6B06AB /* 13.107.6.171/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B8C06 /* 13.107.140.6/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x346C0000 /* 52.108.0.0/14 */, 14, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34EE6A74 /* 52.238.106.116/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34F796BF /* 52.247.150.191/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6A0480 /* 13.106.4.128/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6A3800 /* 13.106.56.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x14BE8000 /* 20.190.128.0/18 */, 18, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x287E0000 /* 40.126.0.0/18 */, 18, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x4136AA80 /* 65.54.170.128/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x682CDA80 /* 104.44.218.128/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x682CFE80 /* 104.44.254.128/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x682CFF00 /* 104.44.255.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x86AA4300 /* 134.170.67.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x86AAAC80 /* 134.170.172.128/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D372D80 /* 157.55.45.128/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D378200 /* 157.55.130.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D379100 /* 157.55.145.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D379B00 /* 157.55.155.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D37E3C0 /* 157.55.227.192/26 */, 26, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0xBFE80280 /* 191.232.2.128/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D507D16 /* 13.80.125.22/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D5B5BF3 /* 13.91.91.243/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B069C /* 13.107.6.156/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B07BE /* 13.107.7.190/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B099C /* 13.107.9.156/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28519C9A /* 40.81.156.154/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x285ADAC6 /* 40.90.218.198/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x346C0000 /* 52.108.0.0/14 */, 14, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34AE38B4 /* 52.174.56.180/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34B74B3E /* 52.183.75.62/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34B8A552 /* 52.184.165.82/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x682AE65B /* 104.42.230.91/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D379100 /* 157.55.145.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D379B00 /* 157.55.155.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D37E3C0 /* 157.55.227.192/26 */, 26, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D507D16 /* 13.80.125.22/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D5B5BF3 /* 13.91.91.243/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B069C /* 13.107.6.156/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B07BE /* 13.107.7.190/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B099C /* 13.107.9.156/31 */, 31, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x28519C9A /* 40.81.156.154/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x285ADAC6 /* 40.90.218.198/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x346C0000 /* 52.108.0.0/14 */, 14, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34AE38B4 /* 52.174.56.180/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34B74B3E /* 52.183.75.62/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34B8A552 /* 52.184.165.82/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x682AE65B /* 104.42.230.91/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D379100 /* 157.55.145.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D379B00 /* 157.55.155.0/25 */, 25, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x9D37E3C0 /* 157.55.227.192/26 */, 26, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B06AB /* 13.107.6.171/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x0D6B8C06 /* 13.107.140.6/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x346C0000 /* 52.108.0.0/14 */, 14, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34EE6A74 /* 52.238.106.116/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + { 0x34F796BF /* 52.247.150.191/32 */, 32, NDPI_PROTOCOL_MICROSOFT_365 }, + /* ** */ + { 0x0D400000 /* 13.64.0.0 */, 11, NDPI_PROTOCOL_MICROSOFT }, + { 0x0D600000 /* 13.96.0.0 */, 13, NDPI_PROTOCOL_MICROSOFT }, + { 0x0D680000 /* 13.104.0.0 */, 14, NDPI_PROTOCOL_MICROSOFT }, + + /* End */ { 0x0, 0, 0 } }; @@ -8365,487 +8660,593 @@ static ndpi_network host_protocol_list[] = { /* ****************************************************** */ -/* +static ndpi_protocol_match host_match[] = + { + { "s3.ll.dash.row.aiv-cdn.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "s3-dub.cf.dash.row.aiv-cdn.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "dmqdd6hw24ucf.cloudfront.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "d25xi40x97liuc.cloudfront.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { ".aiv-delivery.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { ".aiv-cdn.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "1s3.lvlt.dash.us.aiv-cdn.net.c.footprint.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + { ".s.loris.llnwd.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "atv-ext.amazon.com", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "c.media-amazon.com", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "amazon.", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "amazon.com", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "images-amazon.com", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "amazonaws.com", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "amazon-adsystem.com", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { ".aws.", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { ".cloudfront.net", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { ".us-west-2.compute.amazonaws.com","Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".teamviewer.com", "Teamviewer", NDPI_PROTOCOL_TEAMVIEWER, NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, NDPI_PROTOCOL_ACCEPTABLE }, + + /* Microsoft + Azure */ + { ".azure.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_SAFE }, + { ".windows.net", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_SAFE }, + { ".microsoft.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_SAFE }, + + { ".bloombergvault.com", "Bloomberg", NDPI_PROTOCOL_BLOOMBERG, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_SAFE }, + { ".bloomberg.com", "Bloomberg", NDPI_PROTOCOL_BLOOMBERG, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_SAFE }, + + { ".push.apple.com", "ApplePush", NDPI_PROTOCOL_APPLE_PUSH, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_SAFE }, + { ".apple-dns.net", "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".mzstatic.com", "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".aaplimg.com", "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "aaplimg.com", "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { ".apple.com", "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".icloud.com", "AppleiCloud", NDPI_PROTOCOL_APPLE_ICLOUD, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "iosapps.itunes.apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, /* iOS */ + { "osxapps.itunes.apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, /* MacOS */ + { "buy.itunes.apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { "su.itunes.apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { "se.itunes.apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { "myapp.itunes.apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { "swscan.apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { "itunes-apple.com", "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { "itunes.apple.com", "AppleiTunes", NDPI_PROTOCOL_APPLE_ITUNES, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + { "tlnk.io", "AppleiTunes", NDPI_PROTOCOL_APPLE_ITUNES, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + { ".wbagora.com", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_UNRATED }, + { ".wbplay.com", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_UNRATED }, + { ".xbox.com", "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".xboxlive.com", "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".xboxlive.com.akadns.net", "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".xboxlive.com.c.footprint.net", "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { "e13555.b.akamaiedge.net", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { "e1800.d.akamaiedge.net", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { "e1879.e7.akamaiedge.net", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + + { ".cnn.c", "CNN", NDPI_PROTOCOL_CNN, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".cnn.net", "CNN", NDPI_PROTOCOL_CNN, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + + { ".dropbox.com", "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { ".dropboxstatic.com", "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { ".dropbox-dns.com", "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { "log.getdropbox.com", "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".ebay.", "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, /* or FUN */ + { ".ebay.com", "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, + { ".ebaystatic.com", "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, + { ".ebaydesc.com", "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, + { ".ebayrtm.com", "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, + { ".ebaystratus.com", "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, + { ".ebayimg.com", "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, + + /* Detected "instagram.c10r.facebook.com". Omitted "*amazonaws.com" and "*facebook.com" CDNs e.g. "ig-telegraph-shv-04-frc3.facebook.com" */ + { ".instagram.", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "instagram.", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".cdninstagram.com", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { "igcdn-photos-", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "instagramimages-", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "instagramstatic-", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { "facebook.com", "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "fbstatic-a.akamaihd.net", "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".fbcdn.net", "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "fbcdn-", "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".facebook.net", "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".fbsbx.com", "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { "speedtest.", "Ookla", NDPI_PROTOCOL_OOKLA, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_SAFE }, + { ".ooklaserver.net", "Ookla", NDPI_PROTOCOL_OOKLA, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_SAFE }, + + { "ntop.org", "ntop", NDPI_PROTOCOL_NTOP, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_SAFE }, + + { "docs.googleusercontent.com", "GoogleDocs", NDPI_PROTOCOL_GOOGLE_DOCS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "docs.google.com", "GoogleDocs", NDPI_PROTOCOL_GOOGLE_DOCS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + + { "drive-thirdparty.googleusercontent.com", "GoogleDrive", NDPI_PROTOCOL_GOOGLE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { "drive.google.com", "GoogleDrive", NDPI_PROTOCOL_GOOGLE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + + { "android.clients.google.com", "PlayStore", NDPI_PROTOCOL_PLAYSTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + + /* + https://www.fastvue.co/sophos/blog/google-data-saver-affect-security-confidentiality-reporting/ + Used by Google Chrome Lite Mode for Android + + This traffic will bypass checks and blocks as it will include all the communications from/to + the browser instead of using the standard communication mechanisms SSL/HTTP(S)/DNS + + https://github.com/curl/curl/wiki/DNS-over-HTTPS + */ + { ".googlezip.net", "DataSaver", NDPI_PROTOCOL_DATASAVER, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "datasaver.googleapis.com", "DataSaver", NDPI_PROTOCOL_DATASAVER, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + /* http://check.googlezip.net/connect [check browser connectivity] */ + // { ".googlezip.net", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + + /* + https://github.com/bambenek/block-doh/blob/master/db.doh-redirect + https://github.com/curl/curl/wiki/DNS-over-HTTPS + */ + { "dns.google", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "mozilla.cloudflare-dns.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, /* Firefox */ + { "cloudflare-dns.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "commons.host", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.li", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns9.quad9.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.opendns.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.dns.sb", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.netweaver.uk", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.dns-over-https.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "jp.tiarap.org", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.dnsoverhttps.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.powerdns.org", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "adblock.mydns.network", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "jp.tiar.app", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.crypto.sx", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.quad9.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.containerpi.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "ibksturm.synology.me", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.captnemo.in", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.rubyfish.cn", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.42l.fr", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns-family.adguard.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "appliedprivacy.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.cleanbrowsing.org", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns10.quad9.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh-ch.blahdns.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.seby.io", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.adguard.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "ibuki.cgnat.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "jcdns.fun", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh-2.seby.io", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.tiar.app", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.dnswarden.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh-de.blahdns.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh-jp.blahdns.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.appliedprivacy.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.tiarap.org", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.armadillodns.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns-nyc.aaflalo.me", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.aa.net.uk", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.aaflalo.me", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns11.quad9.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.nextdns.io", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.securedns.eu", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "rdns.faelix.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "captnemo.in", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.dnshome.de", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.dnslify.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "resolver-eu.lelux.fi", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.bortzmeyer.fr", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { ".doh.dns.snopyta.org", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "jarjar.meganerd.nl", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "rumpelsepp.org", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dnsnl.alekberg.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dnses.alekberg.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dnsse.alekberg.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "odvr.nic.cz", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.dnscrypt.ca", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns1.dnscrypt.ca", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns2.dnscrypt.ca", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.digitale-gesellschaft.ch", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns1.digitale-gesellschaft.ch", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "dns.cloudflare.com", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { "doh.ffmuc.net", "DoH_DoT", NDPI_PROTOCOL_DOH_DOT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + + + /* + See https://better.fyi/trackers/ + + DoubleClick by Google (2mdn.net) + DoubleClick by Google (doubleclick.net) + DoubleClick by Google + Google AdSense by Google (google.com) + Google AdSense by Google (google.se) + Google AdSense by Google (googleadservices.com) + Google Analytics by Google (google-analytics.com) + Google APIs by Google (ajax.googleapis.com) + Google Fonts by Google (fonts.googleapis.com) + Google Interactive Media Ads (imasdk.googleapis.com) + Google Syndication (googlesyndication.com) + Google Tag Manager by Google (googletagmanager.com) + Google Tag Manager by Google (googletagservices.com) + Gstatic by Google (gstatic.com) + */ + + /* Google Advertisements */ + { ".googlesyndication.com", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, + { "googleads.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, + { ".doubleclick.net", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, + { "googleadservices.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, + { ".2mdn.net", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, + { ".dmtry.com", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, + { "google-analytics.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, + { "gtv1.com", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + /* Google Hangout */ + { "images2-hangout-opensocial.googleusercontent.com", "GoogleHangout", NDPI_PROTOCOL_HANGOUT_DUO, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + + /* Google Services */ + { "googleapis.com", "GoogleServices", NDPI_PROTOCOL_GOOGLE_SERVICES, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { ".googletagservices.com", "GoogleServices", NDPI_PROTOCOL_GOOGLE_SERVICES, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "mtalk.google.com", "GoogleServices", NDPI_PROTOCOL_GOOGLE_SERVICES, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + { "plus.google.com", "GooglePlus", NDPI_PROTOCOL_GOOGLE_PLUS, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "plus.url.google.com", "GooglePlus", NDPI_PROTOCOL_GOOGLE_PLUS, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { "googleusercontent.com", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "1e100.net", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + { "maps.google.", "GoogleMaps", NDPI_PROTOCOL_GOOGLE_MAPS, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "maps.gstatic.com", "GoogleMaps", NDPI_PROTOCOL_GOOGLE_MAPS, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + + { ".gmail.", "GMail", NDPI_PROTOCOL_GMAIL, NDPI_PROTOCOL_CATEGORY_MAIL, NDPI_PROTOCOL_ACCEPTABLE }, + { "mail.google.", "GMail", NDPI_PROTOCOL_GMAIL, NDPI_PROTOCOL_CATEGORY_MAIL, NDPI_PROTOCOL_ACCEPTABLE }, + + { "google.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".google.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".gstatic.com", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "ggpht.com", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + + { "mail.outlook.com", "Hotmail", NDPI_PROTOCOL_HOTMAIL, NDPI_PROTOCOL_CATEGORY_MAIL, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".last.fm", "LastFM", NDPI_PROTOCOL_LASTFM, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + + { "netflix.com", "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "nflxext.com", "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "nflximg.com", "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "nflximg.net", "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "nflxvideo.net", "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "nflxso.net", "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + + { ".skype.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { ".skypeassets.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { ".skypedata.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { ".skypeecs-", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { ".skypeforbusiness.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { ".lync.com", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { "e7768.b.akamaiedge.net", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { "e4593.dspg.akamaiedge.net","Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { "e4593.g.akamaiedge.net", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { "*.gateway.messenger.live.com", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".tuenti.com", "Tuenti", NDPI_PROTOCOL_TUENTI, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".twttr.com", "Twitter", NDPI_PROTOCOL_TWITTER, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "twitter.", "Twitter", NDPI_PROTOCOL_TWITTER, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "twimg.com", "Twitter", NDPI_PROTOCOL_TWITTER, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { ".viber.com", "Viber", NDPI_PROTOCOL_VIBER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { ".cdn.viber.com", "Viber", NDPI_PROTOCOL_VIBER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { ".viber.it", "Viber", NDPI_PROTOCOL_VIBER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + + { "wikipedia.", "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "wikimedia.", "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "mediawiki.", "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "wikimediafoundation.", "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + + { "mmg-fna.whatsapp.net", "WhatsAppFiles", NDPI_PROTOCOL_WHATSAPP_FILES, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_ACCEPTABLE }, + { ".whatsapp.", "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "g.whatsapp.net", "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "v.whatsapp.net", "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "mmg.whatsapp.net", "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".yahoo.", "Yahoo", NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".yimg.com", "Yahoo", NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "yahooapis.", "Yahoo", NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + { "upload.youtube.com", "YouTubeUpload", NDPI_PROTOCOL_YOUTUBE_UPLOAD, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { "upload.video.google.com", "YouTubeUpload", NDPI_PROTOCOL_YOUTUBE_UPLOAD, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { "youtubei.googleapis.com", "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { "youtube.", "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { "youtu.be.", "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { "yt3.ggpht.com", "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { ".googlevideo.com", "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { ".ytimg.com", "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { "youtube-nocookie.", "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + + { ".vevo.com", "Vevo", NDPI_PROTOCOL_VEVO, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + + { ".spotify.", "Spotify", NDPI_PROTOCOL_SPOTIFY, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + { "audio-fa.scdn.co", "Spotify", NDPI_PROTOCOL_SPOTIFY, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + { "spotifycdn.net", "Spotify", NDPI_PROTOCOL_SPOTIFY, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + + { "edge-mqtt.facebook.com", "Messenger", NDPI_PROTOCOL_MESSENGER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "mqtt-mini.facebook.com", "Messenger", NDPI_PROTOCOL_MESSENGER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, /* Messenger Lite */ + { "messenger.com", "Messenger", NDPI_PROTOCOL_MESSENGER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".pandora.com", "Pandora", NDPI_PROTOCOL_PANDORA, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + + { ".torproject.org", "Tor", NDPI_PROTOCOL_TOR, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, + + { ".kakao.com", "KakaoTalk", NDPI_PROTOCOL_KAKAOTALK, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + + { "ttvnw.net", "Twitch", NDPI_PROTOCOL_TWITCH, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "static-cdn.jtvnw.net", "Twitch", NDPI_PROTOCOL_TWITCH, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "www-cdn.jtvnw.net", "Twitch", NDPI_PROTOCOL_TWITCH, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { "twitch.tv", "Twitch", NDPI_PROTOCOL_TWITCH, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + + { ".qq.com", "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".gtimg.com", "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + + { ".weibo.com", "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".weibo.cn", "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sinaimg.cn", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sinajs.cn", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sina.cn", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sina.com.cn", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + /* https://support.cipafilter.com/index.php?/Knowledgebase/Article/View/117/0/snapchat---how-to-block */ + { "feelinsonice.appspot.com", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "feelinsonice-hrd.appspot.com", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "feelinsonice.com", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".snapchat.", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".snapads.", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sc-cdn.net", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sc-prod.net", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".sc-jpl.com", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "sc-analytics.appspot.com", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { ".waze.com", "Waze", NDPI_PROTOCOL_WAZE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".deezer.com", "Deezer", NDPI_PROTOCOL_DEEZER, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + + { "msn.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "i-msdn.sec.s-msft.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, NDPI_PROTOCOL_ACCEPTABLE }, + { "i2-msdn.sec.s-msft.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, NDPI_PROTOCOL_ACCEPTABLE }, + { ".webtrends.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { ".msecnd.net", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "bing.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { ".visualstudio.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, + { "login.live.com", "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, + { "teams.microsoft.com", "Teams", NDPI_PROTOCOL_MSTEAMS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, + { "-teams.cloudapp.net", "Teams", NDPI_PROTOCOL_MSTEAMS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, + { "teams.trafficmanager.net", "Teams", NDPI_PROTOCOL_MSTEAMS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, + { "teams-msgapi.trafficmanager.net", "Teams", NDPI_PROTOCOL_MSTEAMS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, + { "teams.office.net", "Teams", NDPI_PROTOCOL_MSTEAMS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, + { "teams.office.com", "Teams", NDPI_PROTOCOL_MSTEAMS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, + + { "bn1301.storage.live.com", "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE,NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { "skyapi.live.net", "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { "d.docs.live.net", "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { "onedrive.live.com", "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + + { "update.microsoft.com", "WindowsUpdate", NDPI_PROTOCOL_WINDOWS_UPDATE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + { ".windowsupdate.com", "WindowsUpdate", NDPI_PROTOCOL_WINDOWS_UPDATE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, + + { "worldofwarcraft.com", "WorldOfWarcraft", NDPI_PROTOCOL_WORLDOFWARCRAFT, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + + { ".anchorfree.", "HotspotShield", NDPI_PROTOCOL_HOTSPOT_SHIELD, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, + { "hotspotshield.com", "HotspotShield", NDPI_PROTOCOL_HOTSPOT_SHIELD, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, + { ".northghost.com", "HotspotShield", NDPI_PROTOCOL_HOTSPOT_SHIELD, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, + + { ".webex.com", "Webex", NDPI_PROTOCOL_WEBEX, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, + { ".zoom.us", "Zoom", NDPI_PROTOCOL_ZOOM, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".ocsdomain.com", "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { "ocs.fr", "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { ".ocs.fr", "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + { ".labgency.ws", "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, + + { ".iflix.com", "IFLIX", NDPI_PROTOCOL_IFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { ".app.iflixcorp.com", "IFLIX", NDPI_PROTOCOL_IFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + { ".images.iflixassets.com", "IFLIX", NDPI_PROTOCOL_IFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, + + { "crl.microsoft.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "evsecure-ocsp.verisign.com","Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "evsecure-aia.verisign.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "evsecure-crl.verisign.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".omniroot.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".microsoftonline.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".office365.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".office.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "office.net", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".msocsp.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".msocdn.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "officeapps.live.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "outlook.live.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "office.live.com", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".onenote.", "Microsoft365", NDPI_PROTOCOL_MICROSOFT_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + + /* http://www.urlquery.net/report.php?id=1453233646161 */ + { "lifedom.top", "Cloudflare", NDPI_PROTOCOL_CLOUDFLARE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "coby.ns.cloudflare.com", "Cloudflare", NDPI_PROTOCOL_CLOUDFLARE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { "amanda.ns.cloudflare.com", "Cloudflare", NDPI_PROTOCOL_CLOUDFLARE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + + { "d295hzzivaok4k.cloudfront.net","OpenDNS", NDPI_PROTOCOL_OPENDNS, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, + { ".opendns.com", "OpenDNS", NDPI_PROTOCOL_OPENDNS, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + + /* https://get.slack.help/hc/en-us/articles/205138367-Troubleshooting-Slack-connection-issues */ + { "slack.com", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".slack-msgs.com", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "slack-files.com", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "slack-imgs.com", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".slack-edge.com", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".slack-core.com", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "slack-redir.net", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + /* Detected "slack-assets2.s3-us-west-2.amazonaws.com.". Omitted "*amazonaws.com" CDN */ + { "slack-assets2.s3-", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + + { "github.com", "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".github.com", "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "github.io", "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".github.io", "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "githubusercontent.com", "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { ".githubusercontent.com", "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".steampowered.com", "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { "steamcommunity.com", "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".steamcontent.com", "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".steamstatic.com", "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { "steamcommunity-a.akamaihd.net", "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + + { ".wechat.com", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".wechat.org", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".wechatapp.com", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".we.chat", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".wx.", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".weixin.", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + { ".mmsns.qpic.cn", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, + + { "dnscrypt.org", "DNScrypt", NDPI_PROTOCOL_DNSCRYPT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + + { "torrent.", "BitTorrent", NDPI_PROTOCOL_BITTORRENT, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_UNSAFE }, + { "torrents.", "BitTorrent", NDPI_PROTOCOL_BITTORRENT, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_UNSAFE }, + { "torrentz.", "BitTorrent", NDPI_PROTOCOL_BITTORRENT, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_UNSAFE }, + + { ".nintendo.net", "Nintendo", NDPI_PROTOCOL_NINTENDO, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".nintendo.com", "Nintendo", NDPI_PROTOCOL_NINTENDO, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + + { ".playstation.net", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".playstation.com", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + { ".sonyentertainmentnetwork.com", "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, + + { ".linkedin.com", "LinkedIn", NDPI_PROTOCOL_LINKEDIN, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { ".licdn.com", "LinkedIn", NDPI_PROTOCOL_LINKEDIN, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { ".sndcdn.com", "SoundCloud", NDPI_PROTOCOL_SOUNDCLOUD, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + { ".soundcloud.com", "SoundCloud", NDPI_PROTOCOL_SOUNDCLOUD, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + { "getrockerbox.com", "SoundCloud", NDPI_PROTOCOL_SOUNDCLOUD, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, + + { "web.telegram.org", "Telegram", NDPI_PROTOCOL_TELEGRAM, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "tdesktop.com", "Telegram", NDPI_PROTOCOL_TELEGRAM, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "tupdate.com", "Telegram", NDPI_PROTOCOL_TELEGRAM, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".pastebin.com", "Pastebin", NDPI_PROTOCOL_PASTEBIN, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, + { "pastebin.com", "Pastebin", NDPI_PROTOCOL_PASTEBIN, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, + + { ".ppstream.com", "PPStream", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + { ".pps.tv", "PPStream", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + + { ".hulustream.com", "Hulu", NDPI_PROTOCOL_HULU, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + { ".hulu.com", "Hulu", NDPI_PROTOCOL_HULU, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + + /* + VidTO streaming service + */ + { ".vidto.me", "VidTO", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + { ".vidto.se", "VidTO", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, + + { "snapcraft.io", "UbuntuONE", NDPI_PROTOCOL_UBUNTUONE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + { "ubuntu.com", "UbuntuONE", NDPI_PROTOCOL_UBUNTUONE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, + + { "signal.org", "Signal", NDPI_PROTOCOL_SIGNAL, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "whispersystems.org", "Signal", NDPI_PROTOCOL_SIGNAL, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, + { "musical.ly", "TikTok", NDPI_PROTOCOL_TIKTOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "muscdn.com", "TikTok", NDPI_PROTOCOL_TIKTOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + { "byteoversea.com", "TikTok", NDPI_PROTOCOL_TIKTOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, + + { "brasilbandalarga.com.br", "EAQ", NDPI_PROTOCOL_EAQ, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + { ".eaqbr.com.br", "EAQ", NDPI_PROTOCOL_EAQ, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, + + { ".net.anydesk.com", "AnyDesk", NDPI_PROTOCOL_ANYDESK, NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, NDPI_PROTOCOL_ACCEPTABLE }, + + { "discordapp.com", "Discord", NDPI_PROTOCOL_DISCORD, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "discordapp.net", "Discord", NDPI_PROTOCOL_DISCORD, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "discord.com", "Discord", NDPI_PROTOCOL_DISCORD, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "discord.gg", "Discord", NDPI_PROTOCOL_DISCORD, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + { "discord.media", "Discord", NDPI_PROTOCOL_DISCORD, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, + -Each part of a domain name can be no longer than 63 characters. There are no single-digit top-level domains and none contain digits. It doesn't look like ICANN will approve such domains either. + { NULL, NULL, NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_SAFE } + }; -https://www.regular-expressions.info/email.html -(?i) is to make searches case insensitive +/* ******************************************************************** */ -(?:) is a non-capturing group used to allow patterns such as .co.uk or .com.cn - The non-capturing group is used in a nested fashion to capture the - .co, and then, optionally, another m. +/* + IMPORTANT + Do NOT pur here strings that overlap with string in host_match[] + specified above */ -#define TLD "(?i)(?:\\.co(?:m)?)?\\.[a-z]{2,63}$" - -/* ****************************************************** */ - -static ndpi_protocol_match host_match[] = { - { "s3.ll.dash.row.aiv-cdn.net", NULL, "s3\\.ll\\.dash\\.row\\.aiv-cdn\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "s3-dub.cf.dash.row.aiv-cdn.net", NULL, "s3-dub\\.cf\\.dash\\.row\\.aiv-cdn\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "dmqdd6hw24ucf.cloudfront.net", NULL, "dmqdd6hw24ucf\\.cloudfront\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "d25xi40x97liuc.cloudfront.net", NULL, "d25xi40x97liuc\\.cloudfront\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { ".aiv-delivery.net", NULL, "\\.aiv-delivery\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { ".aiv-cdn.net", NULL, "\\.aiv-cdn\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "1s3.lvlt.dash.us.aiv-cdn.net.c.footprint.net", NULL, "1s3\\.lvlt\\.dash\\.us\\.aiv-cdn\\.net\\.c\\.footprint\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - { ".cloudfront.net", NULL, "\\.cloudfront\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { ".s.loris.llnwd.net", NULL, "\\.s\\.loris\\.llnwd\\.net", "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "atv-ext.amazon.com", NULL, NULL, "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "c.media-amazon.com", NULL, NULL, "AmazonVideo", NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "amazon.", NULL, NULL, "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "amazon.com", NULL, "amazon" TLD, "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "images-amazon.com", NULL, "images-amazon" TLD, "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "amazonaws.com", NULL, "amazonaws" TLD, "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "amazon-adsystem.com", NULL, "amazon-adsystem" TLD, "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { ".cloudfront.net", NULL, "\\.cloudfront" TLD, "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { ".us-west-2.compute.amazonaws.com", NULL, "\\.us-west-2\\.compute\\.amazonaws\\.com", "Amazon", NDPI_PROTOCOL_AMAZON, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".push.apple.com", NULL, "\\.push\\.apple" TLD, "ApplePush", NDPI_PROTOCOL_APPLE_PUSH, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_SAFE }, - { ".apple-dns.net", NULL, "\\.apple-dns" TLD, "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".mzstatic.com", NULL, "\\.mzstatic" TLD, "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".aaplimg.com", NULL, "\\.aaplimg" TLD, "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "aaplimg.com", NULL, "aaplimg" TLD, "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { ".apple.com", NULL, "\\.apple" TLD, "Apple", NDPI_PROTOCOL_APPLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".icloud.com", NULL, "\\.icloud\\.com", "AppleiCloud", NDPI_PROTOCOL_APPLE_ICLOUD, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "iosapps.itunes.apple.com", NULL, "iosapps\\.itunes\\.apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, /* iOS */ - { "osxapps.itunes.apple.com", NULL, "osxapps\\.itunes\\.apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, /* MacOS */ - { "buy.itunes.apple.com", NULL, "buy\\.itunes\\.apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { "su.itunes.apple.com", NULL, "su\\.itunes\\.apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { "se.itunes.apple.com", NULL, "se\\.itunes\\.apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { "myapp.itunes.apple.com", NULL, "myapp\\.itunes\\.apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { "swscan.apple.com", NULL, "swscan\\.apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { "itunes-apple.com", NULL, "itunes-apple" TLD, "AppleStore", NDPI_PROTOCOL_APPLESTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { "itunes.apple.com", NULL, "itunes\\.apple" TLD, "AppleiTunes", NDPI_PROTOCOL_APPLE_ITUNES, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - { "tlnk.io", NULL, "tlnk" TLD, "AppleiTunes", NDPI_PROTOCOL_APPLE_ITUNES, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - { ".wbagora.com", NULL, "wbagora" TLD, "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_UNRATED }, - { ".wbplay.com", NULL, "wbplay" TLD, "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_UNRATED }, - { ".xbox.com", NULL, "xbox" TLD, "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".xboxlive.com", NULL, "xboxlive" TLD, "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".xboxlive.com.akadns.net", NULL, "xboxlive" TLD, "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".xboxlive.com.c.footprint.net", NULL, "xboxlive" TLD, "Xbox", NDPI_PROTOCOL_XBOX, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { "e13555.b.akamaiedge.net", NULL, "e13555\\.b\\.akamaiedge" TLD, "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { "e1800.d.akamaiedge.net", NULL, "e1800\\.d\\.akamaiedge" TLD, "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { "e1879.e7.akamaiedge.net", NULL, "e1879\\.e7\\.akamaiedge" TLD, "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - - { ".cnn.c", NULL, "\\.cnn" TLD, "CNN", NDPI_PROTOCOL_CNN, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".cnn.net", NULL, NULL, "CNN", NDPI_PROTOCOL_CNN, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - - { ".dropbox.com", NULL, "\\.dropbox" TLD, "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { ".dropboxstatic.com", NULL, "\\.dropboxstatic" TLD, "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { ".dropbox-dns.com", NULL, "\\.dropbox-dns" TLD, "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { "log.getdropbox.com", NULL, "log\\.getdropbox" TLD, "DropBox", NDPI_PROTOCOL_DROPBOX, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".ebay.", NULL, "\\.ebay" TLD, "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, /* or FUN */ - { ".ebay.com", NULL, NULL, "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, - { ".ebaystatic.com", NULL, "\\.ebaystatic" TLD, "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, - { ".ebaydesc.com", NULL, "\\.ebaydesc" TLD, "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, - { ".ebayrtm.com", NULL, "\\.ebayrtm" TLD, "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, - { ".ebaystratus.com", NULL, "\\.ebaystratus" TLD, "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, - { ".ebayimg.com", NULL, "\\.ebayimg" TLD, "eBay", NDPI_PROTOCOL_EBAY, NDPI_PROTOCOL_CATEGORY_SHOPPING, NDPI_PROTOCOL_SAFE }, - - /* Detected "instagram.c10r.facebook.com". Omitted "*amazonaws.com" and "*facebook.com" CDNs e.g. "ig-telegraph-shv-04-frc3.facebook.com" */ - { ".instagram.", NULL, "\\.instagram" TLD, "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "instagram.", NULL, "instagram" TLD, "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".cdninstagram.com", NULL, "\\.cdninstagram" TLD, "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { "igcdn-photos-", NULL, "igcdn-photos-", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "instagramimages-", NULL, "instagramimages-", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "instagramstatic-", NULL, "instagramstatic-", "Instagram", NDPI_PROTOCOL_INSTAGRAM, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { "facebook.com", NULL, "facebook" TLD, "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "fbstatic-a.akamaihd.net", NULL, "fbstatic-a\\.akamaihd" TLD, "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".fbcdn.net", NULL, "\\.fbcdn" TLD, "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "fbcdn-", NULL, "fbcdn-", "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".facebook.net", NULL, "\\.facebook" TLD, "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".fbsbx.com", NULL, "\\.fbsbx" TLD, "Facebook", NDPI_PROTOCOL_FACEBOOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { "speedtest.", NULL, "speedtest\\." TLD, "Ookla", NDPI_PROTOCOL_OOKLA, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_SAFE }, - { ".ooklaserver.net", NULL, "\\.ooklaserver\\.net" TLD, "Ookla", NDPI_PROTOCOL_OOKLA, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_SAFE }, - - { "ntop.org", NULL, "ntop\\.org$", "ntop", NDPI_PROTOCOL_NTOP, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_SAFE }, - - { "docs.googleusercontent.com", NULL, "docs.googleusercontent" TLD, "GoogleDocs", NDPI_PROTOCOL_GOOGLE_DOCS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "docs.google.com", NULL, "docs.google" TLD, "GoogleDocs", NDPI_PROTOCOL_GOOGLE_DOCS, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - - { "drive-thirdparty.googleusercontent.com", NULL, "drive-thirdparty\\.googleusercontent" TLD, "GoogleDrive", NDPI_PROTOCOL_GOOGLE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { "drive.google.com", NULL, "drive.google" TLD, "GoogleDrive", NDPI_PROTOCOL_GOOGLE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - - { "android.clients.google.com", NULL, "android\\.clients\\.google" TLD, "PlayStore", NDPI_PROTOCOL_PLAYSTORE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - - /* - https://www.fastvue.co/sophos/blog/google-data-saver-affect-security-confidentiality-reporting/ - Used by Google Chrome Lite Mode for Android - - This traffic will bypass checks and blocks as it will include all the communications from/to - the browser instead of using the standard communication mechanisms SSL/HTTP(S)/DNS - */ - { ".googlezip.net", NULL, ".googlezip\\.net" TLD, "DataSaver", NDPI_PROTOCOL_DATASAVER, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "datasaver.googleapis.com", NULL, "datasaver\\.googleapis\\.com" TLD, "DataSaver", NDPI_PROTOCOL_DATASAVER, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - /* http://check.googlezip.net/connect [check browser connectivity] */ - // { ".googlezip.net", NULL, "\\.googlezip" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - - { "dns.google", NULL, "dns\\.google" TLD, "DNSoverHTTPS", NDPI_PROTOCOL_DNS_OVER_HTTPS, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, - // { "mozilla.cloudflare-dns.com", NULL, "mozilla\\.cloudflare-dns\\.com" TLD, "DNSoverHTTPS", NDPI_PROTOCOL_DNS_OVER_HTTPS, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, /* Firefox */ - { "cloudflare-dns.com", NULL, "cloudflare-dns\\.com" TLD, "DNSoverHTTPS", NDPI_PROTOCOL_DNS_OVER_HTTPS, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, - - - /* - See https://better.fyi/trackers/ - - DoubleClick by Google (2mdn.net) - DoubleClick by Google (doubleclick.net) - DoubleClick by Google, Inc. (dmtry.com) - Google AdSense by Google (google.com) - Google AdSense by Google (google.se) - Google AdSense by Google (googleadservices.com) - Google Analytics by Google (google-analytics.com) - Google APIs by Google (ajax.googleapis.com) - Google Fonts by Google (fonts.googleapis.com) - Google Interactive Media Ads (imasdk.googleapis.com) - Google Syndication (googlesyndication.com) - Google Tag Manager by Google (googletagmanager.com) - Google Tag Manager by Google (googletagservices.com) - Gstatic by Google (gstatic.com) - */ - - /* Google Advertisements */ - { ".googlesyndication.com", NULL, "\\.googlesyndication" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, - { "googleads.", NULL, "googleads\\.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, - { ".doubleclick.net", NULL, "\\.doubleclick" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, - { "googleadservices.", NULL, "googleadservices\\.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, - { ".2mdn.net", NULL, "\\.2mdn" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, - { ".dmtry.com", NULL, "\\.dmtry" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, - { "google-analytics.", NULL, "google-analytics\\.", "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_TRACKER_ADS }, - { "gtv1.com", NULL, "gtv1" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - /* Google Hangout */ - { "images2-hangout-opensocial.googleusercontent.com", NULL, "images2-hangout-opensocial\\.googleusercontent" TLD, "GoogleHangout", NDPI_PROTOCOL_HANGOUT_DUO, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - - /* Google Services */ - { "googleapis.com", NULL, "googleapis" TLD, "GoogleServices", NDPI_PROTOCOL_GOOGLE_SERVICES, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { ".googletagservices.com", NULL, "\\.googletagservices" TLD, "GoogleServices", NDPI_PROTOCOL_GOOGLE_SERVICES, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "mtalk.google.com", NULL, "mtalk\\.google" TLD, "GoogleServices", NDPI_PROTOCOL_GOOGLE_SERVICES, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - { "plus.google.com", NULL, "plus\\.google" TLD, "GooglePlus", NDPI_PROTOCOL_GOOGLE_PLUS, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "plus.url.google.com", NULL, "plus\\.url\\.google" TLD, "GooglePlus", NDPI_PROTOCOL_GOOGLE_PLUS, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { "googleusercontent.com", NULL, "googleusercontent" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "1e100.net", NULL, "1e100" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - { "maps.google.", NULL, "maps\\.google" TLD, "GoogleMaps", NDPI_PROTOCOL_GOOGLE_MAPS, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "maps.gstatic.com", NULL, "maps\\.gstatic" TLD, "GoogleMaps", NDPI_PROTOCOL_GOOGLE_MAPS, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - - { ".gmail.", NULL, "\\.gmail" TLD, "GMail", NDPI_PROTOCOL_GMAIL, NDPI_PROTOCOL_CATEGORY_MAIL, NDPI_PROTOCOL_ACCEPTABLE }, - { "mail.google.", NULL, "mail\\.google" TLD, "GMail", NDPI_PROTOCOL_GMAIL, NDPI_PROTOCOL_CATEGORY_MAIL, NDPI_PROTOCOL_ACCEPTABLE }, - - { "google.", NULL, "google" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".google.", NULL, NULL, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".gstatic.com", NULL, "\\.gstatic" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "ggpht.com", NULL, "ggpht" TLD, "Google", NDPI_PROTOCOL_GOOGLE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - - { "mail.outlook.com", NULL, "mail\\.outlook" TLD, "Hotmail", NDPI_PROTOCOL_HOTMAIL, NDPI_PROTOCOL_CATEGORY_MAIL, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".last.fm", NULL, "\\.last\\.fm$", "LastFM", NDPI_PROTOCOL_LASTFM, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - - { "msn.com", NULL, "msn" TLD, "MSN", NDPI_PROTOCOL_MSN, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, /* News site */ - - { "netflix.com", NULL, "netflix" TLD, "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "nflxext.com", NULL, "nflxext" TLD, "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "nflximg.com", NULL, "nflximg" TLD, "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "nflximg.net", NULL, "nflximg" TLD, "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "nflxvideo.net", NULL, "nflxvideo" TLD, "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "nflxso.net", NULL, "nflxso" TLD, "NetFlix", NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - - { ".skype.", NULL, "\\.skype\\.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { ".skypeassets.", NULL, "\\.skypeassets\\.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { ".skypedata.", NULL, "\\.skypedata\\.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { ".skypeecs-", NULL, "\\.skypeecs-", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { ".skypeforbusiness.", NULL, "\\.skypeforbusiness\\.", "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { ".lync.com", NULL, "\\.lync" TLD, "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { "e7768.b.akamaiedge.net", NULL, "e7768\\.b\\.akamaiedge" TLD, "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { "e4593.dspg.akamaiedge.net", NULL, "e4593\\.dspg\\.akamaiedge" TLD,"Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { "e4593.g.akamaiedge.net", NULL, "e4593\\.g\\.akamaiedge" TLD, "Skype", NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".tuenti.com", NULL, "\\.tuenti" TLD, "Tuenti", NDPI_PROTOCOL_TUENTI, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".twttr.com", NULL, "\\.twttr" TLD, "Twitter", NDPI_PROTOCOL_TWITTER, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "twitter.", NULL, "twitter" TLD, "Twitter", NDPI_PROTOCOL_TWITTER, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "twimg.com", NULL, "twimg" TLD, "Twitter", NDPI_PROTOCOL_TWITTER, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { ".viber.com", NULL, "\\.viber" TLD, "Viber", NDPI_PROTOCOL_VIBER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { ".cdn.viber.com", NULL, NULL, "Viber", NDPI_PROTOCOL_VIBER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { ".viber.it", NULL, NULL, "Viber", NDPI_PROTOCOL_VIBER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - - { "wikipedia.", NULL, "wikipedia" TLD, "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "wikimedia.", NULL, "wikimedia" TLD, "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "mediawiki.", NULL, "mediawiki" TLD, "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "wikimediafoundation.", NULL, "wikimediafoundation" TLD, "Wikipedia", NDPI_PROTOCOL_WIKIPEDIA, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - - { "mmg-fna.whatsapp.net", NULL, "mmg-fna\\.whatsapp" TLD, "WhatsAppFiles", NDPI_PROTOCOL_WHATSAPP_FILES, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_ACCEPTABLE }, - { ".whatsapp.", NULL, "\\.whatsapp" TLD, "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "g.whatsapp.net", NULL, "g\\.whatsapp" TLD, "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "v.whatsapp.net", NULL, "v\\.whatsapp" TLD, "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "mmg.whatsapp.net", NULL, "mmg\\.whatsapp" TLD, "WhatsApp", NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".yahoo.", NULL, "\\.yahoo" TLD, "Yahoo", NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".yimg.com", NULL, "\\.yimg" TLD, "Yahoo", NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "yahooapis.", NULL, "yahooapis" TLD, "Yahoo", NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - { "upload.youtube.com", NULL, "upload\\.youtube" TLD, "YouTubeUpload", NDPI_PROTOCOL_YOUTUBE_UPLOAD, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { "upload.video.google.com", NULL, "upload\\.video\\.google" TLD, "YouTubeUpload", NDPI_PROTOCOL_YOUTUBE_UPLOAD, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { "youtubei.googleapis.com", NULL, "youtubei\\.googleapis" TLD, "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { "youtube.", NULL, "youtube" TLD, "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { "youtu.be.", NULL, "youtu\\.be" TLD, "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { "yt3.ggpht.com", NULL, "yt3\\.ggpht" TLD, "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { ".googlevideo.com", NULL, "\\.googlevideo" TLD, "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { ".ytimg.com", NULL, "\\.ytimg" TLD, "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { "youtube-nocookie.", NULL, "youtube-nocookie" TLD, "YouTube", NDPI_PROTOCOL_YOUTUBE, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - - { ".vevo.com", NULL, "\\.vevo" TLD, "Vevo", NDPI_PROTOCOL_VEVO, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - - { ".spotify.", NULL, "\\.spotify" TLD, "Spotify", NDPI_PROTOCOL_SPOTIFY, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - { "audio-fa.scdn.co", NULL, "audio-fa\\.scdn" TLD, "Spotify", NDPI_PROTOCOL_SPOTIFY, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - - { "edge-mqtt.facebook.com", NULL, "edge-mqtt\\.facebook" TLD, "Messenger", NDPI_PROTOCOL_MESSENGER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "mqtt-mini.facebook.com", NULL, "mqtt-mini\\.facebook" TLD, "Messenger", NDPI_PROTOCOL_MESSENGER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, /* Messenger Lite */ - { "messenger.com", NULL, "messenger\\.com" TLD, "Messenger", NDPI_PROTOCOL_MESSENGER, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".pandora.com", NULL, "\\.pandora" TLD, "Pandora", NDPI_PROTOCOL_PANDORA, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - - { ".torproject.org", NULL, "\\.torproject\\.org$", "Tor", NDPI_PROTOCOL_TOR, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, - - { ".kakao.com", NULL, "\\.kakao" TLD, "KakaoTalk", NDPI_PROTOCOL_KAKAOTALK, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - - { "ttvnw.net", NULL, "ttvnw" TLD, "Twitch", NDPI_PROTOCOL_TWITCH, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "static-cdn.jtvnw.net", NULL, "static-cdn\\.jtvnw" TLD, "Twitch", NDPI_PROTOCOL_TWITCH, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { "www-cdn.jtvnw.net", NULL, "www-cdn\\.jtvnw" TLD, "Twitch", NDPI_PROTOCOL_TWITCH, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - - { ".qq.com", NULL, "\\.qq" TLD, "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - { ".gtimg.com", NULL, "\\.gtimg" TLD, "QQ", NDPI_PROTOCOL_QQ, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - - { ".weibo.com", NULL, "\\.weibo" TLD, "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".weibo.cn", NULL, NULL, "Sina(Weibo)", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".sinaimg.cn", NULL, "\\.sinaimg" TLD, "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".sinajs.cn", NULL, "\\.sinajs" TLD, "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".sina.cn", NULL, "\\.sina" TLD, "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".sina.com.cn", NULL, "\\.sina\\.com\\.cn$", "Sina", NDPI_PROTOCOL_SINA, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - /* https://support.cipafilter.com/index.php?/Knowledgebase/Article/View/117/0/snapchat---how-to-block */ - { "feelinsonice.appspot.com", NULL, "\\.appspot" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "feelinsonice-hrd.appspot.com", NULL, "feelinsonice-hrd\\.appspot" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "feelinsonice.com", NULL, "\\.feelsonice" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".snapchat.", NULL, "\\.snapchat" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".snapads.", NULL, "\\.snapads" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".sc-cdn.net", NULL, "\\.sc-cdn\\.net" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".sc-prod.net", NULL, "\\.sc-prod\\.net" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".sc-jpl.com", NULL, "\\.sc-jpl\\.com" TLD, "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "sc-analytics.appspot.com", NULL, "sc-analytics\\.appspot\\.com", "Snapchat", NDPI_PROTOCOL_SNAPCHAT, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { ".waze.com", NULL, "\\.waze" TLD, "Waze", NDPI_PROTOCOL_WAZE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".deezer.com", NULL, "\\.deezer" TLD, "Deezer", NDPI_PROTOCOL_DEEZER, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - - { ".microsoft.com", NULL, "\\.microsoft" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { "i-msdn.sec.s-msft.com", NULL, "i-msdn.sec\\.s-msft" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, NDPI_PROTOCOL_ACCEPTABLE }, - { "i2-msdn.sec.s-msft.com", NULL, "i2-msdn\\.sec\\.s-msft" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, NDPI_PROTOCOL_ACCEPTABLE }, - { ".webtrends.com", NULL, "\\.webtrends" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { ".msecnd.net", NULL, "\\.msecnd" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "bing.com", NULL, "bing" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - { ".visualstudio.com", NULL, "\\.visualstudio" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_SAFE }, - { "login.live.com", NULL, "login\\.live" TLD, "Microsoft", NDPI_PROTOCOL_MICROSOFT, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_SAFE }, - - { "bn1301.storage.live.com", NULL, "bn1301\\.storage\\.live" TLD, "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE,NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { "*.gateway.messenger.live.com", NULL, "\\*\\.gateway\\.messenger\\.live" TLD, "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { "skyapi.live.net", NULL, "skyapi\\.live" TLD, "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { "d.docs.live.net", NULL, "d\\.docs\\.live" TLD, "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { "onedrive.live.com", NULL, "onedrive\\.live" TLD, "MS_OneDrive", NDPI_PROTOCOL_MS_ONE_DRIVE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - - { "update.microsoft.com", NULL, "update\\.microsoft" TLD, "WindowsUpdate", NDPI_PROTOCOL_WINDOWS_UPDATE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - { ".windowsupdate.com", NULL, "\\.windowsupdate" TLD, "WindowsUpdate", NDPI_PROTOCOL_WINDOWS_UPDATE, NDPI_PROTOCOL_CATEGORY_SW_UPDATE, NDPI_PROTOCOL_SAFE }, - - { "worldofwarcraft.com", NULL, "worldofwarcraft" TLD, "WorldOfWarcraft", NDPI_PROTOCOL_WORLDOFWARCRAFT, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - - { ".anchorfree.", NULL, "\\.anchorfree" TLD, "HotspotShield", NDPI_PROTOCOL_HOTSPOT_SHIELD, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, - { "hotspotshield.com", NULL, "hotspotshield" TLD, "HotspotShield", NDPI_PROTOCOL_HOTSPOT_SHIELD, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, - { ".northghost.com", NULL, "\\.northghost" TLD, "HotspotShield", NDPI_PROTOCOL_HOTSPOT_SHIELD, NDPI_PROTOCOL_CATEGORY_VPN, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, - - { ".webex.com", NULL, "\\.webex" TLD, "Webex", NDPI_PROTOCOL_WEBEX, NDPI_PROTOCOL_CATEGORY_VOIP, NDPI_PROTOCOL_ACCEPTABLE }, - { ".zoom.us", NULL, "\\.zoom" TLD, "Zoom", NDPI_PROTOCOL_ZOOM, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_ACCEPTABLE }, - { ".cloudfront.net", NULL, "\\.cloudfront" TLD, "Zoom", NDPI_PROTOCOL_ZOOM, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".ocsdomain.com", NULL, "\\.ocsdomain" TLD, "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { "ocs.fr", NULL, "ocs" TLD, "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { ".ocs.fr", NULL, NULL, "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - { ".labgency.ws", NULL, ".labgency" TLD, "OCS", NDPI_PROTOCOL_OCS, NDPI_PROTOCOL_CATEGORY_MEDIA, NDPI_PROTOCOL_FUN }, - - { ".iflix.com", NULL, "\\.iflix" TLD, "IFLIX", NDPI_PROTOCOL_IFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { ".app.iflixcorp.com", NULL, "\\.app\\.iflixcorp" TLD, "IFLIX", NDPI_PROTOCOL_IFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - { ".images.iflixassets.com", NULL, "\\.images\\.iflixassets" TLD, "IFLIX", NDPI_PROTOCOL_IFLIX, NDPI_PROTOCOL_CATEGORY_VIDEO, NDPI_PROTOCOL_FUN }, - - { "crl.microsoft.com", NULL, "crl\\.microsoft" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "evsecure-ocsp.verisign.com", NULL, "evsecure-ocsp\\.verisign" TLD,"Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "evsecure-aia.verisign.com", NULL, "evsecure-aia\\.verisign" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "evsecure-crl.verisign.com", NULL, "evsecure-crl\\.verisign" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".omniroot.com", NULL, "\\.omniroot" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".microsoftonline.com", NULL, "\\.microsoftonline" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".office365.com", NULL, "\\.office365" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".office.com", NULL, "\\.office" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "office.net", NULL, NULL, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".msocsp.com", NULL, "\\.msocsp" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".msocdn.com", NULL, "\\.msocdn" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "officeapps.live.com", NULL, "officeapps\\.live" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "outlook.live.com", NULL, "outlook\\.live" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "office.live.com", NULL, "office\\.live" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".onenote.", NULL, "\\.onenote" TLD, "Office365", NDPI_PROTOCOL_OFFICE_365, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - - /* http://www.urlquery.net/report.php?id=1453233646161 */ - { "lifedom.top", NULL, "lifedom" TLD, "Cloudflare", NDPI_PROTOCOL_CLOUDFLARE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "coby.ns.cloudflare.com", NULL, "coby\\.ns\\.cloudflare" TLD, "Cloudflare", NDPI_PROTOCOL_CLOUDFLARE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { "amanda.ns.cloudflare.com", NULL, "amanda\\.ns\\.cloudflare" TLD, "Cloudflare", NDPI_PROTOCOL_CLOUDFLARE, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - - { "d295hzzivaok4k.cloudfront.net", NULL, "d295hzzivaok4k\\.cloudfront" TLD,"OpenDNS", NDPI_PROTOCOL_OPENDNS, NDPI_PROTOCOL_CATEGORY_WEB, NDPI_PROTOCOL_ACCEPTABLE }, - { ".opendns.com", NULL, "\\.opendns" TLD, "OpenDNS", NDPI_PROTOCOL_OPENDNS, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, - - /* https://get.slack.help/hc/en-us/articles/205138367-Troubleshooting-Slack-connection-issues */ - { "slack.com", NULL, "slack" TLD, "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".slack-msgs.com", NULL, "\\.slack-msgs" TLD, "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "slack-files.com", NULL, "slack-files" TLD, "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "slack-imgs.com", NULL, "slack-imgs" TLD, "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".slack-edge.com", NULL, "\\.slack-edge" TLD, "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".slack-core.com", NULL, "\\.slack-core" TLD, "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "slack-redir.net", NULL, "slack-redir" TLD, "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - /* Detected "slack-assets2.s3-us-west-2.amazonaws.com.". Omitted "*amazonaws.com" CDN, but no generic pattern to use on first part */ - { "slack-assets2.s3-", NULL, "slack-assets2\\.s3-", "Slack", NDPI_PROTOCOL_SLACK, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - - { "github.com", NULL, "github" TLD, "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".github.com", NULL, "\\.github" TLD, "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "github.io", NULL, NULL, "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".github.io", NULL, NULL, "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { "githubusercontent.com", NULL, "githubusercontent" TLD, "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - { ".githubusercontent.com", NULL, "\\.githubusercontent" TLD, "Github", NDPI_PROTOCOL_GITHUB, NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".steampowered.com", NULL, "\\.steampowered" TLD, "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { "steamcommunity.com", NULL, "steamcommunity" TLD, "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".steamcontent.com", NULL, "\\.steamcontent" TLD, "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".steamstatic.com", NULL, "\\.steamstatic" TLD, "Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { "steamcommunity-a.akamaihd.net", NULL, "steamcommunity-a\\.akamaihd" TLD,"Steam", NDPI_PROTOCOL_STEAM, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - - { ".wechat.com", NULL, "\\.wechat\\.com", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - { ".wechat.org", NULL, "\\.wechat\\.org", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - { ".wechatapp.com", NULL, "\\.wechatapp" TLD, "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - { ".we.chat", NULL, "\\.we\\.chat", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - { ".wx.", NULL, "\\.wx\\.", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - { ".weixin.", NULL, "\\.weixin\\.", "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - { ".mmsns.qpic.cn", NULL, "\\.mmsns\\.qpic" TLD, "WeChat", NDPI_PROTOCOL_WECHAT, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_FUN }, - - { "dnscrypt.org", NULL, "dnscrypt\\.org$", "DNScrypt", NDPI_PROTOCOL_DNSCRYPT, NDPI_PROTOCOL_CATEGORY_NETWORK, NDPI_PROTOCOL_ACCEPTABLE }, - - { "torrent.", NULL, "torrent" TLD, "BitTorrent", NDPI_PROTOCOL_BITTORRENT, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_UNSAFE }, - { "torrents.", NULL, "torrents" TLD, "BitTorrent", NDPI_PROTOCOL_BITTORRENT, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_UNSAFE }, - { "torrentz.", NULL, "torrentz" TLD, "BitTorrent", NDPI_PROTOCOL_BITTORRENT, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_UNSAFE }, - - { ".nintendo.net", NULL, "\\.nintendo" TLD, "Nintendo", NDPI_PROTOCOL_NINTENDO, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".nintendo.com", NULL, NULL, "Nintendo", NDPI_PROTOCOL_NINTENDO, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - - { ".playstation.net", NULL, "\\.playstation" TLD, "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".playstation.com", NULL, NULL, "Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - { ".sonyentertainmentnetwork.com", NULL, "\\.sonyentertainmentnetwork" TLD,"Playstation", NDPI_PROTOCOL_PLAYSTATION, NDPI_PROTOCOL_CATEGORY_GAME, NDPI_PROTOCOL_FUN }, - - { ".linkedin.com", NULL, "\\.linkedin" TLD, "LinkedIn", NDPI_PROTOCOL_LINKEDIN, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { ".licdn.com", NULL, "\\.licdn" TLD, "LinkedIn", NDPI_PROTOCOL_LINKEDIN, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { ".sndcdn.com", NULL, "\\.sndcdn" TLD, "SoundCloud", NDPI_PROTOCOL_SOUNDCLOUD, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - { ".soundcloud.com", NULL, "\\.soundcloud" TLD, "SoundCloud", NDPI_PROTOCOL_SOUNDCLOUD, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - { "getrockerbox.com", NULL, "getrockerbox" TLD, "SoundCloud", NDPI_PROTOCOL_SOUNDCLOUD, NDPI_PROTOCOL_CATEGORY_MUSIC, NDPI_PROTOCOL_FUN }, - - { "web.telegram.org", NULL, "web\\.telegram" TLD, "Telegram", NDPI_PROTOCOL_TELEGRAM, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "tdesktop.com", NULL, "tdesktop" TLD, "Telegram", NDPI_PROTOCOL_TELEGRAM, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "tupdate.com", NULL, "tupdate" TLD, "Telegram", NDPI_PROTOCOL_TELEGRAM, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - - { ".pastebin.com", NULL, "\\.pastebin" TLD, "Pastebin", NDPI_PROTOCOL_PASTEBIN, NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS }, - - { ".ppstream.com", NULL, "\\.ppstream" TLD, "PPStream", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - { ".pps.tv", NULL, "\\.pps\\.tv$", "PPStream", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - - { ".hulu.com", NULL, "\\.hulu", "Hulu", NDPI_PROTOCOL_HULU, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - - /* - VidTO streaming service - */ - { ".vidto.me", NULL, "\\.vidto" TLD, "VidTO", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - { ".vidto.se", NULL, NULL, "VidTO", NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_CATEGORY_STREAMING, NDPI_PROTOCOL_FUN }, - - { "snapcraft.io", NULL, "snapcraft\\.io" TLD, "UbuntuONE", NDPI_PROTOCOL_UBUNTUONE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - { "ubuntu.com", NULL, "ubuntu\\.com" TLD, "UbuntuONE", NDPI_PROTOCOL_UBUNTUONE, NDPI_PROTOCOL_CATEGORY_CLOUD, NDPI_PROTOCOL_ACCEPTABLE }, - - { "signal.org", NULL, "signal\\.org" TLD, "Signal", NDPI_PROTOCOL_SIGNAL, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "whispersystems.org", NULL, "whispersystems\\.org" TLD, "Signal", NDPI_PROTOCOL_SIGNAL, NDPI_PROTOCOL_CATEGORY_CHAT, NDPI_PROTOCOL_ACCEPTABLE }, - { "musical.ly", NULL, "musical\\.ly" TLD, "TikTok", NDPI_PROTOCOL_TIKTOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - { "muscdn.com", NULL, "muscndl\\.com" TLD, "TikTok", NDPI_PROTOCOL_TIKTOK, NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, NDPI_PROTOCOL_FUN }, - - { NULL, NULL, NULL, 0 } -}; - -/* ******************************************************************** */ - static ndpi_category_match category_match[] = { - { ".edgecastcdn.net", "egdecastcdn" TLD, NDPI_PROTOCOL_CATEGORY_MEDIA }, - { ".hwcdn.net", "hwcdn" TLD, NDPI_PROTOCOL_CATEGORY_MEDIA }, - { ".llnwd.net", "llnwd" TLD, NDPI_PROTOCOL_CATEGORY_MEDIA }, - { ".llns.net", "llns" TLD, NDPI_PROTOCOL_CATEGORY_MEDIA }, - { ".fastly.net", "fastly" TLD, NDPI_PROTOCOL_CATEGORY_MEDIA }, - { ".akamaiedge.net", "akamaiedge" TLD, NDPI_PROTOCOL_CATEGORY_MEDIA }, - { ".vultr.com", "vultr" TLD, NDPI_PROTOCOL_CATEGORY_CLOUD }, - { "baidu.com", "baidu" TLD, NDPI_PROTOCOL_CATEGORY_WEB }, - { "icq.com", "icq" TLD, NDPI_PROTOCOL_CATEGORY_CHAT }, - { "quickplay.com", "quickplay" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { ".iqiyi.com", "\\.iqiyi" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { ".qiyi.com", "\\.qiyi" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { ".71.am", "\\.71" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { ".qiyipic.com", "\\.qiyipic" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { ".1kxun.", "\\.1kxun\\.", NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "tcad.wedolook.com", "tcad\\.wedolook" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { ".rapidvideo.com", "\\.rapidvideo" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { ".playercdn.net", "\\.playercdn" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "showmax.com", "showmax" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "showmax.akamaized.net", "showmax\\.akamaized" TLD, NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".edgecastcdn.net", NDPI_PROTOCOL_CATEGORY_MEDIA }, + { ".hwcdn.net", NDPI_PROTOCOL_CATEGORY_MEDIA }, + { ".llnwd.net", NDPI_PROTOCOL_CATEGORY_MEDIA }, + { ".llns.net", NDPI_PROTOCOL_CATEGORY_MEDIA }, + { ".fastly.net", NDPI_PROTOCOL_CATEGORY_MEDIA }, + { ".vultr.com", NDPI_PROTOCOL_CATEGORY_CLOUD }, + { "baidu.com", NDPI_PROTOCOL_CATEGORY_WEB }, + { "icq.com", NDPI_PROTOCOL_CATEGORY_CHAT }, + { "quickplay.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".iqiyi.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".qiyi.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".71.am", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".qiyipic.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".1kxun.", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "tcad.wedolook.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".rapidvideo.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { ".playercdn.net", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "showmax.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "showmax.akamaized.net", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "skyq.sky.com", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "iptv.sky.", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "pcdn.skycdn.", NDPI_PROTOCOL_CATEGORY_STREAMING }, + + /* https://success.tanaza.com/s/article/How-Automatic-Detection-of-Captive-Portal-works */ + { "captive.apple.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "thinkdifferent.us", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "airport.us", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "gsp1.apple.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "msftconnecttest.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "testconnectivity.microsoft.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "msftncsi.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "msftncsi.com.edgesuite.net", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "teredo.ipv6.microsoft.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "teredo.ipv6.microsoft.com.nsatc.net", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "detectportal.firefox.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "connectivitycheck.android.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, + { "connectivitycheck.gstatic.com", NDPI_PROTOCOL_CATEGORY_CONNECTIVITY_CHECK }, /* Hulu Streaming services AS23286 */ - { "8.28.124.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "8.28.125.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "199.200.50.0/23", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "199.200.51.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "199.60.116.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "208.91.158.0/23", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "209.249.186.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "8.28.124.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "8.28.125.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "199.200.50.0/23", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "199.200.51.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "199.60.116.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "208.91.158.0/23", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "209.249.186.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, /* Disney Streaming services AS11251 */ - { "8.4.4.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "8.5.5.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "8.33.30.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "63.116.222.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "63.116.223.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.192.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.193.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.200.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.201.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.202.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.203.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.204.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.205.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.206.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.207.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.208.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.209.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.212.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.216.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - { "139.104.217.0/24", NULL, NDPI_PROTOCOL_CATEGORY_STREAMING }, - - { NULL, NULL, 0 } + { "8.4.4.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "8.5.5.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "8.33.30.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "63.116.222.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "63.116.223.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.192.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.193.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.200.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.201.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.202.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.203.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.204.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.205.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.206.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.207.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.208.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.209.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.212.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.216.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + { "139.104.217.0/24", NDPI_PROTOCOL_CATEGORY_STREAMING }, + + { NULL, 0 } }; /* ******************************************************************** */ @@ -8873,6 +9274,10 @@ static ndpi_category_match category_match[] = { word or just random chars. http://www3.nd.edu/~busiforc/handouts/cryptography/Letter%20Frequencies.html + + DGA Datasets + - https://data.netlab.360.com/dga/ + - https://github.com/baderj/domain_generation_algorithms */ static const char *ndpi_en_bigrams[] = { @@ -8906,24 +9311,67 @@ static const char *ndpi_en_bigrams[] = { "ru", "su", "tu", "uu", "vu", "wu", "xu", "yu", "zu", "av", "bv", "dv", "ev", "iv", "lv", "mv", "nv", "ov", "rv", "sv", "tv", "uv", "vv", "zv", "aw", "bw", "dw", "ew", "fw", "gw", "hw", "iw", "kw", "lw", "mw", "nw", "ow", "pw", "rw", "sw", "tw", "uw", "ww", "xw", "yw", "zw", "ax", "ex", "ix", "nx", "ox", - "rx", "ux", "xx", "yx", "ay", "by", "cy", "dy", "ey", "fy", "gy", "hy", "ky", "ly", "my", "ny", "oy", + "rx", "ux", /* "xx", */ "yx", "ay", "by", "cy", "dy", "ey", "fy", "gy", "hy", "ky", "ly", "my", "ny", "oy", "py", "ry", "sy", "ty", "uy", "vy", "wy", "xy", "yy", "zy", "az", "bz", "cz", "dz", "ez", "gz", "iz", "lz", "nz", "oz", "pz", "rz", "tz", "uz", "zz", NULL }; /* ******************************************************************** */ +#if 0 +static const char *ndpi_en_popular_bigrams[] = { + "th", "he", "in", "er", "an", "re", "on", "at", "en", "nd", "ti", "es", "or", "te", "of", "ed", "is", "it", + "al", "ar", "st", "to", "nt", "ng", "se", "ha", "as", "ou", "io", "le", "ve", "co", "me", "de", "hi", "ri", + "ro", "ic", "ne", "ea", "ra", "ce", "li", "ch", "ll", "be", "ma", "si", "om", "ur", "ca", "el", "ta", "la", + "ns", "di", "fo", "ho", "pe", "ec", "pr", "no", "ct", "us", "ac", "ot", "il", "tr", "ly", "nc", "et", "ut", + "ss", "so", "rs", "un", "lo", "wa", "ge", "ie", "wh", "ee", "wi", "em", "ad", "ol", "rt", "po", "we", "na", + "ul", "ni", "ts", "mo", "ow", "pa", "im", "mi", "ai", "sh", "ir", "su", "id", "os", "iv", "ia", "am", "fi", + "ci", "vi", "pl", "ig", "tu", "ev", "ld", "ry", "mp", "fe", "bl", "ab", "gh", "ty", "op", "wo", "sa", "ay", + "ex", "ke", "fr", "oo", "av", "ag", "if", "ap", "gr", "od", "bo", "sp", "rd", "do", "uc", "bu", "ei", "ov", + "by", "rm", "ep", "tt", "oc", "fa", "ef", "cu", "rn", "sc", "gi", "da", "yo", "cr", "cl", "du", "ga", "qu", + "ue", "ff", "ba", "ey", "ls", "va", "um", "pp", "ua", "up", "lu", "go", "ht", "ru", "ug", "ds", "lt", "pi", + "rc", "rr", "eg", "au", "ck", "ew", "mu", "br", "bi", "pt", "ak", "pu", "ui", "rg", "ib", "tl", "ny", "ki", + "rk", "ys", "ob", "mm", "fu", "ph", "og", "ms", "ye", "ud", "mb", "ip", "ub", "oi", "rl", "gu", "dr", "hr", + "cc", "tw", "ft", "wn", "nu", "af", "hu", "nn", "eo", "vo", "rv", "nf", "xp", "gn", "sm", "fl", "iz", "ok", + "nl", "my", "gl", "aw", "ju", "oa", "eq", "sy", "sl", "ps", "jo", "lf", "nv", "je", "nk", "kn", "gs", "dy", + "hy", "ze", "ks", "xt", "bs", "ik", "dd", "cy", "rp", "sk", "xi", "oe", "oy", "ws", "lv", "dl", "rf", "eu", + "dg", "wr", "xa", "yi", "nm", "eb", "rb", "tm", "xc", "eh", "tc", "gy", "ja", "hn", "yp", "za", "gg", "ym", + "sw", "bj", "lm", "cs", "ii", "ix", "xe", "oh", "lk", "dv", "lp", "ax", "ox", "uf", "dm", "iu", "sf", "bt", + "ka", "yt", "ek", "pm", "ya", "gt", "wl", "rh", "yl", "hs", "ah", "yc", "yn", "rw", "hm", "lw", "hl", "ae", + "zi", "az", "lc", "py", "aj", "iq", "nj", "bb", "nh", "uo", "kl", "lr", "tn", "gm", "sn", "nr", "fy", "mn", + "dw", "sb", "yr", "dn", "sq", "zo", "oj", "yd", "lb", "wt", "lg", "ko", "np", "sr", "nq", "ky", "ln", "nw", + "tf", "fs", "cq", "dh", "sd", "vy", "dj", "hw", "xu", "ao", "ml", "uk", "uy", "ej", "ez", "hb", "nz", "nb", + "mc", "yb", "tp", "xh", "ux", "tz", "bv", "mf", "wd", "oz", "yw", "kh", "gd", "bm", "mr", "ku", "uv", "dt", + "hd", "aa", "xx", "df", "db", "ji", "kr", "xo", "cm", "zz", "nx", "yg", "xy", "kg", "tb", "dc", "bd", "sg", + "wy", "zy", "aq", "hf", "cd", "vu", "kw", "zu", "bn", "ih", "tg", "xv", "uz", "bc", "xf", "yz", "km", "dp", + "lh", "wf", "kf", "pf", "cf", "mt", "yu", "cp", "pb", "td", "zl", "sv", "hc", "mg", "pw", "gf", "pd", "pn", + "pc", "rx", "tv", "ij", "wm", "uh", "wk", "wb", "bh", "oq", "kt", "rq", "kb", "cg", "vr", "cn", "pk", "uu", + "yf", "wp", "cz", "kp", "dq", "wu", "fm", "wc", "md", "kd", "zh", "gw", "rz", "cb", "iw", "xl", "hp", "mw", + "vs", "fc", "rj", "bp", "mh", "hh", "yh", "uj", "fg", "fd", "gb", "pg", "tk", "kk", "hq", "fn", "lz", "vl", + "gp", "hz", "dk", "yk", "qi", "lx", "vd", "zs", "bw", "xq", "mv", "uw", "hg", "fb", "sj", "ww", "gk", "uq", + "bg", "sz", "jr", "ql", "zt", "hk", "vc", "xm", "gc", "fw", "pz", "kc", "hv", "xw", "zw", "fp", "iy", "pv", + "vt", "jp", "cv", "zb", "vp", "zr", "fh", "yv", "zg", "zm", "zv", "qs", "kv", "vn", "zn", "qa", "yx", "jn", + "bf", "mk", "cw", "jm", "lq", "jh", "kj", "jc", "gz", "js", "tx", "fk", "jl", "vm", "lj", "tj", "jj", "cj", + "vg", "mj", "jt", "pj", "wg", "vh", "bk", "vv", "jd", "tq", "vb", "jf", "dz", "xb", "jb", "zc", "fj", "yy", + "qn", "xs", "qr", "jk", "jv", "qq", "xn", "vf", "px", "zd", "qt", "zp", "qo", "dx", "hj", "gv", "jw", "qc", + "jy", "gj", "qb", "pq", "jg", "bz", "mx", "qm", "mz", "qf", "wj", "zq", "xr", "zk", "cx", "fx", "fv", "bx", + "vw", "vj", "mq", "qv", "zf", "qe", "yj", "gx", "kx", "xg", "qd", "xj", "sx", "vz", "vx", "wv", "yq", "bq", + "gq", "vk", "zj", "xk", "qp", "hx", "fz", "qh", "qj", "jz", "vq", "kq", "xd", "qw", "jx", "qx", "kz", "wx", + "fq", "xz", "zx", "jq", "qg", "qk", "qy", "qz", "wq", "wz", NULL + }; +#endif + +/* ******************************************************************** */ + static const char *ndpi_en_impossible_bigrams[] = { "bk", "bq", "bx", "cb", "cf", "cg", "cj", "cp", "cv", "cw", "cx", "dx", "fk", "fq", "fv", "fx", /* "ee", removed it can be found in 'meeting' */ "fz", "gq", "gv", "gx", "hh", "hk", "hv", "hx", "hz", "iy", "jb", /* "jc", jcrew.com */ "jd", "jf", "jg", "jh", "jk", - "jl", "jm", "jn", "jp", "jq", "jr", /* "js", */ "jt", "jv", "jw", "jx", "jy", "jz", "kg", "kq", "kv", "kx", - "kz", "lq", "lx", /* "mg" tamgrt.com , */ "mj", "mq", "mx", "mz", "pq", "pv", "px", "qb", "qc", "qd", "qe", "qf", "ii", + "jl", "jm", "jn", "jp", "jq", /* "jr",*/ /* "js", */ "jt", "jv", "jw", "jx", "jy", "jz", /* "kg", */ "kq", "kv", "kx", + "kz", "lq", "lx", /* "mg" tamgrt.com , */ "mj", /* "mq", mqtt */ "mx", "mz", "pq", "pv", "px", "qb", "qc", "qd", "qe", "qf", "ii", "qg", "qh", "qj", "qk", "ql", "qm", "qn", "qo", "qp", "qr", "qs", "qt", "qv", "qw", "qx", "qy", "uu", "qz", "sx", "sz", "tq", "tx", "vb", "vc", "vd", "vf", "vg", "vh", "vj", "vm", "vn", /* "vp", Removed for vpbank.com */ "bw", /* "vk", "zr" Removed for kavkazr */ "vq", "vt", "vw", "vx", "vz", "wq", "wv", "wx", "wz", /* "xb", foxbusiness.com */ - "xg", "xj", "xk", "xv", "xz", "xw", "yd", /*"yp", Removed for paypal */ + "xg", "xj", "xk", "xv", "xz", "xw", "yd", /*"yp", Removed for paypal */ "yj", "yq", "yv", "yz", "yw", "zb", "zc", "zg", "zh", "zj", "zn", "zq", "zs", "zx", "wh", "wk", - "wb", "zk", "kp", "zk", "xy", + "wb", "zk", "kp", "zk", "xy", "xx", NULL }; - - diff --git a/src/lib/ndpi_main.c b/src/lib/ndpi_main.c index a72917b..4cbe722 100644 --- a/src/lib/ndpi_main.c +++ b/src/lib/ndpi_main.c @@ -1,7 +1,7 @@ /* * ndpi_main.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -21,20 +21,20 @@ * */ -#ifdef HAVE_CONFIG_H -#include "ndpi_config.h" -#endif - #include #include #include -#include "ahocorasick.h" -#include "libcache.h" #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_UNKNOWN -#include "ndpi_api.h" #include "ndpi_config.h" +#include "ndpi_api.h" +#include "ahocorasick.h" +#include "libcache.h" + +#ifdef HAVE_LIBGCRYPT +#include +#endif #include #ifndef WIN32 @@ -48,46 +48,52 @@ #include "ndpi_content_match.c.inc" #include "third_party/include/ndpi_patricia.h" #include "third_party/include/ht_hash.h" +#include "third_party/include/ndpi_md5.h" /* stun.c */ -extern u_int32_t get_stun_lru_key(struct ndpi_flow_struct *flow); +extern u_int32_t get_stun_lru_key(struct ndpi_flow_struct *flow, u_int8_t rev); static int _ndpi_debug_callbacks = 0; +/* #define DGA_DEBUG 1 */ /* #define MATCH_DEBUG 1 */ /* ****************************************** */ static void *(*_ndpi_flow_malloc)(size_t size); -static void (*_ndpi_flow_free)(void *ptr); +static void (*_ndpi_flow_free)(void *ptr); static void *(*_ndpi_malloc)(size_t size); -static void (*_ndpi_free)(void *ptr); +static void (*_ndpi_free)(void *ptr); /* ****************************************** */ /* Forward */ -static void addDefaultPort(struct ndpi_detection_module_struct *ndpi_str, - ndpi_port_range *range, - ndpi_proto_defaults_t *def, - u_int8_t customUserProto, - ndpi_default_ports_tree_node_t **root, - const char *_func, int _line); +static void addDefaultPort(struct ndpi_detection_module_struct *ndpi_str, ndpi_port_range *range, + ndpi_proto_defaults_t *def, u_int8_t customUserProto, ndpi_default_ports_tree_node_t **root, + const char *_func, int _line); -static int removeDefaultPort(ndpi_port_range *range, - ndpi_proto_defaults_t *def, - ndpi_default_ports_tree_node_t **root); +static int removeDefaultPort(ndpi_port_range *range, ndpi_proto_defaults_t *def, ndpi_default_ports_tree_node_t **root); /* ****************************************** */ -void* ndpi_malloc(size_t size) { return(_ndpi_malloc ? _ndpi_malloc(size) : malloc(size)); } -void* ndpi_flow_malloc(size_t size) { return(_ndpi_flow_malloc ? _ndpi_flow_malloc(size) : ndpi_malloc(size)); } +static inline uint8_t flow_is_proto(struct ndpi_flow_struct *flow, u_int16_t p) { + return((flow->detected_protocol_stack[0] == p) || (flow->detected_protocol_stack[1] == p)); +} /* ****************************************** */ -void * ndpi_calloc(unsigned long count, size_t size) -{ - size_t len = count*size; +void *ndpi_malloc(size_t size) { + return(_ndpi_malloc ? _ndpi_malloc(size) : malloc(size)); +} +void *ndpi_flow_malloc(size_t size) { + return(_ndpi_flow_malloc ? _ndpi_flow_malloc(size) : ndpi_malloc(size)); +} + +/* ****************************************** */ + +void *ndpi_calloc(unsigned long count, size_t size) { + size_t len = count * size; void *p = ndpi_malloc(len); if(p) @@ -99,10 +105,13 @@ void * ndpi_calloc(unsigned long count, size_t size) /* ****************************************** */ void ndpi_free(void *ptr) { - if(_ndpi_free) - _ndpi_free(ptr); - else - free(ptr); + if(_ndpi_free) { + if(ptr) + _ndpi_free(ptr); + } else { + if(ptr) + free(ptr); + } } /* ****************************************** */ @@ -116,8 +125,7 @@ void ndpi_flow_free(void *ptr) { /* ****************************************** */ -void * ndpi_realloc(void *ptr, size_t old_size, - size_t new_size) { +void *ndpi_realloc(void *ptr, size_t old_size, size_t new_size) { void *ret = ndpi_malloc(new_size); if(!ret) @@ -130,10 +138,13 @@ void * ndpi_realloc(void *ptr, size_t old_size, } /* ****************************************** */ -char * ndpi_strdup(const char *s) -{ +char *ndpi_strdup(const char *s) { + if(s == NULL ){ + return NULL; + } + int len = strlen(s); - char *m = ndpi_malloc(len+1); + char *m = ndpi_malloc(len + 1); if(m) { memcpy(m, s, len); @@ -145,15 +156,40 @@ char * ndpi_strdup(const char *s) /* *********************************************************************************** */ -u_int32_t ndpi_detection_get_sizeof_ndpi_flow_struct(void) { return(sizeof(struct ndpi_flow_struct)); } +/* Opaque structure defined here */ +struct ndpi_ptree +{ + patricia_tree_t *v4; + patricia_tree_t *v6; +}; /* *********************************************************************************** */ -u_int32_t ndpi_detection_get_sizeof_ndpi_id_struct(void) { return(sizeof(struct ndpi_id_struct)); } +u_int32_t ndpi_detection_get_sizeof_ndpi_flow_struct(void) { + return(sizeof(struct ndpi_flow_struct)); +} /* *********************************************************************************** */ -char * ndpi_get_proto_by_id(struct ndpi_detection_module_struct *ndpi_str, u_int id) { +u_int32_t ndpi_detection_get_sizeof_ndpi_id_struct(void) { + return(sizeof(struct ndpi_id_struct)); +} + +/* *********************************************************************************** */ + +u_int32_t ndpi_detection_get_sizeof_ndpi_flow_tcp_struct(void) { + return(sizeof(struct ndpi_flow_tcp_struct)); +} + +/* *********************************************************************************** */ + +u_int32_t ndpi_detection_get_sizeof_ndpi_flow_udp_struct(void) { + return(sizeof(struct ndpi_flow_udp_struct)); +} + +/* *********************************************************************************** */ + +char *ndpi_get_proto_by_id(struct ndpi_detection_module_struct *ndpi_str, u_int id) { return((id >= ndpi_str->ndpi_num_supported_protocols) ? NULL : ndpi_str->proto_defaults[id].protoName); } @@ -162,7 +198,7 @@ char * ndpi_get_proto_by_id(struct ndpi_detection_module_struct *ndpi_str, u_int u_int16_t ndpi_get_proto_by_name(struct ndpi_detection_module_struct *ndpi_str, const char *name) { u_int16_t i, num = ndpi_get_num_supported_protocols(ndpi_str); - for(i = 0; i < num; i++) + for (i = 0; i < num; i++) if(strcasecmp(ndpi_get_proto_by_id(ndpi_str, i), name) == 0) return(i); @@ -172,18 +208,20 @@ u_int16_t ndpi_get_proto_by_name(struct ndpi_detection_module_struct *ndpi_str, /* ************************************************************************************* */ #ifdef CODE_UNUSED -ndpi_port_range * ndpi_build_default_ports_range(ndpi_port_range *ports, - u_int16_t portA_low, u_int16_t portA_high, - u_int16_t portB_low, u_int16_t portB_high, - u_int16_t portC_low, u_int16_t portC_high, - u_int16_t portD_low, u_int16_t portD_high, - u_int16_t portE_low, u_int16_t portE_high) { +ndpi_port_range *ndpi_build_default_ports_range(ndpi_port_range *ports, u_int16_t portA_low, u_int16_t portA_high, + u_int16_t portB_low, u_int16_t portB_high, u_int16_t portC_low, + u_int16_t portC_high, u_int16_t portD_low, u_int16_t portD_high, + u_int16_t portE_low, u_int16_t portE_high) { int i = 0; - ports[i].port_low = portA_low, ports[i].port_high = portA_high; i++; - ports[i].port_low = portB_low, ports[i].port_high = portB_high; i++; - ports[i].port_low = portC_low, ports[i].port_high = portC_high; i++; - ports[i].port_low = portD_low, ports[i].port_high = portD_high; i++; + ports[i].port_low = portA_low, ports[i].port_high = portA_high; + i++; + ports[i].port_low = portB_low, ports[i].port_high = portB_high; + i++; + ports[i].port_low = portC_low, ports[i].port_high = portC_high; + i++; + ports[i].port_low = portD_low, ports[i].port_high = portD_high; + i++; ports[i].port_low = portE_low, ports[i].port_high = portE_high; return(ports); @@ -192,18 +230,18 @@ ndpi_port_range * ndpi_build_default_ports_range(ndpi_port_range *ports, /* *********************************************************************************** */ -ndpi_port_range * ndpi_build_default_ports(ndpi_port_range *ports, - u_int16_t portA, - u_int16_t portB, - u_int16_t portC, - u_int16_t portD, - u_int16_t portE) { +ndpi_port_range *ndpi_build_default_ports(ndpi_port_range *ports, u_int16_t portA, u_int16_t portB, u_int16_t portC, + u_int16_t portD, u_int16_t portE) { int i = 0; - ports[i].port_low = portA, ports[i].port_high = portA; i++; - ports[i].port_low = portB, ports[i].port_high = portB; i++; - ports[i].port_low = portC, ports[i].port_high = portC; i++; - ports[i].port_low = portD, ports[i].port_high = portD; i++; + ports[i].port_low = portA, ports[i].port_high = portA; + i++; + ports[i].port_low = portB, ports[i].port_high = portB; + i++; + ports[i].port_low = portC, ports[i].port_high = portC; + i++; + ports[i].port_low = portD, ports[i].port_high = portD; + i++; ports[i].port_low = portE, ports[i].port_high = portE; return(ports); @@ -211,9 +249,8 @@ ndpi_port_range * ndpi_build_default_ports(ndpi_port_range *ports, /* ********************************************************************************** */ -void ndpi_set_proto_breed(struct ndpi_detection_module_struct *ndpi_str, - u_int16_t protoId, ndpi_protocol_breed_t breed) { - if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS) +void ndpi_set_proto_breed(struct ndpi_detection_module_struct *ndpi_str, u_int16_t protoId, ndpi_protocol_breed_t breed) { + if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS) return; else ndpi_str->proto_defaults[protoId].protoBreed = breed; @@ -221,9 +258,9 @@ void ndpi_set_proto_breed(struct ndpi_detection_module_struct *ndpi_str, /* ********************************************************************************** */ -void ndpi_set_proto_category(struct ndpi_detection_module_struct *ndpi_str, - u_int16_t protoId, ndpi_protocol_category_t protoCategory) { - if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS) +void ndpi_set_proto_category(struct ndpi_detection_module_struct *ndpi_str, u_int16_t protoId, + ndpi_protocol_category_t protoCategory) { + if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS) return; else ndpi_str->proto_defaults[protoId].protoCategory = protoCategory; @@ -242,18 +279,13 @@ void ndpi_set_proto_category(struct ndpi_detection_module_struct *ndpi_str, - HTTP/SSL are NOT informative as SSL.Facebook (likely) means that this is SSL (HTTPS) traffic containg Facebook traffic. */ -u_int8_t ndpi_is_subprotocol_informative(struct ndpi_detection_module_struct *ndpi_str, - u_int16_t protoId) { - if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS) +u_int8_t ndpi_is_subprotocol_informative(struct ndpi_detection_module_struct *ndpi_str, u_int16_t protoId) { + if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS) return(0); switch(protoId) { /* All dissectors that have calls to ndpi_match_host_subprotocol() */ case NDPI_PROTOCOL_DNS: - case NDPI_PROTOCOL_HTTP: - case NDPI_PROTOCOL_TLS: - case NDPI_PROTOCOL_QUIC: - case NDPI_PROTOCOL_FBZERO: return(1); break; @@ -263,18 +295,13 @@ u_int8_t ndpi_is_subprotocol_informative(struct ndpi_detection_module_struct *nd } /* ********************************************************************************** */ -void ndpi_exclude_protocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int16_t protocol_id, - const char *_file, const char *_func,int _line) { - if(protocol_id < NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS) { +void ndpi_exclude_protocol(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int16_t protocol_id, const char *_file, const char *_func, int _line) { + if(protocol_id < NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS) { #ifdef NDPI_ENABLE_DEBUG_MESSAGES - if( ndpi_str && - ndpi_str->ndpi_log_level >= NDPI_LOG_DEBUG && - ndpi_str->ndpi_debug_printf != NULL) { - - (*(ndpi_str->ndpi_debug_printf))(protocol_id, ndpi_str, NDPI_LOG_DEBUG, - _file, _func, _line, "exclude %s\n",ndpi_get_proto_name(ndpi_str, protocol_id)); + if(ndpi_str && ndpi_str->ndpi_log_level >= NDPI_LOG_DEBUG && ndpi_str->ndpi_debug_printf != NULL) { + (*(ndpi_str->ndpi_debug_printf))(protocol_id, ndpi_str, NDPI_LOG_DEBUG, _file, _func, _line, "exclude %s\n", + ndpi_get_proto_name(ndpi_str, protocol_id)); } #endif NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, protocol_id); @@ -283,16 +310,14 @@ void ndpi_exclude_protocol(struct ndpi_detection_module_struct *ndpi_str, /* ********************************************************************************** */ -void ndpi_set_proto_defaults(struct ndpi_detection_module_struct *ndpi_str, - ndpi_protocol_breed_t breed, u_int16_t protoId, - u_int8_t can_have_a_subprotocol, - u_int16_t tcp_master_protoId[2], u_int16_t udp_master_protoId[2], - char *protoName, ndpi_protocol_category_t protoCategory, - ndpi_port_range *tcpDefPorts, ndpi_port_range *udpDefPorts) { +void ndpi_set_proto_defaults(struct ndpi_detection_module_struct *ndpi_str, ndpi_protocol_breed_t breed, + u_int16_t protoId, u_int8_t can_have_a_subprotocol, u_int16_t tcp_master_protoId[2], + u_int16_t udp_master_protoId[2], char *protoName, ndpi_protocol_category_t protoCategory, + ndpi_port_range *tcpDefPorts, ndpi_port_range *udpDefPorts) { char *name; int j; - if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS) { + if(protoId >= NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS) { #ifdef DEBUG NDPI_LOG_ERR(ndpi_str, "[NDPI] %s/protoId=%d: INTERNAL ERROR\n", protoName, protoId); #endif @@ -308,32 +333,36 @@ void ndpi_set_proto_defaults(struct ndpi_detection_module_struct *ndpi_str, name = ndpi_strdup(protoName); - ndpi_str->proto_defaults[protoId].protoName = name, - ndpi_str->proto_defaults[protoId].protoCategory = protoCategory, - ndpi_str->proto_defaults[protoId].protoId = protoId, - ndpi_str->proto_defaults[protoId].protoBreed = breed; + if(ndpi_str->proto_defaults[protoId].protoName) + ndpi_free(ndpi_str->proto_defaults[protoId].protoName); + + ndpi_str->proto_defaults[protoId].protoName = name, ndpi_str->proto_defaults[protoId].protoCategory = protoCategory, + ndpi_str->proto_defaults[protoId].protoId = protoId, ndpi_str->proto_defaults[protoId].protoBreed = breed; ndpi_str->proto_defaults[protoId].can_have_a_subprotocol = can_have_a_subprotocol; - memcpy(&ndpi_str->proto_defaults[protoId].master_tcp_protoId, tcp_master_protoId, 2*sizeof(u_int16_t)); - memcpy(&ndpi_str->proto_defaults[protoId].master_udp_protoId, udp_master_protoId, 2*sizeof(u_int16_t)); + memcpy(&ndpi_str->proto_defaults[protoId].master_tcp_protoId, tcp_master_protoId, 2 * sizeof(u_int16_t)); + memcpy(&ndpi_str->proto_defaults[protoId].master_udp_protoId, udp_master_protoId, 2 * sizeof(u_int16_t)); - for(j=0; jproto_defaults[protoId], 0, &ndpi_str->udpRoot, __FUNCTION__,__LINE__); + addDefaultPort(ndpi_str, &udpDefPorts[j], &ndpi_str->proto_defaults[protoId], 0, &ndpi_str->udpRoot, + __FUNCTION__, __LINE__); if(tcpDefPorts[j].port_low != 0) - addDefaultPort(ndpi_str, &tcpDefPorts[j], - &ndpi_str->proto_defaults[protoId], 0, &ndpi_str->tcpRoot, __FUNCTION__,__LINE__); + addDefaultPort(ndpi_str, &tcpDefPorts[j], &ndpi_str->proto_defaults[protoId], 0, &ndpi_str->tcpRoot, + __FUNCTION__, __LINE__); + + /* No port range, just the lower port */ + ndpi_str->proto_defaults[protoId].tcp_default_ports[j] = tcpDefPorts[j].port_low; + ndpi_str->proto_defaults[protoId].udp_default_ports[j] = udpDefPorts[j].port_low; } } /* ******************************************************************** */ -static int ndpi_default_ports_tree_node_t_cmp(const void *a, const void *b) -{ - ndpi_default_ports_tree_node_t *fa = (ndpi_default_ports_tree_node_t*)a; - ndpi_default_ports_tree_node_t *fb = (ndpi_default_ports_tree_node_t*)b; +static int ndpi_default_ports_tree_node_t_cmp(const void *a, const void *b) { + ndpi_default_ports_tree_node_t *fa = (ndpi_default_ports_tree_node_t *) a; + ndpi_default_ports_tree_node_t *fb = (ndpi_default_ports_tree_node_t *) b; //printf("[NDPI] %s(%d, %d)\n", __FUNCTION__, fa->default_port, fb->default_port); @@ -342,31 +371,28 @@ static int ndpi_default_ports_tree_node_t_cmp(const void *a, const void *b) /* ******************************************************************** */ -void ndpi_default_ports_tree_node_t_walker(const void *node, const ndpi_VISIT which, const int depth) -{ - ndpi_default_ports_tree_node_t *f = *(ndpi_default_ports_tree_node_t **)node; - - printf("<%d>Walk on node %s (%u)\n", - depth, - which == ndpi_preorder?"ndpi_preorder": - which == ndpi_postorder?"ndpi_postorder": - which == ndpi_endorder?"ndpi_endorder": - which == ndpi_leaf?"ndpi_leaf": "unknown", +void ndpi_default_ports_tree_node_t_walker(const void *node, const ndpi_VISIT which, const int depth) { + ndpi_default_ports_tree_node_t *f = *(ndpi_default_ports_tree_node_t **) node; + + printf("<%d>Walk on node %s (%u)\n", depth, + which == ndpi_preorder ? + "ndpi_preorder" : + which == ndpi_postorder ? + "ndpi_postorder" : + which == ndpi_endorder ? "ndpi_endorder" : which == ndpi_leaf ? "ndpi_leaf" : "unknown", f->default_port); } /* ******************************************************************** */ -static void addDefaultPort(struct ndpi_detection_module_struct *ndpi_str, - ndpi_port_range *range, - ndpi_proto_defaults_t *def, - u_int8_t customUserProto, - ndpi_default_ports_tree_node_t **root, - const char *_func, int _line) { +static void addDefaultPort(struct ndpi_detection_module_struct *ndpi_str, ndpi_port_range *range, + ndpi_proto_defaults_t *def, u_int8_t customUserProto, ndpi_default_ports_tree_node_t **root, + const char *_func, int _line) { u_int16_t port; - for(port=range->port_low; port<=range->port_high; port++) { - ndpi_default_ports_tree_node_t *node = (ndpi_default_ports_tree_node_t*)ndpi_malloc(sizeof(ndpi_default_ports_tree_node_t)); + for (port = range->port_low; port <= range->port_high; port++) { + ndpi_default_ports_tree_node_t *node = + (ndpi_default_ports_tree_node_t *) ndpi_malloc(sizeof(ndpi_default_ports_tree_node_t)); ndpi_default_ports_tree_node_t *ret; if(!node) { @@ -375,11 +401,11 @@ static void addDefaultPort(struct ndpi_detection_module_struct *ndpi_str, } node->proto = def, node->default_port = port, node->customUserProto = customUserProto; - ret = (ndpi_default_ports_tree_node_t*)ndpi_tsearch(node, (void*)root, ndpi_default_ports_tree_node_t_cmp); /* Add it to the tree */ + ret = (ndpi_default_ports_tree_node_t *) ndpi_tsearch(node, (void *) root, ndpi_default_ports_tree_node_t_cmp); /* Add it to the tree */ if(ret != node) { - NDPI_LOG_DBG(ndpi_str, "[NDPI] %s:%d found duplicate for port %u: overwriting it with new value\n", - _func, _line, port); + NDPI_LOG_DBG(ndpi_str, "[NDPI] %s:%d found duplicate for port %u: overwriting it with new value\n", _func, + _line, port); ret->proto = def; ndpi_free(node); @@ -395,22 +421,19 @@ static void addDefaultPort(struct ndpi_detection_module_struct *ndpi_str, This function must be called with a semaphore set, this in order to avoid changing the datastructures while using them */ -static int removeDefaultPort(ndpi_port_range *range, - ndpi_proto_defaults_t *def, - ndpi_default_ports_tree_node_t **root) -{ +static int removeDefaultPort(ndpi_port_range *range, ndpi_proto_defaults_t *def, ndpi_default_ports_tree_node_t **root) { ndpi_default_ports_tree_node_t node; u_int16_t port; - for(port=range->port_low; port<=range->port_high; port++) { + for (port = range->port_low; port <= range->port_high; port++) { ndpi_default_ports_tree_node_t *ret; node.proto = def, node.default_port = port; - ret = (ndpi_default_ports_tree_node_t*)ndpi_tdelete(&node, (void*)root, - ndpi_default_ports_tree_node_t_cmp); /* Add it to the tree */ + ret = (ndpi_default_ports_tree_node_t *) ndpi_tdelete( + &node, (void *) root, ndpi_default_ports_tree_node_t_cmp); /* Add it to the tree */ if(ret != NULL) { - ndpi_free((ndpi_default_ports_tree_node_t*)ret); + ndpi_free((ndpi_default_ports_tree_node_t *) ret); return(0); } } @@ -420,27 +443,25 @@ static int removeDefaultPort(ndpi_port_range *range, /* ****************************************************** */ -static int ndpi_string_to_automa(struct ndpi_detection_module_struct *ndpi_str, - ndpi_automa *automa, - char *value, u_int16_t protocol_id, - ndpi_protocol_category_t category, - ndpi_protocol_breed_t breed) { +static int ndpi_string_to_automa(struct ndpi_detection_module_struct *ndpi_str, ndpi_automa *automa, char *value, + u_int16_t protocol_id, ndpi_protocol_category_t category, ndpi_protocol_breed_t breed, + u_int8_t free_str_on_duplicate) { AC_PATTERN_t ac_pattern; + AC_ERROR_t rc; - if(protocol_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) { + if((value == NULL) || (protocol_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS))) { NDPI_LOG_ERR(ndpi_str, "[NDPI] protoId=%d: INTERNAL ERROR\n", protocol_id); return(-1); } - if(automa->ac_automa == NULL) return(-2); - ac_pattern.astring = value, - ac_pattern.rep.number = protocol_id, - ac_pattern.rep.category = (u_int16_t)category, - ac_pattern.rep.breed = (u_int16_t)breed; + if(automa->ac_automa == NULL) + return(-2); + + ac_pattern.astring = value, ac_pattern.rep.number = protocol_id, + ac_pattern.rep.category = (u_int16_t) category, ac_pattern.rep.breed = (u_int16_t) breed; #ifdef MATCH_DEBUG - printf("Adding to automa [%s][protocol_id: %u][category: %u][breed: %u]\n", - value, protocol_id, category, breed); + printf("Adding to automa [%s][protocol_id: %u][category: %u][breed: %u]\n", value, protocol_id, category, breed); #endif if(value == NULL) @@ -448,34 +469,33 @@ static int ndpi_string_to_automa(struct ndpi_detection_module_struct *ndpi_str, else ac_pattern.length = strlen(ac_pattern.astring); - if(ac_automata_add(((AC_AUTOMATA_t*)automa->ac_automa), &ac_pattern) != ACERR_SUCCESS) + rc = ac_automata_add(((AC_AUTOMATA_t *) automa->ac_automa), &ac_pattern); + if(rc != ACERR_DUPLICATE_PATTERN && rc != ACERR_SUCCESS) return(-2); + if(rc == ACERR_DUPLICATE_PATTERN && free_str_on_duplicate) + ndpi_free(value); return(0); } /* ****************************************************** */ -static int ndpi_add_host_url_subprotocol(struct ndpi_detection_module_struct *ndpi_str, - char *_value, int protocol_id, - ndpi_protocol_category_t category, - ndpi_protocol_breed_t breed) { +static int ndpi_add_host_url_subprotocol(struct ndpi_detection_module_struct *ndpi_str, char *_value, int protocol_id, + ndpi_protocol_category_t category, ndpi_protocol_breed_t breed) { int rv; char *value = ndpi_strdup(_value); - if(!value) return(-1); + if(!value) + return(-1); #ifdef DEBUG - NDPI_LOG_DEBUG2(ndpi_str, "[NDPI] Adding [%s][%d]\n", value, protocol_id); + NDPI_LOG_DBG2(ndpi_str, "[NDPI] Adding [%s][%d]\n", value, protocol_id); #endif - rv = ndpi_string_to_automa(ndpi_str, - &ndpi_str->host_automa, - value, - protocol_id, - category, breed); + rv = ndpi_string_to_automa(ndpi_str, &ndpi_str->host_automa, value, protocol_id, category, breed, 1); - if(rv != 0) ndpi_free(value); + if(rv != 0) + ndpi_free(value); return(rv); } @@ -483,12 +503,9 @@ static int ndpi_add_host_url_subprotocol(struct ndpi_detection_module_struct *nd /* ****************************************************** */ #ifdef CODE_UNUSED -int ndpi_add_content_subprotocol(struct ndpi_detection_module_struct *ndpi_str, - char *value, int protocol_id, - ndpi_protocol_category_t category, - ndpi_protocol_breed_t breed) { - return(ndpi_string_to_automa(ndpi_str, &ndpi_str->content_automa, - value, protocol_id, category, breed)); +int ndpi_add_content_subprotocol(struct ndpi_detection_module_struct *ndpi_str, char *value, int protocol_id, + ndpi_protocol_category_t category, ndpi_protocol_breed_t breed) { + return(ndpi_string_to_automa(ndpi_str, &ndpi_str->content_automa, value, protocol_id, category, breed, 0)); } #endif @@ -500,185 +517,60 @@ int ndpi_add_content_subprotocol(struct ndpi_detection_module_struct *ndpi_str, This function must be called with a semaphore set, this in order to avoid changing the datastructures while using them */ -static int ndpi_remove_host_url_subprotocol(struct ndpi_detection_module_struct *ndpi_str, - char *value, int protocol_id) -{ - NDPI_LOG_ERR(ndpi_str, "[NDPI] Missing implementation for proto %s/%d\n",value,protocol_id); +static int ndpi_remove_host_url_subprotocol(struct ndpi_detection_module_struct *ndpi_str, char *value, int protocol_id) { + NDPI_LOG_ERR(ndpi_str, "[NDPI] Missing implementation for proto %s/%d\n", value, protocol_id); return(-1); } /* ******************************************************************** */ -void ndpi_init_protocol_match(struct ndpi_detection_module_struct *ndpi_str, - ndpi_protocol_match *match) { - u_int16_t no_master[2] = { NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO }; +void ndpi_init_protocol_match(struct ndpi_detection_module_struct *ndpi_str, ndpi_protocol_match *match) { + u_int16_t no_master[2] = {NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO}; ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS]; if(ndpi_str->proto_defaults[match->protocol_id].protoName == NULL) { - ndpi_str->proto_defaults[match->protocol_id].protoName = ndpi_strdup(match->proto_name); + ndpi_str->proto_defaults[match->protocol_id].protoName = ndpi_strdup(match->proto_name); - ndpi_str->proto_defaults[match->protocol_id].protoId = match->protocol_id; + ndpi_str->proto_defaults[match->protocol_id].protoId = match->protocol_id; ndpi_str->proto_defaults[match->protocol_id].protoCategory = match->protocol_category; - ndpi_str->proto_defaults[match->protocol_id].protoBreed = match->protocol_breed; - - ndpi_set_proto_defaults(ndpi_str, - ndpi_str->proto_defaults[match->protocol_id].protoBreed, - ndpi_str->proto_defaults[match->protocol_id].protoId, - 0 /* can_have_a_subprotocol */, - no_master, no_master, - ndpi_str->proto_defaults[match->protocol_id].protoName, + ndpi_str->proto_defaults[match->protocol_id].protoBreed = match->protocol_breed; + + ndpi_set_proto_defaults(ndpi_str, ndpi_str->proto_defaults[match->protocol_id].protoBreed, + ndpi_str->proto_defaults[match->protocol_id].protoId, 0 /* can_have_a_subprotocol */, + no_master, no_master, ndpi_str->proto_defaults[match->protocol_id].protoName, ndpi_str->proto_defaults[match->protocol_id].protoCategory, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); } - ndpi_add_host_url_subprotocol(ndpi_str, - match->string_to_match, - match->protocol_id, - match->protocol_category, + ndpi_add_host_url_subprotocol(ndpi_str, match->string_to_match, match->protocol_id, match->protocol_category, match->protocol_breed); } /* ******************************************************************** */ -#ifdef HAVE_HYPERSCAN - -static int hyperscan_load_patterns(struct hs *hs, u_int num_patterns, - const char **expressions, unsigned int *ids) { - hs_compile_error_t *compile_err; - - if(hs_compile_multi(expressions, NULL, ids, - num_patterns, HS_MODE_BLOCK, NULL, - &hs->database, &compile_err) != HS_SUCCESS) { - NDPI_LOG_ERR(ndpi_str, "Unable to initialize hyperscan database\n"); - hs_free_compile_error(compile_err); - return(-1); - } - - hs->scratch = NULL; - if(hs_alloc_scratch(hs->database, &hs->scratch) != HS_SUCCESS) { - NDPI_LOG_ERR(ndpi_str, "Unable to allocate hyperscan scratch space\n"); - hs_free_database(hs->database); - return(-1); - } - - return(0); -} - -/* ******************************************************************** */ - -static char* string2hex(const char *pat) { - u_int patlen, i; - char *hexbuf, *buf; - - patlen = strlen(pat); - hexbuf = (char*)ndpi_calloc(sizeof(char), patlen * 4 + 1); - if(!hexbuf) return(NULL); - - for (i = 0, buf = hexbuf; i < patlen; i++, buf += 4) { - snprintf(buf, 5, "\\x%02x", (unsigned char)pat[i]); - } - *buf = '\0'; - - return(hexbuf); -} - -static int init_hyperscan(struct ndpi_detection_module_struct *ndpi_str) { - u_int num_patterns = 0, i, j; - char **expressions; - unsigned int *ids; - unsigned char *need_to_be_free; - struct hs *hs; - int rc; - - ndpi_str->hyperscan = (void*)ndpi_malloc(sizeof(struct hs)); - if(!ndpi_str->hyperscan) return(-1); - hs = (struct hs*)ndpi_str->hyperscan; - - for(i = 0; (host_match[i].string_to_match != NULL) - || (host_match[i].pattern_to_match != NULL); i++) - num_patterns++; - - expressions = (char**)ndpi_calloc(sizeof(char*), num_patterns + 1); - if(!expressions) return(-1); - - ids = (unsigned int*)ndpi_calloc(sizeof(unsigned int), num_patterns + 1); - if(!ids) { - ndpi_free(expressions); - return(-1); - } - - need_to_be_free = (unsigned char*)ndpi_calloc(sizeof(unsigned char), num_patterns + 1); - if(!need_to_be_free) { - ndpi_free(expressions); - ndpi_free(ids); - return(-1); - } - - for(i = 0, j = 0; host_match[i].string_to_match != NULL || host_match[i].pattern_to_match != NULL; i++) { - if(host_match[i].pattern_to_match) { - expressions[j] = host_match[i].pattern_to_match; - ids[j] = host_match[i].protocol_id; - need_to_be_free[j] = 0; - ++j; - } else { - expressions[j] = string2hex(host_match[i].string_to_match); - if(expressions[j] != NULL) { - ids[j] = host_match[i].protocol_id; - need_to_be_free[j] = 1; - ++j; - } else { -#ifdef DEBUG - printf("Fail to calloc memory for %s\n", host_match[i].string_to_match); -#endif +/* Self check function to be called onli for testing purposes */ +void ndpi_self_check_host_match() { + u_int32_t i, j; + + for (i = 0; host_match[i].string_to_match != NULL; i++) { + for (j = 0; host_match[j].string_to_match != NULL; j++) { + if((i != j) && (strcmp(host_match[i].string_to_match, host_match[j].string_to_match) == 0)) { + printf("[INTERNAL ERROR]: Duplicate string detected '%s' [id: %u, id %u]\n", + host_match[i].string_to_match, i, j); + printf("\nPlease fix host_match[] in ndpi_content_match.c.inc\n"); + exit(0); } } - /*printf("[DEBUG] %s\n", j ? expressions[j - 1] : "No Expression");*/ - } - - rc = hyperscan_load_patterns(hs, j, (const char**)expressions, ids); - - for(i = 0; i < j; ++i) - if(need_to_be_free[i]) - ndpi_free(expressions[i]); - - ndpi_free(expressions), ndpi_free(ids); - - ndpi_free(need_to_be_free); - - return(rc); -} - -/* ******************************************************************** */ - -static void free_hyperscan_memory(struct hs *h) { - if(h) { - hs_free_scratch(h->scratch); - hs_free_database(h->database); - ndpi_free(h); } } /* ******************************************************************** */ -static void destroy_hyperscan(struct ndpi_detection_module_struct *ndpi_str) { - if(ndpi_str->hyperscan) - free_hyperscan_memory((struct hs*)ndpi_str->hyperscan); -} - -#endif - -/* ******************************************************************** */ - static void init_string_based_protocols(struct ndpi_detection_module_struct *ndpi_str) { int i; -#ifdef HAVE_HYPERSCAN - // TODO check return value - init_hyperscan(ndpi_str); -#endif - - for(i=0; host_match[i].string_to_match != NULL; i++) + for (i = 0; host_match[i].string_to_match != NULL; i++) ndpi_init_protocol_match(ndpi_str, &host_match[i]); ndpi_enable_loaded_categories(ndpi_str); @@ -687,37 +579,30 @@ static void init_string_based_protocols(struct ndpi_detection_module_struct *ndp // ac_automata_display(ndpi_str->host_automa.ac_automa, 'n'); #endif - for(i=0; ndpi_en_bigrams[i] != NULL; i++) - ndpi_string_to_automa(ndpi_str, &ndpi_str->bigrams_automa, - (char*)ndpi_en_bigrams[i], - 1, 1, 1); +#if 1 + for (i = 0; ndpi_en_bigrams[i] != NULL; i++) + ndpi_string_to_automa(ndpi_str, &ndpi_str->bigrams_automa, (char *) ndpi_en_bigrams[i], 1, 1, 1, 0); +#else + for (i = 0; ndpi_en_popular_bigrams[i] != NULL; i++) + ndpi_string_to_automa(ndpi_str, &ndpi_str->bigrams_automa, (char *) ndpi_en_popular_bigrams[i], 1, 1, 1, 0); +#endif - for(i=0; ndpi_en_impossible_bigrams[i] != NULL; i++) - ndpi_string_to_automa(ndpi_str, &ndpi_str->impossible_bigrams_automa, - (char*)ndpi_en_impossible_bigrams[i], - 1, 1, 1); + for (i = 0; ndpi_en_impossible_bigrams[i] != NULL; i++) + ndpi_string_to_automa(ndpi_str, &ndpi_str->impossible_bigrams_automa, (char *) ndpi_en_impossible_bigrams[i], 1, + 1, 1, 0); } /* ******************************************************************** */ -int ndpi_set_detection_preferences(struct ndpi_detection_module_struct *ndpi_str, - ndpi_detection_preference pref, - int value) { +int ndpi_set_detection_preferences(struct ndpi_detection_module_struct *ndpi_str, ndpi_detection_preference pref, + int value) { switch(pref) { - case ndpi_pref_http_dont_dissect_response: - ndpi_str->http_dont_dissect_response = (u_int8_t)value; - break; - - case ndpi_pref_dns_dont_dissect_response: - ndpi_str->dns_dont_dissect_response = (u_int8_t)value; - break; - case ndpi_pref_direction_detect_disable: - ndpi_str->direction_detect_disable = (u_int8_t)value; + ndpi_str->direction_detect_disable = (u_int8_t) value; break; - case ndpi_pref_disable_metadata_export: - ndpi_str->disable_metadata_export = (u_int8_t)value; + case ndpi_pref_enable_tls_block_dissection: + ndpi_str->num_tls_blocks_to_follow = NDPI_MAX_NUM_TLS_APPL_BLOCKS; break; default: @@ -732,14 +617,16 @@ int ndpi_set_detection_preferences(struct ndpi_detection_module_struct *ndpi_str static void ndpi_validate_protocol_initialization(struct ndpi_detection_module_struct *ndpi_str) { int i; - for(i=0; i<(int)ndpi_str->ndpi_num_supported_protocols; i++) { + for (i = 0; i < (int) ndpi_str->ndpi_num_supported_protocols; i++) { if(ndpi_str->proto_defaults[i].protoName == NULL) { - NDPI_LOG_ERR(ndpi_str, "[NDPI] INTERNAL ERROR missing protoName initialization for [protoId=%d]: recovering\n", i); + NDPI_LOG_ERR(ndpi_str, + "[NDPI] INTERNAL ERROR missing protoName initialization for [protoId=%d]: recovering\n", i); } else { - if((i != NDPI_PROTOCOL_UNKNOWN) - && (ndpi_str->proto_defaults[i].protoCategory == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED)) { - NDPI_LOG_ERR(ndpi_str, "[NDPI] INTERNAL ERROR missing category [protoId=%d/%s] initialization: recovering\n", - i, ndpi_str->proto_defaults[i].protoName ? ndpi_str->proto_defaults[i].protoName : "???"); + if((i != NDPI_PROTOCOL_UNKNOWN) && + (ndpi_str->proto_defaults[i].protoCategory == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED)) { + NDPI_LOG_ERR(ndpi_str, + "[NDPI] INTERNAL ERROR missing category [protoId=%d/%s] initialization: recovering\n", i, + ndpi_str->proto_defaults[i].protoName ? ndpi_str->proto_defaults[i].protoName : "???"); } } } @@ -754,1020 +641,893 @@ static void ndpi_validate_protocol_initialization(struct ndpi_detection_module_s */ static void ndpi_init_protocol_defaults(struct ndpi_detection_module_struct *ndpi_str) { ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS]; - u_int16_t no_master[2] = { NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO }, - custom_master[2]; - - /* Reset all settings */ - memset(ndpi_str->proto_defaults, 0, sizeof(ndpi_str->proto_defaults)); - - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNRATED, NDPI_PROTOCOL_UNKNOWN, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Unknown", NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_FTP_CONTROL, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "FTP_CONTROL", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 21, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_FTP_DATA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "FTP_DATA", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 20, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_MAIL_POP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "POP3", NDPI_PROTOCOL_CATEGORY_MAIL, - ndpi_build_default_ports(ports_a, 110, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_MAIL_POPS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "POPS", NDPI_PROTOCOL_CATEGORY_MAIL, - ndpi_build_default_ports(ports_a, 995, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MAIL_SMTP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SMTP", NDPI_PROTOCOL_CATEGORY_MAIL, - ndpi_build_default_ports(ports_a, 25, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_MAIL_SMTPS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SMTPS", NDPI_PROTOCOL_CATEGORY_MAIL, - ndpi_build_default_ports(ports_a, 465, 587, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_MAIL_IMAP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IMAP", NDPI_PROTOCOL_CATEGORY_MAIL, - ndpi_build_default_ports(ports_a, 143, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_MAIL_IMAPS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IMAPS", NDPI_PROTOCOL_CATEGORY_MAIL, - ndpi_build_default_ports(ports_a, 993, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DNS, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "DNS", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 53, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 53, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IPP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IPP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IMO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IMO", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "HTTP", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 80, 0 /* ntop */, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MDNS, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "MDNS", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 5353, 5354, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NTP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "NTP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 123, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NETBIOS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "NetBIOS", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 139, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 137, 138, 139, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NFS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "NFS", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, - ndpi_build_default_ports(ports_a, 2049, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 2049, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SSDP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SSDP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_BGP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "BGP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 179, 2605, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SNMP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SNMP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 161, 162, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_XDMCP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "XDMCP", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 177, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 177, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_DANGEROUS, NDPI_PROTOCOL_SMBV1, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SMBv1", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SYSLOG, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Syslog", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 514, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 514, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DHCP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DHCP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 67, 68, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_POSTGRES, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "PostgreSQL", NDPI_PROTOCOL_CATEGORY_DATABASE, - ndpi_build_default_ports(ports_a, 5432, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MYSQL, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "MySQL", NDPI_PROTOCOL_CATEGORY_DATABASE, - ndpi_build_default_ports(ports_a, 3306, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_DIRECT_DOWNLOAD_LINK, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Direct_Download_Link", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_APPLEJUICE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "AppleJuice", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_DIRECTCONNECT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DirectConnect", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_NTOP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "ntop", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_VMWARE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "VMware", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 903, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 902, 903, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_FBZERO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "FacebookZero", NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, - ndpi_build_default_ports(ports_a, 443, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_KONTIKI, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Kontiki", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_OPENFT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "OpenFT", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_FASTTRACK, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "FastTrack", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_GNUTELLA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Gnutella", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_EDONKEY, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "eDonkey", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_BITTORRENT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "BitTorrent", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 51413, 53646, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 6771, 51413, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SKYPE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Skype", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SKYPE_CALL, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SkypeCall", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_TIKTOK, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "TikTok", NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TEREDO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Teredo", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 3544, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WECHAT, - 0 /* can_have_a_subprotocol */, no_master, /* wechat.com */ - no_master, "WeChat", NDPI_PROTOCOL_CATEGORY_CHAT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MEMCACHED, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Memcached", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 11211, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 11211, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SMBV23, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SMBv23", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 445, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_MINING, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Mining", CUSTOM_CATEGORY_MINING, - ndpi_build_default_ports(ports_a, 8333, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NEST_LOG_SINK, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "NestLogSink", NDPI_PROTOCOL_CATEGORY_CLOUD, - ndpi_build_default_ports(ports_a, 11095, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MODBUS, - 1 /* no subprotocol */, no_master, - no_master, "Modbus", NDPI_PROTOCOL_CATEGORY_NETWORK, /* Perhaps IoT in the future */ - ndpi_build_default_ports(ports_a, 502, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHATSAPP_CALL, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "WhatsAppCall", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DATASAVER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DataSaver", NDPI_PROTOCOL_CATEGORY_WEB /* dummy */, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SIGNAL, - 0 /* can_have_a_subprotocol */, no_master, /* https://signal.org */ - no_master, "Signal", NDPI_PROTOCOL_CATEGORY_CHAT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DNS_OVER_HTTPS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DNSoverHTTPS", NDPI_PROTOCOL_CATEGORY_NETWORK /* dummy */, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_LINE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Line", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WIREGUARD, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "WireGuard", NDPI_PROTOCOL_CATEGORY_VPN, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 51820, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PPSTREAM, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "PPStream", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_XBOX, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Xbox", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 3074, 3076, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 3074, 3076, 500, 3544, 4500) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PLAYSTATION, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Playstation", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 1935, 3478, 3479, 3480, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 3478, 3479, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_QQ, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "QQ", NDPI_PROTOCOL_CATEGORY_CHAT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_RTSP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RTSP", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 554, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 554, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_ICECAST, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IceCast", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PPLIVE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "PPLive", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PPSTREAM, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "PPStream", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_ZATTOO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Zattoo", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SHOUTCAST, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "ShoutCast", NDPI_PROTOCOL_CATEGORY_MUSIC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SOPCAST, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Sopcast", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_TVANTS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Tvants", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_TVUPLAYER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "TVUplayer", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_DOWNLOAD, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "HTTP_Download", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_QQLIVE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "QQLive", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_THUNDER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Thunder", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SOULSEEK, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Soulseek", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_PS_VUE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "PS_VUE", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_IRC, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IRC", NDPI_PROTOCOL_CATEGORY_CHAT, - ndpi_build_default_ports(ports_a, 194, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 194, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AYIYA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Ayiya", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 5072, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_UNENCRYPTED_JABBER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Unencrypted_Jabber", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_OSCAR, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Oscar", NDPI_PROTOCOL_CATEGORY_CHAT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_BATTLEFIELD, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "BattleField", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_VRRP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "VRRP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_STEAM, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Steam", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_HALFLIFE2, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "HalfLife2", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WORLDOFWARCRAFT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "WorldOfWarcraft", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_HOTSPOT_SHIELD, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "HotspotShield", NDPI_PROTOCOL_CATEGORY_VPN, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_TELNET, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Telnet", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 23, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + u_int16_t no_master[2] = {NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO}, custom_master[2]; + + /* Reset all settings */ + memset(ndpi_str->proto_defaults, 0, sizeof(ndpi_str->proto_defaults)); + + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNRATED, NDPI_PROTOCOL_UNKNOWN, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Unknown", NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_FTP_CONTROL, 0 /* can_have_a_subprotocol */, + no_master, no_master, "FTP_CONTROL", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 21, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_FTP_DATA, 0 /* can_have_a_subprotocol */, + no_master, no_master, "FTP_DATA", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 20, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_MAIL_POP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "POP3", NDPI_PROTOCOL_CATEGORY_MAIL, + ndpi_build_default_ports(ports_a, 110, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_MAIL_POPS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "POPS", NDPI_PROTOCOL_CATEGORY_MAIL, + ndpi_build_default_ports(ports_a, 995, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MAIL_SMTP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SMTP", NDPI_PROTOCOL_CATEGORY_MAIL, + ndpi_build_default_ports(ports_a, 25, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_MAIL_SMTPS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SMTPS", NDPI_PROTOCOL_CATEGORY_MAIL, + ndpi_build_default_ports(ports_a, 465, 587, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_MAIL_IMAP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IMAP", NDPI_PROTOCOL_CATEGORY_MAIL, + ndpi_build_default_ports(ports_a, 143, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_MAIL_IMAPS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IMAPS", NDPI_PROTOCOL_CATEGORY_MAIL, + ndpi_build_default_ports(ports_a, 993, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DNS, 1 /* can_have_a_subprotocol */, + no_master, no_master, "DNS", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 53, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 53, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IPP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IPP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IMO, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IMO", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP, 1 /* can_have_a_subprotocol */, + no_master, no_master, "HTTP", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 80, 0 /* ntop */, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MDNS, 1 /* can_have_a_subprotocol */, + no_master, no_master, "MDNS", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5353, 5354, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NTP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "NTP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 123, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NETBIOS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "NetBIOS", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 139, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 137, 138, 139, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NFS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "NFS", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, + ndpi_build_default_ports(ports_a, 2049, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 2049, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SSDP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SSDP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_BGP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "BGP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 179, 2605, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SNMP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SNMP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 161, 162, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_XDMCP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "XDMCP", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 177, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 177, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_DANGEROUS, NDPI_PROTOCOL_SMBV1, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SMBv1", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 445, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SYSLOG, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Syslog", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 514, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 514, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DHCP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "DHCP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 67, 68, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_POSTGRES, 0 /* can_have_a_subprotocol */, + no_master, no_master, "PostgreSQL", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 5432, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MYSQL, 0 /* can_have_a_subprotocol */, + no_master, no_master, "MySQL", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 3306, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_DIRECT_DOWNLOAD_LINK, + 0 /* can_have_a_subprotocol */, no_master, no_master, "Direct_Download_Link", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_APPLEJUICE, + 0 /* can_have_a_subprotocol */, no_master, no_master, "AppleJuice", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_DIRECTCONNECT, + 0 /* can_have_a_subprotocol */, no_master, no_master, "DirectConnect", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NATS, + 0 /* can_have_a_subprotocol */, no_master, no_master, "Nats", + NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_NTOP, 0 /* can_have_a_subprotocol */, no_master, + no_master, "ntop", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_VMWARE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "VMware", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 903, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 902, 903, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_FBZERO, 0 /* can_have_a_subprotocol */, + no_master, no_master, "FacebookZero", NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, + ndpi_build_default_ports(ports_a, 443, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_KONTIKI, + 0 /* can_have_a_subprotocol */, no_master, no_master, "Kontiki", + NDPI_PROTOCOL_CATEGORY_MEDIA, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_OPENFT, + 0 /* can_have_a_subprotocol */, no_master, no_master, "OpenFT", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_FASTTRACK, + 0 /* can_have_a_subprotocol */, no_master, no_master, "FastTrack", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_GNUTELLA, + 0 /* can_have_a_subprotocol */, no_master, no_master, "Gnutella", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_EDONKEY, 0 /* can_have_a_subprotocol */, + no_master, no_master, "eDonkey", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_BITTORRENT, 0 /* can_have_a_subprotocol */, + no_master, no_master, "BitTorrent", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 51413, 53646, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 6771, 51413, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SKYPE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Skype", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SKYPE_CALL, + 0 /* can_have_a_subprotocol */, no_master, no_master, "SkypeCall", + NDPI_PROTOCOL_CATEGORY_VOIP, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_TIKTOK, 0 /* can_have_a_subprotocol */, + no_master, no_master, "TikTok", NDPI_PROTOCOL_CATEGORY_SOCIAL_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TEREDO, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Teredo", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults( + ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WECHAT, 0 /* can_have_a_subprotocol */, no_master, /* wechat.com */ + no_master, "WeChat", NDPI_PROTOCOL_CATEGORY_CHAT, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MEMCACHED, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Memcached", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 11211, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 11211, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SMBV23, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SMBv23", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 445, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_MINING, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Mining", CUSTOM_CATEGORY_MINING, + ndpi_build_default_ports(ports_a, 8333, 30303, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NEST_LOG_SINK, + 0 /* can_have_a_subprotocol */, no_master, no_master, "NestLogSink", + NDPI_PROTOCOL_CATEGORY_CLOUD, + ndpi_build_default_ports(ports_a, 11095, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MODBUS, 1 /* no subprotocol */, no_master, + no_master, "Modbus", NDPI_PROTOCOL_CATEGORY_IOT_SCADA, + ndpi_build_default_ports(ports_a, 502, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHATSAPP_CALL, + 0 /* can_have_a_subprotocol */, no_master, no_master, "WhatsAppCall", + NDPI_PROTOCOL_CATEGORY_VOIP, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DATASAVER, 0 /* can_have_a_subprotocol */, + no_master, no_master, "DataSaver", NDPI_PROTOCOL_CATEGORY_WEB /* dummy */, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SIGNAL, 0 /* can_have_a_subprotocol */, + no_master, /* https://signal.org */ + no_master, "Signal", NDPI_PROTOCOL_CATEGORY_CHAT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DOH_DOT, 0 /* can_have_a_subprotocol */, + no_master, no_master, "DoH_DoT", NDPI_PROTOCOL_CATEGORY_NETWORK /* dummy */, + ndpi_build_default_ports(ports_a, 853, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FREE_205, 0 /* can_have_a_subprotocol */, + no_master, no_master, "FREE_205", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WIREGUARD, 0 /* can_have_a_subprotocol */, + no_master, no_master, "WireGuard", NDPI_PROTOCOL_CATEGORY_VPN, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 51820, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PPSTREAM, 0 /* can_have_a_subprotocol */, + no_master, no_master, "PPStream", NDPI_PROTOCOL_CATEGORY_VIDEO, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_XBOX, 0 /* can_have_a_subprotocol */, no_master, + no_master, "Xbox", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 3074, 3076, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 3074, 3076, 500, 4500, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PLAYSTATION, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Playstation", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 1935, 3478, 3479, 3480, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 3478, 3479, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_QQ, 0 /* can_have_a_subprotocol */, no_master, + no_master, "QQ", NDPI_PROTOCOL_CATEGORY_CHAT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_RTSP, 0 /* can_have_a_subprotocol */, no_master, + no_master, "RTSP", NDPI_PROTOCOL_CATEGORY_MEDIA, + ndpi_build_default_ports(ports_a, 554, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 554, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_ICECAST, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IceCast", NDPI_PROTOCOL_CATEGORY_MEDIA, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FREE_53, 0 /* can_have_a_subprotocol */, + no_master, no_master, "FREE53", NDPI_PROTOCOL_CATEGORY_MEDIA, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PPSTREAM, 0 /* can_have_a_subprotocol */, + no_master, no_master, "PPStream", NDPI_PROTOCOL_CATEGORY_MEDIA, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_ZATTOO, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Zattoo", NDPI_PROTOCOL_CATEGORY_VIDEO, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SHOUTCAST, 0 /* can_have_a_subprotocol */, + no_master, no_master, "ShoutCast", NDPI_PROTOCOL_CATEGORY_MUSIC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SOPCAST, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Sopcast", NDPI_PROTOCOL_CATEGORY_VIDEO, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DISCORD, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Discord", NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_TVUPLAYER, 0 /* can_have_a_subprotocol */, + no_master, no_master, "TVUplayer", NDPI_PROTOCOL_CATEGORY_VIDEO, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_DOWNLOAD, + 1 /* can_have_a_subprotocol */, no_master, no_master, "HTTP_Download", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_QQLIVE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "QQLive", NDPI_PROTOCOL_CATEGORY_VIDEO, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_THUNDER, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Thunder", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_SOULSEEK, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Soulseek", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_PS_VUE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "PS_VUE", NDPI_PROTOCOL_CATEGORY_VIDEO, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_IRC, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IRC", NDPI_PROTOCOL_CATEGORY_CHAT, + ndpi_build_default_ports(ports_a, 194, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 194, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AYIYA, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Ayiya", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5072, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_UNENCRYPTED_JABBER, + 0 /* can_have_a_subprotocol */, no_master, no_master, "Unencrypted_Jabber", + NDPI_PROTOCOL_CATEGORY_WEB, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_FREE_69, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Free69", NDPI_PROTOCOL_CATEGORY_CHAT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FREE_71, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Free71", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_VRRP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "VRRP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_STEAM, 0 /* can_have_a_subprotocol */, no_master, + no_master, "Steam", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_HALFLIFE2, 0 /* can_have_a_subprotocol */, + no_master, no_master, "HalfLife2", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WORLDOFWARCRAFT, 0 /* can_have_a_subprotocol */, + no_master, no_master, "WorldOfWarcraft", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_HOTSPOT_SHIELD, + 0 /* can_have_a_subprotocol */, no_master, no_master, "HotspotShield", + NDPI_PROTOCOL_CATEGORY_VPN, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_UNSAFE, NDPI_PROTOCOL_TELNET, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Telnet", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 23, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + + custom_master[0] = NDPI_PROTOCOL_SIP, custom_master[1] = NDPI_PROTOCOL_H323; + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_STUN, 0 /* can_have_a_subprotocol */, + no_master, custom_master, "STUN", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 3478, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_IP_IPSEC, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IPsec", NDPI_PROTOCOL_CATEGORY_VPN, + ndpi_build_default_ports(ports_a, 500, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 500, 4500, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_GRE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "GRE", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_ICMP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "ICMP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_IGMP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IGMP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_EGP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "EGP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_SCTP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SCTP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_OSPF, 0 /* can_have_a_subprotocol */, + no_master, no_master, "OSPF", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 2604, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_IP_IN_IP, + 0 /* can_have_a_subprotocol */, no_master, no_master, "IP_in_IP", + NDPI_PROTOCOL_CATEGORY_NETWORK, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RTP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "RTP", NDPI_PROTOCOL_CATEGORY_MEDIA, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RDP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "RDP", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 3389, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 3389, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_VNC, 0 /* can_have_a_subprotocol */, + no_master, no_master, "VNC", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 5900, 5901, 5800, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_FREE90, 0 /* can_have_a_subprotocol */, + no_master, no_master, "FREE_90", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ZOOM, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Zoom", NDPI_PROTOCOL_CATEGORY_VIDEO, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHATSAPP_FILES, + 0 /* can_have_a_subprotocol */, no_master, no_master, "WhatsAppFiles", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHATSAPP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "WhatsApp", NDPI_PROTOCOL_CATEGORY_CHAT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_TLS, 1 /* can_have_a_subprotocol */, no_master, + no_master, "TLS", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 443, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SSH, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SSH", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 22, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_USENET, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Usenet", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MGCP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "MGCP", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IAX, 0 /* can_have_a_subprotocol */, + no_master, no_master, "IAX", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 4569, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 4569, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AFP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "AFP", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, + ndpi_build_default_ports(ports_a, 548, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 548, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_HULU, 0 /* can_have_a_subprotocol */, no_master, + no_master, "Hulu", NDPI_PROTOCOL_CATEGORY_STREAMING, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CHECKMK, 0 /* can_have_a_subprotocol */, + no_master, no_master, "CHECKMK", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, + ndpi_build_default_ports(ports_a, 6556, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_STEALTHNET, + 0 /* can_have_a_subprotocol */, no_master, no_master, "Stealthnet", + NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_AIMINI, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Aimini", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SIP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SIP", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 5060, 5061, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5060, 5061, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TRUPHONE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "TruPhone", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_ICMPV6, 0 /* can_have_a_subprotocol */, + no_master, no_master, "ICMPV6", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DHCPV6, 0 /* can_have_a_subprotocol */, + no_master, no_master, "DHCPV6", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_ARMAGETRON, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Armagetron", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_CROSSFIRE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Crossfire", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DOFUS, 0 /* can_have_a_subprotocol */, no_master, + no_master, "Dofus", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FIESTA, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Fiesta", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FLORENSIA, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Florensia", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_GUILDWARS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Guildwars", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_ACTIVESYNC, + 1 /* can_have_a_subprotocol */, no_master, no_master, "HTTP_ActiveSync", + NDPI_PROTOCOL_CATEGORY_CLOUD, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_KERBEROS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Kerberos", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 88, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 88, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LDAP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "LDAP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 389, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 389, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_MAPLESTORY, 0 /* can_have_a_subprotocol */, + no_master, no_master, "MapleStory", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MSSQL_TDS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "MsSQL-TDS", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 1433, 1434, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_PPTP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "PPTP", NDPI_PROTOCOL_CATEGORY_VPN, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WARCRAFT3, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Warcraft3", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WORLD_OF_KUNG_FU, 0 /* can_have_a_subprotocol */, + no_master, no_master, "WorldOfKungFu", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DCERPC, 0 /* can_have_a_subprotocol */, + no_master, no_master, "DCE_RPC", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 135, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NETFLOW, 0 /* can_have_a_subprotocol */, + no_master, no_master, "NetFlow", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 2055, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SFLOW, 0 /* can_have_a_subprotocol */, + no_master, no_master, "sFlow", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 6343, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_CONNECT, + 1 /* can_have_a_subprotocol */, no_master, no_master, "HTTP_Connect", + NDPI_PROTOCOL_CATEGORY_WEB, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_PROXY, + 1 /* can_have_a_subprotocol */, no_master, no_master, "HTTP_Proxy", + NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 8080, 3128, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CITRIX, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Citrix", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 1494, 2598, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WEBEX, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Webex", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RADIUS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Radius", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 1812, 1813, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 1812, 1813, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TEAMVIEWER, + 0 /* can_have_a_subprotocol */, no_master, no_master, "TeamViewer", + NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 5938, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5938, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LOTUS_NOTES, + 0 /* can_have_a_subprotocol */, no_master, no_master, "LotusNotes", + NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, + ndpi_build_default_ports(ports_a, 1352, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults( + ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SAP, 0 /* can_have_a_subprotocol */, no_master, no_master, + "SAP", NDPI_PROTOCOL_CATEGORY_NETWORK, ndpi_build_default_ports(ports_a, 3201, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); /* Missing dissector: port based only */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_GTP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "GTP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 2152, 2123, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_UPNP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "UPnP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 1780, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 1900, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TELEGRAM, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Telegram", NDPI_PROTOCOL_CATEGORY_CHAT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_QUIC, 1 /* can_have_a_subprotocol */, + no_master, no_master, "QUIC", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 443, 80, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DIAMETER, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Diameter", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 3868, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_APPLE_PUSH, + 0 /* can_have_a_subprotocol */, no_master, no_master, "ApplePush", + NDPI_PROTOCOL_CATEGORY_CLOUD, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DROPBOX, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Dropbox", NDPI_PROTOCOL_CATEGORY_CLOUD, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 17500, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SPOTIFY, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Spotify", NDPI_PROTOCOL_CATEGORY_MUSIC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MESSENGER, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Messenger", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LISP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "LISP", NDPI_PROTOCOL_CATEGORY_CLOUD, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 4342, 4341, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_EAQ, 0 /* can_have_a_subprotocol */, + no_master, no_master, "EAQ", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 6000, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_KAKAOTALK_VOICE, + 0 /* can_have_a_subprotocol */, no_master, no_master, "KakaoTalk_Voice", + NDPI_PROTOCOL_CATEGORY_VOIP, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_MPEGTS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "MPEG_TS", NDPI_PROTOCOL_CATEGORY_MEDIA, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + /* http://en.wikipedia.org/wiki/Link-local_Multicast_Name_Resolution */ + ndpi_set_proto_defaults( + ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LLMNR, 0 /* can_have_a_subprotocol */, no_master, no_master, + "LLMNR", NDPI_PROTOCOL_CATEGORY_NETWORK, ndpi_build_default_ports(ports_a, 5355, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5355, 0, 0, 0, 0) /* UDP */); /* Missing dissector: port based only */ + ndpi_set_proto_defaults( + ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_REMOTE_SCAN, 0 /* can_have_a_subprotocol */, + no_master, no_master, "RemoteScan", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 6077, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 6078, 0, 0, 0, 0) /* UDP */); /* Missing dissector: port based only */ + + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_H323, 0 /* can_have_a_subprotocol */, + no_master, no_master, "H323", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 1719, 1720, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 1719, 1720, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_OPENVPN, 0 /* can_have_a_subprotocol */, + no_master, no_master, "OpenVPN", NDPI_PROTOCOL_CATEGORY_VPN, + ndpi_build_default_ports(ports_a, 1194, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 1194, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NOE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "NOE", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CISCOVPN, 0 /* can_have_a_subprotocol */, + no_master, no_master, "CiscoVPN", NDPI_PROTOCOL_CATEGORY_VPN, + ndpi_build_default_ports(ports_a, 10000, 8008, 8009, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 10000, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TEAMSPEAK, 0 /* can_have_a_subprotocol */, + no_master, no_master, "TeamSpeak", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_TOR, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Tor", NDPI_PROTOCOL_CATEGORY_VPN, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SKINNY, 0 /* can_have_a_subprotocol */, + no_master, no_master, "CiscoSkinny", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 2000, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RTCP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "RTCP", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RSYNC, 0 /* can_have_a_subprotocol */, + no_master, no_master, "RSYNC", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, + ndpi_build_default_ports(ports_a, 873, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ORACLE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Oracle", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 1521, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CORBA, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Corba", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_UBUNTUONE, 0 /* can_have_a_subprotocol */, + no_master, no_master, "UbuntuONE", NDPI_PROTOCOL_CATEGORY_CLOUD, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHOIS_DAS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Whois-DAS", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 43, 4343, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_COLLECTD, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Collectd", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 25826, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SOCKS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SOCKS", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 1080, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 1080, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TFTP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "TFTP", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 69, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RTMP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "RTMP", NDPI_PROTOCOL_CATEGORY_MEDIA, + ndpi_build_default_ports(ports_a, 1935, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FREE_183, 0 /* can_have_a_subprotocol */, no_master, + no_master, "FREE183", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MEGACO, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Megaco", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 2944, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_REDIS, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Redis", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 6379, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ZMQ, 0 /* can_have_a_subprotocol */, + no_master, no_master, "ZeroMQ", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_VHUA, 0 /* can_have_a_subprotocol */, no_master, + no_master, "VHUA", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 58267, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_STARCRAFT, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Starcraft", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 1119, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 1119, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_UBNTAC2, 0 /* can_have_a_subprotocol */, + no_master, no_master, "UBNTAC2", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 10001, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_VIBER, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Viber", NDPI_PROTOCOL_CATEGORY_VOIP, + ndpi_build_default_ports(ports_a, 7985, 5242, 5243, 4244, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 7985, 7987, 5242, 5243, 4244)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_COAP, 0 /* can_have_a_subprotocol */, no_master, + no_master, "COAP", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 5683, 5684, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MQTT, 0 /* can_have_a_subprotocol */, + no_master, no_master, "MQTT", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 1883, 8883, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SOMEIP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SOMEIP", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 30491, 30501, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 30491, 30501, 30490, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RX, 0 /* can_have_a_subprotocol */, + no_master, no_master, "RX", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_GIT, 0 /* can_have_a_subprotocol */, no_master, + no_master, "Git", NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, + ndpi_build_default_ports(ports_a, 9418, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DRDA, 0 /* can_have_a_subprotocol */, + no_master, no_master, "DRDA", NDPI_PROTOCOL_CATEGORY_DATABASE, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HANGOUT_DUO, + 0 /* can_have_a_subprotocol */, no_master, no_master, "GoogleHangoutDuo", + NDPI_PROTOCOL_CATEGORY_VOIP, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_BJNP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "BJNP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 8612, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SMPP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "SMPP", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_OOKLA, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Ookla", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AMQP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "AMQP", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_DNSCRYPT, 0 /* can_have_a_subprotocol */, + no_master, no_master, "DNScrypt", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TINC, 0 /* can_have_a_subprotocol */, + no_master, no_master, "TINC", NDPI_PROTOCOL_CATEGORY_VPN, + ndpi_build_default_ports(ports_a, 655, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 655, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_FIX, 0 /* can_have_a_subprotocol */, no_master, + no_master, "FIX", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_NINTENDO, 0 /* can_have_a_subprotocol */, + no_master, no_master, "Nintendo", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_CSGO, 0 /* can_have_a_subprotocol */, no_master, + no_master, "CSGO", NDPI_PROTOCOL_CATEGORY_GAME, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AJP, 0 /* can_have_a_subprotocol */, + no_master, no_master, "AJP", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 8009, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TARGUS_GETDATA, + 0 /* can_have_a_subprotocol */, no_master, no_master, "Targus Dataspeed", + NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 5001, 5201, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5001, 5201, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AMAZON_VIDEO, + 0 /* can_have_a_subprotocol */, no_master, no_master, "AmazonVideo", + NDPI_PROTOCOL_CATEGORY_CLOUD, ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DNP3, 1 /* no subprotocol */, no_master, + no_master, "DNP3", NDPI_PROTOCOL_CATEGORY_IOT_SCADA, + ndpi_build_default_ports(ports_a, 20000, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IEC60870, 1 /* no subprotocol */, + no_master, no_master, "IEC60870", + NDPI_PROTOCOL_CATEGORY_IOT_SCADA, + ndpi_build_default_ports(ports_a, 2404, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_BLOOMBERG, 1 /* no subprotocol */, + no_master, no_master, "Bloomberg", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CAPWAP, 1 /* no subprotocol */, no_master, + no_master, "CAPWAP", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 5246, 5247, 0, 0, 0) /* UDP */ + ); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ZABBIX, 1 /* no subprotocol */, no_master, + no_master, "Zabbix", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 10050, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */ + ); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_S7COMM, 1 /* no subprotocol */, no_master, + no_master, "s7comm", NDPI_PROTOCOL_CATEGORY_NETWORK, + ndpi_build_default_ports(ports_a, 102, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_MSTEAMS, 1 /* no subprotocol */, no_master, + no_master, "Teams", NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */ + ); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WEBSOCKET, + 1 /* can_have_a_subprotocol */, no_master, + no_master, "WebSocket", NDPI_PROTOCOL_CATEGORY_WEB, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ANYDESK, + 1 /* no subprotocol */, no_master, + no_master, "AnyDesk", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SOAP, 1 /* no subprotocol */, + no_master, no_master, "SOAP", NDPI_PROTOCOL_CATEGORY_RPC, + ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, + ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + +#ifdef CUSTOM_NDPI_PROTOCOLS +#include "../../../nDPI-custom/custom_ndpi_main.c" +#endif - custom_master[0] = NDPI_PROTOCOL_SIP, custom_master[1] = NDPI_PROTOCOL_H323; - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_STUN, - 0 /* can_have_a_subprotocol */, no_master, - custom_master, "STUN", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 3478, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_IP_IPSEC, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IPsec", NDPI_PROTOCOL_CATEGORY_VPN, - ndpi_build_default_ports(ports_a, 500, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 500, 4500, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_GRE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "GRE", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_ICMP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "ICMP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_IGMP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IGMP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_EGP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "EGP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_SCTP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SCTP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_OSPF, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "OSPF", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 2604, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_IP_IN_IP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IP_in_IP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RTP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RTP", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RDP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RDP", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 3389, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_VNC, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "VNC", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 5900, 5901, 5800, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_PCANYWHERE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "PcAnywhere", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ZOOM, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Zoom", NDPI_PROTOCOL_CATEGORY_VIDEO, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHATSAPP_FILES, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "WhatsAppFiles", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHATSAPP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "WhatsApp", NDPI_PROTOCOL_CATEGORY_CHAT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + /* calling function for host and content matched protocols */ + init_string_based_protocols(ndpi_str); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_TLS, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "TLS", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 443, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SSH, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SSH", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 22, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_USENET, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Usenet", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MGCP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "MGCP", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IAX, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "IAX", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 4569, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 4569, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AFP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "AFP", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, - ndpi_build_default_ports(ports_a, 548, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 548, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_HULU, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Hulu", NDPI_PROTOCOL_CATEGORY_STREAMING, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CHECKMK, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "CHECKMK", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, - ndpi_build_default_ports(ports_a, 6556, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_STEALTHNET, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Stealthnet", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_AIMINI, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Aimini", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SIP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SIP", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 5060, 5061, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 5060, 5061, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TRUPHONE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "TruPhone", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_IP_ICMPV6, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "ICMPV6", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DHCPV6, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DHCPV6", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_ARMAGETRON, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Armagetron", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_CROSSFIRE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Crossfire", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_DOFUS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Dofus", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FIESTA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Fiesta", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_FLORENSIA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Florensia", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_GUILDWARS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Guildwars", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_ACTIVESYNC, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "HTTP_ActiveSync", NDPI_PROTOCOL_CATEGORY_CLOUD, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_KERBEROS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Kerberos", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 88, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 88, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LDAP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "LDAP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 389, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 389, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_MAPLESTORY, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "MapleStory", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MSSQL_TDS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "MsSQL-TDS", NDPI_PROTOCOL_CATEGORY_DATABASE, - ndpi_build_default_ports(ports_a, 1433, 1434, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_PPTP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "PPTP", NDPI_PROTOCOL_CATEGORY_VPN, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WARCRAFT3, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Warcraft3", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_WORLD_OF_KUNG_FU, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "WorldOfKungFu", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DCERPC, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DCE_RPC", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 135, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NETFLOW, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "NetFlow", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 2055, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SFLOW, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "sFlow", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 6343, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_CONNECT, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "HTTP_Connect", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HTTP_PROXY, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "HTTP_Proxy", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 8080, 3128, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CITRIX, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Citrix", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 1494, 2598, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WEBEX, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Webex", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RADIUS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Radius", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 1812, 1813, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 1812, 1813, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TEAMVIEWER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "TeamViewer", NDPI_PROTOCOL_CATEGORY_REMOTE_ACCESS, - ndpi_build_default_ports(ports_a, 5938, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 5938, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LOTUS_NOTES, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "LotusNotes", NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, - ndpi_build_default_ports(ports_a, 1352, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SAP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SAP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 3201, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); /* Missing dissector: port based only */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_GTP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "GTP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 2152, 2123, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_UPNP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "UPnP", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 1780, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 1900, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TELEGRAM, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Telegram", NDPI_PROTOCOL_CATEGORY_CHAT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_QUIC, - 1 /* can_have_a_subprotocol */, no_master, - no_master, "QUIC", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 443, 80, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DIAMETER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Diameter", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 3868, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_APPLE_PUSH, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "ApplePush", NDPI_PROTOCOL_CATEGORY_CLOUD, - ndpi_build_default_ports(ports_a, 1, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DROPBOX, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Dropbox", NDPI_PROTOCOL_CATEGORY_CLOUD, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 17500, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SPOTIFY, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Spotify", NDPI_PROTOCOL_CATEGORY_MUSIC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MESSENGER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Messenger", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LISP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "LISP", NDPI_PROTOCOL_CATEGORY_CLOUD, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 4342, 4341, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_EAQ, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "EAQ", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 6000, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_KAKAOTALK_VOICE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "KakaoTalk_Voice", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_MPEGTS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "MPEG_TS", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - /* http://en.wikipedia.org/wiki/Link-local_Multicast_Name_Resolution */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_LLMNR, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "LLMNR", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 5355, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 5355, 0, 0, 0, 0) /* UDP */); /* Missing dissector: port based only */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_POTENTIALLY_DANGEROUS, NDPI_PROTOCOL_REMOTE_SCAN, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RemoteScan", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 6077, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 6078, 0, 0, 0, 0) /* UDP */); /* Missing dissector: port based only */ - - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_H323, - 0 /* can_have_a_subprotocol */, no_master, - no_master,"H323", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 1719, 1720, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 1719, 1720, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_OPENVPN, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "OpenVPN", NDPI_PROTOCOL_CATEGORY_VPN, - ndpi_build_default_ports(ports_a, 1194, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 1194, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_NOE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "NOE", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CISCOVPN, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "CiscoVPN", NDPI_PROTOCOL_CATEGORY_VPN, - ndpi_build_default_ports(ports_a, 10000, 8008, 8009, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 10000, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TEAMSPEAK, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "TeamSpeak", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SKINNY, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "CiscoSkinny", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 2000, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RTCP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RTCP", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RSYNC, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RSYNC", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, - ndpi_build_default_ports(ports_a, 873, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ORACLE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Oracle", NDPI_PROTOCOL_CATEGORY_DATABASE, - ndpi_build_default_ports(ports_a, 1521, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_CORBA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Corba", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_UBUNTUONE, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "UbuntuONE", NDPI_PROTOCOL_CATEGORY_CLOUD, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_WHOIS_DAS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Whois-DAS", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 43, 4343, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_COLLECTD, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Collectd", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 25826, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SOCKS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SOCKS", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 1080, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 1080, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TFTP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "TFTP", NDPI_PROTOCOL_CATEGORY_DATA_TRANSFER, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 69, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RTMP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RTMP", NDPI_PROTOCOL_CATEGORY_MEDIA, - ndpi_build_default_ports(ports_a, 1935, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_PANDO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Pando_Media_Booster", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MEGACO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Megaco", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 2944 , 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_REDIS, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Redis", NDPI_PROTOCOL_CATEGORY_DATABASE, - ndpi_build_default_ports(ports_a, 6379, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0 , 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_ZMQ, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "ZeroMQ", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0 , 0, 0, 0, 0) ); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_VHUA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "VHUA", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 58267, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_STARCRAFT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Starcraft", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 1119, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 1119, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_UBNTAC2, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "UBNTAC2", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 10001, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_VIBER, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Viber", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 7985, 5242, 5243, 4244, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 7985, 7987, 5242, 5243, 4244)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_COAP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "COAP", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 5683, 5684, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_MQTT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "MQTT", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 1883, 8883, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SOMEIP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SOMEIP", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 30491, 30501, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 30491, 30501, 30490, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_RX, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "RX", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_GIT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Git", NDPI_PROTOCOL_CATEGORY_COLLABORATIVE, - ndpi_build_default_ports(ports_a, 9418, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DRDA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DRDA", NDPI_PROTOCOL_CATEGORY_DATABASE, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_HANGOUT_DUO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "GoogleHangoutDuo", NDPI_PROTOCOL_CATEGORY_VOIP, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_BJNP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "BJNP", NDPI_PROTOCOL_CATEGORY_SYSTEM_OS, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 8612, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_SMPP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "SMPP", NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_OOKLA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Ookla", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AMQP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "AMQP", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_DNSCRYPT, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "DNScrypt", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0), /* TCP */ - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0)); /* UDP */ - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TINC, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "TINC", NDPI_PROTOCOL_CATEGORY_VPN, - ndpi_build_default_ports(ports_a, 655, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 655, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_SAFE, NDPI_PROTOCOL_FIX, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "FIX", NDPI_PROTOCOL_CATEGORY_RPC, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_NINTENDO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Nintendo", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_FUN, NDPI_PROTOCOL_CSGO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "CSGO", NDPI_PROTOCOL_CATEGORY_GAME, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AJP, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "AJP", NDPI_PROTOCOL_CATEGORY_WEB, - ndpi_build_default_ports(ports_a, 8009, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_TARGUS_GETDATA, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "Targus Dataspeed", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 5001, 5201, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 5001, 5201, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_AMAZON_VIDEO, - 0 /* can_have_a_subprotocol */, no_master, - no_master, "AmazonVideo", NDPI_PROTOCOL_CATEGORY_CLOUD, - ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_DNP3, - 1 /* no subprotocol */, no_master, - no_master, "DNP3", NDPI_PROTOCOL_CATEGORY_NETWORK, - ndpi_build_default_ports(ports_a, 20000, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, NDPI_PROTOCOL_104, - 1 /* no subprotocol */, no_master, - no_master, "104", NDPI_PROTOCOL_CATEGORY_NETWORK, /* Perhaps IoT in the future */ - ndpi_build_default_ports(ports_a, 2404, 0, 0, 0, 0) /* TCP */, - ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); + ndpi_validate_protocol_initialization(ndpi_str); +} - /* calling function for host and content matched protocols */ - init_string_based_protocols(ndpi_str); +/* ****************************************************** */ - ndpi_validate_protocol_initialization(ndpi_str); -} +#ifdef CUSTOM_NDPI_PROTOCOLS +#include "../../../nDPI-custom/custom_ndpi_protocols.c" +#endif /* ****************************************************** */ static int ac_match_handler(AC_MATCH_t *m, AC_TEXT_t *txt, AC_REP_t *match) { int min_len = (txt->length < m->patterns->length) ? txt->length : m->patterns->length; - char buf[64] = { '\0' }; + char buf[64] = {'\0'}, *whatfound; int min_buf_len = (txt->length > 63 /* sizeof(buf)-1 */) ? 63 : txt->length; u_int buf_len = strlen(buf); @@ -1775,31 +1535,30 @@ static int ac_match_handler(AC_MATCH_t *m, AC_TEXT_t *txt, AC_REP_t *match) { buf[min_buf_len] = '\0'; #ifdef MATCH_DEBUG - printf("Searching [to search: %s/%u][pattern: %s/%u] [len: %d][match_num: %u][%s]\n", - buf, (unigned int)txt->length, m->patterns->astring, m->patterns->length, min_len, - m->match_num, m->patterns->astring); + printf("Searching [to search: %s/%u][pattern: %s/%u] [len: %d][match_num: %u][%s]\n", buf, + (unigned int) txt->length, m->patterns->astring, (unigned int) m->patterns->length, min_len, m->match_num, + m->patterns->astring); #endif - { - char *whatfound = strstr(buf, m->patterns->astring); + whatfound = strstr(buf, m->patterns->astring); #ifdef MATCH_DEBUG - printf("[NDPI] %s() [searching=%s][pattern=%s][%s][%c]\n", - __FUNCTION__, buf, m->patterns->astring, - whatfound ? whatfound : "", - whatfound[-1]); + printf("[NDPI] %s() [searching=%s][pattern=%s][%s][%c]\n", __FUNCTION__, buf, m->patterns->astring, + whatfound ? whatfound : "", whatfound[-1]); #endif + if(whatfound) { /* The patch below allows in case of pattern ws.amazon.com to avoid matching aws.amazon.com whereas a.ws.amazon.com has to match */ - if(whatfound - && (whatfound != buf) - && (m->patterns->astring[0] != '.') /* The searched pattern does not start with . */ + if((whatfound != buf) && (m->patterns->astring[0] != '.') /* The searched pattern does not start with . */ && strchr(m->patterns->astring, '.') /* The matched pattern has a . (e.g. numeric or sym IPs) */) { - if(whatfound[-1] != '.') { + int len = strlen(m->patterns->astring); + + if((whatfound[-1] != '.') || ((m->patterns->astring[len - 1] != '.') && + (whatfound[len] != '\0') /* endsWith does not hold here */)) { return(0); } else { memcpy(match, &m->patterns[0].rep, sizeof(AC_REP_t)); /* Partial match? */ @@ -1815,8 +1574,8 @@ static int ac_match_handler(AC_MATCH_t *m, AC_TEXT_t *txt, AC_REP_t *match) { */ memcpy(match, &m->patterns[0].rep, sizeof(AC_REP_t)); - if(((buf_len >= min_len) && (strncmp(&buf[buf_len-min_len], m->patterns->astring, min_len) == 0)) - || (strncmp(buf, m->patterns->astring, min_len) == 0) /* begins with */ + if(((buf_len >= min_len) && (strncmp(&buf[buf_len - min_len], m->patterns->astring, min_len) == 0)) || + (strncmp(buf, m->patterns->astring, min_len) == 0) /* begins with */ ) { #ifdef MATCH_DEBUG printf("Found match [%s][%s] [len: %d]" @@ -1835,33 +1594,68 @@ static int ac_match_handler(AC_MATCH_t *m, AC_TEXT_t *txt, AC_REP_t *match) { /* ******************************************************************** */ -static int fill_prefix_v4(prefix_t *p, struct in_addr *a, int b, int mb) { - do { - if(b < 0 || b > mb) - return(-1); +static int fill_prefix_v4(prefix_t *p, const struct in_addr *a, int b, int mb) { + if(b < 0 || b > mb) + return(-1); - memset(p, 0, sizeof(prefix_t)); - memcpy(&p->add.sin, a, (mb+7)/8); - p->family = AF_INET; - p->bitlen = b; - p->ref_count = 0; - } while(0); + memset(p, 0, sizeof(prefix_t)); + memcpy(&p->add.sin, a, (mb + 7) / 8); + p->family = AF_INET; + p->bitlen = b; + p->ref_count = 0; return(0); } /* ******************************************* */ +static int fill_prefix_v6(prefix_t *prefix, const struct in6_addr *addr, int bits, int maxbits) { +#ifdef PATRICIA_IPV6 + if(bits < 0 || bits > maxbits) + return -1; + + memcpy(&prefix->add.sin6, addr, (maxbits + 7) / 8); + prefix->family = AF_INET6, prefix->bitlen = bits, prefix->ref_count = 0; + + return 0; +#else + return(-1); +#endif +} + +/* ******************************************* */ + u_int16_t ndpi_network_ptree_match(struct ndpi_detection_module_struct *ndpi_str, - struct in_addr *pin /* network byte order */) { + struct in_addr *pin /* network byte order */) { + prefix_t prefix; + patricia_node_t *node; + + /* Make sure all in network byte order otherwise compares wont work */ + fill_prefix_v4(&prefix, pin, 32, ((patricia_tree_t *) ndpi_str->protocols_ptree)->maxbits); + node = ndpi_patricia_search_best(ndpi_str->protocols_ptree, &prefix); + + return(node ? node->value.uv.user_value : NDPI_PROTOCOL_UNKNOWN); +} + +/* ******************************************* */ + +u_int16_t ndpi_network_port_ptree_match(struct ndpi_detection_module_struct *ndpi_str, + struct in_addr *pin /* network byte order */, + u_int16_t port /* network byte order */) { prefix_t prefix; patricia_node_t *node; /* Make sure all in network byte order otherwise compares wont work */ - fill_prefix_v4(&prefix, pin, 32, ((patricia_tree_t*)ndpi_str->protocols_ptree)->maxbits); + fill_prefix_v4(&prefix, pin, 32, ((patricia_tree_t *) ndpi_str->protocols_ptree)->maxbits); node = ndpi_patricia_search_best(ndpi_str->protocols_ptree, &prefix); - return(node ? node->value.user_value : NDPI_PROTOCOL_UNKNOWN); + if(node) { + if((node->value.uv.additional_user_value == 0) + || (node->value.uv.additional_user_value == port)) + return(node->value.uv.user_value); + } + + return(NDPI_PROTOCOL_UNKNOWN); } /* ******************************************* */ @@ -1874,8 +1668,7 @@ static u_int8_t tor_ptree_match(struct ndpi_detection_module_struct *ndpi_str, s /* ******************************************* */ -u_int8_t ndpi_is_tor_flow(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow) { +u_int8_t ndpi_is_tor_flow(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; if(packet->tcp != NULL) { @@ -1890,84 +1683,165 @@ u_int8_t ndpi_is_tor_flow(struct ndpi_detection_module_struct *ndpi_str, /* ******************************************* */ -static patricia_node_t* add_to_ptree(patricia_tree_t *tree, int family, - void *addr, int bits) { +static patricia_node_t *add_to_ptree(patricia_tree_t *tree, int family, void *addr, int bits) { prefix_t prefix; patricia_node_t *node; - fill_prefix_v4(&prefix, (struct in_addr*)addr, bits, tree->maxbits); + fill_prefix_v4(&prefix, (struct in_addr *) addr, bits, tree->maxbits); node = ndpi_patricia_lookup(tree, &prefix); + if(node) memset(&node->value, 0, sizeof(node->value)); return(node); } /* ******************************************* */ +/* + Load a file containing IPv4 addresses in CIDR format as 'protocol_id' + + Return: the number of entries loaded or -1 in case of error +*/ +int ndpi_load_ipv4_ptree(struct ndpi_detection_module_struct *ndpi_str, + const char *path, u_int16_t protocol_id) { + char buffer[128], *line, *addr, *cidr, *saveptr; + FILE *fd; + int len; + u_int num_loaded = 0; + + fd = fopen(path, "r"); + + if(fd == NULL) { + NDPI_LOG_ERR(ndpi_str, "Unable to open file %s [%s]\n", path, strerror(errno)); + return(-1); + } + + while (1) { + line = fgets(buffer, sizeof(buffer), fd); + + if(line == NULL) + break; + + len = strlen(line); + + if((len <= 1) || (line[0] == '#')) + continue; + + line[len - 1] = '\0'; + addr = strtok_r(line, "/", &saveptr); + + if(addr) { + struct in_addr pin; + patricia_node_t *node; + + cidr = strtok_r(NULL, "\n", &saveptr); + + pin.s_addr = inet_addr(addr); + if((node = add_to_ptree(ndpi_str->protocols_ptree, AF_INET, &pin, cidr ? atoi(cidr) : 32 /* bits */)) != NULL) { + node->value.uv.user_value = protocol_id, node->value.uv.additional_user_value = 0 /* port */; + num_loaded++; + } + } + } + + fclose(fd); + return(num_loaded); +} + +/* ******************************************* */ + static void ndpi_init_ptree_ipv4(struct ndpi_detection_module_struct *ndpi_str, - void *ptree, ndpi_network host_list[]) { + void *ptree, ndpi_network host_list[], + u_int8_t skip_tor_hosts) { int i; - for(i=0; host_list[i].network != 0x0; i++) { + for (i = 0; host_list[i].network != 0x0; i++) { struct in_addr pin; patricia_node_t *node; + if(skip_tor_hosts && (host_list[i].value == NDPI_PROTOCOL_TOR)) + continue; + pin.s_addr = htonl(host_list[i].network); - if((node = add_to_ptree(ptree, AF_INET, - &pin, host_list[i].cidr /* bits */)) != NULL) - node->value.user_value = host_list[i].value; + if((node = add_to_ptree(ptree, AF_INET, &pin, host_list[i].cidr /* bits */)) != NULL) { + node->value.uv.user_value = host_list[i].value, node->value.uv.additional_user_value = 0; + } } } /* ******************************************* */ static int ndpi_add_host_ip_subprotocol(struct ndpi_detection_module_struct *ndpi_str, - char *value, int protocol_id) { - + char *value, u_int16_t protocol_id) { patricia_node_t *node; struct in_addr pin; int bits = 32; char *ptr = strrchr(value, '/'); + u_int16_t port = 0; /* Format ip:8.248.73.247:443 */ + char *double_column; if(ptr) { ptr[0] = '\0'; ptr++; - if(atoi(ptr)>=0 && atoi(ptr)<=32) + + if((double_column = strrchr(ptr, ':')) != NULL) { + double_column[0] = '\0'; + port = atoi(&double_column[1]); + } + + if(atoi(ptr) >= 0 && atoi(ptr) <= 32) bits = atoi(ptr); + } else { + /* + Let's check if there is the port defined + Example: ip:8.248.73.247:443@AmazonPrime + */ + double_column = strrchr(value, ':'); + + if(double_column) { + double_column[0] = '\0'; + port = atoi(&double_column[1]); + } } inet_pton(AF_INET, value, &pin); - if((node = add_to_ptree(ndpi_str->protocols_ptree, AF_INET, &pin, bits)) != NULL) - node->value.user_value = protocol_id; + if((node = add_to_ptree(ndpi_str->protocols_ptree, AF_INET, &pin, bits)) != NULL) { + node->value.uv.user_value = protocol_id, node->value.uv.additional_user_value = htons(port); + } return(0); } -void set_ndpi_malloc(void* (*__ndpi_malloc)(size_t size)) { _ndpi_malloc = __ndpi_malloc; } -void set_ndpi_flow_malloc(void* (*__ndpi_flow_malloc)(size_t size)) { _ndpi_flow_malloc = __ndpi_flow_malloc; } +void set_ndpi_malloc(void *(*__ndpi_malloc)(size_t size)) { + _ndpi_malloc = __ndpi_malloc; +} +void set_ndpi_flow_malloc(void *(*__ndpi_flow_malloc)(size_t size)) { + _ndpi_flow_malloc = __ndpi_flow_malloc; +} -void set_ndpi_free(void (*__ndpi_free)(void *ptr)) { _ndpi_free = __ndpi_free; } -void set_ndpi_flow_free(void (*__ndpi_flow_free)(void *ptr)) { _ndpi_flow_free = __ndpi_flow_free; } +void set_ndpi_free(void (*__ndpi_free)(void *ptr)) { + _ndpi_free = __ndpi_free; +} +void set_ndpi_flow_free(void (*__ndpi_flow_free)(void *ptr)) { + _ndpi_flow_free = __ndpi_flow_free; +} -void ndpi_debug_printf(unsigned int proto, struct ndpi_detection_module_struct *ndpi_str, - ndpi_log_level_t log_level, const char *file_name, const char *func_name, int line_number, - const char * format, ...) -{ +void ndpi_debug_printf(unsigned int proto, struct ndpi_detection_module_struct *ndpi_str, ndpi_log_level_t log_level, + const char *file_name, const char *func_name, int line_number, const char *format, ...) { #ifdef NDPI_ENABLE_DEBUG_MESSAGES va_list args; #define MAX_STR_LEN 250 char str[MAX_STR_LEN]; - if(ndpi_str != NULL && log_level > NDPI_LOG_ERROR && - proto > 0 && proto < NDPI_MAX_SUPPORTED_PROTOCOLS && - !NDPI_ISSET(&ndpi_str->debug_bitmask,proto)) return; + if(ndpi_str != NULL && log_level > NDPI_LOG_ERROR && proto > 0 && proto < NDPI_MAX_SUPPORTED_PROTOCOLS && + !NDPI_ISSET(&ndpi_str->debug_bitmask, proto)) + return; va_start(args, format); - vsnprintf(str,sizeof(str)-1, format, args); + vsnprintf(str, sizeof(str) - 1, format, args); va_end(args); if(ndpi_str != NULL) { - printf("%s:%s:%-3d - [%s]: %s", - file_name, func_name, line_number, ndpi_get_proto_name(ndpi_str, proto), str); + printf("%s:%s:%-3d - [%s]: %s", file_name, func_name, line_number, ndpi_get_proto_name(ndpi_str, proto), str); } else { printf("Proto: %u, %s", proto, str); } @@ -1983,7 +1857,7 @@ void set_ndpi_debug_function(struct ndpi_detection_module_struct *ndpi_str, ndpi /* ****************************************** */ /* Keep it in order and in sync with ndpi_protocol_category_t in ndpi_typedefs.h */ -static const char* categories[] = { +static const char *categories[] = { "Unspecified", "Media", "VPN", @@ -2014,8 +1888,8 @@ static const char* categories[] = { "Shopping", "Productivity", "FileSharing", - "", - "", + "ConnectivityCheck", + "IoT-Scada", "", "", "", @@ -2094,25 +1968,58 @@ static const char* categories[] = { /* ******************************************************************** */ -struct ndpi_detection_module_struct *ndpi_init_detection_module(void) { +#ifdef TEST_LRU_HANDLER +void test_lru_handler(ndpi_lru_cache_type cache_type, u_int32_t proto, u_int32_t app_proto) { + + printf("[test_lru_handler] %u / %u / %u\n", cache_type, proto, app_proto); +} +#endif + +/* ******************************************************************** */ + +struct ndpi_detection_module_struct *ndpi_init_detection_module(ndpi_init_prefs prefs) { struct ndpi_detection_module_struct *ndpi_str = ndpi_malloc(sizeof(struct ndpi_detection_module_struct)); int i; if(ndpi_str == NULL) { -#ifdef NDPI_ENABLE_DEBUG_MESSAGES - NDPI_LOG_ERR(ndpi_str, "ndpi_init_detection_module initial malloc failed for ndpi_str\n"); -#endif /* NDPI_ENABLE_DEBUG_MESSAGES */ + /* Logging this error is a bit tricky. At this point, we can't use NDPI_LOG* + functions yet, we don't have a custom log function and, as a library, + we shouldn't use stdout/stderr. Since this error is quite unlikely, + simply avoid any logs at all */ return(NULL); } memset(ndpi_str, 0, sizeof(struct ndpi_detection_module_struct)); +#ifdef TEST_LRU_HANDLER + ndpi_str->ndpi_notify_lru_add_handler_ptr = test_lru_handler; +#endif + #ifdef NDPI_ENABLE_DEBUG_MESSAGES - set_ndpi_debug_function(ndpi_str, (ndpi_debug_function_ptr)ndpi_debug_printf); + set_ndpi_debug_function(ndpi_str, (ndpi_debug_function_ptr) ndpi_debug_printf); + 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); + ndpi_init_ptree_ipv4(ndpi_str, ndpi_str->protocols_ptree, host_protocol_list, prefs & ndpi_dont_load_tor_hosts); NDPI_BITMASK_RESET(ndpi_str->detection_bitmask); #ifdef NDPI_ENABLE_DEBUG_MESSAGES @@ -2125,198 +2032,243 @@ struct ndpi_detection_module_struct *ndpi_init_detection_module(void) { NDPI_DIRECTCONNECT_CONNECTION_IP_TICK_TIMEOUT * ndpi_str->ticks_per_second; ndpi_str->rtsp_connection_timeout = NDPI_RTSP_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second; - ndpi_str->tvants_connection_timeout = NDPI_TVANTS_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second; ndpi_str->irc_timeout = NDPI_IRC_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second; ndpi_str->gnutella_timeout = NDPI_GNUTELLA_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second; - - ndpi_str->battlefield_timeout = NDPI_BATTLEFIELD_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second; - ndpi_str->thunder_timeout = NDPI_THUNDER_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second; - ndpi_str->yahoo_detect_http_connections = NDPI_YAHOO_DETECT_HTTP_CONNECTIONS; - - ndpi_str->yahoo_lan_video_timeout = NDPI_YAHOO_LAN_VIDEO_TIMEOUT * ndpi_str->ticks_per_second; ndpi_str->zattoo_connection_timeout = NDPI_ZATTOO_CONNECTION_TIMEOUT * ndpi_str->ticks_per_second; ndpi_str->jabber_stun_timeout = NDPI_JABBER_STUN_TIMEOUT * ndpi_str->ticks_per_second; ndpi_str->jabber_file_transfer_timeout = NDPI_JABBER_FT_TIMEOUT * ndpi_str->ticks_per_second; - ndpi_str->soulseek_connection_ip_tick_timeout = NDPI_SOULSEEK_CONNECTION_IP_TICK_TIMEOUT * ndpi_str->ticks_per_second; + ndpi_str->soulseek_connection_ip_tick_timeout = + NDPI_SOULSEEK_CONNECTION_IP_TICK_TIMEOUT * ndpi_str->ticks_per_second; ndpi_str->ndpi_num_supported_protocols = NDPI_MAX_SUPPORTED_PROTOCOLS; ndpi_str->ndpi_num_custom_protocols = 0; - ndpi_str->host_automa.ac_automa = ac_automata_init(ac_match_handler); - ndpi_str->content_automa.ac_automa = ac_automata_init(ac_match_handler); - ndpi_str->bigrams_automa.ac_automa = ac_automata_init(ac_match_handler); + ndpi_str->host_automa.ac_automa = ac_automata_init(ac_match_handler); + ndpi_str->content_automa.ac_automa = ac_automata_init(ac_match_handler); + ndpi_str->bigrams_automa.ac_automa = ac_automata_init(ac_match_handler); ndpi_str->impossible_bigrams_automa.ac_automa = ac_automata_init(ac_match_handler); - if((sizeof(categories)/sizeof(char*)) != NDPI_PROTOCOL_NUM_CATEGORIES) { - NDPI_LOG_ERR(ndpi_str, "[NDPI] invalid categories length: expected %u, got %u\n", - NDPI_PROTOCOL_NUM_CATEGORIES, (unsigned int)(sizeof(categories)/sizeof(char*))); + if((sizeof(categories) / sizeof(char *)) != NDPI_PROTOCOL_NUM_CATEGORIES) { + NDPI_LOG_ERR(ndpi_str, "[NDPI] invalid categories length: expected %u, got %u\n", NDPI_PROTOCOL_NUM_CATEGORIES, + (unsigned int) (sizeof(categories) / sizeof(char *))); return(NULL); } -#ifdef HAVE_HYPERSCAN - ndpi_str->custom_categories.num_to_load = 0, ndpi_str->custom_categories.to_load = NULL; - ndpi_str->custom_categories.hostnames = NULL; -#else - ndpi_str->custom_categories.hostnames.ac_automa = ac_automata_init(ac_match_handler); + ndpi_str->custom_categories.hostnames.ac_automa = ac_automata_init(ac_match_handler); ndpi_str->custom_categories.hostnames_shadow.ac_automa = ac_automata_init(ac_match_handler); -#endif - ndpi_str->custom_categories.ipAddresses = ndpi_New_Patricia(32 /* IPv4 */); - ndpi_str->custom_categories.ipAddresses_shadow = ndpi_New_Patricia(32 /* IPv4 */); + ndpi_str->custom_categories.ipAddresses = ndpi_New_Patricia(32 /* IPv4 */); + ndpi_str->custom_categories.ipAddresses_shadow = ndpi_New_Patricia(32 /* IPv4 */); - if((ndpi_str->custom_categories.ipAddresses == NULL) - || (ndpi_str->custom_categories.ipAddresses_shadow == NULL)) + if((ndpi_str->custom_categories.ipAddresses == NULL) || (ndpi_str->custom_categories.ipAddresses_shadow == NULL)) { + NDPI_LOG_ERR(ndpi_str, "[NDPI] Error allocating Patricia trees\n"); return(NULL); + } ndpi_init_protocol_defaults(ndpi_str); - for(i=0; icustom_category_labels[i], - CUSTOM_CATEGORY_LABEL_LEN, "User custom category %u", (unsigned int)(i+1)); + for (i = 0; i < NUM_CUSTOM_CATEGORIES; i++) + snprintf(ndpi_str->custom_category_labels[i], CUSTOM_CATEGORY_LABEL_LEN, "User custom category %u", + (unsigned int) (i + 1)); return(ndpi_str); } /* *********************************************** */ +void ndpi_finalize_initalization(struct ndpi_detection_module_struct *ndpi_str) { + u_int i; + + for (i = 0; i < 4; i++) { + ndpi_automa *automa; + + switch(i) { + case 0: + automa = &ndpi_str->host_automa; + break; + + case 1: + automa = &ndpi_str->content_automa; + break; + + case 2: + automa = &ndpi_str->bigrams_automa; + break; + + case 3: + automa = &ndpi_str->impossible_bigrams_automa; + break; + + default: + automa = NULL; + break; + } + + if(automa) { + ac_automata_finalize((AC_AUTOMATA_t *) automa->ac_automa); + automa->ac_automa_finalized = 1; + } + } +} + +/* *********************************************** */ + /* Wrappers */ -void* ndpi_init_automa(void) { +void *ndpi_init_automa(void) { return(ac_automata_init(ac_match_handler)); } -int ndpi_add_string_value_to_automa(void *_automa, char *str, unsigned long num) { +/* ****************************************************** */ + +int ndpi_add_string_value_to_automa(void *_automa, char *str, u_int32_t num) { AC_PATTERN_t ac_pattern; - AC_AUTOMATA_t *automa = (AC_AUTOMATA_t*)_automa; + AC_AUTOMATA_t *automa = (AC_AUTOMATA_t *) _automa; + AC_ERROR_t rc; - if(automa == NULL) return(-1); + if(automa == NULL) + return(-1); memset(&ac_pattern, 0, sizeof(ac_pattern)); - ac_pattern.astring = str; + ac_pattern.astring = str; ac_pattern.rep.number = num; - ac_pattern.length = strlen(ac_pattern.astring); - return(ac_automata_add(automa, &ac_pattern) == ACERR_SUCCESS ? 0 : -1); + ac_pattern.length = strlen(ac_pattern.astring); + + rc = ac_automata_add(automa, &ac_pattern); + return(rc == ACERR_SUCCESS || rc == ACERR_DUPLICATE_PATTERN ? 0 : -1); } +/* ****************************************************** */ + int ndpi_add_string_to_automa(void *_automa, char *str) { return(ndpi_add_string_value_to_automa(_automa, str, 1)); } -void ndpi_free_automa(void *_automa) { ac_automata_release((AC_AUTOMATA_t*)_automa, 0); } -void ndpi_finalize_automa(void *_automa) { ac_automata_finalize((AC_AUTOMATA_t*)_automa); } +/* ****************************************************** */ + +void ndpi_free_automa(void *_automa) { + ac_automata_release((AC_AUTOMATA_t *) _automa, 0); +} + +/* ****************************************************** */ + +void ndpi_finalize_automa(void *_automa) { + ac_automata_finalize((AC_AUTOMATA_t *) _automa); +} /* ****************************************************** */ int ndpi_match_string(void *_automa, char *string_to_match) { AC_REP_t match = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED }; AC_TEXT_t ac_input_text; - AC_AUTOMATA_t *automa = (AC_AUTOMATA_t*)_automa; + AC_AUTOMATA_t *automa = (AC_AUTOMATA_t *) _automa; int rc; - if((automa == NULL) - || (string_to_match == NULL) - || (string_to_match[0] == '\0')) + if((automa == NULL) || (string_to_match == NULL) || (string_to_match[0] == '\0')) return(-2); ac_input_text.astring = string_to_match, ac_input_text.length = strlen(string_to_match); rc = ac_automata_search(automa, &ac_input_text, &match); - ac_automata_reset(automa); /* As ac_automata_search can detect partial matches and continue the search process in case rc == 0 (i.e. no match), we need to check if there is a partial match and in this case return it */ - if((rc == 0) && (match.number != 0)) rc = 1; + if((rc == 0) && (match.number != 0)) + rc = 1; return(rc ? match.number : 0); } /* ****************************************************** */ -int ndpi_match_string_id(void *_automa, char *string_to_match, u_int match_len, unsigned long *id) { +int ndpi_match_string_protocol_id(void *_automa, char *string_to_match, + u_int match_len, u_int16_t *protocol_id, + ndpi_protocol_category_t *category, + ndpi_protocol_breed_t *breed) { AC_TEXT_t ac_input_text; - AC_AUTOMATA_t *automa = (AC_AUTOMATA_t*)_automa; - AC_REP_t match = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED }; + AC_AUTOMATA_t *automa = (AC_AUTOMATA_t *) _automa; + AC_REP_t match = { 0, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED }; int rc; - *id = -1; - if((automa == NULL) - || (string_to_match == NULL) - || (string_to_match[0] == '\0')) + *protocol_id = (u_int16_t)-1; + if((automa == NULL) || (string_to_match == NULL) || (string_to_match[0] == '\0')) return(-2); ac_input_text.astring = string_to_match, ac_input_text.length = match_len; rc = ac_automata_search(automa, &ac_input_text, &match); - ac_automata_reset(automa); /* As ac_automata_search can detect partial matches and continue the search process in case rc == 0 (i.e. no match), we need to check if there is a partial match and in this case return it */ - if((rc == 0) && (match.number != 0)) rc = 1; + if((rc == 0) && (match.number != 0)) + rc = 1; - *id = rc ? match.number : NDPI_PROTOCOL_UNKNOWN; + if(rc) + *protocol_id = (u_int16_t)match.number, *category = match.category, + *breed = match.breed; + else + *protocol_id = NDPI_PROTOCOL_UNKNOWN; - return(*id != NDPI_PROTOCOL_UNKNOWN ? 0 : -1); + return((*protocol_id != NDPI_PROTOCOL_UNKNOWN) ? 0 : -1); } -/* *********************************************** */ +/* ****************************************************** */ -#ifdef HAVE_HYPERSCAN +int ndpi_match_string_value(void *_automa, char *string_to_match, + u_int match_len, u_int32_t *num) { + AC_TEXT_t ac_input_text; + AC_AUTOMATA_t *automa = (AC_AUTOMATA_t *) _automa; + AC_REP_t match = { 0, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED }; + int rc; -static int hyperscanCustomEventHandler(unsigned int id, - unsigned long long from, - unsigned long long to, - unsigned int flags, void *ctx) { - *((unsigned long *)ctx) = (unsigned long)id; + *num = (u_int32_t)-1; + if((automa == NULL) || (string_to_match == NULL) || (string_to_match[0] == '\0')) + return(-2); -#ifdef DEBUG - printf("[HS] Found category %u\n", id); -#endif + ac_input_text.astring = string_to_match, ac_input_text.length = match_len; + rc = ac_automata_search(automa, &ac_input_text, &match); + + /* + As ac_automata_search can detect partial matches and continue the search process + in case rc == 0 (i.e. no match), we need to check if there is a partial match + and in this case return it + */ + if((rc == 0) && (match.number != 0)) + rc = 1; + + if(rc) + *num = match.number; + else + *num = 0; - return(HS_SCAN_TERMINATED); + return(rc ? 0 : -1); } -#endif /* *********************************************** */ int ndpi_match_custom_category(struct ndpi_detection_module_struct *ndpi_str, - char *name, u_int name_len, unsigned long *id) { -#ifdef HAVE_HYPERSCAN - if(ndpi_str->custom_categories.hostnames == NULL) - return(-1); - else { - hs_error_t rc; - - *id = (unsigned long)-1; - - rc = hs_scan(ndpi_str->custom_categories.hostnames->database, - name, name_len, 0, - ndpi_str->custom_categories.hostnames->scratch, - hyperscanCustomEventHandler, id); + char *name, u_int name_len, + ndpi_protocol_category_t *category) { + ndpi_protocol_breed_t breed; + u_int16_t id; + int rc = ndpi_match_string_protocol_id(ndpi_str->custom_categories.hostnames.ac_automa, + name, name_len, &id, category, &breed); - if(rc == HS_SCAN_TERMINATED) { -#ifdef DEBUG - printf("[HS] Found category %lu for %s\n", *id, name); -#endif - return(0); - } else - return(-1); - } -#else - return(ndpi_match_string_id(ndpi_str->custom_categories.hostnames.ac_automa, name, name_len, id)); -#endif + return(rc); } /* *********************************************** */ int ndpi_get_custom_category_match(struct ndpi_detection_module_struct *ndpi_str, - char *name_or_ip, u_int name_len, unsigned long *id) { + char *name_or_ip, u_int name_len, + ndpi_protocol_category_t *id) { char ipbuf[64], *ptr; struct in_addr pin; - u_int cp_len = ndpi_min(sizeof(ipbuf)-1, name_len); + u_int cp_len = ndpi_min(sizeof(ipbuf) - 1, name_len); if(!ndpi_str->custom_categories.categories_loaded) return(-1); @@ -2338,23 +2290,27 @@ int ndpi_get_custom_category_match(struct ndpi_detection_module_struct *ndpi_str patricia_node_t *node; /* Make sure all in network byte order otherwise compares wont work */ - fill_prefix_v4(&prefix, &pin, 32, ((patricia_tree_t*)ndpi_str->protocols_ptree)->maxbits); + fill_prefix_v4(&prefix, &pin, 32, ((patricia_tree_t *) ndpi_str->protocols_ptree)->maxbits); node = ndpi_patricia_search_best(ndpi_str->custom_categories.ipAddresses, &prefix); if(node) { - *id = node->value.user_value; + *id = node->value.uv.user_value; + return(0); } return(-1); - } else + } else { /* Search Host */ return(ndpi_match_custom_category(ndpi_str, name_or_ip, name_len, id)); + } } /* *********************************************** */ -static void free_ptree_data(void *data) { ; } +static void free_ptree_data(void *data) { + ; +} /* ****************************************************** */ @@ -2362,7 +2318,7 @@ void ndpi_exit_detection_module(struct ndpi_detection_module_struct *ndpi_str) { if(ndpi_str != NULL) { int i; - for(i=0; i<(int)ndpi_str->ndpi_num_supported_protocols; i++) { + for (i = 0; i < (NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS); i++) { if(ndpi_str->proto_defaults[i].protoName) ndpi_free(ndpi_str->proto_defaults[i].protoName); } @@ -2377,8 +2333,11 @@ void ndpi_exit_detection_module(struct ndpi_detection_module_struct *ndpi_str) { if(ndpi_str->stun_cache) ndpi_lru_free_cache(ndpi_str->stun_cache); + if(ndpi_str->msteams_cache) + ndpi_lru_free_cache(ndpi_str->msteams_cache); + if(ndpi_str->protocols_ptree) - ndpi_Destroy_Patricia((patricia_tree_t*)ndpi_str->protocols_ptree, free_ptree_data); + ndpi_Destroy_Patricia((patricia_tree_t *) ndpi_str->protocols_ptree, free_ptree_data); if(ndpi_str->udpRoot != NULL) ndpi_tdestroy(ndpi_str->udpRoot, ndpi_free); @@ -2386,42 +2345,35 @@ void ndpi_exit_detection_module(struct ndpi_detection_module_struct *ndpi_str) { ndpi_tdestroy(ndpi_str->tcpRoot, ndpi_free); if(ndpi_str->host_automa.ac_automa != NULL) - ac_automata_release((AC_AUTOMATA_t*)ndpi_str->host_automa.ac_automa, 1 /* free patterns strings memory */); + ac_automata_release((AC_AUTOMATA_t *) ndpi_str->host_automa.ac_automa, + 1 /* free patterns strings memory */); if(ndpi_str->content_automa.ac_automa != NULL) - ac_automata_release((AC_AUTOMATA_t*)ndpi_str->content_automa.ac_automa, 0); + ac_automata_release((AC_AUTOMATA_t *) ndpi_str->content_automa.ac_automa, 0); if(ndpi_str->bigrams_automa.ac_automa != NULL) - ac_automata_release((AC_AUTOMATA_t*)ndpi_str->bigrams_automa.ac_automa, 0); + ac_automata_release((AC_AUTOMATA_t *) ndpi_str->bigrams_automa.ac_automa, 0); if(ndpi_str->impossible_bigrams_automa.ac_automa != NULL) - ac_automata_release((AC_AUTOMATA_t*)ndpi_str->impossible_bigrams_automa.ac_automa, 0); - -#ifdef HAVE_HYPERSCAN - destroy_hyperscan(ndpi_str); - - while(ndpi_str->custom_categories.to_load != NULL) { - struct hs_list *next = ndpi_str->custom_categories.to_load->next; + ac_automata_release((AC_AUTOMATA_t *) ndpi_str->impossible_bigrams_automa.ac_automa, 0); - ndpi_free(ndpi_str->custom_categories.to_load->expression); - ndpi_free(ndpi_str->custom_categories.to_load); - ndpi_str->custom_categories.to_load = next; - } - - free_hyperscan_memory(ndpi_str->custom_categories.hostnames); -#else if(ndpi_str->custom_categories.hostnames.ac_automa != NULL) - ac_automata_release((AC_AUTOMATA_t*)ndpi_str->custom_categories.hostnames.ac_automa, 1 /* free patterns strings memory */); + ac_automata_release((AC_AUTOMATA_t *) ndpi_str->custom_categories.hostnames.ac_automa, + 1 /* free patterns strings memory */); if(ndpi_str->custom_categories.hostnames_shadow.ac_automa != NULL) - ac_automata_release((AC_AUTOMATA_t*)ndpi_str->custom_categories.hostnames_shadow.ac_automa, 1 /* free patterns strings memory */); -#endif + ac_automata_release((AC_AUTOMATA_t *) ndpi_str->custom_categories.hostnames_shadow.ac_automa, + 1 /* free patterns strings memory */); if(ndpi_str->custom_categories.ipAddresses != NULL) - ndpi_Destroy_Patricia((patricia_tree_t*)ndpi_str->custom_categories.ipAddresses, free_ptree_data); + ndpi_Destroy_Patricia((patricia_tree_t *) ndpi_str->custom_categories.ipAddresses, free_ptree_data); if(ndpi_str->custom_categories.ipAddresses_shadow != NULL) - ndpi_Destroy_Patricia((patricia_tree_t*)ndpi_str->custom_categories.ipAddresses_shadow, free_ptree_data); + ndpi_Destroy_Patricia((patricia_tree_t *) ndpi_str->custom_categories.ipAddresses_shadow, free_ptree_data); + +#ifdef CUSTOM_NDPI_PROTOCOLS +#include "../../../nDPI-custom/ndpi_exit_detection_module.c" +#endif ndpi_free(ndpi_str); } @@ -2429,11 +2381,9 @@ void ndpi_exit_detection_module(struct ndpi_detection_module_struct *ndpi_str) { /* ****************************************************** */ -int ndpi_get_protocol_id_master_proto(struct ndpi_detection_module_struct *ndpi_str, - u_int16_t protocol_id, - u_int16_t** tcp_master_proto, - u_int16_t** udp_master_proto) { - if(protocol_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) { +int ndpi_get_protocol_id_master_proto(struct ndpi_detection_module_struct *ndpi_str, u_int16_t protocol_id, + u_int16_t **tcp_master_proto, u_int16_t **udp_master_proto) { + if(protocol_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) { *tcp_master_proto = ndpi_str->proto_defaults[NDPI_PROTOCOL_UNKNOWN].master_tcp_protoId, *udp_master_proto = ndpi_str->proto_defaults[NDPI_PROTOCOL_UNKNOWN].master_udp_protoId; return(-1); @@ -2447,28 +2397,27 @@ int ndpi_get_protocol_id_master_proto(struct ndpi_detection_module_struct *ndpi_ /* ****************************************************** */ -static ndpi_default_ports_tree_node_t* ndpi_get_guessed_protocol_id(struct ndpi_detection_module_struct *ndpi_str, - u_int8_t proto, u_int16_t sport, u_int16_t dport) { +static ndpi_default_ports_tree_node_t *ndpi_get_guessed_protocol_id(struct ndpi_detection_module_struct *ndpi_str, + u_int8_t proto, u_int16_t sport, u_int16_t dport) { ndpi_default_ports_tree_node_t node; if(sport && dport) { - int low = ndpi_min(sport, dport); + int low = ndpi_min(sport, dport); int high = ndpi_max(sport, dport); const void *ret; node.default_port = low; /* Check server port first */ - ret = ndpi_tfind(&node, - (proto == IPPROTO_TCP) ? (void*)&ndpi_str->tcpRoot : (void*)&ndpi_str->udpRoot, + ret = ndpi_tfind(&node, (proto == IPPROTO_TCP) ? (void *) &ndpi_str->tcpRoot : (void *) &ndpi_str->udpRoot, ndpi_default_ports_tree_node_t_cmp); if(ret == NULL) { node.default_port = high; - ret = ndpi_tfind(&node, - (proto == IPPROTO_TCP) ? (void*)&ndpi_str->tcpRoot : (void*)&ndpi_str->udpRoot, + ret = ndpi_tfind(&node, (proto == IPPROTO_TCP) ? (void *) &ndpi_str->tcpRoot : (void *) &ndpi_str->udpRoot, ndpi_default_ports_tree_node_t_cmp); } - if(ret) return(*(ndpi_default_ports_tree_node_t**)ret); + if(ret) + return(*(ndpi_default_ports_tree_node_t **) ret); } return(NULL); @@ -2495,10 +2444,8 @@ u_int8_t is_udp_guessable_protocol(u_int16_t l7_guessed_proto) { /* ****************************************************** */ -u_int16_t ndpi_guess_protocol_id(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int8_t proto, u_int16_t sport, u_int16_t dport, - u_int8_t *user_defined_proto) { +u_int16_t ndpi_guess_protocol_id(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int8_t proto, u_int16_t sport, u_int16_t dport, u_int8_t *user_defined_proto) { *user_defined_proto = 0; /* Default */ if(sport && dport) { @@ -2508,11 +2455,9 @@ u_int16_t ndpi_guess_protocol_id(struct ndpi_detection_module_struct *ndpi_str, u_int16_t guessed_proto = found->proto->protoId; /* We need to check if the guessed protocol isn't excluded by nDPI */ - if(flow - && (proto == IPPROTO_UDP) - && NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, guessed_proto) - && is_udp_guessable_protocol(guessed_proto) - ) + if(flow && (proto == IPPROTO_UDP) && + NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, guessed_proto) && + is_udp_guessable_protocol(guessed_proto)) return(NDPI_PROTOCOL_UNKNOWN); else { *user_defined_proto = found->customUserProto; @@ -2531,6 +2476,21 @@ u_int16_t ndpi_guess_protocol_id(struct ndpi_detection_module_struct *ndpi_str, return(NDPI_PROTOCOL_IP_GRE); break; case NDPI_ICMP_PROTOCOL_TYPE: + if(flow) { + /* Run some basic consistency tests */ + + if(flow->packet.payload_packet_len < sizeof(struct ndpi_icmphdr)) + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); + else { + u_int8_t icmp_type = (u_int8_t)flow->packet.payload[0]; + u_int8_t icmp_code = (u_int8_t)flow->packet.payload[1]; + + /* https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml */ + if(((icmp_type >= 44) && (icmp_type <= 252)) + || (icmp_code > 15)) + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); + } + } return(NDPI_PROTOCOL_IP_ICMP); break; case NDPI_IGMP_PROTOCOL_TYPE: @@ -2549,6 +2509,22 @@ u_int16_t ndpi_guess_protocol_id(struct ndpi_detection_module_struct *ndpi_str, return(NDPI_PROTOCOL_IP_IP_IN_IP); break; case NDPI_ICMPV6_PROTOCOL_TYPE: + if(flow) { + /* Run some basic consistency tests */ + + if(flow->packet.payload_packet_len < sizeof(struct ndpi_icmphdr)) + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); + else { + u_int8_t icmp6_type = (u_int8_t)flow->packet.payload[0]; + u_int8_t icmp6_code = (u_int8_t)flow->packet.payload[1]; + + /* https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol_for_IPv6 */ + if(((icmp6_type >= 5) && (icmp6_type <= 127)) + || (icmp6_type >= 156) + || ((icmp6_code > 7) && (icmp6_type != 255))) + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); + } + } return(NDPI_PROTOCOL_IP_ICMPV6); break; case 112: @@ -2569,13 +2545,14 @@ u_int ndpi_get_num_supported_protocols(struct ndpi_detection_module_struct *ndpi /* ******************************************************************** */ #ifdef WIN32 -char * strsep(char **sp, char *sep) -{ +char *strsep(char **sp, char *sep) { char *p, *s; - if(sp == NULL || *sp == NULL || **sp == '\0') return(NULL); + if(sp == NULL || *sp == NULL || **sp == '\0') + return(NULL); s = *sp; p = s + strcspn(s, sep); - if(*p != '\0') *p++ = '\0'; + if(*p != '\0') + *p++ = '\0'; *sp = p; return(s); } @@ -2583,11 +2560,10 @@ char * strsep(char **sp, char *sep) /* ******************************************************************** */ -int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, - char* rule, u_int8_t do_add) { +int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, char *rule, u_int8_t do_add) { char *at, *proto, *elem; ndpi_proto_defaults_t *def; - int subprotocol_id, i; + u_int16_t subprotocol_id, i; at = strrchr(rule, '@'); if(at == NULL) { @@ -2596,7 +2572,7 @@ int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, } else at[0] = 0, proto = &at[1]; - for(i=0; proto[i] != '\0'; i++) { + for (i = 0; proto[i] != '\0'; i++) { switch(proto[i]) { case '/': case '&': @@ -2611,7 +2587,7 @@ int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, } } - for(i=0, def = NULL; i<(int)ndpi_str->ndpi_num_supported_protocols; i++) { + for (i = 0, def = NULL; i < (int) ndpi_str->ndpi_num_supported_protocols; i++) { if(ndpi_str->proto_defaults[i].protoName && strcasecmp(ndpi_str->proto_defaults[i].protoName, proto) == 0) { def = &ndpi_str->proto_defaults[i]; subprotocol_id = i; @@ -2626,19 +2602,17 @@ int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, return(-3); } else { ndpi_port_range ports_a[MAX_DEFAULT_PORTS], ports_b[MAX_DEFAULT_PORTS]; - u_int16_t no_master[2] = { NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO }; + u_int16_t no_master[2] = {NDPI_PROTOCOL_NO_MASTER_PROTO, NDPI_PROTOCOL_NO_MASTER_PROTO}; - if(ndpi_str->ndpi_num_custom_protocols >= (NDPI_MAX_NUM_CUSTOM_PROTOCOLS-1)) { + if(ndpi_str->ndpi_num_custom_protocols >= (NDPI_MAX_NUM_CUSTOM_PROTOCOLS - 1)) { NDPI_LOG_ERR(ndpi_str, "Too many protocols defined (%u): skipping protocol %s\n", ndpi_str->ndpi_num_custom_protocols, proto); return(-2); } - ndpi_set_proto_defaults(ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, - ndpi_str->ndpi_num_supported_protocols, - 0 /* can_have_a_subprotocol */, no_master, - no_master, - proto, + ndpi_set_proto_defaults( + ndpi_str, NDPI_PROTOCOL_ACCEPTABLE, ndpi_str->ndpi_num_supported_protocols, + 0 /* can_have_a_subprotocol */, no_master, no_master, proto, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, /* TODO add protocol category support in rules */ ndpi_build_default_ports(ports_a, 0, 0, 0, 0, 0) /* TCP */, ndpi_build_default_ports(ports_b, 0, 0, 0, 0, 0) /* UDP */); @@ -2648,7 +2622,7 @@ int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, } } - while((elem = strsep(&rule, ",")) != NULL) { + while ((elem = strsep(&rule, ",")) != NULL) { char *attr = elem, *value = NULL; ndpi_port_range range; int is_tcp = 0, is_udp = 0, is_ip = 0; @@ -2661,9 +2635,17 @@ int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, is_ip = 1, value = &attr[3]; else if(strncmp(attr, "host:", 5) == 0) { /* host:"",host:"",.....@ */ + u_int i, max_len; + value = &attr[5]; - if(value[0] == '"') value++; /* remove leading " */ - if(value[strlen(value)-1] == '"') value[strlen(value)-1] = '\0'; /* remove trailing " */ + if(value[0] == '"') + value++; /* remove leading " */ + + max_len = strlen(value) - 1; + if(value[max_len] == '"') + value[max_len] = '\0'; /* remove trailing " */ + + for(i=0; itcpRoot : &ndpi_str->udpRoot, __FUNCTION__,__LINE__); + is_tcp ? &ndpi_str->tcpRoot : &ndpi_str->udpRoot, __FUNCTION__, __LINE__); else removeDefaultPort(&range, def, is_tcp ? &ndpi_str->tcpRoot : &ndpi_str->udpRoot); } else if(is_ip) { @@ -2684,8 +2666,7 @@ int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, ndpi_add_host_ip_subprotocol(ndpi_str, value, subprotocol_id); } else { if(do_add) - ndpi_add_host_url_subprotocol(ndpi_str, value, subprotocol_id, - NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, + ndpi_add_host_url_subprotocol(ndpi_str, value, subprotocol_id, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_ACCEPTABLE); else ndpi_remove_host_url_subprotocol(ndpi_str, value, subprotocol_id); @@ -2706,10 +2687,10 @@ int ndpi_handle_rule(struct ndpi_detection_module_struct *ndpi_str, * - host and category are separated by a single TAB * - empty lines or lines starting with # are ignored */ -int ndpi_load_categories_file(struct ndpi_detection_module_struct *ndpi_str, const char* path) { +int ndpi_load_categories_file(struct ndpi_detection_module_struct *ndpi_str, const char *path) { char buffer[512], *line, *name, *category, *saveptr; FILE *fd; - int len; + int len, num = 0; fd = fopen(path, "r"); @@ -2718,7 +2699,7 @@ int ndpi_load_categories_file(struct ndpi_detection_module_struct *ndpi_str, con return(-1); } - while(fd) { + while (1) { line = fgets(buffer, sizeof(buffer), fd); if(line == NULL) @@ -2729,21 +2710,25 @@ int ndpi_load_categories_file(struct ndpi_detection_module_struct *ndpi_str, con if((len <= 1) || (line[0] == '#')) continue; - line[len-1] = '\0'; + line[len - 1] = '\0'; name = strtok_r(line, "\t", &saveptr); if(name) { category = strtok_r(NULL, "\t", &saveptr); - if(category) - ndpi_load_category(ndpi_str, name, (ndpi_protocol_category_t) atoi(category)); + if(category) { + int rc = ndpi_load_category(ndpi_str, name, (ndpi_protocol_category_t) atoi(category)); + + if(rc >= 0) + num++; + } } } fclose(fd); ndpi_enable_loaded_categories(ndpi_str); - return(0); + return(num); } /* ******************************************************************** */ @@ -2763,7 +2748,7 @@ int ndpi_load_categories_file(struct ndpi_detection_module_struct *ndpi_str, con udp:139@NETBIOS */ -int ndpi_load_protocols_file(struct ndpi_detection_module_struct *ndpi_str, const char* path) { +int ndpi_load_protocols_file(struct ndpi_detection_module_struct *ndpi_str, const char *path) { FILE *fd; char *buffer, *old_buffer; int chunk_len = 512, buffer_len = chunk_len, old_buffer_len; @@ -2783,11 +2768,11 @@ int ndpi_load_protocols_file(struct ndpi_detection_module_struct *ndpi_str, cons goto close_fd; } - while(1) { + while (1) { char *line = buffer; int line_len = buffer_len; - while((line = fgets(line, line_len, fd)) != NULL && line[strlen(line)-1] != '\n') { + while ((line = fgets(line, line_len, fd)) != NULL && line[strlen(line) - 1] != '\n') { i = strlen(line); old_buffer = buffer; old_buffer_len = buffer_len; @@ -2796,9 +2781,9 @@ int ndpi_load_protocols_file(struct ndpi_detection_module_struct *ndpi_str, cons buffer = ndpi_realloc(old_buffer, old_buffer_len, buffer_len); if(buffer == NULL) { - NDPI_LOG_ERR(ndpi_str, "Memory allocation failure\n"); - ndpi_free(old_buffer); - goto close_fd; + NDPI_LOG_ERR(ndpi_str, "Memory allocation failure\n"); + ndpi_free(old_buffer); + goto close_fd; } line = &buffer[i]; @@ -2812,7 +2797,7 @@ int ndpi_load_protocols_file(struct ndpi_detection_module_struct *ndpi_str, cons if((i <= 1) || (buffer[0] == '#')) continue; else - buffer[i-1] = '\0'; + buffer[i - 1] = '\0'; ndpi_handle_rule(ndpi_str, buffer, 1); } @@ -2831,32 +2816,30 @@ int ndpi_load_protocols_file(struct ndpi_detection_module_struct *ndpi_str, cons /* ******************************************************************** */ /* ntop */ -void ndpi_set_bitmask_protocol_detection(char * label, - struct ndpi_detection_module_struct *ndpi_str, - const NDPI_PROTOCOL_BITMASK * detection_bitmask, - const u_int32_t idx, - u_int16_t ndpi_protocol_id, - void (*func) (struct ndpi_detection_module_struct *, struct ndpi_flow_struct *flow), - const NDPI_SELECTION_BITMASK_PROTOCOL_SIZE ndpi_selection_bitmask, - u_int8_t b_save_bitmask_unknow, - u_int8_t b_add_detection_bitmask) { +void ndpi_set_bitmask_protocol_detection(char *label, struct ndpi_detection_module_struct *ndpi_str, + const NDPI_PROTOCOL_BITMASK *detection_bitmask, const u_int32_t idx, + u_int16_t ndpi_protocol_id, + void (*func)(struct ndpi_detection_module_struct *, + struct ndpi_flow_struct *flow), + const NDPI_SELECTION_BITMASK_PROTOCOL_SIZE ndpi_selection_bitmask, + u_int8_t b_save_bitmask_unknow, u_int8_t b_add_detection_bitmask) { /* Compare specify protocol bitmask with main detection bitmask */ if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(*detection_bitmask, ndpi_protocol_id) != 0) { #ifdef DEBUG - NDPI_LOG_DBG2(ndpi_str - "[NDPI] ndpi_set_bitmask_protocol_detection: %s : [callback_buffer] idx= %u, [proto_defaults] protocol_id=%u\n", + NDPI_LOG_DBG2(ndpi_str, + "[NDPI] ndpi_set_bitmask_protocol_detection: %s : [callback_buffer] idx= %u, [proto_defaults] " + "protocol_id=%u\n", label, idx, ndpi_protocol_id); #endif if(ndpi_str->proto_defaults[ndpi_protocol_id].protoIdx != 0) { - NDPI_LOG_DBG2(ndpi_str, - "[NDPI] Internal error: protocol %s/%u has been already registered\n", label, ndpi_protocol_id); + NDPI_LOG_DBG2(ndpi_str, "[NDPI] Internal error: protocol %s/%u has been already registered\n", label, + ndpi_protocol_id); #ifdef DEBUG } else { - NDPI_LOG_DBG2(ndpi_str, - "[NDPI] Adding %s with protocol id %d\n", label, ndpi_protocol_id); + NDPI_LOG_DBG2(ndpi_str, "[NDPI] Adding %s with protocol id %d\n", label, ndpi_protocol_id); #endif } @@ -2866,7 +2849,8 @@ void ndpi_set_bitmask_protocol_detection(char * label, */ ndpi_str->proto_defaults[ndpi_protocol_id].protoIdx = idx; ndpi_str->proto_defaults[ndpi_protocol_id].func = ndpi_str->callback_buffer[idx].func = func; - + ndpi_str->callback_buffer[idx].ndpi_protocol_id = ndpi_protocol_id; + /* Set ndpi_selection_bitmask for protocol */ @@ -2876,8 +2860,10 @@ void ndpi_set_bitmask_protocol_detection(char * label, Reset protocol detection bitmask via NDPI_PROTOCOL_UNKNOWN and than add specify protocol bitmast to callback buffer. */ - if(b_save_bitmask_unknow) NDPI_SAVE_AS_BITMASK(ndpi_str->callback_buffer[idx].detection_bitmask, NDPI_PROTOCOL_UNKNOWN); - if(b_add_detection_bitmask) NDPI_ADD_PROTOCOL_TO_BITMASK(ndpi_str->callback_buffer[idx].detection_bitmask, ndpi_protocol_id); + if(b_save_bitmask_unknow) + NDPI_SAVE_AS_BITMASK(ndpi_str->callback_buffer[idx].detection_bitmask, NDPI_PROTOCOL_UNKNOWN); + if(b_add_detection_bitmask) + NDPI_ADD_PROTOCOL_TO_BITMASK(ndpi_str->callback_buffer[idx].detection_bitmask, ndpi_protocol_id); NDPI_SAVE_AS_BITMASK(ndpi_str->callback_buffer[idx].excluded_protocol_bitmask, ndpi_protocol_id); } @@ -2886,7 +2872,7 @@ void ndpi_set_bitmask_protocol_detection(char * label, /* ******************************************************************** */ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *ndpi_str, - const NDPI_PROTOCOL_BITMASK * dbm) { + const NDPI_PROTOCOL_BITMASK *dbm) { NDPI_PROTOCOL_BITMASK detection_bitmask_local; NDPI_PROTOCOL_BITMASK *detection_bitmask = &detection_bitmask_local; u_int32_t a = 0; @@ -2939,14 +2925,8 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* DIRECTCONNECT */ init_directconnect_dissector(ndpi_str, &a, detection_bitmask); - /* MSN */ - init_msn_dissector(ndpi_str, &a, detection_bitmask); - - /* YAHOO */ - init_yahoo_dissector(ndpi_str, &a, detection_bitmask); - - /* OSCAR */ - init_oscar_dissector(ndpi_str, &a, detection_bitmask); + /* NATS */ + init_nats_dissector(ndpi_str, &a, detection_bitmask); /* APPLEJUICE */ init_applejuice_dissector(ndpi_str, &a, detection_bitmask); @@ -2987,9 +2967,6 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* NON_TCP_UDP */ init_non_tcp_udp_dissector(ndpi_str, &a, detection_bitmask); - /* TVANTS */ - init_tvants_dissector(ndpi_str, &a, detection_bitmask); - /* SOPCAST */ init_sopcast_dissector(ndpi_str, &a, detection_bitmask); @@ -2999,9 +2976,6 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* PPSTREAM */ init_ppstream_dissector(ndpi_str, &a, detection_bitmask); - /* PPLIVE */ - init_pplive_dissector(ndpi_str, &a, detection_bitmask); - /* IAX */ init_iax_dissector(ndpi_str, &a, detection_bitmask); @@ -3026,9 +3000,6 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* VNC */ init_vnc_dissector(ndpi_str, &a, detection_bitmask); - /* LINE */ - init_line_dissector(ndpi_str, &a, detection_bitmask); - /* TEAMVIEWER */ init_teamviewer_dissector(ndpi_str, &a, detection_bitmask); @@ -3077,12 +3048,6 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* BGP */ init_bgp_dissector(ndpi_str, &a, detection_bitmask); - /* BATTLEFIELD */ - init_battlefield_dissector(ndpi_str, &a, detection_bitmask); - - /* PCANYWHERE */ - init_pcanywhere_dissector(ndpi_str, &a, detection_bitmask); - /* SNMP */ init_snmp_dissector(ndpi_str, &a, detection_bitmask); @@ -3110,9 +3075,6 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* NETBIOS */ init_netbios_dissector(ndpi_str, &a, detection_bitmask); - /* MDNS */ - init_mdns_dissector(ndpi_str, &a, detection_bitmask); - /* IPP */ init_ipp_dissector(ndpi_str, &a, detection_bitmask); @@ -3215,9 +3177,6 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* TEAMSPEAK */ init_teamspeak_dissector(ndpi_str, &a, detection_bitmask); - /* VIBER */ - init_viber_dissector(ndpi_str, &a, detection_bitmask); - /* TOR */ init_tor_dissector(ndpi_str, &a, detection_bitmask); @@ -3248,9 +3207,6 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* FTP_DATA */ init_ftp_data_dissector(ndpi_str, &a, detection_bitmask); - /* PANDO */ - init_pando_dissector(ndpi_str, &a, detection_bitmask); - /* MEGACO */ init_megaco_dissector(ndpi_str, &a, detection_bitmask); @@ -3329,8 +3285,17 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* MODBUS */ init_modbus_dissector(ndpi_str, &a, detection_bitmask); + /* CAPWAP */ + init_capwap_dissector(ndpi_str, &a, detection_bitmask); + + /* ZABBIX */ + init_zabbix_dissector(ndpi_str, &a, detection_bitmask); + /*** Put false-positive sensitive protocols at the end ***/ + /* VIBER */ + init_viber_dissector(ndpi_str, &a, detection_bitmask); + /* SKYPE */ init_skype_dissector(ndpi_str, &a, detection_bitmask); @@ -3364,72 +3329,94 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n /* WireGuard VPN */ init_wireguard_dissector(ndpi_str, &a, detection_bitmask); - /* AMAZON_VIDEO */ + /* Amazon_Video */ init_amazon_video_dissector(ndpi_str, &a, detection_bitmask); /* Targus Getdata */ init_targus_getdata_dissector(ndpi_str, &a, detection_bitmask); + /* S7 comm */ + init_s7comm_dissector(ndpi_str, &a, detection_bitmask); + + /* IEC 60870-5-104 */ + init_104_dissector(ndpi_str, &a, detection_bitmask); + + /* DNP3 */ + init_dnp3_dissector(ndpi_str, &a, detection_bitmask); + + /* WEBSOCKET */ + init_websocket_dissector(ndpi_str, &a, detection_bitmask); + + /* SOAP */ + init_soap_dissector(ndpi_str, &a, detection_bitmask); + + /* DNScrypt */ + init_dnscrypt_dissector(ndpi_str, &a, detection_bitmask); + +#ifdef CUSTOM_NDPI_PROTOCOLS +#include "../../../nDPI-custom/custom_ndpi_main_init.c" +#endif + /* ----------------------------------------------------------------- */ ndpi_str->callback_buffer_size = a; - NDPI_LOG_DBG2(ndpi_str, - "callback_buffer_size is %u\n", ndpi_str->callback_buffer_size); + NDPI_LOG_DBG2(ndpi_str, "callback_buffer_size is %u\n", ndpi_str->callback_buffer_size); /* now build the specific buffer for tcp, udp and non_tcp_udp */ ndpi_str->callback_buffer_size_tcp_payload = 0; ndpi_str->callback_buffer_size_tcp_no_payload = 0; - for(a = 0; a < ndpi_str->callback_buffer_size; a++) { - if((ndpi_str->callback_buffer[a].ndpi_selection_bitmask - & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP | - NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP | - NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC)) != 0) { - if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_str, - "callback_buffer_tcp_payload, adding buffer %u as entry %u\n", a, - ndpi_str->callback_buffer_size_tcp_payload); + for (a = 0; a < ndpi_str->callback_buffer_size; a++) { + if((ndpi_str->callback_buffer[a].ndpi_selection_bitmask & + (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP | + NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC)) != 0) { + if(_ndpi_debug_callbacks) + NDPI_LOG_DBG2(ndpi_str, "callback_buffer_tcp_payload, adding buffer %u as entry %u\n", a, + ndpi_str->callback_buffer_size_tcp_payload); memcpy(&ndpi_str->callback_buffer_tcp_payload[ndpi_str->callback_buffer_size_tcp_payload], &ndpi_str->callback_buffer[a], sizeof(struct ndpi_call_function_struct)); ndpi_str->callback_buffer_size_tcp_payload++; - if((ndpi_str-> - callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == 0) { - if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_str, - "\tcallback_buffer_tcp_no_payload, additional adding buffer %u to no_payload process\n", a); + if((ndpi_str->callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == + 0) { + if(_ndpi_debug_callbacks) + NDPI_LOG_DBG2( + ndpi_str, + "\tcallback_buffer_tcp_no_payload, additional adding buffer %u to no_payload process\n", a); - memcpy(&ndpi_str->callback_buffer_tcp_no_payload - [ndpi_str->callback_buffer_size_tcp_no_payload], &ndpi_str->callback_buffer[a], - sizeof(struct ndpi_call_function_struct)); + memcpy(&ndpi_str->callback_buffer_tcp_no_payload[ndpi_str->callback_buffer_size_tcp_no_payload], + &ndpi_str->callback_buffer[a], sizeof(struct ndpi_call_function_struct)); ndpi_str->callback_buffer_size_tcp_no_payload++; } } } ndpi_str->callback_buffer_size_udp = 0; - for(a = 0; a < ndpi_str->callback_buffer_size; a++) { - if((ndpi_str->callback_buffer[a].ndpi_selection_bitmask & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP | - NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP | - NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC)) - != 0) { - if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_str, - "callback_buffer_size_udp: adding buffer : %u as entry %u\n", a, ndpi_str->callback_buffer_size_udp); - - memcpy(&ndpi_str->callback_buffer_udp[ndpi_str->callback_buffer_size_udp], - &ndpi_str->callback_buffer[a], sizeof(struct ndpi_call_function_struct)); + for (a = 0; a < ndpi_str->callback_buffer_size; a++) { + if((ndpi_str->callback_buffer[a].ndpi_selection_bitmask & + (NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP | NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP | + NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC)) != 0) { + if(_ndpi_debug_callbacks) + NDPI_LOG_DBG2(ndpi_str, "callback_buffer_size_udp: adding buffer : %u as entry %u\n", a, + ndpi_str->callback_buffer_size_udp); + + memcpy(&ndpi_str->callback_buffer_udp[ndpi_str->callback_buffer_size_udp], &ndpi_str->callback_buffer[a], + sizeof(struct ndpi_call_function_struct)); ndpi_str->callback_buffer_size_udp++; } } ndpi_str->callback_buffer_size_non_tcp_udp = 0; - for(a = 0; a < ndpi_str->callback_buffer_size; a++) { - if((ndpi_str->callback_buffer[a].ndpi_selection_bitmask & (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP | - NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP | - NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP)) == 0 - || (ndpi_str-> - callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC) != 0) { - if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_str, - "callback_buffer_non_tcp_udp: adding buffer : %u as entry %u\n", a, ndpi_str->callback_buffer_size_non_tcp_udp); + for (a = 0; a < ndpi_str->callback_buffer_size; a++) { + if((ndpi_str->callback_buffer[a].ndpi_selection_bitmask & + (NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP | NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP | + NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP)) == 0 || + (ndpi_str->callback_buffer[a].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_COMPLETE_TRAFFIC) != + 0) { + if(_ndpi_debug_callbacks) + NDPI_LOG_DBG2(ndpi_str, "callback_buffer_non_tcp_udp: adding buffer : %u as entry %u\n", a, + ndpi_str->callback_buffer_size_non_tcp_udp); memcpy(&ndpi_str->callback_buffer_non_tcp_udp[ndpi_str->callback_buffer_size_non_tcp_udp], &ndpi_str->callback_buffer[a], sizeof(struct ndpi_call_function_struct)); @@ -3450,27 +3437,34 @@ void ndpi_set_protocol_detection_bitmask2(struct ndpi_detection_module_struct *n * nxt_hdr: protocol of the actual payload * returns 0 upon success and 1 upon failure */ -static int ndpi_handle_ipv6_extension_headers(struct ndpi_detection_module_struct *ndpi_str, const u_int8_t ** l4ptr, u_int16_t * l4len, u_int8_t * nxt_hdr) -{ - while((*nxt_hdr == 0 || *nxt_hdr == 43 || *nxt_hdr == 44 || *nxt_hdr == 60 || *nxt_hdr == 135 || *nxt_hdr == 59)) { +int ndpi_handle_ipv6_extension_headers(struct ndpi_detection_module_struct *ndpi_str, const u_int8_t **l4ptr, + u_int16_t *l4len, u_int8_t *nxt_hdr) { + while ((*nxt_hdr == 0 || *nxt_hdr == 43 || *nxt_hdr == 44 || *nxt_hdr == 60 || *nxt_hdr == 135 || *nxt_hdr == 59)) { u_int16_t ehdr_len; // no next header if(*nxt_hdr == 59) { return(1); } + // fragment extension header has fixed size of 8 bytes and the first byte is the next header type if(*nxt_hdr == 44) { if(*l4len < 8) { return(1); } + *nxt_hdr = (*l4ptr)[0]; *l4len -= 8; (*l4ptr) += 8; continue; } + // the other extension headers have one byte for the next header type // and one byte for the extension header length in 8 byte steps minus the first 8 bytes + if(*l4len < 2) { + return(1); + } + ehdr_len = (*l4ptr)[1]; ehdr_len *= 8; ehdr_len += 8; @@ -3478,20 +3472,24 @@ static int ndpi_handle_ipv6_extension_headers(struct ndpi_detection_module_struc if(*l4len < ehdr_len) { return(1); } + *nxt_hdr = (*l4ptr)[0]; + + if(*l4len < ehdr_len) + return(1); + *l4len -= ehdr_len; (*l4ptr) += ehdr_len; } + return(0); } #endif /* NDPI_DETECTION_SUPPORT_IPV6 */ - -static u_int8_t ndpi_iph_is_valid_and_not_fragmented(const struct ndpi_iphdr *iph, const u_int16_t ipsize) -{ +static u_int8_t ndpi_iph_is_valid_and_not_fragmented(const struct ndpi_iphdr *iph, const u_int16_t ipsize) { //#ifdef REQUIRE_FULL_PACKETS - if(ipsize < iph->ihl * 4 || - ipsize < ntohs(iph->tot_len) || ntohs(iph->tot_len) < iph->ihl * 4 || (iph->frag_off & htons(0x1FFF)) != 0) { + if(ipsize < iph->ihl * 4 || ipsize < ntohs(iph->tot_len) || ntohs(iph->tot_len) < iph->ihl * 4 || + (iph->frag_off & htons(0x1FFF)) != 0) { return(0); } //#endif @@ -3499,11 +3497,9 @@ static u_int8_t ndpi_iph_is_valid_and_not_fragmented(const struct ndpi_iphdr *ip return(1); } -static u_int8_t ndpi_detection_get_l4_internal(struct ndpi_detection_module_struct *ndpi_str, - const u_int8_t * l3, u_int16_t l3_len, - const u_int8_t ** l4_return, u_int16_t * l4_len_return, - u_int8_t * l4_protocol_return, u_int32_t flags) -{ +static u_int8_t ndpi_detection_get_l4_internal(struct ndpi_detection_module_struct *ndpi_str, const u_int8_t *l3, + u_int16_t l3_len, const u_int8_t **l4_return, u_int16_t *l4_len_return, + u_int8_t *l4_protocol_return, u_int32_t flags) { const struct ndpi_iphdr *iph = NULL; #ifdef NDPI_DETECTION_SUPPORT_IPV6 const struct ndpi_ipv6hdr *iph_v6 = NULL; @@ -3515,7 +3511,8 @@ static u_int8_t ndpi_detection_get_l4_internal(struct ndpi_detection_module_stru if(l3 == NULL || l3_len < sizeof(struct ndpi_iphdr)) return(1); - iph = (const struct ndpi_iphdr *) l3; + if((iph = (const struct ndpi_iphdr *) l3) == NULL) + return(1); if(iph->version == IPVERSION && iph->ihl >= 5) { NDPI_LOG_DBG2(ndpi_str, "ipv4 header\n"); @@ -3543,12 +3540,13 @@ static u_int8_t ndpi_detection_get_l4_internal(struct ndpi_detection_module_stru #endif if(iph != NULL && ndpi_iph_is_valid_and_not_fragmented(iph, l3_len)) { - u_int16_t len = ntohs(iph->tot_len); + u_int16_t len = ntohs(iph->tot_len); u_int16_t hlen = (iph->ihl * 4); l4ptr = (((const u_int8_t *) iph) + iph->ihl * 4); - if(len == 0) len = l3_len; + if(len == 0) + len = l3_len; l4len = (len > hlen) ? (len - hlen) : 0; l4protocol = iph->protocol; @@ -3585,16 +3583,18 @@ static u_int8_t ndpi_detection_get_l4_internal(struct ndpi_detection_module_stru return(0); } -void ndpi_apply_flow_protocol_to_packet(struct ndpi_flow_struct *flow, - struct ndpi_packet_struct *packet) -{ +/* ************************************************ */ + +void ndpi_apply_flow_protocol_to_packet(struct ndpi_flow_struct *flow, struct ndpi_packet_struct *packet) { memcpy(&packet->detected_protocol_stack, &flow->detected_protocol_stack, sizeof(packet->detected_protocol_stack)); memcpy(&packet->protocol_stack_info, &flow->protocol_stack_info, sizeof(packet->protocol_stack_info)); } +/* ************************************************ */ + static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, - unsigned short packetlen) { + unsigned short packetlen) { const struct ndpi_iphdr *decaps_iph = NULL; u_int16_t l3len; u_int16_t l4len; @@ -3602,7 +3602,7 @@ static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_str u_int8_t l4protocol; u_int8_t l4_result; - if (!flow) + if(!flow) return(1); /* reset payload_packet_len, will be set if ipv4 tcp or udp */ @@ -3614,7 +3614,7 @@ static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_str flow->packet.generic_l4_ptr = NULL; #ifdef NDPI_DETECTION_SUPPORT_IPV6 flow->packet.iphv6 = NULL; -#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ +#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ ndpi_apply_flow_protocol_to_packet(flow, &flow->packet); @@ -3622,13 +3622,13 @@ static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_str #ifdef NDPI_DETECTION_SUPPORT_IPV6 if(flow->packet.iph != NULL) { -#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ +#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ decaps_iph = flow->packet.iph; #ifdef NDPI_DETECTION_SUPPORT_IPV6 } -#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ +#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ if(decaps_iph && decaps_iph->version == IPVERSION && decaps_iph->ihl >= 5) { NDPI_LOG_DBG2(ndpi_str, "ipv4 header\n"); @@ -3637,7 +3637,7 @@ static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_str else if(decaps_iph && decaps_iph->version == 6 && l3len >= sizeof(struct ndpi_ipv6hdr) && (ndpi_str->ip_version_limit & NDPI_DETECTION_ONLY_IPV4) == 0) { NDPI_LOG_DBG2(ndpi_str, "ipv6 header\n"); - flow->packet.iphv6 = (struct ndpi_ipv6hdr *)flow->packet.iph; + flow->packet.iphv6 = (struct ndpi_ipv6hdr *) flow->packet.iph; flow->packet.iph = NULL; } #endif @@ -3668,47 +3668,69 @@ static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_str flow->l4_proto = l4protocol; /* tcp / udp detection */ - if(l4protocol == IPPROTO_TCP && flow->packet.l4_packet_len >= 20 /* min size of tcp */ ) { + if(l4protocol == IPPROTO_TCP && flow->packet.l4_packet_len >= 20 /* min size of tcp */) { /* tcp */ flow->packet.tcp = (struct ndpi_tcphdr *) l4ptr; - if(flow->packet.l4_packet_len >=flow->packet.tcp->doff * 4) { - flow->packet.payload_packet_len = - flow->packet.l4_packet_len -flow->packet.tcp->doff * 4; + if(flow->packet.l4_packet_len >= flow->packet.tcp->doff * 4) { + flow->packet.payload_packet_len = flow->packet.l4_packet_len - flow->packet.tcp->doff * 4; flow->packet.actual_payload_len = flow->packet.payload_packet_len; - flow->packet.payload = ((u_int8_t *)flow->packet.tcp) + (flow->packet.tcp->doff * 4); + flow->packet.payload = ((u_int8_t *) flow->packet.tcp) + (flow->packet.tcp->doff * 4); /* check for new tcp syn packets, here * idea: reset detection state if a connection is unknown */ - if(flow->packet.tcp->syn != 0 - && flow->packet.tcp->ack == 0 - && flow->init_finished != 0 - && flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { + if(flow->packet.tcp->syn != 0 && flow->packet.tcp->ack == 0 && flow->init_finished != 0 && + flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { u_int8_t backup; u_int16_t backup1, backup2; - if(flow->http.url) ndpi_free(flow->http.url); - if(flow->http.content_type) ndpi_free(flow->http.content_type); + if(flow->http.url) { + ndpi_free(flow->http.url); + flow->http.url = NULL; + } + if(flow->http.content_type) { + ndpi_free(flow->http.content_type); + flow->http.content_type = NULL; + } + if(flow->http.user_agent) { + ndpi_free(flow->http.user_agent); + flow->http.user_agent = NULL; + } + if(flow->kerberos_buf.pktbuf) { + ndpi_free(flow->kerberos_buf.pktbuf); + flow->kerberos_buf.pktbuf = NULL; + } + if(flow->l4.tcp.tls.message.buffer) { + ndpi_free(flow->l4.tcp.tls.message.buffer); + flow->l4.tcp.tls.message.buffer = NULL; + flow->l4.tcp.tls.message.buffer_len = flow->l4.tcp.tls.message.buffer_used = 0; + } - backup = flow->num_processed_pkts; + backup = flow->num_processed_pkts; backup1 = flow->guessed_protocol_id; backup2 = flow->guessed_host_protocol_id; memset(flow, 0, sizeof(*(flow))); + + /* Restore pointers */ flow->num_processed_pkts = backup; - flow->guessed_protocol_id = backup1; + flow->guessed_protocol_id = backup1; flow->guessed_host_protocol_id = backup2; + flow->packet.tcp = (struct ndpi_tcphdr *) l4ptr; - NDPI_LOG_DBG(ndpi_str, - "tcp syn packet for unknown protocol, reset detection state\n"); + NDPI_LOG_DBG(ndpi_str, "tcp syn packet for unknown protocol, reset detection state\n"); } } else { /* tcp header not complete */ flow->packet.tcp = NULL; } - } else if(l4protocol == IPPROTO_UDP && flow->packet.l4_packet_len >= 8 /* size of udp */ ) { + } else if(l4protocol == IPPROTO_UDP && flow->packet.l4_packet_len >= 8 /* size of udp */) { flow->packet.udp = (struct ndpi_udphdr *) l4ptr; - flow->packet.payload_packet_len =flow->packet.l4_packet_len - 8; - flow->packet.payload = ((u_int8_t *)flow->packet.udp) + 8; + flow->packet.payload_packet_len = flow->packet.l4_packet_len - 8; + flow->packet.payload = ((u_int8_t *) flow->packet.udp) + 8; + } else if((l4protocol == IPPROTO_ICMP && flow->packet.l4_packet_len >= sizeof(struct ndpi_icmphdr)) + || (l4protocol == IPPROTO_ICMPV6 && flow->packet.l4_packet_len >= sizeof(struct ndpi_icmp6hdr))) { + flow->packet.payload = ((u_int8_t *) l4ptr); + flow->packet.payload_packet_len = flow->packet.l4_packet_len; } else { flow->packet.generic_l4_ptr = l4ptr; } @@ -3716,6 +3738,8 @@ static int ndpi_init_packet_header(struct ndpi_detection_module_struct *ndpi_str return(0); } +/* ************************************************ */ + void ndpi_connection_tracking(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow) { if(!flow) { @@ -3739,8 +3763,7 @@ void ndpi_connection_tracking(struct ndpi_detection_module_struct *ndpi_str, packet->packet_direction = 1; #ifdef NDPI_DETECTION_SUPPORT_IPV6 - if(iphv6 != NULL && NDPI_COMPARE_IPV6_ADDRESS_STRUCTS(&iphv6->ip6_src, - &iphv6->ip6_dst) != 0) + if(iphv6 != NULL && NDPI_COMPARE_IPV6_ADDRESS_STRUCTS(&iphv6->ip6_src, &iphv6->ip6_dst) != 0) packet->packet_direction = 1; #endif } @@ -3759,20 +3782,20 @@ void ndpi_connection_tracking(struct ndpi_detection_module_struct *ndpi_str, if(!ndpi_str->direction_detect_disable) packet->packet_direction = (ntohs(tcph->source) < ntohs(tcph->dest)) ? 1 : 0; - if(tcph->syn != 0 && tcph->ack == 0 && flow->l4.tcp.seen_syn == 0 && flow->l4.tcp.seen_syn_ack == 0 - && flow->l4.tcp.seen_ack == 0) { + if(tcph->syn != 0 && tcph->ack == 0 && flow->l4.tcp.seen_syn == 0 && flow->l4.tcp.seen_syn_ack == 0 && + flow->l4.tcp.seen_ack == 0) { flow->l4.tcp.seen_syn = 1; } - if(tcph->syn != 0 && tcph->ack != 0 && flow->l4.tcp.seen_syn == 1 && flow->l4.tcp.seen_syn_ack == 0 - && flow->l4.tcp.seen_ack == 0) { + if(tcph->syn != 0 && tcph->ack != 0 && flow->l4.tcp.seen_syn == 1 && flow->l4.tcp.seen_syn_ack == 0 && + flow->l4.tcp.seen_ack == 0) { flow->l4.tcp.seen_syn_ack = 1; } - if(tcph->syn == 0 && tcph->ack == 1 && flow->l4.tcp.seen_syn == 1 && flow->l4.tcp.seen_syn_ack == 1 - && flow->l4.tcp.seen_ack == 0) { + if(tcph->syn == 0 && tcph->ack == 1 && flow->l4.tcp.seen_syn == 1 && flow->l4.tcp.seen_syn_ack == 1 && + flow->l4.tcp.seen_ack == 0) { flow->l4.tcp.seen_ack = 1; } - if((flow->next_tcp_seq_nr[0] == 0 && flow->next_tcp_seq_nr[1] == 0) - || (flow->next_tcp_seq_nr[0] == 0 || flow->next_tcp_seq_nr[1] == 0)) { + if((flow->next_tcp_seq_nr[0] == 0 && flow->next_tcp_seq_nr[1] == 0) || + (flow->next_tcp_seq_nr[0] == 0 || flow->next_tcp_seq_nr[1] == 0)) { /* initialize tcp sequence counters */ /* the ack flag needs to be set to get valid sequence numbers from the other * direction. Usually it will catch the second packet syn+ack but it works @@ -3785,21 +3808,22 @@ void ndpi_connection_tracking(struct ndpi_detection_module_struct *ndpi_str, flow->next_tcp_seq_nr[flow->packet.packet_direction] = ntohl(tcph->seq) + (tcph->syn ? 1 : packet->payload_packet_len); - flow->next_tcp_seq_nr[1 -flow->packet.packet_direction] = ntohl(tcph->ack_seq); + flow->next_tcp_seq_nr[1 - flow->packet.packet_direction] = ntohl(tcph->ack_seq); } } else if(packet->payload_packet_len > 0) { /* check tcp sequence counters */ if(((u_int32_t)(ntohl(tcph->seq) - flow->next_tcp_seq_nr[packet->packet_direction])) > ndpi_str->tcp_max_retransmission_window_size) { - packet->tcp_retransmission = 1; /* CHECK IF PARTIAL RETRY IS HAPPENING */ - if((flow->next_tcp_seq_nr[packet->packet_direction] - ntohl(tcph->seq) < packet->payload_packet_len)) { + if((flow->next_tcp_seq_nr[packet->packet_direction] - ntohl(tcph->seq) < + packet->payload_packet_len)) { /* num_retried_bytes actual_payload_len hold info about the partial retry analyzer which require this info can make use of this info Other analyzer can use packet->payload_packet_len */ - packet->num_retried_bytes = (u_int16_t)(flow->next_tcp_seq_nr[packet->packet_direction] - ntohl(tcph->seq)); + packet->num_retried_bytes = + (u_int16_t)(flow->next_tcp_seq_nr[packet->packet_direction] - ntohl(tcph->seq)); packet->actual_payload_len = packet->payload_packet_len - packet->num_retried_bytes; flow->next_tcp_seq_nr[packet->packet_direction] = ntohl(tcph->seq) + packet->payload_packet_len; } @@ -3827,7 +3851,8 @@ void ndpi_connection_tracking(struct ndpi_detection_module_struct *ndpi_str, flow->packet_counter++; } - if(flow->packet_direction_counter[packet->packet_direction] < MAX_PACKET_COUNTER && packet->payload_packet_len) { + if(flow->packet_direction_counter[packet->packet_direction] < MAX_PACKET_COUNTER && + packet->payload_packet_len) { flow->packet_direction_counter[packet->packet_direction]++; } @@ -3838,274 +3863,239 @@ void ndpi_connection_tracking(struct ndpi_detection_module_struct *ndpi_str, } } -void check_ndpi_other_flow_func(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { - - if (!flow) { - return; - } +/* ************************************************ */ +u_int32_t check_ndpi_other_flow_func(struct ndpi_detection_module_struct *ndpi_str, + struct ndpi_flow_struct *flow, + NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { void *func = NULL; - u_int32_t a; + u_int32_t a, num_calls = 0; u_int16_t proto_index = ndpi_str->proto_defaults[flow->guessed_protocol_id].protoIdx; int16_t proto_id = ndpi_str->proto_defaults[flow->guessed_protocol_id].protoId; NDPI_PROTOCOL_BITMASK detection_bitmask; NDPI_SAVE_AS_BITMASK(detection_bitmask, flow->packet.detected_protocol_stack[0]); - if((proto_id != NDPI_PROTOCOL_UNKNOWN) - && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, - detection_bitmask) != 0 - && (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask - & *ndpi_selection_packet) == ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { - if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) - && (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL)) + if((proto_id != NDPI_PROTOCOL_UNKNOWN) && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, detection_bitmask) != 0 && + (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { + if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) && + (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL)) ndpi_str->proto_defaults[flow->guessed_protocol_id].func(ndpi_str, flow), - func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func; + func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func, num_calls++; } - for(a = 0; a < ndpi_str->callback_buffer_size_non_tcp_udp; a++) { - if((func != ndpi_str->callback_buffer_non_tcp_udp[a].func) - && (ndpi_str->callback_buffer_non_tcp_udp[a].ndpi_selection_bitmask & *ndpi_selection_packet) == - ndpi_str->callback_buffer_non_tcp_udp[a].ndpi_selection_bitmask - && - NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer_non_tcp_udp[a].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_non_tcp_udp[a].detection_bitmask, - detection_bitmask) != 0) { - + for (a = 0; a < ndpi_str->callback_buffer_size_non_tcp_udp; a++) { + if((func != ndpi_str->callback_buffer_non_tcp_udp[a].func) && + (ndpi_str->callback_buffer_non_tcp_udp[a].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer_non_tcp_udp[a].ndpi_selection_bitmask && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer_non_tcp_udp[a].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_non_tcp_udp[a].detection_bitmask, detection_bitmask) != 0) { if(ndpi_str->callback_buffer_non_tcp_udp[a].func != NULL) - ndpi_str->callback_buffer_non_tcp_udp[a].func(ndpi_str, flow); + ndpi_str->callback_buffer_non_tcp_udp[a].func(ndpi_str, flow), num_calls++; if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) break; /* Stop after detecting the first protocol */ } } + + return(num_calls); } +/* ************************************************ */ -void check_ndpi_udp_flow_func(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { +static u_int32_t check_ndpi_udp_flow_func(struct ndpi_detection_module_struct *ndpi_str, + struct ndpi_flow_struct *flow, + NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { void *func = NULL; - u_int32_t a; + u_int32_t a, num_calls = 0; u_int16_t proto_index = ndpi_str->proto_defaults[flow->guessed_protocol_id].protoIdx; int16_t proto_id = ndpi_str->proto_defaults[flow->guessed_protocol_id].protoId; NDPI_PROTOCOL_BITMASK detection_bitmask; NDPI_SAVE_AS_BITMASK(detection_bitmask, flow->packet.detected_protocol_stack[0]); - if((proto_id != NDPI_PROTOCOL_UNKNOWN) - && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, - detection_bitmask) != 0 - && (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask - & *ndpi_selection_packet) == ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { - if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) - && (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL)) + if((proto_id != NDPI_PROTOCOL_UNKNOWN) && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, detection_bitmask) != 0 && + (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { + if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) && + (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL)) ndpi_str->proto_defaults[flow->guessed_protocol_id].func(ndpi_str, flow), - func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func; + func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func, num_calls++; } - for(a = 0; a < ndpi_str->callback_buffer_size_udp; a++) { - if((func != ndpi_str->callback_buffer_udp[a].func) - && (ndpi_str->callback_buffer_udp[a].ndpi_selection_bitmask & *ndpi_selection_packet) == - ndpi_str->callback_buffer_udp[a].ndpi_selection_bitmask - && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer_udp[a].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_udp[a].detection_bitmask, - detection_bitmask) != 0) { - ndpi_str->callback_buffer_udp[a].func(ndpi_str, flow); + if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { + for (a = 0; a < ndpi_str->callback_buffer_size_udp; a++) { + if((func != ndpi_str->callback_buffer_udp[a].func) && + (ndpi_str->callback_buffer_udp[a].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer_udp[a].ndpi_selection_bitmask && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer_udp[a].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_udp[a].detection_bitmask, detection_bitmask) != 0) { + ndpi_str->callback_buffer_udp[a].func(ndpi_str, flow), num_calls++; + + // NDPI_LOG_DBG(ndpi_str, "[UDP,CALL] dissector of protocol as callback_buffer idx = %d\n",a); + +#ifdef DEBUG_UDP_CALLS + { + char buf[64]; + u_int16_t proto_id = ndpi_str->callback_buffer_udp[a].ndpi_protocol_id; + ndpi_protocol proto = { proto_id, proto_id, 0 }; + printf("-> [UDP,CALL] dissector of protocol as callback_buffer idx = %d / %s\n", + proto_id, ndpi_protocol2name(ndpi_str, proto, buf, sizeof(buf))); + } +#endif + + if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) + break; /* Stop after detecting the first protocol */ + } else if(_ndpi_debug_callbacks) + NDPI_LOG_DBG2(ndpi_str, "[UDP,SKIP] dissector of protocol as callback_buffer idx = %d\n", a); + } + } else + num_calls = 1; - // NDPI_LOG_DBG(ndpi_str, "[UDP,CALL] dissector of protocol as callback_buffer idx = %d\n",a); - if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) - break; /* Stop after detecting the first protocol */ - } else - if(_ndpi_debug_callbacks) NDPI_LOG_DBG2(ndpi_str, - "[UDP,SKIP] dissector of protocol as callback_buffer idx = %d\n",a); - } + return(num_calls); } +/* ************************************************ */ -void check_ndpi_tcp_flow_func(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { +static u_int32_t check_ndpi_tcp_flow_func(struct ndpi_detection_module_struct *ndpi_str, + struct ndpi_flow_struct *flow, + NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { void *func = NULL; - u_int32_t a; + u_int32_t a, num_calls = 0; u_int16_t proto_index = ndpi_str->proto_defaults[flow->guessed_protocol_id].protoIdx; int16_t proto_id = ndpi_str->proto_defaults[flow->guessed_protocol_id].protoId; NDPI_PROTOCOL_BITMASK detection_bitmask; NDPI_SAVE_AS_BITMASK(detection_bitmask, flow->packet.detected_protocol_stack[0]); - + if(flow->packet.payload_packet_len != 0) { - if((proto_id != NDPI_PROTOCOL_UNKNOWN) - && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, detection_bitmask) != 0 - && (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask & *ndpi_selection_packet) == ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { - if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) - && (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL)) + if((proto_id != NDPI_PROTOCOL_UNKNOWN) && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, detection_bitmask) != 0 && + (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { + if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) && + (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL)) ndpi_str->proto_defaults[flow->guessed_protocol_id].func(ndpi_str, flow), - func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func; + func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func, num_calls++; } if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { - for(a = 0; a < ndpi_str->callback_buffer_size_tcp_payload; a++) { - if((func != ndpi_str->callback_buffer_tcp_payload[a].func) - && (ndpi_str->callback_buffer_tcp_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) == ndpi_str->callback_buffer_tcp_payload[a].ndpi_selection_bitmask - && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer_tcp_payload[a].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_tcp_payload[a].detection_bitmask, - detection_bitmask) != 0) { - ndpi_str->callback_buffer_tcp_payload[a].func(ndpi_str, flow); - + for (a = 0; a < ndpi_str->callback_buffer_size_tcp_payload; a++) { + if((func != ndpi_str->callback_buffer_tcp_payload[a].func) && + (ndpi_str->callback_buffer_tcp_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer_tcp_payload[a].ndpi_selection_bitmask && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer_tcp_payload[a].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_tcp_payload[a].detection_bitmask, + detection_bitmask) != 0) { + ndpi_str->callback_buffer_tcp_payload[a].func(ndpi_str, flow), num_calls++; if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) break; /* Stop after detecting the first protocol */ } } - } + } } else { /* no payload */ - if((proto_id != NDPI_PROTOCOL_UNKNOWN) - && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, - detection_bitmask) != 0 - && (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask - & *ndpi_selection_packet) == ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { - if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) - && (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL) - && ((ndpi_str->callback_buffer[flow->guessed_protocol_id].ndpi_selection_bitmask & NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == 0)) + + num_calls = 1; + + if((proto_id != NDPI_PROTOCOL_UNKNOWN) && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer[proto_index].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer[proto_index].detection_bitmask, detection_bitmask) != 0 && + (ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer[proto_index].ndpi_selection_bitmask) { + if((flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) && + (ndpi_str->proto_defaults[flow->guessed_protocol_id].func != NULL) && + ((ndpi_str->callback_buffer[flow->guessed_protocol_id].ndpi_selection_bitmask & + NDPI_SELECTION_BITMASK_PROTOCOL_HAS_PAYLOAD) == 0)) ndpi_str->proto_defaults[flow->guessed_protocol_id].func(ndpi_str, flow), - func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func; + func = ndpi_str->proto_defaults[flow->guessed_protocol_id].func, num_calls++; } - for(a = 0; a < ndpi_str->callback_buffer_size_tcp_no_payload; a++) { - if((func != ndpi_str->callback_buffer_tcp_payload[a].func) - && (ndpi_str->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) == - ndpi_str->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask - && NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, - ndpi_str->callback_buffer_tcp_no_payload[a].excluded_protocol_bitmask) == 0 - && NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_tcp_no_payload[a].detection_bitmask, - detection_bitmask) != 0) { - ndpi_str->callback_buffer_tcp_no_payload[a].func(ndpi_str, flow); - + for (a = 0; a < ndpi_str->callback_buffer_size_tcp_no_payload; a++) { + if((func != ndpi_str->callback_buffer_tcp_payload[a].func) && + (ndpi_str->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask & *ndpi_selection_packet) == + ndpi_str->callback_buffer_tcp_no_payload[a].ndpi_selection_bitmask && + NDPI_BITMASK_COMPARE(flow->excluded_protocol_bitmask, + ndpi_str->callback_buffer_tcp_no_payload[a].excluded_protocol_bitmask) == 0 && + NDPI_BITMASK_COMPARE(ndpi_str->callback_buffer_tcp_no_payload[a].detection_bitmask, + detection_bitmask) != 0) { + ndpi_str->callback_buffer_tcp_no_payload[a].func(ndpi_str, flow), num_calls++; if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) break; /* Stop after detecting the first protocol */ } } } + + return(num_calls); } /* ********************************************************************************* */ -void ndpi_check_flow_func(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { - if(flow->packet.tcp != NULL) - check_ndpi_tcp_flow_func(ndpi_str, flow, ndpi_selection_packet); +u_int32_t ndpi_check_flow_func(struct ndpi_detection_module_struct *ndpi_str, + struct ndpi_flow_struct *flow, + NDPI_SELECTION_BITMASK_PROTOCOL_SIZE *ndpi_selection_packet) { + if(!flow) + return(0); + else if(flow->packet.tcp != NULL) + return(check_ndpi_tcp_flow_func(ndpi_str, flow, ndpi_selection_packet)); else if(flow->packet.udp != NULL) - check_ndpi_udp_flow_func(ndpi_str, flow, ndpi_selection_packet); + return(check_ndpi_udp_flow_func(ndpi_str, flow, ndpi_selection_packet)); else - check_ndpi_other_flow_func(ndpi_str, flow, ndpi_selection_packet); + return(check_ndpi_other_flow_func(ndpi_str, flow, ndpi_selection_packet)); } /* ********************************************************************************* */ u_int16_t ndpi_guess_host_protocol_id(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow) { - u_int16_t ret = NDPI_PROTOCOL_UNKNOWN; - - if(flow->packet.iph) { - struct in_addr addr; - - addr.s_addr = flow->packet.iph->saddr; - - /* guess host protocol */ - ret = ndpi_network_ptree_match(ndpi_str, &addr); - - if(ret == NDPI_PROTOCOL_UNKNOWN) { - addr.s_addr = flow->packet.iph->daddr; - ret = ndpi_network_ptree_match(ndpi_str, &addr); - } - } - - return(ret); -} - -/* ********************************************************************************* */ - -static ndpi_protocol ndpi_process_partial_detection(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow) { - ndpi_protocol ret; - ndpi_protocol_match_result ret_match; - - ret.master_protocol = flow->guessed_protocol_id; - ret.app_protocol = ndpi_match_host_subprotocol(ndpi_str, flow, - (char *)flow->host_server_name, - strlen((const char*)flow->host_server_name), - &ret_match, - flow->guessed_protocol_id); - - if(flow->category != NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) - ret.category = flow->category; - else - ret.category = ret_match.protocol_category; - - if(ret.app_protocol == NDPI_PROTOCOL_UNKNOWN) - ret.app_protocol = ret.master_protocol; - - ndpi_fill_protocol_category(ndpi_str, flow, &ret); - - ndpi_int_change_protocol(ndpi_str, flow, ret.app_protocol, ret.master_protocol); + struct ndpi_flow_struct *flow) { + u_int16_t ret = NDPI_PROTOCOL_UNKNOWN; - return(ret); -} + if(flow->packet.iph) { + struct in_addr addr; + u_int16_t sport, dport; -/* ********************************************************************************* */ + addr.s_addr = flow->packet.iph->saddr; -/* - You can call this function at any time in case of unknown match to see if there is - a partial match that has been prevented by the current nDPI preferences configuration -*/ -ndpi_protocol ndpi_get_partial_detection(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow) { - if((flow->guessed_protocol_id == NDPI_PROTOCOL_HTTP) - && (ndpi_str->http_dont_dissect_response == 0) - && (flow->host_server_name[0] != '\0') - && (!NDPI_ISSET(&flow->excluded_protocol_bitmask, flow->guessed_host_protocol_id))) - return(ndpi_process_partial_detection(ndpi_str, flow)); - else if((flow->guessed_protocol_id == NDPI_PROTOCOL_DNS) - && (ndpi_str->dns_dont_dissect_response == 0) - && (flow->host_server_name[0] != '\0') - && (!NDPI_ISSET(&flow->excluded_protocol_bitmask, flow->guessed_host_protocol_id))) - return(ndpi_process_partial_detection(ndpi_str, flow)); - else { - ndpi_protocol ret = { NDPI_PROTOCOL_UNKNOWN, - NDPI_PROTOCOL_UNKNOWN, - NDPI_PROTOCOL_CATEGORY_UNSPECIFIED }; + if((flow->l4_proto == IPPROTO_TCP) && flow->packet.tcp) + sport = flow->packet.tcp->source, dport = flow->packet.tcp->dest; + else if((flow->l4_proto == IPPROTO_UDP) && flow->packet.udp) + sport = flow->packet.udp->source, dport = flow->packet.udp->dest; + else + sport = dport = 0; - if(flow) ret.category = flow->category; + /* guess host protocol */ + ret = ndpi_network_port_ptree_match(ndpi_str, &addr, sport); - return(ret); + if(ret == NDPI_PROTOCOL_UNKNOWN) { + addr.s_addr = flow->packet.iph->daddr; + ret = ndpi_network_port_ptree_match(ndpi_str, &addr, dport); + } } + + return(ret); } /* ********************************************************************************* */ -ndpi_protocol ndpi_detection_giveup(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int8_t enable_guess, - u_int8_t *protocol_was_guessed) { - ndpi_protocol ret = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED }; +ndpi_protocol ndpi_detection_giveup(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int8_t enable_guess, u_int8_t *protocol_was_guessed) { + ndpi_protocol ret = {NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED}; *protocol_was_guessed = 0; - + if(flow == NULL) return(ret); @@ -4119,90 +4109,87 @@ ndpi_protocol ndpi_detection_giveup(struct ndpi_detection_module_struct *ndpi_st /* TODO: add the remaining stage_XXXX protocols */ if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { - u_int16_t guessed_protocol_id, guessed_host_protocol_id; + u_int16_t guessed_protocol_id = NDPI_PROTOCOL_UNKNOWN, guessed_host_protocol_id = NDPI_PROTOCOL_UNKNOWN; if(flow->guessed_protocol_id == NDPI_PROTOCOL_STUN) goto check_stun_export; - else if((flow->guessed_protocol_id == NDPI_PROTOCOL_HANGOUT_DUO) - || (flow->guessed_protocol_id == NDPI_PROTOCOL_MESSENGER) - || (flow->guessed_protocol_id == NDPI_PROTOCOL_WHATSAPP_CALL)) + else if((flow->guessed_protocol_id == NDPI_PROTOCOL_HANGOUT_DUO) || + (flow->guessed_protocol_id == NDPI_PROTOCOL_MESSENGER) || + (flow->guessed_protocol_id == NDPI_PROTOCOL_WHATSAPP_CALL)) { + *protocol_was_guessed = 1; ndpi_set_detected_protocol(ndpi_str, flow, flow->guessed_protocol_id, NDPI_PROTOCOL_UNKNOWN); - else if((flow->l4.tcp.tls_seen_client_cert == 1) - && (flow->protos.stun_ssl.ssl.client_certificate[0] != '\0')) { + } + else if((flow->l4.tcp.tls.hello_processed == 1) && + (flow->protos.stun_ssl.ssl.client_requested_server_name[0] != '\0')) { + *protocol_was_guessed = 1; ndpi_set_detected_protocol(ndpi_str, flow, NDPI_PROTOCOL_TLS, NDPI_PROTOCOL_UNKNOWN); - } else { - ndpi_protocol ret_g = ndpi_get_partial_detection(ndpi_str, flow); - - if(ret_g.master_protocol != NDPI_PROTOCOL_UNKNOWN) - return(ret_g); - else { - if(!enable_guess) - return(ret); - } - - if((flow->guessed_protocol_id == NDPI_PROTOCOL_UNKNOWN) - && (flow->packet.l4_protocol == IPPROTO_TCP) - && (flow->l4.tcp.tls_stage > 1)) + } else if(enable_guess) { + if((flow->guessed_protocol_id == NDPI_PROTOCOL_UNKNOWN) && (flow->packet.l4_protocol == IPPROTO_TCP) && + flow->l4.tcp.tls.hello_processed) flow->guessed_protocol_id = NDPI_PROTOCOL_TLS; guessed_protocol_id = flow->guessed_protocol_id, guessed_host_protocol_id = flow->guessed_host_protocol_id; - if((guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) - && ((flow->packet.l4_protocol == IPPROTO_UDP) - && NDPI_ISSET(&flow->excluded_protocol_bitmask, guessed_host_protocol_id) - && is_udp_guessable_protocol(guessed_host_protocol_id) - )) + if((guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) && + ((flow->packet.l4_protocol == IPPROTO_UDP) && + NDPI_ISSET(&flow->excluded_protocol_bitmask, guessed_host_protocol_id) && + is_udp_guessable_protocol(guessed_host_protocol_id))) flow->guessed_host_protocol_id = guessed_host_protocol_id = NDPI_PROTOCOL_UNKNOWN; /* Ignore guessed protocol if they have been discarded */ if((guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) // && (guessed_host_protocol_id == NDPI_PROTOCOL_UNKNOWN) - && (flow->packet.l4_protocol == IPPROTO_UDP) - && NDPI_ISSET(&flow->excluded_protocol_bitmask, guessed_protocol_id) - && is_udp_guessable_protocol(guessed_protocol_id)) + && (flow->packet.l4_protocol == IPPROTO_UDP) && + NDPI_ISSET(&flow->excluded_protocol_bitmask, guessed_protocol_id) && + is_udp_guessable_protocol(guessed_protocol_id)) flow->guessed_protocol_id = guessed_protocol_id = NDPI_PROTOCOL_UNKNOWN; - if((guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) - || (guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN)) { - if((guessed_protocol_id == 0) - && (flow->protos.stun_ssl.stun.num_binding_requests > 0) - && (flow->protos.stun_ssl.stun.num_processed_pkts > 0)) + if((guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) || (guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN)) { + if((guessed_protocol_id == 0) && (flow->protos.stun_ssl.stun.num_binding_requests > 0) && + (flow->protos.stun_ssl.stun.num_processed_pkts > 0)) guessed_protocol_id = NDPI_PROTOCOL_STUN; if(flow->host_server_name[0] != '\0') { ndpi_protocol_match_result ret_match; - ndpi_match_host_subprotocol(ndpi_str, flow, - (char *)flow->host_server_name, - strlen((const char*)flow->host_server_name), - &ret_match, + memset(&ret_match, 0, sizeof(ret_match)); + + ndpi_match_host_subprotocol(ndpi_str, flow, (char *) flow->host_server_name, + strlen((const char *) flow->host_server_name), &ret_match, NDPI_PROTOCOL_DNS); if(ret_match.protocol_id != NDPI_PROTOCOL_UNKNOWN) guessed_host_protocol_id = ret_match.protocol_id; } - ndpi_int_change_protocol(ndpi_str, flow, - guessed_host_protocol_id, - guessed_protocol_id); + *protocol_was_guessed = 1; + ndpi_int_change_protocol(ndpi_str, flow, guessed_host_protocol_id, guessed_protocol_id); } } - } else { - flow->detected_protocol_stack[1] = flow->guessed_protocol_id, + } else if(enable_guess) { + if(flow->guessed_protocol_id != NDPI_PROTOCOL_UNKNOWN) { + *protocol_was_guessed = 1; + flow->detected_protocol_stack[1] = flow->guessed_protocol_id; + } + + if(flow->guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) { + *protocol_was_guessed = 1; flow->detected_protocol_stack[0] = flow->guessed_host_protocol_id; + } - if(flow->detected_protocol_stack[1] == flow->detected_protocol_stack[0]) + if(flow->detected_protocol_stack[1] == flow->detected_protocol_stack[0]) { + *protocol_was_guessed = 1; flow->detected_protocol_stack[1] = flow->guessed_host_protocol_id; + } } - if((flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) - && (flow->guessed_protocol_id == NDPI_PROTOCOL_STUN)) { + if((flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) && + (flow->guessed_protocol_id == NDPI_PROTOCOL_STUN)) { check_stun_export: if(flow->protos.stun_ssl.stun.num_processed_pkts || flow->protos.stun_ssl.stun.num_udp_pkts) { // if(/* (flow->protos.stun_ssl.stun.num_processed_pkts >= NDPI_MIN_NUM_STUN_DETECTION) */ - ndpi_set_detected_protocol(ndpi_str, flow, - flow->guessed_host_protocol_id, - NDPI_PROTOCOL_STUN); + *protocol_was_guessed = 1; + ndpi_set_detected_protocol(ndpi_str, flow, flow->guessed_host_protocol_id, NDPI_PROTOCOL_STUN); } } @@ -4221,51 +4208,34 @@ ndpi_protocol ndpi_detection_giveup(struct ndpi_detection_module_struct *ndpi_st } } - if(enable_guess - && (ret.app_protocol == NDPI_PROTOCOL_UNKNOWN) - && flow->packet.iph /* Guess only IPv4 */ - && (flow->packet.tcp || flow->packet.udp) - ) { - ret = ndpi_guess_undetected_protocol(ndpi_str, - flow, - flow->packet.l4_protocol, - ntohl(flow->packet.iph->saddr), - ntohs(flow->packet.udp ? flow->packet.udp->source : flow->packet.tcp->source), - ntohl(flow->packet.iph->daddr), - ntohs(flow->packet.udp ? flow->packet.udp->dest : flow->packet.tcp->dest) - ); + if(ret.app_protocol != NDPI_PROTOCOL_UNKNOWN) { *protocol_was_guessed = 1; + ndpi_fill_protocol_category(ndpi_str, flow, &ret); } - - ndpi_fill_protocol_category(ndpi_str, flow, &ret); return(ret); } /* ********************************************************************************* */ -void ndpi_process_extra_packet(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - const unsigned char *packet, - const unsigned short packetlen, - const u_int64_t current_tick_l, - struct ndpi_id_struct *src, - struct ndpi_id_struct *dst) { +void ndpi_process_extra_packet(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + const unsigned char *packet, const unsigned short packetlen, + const u_int64_t current_time_ms, struct ndpi_id_struct *src, struct ndpi_id_struct *dst) { if(flow == NULL) return; - if(flow->server_id == NULL) flow->server_id = dst; /* Default */ + if(flow->server_id == NULL) + flow->server_id = dst; /* Default */ /* need at least 20 bytes for ip header */ if(packetlen < 20) { return; } - flow->packet.tick_timestamp_l = current_tick_l; - flow->packet.tick_timestamp = (u_int32_t)(current_tick_l/ndpi_str->ticks_per_second); + flow->packet.current_time_ms = current_time_ms; /* parse packet */ - flow->packet.iph = (struct ndpi_iphdr *)packet; + flow->packet.iph = (struct ndpi_iphdr *) packet; /* we are interested in ipv4 packet */ /* set up the packet headers for the extra packet function to use if it wants */ @@ -4280,15 +4250,16 @@ void ndpi_process_extra_packet(struct ndpi_detection_module_struct *ndpi_str, if(flow->extra_packets_func) { if((flow->extra_packets_func(ndpi_str, flow)) == 0) flow->check_extra_packets = 0; - } - flow->num_extra_packets_checked++; + if(++flow->num_extra_packets_checked == flow->max_extra_packets_to_check) + flow->extra_packets_func = NULL; /* Enough packets detected */ + } } /* ********************************************************************************* */ -int ndpi_load_ip_category(struct ndpi_detection_module_struct *ndpi_str, - const char *ip_address_and_mask, ndpi_protocol_category_t category) { +int ndpi_load_ip_category(struct ndpi_detection_module_struct *ndpi_str, const char *ip_address_and_mask, + ndpi_protocol_category_t category) { patricia_node_t *node; struct in_addr pin; int bits = 32; @@ -4302,7 +4273,7 @@ int ndpi_load_ip_category(struct ndpi_detection_module_struct *ndpi_str, if(ptr) { *(ptr++) = '\0'; - if(atoi(ptr)>=0 && atoi(ptr)<=32) + if(atoi(ptr) >= 0 && atoi(ptr) <= 32) bits = atoi(ptr); } @@ -4311,16 +4282,18 @@ int ndpi_load_ip_category(struct ndpi_detection_module_struct *ndpi_str, return(-1); } - if((node = add_to_ptree(ndpi_str->custom_categories.ipAddresses_shadow, - AF_INET, &pin, bits)) != NULL) - node->value.user_value = (int)category; + if((node = add_to_ptree(ndpi_str->custom_categories.ipAddresses_shadow, AF_INET, &pin, bits)) != NULL) { + node->value.uv.user_value = (u_int16_t)category, node->value.uv.additional_user_value = 0; + } + return(0); } + /* ********************************************************************************* */ -int ndpi_load_hostname_category(struct ndpi_detection_module_struct *ndpi_str, - const char *name_to_add, ndpi_protocol_category_t category) { +int ndpi_load_hostname_category(struct ndpi_detection_module_struct *ndpi_str, const char *name_to_add, + ndpi_protocol_category_t category) { char *name; if(name_to_add == NULL) @@ -4335,38 +4308,27 @@ int ndpi_load_hostname_category(struct ndpi_detection_module_struct *ndpi_str, printf("===> %s() Loading %s as %u\n", __FUNCTION__, name, category); #endif -#ifdef HAVE_HYPERSCAN - { - struct hs_list *h = (struct hs_list*)ndpi_malloc(sizeof(struct hs_list)); + AC_PATTERN_t ac_pattern; + AC_ERROR_t rc; - if(h) { - h->expression = name, h->id = (unsigned int)category; - h->next = ndpi_str->custom_categories.to_load; - ndpi_str->custom_categories.to_load = h; - ndpi_str->custom_categories.num_to_load++; - } else { - free(name); - return(-1); - } - } -#else - AC_PATTERN_t ac_pattern; + memset(&ac_pattern, 0, sizeof(ac_pattern)); - memset(&ac_pattern, 0, sizeof(ac_pattern)); + if(ndpi_str->custom_categories.hostnames_shadow.ac_automa == NULL) { + free(name); + return(-1); + } - if(ndpi_str->custom_categories.hostnames_shadow.ac_automa == NULL) { - free(name); - return(-1); - } + ac_pattern.astring = name, ac_pattern.length = strlen(ac_pattern.astring); + ac_pattern.rep.number = (u_int32_t) category, ac_pattern.rep.category = category;; - ac_pattern.astring = name, ac_pattern.length = strlen(ac_pattern.astring); - ac_pattern.rep.number = (int)category; + rc = ac_automata_add(ndpi_str->custom_categories.hostnames_shadow.ac_automa, &ac_pattern); + if(rc != ACERR_DUPLICATE_PATTERN && rc != ACERR_SUCCESS) { + free(name); + return(-1); + } - if(ac_automata_add(ndpi_str->custom_categories.hostnames_shadow.ac_automa, &ac_pattern) != ACERR_SUCCESS) { - free(name); - return(-1); - } -#endif + if(rc == ACERR_DUPLICATE_PATTERN) + free(name); return(0); } @@ -4374,8 +4336,8 @@ int ndpi_load_hostname_category(struct ndpi_detection_module_struct *ndpi_str, /* ********************************************************************************* */ /* Loads an IP or name category */ -int ndpi_load_category(struct ndpi_detection_module_struct *ndpi_struct, - const char *ip_or_name, ndpi_protocol_category_t category) { +int ndpi_load_category(struct ndpi_detection_module_struct *ndpi_struct, const char *ip_or_name, + ndpi_protocol_category_t category) { int rv; /* Try to load as IP address first */ @@ -4395,89 +4357,24 @@ int ndpi_enable_loaded_categories(struct ndpi_detection_module_struct *ndpi_str) int i; /* First add the nDPI known categories matches */ - for(i=0; category_match[i].string_to_match != NULL; i++) + for (i = 0; category_match[i].string_to_match != NULL; i++) ndpi_load_category(ndpi_str, category_match[i].string_to_match, category_match[i].protocol_category); -#ifdef HAVE_HYPERSCAN - if(ndpi_str->custom_categories.num_to_load > 0) { - const char **expressions; - unsigned int *ids; - int rc; - struct hs_list *head = ndpi_str->custom_categories.to_load; - - expressions = (const char**)ndpi_calloc(sizeof(char*), - ndpi_str->custom_categories.num_to_load+1); - if(!expressions) return(-1); - - ids = (unsigned int*)ndpi_calloc(sizeof(unsigned int), - ndpi_str->custom_categories.num_to_load+1); - if(!ids) { - ndpi_free(expressions); - return(-1); - } - - for(i=0; head != NULL; i++) { -#ifdef DEBUG - printf("[HS] Loading category %u for %s\n", head->id, head->expression); -#endif - expressions[i] = head->expression, ids[i] = head->id; - head = head->next; - } - - if(i != ndpi_str->custom_categories.num_to_load){ - ndpi_free(expressions); - return(-1); - } - - free_hyperscan_memory(ndpi_str->custom_categories.hostnames); - ndpi_str->custom_categories.hostnames = (struct hs*)ndpi_malloc(sizeof(struct hs)); - - if(ndpi_str->custom_categories.hostnames == NULL) { - ndpi_free(expressions); - ndpi_free(ids); - return(-1); /* Failed */ - } - - rc = hyperscan_load_patterns(ndpi_str->custom_categories.hostnames, - ndpi_str->custom_categories.num_to_load, - expressions, ids); - ndpi_free(expressions), ndpi_free(ids); - - head = ndpi_str->custom_categories.to_load; - while(head != NULL) { - struct hs_list *next = head->next; - - ndpi_free(head->expression); - ndpi_free(head); - - head = next; - } - - ndpi_str->custom_categories.to_load = NULL; - ndpi_str->custom_categories.num_to_load = 0; - - if(rc < 0) { - ndpi_free(ndpi_str->custom_categories.hostnames); - ndpi_str->custom_categories.hostnames = NULL; - } - } -#else /* Free */ - ac_automata_release((AC_AUTOMATA_t*)ndpi_str->custom_categories.hostnames.ac_automa, 1 /* free patterns strings memory */); + ac_automata_release((AC_AUTOMATA_t *) ndpi_str->custom_categories.hostnames.ac_automa, + 1 /* free patterns strings memory */); /* Finalize */ - ac_automata_finalize((AC_AUTOMATA_t*)ndpi_str->custom_categories.hostnames_shadow.ac_automa); + ac_automata_finalize((AC_AUTOMATA_t *) ndpi_str->custom_categories.hostnames_shadow.ac_automa); /* Swap */ ndpi_str->custom_categories.hostnames.ac_automa = ndpi_str->custom_categories.hostnames_shadow.ac_automa; /* Realloc */ ndpi_str->custom_categories.hostnames_shadow.ac_automa = ac_automata_init(ac_match_handler); -#endif if(ndpi_str->custom_categories.ipAddresses != NULL) - ndpi_Destroy_Patricia((patricia_tree_t*)ndpi_str->custom_categories.ipAddresses, - free_ptree_data); + ndpi_Destroy_Patricia((patricia_tree_t *) ndpi_str->custom_categories.ipAddresses, free_ptree_data); ndpi_str->custom_categories.ipAddresses = ndpi_str->custom_categories.ipAddresses_shadow; ndpi_str->custom_categories.ipAddresses_shadow = ndpi_New_Patricia(32 /* IPv4 */); @@ -4489,10 +4386,8 @@ int ndpi_enable_loaded_categories(struct ndpi_detection_module_struct *ndpi_str) /* ********************************************************************************* */ -int ndpi_fill_ip_protocol_category(struct ndpi_detection_module_struct *ndpi_str, - u_int32_t saddr, - u_int32_t daddr, - ndpi_protocol *ret) { +int ndpi_fill_ip_protocol_category(struct ndpi_detection_module_struct *ndpi_str, u_int32_t saddr, u_int32_t daddr, + ndpi_protocol *ret) { if(ndpi_str->custom_categories.categories_loaded) { prefix_t prefix; patricia_node_t *node; @@ -4501,20 +4396,22 @@ int ndpi_fill_ip_protocol_category(struct ndpi_detection_module_struct *ndpi_str node = NULL; else { /* Make sure all in network byte order otherwise compares wont work */ - fill_prefix_v4(&prefix, (struct in_addr *)&saddr, - 32, ((patricia_tree_t*)ndpi_str->protocols_ptree)->maxbits); + fill_prefix_v4(&prefix, (struct in_addr *) &saddr, 32, + ((patricia_tree_t *) ndpi_str->protocols_ptree)->maxbits); node = ndpi_patricia_search_best(ndpi_str->custom_categories.ipAddresses, &prefix); } if(!node) { - if(daddr != 0) - fill_prefix_v4(&prefix, (struct in_addr *)&daddr, - 32, ((patricia_tree_t*)ndpi_str->protocols_ptree)->maxbits); - node = ndpi_patricia_search_best(ndpi_str->custom_categories.ipAddresses, &prefix); + if(daddr != 0) { + fill_prefix_v4(&prefix, (struct in_addr *) &daddr, 32, + ((patricia_tree_t *) ndpi_str->protocols_ptree)->maxbits); + node = ndpi_patricia_search_best(ndpi_str->custom_categories.ipAddresses, &prefix); + } } if(node) { - ret->category = (ndpi_protocol_category_t)node->value.user_value; + ret->category = (ndpi_protocol_category_t) node->value.uv.user_value; + return(1); } } @@ -4526,9 +4423,8 @@ int ndpi_fill_ip_protocol_category(struct ndpi_detection_module_struct *ndpi_str /* ********************************************************************************* */ -void ndpi_fill_protocol_category(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - ndpi_protocol *ret) { +void ndpi_fill_protocol_category(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + ndpi_protocol *ret) { if(ndpi_str->custom_categories.categories_loaded) { if(flow->guessed_header_category != NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) { flow->category = ret->category = flow->guessed_header_category; @@ -4536,25 +4432,24 @@ void ndpi_fill_protocol_category(struct ndpi_detection_module_struct *ndpi_str, } if(flow->host_server_name[0] != '\0') { - unsigned long id; - int rc = ndpi_match_custom_category(ndpi_str, (char *)flow->host_server_name, - strlen((char *)flow->host_server_name), &id); + u_int32_t id; + int rc = ndpi_match_custom_category(ndpi_str, (char *) flow->host_server_name, + strlen((char *) flow->host_server_name), &id); if(rc == 0) { - flow->category = ret->category = (ndpi_protocol_category_t)id; + flow->category = ret->category = (ndpi_protocol_category_t) id; return; } } - if((flow->l4.tcp.tls_seen_client_cert == 1) && (flow->protos.stun_ssl.ssl.client_certificate[0] != '\0')) { - unsigned long id; - int rc = ndpi_match_custom_category(ndpi_str, - (char *)flow->protos.stun_ssl.ssl.client_certificate, - strlen(flow->protos.stun_ssl.ssl.client_certificate), - &id); + if(flow->l4.tcp.tls.hello_processed == 1 && + flow->protos.stun_ssl.ssl.client_requested_server_name[0] != '\0') { + u_int32_t id; + int rc = ndpi_match_custom_category(ndpi_str, (char *) flow->protos.stun_ssl.ssl.client_requested_server_name, + strlen(flow->protos.stun_ssl.ssl.client_requested_server_name), &id); if(rc == 0) { - flow->category = ret->category = (ndpi_protocol_category_t)id; + flow->category = ret->category = (ndpi_protocol_category_t) id; return; } } @@ -4566,57 +4461,135 @@ void ndpi_fill_protocol_category(struct ndpi_detection_module_struct *ndpi_str, /* ********************************************************************************* */ static void ndpi_reset_packet_line_info(struct ndpi_packet_struct *packet) { - packet->parsed_lines = 0, - packet->empty_line_position_set = 0, - packet->host_line.ptr = NULL, - packet->host_line.len = 0, - packet->referer_line.ptr = NULL, - packet->referer_line.len = 0, - packet->content_line.ptr = NULL, - packet->content_line.len = 0, - packet->accept_line.ptr = NULL, - packet->accept_line.len = 0, - packet->user_agent_line.ptr = NULL, - packet->user_agent_line.len = 0, - packet->http_url_name.ptr = NULL, - packet->http_url_name.len = 0, - packet->http_encoding.ptr = NULL, - packet->http_encoding.len = 0, - packet->http_transfer_encoding.ptr = NULL, - packet->http_transfer_encoding.len = 0, - packet->http_contentlen.ptr = NULL, - packet->http_contentlen.len = 0, - packet->http_cookie.ptr = NULL, - packet->http_cookie.len = 0, - packet->http_origin.len = 0, - packet->http_origin.ptr = NULL, - packet->http_x_session_type.ptr = NULL, - packet->http_x_session_type.len = 0, - packet->server_line.ptr = NULL, - packet->server_line.len = 0, - packet->http_method.ptr = NULL, - packet->http_method.len = 0, - packet->http_response.ptr = NULL, - packet->http_response.len = 0, - packet->http_num_headers = 0; + packet->parsed_lines = 0, packet->empty_line_position_set = 0, packet->host_line.ptr = NULL, + packet->host_line.len = 0, packet->referer_line.ptr = NULL, packet->referer_line.len = 0, + packet->content_line.ptr = NULL, packet->content_line.len = 0, packet->accept_line.ptr = NULL, + packet->accept_line.len = 0, packet->user_agent_line.ptr = NULL, packet->user_agent_line.len = 0, + packet->http_url_name.ptr = NULL, packet->http_url_name.len = 0, packet->http_encoding.ptr = NULL, + packet->http_encoding.len = 0, packet->http_transfer_encoding.ptr = NULL, packet->http_transfer_encoding.len = 0, + packet->http_contentlen.ptr = NULL, packet->http_contentlen.len = 0, packet->content_disposition_line.ptr = NULL, + packet->content_disposition_line.len = 0, packet->http_cookie.ptr = NULL, + packet->http_cookie.len = 0, packet->http_origin.len = 0, packet->http_origin.ptr = NULL, + packet->http_x_session_type.ptr = NULL, packet->http_x_session_type.len = 0, packet->server_line.ptr = NULL, + packet->server_line.len = 0, packet->http_method.ptr = NULL, packet->http_method.len = 0, + packet->http_response.ptr = NULL, packet->http_response.len = 0, packet->http_num_headers = 0, + packet->forwarded_line.ptr = NULL, packet->forwarded_line.len = 0; +} + +/* ********************************************************************************* */ + +static int ndpi_check_protocol_port_mismatch_exceptions(struct ndpi_detection_module_struct *ndpi_str, + struct ndpi_flow_struct *flow, + ndpi_default_ports_tree_node_t *expected_proto, + ndpi_protocol *returned_proto) { + /* + For TLS (and other protocols) it is not simple to guess the exact protocol so before + triggering an alert we need to make sure what we have exhausted all the possible + options available + */ + + if(returned_proto->master_protocol == NDPI_PROTOCOL_TLS) { + switch(expected_proto->proto->protoId) { + case NDPI_PROTOCOL_MAIL_IMAPS: + case NDPI_PROTOCOL_MAIL_POPS: + case NDPI_PROTOCOL_MAIL_SMTPS: + return(1); /* This is a reasonable exception */ + break; + } + } + + return(0); +} + +/* ********************************************************************************* */ + +static void ndpi_reconcile_protocols(struct ndpi_detection_module_struct *ndpi_str, + struct ndpi_flow_struct *flow, + ndpi_protocol *ret) { + /* + Skype for a host doing MS Teams means MS Teams + (MS Teams uses Skype as transport protocol for voice/video) + */ + if(flow) { + /* Do not go for DNS when there is an application protocol. Example DNS.Apple */ + if((flow->detected_protocol_stack[1] != NDPI_PROTOCOL_UNKNOWN) + && (flow->detected_protocol_stack[0] /* app */ != flow->detected_protocol_stack[1] /* major */)) + NDPI_CLR_BIT(flow->risk, NDPI_SUSPICIOUS_DGA_DOMAIN); + } + + switch(ret->app_protocol) { + case NDPI_PROTOCOL_MSTEAMS: + if(flow->packet.iph && flow->packet.tcp) { + // printf("====>> NDPI_PROTOCOL_MSTEAMS\n"); + + if(ndpi_str->msteams_cache == NULL) + ndpi_str->msteams_cache = ndpi_lru_cache_init(1024); + + if(ndpi_str->msteams_cache) + ndpi_lru_add_to_cache(ndpi_str->msteams_cache, + flow->packet.iph->saddr, + (flow->packet.current_time_ms / 1000) & 0xFFFF /* 16 bit */); + } + break; + + case NDPI_PROTOCOL_SKYPE: + case NDPI_PROTOCOL_SKYPE_CALL: + if(flow->packet.iph + && flow->packet.udp + && ndpi_str->msteams_cache) { + u_int16_t when; + + if(ndpi_lru_find_cache(ndpi_str->msteams_cache, flow->packet.iph->saddr, + &when, 0 /* Don't remove it as it can be used for other connections */)) { + u_int16_t tdiff = ((flow->packet.current_time_ms /1000) & 0xFFFF) - when; + + if(tdiff < 60 /* sec */) { + // printf("====>> NDPI_PROTOCOL_SKYPE(_CALL) -> NDPI_PROTOCOL_MSTEAMS [%u]\n", tdiff); + ret->app_protocol = NDPI_PROTOCOL_MSTEAMS; + + /* Refresh cache */ + ndpi_lru_add_to_cache(ndpi_str->msteams_cache, + flow->packet.iph->saddr, + (flow->packet.current_time_ms / 1000) & 0xFFFF /* 16 bit */); + } + } + } + break; + } /* switch */ + + if(flow) { + switch(ndpi_get_proto_breed(ndpi_str, ret->app_protocol)) { + case NDPI_PROTOCOL_UNSAFE: + case NDPI_PROTOCOL_POTENTIALLY_DANGEROUS: + case NDPI_PROTOCOL_DANGEROUS: + NDPI_SET_BIT(flow->risk, NDPI_UNSAFE_PROTOCOL); + break; + default: + /* Nothing to do */ + break; + } + } + } /* ********************************************************************************* */ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - const unsigned char *packet, - const unsigned short packetlen, - const u_int64_t current_tick_l, - struct ndpi_id_struct *src, - struct ndpi_id_struct *dst) { + struct ndpi_flow_struct *flow, const unsigned char *packet, + const unsigned short packetlen, const u_int64_t current_time_ms, + struct ndpi_id_struct *src, struct ndpi_id_struct *dst) { NDPI_SELECTION_BITMASK_PROTOCOL_SIZE ndpi_selection_packet; - u_int32_t a; + u_int32_t a, num_calls = 0; ndpi_protocol ret = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED }; - + + if(flow->fail_with_unknown) { + // printf("%s(): FAIL_WITH_UNKNOWN\n", __FUNCTION__); + return(ret); + } + if(ndpi_str->ndpi_log_level >= NDPI_LOG_TRACE) - NDPI_LOG(flow ? flow->detected_protocol_stack[0]:NDPI_PROTOCOL_UNKNOWN, - ndpi_str, NDPI_LOG_TRACE, "START packet processing\n"); + NDPI_LOG(flow ? flow->detected_protocol_stack[0] : NDPI_PROTOCOL_UNKNOWN, ndpi_str, NDPI_LOG_TRACE, + "START packet processing\n"); if(flow == NULL) return(ret); @@ -4626,22 +4599,20 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct flow->num_processed_pkts++; /* Init default */ - ret.master_protocol = flow->detected_protocol_stack[1], ret.app_protocol = flow->detected_protocol_stack[0]; + ret.master_protocol = flow->detected_protocol_stack[1], + ret.app_protocol = flow->detected_protocol_stack[0]; - if(flow->server_id == NULL) flow->server_id = dst; /* Default */ - if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) { - /* - With SSL we might want to dissect further packets to decode - the certificate type for instance - */ - if(flow->check_extra_packets - /* - && (flow->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS) - */ - ) { - ndpi_process_extra_packet(ndpi_str, flow, packet, packetlen, current_tick_l, src, dst); + if(flow->server_id == NULL) + flow->server_id = dst; /* Default */ - return(ret); + if(flow->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) { + if(flow->check_extra_packets) { + ndpi_process_extra_packet(ndpi_str, flow, packet, packetlen, current_time_ms, src, dst); + /* Update in case of new match */ + ret.master_protocol = flow->detected_protocol_stack[1], + ret.app_protocol = flow->detected_protocol_stack[0], + ret.category = flow->category; + goto invalidate_ptr; } else goto ret_protocols; } @@ -4653,11 +4624,10 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct goto invalidate_ptr; } - flow->packet.tick_timestamp_l = current_tick_l; - flow->packet.tick_timestamp = (u_int32_t)(current_tick_l/ndpi_str->ticks_per_second); + flow->packet.current_time_ms = current_time_ms; /* parse packet */ - flow->packet.iph = (struct ndpi_iphdr *)packet; + flow->packet.iph = (struct ndpi_iphdr *) packet; /* we are interested in ipv4 packet */ if(ndpi_init_packet_header(ndpi_str, flow, packetlen) != 0) @@ -4690,14 +4660,13 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct #ifdef NDPI_DETECTION_SUPPORT_IPV6 if(flow->packet.iphv6 != NULL) ndpi_selection_packet |= NDPI_SELECTION_BITMASK_PROTOCOL_IPV6 | NDPI_SELECTION_BITMASK_PROTOCOL_IPV4_OR_IPV6; -#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ +#endif /* NDPI_DETECTION_SUPPORT_IPV6 */ - if((!flow->protocol_id_already_guessed) - && ( + if((!flow->protocol_id_already_guessed) && ( #ifdef NDPI_DETECTION_SUPPORT_IPV6 - flow->packet.iphv6 || + flow->packet.iphv6 || #endif - flow->packet.iph)) { + flow->packet.iph)) { u_int16_t sport, dport; u_int8_t protocol; u_int8_t user_defined_proto; @@ -4713,12 +4682,16 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct protocol = flow->packet.iph->protocol; } - if(flow->packet.udp) sport = ntohs(flow->packet.udp->source), dport = ntohs(flow->packet.udp->dest); - else if(flow->packet.tcp) sport = ntohs(flow->packet.tcp->source), dport = ntohs(flow->packet.tcp->dest); - else sport = dport = 0; + if(flow->packet.udp) + sport = ntohs(flow->packet.udp->source), dport = ntohs(flow->packet.udp->dest); + else if(flow->packet.tcp) + sport = ntohs(flow->packet.tcp->source), dport = ntohs(flow->packet.tcp->dest); + else + sport = dport = 0; /* guess protocol */ - flow->guessed_protocol_id = (int16_t)ndpi_guess_protocol_id(ndpi_str, flow, protocol, sport, dport, &user_defined_proto); + flow->guessed_protocol_id = + (int16_t) ndpi_guess_protocol_id(ndpi_str, flow, protocol, sport, dport, &user_defined_proto); flow->guessed_host_protocol_id = ndpi_guess_host_protocol_id(ndpi_str, flow); if(ndpi_str->custom_categories.categories_loaded && flow->packet.iph) { @@ -4727,7 +4700,7 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct } else flow->guessed_header_category = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED; - if(flow->guessed_protocol_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS-1)) { + if(flow->guessed_protocol_id >= NDPI_MAX_SUPPORTED_PROTOCOLS) { /* This is a custom protocol and it has priority over everything else */ ret.master_protocol = NDPI_PROTOCOL_UNKNOWN, ret.app_protocol = flow->guessed_protocol_id ? flow->guessed_protocol_id : flow->guessed_host_protocol_id; @@ -4739,7 +4712,7 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct if(flow->packet.iph) { if(flow->guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) { u_int8_t protocol_was_guessed; - + /* ret.master_protocol = flow->guessed_protocol_id , ret.app_protocol = flow->guessed_host_protocol_id; /\* ****** *\/ */ ret = ndpi_detection_giveup(ndpi_str, flow, 0, &protocol_was_guessed); } @@ -4750,51 +4723,42 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct } else { /* guess host protocol */ if(flow->packet.iph) { - struct in_addr addr; - addr.s_addr = flow->packet.iph->saddr; - flow->guessed_host_protocol_id = ndpi_network_ptree_match(ndpi_str, &addr); + flow->guessed_host_protocol_id = ndpi_guess_host_protocol_id(ndpi_str, flow); + + /* + We could implement a shortcut here skipping dissectors for + protocols we have identified by other means such as with the IP - if(flow->guessed_host_protocol_id == NDPI_PROTOCOL_UNKNOWN) { - addr.s_addr = flow->packet.iph->daddr; - flow->guessed_host_protocol_id = ndpi_network_ptree_match(ndpi_str, &addr); + However we do NOT stop here and skip invoking the dissectors + because we want to dissect the flow (e.g. dissect the TLS) + and extract metadata. + */ +#if SKIP_INVOKING_THE_DISSECTORS + if(flow->guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) { + /* + We have identified a protocol using the IP address so + it is not worth to dissect the traffic as we already have + the solution + */ + ret.master_protocol = flow->guessed_protocol_id, ret.app_protocol = flow->guessed_host_protocol_id; } +#endif } } } - if(flow->guessed_host_protocol_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS-1)) { + if(flow->guessed_host_protocol_id >= NDPI_MAX_SUPPORTED_PROTOCOLS) { /* This is a custom protocol and it has priority over everything else */ - ret.master_protocol = NDPI_PROTOCOL_UNKNOWN, ret.app_protocol = flow->guessed_host_protocol_id; + ret.master_protocol = flow->guessed_protocol_id, ret.app_protocol = flow->guessed_host_protocol_id; - if(flow->packet.tcp && (ret.master_protocol == NDPI_PROTOCOL_UNKNOWN)) { - /* Minimal guess for HTTP/SSL-based protocols */ - int i; - - for(i=0; i<2; i++) { - u_int16_t port = (i == 0) ? ntohs(flow->packet.tcp->dest) : ntohs(flow->packet.tcp->source); - - switch(port) { - case 80: - ret.master_protocol = NDPI_PROTOCOL_HTTP; - break; - case 443: - ret.master_protocol = NDPI_PROTOCOL_TLS; /* QUIC could also match */ - break; - } - - if(ret.master_protocol != NDPI_PROTOCOL_UNKNOWN) - break; - } - } - - ndpi_check_flow_func(ndpi_str, flow, &ndpi_selection_packet); + num_calls = ndpi_check_flow_func(ndpi_str, flow, &ndpi_selection_packet); ndpi_fill_protocol_category(ndpi_str, flow, &ret); goto invalidate_ptr; } - ndpi_check_flow_func(ndpi_str, flow, &ndpi_selection_packet); - + num_calls = ndpi_check_flow_func(ndpi_str, flow, &ndpi_selection_packet); + a = flow->packet.detected_protocol_stack[0]; if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(ndpi_str->detection_bitmask, a) == 0) a = NDPI_PROTOCOL_UNKNOWN; @@ -4802,11 +4766,11 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct if(a != NDPI_PROTOCOL_UNKNOWN) { int i; - for(i=0; ihost_server_name); i++) { + for (i = 0; i < sizeof(flow->host_server_name); i++) { if(flow->host_server_name[i] != '\0') flow->host_server_name[i] = tolower(flow->host_server_name[i]); else { - flow->host_server_name[i] ='\0'; + flow->host_server_name[i] = '\0'; break; } } @@ -4821,21 +4785,17 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct } else ret.app_protocol = flow->detected_protocol_stack[0]; - /* Don;t overwrite the category if already set */ - if(flow->category == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) + /* Don't overwrite the category if already set */ + if((flow->category == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) && (ret.app_protocol != NDPI_PROTOCOL_UNKNOWN)) ndpi_fill_protocol_category(ndpi_str, flow, &ret); else ret.category = flow->category; - if((flow->num_processed_pkts == 1) - && (ret.master_protocol == NDPI_PROTOCOL_UNKNOWN) - && (ret.app_protocol == NDPI_PROTOCOL_UNKNOWN) - && flow->packet.tcp - && (flow->packet.tcp->syn == 0) - && (flow->guessed_protocol_id == 0) - ) { + if((flow->num_processed_pkts == 1) && (ret.master_protocol == NDPI_PROTOCOL_UNKNOWN) && + (ret.app_protocol == NDPI_PROTOCOL_UNKNOWN) && flow->packet.tcp && (flow->packet.tcp->syn == 0) && + (flow->guessed_protocol_id == 0)) { u_int8_t protocol_was_guessed; - + /* This is a TCP flow - whose first packet is NOT a SYN @@ -4847,10 +4807,77 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct ret = ndpi_detection_giveup(ndpi_str, flow, 0, &protocol_was_guessed); } + if((ret.master_protocol == NDPI_PROTOCOL_UNKNOWN) && (ret.app_protocol != NDPI_PROTOCOL_UNKNOWN) && + (flow->guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN)) { + ret.master_protocol = ret.app_protocol; + ret.app_protocol = flow->guessed_host_protocol_id; + } + + if((!flow->risk_checked) && (ret.master_protocol != NDPI_PROTOCOL_UNKNOWN)) { + ndpi_default_ports_tree_node_t *found; + u_int16_t *default_ports, sport, dport; + + if(flow->packet.udp) + found = ndpi_get_guessed_protocol_id(ndpi_str, IPPROTO_UDP, + sport = ntohs(flow->packet.udp->source), + dport = ntohs(flow->packet.udp->dest)), + default_ports = ndpi_str->proto_defaults[ret.master_protocol].udp_default_ports; + else if(flow->packet.tcp) + found = ndpi_get_guessed_protocol_id(ndpi_str, IPPROTO_TCP, + sport = ntohs(flow->packet.tcp->source), + dport = ntohs(flow->packet.tcp->dest)), + default_ports = ndpi_str->proto_defaults[ret.master_protocol].tcp_default_ports; + else + found = NULL, default_ports = NULL; + + if(found + && (found->proto->protoId != NDPI_PROTOCOL_UNKNOWN) + && (found->proto->protoId != ret.master_protocol) + && (found->proto->protoId != ret.app_protocol) + ) { + // printf("******** %u / %u\n", found->proto->protoId, ret.master_protocol); + + if(!ndpi_check_protocol_port_mismatch_exceptions(ndpi_str, flow, found, &ret)) + NDPI_SET_BIT(flow->risk, NDPI_KNOWN_PROTOCOL_ON_NON_STANDARD_PORT); + } else if(default_ports && (default_ports[0] != 0)) { + u_int8_t found = 0, i, num_loops = 0; + + check_default_ports: + for(i=0; (ipacket.udp) + default_ports = ndpi_str->proto_defaults[ret.app_protocol].udp_default_ports; + else + default_ports = ndpi_str->proto_defaults[ret.app_protocol].tcp_default_ports; + + num_loops = 1; + goto check_default_ports; + } + + if(!found) { + // printf("******** Invalid default port\n"); + NDPI_SET_BIT(flow->risk, NDPI_KNOWN_PROTOCOL_ON_NON_STANDARD_PORT); + } + } + + flow->risk_checked = 1; + } + + ndpi_reconcile_protocols(ndpi_str, flow, &ret); + + if(num_calls == 0) + flow->fail_with_unknown = 1; + invalidate_ptr: /* - Invalidate packet memory to avoid accessing the pointers below - when the packet is no longer accessible + Invalidate packet memory to avoid accessing the pointers below + when the packet is no longer accessible */ flow->packet.iph = NULL, flow->packet.tcp = NULL, flow->packet.udp = NULL, flow->packet.payload = NULL; ndpi_reset_packet_line_info(&flow->packet); @@ -4860,13 +4887,12 @@ ndpi_protocol ndpi_detection_process_packet(struct ndpi_detection_module_struct /* ********************************************************************************* */ -u_int32_t ndpi_bytestream_to_number(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read) -{ +u_int32_t ndpi_bytestream_to_number(const u_int8_t *str, u_int16_t max_chars_to_read, u_int16_t *bytes_read) { u_int32_t val; val = 0; // cancel if eof, ' ' or line end chars are reached - while(*str >= '0' && *str <= '9' && max_chars_to_read > 0) { + while (*str >= '0' && *str <= '9' && max_chars_to_read > 0) { val *= 10; val += *str - '0'; str++; @@ -4880,8 +4906,7 @@ u_int32_t ndpi_bytestream_to_number(const u_int8_t * str, u_int16_t max_chars_to /* ********************************************************************************* */ #ifdef CODE_UNUSED -u_int32_t ndpi_bytestream_dec_or_hex_to_number(const u_int8_t * str, u_int16_t max_chars_to_read, - u_int16_t * bytes_read) { +u_int32_t ndpi_bytestream_dec_or_hex_to_number(const u_int8_t *str, u_int16_t max_chars_to_read, u_int16_t *bytes_read) { u_int32_t val; val = 0; if(max_chars_to_read <= 2 || str[0] != '0' || str[1] != 'x') { @@ -4892,7 +4917,7 @@ u_int32_t ndpi_bytestream_dec_or_hex_to_number(const u_int8_t * str, u_int16_t m max_chars_to_read -= 2; *bytes_read = *bytes_read + 2; - while(max_chars_to_read > 0) { + while (max_chars_to_read > 0) { if(*str >= '0' && *str <= '9') { val *= 16; val += *str - '0'; @@ -4918,12 +4943,11 @@ u_int32_t ndpi_bytestream_dec_or_hex_to_number(const u_int8_t * str, u_int16_t m /* ********************************************************************************* */ -u_int64_t ndpi_bytestream_to_number64(const u_int8_t * str, u_int16_t max_chars_to_read, - u_int16_t * bytes_read) { +u_int64_t ndpi_bytestream_to_number64(const u_int8_t *str, u_int16_t max_chars_to_read, u_int16_t *bytes_read) { u_int64_t val; val = 0; // cancel if eof, ' ' or line end chars are reached - while(max_chars_to_read > 0 && *str >= '0' && *str <= '9') { + while (max_chars_to_read > 0 && *str >= '0' && *str <= '9') { val *= 10; val += *str - '0'; str++; @@ -4935,8 +4959,8 @@ u_int64_t ndpi_bytestream_to_number64(const u_int8_t * str, u_int16_t max_chars_ /* ********************************************************************************* */ -u_int64_t ndpi_bytestream_dec_or_hex_to_number64(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read) -{ +u_int64_t ndpi_bytestream_dec_or_hex_to_number64(const u_int8_t *str, u_int16_t max_chars_to_read, + u_int16_t *bytes_read) { u_int64_t val; val = 0; if(max_chars_to_read <= 2 || str[0] != '0' || str[1] != 'x') { @@ -4946,8 +4970,7 @@ u_int64_t ndpi_bytestream_dec_or_hex_to_number64(const u_int8_t * str, u_int16_t str += 2; max_chars_to_read -= 2; *bytes_read = *bytes_read + 2; - while(max_chars_to_read > 0) { - + while (max_chars_to_read > 0) { if(*str >= '0' && *str <= '9') { val *= 16; val += *str - '0'; @@ -4970,35 +4993,39 @@ u_int64_t ndpi_bytestream_dec_or_hex_to_number64(const u_int8_t * str, u_int16_t /* ********************************************************************************* */ -u_int32_t ndpi_bytestream_to_ipv4(const u_int8_t * str, u_int16_t max_chars_to_read, u_int16_t * bytes_read) -{ +u_int32_t ndpi_bytestream_to_ipv4(const u_int8_t *str, u_int16_t max_chars_to_read, u_int16_t *bytes_read) { u_int32_t val; u_int16_t read = 0; u_int16_t oldread; u_int32_t c; + /* ip address must be X.X.X.X with each X between 0 and 255 */ oldread = read; c = ndpi_bytestream_to_number(str, max_chars_to_read, &read); if(c > 255 || oldread == read || max_chars_to_read == read || str[read] != '.') return(0); + read++; val = c << 24; oldread = read; c = ndpi_bytestream_to_number(&str[read], max_chars_to_read - read, &read); if(c > 255 || oldread == read || max_chars_to_read == read || str[read] != '.') return(0); + read++; val = val + (c << 16); oldread = read; c = ndpi_bytestream_to_number(&str[read], max_chars_to_read - read, &read); if(c > 255 || oldread == read || max_chars_to_read == read || str[read] != '.') return(0); + read++; val = val + (c << 8); oldread = read; c = ndpi_bytestream_to_number(&str[read], max_chars_to_read - read, &read); if(c > 255 || oldread == read || max_chars_to_read == read) return(0); + val = val + c; *bytes_read = *bytes_read + read; @@ -5009,36 +5036,48 @@ u_int32_t ndpi_bytestream_to_ipv4(const u_int8_t * str, u_int16_t max_chars_to_r /* ********************************************************************************* */ /* internal function for every detection to parse one packet and to increase the info buffer */ -void ndpi_parse_packet_line_info(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow) -{ +void ndpi_parse_packet_line_info(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow) { u_int32_t a; struct ndpi_packet_struct *packet = &flow->packet; + if((packet->payload_packet_len < 3) || (packet->payload == NULL)) + return; + if(packet->packet_lines_parsed_complete != 0) return; packet->packet_lines_parsed_complete = 1; ndpi_reset_packet_line_info(packet); - if((packet->payload_packet_len < 3) - || (packet->payload == NULL)) - return; - packet->line[packet->parsed_lines].ptr = packet->payload; packet->line[packet->parsed_lines].len = 0; - for(a = 0; (a < packet->payload_packet_len) - && (packet->parsed_lines < NDPI_MAX_PARSE_LINES_PER_PACKET); a++) { - if((a + 1) == packet->payload_packet_len) - return; /* Return if only one byte remains (prevent invalid reads past end-of-buffer) */ + for (a = 0; ((a+1) < packet->payload_packet_len) && (packet->parsed_lines < NDPI_MAX_PARSE_LINES_PER_PACKET); a++) { + if((packet->payload[a] == 0x0d) && (packet->payload[a+1] == 0x0a)) { + /* If end of line char sequence CR+NL "\r\n", process line */ + + if(((a + 3) < packet->payload_packet_len) + && (packet->payload[a+2] == 0x0d) + && (packet->payload[a+3] == 0x0a)) { + /* \r\n\r\n */ + int diff; /* No unsigned ! */ + u_int32_t a1 = a + 4; + + diff = packet->payload_packet_len - a1; + + if(diff > 0) { + diff = ndpi_min(diff, sizeof(flow->initial_binary_bytes)); + memcpy(&flow->initial_binary_bytes, &packet->payload[a1], diff); + flow->initial_binary_bytes_len = diff; + } + } - if(get_u_int16_t(packet->payload, a) == ntohs(0x0d0a)) { /* If end of line char sequence CR+NL "\r\n", process line */ - packet->line[packet->parsed_lines].len = (u_int16_t)(((unsigned long) &packet->payload[a]) - ((unsigned long) packet->line[packet->parsed_lines].ptr)); + packet->line[packet->parsed_lines].len = + (u_int16_t)(((size_t) &packet->payload[a]) - ((size_t) packet->line[packet->parsed_lines].ptr)); /* First line of a HTTP response parsing. Expected a "HTTP/1.? ???" */ if(packet->parsed_lines == 0 && packet->line[0].len >= NDPI_STATICSTRING_LEN("HTTP/1.X 200 ") && - strncasecmp((const char *)packet->line[0].ptr, "HTTP/1.", NDPI_STATICSTRING_LEN("HTTP/1.")) == 0 && + strncasecmp((const char *) packet->line[0].ptr, "HTTP/1.", NDPI_STATICSTRING_LEN("HTTP/1.")) == 0 && packet->line[0].ptr[NDPI_STATICSTRING_LEN("HTTP/1.X ")] > '0' && /* response code between 000 and 699 */ packet->line[0].ptr[NDPI_STATICSTRING_LEN("HTTP/1.X ")] < '6') { packet->http_response.ptr = &packet->line[0].ptr[NDPI_STATICSTRING_LEN("HTTP/1.1 ")]; @@ -5050,7 +5089,7 @@ void ndpi_parse_packet_line_info(struct ndpi_detection_module_struct *ndpi_str, char buf[4]; /* Set server HTTP response code */ - strncpy(buf, (char*)&packet->payload[9], 3); + strncpy(buf, (char *) &packet->payload[9], 3); buf[3] = '\0'; flow->http.response_status_code = atoi(buf); @@ -5061,126 +5100,151 @@ void ndpi_parse_packet_line_info(struct ndpi_detection_module_struct *ndpi_str, } /* "Server:" header line in HTTP response */ - if(packet->line[packet->parsed_lines].len > NDPI_STATICSTRING_LEN("Server:") + 1 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Server:", NDPI_STATICSTRING_LEN("Server:")) == 0) { + if(packet->line[packet->parsed_lines].len > NDPI_STATICSTRING_LEN("Server:") + 1 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, + "Server:", NDPI_STATICSTRING_LEN("Server:")) == 0) { // some stupid clients omit a space and place the servername directly after the colon - if(packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:")] == ' ') { - packet->server_line.ptr = - &packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:") + 1]; - packet->server_line.len = - packet->line[packet->parsed_lines].len - (NDPI_STATICSTRING_LEN("Server:") + 1); - } else { - packet->server_line.ptr = &packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:")]; - packet->server_line.len = packet->line[packet->parsed_lines].len - NDPI_STATICSTRING_LEN("Server:"); - } - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:")] == ' ') { + packet->server_line.ptr = + &packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:") + 1]; + packet->server_line.len = + packet->line[packet->parsed_lines].len - (NDPI_STATICSTRING_LEN("Server:") + 1); + } else { + packet->server_line.ptr = &packet->line[packet->parsed_lines].ptr[NDPI_STATICSTRING_LEN("Server:")]; + packet->server_line.len = packet->line[packet->parsed_lines].len - NDPI_STATICSTRING_LEN("Server:"); + } + packet->http_num_headers++; } /* "Host:" header line in HTTP request */ - if(packet->line[packet->parsed_lines].len > 6 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, - "Host:", 5) == 0) { - // some stupid clients omit a space and place the hostname directly after the colon - if(packet->line[packet->parsed_lines].ptr[5] == ' ') { - packet->host_line.ptr = &packet->line[packet->parsed_lines].ptr[6]; - packet->host_line.len = packet->line[packet->parsed_lines].len - 6; - } else { - packet->host_line.ptr = &packet->line[packet->parsed_lines].ptr[5]; - packet->host_line.len = packet->line[packet->parsed_lines].len - 5; - } - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 6 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Host:", 5) == 0) { + // some stupid clients omit a space and place the hostname directly after the colon + if(packet->line[packet->parsed_lines].ptr[5] == ' ') { + packet->host_line.ptr = &packet->line[packet->parsed_lines].ptr[6]; + packet->host_line.len = packet->line[packet->parsed_lines].len - 6; + } else { + packet->host_line.ptr = &packet->line[packet->parsed_lines].ptr[5]; + packet->host_line.len = packet->line[packet->parsed_lines].len - 5; + } + packet->http_num_headers++; } /* "X-Forwarded-For:" header line in HTTP request. Commonly used for HTTP proxies. */ - if(packet->line[packet->parsed_lines].len > 17 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "X-Forwarded-For:", 16) == 0) { - // some stupid clients omit a space and place the hostname directly after the colon - if(packet->line[packet->parsed_lines].ptr[16] == ' ') { - packet->forwarded_line.ptr = &packet->line[packet->parsed_lines].ptr[17]; - packet->forwarded_line.len = packet->line[packet->parsed_lines].len - 17; - } else { - packet->forwarded_line.ptr = &packet->line[packet->parsed_lines].ptr[16]; - packet->forwarded_line.len = packet->line[packet->parsed_lines].len - 16; - } - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 17 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "X-Forwarded-For:", 16) == 0) { + // some stupid clients omit a space and place the hostname directly after the colon + if(packet->line[packet->parsed_lines].ptr[16] == ' ') { + packet->forwarded_line.ptr = &packet->line[packet->parsed_lines].ptr[17]; + packet->forwarded_line.len = packet->line[packet->parsed_lines].len - 17; + } else { + packet->forwarded_line.ptr = &packet->line[packet->parsed_lines].ptr[16]; + packet->forwarded_line.len = packet->line[packet->parsed_lines].len - 16; + } + packet->http_num_headers++; } /* "Content-Type:" header line in HTTP. */ - if(packet->line[packet->parsed_lines].len > 14 - && (strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-Type: ", 14) == 0 - || strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-type: ", 14) == 0)) { - packet->content_line.ptr = &packet->line[packet->parsed_lines].ptr[14]; - packet->content_line.len = packet->line[packet->parsed_lines].len - 14; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 14 && + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Content-Type: ", 14) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Content-type: ", 14) == 0)) { + packet->content_line.ptr = &packet->line[packet->parsed_lines].ptr[14]; + packet->content_line.len = packet->line[packet->parsed_lines].len - 14; + + while ((packet->content_line.len > 0) && (packet->content_line.ptr[0] == ' ')) + packet->content_line.len--, packet->content_line.ptr++; + + packet->http_num_headers++; } /* "Content-Type:" header line in HTTP AGAIN. Probably a bogus response without space after ":" */ - if(packet->line[packet->parsed_lines].len > 13 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-type:", 13) == 0) { - packet->content_line.ptr = &packet->line[packet->parsed_lines].ptr[13]; - packet->content_line.len = packet->line[packet->parsed_lines].len - 13; - packet->http_num_headers++; + if((packet->content_line.len == 0) && (packet->line[packet->parsed_lines].len > 13) && + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Content-type:", 13) == 0)) { + packet->content_line.ptr = &packet->line[packet->parsed_lines].ptr[13]; + packet->content_line.len = packet->line[packet->parsed_lines].len - 13; + packet->http_num_headers++; } + + if(packet->content_line.len > 0) { + /* application/json; charset=utf-8 */ + char separator[] = {';', '\r', '\0'}; + int i; + + for (i = 0; separator[i] != '\0'; i++) { + char *c = memchr((char *) packet->content_line.ptr, separator[i], packet->content_line.len); + + if(c != NULL) + packet->content_line.len = c - (char *) packet->content_line.ptr; + } + } + /* "Accept:" header line in HTTP request. */ - if(packet->line[packet->parsed_lines].len > 8 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept: ", 8) == 0) { - packet->accept_line.ptr = &packet->line[packet->parsed_lines].ptr[8]; - packet->accept_line.len = packet->line[packet->parsed_lines].len - 8; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 8 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Accept: ", 8) == 0) { + packet->accept_line.ptr = &packet->line[packet->parsed_lines].ptr[8]; + packet->accept_line.len = packet->line[packet->parsed_lines].len - 8; + packet->http_num_headers++; } /* "Referer:" header line in HTTP request. */ - if(packet->line[packet->parsed_lines].len > 9 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Referer: ", 9) == 0) { - packet->referer_line.ptr = &packet->line[packet->parsed_lines].ptr[9]; - packet->referer_line.len = packet->line[packet->parsed_lines].len - 9; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 9 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Referer: ", 9) == 0) { + packet->referer_line.ptr = &packet->line[packet->parsed_lines].ptr[9]; + packet->referer_line.len = packet->line[packet->parsed_lines].len - 9; + packet->http_num_headers++; } /* "User-Agent:" header line in HTTP request. */ - if(packet->line[packet->parsed_lines].len > 12 - && (strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "User-Agent: ", 12) == 0 - || strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "User-agent: ", 12) == 0)) { - packet->user_agent_line.ptr = &packet->line[packet->parsed_lines].ptr[12]; - packet->user_agent_line.len = packet->line[packet->parsed_lines].len - 12; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 12 && + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "User-Agent: ", 12) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "User-agent: ", 12) == 0)) { + packet->user_agent_line.ptr = &packet->line[packet->parsed_lines].ptr[12]; + packet->user_agent_line.len = packet->line[packet->parsed_lines].len - 12; + packet->http_num_headers++; } /* "Content-Encoding:" header line in HTTP response (and request?). */ - if(packet->line[packet->parsed_lines].len > 18 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-Encoding: ", 18) == 0) { - packet->http_encoding.ptr = &packet->line[packet->parsed_lines].ptr[18]; - packet->http_encoding.len = packet->line[packet->parsed_lines].len - 18; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 18 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Content-Encoding: ", 18) == 0) { + packet->http_encoding.ptr = &packet->line[packet->parsed_lines].ptr[18]; + packet->http_encoding.len = packet->line[packet->parsed_lines].len - 18; + packet->http_num_headers++; } /* "Transfer-Encoding:" header line in HTTP. */ - if(packet->line[packet->parsed_lines].len > 19 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Transfer-Encoding: ", 19) == 0) { - packet->http_transfer_encoding.ptr = &packet->line[packet->parsed_lines].ptr[19]; - packet->http_transfer_encoding.len = packet->line[packet->parsed_lines].len - 19; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 19 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Transfer-Encoding: ", 19) == 0) { + packet->http_transfer_encoding.ptr = &packet->line[packet->parsed_lines].ptr[19]; + packet->http_transfer_encoding.len = packet->line[packet->parsed_lines].len - 19; + packet->http_num_headers++; } /* "Content-Length:" header line in HTTP. */ - if(packet->line[packet->parsed_lines].len > 16 - && ((strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Content-Length: ", 16) == 0) - || (strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "content-length: ", 16) == 0))) { - packet->http_contentlen.ptr = &packet->line[packet->parsed_lines].ptr[16]; - packet->http_contentlen.len = packet->line[packet->parsed_lines].len - 16; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 16 && + ((strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Content-Length: ", 16) == 0) || + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "content-length: ", 16) == 0))) { + packet->http_contentlen.ptr = &packet->line[packet->parsed_lines].ptr[16]; + packet->http_contentlen.len = packet->line[packet->parsed_lines].len - 16; + packet->http_num_headers++; + } + /* "Content-Disposition"*/ + if(packet->line[packet->parsed_lines].len > 21 && + ((strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Content-Disposition: ", 21) == 0))) { + packet->content_disposition_line.ptr = &packet->line[packet->parsed_lines].ptr[21]; + packet->content_disposition_line.len = packet->line[packet->parsed_lines].len - 21; + packet->http_num_headers++; } /* "Cookie:" header line in HTTP. */ - if(packet->line[packet->parsed_lines].len > 8 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Cookie: ", 8) == 0) { - packet->http_cookie.ptr = &packet->line[packet->parsed_lines].ptr[8]; - packet->http_cookie.len = packet->line[packet->parsed_lines].len - 8; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 8 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Cookie: ", 8) == 0) { + packet->http_cookie.ptr = &packet->line[packet->parsed_lines].ptr[8]; + packet->http_cookie.len = packet->line[packet->parsed_lines].len - 8; + packet->http_num_headers++; } /* "Origin:" header line in HTTP. */ - if(packet->line[packet->parsed_lines].len > 8 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Origin: ", 8) == 0) { - packet->http_origin.ptr = &packet->line[packet->parsed_lines].ptr[8]; - packet->http_origin.len = packet->line[packet->parsed_lines].len - 8; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 8 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Origin: ", 8) == 0) { + packet->http_origin.ptr = &packet->line[packet->parsed_lines].ptr[8]; + packet->http_origin.len = packet->line[packet->parsed_lines].len - 8; + packet->http_num_headers++; } /* "X-Session-Type:" header line in HTTP. */ - if(packet->line[packet->parsed_lines].len > 16 - && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "X-Session-Type: ", 16) == 0) { - packet->http_x_session_type.ptr = &packet->line[packet->parsed_lines].ptr[16]; - packet->http_x_session_type.len = packet->line[packet->parsed_lines].len - 16; - packet->http_num_headers++; + if(packet->line[packet->parsed_lines].len > 16 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "X-Session-Type: ", 16) == 0) { + packet->http_x_session_type.ptr = &packet->line[packet->parsed_lines].ptr[16]; + packet->http_x_session_type.len = packet->line[packet->parsed_lines].len - 16; + packet->http_num_headers++; } /* Identification and counting of other HTTP headers. * We consider the most common headers, but there are many others, @@ -5188,31 +5252,38 @@ void ndpi_parse_packet_line_info(struct ndpi_detection_module_struct *ndpi_str, * - https://tools.ietf.org/html/rfc7230 * - https://en.wikipedia.org/wiki/List_of_HTTP_header_fields */ - if((packet->line[packet->parsed_lines].len > 6 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Date: ", 6) == 0 || - strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Vary: ", 6) == 0 || - strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "ETag: ", 6) == 0 )) || - (packet->line[packet->parsed_lines].len > 8 && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Pragma: ", 8) == 0) || - (packet->line[packet->parsed_lines].len > 9 && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Expires: ", 9) == 0) || - (packet->line[packet->parsed_lines].len > 12 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Set-Cookie: ", 12) == 0 || - strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Keep-Alive: ", 12) == 0 || - strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Connection: ", 12) == 0)) || - (packet->line[packet->parsed_lines].len > 15 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Last-Modified: ", 15) == 0 || - strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept-Ranges: ", 15) == 0)) || - (packet->line[packet->parsed_lines].len > 17 && ( strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept-Language: ", 17) == 0 || - strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Accept-Encoding: ", 17) == 0)) || - (packet->line[packet->parsed_lines].len > 27 && strncasecmp((const char *)packet->line[packet->parsed_lines].ptr, "Upgrade-Insecure-Requests: ", 27) == 0)) { - /* Just count. In the future, if needed, this if can be splited to parse these headers */ - packet->http_num_headers++; + if((packet->line[packet->parsed_lines].len > 6 && + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Date: ", 6) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Vary: ", 6) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "ETag: ", 6) == 0)) || + (packet->line[packet->parsed_lines].len > 8 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Pragma: ", 8) == 0) || + (packet->line[packet->parsed_lines].len > 9 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Expires: ", 9) == 0) || + (packet->line[packet->parsed_lines].len > 12 && + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Set-Cookie: ", 12) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Keep-Alive: ", 12) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Connection: ", 12) == 0)) || + (packet->line[packet->parsed_lines].len > 15 && + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Last-Modified: ", 15) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Accept-Ranges: ", 15) == 0)) || + (packet->line[packet->parsed_lines].len > 17 && + (strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Accept-Language: ", 17) == 0 || + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, "Accept-Encoding: ", 17) == 0)) || + (packet->line[packet->parsed_lines].len > 27 && + strncasecmp((const char *) packet->line[packet->parsed_lines].ptr, + "Upgrade-Insecure-Requests: ", 27) == 0)) { + /* Just count. In the future, if needed, this if can be splited to parse these headers */ + packet->http_num_headers++; } - if(packet->line[packet->parsed_lines].len == 0) { - packet->empty_line_position = a; - packet->empty_line_position_set = 1; + packet->empty_line_position = a; + packet->empty_line_position_set = 1; } if(packet->parsed_lines >= (NDPI_MAX_PARSE_LINES_PER_PACKET - 1)) - return; + return; packet->parsed_lines++; packet->line[packet->parsed_lines].ptr = &packet->payload[a + 2]; @@ -5223,18 +5294,16 @@ void ndpi_parse_packet_line_info(struct ndpi_detection_module_struct *ndpi_str, } if(packet->parsed_lines >= 1) { - packet->line[packet->parsed_lines].len - = (u_int16_t)(((unsigned long) &packet->payload[packet->payload_packet_len]) - - ((unsigned long) packet->line[packet->parsed_lines].ptr)); + packet->line[packet->parsed_lines].len = + (u_int16_t)(((size_t) &packet->payload[packet->payload_packet_len]) - + ((size_t) packet->line[packet->parsed_lines].ptr)); packet->parsed_lines++; } } /* ********************************************************************************* */ -void ndpi_parse_packet_line_info_any(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow) -{ +void ndpi_parse_packet_line_info_any(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; u_int32_t a; u_int16_t end = packet->payload_packet_len; @@ -5251,24 +5320,23 @@ void ndpi_parse_packet_line_info_any(struct ndpi_detection_module_struct *ndpi_s packet->line[packet->parsed_lines].ptr = packet->payload; packet->line[packet->parsed_lines].len = 0; - for(a = 0; a < end; a++) { + for (a = 0; a < end; a++) { if(packet->payload[a] == 0x0a) { packet->line[packet->parsed_lines].len = (u_int16_t)( - ((unsigned long) &packet->payload[a]) - - ((unsigned long) packet->line[packet->parsed_lines].ptr)); + ((size_t) &packet->payload[a]) - ((size_t) packet->line[packet->parsed_lines].ptr)); - if(a > 0 && packet->payload[a-1] == 0x0d) - packet->line[packet->parsed_lines].len--; + if(a > 0 && packet->payload[a - 1] == 0x0d) + packet->line[packet->parsed_lines].len--; if(packet->parsed_lines >= (NDPI_MAX_PARSE_LINES_PER_PACKET - 1)) - break; + break; packet->parsed_lines++; packet->line[packet->parsed_lines].ptr = &packet->payload[a + 1]; packet->line[packet->parsed_lines].len = 0; if((a + 1) >= packet->payload_packet_len) - break; + break; //a++; } @@ -5277,63 +5345,61 @@ void ndpi_parse_packet_line_info_any(struct ndpi_detection_module_struct *ndpi_s /* ********************************************************************************* */ -u_int16_t ndpi_check_for_email_address(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, u_int16_t counter) -{ - +u_int16_t ndpi_check_for_email_address(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int16_t counter) { struct ndpi_packet_struct *packet = &flow->packet; NDPI_LOG_DBG2(ndpi_str, "called ndpi_check_for_email_address\n"); - if(packet->payload_packet_len > counter && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') - || (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z') - || (packet->payload[counter] >= '0' && packet->payload[counter] <= '9') - || packet->payload[counter] == '-' || packet->payload[counter] == '_')) { + if(packet->payload_packet_len > counter && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') || + (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z') || + (packet->payload[counter] >= '0' && packet->payload[counter] <= '9') || + packet->payload[counter] == '-' || packet->payload[counter] == '_')) { NDPI_LOG_DBG2(ndpi_str, "first letter\n"); counter++; - while(packet->payload_packet_len > counter - && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') - || (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z') - || (packet->payload[counter] >= '0' && packet->payload[counter] <= '9') - || packet->payload[counter] == '-' || packet->payload[counter] == '_' - || packet->payload[counter] == '.')) { + while (packet->payload_packet_len > counter && + ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') || + (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z') || + (packet->payload[counter] >= '0' && packet->payload[counter] <= '9') || + packet->payload[counter] == '-' || packet->payload[counter] == '_' || + packet->payload[counter] == '.')) { NDPI_LOG_DBG2(ndpi_str, "further letter\n"); counter++; if(packet->payload_packet_len > counter && packet->payload[counter] == '@') { NDPI_LOG_DBG2(ndpi_str, "@\n"); counter++; - while(packet->payload_packet_len > counter - && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') - || (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z') - || (packet->payload[counter] >= '0' && packet->payload[counter] <= '9') - || packet->payload[counter] == '-' || packet->payload[counter] == '_')) { + while (packet->payload_packet_len > counter && + ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') || + (packet->payload[counter] >= 'A' && packet->payload[counter] <= 'Z') || + (packet->payload[counter] >= '0' && packet->payload[counter] <= '9') || + packet->payload[counter] == '-' || packet->payload[counter] == '_')) { NDPI_LOG_DBG2(ndpi_str, "letter\n"); counter++; if(packet->payload_packet_len > counter && packet->payload[counter] == '.') { NDPI_LOG_DBG2(ndpi_str, ".\n"); counter++; - if(packet->payload_packet_len > counter + 1 - && ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') - && (packet->payload[counter + 1] >= 'a' && packet->payload[counter + 1] <= 'z'))) { + if(packet->payload_packet_len > counter + 1 && + ((packet->payload[counter] >= 'a' && packet->payload[counter] <= 'z') && + (packet->payload[counter + 1] >= 'a' && packet->payload[counter + 1] <= 'z'))) { NDPI_LOG_DBG2(ndpi_str, "two letters\n"); counter += 2; - if(packet->payload_packet_len > counter - && (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) { + if(packet->payload_packet_len > counter && + (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) { NDPI_LOG_DBG2(ndpi_str, "whitespace1\n"); return(counter); - } else if(packet->payload_packet_len > counter && packet->payload[counter] >= 'a' - && packet->payload[counter] <= 'z') { + } else if(packet->payload_packet_len > counter && packet->payload[counter] >= 'a' && + packet->payload[counter] <= 'z') { NDPI_LOG_DBG2(ndpi_str, "one letter\n"); counter++; - if(packet->payload_packet_len > counter - && (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) { + if(packet->payload_packet_len > counter && + (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) { NDPI_LOG_DBG2(ndpi_str, "whitespace2\n"); return(counter); - } else if(packet->payload_packet_len > counter && packet->payload[counter] >= 'a' - && packet->payload[counter] <= 'z') { + } else if(packet->payload_packet_len > counter && packet->payload[counter] >= 'a' && + packet->payload[counter] <= 'z') { counter++; - if(packet->payload_packet_len > counter - && (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) { + if(packet->payload_packet_len > counter && + (packet->payload[counter] == ' ' || packet->payload[counter] == ';')) { NDPI_LOG_DBG2(ndpi_str, "whitespace3\n"); return(counter); } else { @@ -5360,9 +5426,8 @@ u_int16_t ndpi_check_for_email_address(struct ndpi_detection_module_struct *ndpi #ifdef NDPI_ENABLE_DEBUG_MESSAGES /* ********************************************************************************* */ -void ndpi_debug_get_last_log_function_line(struct ndpi_detection_module_struct - *ndpi_str, const char **file, const char **func, u_int32_t * line) -{ +void ndpi_debug_get_last_log_function_line(struct ndpi_detection_module_struct *ndpi_str, const char **file, + const char **func, u_int32_t *line) { *file = ""; *func = ""; @@ -5378,21 +5443,16 @@ void ndpi_debug_get_last_log_function_line(struct ndpi_detection_module_struct /* ********************************************************************************* */ -u_int8_t ndpi_detection_get_l4(const u_int8_t * l3, u_int16_t l3_len, - const u_int8_t ** l4_return, u_int16_t * l4_len_return, - u_int8_t * l4_protocol_return, u_int32_t flags) { - return(ndpi_detection_get_l4_internal(NULL, l3, l3_len, - l4_return, l4_len_return, l4_protocol_return, flags)); +u_int8_t ndpi_detection_get_l4(const u_int8_t *l3, u_int16_t l3_len, const u_int8_t **l4_return, + u_int16_t *l4_len_return, u_int8_t *l4_protocol_return, u_int32_t flags) { + return(ndpi_detection_get_l4_internal(NULL, l3, l3_len, l4_return, l4_len_return, l4_protocol_return, flags)); } /* ********************************************************************************* */ -void ndpi_set_detected_protocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int16_t upper_detected_protocol, - u_int16_t lower_detected_protocol) { - struct ndpi_id_struct *src = flow->src; - struct ndpi_id_struct *dst = flow->dst; +void ndpi_set_detected_protocol(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int16_t upper_detected_protocol, u_int16_t lower_detected_protocol) { + struct ndpi_id_struct *src = flow->src, *dst = flow->dst; ndpi_int_change_protocol(ndpi_str, flow, upper_detected_protocol, lower_detected_protocol); @@ -5413,18 +5473,16 @@ void ndpi_set_detected_protocol(struct ndpi_detection_module_struct *ndpi_str, /* ********************************************************************************* */ -u_int16_t ndpi_get_flow_masterprotocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow) { +u_int16_t ndpi_get_flow_masterprotocol(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow) { return(flow->detected_protocol_stack[1]); } /* ********************************************************************************* */ -void ndpi_int_change_flow_protocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int16_t upper_detected_protocol, - u_int16_t lower_detected_protocol) { - if(!flow) return; +void ndpi_int_change_flow_protocol(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int16_t upper_detected_protocol, u_int16_t lower_detected_protocol) { + if(!flow) + return; flow->detected_protocol_stack[0] = upper_detected_protocol, flow->detected_protocol_stack[1] = lower_detected_protocol; @@ -5432,10 +5490,8 @@ void ndpi_int_change_flow_protocol(struct ndpi_detection_module_struct *ndpi_str /* ********************************************************************************* */ -void ndpi_int_change_packet_protocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int16_t upper_detected_protocol, - u_int16_t lower_detected_protocol) { +void ndpi_int_change_packet_protocol(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int16_t upper_detected_protocol, u_int16_t lower_detected_protocol) { struct ndpi_packet_struct *packet = &flow->packet; /* NOTE: everything below is identically to change_flow_protocol * except flow->packet If you want to change something here, @@ -5457,21 +5513,17 @@ void ndpi_int_change_packet_protocol(struct ndpi_detection_module_struct *ndpi_s * 1.update the flow protocol stack with the new protocol * 2.update the packet protocol stack with the new protocol */ -void ndpi_int_change_protocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int16_t upper_detected_protocol, - u_int16_t lower_detected_protocol) { - if((upper_detected_protocol == NDPI_PROTOCOL_UNKNOWN) - && (lower_detected_protocol != NDPI_PROTOCOL_UNKNOWN)) +void ndpi_int_change_protocol(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + u_int16_t upper_detected_protocol, u_int16_t lower_detected_protocol) { + if((upper_detected_protocol == NDPI_PROTOCOL_UNKNOWN) && (lower_detected_protocol != NDPI_PROTOCOL_UNKNOWN)) upper_detected_protocol = lower_detected_protocol; if(upper_detected_protocol == lower_detected_protocol) lower_detected_protocol = NDPI_PROTOCOL_UNKNOWN; - if((upper_detected_protocol != NDPI_PROTOCOL_UNKNOWN) - && (lower_detected_protocol == NDPI_PROTOCOL_UNKNOWN)) { - if((flow->guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) - && (upper_detected_protocol != flow->guessed_host_protocol_id)) { + if((upper_detected_protocol != NDPI_PROTOCOL_UNKNOWN) && (lower_detected_protocol == NDPI_PROTOCOL_UNKNOWN)) { + if((flow->guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) && + (upper_detected_protocol != flow->guessed_host_protocol_id)) { if(ndpi_str->proto_defaults[upper_detected_protocol].can_have_a_subprotocol) { lower_detected_protocol = upper_detected_protocol; upper_detected_protocol = flow->guessed_host_protocol_id; @@ -5479,17 +5531,14 @@ void ndpi_int_change_protocol(struct ndpi_detection_module_struct *ndpi_str, } } - ndpi_int_change_flow_protocol(ndpi_str, flow, - upper_detected_protocol, lower_detected_protocol); - ndpi_int_change_packet_protocol(ndpi_str, flow, - upper_detected_protocol, lower_detected_protocol); + ndpi_int_change_flow_protocol(ndpi_str, flow, upper_detected_protocol, lower_detected_protocol); + ndpi_int_change_packet_protocol(ndpi_str, flow, upper_detected_protocol, lower_detected_protocol); } /* ********************************************************************************* */ -void ndpi_int_change_category(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - ndpi_protocol_category_t protocol_category) { +void ndpi_int_change_category(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + ndpi_protocol_category_t protocol_category) { flow->category = protocol_category; } @@ -5499,7 +5548,7 @@ void ndpi_int_change_category(struct ndpi_detection_module_struct *ndpi_str, void ndpi_int_reset_packet_protocol(struct ndpi_packet_struct *packet) { int a; - for(a = 0; a < NDPI_PROTOCOL_SIZE; a++) + for (a = 0; a < NDPI_PROTOCOL_SIZE; a++) packet->detected_protocol_stack[a] = NDPI_PROTOCOL_UNKNOWN; } @@ -5509,14 +5558,14 @@ void ndpi_int_reset_protocol(struct ndpi_flow_struct *flow) { if(flow) { int a; - for(a = 0; a < NDPI_PROTOCOL_SIZE; a++) + for (a = 0; a < NDPI_PROTOCOL_SIZE; a++) flow->detected_protocol_stack[a] = NDPI_PROTOCOL_UNKNOWN; } } /* ********************************************************************************* */ -void NDPI_PROTOCOL_IP_clear(ndpi_ip_addr_t * ip) { +void NDPI_PROTOCOL_IP_clear(ndpi_ip_addr_t *ip) { memset(ip, 0, sizeof(ndpi_ip_addr_t)); } @@ -5524,8 +5573,7 @@ void NDPI_PROTOCOL_IP_clear(ndpi_ip_addr_t * ip) { #ifdef CODE_UNUSED /* NTOP */ -int NDPI_PROTOCOL_IP_is_set(const ndpi_ip_addr_t * ip) -{ +int NDPI_PROTOCOL_IP_is_set(const ndpi_ip_addr_t *ip) { return(memcmp(ip, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sizeof(ndpi_ip_addr_t)) != 0); } #endif @@ -5534,14 +5582,11 @@ int NDPI_PROTOCOL_IP_is_set(const ndpi_ip_addr_t * ip) /* check if the source ip address in packet and ip are equal */ /* NTOP */ -int ndpi_packet_src_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_ip_addr_t * ip) -{ - +int ndpi_packet_src_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_ip_addr_t *ip) { #ifdef NDPI_DETECTION_SUPPORT_IPV6 /* IPv6 */ if(packet->iphv6 != NULL) { - if(packet->iphv6->ip6_src.u6_addr.u6_addr32[0] == ip->ipv6.u6_addr.u6_addr32[0] && packet->iphv6->ip6_src.u6_addr.u6_addr32[1] == ip->ipv6.u6_addr.u6_addr32[1] && packet->iphv6->ip6_src.u6_addr.u6_addr32[2] == ip->ipv6.u6_addr.u6_addr32[2] && @@ -5561,14 +5606,11 @@ int ndpi_packet_src_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_i /* ********************************************************************************* */ /* check if the destination ip address in packet and ip are equal */ -int ndpi_packet_dst_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_ip_addr_t * ip) -{ - +int ndpi_packet_dst_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_ip_addr_t *ip) { #ifdef NDPI_DETECTION_SUPPORT_IPV6 /* IPv6 */ if(packet->iphv6 != NULL) { - if(packet->iphv6->ip6_dst.u6_addr.u6_addr32[0] == ip->ipv6.u6_addr.u6_addr32[0] && packet->iphv6->ip6_dst.u6_addr.u6_addr32[1] == ip->ipv6.u6_addr.u6_addr32[1] && packet->iphv6->ip6_dst.u6_addr.u6_addr32[2] == ip->ipv6.u6_addr.u6_addr32[2] && @@ -5590,15 +5632,13 @@ int ndpi_packet_dst_ip_eql(const struct ndpi_packet_struct *packet, const ndpi_i /* get the source ip address from packet and put it into ip */ /* NTOP */ -void ndpi_packet_src_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_addr_t * ip) -{ +void ndpi_packet_src_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_addr_t *ip) { NDPI_PROTOCOL_IP_clear(ip); #ifdef NDPI_DETECTION_SUPPORT_IPV6 /* IPv6 */ if(packet->iphv6 != NULL) { - ip->ipv6.u6_addr.u6_addr32[0] = packet->iphv6->ip6_src.u6_addr.u6_addr32[0]; ip->ipv6.u6_addr.u6_addr32[1] = packet->iphv6->ip6_src.u6_addr.u6_addr32[1]; ip->ipv6.u6_addr.u6_addr32[2] = packet->iphv6->ip6_src.u6_addr.u6_addr32[2]; @@ -5615,14 +5655,12 @@ void ndpi_packet_src_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_add /* get the destination ip address from packet and put it into ip */ /* NTOP */ -void ndpi_packet_dst_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_addr_t * ip) -{ +void ndpi_packet_dst_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_addr_t *ip) { NDPI_PROTOCOL_IP_clear(ip); #ifdef NDPI_DETECTION_SUPPORT_IPV6 if(packet->iphv6 != NULL) { - ip->ipv6.u6_addr.u6_addr32[0] = packet->iphv6->ip6_dst.u6_addr.u6_addr32[0]; ip->ipv6.u6_addr.u6_addr32[1] = packet->iphv6->ip6_dst.u6_addr.u6_addr32[1]; ip->ipv6.u6_addr.u6_addr32[2] = packet->iphv6->ip6_dst.u6_addr.u6_addr32[2]; @@ -5631,20 +5669,28 @@ void ndpi_packet_dst_ip_get(const struct ndpi_packet_struct *packet, ndpi_ip_add } else #endif - - ip->ipv4 = packet->iph->daddr; + + ip->ipv4 = packet->iph->daddr; +} + +/* ********************************************************************************* */ + +u_int8_t ndpi_is_ipv6(const ndpi_ip_addr_t *ip) { +#ifdef NDPI_DETECTION_SUPPORT_IPV6 + return(ip->ipv6.u6_addr.u6_addr32[1] != 0 || ip->ipv6.u6_addr.u6_addr32[2] != 0 || + ip->ipv6.u6_addr.u6_addr32[3] != 0); +#else + return(0); +#endif } /* ********************************************************************************* */ -char *ndpi_get_ip_string(const ndpi_ip_addr_t * ip, char *buf, u_int buf_len) { +char *ndpi_get_ip_string(const ndpi_ip_addr_t *ip, char *buf, u_int buf_len) { const u_int8_t *a = (const u_int8_t *) &ip->ipv4; #ifdef NDPI_DETECTION_SUPPORT_IPV6 - if(ip->ipv6.u6_addr.u6_addr32[1] != 0 || - ip->ipv6.u6_addr.u6_addr32[2] != 0 || - ip->ipv6.u6_addr.u6_addr32[3] != 0) { - + if(ndpi_is_ipv6(ip)) { if(inet_ntop(AF_INET6, &ip->ipv6.u6_addr, buf, buf_len) == NULL) buf[0] = '\0'; @@ -5655,7 +5701,6 @@ char *ndpi_get_ip_string(const ndpi_ip_addr_t * ip, char *buf, u_int buf_len) { snprintf(buf, buf_len, "%u.%u.%u.%u", a[0], a[1], a[2], a[3]); return(buf); - } /* ****************************************************** */ @@ -5680,9 +5725,8 @@ int ndpi_parse_ip_string(const char *ip_str, ndpi_ip_addr_t *parsed_ip) { /* ****************************************************** */ -u_int16_t ntohs_ndpi_bytestream_to_number(const u_int8_t * str, - u_int16_t max_chars_to_read, - u_int16_t * bytes_read) { +u_int16_t ntohs_ndpi_bytestream_to_number(const u_int8_t *str, + u_int16_t max_chars_to_read, u_int16_t *bytes_read) { u_int16_t val = ndpi_bytestream_to_number(str, max_chars_to_read, bytes_read); return(ntohs(val)); } @@ -5702,27 +5746,24 @@ u_int16_t ndpi_get_lower_proto(ndpi_protocol proto) { /* ****************************************************** */ ndpi_protocol ndpi_guess_undetected_protocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - u_int8_t proto, - u_int32_t shost /* host byte order */, u_int16_t sport, - u_int32_t dhost /* host byte order */, u_int16_t dport) { + struct ndpi_flow_struct *flow, u_int8_t proto, + u_int32_t shost /* host byte order */, u_int16_t sport, + u_int32_t dhost /* host byte order */, u_int16_t dport) { u_int32_t rc; struct in_addr addr; - ndpi_protocol ret = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED }; + ndpi_protocol ret = {NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED}; u_int8_t user_defined_proto; if((proto == IPPROTO_TCP) || (proto == IPPROTO_UDP)) { rc = ndpi_search_tcp_or_udp_raw(ndpi_str, flow, proto, shost, dhost, sport, dport); if(rc != NDPI_PROTOCOL_UNKNOWN) { - if(flow && (proto == IPPROTO_UDP) - && NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, rc) - && is_udp_guessable_protocol(rc)) + if(flow && (proto == IPPROTO_UDP) && + NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, rc) && is_udp_guessable_protocol(rc)) ; else { ret.app_protocol = rc, - ret.master_protocol = ndpi_guess_protocol_id(ndpi_str, flow, proto, sport, - dport, &user_defined_proto); + ret.master_protocol = ndpi_guess_protocol_id(ndpi_str, flow, proto, sport, dport, &user_defined_proto); if(ret.app_protocol == ret.master_protocol) ret.master_protocol = NDPI_PROTOCOL_UNKNOWN; @@ -5734,9 +5775,8 @@ ndpi_protocol ndpi_guess_undetected_protocol(struct ndpi_detection_module_struct rc = ndpi_guess_protocol_id(ndpi_str, flow, proto, sport, dport, &user_defined_proto); if(rc != NDPI_PROTOCOL_UNKNOWN) { - if(flow && (proto == IPPROTO_UDP) - && NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, rc) - && is_udp_guessable_protocol(rc)) + if(flow && (proto == IPPROTO_UDP) && + NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, rc) && is_udp_guessable_protocol(rc)) ; else { ret.app_protocol = rc; @@ -5760,8 +5800,7 @@ ndpi_protocol ndpi_guess_undetected_protocol(struct ndpi_detection_module_struct ret.app_protocol = NDPI_PROTOCOL_SKYPE; } } else - ret.app_protocol = ndpi_guess_protocol_id(ndpi_str, flow, proto, sport, - dport, &user_defined_proto); + ret.app_protocol = ndpi_guess_protocol_id(ndpi_str, flow, proto, sport, dport, &user_defined_proto); ret.category = ndpi_get_proto_category(ndpi_str, ret); return(ret); @@ -5769,13 +5808,11 @@ ndpi_protocol ndpi_guess_undetected_protocol(struct ndpi_detection_module_struct /* ****************************************************** */ -char* ndpi_protocol2id(struct ndpi_detection_module_struct *ndpi_str, +char *ndpi_protocol2id(struct ndpi_detection_module_struct *ndpi_str, ndpi_protocol proto, char *buf, u_int buf_len) { - if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN) - && (proto.master_protocol != proto.app_protocol)) { + if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN) && (proto.master_protocol != proto.app_protocol)) { if(proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) - snprintf(buf, buf_len, "%u.%u", - proto.master_protocol, proto.app_protocol); + snprintf(buf, buf_len, "%u.%u", proto.master_protocol, proto.app_protocol); else snprintf(buf, buf_len, "%u", proto.master_protocol); } else @@ -5786,20 +5823,16 @@ char* ndpi_protocol2id(struct ndpi_detection_module_struct *ndpi_str, /* ****************************************************** */ -char* ndpi_protocol2name(struct ndpi_detection_module_struct *ndpi_str, +char *ndpi_protocol2name(struct ndpi_detection_module_struct *ndpi_str, ndpi_protocol proto, char *buf, u_int buf_len) { - if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN) - && (proto.master_protocol != proto.app_protocol)) { + if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN) && (proto.master_protocol != proto.app_protocol)) { if(proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) - snprintf(buf, buf_len, "%s.%s", - ndpi_get_proto_name(ndpi_str, proto.master_protocol), + snprintf(buf, buf_len, "%s.%s", ndpi_get_proto_name(ndpi_str, proto.master_protocol), ndpi_get_proto_name(ndpi_str, proto.app_protocol)); else - snprintf(buf, buf_len, "%s", - ndpi_get_proto_name(ndpi_str, proto.master_protocol)); + snprintf(buf, buf_len, "%s", ndpi_get_proto_name(ndpi_str, proto.master_protocol)); } else - snprintf(buf, buf_len, "%s", - ndpi_get_proto_name(ndpi_str, proto.app_protocol)); + snprintf(buf, buf_len, "%s", ndpi_get_proto_name(ndpi_str, proto.app_protocol)); return(buf); } @@ -5825,9 +5858,10 @@ int ndpi_is_custom_category(ndpi_protocol_category_t category) { /* ****************************************************** */ void ndpi_category_set_name(struct ndpi_detection_module_struct *ndpi_str, - ndpi_protocol_category_t category, char *name) { - - if(!name) return; + ndpi_protocol_category_t category, + char *name) { + if(!name) + return; switch(category) { case NDPI_PROTOCOL_CATEGORY_CUSTOM_1: @@ -5857,7 +5891,7 @@ void ndpi_category_set_name(struct ndpi_detection_module_struct *ndpi_str, /* ****************************************************** */ -const char* ndpi_category_get_name(struct ndpi_detection_module_struct *ndpi_str, +const char *ndpi_category_get_name(struct ndpi_detection_module_struct *ndpi_str, ndpi_protocol_category_t category) { if((!ndpi_str) || (category >= NDPI_PROTOCOL_NUM_CATEGORIES)) { static char b[24]; @@ -5865,7 +5899,7 @@ const char* ndpi_category_get_name(struct ndpi_detection_module_struct *ndpi_str if(!ndpi_str) snprintf(b, sizeof(b), "NULL nDPI"); else - snprintf(b, sizeof(b), "Invalid category %d", (int)category); + snprintf(b, sizeof(b), "Invalid category %d", (int) category); return(b); } @@ -5899,18 +5933,22 @@ ndpi_protocol_category_t ndpi_get_proto_category(struct ndpi_detection_module_st /* simple rule: sub protocol first, master after */ else if((proto.master_protocol == NDPI_PROTOCOL_UNKNOWN) || - (ndpi_str->proto_defaults[proto.app_protocol].protoCategory != NDPI_PROTOCOL_CATEGORY_UNSPECIFIED)) - return(ndpi_str->proto_defaults[proto.app_protocol].protoCategory); - else + (ndpi_str->proto_defaults[proto.app_protocol].protoCategory != NDPI_PROTOCOL_CATEGORY_UNSPECIFIED)) { + if(proto.app_protocol < (NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) + return(ndpi_str->proto_defaults[proto.app_protocol].protoCategory); + } else if(proto.master_protocol < (NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) return(ndpi_str->proto_defaults[proto.master_protocol].protoCategory); + + return(NDPI_PROTOCOL_CATEGORY_UNSPECIFIED); } /* ****************************************************** */ -char* ndpi_get_proto_name(struct ndpi_detection_module_struct *ndpi_str, u_int16_t proto_id) { - if((proto_id >= ndpi_str->ndpi_num_supported_protocols) - || (proto_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) - || (ndpi_str->proto_defaults[proto_id].protoName == NULL)) +char *ndpi_get_proto_name(struct ndpi_detection_module_struct *ndpi_str, + u_int16_t proto_id) { + if((proto_id >= ndpi_str->ndpi_num_supported_protocols) || + (proto_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) || + (ndpi_str->proto_defaults[proto_id].protoName == NULL)) proto_id = NDPI_PROTOCOL_UNKNOWN; return(ndpi_str->proto_defaults[proto_id].protoName); @@ -5920,9 +5958,9 @@ char* ndpi_get_proto_name(struct ndpi_detection_module_struct *ndpi_str, u_int16 ndpi_protocol_breed_t ndpi_get_proto_breed(struct ndpi_detection_module_struct *ndpi_str, u_int16_t proto_id) { - if((proto_id >= ndpi_str->ndpi_num_supported_protocols) - || (proto_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS+NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) - || (ndpi_str->proto_defaults[proto_id].protoName == NULL)) + if((proto_id >= ndpi_str->ndpi_num_supported_protocols) || + (proto_id >= (NDPI_MAX_SUPPORTED_PROTOCOLS + NDPI_MAX_NUM_CUSTOM_PROTOCOLS)) || + (ndpi_str->proto_defaults[proto_id].protoName == NULL)) proto_id = NDPI_PROTOCOL_UNKNOWN; return(ndpi_str->proto_defaults[proto_id].protoBreed); @@ -5930,7 +5968,7 @@ ndpi_protocol_breed_t ndpi_get_proto_breed(struct ndpi_detection_module_struct * /* ****************************************************** */ -char* ndpi_get_proto_breed_name(struct ndpi_detection_module_struct *ndpi_str, +char *ndpi_get_proto_breed_name(struct ndpi_detection_module_struct *ndpi_str, ndpi_protocol_breed_t breed_id) { switch(breed_id) { case NDPI_PROTOCOL_SAFE: @@ -5946,9 +5984,11 @@ char* ndpi_get_proto_breed_name(struct ndpi_detection_module_struct *ndpi_str, return("Unsafe"); break; case NDPI_PROTOCOL_POTENTIALLY_DANGEROUS: + return("Potentially Dangerous"); + break; + case NDPI_PROTOCOL_DANGEROUS: return("Dangerous"); break; - case NDPI_PROTOCOL_UNRATED: default: return("Unrated"); @@ -5961,7 +6001,7 @@ char* ndpi_get_proto_breed_name(struct ndpi_detection_module_struct *ndpi_str, int ndpi_get_protocol_id(struct ndpi_detection_module_struct *ndpi_str, char *proto) { int i; - for(i=0; i<(int)ndpi_str->ndpi_num_supported_protocols; i++) + for (i = 0; i < (int) ndpi_str->ndpi_num_supported_protocols; i++) if(strcasecmp(proto, ndpi_str->proto_defaults[i].protoName) == 0) return(i); @@ -5973,7 +6013,7 @@ int ndpi_get_protocol_id(struct ndpi_detection_module_struct *ndpi_str, char *pr int ndpi_get_category_id(struct ndpi_detection_module_struct *ndpi_str, char *cat) { int i; - for(i = 0; i < NDPI_PROTOCOL_NUM_CATEGORIES; i++) { + for (i = 0; i < NDPI_PROTOCOL_NUM_CATEGORIES; i++) { const char *name = ndpi_category_get_name(ndpi_str, i); if(strcasecmp(cat, name) == 0) @@ -5988,13 +6028,11 @@ int ndpi_get_category_id(struct ndpi_detection_module_struct *ndpi_str, char *ca void ndpi_dump_protocols(struct ndpi_detection_module_struct *ndpi_str) { int i; - for(i=0; i<(int)ndpi_str->ndpi_num_supported_protocols; i++) - printf("%3d %-22s %-8s %-12s %s\n", i, - ndpi_str->proto_defaults[i].protoName, + for (i = 0; i < (int) ndpi_str->ndpi_num_supported_protocols; i++) + printf("%3d %-22s %-8s %-12s %s\n", i, ndpi_str->proto_defaults[i].protoName, ndpi_get_l4_proto_name(ndpi_get_l4_proto_info(ndpi_str, i)), ndpi_get_proto_breed_name(ndpi_str, ndpi_str->proto_defaults[i].protoBreed), - ndpi_category_get_name(ndpi_str, ndpi_str->proto_defaults[i].protoCategory) - ); + ndpi_category_get_name(ndpi_str, ndpi_str->proto_defaults[i].protoCategory)); } /* ****************************************************** */ @@ -6003,7 +6041,7 @@ void ndpi_dump_protocols(struct ndpi_detection_module_struct *ndpi_str) { * Find the first occurrence of find in s, where the search is limited to the * first slen characters of s. */ -char* ndpi_strnstr(const char *s, const char *find, size_t slen) { +char *ndpi_strnstr(const char *s, const char *find, size_t slen) { char c; size_t len; @@ -6015,14 +6053,14 @@ char* ndpi_strnstr(const char *s, const char *find, size_t slen) { do { if(slen-- < 1 || (sc = *s++) == '\0') return(NULL); - } while(sc != c); + } while (sc != c); if(len > slen) return(NULL); - } while(strncmp(s, find, len) != 0); + } while (strncmp(s, find, len) != 0); s--; } - return((char *)s); + return((char *) s); } /* ****************************************************** */ @@ -6030,33 +6068,27 @@ char* ndpi_strnstr(const char *s, const char *find, size_t slen) { /* * Same as ndpi_strnstr but case-insensitive */ -char* ndpi_strncasestr(const char *s, const char *find, size_t slen) { - char c; - size_t len; - - if((c = *find++) != '\0') { - len = strlen(find); - do { - char sc; - - do { - if(slen-- < 1 || (sc = *s++) == '\0') - return(NULL); - } while(sc != c); - - if(len > slen) - return(NULL); - } while(strncasecmp(s, find, len) != 0); - - s--; +const char * ndpi_strncasestr(const char *str1, const char *str2, size_t len) { + size_t str1_len = strnlen(str1, len); + size_t str2_len = strlen(str2); + size_t i; + + for(i = 0; i < (str1_len - str2_len + 1); i++){ + if(str1[0] == '\0') + return NULL; + else if(strncasecmp(str1, str2, str2_len) == 0) + return(str1); + + str1++; } - return((char *)s); + + return NULL; } /* ****************************************************** */ -int ndpi_match_prefix(const u_int8_t *payload, size_t payload_len, - const char *str, size_t str_len) { +int ndpi_match_prefix(const u_int8_t *payload, + size_t payload_len, const char *str, size_t str_len) { int rc = str_len <= payload_len ? memcmp(payload, str, str_len) == 0 : 0; return(rc); @@ -6064,92 +6096,69 @@ int ndpi_match_prefix(const u_int8_t *payload, size_t payload_len, /* ****************************************************** */ -int ndpi_match_string_subprotocol(struct ndpi_detection_module_struct *ndpi_str, - char *string_to_match, u_int string_to_match_len, - ndpi_protocol_match_result *ret_match, - u_int8_t is_host_match) { +int ndpi_match_string_subprotocol(struct ndpi_detection_module_struct *ndpi_str, char *string_to_match, + u_int string_to_match_len, ndpi_protocol_match_result *ret_match, + u_int8_t is_host_match) { AC_TEXT_t ac_input_text; ndpi_automa *automa = is_host_match ? &ndpi_str->host_automa : &ndpi_str->content_automa; - AC_REP_t match = { NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED }; + AC_REP_t match = {NDPI_PROTOCOL_UNKNOWN, NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, NDPI_PROTOCOL_UNRATED}; + int rc; if((automa->ac_automa == NULL) || (string_to_match_len == 0)) return(NDPI_PROTOCOL_UNKNOWN); if(!automa->ac_automa_finalized) { - ac_automata_finalize((AC_AUTOMATA_t*)automa->ac_automa); - automa->ac_automa_finalized = 1; + printf("[%s:%d] [NDPI] Internal error: please call ndpi_finalize_initalization()\n", __FILE__, __LINE__); + return(0); /* No matches */ } ac_input_text.astring = string_to_match, ac_input_text.length = string_to_match_len; - ac_automata_search(((AC_AUTOMATA_t*)automa->ac_automa), &ac_input_text, &match); - ac_automata_reset(((AC_AUTOMATA_t*)automa->ac_automa)); + rc = ac_automata_search(((AC_AUTOMATA_t *) automa->ac_automa), &ac_input_text, &match); + + /* + As ac_automata_search can detect partial matches and continue the search process + in case rc == 0 (i.e. no match), we need to check if there is a partial match + and in this case return it + */ + if((rc == 0) && (match.number != 0)) + rc = 1; - /* We need to take into account also rc==0 that is used for partial matches */ - ret_match->protocol_id = match.number, - ret_match->protocol_category = match.category, - ret_match->protocol_breed = match.breed; + /* We need to take into account also rc == 0 that is used for partial matches */ + ret_match->protocol_id = match.number, ret_match->protocol_category = match.category, + ret_match->protocol_breed = match.breed; - return(match.number); + return(rc ? match.number : 0); } -#ifdef HAVE_HYPERSCAN +/* **************************************** */ -/* ******************************************************************** */ +static u_int8_t ndpi_is_more_generic_protocol(u_int16_t previous_proto, u_int16_t new_proto) { + /* Sometimes certificates are more generic than previously identified protocols */ -static int hyperscanEventHandler(unsigned int id, unsigned long long from, - unsigned long long to, unsigned int flags, void *ctx) { - *((int *)ctx) = (int)id; + if((previous_proto == NDPI_PROTOCOL_UNKNOWN) || (previous_proto == new_proto)) + return(0); - NDPI_LOG_DBG2(ndpi_str, "[NDPI] Match with: %d [from: %llu][to: %llu]\n", id, from, to); + switch(previous_proto) { + case NDPI_PROTOCOL_WHATSAPP_CALL: + case NDPI_PROTOCOL_WHATSAPP_FILES: + if(new_proto == NDPI_PROTOCOL_WHATSAPP) + return(1); + } - /* return HS_SCAN_TERMINATED; */ - return(0); /* keep searching */ + return(0); } -#endif - /* ****************************************************** */ static u_int16_t ndpi_automa_match_string_subprotocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - char *string_to_match, u_int string_to_match_len, - u_int16_t master_protocol_id, - ndpi_protocol_match_result *ret_match, - u_int8_t is_host_match) { + struct ndpi_flow_struct *flow, char *string_to_match, + u_int string_to_match_len, u_int16_t master_protocol_id, + ndpi_protocol_match_result *ret_match, u_int8_t is_host_match) { int matching_protocol_id; struct ndpi_packet_struct *packet = &flow->packet; -#ifndef HAVE_HYPERSCAN - matching_protocol_id = ndpi_match_string_subprotocol(ndpi_str, string_to_match, - string_to_match_len, ret_match, - is_host_match); -#else - struct hs *hs = (struct hs*)ndpi_str->hyperscan; - hs_error_t status; - - matching_protocol_id = NDPI_PROTOCOL_UNKNOWN; - /* - TODO HYPERSCAN - In case of match fill up ret_match and set flow protocol + category - */ - status = hs_scan(hs->database, string_to_match, - string_to_match_len, 0, hs->scratch, - hyperscanEventHandler, &matching_protocol_id); - - if(status == HS_SUCCESS) { - NDPI_LOG_DBG2(ndpi_str, "[NDPI] Hyperscan engine completed normally. Result: %s [%d][%s]\n", - ndpi_get_proto_name(ndpi_str, matching_protocol_id), matching_protocol_id, string_to_match); - } else if(status == HS_SCAN_TERMINATED) { - NDPI_LOG_DBG2(ndpi_str, "[NDPI] Hyperscan engine was terminated by callback. Result: %s [%d][%s]\n", - ndpi_get_proto_name(ndpi_str, matching_protocol_id), matching_protocol_id, string_to_match); - } else { - NDPI_LOG_DBG2(ndpi_str, "[NDPI] Hyperscan returned with error.\n"); - } - - ret_match->protocol_id = matching_protocol_id, - ret_match->protocol_category = ndpi_str->proto_defaults[matching_protocol_id].protoCategory, - ret_match->protocol_breed = ndpi_str->proto_defaults[matching_protocol_id].protoBreed; -#endif + matching_protocol_id = + ndpi_match_string_subprotocol(ndpi_str, string_to_match, string_to_match_len, ret_match, is_host_match); #ifdef DEBUG { @@ -6159,12 +6168,13 @@ static u_int16_t ndpi_automa_match_string_subprotocol(struct ndpi_detection_modu strncpy(m, string_to_match, len); m[len] = '\0'; - NDPI_LOG_DBG2(ndpi_str, "[NDPI] ndpi_match_host_subprotocol(%s): %s\n", - m, ndpi_str->proto_defaults[matching_protocol_id].protoName); + NDPI_LOG_DBG2(ndpi_str, "[NDPI] ndpi_match_host_subprotocol(%s): %s\n", m, + ndpi_str->proto_defaults[matching_protocol_id].protoName); } #endif - if(matching_protocol_id != NDPI_PROTOCOL_UNKNOWN) { + if((matching_protocol_id != NDPI_PROTOCOL_UNKNOWN) && + (!ndpi_is_more_generic_protocol(packet->detected_protocol_stack[0], matching_protocol_id))) { /* Move the protocol on slot 0 down one position */ packet->detected_protocol_stack[1] = master_protocol_id, packet->detected_protocol_stack[0] = matching_protocol_id; @@ -6183,8 +6193,7 @@ static u_int16_t ndpi_automa_match_string_subprotocol(struct ndpi_detection_modu NDPI_LOG_DBG2(ndpi_str, "[NTOP] Unable to find a match for '%s'\n", string_to_match); #endif - ret_match->protocol_id = NDPI_PROTOCOL_UNKNOWN, - ret_match->protocol_category = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, + ret_match->protocol_id = NDPI_PROTOCOL_UNKNOWN, ret_match->protocol_category = NDPI_PROTOCOL_CATEGORY_UNSPECIFIED, ret_match->protocol_breed = NDPI_PROTOCOL_UNRATED; return(NDPI_PROTOCOL_UNKNOWN); @@ -6192,39 +6201,57 @@ static u_int16_t ndpi_automa_match_string_subprotocol(struct ndpi_detection_modu /* ****************************************************** */ -u_int16_t ndpi_match_host_subprotocol(struct ndpi_detection_module_struct *ndpi_str, - struct ndpi_flow_struct *flow, - char *string_to_match, u_int string_to_match_len, - ndpi_protocol_match_result *ret_match, - u_int16_t master_protocol_id) { - u_int16_t rc = ndpi_automa_match_string_subprotocol(ndpi_str, - flow, string_to_match, string_to_match_len, +u_int16_t ndpi_match_host_subprotocol(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow, + char *string_to_match, u_int string_to_match_len, + ndpi_protocol_match_result *ret_match, u_int16_t master_protocol_id) { + u_int16_t rc = ndpi_automa_match_string_subprotocol(ndpi_str, flow, string_to_match, string_to_match_len, master_protocol_id, ret_match, 1); + ndpi_protocol_category_t id = ret_match->protocol_category; - if((flow->category == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) - && (ret_match->protocol_category == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED)) { - unsigned long id = ret_match->protocol_category; - - if(ndpi_get_custom_category_match(ndpi_str, string_to_match, string_to_match_len, &id) != -1) { - if(id != -1) { - flow->category = ret_match->protocol_category = id; - rc = master_protocol_id; - } + if(ndpi_get_custom_category_match(ndpi_str, string_to_match, string_to_match_len, &id) != -1) { + /* if(id != -1) */ { + flow->category = ret_match->protocol_category = id; + rc = master_protocol_id; } } return(rc); } +/* **************************************** */ + +int ndpi_match_hostname_protocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, + u_int16_t master_protocol, char *name, u_int name_len) { + ndpi_protocol_match_result ret_match; + u_int16_t subproto, what_len, i; + char *what; + + if((name_len > 2) && (name[0] == '*') && (name[1] == '.')) + what = &name[1], what_len = name_len - 1; + else + what = name, what_len = name_len; + + /* Convert it first to lowercase: we assume meory is writable as in nDPI dissctors */ + for(i=0; iac_automa == NULL) || (bigram_to_match == NULL)) return(-1); if(!automa->ac_automa_finalized) { - ac_automata_finalize((AC_AUTOMATA_t*)automa->ac_automa); - automa->ac_automa_finalized = 1; +#if 1 + ndpi_finalize_initalization(ndpi_str); +#else + printf("[%s:%d] [NDPI] Internal error: please call ndpi_finalize_initalization()\n", __FILE__, __LINE__); + return(0); /* No matches */ +#endif } ac_input_text.astring = bigram_to_match, ac_input_text.length = 2; - rc = ac_automata_search(((AC_AUTOMATA_t*)automa->ac_automa), &ac_input_text, &match); - ac_automata_reset(((AC_AUTOMATA_t*)automa->ac_automa)); + rc = ac_automata_search(((AC_AUTOMATA_t *) automa->ac_automa), &ac_input_text, &match); /* As ac_automata_search can detect partial matches and continue the search process in case rc == 0 (i.e. no match), we need to check if there is a partial match and in this case return it */ - if((rc == 0) && (match.number != 0)) rc = 1; + if((rc == 0) && (match.number != 0)) + rc = 1; return(rc ? match.number : 0); } @@ -6262,12 +6293,42 @@ int ndpi_match_bigram(struct ndpi_detection_module_struct *ndpi_str, void ndpi_free_flow(struct ndpi_flow_struct *flow) { if(flow) { - if(flow->http.url) ndpi_free(flow->http.url); - if(flow->http.content_type) ndpi_free(flow->http.content_type); + if(flow->http.url) + ndpi_free(flow->http.url); + if(flow->http.content_type) + ndpi_free(flow->http.content_type); + if(flow->http.user_agent) + ndpi_free(flow->http.user_agent); + if(flow->kerberos_buf.pktbuf) + ndpi_free(flow->kerberos_buf.pktbuf); + + 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); + + if(flow->protos.stun_ssl.ssl.alpn) + ndpi_free(flow->protos.stun_ssl.ssl.alpn); + + if(flow->protos.stun_ssl.ssl.tls_supported_versions) + ndpi_free(flow->protos.stun_ssl.ssl.tls_supported_versions); + + if(flow->protos.stun_ssl.ssl.issuerDN) + ndpi_free(flow->protos.stun_ssl.ssl.issuerDN); + + if(flow->protos.stun_ssl.ssl.subjectDN) + ndpi_free(flow->protos.stun_ssl.ssl.subjectDN); + + if(flow->l4.tcp.tls.srv_cert_fingerprint_ctx) + ndpi_free(flow->l4.tcp.tls.srv_cert_fingerprint_ctx); + + if(flow->protos.stun_ssl.ssl.encrypted_sni.esni) + ndpi_free(flow->protos.stun_ssl.ssl.encrypted_sni.esni); + } if(flow->l4_proto == IPPROTO_TCP) { - if(flow->l4.tcp.tls_srv_cert_fingerprint_ctx) - ndpi_free(flow->l4.tcp.tls_srv_cert_fingerprint_ctx); + if(flow->l4.tcp.tls.message.buffer) + ndpi_free(flow->l4.tcp.tls.message.buffer); } ndpi_free(flow); @@ -6276,29 +6337,31 @@ void ndpi_free_flow(struct ndpi_flow_struct *flow) { /* ****************************************************** */ -char* ndpi_revision() { return(NDPI_GIT_RELEASE); } +char *ndpi_revision() { + return(NDPI_GIT_RELEASE); +} /* ****************************************************** */ #ifdef WIN32 /* https://stackoverflow.com/questions/10905892/equivalent-of-gettimeday-for-windows */ -int gettimeofday(struct timeval * tp, struct timezone * tzp) { +int gettimeofday(struct timeval *tp, struct timezone *tzp) { // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) // until 00:00:00 January 1, 1970 static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL); - SYSTEMTIME system_time; - FILETIME file_time; - uint64_t time; + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; - GetSystemTime( &system_time ); - SystemTimeToFileTime( &system_time, &file_time ); - time = ((uint64_t)file_time.dwLowDateTime ) ; - time += ((uint64_t)file_time.dwHighDateTime) << 32; + GetSystemTime(&system_time); + SystemTimeToFileTime(&system_time, &file_time); + time = ((uint64_t) file_time.dwLowDateTime); + time += ((uint64_t) file_time.dwHighDateTime) << 32; - tp->tv_sec = (long) ((time - EPOCH) / 10000000L); + tp->tv_sec = (long) ((time - EPOCH) / 10000000L); tp->tv_usec = (long) (system_time.wMilliseconds * 1000); return(0); } @@ -6307,7 +6370,7 @@ int gettimeofday(struct timeval * tp, struct timezone * tzp) { int NDPI_BITMASK_COMPARE(NDPI_PROTOCOL_BITMASK a, NDPI_PROTOCOL_BITMASK b) { int i; - for(i=0; iproto_defaults); } @@ -6355,20 +6426,26 @@ u_int ndpi_get_ndpi_detection_module_size() { return(sizeof(struct ndpi_detection_module_struct)); } -void ndpi_set_log_level(struct ndpi_detection_module_struct *ndpi_str, u_int l) { +void ndpi_set_debug_bitmask(struct ndpi_detection_module_struct *ndpi_str, NDPI_PROTOCOL_BITMASK debug_bitmask) { +#ifdef NDPI_ENABLE_DEBUG_MESSAGES + ndpi_str->debug_bitmask = debug_bitmask; +#endif +} + +void ndpi_set_log_level(struct ndpi_detection_module_struct *ndpi_str, u_int l){ ndpi_str->ndpi_log_level = l; } /* ******************************************************************** */ /* LRU cache */ -struct ndpi_lru_cache* ndpi_lru_cache_init(u_int32_t num_entries) { - struct ndpi_lru_cache *c = (struct ndpi_lru_cache*)ndpi_malloc(sizeof(struct ndpi_lru_cache)); +struct ndpi_lru_cache *ndpi_lru_cache_init(u_int32_t num_entries) { + struct ndpi_lru_cache *c = (struct ndpi_lru_cache *) ndpi_malloc(sizeof(struct ndpi_lru_cache)); - if(!c) return(NULL); + if(!c) + return(NULL); - c->entries = (struct ndpi_lru_cache_entry*)ndpi_calloc(num_entries, - sizeof(struct ndpi_lru_cache_entry)); + c->entries = (struct ndpi_lru_cache_entry *) ndpi_calloc(num_entries, sizeof(struct ndpi_lru_cache_entry)); if(!c->entries) { ndpi_free(c); @@ -6384,12 +6461,14 @@ void ndpi_lru_free_cache(struct ndpi_lru_cache *c) { ndpi_free(c); } -u_int8_t ndpi_lru_find_cache(struct ndpi_lru_cache *c, u_int32_t key, u_int16_t *value, u_int8_t clean_key_when_found) { +u_int8_t ndpi_lru_find_cache(struct ndpi_lru_cache *c, u_int32_t key, + u_int16_t *value, u_int8_t clean_key_when_found) { u_int32_t slot = key % c->num_entries; if(c->entries[slot].is_full) { *value = c->entries[slot].value; - if(clean_key_when_found) c->entries[slot].is_full = 0; + if(clean_key_when_found) + c->entries[slot].is_full = 0; return(1); } else return(0); @@ -6398,73 +6477,63 @@ u_int8_t ndpi_lru_find_cache(struct ndpi_lru_cache *c, u_int32_t key, u_int16_t void ndpi_lru_add_to_cache(struct ndpi_lru_cache *c, u_int32_t key, u_int16_t value) { u_int32_t slot = key % c->num_entries; - c->entries[slot].is_full = 1, - c->entries[slot].key = key, - c->entries[slot].value = value; -} - -/* ******************************************************************** */ - -/* - NOTE: - - Leave fields empty/zero when information is missing (e.g. with ICMP ports are zero) - - The hash_buf most be 30+1 bits or longer - - Return code: 0 = OK, -1 otherwise -*/ - -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, - u_int8_t icmp_type, u_int8_t icmp_code, - u_char *hash_buf, u_int8_t hash_buf_len) { - - return(0); /* OK */ -} - -int ndpi_flowv6_flow_hash(u_int8_t l4_proto, struct ndpi_in6_addr *src_ip, struct ndpi_in6_addr *dst_ip, - u_int16_t src_port, u_int16_t dst_port, u_int8_t icmp_type, u_int8_t icmp_code, - u_char *hash_buf, u_int8_t hash_buf_len) { - - return(0); /* OK */ + c->entries[slot].is_full = 1, c->entries[slot].key = key, c->entries[slot].value = value; } /* ******************************************************************** */ /* - This function tells if it's possible to further dissect a given flow - 0 - All possible dissection has been completed - 1 - Additional dissection is possible + This function tells if it's possible to further dissect a given flow + 0 - All possible dissection has been completed + 1 - Additional dissection is possible */ u_int8_t ndpi_extra_dissection_possible(struct ndpi_detection_module_struct *ndpi_str, struct ndpi_flow_struct *flow) { - u_int16_t proto = flow->detected_protocol_stack[1] ? flow->detected_protocol_stack[1] : flow->detected_protocol_stack[0]; + u_int16_t proto = + flow->detected_protocol_stack[1] ? flow->detected_protocol_stack[1] : flow->detected_protocol_stack[0]; #if 0 printf("[DEBUG] %s(%u.%u): %u\n", __FUNCTION__, flow->detected_protocol_stack[0], flow->detected_protocol_stack[1], - proto); + proto); #endif switch(proto) { case NDPI_PROTOCOL_TLS: - if(!flow->l4.tcp.tls_srv_cert_fingerprint_processed) - return(1); + if((!flow->l4.tcp.tls.certificate_processed) + || (flow->l4.tcp.tls.num_tls_blocks <= ndpi_str->num_tls_blocks_to_follow)) { + // printf("*** %u/%u\n", flow->l4.tcp.tls.num_tls_blocks, ndpi_str->num_tls_blocks_to_follow); + return(1); /* TODO: add check for TLS 1.3 */ + } break; case NDPI_PROTOCOL_HTTP: - if(flow->host_server_name[0] == '\0') + if((flow->host_server_name[0] == '\0') || (flow->http.response_status_code == 0)) return(1); break; case NDPI_PROTOCOL_DNS: - if((ndpi_str->dns_dont_dissect_response == 0) - && (flow->protos.dns.num_answers == 0)) + case NDPI_PROTOCOL_MDNS: + if(flow->protos.dns.num_answers == 0) + return(1); + break; + + case NDPI_PROTOCOL_FTP_CONTROL: + case NDPI_PROTOCOL_MAIL_POP: + case NDPI_PROTOCOL_MAIL_IMAP: + case NDPI_PROTOCOL_MAIL_SMTP: + if(flow->protos.ftp_imap_pop_smtp.password[0] == '\0') return(1); break; case NDPI_PROTOCOL_SSH: - if((flow->protos.ssh.hassh_client[0] == '\0') - || (flow->protos.ssh.hassh_server[0] == '\0')) + if((flow->protos.ssh.hassh_client[0] == '\0') || (flow->protos.ssh.hassh_server[0] == '\0')) + return(1); + break; + + case NDPI_PROTOCOL_TELNET: + if(!flow->protos.telnet.password_detected) return(1); break; } @@ -6474,20 +6543,20 @@ u_int8_t ndpi_extra_dissection_possible(struct ndpi_detection_module_struct *ndp /* ******************************************************************** */ -const char* ndpi_get_l4_proto_name(ndpi_l4_proto_info proto) { +const char *ndpi_get_l4_proto_name(ndpi_l4_proto_info proto) { switch(proto) { case ndpi_l4_proto_unknown: return(""); break; - + case ndpi_l4_proto_tcp_only: return("TCP"); break; - + case ndpi_l4_proto_udp_only: return("UDP"); break; - + case ndpi_l4_proto_tcp_and_udp: return("TCP/UDP"); break; @@ -6500,15 +6569,356 @@ const char* ndpi_get_l4_proto_name(ndpi_l4_proto_info proto) { ndpi_l4_proto_info ndpi_get_l4_proto_info(struct ndpi_detection_module_struct *ndpi_struct, u_int16_t ndpi_proto_id) { - if(ndpi_proto_id < ndpi_struct->ndpi_num_supported_protocols) { u_int16_t idx = ndpi_struct->proto_defaults[ndpi_proto_id].protoIdx; NDPI_SELECTION_BITMASK_PROTOCOL_SIZE bm = ndpi_struct->callback_buffer[idx].ndpi_selection_bitmask; - if(bm & NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP) return(ndpi_l4_proto_tcp_only); - else if(bm & NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP) return(ndpi_l4_proto_udp_only); - else if(bm & NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP) return(ndpi_l4_proto_tcp_and_udp); + if(bm & NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP) + return(ndpi_l4_proto_tcp_only); + else if(bm & NDPI_SELECTION_BITMASK_PROTOCOL_INT_UDP) + return(ndpi_l4_proto_udp_only); + else if(bm & NDPI_SELECTION_BITMASK_PROTOCOL_INT_TCP_OR_UDP) + return(ndpi_l4_proto_tcp_and_udp); } return(ndpi_l4_proto_unknown); /* default */ } + +/* ******************************************************************** */ + +ndpi_ptree_t *ndpi_ptree_create(void) { + ndpi_ptree_t *tree = (ndpi_ptree_t *) ndpi_malloc(sizeof(ndpi_ptree_t)); + + if(tree) { + tree->v4 = ndpi_New_Patricia(32); + tree->v6 = ndpi_New_Patricia(128); + + if((!tree->v4) || (!tree->v6)) { + ndpi_ptree_destroy(tree); + return(NULL); + } + } + + return(tree); +} + +/* ******************************************************************** */ + +void ndpi_ptree_destroy(ndpi_ptree_t *tree) { + if(tree) { + if(tree->v4) + ndpi_Destroy_Patricia(tree->v4, free_ptree_data); + if(tree->v6) + ndpi_Destroy_Patricia(tree->v6, free_ptree_data); + + ndpi_free(tree); + } +} + +/* ******************************************************************** */ + +int ndpi_ptree_insert(ndpi_ptree_t *tree, const ndpi_ip_addr_t *addr, + u_int8_t bits, uint user_data) { + u_int8_t is_v6 = ndpi_is_ipv6(addr); + patricia_tree_t *ptree = is_v6 ? tree->v6 : tree->v4; + prefix_t prefix; + patricia_node_t *node; + + if(bits > ptree->maxbits) + return(-1); + + if(is_v6) + fill_prefix_v6(&prefix, (const struct in6_addr *) &addr->ipv6, bits, ptree->maxbits); + else + fill_prefix_v4(&prefix, (const struct in_addr *) &addr->ipv4, bits, ptree->maxbits); + + /* Verify that the node does not already exist */ + node = ndpi_patricia_search_best(ptree, &prefix); + + if(node && (node->prefix->bitlen == bits)) + return(-2); + + node = ndpi_patricia_lookup(ptree, &prefix); + + if(node != NULL) { + node->value.uv.user_value = user_data, node->value.uv.additional_user_value = 0; + + return(0); + } + + return(-3); +} + +/* ******************************************************************** */ + +int ndpi_ptree_match_addr(ndpi_ptree_t *tree, + const ndpi_ip_addr_t *addr, uint *user_data) { + u_int8_t is_v6 = ndpi_is_ipv6(addr); + patricia_tree_t *ptree = is_v6 ? tree->v6 : tree->v4; + prefix_t prefix; + patricia_node_t *node; + int bits = ptree->maxbits; + + if(is_v6) + fill_prefix_v6(&prefix, (const struct in6_addr *) &addr->ipv6, bits, ptree->maxbits); + else + fill_prefix_v4(&prefix, (const struct in_addr *) &addr->ipv4, bits, ptree->maxbits); + + node = ndpi_patricia_search_best(ptree, &prefix); + + if(node) { + *user_data = node->value.uv.user_value; + + return(0); + } + + return(-1); +} + +/* ******************************************************************** */ + +void ndpi_md5(const u_char *data, size_t data_len, u_char hash[16]) { + ndpi_MD5_CTX ctx; + + ndpi_MD5Init(&ctx); + ndpi_MD5Update(&ctx, data, data_len); + ndpi_MD5Final(hash, &ctx); +} + +/* ******************************************************************** */ + +static int enough(int a, int b) { + u_int8_t percentage = 20; + + if(b == 0) return(0); + if(a == 0) return(1); + + if(b > (((a+1)*percentage)/100)) return(1); + + return(0); +} + +/* ******************************************************************** */ + +static u_int8_t endsWith(char *str, char *ends, u_int8_t ends_len) { + u_int str_len = str ? strlen(str) : 0; + u_int8_t rc; + + if(str_len < ends_len) return(0); + + rc = (strncmp(&str[str_len-ends_len], ends, ends_len) != 0) ? 0 : 1; + +#ifdef DGA_DEBUG + printf("[DGA] %s / %s [rc: %u]\n", str, ends, rc); +#endif + + return(rc); +} + +/* ******************************************************************** */ + +int ndpi_check_dga_name(struct ndpi_detection_module_struct *ndpi_str, + struct ndpi_flow_struct *flow, + char *name, u_int8_t is_hostname) { + int len, rc = 0; + u_int8_t max_num_char_repetitions = 0, last_char = 0, num_char_repetitions = 0, num_dots = 0; + u_int8_t max_domain_element_len = 0, curr_domain_element_len = 0, first_element_is_numeric = 1; + + if(!name) return(0); + +#ifdef DGA_DEBUG + printf("[DGA] %s\n", name); +#endif + + len = strlen(name); + + if(len >= 5) { + int i, j, num_found = 0, num_impossible = 0, num_bigram_checks = 0, num_digits = 0, num_vowels = 0, num_words = 0; + char tmp[128], *word, *tok_tmp; + u_int max_tmp_len = sizeof(tmp)-1; + + len = snprintf(tmp, max_tmp_len, "%s", name); + if(len < 0) { +#ifdef DGA_DEBUG + printf("[DGA] Too short"); +#endif + return(0); + } else + tmp[len < max_tmp_len ? len : max_tmp_len] = '\0'; + + for(i=0, j=0; (i max_num_char_repetitions) + max_num_char_repetitions = num_char_repetitions; + } else + num_char_repetitions = 1, last_char = tmp[j]; + + switch(tmp[j]) { + case '.': + case '-': + case '_': + case '/': + case ')': + case '(': + case ';': + case ':': + case '[': + case ']': + case ' ': + /* + Domain/word separator chars + + NOTE: + this function is used also to detect other type of issues + such as invalid/suspiciuous user agent + */ + if(curr_domain_element_len > max_domain_element_len) + max_domain_element_len = curr_domain_element_len; + + curr_domain_element_len = 0; + break; + + default: + curr_domain_element_len++; + break; + } + + j++; + } + + if(curr_domain_element_len > max_domain_element_len) + max_domain_element_len = curr_domain_element_len; + +#ifdef DGA_DEBUG + printf("[DGA] [max_num_char_repetitions: %u][max_domain_element_len: %u]\n", + max_num_char_repetitions, max_domain_element_len); +#endif + + if( + (is_hostname + && (num_dots > 5) + && (!first_element_is_numeric) + && (!endsWith(tmp, "in-addr.arpa", 12)) + ) + || (max_num_char_repetitions > 5 /* num or consecutive repeated chars */) + /* + In case of a name with too many consecutive chars an alert is triggered + This is the case for instance of the wildcard DNS query used by NetBIOS + (ckaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) and that can be exploited + for reflection attacks + - https://www.akamai.com/uk/en/multimedia/documents/state-of-the-internet/ddos-reflection-netbios-name-server-rpc-portmap-sentinel-udp-threat-advisory.pdf + - http://ubiqx.org/cifs/NetBIOS.html + */ + || (max_domain_element_len >= 19 /* word too long. Example bbcbedxhgjmdobdprmen.com */) + ) { + if(flow) NDPI_SET_BIT(flow->risk, NDPI_SUSPICIOUS_DGA_DOMAIN); +#ifdef DGA_DEBUG + printf("[DGA] Found!"); +#endif + return(1); + } + + tmp[j] = '\0'; + len = j; + + for(word = strtok_r(tmp, ".", &tok_tmp); ; word = strtok_r(NULL, ".", &tok_tmp)) { + if(!word) break; + + num_words++; + + if(strlen(word) < 3) continue; + +#ifdef DGA_DEBUG + printf("-> %s [%s][len: %u]\n", word, name, (unsigned int)strlen(word)); +#endif + + for(i = 0; word[i+1] != '\0'; i++) { + if(isdigit(word[i])) { + num_digits++; + + // if(!isdigit(word[i+1])) num_impossible++; + + continue; + } + + switch(word[i]) { + case '_': + case '-': + case ':': + continue; + break; + + case '.': + continue; + break; + } + + switch(word[i]) { + case 'a': + case 'e': + case 'i': + case 'o': + case 'u': + num_vowels++; + break; + } + + if(isdigit(word[i+1])) { + num_digits++; + // num_impossible++; + continue; + } + + num_bigram_checks++; + +#ifdef DGA_DEBUG + printf("-> Checking %c%c\n", word[i], word[i+1]); +#endif + + if(ndpi_match_bigram(ndpi_str, + &ndpi_str->impossible_bigrams_automa, + &word[i])) { +#ifdef DGA_DEBUG + printf("IMPOSSIBLE %s\n", &word[i]); +#endif + num_impossible++; + } else if(ndpi_match_bigram(ndpi_str, &ndpi_str->bigrams_automa, &word[i])) { + num_found++; + } + } /* for */ + } /* for */ + +#ifdef DGA_DEBUG + printf("[num_found: %u][num_impossible: %u][num_digits: %u][num_bigram_checks: %u][num_vowels: %u/%u]\n", + num_found, num_impossible, num_digits, num_bigram_checks, num_vowels, j-num_vowels); +#endif + + if(num_bigram_checks + && ((num_found == 0) || ((num_digits > 5) && (num_words <= 3)) || enough(num_found, num_impossible))) + rc = 1; + + if(rc && flow) + NDPI_SET_BIT(flow->risk, NDPI_SUSPICIOUS_DGA_DOMAIN); + +#ifdef DGA_DEBUG + if(rc) + printf("DGA %s [num_found: %u][num_impossible: %u]\n", + name, num_found, num_impossible); +#endif + } + +#ifdef DGA_DEBUG + printf("[DGA] Result: %u", rc); +#endif + + return(rc); +} diff --git a/src/lib/ndpi_serializer.c b/src/lib/ndpi_serializer.c index 49f29a4..e60c966 100644 --- a/src/lib/ndpi_serializer.c +++ b/src/lib/ndpi_serializer.c @@ -1,10 +1,7 @@ /* * ndpi_serializer.c * - * Copyright (C) 2011-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -27,6 +24,7 @@ #include #include +#include #include #include "ndpi_api.h" @@ -41,30 +39,44 @@ #include #endif +#ifdef WIN32 +#define NDPI_I64_FORMAT "%" PRId64 +#define NDPI_U64_FORMAT "%" PRIu64 +#else +#define NDPI_I64_FORMAT "%lld" +#define NDPI_U64_FORMAT "%llu" +#endif + /* ********************************** */ -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); u.lv[1] = htonl(v & 0xFFFFFFFFULL); - return u.llv; + + return(u.llv); } /* ********************************** */ -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; - return ((u_int64_t)ntohl(u.lv[0]) << 32) | (u_int64_t)ntohl(u.lv[1]); + + return((u_int64_t)ntohl(u.lv[0]) << 32) | (u_int64_t)ntohl(u.lv[1]); } /* ********************************** */ static int ndpi_is_number(const char *str, u_int32_t str_len) { int i; - for (i = 0; i < str_len; i++) - if (!isdigit(str[i])) return 0; - return 1; + + for(i = 0; i < str_len; i++) + if(!isdigit(str[i])) return(0); + + return(1); } /* ********************************** */ @@ -80,7 +92,7 @@ static int ndpi_json_string_escape(const char *src, int src_len, char *dst, int dst[j++] = '"'; - for (i = 0; i < src_len && j < dst_max_len; i++) { + for(i = 0; i < src_len && j < dst_max_len; i++) { c = src[i]; @@ -122,8 +134,34 @@ static int ndpi_json_string_escape(const char *src, int src_len, char *dst, int dst[j++] = '"'; dst[j+1] = '\0'; - return j; + return(j); +} + +/* ********************************** */ + +#if UNUSED +/* + * Similar to snprintf, this returns the number of bytes actually written + * in any case (unlike snprintf which returns, if the output is truncated, + * the number of bytes which *would have been* written, and a negative + * value on failures) + */ +static inline int ndpi_snappend(char *buf, size_t size, const char *fmt, ...) { + int wlen; + va_list va; + + va_start(va, fmt); + wlen = snprintf(buf, size, fmt, va); + va_end(va); + + if (wlen < 0) + wlen = 0; + else if (wlen >= size) + wlen = size-1; + + return wlen; } +#endif /* ********************************** */ @@ -135,15 +173,27 @@ void ndpi_reset_serializer(ndpi_serializer *_serializer) { if(serializer->fmt == ndpi_serialization_format_json) { u_int32_t buff_diff; - serializer->status.size_used = 0; - buff_diff = serializer->buffer_size - serializer->status.size_used; + serializer->status.buffer.size_used = 0; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; /* Note: please keep a space at the beginning as it is used for arrays when an end-of-record is used */ - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, " {}"); - } else if(serializer->fmt == ndpi_serialization_format_csv) - serializer->status.size_used = 0; - else /* ndpi_serialization_format_tlv */ - serializer->status.size_used = 2 * sizeof(u_int8_t); + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, " {}"); + } else if(serializer->fmt == ndpi_serialization_format_csv) { + serializer->status.header.size_used = 0; + serializer->status.buffer.size_used = 0; + } else { /* ndpi_serialization_format_tlv */ + serializer->status.buffer.size_used = 2 * sizeof(u_int8_t); + } +} + +/* ********************************** */ + +static int ndpi_init_serializer_buffer(ndpi_private_serializer_buffer *buffer, u_int32_t buffer_size) { + buffer->initial_size = buffer->size = buffer_size; + buffer->data = (u_int8_t *) calloc(buffer->size, sizeof(u_int8_t)); + if(buffer->data == NULL) + return -1; + return 0; } /* ********************************** */ @@ -155,16 +205,22 @@ int ndpi_init_serializer_ll(ndpi_serializer *_serializer, memset(serializer, 0, sizeof(ndpi_private_serializer)); - serializer->initial_buffer_size = serializer->buffer_size = buffer_size; - serializer->buffer = (u_int8_t *) malloc(serializer->buffer_size * sizeof(u_int8_t)); + serializer->fmt = fmt; - if(serializer->buffer == NULL) + if (ndpi_init_serializer_buffer(&serializer->buffer, buffer_size) != 0) return(-1); + + if(serializer->fmt == ndpi_serialization_format_json) { + /* nothing to do */ - serializer->fmt = fmt; + } else if (fmt == ndpi_serialization_format_csv) { + if (ndpi_init_serializer_buffer(&serializer->header, NDPI_SERIALIZER_DEFAULT_HEADER_SIZE) != 0) + return(-1); - serializer->buffer[0] = 1; /* version */ - serializer->buffer[1] = (u_int8_t) fmt; + } else /* ndpi_serialization_format_tlv */ { + serializer->buffer.data[0] = 1; /* version */ + serializer->buffer.data[1] = (u_int8_t) fmt; + } serializer->csv_separator[0] = ','; serializer->csv_separator[1] = '\0'; @@ -178,23 +234,114 @@ int ndpi_init_serializer_ll(ndpi_serializer *_serializer, int ndpi_init_serializer(ndpi_serializer *_serializer, ndpi_serialization_format fmt) { - return ndpi_init_serializer_ll(_serializer, fmt, NDPI_SERIALIZER_DEFAULT_BUFFER_SIZE); + return(ndpi_init_serializer_ll(_serializer, fmt, NDPI_SERIALIZER_DEFAULT_BUFFER_SIZE)); +} + +/* ********************************** */ + +static inline int ndpi_extend_serializer_buffer(ndpi_private_serializer_buffer *buffer, u_int32_t min_len) { + u_int32_t new_size; + void *r; + + if(min_len < NDPI_SERIALIZER_DEFAULT_BUFFER_INCR) { + if(buffer->initial_size < NDPI_SERIALIZER_DEFAULT_BUFFER_INCR) { + if(min_len < buffer->initial_size) + min_len = buffer->initial_size; + } else { + min_len = NDPI_SERIALIZER_DEFAULT_BUFFER_INCR; + } + } + + new_size = buffer->size + min_len; + new_size = ((new_size / 4) + 1) * 4; /* required by zmq encryption */ + + r = realloc((void *) buffer->data, new_size); + + if(r == NULL) + return(-1); + + buffer->data = r; + buffer->size = new_size; + + return(0); +} + +/* ********************************** */ + +static inline int ndpi_serializer_check_header_room(ndpi_private_serializer *serializer, u_int32_t needed) { + u_int32_t buff_diff = serializer->header.size - serializer->status.header.size_used; + + if (buff_diff < needed) + if (ndpi_extend_serializer_buffer(&serializer->header, needed - buff_diff) < 0) + return -1; + + buff_diff = serializer->header.size - serializer->status.header.size_used; + + return buff_diff; +} + +/* ********************************** */ + +static inline int ndpi_serializer_header_uint32(ndpi_private_serializer *serializer, u_int32_t key) { + int room; + + if (serializer->status.flags & NDPI_SERIALIZER_STATUS_HDR_DONE) + return 0; + + room = ndpi_serializer_check_header_room(serializer, 12); + + if (room < 0) + return -1; + + serializer->status.header.size_used += snprintf((char *) &serializer->header.data[serializer->status.header.size_used], + room, "%s%u", (serializer->status.header.size_used > 0) ? serializer->csv_separator : "", key); + + return 0; +} + +/* ********************************** */ + +static inline int ndpi_serializer_header_string(ndpi_private_serializer *serializer, const char *key, u_int16_t klen) { + int room; + + if (serializer->status.flags & NDPI_SERIALIZER_STATUS_HDR_DONE) + return 0; + + room = ndpi_serializer_check_header_room(serializer, klen + 4); + + if (room < 0) + return -1; + + if (serializer->status.header.size_used > 0) { + int slen = strlen(serializer->csv_separator); + memcpy(&serializer->header.data[serializer->status.header.size_used], serializer->csv_separator, slen); + serializer->status.header.size_used += slen; + } + + if (klen > 0) { + memcpy(&serializer->header.data[serializer->status.header.size_used], key, klen); + serializer->status.header.size_used += klen; + } + + serializer->header.data[serializer->status.header.size_used] = '\0'; + + return 0; } /* ********************************** */ char* ndpi_serializer_get_buffer(ndpi_serializer *_serializer, u_int32_t *buffer_len) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - char *buf = (char*)serializer->buffer; + char *buf = (char*)serializer->buffer.data; - /* NULL terminate the buffer if there is space available */ - if(serializer->buffer_size > serializer->status.size_used) - serializer->buffer[serializer->status.size_used] = '\0'; + /* NULL terminate the buffer */ + if(serializer->buffer.size > serializer->status.buffer.size_used) /* safety check */ + serializer->buffer.data[serializer->status.buffer.size_used] = '\0'; - *buffer_len = serializer->status.size_used; + *buffer_len = serializer->status.buffer.size_used; if(serializer->fmt == ndpi_serialization_format_json) { - while(buf[0] == '\0') + while((buf[0] == '\0') || (buf[0] == ' ')) buf++, *buffer_len = *buffer_len - 1; } @@ -204,7 +351,13 @@ char* ndpi_serializer_get_buffer(ndpi_serializer *_serializer, u_int32_t *buffer /* ********************************** */ u_int32_t ndpi_serializer_get_buffer_len(ndpi_serializer *_serializer) { - return(((ndpi_private_serializer*)_serializer)->status.size_used); + return(((ndpi_private_serializer*)_serializer)->status.buffer.size_used); +} + +/* ********************************** */ + +u_int32_t ndpi_serializer_get_internal_buffer_size(ndpi_serializer *_serializer) { + return(((ndpi_private_serializer*)_serializer)->buffer.size); } /* ********************************** */ @@ -213,10 +366,10 @@ int ndpi_serializer_set_buffer_len(ndpi_serializer *_serializer, u_int32_t l) { ndpi_private_serializer *p = (ndpi_private_serializer*)_serializer; if(p) { - if(p->buffer_size <= l) + if(p->buffer.size <= l) return(-1); /* Invalid size */ - p->status.size_used = l; + p->status.buffer.size_used = l; return(0); } @@ -225,51 +378,56 @@ int ndpi_serializer_set_buffer_len(ndpi_serializer *_serializer, u_int32_t l) { /* ********************************** */ -void ndpi_serializer_set_csv_separator(ndpi_serializer *_serializer, char separator) { +/* Return the header automatically built from keys (CSV only) */ +char* ndpi_serializer_get_header(ndpi_serializer *_serializer, u_int32_t *buffer_len) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; + char *buf = (char*)serializer->header.data; - serializer->csv_separator[0] = separator; + if(buf == NULL) { + *buffer_len = 0; + return ""; + } + + /* NULL terminate the buffer */ + if(serializer->header.size > serializer->status.header.size_used) /* safety check */ + serializer->header.data[serializer->status.header.size_used] = '\0'; + + *buffer_len = serializer->status.header.size_used; + + return(buf); } /* ********************************** */ -void ndpi_term_serializer(ndpi_serializer *_serializer) { +ndpi_serialization_format ndpi_serializer_get_format(ndpi_serializer *_serializer) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - - if(serializer->buffer) { - free(serializer->buffer); - serializer->buffer_size = 0; - serializer->buffer = NULL; - } + return serializer->fmt; } /* ********************************** */ -static inline int ndpi_extend_serializer_buffer(ndpi_serializer *_serializer, u_int32_t min_len) { - u_int32_t new_size; - void *r; +void ndpi_serializer_set_csv_separator(ndpi_serializer *_serializer, char separator) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - if (min_len < NDPI_SERIALIZER_DEFAULT_BUFFER_INCR) { - if (serializer->initial_buffer_size < NDPI_SERIALIZER_DEFAULT_BUFFER_INCR) { - if (min_len < serializer->initial_buffer_size) - min_len = serializer->initial_buffer_size; - } else { - min_len = NDPI_SERIALIZER_DEFAULT_BUFFER_INCR; - } - } - - new_size = serializer->buffer_size + min_len; + serializer->csv_separator[0] = separator; +} - r = realloc((void *) serializer->buffer, new_size); +/* ********************************** */ - if(r == NULL) - return(-1); +void ndpi_term_serializer(ndpi_serializer *_serializer) { + ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - serializer->buffer = r; - serializer->buffer_size = new_size; + if(serializer->buffer.data) { + free(serializer->buffer.data); + serializer->buffer.size = 0; + serializer->buffer.data = NULL; + } - return(0); + if(serializer->header.data) { + free(serializer->header.data); + serializer->header.size = 0; + serializer->header.data = NULL; + } } /* ********************************** */ @@ -278,8 +436,8 @@ static inline void ndpi_serialize_single_uint8(ndpi_private_serializer *serializ u_int8_t s) { u_int8_t v = s; - memcpy(&serializer->buffer[serializer->status.size_used], &v, sizeof(u_int8_t)); - serializer->status.size_used += sizeof(u_int8_t); + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], &v, sizeof(u_int8_t)); + serializer->status.buffer.size_used += sizeof(u_int8_t); } /* ********************************** */ @@ -288,8 +446,8 @@ static inline void ndpi_serialize_single_uint16(ndpi_private_serializer *seriali u_int16_t s) { u_int16_t v = htons(s); - memcpy(&serializer->buffer[serializer->status.size_used], &v, sizeof(u_int16_t)); - serializer->status.size_used += sizeof(u_int16_t); + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], &v, sizeof(u_int16_t)); + serializer->status.buffer.size_used += sizeof(u_int16_t); } /* ********************************** */ @@ -298,8 +456,8 @@ static inline void ndpi_serialize_single_uint32(ndpi_private_serializer *seriali u_int32_t s) { u_int32_t v = htonl(s); - memcpy(&serializer->buffer[serializer->status.size_used], &v, sizeof(u_int32_t)); - serializer->status.size_used += sizeof(u_int32_t); + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], &v, sizeof(u_int32_t)); + serializer->status.buffer.size_used += sizeof(u_int32_t); } /* ********************************** */ @@ -308,8 +466,8 @@ static inline void ndpi_serialize_single_uint64(ndpi_private_serializer *seriali u_int64_t s) { u_int64_t v = ndpi_htonll(s); - memcpy(&serializer->buffer[serializer->status.size_used], &v, sizeof(u_int64_t)); - serializer->status.size_used += sizeof(u_int64_t); + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], &v, sizeof(u_int64_t)); + serializer->status.buffer.size_used += sizeof(u_int64_t); } /* ********************************** */ @@ -317,8 +475,8 @@ static inline void ndpi_serialize_single_uint64(ndpi_private_serializer *seriali /* TODO: fix portability across platforms */ static inline void ndpi_serialize_single_float(ndpi_private_serializer *serializer, float s) { - memcpy(&serializer->buffer[serializer->status.size_used], &s, sizeof(s)); - serializer->status.size_used += sizeof(float); + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], &s, sizeof(s)); + serializer->status.buffer.size_used += sizeof(float); } /* ********************************** */ @@ -327,69 +485,69 @@ static inline void ndpi_serialize_single_string(ndpi_private_serializer *seriali const char *s, u_int16_t slen) { u_int16_t l = htons(slen); - memcpy(&serializer->buffer[serializer->status.size_used], &l, sizeof(u_int16_t)); - serializer->status.size_used += sizeof(u_int16_t); + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], &l, sizeof(u_int16_t)); + serializer->status.buffer.size_used += sizeof(u_int16_t); if(slen > 0) - memcpy(&serializer->buffer[serializer->status.size_used], s, slen); + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], s, slen); - serializer->status.size_used += slen; + serializer->status.buffer.size_used += slen; } /* ********************************** */ static inline void ndpi_deserialize_single_uint8(ndpi_private_deserializer *deserializer, u_int32_t offset, u_int8_t *s) { - *s = (*((u_int8_t *) &deserializer->buffer[offset])); + *s = (*((u_int8_t *) &deserializer->buffer.data[offset])); } /* ********************************** */ static inline void ndpi_deserialize_single_uint16(ndpi_private_deserializer *deserializer, u_int32_t offset, u_int16_t *s) { - *s = ntohs(*((u_int16_t *) &deserializer->buffer[offset])); + *s = ntohs(*((u_int16_t *) &deserializer->buffer.data[offset])); } /* ********************************** */ static inline void ndpi_deserialize_single_uint32(ndpi_private_deserializer *deserializer, u_int32_t offset, u_int32_t *s) { - *s = ntohl(*((u_int32_t *) &deserializer->buffer[offset])); + *s = ntohl(*((u_int32_t *) &deserializer->buffer.data[offset])); } /* ********************************** */ static inline void ndpi_deserialize_single_int8(ndpi_private_deserializer *deserializer, u_int32_t offset, int8_t *s) { - *s = (*((int8_t *) &deserializer->buffer[offset])); + *s = (*((int8_t *) &deserializer->buffer.data[offset])); } /* ********************************** */ static inline void ndpi_deserialize_single_int16(ndpi_private_deserializer *deserializer, u_int32_t offset, int16_t *s) { - *s = ntohs(*((int16_t *) &deserializer->buffer[offset])); + *s = ntohs(*((int16_t *) &deserializer->buffer.data[offset])); } /* ********************************** */ static inline void ndpi_deserialize_single_int32(ndpi_private_deserializer *deserializer, u_int32_t offset, int32_t *s) { - *s = ntohl(*((int32_t *) &deserializer->buffer[offset])); + *s = ntohl(*((int32_t *) &deserializer->buffer.data[offset])); } /* ********************************** */ static inline void ndpi_deserialize_single_uint64(ndpi_private_deserializer *deserializer, u_int32_t offset, u_int64_t *s) { - *s = ndpi_ntohll(*(u_int64_t*)&deserializer->buffer[offset]); + *s = ndpi_ntohll(*(u_int64_t*)&deserializer->buffer.data[offset]); } /* ********************************** */ static inline void ndpi_deserialize_single_int64(ndpi_private_deserializer *deserializer, u_int32_t offset, int64_t *s) { - *s = ndpi_ntohll(*(int64_t*)&deserializer->buffer[offset]); + *s = ndpi_ntohll(*(int64_t*)&deserializer->buffer.data[offset]); } /* ********************************** */ @@ -397,68 +555,151 @@ static inline void ndpi_deserialize_single_int64(ndpi_private_deserializer *dese /* TODO: fix portability across platforms */ static inline void ndpi_deserialize_single_float(ndpi_private_deserializer *deserializer, u_int32_t offset, float *s) { - *s = *(float*)&deserializer->buffer[offset]; + *s = *(float*)&deserializer->buffer.data[offset]; } /* ********************************** */ static inline void ndpi_deserialize_single_string(ndpi_private_deserializer *deserializer, u_int32_t offset, ndpi_string *v) { - v->str_len = ntohs(*((u_int16_t *) &deserializer->buffer[offset])); - v->str = (char *) &deserializer->buffer[offset + sizeof(u_int16_t)]; + v->str_len = ntohs(*((u_int16_t *) &deserializer->buffer.data[offset])); + v->str = (char *) &deserializer->buffer.data[offset + sizeof(u_int16_t)]; +} + +/* ********************************** */ + +/* + This function helps extending the existing serializer by adding a new + element in the array. This element is handled as raw without any + further check whatsoever +*/ +int ndpi_serialize_raw_record(ndpi_serializer *_serializer, + u_char *record, u_int32_t record_len) { + ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + u_int16_t needed = record_len; + u_int8_t add_comma = 0; + + if(serializer->fmt == ndpi_serialization_format_json) { + needed += 1; + + if(serializer->status.buffer.size_used == 3) /* Empty buffer [{} */ + serializer->status.buffer.size_used = 2; /* Remove {} */ + else + needed += 2, add_comma = 1; + } + + if(buff_diff < needed) { + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) + return(-1); + + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + if(serializer->fmt == ndpi_serialization_format_json) { + if(add_comma) + serializer->buffer.data[serializer->status.buffer.size_used-1] = ','; + else + serializer->status.buffer.size_used--; + } + + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], record, record_len); + serializer->status.buffer.size_used += record_len; + + if(serializer->fmt == ndpi_serialization_format_json) { + serializer->buffer.data[serializer->status.buffer.size_used] = ']'; + if(add_comma) serializer->status.buffer.size_used++; + } + + ndpi_serialize_end_of_record(_serializer); + + return(0); } /* ********************************** */ int ndpi_serialize_end_of_record(ndpi_serializer *_serializer) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; - u_int16_t needed = - sizeof(u_int8_t) /* type */; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + u_int16_t needed = sizeof(u_int8_t) /* type */; - if(serializer->fmt == ndpi_serialization_format_json) + if(serializer->fmt == ndpi_serialization_format_json || + serializer->fmt == ndpi_serialization_format_csv) needed += 1; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } - if(serializer->fmt == ndpi_serialization_format_json) { + if(serializer->fmt == ndpi_serialization_format_csv) { + serializer->buffer.data[serializer->status.buffer.size_used] = '\n'; + serializer->status.buffer.size_used += 1; + serializer->buffer.data[serializer->status.buffer.size_used] = '\0'; + serializer->status.flags |= NDPI_SERIALIZER_STATUS_HDR_DONE; + serializer->status.flags |= NDPI_SERIALIZER_STATUS_EOR; + } else if(serializer->fmt == ndpi_serialization_format_json) { if(!(serializer->status.flags & NDPI_SERIALIZER_STATUS_ARRAY)) { - serializer->buffer[0] = '['; - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], - buff_diff, "]"); + serializer->buffer.data[0] = '['; + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "]"); } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_ARRAY | NDPI_SERIALIZER_STATUS_EOR; serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_COMMA; - } else { - serializer->buffer[serializer->status.size_used++] = ndpi_serialization_end_of_record; + } else /* ndpi_serialization_format_tlv */ { + serializer->buffer.data[serializer->status.buffer.size_used++] = ndpi_serialization_end_of_record; } + serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_NOT_EMPTY; + return(0); } /* ********************************** */ +static inline void ndpi_serialize_csv_pre(ndpi_private_serializer *serializer) { + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_EOR) { + serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_EOR; + } else if (serializer->status.buffer.size_used == 0) { + /* nothing to do */ + } else { + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, "%s", + serializer->csv_separator); + } +} + +/* ********************************** */ + static inline void ndpi_serialize_json_pre(ndpi_serializer *_serializer) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; if(serializer->status.flags & NDPI_SERIALIZER_STATUS_EOR) { - serializer->status.size_used--; /* Remove ']' */ + serializer->status.buffer.size_used--; /* Remove ']' */ serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_EOR; - serializer->buffer[serializer->status.size_used++] = ','; - serializer->buffer[serializer->status.size_used++] = '{'; + serializer->buffer.data[serializer->status.buffer.size_used++] = ','; + serializer->buffer.data[serializer->status.buffer.size_used++] = '{'; } else { if(serializer->status.flags & NDPI_SERIALIZER_STATUS_ARRAY) - serializer->status.size_used--; /* Remove ']'*/ - serializer->status.size_used--; /* Remove '}'*/ + serializer->status.buffer.size_used--; /* Remove ']' */ - if (serializer->status.flags & NDPI_SERIALIZER_STATUS_SOB) - serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_SOB; - else if(serializer->status.flags & NDPI_SERIALIZER_STATUS_COMMA) - serializer->buffer[serializer->status.size_used++] = ','; + serializer->status.buffer.size_used--; /* Remove '}' */ + + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST) { + serializer->status.buffer.size_used--; /* Remove ']' */ + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_SOL) + serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_SOL; + else + serializer->buffer.data[serializer->status.buffer.size_used++] = ','; + } else { + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_SOB) + serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_SOB; + else if(serializer->status.flags & NDPI_SERIALIZER_STATUS_COMMA) + serializer->buffer.data[serializer->status.buffer.size_used++] = ','; + } } } @@ -467,9 +708,13 @@ static inline void ndpi_serialize_json_pre(ndpi_serializer *_serializer) { static inline void ndpi_serialize_json_post(ndpi_serializer *_serializer) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - serializer->buffer[serializer->status.size_used++] = '}'; + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST) + serializer->buffer.data[serializer->status.buffer.size_used++] = ']'; + + serializer->buffer.data[serializer->status.buffer.size_used++] = '}'; + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_ARRAY) - serializer->buffer[serializer->status.size_used++] = ']'; + serializer->buffer.data[serializer->status.buffer.size_used++] = ']'; serializer->status.flags |= NDPI_SERIALIZER_STATUS_COMMA; } @@ -479,10 +724,10 @@ static inline void ndpi_serialize_json_post(ndpi_serializer *_serializer) { static inline ndpi_serialization_type ndpi_serialize_key_uint32(ndpi_private_serializer *serializer, u_int32_t key) { ndpi_serialization_type kt; - if (key <= 0xff) { + if(key <= 0xff) { ndpi_serialize_single_uint8(serializer, key); kt = ndpi_serialization_uint8; - } else if (key <= 0xffff) { + } else if(key <= 0xffff) { ndpi_serialize_single_uint16(serializer, key); kt = ndpi_serialization_uint16; } else { @@ -490,7 +735,7 @@ static inline ndpi_serialization_type ndpi_serialize_key_uint32(ndpi_private_ser kt = ndpi_serialization_uint32; } - return kt; + return(kt); } /* ********************************** */ @@ -498,7 +743,7 @@ static inline ndpi_serialization_type ndpi_serialize_key_uint32(ndpi_private_ser int ndpi_serialize_uint32_uint32(ndpi_serializer *_serializer, u_int32_t key, u_int32_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int16_t needed = sizeof(u_int8_t) /* type */ + sizeof(u_int32_t) /* key */ + @@ -508,31 +753,45 @@ int ndpi_serialize_uint32_uint32(ndpi_serializer *_serializer, needed += 24; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "\"%u\":%u", key, value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "\"%u\":", key); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "%u", value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%u", (serializer->status.size_used > 0) ? serializer->csv_separator : "", value); + if (ndpi_serializer_header_uint32(serializer, key) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%u", value); } else { ndpi_serialization_type kt; u_int8_t type = 0; - u_int32_t type_offset = serializer->status.size_used++; + u_int32_t type_offset = serializer->status.buffer.size_used++; kt = ndpi_serialize_key_uint32(serializer, key); type = (kt << 4); - if (value <= 0xff) { + if(value <= 0xff) { ndpi_serialize_single_uint8(serializer, value); type |= ndpi_serialization_uint8; - } else if (value <= 0xffff) { + } else if(value <= 0xffff) { ndpi_serialize_single_uint16(serializer, value); type |= ndpi_serialization_uint16; } else { @@ -540,9 +799,10 @@ int ndpi_serialize_uint32_uint32(ndpi_serializer *_serializer, type |= ndpi_serialization_uint32; } - serializer->buffer[type_offset] = type; + serializer->buffer.data[type_offset] = type; } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -551,7 +811,7 @@ int ndpi_serialize_uint32_uint32(ndpi_serializer *_serializer, int ndpi_serialize_uint32_uint64(ndpi_serializer *_serializer, u_int32_t key, u_int64_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int16_t needed = sizeof(u_int8_t) /* type */ + sizeof(u_int32_t) /* key */ + @@ -561,28 +821,40 @@ int ndpi_serialize_uint32_uint64(ndpi_serializer *_serializer, needed += 32; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "\"%u\":%llu", key, (unsigned long long)value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "\"%u\":", key); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + NDPI_U64_FORMAT, (unsigned long long)value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%llu", - (serializer->status.size_used > 0) ? serializer->csv_separator : "", - (unsigned long long)value); + if (ndpi_serializer_header_uint32(serializer, key) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + NDPI_U64_FORMAT, (unsigned long long)value); } else { - if (value <= 0xffffffff) { + if(value <= 0xffffffff) { return(ndpi_serialize_uint32_uint32(_serializer, key, value)); } else { ndpi_serialization_type kt; u_int8_t type = 0; - u_int32_t type_offset = serializer->status.size_used++; + u_int32_t type_offset = serializer->status.buffer.size_used++; kt = ndpi_serialize_key_uint32(serializer, key); type = (kt << 4); @@ -590,10 +862,11 @@ int ndpi_serialize_uint32_uint64(ndpi_serializer *_serializer, ndpi_serialize_single_uint64(serializer, value); type |= ndpi_serialization_uint64; - serializer->buffer[type_offset] = type; + serializer->buffer.data[type_offset] = type; } } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -602,7 +875,7 @@ int ndpi_serialize_uint32_uint64(ndpi_serializer *_serializer, int ndpi_serialize_uint32_int32(ndpi_serializer *_serializer, u_int32_t key, int32_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int16_t needed = sizeof(u_int8_t) /* type */ + sizeof(u_int32_t) /* key */ + @@ -612,31 +885,45 @@ int ndpi_serialize_uint32_int32(ndpi_serializer *_serializer, needed += 24; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "\"%u\":%d", key, value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "\"%u\":", key); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "%d", value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%d", (serializer->status.size_used > 0) ? serializer->csv_separator : "", value); + if (ndpi_serializer_header_uint32(serializer, key) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%d", value); } else { ndpi_serialization_type kt; u_int8_t type = 0; - u_int32_t type_offset = serializer->status.size_used++; + u_int32_t type_offset = serializer->status.buffer.size_used++; kt = ndpi_serialize_key_uint32(serializer, key); type = (kt << 4); - if (value <= 127 && value >= -128) { + if(value <= 127 && value >= -128) { ndpi_serialize_single_uint8(serializer, value); type |= ndpi_serialization_int8; - } else if (value <= 32767 && value >= -32768) { + } else if(value <= 32767 && value >= -32768) { ndpi_serialize_single_uint16(serializer, value); type |= ndpi_serialization_int16; } else { @@ -644,9 +931,10 @@ int ndpi_serialize_uint32_int32(ndpi_serializer *_serializer, type |= ndpi_serialization_int32; } - serializer->buffer[type_offset] = type; + serializer->buffer.data[type_offset] = type; } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -655,7 +943,7 @@ int ndpi_serialize_uint32_int32(ndpi_serializer *_serializer, int ndpi_serialize_uint32_int64(ndpi_serializer *_serializer, u_int32_t key, int64_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int16_t needed = sizeof(u_int8_t) /* type */ + sizeof(u_int32_t) /* key */ + @@ -665,29 +953,41 @@ int ndpi_serialize_uint32_int64(ndpi_serializer *_serializer, needed += 32; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "\"%u\":%lld", key, (long long int)value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "\"%u\":", key); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, NDPI_I64_FORMAT, (long long int)value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%lld", - (serializer->status.size_used > 0) ? serializer->csv_separator : "", - (long long int)value); - - } else { - if (value <= 2147483647 && value >= -2147483648) { + if (ndpi_serializer_header_uint32(serializer, key) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + NDPI_I64_FORMAT, (long long int)value); + } + else { + if((value & 0xFFFFFFFF) == value) { return(ndpi_serialize_uint32_int32(_serializer, key, value)); } else { ndpi_serialization_type kt; u_int8_t type = 0; - u_int32_t type_offset = serializer->status.size_used++; + u_int32_t type_offset = serializer->status.buffer.size_used++; kt = ndpi_serialize_key_uint32(serializer, key); type = (kt << 4); @@ -695,10 +995,11 @@ int ndpi_serialize_uint32_int64(ndpi_serializer *_serializer, ndpi_serialize_single_uint64(serializer, value); type |= ndpi_serialization_int64; - serializer->buffer[type_offset] = type; + serializer->buffer.data[type_offset] = type; } } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -708,7 +1009,7 @@ int ndpi_serialize_uint32_float(ndpi_serializer *_serializer, u_int32_t key, float value, const char *format /* e.f. "%.2f" */) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int16_t needed = sizeof(u_int8_t) /* type */ + sizeof(u_int32_t) /* key */ + @@ -718,25 +1019,32 @@ int ndpi_serialize_uint32_float(ndpi_serializer *_serializer, needed += 32; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, "\"%u\":", key); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, format, value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, "\"%u\":", key); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, format, value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, "%s", - (serializer->status.size_used > 0) ? serializer->csv_separator : ""); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, format, value); + if (ndpi_serializer_header_uint32(serializer, key) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, format, value); } else { ndpi_serialization_type kt; u_int8_t type = 0; - u_int32_t type_offset = serializer->status.size_used++; + u_int32_t type_offset = serializer->status.buffer.size_used++; kt = ndpi_serialize_key_uint32(serializer, key); type = (kt << 4); @@ -744,9 +1052,10 @@ int ndpi_serialize_uint32_float(ndpi_serializer *_serializer, ndpi_serialize_single_float(serializer, value); type |= ndpi_serialization_float; - serializer->buffer[type_offset] = type; + serializer->buffer.data[type_offset] = type; } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -755,7 +1064,7 @@ int ndpi_serialize_uint32_float(ndpi_serializer *_serializer, static int ndpi_serialize_uint32_binary(ndpi_serializer *_serializer, u_int32_t key, const char *value, u_int16_t slen) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed = sizeof(u_int8_t) /* type */ + sizeof(u_int32_t) /* key */ + @@ -766,27 +1075,34 @@ static int ndpi_serialize_uint32_binary(ndpi_serializer *_serializer, needed += 24 + slen; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "\"%u\":", key); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += ndpi_json_string_escape(value, slen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "\"%u\":", key); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + serializer->status.buffer.size_used += ndpi_json_string_escape(value, slen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%s", (serializer->status.size_used > 0) ? serializer->csv_separator : "", value); + if (ndpi_serializer_header_uint32(serializer, key) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%s", value); } else { ndpi_serialization_type kt; u_int8_t type = 0; - u_int32_t type_offset = serializer->status.size_used++; + u_int32_t type_offset = serializer->status.buffer.size_used++; kt = ndpi_serialize_key_uint32(serializer, key); type = (kt << 4); @@ -794,9 +1110,10 @@ static int ndpi_serialize_uint32_binary(ndpi_serializer *_serializer, ndpi_serialize_single_string(serializer, value, slen); type |= ndpi_serialization_string; - serializer->buffer[type_offset] = type; + serializer->buffer.data[type_offset] = type; } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -805,20 +1122,66 @@ static int ndpi_serialize_uint32_binary(ndpi_serializer *_serializer, int ndpi_serialize_uint32_string(ndpi_serializer *_serializer, u_int32_t key, const char *_value) { const char *value = _value ? _value : ""; - return ndpi_serialize_uint32_binary(_serializer, key, value, strlen(value)); + return(ndpi_serialize_uint32_binary(_serializer, key, value, strlen(value))); } /* ********************************** */ -static int ndpi_serialize_binary_int32(ndpi_serializer *_serializer, - const char *key, u_int16_t klen, - int32_t value) { +int ndpi_serialize_uint32_boolean(ndpi_serializer *_serializer, + u_int32_t key, u_int8_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + u_int32_t needed = 24; + + if(serializer->fmt != ndpi_serialization_format_json && + serializer->fmt != ndpi_serialization_format_csv) + return -1; + + if(buff_diff < needed) { + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) + return(-1); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + if(serializer->fmt == ndpi_serialization_format_json) { + ndpi_serialize_json_pre(_serializer); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "\"%u\":", key); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "%s", value ? "true" : "false"); + + ndpi_serialize_json_post(_serializer); + } else if(serializer->fmt == ndpi_serialization_format_csv) { + if (ndpi_serializer_header_uint32(serializer, key) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%s", value ? "true" : "false"); + } + + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; + return(0); +} + +/* ********************************** */ + +int ndpi_serialize_binary_int32(ndpi_serializer *_serializer, + const char *key, u_int16_t klen, + int32_t value) { + ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed; - if (ndpi_is_number(key, klen)) - return ndpi_serialize_uint32_int32(_serializer, atoi(key), value); + if(ndpi_is_number(key, klen)) + return(ndpi_serialize_uint32_int32(_serializer, atoi(key), value)); needed = sizeof(u_int8_t) /* type */ + @@ -830,38 +1193,52 @@ static int ndpi_serialize_binary_int32(ndpi_serializer *_serializer, needed += 16 + klen; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += ndpi_json_string_escape(key, klen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - ":%d", value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, ":"); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, "%d", value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%d", (serializer->status.size_used > 0) ? serializer->csv_separator : "", value); + if (ndpi_serializer_header_string(serializer, key, klen) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%d", value); } else { - if (value <= 127 && value >= -128) { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int8; + if(value <= 127 && value >= -128) { + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int8; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint8(serializer, value); - } else if (value <= 32767 && value >= -32768) { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int16; + } else if(value <= 32767 && value >= -32768) { + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int16; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint16(serializer, value); } else { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int32; + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int32; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint32(serializer, value); } } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; + return(0); } @@ -869,7 +1246,7 @@ static int ndpi_serialize_binary_int32(ndpi_serializer *_serializer, int ndpi_serialize_string_int32(ndpi_serializer *_serializer, const char *key, int32_t value) { - return ndpi_serialize_binary_int32(_serializer, key, strlen(key), value); + return(ndpi_serialize_binary_int32(_serializer, key, strlen(key), value)); } /* ********************************** */ @@ -878,11 +1255,11 @@ int ndpi_serialize_binary_int64(ndpi_serializer *_serializer, const char *key, u_int16_t klen, int64_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed; - if (ndpi_is_number(key, klen)) - return ndpi_serialize_uint32_int64(_serializer, atoi(key), value); + if(ndpi_is_number(key, klen)) + return(ndpi_serialize_uint32_int64(_serializer, atoi(key), value)); needed = sizeof(u_int8_t) /* type */ + @@ -894,33 +1271,46 @@ int ndpi_serialize_binary_int64(ndpi_serializer *_serializer, needed += 16 + klen; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += ndpi_json_string_escape(key, klen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - ":%lld", (long long int)value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + ":"); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + NDPI_I64_FORMAT, (long long int)value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%lld", (serializer->status.size_used > 0) ? serializer->csv_separator : "", - (long long int)value); + if (ndpi_serializer_header_string(serializer, key, klen) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + NDPI_I64_FORMAT, (long long int)value); } else { - if (value <= 2147483647 && value >= -2147483648) { + if ((value & 0xFFFFFFFF) == value) { return(ndpi_serialize_string_int32(_serializer, key, value)); } else { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int64; + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_int64; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint32(serializer, value); } } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -928,19 +1318,19 @@ int ndpi_serialize_binary_int64(ndpi_serializer *_serializer, int ndpi_serialize_string_int64(ndpi_serializer *_serializer, const char *key, int64_t value) { - return ndpi_serialize_binary_int64(_serializer, key, strlen(key), value); + return(ndpi_serialize_binary_int64(_serializer, key, strlen(key), value)); } /* ********************************** */ -static int ndpi_serialize_binary_uint32(ndpi_serializer *_serializer, - const char *key, u_int16_t klen, u_int32_t value) { +int ndpi_serialize_binary_uint32(ndpi_serializer *_serializer, + const char *key, u_int16_t klen, u_int32_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed; - if (ndpi_is_number(key, klen)) - return ndpi_serialize_uint32_uint32(_serializer, atoi(key), value); + if(ndpi_is_number(key, klen)) + return(ndpi_serialize_uint32_uint32(_serializer, atoi(key), value)); needed = sizeof(u_int8_t) /* type */ + @@ -952,38 +1342,54 @@ static int ndpi_serialize_binary_uint32(ndpi_serializer *_serializer, needed += 16 + klen; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += ndpi_json_string_escape(key, klen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - ":%u", value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, ":"); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], + buff_diff, "%u", value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%u", (serializer->status.size_used > 0) ? serializer->csv_separator : "", value); + if (ndpi_serializer_header_string(serializer, key, klen) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%u", value); } else { - if (value <= 0xff) { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint8; + if(value <= 0xff) { + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint8; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint8(serializer, value); - } else if (value <= 0xffff) { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint16; + } else if(value <= 0xffff) { + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint16; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint16(serializer, value); } else { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint32; + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint32; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint32(serializer, value); } } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -991,7 +1397,7 @@ static int ndpi_serialize_binary_uint32(ndpi_serializer *_serializer, int ndpi_serialize_string_uint32(ndpi_serializer *_serializer, const char *key, u_int32_t value) { - return ndpi_serialize_binary_uint32(_serializer, key, strlen(key), value); + return(ndpi_serialize_binary_uint32(_serializer, key, strlen(key), value)); } /* ********************************** */ @@ -1008,21 +1414,25 @@ int ndpi_serialize_string_uint32_format(ndpi_serializer *_serializer, */ return(ndpi_serialize_string_uint32(_serializer, key, value)); - } else - return(ndpi_serialize_string_uint32_format(_serializer, key, value, format)); + } else { + char buf[16]; + + snprintf(buf, sizeof(buf), format, value); + return(ndpi_serialize_string_string(_serializer, key, buf)); + } } /* ********************************** */ -static int ndpi_serialize_binary_uint64(ndpi_serializer *_serializer, - const char *key, u_int16_t klen, - u_int64_t value) { +int ndpi_serialize_binary_uint64(ndpi_serializer *_serializer, + const char *key, u_int16_t klen, + u_int64_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed; - if (ndpi_is_number(key, klen)) - return ndpi_serialize_uint32_uint64(_serializer, atoi(key), value); + if(ndpi_is_number(key, klen)) + return(ndpi_serialize_uint32_uint64(_serializer, atoi(key), value)); needed = sizeof(u_int8_t) /* type */ + @@ -1034,33 +1444,47 @@ static int ndpi_serialize_binary_uint64(ndpi_serializer *_serializer, needed += 32 + klen; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += ndpi_json_string_escape(key, klen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - ":%llu", (unsigned long long)value); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + ":"); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + NDPI_U64_FORMAT, (unsigned long long)value); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%llu", (serializer->status.size_used > 0) ? serializer->csv_separator : "", - (unsigned long long)value); + if (ndpi_serializer_header_string(serializer, key, klen) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + NDPI_U64_FORMAT, (unsigned long long)value); } else { - if (value <= 0xffffffff) { + if(value <= 0xffffffff) { return(ndpi_serialize_string_uint32(_serializer, key, value)); } else { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint64; + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_uint64; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_uint64(serializer, value); } } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -1068,22 +1492,22 @@ static int ndpi_serialize_binary_uint64(ndpi_serializer *_serializer, int ndpi_serialize_string_uint64(ndpi_serializer *_serializer, const char *key, u_int64_t value) { - return ndpi_serialize_binary_uint64(_serializer, key, strlen(key), value); + return(ndpi_serialize_binary_uint64(_serializer, key, strlen(key), value)); } /* ********************************** */ -static int ndpi_serialize_binary_float(ndpi_serializer *_serializer, - const char *key, - u_int16_t klen, - float value, - const char *format /* e.f. "%.2f" */) { +int ndpi_serialize_binary_float(ndpi_serializer *_serializer, + const char *key, + u_int16_t klen, + float value, + const char *format /* e.f. "%.2f" */) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed; - if (ndpi_is_number(key, klen)) - return ndpi_serialize_uint32_float(_serializer, atoi(key), value, format); + if(ndpi_is_number(key, klen)) + return(ndpi_serialize_uint32_float(_serializer, atoi(key), value, format)); needed = sizeof(u_int8_t) /* type */ + @@ -1095,35 +1519,38 @@ static int ndpi_serialize_binary_float(ndpi_serializer *_serializer, needed += 32 + klen; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += ndpi_json_string_escape(key, klen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->buffer[serializer->status.size_used] = ':'; - serializer->status.size_used++; + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->buffer.data[serializer->status.buffer.size_used] = ':'; + serializer->status.buffer.size_used++; + } - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, format, value); + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, format, value); ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - if(serializer->status.size_used > 0) - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, "%s", serializer->csv_separator); - - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, format, value); + if (ndpi_serializer_header_string(serializer, key, klen) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, format, value); } else { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_float; + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_float; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_float(serializer, value); } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } @@ -1133,24 +1560,22 @@ int ndpi_serialize_string_float(ndpi_serializer *_serializer, const char *key, float value, const char *format /* e.f. "%.2f" */) { - return ndpi_serialize_binary_float(_serializer, key, strlen(key), value, format); + return(ndpi_serialize_binary_float(_serializer, key, strlen(key), value, format)); } /* ********************************** */ -static int ndpi_serialize_binary_binary(ndpi_serializer *_serializer, +/* Key is a pair, value is a raw value */ +static int ndpi_serialize_binary_raw(ndpi_serializer *_serializer, const char *key, u_int16_t klen, - const char *_value, - u_int16_t vlen) { + const char *value, + u_int16_t vlen, + u_int8_t escape) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - const char *value = _value ? _value : ""; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed; - if (ndpi_is_number(key, klen)) - return ndpi_serialize_uint32_string(_serializer, atoi(key), _value); - needed = sizeof(u_int8_t) /* type */ + sizeof(u_int16_t) /* key len */ + @@ -1162,105 +1587,297 @@ static int ndpi_serialize_binary_binary(ndpi_serializer *_serializer, needed += 16 + klen + vlen; if(buff_diff < needed) { - if(ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } if(serializer->fmt == ndpi_serialization_format_json) { ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += ndpi_json_string_escape(key, klen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, ":"); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += ndpi_json_string_escape(value, vlen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, ":"); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + if (escape) + serializer->status.buffer.size_used += ndpi_json_string_escape(value, vlen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + else + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + value, vlen); + ndpi_serialize_json_post(_serializer); } else if(serializer->fmt == ndpi_serialization_format_csv) { - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, - "%s%s", (serializer->status.size_used > 0) ? serializer->csv_separator : "", - value); + if (ndpi_serializer_header_string(serializer, key, klen) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%s", value); } else { - serializer->buffer[serializer->status.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_string; + serializer->buffer.data[serializer->status.buffer.size_used++] = (ndpi_serialization_string << 4) | ndpi_serialization_string; ndpi_serialize_single_string(serializer, key, klen); ndpi_serialize_single_string(serializer, value, vlen); } + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; return(0); } /* ********************************** */ +/* Key is a pair, value is a pair */ +int ndpi_serialize_binary_binary(ndpi_serializer *_serializer, + const char *key, + u_int16_t klen, + const char *_value, + u_int16_t vlen) { + const char *value = _value ? _value : ""; + + if(ndpi_is_number(key, klen)) + return(ndpi_serialize_uint32_binary(_serializer, atoi(key), value, vlen)); + + return ndpi_serialize_binary_raw(_serializer, key, klen, value, vlen, 1 /* escape */); +} + +/* ********************************** */ + +/* Key is a string, value is a pair */ int ndpi_serialize_string_binary(ndpi_serializer *_serializer, const char *key, const char *_value, u_int16_t vlen) { - return ndpi_serialize_binary_binary(_serializer, key, strlen(key), _value, vlen); + return(ndpi_serialize_binary_binary(_serializer, key, strlen(key), _value, vlen)); } /* ********************************** */ +/* Key is a string, value is a string (strlen is used to compute the len) */ int ndpi_serialize_string_string(ndpi_serializer *_serializer, const char *key, const char *_value) { - return(ndpi_serialize_binary_binary(_serializer, key, strlen(key), _value, strlen(_value))); + const char *value = _value ? _value : ""; + return(ndpi_serialize_binary_binary(_serializer, key, strlen(key), value, strlen(value))); } /* ********************************** */ -/* Serialize start of nested block (JSON only)*/ -int ndpi_serialize_start_of_block(ndpi_serializer *_serializer, - const char *key) { +/* Key is a string, value is a raw json value (it can be a number, an escaped/quoted string, an array, ..) */ +int ndpi_serialize_string_raw(ndpi_serializer *_serializer, + const char *key, const char *_value, u_int16_t vlen) { + return(ndpi_serialize_binary_raw(_serializer, key, strlen(key), _value, vlen, 0 /* do not escape */)); +} + +/* ********************************** */ + +int ndpi_serialize_string_boolean(ndpi_serializer *_serializer, + const char *key, u_int8_t value) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; - u_int32_t needed, klen = strlen(key); + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + u_int16_t klen = strlen(key); + u_int32_t needed; - if (serializer->fmt != ndpi_serialization_format_json) + if(serializer->fmt != ndpi_serialization_format_json && + serializer->fmt != ndpi_serialization_format_csv) return -1; + if(ndpi_is_number(key, klen)) + return(ndpi_serialize_uint32_boolean(_serializer, atoi(key), value)); + + needed = klen + 16; + + if(buff_diff < needed) { + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) + return(-1); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + if(serializer->fmt == ndpi_serialization_format_json) { + ndpi_serialize_json_pre(_serializer); + + if (!(serializer->status.flags & NDPI_SERIALIZER_STATUS_LIST)) { + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, ":"); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + serializer->status.buffer.size_used += snprintf((char *) + &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, "%s", + value ? "true" : "false"); + + ndpi_serialize_json_post(_serializer); + } else if(serializer->fmt == ndpi_serialization_format_csv) { + if (ndpi_serializer_header_string(serializer, key, strlen(key)) < 0) return(-1); + ndpi_serialize_csv_pre(serializer); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, + "%s", value ? "true" : "false"); + } + + serializer->status.flags |= NDPI_SERIALIZER_STATUS_NOT_EMPTY; + return(0); +} + +/* ********************************** */ + +int ndpi_serialize_start_of_list_binary(ndpi_serializer *_serializer, + const char *key, u_int16_t klen) { + ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + u_int32_t needed; + + if(serializer->fmt != ndpi_serialization_format_json && + serializer->fmt != ndpi_serialization_format_tlv) + return(-1); + needed = 16 + klen; - if (buff_diff < needed) { - if (ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(buff_diff < needed) { + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } - ndpi_serialize_json_pre(_serializer); - serializer->status.size_used += ndpi_json_string_escape(key, klen, - (char *) &serializer->buffer[serializer->status.size_used], buff_diff); - buff_diff = serializer->buffer_size - serializer->status.size_used; - serializer->status.size_used += snprintf((char *) &serializer->buffer[serializer->status.size_used], buff_diff, ": {"); - buff_diff = serializer->buffer_size - serializer->status.size_used; - ndpi_serialize_json_post(_serializer); + if (serializer->fmt == ndpi_serialization_format_json) { + ndpi_serialize_json_pre(_serializer); + + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); - serializer->status.flags |= NDPI_SERIALIZER_STATUS_SOB; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, ": ["); + + serializer->status.flags |= NDPI_SERIALIZER_STATUS_LIST | NDPI_SERIALIZER_STATUS_SOL; + + ndpi_serialize_json_post(_serializer); + } else { + serializer->buffer.data[serializer->status.buffer.size_used++] = ndpi_serialization_start_of_list; + ndpi_serialize_single_string(serializer, key, klen); + } + + return(0); +} + +/* ********************************** */ + +/* Serialize start of simple values list */ +int ndpi_serialize_start_of_list(ndpi_serializer *_serializer, + const char *_key) { + const char *key = _key ? _key : ""; + u_int16_t klen = strlen(key); + + return ndpi_serialize_start_of_list_binary(_serializer, key, klen); +} + +/* ********************************** */ + +/* Serialize end of simple values list */ +int ndpi_serialize_end_of_list(ndpi_serializer *_serializer) { + ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; + + if(serializer->fmt != ndpi_serialization_format_json && + serializer->fmt != ndpi_serialization_format_tlv) + return(-1); + + if (serializer->fmt == ndpi_serialization_format_json) { + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_SOL) /* Empty list */ + serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_SOL; + + serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_LIST; + } else { + serializer->buffer.data[serializer->status.buffer.size_used++] = ndpi_serialization_end_of_list; + } return(0); } /* ********************************** */ -/* Serialize start of nested block (JSON only)*/ +/* Serialize start of nested block */ +int ndpi_serialize_start_of_block_binary(ndpi_serializer *_serializer, + const char *key, u_int16_t klen) { + ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + u_int32_t needed; + + if(serializer->fmt != ndpi_serialization_format_json && + serializer->fmt != ndpi_serialization_format_tlv) + return(-1); + + needed = 16 + klen; + + if(buff_diff < needed) { + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) + return(-1); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + } + + if (serializer->fmt == ndpi_serialization_format_json) { + ndpi_serialize_json_pre(_serializer); + serializer->status.buffer.size_used += ndpi_json_string_escape(key, klen, + (char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + serializer->status.buffer.size_used += snprintf((char *) &serializer->buffer.data[serializer->status.buffer.size_used], buff_diff, ": {"); + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + ndpi_serialize_json_post(_serializer); + + serializer->status.flags |= NDPI_SERIALIZER_STATUS_SOB; + } else /* ndpi_serialization_format_tlv */ { + serializer->buffer.data[serializer->status.buffer.size_used++] = ndpi_serialization_start_of_block; + ndpi_serialize_single_string(serializer, key, klen); + } + + return(0); +} + +/* ********************************** */ + +/* Serialize start of nested block */ +int ndpi_serialize_start_of_block(ndpi_serializer *_serializer, + const char *_key) { + const char *key = _key ? _key : ""; + u_int16_t klen = strlen(key); + + return ndpi_serialize_start_of_block_binary(_serializer, key, klen); +} + +/* ********************************** */ + +/* Serialize end of nested block (JSON only)*/ int ndpi_serialize_end_of_block(ndpi_serializer *_serializer) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; u_int32_t needed; - if (serializer->fmt != ndpi_serialization_format_json) - return -1; + if(serializer->fmt != ndpi_serialization_format_json && + serializer->fmt != ndpi_serialization_format_tlv) + return(-1); needed = 4; - if (buff_diff < needed) { - if (ndpi_extend_serializer_buffer(_serializer, needed - buff_diff) < 0) + if(buff_diff < needed) { + if(ndpi_extend_serializer_buffer(&serializer->buffer, needed - buff_diff) < 0) return(-1); - buff_diff = serializer->buffer_size - serializer->status.size_used; + buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } - buff_diff = serializer->buffer_size - serializer->status.size_used; - ndpi_serialize_json_post(_serializer); + if (serializer->fmt == ndpi_serialization_format_json) { + + if(serializer->status.flags & NDPI_SERIALIZER_STATUS_SOB) /* Empty block */ + serializer->status.flags &= ~NDPI_SERIALIZER_STATUS_SOB; + + // buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; + ndpi_serialize_json_post(_serializer); + } else { + serializer->buffer.data[serializer->status.buffer.size_used++] = ndpi_serialization_end_of_block; + } return(0); } @@ -1287,16 +1904,16 @@ void ndpi_serializer_create_snapshot(ndpi_serializer *_serializer) { void ndpi_serializer_rollback_snapshot(ndpi_serializer *_serializer) { ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - if (serializer->has_snapshot) { + if(serializer->has_snapshot) { memcpy(&serializer->status, &serializer->snapshot, sizeof(ndpi_private_serializer_status)); serializer->has_snapshot = 0; if(serializer->fmt == ndpi_serialization_format_json) { if(serializer->status.flags & NDPI_SERIALIZER_STATUS_ARRAY) { - serializer->buffer[serializer->status.size_used-1] = ']'; + serializer->buffer.data[serializer->status.buffer.size_used-1] = ']'; } else { - serializer->buffer[0] = ' '; - serializer->buffer[serializer->status.size_used-1] = '}'; + serializer->buffer.data[0] = ' '; + serializer->buffer.data[serializer->status.buffer.size_used-1] = '}'; } } } @@ -1313,13 +1930,13 @@ int ndpi_init_deserializer_buf(ndpi_deserializer *_deserializer, if(serialized_buffer_len < (2 * sizeof(u_int8_t))) return(-1); - deserializer->buffer = serialized_buffer; + deserializer->buffer.data = serialized_buffer; - if(deserializer->buffer[0] != 1) + if(deserializer->buffer.data[0] != 1) return(-2); /* Invalid version */ - deserializer->buffer_size = serialized_buffer_len; - deserializer->fmt = deserializer->buffer[1]; + deserializer->buffer.size = serialized_buffer_len; + deserializer->fmt = deserializer->buffer.data[1]; ndpi_reset_serializer(_deserializer); return(0); @@ -1332,15 +1949,15 @@ int ndpi_init_deserializer(ndpi_deserializer *deserializer, ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; return(ndpi_init_deserializer_buf(deserializer, - serializer->buffer, - serializer->status.size_used)); + serializer->buffer.data, + serializer->status.buffer.size_used)); } /* ********************************** */ ndpi_serialization_format ndpi_deserialize_get_format(ndpi_deserializer *_deserializer) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; - return deserializer->fmt; + return(deserializer->fmt); } /* ********************************** */ @@ -1348,12 +1965,12 @@ ndpi_serialization_format ndpi_deserialize_get_format(ndpi_deserializer *_deseri static inline ndpi_serialization_type ndpi_deserialize_get_key_subtype(ndpi_private_deserializer *deserializer) { u_int8_t type; - if (deserializer->status.size_used >= deserializer->buffer_size) - return ndpi_serialization_unknown; + if(deserializer->status.buffer.size_used >= deserializer->buffer.size) + return(ndpi_serialization_unknown); - type = deserializer->buffer[deserializer->status.size_used]; + type = deserializer->buffer.data[deserializer->status.buffer.size_used]; - return (ndpi_serialization_type) (type >> 4); + return((ndpi_serialization_type) (type >> 4)); } /* ********************************** */ @@ -1361,12 +1978,12 @@ static inline ndpi_serialization_type ndpi_deserialize_get_key_subtype(ndpi_priv static inline ndpi_serialization_type ndpi_deserialize_get_value_subtype(ndpi_private_deserializer *deserializer) { u_int8_t type; - if (deserializer->status.size_used >= deserializer->buffer_size) + if(deserializer->status.buffer.size_used >= deserializer->buffer.size) return(ndpi_serialization_unknown); - type = deserializer->buffer[deserializer->status.size_used]; + type = deserializer->buffer.data[deserializer->status.buffer.size_used]; - return (ndpi_serialization_type) (type & 0xf); + return(ndpi_serialization_type) (type & 0xf); } /* ********************************** */ @@ -1404,24 +2021,24 @@ ndpi_serialization_type ndpi_deserialize_get_item_type(ndpi_deserializer *_deser } *key_type = kt; - return et; + return(et); } /* ********************************** */ static inline int ndpi_deserialize_get_single_string_size(ndpi_private_deserializer *deserializer, u_int32_t offset) { - u_int32_t buff_diff = deserializer->buffer_size - offset; + u_int32_t buff_diff = deserializer->buffer.size - offset; u_int16_t expected, str_len; expected = sizeof(u_int16_t) /* len */; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); - str_len = ntohs(*((u_int16_t *) &deserializer->buffer[offset])); + str_len = ntohs(*((u_int16_t *) &deserializer->buffer.data[offset])); expected += str_len; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); - return expected; + return(expected); } /* ********************************** */ @@ -1450,50 +2067,56 @@ static inline int ndpi_deserialize_get_single_size(ndpi_private_deserializer *de size = sizeof(float); break; case ndpi_serialization_string: + case ndpi_serialization_start_of_block: + case ndpi_serialization_start_of_list: size = ndpi_deserialize_get_single_string_size(deserializer, offset); break; case ndpi_serialization_end_of_record: + case ndpi_serialization_end_of_block: + case ndpi_serialization_end_of_list: case ndpi_serialization_unknown: size = 0; break; default: - return -2; + return(-2); break; } - return size; + return(size); } /* ********************************** */ +/* Move the current offset (status.buffer.size_used) to point + * to the next element */ int ndpi_deserialize_next(ndpi_deserializer *_deserializer) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer *) _deserializer; - u_int32_t buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; ndpi_serialization_type kt, et; u_int16_t expected; int size; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); - if (size < 0) return -2; + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); - if (size < 0) return -2; + if(size < 0) return(-2); expected += size; - deserializer->status.size_used += expected; + deserializer->status.buffer.size_used += expected; - return 0; + return(0); } /* ********************************** */ @@ -1501,7 +2124,7 @@ int ndpi_deserialize_next(ndpi_deserializer *_deserializer) { int ndpi_deserialize_key_uint32(ndpi_deserializer *_deserializer, u_int32_t *key) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; - u_int32_t offset, buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t offset, buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; ndpi_serialization_type kt; u_int16_t expected; u_int16_t v16; @@ -1509,14 +2132,14 @@ int ndpi_deserialize_key_uint32(ndpi_deserializer *_deserializer, int size; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); - offset = deserializer->status.size_used + expected; + offset = deserializer->status.buffer.size_used + expected; switch(kt) { case ndpi_serialization_uint32: @@ -1531,34 +2154,35 @@ int ndpi_deserialize_key_uint32(ndpi_deserializer *_deserializer, *key = v8; break; default: - return -1; + return(-1); break; } - return 0; + return(0); } /* ********************************** */ +/* Return the string key for the current element */ int ndpi_deserialize_key_string(ndpi_deserializer *_deserializer, ndpi_string *key) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; ndpi_serialization_type kt; - u_int32_t buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; u_int16_t expected; int size; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); - ndpi_deserialize_single_string(deserializer, deserializer->status.size_used + expected, key); + ndpi_deserialize_single_string(deserializer, deserializer->status.buffer.size_used + expected, key); - return 0; + return(0); } /* ********************************** */ @@ -1567,26 +2191,26 @@ int ndpi_deserialize_value_uint32(ndpi_deserializer *_deserializer, u_int32_t *value) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; ndpi_serialization_type kt, et; - u_int32_t offset, buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t offset, buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; u_int16_t v16; u_int8_t v8; u_int16_t expected; int size; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); - offset = deserializer->status.size_used + expected; + offset = deserializer->status.buffer.size_used + expected; switch(et) { case ndpi_serialization_uint32: @@ -1604,7 +2228,7 @@ int ndpi_deserialize_value_uint32(ndpi_deserializer *_deserializer, break; } - return 0; + return(0); } /* ********************************** */ @@ -1613,35 +2237,35 @@ int ndpi_deserialize_value_uint64(ndpi_deserializer *_deserializer, u_int64_t *value) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; ndpi_serialization_type kt, et; - u_int32_t buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; u_int32_t v32; u_int16_t expected; int size; int rc; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); if(et != ndpi_serialization_uint64) { /* Try with smaller uint types */ rc = ndpi_deserialize_value_uint32(_deserializer, &v32); *value = v32; - return rc; + return(rc); } - ndpi_deserialize_single_uint64(deserializer, deserializer->status.size_used + expected, value); + ndpi_deserialize_single_uint64(deserializer, deserializer->status.buffer.size_used + expected, value); - return 0; + return(0); } /* ********************************** */ @@ -1650,26 +2274,26 @@ int ndpi_deserialize_value_int32(ndpi_deserializer *_deserializer, int32_t *value) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; ndpi_serialization_type kt, et; - u_int32_t offset, buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t offset, buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; int16_t v16; int8_t v8; u_int16_t expected; int size; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return -2; + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); - offset = deserializer->status.size_used + expected; + offset = deserializer->status.buffer.size_used + expected; switch(et) { case ndpi_serialization_int32: @@ -1687,7 +2311,7 @@ int ndpi_deserialize_value_int32(ndpi_deserializer *_deserializer, break; } - return 0; + return(0); } /* ********************************** */ @@ -1696,35 +2320,35 @@ int ndpi_deserialize_value_int64(ndpi_deserializer *_deserializer, int64_t *value) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; ndpi_serialization_type kt, et; - u_int32_t buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; int32_t v32; u_int16_t expected; int size; int rc; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return(-2); + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); if(et != ndpi_serialization_int64) { /* Try with smaller int types */ rc = ndpi_deserialize_value_int32(_deserializer, &v32); *value = v32; - return rc; + return(rc); } - ndpi_deserialize_single_int64(deserializer, deserializer->status.size_used + expected, value); + ndpi_deserialize_single_int64(deserializer, deserializer->status.buffer.size_used + expected, value); - return 0; + return(0); } /* ********************************** */ @@ -1733,60 +2357,63 @@ int ndpi_deserialize_value_float(ndpi_deserializer *_deserializer, float *value) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; ndpi_serialization_type kt, et; - u_int32_t buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; u_int16_t expected; int size; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return(-2); + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); - if (size < 0) return -2; + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); if(et != ndpi_serialization_float) - return -1; + return(-1); - ndpi_deserialize_single_float(deserializer, deserializer->status.size_used + expected, value); + ndpi_deserialize_single_float(deserializer, deserializer->status.buffer.size_used + expected, value); - return 0; + return(0); } /* ********************************** */ +/* Return the string value for the current element */ int ndpi_deserialize_value_string(ndpi_deserializer *_deserializer, ndpi_string *value) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer*)_deserializer; ndpi_serialization_type kt, et; - u_int32_t buff_diff = deserializer->buffer_size - deserializer->status.size_used; + u_int32_t buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; u_int16_t expected; int size; expected = sizeof(u_int8_t) /* type */; - if (buff_diff < expected) return(-2); + if(buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); - if (size < 0) return -2; + + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); - if (size < 0) return -2; + + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); + if(size < 0) return(-2); if(et != ndpi_serialization_string) - return -1; + return(-1); - ndpi_deserialize_single_string(deserializer, deserializer->status.size_used + expected, value); + ndpi_deserialize_single_string(deserializer, deserializer->status.buffer.size_used + expected, value); - return 0; + return(0); } /* ********************************** */ @@ -1795,46 +2422,46 @@ int ndpi_deserialize_value_string(ndpi_deserializer *_deserializer, int ndpi_deserialize_clone_item(ndpi_deserializer *_deserializer, ndpi_serializer *_serializer) { ndpi_private_deserializer *deserializer = (ndpi_private_deserializer *) _deserializer; ndpi_private_serializer *serializer = (ndpi_private_serializer*)_serializer; - u_int32_t src_buff_diff = deserializer->buffer_size - deserializer->status.size_used; - u_int32_t dst_buff_diff = serializer->buffer_size - serializer->status.size_used; + u_int32_t src_buff_diff = deserializer->buffer.size - deserializer->status.buffer.size_used; + u_int32_t dst_buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; ndpi_serialization_type kt, et; u_int16_t expected; int size; - if (serializer->fmt != ndpi_serialization_format_tlv) - return -3; + if(serializer->fmt != ndpi_serialization_format_tlv) + return(-3); expected = sizeof(u_int8_t) /* type */; - if (src_buff_diff < expected) return -2; + if(src_buff_diff < expected) return(-2); kt = ndpi_deserialize_get_key_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.size_used + expected); + size = ndpi_deserialize_get_single_size(deserializer, kt, deserializer->status.buffer.size_used + expected); - if (size < 0) return -2; + if(size < 0) return(-2); expected += size; et = ndpi_deserialize_get_value_subtype(deserializer); - size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.size_used + expected); + size = ndpi_deserialize_get_single_size(deserializer, et, deserializer->status.buffer.size_used + expected); - if (size < 0) return -2; + if(size < 0) return(-2); expected += size; - if (dst_buff_diff < expected) { - if (ndpi_extend_serializer_buffer(_serializer, expected - dst_buff_diff) < 0) - return -1; - dst_buff_diff = serializer->buffer_size - serializer->status.size_used; + if(dst_buff_diff < expected) { + if(ndpi_extend_serializer_buffer(&serializer->buffer, expected - dst_buff_diff) < 0) + return(-1); + dst_buff_diff = serializer->buffer.size - serializer->status.buffer.size_used; } - memcpy(&serializer->buffer[serializer->status.size_used], - &deserializer->buffer[deserializer->status.size_used], + memcpy(&serializer->buffer.data[serializer->status.buffer.size_used], + &deserializer->buffer.data[deserializer->status.buffer.size_used], expected); - serializer->status.size_used += expected; + serializer->status.buffer.size_used += expected; - return 0; + return(0); } /* ********************************** */ @@ -1852,10 +2479,23 @@ int ndpi_deserialize_clone_all(ndpi_deserializer *deserializer, ndpi_serializer while((et = ndpi_deserialize_get_item_type(deserializer, &kt)) != ndpi_serialization_unknown) { - if (et == ndpi_serialization_end_of_record) { + if(et == ndpi_serialization_end_of_record) { ndpi_serialize_end_of_record(serializer); - ndpi_deserialize_next(deserializer); - continue; + goto next; + } else if(et == ndpi_serialization_start_of_block) { + ndpi_deserialize_key_string(deserializer, &ks); + ndpi_serialize_start_of_block_binary(serializer, ks.str, ks.str_len); + goto next; + } else if(et == ndpi_serialization_end_of_block) { + ndpi_serialize_end_of_block(serializer); + goto next; + } else if(et == ndpi_serialization_start_of_list) { + ndpi_deserialize_key_string(deserializer, &ks); + ndpi_serialize_start_of_list_binary(serializer, ks.str, ks.str_len); + goto next; + } else if(et == ndpi_serialization_end_of_list) { + ndpi_serialize_end_of_list(serializer); + goto next; } key_is_string = 0; @@ -1868,54 +2508,55 @@ int ndpi_deserialize_clone_all(ndpi_deserializer *deserializer, ndpi_serializer key_is_string = 1; break; default: - return -1; + return(-1); } switch(et) { case ndpi_serialization_uint32: ndpi_deserialize_value_uint32(deserializer, &u32); - if (key_is_string) ndpi_serialize_binary_uint32(serializer, ks.str, ks.str_len, u32); + if(key_is_string) ndpi_serialize_binary_uint32(serializer, ks.str, ks.str_len, u32); else ndpi_serialize_uint32_uint32(serializer, k32, u32); break; case ndpi_serialization_uint64: ndpi_deserialize_value_uint64(deserializer, &u64); - if (key_is_string) ndpi_serialize_binary_uint64(serializer, ks.str, ks.str_len, u64); + if(key_is_string) ndpi_serialize_binary_uint64(serializer, ks.str, ks.str_len, u64); else ndpi_serialize_uint32_uint64(serializer, k32, u64); break; case ndpi_serialization_int32: ndpi_deserialize_value_int32(deserializer, &i32); - if (key_is_string) ndpi_serialize_binary_int32(serializer, ks.str, ks.str_len, i32); + if(key_is_string) ndpi_serialize_binary_int32(serializer, ks.str, ks.str_len, i32); else ndpi_serialize_uint32_int32(serializer, k32, i32); break; case ndpi_serialization_int64: ndpi_deserialize_value_int64(deserializer, &i64); - if (key_is_string) ndpi_serialize_binary_int64(serializer, ks.str, ks.str_len, i64); + if(key_is_string) ndpi_serialize_binary_int64(serializer, ks.str, ks.str_len, i64); else ndpi_serialize_uint32_int64(serializer, k32, i64); break; case ndpi_serialization_float: ndpi_deserialize_value_float(deserializer, &f); - if (key_is_string) ndpi_serialize_binary_float(serializer, ks.str, ks.str_len, f, "%.3f"); + if(key_is_string) ndpi_serialize_binary_float(serializer, ks.str, ks.str_len, f, "%.3f"); else ndpi_serialize_uint32_float(serializer, k32, f, "%.3f"); break; case ndpi_serialization_string: ndpi_deserialize_value_string(deserializer, &vs); - if (key_is_string) ndpi_serialize_binary_binary(serializer, ks.str, ks.str_len, vs.str, vs.str_len); + if(key_is_string) ndpi_serialize_binary_binary(serializer, ks.str, ks.str_len, vs.str, vs.str_len); else ndpi_serialize_uint32_binary(serializer, k32, vs.str, vs.str_len); break; default: - return -2; + return(-2); } + next: ndpi_deserialize_next(deserializer); } - return 0; + return(0); } /* ********************************** */ diff --git a/src/lib/ndpi_utils.c b/src/lib/ndpi_utils.c index de268c1..edf7ebd 100644 --- a/src/lib/ndpi_utils.c +++ b/src/lib/ndpi_utils.c @@ -1,7 +1,7 @@ /* * ndpi_utils.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -21,20 +21,20 @@ * */ -#ifdef HAVE_CONFIG_H -#include "ndpi_config.h" -#endif #include #include #include -#include "ahocorasick.h" -#include "libcache.h" + #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_UNKNOWN -#include "ndpi_api.h" #include "ndpi_config.h" +#include "ndpi_api.h" +#include "ndpi_includes.h" + +#include "ahocorasick.h" +#include "libcache.h" #include #ifndef WIN32 @@ -48,6 +48,11 @@ #include "third_party/include/ndpi_patricia.h" #include "third_party/include/ht_hash.h" +#include "third_party/include/libinjection.h" +#include "third_party/include/libinjection_sqli.h" +#include "third_party/include/libinjection_xss.h" +#include "third_party/include/rce_injection.h" + #define NDPI_CONST_GENERIC_PROTOCOL_NAME "GenericProtocol" // #define MATCH_DEBUG 1 @@ -211,10 +216,11 @@ void ndpi_tdestroy(void *vrootp, void (*freefct)(void *)) u_int8_t ndpi_net_match(u_int32_t ip_to_check, u_int32_t net, - u_int32_t num_bits) -{ + u_int32_t num_bits) { u_int32_t mask = 0; + num_bits &= 0x1F; /* Avoid overflows */ + mask = ~(~mask >> num_bits); return(((ip_to_check & mask) == (net & mask)) ? 1 : 0); @@ -228,7 +234,7 @@ u_int8_t ndpi_ips_match(u_int32_t src, u_int32_t dst, /* ****************************************** */ -#ifdef WIN32 +#if defined(WIN32) && !defined(__MINGW32__) /* http://opensource.apple.com/source/Libc/Libc-186/string.subproj/strcasecmp.c */ /* @@ -331,6 +337,9 @@ u_int8_t ndpi_is_safe_ssl_cipher(u_int32_t cipher) { /* ***************************************************** */ +/* + Some values coming from packet-tls-utils.c (wireshark) +*/ const char* ndpi_cipher2str(u_int32_t cipher) { switch(cipher) { case 0x000000: return("TLS_NULL_WITH_NULL_NULL"); @@ -492,7 +501,31 @@ const char* ndpi_cipher2str(u_int32_t cipher) { case 0x0000C4: return("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256"); case 0x0000C5: return("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256"); case 0x0000FF: return("TLS_EMPTY_RENEGOTIATION_INFO_SCSV"); - case 0x00c001: return("TLS_ECDH_ECDSA_WITH_NULL_SHA"); + /* RFC 8701 */ + case 0x0A0A: return("Reserved (GREASE)"); + /* RFC 8446 */ + case 0x1301: return("TLS_AES_128_GCM_SHA256"); + case 0x1302: return("TLS_AES_256_GCM_SHA384"); + case 0x1303: return("TLS_CHACHA20_POLY1305_SHA256"); + case 0x1304: return("TLS_AES_128_CCM_SHA256"); + case 0x1305: return("TLS_AES_128_CCM_8_SHA256"); + /* RFC 8701 */ + case 0x1A1A: return("Reserved (GREASE)"); + case 0x2A2A: return("Reserved (GREASE)"); + case 0x3A3A: return("Reserved (GREASE)"); + case 0x4A4A: return("Reserved (GREASE)"); + /* From RFC 7507 */ + case 0x5600: return("TLS_FALLBACK_SCSV"); + /* RFC 8701 */ + case 0x5A5A: return("Reserved (GREASE)"); + case 0x6A6A: return("Reserved (GREASE)"); + case 0x7A7A: return("Reserved (GREASE)"); + case 0x8A8A: return("Reserved (GREASE)"); + case 0x9A9A: return("Reserved (GREASE)"); + case 0xAAAA: return("Reserved (GREASE)"); + case 0xBABA: return("Reserved (GREASE)"); + + case 0x00c001: return("TLS_ECDH_ECDSA_WITH_NULL_SHA"); case 0x00c002: return("TLS_ECDH_ECDSA_WITH_RC4_128_SHA"); case 0x00c003: return("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA"); case 0x00c004: return("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA"); @@ -551,6 +584,148 @@ const char* ndpi_cipher2str(u_int32_t cipher) { case 0x00C039: return("TLS_ECDHE_PSK_WITH_NULL_SHA"); case 0x00C03A: return("TLS_ECDHE_PSK_WITH_NULL_SHA256"); case 0x00C03B: return("TLS_ECDHE_PSK_WITH_NULL_SHA384"); + /* RFC 6209 */ + case 0xC03C: return("TLS_RSA_WITH_ARIA_128_CBC_SHA256"); + case 0xC03D: return("TLS_RSA_WITH_ARIA_256_CBC_SHA384"); + case 0xC03E: return("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256"); + case 0xC03F: return("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384"); + case 0xC040: return("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256"); + case 0xC041: return("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384"); + case 0xC042: return("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256"); + case 0xC043: return("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384"); + case 0xC044: return("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256"); + case 0xC045: return("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384"); + case 0xC046: return("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256"); + case 0xC047: return("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384"); + case 0xC048: return("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256"); + case 0xC049: return("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384"); + case 0xC04A: return("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256"); + case 0xC04B: return("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384"); + case 0xC04C: return("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256"); + case 0xC04D: return("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384"); + case 0xC04E: return("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256"); + case 0xC04F: return("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384"); + case 0xC050: return("TLS_RSA_WITH_ARIA_128_GCM_SHA256"); + case 0xC051: return("TLS_RSA_WITH_ARIA_256_GCM_SHA384"); + case 0xC052: return("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256"); + case 0xC053: return("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384"); + case 0xC054: return("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256"); + case 0xC055: return("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384"); + case 0xC056: return("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256"); + case 0xC057: return("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384"); + case 0xC058: return("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256"); + case 0xC059: return("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384"); + case 0xC05A: return("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256"); + case 0xC05B: return("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384"); + case 0xC05C: return("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256"); + case 0xC05D: return("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384"); + case 0xC05E: return("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256"); + case 0xC05F: return("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384"); + case 0xC060: return("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256"); + case 0xC061: return("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384"); + case 0xC062: return("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256"); + case 0xC063: return("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384"); + case 0xC064: return("TLS_PSK_WITH_ARIA_128_CBC_SHA256"); + case 0xC065: return("TLS_PSK_WITH_ARIA_256_CBC_SHA384"); + case 0xC066: return("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256"); + case 0xC067: return("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384"); + case 0xC068: return("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256"); + case 0xC069: return("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384"); + case 0xC06A: return("TLS_PSK_WITH_ARIA_128_GCM_SHA256"); + case 0xC06B: return("TLS_PSK_WITH_ARIA_256_GCM_SHA384"); + case 0xC06C: return("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256"); + case 0xC06D: return("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384"); + case 0xC06E: return("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256"); + case 0xC06F: return("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384"); + case 0xC070: return("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256"); + case 0xC071: return("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384"); + /* RFC 6367 */ + case 0xC072: return("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC073: return("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384"); + case 0xC074: return("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC075: return("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384"); + case 0xC076: return("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC077: return("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384"); + case 0xC078: return("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC079: return("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384"); + case 0xC07A: return("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC07B: return("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC07C: return("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC07D: return("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC07E: return("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC07F: return("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC080: return("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC081: return("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC082: return("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC083: return("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC084: return("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC085: return("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC086: return("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC087: return("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC088: return("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC089: return("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC08A: return("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC08B: return("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC08C: return("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC08D: return("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC08E: return("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC08F: return("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC090: return("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC091: return("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC092: return("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256"); + case 0xC093: return("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384"); + case 0xC094: return("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC095: return("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384"); + case 0xC096: return("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC097: return("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384"); + case 0xC098: return("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC099: return("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384"); + case 0xC09A: return("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256"); + case 0xC09B: return("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384"); + /* RFC 6655 */ + case 0xC09C: return("TLS_RSA_WITH_AES_128_CCM"); + case 0xC09D: return("TLS_RSA_WITH_AES_256_CCM"); + case 0xC09E: return("TLS_DHE_RSA_WITH_AES_128_CCM"); + case 0xC09F: return("TLS_DHE_RSA_WITH_AES_256_CCM"); + case 0xC0A0: return("TLS_RSA_WITH_AES_128_CCM_8"); + case 0xC0A1: return("TLS_RSA_WITH_AES_256_CCM_8"); + case 0xC0A2: return("TLS_DHE_RSA_WITH_AES_128_CCM_8"); + case 0xC0A3: return("TLS_DHE_RSA_WITH_AES_256_CCM_8"); + case 0xC0A4: return("TLS_PSK_WITH_AES_128_CCM"); + case 0xC0A5: return("TLS_PSK_WITH_AES_256_CCM"); + case 0xC0A6: return("TLS_DHE_PSK_WITH_AES_128_CCM"); + case 0xC0A7: return("TLS_DHE_PSK_WITH_AES_256_CCM"); + case 0xC0A8: return("TLS_PSK_WITH_AES_128_CCM_8"); + case 0xC0A9: return("TLS_PSK_WITH_AES_256_CCM_8"); + case 0xC0AA: return("TLS_PSK_DHE_WITH_AES_128_CCM_8"); + case 0xC0AB: return("TLS_PSK_DHE_WITH_AES_256_CCM_8"); + /* RFC 7251 */ + case 0xC0AC: return("TLS_ECDHE_ECDSA_WITH_AES_128_CCM"); + case 0xC0AD: return("TLS_ECDHE_ECDSA_WITH_AES_256_CCM"); + case 0xC0AE: return("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8"); + case 0xC0AF: return("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8"); + /* RFC 8492 */ + case 0xC0B0: return("TLS_ECCPWD_WITH_AES_128_GCM_SHA256"); + case 0xC0B1: return("TLS_ECCPWD_WITH_AES_256_GCM_SHA384"); + case 0xC0B2: return("TLS_ECCPWD_WITH_AES_128_CCM_SHA256"); + case 0xC0B3: return("TLS_ECCPWD_WITH_AES_256_CCM_SHA384"); + /* draft-camwinget-tls-ts13-macciphersuites */ + case 0xC0B4: return("TLS_SHA256_SHA256"); + case 0xC0B5: return("TLS_SHA384_SHA384"); + /* https://www.ietf.org/archive/id/draft-cragie-tls-ecjpake-01.txt */ + case 0xC0FF: return("TLS_ECJPAKE_WITH_AES_128_CCM_8"); + /* draft-smyshlyaev-tls12-gost-suites */ + case 0xC100: return("TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC"); + case 0xC101: return("TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC"); + case 0xC102: return("TLS_GOSTR341112_256_WITH_28147_CNT_IMIT"); + /* draft-smyshlyaev-tls13-gost-suites */ + case 0xC103: return("TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_L"); + case 0xC104: return("TLS_GOSTR341112_256_WITH_MAGMA_MGM_L"); + case 0xC105: return("TLS_GOSTR341112_256_WITH_KUZNYECHIK_MGM_S"); + case 0xC106: return("TLS_GOSTR341112_256_WITH_MAGMA_MGM_S"); + /* RFC 8701 */ + case 0xCACA: return("Reserved (GREASE)"); + case 0x00CC13: return("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"); case 0x00CC14: return("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"); case 0x00CC15: return("TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256"); @@ -589,11 +764,6 @@ const char* ndpi_cipher2str(u_int32_t cipher) { case 0x060040: return("SSL2_DES_64_CBC_WITH_MD5"); case 0x0700c0: return("SSL2_DES_192_EDE3_CBC_WITH_MD5"); case 0x080080: return("SSL2_RC4_64_WITH_MD5"); - case 0x001301: return("TLS_AES_128_GCM_SHA256"); - case 0x001302: return("TLS_AES_256_GCM_SHA384"); - case 0x001303: return("TLS_CHACHA20_POLY1305_SHA256"); - case 0x001304: return("TLS_AES_128_CCM_SHA256"); - case 0x001305: return("TLS_AES_128_CCM_8_SHA256"); default: { @@ -628,7 +798,6 @@ static int ndpi_is_valid_char(char c) { /* ******************************************************************** */ - static int ndpi_find_non_eng_bigrams(struct ndpi_detection_module_struct *ndpi_struct, char *str) { char s[3]; @@ -710,11 +879,12 @@ int ndpi_has_human_readeable_string(struct ndpi_detection_module_struct *ndpi_st /* ********************************** */ -char* ndpi_ssl_version2str(u_int16_t version, u_int8_t *unknown_tls_version) { - static char v[12]; +char* ndpi_ssl_version2str(struct ndpi_flow_struct *flow, + u_int16_t version, u_int8_t *unknown_tls_version) { + + if(unknown_tls_version) + *unknown_tls_version = 0; - *unknown_tls_version = 0; - switch(version) { case 0x0300: return("SSLv3"); case 0x0301: return("TLSv1"); @@ -724,15 +894,911 @@ char* ndpi_ssl_version2str(u_int16_t version, u_int8_t *unknown_tls_version) { case 0XFB1A: return("TLSv1.3 (Fizz)"); /* https://engineering.fb.com/security/fizz/ */ case 0XFEFF: return("DTLSv1.0"); case 0XFEFD: return("DTLSv1.2"); + case 0x0A0A: + case 0x1A1A: + case 0x2A2A: + case 0x3A3A: + case 0x4A4A: + case 0x5A5A: + case 0x6A6A: + case 0x7A7A: + case 0x8A8A: + case 0x9A9A: + case 0xAAAA: + case 0xBABA: + case 0xCACA: + case 0xDADA: + case 0xEAEA: + case 0xFAFA: return("GREASE"); } if((version >= 0x7f00) && (version <= 0x7fff)) return("TLSv1.3 (draft)"); - *unknown_tls_version = 1; - snprintf(v, sizeof(v), "TLS (%04X)", version); + if(unknown_tls_version) + *unknown_tls_version = 1; + + if(flow != NULL) { + snprintf(flow->protos.stun_ssl.ssl.ssl_version_str, + sizeof(flow->protos.stun_ssl.ssl.ssl_version_str), "TLS (%04X)", version); + + return(flow->protos.stun_ssl.ssl.ssl_version_str); + } else + return(""); +} + +/* ***************************************************** */ + +void ndpi_patchIPv6Address(char *str) { + int i = 0, j = 0; + + while(str[i] != '\0') { + if((str[i] == ':') + && (str[i+1] == '0') + && (str[i+2] == ':')) { + str[j++] = ':'; + str[j++] = ':'; + i += 3; + } else + str[j++] = str[i++]; + } + + if(str[j] != '\0') str[j] = '\0'; +} + +/* ********************************** */ + +void ndpi_user_pwd_payload_copy(u_int8_t *dest, u_int dest_len, + u_int offset, + const u_int8_t *src, u_int src_len) { + u_int i, j=0, k = dest_len-1; + + for(i=offset; (i> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if (pad) { + if (pad == 1) + pos--; + else if (pad == 2) + pos -= 2; + else { + /* Invalid padding */ + ndpi_free(out); + return NULL; + } + break; + } + } + } + + *out_len = pos - out; + + return out; +} + +/* ********************************** */ + +/* NOTE: caller MUST free returned pointer */ +char* ndpi_base64_encode(unsigned char const* bytes_to_encode, size_t in_len) { + size_t len = 0, ret_size; + char *ret; + int i = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + ret_size = ((in_len+2)/3)*4; + + if((ret = (char*)ndpi_malloc(ret_size+1)) == NULL) + return NULL; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if(i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; i < 4; i++) + ret[len++] = base64_table[char_array_4[i]]; + i = 0; + } + } + + if(i) { + for(int j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(int j = 0; (j < i + 1); j++) + ret[len++] = base64_table[char_array_4[j]]; + + while((i++ < 3)) + ret[len++] = '='; + } + + ret[len++] = '\0'; + + return ret; +} + +/* ********************************** */ + +void ndpi_serialize_risk(ndpi_serializer *serializer, + struct ndpi_flow_struct *flow) { + if(flow->risk != 0) { + u_int32_t i; + + ndpi_serialize_start_of_block(serializer, "flow_risk"); + + for(i = 0; i < NDPI_MAX_RISK; i++) { + ndpi_risk_enum r = (ndpi_risk_enum)i; + + if(NDPI_ISSET_BIT(flow->risk, r)) + ndpi_serialize_uint32_string(serializer, i, ndpi_risk2str(r)); + } + + ndpi_serialize_end_of_block(serializer); + } +} + +/* ********************************** */ +/* ********************************** */ + +/* NOTE: serializer must have been already initialized */ +int ndpi_dpi2json(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + ndpi_protocol l7_protocol, + ndpi_serializer *serializer) { + char buf[64]; + + if(flow == NULL) return(-1); - return(v); + ndpi_serialize_start_of_block(serializer, "ndpi"); + ndpi_serialize_risk(serializer, flow); + ndpi_serialize_string_string(serializer, "proto", ndpi_protocol2name(ndpi_struct, l7_protocol, buf, sizeof(buf))); + if(l7_protocol.category != NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) + ndpi_serialize_string_string(serializer, "category", ndpi_category_get_name(ndpi_struct, l7_protocol.category)); + ndpi_serialize_end_of_block(serializer); + + switch(l7_protocol.master_protocol ? l7_protocol.master_protocol : l7_protocol.app_protocol) { + case NDPI_PROTOCOL_DHCP: + ndpi_serialize_start_of_block(serializer, "dhcp"); + ndpi_serialize_string_string(serializer, "fingerprint", flow->protos.dhcp.fingerprint); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_BITTORRENT: + { + u_int i, j, n = 0; + char bittorent_hash[32]; + + for(i=0, j = 0; j < sizeof(bittorent_hash)-1; i++) { + sprintf(&bittorent_hash[j], "%02x", + flow->protos.bittorrent.hash[i]); + + j += 2, n += flow->protos.bittorrent.hash[i]; + } + + if(n == 0) bittorent_hash[0] = '\0'; + + ndpi_serialize_start_of_block(serializer, "bittorrent"); + ndpi_serialize_string_string(serializer, "hash", bittorent_hash); + ndpi_serialize_end_of_block(serializer); + } + break; + + case NDPI_PROTOCOL_DNS: + ndpi_serialize_start_of_block(serializer, "dns"); + if(flow->host_server_name[0] != '\0') + ndpi_serialize_string_string(serializer, "query", (const char*)flow->host_server_name); + ndpi_serialize_string_uint32(serializer, "num_queries", flow->protos.dns.num_queries); + ndpi_serialize_string_uint32(serializer, "num_answers", flow->protos.dns.num_answers); + ndpi_serialize_string_uint32(serializer, "reply_code", flow->protos.dns.reply_code); + ndpi_serialize_string_uint32(serializer, "query_type", flow->protos.dns.query_type); + ndpi_serialize_string_uint32(serializer, "rsp_type", flow->protos.dns.rsp_type); + + inet_ntop(AF_INET, &flow->protos.dns.rsp_addr, buf, sizeof(buf)); + ndpi_serialize_string_string(serializer, "rsp_addr", buf); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_MDNS: + ndpi_serialize_start_of_block(serializer, "mdns"); + ndpi_serialize_string_string(serializer, "answer", (const char*)flow->host_server_name); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_UBNTAC2: + ndpi_serialize_start_of_block(serializer, "ubntac2"); + ndpi_serialize_string_string(serializer, "version", flow->protos.ubntac2.version); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_KERBEROS: + ndpi_serialize_start_of_block(serializer, "kerberos"); + ndpi_serialize_string_string(serializer, "hostname", flow->protos.kerberos.hostname); + ndpi_serialize_string_string(serializer, "domain", flow->protos.kerberos.domain); + ndpi_serialize_string_string(serializer, "username", flow->protos.kerberos.username); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_TELNET: + ndpi_serialize_start_of_block(serializer, "telnet"); + ndpi_serialize_string_string(serializer, "username", flow->protos.telnet.username); + ndpi_serialize_string_string(serializer, "password", flow->protos.telnet.password); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_HTTP: + ndpi_serialize_start_of_block(serializer, "http"); + if(flow->host_server_name[0] != '\0') + ndpi_serialize_string_string(serializer, "hostname", (const char*)flow->host_server_name); + if(flow->http.url != NULL){ + ndpi_serialize_string_string(serializer, "url", flow->http.url); + ndpi_serialize_string_uint32(serializer, "code", flow->http.response_status_code); + ndpi_serialize_string_string(serializer, "content_type", flow->http.content_type); + ndpi_serialize_string_string(serializer, "user_agent", flow->http.user_agent); + } + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_QUIC: + ndpi_serialize_start_of_block(serializer, "quic"); + if(flow->host_server_name[0] != '\0') + ndpi_serialize_string_string(serializer, "hostname", (const char*)flow->host_server_name); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_MAIL_IMAP: + ndpi_serialize_start_of_block(serializer, "imap"); + ndpi_serialize_string_string(serializer, "user", flow->protos.ftp_imap_pop_smtp.username); + ndpi_serialize_string_string(serializer, "password", flow->protos.ftp_imap_pop_smtp.password); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_MAIL_POP: + ndpi_serialize_start_of_block(serializer, "pop"); + ndpi_serialize_string_string(serializer, "user", flow->protos.ftp_imap_pop_smtp.username); + ndpi_serialize_string_string(serializer, "password", flow->protos.ftp_imap_pop_smtp.password); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_MAIL_SMTP: + ndpi_serialize_start_of_block(serializer, "smtp"); + ndpi_serialize_string_string(serializer, "user", flow->protos.ftp_imap_pop_smtp.username); + ndpi_serialize_string_string(serializer, "password", flow->protos.ftp_imap_pop_smtp.password); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_FTP_CONTROL: + ndpi_serialize_start_of_block(serializer, "ftp"); + ndpi_serialize_string_string(serializer, "user", flow->protos.ftp_imap_pop_smtp.username); + ndpi_serialize_string_string(serializer, "password", flow->protos.ftp_imap_pop_smtp.password); + ndpi_serialize_string_uint32(serializer, "auth_failed", flow->protos.ftp_imap_pop_smtp.auth_failed); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_SSH: + ndpi_serialize_start_of_block(serializer, "ssh"); + ndpi_serialize_string_string(serializer, "client_signature", flow->protos.ssh.client_signature); + ndpi_serialize_string_string(serializer, "server_signature", flow->protos.ssh.server_signature); + ndpi_serialize_string_string(serializer, "hassh_client", flow->protos.ssh.hassh_client); + ndpi_serialize_string_string(serializer, "hassh_server", flow->protos.ssh.hassh_server); + ndpi_serialize_end_of_block(serializer); + break; + + case NDPI_PROTOCOL_TLS: + if(flow->protos.stun_ssl.ssl.ssl_version) { + char notBefore[32], notAfter[32]; + struct tm a, b, *before = NULL, *after = NULL; + u_int i, off; + u_int8_t unknown_tls_version; + char *version = ndpi_ssl_version2str(flow, flow->protos.stun_ssl.ssl.ssl_version, &unknown_tls_version); + + if(flow->protos.stun_ssl.ssl.notBefore) + before = gmtime_r((const time_t *)&flow->protos.stun_ssl.ssl.notBefore, &a); + if(flow->protos.stun_ssl.ssl.notAfter) + after = gmtime_r((const time_t *)&flow->protos.stun_ssl.ssl.notAfter, &b); + + if(!unknown_tls_version) { + ndpi_serialize_start_of_block(serializer, "tls"); + ndpi_serialize_string_string(serializer, "version", version); + ndpi_serialize_string_string(serializer, "client_requested_server_name", + flow->protos.stun_ssl.ssl.client_requested_server_name); + if(flow->protos.stun_ssl.ssl.server_names) + ndpi_serialize_string_string(serializer, "server_names", flow->protos.stun_ssl.ssl.server_names); + + if(before) { + strftime(notBefore, sizeof(notBefore), "%Y-%m-%d %H:%M:%S", before); + ndpi_serialize_string_string(serializer, "notbefore", notBefore); + } + + if(after) { + strftime(notAfter, sizeof(notAfter), "%Y-%m-%d %H:%M:%S", after); + ndpi_serialize_string_string(serializer, "notafter", notAfter); + } + ndpi_serialize_string_string(serializer, "ja3", flow->protos.stun_ssl.ssl.ja3_client); + ndpi_serialize_string_string(serializer, "ja3s", flow->protos.stun_ssl.ssl.ja3_server); + ndpi_serialize_string_uint32(serializer, "unsafe_cipher", flow->protos.stun_ssl.ssl.server_unsafe_cipher); + ndpi_serialize_string_string(serializer, "cipher", ndpi_cipher2str(flow->protos.stun_ssl.ssl.server_cipher)); + + if(flow->protos.stun_ssl.ssl.issuerDN) + ndpi_serialize_string_string(serializer, "issuerDN", flow->protos.stun_ssl.ssl.issuerDN); + + if(flow->protos.stun_ssl.ssl.subjectDN) + ndpi_serialize_string_string(serializer, "issuerDN", flow->protos.stun_ssl.ssl.subjectDN); + + if(flow->protos.stun_ssl.ssl.alpn) + ndpi_serialize_string_string(serializer, "alpn", flow->protos.stun_ssl.ssl.alpn); + + if(flow->protos.stun_ssl.ssl.tls_supported_versions) + ndpi_serialize_string_string(serializer, "tls_supported_versions", flow->protos.stun_ssl.ssl.tls_supported_versions); + + if(flow->l4.tcp.tls.sha1_certificate_fingerprint[0] != '\0') { + for(i=0, off=0; i<20; i++) { + int rc = snprintf(&buf[off], sizeof(buf)-off,"%s%02X", (i > 0) ? ":" : "", + flow->l4.tcp.tls.sha1_certificate_fingerprint[i] & 0xFF); + + if(rc <= 0) break; else off += rc; + } + + ndpi_serialize_string_string(serializer, "fingerprint", buf); + } + + ndpi_serialize_end_of_block(serializer); + } + } + break; + } /* switch */ + + return(0); +} + +/* ********************************** */ + +/* NOTE: serializer is initialized by the function */ +int ndpi_flow2json(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + u_int8_t ip_version, + u_int8_t l4_protocol, u_int16_t vlan_id, + u_int32_t src_v4, u_int32_t dst_v4, + struct ndpi_in6_addr *src_v6, struct ndpi_in6_addr *dst_v6, + u_int16_t src_port, u_int16_t dst_port, + ndpi_protocol l7_protocol, + ndpi_serializer *serializer) { + char src_name[32], dst_name[32]; + + if(ndpi_init_serializer(serializer, ndpi_serialization_format_json) == -1) + return(-1); + + if(ip_version == 4) { + inet_ntop(AF_INET, &src_v4, src_name, sizeof(src_name)); + inet_ntop(AF_INET, &dst_v4, dst_name, sizeof(dst_name)); + } else { + inet_ntop(AF_INET6, src_v6, src_name, sizeof(src_name)); + inet_ntop(AF_INET6, dst_v6, dst_name, sizeof(dst_name)); + /* For consistency across platforms replace :0: with :: */ + ndpi_patchIPv6Address(src_name), ndpi_patchIPv6Address(dst_name); + } + + ndpi_serialize_string_string(serializer, "src_ip", src_name); + ndpi_serialize_string_string(serializer, "dest_ip", dst_name); + if(src_port) ndpi_serialize_string_uint32(serializer, "src_port", src_port); + if(dst_port) ndpi_serialize_string_uint32(serializer, "dst_port", dst_port); + + switch(l4_protocol) { + case IPPROTO_TCP: + ndpi_serialize_string_string(serializer, "proto", "TCP"); + break; + + case IPPROTO_UDP: + ndpi_serialize_string_string(serializer, "proto", "UDP"); + break; + + case IPPROTO_ICMP: + ndpi_serialize_string_string(serializer, "proto", "ICMP"); + break; + + default: + ndpi_serialize_string_uint32(serializer, "proto", l4_protocol); + break; + } + + return(ndpi_dpi2json(ndpi_struct, flow, l7_protocol, serializer)); } /* ********************************** */ + +const char* ndpi_tunnel2str(ndpi_packet_tunnel tt) { + switch(tt) { + case ndpi_no_tunnel: + return("No-Tunnel"); + break; + + case ndpi_gtp_tunnel: + return("GTP"); + break; + + case ndpi_capwap_tunnel: + return("CAPWAP"); + break; + + case ndpi_tzsp_tunnel: + return("TZSP"); + break; + + case ndpi_l2tp_tunnel: + return("L2TP"); + break; + } + + return(""); +} + +/* ********************************** */ + +/* + /dv/vulnerabilities/xss_r/?name=%3Cscript%3Econsole.log%28%27JUL2D3WXHEGWRAFJE2PI7OS71Z4Z8RFUHXGNFLUFYVP6M3OL55%27%29%3Bconsole.log%28document.cookie%29%3B%3C%2Fscript%3E + /dv/vulnerabilities/sqli/?id=1%27+and+1%3D1+union+select+null%2C+table_name+from+information_schema.tables%23&Submit=Submit +*/ + +/* https://www.rosettacode.org/wiki/URL_decoding#C */ +static int ishex(int x) { + return(x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F'); +} + +/* ********************************** */ + +static int ndpi_url_decode(const char *s, char *out) { + char *o; + const char *end = s + strlen(s); + int c; + + for(o = out; s <= end; o++) { + c = *s++; + if(c == '+') c = ' '; + else if(c == '%' && (!ishex(*s++)|| + !ishex(*s++)|| + !sscanf(s - 2, "%2x", (unsigned int*)&c))) + return(-1); + + if(out) *o = c; + } + + return(o - out); +} + +/* ********************************** */ + +static int ndpi_is_sql_injection(char* query) { + struct libinjection_sqli_state state; + + size_t qlen = strlen(query); + libinjection_sqli_init(&state, query, qlen, FLAG_NONE); + + return libinjection_is_sqli(&state); +} + +/* ********************************** */ + +static int ndpi_is_xss_injection(char* query) { + size_t qlen = strlen(query); + return libinjection_xss(query, qlen); +} + +/* ********************************** */ + +#ifdef HAVE_PCRE + +static void ndpi_compile_rce_regex() { + const char *pcreErrorStr; + int pcreErrorOffset; + + for(int i = 0; i < N_RCE_REGEX; i++) { + comp_rx[i] = (struct pcre_struct*)ndpi_malloc(sizeof(struct pcre_struct)); + + comp_rx[i]->compiled = pcre_compile(rce_regex[i], 0, &pcreErrorStr, + &pcreErrorOffset, NULL); + + if(comp_rx[i]->compiled == NULL) { + #ifdef DEBUG + NDPI_LOG_ERR(ndpi_str, "ERROR: Could not compile '%s': %s\n", rce_regex[i], + pcreErrorStr); + #endif + + continue; + } + + comp_rx[i]->optimized = pcre_study(comp_rx[i]->compiled, 0, &pcreErrorStr); + + #ifdef DEBUG + if(pcreErrorStr != NULL) { + NDPI_LOG_ERR(ndpi_str, "ERROR: Could not study '%s': %s\n", rce_regex[i], + pcreErrorStr); + } + #endif + } + + free((void *)pcreErrorStr); +} + +static int ndpi_is_rce_injection(char* query) { + if (!initialized_comp_rx) { + ndpi_compile_rce_regex(); + initialized_comp_rx = 1; + } + + int pcreExecRet; + int subStrVec[30]; + + for(int i = 0; i < N_RCE_REGEX; i++) { + unsigned int length = strlen(query); + + pcreExecRet = pcre_exec(comp_rx[i]->compiled, + comp_rx[i]->optimized, + query, length, 0, 0, subStrVec, 30); + + if (pcreExecRet >= 0) { + return 1; + } + #ifdef DEBUG + else { + switch(pcreExecRet) { + case PCRE_ERROR_NOMATCH: + NDPI_LOG_ERR(ndpi_str, "ERROR: String did not match the pattern\n"); + break; + case PCRE_ERROR_NULL: + NDPI_LOG_ERR(ndpi_str, "ERROR: Something was null\n"); + break; + case PCRE_ERROR_BADOPTION: + NDPI_LOG_ERR(ndpi_str, "ERROR: A bad option was passed\n"); + break; + case PCRE_ERROR_BADMAGIC: + NDPI_LOG_ERR(ndpi_str, "ERROR: Magic number bad (compiled re corrupt?)\n"); + break; + case PCRE_ERROR_UNKNOWN_NODE: + NDPI_LOG_ERR(ndpi_str, "ERROR: Something kooky in the compiled re\n"); + break; + case PCRE_ERROR_NOMEMORY: + NDPI_LOG_ERR(ndpi_str, "ERROR: Ran out of memory\n"); + break; + default: + NDPI_LOG_ERR(ndpi_str, "ERROR: Unknown error\n"); + break; + } + } + #endif + } + + size_t ushlen = sizeof(ush_commands) / sizeof(ush_commands[0]); + + for(int i = 0; i < ushlen; i++) { + if(strstr(query, ush_commands[i]) != NULL) { + return 1; + } + } + + size_t pwshlen = sizeof(pwsh_commands) / sizeof(pwsh_commands[0]); + + for(int i = 0; i < pwshlen; i++) { + if(strstr(query, pwsh_commands[i]) != NULL) { + return 1; + } + } + + return 0; +} + +#endif + +/* ********************************** */ + +ndpi_risk_enum ndpi_validate_url(char *url) { + char *orig_str = NULL, *str = NULL, *question_mark = strchr(url, '?'); + ndpi_risk_enum rc = NDPI_NO_RISK; + + if(question_mark) { + char *tmp; + + orig_str = str = ndpi_strdup(&question_mark[1]); /* Skip ? */ + + if(!str) goto validate_rc; + + str = strtok_r(str, "&", &tmp); + + while(str != NULL) { + char *value = strchr(str, '='); + char *decoded; + + if(!value) + break; + else + value = &value[1]; + + if(value[0] != '\0') { + if(!(decoded = (char*)ndpi_malloc(strlen(value)+1))) + break; + + if(ndpi_url_decode(value, decoded) < 0) { + /* Invalid string */ + } else if(decoded[0] != '\0') { + /* Valid string */ + + if(ndpi_is_xss_injection(decoded)) + rc = NDPI_URL_POSSIBLE_XSS; + else if(ndpi_is_sql_injection(decoded)) + rc = NDPI_URL_POSSIBLE_SQL_INJECTION; +#ifdef HAVE_PCRE + else if(ndpi_is_rce_injection(decoded)) + rc = NDPI_URL_POSSIBLE_RCE_INJECTION; +#endif + +#ifdef URL_CHECK_DEBUG + printf("=>> [rc: %u] %s\n", rc, decoded); +#endif + } + + ndpi_free(decoded); + + if(rc != NDPI_NO_RISK) + break; + } + + str = strtok_r(NULL, "&", &tmp); + } + } + + validate_rc: + if(orig_str) ndpi_free(orig_str); + + if(rc == NDPI_NO_RISK) { + /* Let's do an extra check */ + if(strstr(url, "..")) { + /* 127.0.0.1/msadc/..%255c../..%255c../..%255c../winnt/system32/cmd.exe */ + rc = NDPI_HTTP_SUSPICIOUS_URL; + } + } + + return(rc); +} + +/* ******************************************************************** */ + +u_int8_t ndpi_is_protocol_detected(struct ndpi_detection_module_struct *ndpi_str, + ndpi_protocol proto) { + if((proto.master_protocol != NDPI_PROTOCOL_UNKNOWN) + || (proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) + || (proto.category != NDPI_PROTOCOL_CATEGORY_UNSPECIFIED)) + return(1); + else + return(0); +} + +/* ******************************************************************** */ + +const char* ndpi_risk2str(ndpi_risk_enum risk) { + static char buf[16]; + + switch(risk) { + case NDPI_URL_POSSIBLE_XSS: + return("XSS attack"); + + case NDPI_URL_POSSIBLE_SQL_INJECTION: + return("SQL injection"); + + case NDPI_URL_POSSIBLE_RCE_INJECTION: + return("RCE injection"); + + case NDPI_BINARY_APPLICATION_TRANSFER: + return("Binary application transfer"); + + case NDPI_KNOWN_PROTOCOL_ON_NON_STANDARD_PORT: + return("Known protocol on non standard port"); + + case NDPI_TLS_SELFSIGNED_CERTIFICATE: + return("Self-signed Certificate"); + + case NDPI_TLS_OBSOLETE_VERSION: + return("Obsolete TLS version (< 1.1)"); + + case NDPI_TLS_WEAK_CIPHER: + return("Weak TLS cipher"); + + case NDPI_TLS_CERTIFICATE_EXPIRED: + return("TLS Expired Certificate"); + + case NDPI_TLS_CERTIFICATE_MISMATCH: + return("TLS Certificate Mismatch"); + + case NDPI_HTTP_SUSPICIOUS_USER_AGENT: + return("HTTP Suspicious User-Agent"); + + case NDPI_HTTP_NUMERIC_IP_HOST: + return("HTTP Numeric IP Address"); + + case NDPI_HTTP_SUSPICIOUS_URL: + return("HTTP Suspicious URL"); + + case NDPI_HTTP_SUSPICIOUS_HEADER: + return("HTTP Suspicious Header"); + + case NDPI_TLS_NOT_CARRYING_HTTPS: + return("TLS (probably) not carrying HTTPS"); + + case NDPI_SUSPICIOUS_DGA_DOMAIN: + return("Suspicious DGA domain name"); + + case NDPI_MALFORMED_PACKET: + return("Malformed packet"); + + case NDPI_SSH_OBSOLETE_CLIENT_VERSION_OR_CIPHER: + return("SSH Obsolete Client Version/Cipher"); + + case NDPI_SSH_OBSOLETE_SERVER_VERSION_OR_CIPHER: + return("SSH Obsolete Server Version/Cipher"); + + case NDPI_SMB_INSECURE_VERSION: + return("SMB Insecure Version"); + + case NDPI_TLS_SUSPICIOUS_ESNI_USAGE: + return("TLS Suspicious ESNI Usage"); + + case NDPI_UNSAFE_PROTOCOL: + return("Unsafe Protocol"); + + case NDPI_DNS_SUSPICIOUS_TRAFFIC: + return("Suspicious DNS traffic"); /* Exfiltration ? */ + + case NDPI_TLS_MISSING_SNI: + return("SNI TLS extension was missing"); + + default: + snprintf(buf, sizeof(buf), "%d", (int)risk); + return(buf); + } +} + +/* ******************************************************************** */ + +const char* ndpi_http_method2str(ndpi_http_method m) { + switch(m) { + case NDPI_HTTP_METHOD_UNKNOWN: break; + case NDPI_HTTP_METHOD_OPTIONS: return("OPTIONS"); + case NDPI_HTTP_METHOD_GET: return("GET"); + case NDPI_HTTP_METHOD_HEAD: return("HEAD"); + case NDPI_HTTP_METHOD_PATCH: return("PATCH"); + case NDPI_HTTP_METHOD_POST: return("POST"); + case NDPI_HTTP_METHOD_PUT: return("PUT"); + case NDPI_HTTP_METHOD_DELETE: return("DELETE"); + case NDPI_HTTP_METHOD_TRACE: return("TRACE"); + case NDPI_HTTP_METHOD_CONNECT: return("CONNECT"); + } + + return("Unknown HTTP method"); +} + +/* ******************************************************************** */ + +ndpi_http_method ndpi_http_str2method(const char* method, ssize_t method_len) { + if(!method || method_len < 3) + return(NDPI_HTTP_METHOD_UNKNOWN); + + switch(method[0]) { + case 'O': return(NDPI_HTTP_METHOD_OPTIONS); + case 'G': return(NDPI_HTTP_METHOD_GET); + case 'H': return(NDPI_HTTP_METHOD_HEAD); + + case 'P': + switch(method[1]) { + case 'A':return(NDPI_HTTP_METHOD_PATCH); + case 'O':return(NDPI_HTTP_METHOD_POST); + case 'U':return(NDPI_HTTP_METHOD_PUT); + } + break; + + case 'D': return(NDPI_HTTP_METHOD_DELETE); + case 'T': return(NDPI_HTTP_METHOD_TRACE); + case 'C': return(NDPI_HTTP_METHOD_CONNECT); + } + + return(NDPI_HTTP_METHOD_UNKNOWN); +} + +/* ******************************************************************** */ + +#define ROR64(x,r) (((x)>>(r))|((x)<<(64-(r)))) + +/* + 'in_16_bytes_long` points to some 16 byte memory data to be hashed; + two independent 64-bit linear congruential generators are applied + results are mixed, scrambled and cast to 32-bit +*/ +u_int32_t ndpi_quick_16_byte_hash(u_int8_t *in_16_bytes_long) { + u_int64_t a = *(u_int64_t*)(in_16_bytes_long + 0); + u_int64_t c = *(u_int64_t*)(in_16_bytes_long + 8); + + // multipliers are taken from sprng.org, addends are prime + a = a * 0x2c6fe96ee78b6955 + 0x9af64480a3486659; + c = c * 0x369dea0f31a53f85 + 0xd0c6225445b76b5b; + + // mix results + a += c; + + // final scramble + a ^= ROR64(a, 13) ^ ROR64(a, 7); + + // down-casting, also taking advantage of upper half + a ^= a >> 32; + + return((u_int32_t)a); +} + diff --git a/src/lib/protocols/afp.c b/src/lib/protocols/afp.c index a8cea6c..79b4b0b 100644 --- a/src/lib/protocols/afp.c +++ b/src/lib/protocols/afp.c @@ -2,7 +2,7 @@ * afp.c * * Copyright (C) 2009-11 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/aimini.c b/src/lib/protocols/aimini.c index b5cea46..34129a0 100644 --- a/src/lib/protocols/aimini.c +++ b/src/lib/protocols/aimini.c @@ -2,7 +2,7 @@ * aimini.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ajp.c b/src/lib/protocols/ajp.c index bd86f0c..beb8453 100644 --- a/src/lib/protocols/ajp.c +++ b/src/lib/protocols/ajp.c @@ -63,7 +63,7 @@ static void set_ajp_detected(struct ndpi_detection_module_struct *ndpi_struct, /* If no custom protocol has been detected */ /* if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) */ ndpi_int_reset_protocol(flow); - ndpi_set_detected_protocol(ndpi_struct, flow, flow->guessed_host_protocol_id, NDPI_PROTOCOL_AJP); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_AJP, flow->guessed_host_protocol_id); } } diff --git a/src/lib/protocols/amazon_video.c b/src/lib/protocols/amazon_video.c index 41356d9..57212f2 100644 --- a/src/lib/protocols/amazon_video.c +++ b/src/lib/protocols/amazon_video.c @@ -1,7 +1,7 @@ /* * amazon_video.c * - * Copyright (C) 2018 by ntop.org + * Copyright (C) 2018-20 by ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -28,37 +28,36 @@ #include "ndpi_api.h" static void ndpi_check_amazon_video(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) { - + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; NDPI_LOG_DBG(ndpi_struct, "search Amazon Prime\n"); - if((packet->tcp != NULL) && - (packet->payload[0] == 0xFE && - packet->payload[1] == 0xED && - packet->payload[2] == 0xFA && - packet->payload[3] == 0xCE)) - { + if(packet->payload_packet_len > 4) { + if((packet->tcp != NULL) && + (packet->payload[0] == 0xFE && + packet->payload[1] == 0xED && + packet->payload[2] == 0xFA && + packet->payload[3] == 0xCE)) { NDPI_LOG_INFO(ndpi_struct, "found Amazon Video on TCP\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_UNKNOWN); return; - } - else if((packet->udp != NULL) && - (packet->payload[0] == 0xDE && - packet->payload[1] == 0xAD && - packet->payload[2] == 0xBE && - packet->payload[3] == 0xEF)) - { + } else if((packet->udp != NULL) && + (packet->payload[0] == 0xDE && + packet->payload[1] == 0xAD && + packet->payload[2] == 0xBE && + packet->payload[3] == 0xEF)) { NDPI_LOG_INFO(ndpi_struct, "found Amazon Video on UDP\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_AMAZON_VIDEO, NDPI_PROTOCOL_UNKNOWN); - } else { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } -void ndpi_search_amazon_video(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ +void ndpi_search_amazon_video(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; NDPI_LOG_DBG(ndpi_struct, "search amazon_video\n"); @@ -69,8 +68,8 @@ void ndpi_search_amazon_video(struct ndpi_detection_module_struct *ndpi_struct, } -void init_amazon_video_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ +void init_amazon_video_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("AMAZON_VIDEO", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_AMAZON_VIDEO, ndpi_search_amazon_video, diff --git a/src/lib/protocols/amqp.c b/src/lib/protocols/amqp.c index 66ae547..cd2ff5f 100644 --- a/src/lib/protocols/amqp.c +++ b/src/lib/protocols/amqp.c @@ -1,7 +1,7 @@ /* * amqp.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/applejuice.c b/src/lib/protocols/applejuice.c index 7805b75..b107791 100644 --- a/src/lib/protocols/applejuice.c +++ b/src/lib/protocols/applejuice.c @@ -2,7 +2,7 @@ * applejuice.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/armagetron.c b/src/lib/protocols/armagetron.c index 29bf5ce..fe964d4 100644 --- a/src/lib/protocols/armagetron.c +++ b/src/lib/protocols/armagetron.c @@ -2,7 +2,7 @@ * armagetron.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/attic/flash.c b/src/lib/protocols/attic/flash.c index 6e228fd..c04bad6 100644 --- a/src/lib/protocols/attic/flash.c +++ b/src/lib/protocols/attic/flash.c @@ -2,7 +2,7 @@ * flash.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/attic/ftp.c b/src/lib/protocols/attic/ftp.c index 3b577e0..5a30e70 100644 --- a/src/lib/protocols/attic/ftp.c +++ b/src/lib/protocols/attic/ftp.c @@ -2,7 +2,7 @@ * ftp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/attic/manolito.c b/src/lib/protocols/attic/manolito.c index 97ceded..6a3e6da 100644 --- a/src/lib/protocols/attic/manolito.c +++ b/src/lib/protocols/attic/manolito.c @@ -2,7 +2,7 @@ * manolito.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/attic/popo.c b/src/lib/protocols/attic/popo.c index 76c3a66..5e6bbb8 100644 --- a/src/lib/protocols/attic/popo.c +++ b/src/lib/protocols/attic/popo.c @@ -2,7 +2,7 @@ * popo.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/attic/secondlife.c b/src/lib/protocols/attic/secondlife.c index 7a80a05..0b69823 100644 --- a/src/lib/protocols/attic/secondlife.c +++ b/src/lib/protocols/attic/secondlife.c @@ -2,7 +2,7 @@ * secondlife.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ayiya.c b/src/lib/protocols/ayiya.c index e10d017..b810da2 100644 --- a/src/lib/protocols/ayiya.c +++ b/src/lib/protocols/ayiya.c @@ -1,7 +1,7 @@ /* * ayiya.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -57,7 +57,7 @@ void ndpi_search_ayiya(struct ndpi_detection_module_struct *ndpi_struct, struct u_int32_t epoch = ntohl(a->epoch), now; u_int32_t fiveyears = 86400 * 365 * 5; - now = flow->packet.tick_timestamp; + now = flow->packet.current_time_ms; if((epoch >= (now - fiveyears)) && (epoch <= (now+86400 /* 1 day */))) { NDPI_LOG_INFO(ndpi_struct, "found AYIYA\n"); diff --git a/src/lib/protocols/battlefield.c b/src/lib/protocols/battlefield.c deleted file mode 100644 index aa5c348..0000000 --- a/src/lib/protocols/battlefield.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * battlefield.c - * - * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ - -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_BATTLEFIELD - -#include "ndpi_api.h" - -static void ndpi_int_battlefield_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - struct ndpi_id_struct *src = flow->src; - struct ndpi_id_struct *dst = flow->dst; - - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_BATTLEFIELD, NDPI_PROTOCOL_UNKNOWN); - - if (src != NULL) { - src->battlefield_ts = packet->tick_timestamp; - } - if (dst != NULL) { - dst->battlefield_ts = packet->tick_timestamp; - } -} - -void ndpi_search_battlefield(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - - struct ndpi_id_struct *src = flow->src; - struct ndpi_id_struct *dst = flow->dst; - - if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_BATTLEFIELD) { - if (src != NULL && ((u_int32_t) - (packet->tick_timestamp - src->battlefield_ts) < ndpi_struct->battlefield_timeout)) { - NDPI_LOG_DBG2(ndpi_struct, - "battlefield : save src connection packet detected\n"); - src->battlefield_ts = packet->tick_timestamp; - } else if (dst != NULL && ((u_int32_t) - (packet->tick_timestamp - dst->battlefield_ts) < ndpi_struct->battlefield_timeout)) { - NDPI_LOG_DBG2(ndpi_struct, - "battlefield : save dst connection packet detected\n"); - dst->battlefield_ts = packet->tick_timestamp; - } - return; - } - - if (NDPI_SRC_OR_DST_HAS_PROTOCOL(src, dst, NDPI_PROTOCOL_BATTLEFIELD)) { - if (flow->l4.udp.battlefield_stage == 0 || flow->l4.udp.battlefield_stage == 1 + packet->packet_direction) { - if (packet->payload_packet_len > 8 && get_u_int16_t(packet->payload, 0) == htons(0xfefd)) { - flow->l4.udp.battlefield_msg_id = get_u_int32_t(packet->payload, 2); - flow->l4.udp.battlefield_stage = 1 + packet->packet_direction; - return; - } - } else if (flow->l4.udp.battlefield_stage == 2 - packet->packet_direction) { - if (packet->payload_packet_len > 8 && get_u_int32_t(packet->payload, 0) == flow->l4.udp.battlefield_msg_id) { - NDPI_LOG_INFO(ndpi_struct, "found Battlefield message and reply detected\n"); - ndpi_int_battlefield_add_connection(ndpi_struct, flow); - return; - } - } - } - - if (flow->l4.udp.battlefield_stage == 0) { - if (packet->payload_packet_len == 46 && packet->payload[2] == 0 && packet->payload[4] == 0 - && get_u_int32_t(packet->payload, 7) == htonl(0x98001100)) { - flow->l4.udp.battlefield_stage = 3 + packet->packet_direction; - return; - } - } else if (flow->l4.udp.battlefield_stage == 4 - packet->packet_direction) { - if (packet->payload_packet_len == 7 - && (packet->payload[0] == 0x02 || packet->payload[packet->payload_packet_len - 1] == 0xe0)) { - NDPI_LOG_INFO(ndpi_struct, "found Battlefield message and reply detected\n"); - ndpi_int_battlefield_add_connection(ndpi_struct, flow); - return; - } - } - - if (packet->payload_packet_len == 18 && memcmp(&packet->payload[5], "battlefield2\x00", 13) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found Battlefield 2 hello packet detected\n"); - ndpi_int_battlefield_add_connection(ndpi_struct, flow); - return; - } else if (packet->payload_packet_len > 10 && - (memcmp(packet->payload, "\x11\x20\x00\x01\x00\x00\x50\xb9\x10\x11", 10) == 0 - || memcmp(packet->payload, "\x11\x20\x00\x01\x00\x00\x30\xb9\x10\x11", 10) == 0 - || memcmp(packet->payload, "\x11\x20\x00\x01\x00\x00\xa0\x98\x00\x11", 10) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found Battlefield safe pattern detected\n"); - ndpi_int_battlefield_add_connection(ndpi_struct, flow); - return; - } - - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); -} - - -void init_battlefield_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("BattleField", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_BATTLEFIELD, - ndpi_search_battlefield, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - *id += 1; -} diff --git a/src/lib/protocols/bgp.c b/src/lib/protocols/bgp.c index 6b409db..dfdf0df 100644 --- a/src/lib/protocols/bgp.c +++ b/src/lib/protocols/bgp.c @@ -1,7 +1,7 @@ /* * bgp.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/bittorrent.c b/src/lib/protocols/bittorrent.c index e33f0c7..3509449 100644 --- a/src/lib/protocols/bittorrent.c +++ b/src/lib/protocols/bittorrent.c @@ -2,7 +2,7 @@ * bittorrent.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -43,7 +43,7 @@ struct ndpi_utp_hdr { u_int16_t sequence_nr, ack_nr; }; -static u_int8_t is_utp_pkt(const u_int8_t *payload, u_int payload_len) { +static u_int8_t is_utpv1_pkt(const u_int8_t *payload, u_int payload_len) { struct ndpi_utp_hdr *h = (struct ndpi_utp_hdr*)payload; if(payload_len < sizeof(struct ndpi_utp_hdr)) return(0); @@ -52,6 +52,9 @@ static u_int8_t is_utp_pkt(const u_int8_t *payload, u_int payload_len) { if(h->next_extension > 2) return(0); if(ntohl(h->window_size) > 65565) return(0); + if((h->window_size == 0) && (payload_len != sizeof(struct ndpi_utp_hdr))) + return(0); + return(1); } @@ -62,7 +65,7 @@ static void ndpi_add_connection_as_bittorrent(struct ndpi_detection_module_struc { if(check_hash) { const char *bt_hash = NULL; /* 20 bytes long */ - + if(bt_offset == -1) { const char *bt_magic = ndpi_strnstr((const char *)flow->packet.payload, "BitTorrent protocol", flow->packet.payload_packet_len); @@ -72,9 +75,8 @@ static void ndpi_add_connection_as_bittorrent(struct ndpi_detection_module_struc } else bt_hash = (const char*)&flow->packet.payload[28]; - if(!ndpi_struct->disable_metadata_export) { - if(bt_hash) memcpy(flow->protos.bittorrent.hash, bt_hash, 20); - } + if(bt_hash && (flow->packet.payload_packet_len >= (20 + (bt_hash-(const char*)flow->packet.payload)))) + memcpy(flow->protos.bittorrent.hash, bt_hash, 20); } ndpi_int_change_protocol(ndpi_struct, flow, NDPI_PROTOCOL_BITTORRENT, NDPI_PROTOCOL_UNKNOWN); @@ -376,19 +378,30 @@ static void ndpi_int_search_bittorrent_tcp(struct ndpi_detection_module_struct * return; } +static u_int8_t is_port(u_int16_t a, u_int16_t b, u_int16_t what) { + return(((what == a) || (what == b)) ? 1 : 0); +} + void ndpi_search_bittorrent(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; char *bt_proto = NULL; /* This is broadcast */ - if(packet->iph - && (((packet->iph->saddr == 0xFFFFFFFF) || (packet->iph->daddr == 0xFFFFFFFF)) - || (packet->udp - && ((ntohs(packet->udp->source) == 3544) /* teredo.c */ - || (ntohs(packet->udp->dest) == 3544))))) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; + if(packet->iph) { + if((packet->iph->saddr == 0xFFFFFFFF) || (packet->iph->daddr == 0xFFFFFFFF)) + goto exclude_bt; + + if(packet->udp) { + u_int16_t sport = ntohs(packet->udp->source), dport = ntohs(packet->udp->dest); + + if(is_port(sport, dport, 3544) /* teredo */ + || is_port(sport, dport, 5246) || is_port(sport, dport, 5247)/* CAPWAP */) { + exclude_bt: + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + } } if(packet->detected_protocol_stack[0] != NDPI_PROTOCOL_BITTORRENT) { @@ -397,14 +410,16 @@ void ndpi_search_bittorrent(struct ndpi_detection_module_struct *ndpi_struct, st if((packet->tcp != NULL) && (packet->tcp_retransmission == 0 || packet->num_retried_bytes)) { ndpi_int_search_bittorrent_tcp(ndpi_struct, flow); - } - else if(packet->udp != NULL) { + } else if(packet->udp != NULL) { + /* UDP */ char *bt_search = "BT-SEARCH * HTTP/1.1\r\n"; if((ntohs(packet->udp->source) < 1024) - || (ntohs(packet->udp->dest) < 1024) /* High ports only */) + || (ntohs(packet->udp->dest) < 1024) /* High ports only */) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; - + } + /* Check for uTP http://www.bittorrent.org/beps/bep_0029.html @@ -421,14 +436,10 @@ void ndpi_search_bittorrent(struct ndpi_detection_module_struct *ndpi_struct, st u_int8_t v0_extension = packet->payload[17]; u_int8_t v0_flags = packet->payload[18]; - /* Check if this is protocol v1 */ - u_int8_t v1_version = packet->payload[0]; - u_int8_t v1_extension = packet->payload[1]; - u_int32_t v1_window_size = *((u_int32_t*)&packet->payload[12]); - - if(is_utp_pkt(packet->payload, packet->payload_packet_len)) + if(is_utpv1_pkt(packet->payload, packet->payload_packet_len)) { + bt_proto = ndpi_strnstr((const char *)&packet->payload[20], "BitTorrent protocol", packet->payload_packet_len-20); goto bittorrent_found; - else if((packet->payload[0]== 0x60) + } else if((packet->payload[0]== 0x60) && (packet->payload[1]== 0x0) && (packet->payload[2]== 0x0) && (packet->payload[3]== 0x0) @@ -436,14 +447,7 @@ void ndpi_search_bittorrent(struct ndpi_detection_module_struct *ndpi_struct, st /* Heuristic */ bt_proto = ndpi_strnstr((const char *)&packet->payload[20], "BitTorrent protocol", packet->payload_packet_len-20); goto bittorrent_found; - /* CSGO/DOTA conflict */ - } else if(flow->packet_counter > 8 && ((v1_version & 0x0f) == 1) - && ((v1_version >> 4) < 5 /* ST_NUM_STATES */) - && (v1_extension < 3 /* EXT_NUM_EXT */) - && (v1_window_size < 32768 /* 32k */) - ) { - bt_proto = ndpi_strnstr((const char *)&packet->payload[20], "BitTorrent protocol", packet->payload_packet_len-20); - goto bittorrent_found; + /* CSGO/DOTA conflict */ } else if((v0_flags < 6 /* ST_NUM_STATES */) && (v0_extension < 3 /* EXT_NUM_EXT */)) { u_int32_t ts = ntohl(*((u_int32_t*)&(packet->payload[4]))); u_int32_t now; @@ -460,7 +464,7 @@ void ndpi_search_bittorrent(struct ndpi_detection_module_struct *ndpi_struct, st flow->bittorrent_stage++; - if(flow->bittorrent_stage < 10) { + if(flow->bittorrent_stage < 5) { /* We have detected bittorrent but we need to wait until we get a hash */ if(packet->payload_packet_len > 19 /* min size */) { @@ -485,6 +489,7 @@ void ndpi_search_bittorrent(struct ndpi_detection_module_struct *ndpi_struct, st return; } + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } } diff --git a/src/lib/protocols/btlib.c b/src/lib/protocols/btlib.c index 8268e14..17456dc 100644 --- a/src/lib/protocols/btlib.c +++ b/src/lib/protocols/btlib.c @@ -1,7 +1,7 @@ /* * btlib.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * Contributed by Vitaly Lavrov * * This file is part of nDPI, an open source deep packet inspection @@ -23,12 +23,13 @@ */ #include "ndpi_api.h" - +#if 0 #ifndef NDPI_NO_STD_INC #include #include -#include #include +#include +#include #include /* @@ -39,8 +40,11 @@ typedef unsigned long long int u_int64_t; #include #include +#ifndef WIN32 #include #endif +#endif +#endif #include "btlib.h" diff --git a/src/lib/protocols/capwap.c b/src/lib/protocols/capwap.c new file mode 100644 index 0000000..33b20fc --- /dev/null +++ b/src/lib/protocols/capwap.c @@ -0,0 +1,125 @@ +/* + * capwap.c + * + * Copyright (C) 2019 - ntop.org + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + + +#include "ndpi_protocol_ids.h" + +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_CAPWAP + +#include "ndpi_api.h" + +#define NDPI_CAPWAP_CONTROL_PORT 5246 +#define NDPI_CAPWAP_DATA_PORT 5247 + + +static void ndpi_int_capwap_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CAPWAP, NDPI_PROTOCOL_UNKNOWN); +} + +/* ************************************************** */ + +static void ndpi_search_setup_capwap(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + u_int16_t sport, dport; + + if(!packet->iph) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + + sport = ntohs(packet->udp->source), dport = ntohs(packet->udp->dest); + + if((dport == NDPI_CAPWAP_CONTROL_PORT) + && (packet->iph->daddr == 0xFFFFFFFF) + && (packet->payload_packet_len >= 16) + && (packet->payload[0] == 0x0) + && (packet->payload[8] == 6 /* Mac len */) + ) + goto capwap_found; + + if(((sport == NDPI_CAPWAP_CONTROL_PORT) || (dport == NDPI_CAPWAP_CONTROL_PORT)) + && ((packet->payload[0] == 0x0) || (packet->payload[0] == 0x1)) + ) { + u_int16_t msg_len, offset, to_add; + + if(packet->payload[0] == 0x0) + offset = 13, to_add = 13; + else + offset = 15, to_add = 17; + + if (packet->payload_packet_len >= offset + sizeof(u_int16_t)) { + msg_len = ntohs(*(u_int16_t*)&packet->payload[offset]); + + if((msg_len+to_add) == packet->payload_packet_len) + goto capwap_found; + } + } + + if( + (((dport == NDPI_CAPWAP_DATA_PORT) && (packet->iph->daddr != 0xFFFFFFFF)) || (sport == NDPI_CAPWAP_DATA_PORT)) + && (packet->payload_packet_len >= 16) + && (packet->payload[0] == 0x0) + ) { + u_int8_t is_80211_data = (packet->payload[9] & 0x0C) >> 2; + + + if((sport == NDPI_CAPWAP_DATA_PORT) && (is_80211_data == 2 /* IEEE 802.11 Data */)) + goto capwap_found; + else if(dport == NDPI_CAPWAP_DATA_PORT) { + u_int16_t msg_len = ntohs(*(u_int16_t*)&packet->payload[13]); + + if((packet->payload[8] == 1 /* Mac len */) + || (packet->payload[8] == 6 /* Mac len */) + || (packet->payload[8] == 4 /* Wireless len */) + || ((msg_len+15) == packet->payload_packet_len)) + goto capwap_found; + } + } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + + capwap_found: + ndpi_int_capwap_add_connection(ndpi_struct, flow); +} + +void ndpi_search_capwap(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) +{ + struct ndpi_packet_struct *packet = &flow->packet; + + if(packet->udp && (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN)) + ndpi_search_setup_capwap(ndpi_struct, flow); +} + + +void init_capwap_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) +{ + ndpi_set_bitmask_protocol_detection("CAPWAP", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_CAPWAP, + ndpi_search_capwap, + NDPI_SELECTION_BITMASK_PROTOCOL_UDP_WITH_PAYLOAD, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); + + *id += 1; +} diff --git a/src/lib/protocols/checkmk.c b/src/lib/protocols/checkmk.c index 4df4979..64adbdb 100644 --- a/src/lib/protocols/checkmk.c +++ b/src/lib/protocols/checkmk.c @@ -1,7 +1,7 @@ /* * checkmk.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ciscovpn.c b/src/lib/protocols/ciscovpn.c index 4a73e57..c97ab25 100644 --- a/src/lib/protocols/ciscovpn.c +++ b/src/lib/protocols/ciscovpn.c @@ -1,6 +1,22 @@ /* * ciscovpn.c - * Copyright (C) 2013 by Remy Mudingay + * + * Copyright (C) 2013-20 - ntop.org + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + * Dissector developed by Remy Mudingay * */ @@ -10,12 +26,15 @@ #include "ndpi_api.h" +/* ****************************************************************** */ static void ndpi_int_ciscovpn_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CISCOVPN, NDPI_PROTOCOL_UNKNOWN); } +/* ****************************************************************** */ + void ndpi_search_ciscovpn(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; @@ -29,6 +48,7 @@ void ndpi_search_ciscovpn(struct ndpi_detection_module_struct *ndpi_struct, stru tsport = ntohs(packet->tcp->source), tdport = ntohs(packet->tcp->dest); NDPI_LOG_DBG2(ndpi_struct, "calculated CISCOVPN over tcp ports\n"); } + if(packet->udp != NULL) { usport = ntohs(packet->udp->source), udport = ntohs(packet->udp->dest); NDPI_LOG_DBG2(ndpi_struct, "calculated CISCOVPN over udp ports\n"); @@ -36,26 +56,26 @@ void ndpi_search_ciscovpn(struct ndpi_detection_module_struct *ndpi_struct, stru if((tdport == 10000 && tsport == 10000) || ((tsport == 443 || tdport == 443) && + (packet->payload_packet_len >= 4) && (packet->payload[0] == 0x17 && packet->payload[1] == 0x01 && packet->payload[2] == 0x00 && packet->payload[3] == 0x00) ) - ) - - { - /* This is a good query 17010000*/ - NDPI_LOG_INFO(ndpi_struct, "found CISCOVPN\n"); - ndpi_int_ciscovpn_add_connection(ndpi_struct, flow); - return; - } + ) { + /* This is a good query 17010000*/ + NDPI_LOG_INFO(ndpi_struct, "found CISCOVPN\n"); + ndpi_int_ciscovpn_add_connection(ndpi_struct, flow); + return; + } else if(((tsport == 443 || tdport == 443) || - (tsport == 80 || tdport == 80)) && + (tsport == 80 || tdport == 80)) && + (packet->payload_packet_len >= 5) && ((packet->payload[0] == 0x17 && - packet->payload[1] == 0x03 && - packet->payload[2] == 0x03 && - packet->payload[3] == 0x00 && - packet->payload[4] == 0x3A))) + packet->payload[1] == 0x03 && + packet->payload[2] == 0x03 && + packet->payload[3] == 0x00 && + packet->payload[4] == 0x3A))) { /* TLS signature of Cisco AnyConnect 0X170303003A */ NDPI_LOG_INFO(ndpi_struct, "found CISCO Anyconnect VPN\n"); @@ -63,12 +83,13 @@ void ndpi_search_ciscovpn(struct ndpi_detection_module_struct *ndpi_struct, stru return; } else if(((tsport == 8009 || tdport == 8009) || - (tsport == 8008 || tdport == 8008)) && + (tsport == 8008 || tdport == 8008)) && + (packet->payload_packet_len >= 5) && ((packet->payload[0] == 0x17 && - packet->payload[1] == 0x03 && - packet->payload[2] == 0x03 && - packet->payload[3] == 0x00 && - packet->payload[4] == 0x69))) + packet->payload[1] == 0x03 && + packet->payload[2] == 0x03 && + packet->payload[3] == 0x00 && + packet->payload[4] == 0x69))) { /* TCP signature of Cisco AnyConnect 0X1703030069 */ NDPI_LOG_INFO(ndpi_struct, "found CISCO Anyconnect VPN\n"); @@ -79,6 +100,7 @@ void ndpi_search_ciscovpn(struct ndpi_detection_module_struct *ndpi_struct, stru ( (usport == 10000 && udport == 10000) && + (packet->payload_packet_len >= 4) && (packet->payload[0] == 0xfe && packet->payload[1] == 0x57 && packet->payload[2] == 0x7e && @@ -86,15 +108,29 @@ void ndpi_search_ciscovpn(struct ndpi_detection_module_struct *ndpi_struct, stru ) ) { - - /* This is a good query fe577e2b */ NDPI_LOG_INFO(ndpi_struct, "found CISCOVPN\n"); ndpi_int_ciscovpn_add_connection(ndpi_struct, flow); - } else { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - } + } else if( + ( + (usport == 443 || udport == 443) + && + (packet->payload_packet_len >= 5) && + (packet->payload[0] == 0x17 && + packet->payload[1] == 0x01 && + packet->payload[2] == 0x00 && + packet->payload[3] == 0x00 && + packet->payload[4] == 0x01) + ) + ) + { + NDPI_LOG_INFO(ndpi_struct, "found CISCOVPN\n"); + ndpi_int_ciscovpn_add_connection(ndpi_struct, flow); + return; + } + if(flow->num_processed_pkts > 5) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } diff --git a/src/lib/protocols/citrix.c b/src/lib/protocols/citrix.c index 4d09019..65852da 100644 --- a/src/lib/protocols/citrix.c +++ b/src/lib/protocols/citrix.c @@ -1,7 +1,7 @@ /* * citrix.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -51,7 +51,7 @@ static void ndpi_check_citrix(struct ndpi_detection_module_struct *ndpi_struct, ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CITRIX, NDPI_PROTOCOL_UNKNOWN); } return; - } else if(payload_len > 4) { + } else if(payload_len > 22) { char citrix_header[] = { 0x1a, 0x43, 0x47, 0x50, 0x2f, 0x30, 0x31 }; if((memcmp(packet->payload, citrix_header, sizeof(citrix_header)) == 0) diff --git a/src/lib/protocols/collectd.c b/src/lib/protocols/collectd.c index 0fabd1a..1b642fd 100644 --- a/src/lib/protocols/collectd.c +++ b/src/lib/protocols/collectd.c @@ -1,7 +1,7 @@ /* * collectd.c * - * Copyright (C) 2014-19 - ntop.org + * Copyright (C) 2014-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/crossfire.c b/src/lib/protocols/crossfire.c index 1298135..90b9169 100644 --- a/src/lib/protocols/crossfire.c +++ b/src/lib/protocols/crossfire.c @@ -1,7 +1,7 @@ /* * crossfire.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/csgo.c b/src/lib/protocols/csgo.c index f316f96..a1f5e56 100644 --- a/src/lib/protocols/csgo.c +++ b/src/lib/protocols/csgo.c @@ -29,8 +29,8 @@ void ndpi_search_csgo(struct ndpi_detection_module_struct* ndpi_struct, struct ndpi_flow_struct* flow) { struct ndpi_packet_struct* packet = &flow->packet; - if (packet->udp != NULL) { - if (packet->payload_packet_len < sizeof(uint32_t)) { + if(packet->udp != NULL) { + if(packet->payload_packet_len < sizeof(uint32_t)) { NDPI_LOG_DBG2(ndpi_struct, "Short csgo packet\n"); return; } @@ -38,58 +38,65 @@ void ndpi_search_csgo(struct ndpi_detection_module_struct* ndpi_struct, struct n uint32_t w = htonl(get_u_int32_t(packet->payload, 0)); NDPI_LOG_DBG2(ndpi_struct, "CSGO: word %08x\n", w); - if (!flow->csgo_state && packet->payload_packet_len == 23 && w == 0xfffffffful) { - if (!memcmp(packet->payload + 5, "connect0x", 9)) { + if(!flow->csgo_state && packet->payload_packet_len == 23 && w == 0xfffffffful) { + if(!memcmp(packet->payload + 5, "connect0x", 9)) { flow->csgo_state++; memcpy(flow->csgo_strid, packet->payload + 5, 18); NDPI_LOG_DBG2(ndpi_struct, "Found csgo connect0x\n"); return; } } - if (flow->csgo_state == 1 && packet->payload_packet_len >= 42 && w == 0xfffffffful) { - if (!memcmp(packet->payload + 24, flow->csgo_strid, 18)) { + + if(flow->csgo_state == 1 && packet->payload_packet_len >= 42 && w == 0xfffffffful) { + if(!memcmp(packet->payload + 24, flow->csgo_strid, 18)) { flow->csgo_state++; ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); NDPI_LOG_INFO( ndpi_struct, "found csgo connect0x reply\n"); return; } } - if (packet->payload_packet_len == 8 && ( w == 0x3a180000 || w == 0x39180000) ) { + + if(packet->payload_packet_len == 8 && ( w == 0x3a180000 || w == 0x39180000) ) { NDPI_LOG_INFO( ndpi_struct, "found csgo udp 8b\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); return; } - if (packet->payload_packet_len >= 36 && w == 0x56533031ul) { + + if(packet->payload_packet_len >= 36 && w == 0x56533031ul) { NDPI_LOG_INFO( ndpi_struct, "found csgo udp\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); return; } - if (packet->payload_packet_len >= 36 && w == 0x01007364) { + + if(packet->payload_packet_len >= 36 && w == 0x01007364) { uint32_t w2 = htonl(get_u_int32_t(packet->payload, 4)); - if (w2 == 0x70696e67) { + if(w2 == 0x70696e67) { NDPI_LOG_INFO( ndpi_struct, "found csgo udp ping\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); return; } } - if (flow->csgo_s2 < 3 && (w & 0xffff0000ul) == 0x0d1d0000) { + + if(flow->csgo_s2 < 3 && (w & 0xffff0000ul) == 0x0d1d0000) { uint32_t w2 = get_u_int32_t(packet->payload, 2); - if (packet->payload_packet_len == 13) { - if (!flow->csgo_s2) { + if(packet->payload_packet_len == 13) { + if(!flow->csgo_s2) { flow->csgo_id2 = w2; flow->csgo_s2 = 1; NDPI_LOG_DBG2( ndpi_struct, "Found csgo udp 0d1d step1\n"); return; } - if (flow->csgo_s2 == 1 && flow->csgo_id2 == w2) { + + if(flow->csgo_s2 == 1 && flow->csgo_id2 == w2) { NDPI_LOG_DBG2( ndpi_struct, "Found csgo udp 0d1d step1 DUP\n"); return; } flow->csgo_s2 = 3; return; } - if (packet->payload_packet_len == 15) { - if (flow->csgo_s2 == 1 && flow->csgo_id2 == w2) { + + if(packet->payload_packet_len == 15) { + if(flow->csgo_s2 == 1 && flow->csgo_id2 == w2) { NDPI_LOG_INFO( ndpi_struct, "found csgo udp 0d1d\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); return; @@ -97,31 +104,39 @@ void ndpi_search_csgo(struct ndpi_detection_module_struct* ndpi_struct, struct n } flow->csgo_s2 = 3; } - if (packet->payload_packet_len >= 140 && (w == 0x02124c6c || w == 0x02125c6c) && + + if(packet->payload_packet_len >= 140 && (w == 0x02124c6c || w == 0x02125c6c) && !memcmp(&packet->payload[3], "lta\000mob\000tpc\000bhj\000bxd\000tae\000urg\000gkh\000", 32)) { - NDPI_LOG_INFO( ndpi_struct, "found csgo dictionary udp\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); - return; + NDPI_LOG_INFO( ndpi_struct, "found csgo dictionary udp\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); + return; } - if (packet->payload_packet_len >= 33 && packet->iph && packet->iph->daddr == 0xffffffff && + + if(packet->payload_packet_len >= 33 && packet->iph && packet->iph->daddr == 0xffffffff && !memcmp(&packet->payload[17], "LanSearch", 9)) { - NDPI_LOG_INFO( ndpi_struct, "found csgo LanSearch udp\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); - return; + NDPI_LOG_INFO( ndpi_struct, "found csgo LanSearch udp\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_CSGO, NDPI_PROTOCOL_UNKNOWN); + return; + } + + if(w == 0) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; } } - if (flow->packet_counter > 20) - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + + if(flow->packet_counter > 5) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_csgo_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { - ndpi_set_bitmask_protocol_detection("CSGO", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_CSGO, - ndpi_search_csgo, - NDPI_SELECTION_BITMASK_PROTOCOL_UDP_WITH_PAYLOAD, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); + ndpi_set_bitmask_protocol_detection("CSGO", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_CSGO, + ndpi_search_csgo, + NDPI_SELECTION_BITMASK_PROTOCOL_UDP_WITH_PAYLOAD, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); *id += 1; } diff --git a/src/lib/protocols/dhcp.c b/src/lib/protocols/dhcp.c index 6a350f8..5bf2a12 100644 --- a/src/lib/protocols/dhcp.c +++ b/src/lib/protocols/dhcp.c @@ -1,7 +1,7 @@ /* * dhcp.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -89,8 +89,10 @@ void ndpi_search_dhcp_udp(struct ndpi_detection_module_struct *ndpi_struct, stru u_int8_t len = ndpi_min(dhcp->options[i+1] /* len as found in the packet */, dhcp_options_size - (i+2) /* 1 for the type and 1 for the value */); - if(len == 0) break; - + if(len == 0) + break; + + #ifdef DHCP_DEBUG NDPI_LOG_DBG2(ndpi_struct, "[DHCP] Id=%d [len=%d]\n", id, len); #endif @@ -100,45 +102,36 @@ void ndpi_search_dhcp_udp(struct ndpi_detection_module_struct *ndpi_struct, stru if(msg_type <= 8) foundValidMsgType = 1; } else if(id == 55 /* Parameter Request List / Fingerprint */) { - if(!ndpi_struct->disable_metadata_export) { - u_int idx, offset = 0; - - for(idx = 0; idx < len && offset < sizeof(flow->protos.dhcp.fingerprint) - 2; idx++) { -#if 1 - offset += snprintf((char*)&flow->protos.dhcp.fingerprint[offset], - sizeof(flow->protos.dhcp.fingerprint) - offset, - "%s%u", (idx > 0) ? "," : "", dhcp->options[i+2+idx] & 0xFF); -#else - offset += snprintf((char*)&flow->protos.dhcp.fingerprint[offset], - sizeof(flow->protos.dhcp.fingerprint) - offset, - "%02X", dhcp->options[i+2+idx] & 0xFF); -#endif - } + u_int idx, offset = 0; + + for(idx = 0; idx < len && offset < sizeof(flow->protos.dhcp.fingerprint) - 2; idx++) { + int rc = snprintf((char*)&flow->protos.dhcp.fingerprint[offset], + sizeof(flow->protos.dhcp.fingerprint) - offset, + "%s%u", (idx > 0) ? "," : "", + (unsigned int)dhcp->options[i+2+idx] & 0xFF); - flow->protos.dhcp.fingerprint[sizeof(flow->protos.dhcp.fingerprint) - 1] = '\0'; + if(rc < 0) break; else offset += rc; } + + flow->protos.dhcp.fingerprint[sizeof(flow->protos.dhcp.fingerprint) - 1] = '\0'; } else if(id == 60 /* Class Identifier */) { - if(!ndpi_struct->disable_metadata_export) { - char *name = (char*)&dhcp->options[i+2]; - int j = 0; - - j = ndpi_min(len, sizeof(flow->protos.dhcp.class_ident)-1); - strncpy((char*)flow->protos.dhcp.class_ident, name, j); - flow->protos.dhcp.class_ident[j] = '\0'; - } + char *name = (char*)&dhcp->options[i+2]; + int j = 0; + + j = ndpi_min(len, sizeof(flow->protos.dhcp.class_ident)-1); + strncpy((char*)flow->protos.dhcp.class_ident, name, j); + flow->protos.dhcp.class_ident[j] = '\0'; } else if(id == 12 /* Host Name */) { - if(!ndpi_struct->disable_metadata_export) { - char *name = (char*)&dhcp->options[i+2]; - int j = 0; - + char *name = (char*)&dhcp->options[i+2]; + int j = 0; + #ifdef DHCP_DEBUG - NDPI_LOG_DBG2(ndpi_struct, "[DHCP] '%.*s'\n",name,len); + NDPI_LOG_DBG2(ndpi_struct, "[DHCP] '%.*s'\n",name,len); // while(j < len) { printf( "%c", name[j]); j++; }; printf("\n"); #endif - j = ndpi_min(len, sizeof(flow->host_server_name)-1); - strncpy((char*)flow->host_server_name, name, j); - flow->host_server_name[j] = '\0'; - } + j = ndpi_min(len, sizeof(flow->host_server_name)-1); + strncpy((char*)flow->host_server_name, name, j); + flow->host_server_name[j] = '\0'; } i += len + 2; @@ -159,8 +152,8 @@ void ndpi_search_dhcp_udp(struct ndpi_detection_module_struct *ndpi_struct, stru } -void init_dhcp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ +void init_dhcp_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("DHCP", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_DHCP, ndpi_search_dhcp_udp, diff --git a/src/lib/protocols/dhcpv6.c b/src/lib/protocols/dhcpv6.c index abafb47..da4b45c 100644 --- a/src/lib/protocols/dhcpv6.c +++ b/src/lib/protocols/dhcpv6.c @@ -2,7 +2,7 @@ * dhcpv6.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/directconnect.c b/src/lib/protocols/directconnect.c index e271b47..e932142 100644 --- a/src/lib/protocols/directconnect.c +++ b/src/lib/protocols/directconnect.c @@ -2,7 +2,7 @@ * directconnect.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -85,7 +85,7 @@ static void ndpi_int_directconnect_add_connection(struct ndpi_detection_module_s ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DIRECTCONNECT, NDPI_PROTOCOL_UNKNOWN); if(src != NULL) { - src->directconnect_last_safe_access_time = packet->tick_timestamp; + src->directconnect_last_safe_access_time = packet->current_time_ms; if(connection_type == DIRECT_CONNECT_TYPE_PEER) { if(packet->tcp != NULL && flow->setup_packet_direction != packet->packet_direction && src->detected_directconnect_port == 0) { @@ -101,7 +101,7 @@ static void ndpi_int_directconnect_add_connection(struct ndpi_detection_module_s } if(dst != NULL) { - dst->directconnect_last_safe_access_time = packet->tick_timestamp; + dst->directconnect_last_safe_access_time = packet->current_time_ms; if(connection_type == DIRECT_CONNECT_TYPE_PEER) { if(packet->tcp != NULL && flow->setup_packet_direction == packet->packet_direction && dst->detected_directconnect_port == 0) { @@ -162,9 +162,9 @@ static void ndpi_search_directconnect_tcp(struct ndpi_detection_module_struct *n if(src != NULL) { if(src->detected_directconnect_port == packet->tcp->source) { if((u_int32_t) - (packet->tick_timestamp - + (packet->current_time_ms - src->directconnect_last_safe_access_time) < ndpi_struct->directconnect_connection_ip_tick_timeout) { - src->directconnect_last_safe_access_time = packet->tick_timestamp; + src->directconnect_last_safe_access_time = packet->current_time_ms; NDPI_LOG_INFO(ndpi_struct, "found DC using port %d\n", ntohs(src->detected_directconnect_port)); ndpi_int_change_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DIRECTCONNECT, NDPI_PROTOCOL_UNKNOWN); return; @@ -176,9 +176,9 @@ static void ndpi_search_directconnect_tcp(struct ndpi_detection_module_struct *n } if(src->detected_directconnect_ssl_port == packet->tcp->dest) { if((u_int32_t) - (packet->tick_timestamp - + (packet->current_time_ms - src->directconnect_last_safe_access_time) < ndpi_struct->directconnect_connection_ip_tick_timeout) { - src->directconnect_last_safe_access_time = packet->tick_timestamp; + src->directconnect_last_safe_access_time = packet->current_time_ms; NDPI_LOG_INFO(ndpi_struct, "found DC using port %d\n", ntohs(src->detected_directconnect_ssl_port)); ndpi_int_change_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DIRECTCONNECT, NDPI_PROTOCOL_UNKNOWN); return; @@ -194,9 +194,9 @@ static void ndpi_search_directconnect_tcp(struct ndpi_detection_module_struct *n if(dst != NULL) { if(dst->detected_directconnect_port == packet->tcp->dest) { if((u_int32_t) - (packet->tick_timestamp - + (packet->current_time_ms - dst->directconnect_last_safe_access_time) < ndpi_struct->directconnect_connection_ip_tick_timeout) { - dst->directconnect_last_safe_access_time = packet->tick_timestamp; + dst->directconnect_last_safe_access_time = packet->current_time_ms; NDPI_LOG_INFO(ndpi_struct, "found DC using port %d\n", ntohs(dst->detected_directconnect_port)); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DIRECTCONNECT, NDPI_PROTOCOL_UNKNOWN); return; @@ -208,9 +208,9 @@ static void ndpi_search_directconnect_tcp(struct ndpi_detection_module_struct *n } if(dst->detected_directconnect_ssl_port == packet->tcp->dest) { if((u_int32_t) - (packet->tick_timestamp - + (packet->current_time_ms - dst->directconnect_last_safe_access_time) < ndpi_struct->directconnect_connection_ip_tick_timeout) { - dst->directconnect_last_safe_access_time = packet->tick_timestamp; + dst->directconnect_last_safe_access_time = packet->current_time_ms; NDPI_LOG_DBG(ndpi_struct, "found DC using port %d\n", ntohs(dst->detected_directconnect_ssl_port)); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DIRECTCONNECT, NDPI_PROTOCOL_UNKNOWN); return; @@ -319,10 +319,10 @@ static void ndpi_search_directconnect_udp(struct ndpi_detection_module_struct if(dst != NULL && dst->detected_directconnect_udp_port == packet->udp->dest) { if((u_int32_t) - (packet->tick_timestamp - + (packet->current_time_ms - dst->directconnect_last_safe_access_time) < ndpi_struct->directconnect_connection_ip_tick_timeout) { - dst->directconnect_last_safe_access_time = packet->tick_timestamp; + dst->directconnect_last_safe_access_time = packet->current_time_ms; NDPI_LOG_INFO(ndpi_struct, "found DC using udp port %d\n", ntohs(dst->detected_directconnect_udp_port)); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DIRECTCONNECT, NDPI_PROTOCOL_UNKNOWN); return; @@ -405,16 +405,16 @@ void ndpi_search_directconnect(struct ndpi_detection_module_struct if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_DIRECTCONNECT) { if(src != NULL && ((u_int32_t) - (packet->tick_timestamp - + (packet->current_time_ms - src->directconnect_last_safe_access_time) < ndpi_struct->directconnect_connection_ip_tick_timeout)) { - src->directconnect_last_safe_access_time = packet->tick_timestamp; + src->directconnect_last_safe_access_time = packet->current_time_ms; } else if(dst != NULL && ((u_int32_t) - (packet->tick_timestamp - + (packet->current_time_ms - dst->directconnect_last_safe_access_time) < ndpi_struct->directconnect_connection_ip_tick_timeout)) { - dst->directconnect_last_safe_access_time = packet->tick_timestamp; + dst->directconnect_last_safe_access_time = packet->current_time_ms; } else { packet->detected_protocol_stack[0] = NDPI_PROTOCOL_UNKNOWN; NDPI_LOG_DBG2(ndpi_struct, "skipping as unknown due to timeout\n"); diff --git a/src/lib/protocols/directdownloadlink.c b/src/lib/protocols/directdownloadlink.c index dae952a..bd15a52 100644 --- a/src/lib/protocols/directdownloadlink.c +++ b/src/lib/protocols/directdownloadlink.c @@ -2,7 +2,7 @@ * directdownloadlink.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -56,7 +56,7 @@ u_int8_t search_ddl_domains(struct ndpi_detection_module_struct *ndpi_struct, st { struct ndpi_packet_struct *packet = &flow->packet; u_int16_t filename_start = 0; - u_int8_t i = 1; + u_int16_t i = 1; u_int16_t host_line_len_without_port; if (packet->payload_packet_len < 100) { diff --git a/src/lib/protocols/dnp3.c b/src/lib/protocols/dnp3.c index 7d0c17f..842e3be 100644 --- a/src/lib/protocols/dnp3.c +++ b/src/lib/protocols/dnp3.c @@ -1,41 +1,61 @@ /* * dnp3.c - * Extension for dnp3 recognition * + * Copyright (C) 2011-20 - ntop.org + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * * Created by Cesar HM + * */ #include "ndpi_protocol_ids.h" +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_DNP3 #include "ndpi_api.h" -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_DNP3 +/* + https://www.ixiacom.com/company/blog/scada-distributed-network-protocol-dnp3 +*/ + +/* ******************************************************** */ void ndpi_search_dnp3_tcp(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) { + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - NDPI_LOG_DBG(ndpi_struct, "search DNP3\n"); - /* Check connection over TCP */ + NDPI_LOG_DBG(ndpi_struct, "search DNP3\n"); if(packet->tcp) { /* The payload of DNP3 is 10 bytes long. * Header bytes: 0x0564 - */ - if ( packet->payload_packet_len >= 10 && - packet->payload[0] == 0x05 && packet->payload[1] == 0x64 ){ - NDPI_LOG_INFO(ndpi_struct, "found DNP3\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DNP3, NDPI_PROTOCOL_UNKNOWN); - return; - } + */ + if ((packet->payload_packet_len >= 10) + && (packet->payload[0] == 0x05) + && (packet->payload[1] == 0x64)) { + NDPI_LOG_INFO(ndpi_struct, "found DNP3\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DNP3, NDPI_PROTOCOL_UNKNOWN); + return; } + } NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } - +/* ******************************************************** */ void init_dnp3_dissector(struct ndpi_detection_module_struct *ndpi_struct, - u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("DNP3", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_DNP3, diff --git a/src/lib/protocols/dns.c b/src/lib/protocols/dns.c index 91598f4..a9ae258 100644 --- a/src/lib/protocols/dns.c +++ b/src/lib/protocols/dns.c @@ -1,7 +1,7 @@ /* * dns.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -27,10 +27,101 @@ #include "ndpi_api.h" - #define FLAGS_MASK 0x8000 -// #define DNS_DEBUG 1 +/* #define DNS_DEBUG 1 */ + +#define DNS_PORT 53 +#define LLMNR_PORT 5355 +#define MDNS_PORT 5353 + +static void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow); + +/* *********************************************** */ + +static void ndpi_check_dns_type(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + u_int16_t dns_type) { + /* https://en.wikipedia.org/wiki/List_of_DNS_record_types */ + + switch(dns_type) { + /* Obsolete record types */ + case 3: + case 4: + case 254: + case 7: + case 8: + case 9: + case 14: + case 253: + case 11: + case 33: + case 10: + case 38: + case 30: + case 25: + case 24: + case 13: + case 17: + case 19: + case 20: + case 21: + case 22: + case 23: + case 26: + case 31: + case 32: + case 34: + case 42: + case 40: + case 27: + case 100: + case 101: + case 102: + case 103: + case 99: + case 56: + case 57: + case 58: + case 104: + case 105: + case 106: + case 107: + case 259: + NDPI_SET_BIT(flow->risk, NDPI_DNS_SUSPICIOUS_TRAFFIC); + break; + } +} + +/* *********************************************** */ + +static u_int16_t checkPort(u_int16_t port) { + switch(port) { + case DNS_PORT: + return(NDPI_PROTOCOL_DNS); + break; + case LLMNR_PORT: + return(NDPI_PROTOCOL_LLMNR); + break; + case MDNS_PORT: + return(NDPI_PROTOCOL_MDNS); + break; + } + + return(0); +} + +/* *********************************************** */ + +static u_int16_t checkDNSSubprotocol(u_int16_t sport, u_int16_t dport) { + u_int16_t rc = checkPort(sport); + + if(rc == 0) + return(checkPort(dport)); + else + return(rc); +} /* *********************************************** */ @@ -45,7 +136,9 @@ static u_int16_t get16(int *i, const u_int8_t *payload) { /* *********************************************** */ static u_int getNameLength(u_int i, const u_int8_t *payload, u_int payloadLen) { - if(payload[i] == 0x00) + if(i >= payloadLen) + return(0); + else if(payload[i] == 0x00) return(1); else if(payload[i] == 0xC0) return(2); @@ -60,18 +153,19 @@ static u_int getNameLength(u_int i, const u_int8_t *payload, u_int payloadLen) { } } /* - allowed chars for dns names A-Z 0-9 _ - - Perl script for generation map: - my @M; - for(my $ch=0; $ch < 256; $ch++) { - $M[$ch >> 5] |= 1 << ($ch & 0x1f) if chr($ch) =~ /[a-z0-9_-]/i; - } - print join(',', map { sprintf "0x%08x",$_ } @M),"\n"; + allowed chars for dns names A-Z 0-9 _ - + Perl script for generation map: + my @M; + for(my $ch=0; $ch < 256; $ch++) { + $M[$ch >> 5] |= 1 << ($ch & 0x1f) if chr($ch) =~ /[a-z0-9_-]/i; + } + print join(',', map { sprintf "0x%08x",$_ } @M),"\n"; */ -static uint32_t dns_validchar[8] = { - 0x00000000,0x03ff2000,0x87fffffe,0x07fffffe,0,0,0,0 -}; +static uint32_t dns_validchar[8] = + { + 0x00000000,0x03ff2000,0x87fffffe,0x07fffffe,0,0,0,0 + }; /* *********************************************** */ @@ -83,6 +177,7 @@ static int search_valid_dns(struct ndpi_detection_module_struct *ndpi_struct, memcpy(dns_header, (struct ndpi_dns_packet_header*)&flow->packet.payload[x], sizeof(struct ndpi_dns_packet_header)); + dns_header->tr_id = ntohs(dns_header->tr_id); dns_header->flags = ntohs(dns_header->flags); dns_header->num_queries = ntohs(dns_header->num_queries); @@ -98,8 +193,10 @@ static int search_valid_dns(struct ndpi_detection_module_struct *ndpi_struct, /* 0x8000 RESPONSE */ else if((dns_header->flags & FLAGS_MASK) == 0x8000) *is_query = 0; - else + else { + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); return(1 /* invalid */); + } if(*is_query) { /* DNS Request */ @@ -107,86 +204,116 @@ static int search_valid_dns(struct ndpi_detection_module_struct *ndpi_struct, && (((dns_header->flags & 0x2800) == 0x2800 /* Dynamic DNS Update */) || ((dns_header->num_answers == 0) && (dns_header->authority_rrs == 0)))) { /* This is a good query */ - while(x < flow->packet.payload_packet_len) { + while(x+2 < flow->packet.payload_packet_len) { if(flow->packet.payload[x] == '\0') { x++; flow->protos.dns.query_type = get16(&x, flow->packet.payload); #ifdef DNS_DEBUG NDPI_LOG_DBG2(ndpi_struct, "query_type=%2d\n", flow->protos.dns.query_type); - printf("[DNS] query_type=%d\n", flow->protos.dns.query_type); + printf("[DNS] [request] query_type=%d\n", flow->protos.dns.query_type); #endif break; } else x++; } - } else + } else { + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); return(1 /* invalid */); + } } else { /* DNS Reply */ flow->protos.dns.reply_code = dns_header->flags & 0x0F; if((dns_header->num_queries > 0) && (dns_header->num_queries <= NDPI_MAX_DNS_REQUESTS) /* Don't assume that num_queries must be zero */ - && (((dns_header->num_answers > 0) && (dns_header->num_answers <= NDPI_MAX_DNS_REQUESTS)) - || ((dns_header->authority_rrs > 0) && (dns_header->authority_rrs <= NDPI_MAX_DNS_REQUESTS)) - || ((dns_header->additional_rrs > 0) && (dns_header->additional_rrs <= NDPI_MAX_DNS_REQUESTS))) + && ((((dns_header->num_answers > 0) && (dns_header->num_answers <= NDPI_MAX_DNS_REQUESTS)) + || ((dns_header->authority_rrs > 0) && (dns_header->authority_rrs <= NDPI_MAX_DNS_REQUESTS)) + || ((dns_header->additional_rrs > 0) && (dns_header->additional_rrs <= NDPI_MAX_DNS_REQUESTS)))) ) { /* This is a good reply: we dissect it both for request and response */ /* Leave the statement below commented necessary in case of call to ndpi_get_partial_detection() */ - /* if(ndpi_struct->dns_dont_dissect_response == 0) */ { + x++; + + if(x < flow->packet.payload_packet_len && flow->packet.payload[x] != '\0') { + while((x < flow->packet.payload_packet_len) + && (flow->packet.payload[x] != '\0')) { + x++; + } + x++; + } + + x += 4; - if(flow->packet.payload[x] != '\0') { - while((x < flow->packet.payload_packet_len) - && (flow->packet.payload[x] != '\0')) { - x++; + if(dns_header->num_answers > 0) { + u_int16_t rsp_type; + u_int16_t num; + + for(num = 0; num < dns_header->num_answers; num++) { + u_int16_t data_len; + + if((x+6) >= flow->packet.payload_packet_len) { + break; } - x++; - } + if((data_len = getNameLength(x, flow->packet.payload, + flow->packet.payload_packet_len)) == 0) { + break; + } else + x += data_len; - x += 4; + if((x+2) >= flow->packet.payload_packet_len) { + break; + } - if(dns_header->num_answers > 0) { - u_int16_t rsp_type; - u_int16_t num; + rsp_type = get16(&x, flow->packet.payload); - for(num = 0; num < dns_header->num_answers; num++) { - u_int16_t data_len; +#ifdef DNS_DEBUG + printf("[DNS] [response] response_type=%d\n", rsp_type); +#endif - if((x+6) >= flow->packet.payload_packet_len) { - break; - } + ndpi_check_dns_type(ndpi_struct, flow, rsp_type); + + flow->protos.dns.rsp_type = rsp_type; - if((data_len = getNameLength(x, flow->packet.payload, flow->packet.payload_packet_len)) == 0) { - break; - } else - x += data_len; + /* here x points to the response "class" field */ + if((x+12) <= flow->packet.payload_packet_len) { + x += 6; + data_len = get16(&x, flow->packet.payload); - rsp_type = get16(&x, flow->packet.payload); - flow->protos.dns.rsp_type = rsp_type; + if((x + data_len) <= flow->packet.payload_packet_len) { + // printf("[rsp_type: %u][data_len: %u]\n", rsp_type, data_len); - /* here x points to the response "class" field */ - if((x+12) <= flow->packet.payload_packet_len) { - x += 6; - data_len = get16(&x, flow->packet.payload); + if(rsp_type == 0x05 /* CNAME */) { + x += data_len; + continue; /* Skip CNAME */ + } - if(((x + data_len) <= flow->packet.payload_packet_len) - && (((rsp_type == 0x1) && (data_len == 4)) /* A */ + if((((rsp_type == 0x1) && (data_len == 4)) /* A */ #ifdef NDPI_DETECTION_SUPPORT_IPV6 - || ((rsp_type == 0x1c) && (data_len == 16)) /* AAAA */ + || ((rsp_type == 0x1c) && (data_len == 16)) /* AAAA */ #endif - )) { + )) { memcpy(&flow->protos.dns.rsp_addr, flow->packet.payload + x, data_len); } } - - break; } + + break; } } - } else - return(1 /* invalid */); + + if((flow->packet.detected_protocol_stack[0] == NDPI_PROTOCOL_DNS) + || (flow->packet.detected_protocol_stack[1] == NDPI_PROTOCOL_DNS)) { + /* Request already set the protocol */ + // flow->extra_packets_func = NULL; /* Removed so the caller can keep dissecting DNS flows */ + } else { + /* We missed the request */ + u_int16_t s_port = flow->packet.udp ? ntohs(flow->packet.udp->source) : ntohs(flow->packet.tcp->source); + + ndpi_set_detected_protocol(ndpi_struct, flow, checkPort(s_port), NDPI_PROTOCOL_UNKNOWN); + } + } } /* Valid */ @@ -199,18 +326,13 @@ static int search_dns_again(struct ndpi_detection_module_struct *ndpi_struct, st /* possibly dissect the DNS reply */ ndpi_search_dns(ndpi_struct, flow); - if(flow->protos.dns.num_answers > 0) { - /* stop extra processing */ - return(0); - } - /* Possibly more processing */ return(1); } /* *********************************************** */ -void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { +static void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { int payload_offset; u_int8_t is_query; u_int16_t s_port = 0, d_port = 0; @@ -230,16 +352,19 @@ void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct nd return; } - if((s_port == 53 || d_port == 53 || d_port == 5355) + if(((s_port == DNS_PORT) || (d_port == DNS_PORT) + || (s_port == MDNS_PORT) || (d_port == MDNS_PORT) + || (d_port == LLMNR_PORT)) && (flow->packet.payload_packet_len > sizeof(struct ndpi_dns_packet_header)+payload_offset)) { struct ndpi_dns_packet_header dns_header; int j = 0, max_len, off; int invalid = search_valid_dns(ndpi_struct, flow, &dns_header, payload_offset, &is_query); ndpi_protocol ret; + u_int num_queries, idx; + + ret.master_protocol = NDPI_PROTOCOL_UNKNOWN; + ret.app_protocol = (d_port == LLMNR_PORT) ? NDPI_PROTOCOL_LLMNR : ((d_port == MDNS_PORT) ? NDPI_PROTOCOL_MDNS : NDPI_PROTOCOL_DNS); - ret.master_protocol = NDPI_PROTOCOL_UNKNOWN; - ret.app_protocol = (d_port == 5355) ? NDPI_PROTOCOL_LLMNR : NDPI_PROTOCOL_DNS; - if(invalid) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; @@ -249,6 +374,34 @@ void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct nd max_len = sizeof(flow->host_server_name)-1; off = sizeof(struct ndpi_dns_packet_header) + payload_offset; + /* Before continuing let's dissect the following queries to see if they are valid */ + for(idx=off, num_queries=0; (num_queries < dns_header.num_queries) && (idx < flow->packet.payload_packet_len);) { + u_int8_t name_len = flow->packet.payload[idx]; + +#ifdef DNS_DEBUG + printf("[DNS] [name_len: %u]\n", name_len); +#endif + + if(name_len == 0) { + /* End of query */ + num_queries++; + idx += 5; + continue; + } + + if((name_len+idx) >= flow->packet.payload_packet_len) { + /* Invalid */ +#ifdef DNS_DEBUG + printf("[DNS] Invalid query len [%u >= %u]\n", + (name_len+idx), + flow->packet.payload_packet_len); +#endif + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); + break; + } else + idx += name_len+1; + } + while(j < max_len && off < flow->packet.payload_packet_len && flow->packet.payload[off] != '\0') { uint8_t c, cl = flow->packet.payload[off++]; @@ -261,35 +414,41 @@ void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct nd if(j && j < max_len) flow->host_server_name[j++] = '.'; while(j < max_len && cl != 0) { + u_int32_t shift; + c = flow->packet.payload[off++]; - flow->host_server_name[j++] = (dns_validchar[c >> 5] & (1 << (c & 0x1f))) ? c : '_'; + shift = ((u_int32_t) 1) << (c & 0x1f); + flow->host_server_name[j++] = tolower((dns_validchar[c >> 5] & shift) ? c : '_'); cl--; } } + flow->host_server_name[j] = '\0'; if(j > 0) { ndpi_protocol_match_result ret_match; - + + ndpi_check_dga_name(ndpi_struct, flow, (char*)flow->host_server_name, 1); + ret.app_protocol = ndpi_match_host_subprotocol(ndpi_struct, flow, (char *)flow->host_server_name, strlen((const char*)flow->host_server_name), &ret_match, NDPI_PROTOCOL_DNS); - + if(ret_match.protocol_category != NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) flow->category = ret_match.protocol_category; if(ret.app_protocol == NDPI_PROTOCOL_UNKNOWN) - ret.master_protocol = (d_port == 5355) ? NDPI_PROTOCOL_LLMNR : NDPI_PROTOCOL_DNS; + ret.master_protocol = checkDNSSubprotocol(s_port, d_port); else ret.master_protocol = NDPI_PROTOCOL_DNS; } /* Report if this is a DNS query or reply */ flow->protos.dns.is_query = is_query; - - if(is_query && (ndpi_struct->dns_dont_dissect_response == 0) && (flow->check_extra_packets == 0)) { + + if(is_query) { /* In this case we say that the protocol has been detected just to let apps carry on with their activities */ ndpi_set_detected_protocol(ndpi_struct, flow, ret.app_protocol, ret.master_protocol); @@ -327,6 +486,9 @@ void ndpi_search_dns(struct ndpi_detection_module_struct *ndpi_struct, struct nd NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } } + + if(flow->packet_counter > 3) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_dns_dissector(struct ndpi_detection_module_struct *ndpi_struct, diff --git a/src/lib/protocols/dnscrypt.c b/src/lib/protocols/dnscrypt.c new file mode 100644 index 0000000..6c89466 --- /dev/null +++ b/src/lib/protocols/dnscrypt.c @@ -0,0 +1,69 @@ +/* + * dnscrypt.c + * + * Copyright (C) 2020 - ntop.org + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#include "ndpi_protocol_ids.h" + +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_DNSCRYPT + +#include "ndpi_api.h" + +static void ndpi_int_dnscrypt_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) +{ + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_DNSCRYPT, NDPI_PROTOCOL_UNKNOWN); +} + +void ndpi_search_dnscrypt(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) +{ + struct ndpi_packet_struct *packet = &flow->packet; + static char const * const dnscrypt_initial = "2\rdnscrypt"; + + NDPI_LOG_DBG(ndpi_struct, "search dnscrypt\n"); + + if (flow->packet_counter > 2) + { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + } + + /* dnscrypt protocol version 1: check magic */ + if (packet->payload_packet_len >= 64 && + strncmp((char*)packet->payload, "r6fnvWj8", strlen("r6fnvWj8")) == 0) + { + ndpi_int_dnscrypt_add_connection(ndpi_struct, flow); + } + /* dnscrypt protocol version 1 and 2: resolver ping */ + if (packet->payload_packet_len > 13 + strlen(dnscrypt_initial) && + strncasecmp((char*)packet->payload + 13, dnscrypt_initial, strlen(dnscrypt_initial)) == 0) + { + ndpi_int_dnscrypt_add_connection(ndpi_struct, flow); + } +} + +void init_dnscrypt_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, + NDPI_PROTOCOL_BITMASK *detection_bitmask) +{ + ndpi_set_bitmask_protocol_detection( + "DNScrypt", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_DNSCRYPT, ndpi_search_dnscrypt, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); + *id += 1; +} + diff --git a/src/lib/protocols/dofus.c b/src/lib/protocols/dofus.c index ec722a1..61973c6 100644 --- a/src/lib/protocols/dofus.c +++ b/src/lib/protocols/dofus.c @@ -2,7 +2,7 @@ * dofus.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/drda.c b/src/lib/protocols/drda.c index 9e0f817..e0d6bbc 100644 --- a/src/lib/protocols/drda.c +++ b/src/lib/protocols/drda.c @@ -1,7 +1,7 @@ /* * drda.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This module is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/eaq.c b/src/lib/protocols/eaq.c index 10eb8f3..40a27eb 100644 --- a/src/lib/protocols/eaq.c +++ b/src/lib/protocols/eaq.c @@ -1,7 +1,7 @@ /* * eaq.c * - * Copyright (C) 2015-19 - ntop.org + * Copyright (C) 2015-20 - ntop.org * * This module is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/edonkey.c b/src/lib/protocols/edonkey.c index ca5abeb..9f73592 100644 --- a/src/lib/protocols/edonkey.c +++ b/src/lib/protocols/edonkey.c @@ -165,8 +165,11 @@ static void ndpi_check_edonkey(struct ndpi_detection_module_struct *ndpi_struct, return; } - if(payload_len == 0) return; - + if(payload_len == 0) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + /* Check if we so far detected the protocol in the request or not. */ if(flow->edonkey_stage == 0) { NDPI_LOG_DBG2(ndpi_struct, "EDONKEY stage 0: \n"); @@ -176,7 +179,8 @@ static void ndpi_check_edonkey(struct ndpi_detection_module_struct *ndpi_struct, /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ flow->edonkey_stage = packet->packet_direction + 1; - } + } else + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } else { NDPI_LOG_DBG2(ndpi_struct, "EDONKEY stage %u: \n", flow->edonkey_stage); @@ -193,8 +197,10 @@ static void ndpi_check_edonkey(struct ndpi_detection_module_struct *ndpi_struct, NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to EDONKEY, resetting the stage to 0\n"); flow->edonkey_stage = 0; } - } + + if(flow->packet_counter > 5) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void ndpi_search_edonkey(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { diff --git a/src/lib/protocols/fasttrack.c b/src/lib/protocols/fasttrack.c index 49a4abd..e88fba6 100644 --- a/src/lib/protocols/fasttrack.c +++ b/src/lib/protocols/fasttrack.c @@ -2,7 +2,7 @@ * fasttrack.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -37,47 +37,48 @@ static void ndpi_int_fasttrack_add_connection(struct ndpi_detection_module_struc void ndpi_search_fasttrack_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; + struct ndpi_packet_struct *packet = &flow->packet; - NDPI_LOG_DBG(ndpi_struct, "search FASTTRACK\n"); - - if ( (packet->payload != NULL) - && (packet->payload_packet_len > 6) - && (ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a)) { - NDPI_LOG_DBG2(ndpi_struct, "detected 0d0a at the end of the packet\n"); - - if (memcmp(packet->payload, "GIVE ", 5) == 0 && packet->payload_packet_len >= 8) { - u_int16_t i; - for (i = 5; i < (packet->payload_packet_len - 2); i++) { - // make shure that the argument to GIVE is numeric - if (!(packet->payload[i] >= '0' && packet->payload[i] <= '9')) { - goto exclude_fasttrack; - } - } - - NDPI_LOG_INFO(ndpi_struct, "found FASTTRACK\n"); - ndpi_int_fasttrack_add_connection(ndpi_struct, flow); - return; - } - - if (packet->payload_packet_len > 50 && memcmp(packet->payload, "GET /", 5) == 0) { - u_int8_t a = 0; - NDPI_LOG_DBG2(ndpi_struct, "detected GET /. \n"); - ndpi_parse_packet_line_info(ndpi_struct, flow); - for (a = 0; a < packet->parsed_lines; a++) { - if ((packet->line[a].len > 17 && memcmp(packet->line[a].ptr, "X-Kazaa-Username: ", 18) == 0) - || (packet->line[a].len > 23 && memcmp(packet->line[a].ptr, "User-Agent: PeerEnabler/", 24) == 0)) { - NDPI_LOG_INFO(ndpi_struct, - "found FASTTRACK X-Kazaa-Username: || User-Agent: PeerEnabler/\n"); - ndpi_int_fasttrack_add_connection(ndpi_struct, flow); - return; - } - } - } + NDPI_LOG_DBG(ndpi_struct, "search FASTTRACK\n"); + + if ( (packet->payload != NULL) + && (packet->payload_packet_len > 6) + && (ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a)) { + NDPI_LOG_DBG2(ndpi_struct, "detected 0d0a at the end of the packet\n"); + + if (memcmp(packet->payload, "GIVE ", 5) == 0 && packet->payload_packet_len >= 8) { + u_int16_t i; + for (i = 5; i < (packet->payload_packet_len - 2); i++) { + // make shure that the argument to GIVE is numeric + if (!(packet->payload[i] >= '0' && packet->payload[i] <= '9')) { + goto exclude_fasttrack; + } + } + + NDPI_LOG_INFO(ndpi_struct, "found FASTTRACK\n"); + ndpi_int_fasttrack_add_connection(ndpi_struct, flow); + return; + } + + if (packet->payload_packet_len > 50 && memcmp(packet->payload, "GET /", 5) == 0) { + u_int16_t a = 0; + NDPI_LOG_DBG2(ndpi_struct, "detected GET /. \n"); + + ndpi_parse_packet_line_info(ndpi_struct, flow); + for (a = 0; a < packet->parsed_lines; a++) { + if ((packet->line[a].len > 17 && memcmp(packet->line[a].ptr, "X-Kazaa-Username: ", 18) == 0) + || (packet->line[a].len > 23 && memcmp(packet->line[a].ptr, "User-Agent: PeerEnabler/", 24) == 0)) { + NDPI_LOG_INFO(ndpi_struct, + "found FASTTRACK X-Kazaa-Username: || User-Agent: PeerEnabler/\n"); + ndpi_int_fasttrack_add_connection(ndpi_struct, flow); + return; } + } + } + } - exclude_fasttrack: - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + exclude_fasttrack: + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } diff --git a/src/lib/protocols/fbzero.c b/src/lib/protocols/fbzero.c index 6c818e3..936d99e 100644 --- a/src/lib/protocols/fbzero.c +++ b/src/lib/protocols/fbzero.c @@ -85,9 +85,12 @@ void ndpi_search_fbzero(struct ndpi_detection_module_struct *ndpi_struct, char *value = (char*)&packet->payload[data_offset + data_prev_offset]; u_int tag_len = t->tag_offset_len-data_prev_offset, max_len; ndpi_protocol_match_result ret_match; - + max_len = ndpi_min(tag_len, sizeof(flow->host_server_name)-1); + if (data_offset + data_prev_offset + max_len >= packet->payload_packet_len) { + return; + } strncpy((char*)flow->host_server_name, value, max_len); flow->host_server_name[max_len] = '\0'; diff --git a/src/lib/protocols/fiesta.c b/src/lib/protocols/fiesta.c index ba567a5..b29c4df 100644 --- a/src/lib/protocols/fiesta.c +++ b/src/lib/protocols/fiesta.c @@ -2,7 +2,7 @@ * fiesta.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/fix.c b/src/lib/protocols/fix.c index 7dbf61b..35cb152 100644 --- a/src/lib/protocols/fix.c +++ b/src/lib/protocols/fix.c @@ -1,7 +1,7 @@ /* * fix.c * - * Copyright (C) 2017-19 - ntop.org + * Copyright (C) 2017-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -33,7 +33,7 @@ void ndpi_search_fix(struct ndpi_detection_module_struct *ndpi_struct, struct nd struct ndpi_packet_struct *packet = &flow->packet; NDPI_LOG_DBG(ndpi_struct, "search FIX\n"); - if(packet->tcp) { + if(packet->tcp && packet->payload_packet_len > 5) { // 8= if(packet->payload[0] == 0x38 && packet->payload[1] == 0x3d) { // FIX. diff --git a/src/lib/protocols/florensia.c b/src/lib/protocols/florensia.c index 88c6459..1787dc2 100644 --- a/src/lib/protocols/florensia.c +++ b/src/lib/protocols/florensia.c @@ -2,7 +2,7 @@ * florensia.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ftp_control.c b/src/lib/protocols/ftp_control.c index 7a3250b..a56f2cd 100644 --- a/src/lib/protocols/ftp_control.c +++ b/src/lib/protocols/ftp_control.c @@ -1,7 +1,7 @@ /* * ftp_control.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . - * + * */ #include "ndpi_protocol_ids.h" @@ -27,982 +27,642 @@ #include "ndpi_api.h" +// #define FTP_DEBUG -static void ndpi_int_ftp_control_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_FTP_CONTROL, NDPI_PROTOCOL_UNKNOWN); -} - -static int ndpi_ftp_control_check_request(const u_int8_t *payload, size_t payload_len) { - - if (ndpi_match_strprefix(payload, payload_len, "ABOR")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "ACCT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "ADAT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "ALLO")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "APPE")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "AUTH")) { - return 1; - } - if (ndpi_match_strprefix(payload, payload_len, "CCC")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "CDUP")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "CONF")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "CWD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "DELE")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "ENC")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "EPRT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "EPSV")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "FEAT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "HELP")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "LANG")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "LIST")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "LPRT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "LPSV")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "MDTM")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "MIC")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "MKD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "MLSD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "MLST")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "MODE")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "NLST")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "NOOP")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "OPTS")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "PASS")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "PASV")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "PBSZ")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "PORT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "PROT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "PWD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "QUIT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "REIN")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "REST")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "RETR")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "RMD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "RNFR")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "RNTO")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "SITE")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "SIZE")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "SMNT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "STAT")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "STOR")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "STOU")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "STRU")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "SYST")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "TYPE")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "USER")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XCUP")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XMKD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XPWD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XRCP")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XRMD")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XRSQ")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XSEM")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "XSEN")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "HOST")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "abor")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "acct")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "adat")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "allo")) { - return 1; - } +/* *************************************************************** */ - if (ndpi_match_strprefix(payload, payload_len, "appe")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "auth")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "ccc")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "cdup")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "conf")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "cwd")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "dele")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "enc")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "eprt")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "epsv")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "feat")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "help")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "lang")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "list")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "lprt")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "lpsv")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "mdtm")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "mic")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "mkd")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "mlsd")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "mlst")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "mode")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "nlst")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "noop")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "opts")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "pass")) { - return 1; - } - - if (ndpi_match_strprefix(payload, payload_len, "pasv")) { - return 1; - } +static void ndpi_int_ftp_control_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + ndpi_set_detected_protocol(ndpi_struct, flow, + NDPI_PROTOCOL_FTP_CONTROL, NDPI_PROTOCOL_UNKNOWN); +} - if (ndpi_match_strprefix(payload, payload_len, "pbsz")) { - return 1; - } +/* *************************************************************** */ - if (ndpi_match_strprefix(payload, payload_len, "port")) { - return 1; - } +static int ndpi_ftp_control_check_request(struct ndpi_flow_struct *flow, + const u_int8_t *payload, + size_t payload_len) { +#ifdef FTP_DEBUG + printf("%s() [%.*s]\n", __FUNCTION__, (int)payload_len, payload); +#endif - if (ndpi_match_strprefix(payload, payload_len, "prot")) { + if(ndpi_match_strprefix(payload, payload_len, "USER")) { + ndpi_user_pwd_payload_copy((u_int8_t*)flow->protos.ftp_imap_pop_smtp.username, + sizeof(flow->protos.ftp_imap_pop_smtp.username), 5, + payload, payload_len); return 1; } - if (ndpi_match_strprefix(payload, payload_len, "pwd")) { + if(ndpi_match_strprefix(payload, payload_len, "PASS")) { + ndpi_user_pwd_payload_copy((u_int8_t*)flow->protos.ftp_imap_pop_smtp.password, + sizeof(flow->protos.ftp_imap_pop_smtp.password), 5, + payload, payload_len); return 1; } - if (ndpi_match_strprefix(payload, payload_len, "quit")) { - return 1; - } + /* ***************************************************** */ - if (ndpi_match_strprefix(payload, payload_len, "rein")) { + if(ndpi_match_strprefix(payload, payload_len, "ABOR")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "rest")) { + if(ndpi_match_strprefix(payload, payload_len, "ACCT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "retr")) { + if(ndpi_match_strprefix(payload, payload_len, "ADAT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "rmd")) { + if(ndpi_match_strprefix(payload, payload_len, "ALLO")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "rnfr")) { + if(ndpi_match_strprefix(payload, payload_len, "APPE")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "rnto")) { + if(ndpi_match_strprefix(payload, payload_len, "AUTH")) { return 1; } - - if (ndpi_match_strprefix(payload, payload_len, "site")) { + if(ndpi_match_strprefix(payload, payload_len, "CCC")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "size")) { + if(ndpi_match_strprefix(payload, payload_len, "CDUP")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "smnt")) { + if(ndpi_match_strprefix(payload, payload_len, "CONF")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "stat")) { + if(ndpi_match_strprefix(payload, payload_len, "CWD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "stor")) { + if(ndpi_match_strprefix(payload, payload_len, "DELE")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "stou")) { + if(ndpi_match_strprefix(payload, payload_len, "ENC")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "stru")) { + if(ndpi_match_strprefix(payload, payload_len, "EPRT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "syst")) { + if(ndpi_match_strprefix(payload, payload_len, "EPSV")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "type")) { + if(ndpi_match_strprefix(payload, payload_len, "FEAT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "user")) { + if(ndpi_match_strprefix(payload, payload_len, "HELP")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xcup")) { + if(ndpi_match_strprefix(payload, payload_len, "LANG")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xmkd")) { + if(ndpi_match_strprefix(payload, payload_len, "LIST")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xpwd")) { + if(ndpi_match_strprefix(payload, payload_len, "LPRT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xrcp")) { + if(ndpi_match_strprefix(payload, payload_len, "LPSV")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xrmd")) { + if(ndpi_match_strprefix(payload, payload_len, "MDTM")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xrsq")) { + if(ndpi_match_strprefix(payload, payload_len, "MIC")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xsem")) { + if(ndpi_match_strprefix(payload, payload_len, "MKD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "xsen")) { + if(ndpi_match_strprefix(payload, payload_len, "MLSD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "host")) { + if(ndpi_match_strprefix(payload, payload_len, "MLST")) { return 1; } - - return 0; -} -static int ndpi_ftp_control_check_response(const u_int8_t *payload, size_t payload_len) { - - if (ndpi_match_strprefix(payload, payload_len, "110-")) { + if(ndpi_match_strprefix(payload, payload_len, "MODE")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "120-")) { + if(ndpi_match_strprefix(payload, payload_len, "NLST")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "125-")) { + if(ndpi_match_strprefix(payload, payload_len, "NOOP")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "150-")) { + if(ndpi_match_strprefix(payload, payload_len, "OPTS")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "202-")) { + if(ndpi_match_strprefix(payload, payload_len, "PASV")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "211-")) { + if(ndpi_match_strprefix(payload, payload_len, "PBSZ")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "212-")) { + if(ndpi_match_strprefix(payload, payload_len, "PORT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "213-")) { + if(ndpi_match_strprefix(payload, payload_len, "PROT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "214-")) { + if(ndpi_match_strprefix(payload, payload_len, "PWD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "215-")) { + if(ndpi_match_strprefix(payload, payload_len, "QUIT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "220-")) { + if(ndpi_match_strprefix(payload, payload_len, "REIN")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "221-")) { + if(ndpi_match_strprefix(payload, payload_len, "REST")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "225-")) { + if(ndpi_match_strprefix(payload, payload_len, "RETR")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "226-")) { + if(ndpi_match_strprefix(payload, payload_len, "RMD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "227-")) { + if(ndpi_match_strprefix(payload, payload_len, "RNFR")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "228-")) { + if(ndpi_match_strprefix(payload, payload_len, "RNTO")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "229-")) { + if(ndpi_match_strprefix(payload, payload_len, "SITE")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "230-")) { + if(ndpi_match_strprefix(payload, payload_len, "SIZE")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "231-")) { + if(ndpi_match_strprefix(payload, payload_len, "SMNT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "232-")) { + if(ndpi_match_strprefix(payload, payload_len, "STAT")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "250-")) { + if(ndpi_match_strprefix(payload, payload_len, "STOR")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "257-")) { + if(ndpi_match_strprefix(payload, payload_len, "STOU")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "331-")) { + if(ndpi_match_strprefix(payload, payload_len, "STRU")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "332-")) { + if(ndpi_match_strprefix(payload, payload_len, "SYST")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "350-")) { + if(ndpi_match_strprefix(payload, payload_len, "TYPE")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "421-")) { + if(ndpi_match_strprefix(payload, payload_len, "XCUP")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "425-")) { + if(ndpi_match_strprefix(payload, payload_len, "XMKD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "426-")) { + if(ndpi_match_strprefix(payload, payload_len, "XPWD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "430-")) { + if(ndpi_match_strprefix(payload, payload_len, "XRCP")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "434-")) { + if(ndpi_match_strprefix(payload, payload_len, "XRMD")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "450-")) { + if(ndpi_match_strprefix(payload, payload_len, "XRSQ")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "451-")) { + if(ndpi_match_strprefix(payload, payload_len, "XSEM")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "452-")) { + if(ndpi_match_strprefix(payload, payload_len, "XSEN")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "501-")) { + if(ndpi_match_strprefix(payload, payload_len, "HOST")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "502-")) { + if(ndpi_match_strprefix(payload, payload_len, "abor")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "503-")) { + if(ndpi_match_strprefix(payload, payload_len, "acct")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "504-")) { + if(ndpi_match_strprefix(payload, payload_len, "adat")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "530-")) { + if(ndpi_match_strprefix(payload, payload_len, "allo")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "532-")) { + if(ndpi_match_strprefix(payload, payload_len, "appe")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "550-")) { + if(ndpi_match_strprefix(payload, payload_len, "auth")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "551-")) { + if(ndpi_match_strprefix(payload, payload_len, "ccc")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "552-")) { + if(ndpi_match_strprefix(payload, payload_len, "cdup")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "553-")) { + if(ndpi_match_strprefix(payload, payload_len, "conf")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "631-")) { + if(ndpi_match_strprefix(payload, payload_len, "cwd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "632-")) { + if(ndpi_match_strprefix(payload, payload_len, "dele")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "633-")) { + if(ndpi_match_strprefix(payload, payload_len, "enc")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "10054-")) { + if(ndpi_match_strprefix(payload, payload_len, "eprt")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "10060-")) { + if(ndpi_match_strprefix(payload, payload_len, "epsv")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "10061-")) { + if(ndpi_match_strprefix(payload, payload_len, "feat")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "10066-")) { + if(ndpi_match_strprefix(payload, payload_len, "help")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "10068-")) { + if(ndpi_match_strprefix(payload, payload_len, "lang")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "110 ")) { + if(ndpi_match_strprefix(payload, payload_len, "list")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "120 ")) { + if(ndpi_match_strprefix(payload, payload_len, "lprt")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "125 ")) { + if(ndpi_match_strprefix(payload, payload_len, "lpsv")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "150 ")) { + if(ndpi_match_strprefix(payload, payload_len, "mdtm")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "202 ")) { + if(ndpi_match_strprefix(payload, payload_len, "mic")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "211 ")) { + if(ndpi_match_strprefix(payload, payload_len, "mkd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "212 ")) { + if(ndpi_match_strprefix(payload, payload_len, "mlsd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "213 ")) { + if(ndpi_match_strprefix(payload, payload_len, "mlst")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "214 ")) { + if(ndpi_match_strprefix(payload, payload_len, "mode")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "215 ")) { + if(ndpi_match_strprefix(payload, payload_len, "nlst")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "220 ")) { + if(ndpi_match_strprefix(payload, payload_len, "noop")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "221 ")) { + if(ndpi_match_strprefix(payload, payload_len, "opts")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "225 ")) { + if(ndpi_match_strprefix(payload, payload_len, "pass")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "226 ")) { + if(ndpi_match_strprefix(payload, payload_len, "pasv")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "227 ")) { + if(ndpi_match_strprefix(payload, payload_len, "pbsz")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "228 ")) { + if(ndpi_match_strprefix(payload, payload_len, "port")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "229 ")) { + if(ndpi_match_strprefix(payload, payload_len, "prot")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "230 ")) { + if(ndpi_match_strprefix(payload, payload_len, "pwd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "231 ")) { + if(ndpi_match_strprefix(payload, payload_len, "quit")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "232 ")) { + if(ndpi_match_strprefix(payload, payload_len, "rein")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "250 ")) { + if(ndpi_match_strprefix(payload, payload_len, "rest")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "257 ")) { + if(ndpi_match_strprefix(payload, payload_len, "retr")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "331 ")) { + if(ndpi_match_strprefix(payload, payload_len, "rmd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "332 ")) { + if(ndpi_match_strprefix(payload, payload_len, "rnfr")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "350 ")) { + if(ndpi_match_strprefix(payload, payload_len, "rnto")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "421 ")) { + if(ndpi_match_strprefix(payload, payload_len, "site")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "425 ")) { + if(ndpi_match_strprefix(payload, payload_len, "size")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "426 ")) { + if(ndpi_match_strprefix(payload, payload_len, "smnt")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "430 ")) { + if(ndpi_match_strprefix(payload, payload_len, "stat")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "434 ")) { + if(ndpi_match_strprefix(payload, payload_len, "stor")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "450 ")) { + if(ndpi_match_strprefix(payload, payload_len, "stou")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "451 ")) { + if(ndpi_match_strprefix(payload, payload_len, "stru")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "452 ")) { + if(ndpi_match_strprefix(payload, payload_len, "syst")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "501 ")) { + if(ndpi_match_strprefix(payload, payload_len, "type")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "502 ")) { + if(ndpi_match_strprefix(payload, payload_len, "user")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "503 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xcup")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "504 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xmkd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "530 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xpwd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "532 ")) { - return 1; - } - if (ndpi_match_strprefix(payload, payload_len, "550 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xrcp")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "551 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xrmd")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "552 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xrsq")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "553 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xsem")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "631 ")) { + if(ndpi_match_strprefix(payload, payload_len, "xsen")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "632 ")) { + if(ndpi_match_strprefix(payload, payload_len, "host")) { return 1; } - if (ndpi_match_strprefix(payload, payload_len, "633 ")) { - return 1; - } + return 0; +} - if (ndpi_match_strprefix(payload, payload_len, "10054 ")) { - return 1; - } +/* *************************************************************** */ - if (ndpi_match_strprefix(payload, payload_len, "10060 ")) { - return 1; - } +static int ndpi_ftp_control_check_response(struct ndpi_flow_struct *flow, + const u_int8_t *payload, + size_t payload_len) { +#ifdef FTP_DEBUG + printf("%s() [%.*s]\n", __FUNCTION__, (int)payload_len, payload); +#endif - if (ndpi_match_strprefix(payload, payload_len, "10061 ")) { - return 1; - } + if(payload_len == 0) return(1); - if (ndpi_match_strprefix(payload, payload_len, "10066 ")) { - return 1; - } + switch(payload[0]) { + case '1': + case '2': + case '3': + case '6': + return(1); + break; - if (ndpi_match_strprefix(payload, payload_len, "10068 ")) { - return 1; + case '4': + case '5': + flow->protos.ftp_imap_pop_smtp.auth_failed = 1; + return(1); + break; } return 0; } -static void ndpi_check_ftp_control(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { +/* *************************************************************** */ - struct ndpi_packet_struct *packet = &flow->packet; +static void ndpi_check_ftp_control(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; u_int32_t payload_len = packet->payload_packet_len; /* Check connection over TCP */ if(packet->tcp) { - + u_int16_t twentyfive = htons(25); + /* Exclude SMTP, which uses similar commands. */ - if (packet->tcp->dest == htons(25) || packet->tcp->source == htons(25)) { + if(packet->tcp->dest == twentyfive || packet->tcp->source == twentyfive) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } - + /* Break after 20 packets. */ - if (flow->packet_counter > 20) { + if(flow->packet_counter > 20) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } - + /* Check if we so far detected the protocol in the request or not. */ - if (flow->ftp_control_stage == 0) { + if(flow->ftp_control_stage == 0) { NDPI_LOG_DBG2(ndpi_struct, "FTP_CONTROL stage 0: \n"); - - if ((payload_len > 0) && ndpi_ftp_control_check_request(packet->payload, payload_len)) { - NDPI_LOG_DBG2(ndpi_struct, "Possible FTP_CONTROL request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ + + if((payload_len > 0) && ndpi_ftp_control_check_request(flow, packet->payload, payload_len)) { + NDPI_LOG_DBG2(ndpi_struct, + "Possible FTP_CONTROL request detected, we will look further for the response..\n"); + + /* + Encode the direction of the packet in the stage, so we will know when we need + to look for the response packet. + */ flow->ftp_control_stage = packet->packet_direction + 1; } - } else { NDPI_LOG_DBG2(ndpi_struct, "FTP_CONTROL stage %u: \n", flow->ftp_control_stage); - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->ftp_control_stage - packet->packet_direction) == 1) { + + /* + At first check, if this is for sure a response packet (in another direction. + If not, do nothing now and return. + */ + if((flow->ftp_control_stage - packet->packet_direction) == 1) { return; } - + /* This is a packet in another direction. Check if we find the proper response. */ - if ((payload_len > 0) && ndpi_ftp_control_check_response(packet->payload, payload_len)) { + if((payload_len > 0) && ndpi_ftp_control_check_response(flow, packet->payload, payload_len)) { NDPI_LOG_INFO(ndpi_struct, "found FTP_CONTROL\n"); - ndpi_int_ftp_control_add_connection(ndpi_struct, flow); + +#ifdef FTP_DEBUG + printf("%s() [user: %s][pwd: %s]\n", __FUNCTION__, + flow->protos.ftp_imap_pop_smtp.username, flow->protos.ftp_imap_pop_smtp.password); +#endif + + if(flow->protos.ftp_imap_pop_smtp.password[0] == '\0') + flow->ftp_control_stage = 0; + else + ndpi_int_ftp_control_add_connection(ndpi_struct, flow); } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to FTP_CONTROL, resetting the stage to 0\n"); + NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to FTP_CONTROL, " + "resetting the stage to 0\n"); flow->ftp_control_stage = 0; } } } } -void ndpi_search_ftp_control(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { +/* *************************************************************** */ + +void ndpi_search_ftp_control(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; NDPI_LOG_DBG(ndpi_struct, "search FTP_CONTROL\n"); /* skip marked packets */ - if (packet->detected_protocol_stack[0] != NDPI_PROTOCOL_FTP_CONTROL) { - if (packet->tcp_retransmission == 0) { + if(packet->detected_protocol_stack[0] != NDPI_PROTOCOL_FTP_CONTROL) { + if(packet->tcp_retransmission == 0) { ndpi_check_ftp_control(ndpi_struct, flow); } } } +/* *************************************************************** */ -void init_ftp_control_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ +void init_ftp_control_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("FTP_CONTROL", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_FTP_CONTROL, ndpi_search_ftp_control, diff --git a/src/lib/protocols/ftp_data.c b/src/lib/protocols/ftp_data.c index edffabb..8d4330d 100644 --- a/src/lib/protocols/ftp_data.c +++ b/src/lib/protocols/ftp_data.c @@ -1,7 +1,7 @@ /* * ftp_data.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * The signature is based on the Libprotoident library. * diff --git a/src/lib/protocols/git.c b/src/lib/protocols/git.c index e8a1db5..ebd5b22 100644 --- a/src/lib/protocols/git.c +++ b/src/lib/protocols/git.c @@ -1,7 +1,7 @@ /* * git.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This module is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/gnutella.c b/src/lib/protocols/gnutella.c index 4531a71..18c5b2f 100644 --- a/src/lib/protocols/gnutella.c +++ b/src/lib/protocols/gnutella.c @@ -2,7 +2,7 @@ * gnutella.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -41,7 +41,7 @@ static void ndpi_int_gnutella_add_connection(struct ndpi_detection_module_struct NDPI_LOG_INFO(ndpi_struct, "found GNUTELLA\n"); if (src != NULL) { - src->gnutella_ts = packet->tick_timestamp; + src->gnutella_ts = packet->current_time_ms; if (packet->udp != NULL) { if (!src->detected_gnutella_udp_port1) { src->detected_gnutella_udp_port1 = (packet->udp->source); @@ -58,7 +58,7 @@ static void ndpi_int_gnutella_add_connection(struct ndpi_detection_module_struct } } if (dst != NULL) { - dst->gnutella_ts = packet->tick_timestamp; + dst->gnutella_ts = packet->current_time_ms; } } @@ -74,19 +74,19 @@ void ndpi_search_gnutella(struct ndpi_detection_module_struct *ndpi_struct, stru if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_GNUTELLA) { if (src != NULL && ((u_int32_t) - (packet->tick_timestamp - src->gnutella_ts) < ndpi_struct->gnutella_timeout)) { + (packet->current_time_ms - src->gnutella_ts) < ndpi_struct->gnutella_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "save src connection packet detected\n"); - src->gnutella_ts = packet->tick_timestamp; + src->gnutella_ts = packet->current_time_ms; } else if (dst != NULL && ((u_int32_t) - (packet->tick_timestamp - dst->gnutella_ts) < ndpi_struct->gnutella_timeout)) { + (packet->current_time_ms - dst->gnutella_ts) < ndpi_struct->gnutella_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "save dst connection packet detected\n"); - dst->gnutella_ts = packet->tick_timestamp; + dst->gnutella_ts = packet->current_time_ms; } - if (src != NULL && (packet->tick_timestamp - src->gnutella_ts) > ndpi_struct->gnutella_timeout) { + if (src != NULL && (packet->current_time_ms - src->gnutella_ts) > ndpi_struct->gnutella_timeout) { src->detected_gnutella_udp_port1 = 0; src->detected_gnutella_udp_port2 = 0; } - if (dst != NULL && (packet->tick_timestamp - dst->gnutella_ts) > ndpi_struct->gnutella_timeout) { + if (dst != NULL && (packet->current_time_ms - dst->gnutella_ts) > ndpi_struct->gnutella_timeout) { dst->detected_gnutella_udp_port1 = 0; dst->detected_gnutella_udp_port2 = 0; } @@ -125,7 +125,7 @@ void ndpi_search_gnutella(struct ndpi_detection_module_struct *ndpi_struct, stru } } } - if (packet->payload_packet_len > 50 && ((memcmp(packet->payload, "GET / HTTP", 9) == 0))) { + if (packet->payload_packet_len > 50 && ((memcmp(packet->payload, "GET / HTTP", 10) == 0))) { ndpi_parse_packet_line_info(ndpi_struct, flow); if ((packet->user_agent_line.ptr != NULL && packet->user_agent_line.len > 15 && memcmp(packet->user_agent_line.ptr, "BearShare Lite ", 15) == 0) @@ -236,7 +236,7 @@ void ndpi_search_gnutella(struct ndpi_detection_module_struct *ndpi_struct, stru } else if (packet->udp != NULL) { if (src != NULL && (packet->udp->source == src->detected_gnutella_udp_port1 || packet->udp->source == src->detected_gnutella_udp_port2) && - (packet->tick_timestamp - src->gnutella_ts) < ndpi_struct->gnutella_timeout) { + (packet->current_time_ms - src->gnutella_ts) < ndpi_struct->gnutella_timeout) { NDPI_LOG_DBG2(ndpi_struct, "port based detection\n\n"); ndpi_int_gnutella_add_connection(ndpi_struct, flow); } diff --git a/src/lib/protocols/gtp.c b/src/lib/protocols/gtp.c index 849cd12..dd9d423 100644 --- a/src/lib/protocols/gtp.c +++ b/src/lib/protocols/gtp.c @@ -1,7 +1,7 @@ /* * gtp.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/guildwars.c b/src/lib/protocols/guildwars.c index 0884b43..d646bb8 100644 --- a/src/lib/protocols/guildwars.c +++ b/src/lib/protocols/guildwars.c @@ -2,7 +2,7 @@ * guildwars.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/h323.c b/src/lib/protocols/h323.c index d407c98..015a6a1 100644 --- a/src/lib/protocols/h323.c +++ b/src/lib/protocols/h323.c @@ -1,9 +1,22 @@ /* * h323.c * - * Copyright (C) 2015-18 ntop.org + * Copyright (C) 2015-20 ntop.org * Copyright (C) 2013 Remy Mudingay * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * */ #include "ndpi_protocol_ids.h" @@ -25,44 +38,48 @@ void ndpi_search_h323(struct ndpi_detection_module_struct *ndpi_struct, struct n NDPI_LOG_DBG(ndpi_struct, "search H323\n"); - if(packet->tcp != NULL) { + /* + The TPKT protocol is used by ISO 8072 (on port 102) + and H.323. So this check below is to avoid ambiguities + */ + if((packet->tcp != NULL) && (packet->tcp->dest != ntohs(102))) { NDPI_LOG_DBG2(ndpi_struct, "calculated dport over tcp\n"); /* H323 */ - if(packet->payload_packet_len >= 3 + if(packet->payload_packet_len > 4 && (packet->payload[0] == 0x03) && (packet->payload[1] == 0x00)) { - struct tpkt *t = (struct tpkt*)packet->payload; - u_int16_t len = ntohs(t->len); - - if(packet->payload_packet_len == len) { - /* - We need to check if this packet is in reality - a RDP (Remote Desktop) packet encapsulated on TPTK - */ - - if(packet->payload[4] == (packet->payload_packet_len - sizeof(struct tpkt) - 1)) { - /* ISO 8073/X.224 */ - if((packet->payload[5] == 0xE0 /* CC Connect Request */) - || (packet->payload[5] == 0xD0 /* CC Connect Confirm */)) { - NDPI_LOG_INFO(ndpi_struct, "found RDP\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_RDP, NDPI_PROTOCOL_UNKNOWN); - return; - } + struct tpkt *t = (struct tpkt*)packet->payload; + u_int16_t len = ntohs(t->len); + + if(packet->payload_packet_len == len) { + /* + We need to check if this packet is in reality + a RDP (Remote Desktop) packet encapsulated on TPTK + */ + + if(packet->payload[4] == (packet->payload_packet_len - sizeof(struct tpkt) - 1)) { + /* ISO 8073/X.224 */ + if((packet->payload[5] == 0xE0 /* CC Connect Request */) + || (packet->payload[5] == 0xD0 /* CC Connect Confirm */)) { + NDPI_LOG_INFO(ndpi_struct, "found RDP\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_RDP, NDPI_PROTOCOL_UNKNOWN); + return; } + } - flow->l4.tcp.h323_valid_packets++; + flow->l4.tcp.h323_valid_packets++; - if(flow->l4.tcp.h323_valid_packets >= 2) { - NDPI_LOG_INFO(ndpi_struct, "found H323 broadcast\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_H323, NDPI_PROTOCOL_UNKNOWN); - } - } else { - /* This is not H.323 */ - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; + if(flow->l4.tcp.h323_valid_packets >= 2) { + NDPI_LOG_INFO(ndpi_struct, "found H323 broadcast\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_H323, NDPI_PROTOCOL_UNKNOWN); } - } + } else { + /* This is not H.323 */ + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + } } else if(packet->udp != NULL) { sport = ntohs(packet->udp->source), dport = ntohs(packet->udp->dest); NDPI_LOG_DBG2(ndpi_struct, "calculated dport over udp\n"); @@ -76,28 +93,28 @@ void ndpi_search_h323(struct ndpi_detection_module_struct *ndpi_struct, struct n return; } /* H323 */ - if(sport == 1719 || dport == 1719) - { - if(packet->payload[0] == 0x16 && packet->payload[1] == 0x80 && packet->payload[4] == 0x06 && packet->payload[5] == 0x00) - { - NDPI_LOG_INFO(ndpi_struct, "found H323 broadcast\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_H323, NDPI_PROTOCOL_UNKNOWN); - return; - } - else if(packet->payload_packet_len >= 20 && packet->payload_packet_len <= 117) - { - NDPI_LOG_INFO(ndpi_struct, "found H323 broadcast\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_H323, NDPI_PROTOCOL_UNKNOWN); - return; - } - else - { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; - } + if(sport == 1719 || dport == 1719) { + if((packet->payload_packet_len >= 5) + && (packet->payload[0] == 0x16) + && (packet->payload[1] == 0x80) + && (packet->payload[4] == 0x06) + && (packet->payload[5] == 0x00)) { + NDPI_LOG_INFO(ndpi_struct, "found H323 broadcast\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_H323, NDPI_PROTOCOL_UNKNOWN); + return; + } else if(packet->payload_packet_len >= 20 && packet->payload_packet_len <= 117) { + NDPI_LOG_INFO(ndpi_struct, "found H323 broadcast\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_H323, NDPI_PROTOCOL_UNKNOWN); + return; + } else { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; } + } } - + + if(flow->packet_counter > 5) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_h323_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) diff --git a/src/lib/protocols/halflife2_and_mods.c b/src/lib/protocols/halflife2_and_mods.c index 46edeb6..13e7170 100644 --- a/src/lib/protocols/halflife2_and_mods.c +++ b/src/lib/protocols/halflife2_and_mods.c @@ -2,7 +2,7 @@ * halflife2_and_mods.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/hangout.c b/src/lib/protocols/hangout.c index 06edafb..acddfed 100644 --- a/src/lib/protocols/hangout.c +++ b/src/lib/protocols/hangout.c @@ -1,7 +1,7 @@ /* * hangout.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This module is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -108,6 +108,8 @@ void ndpi_search_hangout(struct ndpi_detection_module_struct *ndpi_struct, #endif ndpi_lru_add_to_cache(ndpi_struct->stun_cache, key, NDPI_PROTOCOL_HANGOUT_DUO); + if(ndpi_struct->ndpi_notify_lru_add_handler_ptr) + ndpi_struct->ndpi_notify_lru_add_handler_ptr(ndpi_hangout_cache, key, NDPI_PROTOCOL_HANGOUT_DUO); } ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_HANGOUT_DUO, diff --git a/src/lib/protocols/http.c b/src/lib/protocols/http.c index cc27b8e..76947f3 100644 --- a/src/lib/protocols/http.c +++ b/src/lib/protocols/http.c @@ -1,7 +1,7 @@ /* * http.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -28,57 +28,204 @@ #include "ndpi_api.h" #include -static void ndpi_int_http_add_connection(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow, - u_int16_t category) { -#ifdef DEBUG - printf("[%s] [http_dont_dissect_response: %u]->> %s\n", __FUNCTION__, - ndpi_struct->http_dont_dissect_response, flow->http.response_status_code); +static const char* binary_file_mimes_e[] = { "exe", NULL }; +static const char* binary_file_mimes_v[] = { "vnd.ms-cab-compressed", "vnd.microsoft.portable-executable", NULL }; +static const char* binary_file_mimes_x[] = { "x-msdownload", "x-dosexec", NULL }; + +#define ATTACHMENT_LEN 3 +static const char* binary_file_ext[] = { + "exe", + "msi", + "cab", + NULL +}; + +static void ndpi_search_http_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow); + +/* *********************************************** */ + +static void ndpi_analyze_content_signature(struct ndpi_flow_struct *flow) { + if((flow->initial_binary_bytes_len >= 2) && (flow->initial_binary_bytes[0] == 0x4D) && (flow->initial_binary_bytes[1] == 0x5A)) + NDPI_SET_BIT(flow->risk, NDPI_BINARY_APPLICATION_TRANSFER); /* Win executable */ + else if((flow->initial_binary_bytes_len >= 4) && (flow->initial_binary_bytes[0] == 0x7F) && (flow->initial_binary_bytes[1] == 'E') + && (flow->initial_binary_bytes[2] == 'L') && (flow->initial_binary_bytes[3] == 'F')) + NDPI_SET_BIT(flow->risk, NDPI_BINARY_APPLICATION_TRANSFER); /* Linux executable */ + else if((flow->initial_binary_bytes_len >= 4) && (flow->initial_binary_bytes[0] == 0xCF) && (flow->initial_binary_bytes[1] == 0xFA) + && (flow->initial_binary_bytes[2] == 0xED) && (flow->initial_binary_bytes[3] == 0xFE)) + NDPI_SET_BIT(flow->risk, NDPI_BINARY_APPLICATION_TRANSFER); /* Linux executable */ + else if((flow->initial_binary_bytes_len >= 3) + && (flow->initial_binary_bytes[0] == '#') + && (flow->initial_binary_bytes[1] == '!') + && (flow->initial_binary_bytes[2] == '/')) + NDPI_SET_BIT(flow->risk, NDPI_BINARY_APPLICATION_TRANSFER); /* Unix script (e.g. #!/bin/sh) */ + else if(flow->initial_binary_bytes_len >= 8) { + u_int8_t exec_pattern[] = { 0x64, 0x65, 0x78, 0x0A, 0x30, 0x33, 0x35, 0x00 }; + + if(memcmp(flow->initial_binary_bytes, exec_pattern, 8) == 0) + NDPI_SET_BIT(flow->risk, NDPI_BINARY_APPLICATION_TRANSFER); /* Dalvik Executable (Android) */ + } +} + +/* *********************************************** */ + +static int ndpi_search_http_tcp_again(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + + ndpi_search_http_tcp(ndpi_struct, flow); + +#ifdef HTTP_DEBUG + printf("=> %s()\n", __FUNCTION__); #endif - if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { - /* This is HTTP and it is not a sub protocol (e.g. skype or dropbox) */ + if((flow->host_server_name[0] != '\0') + && (flow->http.response_status_code != 0) + ) { + /* stop extra processing */ - ndpi_search_tcp_or_udp(ndpi_struct, flow); + if(flow->initial_binary_bytes_len) ndpi_analyze_content_signature(flow); + flow->extra_packets_func = NULL; /* We're good now */ + return(0); + } - /* If no custom protocol has been detected */ - if(flow->guessed_host_protocol_id != NDPI_PROTOCOL_UNKNOWN) { - ndpi_int_reset_protocol(flow); - flow->http_upper_protocol = flow->guessed_host_protocol_id, flow->http_lower_protocol = NDPI_PROTOCOL_HTTP; - } else - flow->http_upper_protocol = NDPI_PROTOCOL_HTTP, flow->http_lower_protocol = NDPI_PROTOCOL_UNKNOWN; + /* Possibly more processing */ + return(1); +} - if(ndpi_struct->http_dont_dissect_response) - ndpi_set_detected_protocol(ndpi_struct, flow, flow->http_upper_protocol, flow->http_lower_protocol); - else { - flow->detected_protocol_stack[0] = NDPI_PROTOCOL_UNKNOWN, flow->detected_protocol_stack[1] = NDPI_PROTOCOL_UNKNOWN; - flow->packet.detected_protocol_stack[0] = NDPI_PROTOCOL_UNKNOWN, flow->packet.detected_protocol_stack[1] = NDPI_PROTOCOL_UNKNOWN; +/* *********************************************** */ + +/* https://www.freeformatter.com/mime-types-list.html */ +static ndpi_protocol_category_t ndpi_http_check_content(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + + if(packet->content_line.len > 0) { + u_int app_len = sizeof("application"); + + if(packet->content_line.len > app_len) { + const char *app = (const char *)&packet->content_line.ptr[app_len]; + u_int app_len_avail = packet->content_line.len-app_len; + + if(strncasecmp(app, "mpeg", app_len_avail) == 0) { + flow->guessed_category = flow->category = NDPI_PROTOCOL_CATEGORY_STREAMING; + return(flow->category); + } else if(app_len_avail > 3) { + const char** cmp_mimes = NULL; + + switch(app[0]) { + case 'e': cmp_mimes = binary_file_mimes_e; break; + case 'v': cmp_mimes = binary_file_mimes_v; break; + case 'x': cmp_mimes = binary_file_mimes_x; break; + } + + if(cmp_mimes != NULL) { + u_int8_t i; + + for(i = 0; cmp_mimes[i] != NULL; i++) { + if(strncasecmp(app, cmp_mimes[i], app_len_avail) == 0) { + flow->guessed_category = flow->category = NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT; + NDPI_SET_BIT(flow->risk, NDPI_BINARY_APPLICATION_TRANSFER); + NDPI_LOG_INFO(ndpi_struct, "found executable HTTP transfer"); + return(flow->category); + } + } + } + } + } + + /* check for attachment */ + if(packet->content_disposition_line.len > 0) { + u_int8_t attachment_len = sizeof("attachment; filename"); + + if(packet->content_disposition_line.len > attachment_len) { + u_int8_t filename_len = packet->content_disposition_line.len - attachment_len; + + if(filename_len > ATTACHMENT_LEN) { + attachment_len += filename_len-ATTACHMENT_LEN-1; + + if((attachment_len+ATTACHMENT_LEN) <= packet->content_disposition_line.len) { + for(int i = 0; binary_file_ext[i] != NULL; i++) { + /* Use memcmp in case content-disposition contains binary data */ + if(memcmp((const char*)&packet->content_disposition_line.ptr[attachment_len], + binary_file_ext[i], ATTACHMENT_LEN) == 0) { + flow->guessed_category = flow->category = NDPI_PROTOCOL_CATEGORY_DOWNLOAD_FT; + NDPI_SET_BIT(flow->risk, NDPI_BINARY_APPLICATION_TRANSFER); + NDPI_LOG_INFO(ndpi_struct, "found executable HTTP transfer"); + return(flow->category); + } + } + } + } + } } - } else { - if((!ndpi_struct->http_dont_dissect_response) && (flow->http.response_status_code == 0)) { - flow->http_upper_protocol = flow->detected_protocol_stack[0], flow->http_lower_protocol = flow->detected_protocol_stack[1]; - flow->detected_protocol_stack[0] = NDPI_PROTOCOL_UNKNOWN, flow->detected_protocol_stack[1] = NDPI_PROTOCOL_UNKNOWN; - flow->packet.detected_protocol_stack[0] = NDPI_PROTOCOL_UNKNOWN, flow->packet.detected_protocol_stack[1] = NDPI_PROTOCOL_UNKNOWN; + + switch(packet->content_line.ptr[0]) { + case 'a': + if(strncasecmp((const char *)packet->content_line.ptr, "audio", + ndpi_min(packet->content_line.len, 5)) == 0) + flow->guessed_category = flow->category = NDPI_PROTOCOL_CATEGORY_MEDIA; + break; + + case 'v': + if(strncasecmp((const char *)packet->content_line.ptr, "video", + ndpi_min(packet->content_line.len, 5)) == 0) + flow->guessed_category = flow->category = NDPI_PROTOCOL_CATEGORY_MEDIA; + break; } } - flow->http_detected = 1, flow->guessed_category = category; + return(flow->category); } +/* *********************************************** */ + +static void ndpi_int_http_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + u_int16_t http_protocol, + ndpi_protocol_category_t category) { +#ifdef HTTP_DEBUG + printf("=> %s()\n", __FUNCTION__); +#endif + + if(flow->extra_packets_func && (flow->guessed_host_protocol_id == NDPI_PROTOCOL_UNKNOWN)) + return; /* Nothing new to add */ + + /* This is HTTP and it is not a sub protocol (e.g. skype or dropbox) */ + ndpi_search_tcp_or_udp(ndpi_struct, flow); + + /* If no custom protocol has been detected */ + if((flow->guessed_host_protocol_id == NDPI_PROTOCOL_UNKNOWN) || (http_protocol != NDPI_PROTOCOL_HTTP)) + flow->guessed_host_protocol_id = http_protocol; + + // ndpi_int_reset_protocol(flow); + ndpi_set_detected_protocol(ndpi_struct, flow, flow->guessed_host_protocol_id, NDPI_PROTOCOL_HTTP); + + /* This is necessary to inform the core to call this dissector again */ + flow->check_extra_packets = 1; + flow->max_extra_packets_to_check = 5; + flow->extra_packets_func = ndpi_search_http_tcp_again; + flow->http_detected = 1; +} + +/* ************************************************************* */ + static void rtsp_parse_packet_acceptline(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - if(packet->accept_line.len >= 28 && memcmp(packet->accept_line.ptr, "application/x-rtsp-tunnelled", 28) == 0) { + if((packet->accept_line.len >= 28) + && (memcmp(packet->accept_line.ptr, "application/x-rtsp-tunnelled", 28) == 0)) { NDPI_LOG_INFO(ndpi_struct, "found RTSP accept line\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_RTSP); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_RTSP, NDPI_PROTOCOL_CATEGORY_MEDIA); } } +/* ************************************************************* */ + static void setHttpUserAgent(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, char *ua) { - if ( !strcmp(ua, "Windows NT 5.0")) ua = "Windows 2000"; + if( !strcmp(ua, "Windows NT 5.0")) ua = "Windows 2000"; else if(!strcmp(ua, "Windows NT 5.1")) ua = "Windows XP"; else if(!strcmp(ua, "Windows NT 5.2")) ua = "Windows Server 2003"; else if(!strcmp(ua, "Windows NT 6.0")) ua = "Windows Vista"; @@ -90,193 +237,203 @@ static void setHttpUserAgent(struct ndpi_detection_module_struct *ndpi_struct, /* Good reference for future implementations: * https://github.com/ua-parser/uap-core/blob/master/regexes.yaml */ - //printf("==> %s\n", ua); - if(!ndpi_struct->disable_metadata_export) { - snprintf((char*)flow->protos.http.detected_os, sizeof(flow->protos.http.detected_os), "%s", ua); - } + snprintf((char*)flow->protos.http.detected_os, + sizeof(flow->protos.http.detected_os), "%s", ua); } -static void parseHttpSubprotocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { +/* ************************************************************* */ + +static void ndpi_http_parse_subprotocol(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { if((flow->l4.tcp.http_stage == 0) || (flow->http.url && flow->http_detected)) { char *double_col = strchr((char*)flow->host_server_name, ':'); - ndpi_protocol_match_result ret_match; if(double_col) double_col[0] = '\0'; - /** - NOTE - If http_dont_dissect_response = 1 dissection of HTTP response - mime types won't happen - */ - ndpi_match_host_subprotocol(ndpi_struct, flow, (char *)flow->host_server_name, - strlen((const char *)flow->host_server_name), - &ret_match, - NDPI_PROTOCOL_HTTP); + ndpi_match_hostname_protocol(ndpi_struct, flow, NDPI_PROTOCOL_HTTP, + (char *)flow->host_server_name, + strlen((const char *)flow->host_server_name)); + } +} + +/* ************************************************************* */ + +static void ndpi_check_user_agent(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + char *ua) { + if((!ua) || (ua[0] == '\0')) return; + + if((strlen(ua) < 4) + || (!strncmp(ua, "test", 4)) + || (!strncmp(ua, "impossible_bigrams_automa, ua) + ) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_USER_AGENT); + } +} + +int http_process_user_agent(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + const u_int8_t *ua_ptr, u_int16_t ua_ptr_len) +{ + /** + Format examples: + Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) .... + Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0 + */ + if(ua_ptr_len > 7) { + char ua[256]; + u_int mlen = ndpi_min(ua_ptr_len, sizeof(ua)-1); + + strncpy(ua, (const char *)ua_ptr, mlen); + ua[mlen] = '\0'; + + if(strncmp(ua, "Mozilla", 7) == 0) { + char *parent = strchr(ua, '('); + + if(parent) { + char *token, *end; + + parent++; + end = strchr(parent, ')'); + if(end) end[0] = '\0'; + + token = strsep(&parent, ";"); + if(token) { + if((strcmp(token, "X11") == 0) + || (strcmp(token, "compatible") == 0) + || (strcmp(token, "Linux") == 0) + || (strcmp(token, "Macintosh") == 0) + ) { + token = strsep(&parent, ";"); + if(token && (token[0] == ' ')) token++; /* Skip space */ + + if(token + && ((strcmp(token, "U") == 0) + || (strncmp(token, "MSIE", 4) == 0))) { + token = strsep(&parent, ";"); + if(token && (token[0] == ' ')) token++; /* Skip space */ + + if(token && (strncmp(token, "Update", 6) == 0)) { + token = strsep(&parent, ";"); + + if(token && (token[0] == ' ')) token++; /* Skip space */ + + if(token && (strncmp(token, "AOL", 3) == 0)) { + + token = strsep(&parent, ";"); + if(token && (token[0] == ' ')) token++; /* Skip space */ + } + } + } + } + + if(token) + setHttpUserAgent(ndpi_struct, flow, token); + } + } + } else if((ua_ptr_len > 14) && (memcmp(ua, "netflix-ios-app", 15) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found netflix\n"); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_NETFLIX, NDPI_PROTOCOL_CATEGORY_STREAMING); + return -1; + } } + + if(flow->http.user_agent == NULL) { + int len = ua_ptr_len + 1; + + flow->http.user_agent = ndpi_malloc(len); + if(flow->http.user_agent) { + memcpy(flow->http.user_agent, (char*)ua_ptr, ua_ptr_len); + flow->http.user_agent[ua_ptr_len] = '\0'; + + ndpi_check_user_agent(ndpi_struct, flow, flow->http.user_agent); + } + } + + NDPI_LOG_DBG2(ndpi_struct, "User Agent Type line found %.*s\n", + ua_ptr_len, ua_ptr); + return 0; +} + +/* ************************************************************* */ + +static void ndpi_check_numeric_ip(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + char *ip, u_int ip_len) { + char buf[22], *double_dot; + struct in_addr ip_addr; + + strncpy(buf, ip, ip_len); + buf[ip_len] = '\0'; + + if((double_dot = strchr(buf, ':')) != NULL) + double_dot[0] = '\0'; + + ip_addr.s_addr = inet_addr(buf); + if(strcmp(inet_ntoa(ip_addr), buf) == 0) + NDPI_SET_BIT(flow->risk, NDPI_HTTP_NUMERIC_IP_HOST); } +/* ************************************************************* */ + +static void ndpi_check_http_url(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + char *url) { + /* Nothing to do */ +} + +/* ************************************************************* */ + /** NOTE ndpi_parse_packet_line_info is in ndpi_main.c */ static void check_content_type_and_change_protocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; + int ret; - if((!ndpi_struct->http_dont_dissect_response) && flow->http_detected && (flow->http.response_status_code != 0)) { - ndpi_set_detected_protocol(ndpi_struct, flow, flow->http_upper_protocol, flow->http_lower_protocol); -#ifdef DEBUG - printf("[%s] [http_dont_dissect_response: %u]->> %s\n", - __FUNCTION__, ndpi_struct->http_dont_dissect_response, flow->http.response_status_code); -#endif - return; - } - -#if defined(NDPI_PROTOCOL_1KXUN) || defined(NDPI_PROTOCOL_IQIYI) - /* PPStream */ - if(flow->l4.tcp.ppstream_stage > 0 && flow->iqiyi_counter == 0) { - NDPI_LOG_INFO(ndpi_struct, "found PPStream\n"); - /* ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_PPSTREAM); */ - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_PPSTREAM, NDPI_PROTOCOL_HTTP); - } - else if(flow->iqiyi_counter > 0) { - NDPI_LOG_INFO(ndpi_struct, "found iQiyi\n"); - /* ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_IQIYI); */ - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_IQIYI, NDPI_PROTOCOL_HTTP); - } -#endif + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_HTTP, NDPI_PROTOCOL_UNKNOWN); -#if defined(NDPI_PROTOCOL_1KXUN) || defined(NDPI_PROTOCOL_IQIYI) - /* 1KXUN */ - if(flow->kxun_counter > 0) { - NDPI_LOG_INFO(ndpi_struct, "found 1kxun\n"); - /* ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_1KXUN); */ - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_1KXUN, NDPI_PROTOCOL_HTTP); - } -#endif - - /* Leave the statement below commented necessary in case of call to ndpi_get_partial_detection() */ + if(flow->http_detected && (flow->http.response_status_code != 0)) + return; - /* if(!ndpi_struct->http_dont_dissect_response) */ { - if((flow->http.url == NULL) + if((flow->http.url == NULL) && (packet->http_url_name.len > 0) && (packet->host_line.len > 0)) { int len = packet->http_url_name.len + packet->host_line.len + 1; + if(isdigit(packet->host_line.ptr[0]) + && (packet->host_line.len < 21)) + ndpi_check_numeric_ip(ndpi_struct, flow, (char*)packet->host_line.ptr, packet->host_line.len); + flow->http.url = ndpi_malloc(len); if(flow->http.url) { strncpy(flow->http.url, (char*)packet->host_line.ptr, packet->host_line.len); strncpy(&flow->http.url[packet->host_line.len], (char*)packet->http_url_name.ptr, packet->http_url_name.len); flow->http.url[len-1] = '\0'; - } - if(flow->packet.http_method.len < 3) - flow->http.method = NDPI_HTTP_METHOD_UNKNOWN; - else { - switch(flow->packet.http_method.ptr[0]) { - case 'O': flow->http.method = NDPI_HTTP_METHOD_OPTIONS; break; - case 'G': flow->http.method = NDPI_HTTP_METHOD_GET; break; - case 'H': flow->http.method = NDPI_HTTP_METHOD_HEAD; break; - - case 'P': - switch(flow->packet.http_method.ptr[1]) { - case 'A': flow->http.method = NDPI_HTTP_METHOD_PATCH; break; - case 'O': flow->http.method = NDPI_HTTP_METHOD_POST; break; - case 'U': flow->http.method = NDPI_HTTP_METHOD_PUT; break; - } - break; - - case 'D': flow->http.method = NDPI_HTTP_METHOD_DELETE; break; - case 'T': flow->http.method = NDPI_HTTP_METHOD_TRACE; break; - case 'C': flow->http.method = NDPI_HTTP_METHOD_CONNECT; break; - default: - flow->http.method = NDPI_HTTP_METHOD_UNKNOWN; - break; - } + ndpi_check_http_url(ndpi_struct, flow, &flow->http.url[packet->host_line.len]); } - } - if((flow->http.content_type == NULL) && (packet->content_line.len > 0)) { - int len = packet->content_line.len + 1; - - flow->http.content_type = ndpi_malloc(len); - if(flow->http.content_type) { - strncpy(flow->http.content_type, (char*)packet->content_line.ptr, - packet->content_line.len); - flow->http.content_type[packet->content_line.len] = '\0'; - } + flow->http.method = ndpi_http_str2method((const char*)flow->packet.http_method.ptr, flow->packet.http_method.len); } - } + if(packet->server_line.ptr != NULL && (packet->server_line.len > 7)) { + if(strncmp((const char *)packet->server_line.ptr, "ntopng ", 7) == 0) + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_NTOP, NDPI_PROTOCOL_HTTP); + } + if(packet->user_agent_line.ptr != NULL && packet->user_agent_line.len != 0) { - /** - Format examples: - Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) .... - Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0 - */ - if(packet->user_agent_line.len > 7) { - char ua[256]; - u_int mlen = ndpi_min(packet->user_agent_line.len, sizeof(ua)-1); - - strncpy(ua, (const char *)packet->user_agent_line.ptr, mlen); - ua[mlen] = '\0'; - - if(strncmp(ua, "Mozilla", 7) == 0) { - char *parent = strchr(ua, '('); - - if(parent) { - char *token, *end; - - parent++; - end = strchr(parent, ')'); - if(end) end[0] = '\0'; - - token = strsep(&parent, ";"); - if(token) { - if((strcmp(token, "X11") == 0) - || (strcmp(token, "compatible") == 0) - || (strcmp(token, "Linux") == 0) - || (strcmp(token, "Macintosh") == 0) - ) { - token = strsep(&parent, ";"); - if(token && (token[0] == ' ')) token++; /* Skip space */ - - if(token - && ((strcmp(token, "U") == 0) - || (strncmp(token, "MSIE", 4) == 0))) { - token = strsep(&parent, ";"); - if(token && (token[0] == ' ')) token++; /* Skip space */ - - if(token && (strncmp(token, "Update", 6) == 0)) { - token = strsep(&parent, ";"); - - if(token && (token[0] == ' ')) token++; /* Skip space */ - - if(token && (strncmp(token, "AOL", 3) == 0)) { - - token = strsep(&parent, ";"); - if(token && (token[0] == ' ')) token++; /* Skip space */ - } - } - } - } - - if(token) - setHttpUserAgent(ndpi_struct, flow, token); - } - } - } - else if(memcmp(ua, "netflix-ios-app", 15) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found netflix\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_NETFLIX); - return; - } - } - - NDPI_LOG_DBG2(ndpi_struct, "User Agent Type line found %.*s\n", - packet->user_agent_line.len, packet->user_agent_line.ptr); + ret = http_process_user_agent(ndpi_struct, flow, packet->user_agent_line.ptr, packet->user_agent_line.len); + /* TODO: Is it correct to avoid setting ua, host_name,... if we have a (Netflix) subclassification? */ + if(ret != 0) + return; } /* check for host line */ @@ -286,36 +443,22 @@ static void check_content_type_and_change_protocol(struct ndpi_detection_module_ NDPI_LOG_DBG2(ndpi_struct, "HOST line found %.*s\n", packet->host_line.len, packet->host_line.ptr); - /* call ndpi_match_host_subprotocol to see if there is a match with known-host HTTP subprotocol */ - if((ndpi_struct->http_dont_dissect_response) || flow->http_detected) { - ndpi_protocol_match_result ret_match; - - ndpi_match_host_subprotocol(ndpi_struct, flow, - (char*)packet->host_line.ptr, - packet->host_line.len, - &ret_match, - NDPI_PROTOCOL_HTTP); - } - /* Copy result for nDPI apps */ - if(!ndpi_struct->disable_metadata_export) { - len = ndpi_min(packet->host_line.len, sizeof(flow->host_server_name)-1); - strncpy((char*)flow->host_server_name, (char*)packet->host_line.ptr, len); - flow->host_server_name[len] = '\0'; - } + len = ndpi_min(packet->host_line.len, sizeof(flow->host_server_name)-1); + strncpy((char*)flow->host_server_name, (char*)packet->host_line.ptr, len); + flow->host_server_name[len] = '\0'; + flow->extra_packets_func = NULL; /* We're good now */ + if(len > 0) ndpi_check_dga_name(ndpi_struct, flow, (char*)flow->host_server_name, 1); flow->server_id = flow->dst; if(packet->forwarded_line.ptr) { len = ndpi_min(packet->forwarded_line.len, sizeof(flow->protos.http.nat_ip)-1); - if(!ndpi_struct->disable_metadata_export) { - strncpy((char*)flow->protos.http.nat_ip, (char*)packet->forwarded_line.ptr, len); - flow->protos.http.nat_ip[len] = '\0'; - } + strncpy((char*)flow->protos.http.nat_ip, (char*)packet->forwarded_line.ptr, len); + flow->protos.http.nat_ip[len] = '\0'; } - if(!ndpi_struct->http_dont_dissect_response) - parseHttpSubprotocol(ndpi_struct, flow); + ndpi_http_parse_subprotocol(ndpi_struct, flow); /** check result of host subprotocol detection @@ -339,7 +482,7 @@ static void check_content_type_and_change_protocol(struct ndpi_detection_module_ } if((flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) - && ((ndpi_struct->http_dont_dissect_response) || flow->http_detected) + && (flow->http_detected) && (packet->http_origin.len > 0)) { ndpi_protocol_match_result ret_match; @@ -354,15 +497,15 @@ static void check_content_type_and_change_protocol(struct ndpi_detection_module_ if(packet->detected_protocol_stack[0] != NDPI_PROTOCOL_HTTP) { NDPI_LOG_INFO(ndpi_struct, "found HTTP/%s\n", ndpi_get_proto_name(ndpi_struct, packet->detected_protocol_stack[0])); - ndpi_int_http_add_connection(ndpi_struct, flow, packet->detected_protocol_stack[0]); + ndpi_int_http_add_connection(ndpi_struct, flow, packet->detected_protocol_stack[0], NDPI_PROTOCOL_CATEGORY_WEB); return; /* We have identified a sub-protocol so we're done */ } } } #if 0 - if(!ndpi_struct->http_dont_dissect_response && flow->http_detected) - parseHttpSubprotocol(ndpi_struct, flow); + if(flow->http_detected) + ndpi_http_parse_subprotocol(ndpi_struct, flow); #endif if(flow->guessed_protocol_id == NDPI_PROTOCOL_UNKNOWN) @@ -382,7 +525,19 @@ static void check_content_type_and_change_protocol(struct ndpi_detection_module_ NDPI_LOG_DBG2(ndpi_struct, "Content Type line found %.*s\n", packet->content_line.len, packet->content_line.ptr); - if((ndpi_struct->http_dont_dissect_response) || flow->http_detected) { + if((flow->http.content_type == NULL) && (packet->content_line.len > 0)) { + int len = packet->content_line.len + 1; + + flow->http.content_type = ndpi_malloc(len); + if(flow->http.content_type) { + strncpy(flow->http.content_type, (char*)packet->content_line.ptr, + packet->content_line.len); + flow->http.content_type[packet->content_line.len] = '\0'; + + flow->guessed_category = flow->category = ndpi_http_check_content(ndpi_struct, flow);} + } + + if(flow->http_detected) { ndpi_protocol_match_result ret_match; ndpi_match_content_subprotocol(ndpi_struct, flow, @@ -391,13 +546,25 @@ static void check_content_type_and_change_protocol(struct ndpi_detection_module_ } } - ndpi_int_http_add_connection(ndpi_struct, flow, packet->detected_protocol_stack[0]); + ndpi_int_http_add_connection(ndpi_struct, flow, packet->detected_protocol_stack[0], NDPI_PROTOCOL_CATEGORY_WEB); } +/* ************************************************************* */ + static void check_http_payload(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { /* Add here your paylod code check */ } +/* ************************************************************* */ + +#ifdef NDPI_ENABLE_DEBUG_MESSAGES +static uint8_t non_ctrl(uint8_t c) { + return c < 32 ? '.':c; +} +#endif + +/* ************************************************************* */ + /** * Functions to check whether the packet begins with a valid http request * @param ndpi_struct @@ -429,12 +596,16 @@ static u_int16_t http_request_url_offset(struct ndpi_detection_module_struct *nd int i; NDPI_LOG_DBG2(ndpi_struct, "====>>>> HTTP: %c%c%c%c [len: %u]\n", - non_ctrl(packet->payload[0]), non_ctrl(packet->payload[1]), - non_ctrl(packet->payload[2]), non_ctrl(packet->payload[3]), + packet->payload_packet_len > 0 ? non_ctrl(packet->payload[0]) : '.', + packet->payload_packet_len > 1 ? non_ctrl(packet->payload[1]) : '.', + packet->payload_packet_len > 2 ? non_ctrl(packet->payload[2]) : '.', + packet->payload_packet_len > 3 ? non_ctrl(packet->payload[3]) : '.', packet->payload_packet_len); /* Check first char */ - if(!strchr(http_fs,packet->payload[0])) return 0; + if(!packet->payload_packet_len || !strchr(http_fs,packet->payload[0])) + return 0; + /** FIRST PAYLOAD PACKET FROM CLIENT **/ @@ -453,6 +624,106 @@ static void http_bitmask_exclude_other(struct ndpi_flow_struct *flow) NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_XBOX); } +/* *********************************************************************************************** */ + +/* Trick to speed-up detection */ +static const char* suspicious_http_header_keys_A[] = { "Arch", NULL}; +static const char* suspicious_http_header_keys_C[] = { "Cores", NULL}; +static const char* suspicious_http_header_keys_M[] = { "Mem", NULL}; +static const char* suspicious_http_header_keys_O[] = { "Os", "Osname", "Osversion", NULL}; +static const char* suspicious_http_header_keys_R[] = { "Root", NULL}; +static const char* suspicious_http_header_keys_S[] = { "S", NULL}; +static const char* suspicious_http_header_keys_T[] = { "TLS_version", NULL}; +static const char* suspicious_http_header_keys_U[] = { "Uuid", NULL}; +static const char* suspicious_http_header_keys_X[] = { "X-Hire-Me", NULL}; + +static int is_a_suspicious_header(const char* suspicious_headers[], struct ndpi_int_one_line_struct packet_line){ + int i; + unsigned int header_len; + const u_int8_t* header_limit; + + if((header_limit = memchr(packet_line.ptr, ':', packet_line.len))) { + header_len = header_limit - packet_line.ptr; + for(i=0; suspicious_headers[i] != NULL; i++){ + if(!strncasecmp((const char*) packet_line.ptr, + suspicious_headers[i], header_len)) + return 1; + } + } + + return 0; +} + +/* *********************************************************************************************** */ + +static void ndpi_check_http_header(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + u_int32_t i; + struct ndpi_packet_struct *packet = &flow->packet; + + for(i=0; (i < packet->parsed_lines) + && (packet->line[i].ptr != NULL) + && (packet->line[i].len > 0); i++) { + switch(packet->line[i].ptr[0]){ + case 'A': + if(is_a_suspicious_header(suspicious_http_header_keys_A, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'C': + if(is_a_suspicious_header(suspicious_http_header_keys_C, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'M': + if(is_a_suspicious_header(suspicious_http_header_keys_M, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'O': + if(is_a_suspicious_header(suspicious_http_header_keys_O, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'R': + if(is_a_suspicious_header(suspicious_http_header_keys_R, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'S': + if(is_a_suspicious_header(suspicious_http_header_keys_S, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'T': + if(is_a_suspicious_header(suspicious_http_header_keys_T, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'U': + if(is_a_suspicious_header(suspicious_http_header_keys_U, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + break; + case 'X': + if(is_a_suspicious_header(suspicious_http_header_keys_X, packet->line[i])) { + NDPI_SET_BIT(flow->risk, NDPI_HTTP_SUSPICIOUS_HEADER); + return; + } + + break; + } + } +} + /*************************************************************************************************/ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct, @@ -463,7 +734,8 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct packet->packet_lines_parsed_complete = 0; /* Check if we so far detected the protocol in the request or not. */ - if(flow->l4.tcp.http_stage == 0) { + if((packet->payload_packet_len > 0) /* Needed in case of extra packet processing */ + && (flow->l4.tcp.http_stage == 0)) { /* Expected a request */ flow->http_detected = 0; @@ -490,7 +762,7 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct flow->http.response_status_code = 0; /* Out of range */ } - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP); + ndpi_parse_packet_line_info(ndpi_struct, flow); check_content_type_and_change_protocol(ndpi_struct, flow); return; } @@ -528,7 +800,7 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct */ ookla_found: - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OOKLA, NDPI_PROTOCOL_UNKNOWN); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_OOKLA, NDPI_PROTOCOL_CATEGORY_WEB); if(ndpi_struct->ookla_cache == NULL) ndpi_struct->ookla_cache = ndpi_lru_cache_init(1024); @@ -543,6 +815,13 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct return; } + /* try to get some additional request header info even if the packet may not be HTTP */ + ndpi_parse_packet_line_info(ndpi_struct, flow); + if(packet->http_num_headers > 0) { + check_content_type_and_change_protocol(ndpi_struct, flow); + return; + } + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); http_bitmask_exclude_other(flow); return; @@ -552,6 +831,7 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct "Filename HTTP found: %d, we look for line info..\n", filename_start); ndpi_parse_packet_line_info(ndpi_struct, flow); + ndpi_check_http_header(ndpi_struct, flow); if(packet->parsed_lines <= 1) { NDPI_LOG_DBG2(ndpi_struct, @@ -569,7 +849,8 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct "Found more than one line, we look further for the next packet...\n"); if(packet->line[0].len >= (9 + filename_start) - && memcmp(&packet->line[0].ptr[packet->line[0].len - 9], " HTTP/1.", 8) == 0) { /* Request line complete. Ex. "GET / HTTP/1.1" */ + && memcmp(&packet->line[0].ptr[packet->line[0].len - 9], " HTTP/1.", 8) == 0) { + /* Request line complete. Ex. "GET / HTTP/1.1" */ packet->http_url_name.ptr = &packet->payload[filename_start]; packet->http_url_name.len = packet->line[0].len - (filename_start + 9); @@ -592,81 +873,17 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct goto ookla_found; } - /* Check for additional field introduced by Steam */ - int x = 1; - if(packet->line[x].len >= 11 && (memcmp(packet->line[x].ptr, "x-steam-sid", 11)) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found STEAM\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_STEAM); - check_content_type_and_change_protocol(ndpi_struct, flow); - return; - } - - /* Check for additional field introduced by Facebook */ - x = 1; - while(packet->line[x].len != 0) { - if(packet->line[x].len >= 12 && (memcmp(packet->line[x].ptr, "X-FB-SIM-HNI", 12)) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found FACEBOOK\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_FACEBOOK); - check_content_type_and_change_protocol(ndpi_struct, flow); - return; - } - x++; - } - -#if defined(NDPI_PROTOCOL_1KXUN) || defined(NDPI_PROTOCOL_IQIYI) - /* check PPStream protocol or iQiyi service - (iqiyi is delivered by ppstream) */ - // substring in url - if(ndpi_strnstr((const char*) &packet->payload[filename_start], "iqiyi.com", (packet->payload_packet_len - filename_start)) != NULL) { - if(flow->kxun_counter == 0) { - flow->l4.tcp.ppstream_stage++; - flow->iqiyi_counter++; - check_content_type_and_change_protocol(ndpi_struct, flow); /* ***** CHECK ****** */ - return; - } - } - - // additional field in http payload - x = 1; - while((packet->line[x].len >= 4) && (packet->line[x+1].len >= 5) && (packet->line[x+2].len >= 10)) { - if(packet->line[x].ptr && ((memcmp(packet->line[x].ptr, "qyid", 4)) == 0) - && packet->line[x+1].ptr && ((memcmp(packet->line[x+1].ptr, "qypid", 5)) == 0) - && packet->line[x+2].ptr && ((memcmp(packet->line[x+2].ptr, "qyplatform", 10)) == 0) - ) { - flow->l4.tcp.ppstream_stage++; - flow->iqiyi_counter++; - check_content_type_and_change_protocol(ndpi_struct, flow); - return; - } - x++; - } -#endif - -#if defined(NDPI_PROTOCOL_1KXUN) || defined(NDPI_PROTOCOL_IQIYI) - /* Check for 1kxun packet */ - int a; - for (a = 0; a < packet->parsed_lines; a++) { - if(packet->line[a].len >= 14 && (memcmp(packet->line[a].ptr, "Client-Source:", 14)) == 0) { - if((memcmp(packet->line[a].ptr+15, "1kxun", 5)) == 0) { - flow->kxun_counter++; - check_content_type_and_change_protocol(ndpi_struct, flow); - return; - } - } - } -#endif - if((packet->http_url_name.len > 7) && (!strncmp((const char*) packet->http_url_name.ptr, "http://", 7))) { NDPI_LOG_INFO(ndpi_struct, "found HTTP_PROXY\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP_PROXY); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP_PROXY, NDPI_PROTOCOL_CATEGORY_WEB); check_content_type_and_change_protocol(ndpi_struct, flow); } if(filename_start == 8 && (memcmp(packet->payload, "CONNECT ", 8) == 0)) { /* nathan@getoffmalawn.com */ NDPI_LOG_INFO(ndpi_struct, "found HTTP_CONNECT\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP_CONNECT); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP_CONNECT, NDPI_PROTOCOL_CATEGORY_WEB); check_content_type_and_change_protocol(ndpi_struct, flow); } @@ -681,17 +898,11 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct in 99.99% of the cases is like that. */ - if(ndpi_struct->http_dont_dissect_response) { - if(flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) /* No subprotocol found */ - NDPI_LOG_INFO(ndpi_struct, "found HTTP\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP); - } else { - flow->http_detected = 1; - NDPI_LOG_DBG2(ndpi_struct, - "HTTP START Found, we will look further for the response...\n"); - flow->l4.tcp.http_stage = packet->packet_direction + 1; // packet_direction 0: stage 1, packet_direction 1: stage 2 - } - + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP, NDPI_PROTOCOL_CATEGORY_WEB); + flow->http_detected = 1; + NDPI_LOG_DBG2(ndpi_struct, + "HTTP START Found, we will look further for the response...\n"); + flow->l4.tcp.http_stage = packet->packet_direction + 1; // packet_direction 0: stage 1, packet_direction 1: stage 2 check_content_type_and_change_protocol(ndpi_struct, flow); return; } @@ -715,7 +926,8 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_OOKLA); /** - At first check, if this is for sure a response packet (in another direction. If not, if HTTP is detected do nothing now and return, + At first check, if this is for sure a response packet + (in another direction. If not, if HTTP is detected do nothing now and return, otherwise check the second packet for the HTTP request */ if((flow->l4.tcp.http_stage - packet->packet_direction) == 1) { /* Expected a response package */ @@ -749,7 +961,7 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct && memcmp(&packet->line[0].ptr[packet->line[0].len - 9], " HTTP/1.", 8) == 0) { NDPI_LOG_INFO(ndpi_struct, "found HTTP\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP, NDPI_PROTOCOL_CATEGORY_WEB); check_content_type_and_change_protocol(ndpi_struct, flow); NDPI_LOG_DBG2(ndpi_struct, @@ -771,7 +983,7 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct if((packet->parsed_lines == 1) && (packet->packet_direction == 1 /* server -> client */)) { /* In Apache if you do "GET /\n\n" the response comes without any header */ NDPI_LOG_INFO(ndpi_struct, "found HTTP. (apache)\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP, NDPI_PROTOCOL_CATEGORY_WEB); check_content_type_and_change_protocol(ndpi_struct, flow); return; } @@ -779,7 +991,7 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct /* If we already detected the HTTP request, we can add the connection and then check for the sub-protocol */ if(flow->http_detected) { NDPI_LOG_INFO(ndpi_struct, "found HTTP\n"); - ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP); + ndpi_int_http_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_HTTP, NDPI_PROTOCOL_CATEGORY_WEB); } /* Parse packet line and we look for the subprotocols */ @@ -799,10 +1011,10 @@ static void ndpi_check_http_tcp(struct ndpi_detection_module_struct *ndpi_struct } } -void ndpi_search_http_tcp(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; +/* ********************************* */ +static void ndpi_search_http_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { /* Break after 20 packets. */ if(flow->packet_counter > 20) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); @@ -810,10 +1022,6 @@ void ndpi_search_http_tcp(struct ndpi_detection_module_struct *ndpi_struct, return; } - if(packet->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) { - return; - } - NDPI_LOG_DBG(ndpi_struct, "search HTTP\n"); ndpi_check_http_tcp(ndpi_struct, flow); } @@ -822,9 +1030,10 @@ void ndpi_search_http_tcp(struct ndpi_detection_module_struct *ndpi_struct, ndpi_http_method ndpi_get_http_method(struct ndpi_detection_module_struct *ndpi_mod, struct ndpi_flow_struct *flow) { - if(!flow) + if(!flow) { + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); return(NDPI_HTTP_METHOD_UNKNOWN); - else + } else return(flow->http.method); } @@ -850,8 +1059,7 @@ char* ndpi_get_http_content_type(struct ndpi_detection_module_struct *ndpi_mod, void init_http_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, - NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ + NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("HTTP",ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_HTTP, ndpi_search_http_tcp, diff --git a/src/lib/protocols/http_activesync.c b/src/lib/protocols/http_activesync.c index 0287557..5e0f502 100644 --- a/src/lib/protocols/http_activesync.c +++ b/src/lib/protocols/http_activesync.c @@ -2,7 +2,7 @@ * http_activesync.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/iax.c b/src/lib/protocols/iax.c index 5d07888..8f01a66 100644 --- a/src/lib/protocols/iax.c +++ b/src/lib/protocols/iax.c @@ -2,7 +2,7 @@ * iax.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -65,17 +65,18 @@ static void ndpi_search_setup_iax(struct ndpi_detection_module_struct *ndpi_stru ndpi_int_iax_add_connection(ndpi_struct, flow); return; } + packet_len = 12; - for (i = 0; i < NDPI_IAX_MAX_INFORMATION_ELEMENTS; i++) { + for(i = 0; i < NDPI_IAX_MAX_INFORMATION_ELEMENTS; i++) { + if ((packet_len+1) >= packet->payload_packet_len) + break; + packet_len = packet_len + 2 + packet->payload[packet_len + 1]; - if (packet_len == packet->payload_packet_len) { + if(packet_len == packet->payload_packet_len) { NDPI_LOG_INFO(ndpi_struct, "found IAX\n"); ndpi_int_iax_add_connection(ndpi_struct, flow); return; } - if (packet_len > packet->payload_packet_len) { - break; - } } } diff --git a/src/lib/protocols/icecast.c b/src/lib/protocols/icecast.c index 2499962..4cd3cc0 100644 --- a/src/lib/protocols/icecast.c +++ b/src/lib/protocols/icecast.c @@ -2,7 +2,7 @@ * icecast.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -36,7 +36,7 @@ static void ndpi_int_icecast_add_connection(struct ndpi_detection_module_struct void ndpi_search_icecast_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - u_int8_t i; + u_int16_t i; NDPI_LOG_DBG(ndpi_struct, "search icecast\n"); diff --git a/src/lib/protocols/iec60870-5-104.c b/src/lib/protocols/iec60870-5-104.c index b7439f3..e5e5325 100644 --- a/src/lib/protocols/iec60870-5-104.c +++ b/src/lib/protocols/iec60870-5-104.c @@ -2,45 +2,62 @@ * iec60870-5-104.c * Extension for industrial 104 protocol recognition * - * Created by Cesar HM + * Created by Cesar HM + * + * Copyright (C) 2019 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * */ #include "ndpi_protocol_ids.h" +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_IEC60870 #include "ndpi_api.h" -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_104 - -void ndpi_search_104_tcp(struct ndpi_detection_module_struct *ndpi_struct, +void ndpi_search_iec60870_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - NDPI_LOG_DBG(ndpi_struct, "search 104\n"); - u_int16_t iec104_port = htons(2404); // port used by 104 + u_int16_t iec104_port = htons(2404); // port used by IEC60870 /* Check connection over TCP */ - + NDPI_LOG_DBG(ndpi_struct, "search IEC60870\n"); + if(packet->tcp) { /* The start byte of 104 is 0x68 * The usual port: 2404 - */ - if ( packet->payload[0] == 0x68 && - ((packet->tcp->dest == iec104_port) || (packet->tcp->source == iec104_port)) ){ - NDPI_LOG_INFO(ndpi_struct, "found 104\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_104, NDPI_PROTOCOL_UNKNOWN); - return; - } + */ + if((packet->payload[0] == 0x68) && + ((packet->tcp->dest == iec104_port) || (packet->tcp->source == iec104_port)) ){ + NDPI_LOG_INFO(ndpi_struct, "found 104\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_IEC60870, NDPI_PROTOCOL_UNKNOWN); + return; } - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - + } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_104_dissector(struct ndpi_detection_module_struct *ndpi_struct, - u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { - - ndpi_set_bitmask_protocol_detection("104", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_104, - ndpi_search_104_tcp, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { + ndpi_set_bitmask_protocol_detection("IEC60870", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_IEC60870, + ndpi_search_iec60870_tcp, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); diff --git a/src/lib/protocols/imo.c b/src/lib/protocols/imo.c index dfc42a3..90f2c60 100644 --- a/src/lib/protocols/imo.c +++ b/src/lib/protocols/imo.c @@ -59,7 +59,7 @@ void ndpi_search_imo(struct ndpi_detection_module_struct *ndpi_struct, struct nd NDPI_LOG_INFO(ndpi_struct, "found IMO\n"); ndpi_int_imo_add_connection(ndpi_struct, flow); } else { - if(flow->num_processed_pkts > 7) + if(flow->num_processed_pkts > 5) NDPI_EXCLUDE_PROTO(ndpi_struct, flow); else flow->protos.imo.last_one_byte_pkt = 0; diff --git a/src/lib/protocols/ipp.c b/src/lib/protocols/ipp.c index 0200d01..07a99f0 100644 --- a/src/lib/protocols/ipp.c +++ b/src/lib/protocols/ipp.c @@ -2,7 +2,7 @@ * ipp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/irc.c b/src/lib/protocols/irc.c index ec22ee3..e1cc59b 100644 --- a/src/lib/protocols/irc.c +++ b/src/lib/protocols/irc.c @@ -2,7 +2,7 @@ * irc.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -399,25 +399,25 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc } if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_IRC) { if (src != NULL && ((u_int32_t) - (packet->tick_timestamp - src->irc_ts) < ndpi_struct->irc_timeout)) { + (packet->current_time_ms - src->irc_ts) < ndpi_struct->irc_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "irc : save src connection packet detected\n"); - src->irc_ts = packet->tick_timestamp; + src->irc_ts = packet->current_time_ms; } else if (dst != NULL && ((u_int32_t) - (packet->tick_timestamp - dst->irc_ts) < ndpi_struct->irc_timeout)) { + (packet->current_time_ms - dst->irc_ts) < ndpi_struct->irc_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "irc : save dst connection packet detected\n"); - dst->irc_ts = packet->tick_timestamp; + dst->irc_ts = packet->current_time_ms; } } if (((dst != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_IRC) && ((u_int32_t) - (packet->tick_timestamp - dst->irc_ts)) < + (packet->current_time_ms - dst->irc_ts)) < ndpi_struct->irc_timeout)) || (src != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK (src->detected_protocol_bitmask, NDPI_PROTOCOL_IRC) && ((u_int32_t) - (packet->tick_timestamp - src->irc_ts)) < ndpi_struct->irc_timeout)) { + (packet->current_time_ms - src->irc_ts)) < ndpi_struct->irc_timeout)) { if (packet->tcp != NULL) { sport = packet->tcp->source; dport = packet->tcp->dest; @@ -425,7 +425,7 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc if (dst != NULL) { for (counter = 0; counter < dst->irc_number_of_port; counter++) { if (dst->irc_port[counter] == sport || dst->irc_port[counter] == dport) { - dst->last_time_port_used[counter] = packet->tick_timestamp; + dst->last_time_port_used[counter] = packet->current_time_ms; NDPI_LOG_INFO(ndpi_struct, "found IRC: dest port matched with the DCC port"); ndpi_int_irc_add_connection(ndpi_struct, flow); return; @@ -435,7 +435,7 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc if (src != NULL) { for (counter = 0; counter < src->irc_number_of_port; counter++) { if (src->irc_port[counter] == sport || src->irc_port[counter] == dport) { - src->last_time_port_used[counter] = packet->tick_timestamp; + src->last_time_port_used[counter] = packet->current_time_ms; NDPI_LOG_INFO(ndpi_struct, "found IRC: Source port matched with the DCC port"); ndpi_int_irc_add_connection(ndpi_struct, flow); return; @@ -492,9 +492,10 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc ndpi_parse_packet_line_info(ndpi_struct, flow); } else { flow->l4.tcp.irc_3a_counter++; + packet->parsed_lines = 0; } for (i = 0; i < packet->parsed_lines; i++) { - if (packet->line[i].ptr[0] == ':') { + if ((packet->line[i].len > 0) && packet->line[i].ptr[0] == ':') { flow->l4.tcp.irc_3a_counter++; if (flow->l4.tcp.irc_3a_counter == 7) { /* ':' == 0x3a */ NDPI_LOG_INFO(ndpi_struct, "found irc. 0x3a. seven times."); @@ -676,7 +677,7 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc if (memcmp(&packet->line[i].ptr[j], "SEND ", 5) == 0 || (memcmp(&packet->line[i].ptr[j], "CHAT", 4) == 0) || (memcmp(&packet->line[i].ptr[j], "chat", 4) == 0) - || (memcmp(&packet->line[i].ptr[j], "sslchat", 7) == 0) + || (j+7 < packet->line[i].len && memcmp(&packet->line[i].ptr[j], "sslchat", 7) == 0) || (memcmp(&packet->line[i].ptr[j], "TSEND", 5) == 0)) { NDPI_LOG_DBG2(ndpi_struct, "found CHAT,chat,sslchat,TSEND."); j += 4; @@ -715,7 +716,7 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc NDPI_LOG_DBG2(ndpi_struct, "found port=%d jjeeeeeeeeeeeeeeeeeeeeeeeee", ntohs(get_u_int16_t(src->irc_port, 0))); } - src->irc_ts = packet->tick_timestamp; + src->irc_ts = packet->current_time_ms; } else if (port != 0 && src->irc_number_of_port == NDPI_PROTOCOL_IRC_MAXPORT) { if (!ndpi_is_duplicate(src, port)) { less = 0; @@ -723,7 +724,7 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc src->irc_port[less] = port; NDPI_LOG_DBG2(ndpi_struct, "found port=%d", ntohs(get_u_int16_t(src->irc_port, 0))); } - src->irc_ts = packet->tick_timestamp; + src->irc_ts = packet->current_time_ms; } if (dst == NULL) { break; @@ -746,7 +747,7 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc NDPI_LOG_DBG2(ndpi_struct, "found port=%d", ntohs(get_u_int16_t(dst->irc_port, 0))); NDPI_LOG_DBG2(ndpi_struct, "juuuuuuuuuuuuuuuu"); } - dst->irc_ts = packet->tick_timestamp; + dst->irc_ts = packet->current_time_ms; } else if (port != 0 && dst->irc_number_of_port == NDPI_PROTOCOL_IRC_MAXPORT) { if (!ndpi_is_duplicate(dst, port)) { less = 0; @@ -755,7 +756,7 @@ void ndpi_search_irc_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc NDPI_LOG_DBG2(ndpi_struct, "found port=%d", ntohs(get_u_int16_t(dst->irc_port, 0))); } - dst->irc_ts = packet->tick_timestamp; + dst->irc_ts = packet->current_time_ms; } break; diff --git a/src/lib/protocols/jabber.c b/src/lib/protocols/jabber.c index fe65933..ecf29c5 100644 --- a/src/lib/protocols/jabber.c +++ b/src/lib/protocols/jabber.c @@ -2,7 +2,7 @@ * jabber.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -81,9 +81,9 @@ void ndpi_search_jabber_tcp(struct ndpi_detection_module_struct *ndpi_struct, st ntohs(src->jabber_file_transfer_port[0]), ntohs(src->jabber_file_transfer_port[1])); if (((u_int32_t) - (packet->tick_timestamp - src->jabber_stun_or_ft_ts)) >= ndpi_struct->jabber_file_transfer_timeout) { + (packet->current_time_ms - src->jabber_stun_or_ft_ts)) >= ndpi_struct->jabber_file_transfer_timeout) { NDPI_LOG_DBG2(ndpi_struct, "JABBER src stun timeout %u %u\n", - src->jabber_stun_or_ft_ts, packet->tick_timestamp); + src->jabber_stun_or_ft_ts, packet->current_time_ms); src->jabber_file_transfer_port[0] = 0; src->jabber_file_transfer_port[1] = 0; } else if (src->jabber_file_transfer_port[0] == packet->tcp->dest @@ -101,9 +101,9 @@ void ndpi_search_jabber_tcp(struct ndpi_detection_module_struct *ndpi_struct, st ntohs(dst->jabber_file_transfer_port[0]), ntohs(dst->jabber_file_transfer_port[1])); if (((u_int32_t) - (packet->tick_timestamp - dst->jabber_stun_or_ft_ts)) >= ndpi_struct->jabber_file_transfer_timeout) { + (packet->current_time_ms - dst->jabber_stun_or_ft_ts)) >= ndpi_struct->jabber_file_transfer_timeout) { NDPI_LOG_DBG2(ndpi_struct, "JABBER dst stun timeout %u %u\n", - dst->jabber_stun_or_ft_ts, packet->tick_timestamp); + dst->jabber_stun_or_ft_ts, packet->current_time_ms); dst->jabber_file_transfer_port[0] = 0; dst->jabber_file_transfer_port[1] = 0; } else if (dst->jabber_file_transfer_port[0] == packet->tcp->dest @@ -135,7 +135,7 @@ void ndpi_search_jabber_tcp(struct ndpi_detection_module_struct *ndpi_struct, st return; } /* need message to or type for file-transfer */ - if (memcmp(packet->payload, "payload, "payload, "payload, "payload_packet_len - 11; for (x = 10; x < lastlen; x++) { @@ -143,11 +143,11 @@ void ndpi_search_jabber_tcp(struct ndpi_detection_module_struct *ndpi_struct, st if (memcmp(&packet->payload[x], "port=", 5) == 0) { NDPI_LOG_DBG2(ndpi_struct, "port=\n"); if (src != NULL) { - src->jabber_stun_or_ft_ts = packet->tick_timestamp; + src->jabber_stun_or_ft_ts = packet->current_time_ms; } if (dst != NULL) { - dst->jabber_stun_or_ft_ts = packet->tick_timestamp; + dst->jabber_stun_or_ft_ts = packet->current_time_ms; } x += 6; j_port = ntohs_ndpi_bytestream_to_number(&packet->payload[x], packet->payload_packet_len, &x); @@ -204,11 +204,11 @@ void ndpi_search_jabber_tcp(struct ndpi_detection_module_struct *ndpi_struct, st if (memcmp(&packet->payload[x], "port=", 5) == 0) { NDPI_LOG_DBG2(ndpi_struct, "port=\n"); if (src != NULL) { - src->jabber_stun_or_ft_ts = packet->tick_timestamp; + src->jabber_stun_or_ft_ts = packet->current_time_ms; } if (dst != NULL) { - dst->jabber_stun_or_ft_ts = packet->tick_timestamp; + dst->jabber_stun_or_ft_ts = packet->current_time_ms; } x += 6; diff --git a/src/lib/protocols/kakaotalk_voice.c b/src/lib/protocols/kakaotalk_voice.c index 48d5816..d764533 100644 --- a/src/lib/protocols/kakaotalk_voice.c +++ b/src/lib/protocols/kakaotalk_voice.c @@ -1,7 +1,7 @@ /* * kakaotalk_voice.c * - * Copyright (C) 2015-19 - ntop.org + * Copyright (C) 2015-20 - ntop.org * * This module is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/kerberos.c b/src/lib/protocols/kerberos.c index a1c2713..fa0ab6c 100644 --- a/src/lib/protocols/kerberos.c +++ b/src/lib/protocols/kerberos.c @@ -1,8 +1,8 @@ /* * kerberos.c * + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . - * + * */ #include "ndpi_protocol_ids.h" @@ -28,53 +28,411 @@ #include "ndpi_api.h" +/* #define KERBEROS_DEBUG 1 */ + +#define KERBEROS_PORT 88 static void ndpi_int_kerberos_add_connection(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) -{ + struct ndpi_flow_struct *flow) { ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_KERBEROS, NDPI_PROTOCOL_UNKNOWN); NDPI_LOG_DBG(ndpi_struct, "trace KERBEROS\n"); } +/* ************************************************* */ + +void ndpi_search_kerberos(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + u_int16_t sport = packet->tcp ? ntohs(packet->tcp->source) : ntohs(packet->udp->source); + u_int16_t dport = packet->tcp ? ntohs(packet->tcp->dest) : ntohs(packet->udp->dest); + const u_int8_t *original_packet_payload = NULL; + u_int16_t original_payload_packet_len = 0; + + if((sport != KERBEROS_PORT) && (dport != KERBEROS_PORT)) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + + NDPI_LOG_DBG(ndpi_struct, "search KERBEROS\n"); + +#ifdef KERBEROS_DEBUG + printf("\n[Kerberos] Process packet [len: %u]\n", packet->payload_packet_len); +#endif + + if(flow->kerberos_buf.pktbuf != NULL) { + u_int missing = flow->kerberos_buf.pktbuf_maxlen - flow->kerberos_buf.pktbuf_currlen; + + if(packet->payload_packet_len <= missing) { + memcpy(&flow->kerberos_buf.pktbuf[flow->kerberos_buf.pktbuf_currlen], packet->payload, packet->payload_packet_len); + flow->kerberos_buf.pktbuf_currlen += packet->payload_packet_len; + + if(flow->kerberos_buf.pktbuf_currlen == flow->kerberos_buf.pktbuf_maxlen) { + original_packet_payload = packet->payload; + original_payload_packet_len = packet->payload_packet_len; + packet->payload = (u_int8_t *)flow->kerberos_buf.pktbuf; + packet->payload_packet_len = flow->kerberos_buf.pktbuf_currlen; +#ifdef KERBEROS_DEBUG + printf("[Kerberos] Packet is now full: processing\n"); +#endif + } else { +#ifdef KERBEROS_DEBUG + printf("[Kerberos] Missing %u bytes: skipping\n", + flow->kerberos_buf.pktbuf_maxlen - flow->kerberos_buf.pktbuf_currlen); +#endif + + return; + } + } + } + + /* I have observed 0a,0c,0d,0e at packet->payload[19/21], maybe there are other possibilities */ + if(packet->payload_packet_len >= 4) { + u_int32_t kerberos_len, expected_len; + u_int16_t base_offset = 0; -void ndpi_search_kerberos(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; + if(packet->tcp) { + kerberos_len = ntohl(get_u_int32_t(packet->payload, 0)), + expected_len = packet->payload_packet_len - 4; + base_offset = 4; + } else + base_offset = 0, kerberos_len = expected_len = packet->payload_packet_len; - NDPI_LOG_DBG(ndpi_struct, "search KERBEROS\n"); +#ifdef KERBEROS_DEBUG + printf("[Kerberos] [Kerberos len: %u][expected_len: %u]\n", kerberos_len, expected_len); +#endif - /* I have observed 0a,0c,0d,0e at packet->payload[19/21], maybe there are other possibilities */ - if (packet->payload_packet_len >= 4 && ntohl(get_u_int32_t(packet->payload, 0)) == packet->payload_packet_len - 4) { - if (packet->payload_packet_len > 19 && - packet->payload[14] == 0x05 && - (packet->payload[19] == 0x0a || - packet->payload[19] == 0x0c || packet->payload[19] == 0x0d || packet->payload[19] == 0x0e)) { - ndpi_int_kerberos_add_connection(ndpi_struct, flow); - return; + if(kerberos_len < 12000) { + /* + Kerberos packets might be too long for a TCP packet + so it could be split across two packets. Instead of + rebuilding the stream we use a heuristic approach + */ + if(kerberos_len > expected_len) { + if(packet->tcp) { + if(flow->kerberos_buf.pktbuf == NULL) { + flow->kerberos_buf.pktbuf = (char*)ndpi_malloc(kerberos_len+4); + if(flow->kerberos_buf.pktbuf != NULL) { + flow->kerberos_buf.pktbuf_maxlen = kerberos_len+4; +#ifdef KERBEROS_DEBUG + printf("[Kerberos] Allocated %u bytes\n", flow->kerberos_buf.pktbuf_maxlen); +#endif + } + } + + if(flow->kerberos_buf.pktbuf != NULL) { + if(packet->payload_packet_len <= flow->kerberos_buf.pktbuf_maxlen) { + memcpy(flow->kerberos_buf.pktbuf, packet->payload, packet->payload_packet_len); + flow->kerberos_buf.pktbuf_currlen = packet->payload_packet_len; + } + } + } + + return; + } else if(kerberos_len == expected_len) { + if(packet->payload_packet_len > 128) { + u_int16_t koffset, i; + + for(i=8; i<16; i++) + if((packet->payload[base_offset+i] == 0x03) + && (packet->payload[base_offset+i+1] == 0x02) + && (packet->payload[base_offset+i+2] == 0x01) + && (packet->payload[base_offset+i+3] != 0x05) + ) + break; + + koffset = base_offset + i + 3; + +#ifdef KERBEROS_DEBUG + printf("[Kerberos] [msg-type: 0x%02X/%u][koffset: %u]\n", + packet->payload[koffset], packet->payload[koffset], koffset); +#endif + + if(((packet->payload[koffset] == 0x0A) + || (packet->payload[koffset] == 0x0C) + || (packet->payload[koffset] == 0x0D) + || (packet->payload[koffset] == 0x0E))) { + u_int16_t koffsetp, body_offset = 0, pad_len; + u_int8_t msg_type = packet->payload[koffset]; + +#ifdef KERBEROS_DEBUG + printf("[Kerberos] Packet found 0x%02X/%u\n", msg_type, msg_type); +#endif + if(msg_type != 0x0d) /* TGS-REP */ { + /* Process only on requests */ + if(packet->payload[koffset+1] == 0xA3) { + if(packet->payload[koffset+3] == 0x30) + pad_len = packet->payload[koffset+4]; + else { + /* Long pad */ + pad_len = packet->payload[koffset+2]; + for(i=3; i<10; i++) if(packet->payload[koffset+i] == pad_len) break; + + pad_len = (packet->payload[koffset+i+1] << 8) + packet->payload[koffset+i+2]; + koffset += i-2; } - if (packet->payload_packet_len > 21 && - packet->payload[16] == 0x05 && - (packet->payload[21] == 0x0a || - packet->payload[21] == 0x0c || packet->payload[21] == 0x0d || packet->payload[21] == 0x0e)) { - ndpi_int_kerberos_add_connection(ndpi_struct, flow); - return; + } else + pad_len = 0; + +#ifdef KERBEROS_DEBUG + printf("pad_len=0x%02X/%u\n", pad_len, pad_len); +#endif + if(pad_len > 0) { + koffsetp = koffset + 2; + for(i=0; i<4; i++) if(packet->payload[koffsetp] != 0x30) koffsetp++; /* ASN.1 */ +#ifdef KERBEROS_DEBUG + printf("koffsetp=%u [%02X %02X] [byte 0 must be 0x30]\n", koffsetp, packet->payload[koffsetp], packet->payload[koffsetp+1]); +#endif + } else + koffsetp = koffset; + + body_offset = koffsetp + 1 + pad_len; + + for(i=0; i<10; i++) if(body_offsetpayload_packet_len && packet->payload[body_offset] != 0x05) body_offset++; /* ASN.1 */ +#ifdef KERBEROS_DEBUG + printf("body_offset=%u [%02X %02X] [byte 0 must be 0x05]\n", body_offset, packet->payload[body_offset], packet->payload[body_offset+1]); +#endif + } + + if(msg_type == 0x0A) /* AS-REQ */ { +#ifdef KERBEROS_DEBUG + printf("[Kerberos] Processing AS-REQ\n"); +#endif + + + if(body_offset < packet->payload_packet_len) { + u_int16_t name_offset = body_offset + 13; + + for(i=0; (i<20) && (name_offset < packet->payload_packet_len); i++) { + if(packet->payload[name_offset] != 0x1b) + name_offset++; /* ASN.1 */ } + +#ifdef KERBEROS_DEBUG + printf("name_offset=%u [%02X %02X] [byte 0 must be 0x1b]\n", name_offset, packet->payload[name_offset], packet->payload[name_offset+1]); +#endif + + if(name_offset < packet->payload_packet_len) { + u_int cname_len; + + name_offset += 1; + if(packet->payload[name_offset+1] < ' ') /* Isn't printable ? */ + name_offset++; + + if(packet->payload[name_offset+1] == 0x1b) + name_offset += 2; + + cname_len = packet->payload[name_offset]; + + if((cname_len+name_offset) < packet->payload_packet_len) { + u_int realm_len, realm_offset; + char cname_str[48]; + u_int8_t num_cname = 0; + + while(++num_cname <= 2) { + if(cname_len > sizeof(cname_str)-1) + cname_len = sizeof(cname_str)-1; + + strncpy(cname_str, (char*)&packet->payload[name_offset+1], cname_len); + cname_str[cname_len] = '\0'; + for(i=0; ipayload[name_offset+1+cname_len] == 0x1b)) { + name_offset += cname_len + 2; + cname_len = packet->payload[name_offset]; + } else + break; + } + + realm_offset = cname_len + name_offset + 3; + + /* if cname does not end with a $ then it's a username */ + if(cname_len + && (cname_len < sizeof(cname_str)) + && (cname_str[cname_len-1] == '$')) { + cname_str[cname_len-1] = '\0'; + snprintf(flow->protos.kerberos.hostname, sizeof(flow->protos.kerberos.hostname), "%s", cname_str); + } else + snprintf(flow->protos.kerberos.username, sizeof(flow->protos.kerberos.username), "%s", cname_str); + + for(i=0; (i < 14) && (realm_offset < packet->payload_packet_len); i++) { + if(packet->payload[realm_offset] != 0x1b) + realm_offset++; /* ASN.1 */ + } + +#ifdef KERBEROS_DEBUG + printf("realm_offset=%u [%02X %02X] [byte 0 must be 0x1b]\n", realm_offset, + packet->payload[realm_offset], packet->payload[realm_offset+1]); +#endif + + realm_offset += 1; + //if(num_cname == 2) realm_offset++; + if(realm_offset < packet->payload_packet_len) { + realm_len = packet->payload[realm_offset]; + + if((realm_offset+realm_len) < packet->payload_packet_len) { + char realm_str[48]; + + if(realm_len > sizeof(realm_str)-1) + realm_len = sizeof(realm_str)-1; + + realm_offset += 1; + + strncpy(realm_str, (char*)&packet->payload[realm_offset], realm_len); + realm_str[realm_len] = '\0'; + for(i=0; iprotos.kerberos.domain, sizeof(flow->protos.kerberos.domain), "%s", realm_str); + } + } + } + } + } + } else if(msg_type == 0x0c) /* TGS-REQ */ { +#ifdef KERBEROS_DEBUG + printf("[Kerberos] Processing TGS-REQ\n"); +#endif + + if(body_offset < packet->payload_packet_len) { + u_int name_offset, padding_offset = body_offset + 4; + + name_offset = padding_offset; + for(i=0; i<14; i++) if(packet->payload[name_offset] != 0x1b) name_offset++; /* ASN.1 */ + +#ifdef KERBEROS_DEBUG + printf("name_offset=%u [%02X %02X] [byte 0 must be 0x1b]\n", name_offset, packet->payload[name_offset], packet->payload[name_offset+1]); +#endif + + if(name_offset < (packet->payload_packet_len - 1)) { + u_int realm_len; + + name_offset++; + realm_len = packet->payload[name_offset]; + + if((realm_len+name_offset) < packet->payload_packet_len) { + char realm_str[48]; + + if(realm_len > sizeof(realm_str)-1) + realm_len = sizeof(realm_str)-1; + + name_offset += 1; + + strncpy(realm_str, (char*)&packet->payload[name_offset], realm_len); + realm_str[realm_len] = '\0'; + for(i=0; iprotos.kerberos.domain, sizeof(flow->protos.kerberos.domain), "%s", realm_str); + + /* If necessary we can decode sname */ + if(flow->kerberos_buf.pktbuf) { + ndpi_free(flow->kerberos_buf.pktbuf); + packet->payload = original_packet_payload; + packet->payload_packet_len = original_payload_packet_len; + } + flow->kerberos_buf.pktbuf = NULL; + } + } + } + + if(packet->udp) + ndpi_int_kerberos_add_connection(ndpi_struct, flow); + + /* We set the protocol in the response */ + if(flow->kerberos_buf.pktbuf != NULL) { + ndpi_free(flow->kerberos_buf.pktbuf); + packet->payload = original_packet_payload; + packet->payload_packet_len = original_payload_packet_len; + flow->kerberos_buf.pktbuf = NULL; + } + + return; + } else if(msg_type == 0x0d) /* TGS-REP */ { + u_int16_t pad_data_len, cname_offset; + +#ifdef KERBEROS_DEBUG + printf("[Kerberos] Processing TGS-REP\n"); +#endif + + koffsetp = koffset + 4; + pad_data_len = packet->payload[koffsetp]; + /* Skip realm already filled in request */ + cname_offset = pad_data_len + koffsetp + 15; + + if(cname_offset < packet->payload_packet_len) { + u_int8_t cname_len = packet->payload[cname_offset]; + + if((cname_offset+cname_offset) < packet->payload_packet_len) { + char cname_str[48]; + + if(cname_len > sizeof(cname_str)-1) + cname_len = sizeof(cname_str)-1; + + strncpy(cname_str, (char*)&packet->payload[cname_offset+1], cname_len); + cname_str[cname_len] = '\0'; + for(i=0; iprotos.kerberos.hostname, sizeof(flow->protos.kerberos.hostname), "%s", cname_str); + } else + snprintf(flow->protos.kerberos.username, sizeof(flow->protos.kerberos.username), "%s", cname_str); + + ndpi_int_kerberos_add_connection(ndpi_struct, flow); + } + } + } + + return; + } + + if(packet->payload_packet_len > 21 && + packet->payload[16] == 0x05 && + (packet->payload[21] == 0x0a || + packet->payload[21] == 0x0c || packet->payload[21] == 0x0d || packet->payload[21] == 0x0e)) { + ndpi_int_kerberos_add_connection(ndpi_struct, flow); + return; + } } - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + } + } else { +#ifdef KERBEROS_DEBUG + printf("[Kerberos][s/dport: %u/%u] Skipping packet: too long [kerberos_len: %u]\n", + sport, dport, kerberos_len); +#endif + + if(flow->protos.kerberos.domain[0] != '\0') + return; + } + } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } -void init_kerberos_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ +void init_kerberos_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("Kerberos", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_KERBEROS, ndpi_search_kerberos, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, + NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); *id += 1; } - diff --git a/src/lib/protocols/kontiki.c b/src/lib/protocols/kontiki.c index 002ab1c..5d0a9b6 100644 --- a/src/lib/protocols/kontiki.c +++ b/src/lib/protocols/kontiki.c @@ -2,7 +2,7 @@ * kontiki.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ldap.c b/src/lib/protocols/ldap.c index 3e0a4cd..a63de94 100644 --- a/src/lib/protocols/ldap.c +++ b/src/lib/protocols/ldap.c @@ -2,7 +2,7 @@ * ldap.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/line.c b/src/lib/protocols/line.c deleted file mode 100644 index b6e676c..0000000 --- a/src/lib/protocols/line.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * line.c - * - * Copyright (C) 2019 - ntop.org - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_LINE - -#include "ndpi_api.h" - - -static void ndpi_line_report_protocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - /* printf("-> payload_len=%u\n", flow->packet.payload_packet_len); */ - - NDPI_LOG_INFO(ndpi_struct, "found line\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_LINE, NDPI_PROTOCOL_LINE); -} - -void ndpi_search_line(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - - NDPI_LOG_DBG(ndpi_struct, "search line\n"); - - if (packet->iph) { - /* 125.209.252.xxx */ - if (((ntohl(packet->iph->saddr) & 0xFFFFFF00 /* 255.255.255.0 */) == 0x7DD1FC00) || - ((ntohl(packet->iph->daddr) & 0xFFFFFF00 /* 255.255.255.0 */) == 0x7DD1FC00)) { - if ((packet->payload_packet_len == 110) && (flow->packet.payload[0] == 0xB6) && - (flow->packet.payload[1] == 0x18) && (flow->packet.payload[2] == 0x00) && - (flow->packet.payload[3] == 0x6A)) { - ndpi_line_report_protocol(ndpi_struct, flow); - return; - } - } - } - - if ((packet->payload_packet_len == 46 && ntohl(get_u_int32_t(packet->payload, 0)) == 0xb6130006) || - (packet->payload_packet_len == 8 && ntohl(get_u_int32_t(packet->payload, 0)) == 0xb6070004) || - (packet->payload_packet_len == 16 && ntohl(get_u_int32_t(packet->payload, 0)) == 0xb609000c)) { - ndpi_line_report_protocol(ndpi_struct, flow); - return; - } - - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); -} - - -void init_line_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("Line", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_LINE, - ndpi_search_line, NDPI_SELECTION_BITMASK_PROTOCOL_UDP_WITH_PAYLOAD, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); - - *id += 1; -} diff --git a/src/lib/protocols/lisp.c b/src/lib/protocols/lisp.c index d33665f..b74a66b 100644 --- a/src/lib/protocols/lisp.c +++ b/src/lib/protocols/lisp.c @@ -1,7 +1,7 @@ /* * list.c * - * Copyright (C) 2017-19 - ntop.org + * Copyright (C) 2017-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/lotus_notes.c b/src/lib/protocols/lotus_notes.c index 100262c..344c24f 100644 --- a/src/lib/protocols/lotus_notes.c +++ b/src/lib/protocols/lotus_notes.c @@ -1,7 +1,7 @@ /* * lotus_notes.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/mail_imap.c b/src/lib/protocols/mail_imap.c index 4d87275..c3a18c0 100644 --- a/src/lib/protocols/mail_imap.c +++ b/src/lib/protocols/mail_imap.c @@ -1,7 +1,7 @@ /* * mail_imap.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -28,6 +28,7 @@ #include "ndpi_api.h" +/* #define IMAP_DEBUG 1*/ static void ndpi_int_mail_imap_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { @@ -44,17 +45,21 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, /* const u_int8_t *command = 0; */ NDPI_LOG_DBG(ndpi_struct, "search IMAP_IMAP\n"); - - if (flow->l4.tcp.mail_imap_starttls == 2) { + +#ifdef IMAP_DEBUG + printf("%s() [%s]\n", __FUNCTION__, packet->payload); +#endif + + if(flow->l4.tcp.mail_imap_starttls == 2) { NDPI_LOG_DBG2(ndpi_struct, "starttls detected\n"); NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_MAIL_IMAP); NDPI_DEL_PROTOCOL_FROM_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_TLS); return; } - if (packet->payload_packet_len >= 4 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) { + if(packet->payload_packet_len >= 4 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) { // the DONE command appears without a tag - if (packet->payload_packet_len == 6 && ((packet->payload[0] == 'D' || packet->payload[0] == 'd') + if(packet->payload_packet_len == 6 && ((packet->payload[0] == 'D' || packet->payload[0] == 'd') && (packet->payload[1] == 'O' || packet->payload[1] == 'o') && (packet->payload[2] == 'N' || packet->payload[2] == 'n') && (packet->payload[3] == 'E' || packet->payload[3] == 'e'))) { @@ -62,37 +67,37 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, saw_command = 1; } else { - if (flow->l4.tcp.mail_imap_stage < 4) { + if(flow->l4.tcp.mail_imap_stage < 4) { // search for the first space character (end of the tag) while (i < 20 && i < packet->payload_packet_len) { - if (i > 0 && packet->payload[i] == ' ') { + if(i > 0 && packet->payload[i] == ' ') { space_pos = i; break; } - if (!((packet->payload[i] >= 'a' && packet->payload[i] <= 'z') || + if(!((packet->payload[i] >= 'a' && packet->payload[i] <= 'z') || (packet->payload[i] >= 'A' && packet->payload[i] <= 'Z') || (packet->payload[i] >= '0' && packet->payload[i] <= '9') || packet->payload[i] == '*' || packet->payload[i] == '.')) { goto imap_excluded; } i++; } - if (space_pos == 0 || space_pos == (packet->payload_packet_len - 1)) { + if(space_pos == 0 || space_pos == (packet->payload_packet_len - 1)) { goto imap_excluded; } // now walk over a possible mail number to the next space i++; - if (i < packet->payload_packet_len && (packet->payload[i] >= '0' && packet->payload[i] <= '9')) { + if(i < packet->payload_packet_len && (packet->payload[i] >= '0' && packet->payload[i] <= '9')) { while (i < 20 && i < packet->payload_packet_len) { - if (i > 0 && packet->payload[i] == ' ') { + if(i > 0 && packet->payload[i] == ' ') { space_pos = i; break; } - if (!(packet->payload[i] >= '0' && packet->payload[i] <= '9')) { + if(!(packet->payload[i] >= '0' && packet->payload[i] <= '9')) { goto imap_excluded; } i++; } - if (space_pos == 0 || space_pos == (packet->payload_packet_len - 1)) { + if(space_pos == 0 || space_pos == (packet->payload_packet_len - 1)) { goto imap_excluded; } } @@ -103,23 +108,23 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, /* command = &(packet->payload[command_start]); */ } - if ((command_start + 3) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'O' || packet->payload[command_start] == 'o') + if((command_start + 3) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'O' || packet->payload[command_start] == 'o') && (packet->payload[command_start + 1] == 'K' || packet->payload[command_start + 1] == 'k') && packet->payload[command_start + 2] == ' ') { flow->l4.tcp.mail_imap_stage += 1; - if (flow->l4.tcp.mail_imap_starttls == 1) + if(flow->l4.tcp.mail_imap_starttls == 1) flow->l4.tcp.mail_imap_starttls = 2; saw_command = 1; - } else if ((packet->payload[command_start] == 'U' || packet->payload[command_start] == 'u') + } else if((packet->payload[command_start] == 'U' || packet->payload[command_start] == 'u') && (packet->payload[command_start + 1] == 'I' || packet->payload[command_start + 1] == 'i') && (packet->payload[command_start + 2] == 'D' || packet->payload[command_start + 2] == 'd')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; } } - if ((command_start + 10) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'C' || packet->payload[command_start] == 'c') + if((command_start + 10) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'C' || packet->payload[command_start] == 'c') && (packet->payload[command_start + 1] == 'A' || packet->payload[command_start + 1] == 'a') && (packet->payload[command_start + 2] == 'P' || packet->payload[command_start + 2] == 'p') && (packet->payload[command_start + 3] == 'A' || packet->payload[command_start + 3] == 'a') @@ -133,8 +138,8 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, saw_command = 1; } } - if ((command_start + 8) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'S' || packet->payload[command_start] == 's') + if((command_start + 8) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'S' || packet->payload[command_start] == 's') && (packet->payload[command_start + 1] == 'T' || packet->payload[command_start + 1] == 't') && (packet->payload[command_start + 2] == 'A' || packet->payload[command_start + 2] == 'a') && (packet->payload[command_start + 3] == 'R' || packet->payload[command_start + 3] == 'r') @@ -148,36 +153,71 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, saw_command = 1; } } - if ((command_start + 5) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'L' || packet->payload[command_start] == 'l') + if((command_start + 5) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'L' || packet->payload[command_start] == 'l') && (packet->payload[command_start + 1] == 'O' || packet->payload[command_start + 1] == 'o') && (packet->payload[command_start + 2] == 'G' || packet->payload[command_start + 2] == 'g') && (packet->payload[command_start + 3] == 'I' || packet->payload[command_start + 3] == 'i') && (packet->payload[command_start + 4] == 'N' || packet->payload[command_start + 4] == 'n')) { + /* xxxx LOGIN "username" "password" */ + char str[256], *item; + u_int len = packet->payload_packet_len >= sizeof(str) ? sizeof(str)-1 : packet->payload_packet_len; + + strncpy(str, (const char*)packet->payload, len); + str[len] = '\0'; + + item = strchr(str, '"'); + if(item) { + char *column; + + item++; + column = strchr(item, '"'); + + if(column) { + column[0] = '\0'; + snprintf(flow->protos.ftp_imap_pop_smtp.username, + sizeof(flow->protos.ftp_imap_pop_smtp.username), + "%s", item); + + column = strchr(&column[1], '"'); + if(column) { + item = &column[1]; + column = strchr(item, '"'); + + if(column) { + column[0] = '\0'; + snprintf(flow->protos.ftp_imap_pop_smtp.password, + sizeof(flow->protos.ftp_imap_pop_smtp.password), + "%s", item); + } + } + } + } + flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'F' || packet->payload[command_start] == 'f') + } else if((packet->payload[command_start] == 'F' || packet->payload[command_start] == 'f') && (packet->payload[command_start + 1] == 'E' || packet->payload[command_start + 1] == 'e') && (packet->payload[command_start + 2] == 'T' || packet->payload[command_start + 2] == 't') && (packet->payload[command_start + 3] == 'C' || packet->payload[command_start + 3] == 'c') && (packet->payload[command_start + 4] == 'H' || packet->payload[command_start + 4] == 'h')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'F' || packet->payload[command_start] == 'f') + } else if((packet->payload[command_start] == 'F' || packet->payload[command_start] == 'f') && (packet->payload[command_start + 1] == 'L' || packet->payload[command_start + 1] == 'l') && (packet->payload[command_start + 2] == 'A' || packet->payload[command_start + 2] == 'a') && (packet->payload[command_start + 3] == 'G' || packet->payload[command_start + 3] == 'g') && (packet->payload[command_start + 4] == 'S' || packet->payload[command_start + 4] == 's')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'C' || packet->payload[command_start] == 'c') + } else if((packet->payload[command_start] == 'C' || packet->payload[command_start] == 'c') && (packet->payload[command_start + 1] == 'H' || packet->payload[command_start + 1] == 'h') && (packet->payload[command_start + 2] == 'E' || packet->payload[command_start + 2] == 'e') && (packet->payload[command_start + 3] == 'C' || packet->payload[command_start + 3] == 'c') && (packet->payload[command_start + 4] == 'K' || packet->payload[command_start + 4] == 'k')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'S' || packet->payload[command_start] == 's') + } else if((packet->payload[command_start] == 'S' || packet->payload[command_start] == 's') && (packet->payload[command_start + 1] == 'T' || packet->payload[command_start + 1] == 't') && (packet->payload[command_start + 2] == 'O' || packet->payload[command_start + 2] == 'o') && (packet->payload[command_start + 3] == 'R' || packet->payload[command_start + 3] == 'r') @@ -186,8 +226,8 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, saw_command = 1; } } - if ((command_start + 12) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'A' || packet->payload[command_start] == 'a') + if((command_start + 12) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'A' || packet->payload[command_start] == 'a') && (packet->payload[command_start + 1] == 'U' || packet->payload[command_start + 1] == 'u') && (packet->payload[command_start + 2] == 'T' || packet->payload[command_start + 2] == 't') && (packet->payload[command_start + 3] == 'H' || packet->payload[command_start + 3] == 'h') @@ -203,8 +243,8 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, saw_command = 1; } } - if ((command_start + 9) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'N' || packet->payload[command_start] == 'n') + if((command_start + 9) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'N' || packet->payload[command_start] == 'n') && (packet->payload[command_start + 1] == 'A' || packet->payload[command_start + 1] == 'a') && (packet->payload[command_start + 2] == 'M' || packet->payload[command_start + 2] == 'm') && (packet->payload[command_start + 3] == 'E' || packet->payload[command_start + 3] == 'e') @@ -217,26 +257,26 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, saw_command = 1; } } - if ((command_start + 4) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'L' || packet->payload[command_start] == 'l') + if((command_start + 4) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'L' || packet->payload[command_start] == 'l') && (packet->payload[command_start + 1] == 'S' || packet->payload[command_start + 1] == 's') && (packet->payload[command_start + 2] == 'U' || packet->payload[command_start + 2] == 'u') && (packet->payload[command_start + 3] == 'B' || packet->payload[command_start + 3] == 'b')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'L' || packet->payload[command_start] == 'l') + } else if((packet->payload[command_start] == 'L' || packet->payload[command_start] == 'l') && (packet->payload[command_start + 1] == 'I' || packet->payload[command_start + 1] == 'i') && (packet->payload[command_start + 2] == 'S' || packet->payload[command_start + 2] == 's') && (packet->payload[command_start + 3] == 'T' || packet->payload[command_start + 3] == 't')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'N' || packet->payload[command_start] == 'n') + } else if((packet->payload[command_start] == 'N' || packet->payload[command_start] == 'n') && (packet->payload[command_start + 1] == 'O' || packet->payload[command_start + 1] == 'o') && (packet->payload[command_start + 2] == 'O' || packet->payload[command_start + 2] == 'o') && (packet->payload[command_start + 3] == 'P' || packet->payload[command_start + 3] == 'p')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'I' || packet->payload[command_start] == 'i') + } else if((packet->payload[command_start] == 'I' || packet->payload[command_start] == 'i') && (packet->payload[command_start + 1] == 'D' || packet->payload[command_start + 1] == 'd') && (packet->payload[command_start + 2] == 'L' || packet->payload[command_start + 2] == 'l') && (packet->payload[command_start + 3] == 'E' || packet->payload[command_start + 3] == 'e')) { @@ -244,8 +284,8 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, saw_command = 1; } } - if ((command_start + 6) < packet->payload_packet_len) { - if ((packet->payload[command_start] == 'S' || packet->payload[command_start] == 's') + if((command_start + 6) < packet->payload_packet_len) { + if((packet->payload[command_start] == 'S' || packet->payload[command_start] == 's') && (packet->payload[command_start + 1] == 'E' || packet->payload[command_start + 1] == 'e') && (packet->payload[command_start + 2] == 'L' || packet->payload[command_start + 2] == 'l') && (packet->payload[command_start + 3] == 'E' || packet->payload[command_start + 3] == 'e') @@ -253,7 +293,7 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, && (packet->payload[command_start + 5] == 'T' || packet->payload[command_start + 5] == 't')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'E' || packet->payload[command_start] == 'e') + } else if((packet->payload[command_start] == 'E' || packet->payload[command_start] == 'e') && (packet->payload[command_start + 1] == 'X' || packet->payload[command_start + 1] == 'x') && (packet->payload[command_start + 2] == 'I' || packet->payload[command_start + 2] == 'i') && (packet->payload[command_start + 3] == 'S' || packet->payload[command_start + 3] == 's') @@ -261,7 +301,7 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, && (packet->payload[command_start + 5] == 'S' || packet->payload[command_start + 5] == 's')) { flow->l4.tcp.mail_imap_stage += 1; saw_command = 1; - } else if ((packet->payload[command_start] == 'A' || packet->payload[command_start] == 'a') + } else if((packet->payload[command_start] == 'A' || packet->payload[command_start] == 'a') && (packet->payload[command_start + 1] == 'P' || packet->payload[command_start + 1] == 'p') && (packet->payload[command_start + 2] == 'P' || packet->payload[command_start + 2] == 'p') && (packet->payload[command_start + 3] == 'E' || packet->payload[command_start + 3] == 'e') @@ -274,16 +314,23 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, } - if (saw_command == 1) { - if (flow->l4.tcp.mail_imap_stage == 3 || flow->l4.tcp.mail_imap_stage == 5) { - NDPI_LOG_INFO(ndpi_struct, "found MAIL_IMAP\n"); - ndpi_int_mail_imap_add_connection(ndpi_struct, flow); + if(saw_command == 1) { + if((flow->l4.tcp.mail_imap_stage == 3) + || (flow->l4.tcp.mail_imap_stage == 5) + || (flow->l4.tcp.mail_imap_stage == 7) + ) { + if((flow->protos.ftp_imap_pop_smtp.username[0] != '\0') + || (flow->l4.tcp.mail_imap_stage >= 7)) { + NDPI_LOG_INFO(ndpi_struct, "found MAIL_IMAP\n"); + ndpi_int_mail_imap_add_connection(ndpi_struct, flow); + } + return; } } } - if (packet->payload_packet_len > 1 && packet->payload[packet->payload_packet_len - 1] == ' ') { + if(packet->payload_packet_len > 1 && packet->payload[packet->payload_packet_len - 1] == ' ') { NDPI_LOG_DBG2(ndpi_struct, "maybe a split imap command -> need next packet and imap_stage is set to 4.\n"); flow->l4.tcp.mail_imap_stage = 4; @@ -294,7 +341,7 @@ void ndpi_search_mail_imap_tcp(struct ndpi_detection_module_struct *ndpi_struct, // skip over possible authentication hashes etc. that cannot be identified as imap commands or responses // if the packet count is low enough and at least one command or response was seen before - if ((packet->payload_packet_len >= 2 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) + if((packet->payload_packet_len >= 2 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) && flow->packet_counter < 6 && flow->l4.tcp.mail_imap_stage >= 1) { NDPI_LOG_DBG2(ndpi_struct, "no imap command or response but packet count < 6 and imap stage >= 1 -> skip\n"); diff --git a/src/lib/protocols/mail_pop.c b/src/lib/protocols/mail_pop.c index 8ed109c..0ab19c4 100644 --- a/src/lib/protocols/mail_pop.c +++ b/src/lib/protocols/mail_pop.c @@ -1,8 +1,8 @@ /* * mail_pop.c * + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -44,163 +44,206 @@ static void ndpi_int_mail_pop_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ + *ndpi_struct, struct ndpi_flow_struct *flow) { ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MAIL_POP, NDPI_PROTOCOL_UNKNOWN); } +/* **************************************** */ + +static void popInitExtraPacketProcessing(struct ndpi_flow_struct *flow); + +/* **************************************** */ static int ndpi_int_mail_pop_check_for_client_commands(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; + *ndpi_struct, struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; - if (packet->payload_packet_len > 4) { - if ((packet->payload[0] == 'A' || packet->payload[0] == 'a') - && (packet->payload[1] == 'U' || packet->payload[1] == 'u') - && (packet->payload[2] == 'T' || packet->payload[2] == 't') - && (packet->payload[3] == 'H' || packet->payload[3] == 'h')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_AUTH; - return 1; - } else if ((packet->payload[0] == 'A' || packet->payload[0] == 'a') - && (packet->payload[1] == 'P' || packet->payload[1] == 'p') - && (packet->payload[2] == 'O' || packet->payload[2] == 'o') - && (packet->payload[3] == 'P' || packet->payload[3] == 'p')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_APOP; - return 1; - } else if ((packet->payload[0] == 'U' || packet->payload[0] == 'u') - && (packet->payload[1] == 'S' || packet->payload[1] == 's') - && (packet->payload[2] == 'E' || packet->payload[2] == 'e') - && (packet->payload[3] == 'R' || packet->payload[3] == 'r')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_USER; - return 1; - } else if ((packet->payload[0] == 'P' || packet->payload[0] == 'p') - && (packet->payload[1] == 'A' || packet->payload[1] == 'a') - && (packet->payload[2] == 'S' || packet->payload[2] == 's') - && (packet->payload[3] == 'S' || packet->payload[3] == 's')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_PASS; - return 1; - } else if ((packet->payload[0] == 'C' || packet->payload[0] == 'c') - && (packet->payload[1] == 'A' || packet->payload[1] == 'a') - && (packet->payload[2] == 'P' || packet->payload[2] == 'p') - && (packet->payload[3] == 'A' || packet->payload[3] == 'a')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_CAPA; - return 1; - } else if ((packet->payload[0] == 'L' || packet->payload[0] == 'l') - && (packet->payload[1] == 'I' || packet->payload[1] == 'i') - && (packet->payload[2] == 'S' || packet->payload[2] == 's') - && (packet->payload[3] == 'T' || packet->payload[3] == 't')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_LIST; - return 1; - } else if ((packet->payload[0] == 'S' || packet->payload[0] == 's') - && (packet->payload[1] == 'T' || packet->payload[1] == 't') - && (packet->payload[2] == 'A' || packet->payload[2] == 'a') - && (packet->payload[3] == 'T' || packet->payload[3] == 't')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_STAT; - return 1; - } else if ((packet->payload[0] == 'U' || packet->payload[0] == 'u') - && (packet->payload[1] == 'I' || packet->payload[1] == 'i') - && (packet->payload[2] == 'D' || packet->payload[2] == 'd') - && (packet->payload[3] == 'L' || packet->payload[3] == 'l')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_UIDL; - return 1; - } else if ((packet->payload[0] == 'R' || packet->payload[0] == 'r') - && (packet->payload[1] == 'E' || packet->payload[1] == 'e') - && (packet->payload[2] == 'T' || packet->payload[2] == 't') - && (packet->payload[3] == 'R' || packet->payload[3] == 'r')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_RETR; - return 1; - } else if ((packet->payload[0] == 'D' || packet->payload[0] == 'd') - && (packet->payload[1] == 'E' || packet->payload[1] == 'e') - && (packet->payload[2] == 'L' || packet->payload[2] == 'l') - && (packet->payload[3] == 'E' || packet->payload[3] == 'e')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_DELE; - return 1; - } else if ((packet->payload[0] == 'S' || packet->payload[0] == 's') - && (packet->payload[1] == 'T' || packet->payload[1] == 't') - && (packet->payload[2] == 'L' || packet->payload[2] == 'l') - && (packet->payload[3] == 'S' || packet->payload[3] == 's')) { - flow->l4.tcp.pop_command_bitmask |= POP_BIT_STLS; - return 1; - } - } - return 0; + if(packet->payload_packet_len > 4) { + if((packet->payload[0] == 'A' || packet->payload[0] == 'a') + && (packet->payload[1] == 'U' || packet->payload[1] == 'u') + && (packet->payload[2] == 'T' || packet->payload[2] == 't') + && (packet->payload[3] == 'H' || packet->payload[3] == 'h')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_AUTH; + return 1; + } else if((packet->payload[0] == 'A' || packet->payload[0] == 'a') + && (packet->payload[1] == 'P' || packet->payload[1] == 'p') + && (packet->payload[2] == 'O' || packet->payload[2] == 'o') + && (packet->payload[3] == 'P' || packet->payload[3] == 'p')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_APOP; + return 1; + } else if((packet->payload[0] == 'U' || packet->payload[0] == 'u') + && (packet->payload[1] == 'S' || packet->payload[1] == 's') + && (packet->payload[2] == 'E' || packet->payload[2] == 'e') + && (packet->payload[3] == 'R' || packet->payload[3] == 'r')) { + ndpi_user_pwd_payload_copy((u_int8_t*)flow->protos.ftp_imap_pop_smtp.username, + sizeof(flow->protos.ftp_imap_pop_smtp.username), 5, + packet->payload, packet->payload_packet_len); + + flow->l4.tcp.pop_command_bitmask |= POP_BIT_USER; + return 1; + } else if((packet->payload[0] == 'P' || packet->payload[0] == 'p') + && (packet->payload[1] == 'A' || packet->payload[1] == 'a') + && (packet->payload[2] == 'S' || packet->payload[2] == 's') + && (packet->payload[3] == 'S' || packet->payload[3] == 's')) { + ndpi_user_pwd_payload_copy((u_int8_t*)flow->protos.ftp_imap_pop_smtp.password, + sizeof(flow->protos.ftp_imap_pop_smtp.password), 5, + packet->payload, packet->payload_packet_len); + + flow->l4.tcp.pop_command_bitmask |= POP_BIT_PASS; + return 1; + } else if((packet->payload[0] == 'C' || packet->payload[0] == 'c') + && (packet->payload[1] == 'A' || packet->payload[1] == 'a') + && (packet->payload[2] == 'P' || packet->payload[2] == 'p') + && (packet->payload[3] == 'A' || packet->payload[3] == 'a')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_CAPA; + return 1; + } else if((packet->payload[0] == 'L' || packet->payload[0] == 'l') + && (packet->payload[1] == 'I' || packet->payload[1] == 'i') + && (packet->payload[2] == 'S' || packet->payload[2] == 's') + && (packet->payload[3] == 'T' || packet->payload[3] == 't')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_LIST; + return 1; + } else if((packet->payload[0] == 'S' || packet->payload[0] == 's') + && (packet->payload[1] == 'T' || packet->payload[1] == 't') + && (packet->payload[2] == 'A' || packet->payload[2] == 'a') + && (packet->payload[3] == 'T' || packet->payload[3] == 't')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_STAT; + return 1; + } else if((packet->payload[0] == 'U' || packet->payload[0] == 'u') + && (packet->payload[1] == 'I' || packet->payload[1] == 'i') + && (packet->payload[2] == 'D' || packet->payload[2] == 'd') + && (packet->payload[3] == 'L' || packet->payload[3] == 'l')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_UIDL; + return 1; + } else if((packet->payload[0] == 'R' || packet->payload[0] == 'r') + && (packet->payload[1] == 'E' || packet->payload[1] == 'e') + && (packet->payload[2] == 'T' || packet->payload[2] == 't') + && (packet->payload[3] == 'R' || packet->payload[3] == 'r')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_RETR; + return 1; + } else if((packet->payload[0] == 'D' || packet->payload[0] == 'd') + && (packet->payload[1] == 'E' || packet->payload[1] == 'e') + && (packet->payload[2] == 'L' || packet->payload[2] == 'l') + && (packet->payload[3] == 'E' || packet->payload[3] == 'e')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_DELE; + return 1; + } else if((packet->payload[0] == 'S' || packet->payload[0] == 's') + && (packet->payload[1] == 'T' || packet->payload[1] == 't') + && (packet->payload[2] == 'L' || packet->payload[2] == 'l') + && (packet->payload[3] == 'S' || packet->payload[3] == 's')) { + flow->l4.tcp.pop_command_bitmask |= POP_BIT_STLS; + return 1; + } + } + return 0; } void ndpi_search_mail_pop_tcp(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) + *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int8_t a = 0; - u_int8_t bit_count = 0; - - NDPI_LOG_DBG(ndpi_struct, "search mail_pop\n"); - - - - if ((packet->payload_packet_len > 3 - && (packet->payload[0] == '+' && (packet->payload[1] == 'O' || packet->payload[1] == 'o') - && (packet->payload[2] == 'K' || packet->payload[2] == 'k'))) - || (packet->payload_packet_len > 4 - && (packet->payload[0] == '-' && (packet->payload[1] == 'E' || packet->payload[1] == 'e') - && (packet->payload[2] == 'R' || packet->payload[2] == 'r') - && (packet->payload[3] == 'R' || packet->payload[3] == 'r')))) { - // +OK or -ERR seen - flow->l4.tcp.mail_pop_stage += 1; - } else if (!ndpi_int_mail_pop_check_for_client_commands(ndpi_struct, flow)) { - goto maybe_split_pop; + struct ndpi_packet_struct *packet = &flow->packet; + u_int8_t a = 0; + u_int8_t bit_count = 0; + + NDPI_LOG_DBG(ndpi_struct, "search mail_pop\n"); + + if((packet->payload_packet_len > 3 + && (packet->payload[0] == '+' && (packet->payload[1] == 'O' || packet->payload[1] == 'o') + && (packet->payload[2] == 'K' || packet->payload[2] == 'k'))) + || (packet->payload_packet_len > 4 + && (packet->payload[0] == '-' && (packet->payload[1] == 'E' || packet->payload[1] == 'e') + && (packet->payload[2] == 'R' || packet->payload[2] == 'r') + && (packet->payload[3] == 'R' || packet->payload[3] == 'r')))) { + // +OK or -ERR seen + flow->l4.tcp.mail_pop_stage += 1; + } else if(!ndpi_int_mail_pop_check_for_client_commands(ndpi_struct, flow)) { + goto maybe_split_pop; + } + + if(packet->payload_packet_len > 2 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) { + // count the bits set in the bitmask + if(flow->l4.tcp.pop_command_bitmask != 0) { + for (a = 0; a < 16; a++) { + bit_count += (flow->l4.tcp.pop_command_bitmask >> a) & 0x01; + } + } + + NDPI_LOG_DBG2(ndpi_struct, + "mail_pop +OK/-ERR responses: %u, unique commands: %u\n", + flow->l4.tcp.mail_pop_stage, bit_count); + + if((bit_count + flow->l4.tcp.mail_pop_stage) >= 3) { + if(flow->l4.tcp.mail_pop_stage > 0) { + NDPI_LOG_INFO(ndpi_struct, "mail_pop identified\n"); + + if((flow->protos.ftp_imap_pop_smtp.password[0] != '\0') + || (flow->l4.tcp.mail_pop_stage > 3)) { + ndpi_int_mail_pop_add_connection(ndpi_struct, flow); + popInitExtraPacketProcessing(flow); } + } + + return; + } else + return; + } else { + // first part of a split packet + NDPI_LOG_DBG2(ndpi_struct, + "mail_pop command without line ending -> skip\n"); + return; + } + + + maybe_split_pop: + + if(((packet->payload_packet_len > 2 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) + || flow->l4.tcp.pop_command_bitmask != 0 || flow->l4.tcp.mail_pop_stage != 0) && flow->packet_counter < 12) { + // maybe part of a split pop packet + NDPI_LOG_DBG2(ndpi_struct, + "maybe part of split mail_pop packet -> skip\n"); + return; + } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); +} - if (packet->payload_packet_len > 2 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) { - - // count the bits set in the bitmask - if (flow->l4.tcp.pop_command_bitmask != 0) { - for (a = 0; a < 16; a++) { - bit_count += (flow->l4.tcp.pop_command_bitmask >> a) & 0x01; - } - } - - NDPI_LOG_DBG2(ndpi_struct, - "mail_pop +OK/-ERR responses: %u, unique commands: %u\n", flow->l4.tcp.mail_pop_stage, bit_count); - - if ((bit_count + flow->l4.tcp.mail_pop_stage) >= 3) { - if (flow->l4.tcp.mail_pop_stage > 0) { - NDPI_LOG_INFO(ndpi_struct, "mail_pop identified\n"); - ndpi_int_mail_pop_add_connection(ndpi_struct, flow); - return; - } else { - return; - } - } else { - return; - } - - } else { - // first part of a split packet - NDPI_LOG_DBG2(ndpi_struct, - "mail_pop command without line ending -> skip\n"); - return; - } +/* **************************************** */ - maybe_split_pop: +int ndpi_extra_search_mail_pop_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + int rc; + + ndpi_search_mail_pop_tcp(ndpi_struct, flow); - if (((packet->payload_packet_len > 2 && ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) - || flow->l4.tcp.pop_command_bitmask != 0 || flow->l4.tcp.mail_pop_stage != 0) && flow->packet_counter < 12) { - // maybe part of a split pop packet - NDPI_LOG_DBG2(ndpi_struct, - "maybe part of split mail_pop packet -> skip\n"); - return; - } + rc = (flow->protos.ftp_imap_pop_smtp.password[0] == '\0') ? 1 : 0; + +#ifdef POP_DEBUG + printf("**** %s() [rc: %d]\n", __FUNCTION__, rc); +#endif + + return(rc); +} - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); +/* **************************************** */ + +static void popInitExtraPacketProcessing(struct ndpi_flow_struct *flow) { +#ifdef POP_DEBUG + printf("**** %s()\n", __FUNCTION__); +#endif + + flow->check_extra_packets = 1; + /* At most 7 packets should almost always be enough */ + flow->max_extra_packets_to_check = 7; + flow->extra_packets_func = ndpi_extra_search_mail_pop_tcp; } +/* **************************************** */ -void init_mail_pop_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ +void init_mail_pop_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("MAIL_POP", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_MAIL_POP, ndpi_search_mail_pop_tcp, diff --git a/src/lib/protocols/mail_smtp.c b/src/lib/protocols/mail_smtp.c index af3d628..025161b 100644 --- a/src/lib/protocols/mail_smtp.c +++ b/src/lib/protocols/mail_smtp.c @@ -1,8 +1,8 @@ /* * mail_smtp.c * + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . - * + * */ @@ -45,148 +45,268 @@ #define SMTP_BIT_RSET 0x1000 #define SMTP_BIT_TlRM 0x2000 +/* #define SMTP_DEBUG 1 */ + static void ndpi_int_mail_smtp_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MAIL_SMTP, NDPI_PROTOCOL_UNKNOWN); + *ndpi_struct, struct ndpi_flow_struct *flow) { +#ifdef SMTP_DEBUG + printf("**** %s()\n", __FUNCTION__); +#endif + + ndpi_set_detected_protocol(ndpi_struct, flow, + NDPI_PROTOCOL_MAIL_SMTP, NDPI_PROTOCOL_UNKNOWN); } -void ndpi_search_mail_smtp_tcp(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ +/* **************************************** */ + +static void smtpInitExtraPacketProcessing(struct ndpi_flow_struct *flow); + +/* **************************************** */ + +void ndpi_search_mail_smtp_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - + NDPI_LOG_DBG(ndpi_struct, "search mail_smtp\n"); if((packet->payload_packet_len > 2) - && (packet->parsed_lines < NDPI_MAX_PARSE_LINES_PER_PACKET) - && (ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) - ) { - u_int8_t a; + && (packet->parsed_lines < NDPI_MAX_PARSE_LINES_PER_PACKET) + && (ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a) + ) { + u_int16_t a; u_int8_t bit_count = 0; - NDPI_PARSE_PACKET_LINE_INFO(ndpi_struct, flow,packet); + NDPI_PARSE_PACKET_LINE_INFO(ndpi_struct, flow, packet); - for (a = 0; a < packet->parsed_lines; a++) { + for(a = 0; a < packet->parsed_lines; a++) { // expected server responses - if (packet->line[a].len >= 3) { - if (memcmp(packet->line[a].ptr, "220", 3) == 0) { + if(packet->line[a].len >= 3) { + if(memcmp(packet->line[a].ptr, "220", 3) == 0) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_220; - } else if (memcmp(packet->line[a].ptr, "250", 3) == 0) { + } else if(memcmp(packet->line[a].ptr, "250", 3) == 0) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_250; - } else if (memcmp(packet->line[a].ptr, "235", 3) == 0) { + } else if(memcmp(packet->line[a].ptr, "235", 3) == 0) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_235; - } else if (memcmp(packet->line[a].ptr, "334", 3) == 0) { + } else if(memcmp(packet->line[a].ptr, "334", 3) == 0) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_334; - } else if (memcmp(packet->line[a].ptr, "354", 3) == 0) { + } else if(memcmp(packet->line[a].ptr, "354", 3) == 0) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_354; } } + // expected client requests - if (packet->line[a].len >= 5) { - if ((((packet->line[a].ptr[0] == 'H' || packet->line[a].ptr[0] == 'h') - && (packet->line[a].ptr[1] == 'E' || packet->line[a].ptr[1] == 'e')) - || ((packet->line[a].ptr[0] == 'E' || packet->line[a].ptr[0] == 'e') - && (packet->line[a].ptr[1] == 'H' || packet->line[a].ptr[1] == 'h'))) - && (packet->line[a].ptr[2] == 'L' || packet->line[a].ptr[2] == 'l') - && (packet->line[a].ptr[3] == 'O' || packet->line[a].ptr[3] == 'o') - && packet->line[a].ptr[4] == ' ') { + if(packet->line[a].len >= 5) { + if((((packet->line[a].ptr[0] == 'H' || packet->line[a].ptr[0] == 'h') + && (packet->line[a].ptr[1] == 'E' || packet->line[a].ptr[1] == 'e')) + || ((packet->line[a].ptr[0] == 'E' || packet->line[a].ptr[0] == 'e') + && (packet->line[a].ptr[1] == 'H' || packet->line[a].ptr[1] == 'h'))) + && (packet->line[a].ptr[2] == 'L' || packet->line[a].ptr[2] == 'l') + && (packet->line[a].ptr[3] == 'O' || packet->line[a].ptr[3] == 'o') + && packet->line[a].ptr[4] == ' ') { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_HELO_EHLO; - } else if ((packet->line[a].ptr[0] == 'M' || packet->line[a].ptr[0] == 'm') - && (packet->line[a].ptr[1] == 'A' || packet->line[a].ptr[1] == 'a') - && (packet->line[a].ptr[2] == 'I' || packet->line[a].ptr[2] == 'i') - && (packet->line[a].ptr[3] == 'L' || packet->line[a].ptr[3] == 'l') - && packet->line[a].ptr[4] == ' ') { + flow->protos.ftp_imap_pop_smtp.auth_found = 0; + } else if((packet->line[a].ptr[0] == 'M' || packet->line[a].ptr[0] == 'm') + && (packet->line[a].ptr[1] == 'A' || packet->line[a].ptr[1] == 'a') + && (packet->line[a].ptr[2] == 'I' || packet->line[a].ptr[2] == 'i') + && (packet->line[a].ptr[3] == 'L' || packet->line[a].ptr[3] == 'l') + && packet->line[a].ptr[4] == ' ') { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_MAIL; - } else if ((packet->line[a].ptr[0] == 'R' || packet->line[a].ptr[0] == 'r') - && (packet->line[a].ptr[1] == 'C' || packet->line[a].ptr[1] == 'c') - && (packet->line[a].ptr[2] == 'P' || packet->line[a].ptr[2] == 'p') - && (packet->line[a].ptr[3] == 'T' || packet->line[a].ptr[3] == 't') - && packet->line[a].ptr[4] == ' ') { + flow->protos.ftp_imap_pop_smtp.auth_found = 0; + } else if((packet->line[a].ptr[0] == 'R' || packet->line[a].ptr[0] == 'r') + && (packet->line[a].ptr[1] == 'C' || packet->line[a].ptr[1] == 'c') + && (packet->line[a].ptr[2] == 'P' || packet->line[a].ptr[2] == 'p') + && (packet->line[a].ptr[3] == 'T' || packet->line[a].ptr[3] == 't') + && packet->line[a].ptr[4] == ' ') { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_RCPT; - } else if ((packet->line[a].ptr[0] == 'A' || packet->line[a].ptr[0] == 'a') - && (packet->line[a].ptr[1] == 'U' || packet->line[a].ptr[1] == 'u') - && (packet->line[a].ptr[2] == 'T' || packet->line[a].ptr[2] == 't') - && (packet->line[a].ptr[3] == 'H' || packet->line[a].ptr[3] == 'h') - && packet->line[a].ptr[4] == ' ') { + flow->protos.ftp_imap_pop_smtp.auth_found = 0; + } else if((packet->line[a].ptr[0] == 'A' || packet->line[a].ptr[0] == 'a') + && (packet->line[a].ptr[1] == 'U' || packet->line[a].ptr[1] == 'u') + && (packet->line[a].ptr[2] == 'T' || packet->line[a].ptr[2] == 't') + && (packet->line[a].ptr[3] == 'H' || packet->line[a].ptr[3] == 'h') + && packet->line[a].ptr[4] == ' ') { +#ifdef SMTP_DEBUG + printf("%s() AUTH [%.*s]\n", __FUNCTION__, packet->line[a].len, packet->line[a].ptr); +#endif + flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_AUTH; + flow->protos.ftp_imap_pop_smtp.auth_found = 1; + } else { + if(packet->line[a].ptr[3] != ' ') { +#ifdef SMTP_DEBUG + printf("%s() => [%.*s]\n", __FUNCTION__, packet->line[a].len, packet->line[a].ptr); +#endif + + if(flow->protos.ftp_imap_pop_smtp.auth_found) { + if(flow->protos.ftp_imap_pop_smtp.username[0] == '\0') { + /* Username */ + u_int8_t buf[48]; + u_char *out; + size_t out_len; + + ndpi_user_pwd_payload_copy(buf, sizeof(buf), 0, + packet->line[a].ptr, packet->line[a].len); + +#ifdef SMTP_DEBUG + printf("%s() => [auth: %u] (username) [%s]\n", __FUNCTION__, flow->protos.ftp_imap_pop_smtp.auth_found, buf); +#endif + + out = ndpi_base64_decode((const u_char*)buf, (size_t)strlen((const char*)buf), &out_len); + + if(out) { + size_t len = ndpi_min(out_len, sizeof(flow->protos.ftp_imap_pop_smtp.username) - 1); + + memcpy(flow->protos.ftp_imap_pop_smtp.username, out, len); + flow->protos.ftp_imap_pop_smtp.username[len] = '\0'; + + ndpi_free(out); + } + } else if(flow->protos.ftp_imap_pop_smtp.password[0] == '\0') { + /* Password */ + u_int8_t buf[48]; + u_char *out; + size_t out_len; + + ndpi_user_pwd_payload_copy(buf, sizeof(buf), 0, + packet->line[a].ptr, packet->line[a].len); + +#ifdef SMTP_DEBUG + printf("%s() => [auth: %u] (password) [%s]\n", __FUNCTION__, flow->protos.ftp_imap_pop_smtp.auth_found, buf); +#endif + + out = ndpi_base64_decode((const u_char*)buf, (size_t)strlen((const char*)buf), &out_len); + + if(out) { + size_t len = ndpi_min(out_len, sizeof(flow->protos.ftp_imap_pop_smtp.password) - 1); + + memcpy(flow->protos.ftp_imap_pop_smtp.password, out, len); + flow->protos.ftp_imap_pop_smtp.password[len] = '\0'; + + ndpi_free(out); + } + } else { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + } + } } } - if (packet->line[a].len >= 8) { - if ((packet->line[a].ptr[0] == 'S' || packet->line[a].ptr[0] == 's') - && (packet->line[a].ptr[1] == 'T' || packet->line[a].ptr[1] == 't') - && (packet->line[a].ptr[2] == 'A' || packet->line[a].ptr[2] == 'a') - && (packet->line[a].ptr[3] == 'R' || packet->line[a].ptr[3] == 'r') - && (packet->line[a].ptr[4] == 'T' || packet->line[a].ptr[4] == 't') - && (packet->line[a].ptr[5] == 'T' || packet->line[a].ptr[5] == 't') - && (packet->line[a].ptr[6] == 'L' || packet->line[a].ptr[6] == 'l') - && (packet->line[a].ptr[7] == 'S' || packet->line[a].ptr[7] == 's')) { + if(packet->line[a].len >= 8) { + if((packet->line[a].ptr[0] == 'S' || packet->line[a].ptr[0] == 's') + && (packet->line[a].ptr[1] == 'T' || packet->line[a].ptr[1] == 't') + && (packet->line[a].ptr[2] == 'A' || packet->line[a].ptr[2] == 'a') + && (packet->line[a].ptr[3] == 'R' || packet->line[a].ptr[3] == 'r') + && (packet->line[a].ptr[4] == 'T' || packet->line[a].ptr[4] == 't') + && (packet->line[a].ptr[5] == 'T' || packet->line[a].ptr[5] == 't') + && (packet->line[a].ptr[6] == 'L' || packet->line[a].ptr[6] == 'l') + && (packet->line[a].ptr[7] == 'S' || packet->line[a].ptr[7] == 's')) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_STARTTLS; } } - if (packet->line[a].len >= 4) { - if ((packet->line[a].ptr[0] == 'D' || packet->line[a].ptr[0] == 'd') - && (packet->line[a].ptr[1] == 'A' || packet->line[a].ptr[1] == 'a') - && (packet->line[a].ptr[2] == 'T' || packet->line[a].ptr[2] == 't') - && (packet->line[a].ptr[3] == 'A' || packet->line[a].ptr[3] == 'a')) { + if(packet->line[a].len >= 4) { + if((packet->line[a].ptr[0] == 'D' || packet->line[a].ptr[0] == 'd') + && (packet->line[a].ptr[1] == 'A' || packet->line[a].ptr[1] == 'a') + && (packet->line[a].ptr[2] == 'T' || packet->line[a].ptr[2] == 't') + && (packet->line[a].ptr[3] == 'A' || packet->line[a].ptr[3] == 'a')) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_DATA; - } else if ((packet->line[a].ptr[0] == 'N' || packet->line[a].ptr[0] == 'n') - && (packet->line[a].ptr[1] == 'O' || packet->line[a].ptr[1] == 'o') - && (packet->line[a].ptr[2] == 'O' || packet->line[a].ptr[2] == 'o') - && (packet->line[a].ptr[3] == 'P' || packet->line[a].ptr[3] == 'p')) { + } else if((packet->line[a].ptr[0] == 'N' || packet->line[a].ptr[0] == 'n') + && (packet->line[a].ptr[1] == 'O' || packet->line[a].ptr[1] == 'o') + && (packet->line[a].ptr[2] == 'O' || packet->line[a].ptr[2] == 'o') + && (packet->line[a].ptr[3] == 'P' || packet->line[a].ptr[3] == 'p')) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_NOOP; - } else if ((packet->line[a].ptr[0] == 'R' || packet->line[a].ptr[0] == 'r') - && (packet->line[a].ptr[1] == 'S' || packet->line[a].ptr[1] == 's') - && (packet->line[a].ptr[2] == 'E' || packet->line[a].ptr[2] == 'e') - && (packet->line[a].ptr[3] == 'T' || packet->line[a].ptr[3] == 't')) { + } else if((packet->line[a].ptr[0] == 'R' || packet->line[a].ptr[0] == 'r') + && (packet->line[a].ptr[1] == 'S' || packet->line[a].ptr[1] == 's') + && (packet->line[a].ptr[2] == 'E' || packet->line[a].ptr[2] == 'e') + && (packet->line[a].ptr[3] == 'T' || packet->line[a].ptr[3] == 't')) { flow->l4.tcp.smtp_command_bitmask |= SMTP_BIT_RSET; } } - } // now count the bits set in the bitmask - if (flow->l4.tcp.smtp_command_bitmask != 0) { - for (a = 0; a < 16; a++) { + if(flow->l4.tcp.smtp_command_bitmask != 0) { + for(a = 0; a < 16; a++) { bit_count += (flow->l4.tcp.smtp_command_bitmask >> a) & 0x01; } } NDPI_LOG_DBG2(ndpi_struct, "seen smtp commands and responses: %u\n", - bit_count); + bit_count); - if (bit_count >= 3) { + if(bit_count >= 3) { NDPI_LOG_INFO(ndpi_struct, "mail smtp identified\n"); + +#ifdef SMTP_DEBUG + printf("%s() [bit_count: %u][%s]\n", __FUNCTION__, + bit_count, flow->protos.ftp_imap_pop_smtp.password); +#endif + ndpi_int_mail_smtp_add_connection(ndpi_struct, flow); + smtpInitExtraPacketProcessing(flow); return; } - if (bit_count >= 1 && flow->packet_counter < 12) { + + if(bit_count >= 1 && flow->packet_counter < 12) { return; } } + /* when the first or second packets are split into two packets, those packets are ignored. */ - if (flow->packet_counter <= 4 && - packet->payload_packet_len >= 4 && - (ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a - || memcmp(packet->payload, "220", 3) == 0 || memcmp(packet->payload, "EHLO", 4) == 0)) { + if(flow->packet_counter <= 4 && + packet->payload_packet_len >= 4 && + (ntohs(get_u_int16_t(packet->payload, packet->payload_packet_len - 2)) == 0x0d0a + || memcmp(packet->payload, "220", 3) == 0 || memcmp(packet->payload, "EHLO", 4) == 0)) { NDPI_LOG_DBG2(ndpi_struct, "maybe SMTP, need next packet\n"); return; } - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + if(!flow->check_extra_packets) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); +} + +/* **************************************** */ + +int ndpi_extra_search_mail_smtp_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + int rc; + + ndpi_search_mail_smtp_tcp(ndpi_struct, flow); + + rc = (flow->protos.ftp_imap_pop_smtp.password[0] == '\0') ? 1 : 0; + +#ifdef SMTP_DEBUG + printf("**** %s() [rc: %d]\n", __FUNCTION__, rc); +#endif + + return(rc); +} + +/* **************************************** */ +static void smtpInitExtraPacketProcessing(struct ndpi_flow_struct *flow) { +#ifdef SMTP_DEBUG + static u_int num = 0; + + printf("**** %s(%u)\n", __FUNCTION__, ++num); +#endif + + flow->check_extra_packets = 1; + /* At most 7 packets should almost always be enough */ + flow->max_extra_packets_to_check = 12; + flow->extra_packets_func = ndpi_extra_search_mail_smtp_tcp; } -void init_mail_smtp_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ +/* **************************************** */ + +void init_mail_smtp_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("MAIL_SMTP", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_MAIL_SMTP, ndpi_search_mail_smtp_tcp, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); + ADD_TO_DETECTION_BITMASK); *id += 1; } - diff --git a/src/lib/protocols/maplestory.c b/src/lib/protocols/maplestory.c index 23dcce4..a1d31c6 100644 --- a/src/lib/protocols/maplestory.c +++ b/src/lib/protocols/maplestory.c @@ -2,7 +2,7 @@ * maplestory.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/mdns_proto.c b/src/lib/protocols/mdns_proto.c deleted file mode 100644 index 6297bd4..0000000 --- a/src/lib/protocols/mdns_proto.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * mdns.c - * - * Copyright (C) 2016-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_MDNS - -#include "ndpi_api.h" - -#define NDPI_MAX_MDNS_REQUESTS 128 - -PACK_ON -struct mdns_header { - u_int16_t transaction_id, flags, questions, answers, authority_rr, additional_rr; -} PACK_OFF; - -/** - MDNS header is similar to dns header - - 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | ID = 0x0000 | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | FLAGS | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | QDCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | ANCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | NSCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - | ARCOUNT | - +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ -*/ - - -static void ndpi_int_mdns_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) { - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MDNS, NDPI_PROTOCOL_UNKNOWN); -} - -static int ndpi_int_check_mdns_payload(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - struct mdns_header *h = (struct mdns_header*)packet->payload; - u_int16_t questions = ntohs(h->questions), answers = ntohs(h->answers); - - if((questions > NDPI_MAX_MDNS_REQUESTS) - || (answers > NDPI_MAX_MDNS_REQUESTS)) - return(0); - - if((packet->payload[2] & 0x80) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found MDNS with question query\n"); - return 1; - } else if((packet->payload[2] & 0x80) != 0) { - char answer[256]; - int i, j, len; - - for(i=13, j=0; (i < packet->payload_packet_len) && (i < (sizeof(answer)-1)) && (packet->payload[i] != 0); i++) - answer[j++] = (packet->payload[i] < 13) ? '.' : packet->payload[i]; - - answer[j] = '\0'; - - /* printf("==> [%d] %s\n", j, answer); */ - - if(!ndpi_struct->disable_metadata_export) { - len = ndpi_min(sizeof(flow->protos.mdns.answer)-1, j); - strncpy(flow->protos.mdns.answer, (const char *)answer, len); - flow->protos.mdns.answer[len] = '\0'; - } - - NDPI_LOG_INFO(ndpi_struct, "found MDNS with answer query\n"); - return 1; - } - - return 0; -} - -void ndpi_search_mdns(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - NDPI_LOG_DBG(ndpi_struct, "search MDNS\n"); - - /** - information from http://www.it-administrator.de/lexikon/multicast-dns.html - */ - - /* check if UDP packet */ - if(packet->udp != NULL) { - /* read destination port */ - u_int16_t sport = ntohs(packet->udp->source); - u_int16_t dport = ntohs(packet->udp->dest); - - /* check standard MDNS ON port 5353 */ - if(((dport == 5353) || (sport == 5353)) - && (packet->payload_packet_len >= 12)) { - if(packet->iph != NULL) { - if(ndpi_int_check_mdns_payload(ndpi_struct, flow) == 1) { - ndpi_int_mdns_add_connection(ndpi_struct, flow); - return; - } - } -#ifdef NDPI_DETECTION_SUPPORT_IPV6 - if(packet->iphv6 != NULL) { - u_int32_t daddr_0 = packet->iphv6->ip6_dst.u6_addr.u6_addr32[0]; - - if(daddr_0 == htonl(0xff020000) /* && daddr[1] == 0 && daddr[2] == 0 && daddr[3] == htonl(0xfb) */) { - - NDPI_LOG_INFO(ndpi_struct, "found MDNS with destination address ff02::fb\n"); - - if(ndpi_int_check_mdns_payload(ndpi_struct, flow) == 1) { - ndpi_int_mdns_add_connection(ndpi_struct, flow); - return; - } - } - } -#endif - } - } - - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); -} - - -void init_mdns_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("MDNS", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_MDNS, - ndpi_search_mdns, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} - diff --git a/src/lib/protocols/megaco.c b/src/lib/protocols/megaco.c index 149a15f..bd1c29d 100644 --- a/src/lib/protocols/megaco.c +++ b/src/lib/protocols/megaco.c @@ -2,7 +2,7 @@ * megaco.c * * Copyright (C) 2014 by Gianluca Costa http://www.capanalysis.net - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This module is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/memcached.c b/src/lib/protocols/memcached.c index 2b647b9..ef846c9 100644 --- a/src/lib/protocols/memcached.c +++ b/src/lib/protocols/memcached.c @@ -2,7 +2,7 @@ * memcached.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2018 - eGloo Incorporated * * This file is part of nDPI, an open source deep packet inspection @@ -105,7 +105,7 @@ void ndpi_search_memcached( { struct ndpi_packet_struct *packet = &flow->packet; const u_int8_t *offset = packet->payload; - const u_int16_t length = packet->payload_packet_len; + u_int16_t length = packet->payload_packet_len; u_int8_t *matches; NDPI_LOG_DBG(ndpi_struct, "search memcached\n"); @@ -131,6 +131,7 @@ void ndpi_search_memcached( } offset += MEMCACHED_UDP_HDR_LEN; + length -= MEMCACHED_UDP_HDR_LEN; matches = &flow->l4.udp.memcached_matches; } else { diff --git a/src/lib/protocols/mgcp.c b/src/lib/protocols/mgcp.c index 198a8c6..54e0656 100644 --- a/src/lib/protocols/mgcp.c +++ b/src/lib/protocols/mgcp.c @@ -1,7 +1,7 @@ /* * mgcp.c * - * Copyright (C) 2017-19 - ntop.org + * Copyright (C) 2017-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/mining.c b/src/lib/protocols/mining.c index b4361e2..1f86987 100644 --- a/src/lib/protocols/mining.c +++ b/src/lib/protocols/mining.c @@ -1,7 +1,7 @@ /* * mining.c [Bitcoin, Ethereum, ZCash, Monero] * - * Copyright (C) 2018 - ntop.org + * Copyright (C) 2018-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -21,17 +21,53 @@ * */ #include "ndpi_protocol_ids.h" - +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_MINING #include "ndpi_api.h" +/* ************************************************************************** */ + +void ndpi_search_mining_udp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + u_int16_t source = ntohs(packet->udp->source); + u_int16_t dest = ntohs(packet->udp->dest); + + NDPI_LOG_DBG(ndpi_struct, "search MINING UDP\n"); + + // printf("==> %s()\n", __FUNCTION__); + /* + Ethereum P2P Discovery Protocol + https://github.com/ConsenSys/ethereum-dissectors/blob/master/packet-ethereum-disc.c + */ + if((packet->payload_packet_len > 98) + && (packet->payload_packet_len < 1280) + && ((source == 30303) || (dest == 30303)) + && (packet->payload[97] <= 0x04 /* NODES */) + ) { + if((packet->iph) && ((ntohl(packet->iph->daddr) & 0xFF000000 /* 255.0.0.0 */) == 0xFF000000)) + ; + else if(packet->iphv6 && ntohl(packet->iphv6->ip6_dst.u6_addr.u6_addr32[0]) == 0xFF020000) + ; + else { + snprintf(flow->flow_extra_info, sizeof(flow->flow_extra_info), "%s", "ETH"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN); + return; + } + } + + ndpi_exclude_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, __FILE__, __FUNCTION__, __LINE__); +} + +/* ************************************************************************** */ + void ndpi_search_mining_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - NDPI_LOG_DBG(ndpi_struct, "search MINING\n"); + NDPI_LOG_DBG(ndpi_struct, "search MINING TCP\n"); /* Check connection over TCP */ - if(packet->tcp && (packet->payload_packet_len > 10)) { + if(packet->payload_packet_len > 10) { if(packet->tcp->source == htons(8333)) { /* @@ -42,9 +78,18 @@ void ndpi_search_mining_tcp(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t magic = htonl(0xf9beb4d9), magic1 = htonl(0xfabfb5da), *to_match = (u_int32_t*)packet->payload; if((*to_match == magic) || (*to_match == magic1)) { + snprintf(flow->flow_extra_info, sizeof(flow->flow_extra_info), "%s", "ETH"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN); } - } if(ndpi_strnstr((const char *)packet->payload, "{", packet->payload_packet_len) + } + + if((packet->payload_packet_len > 450) + && (packet->payload_packet_len < 600) + && (packet->tcp->dest == htons(30303) /* Ethereum port */) + && (packet->payload[2] == 0x04)) { + snprintf(flow->flow_extra_info, sizeof(flow->flow_extra_info), "%s", "ETH"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN); + } else if(ndpi_strnstr((const char *)packet->payload, "{", packet->payload_packet_len) && ( ndpi_strnstr((const char *)packet->payload, "\"eth1.0\"", packet->payload_packet_len) || ndpi_strnstr((const char *)packet->payload, "\"worker\":", packet->payload_packet_len) @@ -57,6 +102,7 @@ void ndpi_search_mining_tcp(struct ndpi_detection_module_struct *ndpi_struct, { "id": 2, "jsonrpc":"2.0","result":true} {"worker": "", "jsonrpc": "2.0", "params": [], "id": 3, "method": "eth_getWork"} */ + snprintf(flow->flow_extra_info, sizeof(flow->flow_extra_info), "%s", "ETH"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN); } else if(ndpi_strnstr((const char *)packet->payload, "{", packet->payload_packet_len) && (ndpi_strnstr((const char *)packet->payload, "\"method\":", packet->payload_packet_len) @@ -77,6 +123,7 @@ void ndpi_search_mining_tcp(struct ndpi_detection_module_struct *ndpi_struct, {"id":1,"jsonrpc":"2.0","error":null,"result":{"id":"479059546883218","job":{"blob":"0606e89883d205a65d8ee78991838a1cf3ec2ebbc5fb1fa43dec5fa1cd2bee4069212a549cd731000000005a88235653097aa3e97ef2ceef4aee610751a828f9be1a0758a78365fb0a4c8c05","job_id":"722134174127131","target":"dc460300"},"status":"OK"}} {"method":"submit","params":{"id":"479059546883218","job_id":"722134174127131","nonce":"98024001","result":"c9be9381a68d533c059d614d961e0534d7d8785dd5c339c2f9596eb95f320100"},"id":1} */ + snprintf(flow->flow_extra_info, sizeof(flow->flow_extra_info), "%s", "ZCash/Monero"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, NDPI_PROTOCOL_UNKNOWN); } } @@ -84,6 +131,7 @@ void ndpi_search_mining_tcp(struct ndpi_detection_module_struct *ndpi_struct, ndpi_exclude_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MINING, __FILE__, __FUNCTION__, __LINE__); } +/* ************************************************************************** */ void init_mining_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) @@ -96,5 +144,16 @@ void init_mining_dissector(struct ndpi_detection_module_struct *ndpi_struct, ADD_TO_DETECTION_BITMASK); *id += 1; + + /* ************ */ + + ndpi_set_bitmask_protocol_detection("Mining", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_MINING, + ndpi_search_mining_udp, + NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); + + *id += 1; } diff --git a/src/lib/protocols/modbus.c b/src/lib/protocols/modbus.c index 2a6dd2a..c98c712 100644 --- a/src/lib/protocols/modbus.c +++ b/src/lib/protocols/modbus.c @@ -24,9 +24,8 @@ #include "ndpi_protocol_ids.h" -#include "ndpi_api.h" - #define NDPI_CURRENT_PROTO NDPI_PROTOCOL_MODBUS +#include "ndpi_api.h" void ndpi_search_modbus_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { diff --git a/src/lib/protocols/mpegts.c b/src/lib/protocols/mpegts.c index b069141..bb1f219 100644 --- a/src/lib/protocols/mpegts.c +++ b/src/lib/protocols/mpegts.c @@ -2,7 +2,7 @@ * mpegts.c (MPEG Transport Stream) * https://en.wikipedia.org/wiki/MPEG_transport_stream * - * Copyright (C) 2015-19 - ntop.org + * Copyright (C) 2015-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/mqtt.c b/src/lib/protocols/mqtt.c index 45669c0..bf85386 100644 --- a/src/lib/protocols/mqtt.c +++ b/src/lib/protocols/mqtt.c @@ -79,7 +79,11 @@ void ndpi_search_mqtt (struct ndpi_detection_module_struct *ndpi_struct, } NDPI_LOG_DBG2(ndpi_struct, "====>>>> Mqtt header: %4x%4x%4x%4x [len: %u]\n", - packet->payload[0], packet->payload[1], packet->payload[2], packet->payload[3], packet->payload_packet_len); + packet->payload_packet_len > 0 ? packet->payload[0] : '.', + packet->payload_packet_len > 1 ? packet->payload[1] : '.', + packet->payload_packet_len > 2 ? packet->payload[2] : '.', + packet->payload_packet_len > 3 ? packet->payload[3] : '.', + packet->payload_packet_len); if (packet->payload_packet_len < 2) { NDPI_LOG_DBG(ndpi_struct, "Excluding Mqtt .. mandatory header not found!\n"); NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_MQTT); diff --git a/src/lib/protocols/msn.c b/src/lib/protocols/msn.c deleted file mode 100644 index 6469c7c..0000000 --- a/src/lib/protocols/msn.c +++ /dev/null @@ -1,526 +0,0 @@ -/* - * msn.c - * - * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ - -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_MSN - -#include "ndpi_api.h" - -#define MAX_PACKETS_FOR_MSN 100 - -static void ndpi_int_msn_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_MSN, NDPI_PROTOCOL_UNKNOWN); -} - -static u_int8_t ndpi_int_find_xmsn(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - - if(packet->parsed_lines > 3) { - u_int16_t i; - for(i = 2; i < packet->parsed_lines; i++) { - if(packet->line[i].ptr != NULL && packet->line[i].len > NDPI_STATICSTRING_LEN("X-MSN") && - memcmp(packet->line[i].ptr, "X-MSN", NDPI_STATICSTRING_LEN("X-MSN")) == 0) { - return 1; - } - } - } - return 0; -} - -/* search over TCP */ -static void ndpi_search_msn_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - - struct ndpi_id_struct *src = flow->src; - struct ndpi_id_struct *dst = flow->dst; - - u_int16_t plen; - u_int16_t status = 0; - - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS) { - - NDPI_LOG_DBG2(ndpi_struct, "msn ssl ft test\n"); - - if(flow->packet_counter < 10) { - if(flow->packet_counter == 7 && packet->payload_packet_len > 300) { - if(memcmp(packet->payload + 24, "MSNSLP", 6) == 0 - || (get_u_int32_t(packet->payload, 0) == htonl(0x30000000) - && get_u_int32_t(packet->payload, 4) == 0x00000000)) { - NDPI_LOG_INFO(ndpi_struct, "found MSN File Transfer, ifdef ssl\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - - if(flow->packet_counter >= 5 && flow->packet_counter <= 10 - && (get_u_int32_t(packet->payload, 0) == htonl(0x18000000) - && get_u_int32_t(packet->payload, 4) == 0x00000000)) { - flow->l4.tcp.msn_ssl_ft++; - NDPI_LOG_DBG2(ndpi_struct, - "increased msn ft ssl stage to: %u at packet nr: %u\n", - flow->l4.tcp.msn_ssl_ft, - flow->packet_counter); - if (flow->l4.tcp.msn_ssl_ft == 2) { - NDPI_LOG_INFO(ndpi_struct, - "found MSN File Transfer, ifdef ssl 2.\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - } - - return; - } - } - } - - /* we detect the initial connection only ! */ - /* match: "VER " ..... "CVR" x 0x0d 0x0a - * len should be small, lets say less than 100 bytes - * x is now "0", but can be increased - */ - /* now we have a look at the first packet only. */ - if(flow->packet_counter == 1 - || ((packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS) - && flow->packet_counter <= 3) - ) { - - /* this part is working asymmetrically */ - if(packet->payload_packet_len > 32 - && (packet->payload[0] == 0x02 || packet->payload[0] == 0x00) - && (ntohl(get_u_int32_t(packet->payload, 8)) == 0x2112a442 - || ntohl(get_u_int32_t(packet->payload, 4)) == 0x2112a442) - && ((ntohl(get_u_int32_t(packet->payload, 24)) == 0x000f0004 - && ntohl(get_u_int32_t(packet->payload, 28)) == 0x72c64bc6) - || (ntohl(get_u_int32_t(packet->payload, 20)) == 0x000f0004 - && ntohl(get_u_int32_t(packet->payload, 24)) == 0x72c64bc6))) { - NDPI_LOG_INFO(ndpi_struct, - "found MSN in packets that also contain voice.messenger.live.com.\n"); - - /* TODO this is an alternative pattern for video detection */ - /* if (packet->payload_packet_len > 100 && - get_u_int16_t(packet->payload, 86) == htons(0x05dc)) { */ - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - - /* this case works asymmetrically */ - if (packet->payload_packet_len > 10 && packet->payload_packet_len < 100) { - if (get_u_int8_t(packet->payload, packet->payload_packet_len - 2) == 0x0d - && get_u_int8_t(packet->payload, packet->payload_packet_len - 1) == 0x0a) { - /* The MSNP string is used in XBOX clients. */ - if (ndpi_match_strprefix(packet->payload, packet->payload_packet_len, "VER ")) { - - if (memcmp(&packet->payload[packet->payload_packet_len - 6], "CVR", - 3) == 0 || memcmp(&packet->payload[packet->payload_packet_len - 8], "MSNP", 4) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found MSN by pattern VER...CVR/MSNP ODOA\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - if (ndpi_match_strprefix(&packet->payload[4], packet->payload_packet_len-4, "MSNFT")) { - NDPI_LOG_INFO(ndpi_struct, "found MSN FT by pattern VER MSNFT...0d0a\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - } - } - - if( - packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP || - ndpi_match_strprefix(packet->payload, packet->payload_packet_len, "GET ") || - ndpi_match_strprefix(packet->payload, packet->payload_packet_len, "POST ")) { - ndpi_parse_packet_line_info(ndpi_struct, flow); - if (packet->user_agent_line.ptr != NULL && - packet->user_agent_line.len > NDPI_STATICSTRING_LEN("Messenger/") && - memcmp(packet->user_agent_line.ptr, "Messenger/", NDPI_STATICSTRING_LEN("Messenger/")) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found MSN Messenger/\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - - /* not seen this pattern in any trace */ - /* now test for http login, at least 100 a bytes packet */ - if(packet->payload_packet_len > 100) { - if( - packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP || - memcmp(packet->payload, "POST http://", 12) == 0) { - /* scan packet if not already done... */ - ndpi_parse_packet_line_info(ndpi_struct, flow); - - if(packet->content_line.ptr != NULL && - ((packet->content_line.len == NDPI_STATICSTRING_LEN("application/x-msn-messenger") && - memcmp(packet->content_line.ptr, "application/x-msn-messenger", - NDPI_STATICSTRING_LEN("application/x-msn-messenger")) == 0) || - (packet->content_line.len >= NDPI_STATICSTRING_LEN("text/x-msnmsgr") && - memcmp(packet->content_line.ptr, "text/x-msnmsgr", - NDPI_STATICSTRING_LEN("text/x-msnmsgr")) == 0))) { - NDPI_LOG_INFO(ndpi_struct, "found MSN POST application/x-msn-messenger\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - } - /* now test for http login that uses a gateway, at least 400 a bytes packet */ - /* for this case the asymmetric detection is asym (1) */ - if(packet->payload_packet_len > 400) { - if(( - packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP || - (memcmp(packet->payload, "POST ", 5) == 0))) { - u_int16_t c; - if(memcmp(&packet->payload[5], "http://", 7) == 0) { - /* - * We are searching for a pattern "POST http://gateway.messenger.hotmail.com/gateway/gateway.dll" or - * "POST http:///gateway/gateway.dll" - * POST http:// is 12 byte so we are searching for 13 to 70 byte for this paten. - */ - for(c = 13; c < 50; c++) { - if(memcmp(&packet->payload[c], "/", 1) == 0) { - if(memcmp(&packet->payload[c], "/gateway/gateway.dll", 20) == 0) { - NDPI_LOG_DBG2(ndpi_struct, "found pattern http://.../gateway/gateway.ddl\n"); - status = 1; - break; - } - } - } - } else if((memcmp(&packet->payload[5], "/gateway/gateway.dll", 20) == 0)) { - NDPI_LOG_DBG2(ndpi_struct, "found pattern http://.../gateway/gateway.ddl\n"); - status = 1; - } - } - if(status) { - u_int16_t a; - - ndpi_parse_packet_line_info(ndpi_struct, flow); - - if(packet->content_line.ptr != NULL && ((packet->content_line.len == 23 - && memcmp(packet->content_line.ptr, "text/xml; charset=utf-8", 23) == 0) - || - (packet->content_line.len == 24 - && memcmp(packet->content_line.ptr, "text/html; charset=utf-8", 24) == 0) - || - (packet->content_line.len == 33 - && memcmp(packet->content_line.ptr, "application/x-www-form-urlencoded", 33) == 0))) { - - if ((src != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_MSN) != 0) - || (dst != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_MSN) != 0)) { - - NDPI_LOG_INFO(ndpi_struct, "found MSN with pattern text/xml; charset=utf-8\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - for(a = 0; a < packet->parsed_lines; a++) { - if(packet->line[a].len >= 4 && (memcmp(packet->line[a].ptr, "CVR ", 4) == 0 - || memcmp(packet->line[a].ptr, "VER ", 4) == 0 || - memcmp(packet->line[a].ptr, "ANS ", 4) == 0)) { - - NDPI_LOG_DBG2(ndpi_struct, "found MSN with pattern text/sml; charset0utf-8\n"); - NDPI_LOG_INFO(ndpi_struct, "found MSN xml CVS / VER / ANS found\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - } - } - } - /* asym (1) ; possibly occurs in symmetric cases also. */ - if(flow->packet_counter <= 10 && - (flow->packet_direction_counter[0] <= 2 || flow->packet_direction_counter[1] <= 2) - && packet->payload_packet_len > 100) { - /* not necessary to check the length, because this has been done : >400. */ - if( - packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP || - ndpi_match_strprefix(packet->payload, packet->payload_packet_len, "HTTP/1.0 200 OK") || - ndpi_match_strprefix(packet->payload, packet->payload_packet_len, "HTTP/1.1 200 OK") - ) { - - ndpi_parse_packet_line_info(ndpi_struct, flow); - - if(packet->content_line.ptr != NULL && - ((packet->content_line.len == NDPI_STATICSTRING_LEN("application/x-msn-messenger") && - memcmp(packet->content_line.ptr, "application/x-msn-messenger", NDPI_STATICSTRING_LEN("application/x-msn-messenger")) == 0) || - (packet->content_line.len >= NDPI_STATICSTRING_LEN("text/x-msnmsgr") && - memcmp(packet->content_line.ptr, "text/x-msnmsgr", NDPI_STATICSTRING_LEN("text/x-msnmsgr")) == 0))) { - - NDPI_LOG_INFO(ndpi_struct, - "found MSN application/x-msn-messenger.\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - if(ndpi_int_find_xmsn(ndpi_struct, flow) == 1) { - NDPI_LOG_INFO(ndpi_struct, "found MSN X-MSN\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - } - /* did not find any trace with this pattern */ - /* now block proxy connection */ - if(packet->payload_packet_len >= 42) { - if(memcmp(packet->payload, "CONNECT messenger.hotmail.com:1863 HTTP/1.", 42) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found MSN with pattern CONNECT messenger.hotmail.com:1863 HTTP/1.\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - if (packet->payload_packet_len >= 18) { - - if (memcmp(packet->payload, "USR ", 4) == 0 || memcmp(packet->payload, "ANS ", 4) == 0) { - /* now we must see a number */ - const u_int16_t endlen = packet->payload_packet_len - 12; - plen = 4; - while (1) { - if (packet->payload[plen] == ' ') { - break; - } - if (packet->payload[plen] < '0' || packet->payload[plen] > '9') { - goto ndpi_msn_exclude; - } - plen++; - if (plen >= endlen) { - goto ndpi_msn_exclude; - } - } - - while (plen < endlen) { - if (ndpi_check_for_email_address(ndpi_struct, flow, plen) != 0) { - NDPI_LOG_DBG2(ndpi_struct, "found mail address\n"); - break; - } - if (packet->payload_packet_len > plen + 1 - && (packet->payload[plen] < 20 || packet->payload[plen] > 128)) { - goto ndpi_msn_exclude; - } - plen++; - if (plen >= endlen) { - goto ndpi_msn_exclude; - } - } - NDPI_LOG_INFO(ndpi_struct, "found MSN with pattern USR/ANS ...mail_address\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - } - /* finished examining the first packet only. */ - - /* asym (1) ; possibly occurs in symmetric cases also. */ - if(flow->packet_counter <= 10 && - (flow->packet_direction_counter[0] <= 2 || flow->packet_direction_counter[1] <= 2) && - packet->payload_packet_len > 100) { - /* not necessary to check the length, because this has been done : >400. */ - if( - packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP || - (memcmp(packet->payload, "HTTP/1.0 200 OK", 15) == 0) || - (memcmp(packet->payload, "HTTP/1.1 200 OK", 15) == 0)) { - - ndpi_parse_packet_line_info(ndpi_struct, flow); - - if(packet->content_line.ptr != NULL && ((packet->content_line.len == NDPI_STATICSTRING_LEN("application/x-msn-messenger") && - memcmp(packet->content_line.ptr, "application/x-msn-messenger", - NDPI_STATICSTRING_LEN("application/x-msn-messenger")) == 0) || - (packet->content_line.len >= NDPI_STATICSTRING_LEN("text/x-msnmsgr") && - memcmp(packet->content_line.ptr, "text/x-msnmsgr", NDPI_STATICSTRING_LEN("text/x-msnmsgr")) == 0))) { - - NDPI_LOG_INFO(ndpi_struct, "found MSN application/x-msn-messenger\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - if(ndpi_int_find_xmsn(ndpi_struct, flow) == 1) { - NDPI_LOG_INFO(ndpi_struct, "found MSN X-MSN\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - } - /* finished examining the second packet only */ - /* direct user connection (file transfer,...) */ - - if((src != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_MSN) != 0) - || (dst != NULL - && NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_MSN) != 0)) { - if (flow->packet_counter == 1 && - packet->payload_packet_len > 12 && memcmp(packet->payload, "recipientid=", 12) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found MSN file transfer\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - return; - } - } - /* MSN File Transfer of MSN 8.1 and 8.5 - * first packet with length 4 and pattern 0x04000000 - * second packet (in the same direction), with length 56 and pattern 0x00000000 from payload[16] - * third packet (in the opposite direction to 1 & 2), with length 4 and pattern 0x30000000 - */ - if(flow->l4.tcp.msn_stage == 0) { - /* asymmetric detection to this pattern is asym (2) */ - if((packet->payload_packet_len == 4 || packet->payload_packet_len == 8) - && get_u_int32_t(packet->payload, 0) == htonl(0x04000000)) { - - NDPI_LOG_DBG2(ndpi_struct, "maybe first TCP MSN detected\n"); - - if(packet->payload_packet_len == 8 && get_u_int32_t(packet->payload, 4) == htonl(0x666f6f00)) { - flow->l4.tcp.msn_stage = 5 + packet->packet_direction; - return; - } - flow->l4.tcp.msn_stage = 1 + packet->packet_direction; - return; - } - /* asymmetric detection to this pattern is asym (2) */ - } else if (flow->l4.tcp.msn_stage == 1 + packet->packet_direction) { - if (packet->payload_packet_len > 10 && get_u_int32_t(packet->payload, 0) == htonl(0x666f6f00)) { - ndpi_int_msn_add_connection(ndpi_struct, flow); - NDPI_LOG_INFO(ndpi_struct, "found MSN File Transfer 1\n"); - return; - } - /* did not see this pattern in any trace */ - if (packet->payload_packet_len == 56 && get_u_int32_t(packet->payload, 16) == 0) { - NDPI_LOG_DBG2(ndpi_struct, "maybe Second TCP MSN detected\n"); - flow->l4.tcp.msn_stage = 3 + packet->packet_direction; - return; - } - - - } else if (flow->l4.tcp.msn_stage == 2 - packet->packet_direction - && packet->payload_packet_len == 4 && get_u_int32_t(packet->payload, 0) == htonl(0x30000000)) { - ndpi_int_msn_add_connection(ndpi_struct, flow); - NDPI_LOG_INFO(ndpi_struct, "found MSN File Transfer 2\n"); - return; - } else if ((flow->l4.tcp.msn_stage == 3 + packet->packet_direction) - || (flow->l4.tcp.msn_stage == 4 - packet->packet_direction)) { - if (packet->payload_packet_len == 4 && get_u_int32_t(packet->payload, 0) == htonl(0x30000000)) { - ndpi_int_msn_add_connection(ndpi_struct, flow); - NDPI_LOG_INFO(ndpi_struct, "found MSN File Transfer 2\n"); - return; - } - } else if (flow->l4.tcp.msn_stage == 6 - packet->packet_direction) { - if ((packet->payload_packet_len == 4) && - (get_u_int32_t(packet->payload, 0) == htonl(0x10000000) || get_u_int32_t(packet->payload, 0) == htonl(0x30000000))) { - ndpi_int_msn_add_connection(ndpi_struct, flow); - NDPI_LOG_INFO(ndpi_struct, "found MSN File Transfer 3\n"); - return; - } - } else if (flow->l4.tcp.msn_stage == 5 + packet->packet_direction) { - if ((packet->payload_packet_len == 20) && get_u_int32_t(packet->payload, 0) == htonl(0x10000000)) { - ndpi_int_msn_add_connection(ndpi_struct, flow); - NDPI_LOG_INFO(ndpi_struct, "found MSN File Transfer 3\n"); - return; - } - } - NDPI_LOG_DBG(ndpi_struct, "msn 7\n"); - - if (flow->packet_counter <= MAX_PACKETS_FOR_MSN) { - if (packet->payload_packet_len >=4 && (memcmp(&packet->payload[0], "MSG ", 4) == 0 - || memcmp(&packet->payload[0], "PNG", 3) == 0 - || memcmp(&packet->payload[0], "QNG ", 4) == 0 - || memcmp(&packet->payload[0], "OUT", 3) == 0 - || memcmp(&packet->payload[0], "RNG ", 4) == 0 - || memcmp(&packet->payload[0], "NLN ", 4) == 0 - || memcmp(&packet->payload[0], "UBX ", 4) == 0 - || memcmp(&packet->payload[0], "XFR ", 4) == 0) - ){ - ndpi_int_msn_add_connection(ndpi_struct, flow); - - NDPI_LOG_INFO(ndpi_struct, "found MSN\n"); - return; - } - } - ndpi_msn_exclude: - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); -} - -/* search over UDP */ -static void ndpi_search_msn_udp_misc(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - - struct ndpi_id_struct *src = flow->src; - struct ndpi_id_struct *dst = flow->dst; - - - /* do we have an msn login ? */ - if ((src == NULL || NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_MSN) == 0) - && (dst == NULL - || NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_MSN) == 0)) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; - } - - /* asymmetric ft detection works */ - if (packet->payload_packet_len == 20 - && get_u_int32_t(packet->payload, 4) == 0 && packet->payload[9] == 0 - && get_u_int16_t(packet->payload, 10) == htons(0x0100)) { - NDPI_LOG_INFO(ndpi_struct, "found MSN udp misc data connection\n"); - ndpi_int_msn_add_connection(ndpi_struct, flow); - } - - /* asymmetric detection working. */ - return; - //} -} - - -void ndpi_search_msn(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - - NDPI_LOG_DBG(ndpi_struct, "search msn\n"); - /* this if request should always be true */ - if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_MSN) == 0) { - /* we deal with tcp now */ - if(packet->tcp != NULL) { - /* msn can use http or ssl for connection. That's why every http, ssl and ukn packet must enter in the msn detection */ - /* the detection can switch out the http or the ssl detection. In this case we need not check those protocols */ - // need to do the ceck when protocol == http too (POST /gateway ...) - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN - || packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP - || packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS - || packet->detected_protocol_stack[0] == NDPI_PROTOCOL_STUN - ) - ndpi_search_msn_tcp(ndpi_struct, flow); - } else if (packet->udp != NULL) { - ndpi_search_msn_udp_misc(ndpi_struct, flow); - } - } -} - - -void init_msn_dissector(struct ndpi_detection_module_struct *ndpi_struct, - u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - - NDPI_BITMASK_RESET(ndpi_struct->callback_buffer[*id].excluded_protocol_bitmask); - - ndpi_set_bitmask_protocol_detection("MSN", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_MSN, - ndpi_search_msn, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} - diff --git a/src/lib/protocols/mssql_tds.c b/src/lib/protocols/mssql_tds.c index d54704f..06da375 100644 --- a/src/lib/protocols/mssql_tds.c +++ b/src/lib/protocols/mssql_tds.c @@ -1,7 +1,7 @@ /* * mssql.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -51,7 +51,12 @@ void ndpi_search_mssql_tds(struct ndpi_detection_module_struct *ndpi_struct, str NDPI_LOG_DBG(ndpi_struct, "search mssql_tds\n"); - if(packet->payload_packet_len < sizeof(struct tds_packet_header)) { + if((packet->payload_packet_len < sizeof(struct tds_packet_header)) + /* + The TPKT protocol used by ISO 8072 (on port 102) is similar + to this potocol and it can cause false positives + */ + || (packet->tcp->dest == ntohs(102))) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } diff --git a/src/lib/protocols/mysql.c b/src/lib/protocols/mysql.c index 948d330..aa95d46 100644 --- a/src/lib/protocols/mysql.c +++ b/src/lib/protocols/mysql.c @@ -2,7 +2,7 @@ * mysql.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -40,7 +40,7 @@ void ndpi_search_mysql_tcp(struct ndpi_detection_module_struct *ndpi_struct, str && get_u_int8_t(packet->payload, 2) == 0x00 //3rd byte of packet length && get_u_int8_t(packet->payload, 3) == 0x00 //packet sequence number is 0 for startup packet && get_u_int8_t(packet->payload, 5) > 0x30 //server version > 0 - && get_u_int8_t(packet->payload, 5) < 0x37 //server version < 7 + && get_u_int8_t(packet->payload, 5) < 0x39 //server version < 9 && get_u_int8_t(packet->payload, 6) == 0x2e //dot ) { #if 0 diff --git a/src/lib/protocols/nats.c b/src/lib/protocols/nats.c new file mode 100644 index 0000000..0254a02 --- /dev/null +++ b/src/lib/protocols/nats.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#include "ndpi_protocol_ids.h" +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_NATS +#include "ndpi_api.h" + +static const char* commands[] = + { + "INFO {", + "CONNECT {", + "PUB ", + "SUB", + "UNSUB ", + "MSG ", + "PING", + "PONG", + "+OK", + "-ERR", + NULL + }; + +void ndpi_search_nats_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + + /* Check connection over TCP */ + NDPI_LOG_DBG(ndpi_struct, "search NATS\n"); + + if(packet->tcp && (packet->payload_packet_len > 4)) { + int i; + + for(i=0; commands[i] != NULL; i++) { + char *match = ndpi_strnstr((const char *)flow->packet.payload, + commands[i], + flow->packet.payload_packet_len); + + if(!match) continue; + + if(ndpi_strnstr((const char *)match, "\r\n", + flow->packet.payload_packet_len - ((size_t)match - (size_t)flow->packet.payload)) != NULL) { + NDPI_LOG_INFO(ndpi_struct, "found NATS\n"); + + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_NATS, NDPI_PROTOCOL_UNKNOWN); + return; + } + } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + } +} + + + +void init_nats_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { + ndpi_set_bitmask_protocol_detection("Nats", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_NATS, + ndpi_search_nats_tcp, + NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); + *id += 1; +} diff --git a/src/lib/protocols/nest_log_sink.c b/src/lib/protocols/nest_log_sink.c index 6732964..347841d 100644 --- a/src/lib/protocols/nest_log_sink.c +++ b/src/lib/protocols/nest_log_sink.c @@ -2,7 +2,7 @@ * nest_log_sink.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2018 - eGloo Incorporated * * This file is part of nDPI, an open source deep packet inspection diff --git a/src/lib/protocols/netbios.c b/src/lib/protocols/netbios.c index fd0e579..1f3850c 100644 --- a/src/lib/protocols/netbios.c +++ b/src/lib/protocols/netbios.c @@ -1,8 +1,8 @@ /* * netbios.c * + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . - * + * */ @@ -29,32 +29,35 @@ #include "ndpi_api.h" +/* ****************************************************************** */ struct netbios_header { u_int16_t transaction_id, flags, questions, answer_rrs, authority_rrs, additional_rrs; }; +/* ****************************************************************** */ + /* The function below has been inherited by tcpdump */ -int ndpi_netbios_name_interpret(char *in, char *out, u_int out_len) { - int ret = 0, len; +int ndpi_netbios_name_interpret(char *in, size_t inlen, char *out, u_int out_len) { + int ret = 0, len, idx = inlen; char *b; - + len = (*in++)/2; b = out; - *out=0; + *out = 0; + + if((len > (out_len-1)) || (len < 1) || ((2*len) > inlen)) + return(-1); - if(len > (out_len-1) || len < 1) - return(-1); - - while (len--) { - if(in[0] < 'A' || in[0] > 'P' || in[1] < 'A' || in[1] > 'P') { + while(len--) { + if((idx < 2) || (in[0] < 'A') || (in[0] > 'P') || (in[1] < 'A') || (in[1] > 'P')) { *out = 0; break; } - *out = ((in[0]-'A')<<4) + (in[1]-'A'); - - in += 2; + *out = ((in[0] - 'A') << 4) + (in[1] - 'A'); + + in += 2, idx -= 2; if(isprint(*out)) out++, ret++; @@ -69,26 +72,40 @@ int ndpi_netbios_name_interpret(char *in, char *out, u_int out_len) { return(ret); } +/* ****************************************************************** */ -static void ndpi_int_netbios_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_NETBIOS, NDPI_PROTOCOL_UNKNOWN); +static void ndpi_int_netbios_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + u_int16_t sub_protocol) { + char name[64]; + u_int off = flow->packet.payload[12] == 0x20 ? 12 : 14; + + if((off < flow->packet.payload_packet_len) + && ndpi_netbios_name_interpret((char*)&flow->packet.payload[off], + flow->packet.payload_packet_len - off, name, sizeof(name)) > 0) { + snprintf((char*)flow->host_server_name, sizeof(flow->host_server_name)-1, "%s", name); + + ndpi_check_dga_name(ndpi_struct, flow, (char*)flow->host_server_name, 1); + } + + if(sub_protocol == NDPI_PROTOCOL_UNKNOWN) + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_NETBIOS, NDPI_PROTOCOL_UNKNOWN); + else + ndpi_set_detected_protocol(ndpi_struct, flow, sub_protocol, NDPI_PROTOCOL_NETBIOS); } +/* ****************************************************************** */ -void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ +void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; u_int16_t dport; - char name[64]; - + NDPI_LOG_DBG(ndpi_struct, "search netbios\n"); + if(packet->udp != NULL) { dport = ntohs(packet->udp->dest); - /*check standard NETBIOS over udp to port 137 */ if((dport == 137 || 0) && packet->payload_packet_len >= 50) { struct netbios_header h; @@ -97,221 +114,222 @@ void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, struc h.transaction_id = ntohs(h.transaction_id), h.flags = ntohs(h.flags), h.questions = ntohs(h.questions), h.answer_rrs = ntohs(h.answer_rrs), h.authority_rrs = ntohs(h.authority_rrs), h.additional_rrs = ntohs(h.additional_rrs); - + NDPI_LOG_DBG(ndpi_struct, "found netbios port 137 and payload_packet_len 50\n"); if(h.flags == 0 && - h.questions == 1 && - h.answer_rrs == 0 && - h.authority_rrs == 0 && h.additional_rrs == 0) { + h.questions == 1 && + h.answer_rrs == 0 && + h.authority_rrs == 0 && h.additional_rrs == 0) { NDPI_LOG_INFO(ndpi_struct, "found netbios with questions = 1 and answers = 0, authority = 0 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } + if(((h.flags & 0x8710) == 0x10) && - h.questions == 1 && - h.answer_rrs == 0 && - h.authority_rrs == 0) { + h.questions == 1 && + h.answer_rrs == 0 && + h.authority_rrs == 0) { NDPI_LOG_INFO(ndpi_struct, "found netbios with questions = 1 and answers = 0, authority = 0 and broadcast \n"); - - if(ndpi_netbios_name_interpret((char*)&packet->payload[12], name, sizeof(name)) > 0) { - if(!ndpi_struct->disable_metadata_export) { - snprintf((char*)flow->host_server_name, sizeof(flow->host_server_name)-1, "%s", name); - } - } - - ndpi_int_netbios_add_connection(ndpi_struct, flow); + + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } + if(packet->payload[2] == 0x80 && - h.questions == 1 && - h.answer_rrs == 0 && - h.authority_rrs == 0 && h.additional_rrs == 1) { + h.questions == 1 && + h.answer_rrs == 0 && + h.authority_rrs == 0 && h.additional_rrs == 1) { NDPI_LOG_INFO(ndpi_struct, "found netbios with questions = 1 and answers, authority, additional = 0 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } + if(h.flags == 0x4000 && - h.questions == 1 && - h.answer_rrs == 0 && - h.authority_rrs == 0 && h.additional_rrs == 1) { + h.questions == 1 && + h.answer_rrs == 0 && + h.authority_rrs == 0 && h.additional_rrs == 1) { NDPI_LOG_INFO(ndpi_struct, "found netbios with questions = 1 and answers = 0, authority = 0 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } + if(h.flags == 0x8400 && - h.questions == 0 && - h.answer_rrs == 1 && - h.authority_rrs == 0 && h.additional_rrs == 0) { + h.questions == 0 && + h.answer_rrs == 1 && + h.authority_rrs == 0 && h.additional_rrs == 0) { NDPI_LOG_INFO(ndpi_struct, - "found netbios with flag 8400 questions = 0 and answers = 1, authority, additional = 0 \n"); + "found netbios with flag 8400 questions = 0 and answers = 1, authority, additional = 0 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } + if(h.flags == 0x8500 && - h.questions == 0 && - h.answer_rrs == 1 && - h.authority_rrs == 0 && h.additional_rrs == 0) { + h.questions == 0 && + h.answer_rrs == 1 && + h.authority_rrs == 0 && h.additional_rrs == 0) { NDPI_LOG_INFO(ndpi_struct, - "found netbios with flag 8500 questions = 0 and answers = 1, authority, additional = 0 \n"); + "found netbios with flag 8500 questions = 0 and answers = 1, authority, additional = 0 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } - if(h.flags == 0x2910 && - h.questions == 1 && - h.answer_rrs == 0 && - h.authority_rrs == 0 && h.additional_rrs == 1) { + + if(((h.flags == 0x2900) || (h.flags == 0x2910)) && + h.questions == 1 && + h.answer_rrs == 0 && + h.authority_rrs == 0 && h.additional_rrs == 1) { NDPI_LOG_INFO(ndpi_struct, - "found netbios with flag 2910, questions = 1 and answers, authority=0, additional = 1 \n"); + "found netbios with flag 2910, questions = 1 and answers, authority=0, additional = 1 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } + if(h.flags == 0xAD86 && - h.questions == 0 && - h.answer_rrs == 1 && - h.authority_rrs == 0 && h.additional_rrs == 0) { + h.questions == 0 && + h.answer_rrs == 1 && + h.authority_rrs == 0 && h.additional_rrs == 0) { NDPI_LOG_INFO(ndpi_struct, - "found netbios with flag ad86 questions = 0 and answers = 1, authority, additional = 0 \n"); + "found netbios with flag ad86 questions = 0 and answers = 1, authority, additional = 0 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } + if(h.flags == 0x0110 && - h.questions == 1 && - h.answer_rrs == 0 && - h.authority_rrs == 0 && h.additional_rrs == 0) { + h.questions == 1 && + h.answer_rrs == 0 && + h.authority_rrs == 0 && h.additional_rrs == 0) { NDPI_LOG_INFO(ndpi_struct, - "found netbios with flag 0110 questions = 1 and answers = 0, authority, additional = 0 \n"); + "found netbios with flag 0110 questions = 1 and answers = 0, authority, additional = 0 \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } if((h.flags & 0xf800) == 0) { - NDPI_LOG_DBG2(ndpi_struct, "possible netbios name query request\n"); if(get_u_int16_t(packet->payload, 4) == htons(1) && - get_u_int16_t(packet->payload, 6) == 0 && - get_u_int16_t(packet->payload, 8) == 0 && get_u_int16_t(packet->payload, 10) == 0) { + get_u_int16_t(packet->payload, 6) == 0 && + get_u_int16_t(packet->payload, 8) == 0 && get_u_int16_t(packet->payload, 10) == 0) { /* name is encoded as described in rfc883 */ u_int8_t name_length = packet->payload[12]; NDPI_LOG_DBG2(ndpi_struct, - "possible netbios name query request, one question\n"); + "possible netbios name query request, one question\n"); if(packet->payload_packet_len == 12 + 1 + name_length + 1 + 2 + 2) { NDPI_LOG_DBG2(ndpi_struct, - "possible netbios name query request, length matches\n"); + "possible netbios name query request, length matches\n"); /* null terminated? */ if(packet->payload[12 + name_length + 1] == 0 && - get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x0020) && - get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { + get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x0020) && + get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { NDPI_LOG_INFO(ndpi_struct, - "found netbios name query request\n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + "found netbios name query request\n"); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } } } } else if((h.flags & 0xf800) == 0x8000) { NDPI_LOG_DBG2(ndpi_struct, - "possible netbios name query response\n"); + "possible netbios name query response\n"); if(get_u_int16_t(packet->payload, 4) == 0 && - get_u_int16_t(packet->payload, 6) == htons(1) && - get_u_int16_t(packet->payload, 8) == 0 && get_u_int16_t(packet->payload, 10) == 0) { + get_u_int16_t(packet->payload, 6) == htons(1) && + get_u_int16_t(packet->payload, 8) == 0 && get_u_int16_t(packet->payload, 10) == 0) { /* name is encoded as described in rfc883 */ u_int8_t name_length = packet->payload[12]; NDPI_LOG_DBG2(ndpi_struct, - "possible netbios positive name query response, one answer\n"); + "possible netbios positive name query response, one answer\n"); if(packet->payload_packet_len >= 12 + 1 + name_length + 1 + 2 + 2) { NDPI_LOG_DBG2(ndpi_struct, - "possible netbios name query response, length matches\n"); + "possible netbios name query response, length matches\n"); /* null terminated? */ if(packet->payload[12 + name_length + 1] == 0 && - get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x0020) && - get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { + get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x0020) && + get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { NDPI_LOG_INFO(ndpi_struct, - "found netbios name query response\n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + "found netbios name query response\n"); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } } } else if(get_u_int16_t(packet->payload, 4) == 0 && - get_u_int16_t(packet->payload, 6) == 0 && - get_u_int16_t(packet->payload, 8) == 0 && get_u_int16_t(packet->payload, 10) == 0) { + get_u_int16_t(packet->payload, 6) == 0 && + get_u_int16_t(packet->payload, 8) == 0 && get_u_int16_t(packet->payload, 10) == 0) { /* name is encoded as described in rfc883 */ u_int8_t name_length = packet->payload[12]; NDPI_LOG_DBG2(ndpi_struct, - "possible netbios negative name query response, one answer\n"); + "possible netbios negative name query response, one answer\n"); if(packet->payload_packet_len >= 12 + 1 + name_length + 1 + 2 + 2) { NDPI_LOG_DBG2(ndpi_struct, - "possible netbios name query response, length matches\n"); + "possible netbios name query response, length matches\n"); /* null terminated? */ if(packet->payload[12 + name_length + 1] == 0 && - get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x000A) && - get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { + get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x000A) && + get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { NDPI_LOG_INFO(ndpi_struct, - "found netbios name query response\n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + "found netbios name query response\n"); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } } } else if(get_u_int16_t(packet->payload, 4) == 0 && - get_u_int16_t(packet->payload, 6) == 0 && - get_u_int16_t(packet->payload, 8) == htons(1) && get_u_int16_t(packet->payload, 10) == htons(1)) { + get_u_int16_t(packet->payload, 6) == 0 && + get_u_int16_t(packet->payload, 8) == htons(1) && get_u_int16_t(packet->payload, 10) == htons(1)) { /* name is encoded as described in rfc883 */ u_int8_t name_length = packet->payload[12]; NDPI_LOG_DBG2(ndpi_struct, - "possible netbios redirect name query response, one answer\n"); + "possible netbios redirect name query response, one answer\n"); if(packet->payload_packet_len >= 12 + 1 + name_length + 1 + 2 + 2) { NDPI_LOG_DBG2(ndpi_struct, - "possible netbios name query response, length matches\n"); + "possible netbios name query response, length matches\n"); /* null terminated? */ if(packet->payload[12 + name_length + 1] == 0 && - get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x0002) && - get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { + get_u_int16_t(packet->payload, 12 + name_length + 2) == htons(0x0002) && + get_u_int16_t(packet->payload, 12 + name_length + 4) == htons(0x0001)) { NDPI_LOG_INFO(ndpi_struct, - "found netbios name query response\n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + "found netbios name query response\n"); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } } @@ -320,30 +338,29 @@ void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, struc /* TODO: extend according to rfc1002 */ } - /*check standard NETBIOS over udp to port 138 */ + /* check standard NETBIOS over udp to port 138 */ - /*netbios header token from http://www.protocolbase.net/protocols/protocol_NBDGM.php */ + /* netbios header token from http://www.protocolbase.net/protocols/protocol_NBDGM.php */ - if((dport == 138) && - packet->payload_packet_len >= 14 && - ntohs(get_u_int16_t(packet->payload, 10)) == packet->payload_packet_len - 14) { + if((dport == 138) && (packet->payload_packet_len >= 14)) { + u_int16_t netbios_len = ntohs(get_u_int16_t(packet->payload, 10)); - NDPI_LOG_DBG2(ndpi_struct, "found netbios port 138 and payload length >= 112 \n"); + if(netbios_len == packet->payload_packet_len - 14) { + NDPI_LOG_DBG2(ndpi_struct, "found netbios port 138 and payload length >= 112 \n"); - if(packet->payload[0] >= 0x10 && packet->payload[0] <= 0x16) { - NDPI_LOG_DBG2(ndpi_struct, "found netbios with MSG-type 0x10,0x11,0x12,0x13,0x14,0x15 or 0x16\n"); + if(packet->payload[0] >= 0x10 && packet->payload[0] <= 0x16) { + u_int32_t source_ip = ntohl(get_u_int32_t(packet->payload, 4)); - if(ntohl(get_u_int32_t(packet->payload, 4)) == ntohl(packet->iph->saddr)) { - NDPI_LOG_INFO(ndpi_struct, "found netbios with checked ip-address\n"); + NDPI_LOG_DBG2(ndpi_struct, "found netbios with MSG-type 0x10,0x11,0x12,0x13,0x14,0x15 or 0x16\n"); - if(ndpi_netbios_name_interpret((char*)&packet->payload[12], name, sizeof(name)) > 0) { - if(!ndpi_struct->disable_metadata_export) { - snprintf((char*)flow->host_server_name, sizeof(flow->host_server_name)-1, "%s", name); - } + if(source_ip == ntohl(packet->iph->saddr)) { + int16_t leftover = netbios_len - 82; /* NetBIOS len */ + + NDPI_LOG_INFO(ndpi_struct, "found netbios with checked ip-address\n"); + + ndpi_int_netbios_add_connection(ndpi_struct, flow, (leftover > 0) ? NDPI_PROTOCOL_SMBV1 : NDPI_PROTOCOL_UNKNOWN); + return; } - - ndpi_int_netbios_add_connection(ndpi_struct, flow); - return; } } } @@ -354,7 +371,6 @@ void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, struc /* destination port must be 139 */ if(dport == 139) { - NDPI_LOG_DBG2(ndpi_struct, "found netbios with destination port 139\n"); /* payload_packet_len must be 72 */ @@ -363,9 +379,9 @@ void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, struc if(packet->payload[0] == 0x81 && packet->payload[1] == 0 && ntohs(get_u_int16_t(packet->payload, 2)) == 68) { NDPI_LOG_INFO(ndpi_struct, - "found netbios with session request = 81, flags=0 and length od following bytes = 68. \n"); + "found netbios with session request = 81, flags=0 and length od following bytes = 68. \n"); - ndpi_int_netbios_add_connection(ndpi_struct, flow); + ndpi_int_netbios_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNKNOWN); return; } } @@ -376,6 +392,8 @@ void ndpi_search_netbios(struct ndpi_detection_module_struct *ndpi_struct, struc NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } +/* ****************************************************************** */ + void init_netbios_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("NETBIOS", ndpi_struct, detection_bitmask, *id, diff --git a/src/lib/protocols/netflow.c b/src/lib/protocols/netflow.c index 74ba882..ef9125b 100644 --- a/src/lib/protocols/netflow.c +++ b/src/lib/protocols/netflow.c @@ -1,7 +1,7 @@ /* * netflow.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -117,19 +117,24 @@ void ndpi_search_netflow(struct ndpi_detection_module_struct *ndpi_struct, struc case 5: case 7: case 9: - if((n == 0) || (n > 30)) + if((n == 0) || (n > 30)) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; - + } + switch(version) { case 1: expected_len = n * sizeof(struct flow_ver1_rec) + 16 /* header */; break; + case 5: expected_len = n * sizeof(struct flow_ver5_rec) + 24 /* header */; break; + case 7: expected_len = n * sizeof(struct flow_ver7_rec) + 24 /* header */; break; + case 9: /* We need to check the template */ break; @@ -142,16 +147,21 @@ void ndpi_search_netflow(struct ndpi_detection_module_struct *ndpi_struct, struc uptime_offset = 8; break; + case 10: /* IPFIX */ { u_int16_t ipfix_len = n; - if(ipfix_len != payload_len) + if(ipfix_len != payload_len) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; + } } uptime_offset = 4; break; + default: + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } @@ -167,7 +177,8 @@ void ndpi_search_netflow(struct ndpi_detection_module_struct *ndpi_struct, struc ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_NETFLOW, NDPI_PROTOCOL_UNKNOWN); return; } - } + } else + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_netflow_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) diff --git a/src/lib/protocols/nfs.c b/src/lib/protocols/nfs.c index c767fea..01030ab 100644 --- a/src/lib/protocols/nfs.c +++ b/src/lib/protocols/nfs.c @@ -2,7 +2,7 @@ * nfs.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/noe.c b/src/lib/protocols/noe.c index da572b6..f830d31 100644 --- a/src/lib/protocols/noe.c +++ b/src/lib/protocols/noe.c @@ -2,6 +2,23 @@ * noe.c (Alcatel new office environment) * * Copyright (C) 2013 Remy Mudingay + * Copyright (C) 2011-20 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . * */ @@ -47,9 +64,9 @@ void ndpi_search_noe(struct ndpi_detection_module_struct *ndpi_struct, ndpi_int_noe_add_connection(ndpi_struct, flow); return; } - } else { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - } + } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } diff --git a/src/lib/protocols/non_tcp_udp.c b/src/lib/protocols/non_tcp_udp.c index 712fc48..bc2aa54 100644 --- a/src/lib/protocols/non_tcp_udp.c +++ b/src/lib/protocols/non_tcp_udp.c @@ -2,7 +2,7 @@ * non_tcp_udp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ntp.c b/src/lib/protocols/ntp.c index a03ed3b..58465e8 100644 --- a/src/lib/protocols/ntp.c +++ b/src/lib/protocols/ntp.c @@ -2,7 +2,7 @@ * ntp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -58,8 +58,8 @@ void ndpi_search_ntp_udp(struct ndpi_detection_module_struct *ndpi_struct, struc return; } } - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } diff --git a/src/lib/protocols/openft.c b/src/lib/protocols/openft.c index 893a24a..9d81c07 100644 --- a/src/lib/protocols/openft.c +++ b/src/lib/protocols/openft.c @@ -2,7 +2,7 @@ * openft.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/openvpn.c b/src/lib/protocols/openvpn.c index 568e40c..076a681 100644 --- a/src/lib/protocols/openvpn.c +++ b/src/lib/protocols/openvpn.c @@ -1,8 +1,31 @@ /* * openvpn.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#include "ndpi_protocol_ids.h" + +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_OPENVPN + +#include "ndpi_api.h" + +/* * OpenVPN TCP / UDP Detection - 128/160 hmac * * Detection based upon these openvpn protocol properties: @@ -21,13 +44,6 @@ * */ -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_OPENVPN - -#include "ndpi_api.h" - - #define P_CONTROL_HARD_RESET_CLIENT_V1 (0x01 << 3) #define P_CONTROL_HARD_RESET_CLIENT_V2 (0x07 << 3) #define P_CONTROL_HARD_RESET_SERVER_V1 (0x02 << 3) @@ -72,11 +88,12 @@ void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct, u_int8_t alen; int8_t hmac_size; int8_t failed = 0; - - if(packet->payload_packet_len >= 40) { + /* No u_ */int16_t ovpn_payload_len = packet->payload_packet_len; + + if(ovpn_payload_len >= 40) { // skip openvpn TCP transport packet size if(packet->tcp != NULL) - ovpn_payload += 2; + ovpn_payload += 2, ovpn_payload_len -= 2;; opcode = ovpn_payload[0] & P_OPCODE_MASK; @@ -85,16 +102,16 @@ void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct, printf("[packet_id: %u][opcode: %u][Packet ID: %d][%u <-> %u][len: %u]\n", flow->num_processed_pkts, opcode, check_pkid_and_detect_hmac_size(ovpn_payload), - htons(packet->udp->source), htons(packet->udp->dest), packet->payload_packet_len); + htons(packet->udp->source), htons(packet->udp->dest), ovpn_payload_len); #endif if( (flow->num_processed_pkts == 1) && ( - ((packet->payload_packet_len == 112) + ((ovpn_payload_len == 112) && ((opcode == 168) || (opcode == 192)) ) - || ((packet->payload_packet_len == 80) + || ((ovpn_payload_len == 80) && ((opcode == 184) || (opcode == 88) || (opcode == 160) || (opcode == 168) || (opcode == 200))) )) { NDPI_LOG_INFO(ndpi_struct,"found openvpn\n"); @@ -104,35 +121,46 @@ void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct, } if(flow->ovpn_counter < P_HARD_RESET_CLIENT_MAX_COUNT && (opcode == P_CONTROL_HARD_RESET_CLIENT_V1 || - opcode == P_CONTROL_HARD_RESET_CLIENT_V2)) { + opcode == P_CONTROL_HARD_RESET_CLIENT_V2)) { if(check_pkid_and_detect_hmac_size(ovpn_payload) > 0) { memcpy(flow->ovpn_session_id, ovpn_payload+1, 8); NDPI_LOG_DBG2(ndpi_struct, - "session key: %02x%02x%02x%02x%02x%02x%02x%02x\n", - flow->ovpn_session_id[0], flow->ovpn_session_id[1], flow->ovpn_session_id[2], flow->ovpn_session_id[3], - flow->ovpn_session_id[4], flow->ovpn_session_id[5], flow->ovpn_session_id[6], flow->ovpn_session_id[7]); + "session key: %02x%02x%02x%02x%02x%02x%02x%02x\n", + flow->ovpn_session_id[0], flow->ovpn_session_id[1], flow->ovpn_session_id[2], flow->ovpn_session_id[3], + flow->ovpn_session_id[4], flow->ovpn_session_id[5], flow->ovpn_session_id[6], flow->ovpn_session_id[7]); } } else if(flow->ovpn_counter >= 1 && flow->ovpn_counter <= P_HARD_RESET_CLIENT_MAX_COUNT && - (opcode == P_CONTROL_HARD_RESET_SERVER_V1 || opcode == P_CONTROL_HARD_RESET_SERVER_V2)) { + (opcode == P_CONTROL_HARD_RESET_SERVER_V1 || opcode == P_CONTROL_HARD_RESET_SERVER_V2)) { hmac_size = check_pkid_and_detect_hmac_size(ovpn_payload); if(hmac_size > 0) { - alen = ovpn_payload[P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size)]; - session_remote = ovpn_payload + P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size) + 1 + alen * 4; - - if(memcmp(flow->ovpn_session_id, session_remote, 8) == 0) { - NDPI_LOG_INFO(ndpi_struct,"found openvpn\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN); - return; - } else { - NDPI_LOG_DBG2(ndpi_struct, - "key mismatch: %02x%02x%02x%02x%02x%02x%02x%02x\n", - session_remote[0], session_remote[1], session_remote[2], session_remote[3], - session_remote[4], session_remote[5], session_remote[6], session_remote[7]); + u_int16_t offset = P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size); + + alen = ovpn_payload[offset]; + + if (alen > 0) { + offset += 1 + alen * 4; + + if((offset+8) <= ovpn_payload_len) { + session_remote = &ovpn_payload[offset]; + + if(memcmp(flow->ovpn_session_id, session_remote, 8) == 0) { + NDPI_LOG_INFO(ndpi_struct,"found openvpn\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OPENVPN, NDPI_PROTOCOL_UNKNOWN); + return; + } else { + NDPI_LOG_DBG2(ndpi_struct, + "key mismatch: %02x%02x%02x%02x%02x%02x%02x%02x\n", + session_remote[0], session_remote[1], session_remote[2], session_remote[3], + session_remote[4], session_remote[5], session_remote[6], session_remote[7]); + failed = 1; + } + } else + failed = 1; + } else failed = 1; - } } else failed = 1; } else @@ -140,10 +168,12 @@ void ndpi_search_openvpn(struct ndpi_detection_module_struct* ndpi_struct, flow->ovpn_counter++; - if(failed) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - } + if(failed) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } + + if(flow->packet_counter > 5) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_openvpn_dissector(struct ndpi_detection_module_struct *ndpi_struct, diff --git a/src/lib/protocols/oracle.c b/src/lib/protocols/oracle.c index 6ad6bac..a24837a 100644 --- a/src/lib/protocols/oracle.c +++ b/src/lib/protocols/oracle.c @@ -43,7 +43,7 @@ void ndpi_search_oracle(struct ndpi_detection_module_struct *ndpi_struct, struct NDPI_LOG_DBG2(ndpi_struct, "calculating ORACLE over tcp\n"); /* Oracle Database 9g,10g,11g */ if ((dport == 1521 || sport == 1521) - && (((packet->payload[0] == 0x07) && (packet->payload[1] == 0xff) && (packet->payload[2] == 0x00)) + && (((packet->payload_packet_len >= 3 && packet->payload[0] == 0x07) && (packet->payload[1] == 0xff) && (packet->payload[2] == 0x00)) || ((packet->payload_packet_len >= 232) && ((packet->payload[0] == 0x00) || (packet->payload[0] == 0x01)) && (packet->payload[1] != 0x00) && (packet->payload[2] == 0x00) diff --git a/src/lib/protocols/oscar.c b/src/lib/protocols/oscar.c deleted file mode 100644 index 535e513..0000000 --- a/src/lib/protocols/oscar.c +++ /dev/null @@ -1,816 +0,0 @@ -/* - * oscar.c - * - * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ - -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_OSCAR - -#include "ndpi_api.h" - -#define FLAPVERSION 0x00000001 - -/* Flap channels */ -#define SIGNON 0x01 -#define DATA 0x02 -#define O_ERROR 0x03 -#define SIGNOFF 0x04 -#define KEEP_ALIVE 0x05 - -/* Signon tags */ -#define SCREEN_NAME 0x0001 -#define PASSWD 0x0002 -#define CLIENT_NAME 0x0003 -#define BOS 0x0005 -#define LOGIN_COOKIE 0x0006 -#define MAJOR_VERSION 0x0017 -#define MINOR_VERSION 0x0018 -#define POINT_VERSION 0x0019 -#define BUILD_NUM 0x001a -#define MULTICONN_FLAGS 0x004a -#define CLIENT_LANG 0x00OF -#define CLIENT_CNTRY 0x00OE -#define CLIENT_RECONNECT 0x0094 - -/* Family */ -#define GE_SE_CTL 0x0001 -#define LOC_SRV 0x0002 -#define BUDDY_LIST 0x0003 -#define IM 0x0004 -#define IS 0x0006 -#define ACC_ADM 0x0007 -#define POPUP 0x0008 -#define PMS 0x0009 -#define USS 0x000b -#define CHAT_ROOM_SETUP 0x000d -#define CHAT_ROOM_ACT 0x000e -#define USER_SRCH 0x000f -#define BUDDY_ICON_SERVER 0x0010 -#define SERVER_STORED_INFO 0x0013 -#define ICQ 0x0015 -#define INIT_AUTH 0x0017 -#define EMAIL 0x0018 -#define IS_EXT 0x0085 - - -static void ndpi_int_oscar_add_connection(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) -{ - - struct ndpi_packet_struct *packet = &flow->packet; - struct ndpi_id_struct *src = flow->src; - struct ndpi_id_struct *dst = flow->dst; - - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_OSCAR, NDPI_PROTOCOL_UNKNOWN); - - if (src != NULL) { - src->oscar_last_safe_access_time = packet->tick_timestamp; - } - if (dst != NULL) { - dst->oscar_last_safe_access_time = packet->tick_timestamp; - } -} - -/** - Oscar connection work on FLAP protocol. - - FLAP is a low-level communications protocol that facilitates the development of higher-level, datagram-oriented, communications layers. - It is used on the TCP connection between all clients and servers. - Here is format of FLAP datagram -**/ -static void ndpi_search_oscar_tcp_connect(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - - int excluded = 0; -// u_int8_t channel; - u_int16_t family; - u_int16_t type; - u_int16_t flag; - u_int32_t req_ID; - - struct ndpi_packet_struct * packet = &flow->packet; - - struct ndpi_id_struct * src = flow->src; - struct ndpi_id_struct * dst = flow->dst; - - /* FLAP__Header - * - * [ 6 byte FLAP header ] - * +-----------+--------------+-------------+--------------+ - * | 0x2a (1B) | Channel (1B) | SeqNum (2B) | PyldLen (2B) | - * +-----------+--------------+-------------+--------------+ - * - * [ 4 byte of data ] - * - * */ - if (packet->payload_packet_len >= 6 && packet->payload[0] == 0x2a) - { - - /* FLAP__FRAME_TYPE (Channel)*/ - u_int8_t channel = get_u_int8_t(packet->payload, 1); - - /* - Initialize the FLAP connection. - - SIGNON -> FLAP__SIGNON_FRAME - +--------------------------------------------------+ - + FLAP__Header | 6 byte + - + FlapVersion | 4 byte (Always 1 = 0x00000001) + - + TLVs | [Class: FLAP__SIGNON_TAGS] TLVs + - +--------------------------------------------------+ - */ - if (channel == SIGNON && - get_u_int16_t(packet->payload, 4) == htons(packet->payload_packet_len - 6) && - get_u_int32_t(packet->payload, 6) == htonl(FLAPVERSION)) - { - - /* No TLVs */ - if(packet->payload_packet_len == 10) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Sign In \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* /\* SCREEN_NAME *\/ */ - /* if (get_u_int16_t(packet->payload, 10) == htons(SCREEN_NAME)) /\* packet->payload[10] == 0x00 && packet->payload[11] == 0x01 *\/ */ - /* { */ - /* NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Screen Name \n"); */ - /* ndpi_int_oscar_add_connection(ndpi_struct, flow); */ - /* return; */ - /* } */ - /* /\* PASSWD *\/ */ - /* if (get_u_int16_t(packet->payload, 10) == htons(PASSWD)) /\* packet->payload[10] == 0x00 && packet->payload[11] == 0x02 *\/ */ - /* { */ - /* NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Password (roasted) \n"); */ - /* ndpi_int_oscar_add_connection(ndpi_struct, flow); */ - /* return; */ - /* } */ - /* CLIENT_NAME */ - if (get_u_int16_t(packet->payload, 10) == htons(CLIENT_NAME)) /* packet->payload[10] == 0x00 && packet->payload[11] == 0x03 */ - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Client Name \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* LOGIN_COOKIE */ - if (get_u_int16_t(packet->payload, 10) == htons(LOGIN_COOKIE) && - get_u_int16_t(packet->payload, 12) == htons(0x0100)) - { - if(get_u_int16_t(packet->payload, packet->payload_packet_len - 5) == htons(MULTICONN_FLAGS)) /* MULTICONN_FLAGS */ - { - if(get_u_int16_t(packet->payload, packet->payload_packet_len - 3) == htons(0x0001)) - if((get_u_int8_t(packet->payload, packet->payload_packet_len - 1) == 0x00) || - (get_u_int8_t(packet->payload, packet->payload_packet_len - 1) == 0x01) || - (get_u_int8_t(packet->payload, packet->payload_packet_len - 1) == 0x03)) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Login \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - } - /* MAJOR_VERSION */ - if (get_u_int16_t(packet->payload, 10) == htons(MAJOR_VERSION)) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Major_Version \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* MINOR_VERSION */ - if (get_u_int16_t(packet->payload, 10) == htons(MINOR_VERSION)) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Minor_Version \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* POINT_VERSION */ - if (get_u_int16_t(packet->payload, 10) == htons(POINT_VERSION)) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Point_Version \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* BUILD_NUM */ - if (get_u_int16_t(packet->payload, 10) == htons(BUILD_NUM)) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Build_Num \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* CLIENT_RECONNECT */ - if (get_u_int16_t(packet->payload, 10) == htons(CLIENT_RECONNECT)) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Client_Reconnect \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - - /* - Messages using the FLAP connection, usually a SNAC message. - - DATA -> FLAP__DATA_FRAME - +-------------------------+ - + FLAP__Header | 6 byte + - + SNAC__Header | 10 byte + - + snac | + - +-------------------------+ - - SNAC__Header - +----------------------------------------------+ - + ID | 4 byte (2 foodgroup + 2 type) + - + FLAGS | 2 byte + - + requestId | 4 byte + - +----------------------------------------------+ - */ - if (channel == DATA) - { - if (packet->payload_packet_len >= 8) - family = get_u_int16_t(packet->payload, 6); - else - family = 0; - if (packet->payload_packet_len >= 10) - type = get_u_int16_t(packet->payload, 8); - else - type = 0; - if (family == 0 || type == 0) - { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; - } - - /* Family 0x0001 */ - if (family == htons(GE_SE_CTL)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - case (0x000a): break; - case (0x000b): break; - case (0x000c): break; - case (0x000d): break; - case (0x000e): break; - case (0x000f): break; - case (0x0010): break; - case (0x0011): break; - case (0x0012): break; - case (0x0013): break; - case (0x0014): break; - case (0x0015): break; - case (0x0016): break; - case (0x0017): break; - case (0x0018): break; - case (0x001e): break; - case (0x001f): break; - case (0x0020): break; - case (0x0021): break; - default: excluded = 1; - } - } - /* Family 0x0002 */ - if (family == htons(LOC_SRV)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - case (0x000a): break; - case (0x000b): break; - case (0x000c): break; - case (0x000f): break; - case (0x0010): break; - case (0x0015): break; - default: excluded = 1; - } - } - /* Family 0x0003 */ - if (family == htons(BUDDY_LIST)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - case (0x000a): break; - case (0x000b): break; - case (0x000c): break; - default: excluded = 1; - } - } - /* Family 0x0004 */ - if (family == htons(IM)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - case (0x000a): break; - case (0x000b): break; - case (0x000c): break; - case (0x0014): break; - default: excluded = 1; - } - } - /* Family 0x0006 */ - if (family == htons(IS)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - default: excluded = 1; - } - } - /* Family 0x0007 */ - if (family == htons(ACC_ADM)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - default: excluded = 1; - } - } - /* Family 0x0008 */ - if (family == htons(POPUP)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - default: excluded = 1; - } - } - /* Family 0x0009 */ - if (family == htons(PMS)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - case (0x000a): break; - case (0x000b): break; - default: excluded = 1; - } - } - /* Family 0x000b */ - if (family == htons(USS)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - default: excluded = 1; - } - } - /* Family 0x000d */ - if (family == htons(CHAT_ROOM_SETUP)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - default: excluded = 1; - } - } - /* Family 0x000e */ - if (family == htons(CHAT_ROOM_ACT)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - default: excluded = 1; - } - } - /* Family 0x000f */ - if (family == htons(USER_SRCH)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - default: excluded = 1; - } - } - /* Family 0x0010 */ - if (family == htons(BUDDY_ICON_SERVER)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - default: excluded = 1; - } - } - /* Family 0x0013 */ - if (family == htons(SERVER_STORED_INFO)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x0008): break; - case (0x0009): break; - case (0x000a): break; - case (0x000e): break; - case (0x000f): break; - case (0x0011): break; - case (0x0012): break; - case (0x0014): break; - case (0x0015): break; - case (0x0016): break; - case (0x0018): break; - case (0x001a): break; - case (0x001b): break; - case (0x001c): break; - default: excluded = 1; - } - } - /* Family 0x0015 */ - if (family == htons(ICQ)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - default: excluded = 1; - } - } - /* Family 0x0017 */ - if (family == htons(INIT_AUTH)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - case (0x0004): break; - case (0x0005): break; - case (0x0006): break; - case (0x0007): break; - case (0x000a): break; - case (0x000b): break; - default: excluded = 1; - } - } - /* Family 0x0018 */ - if (family == htons(EMAIL)) - { - /* TODO */ - } - /* Family 0x0085 */ - if (family == htons(IS_EXT)) - { - switch (type) { - - case (0x0001): break; - case (0x0002): break; - case (0x0003): break; - default: excluded = 1; - } - } - - if(excluded == 1) - { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - } - - /* flag */ - if (packet->payload_packet_len >= 12) - { - flag = get_u_int16_t(packet->payload, 10); - if (flag == htons(0x0000)|| flag == htons(0x8000) || flag == htons(0x0001)) - { - if (packet->payload_packet_len >= 16) - { - /* request ID */ - req_ID = get_u_int32_t(packet->payload, 12); - if((req_ID <= ((u_int32_t)-1))) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - } - } - } - /* - ERROR -> FLAP__ERROR_CHANNEL_0x03 - A FLAP error - rare - */ - if (channel == O_ERROR) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Error frame \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* - Close down the FLAP connection gracefully. - SIGNOFF: FLAP__SIGNOFF_CHANNEL_0x04 - */ - if (channel == SIGNOFF) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Signoff frame \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - /* - Send a heartbeat to server to help keep connection open. - KEEP_ALIVE: FLAP__KEEP_ALIVE_CHANNEL_0x05 - */ - if (channel == KEEP_ALIVE) - { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR - Keep Alive frame \n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - - - /* detect http connections */ - if (packet->payload_packet_len >= 18) { - if ((packet->payload[0] == 'P') && (memcmp(packet->payload, "POST /photo/upload", 18) == 0)) { - NDPI_PARSE_PACKET_LINE_INFO(ndpi_struct, flow, packet); - if (packet->host_line.len >= 18 && packet->host_line.ptr != NULL) { - if (memcmp(packet->host_line.ptr, "lifestream.aol.com", 18) == 0) { - NDPI_LOG_INFO(ndpi_struct, - "found OSCAR over HTTP, POST method\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - } - } - if (packet->payload_packet_len > 40) { - if ((packet->payload[0] == 'G') && (memcmp(packet->payload, "GET /", 5) == 0)) { - if ((memcmp(&packet->payload[5], "aim/fetchEvents?aimsid=", 23) == 0) || - (memcmp(&packet->payload[5], "aim/startSession?", 17) == 0) || - (memcmp(&packet->payload[5], "aim/gromit/aim_express", 22) == 0) || - (memcmp(&packet->payload[5], "b/ss/aolwpaim", 13) == 0) || - (memcmp(&packet->payload[5], "hss/storage/aimtmpshare", 23) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR over HTTP, GET /aim/\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - - if ((memcmp(&packet->payload[5], "aim", 3) == 0) || (memcmp(&packet->payload[5], "im", 2) == 0)) { - NDPI_PARSE_PACKET_LINE_INFO(ndpi_struct, flow, packet); - if (packet->user_agent_line.len > 15 && packet->user_agent_line.ptr != NULL && - ((memcmp(packet->user_agent_line.ptr, "mobileAIM/", 10) == 0) || - (memcmp(packet->user_agent_line.ptr, "ICQ/", 4) == 0) || - (memcmp(packet->user_agent_line.ptr, "mobileICQ/", 10) == 0) || - (memcmp(packet->user_agent_line.ptr, "AIM%20Free/", NDPI_STATICSTRING_LEN("AIM%20Free/")) == 0) || - (memcmp(packet->user_agent_line.ptr, "AIM/", 4) == 0))) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR over HTTP\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - NDPI_PARSE_PACKET_LINE_INFO(ndpi_struct, flow, packet); - if (packet->referer_line.ptr != NULL && packet->referer_line.len >= 22) { - - if (memcmp(&packet->referer_line.ptr[packet->referer_line.len - NDPI_STATICSTRING_LEN("WidgetMain.swf")], - "WidgetMain.swf", NDPI_STATICSTRING_LEN("WidgetMain.swf")) == 0) { - u_int16_t i; - for (i = 0; i < (packet->referer_line.len - 22); i++) { - if (packet->referer_line.ptr[i] == 'a') { - if (memcmp(&packet->referer_line.ptr[i + 1], "im/gromit/aim_express", 21) == 0) { - NDPI_LOG_INFO(ndpi_struct, - "found OSCAR over HTTP : aim/gromit/aim_express\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - } - } - } - } - if (memcmp(packet->payload, "CONNECT ", 8) == 0) { - if (memcmp(packet->payload, "CONNECT login.icq.com:443 HTTP/1.", 33) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR ICQ-HTTP\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - if (memcmp(packet->payload, "CONNECT login.oscar.aol.com:5190 HTTP/1.", 40) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR AIM-HTTP\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - - } - } - - if (packet->payload_packet_len > 43 - && memcmp(packet->payload, "GET http://http.proxy.icq.com/hello HTTP/1.", 43) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR ICQ-HTTP PROXY\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - - if (packet->payload_packet_len > 46 - && memcmp(packet->payload, "GET http://aimhttp.oscar.aol.com/hello HTTP/1.", 46) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR AIM-HTTP PROXY\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - - if (packet->payload_packet_len > 5 && get_u_int32_t(packet->payload, 0) == htonl(0x05010003)) { - NDPI_LOG_DBG2(ndpi_struct, "Maybe OSCAR Picturetransfer\n"); - return; - } - - if (packet->payload_packet_len == 10 && get_u_int32_t(packet->payload, 0) == htonl(0x05000001) && - get_u_int32_t(packet->payload, 4) == 0) { - NDPI_LOG_DBG2(ndpi_struct, "Maybe OSCAR Picturetransfer\n"); - return; - } - - if (packet->payload_packet_len >= 70 && - memcmp(&packet->payload[packet->payload_packet_len - 26], - "\x67\x00\x65\x00\x74\x00\x43\x00\x61\x00\x74\x00\x61\x00\x6c\x00\x6f\x00\x67", 19) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR PICTURE TRANSFER\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - - if (NDPI_SRC_OR_DST_HAS_PROTOCOL(src, dst, NDPI_PROTOCOL_OSCAR) != 0) { - - if (flow->packet_counter == 1 - && - ((packet->payload_packet_len == 9 - && memcmp(packet->payload, "\x00\x09\x00\x00\x83\x01\xc0\x00\x00", 9) == 0) - || (packet->payload_packet_len == 13 - && (memcmp(packet->payload, "\x00\x0d\x00\x87\x01\xc0", 6) == 0 - || memcmp(packet->payload, "\x00\x0d\x00\x87\x01\xc1", 6) == 0)))) { - flow->oscar_video_voice = 1; - } - if (flow->oscar_video_voice && ntohs(get_u_int16_t(packet->payload, 0)) == packet->payload_packet_len - && packet->payload[2] == 0x00 && packet->payload[3] == 0x00) { - } - - if (packet->payload_packet_len >= 70 && ntohs(get_u_int16_t(packet->payload, 4)) == packet->payload_packet_len) { - if (memcmp(packet->payload, "OFT", 3) == 0 && - ((packet->payload[3] == '3' && ((memcmp(&packet->payload[4], "\x01\x00\x01\x01", 4) == 0) - || (memcmp(&packet->payload[6], "\x01\x01\x00", 3) == 0))) - || (packet->payload[3] == '2' && ((memcmp(&packet->payload[6], "\x01\x01", 2) - == 0) - )))) { - // FILE TRANSFER PATTERN:: OFT3 or OFT2 - NDPI_LOG_INFO(ndpi_struct, "found OSCAR FILE TRANSFER\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - - if (memcmp(packet->payload, "ODC2", 4) == 0 && memcmp(&packet->payload[6], "\x00\x01\x00\x06", 4) == 0) { - //PICTURE TRANSFER PATTERN EXMAPLE:: - //4f 44 43 32 00 4c 00 01 00 06 00 00 00 00 00 00 ODC2.L.......... - NDPI_LOG_INFO(ndpi_struct, "found OSCAR PICTURE TRANSFER\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - return; - } - } - if (packet->payload_packet_len > 40 && (memcmp(&packet->payload[2], "\x04\x4a\x00", 3) == 0) - && (memcmp(&packet->payload[6], "\x00\x00", 2) == 0) - && packet->payload[packet->payload_packet_len - 15] == 'F' - && packet->payload[packet->payload_packet_len - 12] == 'L' - && (memcmp(&packet->payload[packet->payload_packet_len - 6], "DEST", 4) == 0) - && (memcmp(&packet->payload[packet->payload_packet_len - 2], "\x00\x00", 2) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR PICTURE TRANSFER\n"); - ndpi_int_oscar_add_connection(ndpi_struct, flow); - if (ntohs(packet->tcp->dest) == 443 || ntohs(packet->tcp->source) == 443) { - flow->oscar_ssl_voice_stage = 1; - } - return; - - } - } - if (flow->packet_counter < 3 && packet->payload_packet_len > 11 && (memcmp(packet->payload, "\x00\x37\x04\x4a", 4) - || memcmp(packet->payload, "\x00\x0a\x04\x4a", - 4))) { - return; - } - - - if (packet->detected_protocol_stack[0] != NDPI_PROTOCOL_OSCAR) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; - } -} - -void ndpi_search_oscar(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - NDPI_LOG_DBG(ndpi_struct, "search OSCAR\n"); - if (packet->tcp != NULL) { - ndpi_search_oscar_tcp_connect(ndpi_struct, flow); - } -} - - -void init_oscar_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("Oscar", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_OSCAR, - ndpi_search_oscar, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} diff --git a/src/lib/protocols/pando.c b/src/lib/protocols/pando.c deleted file mode 100644 index ece7855..0000000 --- a/src/lib/protocols/pando.c +++ /dev/null @@ -1,167 +0,0 @@ -/* - * pando.c - * - * Copyright (C) 2014 Tomasz Bujlow - * - * The signature is based on the Libprotoident library. - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ - -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_PANDO - -#include "ndpi_api.h" - -static void ndpi_int_pando_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_PANDO, NDPI_PROTOCOL_UNKNOWN); -} - -static void ndpi_check_pando_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int32_t payload_len = packet->payload_packet_len; - - if (ndpi_match_strprefix(packet->payload, payload_len, "\x0ePan")) { - NDPI_LOG_INFO(ndpi_struct, "Found PANDO\n"); - ndpi_int_pando_add_connection(ndpi_struct, flow); - } -} - -static void ndpi_check_pando_udp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int32_t payload_len = packet->payload_packet_len; - - /* Check if we so far detected the protocol in the request or not. */ - NDPI_LOG_DBG2(ndpi_struct, "PANDO stage %u: \n", flow->pando_stage); - if (flow->pando_stage == 0) { - - if ((payload_len >= 4) && (packet->payload[0] == 0x00) && (packet->payload[1] == 0x00) && (packet->payload[2] == 0x00) && (packet->payload[3] == 0x09)) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PANDO request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pando_stage = packet->packet_direction + 1; // packet_direction 0: stage 1, packet_direction 1: stage 2 - return; - } - - if (ndpi_match_strprefix(packet->payload, payload_len, "UDPA")) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PANDO request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pando_stage = packet->packet_direction + 3; // packet_direction 0: stage 3, packet_direction 1: stage 4 - return; - } - - if (ndpi_match_strprefix(packet->payload, payload_len, "UDPR") || ndpi_match_strprefix(packet->payload, payload_len, "UDPE")) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PANDO request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pando_stage = packet->packet_direction + 5; // packet_direction 0: stage 5, packet_direction 1: stage 6 - return; - } - - } else if ((flow->pando_stage == 1) || (flow->pando_stage == 2)) { - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pando_stage - packet->packet_direction) == 1) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if ((payload_len == 0) || ((payload_len >= 4) && (packet->payload[0] == 0x00) && (packet->payload[1] == 0x00) && (packet->payload[2] == 0x00) && (packet->payload[3] == 0x09))) { - NDPI_LOG_INFO(ndpi_struct, "found PANDO\n"); - ndpi_int_pando_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PANDO, resetting the stage to 0..\n"); - flow->pando_stage = 0; - } - - } else if ((flow->pando_stage == 3) || (flow->pando_stage == 4)) { - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pando_stage - packet->packet_direction) == 3) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if ((payload_len == 0) || (ndpi_match_strprefix(packet->payload, payload_len, "UDPR") || ndpi_match_strprefix(packet->payload, payload_len, "UDPE"))) { - NDPI_LOG_INFO(ndpi_struct, "found PANDO\n"); - ndpi_int_pando_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PANDO, resetting the stage to 0..\n"); - flow->pando_stage = 0; - } - - } else if ((flow->pando_stage == 5) || (flow->pando_stage == 6)) { - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pando_stage - packet->packet_direction) == 5) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if (ndpi_match_strprefix(packet->payload, payload_len, "UDPA")) { - NDPI_LOG_INFO(ndpi_struct, "found PANDO\n"); - ndpi_int_pando_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PANDO, resetting the stage to 0\n"); - flow->pando_stage = 0; - } - } -} - -void ndpi_search_pando(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - - NDPI_LOG_DBG(ndpi_struct, "search PANDO\n"); - /* Break after 20 packets. */ - if (flow->packet_counter > 20) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; - } - - /* skip marked or retransmitted packets */ - if (packet->tcp_retransmission != 0) { - return; - } - - if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_PANDO) { - return; - } - - ndpi_check_pando_tcp(ndpi_struct, flow); - - if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_PANDO) { - return; - } - - ndpi_check_pando_udp(ndpi_struct, flow); -} - - -void init_pando_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("Pando_Media_Booster", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_PANDO, - ndpi_search_pando, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITHOUT_RETRANSMISSION, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} diff --git a/src/lib/protocols/pcanywhere.c b/src/lib/protocols/pcanywhere.c deleted file mode 100644 index 7851b48..0000000 --- a/src/lib/protocols/pcanywhere.c +++ /dev/null @@ -1,67 +0,0 @@ -/* - * pcanywhere.c - * - * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ - - -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_PCANYWHERE - -#include "ndpi_api.h" - - -static void ndpi_int_pcanywhere_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_PCANYWHERE, NDPI_PROTOCOL_UNKNOWN); -} - -void ndpi_search_pcanywhere(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - - if (packet->udp != NULL && packet->udp->dest == htons(5632) - && packet->payload_packet_len == 2 - && (memcmp(packet->payload, "NQ", 2) == 0 || memcmp(packet->payload, "ST", 2) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "PC Anywhere name or status query detected\n"); - ndpi_int_pcanywhere_add_connection(ndpi_struct, flow); - return; - } - - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); -} - - -void init_pcanywhere_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("PcAnywhere", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_PCANYWHERE, - ndpi_search_pcanywhere, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} - diff --git a/src/lib/protocols/postgres.c b/src/lib/protocols/postgres.c index 23767ef..a51faba 100644 --- a/src/lib/protocols/postgres.c +++ b/src/lib/protocols/postgres.c @@ -2,7 +2,7 @@ * postgres.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -97,7 +97,7 @@ void ndpi_search_postgres_tcp(struct ndpi_detection_module_struct return; } size = (u_int16_t)ntohl(get_u_int32_t(packet->payload, 1)) + 1; - if (packet->payload[size - 1] == 'S') { + if (size > 0 && size - 1 < packet->payload_packet_len && packet->payload[size - 1] == 'S') { if ((size + get_u_int32_t(packet->payload, (size + 1))) == packet->payload_packet_len) { NDPI_LOG_INFO(ndpi_struct, "found postgres asymmetrically\n"); ndpi_int_postgres_add_connection(ndpi_struct, flow); @@ -105,7 +105,7 @@ void ndpi_search_postgres_tcp(struct ndpi_detection_module_struct } } size += get_u_int32_t(packet->payload, (size + 1)) + 1; - if (packet->payload[size - 1] == 'S') { + if (size > 0 && size - 1 < packet->payload_packet_len && packet->payload[size - 1] == 'S') { NDPI_LOG_INFO(ndpi_struct, "found postgres asymmetrically\n"); ndpi_int_postgres_add_connection(ndpi_struct, flow); return; diff --git a/src/lib/protocols/pplive.c b/src/lib/protocols/pplive.c deleted file mode 100644 index 2e41d64..0000000 --- a/src/lib/protocols/pplive.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * pplive.c - * - * Copyright (C) 2014 Tomasz Bujlow - * - * The signature is mostly based on the Libprotoident library - * except the detection of HTTP Steam flows. - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ - -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_PPLIVE - -#include "ndpi_api.h" - -static void ndpi_int_pplive_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_PPLIVE, NDPI_PROTOCOL_UNKNOWN); -} - -static void ndpi_check_pplive_udp1(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int32_t payload_len = packet->payload_packet_len; - - /* Check if we so far detected the protocol in the request or not. */ - if (flow->pplive_stage1 == 0) { - NDPI_LOG_DBG2(ndpi_struct, "PPLIVE stage 0: \n"); - - if (ndpi_match_strprefix(packet->payload, payload_len, "\xe9\x03\x41\x01")) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PPLIVE request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pplive_stage1 = packet->packet_direction + 1; // packet_direction 0: stage 1, packet_direction 1: stage 2 - return; - } - - if (ndpi_match_strprefix(packet->payload, payload_len, "\xe9\x03\x42\x01")) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PPLIVE request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pplive_stage1 = packet->packet_direction + 3; // packet_direction 0: stage 3, packet_direction 1: stage 4 - return; - } - - if (ndpi_match_strprefix(packet->payload, payload_len, "\x1c\x1c\x32\x01")) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PPLIVE request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pplive_stage1 = packet->packet_direction + 5; // packet_direction 0: stage 5, packet_direction 1: stage 6 - return; - } - - } else if ((flow->pplive_stage1 == 1) || (flow->pplive_stage1 == 2)) { - NDPI_LOG_DBG2(ndpi_struct, "PPLIVE stage %u: \n", flow->pplive_stage1); - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pplive_stage1 - packet->packet_direction) == 1) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if (ndpi_match_strprefix(packet->payload, payload_len, "\xe9\x03\x42\x01") || ndpi_match_strprefix(packet->payload, payload_len, "\xe9\x03\x41\x01")) { - NDPI_LOG_DBG2(ndpi_struct, "Found PPLIVE\n"); - ndpi_int_pplive_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PPLIVE, resetting the stage to 0..\n"); - flow->pplive_stage1 = 0; - } - - } else if ((flow->pplive_stage1 == 3) || (flow->pplive_stage1 == 4)) { - NDPI_LOG_DBG2(ndpi_struct, "PPLIVE stage %u: \n", flow->pplive_stage1); - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pplive_stage1 - packet->packet_direction) == 3) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if (ndpi_match_strprefix(packet->payload, payload_len, "\xe9\x03\x41\x01")) { - NDPI_LOG_INFO(ndpi_struct, "found PPLIVE\n"); - ndpi_int_pplive_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PPLIVE, resetting the stage to 0..\n"); - flow->pplive_stage1 = 0; - } - } else if ((flow->pplive_stage1 == 5) || (flow->pplive_stage1 == 6)) { - NDPI_LOG_DBG2(ndpi_struct, "PPLIVE stage %u: \n", flow->pplive_stage1); - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pplive_stage1 - packet->packet_direction) == 5) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if (ndpi_match_strprefix(packet->payload, payload_len, "\x1c\x1c\x32\x01")) { - NDPI_LOG_INFO(ndpi_struct, "Found PPLIVE\n"); - ndpi_int_pplive_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PPLIVE, resetting the stage to 0..\n"); - flow->pplive_stage1 = 0; - } - } - -} - -static void ndpi_check_pplive_udp2(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int32_t payload_len = packet->payload_packet_len; - - /* Check if we so far detected the protocol in the request or not. */ - NDPI_LOG_DBG2(ndpi_struct, "PPLIVE stage %u: \n", flow->pplive_stage2); - if (flow->pplive_stage2 == 0) { - - if ((payload_len == 57) && ndpi_match_strprefix(packet->payload, payload_len, "\xe9\x03\x41\x01")) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PPLIVE request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pplive_stage2 = packet->packet_direction + 1; // packet_direction 0: stage 1, packet_direction 1: stage 2 - } - - } else { - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pplive_stage2 - packet->packet_direction) == 1) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if (payload_len == 0) { - NDPI_LOG_INFO(ndpi_struct, "found PPLIVE\n"); - ndpi_int_pplive_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PPLIVE, resetting the stage to 0..\n"); - flow->pplive_stage2 = 0; - } - - } -} - -static void ndpi_check_pplive_udp3(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int32_t payload_len = packet->payload_packet_len; - - /* Check if we so far detected the protocol in the request or not. */ - NDPI_LOG_DBG(ndpi_struct, "PPLIVE stage %u: \n", flow->pplive_stage3); - if (flow->pplive_stage3 == 0) { - - if ((payload_len == 94) && (packet->udp->dest == htons(5041) || packet->udp->source == htons(5041) || packet->udp->dest == htons(8303) || packet->udp->source == htons(8303))) { - NDPI_LOG_DBG2(ndpi_struct, "Possible PPLIVE request detected, we will look further for the response..\n"); - - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->pplive_stage3 = packet->packet_direction + 1; // packet_direction 0: stage 1, packet_direction 1: stage 2 - return; - } - - } else { - - /* At first check, if this is for sure a response packet (in another direction. If not, do nothing now and return. */ - if ((flow->pplive_stage3 - packet->packet_direction) == 1) { - return; - } - - /* This is a packet in another direction. Check if we find the proper response. */ - if ((payload_len == 0) || (payload_len == 49) ||(payload_len == 94)) { - NDPI_LOG_INFO(ndpi_struct, "found PPLIVE\n"); - ndpi_int_pplive_add_connection(ndpi_struct, flow); - } else { - NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to PPLIVE, resetting the stage to 0..\n"); - flow->pplive_stage3 = 0; - } - } - -} - -void ndpi_search_pplive(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - - NDPI_LOG_DBG(ndpi_struct, "search PPLIVE\n"); - - /* Break after 20 packets. */ - if (flow->packet_counter > 20) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return; - } - - if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_PPLIVE) { - return; - } - - ndpi_check_pplive_udp1(ndpi_struct, flow); - - if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_PPLIVE) { - return; - } - - ndpi_check_pplive_udp2(ndpi_struct, flow); - - if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_PPLIVE) { - return; - } - - ndpi_check_pplive_udp3(ndpi_struct, flow); -} - - -void init_pplive_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("PPLive", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_PPLIVE, - ndpi_search_pplive, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} - diff --git a/src/lib/protocols/ppstream.c b/src/lib/protocols/ppstream.c index 0f0aadb..6985a7d 100644 --- a/src/lib/protocols/ppstream.c +++ b/src/lib/protocols/ppstream.c @@ -1,7 +1,7 @@ /* * ppstream.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/pptp.c b/src/lib/protocols/pptp.c index 300db5a..037d534 100644 --- a/src/lib/protocols/pptp.c +++ b/src/lib/protocols/pptp.c @@ -2,7 +2,7 @@ * pptp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/quic.c b/src/lib/protocols/quic.c index 86464dd..924b90b 100644 --- a/src/lib/protocols/quic.c +++ b/src/lib/protocols/quic.c @@ -1,7 +1,7 @@ /* * quic.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This module is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -16,10 +16,6 @@ * You should have received a copy of the GNU Lesser General Public License. * If not, see . * - * Based on code of: - * Andrea Buscarinu - - * Michele Campus - - * */ #if defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ @@ -27,39 +23,1197 @@ #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 +#endif +// #define DEBUG_CRYPT +// #define QUIC_DEBUG + +/* 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, uint32_t quic_version); +extern int http_process_user_agent(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + const u_int8_t *ua_ptr, u_int16_t ua_ptr_len); + +/* 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_T050 0x54303530 +#define V_T051 0x54303531 +#define V_MVFST_22 0xfaceb001 +#define V_MVFST_27 0xfaceb002 +#define V_MVFST_EXP 0xfaceb00e + +#define QUIC_MAX_CID_LENGTH 20 + +static int is_version_gquic(uint32_t version) +{ + return ((version & 0xFFFFFF00) == 0x54303500) /* T05X */ || + ((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_T050 || + version == V_T051 || + version == V_MVFST_22 || + version == V_MVFST_27 || + version == V_MVFST_EXP || + 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 */ || + ((version & 0xFFFFFF00) == 0x54303500) /* T05X */; +} +static int is_version_with_tls(uint32_t version) +{ + return is_version_quic(version) || + ((version & 0xFFFFFF00) == 0x54303500) /* T05X */; +} +int is_version_with_var_int_transport_params(uint32_t version) +{ + return (is_version_quic(version) && is_quic_ver_greater_than(version, 27)) || + (version == V_T051); +} -/* ***************************************************************** */ - -static int quic_len(u_int8_t l) { - switch(l) { +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: + *value = ndpi_ntohll(*(uint64_t *)buf) & 0x3FFFFFFFFFFFFFFF; + return 8; + default: /* No Possible */ + return 0; + } +} +int quic_len_buffer_still_required(uint8_t value) +{ + switch(value >> 6) { + case 0: + return 0; + case 1: + return 1; + case 2: + return 3; case 3: - return(8); + return 7; + 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 */ + + uint8_t c = offset / hash_len + 1; + gcry_md_write(h, &c, sizeof(c)); /* 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); */ + + 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)); + } + + /* 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 + }; + static const uint8_t hanshake_salt_draft_t50[20] = { + 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, + 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 + }; + static const uint8_t hanshake_salt_draft_t51[20] = { + 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, + 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d + }; + + 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(version == V_T050) { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t50, + sizeof(hanshake_salt_draft_t50), + cid, cid_len, secret); + } else if(version == V_T051) { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t51, + sizeof(hanshake_salt_draft_t51), + 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 || + version == V_MVFST_EXP) { + 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; + 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, offset; + + counter = 0; + while(counter < clear_payload_len && clear_payload[counter] == 0) + 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((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) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Unexpected stream ID version 0x%x\n", version); +#endif + 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 || version == V_T050 || version == V_T051) { + 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) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Unexpected frame 0x%x\n", first_nonzero_payload_byte); +#endif + } else { + NDPI_LOG_DBG(ndpi_struct, "Unexpected ACK/CC frame\n"); + } + 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], &offset); + if(offset != 0) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Unexpected crypto stream offset 0x%x\n", + offset); +#endif + return NULL; + } + counter += quic_len(&clear_payload[counter], crypto_data_len); + crypto_data = &clear_payload[counter]; } - return(0); /* NOTREACHED */ + if(*crypto_data_len + counter > clear_payload_len) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Invalid length %lu + %d > %d version 0x%x\n", + (unsigned long)*crypto_data_len, counter, clear_payload_len, version); +#endif + 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, + uint32_t version) +{ + 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, version); + + /* 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; + int sni_found = 0, ua_found = 0; + + if(crypto_data_len < 6) + return; + if(memcmp(crypto_data, "CHLO", 4) != 0) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Unexpected handshake message"); +#endif + 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); + sni_found = 1; + if (ua_found) + return; + } + + if(memcmp(tag, "UAID", 4) == 0) { + u_int uaid_offset = tag_offset_start + prev_offset; + + if((uaid_offset + len) < crypto_data_len) { + NDPI_LOG_DBG2(ndpi_struct, "UA: [%.*s]\n", len, &crypto_data[uaid_offset]); + + http_process_user_agent(ndpi_struct, flow, &crypto_data[uaid_offset], len); /* http.c */ + ua_found = 1; + + if (sni_found) + return; + } + } + + prev_offset = offset; + } + if(i != num_tags) + NDPI_LOG_DBG(ndpi_struct, "Something went wrong in tags iteration\n"); +} + + +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)) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Version 0x%x invalid flags 0x%x\n", *version, first_byte); +#endif + return 0; + } + if((*version == V_Q046) && + (pub_bit7 != 1 || pub_bit8 != 1)) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Q46 invalid flag 0x%x\n", first_byte); +#endif + 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; } /* ***************************************************************** */ @@ -67,97 +1221,86 @@ static int quic_len(u_int8_t l) { void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { - 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') - && (version_len == 1) - ) - quic_hlen = 18; /* TODO: Better handle Q046 */ - 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(packet->payload[quic_hlen+12] != 0xA0) - quic_hlen++; - } - - 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; ipayload[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])); - int len = offset-prev_offset; - int sni_offset = i+prev_offset+1; - - while((sni_offset < udp_len) && (packet->payload[sni_offset] == '-')) - sni_offset++; - - if((sni_offset+len) < udp_len) { - if(!ndpi_struct->disable_metadata_export) { - int 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; - } - } - } + 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)) { +#ifdef QUIC_DEBUG + NDPI_LOG_ERR(ndpi_struct, "Unsupported version 0x%x\n", version); +#endif + 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; } - no_quic: - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + /* + * 6) Process ClientHello/CHLO from the Crypto Data + */ + if(!is_version_with_tls(version)) { + process_chlo(ndpi_struct, flow, crypto_data, crypto_data_len); + } else { + process_tls(ndpi_struct, flow, crypto_data, crypto_data_len, version); + } + if(is_version_with_encrypted_header(version)) { + ndpi_free(clear_payload); + } } /* ***************************************************************** */ diff --git a/src/lib/protocols/radius.c b/src/lib/protocols/radius.c index b647677..4e2782d 100644 --- a/src/lib/protocols/radius.c +++ b/src/lib/protocols/radius.c @@ -1,7 +1,7 @@ /* * radius.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -40,11 +40,12 @@ static void ndpi_check_radius(struct ndpi_detection_module_struct *ndpi_struct, if(packet->udp != NULL) { struct radius_header *h = (struct radius_header*)packet->payload; /* RFC2865: The minimum length is 20 and maximum length is 4096. */ - if((payload_len < 20) || (payload_len > 4096)) - return; - - if((payload_len > sizeof(struct radius_header)) - && (h->code > 0) + if((payload_len < 20) || (payload_len > 4096)) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + + if((h->code > 0) && (h->code <= 13) && (ntohs(h->len) == payload_len)) { NDPI_LOG_INFO(ndpi_struct, "Found radius\n"); @@ -52,6 +53,7 @@ static void ndpi_check_radius(struct ndpi_detection_module_struct *ndpi_struct, return; } + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } diff --git a/src/lib/protocols/rdp.c b/src/lib/protocols/rdp.c index e766bc6..810cc9a 100644 --- a/src/lib/protocols/rdp.c +++ b/src/lib/protocols/rdp.c @@ -2,7 +2,7 @@ * rdp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/redis_net.c b/src/lib/protocols/redis_net.c index d1c3149..df56708 100644 --- a/src/lib/protocols/redis_net.c +++ b/src/lib/protocols/redis_net.c @@ -1,7 +1,7 @@ /* * redis.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/rtcp.c b/src/lib/protocols/rtcp.c index 77903d6..0e03ea8 100644 --- a/src/lib/protocols/rtcp.c +++ b/src/lib/protocols/rtcp.c @@ -45,7 +45,7 @@ void ndpi_search_rtcp(struct ndpi_detection_module_struct *ndpi_struct, len = packet->payload[2+offset] * 256 + packet->payload[2+offset+1]; rtcp_section_len = (len + 1) * 4; - if(((offset+rtcp_section_len) > packet->payload_packet_len) || (rtcp_section_len == 0)) + if(((offset+rtcp_section_len) > packet->payload_packet_len) || (rtcp_section_len == 0) || (len == 0)) goto exclude_rtcp; else offset += rtcp_section_len; @@ -60,6 +60,9 @@ void ndpi_search_rtcp(struct ndpi_detection_module_struct *ndpi_struct, NDPI_LOG_INFO(ndpi_struct, "found rtcp\n"); ndpi_int_rtcp_add_connection(ndpi_struct, flow); } + + if(flow->packet_counter > 3) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } else { exclude_rtcp: diff --git a/src/lib/protocols/rtmp.c b/src/lib/protocols/rtmp.c index 9bf73fe..6f40ce4 100644 --- a/src/lib/protocols/rtmp.c +++ b/src/lib/protocols/rtmp.c @@ -1,6 +1,7 @@ /* * rtmp.c * + * Copyright (C) 2020 - ntop.org * Copyright (C) 2014 Tomasz Bujlow * * The signature is based on the Libprotoident library. @@ -46,16 +47,16 @@ static void ndpi_check_rtmp(struct ndpi_detection_module_struct *ndpi_struct, st } /* Check if we so far detected the protocol in the request or not. */ - if (flow->rtmp_stage == 0) { - NDPI_LOG_DBG2(ndpi_struct, "RTMP stage 0: \n"); + if(flow->rtmp_stage == 0) { + NDPI_LOG_DBG2(ndpi_struct, "RTMP stage 0: \n"); - if ((payload_len >= 4) && ((packet->payload[0] == 0x03) || (packet->payload[0] == 0x06))) { - NDPI_LOG_DBG2(ndpi_struct, "Possible RTMP request detected, we will look further for the response\n"); + if ((payload_len >= 4) && ((packet->payload[0] == 0x03) || (packet->payload[0] == 0x06))) { + NDPI_LOG_DBG2(ndpi_struct, "Possible RTMP request detected, we will look further for the response\n"); - /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ - flow->rtmp_stage = packet->packet_direction + 1; - } - + /* Encode the direction of the packet in the stage, so we will know when we need to look for the response packet. */ + flow->rtmp_stage = packet->packet_direction + 1; + } else + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } else { NDPI_LOG_DBG2(ndpi_struct, "RTMP stage %u: \n", flow->rtmp_stage); @@ -72,7 +73,6 @@ static void ndpi_check_rtmp(struct ndpi_detection_module_struct *ndpi_struct, st NDPI_LOG_DBG2(ndpi_struct, "The reply did not seem to belong to RTMP, resetting the stage to 0\n"); flow->rtmp_stage = 0; } - } } diff --git a/src/lib/protocols/rtp.c b/src/lib/protocols/rtp.c index 6cf9e80..bae365e 100644 --- a/src/lib/protocols/rtp.c +++ b/src/lib/protocols/rtp.c @@ -2,7 +2,7 @@ * rtp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -76,6 +76,8 @@ static u_int8_t isValidMSRTPType(u_int8_t payloadType) { static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, const u_int8_t * payload, const u_int16_t payload_len) { + u_int8_t payloadType, payload_type; + NDPI_LOG_DBG(ndpi_struct, "search RTP\n"); if((payload_len < 2) || flow->protos.stun_ssl.stun.num_binding_requests) { @@ -83,9 +85,8 @@ static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct, return; } - //struct ndpi_packet_struct *packet = &flow->packet; - u_int8_t payloadType, payload_type = payload[1] & 0x7F; - + payload_type = payload[1] & 0x7F; + /* Check whether this is an RTP flow */ if((payload_len >= 12) && (((payload[0] & 0xFF) == 0x80) || ((payload[0] & 0xFF) == 0xA0)) /* RTP magic byte[1] */ @@ -95,21 +96,6 @@ static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct, /* http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */ ) ) { - struct ndpi_packet_struct *packet = &flow->packet; - - if(packet->iph) { - /* 125.209.252.xxx */ - if(((ntohl(packet->iph->saddr) & 0xFFFFFF00 /* 255.255.255.0 */) == 0x7DD1FC00) - || ((ntohl(packet->iph->daddr) & 0xFFFFFF00 /* 255.255.255.0 */) == 0x7DD1FC00)) { - if((flow->packet.payload[0] == 0x80) - && ((flow->packet.payload[1] == 0x78) || (flow->packet.payload[1] == 0xE8)) - ) { - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_LINE, NDPI_PROTOCOL_LINE); - return; - } - } - } - NDPI_LOG_INFO(ndpi_struct, "Found RTP\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_RTP, NDPI_PROTOCOL_UNKNOWN); return; @@ -120,13 +106,6 @@ static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct, NDPI_LOG_INFO(ndpi_struct, "Found Skype for Business (former MS Lync)\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SKYPE, NDPI_PROTOCOL_UNKNOWN); return; - } else /* RTCP */ { -#if 0 - /* If it's RTCP the RTCP decoder will catch it */ - NDPI_LOG_INFO(ndpi_struct, "Found MS RTCP\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_RTCP, NDPI_PROTOCOL_UNKNOWN); - return; -#endif } } @@ -139,13 +118,20 @@ static void ndpi_rtp_search(struct ndpi_detection_module_struct *ndpi_struct, void ndpi_search_rtp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - + u_int16_t source = ntohs(packet->udp->source); + u_int16_t dest = ntohs(packet->udp->dest); + + // printf("==> %s()\n", __FUNCTION__); + /* printf("*** %s(pkt=%d)\n", __FUNCTION__, flow->packet_counter); */ if((packet->udp != NULL) - /* && (ntohs(packet->udp->source) > 1023) */ - && (ntohs(packet->udp->dest) > 1023)) + && (source != 30303) && (dest != 30303 /* Avoid to mix it with Ethereum that looks alike */) + && (dest > 1023) + ) ndpi_rtp_search(ndpi_struct, flow, packet->payload, packet->payload_packet_len); + else + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } /* *************************************************************** */ diff --git a/src/lib/protocols/rtsp.c b/src/lib/protocols/rtsp.c index 3969d80..f2baf30 100644 --- a/src/lib/protocols/rtsp.c +++ b/src/lib/protocols/rtsp.c @@ -2,7 +2,7 @@ * rtsp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -75,13 +75,13 @@ void ndpi_search_rtsp_tcp_udp(struct ndpi_detection_module_struct if (dst != NULL) { NDPI_LOG_DBG2(ndpi_struct, "found dst\n"); ndpi_packet_src_ip_get(packet, &dst->rtsp_ip_address); - dst->rtsp_timer = packet->tick_timestamp; + dst->rtsp_timer = packet->current_time_ms; dst->rtsp_ts_set = 1; } if (src != NULL) { NDPI_LOG_DBG2(ndpi_struct, "found src\n"); ndpi_packet_dst_ip_get(packet, &src->rtsp_ip_address); - src->rtsp_timer = packet->tick_timestamp; + src->rtsp_timer = packet->current_time_ms; src->rtsp_ts_set = 1; } NDPI_LOG_INFO(ndpi_struct, "found RTSP\n"); diff --git a/src/lib/protocols/rx.c b/src/lib/protocols/rx.c index cfa0dec..3bcab3b 100644 --- a/src/lib/protocols/rx.c +++ b/src/lib/protocols/rx.c @@ -1,7 +1,7 @@ /* * rx.c * - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * Giovanni Mascellani * @@ -129,19 +129,24 @@ void ndpi_check_rx(struct ndpi_detection_module_struct *ndpi_struct, header->flags == PLUS_2 || header->flags == REQ_ACK || header->flags == MORE_1) goto security; + /* Fall-through */ case ACK: if(header->flags == CLIENT_INIT_1 || header->flags == CLIENT_INIT_2 || header->flags == EMPTY) goto security; + /* Fall-through */ case CHALLENGE: if(header->flags == EMPTY || header->call_number == 0) goto security; + /* Fall-through */ case RESPONSE: if(header->flags == EMPTY || header->call_number == 0) goto security; + /* Fall-through */ case ACKALL: if(header->flags == EMPTY) goto security; + /* Fall-through */ case BUSY: goto security; case ABORT: diff --git a/src/lib/protocols/s7comm.c b/src/lib/protocols/s7comm.c new file mode 100644 index 0000000..ec32c10 --- /dev/null +++ b/src/lib/protocols/s7comm.c @@ -0,0 +1,57 @@ +/* + * s7comm.c + * + * Copyright (C) 2011-20 - ntop.org + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ +#include "ndpi_protocol_ids.h" +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_S7COMM +#include "ndpi_api.h" + +void ndpi_search_s7comm_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + NDPI_LOG_DBG(ndpi_struct, "search S7\n"); + u_int16_t s7comm_port = htons(102); + if(packet->tcp) { + + if((packet->payload_packet_len >= 2) && (packet->payload[0]==0x03)&&(packet->payload[1]==0x00)&&((packet->tcp->dest == s7comm_port) || (packet->tcp->source == s7comm_port))) { + NDPI_LOG_INFO(ndpi_struct, "found S7\n"); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_S7COMM, NDPI_PROTOCOL_UNKNOWN); + + return; + + } + } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + +} + +void init_s7comm_dissector(struct ndpi_detection_module_struct *ndpi_struct, + u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { + + ndpi_set_bitmask_protocol_detection("S7COMM", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_S7COMM, + ndpi_search_s7comm_tcp, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); + *id += 1; +} + diff --git a/src/lib/protocols/sflow.c b/src/lib/protocols/sflow.c index 6330e17..456cf95 100644 --- a/src/lib/protocols/sflow.c +++ b/src/lib/protocols/sflow.c @@ -1,7 +1,7 @@ /* * sflow.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -42,7 +42,7 @@ void ndpi_search_sflow(struct ndpi_detection_module_struct *ndpi_struct, struct return; } -// FIXME NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } void init_sflow_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) diff --git a/src/lib/protocols/shoutcast.c b/src/lib/protocols/shoutcast.c index dd4521d..1693874 100644 --- a/src/lib/protocols/shoutcast.c +++ b/src/lib/protocols/shoutcast.c @@ -2,7 +2,7 @@ * shoutcast.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/sip.c b/src/lib/protocols/sip.c index 94423df..b7806d0 100644 --- a/src/lib/protocols/sip.c +++ b/src/lib/protocols/sip.c @@ -2,7 +2,7 @@ * sip.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -48,142 +48,131 @@ void ndpi_search_sip_handshake(struct ndpi_detection_module_struct const u_int8_t *packet_payload = packet->payload; u_int32_t payload_len = packet->payload_packet_len; - if (payload_len > 4) { + if(payload_len > 4) { /* search for STUN Turn ChannelData Prefix */ u_int16_t message_len = ntohs(get_u_int16_t(packet->payload, 2)); - if (payload_len - 4 == message_len) { + + if(payload_len - 4 == message_len) { NDPI_LOG_DBG2(ndpi_struct, "found STUN TURN ChannelData prefix\n"); payload_len -= 4; packet_payload += 4; } } - if (payload_len >= 14) - { - - if ((memcmp(packet_payload, "NOTIFY ", 7) == 0 || memcmp(packet_payload, "notify ", 7) == 0) - && (memcmp(&packet_payload[7], "SIP:", 4) == 0 || memcmp(&packet_payload[7], "sip:", 4) == 0)) { - - NDPI_LOG_INFO(ndpi_struct, "found sip NOTIFY\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - if ((memcmp(packet_payload, "REGISTER ", 9) == 0 || memcmp(packet_payload, "register ", 9) == 0) - && (memcmp(&packet_payload[9], "SIP:", 4) == 0 || memcmp(&packet_payload[9], "sip:", 4) == 0)) { - - NDPI_LOG_INFO(ndpi_struct, "found sip REGISTER\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - if ((memcmp(packet_payload, "INVITE ", 7) == 0 || memcmp(packet_payload, "invite ", 7) == 0) - && (memcmp(&packet_payload[7], "SIP:", 4) == 0 || memcmp(&packet_payload[7], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip INVITE\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - /* seen this in second direction on the third position, - * maybe it could be deleted, if somebody sees it in the first direction, - * please delete this comment. - */ - - /* - if (memcmp(packet_payload, "SIP/2.0 200 OK", 14) == 0 || memcmp(packet_payload, "sip/2.0 200 OK", 14) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found sip SIP/2.0 0K\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - */ - if (memcmp(packet_payload, "SIP/2.0 ", 8) == 0 || memcmp(packet_payload, "sip/2.0 ", 8) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found sip SIP/2.0 *\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - if ((memcmp(packet_payload, "BYE ", 4) == 0 || memcmp(packet_payload, "bye ", 4) == 0) - && (memcmp(&packet_payload[4], "SIP:", 4) == 0 || memcmp(&packet_payload[4], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip BYE\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - if ((memcmp(packet_payload, "ACK ", 4) == 0 || memcmp(packet_payload, "ack ", 4) == 0) - && (memcmp(&packet_payload[4], "SIP:", 4) == 0 || memcmp(&packet_payload[4], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip ACK\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - if ((memcmp(packet_payload, "CANCEL ", 7) == 0 || memcmp(packet_payload, "cancel ", 7) == 0) - && (memcmp(&packet_payload[7], "SIP:", 4) == 0 || memcmp(&packet_payload[7], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip CANCEL\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - if ((memcmp(packet_payload, "PUBLISH ", 8) == 0 || memcmp(packet_payload, "publish ", 8) == 0) - && (memcmp(&packet_payload[8], "SIP:", 4) == 0 || memcmp(&packet_payload[8], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip PUBLISH\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - if ((memcmp(packet_payload, "SUBSCRIBE ", 10) == 0 || memcmp(packet_payload, "subscribe ", 10) == 0) - && (memcmp(&packet_payload[10], "SIP:", 4) == 0 || memcmp(&packet_payload[10], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip SUBSCRIBE\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - /* SIP message extension RFC 3248 */ - if ((memcmp(packet_payload, "MESSAGE ", 8) == 0 || memcmp(packet_payload, "message ", 8) == 0) - && (memcmp(&packet_payload[8], "SIP:", 4) == 0 || memcmp(&packet_payload[8], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip MESSAGE\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } - - /* Courtesy of Miguel Quesada */ - if ((memcmp(packet_payload, "OPTIONS ", 8) == 0 - || memcmp(packet_payload, "options ", 8) == 0) - && (memcmp(&packet_payload[8], "SIP:", 4) == 0 - || memcmp(&packet_payload[8], "sip:", 4) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found sip OPTIONS\n"); - ndpi_int_sip_add_connection(ndpi_struct, flow, 0); - return; - } + if(payload_len >= 14) { + if((memcmp(packet_payload, "NOTIFY ", 7) == 0 || memcmp(packet_payload, "notify ", 7) == 0) + && (memcmp(&packet_payload[7], "SIP:", 4) == 0 || memcmp(&packet_payload[7], "sip:", 4) == 0)) { + + NDPI_LOG_INFO(ndpi_struct, "found sip NOTIFY\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + if((memcmp(packet_payload, "REGISTER ", 9) == 0 || memcmp(packet_payload, "register ", 9) == 0) + && (memcmp(&packet_payload[9], "SIP:", 4) == 0 || memcmp(&packet_payload[9], "sip:", 4) == 0)) { + + NDPI_LOG_INFO(ndpi_struct, "found sip REGISTER\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + if((memcmp(packet_payload, "INVITE ", 7) == 0 || memcmp(packet_payload, "invite ", 7) == 0) + && (memcmp(&packet_payload[7], "SIP:", 4) == 0 || memcmp(&packet_payload[7], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip INVITE\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + /* seen this in second direction on the third position, + * maybe it could be deleted, if somebody sees it in the first direction, + * please delete this comment. + */ + + /* + if(memcmp(packet_payload, "SIP/2.0 200 OK", 14) == 0 || memcmp(packet_payload, "sip/2.0 200 OK", 14) == 0) { + NDPI_LOG_INFO(ndpi_struct, "found sip SIP/2.0 0K\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; } + */ + if(memcmp(packet_payload, "SIP/2.0 ", 8) == 0 || memcmp(packet_payload, "sip/2.0 ", 8) == 0) { + NDPI_LOG_INFO(ndpi_struct, "found sip SIP/2.0 *\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + if((memcmp(packet_payload, "BYE ", 4) == 0 || memcmp(packet_payload, "bye ", 4) == 0) + && (memcmp(&packet_payload[4], "SIP:", 4) == 0 || memcmp(&packet_payload[4], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip BYE\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + if((memcmp(packet_payload, "ACK ", 4) == 0 || memcmp(packet_payload, "ack ", 4) == 0) + && (memcmp(&packet_payload[4], "SIP:", 4) == 0 || memcmp(&packet_payload[4], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip ACK\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + if((memcmp(packet_payload, "CANCEL ", 7) == 0 || memcmp(packet_payload, "cancel ", 7) == 0) + && (memcmp(&packet_payload[7], "SIP:", 4) == 0 || memcmp(&packet_payload[7], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip CANCEL\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + if((memcmp(packet_payload, "PUBLISH ", 8) == 0 || memcmp(packet_payload, "publish ", 8) == 0) + && (memcmp(&packet_payload[8], "SIP:", 4) == 0 || memcmp(&packet_payload[8], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip PUBLISH\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + if((memcmp(packet_payload, "SUBSCRIBE ", 10) == 0 || memcmp(packet_payload, "subscribe ", 10) == 0) + && (memcmp(&packet_payload[10], "SIP:", 4) == 0 || memcmp(&packet_payload[10], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip SUBSCRIBE\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + /* SIP message extension RFC 3248 */ + if((memcmp(packet_payload, "MESSAGE ", 8) == 0 || memcmp(packet_payload, "message ", 8) == 0) + && (memcmp(&packet_payload[8], "SIP:", 4) == 0 || memcmp(&packet_payload[8], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip MESSAGE\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + + /* Courtesy of Miguel Quesada */ + if((memcmp(packet_payload, "OPTIONS ", 8) == 0 + || memcmp(packet_payload, "options ", 8) == 0) + && (memcmp(&packet_payload[8], "SIP:", 4) == 0 + || memcmp(&packet_payload[8], "sip:", 4) == 0)) { + NDPI_LOG_INFO(ndpi_struct, "found sip OPTIONS\n"); + ndpi_int_sip_add_connection(ndpi_struct, flow, 0); + return; + } + } /* add bitmask for tcp only, some stupid udp programs * send a very few (< 10 ) packets before invite (mostly a 0x0a0x0d, but just search the first 3 payload_packets here */ - if (packet->udp != NULL && flow->packet_counter < 20) { + if(packet->udp != NULL && flow->packet_counter < 20) { NDPI_LOG_DBG2(ndpi_struct, "need next packet\n"); return; } /* for STUN flows we need some more packets */ - if (packet->udp != NULL && flow->detected_protocol_stack[0] == NDPI_PROTOCOL_STUN && flow->packet_counter < 40) { + if(packet->udp != NULL && flow->detected_protocol_stack[0] == NDPI_PROTOCOL_STUN && flow->packet_counter < 40) { NDPI_LOG_DBG2(ndpi_struct, "need next STUN packet\n"); return; } - if (payload_len == 4 && get_u_int32_t(packet_payload, 0) == 0) { + if(payload_len == 4 && get_u_int32_t(packet_payload, 0) == 0) { NDPI_LOG_DBG2(ndpi_struct, "maybe sip. need next packet\n"); return; } - if (payload_len > 30 && packet_payload[0] == 0x90 - && packet_payload[3] == payload_len - 20 && get_u_int32_t(packet_payload, 4) == 0 - && get_u_int32_t(packet_payload, 8) == 0) { - flow->sip_yahoo_voice = 1; - NDPI_LOG_DBG2(ndpi_struct, "maybe sip yahoo. need next packet\n"); - } - if (flow->sip_yahoo_voice && flow->packet_counter < 10) { - return; - } - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } @@ -193,15 +182,18 @@ void ndpi_search_sip(struct ndpi_detection_module_struct *ndpi_struct, struct nd NDPI_LOG_DBG(ndpi_struct, "search sip\n"); - /* skip marked packets */ - if (packet->detected_protocol_stack[0] != NDPI_PROTOCOL_SIP) { - if (packet->tcp_retransmission == 0) { - ndpi_search_sip_handshake(ndpi_struct, flow); + if(flow->packet_counter > 5) + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + else { + /* skip marked packets */ + if(packet->detected_protocol_stack[0] != NDPI_PROTOCOL_SIP) { + if(packet->tcp_retransmission == 0) { + ndpi_search_sip_handshake(ndpi_struct, flow); + } } } } - void init_sip_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("SIP", ndpi_struct, detection_bitmask, *id, diff --git a/src/lib/protocols/skype.c b/src/lib/protocols/skype.c index 8ada5d9..01d93bd 100644 --- a/src/lib/protocols/skype.c +++ b/src/lib/protocols/skype.c @@ -1,7 +1,7 @@ /* * skype.c * - * Copyright (C) 2017-19 - ntop.org + * Copyright (C) 2017-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -32,11 +32,17 @@ static void ndpi_check_skype(struct ndpi_detection_module_struct *ndpi_struct, s // const u_int8_t *packet_payload = packet->payload; u_int32_t payload_len = packet->payload_packet_len; - if(flow->host_server_name[0] != '\0') + /* No need to do ntohl() with 0xFFFFFFFF */ + if(packet->iph && (packet->iph->daddr == 0xFFFFFFFF /* 255.255.255.255 */)) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; + } + if(flow->host_server_name[0] != '\0') + return; + // UDP check - if(packet->udp != NULL) { + if(packet->udp != NULL) { flow->l4.udp.skype_packet_id++; if(flow->l4.udp.skype_packet_id < 5) { @@ -49,15 +55,23 @@ static void ndpi_check_skype(struct ndpi_detection_module_struct *ndpi_struct, s ) { ; } else { - if(((payload_len == 3) && ((packet->payload[2] & 0x0F)== 0x0d)) || + /* Too many false positives */ + if(((payload_len == 3) && ((packet->payload[2] & 0x0F)== 0x0d)) + || ((payload_len >= 16) + && (((packet->payload[0] & 0xC0) >> 6) == 0x02 /* RTPv2 */ + || (((packet->payload[0] & 0xF0) >> 4) == 0 /* Zoom */) + || (((packet->payload[0] & 0xF0) >> 4) == 0x07 /* Skype */) + ) && (packet->payload[0] != 0x30) /* Avoid invalid SNMP detection */ + && (packet->payload[0] != 0x00) /* Avoid invalid CAPWAP detection */ && (packet->payload[2] == 0x02))) { - if(is_port(sport, dport, 8801)) + if(is_port(sport, dport, 8801)) { ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ZOOM, NDPI_PROTOCOL_UNKNOWN); - else + } else if (payload_len >= 16 && packet->payload[0] != 0x01) /* Avoid invalid Cisco HSRP detection / RADIUS */ { ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SKYPE_CALL, NDPI_PROTOCOL_SKYPE); + } } } diff --git a/src/lib/protocols/smb.c b/src/lib/protocols/smb.c index 71305cd..9a56ead 100644 --- a/src/lib/protocols/smb.c +++ b/src/lib/protocols/smb.c @@ -1,7 +1,7 @@ /* * smb.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -21,7 +21,7 @@ * */ #include "ndpi_protocol_ids.h" - +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_SMBV23 #include "ndpi_api.h" @@ -33,18 +33,24 @@ void ndpi_search_smb_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc /* Check connection over TCP */ if(packet->tcp) { + u_int16_t fourfourfive = htons(445); - if(packet->tcp->dest == htons(445) + if(((packet->tcp->dest == fourfourfive) || (packet->tcp->source == fourfourfive)) && packet->payload_packet_len > (32 + 4 + 4) && (packet->payload_packet_len - 4) == ntohl(get_u_int32_t(packet->payload, 0)) - && get_u_int32_t(packet->payload, 4) == htonl(0xff534d42)) { - + ) { + u_int8_t smbv1[] = { 0xff, 0x53, 0x4d, 0x42 }; + NDPI_LOG_INFO(ndpi_struct, "found SMB\n"); - if(packet->payload[8] == 0x72) - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SMBV1, NDPI_PROTOCOL_UNKNOWN); - else - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SMBV23, NDPI_PROTOCOL_UNKNOWN); + if(memcmp(&packet->payload[4], smbv1, sizeof(smbv1)) == 0) { + if(packet->payload[8] != 0x72) /* Skip Negotiate request */ { + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SMBV1, NDPI_PROTOCOL_NETBIOS); + NDPI_SET_BIT(flow->risk, NDPI_SMB_INSECURE_VERSION); + } + } else + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SMBV23, NDPI_PROTOCOL_NETBIOS); + return; } } @@ -65,4 +71,3 @@ void init_smb_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int3 *id += 1; } - diff --git a/src/lib/protocols/smpp.c b/src/lib/protocols/smpp.c index c188bd9..4720499 100644 --- a/src/lib/protocols/smpp.c +++ b/src/lib/protocols/smpp.c @@ -2,7 +2,7 @@ * smpp.c * * Copyright (C) 2016 - Damir Franusic - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -68,7 +68,7 @@ void ndpi_search_smpp_tcp(struct ndpi_detection_module_struct* ndpi_struct, u_int32_t tmp_pdu_l = 0; u_int16_t pdu_c = 1; // loop PDUs (check if lengths are valid) - while(total_pdu_l < packet->payload_packet_len) { + while(total_pdu_l < (packet->payload_packet_len-4)) { // get next PDU length tmp_pdu_l = ntohl(get_u_int32_t(packet->payload, total_pdu_l)); // if zero or overflowing , return, will try the next TCP segment diff --git a/src/lib/protocols/snmp_proto.c b/src/lib/protocols/snmp_proto.c index e7ea615..01de3fe 100644 --- a/src/lib/protocols/snmp_proto.c +++ b/src/lib/protocols/snmp_proto.c @@ -2,7 +2,7 @@ * snmp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/soap.c b/src/lib/protocols/soap.c new file mode 100644 index 0000000..dfbaf6c --- /dev/null +++ b/src/lib/protocols/soap.c @@ -0,0 +1,70 @@ +/* + * soap.c + * + * Copyright (C) 2020 - ntop.org + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#include "ndpi_protocol_ids.h" + +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_SOAP + +#include "ndpi_api.h" + +static void ndpi_int_soap_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) +{ + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SOAP, NDPI_PROTOCOL_UNKNOWN); +} + +void ndpi_search_soap(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) +{ + struct ndpi_packet_struct *packet = &flow->packet; + + NDPI_LOG_DBG(ndpi_struct, "search soap\n"); + + if (flow->packet_counter > 3) + { + if (flow->l4.tcp.soap_stage == 1) + { + ndpi_int_soap_add_connection(ndpi_struct, flow); + } + else { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + } + } + + if (flow->l4.tcp.soap_stage == 0 && + packet->payload_packet_len >= 19) + { + if (strncmp((char*)packet->payload, "l4.tcp.soap_stage = 1; + } + } +} + +void init_soap_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, + NDPI_PROTOCOL_BITMASK *detection_bitmask) +{ + ndpi_set_bitmask_protocol_detection( + "SOAP", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_SOAP, ndpi_search_soap, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); + *id += 1; +} + diff --git a/src/lib/protocols/socks45.c b/src/lib/protocols/socks45.c index 15cd06b..1926ec5 100644 --- a/src/lib/protocols/socks45.c +++ b/src/lib/protocols/socks45.c @@ -1,7 +1,7 @@ /* * socks4.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * Copyright (C) 2014 Tomasz Bujlow * * The signature is based on the Libprotoident library. diff --git a/src/lib/protocols/someip.c b/src/lib/protocols/someip.c index 9211a4b..e894d63 100644 --- a/src/lib/protocols/someip.c +++ b/src/lib/protocols/someip.c @@ -87,6 +87,14 @@ static void ndpi_int_someip_add_connection (struct ndpi_detection_module_struct NDPI_LOG_INFO(ndpi_struct, "found SOME/IP\n"); } +static u_int32_t someip_data_cover_32(const u_int8_t *data) +{ + u_int32_t value; + + memcpy(&value,data,sizeof(u_int32_t)); + + return value; +} /** * Dissector function that searches SOME/IP headers */ @@ -111,8 +119,8 @@ void ndpi_search_someip (struct ndpi_detection_module_struct *ndpi_struct, } //we extract the Message ID and Request ID and check for special cases later - u_int32_t message_id = ntohl(*((u_int32_t *)&packet->payload[0])); - u_int32_t request_id = ntohl(*((u_int32_t *)&packet->payload[8])); + u_int32_t message_id = ntohl(someip_data_cover_32(&packet->payload[0])); + u_int32_t request_id = ntohl(someip_data_cover_32(&packet->payload[8])); NDPI_LOG_DBG2(ndpi_struct, "====>>>> SOME/IP Message ID: %08x [len: %u]\n", message_id, packet->payload_packet_len); @@ -125,7 +133,7 @@ void ndpi_search_someip (struct ndpi_detection_module_struct *ndpi_struct, //####Maximum packet size in SOMEIP depends on the carrier protocol, and I'm not certain how well enforced it is, so let's leave that for round 2#### // we extract the remaining length - u_int32_t someip_len = ntohl(*((u_int32_t *)&packet->payload[4])); + u_int32_t someip_len = ntohl(someip_data_cover_32(&packet->payload[4])); if (packet->payload_packet_len != (someip_len + 8)) { NDPI_LOG_DBG(ndpi_struct, "Excluding SOME/IP .. Length field invalid!\n"); NDPI_ADD_PROTOCOL_TO_BITMASK(flow->excluded_protocol_bitmask, NDPI_PROTOCOL_SOMEIP); diff --git a/src/lib/protocols/sopcast.c b/src/lib/protocols/sopcast.c index 530bcea..6f8f530 100644 --- a/src/lib/protocols/sopcast.c +++ b/src/lib/protocols/sopcast.c @@ -2,7 +2,7 @@ * sopcast.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/soulseek.c b/src/lib/protocols/soulseek.c index be4d2e0..ce7db53 100644 --- a/src/lib/protocols/soulseek.c +++ b/src/lib/protocols/soulseek.c @@ -1,7 +1,7 @@ /* * soulseek.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -29,9 +29,9 @@ #define SOULSEEK_DETECT \ if(src != NULL) \ - src->soulseek_last_safe_access_time = packet->tick_timestamp; \ + src->soulseek_last_safe_access_time = packet->current_time_ms; \ if(dst != NULL) \ - dst->soulseek_last_safe_access_time = packet->tick_timestamp; \ + dst->soulseek_last_safe_access_time = packet->current_time_ms; \ ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SOULSEEK, NDPI_PROTOCOL_UNKNOWN) void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, @@ -50,24 +50,24 @@ void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, NDPI_LOG_DBG2(ndpi_struct, " SRC bitmask: %u, packet tick %llu , last safe access timestamp: %llu\n", NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_SOULSEEK) - != 0 ? 1 : 0, (long long unsigned int) packet->tick_timestamp, + != 0 ? 1 : 0, (long long unsigned int) packet->current_time_ms, (long long unsigned int) src->soulseek_last_safe_access_time); if(dst != NULL) NDPI_LOG_DBG2(ndpi_struct, " DST bitmask: %u, packet tick %llu , last safe ts: %llu\n", NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_SOULSEEK) - != 0 ? 1 : 0, (long long unsigned int) packet->tick_timestamp, + != 0 ? 1 : 0, (long long unsigned int) packet->current_time_ms, (long long unsigned int) dst->soulseek_last_safe_access_time); if(packet->payload_packet_len == 431) { if(dst != NULL) { - dst->soulseek_last_safe_access_time = packet->tick_timestamp; + dst->soulseek_last_safe_access_time = packet->current_time_ms; } return; } if(packet->payload_packet_len == 12 && get_l32(packet->payload, 4) == 0x02) { if(src != NULL) { - src->soulseek_last_safe_access_time = packet->tick_timestamp; + src->soulseek_last_safe_access_time = packet->current_time_ms; if(packet->tcp != NULL && src->soulseek_listen_port == 0) { src->soulseek_listen_port = get_l32(packet->payload, 8); return; @@ -75,30 +75,30 @@ void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, } } - if(src != NULL && ((u_int32_t)(packet->tick_timestamp - src->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) { + if(src != NULL && ((u_int32_t)(packet->current_time_ms - src->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "Soulseek: SRC update last safe access time and SKIP_FOR_TIME \n"); - src->soulseek_last_safe_access_time = packet->tick_timestamp; + src->soulseek_last_safe_access_time = packet->current_time_ms; } - if(dst != NULL && ((u_int32_t)(packet->tick_timestamp - dst->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) { + if(dst != NULL && ((u_int32_t)(packet->current_time_ms - dst->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "Soulseek: DST update last safe access time and SKIP_FOR_TIME \n"); - dst->soulseek_last_safe_access_time = packet->tick_timestamp; + dst->soulseek_last_safe_access_time = packet->current_time_ms; } } if(dst != NULL && dst->soulseek_listen_port != 0 && dst->soulseek_listen_port == ntohs(packet->tcp->dest) - && ((u_int32_t)(packet->tick_timestamp - dst->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) { + && ((u_int32_t)(packet->current_time_ms - dst->soulseek_last_safe_access_time) < ndpi_struct->soulseek_connection_ip_tick_timeout)) { NDPI_LOG_DBG2(ndpi_struct, - "Soulseek: Plain detection on Port : %u packet_tick_timestamp: %u soulseek_last_safe_access_time: %u soulseek_connection_ip_ticktimeout: %u\n", - dst->soulseek_listen_port, packet->tick_timestamp, dst->soulseek_last_safe_access_time, ndpi_struct->soulseek_connection_ip_tick_timeout); + "Soulseek: Plain detection on Port : %u packet_current_time_ms: %u soulseek_last_safe_access_time: %u soulseek_connection_ip_ticktimeout: %u\n", + dst->soulseek_listen_port, packet->current_time_ms, dst->soulseek_last_safe_access_time, ndpi_struct->soulseek_connection_ip_tick_timeout); - dst->soulseek_last_safe_access_time = packet->tick_timestamp; + dst->soulseek_last_safe_access_time = packet->current_time_ms; if(src != NULL) - src->soulseek_last_safe_access_time = packet->tick_timestamp; + src->soulseek_last_safe_access_time = packet->current_time_ms; NDPI_LOG_INFO(ndpi_struct, "found Soulseek\n"); ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SOULSEEK, NDPI_PROTOCOL_UNKNOWN); @@ -121,11 +121,17 @@ void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, } index += get_l32(packet->payload, index) + 4; - } - if(index + get_l32(packet->payload, index) == packet->payload_packet_len - 4 && !get_u_int16_t(packet->payload, 10)) { + } /* while */ + + if((packet->payload_packet_len >= (index+4)) + && (index + get_l32(packet->payload, index)) == (packet->payload_packet_len - 4) + && (get_u_int16_t(packet->payload, 10) != 0)) { /* This structure seems to be soulseek proto */ index = get_l32(packet->payload, 8) + 12; // end of "user name" - if((index + 4) <= packet->payload_packet_len && !get_u_int16_t(packet->payload, index + 2)) // for passwd len + + if(((index + 4) <= packet->payload_packet_len) + && (packet->payload_packet_len >= (index+4)) + && (!get_u_int16_t(packet->payload, index + 2))) // for passwd len { index += get_l32(packet->payload, index) + 4; //end of "Passwd" if((index + 4 + 4) <= packet->payload_packet_len && !get_u_int16_t(packet->payload, index + 6)) // to read version,hashlen @@ -142,7 +148,8 @@ void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, } } if (packet->payload_packet_len > 8 - && packet->payload_packet_len < 200 && get_l32(packet->payload, 0) == packet->payload_packet_len - 4) { + && (packet->payload_packet_len < 200) + && get_l32(packet->payload, 0) == (packet->payload_packet_len - 4)) { //Server Messages: const u_int32_t msgcode = get_l32(packet->payload, 4); @@ -154,14 +161,14 @@ void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, const u_int32_t soulseek_listen_port = get_l32(packet->payload, 8); if(src != NULL) { - src->soulseek_last_safe_access_time = packet->tick_timestamp; + src->soulseek_last_safe_access_time = packet->current_time_ms; if(packet->tcp != NULL && src->soulseek_listen_port == 0) { src->soulseek_listen_port = soulseek_listen_port; NDPI_LOG_DBG2(ndpi_struct, "\n Listen Port Saved : %u", src->soulseek_listen_port); if(dst != NULL) - dst->soulseek_last_safe_access_time = packet->tick_timestamp; + dst->soulseek_last_safe_access_time = packet->current_time_ms; ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SOULSEEK, NDPI_PROTOCOL_UNKNOWN); return; @@ -196,7 +203,7 @@ void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, && !get_u_int16_t(packet->payload, 2)) { const u_int32_t usrlen = get_l32(packet->payload, 5); - if(usrlen <= packet->payload_packet_len - 4 + 1 + 4 + 4 + 1 + 4) { + if(usrlen <= packet->payload_packet_len - (4 + 1 + 4 + 4 + 1 + 4)) { const u_int32_t typelen = get_l32(packet->payload, 4 + 1 + 4 + usrlen); const u_int8_t type = packet->payload[4 + 1 + 4 + usrlen + 4]; if(typelen == 1 && (type == 'F' || type == 'P' || type == 'D')) { @@ -259,7 +266,9 @@ void ndpi_search_soulseek_tcp(struct ndpi_detection_module_struct *ndpi_struct, SOULSEEK_DETECT; return; } + if(flow->l4.tcp.soulseek_stage && flow->packet_counter < 11) { + ; } else { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } diff --git a/src/lib/protocols/spotify.c b/src/lib/protocols/spotify.c index a180a1e..d661090 100644 --- a/src/lib/protocols/spotify.c +++ b/src/lib/protocols/spotify.c @@ -47,7 +47,7 @@ static void ndpi_check_spotify(struct ndpi_detection_module_struct *ndpi_struct, if((packet->udp->source == spotify_port) && (packet->udp->dest == spotify_port)) { - if(payload_len > 2) { + if(payload_len >= 7) { if(memcmp(packet->payload, "SpotUdp", 7) == 0) { NDPI_LOG_INFO(ndpi_struct, "found spotify udp dissector\n"); ndpi_int_spotify_add_connection(ndpi_struct, flow, 0); diff --git a/src/lib/protocols/ssdp.c b/src/lib/protocols/ssdp.c index b5cef8b..10e913e 100644 --- a/src/lib/protocols/ssdp.c +++ b/src/lib/protocols/ssdp.c @@ -2,7 +2,7 @@ * ssdp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ssh.c b/src/lib/protocols/ssh.c index 5dd6fb0..853ec19 100644 --- a/src/lib/protocols/ssh.c +++ b/src/lib/protocols/ssh.c @@ -1,8 +1,8 @@ /* * ssh.c * + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -29,6 +29,8 @@ #include "ndpi_api.h" #include "ndpi_md5.h" +#include + /* HASSH - https://github.com/salesforce/hassh @@ -54,21 +56,177 @@ that usually is packet 14 */ -/* #define SSH_DEBUG 1 */ +// #define SSH_DEBUG 1 + +static void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow); + +typedef struct { + const char *signature; + u_int16_t major, minor, patch; +} ssh_pattern; + +/* ************************************************************************ */ + +static void ssh_analyze_signature_version(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + char *str_to_check, + u_int8_t is_client_signature) { + + if(str_to_check == NULL) return; + + u_int i; + u_int8_t obsolete_ssh_version = 0; + const ssh_pattern ssh_servers_strings[] = + { + { (const char*)"SSH-%*f-OpenSSH_%d.%d.%d", 7, 0, 0 }, /* OpenSSH */ + { (const char*)"SSH-%*f-APACHE-SSHD-%d.%d.%d", 2, 5, 1 }, /* Apache MINA SSHD */ + { (const char*)"SSH-%*f-FileZilla_%d.%d.%d", 3, 40, 0 }, /* FileZilla SSH*/ + { (const char*)"SSH-%*f-paramiko_%d.%d.%d", 2, 4, 0 }, /* Paramiko SSH */ + { (const char*)"SSH-%*f-dropbear_%d.%d", 2020, 0, 0 }, /* Dropbear SSH */ + { NULL, 0, 0, 0 } + }; + + for(i = 0; ssh_servers_strings[i].signature != NULL; i++) { + int matches; + int major = 0; + int minor = 0; + int patch = 0; + matches = sscanf(str_to_check, ssh_servers_strings[i].signature, &major, &minor, &patch); + + if(matches == 3 || matches == 2) { + /* checking if is an old version */ + if(major < ssh_servers_strings[i].major) + obsolete_ssh_version = 1; + else if(major == ssh_servers_strings[i].major) { + if(minor < ssh_servers_strings[i].minor) + obsolete_ssh_version = 1; + else if(minor == ssh_servers_strings[i].minor) + if(patch < ssh_servers_strings[i].patch) + obsolete_ssh_version = 1; + } + +#ifdef SSH_DEBUG + printf("[SSH] [SSH Version: %d.%d.%d]\n", major, minor, patch); +#endif + + break; + } + } + + if(obsolete_ssh_version) + NDPI_SET_BIT(flow->risk, + is_client_signature ? NDPI_SSH_OBSOLETE_CLIENT_VERSION_OR_CIPHER : + NDPI_SSH_OBSOLETE_SERVER_VERSION_OR_CIPHER); +} + +/* ************************************************************************ */ + +static void ssh_analyse_cipher(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + char *ciphers, u_int cipher_len, + u_int8_t is_client_signature) { + + char *rem; + char *cipher; + u_int8_t found_obsolete_cipher = 0; + char *cipher_copy; + /* + List of obsolete ciphers can be found at + https://www.linuxminion.com/deprecated-ssh-cryptographic-settings/ + */ + const char *obsolete_ciphers[] = { + "arcfour256", + "arcfour128", + "3des-cbc", + "blowfish-cbc", + "cast128-cbc", + "arcfour", + NULL, + }; + + if((cipher_copy = (char*)ndpi_malloc(cipher_len+1)) == NULL) { +#ifdef SSH_DEBUG + printf("[SSH] Nout enough memory\n"); +#endif + return; + } + + strncpy(cipher_copy, ciphers, cipher_len); + cipher_copy[cipher_len] = '\0'; + + cipher = strtok_r(cipher_copy, ",", &rem); + + while(cipher && !found_obsolete_cipher) { + u_int i; + + for(i = 0; obsolete_ciphers[i]; i++) { + if(strcmp(cipher, obsolete_ciphers[i]) == 0) { + found_obsolete_cipher = 1; +#ifdef SSH_DEBUG + printf("[SSH] [SSH obsolete %s cipher][%s]\n", + is_client_signature ? "client" : "server", + obsolete_ciphers[i]); +#endif + break; + } + } + + cipher = strtok_r(NULL, ",", &rem); + } + + if(found_obsolete_cipher) { + NDPI_SET_BIT(flow->risk, (is_client_signature ? NDPI_SSH_OBSOLETE_CLIENT_VERSION_OR_CIPHER : NDPI_SSH_OBSOLETE_SERVER_VERSION_OR_CIPHER)); + } + + ndpi_free(cipher_copy); +} + +/* ************************************************************************ */ + +static int search_ssh_again(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { + ndpi_search_ssh_tcp(ndpi_struct, flow); + + if((flow->protos.ssh.hassh_client[0] != '\0') + && (flow->protos.ssh.hassh_server[0] != '\0')) { + /* stop extra processing */ + flow->extra_packets_func = NULL; /* We're good now */ + return(0); + } + + /* Possibly more processing */ + return(1); +} /* ************************************************************************ */ static void ndpi_int_ssh_add_connection(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { + if(flow->extra_packets_func != NULL) + return; + + flow->guessed_host_protocol_id = flow->guessed_protocol_id = NDPI_PROTOCOL_SSH; + + /* This is necessary to inform the core to call this dissector again */ + flow->check_extra_packets = 1; + flow->max_extra_packets_to_check = 12; + flow->extra_packets_func = search_ssh_again; + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_SSH, NDPI_PROTOCOL_UNKNOWN); } /* ************************************************************************ */ -static u_int16_t concat_hash_string(struct ndpi_packet_struct *packet, - char *buf, u_int8_t client_hash) { - u_int16_t offset = 22, buf_out_len = 0; - u_int32_t len = ntohl(*(u_int32_t*)&packet->payload[offset]); +static u_int16_t concat_hash_string(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + struct ndpi_packet_struct *packet, + char *buf, u_int8_t client_hash) { + u_int32_t offset = 22, len, buf_out_len = 0, max_payload_len = packet->payload_packet_len-sizeof(u_int32_t); + const u_int32_t len_max = 65565; + + if(offset >= max_payload_len) + goto invalid_payload; + + len = ntohl(*(u_int32_t*)&packet->payload[offset]); offset += 4; /* -1 for ';' */ @@ -80,118 +238,146 @@ static u_int16_t concat_hash_string(struct ndpi_packet_struct *packet, buf[buf_out_len++] = ';'; offset += len; + if(offset >= max_payload_len) + goto invalid_payload; + /* ssh.server_host_key_algorithms [None] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); + + if(len > len_max) + goto invalid_payload; offset += 4 + len; + if(offset >= max_payload_len) + goto invalid_payload; + /* ssh.encryption_algorithms_client_to_server [C] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); + offset += 4; if(client_hash) { - offset += 4; - if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); + ssh_analyse_cipher(ndpi_struct, flow, (char*)&packet->payload[offset], len, 1 /* client */); buf_out_len += len; buf[buf_out_len++] = ';'; - offset += len; - } else - offset += 4 + len; + } + + if(len > len_max) + goto invalid_payload; + offset += len; + + if(offset >= max_payload_len) + goto invalid_payload; /* ssh.encryption_algorithms_server_to_client [S] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); + offset += 4; if(!client_hash) { - offset += 4; - if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); + ssh_analyse_cipher(ndpi_struct, flow, (char*)&packet->payload[offset], len, 0 /* server */); buf_out_len += len; buf[buf_out_len++] = ';'; - offset += len; - } else - offset += 4 + len; + } + + if(len > len_max) + goto invalid_payload; + offset += len; + if(offset >= max_payload_len) + goto invalid_payload; /* ssh.mac_algorithms_client_to_server [C] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); + offset += 4; if(client_hash) { - offset += 4; - if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; buf[buf_out_len++] = ';'; - offset += len; - } else - offset += 4 + len; + } + + if(len > len_max) + goto invalid_payload; + offset += len; + if(offset >= max_payload_len) + goto invalid_payload; /* ssh.mac_algorithms_server_to_client [S] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); + offset += 4; if(!client_hash) { - offset += 4; - if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; buf[buf_out_len++] = ';'; - offset += len; - } else - offset += 4 + len; + } + + if(len > len_max) + goto invalid_payload; + offset += len; /* ssh.compression_algorithms_client_to_server [C] */ + if(offset >= max_payload_len) + goto invalid_payload; + len = ntohl(*(u_int32_t*)&packet->payload[offset]); + offset += 4; if(client_hash) { - offset += 4; - if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; - offset += len; - } else - offset += 4 + len; + } + + if(len > len_max) + goto invalid_payload; + offset += len; + if(offset >= max_payload_len) + goto invalid_payload; /* ssh.compression_algorithms_server_to_client [S] */ len = ntohl(*(u_int32_t*)&packet->payload[offset]); + offset += 4; if(!client_hash) { - offset += 4; - if((offset >= packet->payload_packet_len) || (len >= packet->payload_packet_len-offset-1)) goto invalid_payload; strncpy(&buf[buf_out_len], (const char *)&packet->payload[offset], len); buf_out_len += len; - offset += len; - } else - offset += 4 + len; + } + + if(len > len_max) + goto invalid_payload; + offset += len; /* ssh.languages_client_to_server [None] */ /* ssh.languages_server_to_client [None] */ #ifdef SSH_DEBUG - printf("\n[SSH] %s\n", buf); + printf("[SSH] %s\n", buf); #endif return(buf_out_len); -invalid_payload: - + invalid_payload: #ifdef SSH_DEBUG - printf("\n[SSH] Invalid packet payload\n"); + printf("[SSH] Invalid packet payload\n"); #endif return(0); @@ -213,78 +399,75 @@ static void ndpi_ssh_zap_cr(char *str, int len) { /* ************************************************************************ */ -void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { +static void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; #ifdef SSH_DEBUG - printf("\n[SSH] [stage: %u]\n", flow->l4.tcp.ssh_stage); + printf("[SSH] %s()\n", __FUNCTION__); #endif if(flow->l4.tcp.ssh_stage == 0) { if(packet->payload_packet_len > 7 && packet->payload_packet_len < 100 - && memcmp(packet->payload, "SSH-", 4) == 0) { - if(!ndpi_struct->disable_metadata_export) { - int len = ndpi_min(sizeof(flow->protos.ssh.client_signature)-1, packet->payload_packet_len); - - strncpy(flow->protos.ssh.client_signature, (const char *)packet->payload, len); - flow->protos.ssh.client_signature[len] = '\0'; - ndpi_ssh_zap_cr(flow->protos.ssh.client_signature, len); - + && memcmp(packet->payload, "SSH-", 4) == 0) { + int len = ndpi_min(sizeof(flow->protos.ssh.client_signature)-1, packet->payload_packet_len); + + strncpy(flow->protos.ssh.client_signature, (const char *)packet->payload, len); + flow->protos.ssh.client_signature[len] = '\0'; + ndpi_ssh_zap_cr(flow->protos.ssh.client_signature, len); + + ssh_analyze_signature_version(ndpi_struct, flow, flow->protos.ssh.client_signature, 1); + #ifdef SSH_DEBUG - printf("\n[SSH] [client_signature: %s]\n", flow->protos.ssh.client_signature); -#endif - } - + printf("[SSH] [client_signature: %s]\n", flow->protos.ssh.client_signature); +#endif + NDPI_LOG_DBG2(ndpi_struct, "ssh stage 0 passed\n"); flow->l4.tcp.ssh_stage = 1 + packet->packet_direction; - flow->guessed_host_protocol_id = flow->guessed_protocol_id = NDPI_PROTOCOL_SSH; + ndpi_int_ssh_add_connection(ndpi_struct, flow); return; } } else if(flow->l4.tcp.ssh_stage == (2 - packet->packet_direction)) { if(packet->payload_packet_len > 7 && packet->payload_packet_len < 500 - && memcmp(packet->payload, "SSH-", 4) == 0) { - if(!ndpi_struct->disable_metadata_export) { - int len = ndpi_min(sizeof(flow->protos.ssh.server_signature)-1, packet->payload_packet_len); - - strncpy(flow->protos.ssh.server_signature, (const char *)packet->payload, len); - flow->protos.ssh.server_signature[len] = '\0'; - ndpi_ssh_zap_cr(flow->protos.ssh.server_signature, len); - + && memcmp(packet->payload, "SSH-", 4) == 0) { + int len = ndpi_min(sizeof(flow->protos.ssh.server_signature)-1, packet->payload_packet_len); + + strncpy(flow->protos.ssh.server_signature, (const char *)packet->payload, len); + flow->protos.ssh.server_signature[len] = '\0'; + ndpi_ssh_zap_cr(flow->protos.ssh.server_signature, len); + + ssh_analyze_signature_version(ndpi_struct, flow, flow->protos.ssh.server_signature, 0); + #ifdef SSH_DEBUG - printf("\n[SSH] [server_signature: %s]\n", flow->protos.ssh.server_signature); + printf("[SSH] [server_signature: %s]\n", flow->protos.ssh.server_signature); #endif - - NDPI_LOG_DBG2(ndpi_struct, "ssh stage 1 passed\n"); - flow->guessed_host_protocol_id = flow->guessed_protocol_id = NDPI_PROTOCOL_SSH; - } else { - NDPI_LOG_INFO(ndpi_struct, "found ssh\n"); - ndpi_int_ssh_add_connection(ndpi_struct, flow); - } - + + NDPI_LOG_DBG2(ndpi_struct, "ssh stage 1 passed\n"); + flow->guessed_host_protocol_id = flow->guessed_protocol_id = NDPI_PROTOCOL_SSH; + #ifdef SSH_DEBUG - printf("\n[SSH] [completed stage: %u]\n", flow->l4.tcp.ssh_stage); + printf("[SSH] [completed stage: %u]\n", flow->l4.tcp.ssh_stage); #endif flow->l4.tcp.ssh_stage = 3; return; } - } else { + } else if(packet->payload_packet_len > 5) { u_int8_t msgcode = *(packet->payload + 5); ndpi_MD5_CTX ctx; + + if(msgcode == 20 /* key exchange init */) { + char *hassh_buf = ndpi_calloc(packet->payload_packet_len, sizeof(char)); + u_int i, len; #ifdef SSH_DEBUG - printf("\n[SSH] [stage: %u][msg: %u]\n", flow->l4.tcp.ssh_stage, msgcode); + printf("[SSH] [stage: %u][msg: %u][direction: %u][key exchange init]\n", flow->l4.tcp.ssh_stage, msgcode, packet->packet_direction); #endif - if(msgcode == 20 /* key exchange init */) { - char *hassh_buf = calloc(packet->payload_packet_len, sizeof(char)); - u_int i, len; - if(hassh_buf) { - if(flow->l4.tcp.ssh_stage == 3) { + if(packet->packet_direction == 0 /* client */) { u_char fingerprint_client[16]; - len = concat_hash_string(packet, hassh_buf, 1 /* client */); + len = concat_hash_string(ndpi_struct, flow, packet, hassh_buf, 1 /* client */); ndpi_MD5Init(&ctx); ndpi_MD5Update(&ctx, (const unsigned char *)hassh_buf, len); @@ -292,7 +475,7 @@ void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc #ifdef SSH_DEBUG { - printf("\n[SSH] [client][%s][", hassh_buf); + printf("[SSH] [client][%s][", hassh_buf); for(i=0; i<16; i++) printf("%02X", fingerprint_client[i]); printf("]\n"); } @@ -302,7 +485,7 @@ void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc } else { u_char fingerprint_server[16]; - len = concat_hash_string(packet, hassh_buf, 0 /* server */); + len = concat_hash_string(ndpi_struct, flow, packet, hassh_buf, 0 /* server */); ndpi_MD5Init(&ctx); ndpi_MD5Update(&ctx, (const unsigned char *)hassh_buf, len); @@ -310,7 +493,7 @@ void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc #ifdef SSH_DEBUG { - printf("\n[SSH] [server][%s][", hassh_buf); + printf("[SSH] [server][%s][", hassh_buf); for(i=0; i<16; i++) printf("%02X", fingerprint_server[i]); printf("]\n"); } @@ -320,20 +503,24 @@ void ndpi_search_ssh_tcp(struct ndpi_detection_module_struct *ndpi_struct, struc flow->protos.ssh.hassh_server[32] = '\0'; } - free(hassh_buf); + ndpi_free(hassh_buf); } - } - if(flow->l4.tcp.ssh_stage++ == 4) { - NDPI_LOG_INFO(ndpi_struct, "found ssh\n"); ndpi_int_ssh_add_connection(ndpi_struct, flow); } + if((flow->protos.ssh.hassh_client[0] != '\0') && (flow->protos.ssh.hassh_server[0] != '\0')) { +#ifdef SSH_DEBUG + printf("[SSH] Dissection completed\n"); +#endif + flow->extra_packets_func = NULL; /* We're good now */ + } + return; } #ifdef SSH_DEBUG - printf("\n[SSH] Excluding SSH"); + printf("[SSH] Excluding SSH"); #endif NDPI_LOG_DBG(ndpi_struct, "excluding ssh at stage %d\n", flow->l4.tcp.ssh_stage); diff --git a/src/lib/protocols/starcraft.c b/src/lib/protocols/starcraft.c index 9cc8abb..144a69c 100644 --- a/src/lib/protocols/starcraft.c +++ b/src/lib/protocols/starcraft.c @@ -2,7 +2,7 @@ * starcraft.c * * Copyright (C) 2015 - Matteo Bracci -* Copyright (C) 2015-19 - ntop.org +* Copyright (C) 2015-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/stealthnet.c b/src/lib/protocols/stealthnet.c index d9f1379..5de88af 100644 --- a/src/lib/protocols/stealthnet.c +++ b/src/lib/protocols/stealthnet.c @@ -2,7 +2,7 @@ * stealthnet.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/steam.c b/src/lib/protocols/steam.c index 6e1034a..198286c 100644 --- a/src/lib/protocols/steam.c +++ b/src/lib/protocols/steam.c @@ -1,7 +1,7 @@ /* * steam.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2014 Tomasz Bujlow * * The signature is mostly based on the Libprotoident library @@ -267,7 +267,6 @@ void ndpi_search_steam(struct ndpi_detection_module_struct *ndpi_struct, struct return; } - /* skip marked or retransmitted packets */ if(packet->tcp_retransmission != 0) { return; diff --git a/src/lib/protocols/stun.c b/src/lib/protocols/stun.c index 448062f..8db5891 100644 --- a/src/lib/protocols/stun.c +++ b/src/lib/protocols/stun.c @@ -1,7 +1,7 @@ /* * stun.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -27,7 +27,7 @@ #include "ndpi_api.h" -#define MAX_NUM_STUN_PKTS 8 +#define MAX_NUM_STUN_PKTS 3 // #define DEBUG_STUN 1 // #define DEBUG_LRU 1 @@ -38,7 +38,6 @@ struct stun_packet_header { u_int8_t transaction_id[8]; }; - /* ************************************************************ */ u_int32_t get_stun_lru_key(struct ndpi_flow_struct *flow, u_int8_t rev) { @@ -89,7 +88,12 @@ void ndpi_int_stun_add_connection(struct ndpi_detection_module_struct *ndpi_stru #endif ndpi_lru_add_to_cache(ndpi_struct->stun_cache, key, app_proto); + if(ndpi_struct->ndpi_notify_lru_add_handler_ptr) + ndpi_struct->ndpi_notify_lru_add_handler_ptr(ndpi_stun_cache, key, app_proto); + ndpi_lru_add_to_cache(ndpi_struct->stun_cache, key_rev, app_proto); + if(ndpi_struct->ndpi_notify_lru_add_handler_ptr) + ndpi_struct->ndpi_notify_lru_add_handler_ptr(ndpi_stun_cache, key_rev, app_proto); } } } @@ -118,15 +122,15 @@ static int is_google_ip_address(u_int32_t host) { /* ************************************************************ */ /* - WhatsApp - 31.13.86.48 - 31.13.92.50 - 157.240.20.51 - 157.240.21.51 - 185.60.216.51 - - Messenger - 31.13.86.5 + WhatsApp + 31.13.86.48 + 31.13.92.50 + 157.240.20.51 + 157.240.21.51 + 185.60.216.51 + + Messenger + 31.13.86.5 */ static int is_messenger_ip_address(u_int32_t host) { @@ -143,12 +147,13 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * const u_int8_t * payload, const u_int16_t payload_length) { u_int16_t msg_type, msg_len; - struct stun_packet_header *h = (struct stun_packet_header*)payload; int rc; - /* STUN over TCP does not look good */ - if (flow->packet.tcp) + /* No need to do ntohl() with 0xFFFFFFFF */ + if(flow->packet.iph && (flow->packet.iph->daddr == 0xFFFFFFFF /* 255.255.255.255 */)) { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return(NDPI_IS_NOT_STUN); + } if(payload_length >= 512) { return(NDPI_IS_NOT_STUN); @@ -156,7 +161,7 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * /* This looks like an invalid packet */ if(flow->protos.stun_ssl.stun.num_udp_pkts > 0) { - flow->guessed_host_protocol_id = NDPI_PROTOCOL_WHATSAPP_CALL; + // flow->guessed_host_protocol_id = NDPI_PROTOCOL_WHATSAPP_CALL; return(NDPI_IS_STUN); } else return(NDPI_IS_NOT_STUN); @@ -168,37 +173,38 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * goto udp_stun_found; } - msg_type = ntohs(h->msg_type), msg_len = ntohs(h->msg_len); + msg_type = ntohs(*((u_int16_t*)payload)); + msg_len = ntohs(*((u_int16_t*)&payload[2])); if(msg_type == 0) return(NDPI_IS_NOT_STUN); /* https://www.iana.org/assignments/stun-parameters/stun-parameters.xhtml */ - if ((msg_type & 0x3EEF) > 0x000B && msg_type != 0x0800) { + if((msg_type & 0x3EEF) > 0x000B && msg_type != 0x0800) { #ifdef DEBUG_STUN printf("[STUN] msg_type = %04X\n", msg_type); #endif /* - If we're here it's because this does not look like STUN anymore - as this was a flow that started as STUN and turned into something - else. Let's investigate what is that about - */ - if (payload[0] == 0x16) { + If we're here it's because this does not look like STUN anymore + as this was a flow that started as STUN and turned into something + else. Let's investigate what is that about + */ + if(payload[0] == 0x16) { /* Let's check if this is DTLS used by some socials */ struct ndpi_packet_struct *packet = &flow->packet; u_int16_t total_len, version = htons(*((u_int16_t*) &packet->payload[1])); switch (version) { - case 0xFEFF: /* DTLS 1.0 */ - case 0xFEFD: /* DTLS 1.2 */ - total_len = ntohs(*((u_int16_t*) &packet->payload[11])) + 13; - - if (payload_length == total_len) { - /* This is DTLS and the only protocol we know behaves like this is signal */ - flow->guessed_host_protocol_id = NDPI_PROTOCOL_SIGNAL; - return(NDPI_IS_STUN); - } + case 0xFEFF: /* DTLS 1.0 */ + case 0xFEFD: /* DTLS 1.2 */ + total_len = ntohs(*((u_int16_t*) &packet->payload[11])) + 13; + + if(payload_length == total_len) { + /* This is DTLS and the only protocol we know behaves like this is signal */ + flow->guessed_host_protocol_id = NDPI_PROTOCOL_SIGNAL; + return(NDPI_IS_STUN); + } } } @@ -208,11 +214,11 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * #if 0 if((flow->packet.udp->dest == htons(3480)) || (flow->packet.udp->source == htons(3480)) - ) + ) printf("[STUN] Here we go\n");; #endif - if (ndpi_struct->stun_cache) { + if(ndpi_struct->stun_cache) { u_int16_t proto; u_int32_t key = get_stun_lru_key(flow, 0); int rc = ndpi_lru_find_cache(ndpi_struct->stun_cache, key, &proto, @@ -222,7 +228,7 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * printf("[LRU] Searching %u\n", key); #endif - if (!rc) { + if(!rc) { key = get_stun_lru_key(flow, 1); rc = ndpi_lru_find_cache(ndpi_struct->stun_cache, key, &proto, 0 /* Don't remove it as it can be used for other connections */); @@ -232,7 +238,7 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * #endif } - if (rc) { + if(rc) { #ifdef DEBUG_LRU printf("[LRU] Cache FOUND %u / %u\n", key, proto); #endif @@ -253,18 +259,18 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * if(msg_type == 0x01 /* Binding Request */) { flow->protos.stun_ssl.stun.num_binding_requests++; - if (!msg_len && flow->guessed_host_protocol_id == NDPI_PROTOCOL_GOOGLE) + if(!msg_len && flow->guessed_host_protocol_id == NDPI_PROTOCOL_GOOGLE) flow->guessed_host_protocol_id = NDPI_PROTOCOL_HANGOUT_DUO; else flow->guessed_protocol_id = NDPI_PROTOCOL_STUN; - if (!msg_len) { + if(!msg_len) { /* flow->protos.stun_ssl.stun.num_udp_pkts++; */ return(NDPI_IS_NOT_STUN); /* This to keep analyzing STUN instead of giving up */ } } - if (!msg_len && flow->guessed_host_protocol_id == NDPI_PROTOCOL_UNKNOWN) { + if(!msg_len && flow->guessed_host_protocol_id == NDPI_PROTOCOL_UNKNOWN) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return(NDPI_IS_NOT_STUN); } @@ -275,12 +281,12 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * flow->guessed_host_protocol_id = NDPI_PROTOCOL_WHATSAPP_CALL; return(NDPI_IS_STUN); /* This is WhatsApp Call */ } else if((payload[0] == 0x90) && (((msg_len+11) == payload_length) || - (flow->protos.stun_ssl.stun.num_binding_requests >= 4))) { + (flow->protos.stun_ssl.stun.num_binding_requests >= 4))) { flow->guessed_host_protocol_id = NDPI_PROTOCOL_WHATSAPP_CALL; return(NDPI_IS_STUN); /* This is WhatsApp Call */ } - if (payload[0] != 0x80 && (msg_len + 20) > payload_length) + if(payload[0] != 0x80 && (msg_len + 20) > payload_length) return(NDPI_IS_NOT_STUN); else { switch(flow->guessed_protocol_id) { @@ -296,25 +302,25 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * } } - if (payload_length == (msg_len+20)) { - if ((msg_type & 0x3EEF) <= 0x000B) /* http://www.3cx.com/blog/voip-howto/stun-details/ */ { + if(payload_length == (msg_len+20)) { + if((msg_type & 0x3EEF) <= 0x000B) /* http://www.3cx.com/blog/voip-howto/stun-details/ */ { u_int offset = 20; /* - This can either be the standard RTCP or Ms Lync RTCP that - later will become Ms Lync RTP. In this case we need to - be careful before deciding about the protocol before dissecting the packet + This can either be the standard RTCP or Ms Lync RTCP that + later will become Ms Lync RTP. In this case we need to + be careful before deciding about the protocol before dissecting the packet - MS Lync = Skype - https://en.wikipedia.org/wiki/Skype_for_Business - */ + MS Lync = Skype + https://en.wikipedia.org/wiki/Skype_for_Business + */ - while((offset+2) < payload_length) { + while((offset+4) < payload_length) { u_int16_t attribute = ntohs(*((u_int16_t*)&payload[offset])); u_int16_t len = ntohs(*((u_int16_t*)&payload[offset+2])); u_int16_t x = (len + 4) % 4; - if (x) + if(x) len += 4-x; #ifdef DEBUG_STUN @@ -336,36 +342,40 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * break; case 0x0014: /* Realm */ - { - u_int16_t realm_len = ntohs(*((u_int16_t*)&payload[offset+2])); - - if(flow->host_server_name[0] == '\0') { - u_int j, i = (realm_len > sizeof(flow->host_server_name)) ? sizeof(flow->host_server_name) : realm_len; - u_int k = offset+4; - - memset(flow->host_server_name, 0, sizeof(flow->host_server_name)); - - for(j=0; jhost_server_name[j] = payload[k++]; - + { + u_int16_t realm_len = ntohs(*((u_int16_t*)&payload[offset+2])); + + if(flow->host_server_name[0] == '\0') { + u_int j, i = (realm_len > sizeof(flow->host_server_name)) ? sizeof(flow->host_server_name) : realm_len; + u_int k = offset+4; + + memset(flow->host_server_name, 0, sizeof(flow->host_server_name)); + + for(j=0; jhost_server_name[j] = payload[k++]; + else + break; + } + #ifdef DEBUG_STUN - printf("==> [%s]\n", flow->host_server_name); + printf("==> [%s]\n", flow->host_server_name); #endif - if (strstr((char*) flow->host_server_name, "google.com") != NULL) { + if(strstr((char*) flow->host_server_name, "google.com") != NULL) { flow->guessed_host_protocol_id = NDPI_PROTOCOL_HANGOUT_DUO; return(NDPI_IS_STUN); - } else if (strstr((char*) flow->host_server_name, "whispersystems.org") != NULL) { - flow->guessed_host_protocol_id = NDPI_PROTOCOL_SIGNAL; - return(NDPI_IS_STUN); - } - } - } - break; + } else if(strstr((char*) flow->host_server_name, "whispersystems.org") != NULL) { + flow->guessed_host_protocol_id = NDPI_PROTOCOL_SIGNAL; + return(NDPI_IS_STUN); + } + } + } + break; case 0xC057: /* Messeger */ - if (msg_type == 0x0001) { - if ((msg_len == 100) || (msg_len == 104)) { + if(msg_type == 0x0001) { + if((msg_len == 100) || (msg_len == 104)) { flow->guessed_host_protocol_id = NDPI_PROTOCOL_MESSENGER; return(NDPI_IS_STUN); } else if(msg_len == 76) { @@ -416,7 +426,7 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * break; case 0x8070: /* Implementation Version */ - if (len == 4 && ((offset+7) < payload_length) + if(len == 4 && ((offset+7) < payload_length) && (payload[offset+4] == 0x00) && (payload[offset+5] == 0x00) && (payload[offset+6] == 0x00) && ((payload[offset+7] == 0x02) || (payload[offset+7] == 0x03))) { #ifdef DEBUG_STUN @@ -450,13 +460,13 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * } } - if ((flow->protos.stun_ssl.stun.num_udp_pkts > 0) && (msg_type <= 0x00FF)) { + if((flow->protos.stun_ssl.stun.num_udp_pkts > 0) && (msg_type <= 0x00FF)) { flow->guessed_host_protocol_id = NDPI_PROTOCOL_WHATSAPP_CALL; return(NDPI_IS_STUN); } else return(NDPI_IS_NOT_STUN); -udp_stun_found: + udp_stun_found: flow->protos.stun_ssl.stun.num_processed_pkts++; struct ndpi_packet_struct *packet = &flow->packet; @@ -475,7 +485,6 @@ static ndpi_int_stun_t ndpi_int_check_stun(struct ndpi_detection_module_struct * return rc; } - void ndpi_search_stun(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; @@ -507,7 +516,7 @@ void ndpi_search_stun(struct ndpi_detection_module_struct *ndpi_struct, struct n if(ndpi_int_check_stun(ndpi_struct, flow, packet->payload, packet->payload_packet_len) == NDPI_IS_STUN) { udp_stun_match: - if (flow->guessed_protocol_id == NDPI_PROTOCOL_UNKNOWN) + if(flow->guessed_protocol_id == NDPI_PROTOCOL_UNKNOWN) flow->guessed_protocol_id = NDPI_PROTOCOL_STUN; if(flow->guessed_host_protocol_id == NDPI_PROTOCOL_UNKNOWN) { @@ -521,13 +530,12 @@ void ndpi_search_stun(struct ndpi_detection_module_struct *ndpi_struct, struct n return; } - if(flow->protos.stun_ssl.stun.num_udp_pkts >= MAX_NUM_STUN_PKTS) - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - if(flow->packet_counter > 0) { /* This might be a RTP stream: let's make sure we check it */ NDPI_CLR(&flow->excluded_protocol_bitmask, NDPI_PROTOCOL_RTP); } + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } @@ -536,7 +544,7 @@ void init_stun_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int ndpi_set_bitmask_protocol_detection("STUN", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_STUN, ndpi_search_stun, - NDPI_SELECTION_BITMASK_PROTOCOL_TCP_OR_UDP_WITH_PAYLOAD, + NDPI_SELECTION_BITMASK_PROTOCOL_UDP_WITH_PAYLOAD, SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); diff --git a/src/lib/protocols/syslog.c b/src/lib/protocols/syslog.c index 80c6a24..d355279 100644 --- a/src/lib/protocols/syslog.c +++ b/src/lib/protocols/syslog.c @@ -2,7 +2,7 @@ * syslog.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/tcp_udp.c b/src/lib/protocols/tcp_udp.c index cb0223d..66050c1 100644 --- a/src/lib/protocols/tcp_udp.c +++ b/src/lib/protocols/tcp_udp.c @@ -1,7 +1,7 @@ /* * tcp_or_udp.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/teamspeak.c b/src/lib/protocols/teamspeak.c index 0fb538e..a2a1002 100644 --- a/src/lib/protocols/teamspeak.c +++ b/src/lib/protocols/teamspeak.c @@ -24,7 +24,7 @@ #include "ndpi_api.h" static void ndpi_int_teamspeak_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) + *ndpi_struct, struct ndpi_flow_struct *flow) { ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TEAMSPEAK, NDPI_PROTOCOL_UNKNOWN); } @@ -36,56 +36,38 @@ void ndpi_search_teamspeak(struct ndpi_detection_module_struct *ndpi_struct, str NDPI_LOG_DBG(ndpi_struct, "search teamspeak\n"); - -#ifdef WEAK_DETECTION_CODE_DISABLED - if(packet->udp != NULL) { - u_int16_t udport, usport; - - usport = ntohs(packet->udp->source), udport = ntohs(packet->udp->dest); - - /* http://www.imfirewall.com/en/protocols/teamSpeak.htm */ - if(((usport == 9987 || udport == 9987) || (usport == 8767 || udport == 8767)) && packet->payload_packet_len >= 20) { - NDPI_LOG_INFO(ndpi_struct, "found TEAMSPEAK udp\n"); - ndpi_int_teamspeak_add_connection(ndpi_struct, flow); - } - } - else -#endif - - if(packet->tcp != NULL) { -#if WEAK_DETECTION_CODE_DISABLED - u_int16_t tdport, tsport; - tsport = ntohs(packet->tcp->source), tdport = ntohs(packet->tcp->dest); -#endif - /* https://github.com/Youx/soliloque-server/wiki/Connection-packet */ - if(packet->payload_packet_len >= 20) { - if(((memcmp(packet->payload, "\xf4\xbe\x03\x00", 4) == 0)) || - ((memcmp(packet->payload, "\xf4\xbe\x02\x00", 4) == 0)) || - ((memcmp(packet->payload, "\xf4\xbe\x01\x00", 4) == 0))) { - NDPI_LOG_INFO(ndpi_struct, "found TEAMSPEAK tcp\n"); - ndpi_int_teamspeak_add_connection(ndpi_struct, flow); - } /* http://www.imfirewall.com/en/protocols/teamSpeak.htm */ + if (packet->payload_packet_len >= 20) { + if (packet->udp != NULL) { + if (memcmp(packet->payload, "TS3INIT1", strlen("TS3INIT1")) == 0) + { + NDPI_LOG_INFO(ndpi_struct, "found TEAMSPEAK udp\n"); + ndpi_int_teamspeak_add_connection(ndpi_struct, flow); } -#if WEAK_DETECTION_CODE_DISABLED - else if((tsport == 14534 || tdport == 14534) || (tsport == 51234 || tdport == 51234)) { - NDPI_LOG_INFO(ndpi_struct, "found TEAMSPEAK\n"); - ndpi_int_teamspeak_add_connection(ndpi_struct, flow); - } -#endif + } else if(packet->tcp != NULL) { + /* https://github.com/Youx/soliloque-server/wiki/Connection-packet */ + if(((memcmp(packet->payload, "\xf4\xbe\x03\x00", 4) == 0)) || + ((memcmp(packet->payload, "\xf4\xbe\x02\x00", 4) == 0)) || + ((memcmp(packet->payload, "\xf4\xbe\x01\x00", 4) == 0))) + { + NDPI_LOG_INFO(ndpi_struct, "found TEAMSPEAK tcp\n"); + ndpi_int_teamspeak_add_connection(ndpi_struct, flow); + } /* http://www.imfirewall.com/en/protocols/teamSpeak.htm */ } - + } + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; } -void init_teamspeak_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) +void init_teamspeak_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, + NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("TeamSpeak", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_TEAMSPEAK, - ndpi_search_teamspeak, - NDPI_SELECTION_BITMASK_PROTOCOL_TCP_OR_UDP_WITH_PAYLOAD, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); + NDPI_PROTOCOL_TEAMSPEAK, + ndpi_search_teamspeak, + NDPI_SELECTION_BITMASK_PROTOCOL_TCP_OR_UDP_WITH_PAYLOAD, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); *id += 1; } diff --git a/src/lib/protocols/teamviewer.c b/src/lib/protocols/teamviewer.c index 33de448..b3b26ad 100644 --- a/src/lib/protocols/teamviewer.c +++ b/src/lib/protocols/teamviewer.c @@ -2,7 +2,7 @@ * teamviewer.c * * Copyright (C) 2012 by Gianluca Costa xplico.org - * Copyright (C) 2012-19 - ntop.org + * Copyright (C) 2012-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/telegram.c b/src/lib/protocols/telegram.c index 8a54258..061262e 100644 --- a/src/lib/protocols/telegram.c +++ b/src/lib/protocols/telegram.c @@ -1,8 +1,8 @@ /* * telegram.c * + * Copyright (C) 2012-20 - ntop.org * Copyright (C) 2014 by Gianluca Costa xplico.org - * Copyright (C) 2012-19 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -30,39 +30,77 @@ #include "ndpi_api.h" static void ndpi_int_telegram_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ + *ndpi_struct, struct ndpi_flow_struct *flow) { ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TELEGRAM, NDPI_PROTOCOL_UNKNOWN); NDPI_LOG_INFO(ndpi_struct, "found telegram\n"); } +static u_int8_t is_telegram_port_range(u_int16_t port) { + if((port >= 500) && (port <= 600)) + return(1); -void ndpi_search_telegram(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ + + return(0); +} + +void ndpi_search_telegram(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; - u_int16_t dport /* , sport */; - + NDPI_LOG_DBG(ndpi_struct, "search telegram\n"); - if (packet->payload_packet_len == 0) + if(packet->payload_packet_len == 0) return; - if (packet->tcp != NULL) { - if (packet->payload_packet_len > 56) { - dport = ntohs(packet->tcp->dest); - /* sport = ntohs(packet->tcp->source); */ - - if (packet->payload[0] == 0xef && ( - dport == 443 || dport == 80 || dport == 25 - )) { - if (packet->payload[1] == 0x7f) { + + if(packet->tcp != NULL) { + if(packet->payload_packet_len > 56) { + u_int16_t dport = ntohs(packet->tcp->dest); + /* u_int16_t sport = ntohs(packet->tcp->source); */ + + if(packet->payload[0] == 0xef && (dport == 443 || dport == 80 || dport == 25)) { + if(packet->payload[1] == 0x7f) { ndpi_int_telegram_add_connection(ndpi_struct, flow); - } - else if (packet->payload[1]*4 <= packet->payload_packet_len - 1) { + } else if(packet->payload[1]*4 <= packet->payload_packet_len - 1) { ndpi_int_telegram_add_connection(ndpi_struct, flow); } return; } } + } else if(packet->udp != NULL) { + /* + The latest telegram protocol + - contains a sequence of 12 consecutive 0xFF packets + - it uses low UDP ports in the 500 ramge + */ + + if(packet->payload_packet_len >= 40) { + u_int16_t sport = ntohs(packet->udp->source), dport = ntohs(packet->udp->dest); + + if(is_telegram_port_range(sport) || is_telegram_port_range(dport)) { + u_int i=0, found = 0; + + for(i=0; ipayload_packet_len; i++) { + if(packet->payload[i] == 0xFF) { + found = 1; + break; + } + } + + if(!found) return; + + for(i += 1; ipayload_packet_len; i++) { + if(packet->payload[i] == 0xFF) + found++; + else + break; + } + + if(found == 12) { + ndpi_int_telegram_add_connection(ndpi_struct, flow); + return; + } + } + } } NDPI_EXCLUDE_PROTO(ndpi_struct, flow); @@ -80,4 +118,3 @@ void init_telegram_dissector(struct ndpi_detection_module_struct *ndpi_struct, u *id += 1; } - diff --git a/src/lib/protocols/telnet.c b/src/lib/protocols/telnet.c index e293fc9..bc3211f 100644 --- a/src/lib/protocols/telnet.c +++ b/src/lib/protocols/telnet.c @@ -1,8 +1,8 @@ /* * telnet.c * + * Copyright (C) 2011-20 - ntop.org * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -19,7 +19,7 @@ * * You should have received a copy of the GNU Lesser General Public License * along with nDPI. If not, see . - * + * */ @@ -29,14 +29,98 @@ #include "ndpi_api.h" +/* #define TELNET_DEBUG 1 */ + +/* ************************************************************************ */ + +static int search_telnet_again(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + int i; + +#ifdef TELNET_DEBUG + printf("==> %s() [%s][direction: %u]\n", __FUNCTION__, packet->payload, packet->packet_direction); +#endif + + if((packet->payload == NULL) + || (packet->payload_packet_len == 0) + || (packet->payload[0] == 0xFF)) + return(1); + + if(flow->protos.telnet.username_detected) { + if((!flow->protos.telnet.password_found) + && (packet->payload_packet_len > 9)) { + + if(strncasecmp((char*)packet->payload, "password:", 9) == 0) { + flow->protos.telnet.password_found = 1; + } + + return(1); + } + + if(packet->payload[0] == '\r') { + if(!flow->protos.telnet.password_found) + return(1); + + flow->protos.telnet.password_detected = 1; + flow->protos.telnet.password[flow->protos.telnet.character_id] = '\0'; + return(0); + } + + if(packet->packet_direction == 0) /* client -> server */ { + for(i=0; ipayload_packet_len; i++) { + if(flow->protos.telnet.character_id < (sizeof(flow->protos.telnet.password)-1)) + flow->protos.telnet.password[flow->protos.telnet.character_id++] = packet->payload[i]; + } + } + + return(1); + } + + if((!flow->protos.telnet.username_found) + && (packet->payload_packet_len > 6)) { + + if(strncasecmp((char*)packet->payload, "login:", 6) == 0) { + flow->protos.telnet.username_found = 1; + } + + return(1); + } + + if(packet->payload[0] == '\r') { + flow->protos.telnet.username_detected = 1; + flow->protos.telnet.username[flow->protos.telnet.character_id] = '\0'; + flow->protos.telnet.character_id = 0; + return(1); + } + + for(i=0; ipayload_packet_len; i++) { + if(packet->packet_direction == 0) /* client -> server */ { + if(flow->protos.telnet.character_id < (sizeof(flow->protos.telnet.username)-1)) + flow->protos.telnet.username[flow->protos.telnet.character_id++] = packet->payload[i]; + } + } + + /* Possibly more processing */ + return(1); +} + +/* ************************************************************************ */ static void ndpi_int_telnet_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ + *ndpi_struct, struct ndpi_flow_struct *flow) { + flow->guessed_host_protocol_id = flow->guessed_protocol_id = NDPI_PROTOCOL_TELNET; + + /* This is necessary to inform the core to call this dissector again */ + flow->check_extra_packets = 1; + flow->max_extra_packets_to_check = 64; + flow->extra_packets_func = search_telnet_again; + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TELNET, NDPI_PROTOCOL_UNKNOWN); } - +/* ************************************************************************ */ + #if !defined(WIN32) static inline #elif defined(MINGW_GCC) @@ -44,62 +128,73 @@ __mingw_forceinline static #else __forceinline static #endif -u_int8_t search_iac(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) -{ +u_int8_t search_iac(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { struct ndpi_packet_struct *packet = &flow->packet; u_int16_t a; - if (packet->payload_packet_len < 3) { - return 0; - } +#ifdef TELNET_DEBUG + printf("==> %s()\n", __FUNCTION__); +#endif - if (!(packet->payload[0] == 0xff - && packet->payload[1] > 0xf9 && packet->payload[1] != 0xff && packet->payload[2] < 0x28)) { - return 0; - } + if(packet->payload_packet_len < 3) + return(0); + + if(!((packet->payload[0] == 0xff) + && (packet->payload[1] > 0xf9) + && (packet->payload[1] != 0xff) + && (packet->payload[2] < 0x28))) + return(0); a = 3; while (a < packet->payload_packet_len - 2) { // commands start with a 0xff byte followed by a command byte >= 0xf0 and < 0xff // command bytes 0xfb to 0xfe are followed by an option byte <= 0x28 - if (!(packet->payload[a] != 0xff || + if(!(packet->payload[a] != 0xff || (packet->payload[a] == 0xff && (packet->payload[a + 1] >= 0xf0) && (packet->payload[a + 1] <= 0xfa)) || (packet->payload[a] == 0xff && (packet->payload[a + 1] >= 0xfb) && (packet->payload[a + 1] != 0xff) - && (packet->payload[a + 2] <= 0x28)))) { - return 0; - } + && (packet->payload[a + 2] <= 0x28)))) + return(0); + a++; } return 1; } -/* this detection also works asymmetrically */ -void ndpi_search_telnet_tcp(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ +/* ************************************************************************ */ +/* this detection also works asymmetrically */ +void ndpi_search_telnet_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { NDPI_LOG_DBG(ndpi_struct, "search telnet\n"); - if (search_iac(ndpi_struct, flow) == 1) { - - if (flow->l4.tcp.telnet_stage == 2) { + if(search_iac(ndpi_struct, flow) == 1) { + if(flow->l4.tcp.telnet_stage == 2) { NDPI_LOG_INFO(ndpi_struct, "found telnet\n"); ndpi_int_telnet_add_connection(ndpi_struct, flow); return; } + flow->l4.tcp.telnet_stage++; NDPI_LOG_DBG2(ndpi_struct, "telnet stage %u\n", flow->l4.tcp.telnet_stage); return; } - if ((flow->packet_counter < 12 && flow->l4.tcp.telnet_stage > 0) || flow->packet_counter < 6) { + if(((flow->packet_counter < 12) && (flow->l4.tcp.telnet_stage > 0)) || (flow->packet_counter < 6)) { +#ifdef TELNET_DEBUG + printf("==> [%s:%d] %s()\n", __FILE__, __LINE__, __FUNCTION__); +#endif return; } else { +#ifdef TELNET_DEBUG + printf("==> [%s:%d] %s()\n", __FILE__, __LINE__, __FUNCTION__); +#endif NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } + return; } @@ -112,6 +207,5 @@ void init_telnet_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_i NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); - *id += 1; } diff --git a/src/lib/protocols/teredo.c b/src/lib/protocols/teredo.c index 32c183a..e1ba322 100644 --- a/src/lib/protocols/teredo.c +++ b/src/lib/protocols/teredo.c @@ -1,7 +1,7 @@ /* * teredo.c * - * Copyright (C) 2015-19 - ntop.org + * Copyright (C) 2015-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/tftp.c b/src/lib/protocols/tftp.c index 27578d4..a9d7a21 100644 --- a/src/lib/protocols/tftp.c +++ b/src/lib/protocols/tftp.c @@ -2,7 +2,7 @@ * tftp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -41,26 +41,31 @@ void ndpi_search_tftp(struct ndpi_detection_module_struct NDPI_LOG_DBG(ndpi_struct, "search TFTP\n"); - if (packet->payload_packet_len > 3 && flow->l4.udp.tftp_stage == 0 - && ntohl(get_u_int32_t(packet->payload, 0)) == 0x00030001) { + if ((packet->payload_packet_len > 3) + && (flow->l4.udp.tftp_stage == 0) + && (ntohl(get_u_int32_t(packet->payload, 0)) == 0x00030001)) { NDPI_LOG_DBG2(ndpi_struct, "maybe tftp. need next packet\n"); flow->l4.udp.tftp_stage = 1; return; } - if (packet->payload_packet_len > 3 && (flow->l4.udp.tftp_stage == 1) - && ntohl(get_u_int32_t(packet->payload, 0)) == 0x00040001) { - NDPI_LOG_INFO(ndpi_struct, "found tftp\n"); - ndpi_int_tftp_add_connection(ndpi_struct, flow); - return; - } - if (packet->payload_packet_len > 1 - && ((packet->payload[0] == 0 && packet->payload[packet->payload_packet_len - 1] == 0) - || (packet->payload_packet_len == 4 && ntohl(get_u_int32_t(packet->payload, 0)) == 0x00040000))) { - NDPI_LOG_DBG2(ndpi_struct, "skip initial packet\n"); - return; - } + if(flow->l4.udp.tftp_stage == 1) { + if (packet->payload_packet_len > 3 && (flow->l4.udp.tftp_stage == 1) + && ntohl(get_u_int32_t(packet->payload, 0)) == 0x00040001) { + + NDPI_LOG_INFO(ndpi_struct, "found tftp\n"); + ndpi_int_tftp_add_connection(ndpi_struct, flow); + return; + } + if (packet->payload_packet_len > 1 + && ((packet->payload[0] == 0 && packet->payload[packet->payload_packet_len - 1] == 0) + || (packet->payload_packet_len == 4 && ntohl(get_u_int32_t(packet->payload, 0)) == 0x00040000))) { + NDPI_LOG_DBG2(ndpi_struct, "skip initial packet\n"); + return; + } + } + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); } diff --git a/src/lib/protocols/thunder.c b/src/lib/protocols/thunder.c index 193488a..4d47749 100644 --- a/src/lib/protocols/thunder.c +++ b/src/lib/protocols/thunder.c @@ -2,7 +2,7 @@ * thunder.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -39,10 +39,10 @@ static void ndpi_int_thunder_add_connection(struct ndpi_detection_module_struct ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_THUNDER, NDPI_PROTOCOL_UNKNOWN); if (src != NULL) { - src->thunder_ts = packet->tick_timestamp; + src->thunder_ts = packet->current_time_ms; } if (dst != NULL) { - dst->thunder_ts = packet->tick_timestamp; + dst->thunder_ts = packet->current_time_ms; } } @@ -118,6 +118,7 @@ void ndpi_int_search_thunder_tcp(struct ndpi_detection_module_struct packet->content_line.len == 24 && memcmp(packet->content_line.ptr, "application/octet-stream", 24) == 0 && packet->empty_line_position_set < (packet->payload_packet_len - 8) + && packet->payload_packet_len > (packet->empty_line_position + 5) && packet->payload[packet->empty_line_position + 2] >= 0x30 && packet->payload[packet->empty_line_position + 2] < 0x40 && packet->payload[packet->empty_line_position + 3] == 0x00 @@ -151,15 +152,15 @@ void ndpi_int_search_thunder_http(struct ndpi_detection_module_struct if (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_THUNDER) { if (src != NULL && ((u_int32_t) - (packet->tick_timestamp - src->thunder_ts) < ndpi_struct->thunder_timeout)) { + (packet->current_time_ms - src->thunder_ts) < ndpi_struct->thunder_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "thunder : save src connection packet detected\n"); - src->thunder_ts = packet->tick_timestamp; + src->thunder_ts = packet->current_time_ms; } else if (dst != NULL && ((u_int32_t) - (packet->tick_timestamp - dst->thunder_ts) < ndpi_struct->thunder_timeout)) { + (packet->current_time_ms - dst->thunder_ts) < ndpi_struct->thunder_timeout)) { NDPI_LOG_DBG2(ndpi_struct, "thunder : save dst connection packet detected\n"); - dst->thunder_ts = packet->tick_timestamp; + dst->thunder_ts = packet->current_time_ms; } return; } diff --git a/src/lib/protocols/tinc.c b/src/lib/protocols/tinc.c index a7ff297..4748700 100644 --- a/src/lib/protocols/tinc.c +++ b/src/lib/protocols/tinc.c @@ -2,7 +2,7 @@ * tinc.c * * Copyright (C) 2017 - William Guglielmo - * Copyright (C) 2017-19 - ntop.org + * Copyright (C) 2017-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -58,9 +58,9 @@ static void ndpi_check_tinc(struct ndpi_detection_module_struct *ndpi_struct, st ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TINC, NDPI_PROTOCOL_UNKNOWN); } } - + + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; - } else if(packet->tcp != NULL) { if(payload_len == 0) { if(packet->tcp->syn == 1 && packet->tcp->ack == 0) { diff --git a/src/lib/protocols/tls.c b/src/lib/protocols/tls.c index 8511241..5b572ca 100644 --- a/src/lib/protocols/tls.c +++ b/src/lib/protocols/tls.c @@ -1,7 +1,7 @@ /* * tls.c - SSL/TLS/DTLS dissector * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -30,10 +30,23 @@ #include "ndpi_sha1.h" extern char *strptime(const char *s, const char *format, struct tm *tm); - -/* #define DEBUG_TLS 1 */ - -#define DEBUG_FINGERPRINT 1 +extern int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, uint32_t quic_version); +extern int http_process_user_agent(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + const u_int8_t *ua_ptr, u_int16_t ua_ptr_len); +/* QUIC/GQUIC stuff */ +extern int quic_len(const uint8_t *buf, uint64_t *value); +extern int quic_len_buffer_still_required(uint8_t value); +extern int is_version_with_var_int_transport_params(uint32_t version); + +// #define DEBUG_TLS_MEMORY 1 +// #define DEBUG_TLS 1 +// #define DEBUG_TLS_BLOCKS 1 +// #define DEBUG_CERTIFICATE_HASH + +/* #define DEBUG_FINGERPRINT 1 */ +/* #define DEBUG_ENCRYPTED_SNI 1 */ /* NOTE @@ -59,13 +72,16 @@ extern u_int8_t is_skype_flow(struct ndpi_detection_module_struct *ndpi_struct, /* stun.c */ extern u_int32_t get_stun_lru_key(struct ndpi_flow_struct *flow, u_int8_t rev); +static void ndpi_int_tls_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, u_int32_t protocol); + /* **************************************** */ static u_int32_t ndpi_tls_refine_master_protocol(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow, u_int32_t protocol) { struct ndpi_packet_struct *packet = &flow->packet; - protocol = NDPI_PROTOCOL_TLS; + // protocol = NDPI_PROTOCOL_TLS; if(packet->tcp != NULL) { switch(protocol) { @@ -89,19 +105,61 @@ static u_int32_t ndpi_tls_refine_master_protocol(struct ndpi_detection_module_st } } - return protocol; + return(protocol); } /* **************************************** */ -static void ndpi_int_tls_add_connection(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow, u_int32_t protocol) { - if(protocol != NDPI_PROTOCOL_TLS) - ; - else - protocol = ndpi_tls_refine_master_protocol(ndpi_struct, flow, protocol); +void ndpi_search_tls_tcp_memory(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; - ndpi_set_detected_protocol(ndpi_struct, flow, protocol, NDPI_PROTOCOL_TLS); + /* TCP */ +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] Handling TCP/TLS flow [payload_len: %u][buffer_len: %u][direction: %u]\n", + packet->payload_packet_len, + flow->l4.tcp.tls.message.buffer_len, + packet->packet_direction); +#endif + + if(flow->l4.tcp.tls.message.buffer == NULL) { + /* Allocate buffer */ + flow->l4.tcp.tls.message.buffer_len = 2048, flow->l4.tcp.tls.message.buffer_used = 0; + flow->l4.tcp.tls.message.buffer = (u_int8_t*)ndpi_malloc(flow->l4.tcp.tls.message.buffer_len); + + if(flow->l4.tcp.tls.message.buffer == NULL) + return; + +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] Allocating %u buffer\n", flow->l4.tcp.tls.message.buffer_len); +#endif + } + + u_int avail_bytes = flow->l4.tcp.tls.message.buffer_len - flow->l4.tcp.tls.message.buffer_used; + if(avail_bytes < packet->payload_packet_len) { + u_int new_len = flow->l4.tcp.tls.message.buffer_len + packet->payload_packet_len; + void *newbuf = ndpi_realloc(flow->l4.tcp.tls.message.buffer, + flow->l4.tcp.tls.message.buffer_len, new_len); + if(!newbuf) return; + + flow->l4.tcp.tls.message.buffer = (u_int8_t*)newbuf, flow->l4.tcp.tls.message.buffer_len = new_len; + avail_bytes = flow->l4.tcp.tls.message.buffer_len - flow->l4.tcp.tls.message.buffer_used; + +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] Enlarging %u -> %u buffer\n", flow->l4.tcp.tls.message.buffer_len, new_len); +#endif + } + + if(avail_bytes >= packet->payload_packet_len) { + memcpy(&flow->l4.tcp.tls.message.buffer[flow->l4.tcp.tls.message.buffer_used], + packet->payload, packet->payload_packet_len); + + flow->l4.tcp.tls.message.buffer_used += packet->payload_packet_len; +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] Copied data to buffer [%u/%u bytes]\n", + flow->l4.tcp.tls.message.buffer_used, flow->l4.tcp.tls.message.buffer_len); +#endif + } } /* **************************************** */ @@ -119,49 +177,8 @@ static void ndpi_int_tls_add_connection(struct ndpi_detection_module_struct *ndp /* **************************************** */ -static void stripCertificateTrailer(char *buffer, int buffer_len) { - int i, is_puny; - - // printf("->%s<-\n", buffer); - - for(i = 0; i < buffer_len; i++) { - // printf("%c [%d]\n", buffer[i], buffer[i]); - - if((buffer[i] != '.') - && (buffer[i] != '-') - && (buffer[i] != '_') - && (buffer[i] != '*') - && (!ndpi_isalpha(buffer[i])) - && (!ndpi_isdigit(buffer[i]))) { - buffer[i] = '\0'; - buffer_len = i; - break; - } - } - - /* check for punycode encoding */ - is_puny = ndpi_check_punycode_string(buffer, buffer_len); - - // not a punycode string - need more checks - if(is_puny == 0) { - - if(i > 0) i--; - - while(i > 0) { - if(!ndpi_isalpha(buffer[i])) { - buffer[i] = '\0'; - buffer_len = i; - i--; - } else - break; - } - - for(i = buffer_len; i > 0; i--) { - if(buffer[i] == '.') break; - else if(ndpi_isdigit(buffer[i])) - buffer[i] = '\0', buffer_len = i; - } - } +static void cleanupServerName(char *buffer, int buffer_len) { + u_int i; /* Now all lowecase */ for(i=0; ipayload[offset+4], is_printable = 1; + char *str; + u_int len, j; + + if (*rdnSeqBuf_offset >= rdnSeqBuf_len) { +#ifdef DEBUG_TLS + printf("[TLS] %s() [buffer capacity reached][%u]\n", + __FUNCTION__, rdnSeqBuf_len); +#endif + return -1; + } -struct ja3_info { - u_int16_t tls_handshake_version; - u_int16_t num_cipher, cipher[MAX_NUM_JA3]; - u_int16_t num_tls_extension, tls_extension[MAX_NUM_JA3]; - u_int16_t num_elliptic_curve, elliptic_curve[MAX_NUM_JA3]; - u_int8_t num_elliptic_curve_point_format, elliptic_curve_point_format[MAX_NUM_JA3]; -}; + // packet is truncated... further inspection is not needed + if((offset+4+str_len) >= packet->payload_packet_len) + return(-1); -/* **************************************** */ + str = (char*)&packet->payload[offset+5]; -int getTLScertificate(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow, - char *buffer, int buffer_len) { - struct ndpi_packet_struct *packet = &flow->packet; - struct ja3_info ja3; - u_int8_t invalid_ja3 = 0; - u_int16_t pkt_tls_version = (packet->payload[1] << 8) + packet->payload[2], ja3_str_len; - char ja3_str[JA3_STR_LEN]; - ndpi_MD5_CTX ctx; - u_char md5_hash[16]; - int i; + len = (u_int)ndpi_min(str_len, buffer_len-1); + strncpy(buffer, str, len); + buffer[len] = '\0'; - if(packet->udp) { - /* Check if this is DTLS or return */ - if((packet->payload[1] != 0xfe) - || ((packet->payload[2] != 0xff) && (packet->payload[2] != 0xfd))) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - return(0); + // check string is printable + for(j = 0; j < len; j++) { + if(!ndpi_isprint(buffer[j])) { + is_printable = 0; + break; } } - flow->protos.stun_ssl.ssl.ssl_version = pkt_tls_version; - - memset(&ja3, 0, sizeof(ja3)); - -#ifdef DEBUG_TLS - { - u_int16_t tls_len = (packet->payload[3] << 8) + packet->payload[4]; + if(is_printable) { + int rc = snprintf(&rdnSeqBuf[*rdnSeqBuf_offset], + rdnSeqBuf_len-(*rdnSeqBuf_offset), + "%s%s=%s", (*rdnSeqBuf_offset > 0) ? ", " : "", + label, buffer); - printf("SSL Record [version: 0x%04X][len: %u]\n", pkt_tls_version, tls_len); + if(rc > 0) + (*rdnSeqBuf_offset) += rc; } -#endif - - /* - Nothing matched so far: let's decode the certificate with some heuristics - Patches courtesy of Denys Fedoryshchenko - */ - if(packet->payload[0] == 0x16 /* Handshake */) { - u_int16_t total_len; - u_int8_t handshake_protocol, header_len; - if(packet->tcp) { - header_len = 5; /* SSL Header */ - handshake_protocol = packet->payload[5]; /* handshake protocol a bit misleading, it is message type according TLS specs */ - total_len = (packet->payload[3] << 8) + packet->payload[4]; - } else { - header_len = 13; /* DTLS header */ - handshake_protocol = packet->payload[13]; - total_len = ntohs(*((u_int16_t*)&packet->payload[11])); - } - - total_len += header_len; - - memset(buffer, 0, buffer_len); + return(is_printable); +} - /* Truncate total len, search at least in incomplete packet */ - if(total_len > packet->payload_packet_len) - total_len = packet->payload_packet_len; +/* **************************************** */ - /* 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 ? 43 : 59; +/* See https://blog.catchpoint.com/2017/05/12/dissecting-tls-using-wireshark/ */ +static void processCertificateElements(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, + u_int16_t p_offset, u_int16_t certificate_len) { + struct ndpi_packet_struct *packet = &flow->packet; + u_int num_found = 0, i; + char buffer[64] = { '\0' }, rdnSeqBuf[2048] = { '\0' }; + u_int rdn_len = 0; #ifdef DEBUG_TLS - printf("SSL [len: %u][handshake_protocol: %02X]\n", packet->payload_packet_len, handshake_protocol); + printf("[TLS] %s() [offset: %u][certificate_len: %u]\n", __FUNCTION__, p_offset, certificate_len); #endif - if((handshake_protocol == 0x02) - || (handshake_protocol == 0x0b) /* Server Hello and Certificate message types are interesting for us */) { - u_int num_found = 0; - u_int16_t tls_version; - int i; - - if(packet->tcp) - tls_version = ntohs(*((u_int16_t*)&packet->payload[header_len+4])); - else - tls_version = ntohs(*((u_int16_t*)&packet->payload[header_len+12])); - - ja3.tls_handshake_version = tls_version; - - if(handshake_protocol == 0x02) { - u_int16_t offset = base_offset, extension_len, j; - u_int8_t session_id_len = packet->payload[offset]; + /* Check after handshake protocol header (5 bytes) and message header (4 bytes) */ + for(i = p_offset; i < certificate_len; i++) { + /* + See https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.sec.doc/q009860_.htm + for X.509 certificate labels + */ + if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x03)) { + /* Common Name */ + int rc = extractRDNSequence(packet, i, buffer, sizeof(buffer), rdnSeqBuf, &rdn_len, sizeof(rdnSeqBuf), "CN"); + if(rc == -1) break; #ifdef DEBUG_TLS - printf("SSL Server Hello [version: 0x%04X]\n", tls_version); + printf("[TLS] %s() [%s][%s: %s]\n", __FUNCTION__, (num_found == 0) ? "Subject" : "Issuer", "Common Name", buffer); #endif - - /* - The server hello decides about the SSL version of this flow - https://networkengineering.stackexchange.com/questions/55752/why-does-wireshark-show-version-tls-1-2-here-instead-of-tls-1-3 - */ - flow->protos.stun_ssl.ssl.ssl_version = tls_version; - - if(packet->udp) - offset += 1; - else { - if(tls_version < 0x7F15 /* TLS 1.3 lacks of session id */) - offset += session_id_len+1; - } - - ja3.num_cipher = 1, ja3.cipher[0] = ntohs(*((u_int16_t*)&packet->payload[offset])); - flow->protos.stun_ssl.ssl.server_unsafe_cipher = ndpi_is_safe_ssl_cipher(ja3.cipher[0]); - flow->protos.stun_ssl.ssl.server_cipher = ja3.cipher[0]; + } else if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x06)) { + /* Country */ + int rc = extractRDNSequence(packet, i, buffer, sizeof(buffer), rdnSeqBuf, &rdn_len, sizeof(rdnSeqBuf), "C"); + if(rc == -1) break; #ifdef DEBUG_TLS - printf("TLS [server][session_id_len: %u][cipher: %04X]\n", session_id_len, ja3.cipher[0]); + printf("[TLS] %s() [%s][%s: %s]\n", __FUNCTION__, (num_found == 0) ? "Subject" : "Issuer", "Country", buffer); #endif - - offset += 2 + 1; - extension_len = ntohs(*((u_int16_t*)&packet->payload[offset])); + } else if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x07)) { + /* Locality */ + int rc = extractRDNSequence(packet, i, buffer, sizeof(buffer), rdnSeqBuf, &rdn_len, sizeof(rdnSeqBuf), "L"); + if(rc == -1) break; #ifdef DEBUG_TLS - printf("TLS [server][extension_len: %u]\n", extension_len); + printf("[TLS] %s() [%s][%s: %s]\n", __FUNCTION__, (num_found == 0) ? "Subject" : "Issuer", "Locality", buffer); #endif - offset += 2; - - for(i=0; i= (packet->payload_packet_len+4)) break; + } else if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x08)) { + /* State or Province */ + int rc = extractRDNSequence(packet, i, buffer, sizeof(buffer), rdnSeqBuf, &rdn_len, sizeof(rdnSeqBuf), "ST"); + if(rc == -1) break; - extension_id = ntohs(*((u_int16_t*)&packet->payload[offset])); - extension_len = ntohs(*((u_int16_t*)&packet->payload[offset+2])); - - if(ja3.num_tls_extension < MAX_NUM_JA3) - ja3.tls_extension[ja3.num_tls_extension++] = extension_id; +#ifdef DEBUG_TLS + printf("[TLS] %s() [%s][%s: %s]\n", __FUNCTION__, (num_found == 0) ? "Subject" : "Issuer", "State or Province", buffer); +#endif + } else if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x0a)) { + /* Organization Name */ + int rc = extractRDNSequence(packet, i, buffer, sizeof(buffer), rdnSeqBuf, &rdn_len, sizeof(rdnSeqBuf), "O"); + if(rc == -1) break; #ifdef DEBUG_TLS - printf("TLS [server][extension_id: %u/0x%04X][len: %u]\n", - extension_id, extension_id, extension_len); + printf("[TLS] %s() [%s][%s: %s]\n", __FUNCTION__, (num_found == 0) ? "Subject" : "Issuer", "Organization Name", buffer); #endif - if(extension_id == 43 /* supported versions */) { - if(extension_len >= 2) { - u_int16_t tls_version = ntohs(*((u_int16_t*)&packet->payload[offset+4])); + } else if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x0b)) { + /* Organization Unit */ + int rc = extractRDNSequence(packet, i, buffer, sizeof(buffer), rdnSeqBuf, &rdn_len, sizeof(rdnSeqBuf), "OU"); + if(rc == -1) break; #ifdef DEBUG_TLS - printf("TLS [server] [TLS version: 0x%04X]\n", tls_version); + printf("[TLS] %s() [%s][%s: %s]\n", __FUNCTION__, (num_found == 0) ? "Subject" : "Issuer", "Organization Unit", buffer); #endif - - flow->protos.stun_ssl.ssl.ssl_version = tls_version; - } - } - - i += 4 + extension_len, offset += 4 + extension_len; - } + } else if((packet->payload[i] == 0x30) && (packet->payload[i+1] == 0x1e) && (packet->payload[i+2] == 0x17)) { + /* Certificate Validity */ + u_int8_t len = packet->payload[i+3]; + u_int offset = i+4; - ja3_str_len = snprintf(ja3_str, sizeof(ja3_str), "%u,", ja3.tls_handshake_version); + if(num_found == 0) { + num_found++; - for(i=0; i 0) ? "-" : "", ja3.cipher[i]); +#ifdef DEBUG_TLS + printf("[TLS] %s() IssuerDN [%s]\n", __FUNCTION__, rdnSeqBuf); +#endif - ja3_str_len += snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); + if(rdn_len && (flow->protos.stun_ssl.ssl.issuerDN == NULL)) + flow->protos.stun_ssl.ssl.issuerDN = ndpi_strdup(rdnSeqBuf); - /* ********** */ + rdn_len = 0; /* Reset buffer */ + } - for(i=0; i 0) ? "-" : "", ja3.tls_extension[i]); + if((offset+len) < packet->payload_packet_len) { + char utcDate[32]; #ifdef DEBUG_TLS - printf("TLS [server] %s\n", ja3_str); -#endif + u_int j; -#ifdef DEBUG_TLS - printf("[JA3] Server: %s \n", ja3_str); + printf("[CERTIFICATE] notBefore [len: %u][", len); + for(j=0; jpayload[i+4+j]); + printf("]\n"); #endif - ndpi_MD5Init(&ctx); - ndpi_MD5Update(&ctx, (const unsigned char *)ja3_str, strlen(ja3_str)); - ndpi_MD5Final(md5_hash, &ctx); + if(len < (sizeof(utcDate)-1)) { + struct tm utc; + utc.tm_isdst = -1; /* Not set by strptime */ - for(i=0, j=0; i<16; i++) - j += snprintf(&flow->protos.stun_ssl.ssl.ja3_server[j], - sizeof(flow->protos.stun_ssl.ssl.ja3_server)-j, "%02x", md5_hash[i]); + strncpy(utcDate, (const char*)&packet->payload[i+4], len); + utcDate[len] = '\0'; + /* 141021000000Z */ + if(strptime(utcDate, "%y%m%d%H%M%SZ", &utc) != NULL) { + flow->protos.stun_ssl.ssl.notBefore = timegm(&utc); #ifdef DEBUG_TLS - printf("[JA3] Server: %s \n", flow->protos.stun_ssl.ssl.ja3_server); + printf("[CERTIFICATE] notBefore %u [%s]\n", + flow->protos.stun_ssl.ssl.notBefore, utcDate); #endif + } + } - flow->l4.tcp.tls_seen_server_cert = 1; - } else - flow->l4.tcp.tls_seen_certificate = 1; + offset += len; - /* Check after handshake protocol header (5 bytes) and message header (4 bytes) */ - for(i = 9; i < packet->payload_packet_len-3; i++) { - if(((packet->payload[i] == 0x04) && (packet->payload[i+1] == 0x03) && (packet->payload[i+2] == 0x0c)) - || ((packet->payload[i] == 0x04) && (packet->payload[i+1] == 0x03) && (packet->payload[i+2] == 0x13)) - || ((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x03))) { - u_int8_t server_len = packet->payload[i+3]; + if((offset+1) < packet->payload_packet_len) { + len = packet->payload[offset+1]; - if(packet->payload[i] == 0x55) { - num_found++; + offset += 2; - if(num_found != 2) continue; - } + if((offset+len) < packet->payload_packet_len) { + u_int32_t time_sec = flow->packet.current_time_ms / 1000; +#ifdef DEBUG_TLS + u_int j; - if((server_len+i+3) < packet->payload_packet_len) { - char *server_name = (char*)&packet->payload[i+4]; - u_int8_t begin = 0, len, j, num_dots; + printf("[CERTIFICATE] notAfter [len: %u][", len); + for(j=0; jpayload[offset+j]); + printf("]\n"); +#endif - while(begin < server_len) { - if(!ndpi_isprint(server_name[begin])) - begin++; - else - break; - } + if(len < (sizeof(utcDate)-1)) { + struct tm utc; + utc.tm_isdst = -1; /* Not set by strptime */ - // len = ndpi_min(server_len-begin, buffer_len-1); - len = buffer_len-1; - strncpy(buffer, &server_name[begin], len); - buffer[len] = '\0'; + strncpy(utcDate, (const char*)&packet->payload[offset], len); + utcDate[len] = '\0'; - /* We now have to check if this looks like an IP address or host name */ - for(j=0, num_dots = 0; j=1) break; - } + /* 141021000000Z */ + if(strptime(utcDate, "%y%m%d%H%M%SZ", &utc) != NULL) { + flow->protos.stun_ssl.ssl.notAfter = timegm(&utc); +#ifdef DEBUG_TLS + printf("[CERTIFICATE] notAfter %u [%s]\n", + flow->protos.stun_ssl.ssl.notAfter, utcDate); +#endif } + } - if(num_dots >= 1) { - if(!ndpi_struct->disable_metadata_export) { - stripCertificateTrailer(buffer, buffer_len); - snprintf(flow->protos.stun_ssl.ssl.server_certificate, - sizeof(flow->protos.stun_ssl.ssl.server_certificate), "%s", buffer); - } - return(1 /* Server Certificate */); - } - } + if((time_sec < flow->protos.stun_ssl.ssl.notBefore) + || (time_sec > flow->protos.stun_ssl.ssl.notAfter)) + NDPI_SET_BIT(flow->risk, NDPI_TLS_CERTIFICATE_EXPIRED); /* Certificate expired */ } } - } else if(handshake_protocol == 0x01 /* Client Hello */) { - u_int offset; + } + } else if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x1d) && (packet->payload[i+2] == 0x11)) { + /* Organization OID: 2.5.29.17 (subjectAltName) */ + u_int8_t matched_name = 0; #ifdef DEBUG_TLS - printf("[base_offset: %u][payload_packet_len: %u]\n", base_offset, packet->payload_packet_len); + printf("******* [TLS] Found subjectAltName\n"); #endif - if(base_offset + 2 <= packet->payload_packet_len) { - u_int16_t session_id_len; - u_int16_t tls_version; - - if(packet->tcp) - tls_version = ntohs(*((u_int16_t*)&packet->payload[header_len+4])); - else - tls_version = ntohs(*((u_int16_t*)&packet->payload[header_len+12])); - - session_id_len = packet->payload[base_offset]; - - ja3.tls_handshake_version = tls_version; - - if((session_id_len+base_offset+2) <= total_len) { - u_int16_t cipher_len, cipher_offset; - - if(packet->tcp) { - 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 { - cipher_len = ntohs(*((u_int16_t*)&packet->payload[base_offset+2])); - cipher_offset = base_offset+4; - } - -#ifdef DEBUG_TLS - printf("Client SSL [client cipher_len: %u][tls_version: 0x%04X]\n", cipher_len, tls_version); + i += 3 /* skip the initial patten 55 1D 11 */; + i++; /* skip the first type, 0x04 == BIT STRING, and jump to it's length */ + if(i < packet->payload_packet_len) { + i += (packet->payload[i] & 0x80) ? (packet->payload[i] & 0x7F) : 0; /* skip BIT STRING length */ + if(i < packet->payload_packet_len) { + i += 2; /* skip the second type, 0x30 == SEQUENCE, and jump to it's length */ + if(i < packet->payload_packet_len) { + i += (packet->payload[i] & 0x80) ? (packet->payload[i] & 0x7F) : 0; /* skip SEQUENCE length */ + i++; + + while(i < packet->payload_packet_len) { + if(packet->payload[i] == 0x82) { + if((i < (packet->payload_packet_len - 1)) + && ((i + packet->payload[i + 1] + 2) < packet->payload_packet_len)) { + u_int8_t len = packet->payload[i + 1]; + char dNSName[256]; + + i += 2; + + /* The check "len > sizeof(dNSName) - 1" will be always false. If we add it, + the compiler is smart enough to detect it and throws a warning */ + if(len == 0 /* Looks something went wrong */) + break; + + strncpy(dNSName, (const char*)&packet->payload[i], len); + dNSName[len] = '\0'; + + cleanupServerName(dNSName, len); + +#if DEBUG_TLS + printf("[TLS] dNSName %s [%s]\n", dNSName, flow->protos.stun_ssl.ssl.client_requested_server_name); #endif + if(matched_name == 0) { + if(flow->protos.stun_ssl.ssl.client_requested_server_name[0] == '\0') + matched_name = 1; /* No SNI */ + else if((dNSName[0] == '*') && strstr(flow->protos.stun_ssl.ssl.client_requested_server_name, &dNSName[1])) + matched_name = 1; + else if(strcmp(flow->protos.stun_ssl.ssl.client_requested_server_name, dNSName) == 0) + matched_name = 1; + } - if((cipher_offset+cipher_len) <= total_len) { - for(i=0; ipayload[cipher_offset+i]; + if(flow->protos.stun_ssl.ssl.server_names == NULL) + flow->protos.stun_ssl.ssl.server_names = ndpi_strdup(dNSName), + flow->protos.stun_ssl.ssl.server_names_len = strlen(dNSName); + else { + u_int16_t dNSName_len = strlen(dNSName); + u_int16_t newstr_len = flow->protos.stun_ssl.ssl.server_names_len + dNSName_len + 1; + char *newstr = (char*)ndpi_realloc(flow->protos.stun_ssl.ssl.server_names, + flow->protos.stun_ssl.ssl.server_names_len+1, newstr_len+1); + + if(newstr) { + flow->protos.stun_ssl.ssl.server_names = newstr; + flow->protos.stun_ssl.ssl.server_names[flow->protos.stun_ssl.ssl.server_names_len] = ','; + strncpy(&flow->protos.stun_ssl.ssl.server_names[flow->protos.stun_ssl.ssl.server_names_len+1], + dNSName, dNSName_len+1); + flow->protos.stun_ssl.ssl.server_names[newstr_len] = '\0'; + flow->protos.stun_ssl.ssl.server_names_len = newstr_len; + } + } -#ifdef DEBUG_TLS - printf("Client SSL [cipher suite: %u/0x%04X] [%d/%u]\n", ntohs(*id), ntohs(*id), i, cipher_len); -#endif - if((*id == 0) || (packet->payload[cipher_offset+i] != packet->payload[cipher_offset+i+1])) { - /* - Skip GREASE [https://tools.ietf.org/id/draft-ietf-tls-grease-01.html] - https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967 - */ + if(!flow->l4.tcp.tls.subprotocol_detected) + if(ndpi_match_hostname_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TLS, dNSName, len)) + flow->l4.tcp.tls.subprotocol_detected = 1; - if(ja3.num_cipher < MAX_NUM_JA3) - ja3.cipher[ja3.num_cipher++] = ntohs(*id); - else { - invalid_ja3 = 1; -#ifdef DEBUG_TLS - printf("Client SSL Invalid cipher %u\n", ja3.num_cipher); + i += len; + } else { +#if DEBUG_TLS + printf("[TLS] Leftover %u bytes", packet->payload_packet_len - i); #endif - } + break; } - - i += 2; + } else { + break; } - } else { - invalid_ja3 = 1; -#ifdef DEBUG_TLS - printf("Client SSL Invalid len %u vs %u\n", (cipher_offset+cipher_len), total_len); -#endif - } - - offset = base_offset + session_id_len + cipher_len + 2; + } /* while */ - flow->l4.tcp.tls_seen_client_cert = 1; + if(!matched_name) + NDPI_SET_BIT(flow->risk, NDPI_TLS_CERTIFICATE_MISMATCH); /* Certificate mismatch */ + } + } + } + } + } - if(offset < total_len) { - u_int16_t compression_len; - u_int16_t extensions_len; + if(rdn_len && (flow->protos.stun_ssl.ssl.subjectDN == NULL)) + flow->protos.stun_ssl.ssl.subjectDN = ndpi_strdup(rdnSeqBuf); - offset += packet->tcp ? 1 : 2; - compression_len = packet->payload[offset]; - offset++; + if(flow->protos.stun_ssl.ssl.subjectDN && flow->protos.stun_ssl.ssl.issuerDN + && (!strcmp(flow->protos.stun_ssl.ssl.subjectDN, flow->protos.stun_ssl.ssl.issuerDN))) + NDPI_SET_BIT(flow->risk, NDPI_TLS_SELFSIGNED_CERTIFICATE); -#ifdef DEBUG_TLS - printf("Client SSL [compression_len: %u]\n", compression_len); +#if DEBUG_TLS + printf("[TLS] %s() SubjectDN [%s]\n", __FUNCTION__, rdnSeqBuf); #endif +} - // offset += compression_len + 3; - offset += compression_len; +/* **************************************** */ - if(offset < total_len) { - extensions_len = ntohs(*((u_int16_t*)&packet->payload[offset])); - offset += 2; +/* See https://blog.catchpoint.com/2017/05/12/dissecting-tls-using-wireshark/ */ +int processCertificate(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + u_int32_t certificates_length, length = (packet->payload[1] << 16) + (packet->payload[2] << 8) + packet->payload[3]; + u_int16_t certificates_offset = 7; + u_int8_t num_certificates_found = 0; #ifdef DEBUG_TLS - printf("Client SSL [extensions_len: %u]\n", extensions_len); + printf("[TLS] %s() [payload_packet_len=%u][direction: %u][%02X %02X %02X %02X %02X %02X...]\n", + __FUNCTION__, packet->payload_packet_len, + packet->packet_direction, + packet->payload[0], packet->payload[1], packet->payload[2], + packet->payload[3], packet->payload[4], packet->payload[5]); #endif - if((extensions_len+offset) <= total_len) { - /* Move to the first extension - Type is u_int to avoid possible overflow on extension_len addition */ - u_int extension_offset = 0; - u_int32_t j; + if((packet->payload_packet_len != (length + 4)) || (packet->payload[1] != 0x0)) { + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); + return(-1); /* Invalid length */ + } + + certificates_length = (packet->payload[4] << 16) + (packet->payload[5] << 8) + packet->payload[6]; - while(extension_offset < extensions_len) { - u_int16_t extension_id, extension_len, extn_off = offset+extension_offset; + if((packet->payload[4] != 0x0) || ((certificates_length+3) != length)) { + NDPI_SET_BIT(flow->risk, NDPI_MALFORMED_PACKET); + return(-2); /* Invalid length */ + } - extension_id = ntohs(*((u_int16_t*)&packet->payload[offset+extension_offset])); - extension_offset += 2; + if(!flow->l4.tcp.tls.srv_cert_fingerprint_ctx) { + if((flow->l4.tcp.tls.srv_cert_fingerprint_ctx = (void*)ndpi_malloc(sizeof(SHA1_CTX))) == NULL) + return(-3); /* Not enough memory */ + } - extension_len = ntohs(*((u_int16_t*)&packet->payload[offset+extension_offset])); - extension_offset += 2; + /* Now let's process each individual certificates */ + while(certificates_offset < certificates_length) { + u_int32_t certificate_len = (packet->payload[certificates_offset] << 16) + (packet->payload[certificates_offset+1] << 8) + packet->payload[certificates_offset+2]; + /* Invalid lenght */ + if((certificate_len == 0) + || (packet->payload[certificates_offset] != 0x0) + || ((certificates_offset+certificate_len) > (4+certificates_length))) { #ifdef DEBUG_TLS - printf("Client SSL [extension_id: %u][extension_len: %u]\n", extension_id, extension_len); + printf("[TLS] Invalid length [certificate_len: %u][certificates_offset: %u][%u vs %u]\n", + certificate_len, certificates_offset, + (certificates_offset+certificate_len), + certificates_length); #endif + break; + } - if((extension_id == 0) || (packet->payload[extn_off] != packet->payload[extn_off+1])) { - /* Skip GREASE */ - - if(ja3.num_tls_extension < MAX_NUM_JA3) - ja3.tls_extension[ja3.num_tls_extension++] = extension_id; - else { - invalid_ja3 = 1; + certificates_offset += 3; #ifdef DEBUG_TLS - printf("Client SSL Invalid extensions %u\n", ja3.num_tls_extension); + printf("[TLS] Processing %u bytes certificate [%02X %02X %02X]\n", + certificate_len, + packet->payload[certificates_offset], + packet->payload[certificates_offset+1], + packet->payload[certificates_offset+2]); #endif - } - } - - if(extension_id == 0 /* server name */) { - u_int16_t len; - len = (packet->payload[offset+extension_offset+3] << 8) + packet->payload[offset+extension_offset+4]; - len = (u_int)ndpi_min(len, buffer_len-1); - strncpy(buffer, (char*)&packet->payload[offset+extension_offset+5], len); - buffer[len] = '\0'; + if(num_certificates_found++ == 0) /* Dissect only the first certificate that is the one we care */ { + /* For SHA-1 we take into account only the first certificate and not all of them */ - stripCertificateTrailer(buffer, buffer_len); + SHA1Init(flow->l4.tcp.tls.srv_cert_fingerprint_ctx); - if(!ndpi_struct->disable_metadata_export) { - snprintf(flow->protos.stun_ssl.ssl.client_certificate, - sizeof(flow->protos.stun_ssl.ssl.client_certificate), "%s", buffer); - } - } else if(extension_id == 10 /* supported groups */) { - u_int16_t s_offset = offset+extension_offset + 2; +#ifdef DEBUG_CERTIFICATE_HASH + { + int i; -#ifdef DEBUG_TLS - printf("Client SSL [EllipticCurveGroups: len=%u]\n", extension_len); + for(i=0;ipayload[certificates_offset+i]); + + printf("\n"); + } #endif - if((s_offset+extension_len-2) <= total_len) { - for(i=0; ipayload[s_offset+i])); + SHA1Update(flow->l4.tcp.tls.srv_cert_fingerprint_ctx, + &packet->payload[certificates_offset], + certificate_len); -#ifdef DEBUG_TLS - printf("Client SSL [EllipticCurve: %u/0x%04X]\n", s_group, s_group); -#endif - if((s_group == 0) || (packet->payload[s_offset+i] != packet->payload[s_offset+i+1])) { - /* Skip GREASE */ - if(ja3.num_elliptic_curve < MAX_NUM_JA3) - ja3.elliptic_curve[ja3.num_elliptic_curve++] = s_group; - else { - invalid_ja3 = 1; -#ifdef DEBUG_TLS - printf("Client SSL Invalid num elliptic %u\n", ja3.num_elliptic_curve); -#endif - } - } + SHA1Final(flow->l4.tcp.tls.sha1_certificate_fingerprint, flow->l4.tcp.tls.srv_cert_fingerprint_ctx); - i += 2; - } - } else { - invalid_ja3 = 1; -#ifdef DEBUG_TLS - printf("Client SSL Invalid len %u vs %u\n", (s_offset+extension_len-1), total_len); -#endif - } - } else if(extension_id == 11 /* ec_point_formats groups */) { - u_int16_t s_offset = offset+extension_offset + 1; + flow->l4.tcp.tls.fingerprint_set = 1; #ifdef DEBUG_TLS - printf("Client SSL [EllipticCurveFormat: len=%u]\n", extension_len); -#endif - if((s_offset+extension_len) < total_len) { - for(i=0; ipayload[s_offset+i]; + { + int i; -#ifdef DEBUG_TLS - printf("Client SSL [EllipticCurveFormat: %u]\n", s_group); + printf("[TLS] SHA-1: "); + for(i=0;i<20;i++) + printf("%s%02X", (i > 0) ? ":" : "", flow->l4.tcp.tls.sha1_certificate_fingerprint[i]); + printf("\n"); + } #endif - if(ja3.num_elliptic_curve_point_format < MAX_NUM_JA3) - ja3.elliptic_curve_point_format[ja3.num_elliptic_curve_point_format++] = s_group; - else { - invalid_ja3 = 1; -#ifdef DEBUG_TLS - printf("Client SSL Invalid num elliptic %u\n", ja3.num_elliptic_curve_point_format); -#endif - } - } - } else { - invalid_ja3 = 1; -#ifdef DEBUG_TLS - printf("Client SSL Invalid len %u vs %u\n", s_offset+extension_len, total_len); -#endif - } - } else if(extension_id == 43 /* supported versions */) { - u_int8_t version_len = packet->payload[offset+4]; - - if(version_len == (extension_len-1)) { -#ifdef DEBUG_TLS - u_int8_t j; - - for(j=0; jpayload[offset+5+j])); - - printf("Client SSL [TLS version: 0x%04X]\n", tls_version); - } -#endif - } - } + processCertificateElements(ndpi_struct, flow, certificates_offset, certificate_len); + } - extension_offset += extension_len; + certificates_offset += certificate_len; + } -#ifdef DEBUG_TLS - printf("Client SSL [extension_offset/len: %u/%u]\n", extension_offset, extension_len); + if(( ndpi_struct->num_tls_blocks_to_follow != 0) + && (flow->l4.tcp.tls.num_tls_blocks >= ndpi_struct->num_tls_blocks_to_follow)) { +#ifdef DEBUG_TLS_BLOCKS + printf("*** [TLS Block] Enough blocks dissected\n"); #endif - } /* while */ - if(!invalid_ja3) { - compute_ja3c: - ja3_str_len = snprintf(ja3_str, sizeof(ja3_str), "%u,", ja3.tls_handshake_version); + flow->extra_packets_func = NULL; /* We're good now */ + } - for(i=0; i 0) ? "-" : "", ja3.cipher[i]); - } + return(1); +} - ja3_str_len += snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); +/* **************************************** */ - /* ********** */ +static int processTLSBlock(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; - for(i=0; i 0) ? "-" : "", ja3.tls_extension[i]); + switch(packet->payload[0] /* block type */) { + case 0x01: /* Client Hello */ + case 0x02: /* Server Hello */ + processClientServerHello(ndpi_struct, flow, 0); + flow->l4.tcp.tls.hello_processed = 1; + ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TLS); + break; + + case 0x0b: /* Certificate */ + /* Important: populate the tls union fields only after + * ndpi_int_tls_add_connection has been called */ + if(flow->l4.tcp.tls.hello_processed) { + processCertificate(ndpi_struct, flow); + flow->l4.tcp.tls.certificate_processed = 1; + } + break; - ja3_str_len += snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); + default: + return(-1); + } - /* ********** */ + return(0); +} - for(i=0; i 0) ? "-" : "", ja3.elliptic_curve[i]); +/* **************************************** */ - ja3_str_len += snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); +static int ndpi_search_tls_tcp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + u_int8_t something_went_wrong = 0; - for(i=0; i 0) ? "-" : "", ja3.elliptic_curve_point_format[i]); +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] ndpi_search_tls_tcp() Processing new packet [payload_packet_len: %u]\n", + packet->payload_packet_len); +#endif -#ifdef DEBUG_TLS - printf("[JA3] Client: %s \n", ja3_str); -#endif + if(packet->payload_packet_len == 0) + return(1); /* Keep working */ - ndpi_MD5Init(&ctx); - ndpi_MD5Update(&ctx, (const unsigned char *)ja3_str, strlen(ja3_str)); - ndpi_MD5Final(md5_hash, &ctx); + ndpi_search_tls_tcp_memory(ndpi_struct, flow); - for(i=0, j=0; i<16; i++) - j += snprintf(&flow->protos.stun_ssl.ssl.ja3_client[j], - sizeof(flow->protos.stun_ssl.ssl.ja3_client)-j, "%02x", - md5_hash[i]); + while(!something_went_wrong) { + u_int16_t len, p_len; + const u_int8_t *p; + u_int8_t content_type; -#ifdef DEBUG_TLS - printf("[JA3] Client: %s \n", flow->protos.stun_ssl.ssl.ja3_client); + if(flow->l4.tcp.tls.message.buffer_used < 5) + return(1); /* Keep working */ + + len = (flow->l4.tcp.tls.message.buffer[3] << 8) + flow->l4.tcp.tls.message.buffer[4] + 5; + + if(len > flow->l4.tcp.tls.message.buffer_used) { +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] Not enough TLS data [%u < %u][%02X %02X %02X %02X %02X]\n", + len, flow->l4.tcp.tls.message.buffer_used, + flow->l4.tcp.tls.message.buffer[0], + flow->l4.tcp.tls.message.buffer[1], + flow->l4.tcp.tls.message.buffer[2], + flow->l4.tcp.tls.message.buffer[3], + flow->l4.tcp.tls.message.buffer[4]); #endif - } + break; + } - return(2 /* Client Certificate */); - } - } else if(offset == total_len) { - /* SSL does not have extensions etc */ - goto compute_ja3c; - } - } - } - } - } + if(len == 0) { + something_went_wrong = 1; + break; } - } - return(0); /* Not found */ -} +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] Processing %u bytes message\n", len); +#endif -/* **************************************** */ + content_type = flow->l4.tcp.tls.message.buffer[0]; -/* See https://blog.catchpoint.com/2017/05/12/dissecting-tls-using-wireshark/ */ -int getSSCertificateFingerprint(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int8_t multiple_messages; + /* Overwriting packet payload */ + p = packet->payload, p_len = packet->payload_packet_len; /* Backup */ - if(flow->l4.tcp.tls_srv_cert_fingerprint_processed) - return(0); /* We're good */ - -#ifdef DEBUG_TLS - printf("=>> [TLS] %s() [tls_record_offset=%d][payload_packet_len=%u][direction: %u][%02X %02X %02X...]\n", - __FUNCTION__, flow->l4.tcp.tls_record_offset, packet->payload_packet_len, - packet->packet_direction, - packet->payload[0], packet->payload[1], packet->payload[2]); -#endif - - if((packet->packet_direction == 0) /* Client -> Server */ - || (packet->payload_packet_len == 0)) - return(1); /* More packets please */ - else if(flow->l4.tcp.tls_srv_cert_fingerprint_processed) - return(0); /* We're good */ - - if(flow->l4.tcp.tls_fingerprint_len > 0) { - unsigned int avail = packet->payload_packet_len - flow->l4.tcp.tls_record_offset; + if((len > 9) + && (content_type != 0x17 /* Application Data */) + && (!flow->l4.tcp.tls.certificate_processed)) { + /* Split the element in blocks */ + u_int16_t processed = 5; - if(avail > flow->l4.tcp.tls_fingerprint_len) - avail = flow->l4.tcp.tls_fingerprint_len; + while((processed+4) <= len) { + const u_int8_t *block = (const u_int8_t *)&flow->l4.tcp.tls.message.buffer[processed]; + u_int32_t block_len = (block[1] << 16) + (block[2] << 8) + block[3]; -#ifdef DEBUG_TLS - printf("=>> [TLS] Certificate record [%02X %02X %02X...][missing: %u][offset: %u][avail: %u] (B)\n", - packet->payload[flow->l4.tcp.tls_record_offset], - packet->payload[flow->l4.tcp.tls_record_offset+1], - packet->payload[flow->l4.tcp.tls_record_offset+2], - flow->l4.tcp.tls_fingerprint_len, flow->l4.tcp.tls_record_offset, avail - ); -#endif - -#ifdef DEBUG_CERTIFICATE_HASH - for(i=0;ipayload[flow->l4.tcp.tls_record_offset+i]); - printf("\n"); -#endif - - SHA1Update(flow->l4.tcp.tls_srv_cert_fingerprint_ctx, - &packet->payload[flow->l4.tcp.tls_record_offset], - avail); - - flow->l4.tcp.tls_fingerprint_len -= avail; - - if(flow->l4.tcp.tls_fingerprint_len == 0) { - SHA1Final(flow->l4.tcp.tls_sha1_certificate_fingerprint, flow->l4.tcp.tls_srv_cert_fingerprint_ctx); + if(/* (block_len == 0) || */ /* Note blocks can have zero lenght */ + (block_len > len) || ((block[1] != 0x0))) { + something_went_wrong = 1; + break; + } -#ifdef DEBUG_TLS - { - int i; - - printf("=>> [TLS] SHA-1: "); - for(i=0;i<20;i++) - printf("%s%02X", (i > 0) ? ":" : "", flow->l4.tcp.tls_sha1_certificate_fingerprint[i]); - printf("\n"); + packet->payload = block, packet->payload_packet_len = ndpi_min(block_len+4, flow->l4.tcp.tls.message.buffer_used); + + if((processed+packet->payload_packet_len) > len) { + something_went_wrong = 1; + break; + } + + processTLSBlock(ndpi_struct, flow); + + processed += packet->payload_packet_len; } -#endif - - flow->l4.tcp.tls_srv_cert_fingerprint_processed = 1; - return(0); /* We're good */ } else { - flow->l4.tcp.tls_record_offset = 0; -#ifdef DEBUG_TLS - printf("=>> [TLS] Certificate record: still missing %u bytes\n", flow->l4.tcp.tls_fingerprint_len); + /* Process element as a whole */ + if(content_type == 0x17 /* Application Data */) { + if(flow->l4.tcp.tls.num_tls_blocks < ndpi_struct->num_tls_blocks_to_follow) + flow->l4.tcp.tls.tls_application_blocks_len[flow->l4.tcp.tls.num_tls_blocks++] = + (packet->packet_direction == 0) ? (len-5) : -(len-5); + +#ifdef DEBUG_TLS_BLOCKS + printf("*** [TLS Block] [len: %u][num_tls_blocks: %u/%u]\n", + len-5, flow->l4.tcp.tls.num_tls_blocks, ndpi_struct->num_tls_blocks_to_follow); #endif - return(1); /* More packets please */ + } } - } - if(packet->payload_packet_len <= flow->l4.tcp.tls_record_offset) { - /* Avoid invalid memory accesses */ - return(1); - } + packet->payload = p, packet->payload_packet_len = p_len; /* Restore */ + flow->l4.tcp.tls.message.buffer_used -= len; - if(packet->payload[flow->l4.tcp.tls_record_offset] == 0x15 /* Alert */) { - u_int len = ntohs(*(u_int16_t*)&packet->payload[flow->l4.tcp.tls_record_offset+3]) + 5 /* SSL header len */; + if(flow->l4.tcp.tls.message.buffer_used > 0) + memmove(flow->l4.tcp.tls.message.buffer, + &flow->l4.tcp.tls.message.buffer[len], + flow->l4.tcp.tls.message.buffer_used); + else + break; - if(len < 10 /* Sanity check */) { - if((flow->l4.tcp.tls_record_offset+len) < packet->payload_packet_len) - flow->l4.tcp.tls_record_offset += len; - } else - goto invalid_len; +#ifdef DEBUG_TLS_MEMORY + printf("[TLS Mem] Left memory buffer %u bytes\n", flow->l4.tcp.tls.message.buffer_used); +#endif } - - multiple_messages = (packet->payload[flow->l4.tcp.tls_record_offset] == 0x16 /* Handshake */) ? 0 : 1; -#ifdef DEBUG_TLS - printf("=>> [TLS] [multiple_messages: %d]\n", multiple_messages); + if(something_went_wrong + || ((ndpi_struct->num_tls_blocks_to_follow > 0) + && (flow->l4.tcp.tls.num_tls_blocks == ndpi_struct->num_tls_blocks_to_follow)) + ) { +#ifdef DEBUG_TLS_BLOCKS + printf("*** [TLS Block] No more blocks\n"); #endif - - if((!multiple_messages) && (packet->payload[flow->l4.tcp.tls_record_offset] != 0x16 /* Handshake */)) + flow->check_extra_packets = 0; + flow->extra_packets_func = NULL; + return(0); /* That's all */ + } else return(1); - else if(((!multiple_messages) && (packet->payload[flow->l4.tcp.tls_record_offset+5] == 0xb) /* Certificate */) - || (packet->payload[flow->l4.tcp.tls_record_offset] == 0xb) /* Certificate */) { - /* TODO: Do not take into account all certificates but only the first one */ +} + +/* **************************************** */ + +static int ndpi_search_tls_udp(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + // u_int8_t handshake_type; + u_int32_t handshake_len; + u_int16_t p_len; + const u_int8_t *p; + #ifdef DEBUG_TLS - printf("=>> [TLS] Certificate found\n"); + printf("[TLS] %s()\n", __FUNCTION__); #endif - if(flow->l4.tcp.tls_srv_cert_fingerprint_ctx == NULL) - flow->l4.tcp.tls_srv_cert_fingerprint_ctx = (void*)ndpi_malloc(sizeof(SHA1_CTX)); - else { -#ifdef DEBUG_TLS - printf("[TLS] Internal error: double allocation\n"); -#endif - } - - if(flow->l4.tcp.tls_srv_cert_fingerprint_ctx) { - SHA1Init(flow->l4.tcp.tls_srv_cert_fingerprint_ctx); - flow->l4.tcp.tls_srv_cert_fingerprint_found = 1; - flow->l4.tcp.tls_record_offset += (!multiple_messages) ? 13 : 8; - flow->l4.tcp.tls_fingerprint_len = ntohs(*(u_int16_t*)&packet->payload[flow->l4.tcp.tls_record_offset]); - flow->l4.tcp.tls_record_offset = flow->l4.tcp.tls_record_offset+2; -#ifdef DEBUG_TLS - printf("=>> [TLS] Certificate [total certificate len: %u][certificate initial offset: %u]\n", - flow->l4.tcp.tls_fingerprint_len, flow->l4.tcp.tls_record_offset); -#endif - return(getSSCertificateFingerprint(ndpi_struct, flow)); - } else - return(0); /* That's all */ - } else if(flow->l4.tcp.tls_seen_certificate) - return(0); /* That's all */ - else if(packet->payload_packet_len > flow->l4.tcp.tls_record_offset+7) { - /* This is a handshake but not a certificate record */ - u_int16_t len = ntohs(*(u_int16_t*)&packet->payload[flow->l4.tcp.tls_record_offset+7]); + /* Consider only specific SSL packets (handshake) */ + if((packet->payload_packet_len < 17) + || (packet->payload[0] != 0x16) + || (packet->payload[1] != 0xfe) /* We ignore old DTLS versions */ + || ((packet->payload[2] != 0xff) && (packet->payload[2] != 0xfd)) + || ((ntohs(*((u_int16_t*)&packet->payload[11]))+13) != packet->payload_packet_len) + ) { + no_dtls: #ifdef DEBUG_TLS - printf("=>> [TLS] Found record %02X [len: %u]\n", - packet->payload[flow->l4.tcp.tls_record_offset+5], len); + printf("[TLS] No DTLS found\n"); #endif - if(len > 4096) { - invalid_len: - /* This looks an invalid len: we giveup */ - flow->l4.tcp.tls_record_offset = 0, flow->l4.tcp.tls_srv_cert_fingerprint_processed = 1; -#ifdef DEBUG_TLS - printf("=>> [TLS] Invalid fingerprint processing %u <-> %u\n", - ntohs(packet->tcp->source), ntohs(packet->tcp->dest)); + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return(0); /* Giveup */ + } + + // handshake_type = packet->payload[13]; + handshake_len = (packet->payload[14] << 16) + (packet->payload[15] << 8) + packet->payload[16]; + + if((handshake_len+25) != packet->payload_packet_len) + goto no_dtls; + + /* Overwriting packet payload */ + p = packet->payload, p_len = packet->payload_packet_len; /* Backup */ + packet->payload = &packet->payload[13], packet->payload_packet_len -= 13; + + processTLSBlock(ndpi_struct, flow); + + packet->payload = p, packet->payload_packet_len = p_len; /* Restore */ + + ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TLS); + + return(1); /* Keep working */ +} + +/* **************************************** */ + +static void tlsInitExtraPacketProcessing(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + flow->check_extra_packets = 1; + + /* At most 12 packets should almost always be enough to find the server certificate if it's there */ + flow->max_extra_packets_to_check = 12 + (ndpi_struct->num_tls_blocks_to_follow*4); + flow->extra_packets_func = (flow->packet.udp != NULL) ? ndpi_search_tls_udp : ndpi_search_tls_tcp; +} + +/* **************************************** */ + +static void ndpi_int_tls_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, u_int32_t protocol) { +#if DEBUG_TLS + printf("[TLS] %s()\n", __FUNCTION__); #endif - return(0); - } else { - flow->l4.tcp.tls_record_offset += len + 9; - - if(flow->l4.tcp.tls_record_offset < packet->payload_packet_len) - return(getSSCertificateFingerprint(ndpi_struct, flow)); - else { - flow->l4.tcp.tls_record_offset -= packet->payload_packet_len; - } - } + + if((flow->detected_protocol_stack[0] == protocol) + || (flow->detected_protocol_stack[1] == protocol)) { + if(!flow->check_extra_packets) + tlsInitExtraPacketProcessing(ndpi_struct, flow); + return; } - - return(1); + + if(protocol != NDPI_PROTOCOL_TLS) + ; + else + protocol = ndpi_tls_refine_master_protocol(ndpi_struct, flow, protocol); + + ndpi_set_detected_protocol(ndpi_struct, flow, protocol, NDPI_PROTOCOL_TLS); + tlsInitExtraPacketProcessing(ndpi_struct, flow); } /* **************************************** */ -/* See https://blog.catchpoint.com/2017/05/12/dissecting-tls-using-wireshark/ */ -void getSSLorganization(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow, - char *buffer, int buffer_len) { +/* https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967 */ + +#define JA3_STR_LEN 1024 +#define MAX_NUM_JA3 512 + +struct ja3_info { + u_int16_t tls_handshake_version; + u_int16_t num_cipher, cipher[MAX_NUM_JA3]; + u_int16_t num_tls_extension, tls_extension[MAX_NUM_JA3]; + u_int16_t num_elliptic_curve, elliptic_curve[MAX_NUM_JA3]; + u_int16_t num_elliptic_curve_point_format, elliptic_curve_point_format[MAX_NUM_JA3]; +}; + +/* **************************************** */ + +int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, uint32_t quic_version) { struct ndpi_packet_struct *packet = &flow->packet; + struct ja3_info ja3; + u_int8_t invalid_ja3 = 0; + u_int16_t tls_version, ja3_str_len; + char ja3_str[JA3_STR_LEN]; + ndpi_MD5_CTX ctx; + u_char md5_hash[16]; + int i; u_int16_t total_len; - u_int8_t handshake_protocol; - - if(packet->payload[0] != 0x16 /* Handshake */) - return; + u_int8_t handshake_type; + char buffer[64] = { '\0' }; + int is_quic = (quic_version != 0); + int is_dtls = packet->udp && (!is_quic); - total_len = (packet->payload[3] << 8) + packet->payload[4] + 5 /* SSL Header */; - handshake_protocol = packet->payload[5]; /* handshake protocol a bit misleading, it is message type according TLS specs */ - - if((handshake_protocol != 0x02) - && (handshake_protocol != 0xb) /* Server Hello and Certificate message types are interesting for us */) - return; +#ifdef DEBUG_TLS + printf("SSL %s() called\n", __FUNCTION__); +#endif + + memset(&ja3, 0, sizeof(ja3)); + + handshake_type = packet->payload[0]; + total_len = (packet->payload[1] << 16) + (packet->payload[2] << 8) + packet->payload[3]; + + if((total_len > packet->payload_packet_len) || (packet->payload[1] != 0x0)) + return(0); /* Not found */ + + total_len = packet->payload_packet_len; + + /* 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 = (!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]; #ifdef DEBUG_TLS - printf("=>> [TLS] Certificate [total_len: %u/%u]\n", ntohs(*(u_int16_t*)&packet->payload[3]), total_len); + printf("SSL [len: %u][handshake_type: %02X]\n", packet->payload_packet_len, handshake_type); #endif - - /* Truncate total len, search at least in incomplete packet */ - if(total_len > packet->payload_packet_len) - total_len = packet->payload_packet_len; - memset(buffer, 0, buffer_len); + tls_version = ntohs(*((u_int16_t*)&packet->payload[version_offset])); + flow->protos.stun_ssl.ssl.ssl_version = ja3.tls_handshake_version = tls_version; + if(flow->protos.stun_ssl.ssl.ssl_version < 0x0302) /* TLSv1.1 */ + NDPI_SET_BIT(flow->risk, NDPI_TLS_OBSOLETE_VERSION); - /* Check after handshake protocol header (5 bytes) and message header (4 bytes) */ - u_int num_found = 0; - u_int i, j; - for(i = 9; i < packet->payload_packet_len-4; i++) { - /* Organization OID: 2.5.4.10 */ - if((packet->payload[i] == 0x55) && (packet->payload[i+1] == 0x04) && (packet->payload[i+2] == 0x0a)) { - u_int8_t server_len = packet->payload[i+4]; - - num_found++; - /* what we want is subject certificate, so we bypass the issuer certificate */ - if(num_found != 2) continue; - - // packet is truncated... further inspection is not needed - if(i+4+server_len >= packet->payload_packet_len) { - break; + if(handshake_type == 0x02 /* Server Hello */) { + int i, rc; + +#ifdef DEBUG_TLS + printf("SSL Server Hello [version: 0x%04X]\n", tls_version); +#endif + + /* + The server hello decides about the SSL version of this flow + https://networkengineering.stackexchange.com/questions/55752/why-does-wireshark-show-version-tls-1-2-here-instead-of-tls-1-3 + */ + if(packet->udp) + offset += 1; + else { + if(tls_version < 0x7F15 /* TLS 1.3 lacks of session id */) + offset += session_id_len+1; } - char *server_org = (char*)&packet->payload[i+5]; + if((offset+3) > packet->payload_packet_len) + return(0); /* Not found */ - u_int len = (u_int)ndpi_min(server_len, buffer_len-1); - strncpy(buffer, server_org, len); - buffer[len] = '\0'; + ja3.num_cipher = 1, ja3.cipher[0] = ntohs(*((u_int16_t*)&packet->payload[offset])); + if((flow->protos.stun_ssl.ssl.server_unsafe_cipher = ndpi_is_safe_ssl_cipher(ja3.cipher[0])) == 1) + NDPI_SET_BIT(flow->risk, NDPI_TLS_WEAK_CIPHER); - // check if organization string are all printable - u_int8_t is_printable = 1; - for (j = 0; j < len; j++) { - if(!ndpi_isprint(buffer[j])) { - is_printable = 0; - break; - } - } + flow->protos.stun_ssl.ssl.server_cipher = ja3.cipher[0]; - if(is_printable == 1) { - snprintf(flow->protos.stun_ssl.ssl.server_organization, - sizeof(flow->protos.stun_ssl.ssl.server_organization), "%s", buffer); #ifdef DEBUG_TLS - printf("Certificate organization: %s\n", flow->protos.stun_ssl.ssl.server_organization); + printf("TLS [server][session_id_len: %u][cipher: %04X]\n", session_id_len, ja3.cipher[0]); #endif - } - } else if((packet->payload[i] == 0x30) && (packet->payload[i+1] == 0x1e) && (packet->payload[i+2] == 0x17)) { - u_int8_t len = packet->payload[i+3]; - u_int offset = i+4; - if((offset+len) < packet->payload_packet_len) { - char utcDate[32]; + offset += 2 + 1; + + if((offset + 1) < packet->payload_packet_len) /* +1 because we are goint to read 2 bytes */ + extension_len = ntohs(*((u_int16_t*)&packet->payload[offset])); + else + extension_len = 0; #ifdef DEBUG_TLS - printf("[CERTIFICATE] notBefore [len: %u][", len); - for(j=0; jpayload[i+4+j]); - printf("]\n"); + printf("TLS [server][extension_len: %u]\n", extension_len); #endif + offset += 2; - if(len < (sizeof(utcDate)-1)) { - struct tm utc; - utc.tm_isdst = -1; /* Not set by strptime */ + for(i=0; ipayload[i+4], len); - utcDate[len] = '\0'; + if((offset+4) > packet->payload_packet_len) break; + + extension_id = ntohs(*((u_int16_t*)&packet->payload[offset])); + extension_len = ntohs(*((u_int16_t*)&packet->payload[offset+2])); + + if(ja3.num_tls_extension < MAX_NUM_JA3) + ja3.tls_extension[ja3.num_tls_extension++] = extension_id; - /* 141021000000Z */ - if(strptime(utcDate, "%y%m%d%H%M%SZ", &utc) != NULL) { - flow->protos.stun_ssl.ssl.notBefore = timegm(&utc); #ifdef DEBUG_TLS - printf("[CERTIFICATE] notBefore %u [%s]\n", - flow->protos.stun_ssl.ssl.notBefore, utcDate); + printf("TLS [server][extension_id: %u/0x%04X][len: %u]\n", + extension_id, extension_id, extension_len); +#endif + + if(extension_id == 43 /* supported versions */) { + if(extension_len >= 2) { + u_int16_t tls_version = ntohs(*((u_int16_t*)&packet->payload[offset+4])); + +#ifdef DEBUG_TLS + printf("TLS [server] [TLS version: 0x%04X]\n", tls_version); #endif + + flow->protos.stun_ssl.ssl.ssl_version = tls_version; } } - offset += len; + i += 4 + extension_len, offset += 4 + extension_len; + } - if((offset+1) < packet->payload_packet_len) { - len = packet->payload[offset+1]; + ja3_str_len = snprintf(ja3_str, sizeof(ja3_str), "%u,", ja3.tls_handshake_version); - offset += 2; + for(i=0; i 0) ? "-" : "", ja3.cipher[i]); + + if(rc <= 0) break; else ja3_str_len += rc; + } + + rc = snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; + + /* ********** */ + + for(i=0; i 0) ? "-" : "", ja3.tls_extension[i]); + + if(rc <= 0) break; else ja3_str_len += rc; + } - if((offset+len) < packet->payload_packet_len) { #ifdef DEBUG_TLS - printf("[CERTIFICATE] notAfter [len: %u][", len); - for(j=0; jpayload[offset+j]); - printf("]\n"); + printf("TLS [server] %s\n", ja3_str); #endif - if(len < (sizeof(utcDate)-1)) { - struct tm utc; - utc.tm_isdst = -1; /* Not set by strptime */ +#ifdef DEBUG_TLS + printf("[JA3] Server: %s \n", ja3_str); +#endif - strncpy(utcDate, (const char*)&packet->payload[offset], len); - utcDate[len] = '\0'; + ndpi_MD5Init(&ctx); + ndpi_MD5Update(&ctx, (const unsigned char *)ja3_str, strlen(ja3_str)); + ndpi_MD5Final(md5_hash, &ctx); + + for(i=0, j=0; i<16; i++) { + int rc = snprintf(&flow->protos.stun_ssl.ssl.ja3_server[j], + sizeof(flow->protos.stun_ssl.ssl.ja3_server)-j, "%02x", md5_hash[i]); + if(rc <= 0) break; else j += rc; + } - /* 141021000000Z */ - if(strptime(utcDate, "%y%m%d%H%M%SZ", &utc) != NULL) { - flow->protos.stun_ssl.ssl.notAfter = timegm(&utc); #ifdef DEBUG_TLS - printf("[CERTIFICATE] notAfter %u [%s]\n", - flow->protos.stun_ssl.ssl.notAfter, utcDate); + printf("[JA3] Server: %s \n", flow->protos.stun_ssl.ssl.ja3_server); +#endif + } else if(handshake_type == 0x01 /* Client Hello */) { + u_int16_t cipher_len, cipher_offset; + + if((session_id_len+base_offset+3) > packet->payload_packet_len) + return(0); /* Not found */ + + 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 { + cipher_len = ntohs(*((u_int16_t*)&packet->payload[base_offset+2])); + cipher_offset = base_offset+4; + } + +#ifdef DEBUG_TLS + printf("Client SSL [client cipher_len: %u][tls_version: 0x%04X]\n", cipher_len, tls_version); +#endif + + if((cipher_offset+cipher_len) <= total_len) { + for(i=0; ipayload[cipher_offset+i]; + +#ifdef DEBUG_TLS + printf("Client SSL [cipher suite: %u/0x%04X] [%d/%u]\n", ntohs(*id), ntohs(*id), i, cipher_len); +#endif + if((*id == 0) || (packet->payload[cipher_offset+i] != packet->payload[cipher_offset+i+1])) { + /* + Skip GREASE [https://tools.ietf.org/id/draft-ietf-tls-grease-01.html] + https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967 + */ + + if(ja3.num_cipher < MAX_NUM_JA3) + ja3.cipher[ja3.num_cipher++] = ntohs(*id); + else { + invalid_ja3 = 1; +#ifdef DEBUG_TLS + printf("Client SSL Invalid cipher %u\n", ja3.num_cipher); #endif - } } } + + i += 2; } + } else { + invalid_ja3 = 1; +#ifdef DEBUG_TLS + printf("Client SSL Invalid len %u vs %u\n", (cipher_offset+cipher_len), total_len); +#endif } - } - } -} -/* **************************************** */ + offset = base_offset + session_id_len + cipher_len + 2; -int sslTryAndRetrieveServerCertificate(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - int rc = 1; - - if(packet->tcp) { - if(!flow->l4.tcp.tls_srv_cert_fingerprint_processed) - getSSCertificateFingerprint(ndpi_struct, flow); - } - -#if 1 - /* consider only specific SSL packets (handshake) */ - if((packet->payload_packet_len > 9) && (packet->payload[0] == 0x16)) { - char certificate[64]; - int rc; - - certificate[0] = '\0'; - rc = getTLScertificate(ndpi_struct, flow, certificate, sizeof(certificate)); - packet->tls_certificate_num_checks++; - - if(rc > 0) { - char organization[64]; - - // try fetch server organization once server certificate is found - organization[0] = '\0'; - getSSLorganization(ndpi_struct, flow, organization, sizeof(organization)); - - packet->tls_certificate_detected++; -#if 0 - if((flow->l4.tcp.tls_seen_server_cert == 1) - && (flow->protos.stun_ssl.ssl.server_certificate[0] != '\0')) - /* 0 means we've done processing extra packets (since we found what we wanted) */ - return 0; + if(offset < total_len) { + u_int16_t compression_len; + u_int16_t extensions_len; + + offset += (!is_dtls) ? 1 : 2; + compression_len = packet->payload[offset]; + offset++; + +#ifdef DEBUG_TLS + printf("Client SSL [compression_len: %u]\n", compression_len); #endif - } - if(flow->l4.tcp.tls_record_offset == 0) { - /* Client hello, Server Hello, and certificate packets probably all checked in this case */ - if(((packet->tls_certificate_num_checks >= 3) - && (flow->l4.tcp.seen_syn) - && (flow->l4.tcp.seen_syn_ack) - && (flow->l4.tcp.seen_ack) /* We have seen the 3-way handshake */ - && flow->l4.tcp.tls_srv_cert_fingerprint_processed) - /* || (flow->protos.stun_ssl.ssl.ja3_server[0] != '\0') */ - ) { - /* We're done processing extra packets since we've probably checked all possible cert packets */ - return(rc); - } - } - } + // offset += compression_len + 3; + offset += compression_len; + + if(offset < total_len) { + extensions_len = ntohs(*((u_int16_t*)&packet->payload[offset])); + offset += 2; + +#ifdef DEBUG_TLS + printf("Client SSL [extensions_len: %u]\n", extensions_len); #endif - - /* 1 means keep looking for more packets */ - if(!flow->l4.tcp.tls_srv_cert_fingerprint_processed) rc = 1; - return(rc); -} -/* **************************************** */ + if((extensions_len+offset) <= total_len) { + /* Move to the first extension + Type is u_int to avoid possible overflow on extension_len addition */ + u_int extension_offset = 0; + u_int32_t j; -void sslInitExtraPacketProcessing(int caseNum, struct ndpi_flow_struct *flow) { - flow->check_extra_packets = 1; - /* 0 is the case for waiting for the server certificate */ - if(caseNum == 0) { - /* At most 7 packets should almost always be enough to find the server certificate if it's there */ - flow->max_extra_packets_to_check = 7; - flow->extra_packets_func = sslTryAndRetrieveServerCertificate; - } -} + while(extension_offset < extensions_len) { + u_int16_t extension_id, extension_len, extn_off = offset+extension_offset; -/* **************************************** */ + extension_id = ntohs(*((u_int16_t*)&packet->payload[offset+extension_offset])); + extension_offset += 2; -int tlsDetectProtocolFromCertificate(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow, - u_int8_t skip_cert_processing) { - struct ndpi_packet_struct *packet = &flow->packet; + extension_len = ntohs(*((u_int16_t*)&packet->payload[offset+extension_offset])); + extension_offset += 2; + +#ifdef DEBUG_TLS + printf("Client SSL [extension_id: %u][extension_len: %u]\n", extension_id, extension_len); +#endif - if((!skip_cert_processing) && packet->tcp) { - if(!flow->l4.tcp.tls_srv_cert_fingerprint_processed) - getSSCertificateFingerprint(ndpi_struct, flow); - } + if((extension_id == 0) || (packet->payload[extn_off] != packet->payload[extn_off+1])) { + /* Skip GREASE */ - if((packet->payload_packet_len > 9) - && (packet->payload[0] == 0x16 /* consider only specific SSL packets (handshake) */)) { - if((packet->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) - || (packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS)) { - char certificate[64]; - int rc; + if(ja3.num_tls_extension < MAX_NUM_JA3) + ja3.tls_extension[ja3.num_tls_extension++] = extension_id; + else { + invalid_ja3 = 1; +#ifdef DEBUG_TLS + printf("Client SSL Invalid extensions %u\n", ja3.num_tls_extension); +#endif + } + } - certificate[0] = '\0'; - rc = getTLScertificate(ndpi_struct, flow, certificate, sizeof(certificate)); - packet->tls_certificate_num_checks++; + if(extension_id == 0 /* server name */) { + u_int16_t len; - if(rc > 0) { - packet->tls_certificate_detected++; #ifdef DEBUG_TLS - NDPI_LOG_DBG2(ndpi_struct, "***** [SSL] %s\n", certificate); -#endif - ndpi_protocol_match_result ret_match; - u_int16_t subproto = ndpi_match_host_subprotocol(ndpi_struct, flow, certificate, - strlen(certificate), - &ret_match, - NDPI_PROTOCOL_TLS); - - if(subproto != NDPI_PROTOCOL_UNKNOWN) { - /* If we've detected the subprotocol from client certificate but haven't had a chance - * to see the server certificate yet, set up extra packet processing to wait - * a few more packets. */ - if(((flow->l4.tcp.tls_seen_client_cert == 1) && (flow->protos.stun_ssl.ssl.client_certificate[0] != '\0')) - && ((flow->l4.tcp.tls_seen_server_cert != 1) && (flow->protos.stun_ssl.ssl.server_certificate[0] == '\0'))) { - sslInitExtraPacketProcessing(0, flow); - } + printf("[TLS] Extensions: found server name\n"); +#endif - ndpi_set_detected_protocol(ndpi_struct, flow, subproto, - ndpi_tls_refine_master_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TLS)); - return(rc); - } + len = (packet->payload[offset+extension_offset+3] << 8) + packet->payload[offset+extension_offset+4]; + len = (u_int)ndpi_min(len, sizeof(buffer)-1); - if(ndpi_is_tls_tor(ndpi_struct, flow, certificate) != 0) - return(rc); - } + if((offset+extension_offset+5+len) <= packet->payload_packet_len) { + strncpy(buffer, (char*)&packet->payload[offset+extension_offset+5], len); + buffer[len] = '\0'; - if(((packet->tls_certificate_num_checks >= 3) - && flow->l4.tcp.seen_syn - && flow->l4.tcp.seen_syn_ack - && flow->l4.tcp.seen_ack /* We have seen the 3-way handshake */ - && flow->l4.tcp.tls_srv_cert_fingerprint_processed - ) - /* - || ((flow->l4.tcp.tls_seen_certificate == 1) - && (flow->l4.tcp.tls_seen_server_cert == 1) - && (flow->protos.stun_ssl.ssl.server_certificate[0] != '\0')) - */ - /* || ((flow->l4.tcp.tls_seen_client_cert == 1) && (flow->protos.stun_ssl.ssl.client_certificate[0] != '\0')) */ - ) { - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TLS); - } - } - } - - return(0); -} + cleanupServerName(buffer, sizeof(buffer)); -/* **************************************** */ + snprintf(flow->protos.stun_ssl.ssl.client_requested_server_name, + sizeof(flow->protos.stun_ssl.ssl.client_requested_server_name), + "%s", buffer); +#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; + } -static void tls_mark_and_payload_search(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow, - u_int8_t skip_cert_processing) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int32_t a; - u_int32_t end; + ndpi_check_dga_name(ndpi_struct, flow, + flow->protos.stun_ssl.ssl.client_requested_server_name, 1); + } else { +#ifdef DEBUG_TLS + printf("[TLS] Extensions server len too short: %u vs %u\n", + offset+extension_offset+5+len, + packet->payload_packet_len); +#endif + } + } else if(extension_id == 10 /* supported groups */) { + u_int16_t s_offset = offset+extension_offset + 2; #ifdef DEBUG_TLS - printf("[TLS] %s()\n", __FUNCTION__); + printf("Client SSL [EllipticCurveGroups: len=%u]\n", extension_len); #endif - - if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(ndpi_struct->detection_bitmask, NDPI_PROTOCOL_UNENCRYPTED_JABBER) != 0) - goto check_for_tls_payload; - if(NDPI_COMPARE_PROTOCOL_TO_BITMASK(ndpi_struct->detection_bitmask, NDPI_PROTOCOL_OSCAR) != 0) - goto check_for_tls_payload; - else - goto no_check_for_tls_payload; - - check_for_tls_payload: - end = packet->payload_packet_len - 20; - for (a = 5; a < end; a++) { - - if(packet->payload[a] == 't') { - if(memcmp(&packet->payload[a], "talk.google.com", 15) == 0) { - if(NDPI_COMPARE_PROTOCOL_TO_BITMASK - (ndpi_struct->detection_bitmask, NDPI_PROTOCOL_UNENCRYPTED_JABBER) != 0) { - NDPI_LOG_INFO(ndpi_struct, "found ssl jabber unencrypted\n"); - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_UNENCRYPTED_JABBER); - return; - } - } - } + if((s_offset+extension_len-2) <= total_len) { + for(i=0; ipayload[s_offset+i])); - if(packet->payload[a] == 'A' || packet->payload[a] == 'k' || packet->payload[a] == 'c' - || packet->payload[a] == 'h') { - if(((a + 19) < packet->payload_packet_len && memcmp(&packet->payload[a], "America Online Inc.", 19) == 0) - // || (end - c > 3 memcmp (&packet->payload[c],"AOL", 3) == 0 ) - // || (end - c > 7 && memcmp (&packet->payload[c], "AOL LLC", 7) == 0) - || ((a + 15) < packet->payload_packet_len && memcmp(&packet->payload[a], "kdc.uas.aol.com", 15) == 0) - || ((a + 14) < packet->payload_packet_len && memcmp(&packet->payload[a], "corehc@aol.net", 14) == 0) - || ((a + 41) < packet->payload_packet_len - && memcmp(&packet->payload[a], "http://crl.aol.com/AOLMSPKI/aolServerCert", 41) == 0) - || ((a + 28) < packet->payload_packet_len - && memcmp(&packet->payload[a], "http://ocsp.web.aol.com/ocsp", 28) == 0) - || ((a + 32) < packet->payload_packet_len - && memcmp(&packet->payload[a], "http://pki-info.aol.com/AOLMSPKI", 32) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found OSCAR SERVER SSL DETECTED\n"); - - if(flow->dst != NULL && packet->payload_packet_len > 75) { - memcpy(flow->dst->oscar_ssl_session_id, &packet->payload[44], 32); - flow->dst->oscar_ssl_session_id[32] = '\0'; - flow->dst->oscar_last_safe_access_time = packet->tick_timestamp; - } +#ifdef DEBUG_TLS + printf("Client SSL [EllipticCurve: %u/0x%04X]\n", s_group, s_group); +#endif + if((s_group == 0) || (packet->payload[s_offset+i] != packet->payload[s_offset+i+1])) { + /* Skip GREASE */ + if(ja3.num_elliptic_curve < MAX_NUM_JA3) + ja3.elliptic_curve[ja3.num_elliptic_curve++] = s_group; + else { + invalid_ja3 = 1; +#ifdef DEBUG_TLS + printf("Client SSL Invalid num elliptic %u\n", ja3.num_elliptic_curve); +#endif + } + } - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_OSCAR); - return; - } - } + i += 2; + } + } else { + invalid_ja3 = 1; +#ifdef DEBUG_TLS + printf("Client SSL Invalid len %u vs %u\n", (s_offset+extension_len-1), total_len); +#endif + } + } else if(extension_id == 11 /* ec_point_formats groups */) { + u_int16_t s_offset = offset+extension_offset + 1; - if(packet->payload[a] == 'm' || packet->payload[a] == 's') { - if((a + 21) < packet->payload_packet_len && - (memcmp(&packet->payload[a], "my.screenname.aol.com", 21) == 0 - || memcmp(&packet->payload[a], "sns-static.aolcdn.com", 21) == 0)) { - NDPI_LOG_DBG(ndpi_struct, "found OSCAR SERVER SSL DETECTED\n"); - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_OSCAR); - return; - } - } - } +#ifdef DEBUG_TLS + printf("Client SSL [EllipticCurveFormat: len=%u]\n", extension_len); +#endif + if((s_offset+extension_len-1) <= total_len) { + for(i=0; ipayload[s_offset+i]; - no_check_for_tls_payload: - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) { - NDPI_LOG_DBG(ndpi_struct, "found ssl connection\n"); - tlsDetectProtocolFromCertificate(ndpi_struct, flow, skip_cert_processing); - - if(!packet->tls_certificate_detected - && (!(flow->l4.tcp.tls_seen_client_cert && flow->l4.tcp.tls_seen_server_cert))) { - /* SSL without certificate (Skype, Ultrasurf?) */ - NDPI_LOG_INFO(ndpi_struct, "found ssl NO_CERT\n"); - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TLS); - } else if((packet->tls_certificate_num_checks >= 3) - && flow->l4.tcp.tls_srv_cert_fingerprint_processed) { - NDPI_LOG_INFO(ndpi_struct, "found ssl\n"); - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TLS); - } - } -} +#ifdef DEBUG_TLS + printf("Client SSL [EllipticCurveFormat: %u]\n", s_group); +#endif -/* **************************************** */ + if(ja3.num_elliptic_curve_point_format < MAX_NUM_JA3) + ja3.elliptic_curve_point_format[ja3.num_elliptic_curve_point_format++] = s_group; + else { + invalid_ja3 = 1; +#ifdef DEBUG_TLS + printf("Client SSL Invalid num elliptic %u\n", ja3.num_elliptic_curve_point_format); +#endif + } + } + } else { + invalid_ja3 = 1; +#ifdef DEBUG_TLS + printf("Client SSL Invalid len %u vs %u\n", s_offset+extension_len, total_len); +#endif + } + } else if(extension_id == 16 /* application_layer_protocol_negotiation */) { + u_int16_t s_offset = offset+extension_offset; + u_int16_t tot_alpn_len = ntohs(*((u_int16_t*)&packet->payload[s_offset])); + char alpn_str[256]; + u_int8_t alpn_str_len = 0; -static u_int8_t ndpi_search_tlsv3_direction1(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; +#ifdef DEBUG_TLS + printf("Client SSL [ALPN: block_len=%u/len=%u]\n", extension_len, tot_alpn_len); +#endif + s_offset += 2; + tot_alpn_len += s_offset; - if((packet->payload_packet_len >= 5) - && ((packet->payload[0] == 0x16) || packet->payload[0] == 0x17) - && (packet->payload[1] == 0x03) - && ((packet->payload[2] == 0x00) || (packet->payload[2] == 0x01) || - (packet->payload[2] == 0x02) || (packet->payload[2] == 0x03))) { - u_int32_t temp; - NDPI_LOG_DBG2(ndpi_struct, "search sslv3\n"); - // SSLv3 Record - if(packet->payload_packet_len >= 1300) { - return 1; - } - temp = ntohs(get_u_int16_t(packet->payload, 3)) + 5; - NDPI_LOG_DBG2(ndpi_struct, "temp = %u\n", temp); - if(packet->payload_packet_len == temp - || (temp < packet->payload_packet_len && packet->payload_packet_len > 500)) { - return 1; - } + while(s_offset < tot_alpn_len && s_offset < total_len) { + u_int8_t alpn_i, alpn_len = packet->payload[s_offset++]; - if(packet->payload_packet_len < temp && temp < 5000 && packet->payload_packet_len > 9) { - /* the server hello may be split into small packets */ - u_int32_t cert_start; + if((s_offset + alpn_len) <= tot_alpn_len) { +#ifdef DEBUG_TLS + printf("Client SSL [ALPN: %u]\n", alpn_len); +#endif - NDPI_LOG_DBG2(ndpi_struct, - "maybe SSLv3 server hello split into smaller packets\n"); + if((alpn_str_len+alpn_len+1) < (sizeof(alpn_str)-1)) { + if(alpn_str_len > 0) { + alpn_str[alpn_str_len] = ','; + alpn_str_len++; + } - /* lets hope at least the server hello and the start of the certificate block are in the first packet */ - cert_start = ntohs(get_u_int16_t(packet->payload, 7)) + 5 + 4; - NDPI_LOG_DBG2(ndpi_struct, "suspected start of certificate: %u\n", - cert_start); + for(alpn_i=0; alpn_ipayload[s_offset+alpn_i]; - if(cert_start < packet->payload_packet_len && packet->payload[cert_start] == 0x0b) { - NDPI_LOG_DBG2(ndpi_struct, - "found 0x0b at suspected start of certificate block\n"); - return 2; - } - } + s_offset += alpn_len, alpn_str_len += alpn_len;; + } else + break; + } else + break; + } /* while */ - if((packet->payload_packet_len > temp) && (packet->payload_packet_len > 100)) { - /* the server hello may be split into small packets and the certificate has its own SSL Record - * so temp contains only the length for the first ServerHello block */ - u_int32_t cert_start; + alpn_str[alpn_str_len] = '\0'; - NDPI_LOG_DBG2(ndpi_struct, - "maybe SSLv3 server hello split into smaller packets but with seperate record for the certificate\n"); +#ifdef DEBUG_TLS + printf("Client SSL [ALPN: %s][len: %u]\n", alpn_str, alpn_str_len); +#endif + if(flow->protos.stun_ssl.ssl.alpn == NULL) + flow->protos.stun_ssl.ssl.alpn = ndpi_strdup(alpn_str); + } else if(extension_id == 43 /* supported versions */) { + u_int16_t s_offset = offset+extension_offset; + u_int8_t version_len = packet->payload[s_offset]; + char version_str[256]; + u_int8_t version_str_len = 0; + version_str[0] = 0; +#ifdef DEBUG_TLS + printf("Client SSL [TLS version len: %u]\n", version_len); +#endif - /* lets hope at least the server hello record and the start of the certificate record are in the first packet */ - cert_start = ntohs(get_u_int16_t(packet->payload, 7)) + 5 + 5 + 4; - NDPI_LOG_DBG2(ndpi_struct, "suspected start of certificate: %u\n", - cert_start); + if(version_len == (extension_len-1)) { + u_int8_t j; - if(cert_start < packet->payload_packet_len && packet->payload[cert_start] == 0x0b) { - NDPI_LOG_DBG2(ndpi_struct, - "found 0x0b at suspected start of certificate block\n"); - return 2; - } - } + s_offset++; + // careful not to overflow and loop forever with u_int8_t + for(j=0; j+1payload[s_offset+j])); + u_int8_t unknown_tls_version; - if(packet->payload_packet_len >= temp + 5 && (packet->payload[temp] == 0x14 || packet->payload[temp] == 0x16) - && packet->payload[temp + 1] == 0x03) { - u_int32_t temp2 = ntohs(get_u_int16_t(packet->payload, temp + 3)) + 5; - if(temp + temp2 > NDPI_MAX_TLS_REQUEST_SIZE) { - return 1; - } - temp += temp2; - NDPI_LOG_DBG2(ndpi_struct, "temp = %u\n", temp); - if(packet->payload_packet_len == temp) { - return 1; - } - if(packet->payload_packet_len >= temp + 5 && - packet->payload[temp] == 0x16 && packet->payload[temp + 1] == 0x03) { - temp2 = ntohs(get_u_int16_t(packet->payload, temp + 3)) + 5; - if(temp + temp2 > NDPI_MAX_TLS_REQUEST_SIZE) { - return 1; - } - temp += temp2; - NDPI_LOG_DBG2(ndpi_struct, "temp = %u\n", temp); - if(packet->payload_packet_len == temp) { - return 1; - } - if(packet->payload_packet_len >= temp + 5 && - packet->payload[temp] == 0x16 && packet->payload[temp + 1] == 0x03) { - temp2 = ntohs(get_u_int16_t(packet->payload, temp + 3)) + 5; - if(temp + temp2 > NDPI_MAX_TLS_REQUEST_SIZE) { - return 1; - } - temp += temp2; - NDPI_LOG_DBG2(ndpi_struct, "temp = %u\n", temp); - if(temp == packet->payload_packet_len) { - return 1; - } - } - } - } - } +#ifdef DEBUG_TLS + printf("Client SSL [TLS version: %s/0x%04X]\n", + ndpi_ssl_version2str(flow, tls_version, &unknown_tls_version), tls_version); +#endif - return 0; -} + if((version_str_len+8) < sizeof(version_str)) { + int rc = snprintf(&version_str[version_str_len], + sizeof(version_str) - version_str_len, "%s%s", + (version_str_len > 0) ? "," : "", + ndpi_ssl_version2str(flow, tls_version, &unknown_tls_version)); + if(rc <= 0) + break; + else + version_str_len += rc; + } + } + if(flow->protos.stun_ssl.ssl.tls_supported_versions == NULL) + flow->protos.stun_ssl.ssl.tls_supported_versions = ndpi_strdup(version_str); + } + } else if(extension_id == 65486 /* encrypted server name */) { + /* + - https://tools.ietf.org/html/draft-ietf-tls-esni-06 + - https://blog.cloudflare.com/encrypted-sni/ + */ + u_int16_t e_offset = offset+extension_offset; + u_int16_t initial_offset = e_offset; + u_int16_t e_sni_len, cipher_suite = ntohs(*((u_int16_t*)&packet->payload[e_offset])); + + flow->protos.stun_ssl.ssl.encrypted_sni.cipher_suite = cipher_suite; + + e_offset += 2; /* Cipher suite len */ + + /* Key Share Entry */ + e_offset += 2; /* Group */ + e_offset += ntohs(*((u_int16_t*)&packet->payload[e_offset])) + 2; /* Lenght */ + + if((e_offset+4) < packet->payload_packet_len) { + /* Record Digest */ + e_offset += ntohs(*((u_int16_t*)&packet->payload[e_offset])) + 2; /* Lenght */ + + if((e_offset+4) < packet->payload_packet_len) { + e_sni_len = ntohs(*((u_int16_t*)&packet->payload[e_offset])); + e_offset += 2; + + if((e_offset+e_sni_len-extension_len-initial_offset) >= 0 && + e_offset+e_sni_len < packet->payload_packet_len) { +#ifdef DEBUG_ENCRYPTED_SNI + printf("Client SSL [Encrypted Server Name len: %u]\n", e_sni_len); +#endif -/* **************************************** */ + if(flow->protos.stun_ssl.ssl.encrypted_sni.esni == NULL) { + flow->protos.stun_ssl.ssl.encrypted_sni.esni = (char*)ndpi_malloc(e_sni_len*2+1); -void ndpi_search_tls_tcp_udp(struct ndpi_detection_module_struct *ndpi_struct, - struct ndpi_flow_struct *flow) { - struct ndpi_packet_struct *packet = &flow->packet; - u_int8_t ret, skip_cert_processing = 0; + if(flow->protos.stun_ssl.ssl.encrypted_sni.esni) { + u_int16_t i, off; - if(packet->udp != NULL) { - /* DTLS dissector */ - int rc = sslTryAndRetrieveServerCertificate(ndpi_struct, flow); + for(i=e_offset, off=0; i<(e_offset+e_sni_len); i++) { + int rc = sprintf(&flow->protos.stun_ssl.ssl.encrypted_sni.esni[off], "%02X", packet->payload[i] & 0XFF); + + if(rc <= 0) { + flow->protos.stun_ssl.ssl.encrypted_sni.esni[off] = '\0'; + break; + } else + off += rc; + } + } + } + } + } + } + } else if(extension_id == 65445 /* QUIC transport parameters */) { + u_int16_t s_offset = offset+extension_offset; + uint16_t final_offset; + int using_var_int = is_version_with_var_int_transport_params(quic_version); + + if(!using_var_int) { + if(s_offset+1 >= total_len) { + final_offset = 0; /* Force skipping extension */ + } else { + u_int16_t seq_len = ntohs(*((u_int16_t*)&packet->payload[s_offset])); + s_offset += 2; + final_offset = MIN(total_len, s_offset + seq_len); + } + } else { + final_offset = MIN(total_len, s_offset + extension_len); + } + + while(s_offset < final_offset) { + u_int64_t param_type, param_len; + + if(!using_var_int) { + if(s_offset+3 >= final_offset) + break; + param_type = ntohs(*((u_int16_t*)&packet->payload[s_offset])); + param_len = ntohs(*((u_int16_t*)&packet->payload[s_offset + 2])); + s_offset += 4; + } else { + if(s_offset >= final_offset || + (s_offset + quic_len_buffer_still_required(packet->payload[s_offset])) >= final_offset) + break; + s_offset += quic_len(&packet->payload[s_offset], ¶m_type); + + if(s_offset >= final_offset || + (s_offset + quic_len_buffer_still_required(packet->payload[s_offset])) >= final_offset) + break; + s_offset += quic_len(&packet->payload[s_offset], ¶m_len); + } #ifdef DEBUG_TLS - printf("==>> %u [rc: %d][len: %u][%s][version: %u]\n", - flow->guessed_host_protocol_id, rc, packet->payload_packet_len, flow->protos.stun_ssl.ssl.ja3_server, - flow->protos.stun_ssl.ssl.ssl_version); + printf("Client SSL [QUIC TP: Param 0x%x Len %d]\n", (int)param_type, (int)param_len); #endif + if(s_offset+param_len >= final_offset) + break; - if((rc == 0) && (flow->protos.stun_ssl.ssl.ssl_version != 0)) { - flow->guessed_protocol_id = NDPI_PROTOCOL_TLS; + if(param_type==0x3129) { +#ifdef DEBUG_TLS + printf("UA [%.*s]\n", (int)param_len, &packet->payload[s_offset]); +#endif + http_process_user_agent(ndpi_struct, flow, + &packet->payload[s_offset], param_len); + break; + } + s_offset += param_len; + } + } - if(flow->protos.stun_ssl.stun.num_udp_pkts > 0) { - if(ndpi_struct->stun_cache == NULL) - ndpi_struct->stun_cache = ndpi_lru_cache_init(1024); + extension_offset += extension_len; /* Move to the next extension */ - if(ndpi_struct->stun_cache) { #ifdef DEBUG_TLS - printf("[LRU] Adding Signal cached keys\n"); + printf("Client SSL [extension_offset/len: %u/%u]\n", extension_offset, extension_len); #endif - - ndpi_lru_add_to_cache(ndpi_struct->stun_cache, get_stun_lru_key(flow, 0), NDPI_PROTOCOL_SIGNAL); - ndpi_lru_add_to_cache(ndpi_struct->stun_cache, get_stun_lru_key(flow, 1), NDPI_PROTOCOL_SIGNAL); - } - - /* In Signal protocol STUN turns into DTLS... */ - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_SIGNAL); - } else if(flow->protos.stun_ssl.ssl.ja3_server[0] != '\0') { - /* Wait the server certificate the bless this flow as TLS */ - ndpi_int_tls_add_connection(ndpi_struct, flow, NDPI_PROTOCOL_TLS); - } - } + } /* while */ - return; - } + if(!invalid_ja3) { + int rc; - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS) { - if(flow->l4.tcp.tls_stage == 3 && packet->payload_packet_len > 20 && flow->packet_counter < 5) { - /* this should only happen, when we detected SSL with a packet that had parts of the certificate in subsequent packets - * so go on checking for certificate patterns for a couple more packets - */ - NDPI_LOG_DBG2(ndpi_struct, - "ssl flow but check another packet for patterns\n"); - tls_mark_and_payload_search(ndpi_struct, flow, skip_cert_processing); - - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS) { - /* still ssl so check another packet */ - return; - } else { - /* protocol has changed so we are done */ - return; - } - } + compute_ja3c: + ja3_str_len = snprintf(ja3_str, sizeof(ja3_str), "%u,", ja3.tls_handshake_version); - return; - } + for(i=0; i 0) ? "-" : "", ja3.cipher[i]); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; else break; + } - NDPI_LOG_DBG(ndpi_struct, "search ssl\n"); + rc = snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; - /* Check if this is whatsapp first (this proto runs over port 443) */ - if((packet->payload_packet_len > 5) - && ((packet->payload[0] == 'W') - && (packet->payload[1] == 'A') - && (packet->payload[4] == 0) - && (packet->payload[2] <= 9) - && (packet->payload[3] <= 9))) { - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_UNKNOWN); - return; - } else if((packet->payload_packet_len == 4) - && (packet->payload[0] == 'W') - && (packet->payload[1] == 'A')) { - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WHATSAPP, NDPI_PROTOCOL_UNKNOWN); - return; - } else { - /* No whatsapp, let's try SSL */ - if(tlsDetectProtocolFromCertificate(ndpi_struct, flow, skip_cert_processing) > 0) - return; - else - skip_cert_processing = 1; - } + /* ********** */ - if(packet->payload_packet_len > 40 && flow->l4.tcp.tls_stage == 0) { - NDPI_LOG_DBG2(ndpi_struct, "first ssl packet\n"); - // SSLv2 Record - if(packet->payload[2] == 0x01 && packet->payload[3] == 0x03 - && (packet->payload[4] == 0x00 || packet->payload[4] == 0x01 || packet->payload[4] == 0x02) - && (packet->payload_packet_len - packet->payload[1] == 2)) { - NDPI_LOG_DBG2(ndpi_struct, "sslv2 len match\n"); - flow->l4.tcp.tls_stage = 1 + packet->packet_direction; - return; - } + for(i=0; i 0) ? "-" : "", ja3.tls_extension[i]); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; else break; + } - if(packet->payload[0] == 0x16 && packet->payload[1] == 0x03 - && (packet->payload[2] == 0x00 || packet->payload[2] == 0x01 || packet->payload[2] == 0x02) - && (packet->payload_packet_len - ntohs(get_u_int16_t(packet->payload, 3)) == 5)) { - // SSLv3 Record - NDPI_LOG_DBG2(ndpi_struct, "sslv3 len match\n"); - flow->l4.tcp.tls_stage = 1 + packet->packet_direction; - return; - } + rc = snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; - // Application Data pkt - if(packet->payload[0] == 0x17 && packet->payload[1] == 0x03 - && (packet->payload[2] == 0x00 || packet->payload[2] == 0x01 || - packet->payload[2] == 0x02 || packet->payload[2] == 0x03)) { - if(packet->payload_packet_len - ntohs(get_u_int16_t(packet->payload, 3)) == 5) { - NDPI_LOG_DBG2(ndpi_struct, "TLS len match\n"); - flow->l4.tcp.tls_stage = 1 + packet->packet_direction; - return; - } - } - } + /* ********** */ - if(packet->payload_packet_len > 40 && - flow->l4.tcp.tls_stage == 1 + packet->packet_direction - && flow->packet_direction_counter[packet->packet_direction] < 5) { - return; - } + for(i=0; i 0) ? "-" : "", ja3.elliptic_curve[i]); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; else break; + } - if(packet->payload_packet_len > 40 && flow->l4.tcp.tls_stage == 2 - packet->packet_direction) { - NDPI_LOG_DBG2(ndpi_struct, "second ssl packet\n"); - // SSLv2 Record - if(packet->payload[2] == 0x01 && packet->payload[3] == 0x03 - && (packet->payload[4] == 0x00 || packet->payload[4] == 0x01 || packet->payload[4] == 0x02) - && (packet->payload_packet_len - 2) >= packet->payload[1]) { - NDPI_LOG_DBG2(ndpi_struct, "sslv2 server len match\n"); - tls_mark_and_payload_search(ndpi_struct, flow, skip_cert_processing); - return; - } + rc = snprintf(&ja3_str[ja3_str_len], sizeof(ja3_str)-ja3_str_len, ","); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; - ret = ndpi_search_tlsv3_direction1(ndpi_struct, flow); - if(ret == 1) { - NDPI_LOG_DBG2(ndpi_struct, "sslv3 server len match\n"); - tls_mark_and_payload_search(ndpi_struct, flow, skip_cert_processing); - return; - } else if(ret == 2) { - NDPI_LOG_DBG2(ndpi_struct, - "sslv3 server len match with split packet -> check some more packets for SSL patterns\n"); - tls_mark_and_payload_search(ndpi_struct, flow, skip_cert_processing); - - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS) - flow->l4.tcp.tls_stage = 3; - return; - } + for(i=0; i 0) ? "-" : "", ja3.elliptic_curve_point_format[i]); + if(rc > 0 && ja3_str_len + rc < JA3_STR_LEN) ja3_str_len += rc; else break; + } - if(packet->payload_packet_len > 40 && flow->packet_direction_counter[packet->packet_direction] < 5) { - NDPI_LOG_DBG2(ndpi_struct, "need next packet\n"); - return; +#ifdef DEBUG_TLS + printf("[JA3] Client: %s \n", ja3_str); +#endif + + ndpi_MD5Init(&ctx); + ndpi_MD5Update(&ctx, (const unsigned char *)ja3_str, strlen(ja3_str)); + ndpi_MD5Final(md5_hash, &ctx); + + for(i=0, j=0; i<16; i++) { + rc = snprintf(&flow->protos.stun_ssl.ssl.ja3_client[j], + sizeof(flow->protos.stun_ssl.ssl.ja3_client)-j, "%02x", + md5_hash[i]); + if(rc > 0) j += rc; else break; + } +#ifdef DEBUG_TLS + printf("[JA3] Client: %s \n", flow->protos.stun_ssl.ssl.ja3_client); +#endif + } + + /* Before returning to the caller we need to make a final check */ + if((flow->protos.stun_ssl.ssl.ssl_version >= 0x0303) /* >= TLSv1.2 */ + && (flow->protos.stun_ssl.ssl.alpn == NULL) /* No ALPN */) { + NDPI_SET_BIT(flow->risk, NDPI_TLS_NOT_CARRYING_HTTPS); + } + + /* Suspicious Domain Fronting: + https://github.com/SixGenInc/Noctilucent/blob/master/docs/ */ + if(flow->protos.stun_ssl.ssl.encrypted_sni.esni && + flow->protos.stun_ssl.ssl.client_requested_server_name[0] != '\0') { + NDPI_SET_BIT(flow->risk, NDPI_TLS_SUSPICIOUS_ESNI_USAGE); + } + + /* Add check for missing SNI */ + if((flow->protos.stun_ssl.ssl.client_requested_server_name[0] == 0) + && (flow->protos.stun_ssl.ssl.ssl_version >= 0x0302) /* TLSv1.1 */) { + /* This is a bit suspicious */ + NDPI_SET_BIT(flow->risk, NDPI_TLS_MISSING_SNI); + } + + return(2 /* Client Certificate */); + } else { +#ifdef DEBUG_TLS + printf("[TLS] Client: too short [%u vs %u]\n", + (extensions_len+offset), total_len); +#endif + } + } else if(offset == total_len) { + /* SSL does not have extensions etc */ + goto compute_ja3c; + } + } else { +#ifdef DEBUG_TLS + printf("[JA3] Client: invalid length detected\n"); +#endif + } } } - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return(0); /* Not found */ +} + +/* **************************************** */ + +static void ndpi_search_tls_wrapper(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + +#ifdef DEBUG_TLS + printf("==>> %s() %u [len: %u][version: %u]\n", + __FUNCTION__, + flow->guessed_host_protocol_id, + packet->payload_packet_len, + flow->protos.stun_ssl.ssl.ssl_version); +#endif - return; + if(packet->udp != NULL) + ndpi_search_tls_udp(ndpi_struct, flow); + else + ndpi_search_tls_tcp(ndpi_struct, flow); } /* **************************************** */ @@ -1519,8 +1569,19 @@ void init_tls_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) { ndpi_set_bitmask_protocol_detection("TLS", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_TLS, - ndpi_search_tls_tcp_udp, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD, + ndpi_search_tls_wrapper, + NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); + + *id += 1; + + /* *************************************************** */ + + ndpi_set_bitmask_protocol_detection("TLS", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_TLS, + ndpi_search_tls_wrapper, + NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_UDP_WITH_PAYLOAD, SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); diff --git a/src/lib/protocols/tor.c b/src/lib/protocols/tor.c index 9dd1404..71172e2 100644 --- a/src/lib/protocols/tor.c +++ b/src/lib/protocols/tor.c @@ -48,45 +48,16 @@ int ndpi_is_tls_tor(struct ndpi_detection_module_struct *ndpi_struct, if((dot = strrchr(dummy, '.')) == NULL) return(0); name = &dot[1]; - len = strlen(name); - - if(len >= 5) { - int i, prev_num = 0, numbers_found = 0, num_found = 0, num_impossible = 0; - - for(i = 0; name[i+1] != '\0'; i++) { - // printf("***** [SSL] %s(): [%d][%c]", __FUNCTION__, i, name[i]); - - if((name[i] >= '0') && (name[i] <= '9')) { - if(prev_num != 1) { - numbers_found++; - - if(numbers_found == 2) { - ndpi_int_tor_add_connection(ndpi_struct, flow); - return(1); - } - prev_num = 1; - } - } else - prev_num = 0; - - if(ndpi_match_bigram(ndpi_struct, &ndpi_struct->bigrams_automa, &name[i])) { - num_found++; - } else if(ndpi_match_bigram(ndpi_struct, &ndpi_struct->impossible_bigrams_automa, &name[i])) { - num_impossible++; - } - } - - if((num_found == 0) || (num_impossible > 1)) { + if(ndpi_check_dga_name(ndpi_struct, flow, name, 1)) { + ndpi_int_tor_add_connection(ndpi_struct, flow); + return(1); + } else { +#ifdef PEDANTIC_TOR_CHECK + if(gethostbyname(certificate) == NULL) { ndpi_int_tor_add_connection(ndpi_struct, flow); return(1); - } else { -#ifdef PEDANTIC_TOR_CHECK - if(gethostbyname(certificate) == NULL) { - ndpi_int_tor_add_connection(ndpi_struct, flow); - return(1); - } -#endif } +#endif } return(0); diff --git a/src/lib/protocols/tvants.c b/src/lib/protocols/tvants.c deleted file mode 100644 index 2c31974..0000000 --- a/src/lib/protocols/tvants.c +++ /dev/null @@ -1,85 +0,0 @@ -/* - * tvants.c - * - * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ - -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_TVANTS - -#include "ndpi_api.h" - -static void ndpi_int_tvants_add_connection(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_TVANTS, NDPI_PROTOCOL_UNKNOWN); -} - - - - -void ndpi_search_tvants_udp(struct ndpi_detection_module_struct - *ndpi_struct, struct ndpi_flow_struct *flow) -{ - struct ndpi_packet_struct *packet = &flow->packet; - - NDPI_LOG_DBG(ndpi_struct, "search tvants. \n"); - - if (packet->udp != NULL && packet->payload_packet_len > 57 - && packet->payload[0] == 0x04 && packet->payload[1] == 0x00 - && (packet->payload[2] == 0x05 || packet->payload[2] == 0x06 - || packet->payload[2] == 0x07) && packet->payload[3] == 0x00 - && packet->payload_packet_len == (packet->payload[5] << 8) + packet->payload[4] - && packet->payload[6] == 0x00 && packet->payload[7] == 0x00 - && (memcmp(&packet->payload[48], "TVANTS", 6) == 0 - || memcmp(&packet->payload[49], "TVANTS", 6) == 0 || memcmp(&packet->payload[51], "TVANTS", 6) == 0)) { - - NDPI_LOG_INFO(ndpi_struct, "found tvants over udp. \n"); - ndpi_int_tvants_add_connection(ndpi_struct, flow); - - } else if (packet->tcp != NULL && packet->payload_packet_len > 15 - && packet->payload[0] == 0x04 && packet->payload[1] == 0x00 - && packet->payload[2] == 0x07 && packet->payload[3] == 0x00 - && packet->payload_packet_len == (packet->payload[5] << 8) + packet->payload[4] - && packet->payload[6] == 0x00 && packet->payload[7] == 0x00 - && memcmp(&packet->payload[8], "TVANTS", 6) == 0) { - - NDPI_LOG_INFO(ndpi_struct, "found tvants over tcp. \n"); - ndpi_int_tvants_add_connection(ndpi_struct, flow); - - } - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - -} - - -void init_tvants_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - ndpi_set_bitmask_protocol_detection("Tvants", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_TVANTS, - ndpi_search_tvants_udp, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP_WITH_PAYLOAD, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} diff --git a/src/lib/protocols/tvuplayer.c b/src/lib/protocols/tvuplayer.c index b71eb17..6989b1f 100644 --- a/src/lib/protocols/tvuplayer.c +++ b/src/lib/protocols/tvuplayer.c @@ -2,7 +2,7 @@ * tvuplayer.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/ubntac2.c b/src/lib/protocols/ubntac2.c index 6fc0042..49a63ed 100644 --- a/src/lib/protocols/ubntac2.c +++ b/src/lib/protocols/ubntac2.c @@ -64,11 +64,9 @@ void ndpi_search_ubntac2(struct ndpi_detection_module_struct *ndpi_struct, struc version[j] = '\0'; - if(!ndpi_struct->disable_metadata_export) { - len = ndpi_min(sizeof(flow->protos.ubntac2.version)-1, j); - strncpy(flow->protos.ubntac2.version, (const char *)version, len); - flow->protos.ubntac2.version[len] = '\0'; - } + len = ndpi_min(sizeof(flow->protos.ubntac2.version)-1, j); + strncpy(flow->protos.ubntac2.version, (const char *)version, len); + flow->protos.ubntac2.version[len] = '\0'; } NDPI_LOG_INFO(ndpi_struct, "UBNT AirControl 2 request\n"); diff --git a/src/lib/protocols/usenet.c b/src/lib/protocols/usenet.c index a69c34a..3137b2a 100644 --- a/src/lib/protocols/usenet.c +++ b/src/lib/protocols/usenet.c @@ -2,7 +2,7 @@ * usenet.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/vhua.c b/src/lib/protocols/vhua.c index e7ede09..03caa13 100644 --- a/src/lib/protocols/vhua.c +++ b/src/lib/protocols/vhua.c @@ -1,7 +1,7 @@ /* * vhua.c * - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * nDPI is free software: you can vhuatribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/vmware.c b/src/lib/protocols/vmware.c index 312265e..79abeab 100644 --- a/src/lib/protocols/vmware.c +++ b/src/lib/protocols/vmware.c @@ -1,7 +1,7 @@ /* * vmware.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/protocols/vnc.c b/src/lib/protocols/vnc.c index e8a3811..c91c297 100644 --- a/src/lib/protocols/vnc.c +++ b/src/lib/protocols/vnc.c @@ -1,7 +1,7 @@ /* * vnc.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/warcraft3.c b/src/lib/protocols/warcraft3.c index 5c46999..f32bb60 100644 --- a/src/lib/protocols/warcraft3.c +++ b/src/lib/protocols/warcraft3.c @@ -2,7 +2,7 @@ * warcraft3.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/websocket.c b/src/lib/protocols/websocket.c new file mode 100644 index 0000000..fe4603a --- /dev/null +++ b/src/lib/protocols/websocket.c @@ -0,0 +1,131 @@ +/* + * websocket.c + * + * Copyright (C) 2018 by Leonn Paiva + * + * This file is part of nDPI, an open source deep packet inspection + * library based on the OpenDPI and PACE technology by ipoque GmbH + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + */ + +#include "ndpi_protocol_ids.h" + +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_WEBSOCKET + +#include "ndpi_api.h" + +enum websocket_opcode +{ + /* + * CONTINUATION_FRAME is not relevant for the detection and leads to many false positives + CONTINUATION_FRAME = 0x00, + FIN_CONTINUATION_FRAME = 0x80, + */ + TEXT_FRAME = 0x01, + FIN_TEXT_FRAME = 0x81, + BINARY_FRAME = 0x02, + FIN_BINARY_FRAME = 0x82, + CONNECTION_CLOSE_FRAME = 0x08, + FIN_CONNECTION_CLOSE_FRAME = 0x88, + PING_FRAME = 0x09, + FIN_PING_FRAME = 0x89, + PONG_FRAME = 0x0A, + FIN_PONG_FRAME = 0x8A +}; + +static void set_websocket_detected(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) +{ + /* If no custom protocol has been detected */ + if (flow->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN) + { + ndpi_search_tcp_or_udp(ndpi_struct, flow); + + ndpi_int_reset_protocol(flow); + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_WEBSOCKET, flow->guessed_host_protocol_id); + } +} + +/*************************************************************************************************/ + +static void ndpi_check_websocket(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) +{ + struct ndpi_packet_struct *packet = &flow->packet; + + if (packet->payload_packet_len < sizeof(u_int16_t)) + { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + + u_int8_t websocket_payload_length = packet->payload[1] & 0x7F; + u_int8_t websocket_masked = packet->payload[1] & 0x80; + + uint8_t hdr_size = (websocket_masked == 1) ? 6 : 2; + + if (packet->payload_packet_len != hdr_size + websocket_payload_length) + { + NDPI_LOG_DBG(ndpi_struct, "Invalid WEBSOCKET payload"); + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + + if (packet->payload[0] == TEXT_FRAME || packet->payload[0] == FIN_TEXT_FRAME || + packet->payload[0] == BINARY_FRAME || packet->payload[0] == FIN_BINARY_FRAME || + packet->payload[0] == CONNECTION_CLOSE_FRAME || packet->payload[0] == FIN_CONNECTION_CLOSE_FRAME || + packet->payload[0] == PING_FRAME || packet->payload[0] == FIN_PING_FRAME || + packet->payload[0] == PONG_FRAME || packet->payload[0] == FIN_PONG_FRAME) { + + set_websocket_detected(ndpi_struct, flow); + + } else { + NDPI_LOG_DBG(ndpi_struct, "Invalid WEBSOCKET payload"); + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } +} + +void ndpi_search_websocket(struct ndpi_detection_module_struct *ndpi_struct, struct ndpi_flow_struct *flow) +{ + struct ndpi_packet_struct *packet = &flow->packet; + + // Break after 6 packets. + if (flow->packet_counter > 10) + { + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); + return; + } + + if (packet->detected_protocol_stack[0] != NDPI_PROTOCOL_UNKNOWN) + { + return; + } + + NDPI_LOG_DBG(ndpi_struct, "search WEBSOCKET\n"); + ndpi_check_websocket(ndpi_struct, flow); + + return; +} + +/* ********************************* */ + +void init_websocket_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, + NDPI_PROTOCOL_BITMASK *detection_bitmask) +{ + ndpi_set_bitmask_protocol_detection("WEBSOCKET", ndpi_struct, detection_bitmask, *id, NDPI_PROTOCOL_WEBSOCKET, + ndpi_search_websocket, NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, ADD_TO_DETECTION_BITMASK); + + *id += 1; +} diff --git a/src/lib/protocols/whoisdas.c b/src/lib/protocols/whoisdas.c index 381acc9..6ccac8c 100644 --- a/src/lib/protocols/whoisdas.c +++ b/src/lib/protocols/whoisdas.c @@ -1,7 +1,7 @@ /* * whoisdas.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * nDPI is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -40,15 +40,13 @@ void ndpi_search_whois_das(struct ndpi_detection_module_struct *ndpi_struct, str u_int max_len = sizeof(flow->host_server_name) - 1; u_int i, j; - if(!ndpi_struct->disable_metadata_export) { - for(i=strlen((const char *)flow->host_server_name), j=0; (ipayload_packet_len); i++, j++) { - if((packet->payload[j] == '\n') || (packet->payload[j] == '\r')) break; - flow->host_server_name[i] = packet->payload[j]; - } - - flow->host_server_name[i] = '\0'; + for(i=strlen((const char *)flow->host_server_name), j=0; (ipayload_packet_len); i++, j++) { + if((packet->payload[j] == '\n') || (packet->payload[j] == '\r')) break; + flow->host_server_name[i] = packet->payload[j]; } + flow->host_server_name[i] = '\0'; + flow->server_id = ((sport == 43) || (sport == 4343)) ? flow->src : flow->dst; NDPI_LOG_INFO(ndpi_struct, "[WHOIS/DAS] %s\n", flow->host_server_name); diff --git a/src/lib/protocols/world_of_kung_fu.c b/src/lib/protocols/world_of_kung_fu.c index b1312d3..6404302 100644 --- a/src/lib/protocols/world_of_kung_fu.c +++ b/src/lib/protocols/world_of_kung_fu.c @@ -2,7 +2,7 @@ * world_of_kung_fu.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/world_of_warcraft.c b/src/lib/protocols/world_of_warcraft.c index 39e641f..383c644 100644 --- a/src/lib/protocols/world_of_warcraft.c +++ b/src/lib/protocols/world_of_warcraft.c @@ -2,7 +2,7 @@ * world_of_warcraft.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/xbox.c b/src/lib/protocols/xbox.c index 7b03d63..4c26e1f 100644 --- a/src/lib/protocols/xbox.c +++ b/src/lib/protocols/xbox.c @@ -1,7 +1,7 @@ /* * xbox.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/xdmcp.c b/src/lib/protocols/xdmcp.c index 753213d..264da2f 100644 --- a/src/lib/protocols/xdmcp.c +++ b/src/lib/protocols/xdmcp.c @@ -2,7 +2,7 @@ * xdmcp.c * * Copyright (C) 2009-2011 by ipoque GmbH - * Copyright (C) 2011-19 - ntop.org + * Copyright (C) 2011-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH diff --git a/src/lib/protocols/yahoo.c b/src/lib/protocols/yahoo.c deleted file mode 100644 index 0852eec..0000000 --- a/src/lib/protocols/yahoo.c +++ /dev/null @@ -1,406 +0,0 @@ -/* - * yahoo.c - * - * Copyright (C) 2016-19 - ntop.org - * - * This file is part of nDPI, an open source deep packet inspection - * library based on the OpenDPI and PACE technology by ipoque GmbH - * - * nDPI is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * nDPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with nDPI. If not, see . - * - */ -#include "ndpi_protocol_ids.h" - -#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_YAHOO - -#include "ndpi_api.h" - -struct ndpi_yahoo_header { - u_int8_t YMSG_str[4]; - u_int16_t version; - u_int16_t nothing0; - u_int16_t len; - u_int16_t service; - u_int32_t status; - u_int32_t session_id; -}; - -/* This function checks the pattern 'packet; - struct ndpi_id_struct *src = flow->src; - struct ndpi_id_struct *dst = flow->dst; - - const struct ndpi_yahoo_header *yahoo = (struct ndpi_yahoo_header *) packet->payload; - - if(packet->payload_packet_len > 0) { - /* packet must be at least 20 bytes long */ - if(packet->payload_packet_len >= 20 - && memcmp(yahoo->YMSG_str, "YMSG", 4) == 0 && ((packet->payload_packet_len - 20) == ntohs(yahoo->len) - || check_ymsg(packet->payload, packet->payload_packet_len))) { - - NDPI_LOG_DBG(ndpi_struct, "YAHOO FOUND\n"); - flow->yahoo_detection_finished = 2; - - if(ntohs(yahoo->service) == 24 || ntohs(yahoo->service) == 152 || ntohs(yahoo->service) == 74) { - NDPI_LOG_DBG(ndpi_struct, "YAHOO conference or chat invite found"); - - if(src != NULL) - src->yahoo_conf_logged_in = 1; - if(dst != NULL) - dst->yahoo_conf_logged_in = 1; - } - if(ntohs(yahoo->service) == 27 || ntohs(yahoo->service) == 155 || ntohs(yahoo->service) == 160) { - NDPI_LOG_DBG(ndpi_struct, "YAHOO conference or chat logoff found"); - if(src != NULL) { - src->yahoo_conf_logged_in = 0; - src->yahoo_voice_conf_logged_in = 0; - } - } - NDPI_LOG_INFO(ndpi_struct, "found YAHOO"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - - } else if(flow->yahoo_detection_finished == 2 && packet->detected_protocol_stack[0] == NDPI_PROTOCOL_YAHOO) { - return; - } else if(packet->payload_packet_len == 4 && memcmp(yahoo->YMSG_str, "YMSG", 4) == 0) { - flow->l4.tcp.yahoo_sip_comm = 1; - return; - } else if(flow->l4.tcp.yahoo_sip_comm && packet->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN - && flow->packet_counter < 3) { - return; - } - - /* now test for http login, at least 100 a bytes packet */ - if(ndpi_struct->yahoo_detect_http_connections != 0 && packet->payload_packet_len > 100) { - if(memcmp(packet->payload, "POST /relay?token=", 18) == 0 - || memcmp(packet->payload, "GET /relay?token=", 17) == 0 - || memcmp(packet->payload, "GET /?token=", 12) == 0 - || memcmp(packet->payload, "HEAD /relay?token=", 18) == 0) { - if((src != NULL - && NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_YAHOO) - != 0) || (dst != NULL - && NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_YAHOO) - != 0)) { - /* this is mostly a file transfer */ - NDPI_LOG_INFO(ndpi_struct, "found YAHOO"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - } - if(memcmp(packet->payload, "POST ", 5) == 0) { - u_int16_t a; - ndpi_parse_packet_line_info(ndpi_struct, flow); - - if ((packet->user_agent_line.len >= 21) - && (memcmp(packet->user_agent_line.ptr, "YahooMobileMessenger/", 21) == 0)) { - NDPI_LOG_INFO(ndpi_struct, "found YAHOO(Mobile)"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - - if (NDPI_SRC_OR_DST_HAS_PROTOCOL(src, dst, NDPI_PROTOCOL_YAHOO) - && packet->parsed_lines > 5 - && memcmp(&packet->payload[5], "/Messenger.", 11) == 0 - && packet->line[1].len >= 17 - && memcmp(packet->line[1].ptr, "Connection: Close", - 17) == 0 && packet->line[2].len >= 6 - && memcmp(packet->line[2].ptr, "Host: ", 6) == 0 - && packet->line[3].len >= 16 - && memcmp(packet->line[3].ptr, "Content-Length: ", - 16) == 0 && packet->line[4].len >= 23 - && memcmp(packet->line[4].ptr, "User-Agent: Mozilla/5.0", - 23) == 0 && packet->line[5].len >= 23 - && memcmp(packet->line[5].ptr, "Cache-Control: no-cache", 23) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found YAHOO HTTP POST P2P FILETRANSFER\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - - if (packet->host_line.ptr != NULL && packet->host_line.len >= 26 && - memcmp(packet->host_line.ptr, "filetransfer.msg.yahoo.com", 26) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found YAHOO HTTP POST FILETRANSFER\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - /* now check every line */ - for (a = 0; a < packet->parsed_lines; a++) { - if (packet->line[a].len >= 4 && memcmp(packet->line[a].ptr, "YMSG", 4) == 0) { - NDPI_LOG_DBG(ndpi_struct, - "YAHOO HTTP POST FOUND, line is: %.*s\n", packet->line[a].len, packet->line[a].ptr); - NDPI_LOG_INFO(ndpi_struct, "found YAHOO"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - } - if (packet->parsed_lines > 8 && packet->line[8].len > 250 && packet->line[8].ptr != NULL) { - if (memcmp(packet->line[8].ptr, "line[8].len, packet->line[8].ptr)) { - NDPI_LOG_INFO(ndpi_struct, - "found YAHOO HTTP Proxy Yahoo Chat payload, "GET /Messenger.", 15) == 0) { - if((src != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_YAHOO) != 0) - || (dst != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_YAHOO) != 0)) { - - NDPI_LOG_INFO(ndpi_struct, "found YAHOO HTTP GET /Messenger. match\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - } - - if((memcmp(packet->payload, "GET /", 5) == 0)) { - ndpi_parse_packet_line_info(ndpi_struct, flow); - if((packet->user_agent_line.ptr != NULL && packet->user_agent_line.len >= NDPI_STATICSTRING_LEN("YahooMobileMessenger/") - && memcmp(packet->user_agent_line.ptr, "YahooMobileMessenger/", NDPI_STATICSTRING_LEN("YahooMobileMessenger/")) == 0) - || (packet->user_agent_line.len >= 15 && (memcmp(packet->user_agent_line.ptr, "Y!%20Messenger/", 15) == 0))) { - - NDPI_LOG_INFO(ndpi_struct, "found YAHOO(Mobile)"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - if(packet->host_line.ptr != NULL && packet->host_line.len >= NDPI_STATICSTRING_LEN("msg.yahoo.com") && - memcmp(&packet->host_line.ptr[packet->host_line.len - NDPI_STATICSTRING_LEN("msg.yahoo.com")], "msg.yahoo.com", NDPI_STATICSTRING_LEN("msg.yahoo.com")) == 0) { - NDPI_LOG_INFO(ndpi_struct, "found YAHOO"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - } - } - /* found another http login command for yahoo, it is like OSCAR */ - /* detect http connections */ - if (packet->payload_packet_len > 50 && (memcmp(packet->payload, "content-length: ", 16) == 0)) { - - ndpi_parse_packet_line_info(ndpi_struct, flow); - - if (packet->parsed_lines > 2 && packet->line[1].len == 0) { - - NDPI_LOG_DBG(ndpi_struct, "first line is empty\n"); - if (packet->line[2].len > 13 && memcmp(packet->line[2].ptr, "payload_packet_len > 38 && memcmp(packet->payload, "CONNECT scs.msg.yahoo.com:5050 HTTP/1.", 38) == 0) { - - NDPI_LOG_INFO(ndpi_struct, "found YAHOO-HTTP\n"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - - if ((src != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(src->detected_protocol_bitmask, NDPI_PROTOCOL_YAHOO) != 0) - || (dst != NULL && NDPI_COMPARE_PROTOCOL_TO_BITMASK(dst->detected_protocol_bitmask, NDPI_PROTOCOL_YAHOO) != 0)) { - if (packet->payload_packet_len == 6 && memcmp(packet->payload, "YAHOO!", 6) == 0) { - - NDPI_LOG_INFO(ndpi_struct, "found YAHOO"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - /* asymmetric detection for SNDIMG not done yet. - * See ./Yahoo8.1-VideoCall-LAN.pcap and ./Yahoo-VideoCall-inPublicIP.pcap */ - - if (packet->payload_packet_len == 8 && (memcmp(packet->payload, "", 8) == 0 || memcmp(packet->payload, "", 8) == 0 - || memcmp(packet->payload, "", 8) == 0 || memcmp(packet->payload, "", 8) == 0)) { - - if(src != NULL) { - if (memcmp(packet->payload, "", 8) == 0) { - src->yahoo_video_lan_dir = 0; - } else { - src->yahoo_video_lan_dir = 1; - } - src->yahoo_video_lan_timer = packet->tick_timestamp; - } - if(dst != NULL) { - if (memcmp(packet->payload, "", 8) == 0) { - dst->yahoo_video_lan_dir = 0; - } else { - dst->yahoo_video_lan_dir = 1; - } - dst->yahoo_video_lan_timer = packet->tick_timestamp; - - } - NDPI_LOG_INFO(ndpi_struct, "found YAHOO subtype VIDEO"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - if(src != NULL && packet->tcp->dest == htons(5100) - && ((u_int32_t) (packet->tick_timestamp - src->yahoo_video_lan_timer) < ndpi_struct->yahoo_lan_video_timeout)) { - - if (src->yahoo_video_lan_dir == 1) { - - NDPI_LOG_INFO(ndpi_struct, "found YAHOO IMG MARKED"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - } - if (dst != NULL && packet->tcp->dest == htons(5100) - && ((u_int32_t) (packet->tick_timestamp - dst->yahoo_video_lan_timer) < ndpi_struct->yahoo_lan_video_timeout)) { - if (dst->yahoo_video_lan_dir == 0) { - - NDPI_LOG_INFO(ndpi_struct, "found YAHOO IMG MARKED"); - ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_YAHOO, NDPI_PROTOCOL_UNKNOWN); - return; - } - } - } - /* detect YAHOO over HTTP proxy */ - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP) - { - if (flow->l4.tcp.yahoo_http_proxy_stage == 0) { - - NDPI_LOG_DBG2(ndpi_struct, "YAHOO maybe HTTP proxy packet 1 => need next packet\n"); - flow->l4.tcp.yahoo_http_proxy_stage = 1 + packet->packet_direction; - return; - } - if (flow->l4.tcp.yahoo_http_proxy_stage == 1 + packet->packet_direction) { - if ((packet->payload_packet_len > 250) && (memcmp(packet->payload, "payload_packet_len, packet->payload)) { - - NDPI_LOG_INFO(ndpi_struct, "found HTTP Proxy Yahoo Chat need next packet\n"); - return; - } - if (flow->l4.tcp.yahoo_http_proxy_stage == 2 - packet->packet_direction) { - - ndpi_parse_packet_line_info_any(ndpi_struct, flow); - - if (packet->parsed_lines >= 9) { - - if (packet->line[4].ptr != NULL && packet->line[4].len >= 9 && - packet->line[8].ptr != NULL && packet->line[8].len >= 6 && - memcmp(packet->line[4].ptr, "line[8].ptr, "packet; - - NDPI_LOG_DBG(ndpi_struct, "search yahoo\n"); - - if(packet->payload_packet_len > 0 && flow->yahoo_detection_finished == 0) { - - /* search over TCP */ - if(packet->tcp != NULL && packet->tcp_retransmission == 0) { - - if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_UNKNOWN - || packet->detected_protocol_stack[0] == NDPI_PROTOCOL_HTTP - || packet->detected_protocol_stack[0] == NDPI_PROTOCOL_TLS) { - /* search over TCP */ - ndpi_search_yahoo_tcp(ndpi_struct, flow); - } - } - /* search over UDP */ - else if(packet->udp != NULL) { - if ( flow->src == NULL || - NDPI_COMPARE_PROTOCOL_TO_BITMASK(flow->src->detected_protocol_bitmask, NDPI_PROTOCOL_YAHOO) == 0) { - NDPI_EXCLUDE_PROTO(ndpi_struct, flow); - } - return; - } - } - - if(packet->payload_packet_len > 0 && flow->yahoo_detection_finished == 2) { - if(packet->tcp != NULL && packet->tcp_retransmission == 0) { - /* search over TCP */ - ndpi_search_yahoo_tcp(ndpi_struct, flow); - return; - } - } -} - -void init_yahoo_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, NDPI_PROTOCOL_BITMASK *detection_bitmask) -{ - - ndpi_set_bitmask_protocol_detection("YAHOO", ndpi_struct, detection_bitmask, *id, - NDPI_PROTOCOL_YAHOO, - ndpi_search_yahoo, - NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_OR_UDP, - SAVE_DETECTION_BITMASK_AS_UNKNOWN, - ADD_TO_DETECTION_BITMASK); - - *id += 1; -} - diff --git a/src/lib/protocols/zabbix.c b/src/lib/protocols/zabbix.c new file mode 100644 index 0000000..9e67a31 --- /dev/null +++ b/src/lib/protocols/zabbix.c @@ -0,0 +1,63 @@ +/* + * zabbix.c + * + * Copyright (C) 2019 - ntop.org + * + * nDPI is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nDPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with nDPI. If not, see . + * + */ + +#include "ndpi_protocol_ids.h" + +#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_ZABBIX + +#include "ndpi_api.h" + +/* *************************************************** */ + +static void ndpi_int_zabbix_add_connection(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow/* , */ + /* ndpi_protocol_type_t protocol_type */) { + ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ZABBIX, NDPI_PROTOCOL_UNKNOWN); +} + +/* *************************************************** */ + +void ndpi_search_zabbix(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow) { + struct ndpi_packet_struct *packet = &flow->packet; + u_int8_t tomatch[] = { 'Z', 'B', 'X', 'D', 0x1 }; + + NDPI_LOG_DBG(ndpi_struct, "search Zabbix\n"); + + if((packet->payload_packet_len > 4) + && (memcmp(packet->payload, tomatch, 5) == 0)) + ndpi_int_zabbix_add_connection(ndpi_struct, flow); + else + NDPI_EXCLUDE_PROTO(ndpi_struct, flow); +} + +/* *************************************************** */ + +void init_zabbix_dissector(struct ndpi_detection_module_struct *ndpi_struct, u_int32_t *id, + NDPI_PROTOCOL_BITMASK *detection_bitmask) { + ndpi_set_bitmask_protocol_detection("Zabbix", ndpi_struct, detection_bitmask, *id, + NDPI_PROTOCOL_ZABBIX, + ndpi_search_zabbix, + NDPI_SELECTION_BITMASK_PROTOCOL_V4_V6_TCP_WITH_PAYLOAD_WITHOUT_RETRANSMISSION, + SAVE_DETECTION_BITMASK_AS_UNKNOWN, + ADD_TO_DETECTION_BITMASK); + + *id += 1; +} diff --git a/src/lib/protocols/zattoo.c b/src/lib/protocols/zattoo.c index 4f2d115..b43dd76 100644 --- a/src/lib/protocols/zattoo.c +++ b/src/lib/protocols/zattoo.c @@ -1,7 +1,7 @@ /* * zattoo.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * This file is part of nDPI, an open source deep packet inspection * library based on the OpenDPI and PACE technology by ipoque GmbH @@ -46,9 +46,9 @@ u_int8_t ndpi_int_zattoo_user_agent_set(struct ndpi_detection_module_struct *ndp #define ZATTOO_DETECTED \ if (src != NULL) \ - src->zattoo_ts = packet->tick_timestamp; \ + src->zattoo_ts = packet->current_time_ms; \ if (dst != NULL) \ - dst->zattoo_ts = packet->tick_timestamp; \ + dst->zattoo_ts = packet->current_time_ms; \ \ ndpi_set_detected_protocol(ndpi_struct, flow, NDPI_PROTOCOL_ZATTOO, NDPI_PROTOCOL_UNKNOWN) @@ -63,10 +63,10 @@ void ndpi_search_zattoo(struct ndpi_detection_module_struct *ndpi_struct, struct NDPI_LOG_DBG(ndpi_struct, "search ZATTOO\n"); if(packet->detected_protocol_stack[0] == NDPI_PROTOCOL_ZATTOO) { - if(src != NULL && ((u_int32_t) (packet->tick_timestamp - src->zattoo_ts) < ndpi_struct->zattoo_connection_timeout)) - src->zattoo_ts = packet->tick_timestamp; - if (dst != NULL && ((u_int32_t) (packet->tick_timestamp - dst->zattoo_ts) < ndpi_struct->zattoo_connection_timeout)) - dst->zattoo_ts = packet->tick_timestamp; + if(src != NULL && ((u_int32_t) (packet->current_time_ms - src->zattoo_ts) < ndpi_struct->zattoo_connection_timeout)) + src->zattoo_ts = packet->current_time_ms; + if (dst != NULL && ((u_int32_t) (packet->current_time_ms - dst->zattoo_ts) < ndpi_struct->zattoo_connection_timeout)) + dst->zattoo_ts = packet->current_time_ms; return; } /* search over TCP */ @@ -164,12 +164,15 @@ void ndpi_search_zattoo(struct ndpi_detection_module_struct *ndpi_struct, struct NDPI_LOG_DBG2(ndpi_struct, "need next packet, seen pattern 0x0000\n"); return; } +#if 0 if(packet->payload_packet_len > 50 && packet->payload[0] == 0x03 && packet->payload[1] == 0x04 && packet->payload[2] == 0x00 && packet->payload[3] == 0x04 && packet->payload[4] == 0x0a && packet->payload[5] == 0x00) { } +#endif + NDPI_LOG_DBG2(ndpi_struct, "need next packet, seen pattern 0x030400040a00\n"); return; diff --git a/src/lib/protocols/zeromq.c b/src/lib/protocols/zeromq.c index 8d30bc9..1debd18 100644 --- a/src/lib/protocols/zeromq.c +++ b/src/lib/protocols/zeromq.c @@ -1,7 +1,7 @@ /* * zmq.c * - * Copyright (C) 2016-19 - ntop.org + * Copyright (C) 2016-20 - ntop.org * * nDPI is free software: you can zmqtribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by diff --git a/src/lib/third_party/include/MurmurHash3.h b/src/lib/third_party/include/MurmurHash3.h new file mode 100644 index 0000000..a048eb3 --- /dev/null +++ b/src/lib/third_party/include/MurmurHash3.h @@ -0,0 +1,8 @@ +#ifndef _MURMURHASH3_H_ +#define _MURMURHASH3_H_ + +#include + +uint32_t MurmurHash3_x86_32(const void * key, uint32_t len, uint32_t seed); + +#endif diff --git a/src/lib/third_party/include/actypes.h b/src/lib/third_party/include/actypes.h index 2308cd6..b775f6a 100644 --- a/src/lib/third_party/include/actypes.h +++ b/src/lib/third_party/include/actypes.h @@ -43,8 +43,8 @@ typedef char AC_ALPHABET_t; * union for this purpose. you can add your desired type in it. **/ typedef struct { - int number; - unsigned int category, breed; + u_int32_t number; /* Often used to store procotolId */ + u_int16_t category, breed; } AC_REP_t; /* AC_PATTERN_t: diff --git a/src/lib/third_party/include/ahocorasick.h b/src/lib/third_party/include/ahocorasick.h index 74812be..943be88 100644 --- a/src/lib/third_party/include/ahocorasick.h +++ b/src/lib/third_party/include/ahocorasick.h @@ -44,6 +44,12 @@ typedef struct * add pattern to automata anymore. */ unsigned short automata_open; + /* Statistic Variables */ + unsigned long total_patterns; /* Total patterns in the automata */ + +} AC_AUTOMATA_t; + +typedef struct { /* It is possible to feed a large input to the automata chunk by chunk to * be searched using ac_automata_search(). in fact by default automata * thinks that all chunks are related unless you do ac_automata_reset(). @@ -51,18 +57,13 @@ typedef struct AC_NODE_t * current_node; /* Pointer to current node while searching */ unsigned long base_position; /* Represents the position of current chunk related to whole input text */ - - /* Statistic Variables */ - unsigned long total_patterns; /* Total patterns in the automata */ - -} AC_AUTOMATA_t; +} AC_SEARCH_t; AC_AUTOMATA_t * ac_automata_init (MATCH_CALLBACK_f mc); AC_ERROR_t ac_automata_add (AC_AUTOMATA_t * thiz, AC_PATTERN_t * str); void ac_automata_finalize (AC_AUTOMATA_t * thiz); int ac_automata_search (AC_AUTOMATA_t * thiz, AC_TEXT_t * str, AC_REP_t * param); -void ac_automata_reset (AC_AUTOMATA_t * thiz); void ac_automata_release (AC_AUTOMATA_t * thiz, u_int8_t free_pattern); void ac_automata_display (AC_AUTOMATA_t * thiz, char repcast); diff --git a/src/lib/third_party/include/hll.h b/src/lib/third_party/include/hll.h new file mode 100644 index 0000000..00975ec --- /dev/null +++ b/src/lib/third_party/include/hll.h @@ -0,0 +1,27 @@ +/* + Code taken from https://github.com/avz/hll + + Copyright (c) 2015 Artem Zaytsev + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + */ + + +extern int hll_init(struct ndpi_hll *hll, u_int8_t bits); +extern void hll_destroy(struct ndpi_hll *hll); +extern void hll_add(struct ndpi_hll *hll, const void *buf, size_t size); +extern double hll_count(const struct ndpi_hll *hll); diff --git a/src/lib/third_party/include/libinjection.h b/src/lib/third_party/include/libinjection.h new file mode 100644 index 0000000..6b40b1d --- /dev/null +++ b/src/lib/third_party/include/libinjection.h @@ -0,0 +1,65 @@ +/** + * Copyright 2012-2016 Nick Galbreath + * nickg@client9.com + * BSD License -- see COPYING.txt for details + * + * https://libinjection.client9.com/ + * + */ + +#ifndef LIBINJECTION_H +#define LIBINJECTION_H + +#ifdef __cplusplus +# define LIBINJECTION_BEGIN_DECLS extern "C" { +# define LIBINJECTION_END_DECLS } +#else +# define LIBINJECTION_BEGIN_DECLS +# define LIBINJECTION_END_DECLS +#endif + +LIBINJECTION_BEGIN_DECLS + +/* + * Pull in size_t + */ +#include + +/* + * Version info. + * + * This is moved into a function to allow SWIG and other auto-generated + * binding to not be modified during minor release changes. We change + * change the version number in the c source file, and not regenerated + * the binding + * + * See python's normalized version + * http://www.python.org/dev/peps/pep-0386/#normalizedversion + */ +const char* libinjection_version(void); + +/** + * Simple API for SQLi detection - returns a SQLi fingerprint or NULL + * is benign input + * + * \param[in] s input string, may contain nulls, does not need to be null-terminated + * \param[in] slen input string length + * \param[out] fingerprint buffer of 8+ characters. c-string, + * \return 1 if SQLi, 0 if benign. fingerprint will be set or set to empty string. + */ +int libinjection_sqli(const char* s, size_t slen, char fingerprint[]); + +/** ALPHA version of xss detector. + * + * NOT DONE. + * + * \param[in] s input string, may contain nulls, does not need to be null-terminated + * \param[in] slen input string length + * \return 1 if XSS found, 0 if benign + * + */ +int libinjection_xss(const char* s, size_t slen); + +LIBINJECTION_END_DECLS + +#endif /* LIBINJECTION_H */ diff --git a/src/lib/third_party/include/libinjection_html5.h b/src/lib/third_party/include/libinjection_html5.h new file mode 100644 index 0000000..29a1459 --- /dev/null +++ b/src/lib/third_party/include/libinjection_html5.h @@ -0,0 +1,54 @@ +#ifndef LIBINJECTION_HTML5 +#define LIBINJECTION_HTML5 + +#ifdef __cplusplus +extern "C" { +#endif + +/* pull in size_t */ + +#include + +enum html5_type { + DATA_TEXT + , TAG_NAME_OPEN + , TAG_NAME_CLOSE + , TAG_NAME_SELFCLOSE + , TAG_DATA + , TAG_CLOSE + , ATTR_NAME + , ATTR_VALUE + , TAG_COMMENT + , DOCTYPE +}; + +enum html5_flags { + DATA_STATE + , VALUE_NO_QUOTE + , VALUE_SINGLE_QUOTE + , VALUE_DOUBLE_QUOTE + , VALUE_BACK_QUOTE +}; + +struct h5_state; +typedef int (*ptr_html5_state)(struct h5_state*); + +typedef struct h5_state { + const char* s; + size_t len; + size_t pos; + int is_close; + ptr_html5_state state; + const char* token_start; + size_t token_len; + enum html5_type token_type; +} h5_state_t; + + +void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags); +int libinjection_h5_next(h5_state_t* hs); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/lib/third_party/include/libinjection_sqli.h b/src/lib/third_party/include/libinjection_sqli.h new file mode 100644 index 0000000..b974655 --- /dev/null +++ b/src/lib/third_party/include/libinjection_sqli.h @@ -0,0 +1,294 @@ +/** + * Copyright 2012-2016 Nick Galbreath + * nickg@client9.com + * BSD License -- see `COPYING.txt` for details + * + * https://libinjection.client9.com/ + * + */ + +#ifndef LIBINJECTION_SQLI_H +#define LIBINJECTION_SQLI_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Pull in size_t + */ +#include + +enum sqli_flags { + FLAG_NONE = 0 + , FLAG_QUOTE_NONE = 1 /* 1 << 0 */ + , FLAG_QUOTE_SINGLE = 2 /* 1 << 1 */ + , FLAG_QUOTE_DOUBLE = 4 /* 1 << 2 */ + + , FLAG_SQL_ANSI = 8 /* 1 << 3 */ + , FLAG_SQL_MYSQL = 16 /* 1 << 4 */ +}; + +enum lookup_type { + LOOKUP_WORD = 1 + , LOOKUP_TYPE = 2 + , LOOKUP_OPERATOR = 3 + , LOOKUP_FINGERPRINT = 4 +}; + +struct libinjection_sqli_token { +#ifdef SWIG +%immutable; +#endif + /* + * position and length of token + * in original string + */ + size_t pos; + size_t len; + + /* count: + * in type 'v', used for number of opening '@' + * but maybe used in other contexts + */ + int count; + + char type; + char str_open; + char str_close; + char val[32]; +}; + +typedef struct libinjection_sqli_token stoken_t; + +/** + * Pointer to function, takes c-string input, + * returns '\0' for no match, else a char + */ +struct libinjection_sqli_state; +typedef char (*ptr_lookup_fn)(struct libinjection_sqli_state*, int lookuptype, const char* word, size_t len); + +struct libinjection_sqli_state { +#ifdef SWIG +%immutable; +#endif + + /* + * input, does not need to be null terminated. + * it is also not modified. + */ + const char *s; + + /* + * input length + */ + size_t slen; + + /* + * How to lookup a word or fingerprint + */ + ptr_lookup_fn lookup; + void* userdata; + + /* + * + */ + int flags; + + /* + * pos is the index in the string during tokenization + */ + size_t pos; + +#ifndef SWIG + /* for SWIG.. don't use this.. use functional API instead */ + + /* MAX TOKENS + 1 since we use one extra token + * to determine the type of the previous token + */ + struct libinjection_sqli_token tokenvec[8]; +#endif + + /* + * Pointer to token position in tokenvec, above + */ + struct libinjection_sqli_token *current; + + /* + * fingerprint pattern c-string + * +1 for ending null + * Minimum of 8 bytes to add gcc's -fstack-protector to work + */ + char fingerprint[8]; + + /* + * Line number of code that said decided if the input was SQLi or + * not. Most of the time it's line that said "it's not a matching + * fingerprint" but there is other logic that sometimes approves + * an input. This is only useful for debugging. + * + */ + int reason; + + /* Number of ddw (dash-dash-white) comments + * These comments are in the form of + * '--[whitespace]' or '--[EOF]' + * + * All databases treat this as a comment. + */ + int stats_comment_ddw; + + /* Number of ddx (dash-dash-[notwhite]) comments + * + * ANSI SQL treats these are comments, MySQL treats this as + * two unary operators '-' '-' + * + * If you are parsing result returns FALSE and + * stats_comment_dd > 0, you should reparse with + * COMMENT_MYSQL + * + */ + int stats_comment_ddx; + + /* + * c-style comments found /x .. x/ + */ + int stats_comment_c; + + /* '#' operators or MySQL EOL comments found + * + */ + int stats_comment_hash; + + /* + * number of tokens folded away + */ + int stats_folds; + + /* + * total tokens processed + */ + int stats_tokens; + +}; + +typedef struct libinjection_sqli_state sfilter; + +struct libinjection_sqli_token* libinjection_sqli_get_token( + struct libinjection_sqli_state* sqlistate, int i); + +/* + * Version info. + * + * This is moved into a function to allow SWIG and other auto-generated + * binding to not be modified during minor release changes. We change + * change the version number in the c source file, and not regenerated + * the binding + * + * See python's normalized version + * http://www.python.org/dev/peps/pep-0386/#normalizedversion + */ +const char* libinjection_version(void); + +/** + * + */ +void libinjection_sqli_init(struct libinjection_sqli_state* sql_state, + const char* s, size_t slen, + int flags); + +/** + * Main API: tests for SQLi in three possible contexts, no quotes, + * single quote and double quote + * + * \param sql_state core data structure + * + * \return 1 (true) if SQLi, 0 (false) if benign + */ +int libinjection_is_sqli(struct libinjection_sqli_state* sql_state); + +/* FOR HACKERS ONLY + * provides deep hooks into the decision making process + */ +void libinjection_sqli_callback(struct libinjection_sqli_state* sql_state, + ptr_lookup_fn fn, + void* userdata); + + +/* + * Resets state, but keeps initial string and callbacks + */ +void libinjection_sqli_reset(struct libinjection_sqli_state* sql_state, + int flags); + +/** + * + */ + +/** + * This detects SQLi in a single context, mostly useful for custom + * logic and debugging. + * + * \param sql_state Main data structure + * \param flags flags to adjust parsing + * + * \returns a pointer to sfilter.fingerprint as convenience + * do not free! + * + */ +const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state* sql_state, + int flags); + +/** + * The default "word" to token-type or fingerprint function. This + * uses a ASCII case-insensitive binary tree. + */ +char libinjection_sqli_lookup_word(struct libinjection_sqli_state* sql_state, + int lookup_type, + const char* s, + size_t slen); + +/* Streaming tokenization interface. + * + * sql_state->current is updated with the current token. + * + * \returns 1, has a token, keep going, or 0 no tokens + * + */ +int libinjection_sqli_tokenize(struct libinjection_sqli_state * sql_state); + +/** + * parses and folds input, up to 5 tokens + * + */ +int libinjection_sqli_fold(struct libinjection_sqli_state * sql_state); + +/** The built-in default function to match fingerprints + * and do false negative/positive analysis. This calls the following + * two functions. With this, you over-ride one part or the other. + * + * return libinjection_sqli_blacklist(sql_state) && + * libinjection_sqli_not_whitelist(sql_state); + * + * \param sql_state should be filled out after libinjection_sqli_fingerprint is called + */ +int libinjection_sqli_check_fingerprint(struct libinjection_sqli_state * sql_state); + +/* Given a pattern determine if it's a SQLi pattern. + * + * \return TRUE if sqli, false otherwise + */ +int libinjection_sqli_blacklist(struct libinjection_sqli_state* sql_state); + +/* Given a positive match for a pattern (i.e. pattern is SQLi), this function + * does additional analysis to reduce false positives. + * + * \return TRUE if SQLi, false otherwise + */ +int libinjection_sqli_not_whitelist(struct libinjection_sqli_state * sql_state); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBINJECTION_SQLI_H */ diff --git a/src/lib/third_party/include/libinjection_sqli_data.h b/src/lib/third_party/include/libinjection_sqli_data.h new file mode 100644 index 0000000..f5e1454 --- /dev/null +++ b/src/lib/third_party/include/libinjection_sqli_data.h @@ -0,0 +1,9652 @@ + +#ifndef LIBINJECTION_SQLI_DATA_H +#define LIBINJECTION_SQLI_DATA_H + +#include "libinjection.h" +#include "libinjection_sqli.h" + +typedef struct { + const char *word; + char type; +} keyword_t; + +static size_t parse_money(sfilter * sf); +static size_t parse_other(sfilter * sf); +static size_t parse_white(sfilter * sf); +static size_t parse_operator1(sfilter *sf); +static size_t parse_char(sfilter *sf); +static size_t parse_hash(sfilter *sf); +static size_t parse_dash(sfilter *sf); +static size_t parse_slash(sfilter *sf); +static size_t parse_backslash(sfilter * sf); +static size_t parse_operator2(sfilter *sf); +static size_t parse_string(sfilter *sf); +static size_t parse_word(sfilter * sf); +static size_t parse_var(sfilter * sf); +static size_t parse_number(sfilter * sf); +static size_t parse_tick(sfilter * sf); +static size_t parse_ustring(sfilter * sf); +static size_t parse_qstring(sfilter * sf); +static size_t parse_nqstring(sfilter * sf); +static size_t parse_xstring(sfilter * sf); +static size_t parse_bstring(sfilter * sf); +static size_t parse_estring(sfilter * sf); +static size_t parse_bword(sfilter * sf); + + +typedef size_t (*pt2Function)(sfilter *sf); +static const pt2Function char_parse_map[] = { + &parse_white, /* 0 */ + &parse_white, /* 1 */ + &parse_white, /* 2 */ + &parse_white, /* 3 */ + &parse_white, /* 4 */ + &parse_white, /* 5 */ + &parse_white, /* 6 */ + &parse_white, /* 7 */ + &parse_white, /* 8 */ + &parse_white, /* 9 */ + &parse_white, /* 10 */ + &parse_white, /* 11 */ + &parse_white, /* 12 */ + &parse_white, /* 13 */ + &parse_white, /* 14 */ + &parse_white, /* 15 */ + &parse_white, /* 16 */ + &parse_white, /* 17 */ + &parse_white, /* 18 */ + &parse_white, /* 19 */ + &parse_white, /* 20 */ + &parse_white, /* 21 */ + &parse_white, /* 22 */ + &parse_white, /* 23 */ + &parse_white, /* 24 */ + &parse_white, /* 25 */ + &parse_white, /* 26 */ + &parse_white, /* 27 */ + &parse_white, /* 28 */ + &parse_white, /* 29 */ + &parse_white, /* 30 */ + &parse_white, /* 31 */ + &parse_white, /* 32 */ + &parse_operator2, /* 33 */ + &parse_string, /* 34 */ + &parse_hash, /* 35 */ + &parse_money, /* 36 */ + &parse_operator1, /* 37 */ + &parse_operator2, /* 38 */ + &parse_string, /* 39 */ + &parse_char, /* 40 */ + &parse_char, /* 41 */ + &parse_operator2, /* 42 */ + &parse_operator1, /* 43 */ + &parse_char, /* 44 */ + &parse_dash, /* 45 */ + &parse_number, /* 46 */ + &parse_slash, /* 47 */ + &parse_number, /* 48 */ + &parse_number, /* 49 */ + &parse_number, /* 50 */ + &parse_number, /* 51 */ + &parse_number, /* 52 */ + &parse_number, /* 53 */ + &parse_number, /* 54 */ + &parse_number, /* 55 */ + &parse_number, /* 56 */ + &parse_number, /* 57 */ + &parse_operator2, /* 58 */ + &parse_char, /* 59 */ + &parse_operator2, /* 60 */ + &parse_operator2, /* 61 */ + &parse_operator2, /* 62 */ + &parse_other, /* 63 */ + &parse_var, /* 64 */ + &parse_word, /* 65 */ + &parse_bstring, /* 66 */ + &parse_word, /* 67 */ + &parse_word, /* 68 */ + &parse_estring, /* 69 */ + &parse_word, /* 70 */ + &parse_word, /* 71 */ + &parse_word, /* 72 */ + &parse_word, /* 73 */ + &parse_word, /* 74 */ + &parse_word, /* 75 */ + &parse_word, /* 76 */ + &parse_word, /* 77 */ + &parse_nqstring, /* 78 */ + &parse_word, /* 79 */ + &parse_word, /* 80 */ + &parse_qstring, /* 81 */ + &parse_word, /* 82 */ + &parse_word, /* 83 */ + &parse_word, /* 84 */ + &parse_ustring, /* 85 */ + &parse_word, /* 86 */ + &parse_word, /* 87 */ + &parse_xstring, /* 88 */ + &parse_word, /* 89 */ + &parse_word, /* 90 */ + &parse_bword, /* 91 */ + &parse_backslash, /* 92 */ + &parse_other, /* 93 */ + &parse_operator1, /* 94 */ + &parse_word, /* 95 */ + &parse_tick, /* 96 */ + &parse_word, /* 97 */ + &parse_bstring, /* 98 */ + &parse_word, /* 99 */ + &parse_word, /* 100 */ + &parse_estring, /* 101 */ + &parse_word, /* 102 */ + &parse_word, /* 103 */ + &parse_word, /* 104 */ + &parse_word, /* 105 */ + &parse_word, /* 106 */ + &parse_word, /* 107 */ + &parse_word, /* 108 */ + &parse_word, /* 109 */ + &parse_nqstring, /* 110 */ + &parse_word, /* 111 */ + &parse_word, /* 112 */ + &parse_qstring, /* 113 */ + &parse_word, /* 114 */ + &parse_word, /* 115 */ + &parse_word, /* 116 */ + &parse_ustring, /* 117 */ + &parse_word, /* 118 */ + &parse_word, /* 119 */ + &parse_xstring, /* 120 */ + &parse_word, /* 121 */ + &parse_word, /* 122 */ + &parse_char, /* 123 */ + &parse_operator2, /* 124 */ + &parse_char, /* 125 */ + &parse_operator1, /* 126 */ + &parse_white, /* 127 */ + &parse_word, /* 128 */ + &parse_word, /* 129 */ + &parse_word, /* 130 */ + &parse_word, /* 131 */ + &parse_word, /* 132 */ + &parse_word, /* 133 */ + &parse_word, /* 134 */ + &parse_word, /* 135 */ + &parse_word, /* 136 */ + &parse_word, /* 137 */ + &parse_word, /* 138 */ + &parse_word, /* 139 */ + &parse_word, /* 140 */ + &parse_word, /* 141 */ + &parse_word, /* 142 */ + &parse_word, /* 143 */ + &parse_word, /* 144 */ + &parse_word, /* 145 */ + &parse_word, /* 146 */ + &parse_word, /* 147 */ + &parse_word, /* 148 */ + &parse_word, /* 149 */ + &parse_word, /* 150 */ + &parse_word, /* 151 */ + &parse_word, /* 152 */ + &parse_word, /* 153 */ + &parse_word, /* 154 */ + &parse_word, /* 155 */ + &parse_word, /* 156 */ + &parse_word, /* 157 */ + &parse_word, /* 158 */ + &parse_word, /* 159 */ + &parse_white, /* 160 */ + &parse_word, /* 161 */ + &parse_word, /* 162 */ + &parse_word, /* 163 */ + &parse_word, /* 164 */ + &parse_word, /* 165 */ + &parse_word, /* 166 */ + &parse_word, /* 167 */ + &parse_word, /* 168 */ + &parse_word, /* 169 */ + &parse_word, /* 170 */ + &parse_word, /* 171 */ + &parse_word, /* 172 */ + &parse_word, /* 173 */ + &parse_word, /* 174 */ + &parse_word, /* 175 */ + &parse_word, /* 176 */ + &parse_word, /* 177 */ + &parse_word, /* 178 */ + &parse_word, /* 179 */ + &parse_word, /* 180 */ + &parse_word, /* 181 */ + &parse_word, /* 182 */ + &parse_word, /* 183 */ + &parse_word, /* 184 */ + &parse_word, /* 185 */ + &parse_word, /* 186 */ + &parse_word, /* 187 */ + &parse_word, /* 188 */ + &parse_word, /* 189 */ + &parse_word, /* 190 */ + &parse_word, /* 191 */ + &parse_word, /* 192 */ + &parse_word, /* 193 */ + &parse_word, /* 194 */ + &parse_word, /* 195 */ + &parse_word, /* 196 */ + &parse_word, /* 197 */ + &parse_word, /* 198 */ + &parse_word, /* 199 */ + &parse_word, /* 200 */ + &parse_word, /* 201 */ + &parse_word, /* 202 */ + &parse_word, /* 203 */ + &parse_word, /* 204 */ + &parse_word, /* 205 */ + &parse_word, /* 206 */ + &parse_word, /* 207 */ + &parse_word, /* 208 */ + &parse_word, /* 209 */ + &parse_word, /* 210 */ + &parse_word, /* 211 */ + &parse_word, /* 212 */ + &parse_word, /* 213 */ + &parse_word, /* 214 */ + &parse_word, /* 215 */ + &parse_word, /* 216 */ + &parse_word, /* 217 */ + &parse_word, /* 218 */ + &parse_word, /* 219 */ + &parse_word, /* 220 */ + &parse_word, /* 221 */ + &parse_word, /* 222 */ + &parse_word, /* 223 */ + &parse_word, /* 224 */ + &parse_word, /* 225 */ + &parse_word, /* 226 */ + &parse_word, /* 227 */ + &parse_word, /* 228 */ + &parse_word, /* 229 */ + &parse_word, /* 230 */ + &parse_word, /* 231 */ + &parse_word, /* 232 */ + &parse_word, /* 233 */ + &parse_word, /* 234 */ + &parse_word, /* 235 */ + &parse_word, /* 236 */ + &parse_word, /* 237 */ + &parse_word, /* 238 */ + &parse_word, /* 239 */ + &parse_word, /* 240 */ + &parse_word, /* 241 */ + &parse_word, /* 242 */ + &parse_word, /* 243 */ + &parse_word, /* 244 */ + &parse_word, /* 245 */ + &parse_word, /* 246 */ + &parse_word, /* 247 */ + &parse_word, /* 248 */ + &parse_word, /* 249 */ + &parse_word, /* 250 */ + &parse_word, /* 251 */ + &parse_word, /* 252 */ + &parse_word, /* 253 */ + &parse_word, /* 254 */ + &parse_word, /* 255 */ +}; + +static const keyword_t sql_keywords[] = { + {"!!", 'o'}, + {"!<", 'o'}, + {"!=", 'o'}, + {"!>", 'o'}, + {"%=", 'o'}, + {"&&", '&'}, + {"&=", 'o'}, + {"*=", 'o'}, + {"+=", 'o'}, + {"-=", 'o'}, + {"/=", 'o'}, + {"0&(1)O", 'F'}, + {"0&(1)U", 'F'}, + {"0&(1O(", 'F'}, + {"0&(1OF", 'F'}, + {"0&(1OS", 'F'}, + {"0&(1OV", 'F'}, + {"0&(F()", 'F'}, + {"0&(F(1", 'F'}, + {"0&(F(F", 'F'}, + {"0&(F(N", 'F'}, + {"0&(F(S", 'F'}, + {"0&(F(V", 'F'}, + {"0&(N)O", 'F'}, + {"0&(N)U", 'F'}, + {"0&(NO(", 'F'}, + {"0&(NOF", 'F'}, + {"0&(NOS", 'F'}, + {"0&(NOV", 'F'}, + {"0&(S)O", 'F'}, + {"0&(S)U", 'F'}, + {"0&(SO(", 'F'}, + {"0&(SO1", 'F'}, + {"0&(SOF", 'F'}, + {"0&(SON", 'F'}, + {"0&(SOS", 'F'}, + {"0&(SOV", 'F'}, + {"0&(V)O", 'F'}, + {"0&(V)U", 'F'}, + {"0&(VO(", 'F'}, + {"0&(VOF", 'F'}, + {"0&(VOS", 'F'}, + {"0&1O(1", 'F'}, + {"0&1O(F", 'F'}, + {"0&1O(N", 'F'}, + {"0&1O(S", 'F'}, + {"0&1O(V", 'F'}, + {"0&1OF(", 'F'}, + {"0&1OS(", 'F'}, + {"0&1OS1", 'F'}, + {"0&1OSF", 'F'}, + {"0&1OSU", 'F'}, + {"0&1OSV", 'F'}, + {"0&1OV(", 'F'}, + {"0&1OVF", 'F'}, + {"0&1OVO", 'F'}, + {"0&1OVS", 'F'}, + {"0&1OVU", 'F'}, + {"0&1UE(", 'F'}, + {"0&1UE1", 'F'}, + {"0&1UEF", 'F'}, + {"0&1UEK", 'F'}, + {"0&1UEN", 'F'}, + {"0&1UES", 'F'}, + {"0&1UEV", 'F'}, + {"0&F()O", 'F'}, + {"0&F()U", 'F'}, + {"0&F(1)", 'F'}, + {"0&F(1O", 'F'}, + {"0&F(F(", 'F'}, + {"0&F(N)", 'F'}, + {"0&F(NO", 'F'}, + {"0&F(S)", 'F'}, + {"0&F(SO", 'F'}, + {"0&F(V)", 'F'}, + {"0&F(VO", 'F'}, + {"0&NO(1", 'F'}, + {"0&NO(F", 'F'}, + {"0&NO(N", 'F'}, + {"0&NO(S", 'F'}, + {"0&NO(V", 'F'}, + {"0&NOF(", 'F'}, + {"0&NOS(", 'F'}, + {"0&NOS1", 'F'}, + {"0&NOSF", 'F'}, + {"0&NOSU", 'F'}, + {"0&NOSV", 'F'}, + {"0&NOV(", 'F'}, + {"0&NOVF", 'F'}, + {"0&NOVO", 'F'}, + {"0&NOVS", 'F'}, + {"0&NOVU", 'F'}, + {"0&NUE(", 'F'}, + {"0&NUE1", 'F'}, + {"0&NUEF", 'F'}, + {"0&NUEK", 'F'}, + {"0&NUEN", 'F'}, + {"0&NUES", 'F'}, + {"0&NUEV", 'F'}, + {"0&SO(1", 'F'}, + {"0&SO(F", 'F'}, + {"0&SO(N", 'F'}, + {"0&SO(S", 'F'}, + {"0&SO(V", 'F'}, + {"0&SO1(", 'F'}, + {"0&SO1F", 'F'}, + {"0&SO1N", 'F'}, + {"0&SO1S", 'F'}, + {"0&SO1U", 'F'}, + {"0&SO1V", 'F'}, + {"0&SOF(", 'F'}, + {"0&SON(", 'F'}, + {"0&SON1", 'F'}, + {"0&SONF", 'F'}, + {"0&SONU", 'F'}, + {"0&SOS(", 'F'}, + {"0&SOS1", 'F'}, + {"0&SOSF", 'F'}, + {"0&SOSU", 'F'}, + {"0&SOSV", 'F'}, + {"0&SOV(", 'F'}, + {"0&SOVF", 'F'}, + {"0&SOVO", 'F'}, + {"0&SOVS", 'F'}, + {"0&SOVU", 'F'}, + {"0&SUE(", 'F'}, + {"0&SUE1", 'F'}, + {"0&SUEF", 'F'}, + {"0&SUEK", 'F'}, + {"0&SUEN", 'F'}, + {"0&SUES", 'F'}, + {"0&SUEV", 'F'}, + {"0&VO(1", 'F'}, + {"0&VO(F", 'F'}, + {"0&VO(N", 'F'}, + {"0&VO(S", 'F'}, + {"0&VO(V", 'F'}, + {"0&VOF(", 'F'}, + {"0&VOS(", 'F'}, + {"0&VOS1", 'F'}, + {"0&VOSF", 'F'}, + {"0&VOSU", 'F'}, + {"0&VOSV", 'F'}, + {"0&VUE(", 'F'}, + {"0&VUE1", 'F'}, + {"0&VUEF", 'F'}, + {"0&VUEK", 'F'}, + {"0&VUEN", 'F'}, + {"0&VUES", 'F'}, + {"0&VUEV", 'F'}, + {"0)&(EK", 'F'}, + {"0)&(EN", 'F'}, + {"0)UE(1", 'F'}, + {"0)UE(F", 'F'}, + {"0)UE(N", 'F'}, + {"0)UE(S", 'F'}, + {"0)UE(V", 'F'}, + {"0)UE1K", 'F'}, + {"0)UE1O", 'F'}, + {"0)UEF(", 'F'}, + {"0)UEK(", 'F'}, + {"0)UEK1", 'F'}, + {"0)UEKF", 'F'}, + {"0)UEKN", 'F'}, + {"0)UEKS", 'F'}, + {"0)UEKV", 'F'}, + {"0)UENK", 'F'}, + {"0)UENO", 'F'}, + {"0)UESK", 'F'}, + {"0)UESO", 'F'}, + {"0)UEVK", 'F'}, + {"0)UEVO", 'F'}, + {"01&(1&", 'F'}, + {"01&(1)", 'F'}, + {"01&(1,", 'F'}, + {"01&(1O", 'F'}, + {"01&(E(", 'F'}, + {"01&(E1", 'F'}, + {"01&(EF", 'F'}, + {"01&(EK", 'F'}, + {"01&(EN", 'F'}, + {"01&(EO", 'F'}, + {"01&(ES", 'F'}, + {"01&(EV", 'F'}, + {"01&(F(", 'F'}, + {"01&(N&", 'F'}, + {"01&(N)", 'F'}, + {"01&(N,", 'F'}, + {"01&(NO", 'F'}, + {"01&(S&", 'F'}, + {"01&(S)", 'F'}, + {"01&(S,", 'F'}, + {"01&(SO", 'F'}, + {"01&(V&", 'F'}, + {"01&(V)", 'F'}, + {"01&(V,", 'F'}, + {"01&(VO", 'F'}, + {"01&1", 'F'}, + {"01&1&(", 'F'}, + {"01&1&1", 'F'}, + {"01&1&F", 'F'}, + {"01&1&N", 'F'}, + {"01&1&S", 'F'}, + {"01&1&V", 'F'}, + {"01&1)&", 'F'}, + {"01&1)C", 'F'}, + {"01&1)O", 'F'}, + {"01&1)U", 'F'}, + {"01&1;", 'F'}, + {"01&1;C", 'F'}, + {"01&1;E", 'F'}, + {"01&1;T", 'F'}, + {"01&1B(", 'F'}, + {"01&1B1", 'F'}, + {"01&1BF", 'F'}, + {"01&1BN", 'F'}, + {"01&1BS", 'F'}, + {"01&1BV", 'F'}, + {"01&1C", 'F'}, + {"01&1EK", 'F'}, + {"01&1EN", 'F'}, + {"01&1F(", 'F'}, + {"01&1K(", 'F'}, + {"01&1K1", 'F'}, + {"01&1KF", 'F'}, + {"01&1KN", 'F'}, + {"01&1KS", 'F'}, + {"01&1KV", 'F'}, + {"01&1O(", 'F'}, + {"01&1OF", 'F'}, + {"01&1OS", 'F'}, + {"01&1OV", 'F'}, + {"01&1TN", 'F'}, + {"01&1U", 'F'}, + {"01&1U(", 'F'}, + {"01&1U;", 'F'}, + {"01&1UC", 'F'}, + {"01&1UE", 'F'}, + {"01&E(1", 'F'}, + {"01&E(F", 'F'}, + {"01&E(N", 'F'}, + {"01&E(O", 'F'}, + {"01&E(S", 'F'}, + {"01&E(V", 'F'}, + {"01&E1", 'F'}, + {"01&E1;", 'F'}, + {"01&E1C", 'F'}, + {"01&E1K", 'F'}, + {"01&E1O", 'F'}, + {"01&EF(", 'F'}, + {"01&EK(", 'F'}, + {"01&EK1", 'F'}, + {"01&EKF", 'F'}, + {"01&EKN", 'F'}, + {"01&EKS", 'F'}, + {"01&EKU", 'F'}, + {"01&EKV", 'F'}, + {"01&EN", 'F'}, + {"01&EN;", 'F'}, + {"01&ENC", 'F'}, + {"01&ENK", 'F'}, + {"01&ENO", 'F'}, + {"01&ES", 'F'}, + {"01&ES;", 'F'}, + {"01&ESC", 'F'}, + {"01&ESK", 'F'}, + {"01&ESO", 'F'}, + {"01&EUE", 'F'}, + {"01&EV", 'F'}, + {"01&EV;", 'F'}, + {"01&EVC", 'F'}, + {"01&EVK", 'F'}, + {"01&EVO", 'F'}, + {"01&F()", 'F'}, + {"01&F(1", 'F'}, + {"01&F(E", 'F'}, + {"01&F(F", 'F'}, + {"01&F(N", 'F'}, + {"01&F(S", 'F'}, + {"01&F(V", 'F'}, + {"01&K&(", 'F'}, + {"01&K&1", 'F'}, + {"01&K&F", 'F'}, + {"01&K&N", 'F'}, + {"01&K&S", 'F'}, + {"01&K&V", 'F'}, + {"01&K(1", 'F'}, + {"01&K(F", 'F'}, + {"01&K(N", 'F'}, + {"01&K(S", 'F'}, + {"01&K(V", 'F'}, + {"01&K1O", 'F'}, + {"01&KC", 'F'}, + {"01&KF(", 'F'}, + {"01&KNK", 'F'}, + {"01&KO(", 'F'}, + {"01&KO1", 'F'}, + {"01&KOF", 'F'}, + {"01&KOK", 'F'}, + {"01&KON", 'F'}, + {"01&KOS", 'F'}, + {"01&KOV", 'F'}, + {"01&KSO", 'F'}, + {"01&KVO", 'F'}, + {"01&N&(", 'F'}, + {"01&N&1", 'F'}, + {"01&N&F", 'F'}, + {"01&N&N", 'F'}, + {"01&N&S", 'F'}, + {"01&N&V", 'F'}, + {"01&N)&", 'F'}, + {"01&N)C", 'F'}, + {"01&N)O", 'F'}, + {"01&N)U", 'F'}, + {"01&N;", 'F'}, + {"01&N;C", 'F'}, + {"01&N;E", 'F'}, + {"01&N;T", 'F'}, + {"01&NB(", 'F'}, + {"01&NB1", 'F'}, + {"01&NBF", 'F'}, + {"01&NBN", 'F'}, + {"01&NBS", 'F'}, + {"01&NBV", 'F'}, + {"01&NC", 'F'}, + {"01&NEN", 'F'}, + {"01&NF(", 'F'}, + {"01&NK(", 'F'}, + {"01&NK1", 'F'}, + {"01&NKF", 'F'}, + {"01&NKN", 'F'}, + {"01&NKS", 'F'}, + {"01&NKV", 'F'}, + {"01&NO(", 'F'}, + {"01&NOF", 'F'}, + {"01&NOS", 'F'}, + {"01&NOV", 'F'}, + {"01&NTN", 'F'}, + {"01&NU", 'F'}, + {"01&NU(", 'F'}, + {"01&NU;", 'F'}, + {"01&NUC", 'F'}, + {"01&NUE", 'F'}, + {"01&S", 'F'}, + {"01&S&(", 'F'}, + {"01&S&1", 'F'}, + {"01&S&F", 'F'}, + {"01&S&N", 'F'}, + {"01&S&S", 'F'}, + {"01&S&V", 'F'}, + {"01&S)&", 'F'}, + {"01&S)C", 'F'}, + {"01&S)O", 'F'}, + {"01&S)U", 'F'}, + {"01&S1", 'F'}, + {"01&S1;", 'F'}, + {"01&S1C", 'F'}, + {"01&S;", 'F'}, + {"01&S;C", 'F'}, + {"01&S;E", 'F'}, + {"01&S;T", 'F'}, + {"01&SB(", 'F'}, + {"01&SB1", 'F'}, + {"01&SBF", 'F'}, + {"01&SBN", 'F'}, + {"01&SBS", 'F'}, + {"01&SBV", 'F'}, + {"01&SC", 'F'}, + {"01&SEK", 'F'}, + {"01&SEN", 'F'}, + {"01&SF(", 'F'}, + {"01&SK(", 'F'}, + {"01&SK1", 'F'}, + {"01&SKF", 'F'}, + {"01&SKN", 'F'}, + {"01&SKS", 'F'}, + {"01&SKV", 'F'}, + {"01&SO(", 'F'}, + {"01&SO1", 'F'}, + {"01&SOF", 'F'}, + {"01&SON", 'F'}, + {"01&SOS", 'F'}, + {"01&SOV", 'F'}, + {"01&STN", 'F'}, + {"01&SU", 'F'}, + {"01&SU(", 'F'}, + {"01&SU;", 'F'}, + {"01&SUC", 'F'}, + {"01&SUE", 'F'}, + {"01&SV", 'F'}, + {"01&SV;", 'F'}, + {"01&SVC", 'F'}, + {"01&SVO", 'F'}, + {"01&V", 'F'}, + {"01&V&(", 'F'}, + {"01&V&1", 'F'}, + {"01&V&F", 'F'}, + {"01&V&N", 'F'}, + {"01&V&S", 'F'}, + {"01&V&V", 'F'}, + {"01&V)&", 'F'}, + {"01&V)C", 'F'}, + {"01&V)O", 'F'}, + {"01&V)U", 'F'}, + {"01&V;", 'F'}, + {"01&V;C", 'F'}, + {"01&V;E", 'F'}, + {"01&V;T", 'F'}, + {"01&VB(", 'F'}, + {"01&VB1", 'F'}, + {"01&VBF", 'F'}, + {"01&VBN", 'F'}, + {"01&VBS", 'F'}, + {"01&VBV", 'F'}, + {"01&VC", 'F'}, + {"01&VEK", 'F'}, + {"01&VEN", 'F'}, + {"01&VF(", 'F'}, + {"01&VK(", 'F'}, + {"01&VK1", 'F'}, + {"01&VKF", 'F'}, + {"01&VKN", 'F'}, + {"01&VKS", 'F'}, + {"01&VKV", 'F'}, + {"01&VO(", 'F'}, + {"01&VOF", 'F'}, + {"01&VOS", 'F'}, + {"01&VS", 'F'}, + {"01&VS;", 'F'}, + {"01&VSC", 'F'}, + {"01&VSO", 'F'}, + {"01&VTN", 'F'}, + {"01&VU", 'F'}, + {"01&VU(", 'F'}, + {"01&VU;", 'F'}, + {"01&VUC", 'F'}, + {"01&VUE", 'F'}, + {"01(EF(", 'F'}, + {"01(EKF", 'F'}, + {"01(EKN", 'F'}, + {"01(ENK", 'F'}, + {"01(U(E", 'F'}, + {"01)&(1", 'F'}, + {"01)&(E", 'F'}, + {"01)&(F", 'F'}, + {"01)&(N", 'F'}, + {"01)&(S", 'F'}, + {"01)&(V", 'F'}, + {"01)&1", 'F'}, + {"01)&1&", 'F'}, + {"01)&1)", 'F'}, + {"01)&1;", 'F'}, + {"01)&1B", 'F'}, + {"01)&1C", 'F'}, + {"01)&1F", 'F'}, + {"01)&1O", 'F'}, + {"01)&1U", 'F'}, + {"01)&F(", 'F'}, + {"01)&N", 'F'}, + {"01)&N&", 'F'}, + {"01)&N)", 'F'}, + {"01)&N;", 'F'}, + {"01)&NB", 'F'}, + {"01)&NC", 'F'}, + {"01)&NF", 'F'}, + {"01)&NO", 'F'}, + {"01)&NU", 'F'}, + {"01)&S", 'F'}, + {"01)&S&", 'F'}, + {"01)&S)", 'F'}, + {"01)&S;", 'F'}, + {"01)&SB", 'F'}, + {"01)&SC", 'F'}, + {"01)&SF", 'F'}, + {"01)&SO", 'F'}, + {"01)&SU", 'F'}, + {"01)&V", 'F'}, + {"01)&V&", 'F'}, + {"01)&V)", 'F'}, + {"01)&V;", 'F'}, + {"01)&VB", 'F'}, + {"01)&VC", 'F'}, + {"01)&VF", 'F'}, + {"01)&VO", 'F'}, + {"01)&VU", 'F'}, + {"01),(1", 'F'}, + {"01),(F", 'F'}, + {"01),(N", 'F'}, + {"01),(S", 'F'}, + {"01),(V", 'F'}, + {"01);E(", 'F'}, + {"01);E1", 'F'}, + {"01);EF", 'F'}, + {"01);EK", 'F'}, + {"01);EN", 'F'}, + {"01);EO", 'F'}, + {"01);ES", 'F'}, + {"01);EV", 'F'}, + {"01);T(", 'F'}, + {"01);T1", 'F'}, + {"01);TF", 'F'}, + {"01);TK", 'F'}, + {"01);TN", 'F'}, + {"01);TO", 'F'}, + {"01);TS", 'F'}, + {"01);TV", 'F'}, + {"01)B(1", 'F'}, + {"01)B(F", 'F'}, + {"01)B(N", 'F'}, + {"01)B(S", 'F'}, + {"01)B(V", 'F'}, + {"01)B1", 'F'}, + {"01)B1&", 'F'}, + {"01)B1;", 'F'}, + {"01)B1C", 'F'}, + {"01)B1K", 'F'}, + {"01)B1N", 'F'}, + {"01)B1O", 'F'}, + {"01)B1U", 'F'}, + {"01)BF(", 'F'}, + {"01)BN", 'F'}, + {"01)BN&", 'F'}, + {"01)BN;", 'F'}, + {"01)BNC", 'F'}, + {"01)BNK", 'F'}, + {"01)BNO", 'F'}, + {"01)BNU", 'F'}, + {"01)BS", 'F'}, + {"01)BS&", 'F'}, + {"01)BS;", 'F'}, + {"01)BSC", 'F'}, + {"01)BSK", 'F'}, + {"01)BSO", 'F'}, + {"01)BSU", 'F'}, + {"01)BV", 'F'}, + {"01)BV&", 'F'}, + {"01)BV;", 'F'}, + {"01)BVC", 'F'}, + {"01)BVK", 'F'}, + {"01)BVO", 'F'}, + {"01)BVU", 'F'}, + {"01)C", 'F'}, + {"01)E(1", 'F'}, + {"01)E(F", 'F'}, + {"01)E(N", 'F'}, + {"01)E(S", 'F'}, + {"01)E(V", 'F'}, + {"01)E1C", 'F'}, + {"01)E1O", 'F'}, + {"01)EF(", 'F'}, + {"01)EK(", 'F'}, + {"01)EK1", 'F'}, + {"01)EKF", 'F'}, + {"01)EKN", 'F'}, + {"01)EKS", 'F'}, + {"01)EKV", 'F'}, + {"01)ENC", 'F'}, + {"01)ENO", 'F'}, + {"01)ESC", 'F'}, + {"01)ESO", 'F'}, + {"01)EVC", 'F'}, + {"01)EVO", 'F'}, + {"01)F(F", 'F'}, + {"01)K(1", 'F'}, + {"01)K(F", 'F'}, + {"01)K(N", 'F'}, + {"01)K(S", 'F'}, + {"01)K(V", 'F'}, + {"01)K1&", 'F'}, + {"01)K1;", 'F'}, + {"01)K1B", 'F'}, + {"01)K1E", 'F'}, + {"01)K1O", 'F'}, + {"01)K1U", 'F'}, + {"01)KB(", 'F'}, + {"01)KB1", 'F'}, + {"01)KBF", 'F'}, + {"01)KBN", 'F'}, + {"01)KBS", 'F'}, + {"01)KBV", 'F'}, + {"01)KF(", 'F'}, + {"01)KN&", 'F'}, + {"01)KN;", 'F'}, + {"01)KNB", 'F'}, + {"01)KNC", 'F'}, + {"01)KNE", 'F'}, + {"01)KNK", 'F'}, + {"01)KNU", 'F'}, + {"01)KS&", 'F'}, + {"01)KS;", 'F'}, + {"01)KSB", 'F'}, + {"01)KSE", 'F'}, + {"01)KSO", 'F'}, + {"01)KSU", 'F'}, + {"01)KUE", 'F'}, + {"01)KV&", 'F'}, + {"01)KV;", 'F'}, + {"01)KVB", 'F'}, + {"01)KVE", 'F'}, + {"01)KVO", 'F'}, + {"01)KVU", 'F'}, + {"01)O(1", 'F'}, + {"01)O(E", 'F'}, + {"01)O(F", 'F'}, + {"01)O(N", 'F'}, + {"01)O(S", 'F'}, + {"01)O(V", 'F'}, + {"01)O1", 'F'}, + {"01)O1&", 'F'}, + {"01)O1)", 'F'}, + {"01)O1;", 'F'}, + {"01)O1B", 'F'}, + {"01)O1C", 'F'}, + {"01)O1K", 'F'}, + {"01)O1U", 'F'}, + {"01)OF(", 'F'}, + {"01)ON&", 'F'}, + {"01)ON)", 'F'}, + {"01)ON;", 'F'}, + {"01)ONB", 'F'}, + {"01)ONC", 'F'}, + {"01)ONK", 'F'}, + {"01)ONU", 'F'}, + {"01)OS", 'F'}, + {"01)OS&", 'F'}, + {"01)OS)", 'F'}, + {"01)OS;", 'F'}, + {"01)OSB", 'F'}, + {"01)OSC", 'F'}, + {"01)OSK", 'F'}, + {"01)OSU", 'F'}, + {"01)OV", 'F'}, + {"01)OV&", 'F'}, + {"01)OV)", 'F'}, + {"01)OV;", 'F'}, + {"01)OVB", 'F'}, + {"01)OVC", 'F'}, + {"01)OVK", 'F'}, + {"01)OVO", 'F'}, + {"01)OVU", 'F'}, + {"01)U(E", 'F'}, + {"01)UE(", 'F'}, + {"01)UE1", 'F'}, + {"01)UEF", 'F'}, + {"01)UEK", 'F'}, + {"01)UEN", 'F'}, + {"01)UES", 'F'}, + {"01)UEV", 'F'}, + {"01,(1)", 'F'}, + {"01,(1O", 'F'}, + {"01,(E(", 'F'}, + {"01,(E1", 'F'}, + {"01,(EF", 'F'}, + {"01,(EK", 'F'}, + {"01,(EN", 'F'}, + {"01,(ES", 'F'}, + {"01,(EV", 'F'}, + {"01,(F(", 'F'}, + {"01,(N)", 'F'}, + {"01,(NO", 'F'}, + {"01,(S)", 'F'}, + {"01,(SO", 'F'}, + {"01,(V)", 'F'}, + {"01,(VO", 'F'}, + {"01,F()", 'F'}, + {"01,F(1", 'F'}, + {"01,F(F", 'F'}, + {"01,F(N", 'F'}, + {"01,F(S", 'F'}, + {"01,F(V", 'F'}, + {"01;E(1", 'F'}, + {"01;E(E", 'F'}, + {"01;E(F", 'F'}, + {"01;E(N", 'F'}, + {"01;E(S", 'F'}, + {"01;E(V", 'F'}, + {"01;E1,", 'F'}, + {"01;E1;", 'F'}, + {"01;E1C", 'F'}, + {"01;E1K", 'F'}, + {"01;E1O", 'F'}, + {"01;E1T", 'F'}, + {"01;EF(", 'F'}, + {"01;EK(", 'F'}, + {"01;EK1", 'F'}, + {"01;EKF", 'F'}, + {"01;EKN", 'F'}, + {"01;EKO", 'F'}, + {"01;EKS", 'F'}, + {"01;EKV", 'F'}, + {"01;EN,", 'F'}, + {"01;EN;", 'F'}, + {"01;ENC", 'F'}, + {"01;ENE", 'F'}, + {"01;ENK", 'F'}, + {"01;ENO", 'F'}, + {"01;ENT", 'F'}, + {"01;ES,", 'F'}, + {"01;ES;", 'F'}, + {"01;ESC", 'F'}, + {"01;ESK", 'F'}, + {"01;ESO", 'F'}, + {"01;EST", 'F'}, + {"01;EV,", 'F'}, + {"01;EV;", 'F'}, + {"01;EVC", 'F'}, + {"01;EVK", 'F'}, + {"01;EVO", 'F'}, + {"01;EVT", 'F'}, + {"01;N:T", 'F'}, + {"01;T(1", 'F'}, + {"01;T(C", 'F'}, + {"01;T(E", 'F'}, + {"01;T(F", 'F'}, + {"01;T(N", 'F'}, + {"01;T(S", 'F'}, + {"01;T(V", 'F'}, + {"01;T1(", 'F'}, + {"01;T1,", 'F'}, + {"01;T1;", 'F'}, + {"01;T1C", 'F'}, + {"01;T1F", 'F'}, + {"01;T1K", 'F'}, + {"01;T1O", 'F'}, + {"01;T1T", 'F'}, + {"01;T;", 'F'}, + {"01;T;C", 'F'}, + {"01;TF(", 'F'}, + {"01;TK(", 'F'}, + {"01;TK1", 'F'}, + {"01;TKF", 'F'}, + {"01;TKK", 'F'}, + {"01;TKN", 'F'}, + {"01;TKO", 'F'}, + {"01;TKS", 'F'}, + {"01;TKV", 'F'}, + {"01;TN(", 'F'}, + {"01;TN,", 'F'}, + {"01;TN1", 'F'}, + {"01;TN;", 'F'}, + {"01;TNC", 'F'}, + {"01;TNF", 'F'}, + {"01;TNK", 'F'}, + {"01;TNN", 'F'}, + {"01;TNO", 'F'}, + {"01;TNS", 'F'}, + {"01;TNT", 'F'}, + {"01;TNV", 'F'}, + {"01;TO(", 'F'}, + {"01;TS(", 'F'}, + {"01;TS,", 'F'}, + {"01;TS;", 'F'}, + {"01;TSC", 'F'}, + {"01;TSF", 'F'}, + {"01;TSK", 'F'}, + {"01;TSO", 'F'}, + {"01;TST", 'F'}, + {"01;TTN", 'F'}, + {"01;TV(", 'F'}, + {"01;TV,", 'F'}, + {"01;TV;", 'F'}, + {"01;TVC", 'F'}, + {"01;TVF", 'F'}, + {"01;TVK", 'F'}, + {"01;TVO", 'F'}, + {"01;TVT", 'F'}, + {"01A(F(", 'F'}, + {"01A(N)", 'F'}, + {"01A(NO", 'F'}, + {"01A(S)", 'F'}, + {"01A(SO", 'F'}, + {"01A(V)", 'F'}, + {"01A(VO", 'F'}, + {"01AF()", 'F'}, + {"01AF(1", 'F'}, + {"01AF(F", 'F'}, + {"01AF(N", 'F'}, + {"01AF(S", 'F'}, + {"01AF(V", 'F'}, + {"01ASO(", 'F'}, + {"01ASO1", 'F'}, + {"01ASOF", 'F'}, + {"01ASON", 'F'}, + {"01ASOS", 'F'}, + {"01ASOV", 'F'}, + {"01ASUE", 'F'}, + {"01ATO(", 'F'}, + {"01ATO1", 'F'}, + {"01ATOF", 'F'}, + {"01ATON", 'F'}, + {"01ATOS", 'F'}, + {"01ATOV", 'F'}, + {"01ATUE", 'F'}, + {"01AVO(", 'F'}, + {"01AVOF", 'F'}, + {"01AVOS", 'F'}, + {"01AVUE", 'F'}, + {"01B(1)", 'F'}, + {"01B(1O", 'F'}, + {"01B(F(", 'F'}, + {"01B(NO", 'F'}, + {"01B(S)", 'F'}, + {"01B(SO", 'F'}, + {"01B(V)", 'F'}, + {"01B(VO", 'F'}, + {"01B1", 'F'}, + {"01B1&(", 'F'}, + {"01B1&1", 'F'}, + {"01B1&F", 'F'}, + {"01B1&N", 'F'}, + {"01B1&S", 'F'}, + {"01B1&V", 'F'}, + {"01B1,(", 'F'}, + {"01B1,F", 'F'}, + {"01B1;", 'F'}, + {"01B1;C", 'F'}, + {"01B1B(", 'F'}, + {"01B1B1", 'F'}, + {"01B1BF", 'F'}, + {"01B1BN", 'F'}, + {"01B1BS", 'F'}, + {"01B1BV", 'F'}, + {"01B1C", 'F'}, + {"01B1K(", 'F'}, + {"01B1K1", 'F'}, + {"01B1KF", 'F'}, + {"01B1KN", 'F'}, + {"01B1KS", 'F'}, + {"01B1KV", 'F'}, + {"01B1O(", 'F'}, + {"01B1OF", 'F'}, + {"01B1OS", 'F'}, + {"01B1OV", 'F'}, + {"01B1U(", 'F'}, + {"01B1UE", 'F'}, + {"01BE(1", 'F'}, + {"01BE(F", 'F'}, + {"01BE(N", 'F'}, + {"01BE(S", 'F'}, + {"01BE(V", 'F'}, + {"01BEK(", 'F'}, + {"01BF()", 'F'}, + {"01BF(1", 'F'}, + {"01BF(F", 'F'}, + {"01BF(N", 'F'}, + {"01BF(S", 'F'}, + {"01BF(V", 'F'}, + {"01BN", 'F'}, + {"01BN&(", 'F'}, + {"01BN&1", 'F'}, + {"01BN&F", 'F'}, + {"01BN&N", 'F'}, + {"01BN&S", 'F'}, + {"01BN&V", 'F'}, + {"01BN,(", 'F'}, + {"01BN,F", 'F'}, + {"01BN;", 'F'}, + {"01BN;C", 'F'}, + {"01BNB(", 'F'}, + {"01BNB1", 'F'}, + {"01BNBF", 'F'}, + {"01BNBN", 'F'}, + {"01BNBS", 'F'}, + {"01BNBV", 'F'}, + {"01BNC", 'F'}, + {"01BNK(", 'F'}, + {"01BNK1", 'F'}, + {"01BNKF", 'F'}, + {"01BNKN", 'F'}, + {"01BNKS", 'F'}, + {"01BNKV", 'F'}, + {"01BNO(", 'F'}, + {"01BNOF", 'F'}, + {"01BNOS", 'F'}, + {"01BNOV", 'F'}, + {"01BNU(", 'F'}, + {"01BNUE", 'F'}, + {"01BS", 'F'}, + {"01BS&(", 'F'}, + {"01BS&1", 'F'}, + {"01BS&F", 'F'}, + {"01BS&N", 'F'}, + {"01BS&S", 'F'}, + {"01BS&V", 'F'}, + {"01BS,(", 'F'}, + {"01BS,F", 'F'}, + {"01BS;", 'F'}, + {"01BS;C", 'F'}, + {"01BSB(", 'F'}, + {"01BSB1", 'F'}, + {"01BSBF", 'F'}, + {"01BSBN", 'F'}, + {"01BSBS", 'F'}, + {"01BSBV", 'F'}, + {"01BSC", 'F'}, + {"01BSK(", 'F'}, + {"01BSK1", 'F'}, + {"01BSKF", 'F'}, + {"01BSKN", 'F'}, + {"01BSKS", 'F'}, + {"01BSKV", 'F'}, + {"01BSO(", 'F'}, + {"01BSO1", 'F'}, + {"01BSOF", 'F'}, + {"01BSON", 'F'}, + {"01BSOS", 'F'}, + {"01BSOV", 'F'}, + {"01BSU(", 'F'}, + {"01BSUE", 'F'}, + {"01BV", 'F'}, + {"01BV&(", 'F'}, + {"01BV&1", 'F'}, + {"01BV&F", 'F'}, + {"01BV&N", 'F'}, + {"01BV&S", 'F'}, + {"01BV&V", 'F'}, + {"01BV,(", 'F'}, + {"01BV,F", 'F'}, + {"01BV;", 'F'}, + {"01BV;C", 'F'}, + {"01BVB(", 'F'}, + {"01BVB1", 'F'}, + {"01BVBF", 'F'}, + {"01BVBN", 'F'}, + {"01BVBS", 'F'}, + {"01BVBV", 'F'}, + {"01BVC", 'F'}, + {"01BVK(", 'F'}, + {"01BVK1", 'F'}, + {"01BVKF", 'F'}, + {"01BVKN", 'F'}, + {"01BVKS", 'F'}, + {"01BVKV", 'F'}, + {"01BVO(", 'F'}, + {"01BVOF", 'F'}, + {"01BVOS", 'F'}, + {"01BVU(", 'F'}, + {"01BVUE", 'F'}, + {"01C", 'F'}, + {"01E(1)", 'F'}, + {"01E(1O", 'F'}, + {"01E(F(", 'F'}, + {"01E(N)", 'F'}, + {"01E(NO", 'F'}, + {"01E(S)", 'F'}, + {"01E(SO", 'F'}, + {"01E(V)", 'F'}, + {"01E(VO", 'F'}, + {"01E1;T", 'F'}, + {"01E1C", 'F'}, + {"01E1O(", 'F'}, + {"01E1OF", 'F'}, + {"01E1OS", 'F'}, + {"01E1OV", 'F'}, + {"01E1T(", 'F'}, + {"01E1T1", 'F'}, + {"01E1TF", 'F'}, + {"01E1TN", 'F'}, + {"01E1TS", 'F'}, + {"01E1TV", 'F'}, + {"01E1UE", 'F'}, + {"01EF()", 'F'}, + {"01EF(1", 'F'}, + {"01EF(F", 'F'}, + {"01EF(N", 'F'}, + {"01EF(S", 'F'}, + {"01EF(V", 'F'}, + {"01EK(1", 'F'}, + {"01EK(E", 'F'}, + {"01EK(F", 'F'}, + {"01EK(N", 'F'}, + {"01EK(S", 'F'}, + {"01EK(V", 'F'}, + {"01EK1;", 'F'}, + {"01EK1C", 'F'}, + {"01EK1O", 'F'}, + {"01EK1T", 'F'}, + {"01EK1U", 'F'}, + {"01EKF(", 'F'}, + {"01EKN;", 'F'}, + {"01EKNC", 'F'}, + {"01EKNE", 'F'}, + {"01EKNT", 'F'}, + {"01EKNU", 'F'}, + {"01EKOK", 'F'}, + {"01EKS;", 'F'}, + {"01EKSC", 'F'}, + {"01EKSO", 'F'}, + {"01EKST", 'F'}, + {"01EKSU", 'F'}, + {"01EKU(", 'F'}, + {"01EKU1", 'F'}, + {"01EKUE", 'F'}, + {"01EKUF", 'F'}, + {"01EKUS", 'F'}, + {"01EKUV", 'F'}, + {"01EKV;", 'F'}, + {"01EKVC", 'F'}, + {"01EKVO", 'F'}, + {"01EKVT", 'F'}, + {"01EKVU", 'F'}, + {"01EN;T", 'F'}, + {"01ENC", 'F'}, + {"01ENEN", 'F'}, + {"01ENO(", 'F'}, + {"01ENOF", 'F'}, + {"01ENOS", 'F'}, + {"01ENOV", 'F'}, + {"01ENT(", 'F'}, + {"01ENT1", 'F'}, + {"01ENTF", 'F'}, + {"01ENTN", 'F'}, + {"01ENTS", 'F'}, + {"01ENTV", 'F'}, + {"01ENUE", 'F'}, + {"01EOKN", 'F'}, + {"01ES;T", 'F'}, + {"01ESC", 'F'}, + {"01ESO(", 'F'}, + {"01ESO1", 'F'}, + {"01ESOF", 'F'}, + {"01ESON", 'F'}, + {"01ESOS", 'F'}, + {"01ESOV", 'F'}, + {"01EST(", 'F'}, + {"01EST1", 'F'}, + {"01ESTF", 'F'}, + {"01ESTN", 'F'}, + {"01ESTS", 'F'}, + {"01ESTV", 'F'}, + {"01ESUE", 'F'}, + {"01EU(1", 'F'}, + {"01EU(F", 'F'}, + {"01EU(N", 'F'}, + {"01EU(S", 'F'}, + {"01EU(V", 'F'}, + {"01EU1,", 'F'}, + {"01EU1C", 'F'}, + {"01EU1O", 'F'}, + {"01EUEF", 'F'}, + {"01EUEK", 'F'}, + {"01EUF(", 'F'}, + {"01EUS,", 'F'}, + {"01EUSC", 'F'}, + {"01EUSO", 'F'}, + {"01EUV,", 'F'}, + {"01EUVC", 'F'}, + {"01EUVO", 'F'}, + {"01EV;T", 'F'}, + {"01EVC", 'F'}, + {"01EVO(", 'F'}, + {"01EVOF", 'F'}, + {"01EVOS", 'F'}, + {"01EVT(", 'F'}, + {"01EVT1", 'F'}, + {"01EVTF", 'F'}, + {"01EVTN", 'F'}, + {"01EVTS", 'F'}, + {"01EVTV", 'F'}, + {"01EVUE", 'F'}, + {"01F()1", 'F'}, + {"01F()F", 'F'}, + {"01F()K", 'F'}, + {"01F()N", 'F'}, + {"01F()O", 'F'}, + {"01F()S", 'F'}, + {"01F()U", 'F'}, + {"01F()V", 'F'}, + {"01F(1)", 'F'}, + {"01F(1N", 'F'}, + {"01F(1O", 'F'}, + {"01F(E(", 'F'}, + {"01F(E1", 'F'}, + {"01F(EF", 'F'}, + {"01F(EK", 'F'}, + {"01F(EN", 'F'}, + {"01F(ES", 'F'}, + {"01F(EV", 'F'}, + {"01F(F(", 'F'}, + {"01F(N)", 'F'}, + {"01F(N,", 'F'}, + {"01F(NO", 'F'}, + {"01F(S)", 'F'}, + {"01F(SO", 'F'}, + {"01F(V)", 'F'}, + {"01F(VO", 'F'}, + {"01K(1O", 'F'}, + {"01K(F(", 'F'}, + {"01K(N)", 'F'}, + {"01K(NO", 'F'}, + {"01K(S)", 'F'}, + {"01K(SO", 'F'}, + {"01K(V)", 'F'}, + {"01K(VO", 'F'}, + {"01K)&(", 'F'}, + {"01K)&1", 'F'}, + {"01K)&F", 'F'}, + {"01K)&N", 'F'}, + {"01K)&S", 'F'}, + {"01K)&V", 'F'}, + {"01K);E", 'F'}, + {"01K);T", 'F'}, + {"01K)B(", 'F'}, + {"01K)B1", 'F'}, + {"01K)BF", 'F'}, + {"01K)BN", 'F'}, + {"01K)BS", 'F'}, + {"01K)BV", 'F'}, + {"01K)E(", 'F'}, + {"01K)E1", 'F'}, + {"01K)EF", 'F'}, + {"01K)EK", 'F'}, + {"01K)EN", 'F'}, + {"01K)ES", 'F'}, + {"01K)EV", 'F'}, + {"01K)F(", 'F'}, + {"01K)O(", 'F'}, + {"01K)OF", 'F'}, + {"01K)UE", 'F'}, + {"01K1", 'F'}, + {"01K1&(", 'F'}, + {"01K1&1", 'F'}, + {"01K1&F", 'F'}, + {"01K1&N", 'F'}, + {"01K1&S", 'F'}, + {"01K1&V", 'F'}, + {"01K1;", 'F'}, + {"01K1;C", 'F'}, + {"01K1;E", 'F'}, + {"01K1;T", 'F'}, + {"01K1B(", 'F'}, + {"01K1B1", 'F'}, + {"01K1BF", 'F'}, + {"01K1BN", 'F'}, + {"01K1BS", 'F'}, + {"01K1BV", 'F'}, + {"01K1C", 'F'}, + {"01K1E(", 'F'}, + {"01K1E1", 'F'}, + {"01K1EF", 'F'}, + {"01K1EK", 'F'}, + {"01K1EN", 'F'}, + {"01K1ES", 'F'}, + {"01K1EV", 'F'}, + {"01K1O(", 'F'}, + {"01K1OF", 'F'}, + {"01K1OS", 'F'}, + {"01K1OV", 'F'}, + {"01K1U(", 'F'}, + {"01K1UE", 'F'}, + {"01KF()", 'F'}, + {"01KF(1", 'F'}, + {"01KF(F", 'F'}, + {"01KF(N", 'F'}, + {"01KF(S", 'F'}, + {"01KF(V", 'F'}, + {"01KN", 'F'}, + {"01KN&(", 'F'}, + {"01KN&1", 'F'}, + {"01KN&F", 'F'}, + {"01KN&N", 'F'}, + {"01KN&S", 'F'}, + {"01KN&V", 'F'}, + {"01KN;", 'F'}, + {"01KN;C", 'F'}, + {"01KN;E", 'F'}, + {"01KN;T", 'F'}, + {"01KNB(", 'F'}, + {"01KNB1", 'F'}, + {"01KNBF", 'F'}, + {"01KNBN", 'F'}, + {"01KNBS", 'F'}, + {"01KNBV", 'F'}, + {"01KNC", 'F'}, + {"01KNE(", 'F'}, + {"01KNE1", 'F'}, + {"01KNEF", 'F'}, + {"01KNEN", 'F'}, + {"01KNES", 'F'}, + {"01KNEV", 'F'}, + {"01KNU(", 'F'}, + {"01KNUE", 'F'}, + {"01KS", 'F'}, + {"01KS&(", 'F'}, + {"01KS&1", 'F'}, + {"01KS&F", 'F'}, + {"01KS&N", 'F'}, + {"01KS&S", 'F'}, + {"01KS&V", 'F'}, + {"01KS;", 'F'}, + {"01KS;C", 'F'}, + {"01KS;E", 'F'}, + {"01KS;T", 'F'}, + {"01KSB(", 'F'}, + {"01KSB1", 'F'}, + {"01KSBF", 'F'}, + {"01KSBN", 'F'}, + {"01KSBS", 'F'}, + {"01KSBV", 'F'}, + {"01KSC", 'F'}, + {"01KSE(", 'F'}, + {"01KSE1", 'F'}, + {"01KSEF", 'F'}, + {"01KSEK", 'F'}, + {"01KSEN", 'F'}, + {"01KSES", 'F'}, + {"01KSEV", 'F'}, + {"01KSO(", 'F'}, + {"01KSO1", 'F'}, + {"01KSOF", 'F'}, + {"01KSON", 'F'}, + {"01KSOS", 'F'}, + {"01KSOV", 'F'}, + {"01KSU(", 'F'}, + {"01KSUE", 'F'}, + {"01KUE(", 'F'}, + {"01KUE1", 'F'}, + {"01KUEF", 'F'}, + {"01KUEK", 'F'}, + {"01KUEN", 'F'}, + {"01KUES", 'F'}, + {"01KUEV", 'F'}, + {"01KV", 'F'}, + {"01KV&(", 'F'}, + {"01KV&1", 'F'}, + {"01KV&F", 'F'}, + {"01KV&N", 'F'}, + {"01KV&S", 'F'}, + {"01KV&V", 'F'}, + {"01KV;", 'F'}, + {"01KV;C", 'F'}, + {"01KV;E", 'F'}, + {"01KV;T", 'F'}, + {"01KVB(", 'F'}, + {"01KVB1", 'F'}, + {"01KVBF", 'F'}, + {"01KVBN", 'F'}, + {"01KVBS", 'F'}, + {"01KVBV", 'F'}, + {"01KVC", 'F'}, + {"01KVE(", 'F'}, + {"01KVE1", 'F'}, + {"01KVEF", 'F'}, + {"01KVEK", 'F'}, + {"01KVEN", 'F'}, + {"01KVES", 'F'}, + {"01KVEV", 'F'}, + {"01KVO(", 'F'}, + {"01KVOF", 'F'}, + {"01KVOS", 'F'}, + {"01KVU(", 'F'}, + {"01KVUE", 'F'}, + {"01N&F(", 'F'}, + {"01N(1O", 'F'}, + {"01N(F(", 'F'}, + {"01N(S)", 'F'}, + {"01N(SO", 'F'}, + {"01N(V)", 'F'}, + {"01N(VO", 'F'}, + {"01N)UE", 'F'}, + {"01N,F(", 'F'}, + {"01NE(1", 'F'}, + {"01NE(F", 'F'}, + {"01NE(N", 'F'}, + {"01NE(S", 'F'}, + {"01NE(V", 'F'}, + {"01NE1C", 'F'}, + {"01NE1O", 'F'}, + {"01NEF(", 'F'}, + {"01NENC", 'F'}, + {"01NENO", 'F'}, + {"01NESC", 'F'}, + {"01NESO", 'F'}, + {"01NEVC", 'F'}, + {"01NEVO", 'F'}, + {"01NU(E", 'F'}, + {"01NUE", 'F'}, + {"01NUE(", 'F'}, + {"01NUE1", 'F'}, + {"01NUE;", 'F'}, + {"01NUEC", 'F'}, + {"01NUEF", 'F'}, + {"01NUEK", 'F'}, + {"01NUEN", 'F'}, + {"01NUES", 'F'}, + {"01NUEV", 'F'}, + {"01O(1&", 'F'}, + {"01O(1)", 'F'}, + {"01O(1,", 'F'}, + {"01O(1O", 'F'}, + {"01O(E(", 'F'}, + {"01O(E1", 'F'}, + {"01O(EE", 'F'}, + {"01O(EF", 'F'}, + {"01O(EK", 'F'}, + {"01O(EN", 'F'}, + {"01O(EO", 'F'}, + {"01O(ES", 'F'}, + {"01O(EV", 'F'}, + {"01O(F(", 'F'}, + {"01O(N&", 'F'}, + {"01O(N)", 'F'}, + {"01O(N,", 'F'}, + {"01O(NO", 'F'}, + {"01O(S&", 'F'}, + {"01O(S)", 'F'}, + {"01O(S,", 'F'}, + {"01O(SO", 'F'}, + {"01O(V&", 'F'}, + {"01O(V)", 'F'}, + {"01O(V,", 'F'}, + {"01O(VO", 'F'}, + {"01OF()", 'F'}, + {"01OF(1", 'F'}, + {"01OF(E", 'F'}, + {"01OF(F", 'F'}, + {"01OF(N", 'F'}, + {"01OF(S", 'F'}, + {"01OF(V", 'F'}, + {"01OK&(", 'F'}, + {"01OK&1", 'F'}, + {"01OK&F", 'F'}, + {"01OK&N", 'F'}, + {"01OK&S", 'F'}, + {"01OK&V", 'F'}, + {"01OK(1", 'F'}, + {"01OK(F", 'F'}, + {"01OK(N", 'F'}, + {"01OK(S", 'F'}, + {"01OK(V", 'F'}, + {"01OK1C", 'F'}, + {"01OK1O", 'F'}, + {"01OKF(", 'F'}, + {"01OKNC", 'F'}, + {"01OKO(", 'F'}, + {"01OKO1", 'F'}, + {"01OKOF", 'F'}, + {"01OKON", 'F'}, + {"01OKOS", 'F'}, + {"01OKOV", 'F'}, + {"01OKSC", 'F'}, + {"01OKSO", 'F'}, + {"01OKVC", 'F'}, + {"01OKVO", 'F'}, + {"01ONSU", 'F'}, + {"01OS&(", 'F'}, + {"01OS&1", 'F'}, + {"01OS&E", 'F'}, + {"01OS&F", 'F'}, + {"01OS&K", 'F'}, + {"01OS&N", 'F'}, + {"01OS&S", 'F'}, + {"01OS&U", 'F'}, + {"01OS&V", 'F'}, + {"01OS(E", 'F'}, + {"01OS(U", 'F'}, + {"01OS)&", 'F'}, + {"01OS),", 'F'}, + {"01OS);", 'F'}, + {"01OS)B", 'F'}, + {"01OS)C", 'F'}, + {"01OS)E", 'F'}, + {"01OS)F", 'F'}, + {"01OS)K", 'F'}, + {"01OS)O", 'F'}, + {"01OS)U", 'F'}, + {"01OS,(", 'F'}, + {"01OS,F", 'F'}, + {"01OS1(", 'F'}, + {"01OS1F", 'F'}, + {"01OS1N", 'F'}, + {"01OS1S", 'F'}, + {"01OS1U", 'F'}, + {"01OS1V", 'F'}, + {"01OS;", 'F'}, + {"01OS;C", 'F'}, + {"01OS;E", 'F'}, + {"01OS;N", 'F'}, + {"01OS;T", 'F'}, + {"01OSA(", 'F'}, + {"01OSAF", 'F'}, + {"01OSAS", 'F'}, + {"01OSAT", 'F'}, + {"01OSAV", 'F'}, + {"01OSB(", 'F'}, + {"01OSB1", 'F'}, + {"01OSBE", 'F'}, + {"01OSBF", 'F'}, + {"01OSBN", 'F'}, + {"01OSBS", 'F'}, + {"01OSBV", 'F'}, + {"01OSC", 'F'}, + {"01OSE(", 'F'}, + {"01OSE1", 'F'}, + {"01OSEF", 'F'}, + {"01OSEK", 'F'}, + {"01OSEN", 'F'}, + {"01OSEO", 'F'}, + {"01OSES", 'F'}, + {"01OSEU", 'F'}, + {"01OSEV", 'F'}, + {"01OSF(", 'F'}, + {"01OSK(", 'F'}, + {"01OSK)", 'F'}, + {"01OSK1", 'F'}, + {"01OSKB", 'F'}, + {"01OSKF", 'F'}, + {"01OSKN", 'F'}, + {"01OSKS", 'F'}, + {"01OSKU", 'F'}, + {"01OSKV", 'F'}, + {"01OST(", 'F'}, + {"01OST1", 'F'}, + {"01OSTE", 'F'}, + {"01OSTF", 'F'}, + {"01OSTN", 'F'}, + {"01OSTS", 'F'}, + {"01OSTT", 'F'}, + {"01OSTV", 'F'}, + {"01OSU", 'F'}, + {"01OSU(", 'F'}, + {"01OSU1", 'F'}, + {"01OSU;", 'F'}, + {"01OSUC", 'F'}, + {"01OSUE", 'F'}, + {"01OSUF", 'F'}, + {"01OSUK", 'F'}, + {"01OSUO", 'F'}, + {"01OSUS", 'F'}, + {"01OSUT", 'F'}, + {"01OSUV", 'F'}, + {"01OSV(", 'F'}, + {"01OSVF", 'F'}, + {"01OSVO", 'F'}, + {"01OSVS", 'F'}, + {"01OSVU", 'F'}, + {"01OU(E", 'F'}, + {"01OUEK", 'F'}, + {"01OUEN", 'F'}, + {"01OV", 'F'}, + {"01OV&(", 'F'}, + {"01OV&1", 'F'}, + {"01OV&E", 'F'}, + {"01OV&F", 'F'}, + {"01OV&K", 'F'}, + {"01OV&N", 'F'}, + {"01OV&S", 'F'}, + {"01OV&U", 'F'}, + {"01OV&V", 'F'}, + {"01OV(E", 'F'}, + {"01OV(U", 'F'}, + {"01OV)&", 'F'}, + {"01OV),", 'F'}, + {"01OV);", 'F'}, + {"01OV)B", 'F'}, + {"01OV)C", 'F'}, + {"01OV)E", 'F'}, + {"01OV)F", 'F'}, + {"01OV)K", 'F'}, + {"01OV)O", 'F'}, + {"01OV)U", 'F'}, + {"01OV,(", 'F'}, + {"01OV,F", 'F'}, + {"01OV;", 'F'}, + {"01OV;C", 'F'}, + {"01OV;E", 'F'}, + {"01OV;N", 'F'}, + {"01OV;T", 'F'}, + {"01OVA(", 'F'}, + {"01OVAF", 'F'}, + {"01OVAS", 'F'}, + {"01OVAT", 'F'}, + {"01OVAV", 'F'}, + {"01OVB(", 'F'}, + {"01OVB1", 'F'}, + {"01OVBE", 'F'}, + {"01OVBF", 'F'}, + {"01OVBN", 'F'}, + {"01OVBS", 'F'}, + {"01OVBV", 'F'}, + {"01OVC", 'F'}, + {"01OVE(", 'F'}, + {"01OVE1", 'F'}, + {"01OVEF", 'F'}, + {"01OVEK", 'F'}, + {"01OVEN", 'F'}, + {"01OVEO", 'F'}, + {"01OVES", 'F'}, + {"01OVEU", 'F'}, + {"01OVEV", 'F'}, + {"01OVF(", 'F'}, + {"01OVK(", 'F'}, + {"01OVK)", 'F'}, + {"01OVK1", 'F'}, + {"01OVKB", 'F'}, + {"01OVKF", 'F'}, + {"01OVKN", 'F'}, + {"01OVKS", 'F'}, + {"01OVKU", 'F'}, + {"01OVKV", 'F'}, + {"01OVO(", 'F'}, + {"01OVOF", 'F'}, + {"01OVOK", 'F'}, + {"01OVOS", 'F'}, + {"01OVOU", 'F'}, + {"01OVS(", 'F'}, + {"01OVS1", 'F'}, + {"01OVSF", 'F'}, + {"01OVSO", 'F'}, + {"01OVSU", 'F'}, + {"01OVSV", 'F'}, + {"01OVT(", 'F'}, + {"01OVT1", 'F'}, + {"01OVTE", 'F'}, + {"01OVTF", 'F'}, + {"01OVTN", 'F'}, + {"01OVTS", 'F'}, + {"01OVTT", 'F'}, + {"01OVTV", 'F'}, + {"01OVU", 'F'}, + {"01OVU(", 'F'}, + {"01OVU1", 'F'}, + {"01OVU;", 'F'}, + {"01OVUC", 'F'}, + {"01OVUE", 'F'}, + {"01OVUF", 'F'}, + {"01OVUK", 'F'}, + {"01OVUO", 'F'}, + {"01OVUS", 'F'}, + {"01OVUT", 'F'}, + {"01OVUV", 'F'}, + {"01SF()", 'F'}, + {"01SF(1", 'F'}, + {"01SF(F", 'F'}, + {"01SF(N", 'F'}, + {"01SF(S", 'F'}, + {"01SF(V", 'F'}, + {"01SUE", 'F'}, + {"01SUE;", 'F'}, + {"01SUEC", 'F'}, + {"01SUEK", 'F'}, + {"01SV", 'F'}, + {"01SV;", 'F'}, + {"01SV;C", 'F'}, + {"01SVC", 'F'}, + {"01SVO(", 'F'}, + {"01SVOF", 'F'}, + {"01SVOS", 'F'}, + {"01T(1)", 'F'}, + {"01T(1O", 'F'}, + {"01T(F(", 'F'}, + {"01T(N)", 'F'}, + {"01T(NO", 'F'}, + {"01T(S)", 'F'}, + {"01T(SO", 'F'}, + {"01T(V)", 'F'}, + {"01T(VO", 'F'}, + {"01T1(F", 'F'}, + {"01T1O(", 'F'}, + {"01T1OF", 'F'}, + {"01T1OS", 'F'}, + {"01T1OV", 'F'}, + {"01TE(1", 'F'}, + {"01TE(F", 'F'}, + {"01TE(N", 'F'}, + {"01TE(S", 'F'}, + {"01TE(V", 'F'}, + {"01TE1N", 'F'}, + {"01TE1O", 'F'}, + {"01TEF(", 'F'}, + {"01TEK(", 'F'}, + {"01TEK1", 'F'}, + {"01TEKF", 'F'}, + {"01TEKN", 'F'}, + {"01TEKS", 'F'}, + {"01TEKV", 'F'}, + {"01TENN", 'F'}, + {"01TENO", 'F'}, + {"01TESN", 'F'}, + {"01TESO", 'F'}, + {"01TEVN", 'F'}, + {"01TEVO", 'F'}, + {"01TF()", 'F'}, + {"01TF(1", 'F'}, + {"01TF(F", 'F'}, + {"01TF(N", 'F'}, + {"01TF(S", 'F'}, + {"01TF(V", 'F'}, + {"01TN(1", 'F'}, + {"01TN(F", 'F'}, + {"01TN(S", 'F'}, + {"01TN(V", 'F'}, + {"01TN1C", 'F'}, + {"01TN1O", 'F'}, + {"01TN;E", 'F'}, + {"01TN;N", 'F'}, + {"01TN;T", 'F'}, + {"01TNE(", 'F'}, + {"01TNE1", 'F'}, + {"01TNEF", 'F'}, + {"01TNEN", 'F'}, + {"01TNES", 'F'}, + {"01TNEV", 'F'}, + {"01TNF(", 'F'}, + {"01TNKN", 'F'}, + {"01TNN:", 'F'}, + {"01TNNC", 'F'}, + {"01TNNO", 'F'}, + {"01TNO(", 'F'}, + {"01TNOF", 'F'}, + {"01TNOS", 'F'}, + {"01TNOV", 'F'}, + {"01TNSC", 'F'}, + {"01TNSO", 'F'}, + {"01TNT(", 'F'}, + {"01TNT1", 'F'}, + {"01TNTF", 'F'}, + {"01TNTN", 'F'}, + {"01TNTS", 'F'}, + {"01TNTV", 'F'}, + {"01TNVC", 'F'}, + {"01TNVO", 'F'}, + {"01TS(F", 'F'}, + {"01TSO(", 'F'}, + {"01TSO1", 'F'}, + {"01TSOF", 'F'}, + {"01TSON", 'F'}, + {"01TSOS", 'F'}, + {"01TSOV", 'F'}, + {"01TTNE", 'F'}, + {"01TTNK", 'F'}, + {"01TTNN", 'F'}, + {"01TTNT", 'F'}, + {"01TV(1", 'F'}, + {"01TV(F", 'F'}, + {"01TVO(", 'F'}, + {"01TVOF", 'F'}, + {"01TVOS", 'F'}, + {"01U", 'F'}, + {"01U(1)", 'F'}, + {"01U(1O", 'F'}, + {"01U(E(", 'F'}, + {"01U(E1", 'F'}, + {"01U(EF", 'F'}, + {"01U(EK", 'F'}, + {"01U(EN", 'F'}, + {"01U(ES", 'F'}, + {"01U(EV", 'F'}, + {"01U(F(", 'F'}, + {"01U(N)", 'F'}, + {"01U(NO", 'F'}, + {"01U(S)", 'F'}, + {"01U(SO", 'F'}, + {"01U(V)", 'F'}, + {"01U(VO", 'F'}, + {"01U1,(", 'F'}, + {"01U1,F", 'F'}, + {"01U1C", 'F'}, + {"01U1O(", 'F'}, + {"01U1OF", 'F'}, + {"01U1OS", 'F'}, + {"01U1OV", 'F'}, + {"01U;", 'F'}, + {"01U;C", 'F'}, + {"01UC", 'F'}, + {"01UE", 'F'}, + {"01UE(1", 'F'}, + {"01UE(E", 'F'}, + {"01UE(F", 'F'}, + {"01UE(N", 'F'}, + {"01UE(O", 'F'}, + {"01UE(S", 'F'}, + {"01UE(V", 'F'}, + {"01UE1", 'F'}, + {"01UE1&", 'F'}, + {"01UE1(", 'F'}, + {"01UE1)", 'F'}, + {"01UE1,", 'F'}, + {"01UE1;", 'F'}, + {"01UE1B", 'F'}, + {"01UE1C", 'F'}, + {"01UE1F", 'F'}, + {"01UE1K", 'F'}, + {"01UE1N", 'F'}, + {"01UE1O", 'F'}, + {"01UE1S", 'F'}, + {"01UE1U", 'F'}, + {"01UE1V", 'F'}, + {"01UE;", 'F'}, + {"01UE;C", 'F'}, + {"01UEC", 'F'}, + {"01UEF", 'F'}, + {"01UEF(", 'F'}, + {"01UEF,", 'F'}, + {"01UEF;", 'F'}, + {"01UEFC", 'F'}, + {"01UEK", 'F'}, + {"01UEK(", 'F'}, + {"01UEK1", 'F'}, + {"01UEK;", 'F'}, + {"01UEKC", 'F'}, + {"01UEKF", 'F'}, + {"01UEKN", 'F'}, + {"01UEKO", 'F'}, + {"01UEKS", 'F'}, + {"01UEKV", 'F'}, + {"01UEN", 'F'}, + {"01UEN&", 'F'}, + {"01UEN(", 'F'}, + {"01UEN)", 'F'}, + {"01UEN,", 'F'}, + {"01UEN1", 'F'}, + {"01UEN;", 'F'}, + {"01UENB", 'F'}, + {"01UENC", 'F'}, + {"01UENF", 'F'}, + {"01UENK", 'F'}, + {"01UENN", 'F'}, + {"01UENO", 'F'}, + {"01UENS", 'F'}, + {"01UENU", 'F'}, + {"01UEOK", 'F'}, + {"01UEON", 'F'}, + {"01UES", 'F'}, + {"01UES&", 'F'}, + {"01UES(", 'F'}, + {"01UES)", 'F'}, + {"01UES,", 'F'}, + {"01UES1", 'F'}, + {"01UES;", 'F'}, + {"01UESB", 'F'}, + {"01UESC", 'F'}, + {"01UESF", 'F'}, + {"01UESK", 'F'}, + {"01UESO", 'F'}, + {"01UESU", 'F'}, + {"01UESV", 'F'}, + {"01UEV", 'F'}, + {"01UEV&", 'F'}, + {"01UEV(", 'F'}, + {"01UEV)", 'F'}, + {"01UEV,", 'F'}, + {"01UEV;", 'F'}, + {"01UEVB", 'F'}, + {"01UEVC", 'F'}, + {"01UEVF", 'F'}, + {"01UEVK", 'F'}, + {"01UEVN", 'F'}, + {"01UEVO", 'F'}, + {"01UEVS", 'F'}, + {"01UEVU", 'F'}, + {"01UF()", 'F'}, + {"01UF(1", 'F'}, + {"01UF(F", 'F'}, + {"01UF(N", 'F'}, + {"01UF(S", 'F'}, + {"01UF(V", 'F'}, + {"01UK(E", 'F'}, + {"01UO(E", 'F'}, + {"01UON(", 'F'}, + {"01UON1", 'F'}, + {"01UONF", 'F'}, + {"01UONS", 'F'}, + {"01US,(", 'F'}, + {"01US,F", 'F'}, + {"01USC", 'F'}, + {"01USO(", 'F'}, + {"01USO1", 'F'}, + {"01USOF", 'F'}, + {"01USON", 'F'}, + {"01USOS", 'F'}, + {"01USOV", 'F'}, + {"01UTN(", 'F'}, + {"01UTN1", 'F'}, + {"01UTNF", 'F'}, + {"01UTNN", 'F'}, + {"01UTNS", 'F'}, + {"01UTNV", 'F'}, + {"01UV,(", 'F'}, + {"01UV,F", 'F'}, + {"01UVC", 'F'}, + {"01UVO(", 'F'}, + {"01UVOF", 'F'}, + {"01UVOS", 'F'}, + {"01VF()", 'F'}, + {"01VF(1", 'F'}, + {"01VF(F", 'F'}, + {"01VF(N", 'F'}, + {"01VF(S", 'F'}, + {"01VF(V", 'F'}, + {"01VO(1", 'F'}, + {"01VO(F", 'F'}, + {"01VO(N", 'F'}, + {"01VO(S", 'F'}, + {"01VO(V", 'F'}, + {"01VOF(", 'F'}, + {"01VOS(", 'F'}, + {"01VOS1", 'F'}, + {"01VOSF", 'F'}, + {"01VOSU", 'F'}, + {"01VOSV", 'F'}, + {"01VS", 'F'}, + {"01VS;", 'F'}, + {"01VS;C", 'F'}, + {"01VSC", 'F'}, + {"01VSO(", 'F'}, + {"01VSO1", 'F'}, + {"01VSOF", 'F'}, + {"01VSON", 'F'}, + {"01VSOS", 'F'}, + {"01VSOV", 'F'}, + {"01VUE", 'F'}, + {"01VUE;", 'F'}, + {"01VUEC", 'F'}, + {"01VUEK", 'F'}, + {"0;T(EF", 'F'}, + {"0;T(EK", 'F'}, + {"0;TKNC", 'F'}, + {"0E(1&(", 'F'}, + {"0E(1&1", 'F'}, + {"0E(1&F", 'F'}, + {"0E(1&N", 'F'}, + {"0E(1&S", 'F'}, + {"0E(1&V", 'F'}, + {"0E(1)&", 'F'}, + {"0E(1),", 'F'}, + {"0E(1)1", 'F'}, + {"0E(1);", 'F'}, + {"0E(1)B", 'F'}, + {"0E(1)C", 'F'}, + {"0E(1)F", 'F'}, + {"0E(1)K", 'F'}, + {"0E(1)N", 'F'}, + {"0E(1)O", 'F'}, + {"0E(1)S", 'F'}, + {"0E(1)U", 'F'}, + {"0E(1)V", 'F'}, + {"0E(1,F", 'F'}, + {"0E(1F(", 'F'}, + {"0E(1N)", 'F'}, + {"0E(1O(", 'F'}, + {"0E(1OF", 'F'}, + {"0E(1OS", 'F'}, + {"0E(1OV", 'F'}, + {"0E(1S)", 'F'}, + {"0E(1V)", 'F'}, + {"0E(1VO", 'F'}, + {"0E(E(1", 'F'}, + {"0E(E(E", 'F'}, + {"0E(E(F", 'F'}, + {"0E(E(N", 'F'}, + {"0E(E(S", 'F'}, + {"0E(E(V", 'F'}, + {"0E(E1&", 'F'}, + {"0E(E1)", 'F'}, + {"0E(E1O", 'F'}, + {"0E(EF(", 'F'}, + {"0E(EK(", 'F'}, + {"0E(EK1", 'F'}, + {"0E(EKF", 'F'}, + {"0E(EKN", 'F'}, + {"0E(EKS", 'F'}, + {"0E(EKV", 'F'}, + {"0E(EN&", 'F'}, + {"0E(EN)", 'F'}, + {"0E(ENO", 'F'}, + {"0E(ES&", 'F'}, + {"0E(ES)", 'F'}, + {"0E(ESO", 'F'}, + {"0E(EV&", 'F'}, + {"0E(EV)", 'F'}, + {"0E(EVO", 'F'}, + {"0E(F()", 'F'}, + {"0E(F(1", 'F'}, + {"0E(F(E", 'F'}, + {"0E(F(F", 'F'}, + {"0E(F(N", 'F'}, + {"0E(F(S", 'F'}, + {"0E(F(V", 'F'}, + {"0E(N&(", 'F'}, + {"0E(N&1", 'F'}, + {"0E(N&F", 'F'}, + {"0E(N&N", 'F'}, + {"0E(N&S", 'F'}, + {"0E(N&V", 'F'}, + {"0E(N(1", 'F'}, + {"0E(N(F", 'F'}, + {"0E(N(S", 'F'}, + {"0E(N(V", 'F'}, + {"0E(N)&", 'F'}, + {"0E(N),", 'F'}, + {"0E(N)1", 'F'}, + {"0E(N);", 'F'}, + {"0E(N)B", 'F'}, + {"0E(N)C", 'F'}, + {"0E(N)F", 'F'}, + {"0E(N)K", 'F'}, + {"0E(N)N", 'F'}, + {"0E(N)O", 'F'}, + {"0E(N)S", 'F'}, + {"0E(N)U", 'F'}, + {"0E(N)V", 'F'}, + {"0E(N,F", 'F'}, + {"0E(N1)", 'F'}, + {"0E(N1O", 'F'}, + {"0E(NF(", 'F'}, + {"0E(NO(", 'F'}, + {"0E(NOF", 'F'}, + {"0E(NOS", 'F'}, + {"0E(NOV", 'F'}, + {"0E(S&(", 'F'}, + {"0E(S&1", 'F'}, + {"0E(S&F", 'F'}, + {"0E(S&N", 'F'}, + {"0E(S&S", 'F'}, + {"0E(S&V", 'F'}, + {"0E(S)&", 'F'}, + {"0E(S),", 'F'}, + {"0E(S)1", 'F'}, + {"0E(S);", 'F'}, + {"0E(S)B", 'F'}, + {"0E(S)C", 'F'}, + {"0E(S)F", 'F'}, + {"0E(S)K", 'F'}, + {"0E(S)N", 'F'}, + {"0E(S)O", 'F'}, + {"0E(S)S", 'F'}, + {"0E(S)U", 'F'}, + {"0E(S)V", 'F'}, + {"0E(S,F", 'F'}, + {"0E(S1)", 'F'}, + {"0E(SF(", 'F'}, + {"0E(SO(", 'F'}, + {"0E(SO1", 'F'}, + {"0E(SOF", 'F'}, + {"0E(SON", 'F'}, + {"0E(SOS", 'F'}, + {"0E(SOV", 'F'}, + {"0E(SV)", 'F'}, + {"0E(SVO", 'F'}, + {"0E(V&(", 'F'}, + {"0E(V&1", 'F'}, + {"0E(V&F", 'F'}, + {"0E(V&N", 'F'}, + {"0E(V&S", 'F'}, + {"0E(V&V", 'F'}, + {"0E(V)&", 'F'}, + {"0E(V),", 'F'}, + {"0E(V)1", 'F'}, + {"0E(V);", 'F'}, + {"0E(V)B", 'F'}, + {"0E(V)C", 'F'}, + {"0E(V)F", 'F'}, + {"0E(V)K", 'F'}, + {"0E(V)N", 'F'}, + {"0E(V)O", 'F'}, + {"0E(V)S", 'F'}, + {"0E(V)U", 'F'}, + {"0E(V)V", 'F'}, + {"0E(V,F", 'F'}, + {"0E(VF(", 'F'}, + {"0E(VO(", 'F'}, + {"0E(VOF", 'F'}, + {"0E(VOS", 'F'}, + {"0E(VS)", 'F'}, + {"0E(VSO", 'F'}, + {"0E1&(1", 'F'}, + {"0E1&(E", 'F'}, + {"0E1&(F", 'F'}, + {"0E1&(N", 'F'}, + {"0E1&(S", 'F'}, + {"0E1&(V", 'F'}, + {"0E1&1)", 'F'}, + {"0E1&1O", 'F'}, + {"0E1&F(", 'F'}, + {"0E1&N)", 'F'}, + {"0E1&NO", 'F'}, + {"0E1&S)", 'F'}, + {"0E1&SO", 'F'}, + {"0E1&V)", 'F'}, + {"0E1&VO", 'F'}, + {"0E1)", 'F'}, + {"0E1)&(", 'F'}, + {"0E1)&1", 'F'}, + {"0E1)&F", 'F'}, + {"0E1)&N", 'F'}, + {"0E1)&S", 'F'}, + {"0E1)&V", 'F'}, + {"0E1);", 'F'}, + {"0E1);(", 'F'}, + {"0E1);C", 'F'}, + {"0E1);E", 'F'}, + {"0E1);T", 'F'}, + {"0E1)C", 'F'}, + {"0E1)KN", 'F'}, + {"0E1)O(", 'F'}, + {"0E1)O1", 'F'}, + {"0E1)OF", 'F'}, + {"0E1)ON", 'F'}, + {"0E1)OS", 'F'}, + {"0E1)OV", 'F'}, + {"0E1)UE", 'F'}, + {"0E1,(1", 'F'}, + {"0E1,(F", 'F'}, + {"0E1,(N", 'F'}, + {"0E1,(S", 'F'}, + {"0E1,(V", 'F'}, + {"0E1,F(", 'F'}, + {"0E1;(E", 'F'}, + {"0E1B(1", 'F'}, + {"0E1B(F", 'F'}, + {"0E1B(N", 'F'}, + {"0E1B(S", 'F'}, + {"0E1B(V", 'F'}, + {"0E1B1)", 'F'}, + {"0E1B1O", 'F'}, + {"0E1BF(", 'F'}, + {"0E1BN)", 'F'}, + {"0E1BNO", 'F'}, + {"0E1BS)", 'F'}, + {"0E1BSO", 'F'}, + {"0E1BV)", 'F'}, + {"0E1BVO", 'F'}, + {"0E1F()", 'F'}, + {"0E1F(1", 'F'}, + {"0E1F(F", 'F'}, + {"0E1F(N", 'F'}, + {"0E1F(S", 'F'}, + {"0E1F(V", 'F'}, + {"0E1K(1", 'F'}, + {"0E1K(E", 'F'}, + {"0E1K(F", 'F'}, + {"0E1K(N", 'F'}, + {"0E1K(S", 'F'}, + {"0E1K(V", 'F'}, + {"0E1K1)", 'F'}, + {"0E1K1K", 'F'}, + {"0E1K1O", 'F'}, + {"0E1KF(", 'F'}, + {"0E1KN", 'F'}, + {"0E1KN)", 'F'}, + {"0E1KN;", 'F'}, + {"0E1KNC", 'F'}, + {"0E1KNK", 'F'}, + {"0E1KNU", 'F'}, + {"0E1KS)", 'F'}, + {"0E1KSK", 'F'}, + {"0E1KSO", 'F'}, + {"0E1KV)", 'F'}, + {"0E1KVK", 'F'}, + {"0E1KVO", 'F'}, + {"0E1N)U", 'F'}, + {"0E1N;", 'F'}, + {"0E1N;C", 'F'}, + {"0E1NC", 'F'}, + {"0E1NKN", 'F'}, + {"0E1O(1", 'F'}, + {"0E1O(E", 'F'}, + {"0E1O(F", 'F'}, + {"0E1O(N", 'F'}, + {"0E1O(S", 'F'}, + {"0E1O(V", 'F'}, + {"0E1OF(", 'F'}, + {"0E1OS&", 'F'}, + {"0E1OS(", 'F'}, + {"0E1OS)", 'F'}, + {"0E1OS,", 'F'}, + {"0E1OS1", 'F'}, + {"0E1OS;", 'F'}, + {"0E1OSB", 'F'}, + {"0E1OSF", 'F'}, + {"0E1OSK", 'F'}, + {"0E1OSU", 'F'}, + {"0E1OSV", 'F'}, + {"0E1OV&", 'F'}, + {"0E1OV(", 'F'}, + {"0E1OV)", 'F'}, + {"0E1OV,", 'F'}, + {"0E1OV;", 'F'}, + {"0E1OVB", 'F'}, + {"0E1OVF", 'F'}, + {"0E1OVK", 'F'}, + {"0E1OVO", 'F'}, + {"0E1OVS", 'F'}, + {"0E1OVU", 'F'}, + {"0E1S;", 'F'}, + {"0E1S;C", 'F'}, + {"0E1SC", 'F'}, + {"0E1U(E", 'F'}, + {"0E1UE(", 'F'}, + {"0E1UE1", 'F'}, + {"0E1UEF", 'F'}, + {"0E1UEK", 'F'}, + {"0E1UEN", 'F'}, + {"0E1UES", 'F'}, + {"0E1UEV", 'F'}, + {"0E1V", 'F'}, + {"0E1V;", 'F'}, + {"0E1V;C", 'F'}, + {"0E1VC", 'F'}, + {"0E1VO(", 'F'}, + {"0E1VOF", 'F'}, + {"0E1VOS", 'F'}, + {"0EE(F(", 'F'}, + {"0EEK(F", 'F'}, + {"0EF()&", 'F'}, + {"0EF(),", 'F'}, + {"0EF()1", 'F'}, + {"0EF();", 'F'}, + {"0EF()B", 'F'}, + {"0EF()F", 'F'}, + {"0EF()K", 'F'}, + {"0EF()N", 'F'}, + {"0EF()O", 'F'}, + {"0EF()S", 'F'}, + {"0EF()U", 'F'}, + {"0EF()V", 'F'}, + {"0EF(1&", 'F'}, + {"0EF(1)", 'F'}, + {"0EF(1,", 'F'}, + {"0EF(1O", 'F'}, + {"0EF(E(", 'F'}, + {"0EF(E1", 'F'}, + {"0EF(EF", 'F'}, + {"0EF(EK", 'F'}, + {"0EF(EN", 'F'}, + {"0EF(ES", 'F'}, + {"0EF(EV", 'F'}, + {"0EF(F(", 'F'}, + {"0EF(N&", 'F'}, + {"0EF(N)", 'F'}, + {"0EF(N,", 'F'}, + {"0EF(NO", 'F'}, + {"0EF(O)", 'F'}, + {"0EF(S&", 'F'}, + {"0EF(S)", 'F'}, + {"0EF(S,", 'F'}, + {"0EF(SO", 'F'}, + {"0EF(V&", 'F'}, + {"0EF(V)", 'F'}, + {"0EF(V,", 'F'}, + {"0EF(VO", 'F'}, + {"0EK(1&", 'F'}, + {"0EK(1(", 'F'}, + {"0EK(1)", 'F'}, + {"0EK(1,", 'F'}, + {"0EK(1F", 'F'}, + {"0EK(1N", 'F'}, + {"0EK(1O", 'F'}, + {"0EK(1S", 'F'}, + {"0EK(1V", 'F'}, + {"0EK(E(", 'F'}, + {"0EK(E1", 'F'}, + {"0EK(EF", 'F'}, + {"0EK(EK", 'F'}, + {"0EK(EN", 'F'}, + {"0EK(ES", 'F'}, + {"0EK(EV", 'F'}, + {"0EK(F(", 'F'}, + {"0EK(N&", 'F'}, + {"0EK(N(", 'F'}, + {"0EK(N)", 'F'}, + {"0EK(N,", 'F'}, + {"0EK(N1", 'F'}, + {"0EK(NF", 'F'}, + {"0EK(NO", 'F'}, + {"0EK(S&", 'F'}, + {"0EK(S(", 'F'}, + {"0EK(S)", 'F'}, + {"0EK(S,", 'F'}, + {"0EK(S1", 'F'}, + {"0EK(SF", 'F'}, + {"0EK(SO", 'F'}, + {"0EK(SV", 'F'}, + {"0EK(V&", 'F'}, + {"0EK(V(", 'F'}, + {"0EK(V)", 'F'}, + {"0EK(V,", 'F'}, + {"0EK(VF", 'F'}, + {"0EK(VO", 'F'}, + {"0EK(VS", 'F'}, + {"0EK1&(", 'F'}, + {"0EK1&1", 'F'}, + {"0EK1&F", 'F'}, + {"0EK1&N", 'F'}, + {"0EK1&S", 'F'}, + {"0EK1&V", 'F'}, + {"0EK1)", 'F'}, + {"0EK1)&", 'F'}, + {"0EK1);", 'F'}, + {"0EK1)C", 'F'}, + {"0EK1)K", 'F'}, + {"0EK1)O", 'F'}, + {"0EK1)U", 'F'}, + {"0EK1,(", 'F'}, + {"0EK1,F", 'F'}, + {"0EK1;(", 'F'}, + {"0EK1B(", 'F'}, + {"0EK1B1", 'F'}, + {"0EK1BF", 'F'}, + {"0EK1BN", 'F'}, + {"0EK1BS", 'F'}, + {"0EK1BV", 'F'}, + {"0EK1F(", 'F'}, + {"0EK1K(", 'F'}, + {"0EK1K1", 'F'}, + {"0EK1KF", 'F'}, + {"0EK1KN", 'F'}, + {"0EK1KS", 'F'}, + {"0EK1KV", 'F'}, + {"0EK1N", 'F'}, + {"0EK1N)", 'F'}, + {"0EK1N;", 'F'}, + {"0EK1NC", 'F'}, + {"0EK1NK", 'F'}, + {"0EK1O(", 'F'}, + {"0EK1OF", 'F'}, + {"0EK1OS", 'F'}, + {"0EK1OV", 'F'}, + {"0EK1S", 'F'}, + {"0EK1S;", 'F'}, + {"0EK1SC", 'F'}, + {"0EK1SF", 'F'}, + {"0EK1SK", 'F'}, + {"0EK1U(", 'F'}, + {"0EK1UE", 'F'}, + {"0EK1V", 'F'}, + {"0EK1V;", 'F'}, + {"0EK1VC", 'F'}, + {"0EK1VF", 'F'}, + {"0EK1VK", 'F'}, + {"0EK1VO", 'F'}, + {"0EKE(F", 'F'}, + {"0EKEK(", 'F'}, + {"0EKF()", 'F'}, + {"0EKF(1", 'F'}, + {"0EKF(E", 'F'}, + {"0EKF(F", 'F'}, + {"0EKF(N", 'F'}, + {"0EKF(O", 'F'}, + {"0EKF(S", 'F'}, + {"0EKF(V", 'F'}, + {"0EKN&(", 'F'}, + {"0EKN&1", 'F'}, + {"0EKN&F", 'F'}, + {"0EKN&N", 'F'}, + {"0EKN&S", 'F'}, + {"0EKN&V", 'F'}, + {"0EKN(1", 'F'}, + {"0EKN(F", 'F'}, + {"0EKN(S", 'F'}, + {"0EKN(V", 'F'}, + {"0EKN)", 'F'}, + {"0EKN)&", 'F'}, + {"0EKN);", 'F'}, + {"0EKN)C", 'F'}, + {"0EKN)K", 'F'}, + {"0EKN)O", 'F'}, + {"0EKN)U", 'F'}, + {"0EKN,(", 'F'}, + {"0EKN,F", 'F'}, + {"0EKN1", 'F'}, + {"0EKN1;", 'F'}, + {"0EKN1C", 'F'}, + {"0EKN1K", 'F'}, + {"0EKN1O", 'F'}, + {"0EKN;(", 'F'}, + {"0EKNB(", 'F'}, + {"0EKNB1", 'F'}, + {"0EKNBF", 'F'}, + {"0EKNBN", 'F'}, + {"0EKNBS", 'F'}, + {"0EKNBV", 'F'}, + {"0EKNF(", 'F'}, + {"0EKNK(", 'F'}, + {"0EKNK1", 'F'}, + {"0EKNKF", 'F'}, + {"0EKNKN", 'F'}, + {"0EKNKS", 'F'}, + {"0EKNKV", 'F'}, + {"0EKNU(", 'F'}, + {"0EKNUE", 'F'}, + {"0EKO(1", 'F'}, + {"0EKO(F", 'F'}, + {"0EKO(N", 'F'}, + {"0EKO(S", 'F'}, + {"0EKO(V", 'F'}, + {"0EKOK(", 'F'}, + {"0EKOKN", 'F'}, + {"0EKS&(", 'F'}, + {"0EKS&1", 'F'}, + {"0EKS&F", 'F'}, + {"0EKS&N", 'F'}, + {"0EKS&S", 'F'}, + {"0EKS&V", 'F'}, + {"0EKS)", 'F'}, + {"0EKS)&", 'F'}, + {"0EKS);", 'F'}, + {"0EKS)C", 'F'}, + {"0EKS)K", 'F'}, + {"0EKS)O", 'F'}, + {"0EKS)U", 'F'}, + {"0EKS,(", 'F'}, + {"0EKS,F", 'F'}, + {"0EKS1", 'F'}, + {"0EKS1;", 'F'}, + {"0EKS1C", 'F'}, + {"0EKS1F", 'F'}, + {"0EKS1K", 'F'}, + {"0EKS;(", 'F'}, + {"0EKSB(", 'F'}, + {"0EKSB1", 'F'}, + {"0EKSBF", 'F'}, + {"0EKSBN", 'F'}, + {"0EKSBS", 'F'}, + {"0EKSBV", 'F'}, + {"0EKSF(", 'F'}, + {"0EKSK(", 'F'}, + {"0EKSK1", 'F'}, + {"0EKSKF", 'F'}, + {"0EKSKN", 'F'}, + {"0EKSKS", 'F'}, + {"0EKSKV", 'F'}, + {"0EKSO(", 'F'}, + {"0EKSO1", 'F'}, + {"0EKSOF", 'F'}, + {"0EKSON", 'F'}, + {"0EKSOS", 'F'}, + {"0EKSOV", 'F'}, + {"0EKSU(", 'F'}, + {"0EKSUE", 'F'}, + {"0EKSV", 'F'}, + {"0EKSV;", 'F'}, + {"0EKSVC", 'F'}, + {"0EKSVF", 'F'}, + {"0EKSVK", 'F'}, + {"0EKSVO", 'F'}, + {"0EKV&(", 'F'}, + {"0EKV&1", 'F'}, + {"0EKV&F", 'F'}, + {"0EKV&N", 'F'}, + {"0EKV&S", 'F'}, + {"0EKV&V", 'F'}, + {"0EKV)", 'F'}, + {"0EKV)&", 'F'}, + {"0EKV);", 'F'}, + {"0EKV)C", 'F'}, + {"0EKV)K", 'F'}, + {"0EKV)O", 'F'}, + {"0EKV)U", 'F'}, + {"0EKV,(", 'F'}, + {"0EKV,F", 'F'}, + {"0EKV;(", 'F'}, + {"0EKVB(", 'F'}, + {"0EKVB1", 'F'}, + {"0EKVBF", 'F'}, + {"0EKVBN", 'F'}, + {"0EKVBS", 'F'}, + {"0EKVBV", 'F'}, + {"0EKVF(", 'F'}, + {"0EKVK(", 'F'}, + {"0EKVK1", 'F'}, + {"0EKVKF", 'F'}, + {"0EKVKN", 'F'}, + {"0EKVKS", 'F'}, + {"0EKVKV", 'F'}, + {"0EKVO(", 'F'}, + {"0EKVOF", 'F'}, + {"0EKVOS", 'F'}, + {"0EKVS", 'F'}, + {"0EKVS;", 'F'}, + {"0EKVSC", 'F'}, + {"0EKVSF", 'F'}, + {"0EKVSK", 'F'}, + {"0EKVSO", 'F'}, + {"0EKVU(", 'F'}, + {"0EKVUE", 'F'}, + {"0EN&(1", 'F'}, + {"0EN&(E", 'F'}, + {"0EN&(F", 'F'}, + {"0EN&(N", 'F'}, + {"0EN&(S", 'F'}, + {"0EN&(V", 'F'}, + {"0EN&1)", 'F'}, + {"0EN&1O", 'F'}, + {"0EN&F(", 'F'}, + {"0EN&N)", 'F'}, + {"0EN&NO", 'F'}, + {"0EN&S)", 'F'}, + {"0EN&SO", 'F'}, + {"0EN&V)", 'F'}, + {"0EN&VO", 'F'}, + {"0EN(1O", 'F'}, + {"0EN(F(", 'F'}, + {"0EN(S)", 'F'}, + {"0EN(SO", 'F'}, + {"0EN(V)", 'F'}, + {"0EN(VO", 'F'}, + {"0EN)", 'F'}, + {"0EN)&(", 'F'}, + {"0EN)&1", 'F'}, + {"0EN)&F", 'F'}, + {"0EN)&N", 'F'}, + {"0EN)&S", 'F'}, + {"0EN)&V", 'F'}, + {"0EN);", 'F'}, + {"0EN);(", 'F'}, + {"0EN);C", 'F'}, + {"0EN);E", 'F'}, + {"0EN);T", 'F'}, + {"0EN)C", 'F'}, + {"0EN)KN", 'F'}, + {"0EN)O(", 'F'}, + {"0EN)O1", 'F'}, + {"0EN)OF", 'F'}, + {"0EN)ON", 'F'}, + {"0EN)OS", 'F'}, + {"0EN)OV", 'F'}, + {"0EN)UE", 'F'}, + {"0EN,(1", 'F'}, + {"0EN,(F", 'F'}, + {"0EN,(N", 'F'}, + {"0EN,(S", 'F'}, + {"0EN,(V", 'F'}, + {"0EN,F(", 'F'}, + {"0EN1;", 'F'}, + {"0EN1;C", 'F'}, + {"0EN1O(", 'F'}, + {"0EN1OF", 'F'}, + {"0EN1OS", 'F'}, + {"0EN1OV", 'F'}, + {"0EN;(E", 'F'}, + {"0ENB(1", 'F'}, + {"0ENB(F", 'F'}, + {"0ENB(N", 'F'}, + {"0ENB(S", 'F'}, + {"0ENB(V", 'F'}, + {"0ENB1)", 'F'}, + {"0ENB1O", 'F'}, + {"0ENBF(", 'F'}, + {"0ENBN)", 'F'}, + {"0ENBNO", 'F'}, + {"0ENBS)", 'F'}, + {"0ENBSO", 'F'}, + {"0ENBV)", 'F'}, + {"0ENBVO", 'F'}, + {"0ENF()", 'F'}, + {"0ENF(1", 'F'}, + {"0ENF(F", 'F'}, + {"0ENF(N", 'F'}, + {"0ENF(S", 'F'}, + {"0ENF(V", 'F'}, + {"0ENK(1", 'F'}, + {"0ENK(E", 'F'}, + {"0ENK(F", 'F'}, + {"0ENK(N", 'F'}, + {"0ENK(S", 'F'}, + {"0ENK(V", 'F'}, + {"0ENK1)", 'F'}, + {"0ENK1K", 'F'}, + {"0ENK1O", 'F'}, + {"0ENKF(", 'F'}, + {"0ENKN)", 'F'}, + {"0ENKN,", 'F'}, + {"0ENKN;", 'F'}, + {"0ENKNB", 'F'}, + {"0ENKNC", 'F'}, + {"0ENKNK", 'F'}, + {"0ENKNU", 'F'}, + {"0ENKS)", 'F'}, + {"0ENKSK", 'F'}, + {"0ENKSO", 'F'}, + {"0ENKV)", 'F'}, + {"0ENKVK", 'F'}, + {"0ENKVO", 'F'}, + {"0ENO(1", 'F'}, + {"0ENO(E", 'F'}, + {"0ENO(F", 'F'}, + {"0ENO(N", 'F'}, + {"0ENO(S", 'F'}, + {"0ENO(V", 'F'}, + {"0ENOF(", 'F'}, + {"0ENOS&", 'F'}, + {"0ENOS(", 'F'}, + {"0ENOS)", 'F'}, + {"0ENOS,", 'F'}, + {"0ENOS1", 'F'}, + {"0ENOS;", 'F'}, + {"0ENOSB", 'F'}, + {"0ENOSF", 'F'}, + {"0ENOSK", 'F'}, + {"0ENOSU", 'F'}, + {"0ENOSV", 'F'}, + {"0ENOV&", 'F'}, + {"0ENOV(", 'F'}, + {"0ENOV)", 'F'}, + {"0ENOV,", 'F'}, + {"0ENOV;", 'F'}, + {"0ENOVB", 'F'}, + {"0ENOVF", 'F'}, + {"0ENOVK", 'F'}, + {"0ENOVO", 'F'}, + {"0ENOVS", 'F'}, + {"0ENOVU", 'F'}, + {"0ENU(E", 'F'}, + {"0ENUE(", 'F'}, + {"0ENUE1", 'F'}, + {"0ENUEF", 'F'}, + {"0ENUEK", 'F'}, + {"0ENUEN", 'F'}, + {"0ENUES", 'F'}, + {"0ENUEV", 'F'}, + {"0EOK(E", 'F'}, + {"0EOKNK", 'F'}, + {"0ES&(1", 'F'}, + {"0ES&(E", 'F'}, + {"0ES&(F", 'F'}, + {"0ES&(N", 'F'}, + {"0ES&(S", 'F'}, + {"0ES&(V", 'F'}, + {"0ES&1)", 'F'}, + {"0ES&1O", 'F'}, + {"0ES&F(", 'F'}, + {"0ES&N)", 'F'}, + {"0ES&NO", 'F'}, + {"0ES&S)", 'F'}, + {"0ES&SO", 'F'}, + {"0ES&V)", 'F'}, + {"0ES&VO", 'F'}, + {"0ES)", 'F'}, + {"0ES)&(", 'F'}, + {"0ES)&1", 'F'}, + {"0ES)&F", 'F'}, + {"0ES)&N", 'F'}, + {"0ES)&S", 'F'}, + {"0ES)&V", 'F'}, + {"0ES);", 'F'}, + {"0ES);(", 'F'}, + {"0ES);C", 'F'}, + {"0ES);E", 'F'}, + {"0ES);T", 'F'}, + {"0ES)C", 'F'}, + {"0ES)KN", 'F'}, + {"0ES)O(", 'F'}, + {"0ES)O1", 'F'}, + {"0ES)OF", 'F'}, + {"0ES)ON", 'F'}, + {"0ES)OS", 'F'}, + {"0ES)OV", 'F'}, + {"0ES)UE", 'F'}, + {"0ES,(1", 'F'}, + {"0ES,(F", 'F'}, + {"0ES,(N", 'F'}, + {"0ES,(S", 'F'}, + {"0ES,(V", 'F'}, + {"0ES,F(", 'F'}, + {"0ES1", 'F'}, + {"0ES1;", 'F'}, + {"0ES1;C", 'F'}, + {"0ES1C", 'F'}, + {"0ES;(E", 'F'}, + {"0ESB(1", 'F'}, + {"0ESB(F", 'F'}, + {"0ESB(N", 'F'}, + {"0ESB(S", 'F'}, + {"0ESB(V", 'F'}, + {"0ESB1)", 'F'}, + {"0ESB1O", 'F'}, + {"0ESBF(", 'F'}, + {"0ESBN)", 'F'}, + {"0ESBNO", 'F'}, + {"0ESBS)", 'F'}, + {"0ESBSO", 'F'}, + {"0ESBV)", 'F'}, + {"0ESBVO", 'F'}, + {"0ESF()", 'F'}, + {"0ESF(1", 'F'}, + {"0ESF(F", 'F'}, + {"0ESF(N", 'F'}, + {"0ESF(S", 'F'}, + {"0ESF(V", 'F'}, + {"0ESK(1", 'F'}, + {"0ESK(E", 'F'}, + {"0ESK(F", 'F'}, + {"0ESK(N", 'F'}, + {"0ESK(S", 'F'}, + {"0ESK(V", 'F'}, + {"0ESK1)", 'F'}, + {"0ESK1K", 'F'}, + {"0ESK1O", 'F'}, + {"0ESKF(", 'F'}, + {"0ESKN", 'F'}, + {"0ESKN)", 'F'}, + {"0ESKN;", 'F'}, + {"0ESKNC", 'F'}, + {"0ESKNK", 'F'}, + {"0ESKNU", 'F'}, + {"0ESKS)", 'F'}, + {"0ESKSK", 'F'}, + {"0ESKSO", 'F'}, + {"0ESKV)", 'F'}, + {"0ESKVK", 'F'}, + {"0ESKVO", 'F'}, + {"0ESO(1", 'F'}, + {"0ESO(E", 'F'}, + {"0ESO(F", 'F'}, + {"0ESO(N", 'F'}, + {"0ESO(S", 'F'}, + {"0ESO(V", 'F'}, + {"0ESO1&", 'F'}, + {"0ESO1(", 'F'}, + {"0ESO1)", 'F'}, + {"0ESO1,", 'F'}, + {"0ESO1;", 'F'}, + {"0ESO1B", 'F'}, + {"0ESO1F", 'F'}, + {"0ESO1K", 'F'}, + {"0ESO1N", 'F'}, + {"0ESO1S", 'F'}, + {"0ESO1U", 'F'}, + {"0ESO1V", 'F'}, + {"0ESOF(", 'F'}, + {"0ESON&", 'F'}, + {"0ESON(", 'F'}, + {"0ESON)", 'F'}, + {"0ESON,", 'F'}, + {"0ESON1", 'F'}, + {"0ESON;", 'F'}, + {"0ESONB", 'F'}, + {"0ESONF", 'F'}, + {"0ESONK", 'F'}, + {"0ESONU", 'F'}, + {"0ESOS&", 'F'}, + {"0ESOS(", 'F'}, + {"0ESOS)", 'F'}, + {"0ESOS,", 'F'}, + {"0ESOS1", 'F'}, + {"0ESOS;", 'F'}, + {"0ESOSB", 'F'}, + {"0ESOSF", 'F'}, + {"0ESOSK", 'F'}, + {"0ESOSU", 'F'}, + {"0ESOSV", 'F'}, + {"0ESOV&", 'F'}, + {"0ESOV(", 'F'}, + {"0ESOV)", 'F'}, + {"0ESOV,", 'F'}, + {"0ESOV;", 'F'}, + {"0ESOVB", 'F'}, + {"0ESOVF", 'F'}, + {"0ESOVK", 'F'}, + {"0ESOVO", 'F'}, + {"0ESOVS", 'F'}, + {"0ESOVU", 'F'}, + {"0ESU(E", 'F'}, + {"0ESUE(", 'F'}, + {"0ESUE1", 'F'}, + {"0ESUEF", 'F'}, + {"0ESUEK", 'F'}, + {"0ESUEN", 'F'}, + {"0ESUES", 'F'}, + {"0ESUEV", 'F'}, + {"0ESV", 'F'}, + {"0ESV;", 'F'}, + {"0ESV;C", 'F'}, + {"0ESVC", 'F'}, + {"0ESVO(", 'F'}, + {"0ESVOF", 'F'}, + {"0ESVOS", 'F'}, + {"0EV&(1", 'F'}, + {"0EV&(E", 'F'}, + {"0EV&(F", 'F'}, + {"0EV&(N", 'F'}, + {"0EV&(S", 'F'}, + {"0EV&(V", 'F'}, + {"0EV&1)", 'F'}, + {"0EV&1O", 'F'}, + {"0EV&F(", 'F'}, + {"0EV&N)", 'F'}, + {"0EV&NO", 'F'}, + {"0EV&S)", 'F'}, + {"0EV&SO", 'F'}, + {"0EV&V)", 'F'}, + {"0EV&VO", 'F'}, + {"0EV)", 'F'}, + {"0EV)&(", 'F'}, + {"0EV)&1", 'F'}, + {"0EV)&F", 'F'}, + {"0EV)&N", 'F'}, + {"0EV)&S", 'F'}, + {"0EV)&V", 'F'}, + {"0EV);", 'F'}, + {"0EV);(", 'F'}, + {"0EV);C", 'F'}, + {"0EV);E", 'F'}, + {"0EV);T", 'F'}, + {"0EV)C", 'F'}, + {"0EV)KN", 'F'}, + {"0EV)O(", 'F'}, + {"0EV)O1", 'F'}, + {"0EV)OF", 'F'}, + {"0EV)ON", 'F'}, + {"0EV)OS", 'F'}, + {"0EV)OV", 'F'}, + {"0EV)UE", 'F'}, + {"0EV,(1", 'F'}, + {"0EV,(F", 'F'}, + {"0EV,(N", 'F'}, + {"0EV,(S", 'F'}, + {"0EV,(V", 'F'}, + {"0EV,F(", 'F'}, + {"0EV;(E", 'F'}, + {"0EVB(1", 'F'}, + {"0EVB(F", 'F'}, + {"0EVB(N", 'F'}, + {"0EVB(S", 'F'}, + {"0EVB(V", 'F'}, + {"0EVB1)", 'F'}, + {"0EVB1O", 'F'}, + {"0EVBF(", 'F'}, + {"0EVBN)", 'F'}, + {"0EVBNO", 'F'}, + {"0EVBS)", 'F'}, + {"0EVBSO", 'F'}, + {"0EVBV)", 'F'}, + {"0EVBVO", 'F'}, + {"0EVF()", 'F'}, + {"0EVF(1", 'F'}, + {"0EVF(F", 'F'}, + {"0EVF(N", 'F'}, + {"0EVF(S", 'F'}, + {"0EVF(V", 'F'}, + {"0EVK(1", 'F'}, + {"0EVK(E", 'F'}, + {"0EVK(F", 'F'}, + {"0EVK(N", 'F'}, + {"0EVK(S", 'F'}, + {"0EVK(V", 'F'}, + {"0EVK1)", 'F'}, + {"0EVK1K", 'F'}, + {"0EVK1O", 'F'}, + {"0EVKF(", 'F'}, + {"0EVKN", 'F'}, + {"0EVKN)", 'F'}, + {"0EVKN;", 'F'}, + {"0EVKNC", 'F'}, + {"0EVKNK", 'F'}, + {"0EVKNU", 'F'}, + {"0EVKS)", 'F'}, + {"0EVKSK", 'F'}, + {"0EVKSO", 'F'}, + {"0EVKV)", 'F'}, + {"0EVKVK", 'F'}, + {"0EVKVO", 'F'}, + {"0EVN", 'F'}, + {"0EVN)U", 'F'}, + {"0EVN;", 'F'}, + {"0EVN;C", 'F'}, + {"0EVNC", 'F'}, + {"0EVNKN", 'F'}, + {"0EVNO(", 'F'}, + {"0EVNOF", 'F'}, + {"0EVNOS", 'F'}, + {"0EVNOV", 'F'}, + {"0EVO(1", 'F'}, + {"0EVO(E", 'F'}, + {"0EVO(F", 'F'}, + {"0EVO(N", 'F'}, + {"0EVO(S", 'F'}, + {"0EVO(V", 'F'}, + {"0EVOF(", 'F'}, + {"0EVOS&", 'F'}, + {"0EVOS(", 'F'}, + {"0EVOS)", 'F'}, + {"0EVOS,", 'F'}, + {"0EVOS1", 'F'}, + {"0EVOS;", 'F'}, + {"0EVOSB", 'F'}, + {"0EVOSF", 'F'}, + {"0EVOSK", 'F'}, + {"0EVOSU", 'F'}, + {"0EVOSV", 'F'}, + {"0EVS", 'F'}, + {"0EVS;", 'F'}, + {"0EVS;C", 'F'}, + {"0EVSC", 'F'}, + {"0EVSO(", 'F'}, + {"0EVSO1", 'F'}, + {"0EVSOF", 'F'}, + {"0EVSON", 'F'}, + {"0EVSOS", 'F'}, + {"0EVSOV", 'F'}, + {"0EVU(E", 'F'}, + {"0EVUE(", 'F'}, + {"0EVUE1", 'F'}, + {"0EVUEF", 'F'}, + {"0EVUEK", 'F'}, + {"0EVUEN", 'F'}, + {"0EVUES", 'F'}, + {"0EVUEV", 'F'}, + {"0F()&(", 'F'}, + {"0F()&1", 'F'}, + {"0F()&E", 'F'}, + {"0F()&F", 'F'}, + {"0F()&K", 'F'}, + {"0F()&N", 'F'}, + {"0F()&S", 'F'}, + {"0F()&V", 'F'}, + {"0F(),(", 'F'}, + {"0F(),1", 'F'}, + {"0F(),F", 'F'}, + {"0F(),N", 'F'}, + {"0F(),S", 'F'}, + {"0F(),V", 'F'}, + {"0F()1(", 'F'}, + {"0F()1F", 'F'}, + {"0F()1N", 'F'}, + {"0F()1O", 'F'}, + {"0F()1S", 'F'}, + {"0F()1U", 'F'}, + {"0F()1V", 'F'}, + {"0F();E", 'F'}, + {"0F();N", 'F'}, + {"0F();T", 'F'}, + {"0F()A(", 'F'}, + {"0F()AF", 'F'}, + {"0F()AS", 'F'}, + {"0F()AT", 'F'}, + {"0F()AV", 'F'}, + {"0F()B(", 'F'}, + {"0F()B1", 'F'}, + {"0F()BE", 'F'}, + {"0F()BF", 'F'}, + {"0F()BN", 'F'}, + {"0F()BS", 'F'}, + {"0F()BV", 'F'}, + {"0F()C", 'F'}, + {"0F()E(", 'F'}, + {"0F()E1", 'F'}, + {"0F()EF", 'F'}, + {"0F()EK", 'F'}, + {"0F()EN", 'F'}, + {"0F()EO", 'F'}, + {"0F()ES", 'F'}, + {"0F()EU", 'F'}, + {"0F()EV", 'F'}, + {"0F()F(", 'F'}, + {"0F()K(", 'F'}, + {"0F()K)", 'F'}, + {"0F()K1", 'F'}, + {"0F()KF", 'F'}, + {"0F()KN", 'F'}, + {"0F()KS", 'F'}, + {"0F()KU", 'F'}, + {"0F()KV", 'F'}, + {"0F()N&", 'F'}, + {"0F()N(", 'F'}, + {"0F()N)", 'F'}, + {"0F()N,", 'F'}, + {"0F()N1", 'F'}, + {"0F()NE", 'F'}, + {"0F()NF", 'F'}, + {"0F()NO", 'F'}, + {"0F()NU", 'F'}, + {"0F()O(", 'F'}, + {"0F()O1", 'F'}, + {"0F()OF", 'F'}, + {"0F()OK", 'F'}, + {"0F()ON", 'F'}, + {"0F()OS", 'F'}, + {"0F()OU", 'F'}, + {"0F()OV", 'F'}, + {"0F()S(", 'F'}, + {"0F()S1", 'F'}, + {"0F()SF", 'F'}, + {"0F()SO", 'F'}, + {"0F()SU", 'F'}, + {"0F()SV", 'F'}, + {"0F()T(", 'F'}, + {"0F()T1", 'F'}, + {"0F()TE", 'F'}, + {"0F()TF", 'F'}, + {"0F()TN", 'F'}, + {"0F()TS", 'F'}, + {"0F()TT", 'F'}, + {"0F()TV", 'F'}, + {"0F()U", 'F'}, + {"0F()U(", 'F'}, + {"0F()U1", 'F'}, + {"0F()U;", 'F'}, + {"0F()UC", 'F'}, + {"0F()UE", 'F'}, + {"0F()UF", 'F'}, + {"0F()UK", 'F'}, + {"0F()UO", 'F'}, + {"0F()US", 'F'}, + {"0F()UT", 'F'}, + {"0F()UV", 'F'}, + {"0F()V(", 'F'}, + {"0F()VF", 'F'}, + {"0F()VO", 'F'}, + {"0F()VS", 'F'}, + {"0F()VU", 'F'}, + {"0F(1&(", 'F'}, + {"0F(1&1", 'F'}, + {"0F(1&F", 'F'}, + {"0F(1&N", 'F'}, + {"0F(1&S", 'F'}, + {"0F(1&V", 'F'}, + {"0F(1)", 'F'}, + {"0F(1)&", 'F'}, + {"0F(1),", 'F'}, + {"0F(1)1", 'F'}, + {"0F(1);", 'F'}, + {"0F(1)A", 'F'}, + {"0F(1)B", 'F'}, + {"0F(1)C", 'F'}, + {"0F(1)E", 'F'}, + {"0F(1)F", 'F'}, + {"0F(1)K", 'F'}, + {"0F(1)N", 'F'}, + {"0F(1)O", 'F'}, + {"0F(1)S", 'F'}, + {"0F(1)T", 'F'}, + {"0F(1)U", 'F'}, + {"0F(1)V", 'F'}, + {"0F(1,(", 'F'}, + {"0F(1,F", 'F'}, + {"0F(1O(", 'F'}, + {"0F(1OF", 'F'}, + {"0F(1OS", 'F'}, + {"0F(1OV", 'F'}, + {"0F(E(1", 'F'}, + {"0F(E(E", 'F'}, + {"0F(E(F", 'F'}, + {"0F(E(N", 'F'}, + {"0F(E(S", 'F'}, + {"0F(E(V", 'F'}, + {"0F(E1&", 'F'}, + {"0F(E1)", 'F'}, + {"0F(E1K", 'F'}, + {"0F(E1O", 'F'}, + {"0F(EF(", 'F'}, + {"0F(EK(", 'F'}, + {"0F(EK1", 'F'}, + {"0F(EKF", 'F'}, + {"0F(EKN", 'F'}, + {"0F(EKO", 'F'}, + {"0F(EKS", 'F'}, + {"0F(EKV", 'F'}, + {"0F(EN&", 'F'}, + {"0F(EN)", 'F'}, + {"0F(ENK", 'F'}, + {"0F(ENO", 'F'}, + {"0F(EOK", 'F'}, + {"0F(ES&", 'F'}, + {"0F(ES)", 'F'}, + {"0F(ESK", 'F'}, + {"0F(ESO", 'F'}, + {"0F(EV&", 'F'}, + {"0F(EV)", 'F'}, + {"0F(EVK", 'F'}, + {"0F(EVO", 'F'}, + {"0F(F()", 'F'}, + {"0F(F(1", 'F'}, + {"0F(F(E", 'F'}, + {"0F(F(F", 'F'}, + {"0F(F(N", 'F'}, + {"0F(F(S", 'F'}, + {"0F(F(V", 'F'}, + {"0F(K()", 'F'}, + {"0F(K,(", 'F'}, + {"0F(K,F", 'F'}, + {"0F(N&(", 'F'}, + {"0F(N&1", 'F'}, + {"0F(N&F", 'F'}, + {"0F(N&N", 'F'}, + {"0F(N&S", 'F'}, + {"0F(N&V", 'F'}, + {"0F(N)", 'F'}, + {"0F(N)&", 'F'}, + {"0F(N),", 'F'}, + {"0F(N)1", 'F'}, + {"0F(N);", 'F'}, + {"0F(N)A", 'F'}, + {"0F(N)B", 'F'}, + {"0F(N)C", 'F'}, + {"0F(N)E", 'F'}, + {"0F(N)F", 'F'}, + {"0F(N)K", 'F'}, + {"0F(N)N", 'F'}, + {"0F(N)O", 'F'}, + {"0F(N)S", 'F'}, + {"0F(N)T", 'F'}, + {"0F(N)U", 'F'}, + {"0F(N)V", 'F'}, + {"0F(N,(", 'F'}, + {"0F(N,F", 'F'}, + {"0F(NO(", 'F'}, + {"0F(NOF", 'F'}, + {"0F(NOS", 'F'}, + {"0F(NOV", 'F'}, + {"0F(S&(", 'F'}, + {"0F(S&1", 'F'}, + {"0F(S&F", 'F'}, + {"0F(S&N", 'F'}, + {"0F(S&S", 'F'}, + {"0F(S&V", 'F'}, + {"0F(S)", 'F'}, + {"0F(S)&", 'F'}, + {"0F(S),", 'F'}, + {"0F(S)1", 'F'}, + {"0F(S);", 'F'}, + {"0F(S)A", 'F'}, + {"0F(S)B", 'F'}, + {"0F(S)C", 'F'}, + {"0F(S)E", 'F'}, + {"0F(S)F", 'F'}, + {"0F(S)K", 'F'}, + {"0F(S)N", 'F'}, + {"0F(S)O", 'F'}, + {"0F(S)S", 'F'}, + {"0F(S)T", 'F'}, + {"0F(S)U", 'F'}, + {"0F(S)V", 'F'}, + {"0F(S,(", 'F'}, + {"0F(S,F", 'F'}, + {"0F(SO(", 'F'}, + {"0F(SO1", 'F'}, + {"0F(SOF", 'F'}, + {"0F(SON", 'F'}, + {"0F(SOS", 'F'}, + {"0F(SOV", 'F'}, + {"0F(T,(", 'F'}, + {"0F(T,F", 'F'}, + {"0F(V&(", 'F'}, + {"0F(V&1", 'F'}, + {"0F(V&F", 'F'}, + {"0F(V&N", 'F'}, + {"0F(V&S", 'F'}, + {"0F(V&V", 'F'}, + {"0F(V)", 'F'}, + {"0F(V)&", 'F'}, + {"0F(V),", 'F'}, + {"0F(V)1", 'F'}, + {"0F(V);", 'F'}, + {"0F(V)A", 'F'}, + {"0F(V)B", 'F'}, + {"0F(V)C", 'F'}, + {"0F(V)E", 'F'}, + {"0F(V)F", 'F'}, + {"0F(V)K", 'F'}, + {"0F(V)N", 'F'}, + {"0F(V)O", 'F'}, + {"0F(V)S", 'F'}, + {"0F(V)T", 'F'}, + {"0F(V)U", 'F'}, + {"0F(V)V", 'F'}, + {"0F(V,(", 'F'}, + {"0F(V,F", 'F'}, + {"0F(VO(", 'F'}, + {"0F(VOF", 'F'}, + {"0F(VOS", 'F'}, + {"0K(1),", 'F'}, + {"0K(1)A", 'F'}, + {"0K(1)K", 'F'}, + {"0K(1)O", 'F'}, + {"0K(1O(", 'F'}, + {"0K(1OF", 'F'}, + {"0K(1OS", 'F'}, + {"0K(1OV", 'F'}, + {"0K(F()", 'F'}, + {"0K(F(1", 'F'}, + {"0K(F(F", 'F'}, + {"0K(F(N", 'F'}, + {"0K(F(S", 'F'}, + {"0K(F(V", 'F'}, + {"0K(N),", 'F'}, + {"0K(N)A", 'F'}, + {"0K(N)K", 'F'}, + {"0K(N)O", 'F'}, + {"0K(NO(", 'F'}, + {"0K(NOF", 'F'}, + {"0K(NOS", 'F'}, + {"0K(NOV", 'F'}, + {"0K(S),", 'F'}, + {"0K(S)A", 'F'}, + {"0K(S)K", 'F'}, + {"0K(S)O", 'F'}, + {"0K(SO(", 'F'}, + {"0K(SO1", 'F'}, + {"0K(SOF", 'F'}, + {"0K(SON", 'F'}, + {"0K(SOS", 'F'}, + {"0K(SOV", 'F'}, + {"0K(V),", 'F'}, + {"0K(V)A", 'F'}, + {"0K(V)K", 'F'}, + {"0K(V)O", 'F'}, + {"0K(VO(", 'F'}, + {"0K(VOF", 'F'}, + {"0K(VOS", 'F'}, + {"0K1,(1", 'F'}, + {"0K1,(F", 'F'}, + {"0K1,(N", 'F'}, + {"0K1,(S", 'F'}, + {"0K1,(V", 'F'}, + {"0K1,F(", 'F'}, + {"0K1A(F", 'F'}, + {"0K1A(N", 'F'}, + {"0K1A(S", 'F'}, + {"0K1A(V", 'F'}, + {"0K1AF(", 'F'}, + {"0K1ASO", 'F'}, + {"0K1AVO", 'F'}, + {"0K1K(1", 'F'}, + {"0K1K(F", 'F'}, + {"0K1K(N", 'F'}, + {"0K1K(S", 'F'}, + {"0K1K(V", 'F'}, + {"0K1K1O", 'F'}, + {"0K1K1U", 'F'}, + {"0K1KF(", 'F'}, + {"0K1KNU", 'F'}, + {"0K1KSO", 'F'}, + {"0K1KSU", 'F'}, + {"0K1KVO", 'F'}, + {"0K1KVU", 'F'}, + {"0K1O(1", 'F'}, + {"0K1O(F", 'F'}, + {"0K1O(N", 'F'}, + {"0K1O(S", 'F'}, + {"0K1O(V", 'F'}, + {"0K1OF(", 'F'}, + {"0K1OS(", 'F'}, + {"0K1OS,", 'F'}, + {"0K1OS1", 'F'}, + {"0K1OSA", 'F'}, + {"0K1OSF", 'F'}, + {"0K1OSK", 'F'}, + {"0K1OSV", 'F'}, + {"0K1OV(", 'F'}, + {"0K1OV,", 'F'}, + {"0K1OVA", 'F'}, + {"0K1OVF", 'F'}, + {"0K1OVK", 'F'}, + {"0K1OVO", 'F'}, + {"0K1OVS", 'F'}, + {"0KF(),", 'F'}, + {"0KF()A", 'F'}, + {"0KF()K", 'F'}, + {"0KF()O", 'F'}, + {"0KF(1)", 'F'}, + {"0KF(1O", 'F'}, + {"0KF(F(", 'F'}, + {"0KF(N)", 'F'}, + {"0KF(NO", 'F'}, + {"0KF(S)", 'F'}, + {"0KF(SO", 'F'}, + {"0KF(V)", 'F'}, + {"0KF(VO", 'F'}, + {"0KN,(1", 'F'}, + {"0KN,(F", 'F'}, + {"0KN,(N", 'F'}, + {"0KN,(S", 'F'}, + {"0KN,(V", 'F'}, + {"0KN,F(", 'F'}, + {"0KNA(F", 'F'}, + {"0KNA(N", 'F'}, + {"0KNA(S", 'F'}, + {"0KNA(V", 'F'}, + {"0KNAF(", 'F'}, + {"0KNASO", 'F'}, + {"0KNAVO", 'F'}, + {"0KNK(1", 'F'}, + {"0KNK(F", 'F'}, + {"0KNK(N", 'F'}, + {"0KNK(S", 'F'}, + {"0KNK(V", 'F'}, + {"0KNK1O", 'F'}, + {"0KNK1U", 'F'}, + {"0KNKF(", 'F'}, + {"0KNKNU", 'F'}, + {"0KNKSO", 'F'}, + {"0KNKSU", 'F'}, + {"0KNKVO", 'F'}, + {"0KNKVU", 'F'}, + {"0KS,(1", 'F'}, + {"0KS,(F", 'F'}, + {"0KS,(N", 'F'}, + {"0KS,(S", 'F'}, + {"0KS,(V", 'F'}, + {"0KS,F(", 'F'}, + {"0KSA(F", 'F'}, + {"0KSA(N", 'F'}, + {"0KSA(S", 'F'}, + {"0KSA(V", 'F'}, + {"0KSAF(", 'F'}, + {"0KSASO", 'F'}, + {"0KSAVO", 'F'}, + {"0KSK(1", 'F'}, + {"0KSK(F", 'F'}, + {"0KSK(N", 'F'}, + {"0KSK(S", 'F'}, + {"0KSK(V", 'F'}, + {"0KSK1O", 'F'}, + {"0KSK1U", 'F'}, + {"0KSKF(", 'F'}, + {"0KSKNU", 'F'}, + {"0KSKSO", 'F'}, + {"0KSKSU", 'F'}, + {"0KSKVO", 'F'}, + {"0KSKVU", 'F'}, + {"0KSO(1", 'F'}, + {"0KSO(F", 'F'}, + {"0KSO(N", 'F'}, + {"0KSO(S", 'F'}, + {"0KSO(V", 'F'}, + {"0KSO1(", 'F'}, + {"0KSO1,", 'F'}, + {"0KSO1A", 'F'}, + {"0KSO1F", 'F'}, + {"0KSO1K", 'F'}, + {"0KSO1N", 'F'}, + {"0KSO1S", 'F'}, + {"0KSO1V", 'F'}, + {"0KSOF(", 'F'}, + {"0KSON(", 'F'}, + {"0KSON,", 'F'}, + {"0KSON1", 'F'}, + {"0KSONA", 'F'}, + {"0KSONF", 'F'}, + {"0KSONK", 'F'}, + {"0KSOS(", 'F'}, + {"0KSOS,", 'F'}, + {"0KSOS1", 'F'}, + {"0KSOSA", 'F'}, + {"0KSOSF", 'F'}, + {"0KSOSK", 'F'}, + {"0KSOSV", 'F'}, + {"0KSOV(", 'F'}, + {"0KSOV,", 'F'}, + {"0KSOVA", 'F'}, + {"0KSOVF", 'F'}, + {"0KSOVK", 'F'}, + {"0KSOVO", 'F'}, + {"0KSOVS", 'F'}, + {"0KV,(1", 'F'}, + {"0KV,(F", 'F'}, + {"0KV,(N", 'F'}, + {"0KV,(S", 'F'}, + {"0KV,(V", 'F'}, + {"0KV,F(", 'F'}, + {"0KVA(F", 'F'}, + {"0KVA(N", 'F'}, + {"0KVA(S", 'F'}, + {"0KVA(V", 'F'}, + {"0KVAF(", 'F'}, + {"0KVASO", 'F'}, + {"0KVAVO", 'F'}, + {"0KVK(1", 'F'}, + {"0KVK(F", 'F'}, + {"0KVK(N", 'F'}, + {"0KVK(S", 'F'}, + {"0KVK(V", 'F'}, + {"0KVK1O", 'F'}, + {"0KVK1U", 'F'}, + {"0KVKF(", 'F'}, + {"0KVKNU", 'F'}, + {"0KVKSO", 'F'}, + {"0KVKSU", 'F'}, + {"0KVKVO", 'F'}, + {"0KVKVU", 'F'}, + {"0KVO(1", 'F'}, + {"0KVO(F", 'F'}, + {"0KVO(N", 'F'}, + {"0KVO(S", 'F'}, + {"0KVO(V", 'F'}, + {"0KVOF(", 'F'}, + {"0KVOS(", 'F'}, + {"0KVOS,", 'F'}, + {"0KVOS1", 'F'}, + {"0KVOSA", 'F'}, + {"0KVOSF", 'F'}, + {"0KVOSK", 'F'}, + {"0KVOSV", 'F'}, + {"0N&(1&", 'F'}, + {"0N&(1)", 'F'}, + {"0N&(1,", 'F'}, + {"0N&(1O", 'F'}, + {"0N&(E(", 'F'}, + {"0N&(E1", 'F'}, + {"0N&(EF", 'F'}, + {"0N&(EK", 'F'}, + {"0N&(EN", 'F'}, + {"0N&(EO", 'F'}, + {"0N&(ES", 'F'}, + {"0N&(EV", 'F'}, + {"0N&(F(", 'F'}, + {"0N&(N&", 'F'}, + {"0N&(N)", 'F'}, + {"0N&(N,", 'F'}, + {"0N&(NO", 'F'}, + {"0N&(S&", 'F'}, + {"0N&(S)", 'F'}, + {"0N&(S,", 'F'}, + {"0N&(SO", 'F'}, + {"0N&(V&", 'F'}, + {"0N&(V)", 'F'}, + {"0N&(V,", 'F'}, + {"0N&(VO", 'F'}, + {"0N&1", 'F'}, + {"0N&1&(", 'F'}, + {"0N&1&1", 'F'}, + {"0N&1&F", 'F'}, + {"0N&1&N", 'F'}, + {"0N&1&S", 'F'}, + {"0N&1&V", 'F'}, + {"0N&1)&", 'F'}, + {"0N&1)C", 'F'}, + {"0N&1)O", 'F'}, + {"0N&1)U", 'F'}, + {"0N&1;", 'F'}, + {"0N&1;C", 'F'}, + {"0N&1;E", 'F'}, + {"0N&1;T", 'F'}, + {"0N&1B(", 'F'}, + {"0N&1B1", 'F'}, + {"0N&1BF", 'F'}, + {"0N&1BN", 'F'}, + {"0N&1BS", 'F'}, + {"0N&1BV", 'F'}, + {"0N&1C", 'F'}, + {"0N&1EK", 'F'}, + {"0N&1EN", 'F'}, + {"0N&1F(", 'F'}, + {"0N&1K(", 'F'}, + {"0N&1K1", 'F'}, + {"0N&1KF", 'F'}, + {"0N&1KN", 'F'}, + {"0N&1KS", 'F'}, + {"0N&1KV", 'F'}, + {"0N&1O(", 'F'}, + {"0N&1OF", 'F'}, + {"0N&1OS", 'F'}, + {"0N&1OV", 'F'}, + {"0N&1TN", 'F'}, + {"0N&1U", 'F'}, + {"0N&1U(", 'F'}, + {"0N&1U;", 'F'}, + {"0N&1UC", 'F'}, + {"0N&1UE", 'F'}, + {"0N&E(1", 'F'}, + {"0N&E(F", 'F'}, + {"0N&E(N", 'F'}, + {"0N&E(O", 'F'}, + {"0N&E(S", 'F'}, + {"0N&E(V", 'F'}, + {"0N&E1", 'F'}, + {"0N&E1;", 'F'}, + {"0N&E1C", 'F'}, + {"0N&E1K", 'F'}, + {"0N&E1O", 'F'}, + {"0N&EF(", 'F'}, + {"0N&EK(", 'F'}, + {"0N&EK1", 'F'}, + {"0N&EKF", 'F'}, + {"0N&EKN", 'F'}, + {"0N&EKS", 'F'}, + {"0N&EKV", 'F'}, + {"0N&EN;", 'F'}, + {"0N&ENC", 'F'}, + {"0N&ENK", 'F'}, + {"0N&ENO", 'F'}, + {"0N&ES", 'F'}, + {"0N&ES;", 'F'}, + {"0N&ESC", 'F'}, + {"0N&ESK", 'F'}, + {"0N&ESO", 'F'}, + {"0N&EV", 'F'}, + {"0N&EV;", 'F'}, + {"0N&EVC", 'F'}, + {"0N&EVK", 'F'}, + {"0N&EVO", 'F'}, + {"0N&F()", 'F'}, + {"0N&F(1", 'F'}, + {"0N&F(E", 'F'}, + {"0N&F(F", 'F'}, + {"0N&F(N", 'F'}, + {"0N&F(S", 'F'}, + {"0N&F(V", 'F'}, + {"0N&K&(", 'F'}, + {"0N&K&1", 'F'}, + {"0N&K&F", 'F'}, + {"0N&K&N", 'F'}, + {"0N&K&S", 'F'}, + {"0N&K&V", 'F'}, + {"0N&K(1", 'F'}, + {"0N&K(F", 'F'}, + {"0N&K(N", 'F'}, + {"0N&K(S", 'F'}, + {"0N&K(V", 'F'}, + {"0N&K1O", 'F'}, + {"0N&KC", 'F'}, + {"0N&KF(", 'F'}, + {"0N&KNK", 'F'}, + {"0N&KO(", 'F'}, + {"0N&KO1", 'F'}, + {"0N&KOF", 'F'}, + {"0N&KOK", 'F'}, + {"0N&KON", 'F'}, + {"0N&KOS", 'F'}, + {"0N&KOV", 'F'}, + {"0N&KSO", 'F'}, + {"0N&KVO", 'F'}, + {"0N&N&(", 'F'}, + {"0N&N&1", 'F'}, + {"0N&N&F", 'F'}, + {"0N&N&S", 'F'}, + {"0N&N&V", 'F'}, + {"0N&N)&", 'F'}, + {"0N&N)C", 'F'}, + {"0N&N)O", 'F'}, + {"0N&N)U", 'F'}, + {"0N&N;C", 'F'}, + {"0N&N;E", 'F'}, + {"0N&N;T", 'F'}, + {"0N&NB(", 'F'}, + {"0N&NB1", 'F'}, + {"0N&NBF", 'F'}, + {"0N&NBS", 'F'}, + {"0N&NBV", 'F'}, + {"0N&NF(", 'F'}, + {"0N&NK(", 'F'}, + {"0N&NK1", 'F'}, + {"0N&NKF", 'F'}, + {"0N&NKS", 'F'}, + {"0N&NKV", 'F'}, + {"0N&NO(", 'F'}, + {"0N&NOF", 'F'}, + {"0N&NOS", 'F'}, + {"0N&NOV", 'F'}, + {"0N&NU", 'F'}, + {"0N&NU(", 'F'}, + {"0N&NU;", 'F'}, + {"0N&NUC", 'F'}, + {"0N&NUE", 'F'}, + {"0N&S&(", 'F'}, + {"0N&S&1", 'F'}, + {"0N&S&F", 'F'}, + {"0N&S&N", 'F'}, + {"0N&S&S", 'F'}, + {"0N&S&V", 'F'}, + {"0N&S)&", 'F'}, + {"0N&S)C", 'F'}, + {"0N&S)O", 'F'}, + {"0N&S)U", 'F'}, + {"0N&S1", 'F'}, + {"0N&S1;", 'F'}, + {"0N&S1C", 'F'}, + {"0N&S;", 'F'}, + {"0N&S;C", 'F'}, + {"0N&S;E", 'F'}, + {"0N&S;T", 'F'}, + {"0N&SB(", 'F'}, + {"0N&SB1", 'F'}, + {"0N&SBF", 'F'}, + {"0N&SBN", 'F'}, + {"0N&SBS", 'F'}, + {"0N&SBV", 'F'}, + {"0N&SC", 'F'}, + {"0N&SEK", 'F'}, + {"0N&SEN", 'F'}, + {"0N&SF(", 'F'}, + {"0N&SK(", 'F'}, + {"0N&SK1", 'F'}, + {"0N&SKF", 'F'}, + {"0N&SKN", 'F'}, + {"0N&SKS", 'F'}, + {"0N&SKV", 'F'}, + {"0N&SO(", 'F'}, + {"0N&SO1", 'F'}, + {"0N&SOF", 'F'}, + {"0N&SON", 'F'}, + {"0N&SOS", 'F'}, + {"0N&SOV", 'F'}, + {"0N&STN", 'F'}, + {"0N&SU", 'F'}, + {"0N&SU(", 'F'}, + {"0N&SU;", 'F'}, + {"0N&SUC", 'F'}, + {"0N&SUE", 'F'}, + {"0N&SV", 'F'}, + {"0N&SV;", 'F'}, + {"0N&SVC", 'F'}, + {"0N&SVO", 'F'}, + {"0N&V", 'F'}, + {"0N&V&(", 'F'}, + {"0N&V&1", 'F'}, + {"0N&V&F", 'F'}, + {"0N&V&N", 'F'}, + {"0N&V&S", 'F'}, + {"0N&V&V", 'F'}, + {"0N&V)&", 'F'}, + {"0N&V)C", 'F'}, + {"0N&V)O", 'F'}, + {"0N&V)U", 'F'}, + {"0N&V;", 'F'}, + {"0N&V;C", 'F'}, + {"0N&V;E", 'F'}, + {"0N&V;T", 'F'}, + {"0N&VB(", 'F'}, + {"0N&VB1", 'F'}, + {"0N&VBF", 'F'}, + {"0N&VBN", 'F'}, + {"0N&VBS", 'F'}, + {"0N&VBV", 'F'}, + {"0N&VC", 'F'}, + {"0N&VEK", 'F'}, + {"0N&VEN", 'F'}, + {"0N&VF(", 'F'}, + {"0N&VK(", 'F'}, + {"0N&VK1", 'F'}, + {"0N&VKF", 'F'}, + {"0N&VKN", 'F'}, + {"0N&VKS", 'F'}, + {"0N&VKV", 'F'}, + {"0N&VO(", 'F'}, + {"0N&VOF", 'F'}, + {"0N&VOS", 'F'}, + {"0N&VS", 'F'}, + {"0N&VS;", 'F'}, + {"0N&VSC", 'F'}, + {"0N&VSO", 'F'}, + {"0N&VTN", 'F'}, + {"0N&VU", 'F'}, + {"0N&VU(", 'F'}, + {"0N&VU;", 'F'}, + {"0N&VUC", 'F'}, + {"0N&VUE", 'F'}, + {"0N)&(1", 'F'}, + {"0N)&(E", 'F'}, + {"0N)&(F", 'F'}, + {"0N)&(N", 'F'}, + {"0N)&(S", 'F'}, + {"0N)&(V", 'F'}, + {"0N)&1", 'F'}, + {"0N)&1&", 'F'}, + {"0N)&1)", 'F'}, + {"0N)&1;", 'F'}, + {"0N)&1B", 'F'}, + {"0N)&1C", 'F'}, + {"0N)&1F", 'F'}, + {"0N)&1O", 'F'}, + {"0N)&1U", 'F'}, + {"0N)&F(", 'F'}, + {"0N)&N", 'F'}, + {"0N)&N&", 'F'}, + {"0N)&N)", 'F'}, + {"0N)&N;", 'F'}, + {"0N)&NB", 'F'}, + {"0N)&NC", 'F'}, + {"0N)&NF", 'F'}, + {"0N)&NO", 'F'}, + {"0N)&NU", 'F'}, + {"0N)&S", 'F'}, + {"0N)&S&", 'F'}, + {"0N)&S)", 'F'}, + {"0N)&S;", 'F'}, + {"0N)&SB", 'F'}, + {"0N)&SC", 'F'}, + {"0N)&SF", 'F'}, + {"0N)&SO", 'F'}, + {"0N)&SU", 'F'}, + {"0N)&V", 'F'}, + {"0N)&V&", 'F'}, + {"0N)&V)", 'F'}, + {"0N)&V;", 'F'}, + {"0N)&VB", 'F'}, + {"0N)&VC", 'F'}, + {"0N)&VF", 'F'}, + {"0N)&VO", 'F'}, + {"0N)&VU", 'F'}, + {"0N),(1", 'F'}, + {"0N),(F", 'F'}, + {"0N),(N", 'F'}, + {"0N),(S", 'F'}, + {"0N),(V", 'F'}, + {"0N);E(", 'F'}, + {"0N);E1", 'F'}, + {"0N);EF", 'F'}, + {"0N);EK", 'F'}, + {"0N);EN", 'F'}, + {"0N);EO", 'F'}, + {"0N);ES", 'F'}, + {"0N);EV", 'F'}, + {"0N);T(", 'F'}, + {"0N);T1", 'F'}, + {"0N);TF", 'F'}, + {"0N);TK", 'F'}, + {"0N);TN", 'F'}, + {"0N);TO", 'F'}, + {"0N);TS", 'F'}, + {"0N);TV", 'F'}, + {"0N)B(1", 'F'}, + {"0N)B(F", 'F'}, + {"0N)B(N", 'F'}, + {"0N)B(S", 'F'}, + {"0N)B(V", 'F'}, + {"0N)B1", 'F'}, + {"0N)B1&", 'F'}, + {"0N)B1;", 'F'}, + {"0N)B1C", 'F'}, + {"0N)B1K", 'F'}, + {"0N)B1N", 'F'}, + {"0N)B1O", 'F'}, + {"0N)B1U", 'F'}, + {"0N)BF(", 'F'}, + {"0N)BN", 'F'}, + {"0N)BN&", 'F'}, + {"0N)BN;", 'F'}, + {"0N)BNC", 'F'}, + {"0N)BNK", 'F'}, + {"0N)BNO", 'F'}, + {"0N)BNU", 'F'}, + {"0N)BS", 'F'}, + {"0N)BS&", 'F'}, + {"0N)BS;", 'F'}, + {"0N)BSC", 'F'}, + {"0N)BSK", 'F'}, + {"0N)BSO", 'F'}, + {"0N)BSU", 'F'}, + {"0N)BV", 'F'}, + {"0N)BV&", 'F'}, + {"0N)BV;", 'F'}, + {"0N)BVC", 'F'}, + {"0N)BVK", 'F'}, + {"0N)BVO", 'F'}, + {"0N)BVU", 'F'}, + {"0N)E(1", 'F'}, + {"0N)E(F", 'F'}, + {"0N)E(N", 'F'}, + {"0N)E(S", 'F'}, + {"0N)E(V", 'F'}, + {"0N)E1C", 'F'}, + {"0N)E1O", 'F'}, + {"0N)EF(", 'F'}, + {"0N)EK(", 'F'}, + {"0N)EK1", 'F'}, + {"0N)EKF", 'F'}, + {"0N)EKN", 'F'}, + {"0N)EKS", 'F'}, + {"0N)EKV", 'F'}, + {"0N)ENC", 'F'}, + {"0N)ENO", 'F'}, + {"0N)ESC", 'F'}, + {"0N)ESO", 'F'}, + {"0N)EVC", 'F'}, + {"0N)EVO", 'F'}, + {"0N)F(F", 'F'}, + {"0N)K(1", 'F'}, + {"0N)K(F", 'F'}, + {"0N)K(N", 'F'}, + {"0N)K(S", 'F'}, + {"0N)K(V", 'F'}, + {"0N)K1&", 'F'}, + {"0N)K1;", 'F'}, + {"0N)K1B", 'F'}, + {"0N)K1E", 'F'}, + {"0N)K1O", 'F'}, + {"0N)K1U", 'F'}, + {"0N)KB(", 'F'}, + {"0N)KB1", 'F'}, + {"0N)KBF", 'F'}, + {"0N)KBN", 'F'}, + {"0N)KBS", 'F'}, + {"0N)KBV", 'F'}, + {"0N)KF(", 'F'}, + {"0N)KN&", 'F'}, + {"0N)KN;", 'F'}, + {"0N)KNB", 'F'}, + {"0N)KNC", 'F'}, + {"0N)KNE", 'F'}, + {"0N)KNK", 'F'}, + {"0N)KNU", 'F'}, + {"0N)KS&", 'F'}, + {"0N)KS;", 'F'}, + {"0N)KSB", 'F'}, + {"0N)KSE", 'F'}, + {"0N)KSO", 'F'}, + {"0N)KSU", 'F'}, + {"0N)KUE", 'F'}, + {"0N)KV&", 'F'}, + {"0N)KV;", 'F'}, + {"0N)KVB", 'F'}, + {"0N)KVE", 'F'}, + {"0N)KVO", 'F'}, + {"0N)KVU", 'F'}, + {"0N)O(1", 'F'}, + {"0N)O(E", 'F'}, + {"0N)O(F", 'F'}, + {"0N)O(N", 'F'}, + {"0N)O(S", 'F'}, + {"0N)O(V", 'F'}, + {"0N)O1&", 'F'}, + {"0N)O1)", 'F'}, + {"0N)O1;", 'F'}, + {"0N)O1B", 'F'}, + {"0N)O1C", 'F'}, + {"0N)O1K", 'F'}, + {"0N)O1U", 'F'}, + {"0N)OF(", 'F'}, + {"0N)ON&", 'F'}, + {"0N)ON)", 'F'}, + {"0N)ON;", 'F'}, + {"0N)ONB", 'F'}, + {"0N)ONC", 'F'}, + {"0N)ONK", 'F'}, + {"0N)ONU", 'F'}, + {"0N)OS", 'F'}, + {"0N)OS&", 'F'}, + {"0N)OS)", 'F'}, + {"0N)OS;", 'F'}, + {"0N)OSB", 'F'}, + {"0N)OSC", 'F'}, + {"0N)OSK", 'F'}, + {"0N)OSU", 'F'}, + {"0N)OV", 'F'}, + {"0N)OV&", 'F'}, + {"0N)OV)", 'F'}, + {"0N)OV;", 'F'}, + {"0N)OVB", 'F'}, + {"0N)OVC", 'F'}, + {"0N)OVK", 'F'}, + {"0N)OVO", 'F'}, + {"0N)OVU", 'F'}, + {"0N)U(E", 'F'}, + {"0N)UE(", 'F'}, + {"0N)UE1", 'F'}, + {"0N)UEF", 'F'}, + {"0N)UEK", 'F'}, + {"0N)UEN", 'F'}, + {"0N)UES", 'F'}, + {"0N)UEV", 'F'}, + {"0N,(1)", 'F'}, + {"0N,(1O", 'F'}, + {"0N,(E(", 'F'}, + {"0N,(E1", 'F'}, + {"0N,(EF", 'F'}, + {"0N,(EK", 'F'}, + {"0N,(EN", 'F'}, + {"0N,(ES", 'F'}, + {"0N,(EV", 'F'}, + {"0N,(F(", 'F'}, + {"0N,(NO", 'F'}, + {"0N,(S)", 'F'}, + {"0N,(SO", 'F'}, + {"0N,(V)", 'F'}, + {"0N,(VO", 'F'}, + {"0N,F()", 'F'}, + {"0N,F(1", 'F'}, + {"0N,F(F", 'F'}, + {"0N,F(N", 'F'}, + {"0N,F(S", 'F'}, + {"0N,F(V", 'F'}, + {"0N1O(1", 'F'}, + {"0N1O(F", 'F'}, + {"0N1O(N", 'F'}, + {"0N1O(S", 'F'}, + {"0N1O(V", 'F'}, + {"0N1OF(", 'F'}, + {"0N1OS(", 'F'}, + {"0N1OS1", 'F'}, + {"0N1OSF", 'F'}, + {"0N1OSU", 'F'}, + {"0N1OSV", 'F'}, + {"0N1OV(", 'F'}, + {"0N1OVF", 'F'}, + {"0N1OVO", 'F'}, + {"0N1OVS", 'F'}, + {"0N1OVU", 'F'}, + {"0N1S;", 'F'}, + {"0N1S;C", 'F'}, + {"0N1SC", 'F'}, + {"0N1UE", 'F'}, + {"0N1UE;", 'F'}, + {"0N1UEC", 'F'}, + {"0N1UEK", 'F'}, + {"0N1V;", 'F'}, + {"0N1V;C", 'F'}, + {"0N1VC", 'F'}, + {"0N1VO(", 'F'}, + {"0N1VOF", 'F'}, + {"0N1VOS", 'F'}, + {"0N;E(1", 'F'}, + {"0N;E(E", 'F'}, + {"0N;E(F", 'F'}, + {"0N;E(N", 'F'}, + {"0N;E(S", 'F'}, + {"0N;E(V", 'F'}, + {"0N;E1,", 'F'}, + {"0N;E1;", 'F'}, + {"0N;E1C", 'F'}, + {"0N;E1K", 'F'}, + {"0N;E1O", 'F'}, + {"0N;E1T", 'F'}, + {"0N;EF(", 'F'}, + {"0N;EK(", 'F'}, + {"0N;EK1", 'F'}, + {"0N;EKF", 'F'}, + {"0N;EKN", 'F'}, + {"0N;EKO", 'F'}, + {"0N;EKS", 'F'}, + {"0N;EKV", 'F'}, + {"0N;EN,", 'F'}, + {"0N;EN;", 'F'}, + {"0N;ENC", 'F'}, + {"0N;ENE", 'F'}, + {"0N;ENK", 'F'}, + {"0N;ENO", 'F'}, + {"0N;ENT", 'F'}, + {"0N;ES,", 'F'}, + {"0N;ES;", 'F'}, + {"0N;ESC", 'F'}, + {"0N;ESK", 'F'}, + {"0N;ESO", 'F'}, + {"0N;EST", 'F'}, + {"0N;EV,", 'F'}, + {"0N;EV;", 'F'}, + {"0N;EVC", 'F'}, + {"0N;EVK", 'F'}, + {"0N;EVO", 'F'}, + {"0N;EVT", 'F'}, + {"0N;N:T", 'F'}, + {"0N;T(1", 'F'}, + {"0N;T(C", 'F'}, + {"0N;T(E", 'F'}, + {"0N;T(F", 'F'}, + {"0N;T(N", 'F'}, + {"0N;T(S", 'F'}, + {"0N;T(V", 'F'}, + {"0N;T1(", 'F'}, + {"0N;T1,", 'F'}, + {"0N;T1;", 'F'}, + {"0N;T1C", 'F'}, + {"0N;T1F", 'F'}, + {"0N;T1K", 'F'}, + {"0N;T1O", 'F'}, + {"0N;T1T", 'F'}, + {"0N;T;", 'F'}, + {"0N;T;C", 'F'}, + {"0N;TF(", 'F'}, + {"0N;TK(", 'F'}, + {"0N;TK1", 'F'}, + {"0N;TKF", 'F'}, + {"0N;TKK", 'F'}, + {"0N;TKO", 'F'}, + {"0N;TKS", 'F'}, + {"0N;TKV", 'F'}, + {"0N;TN(", 'F'}, + {"0N;TN,", 'F'}, + {"0N;TN1", 'F'}, + {"0N;TN;", 'F'}, + {"0N;TNC", 'F'}, + {"0N;TNE", 'F'}, + {"0N;TNF", 'F'}, + {"0N;TNK", 'F'}, + {"0N;TNN", 'F'}, + {"0N;TNO", 'F'}, + {"0N;TNS", 'F'}, + {"0N;TNT", 'F'}, + {"0N;TNV", 'F'}, + {"0N;TO(", 'F'}, + {"0N;TS(", 'F'}, + {"0N;TS,", 'F'}, + {"0N;TS;", 'F'}, + {"0N;TSC", 'F'}, + {"0N;TSF", 'F'}, + {"0N;TSK", 'F'}, + {"0N;TSO", 'F'}, + {"0N;TST", 'F'}, + {"0N;TTN", 'F'}, + {"0N;TV(", 'F'}, + {"0N;TV,", 'F'}, + {"0N;TV;", 'F'}, + {"0N;TVC", 'F'}, + {"0N;TVF", 'F'}, + {"0N;TVK", 'F'}, + {"0N;TVO", 'F'}, + {"0N;TVT", 'F'}, + {"0NA(F(", 'F'}, + {"0NA(N)", 'F'}, + {"0NA(NO", 'F'}, + {"0NA(S)", 'F'}, + {"0NA(SO", 'F'}, + {"0NA(V)", 'F'}, + {"0NA(VO", 'F'}, + {"0NAF()", 'F'}, + {"0NAF(1", 'F'}, + {"0NAF(F", 'F'}, + {"0NAF(N", 'F'}, + {"0NAF(S", 'F'}, + {"0NAF(V", 'F'}, + {"0NASO(", 'F'}, + {"0NASO1", 'F'}, + {"0NASOF", 'F'}, + {"0NASON", 'F'}, + {"0NASOS", 'F'}, + {"0NASOV", 'F'}, + {"0NASUE", 'F'}, + {"0NATO(", 'F'}, + {"0NATO1", 'F'}, + {"0NATOF", 'F'}, + {"0NATON", 'F'}, + {"0NATOS", 'F'}, + {"0NATOV", 'F'}, + {"0NATUE", 'F'}, + {"0NAVO(", 'F'}, + {"0NAVOF", 'F'}, + {"0NAVOS", 'F'}, + {"0NAVUE", 'F'}, + {"0NB(1&", 'F'}, + {"0NB(1)", 'F'}, + {"0NB(1O", 'F'}, + {"0NB(F(", 'F'}, + {"0NB(N&", 'F'}, + {"0NB(NO", 'F'}, + {"0NB(S&", 'F'}, + {"0NB(S)", 'F'}, + {"0NB(SO", 'F'}, + {"0NB(V&", 'F'}, + {"0NB(V)", 'F'}, + {"0NB(VO", 'F'}, + {"0NB1", 'F'}, + {"0NB1&(", 'F'}, + {"0NB1&1", 'F'}, + {"0NB1&F", 'F'}, + {"0NB1&N", 'F'}, + {"0NB1&S", 'F'}, + {"0NB1&V", 'F'}, + {"0NB1,(", 'F'}, + {"0NB1,F", 'F'}, + {"0NB1;", 'F'}, + {"0NB1;C", 'F'}, + {"0NB1B(", 'F'}, + {"0NB1B1", 'F'}, + {"0NB1BF", 'F'}, + {"0NB1BN", 'F'}, + {"0NB1BS", 'F'}, + {"0NB1BV", 'F'}, + {"0NB1C", 'F'}, + {"0NB1K(", 'F'}, + {"0NB1K1", 'F'}, + {"0NB1KF", 'F'}, + {"0NB1KN", 'F'}, + {"0NB1KS", 'F'}, + {"0NB1KV", 'F'}, + {"0NB1O(", 'F'}, + {"0NB1OF", 'F'}, + {"0NB1OS", 'F'}, + {"0NB1OV", 'F'}, + {"0NB1U(", 'F'}, + {"0NB1UE", 'F'}, + {"0NBE(1", 'F'}, + {"0NBE(F", 'F'}, + {"0NBE(N", 'F'}, + {"0NBE(S", 'F'}, + {"0NBE(V", 'F'}, + {"0NBEK(", 'F'}, + {"0NBF()", 'F'}, + {"0NBF(1", 'F'}, + {"0NBF(F", 'F'}, + {"0NBF(N", 'F'}, + {"0NBF(S", 'F'}, + {"0NBF(V", 'F'}, + {"0NBN&(", 'F'}, + {"0NBN&1", 'F'}, + {"0NBN&F", 'F'}, + {"0NBN&N", 'F'}, + {"0NBN&S", 'F'}, + {"0NBN&V", 'F'}, + {"0NBN,(", 'F'}, + {"0NBN,F", 'F'}, + {"0NBN;", 'F'}, + {"0NBN;C", 'F'}, + {"0NBNB(", 'F'}, + {"0NBNB1", 'F'}, + {"0NBNBF", 'F'}, + {"0NBNBN", 'F'}, + {"0NBNBS", 'F'}, + {"0NBNBV", 'F'}, + {"0NBNC", 'F'}, + {"0NBNK(", 'F'}, + {"0NBNK1", 'F'}, + {"0NBNKF", 'F'}, + {"0NBNKN", 'F'}, + {"0NBNKS", 'F'}, + {"0NBNKV", 'F'}, + {"0NBNO(", 'F'}, + {"0NBNOF", 'F'}, + {"0NBNOS", 'F'}, + {"0NBNOV", 'F'}, + {"0NBNU(", 'F'}, + {"0NBNUE", 'F'}, + {"0NBS", 'F'}, + {"0NBS&(", 'F'}, + {"0NBS&1", 'F'}, + {"0NBS&F", 'F'}, + {"0NBS&N", 'F'}, + {"0NBS&S", 'F'}, + {"0NBS&V", 'F'}, + {"0NBS,(", 'F'}, + {"0NBS,F", 'F'}, + {"0NBS;", 'F'}, + {"0NBS;C", 'F'}, + {"0NBSB(", 'F'}, + {"0NBSB1", 'F'}, + {"0NBSBF", 'F'}, + {"0NBSBN", 'F'}, + {"0NBSBS", 'F'}, + {"0NBSBV", 'F'}, + {"0NBSC", 'F'}, + {"0NBSK(", 'F'}, + {"0NBSK1", 'F'}, + {"0NBSKF", 'F'}, + {"0NBSKN", 'F'}, + {"0NBSKS", 'F'}, + {"0NBSKV", 'F'}, + {"0NBSO(", 'F'}, + {"0NBSO1", 'F'}, + {"0NBSOF", 'F'}, + {"0NBSON", 'F'}, + {"0NBSOS", 'F'}, + {"0NBSOV", 'F'}, + {"0NBSU(", 'F'}, + {"0NBSUE", 'F'}, + {"0NBV", 'F'}, + {"0NBV&(", 'F'}, + {"0NBV&1", 'F'}, + {"0NBV&F", 'F'}, + {"0NBV&N", 'F'}, + {"0NBV&S", 'F'}, + {"0NBV&V", 'F'}, + {"0NBV,(", 'F'}, + {"0NBV,F", 'F'}, + {"0NBV;", 'F'}, + {"0NBV;C", 'F'}, + {"0NBVB(", 'F'}, + {"0NBVB1", 'F'}, + {"0NBVBF", 'F'}, + {"0NBVBN", 'F'}, + {"0NBVBS", 'F'}, + {"0NBVBV", 'F'}, + {"0NBVC", 'F'}, + {"0NBVK(", 'F'}, + {"0NBVK1", 'F'}, + {"0NBVKF", 'F'}, + {"0NBVKN", 'F'}, + {"0NBVKS", 'F'}, + {"0NBVKV", 'F'}, + {"0NBVO(", 'F'}, + {"0NBVOF", 'F'}, + {"0NBVOS", 'F'}, + {"0NBVU(", 'F'}, + {"0NBVUE", 'F'}, + {"0NC", 'F'}, + {"0NE(1)", 'F'}, + {"0NE(1O", 'F'}, + {"0NE(F(", 'F'}, + {"0NE(N)", 'F'}, + {"0NE(NO", 'F'}, + {"0NE(S)", 'F'}, + {"0NE(SO", 'F'}, + {"0NE(V)", 'F'}, + {"0NE(VO", 'F'}, + {"0NE1;T", 'F'}, + {"0NE1C", 'F'}, + {"0NE1O(", 'F'}, + {"0NE1OF", 'F'}, + {"0NE1OS", 'F'}, + {"0NE1OV", 'F'}, + {"0NE1T(", 'F'}, + {"0NE1T1", 'F'}, + {"0NE1TF", 'F'}, + {"0NE1TN", 'F'}, + {"0NE1TS", 'F'}, + {"0NE1TV", 'F'}, + {"0NE1UE", 'F'}, + {"0NEF()", 'F'}, + {"0NEF(1", 'F'}, + {"0NEF(F", 'F'}, + {"0NEF(N", 'F'}, + {"0NEF(S", 'F'}, + {"0NEF(V", 'F'}, + {"0NEN;T", 'F'}, + {"0NENO(", 'F'}, + {"0NENOF", 'F'}, + {"0NENOS", 'F'}, + {"0NENOV", 'F'}, + {"0NENT(", 'F'}, + {"0NENT1", 'F'}, + {"0NENTF", 'F'}, + {"0NENTN", 'F'}, + {"0NENTS", 'F'}, + {"0NENTV", 'F'}, + {"0NENUE", 'F'}, + {"0NEOKN", 'F'}, + {"0NES;T", 'F'}, + {"0NESC", 'F'}, + {"0NESO(", 'F'}, + {"0NESO1", 'F'}, + {"0NESOF", 'F'}, + {"0NESON", 'F'}, + {"0NESOS", 'F'}, + {"0NESOV", 'F'}, + {"0NEST(", 'F'}, + {"0NEST1", 'F'}, + {"0NESTF", 'F'}, + {"0NESTN", 'F'}, + {"0NESTS", 'F'}, + {"0NESTV", 'F'}, + {"0NESUE", 'F'}, + {"0NEU(1", 'F'}, + {"0NEU(F", 'F'}, + {"0NEU(N", 'F'}, + {"0NEU(S", 'F'}, + {"0NEU(V", 'F'}, + {"0NEU1,", 'F'}, + {"0NEU1C", 'F'}, + {"0NEU1O", 'F'}, + {"0NEUEF", 'F'}, + {"0NEUEK", 'F'}, + {"0NEUF(", 'F'}, + {"0NEUS,", 'F'}, + {"0NEUSC", 'F'}, + {"0NEUSO", 'F'}, + {"0NEUV,", 'F'}, + {"0NEUVC", 'F'}, + {"0NEUVO", 'F'}, + {"0NEV;T", 'F'}, + {"0NEVC", 'F'}, + {"0NEVO(", 'F'}, + {"0NEVOF", 'F'}, + {"0NEVOS", 'F'}, + {"0NEVT(", 'F'}, + {"0NEVT1", 'F'}, + {"0NEVTF", 'F'}, + {"0NEVTN", 'F'}, + {"0NEVTS", 'F'}, + {"0NEVTV", 'F'}, + {"0NEVUE", 'F'}, + {"0NF()1", 'F'}, + {"0NF()F", 'F'}, + {"0NF()K", 'F'}, + {"0NF()N", 'F'}, + {"0NF()O", 'F'}, + {"0NF()S", 'F'}, + {"0NF()U", 'F'}, + {"0NF()V", 'F'}, + {"0NF(1)", 'F'}, + {"0NF(1O", 'F'}, + {"0NF(E(", 'F'}, + {"0NF(E1", 'F'}, + {"0NF(EF", 'F'}, + {"0NF(EK", 'F'}, + {"0NF(EN", 'F'}, + {"0NF(ES", 'F'}, + {"0NF(EV", 'F'}, + {"0NF(F(", 'F'}, + {"0NF(N,", 'F'}, + {"0NF(NO", 'F'}, + {"0NF(S)", 'F'}, + {"0NF(SO", 'F'}, + {"0NF(V)", 'F'}, + {"0NF(VO", 'F'}, + {"0NK(1)", 'F'}, + {"0NK(1O", 'F'}, + {"0NK(F(", 'F'}, + {"0NK(NO", 'F'}, + {"0NK(S)", 'F'}, + {"0NK(SO", 'F'}, + {"0NK(V)", 'F'}, + {"0NK(VO", 'F'}, + {"0NK)&(", 'F'}, + {"0NK)&1", 'F'}, + {"0NK)&F", 'F'}, + {"0NK)&N", 'F'}, + {"0NK)&S", 'F'}, + {"0NK)&V", 'F'}, + {"0NK);E", 'F'}, + {"0NK);T", 'F'}, + {"0NK)B(", 'F'}, + {"0NK)B1", 'F'}, + {"0NK)BF", 'F'}, + {"0NK)BN", 'F'}, + {"0NK)BS", 'F'}, + {"0NK)BV", 'F'}, + {"0NK)E(", 'F'}, + {"0NK)E1", 'F'}, + {"0NK)EF", 'F'}, + {"0NK)EK", 'F'}, + {"0NK)EN", 'F'}, + {"0NK)ES", 'F'}, + {"0NK)EV", 'F'}, + {"0NK)F(", 'F'}, + {"0NK)O(", 'F'}, + {"0NK)OF", 'F'}, + {"0NK)UE", 'F'}, + {"0NK1", 'F'}, + {"0NK1&(", 'F'}, + {"0NK1&1", 'F'}, + {"0NK1&F", 'F'}, + {"0NK1&N", 'F'}, + {"0NK1&S", 'F'}, + {"0NK1&V", 'F'}, + {"0NK1;C", 'F'}, + {"0NK1;E", 'F'}, + {"0NK1;T", 'F'}, + {"0NK1B(", 'F'}, + {"0NK1B1", 'F'}, + {"0NK1BF", 'F'}, + {"0NK1BN", 'F'}, + {"0NK1BS", 'F'}, + {"0NK1BV", 'F'}, + {"0NK1C", 'F'}, + {"0NK1E(", 'F'}, + {"0NK1E1", 'F'}, + {"0NK1EF", 'F'}, + {"0NK1EK", 'F'}, + {"0NK1EN", 'F'}, + {"0NK1ES", 'F'}, + {"0NK1EV", 'F'}, + {"0NK1O(", 'F'}, + {"0NK1OF", 'F'}, + {"0NK1OS", 'F'}, + {"0NK1OV", 'F'}, + {"0NK1U(", 'F'}, + {"0NK1UE", 'F'}, + {"0NKF()", 'F'}, + {"0NKF(1", 'F'}, + {"0NKF(F", 'F'}, + {"0NKF(N", 'F'}, + {"0NKF(S", 'F'}, + {"0NKF(V", 'F'}, + {"0NKN", 'F'}, + {"0NKN&(", 'F'}, + {"0NKN&1", 'F'}, + {"0NKN&F", 'F'}, + {"0NKN&S", 'F'}, + {"0NKN&V", 'F'}, + {"0NKN;C", 'F'}, + {"0NKN;E", 'F'}, + {"0NKN;T", 'F'}, + {"0NKNB(", 'F'}, + {"0NKNB1", 'F'}, + {"0NKNBF", 'F'}, + {"0NKNBN", 'F'}, + {"0NKNBS", 'F'}, + {"0NKNBV", 'F'}, + {"0NKNE(", 'F'}, + {"0NKNE1", 'F'}, + {"0NKNEF", 'F'}, + {"0NKNES", 'F'}, + {"0NKNEV", 'F'}, + {"0NKNU(", 'F'}, + {"0NKNUE", 'F'}, + {"0NKS", 'F'}, + {"0NKS&(", 'F'}, + {"0NKS&1", 'F'}, + {"0NKS&F", 'F'}, + {"0NKS&N", 'F'}, + {"0NKS&S", 'F'}, + {"0NKS&V", 'F'}, + {"0NKS;", 'F'}, + {"0NKS;C", 'F'}, + {"0NKS;E", 'F'}, + {"0NKS;T", 'F'}, + {"0NKSB(", 'F'}, + {"0NKSB1", 'F'}, + {"0NKSBF", 'F'}, + {"0NKSBN", 'F'}, + {"0NKSBS", 'F'}, + {"0NKSBV", 'F'}, + {"0NKSC", 'F'}, + {"0NKSE(", 'F'}, + {"0NKSE1", 'F'}, + {"0NKSEF", 'F'}, + {"0NKSEK", 'F'}, + {"0NKSEN", 'F'}, + {"0NKSES", 'F'}, + {"0NKSEV", 'F'}, + {"0NKSO(", 'F'}, + {"0NKSO1", 'F'}, + {"0NKSOF", 'F'}, + {"0NKSON", 'F'}, + {"0NKSOS", 'F'}, + {"0NKSOV", 'F'}, + {"0NKSU(", 'F'}, + {"0NKSUE", 'F'}, + {"0NKUE(", 'F'}, + {"0NKUE1", 'F'}, + {"0NKUEF", 'F'}, + {"0NKUEK", 'F'}, + {"0NKUEN", 'F'}, + {"0NKUES", 'F'}, + {"0NKUEV", 'F'}, + {"0NKV", 'F'}, + {"0NKV&(", 'F'}, + {"0NKV&1", 'F'}, + {"0NKV&F", 'F'}, + {"0NKV&N", 'F'}, + {"0NKV&S", 'F'}, + {"0NKV&V", 'F'}, + {"0NKV;", 'F'}, + {"0NKV;C", 'F'}, + {"0NKV;E", 'F'}, + {"0NKV;T", 'F'}, + {"0NKVB(", 'F'}, + {"0NKVB1", 'F'}, + {"0NKVBF", 'F'}, + {"0NKVBN", 'F'}, + {"0NKVBS", 'F'}, + {"0NKVBV", 'F'}, + {"0NKVC", 'F'}, + {"0NKVE(", 'F'}, + {"0NKVE1", 'F'}, + {"0NKVEF", 'F'}, + {"0NKVEK", 'F'}, + {"0NKVEN", 'F'}, + {"0NKVES", 'F'}, + {"0NKVEV", 'F'}, + {"0NKVO(", 'F'}, + {"0NKVOF", 'F'}, + {"0NKVOS", 'F'}, + {"0NKVU(", 'F'}, + {"0NKVUE", 'F'}, + {"0NO(1&", 'F'}, + {"0NO(1)", 'F'}, + {"0NO(1,", 'F'}, + {"0NO(1O", 'F'}, + {"0NO(E(", 'F'}, + {"0NO(E1", 'F'}, + {"0NO(EE", 'F'}, + {"0NO(EF", 'F'}, + {"0NO(EK", 'F'}, + {"0NO(EN", 'F'}, + {"0NO(EO", 'F'}, + {"0NO(ES", 'F'}, + {"0NO(EV", 'F'}, + {"0NO(F(", 'F'}, + {"0NO(N&", 'F'}, + {"0NO(N)", 'F'}, + {"0NO(N,", 'F'}, + {"0NO(NO", 'F'}, + {"0NO(S&", 'F'}, + {"0NO(S)", 'F'}, + {"0NO(S,", 'F'}, + {"0NO(SO", 'F'}, + {"0NO(V&", 'F'}, + {"0NO(V)", 'F'}, + {"0NO(V,", 'F'}, + {"0NO(VO", 'F'}, + {"0NOF()", 'F'}, + {"0NOF(1", 'F'}, + {"0NOF(E", 'F'}, + {"0NOF(F", 'F'}, + {"0NOF(N", 'F'}, + {"0NOF(S", 'F'}, + {"0NOF(V", 'F'}, + {"0NOK&(", 'F'}, + {"0NOK(1", 'F'}, + {"0NOK(F", 'F'}, + {"0NOK(N", 'F'}, + {"0NOK(S", 'F'}, + {"0NOK(V", 'F'}, + {"0NOK1C", 'F'}, + {"0NOK1O", 'F'}, + {"0NOKF(", 'F'}, + {"0NOKNC", 'F'}, + {"0NOKO(", 'F'}, + {"0NOKO1", 'F'}, + {"0NOKOF", 'F'}, + {"0NOKON", 'F'}, + {"0NOKOS", 'F'}, + {"0NOKOV", 'F'}, + {"0NOKSC", 'F'}, + {"0NOKSO", 'F'}, + {"0NOKVC", 'F'}, + {"0NOKVO", 'F'}, + {"0NONSU", 'F'}, + {"0NOS&(", 'F'}, + {"0NOS&1", 'F'}, + {"0NOS&E", 'F'}, + {"0NOS&F", 'F'}, + {"0NOS&K", 'F'}, + {"0NOS&N", 'F'}, + {"0NOS&S", 'F'}, + {"0NOS&U", 'F'}, + {"0NOS&V", 'F'}, + {"0NOS(E", 'F'}, + {"0NOS(U", 'F'}, + {"0NOS)&", 'F'}, + {"0NOS),", 'F'}, + {"0NOS);", 'F'}, + {"0NOS)B", 'F'}, + {"0NOS)C", 'F'}, + {"0NOS)E", 'F'}, + {"0NOS)F", 'F'}, + {"0NOS)K", 'F'}, + {"0NOS)O", 'F'}, + {"0NOS)U", 'F'}, + {"0NOS,(", 'F'}, + {"0NOS,F", 'F'}, + {"0NOS1(", 'F'}, + {"0NOS1F", 'F'}, + {"0NOS1N", 'F'}, + {"0NOS1S", 'F'}, + {"0NOS1U", 'F'}, + {"0NOS1V", 'F'}, + {"0NOS;", 'F'}, + {"0NOS;C", 'F'}, + {"0NOS;E", 'F'}, + {"0NOS;T", 'F'}, + {"0NOSA(", 'F'}, + {"0NOSAF", 'F'}, + {"0NOSAS", 'F'}, + {"0NOSAT", 'F'}, + {"0NOSAV", 'F'}, + {"0NOSB(", 'F'}, + {"0NOSB1", 'F'}, + {"0NOSBE", 'F'}, + {"0NOSBF", 'F'}, + {"0NOSBN", 'F'}, + {"0NOSBS", 'F'}, + {"0NOSBV", 'F'}, + {"0NOSC", 'F'}, + {"0NOSE(", 'F'}, + {"0NOSE1", 'F'}, + {"0NOSEF", 'F'}, + {"0NOSEK", 'F'}, + {"0NOSEN", 'F'}, + {"0NOSEO", 'F'}, + {"0NOSES", 'F'}, + {"0NOSEU", 'F'}, + {"0NOSEV", 'F'}, + {"0NOSF(", 'F'}, + {"0NOSK(", 'F'}, + {"0NOSK)", 'F'}, + {"0NOSK1", 'F'}, + {"0NOSKB", 'F'}, + {"0NOSKF", 'F'}, + {"0NOSKN", 'F'}, + {"0NOSKS", 'F'}, + {"0NOSKU", 'F'}, + {"0NOSKV", 'F'}, + {"0NOST(", 'F'}, + {"0NOST1", 'F'}, + {"0NOSTE", 'F'}, + {"0NOSTF", 'F'}, + {"0NOSTN", 'F'}, + {"0NOSTS", 'F'}, + {"0NOSTT", 'F'}, + {"0NOSTV", 'F'}, + {"0NOSU", 'F'}, + {"0NOSU(", 'F'}, + {"0NOSU1", 'F'}, + {"0NOSU;", 'F'}, + {"0NOSUC", 'F'}, + {"0NOSUE", 'F'}, + {"0NOSUF", 'F'}, + {"0NOSUK", 'F'}, + {"0NOSUO", 'F'}, + {"0NOSUS", 'F'}, + {"0NOSUT", 'F'}, + {"0NOSUV", 'F'}, + {"0NOSV(", 'F'}, + {"0NOSVF", 'F'}, + {"0NOSVO", 'F'}, + {"0NOSVS", 'F'}, + {"0NOSVU", 'F'}, + {"0NOU(E", 'F'}, + {"0NOUEK", 'F'}, + {"0NOUEN", 'F'}, + {"0NOV&(", 'F'}, + {"0NOV&1", 'F'}, + {"0NOV&E", 'F'}, + {"0NOV&F", 'F'}, + {"0NOV&K", 'F'}, + {"0NOV&N", 'F'}, + {"0NOV&S", 'F'}, + {"0NOV&U", 'F'}, + {"0NOV&V", 'F'}, + {"0NOV(E", 'F'}, + {"0NOV(U", 'F'}, + {"0NOV)&", 'F'}, + {"0NOV),", 'F'}, + {"0NOV);", 'F'}, + {"0NOV)B", 'F'}, + {"0NOV)C", 'F'}, + {"0NOV)E", 'F'}, + {"0NOV)F", 'F'}, + {"0NOV)K", 'F'}, + {"0NOV)O", 'F'}, + {"0NOV)U", 'F'}, + {"0NOV,(", 'F'}, + {"0NOV,F", 'F'}, + {"0NOV;", 'F'}, + {"0NOV;C", 'F'}, + {"0NOV;E", 'F'}, + {"0NOV;N", 'F'}, + {"0NOV;T", 'F'}, + {"0NOVA(", 'F'}, + {"0NOVAF", 'F'}, + {"0NOVAS", 'F'}, + {"0NOVAT", 'F'}, + {"0NOVAV", 'F'}, + {"0NOVB(", 'F'}, + {"0NOVB1", 'F'}, + {"0NOVBE", 'F'}, + {"0NOVBF", 'F'}, + {"0NOVBN", 'F'}, + {"0NOVBS", 'F'}, + {"0NOVBV", 'F'}, + {"0NOVC", 'F'}, + {"0NOVE(", 'F'}, + {"0NOVE1", 'F'}, + {"0NOVEF", 'F'}, + {"0NOVEK", 'F'}, + {"0NOVEN", 'F'}, + {"0NOVEO", 'F'}, + {"0NOVES", 'F'}, + {"0NOVEU", 'F'}, + {"0NOVEV", 'F'}, + {"0NOVF(", 'F'}, + {"0NOVK(", 'F'}, + {"0NOVK)", 'F'}, + {"0NOVK1", 'F'}, + {"0NOVKB", 'F'}, + {"0NOVKF", 'F'}, + {"0NOVKN", 'F'}, + {"0NOVKS", 'F'}, + {"0NOVKU", 'F'}, + {"0NOVKV", 'F'}, + {"0NOVO(", 'F'}, + {"0NOVOF", 'F'}, + {"0NOVOK", 'F'}, + {"0NOVOS", 'F'}, + {"0NOVOU", 'F'}, + {"0NOVS(", 'F'}, + {"0NOVS1", 'F'}, + {"0NOVSF", 'F'}, + {"0NOVSO", 'F'}, + {"0NOVSU", 'F'}, + {"0NOVSV", 'F'}, + {"0NOVT(", 'F'}, + {"0NOVT1", 'F'}, + {"0NOVTE", 'F'}, + {"0NOVTF", 'F'}, + {"0NOVTN", 'F'}, + {"0NOVTS", 'F'}, + {"0NOVTT", 'F'}, + {"0NOVTV", 'F'}, + {"0NOVU", 'F'}, + {"0NOVU(", 'F'}, + {"0NOVU1", 'F'}, + {"0NOVU;", 'F'}, + {"0NOVUC", 'F'}, + {"0NOVUE", 'F'}, + {"0NOVUF", 'F'}, + {"0NOVUK", 'F'}, + {"0NOVUO", 'F'}, + {"0NOVUS", 'F'}, + {"0NOVUT", 'F'}, + {"0NOVUV", 'F'}, + {"0NSO1U", 'F'}, + {"0NSONU", 'F'}, + {"0NSOSU", 'F'}, + {"0NSOVU", 'F'}, + {"0NSUE", 'F'}, + {"0NSUE;", 'F'}, + {"0NSUEC", 'F'}, + {"0NSUEK", 'F'}, + {"0NT(1)", 'F'}, + {"0NT(1O", 'F'}, + {"0NT(F(", 'F'}, + {"0NT(N)", 'F'}, + {"0NT(NO", 'F'}, + {"0NT(S)", 'F'}, + {"0NT(SO", 'F'}, + {"0NT(V)", 'F'}, + {"0NT(VO", 'F'}, + {"0NT1(F", 'F'}, + {"0NT1O(", 'F'}, + {"0NT1OF", 'F'}, + {"0NT1OS", 'F'}, + {"0NT1OV", 'F'}, + {"0NTE(1", 'F'}, + {"0NTE(F", 'F'}, + {"0NTE(N", 'F'}, + {"0NTE(S", 'F'}, + {"0NTE(V", 'F'}, + {"0NTE1N", 'F'}, + {"0NTE1O", 'F'}, + {"0NTEF(", 'F'}, + {"0NTEK(", 'F'}, + {"0NTEK1", 'F'}, + {"0NTEKF", 'F'}, + {"0NTEKN", 'F'}, + {"0NTEKS", 'F'}, + {"0NTEKV", 'F'}, + {"0NTENN", 'F'}, + {"0NTENO", 'F'}, + {"0NTESN", 'F'}, + {"0NTESO", 'F'}, + {"0NTEVN", 'F'}, + {"0NTEVO", 'F'}, + {"0NTF()", 'F'}, + {"0NTF(1", 'F'}, + {"0NTF(F", 'F'}, + {"0NTF(N", 'F'}, + {"0NTF(S", 'F'}, + {"0NTF(V", 'F'}, + {"0NTN(1", 'F'}, + {"0NTN(F", 'F'}, + {"0NTN(S", 'F'}, + {"0NTN(V", 'F'}, + {"0NTN1C", 'F'}, + {"0NTN1O", 'F'}, + {"0NTN;E", 'F'}, + {"0NTN;N", 'F'}, + {"0NTN;T", 'F'}, + {"0NTNE(", 'F'}, + {"0NTNE1", 'F'}, + {"0NTNEF", 'F'}, + {"0NTNEN", 'F'}, + {"0NTNES", 'F'}, + {"0NTNEV", 'F'}, + {"0NTNF(", 'F'}, + {"0NTNKN", 'F'}, + {"0NTNN:", 'F'}, + {"0NTNNC", 'F'}, + {"0NTNNO", 'F'}, + {"0NTNO(", 'F'}, + {"0NTNOF", 'F'}, + {"0NTNOS", 'F'}, + {"0NTNOV", 'F'}, + {"0NTNSC", 'F'}, + {"0NTNSO", 'F'}, + {"0NTNT(", 'F'}, + {"0NTNT1", 'F'}, + {"0NTNTF", 'F'}, + {"0NTNTN", 'F'}, + {"0NTNTS", 'F'}, + {"0NTNTV", 'F'}, + {"0NTNVC", 'F'}, + {"0NTNVO", 'F'}, + {"0NTS(F", 'F'}, + {"0NTSO(", 'F'}, + {"0NTSO1", 'F'}, + {"0NTSOF", 'F'}, + {"0NTSON", 'F'}, + {"0NTSOS", 'F'}, + {"0NTSOV", 'F'}, + {"0NTTNE", 'F'}, + {"0NTTNK", 'F'}, + {"0NTTNN", 'F'}, + {"0NTTNT", 'F'}, + {"0NTV(1", 'F'}, + {"0NTV(F", 'F'}, + {"0NTVO(", 'F'}, + {"0NTVOF", 'F'}, + {"0NTVOS", 'F'}, + {"0NU(1)", 'F'}, + {"0NU(1O", 'F'}, + {"0NU(E(", 'F'}, + {"0NU(E1", 'F'}, + {"0NU(EF", 'F'}, + {"0NU(EK", 'F'}, + {"0NU(EN", 'F'}, + {"0NU(ES", 'F'}, + {"0NU(EV", 'F'}, + {"0NU(F(", 'F'}, + {"0NU(N)", 'F'}, + {"0NU(NO", 'F'}, + {"0NU(S)", 'F'}, + {"0NU(SO", 'F'}, + {"0NU(V)", 'F'}, + {"0NU(VO", 'F'}, + {"0NU1,(", 'F'}, + {"0NU1,F", 'F'}, + {"0NU1C", 'F'}, + {"0NU1O(", 'F'}, + {"0NU1OF", 'F'}, + {"0NU1OS", 'F'}, + {"0NU1OV", 'F'}, + {"0NU;", 'F'}, + {"0NU;C", 'F'}, + {"0NUC", 'F'}, + {"0NUE", 'F'}, + {"0NUE(1", 'F'}, + {"0NUE(E", 'F'}, + {"0NUE(F", 'F'}, + {"0NUE(N", 'F'}, + {"0NUE(O", 'F'}, + {"0NUE(S", 'F'}, + {"0NUE(V", 'F'}, + {"0NUE1", 'F'}, + {"0NUE1&", 'F'}, + {"0NUE1(", 'F'}, + {"0NUE1)", 'F'}, + {"0NUE1,", 'F'}, + {"0NUE1;", 'F'}, + {"0NUE1B", 'F'}, + {"0NUE1C", 'F'}, + {"0NUE1F", 'F'}, + {"0NUE1K", 'F'}, + {"0NUE1N", 'F'}, + {"0NUE1O", 'F'}, + {"0NUE1S", 'F'}, + {"0NUE1U", 'F'}, + {"0NUE1V", 'F'}, + {"0NUE;", 'F'}, + {"0NUE;C", 'F'}, + {"0NUEC", 'F'}, + {"0NUEF", 'F'}, + {"0NUEF(", 'F'}, + {"0NUEF,", 'F'}, + {"0NUEF;", 'F'}, + {"0NUEFC", 'F'}, + {"0NUEK", 'F'}, + {"0NUEK(", 'F'}, + {"0NUEK1", 'F'}, + {"0NUEK;", 'F'}, + {"0NUEKC", 'F'}, + {"0NUEKF", 'F'}, + {"0NUEKN", 'F'}, + {"0NUEKO", 'F'}, + {"0NUEKS", 'F'}, + {"0NUEKV", 'F'}, + {"0NUEN", 'F'}, + {"0NUEN&", 'F'}, + {"0NUEN(", 'F'}, + {"0NUEN)", 'F'}, + {"0NUEN,", 'F'}, + {"0NUEN1", 'F'}, + {"0NUEN;", 'F'}, + {"0NUENB", 'F'}, + {"0NUENC", 'F'}, + {"0NUENF", 'F'}, + {"0NUENK", 'F'}, + {"0NUENO", 'F'}, + {"0NUENS", 'F'}, + {"0NUENU", 'F'}, + {"0NUEOK", 'F'}, + {"0NUEON", 'F'}, + {"0NUES", 'F'}, + {"0NUES&", 'F'}, + {"0NUES(", 'F'}, + {"0NUES)", 'F'}, + {"0NUES,", 'F'}, + {"0NUES1", 'F'}, + {"0NUES;", 'F'}, + {"0NUESB", 'F'}, + {"0NUESC", 'F'}, + {"0NUESF", 'F'}, + {"0NUESK", 'F'}, + {"0NUESO", 'F'}, + {"0NUESU", 'F'}, + {"0NUESV", 'F'}, + {"0NUEV", 'F'}, + {"0NUEV&", 'F'}, + {"0NUEV(", 'F'}, + {"0NUEV)", 'F'}, + {"0NUEV,", 'F'}, + {"0NUEV;", 'F'}, + {"0NUEVB", 'F'}, + {"0NUEVC", 'F'}, + {"0NUEVF", 'F'}, + {"0NUEVK", 'F'}, + {"0NUEVN", 'F'}, + {"0NUEVO", 'F'}, + {"0NUEVS", 'F'}, + {"0NUEVU", 'F'}, + {"0NUF()", 'F'}, + {"0NUF(1", 'F'}, + {"0NUF(F", 'F'}, + {"0NUF(N", 'F'}, + {"0NUF(S", 'F'}, + {"0NUF(V", 'F'}, + {"0NUK(E", 'F'}, + {"0NUO(E", 'F'}, + {"0NUON(", 'F'}, + {"0NUON1", 'F'}, + {"0NUONF", 'F'}, + {"0NUONS", 'F'}, + {"0NUS,(", 'F'}, + {"0NUS,F", 'F'}, + {"0NUSC", 'F'}, + {"0NUSO(", 'F'}, + {"0NUSO1", 'F'}, + {"0NUSOF", 'F'}, + {"0NUSON", 'F'}, + {"0NUSOS", 'F'}, + {"0NUSOV", 'F'}, + {"0NUTN(", 'F'}, + {"0NUTN1", 'F'}, + {"0NUTNF", 'F'}, + {"0NUTNN", 'F'}, + {"0NUTNS", 'F'}, + {"0NUTNV", 'F'}, + {"0NUV,(", 'F'}, + {"0NUV,F", 'F'}, + {"0NUVC", 'F'}, + {"0NUVO(", 'F'}, + {"0NUVOF", 'F'}, + {"0NUVOS", 'F'}, + {"0S&(1&", 'F'}, + {"0S&(1)", 'F'}, + {"0S&(1,", 'F'}, + {"0S&(1O", 'F'}, + {"0S&(E(", 'F'}, + {"0S&(E1", 'F'}, + {"0S&(EF", 'F'}, + {"0S&(EK", 'F'}, + {"0S&(EN", 'F'}, + {"0S&(EO", 'F'}, + {"0S&(ES", 'F'}, + {"0S&(EV", 'F'}, + {"0S&(F(", 'F'}, + {"0S&(N&", 'F'}, + {"0S&(N)", 'F'}, + {"0S&(N,", 'F'}, + {"0S&(NO", 'F'}, + {"0S&(S&", 'F'}, + {"0S&(S)", 'F'}, + {"0S&(S,", 'F'}, + {"0S&(SO", 'F'}, + {"0S&(V&", 'F'}, + {"0S&(V)", 'F'}, + {"0S&(V,", 'F'}, + {"0S&(VO", 'F'}, + {"0S&1", 'F'}, + {"0S&1&(", 'F'}, + {"0S&1&1", 'F'}, + {"0S&1&F", 'F'}, + {"0S&1&N", 'F'}, + {"0S&1&S", 'F'}, + {"0S&1&V", 'F'}, + {"0S&1)&", 'F'}, + {"0S&1)C", 'F'}, + {"0S&1)O", 'F'}, + {"0S&1)U", 'F'}, + {"0S&1;", 'F'}, + {"0S&1;C", 'F'}, + {"0S&1;E", 'F'}, + {"0S&1;T", 'F'}, + {"0S&1B(", 'F'}, + {"0S&1B1", 'F'}, + {"0S&1BF", 'F'}, + {"0S&1BN", 'F'}, + {"0S&1BS", 'F'}, + {"0S&1BV", 'F'}, + {"0S&1C", 'F'}, + {"0S&1EK", 'F'}, + {"0S&1EN", 'F'}, + {"0S&1F(", 'F'}, + {"0S&1K(", 'F'}, + {"0S&1K1", 'F'}, + {"0S&1KF", 'F'}, + {"0S&1KN", 'F'}, + {"0S&1KS", 'F'}, + {"0S&1KV", 'F'}, + {"0S&1O(", 'F'}, + {"0S&1OF", 'F'}, + {"0S&1OS", 'F'}, + {"0S&1OV", 'F'}, + {"0S&1TN", 'F'}, + {"0S&1U", 'F'}, + {"0S&1U(", 'F'}, + {"0S&1U;", 'F'}, + {"0S&1UC", 'F'}, + {"0S&1UE", 'F'}, + {"0S&E(1", 'F'}, + {"0S&E(F", 'F'}, + {"0S&E(N", 'F'}, + {"0S&E(O", 'F'}, + {"0S&E(S", 'F'}, + {"0S&E(V", 'F'}, + {"0S&E1", 'F'}, + {"0S&E1;", 'F'}, + {"0S&E1C", 'F'}, + {"0S&E1K", 'F'}, + {"0S&E1O", 'F'}, + {"0S&EF(", 'F'}, + {"0S&EK(", 'F'}, + {"0S&EK1", 'F'}, + {"0S&EKF", 'F'}, + {"0S&EKN", 'F'}, + {"0S&EKS", 'F'}, + {"0S&EKV", 'F'}, + {"0S&EN", 'F'}, + {"0S&EN;", 'F'}, + {"0S&ENC", 'F'}, + {"0S&ENK", 'F'}, + {"0S&ENO", 'F'}, + {"0S&ES", 'F'}, + {"0S&ES;", 'F'}, + {"0S&ESC", 'F'}, + {"0S&ESK", 'F'}, + {"0S&ESO", 'F'}, + {"0S&EV", 'F'}, + {"0S&EV;", 'F'}, + {"0S&EVC", 'F'}, + {"0S&EVK", 'F'}, + {"0S&EVO", 'F'}, + {"0S&F()", 'F'}, + {"0S&F(1", 'F'}, + {"0S&F(E", 'F'}, + {"0S&F(F", 'F'}, + {"0S&F(N", 'F'}, + {"0S&F(S", 'F'}, + {"0S&F(V", 'F'}, + {"0S&K&(", 'F'}, + {"0S&K&1", 'F'}, + {"0S&K&F", 'F'}, + {"0S&K&N", 'F'}, + {"0S&K&S", 'F'}, + {"0S&K&V", 'F'}, + {"0S&K(1", 'F'}, + {"0S&K(F", 'F'}, + {"0S&K(N", 'F'}, + {"0S&K(S", 'F'}, + {"0S&K(V", 'F'}, + {"0S&K1O", 'F'}, + {"0S&KC", 'F'}, + {"0S&KF(", 'F'}, + {"0S&KNK", 'F'}, + {"0S&KO(", 'F'}, + {"0S&KO1", 'F'}, + {"0S&KOF", 'F'}, + {"0S&KOK", 'F'}, + {"0S&KON", 'F'}, + {"0S&KOS", 'F'}, + {"0S&KOV", 'F'}, + {"0S&KSO", 'F'}, + {"0S&KVO", 'F'}, + {"0S&N", 'F'}, + {"0S&N&(", 'F'}, + {"0S&N&1", 'F'}, + {"0S&N&F", 'F'}, + {"0S&N&N", 'F'}, + {"0S&N&S", 'F'}, + {"0S&N&V", 'F'}, + {"0S&N)&", 'F'}, + {"0S&N)C", 'F'}, + {"0S&N)O", 'F'}, + {"0S&N)U", 'F'}, + {"0S&N;", 'F'}, + {"0S&N;C", 'F'}, + {"0S&N;E", 'F'}, + {"0S&N;T", 'F'}, + {"0S&NB(", 'F'}, + {"0S&NB1", 'F'}, + {"0S&NBF", 'F'}, + {"0S&NBN", 'F'}, + {"0S&NBS", 'F'}, + {"0S&NBV", 'F'}, + {"0S&NC", 'F'}, + {"0S&NEN", 'F'}, + {"0S&NF(", 'F'}, + {"0S&NK(", 'F'}, + {"0S&NK1", 'F'}, + {"0S&NKF", 'F'}, + {"0S&NKN", 'F'}, + {"0S&NKS", 'F'}, + {"0S&NKV", 'F'}, + {"0S&NO(", 'F'}, + {"0S&NOF", 'F'}, + {"0S&NOS", 'F'}, + {"0S&NOV", 'F'}, + {"0S&NTN", 'F'}, + {"0S&NU", 'F'}, + {"0S&NU(", 'F'}, + {"0S&NU;", 'F'}, + {"0S&NUC", 'F'}, + {"0S&NUE", 'F'}, + {"0S&S", 'F'}, + {"0S&S&(", 'F'}, + {"0S&S&1", 'F'}, + {"0S&S&F", 'F'}, + {"0S&S&N", 'F'}, + {"0S&S&S", 'F'}, + {"0S&S&V", 'F'}, + {"0S&S)&", 'F'}, + {"0S&S)C", 'F'}, + {"0S&S)O", 'F'}, + {"0S&S)U", 'F'}, + {"0S&S1", 'F'}, + {"0S&S1;", 'F'}, + {"0S&S1C", 'F'}, + {"0S&S;", 'F'}, + {"0S&S;C", 'F'}, + {"0S&S;E", 'F'}, + {"0S&S;T", 'F'}, + {"0S&SB(", 'F'}, + {"0S&SB1", 'F'}, + {"0S&SBF", 'F'}, + {"0S&SBN", 'F'}, + {"0S&SBS", 'F'}, + {"0S&SBV", 'F'}, + {"0S&SC", 'F'}, + {"0S&SEK", 'F'}, + {"0S&SEN", 'F'}, + {"0S&SF(", 'F'}, + {"0S&SK(", 'F'}, + {"0S&SK1", 'F'}, + {"0S&SKF", 'F'}, + {"0S&SKN", 'F'}, + {"0S&SKS", 'F'}, + {"0S&SKV", 'F'}, + {"0S&SO(", 'F'}, + {"0S&SO1", 'F'}, + {"0S&SOF", 'F'}, + {"0S&SON", 'F'}, + {"0S&SOS", 'F'}, + {"0S&SOV", 'F'}, + {"0S&STN", 'F'}, + {"0S&SU", 'F'}, + {"0S&SU(", 'F'}, + {"0S&SU;", 'F'}, + {"0S&SUC", 'F'}, + {"0S&SUE", 'F'}, + {"0S&SV", 'F'}, + {"0S&SV;", 'F'}, + {"0S&SVC", 'F'}, + {"0S&SVO", 'F'}, + {"0S&V", 'F'}, + {"0S&V&(", 'F'}, + {"0S&V&1", 'F'}, + {"0S&V&F", 'F'}, + {"0S&V&N", 'F'}, + {"0S&V&S", 'F'}, + {"0S&V&V", 'F'}, + {"0S&V)&", 'F'}, + {"0S&V)C", 'F'}, + {"0S&V)O", 'F'}, + {"0S&V)U", 'F'}, + {"0S&V;", 'F'}, + {"0S&V;C", 'F'}, + {"0S&V;E", 'F'}, + {"0S&V;T", 'F'}, + {"0S&VB(", 'F'}, + {"0S&VB1", 'F'}, + {"0S&VBF", 'F'}, + {"0S&VBN", 'F'}, + {"0S&VBS", 'F'}, + {"0S&VBV", 'F'}, + {"0S&VC", 'F'}, + {"0S&VEK", 'F'}, + {"0S&VEN", 'F'}, + {"0S&VF(", 'F'}, + {"0S&VK(", 'F'}, + {"0S&VK1", 'F'}, + {"0S&VKF", 'F'}, + {"0S&VKN", 'F'}, + {"0S&VKS", 'F'}, + {"0S&VKV", 'F'}, + {"0S&VO(", 'F'}, + {"0S&VOF", 'F'}, + {"0S&VOS", 'F'}, + {"0S&VS", 'F'}, + {"0S&VS;", 'F'}, + {"0S&VSC", 'F'}, + {"0S&VSO", 'F'}, + {"0S&VTN", 'F'}, + {"0S&VU", 'F'}, + {"0S&VU(", 'F'}, + {"0S&VU;", 'F'}, + {"0S&VUC", 'F'}, + {"0S&VUE", 'F'}, + {"0S(EF(", 'F'}, + {"0S(EKF", 'F'}, + {"0S(EKN", 'F'}, + {"0S(ENK", 'F'}, + {"0S(U(E", 'F'}, + {"0S)&(1", 'F'}, + {"0S)&(E", 'F'}, + {"0S)&(F", 'F'}, + {"0S)&(N", 'F'}, + {"0S)&(S", 'F'}, + {"0S)&(V", 'F'}, + {"0S)&1", 'F'}, + {"0S)&1&", 'F'}, + {"0S)&1)", 'F'}, + {"0S)&1;", 'F'}, + {"0S)&1B", 'F'}, + {"0S)&1C", 'F'}, + {"0S)&1F", 'F'}, + {"0S)&1O", 'F'}, + {"0S)&1U", 'F'}, + {"0S)&F(", 'F'}, + {"0S)&N", 'F'}, + {"0S)&N&", 'F'}, + {"0S)&N)", 'F'}, + {"0S)&N;", 'F'}, + {"0S)&NB", 'F'}, + {"0S)&NC", 'F'}, + {"0S)&NF", 'F'}, + {"0S)&NO", 'F'}, + {"0S)&NU", 'F'}, + {"0S)&S", 'F'}, + {"0S)&S&", 'F'}, + {"0S)&S)", 'F'}, + {"0S)&S;", 'F'}, + {"0S)&SB", 'F'}, + {"0S)&SC", 'F'}, + {"0S)&SF", 'F'}, + {"0S)&SO", 'F'}, + {"0S)&SU", 'F'}, + {"0S)&V", 'F'}, + {"0S)&V&", 'F'}, + {"0S)&V)", 'F'}, + {"0S)&V;", 'F'}, + {"0S)&VB", 'F'}, + {"0S)&VC", 'F'}, + {"0S)&VF", 'F'}, + {"0S)&VO", 'F'}, + {"0S)&VU", 'F'}, + {"0S),(1", 'F'}, + {"0S),(F", 'F'}, + {"0S),(N", 'F'}, + {"0S),(S", 'F'}, + {"0S),(V", 'F'}, + {"0S);E(", 'F'}, + {"0S);E1", 'F'}, + {"0S);EF", 'F'}, + {"0S);EK", 'F'}, + {"0S);EN", 'F'}, + {"0S);EO", 'F'}, + {"0S);ES", 'F'}, + {"0S);EV", 'F'}, + {"0S);T(", 'F'}, + {"0S);T1", 'F'}, + {"0S);TF", 'F'}, + {"0S);TK", 'F'}, + {"0S);TN", 'F'}, + {"0S);TO", 'F'}, + {"0S);TS", 'F'}, + {"0S);TV", 'F'}, + {"0S)B(1", 'F'}, + {"0S)B(F", 'F'}, + {"0S)B(N", 'F'}, + {"0S)B(S", 'F'}, + {"0S)B(V", 'F'}, + {"0S)B1", 'F'}, + {"0S)B1&", 'F'}, + {"0S)B1;", 'F'}, + {"0S)B1C", 'F'}, + {"0S)B1K", 'F'}, + {"0S)B1N", 'F'}, + {"0S)B1O", 'F'}, + {"0S)B1U", 'F'}, + {"0S)BF(", 'F'}, + {"0S)BN", 'F'}, + {"0S)BN&", 'F'}, + {"0S)BN;", 'F'}, + {"0S)BNC", 'F'}, + {"0S)BNK", 'F'}, + {"0S)BNO", 'F'}, + {"0S)BNU", 'F'}, + {"0S)BS", 'F'}, + {"0S)BS&", 'F'}, + {"0S)BS;", 'F'}, + {"0S)BSC", 'F'}, + {"0S)BSK", 'F'}, + {"0S)BSO", 'F'}, + {"0S)BSU", 'F'}, + {"0S)BV", 'F'}, + {"0S)BV&", 'F'}, + {"0S)BV;", 'F'}, + {"0S)BVC", 'F'}, + {"0S)BVK", 'F'}, + {"0S)BVO", 'F'}, + {"0S)BVU", 'F'}, + {"0S)C", 'F'}, + {"0S)E(1", 'F'}, + {"0S)E(F", 'F'}, + {"0S)E(N", 'F'}, + {"0S)E(S", 'F'}, + {"0S)E(V", 'F'}, + {"0S)E1C", 'F'}, + {"0S)E1O", 'F'}, + {"0S)EF(", 'F'}, + {"0S)EK(", 'F'}, + {"0S)EK1", 'F'}, + {"0S)EKF", 'F'}, + {"0S)EKN", 'F'}, + {"0S)EKS", 'F'}, + {"0S)EKV", 'F'}, + {"0S)ENC", 'F'}, + {"0S)ENO", 'F'}, + {"0S)ESC", 'F'}, + {"0S)ESO", 'F'}, + {"0S)EVC", 'F'}, + {"0S)EVO", 'F'}, + {"0S)F(F", 'F'}, + {"0S)K(1", 'F'}, + {"0S)K(F", 'F'}, + {"0S)K(N", 'F'}, + {"0S)K(S", 'F'}, + {"0S)K(V", 'F'}, + {"0S)K1&", 'F'}, + {"0S)K1;", 'F'}, + {"0S)K1B", 'F'}, + {"0S)K1E", 'F'}, + {"0S)K1O", 'F'}, + {"0S)K1U", 'F'}, + {"0S)KB(", 'F'}, + {"0S)KB1", 'F'}, + {"0S)KBF", 'F'}, + {"0S)KBN", 'F'}, + {"0S)KBS", 'F'}, + {"0S)KBV", 'F'}, + {"0S)KF(", 'F'}, + {"0S)KN&", 'F'}, + {"0S)KN;", 'F'}, + {"0S)KNB", 'F'}, + {"0S)KNC", 'F'}, + {"0S)KNE", 'F'}, + {"0S)KNK", 'F'}, + {"0S)KNU", 'F'}, + {"0S)KS&", 'F'}, + {"0S)KS;", 'F'}, + {"0S)KSB", 'F'}, + {"0S)KSE", 'F'}, + {"0S)KSO", 'F'}, + {"0S)KSU", 'F'}, + {"0S)KUE", 'F'}, + {"0S)KV&", 'F'}, + {"0S)KV;", 'F'}, + {"0S)KVB", 'F'}, + {"0S)KVE", 'F'}, + {"0S)KVO", 'F'}, + {"0S)KVU", 'F'}, + {"0S)O(1", 'F'}, + {"0S)O(E", 'F'}, + {"0S)O(F", 'F'}, + {"0S)O(N", 'F'}, + {"0S)O(S", 'F'}, + {"0S)O(V", 'F'}, + {"0S)O1", 'F'}, + {"0S)O1&", 'F'}, + {"0S)O1)", 'F'}, + {"0S)O1;", 'F'}, + {"0S)O1B", 'F'}, + {"0S)O1C", 'F'}, + {"0S)O1K", 'F'}, + {"0S)O1U", 'F'}, + {"0S)OF(", 'F'}, + {"0S)ON&", 'F'}, + {"0S)ON)", 'F'}, + {"0S)ON;", 'F'}, + {"0S)ONB", 'F'}, + {"0S)ONC", 'F'}, + {"0S)ONK", 'F'}, + {"0S)ONU", 'F'}, + {"0S)OS", 'F'}, + {"0S)OS&", 'F'}, + {"0S)OS)", 'F'}, + {"0S)OS;", 'F'}, + {"0S)OSB", 'F'}, + {"0S)OSC", 'F'}, + {"0S)OSK", 'F'}, + {"0S)OSU", 'F'}, + {"0S)OV", 'F'}, + {"0S)OV&", 'F'}, + {"0S)OV)", 'F'}, + {"0S)OV;", 'F'}, + {"0S)OVB", 'F'}, + {"0S)OVC", 'F'}, + {"0S)OVK", 'F'}, + {"0S)OVO", 'F'}, + {"0S)OVU", 'F'}, + {"0S)U(E", 'F'}, + {"0S)UE(", 'F'}, + {"0S)UE1", 'F'}, + {"0S)UEF", 'F'}, + {"0S)UEK", 'F'}, + {"0S)UEN", 'F'}, + {"0S)UES", 'F'}, + {"0S)UEV", 'F'}, + {"0S,(1)", 'F'}, + {"0S,(1O", 'F'}, + {"0S,(E(", 'F'}, + {"0S,(E1", 'F'}, + {"0S,(EF", 'F'}, + {"0S,(EK", 'F'}, + {"0S,(EN", 'F'}, + {"0S,(ES", 'F'}, + {"0S,(EV", 'F'}, + {"0S,(F(", 'F'}, + {"0S,(N)", 'F'}, + {"0S,(NO", 'F'}, + {"0S,(S)", 'F'}, + {"0S,(SO", 'F'}, + {"0S,(V)", 'F'}, + {"0S,(VO", 'F'}, + {"0S,F()", 'F'}, + {"0S,F(1", 'F'}, + {"0S,F(F", 'F'}, + {"0S,F(N", 'F'}, + {"0S,F(S", 'F'}, + {"0S,F(V", 'F'}, + {"0S1F()", 'F'}, + {"0S1F(1", 'F'}, + {"0S1F(F", 'F'}, + {"0S1F(N", 'F'}, + {"0S1F(S", 'F'}, + {"0S1F(V", 'F'}, + {"0S1NC", 'F'}, + {"0S1S;", 'F'}, + {"0S1S;C", 'F'}, + {"0S1SC", 'F'}, + {"0S1UE", 'F'}, + {"0S1UE;", 'F'}, + {"0S1UEC", 'F'}, + {"0S1UEK", 'F'}, + {"0S1V", 'F'}, + {"0S1V;", 'F'}, + {"0S1V;C", 'F'}, + {"0S1VC", 'F'}, + {"0S1VO(", 'F'}, + {"0S1VOF", 'F'}, + {"0S1VOS", 'F'}, + {"0S;E(1", 'F'}, + {"0S;E(E", 'F'}, + {"0S;E(F", 'F'}, + {"0S;E(N", 'F'}, + {"0S;E(S", 'F'}, + {"0S;E(V", 'F'}, + {"0S;E1,", 'F'}, + {"0S;E1;", 'F'}, + {"0S;E1C", 'F'}, + {"0S;E1K", 'F'}, + {"0S;E1O", 'F'}, + {"0S;E1T", 'F'}, + {"0S;EF(", 'F'}, + {"0S;EK(", 'F'}, + {"0S;EK1", 'F'}, + {"0S;EKF", 'F'}, + {"0S;EKN", 'F'}, + {"0S;EKO", 'F'}, + {"0S;EKS", 'F'}, + {"0S;EKV", 'F'}, + {"0S;EN,", 'F'}, + {"0S;EN;", 'F'}, + {"0S;ENC", 'F'}, + {"0S;ENE", 'F'}, + {"0S;ENK", 'F'}, + {"0S;ENO", 'F'}, + {"0S;ENT", 'F'}, + {"0S;ES,", 'F'}, + {"0S;ES;", 'F'}, + {"0S;ESC", 'F'}, + {"0S;ESK", 'F'}, + {"0S;ESO", 'F'}, + {"0S;EST", 'F'}, + {"0S;EV,", 'F'}, + {"0S;EV;", 'F'}, + {"0S;EVC", 'F'}, + {"0S;EVK", 'F'}, + {"0S;EVO", 'F'}, + {"0S;EVT", 'F'}, + {"0S;N:T", 'F'}, + {"0S;T(1", 'F'}, + {"0S;T(C", 'F'}, + {"0S;T(E", 'F'}, + {"0S;T(F", 'F'}, + {"0S;T(N", 'F'}, + {"0S;T(S", 'F'}, + {"0S;T(V", 'F'}, + {"0S;T1(", 'F'}, + {"0S;T1,", 'F'}, + {"0S;T1;", 'F'}, + {"0S;T1C", 'F'}, + {"0S;T1F", 'F'}, + {"0S;T1K", 'F'}, + {"0S;T1O", 'F'}, + {"0S;T1T", 'F'}, + {"0S;T;", 'F'}, + {"0S;T;C", 'F'}, + {"0S;TF(", 'F'}, + {"0S;TK(", 'F'}, + {"0S;TK1", 'F'}, + {"0S;TKF", 'F'}, + {"0S;TKK", 'F'}, + {"0S;TKN", 'F'}, + {"0S;TKO", 'F'}, + {"0S;TKS", 'F'}, + {"0S;TKV", 'F'}, + {"0S;TN(", 'F'}, + {"0S;TN,", 'F'}, + {"0S;TN1", 'F'}, + {"0S;TN;", 'F'}, + {"0S;TNC", 'F'}, + {"0S;TNE", 'F'}, + {"0S;TNF", 'F'}, + {"0S;TNK", 'F'}, + {"0S;TNN", 'F'}, + {"0S;TNO", 'F'}, + {"0S;TNS", 'F'}, + {"0S;TNT", 'F'}, + {"0S;TNV", 'F'}, + {"0S;TO(", 'F'}, + {"0S;TS(", 'F'}, + {"0S;TS,", 'F'}, + {"0S;TS;", 'F'}, + {"0S;TSC", 'F'}, + {"0S;TSF", 'F'}, + {"0S;TSK", 'F'}, + {"0S;TSO", 'F'}, + {"0S;TST", 'F'}, + {"0S;TTN", 'F'}, + {"0S;TV(", 'F'}, + {"0S;TV,", 'F'}, + {"0S;TV;", 'F'}, + {"0S;TVC", 'F'}, + {"0S;TVF", 'F'}, + {"0S;TVK", 'F'}, + {"0S;TVO", 'F'}, + {"0S;TVT", 'F'}, + {"0SA(F(", 'F'}, + {"0SA(N)", 'F'}, + {"0SA(NO", 'F'}, + {"0SA(S)", 'F'}, + {"0SA(SO", 'F'}, + {"0SA(V)", 'F'}, + {"0SA(VO", 'F'}, + {"0SAF()", 'F'}, + {"0SAF(1", 'F'}, + {"0SAF(F", 'F'}, + {"0SAF(N", 'F'}, + {"0SAF(S", 'F'}, + {"0SAF(V", 'F'}, + {"0SASO(", 'F'}, + {"0SASO1", 'F'}, + {"0SASOF", 'F'}, + {"0SASON", 'F'}, + {"0SASOS", 'F'}, + {"0SASOV", 'F'}, + {"0SASUE", 'F'}, + {"0SATO(", 'F'}, + {"0SATO1", 'F'}, + {"0SATOF", 'F'}, + {"0SATON", 'F'}, + {"0SATOS", 'F'}, + {"0SATOV", 'F'}, + {"0SATUE", 'F'}, + {"0SAVO(", 'F'}, + {"0SAVOF", 'F'}, + {"0SAVOS", 'F'}, + {"0SAVUE", 'F'}, + {"0SB(1)", 'F'}, + {"0SB(1O", 'F'}, + {"0SB(F(", 'F'}, + {"0SB(NO", 'F'}, + {"0SB(S)", 'F'}, + {"0SB(SO", 'F'}, + {"0SB(V)", 'F'}, + {"0SB(VO", 'F'}, + {"0SB1", 'F'}, + {"0SB1&(", 'F'}, + {"0SB1&1", 'F'}, + {"0SB1&F", 'F'}, + {"0SB1&N", 'F'}, + {"0SB1&S", 'F'}, + {"0SB1&V", 'F'}, + {"0SB1,(", 'F'}, + {"0SB1,F", 'F'}, + {"0SB1;", 'F'}, + {"0SB1;C", 'F'}, + {"0SB1B(", 'F'}, + {"0SB1B1", 'F'}, + {"0SB1BF", 'F'}, + {"0SB1BN", 'F'}, + {"0SB1BS", 'F'}, + {"0SB1BV", 'F'}, + {"0SB1C", 'F'}, + {"0SB1K(", 'F'}, + {"0SB1K1", 'F'}, + {"0SB1KF", 'F'}, + {"0SB1KN", 'F'}, + {"0SB1KS", 'F'}, + {"0SB1KV", 'F'}, + {"0SB1O(", 'F'}, + {"0SB1OF", 'F'}, + {"0SB1OS", 'F'}, + {"0SB1OV", 'F'}, + {"0SB1U(", 'F'}, + {"0SB1UE", 'F'}, + {"0SBE(1", 'F'}, + {"0SBE(F", 'F'}, + {"0SBE(N", 'F'}, + {"0SBE(S", 'F'}, + {"0SBE(V", 'F'}, + {"0SBEK(", 'F'}, + {"0SBF()", 'F'}, + {"0SBF(1", 'F'}, + {"0SBF(F", 'F'}, + {"0SBF(N", 'F'}, + {"0SBF(S", 'F'}, + {"0SBF(V", 'F'}, + {"0SBN", 'F'}, + {"0SBN&(", 'F'}, + {"0SBN&1", 'F'}, + {"0SBN&F", 'F'}, + {"0SBN&N", 'F'}, + {"0SBN&S", 'F'}, + {"0SBN&V", 'F'}, + {"0SBN,(", 'F'}, + {"0SBN,F", 'F'}, + {"0SBN;", 'F'}, + {"0SBN;C", 'F'}, + {"0SBNB(", 'F'}, + {"0SBNB1", 'F'}, + {"0SBNBF", 'F'}, + {"0SBNBN", 'F'}, + {"0SBNBS", 'F'}, + {"0SBNBV", 'F'}, + {"0SBNC", 'F'}, + {"0SBNK(", 'F'}, + {"0SBNK1", 'F'}, + {"0SBNKF", 'F'}, + {"0SBNKN", 'F'}, + {"0SBNKS", 'F'}, + {"0SBNKV", 'F'}, + {"0SBNO(", 'F'}, + {"0SBNOF", 'F'}, + {"0SBNOS", 'F'}, + {"0SBNOV", 'F'}, + {"0SBNU(", 'F'}, + {"0SBNUE", 'F'}, + {"0SBS", 'F'}, + {"0SBS&(", 'F'}, + {"0SBS&1", 'F'}, + {"0SBS&F", 'F'}, + {"0SBS&N", 'F'}, + {"0SBS&S", 'F'}, + {"0SBS&V", 'F'}, + {"0SBS,(", 'F'}, + {"0SBS,F", 'F'}, + {"0SBS;", 'F'}, + {"0SBS;C", 'F'}, + {"0SBSB(", 'F'}, + {"0SBSB1", 'F'}, + {"0SBSBF", 'F'}, + {"0SBSBN", 'F'}, + {"0SBSBS", 'F'}, + {"0SBSBV", 'F'}, + {"0SBSC", 'F'}, + {"0SBSK(", 'F'}, + {"0SBSK1", 'F'}, + {"0SBSKF", 'F'}, + {"0SBSKN", 'F'}, + {"0SBSKS", 'F'}, + {"0SBSKV", 'F'}, + {"0SBSO(", 'F'}, + {"0SBSO1", 'F'}, + {"0SBSOF", 'F'}, + {"0SBSON", 'F'}, + {"0SBSOS", 'F'}, + {"0SBSOV", 'F'}, + {"0SBSU(", 'F'}, + {"0SBSUE", 'F'}, + {"0SBV", 'F'}, + {"0SBV&(", 'F'}, + {"0SBV&1", 'F'}, + {"0SBV&F", 'F'}, + {"0SBV&N", 'F'}, + {"0SBV&S", 'F'}, + {"0SBV&V", 'F'}, + {"0SBV,(", 'F'}, + {"0SBV,F", 'F'}, + {"0SBV;", 'F'}, + {"0SBV;C", 'F'}, + {"0SBVB(", 'F'}, + {"0SBVB1", 'F'}, + {"0SBVBF", 'F'}, + {"0SBVBN", 'F'}, + {"0SBVBS", 'F'}, + {"0SBVBV", 'F'}, + {"0SBVC", 'F'}, + {"0SBVK(", 'F'}, + {"0SBVK1", 'F'}, + {"0SBVKF", 'F'}, + {"0SBVKN", 'F'}, + {"0SBVKS", 'F'}, + {"0SBVKV", 'F'}, + {"0SBVO(", 'F'}, + {"0SBVOF", 'F'}, + {"0SBVOS", 'F'}, + {"0SBVU(", 'F'}, + {"0SBVUE", 'F'}, + {"0SC", 'F'}, + {"0SE(1)", 'F'}, + {"0SE(1O", 'F'}, + {"0SE(F(", 'F'}, + {"0SE(N)", 'F'}, + {"0SE(NO", 'F'}, + {"0SE(S)", 'F'}, + {"0SE(SO", 'F'}, + {"0SE(V)", 'F'}, + {"0SE(VO", 'F'}, + {"0SE1;T", 'F'}, + {"0SE1C", 'F'}, + {"0SE1O(", 'F'}, + {"0SE1OF", 'F'}, + {"0SE1OS", 'F'}, + {"0SE1OV", 'F'}, + {"0SE1T(", 'F'}, + {"0SE1T1", 'F'}, + {"0SE1TF", 'F'}, + {"0SE1TN", 'F'}, + {"0SE1TS", 'F'}, + {"0SE1TV", 'F'}, + {"0SE1UE", 'F'}, + {"0SEF()", 'F'}, + {"0SEF(1", 'F'}, + {"0SEF(F", 'F'}, + {"0SEF(N", 'F'}, + {"0SEF(S", 'F'}, + {"0SEF(V", 'F'}, + {"0SEK(1", 'F'}, + {"0SEK(E", 'F'}, + {"0SEK(F", 'F'}, + {"0SEK(N", 'F'}, + {"0SEK(S", 'F'}, + {"0SEK(V", 'F'}, + {"0SEK1;", 'F'}, + {"0SEK1C", 'F'}, + {"0SEK1O", 'F'}, + {"0SEK1T", 'F'}, + {"0SEK1U", 'F'}, + {"0SEKF(", 'F'}, + {"0SEKN;", 'F'}, + {"0SEKNC", 'F'}, + {"0SEKNE", 'F'}, + {"0SEKNT", 'F'}, + {"0SEKNU", 'F'}, + {"0SEKOK", 'F'}, + {"0SEKS;", 'F'}, + {"0SEKSC", 'F'}, + {"0SEKSO", 'F'}, + {"0SEKST", 'F'}, + {"0SEKSU", 'F'}, + {"0SEKU(", 'F'}, + {"0SEKU1", 'F'}, + {"0SEKUE", 'F'}, + {"0SEKUF", 'F'}, + {"0SEKUS", 'F'}, + {"0SEKUV", 'F'}, + {"0SEKV;", 'F'}, + {"0SEKVC", 'F'}, + {"0SEKVO", 'F'}, + {"0SEKVT", 'F'}, + {"0SEKVU", 'F'}, + {"0SEN;T", 'F'}, + {"0SENC", 'F'}, + {"0SENEN", 'F'}, + {"0SENO(", 'F'}, + {"0SENOF", 'F'}, + {"0SENOS", 'F'}, + {"0SENOV", 'F'}, + {"0SENT(", 'F'}, + {"0SENT1", 'F'}, + {"0SENTF", 'F'}, + {"0SENTN", 'F'}, + {"0SENTS", 'F'}, + {"0SENTV", 'F'}, + {"0SENUE", 'F'}, + {"0SEOKN", 'F'}, + {"0SES;T", 'F'}, + {"0SESC", 'F'}, + {"0SESO(", 'F'}, + {"0SESO1", 'F'}, + {"0SESOF", 'F'}, + {"0SESON", 'F'}, + {"0SESOS", 'F'}, + {"0SESOV", 'F'}, + {"0SEST(", 'F'}, + {"0SEST1", 'F'}, + {"0SESTF", 'F'}, + {"0SESTN", 'F'}, + {"0SESTS", 'F'}, + {"0SESTV", 'F'}, + {"0SESUE", 'F'}, + {"0SEU(1", 'F'}, + {"0SEU(F", 'F'}, + {"0SEU(N", 'F'}, + {"0SEU(S", 'F'}, + {"0SEU(V", 'F'}, + {"0SEU1,", 'F'}, + {"0SEU1C", 'F'}, + {"0SEU1O", 'F'}, + {"0SEUEF", 'F'}, + {"0SEUEK", 'F'}, + {"0SEUF(", 'F'}, + {"0SEUS,", 'F'}, + {"0SEUSC", 'F'}, + {"0SEUSO", 'F'}, + {"0SEUV,", 'F'}, + {"0SEUVC", 'F'}, + {"0SEUVO", 'F'}, + {"0SEV;T", 'F'}, + {"0SEVC", 'F'}, + {"0SEVO(", 'F'}, + {"0SEVOF", 'F'}, + {"0SEVOS", 'F'}, + {"0SEVT(", 'F'}, + {"0SEVT1", 'F'}, + {"0SEVTF", 'F'}, + {"0SEVTN", 'F'}, + {"0SEVTS", 'F'}, + {"0SEVTV", 'F'}, + {"0SEVUE", 'F'}, + {"0SF()1", 'F'}, + {"0SF()F", 'F'}, + {"0SF()K", 'F'}, + {"0SF()N", 'F'}, + {"0SF()O", 'F'}, + {"0SF()S", 'F'}, + {"0SF()U", 'F'}, + {"0SF()V", 'F'}, + {"0SF(1)", 'F'}, + {"0SF(1N", 'F'}, + {"0SF(1O", 'F'}, + {"0SF(E(", 'F'}, + {"0SF(E1", 'F'}, + {"0SF(EF", 'F'}, + {"0SF(EK", 'F'}, + {"0SF(EN", 'F'}, + {"0SF(ES", 'F'}, + {"0SF(EV", 'F'}, + {"0SF(F(", 'F'}, + {"0SF(N)", 'F'}, + {"0SF(N,", 'F'}, + {"0SF(NO", 'F'}, + {"0SF(S)", 'F'}, + {"0SF(SO", 'F'}, + {"0SF(V)", 'F'}, + {"0SF(VO", 'F'}, + {"0SK(1)", 'F'}, + {"0SK(1O", 'F'}, + {"0SK(F(", 'F'}, + {"0SK(N)", 'F'}, + {"0SK(NO", 'F'}, + {"0SK(S)", 'F'}, + {"0SK(SO", 'F'}, + {"0SK(V)", 'F'}, + {"0SK(VO", 'F'}, + {"0SK)&(", 'F'}, + {"0SK)&1", 'F'}, + {"0SK)&F", 'F'}, + {"0SK)&N", 'F'}, + {"0SK)&S", 'F'}, + {"0SK)&V", 'F'}, + {"0SK);E", 'F'}, + {"0SK);T", 'F'}, + {"0SK)B(", 'F'}, + {"0SK)B1", 'F'}, + {"0SK)BF", 'F'}, + {"0SK)BN", 'F'}, + {"0SK)BS", 'F'}, + {"0SK)BV", 'F'}, + {"0SK)E(", 'F'}, + {"0SK)E1", 'F'}, + {"0SK)EF", 'F'}, + {"0SK)EK", 'F'}, + {"0SK)EN", 'F'}, + {"0SK)ES", 'F'}, + {"0SK)EV", 'F'}, + {"0SK)F(", 'F'}, + {"0SK)O(", 'F'}, + {"0SK)OF", 'F'}, + {"0SK)UE", 'F'}, + {"0SK1", 'F'}, + {"0SK1&(", 'F'}, + {"0SK1&1", 'F'}, + {"0SK1&F", 'F'}, + {"0SK1&N", 'F'}, + {"0SK1&S", 'F'}, + {"0SK1&V", 'F'}, + {"0SK1;", 'F'}, + {"0SK1;C", 'F'}, + {"0SK1;E", 'F'}, + {"0SK1;T", 'F'}, + {"0SK1B(", 'F'}, + {"0SK1B1", 'F'}, + {"0SK1BF", 'F'}, + {"0SK1BN", 'F'}, + {"0SK1BS", 'F'}, + {"0SK1BV", 'F'}, + {"0SK1C", 'F'}, + {"0SK1E(", 'F'}, + {"0SK1E1", 'F'}, + {"0SK1EF", 'F'}, + {"0SK1EK", 'F'}, + {"0SK1EN", 'F'}, + {"0SK1ES", 'F'}, + {"0SK1EV", 'F'}, + {"0SK1O(", 'F'}, + {"0SK1OF", 'F'}, + {"0SK1OS", 'F'}, + {"0SK1OV", 'F'}, + {"0SK1U(", 'F'}, + {"0SK1UE", 'F'}, + {"0SKF()", 'F'}, + {"0SKF(1", 'F'}, + {"0SKF(F", 'F'}, + {"0SKF(N", 'F'}, + {"0SKF(S", 'F'}, + {"0SKF(V", 'F'}, + {"0SKN", 'F'}, + {"0SKN&(", 'F'}, + {"0SKN&1", 'F'}, + {"0SKN&F", 'F'}, + {"0SKN&N", 'F'}, + {"0SKN&S", 'F'}, + {"0SKN&V", 'F'}, + {"0SKN;", 'F'}, + {"0SKN;C", 'F'}, + {"0SKN;E", 'F'}, + {"0SKN;T", 'F'}, + {"0SKNB(", 'F'}, + {"0SKNB1", 'F'}, + {"0SKNBF", 'F'}, + {"0SKNBN", 'F'}, + {"0SKNBS", 'F'}, + {"0SKNBV", 'F'}, + {"0SKNC", 'F'}, + {"0SKNE(", 'F'}, + {"0SKNE1", 'F'}, + {"0SKNEF", 'F'}, + {"0SKNEN", 'F'}, + {"0SKNES", 'F'}, + {"0SKNEV", 'F'}, + {"0SKNU(", 'F'}, + {"0SKNUE", 'F'}, + {"0SKS", 'F'}, + {"0SKS&(", 'F'}, + {"0SKS&1", 'F'}, + {"0SKS&F", 'F'}, + {"0SKS&N", 'F'}, + {"0SKS&S", 'F'}, + {"0SKS&V", 'F'}, + {"0SKS;", 'F'}, + {"0SKS;C", 'F'}, + {"0SKS;E", 'F'}, + {"0SKS;T", 'F'}, + {"0SKSB(", 'F'}, + {"0SKSB1", 'F'}, + {"0SKSBF", 'F'}, + {"0SKSBN", 'F'}, + {"0SKSBS", 'F'}, + {"0SKSBV", 'F'}, + {"0SKSC", 'F'}, + {"0SKSE(", 'F'}, + {"0SKSE1", 'F'}, + {"0SKSEF", 'F'}, + {"0SKSEK", 'F'}, + {"0SKSEN", 'F'}, + {"0SKSES", 'F'}, + {"0SKSEV", 'F'}, + {"0SKSO(", 'F'}, + {"0SKSO1", 'F'}, + {"0SKSOF", 'F'}, + {"0SKSON", 'F'}, + {"0SKSOS", 'F'}, + {"0SKSOV", 'F'}, + {"0SKSU(", 'F'}, + {"0SKSUE", 'F'}, + {"0SKUE(", 'F'}, + {"0SKUE1", 'F'}, + {"0SKUEF", 'F'}, + {"0SKUEK", 'F'}, + {"0SKUEN", 'F'}, + {"0SKUES", 'F'}, + {"0SKUEV", 'F'}, + {"0SKV", 'F'}, + {"0SKV&(", 'F'}, + {"0SKV&1", 'F'}, + {"0SKV&F", 'F'}, + {"0SKV&N", 'F'}, + {"0SKV&S", 'F'}, + {"0SKV&V", 'F'}, + {"0SKV;", 'F'}, + {"0SKV;C", 'F'}, + {"0SKV;E", 'F'}, + {"0SKV;T", 'F'}, + {"0SKVB(", 'F'}, + {"0SKVB1", 'F'}, + {"0SKVBF", 'F'}, + {"0SKVBN", 'F'}, + {"0SKVBS", 'F'}, + {"0SKVBV", 'F'}, + {"0SKVC", 'F'}, + {"0SKVE(", 'F'}, + {"0SKVE1", 'F'}, + {"0SKVEF", 'F'}, + {"0SKVEK", 'F'}, + {"0SKVEN", 'F'}, + {"0SKVES", 'F'}, + {"0SKVEV", 'F'}, + {"0SKVO(", 'F'}, + {"0SKVOF", 'F'}, + {"0SKVOS", 'F'}, + {"0SKVU(", 'F'}, + {"0SKVUE", 'F'}, + {"0SO(1&", 'F'}, + {"0SO(1)", 'F'}, + {"0SO(1,", 'F'}, + {"0SO(1O", 'F'}, + {"0SO(E(", 'F'}, + {"0SO(E1", 'F'}, + {"0SO(EE", 'F'}, + {"0SO(EF", 'F'}, + {"0SO(EK", 'F'}, + {"0SO(EN", 'F'}, + {"0SO(EO", 'F'}, + {"0SO(ES", 'F'}, + {"0SO(EV", 'F'}, + {"0SO(F(", 'F'}, + {"0SO(N&", 'F'}, + {"0SO(N)", 'F'}, + {"0SO(N,", 'F'}, + {"0SO(NO", 'F'}, + {"0SO(S&", 'F'}, + {"0SO(S)", 'F'}, + {"0SO(S,", 'F'}, + {"0SO(SO", 'F'}, + {"0SO(V&", 'F'}, + {"0SO(V)", 'F'}, + {"0SO(V,", 'F'}, + {"0SO(VO", 'F'}, + {"0SO1&(", 'F'}, + {"0SO1&1", 'F'}, + {"0SO1&E", 'F'}, + {"0SO1&F", 'F'}, + {"0SO1&K", 'F'}, + {"0SO1&N", 'F'}, + {"0SO1&S", 'F'}, + {"0SO1&U", 'F'}, + {"0SO1&V", 'F'}, + {"0SO1(E", 'F'}, + {"0SO1(U", 'F'}, + {"0SO1)&", 'F'}, + {"0SO1),", 'F'}, + {"0SO1);", 'F'}, + {"0SO1)B", 'F'}, + {"0SO1)C", 'F'}, + {"0SO1)E", 'F'}, + {"0SO1)F", 'F'}, + {"0SO1)K", 'F'}, + {"0SO1)O", 'F'}, + {"0SO1)U", 'F'}, + {"0SO1,(", 'F'}, + {"0SO1,F", 'F'}, + {"0SO1;", 'F'}, + {"0SO1;C", 'F'}, + {"0SO1;E", 'F'}, + {"0SO1;N", 'F'}, + {"0SO1;T", 'F'}, + {"0SO1A(", 'F'}, + {"0SO1AF", 'F'}, + {"0SO1AS", 'F'}, + {"0SO1AT", 'F'}, + {"0SO1AV", 'F'}, + {"0SO1B(", 'F'}, + {"0SO1B1", 'F'}, + {"0SO1BE", 'F'}, + {"0SO1BF", 'F'}, + {"0SO1BN", 'F'}, + {"0SO1BS", 'F'}, + {"0SO1BV", 'F'}, + {"0SO1C", 'F'}, + {"0SO1E(", 'F'}, + {"0SO1E1", 'F'}, + {"0SO1EF", 'F'}, + {"0SO1EK", 'F'}, + {"0SO1EN", 'F'}, + {"0SO1EO", 'F'}, + {"0SO1ES", 'F'}, + {"0SO1EU", 'F'}, + {"0SO1EV", 'F'}, + {"0SO1F(", 'F'}, + {"0SO1K(", 'F'}, + {"0SO1K)", 'F'}, + {"0SO1K1", 'F'}, + {"0SO1KB", 'F'}, + {"0SO1KF", 'F'}, + {"0SO1KN", 'F'}, + {"0SO1KS", 'F'}, + {"0SO1KU", 'F'}, + {"0SO1KV", 'F'}, + {"0SO1N&", 'F'}, + {"0SO1N(", 'F'}, + {"0SO1N,", 'F'}, + {"0SO1NE", 'F'}, + {"0SO1NU", 'F'}, + {"0SO1SU", 'F'}, + {"0SO1SV", 'F'}, + {"0SO1T(", 'F'}, + {"0SO1T1", 'F'}, + {"0SO1TE", 'F'}, + {"0SO1TF", 'F'}, + {"0SO1TN", 'F'}, + {"0SO1TS", 'F'}, + {"0SO1TT", 'F'}, + {"0SO1TV", 'F'}, + {"0SO1U", 'F'}, + {"0SO1U(", 'F'}, + {"0SO1U1", 'F'}, + {"0SO1U;", 'F'}, + {"0SO1UC", 'F'}, + {"0SO1UE", 'F'}, + {"0SO1UF", 'F'}, + {"0SO1UK", 'F'}, + {"0SO1UO", 'F'}, + {"0SO1US", 'F'}, + {"0SO1UT", 'F'}, + {"0SO1UV", 'F'}, + {"0SO1V(", 'F'}, + {"0SO1VF", 'F'}, + {"0SO1VO", 'F'}, + {"0SO1VS", 'F'}, + {"0SO1VU", 'F'}, + {"0SOF()", 'F'}, + {"0SOF(1", 'F'}, + {"0SOF(E", 'F'}, + {"0SOF(F", 'F'}, + {"0SOF(N", 'F'}, + {"0SOF(S", 'F'}, + {"0SOF(V", 'F'}, + {"0SOK&(", 'F'}, + {"0SOK&1", 'F'}, + {"0SOK&F", 'F'}, + {"0SOK&N", 'F'}, + {"0SOK&S", 'F'}, + {"0SOK&V", 'F'}, + {"0SOK(1", 'F'}, + {"0SOK(F", 'F'}, + {"0SOK(N", 'F'}, + {"0SOK(S", 'F'}, + {"0SOK(V", 'F'}, + {"0SOK1C", 'F'}, + {"0SOK1O", 'F'}, + {"0SOKF(", 'F'}, + {"0SOKNC", 'F'}, + {"0SOKO(", 'F'}, + {"0SOKO1", 'F'}, + {"0SOKOF", 'F'}, + {"0SOKON", 'F'}, + {"0SOKOS", 'F'}, + {"0SOKOV", 'F'}, + {"0SOKSC", 'F'}, + {"0SOKSO", 'F'}, + {"0SOKVC", 'F'}, + {"0SOKVO", 'F'}, + {"0SON&(", 'F'}, + {"0SON&1", 'F'}, + {"0SON&E", 'F'}, + {"0SON&F", 'F'}, + {"0SON&K", 'F'}, + {"0SON&N", 'F'}, + {"0SON&S", 'F'}, + {"0SON&U", 'F'}, + {"0SON&V", 'F'}, + {"0SON(1", 'F'}, + {"0SON(E", 'F'}, + {"0SON(F", 'F'}, + {"0SON(S", 'F'}, + {"0SON(U", 'F'}, + {"0SON(V", 'F'}, + {"0SON)&", 'F'}, + {"0SON),", 'F'}, + {"0SON);", 'F'}, + {"0SON)B", 'F'}, + {"0SON)C", 'F'}, + {"0SON)E", 'F'}, + {"0SON)F", 'F'}, + {"0SON)K", 'F'}, + {"0SON)O", 'F'}, + {"0SON)U", 'F'}, + {"0SON,(", 'F'}, + {"0SON,F", 'F'}, + {"0SON1(", 'F'}, + {"0SON1O", 'F'}, + {"0SON1U", 'F'}, + {"0SON1V", 'F'}, + {"0SON;", 'F'}, + {"0SON;C", 'F'}, + {"0SON;E", 'F'}, + {"0SON;N", 'F'}, + {"0SON;T", 'F'}, + {"0SONA(", 'F'}, + {"0SONAF", 'F'}, + {"0SONAS", 'F'}, + {"0SONAT", 'F'}, + {"0SONAV", 'F'}, + {"0SONB(", 'F'}, + {"0SONB1", 'F'}, + {"0SONBE", 'F'}, + {"0SONBF", 'F'}, + {"0SONBN", 'F'}, + {"0SONBS", 'F'}, + {"0SONBV", 'F'}, + {"0SONE(", 'F'}, + {"0SONE1", 'F'}, + {"0SONEF", 'F'}, + {"0SONEN", 'F'}, + {"0SONEO", 'F'}, + {"0SONES", 'F'}, + {"0SONEU", 'F'}, + {"0SONEV", 'F'}, + {"0SONF(", 'F'}, + {"0SONK(", 'F'}, + {"0SONK)", 'F'}, + {"0SONK1", 'F'}, + {"0SONKB", 'F'}, + {"0SONKF", 'F'}, + {"0SONKS", 'F'}, + {"0SONKU", 'F'}, + {"0SONKV", 'F'}, + {"0SONSU", 'F'}, + {"0SONT(", 'F'}, + {"0SONT1", 'F'}, + {"0SONTE", 'F'}, + {"0SONTF", 'F'}, + {"0SONTN", 'F'}, + {"0SONTS", 'F'}, + {"0SONTT", 'F'}, + {"0SONTV", 'F'}, + {"0SONU", 'F'}, + {"0SONU(", 'F'}, + {"0SONU1", 'F'}, + {"0SONU;", 'F'}, + {"0SONUC", 'F'}, + {"0SONUE", 'F'}, + {"0SONUF", 'F'}, + {"0SONUK", 'F'}, + {"0SONUO", 'F'}, + {"0SONUS", 'F'}, + {"0SONUT", 'F'}, + {"0SONUV", 'F'}, + {"0SOS", 'F'}, + {"0SOS&(", 'F'}, + {"0SOS&1", 'F'}, + {"0SOS&E", 'F'}, + {"0SOS&F", 'F'}, + {"0SOS&K", 'F'}, + {"0SOS&N", 'F'}, + {"0SOS&S", 'F'}, + {"0SOS&U", 'F'}, + {"0SOS&V", 'F'}, + {"0SOS(E", 'F'}, + {"0SOS(U", 'F'}, + {"0SOS)&", 'F'}, + {"0SOS),", 'F'}, + {"0SOS);", 'F'}, + {"0SOS)B", 'F'}, + {"0SOS)C", 'F'}, + {"0SOS)E", 'F'}, + {"0SOS)F", 'F'}, + {"0SOS)K", 'F'}, + {"0SOS)O", 'F'}, + {"0SOS)U", 'F'}, + {"0SOS,(", 'F'}, + {"0SOS,F", 'F'}, + {"0SOS1(", 'F'}, + {"0SOS1F", 'F'}, + {"0SOS1N", 'F'}, + {"0SOS1S", 'F'}, + {"0SOS1U", 'F'}, + {"0SOS1V", 'F'}, + {"0SOS;", 'F'}, + {"0SOS;C", 'F'}, + {"0SOS;E", 'F'}, + {"0SOS;N", 'F'}, + {"0SOS;T", 'F'}, + {"0SOSA(", 'F'}, + {"0SOSAF", 'F'}, + {"0SOSAS", 'F'}, + {"0SOSAT", 'F'}, + {"0SOSAV", 'F'}, + {"0SOSB(", 'F'}, + {"0SOSB1", 'F'}, + {"0SOSBE", 'F'}, + {"0SOSBF", 'F'}, + {"0SOSBN", 'F'}, + {"0SOSBS", 'F'}, + {"0SOSBV", 'F'}, + {"0SOSC", 'F'}, + {"0SOSE(", 'F'}, + {"0SOSE1", 'F'}, + {"0SOSEF", 'F'}, + {"0SOSEK", 'F'}, + {"0SOSEN", 'F'}, + {"0SOSEO", 'F'}, + {"0SOSES", 'F'}, + {"0SOSEU", 'F'}, + {"0SOSEV", 'F'}, + {"0SOSF(", 'F'}, + {"0SOSK(", 'F'}, + {"0SOSK)", 'F'}, + {"0SOSK1", 'F'}, + {"0SOSKB", 'F'}, + {"0SOSKF", 'F'}, + {"0SOSKN", 'F'}, + {"0SOSKS", 'F'}, + {"0SOSKU", 'F'}, + {"0SOSKV", 'F'}, + {"0SOST(", 'F'}, + {"0SOST1", 'F'}, + {"0SOSTE", 'F'}, + {"0SOSTF", 'F'}, + {"0SOSTN", 'F'}, + {"0SOSTS", 'F'}, + {"0SOSTT", 'F'}, + {"0SOSTV", 'F'}, + {"0SOSU", 'F'}, + {"0SOSU(", 'F'}, + {"0SOSU1", 'F'}, + {"0SOSU;", 'F'}, + {"0SOSUC", 'F'}, + {"0SOSUE", 'F'}, + {"0SOSUF", 'F'}, + {"0SOSUK", 'F'}, + {"0SOSUO", 'F'}, + {"0SOSUS", 'F'}, + {"0SOSUT", 'F'}, + {"0SOSUV", 'F'}, + {"0SOSV(", 'F'}, + {"0SOSVF", 'F'}, + {"0SOSVO", 'F'}, + {"0SOSVS", 'F'}, + {"0SOSVU", 'F'}, + {"0SOU(E", 'F'}, + {"0SOUEK", 'F'}, + {"0SOUEN", 'F'}, + {"0SOV", 'F'}, + {"0SOV&(", 'F'}, + {"0SOV&1", 'F'}, + {"0SOV&E", 'F'}, + {"0SOV&F", 'F'}, + {"0SOV&K", 'F'}, + {"0SOV&N", 'F'}, + {"0SOV&S", 'F'}, + {"0SOV&U", 'F'}, + {"0SOV&V", 'F'}, + {"0SOV(E", 'F'}, + {"0SOV(U", 'F'}, + {"0SOV)&", 'F'}, + {"0SOV),", 'F'}, + {"0SOV);", 'F'}, + {"0SOV)B", 'F'}, + {"0SOV)C", 'F'}, + {"0SOV)E", 'F'}, + {"0SOV)F", 'F'}, + {"0SOV)K", 'F'}, + {"0SOV)O", 'F'}, + {"0SOV)U", 'F'}, + {"0SOV,(", 'F'}, + {"0SOV,F", 'F'}, + {"0SOV;", 'F'}, + {"0SOV;C", 'F'}, + {"0SOV;E", 'F'}, + {"0SOV;N", 'F'}, + {"0SOV;T", 'F'}, + {"0SOVA(", 'F'}, + {"0SOVAF", 'F'}, + {"0SOVAS", 'F'}, + {"0SOVAT", 'F'}, + {"0SOVAV", 'F'}, + {"0SOVB(", 'F'}, + {"0SOVB1", 'F'}, + {"0SOVBE", 'F'}, + {"0SOVBF", 'F'}, + {"0SOVBN", 'F'}, + {"0SOVBS", 'F'}, + {"0SOVBV", 'F'}, + {"0SOVC", 'F'}, + {"0SOVE(", 'F'}, + {"0SOVE1", 'F'}, + {"0SOVEF", 'F'}, + {"0SOVEK", 'F'}, + {"0SOVEN", 'F'}, + {"0SOVEO", 'F'}, + {"0SOVES", 'F'}, + {"0SOVEU", 'F'}, + {"0SOVEV", 'F'}, + {"0SOVF(", 'F'}, + {"0SOVK(", 'F'}, + {"0SOVK)", 'F'}, + {"0SOVK1", 'F'}, + {"0SOVKB", 'F'}, + {"0SOVKF", 'F'}, + {"0SOVKN", 'F'}, + {"0SOVKS", 'F'}, + {"0SOVKU", 'F'}, + {"0SOVKV", 'F'}, + {"0SOVO(", 'F'}, + {"0SOVOF", 'F'}, + {"0SOVOK", 'F'}, + {"0SOVOS", 'F'}, + {"0SOVOU", 'F'}, + {"0SOVS(", 'F'}, + {"0SOVS1", 'F'}, + {"0SOVSF", 'F'}, + {"0SOVSO", 'F'}, + {"0SOVSU", 'F'}, + {"0SOVSV", 'F'}, + {"0SOVT(", 'F'}, + {"0SOVT1", 'F'}, + {"0SOVTE", 'F'}, + {"0SOVTF", 'F'}, + {"0SOVTN", 'F'}, + {"0SOVTS", 'F'}, + {"0SOVTT", 'F'}, + {"0SOVTV", 'F'}, + {"0SOVU", 'F'}, + {"0SOVU(", 'F'}, + {"0SOVU1", 'F'}, + {"0SOVU;", 'F'}, + {"0SOVUC", 'F'}, + {"0SOVUE", 'F'}, + {"0SOVUF", 'F'}, + {"0SOVUK", 'F'}, + {"0SOVUO", 'F'}, + {"0SOVUS", 'F'}, + {"0SOVUT", 'F'}, + {"0SOVUV", 'F'}, + {"0ST(1)", 'F'}, + {"0ST(1O", 'F'}, + {"0ST(F(", 'F'}, + {"0ST(N)", 'F'}, + {"0ST(NO", 'F'}, + {"0ST(S)", 'F'}, + {"0ST(SO", 'F'}, + {"0ST(V)", 'F'}, + {"0ST(VO", 'F'}, + {"0ST1(F", 'F'}, + {"0ST1O(", 'F'}, + {"0ST1OF", 'F'}, + {"0ST1OS", 'F'}, + {"0ST1OV", 'F'}, + {"0STE(1", 'F'}, + {"0STE(F", 'F'}, + {"0STE(N", 'F'}, + {"0STE(S", 'F'}, + {"0STE(V", 'F'}, + {"0STE1N", 'F'}, + {"0STE1O", 'F'}, + {"0STEF(", 'F'}, + {"0STEK(", 'F'}, + {"0STEK1", 'F'}, + {"0STEKF", 'F'}, + {"0STEKN", 'F'}, + {"0STEKS", 'F'}, + {"0STEKV", 'F'}, + {"0STENN", 'F'}, + {"0STENO", 'F'}, + {"0STESN", 'F'}, + {"0STESO", 'F'}, + {"0STEVN", 'F'}, + {"0STEVO", 'F'}, + {"0STF()", 'F'}, + {"0STF(1", 'F'}, + {"0STF(F", 'F'}, + {"0STF(N", 'F'}, + {"0STF(S", 'F'}, + {"0STF(V", 'F'}, + {"0STN(1", 'F'}, + {"0STN(F", 'F'}, + {"0STN(S", 'F'}, + {"0STN(V", 'F'}, + {"0STN1C", 'F'}, + {"0STN1O", 'F'}, + {"0STN;E", 'F'}, + {"0STN;N", 'F'}, + {"0STN;T", 'F'}, + {"0STNE(", 'F'}, + {"0STNE1", 'F'}, + {"0STNEF", 'F'}, + {"0STNEN", 'F'}, + {"0STNES", 'F'}, + {"0STNEV", 'F'}, + {"0STNF(", 'F'}, + {"0STNKN", 'F'}, + {"0STNN:", 'F'}, + {"0STNNC", 'F'}, + {"0STNNO", 'F'}, + {"0STNO(", 'F'}, + {"0STNOF", 'F'}, + {"0STNOS", 'F'}, + {"0STNOV", 'F'}, + {"0STNSC", 'F'}, + {"0STNSO", 'F'}, + {"0STNT(", 'F'}, + {"0STNT1", 'F'}, + {"0STNTF", 'F'}, + {"0STNTN", 'F'}, + {"0STNTS", 'F'}, + {"0STNTV", 'F'}, + {"0STNVC", 'F'}, + {"0STNVO", 'F'}, + {"0STS(F", 'F'}, + {"0STSO(", 'F'}, + {"0STSO1", 'F'}, + {"0STSOF", 'F'}, + {"0STSON", 'F'}, + {"0STSOS", 'F'}, + {"0STSOV", 'F'}, + {"0STTNE", 'F'}, + {"0STTNK", 'F'}, + {"0STTNN", 'F'}, + {"0STTNT", 'F'}, + {"0STV(1", 'F'}, + {"0STV(F", 'F'}, + {"0STVO(", 'F'}, + {"0STVOF", 'F'}, + {"0STVOS", 'F'}, + {"0SU(1)", 'F'}, + {"0SU(1O", 'F'}, + {"0SU(E(", 'F'}, + {"0SU(E1", 'F'}, + {"0SU(EF", 'F'}, + {"0SU(EK", 'F'}, + {"0SU(EN", 'F'}, + {"0SU(ES", 'F'}, + {"0SU(EV", 'F'}, + {"0SU(F(", 'F'}, + {"0SU(N)", 'F'}, + {"0SU(NO", 'F'}, + {"0SU(S)", 'F'}, + {"0SU(SO", 'F'}, + {"0SU(V)", 'F'}, + {"0SU(VO", 'F'}, + {"0SU1,(", 'F'}, + {"0SU1,F", 'F'}, + {"0SU1C", 'F'}, + {"0SU1O(", 'F'}, + {"0SU1OF", 'F'}, + {"0SU1OS", 'F'}, + {"0SU1OV", 'F'}, + {"0SU;", 'F'}, + {"0SU;C", 'F'}, + {"0SUC", 'F'}, + {"0SUE", 'F'}, + {"0SUE(1", 'F'}, + {"0SUE(E", 'F'}, + {"0SUE(F", 'F'}, + {"0SUE(N", 'F'}, + {"0SUE(O", 'F'}, + {"0SUE(S", 'F'}, + {"0SUE(V", 'F'}, + {"0SUE1", 'F'}, + {"0SUE1&", 'F'}, + {"0SUE1(", 'F'}, + {"0SUE1)", 'F'}, + {"0SUE1,", 'F'}, + {"0SUE1;", 'F'}, + {"0SUE1B", 'F'}, + {"0SUE1C", 'F'}, + {"0SUE1F", 'F'}, + {"0SUE1K", 'F'}, + {"0SUE1N", 'F'}, + {"0SUE1O", 'F'}, + {"0SUE1S", 'F'}, + {"0SUE1U", 'F'}, + {"0SUE1V", 'F'}, + {"0SUE;", 'F'}, + {"0SUE;C", 'F'}, + {"0SUEC", 'F'}, + {"0SUEF", 'F'}, + {"0SUEF(", 'F'}, + {"0SUEF,", 'F'}, + {"0SUEF;", 'F'}, + {"0SUEFC", 'F'}, + {"0SUEK", 'F'}, + {"0SUEK(", 'F'}, + {"0SUEK1", 'F'}, + {"0SUEK;", 'F'}, + {"0SUEKC", 'F'}, + {"0SUEKF", 'F'}, + {"0SUEKN", 'F'}, + {"0SUEKO", 'F'}, + {"0SUEKS", 'F'}, + {"0SUEKV", 'F'}, + {"0SUEN", 'F'}, + {"0SUEN&", 'F'}, + {"0SUEN(", 'F'}, + {"0SUEN)", 'F'}, + {"0SUEN,", 'F'}, + {"0SUEN1", 'F'}, + {"0SUEN;", 'F'}, + {"0SUENB", 'F'}, + {"0SUENC", 'F'}, + {"0SUENF", 'F'}, + {"0SUENK", 'F'}, + {"0SUENO", 'F'}, + {"0SUENS", 'F'}, + {"0SUENU", 'F'}, + {"0SUEOK", 'F'}, + {"0SUEON", 'F'}, + {"0SUES", 'F'}, + {"0SUES&", 'F'}, + {"0SUES(", 'F'}, + {"0SUES)", 'F'}, + {"0SUES,", 'F'}, + {"0SUES1", 'F'}, + {"0SUES;", 'F'}, + {"0SUESB", 'F'}, + {"0SUESC", 'F'}, + {"0SUESF", 'F'}, + {"0SUESK", 'F'}, + {"0SUESO", 'F'}, + {"0SUESU", 'F'}, + {"0SUESV", 'F'}, + {"0SUEV", 'F'}, + {"0SUEV&", 'F'}, + {"0SUEV(", 'F'}, + {"0SUEV)", 'F'}, + {"0SUEV,", 'F'}, + {"0SUEV;", 'F'}, + {"0SUEVB", 'F'}, + {"0SUEVC", 'F'}, + {"0SUEVF", 'F'}, + {"0SUEVK", 'F'}, + {"0SUEVN", 'F'}, + {"0SUEVO", 'F'}, + {"0SUEVS", 'F'}, + {"0SUEVU", 'F'}, + {"0SUF()", 'F'}, + {"0SUF(1", 'F'}, + {"0SUF(F", 'F'}, + {"0SUF(N", 'F'}, + {"0SUF(S", 'F'}, + {"0SUF(V", 'F'}, + {"0SUK(E", 'F'}, + {"0SUO(E", 'F'}, + {"0SUON(", 'F'}, + {"0SUON1", 'F'}, + {"0SUONF", 'F'}, + {"0SUONS", 'F'}, + {"0SUS,(", 'F'}, + {"0SUS,F", 'F'}, + {"0SUSC", 'F'}, + {"0SUSO(", 'F'}, + {"0SUSO1", 'F'}, + {"0SUSOF", 'F'}, + {"0SUSON", 'F'}, + {"0SUSOS", 'F'}, + {"0SUSOV", 'F'}, + {"0SUTN(", 'F'}, + {"0SUTN1", 'F'}, + {"0SUTNF", 'F'}, + {"0SUTNN", 'F'}, + {"0SUTNS", 'F'}, + {"0SUTNV", 'F'}, + {"0SUV,(", 'F'}, + {"0SUV,F", 'F'}, + {"0SUVC", 'F'}, + {"0SUVO(", 'F'}, + {"0SUVOF", 'F'}, + {"0SUVOS", 'F'}, + {"0SVF()", 'F'}, + {"0SVF(1", 'F'}, + {"0SVF(F", 'F'}, + {"0SVF(N", 'F'}, + {"0SVF(S", 'F'}, + {"0SVF(V", 'F'}, + {"0SVO(1", 'F'}, + {"0SVO(F", 'F'}, + {"0SVO(N", 'F'}, + {"0SVO(S", 'F'}, + {"0SVO(V", 'F'}, + {"0SVOF(", 'F'}, + {"0SVOS(", 'F'}, + {"0SVOS1", 'F'}, + {"0SVOSF", 'F'}, + {"0SVOSU", 'F'}, + {"0SVOSV", 'F'}, + {"0SVS;", 'F'}, + {"0SVS;C", 'F'}, + {"0SVSC", 'F'}, + {"0SVSO(", 'F'}, + {"0SVSO1", 'F'}, + {"0SVSOF", 'F'}, + {"0SVSON", 'F'}, + {"0SVSOS", 'F'}, + {"0SVSOV", 'F'}, + {"0SVUE", 'F'}, + {"0SVUE;", 'F'}, + {"0SVUEC", 'F'}, + {"0SVUEK", 'F'}, + {"0T(1)F", 'F'}, + {"0T(1)O", 'F'}, + {"0T(1F(", 'F'}, + {"0T(1N)", 'F'}, + {"0T(1O(", 'F'}, + {"0T(1OF", 'F'}, + {"0T(1OS", 'F'}, + {"0T(1OV", 'F'}, + {"0T(1S)", 'F'}, + {"0T(1V)", 'F'}, + {"0T(1VO", 'F'}, + {"0T(F()", 'F'}, + {"0T(F(1", 'F'}, + {"0T(F(F", 'F'}, + {"0T(F(N", 'F'}, + {"0T(F(S", 'F'}, + {"0T(F(V", 'F'}, + {"0T(N(1", 'F'}, + {"0T(N(F", 'F'}, + {"0T(N(S", 'F'}, + {"0T(N(V", 'F'}, + {"0T(N)F", 'F'}, + {"0T(N)O", 'F'}, + {"0T(N1)", 'F'}, + {"0T(N1O", 'F'}, + {"0T(NF(", 'F'}, + {"0T(NN)", 'F'}, + {"0T(NNO", 'F'}, + {"0T(NO(", 'F'}, + {"0T(NOF", 'F'}, + {"0T(NOS", 'F'}, + {"0T(NOV", 'F'}, + {"0T(NS)", 'F'}, + {"0T(NSO", 'F'}, + {"0T(NV)", 'F'}, + {"0T(NVO", 'F'}, + {"0T(S)F", 'F'}, + {"0T(S)O", 'F'}, + {"0T(S1)", 'F'}, + {"0T(SF(", 'F'}, + {"0T(SN)", 'F'}, + {"0T(SNO", 'F'}, + {"0T(SO(", 'F'}, + {"0T(SO1", 'F'}, + {"0T(SOF", 'F'}, + {"0T(SON", 'F'}, + {"0T(SOS", 'F'}, + {"0T(SOV", 'F'}, + {"0T(SV)", 'F'}, + {"0T(SVO", 'F'}, + {"0T(V)F", 'F'}, + {"0T(V)O", 'F'}, + {"0T(VF(", 'F'}, + {"0T(VO(", 'F'}, + {"0T(VOF", 'F'}, + {"0T(VOS", 'F'}, + {"0T(VS)", 'F'}, + {"0T(VSO", 'F'}, + {"0T(VV)", 'F'}, + {"0T1F(1", 'F'}, + {"0T1F(F", 'F'}, + {"0T1F(N", 'F'}, + {"0T1F(S", 'F'}, + {"0T1F(V", 'F'}, + {"0T1O(1", 'F'}, + {"0T1O(F", 'F'}, + {"0T1O(N", 'F'}, + {"0T1O(S", 'F'}, + {"0T1O(V", 'F'}, + {"0T1OF(", 'F'}, + {"0T1OSF", 'F'}, + {"0T1OVF", 'F'}, + {"0T1OVO", 'F'}, + {"0TF()F", 'F'}, + {"0TF()O", 'F'}, + {"0TF(1)", 'F'}, + {"0TF(1O", 'F'}, + {"0TF(F(", 'F'}, + {"0TF(N)", 'F'}, + {"0TF(NO", 'F'}, + {"0TF(S)", 'F'}, + {"0TF(SO", 'F'}, + {"0TF(V)", 'F'}, + {"0TF(VO", 'F'}, + {"0TN(1)", 'F'}, + {"0TN(1O", 'F'}, + {"0TN(F(", 'F'}, + {"0TN(S)", 'F'}, + {"0TN(SO", 'F'}, + {"0TN(V)", 'F'}, + {"0TN(VO", 'F'}, + {"0TN1;", 'F'}, + {"0TN1;C", 'F'}, + {"0TN1O(", 'F'}, + {"0TN1OF", 'F'}, + {"0TN1OS", 'F'}, + {"0TN1OV", 'F'}, + {"0TNF()", 'F'}, + {"0TNF(1", 'F'}, + {"0TNF(F", 'F'}, + {"0TNF(N", 'F'}, + {"0TNF(S", 'F'}, + {"0TNF(V", 'F'}, + {"0TNN;", 'F'}, + {"0TNN;C", 'F'}, + {"0TNNO(", 'F'}, + {"0TNNOF", 'F'}, + {"0TNNOS", 'F'}, + {"0TNNOV", 'F'}, + {"0TNO(1", 'F'}, + {"0TNO(F", 'F'}, + {"0TNO(N", 'F'}, + {"0TNO(S", 'F'}, + {"0TNO(V", 'F'}, + {"0TNOF(", 'F'}, + {"0TNOSF", 'F'}, + {"0TNOVF", 'F'}, + {"0TNOVO", 'F'}, + {"0TNS;", 'F'}, + {"0TNS;C", 'F'}, + {"0TNSO(", 'F'}, + {"0TNSO1", 'F'}, + {"0TNSOF", 'F'}, + {"0TNSON", 'F'}, + {"0TNSOS", 'F'}, + {"0TNSOV", 'F'}, + {"0TNV;", 'F'}, + {"0TNV;C", 'F'}, + {"0TNVO(", 'F'}, + {"0TNVOF", 'F'}, + {"0TNVOS", 'F'}, + {"0TSF(1", 'F'}, + {"0TSF(F", 'F'}, + {"0TSF(N", 'F'}, + {"0TSF(S", 'F'}, + {"0TSF(V", 'F'}, + {"0TSO(1", 'F'}, + {"0TSO(F", 'F'}, + {"0TSO(N", 'F'}, + {"0TSO(S", 'F'}, + {"0TSO(V", 'F'}, + {"0TSO1F", 'F'}, + {"0TSOF(", 'F'}, + {"0TSONF", 'F'}, + {"0TSOSF", 'F'}, + {"0TSOVF", 'F'}, + {"0TSOVO", 'F'}, + {"0TVF(1", 'F'}, + {"0TVF(F", 'F'}, + {"0TVF(N", 'F'}, + {"0TVF(S", 'F'}, + {"0TVF(V", 'F'}, + {"0TVO(1", 'F'}, + {"0TVO(F", 'F'}, + {"0TVO(N", 'F'}, + {"0TVO(S", 'F'}, + {"0TVO(V", 'F'}, + {"0TVOF(", 'F'}, + {"0TVOSF", 'F'}, + {"0U(E(1", 'F'}, + {"0U(E(F", 'F'}, + {"0U(E(K", 'F'}, + {"0U(E(N", 'F'}, + {"0U(E(S", 'F'}, + {"0U(E(V", 'F'}, + {"0U(E1)", 'F'}, + {"0U(E1O", 'F'}, + {"0U(EF(", 'F'}, + {"0U(EK(", 'F'}, + {"0U(EK1", 'F'}, + {"0U(EKF", 'F'}, + {"0U(EKN", 'F'}, + {"0U(EKO", 'F'}, + {"0U(EKS", 'F'}, + {"0U(EKV", 'F'}, + {"0U(EN)", 'F'}, + {"0U(ENK", 'F'}, + {"0U(ENO", 'F'}, + {"0U(EOK", 'F'}, + {"0U(ES)", 'F'}, + {"0U(ESO", 'F'}, + {"0U(EV)", 'F'}, + {"0U(EVO", 'F'}, + {"0UE(1)", 'F'}, + {"0UE(1,", 'F'}, + {"0UE(1O", 'F'}, + {"0UE(F(", 'F'}, + {"0UE(N)", 'F'}, + {"0UE(N,", 'F'}, + {"0UE(NO", 'F'}, + {"0UE(S)", 'F'}, + {"0UE(S,", 'F'}, + {"0UE(SO", 'F'}, + {"0UE(V)", 'F'}, + {"0UE(V,", 'F'}, + {"0UE(VO", 'F'}, + {"0UE1", 'F'}, + {"0UE1,(", 'F'}, + {"0UE1,F", 'F'}, + {"0UE1;", 'F'}, + {"0UE1;C", 'F'}, + {"0UE1C", 'F'}, + {"0UE1K(", 'F'}, + {"0UE1K1", 'F'}, + {"0UE1KF", 'F'}, + {"0UE1KN", 'F'}, + {"0UE1KS", 'F'}, + {"0UE1KV", 'F'}, + {"0UE1O(", 'F'}, + {"0UE1OF", 'F'}, + {"0UE1OS", 'F'}, + {"0UE1OV", 'F'}, + {"0UEF()", 'F'}, + {"0UEF(1", 'F'}, + {"0UEF(F", 'F'}, + {"0UEF(N", 'F'}, + {"0UEF(S", 'F'}, + {"0UEF(V", 'F'}, + {"0UEK(1", 'F'}, + {"0UEK(F", 'F'}, + {"0UEK(N", 'F'}, + {"0UEK(S", 'F'}, + {"0UEK(V", 'F'}, + {"0UEK1", 'F'}, + {"0UEK1,", 'F'}, + {"0UEK1;", 'F'}, + {"0UEK1C", 'F'}, + {"0UEK1K", 'F'}, + {"0UEK1O", 'F'}, + {"0UEKF(", 'F'}, + {"0UEKN", 'F'}, + {"0UEKN(", 'F'}, + {"0UEKN,", 'F'}, + {"0UEKN;", 'F'}, + {"0UEKNC", 'F'}, + {"0UEKNK", 'F'}, + {"0UEKS", 'F'}, + {"0UEKS,", 'F'}, + {"0UEKS;", 'F'}, + {"0UEKSC", 'F'}, + {"0UEKSK", 'F'}, + {"0UEKSO", 'F'}, + {"0UEKV", 'F'}, + {"0UEKV,", 'F'}, + {"0UEKV;", 'F'}, + {"0UEKVC", 'F'}, + {"0UEKVK", 'F'}, + {"0UEKVO", 'F'}, + {"0UEN()", 'F'}, + {"0UEN,(", 'F'}, + {"0UEN,F", 'F'}, + {"0UEN;", 'F'}, + {"0UEN;C", 'F'}, + {"0UENC", 'F'}, + {"0UENK(", 'F'}, + {"0UENK1", 'F'}, + {"0UENKF", 'F'}, + {"0UENKN", 'F'}, + {"0UENKS", 'F'}, + {"0UENKV", 'F'}, + {"0UENO(", 'F'}, + {"0UENOF", 'F'}, + {"0UENOS", 'F'}, + {"0UENOV", 'F'}, + {"0UES", 'F'}, + {"0UES,(", 'F'}, + {"0UES,F", 'F'}, + {"0UES;", 'F'}, + {"0UES;C", 'F'}, + {"0UESC", 'F'}, + {"0UESK(", 'F'}, + {"0UESK1", 'F'}, + {"0UESKF", 'F'}, + {"0UESKN", 'F'}, + {"0UESKS", 'F'}, + {"0UESKV", 'F'}, + {"0UESO(", 'F'}, + {"0UESO1", 'F'}, + {"0UESOF", 'F'}, + {"0UESON", 'F'}, + {"0UESOS", 'F'}, + {"0UESOV", 'F'}, + {"0UEV", 'F'}, + {"0UEV,(", 'F'}, + {"0UEV,F", 'F'}, + {"0UEV;", 'F'}, + {"0UEV;C", 'F'}, + {"0UEVC", 'F'}, + {"0UEVK(", 'F'}, + {"0UEVK1", 'F'}, + {"0UEVKF", 'F'}, + {"0UEVKN", 'F'}, + {"0UEVKS", 'F'}, + {"0UEVKV", 'F'}, + {"0UEVO(", 'F'}, + {"0UEVOF", 'F'}, + {"0UEVOS", 'F'}, + {"0UF(1O", 'F'}, + {"0UF(F(", 'F'}, + {"0UF(NO", 'F'}, + {"0UF(SO", 'F'}, + {"0UF(VO", 'F'}, + {"0V&(1&", 'F'}, + {"0V&(1)", 'F'}, + {"0V&(1,", 'F'}, + {"0V&(1O", 'F'}, + {"0V&(E(", 'F'}, + {"0V&(E1", 'F'}, + {"0V&(EF", 'F'}, + {"0V&(EK", 'F'}, + {"0V&(EN", 'F'}, + {"0V&(EO", 'F'}, + {"0V&(ES", 'F'}, + {"0V&(EV", 'F'}, + {"0V&(F(", 'F'}, + {"0V&(N&", 'F'}, + {"0V&(N)", 'F'}, + {"0V&(N,", 'F'}, + {"0V&(NO", 'F'}, + {"0V&(S&", 'F'}, + {"0V&(S)", 'F'}, + {"0V&(S,", 'F'}, + {"0V&(SO", 'F'}, + {"0V&(V&", 'F'}, + {"0V&(V)", 'F'}, + {"0V&(V,", 'F'}, + {"0V&(VO", 'F'}, + {"0V&1", 'F'}, + {"0V&1&(", 'F'}, + {"0V&1&1", 'F'}, + {"0V&1&F", 'F'}, + {"0V&1&N", 'F'}, + {"0V&1&S", 'F'}, + {"0V&1&V", 'F'}, + {"0V&1)&", 'F'}, + {"0V&1)C", 'F'}, + {"0V&1)O", 'F'}, + {"0V&1)U", 'F'}, + {"0V&1;", 'F'}, + {"0V&1;C", 'F'}, + {"0V&1;E", 'F'}, + {"0V&1;T", 'F'}, + {"0V&1B(", 'F'}, + {"0V&1B1", 'F'}, + {"0V&1BF", 'F'}, + {"0V&1BN", 'F'}, + {"0V&1BS", 'F'}, + {"0V&1BV", 'F'}, + {"0V&1C", 'F'}, + {"0V&1EK", 'F'}, + {"0V&1EN", 'F'}, + {"0V&1F(", 'F'}, + {"0V&1K(", 'F'}, + {"0V&1K1", 'F'}, + {"0V&1KF", 'F'}, + {"0V&1KN", 'F'}, + {"0V&1KS", 'F'}, + {"0V&1KV", 'F'}, + {"0V&1O(", 'F'}, + {"0V&1OF", 'F'}, + {"0V&1OS", 'F'}, + {"0V&1OV", 'F'}, + {"0V&1TN", 'F'}, + {"0V&1U", 'F'}, + {"0V&1U(", 'F'}, + {"0V&1U;", 'F'}, + {"0V&1UC", 'F'}, + {"0V&1UE", 'F'}, + {"0V&E(1", 'F'}, + {"0V&E(F", 'F'}, + {"0V&E(N", 'F'}, + {"0V&E(O", 'F'}, + {"0V&E(S", 'F'}, + {"0V&E(V", 'F'}, + {"0V&E1", 'F'}, + {"0V&E1;", 'F'}, + {"0V&E1C", 'F'}, + {"0V&E1K", 'F'}, + {"0V&E1O", 'F'}, + {"0V&EF(", 'F'}, + {"0V&EK(", 'F'}, + {"0V&EK1", 'F'}, + {"0V&EKF", 'F'}, + {"0V&EKN", 'F'}, + {"0V&EKS", 'F'}, + {"0V&EKV", 'F'}, + {"0V&EN", 'F'}, + {"0V&EN;", 'F'}, + {"0V&ENC", 'F'}, + {"0V&ENK", 'F'}, + {"0V&ENO", 'F'}, + {"0V&ES", 'F'}, + {"0V&ES;", 'F'}, + {"0V&ESC", 'F'}, + {"0V&ESK", 'F'}, + {"0V&ESO", 'F'}, + {"0V&EV", 'F'}, + {"0V&EV;", 'F'}, + {"0V&EVC", 'F'}, + {"0V&EVK", 'F'}, + {"0V&EVO", 'F'}, + {"0V&F()", 'F'}, + {"0V&F(1", 'F'}, + {"0V&F(E", 'F'}, + {"0V&F(F", 'F'}, + {"0V&F(N", 'F'}, + {"0V&F(S", 'F'}, + {"0V&F(V", 'F'}, + {"0V&K&(", 'F'}, + {"0V&K&1", 'F'}, + {"0V&K&F", 'F'}, + {"0V&K&N", 'F'}, + {"0V&K&S", 'F'}, + {"0V&K&V", 'F'}, + {"0V&K(1", 'F'}, + {"0V&K(F", 'F'}, + {"0V&K(N", 'F'}, + {"0V&K(S", 'F'}, + {"0V&K(V", 'F'}, + {"0V&K1O", 'F'}, + {"0V&KC", 'F'}, + {"0V&KF(", 'F'}, + {"0V&KNK", 'F'}, + {"0V&KO(", 'F'}, + {"0V&KO1", 'F'}, + {"0V&KOF", 'F'}, + {"0V&KOK", 'F'}, + {"0V&KON", 'F'}, + {"0V&KOS", 'F'}, + {"0V&KOV", 'F'}, + {"0V&KSO", 'F'}, + {"0V&KVO", 'F'}, + {"0V&N", 'F'}, + {"0V&N&(", 'F'}, + {"0V&N&1", 'F'}, + {"0V&N&F", 'F'}, + {"0V&N&N", 'F'}, + {"0V&N&S", 'F'}, + {"0V&N&V", 'F'}, + {"0V&N)&", 'F'}, + {"0V&N)C", 'F'}, + {"0V&N)O", 'F'}, + {"0V&N)U", 'F'}, + {"0V&N;", 'F'}, + {"0V&N;C", 'F'}, + {"0V&N;E", 'F'}, + {"0V&N;T", 'F'}, + {"0V&NB(", 'F'}, + {"0V&NB1", 'F'}, + {"0V&NBF", 'F'}, + {"0V&NBN", 'F'}, + {"0V&NBS", 'F'}, + {"0V&NBV", 'F'}, + {"0V&NC", 'F'}, + {"0V&NEN", 'F'}, + {"0V&NF(", 'F'}, + {"0V&NK(", 'F'}, + {"0V&NK1", 'F'}, + {"0V&NKF", 'F'}, + {"0V&NKN", 'F'}, + {"0V&NKS", 'F'}, + {"0V&NKV", 'F'}, + {"0V&NO(", 'F'}, + {"0V&NOF", 'F'}, + {"0V&NOS", 'F'}, + {"0V&NOV", 'F'}, + {"0V&NTN", 'F'}, + {"0V&NU", 'F'}, + {"0V&NU(", 'F'}, + {"0V&NU;", 'F'}, + {"0V&NUC", 'F'}, + {"0V&NUE", 'F'}, + {"0V&S", 'F'}, + {"0V&S&(", 'F'}, + {"0V&S&1", 'F'}, + {"0V&S&F", 'F'}, + {"0V&S&N", 'F'}, + {"0V&S&S", 'F'}, + {"0V&S&V", 'F'}, + {"0V&S)&", 'F'}, + {"0V&S)C", 'F'}, + {"0V&S)O", 'F'}, + {"0V&S)U", 'F'}, + {"0V&S1", 'F'}, + {"0V&S1;", 'F'}, + {"0V&S1C", 'F'}, + {"0V&S;", 'F'}, + {"0V&S;C", 'F'}, + {"0V&S;E", 'F'}, + {"0V&S;T", 'F'}, + {"0V&SB(", 'F'}, + {"0V&SB1", 'F'}, + {"0V&SBF", 'F'}, + {"0V&SBN", 'F'}, + {"0V&SBS", 'F'}, + {"0V&SBV", 'F'}, + {"0V&SC", 'F'}, + {"0V&SEK", 'F'}, + {"0V&SEN", 'F'}, + {"0V&SF(", 'F'}, + {"0V&SK(", 'F'}, + {"0V&SK1", 'F'}, + {"0V&SKF", 'F'}, + {"0V&SKN", 'F'}, + {"0V&SKS", 'F'}, + {"0V&SKV", 'F'}, + {"0V&SO(", 'F'}, + {"0V&SO1", 'F'}, + {"0V&SOF", 'F'}, + {"0V&SON", 'F'}, + {"0V&SOS", 'F'}, + {"0V&SOV", 'F'}, + {"0V&STN", 'F'}, + {"0V&SU", 'F'}, + {"0V&SU(", 'F'}, + {"0V&SU;", 'F'}, + {"0V&SUC", 'F'}, + {"0V&SUE", 'F'}, + {"0V&SV", 'F'}, + {"0V&SV;", 'F'}, + {"0V&SVC", 'F'}, + {"0V&SVO", 'F'}, + {"0V&V", 'F'}, + {"0V&V&(", 'F'}, + {"0V&V&1", 'F'}, + {"0V&V&F", 'F'}, + {"0V&V&N", 'F'}, + {"0V&V&S", 'F'}, + {"0V&V&V", 'F'}, + {"0V&V)&", 'F'}, + {"0V&V)C", 'F'}, + {"0V&V)O", 'F'}, + {"0V&V)U", 'F'}, + {"0V&V;", 'F'}, + {"0V&V;C", 'F'}, + {"0V&V;E", 'F'}, + {"0V&V;T", 'F'}, + {"0V&VB(", 'F'}, + {"0V&VB1", 'F'}, + {"0V&VBF", 'F'}, + {"0V&VBN", 'F'}, + {"0V&VBS", 'F'}, + {"0V&VBV", 'F'}, + {"0V&VC", 'F'}, + {"0V&VEK", 'F'}, + {"0V&VEN", 'F'}, + {"0V&VF(", 'F'}, + {"0V&VK(", 'F'}, + {"0V&VK1", 'F'}, + {"0V&VKF", 'F'}, + {"0V&VKN", 'F'}, + {"0V&VKS", 'F'}, + {"0V&VKV", 'F'}, + {"0V&VO(", 'F'}, + {"0V&VOF", 'F'}, + {"0V&VOS", 'F'}, + {"0V&VS", 'F'}, + {"0V&VS;", 'F'}, + {"0V&VSC", 'F'}, + {"0V&VSO", 'F'}, + {"0V&VTN", 'F'}, + {"0V&VU", 'F'}, + {"0V&VU(", 'F'}, + {"0V&VU;", 'F'}, + {"0V&VUC", 'F'}, + {"0V&VUE", 'F'}, + {"0V(EF(", 'F'}, + {"0V(EKF", 'F'}, + {"0V(EKN", 'F'}, + {"0V(ENK", 'F'}, + {"0V(U(E", 'F'}, + {"0V)&(1", 'F'}, + {"0V)&(E", 'F'}, + {"0V)&(F", 'F'}, + {"0V)&(N", 'F'}, + {"0V)&(S", 'F'}, + {"0V)&(V", 'F'}, + {"0V)&1", 'F'}, + {"0V)&1&", 'F'}, + {"0V)&1)", 'F'}, + {"0V)&1;", 'F'}, + {"0V)&1B", 'F'}, + {"0V)&1C", 'F'}, + {"0V)&1F", 'F'}, + {"0V)&1O", 'F'}, + {"0V)&1U", 'F'}, + {"0V)&F(", 'F'}, + {"0V)&N", 'F'}, + {"0V)&N&", 'F'}, + {"0V)&N)", 'F'}, + {"0V)&N;", 'F'}, + {"0V)&NB", 'F'}, + {"0V)&NC", 'F'}, + {"0V)&NF", 'F'}, + {"0V)&NO", 'F'}, + {"0V)&NU", 'F'}, + {"0V)&S", 'F'}, + {"0V)&S&", 'F'}, + {"0V)&S)", 'F'}, + {"0V)&S;", 'F'}, + {"0V)&SB", 'F'}, + {"0V)&SC", 'F'}, + {"0V)&SF", 'F'}, + {"0V)&SO", 'F'}, + {"0V)&SU", 'F'}, + {"0V)&V", 'F'}, + {"0V)&V&", 'F'}, + {"0V)&V)", 'F'}, + {"0V)&V;", 'F'}, + {"0V)&VB", 'F'}, + {"0V)&VC", 'F'}, + {"0V)&VF", 'F'}, + {"0V)&VO", 'F'}, + {"0V)&VU", 'F'}, + {"0V),(1", 'F'}, + {"0V),(F", 'F'}, + {"0V),(N", 'F'}, + {"0V),(S", 'F'}, + {"0V),(V", 'F'}, + {"0V);E(", 'F'}, + {"0V);E1", 'F'}, + {"0V);EF", 'F'}, + {"0V);EK", 'F'}, + {"0V);EN", 'F'}, + {"0V);EO", 'F'}, + {"0V);ES", 'F'}, + {"0V);EV", 'F'}, + {"0V);T(", 'F'}, + {"0V);T1", 'F'}, + {"0V);TF", 'F'}, + {"0V);TK", 'F'}, + {"0V);TN", 'F'}, + {"0V);TO", 'F'}, + {"0V);TS", 'F'}, + {"0V);TV", 'F'}, + {"0V)B(1", 'F'}, + {"0V)B(F", 'F'}, + {"0V)B(N", 'F'}, + {"0V)B(S", 'F'}, + {"0V)B(V", 'F'}, + {"0V)B1", 'F'}, + {"0V)B1&", 'F'}, + {"0V)B1;", 'F'}, + {"0V)B1C", 'F'}, + {"0V)B1K", 'F'}, + {"0V)B1N", 'F'}, + {"0V)B1O", 'F'}, + {"0V)B1U", 'F'}, + {"0V)BF(", 'F'}, + {"0V)BN", 'F'}, + {"0V)BN&", 'F'}, + {"0V)BN;", 'F'}, + {"0V)BNC", 'F'}, + {"0V)BNK", 'F'}, + {"0V)BNO", 'F'}, + {"0V)BNU", 'F'}, + {"0V)BS", 'F'}, + {"0V)BS&", 'F'}, + {"0V)BS;", 'F'}, + {"0V)BSC", 'F'}, + {"0V)BSK", 'F'}, + {"0V)BSO", 'F'}, + {"0V)BSU", 'F'}, + {"0V)BV", 'F'}, + {"0V)BV&", 'F'}, + {"0V)BV;", 'F'}, + {"0V)BVC", 'F'}, + {"0V)BVK", 'F'}, + {"0V)BVO", 'F'}, + {"0V)BVU", 'F'}, + {"0V)C", 'F'}, + {"0V)E(1", 'F'}, + {"0V)E(F", 'F'}, + {"0V)E(N", 'F'}, + {"0V)E(S", 'F'}, + {"0V)E(V", 'F'}, + {"0V)E1C", 'F'}, + {"0V)E1O", 'F'}, + {"0V)EF(", 'F'}, + {"0V)EK(", 'F'}, + {"0V)EK1", 'F'}, + {"0V)EKF", 'F'}, + {"0V)EKN", 'F'}, + {"0V)EKS", 'F'}, + {"0V)EKV", 'F'}, + {"0V)ENC", 'F'}, + {"0V)ENO", 'F'}, + {"0V)ESC", 'F'}, + {"0V)ESO", 'F'}, + {"0V)EVC", 'F'}, + {"0V)EVO", 'F'}, + {"0V)F(F", 'F'}, + {"0V)K(1", 'F'}, + {"0V)K(F", 'F'}, + {"0V)K(N", 'F'}, + {"0V)K(S", 'F'}, + {"0V)K(V", 'F'}, + {"0V)K1&", 'F'}, + {"0V)K1;", 'F'}, + {"0V)K1B", 'F'}, + {"0V)K1E", 'F'}, + {"0V)K1O", 'F'}, + {"0V)K1U", 'F'}, + {"0V)KB(", 'F'}, + {"0V)KB1", 'F'}, + {"0V)KBF", 'F'}, + {"0V)KBN", 'F'}, + {"0V)KBS", 'F'}, + {"0V)KBV", 'F'}, + {"0V)KF(", 'F'}, + {"0V)KN&", 'F'}, + {"0V)KN;", 'F'}, + {"0V)KNB", 'F'}, + {"0V)KNC", 'F'}, + {"0V)KNE", 'F'}, + {"0V)KNK", 'F'}, + {"0V)KNU", 'F'}, + {"0V)KS&", 'F'}, + {"0V)KS;", 'F'}, + {"0V)KSB", 'F'}, + {"0V)KSE", 'F'}, + {"0V)KSO", 'F'}, + {"0V)KSU", 'F'}, + {"0V)KUE", 'F'}, + {"0V)KV&", 'F'}, + {"0V)KV;", 'F'}, + {"0V)KVB", 'F'}, + {"0V)KVE", 'F'}, + {"0V)KVO", 'F'}, + {"0V)KVU", 'F'}, + {"0V)O(1", 'F'}, + {"0V)O(E", 'F'}, + {"0V)O(F", 'F'}, + {"0V)O(N", 'F'}, + {"0V)O(S", 'F'}, + {"0V)O(V", 'F'}, + {"0V)O1", 'F'}, + {"0V)O1&", 'F'}, + {"0V)O1)", 'F'}, + {"0V)O1;", 'F'}, + {"0V)O1B", 'F'}, + {"0V)O1C", 'F'}, + {"0V)O1K", 'F'}, + {"0V)O1U", 'F'}, + {"0V)OF(", 'F'}, + {"0V)ON", 'F'}, + {"0V)ON&", 'F'}, + {"0V)ON)", 'F'}, + {"0V)ON;", 'F'}, + {"0V)ONB", 'F'}, + {"0V)ONC", 'F'}, + {"0V)ONK", 'F'}, + {"0V)ONU", 'F'}, + {"0V)OS", 'F'}, + {"0V)OS&", 'F'}, + {"0V)OS)", 'F'}, + {"0V)OS;", 'F'}, + {"0V)OSB", 'F'}, + {"0V)OSC", 'F'}, + {"0V)OSK", 'F'}, + {"0V)OSU", 'F'}, + {"0V)OV", 'F'}, + {"0V)OV&", 'F'}, + {"0V)OV)", 'F'}, + {"0V)OV;", 'F'}, + {"0V)OVB", 'F'}, + {"0V)OVC", 'F'}, + {"0V)OVK", 'F'}, + {"0V)OVO", 'F'}, + {"0V)OVU", 'F'}, + {"0V)U(E", 'F'}, + {"0V)UE(", 'F'}, + {"0V)UE1", 'F'}, + {"0V)UEF", 'F'}, + {"0V)UEK", 'F'}, + {"0V)UEN", 'F'}, + {"0V)UES", 'F'}, + {"0V)UEV", 'F'}, + {"0V,(1)", 'F'}, + {"0V,(1O", 'F'}, + {"0V,(E(", 'F'}, + {"0V,(E1", 'F'}, + {"0V,(EF", 'F'}, + {"0V,(EK", 'F'}, + {"0V,(EN", 'F'}, + {"0V,(ES", 'F'}, + {"0V,(EV", 'F'}, + {"0V,(F(", 'F'}, + {"0V,(N)", 'F'}, + {"0V,(NO", 'F'}, + {"0V,(S)", 'F'}, + {"0V,(SO", 'F'}, + {"0V,(V)", 'F'}, + {"0V,(VO", 'F'}, + {"0V,F()", 'F'}, + {"0V,F(1", 'F'}, + {"0V,F(F", 'F'}, + {"0V,F(N", 'F'}, + {"0V,F(S", 'F'}, + {"0V,F(V", 'F'}, + {"0V;E(1", 'F'}, + {"0V;E(E", 'F'}, + {"0V;E(F", 'F'}, + {"0V;E(N", 'F'}, + {"0V;E(S", 'F'}, + {"0V;E(V", 'F'}, + {"0V;E1,", 'F'}, + {"0V;E1;", 'F'}, + {"0V;E1C", 'F'}, + {"0V;E1K", 'F'}, + {"0V;E1O", 'F'}, + {"0V;E1T", 'F'}, + {"0V;EF(", 'F'}, + {"0V;EK(", 'F'}, + {"0V;EK1", 'F'}, + {"0V;EKF", 'F'}, + {"0V;EKN", 'F'}, + {"0V;EKO", 'F'}, + {"0V;EKS", 'F'}, + {"0V;EKV", 'F'}, + {"0V;EN,", 'F'}, + {"0V;EN;", 'F'}, + {"0V;ENC", 'F'}, + {"0V;ENE", 'F'}, + {"0V;ENK", 'F'}, + {"0V;ENO", 'F'}, + {"0V;ENT", 'F'}, + {"0V;ES,", 'F'}, + {"0V;ES;", 'F'}, + {"0V;ESC", 'F'}, + {"0V;ESK", 'F'}, + {"0V;ESO", 'F'}, + {"0V;EST", 'F'}, + {"0V;EV,", 'F'}, + {"0V;EV;", 'F'}, + {"0V;EVC", 'F'}, + {"0V;EVK", 'F'}, + {"0V;EVO", 'F'}, + {"0V;EVT", 'F'}, + {"0V;N:T", 'F'}, + {"0V;T(1", 'F'}, + {"0V;T(C", 'F'}, + {"0V;T(E", 'F'}, + {"0V;T(F", 'F'}, + {"0V;T(N", 'F'}, + {"0V;T(S", 'F'}, + {"0V;T(V", 'F'}, + {"0V;T1(", 'F'}, + {"0V;T1,", 'F'}, + {"0V;T1;", 'F'}, + {"0V;T1C", 'F'}, + {"0V;T1F", 'F'}, + {"0V;T1K", 'F'}, + {"0V;T1O", 'F'}, + {"0V;T1T", 'F'}, + {"0V;T;", 'F'}, + {"0V;T;C", 'F'}, + {"0V;TF(", 'F'}, + {"0V;TK(", 'F'}, + {"0V;TK1", 'F'}, + {"0V;TKF", 'F'}, + {"0V;TKK", 'F'}, + {"0V;TKN", 'F'}, + {"0V;TKO", 'F'}, + {"0V;TKS", 'F'}, + {"0V;TKV", 'F'}, + {"0V;TN(", 'F'}, + {"0V;TN,", 'F'}, + {"0V;TN1", 'F'}, + {"0V;TN;", 'F'}, + {"0V;TNC", 'F'}, + {"0V;TNE", 'F'}, + {"0V;TNF", 'F'}, + {"0V;TNK", 'F'}, + {"0V;TNN", 'F'}, + {"0V;TNO", 'F'}, + {"0V;TNS", 'F'}, + {"0V;TNT", 'F'}, + {"0V;TNV", 'F'}, + {"0V;TO(", 'F'}, + {"0V;TS(", 'F'}, + {"0V;TS,", 'F'}, + {"0V;TS;", 'F'}, + {"0V;TSC", 'F'}, + {"0V;TSF", 'F'}, + {"0V;TSK", 'F'}, + {"0V;TSO", 'F'}, + {"0V;TST", 'F'}, + {"0V;TTN", 'F'}, + {"0V;TV(", 'F'}, + {"0V;TV,", 'F'}, + {"0V;TV;", 'F'}, + {"0V;TVC", 'F'}, + {"0V;TVF", 'F'}, + {"0V;TVK", 'F'}, + {"0V;TVO", 'F'}, + {"0V;TVT", 'F'}, + {"0VA(F(", 'F'}, + {"0VA(N)", 'F'}, + {"0VA(NO", 'F'}, + {"0VA(S)", 'F'}, + {"0VA(SO", 'F'}, + {"0VA(V)", 'F'}, + {"0VA(VO", 'F'}, + {"0VAF()", 'F'}, + {"0VAF(1", 'F'}, + {"0VAF(F", 'F'}, + {"0VAF(N", 'F'}, + {"0VAF(S", 'F'}, + {"0VAF(V", 'F'}, + {"0VASO(", 'F'}, + {"0VASO1", 'F'}, + {"0VASOF", 'F'}, + {"0VASON", 'F'}, + {"0VASOS", 'F'}, + {"0VASOV", 'F'}, + {"0VASUE", 'F'}, + {"0VATO(", 'F'}, + {"0VATO1", 'F'}, + {"0VATOF", 'F'}, + {"0VATON", 'F'}, + {"0VATOS", 'F'}, + {"0VATOV", 'F'}, + {"0VATUE", 'F'}, + {"0VAVO(", 'F'}, + {"0VAVOF", 'F'}, + {"0VAVOS", 'F'}, + {"0VAVUE", 'F'}, + {"0VB(1)", 'F'}, + {"0VB(1O", 'F'}, + {"0VB(F(", 'F'}, + {"0VB(NO", 'F'}, + {"0VB(S)", 'F'}, + {"0VB(SO", 'F'}, + {"0VB(V)", 'F'}, + {"0VB(VO", 'F'}, + {"0VB1", 'F'}, + {"0VB1&(", 'F'}, + {"0VB1&1", 'F'}, + {"0VB1&F", 'F'}, + {"0VB1&N", 'F'}, + {"0VB1&S", 'F'}, + {"0VB1&V", 'F'}, + {"0VB1,(", 'F'}, + {"0VB1,F", 'F'}, + {"0VB1;", 'F'}, + {"0VB1;C", 'F'}, + {"0VB1B(", 'F'}, + {"0VB1B1", 'F'}, + {"0VB1BF", 'F'}, + {"0VB1BN", 'F'}, + {"0VB1BS", 'F'}, + {"0VB1BV", 'F'}, + {"0VB1C", 'F'}, + {"0VB1K(", 'F'}, + {"0VB1K1", 'F'}, + {"0VB1KF", 'F'}, + {"0VB1KN", 'F'}, + {"0VB1KS", 'F'}, + {"0VB1KV", 'F'}, + {"0VB1O(", 'F'}, + {"0VB1OF", 'F'}, + {"0VB1OS", 'F'}, + {"0VB1OV", 'F'}, + {"0VB1U(", 'F'}, + {"0VB1UE", 'F'}, + {"0VBE(1", 'F'}, + {"0VBE(F", 'F'}, + {"0VBE(N", 'F'}, + {"0VBE(S", 'F'}, + {"0VBE(V", 'F'}, + {"0VBEK(", 'F'}, + {"0VBF()", 'F'}, + {"0VBF(1", 'F'}, + {"0VBF(F", 'F'}, + {"0VBF(N", 'F'}, + {"0VBF(S", 'F'}, + {"0VBF(V", 'F'}, + {"0VBN", 'F'}, + {"0VBN&(", 'F'}, + {"0VBN&1", 'F'}, + {"0VBN&F", 'F'}, + {"0VBN&N", 'F'}, + {"0VBN&S", 'F'}, + {"0VBN&V", 'F'}, + {"0VBN,(", 'F'}, + {"0VBN,F", 'F'}, + {"0VBN;", 'F'}, + {"0VBN;C", 'F'}, + {"0VBNB(", 'F'}, + {"0VBNB1", 'F'}, + {"0VBNBF", 'F'}, + {"0VBNBN", 'F'}, + {"0VBNBS", 'F'}, + {"0VBNBV", 'F'}, + {"0VBNC", 'F'}, + {"0VBNK(", 'F'}, + {"0VBNK1", 'F'}, + {"0VBNKF", 'F'}, + {"0VBNKN", 'F'}, + {"0VBNKS", 'F'}, + {"0VBNKV", 'F'}, + {"0VBNO(", 'F'}, + {"0VBNOF", 'F'}, + {"0VBNOS", 'F'}, + {"0VBNOV", 'F'}, + {"0VBNU(", 'F'}, + {"0VBNUE", 'F'}, + {"0VBS", 'F'}, + {"0VBS&(", 'F'}, + {"0VBS&1", 'F'}, + {"0VBS&F", 'F'}, + {"0VBS&N", 'F'}, + {"0VBS&S", 'F'}, + {"0VBS&V", 'F'}, + {"0VBS,(", 'F'}, + {"0VBS,F", 'F'}, + {"0VBS;", 'F'}, + {"0VBS;C", 'F'}, + {"0VBSB(", 'F'}, + {"0VBSB1", 'F'}, + {"0VBSBF", 'F'}, + {"0VBSBN", 'F'}, + {"0VBSBS", 'F'}, + {"0VBSBV", 'F'}, + {"0VBSC", 'F'}, + {"0VBSK(", 'F'}, + {"0VBSK1", 'F'}, + {"0VBSKF", 'F'}, + {"0VBSKN", 'F'}, + {"0VBSKS", 'F'}, + {"0VBSKV", 'F'}, + {"0VBSO(", 'F'}, + {"0VBSO1", 'F'}, + {"0VBSOF", 'F'}, + {"0VBSON", 'F'}, + {"0VBSOS", 'F'}, + {"0VBSOV", 'F'}, + {"0VBSU(", 'F'}, + {"0VBSUE", 'F'}, + {"0VBV", 'F'}, + {"0VBV&(", 'F'}, + {"0VBV&1", 'F'}, + {"0VBV&F", 'F'}, + {"0VBV&N", 'F'}, + {"0VBV&S", 'F'}, + {"0VBV&V", 'F'}, + {"0VBV,(", 'F'}, + {"0VBV,F", 'F'}, + {"0VBV;", 'F'}, + {"0VBV;C", 'F'}, + {"0VBVB(", 'F'}, + {"0VBVB1", 'F'}, + {"0VBVBF", 'F'}, + {"0VBVBN", 'F'}, + {"0VBVBS", 'F'}, + {"0VBVBV", 'F'}, + {"0VBVC", 'F'}, + {"0VBVK(", 'F'}, + {"0VBVK1", 'F'}, + {"0VBVKF", 'F'}, + {"0VBVKN", 'F'}, + {"0VBVKS", 'F'}, + {"0VBVKV", 'F'}, + {"0VBVO(", 'F'}, + {"0VBVOF", 'F'}, + {"0VBVOS", 'F'}, + {"0VBVU(", 'F'}, + {"0VBVUE", 'F'}, + {"0VC", 'F'}, + {"0VE(1)", 'F'}, + {"0VE(1O", 'F'}, + {"0VE(F(", 'F'}, + {"0VE(N)", 'F'}, + {"0VE(NO", 'F'}, + {"0VE(S)", 'F'}, + {"0VE(SO", 'F'}, + {"0VE(V)", 'F'}, + {"0VE(VO", 'F'}, + {"0VE1;T", 'F'}, + {"0VE1C", 'F'}, + {"0VE1O(", 'F'}, + {"0VE1OF", 'F'}, + {"0VE1OS", 'F'}, + {"0VE1OV", 'F'}, + {"0VE1T(", 'F'}, + {"0VE1T1", 'F'}, + {"0VE1TF", 'F'}, + {"0VE1TN", 'F'}, + {"0VE1TS", 'F'}, + {"0VE1TV", 'F'}, + {"0VE1UE", 'F'}, + {"0VEF()", 'F'}, + {"0VEF(1", 'F'}, + {"0VEF(F", 'F'}, + {"0VEF(N", 'F'}, + {"0VEF(S", 'F'}, + {"0VEF(V", 'F'}, + {"0VEK(1", 'F'}, + {"0VEK(E", 'F'}, + {"0VEK(F", 'F'}, + {"0VEK(N", 'F'}, + {"0VEK(S", 'F'}, + {"0VEK(V", 'F'}, + {"0VEK1;", 'F'}, + {"0VEK1C", 'F'}, + {"0VEK1O", 'F'}, + {"0VEK1T", 'F'}, + {"0VEK1U", 'F'}, + {"0VEKF(", 'F'}, + {"0VEKN;", 'F'}, + {"0VEKNC", 'F'}, + {"0VEKNE", 'F'}, + {"0VEKNT", 'F'}, + {"0VEKNU", 'F'}, + {"0VEKOK", 'F'}, + {"0VEKS;", 'F'}, + {"0VEKSC", 'F'}, + {"0VEKSO", 'F'}, + {"0VEKST", 'F'}, + {"0VEKSU", 'F'}, + {"0VEKU(", 'F'}, + {"0VEKU1", 'F'}, + {"0VEKUE", 'F'}, + {"0VEKUF", 'F'}, + {"0VEKUS", 'F'}, + {"0VEKUV", 'F'}, + {"0VEKV;", 'F'}, + {"0VEKVC", 'F'}, + {"0VEKVO", 'F'}, + {"0VEKVT", 'F'}, + {"0VEKVU", 'F'}, + {"0VEN;T", 'F'}, + {"0VENC", 'F'}, + {"0VENEN", 'F'}, + {"0VENO(", 'F'}, + {"0VENOF", 'F'}, + {"0VENOS", 'F'}, + {"0VENOV", 'F'}, + {"0VENT(", 'F'}, + {"0VENT1", 'F'}, + {"0VENTF", 'F'}, + {"0VENTN", 'F'}, + {"0VENTS", 'F'}, + {"0VENTV", 'F'}, + {"0VENUE", 'F'}, + {"0VEOKN", 'F'}, + {"0VES;T", 'F'}, + {"0VESC", 'F'}, + {"0VESO(", 'F'}, + {"0VESO1", 'F'}, + {"0VESOF", 'F'}, + {"0VESON", 'F'}, + {"0VESOS", 'F'}, + {"0VESOV", 'F'}, + {"0VEST(", 'F'}, + {"0VEST1", 'F'}, + {"0VESTF", 'F'}, + {"0VESTN", 'F'}, + {"0VESTS", 'F'}, + {"0VESTV", 'F'}, + {"0VESUE", 'F'}, + {"0VEU(1", 'F'}, + {"0VEU(F", 'F'}, + {"0VEU(N", 'F'}, + {"0VEU(S", 'F'}, + {"0VEU(V", 'F'}, + {"0VEU1,", 'F'}, + {"0VEU1C", 'F'}, + {"0VEU1O", 'F'}, + {"0VEUEF", 'F'}, + {"0VEUEK", 'F'}, + {"0VEUF(", 'F'}, + {"0VEUS,", 'F'}, + {"0VEUSC", 'F'}, + {"0VEUSO", 'F'}, + {"0VEUV,", 'F'}, + {"0VEUVC", 'F'}, + {"0VEUVO", 'F'}, + {"0VEV;T", 'F'}, + {"0VEVC", 'F'}, + {"0VEVO(", 'F'}, + {"0VEVOF", 'F'}, + {"0VEVOS", 'F'}, + {"0VEVT(", 'F'}, + {"0VEVT1", 'F'}, + {"0VEVTF", 'F'}, + {"0VEVTN", 'F'}, + {"0VEVTS", 'F'}, + {"0VEVTV", 'F'}, + {"0VEVUE", 'F'}, + {"0VF()1", 'F'}, + {"0VF()F", 'F'}, + {"0VF()K", 'F'}, + {"0VF()N", 'F'}, + {"0VF()O", 'F'}, + {"0VF()S", 'F'}, + {"0VF()U", 'F'}, + {"0VF()V", 'F'}, + {"0VF(1)", 'F'}, + {"0VF(1N", 'F'}, + {"0VF(1O", 'F'}, + {"0VF(E(", 'F'}, + {"0VF(E1", 'F'}, + {"0VF(EF", 'F'}, + {"0VF(EK", 'F'}, + {"0VF(EN", 'F'}, + {"0VF(ES", 'F'}, + {"0VF(EV", 'F'}, + {"0VF(F(", 'F'}, + {"0VF(N)", 'F'}, + {"0VF(N,", 'F'}, + {"0VF(NO", 'F'}, + {"0VF(S)", 'F'}, + {"0VF(SO", 'F'}, + {"0VF(V)", 'F'}, + {"0VF(VO", 'F'}, + {"0VK(1)", 'F'}, + {"0VK(1O", 'F'}, + {"0VK(F(", 'F'}, + {"0VK(N)", 'F'}, + {"0VK(NO", 'F'}, + {"0VK(S)", 'F'}, + {"0VK(SO", 'F'}, + {"0VK(V)", 'F'}, + {"0VK(VO", 'F'}, + {"0VK)&(", 'F'}, + {"0VK)&1", 'F'}, + {"0VK)&F", 'F'}, + {"0VK)&N", 'F'}, + {"0VK)&S", 'F'}, + {"0VK)&V", 'F'}, + {"0VK);E", 'F'}, + {"0VK);T", 'F'}, + {"0VK)B(", 'F'}, + {"0VK)B1", 'F'}, + {"0VK)BF", 'F'}, + {"0VK)BN", 'F'}, + {"0VK)BS", 'F'}, + {"0VK)BV", 'F'}, + {"0VK)E(", 'F'}, + {"0VK)E1", 'F'}, + {"0VK)EF", 'F'}, + {"0VK)EK", 'F'}, + {"0VK)EN", 'F'}, + {"0VK)ES", 'F'}, + {"0VK)EV", 'F'}, + {"0VK)F(", 'F'}, + {"0VK)O(", 'F'}, + {"0VK)OF", 'F'}, + {"0VK)UE", 'F'}, + {"0VK1", 'F'}, + {"0VK1&(", 'F'}, + {"0VK1&1", 'F'}, + {"0VK1&F", 'F'}, + {"0VK1&N", 'F'}, + {"0VK1&S", 'F'}, + {"0VK1&V", 'F'}, + {"0VK1;", 'F'}, + {"0VK1;C", 'F'}, + {"0VK1;E", 'F'}, + {"0VK1;T", 'F'}, + {"0VK1B(", 'F'}, + {"0VK1B1", 'F'}, + {"0VK1BF", 'F'}, + {"0VK1BN", 'F'}, + {"0VK1BS", 'F'}, + {"0VK1BV", 'F'}, + {"0VK1C", 'F'}, + {"0VK1E(", 'F'}, + {"0VK1E1", 'F'}, + {"0VK1EF", 'F'}, + {"0VK1EK", 'F'}, + {"0VK1EN", 'F'}, + {"0VK1ES", 'F'}, + {"0VK1EV", 'F'}, + {"0VK1O(", 'F'}, + {"0VK1OF", 'F'}, + {"0VK1OS", 'F'}, + {"0VK1OV", 'F'}, + {"0VK1U(", 'F'}, + {"0VK1UE", 'F'}, + {"0VKF()", 'F'}, + {"0VKF(1", 'F'}, + {"0VKF(F", 'F'}, + {"0VKF(N", 'F'}, + {"0VKF(S", 'F'}, + {"0VKF(V", 'F'}, + {"0VKN", 'F'}, + {"0VKN&(", 'F'}, + {"0VKN&1", 'F'}, + {"0VKN&F", 'F'}, + {"0VKN&N", 'F'}, + {"0VKN&S", 'F'}, + {"0VKN&V", 'F'}, + {"0VKN;", 'F'}, + {"0VKN;C", 'F'}, + {"0VKN;E", 'F'}, + {"0VKN;T", 'F'}, + {"0VKNB(", 'F'}, + {"0VKNB1", 'F'}, + {"0VKNBF", 'F'}, + {"0VKNBN", 'F'}, + {"0VKNBS", 'F'}, + {"0VKNBV", 'F'}, + {"0VKNC", 'F'}, + {"0VKNE(", 'F'}, + {"0VKNE1", 'F'}, + {"0VKNEF", 'F'}, + {"0VKNEN", 'F'}, + {"0VKNES", 'F'}, + {"0VKNEV", 'F'}, + {"0VKNU(", 'F'}, + {"0VKNUE", 'F'}, + {"0VKS", 'F'}, + {"0VKS&(", 'F'}, + {"0VKS&1", 'F'}, + {"0VKS&F", 'F'}, + {"0VKS&N", 'F'}, + {"0VKS&S", 'F'}, + {"0VKS&V", 'F'}, + {"0VKS;", 'F'}, + {"0VKS;C", 'F'}, + {"0VKS;E", 'F'}, + {"0VKS;T", 'F'}, + {"0VKSB(", 'F'}, + {"0VKSB1", 'F'}, + {"0VKSBF", 'F'}, + {"0VKSBN", 'F'}, + {"0VKSBS", 'F'}, + {"0VKSBV", 'F'}, + {"0VKSC", 'F'}, + {"0VKSE(", 'F'}, + {"0VKSE1", 'F'}, + {"0VKSEF", 'F'}, + {"0VKSEK", 'F'}, + {"0VKSEN", 'F'}, + {"0VKSES", 'F'}, + {"0VKSEV", 'F'}, + {"0VKSO(", 'F'}, + {"0VKSO1", 'F'}, + {"0VKSOF", 'F'}, + {"0VKSON", 'F'}, + {"0VKSOS", 'F'}, + {"0VKSOV", 'F'}, + {"0VKSU(", 'F'}, + {"0VKSUE", 'F'}, + {"0VKUE(", 'F'}, + {"0VKUE1", 'F'}, + {"0VKUEF", 'F'}, + {"0VKUEK", 'F'}, + {"0VKUEN", 'F'}, + {"0VKUES", 'F'}, + {"0VKUEV", 'F'}, + {"0VKV", 'F'}, + {"0VKV&(", 'F'}, + {"0VKV&1", 'F'}, + {"0VKV&F", 'F'}, + {"0VKV&N", 'F'}, + {"0VKV&S", 'F'}, + {"0VKV&V", 'F'}, + {"0VKV;", 'F'}, + {"0VKV;C", 'F'}, + {"0VKV;E", 'F'}, + {"0VKV;T", 'F'}, + {"0VKVB(", 'F'}, + {"0VKVB1", 'F'}, + {"0VKVBF", 'F'}, + {"0VKVBN", 'F'}, + {"0VKVBS", 'F'}, + {"0VKVBV", 'F'}, + {"0VKVC", 'F'}, + {"0VKVE(", 'F'}, + {"0VKVE1", 'F'}, + {"0VKVEF", 'F'}, + {"0VKVEK", 'F'}, + {"0VKVEN", 'F'}, + {"0VKVES", 'F'}, + {"0VKVEV", 'F'}, + {"0VKVO(", 'F'}, + {"0VKVOF", 'F'}, + {"0VKVOS", 'F'}, + {"0VKVU(", 'F'}, + {"0VKVUE", 'F'}, + {"0VO(1&", 'F'}, + {"0VO(1)", 'F'}, + {"0VO(1,", 'F'}, + {"0VO(1O", 'F'}, + {"0VO(E(", 'F'}, + {"0VO(E1", 'F'}, + {"0VO(EE", 'F'}, + {"0VO(EF", 'F'}, + {"0VO(EK", 'F'}, + {"0VO(EN", 'F'}, + {"0VO(EO", 'F'}, + {"0VO(ES", 'F'}, + {"0VO(EV", 'F'}, + {"0VO(F(", 'F'}, + {"0VO(N&", 'F'}, + {"0VO(N)", 'F'}, + {"0VO(N,", 'F'}, + {"0VO(NO", 'F'}, + {"0VO(S&", 'F'}, + {"0VO(S)", 'F'}, + {"0VO(S,", 'F'}, + {"0VO(SO", 'F'}, + {"0VO(V&", 'F'}, + {"0VO(V)", 'F'}, + {"0VO(V,", 'F'}, + {"0VO(VO", 'F'}, + {"0VOF()", 'F'}, + {"0VOF(1", 'F'}, + {"0VOF(E", 'F'}, + {"0VOF(F", 'F'}, + {"0VOF(N", 'F'}, + {"0VOF(S", 'F'}, + {"0VOF(V", 'F'}, + {"0VOK&(", 'F'}, + {"0VOK&1", 'F'}, + {"0VOK&F", 'F'}, + {"0VOK&N", 'F'}, + {"0VOK&S", 'F'}, + {"0VOK&V", 'F'}, + {"0VOK(1", 'F'}, + {"0VOK(F", 'F'}, + {"0VOK(N", 'F'}, + {"0VOK(S", 'F'}, + {"0VOK(V", 'F'}, + {"0VOK1C", 'F'}, + {"0VOK1O", 'F'}, + {"0VOKF(", 'F'}, + {"0VOKNC", 'F'}, + {"0VOKO(", 'F'}, + {"0VOKO1", 'F'}, + {"0VOKOF", 'F'}, + {"0VOKON", 'F'}, + {"0VOKOS", 'F'}, + {"0VOKOV", 'F'}, + {"0VOKSC", 'F'}, + {"0VOKSO", 'F'}, + {"0VOKVC", 'F'}, + {"0VOKVO", 'F'}, + {"0VOS", 'F'}, + {"0VOS&(", 'F'}, + {"0VOS&1", 'F'}, + {"0VOS&E", 'F'}, + {"0VOS&F", 'F'}, + {"0VOS&K", 'F'}, + {"0VOS&N", 'F'}, + {"0VOS&S", 'F'}, + {"0VOS&U", 'F'}, + {"0VOS&V", 'F'}, + {"0VOS(E", 'F'}, + {"0VOS(U", 'F'}, + {"0VOS)&", 'F'}, + {"0VOS),", 'F'}, + {"0VOS);", 'F'}, + {"0VOS)B", 'F'}, + {"0VOS)C", 'F'}, + {"0VOS)E", 'F'}, + {"0VOS)F", 'F'}, + {"0VOS)K", 'F'}, + {"0VOS)O", 'F'}, + {"0VOS)U", 'F'}, + {"0VOS,(", 'F'}, + {"0VOS,F", 'F'}, + {"0VOS1(", 'F'}, + {"0VOS1F", 'F'}, + {"0VOS1N", 'F'}, + {"0VOS1S", 'F'}, + {"0VOS1U", 'F'}, + {"0VOS1V", 'F'}, + {"0VOS;", 'F'}, + {"0VOS;C", 'F'}, + {"0VOS;E", 'F'}, + {"0VOS;N", 'F'}, + {"0VOS;T", 'F'}, + {"0VOSA(", 'F'}, + {"0VOSAF", 'F'}, + {"0VOSAS", 'F'}, + {"0VOSAT", 'F'}, + {"0VOSAV", 'F'}, + {"0VOSB(", 'F'}, + {"0VOSB1", 'F'}, + {"0VOSBE", 'F'}, + {"0VOSBF", 'F'}, + {"0VOSBN", 'F'}, + {"0VOSBS", 'F'}, + {"0VOSBV", 'F'}, + {"0VOSC", 'F'}, + {"0VOSE(", 'F'}, + {"0VOSE1", 'F'}, + {"0VOSEF", 'F'}, + {"0VOSEK", 'F'}, + {"0VOSEN", 'F'}, + {"0VOSEO", 'F'}, + {"0VOSES", 'F'}, + {"0VOSEU", 'F'}, + {"0VOSEV", 'F'}, + {"0VOSF(", 'F'}, + {"0VOSK(", 'F'}, + {"0VOSK)", 'F'}, + {"0VOSK1", 'F'}, + {"0VOSKB", 'F'}, + {"0VOSKF", 'F'}, + {"0VOSKN", 'F'}, + {"0VOSKS", 'F'}, + {"0VOSKU", 'F'}, + {"0VOSKV", 'F'}, + {"0VOST(", 'F'}, + {"0VOST1", 'F'}, + {"0VOSTE", 'F'}, + {"0VOSTF", 'F'}, + {"0VOSTN", 'F'}, + {"0VOSTS", 'F'}, + {"0VOSTT", 'F'}, + {"0VOSTV", 'F'}, + {"0VOSU", 'F'}, + {"0VOSU(", 'F'}, + {"0VOSU1", 'F'}, + {"0VOSU;", 'F'}, + {"0VOSUC", 'F'}, + {"0VOSUE", 'F'}, + {"0VOSUF", 'F'}, + {"0VOSUK", 'F'}, + {"0VOSUO", 'F'}, + {"0VOSUS", 'F'}, + {"0VOSUT", 'F'}, + {"0VOSUV", 'F'}, + {"0VOSV(", 'F'}, + {"0VOSVF", 'F'}, + {"0VOSVO", 'F'}, + {"0VOSVS", 'F'}, + {"0VOSVU", 'F'}, + {"0VOU(E", 'F'}, + {"0VOUEK", 'F'}, + {"0VOUEN", 'F'}, + {"0VT(1)", 'F'}, + {"0VT(1O", 'F'}, + {"0VT(F(", 'F'}, + {"0VT(N)", 'F'}, + {"0VT(NO", 'F'}, + {"0VT(S)", 'F'}, + {"0VT(SO", 'F'}, + {"0VT(V)", 'F'}, + {"0VT(VO", 'F'}, + {"0VT1(F", 'F'}, + {"0VT1O(", 'F'}, + {"0VT1OF", 'F'}, + {"0VT1OS", 'F'}, + {"0VT1OV", 'F'}, + {"0VTE(1", 'F'}, + {"0VTE(F", 'F'}, + {"0VTE(N", 'F'}, + {"0VTE(S", 'F'}, + {"0VTE(V", 'F'}, + {"0VTE1N", 'F'}, + {"0VTE1O", 'F'}, + {"0VTEF(", 'F'}, + {"0VTEK(", 'F'}, + {"0VTEK1", 'F'}, + {"0VTEKF", 'F'}, + {"0VTEKN", 'F'}, + {"0VTEKS", 'F'}, + {"0VTEKV", 'F'}, + {"0VTENN", 'F'}, + {"0VTENO", 'F'}, + {"0VTESN", 'F'}, + {"0VTESO", 'F'}, + {"0VTEVN", 'F'}, + {"0VTEVO", 'F'}, + {"0VTF()", 'F'}, + {"0VTF(1", 'F'}, + {"0VTF(F", 'F'}, + {"0VTF(N", 'F'}, + {"0VTF(S", 'F'}, + {"0VTF(V", 'F'}, + {"0VTN(1", 'F'}, + {"0VTN(F", 'F'}, + {"0VTN(S", 'F'}, + {"0VTN(V", 'F'}, + {"0VTN1C", 'F'}, + {"0VTN1O", 'F'}, + {"0VTN;E", 'F'}, + {"0VTN;N", 'F'}, + {"0VTN;T", 'F'}, + {"0VTNE(", 'F'}, + {"0VTNE1", 'F'}, + {"0VTNEF", 'F'}, + {"0VTNEN", 'F'}, + {"0VTNES", 'F'}, + {"0VTNEV", 'F'}, + {"0VTNF(", 'F'}, + {"0VTNKN", 'F'}, + {"0VTNN:", 'F'}, + {"0VTNNC", 'F'}, + {"0VTNNO", 'F'}, + {"0VTNO(", 'F'}, + {"0VTNOF", 'F'}, + {"0VTNOS", 'F'}, + {"0VTNOV", 'F'}, + {"0VTNSC", 'F'}, + {"0VTNSO", 'F'}, + {"0VTNT(", 'F'}, + {"0VTNT1", 'F'}, + {"0VTNTF", 'F'}, + {"0VTNTN", 'F'}, + {"0VTNTS", 'F'}, + {"0VTNTV", 'F'}, + {"0VTNVC", 'F'}, + {"0VTNVO", 'F'}, + {"0VTS(F", 'F'}, + {"0VTSO(", 'F'}, + {"0VTSO1", 'F'}, + {"0VTSOF", 'F'}, + {"0VTSON", 'F'}, + {"0VTSOS", 'F'}, + {"0VTSOV", 'F'}, + {"0VTTNE", 'F'}, + {"0VTTNK", 'F'}, + {"0VTTNN", 'F'}, + {"0VTTNT", 'F'}, + {"0VTV(1", 'F'}, + {"0VTV(F", 'F'}, + {"0VTVO(", 'F'}, + {"0VTVOF", 'F'}, + {"0VTVOS", 'F'}, + {"0VU", 'F'}, + {"0VU(1)", 'F'}, + {"0VU(1O", 'F'}, + {"0VU(E(", 'F'}, + {"0VU(E1", 'F'}, + {"0VU(EF", 'F'}, + {"0VU(EK", 'F'}, + {"0VU(EN", 'F'}, + {"0VU(ES", 'F'}, + {"0VU(EV", 'F'}, + {"0VU(F(", 'F'}, + {"0VU(N)", 'F'}, + {"0VU(NO", 'F'}, + {"0VU(S)", 'F'}, + {"0VU(SO", 'F'}, + {"0VU(V)", 'F'}, + {"0VU(VO", 'F'}, + {"0VU1,(", 'F'}, + {"0VU1,F", 'F'}, + {"0VU1C", 'F'}, + {"0VU1O(", 'F'}, + {"0VU1OF", 'F'}, + {"0VU1OS", 'F'}, + {"0VU1OV", 'F'}, + {"0VU;", 'F'}, + {"0VU;C", 'F'}, + {"0VUC", 'F'}, + {"0VUE", 'F'}, + {"0VUE(1", 'F'}, + {"0VUE(E", 'F'}, + {"0VUE(F", 'F'}, + {"0VUE(N", 'F'}, + {"0VUE(O", 'F'}, + {"0VUE(S", 'F'}, + {"0VUE(V", 'F'}, + {"0VUE1", 'F'}, + {"0VUE1&", 'F'}, + {"0VUE1(", 'F'}, + {"0VUE1)", 'F'}, + {"0VUE1,", 'F'}, + {"0VUE1;", 'F'}, + {"0VUE1B", 'F'}, + {"0VUE1C", 'F'}, + {"0VUE1F", 'F'}, + {"0VUE1K", 'F'}, + {"0VUE1N", 'F'}, + {"0VUE1O", 'F'}, + {"0VUE1S", 'F'}, + {"0VUE1U", 'F'}, + {"0VUE1V", 'F'}, + {"0VUE;", 'F'}, + {"0VUE;C", 'F'}, + {"0VUEC", 'F'}, + {"0VUEF", 'F'}, + {"0VUEF(", 'F'}, + {"0VUEF,", 'F'}, + {"0VUEF;", 'F'}, + {"0VUEFC", 'F'}, + {"0VUEK", 'F'}, + {"0VUEK(", 'F'}, + {"0VUEK1", 'F'}, + {"0VUEK;", 'F'}, + {"0VUEKC", 'F'}, + {"0VUEKF", 'F'}, + {"0VUEKN", 'F'}, + {"0VUEKO", 'F'}, + {"0VUEKS", 'F'}, + {"0VUEKV", 'F'}, + {"0VUEN", 'F'}, + {"0VUEN&", 'F'}, + {"0VUEN(", 'F'}, + {"0VUEN)", 'F'}, + {"0VUEN,", 'F'}, + {"0VUEN1", 'F'}, + {"0VUEN;", 'F'}, + {"0VUENB", 'F'}, + {"0VUENC", 'F'}, + {"0VUENF", 'F'}, + {"0VUENK", 'F'}, + {"0VUENO", 'F'}, + {"0VUENS", 'F'}, + {"0VUENU", 'F'}, + {"0VUEOK", 'F'}, + {"0VUEON", 'F'}, + {"0VUES", 'F'}, + {"0VUES&", 'F'}, + {"0VUES(", 'F'}, + {"0VUES)", 'F'}, + {"0VUES,", 'F'}, + {"0VUES1", 'F'}, + {"0VUES;", 'F'}, + {"0VUESB", 'F'}, + {"0VUESC", 'F'}, + {"0VUESF", 'F'}, + {"0VUESK", 'F'}, + {"0VUESO", 'F'}, + {"0VUESU", 'F'}, + {"0VUESV", 'F'}, + {"0VUEV", 'F'}, + {"0VUEV&", 'F'}, + {"0VUEV(", 'F'}, + {"0VUEV)", 'F'}, + {"0VUEV,", 'F'}, + {"0VUEV;", 'F'}, + {"0VUEVB", 'F'}, + {"0VUEVC", 'F'}, + {"0VUEVF", 'F'}, + {"0VUEVK", 'F'}, + {"0VUEVN", 'F'}, + {"0VUEVO", 'F'}, + {"0VUEVS", 'F'}, + {"0VUEVU", 'F'}, + {"0VUF()", 'F'}, + {"0VUF(1", 'F'}, + {"0VUF(F", 'F'}, + {"0VUF(N", 'F'}, + {"0VUF(S", 'F'}, + {"0VUF(V", 'F'}, + {"0VUK(E", 'F'}, + {"0VUO(E", 'F'}, + {"0VUON(", 'F'}, + {"0VUON1", 'F'}, + {"0VUONF", 'F'}, + {"0VUONS", 'F'}, + {"0VUS,(", 'F'}, + {"0VUS,F", 'F'}, + {"0VUSC", 'F'}, + {"0VUSO(", 'F'}, + {"0VUSO1", 'F'}, + {"0VUSOF", 'F'}, + {"0VUSON", 'F'}, + {"0VUSOS", 'F'}, + {"0VUSOV", 'F'}, + {"0VUTN(", 'F'}, + {"0VUTN1", 'F'}, + {"0VUTNF", 'F'}, + {"0VUTNN", 'F'}, + {"0VUTNS", 'F'}, + {"0VUTNV", 'F'}, + {"0VUV,(", 'F'}, + {"0VUV,F", 'F'}, + {"0VUVC", 'F'}, + {"0VUVO(", 'F'}, + {"0VUVOF", 'F'}, + {"0VUVOS", 'F'}, + {"0X", 'F'}, + {"::", 'o'}, + {":=", 'o'}, + {"<<", 'o'}, + {"<=", 'o'}, + {"<>", 'o'}, + {"<@", 'o'}, + {">=", 'o'}, + {">>", 'o'}, + {"@>", 'o'}, + {"ABORT", 'k'}, + {"ABS", 'f'}, + {"ACCESSIBLE", 'k'}, + {"ACOS", 'f'}, + {"ADDDATE", 'f'}, + {"ADDTIME", 'f'}, + {"AES_DECRYPT", 'f'}, + {"AES_ENCRYPT", 'f'}, + {"AGAINST", 'k'}, + {"AGE", 'f'}, + {"ALL_USERS", 'k'}, + {"ALTER", 'k'}, + {"ALTER DOMAIN", 'k'}, + {"ALTER TABLE", 'k'}, + {"ANALYZE", 'k'}, + {"AND", '&'}, + {"ANY", 'f'}, + {"ANYARRAY", 't'}, + {"ANYELEMENT", 't'}, + {"ANYNONARRY", 't'}, + {"APPLOCK_MODE", 'f'}, + {"APPLOCK_TEST", 'f'}, + {"APP_NAME", 'f'}, + {"ARRAY_AGG", 'f'}, + {"ARRAY_CAT", 'f'}, + {"ARRAY_DIM", 'f'}, + {"ARRAY_FILL", 'f'}, + {"ARRAY_LENGTH", 'f'}, + {"ARRAY_LOWER", 'f'}, + {"ARRAY_NDIMS", 'f'}, + {"ARRAY_PREPEND", 'f'}, + {"ARRAY_TO_JSON", 'f'}, + {"ARRAY_TO_STRING", 'f'}, + {"ARRAY_UPPER", 'f'}, + {"AS", 'k'}, + {"ASC", 'k'}, + {"ASCII", 'f'}, + {"ASENSITIVE", 'k'}, + {"ASIN", 'f'}, + {"ASSEMBLYPROPERTY", 'f'}, + {"ASYMKEY_ID", 'f'}, + {"AT TIME", 'n'}, + {"AT TIME ZONE", 'k'}, + {"ATAN", 'f'}, + {"ATAN2", 'f'}, + {"AUTOINCREMENT", 'k'}, + {"AVG", 'f'}, + {"BEFORE", 'k'}, + {"BEGIN", 'T'}, + {"BEGIN DECLARE", 'T'}, + {"BEGIN GOTO", 'T'}, + {"BEGIN TRY", 'T'}, + {"BEGIN TRY DECLARE", 'T'}, + {"BENCHMARK", 'f'}, + {"BETWEEN", 'o'}, + {"BIGINT", 't'}, + {"BIGSERIAL", 't'}, + {"BIN", 'f'}, + {"BINARY", 't'}, + {"BINARY_DOUBLE_INFINITY", '1'}, + {"BINARY_DOUBLE_NAN", '1'}, + {"BINARY_FLOAT_INFINITY", '1'}, + {"BINARY_FLOAT_NAN", '1'}, + {"BINBINARY", 'f'}, + {"BIT_AND", 'f'}, + {"BIT_COUNT", 'f'}, + {"BIT_LENGTH", 'f'}, + {"BIT_OR", 'f'}, + {"BIT_XOR", 'f'}, + {"BLOB", 'k'}, + {"BOOLEAN", 't'}, + {"BOOL_AND", 'f'}, + {"BOOL_OR", 'f'}, + {"BOTH", 'k'}, + {"BTRIM", 'f'}, + {"BY", 'n'}, + {"BYTEA", 't'}, + {"CALL", 'T'}, + {"CASCADE", 'k'}, + {"CASE", 'E'}, + {"CAST", 'f'}, + {"CBOOL", 'f'}, + {"CBRT", 'f'}, + {"CBYTE", 'f'}, + {"CCUR", 'f'}, + {"CDATE", 'f'}, + {"CDBL", 'f'}, + {"CEIL", 'f'}, + {"CEILING", 'f'}, + {"CERTENCODED", 'f'}, + {"CERTPRIVATEKEY", 'f'}, + {"CERT_ID", 'f'}, + {"CERT_PROPERTY", 'f'}, + {"CHANGE", 'k'}, + {"CHANGES", 'f'}, + {"CHAR", 'f'}, + {"CHARACTER", 't'}, + {"CHARACTER VARYING", 't'}, + {"CHARACTER_LENGTH", 'f'}, + {"CHARINDEX", 'f'}, + {"CHARSET", 'f'}, + {"CHAR_LENGTH", 'f'}, + {"CHDIR", 'f'}, + {"CHDRIVE", 'f'}, + {"CHECK", 'n'}, + {"CHECKSUM_AGG", 'f'}, + {"CHOOSE", 'f'}, + {"CHR", 'f'}, + {"CINT", 'f'}, + {"CLNG", 'f'}, + {"CLOCK_TIMESTAMP", 'f'}, + {"COALESCE", 'f'}, + {"COERCIBILITY", 'f'}, + {"COLLATE", 'A'}, + {"COLLATION", 'f'}, + {"COLLATIONPROPERTY", 'f'}, + {"COLUMN", 'k'}, + {"COLUMNPROPERTY", 'f'}, + {"COLUMNS_UPDATED", 'f'}, + {"COL_LENGTH", 'f'}, + {"COL_NAME", 'f'}, + {"COMPRESS", 'f'}, + {"CONCAT", 'f'}, + {"CONCAT_WS", 'f'}, + {"CONDITION", 'k'}, + {"CONNECTION_ID", 'f'}, + {"CONSTRAINT", 'k'}, + {"CONTINUE", 'k'}, + {"CONV", 'f'}, + {"CONVERT", 'f'}, + {"CONVERT_FROM", 'f'}, + {"CONVERT_TO", 'f'}, + {"CONVERT_TZ", 'f'}, + {"COS", 'f'}, + {"COT", 'f'}, + {"COUNT", 'f'}, + {"COUNT_BIG", 'k'}, + {"CRC32", 'f'}, + {"CREATE", 'E'}, + {"CREATE OR", 'n'}, + {"CREATE OR REPLACE", 'T'}, + {"CROSS", 'n'}, + {"CROSS JOIN", 'k'}, + {"CSNG", 'f'}, + {"CSTRING", 't'}, + {"CTXSYS.DRITHSX.SN", 'f'}, + {"CUME_DIST", 'f'}, + {"CURDATE", 'f'}, + {"CURDIR", 'f'}, + {"CURRENT DATE", 'v'}, + {"CURRENT DEGREE", 'v'}, + {"CURRENT FUNCTION", 'v'}, + {"CURRENT FUNCTION PATH", 'v'}, + {"CURRENT PATH", 'v'}, + {"CURRENT SCHEMA", 'v'}, + {"CURRENT SERVER", 'v'}, + {"CURRENT TIME", 'v'}, + {"CURRENT TIMEZONE", 'v'}, + {"CURRENTUSER", 'f'}, + {"CURRENT_DATABASE", 'f'}, + {"CURRENT_DATE", 'v'}, + {"CURRENT_PATH", 'v'}, + {"CURRENT_QUERY", 'f'}, + {"CURRENT_SCHEMA", 'f'}, + {"CURRENT_SCHEMAS", 'f'}, + {"CURRENT_SERVER", 'v'}, + {"CURRENT_SETTING", 'f'}, + {"CURRENT_TIME", 'v'}, + {"CURRENT_TIMESTAMP", 'v'}, + {"CURRENT_TIMEZONE", 'v'}, + {"CURRENT_USER", 'v'}, + {"CURRVAL", 'f'}, + {"CURSOR", 'k'}, + {"CURSOR_STATUS", 'f'}, + {"CURTIME", 'f'}, + {"CVAR", 'f'}, + {"DATABASE", 'n'}, + {"DATABASEPROPERTYEX", 'f'}, + {"DATABASES", 'k'}, + {"DATABASE_PRINCIPAL_ID", 'f'}, + {"DATALENGTH", 'f'}, + {"DATE", 'f'}, + {"DATEADD", 'f'}, + {"DATEDIFF", 'f'}, + {"DATEFROMPARTS", 'f'}, + {"DATENAME", 'f'}, + {"DATEPART", 'f'}, + {"DATESERIAL", 'f'}, + {"DATETIME2FROMPARTS", 'f'}, + {"DATETIMEFROMPARTS", 'f'}, + {"DATETIMEOFFSETFROMPARTS", 'f'}, + {"DATEVALUE", 'f'}, + {"DATE_ADD", 'f'}, + {"DATE_FORMAT", 'f'}, + {"DATE_PART", 'f'}, + {"DATE_SUB", 'f'}, + {"DATE_TRUNC", 'f'}, + {"DAVG", 'f'}, + {"DAY", 'f'}, + {"DAYNAME", 'f'}, + {"DAYOFMONTH", 'f'}, + {"DAYOFWEEK", 'f'}, + {"DAYOFYEAR", 'f'}, + {"DAY_HOUR", 'k'}, + {"DAY_MICROSECOND", 'k'}, + {"DAY_MINUTE", 'k'}, + {"DAY_SECOND", 'k'}, + {"DBMS_LOCK.SLEEP", 'f'}, + {"DBMS_PIPE.RECEIVE_MESSAGE", 'f'}, + {"DBMS_UTILITY.SQLID_TO_SQLHASH", 'f'}, + {"DB_ID", 'f'}, + {"DB_NAME", 'f'}, + {"DCOUNT", 'f'}, + {"DEC", 'k'}, + {"DECIMAL", 't'}, + {"DECLARE", 'T'}, + {"DECODE", 'f'}, + {"DECRYPTBYASMKEY", 'f'}, + {"DECRYPTBYCERT", 'f'}, + {"DECRYPTBYKEY", 'f'}, + {"DECRYPTBYKEYAUTOCERT", 'f'}, + {"DECRYPTBYPASSPHRASE", 'f'}, + {"DEFAULT", 'k'}, + {"DEGREES", 'f'}, + {"DELAY", 'k'}, + {"DELAYED", 'k'}, + {"DELETE", 'T'}, + {"DENSE_RANK", 'f'}, + {"DESC", 'k'}, + {"DESCRIBE", 'k'}, + {"DES_DECRYPT", 'f'}, + {"DES_ENCRYPT", 'f'}, + {"DETERMINISTIC", 'k'}, + {"DFIRST", 'f'}, + {"DIFFERENCE", 'f'}, + {"DISTINCT", 'k'}, + {"DISTINCTROW", 'k'}, + {"DIV", 'o'}, + {"DLAST", 'f'}, + {"DLOOKUP", 'f'}, + {"DMAX", 'f'}, + {"DMIN", 'f'}, + {"DO", 'n'}, + {"DOUBLE", 't'}, + {"DOUBLE PRECISION", 't'}, + {"DROP", 'T'}, + {"DSUM", 'f'}, + {"DUAL", 'n'}, + {"EACH", 'k'}, + {"ELSE", 'k'}, + {"ELSEIF", 'k'}, + {"ELT", 'f'}, + {"ENCLOSED", 'k'}, + {"ENCODE", 'f'}, + {"ENCRYPT", 'f'}, + {"ENCRYPTBYASMKEY", 'f'}, + {"ENCRYPTBYCERT", 'f'}, + {"ENCRYPTBYKEY", 'f'}, + {"ENCRYPTBYPASSPHRASE", 'f'}, + {"ENUM_FIRST", 'f'}, + {"ENUM_LAST", 'f'}, + {"ENUM_RANGE", 'f'}, + {"EOMONTH", 'f'}, + {"EQV", 'o'}, + {"ESCAPED", 'k'}, + {"EVENTDATA", 'f'}, + {"EXCEPT", 'U'}, + {"EXEC", 'T'}, + {"EXECUTE", 'T'}, + {"EXECUTE AS", 'E'}, + {"EXECUTE AS LOGIN", 'E'}, + {"EXISTS", 'f'}, + {"EXIT", 'k'}, + {"EXP", 'f'}, + {"EXPLAIN", 'k'}, + {"EXPORT_SET", 'f'}, + {"EXTRACT", 'f'}, + {"EXTRACTVALUE", 'f'}, + {"EXTRACT_VALUE", 'f'}, + {"FALSE", '1'}, + {"FETCH", 'k'}, + {"FIELD", 'f'}, + {"FILEDATETIME", 'f'}, + {"FILEGROUPPROPERTY", 'f'}, + {"FILEGROUP_ID", 'f'}, + {"FILEGROUP_NAME", 'f'}, + {"FILELEN", 'f'}, + {"FILEPROPERTY", 'f'}, + {"FILETOBLOB", 'f'}, + {"FILETOCLOB", 'f'}, + {"FILE_ID", 'f'}, + {"FILE_IDEX", 'f'}, + {"FILE_NAME", 'f'}, + {"FIND_IN_SET", 'f'}, + {"FIRST_VALUE", 'f'}, + {"FLOAT", 't'}, + {"FLOAT4", 't'}, + {"FLOAT8", 't'}, + {"FLOOR", 'f'}, + {"FN_VIRTUALFILESTATS", 'f'}, + {"FOR", 'n'}, + {"FOR UPDATE", 'k'}, + {"FOR UPDATE NOWAIT", 'k'}, + {"FOR UPDATE OF", 'k'}, + {"FOR UPDATE SKIP", 'k'}, + {"FOR UPDATE SKIP LOCKED", 'k'}, + {"FOR UPDATE WAIT", 'k'}, + {"FORCE", 'k'}, + {"FOREIGN", 'k'}, + {"FORMAT", 'f'}, + {"FOUND_ROWS", 'f'}, + {"FROM", 'k'}, + {"FROM_BASE64", 'f'}, + {"FROM_DAYS", 'f'}, + {"FROM_UNIXTIME", 'f'}, + {"FULL JOIN", 'k'}, + {"FULL OUTER", 'k'}, + {"FULL OUTER JOIN", 'k'}, + {"FULLTEXT", 'k'}, + {"FULLTEXTCATALOGPROPERTY", 'f'}, + {"FULLTEXTSERVICEPROPERTY", 'f'}, + {"FUNCTION", 'k'}, + {"GENERATE_SERIES", 'f'}, + {"GENERATE_SUBSCRIPTS", 'f'}, + {"GETATTR", 'f'}, + {"GETDATE", 'f'}, + {"GETUTCDATE", 'f'}, + {"GET_BIT", 'f'}, + {"GET_BYTE", 'f'}, + {"GET_FORMAT", 'f'}, + {"GET_LOCK", 'f'}, + {"GO", 'T'}, + {"GOTO", 'T'}, + {"GRANT", 'k'}, + {"GREATEST", 'f'}, + {"GROUP", 'n'}, + {"GROUP BY", 'B'}, + {"GROUPING", 'f'}, + {"GROUPING_ID", 'f'}, + {"GROUP_CONCAT", 'f'}, + {"HANDLER", 'T'}, + {"HASHBYTES", 'f'}, + {"HAS_PERMS_BY_NAME", 'f'}, + {"HAVING", 'B'}, + {"HEX", 'f'}, + {"HIGH_PRIORITY", 'k'}, + {"HOST_NAME", 'f'}, + {"HOUR", 'f'}, + {"HOUR_MICROSECOND", 'k'}, + {"HOUR_MINUTE", 'k'}, + {"HOUR_SECOND", 'k'}, + {"IDENTIFY", 'f'}, + {"IDENT_CURRENT", 'f'}, + {"IDENT_INCR", 'f'}, + {"IDENT_SEED", 'f'}, + {"IF", 'f'}, + {"IF EXISTS", 'f'}, + {"IF NOT", 'f'}, + {"IF NOT EXISTS", 'f'}, + {"IFF", 'f'}, + {"IFNULL", 'f'}, + {"IGNORE", 'k'}, + {"IIF", 'f'}, + {"IN", 'k'}, + {"IN BOOLEAN", 'n'}, + {"IN BOOLEAN MODE", 'k'}, + {"INDEX", 'k'}, + {"INDEXKEY_PROPERTY", 'f'}, + {"INDEXPROPERTY", 'f'}, + {"INDEX_COL", 'f'}, + {"INET_ATON", 'f'}, + {"INET_NTOA", 'f'}, + {"INFILE", 'k'}, + {"INITCAP", 'f'}, + {"INNER", 'k'}, + {"INNER JOIN", 'k'}, + {"INOUT", 'k'}, + {"INSENSITIVE", 'k'}, + {"INSERT", 'E'}, + {"INSERT DELAYED", 'E'}, + {"INSERT DELAYED INTO", 'T'}, + {"INSERT HIGH_PRIORITY", 'E'}, + {"INSERT HIGH_PRIORITY INTO", 'T'}, + {"INSERT IGNORE", 'E'}, + {"INSERT IGNORE INTO", 'T'}, + {"INSERT INTO", 'T'}, + {"INSERT LOW_PRIORITY", 'E'}, + {"INSERT LOW_PRIORITY INTO", 'T'}, + {"INSTR", 'f'}, + {"INSTRREV", 'f'}, + {"INT", 't'}, + {"INT1", 't'}, + {"INT2", 't'}, + {"INT3", 't'}, + {"INT4", 't'}, + {"INT8", 't'}, + {"INTEGER", 't'}, + {"INTERSECT", 'U'}, + {"INTERSECT ALL", 'U'}, + {"INTERVAL", 'k'}, + {"INTO", 'k'}, + {"INTO DUMPFILE", 'k'}, + {"INTO OUTFILE", 'k'}, + {"IS", 'o'}, + {"IS DISTINCT", 'n'}, + {"IS DISTINCT FROM", 'o'}, + {"IS NOT", 'o'}, + {"IS NOT DISTINCT", 'n'}, + {"IS NOT DISTINCT FROM", 'o'}, + {"ISDATE", 'f'}, + {"ISEMPTY", 'f'}, + {"ISFINITE", 'f'}, + {"ISNULL", 'f'}, + {"ISNUMERIC", 'f'}, + {"IS_FREE_LOCK", 'f'}, + {"IS_MEMBER", 'f'}, + {"IS_OBJECTSIGNED", 'f'}, + {"IS_ROLEMEMBER", 'f'}, + {"IS_SRVROLEMEMBER", 'f'}, + {"IS_USED_LOCK", 'f'}, + {"ITERATE", 'k'}, + {"JOIN", 'k'}, + {"JSON_KEYS", 'f'}, + {"JULIANDAY", 'f'}, + {"JUSTIFY_DAYS", 'f'}, + {"JUSTIFY_HOURS", 'f'}, + {"JUSTIFY_INTERVAL", 'f'}, + {"KEYS", 'k'}, + {"KEY_GUID", 'f'}, + {"KEY_ID", 'f'}, + {"KILL", 'k'}, + {"LAG", 'f'}, + {"LASTVAL", 'f'}, + {"LAST_INSERT_ID", 'f'}, + {"LAST_INSERT_ROWID", 'f'}, + {"LAST_VALUE", 'f'}, + {"LCASE", 'f'}, + {"LEAD", 'f'}, + {"LEADING", 'k'}, + {"LEAST", 'f'}, + {"LEAVE", 'k'}, + {"LEFT", 'f'}, + {"LEFT JOIN", 'k'}, + {"LEFT OUTER", 'k'}, + {"LEFT OUTER JOIN", 'k'}, + {"LENGTH", 'f'}, + {"LIKE", 'o'}, + {"LIMIT", 'B'}, + {"LINEAR", 'k'}, + {"LINES", 'k'}, + {"LN", 'f'}, + {"LOAD", 'k'}, + {"LOAD DATA", 'T'}, + {"LOAD XML", 'T'}, + {"LOAD_EXTENSION", 'f'}, + {"LOAD_FILE", 'f'}, + {"LOCALTIME", 'v'}, + {"LOCALTIMESTAMP", 'v'}, + {"LOCATE", 'f'}, + {"LOCK", 'n'}, + {"LOCK IN", 'n'}, + {"LOCK IN SHARE", 'n'}, + {"LOCK IN SHARE MODE", 'k'}, + {"LOCK TABLE", 'k'}, + {"LOCK TABLES", 'k'}, + {"LOG", 'f'}, + {"LOG10", 'f'}, + {"LOG2", 'f'}, + {"LONGBLOB", 'k'}, + {"LONGTEXT", 'k'}, + {"LOOP", 'k'}, + {"LOWER", 'f'}, + {"LOWER_INC", 'f'}, + {"LOWER_INF", 'f'}, + {"LOW_PRIORITY", 'k'}, + {"LPAD", 'f'}, + {"LTRIM", 'f'}, + {"MAKEDATE", 'f'}, + {"MAKE_SET", 'f'}, + {"MASKLEN", 'f'}, + {"MASTER_BIND", 'k'}, + {"MASTER_POS_WAIT", 'f'}, + {"MASTER_SSL_VERIFY_SERVER_CERT", 'k'}, + {"MATCH", 'k'}, + {"MAX", 'f'}, + {"MAXVALUE", 'k'}, + {"MD5", 'f'}, + {"MEDIUMBLOB", 'k'}, + {"MEDIUMINT", 'k'}, + {"MEDIUMTEXT", 'k'}, + {"MERGE", 'k'}, + {"MICROSECOND", 'f'}, + {"MID", 'f'}, + {"MIDDLEINT", 'k'}, + {"MIN", 'f'}, + {"MINUTE", 'f'}, + {"MINUTE_MICROSECOND", 'k'}, + {"MINUTE_SECOND", 'k'}, + {"MKDIR", 'f'}, + {"MOD", 'o'}, + {"MODE", 'n'}, + {"MODIFIES", 'k'}, + {"MONEY", 't'}, + {"MONTH", 'f'}, + {"MONTHNAME", 'f'}, + {"NAME_CONST", 'f'}, + {"NATURAL", 'n'}, + {"NATURAL FULL", 'k'}, + {"NATURAL FULL OUTER JOIN", 'k'}, + {"NATURAL INNER", 'k'}, + {"NATURAL JOIN", 'k'}, + {"NATURAL LEFT", 'k'}, + {"NATURAL LEFT OUTER", 'k'}, + {"NATURAL LEFT OUTER JOIN", 'k'}, + {"NATURAL OUTER", 'k'}, + {"NATURAL RIGHT", 'k'}, + {"NATURAL RIGHT OUTER JOIN", 'k'}, + {"NETMASK", 'f'}, + {"NEXT VALUE", 'n'}, + {"NEXT VALUE FOR", 'k'}, + {"NEXTVAL", 'f'}, + {"NOT", 'o'}, + {"NOT BETWEEN", 'o'}, + {"NOT IN", 'k'}, + {"NOT LIKE", 'o'}, + {"NOT REGEXP", 'o'}, + {"NOT RLIKE", 'o'}, + {"NOT SIMILAR", 'o'}, + {"NOT SIMILAR TO", 'o'}, + {"NOTNULL", 'k'}, + {"NOW", 'f'}, + {"NOWAIT", 'k'}, + {"NO_WRITE_TO_BINLOG", 'k'}, + {"NTH_VALUE", 'f'}, + {"NTILE", 'f'}, + {"NULL", 'v'}, + {"NULLIF", 'f'}, + {"NUMERIC", 't'}, + {"NZ", 'f'}, + {"OBJECTPROPERTY", 'f'}, + {"OBJECTPROPERTYEX", 'f'}, + {"OBJECT_DEFINITION", 'f'}, + {"OBJECT_ID", 'f'}, + {"OBJECT_NAME", 'f'}, + {"OBJECT_SCHEMA_NAME", 'f'}, + {"OCT", 'f'}, + {"OCTET_LENGTH", 'f'}, + {"OFFSET", 'k'}, + {"OID", 't'}, + {"OLD_PASSWORD", 'f'}, + {"ONE_SHOT", 'k'}, + {"OPEN", 'k'}, + {"OPENDATASOURCE", 'f'}, + {"OPENQUERY", 'f'}, + {"OPENROWSET", 'f'}, + {"OPENXML", 'f'}, + {"OPTIMIZE", 'k'}, + {"OPTION", 'k'}, + {"OPTIONALLY", 'k'}, + {"OR", '&'}, + {"ORD", 'f'}, + {"ORDER", 'n'}, + {"ORDER BY", 'B'}, + {"ORIGINAL_DB_NAME", 'f'}, + {"ORIGINAL_LOGIN", 'f'}, + {"OUT", 'n'}, + {"OUTER", 'n'}, + {"OUTFILE", 'k'}, + {"OVERLAPS", 'f'}, + {"OVERLAY", 'f'}, + {"OWN3D", 'k'}, + {"OWN3D BY", 'B'}, + {"PARSENAME", 'f'}, + {"PARTITION", 'k'}, + {"PARTITION BY", 'B'}, + {"PASSWORD", 'n'}, + {"PATHINDEX", 'f'}, + {"PATINDEX", 'f'}, + {"PERCENTILE_COUNT", 'f'}, + {"PERCENTILE_DISC", 'f'}, + {"PERCENTILE_RANK", 'f'}, + {"PERCENT_RANK", 'f'}, + {"PERIOD_ADD", 'f'}, + {"PERIOD_DIFF", 'f'}, + {"PERMISSIONS", 'f'}, + {"PG_ADVISORY_LOCK", 'f'}, + {"PG_BACKEND_PID", 'f'}, + {"PG_CANCEL_BACKEND", 'f'}, + {"PG_CLIENT_ENCODING", 'f'}, + {"PG_CONF_LOAD_TIME", 'f'}, + {"PG_CREATE_RESTORE_POINT", 'f'}, + {"PG_HAS_ROLE", 'f'}, + {"PG_IS_IN_RECOVERY", 'f'}, + {"PG_IS_OTHER_TEMP_SCHEMA", 'f'}, + {"PG_LISTENING_CHANNELS", 'f'}, + {"PG_LS_DIR", 'f'}, + {"PG_MY_TEMP_SCHEMA", 'f'}, + {"PG_POSTMASTER_START_TIME", 'f'}, + {"PG_READ_BINARY_FILE", 'f'}, + {"PG_READ_FILE", 'f'}, + {"PG_RELOAD_CONF", 'f'}, + {"PG_ROTATE_LOGFILE", 'f'}, + {"PG_SLEEP", 'f'}, + {"PG_START_BACKUP", 'f'}, + {"PG_STAT_FILE", 'f'}, + {"PG_STOP_BACKUP", 'f'}, + {"PG_SWITCH_XLOG", 'f'}, + {"PG_TERMINATE_BACKEND", 'f'}, + {"PG_TRIGGER_DEPTH", 'f'}, + {"PI", 'f'}, + {"POSITION", 'f'}, + {"POW", 'f'}, + {"POWER", 'f'}, + {"PRECISION", 'k'}, + {"PREVIOUS VALUE", 'n'}, + {"PREVIOUS VALUE FOR", 'k'}, + {"PRIMARY", 'k'}, + {"PRINT", 'T'}, + {"PROCEDURE", 'k'}, + {"PROCEDURE ANALYSE", 'f'}, + {"PUBLISHINGSERVERNAME", 'f'}, + {"PURGE", 'k'}, + {"PWDCOMPARE", 'f'}, + {"PWDENCRYPT", 'f'}, + {"QUARTER", 'f'}, + {"QUOTE", 'f'}, + {"QUOTENAME", 'f'}, + {"QUOTE_IDENT", 'f'}, + {"QUOTE_LITERAL", 'f'}, + {"QUOTE_NULLABLE", 'f'}, + {"RADIANS", 'f'}, + {"RAISEERROR", 'E'}, + {"RAND", 'f'}, + {"RANDOM", 'f'}, + {"RANDOMBLOB", 'f'}, + {"RANGE", 'k'}, + {"RANK", 'f'}, + {"READ", 'k'}, + {"READ WRITE", 'k'}, + {"READS", 'k'}, + {"READ_WRITE", 'k'}, + {"REAL", 't'}, + {"REFERENCES", 'k'}, + {"REGCLASS", 't'}, + {"REGCONFIG", 't'}, + {"REGDICTIONARY", 't'}, + {"REGEXP", 'o'}, + {"REGEXP_INSTR", 'f'}, + {"REGEXP_MATCHES", 'f'}, + {"REGEXP_REPLACE", 'f'}, + {"REGEXP_SPLIT_TO_ARRAY", 'f'}, + {"REGEXP_SPLIT_TO_TABLE", 'f'}, + {"REGEXP_SUBSTR", 'f'}, + {"REGOPER", 't'}, + {"REGOPERATOR", 't'}, + {"REGPROC", 't'}, + {"REGPROCEDURE", 't'}, + {"REGTYPE", 't'}, + {"RELEASE", 'k'}, + {"RELEASE_LOCK", 'f'}, + {"RENAME", 'k'}, + {"REPEAT", 'k'}, + {"REPLACE", 'k'}, + {"REPLICATE", 'f'}, + {"REQUIRE", 'k'}, + {"RESIGNAL", 'k'}, + {"RESTRICT", 'k'}, + {"RETURN", 'k'}, + {"REVERSE", 'f'}, + {"REVOKE", 'k'}, + {"RIGHT", 'n'}, + {"RIGHT JOIN", 'k'}, + {"RIGHT OUTER", 'k'}, + {"RIGHT OUTER JOIN", 'k'}, + {"RLIKE", 'o'}, + {"ROUND", 'f'}, + {"ROW", 'f'}, + {"ROW_COUNT", 'f'}, + {"ROW_NUMBER", 'f'}, + {"ROW_TO_JSON", 'f'}, + {"RPAD", 'f'}, + {"RTRIM", 'f'}, + {"SCHAMA_NAME", 'f'}, + {"SCHEMA", 'k'}, + {"SCHEMAS", 'k'}, + {"SCHEMA_ID", 'f'}, + {"SCOPE_IDENTITY", 'f'}, + {"SECOND_MICROSECOND", 'k'}, + {"SEC_TO_TIME", 'f'}, + {"SELECT", 'E'}, + {"SELECT ALL", 'E'}, + {"SELECT DISTINCT", 'E'}, + {"SENSITIVE", 'k'}, + {"SEPARATOR", 'k'}, + {"SERIAL", 't'}, + {"SERIAL2", 't'}, + {"SERIAL4", 't'}, + {"SERIAL8", 't'}, + {"SERVERPROPERTY", 'f'}, + {"SESSION_USER", 'f'}, + {"SET", 'E'}, + {"SETATTR", 'f'}, + {"SETSEED", 'f'}, + {"SETVAL", 'f'}, + {"SET_BIT", 'f'}, + {"SET_BYTE", 'f'}, + {"SET_CONFIG", 'f'}, + {"SET_MASKLEN", 'f'}, + {"SHA", 'f'}, + {"SHA1", 'f'}, + {"SHA2", 'f'}, + {"SHOW", 'n'}, + {"SHUTDOWN", 'T'}, + {"SIGN", 'f'}, + {"SIGNAL", 'k'}, + {"SIGNBYASMKEY", 'f'}, + {"SIGNBYCERT", 'f'}, + {"SIMILAR", 'k'}, + {"SIMILAR TO", 'o'}, + {"SIN", 'f'}, + {"SLEEP", 'f'}, + {"SMALLDATETIMEFROMPARTS", 'f'}, + {"SMALLINT", 't'}, + {"SMALLSERIAL", 't'}, + {"SOME", 'f'}, + {"SOUNDEX", 'f'}, + {"SOUNDS", 'o'}, + {"SOUNDS LIKE", 'o'}, + {"SPACE", 'f'}, + {"SPATIAL", 'k'}, + {"SPECIFIC", 'k'}, + {"SPLIT_PART", 'f'}, + {"SQL", 'k'}, + {"SQLEXCEPTION", 'k'}, + {"SQLITE_VERSION", 'f'}, + {"SQLSTATE", 'k'}, + {"SQLWARNING", 'k'}, + {"SQL_BIG_RESULT", 'k'}, + {"SQL_BUFFER_RESULT", 'k'}, + {"SQL_CACHE", 'k'}, + {"SQL_CALC_FOUND_ROWS", 'k'}, + {"SQL_NO_CACHE", 'k'}, + {"SQL_SMALL_RESULT", 'k'}, + {"SQL_VARIANT_PROPERTY", 'f'}, + {"SQRT", 'f'}, + {"SSL", 'k'}, + {"STARTING", 'k'}, + {"STATEMENT_TIMESTAMP", 'f'}, + {"STATS_DATE", 'f'}, + {"STDDEV", 'f'}, + {"STDDEV_POP", 'f'}, + {"STDDEV_SAMP", 'f'}, + {"STRAIGHT_JOIN", 'k'}, + {"STRCMP", 'f'}, + {"STRCOMP", 'f'}, + {"STRCONV", 'f'}, + {"STRING_AGG", 'f'}, + {"STRING_TO_ARRAY", 'f'}, + {"STRPOS", 'f'}, + {"STR_TO_DATE", 'f'}, + {"STUFF", 'f'}, + {"SUBDATE", 'f'}, + {"SUBSTR", 'f'}, + {"SUBSTRING", 'f'}, + {"SUBSTRING_INDEX", 'f'}, + {"SUBTIME", 'f'}, + {"SUM", 'f'}, + {"SUSER_ID", 'f'}, + {"SUSER_NAME", 'f'}, + {"SUSER_SID", 'f'}, + {"SUSER_SNAME", 'f'}, + {"SWITCHOFFET", 'f'}, + {"SYS.DATABASE_NAME", 'n'}, + {"SYS.FN_BUILTIN_PERMISSIONS", 'f'}, + {"SYS.FN_GET_AUDIT_FILE", 'f'}, + {"SYS.FN_MY_PERMISSIONS", 'f'}, + {"SYS.STRAGG", 'f'}, + {"SYSCOLUMNS", 'k'}, + {"SYSDATE", 'f'}, + {"SYSDATETIME", 'f'}, + {"SYSDATETIMEOFFSET", 'f'}, + {"SYSOBJECTS", 'k'}, + {"SYSTEM_USER", 'f'}, + {"SYSUSERS", 'k'}, + {"SYSUTCDATETME", 'f'}, + {"TABLE", 'n'}, + {"TAN", 'f'}, + {"TERMINATED", 'k'}, + {"TERTIARY_WEIGHTS", 'f'}, + {"TEXT", 't'}, + {"TEXTPOS", 'f'}, + {"TEXTPTR", 'f'}, + {"TEXTVALID", 'f'}, + {"THEN", 'k'}, + {"TIME", 'k'}, + {"TIMEDIFF", 'f'}, + {"TIMEFROMPARTS", 'f'}, + {"TIMEOFDAY", 'f'}, + {"TIMESERIAL", 'f'}, + {"TIMESTAMP", 't'}, + {"TIMESTAMPADD", 'f'}, + {"TIMEVALUE", 'f'}, + {"TIME_FORMAT", 'f'}, + {"TIME_TO_SEC", 'f'}, + {"TINYBLOB", 'k'}, + {"TINYINT", 'k'}, + {"TINYTEXT", 'k'}, + {"TODATETIMEOFFSET", 'f'}, + {"TOP", 'k'}, + {"TOTAL", 'f'}, + {"TOTAL_CHANGES", 'f'}, + {"TO_ASCII", 'f'}, + {"TO_BASE64", 'f'}, + {"TO_CHAR", 'f'}, + {"TO_DATE", 'f'}, + {"TO_DAYS", 'f'}, + {"TO_HEX", 'f'}, + {"TO_NUMBER", 'f'}, + {"TO_SECONDS", 'f'}, + {"TO_TIMESTAMP", 'f'}, + {"TRAILING", 'n'}, + {"TRANSACTION_TIMESTAMP", 'f'}, + {"TRANSLATE", 'f'}, + {"TRIGGER", 'k'}, + {"TRIGGER_NESTLEVEL", 'f'}, + {"TRIM", 'f'}, + {"TRUE", '1'}, + {"TRUNC", 'f'}, + {"TRUNCATE", 'f'}, + {"TRY", 'T'}, + {"TRY_CAST", 'f'}, + {"TRY_CONVERT", 'f'}, + {"TRY_PARSE", 'f'}, + {"TYPEOF", 'f'}, + {"TYPEPROPERTY", 'f'}, + {"TYPE_ID", 'f'}, + {"TYPE_NAME", 'f'}, + {"UCASE", 'f'}, + {"UESCAPE", 'o'}, + {"UNCOMPRESS", 'f'}, + {"UNCOMPRESS_LENGTH", 'f'}, + {"UNDO", 'k'}, + {"UNHEX", 'f'}, + {"UNICODE", 'f'}, + {"UNION", 'U'}, + {"UNION ALL", 'U'}, + {"UNION ALL DISTINCT", 'U'}, + {"UNION DISTINCT", 'U'}, + {"UNION DISTINCT ALL", 'U'}, + {"UNIQUE", 'n'}, + {"UNIX_TIMESTAMP", 'f'}, + {"UNI_ON", 'U'}, + {"UNKNOWN", 'v'}, + {"UNLOCK", 'k'}, + {"UNNEST", 'f'}, + {"UNSIGNED", 'k'}, + {"UPDATE", 'E'}, + {"UPDATEXML", 'f'}, + {"UPPER", 'f'}, + {"UPPER_INC", 'f'}, + {"UPPER_INF", 'f'}, + {"USAGE", 'k'}, + {"USE", 'T'}, + {"USER", 'n'}, + {"USER_ID", 'n'}, + {"USER_LOCK.SLEEP", 'f'}, + {"USER_NAME", 'n'}, + {"USING", 'f'}, + {"UTC_DATE", 'k'}, + {"UTC_TIME", 'k'}, + {"UTC_TIMESTAMP", 'k'}, + {"UTL_HTTP.REQUEST", 'f'}, + {"UTL_INADDR.GET_HOST_ADDRESS", 'f'}, + {"UTL_INADDR.GET_HOST_NAME", 'f'}, + {"UUID", 'f'}, + {"UUID_SHORT", 'f'}, + {"VALUES", 'k'}, + {"VAR", 'f'}, + {"VARBINARY", 'k'}, + {"VARCHAR", 't'}, + {"VARCHARACTER", 'k'}, + {"VARIANCE", 'f'}, + {"VARP", 'f'}, + {"VARYING", 'k'}, + {"VAR_POP", 'f'}, + {"VAR_SAMP", 'f'}, + {"VERIFYSIGNEDBYASMKEY", 'f'}, + {"VERIFYSIGNEDBYCERT", 'f'}, + {"VERSION", 'f'}, + {"VOID", 't'}, + {"WAIT", 'k'}, + {"WAITFOR", 'n'}, + {"WAITFOR DELAY", 'E'}, + {"WAITFOR RECEIVE", 'E'}, + {"WAITFOR TIME", 'E'}, + {"WEEK", 'f'}, + {"WEEKDAY", 'f'}, + {"WEEKDAYNAME", 'f'}, + {"WEEKOFYEAR", 'f'}, + {"WHEN", 'k'}, + {"WHERE", 'k'}, + {"WHILE", 'T'}, + {"WIDTH_BUCKET", 'f'}, + {"WITH", 'n'}, + {"WITH ROLLUP", 'k'}, + {"XMLAGG", 'f'}, + {"XMLCOMMENT", 'f'}, + {"XMLCONCAT", 'f'}, + {"XMLELEMENT", 'f'}, + {"XMLEXISTS", 'f'}, + {"XMLFOREST", 'f'}, + {"XMLFORMAT", 'f'}, + {"XMLPI", 'f'}, + {"XMLROOT", 'f'}, + {"XMLTYPE", 'f'}, + {"XML_IS_WELL_FORMED", 'f'}, + {"XOR", '&'}, + {"XPATH", 'f'}, + {"XPATH_EXISTS", 'f'}, + {"XP_EXECRESULTSET", 'k'}, + {"YEAR", 'f'}, + {"YEARWEEK", 'f'}, + {"YEAR_MONTH", 'k'}, + {"ZEROBLOB", 'f'}, + {"ZEROFILL", 'k'}, + {"^=", 'o'}, + {"_ARMSCII8", 't'}, + {"_ASCII", 't'}, + {"_BIG5", 't'}, + {"_BINARY", 't'}, + {"_CP1250", 't'}, + {"_CP1251", 't'}, + {"_CP1257", 't'}, + {"_CP850", 't'}, + {"_CP852", 't'}, + {"_CP866", 't'}, + {"_CP932", 't'}, + {"_DEC8", 't'}, + {"_EUCJPMS", 't'}, + {"_EUCKR", 't'}, + {"_GB2312", 't'}, + {"_GBK", 't'}, + {"_GEOSTD8", 't'}, + {"_GREEK", 't'}, + {"_HEBREW", 't'}, + {"_HP8", 't'}, + {"_KEYBCS2", 't'}, + {"_KOI8R", 't'}, + {"_KOI8U", 't'}, + {"_LATIN1", 't'}, + {"_LATIN2", 't'}, + {"_LATIN5", 't'}, + {"_LATIN7", 't'}, + {"_MACCE", 't'}, + {"_MACROMAN", 't'}, + {"_SJIS", 't'}, + {"_SWE7", 't'}, + {"_TIS620", 't'}, + {"_UJIS", 't'}, + {"_USC2", 't'}, + {"_UTF8", 't'}, + {"|/", 'o'}, + {"|=", 'o'}, + {"||", '&'}, + {"~*", 'o'}, +}; +static const size_t sql_keywords_sz = 9352; +#endif diff --git a/src/lib/third_party/include/libinjection_xss.h b/src/lib/third_party/include/libinjection_xss.h new file mode 100644 index 0000000..3443d6e --- /dev/null +++ b/src/lib/third_party/include/libinjection_xss.h @@ -0,0 +1,21 @@ +#ifndef LIBINJECTION_XSS +#define LIBINJECTION_XSS + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * HEY THIS ISN'T DONE + */ + +/* pull in size_t */ + +#include + + int libinjection_is_xss(const char* s, size_t len, int flags); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/lib/third_party/include/ndpi_patricia.h b/src/lib/third_party/include/ndpi_patricia.h index c61513f..b53253d 100644 --- a/src/lib/third_party/include/ndpi_patricia.h +++ b/src/lib/third_party/include/ndpi_patricia.h @@ -66,6 +66,8 @@ #else #ifndef WIN32 # include /* for struct in_addr */ +#else +#include #endif #endif @@ -79,15 +81,15 @@ /* { from mrt.h */ typedef struct the_prefix4_t { - unsigned short family; /* AF_INET | AF_INET6 */ - unsigned short bitlen; /* same as mask? */ + u_int16_t family; /* AF_INET | AF_INET6 */ + u_int16_t bitlen; /* same as mask? */ int ref_count; /* reference count */ struct in_addr sin; } prefix4_t; typedef struct the_prefix_t { - unsigned short family; /* AF_INET | AF_INET6 */ - unsigned short bitlen; /* same as mask? */ + u_int16_t family; /* AF_INET | AF_INET6 */ + u_int16_t bitlen; /* same as mask? */ int ref_count; /* reference count */ union { struct in_addr sin; @@ -102,11 +104,15 @@ typedef struct the_prefix_t { /* pointer to usr data (ex. route flap info) */ union patricia_node_value_t { void *user_data; - unsigned int user_value; + + /* User-defined values */ + struct { + u_int32_t user_value, additional_user_value; + } uv; }; typedef struct _patricia_node_t { - u_int bit; /* flag if this node used */ + u_int16_t bit; /* flag if this node used */ prefix_t *prefix; /* who we are in patricia tree */ struct _patricia_node_t *l, *r; /* left and right children */ struct _patricia_node_t *parent;/* may be used */ @@ -116,7 +122,7 @@ typedef struct _patricia_node_t { typedef struct _patricia_tree_t { patricia_node_t *head; - u_int maxbits; /* for IP, 32 bit addresses */ + u_int16_t maxbits; /* for IP, 32 bit addresses */ int num_active_node; /* for debug purpose */ } patricia_tree_t; @@ -130,7 +136,7 @@ patricia_node_t * ndpi_patricia_search_best2 (patricia_tree_t *patricia, prefix_ int inclusive); patricia_node_t *ndpi_patricia_lookup (patricia_tree_t *patricia, prefix_t *prefix); void ndpi_patricia_remove (patricia_tree_t *patricia, patricia_node_t *node); -patricia_tree_t *ndpi_New_Patricia (int maxbits); +patricia_tree_t *ndpi_New_Patricia (u_int16_t maxbits); void ndpi_Clear_Patricia (patricia_tree_t *patricia, void_fn_t func); void ndpi_Destroy_Patricia (patricia_tree_t *patricia, void_fn_t func); void ndpi_patricia_process (patricia_tree_t *patricia, void_fn2_t func); diff --git a/src/lib/third_party/include/rce_injection.h b/src/lib/third_party/include/rce_injection.h new file mode 100644 index 0000000..326edac --- /dev/null +++ b/src/lib/third_party/include/rce_injection.h @@ -0,0 +1,618 @@ +#ifdef HAVE_PCRE + +#ifndef NDPI_RCE_H +#define NDPI_RCE_H + +#endif //NDPI_RCE_H + +#define N_RCE_REGEX 7 + +/* Compiled regex */ +static struct pcre_struct *comp_rx[N_RCE_REGEX]; + +static unsigned int initialized_comp_rx = 0; + +static const char *rce_regex[N_RCE_REGEX] = { +/** + * https://github.com/SpiderLabs/owasp-modsecurity-crs/blob/v3.3/dev/rules/REQUEST-932-APPLICATION-ATTACK-RCE.conf + */ + +/** + * [ Unix command injection ] + * + * This regex detects Unix command injections. + * A command injection takes a form such as: + * + * foo.jpg;uname -a + * foo.jpg||uname -a + * + * The vulnerability exists when an application executes a shell command + * without proper input escaping/validation. + * + * This regex is also triggered by an Oracle WebLogic Remote Command Execution exploit: [ Oracle WebLogic vulnerability CVE-2017-10271 - Exploit tested: https://www.exploit-db.com/exploits/43458 ] + * + * To prevent false positives, we look for a 'starting sequence' that + * precedes a command in shell syntax, such as: ; | & $( ` <( >( + * Anatomy of the regexp with examples of patterns caught: + * + * 1. Starting tokens + * + * ; ;ifconfig + * \{ {ifconfig} + * \| |ifconfig + * \|\| ||ifconfig + * & &ifconfig + * && &&ifconfig + * \n ;\nifconfig + * \r ;\rifconfig + * \$\( $(ifconfig) + * $\(\( $((ifconfig)) + * ` `ifconfig` + * \${ ${ifconfig} + * <\( <( ifconfig ) + * >\( >( ifconfig ) + * \(\s*\) a() ( ifconfig; ); a + * + * 2. Command prefixes + * + * { { ifconfig } + * \s*\(\s* ( ifconfig ) + * \w+=(?:[^\s]*|\$.*|\$.*|<.*|>.*|\'.*\'|\".*\")\s+ VARNAME=xyz ifconfig + * !\s* ! ifconfig + * \$ $ifconfig + * + * 3. Quoting + * + * ' 'ifconfig' + * \" "ifconfig" + * + * 4. Paths + * + * [\?\*\[\]\(\)\-\|+\w'\"\./\\\\]+/ /sbin/ifconfig, /s?in/./ifconfig, /s[a-b]in/ifconfig etc. + * + * This regex is case-sensitive to prevent FP ("Cat" vs. "cat"). + * + * An effort was made to combat evasions by shell quoting (e.g. 'ls', + * 'l'"s", \l\s are all valid). + * + * This is the base regex to prevent Unix Command Injection + */ + + "(?:;|\\{|\\||\\|\\||&|&&|\\n|\\r|\\$\\(|\\$\\(\\(|`|\\${|<\\(|>\\(|\\(\\s*\\))\\s*(?:{|\\s*\\(\\s*|\\w+=(?:[^\\s]*|\\$.*|\\$.*|<.*|>.*|\\'.*\\'|\\\".*\\\")\\s+|!\\s*|\\$)*\\s*(?:'|\\\")*(?:[\\?\\*\\[\\]\\(\\)\\-\\|+\\w'\\\"\\./\\\\\\\\]+/)?[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*(?:w[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*-[\\\\\\\\'\\\"]*(?:d[\\\\\\\\'\\\"]*(?:o[\\\\\\\\'\\\"]*w[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*d|u[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*p)|r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*q[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t|m[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r)|s(?:[\\\\\\\\'\\\"]*(?:b[\\\\\\\\'\\\"]*_[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*e|c[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*u|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*d|p[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*i|u[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*b|-[\\\\\\\\'\\\"]*F|h[\\\\\\\\'\\\"]*w|o[\\\\\\\\'\\\"]*f))?|z[\\\\\\\\'\\\"]*(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|c[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*t|m[\\\\\\\\'\\\"]*p)|m[\\\\\\\\'\\\"]*(?:o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|a)|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s)|e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*(?:(?:f[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l|p[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*p)[\\\\\\\\'\\\"]*e|e[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o|(?:\\s|<|>).*)|a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*g(?:[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n)?|c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*m|(?:\\s|<|>).*)|o[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*e|l)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|g[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e)|d[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*g|d[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|f[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*p(?:[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t)?|(?:[np]|y[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*x)[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|b[\\\\\\\\'\\\"]*(?:z[\\\\\\\\'\\\"]*(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t|i[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*2)|s[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t|i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*r)|a[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*(?:\\s|<|>).*|s[\\\\\\\\'\\\"]*h)|r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*k[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*w|u[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n)|c[\\\\\\\\'\\\"]*(?:o[\\\\\\\\'\\\"]*(?:m[\\\\\\\\'\\\"]*(?:p[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|m[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*d)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|p[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*c)|h[\\\\\\\\'\\\"]*(?:d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*(?:\\s|<|>).*|f[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*s|a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*r|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*d)|r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*b|(?:[cp]|a[\\\\\\\\'\\\"]*t)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|u[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*l|s[\\\\\\\\'\\\"]*h)|f[\\\\\\\\'\\\"]*(?:i(?:[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t|(?:\\s|<|>).*)|n[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*(?:\\s|<|>).*))?|t[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*(?:s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*s|w[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o|(?:\\s|<|>).*)|u[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n|(?:e[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h|c)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h|g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p)|e[\\\\\\\\'\\\"]*(?:n[\\\\\\\\'\\\"]*(?:v(?:[\\\\\\\\'\\\"]*-[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e)?|d[\\\\\\\\'\\\"]*(?:i[\\\\\\\\'\\\"]*f|s[\\\\\\\\'\\\"]*w))|x[\\\\\\\\'\\\"]*(?:p[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*d|o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*t|r)|e[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*(?:\\s|<|>).*|g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|s[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c|v[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*l)|h[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*(?:d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t|p[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*w[\\\\\\\\'\\\"]*d)|o[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*(?:n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e|i[\\\\\\\\'\\\"]*d)|(?:e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*d|u[\\\\\\\\'\\\"]*p)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|i[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*y)|i[\\\\\\\\'\\\"]*(?:p[\\\\\\\\'\\\"]*(?:(?:6[\\\\\\\\'\\\"]*)?t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*b[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s|c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*g)|r[\\\\\\\\'\\\"]*b(?:[\\\\\\\\'\\\"]*(?:1(?:[\\\\\\\\'\\\"]*[89])?|2[\\\\\\\\'\\\"]*[012]))?|f[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*g|d[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|g[\\\\\\\\'\\\"]*(?:(?:e[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*l|r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|c[\\\\\\\\'\\\"]*c|i[\\\\\\\\'\\\"]*t)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|z[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t|i[\\\\\\\\'\\\"]*p)|u[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*z[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*p|d[\\\\\\\\'\\\"]*b)|a[\\\\\\\\'\\\"]*(?:(?:l[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*s|w[\\\\\\\\'\\\"]*k)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|d[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r|p[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*-[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t|r[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*(?:\\s|<|>).*|p))|d[\\\\\\\\'\\\"]*(?:h[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*t|(?:i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|u)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|(?:m[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s|p[\\\\\\\\'\\\"]*k)[\\\\\\\\'\\\"]*g|o[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*s|n[\\\\\\\\'\\\"]*e)|a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*h)|m[\\\\\\\\'\\\"]*(?:(?:k[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*r|o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|a[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*(?:x[\\\\\\\\'\\\"]*(?:\\s|<|>).*|q)|l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e)|j[\\\\\\\\'\\\"]*(?:(?:a[\\\\\\\\'\\\"]*v[\\\\\\\\'\\\"]*a|o[\\\\\\\\'\\\"]*b[\\\\\\\\'\\\"]*s)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|e[\\\\\\\\'\\\"]*x[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*c)|k[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*l|(?:\\s|<|>).*)|(?:G[\\\\\\\\'\\\"]*E[\\\\\\\\'\\\"]*T[\\\\\\\\'\\\"]*(?:\\s|<|>)|\\.\\s).*|7[\\\\\\\\'\\\"]*z(?:[\\\\\\\\'\\\"]*[ar])?)\\b", + + "(?:;|\\{|\\||\\|\\||&|&&|\\n|\\r|\\$\\(|\\$\\(\\(|`|\\${|<\\(|>\\(|\\(\\s*\\))\\s*(?:{|\\s*\\(\\s*|\\w+=(?:[^\\s]*|\\$.*|\\$.*|<.*|>.*|\\'.*\\'|\\\".*\\\")\\s+|!\\s*|\\$)*\\s*(?:'|\\\")*(?:[\\?\\*\\[\\]\\(\\)\\-\\|+\\w'\\\"\\./\\\\\\\\]+/)?[\\\\\\\\'\\\"]*(?:s[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*(?:(?:f[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*)?(?:\\s|<|>).*|e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*v|s[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*d)|n[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l|d[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|h[\\\\\\\\'\\\"]*(?:\\.[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*b|u[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*w[\\\\\\\\'\\\"]*n|(?:\\s|<|>).*)|o[\\\\\\\\'\\\"]*(?:(?:u[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e|r[\\\\\\\\'\\\"]*t)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|c[\\\\\\\\'\\\"]*(?:h[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*d|p[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*s|(?:l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*e|f[\\\\\\\\'\\\"]*t)[\\\\\\\\'\\\"]*p|y[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*l|u[\\\\\\\\'\\\"]*(?:(?:\\s|<|>).*|d[\\\\\\\\'\\\"]*o)|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|s[\\\\\\\\'\\\"]*h|v[\\\\\\\\'\\\"]*n)|p[\\\\\\\\'\\\"]*(?:k[\\\\\\\\'\\\"]*(?:g(?:(?:[\\\\\\\\'\\\"]*_)?[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*o)?|e[\\\\\\\\'\\\"]*x[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*c|i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*l)|t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*r(?:[\\\\\\\\'\\\"]*(?:d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p))?|a[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*(?:\\s|<|>).*|s[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*w[\\\\\\\\'\\\"]*d)|r[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*v|f[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|y[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n(?:[\\\\\\\\'\\\"]*(?:3(?:[\\\\\\\\'\\\"]*m)?|2))?|e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*(?:l(?:[\\\\\\\\'\\\"]*(?:s[\\\\\\\\'\\\"]*h|5))?|m[\\\\\\\\'\\\"]*s)|(?:g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|f[\\\\\\\\'\\\"]*t)[\\\\\\\\'\\\"]*p|(?:u[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*h|o[\\\\\\\\'\\\"]*p)[\\\\\\\\'\\\"]*d|h[\\\\\\\\'\\\"]*p(?:[\\\\\\\\'\\\"]*[57])?|i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g|s[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|n[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*(?:\\.[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*l|o[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*b[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*d)|(?:\\s|<|>).*|a[\\\\\\\\'\\\"]*t)|e[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*(?:k[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*-[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*p|(?:s[\\\\\\\\'\\\"]*t|c)[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t|(?:\\s|<|>).*)|s[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*k[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*p|t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|(?:a[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*o|i[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|(?:o[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*u|m[\\\\\\\\'\\\"]*a)[\\\\\\\\'\\\"]*p|p[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g)|r[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*(?:(?:p[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e|e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|a[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*h)|m[\\\\\\\\'\\\"]*(?:(?:d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*)?(?:\\s|<|>).*|u[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r)|u[\\\\\\\\'\\\"]*b[\\\\\\\\'\\\"]*y(?:[\\\\\\\\'\\\"]*(?:1(?:[\\\\\\\\'\\\"]*[89])?|2[\\\\\\\\'\\\"]*[012]))?|(?:a[\\\\\\\\'\\\"]*r|c[\\\\\\\\'\\\"]*p|p[\\\\\\\\'\\\"]*m)[\\\\\\\\'\\\"]*(?:\\s|<|>).*|n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*o|o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e|s[\\\\\\\\'\\\"]*y[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*c)|t[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*(?:p[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e|i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g)|s[\\\\\\\\'\\\"]*h)|r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e(?:[\\\\\\\\'\\\"]*6)?|e[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t|e[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|i[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*(?:o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*t|(?:\\s|<|>).*)|a[\\\\\\\\'\\\"]*(?:i[\\\\\\\\'\\\"]*l(?:[\\\\\\\\'\\\"]*f)?|r[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|o[\\\\\\\\'\\\"]*(?:u[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*(?:\\s|<|>).*|p))|u[\\\\\\\\'\\\"]*(?:n[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*(?:i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*k[\\\\\\\\'\\\"]*(?:\\s|<|>).*|z[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*a)|c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e|r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*r|s[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t|z[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*p|x[\\\\\\\\'\\\"]*z)|s[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*(?:(?:a[\\\\\\\\'\\\"]*d|m[\\\\\\\\'\\\"]*o)[\\\\\\\\'\\\"]*d|d[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*l)|l[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|m[\\\\\\\\'\\\"]*(?:y[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*q[\\\\\\\\'\\\"]*l(?:[\\\\\\\\'\\\"]*(?:d[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*p(?:[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*w)?|h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*y|a[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n|s[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*w))?|(?:(?:o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*n|u[\\\\\\\\'\\\"]*t)[\\\\\\\\'\\\"]*t|v)[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|x[\\\\\\\\'\\\"]*(?:z[\\\\\\\\'\\\"]*(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|d[\\\\\\\\'\\\"]*(?:i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|e[\\\\\\\\'\\\"]*c)|c[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*t|m[\\\\\\\\'\\\"]*p)|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|(?:\\s|<|>).*)|a[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*s|t[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*m|x[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*(?:\\s|<|>).*)|z[\\\\\\\\'\\\"]*(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|c[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*t|m[\\\\\\\\'\\\"]*p)|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|i[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*(?:\\s|<|>).*|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|r[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*n|s[\\\\\\\\'\\\"]*h)|o[\\\\\\\\'\\\"]*(?:p[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*l|n[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*r)|w[\\\\\\\\'\\\"]*(?:h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*i|(?:\\s|<|>).*)|g[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t|3[\\\\\\\\'\\\"]*m)|v[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*(?:m[\\\\\\\\'\\\"]*(?:\\s|<|>).*|g[\\\\\\\\'\\\"]*r|p[\\\\\\\\'\\\"]*w)|y[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*m)\\b", + +/* ********************************** */ + +/** + * [ Windows command injection ] + * + * This regex detects Windows shell command injections. + * + * A command injection takes a form such as: + * + * foo.jpg&ver /r + * foo.jpg|ver /r + * + * The vulnerability exists when an application executes a shell command + * without proper input escaping/validation. + * + * To prevent false positives, we look for a 'starting sequence' that + * precedes a command in CMD syntax, such as: ; | & ` + * + * Anatomy of the regexp: + * + * 1. Starting tokens + * + * ; ;cmd + * \{ {cmd + * \| |cmd + * \|\| ||cmd + * & &cmd + * && &&cmd + * \n \ncmd + * \r \rcmd + * ` `cmd + * + * 2. Command prefixes + * + * ( (cmd) + * , ,cmd + * @ @cmd + * ' 'cmd' + * " "cmd" + * \s spacing+cmd + * + * 3. Paths + * + * [\w'\"\./]+/ /path/cmd + * [\\\\'\"\^]*\w[\\\\'\"\^]*:.*\\\\ C:\Program Files\cmd + * [\^\.\w '\"/\\\\]*\\\\)?[\"\^]* \\net\share\dir\cmd + * + * 4. Quoting + * + * \" "cmd" + * \^ ^cmd + * + * 5. Extension/switches + * + * \.[\"\^]*\w+ cmd.com, cmd.exe, etc. + * /b cmd/h + * + * An effort is made to combat evasions by CMD syntax; for example, + * the following strings are valid: c^md, @cmd, "c"md. + * + * This regex is case-insensitive. + */ + + "(?i)(?:;|\\{|\\||\\|\\||&|&&|\\n|\\r|`)\\s*[\\(,@\\'\\\"\\s]*(?:[\\w'\\\"\\./]+/|[\\\\\\\\'\\\"\\^]*\\w[\\\\\\\\'\\\"\\^]*:.*\\\\\\\\|[\\^\\.\\w '\\\"/\\\\\\\\]*\\\\\\\\)?[\\\"\\^]*(?:m[\\\"\\^]*(?:y[\\\"\\^]*s[\\\"\\^]*q[\\\"\\^]*l(?:[\\\"\\^]*(?:d[\\\"\\^]*u[\\\"\\^]*m[\\\"\\^]*p(?:[\\\"\\^]*s[\\\"\\^]*l[\\\"\\^]*o[\\\"\\^]*w)?|h[\\\"\\^]*o[\\\"\\^]*t[\\\"\\^]*c[\\\"\\^]*o[\\\"\\^]*p[\\\"\\^]*y|a[\\\"\\^]*d[\\\"\\^]*m[\\\"\\^]*i[\\\"\\^]*n|s[\\\"\\^]*h[\\\"\\^]*o[\\\"\\^]*w))?|s[\\\"\\^]*(?:i[\\\"\\^]*(?:n[\\\"\\^]*f[\\\"\\^]*o[\\\"\\^]*3[\\\"\\^]*2|e[\\\"\\^]*x[\\\"\\^]*e[\\\"\\^]*c)|c[\\\"\\^]*o[\\\"\\^]*n[\\\"\\^]*f[\\\"\\^]*i[\\\"\\^]*g|g[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|t[\\\"\\^]*s[\\\"\\^]*c)|o[\\\"\\^]*(?:u[\\\"\\^]*n[\\\"\\^]*t[\\\"\\^]*(?:(?:[\\s,;]|\\.|/|<|>).*|v[\\\"\\^]*o[\\\"\\^]*l)|v[\\\"\\^]*e[\\\"\\^]*u[\\\"\\^]*s[\\\"\\^]*e[\\\"\\^]*r|[dr][\\\"\\^]*e[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*)|k[\\\"\\^]*(?:d[\\\"\\^]*i[\\\"\\^]*r[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|l[\\\"\\^]*i[\\\"\\^]*n[\\\"\\^]*k)|d[\\\"\\^]*(?:s[\\\"\\^]*c[\\\"\\^]*h[\\\"\\^]*e[\\\"\\^]*d|(?:[\\s,;]|\\.|/|<|>).*)|a[\\\"\\^]*p[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*e[\\\"\\^]*n[\\\"\\^]*d|b[\\\"\\^]*s[\\\"\\^]*a[\\\"\\^]*c[\\\"\\^]*l[\\\"\\^]*i|e[\\\"\\^]*a[\\\"\\^]*s[\\\"\\^]*u[\\\"\\^]*r[\\\"\\^]*e|m[\\\"\\^]*s[\\\"\\^]*y[\\\"\\^]*s)|d[\\\"\\^]*(?:i[\\\"\\^]*(?:s[\\\"\\^]*k[\\\"\\^]*(?:(?:m[\\\"\\^]*g[\\\"\\^]*m|p[\\\"\\^]*a[\\\"\\^]*r)[\\\"\\^]*t|s[\\\"\\^]*h[\\\"\\^]*a[\\\"\\^]*d[\\\"\\^]*o[\\\"\\^]*w)|r[\\\"\\^]*(?:(?:[\\s,;]|\\.|/|<|>).*|u[\\\"\\^]*s[\\\"\\^]*e)|f[\\\"\\^]*f[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*)|e[\\\"\\^]*(?:l[\\\"\\^]*(?:p[\\\"\\^]*r[\\\"\\^]*o[\\\"\\^]*f|t[\\\"\\^]*r[\\\"\\^]*e[\\\"\\^]*e|(?:[\\s,;]|\\.|/|<|>).*)|v[\\\"\\^]*(?:m[\\\"\\^]*g[\\\"\\^]*m[\\\"\\^]*t|c[\\\"\\^]*o[\\\"\\^]*n)|(?:f[\\\"\\^]*r[\\\"\\^]*a|b[\\\"\\^]*u)[\\\"\\^]*g)|s[\\\"\\^]*(?:a[\\\"\\^]*(?:c[\\\"\\^]*l[\\\"\\^]*s|d[\\\"\\^]*d)|q[\\\"\\^]*u[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*y|m[\\\"\\^]*o[\\\"\\^]*(?:v[\\\"\\^]*e|d)|g[\\\"\\^]*e[\\\"\\^]*t|r[\\\"\\^]*m)|(?:r[\\\"\\^]*i[\\\"\\^]*v[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*q[\\\"\\^]*u[\\\"\\^]*e[\\\"\\^]*r|o[\\\"\\^]*s[\\\"\\^]*k[\\\"\\^]*e)[\\\"\\^]*y|(?:c[\\\"\\^]*o[\\\"\\^]*m[\\\"\\^]*c[\\\"\\^]*n[\\\"\\^]*f|x[\\\"\\^]*d[\\\"\\^]*i[\\\"\\^]*a)[\\\"\\^]*g|a[\\\"\\^]*t[\\\"\\^]*e[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|n[\\\"\\^]*s[\\\"\\^]*s[\\\"\\^]*t[\\\"\\^]*a[\\\"\\^]*t)|c[\\\"\\^]*(?:o[\\\"\\^]*(?:m[\\\"\\^]*(?:p[\\\"\\^]*(?:(?:a[\\\"\\^]*c[\\\"\\^]*t[\\\"\\^]*)?(?:[\\s,;]|\\.|/|<|>).*|m[\\\"\\^]*g[\\\"\\^]*m[\\\"\\^]*t)|e[\\\"\\^]*x[\\\"\\^]*p)|n[\\\"\\^]*(?:2[\\\"\\^]*p|v[\\\"\\^]*e)[\\\"\\^]*r[\\\"\\^]*t|p[\\\"\\^]*y)|l[\\\"\\^]*(?:e[\\\"\\^]*a[\\\"\\^]*(?:n[\\\"\\^]*m[\\\"\\^]*g[\\\"\\^]*r|r[\\\"\\^]*m[\\\"\\^]*e[\\\"\\^]*m)|u[\\\"\\^]*s[\\\"\\^]*t[\\\"\\^]*e[\\\"\\^]*r)|h[\\\"\\^]*(?:k[\\\"\\^]*(?:n[\\\"\\^]*t[\\\"\\^]*f[\\\"\\^]*s|d[\\\"\\^]*s[\\\"\\^]*k)|d[\\\"\\^]*i[\\\"\\^]*r[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*)|s[\\\"\\^]*(?:c[\\\"\\^]*(?:r[\\\"\\^]*i[\\\"\\^]*p[\\\"\\^]*t|c[\\\"\\^]*m[\\\"\\^]*d)|v[\\\"\\^]*d[\\\"\\^]*e)|e[\\\"\\^]*r[\\\"\\^]*t[\\\"\\^]*(?:u[\\\"\\^]*t[\\\"\\^]*i[\\\"\\^]*l|r[\\\"\\^]*e[\\\"\\^]*q)|a[\\\"\\^]*(?:l[\\\"\\^]*l[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|c[\\\"\\^]*l[\\\"\\^]*s)|m[\\\"\\^]*d(?:[\\\"\\^]*k[\\\"\\^]*e[\\\"\\^]*y)?|i[\\\"\\^]*p[\\\"\\^]*h[\\\"\\^]*e[\\\"\\^]*r|u[\\\"\\^]*r[\\\"\\^]*l)|f[\\\"\\^]*(?:o[\\\"\\^]*r[\\\"\\^]*(?:m[\\\"\\^]*a[\\\"\\^]*t[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|f[\\\"\\^]*i[\\\"\\^]*l[\\\"\\^]*e[\\\"\\^]*s|e[\\\"\\^]*a[\\\"\\^]*c[\\\"\\^]*h)|i[\\\"\\^]*n[\\\"\\^]*d[\\\"\\^]*(?:(?:[\\s,;]|\\.|/|<|>).*|s[\\\"\\^]*t[\\\"\\^]*r)|s[\\\"\\^]*(?:m[\\\"\\^]*g[\\\"\\^]*m[\\\"\\^]*t|u[\\\"\\^]*t[\\\"\\^]*i[\\\"\\^]*l)|t[\\\"\\^]*(?:p[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|y[\\\"\\^]*p[\\\"\\^]*e)|r[\\\"\\^]*e[\\\"\\^]*e[\\\"\\^]*d[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*k|c[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|g[\\\"\\^]*r[\\\"\\^]*e[\\\"\\^]*p)|n[\\\"\\^]*(?:e[\\\"\\^]*t[\\\"\\^]*(?:s[\\\"\\^]*(?:t[\\\"\\^]*a[\\\"\\^]*t|v[\\\"\\^]*c|h)|(?:[\\s,;]|\\.|/|<|>).*|c[\\\"\\^]*a[\\\"\\^]*t|d[\\\"\\^]*o[\\\"\\^]*m)|t[\\\"\\^]*(?:b[\\\"\\^]*a[\\\"\\^]*c[\\\"\\^]*k[\\\"\\^]*u[\\\"\\^]*p|r[\\\"\\^]*i[\\\"\\^]*g[\\\"\\^]*h[\\\"\\^]*t[\\\"\\^]*s)|(?:s[\\\"\\^]*l[\\\"\\^]*o[\\\"\\^]*o[\\\"\\^]*k[\\\"\\^]*u|m[\\\"\\^]*a)[\\\"\\^]*p|c[\\\"\\^]*(?:(?:[\\s,;]|\\.|/|<|>).*|a[\\\"\\^]*t)|b[\\\"\\^]*t[\\\"\\^]*s[\\\"\\^]*t[\\\"\\^]*a[\\\"\\^]*t)|e[\\\"\\^]*(?:x[\\\"\\^]*p[\\\"\\^]*(?:a[\\\"\\^]*n[\\\"\\^]*d[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|l[\\\"\\^]*o[\\\"\\^]*r[\\\"\\^]*e[\\\"\\^]*r)|v[\\\"\\^]*e[\\\"\\^]*n[\\\"\\^]*t[\\\"\\^]*(?:c[\\\"\\^]*r[\\\"\\^]*e[\\\"\\^]*a[\\\"\\^]*t[\\\"\\^]*e|v[\\\"\\^]*w[\\\"\\^]*r)|n[\\\"\\^]*d[\\\"\\^]*l[\\\"\\^]*o[\\\"\\^]*c[\\\"\\^]*a[\\\"\\^]*l|g[\\\"\\^]*r[\\\"\\^]*e[\\\"\\^]*p|r[\\\"\\^]*a[\\\"\\^]*s[\\\"\\^]*e|c[\\\"\\^]*h[\\\"\\^]*o)|g[\\\"\\^]*(?:a[\\\"\\^]*t[\\\"\\^]*h[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*n[\\\"\\^]*e[\\\"\\^]*t[\\\"\\^]*w[\\\"\\^]*o[\\\"\\^]*r[\\\"\\^]*k[\\\"\\^]*i[\\\"\\^]*n[\\\"\\^]*f[\\\"\\^]*o|p[\\\"\\^]*(?:(?:r[\\\"\\^]*e[\\\"\\^]*s[\\\"\\^]*u[\\\"\\^]*l|e[\\\"\\^]*d[\\\"\\^]*i)[\\\"\\^]*t|u[\\\"\\^]*p[\\\"\\^]*d[\\\"\\^]*a[\\\"\\^]*t[\\\"\\^]*e)|i[\\\"\\^]*t[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|e[\\\"\\^]*t[\\\"\\^]*m[\\\"\\^]*a[\\\"\\^]*c)|i[\\\"\\^]*(?:r[\\\"\\^]*b(?:[\\\"\\^]*(?:1(?:[\\\"\\^]*[89])?|2[\\\"\\^]*[012]))?|f[\\\"\\^]*m[\\\"\\^]*e[\\\"\\^]*m[\\\"\\^]*b[\\\"\\^]*e[\\\"\\^]*r|p[\\\"\\^]*c[\\\"\\^]*o[\\\"\\^]*n[\\\"\\^]*f[\\\"\\^]*i[\\\"\\^]*g|n[\\\"\\^]*e[\\\"\\^]*t[\\\"\\^]*c[\\\"\\^]*p[\\\"\\^]*l|c[\\\"\\^]*a[\\\"\\^]*c[\\\"\\^]*l[\\\"\\^]*s)|a[\\\"\\^]*(?:d[\\\"\\^]*(?:d[\\\"\\^]*u[\\\"\\^]*s[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*s|m[\\\"\\^]*o[\\\"\\^]*d[\\\"\\^]*c[\\\"\\^]*m[\\\"\\^]*d)|r[\\\"\\^]*p[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|t[\\\"\\^]*t[\\\"\\^]*r[\\\"\\^]*i[\\\"\\^]*b|s[\\\"\\^]*s[\\\"\\^]*o[\\\"\\^]*c|z[\\\"\\^]*m[\\\"\\^]*a[\\\"\\^]*n)|l[\\\"\\^]*(?:o[\\\"\\^]*g[\\\"\\^]*(?:e[\\\"\\^]*v[\\\"\\^]*e[\\\"\\^]*n[\\\"\\^]*t|t[\\\"\\^]*i[\\\"\\^]*m[\\\"\\^]*e|m[\\\"\\^]*a[\\\"\\^]*n|o[\\\"\\^]*f[\\\"\\^]*f)|a[\\\"\\^]*b[\\\"\\^]*e[\\\"\\^]*l[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|u[\\\"\\^]*s[\\\"\\^]*r[\\\"\\^]*m[\\\"\\^]*g[\\\"\\^]*r)|b[\\\"\\^]*(?:(?:c[\\\"\\^]*d[\\\"\\^]*(?:b[\\\"\\^]*o[\\\"\\^]*o|e[\\\"\\^]*d[\\\"\\^]*i)|r[\\\"\\^]*o[\\\"\\^]*w[\\\"\\^]*s[\\\"\\^]*t[\\\"\\^]*a)[\\\"\\^]*t|i[\\\"\\^]*t[\\\"\\^]*s[\\\"\\^]*a[\\\"\\^]*d[\\\"\\^]*m[\\\"\\^]*i[\\\"\\^]*n|o[\\\"\\^]*o[\\\"\\^]*t[\\\"\\^]*c[\\\"\\^]*f[\\\"\\^]*g)|h[\\\"\\^]*(?:o[\\\"\\^]*s[\\\"\\^]*t[\\\"\\^]*n[\\\"\\^]*a[\\\"\\^]*m[\\\"\\^]*e|d[\\\"\\^]*w[\\\"\\^]*w[\\\"\\^]*i[\\\"\\^]*z)|j[\\\"\\^]*a[\\\"\\^]*v[\\\"\\^]*a[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|7[\\\"\\^]*z(?:[\\\"\\^]*[ar])?)(?:\\.[\\\"\\^]*\\w+)?\\b", + +/* ********************************** */ + +/** + *This regex is also triggered by an Oracle WebLogic Remote Command Execution exploit: + *[ Oracle WebLogic vulnerability CVE-2017-10271 - Exploit tested: https://www.exploit-db.com/exploits/43458 ] + */ + + "(?i)(?:;|\\{|\\||\\|\\||&|&&|\\n|\\r|`)\\s*[\\(,@\\'\\\"\\s]*(?:[\\w'\\\"\\./]+/|[\\\\\\\\'\\\"\\^]*\\w[\\\\\\\\'\\\"\\^]*:.*\\\\\\\\|[\\^\\.\\w '\\\"/\\\\\\\\]*\\\\\\\\)?[\\\"\\^]*(?:s[\\\"\\^]*(?:y[\\\"\\^]*s[\\\"\\^]*(?:t[\\\"\\^]*e[\\\"\\^]*m[\\\"\\^]*(?:p[\\\"\\^]*r[\\\"\\^]*o[\\\"\\^]*p[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*t[\\\"\\^]*i[\\\"\\^]*e[\\\"\\^]*s[\\\"\\^]*(?:d[\\\"\\^]*a[\\\"\\^]*t[\\\"\\^]*a[\\\"\\^]*e[\\\"\\^]*x[\\\"\\^]*e[\\\"\\^]*c[\\\"\\^]*u[\\\"\\^]*t[\\\"\\^]*i[\\\"\\^]*o[\\\"\\^]*n[\\\"\\^]*p[\\\"\\^]*r[\\\"\\^]*e[\\\"\\^]*v[\\\"\\^]*e[\\\"\\^]*n[\\\"\\^]*t[\\\"\\^]*i[\\\"\\^]*o[\\\"\\^]*n|(?:p[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*f[\\\"\\^]*o[\\\"\\^]*r[\\\"\\^]*m[\\\"\\^]*a[\\\"\\^]*n[\\\"\\^]*c|h[\\\"\\^]*a[\\\"\\^]*r[\\\"\\^]*d[\\\"\\^]*w[\\\"\\^]*a[\\\"\\^]*r)[\\\"\\^]*e|a[\\\"\\^]*d[\\\"\\^]*v[\\\"\\^]*a[\\\"\\^]*n[\\\"\\^]*c[\\\"\\^]*e[\\\"\\^]*d)|i[\\\"\\^]*n[\\\"\\^]*f[\\\"\\^]*o)|k[\\\"\\^]*e[\\\"\\^]*y|d[\\\"\\^]*m)|h[\\\"\\^]*(?:o[\\\"\\^]*(?:w[\\\"\\^]*(?:g[\\\"\\^]*r[\\\"\\^]*p|m[\\\"\\^]*b[\\\"\\^]*r)[\\\"\\^]*s|r[\\\"\\^]*t[\\\"\\^]*c[\\\"\\^]*u[\\\"\\^]*t)|e[\\\"\\^]*l[\\\"\\^]*l[\\\"\\^]*r[\\\"\\^]*u[\\\"\\^]*n[\\\"\\^]*a[\\\"\\^]*s|u[\\\"\\^]*t[\\\"\\^]*d[\\\"\\^]*o[\\\"\\^]*w[\\\"\\^]*n|r[\\\"\\^]*p[\\\"\\^]*u[\\\"\\^]*b[\\\"\\^]*w|a[\\\"\\^]*r[\\\"\\^]*e|i[\\\"\\^]*f[\\\"\\^]*t)|e[\\\"\\^]*(?:t[\\\"\\^]*(?:(?:x[\\\"\\^]*)?(?:[\\s,;]|\\.|/|<|>).*|l[\\\"\\^]*o[\\\"\\^]*c[\\\"\\^]*a[\\\"\\^]*l)|c[\\\"\\^]*p[\\\"\\^]*o[\\\"\\^]*l|l[\\\"\\^]*e[\\\"\\^]*c[\\\"\\^]*t)|c[\\\"\\^]*(?:h[\\\"\\^]*t[\\\"\\^]*a[\\\"\\^]*s[\\\"\\^]*k[\\\"\\^]*s|l[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*t)|u[\\\"\\^]*b[\\\"\\^]*(?:i[\\\"\\^]*n[\\\"\\^]*a[\\\"\\^]*c[\\\"\\^]*l|s[\\\"\\^]*t)|t[\\\"\\^]*a[\\\"\\^]*r[\\\"\\^]*t[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|i[\\\"\\^]*g[\\\"\\^]*v[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*i[\\\"\\^]*f|l[\\\"\\^]*(?:e[\\\"\\^]*e[\\\"\\^]*p|m[\\\"\\^]*g[\\\"\\^]*r)|o[\\\"\\^]*r[\\\"\\^]*t|f[\\\"\\^]*c|v[\\\"\\^]*n)|p[\\\"\\^]*(?:s[\\\"\\^]*(?:s[\\\"\\^]*(?:h[\\\"\\^]*u[\\\"\\^]*t[\\\"\\^]*d[\\\"\\^]*o[\\\"\\^]*w[\\\"\\^]*n|e[\\\"\\^]*r[\\\"\\^]*v[\\\"\\^]*i[\\\"\\^]*c[\\\"\\^]*e|u[\\\"\\^]*s[\\\"\\^]*p[\\\"\\^]*e[\\\"\\^]*n[\\\"\\^]*d)|l[\\\"\\^]*(?:o[\\\"\\^]*g[\\\"\\^]*(?:g[\\\"\\^]*e[\\\"\\^]*d[\\\"\\^]*o[\\\"\\^]*n|l[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*t)|i[\\\"\\^]*s[\\\"\\^]*t)|p[\\\"\\^]*(?:a[\\\"\\^]*s[\\\"\\^]*s[\\\"\\^]*w[\\\"\\^]*d|i[\\\"\\^]*n[\\\"\\^]*g)|g[\\\"\\^]*e[\\\"\\^]*t[\\\"\\^]*s[\\\"\\^]*i[\\\"\\^]*d|e[\\\"\\^]*x[\\\"\\^]*e[\\\"\\^]*c|f[\\\"\\^]*i[\\\"\\^]*l[\\\"\\^]*e|i[\\\"\\^]*n[\\\"\\^]*f[\\\"\\^]*o|k[\\\"\\^]*i[\\\"\\^]*l[\\\"\\^]*l)|o[\\\"\\^]*(?:w[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*(?:s[\\\"\\^]*h[\\\"\\^]*e[\\\"\\^]*l[\\\"\\^]*l(?:[\\\"\\^]*_[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*e)?|c[\\\"\\^]*f[\\\"\\^]*g)|r[\\\"\\^]*t[\\\"\\^]*q[\\\"\\^]*r[\\\"\\^]*y|p[\\\"\\^]*d)|r[\\\"\\^]*(?:i[\\\"\\^]*n[\\\"\\^]*t[\\\"\\^]*(?:(?:[\\s,;]|\\.|/|<|>).*|b[\\\"\\^]*r[\\\"\\^]*m)|n[\\\"\\^]*(?:c[\\\"\\^]*n[\\\"\\^]*f[\\\"\\^]*g|m[\\\"\\^]*n[\\\"\\^]*g[\\\"\\^]*r)|o[\\\"\\^]*m[\\\"\\^]*p[\\\"\\^]*t)|a[\\\"\\^]*t[\\\"\\^]*h[\\\"\\^]*(?:p[\\\"\\^]*i[\\\"\\^]*n[\\\"\\^]*g|(?:[\\s,;]|\\.|/|<|>).*)|e[\\\"\\^]*r[\\\"\\^]*(?:l(?:[\\\"\\^]*(?:s[\\\"\\^]*h|5))?|f[\\\"\\^]*m[\\\"\\^]*o[\\\"\\^]*n)|y[\\\"\\^]*t[\\\"\\^]*h[\\\"\\^]*o[\\\"\\^]*n(?:[\\\"\\^]*(?:3(?:[\\\"\\^]*m)?|2))?|k[\\\"\\^]*g[\\\"\\^]*m[\\\"\\^]*g[\\\"\\^]*r|h[\\\"\\^]*p(?:[\\\"\\^]*[57])?|u[\\\"\\^]*s[\\\"\\^]*h[\\\"\\^]*d|i[\\\"\\^]*n[\\\"\\^]*g)|r[\\\"\\^]*(?:e[\\\"\\^]*(?:(?:p[\\\"\\^]*l[\\\"\\^]*a[\\\"\\^]*c[\\\"\\^]*e|n(?:[\\\"\\^]*a[\\\"\\^]*m[\\\"\\^]*e)?|s[\\\"\\^]*e[\\\"\\^]*t)[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|g[\\\"\\^]*(?:s[\\\"\\^]*v[\\\"\\^]*r[\\\"\\^]*3[\\\"\\^]*2|e[\\\"\\^]*d[\\\"\\^]*i[\\\"\\^]*t|(?:[\\s,;]|\\.|/|<|>).*|i[\\\"\\^]*n[\\\"\\^]*i)|c[\\\"\\^]*(?:d[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*c|o[\\\"\\^]*v[\\\"\\^]*e[\\\"\\^]*r)|k[\\\"\\^]*e[\\\"\\^]*y[\\\"\\^]*w[\\\"\\^]*i[\\\"\\^]*z)|u[\\\"\\^]*(?:n[\\\"\\^]*(?:d[\\\"\\^]*l[\\\"\\^]*l[\\\"\\^]*3[\\\"\\^]*2|a[\\\"\\^]*s)|b[\\\"\\^]*y[\\\"\\^]*(?:1(?:[\\\"\\^]*[89])?|2[\\\"\\^]*[012]))|a[\\\"\\^]*(?:s[\\\"\\^]*(?:p[\\\"\\^]*h[\\\"\\^]*o[\\\"\\^]*n[\\\"\\^]*e|d[\\\"\\^]*i[\\\"\\^]*a[\\\"\\^]*l)|r[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*)|m[\\\"\\^]*(?:(?:d[\\\"\\^]*i[\\\"\\^]*r[\\\"\\^]*)?(?:[\\s,;]|\\.|/|<|>).*|t[\\\"\\^]*s[\\\"\\^]*h[\\\"\\^]*a[\\\"\\^]*r[\\\"\\^]*e)|o[\\\"\\^]*(?:u[\\\"\\^]*t[\\\"\\^]*e[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|b[\\\"\\^]*o[\\\"\\^]*c[\\\"\\^]*o[\\\"\\^]*p[\\\"\\^]*y)|s[\\\"\\^]*(?:t[\\\"\\^]*r[\\\"\\^]*u[\\\"\\^]*i|y[\\\"\\^]*n[\\\"\\^]*c)|d[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*)|t[\\\"\\^]*(?:a[\\\"\\^]*(?:s[\\\"\\^]*k[\\\"\\^]*(?:k[\\\"\\^]*i[\\\"\\^]*l[\\\"\\^]*l|l[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*t|s[\\\"\\^]*c[\\\"\\^]*h[\\\"\\^]*d|m[\\\"\\^]*g[\\\"\\^]*r)|k[\\\"\\^]*e[\\\"\\^]*o[\\\"\\^]*w[\\\"\\^]*n)|(?:i[\\\"\\^]*m[\\\"\\^]*e[\\\"\\^]*o[\\\"\\^]*u|p[\\\"\\^]*m[\\\"\\^]*i[\\\"\\^]*n[\\\"\\^]*i|e[\\\"\\^]*l[\\\"\\^]*n[\\\"\\^]*e|l[\\\"\\^]*i[\\\"\\^]*s)[\\\"\\^]*t|s[\\\"\\^]*(?:d[\\\"\\^]*i[\\\"\\^]*s[\\\"\\^]*c[\\\"\\^]*o|s[\\\"\\^]*h[\\\"\\^]*u[\\\"\\^]*t[\\\"\\^]*d)[\\\"\\^]*n|y[\\\"\\^]*p[\\\"\\^]*e[\\\"\\^]*(?:p[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*f|(?:[\\s,;]|\\.|/|<|>).*)|r[\\\"\\^]*(?:a[\\\"\\^]*c[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*t|e[\\\"\\^]*e))|w[\\\"\\^]*(?:i[\\\"\\^]*n[\\\"\\^]*(?:d[\\\"\\^]*i[\\\"\\^]*f[\\\"\\^]*f|m[\\\"\\^]*s[\\\"\\^]*d[\\\"\\^]*p|v[\\\"\\^]*a[\\\"\\^]*r|r[\\\"\\^]*[ms])|u[\\\"\\^]*(?:a[\\\"\\^]*(?:u[\\\"\\^]*c[\\\"\\^]*l[\\\"\\^]*t|p[\\\"\\^]*p)|s[\\\"\\^]*a)|s[\\\"\\^]*c[\\\"\\^]*(?:r[\\\"\\^]*i[\\\"\\^]*p[\\\"\\^]*t|u[\\\"\\^]*i)|e[\\\"\\^]*v[\\\"\\^]*t[\\\"\\^]*u[\\\"\\^]*t[\\\"\\^]*i[\\\"\\^]*l|m[\\\"\\^]*i[\\\"\\^]*(?:m[\\\"\\^]*g[\\\"\\^]*m[\\\"\\^]*t|c)|a[\\\"\\^]*i[\\\"\\^]*t[\\\"\\^]*f[\\\"\\^]*o[\\\"\\^]*r|h[\\\"\\^]*o[\\\"\\^]*a[\\\"\\^]*m[\\\"\\^]*i|g[\\\"\\^]*e[\\\"\\^]*t)|u[\\\"\\^]*(?:s[\\\"\\^]*(?:e[\\\"\\^]*r[\\\"\\^]*a[\\\"\\^]*c[\\\"\\^]*c[\\\"\\^]*o[\\\"\\^]*u[\\\"\\^]*n[\\\"\\^]*t[\\\"\\^]*c[\\\"\\^]*o[\\\"\\^]*n[\\\"\\^]*t[\\\"\\^]*r[\\\"\\^]*o[\\\"\\^]*l[\\\"\\^]*s[\\\"\\^]*e[\\\"\\^]*t[\\\"\\^]*t[\\\"\\^]*i[\\\"\\^]*n[\\\"\\^]*g[\\\"\\^]*s|r[\\\"\\^]*s[\\\"\\^]*t[\\\"\\^]*a[\\\"\\^]*t)|n[\\\"\\^]*(?:r[\\\"\\^]*a[\\\"\\^]*r|z[\\\"\\^]*i[\\\"\\^]*p))|q[\\\"\\^]*(?:u[\\\"\\^]*e[\\\"\\^]*r[\\\"\\^]*y[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|p[\\\"\\^]*r[\\\"\\^]*o[\\\"\\^]*c[\\\"\\^]*e[\\\"\\^]*s[\\\"\\^]*s|w[\\\"\\^]*i[\\\"\\^]*n[\\\"\\^]*s[\\\"\\^]*t[\\\"\\^]*a|g[\\\"\\^]*r[\\\"\\^]*e[\\\"\\^]*p)|o[\\\"\\^]*(?:d[\\\"\\^]*b[\\\"\\^]*c[\\\"\\^]*(?:a[\\\"\\^]*d[\\\"\\^]*3[\\\"\\^]*2|c[\\\"\\^]*o[\\\"\\^]*n[\\\"\\^]*f)|p[\\\"\\^]*e[\\\"\\^]*n[\\\"\\^]*f[\\\"\\^]*i[\\\"\\^]*l[\\\"\\^]*e[\\\"\\^]*s)|v[\\\"\\^]*(?:o[\\\"\\^]*l[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*|e[\\\"\\^]*r[\\\"\\^]*i[\\\"\\^]*f[\\\"\\^]*y)|x[\\\"\\^]*c[\\\"\\^]*(?:a[\\\"\\^]*c[\\\"\\^]*l[\\\"\\^]*s|o[\\\"\\^]*p[\\\"\\^]*y)|z[\\\"\\^]*i[\\\"\\^]*p[\\\"\\^]*(?:[\\s,;]|\\.|/|<|>).*)(?:\\.[\\\"\\^]*\\w+)?\\b", + +/* ********************************** */ + +/** + * [ Unix shell expressions ] + * + * Detects the following patterns which are common in Unix shell scripts + * and oneliners: + * + * $(foo) Command substitution + * ${foo} Parameter expansion + * <(foo) Process substitution + * >(foo) Process substitution + * $((foo)) Arithmetic expansion + */ + + "(?:\\$(?:\\((?:\\(.*\\)|.*)\\)|\\{.*\\})|[<>]\\(.*\\))", + +/* ********************************** */ + +/** + * [ Windows FOR, IF commands ] + * + * This regex detects Windows command shell FOR and IF commands. + * + * Examples: + * + * FOR %a IN (set) DO + * FOR /D %a IN (dirs) DO + * FOR /F "options" %a IN (text|"text") DO + * FOR /L %a IN (start,step,end) DO + * FOR /R C:\dir %A IN (set) DO + * + * IF [/I] [NOT] EXIST filename | DEFINED define | ERRORLEVEL n | CMDEXTVERSION n + * IF [/I] [NOT] item1 [==|EQU|NEQ|LSS|LEQ|GTR|GEQ] item2 + * IF [/I] [NOT] (item1) [==|EQU|NEQ|LSS|LEQ|GTR|GEQ] (item2) + * + * http://ss64.com/nt/if.html + * http://ss64.com/nt/for.html + */ + + "\\b(?:if(?:/i)?(?: not)?(?: exist\\b| defined\\b| errorlevel\\b| cmdextversion\\b|(?: |\\().*(?:\\bgeq\\b|\\bequ\\b|\\bneq\\b|\\bleq\\b|\\bgtr\\b|\\blss\\b|==))|for(?:/[dflr].*)? %+[^ ]+ in\\(.*\\)\\s?do)", + +/* ********************************** */ + +/** + * [ Unix direct remote command execution ] + * + * Detects Unix commands at the start of a parameter (direct RCE). + * Example: foo=wget%20www.example.com + * + * This case is different from command injection, where a + * command string is appended (injected) to a regular parameter, and then + * passed to a shell unescaped. + * + * This regex is also triggered by an Oracle WebLogic Remote Command Execution exploit: + * [ Oracle WebLogic vulnerability CVE-2017-10271 - Exploit tested: https://www.exploit-db.com/exploits/43458 ] + */ + + "(?:^|=)\\s*(?:{|\\s*\\(\\s*|\\w+=(?:[^\\s]*|\\$.*|\\$.*|<.*|>.*|\\'.*\\'|\\\".*\\\")\\s+|!\\s*|\\$)*\\s*(?:'|\\\")*(?:[\\?\\*\\[\\]\\(\\)\\-\\|+\\w'\\\"\\./\\\\\\\\]+/)?[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*(?:s(?:[\\\\\\\\'\\\"]*(?:b[\\\\\\\\'\\\"]*_[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*e|c[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*u|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*d|p[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*i|u[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*b|-[\\\\\\\\'\\\"]*F|o[\\\\\\\\'\\\"]*f))?|z[\\\\\\\\'\\\"]*(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|c[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*t|m[\\\\\\\\'\\\"]*p)|m[\\\\\\\\'\\\"]*(?:o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|a)|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s)|e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*(?:(?:f[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l|p[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*p)[\\\\\\\\'\\\"]*e|e[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o)|a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*g(?:[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n)?|c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*m)|w[\\\\\\\\'\\\"]*p(?:[\\\\\\\\'\\\"]*-[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*w[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*d)?|f[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*p(?:[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t)?|y[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*x)|s[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*v|s[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*d)|n[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l|d)|h(?:[\\\\\\\\'\\\"]*\\.[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*b)?|o[\\\\\\\\'\\\"]*(?:u[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e|c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*s|y[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*l|c[\\\\\\\\'\\\"]*(?:h[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*d|p)|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|f[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*p|u[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*o|s[\\\\\\\\'\\\"]*h|v[\\\\\\\\'\\\"]*n)|p[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*r(?:[\\\\\\\\'\\\"]*(?:d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p))?|y[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n(?:[\\\\\\\\'\\\"]*(?:3(?:[\\\\\\\\'\\\"]*m)?|2))?|k[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*x[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*c|i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*l)|r[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*v|(?:g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|f[\\\\\\\\'\\\"]*t)[\\\\\\\\'\\\"]*p|e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*l(?:[\\\\\\\\'\\\"]*5)?|h[\\\\\\\\'\\\"]*p(?:[\\\\\\\\'\\\"]*[57])?|i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g|o[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*d)|n[\\\\\\\\'\\\"]*(?:c(?:[\\\\\\\\'\\\"]*(?:\\.[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*l|o[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*b[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*d)|a[\\\\\\\\'\\\"]*t))?|e[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*(?:k[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*-[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*p|(?:s[\\\\\\\\'\\\"]*t|c)[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|o[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*p|p[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g|s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|t[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*(?:p[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e|i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*g)|s[\\\\\\\\'\\\"]*h)|r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e(?:[\\\\\\\\'\\\"]*6)?|i[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e(?:[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*t)?|a[\\\\\\\\'\\\"]*(?:i[\\\\\\\\'\\\"]*l(?:[\\\\\\\\'\\\"]*f)?|r)|e[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t)|r[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*(?:p[\\\\\\\\'\\\"]*(?:l[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*e|e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|a[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*h|n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e)|u[\\\\\\\\'\\\"]*b[\\\\\\\\'\\\"]*y(?:[\\\\\\\\'\\\"]*(?:1(?:[\\\\\\\\'\\\"]*[89])?|2[\\\\\\\\'\\\"]*[012]))?|m[\\\\\\\\'\\\"]*(?:u[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*e|d[\\\\\\\\'\\\"]*i)[\\\\\\\\'\\\"]*r|n[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*o|s[\\\\\\\\'\\\"]*y[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*c|c[\\\\\\\\'\\\"]*p)|b[\\\\\\\\'\\\"]*(?:z[\\\\\\\\'\\\"]*(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t)|s[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t|i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*r)|u[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n|a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*h)|m[\\\\\\\\'\\\"]*(?:y[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*q[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*(?:d[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*p(?:[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*w)?|h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*y|a[\\\\\\\\'\\\"]*d[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n|s[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*w)|l[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e|a[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*q)|u[\\\\\\\\'\\\"]*(?:n[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|l[\\\\\\\\'\\\"]*z[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*a|a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*e|r[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*r|s[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t|z[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*p|x[\\\\\\\\'\\\"]*z)|s[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*(?:(?:a[\\\\\\\\'\\\"]*d|m[\\\\\\\\'\\\"]*o)[\\\\\\\\'\\\"]*d|d[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*l))|x[\\\\\\\\'\\\"]*(?:z(?:[\\\\\\\\'\\\"]*(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|d[\\\\\\\\'\\\"]*(?:i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|e[\\\\\\\\'\\\"]*c)|c[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*t|m[\\\\\\\\'\\\"]*p)|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e))?|a[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*g[\\\\\\\\'\\\"]*s)|z[\\\\\\\\'\\\"]*(?:(?:(?:[ef][\\\\\\\\'\\\"]*)?g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|i)[\\\\\\\\'\\\"]*p|c[\\\\\\\\'\\\"]*(?:a[\\\\\\\\'\\\"]*t|m[\\\\\\\\'\\\"]*p)|d[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*s|m[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e|r[\\\\\\\\'\\\"]*u[\\\\\\\\'\\\"]*n|s[\\\\\\\\'\\\"]*h)|f[\\\\\\\\'\\\"]*(?:t[\\\\\\\\'\\\"]*p[\\\\\\\\'\\\"]*(?:s[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*s|w[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o)|i[\\\\\\\\'\\\"]*l[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*t|e[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*c[\\\\\\\\'\\\"]*h|g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p)|c[\\\\\\\\'\\\"]*(?:o[\\\\\\\\'\\\"]*(?:m[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*d|p[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*c)|u[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*l|s[\\\\\\\\'\\\"]*h|c)|e[\\\\\\\\'\\\"]*(?:g[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|c[\\\\\\\\'\\\"]*h[\\\\\\\\'\\\"]*o|v[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*l|x[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*c|n[\\\\\\\\'\\\"]*v)|d[\\\\\\\\'\\\"]*(?:m[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*g|a[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*h|i[\\\\\\\\'\\\"]*f[\\\\\\\\'\\\"]*f|o[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*s)|g[\\\\\\\\'\\\"]*(?:z[\\\\\\\\'\\\"]*(?:c[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*t|i[\\\\\\\\'\\\"]*p)|r[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*p|c[\\\\\\\\'\\\"]*c)|j[\\\\\\\\'\\\"]*(?:o[\\\\\\\\'\\\"]*b[\\\\\\\\'\\\"]*s[\\\\\\\\'\\\"]*\\s+[\\\\\\\\'\\\"]*-[\\\\\\\\'\\\"]*x|a[\\\\\\\\'\\\"]*v[\\\\\\\\'\\\"]*a)|w[\\\\\\\\'\\\"]*(?:h[\\\\\\\\'\\\"]*o[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*m[\\\\\\\\'\\\"]*i|g[\\\\\\\\'\\\"]*e[\\\\\\\\'\\\"]*t|3[\\\\\\\\'\\\"]*m)|i[\\\\\\\\'\\\"]*r[\\\\\\\\'\\\"]*b(?:[\\\\\\\\'\\\"]*(?:1(?:[\\\\\\\\'\\\"]*[89])?|2[\\\\\\\\'\\\"]*[012]))?|o[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*n[\\\\\\\\'\\\"]*t[\\\\\\\\'\\\"]*r|h[\\\\\\\\'\\\"]*(?:e[\\\\\\\\'\\\"]*a[\\\\\\\\'\\\"]*d|u[\\\\\\\\'\\\"]*p)|v[\\\\\\\\'\\\"]*i[\\\\\\\\'\\\"]*(?:g[\\\\\\\\'\\\"]*r|p[\\\\\\\\'\\\"]*w)|G[\\\\\\\\'\\\"]*E[\\\\\\\\'\\\"]*T)[\\\\\\\\'\\\"]*(?:\\s|;|\\||&|<|>)" +}; + +/* ********************************** */ + +/** + * [ Unix shell snippets ] + * + * Detect some common sequences found in shell commands and scripts. + * + * This regex is also triggered by an Apache Struts Remote Code Execution exploit: + * [ Apache Struts vulnerability CVE-2017-9805 - Exploit tested: https://www.exploit-db.com/exploits/42627 ] + * + * This regex is also triggered by an Oracle WebLogic Remote Command Execution exploit: + * [ Oracle WebLogic vulnerability CVE-2017-10271 - Exploit tested: https://www.exploit-db.com/exploits/43458 ] + */ + +static const char *ush_commands[] = { + "${CDPATH}", + "${DIRSTACK}", + "${HOME}", + "${HOSTNAME}", + "${IFS}", + "${OLDPWD}", + "${OSTYPE}", + "${PATH}", + "${PWD}", + "$CDPATH", + "$DIRSTACK", + "$HOME", + "$HOSTNAME", + "$IFS", + "$OLDPWD", + "$OSTYPE", + "$PATH", + "$PWD", + "bin/bash", + "bin/cat", + "bin/csh", + "bin/dash", + "bin/du", + "bin/echo", + "bin/grep", + "bin/less", + "bin/ls", + "bin/mknod", + "bin/more", + "bin/nc", + "bin/ps", + "bin/rbash", + "bin/sh", + "bin/sleep", + "bin/su", + "bin/tcsh", + "bin/uname", + "dev/fd/", + "dev/null", + "dev/stderr", + "dev/stdin", + "dev/stdout", + "dev/tcp/", + "dev/udp/", + "dev/zero", + "etc/group", + "etc/master.passwd", + "etc/passwd", + "etc/pwd.db", + "etc/shadow", + "etc/shells", + "etc/spwd.db", + "proc/self/", + "usr/bin/awk", + "usr/bin/base64", + "usr/bin/cat", + "usr/bin/cc", + "usr/bin/clang", + "usr/bin/clang++", + "usr/bin/curl", + "usr/bin/diff", + "usr/bin/env", + "usr/bin/fetch", + "usr/bin/file", + "usr/bin/find", + "usr/bin/ftp", + "usr/bin/gawk", + "usr/bin/gcc", + "usr/bin/head", + "usr/bin/hexdump", + "usr/bin/id", + "usr/bin/less", + "usr/bin/ln", + "usr/bin/mkfifo", + "usr/bin/more", + "usr/bin/nc", + "usr/bin/ncat", + "usr/bin/nice", + "usr/bin/nmap", + "usr/bin/perl", + "usr/bin/php", + "usr/bin/php5", + "usr/bin/php7", + "usr/bin/php-cgi", + "usr/bin/printf", + "usr/bin/psed", + "usr/bin/python", + "usr/bin/python2", + "usr/bin/python3", + "usr/bin/ruby", + "usr/bin/sed", + "usr/bin/socat", + "usr/bin/tail", + "usr/bin/tee", + "usr/bin/telnet", + "usr/bin/top", + "usr/bin/uname", + "usr/bin/wget", + "usr/bin/who", + "usr/bin/whoami", + "usr/bin/xargs", + "usr/bin/xxd", + "usr/bin/yes", + "usr/local/bin/bash", + "usr/local/bin/curl", + "usr/local/bin/ncat", + "usr/local/bin/nmap", + "usr/local/bin/perl", + "usr/local/bin/php", + "usr/local/bin/python", + "usr/local/bin/python2", + "usr/local/bin/python3", + "usr/local/bin/rbash", + "usr/local/bin/ruby", + "usr/local/bin/wget" +}; + +/* ********************************** */ + +/** + * [ Windows PowerShell, cmdlets and options ] + * + * Detect some common PowerShell commands, cmdlets and options. + * These commands should be relatively uncommon in normal text, but + * potentially useful for code injection. + */ + +static const char *pwsh_commands[] = { + "powershell.exe", + "Add-BitsFile", + "Add-Computer", + "Add-Content", + "Add-History", + "Add-Member", + "Add-PSSnapin", + "Add-Type", + "Checkpoint-Computer", + "Clear-Content", + "Clear-EventLog", + "Clear-History", + "Clear-Item", + "Clear-ItemProperty", + "Clear-Variable", + "Compare-Object", + "Complete-BitsTransfer", + "Complete-Transaction", + "Connect-WSMan", + "ConvertFrom-CSV", + "ConvertFrom-SecureString", + "ConvertFrom-StringData", + "Convert-Path", + "ConvertTo-CSV", + "ConvertTo-Html", + "ConvertTo-SecureString", + "ConvertTo-XML", + "Copy-Item", + "Copy-ItemProperty", + "Debug-Process", + "Disable-ComputerRestore", + "Disable-PSBreakpoint", + "Disable-PSSessionConfiguration", + "Disable-WSManCredSSP", + "Disconnect-WSMan", + "Enable-ComputerRestore", + "Enable-PSBreakpoint", + "Enable-PSRemoting", + "Enable-PSSessionConfiguration", + "Enable-WSManCredSSP", + "Enter-PSSession", + "Exit-PSSession", + "Export-Alias", + "Export-Clixml", + "Export-Console", + "Export-Counter", + "Export-CSV", + "Export-FormatData", + "Export-ModuleMember", + "Export-PSSession", + "ForEach-Object", + "Format-Custom", + "Format-List", + "Format-Table", + "Format-Wide", + "Get-Acl", + "Get-Alias", + "Get-AppLockerFileInformation", + "Get-AppLockerPolicy", + "Get-AuthenticodeSignature", + "Get-BitsTransfer", + "Get-ChildItem", + "Get-Command", + "Get-ComputerRestorePoint", + "Get-Content", + "Get-Counter", + "Get-Credential", + "Get-Culture", + "Get-Event", + "Get-EventLog", + "Get-EventSubscriber", + "Get-ExecutionPolicy", + "Get-FormatData", + "Get-History", + "Get-Host", + "Get-HotFix", + "Get-Item", + "Get-ItemProperty", + "Get-Job", + "Get-Location", + "Get-Member", + "Get-Module", + "Get-PfxCertificate", + "Get-Process", + "Get-PSBreakpoint", + "Get-PSCallStack", + "Get-PSDrive", + "Get-PSProvider", + "Get-PSSession", + "Get-PSSessionConfiguration", + "Get-PSSnapin", + "Get-Random", + "Get-Service", + "Get-TraceSource", + "Get-Transaction", + "Get-TroubleshootingPack", + "Get-UICulture", + "Get-Unique", + "Get-Variable", + "Get-WinEvent", + "Get-WmiObject", + "Get-WSManCredSSP", + "Get-WSManInstance", + "Group-Object", + "Import-Alias", + "Import-Clixml", + "Import-Counter", + "Import-CSV", + "Import-LocalizedData", + "Import-Module", + "Import-PSSession", + "Invoke-Command", + "Invoke-Expression", + "Invoke-History", + "Invoke-Item", + "Invoke-TroubleshootingPack", + "Invoke-WmiMethod", + "Invoke-WSManAction", + "Join-Path", + "Limit-EventLog", + "Measure-Command", + "Measure-Object", + "Move-Item", + "Move-ItemProperty", + "New-Alias", + "New-AppLockerPolicy", + "New-Event", + "New-EventLog", + "New-Item", + "New-ItemProperty", + "New-Module", + "New-ModuleManifest", + "New-Object", + "New-PSDrive", + "New-PSSession", + "New-PSSessionOption", + "New-Service", + "New-TimeSpan", + "New-Variable", + "New-WebServiceProxy", + "New-WSManInstance", + "New-WSManSessionOption", + "Out-Default", + "Out-File", + "Out-GridView", + "Out-Host", + "Out-Null", + "Out-Printer", + "Out-String", + "Pop-Location", + "Push-Location", + "Read-Host", + "Receive-Job", + "Register-EngineEvent", + "Register-ObjectEvent", + "Register-PSSessionConfiguration", + "Register-WmiEvent", + "Remove-BitsTransfer", + "Remove-Computer", + "Remove-Event", + "Remove-EventLog", + "Remove-Item", + "Remove-ItemProperty", + "Remove-Job", + "Remove-Module", + "Remove-PSBreakpoint", + "Remove-PSDrive", + "Remove-PSSession", + "Remove-PSSnapin", + "Remove-Variable", + "Remove-WmiObject", + "Remove-WSManInstance", + "Rename-Item", + "Rename-ItemProperty", + "Reset-ComputerMachinePassword", + "Resolve-Path", + "Restart-Computer", + "Restart-Service", + "Restore-Computer", + "Resume-BitsTransfer", + "Resume-Service", + "Select-Object", + "Select-String", + "Select-XML", + "Send-MailMessage", + "Set-Acl", + "Set-Alias", + "Set-AppLockerPolicy", + "Set-AuthenticodeSignature", + "Set-BitsTransfer", + "Set-Content", + "Set-Date", + "Set-ExecutionPolicy", + "Set-Item", + "Set-ItemProperty", + "Set-Location", + "Set-PSBreakpoint", + "Set-PSDebug", + "Set-PSSessionConfiguration", + "Set-Service", + "Set-StrictMode", + "Set-TraceSource", + "Set-Variable", + "Set-WmiInstance", + "Set-WSManInstance", + "Set-WSManQuickConfig", + "Show-EventLog", + "Sort-Object", + "Split-Path", + "Start-BitsTransfer", + "Start-Job", + "Start-Process", + "Start-Service", + "Start-Sleep", + "Start-Transaction", + "Start-Transcript", + "Stop-Computer", + "Stop-Job", + "Stop-Process", + "Stop-Service", + "Stop-Transcript", + "Suspend-BitsTransfer", + "Suspend-Service", + "Tee-Object", + "Test-AppLockerPolicy", + "Test-ComputerSecureChannel", + "Test-Connection", + "Test-ModuleManifest", + "Test-Path", + "Test-WSMan", + "Trace-Command", + "Undo-Transaction", + "Unregister-Event", + "Unregister-PSSessionConfiguration", + "Update-FormatData", + "Update-List", + "Update-TypeData", + "Use-Transaction", + "Wait-Event", + "Wait-Job", + "Wait-Process", + "Where-Object", + "Write-Debug", + "Write-Error", + "Write-EventLog", + "Write-Host", + "Write-Output", + "Write-Progress", + "Write-Verbose", + "Write-Warning", + "-EncodedCommand", + "-ExecutionPolicy", + "-PSConsoleFile" +}; + +#endif //HAVE_PCRE \ No newline at end of file diff --git a/src/lib/third_party/src/ahocorasick.c b/src/lib/third_party/src/ahocorasick.c index ffa8bac..e473332 100644 --- a/src/lib/third_party/src/ahocorasick.c +++ b/src/lib/third_party/src/ahocorasick.c @@ -3,7 +3,8 @@ * This file is part of multifast. * Copyright 2010-2012 Kamiar Kanani - + Copyright 2012-2019 ntop.org (Incremental improvements) + multifast is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or @@ -56,7 +57,6 @@ AC_AUTOMATA_t * ac_automata_init (MATCH_CALLBACK_f mc) thiz->all_nodes = (AC_NODE_t **) ndpi_malloc (thiz->all_nodes_max*sizeof(AC_NODE_t *)); thiz->match_callback = mc; ac_automata_register_nodeptr (thiz, thiz->root); - ac_automata_reset (thiz); thiz->total_patterns = 0; thiz->automata_open = 1; return thiz; @@ -88,25 +88,33 @@ AC_ERROR_t ac_automata_add (AC_AUTOMATA_t * thiz, AC_PATTERN_t * patt) return ACERR_LONG_PATTERN; for (i=0; ilength; i++) + { + alpha = patt->astring[i]; + if ((next = node_find_next(n, alpha))) { - alpha = patt->astring[i]; - if ((next = node_find_next(n, alpha))) - { - n = next; - continue; - } - else - { - next = node_create_next(n, alpha); - next->depth = n->depth + 1; - n = next; - ac_automata_register_nodeptr(thiz, n); - } + n = next; + continue; + } + else + { + next = node_create_next(n, alpha); + next->depth = n->depth + 1; + n = next; + ac_automata_register_nodeptr(thiz, n); } + } - if(n->final) + if(n->final) { +#if 0 + /* Original code */ return ACERR_DUPLICATE_PATTERN; - +#else + /* ntop */ + memcpy(&n->matched_patterns->rep, &patt->rep, sizeof(AC_REP_t)); + return ACERR_DUPLICATE_PATTERN; /* Caller might need to free patt->astring */ +#endif + } + n->final = 1; node_register_matchstr(n, patt, 0); thiz->total_patterns++; @@ -133,11 +141,11 @@ void ac_automata_finalize (AC_AUTOMATA_t * thiz) ac_automata_traverse_setfailure (thiz, thiz->root, alphas); for (i=0; i < thiz->all_nodes_num; i++) - { - node = thiz->all_nodes[i]; - ac_automata_union_matchstrs (node); - node_sort_edges (node); - } + { + node = thiz->all_nodes[i]; + ac_automata_union_matchstrs (node); + node_sort_edges (node); + } thiz->automata_open = 0; /* do not accept patterns any more */ ndpi_free(alphas); } @@ -159,69 +167,52 @@ void ac_automata_finalize (AC_AUTOMATA_t * thiz) * 0: success; continue searching; call-back sent me a 0 value * 1: success; stop searching; call-back sent me a non-0 value ******************************************************************************/ -int ac_automata_search (AC_AUTOMATA_t * thiz, AC_TEXT_t * txt, AC_REP_t * param) -{ +int ac_automata_search (AC_AUTOMATA_t * thiz, AC_TEXT_t * txt, AC_REP_t * param) { unsigned long position; AC_NODE_t *curr; AC_NODE_t *next; - + AC_SEARCH_t s; + if(thiz->automata_open) /* you must call ac_automata_locate_failure() first */ return -1; + /* Reset search */ + s.current_node = thiz->root; + s.base_position = 0; + position = 0; - curr = thiz->current_node; + curr = s.current_node; /* This is the main search loop. * it must be keep as lightweight as possible. */ - while (position < txt->length) - { - if(!(next = node_findbs_next(curr, txt->astring[position]))) - { - if(curr->failure_node /* we are not in the root node */) - curr = curr->failure_node; - else - position++; - } + while (position < txt->length) { + if(!(next = node_findbs_next(curr, txt->astring[position]))) { + if(curr->failure_node /* we are not in the root node */) + curr = curr->failure_node; else - { - curr = next; - position++; - } + position++; + } else { + curr = next; + position++; + } - if(curr->final && next) { - /* We check 'next' to find out if we came here after a alphabet - * transition or due to a fail. in second case we should not report - * matching because it was reported in previous node */ - thiz->match.position = position + thiz->base_position; - thiz->match.match_num = curr->matched_patterns_num; - thiz->match.patterns = curr->matched_patterns; - /* we found a match! do call-back */ - if (thiz->match_callback(&thiz->match, txt, param)) - return 1; - } + if(curr->final && next) { + /* We check 'next' to find out if we came here after a alphabet + * transition or due to a fail. in second case we should not report + * matching because it was reported in previous node */ + thiz->match.position = position + s.base_position; + thiz->match.match_num = curr->matched_patterns_num; + thiz->match.patterns = curr->matched_patterns; + /* we found a match! do call-back */ + if (thiz->match_callback(&thiz->match, txt, param)) + return 1; } + } - /* save status variables */ - thiz->current_node = curr; - thiz->base_position += position; return 0; } -/****************************************************************************** - * FUNCTION: ac_automata_reset - * reset the automata and make it ready for doing new search on a new text. - * when you finished with the input text, you must reset automata state for - * new input, otherwise it will not work. - * PARAMS: - * AC_AUTOMATA_t * thiz: the pointer to the automata - ******************************************************************************/ -void ac_automata_reset (AC_AUTOMATA_t * thiz) -{ - thiz->current_node = thiz->root; - thiz->base_position = 0; -} - /****************************************************************************** * FUNCTION: ac_automata_release * Release all allocated memories to the automata @@ -235,10 +226,10 @@ void ac_automata_release (AC_AUTOMATA_t * thiz, u_int8_t free_pattern) AC_NODE_t * n; for (i=0; i < thiz->all_nodes_num; i++) - { - n = thiz->all_nodes[i]; - node_release(n, free_pattern); - } + { + n = thiz->all_nodes[i]; + node_release(n, free_pattern); + } ndpi_free(thiz->all_nodes); ndpi_free(thiz); } @@ -261,40 +252,40 @@ void ac_automata_display (AC_AUTOMATA_t * thiz, char repcast) printf("---------------------------------\n"); for (i=0; iall_nodes_num; i++) + { + n = thiz->all_nodes[i]; + printf("NODE(%3d)/----fail----> NODE(%3d)\n", + n->id, (n->failure_node)?n->failure_node->id:1); + for (j=0; joutgoing_degree; j++) { - n = thiz->all_nodes[i]; - printf("NODE(%3d)/----fail----> NODE(%3d)\n", - n->id, (n->failure_node)?n->failure_node->id:1); - for (j=0; joutgoing_degree; j++) + e = &n->outgoing[j]; + printf(" |----("); + if(isgraph(e->alpha)) + printf("%c)---", e->alpha); + else + printf("0x%x)", e->alpha); + printf("--> NODE(%3d)\n", e->next->id); + } + if (n->matched_patterns_num) { + printf("Accepted patterns: {"); + for (j=0; jmatched_patterns_num; j++) + { + sid = n->matched_patterns[j]; + if(j) printf(", "); + switch (repcast) { - e = &n->outgoing[j]; - printf(" |----("); - if(isgraph(e->alpha)) - printf("%c)---", e->alpha); - else - printf("0x%x)", e->alpha); - printf("--> NODE(%3d)\n", e->next->id); + case 'n': + printf("%u/%u/%u", + sid.rep.number, + sid.rep.category, + sid.rep.breed); + break; } - if (n->matched_patterns_num) { - printf("Accepted patterns: {"); - for (j=0; jmatched_patterns_num; j++) - { - sid = n->matched_patterns[j]; - if(j) printf(", "); - switch (repcast) - { - case 'n': - printf("%u/%u/%u", - sid.rep.number, - sid.rep.category, - sid.rep.breed); - break; - } - } - printf("}\n"); } - printf("---------------------------------\n"); + printf("}\n"); } + printf("---------------------------------\n"); + } } /****************************************************************************** @@ -304,13 +295,13 @@ void ac_automata_display (AC_AUTOMATA_t * thiz, char repcast) static void ac_automata_register_nodeptr (AC_AUTOMATA_t * thiz, AC_NODE_t * node) { if(thiz->all_nodes_num >= thiz->all_nodes_max) - { - thiz->all_nodes = ndpi_realloc(thiz->all_nodes, - thiz->all_nodes_max*sizeof(AC_NODE_t *), - (REALLOC_CHUNK_ALLNODES+thiz->all_nodes_max)*sizeof(AC_NODE_t *) - ); - thiz->all_nodes_max += REALLOC_CHUNK_ALLNODES; - } + { + thiz->all_nodes = ndpi_realloc(thiz->all_nodes, + thiz->all_nodes_max*sizeof(AC_NODE_t *), + (REALLOC_CHUNK_ALLNODES+thiz->all_nodes_max)*sizeof(AC_NODE_t *) + ); + thiz->all_nodes_max += REALLOC_CHUNK_ALLNODES; + } thiz->all_nodes[thiz->all_nodes_num++] = node; } @@ -325,13 +316,13 @@ static void ac_automata_union_matchstrs (AC_NODE_t * node) AC_NODE_t * m = node; while ((m = m->failure_node)) - { - for (i=0; i < m->matched_patterns_num; i++) - node_register_matchstr(node, &(m->matched_patterns[i]), 1 /* this is an existing node */); + { + for (i=0; i < m->matched_patterns_num; i++) + node_register_matchstr(node, &(m->matched_patterns[i]), 1 /* this is an existing node */); - if (m->final) - node->final = 1; - } + if (m->final) + node->final = 1; + } // TODO : sort matched_patterns? is that necessary? I don't think so. } @@ -346,16 +337,16 @@ static void ac_automata_set_failure AC_NODE_t * m; for (i=1; i < node->depth; i++) + { + m = thiz->root; + for (j=i; j < node->depth && m; j++) + m = node_find_next (m, alphas[j]); + if (m) { - m = thiz->root; - for (j=i; j < node->depth && m; j++) - m = node_find_next (m, alphas[j]); - if (m) - { - node->failure_node = m; - break; - } + node->failure_node = m; + break; } + } if (!node->failure_node) node->failure_node = thiz->root; } @@ -373,15 +364,14 @@ static void ac_automata_traverse_setfailure unsigned int i; AC_NODE_t * next; - for (i=0; i < node->outgoing_degree; i++) - { - alphas[node->depth] = node->outgoing[i].alpha; - next = node->outgoing[i].next; + for (i=0; i < node->outgoing_degree; i++) { + alphas[node->depth] = node->outgoing[i].alpha; + next = node->outgoing[i].next; - /* At every node look for its failure node */ - ac_automata_set_failure (thiz, next, alphas); + /* At every node look for its failure node */ + ac_automata_set_failure (thiz, next, alphas); - /* Recursively call itself to traverse all nodes */ - ac_automata_traverse_setfailure (thiz, next, alphas); - } + /* Recursively call itself to traverse all nodes */ + ac_automata_traverse_setfailure (thiz, next, alphas); + } } diff --git a/src/lib/third_party/src/hll/MurmurHash3.c b/src/lib/third_party/src/hll/MurmurHash3.c new file mode 100644 index 0000000..47fb9da --- /dev/null +++ b/src/lib/third_party/src/hll/MurmurHash3.c @@ -0,0 +1,61 @@ +/*----------------------------------------------------------------------------- + MurmurHash3 was written by Austin Appleby, and is placed in the public + domain. The author hereby disclaims copyright to this source code. +*/ + +#include "MurmurHash3.h" + +#define ROTL32(x, r) ((x) << (r)) | ((x) >> (32 - (r))) + +u_int32_t MurmurHash3_x86_32(const void *key, u_int32_t len, u_int32_t seed) { + const u_int8_t *data = (const u_int8_t *)key; + const int32_t nblocks = (int32_t)len / 4; + + u_int32_t h1 = seed; + int i; + + const u_int32_t c1 = 0xcc9e2d51; + const u_int32_t c2 = 0x1b873593; + const u_int32_t *blocks = (const u_int32_t *)(data + nblocks * 4); + + for(i = -nblocks; i; i++) + { + u_int32_t k1 = blocks[i]; + + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = ROTL32(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + } + + const u_int8_t * tail = (const u_int8_t *)(data + nblocks * 4); + + u_int32_t k1 = 0; + + switch(len & 3) + { + case 3: + k1 ^= (u_int32_t)tail[2] << 16; + case 2: + k1 ^= (u_int32_t)tail[1] << 8; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = ROTL32(k1, 15); + k1 *= c2; + h1 ^= k1; + }; + + h1 ^= len; + + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1; +} diff --git a/src/lib/third_party/src/hll/hll.c b/src/lib/third_party/src/hll/hll.c new file mode 100644 index 0000000..c526c6a --- /dev/null +++ b/src/lib/third_party/src/hll/hll.c @@ -0,0 +1,161 @@ +/* + Code taken from https://github.com/avz/hll + + Copyright (c) 2015 Artem Zaytsev + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include + +#include "../include/MurmurHash3.h" +#include "../include/hll.h" + +u_int32_t _hll_hash(const struct ndpi_hll *hll) { + return MurmurHash3_x86_32(hll->registers, (u_int32_t)hll->size, 0); +} + +/* Count the number of leading zero's */ +static __inline u_int8_t _hll_rank(u_int32_t hash, u_int8_t bits) { + u_int8_t i; + + for(i = 1; i <= 32 - bits; i++) { + if(hash & 1) + break; + + hash >>= 1; + } + + return i; +} + +/* + IMPORTANT: HyperLogLog Memory and StandardError Notes + + StdError = 1.04/sqrt(2^i) + + [i: 4] 16 bytes [StdError: 26% ] + [i: 5] 32 bytes [StdError: 18.4%] + [i: 6] 64 bytes [StdError: 13% ] + [i: 7] 128 bytes [StdError: 9.2% ] + [i: 8] 256 bytes [StdError: 6.5% ] + [i: 9] 512 bytes [StdError: 4.6% ] + [i: 10] 1024 bytes [StdError: 3.25%] + [i: 11] 2048 bytes [StdError: 2.3% ] + [i: 12] 4096 bytes [StdError: 1.6% ] + [i: 13] 8192 bytes [StdError: 1.15%] + [i: 14] 16384 bytes [StdError: 0.81%] + [i: 15] 32768 bytes [StdError: 0.57%] + [i: 16] 65536 bytes [StdError: 0.41%] + [i: 17] 131072 bytes [StdError: 0.29%] + [i: 18] 262144 bytes [StdError: 0.2% ] + [i: 19] 524288 bytes [StdError: 0.14%] +*/ +int hll_init(struct ndpi_hll *hll, u_int8_t bits) { + if(bits < 4 || bits > 20) { + errno = ERANGE; + return -1; + } + + hll->bits = bits; /* Number of bits of buckets number */ + hll->size = (size_t)1 << bits; /* Number of buckets 2^bits */ + hll->registers = ndpi_calloc(hll->size, 1); /* Create the bucket register counters */ + + /* printf("%lu bytes\n", hll->size); */ + return 0; +} + +void hll_destroy(struct ndpi_hll *hll) { + if(hll->registers) { + ndpi_free(hll->registers); + + hll->registers = NULL; + } +} + +void hll_reset(struct ndpi_hll *hll) { + if(hll->registers) + memset(hll->registers, 0, hll->size); +} + +static __inline void _hll_add_hash(struct ndpi_hll *hll, u_int32_t hash) { + if(hll->registers) { + u_int32_t index = hash >> (32 - hll->bits); /* Use the first 'hll->bits' bits as bucket index */ + u_int8_t rank = _hll_rank(hash, hll->bits); /* Count the number of leading 0 */ + + if(rank > hll->registers[index]) + hll->registers[index] = rank; /* Store the largest number of lesding zeros for the bucket */ + } +} + +void hll_add(struct ndpi_hll *hll, const void *buf, size_t size) { + u_int32_t hash = MurmurHash3_x86_32((const char *)buf, (u_int32_t)size, 0x5f61767a); + + _hll_add_hash(hll, hash); +} + +double hll_count(const struct ndpi_hll *hll) { + if(hll->registers) { + double alpha_mm, sum, estimate; + u_int32_t i; + + switch(hll->bits) { + case 4: + alpha_mm = 0.673; + break; + case 5: + alpha_mm = 0.697; + break; + case 6: + alpha_mm = 0.709; + break; + default: + alpha_mm = 0.7213 / (1.0 + 1.079 / (double)hll->size); + break; + } + + alpha_mm *= ((double)hll->size * (double)hll->size); + + sum = 0; + for(i = 0; i < hll->size; i++) + sum += 1.0 / (1 << hll->registers[i]); + + estimate = alpha_mm / sum; + + if(estimate <= (5.0 / 2.0 * (double)hll->size)) { + int zeros = 0; + + for(i = 0; i < hll->size; i++) + zeros += (hll->registers[i] == 0); + + if(zeros) + estimate = (double)hll->size * log((double)hll->size / zeros); + + } else if(estimate > ((1.0 / 30.0) * 4294967296.0)) { + estimate = -4294967296.0 * log(1.0 - (estimate / 4294967296.0)); + } + + return estimate; + } else + return(0.); +} + diff --git a/src/lib/third_party/src/libinjection_html5.c b/src/lib/third_party/src/libinjection_html5.c new file mode 100644 index 0000000..a380ca0 --- /dev/null +++ b/src/lib/third_party/src/libinjection_html5.c @@ -0,0 +1,850 @@ +#include "libinjection_html5.h" + +#include +#include + +#ifdef DEBUG +#include +#define TRACE() printf("%s:%d\n", __FUNCTION__, __LINE__) +#else +#define TRACE() +#endif + + +#define CHAR_EOF -1 +#define CHAR_NULL 0 +#define CHAR_BANG 33 +#define CHAR_DOUBLE 34 +#define CHAR_PERCENT 37 +#define CHAR_SINGLE 39 +#define CHAR_DASH 45 +#define CHAR_SLASH 47 +#define CHAR_LT 60 +#define CHAR_EQUALS 61 +#define CHAR_GT 62 +#define CHAR_QUESTION 63 +#define CHAR_RIGHTB 93 +#define CHAR_TICK 96 + +/* prototypes */ + +static int h5_skip_white(h5_state_t* hs); +static int h5_is_white(char c); +static int h5_state_eof(h5_state_t* hs); +static int h5_state_data(h5_state_t* hs); +static int h5_state_tag_open(h5_state_t* hs); +static int h5_state_tag_name(h5_state_t* hs); +static int h5_state_tag_name_close(h5_state_t* hs); +static int h5_state_end_tag_open(h5_state_t* hs); +static int h5_state_self_closing_start_tag(h5_state_t* hs); +static int h5_state_attribute_name(h5_state_t* hs); +static int h5_state_after_attribute_name(h5_state_t* hs); +static int h5_state_before_attribute_name(h5_state_t* hs); +static int h5_state_before_attribute_value(h5_state_t* hs); +static int h5_state_attribute_value_double_quote(h5_state_t* hs); +static int h5_state_attribute_value_single_quote(h5_state_t* hs); +static int h5_state_attribute_value_back_quote(h5_state_t* hs); +static int h5_state_attribute_value_no_quote(h5_state_t* hs); +static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs); +static int h5_state_comment(h5_state_t* hs); +static int h5_state_cdata(h5_state_t* hs); + + +/* 12.2.4.44 */ +static int h5_state_bogus_comment(h5_state_t* hs); +static int h5_state_bogus_comment2(h5_state_t* hs); + +/* 12.2.4.45 */ +static int h5_state_markup_declaration_open(h5_state_t* hs); + +/* 8.2.4.52 */ +static int h5_state_doctype(h5_state_t* hs); + +/** + * public function + */ +void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags flags) +{ + memset(hs, 0, sizeof(h5_state_t)); + hs->s = s; + hs->len = len; + + switch (flags) { + case DATA_STATE: + hs->state = h5_state_data; + break; + case VALUE_NO_QUOTE: + hs->state = h5_state_before_attribute_name; + break; + case VALUE_SINGLE_QUOTE: + hs->state = h5_state_attribute_value_single_quote; + break; + case VALUE_DOUBLE_QUOTE: + hs->state = h5_state_attribute_value_double_quote; + break; + case VALUE_BACK_QUOTE: + hs->state = h5_state_attribute_value_back_quote; + break; + } +} + +/** + * public function + */ +int libinjection_h5_next(h5_state_t* hs) +{ + assert(hs->state != NULL); + return (*hs->state)(hs); +} + +/** + * Everything below here is private + * + */ + + +static int h5_is_white(char ch) +{ + /* + * \t = horizontal tab = 0x09 + * \n = newline = 0x0A + * \v = vertical tab = 0x0B + * \f = form feed = 0x0C + * \r = cr = 0x0D + */ + return strchr(" \t\n\v\f\r", ch) != NULL; +} + +static int h5_skip_white(h5_state_t* hs) +{ + char ch; + while (hs->pos < hs->len) { + ch = hs->s[hs->pos]; + switch (ch) { + case 0x00: /* IE only */ + case 0x20: + case 0x09: + case 0x0A: + case 0x0B: /* IE only */ + case 0x0C: + case 0x0D: /* IE only */ + hs->pos += 1; + break; + default: + return ch; + } + } + return CHAR_EOF; +} + +static int h5_state_eof(h5_state_t* hs) +{ + /* eliminate unused function argument warning */ + (void)hs; + return 0; +} + +static int h5_state_data(h5_state_t* hs) +{ + const char* idx; + + TRACE(); + assert(hs->len >= hs->pos); + idx = (const char*) memchr(hs->s + hs->pos, CHAR_LT, hs->len - hs->pos); + if (idx == NULL) { + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = DATA_TEXT; + hs->state = h5_state_eof; + if (hs->token_len == 0) { + return 0; + } + } else { + hs->token_start = hs->s + hs->pos; + hs->token_type = DATA_TEXT; + hs->token_len = (size_t)(idx - hs->s) - hs->pos; + hs->pos = (size_t)(idx - hs->s) + 1; + hs->state = h5_state_tag_open; + if (hs->token_len == 0) { + return h5_state_tag_open(hs); + } + } + return 1; +} + +/** + * 12 2.4.8 + */ +static int h5_state_tag_open(h5_state_t* hs) +{ + char ch; + + TRACE(); + if (hs->pos >= hs->len) { + return 0; + } + ch = hs->s[hs->pos]; + if (ch == CHAR_BANG) { + hs->pos += 1; + return h5_state_markup_declaration_open(hs); + } else if (ch == CHAR_SLASH) { + hs->pos += 1; + hs->is_close = 1; + return h5_state_end_tag_open(hs); + } else if (ch == CHAR_QUESTION) { + hs->pos += 1; + return h5_state_bogus_comment(hs); + } else if (ch == CHAR_PERCENT) { + /* this is not in spec.. alternative comment format used + by IE <= 9 and Safari < 4.0.3 */ + hs->pos += 1; + return h5_state_bogus_comment2(hs); + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + return h5_state_tag_name(hs); + } else if (ch == CHAR_NULL) { + /* IE-ism NULL characters are ignored */ + return h5_state_tag_name(hs); + } else { + /* user input mistake in configuring state */ + if (hs->pos == 0) { + return h5_state_data(hs); + } + hs->token_start = hs->s + hs->pos - 1; + hs->token_len = 1; + hs->token_type = DATA_TEXT; + hs->state = h5_state_data; + return 1; + } +} +/** + * 12.2.4.9 + */ +static int h5_state_end_tag_open(h5_state_t* hs) +{ + char ch; + + TRACE(); + + if (hs->pos >= hs->len) { + return 0; + } + ch = hs->s[hs->pos]; + if (ch == CHAR_GT) { + return h5_state_data(hs); + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + return h5_state_tag_name(hs); + } + + hs->is_close = 0; + return h5_state_bogus_comment(hs); +} +/* + * + */ +static int h5_state_tag_name_close(h5_state_t* hs) +{ + TRACE(); + hs->is_close = 0; + hs->token_start = hs->s + hs->pos; + hs->token_len = 1; + hs->token_type = TAG_NAME_CLOSE; + hs->pos += 1; + if (hs->pos < hs->len) { + hs->state = h5_state_data; + } else { + hs->state = h5_state_eof; + } + + return 1; +} + +/** + * 12.2.4.10 + */ +static int h5_state_tag_name(h5_state_t* hs) +{ + char ch; + size_t pos; + + TRACE(); + pos = hs->pos; + while (pos < hs->len) { + ch = hs->s[pos]; + if (ch == 0) { + /* special non-standard case */ + /* allow nulls in tag name */ + /* some old browsers apparently allow and ignore them */ + pos += 1; + } else if (h5_is_white(ch)) { + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->token_type = TAG_NAME_OPEN; + hs->pos = pos + 1; + hs->state = h5_state_before_attribute_name; + return 1; + } else if (ch == CHAR_SLASH) { + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->token_type = TAG_NAME_OPEN; + hs->pos = pos + 1; + hs->state = h5_state_self_closing_start_tag; + return 1; + } else if (ch == CHAR_GT) { + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + if (hs->is_close) { + hs->pos = pos + 1; + hs->is_close = 0; + hs->token_type = TAG_CLOSE; + hs->state = h5_state_data; + } else { + hs->pos = pos; + hs->token_type = TAG_NAME_OPEN; + hs->state = h5_state_tag_name_close; + } + return 1; + } else { + pos += 1; + } + } + + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = TAG_NAME_OPEN; + hs->state = h5_state_eof; + return 1; +} + +/** + * 12.2.4.34 + */ +static int h5_state_before_attribute_name(h5_state_t* hs) +{ + int ch; + + TRACE(); + ch = h5_skip_white(hs); + switch (ch) { + case CHAR_EOF: { + return 0; + } + case CHAR_SLASH: { + hs->pos += 1; + return h5_state_self_closing_start_tag(hs); + } + case CHAR_GT: { + hs->state = h5_state_data; + hs->token_start = hs->s + hs->pos; + hs->token_len = 1; + hs->token_type = TAG_NAME_CLOSE; + hs->pos += 1; + return 1; + } + default: { + return h5_state_attribute_name(hs); + } + } +} + +static int h5_state_attribute_name(h5_state_t* hs) +{ + char ch; + size_t pos; + + TRACE(); + pos = hs->pos + 1; + while (pos < hs->len) { + ch = hs->s[pos]; + if (h5_is_white(ch)) { + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->token_type = ATTR_NAME; + hs->state = h5_state_after_attribute_name; + hs->pos = pos + 1; + return 1; + } else if (ch == CHAR_SLASH) { + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->token_type = ATTR_NAME; + hs->state = h5_state_self_closing_start_tag; + hs->pos = pos + 1; + return 1; + } else if (ch == CHAR_EQUALS) { + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->token_type = ATTR_NAME; + hs->state = h5_state_before_attribute_value; + hs->pos = pos + 1; + return 1; + } else if (ch == CHAR_GT) { + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->token_type = ATTR_NAME; + hs->state = h5_state_tag_name_close; + hs->pos = pos; + return 1; + } else { + pos += 1; + } + } + /* EOF */ + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = ATTR_NAME; + hs->state = h5_state_eof; + hs->pos = hs->len; + return 1; +} + +/** + * 12.2.4.36 + */ +static int h5_state_after_attribute_name(h5_state_t* hs) +{ + int c; + + TRACE(); + c = h5_skip_white(hs); + switch (c) { + case CHAR_EOF: { + return 0; + } + case CHAR_SLASH: { + hs->pos += 1; + return h5_state_self_closing_start_tag(hs); + } + case CHAR_EQUALS: { + hs->pos += 1; + return h5_state_before_attribute_value(hs); + } + case CHAR_GT: { + return h5_state_tag_name_close(hs); + } + default: { + return h5_state_attribute_name(hs); + } + } +} + +/** + * 12.2.4.37 + */ +static int h5_state_before_attribute_value(h5_state_t* hs) +{ + int c; + TRACE(); + + c = h5_skip_white(hs); + + if (c == CHAR_EOF) { + hs->state = h5_state_eof; + return 0; + } + + if (c == CHAR_DOUBLE) { + return h5_state_attribute_value_double_quote(hs); + } else if (c == CHAR_SINGLE) { + return h5_state_attribute_value_single_quote(hs); + } else if (c == CHAR_TICK) { + /* NON STANDARD IE */ + return h5_state_attribute_value_back_quote(hs); + } else { + return h5_state_attribute_value_no_quote(hs); + } +} + + +static int h5_state_attribute_value_quote(h5_state_t* hs, char qchar) +{ + const char* idx; + + TRACE(); + + /* skip initial quote in normal case. + * don't do this "if (pos == 0)" since it means we have started + * in a non-data state. given an input of '>pos > 0) { + hs->pos += 1; + } + + + idx = (const char*) memchr(hs->s + hs->pos, qchar, hs->len - hs->pos); + if (idx == NULL) { + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = ATTR_VALUE; + hs->state = h5_state_eof; + } else { + hs->token_start = hs->s + hs->pos; + hs->token_len = (size_t)(idx - hs->s) - hs->pos; + hs->token_type = ATTR_VALUE; + hs->state = h5_state_after_attribute_value_quoted_state; + hs->pos += hs->token_len + 1; + } + return 1; +} + +static +int h5_state_attribute_value_double_quote(h5_state_t* hs) +{ + TRACE(); + return h5_state_attribute_value_quote(hs, CHAR_DOUBLE); +} + +static +int h5_state_attribute_value_single_quote(h5_state_t* hs) +{ + TRACE(); + return h5_state_attribute_value_quote(hs, CHAR_SINGLE); +} + +static +int h5_state_attribute_value_back_quote(h5_state_t* hs) +{ + TRACE(); + return h5_state_attribute_value_quote(hs, CHAR_TICK); +} + +static int h5_state_attribute_value_no_quote(h5_state_t* hs) +{ + char ch; + size_t pos; + + TRACE(); + pos = hs->pos; + while (pos < hs->len) { + ch = hs->s[pos]; + if (h5_is_white(ch)) { + hs->token_type = ATTR_VALUE; + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->pos = pos + 1; + hs->state = h5_state_before_attribute_name; + return 1; + } else if (ch == CHAR_GT) { + hs->token_type = ATTR_VALUE; + hs->token_start = hs->s + hs->pos; + hs->token_len = pos - hs->pos; + hs->pos = pos; + hs->state = h5_state_tag_name_close; + return 1; + } + pos += 1; + } + TRACE(); + /* EOF */ + hs->state = h5_state_eof; + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = ATTR_VALUE; + return 1; +} + +/** + * 12.2.4.41 + */ +static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs) +{ + char ch; + + TRACE(); + if (hs->pos >= hs->len) { + return 0; + } + ch = hs->s[hs->pos]; + if (h5_is_white(ch)) { + hs->pos += 1; + return h5_state_before_attribute_name(hs); + } else if (ch == CHAR_SLASH) { + hs->pos += 1; + return h5_state_self_closing_start_tag(hs); + } else if (ch == CHAR_GT) { + hs->token_start = hs->s + hs->pos; + hs->token_len = 1; + hs->token_type = TAG_NAME_CLOSE; + hs->pos += 1; + hs->state = h5_state_data; + return 1; + } else { + return h5_state_before_attribute_name(hs); + } +} + +/** + * 12.2.4.43 + */ +static int h5_state_self_closing_start_tag(h5_state_t* hs) +{ + char ch; + + TRACE(); + if (hs->pos >= hs->len) { + return 0; + } + ch = hs->s[hs->pos]; + if (ch == CHAR_GT) { + assert(hs->pos > 0); + hs->token_start = hs->s + hs->pos -1; + hs->token_len = 2; + hs->token_type = TAG_NAME_SELFCLOSE; + hs->state = h5_state_data; + hs->pos += 1; + return 1; + } else { + return h5_state_before_attribute_name(hs); + } +} + +/** + * 12.2.4.44 + */ +static int h5_state_bogus_comment(h5_state_t* hs) +{ + const char* idx; + + TRACE(); + idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos); + if (idx == NULL) { + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->pos = hs->len; + hs->state = h5_state_eof; + } else { + hs->token_start = hs->s + hs->pos; + hs->token_len = (size_t)(idx - hs->s) - hs->pos; + hs->pos = (size_t)(idx - hs->s) + 1; + hs->state = h5_state_data; + } + + hs->token_type = TAG_COMMENT; + return 1; +} + +/** + * 12.2.4.44 ALT + */ +static int h5_state_bogus_comment2(h5_state_t* hs) +{ + const char* idx; + size_t pos; + + TRACE(); + pos = hs->pos; + while (1) { + idx = (const char*) memchr(hs->s + pos, CHAR_PERCENT, hs->len - pos); + if (idx == NULL || (idx + 1 >= hs->s + hs->len)) { + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->pos = hs->len; + hs->token_type = TAG_COMMENT; + hs->state = h5_state_eof; + return 1; + } + + if (*(idx +1) != CHAR_GT) { + pos = (size_t)(idx - hs->s) + 1; + continue; + } + + /* ends in %> */ + hs->token_start = hs->s + hs->pos; + hs->token_len = (size_t)(idx - hs->s) - hs->pos; + hs->pos = (size_t)(idx - hs->s) + 2; + hs->state = h5_state_data; + hs->token_type = TAG_COMMENT; + return 1; + } +} + +/** + * 8.2.4.45 + */ +static int h5_state_markup_declaration_open(h5_state_t* hs) +{ + size_t remaining; + + TRACE(); + remaining = hs->len - hs->pos; + if (remaining >= 7 && + /* case insensitive */ + (hs->s[hs->pos + 0] == 'D' || hs->s[hs->pos + 0] == 'd') && + (hs->s[hs->pos + 1] == 'O' || hs->s[hs->pos + 1] == 'o') && + (hs->s[hs->pos + 2] == 'C' || hs->s[hs->pos + 2] == 'c') && + (hs->s[hs->pos + 3] == 'T' || hs->s[hs->pos + 3] == 't') && + (hs->s[hs->pos + 4] == 'Y' || hs->s[hs->pos + 4] == 'y') && + (hs->s[hs->pos + 5] == 'P' || hs->s[hs->pos + 5] == 'p') && + (hs->s[hs->pos + 6] == 'E' || hs->s[hs->pos + 6] == 'e') + ) { + return h5_state_doctype(hs); + } else if (remaining >= 7 && + /* upper case required */ + hs->s[hs->pos + 0] == '[' && + hs->s[hs->pos + 1] == 'C' && + hs->s[hs->pos + 2] == 'D' && + hs->s[hs->pos + 3] == 'A' && + hs->s[hs->pos + 4] == 'T' && + hs->s[hs->pos + 5] == 'A' && + hs->s[hs->pos + 6] == '[' + ) { + hs->pos += 7; + return h5_state_cdata(hs); + } else if (remaining >= 2 && + hs->s[hs->pos + 0] == '-' && + hs->s[hs->pos + 1] == '-') { + hs->pos += 2; + return h5_state_comment(hs); + } + + return h5_state_bogus_comment(hs); +} + +/** + * 12.2.4.48 + * 12.2.4.49 + * 12.2.4.50 + * 12.2.4.51 + * state machine spec is confusing since it can only look + * at one character at a time but simply it's comments end by: + * 1) EOF + * 2) ending in --> + * 3) ending in -!> + */ +static int h5_state_comment(h5_state_t* hs) +{ + char ch; + const char* idx; + size_t pos; + size_t offset; + const char* end = hs->s + hs->len; + + TRACE(); + pos = hs->pos; + while (1) { + + idx = (const char*) memchr(hs->s + pos, CHAR_DASH, hs->len - pos); + + /* did not find anything or has less than 3 chars left */ + if (idx == NULL || idx > hs->s + hs->len - 3) { + hs->state = h5_state_eof; + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = TAG_COMMENT; + return 1; + } + offset = 1; + + /* skip all nulls */ + while (idx + offset < end && *(idx + offset) == 0) { + offset += 1; + } + if (idx + offset == end) { + hs->state = h5_state_eof; + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = TAG_COMMENT; + return 1; + } + + ch = *(idx + offset); + if (ch != CHAR_DASH && ch != CHAR_BANG) { + pos = (size_t)(idx - hs->s) + 1; + continue; + } + + /* need to test */ +#if 0 + /* skip all nulls */ + while (idx + offset < end && *(idx + offset) == 0) { + offset += 1; + } + if (idx + offset == end) { + hs->state = h5_state_eof; + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = TAG_COMMENT; + return 1; + } +#endif + + offset += 1; + if (idx + offset == end) { + hs->state = h5_state_eof; + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = TAG_COMMENT; + return 1; + } + + + ch = *(idx + offset); + if (ch != CHAR_GT) { + pos = (size_t)(idx - hs->s) + 1; + continue; + } + offset += 1; + + /* ends in --> or -!> */ + hs->token_start = hs->s + hs->pos; + hs->token_len = (size_t)(idx - hs->s) - hs->pos; + hs->pos = (size_t)(idx + offset - hs->s); + hs->state = h5_state_data; + hs->token_type = TAG_COMMENT; + return 1; + } +} + +static int h5_state_cdata(h5_state_t* hs) +{ + const char* idx; + size_t pos; + + TRACE(); + pos = hs->pos; + while (1) { + idx = (const char*) memchr(hs->s + pos, CHAR_RIGHTB, hs->len - pos); + + /* did not find anything or has less than 3 chars left */ + if (idx == NULL || idx > hs->s + hs->len - 3) { + hs->state = h5_state_eof; + hs->token_start = hs->s + hs->pos; + hs->token_len = hs->len - hs->pos; + hs->token_type = DATA_TEXT; + return 1; + } else if ( *(idx+1) == CHAR_RIGHTB && *(idx+2) == CHAR_GT) { + hs->state = h5_state_data; + hs->token_start = hs->s + hs->pos; + hs->token_len = (size_t)(idx - hs->s) - hs->pos; + hs->pos = (size_t)(idx - hs->s) + 3; + hs->token_type = DATA_TEXT; + return 1; + } else { + pos = (size_t)(idx - hs->s) + 1; + } + } +} + +/** + * 8.2.4.52 + * http://www.w3.org/html/wg/drafts/html/master/syntax.html#doctype-state + */ +static int h5_state_doctype(h5_state_t* hs) +{ + const char* idx; + + TRACE(); + hs->token_start = hs->s + hs->pos; + hs->token_type = DOCTYPE; + + idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos); + if (idx == NULL) { + hs->state = h5_state_eof; + hs->token_len = hs->len - hs->pos; + } else { + hs->state = h5_state_data; + hs->token_len = (size_t)(idx - hs->s) - hs->pos; + hs->pos = (size_t)(idx - hs->s) + 1; + } + return 1; +} diff --git a/src/lib/third_party/src/libinjection_sqli.c b/src/lib/third_party/src/libinjection_sqli.c new file mode 100644 index 0000000..c411677 --- /dev/null +++ b/src/lib/third_party/src/libinjection_sqli.c @@ -0,0 +1,2325 @@ +/** + * Copyright 2012,2016 Nick Galbreath + * nickg@client9.com + * BSD License -- see COPYING.txt for details + * + * https://libinjection.client9.com/ + * + */ + +#include +#include +#include +#include +#include +#include + +#include "libinjection.h" +#include "libinjection_sqli.h" +#include "libinjection_sqli_data.h" + +#define LIBINJECTION_VERSION "3.9.2" + +#define LIBINJECTION_SQLI_TOKEN_SIZE sizeof(((stoken_t*)(0))->val) +#define LIBINJECTION_SQLI_MAX_TOKENS 5 + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define CHAR_NULL '\0' +#define CHAR_SINGLE '\'' +#define CHAR_DOUBLE '"' +#define CHAR_TICK '`' + +/* faster than calling out to libc isdigit */ +#define ISDIGIT(a) ((unsigned)((a) - '0') <= 9) + +#if 0 +#define FOLD_DEBUG printf("%d \t more=%d pos=%d left=%d\n", __LINE__, more, (int)pos, (int)left); +#else +#define FOLD_DEBUG +#endif + +/* + * not making public just yet + */ +typedef enum { + TYPE_NONE = 0 + , TYPE_KEYWORD = (int)'k' + , TYPE_UNION = (int)'U' + , TYPE_GROUP = (int)'B' + , TYPE_EXPRESSION = (int)'E' + , TYPE_SQLTYPE = (int)'t' + , TYPE_FUNCTION = (int)'f' + , TYPE_BAREWORD = (int)'n' + , TYPE_NUMBER = (int)'1' + , TYPE_VARIABLE = (int)'v' + , TYPE_STRING = (int)'s' + , TYPE_OPERATOR = (int)'o' + , TYPE_LOGIC_OPERATOR = (int)'&' + , TYPE_COMMENT = (int)'c' + , TYPE_COLLATE = (int)'A' + , TYPE_LEFTPARENS = (int)'(' + , TYPE_RIGHTPARENS = (int)')' /* not used? */ + , TYPE_LEFTBRACE = (int)'{' + , TYPE_RIGHTBRACE = (int)'}' + , TYPE_DOT = (int)'.' + , TYPE_COMMA = (int)',' + , TYPE_COLON = (int)':' + , TYPE_SEMICOLON = (int)';' + , TYPE_TSQL = (int)'T' /* TSQL start */ + , TYPE_UNKNOWN = (int)'?' + , TYPE_EVIL = (int)'X' /* unparsable, abort */ + , TYPE_FINGERPRINT = (int)'F' /* not really a token */ + , TYPE_BACKSLASH = (int)'\\' +} sqli_token_types; + +/** + * Initializes parsing state + * + */ +static char flag2delim(int flag) +{ + if (flag & FLAG_QUOTE_SINGLE) { + return CHAR_SINGLE; + } else if (flag & FLAG_QUOTE_DOUBLE) { + return CHAR_DOUBLE; + } else { + return CHAR_NULL; + } +} + +/* memchr2 finds a string of 2 characters inside another string + * This a specialized version of "memmem" or "memchr". + * 'memmem' doesn't exist on all platforms + * + * Porting notes: this is just a special version of + * astring.find("AB") + * + */ +static const char * +memchr2(const char *haystack, size_t haystack_len, char c0, char c1) +{ + const char *cur = haystack; + const char *last = haystack + haystack_len - 1; + + if (haystack_len < 2) { + return NULL; + } + + while (cur < last) { + /* safe since cur < len - 1 always */ + if (cur[0] == c0 && cur[1] == c1) { + return cur; + } + cur += 1; + } + + return NULL; +} + +/** + * memmem might not exist on some systems + */ +static const char * +my_memmem(const char* haystack, size_t hlen, const char* needle, size_t nlen) +{ + const char* cur; + const char* last; + assert(haystack); + assert(needle); + assert(nlen > 1); + last = haystack + hlen - nlen; + for (cur = haystack; cur <= last; ++cur) { + if (cur[0] == needle[0] && memcmp(cur, needle, nlen) == 0) { + return cur; + } + } + return NULL; +} + +/** Find largest string containing certain characters. + * + * C Standard library 'strspn' only works for 'c-strings' (null terminated) + * This works on arbitrary length. + * + * Performance notes: + * not critical + * + * Porting notes: + * if accept is 'ABC', then this function would be similar to + * a_regexp.match(a_str, '[ABC]*'), + */ +static size_t +strlenspn(const char *s, size_t len, const char *accept) +{ + size_t i; + for (i = 0; i < len; ++i) { + /* likely we can do better by inlining this function + * but this works for now + */ + if (strchr(accept, s[i]) == NULL) { + return i; + } + } + return len; +} + +static size_t +strlencspn(const char *s, size_t len, const char *accept) +{ + size_t i; + for (i = 0; i < len; ++i) { + /* likely we can do better by inlining this function + * but this works for now + */ + if (strchr(accept, s[i]) != NULL) { + return i; + } + } + return len; +} +static int char_is_white(char ch) { + /* ' ' space is 0x32 + '\t 0x09 \011 horizontal tab + '\n' 0x0a \012 new line + '\v' 0x0b \013 vertical tab + '\f' 0x0c \014 new page + '\r' 0x0d \015 carriage return + 0x00 \000 null (oracle) + 0xa0 \240 is Latin-1 + */ + return strchr(" \t\n\v\f\r\240\000", ch) != NULL; +} + +/* DANGER DANGER + * This is -very specialized function- + * + * this compares a ALL_UPPER CASE C STRING + * with a *arbitrary memory* + length + * + * Sane people would just make a copy, up-case + * and use a hash table. + * + * Required since libc version uses the current locale + * and is much slower. + */ +static int cstrcasecmp(const char *a, const char *b, size_t n) +{ + char cb; + + for (; n > 0; a++, b++, n--) { + cb = *b; + if (cb >= 'a' && cb <= 'z') { + cb -= 0x20; + } + if (*a != cb) { + return *a - cb; + } else if (*a == '\0') { + return -1; + } + } + + return (*a == 0) ? 0 : 1; +} + +/** + * Case sensitive string compare. + * Here only to make code more readable + */ +static int streq(const char *a, const char *b) +{ + return strcmp(a, b) == 0; +} + +/** + * + * + * + * Porting Notes: + * given a mapping/hash of string to char + * this is just + * typecode = mapping[key.upper()] + */ + +static char bsearch_keyword_type(const char *key, size_t len, + const keyword_t * keywords, size_t numb) +{ + size_t pos; + size_t left = 0; + size_t right = numb - 1; + + while (left < right) { + pos = (left + right) >> 1; + + /* arg0 = upper case only, arg1 = mixed case */ + if (cstrcasecmp(keywords[pos].word, key, len) < 0) { + left = pos + 1; + } else { + right = pos; + } + } + if ((left == right) && cstrcasecmp(keywords[left].word, key, len) == 0) { + return keywords[left].type; + } else { + return CHAR_NULL; + } +} + +static char is_keyword(const char* key, size_t len) +{ + return bsearch_keyword_type(key, len, sql_keywords, sql_keywords_sz); +} + +/* st_token methods + * + * The following functions manipulates the stoken_t type + * + * + */ + +static void st_clear(stoken_t * st) +{ + memset(st, 0, sizeof(stoken_t)); +} + +static void st_assign_char(stoken_t * st, const char stype, size_t pos, size_t len, + const char value) +{ + /* done to eliminate unused warning */ + (void)len; + st->type = (char) stype; + st->pos = pos; + st->len = 1; + st->val[0] = value; + st->val[1] = CHAR_NULL; +} + +static void st_assign(stoken_t * st, const char stype, + size_t pos, size_t len, const char* value) +{ + const size_t MSIZE = LIBINJECTION_SQLI_TOKEN_SIZE; + size_t last = len < MSIZE ? len : (MSIZE - 1); + st->type = (char) stype; + st->pos = pos; + st->len = last; + memcpy(st->val, value, last); + st->val[last] = CHAR_NULL; +} + +static void st_copy(stoken_t * dest, const stoken_t * src) +{ + memcpy(dest, src, sizeof(stoken_t)); +} + +static int st_is_arithmetic_op(const stoken_t* st) +{ + const char ch = st->val[0]; + return (st->type == TYPE_OPERATOR && st->len == 1 && + (ch == '*' || ch == '/' || ch == '-' || ch == '+' || ch == '%')); +} + +static int st_is_unary_op(const stoken_t * st) +{ + const char* str = st->val; + const size_t len = st->len; + + if (st->type != TYPE_OPERATOR) { + return FALSE; + } + + switch (len) { + case 1: + return *str == '+' || *str == '-' || *str == '!' || *str == '~'; + case 2: + return str[0] == '!' && str[1] == '!'; + case 3: + return cstrcasecmp("NOT", str, 3) == 0; + default: + return FALSE; + } +} + +/* Parsers + * + * + */ + +static size_t parse_white(struct libinjection_sqli_state * sf) +{ + return sf->pos + 1; +} + +static size_t parse_operator1(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + size_t pos = sf->pos; + + st_assign_char(sf->current, TYPE_OPERATOR, pos, 1, cs[pos]); + return pos + 1; +} + +static size_t parse_other(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + size_t pos = sf->pos; + + st_assign_char(sf->current, TYPE_UNKNOWN, pos, 1, cs[pos]); + return pos + 1; +} + +static size_t parse_char(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + size_t pos = sf->pos; + + st_assign_char(sf->current, cs[pos], pos, 1, cs[pos]); + return pos + 1; +} + +static size_t parse_eol_comment(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + + const char *endpos = + (const char *) memchr((const void *) (cs + pos), '\n', slen - pos); + if (endpos == NULL) { + st_assign(sf->current, TYPE_COMMENT, pos, slen - pos, cs + pos); + return slen; + } else { + st_assign(sf->current, TYPE_COMMENT, pos, (size_t)(endpos - cs) - pos, cs + pos); + return (size_t)((endpos - cs) + 1); + } +} + +/** In ANSI mode, hash is an operator + * In MYSQL mode, it's a EOL comment like '--' + */ +static size_t parse_hash(struct libinjection_sqli_state * sf) +{ + sf->stats_comment_hash += 1; + if (sf->flags & FLAG_SQL_MYSQL) { + sf->stats_comment_hash += 1; + return parse_eol_comment(sf); + } else { + st_assign_char(sf->current, TYPE_OPERATOR, sf->pos, 1, '#'); + return sf->pos + 1; + } +} + +static size_t parse_dash(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + + /* + * five cases + * 1) --[white] this is always a SQL comment + * 2) --[EOF] this is a comment + * 3) --[notwhite] in MySQL this is NOT a comment but two unary operators + * 4) --[notwhite] everyone else thinks this is a comment + * 5) -[not dash] '-' is a unary operator + */ + + if (pos + 2 < slen && cs[pos + 1] == '-' && char_is_white(cs[pos+2]) ) { + return parse_eol_comment(sf); + } else if (pos +2 == slen && cs[pos + 1] == '-') { + return parse_eol_comment(sf); + } else if (pos + 1 < slen && cs[pos + 1] == '-' && (sf->flags & FLAG_SQL_ANSI)) { + /* --[not-white] not-white case: + * + */ + sf->stats_comment_ddx += 1; + return parse_eol_comment(sf); + } else { + st_assign_char(sf->current, TYPE_OPERATOR, pos, 1, '-'); + return pos + 1; + } +} + + +/** This detects MySQL comments, comments that + * start with /x! We just ban these now but + * previously we attempted to parse the inside + * + * For reference: + * the form of /x![anything]x/ or /x!12345[anything] x/ + * + * Mysql 3 (maybe 4), allowed this: + * /x!0selectx/ 1; + * where 0 could be any number. + * + * The last version of MySQL 3 was in 2003. + + * It is unclear if the MySQL 3 syntax was allowed + * in MySQL 4. The last version of MySQL 4 was in 2008 + * + */ +static size_t is_mysql_comment(const char *cs, const size_t len, size_t pos) +{ + /* so far... + * cs[pos] == '/' && cs[pos+1] == '*' + */ + + if (pos + 2 >= len) { + /* not a mysql comment */ + return 0; + } + + if (cs[pos + 2] != '!') { + /* not a mysql comment */ + return 0; + } + + /* + * this is a mysql comment + * got "/x!" + */ + return 1; +} + +static size_t parse_slash(struct libinjection_sqli_state * sf) +{ + const char* ptr; + size_t clen; + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + const char* cur = cs + pos; + char ctype = TYPE_COMMENT; + size_t pos1 = pos + 1; + if (pos1 == slen || cs[pos1] != '*') { + return parse_operator1(sf); + } + + /* + * skip over initial '/x' + */ + ptr = memchr2(cur + 2, slen - (pos + 2), '*', '/'); + + /* + * (ptr == NULL) causes false positive in cppcheck 1.61 + * casting to type seems to fix it + */ + if (ptr == (const char*) NULL) { + /* till end of line */ + clen = slen - pos; + } else { + clen = (size_t)(ptr + 2 - cur); + } + + /* + * postgresql allows nested comments which makes + * this is incompatible with parsing so + * if we find a '/x' inside the coment, then + * make a new token. + * + * Also, Mysql's "conditional" comments for version + * are an automatic black ban! + */ + + if(ptr && (memchr2(cur + 2, (size_t)(ptr - (cur + 1)), '/', '*') != NULL)) { + ctype = TYPE_EVIL; + } else if (is_mysql_comment(cs, slen, pos)) { + ctype = TYPE_EVIL; + } + + st_assign(sf->current, ctype, pos, clen, cs + pos); + return pos + clen; +} + + +static size_t parse_backslash(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + + /* + * Weird MySQL alias for NULL, "\N" (capital N only) + */ + if (pos + 1 < slen && cs[pos +1] == 'N') { + st_assign(sf->current, TYPE_NUMBER, pos, 2, cs + pos); + return pos + 2; + } else { + st_assign_char(sf->current, TYPE_BACKSLASH, pos, 1, cs[pos]); + return pos + 1; + } +} + +static size_t parse_operator2(struct libinjection_sqli_state * sf) +{ + char ch; + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + + if (pos + 1 >= slen) { + return parse_operator1(sf); + } + + if (pos + 2 < slen && + cs[pos] == '<' && + cs[pos + 1] == '=' && + cs[pos + 2] == '>') { + /* + * special 3-char operator + */ + st_assign(sf->current, TYPE_OPERATOR, pos, 3, cs + pos); + return pos + 3; + } + + ch = sf->lookup(sf, LOOKUP_OPERATOR, cs + pos, 2); + if (ch != CHAR_NULL) { + st_assign(sf->current, ch, pos, 2, cs+pos); + return pos + 2; + } + + /* + * not an operator.. what to do with the two + * characters we got? + */ + + if (cs[pos] == ':') { + /* ':' is not an operator */ + st_assign(sf->current, TYPE_COLON, pos, 1, cs+pos); + return pos + 1; + } else { + /* + * must be a single char operator + */ + return parse_operator1(sf); + } +} + +/* + * Ok! " \" " one backslash = escaped! + * " \\" " two backslash = not escaped! + * "\\\" " three backslash = escaped! + */ +static int is_backslash_escaped(const char* end, const char* start) +{ + const char* ptr; + for (ptr = end; ptr >= start; ptr--) { + if (*ptr != '\\') { + break; + } + } + /* if number of backslashes is odd, it is escaped */ + + return (end - ptr) & 1; +} + +static size_t is_double_delim_escaped(const char* cur, const char* end) +{ + return ((cur + 1) < end) && *(cur+1) == *cur; +} + +/* Look forward for doubling of delimiter + * + * case 'foo''bar' --> foo''bar + * + * ending quote isn't duplicated (i.e. escaped) + * since it's the wrong char or EOL + * + */ +static size_t parse_string_core(const char *cs, const size_t len, size_t pos, + stoken_t * st, char delim, size_t offset) +{ + /* + * offset is to skip the perhaps first quote char + */ + const char *qpos = + (const char *) memchr((const void *) (cs + pos + offset), delim, + len - pos - offset); + + /* + * then keep string open/close info + */ + if (offset > 0) { + /* + * this is real quote + */ + st->str_open = delim; + } else { + /* + * this was a simulated quote + */ + st->str_open = CHAR_NULL; + } + + while (TRUE) { + if (qpos == NULL) { + /* + * string ended with no trailing quote + * assign what we have + */ + st_assign(st, TYPE_STRING, pos + offset, len - pos - offset, cs + pos + offset); + st->str_close = CHAR_NULL; + return len; + } else if ( is_backslash_escaped(qpos - 1, cs + pos + offset)) { + /* keep going, move ahead one character */ + qpos = + (const char *) memchr((const void *) (qpos + 1), delim, + (size_t)((cs + len) - (qpos + 1))); + continue; + } else if (is_double_delim_escaped(qpos, cs + len)) { + /* keep going, move ahead two characters */ + qpos = + (const char *) memchr((const void *) (qpos + 2), delim, + (size_t)((cs + len) - (qpos + 2))); + continue; + } else { + /* hey it's a normal string */ + st_assign(st, TYPE_STRING, pos + offset, + (size_t)(qpos - (cs + pos + offset)), cs + pos + offset); + st->str_close = delim; + return (size_t)(qpos - cs + 1); + } + } +} + +/** + * Used when first char is a ' or " + */ +static size_t parse_string(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + + /* + * assert cs[pos] == single or double quote + */ + return parse_string_core(cs, slen, pos, sf->current, cs[pos], 1); +} + +/** + * Used when first char is: + * N or n: mysql "National Character set" + * E : psql "Escaped String" + */ +static size_t parse_estring(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + + if (pos + 2 >= slen || cs[pos+1] != CHAR_SINGLE) { + return parse_word(sf); + } + return parse_string_core(cs, slen, pos, sf->current, CHAR_SINGLE, 2); +} + +static size_t parse_ustring(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + size_t slen = sf->slen; + size_t pos = sf->pos; + + if (pos + 2 < slen && cs[pos+1] == '&' && cs[pos+2] == '\'') { + sf->pos += 2; + pos = parse_string(sf); + sf->current->str_open = 'u'; + if (sf->current->str_close == '\'') { + sf->current->str_close = 'u'; + } + return pos; + } else { + return parse_word(sf); + } +} + +static size_t parse_qstring_core(struct libinjection_sqli_state * sf, size_t offset) +{ + char ch; + const char *strend; + const char *cs = sf->s; + size_t slen = sf->slen; + size_t pos = sf->pos + offset; + + /* if we are already at end of string.. + if current char is not q or Q + if we don't have 2 more chars + if char2 != a single quote + then, just treat as word + */ + if (pos >= slen || + (cs[pos] != 'q' && cs[pos] != 'Q') || + pos + 2 >= slen || + cs[pos + 1] != '\'') { + return parse_word(sf); + } + + ch = cs[pos + 2]; + + /* the ch > 127 is un-needed since + * we assume char is signed + */ + if (ch < 33 /* || ch > 127 */) { + return parse_word(sf); + } + switch (ch) { + case '(' : ch = ')'; break; + case '[' : ch = ']'; break; + case '{' : ch = '}'; break; + case '<' : ch = '>'; break; + } + + strend = memchr2(cs + pos + 3, slen - pos - 3, ch, '\''); + if (strend == NULL) { + st_assign(sf->current, TYPE_STRING, pos + 3, slen - pos - 3, cs + pos + 3); + sf->current->str_open = 'q'; + sf->current->str_close = CHAR_NULL; + return slen; + } else { + st_assign(sf->current, TYPE_STRING, pos + 3, (size_t)(strend - cs) - pos - 3, cs + pos + 3); + sf->current->str_open = 'q'; + sf->current->str_close = 'q'; + return (size_t)(strend - cs + 2); + } +} + +/* + * Oracle's q string + */ +static size_t parse_qstring(struct libinjection_sqli_state * sf) +{ + return parse_qstring_core(sf, 0); +} + +/* + * mysql's N'STRING' or + * ... Oracle's nq string + */ +static size_t parse_nqstring(struct libinjection_sqli_state * sf) +{ + size_t slen = sf->slen; + size_t pos = sf->pos; + if (pos + 2 < slen && sf->s[pos+1] == CHAR_SINGLE) { + return parse_estring(sf); + } + return parse_qstring_core(sf, 1); +} + +/* + * binary literal string + * re: [bB]'[01]*' + */ +static size_t parse_bstring(struct libinjection_sqli_state *sf) +{ + size_t wlen; + const char *cs = sf->s; + size_t pos = sf->pos; + size_t slen = sf->slen; + + /* need at least 2 more characters + * if next char isn't a single quote, then + * continue as normal word + */ + if (pos + 2 >= slen || cs[pos+1] != '\'') { + return parse_word(sf); + } + + wlen = strlenspn(cs + pos + 2, sf->slen - pos - 2, "01"); + if (pos + 2 + wlen >= slen || cs[pos + 2 + wlen] != '\'') { + return parse_word(sf); + } + st_assign(sf->current, TYPE_NUMBER, pos, wlen + 3, cs + pos); + return pos + 2 + wlen + 1; +} + +/* + * hex literal string + * re: [xX]'[0123456789abcdefABCDEF]*' + * mysql has requirement of having EVEN number of chars, + * but pgsql does not + */ +static size_t parse_xstring(struct libinjection_sqli_state *sf) +{ + size_t wlen; + const char *cs = sf->s; + size_t pos = sf->pos; + size_t slen = sf->slen; + + /* need at least 2 more characters + * if next char isn't a single quote, then + * continue as normal word + */ + if (pos + 2 >= slen || cs[pos+1] != '\'') { + return parse_word(sf); + } + + wlen = strlenspn(cs + pos + 2, sf->slen - pos - 2, "0123456789ABCDEFabcdef"); + if (pos + 2 + wlen >= slen || cs[pos + 2 + wlen] != '\'') { + return parse_word(sf); + } + st_assign(sf->current, TYPE_NUMBER, pos, wlen + 3, cs + pos); + return pos + 2 + wlen + 1; +} + +/** + * This handles MS SQLSERVER bracket words + * http://stackoverflow.com/questions/3551284/sql-serverwhat-do-brackets-mean-around-column-name + * + */ +static size_t parse_bword(struct libinjection_sqli_state * sf) +{ + const char *cs = sf->s; + size_t pos = sf->pos; + const char* endptr = (const char*) memchr(cs + pos, ']', sf->slen - pos); + if (endptr == NULL) { + st_assign(sf->current, TYPE_BAREWORD, pos, sf->slen - pos, cs + pos); + return sf->slen; + } else { + st_assign(sf->current, TYPE_BAREWORD, pos, (size_t)(endptr - cs) - pos + 1, cs + pos); + return (size_t)((endptr - cs) + 1); + } +} + +static size_t parse_word(struct libinjection_sqli_state * sf) +{ + char ch; + char delim; + size_t i; + const char *cs = sf->s; + size_t pos = sf->pos; + size_t wlen = strlencspn(cs + pos, sf->slen - pos, + " []{}<>:\\?=@!#~+-*/&|^%(),';\t\n\v\f\r\"\240\000"); + + st_assign(sf->current, TYPE_BAREWORD, pos, wlen, cs + pos); + + /* now we need to look inside what we good for "." and "`" + * and see if what is before is a keyword or not + */ + for (i =0; i < sf->current->len; ++i) { + delim = sf->current->val[i]; + if (delim == '.' || delim == '`') { + ch = sf->lookup(sf, LOOKUP_WORD, sf->current->val, i); + if (ch != TYPE_NONE && ch != TYPE_BAREWORD) { + /* needed for swig */ + st_clear(sf->current); + /* + * we got something like "SELECT.1" + * or SELECT`column` + */ + st_assign(sf->current, ch, pos, i, cs + pos); + return pos + i; + } + } + } + + /* + * do normal lookup with word including '.' + */ + if (wlen < LIBINJECTION_SQLI_TOKEN_SIZE) { + + ch = sf->lookup(sf, LOOKUP_WORD, sf->current->val, wlen); + if (ch == CHAR_NULL) { + ch = TYPE_BAREWORD; + } + sf->current->type = ch; + } + return pos + wlen; +} + +/* MySQL backticks are a cross between string and + * and a bare word. + * + */ +static size_t parse_tick(struct libinjection_sqli_state* sf) +{ + size_t pos = parse_string_core(sf->s, sf->slen, sf->pos, sf->current, CHAR_TICK, 1); + + /* we could check to see if start and end of + * of string are both "`", i.e. make sure we have + * matching set. `foo` vs. `foo + * but I don't think it matters much + */ + + /* check value of string to see if it's a keyword, + * function, operator, etc + */ + char ch = sf->lookup(sf, LOOKUP_WORD, sf->current->val, sf->current->len); + if (ch == TYPE_FUNCTION) { + /* if it's a function, then convert token */ + sf->current->type = TYPE_FUNCTION; + } else { + /* otherwise it's a 'n' type -- mysql treats + * everything as a bare word + */ + sf->current->type = TYPE_BAREWORD; + } + return pos; +} + +static size_t parse_var(struct libinjection_sqli_state * sf) +{ + size_t xlen; + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos + 1; + + /* + * var_count is only used to reconstruct + * the input. It counts the number of '@' + * seen 0 in the case of NULL, 1 or 2 + */ + + /* + * move past optional other '@' + */ + if (pos < slen && cs[pos] == '@') { + pos += 1; + sf->current->count = 2; + } else { + sf->current->count = 1; + } + + /* + * MySQL allows @@`version` + */ + if (pos < slen) { + if (cs[pos] == '`') { + sf->pos = pos; + pos = parse_tick(sf); + sf->current->type = TYPE_VARIABLE; + return pos; + } else if (cs[pos] == CHAR_SINGLE || cs[pos] == CHAR_DOUBLE) { + sf->pos = pos; + pos = parse_string(sf); + sf->current->type = TYPE_VARIABLE; + return pos; + } + } + + + xlen = strlencspn(cs + pos, slen - pos, + " <>:\\?=@!#~+-*/&|^%(),';\t\n\v\f\r'`\""); + if (xlen == 0) { + st_assign(sf->current, TYPE_VARIABLE, pos, 0, cs + pos); + return pos; + } else { + st_assign(sf->current, TYPE_VARIABLE, pos, xlen, cs + pos); + return pos + xlen; + } +} + +static size_t parse_money(struct libinjection_sqli_state *sf) +{ + size_t xlen; + const char* strend; + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + + if (pos + 1 == slen) { + /* end of line */ + st_assign_char(sf->current, TYPE_BAREWORD, pos, 1, '$'); + return slen; + } + + /* + * $1,000.00 or $1.000,00 ok! + * This also parses $....,,,111 but that's ok + */ + + xlen = strlenspn(cs + pos + 1, slen - pos - 1, "0123456789.,"); + if (xlen == 0) { + if (cs[pos + 1] == '$') { + /* we have $$ .. find ending $$ and make string */ + strend = memchr2(cs + pos + 2, slen - pos -2, '$', '$'); + if (strend == NULL) { + /* fell off edge */ + st_assign(sf->current, TYPE_STRING, pos + 2, slen - (pos + 2), cs + pos + 2); + sf->current->str_open = '$'; + sf->current->str_close = CHAR_NULL; + return slen; + } else { + st_assign(sf->current, TYPE_STRING, pos + 2, + (size_t)(strend - (cs + pos + 2)), cs + pos + 2); + sf->current->str_open = '$'; + sf->current->str_close = '$'; + return (size_t)(strend - cs + 2); + } + } else { + /* ok it's not a number or '$$', but maybe it's pgsql "$ quoted strings" */ + xlen = strlenspn(cs + pos + 1, slen - pos - 1, "abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); + if (xlen == 0) { + /* hmm it's "$" _something_ .. just add $ and keep going*/ + st_assign_char(sf->current, TYPE_BAREWORD, pos, 1, '$'); + return pos + 1; + } + /* we have $foobar????? */ + /* is it $foobar$ */ + if (pos + xlen + 1 == slen || cs[pos+xlen+1] != '$') { + /* not $foobar$, or fell off edge */ + st_assign_char(sf->current, TYPE_BAREWORD, pos, 1, '$'); + return pos + 1; + } + + /* we have $foobar$ ... find it again */ + strend = my_memmem(cs+xlen+2, slen - (pos+xlen+2), cs + pos, xlen+2); + + if (strend == NULL || ((size_t)(strend - cs) < (pos+xlen+2))) { + /* fell off edge */ + st_assign(sf->current, TYPE_STRING, pos+xlen+2, slen - pos - xlen - 2, cs+pos+xlen+2); + sf->current->str_open = '$'; + sf->current->str_close = CHAR_NULL; + return slen; + } else { + /* got one */ + st_assign(sf->current, TYPE_STRING, pos+xlen+2, + (size_t)(strend - (cs + pos + xlen + 2)), cs+pos+xlen+2); + sf->current->str_open = '$'; + sf->current->str_close = '$'; + return (size_t)((strend + xlen + 2) - cs); + } + } + } else if (xlen == 1 && cs[pos + 1] == '.') { + /* $. should parsed as a word */ + return parse_word(sf); + } else { + st_assign(sf->current, TYPE_NUMBER, pos, 1 + xlen, cs + pos); + return pos + 1 + xlen; + } +} + +static size_t parse_number(struct libinjection_sqli_state * sf) +{ + size_t xlen; + size_t start; + const char* digits = NULL; + const char *cs = sf->s; + const size_t slen = sf->slen; + size_t pos = sf->pos; + int have_e = 0; + int have_exp = 0; + + /* cs[pos] == '0' has 1/10 chance of being true, + * while pos+1< slen is almost always true + */ + if (cs[pos] == '0' && pos + 1 < slen) { + if (cs[pos + 1] == 'X' || cs[pos + 1] == 'x') { + digits = "0123456789ABCDEFabcdef"; + } else if (cs[pos + 1] == 'B' || cs[pos + 1] == 'b') { + digits = "01"; + } + + if (digits) { + xlen = strlenspn(cs + pos + 2, slen - pos - 2, digits); + if (xlen == 0) { + st_assign(sf->current, TYPE_BAREWORD, pos, 2, cs + pos); + return pos + 2; + } else { + st_assign(sf->current, TYPE_NUMBER, pos, 2 + xlen, cs + pos); + return pos + 2 + xlen; + } + } + } + + start = pos; + while (pos < slen && ISDIGIT(cs[pos])) { + pos += 1; + } + + if (pos < slen && cs[pos] == '.') { + pos += 1; + while (pos < slen && ISDIGIT(cs[pos])) { + pos += 1; + } + if (pos - start == 1) { + /* only one character read so far */ + st_assign_char(sf->current, TYPE_DOT, start, 1, '.'); + return pos; + } + } + + if (pos < slen) { + if (cs[pos] == 'E' || cs[pos] == 'e') { + have_e = 1; + pos += 1; + if (pos < slen && (cs[pos] == '+' || cs[pos] == '-')) { + pos += 1; + } + while (pos < slen && ISDIGIT(cs[pos])) { + have_exp = 1; + pos += 1; + } + } + } + + /* oracle's ending float or double suffix + * http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements003.htm#i139891 + */ + if (pos < slen && (cs[pos] == 'd' || cs[pos] == 'D' || cs[pos] == 'f' || cs[pos] == 'F')) { + if (pos + 1 == slen) { + /* line ends evaluate "... 1.2f$" as '1.2f' */ + pos += 1; + } else if ((char_is_white(cs[pos+1]) || cs[pos+1] == ';')) { + /* + * easy case, evaluate "... 1.2f ... as '1.2f' + */ + pos += 1; + } else if (cs[pos+1] == 'u' || cs[pos+1] == 'U') { + /* + * a bit of a hack but makes '1fUNION' parse as '1f UNION' + */ + pos += 1; + } else { + /* it's like "123FROM" */ + /* parse as "123" only */ + } + } + + if (have_e == 1 && have_exp == 0) { + /* very special form of + * "1234.e" + * "10.10E" + * ".E" + * this is a WORD not a number!! */ + st_assign(sf->current, TYPE_BAREWORD, start, pos - start, cs + start); + } else { + st_assign(sf->current, TYPE_NUMBER, start, pos - start, cs + start); + } + return pos; +} + +/* + * API to return version. This allows us to increment the version + * without having to regenerated the SWIG (or other binding) in minor + * releases. + */ +const char* libinjection_version() +{ + return LIBINJECTION_VERSION; +} + +int libinjection_sqli_tokenize(struct libinjection_sqli_state * sf) +{ + pt2Function fnptr; + size_t *pos = &sf->pos; + stoken_t *current = sf->current; + const char *s = sf->s; + const size_t slen = sf->slen; + + if (slen == 0) { + return FALSE; + } + + st_clear(current); + sf->current = current; + + /* + * if we are at beginning of string + * and in single-quote or double quote mode + * then pretend the input starts with a quote + */ + if (*pos == 0 && (sf->flags & (FLAG_QUOTE_SINGLE | FLAG_QUOTE_DOUBLE))) { + *pos = parse_string_core(s, slen, 0, current, flag2delim(sf->flags), 0); + sf->stats_tokens += 1; + return TRUE; + } + + while (*pos < slen) { + + /* + * get current character + */ + const unsigned char ch = (unsigned char) (s[*pos]); + + /* + * look up the parser, and call it + * + * Porting Note: this is mapping of char to function + * charparsers[ch]() + */ + fnptr = char_parse_map[ch]; + + *pos = (*fnptr) (sf); + + /* + * + */ + if (current->type != CHAR_NULL) { + sf->stats_tokens += 1; + return TRUE; + } + } + return FALSE; +} + +void libinjection_sqli_init(struct libinjection_sqli_state * sf, const char *s, size_t len, int flags) +{ + if (flags == 0) { + flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI; + } + + memset(sf, 0, sizeof(struct libinjection_sqli_state)); + sf->s = s; + sf->slen = len; + sf->lookup = libinjection_sqli_lookup_word; + sf->userdata = 0; + sf->flags = flags; + sf->current = &(sf->tokenvec[0]); +} + +void libinjection_sqli_reset(struct libinjection_sqli_state * sf, int flags) +{ + void *userdata = sf->userdata; + ptr_lookup_fn lookup = sf->lookup;; + + if (flags == 0) { + flags = FLAG_QUOTE_NONE | FLAG_SQL_ANSI; + } + libinjection_sqli_init(sf, sf->s, sf->slen, flags); + sf->lookup = lookup; + sf->userdata = userdata; +} + +void libinjection_sqli_callback(struct libinjection_sqli_state * sf, ptr_lookup_fn fn, void* userdata) +{ + if (fn == NULL) { + sf->lookup = libinjection_sqli_lookup_word; + sf->userdata = (void*)(NULL); + } else { + sf->lookup = fn; + sf->userdata = userdata; + } +} + +/** See if two tokens can be merged since they are compound SQL phrases. + * + * This takes two tokens, and, if they are the right type, + * merges their values together. Then checks to see if the + * new value is special using the PHRASES mapping. + * + * Example: "UNION" + "ALL" ==> "UNION ALL" + * + * C Security Notes: this is safe to use C-strings (null-terminated) + * since the types involved by definition do not have embedded nulls + * (e.g. there is no keyword with embedded null) + * + * Porting Notes: since this is C, it's oddly complicated. + * This is just: multikeywords[token.value + ' ' + token2.value] + * + */ +static int syntax_merge_words(struct libinjection_sqli_state * sf,stoken_t * a, stoken_t * b) +{ + size_t sz1; + size_t sz2; + size_t sz3; + char tmp[LIBINJECTION_SQLI_TOKEN_SIZE]; + char ch; + + /* first token is of right type? */ + if (! + (a->type == TYPE_KEYWORD || + a->type == TYPE_BAREWORD || + a->type == TYPE_OPERATOR || + a->type == TYPE_UNION || + a->type == TYPE_FUNCTION || + a->type == TYPE_EXPRESSION || + a->type == TYPE_TSQL || + a->type == TYPE_SQLTYPE)) { + return FALSE; + } + + if (! + (b->type == TYPE_KEYWORD || + b->type == TYPE_BAREWORD || + b->type == TYPE_OPERATOR || + b->type == TYPE_UNION || + b->type == TYPE_FUNCTION || + b->type == TYPE_EXPRESSION || + b->type == TYPE_TSQL || + b->type == TYPE_SQLTYPE || + b->type == TYPE_LOGIC_OPERATOR)) { + return FALSE; + } + + sz1 = a->len; + sz2 = b->len; + sz3 = sz1 + sz2 + 1; /* +1 for space in the middle */ + if (sz3 >= LIBINJECTION_SQLI_TOKEN_SIZE) { /* make sure there is room for ending null */ + return FALSE; + } + /* + * oddly annoying last.val + ' ' + current.val + */ + memcpy(tmp, a->val, sz1); + tmp[sz1] = ' '; + memcpy(tmp + sz1 + 1, b->val, sz2); + tmp[sz3] = CHAR_NULL; + ch = sf->lookup(sf, LOOKUP_WORD, tmp, sz3); + + if (ch != CHAR_NULL) { + st_assign(a, ch, a->pos, sz3, tmp); + return TRUE; + } else { + return FALSE; + } +} + +int libinjection_sqli_fold(struct libinjection_sqli_state * sf) +{ + stoken_t last_comment; + + /* POS is the position of where the NEXT token goes */ + size_t pos = 0; + + /* LEFT is a count of how many tokens that are already + folded or processed (i.e. part of the fingerprint) */ + size_t left = 0; + + int more = 1; + + st_clear(&last_comment); + + /* Skip all initial comments, right-parens ( and unary operators + * + */ + sf->current = &(sf->tokenvec[0]); + while (more) { + more = libinjection_sqli_tokenize(sf); + if ( ! (sf->current->type == TYPE_COMMENT || + sf->current->type == TYPE_LEFTPARENS || + sf->current->type == TYPE_SQLTYPE || + st_is_unary_op(sf->current))) { + break; + } + } + + if (! more) { + /* If input was only comments, unary or (, then exit */ + return 0; + } else { + /* it's some other token */ + pos += 1; + } + + while (1) { + FOLD_DEBUG; + + /* do we have all the max number of tokens? if so do + * some special cases for 5 tokens + */ + if (pos >= LIBINJECTION_SQLI_MAX_TOKENS) { + if ( + ( + sf->tokenvec[0].type == TYPE_NUMBER && + (sf->tokenvec[1].type == TYPE_OPERATOR || sf->tokenvec[1].type == TYPE_COMMA) && + sf->tokenvec[2].type == TYPE_LEFTPARENS && + sf->tokenvec[3].type == TYPE_NUMBER && + sf->tokenvec[4].type == TYPE_RIGHTPARENS + ) || + ( + sf->tokenvec[0].type == TYPE_BAREWORD && + sf->tokenvec[1].type == TYPE_OPERATOR && + sf->tokenvec[2].type == TYPE_LEFTPARENS && + (sf->tokenvec[3].type == TYPE_BAREWORD || sf->tokenvec[3].type == TYPE_NUMBER) && + sf->tokenvec[4].type == TYPE_RIGHTPARENS + ) || + ( + sf->tokenvec[0].type == TYPE_NUMBER && + sf->tokenvec[1].type == TYPE_RIGHTPARENS && + sf->tokenvec[2].type == TYPE_COMMA && + sf->tokenvec[3].type == TYPE_LEFTPARENS && + sf->tokenvec[4].type == TYPE_NUMBER + ) || + ( + sf->tokenvec[0].type == TYPE_BAREWORD && + sf->tokenvec[1].type == TYPE_RIGHTPARENS && + sf->tokenvec[2].type == TYPE_OPERATOR && + sf->tokenvec[3].type == TYPE_LEFTPARENS && + sf->tokenvec[4].type == TYPE_BAREWORD + ) + ) + { + if (pos > LIBINJECTION_SQLI_MAX_TOKENS) { + st_copy(&(sf->tokenvec[1]), &(sf->tokenvec[LIBINJECTION_SQLI_MAX_TOKENS])); + pos = 2; + left = 0; + } else { + pos = 1; + left = 0; + } + } + } + + if (! more || left >= LIBINJECTION_SQLI_MAX_TOKENS) { + left = pos; + break; + } + + /* get up to two tokens */ + while (more && pos <= LIBINJECTION_SQLI_MAX_TOKENS && (pos - left) < 2) { + sf->current = &(sf->tokenvec[pos]); + more = libinjection_sqli_tokenize(sf); + if (more) { + if (sf->current->type == TYPE_COMMENT) { + st_copy(&last_comment, sf->current); + } else { + last_comment.type = CHAR_NULL; + pos += 1; + } + } + } + FOLD_DEBUG; + /* did we get 2 tokens? if not then we are done */ + if (pos - left < 2) { + left = pos; + continue; + } + + /* FOLD: "ss" -> "s" + * "foo" "bar" is valid SQL + * just ignore second string + */ + if (sf->tokenvec[left].type == TYPE_STRING && sf->tokenvec[left+1].type == TYPE_STRING) { + pos -= 1; + sf->stats_folds += 1; + continue; + } else if (sf->tokenvec[left].type == TYPE_SEMICOLON && sf->tokenvec[left+1].type == TYPE_SEMICOLON) { + /* not sure how various engines handle + * 'select 1;;drop table foo' or + * 'select 1; /x foo x/; drop table foo' + * to prevent surprises, just fold away repeated semicolons + */ + pos -= 1; + sf->stats_folds += 1; + continue; + } else if ((sf->tokenvec[left].type == TYPE_OPERATOR || + sf->tokenvec[left].type == TYPE_LOGIC_OPERATOR) && + (st_is_unary_op(&sf->tokenvec[left+1]) || + sf->tokenvec[left+1].type == TYPE_SQLTYPE)) { + pos -= 1; + sf->stats_folds += 1; + left = 0; + continue; + } else if (sf->tokenvec[left].type == TYPE_LEFTPARENS && + st_is_unary_op(&sf->tokenvec[left+1])) { + pos -= 1; + sf->stats_folds += 1; + if (left > 0) { + left -= 1; + } + continue; + } else if (syntax_merge_words(sf, &sf->tokenvec[left], &sf->tokenvec[left+1])) { + pos -= 1; + sf->stats_folds += 1; + if (left > 0) { + left -= 1; + } + continue; + } else if (sf->tokenvec[left].type == TYPE_SEMICOLON && + sf->tokenvec[left+1].type == TYPE_FUNCTION && + (sf->tokenvec[left+1].val[0] == 'I' || + sf->tokenvec[left+1].val[0] == 'i' ) && + (sf->tokenvec[left+1].val[1] == 'F' || + sf->tokenvec[left+1].val[1] == 'f' )) { + /* IF is normally a function, except in Transact-SQL where it can be used as a + * standalone control flow operator, e.g. ; IF 1=1 ... + * if found after a semicolon, convert from 'f' type to 'T' type + */ + sf->tokenvec[left+1].type = TYPE_TSQL; + /* left += 2; */ + continue; /* reparse everything, but we probably can advance left, and pos */ + } else if ((sf->tokenvec[left].type == TYPE_BAREWORD || sf->tokenvec[left].type == TYPE_VARIABLE) && + sf->tokenvec[left+1].type == TYPE_LEFTPARENS && ( + /* TSQL functions but common enough to be column names */ + cstrcasecmp("USER_ID", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("USER_NAME", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + + /* Function in MYSQL */ + cstrcasecmp("DATABASE", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("PASSWORD", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("USER", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + + /* Mysql words that act as a variable and are a function */ + + /* TSQL current_users is fake-variable */ + /* http://msdn.microsoft.com/en-us/library/ms176050.aspx */ + cstrcasecmp("CURRENT_USER", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("CURRENT_DATE", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("CURRENT_TIME", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("CURRENT_TIMESTAMP", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("LOCALTIME", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("LOCALTIMESTAMP", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 + )) { + + /* pos is the same + * other conversions need to go here... for instance + * password CAN be a function, coalesce CAN be a function + */ + sf->tokenvec[left].type = TYPE_FUNCTION; + continue; + } else if (sf->tokenvec[left].type == TYPE_KEYWORD && ( + cstrcasecmp("IN", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("NOT IN", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 + )) { + + if (sf->tokenvec[left+1].type == TYPE_LEFTPARENS) { + /* got .... IN ( ... (or 'NOT IN') + * it's an operator + */ + sf->tokenvec[left].type = TYPE_OPERATOR; + } else { + /* + * it's a nothing + */ + sf->tokenvec[left].type = TYPE_BAREWORD; + } + + /* "IN" can be used as "IN BOOLEAN MODE" for mysql + * in which case merging of words can be done later + * other wise it acts as an equality operator __ IN (values..) + * + * here we got "IN" "(" so it's an operator. + * also back track to handle "NOT IN" + * might need to do the same with like + * two use cases "foo" LIKE "BAR" (normal operator) + * "foo" = LIKE(1,2) + */ + continue; + } else if ((sf->tokenvec[left].type == TYPE_OPERATOR) && ( + cstrcasecmp("LIKE", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0 || + cstrcasecmp("NOT LIKE", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0)) { + if (sf->tokenvec[left+1].type == TYPE_LEFTPARENS) { + /* SELECT LIKE(... + * it's a function + */ + sf->tokenvec[left].type = TYPE_FUNCTION; + } + } else if (sf->tokenvec[left].type == TYPE_SQLTYPE && + (sf->tokenvec[left+1].type == TYPE_BAREWORD || + sf->tokenvec[left+1].type == TYPE_NUMBER || + sf->tokenvec[left+1].type == TYPE_SQLTYPE || + sf->tokenvec[left+1].type == TYPE_LEFTPARENS || + sf->tokenvec[left+1].type == TYPE_FUNCTION || + sf->tokenvec[left+1].type == TYPE_VARIABLE || + sf->tokenvec[left+1].type == TYPE_STRING)) { + st_copy(&sf->tokenvec[left], &sf->tokenvec[left+1]); + pos -= 1; + sf->stats_folds += 1; + left = 0; + continue; + } else if (sf->tokenvec[left].type == TYPE_COLLATE && + sf->tokenvec[left+1].type == TYPE_BAREWORD) { + /* + * there are too many collation types.. so if the bareword has a "_" + * then it's TYPE_SQLTYPE + */ + if (strchr(sf->tokenvec[left+1].val, '_') != NULL) { + sf->tokenvec[left+1].type = TYPE_SQLTYPE; + left = 0; + } + } else if (sf->tokenvec[left].type == TYPE_BACKSLASH) { + if (st_is_arithmetic_op(&(sf->tokenvec[left+1]))) { + /* very weird case in TSQL where '\%1' is parsed as '0 % 1', etc */ + sf->tokenvec[left].type = TYPE_NUMBER; + } else { + /* just ignore it.. Again T-SQL seems to parse \1 as "1" */ + st_copy(&sf->tokenvec[left], &sf->tokenvec[left+1]); + pos -= 1; + sf->stats_folds += 1; + } + left = 0; + continue; + } else if (sf->tokenvec[left].type == TYPE_LEFTPARENS && + sf->tokenvec[left+1].type == TYPE_LEFTPARENS) { + pos -= 1; + left = 0; + sf->stats_folds += 1; + continue; + } else if (sf->tokenvec[left].type == TYPE_RIGHTPARENS && + sf->tokenvec[left+1].type == TYPE_RIGHTPARENS) { + pos -= 1; + left = 0; + sf->stats_folds += 1; + continue; + } else if (sf->tokenvec[left].type == TYPE_LEFTBRACE && + sf->tokenvec[left+1].type == TYPE_BAREWORD) { + + /* + * MySQL Degenerate case -- + * + * select { ``.``.id }; -- valid !!! + * select { ``.``.``.id }; -- invalid + * select ``.``.id; -- invalid + * select { ``.id }; -- invalid + * + * so it appears {``.``.id} is a magic case + * I suspect this is "current database, current table, field id" + * + * The folding code can't look at more than 3 tokens, and + * I don't want to make two passes. + * + * Since "{ ``" so rare, we are just going to blacklist it. + * + * Highly likely this will need revisiting! + * + * CREDIT @rsalgado 2013-11-25 + */ + if (sf->tokenvec[left+1].len == 0) { + sf->tokenvec[left+1].type = TYPE_EVIL; + return (int)(left+2); + } + /* weird ODBC / MYSQL {foo expr} --> expr + * but for this rule we just strip away the "{ foo" part + */ + left = 0; + pos -= 2; + sf->stats_folds += 2; + continue; + } else if (sf->tokenvec[left+1].type == TYPE_RIGHTBRACE) { + pos -= 1; + left = 0; + sf->stats_folds += 1; + continue; + } + + /* all cases of handing 2 tokens is done + and nothing matched. Get one more token + */ + FOLD_DEBUG; + while (more && pos <= LIBINJECTION_SQLI_MAX_TOKENS && pos - left < 3) { + sf->current = &(sf->tokenvec[pos]); + more = libinjection_sqli_tokenize(sf); + if (more) { + if (sf->current->type == TYPE_COMMENT) { + st_copy(&last_comment, sf->current); + } else { + last_comment.type = CHAR_NULL; + pos += 1; + } + } + } + + /* do we have three tokens? If not then we are done */ + if (pos -left < 3) { + left = pos; + continue; + } + + /* + * now look for three token folding + */ + if (sf->tokenvec[left].type == TYPE_NUMBER && + sf->tokenvec[left+1].type == TYPE_OPERATOR && + sf->tokenvec[left+2].type == TYPE_NUMBER) { + pos -= 2; + left = 0; + continue; + } else if (sf->tokenvec[left].type == TYPE_OPERATOR && + sf->tokenvec[left+1].type != TYPE_LEFTPARENS && + sf->tokenvec[left+2].type == TYPE_OPERATOR) { + left = 0; + pos -= 2; + continue; + } else if (sf->tokenvec[left].type == TYPE_LOGIC_OPERATOR && + sf->tokenvec[left+2].type == TYPE_LOGIC_OPERATOR) { + pos -= 2; + left = 0; + continue; + } else if (sf->tokenvec[left].type == TYPE_VARIABLE && + sf->tokenvec[left+1].type == TYPE_OPERATOR && + (sf->tokenvec[left+2].type == TYPE_VARIABLE || + sf->tokenvec[left+2].type == TYPE_NUMBER || + sf->tokenvec[left+2].type == TYPE_BAREWORD)) { + pos -= 2; + left = 0; + continue; + } else if ((sf->tokenvec[left].type == TYPE_BAREWORD || + sf->tokenvec[left].type == TYPE_NUMBER ) && + sf->tokenvec[left+1].type == TYPE_OPERATOR && + (sf->tokenvec[left+2].type == TYPE_NUMBER || + sf->tokenvec[left+2].type == TYPE_BAREWORD)) { + pos -= 2; + left = 0; + continue; + } else if ((sf->tokenvec[left].type == TYPE_BAREWORD || + sf->tokenvec[left].type == TYPE_NUMBER || + sf->tokenvec[left].type == TYPE_VARIABLE || + sf->tokenvec[left].type == TYPE_STRING) && + sf->tokenvec[left+1].type == TYPE_OPERATOR && + streq(sf->tokenvec[left+1].val, "::") && + sf->tokenvec[left+2].type == TYPE_SQLTYPE) { + pos -= 2; + left = 0; + sf->stats_folds += 2; + continue; + } else if ((sf->tokenvec[left].type == TYPE_BAREWORD || + sf->tokenvec[left].type == TYPE_NUMBER || + sf->tokenvec[left].type == TYPE_STRING || + sf->tokenvec[left].type == TYPE_VARIABLE) && + sf->tokenvec[left+1].type == TYPE_COMMA && + (sf->tokenvec[left+2].type == TYPE_NUMBER || + sf->tokenvec[left+2].type == TYPE_BAREWORD || + sf->tokenvec[left+2].type == TYPE_STRING || + sf->tokenvec[left+2].type == TYPE_VARIABLE)) { + pos -= 2; + left = 0; + continue; + } else if ((sf->tokenvec[left].type == TYPE_EXPRESSION || + sf->tokenvec[left].type == TYPE_GROUP || + sf->tokenvec[left].type == TYPE_COMMA) && + st_is_unary_op(&sf->tokenvec[left+1]) && + sf->tokenvec[left+2].type == TYPE_LEFTPARENS) { + /* got something like SELECT + (, LIMIT + ( + * remove unary operator + */ + st_copy(&sf->tokenvec[left+1], &sf->tokenvec[left+2]); + pos -= 1; + left = 0; + continue; + } else if ((sf->tokenvec[left].type == TYPE_KEYWORD || + sf->tokenvec[left].type == TYPE_EXPRESSION || + sf->tokenvec[left].type == TYPE_GROUP ) && + st_is_unary_op(&sf->tokenvec[left+1]) && + (sf->tokenvec[left+2].type == TYPE_NUMBER || + sf->tokenvec[left+2].type == TYPE_BAREWORD || + sf->tokenvec[left+2].type == TYPE_VARIABLE || + sf->tokenvec[left+2].type == TYPE_STRING || + sf->tokenvec[left+2].type == TYPE_FUNCTION )) { + /* remove unary operators + * select - 1 + */ + st_copy(&sf->tokenvec[left+1], &sf->tokenvec[left+2]); + pos -= 1; + left = 0; + continue; + } else if (sf->tokenvec[left].type == TYPE_COMMA && + st_is_unary_op(&sf->tokenvec[left+1]) && + (sf->tokenvec[left+2].type == TYPE_NUMBER || + sf->tokenvec[left+2].type == TYPE_BAREWORD || + sf->tokenvec[left+2].type == TYPE_VARIABLE || + sf->tokenvec[left+2].type == TYPE_STRING)) { + /* + * interesting case turn ", -1" ->> ",1" PLUS we need to back up + * one token if possible to see if more folding can be done + * "1,-1" --> "1" + */ + st_copy(&sf->tokenvec[left+1], &sf->tokenvec[left+2]); + left = 0; + /* pos is >= 3 so this is safe */ + assert(pos >= 3); + pos -= 3; + continue; + } else if (sf->tokenvec[left].type == TYPE_COMMA && + st_is_unary_op(&sf->tokenvec[left+1]) && + sf->tokenvec[left+2].type == TYPE_FUNCTION) { + + /* Separate case from above since you end up with + * 1,-sin(1) --> 1 (1) + * Here, just do + * 1,-sin(1) --> 1,sin(1) + * just remove unary operator + */ + st_copy(&sf->tokenvec[left+1], &sf->tokenvec[left+2]); + pos -= 1; + left = 0; + continue; + } else if ((sf->tokenvec[left].type == TYPE_BAREWORD) && + (sf->tokenvec[left+1].type == TYPE_DOT) && + (sf->tokenvec[left+2].type == TYPE_BAREWORD)) { + /* ignore the '.n' + * typically is this databasename.table + */ + assert(pos >= 3); + pos -= 2; + left = 0; + continue; + } else if ((sf->tokenvec[left].type == TYPE_EXPRESSION) && + (sf->tokenvec[left+1].type == TYPE_DOT) && + (sf->tokenvec[left+2].type == TYPE_BAREWORD)) { + /* select . `foo` --> select `foo` */ + st_copy(&sf->tokenvec[left+1], &sf->tokenvec[left+2]); + pos -= 1; + left = 0; + continue; + } else if ((sf->tokenvec[left].type == TYPE_FUNCTION) && + (sf->tokenvec[left+1].type == TYPE_LEFTPARENS) && + (sf->tokenvec[left+2].type != TYPE_RIGHTPARENS)) { + /* + * whats going on here + * Some SQL functions like USER() have 0 args + * if we get User(foo), then User is not a function + * This should be expanded since it eliminated a lot of false + * positives. + */ + if (cstrcasecmp("USER", sf->tokenvec[left].val, sf->tokenvec[left].len) == 0) { + sf->tokenvec[left].type = TYPE_BAREWORD; + } + } + + /* no folding -- assume left-most token is + is good, now use the existing 2 tokens -- + do not get another + */ + + left += 1; + + } /* while(1) */ + + /* if we have 4 or less tokens, and we had a comment token + * at the end, add it back + */ + + if (left < LIBINJECTION_SQLI_MAX_TOKENS && last_comment.type == TYPE_COMMENT) { + st_copy(&sf->tokenvec[left], &last_comment); + left += 1; + } + + /* sometimes we grab a 6th token to help + determine the type of token 5. + */ + if (left > LIBINJECTION_SQLI_MAX_TOKENS) { + left = LIBINJECTION_SQLI_MAX_TOKENS; + } + + return (int)left; +} + +/* secondary api: detects SQLi in a string, GIVEN a context. + * + * A context can be: + * * CHAR_NULL (\0), process as is + * * CHAR_SINGLE ('), process pretending input started with a + * single quote. + * * CHAR_DOUBLE ("), process pretending input started with a + * double quote. + * + */ +const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state * sql_state, int flags) +{ + int i; + int tlen = 0; + + libinjection_sqli_reset(sql_state, flags); + + tlen = libinjection_sqli_fold(sql_state); + + /* Check for magic PHP backquote comment + * If: + * * last token is of type "bareword" + * * And is quoted in a backtick + * * And isn't closed + * * And it's empty? + * Then convert it to comment + */ + if (tlen > 2 && + sql_state->tokenvec[tlen-1].type == TYPE_BAREWORD && + sql_state->tokenvec[tlen-1].str_open == CHAR_TICK && + sql_state->tokenvec[tlen-1].len == 0 && + sql_state->tokenvec[tlen-1].str_close == CHAR_NULL) { + sql_state->tokenvec[tlen-1].type = TYPE_COMMENT; + } + + for (i = 0; i < tlen; ++i) { + sql_state->fingerprint[i] = sql_state->tokenvec[i].type; + } + + /* + * make the fingerprint pattern a c-string (null delimited) + */ + sql_state->fingerprint[tlen] = CHAR_NULL; + + /* + * check for 'X' in pattern, and then + * clear out all tokens + * + * this means parsing could not be done + * accurately due to pgsql's double comments + * or other syntax that isn't consistent. + * Should be very rare false positive + */ + if (strchr(sql_state->fingerprint, TYPE_EVIL)) { + /* needed for SWIG */ + memset((void*)sql_state->fingerprint, 0, LIBINJECTION_SQLI_MAX_TOKENS + 1); + memset((void*)sql_state->tokenvec[0].val, 0, LIBINJECTION_SQLI_TOKEN_SIZE); + + sql_state->fingerprint[0] = TYPE_EVIL; + + sql_state->tokenvec[0].type = TYPE_EVIL; + sql_state->tokenvec[0].val[0] = TYPE_EVIL; + sql_state->tokenvec[1].type = CHAR_NULL; + } + + + return sql_state->fingerprint; +} + +int libinjection_sqli_check_fingerprint(struct libinjection_sqli_state* sql_state) +{ + return libinjection_sqli_blacklist(sql_state) && + libinjection_sqli_not_whitelist(sql_state); +} + +char libinjection_sqli_lookup_word(struct libinjection_sqli_state *sql_state, int lookup_type, + const char* str, size_t len) +{ + if (lookup_type == LOOKUP_FINGERPRINT) { + return libinjection_sqli_check_fingerprint(sql_state) ? 'X' : '\0'; + } else { + return bsearch_keyword_type(str, len, sql_keywords, sql_keywords_sz); + } +} + +int libinjection_sqli_blacklist(struct libinjection_sqli_state* sql_state) +{ + /* + * use minimum of 8 bytes to make sure gcc -fstack-protector + * works correctly + */ + char fp2[8]; + char ch; + size_t i; + size_t len = strlen(sql_state->fingerprint); + int patmatch; + + if (len < 1) { + sql_state->reason = __LINE__; + return FALSE; + } + + /* + to keep everything compatible, convert the + v0 fingerprint pattern to v1 + v0: up to 5 chars, mixed case + v1: 1 char is '0', up to 5 more chars, upper case + */ + + fp2[0] = '0'; + for (i = 0; i < len; ++i) { + ch = sql_state->fingerprint[i]; + if (ch >= 'a' && ch <= 'z') { + ch -= 0x20; + } + fp2[i+1] = ch; + } + fp2[i+1] = '\0'; + + patmatch = is_keyword(fp2, len + 1) == TYPE_FINGERPRINT; + + /* + * No match. + * + * Set sql_state->reason to current line number + * only for debugging purposes. + */ + if (!patmatch) { + sql_state->reason = __LINE__; + return FALSE; + } + + return TRUE; +} + +/* + * return TRUE if SQLi, false is benign + */ +int libinjection_sqli_not_whitelist(struct libinjection_sqli_state* sql_state) +{ + /* + * We assume we got a SQLi match + * This next part just helps reduce false positives. + * + */ + char ch; + size_t tlen = strlen(sql_state->fingerprint); + + if (tlen > 1 && sql_state->fingerprint[tlen-1] == TYPE_COMMENT) { + /* + * if ending comment is contains 'sp_password' then it's SQLi! + * MS Audit log apparently ignores anything with + * 'sp_password' in it. Unable to find primary reference to + * this "feature" of SQL Server but seems to be known SQLi + * technique + */ + if (my_memmem(sql_state->s, sql_state->slen, + "sp_password", strlen("sp_password"))) { + sql_state->reason = __LINE__; + return TRUE; + } + } + + switch (tlen) { + case 2:{ + /* + * case 2 are "very small SQLi" which make them + * hard to tell from normal input... + */ + + if (sql_state->fingerprint[1] == TYPE_UNION) { + if (sql_state->stats_tokens == 2) { + /* not sure why but 1U comes up in SQLi attack + * likely part of parameter splitting/etc. + * lots of reasons why "1 union" might be normal + * input, so beep only if other SQLi things are present + */ + /* it really is a number and 'union' + * other wise it has folding or comments + */ + sql_state->reason = __LINE__; + return FALSE; + } else { + sql_state->reason = __LINE__; + return TRUE; + } + } + /* + * if 'comment' is '#' ignore.. too many FP + */ + if (sql_state->tokenvec[1].val[0] == '#') { + sql_state->reason = __LINE__; + return FALSE; + } + + /* + * for fingerprint like 'nc', only comments of /x are treated + * as SQL... ending comments of "--" and "#" are not SQLi + */ + if (sql_state->tokenvec[0].type == TYPE_BAREWORD && + sql_state->tokenvec[1].type == TYPE_COMMENT && + sql_state->tokenvec[1].val[0] != '/') { + sql_state->reason = __LINE__; + return FALSE; + } + + /* + * if '1c' ends with '/x' then it's SQLi + */ + if (sql_state->tokenvec[0].type == TYPE_NUMBER && + sql_state->tokenvec[1].type == TYPE_COMMENT && + sql_state->tokenvec[1].val[0] == '/') { + return TRUE; + } + + /** + * there are some odd base64-looking query string values + * 1234-ABCDEFEhfhihwuefi-- + * which evaluate to "1c"... these are not SQLi + * but 1234-- probably is. + * Make sure the "1" in "1c" is actually a true decimal number + * + * Need to check -original- string since the folding step + * may have merged tokens, e.g. "1+FOO" is folded into "1" + * + * Note: evasion: 1*1-- + */ + if (sql_state->tokenvec[0].type == TYPE_NUMBER && + sql_state->tokenvec[1].type == TYPE_COMMENT) { + if (sql_state->stats_tokens > 2) { + /* we have some folding going on, highly likely SQLi */ + sql_state->reason = __LINE__; + return TRUE; + } + /* + * we check that next character after the number is either whitespace, + * or '/' or a '-' ==> SQLi. + */ + ch = sql_state->s[sql_state->tokenvec[0].len]; + if ( ch <= 32 ) { + /* next char was whitespace,e.g. "1234 --" + * this isn't exactly correct.. ideally we should skip over all whitespace + * but this seems to be ok for now + */ + return TRUE; + } + if (ch == '/' && sql_state->s[sql_state->tokenvec[0].len + 1] == '*') { + return TRUE; + } + if (ch == '-' && sql_state->s[sql_state->tokenvec[0].len + 1] == '-') { + return TRUE; + } + + sql_state->reason = __LINE__; + return FALSE; + } + + /* + * detect obvious SQLi scans.. many people put '--' in plain text + * so only detect if input ends with '--', e.g. 1-- but not 1-- foo + */ + if ((sql_state->tokenvec[1].len > 2) + && sql_state->tokenvec[1].val[0] == '-') { + sql_state->reason = __LINE__; + return FALSE; + } + + break; + } /* case 2 */ + case 3:{ + /* + * ...foo' + 'bar... + * no opening quote, no closing quote + * and each string has data + */ + + if (streq(sql_state->fingerprint, "sos") + || streq(sql_state->fingerprint, "s&s")) { + + if ((sql_state->tokenvec[0].str_open == CHAR_NULL) + && (sql_state->tokenvec[2].str_close == CHAR_NULL) + && (sql_state->tokenvec[0].str_close == sql_state->tokenvec[2].str_open)) { + /* + * if ....foo" + "bar.... + */ + sql_state->reason = __LINE__; + return TRUE; + } + if (sql_state->stats_tokens == 3) { + sql_state->reason = __LINE__; + return FALSE; + } + + /* + * not SQLi + */ + sql_state->reason = __LINE__; + return FALSE; + } else if (streq(sql_state->fingerprint, "s&n") || + streq(sql_state->fingerprint, "n&1") || + streq(sql_state->fingerprint, "1&1") || + streq(sql_state->fingerprint, "1&v") || + streq(sql_state->fingerprint, "1&s")) { + /* 'sexy and 17' not SQLi + * 'sexy and 17<18' SQLi + */ + if (sql_state->stats_tokens == 3) { + sql_state->reason = __LINE__; + return FALSE; + } + } else if (sql_state->tokenvec[1].type == TYPE_KEYWORD) { + if ((sql_state->tokenvec[1].len < 5) || + cstrcasecmp("INTO", sql_state->tokenvec[1].val, 4)) { + /* if it's not "INTO OUTFILE", or "INTO DUMPFILE" (MySQL) + * then treat as safe + */ + sql_state->reason = __LINE__; + return FALSE; + } + } + break; + } /* case 3 */ + case 4: + case 5: { + /* nothing right now */ + break; + } /* case 5 */ + } /* end switch */ + + return TRUE; +} + +/** Main API, detects SQLi in an input. + * + * + */ +static int reparse_as_mysql(struct libinjection_sqli_state * sql_state) +{ + return sql_state->stats_comment_ddx || + sql_state->stats_comment_hash; +} + +/* + * This function is mostly use with SWIG + */ +struct libinjection_sqli_token* +libinjection_sqli_get_token(struct libinjection_sqli_state * sql_state, int i) +{ + if (i < 0 || i > (int)LIBINJECTION_SQLI_MAX_TOKENS) { + return NULL; + } + return &(sql_state->tokenvec[i]); +} + +int libinjection_is_sqli(struct libinjection_sqli_state * sql_state) +{ + const char *s = sql_state->s; + size_t slen = sql_state->slen; + + /* + * no input? not SQLi + */ + if (slen == 0) { + return FALSE; + } + + /* + * test input "as-is" + */ + libinjection_sqli_fingerprint(sql_state, FLAG_QUOTE_NONE | FLAG_SQL_ANSI); + if (sql_state->lookup(sql_state, LOOKUP_FINGERPRINT, + sql_state->fingerprint, strlen(sql_state->fingerprint))) { + return TRUE; + } else if (reparse_as_mysql(sql_state)) { + libinjection_sqli_fingerprint(sql_state, FLAG_QUOTE_NONE | FLAG_SQL_MYSQL); + if (sql_state->lookup(sql_state, LOOKUP_FINGERPRINT, + sql_state->fingerprint, strlen(sql_state->fingerprint))) { + return TRUE; + } + } + + /* + * if input has a single_quote, then + * test as if input was actually ' + * example: if input if "1' = 1", then pretend it's + * "'1' = 1" + * Porting Notes: example the same as doing + * is_string_sqli(sql_state, "'" + s, slen+1, NULL, fn, arg) + * + */ + if (memchr(s, CHAR_SINGLE, slen)) { + libinjection_sqli_fingerprint(sql_state, FLAG_QUOTE_SINGLE | FLAG_SQL_ANSI); + if (sql_state->lookup(sql_state, LOOKUP_FINGERPRINT, + sql_state->fingerprint, strlen(sql_state->fingerprint))) { + return TRUE; + } else if (reparse_as_mysql(sql_state)) { + libinjection_sqli_fingerprint(sql_state, FLAG_QUOTE_SINGLE | FLAG_SQL_MYSQL); + if (sql_state->lookup(sql_state, LOOKUP_FINGERPRINT, + sql_state->fingerprint, strlen(sql_state->fingerprint))) { + return TRUE; + } + } + } + + /* + * same as above but with a double-quote " + */ + if (memchr(s, CHAR_DOUBLE, slen)) { + libinjection_sqli_fingerprint(sql_state, FLAG_QUOTE_DOUBLE | FLAG_SQL_MYSQL); + if (sql_state->lookup(sql_state, LOOKUP_FINGERPRINT, + sql_state->fingerprint, strlen(sql_state->fingerprint))) { + return TRUE; + } + } + + /* + * Hurray, input is not SQLi + */ + return FALSE; +} + +int libinjection_sqli(const char* input, size_t slen, char fingerprint[]) +{ + int issqli; + struct libinjection_sqli_state state; + + libinjection_sqli_init(&state, input, slen, 0); + issqli = libinjection_is_sqli(&state); + if (issqli) { + strcpy(fingerprint, state.fingerprint); + } else { + fingerprint[0] = '\0'; + } + return issqli; +} diff --git a/src/lib/third_party/src/libinjection_xss.c b/src/lib/third_party/src/libinjection_xss.c new file mode 100644 index 0000000..f0df4d8 --- /dev/null +++ b/src/lib/third_party/src/libinjection_xss.c @@ -0,0 +1,532 @@ + +#include "libinjection.h" +#include "libinjection_xss.h" +#include "libinjection_html5.h" + +#include +#include + +typedef enum attribute { + TYPE_NONE + , TYPE_BLACK /* ban always */ + , TYPE_ATTR_URL /* attribute value takes a URL-like object */ + , TYPE_STYLE + , TYPE_ATTR_INDIRECT /* attribute *name* is given in *value* */ +} attribute_t; + + +static attribute_t is_black_attr(const char* s, size_t len); +static int is_black_tag(const char* s, size_t len); +static int is_black_url(const char* s, size_t len); +static int cstrcasecmp_with_null(const char *a, const char *b, size_t n); +static int html_decode_char_at(const char* src, size_t len, size_t* consumed); +static int htmlencode_startswith(const char* prefix, const char *src, size_t n); + + +typedef struct stringtype { + const char* name; + attribute_t atype; +} stringtype_t; + + +static const int gsHexDecodeMap[256] = { + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 256, 256, + 256, 256, 256, 256, 256, 10, 11, 12, 13, 14, 15, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 10, 11, 12, 13, 14, 15, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, + 256, 256, 256, 256 +}; + +static int html_decode_char_at(const char* src, size_t len, size_t* consumed) +{ + int val = 0; + size_t i; + int ch; + + if (len == 0 || src == NULL) { + *consumed = 0; + return -1; + } + + *consumed = 1; + if (*src != '&' || len < 2) { + return (unsigned char)(*src); + } + + + if (*(src+1) != '#') { + /* normally this would be for named entities + * but for this case we don't actually care + */ + return '&'; + } + + if (*(src+2) == 'x' || *(src+2) == 'X') { + ch = (unsigned char) (*(src+3)); + ch = gsHexDecodeMap[ch]; + if (ch == 256) { + /* degenerate case '&#[?]' */ + return '&'; + } + val = ch; + i = 4; + while (i < len) { + ch = (unsigned char) src[i]; + if (ch == ';') { + *consumed = i + 1; + return val; + } + ch = gsHexDecodeMap[ch]; + if (ch == 256) { + *consumed = i; + return val; + } + val = (val * 16) + ch; + if (val > 0x1000FF) { + return '&'; + } + ++i; + } + *consumed = i; + return val; + } else { + i = 2; + ch = (unsigned char) src[i]; + if (ch < '0' || ch > '9') { + return '&'; + } + val = ch - '0'; + i += 1; + while (i < len) { + ch = (unsigned char) src[i]; + if (ch == ';') { + *consumed = i + 1; + return val; + } + if (ch < '0' || ch > '9') { + *consumed = i; + return val; + } + val = (val * 10) + (ch - '0'); + if (val > 0x1000FF) { + return '&'; + } + ++i; + } + *consumed = i; + return val; + } +} + + +/* + * view-source: + * data: + * javascript: + */ +static stringtype_t BLACKATTR[] = { + { "ACTION", TYPE_ATTR_URL } /* form */ + , { "ATTRIBUTENAME", TYPE_ATTR_INDIRECT } /* SVG allow indirection of attribute names */ + , { "BY", TYPE_ATTR_URL } /* SVG */ + , { "BACKGROUND", TYPE_ATTR_URL } /* IE6, O11 */ + , { "DATAFORMATAS", TYPE_BLACK } /* IE */ + , { "DATASRC", TYPE_BLACK } /* IE */ + , { "DYNSRC", TYPE_ATTR_URL } /* Obsolete img attribute */ + , { "FILTER", TYPE_STYLE } /* Opera, SVG inline style */ + , { "FORMACTION", TYPE_ATTR_URL } /* HTML 5 */ + , { "FOLDER", TYPE_ATTR_URL } /* Only on A tags, IE-only */ + , { "FROM", TYPE_ATTR_URL } /* SVG */ + , { "HANDLER", TYPE_ATTR_URL } /* SVG Tiny, Opera */ + , { "HREF", TYPE_ATTR_URL } + , { "LOWSRC", TYPE_ATTR_URL } /* Obsolete img attribute */ + , { "POSTER", TYPE_ATTR_URL } /* Opera 10,11 */ + , { "SRC", TYPE_ATTR_URL } + , { "STYLE", TYPE_STYLE } + , { "TO", TYPE_ATTR_URL } /* SVG */ + , { "VALUES", TYPE_ATTR_URL } /* SVG */ + , { "XLINK:HREF", TYPE_ATTR_URL } + , { NULL, TYPE_NONE } +}; + +/* xmlns */ +/* `xml-stylesheet` > , */ + +/* + static const char* BLACKATTR[] = { + "ATTRIBUTENAME", + "BACKGROUND", + "DATAFORMATAS", + "HREF", + "SCROLL", + "SRC", + "STYLE", + "SRCDOC", + NULL + }; +*/ + +static const char* BLACKTAG[] = { + "APPLET" + /* , "AUDIO" */ + , "BASE" + , "COMMENT" /* IE http://html5sec.org/#38 */ + , "EMBED" + /* , "FORM" */ + , "FRAME" + , "FRAMESET" + , "HANDLER" /* Opera SVG, effectively a script tag */ + , "IFRAME" + , "IMPORT" + , "ISINDEX" + , "LINK" + , "LISTENER" + /* , "MARQUEE" */ + , "META" + , "NOSCRIPT" + , "OBJECT" + , "SCRIPT" + , "STYLE" + /* , "VIDEO" */ + , "VMLFRAME" + , "XML" + , "XSS" + , NULL +}; + + +static int cstrcasecmp_with_null(const char *a, const char *b, size_t n) +{ + char ca; + char cb; + /* printf("Comparing to %s %.*s\n", a, (int)n, b); */ + while (n-- > 0) { + cb = *b++; + if (cb == '\0') continue; + + ca = *a++; + + if (cb >= 'a' && cb <= 'z') { + cb -= 0x20; + } + /* printf("Comparing %c vs %c with %d left\n", ca, cb, (int)n); */ + if (ca != cb) { + return 1; + } + } + + if (*a == 0) { + /* printf(" MATCH \n"); */ + return 0; + } else { + return 1; + } +} + +/* + * Does an HTML encoded binary string (const char*, length) start with + * a all uppercase c-string (null terminated), case insensitive! + * + * also ignore any embedded nulls in the HTML string! + * + * return 1 if match / starts with + * return 0 if not + */ +static int htmlencode_startswith(const char *a, const char *b, size_t n) +{ + size_t consumed; + int cb; + int first = 1; + /* printf("Comparing %s with %.*s\n", a,(int)n,b); */ + while (n > 0) { + if (*a == 0) { + /* printf("Match EOL!\n"); */ + return 1; + } + cb = html_decode_char_at(b, n, &consumed); + b += consumed; + n -= consumed; + + if (first && cb <= 32) { + /* ignore all leading whitespace and control characters */ + continue; + } + first = 0; + + if (cb == 0) { + /* always ignore null characters in user input */ + continue; + } + + if (cb == 10) { + /* always ignore vertical tab characters in user input */ + /* who allows this?? */ + continue; + } + + if (cb >= 'a' && cb <= 'z') { + /* upcase */ + cb -= 0x20; + } + + if (*a != (char) cb) { + /* printf(" %c != %c\n", *a, cb); */ + /* mismatch */ + return 0; + } + a++; + } + + return (*a == 0) ? 1 : 0; +} + +static int is_black_tag(const char* s, size_t len) +{ + const char** black; + + if (len < 3) { + return 0; + } + + black = BLACKTAG; + while (*black != NULL) { + if (cstrcasecmp_with_null(*black, s, len) == 0) { + /* printf("Got black tag %s\n", *black); */ + return 1; + } + black += 1; + } + + /* anything SVG related */ + if ((s[0] == 's' || s[0] == 'S') && + (s[1] == 'v' || s[1] == 'V') && + (s[2] == 'g' || s[2] == 'G')) { + /* printf("Got SVG tag \n"); */ + return 1; + } + + /* Anything XSL(t) related */ + if ((s[0] == 'x' || s[0] == 'X') && + (s[1] == 's' || s[1] == 'S') && + (s[2] == 'l' || s[2] == 'L')) { + /* printf("Got XSL tag\n"); */ + return 1; + } + + return 0; +} + +static attribute_t is_black_attr(const char* s, size_t len) +{ + stringtype_t* black; + + if (len < 2) { + return TYPE_NONE; + } + + if (len >= 5) { + /* JavaScript on.* */ + if ((s[0] == 'o' || s[0] == 'O') && (s[1] == 'n' || s[1] == 'N')) { + /* printf("Got JavaScript on- attribute name\n"); */ + return TYPE_BLACK; + } + + + + /* XMLNS can be used to create arbitrary tags */ + if (cstrcasecmp_with_null("XMLNS", s, 5) == 0 || cstrcasecmp_with_null("XLINK", s, 5) == 0) { + /* printf("Got XMLNS and XLINK tags\n"); */ + return TYPE_BLACK; + } + } + + black = BLACKATTR; + while (black->name != NULL) { + if (cstrcasecmp_with_null(black->name, s, len) == 0) { + /* printf("Got banned attribute name %s\n", black->name); */ + return black->atype; + } + black += 1; + } + + return TYPE_NONE; +} + +static int is_black_url(const char* s, size_t len) +{ + + static const char* data_url = "DATA"; + static const char* viewsource_url = "VIEW-SOURCE"; + + /* obsolete but interesting signal */ + static const char* vbscript_url = "VBSCRIPT"; + + /* covers JAVA, JAVASCRIPT, + colon */ + static const char* javascript_url = "JAVA"; + + /* skip whitespace */ + while (len > 0 && (*s <= 32 || *s >= 127)) { + /* + * HEY: this is a signed character. + * We are intentionally skipping high-bit characters too + * since they are not ASCII, and Opera sometimes uses UTF-8 whitespace. + * + * Also in EUC-JP some of the high bytes are just ignored. + */ + ++s; + --len; + } + + if (htmlencode_startswith(data_url, s, len)) { + return 1; + } + + if (htmlencode_startswith(viewsource_url, s, len)) { + return 1; + } + + if (htmlencode_startswith(javascript_url, s, len)) { + return 1; + } + + if (htmlencode_startswith(vbscript_url, s, len)) { + return 1; + } + return 0; +} + +int libinjection_is_xss(const char* s, size_t len, int flags) +{ + h5_state_t h5; + attribute_t attr = TYPE_NONE; + + libinjection_h5_init(&h5, s, len, (enum html5_flags) flags); + while (libinjection_h5_next(&h5)) { + if (h5.token_type != ATTR_VALUE) { + attr = TYPE_NONE; + } + + if (h5.token_type == DOCTYPE) { + return 1; + } else if (h5.token_type == TAG_NAME_OPEN) { + if (is_black_tag(h5.token_start, h5.token_len)) { + return 1; + } + } else if (h5.token_type == ATTR_NAME) { + attr = is_black_attr(h5.token_start, h5.token_len); + } else if (h5.token_type == ATTR_VALUE) { + /* + * IE6,7,8 parsing works a bit differently so + * a whole