summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md99
-rw-r--r--examples/c-analysed/c-analysed.c2130
-rw-r--r--examples/c-captured/c-captured.c1391
-rw-r--r--examples/c-collectd/README.md14
-rw-r--r--examples/c-collectd/c-collectd.c1588
-rw-r--r--examples/c-collectd/plugin_nDPIsrvd.conf14
-rwxr-xr-xexamples/c-collectd/rrdgraph.sh631
-rw-r--r--examples/c-collectd/www/dpi/bootstrap.css7
-rw-r--r--examples/c-collectd/www/dpi/bootstrap.js7
-rw-r--r--examples/c-collectd/www/dpi/categories.html198
-rw-r--r--examples/c-collectd/www/dpi/dashboard.css93
-rw-r--r--examples/c-collectd/www/dpi/detections.html198
-rw-r--r--examples/c-collectd/www/dpi/events.html198
-rw-r--r--examples/c-collectd/www/dpi/feather.js13
-rw-r--r--examples/c-collectd/www/dpi/flows.html179
-rw-r--r--examples/c-collectd/www/dpi/index.html425
-rw-r--r--examples/c-collectd/www/dpi/jquery-3.js4
-rw-r--r--examples/c-collectd/www/dpi/jsons.html198
-rw-r--r--examples/c-collectd/www/dpi/other.html217
-rw-r--r--examples/c-collectd/www/dpi/popper.js5
-rw-r--r--examples/c-collectd/www/dpi/risks.html198
-rw-r--r--examples/c-influxd/c-influxd.c1750
-rw-r--r--examples/c-influxd/grafana-dashboard-simple.json6468
-rw-r--r--examples/c-notifyd/c-notifyd.c668
-rw-r--r--examples/c-simple/c-simple.c273
m---------examples/cxx-graph0
m---------examples/js-rt-analyzer0
m---------examples/js-rt-analyzer-frontend0
-rw-r--r--examples/ndpid_grafana_example.pngbin0 -> 63737 bytes
-rw-r--r--examples/ndpid_install_and_run.gifbin0 -> 17122501 bytes
-rw-r--r--examples/py-flow-dashboard/assets/flow-dash.css3
-rwxr-xr-xexamples/py-flow-dashboard/flow-dash.py300
-rw-r--r--examples/py-flow-dashboard/plotly_dash.py415
-rw-r--r--examples/py-flow-dashboard/requirements.txt3
-rwxr-xr-xexamples/py-flow-info/flow-info.py641
-rwxr-xr-xexamples/py-flow-muliprocess/py-flow-multiprocess.py91
-rwxr-xr-xexamples/py-json-stdout/json-stdout.py28
-rwxr-xr-xexamples/py-machine-learning/keras-autoencoder.py384
-rw-r--r--examples/py-machine-learning/requirements.txt7
-rwxr-xr-xexamples/py-machine-learning/sklearn-random-forest.py352
-rwxr-xr-xexamples/py-schema-validation/py-schema-validation.py51
-rw-r--r--examples/py-schema-validation/requirements.txt1
-rwxr-xr-xexamples/py-semantic-validation/py-semantic-validation.py373
-rw-r--r--examples/yaml-filebeat/filebeat.yml28
44 files changed, 19643 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 000000000..524fa489d
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,99 @@
+# examples
+
+Some ready-2-use/ready-2-extend examples/utils.
+All examples are prefixed with their used LANG.
+
+## c-analysed
+
+A feature extractor useful for ML/DL use cases.
+It generates CSV files from flow "analyse" events.
+Used also by `tests/run_tests.sh` if available.
+
+## c-captured
+
+A capture daemon suitable for low-resource devices.
+It saves flows that were guessed/undetected/risky/midstream to a PCAP file for manual analysis.
+Used also by `tests/run_tests.sh` if available.
+
+## c-collectd
+
+A collecd-exec compatible middleware that gathers statistic values from nDPId.
+Used also by `tests/run_tests.sh` if available.
+
+## c-influxd
+
+An InfluxDB push daemon. It aggregates various statistics gathered from nDPId.
+The results are sent to a specified InfluxDB endpoint.
+
+![](ndpid_grafana_example.png)
+
+## c-notifyd
+
+A notification daemon that sends information about suspicious flow events to DBUS.
+
+## c-simple
+
+Integration example that verifies flow timeouts on SIGUSR1.
+
+## cxx-graph
+
+A standalone GLFW/OpenGL application that draws statistical data using ImWeb/ImPlot/ImGui.
+
+## js-rt-analyzer
+
+[nDPId-rt-analyzer](https://gitlab.com/verzulli/ndpid-rt-analyzer.git)
+
+## js-rt-analyzer-frontend
+
+[nDPId-rt-analyzer-frontend](https://gitlab.com/verzulli/ndpid-rt-analyzer-frontend.git)
+
+## py-flow-info
+
+Console friendly, colorful, prettyfied event printer.
+Required by `tests/run_tests.sh`
+
+## py-machine-learning
+
+Contains:
+
+1. Classification via Random Forests and SciLearn
+2. Anomaly Detection via Autoencoder and Keras (Work-In-Progress!)
+
+Use sklearn together with CSVs created with **c-analysed** to train and predict DPI detections.
+
+Try it with: `./examples/py-machine-learning/sklearn_random_forest.py --csv ./ndpi-analysed.csv --proto-class tls.youtube --proto-class tls.github --proto-class tls.spotify --proto-class tls.facebook --proto-class tls.instagram --proto-class tls.doh_dot --proto-class quic --proto-class icmp`
+
+This way you should get 9 different classification classes.
+You may notice that some classes e.g. TLS protocol classifications have a higher false-negative/false-positive rate.
+Unfortunately, I can not provide any datasets due to some privacy concerns.
+
+But you may use a [pre-trained model](https://drive.google.com/file/d/1KEwbP-Gx7KJr54wNoa63I56VI4USCAPL/view?usp=sharing) with `--load-model`.
+
+## py-flow-dashboard
+
+A realtime web based graph using Plotly/Dash.
+Probably the most informative example.
+
+## py-flow-multiprocess
+
+Simple Python Multiprocess example spawning two worker processes, one connecting to nDPIsrvd and one printing flow id's to STDOUT.
+
+## py-json-stdout
+
+Dump received and parsed JSON objects.
+
+## py-schema-validation
+
+Validate nDPId JSON messages against pre-defined JSON schema's.
+See `schema/`.
+Required by `tests/run_tests.sh`
+
+## py-semantic-validation
+
+Validate nDPId JSON messages against internal event semantics.
+Required by `tests/run_tests.sh`
+
+## yaml-filebeat
+An example filebeat configuration to parse and send nDPId JSON
+messages to Elasticsearch. Allowing long term storage and data visualization with kibana
+and various other tools that interact with Elasticsearch (No logstash required). \ No newline at end of file
diff --git a/examples/c-analysed/c-analysed.c b/examples/c-analysed/c-analysed.c
new file mode 100644
index 000000000..2811f70f8
--- /dev/null
+++ b/examples/c-analysed/c-analysed.c
@@ -0,0 +1,2130 @@
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/epoll.h>
+#include <sys/stat.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+#include <ndpi_typedefs.h>
+
+#include "nDPIsrvd.h"
+#include "utils.h"
+
+#define BUFFER_MAX (NETWORK_BUFFER_MAX_SIZE / 3)
+#define BUFFER_REMAINING(siz) (BUFFER_MAX - siz)
+#define MAX_RISKS_PER_FLOW 8
+#define MAX_SEVERITIES_PER_FLOW 4
+
+typedef char csv_buf_t[(NETWORK_BUFFER_MAX_SIZE / 3) + 1];
+
+static int main_thread_shutdown = 0;
+static int analysed_timerfd = -1;
+static struct nDPIsrvd_socket * sock = NULL;
+
+static char * pidfile = NULL;
+static char * serv_optarg = NULL;
+static char * user = NULL;
+static char * group = NULL;
+static char * analysed_interval = NULL;
+static nDPIsrvd_ull analysed_interval_ull = 0uL;
+static char * csv_outfile = NULL;
+static FILE * csv_fp = NULL;
+static char * stats_csv_outfile = NULL;
+static FILE * stats_csv_fp = NULL;
+
+struct flow_user_data
+{
+ nDPIsrvd_ull last_flow_src_l4_payload_len;
+ nDPIsrvd_ull last_flow_dst_l4_payload_len;
+ uint8_t risks[MAX_RISKS_PER_FLOW];
+ uint8_t severities[MAX_SEVERITIES_PER_FLOW];
+ uint8_t category;
+ uint8_t breed;
+ uint8_t confidence;
+ // "fallthroughs" if we are not in sync with nDPI
+ uint8_t risk_ndpid_invalid : 1;
+ uint8_t category_ndpid_invalid : 1;
+ uint8_t breed_ndpid_invalid : 1;
+ uint8_t confidence_ndpid_invalid : 1;
+ // detection status
+ uint8_t new_seen : 1;
+ uint8_t is_detected : 1;
+ uint8_t is_guessed : 1;
+ uint8_t is_not_detected : 1;
+ // flow state
+ uint8_t is_info : 1;
+ uint8_t is_finished : 1;
+ // Layer3 / Layer4
+ uint8_t is_ip4 : 1;
+ uint8_t is_ip6 : 1;
+ uint8_t is_other_l3 : 1;
+ uint8_t is_tcp : 1;
+ uint8_t is_udp : 1;
+ uint8_t is_icmp : 1;
+ uint8_t is_other_l4 : 1;
+};
+
+static struct
+{
+ struct
+ {
+ uint64_t json_lines;
+ uint64_t json_bytes;
+
+ uint64_t flow_new_count;
+ uint64_t flow_end_count;
+ uint64_t flow_idle_count;
+ uint64_t flow_update_count;
+ uint64_t flow_analyse_count;
+ uint64_t flow_guessed_count;
+ uint64_t flow_detected_count;
+ uint64_t flow_detection_update_count;
+ uint64_t flow_not_detected_count;
+
+ uint64_t packet_count;
+ uint64_t packet_flow_count;
+
+ uint64_t init_count;
+ uint64_t reconnect_count;
+ uint64_t shutdown_count;
+ uint64_t status_count;
+
+ uint64_t error_unknown_datalink;
+ uint64_t error_unknown_l3_protocol;
+ uint64_t error_unsupported_datalink;
+ uint64_t error_packet_too_short;
+ uint64_t error_packet_type_unknown;
+ uint64_t error_packet_header_invalid;
+ uint64_t error_ip4_packet_too_short;
+ uint64_t error_ip4_size_smaller_than_header;
+ uint64_t error_ip4_l4_payload_detection;
+ uint64_t error_ip6_packet_too_short;
+ uint64_t error_ip6_size_smaller_than_header;
+ uint64_t error_ip6_l4_payload_detection;
+ uint64_t error_tcp_packet_too_short;
+ uint64_t error_udp_packet_too_short;
+ uint64_t error_capture_size_smaller_than_packet;
+ uint64_t error_max_flows_to_track;
+ uint64_t error_flow_memory_alloc;
+
+ uint64_t flow_src_total_bytes;
+ uint64_t flow_dst_total_bytes;
+ uint64_t flow_risky_count;
+ } counters;
+
+ struct
+ {
+ uint64_t flow_state_info;
+ uint64_t flow_state_finished;
+
+ uint64_t flow_breed_safe_count;
+ uint64_t flow_breed_acceptable_count;
+ uint64_t flow_breed_fun_count;
+ uint64_t flow_breed_unsafe_count;
+ uint64_t flow_breed_potentially_dangerous_count;
+ uint64_t flow_breed_tracker_ads_count;
+ uint64_t flow_breed_dangerous_count;
+ uint64_t flow_breed_unrated_count;
+ uint64_t flow_breed_unknown_count;
+
+ uint64_t flow_category_unspecified_count;
+ uint64_t flow_category_media_count;
+ uint64_t flow_category_vpn_count;
+ uint64_t flow_category_email_count;
+ uint64_t flow_category_data_transfer_count;
+ uint64_t flow_category_web_count;
+ uint64_t flow_category_social_network_count;
+ uint64_t flow_category_download_count;
+ uint64_t flow_category_game_count;
+ uint64_t flow_category_chat_count;
+ uint64_t flow_category_voip_count;
+ uint64_t flow_category_database_count;
+ uint64_t flow_category_remote_access_count;
+ uint64_t flow_category_cloud_count;
+ uint64_t flow_category_network_count;
+ uint64_t flow_category_collaborative_count;
+ uint64_t flow_category_rpc_count;
+ uint64_t flow_category_streaming_count;
+ uint64_t flow_category_system_count;
+ uint64_t flow_category_software_update_count;
+ uint64_t flow_category_music_count;
+ uint64_t flow_category_video_count;
+ uint64_t flow_category_shopping_count;
+ uint64_t flow_category_productivity_count;
+ uint64_t flow_category_file_sharing_count;
+ uint64_t flow_category_conn_check_count;
+ uint64_t flow_category_iot_scada_count;
+ uint64_t flow_category_virt_assistant_count;
+ uint64_t flow_category_cybersecurity_count;
+ uint64_t flow_category_adult_content_count;
+ uint64_t flow_category_mining_count;
+ uint64_t flow_category_malware_count;
+ uint64_t flow_category_advertisment_count;
+ uint64_t flow_category_banned_site_count;
+ uint64_t flow_category_site_unavail_count;
+ uint64_t flow_category_allowed_site_count;
+ uint64_t flow_category_antimalware_count;
+ uint64_t flow_category_crypto_currency_count;
+ uint64_t flow_category_gambling_count;
+ uint64_t flow_category_unknown_count;
+
+ uint64_t flow_confidence_by_port;
+ uint64_t flow_confidence_dpi_partial;
+ uint64_t flow_confidence_dpi_partial_cache;
+ uint64_t flow_confidence_dpi_cache;
+ uint64_t flow_confidence_dpi;
+ uint64_t flow_confidence_nbpf;
+ uint64_t flow_confidence_by_ip;
+ uint64_t flow_confidence_dpi_aggressive;
+ uint64_t flow_confidence_custom_rule;
+ uint64_t flow_confidence_unknown;
+
+ uint64_t flow_severity_low;
+ uint64_t flow_severity_medium;
+ uint64_t flow_severity_high;
+ uint64_t flow_severity_severe;
+ uint64_t flow_severity_critical;
+ uint64_t flow_severity_emergency;
+ uint64_t flow_severity_unknown;
+
+ uint64_t flow_l3_ip4_count;
+ uint64_t flow_l3_ip6_count;
+ uint64_t flow_l3_other_count;
+
+ uint64_t flow_l4_tcp_count;
+ uint64_t flow_l4_udp_count;
+ uint64_t flow_l4_icmp_count;
+ uint64_t flow_l4_other_count;
+
+ uint64_t flow_active_count;
+ uint64_t flow_detected_count;
+ uint64_t flow_guessed_count;
+ uint64_t flow_not_detected_count;
+
+ nDPIsrvd_ull flow_risk_count[NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */];
+ nDPIsrvd_ull flow_risk_unknown_count;
+ } gauges[2]; /* values after InfluxDB push: gauges[0] -= gauges[1], gauges[1] is zero'd afterwards */
+} analysed_statistics = {};
+
+struct global_map
+{
+ char const * const json_key;
+ struct
+ {
+ uint64_t * const global_stat_inc;
+ uint64_t * const global_stat_dec;
+ };
+};
+
+#define ANALYSED_STATS_COUNTER_PTR(member) \
+ { \
+ .global_stat_inc = &(analysed_statistics.counters.member), NULL \
+ }
+#define ANALYSED_STATS_GAUGE_PTR(member) \
+ { \
+ .global_stat_inc = &(analysed_statistics.gauges[0].member), \
+ .global_stat_dec = &(analysed_statistics.gauges[1].member) \
+ }
+#define ANALYSED_STATS_COUNTER_INC(member) (analysed_statistics.counters.member++)
+#define ANALYSED_STATS_GAUGE_RES(member) (analysed_statistics.gauges[0].member--)
+#define ANALYSED_STATS_GAUGE_INC(member) (analysed_statistics.gauges[0].member++)
+#define ANALYSED_STATS_GAUGE_DEC(member) (analysed_statistics.gauges[1].member++)
+#define ANALYSED_STATS_GAUGE_SUB(member) (analysed_statistics.gauges[0].member -= analysed_statistics.gauges[1].member)
+#define ANALYSED_STATS_MAP_NOTNULL(map, index) (map[index - 1].global_stat_dec != NULL)
+#define ANALYSED_STATS_MAP_DEC(map, index) ((*map[index - 1].global_stat_dec)++)
+
+static struct global_map const flow_event_map[] = {{"new", ANALYSED_STATS_COUNTER_PTR(flow_new_count)},
+ {"end", ANALYSED_STATS_COUNTER_PTR(flow_end_count)},
+ {"idle", ANALYSED_STATS_COUNTER_PTR(flow_idle_count)},
+ {"update", ANALYSED_STATS_COUNTER_PTR(flow_update_count)},
+ {"analyse", ANALYSED_STATS_COUNTER_PTR(flow_analyse_count)},
+ {"guessed", ANALYSED_STATS_COUNTER_PTR(flow_guessed_count)},
+ {"detected", ANALYSED_STATS_COUNTER_PTR(flow_detected_count)},
+ {"detection-update",
+ ANALYSED_STATS_COUNTER_PTR(flow_detection_update_count)},
+ {"not-detected",
+ ANALYSED_STATS_COUNTER_PTR(flow_not_detected_count)}};
+
+static struct global_map const packet_event_map[] = {{"packet", ANALYSED_STATS_COUNTER_PTR(packet_count)},
+ {"packet-flow", ANALYSED_STATS_COUNTER_PTR(packet_flow_count)}};
+
+static struct global_map const daemon_event_map[] = {{"init", ANALYSED_STATS_COUNTER_PTR(init_count)},
+ {"reconnect", ANALYSED_STATS_COUNTER_PTR(reconnect_count)},
+ {"shutdown", ANALYSED_STATS_COUNTER_PTR(shutdown_count)},
+ {"status", ANALYSED_STATS_COUNTER_PTR(status_count)}};
+
+static struct global_map const error_event_map[] = {
+ {"Unknown datalink layer packet", ANALYSED_STATS_COUNTER_PTR(error_unknown_datalink)},
+ {"Unknown L3 protocol", ANALYSED_STATS_COUNTER_PTR(error_unknown_l3_protocol)},
+ {"Unsupported datalink layer", ANALYSED_STATS_COUNTER_PTR(error_unsupported_datalink)},
+ {"Packet too short", ANALYSED_STATS_COUNTER_PTR(error_packet_too_short)},
+ {"Unknown packet type", ANALYSED_STATS_COUNTER_PTR(error_packet_type_unknown)},
+ {"Packet header invalid", ANALYSED_STATS_COUNTER_PTR(error_packet_header_invalid)},
+ {"IP4 packet too short", ANALYSED_STATS_COUNTER_PTR(error_ip4_packet_too_short)},
+ {"Packet smaller than IP4 header", ANALYSED_STATS_COUNTER_PTR(error_ip4_size_smaller_than_header)},
+ {"nDPI IPv4\\/L4 payload detection failed", ANALYSED_STATS_COUNTER_PTR(error_ip4_l4_payload_detection)},
+ {"IP6 packet too short", ANALYSED_STATS_COUNTER_PTR(error_ip6_packet_too_short)},
+ {"Packet smaller than IP6 header", ANALYSED_STATS_COUNTER_PTR(error_ip6_size_smaller_than_header)},
+ {"nDPI IPv6\\/L4 payload detection failed", ANALYSED_STATS_COUNTER_PTR(error_ip6_l4_payload_detection)},
+ {"TCP packet smaller than expected", ANALYSED_STATS_COUNTER_PTR(error_tcp_packet_too_short)},
+ {"UDP packet smaller than expected", ANALYSED_STATS_COUNTER_PTR(error_udp_packet_too_short)},
+ {"Captured packet size is smaller than expected packet size",
+ ANALYSED_STATS_COUNTER_PTR(error_capture_size_smaller_than_packet)},
+ {"Max flows to track reached", ANALYSED_STATS_COUNTER_PTR(error_max_flows_to_track)},
+ {"Flow memory allocation failed", ANALYSED_STATS_COUNTER_PTR(error_flow_memory_alloc)}};
+
+static struct global_map const breeds_map[] = {{"Safe", ANALYSED_STATS_GAUGE_PTR(flow_breed_safe_count)},
+ {"Acceptable", ANALYSED_STATS_GAUGE_PTR(flow_breed_acceptable_count)},
+ {"Fun", ANALYSED_STATS_GAUGE_PTR(flow_breed_fun_count)},
+ {"Unsafe", ANALYSED_STATS_GAUGE_PTR(flow_breed_unsafe_count)},
+ {"Potentially Dangerous",
+ ANALYSED_STATS_GAUGE_PTR(flow_breed_potentially_dangerous_count)},
+ {"Tracker\\/Ads",
+ ANALYSED_STATS_GAUGE_PTR(flow_breed_tracker_ads_count)},
+ {"Dangerous", ANALYSED_STATS_GAUGE_PTR(flow_breed_dangerous_count)},
+ {"Unrated", ANALYSED_STATS_GAUGE_PTR(flow_breed_unrated_count)},
+ {NULL, ANALYSED_STATS_GAUGE_PTR(flow_breed_unknown_count)}};
+
+static struct global_map const categories_map[] = {
+ {"Unspecified", ANALYSED_STATS_GAUGE_PTR(flow_category_unspecified_count)},
+ {"Media", ANALYSED_STATS_GAUGE_PTR(flow_category_media_count)},
+ {"VPN", ANALYSED_STATS_GAUGE_PTR(flow_category_vpn_count)},
+ {"Email", ANALYSED_STATS_GAUGE_PTR(flow_category_email_count)},
+ {"DataTransfer", ANALYSED_STATS_GAUGE_PTR(flow_category_data_transfer_count)},
+ {"Web", ANALYSED_STATS_GAUGE_PTR(flow_category_web_count)},
+ {"SocialNetwork", ANALYSED_STATS_GAUGE_PTR(flow_category_social_network_count)},
+ {"Download", ANALYSED_STATS_GAUGE_PTR(flow_category_download_count)},
+ {"Game", ANALYSED_STATS_GAUGE_PTR(flow_category_game_count)},
+ {"Chat", ANALYSED_STATS_GAUGE_PTR(flow_category_chat_count)},
+ {"VoIP", ANALYSED_STATS_GAUGE_PTR(flow_category_voip_count)},
+ {"Database", ANALYSED_STATS_GAUGE_PTR(flow_category_database_count)},
+ {"RemoteAccess", ANALYSED_STATS_GAUGE_PTR(flow_category_remote_access_count)},
+ {"Cloud", ANALYSED_STATS_GAUGE_PTR(flow_category_cloud_count)},
+ {"Network", ANALYSED_STATS_GAUGE_PTR(flow_category_network_count)},
+ {"Collaborative", ANALYSED_STATS_GAUGE_PTR(flow_category_collaborative_count)},
+ {"RPC", ANALYSED_STATS_GAUGE_PTR(flow_category_rpc_count)},
+ {"Streaming", ANALYSED_STATS_GAUGE_PTR(flow_category_streaming_count)},
+ {"System", ANALYSED_STATS_GAUGE_PTR(flow_category_system_count)},
+ {"SoftwareUpdate", ANALYSED_STATS_GAUGE_PTR(flow_category_software_update_count)},
+ {"Music", ANALYSED_STATS_GAUGE_PTR(flow_category_music_count)},
+ {"Video", ANALYSED_STATS_GAUGE_PTR(flow_category_video_count)},
+ {"Shopping", ANALYSED_STATS_GAUGE_PTR(flow_category_shopping_count)},
+ {"Productivity", ANALYSED_STATS_GAUGE_PTR(flow_category_productivity_count)},
+ {"FileSharing", ANALYSED_STATS_GAUGE_PTR(flow_category_file_sharing_count)},
+ {"ConnCheck", ANALYSED_STATS_GAUGE_PTR(flow_category_conn_check_count)},
+ {"IoT-Scada", ANALYSED_STATS_GAUGE_PTR(flow_category_iot_scada_count)},
+ {"VirtAssistant", ANALYSED_STATS_GAUGE_PTR(flow_category_virt_assistant_count)},
+ {"Cybersecurity", ANALYSED_STATS_GAUGE_PTR(flow_category_cybersecurity_count)},
+ {"AdultContent", ANALYSED_STATS_GAUGE_PTR(flow_category_adult_content_count)},
+ {"Mining", ANALYSED_STATS_GAUGE_PTR(flow_category_mining_count)},
+ {"Malware", ANALYSED_STATS_GAUGE_PTR(flow_category_malware_count)},
+ {"Advertisement", ANALYSED_STATS_GAUGE_PTR(flow_category_advertisment_count)},
+ {"Banned_Site", ANALYSED_STATS_GAUGE_PTR(flow_category_banned_site_count)},
+ {"Site_Unavailable", ANALYSED_STATS_GAUGE_PTR(flow_category_site_unavail_count)},
+ {"Allowed_Site", ANALYSED_STATS_GAUGE_PTR(flow_category_allowed_site_count)},
+ {"Antimalware", ANALYSED_STATS_GAUGE_PTR(flow_category_antimalware_count)},
+ {"Crypto_Currency", ANALYSED_STATS_GAUGE_PTR(flow_category_crypto_currency_count)},
+ {"Gambling", ANALYSED_STATS_GAUGE_PTR(flow_category_gambling_count)},
+ {NULL, ANALYSED_STATS_GAUGE_PTR(flow_category_unknown_count)}};
+
+static struct global_map const confidence_map[] = {
+ {"Match by port", ANALYSED_STATS_GAUGE_PTR(flow_confidence_by_port)},
+ {"DPI (partial)", ANALYSED_STATS_GAUGE_PTR(flow_confidence_dpi_partial)},
+ {"DPI (partial cache)", ANALYSED_STATS_GAUGE_PTR(flow_confidence_dpi_partial_cache)},
+ {"DPI (cache)", ANALYSED_STATS_GAUGE_PTR(flow_confidence_dpi_cache)},
+ {"DPI", ANALYSED_STATS_GAUGE_PTR(flow_confidence_dpi)},
+ {"nBPF", ANALYSED_STATS_GAUGE_PTR(flow_confidence_nbpf)},
+ {"Match by IP", ANALYSED_STATS_GAUGE_PTR(flow_confidence_by_ip)},
+ {"DPI (aggressive)", ANALYSED_STATS_GAUGE_PTR(flow_confidence_dpi_aggressive)},
+ {"Match by custom rule", ANALYSED_STATS_GAUGE_PTR(flow_confidence_custom_rule)},
+ {NULL, ANALYSED_STATS_GAUGE_PTR(flow_confidence_unknown)}};
+
+static struct global_map const severity_map[] = {{"Low", ANALYSED_STATS_GAUGE_PTR(flow_severity_low)},
+ {"Medium", ANALYSED_STATS_GAUGE_PTR(flow_severity_medium)},
+ {"High", ANALYSED_STATS_GAUGE_PTR(flow_severity_high)},
+ {"Severe", ANALYSED_STATS_GAUGE_PTR(flow_severity_severe)},
+ {"Critical", ANALYSED_STATS_GAUGE_PTR(flow_severity_critical)},
+ {"Emergency", ANALYSED_STATS_GAUGE_PTR(flow_severity_emergency)},
+ {NULL, ANALYSED_STATS_GAUGE_PTR(flow_severity_unknown)}};
+
+#ifdef ENABLE_MEMORY_PROFILING
+void nDPIsrvd_memprof_log_alloc(size_t alloc_size)
+{
+ (void)alloc_size;
+}
+
+void nDPIsrvd_memprof_log_free(size_t free_size)
+{
+ (void)free_size;
+}
+
+void nDPIsrvd_memprof_log(char const * const format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
+ vfprintf(stderr, format, ap);
+ fprintf(stderr, "%s\n", "");
+ va_end(ap);
+}
+#endif
+
+static void nDPIsrvd_write_flow_info_cb(struct nDPIsrvd_socket const * sock,
+ struct nDPIsrvd_instance const * instance,
+ struct nDPIsrvd_thread_data const * thread_data,
+ struct nDPIsrvd_flow const * flow,
+ void * user_data)
+{
+ (void)sock;
+ (void)instance;
+ (void)user_data;
+
+ if (flow == NULL || thread_data == NULL)
+ {
+ fprintf(stderr, "%s\n", "[WriteFlowInfoCallback] BUG: Internal error.");
+ return;
+ }
+
+ fprintf(stderr,
+ "[Thread %2d][Flow %5llu][ptr: "
+#ifdef __LP64__
+ "0x%016llx"
+#else
+ "0x%08lx"
+#endif
+ "][last-seen: %13llu][idle-time: %7llu][time-until-timeout: %7llu]\n",
+ flow->thread_id,
+ flow->id_as_ull,
+#ifdef __LP64__
+ (unsigned long long int)flow,
+#else
+ (unsigned long int)flow,
+#endif
+ flow->last_seen,
+ flow->idle_time,
+ (flow->last_seen + flow->idle_time >= thread_data->most_recent_flow_time
+ ? flow->last_seen + flow->idle_time - thread_data->most_recent_flow_time
+ : 0));
+}
+
+static void nDPIsrvd_verify_flows_cb(struct nDPIsrvd_thread_data const * const thread_data,
+ struct nDPIsrvd_flow const * const flow,
+ void * user_data)
+{
+ (void)user_data;
+
+ if (thread_data != NULL)
+ {
+ if (flow->last_seen + flow->idle_time >= thread_data->most_recent_flow_time)
+ {
+ fprintf(stderr,
+ "Thread %d / %d, Flow %llu verification failed\n",
+ thread_data->thread_key,
+ flow->thread_id,
+ flow->id_as_ull);
+ }
+ else
+ {
+ fprintf(stderr,
+ "Thread %d / %d, Flow %llu verification failed, diff: %llu\n",
+ thread_data->thread_key,
+ flow->thread_id,
+ flow->id_as_ull,
+ thread_data->most_recent_flow_time - flow->last_seen + flow->idle_time);
+ }
+ }
+ else
+ {
+ fprintf(stderr, "Thread [UNKNOWN], Flow %llu verification failed\n", flow->id_as_ull);
+ }
+}
+
+static void sighandler(int signum)
+{
+ struct nDPIsrvd_instance * current_instance;
+ struct nDPIsrvd_instance * itmp;
+ int verification_failed = 0;
+
+ if (signum == SIGUSR1)
+ {
+ nDPIsrvd_flow_info(sock, nDPIsrvd_write_flow_info_cb, NULL);
+
+ HASH_ITER(hh, sock->instance_table, current_instance, itmp)
+ {
+ if (nDPIsrvd_verify_flows(current_instance, nDPIsrvd_verify_flows_cb, NULL) != 0)
+ {
+ fprintf(stderr, "Flow verification failed for instance %d\n", current_instance->alias_source_key);
+ verification_failed = 1;
+ }
+ }
+ if (verification_failed == 0)
+ {
+ fprintf(stderr, "%s\n", "Flow verification succeeded.");
+ }
+ else
+ {
+ /* FATAL! */
+ exit(EXIT_FAILURE);
+ }
+ }
+ else if (signum == SIGUSR2)
+ {
+ if (csv_fp != NULL)
+ {
+ fflush(csv_fp);
+ }
+ if (stats_csv_fp != NULL)
+ {
+ fflush(stats_csv_fp);
+ }
+ }
+ else if (main_thread_shutdown == 0)
+ {
+ main_thread_shutdown = 1;
+ }
+}
+
+static void csv_buf_add(csv_buf_t buf, size_t * const csv_buf_used, char const * const str, size_t siz_len)
+{
+ size_t len;
+
+ if (siz_len > 0 && str != NULL)
+ {
+ len = MIN(BUFFER_REMAINING(*csv_buf_used), siz_len);
+ if (len == 0)
+ {
+ return;
+ }
+ snprintf(buf + *csv_buf_used, BUFFER_MAX - len, "%.*s", (int)len, str);
+ }
+ else
+ {
+ len = 0;
+ }
+
+ *csv_buf_used += len;
+ if (BUFFER_REMAINING(*csv_buf_used) > 0)
+ {
+ buf[*csv_buf_used] = ',';
+ (*csv_buf_used)++;
+ }
+ buf[*csv_buf_used] = '\0';
+}
+
+static int json_value_to_csv(
+ struct nDPIsrvd_socket * const sock, csv_buf_t buf, size_t * const csv_buf_used, char const * const json_key, ...)
+{
+ va_list ap;
+ nDPIsrvd_hashkey key;
+ struct nDPIsrvd_json_token const * token;
+ size_t val_length = 0;
+ char const * val;
+ int ret = 0;
+
+ va_start(ap, json_key);
+ key = nDPIsrvd_vbuild_jsmn_key(json_key, ap);
+ va_end(ap);
+
+ token = nDPIsrvd_find_token(sock, key);
+ if (token == NULL)
+ {
+ ret++;
+ }
+
+ val = TOKEN_GET_VALUE(sock, token, &val_length);
+ if (val == NULL)
+ {
+ ret++;
+ }
+
+ csv_buf_add(buf, csv_buf_used, val, val_length);
+
+ return ret;
+}
+
+static int json_array_to_csv(
+ struct nDPIsrvd_socket * const sock, csv_buf_t buf, size_t * const csv_buf_used, char const * const json_key, ...)
+{
+ va_list ap;
+ nDPIsrvd_hashkey key;
+ struct nDPIsrvd_json_token const * token;
+ int ret = 0;
+
+ va_start(ap, json_key);
+ key = nDPIsrvd_vbuild_jsmn_key(json_key, ap);
+ va_end(ap);
+
+ token = nDPIsrvd_find_token(sock, key);
+ if (token == NULL)
+ {
+ ret++;
+ csv_buf_add(buf, csv_buf_used, NULL, 0);
+ }
+
+ {
+ size_t token_count = 0;
+ struct nDPIsrvd_json_token next = {};
+
+ csv_buf_add(buf, csv_buf_used, "\"", 1);
+ buf[--(*csv_buf_used)] = '\0';
+ while (nDPIsrvd_token_iterate(sock, token, &next) == 0)
+ {
+ size_t val_length = 0;
+ char const * const val = TOKEN_GET_VALUE(sock, &next, &val_length);
+
+ csv_buf_add(buf, csv_buf_used, val, val_length);
+ token_count++;
+ }
+ if (token_count > 0)
+ {
+ buf[--(*csv_buf_used)] = '\0';
+ }
+ csv_buf_add(buf, csv_buf_used, "\"", 1);
+ }
+
+ return ret;
+}
+
+static int analysed_map_to_stat(char const * const token_str,
+ size_t token_length,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ size_t i, null_i = map_length;
+
+ for (i = 0; i < map_length; ++i)
+ {
+ if (map[i].json_key == NULL)
+ {
+ null_i = i;
+ break;
+ }
+
+ size_t key_length = strlen(map[i].json_key);
+ if (key_length == token_length && strncmp(map[i].json_key, token_str, token_length) == 0)
+ {
+ (*map[i].global_stat_inc)++;
+ return 0;
+ }
+ }
+
+ if (null_i < map_length && map[null_i].global_stat_inc != NULL)
+ {
+ (*map[null_i].global_stat_inc)++;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int analysed_map_value_to_stat(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_json_token const * const token,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ char const * value_str = NULL;
+ size_t value_length = 0;
+
+ value_str = TOKEN_GET_VALUE(sock, token, &value_length);
+ if (value_length == 0 || value_str == NULL)
+ {
+ return 1;
+ }
+
+ return analysed_map_to_stat(value_str, value_length, map, map_length);
+}
+
+static void analysed_unmap_flow_from_stat(struct flow_user_data * const flow_user_data)
+{
+ if (flow_user_data->is_ip4 != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_l3_ip4_count);
+ }
+
+ if (flow_user_data->is_ip6 != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_l3_ip6_count);
+ }
+
+ if (flow_user_data->is_other_l3 != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_l3_other_count);
+ }
+
+ if (flow_user_data->is_tcp != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_l4_tcp_count);
+ }
+
+ if (flow_user_data->is_udp != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_l4_udp_count);
+ }
+
+ if (flow_user_data->is_icmp != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_l4_icmp_count);
+ }
+
+ if (flow_user_data->is_other_l4 != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_l4_other_count);
+ }
+
+ if (flow_user_data->new_seen != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_active_count);
+ }
+
+ if (flow_user_data->is_detected != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_detected_count);
+ }
+
+ if (flow_user_data->is_guessed != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_guessed_count);
+ }
+
+ if (flow_user_data->is_not_detected != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_not_detected_count);
+ }
+
+ if (flow_user_data->is_info != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_state_info);
+ }
+
+ if (flow_user_data->is_finished != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_state_finished);
+ }
+
+ if (flow_user_data->breed > 0 && flow_user_data->breed_ndpid_invalid == 0 &&
+ ANALYSED_STATS_MAP_NOTNULL(breeds_map, flow_user_data->breed) != 0)
+ {
+ ANALYSED_STATS_MAP_DEC(breeds_map, flow_user_data->breed);
+ }
+
+ if (flow_user_data->category > 0 && flow_user_data->category_ndpid_invalid == 0 &&
+ ANALYSED_STATS_MAP_NOTNULL(categories_map, flow_user_data->category) != 0)
+ {
+ ANALYSED_STATS_MAP_DEC(categories_map, flow_user_data->category);
+ }
+
+ if (flow_user_data->confidence > 0 && flow_user_data->confidence_ndpid_invalid == 0 &&
+ ANALYSED_STATS_MAP_NOTNULL(confidence_map, flow_user_data->confidence) != 0)
+ {
+ ANALYSED_STATS_MAP_DEC(confidence_map, flow_user_data->confidence);
+ }
+
+ for (uint8_t i = 0; i < MAX_SEVERITIES_PER_FLOW; ++i)
+ {
+ if (flow_user_data->severities[i] > 0)
+ {
+ ANALYSED_STATS_MAP_DEC(severity_map, flow_user_data->severities[i]);
+ }
+ }
+
+ for (uint8_t i = 0; i < MAX_RISKS_PER_FLOW; ++i)
+ {
+ if (flow_user_data->risks[i] > 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_risk_count[flow_user_data->risks[i]]);
+ }
+ }
+
+ if (flow_user_data->risk_ndpid_invalid != 0)
+ {
+ ANALYSED_STATS_GAUGE_DEC(flow_risk_unknown_count);
+ }
+}
+
+static ssize_t analysed_map_index(char const * const json_key,
+ size_t key_length,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ ssize_t unknown_key = -1;
+
+ if (json_key == NULL || key_length == 0)
+ {
+ return -1;
+ }
+
+ for (size_t i = 0; i < map_length; ++i)
+ {
+ if (map[i].json_key == NULL)
+ {
+ unknown_key = i;
+ continue;
+ }
+
+ if (key_length == strlen(map[i].json_key) && strncmp(json_key, map[i].json_key, key_length) == 0)
+ {
+ return i;
+ }
+ }
+
+ return unknown_key;
+}
+
+static int analysed_map_flow_u8(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_json_token const * const token,
+ struct global_map const * const map,
+ size_t map_length,
+ uint8_t * const dest)
+{
+ if (token == NULL || dest == NULL)
+ {
+ return 1;
+ }
+
+ size_t len;
+ char const * const str = TOKEN_GET_VALUE(sock, token, &len);
+ if (str == NULL || len == 0)
+ {
+ return 1;
+ }
+
+ ssize_t const map_index = analysed_map_index(str, len, map, map_length);
+ if (map_index < 0 || map_index > UCHAR_MAX)
+ {
+ return 1;
+ }
+
+ *dest = map_index + 1;
+ return 0;
+}
+
+static void process_flow_stats(struct nDPIsrvd_socket * const sock, struct nDPIsrvd_flow * const flow)
+{
+ struct flow_user_data * flow_user_data;
+ struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
+ struct nDPIsrvd_json_token const * const flow_state = TOKEN_GET_SZ(sock, "flow_state");
+ nDPIsrvd_ull total_bytes_ull[2];
+
+ if (flow == NULL)
+ {
+ return;
+ }
+ flow_user_data = (struct flow_user_data *)flow->flow_user_data;
+ if (flow_user_data == NULL)
+ {
+ return;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "new") != 0)
+ {
+ flow_user_data->new_seen = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_active_count);
+
+ struct nDPIsrvd_json_token const * const l3_proto = TOKEN_GET_SZ(sock, "l3_proto");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, l3_proto, "ip4") != 0)
+ {
+ flow_user_data->is_ip4 = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_l3_ip4_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l3_proto, "ip6") != 0)
+ {
+ flow_user_data->is_ip6 = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_l3_ip6_count);
+ }
+ else if (l3_proto != NULL)
+ {
+ flow_user_data->is_other_l3 = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_l3_other_count);
+ }
+
+ struct nDPIsrvd_json_token const * const l4_proto = TOKEN_GET_SZ(sock, "l4_proto");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "tcp") != 0)
+ {
+ flow_user_data->is_tcp = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_l4_tcp_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "udp") != 0)
+ {
+ flow_user_data->is_udp = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_l4_udp_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "icmp") != 0)
+ {
+ flow_user_data->is_icmp = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_l4_icmp_count);
+ }
+ else if (l4_proto != NULL)
+ {
+ flow_user_data->is_other_l4 = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_l4_other_count);
+ }
+ }
+ else if (flow_user_data->new_seen == 0)
+ {
+ return;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "not-detected") != 0)
+ {
+ flow_user_data->is_not_detected = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_not_detected_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "guessed") != 0)
+ {
+ flow_user_data->is_guessed = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_guessed_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detected") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detection-update") != 0)
+ {
+ struct nDPIsrvd_json_token const * const flow_risk = TOKEN_GET_SZ(sock, "ndpi", "flow_risk");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ if (flow_user_data->is_detected == 0)
+ {
+ flow_user_data->is_detected = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_detected_count);
+ }
+
+ if (flow_risk != NULL)
+ {
+ if (flow_user_data->risks[0] == 0)
+ {
+ ANALYSED_STATS_COUNTER_INC(flow_risky_count);
+ }
+
+ while ((current = nDPIsrvd_get_next_token(sock, flow_risk, &next_child_index)) != NULL)
+ {
+ size_t numeric_risk_len = 0;
+ char const * const numeric_risk_str = TOKEN_GET_KEY(sock, current, &numeric_risk_len);
+ nDPIsrvd_ull numeric_risk_value = 0;
+ char numeric_risk_buf[numeric_risk_len + 1];
+
+ if (numeric_risk_len > 0 && numeric_risk_str != NULL)
+ {
+ strncpy(numeric_risk_buf, numeric_risk_str, numeric_risk_len);
+ numeric_risk_buf[numeric_risk_len] = '\0';
+
+ struct nDPIsrvd_json_token const * const severity =
+ TOKEN_GET_SZ(sock, "ndpi", "flow_risk", numeric_risk_buf, "severity");
+ uint8_t severity_index;
+
+ if (analysed_map_flow_u8(
+ sock, severity, severity_map, nDPIsrvd_ARRAY_LENGTH(severity_map), &severity_index) != 0)
+ {
+ severity_index = 0;
+ }
+
+ if (severity_index != 0)
+ {
+ for (uint8_t i = 0; i < MAX_SEVERITIES_PER_FLOW; ++i)
+ {
+ if (flow_user_data->severities[i] != 0)
+ {
+ continue;
+ }
+ if (flow_user_data->severities[i] == severity_index)
+ {
+ break;
+ }
+
+ if (analysed_map_value_to_stat(
+ sock, severity, severity_map, nDPIsrvd_ARRAY_LENGTH(severity_map)) != 0)
+ {
+ severity_index = 0;
+ break;
+ }
+ flow_user_data->severities[i] = severity_index;
+ break;
+ }
+ }
+ if (severity_index == 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, severity, &value_len);
+
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1,
+ "Unknown/Invalid JSON value for key 'ndpi','breed': %.*s",
+ (int)value_len,
+ value_str);
+ }
+ }
+
+ if (str_value_to_ull(numeric_risk_str, &numeric_risk_value) == CONVERSION_OK)
+ {
+ if (numeric_risk_value < NDPI_MAX_RISK && numeric_risk_value > 0)
+ {
+ for (uint8_t i = 0; i < MAX_RISKS_PER_FLOW; ++i)
+ {
+ if (flow_user_data->risks[i] != 0)
+ {
+ continue;
+ }
+ if (flow_user_data->risks[i] == numeric_risk_value - 1)
+ {
+ break;
+ }
+
+ ANALYSED_STATS_GAUGE_INC(flow_risk_count[numeric_risk_value - 1]);
+ flow_user_data->risks[i] = numeric_risk_value - 1;
+ break;
+ }
+ }
+ else if (flow_user_data->risk_ndpid_invalid == 0)
+ {
+ flow_user_data->risk_ndpid_invalid = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_risk_unknown_count);
+ }
+ }
+ else
+ {
+ logger(1, "Invalid numeric risk value: %s", numeric_risk_buf);
+ }
+ }
+ else
+ {
+ logger(1, "%s", "Missing numeric risk value");
+ }
+ }
+ }
+
+ if (flow_user_data->breed == 0 && flow_user_data->breed_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const breed = TOKEN_GET_SZ(sock, "ndpi", "breed");
+ if (analysed_map_flow_u8(
+ sock, breed, breeds_map, nDPIsrvd_ARRAY_LENGTH(breeds_map), &flow_user_data->breed) != 0 ||
+ analysed_map_value_to_stat(sock, breed, breeds_map, nDPIsrvd_ARRAY_LENGTH(breeds_map)) != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, breed, &value_len);
+
+ flow_user_data->breed = 0;
+ flow_user_data->breed_ndpid_invalid = 1;
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','breed': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (flow_user_data->category == 0 && flow_user_data->category_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const category = TOKEN_GET_SZ(sock, "ndpi", "category");
+ if (analysed_map_flow_u8(
+ sock, category, categories_map, nDPIsrvd_ARRAY_LENGTH(categories_map), &flow_user_data->category) !=
+ 0 ||
+ analysed_map_value_to_stat(sock, category, categories_map, nDPIsrvd_ARRAY_LENGTH(categories_map)) != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, category, &value_len);
+
+ flow_user_data->category = 0;
+ flow_user_data->category_ndpid_invalid = 1;
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','category': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (flow_user_data->confidence == 0 && flow_user_data->confidence_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const token = TOKEN_GET_SZ(sock, "ndpi", "confidence");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ if ((current = nDPIsrvd_get_next_token(sock, token, &next_child_index)) == NULL)
+ {
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+ else if (nDPIsrvd_get_next_token(sock, token, &next_child_index) == NULL)
+ {
+ if (analysed_map_flow_u8(sock,
+ current,
+ confidence_map,
+ nDPIsrvd_ARRAY_LENGTH(confidence_map),
+ &flow_user_data->confidence) != 0 ||
+ analysed_map_value_to_stat(sock, current, confidence_map, nDPIsrvd_ARRAY_LENGTH(confidence_map)) !=
+ 0)
+ {
+ flow_user_data->confidence = 0;
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+ }
+ else
+ {
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+
+ if (flow_user_data->confidence_ndpid_invalid != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, current, &value_len);
+
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','confidence': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_state, "info") != 0)
+ {
+ if (flow_user_data->is_info == 0)
+ {
+ flow_user_data->is_info = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_state_info);
+ }
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_state, "finished") != 0)
+ {
+ if (flow_user_data->is_finished == 0)
+ {
+ if (flow_user_data->is_info != 0)
+ {
+ flow_user_data->is_info = 0;
+ ANALYSED_STATS_GAUGE_RES(flow_state_info);
+ }
+ flow_user_data->is_finished = 1;
+ ANALYSED_STATS_GAUGE_INC(flow_state_finished);
+ }
+ }
+
+ if (TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_src_tot_l4_payload_len"), &total_bytes_ull[0]) ==
+ CONVERSION_OK &&
+ TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_dst_tot_l4_payload_len"), &total_bytes_ull[1]) ==
+ CONVERSION_OK)
+ {
+ analysed_statistics.counters.flow_src_total_bytes +=
+ total_bytes_ull[0] - flow_user_data->last_flow_src_l4_payload_len;
+ analysed_statistics.counters.flow_dst_total_bytes +=
+ total_bytes_ull[1] - flow_user_data->last_flow_dst_l4_payload_len;
+
+ flow_user_data->last_flow_src_l4_payload_len = total_bytes_ull[0];
+ flow_user_data->last_flow_dst_l4_payload_len = total_bytes_ull[1];
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "end") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "idle") != 0)
+ {
+ analysed_unmap_flow_from_stat(flow_user_data);
+ }
+}
+
+static void process_global_stats(struct nDPIsrvd_socket * const sock)
+{
+ struct nDPIsrvd_json_token const * const flow_event = TOKEN_GET_SZ(sock, "flow_event_name");
+ struct nDPIsrvd_json_token const * const packet_event = TOKEN_GET_SZ(sock, "packet_event_name");
+ struct nDPIsrvd_json_token const * const daemon_event = TOKEN_GET_SZ(sock, "daemon_event_name");
+ struct nDPIsrvd_json_token const * const error_event = TOKEN_GET_SZ(sock, "error_event_name");
+
+ ANALYSED_STATS_COUNTER_INC(json_lines);
+ analysed_statistics.counters.json_bytes += sock->buffer.json_message_length + NETWORK_BUFFER_LENGTH_DIGITS;
+
+ if (flow_event != NULL &&
+ analysed_map_value_to_stat(sock, flow_event, flow_event_map, nDPIsrvd_ARRAY_LENGTH(flow_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown flow_event_name");
+ }
+
+ if (packet_event != NULL &&
+ analysed_map_value_to_stat(sock, packet_event, packet_event_map, nDPIsrvd_ARRAY_LENGTH(packet_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown packet_event_name");
+ }
+
+ if (daemon_event != NULL &&
+ analysed_map_value_to_stat(sock, daemon_event, daemon_event_map, nDPIsrvd_ARRAY_LENGTH(daemon_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown daemon_event_name");
+ }
+
+ if (error_event != NULL &&
+ analysed_map_value_to_stat(sock, error_event, error_event_map, nDPIsrvd_ARRAY_LENGTH(error_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown error_event_name");
+ }
+}
+
+static enum nDPIsrvd_callback_return process_analyse_events(struct nDPIsrvd_socket * const sock)
+{
+ csv_buf_t buf;
+ size_t csv_buf_used = 0;
+
+ struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "analyse") == 0)
+ {
+ return CALLBACK_OK;
+ }
+
+ if (TOKEN_GET_SZ(sock, "data_analysis") == NULL)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ buf[0] = '\0';
+
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_datalink", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "l3_proto", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "src_ip", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "dst_ip", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "l4_proto", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "src_port", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "dst_port", NULL);
+
+ if (json_value_to_csv(sock, buf, &csv_buf_used, "flow_state", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_src_packets_processed", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_dst_packets_processed", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_first_seen", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_src_last_pkt_time", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_dst_last_pkt_time", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_src_min_l4_payload_len", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_dst_min_l4_payload_len", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_src_max_l4_payload_len", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_dst_max_l4_payload_len", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_src_tot_l4_payload_len", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "flow_dst_tot_l4_payload_len", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "midstream", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "iat", "min", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "iat", "avg", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "iat", "max", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "iat", "stddev", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "iat", "var", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "iat", "ent", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_array_to_csv(sock, buf, &csv_buf_used, "data_analysis", "iat", "data", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "pktlen", "min", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "pktlen", "avg", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "pktlen", "max", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "pktlen", "stddev", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "pktlen", "var", NULL) != 0 ||
+ json_value_to_csv(sock, buf, &csv_buf_used, "data_analysis", "pktlen", "ent", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_array_to_csv(sock, buf, &csv_buf_used, "data_analysis", "pktlen", "data", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_array_to_csv(sock, buf, &csv_buf_used, "data_analysis", "bins", "c_to_s", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_array_to_csv(sock, buf, &csv_buf_used, "data_analysis", "bins", "s_to_c", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_array_to_csv(sock, buf, &csv_buf_used, "data_analysis", "directions", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ if (json_array_to_csv(sock, buf, &csv_buf_used, "data_analysis", "entropies", NULL) != 0)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ json_value_to_csv(sock, buf, &csv_buf_used, "ndpi", "proto", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "ndpi", "proto_id", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "ndpi", "encrypted", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "ndpi", "breed", NULL);
+ json_value_to_csv(sock, buf, &csv_buf_used, "ndpi", "category", NULL);
+ {
+ struct nDPIsrvd_json_token const * const token = TOKEN_GET_SZ(sock, "ndpi", "confidence");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ if (token == NULL)
+ {
+ csv_buf_add(buf, &csv_buf_used, NULL, 0);
+ csv_buf_add(buf, &csv_buf_used, NULL, 0);
+ }
+ else
+ {
+ while ((current = nDPIsrvd_get_next_token(sock, token, &next_child_index)) != NULL)
+ {
+ size_t key_length = 0, value_length = 0;
+ char const * const key = TOKEN_GET_KEY(sock, current, &key_length);
+ char const * const value = TOKEN_GET_VALUE(sock, current, &value_length);
+
+ csv_buf_add(buf, &csv_buf_used, key, key_length);
+ csv_buf_add(buf, &csv_buf_used, value, value_length);
+ }
+ }
+ }
+ {
+ csv_buf_t risks;
+ size_t csv_risks_used = 0;
+ struct nDPIsrvd_json_token const * const flow_risk = TOKEN_GET_SZ(sock, "ndpi", "flow_risk");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ risks[csv_risks_used++] = '"';
+ risks[csv_risks_used] = '\0';
+ if (flow_risk != NULL)
+ {
+ while ((current = nDPIsrvd_get_next_token(sock, flow_risk, &next_child_index)) != NULL)
+ {
+ size_t key_length = 0;
+ char const * const key = TOKEN_GET_KEY(sock, current, &key_length);
+
+ csv_buf_add(risks, &csv_risks_used, key, key_length);
+ }
+ }
+ if (csv_risks_used > 1)
+ {
+ risks[csv_risks_used - 1] = '"';
+ }
+ else if (BUFFER_REMAINING(csv_risks_used) > 0)
+ {
+ risks[csv_risks_used++] = '"';
+ }
+ csv_buf_add(buf, &csv_buf_used, risks, csv_risks_used);
+ }
+
+ if (csv_buf_used > 0 && buf[csv_buf_used - 1] == ',')
+ {
+ buf[--csv_buf_used] = '\0';
+ }
+
+ fprintf(csv_fp, "%.*s\n", (int)csv_buf_used, buf);
+ return CALLBACK_OK;
+}
+
+static enum nDPIsrvd_callback_return analysed_json_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow)
+{
+ (void)instance;
+ (void)thread_data;
+
+ if (stats_csv_fp != NULL)
+ {
+ process_global_stats(sock);
+ }
+
+ if (flow == NULL)
+ {
+ return CALLBACK_OK;
+ }
+
+ if (stats_csv_fp != NULL)
+ {
+ process_flow_stats(sock, flow);
+ }
+
+ if (csv_fp != NULL && process_analyse_events(sock) != CALLBACK_OK)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ return CALLBACK_OK;
+}
+
+static void print_usage(char const * const arg0)
+{
+ static char const usage[] =
+ "Usage: %s "
+ "[-d] [-p pidfile] [-s host]\n"
+ "\t \t[-u user] [-g group] [-o csv-outfile] [-O csv-outfile]\n\n"
+ "\t-d\tForking into background after initialization.\n"
+ "\t-p\tWrite the daemon PID to the given file path.\n"
+ "\t-s\tDestination where nDPIsrvd is listening on.\n"
+ "\t \tCan be either a path to UNIX socket or an IPv4/TCP-Port IPv6/TCP-Port tuple.\n"
+ "\t-u\tChange user.\n"
+ "\t-g\tChange group.\n"
+ "\t-o\tSpecify the CSV output file for analysis results.\n"
+ "\t-O\tWrite some global statistics to a CSV every `-t' seconds.\n"
+ "\t-t\tTime interval for `-O'.\n\n";
+
+ fprintf(stderr, usage, arg0);
+}
+
+static int parse_options(int argc, char ** argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "hdp:s:u:g:o:O:t:")) != -1)
+ {
+ switch (opt)
+ {
+ case 'd':
+ daemonize_enable();
+ break;
+ case 'p':
+ free(pidfile);
+ pidfile = strdup(optarg);
+ break;
+ case 's':
+ free(serv_optarg);
+ serv_optarg = strdup(optarg);
+ break;
+ case 'u':
+ free(user);
+ user = strdup(optarg);
+ break;
+ case 'g':
+ free(group);
+ group = strdup(optarg);
+ break;
+ case 'o':
+ free(csv_outfile);
+ csv_outfile = strdup(optarg);
+ break;
+ case 'O':
+ free(stats_csv_outfile);
+ stats_csv_outfile = strdup(optarg);
+ break;
+ case 't':
+ free(analysed_interval);
+ analysed_interval = strdup(optarg);
+ break;
+ default:
+ print_usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (csv_outfile == NULL && stats_csv_outfile == NULL)
+ {
+ fprintf(stderr,
+ "%s: Missing either analyse CSV output file (`-o') or global stats CSV output file (`-O')\n",
+ argv[0]);
+ return 1;
+ }
+
+ if (csv_outfile != NULL)
+ {
+ opt = 0;
+ if (access(csv_outfile, F_OK) != 0 && errno == ENOENT)
+ {
+ opt = 1;
+ }
+
+ csv_fp = fopen(csv_outfile, "a+");
+ if (csv_fp == NULL)
+ {
+ fprintf(stderr, "%s: Could not open file `%s' for appending: %s\n", argv[0], csv_outfile, strerror(errno));
+ return 1;
+ }
+
+ if (opt != 0)
+ {
+ fprintf(csv_fp,
+ "flow_datalink,l3_proto,src_ip,dst_ip,l4_proto,src_port,dst_port,flow_state,flow_src_packets_"
+ "processed,"
+ "flow_dst_packets_processed,flow_first_seen,flow_src_last_pkt_time,flow_dst_last_pkt_time,flow_src_"
+ "min_"
+ "l4_payload_len,flow_dst_min_l4_payload_len,flow_src_max_l4_payload_len,flow_dst_max_l4_payload_"
+ "len,"
+ "flow_src_tot_l4_payload_len,flow_dst_tot_l4_payload_len,midstream,iat_min,iat_avg,iat_max,iat_"
+ "stddev,"
+ "iat_var,iat_ent,iat_data,pktlen_min,pktlen_avg,pktlen_max,pktlen_stddev,pktlen_var,pktlen_ent,"
+ "pktlen_"
+ "data,bins_c_to_s,bins_s_to_c,directions,entropies,proto,proto_id,encrypted,breed,category,"
+ "confidence_id,confidence,risks\n");
+ }
+ }
+
+ if (serv_optarg == NULL)
+ {
+ serv_optarg = strdup(DISTRIBUTOR_UNIX_SOCKET);
+ }
+
+ if (stats_csv_outfile != NULL)
+ {
+ opt = 0;
+ if (access(stats_csv_outfile, F_OK) != 0 && errno == ENOENT)
+ {
+ opt = 1;
+ }
+
+ stats_csv_fp = fopen(stats_csv_outfile, "a+");
+ if (stats_csv_fp == NULL)
+ {
+ fprintf(stderr,
+ "%s: Could not open file `%s' for appending: %s\n",
+ argv[0],
+ stats_csv_outfile,
+ strerror(errno));
+ return 1;
+ }
+
+ if (opt != 0)
+ {
+ fprintf(stats_csv_fp,
+ "%s",
+ "timestamp,"
+ "json_lines,json_bytes,flow_src_total_bytes,flow_dst_total_bytes,"
+ "flow_new_count,flow_end_count,flow_idle_count,flow_update_count,flow_analyse_count,flow_guessed_"
+ "count,flow_detected_count,flow_detection_update_count,flow_not_detected_count,flow_risky_count,"
+ "packet_count,packet_flow_count,init_count,reconnect_count,shutdown_count,status_count,error_"
+ "unknown_datalink,error_unknown_l3_protocol,error_unsupported_datalink,error_packet_too_short,"
+ "error_packet_type_unknown,error_packet_header_invalid,error_ip4_packet_too_short,error_ip4_size_"
+ "smaller_than_header,error_ip4_l4_payload_detection,error_ip6_packet_too_short,error_ip6_size_"
+ "smaller_than_header,error_ip6_l4_payload_detection,error_tcp_packet_too_short,error_udp_packet_"
+ "too_short,error_capture_size_smaller_than_packet,error_max_flows_to_track,error_flow_memory_"
+ "alloc,"
+ "flow_state_info,flow_state_finished,"
+ "flow_breed_safe_count,flow_breed_acceptable_count,flow_breed_fun_count,flow_breed_unsafe_count,"
+ "flow_breed_potentially_dangerous_count,flow_breed_tracker_ads_count,flow_breed_dangerous_count,"
+ "flow_breed_unrated_count,flow_breed_unknown_count,"
+ "flow_category_unspecified_count,flow_category_media_count,flow_category_vpn_count,flow_category_"
+ "email_count,flow_category_data_transfer_count,flow_category_web_count,flow_category_social_"
+ "network_count,flow_category_download_count,flow_category_game_count,flow_category_chat_count,flow_"
+ "category_voip_count,flow_category_database_count,flow_category_remote_access_count,flow_category_"
+ "cloud_count,flow_category_network_count,flow_category_collaborative_count,flow_category_rpc_count,"
+ "flow_category_streaming_count,flow_category_system_count,flow_category_software_update_count,flow_"
+ "category_music_count,flow_category_video_count,flow_category_shopping_count,flow_category_"
+ "productivity_count,flow_category_file_sharing_count,flow_category_conn_check_count,flow_category_"
+ "iot_scada_count,flow_category_virt_assistant_count,flow_category_cybersecurity_count,flow_"
+ "category_adult_content_count,flow_category_mining_count,flow_category_malware_count,flow_category_"
+ "advertisment_count,flow_category_banned_site_count,flow_category_site_unavail_count,flow_category_"
+ "allowed_site_count,flow_category_antimalware_count,flow_category_crypto_currency_count,flow_"
+ "category_gambling_count,flow_category_unknown_count,"
+ "flow_confidence_by_port,flow_confidence_dpi_partial,flow_confidence_dpi_partial_cache,flow_"
+ "confidence_dpi_cache,flow_confidence_dpi,flow_confidence_nbpf,flow_confidence_by_ip,flow_"
+ "confidence_dpi_aggressive,flow_confidence_custom_rule,flow_confidence_unknown,"
+ "flow_severity_low,flow_severity_medium,flow_severity_high,flow_severity_severe,flow_severity_"
+ "critical,flow_severity_emergency,flow_severity_unknown,"
+ "flow_l3_ip4_count,flow_l3_ip6_count,flow_l3_other_count,"
+ "flow_l4_tcp_count,flow_l4_udp_count,flow_l4_icmp_count,flow_l4_other_count,"
+ "flow_active_count,flow_detected_count,flow_guessed_count,flow_not_detected_count,");
+ for (size_t i = 0; i < NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */; ++i)
+ {
+ fprintf(stats_csv_fp, "flow_risk_%zu_count,", i + 1);
+ }
+ fprintf(stats_csv_fp, "%s\n", "flow_risk_unknown_count");
+ }
+
+ if (analysed_interval == NULL)
+ {
+ analysed_interval = strdup("30");
+ }
+
+ if (str_value_to_ull(analysed_interval, &analysed_interval_ull) != CONVERSION_OK)
+ {
+ logger_early(1, "InfluxDB push interval `%s' is not a valid number", analysed_interval);
+ return 1;
+ }
+ }
+
+ if (optind < argc)
+ {
+ fprintf(stderr, "Unexpected argument after options\n\n");
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int set_analysed_timer(void)
+{
+ const time_t interval = analysed_interval_ull * 1000;
+ struct itimerspec its;
+ its.it_value.tv_sec = interval / 1000;
+ its.it_value.tv_nsec = (interval % 1000) * 1000000;
+ its.it_interval.tv_nsec = 0;
+ its.it_interval.tv_sec = 0;
+
+ errno = 0;
+ return timerfd_settime(analysed_timerfd, 0, &its, NULL);
+}
+
+static int create_analysed_timer(void)
+{
+ analysed_timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (analysed_timerfd < 0)
+ {
+ return 1;
+ }
+
+ return set_analysed_timer();
+}
+
+#define ANALYSEDB_FORMAT() "%llu,"
+#define ANALYSEDB_VALUE_COUNTER(value) (unsigned long long int)analysed_statistics.counters.value
+#define ANALYSEDB_VALUE_GAUGE(value) (unsigned long long int)analysed_statistics.gauges[0].value
+#define CHECK_SNPRINTF_RET(bytes) \
+ do \
+ { \
+ if (bytes <= 0 || (size_t)bytes >= siz) \
+ { \
+ goto failure; \
+ } \
+ else \
+ { \
+ buf += bytes; \
+ siz -= bytes; \
+ } \
+ } while (0)
+static int write_global_flow_stats(void)
+{
+ int rc = 1;
+ char output_buffer[BUFSIZ];
+ char * buf = &output_buffer[0];
+ size_t siz = sizeof(output_buffer);
+ int bytes;
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_COUNTER(json_lines),
+ ANALYSEDB_VALUE_COUNTER(json_bytes),
+ ANALYSEDB_VALUE_COUNTER(flow_src_total_bytes),
+ ANALYSEDB_VALUE_COUNTER(flow_dst_total_bytes));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_COUNTER(flow_new_count),
+ ANALYSEDB_VALUE_COUNTER(flow_end_count),
+ ANALYSEDB_VALUE_COUNTER(flow_idle_count),
+ ANALYSEDB_VALUE_COUNTER(flow_update_count),
+ ANALYSEDB_VALUE_COUNTER(flow_analyse_count),
+ ANALYSEDB_VALUE_COUNTER(flow_guessed_count),
+ ANALYSEDB_VALUE_COUNTER(flow_detected_count),
+ ANALYSEDB_VALUE_COUNTER(flow_detection_update_count),
+ ANALYSEDB_VALUE_COUNTER(flow_not_detected_count),
+ ANALYSEDB_VALUE_COUNTER(flow_risky_count),
+ ANALYSEDB_VALUE_COUNTER(packet_count),
+ ANALYSEDB_VALUE_COUNTER(packet_flow_count),
+ ANALYSEDB_VALUE_COUNTER(init_count),
+ ANALYSEDB_VALUE_COUNTER(reconnect_count),
+ ANALYSEDB_VALUE_COUNTER(shutdown_count),
+ ANALYSEDB_VALUE_COUNTER(status_count),
+ ANALYSEDB_VALUE_COUNTER(error_unknown_datalink),
+ ANALYSEDB_VALUE_COUNTER(error_unknown_l3_protocol),
+ ANALYSEDB_VALUE_COUNTER(error_unsupported_datalink),
+ ANALYSEDB_VALUE_COUNTER(error_packet_too_short),
+ ANALYSEDB_VALUE_COUNTER(error_packet_type_unknown),
+ ANALYSEDB_VALUE_COUNTER(error_packet_header_invalid),
+ ANALYSEDB_VALUE_COUNTER(error_ip4_packet_too_short),
+ ANALYSEDB_VALUE_COUNTER(error_ip4_size_smaller_than_header),
+ ANALYSEDB_VALUE_COUNTER(error_ip4_l4_payload_detection),
+ ANALYSEDB_VALUE_COUNTER(error_ip6_packet_too_short),
+ ANALYSEDB_VALUE_COUNTER(error_ip6_size_smaller_than_header),
+ ANALYSEDB_VALUE_COUNTER(error_ip6_l4_payload_detection),
+ ANALYSEDB_VALUE_COUNTER(error_tcp_packet_too_short),
+ ANALYSEDB_VALUE_COUNTER(error_udp_packet_too_short),
+ ANALYSEDB_VALUE_COUNTER(error_capture_size_smaller_than_packet),
+ ANALYSEDB_VALUE_COUNTER(error_max_flows_to_track),
+ ANALYSEDB_VALUE_COUNTER(error_flow_memory_alloc));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_GAUGE(flow_state_info),
+ ANALYSEDB_VALUE_GAUGE(flow_state_finished));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_safe_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_acceptable_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_fun_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_unsafe_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_potentially_dangerous_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_tracker_ads_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_dangerous_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_unrated_count),
+ ANALYSEDB_VALUE_GAUGE(flow_breed_unknown_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes =
+ snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+
+ ANALYSEDB_VALUE_GAUGE(flow_category_unspecified_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_media_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_vpn_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_email_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_data_transfer_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_web_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_social_network_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_download_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_game_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_chat_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_voip_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_database_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_remote_access_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_cloud_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_network_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_collaborative_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_rpc_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_streaming_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_system_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_software_update_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_music_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_video_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_shopping_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_productivity_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_file_sharing_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_conn_check_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_iot_scada_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_virt_assistant_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_cybersecurity_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_adult_content_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_mining_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_malware_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_advertisment_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_banned_site_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_site_unavail_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_allowed_site_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_antimalware_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_crypto_currency_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_gambling_count),
+ ANALYSEDB_VALUE_GAUGE(flow_category_unknown_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_by_port),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_dpi_partial),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_dpi_partial_cache),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_dpi_cache),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_dpi),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_nbpf),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_by_ip),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_dpi_aggressive),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_custom_rule),
+ ANALYSEDB_VALUE_GAUGE(flow_confidence_unknown));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT()
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_GAUGE(flow_severity_low),
+ ANALYSEDB_VALUE_GAUGE(flow_severity_medium),
+ ANALYSEDB_VALUE_GAUGE(flow_severity_high),
+ ANALYSEDB_VALUE_GAUGE(flow_severity_severe),
+ ANALYSEDB_VALUE_GAUGE(flow_severity_critical),
+ ANALYSEDB_VALUE_GAUGE(flow_severity_emergency),
+ ANALYSEDB_VALUE_GAUGE(flow_severity_unknown));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_GAUGE(flow_l3_ip4_count),
+ ANALYSEDB_VALUE_GAUGE(flow_l3_ip6_count),
+ ANALYSEDB_VALUE_GAUGE(flow_l3_other_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_GAUGE(flow_l4_tcp_count),
+ ANALYSEDB_VALUE_GAUGE(flow_l4_udp_count),
+ ANALYSEDB_VALUE_GAUGE(flow_l4_icmp_count),
+ ANALYSEDB_VALUE_GAUGE(flow_l4_other_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT() ANALYSEDB_FORMAT(),
+ ANALYSEDB_VALUE_GAUGE(flow_active_count),
+ ANALYSEDB_VALUE_GAUGE(flow_detected_count),
+ ANALYSEDB_VALUE_GAUGE(flow_guessed_count),
+ ANALYSEDB_VALUE_GAUGE(flow_not_detected_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf, siz, ANALYSEDB_FORMAT(), ANALYSEDB_VALUE_GAUGE(flow_risk_unknown_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ for (size_t i = 0; i < NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */; ++i)
+ {
+ bytes = snprintf(buf, siz, "%llu,", (unsigned long long int)analysed_statistics.gauges[0].flow_risk_count[i]);
+ CHECK_SNPRINTF_RET(bytes);
+ }
+ buf[-1] = '\n';
+
+ struct timeval tval;
+ if (gettimeofday(&tval, NULL) == 0)
+ {
+ unsigned long long int sec = tval.tv_sec;
+ unsigned long long int usec = tval.tv_usec;
+ unsigned long long int timestamp = usec + sec * 1000 * 1000;
+ fprintf(stats_csv_fp, "%llu,%s", timestamp, output_buffer);
+ rc = 0;
+ }
+ else
+ {
+ fprintf(stats_csv_fp, "0,%s", output_buffer);
+ }
+failure:
+ // reset all counters until the analysed timer is ready again
+ memset(&analysed_statistics.counters, 0, sizeof(analysed_statistics.counters));
+
+ ANALYSED_STATS_GAUGE_SUB(flow_state_info);
+ ANALYSED_STATS_GAUGE_SUB(flow_state_finished);
+
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_safe_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_acceptable_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_fun_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_unsafe_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_potentially_dangerous_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_tracker_ads_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_dangerous_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_unrated_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_breed_unknown_count);
+
+ ANALYSED_STATS_GAUGE_SUB(flow_category_unspecified_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_media_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_vpn_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_email_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_data_transfer_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_web_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_social_network_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_download_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_game_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_chat_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_voip_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_database_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_remote_access_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_cloud_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_network_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_collaborative_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_rpc_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_streaming_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_system_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_software_update_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_music_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_video_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_shopping_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_productivity_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_file_sharing_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_conn_check_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_iot_scada_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_virt_assistant_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_cybersecurity_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_adult_content_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_mining_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_malware_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_advertisment_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_banned_site_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_site_unavail_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_allowed_site_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_antimalware_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_crypto_currency_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_gambling_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_category_unknown_count);
+
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_by_port);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_dpi_partial);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_dpi_partial_cache);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_dpi_cache);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_dpi);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_nbpf);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_by_ip);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_dpi_aggressive);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_custom_rule);
+ ANALYSED_STATS_GAUGE_SUB(flow_confidence_unknown);
+
+ ANALYSED_STATS_GAUGE_SUB(flow_severity_low);
+ ANALYSED_STATS_GAUGE_SUB(flow_severity_medium);
+ ANALYSED_STATS_GAUGE_SUB(flow_severity_high);
+ ANALYSED_STATS_GAUGE_SUB(flow_severity_severe);
+ ANALYSED_STATS_GAUGE_SUB(flow_severity_critical);
+ ANALYSED_STATS_GAUGE_SUB(flow_severity_emergency);
+ ANALYSED_STATS_GAUGE_SUB(flow_severity_unknown);
+
+ ANALYSED_STATS_GAUGE_SUB(flow_l3_ip4_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_l3_ip6_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_l3_other_count);
+
+ ANALYSED_STATS_GAUGE_SUB(flow_l4_tcp_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_l4_udp_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_l4_icmp_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_l4_other_count);
+
+ ANALYSED_STATS_GAUGE_SUB(flow_active_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_detected_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_guessed_count);
+ ANALYSED_STATS_GAUGE_SUB(flow_not_detected_count);
+
+ for (size_t i = 0; i < NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */; ++i)
+ {
+ ANALYSED_STATS_GAUGE_SUB(flow_risk_count[i]);
+ }
+ ANALYSED_STATS_GAUGE_SUB(flow_risk_unknown_count);
+
+ memset(&analysed_statistics.gauges[1], 0, sizeof(analysed_statistics.gauges[1]));
+
+ return rc;
+}
+
+static int mainloop(int epollfd, struct nDPIsrvd_socket * const sock)
+{
+ struct epoll_event events[32];
+ size_t const events_size = sizeof(events) / sizeof(events[0]);
+
+ while (main_thread_shutdown == 0)
+ {
+ int nready = epoll_wait(epollfd, events, events_size, -1);
+
+ for (int i = 0; i < nready; i++)
+ {
+ if (events[i].events & EPOLLERR)
+ {
+ logger(1, "Epoll event error: %s", (errno != 0 ? strerror(errno) : "EPOLLERR"));
+ break;
+ }
+
+ if (events[i].data.fd == analysed_timerfd)
+ {
+ uint64_t expirations;
+
+ errno = 0;
+ if (read(analysed_timerfd, &expirations, sizeof(expirations)) != sizeof(expirations))
+ {
+ logger(1, "Could not read timer expirations: %s", strerror(errno));
+ return 1;
+ }
+ if (set_analysed_timer() != 0)
+ {
+ logger(1, "Could not set timer: %s", strerror(errno));
+ return 1;
+ }
+
+ if (write_global_flow_stats() != 0)
+ {
+ logger(1, "%s", "Could not write global/flow stats.");
+ return 1;
+ }
+ }
+ else if (events[i].data.fd == sock->fd)
+ {
+ errno = 0;
+ enum nDPIsrvd_read_return read_ret = nDPIsrvd_read(sock);
+ if (read_ret != READ_OK)
+ {
+ logger(1, "nDPIsrvd read failed with: %s", nDPIsrvd_enum_to_string(read_ret));
+ return 1;
+ }
+
+ enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse_all(sock);
+ if (parse_ret != PARSE_NEED_MORE_DATA)
+ {
+ logger(1, "nDPIsrvd parse failed with: %s", nDPIsrvd_enum_to_string(parse_ret));
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, char ** argv)
+{
+ int retval = 1, epollfd = -1;
+
+ init_logging("nDPIsrvd-analysed");
+
+ if (parse_options(argc, argv) != 0)
+ {
+ goto failure;
+ }
+
+ sock = nDPIsrvd_socket_init(
+ 0, 0, 0, (stats_csv_outfile != NULL ? sizeof(struct flow_user_data) : 0), analysed_json_callback, NULL, NULL);
+ if (sock == NULL)
+ {
+ logger_early(1, "%s", "nDPIsrvd socket memory allocation failed!");
+ goto failure;
+ }
+
+ if (nDPIsrvd_setup_address(&sock->address, serv_optarg) != 0)
+ {
+ fprintf(stderr, "%s: Could not parse address `%s'\n", argv[0], serv_optarg);
+ goto failure;
+ }
+
+ printf("Recv buffer size: %u\n", NETWORK_BUFFER_MAX_SIZE);
+ printf("Connecting to `%s'..\n", serv_optarg);
+
+ if (nDPIsrvd_connect(sock) != CONNECT_OK)
+ {
+ logger_early(1, "nDPIsrvd socket connect to %s failed!", serv_optarg);
+ goto failure;
+ }
+
+ if (nDPIsrvd_set_nonblock(sock) != 0)
+ {
+ logger_early(1, "nDPIsrvd set nonblock failed: %s", strerror(errno));
+ goto failure;
+ }
+
+ signal(SIGUSR1, sighandler);
+ signal(SIGUSR2, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+
+ if (daemonize_with_pidfile(pidfile) != 0)
+ {
+ goto failure;
+ }
+
+ if (user != NULL)
+ {
+ struct passwd * pwd;
+ struct group * grp;
+ gid_t gid;
+
+ errno = 0;
+ pwd = getpwnam(user);
+ if (pwd == NULL)
+ {
+ logger_early(1, "Get user failed: %s", strerror(errno));
+ goto failure;
+ }
+
+ if (group != NULL)
+ {
+ errno = 0;
+ grp = getgrnam(group);
+ if (grp == NULL)
+ {
+ logger_early(1, "Get group failed: %s", strerror(errno));
+ goto failure;
+ }
+ gid = grp->gr_gid;
+ }
+ else
+ {
+ gid = pwd->pw_gid;
+ }
+
+ if (csv_outfile != NULL &&
+ (chmod(csv_outfile, S_IRUSR | S_IWUSR) != 0 || chown(csv_outfile, pwd->pw_uid, gid) != 0))
+ {
+ logger_early(1, "Change user/group of `%s' failed: %s", csv_outfile, strerror(errno));
+ goto failure;
+ }
+ if (stats_csv_outfile != NULL &&
+ (chmod(stats_csv_outfile, S_IRUSR | S_IWUSR) != 0 || chown(stats_csv_outfile, pwd->pw_uid, gid) != 0))
+ {
+ logger_early(1, "Change user/group of `%s' failed: %s", stats_csv_outfile, strerror(errno));
+ goto failure;
+ }
+ }
+
+ errno = 0;
+ if (user != NULL && change_user_group(user, group, pidfile) != 0)
+ {
+ if (errno != 0)
+ {
+ logger_early(1, "Change user/group failed: %s", strerror(errno));
+ }
+ else
+ {
+ logger_early(1, "Change user/group failed.");
+ }
+
+ goto failure;
+ }
+
+ epollfd = epoll_create1(0);
+ if (epollfd < 0)
+ {
+ logger_early(1, "Error creating epoll: %s", strerror(errno));
+ goto failure;
+ }
+
+ if (stats_csv_fp != NULL)
+ {
+ if (create_analysed_timer() != 0)
+ {
+ logger_early(1, "Error creating timer: %s", strerror(errno));
+ goto failure;
+ }
+
+ {
+ struct epoll_event timer_event = {.data.fd = analysed_timerfd, .events = EPOLLIN};
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, analysed_timerfd, &timer_event) < 0)
+ {
+ logger_early(1, "Error adding JSON fd to epoll: %s", strerror(errno));
+ goto failure;
+ }
+ }
+ }
+
+ {
+ struct epoll_event socket_event = {.data.fd = sock->fd, .events = EPOLLIN};
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock->fd, &socket_event) < 0)
+ {
+ logger_early(1, "Error adding nDPIsrvd socket fd to epoll: %s", strerror(errno));
+ goto failure;
+ }
+ }
+
+ logger(0, "%s", "Initialization succeeded.");
+ retval = mainloop(epollfd, sock);
+failure:
+ nDPIsrvd_socket_free(&sock);
+ daemonize_shutdown(pidfile);
+ shutdown_logging();
+
+ if (csv_fp != NULL)
+ {
+ fflush(csv_fp);
+ fclose(csv_fp);
+ }
+
+ if (stats_csv_fp != NULL)
+ {
+ fflush(stats_csv_fp);
+ fclose(stats_csv_fp);
+ }
+
+ return retval;
+}
diff --git a/examples/c-captured/c-captured.c b/examples/c-captured/c-captured.c
new file mode 100644
index 000000000..98df7c4b8
--- /dev/null
+++ b/examples/c-captured/c-captured.c
@@ -0,0 +1,1391 @@
+#include <arpa/inet.h>
+#include <errno.h>
+#include <linux/limits.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+#include <pcap/pcap.h>
+#include <signal.h>
+#include <stdbool.h> // ndpi_typedefs.h
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <ndpi_typedefs.h>
+#include <ndpi_api.h>
+
+#include "nDPIsrvd.h"
+#include "utarray.h"
+#include "utils.h"
+
+// #define VERBOSE
+#define DEFAULT_DATADIR "/tmp/nDPId-captured"
+
+struct packet_data
+{
+ nDPIsrvd_ull error_event_id;
+ nDPIsrvd_ull packet_id;
+ nDPIsrvd_ull packet_datalink;
+ nDPIsrvd_ull packet_ts_sec;
+ nDPIsrvd_ull packet_ts_usec;
+ nDPIsrvd_ull packet_len;
+ int base64_packet_size;
+ union
+ {
+ char * base64_packet;
+ char const * base64_packet_const;
+ };
+};
+
+struct flow_packet_data
+{
+ nDPIsrvd_ull packet_ts_sec;
+ nDPIsrvd_ull packet_ts_usec;
+ nDPIsrvd_ull packet_len;
+ int base64_packet_size;
+ union
+ {
+ char * base64_packet;
+ char const * base64_packet_const;
+ };
+};
+
+struct global_user_data
+{
+ UT_array * packets; // packet_data
+};
+
+struct flow_user_data
+{
+ uint8_t new_seen : 1;
+ uint8_t detection_finished : 1;
+ uint8_t guessed : 1;
+ uint8_t detected : 1;
+ uint8_t risky : 1;
+ uint8_t midstream : 1;
+ nDPIsrvd_ull flow_datalink;
+ nDPIsrvd_ull flow_max_packets;
+ nDPIsrvd_ull flow_tot_l4_payload_len;
+ UT_array * packets; // flow_packet_data
+};
+
+static struct nDPIsrvd_socket * ndpisrvd_socket = NULL;
+static int main_thread_shutdown = 0;
+
+static char * pidfile = NULL;
+static char * serv_optarg = NULL;
+static nDPIsrvd_ull pcap_filename_rotation = 0;
+static time_t pcap_filename_last_rotation = 0;
+static struct tm pcap_filename_last_rotation_tm = {};
+static char * user = NULL;
+static char * group = NULL;
+static uint8_t logging_mode = 0;
+static uint8_t capture_mode = 0;
+static char * datadir = NULL;
+static uint8_t process_guessed = 0;
+static uint8_t process_undetected = 0;
+static ndpi_risk process_risky = NDPI_NO_RISK;
+static uint8_t process_midstream = 0;
+static uint8_t ignore_empty_flows = 0;
+
+size_t const max_packet_len = 65535;
+
+#ifdef ENABLE_MEMORY_PROFILING
+void nDPIsrvd_memprof_log_alloc(size_t alloc_size)
+{
+ (void)alloc_size;
+}
+
+void nDPIsrvd_memprof_log_free(size_t free_size)
+{
+ (void)free_size;
+}
+
+void nDPIsrvd_memprof_log(char const * const format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
+ vfprintf(stderr, format, ap);
+ fprintf(stderr, "%s\n", "");
+ va_end(ap);
+}
+#endif
+
+static int pcap_open_or_append(int packet_datalink,
+ const char * const filename,
+ pcap_t ** const p,
+ pcap_dumper_t ** const pd)
+{
+ *p = pcap_open_dead(packet_datalink, max_packet_len);
+ if (*p == NULL)
+ {
+ return 1;
+ }
+
+ if (access(filename, F_OK) == 0)
+ {
+ *pd = pcap_dump_open_append(*p, filename);
+ }
+ else
+ {
+ *pd = pcap_dump_open(*p, filename);
+ }
+
+ if (*pd == NULL)
+ {
+ logger(1, "pcap error %s", pcap_geterr(*p));
+ pcap_close(*p);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void decode_base64(pcap_dumper_t * const pd,
+ struct packet_data * const pd_elt,
+ struct flow_packet_data * const fd_elt)
+{
+ char const * base64_packet;
+ size_t base64_packet_size;
+ nDPIsrvd_ull packet_ts_sec, packet_ts_usec;
+ unsigned char pkt_buf[max_packet_len];
+ size_t pkt_buf_len = sizeof(pkt_buf);
+
+ if (pd_elt != NULL)
+ {
+ base64_packet = pd_elt->base64_packet;
+ base64_packet_size = pd_elt->base64_packet_size;
+ packet_ts_sec = pd_elt->packet_ts_sec;
+ packet_ts_usec = pd_elt->packet_ts_usec;
+ }
+ else if (fd_elt != NULL)
+ {
+ base64_packet = fd_elt->base64_packet;
+ base64_packet_size = fd_elt->base64_packet_size;
+ packet_ts_sec = fd_elt->packet_ts_sec;
+ packet_ts_usec = fd_elt->packet_ts_usec;
+ }
+ else
+ {
+ logger(1, "%s", "BUG: Can not decode base64 packet.");
+ return;
+ }
+
+ if (nDPIsrvd_base64decode(base64_packet, base64_packet_size, pkt_buf, &pkt_buf_len) != 0 || pkt_buf_len == 0)
+ {
+ logger(1, "packet base64 decode failed (%zu bytes): %s", base64_packet_size, base64_packet);
+ }
+ else
+ {
+ struct pcap_pkthdr phdr;
+ phdr.ts.tv_sec = packet_ts_sec;
+ phdr.ts.tv_usec = packet_ts_usec;
+ phdr.caplen = pkt_buf_len;
+ phdr.len = pkt_buf_len;
+ pcap_dump((unsigned char *)pd, &phdr, pkt_buf);
+ }
+}
+
+static void packet_data_copy(void * dst, const void * src)
+{
+ struct packet_data * const pd_dst = (struct packet_data *)dst;
+ struct packet_data const * const pd_src = (struct packet_data *)src;
+ *pd_dst = *pd_src;
+ if (pd_src->base64_packet != NULL && pd_src->base64_packet_size > 0)
+ {
+ pd_dst->base64_packet = strndup(pd_src->base64_packet, pd_src->base64_packet_size);
+ }
+ else
+ {
+ pd_dst->base64_packet = NULL;
+ pd_dst->base64_packet_size = 0;
+ }
+}
+
+static void packet_data_dtor(void * elt)
+{
+ struct packet_data * const pd_elt = (struct packet_data *)elt;
+ if (pd_elt->base64_packet != NULL)
+ {
+ free(pd_elt->base64_packet);
+ pd_elt->base64_packet = NULL;
+ pd_elt->base64_packet_size = 0;
+ }
+}
+
+static void flow_packet_data_copy(void * dst, const void * src)
+{
+ struct flow_packet_data * const pd_dst = (struct flow_packet_data *)dst;
+ struct flow_packet_data const * const pd_src = (struct flow_packet_data *)src;
+ *pd_dst = *pd_src;
+ if (pd_src->base64_packet != NULL && pd_src->base64_packet_size > 0)
+ {
+ pd_dst->base64_packet = strndup(pd_src->base64_packet, pd_src->base64_packet_size);
+ }
+ else
+ {
+ pd_dst->base64_packet = NULL;
+ pd_dst->base64_packet_size = 0;
+ }
+}
+
+static void flow_packet_data_dtor(void * elt)
+{
+ struct flow_packet_data * const pd_elt = (struct flow_packet_data *)elt;
+ if (pd_elt->base64_packet != NULL)
+ {
+ free(pd_elt->base64_packet);
+ pd_elt->base64_packet = NULL;
+ pd_elt->base64_packet_size = 0;
+ }
+}
+
+static const UT_icd packet_data_icd = {sizeof(struct packet_data), NULL, packet_data_copy, packet_data_dtor};
+static const UT_icd flow_packet_data_icd = {sizeof(struct flow_packet_data),
+ NULL,
+ flow_packet_data_copy,
+ flow_packet_data_dtor};
+
+static int utarray_packets_init(struct global_user_data * const global_user)
+{
+ if (capture_mode == 0)
+ {
+ return -1;
+ }
+
+ if (global_user->packets == NULL)
+ {
+ utarray_new(global_user->packets, &packet_data_icd);
+ }
+
+ return global_user->packets != NULL;
+}
+
+static void utarray_packets_free(struct global_user_data * const global_user)
+{
+ if (capture_mode == 0)
+ {
+ return;
+ }
+
+ if (global_user->packets != NULL)
+ {
+ utarray_free(global_user->packets);
+ global_user->packets = NULL;
+ }
+}
+
+static int utarray_packets_add(struct global_user_data * const global_user, struct packet_data const * const pd)
+{
+ if (capture_mode == 0)
+ {
+ return -1;
+ }
+
+ unsigned int array_len = utarray_len(global_user->packets);
+
+ utarray_push_back(global_user->packets, pd);
+
+ return utarray_len(global_user->packets) == array_len + 1u;
+}
+
+static struct packet_data * utarray_packets_get(struct global_user_data * const global_user, nDPIsrvd_ull packet_id)
+{
+ if (capture_mode == 0)
+ {
+ return NULL;
+ }
+
+ for (size_t i = 0; i < utarray_len(global_user->packets); ++i)
+ {
+ struct packet_data * const pd_elt = (struct packet_data *)utarray_eltptr(global_user->packets, i);
+
+ if (pd_elt->packet_id == packet_id)
+ {
+ return pd_elt;
+ }
+ }
+
+ return NULL;
+}
+
+static int utarray_flow_packets_init(struct flow_user_data * const flow_user)
+{
+ if (capture_mode == 0)
+ {
+ return -1;
+ }
+
+ if (flow_user->packets == NULL)
+ {
+ utarray_new(flow_user->packets, &flow_packet_data_icd);
+ }
+
+ return flow_user->packets != NULL;
+}
+
+static void utarray_flow_packets_free(struct flow_user_data * const flow_user)
+{
+ if (capture_mode == 0)
+ {
+ return;
+ }
+
+ if (flow_user->packets != NULL)
+ {
+ utarray_free(flow_user->packets);
+ flow_user->packets = NULL;
+ }
+}
+
+static int utarray_flow_packets_add(struct flow_user_data * const flow_user, struct flow_packet_data const * const pd)
+{
+ if (capture_mode == 0)
+ {
+ return -1;
+ }
+
+ unsigned int array_len = utarray_len(flow_user->packets);
+
+ utarray_push_back(flow_user->packets, pd);
+
+ return utarray_len(flow_user->packets) == array_len + 1u;
+}
+
+static void set_ndpi_risk(ndpi_risk * const risk, nDPIsrvd_ull risk_to_add)
+{
+ if (risk_to_add == 0)
+ {
+ *risk = (ndpi_risk)-1;
+ }
+ else
+ {
+ *risk |= 1ull << --risk_to_add;
+ }
+}
+
+static void unset_ndpi_risk(ndpi_risk * const risk, nDPIsrvd_ull risk_to_del)
+{
+ if (risk_to_del == 0)
+ {
+ *risk = 0;
+ }
+ else
+ {
+ *risk &= ~(1ull << --risk_to_del);
+ }
+}
+
+static int has_ndpi_risk(ndpi_risk * const risk, nDPIsrvd_ull risk_to_check)
+{
+ return (*risk & (1ull << --risk_to_check)) != 0;
+}
+
+static int generate_pcap_appendix(char * const appendix, size_t appendix_size)
+{
+ if (pcap_filename_rotation > 0)
+ {
+ time_t current_time = time(NULL);
+
+ if (current_time >= pcap_filename_last_rotation + (time_t)pcap_filename_rotation)
+ {
+ pcap_filename_last_rotation = current_time;
+ if (localtime_r(&pcap_filename_last_rotation, &pcap_filename_last_rotation_tm) == NULL)
+ {
+ return 1;
+ }
+ }
+
+ if (strftime(appendix, appendix_size, "-%d_%m_%y-%H_%M_%S", &pcap_filename_last_rotation_tm) == 0)
+ {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static char * packet_generate_pcap_filename(char * const dest, size_t size, nDPIsrvd_ull packet_datalink)
+{
+ char appendix[32] = {};
+
+ if (generate_pcap_appendix(appendix, sizeof(appendix)) != 0)
+ {
+ return NULL;
+ }
+
+ int ret = snprintf(dest, size, "%s/packet%s-dlt%llu.pcap", datadir, appendix, packet_datalink);
+ if (ret <= 0 || (size_t)ret > size)
+ {
+ return NULL;
+ }
+
+ return dest;
+}
+
+static char * flow_generate_pcap_filename(struct flow_user_data const * const flow_user, char * const dest, size_t size)
+{
+ char appendix[32] = {};
+
+ if (generate_pcap_appendix(appendix, sizeof(appendix)) != 0)
+ {
+ return NULL;
+ }
+
+ if (flow_user->guessed != 0 || flow_user->detected == 0 || flow_user->risky != 0 || flow_user->midstream != 0)
+ {
+ char const * flow_type = NULL;
+
+ if (flow_user->midstream != 0)
+ {
+ flow_type = "midstream";
+ }
+ else if (flow_user->guessed != 0)
+ {
+ flow_type = "guessed";
+ }
+ else if (flow_user->detected == 0)
+ {
+ flow_type = "undetected";
+ }
+ else if (flow_user->risky != 0)
+ {
+ flow_type = "risky";
+ }
+ else
+ {
+ flow_type = "unknown-type";
+ }
+
+ int ret = snprintf(dest, size, "%s/flow-%s%s.pcap", datadir, flow_type, appendix);
+ if (ret <= 0 || (size_t)ret > size)
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+ return NULL;
+ }
+
+ return dest;
+}
+
+static int packet_write_pcap_file(struct global_user_data const * const global_user)
+{
+ UT_array * const pd_array = global_user->packets;
+
+ if (utarray_len(pd_array) == 0)
+ {
+ logger(0, "Can not dump packets to pcap; packet array empty");
+ return 1;
+ }
+
+ while (utarray_len(pd_array) > 0)
+ {
+ struct packet_data * const pd_elt = (struct packet_data *)utarray_front(pd_array);
+ if (pd_elt == NULL)
+ {
+ return 1;
+ }
+ nDPIsrvd_ull packet_datalink = pd_elt->packet_datalink;
+
+ char filename[PATH_MAX];
+ if (packet_generate_pcap_filename(filename, sizeof(filename), packet_datalink) == NULL)
+ {
+ logger(1, "%s", "Internal error. Could not generate PCAP filename, exit ..");
+ return 1;
+ }
+
+ pcap_t * p = NULL;
+ pcap_dumper_t * pd = NULL;
+ if (pcap_open_or_append(packet_datalink, filename, &p, &pd) != 0)
+ {
+ logger(1, "Can not dump packets to pcap; file open/append failed");
+ return 1;
+ }
+
+ for (size_t i = 0; i < utarray_len(pd_array); ++i)
+ {
+ struct packet_data * const pd_elt_dmp = (struct packet_data *)utarray_eltptr(pd_array, i);
+ if (pd_elt_dmp == NULL)
+ {
+ return 1;
+ }
+
+ decode_base64(pd, pd_elt_dmp, NULL);
+ }
+#ifdef VERBOSE
+ printf("packets dumped to %s\n", filename);
+#endif
+ pcap_dump_close(pd);
+ pcap_close(p);
+
+ size_t i = 0;
+ while (utarray_len(pd_array) > 0)
+ {
+ struct packet_data * const pd_elt_rm = (struct packet_data *)utarray_eltptr(pd_array, i);
+ if (pd_elt_rm == NULL)
+ {
+ return 1;
+ }
+
+ if (pd_elt_rm->packet_datalink == packet_datalink)
+ {
+#ifdef VERBOSE
+ printf("Remove packet index %zu with datalink %llu\n", i, packet_datalink);
+#endif
+ utarray_erase(pd_array, i, 1);
+ }
+ else
+ {
+ i++;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int flow_write_pcap_file(struct flow_user_data const * const flow_user, char const * const filename)
+{
+ UT_array const * const pd_array = flow_user->packets;
+ int packet_datalink = flow_user->flow_datalink;
+ pcap_t * p = NULL;
+ pcap_dumper_t * pd = NULL;
+
+ if (utarray_len(pd_array) == 0)
+ {
+ logger(1, "Can not dump flow packets to pcap; flow packet array empty");
+ return 0;
+ }
+
+ if (pcap_open_or_append(packet_datalink, filename, &p, &pd) != 0)
+ {
+ logger(1, "Can not dump flow packets to pcap; file open/append failed");
+ return 0;
+ }
+
+ struct flow_packet_data * pd_elt = (struct flow_packet_data *)utarray_front(pd_array);
+ do
+ {
+ if (pd_elt == NULL)
+ {
+ break;
+ }
+
+ decode_base64(pd, NULL, pd_elt);
+ } while ((pd_elt = (struct flow_packet_data *)utarray_next(pd_array, pd_elt)) != NULL);
+
+ pcap_dump_close(pd);
+ pcap_close(p);
+
+ return 0;
+}
+
+#ifdef VERBOSE
+static void flow_packet_data_print(struct flow_user_data const * const flow_user)
+{
+ UT_array const * const pd_array = flow_user->packets;
+
+ printf("packet-data array size(): %u\n", pd_array->n);
+ struct flow_packet_data * pd_elt = (struct flow_packet_data *)utarray_front(pd_array);
+ do
+ {
+ if (pd_elt == NULL)
+ {
+ break;
+ }
+ printf("\tpacket-data base64 length: %d\n", pd_elt->base64_packet_size);
+ } while ((pd_elt = (struct flow_packet_data *)utarray_next(pd_array, pd_elt)) != NULL);
+}
+#else
+#define flow_packet_data_print(pd_array)
+#endif
+
+static enum nDPIsrvd_conversion_return perror_ull(enum nDPIsrvd_conversion_return retval, char const * const prefix)
+{
+ switch (retval)
+ {
+ case CONVERSION_OK:
+ break;
+
+ case CONVERISON_KEY_NOT_FOUND:
+ logger(1, "%s: Key not found.", prefix);
+ break;
+ case CONVERSION_NOT_A_NUMBER:
+ logger(1, "%s: Not a valid number.", prefix);
+ break;
+ case CONVERSION_RANGE_EXCEEDED:
+ logger(1, "%s: Number too large.", prefix);
+ break;
+
+ default:
+ logger(1, "Internal error, invalid conversion return value.");
+ break;
+ }
+
+ return retval;
+}
+
+static void log_event(struct nDPIsrvd_socket const * const sock,
+ struct nDPIsrvd_flow * const flow,
+ char const * const event_name)
+{
+ struct nDPIsrvd_json_token const * const src_ip = TOKEN_GET_SZ(sock, "src_ip");
+ struct nDPIsrvd_json_token const * const dst_ip = TOKEN_GET_SZ(sock, "dst_ip");
+ struct nDPIsrvd_json_token const * const l4_proto = TOKEN_GET_SZ(sock, "l4_proto");
+ struct nDPIsrvd_json_token const * const src_port = TOKEN_GET_SZ(sock, "src_port");
+ struct nDPIsrvd_json_token const * const dst_port = TOKEN_GET_SZ(sock, "dst_port");
+ char const *src_ip_str = NULL, *dst_ip_str = NULL, *l4_proto_str = NULL;
+ size_t src_ip_len, dst_ip_len, l4_proto_len;
+ char src_port_str[8] = {}, dst_port_str[8] = {};
+ char const * const na = "n/a";
+ const int na_len = (int)strlen(na);
+
+ if (src_ip == NULL || dst_ip == NULL)
+ {
+ logger(1, "Flow %llu: Missing essential source/destination IP address.", flow->id_as_ull);
+ }
+ else
+ {
+ src_ip_str = TOKEN_GET_VALUE(sock, src_ip, &src_ip_len);
+ dst_ip_str = TOKEN_GET_VALUE(sock, dst_ip, &dst_ip_len);
+ }
+ if (l4_proto != NULL)
+ {
+ l4_proto_str = TOKEN_GET_VALUE(sock, l4_proto, &l4_proto_len);
+ }
+ if (src_port != NULL && dst_port != NULL)
+ {
+ size_t src_port_len = 0;
+ size_t dst_port_len = 0;
+ char const * const tmp_src_port_str = TOKEN_GET_VALUE(sock, src_port, &src_port_len);
+ char const * const tmp_dst_port_str = TOKEN_GET_VALUE(sock, dst_port, &dst_port_len);
+ if (tmp_src_port_str != NULL && tmp_dst_port_str != NULL)
+ {
+ snprintf(src_port_str, nDPIsrvd_ARRAY_LENGTH(src_port_str), ":%.*s", (int)src_port_len, tmp_src_port_str);
+ snprintf(dst_port_str, nDPIsrvd_ARRAY_LENGTH(dst_port_str), ":%.*s", (int)dst_port_len, tmp_dst_port_str);
+ }
+ }
+
+ if (is_console_logger_enabled() != 0)
+ {
+ printf("Flow %llu %s: %.*s %.*s%s -> %.*s%s\n",
+ flow->id_as_ull,
+ event_name,
+ (l4_proto_str != NULL ? (int)l4_proto_len : na_len),
+ (l4_proto_str != NULL ? l4_proto_str : na),
+ (src_ip_str != NULL ? (int)src_ip_len : na_len),
+ (src_ip_str != NULL ? src_ip_str : na),
+ src_port_str,
+ (dst_ip_str != NULL ? (int)dst_ip_len : na_len),
+ (dst_ip_str != NULL ? dst_ip_str : na),
+ dst_port_str);
+ }
+ else
+ {
+ logger(0,
+ "Flow %llu %s: %.*s %.*s%s -> %.*s%s",
+ flow->id_as_ull,
+ event_name,
+ (l4_proto_str != NULL ? (int)l4_proto_len : na_len),
+ (l4_proto_str != NULL ? l4_proto_str : na),
+ (src_ip_str != NULL ? (int)src_ip_len : na_len),
+ (src_ip_str != NULL ? src_ip_str : na),
+ src_port_str,
+ (dst_ip_str != NULL ? (int)dst_ip_len : na_len),
+ (dst_ip_str != NULL ? dst_ip_str : na),
+ dst_port_str);
+ }
+}
+
+static enum nDPIsrvd_callback_return captured_json_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow)
+{
+ (void)instance;
+ (void)thread_data;
+
+ struct global_user_data * const global_user = (struct global_user_data *)&sock->global_user_data[0];
+
+ if (flow == NULL)
+ {
+ nDPIsrvd_ull packet_id = 0ull;
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "packet_id"), &packet_id), "packet_id");
+
+ if (utarray_packets_init(global_user) == 0)
+ {
+ logger(1, "Memory allocation for packet data failed.");
+ return CALLBACK_ERROR;
+ }
+
+ if (TOKEN_GET_SZ(sock, "error_event_name") != NULL)
+ {
+ logger(1, "Received an error event for packet id %llu.", packet_id);
+
+ if (TOKEN_GET_SZ(sock, "error_event_id") == NULL)
+ {
+ logger(1, "Missing error event id.");
+ return CALLBACK_ERROR;
+ }
+
+ nDPIsrvd_ull error_event_id = 0;
+ TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "error_event_id"), &error_event_id);
+ struct packet_data pd = {.error_event_id = error_event_id, .packet_id = packet_id};
+ if (utarray_packets_add(global_user, &pd) == 0)
+ {
+ logger(1, "Could not add packet to array with id %llu.", packet_id);
+ return CALLBACK_ERROR;
+ }
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, TOKEN_GET_SZ(sock, "packet_event_name"), "packet") != 0)
+ {
+ logger(1, "Received an packet event for packet id %llu.", packet_id);
+
+ if (capture_mode != 0)
+ {
+ struct packet_data * const pd = utarray_packets_get(global_user, packet_id);
+ if (pd == NULL)
+ {
+ logger(1, "Received an packet event w/o a previous error event for packet id %llu.", packet_id);
+ return CALLBACK_OK;
+ }
+
+ if (pd->packet_id != packet_id)
+ {
+ logger(1,
+ "Received a packet event with a different packet id then the one seen in the error event: "
+ "%llu != %llu.",
+ packet_id,
+ pd->packet_id);
+ return CALLBACK_OK;
+ }
+
+ struct nDPIsrvd_json_token const * const pkt = TOKEN_GET_SZ(sock, "pkt");
+ if (pkt == NULL)
+ {
+ logger(1, "%s", "No packet data available.");
+ logger(1,
+ "JSON message: '%.*s'",
+ nDPIsrvd_json_buffer_length(sock),
+ nDPIsrvd_json_buffer_string(sock));
+ return CALLBACK_OK;
+ }
+
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "pkt_datalink"), &pd->packet_datalink),
+ "pkt_datalink");
+
+ nDPIsrvd_ull thread_ts_usec = 0ull;
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "thread_ts_usec"), &thread_ts_usec),
+ "thread_ts_usec");
+
+ nDPIsrvd_ull pkt_len = 0ull;
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "pkt_caplen"), &pkt_len), "pkt_caplen");
+
+ pd->packet_ts_sec = thread_ts_usec / (1000 * 1000);
+ pd->packet_ts_usec = (thread_ts_usec % (1000 * 1000));
+ pd->packet_len = pkt_len;
+ pd->base64_packet_size = nDPIsrvd_get_token_size(sock, pkt);
+ pd->base64_packet_const = strndup(nDPIsrvd_get_token_value(sock, pkt), pd->base64_packet_size);
+
+ if (packet_write_pcap_file(global_user) != 0)
+ {
+ logger(1, "%s", "Could not dump non-flow packet data");
+ return CALLBACK_OK;
+ }
+ }
+
+ utarray_packets_free(global_user);
+ }
+
+ return CALLBACK_OK;
+ }
+
+ struct flow_user_data * const flow_user = (struct flow_user_data *)flow->flow_user_data;
+
+ if (flow_user == NULL || flow_user->detection_finished != 0)
+ {
+ return CALLBACK_OK;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, TOKEN_GET_SZ(sock, "flow_state"), "finished") != 0)
+ {
+ flow_user->detection_finished = 1;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, TOKEN_GET_SZ(sock, "packet_event_name"), "packet-flow") != 0)
+ {
+ struct nDPIsrvd_json_token const * const pkt = TOKEN_GET_SZ(sock, "pkt");
+ if (pkt == NULL)
+ {
+ logger(1, "%s", "No packet data available.");
+ logger(1, "JSON message: '%.*s'", nDPIsrvd_json_buffer_length(sock), nDPIsrvd_json_buffer_string(sock));
+ return CALLBACK_OK;
+ }
+
+ if (utarray_flow_packets_init(flow_user) == 0)
+ {
+ logger(1, "%s", "Memory allocation for captured packets failed.");
+ return CALLBACK_ERROR;
+ }
+
+ nDPIsrvd_ull thread_ts_usec = 0ull;
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "thread_ts_usec"), &thread_ts_usec), "thread_ts_usec");
+
+ nDPIsrvd_ull pkt_len = 0ull;
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "pkt_caplen"), &pkt_len), "pkt_caplen");
+
+ struct flow_packet_data pd = {.packet_ts_sec = thread_ts_usec / (1000 * 1000),
+ .packet_ts_usec = (thread_ts_usec % (1000 * 1000)),
+ .packet_len = pkt_len,
+ .base64_packet_size = nDPIsrvd_get_token_size(sock, pkt),
+ .base64_packet_const = nDPIsrvd_get_token_value(sock, pkt)};
+ if (utarray_flow_packets_add(flow_user, &pd) == 0)
+ {
+ logger(1, "%s", "Memory allocation to add a captured packet failed.");
+ return CALLBACK_ERROR;
+ }
+ }
+
+ {
+ struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
+
+ if (flow_event_name != NULL)
+ {
+ nDPIsrvd_ull nmb = 0;
+
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_src_tot_l4_payload_len"), &nmb),
+ "flow_src_tot_l4_payload_len");
+ flow_user->flow_tot_l4_payload_len += nmb;
+
+ nmb = 0;
+
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_dst_tot_l4_payload_len"), &nmb),
+ "flow_dst_tot_l4_payload_len");
+ flow_user->flow_tot_l4_payload_len += nmb;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "new") != 0)
+ {
+ flow_user->new_seen = 1;
+
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_datalink"), &flow_user->flow_datalink),
+ "flow_datalink");
+ perror_ull(TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_max_packets"), &flow_user->flow_max_packets),
+ "flow_max_packets");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, TOKEN_GET_SZ(sock, "midstream"), "1") != 0)
+ {
+ flow_user->midstream = 1;
+ }
+
+ return CALLBACK_OK;
+ }
+ else if (flow_user->new_seen == 0)
+ {
+ return CALLBACK_OK;
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "guessed") != 0)
+ {
+ flow_user->guessed = 1;
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "not-detected") != 0)
+ {
+ flow_user->detected = 0;
+ flow_user->detection_finished = 1;
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detected") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detection-update"))
+ {
+ struct nDPIsrvd_json_token const * const flow_risk = TOKEN_GET_SZ(sock, "ndpi", "flow_risk");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ flow_user->detected = 1;
+
+ if (flow_risk != NULL)
+ {
+ while ((current = nDPIsrvd_get_next_token(sock, flow_risk, &next_child_index)) != NULL)
+ {
+ nDPIsrvd_ull numeric_risk_value = (nDPIsrvd_ull)-1;
+
+ if (str_value_to_ull(TOKEN_GET_KEY(sock, current, NULL), &numeric_risk_value) == CONVERSION_OK &&
+ numeric_risk_value < NDPI_MAX_RISK && has_ndpi_risk(&process_risky, numeric_risk_value) != 0)
+ {
+ flow_user->risky = 1;
+ }
+ }
+ }
+ }
+
+ if (flow_user->detection_finished != 0 &&
+ ((flow_user->guessed != 0 && process_guessed != 0) ||
+ (flow_user->detected == 0 && process_undetected != 0) || (flow_user->risky != 0 && process_risky != 0) ||
+ (flow_user->midstream != 0 && process_midstream != 0)))
+ {
+ if (flow_user->guessed != 0 && flow_user->detected != 0)
+ {
+ log_event(sock, flow, "BUG: guessed and detected at the same time");
+ }
+
+ if (logging_mode != 0)
+ {
+ if (flow_user->guessed != 0)
+ log_event(sock, flow, "guessed");
+ if (flow_user->detected == 0)
+ log_event(sock, flow, "not-detected");
+ if (flow_user->risky != 0)
+ log_event(sock, flow, "risky");
+ if (flow_user->midstream != 0)
+ log_event(sock, flow, "midstream");
+ }
+
+ if (flow_user->packets == NULL || flow_user->flow_max_packets == 0 || utarray_len(flow_user->packets) == 0)
+ {
+ if (capture_mode != 0)
+ {
+ log_event(sock, flow, "No packets captured");
+ }
+ }
+ else if (capture_mode != 0)
+ {
+ flow_packet_data_print(flow_user);
+ if (ignore_empty_flows == 0 || flow_user->flow_tot_l4_payload_len > 0)
+ {
+ char pcap_filename[PATH_MAX];
+ if (flow_generate_pcap_filename(flow_user, pcap_filename, sizeof(pcap_filename)) == NULL)
+ {
+ log_event(sock, flow, "Internal error. Could not generate PCAP filename, exit ..");
+ return CALLBACK_ERROR;
+ }
+#ifdef VERBOSE
+ printf("Flow %llu saved to %s\n", flow->id_as_ull, pcap_filename);
+#endif
+ errno = 0;
+ if (flow_write_pcap_file(flow_user, pcap_filename) != 0)
+ {
+ logger(1, "Could not dump packet data to pcap file %s: %s", pcap_filename, strerror(errno));
+ return CALLBACK_OK;
+ }
+ }
+ }
+
+ utarray_flow_packets_free(flow_user);
+ }
+ }
+
+ return CALLBACK_OK;
+}
+
+static void nDPIsrvd_write_flow_info_cb(struct nDPIsrvd_socket const * sock,
+ struct nDPIsrvd_instance const * instance,
+ struct nDPIsrvd_thread_data const * thread_data,
+ struct nDPIsrvd_flow const * flow,
+ void * user_data)
+{
+ (void)sock;
+ (void)instance;
+ (void)thread_data;
+ (void)user_data;
+
+ struct flow_user_data const * const flow_user = (struct flow_user_data const *)flow->flow_user_data;
+
+ fprintf(stderr,
+ "[Flow %4llu][ptr: "
+#ifdef __LP64__
+ "0x%016llx"
+#else
+ "0x%08lx"
+#endif
+ "][last-seen: %13llu][finished: %u][detected: %u][risky: "
+ "%u][total-L4-payload-length: "
+ "%4llu][packets-captured: %u]\n",
+ flow->id_as_ull,
+#ifdef __LP64__
+ (unsigned long long int)flow,
+#else
+ (unsigned long int)flow,
+#endif
+ flow->last_seen,
+ flow_user->detection_finished,
+ flow_user->detected,
+ flow_user->risky,
+ flow_user->flow_tot_l4_payload_len,
+ flow_user->packets != NULL ? utarray_len(flow_user->packets) : 0);
+
+ logger(0,
+ "[Flow %4llu][ptr: "
+#ifdef __LP64__
+ "0x%016llx"
+#else
+ "0x%08lx"
+#endif
+ "][last-seen: %13llu][finished: %u][detected: %u][risky: "
+ "%u][total-L4-payload-length: "
+ "%4llu][packets-captured: %u]",
+ flow->id_as_ull,
+#ifdef __LP64__
+ (unsigned long long int)flow,
+#else
+ (unsigned long int)flow,
+#endif
+ flow->last_seen,
+ flow_user->detection_finished,
+ flow_user->detected,
+ flow_user->risky,
+ flow_user->flow_tot_l4_payload_len,
+ flow_user->packets != NULL ? utarray_len(flow_user->packets) : 0);
+}
+
+static void sighandler(int signum)
+{
+ if (signum == SIGUSR1)
+ {
+ nDPIsrvd_flow_info(ndpisrvd_socket, nDPIsrvd_write_flow_info_cb, NULL);
+ }
+ else if (main_thread_shutdown == 0)
+ {
+ main_thread_shutdown = 1;
+ }
+}
+
+static void captured_flow_cleanup_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow,
+ enum nDPIsrvd_cleanup_reason reason)
+{
+ (void)sock;
+ (void)instance;
+ (void)thread_data;
+ (void)reason;
+
+ struct flow_user_data * const ud = (struct flow_user_data *)flow->flow_user_data;
+ if (ud != NULL && ud->packets != NULL)
+ {
+ utarray_free(ud->packets);
+ ud->packets = NULL;
+ }
+}
+
+static void print_usage(char const * const arg0)
+{
+ static char const usage[] =
+ "Usage: %s "
+ "[-c] [-d] [-p pidfile] [-s host] [-r rotate-every-n-seconds]\n"
+ "\t \t[-u user] [-g group] [-D dir] [-G] [-U] [-R risk] [-M]\n\n"
+ "\t-c\tLog all messages to stdout/stderr instead of syslog.\n"
+ "\t-d\tForking into background after initialization.\n"
+ "\t-p\tWrite the daemon PID to the given file path.\n"
+ "\t-s\tDestination where nDPIsrvd is listening on.\n"
+ "\t \tCan be either a path to UNIX socket or an IPv4/TCP-Port IPv6/TCP-Port tuple.\n"
+ "\t-r\tRotate PCAP files every n seconds\n"
+ "\t-u\tChange user.\n"
+ "\t-g\tChange group.\n"
+ "\t-l\tLogging mode: Log events and some flow metadata to syslog.\n"
+ "\t-L\tCapture mode: Capture and dump packets.\n"
+ "\t-D\tDatadir - Where to store PCAP files.\n"
+ "\t-G\tGuessed - Dump guessed flows to a PCAP file.\n"
+ "\t-U\tUndetected - Dump undetected flows to a PCAP file.\n"
+ "\t-R\tRisky - Dump risky flows to a PCAP file. See additional help below.\n"
+ "\t-M\tMidstream - Dump midstream flows to a PCAP file.\n"
+ "\t-E\tEmpty - Ignore flows w/o any layer 4 payload\n\n"
+ "\tPossible options for `-R' (can be specified multiple times, processed from left to right, ~ disables a "
+ "risk):\n"
+ "\t \tExample: -R0 -R~15 would enable all risks except risk with id 15\n";
+
+ fprintf(stderr, usage, arg0);
+#ifndef LIBNDPI_STATIC
+ fprintf(stderr, "\t\t%d - %s\n", 0, "Capture all risks");
+#else
+ fprintf(stderr, "\t\t%d - %s\n\t\t", 0, "Capture all risks");
+#endif
+ for (int risk = NDPI_NO_RISK + 1; risk < NDPI_MAX_RISK; ++risk)
+ {
+#ifndef LIBNDPI_STATIC
+ fprintf(stderr, "\t\t%d - %s%s", risk, ndpi_risk2str(risk), (risk == NDPI_MAX_RISK - 1 ? "\n\n" : "\n"));
+#else
+ fprintf(stderr, "%d%s", risk, (risk == NDPI_MAX_RISK - 1 ? "\n" : ","));
+#endif
+ }
+}
+
+static int parse_options(int argc, char ** argv)
+{
+ int opt;
+
+ while ((opt = getopt(argc, argv, "hcdp:s:r:u:g:lLD:GUR:ME")) != -1)
+ {
+ switch (opt)
+ {
+ case 'c':
+ enable_console_logger();
+ break;
+ case 'd':
+ daemonize_enable();
+ break;
+ case 'p':
+ free(pidfile);
+ pidfile = strdup(optarg);
+ break;
+ case 's':
+ free(serv_optarg);
+ serv_optarg = strdup(optarg);
+ break;
+ case 'r':
+ if (perror_ull(str_value_to_ull(optarg, &pcap_filename_rotation), "pcap_filename_rotation") !=
+ CONVERSION_OK)
+ {
+ fprintf(stderr, "%s: Argument for `-r' is not a number: %s\n", argv[0], optarg);
+ return 1;
+ }
+ break;
+ case 'u':
+ free(user);
+ user = strdup(optarg);
+ break;
+ case 'g':
+ free(group);
+ group = strdup(optarg);
+ break;
+ case 'l':
+ logging_mode = 1;
+ break;
+ case 'L':
+ capture_mode = 1;
+ break;
+ case 'D':
+ free(datadir);
+ datadir = strdup(optarg);
+ break;
+ case 'G':
+ process_guessed = 1;
+ break;
+ case 'U':
+ process_undetected = 1;
+ break;
+ case 'R':
+ {
+ char * value = (optarg[0] == '~' ? optarg + 1 : optarg);
+ nDPIsrvd_ull risk;
+ if (perror_ull(str_value_to_ull(value, &risk), "process_risky") != CONVERSION_OK)
+ {
+ fprintf(stderr, "%s: Argument for `-R' is not a number: %s\n", argv[0], optarg);
+ return 1;
+ }
+ if (risk >= NDPI_MAX_RISK)
+ {
+ fprintf(stderr, "%s: Invalid risk set: %s\n", argv[0], optarg);
+ return 1;
+ }
+ if (optarg[0] == '~')
+ {
+ unset_ndpi_risk(&process_risky, risk);
+ }
+ else
+ {
+ set_ndpi_risk(&process_risky, risk);
+ }
+ break;
+ }
+ case 'M':
+ process_midstream = 1;
+ break;
+ case 'E':
+ ignore_empty_flows = 1;
+ break;
+ default:
+ print_usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (logging_mode == 0 && capture_mode == 0)
+ {
+ fprintf(stderr, "%s: Neither `-l' nor `-L' given. See application usage for more information.\n", argv[0]);
+ return 1;
+ }
+
+ if (serv_optarg == NULL)
+ {
+ serv_optarg = strdup(DISTRIBUTOR_UNIX_SOCKET);
+ }
+
+ if (nDPIsrvd_setup_address(&ndpisrvd_socket->address, serv_optarg) != 0)
+ {
+ fprintf(stderr, "%s: Could not parse address `%s'\n", argv[0], serv_optarg);
+ return 1;
+ }
+
+ if (datadir == NULL)
+ {
+ datadir = strdup(DEFAULT_DATADIR);
+ }
+
+ if (process_guessed == 0 && process_undetected == 0 && process_risky == 0 && process_midstream == 0)
+ {
+ fprintf(stderr, "%s: Nothing to capture. Use at least one of -G / -U / -R / -M flags.\n", argv[0]);
+ return 1;
+ }
+
+ if (optind < argc)
+ {
+ fprintf(stderr, "Unexpected argument after options\n\n");
+ print_usage(argv[0]);
+ return 1;
+ }
+
+ if (capture_mode != 0)
+ {
+ errno = 0;
+ if (datadir[0] != '/')
+ {
+ fprintf(stderr,
+ "%s: PCAP capture directory must be absolut i.e. starting with `/', path given: `%s'\n",
+ argv[0],
+ datadir);
+ return 1;
+ }
+ if (mkdir(datadir, S_IRWXU) != 0 && errno != EEXIST)
+ {
+ fprintf(stderr, "%s: Could not create directory %s: %s\n", argv[0], datadir, strerror(errno));
+ return 1;
+ }
+ {
+ struct stat datadir_stat;
+ if (stat(datadir, &datadir_stat) != 0 || S_ISDIR(datadir_stat.st_mode) == 0)
+ {
+ fprintf(stderr, "%s: Data directory %s is not a directory\n", argv[0], datadir);
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int mainloop(void)
+{
+ enum nDPIsrvd_read_return read_ret = READ_OK;
+
+ while (main_thread_shutdown == 0)
+ {
+ read_ret = nDPIsrvd_read(ndpisrvd_socket);
+ if (errno == EINTR)
+ {
+ continue;
+ }
+ if (read_ret == READ_TIMEOUT)
+ {
+ logger(0,
+ "No data received during the last %llu second(s).\n",
+ (long long unsigned int)ndpisrvd_socket->read_timeout.tv_sec);
+ continue;
+ }
+ if (read_ret != READ_OK)
+ {
+ logger(1, "Could not read from socket: %s", nDPIsrvd_enum_to_string(read_ret));
+ break;
+ }
+
+ enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse_all(ndpisrvd_socket);
+ if (parse_ret != PARSE_NEED_MORE_DATA)
+ {
+ logger(1, "Could not parse json message: %s", nDPIsrvd_enum_to_string(parse_ret));
+ break;
+ }
+ }
+
+ if (main_thread_shutdown == 0 && read_ret != READ_OK)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char ** argv)
+{
+ init_logging("nDPIsrvd-captured");
+
+ ndpisrvd_socket = nDPIsrvd_socket_init(sizeof(struct global_user_data),
+ 0,
+ 0,
+ sizeof(struct flow_user_data),
+ captured_json_callback,
+ NULL,
+ captured_flow_cleanup_callback);
+ if (ndpisrvd_socket == NULL)
+ {
+ fprintf(stderr, "%s: nDPIsrvd socket memory allocation failed!\n", argv[0]);
+ return 1;
+ }
+
+ if (parse_options(argc, argv) != 0)
+ {
+ return 1;
+ }
+
+ logger(0, "Recv buffer size: %u", NETWORK_BUFFER_MAX_SIZE);
+ logger(0, "Connecting to `%s'..", serv_optarg);
+
+ if (nDPIsrvd_connect(ndpisrvd_socket) != CONNECT_OK)
+ {
+ fprintf(stderr, "%s: nDPIsrvd socket connect to %s failed!\n", argv[0], serv_optarg);
+ nDPIsrvd_socket_free(&ndpisrvd_socket);
+ return 1;
+ }
+
+ signal(SIGUSR1, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+
+ if (daemonize_with_pidfile(pidfile) != 0)
+ {
+ return 1;
+ }
+
+ if (capture_mode != 0 && chmod_chown(datadir, S_IRWXU | S_IRGRP | S_IXGRP, user, group) != 0)
+ {
+ logger(1, "Could not chmod/chown `%s': %s", datadir, strerror(errno));
+ return 1;
+ }
+
+ errno = 0;
+ if (user != NULL && change_user_group(user, group, pidfile) != 0)
+ {
+ if (errno != 0)
+ {
+ logger(1, "Change user/group failed: %s", strerror(errno));
+ }
+ else
+ {
+ logger(1, "Change user/group failed.");
+ }
+ return 1;
+ }
+
+ if (nDPIsrvd_set_read_timeout(ndpisrvd_socket, 180, 0) != 0)
+ {
+ return 1;
+ }
+
+ int retval = mainloop();
+
+ utarray_packets_free((struct global_user_data *)ndpisrvd_socket->global_user_data);
+ nDPIsrvd_socket_free(&ndpisrvd_socket);
+ daemonize_shutdown(pidfile);
+ shutdown_logging();
+
+ return retval;
+}
diff --git a/examples/c-collectd/README.md b/examples/c-collectd/README.md
new file mode 100644
index 000000000..4e1a16fc4
--- /dev/null
+++ b/examples/c-collectd/README.md
@@ -0,0 +1,14 @@
+HowTo use this
+==============
+
+This HowTo assumes that the examples were sucessfully compiled and installed within the prefix `/usr` on your target machine.
+
+ 1. Make sure nDPId and Collectd is running.
+ 2. Edit `collectd.conf` usually in `/etc`.
+ 3. Add the lines in `plugin_nDPIsrvd.conf` to your `collectd.conf`.
+ You may adapt this file depending what command line arguments you'd supplied to `nDPId`.
+ 4. Reload your Collectd instance.
+ 5. Optional: Install a http server of your choice.
+ Place the files in `/usr/share/nDPId/nDPIsrvd-collectd/www` somewhere in your www root.
+ 6. Optional: Add `rrdgraph.sh` as cron job e.g. `0 * * * * /usr/share/nDPId/nDPIsrvd-collectd/rrdgraph.sh [path-to-the-collectd-rrd-directory] [path-to-your-dpi-wwwroot]`.
+ This will run `rrdgraph.sh` once per hour. You can adjust this until it fit your needs.
diff --git a/examples/c-collectd/c-collectd.c b/examples/c-collectd/c-collectd.c
new file mode 100644
index 000000000..7d7d65244
--- /dev/null
+++ b/examples/c-collectd/c-collectd.c
@@ -0,0 +1,1588 @@
+#include <arpa/inet.h> // ndpi_typedefs.h
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h> // ndpi_typedefs.h
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+#include <unistd.h>
+
+#include <ndpi_typedefs.h>
+
+#include "nDPIsrvd.h"
+#include "utils.h"
+
+#define DEFAULT_COLLECTD_EXEC_INST "nDPIsrvd"
+#define MAX_RISKS_PER_FLOW 8
+#define MAX_SEVERITIES_PER_FLOW 4
+#define LOGGER_EARLY(is_error, fmt, ...) \
+ do \
+ { \
+ if (enable_console_logging != 0) \
+ { \
+ logger_early(is_error, fmt, __VA_ARGS__); \
+ } \
+ else \
+ { \
+ logger(is_error, fmt, __VA_ARGS__); \
+ } \
+ } while (0)
+//#define GENERATE_TIMESTAMP 1
+
+struct flow_user_data
+{
+ nDPIsrvd_ull last_flow_src_l4_payload_len;
+ nDPIsrvd_ull last_flow_dst_l4_payload_len;
+ uint8_t risks[MAX_RISKS_PER_FLOW];
+ uint8_t severities[MAX_SEVERITIES_PER_FLOW];
+ uint8_t category;
+ uint8_t breed;
+ uint8_t confidence;
+ // "fallthroughs" if we are not in sync with nDPI
+ uint8_t risk_ndpid_invalid : 1;
+ uint8_t category_ndpid_invalid : 1;
+ uint8_t breed_ndpid_invalid : 1;
+ uint8_t confidence_ndpid_invalid : 1;
+ // detection status
+ uint8_t new_seen : 1;
+ uint8_t is_detected : 1;
+ uint8_t is_guessed : 1;
+ uint8_t is_not_detected : 1;
+ // flow state
+ uint8_t is_info : 1;
+ uint8_t is_finished : 1;
+ // Layer3 / Layer4
+ uint8_t is_ip4 : 1;
+ uint8_t is_ip6 : 1;
+ uint8_t is_other_l3 : 1;
+ uint8_t is_tcp : 1;
+ uint8_t is_udp : 1;
+ uint8_t is_icmp : 1;
+ uint8_t is_other_l4 : 1;
+};
+
+static int main_thread_shutdown = 0;
+static int collectd_timerfd = -1;
+static pid_t collectd_pid;
+
+static int enable_console_logging = 0;
+static char * serv_optarg = NULL;
+static char * collectd_hostname = NULL;
+static char * collectd_interval = NULL;
+static char * instance_name = NULL;
+static nDPIsrvd_ull collectd_interval_ull = 0uL;
+
+static struct
+{
+ struct
+ {
+ uint64_t json_lines;
+ uint64_t json_bytes;
+
+ uint64_t flow_new_count;
+ uint64_t flow_end_count;
+ uint64_t flow_idle_count;
+ uint64_t flow_update_count;
+ uint64_t flow_analyse_count;
+ uint64_t flow_guessed_count;
+ uint64_t flow_detected_count;
+ uint64_t flow_detection_update_count;
+ uint64_t flow_not_detected_count;
+
+ uint64_t packet_count;
+ uint64_t packet_flow_count;
+
+ uint64_t init_count;
+ uint64_t reconnect_count;
+ uint64_t shutdown_count;
+ uint64_t status_count;
+
+ uint64_t error_unknown_datalink;
+ uint64_t error_unknown_l3_protocol;
+ uint64_t error_unsupported_datalink;
+ uint64_t error_packet_too_short;
+ uint64_t error_packet_type_unknown;
+ uint64_t error_packet_header_invalid;
+ uint64_t error_ip4_packet_too_short;
+ uint64_t error_ip4_size_smaller_than_header;
+ uint64_t error_ip4_l4_payload_detection;
+ uint64_t error_ip6_packet_too_short;
+ uint64_t error_ip6_size_smaller_than_header;
+ uint64_t error_ip6_l4_payload_detection;
+ uint64_t error_tcp_packet_too_short;
+ uint64_t error_udp_packet_too_short;
+ uint64_t error_capture_size_smaller_than_packet;
+ uint64_t error_max_flows_to_track;
+ uint64_t error_flow_memory_alloc;
+
+ uint64_t flow_src_total_bytes;
+ uint64_t flow_dst_total_bytes;
+ uint64_t flow_risky_count;
+ } counters;
+
+ struct
+ {
+ uint64_t flow_state_info;
+ uint64_t flow_state_finished;
+
+ uint64_t flow_breed_safe_count;
+ uint64_t flow_breed_acceptable_count;
+ uint64_t flow_breed_fun_count;
+ uint64_t flow_breed_unsafe_count;
+ uint64_t flow_breed_potentially_dangerous_count;
+ uint64_t flow_breed_tracker_ads_count;
+ uint64_t flow_breed_dangerous_count;
+ uint64_t flow_breed_unrated_count;
+ uint64_t flow_breed_unknown_count;
+
+ uint64_t flow_category_unspecified_count;
+ uint64_t flow_category_media_count;
+ uint64_t flow_category_vpn_count;
+ uint64_t flow_category_email_count;
+ uint64_t flow_category_data_transfer_count;
+ uint64_t flow_category_web_count;
+ uint64_t flow_category_social_network_count;
+ uint64_t flow_category_download_count;
+ uint64_t flow_category_game_count;
+ uint64_t flow_category_chat_count;
+ uint64_t flow_category_voip_count;
+ uint64_t flow_category_database_count;
+ uint64_t flow_category_remote_access_count;
+ uint64_t flow_category_cloud_count;
+ uint64_t flow_category_network_count;
+ uint64_t flow_category_collaborative_count;
+ uint64_t flow_category_rpc_count;
+ uint64_t flow_category_streaming_count;
+ uint64_t flow_category_system_count;
+ uint64_t flow_category_software_update_count;
+ uint64_t flow_category_music_count;
+ uint64_t flow_category_video_count;
+ uint64_t flow_category_shopping_count;
+ uint64_t flow_category_productivity_count;
+ uint64_t flow_category_file_sharing_count;
+ uint64_t flow_category_conn_check_count;
+ uint64_t flow_category_iot_scada_count;
+ uint64_t flow_category_virt_assistant_count;
+ uint64_t flow_category_cybersecurity_count;
+ uint64_t flow_category_adult_content_count;
+ uint64_t flow_category_mining_count;
+ uint64_t flow_category_malware_count;
+ uint64_t flow_category_advertisment_count;
+ uint64_t flow_category_banned_site_count;
+ uint64_t flow_category_site_unavail_count;
+ uint64_t flow_category_allowed_site_count;
+ uint64_t flow_category_antimalware_count;
+ uint64_t flow_category_crypto_currency_count;
+ uint64_t flow_category_gambling_count;
+ uint64_t flow_category_unknown_count;
+
+ uint64_t flow_confidence_by_port;
+ uint64_t flow_confidence_dpi_partial;
+ uint64_t flow_confidence_dpi_partial_cache;
+ uint64_t flow_confidence_dpi_cache;
+ uint64_t flow_confidence_dpi;
+ uint64_t flow_confidence_nbpf;
+ uint64_t flow_confidence_by_ip;
+ uint64_t flow_confidence_dpi_aggressive;
+ uint64_t flow_confidence_custom_rule;
+ uint64_t flow_confidence_unknown;
+
+ uint64_t flow_severity_low;
+ uint64_t flow_severity_medium;
+ uint64_t flow_severity_high;
+ uint64_t flow_severity_severe;
+ uint64_t flow_severity_critical;
+ uint64_t flow_severity_emergency;
+ uint64_t flow_severity_unknown;
+
+ uint64_t flow_l3_ip4_count;
+ uint64_t flow_l3_ip6_count;
+ uint64_t flow_l3_other_count;
+
+ uint64_t flow_l4_tcp_count;
+ uint64_t flow_l4_udp_count;
+ uint64_t flow_l4_icmp_count;
+ uint64_t flow_l4_other_count;
+
+ uint64_t flow_active_count;
+ uint64_t flow_detected_count;
+ uint64_t flow_guessed_count;
+ uint64_t flow_not_detected_count;
+
+ nDPIsrvd_ull flow_risk_count[NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */];
+ nDPIsrvd_ull flow_risk_unknown_count;
+ } gauges[2]; /* values after InfluxDB push: gauges[0] -= gauges[1], gauges[1] is zero'd afterwards */
+} collectd_statistics = {};
+
+struct global_map
+{
+ char const * const json_key;
+ struct
+ {
+ uint64_t * const global_stat_inc;
+ uint64_t * const global_stat_dec;
+ };
+};
+
+#define COLLECTD_STATS_COUNTER_PTR(member) \
+ { \
+ .global_stat_inc = &(collectd_statistics.counters.member), NULL \
+ }
+#define COLLECTD_STATS_GAUGE_PTR(member) \
+ { \
+ .global_stat_inc = &(collectd_statistics.gauges[0].member), \
+ .global_stat_dec = &(collectd_statistics.gauges[1].member) \
+ }
+#define COLLECTD_STATS_COUNTER_INC(member) (collectd_statistics.counters.member++)
+#define COLLECTD_STATS_GAUGE_RES(member) (collectd_statistics.gauges[0].member--)
+#define COLLECTD_STATS_GAUGE_INC(member) (collectd_statistics.gauges[0].member++)
+#define COLLECTD_STATS_GAUGE_DEC(member) (collectd_statistics.gauges[1].member++)
+#define COLLECTD_STATS_GAUGE_SUB(member) (collectd_statistics.gauges[0].member -= collectd_statistics.gauges[1].member)
+#define COLLECTD_STATS_MAP_NOTNULL(map, index) (map[index - 1].global_stat_dec != NULL)
+#define COLLECTD_STATS_MAP_DEC(map, index) ((*map[index - 1].global_stat_dec)++)
+
+static struct global_map const flow_event_map[] = {{"new", COLLECTD_STATS_COUNTER_PTR(flow_new_count)},
+ {"end", COLLECTD_STATS_COUNTER_PTR(flow_end_count)},
+ {"idle", COLLECTD_STATS_COUNTER_PTR(flow_idle_count)},
+ {"update", COLLECTD_STATS_COUNTER_PTR(flow_update_count)},
+ {"analyse", COLLECTD_STATS_COUNTER_PTR(flow_analyse_count)},
+ {"guessed", COLLECTD_STATS_COUNTER_PTR(flow_guessed_count)},
+ {"detected", COLLECTD_STATS_COUNTER_PTR(flow_detected_count)},
+ {"detection-update",
+ COLLECTD_STATS_COUNTER_PTR(flow_detection_update_count)},
+ {"not-detected",
+ COLLECTD_STATS_COUNTER_PTR(flow_not_detected_count)}};
+
+static struct global_map const packet_event_map[] = {{"packet", COLLECTD_STATS_COUNTER_PTR(packet_count)},
+ {"packet-flow", COLLECTD_STATS_COUNTER_PTR(packet_flow_count)}};
+
+static struct global_map const daemon_event_map[] = {{"init", COLLECTD_STATS_COUNTER_PTR(init_count)},
+ {"reconnect", COLLECTD_STATS_COUNTER_PTR(reconnect_count)},
+ {"shutdown", COLLECTD_STATS_COUNTER_PTR(shutdown_count)},
+ {"status", COLLECTD_STATS_COUNTER_PTR(status_count)}};
+
+static struct global_map const error_event_map[] = {
+ {"Unknown datalink layer packet", COLLECTD_STATS_COUNTER_PTR(error_unknown_datalink)},
+ {"Unknown L3 protocol", COLLECTD_STATS_COUNTER_PTR(error_unknown_l3_protocol)},
+ {"Unsupported datalink layer", COLLECTD_STATS_COUNTER_PTR(error_unsupported_datalink)},
+ {"Packet too short", COLLECTD_STATS_COUNTER_PTR(error_packet_too_short)},
+ {"Unknown packet type", COLLECTD_STATS_COUNTER_PTR(error_packet_type_unknown)},
+ {"Packet header invalid", COLLECTD_STATS_COUNTER_PTR(error_packet_header_invalid)},
+ {"IP4 packet too short", COLLECTD_STATS_COUNTER_PTR(error_ip4_packet_too_short)},
+ {"Packet smaller than IP4 header", COLLECTD_STATS_COUNTER_PTR(error_ip4_size_smaller_than_header)},
+ {"nDPI IPv4\\/L4 payload detection failed", COLLECTD_STATS_COUNTER_PTR(error_ip4_l4_payload_detection)},
+ {"IP6 packet too short", COLLECTD_STATS_COUNTER_PTR(error_ip6_packet_too_short)},
+ {"Packet smaller than IP6 header", COLLECTD_STATS_COUNTER_PTR(error_ip6_size_smaller_than_header)},
+ {"nDPI IPv6\\/L4 payload detection failed", COLLECTD_STATS_COUNTER_PTR(error_ip6_l4_payload_detection)},
+ {"TCP packet smaller than expected", COLLECTD_STATS_COUNTER_PTR(error_tcp_packet_too_short)},
+ {"UDP packet smaller than expected", COLLECTD_STATS_COUNTER_PTR(error_udp_packet_too_short)},
+ {"Captured packet size is smaller than expected packet size",
+ COLLECTD_STATS_COUNTER_PTR(error_capture_size_smaller_than_packet)},
+ {"Max flows to track reached", COLLECTD_STATS_COUNTER_PTR(error_max_flows_to_track)},
+ {"Flow memory allocation failed", COLLECTD_STATS_COUNTER_PTR(error_flow_memory_alloc)}};
+
+static struct global_map const breeds_map[] = {{"Safe", COLLECTD_STATS_GAUGE_PTR(flow_breed_safe_count)},
+ {"Acceptable", COLLECTD_STATS_GAUGE_PTR(flow_breed_acceptable_count)},
+ {"Fun", COLLECTD_STATS_GAUGE_PTR(flow_breed_fun_count)},
+ {"Unsafe", COLLECTD_STATS_GAUGE_PTR(flow_breed_unsafe_count)},
+ {"Potentially Dangerous",
+ COLLECTD_STATS_GAUGE_PTR(flow_breed_potentially_dangerous_count)},
+ {"Tracker\\/Ads",
+ COLLECTD_STATS_GAUGE_PTR(flow_breed_tracker_ads_count)},
+ {"Dangerous", COLLECTD_STATS_GAUGE_PTR(flow_breed_dangerous_count)},
+ {"Unrated", COLLECTD_STATS_GAUGE_PTR(flow_breed_unrated_count)},
+ {NULL, COLLECTD_STATS_GAUGE_PTR(flow_breed_unknown_count)}};
+
+static struct global_map const categories_map[] = {
+ {"Unspecified", COLLECTD_STATS_GAUGE_PTR(flow_category_unspecified_count)},
+ {"Media", COLLECTD_STATS_GAUGE_PTR(flow_category_media_count)},
+ {"VPN", COLLECTD_STATS_GAUGE_PTR(flow_category_vpn_count)},
+ {"Email", COLLECTD_STATS_GAUGE_PTR(flow_category_email_count)},
+ {"DataTransfer", COLLECTD_STATS_GAUGE_PTR(flow_category_data_transfer_count)},
+ {"Web", COLLECTD_STATS_GAUGE_PTR(flow_category_web_count)},
+ {"SocialNetwork", COLLECTD_STATS_GAUGE_PTR(flow_category_social_network_count)},
+ {"Download", COLLECTD_STATS_GAUGE_PTR(flow_category_download_count)},
+ {"Game", COLLECTD_STATS_GAUGE_PTR(flow_category_game_count)},
+ {"Chat", COLLECTD_STATS_GAUGE_PTR(flow_category_chat_count)},
+ {"VoIP", COLLECTD_STATS_GAUGE_PTR(flow_category_voip_count)},
+ {"Database", COLLECTD_STATS_GAUGE_PTR(flow_category_database_count)},
+ {"RemoteAccess", COLLECTD_STATS_GAUGE_PTR(flow_category_remote_access_count)},
+ {"Cloud", COLLECTD_STATS_GAUGE_PTR(flow_category_cloud_count)},
+ {"Network", COLLECTD_STATS_GAUGE_PTR(flow_category_network_count)},
+ {"Collaborative", COLLECTD_STATS_GAUGE_PTR(flow_category_collaborative_count)},
+ {"RPC", COLLECTD_STATS_GAUGE_PTR(flow_category_rpc_count)},
+ {"Streaming", COLLECTD_STATS_GAUGE_PTR(flow_category_streaming_count)},
+ {"System", COLLECTD_STATS_GAUGE_PTR(flow_category_system_count)},
+ {"SoftwareUpdate", COLLECTD_STATS_GAUGE_PTR(flow_category_software_update_count)},
+ {"Music", COLLECTD_STATS_GAUGE_PTR(flow_category_music_count)},
+ {"Video", COLLECTD_STATS_GAUGE_PTR(flow_category_video_count)},
+ {"Shopping", COLLECTD_STATS_GAUGE_PTR(flow_category_shopping_count)},
+ {"Productivity", COLLECTD_STATS_GAUGE_PTR(flow_category_productivity_count)},
+ {"FileSharing", COLLECTD_STATS_GAUGE_PTR(flow_category_file_sharing_count)},
+ {"ConnCheck", COLLECTD_STATS_GAUGE_PTR(flow_category_conn_check_count)},
+ {"IoT-Scada", COLLECTD_STATS_GAUGE_PTR(flow_category_iot_scada_count)},
+ {"VirtAssistant", COLLECTD_STATS_GAUGE_PTR(flow_category_virt_assistant_count)},
+ {"Cybersecurity", COLLECTD_STATS_GAUGE_PTR(flow_category_cybersecurity_count)},
+ {"AdultContent", COLLECTD_STATS_GAUGE_PTR(flow_category_adult_content_count)},
+ {"Mining", COLLECTD_STATS_GAUGE_PTR(flow_category_mining_count)},
+ {"Malware", COLLECTD_STATS_GAUGE_PTR(flow_category_malware_count)},
+ {"Advertisement", COLLECTD_STATS_GAUGE_PTR(flow_category_advertisment_count)},
+ {"Banned_Site", COLLECTD_STATS_GAUGE_PTR(flow_category_banned_site_count)},
+ {"Site_Unavailable", COLLECTD_STATS_GAUGE_PTR(flow_category_site_unavail_count)},
+ {"Allowed_Site", COLLECTD_STATS_GAUGE_PTR(flow_category_allowed_site_count)},
+ {"Antimalware", COLLECTD_STATS_GAUGE_PTR(flow_category_antimalware_count)},
+ {"Crypto_Currency", COLLECTD_STATS_GAUGE_PTR(flow_category_crypto_currency_count)},
+ {"Gambling", COLLECTD_STATS_GAUGE_PTR(flow_category_gambling_count)},
+ {NULL, COLLECTD_STATS_GAUGE_PTR(flow_category_unknown_count)}};
+
+static struct global_map const confidence_map[] = {
+ {"Match by port", COLLECTD_STATS_GAUGE_PTR(flow_confidence_by_port)},
+ {"DPI (partial)", COLLECTD_STATS_GAUGE_PTR(flow_confidence_dpi_partial)},
+ {"DPI (partial cache)", COLLECTD_STATS_GAUGE_PTR(flow_confidence_dpi_partial_cache)},
+ {"DPI (cache)", COLLECTD_STATS_GAUGE_PTR(flow_confidence_dpi_cache)},
+ {"DPI", COLLECTD_STATS_GAUGE_PTR(flow_confidence_dpi)},
+ {"nBPF", COLLECTD_STATS_GAUGE_PTR(flow_confidence_nbpf)},
+ {"Match by IP", COLLECTD_STATS_GAUGE_PTR(flow_confidence_by_ip)},
+ {"DPI (aggressive)", COLLECTD_STATS_GAUGE_PTR(flow_confidence_dpi_aggressive)},
+ {"Match by custom rule", COLLECTD_STATS_GAUGE_PTR(flow_confidence_custom_rule)},
+ {NULL, COLLECTD_STATS_GAUGE_PTR(flow_confidence_unknown)}};
+
+static struct global_map const severity_map[] = {{"Low", COLLECTD_STATS_GAUGE_PTR(flow_severity_low)},
+ {"Medium", COLLECTD_STATS_GAUGE_PTR(flow_severity_medium)},
+ {"High", COLLECTD_STATS_GAUGE_PTR(flow_severity_high)},
+ {"Severe", COLLECTD_STATS_GAUGE_PTR(flow_severity_severe)},
+ {"Critical", COLLECTD_STATS_GAUGE_PTR(flow_severity_critical)},
+ {"Emergency", COLLECTD_STATS_GAUGE_PTR(flow_severity_emergency)},
+ {NULL, COLLECTD_STATS_GAUGE_PTR(flow_severity_unknown)}};
+
+#ifdef ENABLE_MEMORY_PROFILING
+void nDPIsrvd_memprof_log_alloc(size_t alloc_size)
+{
+ (void)alloc_size;
+}
+
+void nDPIsrvd_memprof_log_free(size_t free_size)
+{
+ (void)free_size;
+}
+
+void nDPIsrvd_memprof_log(char const * const format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
+ vfprintf(stderr, format, ap);
+ fprintf(stderr, "%s\n", "");
+ va_end(ap);
+}
+#endif
+
+static int set_collectd_timer(void)
+{
+ const time_t interval = collectd_interval_ull * 1000;
+ struct itimerspec its;
+ its.it_value.tv_sec = interval / 1000;
+ its.it_value.tv_nsec = (interval % 1000) * 1000000;
+ its.it_interval.tv_nsec = 0;
+ its.it_interval.tv_sec = 0;
+
+ errno = 0;
+ return timerfd_settime(collectd_timerfd, 0, &its, NULL);
+}
+
+static int create_collectd_timer(void)
+{
+ collectd_timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (collectd_timerfd < 0)
+ {
+ return 1;
+ }
+
+ return set_collectd_timer();
+}
+
+static void sighandler(int signum)
+{
+ logger(0, "Received SIGNAL %d", signum);
+
+ if (main_thread_shutdown == 0)
+ {
+ logger(0, "%s", "Shutting down ..");
+ main_thread_shutdown = 1;
+ }
+}
+
+static int parse_options(int argc, char ** argv, struct nDPIsrvd_socket * const sock)
+{
+ int opt;
+
+ static char const usage[] =
+ "Usage: %s "
+ "[-l] [-s host] [-c hostname] [-n collectd-instance-name] [-i interval]\n\n"
+ "\t-l\tLog to console instead of syslog.\n"
+ "\t-s\tDestination where nDPIsrvd is listening on.\n"
+ "\t-c\tCollectd hostname.\n"
+ "\t \tThis value defaults to the environment variable COLLECTD_HOSTNAME.\n"
+ "\t-n\tName of the collectd(-exec) instance.\n"
+ "\t \tDefaults to: " DEFAULT_COLLECTD_EXEC_INST
+ "\n"
+ "\t-i\tInterval between print statistics to stdout.\n"
+ "\t \tThis value defaults to the environment variable COLLECTD_INTERVAL.\n\n";
+
+ while ((opt = getopt(argc, argv, "hls:c:n:i:")) != -1)
+ {
+ switch (opt)
+ {
+ case 'l':
+ enable_console_logging = 1;
+ break;
+ case 's':
+ free(serv_optarg);
+ serv_optarg = strdup(optarg);
+ break;
+ case 'c':
+ free(collectd_hostname);
+ collectd_hostname = strdup(optarg);
+ break;
+ case 'n':
+ free(instance_name);
+ instance_name = strdup(optarg);
+ break;
+ case 'i':
+ free(collectd_interval);
+ collectd_interval = strdup(optarg);
+ break;
+ default:
+ fprintf(stderr, usage, argv[0]);
+ return 1;
+ }
+ }
+
+ if (serv_optarg == NULL)
+ {
+ serv_optarg = strdup(DISTRIBUTOR_UNIX_SOCKET);
+ }
+
+ if (collectd_hostname == NULL)
+ {
+ collectd_hostname = getenv("COLLECTD_HOSTNAME");
+ if (collectd_hostname == NULL)
+ {
+ collectd_hostname = strdup("localhost");
+ }
+ else
+ {
+ enable_console_logging = 0;
+ }
+ }
+
+ if (instance_name == NULL)
+ {
+ instance_name = strdup(DEFAULT_COLLECTD_EXEC_INST);
+ }
+
+ if (collectd_interval == NULL)
+ {
+ collectd_interval = getenv("COLLECTD_INTERVAL");
+ if (collectd_interval == NULL)
+ {
+ collectd_interval = strdup("60");
+ }
+ else
+ {
+ enable_console_logging = 0;
+ }
+ }
+
+ if (enable_console_logging != 0)
+ {
+ enable_console_logger();
+ }
+
+ if (str_value_to_ull(collectd_interval, &collectd_interval_ull) != CONVERSION_OK)
+ {
+ LOGGER_EARLY(1, "Collectd interval `%s' is not a valid number", collectd_interval);
+ return 1;
+ }
+
+ if (nDPIsrvd_setup_address(&sock->address, serv_optarg) != 0)
+ {
+ LOGGER_EARLY(1, "Could not parse address `%s'", serv_optarg);
+ return 1;
+ }
+
+ if (optind < argc)
+ {
+ LOGGER_EARLY(1, "%s", "Unexpected argument after options");
+ LOGGER_EARLY(1, "%s", "");
+ LOGGER_EARLY(1, usage, argv[0]);
+ return 1;
+ }
+
+ return 0;
+}
+
+#ifdef GENERATE_TIMESTAMP
+#define COLLECTD_COUNTER_PREFIX "PUTVAL \"%s/exec-%s/counter-"
+#define COLLECTD_COUNTER_SUFFIX "\" interval=%llu %llu:%llu\n"
+#define COLLECTD_COUNTER_N(value) \
+ collectd_hostname, instance_name, #value, collectd_interval_ull, (unsigned long long int)now, \
+ (unsigned long long int)collectd_statistics.value
+#define COLLECTD_COUNTER_N2(name, value) \
+ collectd_hostname, instance_name, name, collectd_interval_ull, (unsigned long long int)now, \
+ (unsigned long long int)collectd_statistics.value
+
+#define COLLECTD_GAUGE_PREFIX "PUTVAL \"%s/exec-%s/gauge-"
+#define COLLECTD_GAUGE_SUFFIX "\" interval=%llu %llu:%llu\n"
+#define COLLECTD_GAUGE_N(value) \
+ collectd_hostname, instance_name, #value, collectd_interval_ull, (unsigned long long int)now, \
+ (unsigned long long int)collectd_statistics.value
+#define COLLECTD_GAUGE_N2(name, value) \
+ collectd_hostname, instance_name, name, collectd_interval_ull, (unsigned long long int)now, \
+ (unsigned long long int)collectd_statistics.value
+#else
+#define COLLECTD_COUNTER_PREFIX "PUTVAL \"%s/exec-%s/counter-"
+#define COLLECTD_COUNTER_SUFFIX "\" interval=%llu N:%llu\n"
+#define COLLECTD_COUNTER_N(value) \
+ collectd_hostname, instance_name, #value, collectd_interval_ull, \
+ (unsigned long long int)collectd_statistics.counters.value
+#define COLLECTD_COUNTER_N2(name, value) \
+ collectd_hostname, instance_name, name, collectd_interval_ull, \
+ (unsigned long long int)collectd_statistics.counters.value
+
+#define COLLECTD_GAUGE_PREFIX "PUTVAL \"%s/exec-%s/gauge-"
+#define COLLECTD_GAUGE_SUFFIX "\" interval=%llu N:%llu\n"
+#define COLLECTD_GAUGE_N(value) \
+ collectd_hostname, instance_name, #value, collectd_interval_ull, \
+ (unsigned long long int)collectd_statistics.gauges[0].value
+#define COLLECTD_GAUGE_N2(name, value) \
+ collectd_hostname, instance_name, name, collectd_interval_ull, \
+ (unsigned long long int)collectd_statistics.gauges[0].value
+#endif
+
+#define COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_PREFIX "%s" COLLECTD_COUNTER_SUFFIX
+#define COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_PREFIX "%s" COLLECTD_GAUGE_SUFFIX
+static void print_collectd_exec_output(void)
+{
+ size_t i;
+#ifdef GENERATE_TIMESTAMP
+ time_t now = time(NULL);
+#endif
+
+ printf(COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT() COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT()
+ COLLECTD_COUNTER_N_FORMAT(),
+
+ COLLECTD_COUNTER_N(json_lines),
+ COLLECTD_COUNTER_N(json_bytes),
+ COLLECTD_COUNTER_N(flow_new_count),
+ COLLECTD_COUNTER_N(flow_end_count),
+ COLLECTD_COUNTER_N(flow_idle_count),
+ COLLECTD_COUNTER_N(flow_update_count),
+ COLLECTD_COUNTER_N(flow_analyse_count),
+ COLLECTD_COUNTER_N(flow_guessed_count),
+ COLLECTD_COUNTER_N(flow_detected_count),
+ COLLECTD_COUNTER_N(flow_detection_update_count),
+ COLLECTD_COUNTER_N(flow_not_detected_count),
+ COLLECTD_COUNTER_N(flow_src_total_bytes),
+ COLLECTD_COUNTER_N(flow_dst_total_bytes),
+ COLLECTD_COUNTER_N(flow_risky_count),
+ COLLECTD_COUNTER_N(packet_count),
+ COLLECTD_COUNTER_N(packet_flow_count),
+ COLLECTD_COUNTER_N(init_count),
+ COLLECTD_COUNTER_N(reconnect_count),
+ COLLECTD_COUNTER_N(shutdown_count),
+ COLLECTD_COUNTER_N(status_count),
+ COLLECTD_COUNTER_N(error_unknown_datalink),
+ COLLECTD_COUNTER_N(error_unknown_l3_protocol),
+ COLLECTD_COUNTER_N(error_unsupported_datalink),
+ COLLECTD_COUNTER_N(error_packet_too_short),
+ COLLECTD_COUNTER_N(error_packet_type_unknown),
+ COLLECTD_COUNTER_N(error_packet_header_invalid),
+ COLLECTD_COUNTER_N(error_ip4_packet_too_short),
+ COLLECTD_COUNTER_N(error_ip4_size_smaller_than_header),
+ COLLECTD_COUNTER_N(error_ip4_l4_payload_detection),
+ COLLECTD_COUNTER_N(error_ip6_packet_too_short),
+ COLLECTD_COUNTER_N(error_ip6_size_smaller_than_header),
+ COLLECTD_COUNTER_N(error_ip6_l4_payload_detection),
+ COLLECTD_COUNTER_N(error_tcp_packet_too_short),
+ COLLECTD_COUNTER_N(error_udp_packet_too_short),
+ COLLECTD_COUNTER_N(error_capture_size_smaller_than_packet),
+ COLLECTD_COUNTER_N(error_max_flows_to_track),
+ COLLECTD_COUNTER_N(error_flow_memory_alloc));
+
+ printf(COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT(),
+
+ COLLECTD_GAUGE_N(flow_breed_safe_count),
+ COLLECTD_GAUGE_N(flow_breed_acceptable_count),
+ COLLECTD_GAUGE_N(flow_breed_fun_count),
+ COLLECTD_GAUGE_N(flow_breed_unsafe_count),
+ COLLECTD_GAUGE_N(flow_breed_potentially_dangerous_count),
+ COLLECTD_GAUGE_N(flow_breed_tracker_ads_count),
+ COLLECTD_GAUGE_N(flow_breed_dangerous_count),
+ COLLECTD_GAUGE_N(flow_breed_unrated_count),
+ COLLECTD_GAUGE_N(flow_breed_unknown_count));
+
+ printf(COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT(),
+
+ COLLECTD_GAUGE_N(flow_category_unspecified_count),
+ COLLECTD_GAUGE_N(flow_category_media_count),
+ COLLECTD_GAUGE_N(flow_category_vpn_count),
+ COLLECTD_GAUGE_N(flow_category_email_count),
+ COLLECTD_GAUGE_N(flow_category_data_transfer_count),
+ COLLECTD_GAUGE_N(flow_category_web_count),
+ COLLECTD_GAUGE_N(flow_category_social_network_count),
+ COLLECTD_GAUGE_N(flow_category_download_count),
+ COLLECTD_GAUGE_N(flow_category_game_count),
+ COLLECTD_GAUGE_N(flow_category_chat_count),
+ COLLECTD_GAUGE_N(flow_category_voip_count),
+ COLLECTD_GAUGE_N(flow_category_database_count),
+ COLLECTD_GAUGE_N(flow_category_remote_access_count),
+ COLLECTD_GAUGE_N(flow_category_cloud_count),
+ COLLECTD_GAUGE_N(flow_category_network_count),
+ COLLECTD_GAUGE_N(flow_category_collaborative_count),
+ COLLECTD_GAUGE_N(flow_category_rpc_count),
+ COLLECTD_GAUGE_N(flow_category_streaming_count),
+ COLLECTD_GAUGE_N(flow_category_system_count),
+ COLLECTD_GAUGE_N(flow_category_software_update_count),
+ COLLECTD_GAUGE_N(flow_category_music_count),
+ COLLECTD_GAUGE_N(flow_category_video_count),
+ COLLECTD_GAUGE_N(flow_category_shopping_count),
+ COLLECTD_GAUGE_N(flow_category_productivity_count),
+ COLLECTD_GAUGE_N(flow_category_file_sharing_count),
+ COLLECTD_GAUGE_N(flow_category_conn_check_count),
+ COLLECTD_GAUGE_N(flow_category_iot_scada_count),
+ COLLECTD_GAUGE_N(flow_category_virt_assistant_count),
+ COLLECTD_GAUGE_N(flow_category_cybersecurity_count),
+ COLLECTD_GAUGE_N(flow_category_adult_content_count),
+ COLLECTD_GAUGE_N(flow_category_mining_count),
+ COLLECTD_GAUGE_N(flow_category_malware_count),
+ COLLECTD_GAUGE_N(flow_category_advertisment_count),
+ COLLECTD_GAUGE_N(flow_category_banned_site_count),
+ COLLECTD_GAUGE_N(flow_category_site_unavail_count),
+ COLLECTD_GAUGE_N(flow_category_allowed_site_count),
+ COLLECTD_GAUGE_N(flow_category_antimalware_count),
+ COLLECTD_GAUGE_N(flow_category_crypto_currency_count),
+ COLLECTD_GAUGE_N(flow_category_gambling_count),
+ COLLECTD_GAUGE_N(flow_category_unknown_count));
+
+ printf(COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT(),
+
+ COLLECTD_GAUGE_N(flow_confidence_by_port),
+ COLLECTD_GAUGE_N(flow_confidence_dpi_partial),
+ COLLECTD_GAUGE_N(flow_confidence_dpi_partial_cache),
+ COLLECTD_GAUGE_N(flow_confidence_dpi_cache),
+ COLLECTD_GAUGE_N(flow_confidence_dpi),
+ COLLECTD_GAUGE_N(flow_confidence_nbpf),
+ COLLECTD_GAUGE_N(flow_confidence_by_ip),
+ COLLECTD_GAUGE_N(flow_confidence_dpi_aggressive),
+ COLLECTD_GAUGE_N(flow_confidence_custom_rule),
+ COLLECTD_GAUGE_N(flow_confidence_unknown));
+
+ printf(COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT(),
+
+ COLLECTD_GAUGE_N(flow_severity_low),
+ COLLECTD_GAUGE_N(flow_severity_medium),
+ COLLECTD_GAUGE_N(flow_severity_high),
+ COLLECTD_GAUGE_N(flow_severity_severe),
+ COLLECTD_GAUGE_N(flow_severity_critical),
+ COLLECTD_GAUGE_N(flow_severity_emergency),
+ COLLECTD_GAUGE_N(flow_severity_unknown));
+
+ printf(COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT()
+ COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT(),
+
+ COLLECTD_GAUGE_N(flow_l3_ip4_count),
+ COLLECTD_GAUGE_N(flow_l3_ip6_count),
+ COLLECTD_GAUGE_N(flow_l3_other_count),
+ COLLECTD_GAUGE_N(flow_l4_tcp_count),
+ COLLECTD_GAUGE_N(flow_l4_udp_count),
+ COLLECTD_GAUGE_N(flow_l4_icmp_count),
+ COLLECTD_GAUGE_N(flow_l4_other_count),
+ COLLECTD_GAUGE_N(flow_risk_unknown_count));
+
+ printf(COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT() COLLECTD_GAUGE_N_FORMAT(),
+ COLLECTD_GAUGE_N(flow_active_count),
+ COLLECTD_GAUGE_N(flow_detected_count),
+ COLLECTD_GAUGE_N(flow_guessed_count),
+ COLLECTD_GAUGE_N(flow_not_detected_count));
+
+ for (i = 0; i < NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */; ++i)
+ {
+ char gauge_name[BUFSIZ];
+ snprintf(gauge_name, sizeof(gauge_name), "flow_risk_%zu_count", i + 1);
+ printf(COLLECTD_GAUGE_N_FORMAT(), COLLECTD_GAUGE_N2(gauge_name, flow_risk_count[i]));
+ }
+
+ COLLECTD_STATS_GAUGE_SUB(flow_state_info);
+ COLLECTD_STATS_GAUGE_SUB(flow_state_finished);
+
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_safe_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_acceptable_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_fun_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_unsafe_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_potentially_dangerous_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_tracker_ads_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_dangerous_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_unrated_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_breed_unknown_count);
+
+ COLLECTD_STATS_GAUGE_SUB(flow_category_unspecified_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_media_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_vpn_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_email_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_data_transfer_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_web_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_social_network_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_download_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_game_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_chat_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_voip_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_database_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_remote_access_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_cloud_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_network_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_collaborative_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_rpc_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_streaming_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_system_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_software_update_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_music_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_video_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_shopping_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_productivity_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_file_sharing_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_conn_check_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_iot_scada_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_virt_assistant_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_cybersecurity_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_adult_content_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_mining_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_malware_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_advertisment_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_banned_site_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_site_unavail_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_allowed_site_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_antimalware_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_crypto_currency_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_gambling_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_category_unknown_count);
+
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_by_port);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_dpi_partial);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_dpi_partial_cache);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_dpi_cache);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_dpi);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_nbpf);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_by_ip);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_dpi_aggressive);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_custom_rule);
+ COLLECTD_STATS_GAUGE_SUB(flow_confidence_unknown);
+
+ COLLECTD_STATS_GAUGE_SUB(flow_severity_low);
+ COLLECTD_STATS_GAUGE_SUB(flow_severity_medium);
+ COLLECTD_STATS_GAUGE_SUB(flow_severity_high);
+ COLLECTD_STATS_GAUGE_SUB(flow_severity_severe);
+ COLLECTD_STATS_GAUGE_SUB(flow_severity_critical);
+ COLLECTD_STATS_GAUGE_SUB(flow_severity_emergency);
+ COLLECTD_STATS_GAUGE_SUB(flow_severity_unknown);
+
+ COLLECTD_STATS_GAUGE_SUB(flow_l3_ip4_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_l3_ip6_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_l3_other_count);
+
+ COLLECTD_STATS_GAUGE_SUB(flow_l4_tcp_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_l4_udp_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_l4_icmp_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_l4_other_count);
+
+ COLLECTD_STATS_GAUGE_SUB(flow_active_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_detected_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_guessed_count);
+ COLLECTD_STATS_GAUGE_SUB(flow_not_detected_count);
+
+ for (size_t i = 0; i < NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */; ++i)
+ {
+ COLLECTD_STATS_GAUGE_SUB(flow_risk_count[i]);
+ }
+ COLLECTD_STATS_GAUGE_SUB(flow_risk_unknown_count);
+
+ memset(&collectd_statistics.gauges[1], 0, sizeof(collectd_statistics.gauges[1]));
+}
+
+static int mainloop(int epollfd, struct nDPIsrvd_socket * const sock)
+{
+ struct epoll_event events[32];
+ size_t const events_size = sizeof(events) / sizeof(events[0]);
+
+ while (main_thread_shutdown == 0)
+ {
+ int nready = epoll_wait(epollfd, events, events_size, -1);
+
+ for (int i = 0; i < nready; i++)
+ {
+ if (events[i].events & EPOLLERR)
+ {
+ logger(1, "Epoll event error: %s", (errno != 0 ? strerror(errno) : "EPOLLERR"));
+ break;
+ }
+
+ if (events[i].data.fd == collectd_timerfd)
+ {
+ uint64_t expirations;
+
+ /*
+ * Check if collectd parent process is still running.
+ * May happen if collectd was killed with singals e.g. SIGKILL.
+ */
+ if (getppid() != collectd_pid)
+ {
+ logger(1, "Parent process %d exited. Nothing left to do here, bye.", collectd_pid);
+ return 1;
+ }
+
+ errno = 0;
+ if (read(collectd_timerfd, &expirations, sizeof(expirations)) != sizeof(expirations))
+ {
+ logger(1, "Could not read timer expirations: %s", strerror(errno));
+ return 1;
+ }
+ if (set_collectd_timer() != 0)
+ {
+ logger(1, "Could not set timer: %s", strerror(errno));
+ return 1;
+ }
+
+ print_collectd_exec_output();
+ }
+ else if (events[i].data.fd == sock->fd)
+ {
+ errno = 0;
+ enum nDPIsrvd_read_return read_ret = nDPIsrvd_read(sock);
+ if (read_ret != READ_OK)
+ {
+ logger(1, "nDPIsrvd read failed with: %s", nDPIsrvd_enum_to_string(read_ret));
+ return 1;
+ }
+
+ enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse_all(sock);
+ if (parse_ret != PARSE_NEED_MORE_DATA)
+ {
+ logger(1, "nDPIsrvd parse failed with: %s", nDPIsrvd_enum_to_string(parse_ret));
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int collectd_map_to_stat(char const * const token_str,
+ size_t token_length,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ size_t i, null_i = map_length;
+
+ for (i = 0; i < map_length; ++i)
+ {
+ if (map[i].json_key == NULL)
+ {
+ null_i = i;
+ break;
+ }
+
+ size_t key_length = strlen(map[i].json_key);
+ if (key_length == token_length && strncmp(map[i].json_key, token_str, token_length) == 0)
+ {
+ (*map[i].global_stat_inc)++;
+ return 0;
+ }
+ }
+
+ if (null_i < map_length && map[null_i].global_stat_inc != NULL)
+ {
+ (*map[null_i].global_stat_inc)++;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int collectd_map_value_to_stat(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_json_token const * const token,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ char const * value_str = NULL;
+ size_t value_length = 0;
+
+ value_str = TOKEN_GET_VALUE(sock, token, &value_length);
+ if (value_length == 0 || value_str == NULL)
+ {
+ return 1;
+ }
+
+ return collectd_map_to_stat(value_str, value_length, map, map_length);
+}
+
+static void collectd_unmap_flow_from_stat(struct flow_user_data * const flow_user_data)
+{
+ if (flow_user_data->is_ip4 != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_l3_ip4_count);
+ }
+
+ if (flow_user_data->is_ip6 != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_l3_ip6_count);
+ }
+
+ if (flow_user_data->is_other_l3 != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_l3_other_count);
+ }
+
+ if (flow_user_data->is_tcp != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_l4_tcp_count);
+ }
+
+ if (flow_user_data->is_udp != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_l4_udp_count);
+ }
+
+ if (flow_user_data->is_icmp != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_l4_icmp_count);
+ }
+
+ if (flow_user_data->is_other_l4 != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_l4_other_count);
+ }
+
+ if (flow_user_data->new_seen != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_active_count);
+ }
+
+ if (flow_user_data->is_detected != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_detected_count);
+ }
+
+ if (flow_user_data->is_guessed != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_guessed_count);
+ }
+
+ if (flow_user_data->is_not_detected != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_not_detected_count);
+ }
+
+ if (flow_user_data->is_info != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_state_info);
+ }
+
+ if (flow_user_data->is_finished != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_state_finished);
+ }
+
+ if (flow_user_data->breed > 0 && flow_user_data->breed_ndpid_invalid == 0 &&
+ COLLECTD_STATS_MAP_NOTNULL(breeds_map, flow_user_data->breed) != 0)
+ {
+ COLLECTD_STATS_MAP_DEC(breeds_map, flow_user_data->breed);
+ }
+
+ if (flow_user_data->category > 0 && flow_user_data->category_ndpid_invalid == 0 &&
+ COLLECTD_STATS_MAP_NOTNULL(categories_map, flow_user_data->category) != 0)
+ {
+ COLLECTD_STATS_MAP_DEC(categories_map, flow_user_data->category);
+ }
+
+ if (flow_user_data->confidence > 0 && flow_user_data->confidence_ndpid_invalid == 0 &&
+ COLLECTD_STATS_MAP_NOTNULL(confidence_map, flow_user_data->confidence) != 0)
+ {
+ COLLECTD_STATS_MAP_DEC(confidence_map, flow_user_data->confidence);
+ }
+
+ for (uint8_t i = 0; i < MAX_SEVERITIES_PER_FLOW; ++i)
+ {
+ if (flow_user_data->severities[i] > 0)
+ {
+ COLLECTD_STATS_MAP_DEC(severity_map, flow_user_data->severities[i]);
+ }
+ }
+
+ for (uint8_t i = 0; i < MAX_RISKS_PER_FLOW; ++i)
+ {
+ if (flow_user_data->risks[i] > 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_risk_count[flow_user_data->risks[i]]);
+ }
+ }
+
+ if (flow_user_data->risk_ndpid_invalid != 0)
+ {
+ COLLECTD_STATS_GAUGE_DEC(flow_risk_unknown_count);
+ }
+}
+
+static ssize_t collectd_map_index(char const * const json_key,
+ size_t key_length,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ ssize_t unknown_key = -1;
+
+ if (json_key == NULL || key_length == 0)
+ {
+ return -1;
+ }
+
+ for (size_t i = 0; i < map_length; ++i)
+ {
+ if (map[i].json_key == NULL)
+ {
+ unknown_key = i;
+ continue;
+ }
+
+ if (key_length == strlen(map[i].json_key) && strncmp(json_key, map[i].json_key, key_length) == 0)
+ {
+ return i;
+ }
+ }
+
+ return unknown_key;
+}
+
+static int collectd_map_flow_u8(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_json_token const * const token,
+ struct global_map const * const map,
+ size_t map_length,
+ uint8_t * const dest)
+{
+ if (token == NULL || dest == NULL)
+ {
+ return 1;
+ }
+
+ size_t len;
+ char const * const str = TOKEN_GET_VALUE(sock, token, &len);
+ if (str == NULL || len == 0)
+ {
+ return 1;
+ }
+
+ ssize_t const map_index = collectd_map_index(str, len, map, map_length);
+ if (map_index < 0 || map_index > UCHAR_MAX)
+ {
+ return 1;
+ }
+
+ *dest = map_index + 1;
+ return 0;
+}
+
+static void process_flow_stats(struct nDPIsrvd_socket * const sock, struct nDPIsrvd_flow * const flow)
+{
+ struct flow_user_data * flow_user_data;
+ struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
+ struct nDPIsrvd_json_token const * const flow_state = TOKEN_GET_SZ(sock, "flow_state");
+ nDPIsrvd_ull total_bytes_ull[2];
+
+ if (flow == NULL)
+ {
+ return;
+ }
+ flow_user_data = (struct flow_user_data *)flow->flow_user_data;
+ if (flow_user_data == NULL)
+ {
+ return;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "new") != 0)
+ {
+ flow_user_data->new_seen = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_active_count);
+
+ struct nDPIsrvd_json_token const * const l3_proto = TOKEN_GET_SZ(sock, "l3_proto");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, l3_proto, "ip4") != 0)
+ {
+ flow_user_data->is_ip4 = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_l3_ip4_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l3_proto, "ip6") != 0)
+ {
+ flow_user_data->is_ip6 = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_l3_ip6_count);
+ }
+ else if (l3_proto != NULL)
+ {
+ flow_user_data->is_other_l3 = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_l3_other_count);
+ }
+
+ struct nDPIsrvd_json_token const * const l4_proto = TOKEN_GET_SZ(sock, "l4_proto");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "tcp") != 0)
+ {
+ flow_user_data->is_tcp = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_l4_tcp_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "udp") != 0)
+ {
+ flow_user_data->is_udp = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_l4_udp_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "icmp") != 0)
+ {
+ flow_user_data->is_icmp = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_l4_icmp_count);
+ }
+ else if (l4_proto != NULL)
+ {
+ flow_user_data->is_other_l4 = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_l4_other_count);
+ }
+ }
+ else if (flow_user_data->new_seen == 0)
+ {
+ return;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "not-detected") != 0)
+ {
+ flow_user_data->is_not_detected = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_not_detected_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "guessed") != 0)
+ {
+ flow_user_data->is_guessed = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_guessed_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detected") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detection-update") != 0)
+ {
+ struct nDPIsrvd_json_token const * const flow_risk = TOKEN_GET_SZ(sock, "ndpi", "flow_risk");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ if (flow_user_data->is_detected == 0)
+ {
+ flow_user_data->is_detected = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_detected_count);
+ }
+
+ if (flow_risk != NULL)
+ {
+ if (flow_user_data->risks[0] == 0)
+ {
+ COLLECTD_STATS_COUNTER_INC(flow_risky_count);
+ }
+
+ while ((current = nDPIsrvd_get_next_token(sock, flow_risk, &next_child_index)) != NULL)
+ {
+ size_t numeric_risk_len = 0;
+ char const * const numeric_risk_str = TOKEN_GET_KEY(sock, current, &numeric_risk_len);
+ nDPIsrvd_ull numeric_risk_value = 0;
+ char numeric_risk_buf[numeric_risk_len + 1];
+
+ if (numeric_risk_len == 0 || numeric_risk_str == NULL)
+ {
+ logger(1, "%s", "Missing numeric risk value");
+ continue;
+ }
+
+ strncpy(numeric_risk_buf, numeric_risk_str, numeric_risk_len);
+ numeric_risk_buf[numeric_risk_len] = '\0';
+
+ struct nDPIsrvd_json_token const * const severity =
+ TOKEN_GET_SZ(sock, "ndpi", "flow_risk", numeric_risk_buf, "severity");
+ uint8_t severity_index;
+
+ if (collectd_map_flow_u8(
+ sock, severity, severity_map, nDPIsrvd_ARRAY_LENGTH(severity_map), &severity_index) != 0)
+ {
+ severity_index = 0;
+ }
+
+ if (severity_index != 0)
+ {
+ for (uint8_t i = 0; i < MAX_SEVERITIES_PER_FLOW; ++i)
+ {
+ if (flow_user_data->severities[i] != 0)
+ {
+ continue;
+ }
+ if (flow_user_data->severities[i] == severity_index)
+ {
+ break;
+ }
+
+ if (collectd_map_value_to_stat(
+ sock, severity, severity_map, nDPIsrvd_ARRAY_LENGTH(severity_map)) != 0)
+ {
+ severity_index = 0;
+ break;
+ }
+ flow_user_data->severities[i] = severity_index;
+ break;
+ }
+ }
+
+ if (severity_index == 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, severity, &value_len);
+
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','breed': %.*s", (int)value_len, value_str);
+ }
+ }
+
+ if (str_value_to_ull(numeric_risk_str, &numeric_risk_value) == CONVERSION_OK)
+ {
+ if (numeric_risk_value < NDPI_MAX_RISK && numeric_risk_value > 0)
+ {
+ for (uint8_t i = 0; i < MAX_RISKS_PER_FLOW; ++i)
+ {
+ if (flow_user_data->risks[i] != 0)
+ {
+ continue;
+ }
+ if (flow_user_data->risks[i] == numeric_risk_value - 1)
+ {
+ break;
+ }
+
+ COLLECTD_STATS_GAUGE_INC(flow_risk_count[numeric_risk_value - 1]);
+ flow_user_data->risks[i] = numeric_risk_value - 1;
+ break;
+ }
+ }
+ else if (flow_user_data->risk_ndpid_invalid == 0)
+ {
+ flow_user_data->risk_ndpid_invalid = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_risk_unknown_count);
+ }
+ }
+ else
+ {
+ logger(1, "Invalid numeric risk value: %s", numeric_risk_buf);
+ }
+ }
+ }
+
+ if (flow_user_data->breed == 0 && flow_user_data->breed_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const breed = TOKEN_GET_SZ(sock, "ndpi", "breed");
+ if (collectd_map_flow_u8(
+ sock, breed, breeds_map, nDPIsrvd_ARRAY_LENGTH(breeds_map), &flow_user_data->breed) != 0 ||
+ collectd_map_value_to_stat(sock, breed, breeds_map, nDPIsrvd_ARRAY_LENGTH(breeds_map)) != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, breed, &value_len);
+
+ flow_user_data->breed = 0;
+ flow_user_data->breed_ndpid_invalid = 1;
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','breed': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (flow_user_data->category == 0 && flow_user_data->category_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const category = TOKEN_GET_SZ(sock, "ndpi", "category");
+ if (collectd_map_flow_u8(
+ sock, category, categories_map, nDPIsrvd_ARRAY_LENGTH(categories_map), &flow_user_data->category) !=
+ 0 ||
+ collectd_map_value_to_stat(sock, category, categories_map, nDPIsrvd_ARRAY_LENGTH(categories_map)) != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, category, &value_len);
+
+ flow_user_data->category = 0;
+ flow_user_data->category_ndpid_invalid = 1;
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','category': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (flow_user_data->confidence == 0 && flow_user_data->confidence_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const token = TOKEN_GET_SZ(sock, "ndpi", "confidence");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ if ((current = nDPIsrvd_get_next_token(sock, token, &next_child_index)) == NULL)
+ {
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+ else if (nDPIsrvd_get_next_token(sock, token, &next_child_index) == NULL)
+ {
+ if (collectd_map_flow_u8(sock,
+ current,
+ confidence_map,
+ nDPIsrvd_ARRAY_LENGTH(confidence_map),
+ &flow_user_data->confidence) != 0 ||
+ collectd_map_value_to_stat(sock, current, confidence_map, nDPIsrvd_ARRAY_LENGTH(confidence_map)) !=
+ 0)
+ {
+ flow_user_data->confidence = 0;
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+ }
+ else
+ {
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+
+ if (flow_user_data->confidence_ndpid_invalid != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, current, &value_len);
+
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','confidence': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_state, "info") != 0)
+ {
+ if (flow_user_data->is_info == 0)
+ {
+ flow_user_data->is_info = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_state_info);
+ }
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_state, "finished") != 0)
+ {
+ if (flow_user_data->is_finished == 0)
+ {
+ if (flow_user_data->is_info != 0)
+ {
+ flow_user_data->is_info = 0;
+ COLLECTD_STATS_GAUGE_RES(flow_state_info);
+ }
+ flow_user_data->is_finished = 1;
+ COLLECTD_STATS_GAUGE_INC(flow_state_finished);
+ }
+ }
+
+ if (TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_src_tot_l4_payload_len"), &total_bytes_ull[0]) ==
+ CONVERSION_OK &&
+ TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_dst_tot_l4_payload_len"), &total_bytes_ull[1]) ==
+ CONVERSION_OK)
+ {
+ collectd_statistics.counters.flow_src_total_bytes +=
+ total_bytes_ull[0] - flow_user_data->last_flow_src_l4_payload_len;
+ collectd_statistics.counters.flow_dst_total_bytes +=
+ total_bytes_ull[1] - flow_user_data->last_flow_dst_l4_payload_len;
+
+ flow_user_data->last_flow_src_l4_payload_len = total_bytes_ull[0];
+ flow_user_data->last_flow_dst_l4_payload_len = total_bytes_ull[1];
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "end") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "idle") != 0)
+ {
+ collectd_unmap_flow_from_stat(flow_user_data);
+ }
+}
+
+static enum nDPIsrvd_callback_return collectd_json_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow)
+{
+ (void)instance;
+ (void)thread_data;
+
+ struct nDPIsrvd_json_token const * const flow_event = TOKEN_GET_SZ(sock, "flow_event_name");
+ struct nDPIsrvd_json_token const * const packet_event = TOKEN_GET_SZ(sock, "packet_event_name");
+ struct nDPIsrvd_json_token const * const daemon_event = TOKEN_GET_SZ(sock, "daemon_event_name");
+ struct nDPIsrvd_json_token const * const error_event = TOKEN_GET_SZ(sock, "error_event_name");
+
+ COLLECTD_STATS_COUNTER_INC(json_lines);
+ collectd_statistics.counters.json_bytes += sock->buffer.json_message_length + NETWORK_BUFFER_LENGTH_DIGITS;
+
+ process_flow_stats(sock, flow);
+
+ if (flow_event != NULL &&
+ collectd_map_value_to_stat(sock, flow_event, flow_event_map, nDPIsrvd_ARRAY_LENGTH(flow_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown flow_event_name");
+ }
+
+ if (packet_event != NULL &&
+ collectd_map_value_to_stat(sock, packet_event, packet_event_map, nDPIsrvd_ARRAY_LENGTH(packet_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown packet_event_name");
+ }
+
+ if (daemon_event != NULL &&
+ collectd_map_value_to_stat(sock, daemon_event, daemon_event_map, nDPIsrvd_ARRAY_LENGTH(daemon_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown daemon_event_name");
+ }
+
+ if (error_event != NULL &&
+ collectd_map_value_to_stat(sock, error_event, error_event_map, nDPIsrvd_ARRAY_LENGTH(error_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown error_event_name");
+ }
+
+ return CALLBACK_OK;
+}
+
+int main(int argc, char ** argv)
+{
+ enum nDPIsrvd_connect_return connect_ret;
+ int retval = 1, epollfd = -1;
+
+ init_logging("nDPIsrvd-collectd");
+
+ struct nDPIsrvd_socket * sock =
+ nDPIsrvd_socket_init(0, 0, 0, sizeof(struct flow_user_data), collectd_json_callback, NULL, NULL);
+ if (sock == NULL)
+ {
+ LOGGER_EARLY(1, "%s", "nDPIsrvd socket memory allocation failed!");
+ return 1;
+ }
+
+ if (parse_options(argc, argv, sock) != 0)
+ {
+ goto failure;
+ }
+
+ if (getenv("COLLECTD_HOSTNAME") == NULL && getenv("COLLECTD_INTERVAL") == NULL)
+ {
+ LOGGER_EARLY(0, "Recv buffer size: %u", NETWORK_BUFFER_MAX_SIZE);
+ LOGGER_EARLY(0, "Connecting to `%s'..", serv_optarg);
+ }
+ else
+ {
+ LOGGER_EARLY(0, "Collectd hostname: %s", getenv("COLLECTD_HOSTNAME"));
+ LOGGER_EARLY(0, "Collectd interval: %llu", collectd_interval_ull);
+ }
+
+ if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
+ {
+ LOGGER_EARLY(1,
+ "Could not set stdout unbuffered: %s. Collectd may receive too old PUTVALs and complain.",
+ strerror(errno));
+ }
+
+ connect_ret = nDPIsrvd_connect(sock);
+ if (connect_ret != CONNECT_OK)
+ {
+ LOGGER_EARLY(1, "nDPIsrvd socket connect to %s failed!", serv_optarg);
+ goto failure;
+ }
+
+ if (nDPIsrvd_set_nonblock(sock) != 0)
+ {
+ LOGGER_EARLY(1, "nDPIsrvd set nonblock failed: %s", strerror(errno));
+ goto failure;
+ }
+
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, SIG_IGN);
+
+ collectd_pid = getppid();
+
+ epollfd = epoll_create1(0);
+ if (epollfd < 0)
+ {
+ LOGGER_EARLY(1, "Error creating epoll: %s", strerror(errno));
+ goto failure;
+ }
+
+ if (create_collectd_timer() != 0)
+ {
+ LOGGER_EARLY(1, "Error creating timer: %s", strerror(errno));
+ goto failure;
+ }
+
+ {
+ struct epoll_event timer_event = {.data.fd = collectd_timerfd, .events = EPOLLIN};
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, collectd_timerfd, &timer_event) < 0)
+ {
+ LOGGER_EARLY(1, "Error adding JSON fd to epoll: %s", strerror(errno));
+ goto failure;
+ }
+ }
+
+ {
+ struct epoll_event socket_event = {.data.fd = sock->fd, .events = EPOLLIN};
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock->fd, &socket_event) < 0)
+ {
+ LOGGER_EARLY(1, "Error adding nDPIsrvd socket fd to epoll: %s", strerror(errno));
+ goto failure;
+ }
+ }
+
+ logger(0, "%s", "Initialization succeeded.");
+ retval = mainloop(epollfd, sock);
+
+ if (getenv("COLLECTD_INTERVAL") == NULL)
+ {
+ print_collectd_exec_output();
+ }
+
+failure:
+ nDPIsrvd_socket_free(&sock);
+ close(collectd_timerfd);
+ close(epollfd);
+ shutdown_logging();
+
+ return retval;
+}
diff --git a/examples/c-collectd/plugin_nDPIsrvd.conf b/examples/c-collectd/plugin_nDPIsrvd.conf
new file mode 100644
index 000000000..f3cc37f62
--- /dev/null
+++ b/examples/c-collectd/plugin_nDPIsrvd.conf
@@ -0,0 +1,14 @@
+# nDPIsrvd collectd config file
+LoadPlugin exec
+<Plugin exec>
+ Exec "ndpi" "/usr/bin/nDPIsrvd-collectd"
+# Exec "ndpi" "/usr/bin/nDPIsrvd-collectd" "-s" "/tmp/ndpid-distributor.sock"
+# Exec "ndpi" "/tmp/nDPIsrvd-collectd" "-s" "127.0.0.1:7000"
+</Plugin>
+
+# Uncomment for testing
+#LoadPlugin write_log
+#LoadPlugin rrdtool
+#<Plugin rrdtool>
+# DataDir "nDPIsrvd-collectd"
+#</Plugin>
diff --git a/examples/c-collectd/rrdgraph.sh b/examples/c-collectd/rrdgraph.sh
new file mode 100755
index 000000000..3f9d8fb2b
--- /dev/null
+++ b/examples/c-collectd/rrdgraph.sh
@@ -0,0 +1,631 @@
+#!/usr/bin/env sh
+
+RRDDIR="${1}"
+OUTDIR="${2}"
+RRDARGS="--width=800 --height=400"
+REQUIRED_RRDCNT=130
+
+if [ -z "${RRDDIR}" ]; then
+ printf '%s: Missing RRD directory which contains nDPIsrvd/Collectd files.\n' "${0}"
+ exit 1
+fi
+
+if [ -z "${OUTDIR}" ]; then
+ printf '%s: Missing Output directory which contains HTML files.\n' "${0}"
+ exit 1
+fi
+
+if [ $(ls -al ${RRDDIR}/gauge-flow_*.rrd | wc -l) -ne ${REQUIRED_RRDCNT} ]; then
+ printf '%s: Missing some *.rrd files. Expected: %s, Got: %s\n' "${0}" "${REQUIRED_RRDCNT}" "$(ls -al ${RRDDIR}/gauge-flow_*.rrd | wc -l)"
+ exit 1
+fi
+
+if [ ! -r "${OUTDIR}/index.html" -o ! -r "${OUTDIR}/flows.html" -o ! -r "${OUTDIR}/other.html" -o ! -r "${OUTDIR}/detections.html" -o ! -r "${OUTDIR}/categories.html" ]; then
+ printf '%s: Missing some *.html files.\n' "${0}"
+ exit 1
+fi
+
+TIME_PAST_HOUR="--start=-3600 --end=-0"
+TIME_PAST_12HOURS="--start=-43200 --end=-0"
+TIME_PAST_DAY="--start=-86400 --end=-0"
+TIME_PAST_WEEK="--start=-604800 --end=-0"
+TIME_PAST_MONTH="--start=-2419200 --end=-0"
+TIME_PAST_3MONTHS="--start=-8035200 --end=-0"
+TIME_PAST_YEAR="--start=-31536000 --end=-0"
+
+rrdtool_graph_colorize_missing_data() {
+ printf 'CDEF:offline=%s,UN,INF,* AREA:offline#B3B3B311:' "${1}"
+}
+
+rrdtool_graph_print_cur_min_max_avg() {
+ printf 'GPRINT:%s:LAST:Current\:%%8.2lf ' "${1}"
+ printf 'GPRINT:%s:MIN:Minimum\:%%8.2lf ' "${1}"
+ printf 'GPRINT:%s:MAX:Maximum\:%%8.2lf ' "${1}"
+ printf 'GPRINT:%s:AVERAGE:Average\:%%8.2lf\\n' "${1}"
+}
+
+rrdtool_graph() {
+ TITLE="${1}"
+ shift
+ YAXIS_NAME="${1}"
+ shift
+ OUTPNG="${1}"
+ shift
+
+ rrdtool graph ${RRDARGS} -t "${TITLE} (past hour)" -v ${YAXIS_NAME} -Y ${TIME_PAST_HOUR} "${OUTPNG}_past_hour.png" ${*}
+ rrdtool graph ${RRDARGS} -t "${TITLE} (past 12 hours)" -v ${YAXIS_NAME} -Y ${TIME_PAST_12HOURS} "${OUTPNG}_past_12hours.png" ${*}
+ rrdtool graph ${RRDARGS} -t "${TITLE} (past day)" -v ${YAXIS_NAME} -Y ${TIME_PAST_DAY} "${OUTPNG}_past_day.png" ${*}
+ rrdtool graph ${RRDARGS} -t "${TITLE} (past week)" -v ${YAXIS_NAME} -Y ${TIME_PAST_WEEK} "${OUTPNG}_past_week.png" ${*}
+ rrdtool graph ${RRDARGS} -t "${TITLE} (past month)" -v ${YAXIS_NAME} -Y ${TIME_PAST_MONTH} "${OUTPNG}_past_month.png" ${*}
+ rrdtool graph ${RRDARGS} -t "${TITLE} (past 3 months)" -v ${YAXIS_NAME} -Y ${TIME_PAST_3MONTHS} "${OUTPNG}_past_month.png" ${*}
+ rrdtool graph ${RRDARGS} -t "${TITLE} (past year)" -v ${YAXIS_NAME} -Y ${TIME_PAST_YEAR} "${OUTPNG}_past_year.png" ${*}
+}
+
+rrdtool_graph Flows Amount "${OUTDIR}/flows" \
+ DEF:flows_active=${RRDDIR}/gauge-flow_active_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data flows_active) \
+ AREA:flows_active#54EC48::STACK \
+ LINE2:flows_active#24BC14:"Active" \
+ $(rrdtool_graph_print_cur_min_max_avg flows_active)
+rrdtool_graph Detections Amount "${OUTDIR}/detections" \
+ DEF:flows_detected=${RRDDIR}/gauge-flow_detected_count.rrd:value:AVERAGE \
+ DEF:flows_guessed=${RRDDIR}/gauge-flow_guessed_count.rrd:value:AVERAGE \
+ DEF:flows_not_detected=${RRDDIR}/gauge-flow_not_detected_count.rrd:value:AVERAGE \
+ DEF:flows_detection_update=${RRDDIR}/counter-flow_detection_update_count.rrd:value:AVERAGE \
+ DEF:flows_risky=${RRDDIR}/counter-flow_risky_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data flows_detected) \
+ AREA:flows_detected#00bfff::STACK \
+ AREA:flows_detection_update#a1b8c4::STACK \
+ AREA:flows_guessed#ffff4d::STACK \
+ AREA:flows_not_detected#ffa64d::STACK \
+ AREA:flows_risky#ff4000::STACK \
+ LINE2:flows_detected#0000ff:"Detected........" \
+ $(rrdtool_graph_print_cur_min_max_avg flows_detected) \
+ LINE2:flows_guessed#cccc00:"Guessed........." \
+ $(rrdtool_graph_print_cur_min_max_avg flows_guessed) \
+ LINE2:flows_not_detected#ff8000:"Not-Detected...." \
+ $(rrdtool_graph_print_cur_min_max_avg flows_not_detected) \
+ LINE2:flows_detection_update#4f6e7d:"Detection-Update" \
+ $(rrdtool_graph_print_cur_min_max_avg flows_detection_update) \
+ LINE2:flows_risky#b32d00:"Risky..........." \
+ $(rrdtool_graph_print_cur_min_max_avg flows_risky)
+rrdtool_graph "Traffic (IN/OUT)" Bytes "${OUTDIR}/traffic" \
+ DEF:total_src_bytes=${RRDDIR}/counter-flow_src_total_bytes.rrd:value:AVERAGE \
+ DEF:total_dst_bytes=${RRDDIR}/counter-flow_dst_total_bytes.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data total_src_bytes) \
+ AREA:total_src_bytes#00cc99:"Total-Bytes-Source2Dest":STACK \
+ $(rrdtool_graph_print_cur_min_max_avg total_src_bytes) \
+ STACK:total_dst_bytes#669999:"Total-Bytes-Dest2Source" \
+ $(rrdtool_graph_print_cur_min_max_avg total_dst_bytes)
+rrdtool_graph Layer3-Flows Amount "${OUTDIR}/layer3" \
+ DEF:layer3_ip4=${RRDDIR}/gauge-flow_l3_ip4_count.rrd:value:AVERAGE \
+ DEF:layer3_ip6=${RRDDIR}/gauge-flow_l3_ip6_count.rrd:value:AVERAGE \
+ DEF:layer3_other=${RRDDIR}/gauge-flow_l3_other_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data layer3_ip4) \
+ AREA:layer3_ip4#73d97d::STACK \
+ AREA:layer3_ip6#66b3ff::STACK \
+ AREA:layer3_other#bea1c4::STACK \
+ LINE2:layer3_ip4#21772a:"IPv4." \
+ $(rrdtool_graph_print_cur_min_max_avg layer3_ip4) \
+ LINE2:layer3_ip6#0066cc:"IPv6." \
+ $(rrdtool_graph_print_cur_min_max_avg layer3_ip6) \
+ LINE2:layer3_other#92629d:"Other" \
+ $(rrdtool_graph_print_cur_min_max_avg layer3_other)
+rrdtool_graph Layer4-Flows Amount "${OUTDIR}/layer4" \
+ DEF:layer4_tcp=${RRDDIR}/gauge-flow_l4_tcp_count.rrd:value:AVERAGE \
+ DEF:layer4_udp=${RRDDIR}/gauge-flow_l4_udp_count.rrd:value:AVERAGE \
+ DEF:layer4_icmp=${RRDDIR}/gauge-flow_l4_icmp_count.rrd:value:AVERAGE \
+ DEF:layer4_other=${RRDDIR}/gauge-flow_l4_other_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data layer4_tcp) \
+ AREA:layer4_tcp#73d97d::STACK \
+ AREA:layer4_udp#66b3ff::STACK \
+ AREA:layer4_icmp#ee5d9a::STACK \
+ AREA:layer4_other#bea1c4::STACK \
+ LINE2:layer4_tcp#21772a:"TCP.." \
+ $(rrdtool_graph_print_cur_min_max_avg layer4_tcp) \
+ LINE2:layer4_udp#0066cc:"UDP.." \
+ $(rrdtool_graph_print_cur_min_max_avg layer4_udp) \
+ LINE2:layer4_icmp#d01663:"ICMP." \
+ $(rrdtool_graph_print_cur_min_max_avg layer4_icmp) \
+ LINE2:layer4_other#83588d:"Other" \
+ $(rrdtool_graph_print_cur_min_max_avg layer4_other)
+rrdtool_graph Confidence Amount "${OUTDIR}/confidence" \
+ DEF:conf_ip=${RRDDIR}/gauge-flow_confidence_by_ip.rrd:value:AVERAGE \
+ DEF:conf_port=${RRDDIR}/gauge-flow_confidence_by_port.rrd:value:AVERAGE \
+ DEF:conf_aggr=${RRDDIR}/gauge-flow_confidence_dpi_aggressive.rrd:value:AVERAGE \
+ DEF:conf_cache=${RRDDIR}/gauge-flow_confidence_dpi_cache.rrd:value:AVERAGE \
+ DEF:conf_pcache=${RRDDIR}/gauge-flow_confidence_dpi_partial_cache.rrd:value:AVERAGE \
+ DEF:conf_part=${RRDDIR}/gauge-flow_confidence_dpi_partial.rrd:value:AVERAGE \
+ DEF:conf_dpi=${RRDDIR}/gauge-flow_confidence_dpi.rrd:value:AVERAGE \
+ DEF:conf_nbpf=${RRDDIR}/gauge-flow_confidence_nbpf.rrd:value:AVERAGE \
+ DEF:conf_ukn=${RRDDIR}/gauge-flow_confidence_unknown.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data conf_ip) \
+ AREA:conf_ip#4dff4d::STACK \
+ AREA:conf_port#c2ff33::STACK \
+ AREA:conf_aggr#ffe433::STACK \
+ AREA:conf_cache#ffb133::STACK \
+ AREA:conf_pcache#ff5f33::STACK \
+ AREA:conf_part#e74b5b::STACK \
+ AREA:conf_dpi#a5aca0::STACK \
+ AREA:conf_nbpf#d7c1cc::STACK \
+ AREA:conf_ukn#ddccbb::STACK \
+ LINE2:conf_ip#00e600:"By-IP................" \
+ $(rrdtool_graph_print_cur_min_max_avg conf_ip) \
+ LINE2:conf_port#8fce00:"By-Port.............." \
+ $(rrdtool_graph_print_cur_min_max_avg conf_port) \
+ LINE2:conf_aggr#e6c700:"DPI-Aggressive......." \
+ $(rrdtool_graph_print_cur_min_max_avg conf_aggr) \
+ LINE2:conf_cache#e68e00:"DPI-Cache............" \
+ $(rrdtool_graph_print_cur_min_max_avg conf_cache) \
+ LINE2:conf_pcache#e63200:"DPI-Partial-Cache...." \
+ $(rrdtool_graph_print_cur_min_max_avg conf_pcache) \
+ LINE2:conf_part#c61b2b:"DPI-Partial.........." \
+ $(rrdtool_graph_print_cur_min_max_avg conf_part) \
+ LINE2:conf_dpi#7e8877:"DPI.................." \
+ $(rrdtool_graph_print_cur_min_max_avg conf_dpi) \
+ LINE2:conf_nbpf#ae849a:"nBPF................." \
+ $(rrdtool_graph_print_cur_min_max_avg conf_nbpf) \
+ LINE2:conf_ukn#aa9988:"Unknown.............." \
+ $(rrdtool_graph_print_cur_min_max_avg conf_ukn)
+rrdtool_graph Breeds Amount "${OUTDIR}/breed" \
+ DEF:breed_safe=${RRDDIR}/gauge-flow_breed_safe_count.rrd:value:AVERAGE \
+ DEF:breed_acceptable=${RRDDIR}/gauge-flow_breed_acceptable_count.rrd:value:AVERAGE \
+ DEF:breed_fun=${RRDDIR}/gauge-flow_breed_fun_count.rrd:value:AVERAGE \
+ DEF:breed_unsafe=${RRDDIR}/gauge-flow_breed_unsafe_count.rrd:value:AVERAGE \
+ DEF:breed_potentially_dangerous=${RRDDIR}/gauge-flow_breed_potentially_dangerous_count.rrd:value:AVERAGE \
+ DEF:breed_dangerous=${RRDDIR}/gauge-flow_breed_dangerous_count.rrd:value:AVERAGE \
+ DEF:breed_unrated=${RRDDIR}/gauge-flow_breed_unrated_count.rrd:value:AVERAGE \
+ DEF:breed_unknown=${RRDDIR}/gauge-flow_breed_unknown_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data breed_safe) \
+ AREA:breed_safe#4dff4d::STACK \
+ AREA:breed_acceptable#c2ff33::STACK \
+ AREA:breed_fun#ffe433::STACK \
+ AREA:breed_unsafe#ffb133::STACK \
+ AREA:breed_potentially_dangerous#ff5f33::STACK \
+ AREA:breed_dangerous#e74b5b::STACK \
+ AREA:breed_unrated#a5aca0::STACK \
+ AREA:breed_unknown#d7c1cc::STACK \
+ LINE2:breed_safe#00e600:"Safe................." \
+ $(rrdtool_graph_print_cur_min_max_avg breed_safe) \
+ LINE2:breed_acceptable#8fce00:"Acceptable..........." \
+ $(rrdtool_graph_print_cur_min_max_avg breed_acceptable) \
+ LINE2:breed_fun#e6c700:"Fun.................." \
+ $(rrdtool_graph_print_cur_min_max_avg breed_fun) \
+ LINE2:breed_unsafe#e68e00:"Unsafe..............." \
+ $(rrdtool_graph_print_cur_min_max_avg breed_unsafe) \
+ LINE2:breed_potentially_dangerous#e63200:"Potentially-Dangerous" \
+ $(rrdtool_graph_print_cur_min_max_avg breed_potentially_dangerous) \
+ LINE2:breed_dangerous#c61b2b:"Dangerous............" \
+ $(rrdtool_graph_print_cur_min_max_avg breed_dangerous) \
+ LINE2:breed_unrated#7e8877:"Unrated.............." \
+ $(rrdtool_graph_print_cur_min_max_avg breed_unrated) \
+ LINE2:breed_unknown#ae849a:"Unknown.............." \
+ $(rrdtool_graph_print_cur_min_max_avg breed_unknown)
+rrdtool_graph Categories 'Amount(SUM)' "${OUTDIR}/categories" \
+ DEF:cat_adlt=${RRDDIR}/gauge-flow_category_adult_content_count.rrd:value:AVERAGE \
+ DEF:cat_ads=${RRDDIR}/gauge-flow_category_advertisment_count.rrd:value:AVERAGE \
+ DEF:cat_chat=${RRDDIR}/gauge-flow_category_chat_count.rrd:value:AVERAGE \
+ DEF:cat_cloud=${RRDDIR}/gauge-flow_category_cloud_count.rrd:value:AVERAGE \
+ DEF:cat_collab=${RRDDIR}/gauge-flow_category_collaborative_count.rrd:value:AVERAGE \
+ DEF:cat_conn=${RRDDIR}/gauge-flow_category_conn_check_count.rrd:value:AVERAGE \
+ DEF:cat_cybr=${RRDDIR}/gauge-flow_category_cybersecurity_count.rrd:value:AVERAGE \
+ DEF:cat_xfer=${RRDDIR}/gauge-flow_category_data_transfer_count.rrd:value:AVERAGE \
+ DEF:cat_db=${RRDDIR}/gauge-flow_category_database_count.rrd:value:AVERAGE \
+ DEF:cat_dl=${RRDDIR}/gauge-flow_category_download_count.rrd:value:AVERAGE \
+ DEF:cat_mail=${RRDDIR}/gauge-flow_category_email_count.rrd:value:AVERAGE \
+ DEF:cat_fs=${RRDDIR}/gauge-flow_category_file_sharing_count.rrd:value:AVERAGE \
+ DEF:cat_game=${RRDDIR}/gauge-flow_category_game_count.rrd:value:AVERAGE \
+ DEF:cat_gamb=${RRDDIR}/gauge-flow_category_gambling_count.rrd:value:AVERAGE \
+ DEF:cat_iot=${RRDDIR}/gauge-flow_category_iot_scada_count.rrd:value:AVERAGE \
+ DEF:cat_mal=${RRDDIR}/gauge-flow_category_malware_count.rrd:value:AVERAGE \
+ DEF:cat_med=${RRDDIR}/gauge-flow_category_media_count.rrd:value:AVERAGE \
+ DEF:cat_min=${RRDDIR}/gauge-flow_category_mining_count.rrd:value:AVERAGE \
+ DEF:cat_mus=${RRDDIR}/gauge-flow_category_music_count.rrd:value:AVERAGE \
+ DEF:cat_net=${RRDDIR}/gauge-flow_category_network_count.rrd:value:AVERAGE \
+ DEF:cat_prod=${RRDDIR}/gauge-flow_category_productivity_count.rrd:value:AVERAGE \
+ DEF:cat_rem=${RRDDIR}/gauge-flow_category_remote_access_count.rrd:value:AVERAGE \
+ DEF:cat_rpc=${RRDDIR}/gauge-flow_category_rpc_count.rrd:value:AVERAGE \
+ DEF:cat_shop=${RRDDIR}/gauge-flow_category_shopping_count.rrd:value:AVERAGE \
+ DEF:cat_soc=${RRDDIR}/gauge-flow_category_social_network_count.rrd:value:AVERAGE \
+ DEF:cat_soft=${RRDDIR}/gauge-flow_category_software_update_count.rrd:value:AVERAGE \
+ DEF:cat_str=${RRDDIR}/gauge-flow_category_streaming_count.rrd:value:AVERAGE \
+ DEF:cat_sys=${RRDDIR}/gauge-flow_category_system_count.rrd:value:AVERAGE \
+ DEF:cat_ukn=${RRDDIR}/gauge-flow_category_unknown_count.rrd:value:AVERAGE \
+ DEF:cat_uns=${RRDDIR}/gauge-flow_category_unspecified_count.rrd:value:AVERAGE \
+ DEF:cat_vid=${RRDDIR}/gauge-flow_category_video_count.rrd:value:AVERAGE \
+ DEF:cat_vrt=${RRDDIR}/gauge-flow_category_virt_assistant_count.rrd:value:AVERAGE \
+ DEF:cat_voip=${RRDDIR}/gauge-flow_category_voip_count.rrd:value:AVERAGE \
+ DEF:cat_vpn=${RRDDIR}/gauge-flow_category_vpn_count.rrd:value:AVERAGE \
+ DEF:cat_web=${RRDDIR}/gauge-flow_category_web_count.rrd:value:AVERAGE \
+ DEF:cat_banned=${RRDDIR}/gauge-flow_category_banned_site_count.rrd:value:AVERAGE \
+ DEF:cat_unavail=${RRDDIR}/gauge-flow_category_site_unavail_count.rrd:value:AVERAGE \
+ DEF:cat_allowed=${RRDDIR}/gauge-flow_category_allowed_site_count.rrd:value:AVERAGE \
+ DEF:cat_antimal=${RRDDIR}/gauge-flow_category_antimalware_count.rrd:value:AVERAGE \
+ DEF:cat_crypto=${RRDDIR}/gauge-flow_category_crypto_currency_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data cat_adlt) \
+ AREA:cat_adlt#f0c032:"Adult.................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_adlt) \
+ STACK:cat_ads#f1c232:"Advertisment..........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_ads) \
+ STACK:cat_chat#6fa8dc:"Chat..................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_chat) \
+ STACK:cat_cloud#2986cc:"Cloud.................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_cloud) \
+ STACK:cat_collab#3212aa:"Collaborative.........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_collab) \
+ STACK:cat_conn#22aa11:"Connection-Check......." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_conn) \
+ STACK:cat_cybr#00ff00:"Cybersecurity.........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_cybr) \
+ STACK:cat_xfer#16537e:"Data-Transfer.........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_xfer) \
+ STACK:cat_db#cc0000:"Database..............." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_db) \
+ STACK:cat_dl#6a329f:"Download..............." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_dl) \
+ STACK:cat_mail#3600cc:"Mail..................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_mail) \
+ STACK:cat_fs#c90076:"File-Sharing..........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_fs) \
+ STACK:cat_game#00ff26:"Game..................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_game) \
+ STACK:cat_gamb#aa0026:"Gambling..............." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_gamb) \
+ STACK:cat_iot#227867:"IoT-Scada.............." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_iot) \
+ STACK:cat_mal#f44336:"Malware................" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_mal) \
+ STACK:cat_med#ff8300:"Media.................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_med) \
+ STACK:cat_min#ff0000:"Mining................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_min) \
+ STACK:cat_mus#00fff0:"Music.................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_mus) \
+ STACK:cat_net#ddff00:"Network................" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_net) \
+ STACK:cat_prod#29ff00:"Productivity..........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_prod) \
+ STACK:cat_rem#b52c2c:"Remote-Access.........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_rem) \
+ STACK:cat_rpc#e15a5a:"Remote-Procedure-Call.." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_rpc) \
+ STACK:cat_shop#0065ff:"Shopping..............." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_shop) \
+ STACK:cat_soc#8fce00:"Social-Network........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_soc) \
+ STACK:cat_soft#007a0d:"Software-Update........" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_soft) \
+ STACK:cat_str#ff00b8:"Streaming.............." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_str) \
+ STACK:cat_sys#f4ff00:"System................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_sys) \
+ STACK:cat_ukn#999999:"Unknown................" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_ukn) \
+ STACK:cat_uns#999999:"Unspecified............" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_uns) \
+ STACK:cat_vid#518820:"Video.................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_vid) \
+ STACK:cat_vrt#216820:"Virtual-Assistant......" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_vrt) \
+ STACK:cat_voip#ffc700:"Voice-Over-IP.........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_voip) \
+ STACK:cat_vpn#378035:"Virtual-Private-Network" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_vpn) \
+ STACK:cat_web#00fffb:"Web...................." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_web) \
+ STACK:cat_banned#ff1010:"Banned-Sites..........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_banned) \
+ STACK:cat_unavail#ff1010:"Sites-Unavailable......" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_unavail) \
+ STACK:cat_allowed#ff1010:"Allowed-Sites.........." \
+ $(rrdtool_graph_print_cur_min_max_avg cat_allowed) \
+ STACK:cat_antimal#ff1010:"Antimalware............" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_antimal) \
+ STACK:cat_crypto#afaf00:"Crypto-Currency........" \
+ $(rrdtool_graph_print_cur_min_max_avg cat_crypto)
+rrdtool_graph JSON 'Lines' "${OUTDIR}/json_lines" \
+ DEF:json_lines=${RRDDIR}/counter-json_lines.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data json_lines) \
+ AREA:json_lines#4dff4d::STACK \
+ LINE2:json_lines#00e600:"JSON-lines" \
+ $(rrdtool_graph_print_cur_min_max_avg json_lines)
+rrdtool_graph JSON 'Bytes' "${OUTDIR}/json_bytes" \
+ DEF:json_bytes=${RRDDIR}/counter-json_bytes.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data json_bytes) \
+ AREA:json_bytes#4dff4d::STACK \
+ LINE2:json_bytes#00e600:"JSON-bytes" \
+ $(rrdtool_graph_print_cur_min_max_avg json_bytes)
+rrdtool_graph Events 'Amount' "${OUTDIR}/events" \
+ DEF:init=${RRDDIR}/counter-init_count.rrd:value:AVERAGE \
+ DEF:reconnect=${RRDDIR}/counter-reconnect_count.rrd:value:AVERAGE \
+ DEF:shutdown=${RRDDIR}/counter-shutdown_count.rrd:value:AVERAGE \
+ DEF:status=${RRDDIR}/counter-status_count.rrd:value:AVERAGE \
+ DEF:packet=${RRDDIR}/counter-packet_count.rrd:value:AVERAGE \
+ DEF:packet_flow=${RRDDIR}/counter-packet_flow_count.rrd:value:AVERAGE \
+ DEF:new=${RRDDIR}/counter-flow_new_count.rrd:value:AVERAGE \
+ DEF:ewd=${RRDDIR}/counter-flow_end_count.rrd:value:AVERAGE \
+ DEF:idle=${RRDDIR}/counter-flow_idle_count.rrd:value:AVERAGE \
+ DEF:update=${RRDDIR}/counter-flow_update_count.rrd:value:AVERAGE \
+ DEF:detection_update=${RRDDIR}/counter-flow_detection_update_count.rrd:value:AVERAGE \
+ DEF:guessed=${RRDDIR}/counter-flow_guessed_count.rrd:value:AVERAGE \
+ DEF:detected=${RRDDIR}/counter-flow_detected_count.rrd:value:AVERAGE \
+ DEF:not_detected=${RRDDIR}/counter-flow_not_detected_count.rrd:value:AVERAGE \
+ DEF:analyse=${RRDDIR}/counter-flow_analyse_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data init) \
+ AREA:init#f1c232:"Init..................." \
+ $(rrdtool_graph_print_cur_min_max_avg init) \
+ STACK:reconnect#63bad9:"Reconnect.............." \
+ $(rrdtool_graph_print_cur_min_max_avg reconnect) \
+ STACK:shutdown#3a6f82:"Shutdown..............." \
+ $(rrdtool_graph_print_cur_min_max_avg shutdown) \
+ STACK:status#b7cbd1:"Status................." \
+ $(rrdtool_graph_print_cur_min_max_avg status) \
+ STACK:packet#0aff3f:"Packet................." \
+ $(rrdtool_graph_print_cur_min_max_avg packet) \
+ STACK:packet_flow#00c72b:"Packet-Flow............" \
+ $(rrdtool_graph_print_cur_min_max_avg packet_flow) \
+ STACK:new#c76700:"New...................." \
+ $(rrdtool_graph_print_cur_min_max_avg new) \
+ STACK:ewd#c78500:"End...................." \
+ $(rrdtool_graph_print_cur_min_max_avg ewd) \
+ STACK:idle#c7a900:"Idle..................." \
+ $(rrdtool_graph_print_cur_min_max_avg idle) \
+ STACK:update#c7c400:"Updates................" \
+ $(rrdtool_graph_print_cur_min_max_avg update) \
+ STACK:detection_update#a2c700:"Detection-Updates......" \
+ $(rrdtool_graph_print_cur_min_max_avg detection_update) \
+ STACK:guessed#7bc700:"Guessed................" \
+ $(rrdtool_graph_print_cur_min_max_avg guessed) \
+ STACK:detected#00c781:"Detected..............." \
+ $(rrdtool_graph_print_cur_min_max_avg detected) \
+ STACK:not_detected#00bdc7:"Not-Detected..........." \
+ $(rrdtool_graph_print_cur_min_max_avg not_detected) \
+ STACK:analyse#1400c7:"Analyse................" \
+ $(rrdtool_graph_print_cur_min_max_avg analyse)
+rrdtool_graph Error-Events 'Amount' "${OUTDIR}/error_events" \
+ DEF:error_0=${RRDDIR}/counter-error_unknown_datalink.rrd:value:AVERAGE \
+ DEF:error_1=${RRDDIR}/counter-error_unknown_l3_protocol.rrd:value:AVERAGE \
+ DEF:error_2=${RRDDIR}/counter-error_unsupported_datalink.rrd:value:AVERAGE \
+ DEF:error_3=${RRDDIR}/counter-error_packet_too_short.rrd:value:AVERAGE \
+ DEF:error_4=${RRDDIR}/counter-error_packet_type_unknown.rrd:value:AVERAGE \
+ DEF:error_5=${RRDDIR}/counter-error_packet_header_invalid.rrd:value:AVERAGE \
+ DEF:error_6=${RRDDIR}/counter-error_ip4_packet_too_short.rrd:value:AVERAGE \
+ DEF:error_7=${RRDDIR}/counter-error_ip4_size_smaller_than_header.rrd:value:AVERAGE \
+ DEF:error_8=${RRDDIR}/counter-error_ip4_l4_payload_detection.rrd:value:AVERAGE \
+ DEF:error_9=${RRDDIR}/counter-error_ip6_packet_too_short.rrd:value:AVERAGE \
+ DEF:error_10=${RRDDIR}/counter-error_ip6_size_smaller_than_header.rrd:value:AVERAGE \
+ DEF:error_11=${RRDDIR}/counter-error_ip6_l4_payload_detection.rrd:value:AVERAGE \
+ DEF:error_12=${RRDDIR}/counter-error_tcp_packet_too_short.rrd:value:AVERAGE \
+ DEF:error_13=${RRDDIR}/counter-error_udp_packet_too_short.rrd:value:AVERAGE \
+ DEF:error_14=${RRDDIR}/counter-error_capture_size_smaller_than_packet.rrd:value:AVERAGE \
+ DEF:error_15=${RRDDIR}/counter-error_max_flows_to_track.rrd:value:AVERAGE \
+ DEF:error_16=${RRDDIR}/counter-error_flow_memory_alloc.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data error_0) \
+ AREA:error_0#ff6a00:"Unknown-datalink-layer-packet............................" \
+ $(rrdtool_graph_print_cur_min_max_avg error_0) \
+ STACK:error_1#bf7540:"Unknown-L3-protocol......................................" \
+ $(rrdtool_graph_print_cur_min_max_avg error_1) \
+ STACK:error_2#ffd500:"Unsupported-datalink-layer..............................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_2) \
+ STACK:error_3#bfaa40:"Packet-too-short........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_3) \
+ STACK:error_4#bfff00:"Unknown-packet-type......................................" \
+ $(rrdtool_graph_print_cur_min_max_avg error_4) \
+ STACK:error_5#9fbf40:"Packet-header-invalid...................................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_5) \
+ STACK:error_6#55ff00:"IP4-packet-too-short....................................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_6) \
+ STACK:error_7#6abf40:"Packet-smaller-than-IP4-header..........................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_7) \
+ STACK:error_8#00ff15:"nDPI-IPv4/L4-payload-detection-failed...................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_8) \
+ STACK:error_9#40bf4a:"IP6-packet-too-short....................................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_9) \
+ STACK:error_10#00ff80:"Packet-smaller-than-IP6-header..........................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_10) \
+ STACK:error_11#40bf80:"nDPI-IPv6/L4-payload-detection-failed...................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_11) \
+ STACK:error_12#00ffea:"TCP-packet-smaller-than-expected........................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_12) \
+ STACK:error_13#40bfb5:"UDP-packet-smaller-than-expected........................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_13) \
+ STACK:error_14#00aaff:"Captured-packet-size-is-smaller-than-expected-packet-size" \
+ $(rrdtool_graph_print_cur_min_max_avg error_14) \
+ STACK:error_15#4095bf:"Max-flows-to-track-reached..............................." \
+ $(rrdtool_graph_print_cur_min_max_avg error_15) \
+ STACK:error_16#0040ff:"Flow-memory-allocation-failed............................" \
+ $(rrdtool_graph_print_cur_min_max_avg error_16)
+rrdtool_graph Risk-Severites Amount "${OUTDIR}/severities" \
+ DEF:sever_crit=${RRDDIR}/gauge-flow_severity_critical.rrd:value:AVERAGE \
+ DEF:sever_emer=${RRDDIR}/gauge-flow_severity_emergency.rrd:value:AVERAGE \
+ DEF:sever_high=${RRDDIR}/gauge-flow_severity_high.rrd:value:AVERAGE \
+ DEF:sever_low=${RRDDIR}/gauge-flow_severity_low.rrd:value:AVERAGE \
+ DEF:sever_med=${RRDDIR}/gauge-flow_severity_medium.rrd:value:AVERAGE \
+ DEF:sever_sev=${RRDDIR}/gauge-flow_severity_severe.rrd:value:AVERAGE \
+ DEF:sever_ukn=${RRDDIR}/gauge-flow_severity_unknown.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data sever_crit) \
+ AREA:sever_crit#e68e00::STACK \
+ AREA:sever_emer#e63200::STACK \
+ AREA:sever_high#e6c700::STACK \
+ AREA:sever_low#00e600::STACK \
+ AREA:sever_med#8fce00::STACK \
+ AREA:sever_sev#c61b2b::STACK \
+ AREA:sever_ukn#7e8877::STACK \
+ LINE2:sever_crit#e68e00:"Critical............." \
+ $(rrdtool_graph_print_cur_min_max_avg sever_crit) \
+ LINE2:sever_emer#e63200:"Emergency............" \
+ $(rrdtool_graph_print_cur_min_max_avg sever_emer) \
+ LINE2:sever_high#e6c700:"High................." \
+ $(rrdtool_graph_print_cur_min_max_avg sever_high) \
+ LINE2:sever_low#00e600:"Low.................." \
+ $(rrdtool_graph_print_cur_min_max_avg sever_low) \
+ LINE2:sever_med#8fce00:"Medium..............." \
+ $(rrdtool_graph_print_cur_min_max_avg sever_med) \
+ LINE2:sever_sev#c61b2b:"Severe..............." \
+ $(rrdtool_graph_print_cur_min_max_avg sever_sev) \
+ LINE2:sever_ukn#7e8877:"Unknown.............." \
+ $(rrdtool_graph_print_cur_min_max_avg sever_ukn)
+rrdtool_graph Risks 'Amount' "${OUTDIR}/risky_events" \
+ DEF:risk_1=${RRDDIR}/gauge-flow_risk_1_count.rrd:value:AVERAGE \
+ DEF:risk_2=${RRDDIR}/gauge-flow_risk_2_count.rrd:value:AVERAGE \
+ DEF:risk_3=${RRDDIR}/gauge-flow_risk_3_count.rrd:value:AVERAGE \
+ DEF:risk_4=${RRDDIR}/gauge-flow_risk_4_count.rrd:value:AVERAGE \
+ DEF:risk_5=${RRDDIR}/gauge-flow_risk_5_count.rrd:value:AVERAGE \
+ DEF:risk_6=${RRDDIR}/gauge-flow_risk_6_count.rrd:value:AVERAGE \
+ DEF:risk_7=${RRDDIR}/gauge-flow_risk_7_count.rrd:value:AVERAGE \
+ DEF:risk_8=${RRDDIR}/gauge-flow_risk_8_count.rrd:value:AVERAGE \
+ DEF:risk_9=${RRDDIR}/gauge-flow_risk_9_count.rrd:value:AVERAGE \
+ DEF:risk_10=${RRDDIR}/gauge-flow_risk_10_count.rrd:value:AVERAGE \
+ DEF:risk_11=${RRDDIR}/gauge-flow_risk_11_count.rrd:value:AVERAGE \
+ DEF:risk_12=${RRDDIR}/gauge-flow_risk_12_count.rrd:value:AVERAGE \
+ DEF:risk_13=${RRDDIR}/gauge-flow_risk_13_count.rrd:value:AVERAGE \
+ DEF:risk_14=${RRDDIR}/gauge-flow_risk_14_count.rrd:value:AVERAGE \
+ DEF:risk_15=${RRDDIR}/gauge-flow_risk_15_count.rrd:value:AVERAGE \
+ DEF:risk_16=${RRDDIR}/gauge-flow_risk_16_count.rrd:value:AVERAGE \
+ DEF:risk_17=${RRDDIR}/gauge-flow_risk_17_count.rrd:value:AVERAGE \
+ DEF:risk_18=${RRDDIR}/gauge-flow_risk_18_count.rrd:value:AVERAGE \
+ DEF:risk_19=${RRDDIR}/gauge-flow_risk_19_count.rrd:value:AVERAGE \
+ DEF:risk_20=${RRDDIR}/gauge-flow_risk_20_count.rrd:value:AVERAGE \
+ DEF:risk_21=${RRDDIR}/gauge-flow_risk_21_count.rrd:value:AVERAGE \
+ DEF:risk_22=${RRDDIR}/gauge-flow_risk_22_count.rrd:value:AVERAGE \
+ DEF:risk_23=${RRDDIR}/gauge-flow_risk_23_count.rrd:value:AVERAGE \
+ DEF:risk_24=${RRDDIR}/gauge-flow_risk_24_count.rrd:value:AVERAGE \
+ DEF:risk_25=${RRDDIR}/gauge-flow_risk_25_count.rrd:value:AVERAGE \
+ DEF:risk_26=${RRDDIR}/gauge-flow_risk_26_count.rrd:value:AVERAGE \
+ DEF:risk_27=${RRDDIR}/gauge-flow_risk_27_count.rrd:value:AVERAGE \
+ DEF:risk_28=${RRDDIR}/gauge-flow_risk_28_count.rrd:value:AVERAGE \
+ DEF:risk_29=${RRDDIR}/gauge-flow_risk_29_count.rrd:value:AVERAGE \
+ DEF:risk_30=${RRDDIR}/gauge-flow_risk_30_count.rrd:value:AVERAGE \
+ DEF:risk_31=${RRDDIR}/gauge-flow_risk_31_count.rrd:value:AVERAGE \
+ DEF:risk_32=${RRDDIR}/gauge-flow_risk_32_count.rrd:value:AVERAGE \
+ DEF:risk_33=${RRDDIR}/gauge-flow_risk_33_count.rrd:value:AVERAGE \
+ DEF:risk_34=${RRDDIR}/gauge-flow_risk_34_count.rrd:value:AVERAGE \
+ DEF:risk_35=${RRDDIR}/gauge-flow_risk_35_count.rrd:value:AVERAGE \
+ DEF:risk_36=${RRDDIR}/gauge-flow_risk_36_count.rrd:value:AVERAGE \
+ DEF:risk_37=${RRDDIR}/gauge-flow_risk_37_count.rrd:value:AVERAGE \
+ DEF:risk_38=${RRDDIR}/gauge-flow_risk_38_count.rrd:value:AVERAGE \
+ DEF:risk_39=${RRDDIR}/gauge-flow_risk_39_count.rrd:value:AVERAGE \
+ DEF:risk_40=${RRDDIR}/gauge-flow_risk_40_count.rrd:value:AVERAGE \
+ DEF:risk_41=${RRDDIR}/gauge-flow_risk_41_count.rrd:value:AVERAGE \
+ DEF:risk_42=${RRDDIR}/gauge-flow_risk_42_count.rrd:value:AVERAGE \
+ DEF:risk_43=${RRDDIR}/gauge-flow_risk_43_count.rrd:value:AVERAGE \
+ DEF:risk_44=${RRDDIR}/gauge-flow_risk_44_count.rrd:value:AVERAGE \
+ DEF:risk_45=${RRDDIR}/gauge-flow_risk_45_count.rrd:value:AVERAGE \
+ DEF:risk_46=${RRDDIR}/gauge-flow_risk_46_count.rrd:value:AVERAGE \
+ DEF:risk_47=${RRDDIR}/gauge-flow_risk_47_count.rrd:value:AVERAGE \
+ DEF:risk_48=${RRDDIR}/gauge-flow_risk_48_count.rrd:value:AVERAGE \
+ DEF:risk_49=${RRDDIR}/gauge-flow_risk_49_count.rrd:value:AVERAGE \
+ DEF:risk_50=${RRDDIR}/gauge-flow_risk_50_count.rrd:value:AVERAGE \
+ DEF:risk_51=${RRDDIR}/gauge-flow_risk_51_count.rrd:value:AVERAGE \
+ DEF:risk_52=${RRDDIR}/gauge-flow_risk_52_count.rrd:value:AVERAGE \
+ DEF:risk_53=${RRDDIR}/gauge-flow_risk_53_count.rrd:value:AVERAGE \
+ DEF:risk_unknown=${RRDDIR}/gauge-flow_risk_unknown_count.rrd:value:AVERAGE \
+ $(rrdtool_graph_colorize_missing_data risk_1) \
+ AREA:risk_1#ff0000:"XSS-Attack..............................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_1) \
+ STACK:risk_2#ff5500:"SQL-Injection............................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_2) \
+ STACK:risk_3#ffaa00:"RCE-Injection............................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_3) \
+ STACK:risk_4#ffff00:"Binary-App-Transfer......................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_4) \
+ STACK:risk_5#aaff00:"Known-Proto-on-Non-Std-Port.............................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_5) \
+ STACK:risk_6#55ff00:"Self-signed-Cert........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_6) \
+ STACK:risk_7#00ff55:"Obsolete-TLS-v1.1-or-older..............................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_7) \
+ STACK:risk_8#00ffaa:"Weak-TLS-Cipher.........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_8) \
+ STACK:risk_9#00ffff:"TLS-Cert-Expired........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_9) \
+ STACK:risk_10#00aaff:"TLS-Cert-Mismatch........................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_10) \
+ STACK:risk_11#0055ff:"HTTP-Suspicious-User-Agent..............................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_11) \
+ STACK:risk_12#0000ff:"HTTP-Numeric-IP-Address.................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_12) \
+ STACK:risk_13#5500ff:"HTTP-Suspicious-URL......................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_13) \
+ STACK:risk_14#aa00ff:"HTTP-Suspicious-Header..................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_14) \
+ STACK:risk_15#ff00ff:"TLS-probably-Not-Carrying-HTTPS.........................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_15) \
+ STACK:risk_16#ff00aa:"Suspicious-DGA-Domain-name..............................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_16) \
+ STACK:risk_17#ff0055:"Malformed-Packet........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_17) \
+ STACK:risk_18#602020:"SSH-Obsolete-Client-Version/Cipher......................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_18) \
+ STACK:risk_19#603a20:"SSH-Obsolete-Server-Version/Cipher......................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_19) \
+ STACK:risk_20#605520:"SMB-Insecure-Version....................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_20) \
+ STACK:risk_21#506020:"TLS-Suspicious-ESNI-Usage................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_21) \
+ STACK:risk_22#356020:"Unsafe-Protocol.........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_22) \
+ STACK:risk_23#206025:"Suspicious-DNS-Traffic..................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_23) \
+ STACK:risk_24#206040:"Missing-SNI-TLS-Extension................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_24) \
+ STACK:risk_25#20605a:"HTTP-Suspicious-Content.................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_25) \
+ STACK:risk_26#204a60:"Risky-ASN................................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_26) \
+ STACK:risk_27#203060:"Risky-Domain-Name........................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_27) \
+ STACK:risk_28#2a2060:"Malicious-JA3-Fingerprint................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_28) \
+ STACK:risk_29#452060:"Malicious-SSL-Cert/SHA1-Fingerprint......................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_29) \
+ STACK:risk_30#602060:"Desktop/File-Sharing....................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_30) \
+ STACK:risk_31#602045:"Uncommon-TLS-ALPN........................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_31) \
+ STACK:risk_32#df2020:"TLS-Cert-Validity-Too-Long..............................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_32) \
+ STACK:risk_33#df6020:"TLS-Suspicious-Extension................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_33) \
+ STACK:risk_34#df9f20:"TLS-Fatal-Alert.........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_34) \
+ STACK:risk_35#dfdf20:"Suspicious-Entropy......................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_35) \
+ STACK:risk_36#9fdf20:"Clear-Text-Credentials..................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_36) \
+ STACK:risk_37#60df20:"Large-DNS-Packet........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_37) \
+ STACK:risk_38#20df20:"Fragmented-DNS-Message..................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_38) \
+ STACK:risk_39#20df60:"Text-With-Non-Printable-Chars............................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_39) \
+ STACK:risk_40#20df9f:"Possible-Exploit........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_40) \
+ STACK:risk_41#20dfdf:"TLS-Cert-About-To-Expire................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_41) \
+ STACK:risk_42#209fdf:"IDN-Domain-Name.........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_42) \
+ STACK:risk_43#2060df:"Error-Code..............................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_43) \
+ STACK:risk_44#2020df:"Crawler/Bot.............................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_44) \
+ STACK:risk_45#6020df:"Anonymous-Subscriber....................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_45) \
+ STACK:risk_46#9f20df:"Unidirectional-Traffic..................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_46) \
+ STACK:risk_47#df20df:"HTTP-Obsolete-Server....................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_47) \
+ STACK:risk_48#df68df:"Periodic-Flow............................................" \
+ $(rrdtool_graph_print_cur_min_max_avg risk_48) \
+ STACK:risk_49#dfffdf:"Minor-Issues............................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_49) \
+ STACK:risk_50#ef20df:"TCP-Connection-Issues...................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_50) \
+ STACK:risk_51#ef60df:"Fully-Encrypted.........................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_51) \
+ STACK:risk_52#efa0df:"Invalid-ALPN/SNI-combination............................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_52) \
+ STACK:risk_53#efffdf:"Malware-Host-Contacted..................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_53) \
+ STACK:risk_unknown#df2060:"Unknown.................................................." \
+ $(rrdtool_graph_print_cur_min_max_avg risk_unknown)
diff --git a/examples/c-collectd/www/dpi/bootstrap.css b/examples/c-collectd/www/dpi/bootstrap.css
new file mode 100644
index 000000000..6561b6f4c
--- /dev/null
+++ b/examples/c-collectd/www/dpi/bootstrap.css
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v4.0.0 (https://getbootstrap.com)
+ * Copyright 2011-2018 The Bootstrap Authors
+ * Copyright 2011-2018 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014 \00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;min-height:1px;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-sm-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-sm-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-sm-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-sm-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-sm-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-sm-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-sm-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-sm-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-sm-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-sm-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-sm-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-sm-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-sm-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-sm-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-sm-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-md-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-md-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-md-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-md-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-md-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-md-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-md-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-md-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-md-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-md-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-md-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-md-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-md-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-md-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-md-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-lg-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-lg-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-lg-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-lg-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-lg-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-lg-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-lg-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-lg-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-lg-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-lg-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-lg-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-lg-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-lg-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-lg-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-lg-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:none}.col-xl-1{-webkit-box-flex:0;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.order-xl-last{-webkit-box-ordinal-group:14;-ms-flex-order:13;order:13}.order-xl-0{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.order-xl-1{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.order-xl-2{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.order-xl-3{-webkit-box-ordinal-group:4;-ms-flex-order:3;order:3}.order-xl-4{-webkit-box-ordinal-group:5;-ms-flex-order:4;order:4}.order-xl-5{-webkit-box-ordinal-group:6;-ms-flex-order:5;order:5}.order-xl-6{-webkit-box-ordinal-group:7;-ms-flex-order:6;order:6}.order-xl-7{-webkit-box-ordinal-group:8;-ms-flex-order:7;order:7}.order-xl-8{-webkit-box-ordinal-group:9;-ms-flex-order:8;order:8}.order-xl-9{-webkit-box-ordinal-group:10;-ms-flex-order:9;order:9}.order-xl-10{-webkit-box-ordinal-group:11;-ms-flex-order:10;order:10}.order-xl-11{-webkit-box-ordinal-group:12;-ms-flex-order:11;order:11}.order-xl-12{-webkit-box-ordinal-group:13;-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;max-width:100%;margin-bottom:1rem;background-color:transparent}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table .table{background-color:#fff}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#212529;border-color:#32383e}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#212529}.table-dark td,.table-dark th,.table-dark thead th{border-color:#32383e}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:not([size]):not([multiple]){height:calc(2.25rem + 2px)}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm,.input-group-lg>.form-control-plaintext.form-control,.input-group-lg>.input-group-append>.form-control-plaintext.btn,.input-group-lg>.input-group-append>.form-control-plaintext.input-group-text,.input-group-lg>.input-group-prepend>.form-control-plaintext.btn,.input-group-lg>.input-group-prepend>.form-control-plaintext.input-group-text,.input-group-sm>.form-control-plaintext.form-control,.input-group-sm>.input-group-append>.form-control-plaintext.btn,.input-group-sm>.input-group-append>.form-control-plaintext.input-group-text,.input-group-sm>.input-group-prepend>.form-control-plaintext.btn,.input-group-sm>.input-group-prepend>.form-control-plaintext.input-group-text{padding-right:0;padding-left:0}.form-control-sm,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-sm>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-sm>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-sm>select.form-control:not([size]):not([multiple]),select.form-control-sm:not([size]):not([multiple]){height:calc(1.8125rem + 2px)}.form-control-lg,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-lg>.input-group-append>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-append>select.input-group-text:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.btn:not([size]):not([multiple]),.input-group-lg>.input-group-prepend>select.input-group-text:not([size]):not([multiple]),.input-group-lg>select.form-control:not([size]):not([multiple]),select.form-control-lg:not([size]):not([multiple]){height:calc(2.875rem + 2px)}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(40,167,69,.8);border-radius:.2rem}.custom-select.is-valid,.form-control.is-valid,.was-validated .custom-select:valid,.was-validated .form-control:valid{border-color:#28a745}.custom-select.is-valid:focus,.form-control.is-valid:focus,.was-validated .custom-select:valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{background-color:#71dd8a}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(40,167,69,.25)}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label::before,.was-validated .custom-file-input:valid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.5rem;margin-top:.1rem;font-size:.875rem;line-height:1;color:#fff;background-color:rgba(220,53,69,.8);border-radius:.2rem}.custom-select.is-invalid,.form-control.is-invalid,.was-validated .custom-select:invalid,.was-validated .form-control:invalid{border-color:#dc3545}.custom-select.is-invalid:focus,.form-control.is-invalid:focus,.was-validated .custom-select:invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{background-color:#efa2a9}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(220,53,69,.25)}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label::before,.was-validated .custom-file-input:invalid~.custom-file-label::before{border-color:inherit}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .input-group{width:auto}.form-inline .form-check{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;text-align:center;white-space:nowrap;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.btn:focus,.btn:hover{text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}.btn:not(:disabled):not(.disabled).active,.btn:not(:disabled):not(.disabled):active{background-image:none}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-primary{color:#007bff;background-color:transparent;background-image:none;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;background-color:transparent;background-image:none;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;background-color:transparent;background-image:none;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;background-color:transparent;background-image:none;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;background-color:transparent;background-image:none;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;background-color:transparent;background-image:none;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;background-color:transparent;background-image:none;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;background-color:transparent;background-image:none;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;background-color:transparent}.btn-link:hover{color:#0056b3;text-decoration:underline;background-color:transparent;border-color:transparent}.btn-link.focus,.btn-link:focus{text-decoration:underline;border-color:transparent;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;transition:opacity .15s linear}.fade.show{opacity:1}.collapse{display:none}.collapse.show{display:block}tr.collapse.show{display:table-row}tbody.collapse.show{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}.dropdown,.dropup{position:relative}.dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropup .dropdown-menu{margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;width:0;height:0;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;width:0;height:0;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.btn-group,.btn-group-vertical{position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group,.btn-group-vertical .btn+.btn,.btn-group-vertical .btn+.btn-group,.btn-group-vertical .btn-group+.btn,.btn-group-vertical .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after{margin-left:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.btn-group-vertical .btn,.btn-group-vertical .btn-group{width:100%}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file:focus,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control{margin-left:-1px}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::before{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label,.input-group>.custom-file:not(:first-child) .custom-file-label::before{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-webkit-box;display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:active~.custom-control-label::before{color:#fff;background-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{margin-bottom:0}.custom-control-label::before{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;pointer-events:none;content:"";-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#dee2e6}.custom-control-label::after{position:absolute;top:.25rem;left:0;display:block;width:1rem;height:1rem;content:"";background-repeat:no-repeat;background-position:center center;background-size:50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3E%3Cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::before{background-color:#007bff}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(2.25rem + 2px);padding:.375rem 1.75rem .375rem .75rem;line-height:1.5;color:#495057;vertical-align:middle;background:#fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3E%3Cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") no-repeat right .75rem center;background-size:8px 10px;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:inset 0 1px 2px rgba(0,0,0,.075),0 0 5px rgba(128,189,255,.5)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{opacity:0}.custom-select-sm{height:calc(1.8125rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:75%}.custom-select-lg{height:calc(2.875rem + 2px);padding-top:.375rem;padding-bottom:.375rem;font-size:125%}.custom-file{position:relative;display:inline-block;width:100%;height:calc(2.25rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(2.25rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-control{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:focus~.custom-file-control::before{border-color:#80bdff}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(2.25rem + 2px);padding:.375rem .75rem;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(calc(2.25rem + 2px) - 1px * 2);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:1px solid #ced4da;border-radius:0 .25rem .25rem 0}.nav{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler:not(:disabled):not(.disabled){cursor:pointer}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .dropup .dropdown-menu{top:auto;bottom:100%}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .dropup .dropdown-menu{top:auto;bottom:100%}}.navbar-expand{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .dropdown-menu-right{right:0;left:auto}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .dropup .dropdown-menu{top:auto;bottom:100%}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-webkit-box-flex:1;-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:first-child .card-header,.card-group>.card:first-child .card-img-top{border-top-right-radius:0}.card-group>.card:first-child .card-footer,.card-group>.card:first-child .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:last-child .card-header,.card-group>.card:last-child .card-img-top{border-top-left-radius:0}.card-group>.card:last-child .card-footer,.card-group>.card:last-child .card-img-bottom{border-bottom-left-radius:0}.card-group>.card:only-child{border-radius:.25rem}.card-group>.card:only-child .card-header,.card-group>.card:only-child .card-img-top{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-group>.card:only-child .card-footer,.card-group>.card:only-child .card-img-bottom{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-group>.card:not(:first-child):not(:last-child):not(:only-child){border-radius:0}.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-footer,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-header,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom,.card-group>.card:not(:first-child):not(:last-child):not(:only-child) .card-img-top{border-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem}.card-columns .card{display:inline-block;width:100%}}.breadcrumb{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;padding-left:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-webkit-box;display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-link:not(:disabled):not(.disabled){cursor:pointer}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}.badge-primary[href]:focus,.badge-primary[href]:hover{color:#fff;text-decoration:none;background-color:#0062cc}.badge-secondary{color:#fff;background-color:#6c757d}.badge-secondary[href]:focus,.badge-secondary[href]:hover{color:#fff;text-decoration:none;background-color:#545b62}.badge-success{color:#fff;background-color:#28a745}.badge-success[href]:focus,.badge-success[href]:hover{color:#fff;text-decoration:none;background-color:#1e7e34}.badge-info{color:#fff;background-color:#17a2b8}.badge-info[href]:focus,.badge-info[href]:hover{color:#fff;text-decoration:none;background-color:#117a8b}.badge-warning{color:#212529;background-color:#ffc107}.badge-warning[href]:focus,.badge-warning[href]:hover{color:#212529;text-decoration:none;background-color:#d39e00}.badge-danger{color:#fff;background-color:#dc3545}.badge-danger[href]:focus,.badge-danger[href]:hover{color:#fff;text-decoration:none;background-color:#bd2130}.badge-light{color:#212529;background-color:#f8f9fa}.badge-light[href]:focus,.badge-light[href]:hover{color:#212529;text-decoration:none;background-color:#dae0e5}.badge-dark{color:#fff;background-color:#343a40}.badge-dark[href]:focus,.badge-dark[href]:hover{color:#fff;text-decoration:none;background-color:#1d2124}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-webkit-box;display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;background-color:#007bff;transition:width .6s ease}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}.media{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.media-body{-webkit-box-flex:1;-ms-flex:1;flex:1}.list-group{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item:focus,.list-group-item:hover{z-index:1;text-decoration:none}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:focus,.close:hover{color:#000;text-decoration:none;opacity:.75}.close:not(:disabled):not(.disabled){cursor:pointer}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;outline:0}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.show .modal-dialog{-webkit-transform:translate(0,0);transform:translate(0,0)}.modal-dialog-centered{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:calc(100% - (.5rem * 2))}.modal-content{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #e9ecef}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-centered{min-height:calc(100% - (1.75rem * 2))}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg{max-width:800px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top] .arrow,.bs-popover-top .arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::after,.bs-popover-top .arrow::before{border-width:.5rem .5rem 0}.bs-popover-auto[x-placement^=top] .arrow::before,.bs-popover-top .arrow::before{bottom:0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top] .arrow::after,.bs-popover-top .arrow::after{bottom:1px;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right] .arrow,.bs-popover-right .arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::after,.bs-popover-right .arrow::before{border-width:.5rem .5rem .5rem 0}.bs-popover-auto[x-placement^=right] .arrow::before,.bs-popover-right .arrow::before{left:0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right] .arrow::after,.bs-popover-right .arrow::after{left:1px;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom] .arrow,.bs-popover-bottom .arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::after,.bs-popover-bottom .arrow::before{border-width:0 .5rem .5rem .5rem}.bs-popover-auto[x-placement^=bottom] .arrow::before,.bs-popover-bottom .arrow::before{top:0;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom] .arrow::after,.bs-popover-bottom .arrow::after{top:1px;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left] .arrow,.bs-popover-left .arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::after,.bs-popover-left .arrow::before{border-width:.5rem 0 .5rem .5rem}.bs-popover-auto[x-placement^=left] .arrow::before,.bs-popover-left .arrow::before{right:0;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left] .arrow::after,.bs-popover-left .arrow::after{right:1px;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;color:inherit;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-item{position:relative;display:none;-webkit-box-align:center;-ms-flex-align:center;align-items:center;width:100%;transition:-webkit-transform .6s ease;transition:transform .6s ease;transition:transform .6s ease,-webkit-transform .6s ease;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.carousel-item-next,.carousel-item-prev{position:absolute;top:0}.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translateX(0);transform:translateX(0)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.carousel-item-next.carousel-item-left,.carousel-item-prev.carousel-item-right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.active.carousel-item-right,.carousel-item-next{-webkit-transform:translateX(100%);transform:translateX(100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-right,.carousel-item-next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translateX(-100%);transform:translateX(-100%)}@supports ((-webkit-transform-style:preserve-3d) or (transform-style:preserve-3d)){.active.carousel-item-left,.carousel-item-prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:transparent no-repeat center center;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{position:absolute;right:0;bottom:10px;left:0;z-index:15;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{position:relative;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;background-color:rgba(255,255,255,.5)}.carousel-indicators li::before{position:absolute;top:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators li::after{position:absolute;bottom:-10px;left:0;display:inline-block;width:100%;height:10px;content:""}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-circle{border-radius:50%!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-webkit-inline-box!important;display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-sm-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-md-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-lg-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-webkit-box-orient:horizontal!important;-webkit-box-direction:normal!important;-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-webkit-box-orient:vertical!important;-webkit-box-direction:normal!important;-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-webkit-box-orient:horizontal!important;-webkit-box-direction:reverse!important;-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-webkit-box-orient:vertical!important;-webkit-box-direction:reverse!important;-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.justify-content-xl-start{-webkit-box-pack:start!important;-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-webkit-box-pack:end!important;-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-webkit-box-pack:center!important;-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-webkit-box-pack:justify!important;-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-webkit-box-align:start!important;-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-webkit-box-align:end!important;-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-webkit-box-align:center!important;-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-webkit-box-align:baseline!important;-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-webkit-box-align:stretch!important;-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;-webkit-clip-path:inset(50%);clip-path:inset(50%);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal;-webkit-clip-path:none;clip-path:none}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-justify{text-align:justify!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0062cc!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#545b62!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#1e7e34!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#117a8b!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#d39e00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#bd2130!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#dae0e5!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#1d2124!important}.text-muted{color:#6c757d!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}
+/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file
diff --git a/examples/c-collectd/www/dpi/bootstrap.js b/examples/c-collectd/www/dpi/bootstrap.js
new file mode 100644
index 000000000..534d53343
--- /dev/null
+++ b/examples/c-collectd/www/dpi/bootstrap.js
@@ -0,0 +1,7 @@
+/*!
+ * Bootstrap v4.0.0 (https://getbootstrap.com)
+ * Copyright 2011-2018 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ */
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e(t.bootstrap={},t.jQuery,t.Popper)}(this,function(t,e,n){"use strict";function i(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,i.key,i)}}function s(t,e,n){return e&&i(t.prototype,e),n&&i(t,n),t}function r(){return(r=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t}).apply(this,arguments)}e=e&&e.hasOwnProperty("default")?e.default:e,n=n&&n.hasOwnProperty("default")?n.default:n;var o,a,l,h,c,u,f,d,_,g,p,m,v,E,T,y,C,I,A,b,D,S,w,N,O,k,P=function(t){var e=!1;function n(e){var n=this,s=!1;return t(this).one(i.TRANSITION_END,function(){s=!0}),setTimeout(function(){s||i.triggerTransitionEnd(n)},e),this}var i={TRANSITION_END:"bsTransitionEnd",getUID:function(t){do{t+=~~(1e6*Math.random())}while(document.getElementById(t));return t},getSelectorFromElement:function(e){var n,i=e.getAttribute("data-target");i&&"#"!==i||(i=e.getAttribute("href")||""),"#"===i.charAt(0)&&(n=i,i=n="function"==typeof t.escapeSelector?t.escapeSelector(n).substr(1):n.replace(/(:|\.|\[|\]|,|=|@)/g,"\\$1"));try{return t(document).find(i).length>0?i:null}catch(t){return null}},reflow:function(t){return t.offsetHeight},triggerTransitionEnd:function(n){t(n).trigger(e.end)},supportsTransitionEnd:function(){return Boolean(e)},isElement:function(t){return(t[0]||t).nodeType},typeCheckConfig:function(t,e,n){for(var s in n)if(Object.prototype.hasOwnProperty.call(n,s)){var r=n[s],o=e[s],a=o&&i.isElement(o)?"element":(l=o,{}.toString.call(l).match(/\s([a-zA-Z]+)/)[1].toLowerCase());if(!new RegExp(r).test(a))throw new Error(t.toUpperCase()+': Option "'+s+'" provided type "'+a+'" but expected type "'+r+'".')}var l}};return e=("undefined"==typeof window||!window.QUnit)&&{end:"transitionend"},t.fn.emulateTransitionEnd=n,i.supportsTransitionEnd()&&(t.event.special[i.TRANSITION_END]={bindType:e.end,delegateType:e.end,handle:function(e){if(t(e.target).is(this))return e.handleObj.handler.apply(this,arguments)}}),i}(e),L=(a="alert",h="."+(l="bs.alert"),c=(o=e).fn[a],u={CLOSE:"close"+h,CLOSED:"closed"+h,CLICK_DATA_API:"click"+h+".data-api"},f="alert",d="fade",_="show",g=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){t=t||this._element;var e=this._getRootElement(t);this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){o.removeData(this._element,l),this._element=null},e._getRootElement=function(t){var e=P.getSelectorFromElement(t),n=!1;return e&&(n=o(e)[0]),n||(n=o(t).closest("."+f)[0]),n},e._triggerCloseEvent=function(t){var e=o.Event(u.CLOSE);return o(t).trigger(e),e},e._removeElement=function(t){var e=this;o(t).removeClass(_),P.supportsTransitionEnd()&&o(t).hasClass(d)?o(t).one(P.TRANSITION_END,function(n){return e._destroyElement(t,n)}).emulateTransitionEnd(150):this._destroyElement(t)},e._destroyElement=function(t){o(t).detach().trigger(u.CLOSED).remove()},t._jQueryInterface=function(e){return this.each(function(){var n=o(this),i=n.data(l);i||(i=new t(this),n.data(l,i)),"close"===e&&i[e](this)})},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),o(document).on(u.CLICK_DATA_API,'[data-dismiss="alert"]',g._handleDismiss(new g)),o.fn[a]=g._jQueryInterface,o.fn[a].Constructor=g,o.fn[a].noConflict=function(){return o.fn[a]=c,g._jQueryInterface},g),R=(m="button",E="."+(v="bs.button"),T=".data-api",y=(p=e).fn[m],C="active",I="btn",A="focus",b='[data-toggle^="button"]',D='[data-toggle="buttons"]',S="input",w=".active",N=".btn",O={CLICK_DATA_API:"click"+E+T,FOCUS_BLUR_DATA_API:"focus"+E+T+" blur"+E+T},k=function(){function t(t){this._element=t}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,n=p(this._element).closest(D)[0];if(n){var i=p(this._element).find(S)[0];if(i){if("radio"===i.type)if(i.checked&&p(this._element).hasClass(C))t=!1;else{var s=p(n).find(w)[0];s&&p(s).removeClass(C)}if(t){if(i.hasAttribute("disabled")||n.hasAttribute("disabled")||i.classList.contains("disabled")||n.classList.contains("disabled"))return;i.checked=!p(this._element).hasClass(C),p(i).trigger("change")}i.focus(),e=!1}}e&&this._element.setAttribute("aria-pressed",!p(this._element).hasClass(C)),t&&p(this._element).toggleClass(C)},e.dispose=function(){p.removeData(this._element,v),this._element=null},t._jQueryInterface=function(e){return this.each(function(){var n=p(this).data(v);n||(n=new t(this),p(this).data(v,n)),"toggle"===e&&n[e]()})},s(t,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),t}(),p(document).on(O.CLICK_DATA_API,b,function(t){t.preventDefault();var e=t.target;p(e).hasClass(I)||(e=p(e).closest(N)),k._jQueryInterface.call(p(e),"toggle")}).on(O.FOCUS_BLUR_DATA_API,b,function(t){var e=p(t.target).closest(N)[0];p(e).toggleClass(A,/^focus(in)?$/.test(t.type))}),p.fn[m]=k._jQueryInterface,p.fn[m].Constructor=k,p.fn[m].noConflict=function(){return p.fn[m]=y,k._jQueryInterface},k),j=function(t){var e="carousel",n="bs.carousel",i="."+n,o=t.fn[e],a={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0},l={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean"},h="next",c="prev",u="left",f="right",d={SLIDE:"slide"+i,SLID:"slid"+i,KEYDOWN:"keydown"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i,TOUCHEND:"touchend"+i,LOAD_DATA_API:"load"+i+".data-api",CLICK_DATA_API:"click"+i+".data-api"},_="carousel",g="active",p="slide",m="carousel-item-right",v="carousel-item-left",E="carousel-item-next",T="carousel-item-prev",y={ACTIVE:".active",ACTIVE_ITEM:".active.carousel-item",ITEM:".carousel-item",NEXT_PREV:".carousel-item-next, .carousel-item-prev",INDICATORS:".carousel-indicators",DATA_SLIDE:"[data-slide], [data-slide-to]",DATA_RIDE:'[data-ride="carousel"]'},C=function(){function o(e,n){this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this._config=this._getConfig(n),this._element=t(e)[0],this._indicatorsElement=t(this._element).find(y.INDICATORS)[0],this._addEventListeners()}var C=o.prototype;return C.next=function(){this._isSliding||this._slide(h)},C.nextWhenVisible=function(){!document.hidden&&t(this._element).is(":visible")&&"hidden"!==t(this._element).css("visibility")&&this.next()},C.prev=function(){this._isSliding||this._slide(c)},C.pause=function(e){e||(this._isPaused=!0),t(this._element).find(y.NEXT_PREV)[0]&&P.supportsTransitionEnd()&&(P.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},C.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},C.to=function(e){var n=this;this._activeElement=t(this._element).find(y.ACTIVE_ITEM)[0];var i=this._getItemIndex(this._activeElement);if(!(e>this._items.length-1||e<0))if(this._isSliding)t(this._element).one(d.SLID,function(){return n.to(e)});else{if(i===e)return this.pause(),void this.cycle();var s=e>i?h:c;this._slide(s,this._items[e])}},C.dispose=function(){t(this._element).off(i),t.removeData(this._element,n),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},C._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},C._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(d.KEYDOWN,function(t){return e._keydown(t)}),"hover"===this._config.pause&&(t(this._element).on(d.MOUSEENTER,function(t){return e.pause(t)}).on(d.MOUSELEAVE,function(t){return e.cycle(t)}),"ontouchstart"in document.documentElement&&t(this._element).on(d.TOUCHEND,function(){e.pause(),e.touchTimeout&&clearTimeout(e.touchTimeout),e.touchTimeout=setTimeout(function(t){return e.cycle(t)},500+e._config.interval)}))},C._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case 37:t.preventDefault(),this.prev();break;case 39:t.preventDefault(),this.next()}},C._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(y.ITEM)),this._items.indexOf(e)},C._getItemByDirection=function(t,e){var n=t===h,i=t===c,s=this._getItemIndex(e),r=this._items.length-1;if((i&&0===s||n&&s===r)&&!this._config.wrap)return e;var o=(s+(t===c?-1:1))%this._items.length;return-1===o?this._items[this._items.length-1]:this._items[o]},C._triggerSlideEvent=function(e,n){var i=this._getItemIndex(e),s=this._getItemIndex(t(this._element).find(y.ACTIVE_ITEM)[0]),r=t.Event(d.SLIDE,{relatedTarget:e,direction:n,from:s,to:i});return t(this._element).trigger(r),r},C._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(y.ACTIVE).removeClass(g);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(g)}},C._slide=function(e,n){var i,s,r,o=this,a=t(this._element).find(y.ACTIVE_ITEM)[0],l=this._getItemIndex(a),c=n||a&&this._getItemByDirection(e,a),_=this._getItemIndex(c),C=Boolean(this._interval);if(e===h?(i=v,s=E,r=u):(i=m,s=T,r=f),c&&t(c).hasClass(g))this._isSliding=!1;else if(!this._triggerSlideEvent(c,r).isDefaultPrevented()&&a&&c){this._isSliding=!0,C&&this.pause(),this._setActiveIndicatorElement(c);var I=t.Event(d.SLID,{relatedTarget:c,direction:r,from:l,to:_});P.supportsTransitionEnd()&&t(this._element).hasClass(p)?(t(c).addClass(s),P.reflow(c),t(a).addClass(i),t(c).addClass(i),t(a).one(P.TRANSITION_END,function(){t(c).removeClass(i+" "+s).addClass(g),t(a).removeClass(g+" "+s+" "+i),o._isSliding=!1,setTimeout(function(){return t(o._element).trigger(I)},0)}).emulateTransitionEnd(600)):(t(a).removeClass(g),t(c).addClass(g),this._isSliding=!1,t(this._element).trigger(I)),C&&this.cycle()}},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s=r({},a,t(this).data());"object"==typeof e&&(s=r({},s,e));var l="string"==typeof e?e:s.slide;if(i||(i=new o(this,s),t(this).data(n,i)),"number"==typeof e)i.to(e);else if("string"==typeof l){if("undefined"==typeof i[l])throw new TypeError('No method named "'+l+'"');i[l]()}else s.interval&&(i.pause(),i.cycle())})},o._dataApiClickHandler=function(e){var i=P.getSelectorFromElement(this);if(i){var s=t(i)[0];if(s&&t(s).hasClass(_)){var a=r({},t(s).data(),t(this).data()),l=this.getAttribute("data-slide-to");l&&(a.interval=!1),o._jQueryInterface.call(t(s),a),l&&t(s).data(n).to(l),e.preventDefault()}}},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(d.CLICK_DATA_API,y.DATA_SLIDE,C._dataApiClickHandler),t(window).on(d.LOAD_DATA_API,function(){t(y.DATA_RIDE).each(function(){var e=t(this);C._jQueryInterface.call(e,e.data())})}),t.fn[e]=C._jQueryInterface,t.fn[e].Constructor=C,t.fn[e].noConflict=function(){return t.fn[e]=o,C._jQueryInterface},C}(e),H=function(t){var e="collapse",n="bs.collapse",i="."+n,o=t.fn[e],a={toggle:!0,parent:""},l={toggle:"boolean",parent:"(string|element)"},h={SHOW:"show"+i,SHOWN:"shown"+i,HIDE:"hide"+i,HIDDEN:"hidden"+i,CLICK_DATA_API:"click"+i+".data-api"},c="show",u="collapse",f="collapsing",d="collapsed",_="width",g="height",p={ACTIVES:".show, .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},m=function(){function i(e,n){this._isTransitioning=!1,this._element=e,this._config=this._getConfig(n),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'));for(var i=t(p.DATA_TOGGLE),s=0;s<i.length;s++){var r=i[s],o=P.getSelectorFromElement(r);null!==o&&t(o).filter(e).length>0&&(this._selector=o,this._triggerArray.push(r))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var o=i.prototype;return o.toggle=function(){t(this._element).hasClass(c)?this.hide():this.show()},o.show=function(){var e,s,r=this;if(!this._isTransitioning&&!t(this._element).hasClass(c)&&(this._parent&&0===(e=t.makeArray(t(this._parent).find(p.ACTIVES).filter('[data-parent="'+this._config.parent+'"]'))).length&&(e=null),!(e&&(s=t(e).not(this._selector).data(n))&&s._isTransitioning))){var o=t.Event(h.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){e&&(i._jQueryInterface.call(t(e).not(this._selector),"hide"),s||t(e).data(n,null));var a=this._getDimension();t(this._element).removeClass(u).addClass(f),this._element.style[a]=0,this._triggerArray.length>0&&t(this._triggerArray).removeClass(d).attr("aria-expanded",!0),this.setTransitioning(!0);var l=function(){t(r._element).removeClass(f).addClass(u).addClass(c),r._element.style[a]="",r.setTransitioning(!1),t(r._element).trigger(h.SHOWN)};if(P.supportsTransitionEnd()){var _="scroll"+(a[0].toUpperCase()+a.slice(1));t(this._element).one(P.TRANSITION_END,l).emulateTransitionEnd(600),this._element.style[a]=this._element[_]+"px"}else l()}}},o.hide=function(){var e=this;if(!this._isTransitioning&&t(this._element).hasClass(c)){var n=t.Event(h.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension();if(this._element.style[i]=this._element.getBoundingClientRect()[i]+"px",P.reflow(this._element),t(this._element).addClass(f).removeClass(u).removeClass(c),this._triggerArray.length>0)for(var s=0;s<this._triggerArray.length;s++){var r=this._triggerArray[s],o=P.getSelectorFromElement(r);if(null!==o)t(o).hasClass(c)||t(r).addClass(d).attr("aria-expanded",!1)}this.setTransitioning(!0);var a=function(){e.setTransitioning(!1),t(e._element).removeClass(f).addClass(u).trigger(h.HIDDEN)};this._element.style[i]="",P.supportsTransitionEnd()?t(this._element).one(P.TRANSITION_END,a).emulateTransitionEnd(600):a()}}},o.setTransitioning=function(t){this._isTransitioning=t},o.dispose=function(){t.removeData(this._element,n),this._config=null,this._parent=null,this._element=null,this._triggerArray=null,this._isTransitioning=null},o._getConfig=function(t){return(t=r({},a,t)).toggle=Boolean(t.toggle),P.typeCheckConfig(e,t,l),t},o._getDimension=function(){return t(this._element).hasClass(_)?_:g},o._getParent=function(){var e=this,n=null;P.isElement(this._config.parent)?(n=this._config.parent,"undefined"!=typeof this._config.parent.jquery&&(n=this._config.parent[0])):n=t(this._config.parent)[0];var s='[data-toggle="collapse"][data-parent="'+this._config.parent+'"]';return t(n).find(s).each(function(t,n){e._addAriaAndCollapsedClass(i._getTargetFromElement(n),[n])}),n},o._addAriaAndCollapsedClass=function(e,n){if(e){var i=t(e).hasClass(c);n.length>0&&t(n).toggleClass(d,!i).attr("aria-expanded",i)}},i._getTargetFromElement=function(e){var n=P.getSelectorFromElement(e);return n?t(n)[0]:null},i._jQueryInterface=function(e){return this.each(function(){var s=t(this),o=s.data(n),l=r({},a,s.data(),"object"==typeof e&&e);if(!o&&l.toggle&&/show|hide/.test(e)&&(l.toggle=!1),o||(o=new i(this,l),s.data(n,o)),"string"==typeof e){if("undefined"==typeof o[e])throw new TypeError('No method named "'+e+'"');o[e]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),i}();return t(document).on(h.CLICK_DATA_API,p.DATA_TOGGLE,function(e){"A"===e.currentTarget.tagName&&e.preventDefault();var i=t(this),s=P.getSelectorFromElement(this);t(s).each(function(){var e=t(this),s=e.data(n)?"toggle":i.data();m._jQueryInterface.call(e,s)})}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=o,m._jQueryInterface},m}(e),W=function(t){var e="dropdown",i="bs.dropdown",o="."+i,a=".data-api",l=t.fn[e],h=new RegExp("38|40|27"),c={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,CLICK:"click"+o,CLICK_DATA_API:"click"+o+a,KEYDOWN_DATA_API:"keydown"+o+a,KEYUP_DATA_API:"keyup"+o+a},u="disabled",f="show",d="dropup",_="dropright",g="dropleft",p="dropdown-menu-right",m="dropdown-menu-left",v="position-static",E='[data-toggle="dropdown"]',T=".dropdown form",y=".dropdown-menu",C=".navbar-nav",I=".dropdown-menu .dropdown-item:not(.disabled)",A="top-start",b="top-end",D="bottom-start",S="bottom-end",w="right-start",N="left-start",O={offset:0,flip:!0,boundary:"scrollParent"},k={offset:"(number|string|function)",flip:"boolean",boundary:"(string|element)"},L=function(){function a(t,e){this._element=t,this._popper=null,this._config=this._getConfig(e),this._menu=this._getMenuElement(),this._inNavbar=this._detectNavbar(),this._addEventListeners()}var l=a.prototype;return l.toggle=function(){if(!this._element.disabled&&!t(this._element).hasClass(u)){var e=a._getParentFromElement(this._element),i=t(this._menu).hasClass(f);if(a._clearMenus(),!i){var s={relatedTarget:this._element},r=t.Event(c.SHOW,s);if(t(e).trigger(r),!r.isDefaultPrevented()){if(!this._inNavbar){if("undefined"==typeof n)throw new TypeError("Bootstrap dropdown require Popper.js (https://popper.js.org)");var o=this._element;t(e).hasClass(d)&&(t(this._menu).hasClass(m)||t(this._menu).hasClass(p))&&(o=e),"scrollParent"!==this._config.boundary&&t(e).addClass(v),this._popper=new n(o,this._menu,this._getPopperConfig())}"ontouchstart"in document.documentElement&&0===t(e).closest(C).length&&t("body").children().on("mouseover",null,t.noop),this._element.focus(),this._element.setAttribute("aria-expanded",!0),t(this._menu).toggleClass(f),t(e).toggleClass(f).trigger(t.Event(c.SHOWN,s))}}}},l.dispose=function(){t.removeData(this._element,i),t(this._element).off(o),this._element=null,this._menu=null,null!==this._popper&&(this._popper.destroy(),this._popper=null)},l.update=function(){this._inNavbar=this._detectNavbar(),null!==this._popper&&this._popper.scheduleUpdate()},l._addEventListeners=function(){var e=this;t(this._element).on(c.CLICK,function(t){t.preventDefault(),t.stopPropagation(),e.toggle()})},l._getConfig=function(n){return n=r({},this.constructor.Default,t(this._element).data(),n),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},l._getMenuElement=function(){if(!this._menu){var e=a._getParentFromElement(this._element);this._menu=t(e).find(y)[0]}return this._menu},l._getPlacement=function(){var e=t(this._element).parent(),n=D;return e.hasClass(d)?(n=A,t(this._menu).hasClass(p)&&(n=b)):e.hasClass(_)?n=w:e.hasClass(g)?n=N:t(this._menu).hasClass(p)&&(n=S),n},l._detectNavbar=function(){return t(this._element).closest(".navbar").length>0},l._getPopperConfig=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets)||{}),e}:e.offset=this._config.offset,{placement:this._getPlacement(),modifiers:{offset:e,flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}}},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i);if(n||(n=new a(this,"object"==typeof e?e:null),t(this).data(i,n)),"string"==typeof e){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},a._clearMenus=function(e){if(!e||3!==e.which&&("keyup"!==e.type||9===e.which))for(var n=t.makeArray(t(E)),s=0;s<n.length;s++){var r=a._getParentFromElement(n[s]),o=t(n[s]).data(i),l={relatedTarget:n[s]};if(o){var h=o._menu;if(t(r).hasClass(f)&&!(e&&("click"===e.type&&/input|textarea/i.test(e.target.tagName)||"keyup"===e.type&&9===e.which)&&t.contains(r,e.target))){var u=t.Event(c.HIDE,l);t(r).trigger(u),u.isDefaultPrevented()||("ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),n[s].setAttribute("aria-expanded","false"),t(h).removeClass(f),t(r).removeClass(f).trigger(t.Event(c.HIDDEN,l)))}}}},a._getParentFromElement=function(e){var n,i=P.getSelectorFromElement(e);return i&&(n=t(i)[0]),n||e.parentNode},a._dataApiKeydownHandler=function(e){if((/input|textarea/i.test(e.target.tagName)?!(32===e.which||27!==e.which&&(40!==e.which&&38!==e.which||t(e.target).closest(y).length)):h.test(e.which))&&(e.preventDefault(),e.stopPropagation(),!this.disabled&&!t(this).hasClass(u))){var n=a._getParentFromElement(this),i=t(n).hasClass(f);if((i||27===e.which&&32===e.which)&&(!i||27!==e.which&&32!==e.which)){var s=t(n).find(I).get();if(0!==s.length){var r=s.indexOf(e.target);38===e.which&&r>0&&r--,40===e.which&&r<s.length-1&&r++,r<0&&(r=0),s[r].focus()}}else{if(27===e.which){var o=t(n).find(E)[0];t(o).trigger("focus")}t(this).trigger("click")}}},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return O}},{key:"DefaultType",get:function(){return k}}]),a}();return t(document).on(c.KEYDOWN_DATA_API,E,L._dataApiKeydownHandler).on(c.KEYDOWN_DATA_API,y,L._dataApiKeydownHandler).on(c.CLICK_DATA_API+" "+c.KEYUP_DATA_API,L._clearMenus).on(c.CLICK_DATA_API,E,function(e){e.preventDefault(),e.stopPropagation(),L._jQueryInterface.call(t(this),"toggle")}).on(c.CLICK_DATA_API,T,function(t){t.stopPropagation()}),t.fn[e]=L._jQueryInterface,t.fn[e].Constructor=L,t.fn[e].noConflict=function(){return t.fn[e]=l,L._jQueryInterface},L}(e),M=function(t){var e="modal",n="bs.modal",i="."+n,o=t.fn.modal,a={backdrop:!0,keyboard:!0,focus:!0,show:!0},l={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean",show:"boolean"},h={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,FOCUSIN:"focusin"+i,RESIZE:"resize"+i,CLICK_DISMISS:"click.dismiss"+i,KEYDOWN_DISMISS:"keydown.dismiss"+i,MOUSEUP_DISMISS:"mouseup.dismiss"+i,MOUSEDOWN_DISMISS:"mousedown.dismiss"+i,CLICK_DATA_API:"click"+i+".data-api"},c="modal-scrollbar-measure",u="modal-backdrop",f="modal-open",d="fade",_="show",g={DIALOG:".modal-dialog",DATA_TOGGLE:'[data-toggle="modal"]',DATA_DISMISS:'[data-dismiss="modal"]',FIXED_CONTENT:".fixed-top, .fixed-bottom, .is-fixed, .sticky-top",STICKY_CONTENT:".sticky-top",NAVBAR_TOGGLER:".navbar-toggler"},p=function(){function o(e,n){this._config=this._getConfig(n),this._element=e,this._dialog=t(e).find(g.DIALOG)[0],this._backdrop=null,this._isShown=!1,this._isBodyOverflowing=!1,this._ignoreBackdropClick=!1,this._originalBodyPadding=0,this._scrollbarWidth=0}var p=o.prototype;return p.toggle=function(t){return this._isShown?this.hide():this.show(t)},p.show=function(e){var n=this;if(!this._isTransitioning&&!this._isShown){P.supportsTransitionEnd()&&t(this._element).hasClass(d)&&(this._isTransitioning=!0);var i=t.Event(h.SHOW,{relatedTarget:e});t(this._element).trigger(i),this._isShown||i.isDefaultPrevented()||(this._isShown=!0,this._checkScrollbar(),this._setScrollbar(),this._adjustDialog(),t(document.body).addClass(f),this._setEscapeEvent(),this._setResizeEvent(),t(this._element).on(h.CLICK_DISMISS,g.DATA_DISMISS,function(t){return n.hide(t)}),t(this._dialog).on(h.MOUSEDOWN_DISMISS,function(){t(n._element).one(h.MOUSEUP_DISMISS,function(e){t(e.target).is(n._element)&&(n._ignoreBackdropClick=!0)})}),this._showBackdrop(function(){return n._showElement(e)}))}},p.hide=function(e){var n=this;if(e&&e.preventDefault(),!this._isTransitioning&&this._isShown){var i=t.Event(h.HIDE);if(t(this._element).trigger(i),this._isShown&&!i.isDefaultPrevented()){this._isShown=!1;var s=P.supportsTransitionEnd()&&t(this._element).hasClass(d);s&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),t(document).off(h.FOCUSIN),t(this._element).removeClass(_),t(this._element).off(h.CLICK_DISMISS),t(this._dialog).off(h.MOUSEDOWN_DISMISS),s?t(this._element).one(P.TRANSITION_END,function(t){return n._hideModal(t)}).emulateTransitionEnd(300):this._hideModal()}}},p.dispose=function(){t.removeData(this._element,n),t(window,document,this._element,this._backdrop).off(i),this._config=null,this._element=null,this._dialog=null,this._backdrop=null,this._isShown=null,this._isBodyOverflowing=null,this._ignoreBackdropClick=null,this._scrollbarWidth=null},p.handleUpdate=function(){this._adjustDialog()},p._getConfig=function(t){return t=r({},a,t),P.typeCheckConfig(e,t,l),t},p._showElement=function(e){var n=this,i=P.supportsTransitionEnd()&&t(this._element).hasClass(d);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.scrollTop=0,i&&P.reflow(this._element),t(this._element).addClass(_),this._config.focus&&this._enforceFocus();var s=t.Event(h.SHOWN,{relatedTarget:e}),r=function(){n._config.focus&&n._element.focus(),n._isTransitioning=!1,t(n._element).trigger(s)};i?t(this._dialog).one(P.TRANSITION_END,r).emulateTransitionEnd(300):r()},p._enforceFocus=function(){var e=this;t(document).off(h.FOCUSIN).on(h.FOCUSIN,function(n){document!==n.target&&e._element!==n.target&&0===t(e._element).has(n.target).length&&e._element.focus()})},p._setEscapeEvent=function(){var e=this;this._isShown&&this._config.keyboard?t(this._element).on(h.KEYDOWN_DISMISS,function(t){27===t.which&&(t.preventDefault(),e.hide())}):this._isShown||t(this._element).off(h.KEYDOWN_DISMISS)},p._setResizeEvent=function(){var e=this;this._isShown?t(window).on(h.RESIZE,function(t){return e.handleUpdate(t)}):t(window).off(h.RESIZE)},p._hideModal=function(){var e=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._isTransitioning=!1,this._showBackdrop(function(){t(document.body).removeClass(f),e._resetAdjustments(),e._resetScrollbar(),t(e._element).trigger(h.HIDDEN)})},p._removeBackdrop=function(){this._backdrop&&(t(this._backdrop).remove(),this._backdrop=null)},p._showBackdrop=function(e){var n=this,i=t(this._element).hasClass(d)?d:"";if(this._isShown&&this._config.backdrop){var s=P.supportsTransitionEnd()&&i;if(this._backdrop=document.createElement("div"),this._backdrop.className=u,i&&t(this._backdrop).addClass(i),t(this._backdrop).appendTo(document.body),t(this._element).on(h.CLICK_DISMISS,function(t){n._ignoreBackdropClick?n._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===n._config.backdrop?n._element.focus():n.hide())}),s&&P.reflow(this._backdrop),t(this._backdrop).addClass(_),!e)return;if(!s)return void e();t(this._backdrop).one(P.TRANSITION_END,e).emulateTransitionEnd(150)}else if(!this._isShown&&this._backdrop){t(this._backdrop).removeClass(_);var r=function(){n._removeBackdrop(),e&&e()};P.supportsTransitionEnd()&&t(this._element).hasClass(d)?t(this._backdrop).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r()}else e&&e()},p._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},p._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},p._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right<window.innerWidth,this._scrollbarWidth=this._getScrollbarWidth()},p._setScrollbar=function(){var e=this;if(this._isBodyOverflowing){t(g.FIXED_CONTENT).each(function(n,i){var s=t(i)[0].style.paddingRight,r=t(i).css("padding-right");t(i).data("padding-right",s).css("padding-right",parseFloat(r)+e._scrollbarWidth+"px")}),t(g.STICKY_CONTENT).each(function(n,i){var s=t(i)[0].style.marginRight,r=t(i).css("margin-right");t(i).data("margin-right",s).css("margin-right",parseFloat(r)-e._scrollbarWidth+"px")}),t(g.NAVBAR_TOGGLER).each(function(n,i){var s=t(i)[0].style.marginRight,r=t(i).css("margin-right");t(i).data("margin-right",s).css("margin-right",parseFloat(r)+e._scrollbarWidth+"px")});var n=document.body.style.paddingRight,i=t("body").css("padding-right");t("body").data("padding-right",n).css("padding-right",parseFloat(i)+this._scrollbarWidth+"px")}},p._resetScrollbar=function(){t(g.FIXED_CONTENT).each(function(e,n){var i=t(n).data("padding-right");"undefined"!=typeof i&&t(n).css("padding-right",i).removeData("padding-right")}),t(g.STICKY_CONTENT+", "+g.NAVBAR_TOGGLER).each(function(e,n){var i=t(n).data("margin-right");"undefined"!=typeof i&&t(n).css("margin-right",i).removeData("margin-right")});var e=t("body").data("padding-right");"undefined"!=typeof e&&t("body").css("padding-right",e).removeData("padding-right")},p._getScrollbarWidth=function(){var t=document.createElement("div");t.className=c,document.body.appendChild(t);var e=t.getBoundingClientRect().width-t.clientWidth;return document.body.removeChild(t),e},o._jQueryInterface=function(e,i){return this.each(function(){var s=t(this).data(n),a=r({},o.Default,t(this).data(),"object"==typeof e&&e);if(s||(s=new o(this,a),t(this).data(n,s)),"string"==typeof e){if("undefined"==typeof s[e])throw new TypeError('No method named "'+e+'"');s[e](i)}else a.show&&s.show(i)})},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(document).on(h.CLICK_DATA_API,g.DATA_TOGGLE,function(e){var i,s=this,o=P.getSelectorFromElement(this);o&&(i=t(o)[0]);var a=t(i).data(n)?"toggle":r({},t(i).data(),t(this).data());"A"!==this.tagName&&"AREA"!==this.tagName||e.preventDefault();var l=t(i).one(h.SHOW,function(e){e.isDefaultPrevented()||l.one(h.HIDDEN,function(){t(s).is(":visible")&&s.focus()})});p._jQueryInterface.call(t(i),a,this)}),t.fn.modal=p._jQueryInterface,t.fn.modal.Constructor=p,t.fn.modal.noConflict=function(){return t.fn.modal=o,p._jQueryInterface},p}(e),U=function(t){var e="tooltip",i="bs.tooltip",o="."+i,a=t.fn[e],l=new RegExp("(^|\\s)bs-tooltip\\S+","g"),h={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)"},c={AUTO:"auto",TOP:"top",RIGHT:"right",BOTTOM:"bottom",LEFT:"left"},u={animation:!0,template:'<div class="tooltip" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent"},f="show",d="out",_={HIDE:"hide"+o,HIDDEN:"hidden"+o,SHOW:"show"+o,SHOWN:"shown"+o,INSERTED:"inserted"+o,CLICK:"click"+o,FOCUSIN:"focusin"+o,FOCUSOUT:"focusout"+o,MOUSEENTER:"mouseenter"+o,MOUSELEAVE:"mouseleave"+o},g="fade",p="show",m=".tooltip-inner",v=".arrow",E="hover",T="focus",y="click",C="manual",I=function(){function a(t,e){if("undefined"==typeof n)throw new TypeError("Bootstrap tooltips require Popper.js (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var I=a.prototype;return I.enable=function(){this._isEnabled=!0},I.disable=function(){this._isEnabled=!1},I.toggleEnabled=function(){this._isEnabled=!this._isEnabled},I.toggle=function(e){if(this._isEnabled)if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(p))return void this._leave(null,this);this._enter(null,this)}},I.dispose=function(){clearTimeout(this._timeout),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,null!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},I.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var i=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){t(this.element).trigger(i);var s=t.contains(this.element.ownerDocument.documentElement,this.element);if(i.isDefaultPrevented()||!s)return;var r=this.getTipElement(),o=P.getUID(this.constructor.NAME);r.setAttribute("id",o),this.element.setAttribute("aria-describedby",o),this.setContent(),this.config.animation&&t(r).addClass(g);var l="function"==typeof this.config.placement?this.config.placement.call(this,r,this.element):this.config.placement,h=this._getAttachment(l);this.addAttachmentClass(h);var c=!1===this.config.container?document.body:t(this.config.container);t(r).data(this.constructor.DATA_KEY,this),t.contains(this.element.ownerDocument.documentElement,this.tip)||t(r).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new n(this.element,r,{placement:h,modifiers:{offset:{offset:this.config.offset},flip:{behavior:this.config.fallbackPlacement},arrow:{element:v},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){e._handlePopperPlacementChange(t)}}),t(r).addClass(p),"ontouchstart"in document.documentElement&&t("body").children().on("mouseover",null,t.noop);var u=function(){e.config.animation&&e._fixTransition();var n=e._hoverState;e._hoverState=null,t(e.element).trigger(e.constructor.Event.SHOWN),n===d&&e._leave(null,e)};P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(this.tip).one(P.TRANSITION_END,u).emulateTransitionEnd(a._TRANSITION_DURATION):u()}},I.hide=function(e){var n=this,i=this.getTipElement(),s=t.Event(this.constructor.Event.HIDE),r=function(){n._hoverState!==f&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),e&&e()};t(this.element).trigger(s),s.isDefaultPrevented()||(t(i).removeClass(p),"ontouchstart"in document.documentElement&&t("body").children().off("mouseover",null,t.noop),this._activeTrigger[y]=!1,this._activeTrigger[T]=!1,this._activeTrigger[E]=!1,P.supportsTransitionEnd()&&t(this.tip).hasClass(g)?t(i).one(P.TRANSITION_END,r).emulateTransitionEnd(150):r(),this._hoverState="")},I.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},I.isWithContent=function(){return Boolean(this.getTitle())},I.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-tooltip-"+e)},I.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},I.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(m),this.getTitle()),e.removeClass(g+" "+p)},I.setElementContent=function(e,n){var i=this.config.html;"object"==typeof n&&(n.nodeType||n.jquery)?i?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[i?"html":"text"](n)},I.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},I._getAttachment=function(t){return c[t.toUpperCase()]},I._setListeners=function(){var e=this;this.config.trigger.split(" ").forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==C){var i=n===E?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,s=n===E?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(s,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},I._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},I._enter=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T:E]=!0),t(n.getTipElement()).hasClass(p)||n._hoverState===f?n._hoverState=f:(clearTimeout(n._timeout),n._hoverState=f,n.config.delay&&n.config.delay.show?n._timeout=setTimeout(function(){n._hoverState===f&&n.show()},n.config.delay.show):n.show())},I._leave=function(e,n){var i=this.constructor.DATA_KEY;(n=n||t(e.currentTarget).data(i))||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T:E]=!1),n._isWithActiveTrigger()||(clearTimeout(n._timeout),n._hoverState=d,n.config.delay&&n.config.delay.hide?n._timeout=setTimeout(function(){n._hoverState===d&&n.hide()},n.config.delay.hide):n.hide())},I._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},I._getConfig=function(n){return"number"==typeof(n=r({},this.constructor.Default,t(this.element).data(),n)).delay&&(n.delay={show:n.delay,hide:n.delay}),"number"==typeof n.title&&(n.title=n.title.toString()),"number"==typeof n.content&&(n.content=n.content.toString()),P.typeCheckConfig(e,n,this.constructor.DefaultType),n},I._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},I._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(l);null!==n&&n.length>0&&e.removeClass(n.join(""))},I._handlePopperPlacementChange=function(t){this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},I._fixTransition=function(){var e=this.getTipElement(),n=this.config.animation;null===e.getAttribute("x-placement")&&(t(e).removeClass(g),this.config.animation=!1,this.hide(),this.show(),this.config.animation=n)},a._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(i),s="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new a(this,s),t(this).data(i,n)),"string"==typeof e)){if("undefined"==typeof n[e])throw new TypeError('No method named "'+e+'"');n[e]()}})},s(a,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return u}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return i}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return o}},{key:"DefaultType",get:function(){return h}}]),a}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=a,I._jQueryInterface},I}(e),x=function(t){var e="popover",n="bs.popover",i="."+n,o=t.fn[e],a=new RegExp("(^|\\s)bs-popover\\S+","g"),l=r({},U.Default,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-header"></h3><div class="popover-body"></div></div>'}),h=r({},U.DefaultType,{content:"(string|element|function)"}),c="fade",u="show",f=".popover-header",d=".popover-body",_={HIDE:"hide"+i,HIDDEN:"hidden"+i,SHOW:"show"+i,SHOWN:"shown"+i,INSERTED:"inserted"+i,CLICK:"click"+i,FOCUSIN:"focusin"+i,FOCUSOUT:"focusout"+i,MOUSEENTER:"mouseenter"+i,MOUSELEAVE:"mouseleave"+i},g=function(r){var o,g;function p(){return r.apply(this,arguments)||this}g=r,(o=p).prototype=Object.create(g.prototype),o.prototype.constructor=o,o.__proto__=g;var m=p.prototype;return m.isWithContent=function(){return this.getTitle()||this._getContent()},m.addAttachmentClass=function(e){t(this.getTipElement()).addClass("bs-popover-"+e)},m.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0],this.tip},m.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(f),this.getTitle());var n=this._getContent();"function"==typeof n&&(n=n.call(this.element)),this.setElementContent(e.find(d),n),e.removeClass(c+" "+u)},m._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},m._cleanTipClass=function(){var e=t(this.getTipElement()),n=e.attr("class").match(a);null!==n&&n.length>0&&e.removeClass(n.join(""))},p._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n),s="object"==typeof e?e:null;if((i||!/destroy|hide/.test(e))&&(i||(i=new p(this,s),t(this).data(n,i)),"string"==typeof e)){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(p,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return l}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return n}},{key:"Event",get:function(){return _}},{key:"EVENT_KEY",get:function(){return i}},{key:"DefaultType",get:function(){return h}}]),p}(U);return t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),K=function(t){var e="scrollspy",n="bs.scrollspy",i="."+n,o=t.fn[e],a={offset:10,method:"auto",target:""},l={offset:"number",method:"string",target:"(string|element)"},h={ACTIVATE:"activate"+i,SCROLL:"scroll"+i,LOAD_DATA_API:"load"+i+".data-api"},c="dropdown-item",u="active",f={DATA_SPY:'[data-spy="scroll"]',ACTIVE:".active",NAV_LIST_GROUP:".nav, .list-group",NAV_LINKS:".nav-link",NAV_ITEMS:".nav-item",LIST_ITEMS:".list-group-item",DROPDOWN:".dropdown",DROPDOWN_ITEMS:".dropdown-item",DROPDOWN_TOGGLE:".dropdown-toggle"},d="offset",_="position",g=function(){function o(e,n){var i=this;this._element=e,this._scrollElement="BODY"===e.tagName?window:e,this._config=this._getConfig(n),this._selector=this._config.target+" "+f.NAV_LINKS+","+this._config.target+" "+f.LIST_ITEMS+","+this._config.target+" "+f.DROPDOWN_ITEMS,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,t(this._scrollElement).on(h.SCROLL,function(t){return i._process(t)}),this.refresh(),this._process()}var g=o.prototype;return g.refresh=function(){var e=this,n=this._scrollElement===this._scrollElement.window?d:_,i="auto"===this._config.method?n:this._config.method,s=i===_?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),t.makeArray(t(this._selector)).map(function(e){var n,r=P.getSelectorFromElement(e);if(r&&(n=t(r)[0]),n){var o=n.getBoundingClientRect();if(o.width||o.height)return[t(n)[i]().top+s,r]}return null}).filter(function(t){return t}).sort(function(t,e){return t[0]-e[0]}).forEach(function(t){e._offsets.push(t[0]),e._targets.push(t[1])})},g.dispose=function(){t.removeData(this._element,n),t(this._scrollElement).off(i),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},g._getConfig=function(n){if("string"!=typeof(n=r({},a,n)).target){var i=t(n.target).attr("id");i||(i=P.getUID(e),t(n.target).attr("id",i)),n.target="#"+i}return P.typeCheckConfig(e,n,l),n},g._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},g._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},g._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},g._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),n=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=n){var i=this._targets[this._targets.length-1];this._activeTarget!==i&&this._activate(i)}else{if(this._activeTarget&&t<this._offsets[0]&&this._offsets[0]>0)return this._activeTarget=null,void this._clear();for(var s=this._offsets.length;s--;){this._activeTarget!==this._targets[s]&&t>=this._offsets[s]&&("undefined"==typeof this._offsets[s+1]||t<this._offsets[s+1])&&this._activate(this._targets[s])}}},g._activate=function(e){this._activeTarget=e,this._clear();var n=this._selector.split(",");n=n.map(function(t){return t+'[data-target="'+e+'"],'+t+'[href="'+e+'"]'});var i=t(n.join(","));i.hasClass(c)?(i.closest(f.DROPDOWN).find(f.DROPDOWN_TOGGLE).addClass(u),i.addClass(u)):(i.addClass(u),i.parents(f.NAV_LIST_GROUP).prev(f.NAV_LINKS+", "+f.LIST_ITEMS).addClass(u),i.parents(f.NAV_LIST_GROUP).prev(f.NAV_ITEMS).children(f.NAV_LINKS).addClass(u)),t(this._scrollElement).trigger(h.ACTIVATE,{relatedTarget:e})},g._clear=function(){t(this._selector).filter(f.ACTIVE).removeClass(u)},o._jQueryInterface=function(e){return this.each(function(){var i=t(this).data(n);if(i||(i=new o(this,"object"==typeof e&&e),t(this).data(n,i)),"string"==typeof e){if("undefined"==typeof i[e])throw new TypeError('No method named "'+e+'"');i[e]()}})},s(o,null,[{key:"VERSION",get:function(){return"4.0.0"}},{key:"Default",get:function(){return a}}]),o}();return t(window).on(h.LOAD_DATA_API,function(){for(var e=t.makeArray(t(f.DATA_SPY)),n=e.length;n--;){var i=t(e[n]);g._jQueryInterface.call(i,i.data())}}),t.fn[e]=g._jQueryInterface,t.fn[e].Constructor=g,t.fn[e].noConflict=function(){return t.fn[e]=o,g._jQueryInterface},g}(e),V=function(t){var e="bs.tab",n="."+e,i=t.fn.tab,r={HIDE:"hide"+n,HIDDEN:"hidden"+n,SHOW:"show"+n,SHOWN:"shown"+n,CLICK_DATA_API:"click.bs.tab.data-api"},o="dropdown-menu",a="active",l="disabled",h="fade",c="show",u=".dropdown",f=".nav, .list-group",d=".active",_="> li > .active",g='[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]',p=".dropdown-toggle",m="> .dropdown-menu .active",v=function(){function n(t){this._element=t}var i=n.prototype;return i.show=function(){var e=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&t(this._element).hasClass(a)||t(this._element).hasClass(l))){var n,i,s=t(this._element).closest(f)[0],o=P.getSelectorFromElement(this._element);if(s){var h="UL"===s.nodeName?_:d;i=(i=t.makeArray(t(s).find(h)))[i.length-1]}var c=t.Event(r.HIDE,{relatedTarget:this._element}),u=t.Event(r.SHOW,{relatedTarget:i});if(i&&t(i).trigger(c),t(this._element).trigger(u),!u.isDefaultPrevented()&&!c.isDefaultPrevented()){o&&(n=t(o)[0]),this._activate(this._element,s);var g=function(){var n=t.Event(r.HIDDEN,{relatedTarget:e._element}),s=t.Event(r.SHOWN,{relatedTarget:i});t(i).trigger(n),t(e._element).trigger(s)};n?this._activate(n,n.parentNode,g):g()}}},i.dispose=function(){t.removeData(this._element,e),this._element=null},i._activate=function(e,n,i){var s=this,r=("UL"===n.nodeName?t(n).find(_):t(n).children(d))[0],o=i&&P.supportsTransitionEnd()&&r&&t(r).hasClass(h),a=function(){return s._transitionComplete(e,r,i)};r&&o?t(r).one(P.TRANSITION_END,a).emulateTransitionEnd(150):a()},i._transitionComplete=function(e,n,i){if(n){t(n).removeClass(c+" "+a);var s=t(n.parentNode).find(m)[0];s&&t(s).removeClass(a),"tab"===n.getAttribute("role")&&n.setAttribute("aria-selected",!1)}if(t(e).addClass(a),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!0),P.reflow(e),t(e).addClass(c),e.parentNode&&t(e.parentNode).hasClass(o)){var r=t(e).closest(u)[0];r&&t(r).find(p).addClass(a),e.setAttribute("aria-expanded",!0)}i&&i()},n._jQueryInterface=function(i){return this.each(function(){var s=t(this),r=s.data(e);if(r||(r=new n(this),s.data(e,r)),"string"==typeof i){if("undefined"==typeof r[i])throw new TypeError('No method named "'+i+'"');r[i]()}})},s(n,null,[{key:"VERSION",get:function(){return"4.0.0"}}]),n}();return t(document).on(r.CLICK_DATA_API,g,function(e){e.preventDefault(),v._jQueryInterface.call(t(this),"show")}),t.fn.tab=v._jQueryInterface,t.fn.tab.Constructor=v,t.fn.tab.noConflict=function(){return t.fn.tab=i,v._jQueryInterface},v}(e);!function(t){if("undefined"==typeof t)throw new TypeError("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1===e[0]&&9===e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(e),t.Util=P,t.Alert=L,t.Button=R,t.Carousel=j,t.Collapse=H,t.Dropdown=W,t.Modal=M,t.Popover=x,t.Scrollspy=K,t.Tab=V,t.Tooltip=U,Object.defineProperty(t,"__esModule",{value:!0})});
+//# sourceMappingURL=bootstrap.min.js.map \ No newline at end of file
diff --git a/examples/c-collectd/www/dpi/categories.html b/examples/c-collectd/www/dpi/categories.html
new file mode 100644
index 000000000..165b11a04
--- /dev/null
+++ b/examples/c-collectd/www/dpi/categories.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-collectd/www/dpi/dashboard.css b/examples/c-collectd/www/dpi/dashboard.css
new file mode 100644
index 000000000..ef40fe78f
--- /dev/null
+++ b/examples/c-collectd/www/dpi/dashboard.css
@@ -0,0 +1,93 @@
+body {
+ font-size: .875rem;
+}
+
+.feather {
+ width: 16px;
+ height: 16px;
+ vertical-align: text-bottom;
+}
+
+/*
+ * Sidebar
+ */
+
+.sidebar {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 100; /* Behind the navbar */
+ padding: 0;
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
+}
+
+.sidebar-sticky {
+ position: -webkit-sticky;
+ position: sticky;
+ top: 48px; /* Height of navbar */
+ height: calc(100vh - 48px);
+ padding-top: .5rem;
+ overflow-x: hidden;
+ overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
+}
+
+.sidebar .nav-link {
+ font-weight: 500;
+ color: #333;
+}
+
+.sidebar .nav-link .feather {
+ margin-right: 4px;
+ color: #999;
+}
+
+.sidebar .nav-link.active {
+ color: #007bff;
+}
+
+.sidebar .nav-link:hover .feather,
+.sidebar .nav-link.active .feather {
+ color: inherit;
+}
+
+.sidebar-heading {
+ font-size: .75rem;
+ text-transform: uppercase;
+}
+
+/*
+ * Navbar
+ */
+
+.navbar-brand {
+ padding-top: .75rem;
+ padding-bottom: .75rem;
+ font-size: 1rem;
+ background-color: rgba(0, 0, 0, .25);
+ box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
+}
+
+.navbar .form-control {
+ padding: .75rem 1rem;
+ border-width: 0;
+ border-radius: 0;
+}
+
+.form-control-dark {
+ color: #fff;
+ background-color: rgba(255, 255, 255, .1);
+ border-color: rgba(255, 255, 255, .1);
+}
+
+.form-control-dark:focus {
+ border-color: transparent;
+ box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
+}
+
+/*
+ * Utilities
+ */
+
+.border-top { border-top: 1px solid #e5e5e5; }
+.border-bottom { border-bottom: 1px solid #e5e5e5; }
diff --git a/examples/c-collectd/www/dpi/detections.html b/examples/c-collectd/www/dpi/detections.html
new file mode 100644
index 000000000..ed4556b64
--- /dev/null
+++ b/examples/c-collectd/www/dpi/detections.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-collectd/www/dpi/events.html b/examples/c-collectd/www/dpi/events.html
new file mode 100644
index 000000000..f1ef8cbae
--- /dev/null
+++ b/examples/c-collectd/www/dpi/events.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-collectd/www/dpi/feather.js b/examples/c-collectd/www/dpi/feather.js
new file mode 100644
index 000000000..19243bcef
--- /dev/null
+++ b/examples/c-collectd/www/dpi/feather.js
@@ -0,0 +1,13 @@
+!function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.feather=n():e.feather=n()}("undefined"!=typeof self?self:this,function(){return function(e){var n={};function i(t){if(n[t])return n[t].exports;var l=n[t]={i:t,l:!1,exports:{}};return e[t].call(l.exports,l,l.exports,i),l.l=!0,l.exports}return i.m=e,i.c=n,i.d=function(e,n,t){i.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:t})},i.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},i.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(n,"a",n),n},i.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},i.p="",i(i.s=80)}([function(e,n,i){(function(n){var i="object",t=function(e){return e&&e.Math==Math&&e};e.exports=t(typeof globalThis==i&&globalThis)||t(typeof window==i&&window)||t(typeof self==i&&self)||t(typeof n==i&&n)||Function("return this")()}).call(this,i(75))},function(e,n){var i={}.hasOwnProperty;e.exports=function(e,n){return i.call(e,n)}},function(e,n,i){var t=i(0),l=i(11),r=i(33),o=i(62),a=t.Symbol,c=l("wks");e.exports=function(e){return c[e]||(c[e]=o&&a[e]||(o?a:r)("Symbol."+e))}},function(e,n,i){var t=i(6);e.exports=function(e){if(!t(e))throw TypeError(String(e)+" is not an object");return e}},function(e,n){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,n,i){var t=i(8),l=i(7),r=i(10);e.exports=t?function(e,n,i){return l.f(e,n,r(1,i))}:function(e,n,i){return e[n]=i,e}},function(e,n){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,n,i){var t=i(8),l=i(35),r=i(3),o=i(18),a=Object.defineProperty;n.f=t?a:function(e,n,i){if(r(e),n=o(n,!0),r(i),l)try{return a(e,n,i)}catch(e){}if("get"in i||"set"in i)throw TypeError("Accessors not supported");return"value"in i&&(e[n]=i.value),e}},function(e,n,i){var t=i(4);e.exports=!t(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,n){e.exports={}},function(e,n){e.exports=function(e,n){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:n}}},function(e,n,i){var t=i(0),l=i(19),r=i(17),o=t["__core-js_shared__"]||l("__core-js_shared__",{});(e.exports=function(e,n){return o[e]||(o[e]=void 0!==n?n:{})})("versions",[]).push({version:"3.1.3",mode:r?"pure":"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=o(i(43)),l=o(i(41)),r=o(i(40));function o(e){return e&&e.__esModule?e:{default:e}}n.default=Object.keys(l.default).map(function(e){return new t.default(e,l.default[e],r.default[e])}).reduce(function(e,n){return e[n.name]=n,e},{})},function(e,n){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(e,n,i){var t=i(72),l=i(20);e.exports=function(e){return t(l(e))}},function(e,n){e.exports={}},function(e,n,i){var t=i(11),l=i(33),r=t("keys");e.exports=function(e){return r[e]||(r[e]=l(e))}},function(e,n){e.exports=!1},function(e,n,i){var t=i(6);e.exports=function(e,n){if(!t(e))return e;var i,l;if(n&&"function"==typeof(i=e.toString)&&!t(l=i.call(e)))return l;if("function"==typeof(i=e.valueOf)&&!t(l=i.call(e)))return l;if(!n&&"function"==typeof(i=e.toString)&&!t(l=i.call(e)))return l;throw TypeError("Can't convert object to primitive value")}},function(e,n,i){var t=i(0),l=i(5);e.exports=function(e,n){try{l(t,e,n)}catch(i){t[e]=n}return n}},function(e,n){e.exports=function(e){if(void 0==e)throw TypeError("Can't call method on "+e);return e}},function(e,n){var i=Math.ceil,t=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?t:i)(e)}},function(e,n,i){var t;
+/*!
+ Copyright (c) 2016 Jed Watson.
+ Licensed under the MIT License (MIT), see
+ http://jedwatson.github.io/classnames
+*/
+/*!
+ Copyright (c) 2016 Jed Watson.
+ Licensed under the MIT License (MIT), see
+ http://jedwatson.github.io/classnames
+*/
+!function(){"use strict";var i=function(){function e(){}function n(e,n){for(var i=n.length,t=0;t<i;++t)l(e,n[t])}e.prototype=Object.create(null);var i={}.hasOwnProperty;var t=/\s+/;function l(e,l){if(l){var r=typeof l;"string"===r?function(e,n){for(var i=n.split(t),l=i.length,r=0;r<l;++r)e[i[r]]=!0}(e,l):Array.isArray(l)?n(e,l):"object"===r?function(e,n){for(var t in n)i.call(n,t)&&(e[t]=!!n[t])}(e,l):"number"===r&&function(e,n){e[n]=!0}(e,l)}}return function(){for(var i=arguments.length,t=Array(i),l=0;l<i;l++)t[l]=arguments[l];var r=new e;n(r,t);var o=[];for(var a in r)r[a]&&o.push(a);return o.join(" ")}}();void 0!==e&&e.exports?e.exports=i:void 0===(t=function(){return i}.apply(n,[]))||(e.exports=t)}()},function(e,n,i){var t=i(7).f,l=i(1),r=i(2)("toStringTag");e.exports=function(e,n,i){e&&!l(e=i?e:e.prototype,r)&&t(e,r,{configurable:!0,value:n})}},function(e,n,i){var t=i(20);e.exports=function(e){return Object(t(e))}},function(e,n,i){var t=i(1),l=i(24),r=i(16),o=i(63),a=r("IE_PROTO"),c=Object.prototype;e.exports=o?Object.getPrototypeOf:function(e){return e=l(e),t(e,a)?e[a]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?c:null}},function(e,n,i){"use strict";var t,l,r,o=i(25),a=i(5),c=i(1),p=i(2),y=i(17),h=p("iterator"),x=!1;[].keys&&("next"in(r=[].keys())?(l=o(o(r)))!==Object.prototype&&(t=l):x=!0),void 0==t&&(t={}),y||c(t,h)||a(t,h,function(){return this}),e.exports={IteratorPrototype:t,BUGGY_SAFARI_ITERATORS:x}},function(e,n,i){var t=i(21),l=Math.min;e.exports=function(e){return e>0?l(t(e),9007199254740991):0}},function(e,n,i){var t=i(1),l=i(14),r=i(68),o=i(15),a=r(!1);e.exports=function(e,n){var i,r=l(e),c=0,p=[];for(i in r)!t(o,i)&&t(r,i)&&p.push(i);for(;n.length>c;)t(r,i=n[c++])&&(~a(p,i)||p.push(i));return p}},function(e,n,i){var t=i(0),l=i(11),r=i(5),o=i(1),a=i(19),c=i(36),p=i(37),y=p.get,h=p.enforce,x=String(c).split("toString");l("inspectSource",function(e){return c.call(e)}),(e.exports=function(e,n,i,l){var c=!!l&&!!l.unsafe,p=!!l&&!!l.enumerable,y=!!l&&!!l.noTargetGet;"function"==typeof i&&("string"!=typeof n||o(i,"name")||r(i,"name",n),h(i).source=x.join("string"==typeof n?n:"")),e!==t?(c?!y&&e[n]&&(p=!0):delete e[n],p?e[n]=i:r(e,n,i)):p?e[n]=i:a(n,i)})(Function.prototype,"toString",function(){return"function"==typeof this&&y(this).source||c.call(this)})},function(e,n){var i={}.toString;e.exports=function(e){return i.call(e).slice(8,-1)}},function(e,n,i){var t=i(8),l=i(73),r=i(10),o=i(14),a=i(18),c=i(1),p=i(35),y=Object.getOwnPropertyDescriptor;n.f=t?y:function(e,n){if(e=o(e),n=a(n,!0),p)try{return y(e,n)}catch(e){}if(c(e,n))return r(!l.f.call(e,n),e[n])}},function(e,n,i){var t=i(0),l=i(31).f,r=i(5),o=i(29),a=i(19),c=i(71),p=i(65);e.exports=function(e,n){var i,y,h,x,s,u=e.target,d=e.global,f=e.stat;if(i=d?t:f?t[u]||a(u,{}):(t[u]||{}).prototype)for(y in n){if(x=n[y],h=e.noTargetGet?(s=l(i,y))&&s.value:i[y],!p(d?y:u+(f?".":"#")+y,e.forced)&&void 0!==h){if(typeof x==typeof h)continue;c(x,h)}(e.sham||h&&h.sham)&&r(x,"sham",!0),o(i,y,x,e)}}},function(e,n){var i=0,t=Math.random();e.exports=function(e){return"Symbol(".concat(void 0===e?"":e,")_",(++i+t).toString(36))}},function(e,n,i){var t=i(0),l=i(6),r=t.document,o=l(r)&&l(r.createElement);e.exports=function(e){return o?r.createElement(e):{}}},function(e,n,i){var t=i(8),l=i(4),r=i(34);e.exports=!t&&!l(function(){return 7!=Object.defineProperty(r("div"),"a",{get:function(){return 7}}).a})},function(e,n,i){var t=i(11);e.exports=t("native-function-to-string",Function.toString)},function(e,n,i){var t,l,r,o=i(76),a=i(0),c=i(6),p=i(5),y=i(1),h=i(16),x=i(15),s=a.WeakMap;if(o){var u=new s,d=u.get,f=u.has,g=u.set;t=function(e,n){return g.call(u,e,n),n},l=function(e){return d.call(u,e)||{}},r=function(e){return f.call(u,e)}}else{var v=h("state");x[v]=!0,t=function(e,n){return p(e,v,n),n},l=function(e){return y(e,v)?e[v]:{}},r=function(e){return y(e,v)}}e.exports={set:t,get:l,has:r,enforce:function(e){return r(e)?l(e):t(e,{})},getterFor:function(e){return function(n){var i;if(!c(n)||(i=l(n)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return i}}}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=Object.assign||function(e){for(var n=1;n<arguments.length;n++){var i=arguments[n];for(var t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t])}return e},l=o(i(22)),r=o(i(12));function o(e){return e&&e.__esModule?e:{default:e}}n.default=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if("undefined"==typeof document)throw new Error("`feather.replace()` only works in a browser environment.");var n=document.querySelectorAll("[data-feather]");Array.from(n).forEach(function(n){return function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=function(e){return Array.from(e.attributes).reduce(function(e,n){return e[n.name]=n.value,e},{})}(e),o=i["data-feather"];delete i["data-feather"];var a=r.default[o].toSvg(t({},n,i,{class:(0,l.default)(n.class,i.class)})),c=(new DOMParser).parseFromString(a,"image/svg+xml").querySelector("svg");e.parentNode.replaceChild(c,e)}(n,e)})}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t,l=i(12),r=(t=l)&&t.__esModule?t:{default:t};n.default=function(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};if(console.warn("feather.toSvg() is deprecated. Please use feather.icons[name].toSvg() instead."),!e)throw new Error("The required `key` (icon name) parameter is missing.");if(!r.default[e])throw new Error("No icon matching '"+e+"'. See the complete list of icons at https://feathericons.com");return r.default[e].toSvg(n)}},function(e){e.exports={activity:["pulse","health","action","motion"],airplay:["stream","cast","mirroring"],"alert-circle":["warning","alert","danger"],"alert-octagon":["warning","alert","danger"],"alert-triangle":["warning","alert","danger"],"align-center":["text alignment","center"],"align-justify":["text alignment","justified"],"align-left":["text alignment","left"],"align-right":["text alignment","right"],anchor:[],archive:["index","box"],"at-sign":["mention","at","email","message"],award:["achievement","badge"],aperture:["camera","photo"],"bar-chart":["statistics","diagram","graph"],"bar-chart-2":["statistics","diagram","graph"],battery:["power","electricity"],"battery-charging":["power","electricity"],bell:["alarm","notification","sound"],"bell-off":["alarm","notification","silent"],bluetooth:["wireless"],"book-open":["read","library"],book:["read","dictionary","booklet","magazine","library"],bookmark:["read","clip","marker","tag"],box:["cube"],briefcase:["work","bag","baggage","folder"],calendar:["date"],camera:["photo"],cast:["chromecast","airplay"],"chevron-down":["expand"],"chevron-up":["collapse"],circle:["off","zero","record"],clipboard:["copy"],clock:["time","watch","alarm"],"cloud-drizzle":["weather","shower"],"cloud-lightning":["weather","bolt"],"cloud-rain":["weather"],"cloud-snow":["weather","blizzard"],cloud:["weather"],codepen:["logo"],codesandbox:["logo"],code:["source","programming"],coffee:["drink","cup","mug","tea","cafe","hot","beverage"],columns:["layout"],command:["keyboard","cmd","terminal","prompt"],compass:["navigation","safari","travel","direction"],copy:["clone","duplicate"],"corner-down-left":["arrow","return"],"corner-down-right":["arrow"],"corner-left-down":["arrow"],"corner-left-up":["arrow"],"corner-right-down":["arrow"],"corner-right-up":["arrow"],"corner-up-left":["arrow"],"corner-up-right":["arrow"],cpu:["processor","technology"],"credit-card":["purchase","payment","cc"],crop:["photo","image"],crosshair:["aim","target"],database:["storage","memory"],delete:["remove"],disc:["album","cd","dvd","music"],"dollar-sign":["currency","money","payment"],droplet:["water"],edit:["pencil","change"],"edit-2":["pencil","change"],"edit-3":["pencil","change"],eye:["view","watch"],"eye-off":["view","watch","hide","hidden"],"external-link":["outbound"],facebook:["logo","social"],"fast-forward":["music"],figma:["logo","design","tool"],"file-minus":["delete","remove","erase"],"file-plus":["add","create","new"],"file-text":["data","txt","pdf"],film:["movie","video"],filter:["funnel","hopper"],flag:["report"],"folder-minus":["directory"],"folder-plus":["directory"],folder:["directory"],framer:["logo","design","tool"],frown:["emoji","face","bad","sad","emotion"],gift:["present","box","birthday","party"],"git-branch":["code","version control"],"git-commit":["code","version control"],"git-merge":["code","version control"],"git-pull-request":["code","version control"],github:["logo","version control"],gitlab:["logo","version control"],globe:["world","browser","language","translate"],"hard-drive":["computer","server","memory","data"],hash:["hashtag","number","pound"],headphones:["music","audio","sound"],heart:["like","love","emotion"],"help-circle":["question mark"],hexagon:["shape","node.js","logo"],home:["house","living"],image:["picture"],inbox:["email"],instagram:["logo","camera"],key:["password","login","authentication","secure"],layers:["stack"],layout:["window","webpage"],"life-bouy":["help","life ring","support"],link:["chain","url"],"link-2":["chain","url"],linkedin:["logo","social media"],list:["options"],lock:["security","password","secure"],"log-in":["sign in","arrow","enter"],"log-out":["sign out","arrow","exit"],mail:["email","message"],"map-pin":["location","navigation","travel","marker"],map:["location","navigation","travel"],maximize:["fullscreen"],"maximize-2":["fullscreen","arrows","expand"],meh:["emoji","face","neutral","emotion"],menu:["bars","navigation","hamburger"],"message-circle":["comment","chat"],"message-square":["comment","chat"],"mic-off":["record","sound","mute"],mic:["record","sound","listen"],minimize:["exit fullscreen","close"],"minimize-2":["exit fullscreen","arrows","close"],minus:["subtract"],monitor:["tv","screen","display"],moon:["dark","night"],"more-horizontal":["ellipsis"],"more-vertical":["ellipsis"],"mouse-pointer":["arrow","cursor"],move:["arrows"],music:["note"],navigation:["location","travel"],"navigation-2":["location","travel"],octagon:["stop"],package:["box","container"],paperclip:["attachment"],pause:["music","stop"],"pause-circle":["music","audio","stop"],"pen-tool":["vector","drawing"],percent:["discount"],"phone-call":["ring"],"phone-forwarded":["call"],"phone-incoming":["call"],"phone-missed":["call"],"phone-off":["call","mute"],"phone-outgoing":["call"],phone:["call"],play:["music","start"],"pie-chart":["statistics","diagram"],"play-circle":["music","start"],plus:["add","new"],"plus-circle":["add","new"],"plus-square":["add","new"],pocket:["logo","save"],power:["on","off"],printer:["fax","office","device"],radio:["signal"],"refresh-cw":["synchronise","arrows"],"refresh-ccw":["arrows"],repeat:["loop","arrows"],rewind:["music"],"rotate-ccw":["arrow"],"rotate-cw":["arrow"],rss:["feed","subscribe"],save:["floppy disk"],scissors:["cut"],search:["find","magnifier","magnifying glass"],send:["message","mail","email","paper airplane","paper aeroplane"],settings:["cog","edit","gear","preferences"],"share-2":["network","connections"],shield:["security","secure"],"shield-off":["security","insecure"],"shopping-bag":["ecommerce","cart","purchase","store"],"shopping-cart":["ecommerce","cart","purchase","store"],shuffle:["music"],"skip-back":["music"],"skip-forward":["music"],slack:["logo"],slash:["ban","no"],sliders:["settings","controls"],smartphone:["cellphone","device"],smile:["emoji","face","happy","good","emotion"],speaker:["audio","music"],star:["bookmark","favorite","like"],"stop-circle":["media","music"],sun:["brightness","weather","light"],sunrise:["weather","time","morning","day"],sunset:["weather","time","evening","night"],tablet:["device"],tag:["label"],target:["logo","bullseye"],terminal:["code","command line","prompt"],thermometer:["temperature","celsius","fahrenheit","weather"],"thumbs-down":["dislike","bad","emotion"],"thumbs-up":["like","good","emotion"],"toggle-left":["on","off","switch"],"toggle-right":["on","off","switch"],tool:["settings","spanner"],trash:["garbage","delete","remove","bin"],"trash-2":["garbage","delete","remove","bin"],triangle:["delta"],truck:["delivery","van","shipping","transport","lorry"],tv:["television","stream"],twitch:["logo"],twitter:["logo","social"],type:["text"],umbrella:["rain","weather"],unlock:["security"],"user-check":["followed","subscribed"],"user-minus":["delete","remove","unfollow","unsubscribe"],"user-plus":["new","add","create","follow","subscribe"],"user-x":["delete","remove","unfollow","unsubscribe","unavailable"],user:["person","account"],users:["group"],"video-off":["camera","movie","film"],video:["camera","movie","film"],voicemail:["phone"],volume:["music","sound","mute"],"volume-1":["music","sound"],"volume-2":["music","sound"],"volume-x":["music","sound","mute"],watch:["clock","time"],"wifi-off":["disabled"],wifi:["connection","signal","wireless"],wind:["weather","air"],"x-circle":["cancel","close","delete","remove","times","clear"],"x-octagon":["delete","stop","alert","warning","times","clear"],"x-square":["cancel","close","delete","remove","times","clear"],x:["cancel","close","delete","remove","times","clear"],youtube:["logo","video","play"],"zap-off":["flash","camera","lightning"],zap:["flash","camera","lightning"],"zoom-in":["magnifying glass"],"zoom-out":["magnifying glass"]}},function(e){e.exports={activity:'<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>',airplay:'<path d="M5 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-1"></path><polygon points="12 15 17 21 7 21 12 15"></polygon>',"alert-circle":'<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line>',"alert-octagon":'<polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line>',"alert-triangle":'<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line>',"align-center":'<line x1="18" y1="10" x2="6" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="18" y1="18" x2="6" y2="18"></line>',"align-justify":'<line x1="21" y1="10" x2="3" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="21" y1="18" x2="3" y2="18"></line>',"align-left":'<line x1="17" y1="10" x2="3" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="17" y1="18" x2="3" y2="18"></line>',"align-right":'<line x1="21" y1="10" x2="7" y2="10"></line><line x1="21" y1="6" x2="3" y2="6"></line><line x1="21" y1="14" x2="3" y2="14"></line><line x1="21" y1="18" x2="7" y2="18"></line>',anchor:'<circle cx="12" cy="5" r="3"></circle><line x1="12" y1="22" x2="12" y2="8"></line><path d="M5 12H2a10 10 0 0 0 20 0h-3"></path>',aperture:'<circle cx="12" cy="12" r="10"></circle><line x1="14.31" y1="8" x2="20.05" y2="17.94"></line><line x1="9.69" y1="8" x2="21.17" y2="8"></line><line x1="7.38" y1="12" x2="13.12" y2="2.06"></line><line x1="9.69" y1="16" x2="3.95" y2="6.06"></line><line x1="14.31" y1="16" x2="2.83" y2="16"></line><line x1="16.62" y1="12" x2="10.88" y2="21.94"></line>',archive:'<polyline points="21 8 21 21 3 21 3 8"></polyline><rect x="1" y="3" width="22" height="5"></rect><line x1="10" y1="12" x2="14" y2="12"></line>',"arrow-down-circle":'<circle cx="12" cy="12" r="10"></circle><polyline points="8 12 12 16 16 12"></polyline><line x1="12" y1="8" x2="12" y2="16"></line>',"arrow-down-left":'<line x1="17" y1="7" x2="7" y2="17"></line><polyline points="17 17 7 17 7 7"></polyline>',"arrow-down-right":'<line x1="7" y1="7" x2="17" y2="17"></line><polyline points="17 7 17 17 7 17"></polyline>',"arrow-down":'<line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline>',"arrow-left-circle":'<circle cx="12" cy="12" r="10"></circle><polyline points="12 8 8 12 12 16"></polyline><line x1="16" y1="12" x2="8" y2="12"></line>',"arrow-left":'<line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline>',"arrow-right-circle":'<circle cx="12" cy="12" r="10"></circle><polyline points="12 16 16 12 12 8"></polyline><line x1="8" y1="12" x2="16" y2="12"></line>',"arrow-right":'<line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline>',"arrow-up-circle":'<circle cx="12" cy="12" r="10"></circle><polyline points="16 12 12 8 8 12"></polyline><line x1="12" y1="16" x2="12" y2="8"></line>',"arrow-up-left":'<line x1="17" y1="17" x2="7" y2="7"></line><polyline points="7 17 7 7 17 7"></polyline>',"arrow-up-right":'<line x1="7" y1="17" x2="17" y2="7"></line><polyline points="7 7 17 7 17 17"></polyline>',"arrow-up":'<line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline>',"at-sign":'<circle cx="12" cy="12" r="4"></circle><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94"></path>',award:'<circle cx="12" cy="8" r="7"></circle><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"></polyline>',"bar-chart-2":'<line x1="18" y1="20" x2="18" y2="10"></line><line x1="12" y1="20" x2="12" y2="4"></line><line x1="6" y1="20" x2="6" y2="14"></line>',"bar-chart":'<line x1="12" y1="20" x2="12" y2="10"></line><line x1="18" y1="20" x2="18" y2="4"></line><line x1="6" y1="20" x2="6" y2="16"></line>',"battery-charging":'<path d="M5 18H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3.19M15 6h2a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-3.19"></path><line x1="23" y1="13" x2="23" y2="11"></line><polyline points="11 6 7 12 13 12 9 18"></polyline>',battery:'<rect x="1" y="6" width="18" height="12" rx="2" ry="2"></rect><line x1="23" y1="13" x2="23" y2="11"></line>',"bell-off":'<path d="M13.73 21a2 2 0 0 1-3.46 0"></path><path d="M18.63 13A17.89 17.89 0 0 1 18 8"></path><path d="M6.26 6.26A5.86 5.86 0 0 0 6 8c0 7-3 9-3 9h14"></path><path d="M18 8a6 6 0 0 0-9.33-5"></path><line x1="1" y1="1" x2="23" y2="23"></line>',bell:'<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path>',bluetooth:'<polyline points="6.5 6.5 17.5 17.5 12 23 12 1 17.5 6.5 6.5 17.5"></polyline>',bold:'<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>',"book-open":'<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>',book:'<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>',bookmark:'<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>',box:'<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line>',briefcase:'<rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path>',calendar:'<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line>',"camera-off":'<line x1="1" y1="1" x2="23" y2="23"></line><path d="M21 21H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h3m3-3h6l2 3h4a2 2 0 0 1 2 2v9.34m-7.72-2.06a4 4 0 1 1-5.56-5.56"></path>',camera:'<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle>',cast:'<path d="M2 16.1A5 5 0 0 1 5.9 20M2 12.05A9 9 0 0 1 9.95 20M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6"></path><line x1="2" y1="20" x2="2.01" y2="20"></line>',"check-circle":'<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline>',"check-square":'<polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>',check:'<polyline points="20 6 9 17 4 12"></polyline>',"chevron-down":'<polyline points="6 9 12 15 18 9"></polyline>',"chevron-left":'<polyline points="15 18 9 12 15 6"></polyline>',"chevron-right":'<polyline points="9 18 15 12 9 6"></polyline>',"chevron-up":'<polyline points="18 15 12 9 6 15"></polyline>',"chevrons-down":'<polyline points="7 13 12 18 17 13"></polyline><polyline points="7 6 12 11 17 6"></polyline>',"chevrons-left":'<polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline>',"chevrons-right":'<polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline>',"chevrons-up":'<polyline points="17 11 12 6 7 11"></polyline><polyline points="17 18 12 13 7 18"></polyline>',chrome:'<circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="4"></circle><line x1="21.17" y1="8" x2="12" y2="8"></line><line x1="3.95" y1="6.06" x2="8.54" y2="14"></line><line x1="10.88" y1="21.94" x2="15.46" y2="14"></line>',circle:'<circle cx="12" cy="12" r="10"></circle>',clipboard:'<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect>',clock:'<circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline>',"cloud-drizzle":'<line x1="8" y1="19" x2="8" y2="21"></line><line x1="8" y1="13" x2="8" y2="15"></line><line x1="16" y1="19" x2="16" y2="21"></line><line x1="16" y1="13" x2="16" y2="15"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="12" y1="15" x2="12" y2="17"></line><path d="M20 16.58A5 5 0 0 0 18 7h-1.26A8 8 0 1 0 4 15.25"></path>',"cloud-lightning":'<path d="M19 16.9A5 5 0 0 0 18 7h-1.26a8 8 0 1 0-11.62 9"></path><polyline points="13 11 9 17 15 17 11 23"></polyline>',"cloud-off":'<path d="M22.61 16.95A5 5 0 0 0 18 10h-1.26a8 8 0 0 0-7.05-6M5 5a8 8 0 0 0 4 15h9a5 5 0 0 0 1.7-.3"></path><line x1="1" y1="1" x2="23" y2="23"></line>',"cloud-rain":'<line x1="16" y1="13" x2="16" y2="21"></line><line x1="8" y1="13" x2="8" y2="21"></line><line x1="12" y1="15" x2="12" y2="23"></line><path d="M20 16.58A5 5 0 0 0 18 7h-1.26A8 8 0 1 0 4 15.25"></path>',"cloud-snow":'<path d="M20 17.58A5 5 0 0 0 18 8h-1.26A8 8 0 1 0 4 16.25"></path><line x1="8" y1="16" x2="8.01" y2="16"></line><line x1="8" y1="20" x2="8.01" y2="20"></line><line x1="12" y1="18" x2="12.01" y2="18"></line><line x1="12" y1="22" x2="12.01" y2="22"></line><line x1="16" y1="16" x2="16.01" y2="16"></line><line x1="16" y1="20" x2="16.01" y2="20"></line>',cloud:'<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"></path>',code:'<polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline>',codepen:'<polygon points="12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2"></polygon><line x1="12" y1="22" x2="12" y2="15.5"></line><polyline points="22 8.5 12 15.5 2 8.5"></polyline><polyline points="2 15.5 12 8.5 22 15.5"></polyline><line x1="12" y1="2" x2="12" y2="8.5"></line>',codesandbox:'<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="7.5 4.21 12 6.81 16.5 4.21"></polyline><polyline points="7.5 19.79 7.5 14.6 3 12"></polyline><polyline points="21 12 16.5 14.6 16.5 19.79"></polyline><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line>',coffee:'<path d="M18 8h1a4 4 0 0 1 0 8h-1"></path><path d="M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V8z"></path><line x1="6" y1="1" x2="6" y2="4"></line><line x1="10" y1="1" x2="10" y2="4"></line><line x1="14" y1="1" x2="14" y2="4"></line>',columns:'<path d="M12 3h7a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-7m0-18H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h7m0-18v18"></path>',command:'<path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z"></path>',compass:'<circle cx="12" cy="12" r="10"></circle><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"></polygon>',copy:'<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>',"corner-down-left":'<polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path>',"corner-down-right":'<polyline points="15 10 20 15 15 20"></polyline><path d="M4 4v7a4 4 0 0 0 4 4h12"></path>',"corner-left-down":'<polyline points="14 15 9 20 4 15"></polyline><path d="M20 4h-7a4 4 0 0 0-4 4v12"></path>',"corner-left-up":'<polyline points="14 9 9 4 4 9"></polyline><path d="M20 20h-7a4 4 0 0 1-4-4V4"></path>',"corner-right-down":'<polyline points="10 15 15 20 20 15"></polyline><path d="M4 4h7a4 4 0 0 1 4 4v12"></path>',"corner-right-up":'<polyline points="10 9 15 4 20 9"></polyline><path d="M4 20h7a4 4 0 0 0 4-4V4"></path>',"corner-up-left":'<polyline points="9 14 4 9 9 4"></polyline><path d="M20 20v-7a4 4 0 0 0-4-4H4"></path>',"corner-up-right":'<polyline points="15 14 20 9 15 4"></polyline><path d="M4 20v-7a4 4 0 0 1 4-4h12"></path>',cpu:'<rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="14" x2="23" y2="14"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="14" x2="4" y2="14"></line>',"credit-card":'<rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect><line x1="1" y1="10" x2="23" y2="10"></line>',crop:'<path d="M6.13 1L6 16a2 2 0 0 0 2 2h15"></path><path d="M1 6.13L16 6a2 2 0 0 1 2 2v15"></path>',crosshair:'<circle cx="12" cy="12" r="10"></circle><line x1="22" y1="12" x2="18" y2="12"></line><line x1="6" y1="12" x2="2" y2="12"></line><line x1="12" y1="6" x2="12" y2="2"></line><line x1="12" y1="22" x2="12" y2="18"></line>',database:'<ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path>',delete:'<path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path><line x1="18" y1="9" x2="12" y2="15"></line><line x1="12" y1="9" x2="18" y2="15"></line>',disc:'<circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="3"></circle>',"divide-circle":'<line x1="8" y1="12" x2="16" y2="12"></line><line x1="12" y1="16" x2="12" y2="16"></line><line x1="12" y1="8" x2="12" y2="8"></line><circle cx="12" cy="12" r="10"></circle>',"divide-square":'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="8" y1="12" x2="16" y2="12"></line><line x1="12" y1="16" x2="12" y2="16"></line><line x1="12" y1="8" x2="12" y2="8"></line>',divide:'<circle cx="12" cy="6" r="2"></circle><line x1="5" y1="12" x2="19" y2="12"></line><circle cx="12" cy="18" r="2"></circle>',"dollar-sign":'<line x1="12" y1="1" x2="12" y2="23"></line><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path>',"download-cloud":'<polyline points="8 17 12 21 16 17"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.88 18.09A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.29"></path>',download:'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line>',dribbble:'<circle cx="12" cy="12" r="10"></circle><path d="M8.56 2.75c4.37 6.03 6.02 9.42 8.03 17.72m2.54-15.38c-3.72 4.35-8.94 5.66-16.88 5.85m19.5 1.9c-3.5-.93-6.63-.82-8.94 0-2.58.92-5.01 2.86-7.44 6.32"></path>',droplet:'<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"></path>',"edit-2":'<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>',"edit-3":'<path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>',edit:'<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>',"external-link":'<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line>',"eye-off":'<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line>',eye:'<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle>',facebook:'<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z"></path>',"fast-forward":'<polygon points="13 19 22 12 13 5 13 19"></polygon><polygon points="2 19 11 12 2 5 2 19"></polygon>',feather:'<path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"></path><line x1="16" y1="8" x2="2" y2="22"></line><line x1="17.5" y1="15" x2="9" y2="15"></line>',figma:'<path d="M5 5.5A3.5 3.5 0 0 1 8.5 2H12v7H8.5A3.5 3.5 0 0 1 5 5.5z"></path><path d="M12 2h3.5a3.5 3.5 0 1 1 0 7H12V2z"></path><path d="M12 12.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 1 1-7 0z"></path><path d="M5 19.5A3.5 3.5 0 0 1 8.5 16H12v3.5a3.5 3.5 0 1 1-7 0z"></path><path d="M5 12.5A3.5 3.5 0 0 1 8.5 9H12v7H8.5A3.5 3.5 0 0 1 5 12.5z"></path>',"file-minus":'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="9" y1="15" x2="15" y2="15"></line>',"file-plus":'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="12" y1="18" x2="12" y2="12"></line><line x1="9" y1="15" x2="15" y2="15"></line>',"file-text":'<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>',file:'<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline>',film:'<rect x="2" y="2" width="20" height="20" rx="2.18" ry="2.18"></rect><line x1="7" y1="2" x2="7" y2="22"></line><line x1="17" y1="2" x2="17" y2="22"></line><line x1="2" y1="12" x2="22" y2="12"></line><line x1="2" y1="7" x2="7" y2="7"></line><line x1="2" y1="17" x2="7" y2="17"></line><line x1="17" y1="17" x2="22" y2="17"></line><line x1="17" y1="7" x2="22" y2="7"></line>',filter:'<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>',flag:'<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line>',"folder-minus":'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="9" y1="14" x2="15" y2="14"></line>',"folder-plus":'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="12" y1="11" x2="12" y2="17"></line><line x1="9" y1="14" x2="15" y2="14"></line>',folder:'<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>',framer:'<path d="M5 16V9h14V2H5l14 14h-7m-7 0l7 7v-7m-7 0h7"></path>',frown:'<circle cx="12" cy="12" r="10"></circle><path d="M16 16s-1.5-2-4-2-4 2-4 2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line>',gift:'<polyline points="20 12 20 22 4 22 4 12"></polyline><rect x="2" y="7" width="20" height="5"></rect><line x1="12" y1="22" x2="12" y2="7"></line><path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path><path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>',"git-branch":'<line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path>',"git-commit":'<circle cx="12" cy="12" r="4"></circle><line x1="1.05" y1="12" x2="7" y2="12"></line><line x1="17.01" y1="12" x2="22.96" y2="12"></line>',"git-merge":'<circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M6 21V9a9 9 0 0 0 9 9"></path>',"git-pull-request":'<circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M13 6h3a2 2 0 0 1 2 2v7"></path><line x1="6" y1="9" x2="6" y2="21"></line>',github:'<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>',gitlab:'<path d="M22.65 14.39L12 22.13 1.35 14.39a.84.84 0 0 1-.3-.94l1.22-3.78 2.44-7.51A.42.42 0 0 1 4.82 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.49h8.1l2.44-7.51A.42.42 0 0 1 18.6 2a.43.43 0 0 1 .58 0 .42.42 0 0 1 .11.18l2.44 7.51L23 13.45a.84.84 0 0 1-.35.94z"></path>',globe:'<circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>',grid:'<rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect>',"hard-drive":'<line x1="22" y1="12" x2="2" y2="12"></line><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path><line x1="6" y1="16" x2="6.01" y2="16"></line><line x1="10" y1="16" x2="10.01" y2="16"></line>',hash:'<line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line><line x1="10" y1="3" x2="8" y2="21"></line><line x1="16" y1="3" x2="14" y2="21"></line>',headphones:'<path d="M3 18v-6a9 9 0 0 1 18 0v6"></path><path d="M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z"></path>',heart:'<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path>',"help-circle":'<circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line>',hexagon:'<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>',home:'<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline>',image:'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline>',inbox:'<polyline points="22 12 16 12 14 15 10 15 8 12 2 12"></polyline><path d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>',info:'<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line>',instagram:'<rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line>',italic:'<line x1="19" y1="4" x2="10" y2="4"></line><line x1="14" y1="20" x2="5" y2="20"></line><line x1="15" y1="4" x2="9" y2="20"></line>',key:'<path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path>',layers:'<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline>',layout:'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line>',"life-buoy":'<circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="4"></circle><line x1="4.93" y1="4.93" x2="9.17" y2="9.17"></line><line x1="14.83" y1="14.83" x2="19.07" y2="19.07"></line><line x1="14.83" y1="9.17" x2="19.07" y2="4.93"></line><line x1="14.83" y1="9.17" x2="18.36" y2="5.64"></line><line x1="4.93" y1="19.07" x2="9.17" y2="14.83"></line>',"link-2":'<path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path><line x1="8" y1="12" x2="16" y2="12"></line>',link:'<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>',linkedin:'<path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle>',list:'<line x1="8" y1="6" x2="21" y2="6"></line><line x1="8" y1="12" x2="21" y2="12"></line><line x1="8" y1="18" x2="21" y2="18"></line><line x1="3" y1="6" x2="3.01" y2="6"></line><line x1="3" y1="12" x2="3.01" y2="12"></line><line x1="3" y1="18" x2="3.01" y2="18"></line>',loader:'<line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>',lock:'<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path>',"log-in":'<path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line>',"log-out":'<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line>',mail:'<path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline>',"map-pin":'<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle>',map:'<polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"></polygon><line x1="8" y1="2" x2="8" y2="18"></line><line x1="16" y1="6" x2="16" y2="22"></line>',"maximize-2":'<polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" y1="3" x2="14" y2="10"></line><line x1="3" y1="21" x2="10" y2="14"></line>',maximize:'<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>',meh:'<circle cx="12" cy="12" r="10"></circle><line x1="8" y1="15" x2="16" y2="15"></line><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line>',menu:'<line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line>',"message-circle":'<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"></path>',"message-square":'<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>',"mic-off":'<line x1="1" y1="1" x2="23" y2="23"></line><path d="M9 9v3a3 3 0 0 0 5.12 2.12M15 9.34V4a3 3 0 0 0-5.94-.6"></path><path d="M17 16.95A7 7 0 0 1 5 12v-2m14 0v2a7 7 0 0 1-.11 1.23"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line>',mic:'<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line>',"minimize-2":'<polyline points="4 14 10 14 10 20"></polyline><polyline points="20 10 14 10 14 4"></polyline><line x1="14" y1="10" x2="21" y2="3"></line><line x1="3" y1="21" x2="10" y2="14"></line>',minimize:'<path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path>',"minus-circle":'<circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line>',"minus-square":'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="8" y1="12" x2="16" y2="12"></line>',minus:'<line x1="5" y1="12" x2="19" y2="12"></line>',monitor:'<rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line>',moon:'<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>',"more-horizontal":'<circle cx="12" cy="12" r="1"></circle><circle cx="19" cy="12" r="1"></circle><circle cx="5" cy="12" r="1"></circle>',"more-vertical":'<circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle>',"mouse-pointer":'<path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"></path><path d="M13 13l6 6"></path>',move:'<polyline points="5 9 2 12 5 15"></polyline><polyline points="9 5 12 2 15 5"></polyline><polyline points="15 19 12 22 9 19"></polyline><polyline points="19 9 22 12 19 15"></polyline><line x1="2" y1="12" x2="22" y2="12"></line><line x1="12" y1="2" x2="12" y2="22"></line>',music:'<path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle>',"navigation-2":'<polygon points="12 2 19 21 12 17 5 21 12 2"></polygon>',navigation:'<polygon points="3 11 22 2 13 21 11 13 3 11"></polygon>',octagon:'<polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon>',package:'<line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line>',paperclip:'<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>',"pause-circle":'<circle cx="12" cy="12" r="10"></circle><line x1="10" y1="15" x2="10" y2="9"></line><line x1="14" y1="15" x2="14" y2="9"></line>',pause:'<rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect>',"pen-tool":'<path d="M12 19l7-7 3 3-7 7-3-3z"></path><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"></path><path d="M2 2l7.586 7.586"></path><circle cx="11" cy="11" r="2"></circle>',percent:'<line x1="19" y1="5" x2="5" y2="19"></line><circle cx="6.5" cy="6.5" r="2.5"></circle><circle cx="17.5" cy="17.5" r="2.5"></circle>',"phone-call":'<path d="M15.05 5A5 5 0 0 1 19 8.95M15.05 1A9 9 0 0 1 23 8.94m-1 7.98v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>',"phone-forwarded":'<polyline points="19 1 23 5 19 9"></polyline><line x1="15" y1="5" x2="23" y2="5"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>',"phone-incoming":'<polyline points="16 2 16 8 22 8"></polyline><line x1="23" y1="1" x2="16" y2="8"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>',"phone-missed":'<line x1="23" y1="1" x2="17" y2="7"></line><line x1="17" y1="1" x2="23" y2="7"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>',"phone-off":'<path d="M10.68 13.31a16 16 0 0 0 3.41 2.6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7 2 2 0 0 1 1.72 2v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.42 19.42 0 0 1-3.33-2.67m-2.67-3.34a19.79 19.79 0 0 1-3.07-8.63A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91"></path><line x1="23" y1="1" x2="1" y2="23"></line>',"phone-outgoing":'<polyline points="23 7 23 1 17 1"></polyline><line x1="16" y1="8" x2="23" y2="1"></line><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>',phone:'<path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path>',"pie-chart":'<path d="M21.21 15.89A10 10 0 1 1 8 2.83"></path><path d="M22 12A10 10 0 0 0 12 2v10z"></path>',"play-circle":'<circle cx="12" cy="12" r="10"></circle><polygon points="10 8 16 12 10 16 10 8"></polygon>',play:'<polygon points="5 3 19 12 5 21 5 3"></polygon>',"plus-circle":'<circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line>',"plus-square":'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line>',plus:'<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>',pocket:'<path d="M4 3h16a2 2 0 0 1 2 2v6a10 10 0 0 1-10 10A10 10 0 0 1 2 11V5a2 2 0 0 1 2-2z"></path><polyline points="8 10 12 14 16 10"></polyline>',power:'<path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path><line x1="12" y1="2" x2="12" y2="12"></line>',printer:'<polyline points="6 9 6 2 18 2 18 9"></polyline><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"></path><rect x="6" y="14" width="12" height="8"></rect>',radio:'<circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>',"refresh-ccw":'<polyline points="1 4 1 10 7 10"></polyline><polyline points="23 20 23 14 17 14"></polyline><path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"></path>',"refresh-cw":'<polyline points="23 4 23 10 17 10"></polyline><polyline points="1 20 1 14 7 14"></polyline><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"></path>',repeat:'<polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path>',rewind:'<polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon>',"rotate-ccw":'<polyline points="1 4 1 10 7 10"></polyline><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"></path>',"rotate-cw":'<polyline points="23 4 23 10 17 10"></polyline><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>',rss:'<path d="M4 11a9 9 0 0 1 9 9"></path><path d="M4 4a16 16 0 0 1 16 16"></path><circle cx="5" cy="19" r="1"></circle>',save:'<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline>',scissors:'<circle cx="6" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><line x1="20" y1="4" x2="8.12" y2="15.88"></line><line x1="14.47" y1="14.48" x2="20" y2="20"></line><line x1="8.12" y1="8.12" x2="12" y2="12"></line>',search:'<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>',send:'<line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>',server:'<rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line>',settings:'<circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>',"share-2":'<circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>',share:'<path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="15"></line>',"shield-off":'<path d="M19.69 14a6.9 6.9 0 0 0 .31-2V5l-8-3-3.16 1.18"></path><path d="M4.73 4.73L4 5v7c0 6 8 10 8 10a20.29 20.29 0 0 0 5.62-4.38"></path><line x1="1" y1="1" x2="23" y2="23"></line>',shield:'<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path>',"shopping-bag":'<path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"></path><line x1="3" y1="6" x2="21" y2="6"></line><path d="M16 10a4 4 0 0 1-8 0"></path>',"shopping-cart":'<circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path>',shuffle:'<polyline points="16 3 21 3 21 8"></polyline><line x1="4" y1="20" x2="21" y2="3"></line><polyline points="21 16 21 21 16 21"></polyline><line x1="15" y1="15" x2="21" y2="21"></line><line x1="4" y1="4" x2="9" y2="9"></line>',sidebar:'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="3" x2="9" y2="21"></line>',"skip-back":'<polygon points="19 20 9 12 19 4 19 20"></polygon><line x1="5" y1="19" x2="5" y2="5"></line>',"skip-forward":'<polygon points="5 4 15 12 5 20 5 4"></polygon><line x1="19" y1="5" x2="19" y2="19"></line>',slack:'<path d="M14.5 10c-.83 0-1.5-.67-1.5-1.5v-5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5v5c0 .83-.67 1.5-1.5 1.5z"></path><path d="M20.5 10H19V8.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z"></path><path d="M9.5 14c.83 0 1.5.67 1.5 1.5v5c0 .83-.67 1.5-1.5 1.5S8 21.33 8 20.5v-5c0-.83.67-1.5 1.5-1.5z"></path><path d="M3.5 14H5v1.5c0 .83-.67 1.5-1.5 1.5S2 16.33 2 15.5 2.67 14 3.5 14z"></path><path d="M14 14.5c0-.83.67-1.5 1.5-1.5h5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-5c-.83 0-1.5-.67-1.5-1.5z"></path><path d="M15.5 19H14v1.5c0 .83.67 1.5 1.5 1.5s1.5-.67 1.5-1.5-.67-1.5-1.5-1.5z"></path><path d="M10 9.5C10 8.67 9.33 8 8.5 8h-5C2.67 8 2 8.67 2 9.5S2.67 11 3.5 11h5c.83 0 1.5-.67 1.5-1.5z"></path><path d="M8.5 5H10V3.5C10 2.67 9.33 2 8.5 2S7 2.67 7 3.5 7.67 5 8.5 5z"></path>',slash:'<circle cx="12" cy="12" r="10"></circle><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line>',sliders:'<line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line>',smartphone:'<rect x="5" y="2" width="14" height="20" rx="2" ry="2"></rect><line x1="12" y1="18" x2="12.01" y2="18"></line>',smile:'<circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line>',speaker:'<rect x="4" y="2" width="16" height="20" rx="2" ry="2"></rect><circle cx="12" cy="14" r="4"></circle><line x1="12" y1="6" x2="12.01" y2="6"></line>',square:'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>',star:'<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>',"stop-circle":'<circle cx="12" cy="12" r="10"></circle><rect x="9" y="9" width="6" height="6"></rect>',sun:'<circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>',sunrise:'<path d="M17 18a5 5 0 0 0-10 0"></path><line x1="12" y1="2" x2="12" y2="9"></line><line x1="4.22" y1="10.22" x2="5.64" y2="11.64"></line><line x1="1" y1="18" x2="3" y2="18"></line><line x1="21" y1="18" x2="23" y2="18"></line><line x1="18.36" y1="11.64" x2="19.78" y2="10.22"></line><line x1="23" y1="22" x2="1" y2="22"></line><polyline points="8 6 12 2 16 6"></polyline>',sunset:'<path d="M17 18a5 5 0 0 0-10 0"></path><line x1="12" y1="9" x2="12" y2="2"></line><line x1="4.22" y1="10.22" x2="5.64" y2="11.64"></line><line x1="1" y1="18" x2="3" y2="18"></line><line x1="21" y1="18" x2="23" y2="18"></line><line x1="18.36" y1="11.64" x2="19.78" y2="10.22"></line><line x1="23" y1="22" x2="1" y2="22"></line><polyline points="16 5 12 9 8 5"></polyline>',table:'<path d="M9 3H5a2 2 0 0 0-2 2v4m6-6h10a2 2 0 0 1 2 2v4M9 3v18m0 0h10a2 2 0 0 0 2-2V9M9 21H5a2 2 0 0 1-2-2V9m0 0h18"></path>',tablet:'<rect x="4" y="2" width="16" height="20" rx="2" ry="2"></rect><line x1="12" y1="18" x2="12.01" y2="18"></line>',tag:'<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line>',target:'<circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="6"></circle><circle cx="12" cy="12" r="2"></circle>',terminal:'<polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line>',thermometer:'<path d="M14 14.76V3.5a2.5 2.5 0 0 0-5 0v11.26a4.5 4.5 0 1 0 5 0z"></path>',"thumbs-down":'<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17"></path>',"thumbs-up":'<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3"></path>',"toggle-left":'<rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect><circle cx="8" cy="12" r="3"></circle>',"toggle-right":'<rect x="1" y="5" width="22" height="14" rx="7" ry="7"></rect><circle cx="16" cy="12" r="3"></circle>',tool:'<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path>',"trash-2":'<polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line>',trash:'<polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>',trello:'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><rect x="7" y="7" width="3" height="9"></rect><rect x="14" y="7" width="3" height="5"></rect>',"trending-down":'<polyline points="23 18 13.5 8.5 8.5 13.5 1 6"></polyline><polyline points="17 18 23 18 23 12"></polyline>',"trending-up":'<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline><polyline points="17 6 23 6 23 12"></polyline>',triangle:'<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>',truck:'<rect x="1" y="3" width="15" height="13"></rect><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"></polygon><circle cx="5.5" cy="18.5" r="2.5"></circle><circle cx="18.5" cy="18.5" r="2.5"></circle>',tv:'<rect x="2" y="7" width="20" height="15" rx="2" ry="2"></rect><polyline points="17 2 12 7 7 2"></polyline>',twitch:'<path d="M21 2H3v16h5v4l4-4h5l4-4V2zm-10 9V7m5 4V7"></path>',twitter:'<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path>',type:'<polyline points="4 7 4 4 20 4 20 7"></polyline><line x1="9" y1="20" x2="15" y2="20"></line><line x1="12" y1="4" x2="12" y2="20"></line>',umbrella:'<path d="M23 12a11.05 11.05 0 0 0-22 0zm-5 7a3 3 0 0 1-6 0v-7"></path>',underline:'<path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"></path><line x1="4" y1="21" x2="20" y2="21"></line>',unlock:'<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 9.9-1"></path>',"upload-cloud":'<polyline points="16 16 12 12 8 16"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path><polyline points="16 16 12 12 8 16"></polyline>',upload:'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line>',"user-check":'<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><polyline points="17 11 19 13 23 9"></polyline>',"user-minus":'<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="23" y1="11" x2="17" y2="11"></line>',"user-plus":'<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line>',"user-x":'<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="18" y1="8" x2="23" y2="13"></line><line x1="23" y1="8" x2="18" y2="13"></line>',user:'<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle>',users:'<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path>',"video-off":'<path d="M16 16v1a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2m5.66 0H14a2 2 0 0 1 2 2v3.34l1 1L23 7v10"></path><line x1="1" y1="1" x2="23" y2="23"></line>',video:'<polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect>',voicemail:'<circle cx="5.5" cy="11.5" r="4.5"></circle><circle cx="18.5" cy="11.5" r="4.5"></circle><line x1="5.5" y1="16" x2="18.5" y2="16"></line>',"volume-1":'<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>',"volume-2":'<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>',"volume-x":'<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon><line x1="23" y1="9" x2="17" y2="15"></line><line x1="17" y1="9" x2="23" y2="15"></line>',volume:'<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>',watch:'<circle cx="12" cy="12" r="7"></circle><polyline points="12 9 12 12 13.5 13.5"></polyline><path d="M16.51 17.35l-.35 3.83a2 2 0 0 1-2 1.82H9.83a2 2 0 0 1-2-1.82l-.35-3.83m.01-10.7l.35-3.83A2 2 0 0 1 9.83 1h4.35a2 2 0 0 1 2 1.82l.35 3.83"></path>',"wifi-off":'<line x1="1" y1="1" x2="23" y2="23"></line><path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"></path><path d="M5 12.55a10.94 10.94 0 0 1 5.17-2.39"></path><path d="M10.71 5.05A16 16 0 0 1 22.58 9"></path><path d="M1.42 9a15.91 15.91 0 0 1 4.7-2.88"></path><path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path><line x1="12" y1="20" x2="12.01" y2="20"></line>',wifi:'<path d="M5 12.55a11 11 0 0 1 14.08 0"></path><path d="M1.42 9a16 16 0 0 1 21.16 0"></path><path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path><line x1="12" y1="20" x2="12.01" y2="20"></line>',wind:'<path d="M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2.5 2.5 0 1 1 19.5 12H2"></path>',"x-circle":'<circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>',"x-octagon":'<polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line>',"x-square":'<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="9" x2="15" y2="15"></line><line x1="15" y1="9" x2="9" y2="15"></line>',x:'<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>',youtube:'<path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"></path><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"></polygon>',"zap-off":'<polyline points="12.41 6.75 13 2 10.57 4.92"></polyline><polyline points="18.57 12.91 21 10 15.66 10"></polyline><polyline points="8 8 3 14 12 14 11 22 16 16"></polyline><line x1="1" y1="1" x2="23" y2="23"></line>',zap:'<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon>',"zoom-in":'<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line>',"zoom-out":'<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line>'}},function(e){e.exports={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round"}},function(e,n,i){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var t=Object.assign||function(e){for(var n=1;n<arguments.length;n++){var i=arguments[n];for(var t in i)Object.prototype.hasOwnProperty.call(i,t)&&(e[t]=i[t])}return e},l=function(){function e(e,n){for(var i=0;i<n.length;i++){var t=n[i];t.enumerable=t.enumerable||!1,t.configurable=!0,"value"in t&&(t.writable=!0),Object.defineProperty(e,t.key,t)}}return function(n,i,t){return i&&e(n.prototype,i),t&&e(n,t),n}}(),r=a(i(22)),o=a(i(42));function a(e){return e&&e.__esModule?e:{default:e}}var c=function(){function e(n,i){var l=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[];!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e),this.name=n,this.contents=i,this.tags=l,this.attrs=t({},o.default,{class:"feather feather-"+n})}return l(e,[{key:"toSvg",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return"<svg "+function(e){return Object.keys(e).map(function(n){return n+'="'+e[n]+'"'}).join(" ")}(t({},this.attrs,e,{class:(0,r.default)(this.attrs.class,e.class)}))+">"+this.contents+"</svg>"}},{key:"toString",value:function(){return this.contents}}]),e}();n.default=c},function(e,n,i){"use strict";var t=o(i(12)),l=o(i(39)),r=o(i(38));function o(e){return e&&e.__esModule?e:{default:e}}e.exports={icons:t.default,toSvg:l.default,replace:r.default}},function(e,n,i){e.exports=i(0)},function(e,n,i){var t=i(2)("iterator"),l=!1;try{var r=0,o={next:function(){return{done:!!r++}},return:function(){l=!0}};o[t]=function(){return this},Array.from(o,function(){throw 2})}catch(e){}e.exports=function(e,n){if(!n&&!l)return!1;var i=!1;try{var r={};r[t]=function(){return{next:function(){return{done:i=!0}}}},e(r)}catch(e){}return i}},function(e,n,i){var t=i(30),l=i(2)("toStringTag"),r="Arguments"==t(function(){return arguments}());e.exports=function(e){var n,i,o;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(i=function(e,n){try{return e[n]}catch(e){}}(n=Object(e),l))?i:r?t(n):"Object"==(o=t(n))&&"function"==typeof n.callee?"Arguments":o}},function(e,n,i){var t=i(47),l=i(9),r=i(2)("iterator");e.exports=function(e){if(void 0!=e)return e[r]||e["@@iterator"]||l[t(e)]}},function(e,n,i){"use strict";var t=i(18),l=i(7),r=i(10);e.exports=function(e,n,i){var o=t(n);o in e?l.f(e,o,r(0,i)):e[o]=i}},function(e,n,i){var t=i(2),l=i(9),r=t("iterator"),o=Array.prototype;e.exports=function(e){return void 0!==e&&(l.Array===e||o[r]===e)}},function(e,n,i){var t=i(3);e.exports=function(e,n,i,l){try{return l?n(t(i)[0],i[1]):n(i)}catch(n){var r=e.return;throw void 0!==r&&t(r.call(e)),n}}},function(e,n){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,n,i){var t=i(52);e.exports=function(e,n,i){if(t(e),void 0===n)return e;switch(i){case 0:return function(){return e.call(n)};case 1:return function(i){return e.call(n,i)};case 2:return function(i,t){return e.call(n,i,t)};case 3:return function(i,t,l){return e.call(n,i,t,l)}}return function(){return e.apply(n,arguments)}}},function(e,n,i){"use strict";var t=i(53),l=i(24),r=i(51),o=i(50),a=i(27),c=i(49),p=i(48);e.exports=function(e){var n,i,y,h,x=l(e),s="function"==typeof this?this:Array,u=arguments.length,d=u>1?arguments[1]:void 0,f=void 0!==d,g=0,v=p(x);if(f&&(d=t(d,u>2?arguments[2]:void 0,2)),void 0==v||s==Array&&o(v))for(i=new s(n=a(x.length));n>g;g++)c(i,g,f?d(x[g],g):x[g]);else for(h=v.call(x),i=new s;!(y=h.next()).done;g++)c(i,g,f?r(h,d,[y.value,g],!0):y.value);return i.length=g,i}},function(e,n,i){var t=i(32),l=i(54);t({target:"Array",stat:!0,forced:!i(46)(function(e){Array.from(e)})},{from:l})},function(e,n,i){var t=i(6),l=i(3);e.exports=function(e,n){if(l(e),!t(n)&&null!==n)throw TypeError("Can't set "+String(n)+" as a prototype")}},function(e,n,i){var t=i(56);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,n=!1,i={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(i,[]),n=i instanceof Array}catch(e){}return function(i,l){return t(i,l),n?e.call(i,l):i.__proto__=l,i}}():void 0)},function(e,n,i){var t=i(0).document;e.exports=t&&t.documentElement},function(e,n,i){var t=i(28),l=i(13);e.exports=Object.keys||function(e){return t(e,l)}},function(e,n,i){var t=i(8),l=i(7),r=i(3),o=i(59);e.exports=t?Object.defineProperties:function(e,n){r(e);for(var i,t=o(n),a=t.length,c=0;a>c;)l.f(e,i=t[c++],n[i]);return e}},function(e,n,i){var t=i(3),l=i(60),r=i(13),o=i(15),a=i(58),c=i(34),p=i(16)("IE_PROTO"),y=function(){},h=function(){var e,n=c("iframe"),i=r.length;for(n.style.display="none",a.appendChild(n),n.src=String("javascript:"),(e=n.contentWindow.document).open(),e.write("<script>document.F=Object<\/script>"),e.close(),h=e.F;i--;)delete h.prototype[r[i]];return h()};e.exports=Object.create||function(e,n){var i;return null!==e?(y.prototype=t(e),i=new y,y.prototype=null,i[p]=e):i=h(),void 0===n?i:l(i,n)},o[p]=!0},function(e,n,i){var t=i(4);e.exports=!!Object.getOwnPropertySymbols&&!t(function(){return!String(Symbol())})},function(e,n,i){var t=i(4);e.exports=!t(function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype})},function(e,n,i){"use strict";var t=i(26).IteratorPrototype,l=i(61),r=i(10),o=i(23),a=i(9),c=function(){return this};e.exports=function(e,n,i){var p=n+" Iterator";return e.prototype=l(t,{next:r(1,i)}),o(e,p,!1,!0),a[p]=c,e}},function(e,n,i){var t=i(4),l=/#|\.prototype\./,r=function(e,n){var i=a[o(e)];return i==p||i!=c&&("function"==typeof n?t(n):!!n)},o=r.normalize=function(e){return String(e).replace(l,".").toLowerCase()},a=r.data={},c=r.NATIVE="N",p=r.POLYFILL="P";e.exports=r},function(e,n){n.f=Object.getOwnPropertySymbols},function(e,n,i){var t=i(21),l=Math.max,r=Math.min;e.exports=function(e,n){var i=t(e);return i<0?l(i+n,0):r(i,n)}},function(e,n,i){var t=i(14),l=i(27),r=i(67);e.exports=function(e){return function(n,i,o){var a,c=t(n),p=l(c.length),y=r(o,p);if(e&&i!=i){for(;p>y;)if((a=c[y++])!=a)return!0}else for(;p>y;y++)if((e||y in c)&&c[y]===i)return e||y||0;return!e&&-1}}},function(e,n,i){var t=i(28),l=i(13).concat("length","prototype");n.f=Object.getOwnPropertyNames||function(e){return t(e,l)}},function(e,n,i){var t=i(0),l=i(69),r=i(66),o=i(3),a=t.Reflect;e.exports=a&&a.ownKeys||function(e){var n=l.f(o(e)),i=r.f;return i?n.concat(i(e)):n}},function(e,n,i){var t=i(1),l=i(70),r=i(31),o=i(7);e.exports=function(e,n){for(var i=l(n),a=o.f,c=r.f,p=0;p<i.length;p++){var y=i[p];t(e,y)||a(e,y,c(n,y))}}},function(e,n,i){var t=i(4),l=i(30),r="".split;e.exports=t(function(){return!Object("z").propertyIsEnumerable(0)})?function(e){return"String"==l(e)?r.call(e,""):Object(e)}:Object},function(e,n,i){"use strict";var t={}.propertyIsEnumerable,l=Object.getOwnPropertyDescriptor,r=l&&!t.call({1:2},1);n.f=r?function(e){var n=l(this,e);return!!n&&n.enumerable}:t},function(e,n,i){"use strict";var t=i(32),l=i(64),r=i(25),o=i(57),a=i(23),c=i(5),p=i(29),y=i(2),h=i(17),x=i(9),s=i(26),u=s.IteratorPrototype,d=s.BUGGY_SAFARI_ITERATORS,f=y("iterator"),g=function(){return this};e.exports=function(e,n,i,y,s,v,m){l(i,n,y);var w,M,b,z=function(e){if(e===s&&O)return O;if(!d&&e in H)return H[e];switch(e){case"keys":case"values":case"entries":return function(){return new i(this,e)}}return function(){return new i(this)}},A=n+" Iterator",k=!1,H=e.prototype,V=H[f]||H["@@iterator"]||s&&H[s],O=!d&&V||z(s),j="Array"==n&&H.entries||V;if(j&&(w=r(j.call(new e)),u!==Object.prototype&&w.next&&(h||r(w)===u||(o?o(w,u):"function"!=typeof w[f]&&c(w,f,g)),a(w,A,!0,!0),h&&(x[A]=g))),"values"==s&&V&&"values"!==V.name&&(k=!0,O=function(){return V.call(this)}),h&&!m||H[f]===O||c(H,f,O),x[n]=O,s)if(M={values:z("values"),keys:v?O:z("keys"),entries:z("entries")},m)for(b in M)!d&&!k&&b in H||p(H,b,M[b]);else t({target:n,proto:!0,forced:d||k},M);return M}},function(e,n){var i;i=function(){return this}();try{i=i||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(i=window)}e.exports=i},function(e,n,i){var t=i(0),l=i(36),r=t.WeakMap;e.exports="function"==typeof r&&/native code/.test(l.call(r))},function(e,n,i){var t=i(21),l=i(20);e.exports=function(e,n,i){var r,o,a=String(l(e)),c=t(n),p=a.length;return c<0||c>=p?i?"":void 0:(r=a.charCodeAt(c))<55296||r>56319||c+1===p||(o=a.charCodeAt(c+1))<56320||o>57343?i?a.charAt(c):r:i?a.slice(c,c+2):o-56320+(r-55296<<10)+65536}},function(e,n,i){"use strict";var t=i(77),l=i(37),r=i(74),o=l.set,a=l.getterFor("String Iterator");r(String,"String",function(e){o(this,{type:"String Iterator",string:String(e),index:0})},function(){var e,n=a(this),i=n.string,l=n.index;return l>=i.length?{value:void 0,done:!0}:(e=t(i,l,!0),n.index+=e.length,{value:e,done:!1})})},function(e,n,i){i(78),i(55);var t=i(45);e.exports=t.Array.from},function(e,n,i){i(79),e.exports=i(44)}])});
+//# sourceMappingURL=feather.min.js.map \ No newline at end of file
diff --git a/examples/c-collectd/www/dpi/flows.html b/examples/c-collectd/www/dpi/flows.html
new file mode 100644
index 000000000..91fce11b2
--- /dev/null
+++ b/examples/c-collectd/www/dpi/flows.html
@@ -0,0 +1,179 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-collectd/www/dpi/index.html b/examples/c-collectd/www/dpi/index.html
new file mode 100644
index 000000000..0a04c68aa
--- /dev/null
+++ b/examples/c-collectd/www/dpi/index.html
@@ -0,0 +1,425 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link active" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="flows_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="detections_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="confidence_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="breed_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="categories_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="events_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="error_events_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-collectd/www/dpi/jquery-3.js b/examples/c-collectd/www/dpi/jquery-3.js
new file mode 100644
index 000000000..105d00e61
--- /dev/null
+++ b/examples/c-collectd/www/dpi/jquery-3.js
@@ -0,0 +1,4 @@
+/*! jQuery v3.2.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */
+!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/parseXML,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-event/ajax,-effects,-effects/Tween,-effects/animatedSelector",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c<b?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:h,sort:c.sort,splice:c.splice},r.extend=r.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||r.isFunction(g)||(g={}),h===i&&(g=this,h--);h<i;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(r.isPlainObject(d)||(e=Array.isArray(d)))?(e?(e=!1,f=c&&Array.isArray(c)?c:[]):f=c&&r.isPlainObject(c)?c:{},g[b]=r.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},r.extend({expando:"jQuery"+(q+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===r.type(a)},isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=r.type(a);return("number"===b||"string"===b)&&!isNaN(a-parseFloat(a))},isPlainObject:function(a){var b,c;return!(!a||"[object Object]"!==k.call(a))&&(!(b=e(a))||(c=l.call(b,"constructor")&&b.constructor,"function"==typeof c&&m.call(c)===n))},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?j[k.call(a)]||"object":typeof a},globalEval:function(a){p(a)},camelCase:function(a){return a.replace(t,"ms-").replace(u,v)},each:function(a,b){var c,d=0;if(w(a)){for(c=a.length;d<c;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(s,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(w(Object(a))?r.merge(c,"string"==typeof a?[a]:a):h.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:i.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;d<c;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;f<g;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,f=0,h=[];if(w(a))for(d=a.length;f<d;f++)e=b(a[f],f,c),null!=e&&h.push(e);else for(f in a)e=b(a[f],f,c),null!=e&&h.push(e);return g.apply([],h)},guid:1,proxy:function(a,b){var c,d,e;if("string"==typeof b&&(c=a[b],b=a,a=c),r.isFunction(a))return d=f.call(arguments,2),e=function(){return a.apply(b||this,d.concat(f.call(arguments)))},e.guid=a.guid=a.guid||r.guid++,e},now:Date.now,support:o}),"function"==typeof Symbol&&(r.fn[Symbol.iterator]=c[Symbol.iterator]),r.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){j["[object "+b+"]"]=b.toLowerCase()});function w(a){var b=!!a&&"length"in a&&a.length,c=r.type(a);return"function"!==c&&!r.isWindow(a)&&("array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},J="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",K="[\\x20\\t\\r\\n\\f]",L="(?:\\\\.|[\\w-]|[^\0-\\xa0])+",M="\\["+K+"*("+L+")(?:"+K+"*([*^$|!~]?=)"+K+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+L+"))|)"+K+"*\\]",N=":("+L+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",O=new RegExp(K+"+","g"),P=new RegExp("^"+K+"+|((?:^|[^\\\\])(?:\\\\.)*)"+K+"+$","g"),Q=new RegExp("^"+K+"*,"+K+"*"),R=new RegExp("^"+K+"*([>+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="<a id='"+u+"'></a><select id='"+u+"-\r\\' msallowcapture=''><option selected=''></option></select>",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c<b;c+=2)a.push(c);return a}),odd:pa(function(a,b){for(var c=1;c<b;c+=2)a.push(c);return a}),lt:pa(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=ma(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=na(b);function ra(){}ra.prototype=d.filters=d.pseudos,d.setFilters=new ra,g=ga.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){c&&!(e=Q.exec(h))||(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=R.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(P," ")}),h=h.slice(c.length));for(g in d.filter)!(e=V[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?ga.error(a):z(a,i).slice(0)};function sa(a){for(var b=0,c=a.length,d="";b<c;b++)d+=a[b].value;return d}function ta(a,b,c){var d=b.dir,e=b.next,f=e||d,g=c&&"parentNode"===f,h=x++;return b.first?function(b,c,e){while(b=b[d])if(1===b.nodeType||g)return a(b,c,e);return!1}:function(b,c,i){var j,k,l,m=[w,h];if(i){while(b=b[d])if((1===b.nodeType||g)&&a(b,c,i))return!0}else while(b=b[d])if(1===b.nodeType||g)if(l=b[u]||(b[u]={}),k=l[b.uniqueID]||(l[b.uniqueID]={}),e&&e===b.nodeName.toLowerCase())b=b[d]||b;else{if((j=k[f])&&j[0]===w&&j[1]===h)return m[2]=j[2];if(k[f]=m,m[2]=a(b,c,i))return!0}return!1}}function ua(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d<e;d++)ga(a,b[d],c);return c}function wa(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;h<i;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function xa(a,b,c,d,e,f){return d&&!d[u]&&(d=xa(d)),e&&!e[u]&&(e=xa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||va(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:wa(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=wa(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?I(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i<f;i++)if(c=d.relative[a[i].type])m=[ta(ua(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;e<f;e++)if(d.relative[a[e].type])break;return xa(i>1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i<e&&ya(a.slice(i,e)),e<f&&ya(a=a.slice(e)),e<f&&sa(a))}m.push(c)}return ua(m)}function za(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b<d;b++)if(r.contains(e[b],this))return!0}));for(c=this.pushStack([]),b=0;b<d;b++)r.find(a,e[b],c);return d>1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a<c;a++)if(r.contains(this,b[a]))return!0})},closest:function(a,b){var c,d=0,e=this.length,f=[],g="string"!=typeof a&&r(a);if(!A.test(a))for(;d<e;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h<f.length)f[h].apply(c[0],c[1])===!1&&a.stopOnFalse&&(h=f.length,c=!1)}a.memory||(c=!1),b=!1,e&&(f=c?[]:"")},j={add:function(){return f&&(c&&!b&&(h=f.length-1,g.push(c)),function d(b){r.each(b,function(b,c){r.isFunction(c)?a.unique&&j.has(c)||f.push(c):c&&c.length&&"string"!==r.type(c)&&d(c)})}(arguments),c&&!b&&i()),this},remove:function(){return r.each(arguments,function(a,b){var c;while((c=r.inArray(b,f,c))>-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b<f)){if(a=d.apply(h,i),a===c.promise())throw new TypeError("Thenable self-resolution");j=a&&("object"==typeof a||"function"==typeof a)&&a.then,r.isFunction(j)?e?j.call(a,g(f,c,N,e),g(f,c,O,e)):(f++,j.call(a,g(f,c,N,e),g(f,c,O,e),g(f,c,N,c.notifyWith))):(d!==N&&(h=void 0,i=[a]),(e||c.resolveWith)(h,i))}},k=e?j:function(){try{j()}catch(a){r.Deferred.exceptionHook&&r.Deferred.exceptionHook(a,k.stackTrace),b+1>=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a);
+}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S),a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h<i;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},U=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function V(){this.expando=r.expando+V.uid++}V.uid=1,V.prototype={cache:function(a){var b=a[this.expando];return b||(b={},U(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[r.camelCase(b)]=c;else for(d in b)e[r.camelCase(d)]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][r.camelCase(b)]},access:function(a,b,c){return void 0===b||b&&"string"==typeof b&&void 0===c?this.get(a,b):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d=a[this.expando];if(void 0!==d){if(void 0!==b){Array.isArray(b)?b=b.map(r.camelCase):(b=r.camelCase(b),b=b in d?[b]:b.match(L)||[]),c=b.length;while(c--)delete d[b[c]]}(void 0===b||r.isEmptyObject(d))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!r.isEmptyObject(b)}};var W=new V,X=new V,Y=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Z=/[A-Z]/g;function $(a){return"true"===a||"false"!==a&&("null"===a?null:a===+a+""?+a:Y.test(a)?JSON.parse(a):a)}function _(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Z,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c=$(c)}catch(e){}X.set(a,b,c)}else c=void 0;return c}r.extend({hasData:function(a){return X.hasData(a)||W.hasData(a)},data:function(a,b,c){return X.access(a,b,c)},removeData:function(a,b){X.remove(a,b)},_data:function(a,b,c){return W.access(a,b,c)},_removeData:function(a,b){W.remove(a,b)}}),r.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=X.get(f),1===f.nodeType&&!W.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=r.camelCase(d.slice(5)),_(f,d,e[d])));W.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){X.set(this,a)}):T(this,function(b){var c;if(f&&void 0===b){if(c=X.get(f,a),void 0!==c)return c;if(c=_(f,a),void 0!==c)return c}else this.each(function(){X.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?r.queue(this[0],a):void 0===b?this:this.each(function(){var c=r.queue(this,a,b);r._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&r.dequeue(this,a)})},dequeue:function(a){return this.each(function(){r.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=r.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=W.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var aa=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,ba=new RegExp("^(?:([+-])=|)("+aa+")([a-z%]*)$","i"),ca=["Top","Right","Bottom","Left"],da=function(a,b){return a=b||a,"none"===a.style.display||""===a.style.display&&r.contains(a.ownerDocument,a)&&"none"===r.css(a,"display")},ea=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};function fa(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return r.css(a,b,"")},i=h(),j=c&&c[3]||(r.cssNumber[b]?"":"px"),k=(r.cssNumber[b]||"px"!==j&&+i)&&ba.exec(r.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,r.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var ga={};function ha(a){var b,c=a.ownerDocument,d=a.nodeName,e=ga[d];return e?e:(b=c.body.appendChild(c.createElement(d)),e=r.css(b,"display"),b.parentNode.removeChild(b),"none"===e&&(e="block"),ga[d]=e,e)}function ia(a,b){for(var c,d,e=[],f=0,g=a.length;f<g;f++)d=a[f],d.style&&(c=d.style.display,b?("none"===c&&(e[f]=W.get(d,"display")||null,e[f]||(d.style.display="")),""===d.style.display&&da(d)&&(e[f]=ha(d))):"none"!==c&&(e[f]="none",W.set(d,"display",c)));for(f=0;f<g;f++)null!=e[f]&&(a[f].style.display=e[f]);return a}r.fn.extend({show:function(){return ia(this,!0)},hide:function(){return ia(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){da(this)?r(this).show():r(this).hide()})}});var ja=/^(?:checkbox|radio)$/i,ka=/<([a-z][^\/\0>\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c<d;c++)W.set(a[c],"globalEval",!b||W.get(b[c],"globalEval"))}var pa=/<|&#?\w+;/;function qa(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],n=0,o=a.length;n<o;n++)if(f=a[n],f||0===f)if("object"===r.type(f))r.merge(m,f.nodeType?[f]:f);else if(pa.test(f)){g=g||l.appendChild(b.createElement("div")),h=(ka.exec(f)||["",""])[1].toLowerCase(),i=ma[h]||ma._default,g.innerHTML=i[1]+r.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;r.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",n=0;while(f=m[n++])if(d&&r.inArray(f,d)>-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c<arguments.length;c++)i[c]=arguments[c];if(b.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,b)!==!1){h=r.event.handlers.call(this,b,j),c=0;while((f=h[c++])&&!b.isPropagationStopped()){b.currentTarget=f.elem,d=0;while((g=f.handlers[d++])&&!b.isImmediatePropagationStopped())b.rnamespace&&!b.rnamespace.test(g.namespace)||(b.handleObj=g,b.data=g.data,e=((r.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(b.result=e)===!1&&(b.preventDefault(),b.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,b),b.result}},handlers:function(a,b){var c,d,e,f,g,h=[],i=b.delegateCount,j=a.target;if(i&&j.nodeType&&!("click"===a.type&&a.button>=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c<i;c++)d=b[c],e=d.selector+" ",void 0===g[e]&&(g[e]=d.needsContext?r(e,this).index(j)>-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i<b.length&&h.push({elem:j,handlers:b.slice(i)}),h},addProp:function(a,b){Object.defineProperty(r.Event.prototype,a,{enumerable:!0,configurable:!0,get:r.isFunction(b)?function(){if(this.originalEvent)return b(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[a]},set:function(b){Object.defineProperty(this,a,{enumerable:!0,configurable:!0,writable:!0,value:b})}})},fix:function(a){return a[r.expando]?a:new r.Event(a)},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==xa()&&this.focus)return this.focus(),!1},delegateType:"focusin"},blur:{trigger:function(){if(this===xa()&&this.blur)return this.blur(),!1},delegateType:"focusout"},click:{trigger:function(){if("checkbox"===this.type&&this.click&&B(this,"input"))return this.click(),!1},_default:function(a){return B(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}}},r.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c)},r.Event=function(a,b){return this instanceof r.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?va:wa,this.target=a.target&&3===a.target.nodeType?a.target.parentNode:a.target,this.currentTarget=a.currentTarget,this.relatedTarget=a.relatedTarget):this.type=a,b&&r.extend(this,b),this.timeStamp=a&&a.timeStamp||r.now(),void(this[r.expando]=!0)):new r.Event(a,b)},r.Event.prototype={constructor:r.Event,isDefaultPrevented:wa,isPropagationStopped:wa,isImmediatePropagationStopped:wa,isSimulated:!1,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=va,a&&!this.isSimulated&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=va,a&&!this.isSimulated&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=va,a&&!this.isSimulated&&a.stopImmediatePropagation(),this.stopPropagation()}},r.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(a){var b=a.button;return null==a.which&&sa.test(a.type)?null!=a.charCode?a.charCode:a.keyCode:!a.which&&void 0!==b&&ta.test(a.type)?1&b?1:2&b?3:4&b?2:0:a.which}},r.event.addProp),r.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){r.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return e&&(e===d||r.contains(d,e))||(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),r.fn.extend({on:function(a,b,c,d){return ya(this,a,b,c,d)},one:function(a,b,c,d){return ya(this,a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,r(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return b!==!1&&"function"!=typeof b||(c=b,b=void 0),c===!1&&(c=wa),this.each(function(){r.event.remove(this,a,c,b)})}});var za=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/<script|<style|<link/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,Ca=/^true\/(.*)/,Da=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c<d;c++)r.event.add(b,e,j[e][c])}X.hasData(a)&&(h=X.access(a),i=r.extend({},h),X.set(b,i))}}function Ia(a,b){var c=b.nodeName.toLowerCase();"input"===c&&ja.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function Ja(a,b,c,d){b=g.apply([],b);var e,f,h,i,j,k,l=0,m=a.length,n=m-1,q=b[0],s=r.isFunction(q);if(s||m>1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l<m;l++)j=e,l!==n&&(j=r.clone(j,!0,!0),i&&r.merge(h,na(j,"script"))),c.call(a[l],j,l);if(i)for(k=h[h.length-1].ownerDocument,r.map(h,Ga),l=0;l<i;l++)j=h[l],la.test(j.type||"")&&!W.access(j,"globalEval")&&r.contains(k,j)&&(j.src?r._evalUrl&&r._evalUrl(j.src):p(j.textContent.replace(Da,""),k))}return a}function Ka(a,b,c){for(var d,e=b?r.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||r.cleanData(na(d)),d.parentNode&&(c&&r.contains(d.ownerDocument,d)&&oa(na(d,"script")),d.parentNode.removeChild(d));return a}r.extend({htmlPrefilter:function(a){return a.replace(za,"<$1></$2>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d<e;d++)Ia(f[d],g[d]);if(b)if(c)for(f=f||na(a),g=g||na(h),d=0,e=f.length;d<e;d++)Ha(f[d],g[d]);else Ha(a,h);return g=na(h,"script"),g.length>0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c<d;c++)b=this[c]||{},1===b.nodeType&&(r.cleanData(na(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ja(this,arguments,function(b){var c=this.parentNode;r.inArray(this,a)<0&&(r.cleanData(na(this)),c&&c.replaceChild(b,this))},a)}}),r.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){r.fn[a]=function(a){for(var c,d=[],e=r(a),f=e.length-1,g=0;g<=f;g++)c=g===f?this:this.clone(!0),r(e[g])[b](c),h.apply(d,c.get());return this.pushStack(d)}});var La=/^margin/,Ma=new RegExp("^("+aa+")(?!px)[a-z%]+$","i"),Na=function(b){var c=b.ownerDocument.defaultView;return c&&c.opener||(c=a),c.getComputedStyle(b)};!function(){function b(){if(i){i.style.cssText="box-sizing:border-box;position:relative;display:block;margin:auto;border:1px;padding:1px;top:1%;width:50%",i.innerHTML="",ra.appendChild(h);var b=a.getComputedStyle(i);c="1%"!==b.top,g="2px"===b.marginLeft,e="4px"===b.width,i.style.marginRight="50%",f="4px"===b.marginRight,ra.removeChild(h),i=null}}var c,e,f,g,h=d.createElement("div"),i=d.createElement("div");i.style&&(i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",o.clearCloneStyle="content-box"===i.style.backgroundClip,h.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",h.appendChild(i),r.extend(o,{pixelPosition:function(){return b(),c},boxSizingReliable:function(){return b(),e},pixelMarginRight:function(){return b(),f},reliableMarginLeft:function(){return b(),g}}))}();function Oa(a,b,c){var d,e,f,g,h=a.style;return c=c||Na(a),c&&(g=c.getPropertyValue(b)||c[b],""!==g||r.contains(a.ownerDocument,a)||(g=r.style(a,b)),!o.pixelMarginRight()&&Ma.test(g)&&La.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function Pa(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}var Qa=/^(none|table(?!-c[ea]).+)/,Ra=/^--/,Sa={position:"absolute",visibility:"hidden",display:"block"},Ta={letterSpacing:"0",fontWeight:"400"},Ua=["Webkit","Moz","ms"],Va=d.createElement("div").style;function Wa(a){if(a in Va)return a;var b=a[0].toUpperCase()+a.slice(1),c=Ua.length;while(c--)if(a=Ua[c]+b,a in Va)return a}function Xa(a){var b=r.cssProps[a];return b||(b=r.cssProps[a]=Wa(a)||a),b}function Ya(a,b,c){var d=ba.exec(b);return d?Math.max(0,d[2]-(c||0))+(d[3]||"px"):b}function Za(a,b,c,d,e){var f,g=0;for(f=c===(d?"border":"content")?4:"width"===b?1:0;f<4;f+=2)"margin"===c&&(g+=r.css(a,c+ca[f],!0,e)),d?("content"===c&&(g-=r.css(a,"padding"+ca[f],!0,e)),"margin"!==c&&(g-=r.css(a,"border"+ca[f]+"Width",!0,e))):(g+=r.css(a,"padding"+ca[f],!0,e),"padding"!==c&&(g+=r.css(a,"border"+ca[f]+"Width",!0,e)));return g}function $a(a,b,c){var d,e=Na(a),f=Oa(a,b,e),g="border-box"===r.css(a,"boxSizing",!1,e);return Ma.test(f)?f:(d=g&&(o.boxSizingReliable()||f===a.style[b]),"auto"===f&&(f=a["offset"+b[0].toUpperCase()+b.slice(1)]),f=parseFloat(f)||0,f+Za(a,b,c||(g?"border":"content"),d,e)+"px")}r.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=Oa(a,"opacity");return""===c?"1":c}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=r.camelCase(b),i=Ra.test(b),j=a.style;return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:j[b]:(f=typeof c,"string"===f&&(e=ba.exec(c))&&e[1]&&(c=fa(a,b,e),f="number"),null!=c&&c===c&&("number"===f&&(c+=e&&e[3]||(r.cssNumber[h]?"":"px")),o.clearCloneStyle||""!==c||0!==b.indexOf("background")||(j[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i?j.setProperty(b,c):j[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=r.camelCase(b),i=Ra.test(b);return i||(b=Xa(h)),g=r.cssHooks[b]||r.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=Oa(a,b,d)),"normal"===e&&b in Ta&&(e=Ta[b]),""===c||c?(f=parseFloat(e),c===!0||isFinite(f)?f||0:e):e}}),r.each(["height","width"],function(a,b){r.cssHooks[b]={get:function(a,c,d){if(c)return!Qa.test(r.css(a,"display"))||a.getClientRects().length&&a.getBoundingClientRect().width?$a(a,b,d):ea(a,Sa,function(){return $a(a,b,d)})},set:function(a,c,d){var e,f=d&&Na(a),g=d&&Za(a,b,d,"border-box"===r.css(a,"boxSizing",!1,f),f);return g&&(e=ba.exec(c))&&"px"!==(e[3]||"px")&&(a.style[b]=c,c=r.css(a,b)),Ya(a,c,g)}}}),r.cssHooks.marginLeft=Pa(o.reliableMarginLeft,function(a,b){if(b)return(parseFloat(Oa(a,"marginLeft"))||a.getBoundingClientRect().left-ea(a,{marginLeft:0},function(){return a.getBoundingClientRect().left}))+"px"}),r.each({margin:"",padding:"",border:"Width"},function(a,b){r.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];d<4;d++)e[a+ca[d]+b]=f[d]||f[d-2]||f[0];return e}},La.test(a)||(r.cssHooks[a+b].set=Ya)}),r.fn.extend({css:function(a,b){return T(this,function(a,b,c){var d,e,f={},g=0;if(Array.isArray(b)){for(d=Na(a),e=b.length;g<e;g++)f[b[g]]=r.css(a,b[g],!1,d);return f}return void 0!==c?r.style(a,b,c):r.css(a,b)},a,b,arguments.length>1)}}),r.fn.delay=function(b,c){return b=r.fx?r.fx.speeds[b]||b:b,c=c||"fx",this.queue(c,function(c,d){var e=a.setTimeout(c,b);d.stop=function(){a.clearTimeout(e)}})},function(){var a=d.createElement("input"),b=d.createElement("select"),c=b.appendChild(d.createElement("option"));a.type="checkbox",o.checkOn=""!==a.value,o.optSelected=c.selected,a=d.createElement("input"),a.value="t",a.type="radio",o.radioValue="t"===a.value}();var _a,ab=r.expr.attrHandle;r.fn.extend({attr:function(a,b){return T(this,r.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?_a:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),_a={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=ab[b]||r.find.attr;ab[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=ab[g],ab[g]=e,e=null!=c(a,b,d)?g:null,ab[g]=f),e}});var bb=/^(?:input|select|textarea|button)$/i,cb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function db(a){var b=a.match(L)||[];return b.join(" ")}function eb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,eb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=eb(c),d=1===c.nodeType&&" "+db(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=db(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,eb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=eb(c),d=1===c.nodeType&&" "+db(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=db(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,eb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=eb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+db(eb(c))+" ").indexOf(b)>-1)return!0;return!1}});var fb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(fb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:db(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d<i;d++)if(c=e[d],(c.selected||d===f)&&!c.disabled&&(!c.parentNode.disabled||!B(c.parentNode,"optgroup"))){if(b=r(c).val(),g)return b;h.push(b)}return h},set:function(a,b){var c,d,e=a.options,f=r.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=r.inArray(r.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var gb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!gb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,gb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var hb=/\[\]$/,ib=/\r?\n/g,jb=/^(?:submit|button|image|reset|file)$/i,kb=/^(?:input|select|textarea|keygen)/i;function lb(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||hb.test(a)?d(a,e):lb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d);
+});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)lb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)lb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&kb.test(this.nodeName)&&!jb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ib,"\r\n")}}):{name:b.name,value:c.replace(ib,"\r\n")}}).get()}}),r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},o.createHTMLDocument=function(){var a=d.implementation.createHTMLDocument("").body;return a.innerHTML="<form></form><form></form>",2===a.childNodes.length}(),r.parseHTML=function(a,b,c){if("string"!=typeof a)return[];"boolean"==typeof b&&(c=b,b=!1);var e,f,g;return b||(o.createHTMLDocument?(b=d.implementation.createHTMLDocument(""),e=b.createElement("base"),e.href=d.location.href,b.head.appendChild(e)):b=d),f=C.exec(a),g=!c&&[],f?[b.createElement(f[1])]:(f=qa([a],b,g),g&&g.length&&r(g).remove(),r.merge([],f.childNodes))},r.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=r.css(a,"position"),l=r(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=r.css(a,"top"),i=r.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),r.isFunction(b)&&(b=b.call(a,c,r.extend({},h))),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},r.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){r.offset.setOffset(this,a,b)});var b,c,d,e,f=this[0];if(f)return f.getClientRects().length?(d=f.getBoundingClientRect(),b=f.ownerDocument,c=b.documentElement,e=b.defaultView,{top:d.top+e.pageYOffset-c.clientTop,left:d.left+e.pageXOffset-c.clientLeft}):{top:0,left:0}},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===r.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),B(a[0],"html")||(d=a.offset()),d={top:d.top+r.css(a[0],"borderTopWidth",!0),left:d.left+r.css(a[0],"borderLeftWidth",!0)}),{top:b.top-d.top-r.css(c,"marginTop",!0),left:b.left-d.left-r.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent;while(a&&"static"===r.css(a,"position"))a=a.offsetParent;return a||ra})}}),r.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,b){var c="pageYOffset"===b;r.fn[a]=function(d){return T(this,function(a,d,e){var f;return r.isWindow(a)?f=a:9===a.nodeType&&(f=a.defaultView),void 0===e?f?f[b]:a[d]:void(f?f.scrollTo(c?f.pageXOffset:e,c?e:f.pageYOffset):a[d]=e)},a,d,arguments.length)}}),r.each(["top","left"],function(a,b){r.cssHooks[b]=Pa(o.pixelPosition,function(a,c){if(c)return c=Oa(a,b),Ma.test(c)?r(a).position()[b]+"px":c})}),r.each({Height:"height",Width:"width"},function(a,b){r.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){r.fn[d]=function(e,f){var g=arguments.length&&(c||"boolean"!=typeof e),h=c||(e===!0||f===!0?"margin":"border");return T(this,function(b,c,e){var f;return r.isWindow(b)?0===d.indexOf("outer")?b["inner"+a]:b.document.documentElement["client"+a]:9===b.nodeType?(f=b.documentElement,Math.max(b.body["scroll"+a],f["scroll"+a],b.body["offset"+a],f["offset"+a],f["client"+a])):void 0===e?r.css(b,c,h):r.style(b,c,e,h)},b,g?e:void 0,g)}})}),r.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),r.holdReady=function(a){a?r.readyWait++:r.ready(!0)},r.isArray=Array.isArray,r.parseJSON=JSON.parse,r.nodeName=B,"function"==typeof define&&define.amd&&define("jquery",[],function(){return r});var mb=a.jQuery,nb=a.$;return r.noConflict=function(b){return a.$===r&&(a.$=nb),b&&a.jQuery===r&&(a.jQuery=mb),r},b||(a.jQuery=a.$=r),r});
diff --git a/examples/c-collectd/www/dpi/jsons.html b/examples/c-collectd/www/dpi/jsons.html
new file mode 100644
index 000000000..4381c68c2
--- /dev/null
+++ b/examples/c-collectd/www/dpi/jsons.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_lines_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="json_bytes_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-collectd/www/dpi/other.html b/examples/c-collectd/www/dpi/other.html
new file mode 100644
index 000000000..2ba9e060c
--- /dev/null
+++ b/examples/c-collectd/www/dpi/other.html
@@ -0,0 +1,217 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="traffic_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer3_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="layer4_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-collectd/www/dpi/popper.js b/examples/c-collectd/www/dpi/popper.js
new file mode 100644
index 000000000..0f20d2a89
--- /dev/null
+++ b/examples/c-collectd/www/dpi/popper.js
@@ -0,0 +1,5 @@
+/*
+ Copyright (C) Federico Zivolo 2017
+ Distributed under the MIT License (license terms are at http://opensource.org/licenses/MIT).
+ */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=getComputedStyle(e,null);return t?o[t]:o}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll)/.test(r+s+p)?e:n(o(e))}function r(e){var o=e&&e.offsetParent,i=o&&o.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TD','TABLE'].indexOf(o.nodeName)&&'static'===t(o,'position')?r(o):o:e?e.ownerDocument.documentElement:document.documentElement}function p(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||r(e.firstElementChild)===e)}function s(e){return null===e.parentNode?e:s(e.parentNode)}function d(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,i=o?e:t,n=o?t:e,a=document.createRange();a.setStart(i,0),a.setEnd(n,0);var l=a.commonAncestorContainer;if(e!==l&&t!==l||i.contains(n))return p(l)?l:r(l);var f=s(e);return f.host?d(f.host,t):d(e,s(t).host)}function a(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:'top',o='top'===t?'scrollTop':'scrollLeft',i=e.nodeName;if('BODY'===i||'HTML'===i){var n=e.ownerDocument.documentElement,r=e.ownerDocument.scrollingElement||n;return r[o]}return e[o]}function l(e,t){var o=2<arguments.length&&void 0!==arguments[2]&&arguments[2],i=a(t,'top'),n=a(t,'left'),r=o?-1:1;return e.top+=i*r,e.bottom+=i*r,e.left+=n*r,e.right+=n*r,e}function f(e,t){var o='x'===t?'Left':'Top',i='Left'==o?'Right':'Bottom';return parseFloat(e['border'+o+'Width'],10)+parseFloat(e['border'+i+'Width'],10)}function m(e,t,o,i){return J(t['offset'+e],t['scroll'+e],o['client'+e],o['offset'+e],o['scroll'+e],ie()?o['offset'+e]+i['margin'+('Height'===e?'Top':'Left')]+i['margin'+('Height'===e?'Bottom':'Right')]:0)}function h(){var e=document.body,t=document.documentElement,o=ie()&&getComputedStyle(t);return{height:m('Height',e,t,o),width:m('Width',e,t,o)}}function c(e){return se({},e,{right:e.left+e.width,bottom:e.top+e.height})}function g(e){var o={};if(ie())try{o=e.getBoundingClientRect();var i=a(e,'top'),n=a(e,'left');o.top+=i,o.left+=n,o.bottom+=i,o.right+=n}catch(e){}else o=e.getBoundingClientRect();var r={left:o.left,top:o.top,width:o.right-o.left,height:o.bottom-o.top},p='HTML'===e.nodeName?h():{},s=p.width||e.clientWidth||r.right-r.left,d=p.height||e.clientHeight||r.bottom-r.top,l=e.offsetWidth-s,m=e.offsetHeight-d;if(l||m){var g=t(e);l-=f(g,'x'),m-=f(g,'y'),r.width-=l,r.height-=m}return c(r)}function u(e,o){var i=ie(),r='HTML'===o.nodeName,p=g(e),s=g(o),d=n(e),a=t(o),f=parseFloat(a.borderTopWidth,10),m=parseFloat(a.borderLeftWidth,10),h=c({top:p.top-s.top-f,left:p.left-s.left-m,width:p.width,height:p.height});if(h.marginTop=0,h.marginLeft=0,!i&&r){var u=parseFloat(a.marginTop,10),b=parseFloat(a.marginLeft,10);h.top-=f-u,h.bottom-=f-u,h.left-=m-b,h.right-=m-b,h.marginTop=u,h.marginLeft=b}return(i?o.contains(d):o===d&&'BODY'!==d.nodeName)&&(h=l(h,o)),h}function b(e){var t=e.ownerDocument.documentElement,o=u(e,t),i=J(t.clientWidth,window.innerWidth||0),n=J(t.clientHeight,window.innerHeight||0),r=a(t),p=a(t,'left'),s={top:r-o.top+o.marginTop,left:p-o.left+o.marginLeft,width:i,height:n};return c(s)}function w(e){var i=e.nodeName;return'BODY'===i||'HTML'===i?!1:'fixed'===t(e,'position')||w(o(e))}function y(e,t,i,r){var p={top:0,left:0},s=d(e,t);if('viewport'===r)p=b(s);else{var a;'scrollParent'===r?(a=n(o(t)),'BODY'===a.nodeName&&(a=e.ownerDocument.documentElement)):'window'===r?a=e.ownerDocument.documentElement:a=r;var l=u(a,s);if('HTML'===a.nodeName&&!w(s)){var f=h(),m=f.height,c=f.width;p.top+=l.top-l.marginTop,p.bottom=m+l.top,p.left+=l.left-l.marginLeft,p.right=c+l.left}else p=l}return p.left+=i,p.top+=i,p.right-=i,p.bottom-=i,p}function E(e){var t=e.width,o=e.height;return t*o}function v(e,t,o,i,n){var r=5<arguments.length&&void 0!==arguments[5]?arguments[5]:0;if(-1===e.indexOf('auto'))return e;var p=y(o,i,r,n),s={top:{width:p.width,height:t.top-p.top},right:{width:p.right-t.right,height:p.height},bottom:{width:p.width,height:p.bottom-t.bottom},left:{width:t.left-p.left,height:p.height}},d=Object.keys(s).map(function(e){return se({key:e},s[e],{area:E(s[e])})}).sort(function(e,t){return t.area-e.area}),a=d.filter(function(e){var t=e.width,i=e.height;return t>=o.clientWidth&&i>=o.clientHeight}),l=0<a.length?a[0].key:d[0].key,f=e.split('-')[1];return l+(f?'-'+f:'')}function O(e,t,o){var i=d(t,o);return u(o,i)}function L(e){var t=getComputedStyle(e),o=parseFloat(t.marginTop)+parseFloat(t.marginBottom),i=parseFloat(t.marginLeft)+parseFloat(t.marginRight),n={width:e.offsetWidth+i,height:e.offsetHeight+o};return n}function x(e){var t={left:'right',right:'left',bottom:'top',top:'bottom'};return e.replace(/left|right|bottom|top/g,function(e){return t[e]})}function S(e,t,o){o=o.split('-')[0];var i=L(e),n={width:i.width,height:i.height},r=-1!==['right','left'].indexOf(o),p=r?'top':'left',s=r?'left':'top',d=r?'height':'width',a=r?'width':'height';return n[p]=t[p]+t[d]/2-i[d]/2,n[s]=o===s?t[s]-i[a]:t[x(s)],n}function T(e,t){return Array.prototype.find?e.find(t):e.filter(t)[0]}function D(e,t,o){if(Array.prototype.findIndex)return e.findIndex(function(e){return e[t]===o});var i=T(e,function(e){return e[t]===o});return e.indexOf(i)}function C(t,o,i){var n=void 0===i?t:t.slice(0,D(t,'name',i));return n.forEach(function(t){t['function']&&console.warn('`modifier.function` is deprecated, use `modifier.fn`!');var i=t['function']||t.fn;t.enabled&&e(i)&&(o.offsets.popper=c(o.offsets.popper),o.offsets.reference=c(o.offsets.reference),o=i(o,t))}),o}function N(){if(!this.state.isDestroyed){var e={instance:this,styles:{},arrowStyles:{},attributes:{},flipped:!1,offsets:{}};e.offsets.reference=O(this.state,this.popper,this.reference),e.placement=v(this.options.placement,e.offsets.reference,this.popper,this.reference,this.options.modifiers.flip.boundariesElement,this.options.modifiers.flip.padding),e.originalPlacement=e.placement,e.offsets.popper=S(this.popper,e.offsets.reference,e.placement),e.offsets.popper.position='absolute',e=C(this.modifiers,e),this.state.isCreated?this.options.onUpdate(e):(this.state.isCreated=!0,this.options.onCreate(e))}}function k(e,t){return e.some(function(e){var o=e.name,i=e.enabled;return i&&o===t})}function W(e){for(var t=[!1,'ms','Webkit','Moz','O'],o=e.charAt(0).toUpperCase()+e.slice(1),n=0;n<t.length-1;n++){var i=t[n],r=i?''+i+o:e;if('undefined'!=typeof document.body.style[r])return r}return null}function P(){return this.state.isDestroyed=!0,k(this.modifiers,'applyStyle')&&(this.popper.removeAttribute('x-placement'),this.popper.style.left='',this.popper.style.position='',this.popper.style.top='',this.popper.style[W('transform')]=''),this.disableEventListeners(),this.options.removeOnDestroy&&this.popper.parentNode.removeChild(this.popper),this}function B(e){var t=e.ownerDocument;return t?t.defaultView:window}function H(e,t,o,i){var r='BODY'===e.nodeName,p=r?e.ownerDocument.defaultView:e;p.addEventListener(t,o,{passive:!0}),r||H(n(p.parentNode),t,o,i),i.push(p)}function A(e,t,o,i){o.updateBound=i,B(e).addEventListener('resize',o.updateBound,{passive:!0});var r=n(e);return H(r,'scroll',o.updateBound,o.scrollParents),o.scrollElement=r,o.eventsEnabled=!0,o}function I(){this.state.eventsEnabled||(this.state=A(this.reference,this.options,this.state,this.scheduleUpdate))}function M(e,t){return B(e).removeEventListener('resize',t.updateBound),t.scrollParents.forEach(function(e){e.removeEventListener('scroll',t.updateBound)}),t.updateBound=null,t.scrollParents=[],t.scrollElement=null,t.eventsEnabled=!1,t}function R(){this.state.eventsEnabled&&(cancelAnimationFrame(this.scheduleUpdate),this.state=M(this.reference,this.state))}function U(e){return''!==e&&!isNaN(parseFloat(e))&&isFinite(e)}function Y(e,t){Object.keys(t).forEach(function(o){var i='';-1!==['width','height','top','right','bottom','left'].indexOf(o)&&U(t[o])&&(i='px'),e.style[o]=t[o]+i})}function j(e,t){Object.keys(t).forEach(function(o){var i=t[o];!1===i?e.removeAttribute(o):e.setAttribute(o,t[o])})}function F(e,t,o){var i=T(e,function(e){var o=e.name;return o===t}),n=!!i&&e.some(function(e){return e.name===o&&e.enabled&&e.order<i.order});if(!n){var r='`'+t+'`';console.warn('`'+o+'`'+' modifier is required by '+r+' modifier in order to work, be sure to include it before '+r+'!')}return n}function K(e){return'end'===e?'start':'start'===e?'end':e}function q(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],o=ae.indexOf(e),i=ae.slice(o+1).concat(ae.slice(0,o));return t?i.reverse():i}function V(e,t,o,i){var n=e.match(/((?:\-|\+)?\d*\.?\d*)(.*)/),r=+n[1],p=n[2];if(!r)return e;if(0===p.indexOf('%')){var s;switch(p){case'%p':s=o;break;case'%':case'%r':default:s=i;}var d=c(s);return d[t]/100*r}if('vh'===p||'vw'===p){var a;return a='vh'===p?J(document.documentElement.clientHeight,window.innerHeight||0):J(document.documentElement.clientWidth,window.innerWidth||0),a/100*r}return r}function z(e,t,o,i){var n=[0,0],r=-1!==['right','left'].indexOf(i),p=e.split(/(\+|\-)/).map(function(e){return e.trim()}),s=p.indexOf(T(p,function(e){return-1!==e.search(/,|\s/)}));p[s]&&-1===p[s].indexOf(',')&&console.warn('Offsets separated by white space(s) are deprecated, use a comma (,) instead.');var d=/\s*,\s*|\s+/,a=-1===s?[p]:[p.slice(0,s).concat([p[s].split(d)[0]]),[p[s].split(d)[1]].concat(p.slice(s+1))];return a=a.map(function(e,i){var n=(1===i?!r:r)?'height':'width',p=!1;return e.reduce(function(e,t){return''===e[e.length-1]&&-1!==['+','-'].indexOf(t)?(e[e.length-1]=t,p=!0,e):p?(e[e.length-1]+=t,p=!1,e):e.concat(t)},[]).map(function(e){return V(e,n,t,o)})}),a.forEach(function(e,t){e.forEach(function(o,i){U(o)&&(n[t]+=o*('-'===e[i-1]?-1:1))})}),n}function G(e,t){var o,i=t.offset,n=e.placement,r=e.offsets,p=r.popper,s=r.reference,d=n.split('-')[0];return o=U(+i)?[+i,0]:z(i,p,s,d),'left'===d?(p.top+=o[0],p.left-=o[1]):'right'===d?(p.top+=o[0],p.left+=o[1]):'top'===d?(p.left+=o[0],p.top-=o[1]):'bottom'===d&&(p.left+=o[0],p.top+=o[1]),e.popper=p,e}for(var _=Math.min,X=Math.floor,J=Math.max,Q='undefined'!=typeof window&&'undefined'!=typeof document,Z=['Edge','Trident','Firefox'],$=0,ee=0;ee<Z.length;ee+=1)if(Q&&0<=navigator.userAgent.indexOf(Z[ee])){$=1;break}var i,te=Q&&window.Promise,oe=te?function(e){var t=!1;return function(){t||(t=!0,window.Promise.resolve().then(function(){t=!1,e()}))}}:function(e){var t=!1;return function(){t||(t=!0,setTimeout(function(){t=!1,e()},$))}},ie=function(){return void 0==i&&(i=-1!==navigator.appVersion.indexOf('MSIE 10')),i},ne=function(e,t){if(!(e instanceof t))throw new TypeError('Cannot call a class as a function')},re=function(){function e(e,t){for(var o,n=0;n<t.length;n++)o=t[n],o.enumerable=o.enumerable||!1,o.configurable=!0,'value'in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}return function(t,o,i){return o&&e(t.prototype,o),i&&e(t,i),t}}(),pe=function(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e},se=Object.assign||function(e){for(var t,o=1;o<arguments.length;o++)for(var i in t=arguments[o],t)Object.prototype.hasOwnProperty.call(t,i)&&(e[i]=t[i]);return e},de=['auto-start','auto','auto-end','top-start','top','top-end','right-start','right','right-end','bottom-end','bottom','bottom-start','left-end','left','left-start'],ae=de.slice(3),le={FLIP:'flip',CLOCKWISE:'clockwise',COUNTERCLOCKWISE:'counterclockwise'},fe=function(){function t(o,i){var n=this,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:{};ne(this,t),this.scheduleUpdate=function(){return requestAnimationFrame(n.update)},this.update=oe(this.update.bind(this)),this.options=se({},t.Defaults,r),this.state={isDestroyed:!1,isCreated:!1,scrollParents:[]},this.reference=o&&o.jquery?o[0]:o,this.popper=i&&i.jquery?i[0]:i,this.options.modifiers={},Object.keys(se({},t.Defaults.modifiers,r.modifiers)).forEach(function(e){n.options.modifiers[e]=se({},t.Defaults.modifiers[e]||{},r.modifiers?r.modifiers[e]:{})}),this.modifiers=Object.keys(this.options.modifiers).map(function(e){return se({name:e},n.options.modifiers[e])}).sort(function(e,t){return e.order-t.order}),this.modifiers.forEach(function(t){t.enabled&&e(t.onLoad)&&t.onLoad(n.reference,n.popper,n.options,t,n.state)}),this.update();var p=this.options.eventsEnabled;p&&this.enableEventListeners(),this.state.eventsEnabled=p}return re(t,[{key:'update',value:function(){return N.call(this)}},{key:'destroy',value:function(){return P.call(this)}},{key:'enableEventListeners',value:function(){return I.call(this)}},{key:'disableEventListeners',value:function(){return R.call(this)}}]),t}();return fe.Utils=('undefined'==typeof window?global:window).PopperUtils,fe.placements=de,fe.Defaults={placement:'bottom',eventsEnabled:!0,removeOnDestroy:!1,onCreate:function(){},onUpdate:function(){},modifiers:{shift:{order:100,enabled:!0,fn:function(e){var t=e.placement,o=t.split('-')[0],i=t.split('-')[1];if(i){var n=e.offsets,r=n.reference,p=n.popper,s=-1!==['bottom','top'].indexOf(o),d=s?'left':'top',a=s?'width':'height',l={start:pe({},d,r[d]),end:pe({},d,r[d]+r[a]-p[a])};e.offsets.popper=se({},p,l[i])}return e}},offset:{order:200,enabled:!0,fn:G,offset:0},preventOverflow:{order:300,enabled:!0,fn:function(e,t){var o=t.boundariesElement||r(e.instance.popper);e.instance.reference===o&&(o=r(o));var i=y(e.instance.popper,e.instance.reference,t.padding,o);t.boundaries=i;var n=t.priority,p=e.offsets.popper,s={primary:function(e){var o=p[e];return p[e]<i[e]&&!t.escapeWithReference&&(o=J(p[e],i[e])),pe({},e,o)},secondary:function(e){var o='right'===e?'left':'top',n=p[o];return p[e]>i[e]&&!t.escapeWithReference&&(n=_(p[o],i[e]-('right'===e?p.width:p.height))),pe({},o,n)}};return n.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';p=se({},p,s[t](e))}),e.offsets.popper=p,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,i=t.reference,n=e.placement.split('-')[0],r=X,p=-1!==['top','bottom'].indexOf(n),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]<r(i[d])&&(e.offsets.popper[d]=r(i[d])-o[a]),o[d]>r(i[s])&&(e.offsets.popper[d]=r(i[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var i;if(!F(e.instance.modifiers,'arrow','keepTogether'))return e;var n=o.element;if('string'==typeof n){if(n=e.instance.popper.querySelector(n),!n)return e;}else if(!e.instance.popper.contains(n))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',g=a?'bottom':'right',u=L(n)[l];d[g]-u<s[m]&&(e.offsets.popper[m]-=s[m]-(d[g]-u)),d[m]+u>s[g]&&(e.offsets.popper[m]+=d[m]+u-s[g]),e.offsets.popper=c(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=J(_(s[l]-u,v),0),e.arrowElement=n,e.offsets.arrow=(i={},pe(i,m,Math.round(v)),pe(i,h,''),i),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(k(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=y(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement),i=e.placement.split('-')[0],n=x(i),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case le.FLIP:p=[i,n];break;case le.CLOCKWISE:p=q(i);break;case le.COUNTERCLOCKWISE:p=q(i,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(i!==s||p.length===d+1)return e;i=e.placement.split('-')[0],n=x(i);var a=e.offsets.popper,l=e.offsets.reference,f=X,m='left'===i&&f(a.right)>f(l.left)||'right'===i&&f(a.left)<f(l.right)||'top'===i&&f(a.bottom)>f(l.top)||'bottom'===i&&f(a.top)<f(l.bottom),h=f(a.left)<f(o.left),c=f(a.right)>f(o.right),g=f(a.top)<f(o.top),u=f(a.bottom)>f(o.bottom),b='left'===i&&h||'right'===i&&c||'top'===i&&g||'bottom'===i&&u,w=-1!==['top','bottom'].indexOf(i),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u);(m||b||y)&&(e.flipped=!0,(m||b)&&(i=p[d+1]),y&&(r=K(r)),e.placement=i+(r?'-'+r:''),e.offsets.popper=se({},e.offsets.popper,S(e.instance.popper,e.offsets.reference,e.placement)),e=C(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport'},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],i=e.offsets,n=i.popper,r=i.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return n[p?'left':'top']=r[o]-(s?n[p?'width':'height']:0),e.placement=x(t),e.offsets.popper=c(n),e}},hide:{order:800,enabled:!0,fn:function(e){if(!F(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=T(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottom<o.top||t.left>o.right||t.top>o.bottom||t.right<o.left){if(!0===e.hide)return e;e.hide=!0,e.attributes['x-out-of-boundaries']=''}else{if(!1===e.hide)return e;e.hide=!1,e.attributes['x-out-of-boundaries']=!1}return e}},computeStyle:{order:850,enabled:!0,fn:function(e,t){var o=t.x,i=t.y,n=e.offsets.popper,p=T(e.instance.modifiers,function(e){return'applyStyle'===e.name}).gpuAcceleration;void 0!==p&&console.warn('WARNING: `gpuAcceleration` option moved to `computeStyle` modifier and will not be supported in future versions of Popper.js!');var s,d,a=void 0===p?t.gpuAcceleration:p,l=r(e.instance.popper),f=g(l),m={position:n.position},h={left:X(n.left),top:X(n.top),bottom:X(n.bottom),right:X(n.right)},c='bottom'===o?'top':'bottom',u='right'===i?'left':'right',b=W('transform');if(d='bottom'==c?-f.height+h.bottom:h.top,s='right'==u?-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[u]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==u?-1:1;m[c]=d*w,m[u]=s*y,m.willChange=c+', '+u}var E={"x-placement":e.placement};return e.attributes=se({},E,e.attributes),e.styles=se({},m,e.styles),e.arrowStyles=se({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return Y(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&Y(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,i,n){var r=O(n,t,e),p=v(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),Y(t,{position:'absolute'}),o},gpuAcceleration:void 0}}},fe});
+//# sourceMappingURL=popper.min.js.map
diff --git a/examples/c-collectd/www/dpi/risks.html b/examples/c-collectd/www/dpi/risks.html
new file mode 100644
index 000000000..f8f6bf9c8
--- /dev/null
+++ b/examples/c-collectd/www/dpi/risks.html
@@ -0,0 +1,198 @@
+<!DOCTYPE html>
+<html lang="en"><head>
+ <meta http-equiv="cache-control" content="max-age=0" />
+ <meta http-equiv="cache-control" content="no-cache" />
+ <meta http-equiv="expires" content="0" />
+ <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
+ <meta http-equiv="pragma" content="no-cache" />
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <meta name="description" content="nDPId RRD Graph">
+ <meta name="author" content="Toni Uhlig">
+ <link rel="icon" href="https://getbootstrap.com/docs/4.0/assets/img/favicons/favicon.ico">
+
+ <title>nDPId Dashboard</title>
+
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/dashboard/">
+
+ <!-- Bootstrap core CSS -->
+ <link href="bootstrap.css" rel="stylesheet">
+
+ <!-- Custom styles for this template -->
+ <link href="dashboard.css" rel="stylesheet">
+ </head>
+
+ <body>
+ <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
+ <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="https://github.com/utoni/nDPId">nDPId Collectd RRD Graph</a>
+ </nav>
+
+ <div class="container-fluid">
+ <div class="row">
+ <nav class="col-md-2 d-none d-md-block bg-light sidebar">
+ <div class="sidebar-sticky">
+
+ <h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
+ <span>Graphs</span>
+ </h6>
+
+ <ul class="nav flex-column mb-2">
+ <li class="nav-item">
+ <a class="nav-link" href="index.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Home
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="flows.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Flows
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="other.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Other
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="detections.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Detections
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="categories.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Categories
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link active" href="risks.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Risks
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="jsons.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ JSONs
+ </a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" href="events.html">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file-text">
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
+ <polyline points="14 2 14 8 20 8"></polyline>
+ <line x1="16" y1="13" x2="8" y2="13"></line>
+ <line x1="16" y1="17" x2="8" y2="17"></line>
+ <polyline points="10 9 9 9 8 9"></polyline>
+ </svg>
+ Events
+ </a>
+ </li>
+ </ul>
+ </div>
+ </nav>
+
+ <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="severities_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_hour.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_12hours.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_day.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_week.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_month.png" class="img-fluid" alt="Responsive image">
+ </div>
+ <div class="d-flex justify-content-center flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
+ <img src="risky_events_past_year.png" class="img-fluid" alt="Responsive image">
+ </div>
+
+ </main>
+ </div>
+ </div>
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="jquery-3.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
+ <script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')</script>
+ <script src="popper.js"></script>
+ <script src="bootstrap.js"></script>
+
+ <!-- Icons -->
+ <script src="feather.js"></script>
+ <script>
+ feather.replace()
+ </script>
+
+</body></html>
diff --git a/examples/c-influxd/c-influxd.c b/examples/c-influxd/c-influxd.c
new file mode 100644
index 000000000..9eabbe461
--- /dev/null
+++ b/examples/c-influxd/c-influxd.c
@@ -0,0 +1,1750 @@
+#include <curl/curl.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/timerfd.h>
+
+#include <ndpi_typedefs.h>
+
+#include "nDPIsrvd.h"
+#include "utils.h"
+
+#define MAX_RISKS_PER_FLOW 8
+#define MAX_SEVERITIES_PER_FLOW 4
+
+static int main_thread_shutdown = 0;
+static int influxd_timerfd = -1;
+
+static char * pidfile = NULL;
+static char * serv_optarg = NULL;
+static char * user = NULL;
+static char * group = NULL;
+static int test_mode = 0;
+static char * influxdb_interval = NULL;
+static nDPIsrvd_ull influxdb_interval_ull = 0uL;
+static char * influxdb_url = NULL;
+static char * influxdb_token = NULL;
+
+struct flow_user_data
+{
+ nDPIsrvd_ull last_flow_src_l4_payload_len;
+ nDPIsrvd_ull last_flow_dst_l4_payload_len;
+ uint8_t risks[MAX_RISKS_PER_FLOW];
+ uint8_t severities[MAX_SEVERITIES_PER_FLOW];
+ uint8_t category;
+ uint8_t breed;
+ uint8_t confidence;
+ // "fallthroughs" if we are not in sync with nDPI
+ uint8_t risk_ndpid_invalid : 1;
+ uint8_t category_ndpid_invalid : 1;
+ uint8_t breed_ndpid_invalid : 1;
+ uint8_t confidence_ndpid_invalid : 1;
+ // detection status
+ uint8_t new_seen : 1;
+ uint8_t is_detected : 1;
+ uint8_t is_guessed : 1;
+ uint8_t is_not_detected : 1;
+ // flow state
+ uint8_t is_info : 1;
+ uint8_t is_finished : 1;
+ // Layer3 / Layer4
+ uint8_t is_ip4 : 1;
+ uint8_t is_ip6 : 1;
+ uint8_t is_other_l3 : 1;
+ uint8_t is_tcp : 1;
+ uint8_t is_udp : 1;
+ uint8_t is_icmp : 1;
+ uint8_t is_other_l4 : 1;
+};
+
+struct influx_ctx
+{
+ CURL * curl;
+ CURLcode last_result;
+ struct curl_slist * http_header;
+};
+
+static struct
+{
+ pthread_mutex_t rw_lock;
+
+ struct
+ {
+ uint64_t json_lines;
+ uint64_t json_bytes;
+
+ uint64_t flow_new_count;
+ uint64_t flow_end_count;
+ uint64_t flow_idle_count;
+ uint64_t flow_update_count;
+ uint64_t flow_analyse_count;
+ uint64_t flow_guessed_count;
+ uint64_t flow_detected_count;
+ uint64_t flow_detection_update_count;
+ uint64_t flow_not_detected_count;
+
+ uint64_t packet_count;
+ uint64_t packet_flow_count;
+
+ uint64_t init_count;
+ uint64_t reconnect_count;
+ uint64_t shutdown_count;
+ uint64_t status_count;
+
+ uint64_t error_unknown_datalink;
+ uint64_t error_unknown_l3_protocol;
+ uint64_t error_unsupported_datalink;
+ uint64_t error_packet_too_short;
+ uint64_t error_packet_type_unknown;
+ uint64_t error_packet_header_invalid;
+ uint64_t error_ip4_packet_too_short;
+ uint64_t error_ip4_size_smaller_than_header;
+ uint64_t error_ip4_l4_payload_detection;
+ uint64_t error_ip6_packet_too_short;
+ uint64_t error_ip6_size_smaller_than_header;
+ uint64_t error_ip6_l4_payload_detection;
+ uint64_t error_tcp_packet_too_short;
+ uint64_t error_udp_packet_too_short;
+ uint64_t error_capture_size_smaller_than_packet;
+ uint64_t error_max_flows_to_track;
+ uint64_t error_flow_memory_alloc;
+
+ uint64_t flow_src_total_bytes;
+ uint64_t flow_dst_total_bytes;
+ uint64_t flow_risky_count;
+ } counters;
+
+ struct
+ {
+ uint64_t flow_state_info;
+ uint64_t flow_state_finished;
+
+ uint64_t flow_breed_safe_count;
+ uint64_t flow_breed_acceptable_count;
+ uint64_t flow_breed_fun_count;
+ uint64_t flow_breed_unsafe_count;
+ uint64_t flow_breed_potentially_dangerous_count;
+ uint64_t flow_breed_tracker_ads_count;
+ uint64_t flow_breed_dangerous_count;
+ uint64_t flow_breed_unrated_count;
+ uint64_t flow_breed_unknown_count;
+
+ uint64_t flow_category_unspecified_count;
+ uint64_t flow_category_media_count;
+ uint64_t flow_category_vpn_count;
+ uint64_t flow_category_email_count;
+ uint64_t flow_category_data_transfer_count;
+ uint64_t flow_category_web_count;
+ uint64_t flow_category_social_network_count;
+ uint64_t flow_category_download_count;
+ uint64_t flow_category_game_count;
+ uint64_t flow_category_chat_count;
+ uint64_t flow_category_voip_count;
+ uint64_t flow_category_database_count;
+ uint64_t flow_category_remote_access_count;
+ uint64_t flow_category_cloud_count;
+ uint64_t flow_category_network_count;
+ uint64_t flow_category_collaborative_count;
+ uint64_t flow_category_rpc_count;
+ uint64_t flow_category_streaming_count;
+ uint64_t flow_category_system_count;
+ uint64_t flow_category_software_update_count;
+ uint64_t flow_category_music_count;
+ uint64_t flow_category_video_count;
+ uint64_t flow_category_shopping_count;
+ uint64_t flow_category_productivity_count;
+ uint64_t flow_category_file_sharing_count;
+ uint64_t flow_category_conn_check_count;
+ uint64_t flow_category_iot_scada_count;
+ uint64_t flow_category_virt_assistant_count;
+ uint64_t flow_category_cybersecurity_count;
+ uint64_t flow_category_adult_content_count;
+ uint64_t flow_category_mining_count;
+ uint64_t flow_category_malware_count;
+ uint64_t flow_category_advertisment_count;
+ uint64_t flow_category_banned_site_count;
+ uint64_t flow_category_site_unavail_count;
+ uint64_t flow_category_allowed_site_count;
+ uint64_t flow_category_antimalware_count;
+ uint64_t flow_category_crypto_currency_count;
+ uint64_t flow_category_gambling_count;
+ uint64_t flow_category_unknown_count;
+
+ uint64_t flow_confidence_by_port;
+ uint64_t flow_confidence_dpi_partial;
+ uint64_t flow_confidence_dpi_partial_cache;
+ uint64_t flow_confidence_dpi_cache;
+ uint64_t flow_confidence_dpi;
+ uint64_t flow_confidence_nbpf;
+ uint64_t flow_confidence_by_ip;
+ uint64_t flow_confidence_dpi_aggressive;
+ uint64_t flow_confidence_custom_rule;
+ uint64_t flow_confidence_unknown;
+
+ uint64_t flow_severity_low;
+ uint64_t flow_severity_medium;
+ uint64_t flow_severity_high;
+ uint64_t flow_severity_severe;
+ uint64_t flow_severity_critical;
+ uint64_t flow_severity_emergency;
+ uint64_t flow_severity_unknown;
+
+ uint64_t flow_l3_ip4_count;
+ uint64_t flow_l3_ip6_count;
+ uint64_t flow_l3_other_count;
+
+ uint64_t flow_l4_tcp_count;
+ uint64_t flow_l4_udp_count;
+ uint64_t flow_l4_icmp_count;
+ uint64_t flow_l4_other_count;
+
+ uint64_t flow_active_count;
+ uint64_t flow_detected_count;
+ uint64_t flow_guessed_count;
+ uint64_t flow_not_detected_count;
+
+ nDPIsrvd_ull flow_risk_count[NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */];
+ nDPIsrvd_ull flow_risk_unknown_count;
+ } gauges[2]; /* values after InfluxDB push: gauges[0] -= gauges[1], gauges[1] is zero'd afterwards */
+} influxd_statistics = {.rw_lock = PTHREAD_MUTEX_INITIALIZER};
+
+struct global_map
+{
+ char const * const json_key;
+ struct
+ {
+ uint64_t * const global_stat_inc;
+ uint64_t * const global_stat_dec;
+ };
+};
+
+#define INFLUXD_STATS_COUNTER_PTR(member) \
+ { \
+ .global_stat_inc = &(influxd_statistics.counters.member), NULL \
+ }
+#define INFLUXD_STATS_GAUGE_PTR(member) \
+ { \
+ .global_stat_inc = &(influxd_statistics.gauges[0].member), \
+ .global_stat_dec = &(influxd_statistics.gauges[1].member) \
+ }
+#define INFLUXD_STATS_COUNTER_INC(member) (influxd_statistics.counters.member++)
+#define INFLUXD_STATS_GAUGE_RES(member) (influxd_statistics.gauges[0].member--)
+#define INFLUXD_STATS_GAUGE_INC(member) (influxd_statistics.gauges[0].member++)
+#define INFLUXD_STATS_GAUGE_DEC(member) (influxd_statistics.gauges[1].member++)
+#define INFLUXD_STATS_GAUGE_SUB(member) (influxd_statistics.gauges[0].member -= influxd_statistics.gauges[1].member)
+#define INFLUXD_STATS_MAP_NOTNULL(map, index) (map[index - 1].global_stat_dec != NULL)
+#define INFLUXD_STATS_MAP_DEC(map, index) ((*map[index - 1].global_stat_dec)++)
+
+static struct global_map const flow_event_map[] = {{"new", INFLUXD_STATS_COUNTER_PTR(flow_new_count)},
+ {"end", INFLUXD_STATS_COUNTER_PTR(flow_end_count)},
+ {"idle", INFLUXD_STATS_COUNTER_PTR(flow_idle_count)},
+ {"update", INFLUXD_STATS_COUNTER_PTR(flow_update_count)},
+ {"analyse", INFLUXD_STATS_COUNTER_PTR(flow_analyse_count)},
+ {"guessed", INFLUXD_STATS_COUNTER_PTR(flow_guessed_count)},
+ {"detected", INFLUXD_STATS_COUNTER_PTR(flow_detected_count)},
+ {"detection-update",
+ INFLUXD_STATS_COUNTER_PTR(flow_detection_update_count)},
+ {"not-detected",
+ INFLUXD_STATS_COUNTER_PTR(flow_not_detected_count)}};
+
+static struct global_map const packet_event_map[] = {{"packet", INFLUXD_STATS_COUNTER_PTR(packet_count)},
+ {"packet-flow", INFLUXD_STATS_COUNTER_PTR(packet_flow_count)}};
+
+static struct global_map const daemon_event_map[] = {{"init", INFLUXD_STATS_COUNTER_PTR(init_count)},
+ {"reconnect", INFLUXD_STATS_COUNTER_PTR(reconnect_count)},
+ {"shutdown", INFLUXD_STATS_COUNTER_PTR(shutdown_count)},
+ {"status", INFLUXD_STATS_COUNTER_PTR(status_count)}};
+
+static struct global_map const error_event_map[] = {
+ {"Unknown datalink layer packet", INFLUXD_STATS_COUNTER_PTR(error_unknown_datalink)},
+ {"Unknown L3 protocol", INFLUXD_STATS_COUNTER_PTR(error_unknown_l3_protocol)},
+ {"Unsupported datalink layer", INFLUXD_STATS_COUNTER_PTR(error_unsupported_datalink)},
+ {"Packet too short", INFLUXD_STATS_COUNTER_PTR(error_packet_too_short)},
+ {"Unknown packet type", INFLUXD_STATS_COUNTER_PTR(error_packet_type_unknown)},
+ {"Packet header invalid", INFLUXD_STATS_COUNTER_PTR(error_packet_header_invalid)},
+ {"IP4 packet too short", INFLUXD_STATS_COUNTER_PTR(error_ip4_packet_too_short)},
+ {"Packet smaller than IP4 header", INFLUXD_STATS_COUNTER_PTR(error_ip4_size_smaller_than_header)},
+ {"nDPI IPv4\\/L4 payload detection failed", INFLUXD_STATS_COUNTER_PTR(error_ip4_l4_payload_detection)},
+ {"IP6 packet too short", INFLUXD_STATS_COUNTER_PTR(error_ip6_packet_too_short)},
+ {"Packet smaller than IP6 header", INFLUXD_STATS_COUNTER_PTR(error_ip6_size_smaller_than_header)},
+ {"nDPI IPv6\\/L4 payload detection failed", INFLUXD_STATS_COUNTER_PTR(error_ip6_l4_payload_detection)},
+ {"TCP packet smaller than expected", INFLUXD_STATS_COUNTER_PTR(error_tcp_packet_too_short)},
+ {"UDP packet smaller than expected", INFLUXD_STATS_COUNTER_PTR(error_udp_packet_too_short)},
+ {"Captured packet size is smaller than expected packet size",
+ INFLUXD_STATS_COUNTER_PTR(error_capture_size_smaller_than_packet)},
+ {"Max flows to track reached", INFLUXD_STATS_COUNTER_PTR(error_max_flows_to_track)},
+ {"Flow memory allocation failed", INFLUXD_STATS_COUNTER_PTR(error_flow_memory_alloc)}};
+
+static struct global_map const breeds_map[] = {{"Safe", INFLUXD_STATS_GAUGE_PTR(flow_breed_safe_count)},
+ {"Acceptable", INFLUXD_STATS_GAUGE_PTR(flow_breed_acceptable_count)},
+ {"Fun", INFLUXD_STATS_GAUGE_PTR(flow_breed_fun_count)},
+ {"Unsafe", INFLUXD_STATS_GAUGE_PTR(flow_breed_unsafe_count)},
+ {"Potentially Dangerous",
+ INFLUXD_STATS_GAUGE_PTR(flow_breed_potentially_dangerous_count)},
+ {"Tracker\\/Ads", INFLUXD_STATS_GAUGE_PTR(flow_breed_tracker_ads_count)},
+ {"Dangerous", INFLUXD_STATS_GAUGE_PTR(flow_breed_dangerous_count)},
+ {"Unrated", INFLUXD_STATS_GAUGE_PTR(flow_breed_unrated_count)},
+ {NULL, INFLUXD_STATS_GAUGE_PTR(flow_breed_unknown_count)}};
+
+static struct global_map const categories_map[] = {
+ {"Unspecified", INFLUXD_STATS_GAUGE_PTR(flow_category_unspecified_count)},
+ {"Media", INFLUXD_STATS_GAUGE_PTR(flow_category_media_count)},
+ {"VPN", INFLUXD_STATS_GAUGE_PTR(flow_category_vpn_count)},
+ {"Email", INFLUXD_STATS_GAUGE_PTR(flow_category_email_count)},
+ {"DataTransfer", INFLUXD_STATS_GAUGE_PTR(flow_category_data_transfer_count)},
+ {"Web", INFLUXD_STATS_GAUGE_PTR(flow_category_web_count)},
+ {"SocialNetwork", INFLUXD_STATS_GAUGE_PTR(flow_category_social_network_count)},
+ {"Download", INFLUXD_STATS_GAUGE_PTR(flow_category_download_count)},
+ {"Game", INFLUXD_STATS_GAUGE_PTR(flow_category_game_count)},
+ {"Chat", INFLUXD_STATS_GAUGE_PTR(flow_category_chat_count)},
+ {"VoIP", INFLUXD_STATS_GAUGE_PTR(flow_category_voip_count)},
+ {"Database", INFLUXD_STATS_GAUGE_PTR(flow_category_database_count)},
+ {"RemoteAccess", INFLUXD_STATS_GAUGE_PTR(flow_category_remote_access_count)},
+ {"Cloud", INFLUXD_STATS_GAUGE_PTR(flow_category_cloud_count)},
+ {"Network", INFLUXD_STATS_GAUGE_PTR(flow_category_network_count)},
+ {"Collaborative", INFLUXD_STATS_GAUGE_PTR(flow_category_collaborative_count)},
+ {"RPC", INFLUXD_STATS_GAUGE_PTR(flow_category_rpc_count)},
+ {"Streaming", INFLUXD_STATS_GAUGE_PTR(flow_category_streaming_count)},
+ {"System", INFLUXD_STATS_GAUGE_PTR(flow_category_system_count)},
+ {"SoftwareUpdate", INFLUXD_STATS_GAUGE_PTR(flow_category_software_update_count)},
+ {"Music", INFLUXD_STATS_GAUGE_PTR(flow_category_music_count)},
+ {"Video", INFLUXD_STATS_GAUGE_PTR(flow_category_video_count)},
+ {"Shopping", INFLUXD_STATS_GAUGE_PTR(flow_category_shopping_count)},
+ {"Productivity", INFLUXD_STATS_GAUGE_PTR(flow_category_productivity_count)},
+ {"FileSharing", INFLUXD_STATS_GAUGE_PTR(flow_category_file_sharing_count)},
+ {"ConnCheck", INFLUXD_STATS_GAUGE_PTR(flow_category_conn_check_count)},
+ {"IoT-Scada", INFLUXD_STATS_GAUGE_PTR(flow_category_iot_scada_count)},
+ {"VirtAssistant", INFLUXD_STATS_GAUGE_PTR(flow_category_virt_assistant_count)},
+ {"Cybersecurity", INFLUXD_STATS_GAUGE_PTR(flow_category_cybersecurity_count)},
+ {"AdultContent", INFLUXD_STATS_GAUGE_PTR(flow_category_adult_content_count)},
+ {"Mining", INFLUXD_STATS_GAUGE_PTR(flow_category_mining_count)},
+ {"Malware", INFLUXD_STATS_GAUGE_PTR(flow_category_malware_count)},
+ {"Advertisement", INFLUXD_STATS_GAUGE_PTR(flow_category_advertisment_count)},
+ {"Banned_Site", INFLUXD_STATS_GAUGE_PTR(flow_category_banned_site_count)},
+ {"Site_Unavailable", INFLUXD_STATS_GAUGE_PTR(flow_category_site_unavail_count)},
+ {"Allowed_Site", INFLUXD_STATS_GAUGE_PTR(flow_category_allowed_site_count)},
+ {"Antimalware", INFLUXD_STATS_GAUGE_PTR(flow_category_antimalware_count)},
+ {"Crypto_Currency", INFLUXD_STATS_GAUGE_PTR(flow_category_crypto_currency_count)},
+ {"Gambling", INFLUXD_STATS_GAUGE_PTR(flow_category_gambling_count)},
+ {NULL, INFLUXD_STATS_GAUGE_PTR(flow_category_unknown_count)}};
+
+static struct global_map const confidence_map[] = {
+ {"Match by port", INFLUXD_STATS_GAUGE_PTR(flow_confidence_by_port)},
+ {"DPI (partial)", INFLUXD_STATS_GAUGE_PTR(flow_confidence_dpi_partial)},
+ {"DPI (partial cache)", INFLUXD_STATS_GAUGE_PTR(flow_confidence_dpi_partial_cache)},
+ {"DPI (cache)", INFLUXD_STATS_GAUGE_PTR(flow_confidence_dpi_cache)},
+ {"DPI", INFLUXD_STATS_GAUGE_PTR(flow_confidence_dpi)},
+ {"nBPF", INFLUXD_STATS_GAUGE_PTR(flow_confidence_nbpf)},
+ {"Match by IP", INFLUXD_STATS_GAUGE_PTR(flow_confidence_by_ip)},
+ {"DPI (aggressive)", INFLUXD_STATS_GAUGE_PTR(flow_confidence_dpi_aggressive)},
+ {"Match by custom rule", INFLUXD_STATS_GAUGE_PTR(flow_confidence_custom_rule)},
+ {NULL, INFLUXD_STATS_GAUGE_PTR(flow_confidence_unknown)}};
+
+static struct global_map const severity_map[] = {{"Low", INFLUXD_STATS_GAUGE_PTR(flow_severity_low)},
+ {"Medium", INFLUXD_STATS_GAUGE_PTR(flow_severity_medium)},
+ {"High", INFLUXD_STATS_GAUGE_PTR(flow_severity_high)},
+ {"Severe", INFLUXD_STATS_GAUGE_PTR(flow_severity_severe)},
+ {"Critical", INFLUXD_STATS_GAUGE_PTR(flow_severity_critical)},
+ {"Emergency", INFLUXD_STATS_GAUGE_PTR(flow_severity_emergency)},
+ {NULL, INFLUXD_STATS_GAUGE_PTR(flow_severity_unknown)}};
+
+#ifdef ENABLE_MEMORY_PROFILING
+void nDPIsrvd_memprof_log_alloc(size_t alloc_size)
+{
+ (void)alloc_size;
+}
+
+void nDPIsrvd_memprof_log_free(size_t free_size)
+{
+ (void)free_size;
+}
+
+void nDPIsrvd_memprof_log(char const * const format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
+ vfprintf(stderr, format, ap);
+ fprintf(stderr, "%s\n", "");
+ va_end(ap);
+}
+#endif
+
+#define INFLUXDB_FORMAT() "%s=%llu,"
+#define INFLUXDB_FORMAT_END() "%s=%llu\n"
+#define INFLUXDB_VALUE_COUNTER(value) #value, (unsigned long long int)influxd_statistics.counters.value
+#define INFLUXDB_VALUE_GAUGE(value) #value, (unsigned long long int)influxd_statistics.gauges[0].value
+#define CHECK_SNPRINTF_RET(bytes) \
+ do \
+ { \
+ if (bytes <= 0 || (size_t)bytes >= siz) \
+ { \
+ goto failure; \
+ } \
+ else \
+ { \
+ buf += bytes; \
+ siz -= bytes; \
+ } \
+ } while (0)
+static int serialize_influx_line(char * buf, size_t siz)
+{
+ int bytes;
+
+ pthread_mutex_lock(&influxd_statistics.rw_lock);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "general",
+ INFLUXDB_VALUE_COUNTER(json_lines),
+ INFLUXDB_VALUE_COUNTER(json_bytes),
+ INFLUXDB_VALUE_COUNTER(flow_src_total_bytes),
+ INFLUXDB_VALUE_COUNTER(flow_dst_total_bytes));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "events",
+ INFLUXDB_VALUE_COUNTER(flow_new_count),
+ INFLUXDB_VALUE_COUNTER(flow_end_count),
+ INFLUXDB_VALUE_COUNTER(flow_idle_count),
+ INFLUXDB_VALUE_COUNTER(flow_update_count),
+ INFLUXDB_VALUE_COUNTER(flow_analyse_count),
+ INFLUXDB_VALUE_COUNTER(flow_guessed_count),
+ INFLUXDB_VALUE_COUNTER(flow_detected_count),
+ INFLUXDB_VALUE_COUNTER(flow_detection_update_count),
+ INFLUXDB_VALUE_COUNTER(flow_not_detected_count),
+ INFLUXDB_VALUE_COUNTER(flow_risky_count),
+ INFLUXDB_VALUE_COUNTER(packet_count),
+ INFLUXDB_VALUE_COUNTER(packet_flow_count),
+ INFLUXDB_VALUE_COUNTER(init_count),
+ INFLUXDB_VALUE_COUNTER(reconnect_count),
+ INFLUXDB_VALUE_COUNTER(shutdown_count),
+ INFLUXDB_VALUE_COUNTER(status_count),
+ INFLUXDB_VALUE_COUNTER(error_unknown_datalink),
+ INFLUXDB_VALUE_COUNTER(error_unknown_l3_protocol),
+ INFLUXDB_VALUE_COUNTER(error_unsupported_datalink),
+ INFLUXDB_VALUE_COUNTER(error_packet_too_short),
+ INFLUXDB_VALUE_COUNTER(error_packet_type_unknown),
+ INFLUXDB_VALUE_COUNTER(error_packet_header_invalid),
+ INFLUXDB_VALUE_COUNTER(error_ip4_packet_too_short),
+ INFLUXDB_VALUE_COUNTER(error_ip4_size_smaller_than_header),
+ INFLUXDB_VALUE_COUNTER(error_ip4_l4_payload_detection),
+ INFLUXDB_VALUE_COUNTER(error_ip6_packet_too_short),
+ INFLUXDB_VALUE_COUNTER(error_ip6_size_smaller_than_header),
+ INFLUXDB_VALUE_COUNTER(error_ip6_l4_payload_detection),
+ INFLUXDB_VALUE_COUNTER(error_tcp_packet_too_short),
+ INFLUXDB_VALUE_COUNTER(error_udp_packet_too_short),
+ INFLUXDB_VALUE_COUNTER(error_capture_size_smaller_than_packet),
+ INFLUXDB_VALUE_COUNTER(error_max_flows_to_track),
+ INFLUXDB_VALUE_COUNTER(error_flow_memory_alloc));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "state",
+ INFLUXDB_VALUE_GAUGE(flow_state_info),
+ INFLUXDB_VALUE_GAUGE(flow_state_finished));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "breed",
+ INFLUXDB_VALUE_GAUGE(flow_breed_safe_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_acceptable_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_fun_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_unsafe_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_potentially_dangerous_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_tracker_ads_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_dangerous_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_unrated_count),
+ INFLUXDB_VALUE_GAUGE(flow_breed_unknown_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+
+ "category",
+ INFLUXDB_VALUE_GAUGE(flow_category_unspecified_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_media_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_vpn_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_email_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_data_transfer_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_web_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_social_network_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_download_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_game_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_chat_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_voip_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_database_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_remote_access_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_cloud_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_network_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_collaborative_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_rpc_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_streaming_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_system_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_software_update_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_music_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_video_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_shopping_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_productivity_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_file_sharing_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_conn_check_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_iot_scada_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_virt_assistant_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_cybersecurity_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_adult_content_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_mining_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_malware_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_advertisment_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_banned_site_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_site_unavail_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_allowed_site_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_antimalware_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_crypto_currency_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_gambling_count),
+ INFLUXDB_VALUE_GAUGE(flow_category_unknown_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "confidence",
+ INFLUXDB_VALUE_GAUGE(flow_confidence_by_port),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_dpi_partial),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_dpi_partial_cache),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_dpi_cache),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_dpi),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_nbpf),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_by_ip),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_dpi_aggressive),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_custom_rule),
+ INFLUXDB_VALUE_GAUGE(flow_confidence_unknown));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT()
+ INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "severity",
+ INFLUXDB_VALUE_GAUGE(flow_severity_low),
+ INFLUXDB_VALUE_GAUGE(flow_severity_medium),
+ INFLUXDB_VALUE_GAUGE(flow_severity_high),
+ INFLUXDB_VALUE_GAUGE(flow_severity_severe),
+ INFLUXDB_VALUE_GAUGE(flow_severity_critical),
+ INFLUXDB_VALUE_GAUGE(flow_severity_emergency),
+ INFLUXDB_VALUE_GAUGE(flow_severity_unknown));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "layer3",
+ INFLUXDB_VALUE_GAUGE(flow_l3_ip4_count),
+ INFLUXDB_VALUE_GAUGE(flow_l3_ip6_count),
+ INFLUXDB_VALUE_GAUGE(flow_l3_other_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "layer4",
+ INFLUXDB_VALUE_GAUGE(flow_l4_tcp_count),
+ INFLUXDB_VALUE_GAUGE(flow_l4_udp_count),
+ INFLUXDB_VALUE_GAUGE(flow_l4_icmp_count),
+ INFLUXDB_VALUE_GAUGE(flow_l4_other_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf,
+ siz,
+ "%s " INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT() INFLUXDB_FORMAT_END(),
+ "detection",
+ INFLUXDB_VALUE_GAUGE(flow_active_count),
+ INFLUXDB_VALUE_GAUGE(flow_detected_count),
+ INFLUXDB_VALUE_GAUGE(flow_guessed_count),
+ INFLUXDB_VALUE_GAUGE(flow_not_detected_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ bytes = snprintf(buf, siz, "%s " INFLUXDB_FORMAT(), "risks", INFLUXDB_VALUE_GAUGE(flow_risk_unknown_count));
+ CHECK_SNPRINTF_RET(bytes);
+
+ for (size_t i = 0; i < NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */; ++i)
+ {
+ bytes = snprintf(buf,
+ siz,
+ "flow_risk_%zu_count=%llu,",
+ i + 1,
+ (unsigned long long int)influxd_statistics.gauges[0].flow_risk_count[i]);
+ CHECK_SNPRINTF_RET(bytes);
+ }
+ buf[-1] = '\n';
+
+failure:
+ memset(&influxd_statistics.counters, 0, sizeof(influxd_statistics.counters));
+
+ INFLUXD_STATS_GAUGE_SUB(flow_state_info);
+ INFLUXD_STATS_GAUGE_SUB(flow_state_finished);
+
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_safe_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_acceptable_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_fun_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_unsafe_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_potentially_dangerous_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_tracker_ads_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_dangerous_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_unrated_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_breed_unknown_count);
+
+ INFLUXD_STATS_GAUGE_SUB(flow_category_unspecified_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_media_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_vpn_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_email_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_data_transfer_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_web_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_social_network_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_download_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_game_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_chat_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_voip_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_database_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_remote_access_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_cloud_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_network_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_collaborative_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_rpc_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_streaming_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_system_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_software_update_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_music_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_video_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_shopping_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_productivity_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_file_sharing_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_conn_check_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_iot_scada_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_virt_assistant_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_cybersecurity_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_adult_content_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_mining_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_malware_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_advertisment_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_banned_site_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_site_unavail_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_allowed_site_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_antimalware_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_crypto_currency_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_gambling_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_category_unknown_count);
+
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_by_port);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_dpi_partial);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_dpi_partial_cache);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_dpi_cache);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_dpi);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_nbpf);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_by_ip);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_dpi_aggressive);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_custom_rule);
+ INFLUXD_STATS_GAUGE_SUB(flow_confidence_unknown);
+
+ INFLUXD_STATS_GAUGE_SUB(flow_severity_low);
+ INFLUXD_STATS_GAUGE_SUB(flow_severity_medium);
+ INFLUXD_STATS_GAUGE_SUB(flow_severity_high);
+ INFLUXD_STATS_GAUGE_SUB(flow_severity_severe);
+ INFLUXD_STATS_GAUGE_SUB(flow_severity_critical);
+ INFLUXD_STATS_GAUGE_SUB(flow_severity_emergency);
+ INFLUXD_STATS_GAUGE_SUB(flow_severity_unknown);
+
+ INFLUXD_STATS_GAUGE_SUB(flow_l3_ip4_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_l3_ip6_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_l3_other_count);
+
+ INFLUXD_STATS_GAUGE_SUB(flow_l4_tcp_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_l4_udp_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_l4_icmp_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_l4_other_count);
+
+ INFLUXD_STATS_GAUGE_SUB(flow_active_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_detected_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_guessed_count);
+ INFLUXD_STATS_GAUGE_SUB(flow_not_detected_count);
+
+ for (size_t i = 0; i < NDPI_MAX_RISK - 1 /* NDPI_NO_RISK */; ++i)
+ {
+ INFLUXD_STATS_GAUGE_SUB(flow_risk_count[i]);
+ }
+ INFLUXD_STATS_GAUGE_SUB(flow_risk_unknown_count);
+
+ memset(&influxd_statistics.gauges[1], 0, sizeof(influxd_statistics.gauges[1]));
+ pthread_mutex_unlock(&influxd_statistics.rw_lock);
+
+ return 0;
+}
+
+static int init_influx_ctx(struct influx_ctx * const ctx, char const * const url, char const * const api_token)
+{
+ char auth[128];
+
+ ctx->http_header = curl_slist_append(ctx->http_header, "Content-Type: application/json");
+ if (ctx->http_header == NULL)
+ {
+ return -1;
+ }
+ if (snprintf(auth, sizeof(auth), "Authorization: Token %s", api_token) >= (int)sizeof(auth))
+ {
+ return -1;
+ }
+ ctx->http_header = curl_slist_append(ctx->http_header, auth);
+ memset(auth, '\0', sizeof(auth));
+ if (ctx->http_header == NULL)
+ {
+ return -1;
+ }
+
+ ctx->curl = curl_easy_init();
+ if (ctx->curl == NULL)
+ {
+ return -1;
+ }
+
+ if (curl_easy_setopt(ctx->curl, CURLOPT_URL, url) != CURLE_OK ||
+ curl_easy_setopt(ctx->curl, CURLOPT_USERAGENT, "nDPIsrvd-influxd") != CURLE_OK ||
+ curl_easy_setopt(ctx->curl, CURLOPT_HTTPHEADER, ctx->http_header) != CURLE_OK ||
+ curl_easy_setopt(ctx->curl, CURLOPT_TIMEOUT, (long)influxdb_interval_ull) != CURLE_OK)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void free_influx_ctx(struct influx_ctx * const ctx)
+{
+ curl_easy_cleanup(ctx->curl);
+ curl_slist_free_all(ctx->http_header);
+ ctx->curl = NULL;
+ ctx->http_header = NULL;
+}
+
+static void post_influx_ctx(struct influx_ctx * const ctx)
+{
+ CURLcode res;
+ char post_buffer[BUFSIZ];
+
+ if (serialize_influx_line(post_buffer, sizeof(post_buffer)) != 0)
+ {
+ logger(1, "%s", "Could not serialize influx buffer");
+ return;
+ }
+ curl_easy_setopt(ctx->curl, CURLOPT_POSTFIELDS, post_buffer);
+ res = curl_easy_perform(ctx->curl);
+ if (res != CURLE_OK)
+ {
+ logger(1, "curl_easy_perform() failed: %s", curl_easy_strerror(res));
+ return;
+ }
+}
+
+static void * send_to_influxdb(void * thread_data)
+{
+ struct influx_ctx influx_ctx = {};
+
+ (void)thread_data;
+ init_influx_ctx(&influx_ctx, influxdb_url, influxdb_token);
+ post_influx_ctx(&influx_ctx);
+ free_influx_ctx(&influx_ctx);
+
+ return NULL;
+}
+
+static int start_influxdb_thread(void)
+{
+ pthread_t tid;
+ pthread_attr_t att;
+
+ if (pthread_attr_init(&att) != 0)
+ {
+ return 1;
+ }
+ if (pthread_attr_setdetachstate(&att, PTHREAD_CREATE_DETACHED) != 0)
+ {
+ return 1;
+ }
+
+ int error = pthread_create(&tid, &att, send_to_influxdb, NULL);
+ if (0 != error)
+ {
+ logger(1, "Couldn't run thread, errno %d", error);
+ }
+
+ pthread_attr_destroy(&att);
+ return 0;
+}
+
+static int influxd_map_to_stat(char const * const token_str,
+ size_t token_length,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ size_t i, null_i = map_length;
+
+ for (i = 0; i < map_length; ++i)
+ {
+ if (map[i].json_key == NULL)
+ {
+ null_i = i;
+ break;
+ }
+
+ size_t key_length = strlen(map[i].json_key);
+ if (key_length == token_length && strncmp(map[i].json_key, token_str, token_length) == 0)
+ {
+ (*map[i].global_stat_inc)++;
+ return 0;
+ }
+ }
+
+ if (null_i < map_length && map[null_i].global_stat_inc != NULL)
+ {
+ (*map[null_i].global_stat_inc)++;
+ return 0;
+ }
+
+ return 1;
+}
+
+static int influxd_map_value_to_stat(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_json_token const * const token,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ char const * value_str = NULL;
+ size_t value_length = 0;
+
+ value_str = TOKEN_GET_VALUE(sock, token, &value_length);
+ if (value_length == 0 || value_str == NULL)
+ {
+ return 1;
+ }
+
+ return influxd_map_to_stat(value_str, value_length, map, map_length);
+}
+
+static void influxd_unmap_flow_from_stat(struct flow_user_data * const flow_user_data)
+{
+ if (flow_user_data->is_ip4 != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_l3_ip4_count);
+ }
+
+ if (flow_user_data->is_ip6 != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_l3_ip6_count);
+ }
+
+ if (flow_user_data->is_other_l3 != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_l3_other_count);
+ }
+
+ if (flow_user_data->is_tcp != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_l4_tcp_count);
+ }
+
+ if (flow_user_data->is_udp != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_l4_udp_count);
+ }
+
+ if (flow_user_data->is_icmp != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_l4_icmp_count);
+ }
+
+ if (flow_user_data->is_other_l4 != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_l4_other_count);
+ }
+
+ if (flow_user_data->new_seen != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_active_count);
+ }
+
+ if (flow_user_data->is_detected != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_detected_count);
+ }
+
+ if (flow_user_data->is_guessed != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_guessed_count);
+ }
+
+ if (flow_user_data->is_not_detected != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_not_detected_count);
+ }
+
+ if (flow_user_data->is_info != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_state_info);
+ }
+
+ if (flow_user_data->is_finished != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_state_finished);
+ }
+
+ if (flow_user_data->breed > 0 && flow_user_data->breed_ndpid_invalid == 0 &&
+ INFLUXD_STATS_MAP_NOTNULL(breeds_map, flow_user_data->breed) != 0)
+ {
+ INFLUXD_STATS_MAP_DEC(breeds_map, flow_user_data->breed);
+ }
+
+ if (flow_user_data->category > 0 && flow_user_data->category_ndpid_invalid == 0 &&
+ INFLUXD_STATS_MAP_NOTNULL(categories_map, flow_user_data->category) != 0)
+ {
+ INFLUXD_STATS_MAP_DEC(categories_map, flow_user_data->category);
+ }
+
+ if (flow_user_data->confidence > 0 && flow_user_data->confidence_ndpid_invalid == 0 &&
+ INFLUXD_STATS_MAP_NOTNULL(confidence_map, flow_user_data->confidence) != 0)
+ {
+ INFLUXD_STATS_MAP_DEC(confidence_map, flow_user_data->confidence);
+ }
+
+ for (uint8_t i = 0; i < MAX_SEVERITIES_PER_FLOW; ++i)
+ {
+ if (flow_user_data->severities[i] > 0)
+ {
+ INFLUXD_STATS_MAP_DEC(severity_map, flow_user_data->severities[i]);
+ }
+ }
+
+ for (uint8_t i = 0; i < MAX_RISKS_PER_FLOW; ++i)
+ {
+ if (flow_user_data->risks[i] > 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_risk_count[flow_user_data->risks[i]]);
+ }
+ }
+
+ if (flow_user_data->risk_ndpid_invalid != 0)
+ {
+ INFLUXD_STATS_GAUGE_DEC(flow_risk_unknown_count);
+ }
+}
+
+static ssize_t influxd_map_index(char const * const json_key,
+ size_t key_length,
+ struct global_map const * const map,
+ size_t map_length)
+{
+ ssize_t unknown_key = -1;
+
+ if (json_key == NULL || key_length == 0)
+ {
+ return -1;
+ }
+
+ for (size_t i = 0; i < map_length; ++i)
+ {
+ if (map[i].json_key == NULL)
+ {
+ unknown_key = i;
+ continue;
+ }
+
+ if (key_length == strlen(map[i].json_key) && strncmp(json_key, map[i].json_key, key_length) == 0)
+ {
+ return i;
+ }
+ }
+
+ return unknown_key;
+}
+
+static int influxd_map_flow_u8(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_json_token const * const token,
+ struct global_map const * const map,
+ size_t map_length,
+ uint8_t * const dest)
+{
+ if (token == NULL || dest == NULL)
+ {
+ return 1;
+ }
+
+ size_t len;
+ char const * const str = TOKEN_GET_VALUE(sock, token, &len);
+ if (str == NULL || len == 0)
+ {
+ return 1;
+ }
+
+ ssize_t const map_index = influxd_map_index(str, len, map, map_length);
+ if (map_index < 0 || map_index > UCHAR_MAX)
+ {
+ return 1;
+ }
+
+ *dest = map_index + 1;
+ return 0;
+}
+
+static void process_flow_stats(struct nDPIsrvd_socket * const sock, struct nDPIsrvd_flow * const flow)
+{
+ struct flow_user_data * flow_user_data;
+ struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
+ struct nDPIsrvd_json_token const * const flow_state = TOKEN_GET_SZ(sock, "flow_state");
+ nDPIsrvd_ull total_bytes_ull[2];
+
+ if (flow == NULL)
+ {
+ return;
+ }
+ flow_user_data = (struct flow_user_data *)flow->flow_user_data;
+ if (flow_user_data == NULL)
+ {
+ return;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "new") != 0)
+ {
+ flow_user_data->new_seen = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_active_count);
+
+ struct nDPIsrvd_json_token const * const l3_proto = TOKEN_GET_SZ(sock, "l3_proto");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, l3_proto, "ip4") != 0)
+ {
+ flow_user_data->is_ip4 = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_l3_ip4_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l3_proto, "ip6") != 0)
+ {
+ flow_user_data->is_ip6 = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_l3_ip6_count);
+ }
+ else if (l3_proto != NULL)
+ {
+ flow_user_data->is_other_l3 = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_l3_other_count);
+ }
+
+ struct nDPIsrvd_json_token const * const l4_proto = TOKEN_GET_SZ(sock, "l4_proto");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "tcp") != 0)
+ {
+ flow_user_data->is_tcp = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_l4_tcp_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "udp") != 0)
+ {
+ flow_user_data->is_udp = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_l4_udp_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, l4_proto, "icmp") != 0)
+ {
+ flow_user_data->is_icmp = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_l4_icmp_count);
+ }
+ else if (l4_proto != NULL)
+ {
+ flow_user_data->is_other_l4 = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_l4_other_count);
+ }
+ }
+ else if (flow_user_data->new_seen == 0)
+ {
+ return;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "not-detected") != 0)
+ {
+ flow_user_data->is_not_detected = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_not_detected_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "guessed") != 0)
+ {
+ flow_user_data->is_guessed = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_guessed_count);
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detected") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detection-update") != 0)
+ {
+ struct nDPIsrvd_json_token const * const flow_risk = TOKEN_GET_SZ(sock, "ndpi", "flow_risk");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ if (flow_user_data->is_detected == 0)
+ {
+ flow_user_data->is_detected = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_detected_count);
+ }
+
+ if (flow_risk != NULL)
+ {
+ if (flow_user_data->risks[0] == 0)
+ {
+ INFLUXD_STATS_COUNTER_INC(flow_risky_count);
+ }
+
+ while ((current = nDPIsrvd_get_next_token(sock, flow_risk, &next_child_index)) != NULL)
+ {
+ size_t numeric_risk_len = 0;
+ char const * const numeric_risk_str = TOKEN_GET_KEY(sock, current, &numeric_risk_len);
+ nDPIsrvd_ull numeric_risk_value = 0;
+ char numeric_risk_buf[numeric_risk_len + 1];
+
+ if (numeric_risk_len > 0 && numeric_risk_str != NULL)
+ {
+ strncpy(numeric_risk_buf, numeric_risk_str, numeric_risk_len);
+ numeric_risk_buf[numeric_risk_len] = '\0';
+
+ struct nDPIsrvd_json_token const * const severity =
+ TOKEN_GET_SZ(sock, "ndpi", "flow_risk", numeric_risk_buf, "severity");
+ uint8_t severity_index;
+
+ if (influxd_map_flow_u8(
+ sock, severity, severity_map, nDPIsrvd_ARRAY_LENGTH(severity_map), &severity_index) != 0)
+ {
+ severity_index = 0;
+ }
+
+ if (severity_index != 0)
+ {
+ for (uint8_t i = 0; i < MAX_SEVERITIES_PER_FLOW; ++i)
+ {
+ if (flow_user_data->severities[i] != 0)
+ {
+ continue;
+ }
+ if (flow_user_data->severities[i] == severity_index)
+ {
+ break;
+ }
+
+ if (influxd_map_value_to_stat(
+ sock, severity, severity_map, nDPIsrvd_ARRAY_LENGTH(severity_map)) != 0)
+ {
+ severity_index = 0;
+ break;
+ }
+ flow_user_data->severities[i] = severity_index;
+ break;
+ }
+ }
+ if (severity_index == 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, severity, &value_len);
+
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1,
+ "Unknown/Invalid JSON value for key 'ndpi','breed': %.*s",
+ (int)value_len,
+ value_str);
+ }
+ }
+
+ if (str_value_to_ull(numeric_risk_str, &numeric_risk_value) == CONVERSION_OK)
+ {
+ if (numeric_risk_value < NDPI_MAX_RISK && numeric_risk_value > 0)
+ {
+ for (uint8_t i = 0; i < MAX_RISKS_PER_FLOW; ++i)
+ {
+ if (flow_user_data->risks[i] != 0)
+ {
+ continue;
+ }
+ if (flow_user_data->risks[i] == numeric_risk_value - 1)
+ {
+ break;
+ }
+
+ INFLUXD_STATS_GAUGE_INC(flow_risk_count[numeric_risk_value - 1]);
+ flow_user_data->risks[i] = numeric_risk_value - 1;
+ break;
+ }
+ }
+ else if (flow_user_data->risk_ndpid_invalid == 0)
+ {
+ flow_user_data->risk_ndpid_invalid = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_risk_unknown_count);
+ }
+ }
+ else
+ {
+ logger(1, "Invalid numeric risk value: %s", numeric_risk_buf);
+ }
+ }
+ else
+ {
+ logger(1, "%s", "Missing numeric risk value");
+ }
+ }
+ }
+
+ if (flow_user_data->breed == 0 && flow_user_data->breed_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const breed = TOKEN_GET_SZ(sock, "ndpi", "breed");
+ if (influxd_map_flow_u8(
+ sock, breed, breeds_map, nDPIsrvd_ARRAY_LENGTH(breeds_map), &flow_user_data->breed) != 0 ||
+ influxd_map_value_to_stat(sock, breed, breeds_map, nDPIsrvd_ARRAY_LENGTH(breeds_map)) != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, breed, &value_len);
+
+ flow_user_data->breed = 0;
+ flow_user_data->breed_ndpid_invalid = 1;
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','breed': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (flow_user_data->category == 0 && flow_user_data->category_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const category = TOKEN_GET_SZ(sock, "ndpi", "category");
+ if (influxd_map_flow_u8(
+ sock, category, categories_map, nDPIsrvd_ARRAY_LENGTH(categories_map), &flow_user_data->category) !=
+ 0 ||
+ influxd_map_value_to_stat(sock, category, categories_map, nDPIsrvd_ARRAY_LENGTH(categories_map)) != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, category, &value_len);
+
+ flow_user_data->category = 0;
+ flow_user_data->category_ndpid_invalid = 1;
+ if (value_len > 0 && value_str != NULL)
+ {
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','category': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (flow_user_data->confidence == 0 && flow_user_data->confidence_ndpid_invalid == 0)
+ {
+ struct nDPIsrvd_json_token const * const token = TOKEN_GET_SZ(sock, "ndpi", "confidence");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1;
+
+ if ((current = nDPIsrvd_get_next_token(sock, token, &next_child_index)) == NULL)
+ {
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+ else if (nDPIsrvd_get_next_token(sock, token, &next_child_index) == NULL)
+ {
+ if (influxd_map_flow_u8(sock,
+ current,
+ confidence_map,
+ nDPIsrvd_ARRAY_LENGTH(confidence_map),
+ &flow_user_data->confidence) != 0 ||
+ influxd_map_value_to_stat(sock, current, confidence_map, nDPIsrvd_ARRAY_LENGTH(confidence_map)) !=
+ 0)
+ {
+ flow_user_data->confidence = 0;
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+ }
+ else
+ {
+ flow_user_data->confidence_ndpid_invalid = 1;
+ }
+
+ if (flow_user_data->confidence_ndpid_invalid != 0)
+ {
+ size_t value_len = 0;
+ char const * const value_str = TOKEN_GET_VALUE(sock, current, &value_len);
+
+ logger(1, "Unknown/Invalid JSON value for key 'ndpi','confidence': %.*s", (int)value_len, value_str);
+ }
+ }
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_state, "info") != 0)
+ {
+ if (flow_user_data->is_info == 0)
+ {
+ flow_user_data->is_info = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_state_info);
+ }
+ }
+ else if (TOKEN_VALUE_EQUALS_SZ(sock, flow_state, "finished") != 0)
+ {
+ if (flow_user_data->is_finished == 0)
+ {
+ if (flow_user_data->is_info != 0)
+ {
+ flow_user_data->is_info = 0;
+ INFLUXD_STATS_GAUGE_RES(flow_state_info);
+ }
+ flow_user_data->is_finished = 1;
+ INFLUXD_STATS_GAUGE_INC(flow_state_finished);
+ }
+ }
+
+ if (TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_src_tot_l4_payload_len"), &total_bytes_ull[0]) ==
+ CONVERSION_OK &&
+ TOKEN_VALUE_TO_ULL(sock, TOKEN_GET_SZ(sock, "flow_dst_tot_l4_payload_len"), &total_bytes_ull[1]) ==
+ CONVERSION_OK)
+ {
+ influxd_statistics.counters.flow_src_total_bytes +=
+ total_bytes_ull[0] - flow_user_data->last_flow_src_l4_payload_len;
+ influxd_statistics.counters.flow_dst_total_bytes +=
+ total_bytes_ull[1] - flow_user_data->last_flow_dst_l4_payload_len;
+
+ flow_user_data->last_flow_src_l4_payload_len = total_bytes_ull[0];
+ flow_user_data->last_flow_dst_l4_payload_len = total_bytes_ull[1];
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "end") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "idle") != 0)
+ {
+ influxd_unmap_flow_from_stat(flow_user_data);
+ }
+}
+
+static enum nDPIsrvd_callback_return influxd_json_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow)
+{
+ (void)instance;
+ (void)thread_data;
+
+ struct nDPIsrvd_json_token const * const flow_event = TOKEN_GET_SZ(sock, "flow_event_name");
+ struct nDPIsrvd_json_token const * const packet_event = TOKEN_GET_SZ(sock, "packet_event_name");
+ struct nDPIsrvd_json_token const * const daemon_event = TOKEN_GET_SZ(sock, "daemon_event_name");
+ struct nDPIsrvd_json_token const * const error_event = TOKEN_GET_SZ(sock, "error_event_name");
+
+ pthread_mutex_lock(&influxd_statistics.rw_lock);
+
+ INFLUXD_STATS_COUNTER_INC(json_lines);
+ influxd_statistics.counters.json_bytes += sock->buffer.json_message_length + NETWORK_BUFFER_LENGTH_DIGITS;
+
+ process_flow_stats(sock, flow);
+
+ if (flow_event != NULL &&
+ influxd_map_value_to_stat(sock, flow_event, flow_event_map, nDPIsrvd_ARRAY_LENGTH(flow_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown flow_event_name");
+ }
+
+ if (packet_event != NULL &&
+ influxd_map_value_to_stat(sock, packet_event, packet_event_map, nDPIsrvd_ARRAY_LENGTH(packet_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown packet_event_name");
+ }
+
+ if (daemon_event != NULL &&
+ influxd_map_value_to_stat(sock, daemon_event, daemon_event_map, nDPIsrvd_ARRAY_LENGTH(daemon_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown daemon_event_name");
+ }
+
+ if (error_event != NULL &&
+ influxd_map_value_to_stat(sock, error_event, error_event_map, nDPIsrvd_ARRAY_LENGTH(error_event_map)) != 0)
+ {
+ logger(1, "%s", "Unknown error_event_name");
+ }
+
+ pthread_mutex_unlock(&influxd_statistics.rw_lock);
+ return CALLBACK_OK;
+}
+
+static int set_influxd_timer(void)
+{
+ const time_t interval = influxdb_interval_ull * 1000;
+ struct itimerspec its;
+ its.it_value.tv_sec = interval / 1000;
+ its.it_value.tv_nsec = (interval % 1000) * 1000000;
+ its.it_interval.tv_nsec = 0;
+ its.it_interval.tv_sec = 0;
+
+ errno = 0;
+ return timerfd_settime(influxd_timerfd, 0, &its, NULL);
+}
+
+static int create_influxd_timer(void)
+{
+ influxd_timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (influxd_timerfd < 0)
+ {
+ return 1;
+ }
+
+ return set_influxd_timer();
+}
+
+static int mainloop(int epollfd, struct nDPIsrvd_socket * const sock)
+{
+ struct epoll_event events[32];
+ size_t const events_size = sizeof(events) / sizeof(events[0]);
+
+ while (main_thread_shutdown == 0)
+ {
+ int nready = epoll_wait(epollfd, events, events_size, -1);
+
+ for (int i = 0; i < nready; i++)
+ {
+ if (events[i].events & EPOLLERR)
+ {
+ logger(1, "Epoll event error: %s", (errno != 0 ? strerror(errno) : "EPOLLERR"));
+ break;
+ }
+
+ if (events[i].data.fd == influxd_timerfd)
+ {
+ uint64_t expirations;
+
+ errno = 0;
+ if (read(influxd_timerfd, &expirations, sizeof(expirations)) != sizeof(expirations))
+ {
+ logger(1, "Could not read timer expirations: %s", strerror(errno));
+ return 1;
+ }
+ if (set_influxd_timer() != 0)
+ {
+ logger(1, "Could not set timer: %s", strerror(errno));
+ return 1;
+ }
+
+ if (test_mode == 0)
+ {
+ start_influxdb_thread();
+ }
+ else
+ {
+ char stdout_buffer[BUFSIZ];
+
+ if (serialize_influx_line(stdout_buffer, sizeof(stdout_buffer)) != 0)
+ {
+ logger(1, "%s", "Could not serialize influx buffer");
+ return 1;
+ }
+ printf("%s", stdout_buffer);
+ }
+ }
+ else if (events[i].data.fd == sock->fd)
+ {
+ errno = 0;
+ enum nDPIsrvd_read_return read_ret = nDPIsrvd_read(sock);
+ if (read_ret != READ_OK)
+ {
+ logger(1, "nDPIsrvd read failed with: %s", nDPIsrvd_enum_to_string(read_ret));
+ return 1;
+ }
+
+ enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse_all(sock);
+ if (parse_ret != PARSE_NEED_MORE_DATA)
+ {
+ logger(1, "nDPIsrvd parse failed with: %s", nDPIsrvd_enum_to_string(parse_ret));
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int parse_options(int argc, char ** argv, struct nDPIsrvd_socket * const sock)
+{
+ int opt;
+
+ static char const usage[] =
+ "Usage: %s "
+ "[-c] [-d] [-p pidfile] [-s host] [-u user] [-g group]\n"
+ "\t \t[-i interval] [-U URL] [-T token]\n\n"
+ "\t-c\tLog to console instead of syslog.\n"
+ "\t-d\tForking into background after initialization.\n"
+ "\t-p\tWrite the daemon PID to the given file path.\n"
+ "\t-s\tDestination where nDPIsrvd is listening on.\n"
+ "\t-u\tChange user.\n"
+ "\t-g\tChange group.\n"
+ "\t-i\tInterval between pushing statistics to an influxdb endpoint.\n"
+ "\t-t\tTest mode: Ignores `-U' / `-T' and prints stats to stdout.\n"
+ "\t-U\tInfluxDB URL.\n"
+ "\t \tExample: http://127.0.0.1:8086/write?db=ndpi-daemon\n"
+ "\t-T\tInfluxDB access token.\n"
+ "\t \tNot recommended, use environment variable INFLUXDB_AUTH_TOKEN instead.\n";
+
+ while ((opt = getopt(argc, argv, "hcdp:s:u:g:i:tU:T:")) != -1)
+ {
+ switch (opt)
+ {
+ case 'c':
+ enable_console_logger();
+ break;
+ case 'd':
+ daemonize_enable();
+ break;
+ case 'p':
+ free(pidfile);
+ pidfile = strdup(optarg);
+ break;
+ case 's':
+ free(serv_optarg);
+ serv_optarg = strdup(optarg);
+ break;
+ case 'u':
+ free(user);
+ user = strdup(optarg);
+ break;
+ case 'g':
+ free(group);
+ group = strdup(optarg);
+ break;
+ case 'i':
+ free(influxdb_interval);
+ influxdb_interval = strdup(optarg);
+ break;
+ case 't':
+ test_mode = 1;
+ break;
+ case 'U':
+ free(influxdb_url);
+ influxdb_url = strdup(optarg);
+ break;
+ case 'T':
+ free(influxdb_token);
+ influxdb_token = strdup(optarg);
+ break;
+ default:
+ fprintf(stderr, usage, argv[0]);
+ return 1;
+ }
+ }
+
+ if (test_mode != 0)
+ {
+ logger_early(1, "%s", "Test mode enabled: ignoring `-U' / `-T' command line parameters");
+ free(influxdb_url);
+ free(influxdb_token);
+ influxdb_url = NULL;
+ influxdb_token = NULL;
+ }
+
+ if (serv_optarg == NULL)
+ {
+ serv_optarg = strdup(DISTRIBUTOR_UNIX_SOCKET);
+ }
+
+ if (influxdb_interval == NULL)
+ {
+ influxdb_interval = strdup("60");
+ }
+
+ if (str_value_to_ull(influxdb_interval, &influxdb_interval_ull) != CONVERSION_OK)
+ {
+ logger_early(1, "InfluxDB push interval `%s' is not a valid number", influxdb_interval);
+ return 1;
+ }
+
+ if (test_mode == 0)
+ {
+ if (influxdb_url == NULL)
+ {
+ logger_early(1, "%s", "Missing InfluxDB URL.");
+ return 1;
+ }
+
+ if (influxdb_token == NULL && getenv("INFLUXDB_AUTH_TOKEN") != NULL)
+ {
+ influxdb_token = strdup(getenv("INFLUXDB_AUTH_TOKEN"));
+ }
+ if (influxdb_token == NULL)
+ {
+ logger_early(1, "%s", "Missing InfluxDB authentication token.");
+ return 1;
+ }
+ }
+
+ if (nDPIsrvd_setup_address(&sock->address, serv_optarg) != 0)
+ {
+ logger_early(1, "Could not parse address `%s'", serv_optarg);
+ return 1;
+ }
+
+ if (optind < argc)
+ {
+ logger_early(1, "%s", "Unexpected argument after options");
+ logger_early(1, "%s", "");
+ logger_early(1, usage, argv[0]);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void sighandler(int signum)
+{
+ logger(0, "Received SIGNAL %d", signum);
+
+ if (main_thread_shutdown == 0)
+ {
+ logger(0, "%s", "Shutting down ..");
+ main_thread_shutdown = 1;
+ }
+}
+
+int main(int argc, char ** argv)
+{
+ int retval = 1, epollfd = -1;
+
+ init_logging("nDPIsrvd-influxd");
+
+ struct nDPIsrvd_socket * sock =
+ nDPIsrvd_socket_init(0, 0, 0, sizeof(struct flow_user_data), influxd_json_callback, NULL, NULL);
+ if (sock == NULL)
+ {
+ logger_early(1, "%s", "nDPIsrvd socket memory allocation failed!");
+ goto failure;
+ }
+
+ if (parse_options(argc, argv, sock) != 0)
+ {
+ goto failure;
+ }
+
+ logger_early(0, "Recv buffer size: %u", NETWORK_BUFFER_MAX_SIZE);
+ logger_early(0, "Connecting to `%s'..", serv_optarg);
+ logger_early(0, "InfluxDB push URL: %s", influxdb_url);
+
+ if (setvbuf(stdout, NULL, _IONBF, 0) != 0)
+ {
+ logger_early(1,
+ "Could not set stdout unbuffered: %s. Collectd may receive too old PUTVALs and complain.",
+ strerror(errno));
+ }
+
+ if (nDPIsrvd_connect(sock) != CONNECT_OK)
+ {
+ logger_early(1, "nDPIsrvd socket connect to %s failed!", serv_optarg);
+ goto failure;
+ }
+
+ if (nDPIsrvd_set_nonblock(sock) != 0)
+ {
+ logger_early(1, "nDPIsrvd set nonblock failed: %s", strerror(errno));
+ goto failure;
+ }
+
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (daemonize_with_pidfile(pidfile) != 0)
+ {
+ goto failure;
+ }
+
+ errno = 0;
+ if (user != NULL && change_user_group(user, group, pidfile) != 0)
+ {
+ if (errno != 0)
+ {
+ logger_early(1, "Change user/group failed: %s", strerror(errno));
+ }
+ else
+ {
+ logger_early(1, "%s", "Change user/group failed.");
+ }
+ goto failure;
+ }
+
+ epollfd = epoll_create1(0);
+ if (epollfd < 0)
+ {
+ logger_early(1, "Error creating epoll: %s", strerror(errno));
+ goto failure;
+ }
+
+ if (create_influxd_timer() != 0)
+ {
+ logger_early(1, "Error creating timer: %s", strerror(errno));
+ goto failure;
+ }
+
+ {
+ struct epoll_event timer_event = {.data.fd = influxd_timerfd, .events = EPOLLIN};
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, influxd_timerfd, &timer_event) < 0)
+ {
+ logger_early(1, "Error adding JSON fd to epoll: %s", strerror(errno));
+ goto failure;
+ }
+ }
+
+ {
+ struct epoll_event socket_event = {.data.fd = sock->fd, .events = EPOLLIN};
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock->fd, &socket_event) < 0)
+ {
+ logger_early(1, "Error adding nDPIsrvd socket fd to epoll: %s", strerror(errno));
+ goto failure;
+ }
+ }
+
+ if (test_mode == 0)
+ {
+ curl_global_init(CURL_GLOBAL_ALL);
+ }
+
+ logger_early(0, "%s", "Initialization succeeded.");
+ retval = mainloop(epollfd, sock);
+ logger_early(0, "%s", "Bye.");
+
+ if (test_mode == 0)
+ {
+ curl_global_cleanup();
+ }
+ else
+ {
+ char stdout_buffer[BUFSIZ];
+
+ if (serialize_influx_line(stdout_buffer, sizeof(stdout_buffer)) != 0)
+ {
+ logger(1, "%s", "Could not serialize influx buffer");
+ return 1;
+ }
+ printf("%s", stdout_buffer);
+ }
+failure:
+ nDPIsrvd_socket_free(&sock);
+ close(influxd_timerfd);
+ close(epollfd);
+ daemonize_shutdown(pidfile);
+ shutdown_logging();
+
+ return retval;
+}
diff --git a/examples/c-influxd/grafana-dashboard-simple.json b/examples/c-influxd/grafana-dashboard-simple.json
new file mode 100644
index 000000000..9d7208525
--- /dev/null
+++ b/examples/c-influxd/grafana-dashboard-simple.json
@@ -0,0 +1,6468 @@
+{
+ "__inputs": [
+ {
+ "name": "DS_INFLUXDB",
+ "label": "InfluxDB",
+ "description": "",
+ "type": "datasource",
+ "pluginId": "influxdb",
+ "pluginName": "InfluxDB"
+ },
+ {
+ "name": "VAR_NDPID_DB_NAME",
+ "type": "constant",
+ "label": "ndpid_db_name",
+ "value": "ndpi-daemon",
+ "description": ""
+ }
+ ],
+ "__elements": {
+ "f54c2b02-7c6c-4d3f-90d8-e9d31dee65a5": {
+ "name": "Risk",
+ "uid": "f54c2b02-7c6c-4d3f-90d8-e9d31dee65a5",
+ "kind": 1,
+ "model": {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "description": "",
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 0.01
+ },
+ {
+ "color": "dark-red",
+ "value": 50
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_1_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "XSS Attack"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_2_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SQL Injection"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_3_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "RCE Injection"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_4_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Binary App Transfer"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_5_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Known Proto on Non Std Port"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_6_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Self signed Cert"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_7_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Obsolete TLS v1.1 or older"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_8_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Weak TLS Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_9_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Expired"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_10_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Mismatch"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_11_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious User Agent"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_12_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Numeric IP Address"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_13_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious URL"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_14_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious Header"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_15_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS probably Not Carrying HTTPS"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_16_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious DGA Domain name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_17_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malformed Packet"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_18_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SSH Obsolete Client Version/Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_19_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SSH Obsolete Server Version/Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_20_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SMB Insecure Version"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_21_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Suspicious ESNI Usage"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_22_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unsafe Protocol"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_23_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious DNS Traffic"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_24_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Missing SNI TLS Extension"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_25_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious Content"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_26_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Risky ASN"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_27_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Risky Domain Name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_28_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malicious Fingerprint"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_29_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malicious SSL Cert/SHA1 Fingerprint"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_30_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Desktop/File-Sharing"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_31_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Uncommon TLS ALPN"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_32_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Validity Too Long"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_33_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Suspicious Extension"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_34_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Fatal Alert"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_35_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious Entropy"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_36_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Clear Text Credentials"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_37_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Large DNS Packet"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_38_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Fragmented DNS Message"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_39_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Text With Non Printable Chars"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_40_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Possible Exploit"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_41_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert About To Expire"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_42_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IDN Domain Name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_43_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Error Code"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_44_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Crawler/Bot"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_45_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Anonymous Subscriber"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_46_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unidirectional Traffic"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_47_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Obsolete Server"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_48_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Periodic Flow"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_49_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Minor Issues"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_50_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TCP Connection Issues"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_51_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Fully Encrypted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_52_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Invalid ALPN/SNI combination"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_53_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malware Host Contacted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_unknown_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown Risk"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_54_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Binary Transfer Attempt"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_55_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Probing Attempt"
+ }
+ ]
+ }
+ ]
+ },
+ "options": {
+ "minVizHeight": 75,
+ "minVizWidth": 75,
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": false
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "dabd3b1d-a74e-4ae6-9dfd-e1344e589ba0"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"risks\"\n )",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "dabd3b1d-a74e-4ae6-9dfd-e1344e589ba0"
+ },
+ "hide": false,
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "B"
+ }
+ ],
+ "title": "Risk",
+ "transformations": [
+ {
+ "id": "configFromData",
+ "options": {
+ "configRefId": "B",
+ "mappings": [
+ {
+ "fieldName": "Time",
+ "handlerKey": "__ignore"
+ },
+ {
+ "fieldName": "flow_active_count",
+ "handlerKey": "max"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "gauge"
+ }
+ }
+ },
+ "__requires": [
+ {
+ "type": "panel",
+ "id": "bargauge",
+ "name": "Bar gauge",
+ "version": ""
+ },
+ {
+ "type": "panel",
+ "id": "gauge",
+ "name": "Gauge",
+ "version": ""
+ },
+ {
+ "type": "grafana",
+ "id": "grafana",
+ "name": "Grafana",
+ "version": "10.2.0"
+ },
+ {
+ "type": "datasource",
+ "id": "influxdb",
+ "name": "InfluxDB",
+ "version": "1.0.0"
+ },
+ {
+ "type": "panel",
+ "id": "piechart",
+ "name": "Pie chart",
+ "version": ""
+ },
+ {
+ "type": "panel",
+ "id": "stat",
+ "name": "Stat",
+ "version": ""
+ },
+ {
+ "type": "panel",
+ "id": "state-timeline",
+ "name": "State timeline",
+ "version": ""
+ },
+ {
+ "type": "panel",
+ "id": "status-history",
+ "name": "Status history",
+ "version": ""
+ },
+ {
+ "type": "panel",
+ "id": "timeseries",
+ "name": "Time series",
+ "version": ""
+ }
+ ],
+ "annotations": {
+ "list": [
+ {
+ "builtIn": 1,
+ "datasource": {
+ "type": "grafana",
+ "uid": "-- Grafana --"
+ },
+ "enable": true,
+ "hide": true,
+ "iconColor": "rgba(0, 211, 255, 1)",
+ "name": "Annotations & Alerts",
+ "type": "dashboard"
+ }
+ ]
+ },
+ "editable": false,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "id": null,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 22,
+ "panels": [],
+ "title": "Events",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "#EAB839",
+ "value": 25
+ },
+ {
+ "color": "red",
+ "value": 50
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_ip4_l4_payload_detection"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv4 L4 Failed"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_ip4_packet_too_short"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv4 Packet Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_ip4_size_smaller_than_header"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv4 Header Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_ip6_l4_payload_detection"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv6 L4 Failed"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_ip6_packet_too_short"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv6 Packet Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_ip6_size_smaller_than_header"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv6 Header Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_packet_header_invalid"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Packet Header Invalid"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_packet_too_short"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Packet Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_packet_type_unknown"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Packet Type Unknown"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_tcp_packet_too_short"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TCP Packet Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_udp_packet_too_short"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "UDP Packet Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_unknown_datalink"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown Datalink"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_unknown_l3_protocol"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown L3 Protocol"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_unsupported_datalink"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unsupported Datalink"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_analyse_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Analyse"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_detected_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Detections"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_detection_update_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Detection Updates"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_end_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "End"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_guessed_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Guessed"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 5
+ },
+ {
+ "color": "red",
+ "value": 10
+ }
+ ]
+ }
+ },
+ {
+ "id": "color"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_idle_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Idle"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_new_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "New"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_not_detected_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Not Detected"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risky_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Risky"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_update_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Updates"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "init_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Init"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "packet_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Packet"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 25
+ },
+ {
+ "color": "red",
+ "value": 50
+ }
+ ]
+ }
+ },
+ {
+ "id": "color",
+ "value": {
+ "mode": "thresholds"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "packet_flow_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Packet Flow"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "reconnect_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Reconnect"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "shutdown_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Shutdown"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "status_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Status"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_capture_size_smaller_than_packet"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Capture Size < Packet Size"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_flow_memory_alloc"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Memory Allocation Failed"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "error_max_flows_to_track"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Max Flows"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 15,
+ "x": 0,
+ "y": 1
+ },
+ "id": 20,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"events\"\n )",
+ "refId": "A"
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic-by-name"
+ },
+ "custom": {
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ }
+ },
+ "mappings": []
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 3,
+ "x": 15,
+ "y": 1
+ },
+ "id": 19,
+ "options": {
+ "legend": {
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "pieType": "pie",
+ "reduceOptions": {
+ "calcs": [
+ "sum"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"events\"\n )",
+ "refId": "A"
+ }
+ ],
+ "type": "piechart"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic-by-name"
+ },
+ "custom": {
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ }
+ },
+ "mappings": []
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 9,
+ "w": 3,
+ "x": 18,
+ "y": 1
+ },
+ "id": 28,
+ "options": {
+ "legend": {
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "pieType": "pie",
+ "reduceOptions": {
+ "calcs": [
+ "sum"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"events\" and\n r._field != \"packet_flow_count\"\n )",
+ "refId": "A"
+ }
+ ],
+ "type": "piechart"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 3,
+ "x": 21,
+ "y": 1
+ },
+ "id": 27,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"events\" and\n (r._field == \"packet_count\" or\n r._field == \"packet_flow_count\")\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Packet",
+ "transformations": [
+ {
+ "id": "calculateField",
+ "options": {
+ "mode": "reduceRow",
+ "reduce": {
+ "reducer": "sum"
+ },
+ "replaceFields": true
+ }
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 3,
+ "x": 21,
+ "y": 4
+ },
+ "id": 26,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"events\" and\n (r._field == \"flow_detected_count\" or\n r._field == \"flow_detection_update_count\" or\n r._field == \"flow_guessed_count\")\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Detection",
+ "transformations": [
+ {
+ "id": "calculateField",
+ "options": {
+ "mode": "reduceRow",
+ "reduce": {
+ "reducer": "sum"
+ },
+ "replaceFields": true
+ }
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 3,
+ "w": 3,
+ "x": 21,
+ "y": 7
+ },
+ "id": 21,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"events\"\n )",
+ "refId": "A"
+ }
+ ],
+ "transformations": [
+ {
+ "id": "calculateField",
+ "options": {
+ "mode": "reduceRow",
+ "reduce": {
+ "reducer": "sum"
+ },
+ "replaceFields": true
+ }
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 10
+ },
+ "id": 5,
+ "panels": [],
+ "title": "General",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "binBps"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_dst_total_bytes"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Total Bytes Received"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_src_total_bytes"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Total Bytes Transmitted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "json_bytes"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Total JSON Bytes"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 15,
+ "x": 0,
+ "y": 11
+ },
+ "id": 1,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"general\" and\n (r._field == \"flow_src_total_bytes\" or\n r._field == \"flow_dst_total_bytes\" or\n r._field == \"json_bytes\")\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Data Processed",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ }
+ },
+ "mappings": [],
+ "unit": "bytes"
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_dst_total_bytes"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Total Bytes Received"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_src_total_bytes"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Total Bytes Transmitted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "json_bytes"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Total JSON Bytes"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 3,
+ "x": 15,
+ "y": 11
+ },
+ "id": 3,
+ "options": {
+ "legend": {
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "pieType": "pie",
+ "reduceOptions": {
+ "calcs": [
+ "sum"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"general\" and\n (r._field == \"flow_src_total_bytes\" or\n r._field == \"flow_dst_total_bytes\" or\n r._field == \"json_bytes\")\n )",
+ "refId": "A"
+ }
+ ],
+ "type": "piechart"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "binBps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 18,
+ "y": 11
+ },
+ "id": 24,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"general\" and\n r._field == \"flow_src_total_bytes\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Bytes Transmitted",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 21,
+ "y": 11
+ },
+ "id": 7,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"general\" and\n r._field == \"json_lines\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "JSON Lines",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "binBps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 18,
+ "y": 15
+ },
+ "id": 25,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"general\" and\n r._field == \"flow_dst_total_bytes\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Bytes Received",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ },
+ "unit": "binBps"
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 4,
+ "w": 3,
+ "x": 21,
+ "y": 15
+ },
+ "id": 23,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"general\" and\n (r._field == \"flow_src_total_bytes\" or\n r._field == \"flow_dst_total_bytes\")\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Total Bytes",
+ "transformations": [
+ {
+ "id": "calculateField",
+ "options": {
+ "mode": "reduceRow",
+ "reduce": {
+ "reducer": "sum"
+ },
+ "replaceFields": true
+ }
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 19
+ },
+ "id": 6,
+ "panels": [],
+ "title": "Flow",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_acceptable_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Acceptable"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_dangerous_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Dangerous"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_fun_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Fun"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_potentially_dangerous_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Potentially Dangerous"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_safe_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Safe"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_tracker_ads_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Tracker/Ads"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_unknown_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown"
+ },
+ {
+ "id": "color",
+ "value": {
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_unrated_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unrated"
+ },
+ {
+ "id": "color",
+ "value": {
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_unsafe_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unsafe"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_dangerous_count"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "dark-red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_breed_potentially_dangerous_count"
+ },
+ "properties": [
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "dark-orange",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 12,
+ "x": 0,
+ "y": 20
+ },
+ "id": 4,
+ "options": {
+ "minVizHeight": 75,
+ "minVizWidth": 75,
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": false
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"breed\"\n )",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "hide": false,
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "B"
+ }
+ ],
+ "title": "Breed",
+ "transformations": [
+ {
+ "id": "configFromData",
+ "options": {
+ "configRefId": "B",
+ "mappings": [
+ {
+ "fieldName": "Time",
+ "handlerKey": "__ignore"
+ },
+ {
+ "fieldName": "flow_active_count",
+ "handlerKey": "max"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_active_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Active Flows"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 6,
+ "w": 2,
+ "x": 12,
+ "y": 20
+ },
+ "id": 8,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Active",
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_guessed_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Guessed"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_not_detected_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Not Detected"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_detected_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Detected"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 10,
+ "x": 14,
+ "y": 20
+ },
+ "id": 9,
+ "options": {
+ "minVizHeight": 75,
+ "minVizWidth": 75,
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": false
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_detected_count\" or\n r._field == \"flow_guessed_count\" or\n r._field == \"flow_not_detected_count\")\n )",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "hide": false,
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "B"
+ }
+ ],
+ "title": "Detection",
+ "transformations": [
+ {
+ "id": "configFromData",
+ "options": {
+ "configRefId": "B",
+ "mappings": [
+ {
+ "fieldName": "Time",
+ "handlerKey": "__ignore"
+ },
+ {
+ "fieldName": "flow_active_count",
+ "handlerKey": "max"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "#EAB839",
+ "value": 50
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_adult_content_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Adult Content"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_advertisment_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Advertisment"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_allowed_site_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Allowed Site"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_antimalware_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Anti Malware"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_banned_site_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Banned Site"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_chat_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Chat"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_cloud_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Cloud"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_collaborative_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Collaborative"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_conn_check_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Connection Check"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_crypto_currency_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Crypto Currency"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_cybersecurity_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Cybersecurity"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_data_transfer_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Data Transfer"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_database_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Database"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_download_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Download"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_email_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "E-Mail"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_file_sharing_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "File Sharing"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_gambling_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Gambling"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_game_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Game"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_iot_scada_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IoT/Scada"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_malware_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malware"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_media_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Media"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_mining_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Mining"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_music_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Music"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_network_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Network"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_productivity_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Productivity"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_remote_access_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Remote Access"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_rpc_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "RPC"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_shopping_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Shopping"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_site_unavail_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Site Unavailable"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_social_network_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Social Network"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_software_update_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Software Update"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_streaming_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Streaming"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_system_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "System"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_unknown_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_unspecified_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unspecified"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_video_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Video"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_virt_assistant_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Virtual Assistant"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_voip_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "VoIP"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_vpn_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "VPN"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_category_web_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Web"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 27,
+ "w": 12,
+ "x": 0,
+ "y": 26
+ },
+ "id": 10,
+ "options": {
+ "minVizHeight": 75,
+ "minVizWidth": 75,
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showThresholdLabels": false,
+ "showThresholdMarkers": false
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"category\"\n )",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "hide": false,
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "B"
+ }
+ ],
+ "title": "Category",
+ "transformations": [
+ {
+ "id": "configFromData",
+ "options": {
+ "configRefId": "B",
+ "mappings": [
+ {
+ "fieldName": "Time",
+ "handlerKey": "__ignore"
+ },
+ {
+ "fieldName": "flow_active_count",
+ "handlerKey": "max"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "gauge"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_state_finished"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Finished"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_state_info"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Processing"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 11,
+ "w": 2,
+ "x": 12,
+ "y": 26
+ },
+ "id": 13,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"state\"\n )",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "hide": false,
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "B"
+ }
+ ],
+ "title": "State",
+ "transformations": [
+ {
+ "id": "configFromData",
+ "options": {
+ "configRefId": "B",
+ "mappings": [
+ {
+ "fieldName": "Time",
+ "handlerKey": "__ignore"
+ },
+ {
+ "fieldName": "flow_active_count",
+ "handlerKey": "max"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_by_ip"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "By IP"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_by_port"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "By Port"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_dpi"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "DPI"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_dpi_aggressive"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "DPI Aggressive"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "blue",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_dpi_cache"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "DPI Cache"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_dpi_partial"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "DPI Partial"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "light-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_dpi_partial_cache"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "DPI Partial Cache"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "super-light-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_nbpf"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "nBPF"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "blue",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_unknown"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown"
+ },
+ {
+ "id": "color",
+ "value": {
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_confidence_custom_rule"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Custom Rule"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "blue",
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 14,
+ "w": 10,
+ "x": 14,
+ "y": 28
+ },
+ "id": 14,
+ "options": {
+ "displayMode": "gradient",
+ "minVizHeight": 10,
+ "minVizWidth": 0,
+ "namePlacement": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showUnfilled": true,
+ "valueMode": "color"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"confidence\"\n )",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "hide": false,
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "B"
+ }
+ ],
+ "title": "Confidence",
+ "transformations": [
+ {
+ "id": "configFromData",
+ "options": {
+ "configRefId": "B",
+ "mappings": [
+ {
+ "fieldName": "Time",
+ "handlerKey": "__ignore"
+ },
+ {
+ "fieldName": "flow_active_count",
+ "handlerKey": "max"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "bargauge"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 5,
+ "w": 2,
+ "x": 12,
+ "y": 37
+ },
+ "id": 18,
+ "options": {
+ "colorMode": "value",
+ "graphMode": "area",
+ "justifyMode": "auto",
+ "orientation": "auto",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "textMode": "auto"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"risks\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Total Risks",
+ "transformations": [
+ {
+ "id": "calculateField",
+ "options": {
+ "mode": "reduceRow",
+ "reduce": {
+ "reducer": "sum"
+ },
+ "replaceFields": true
+ }
+ }
+ ],
+ "type": "stat"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_severity_critical"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Critical"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_severity_emergency"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Emergency"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_severity_high"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "High"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "yellow",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_severity_low"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Low"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "light-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_severity_medium"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Medium"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_severity_severe"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Severe"
+ },
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "dark-orange",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_severity_unknown"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown"
+ },
+ {
+ "id": "color",
+ "value": {
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 11,
+ "w": 12,
+ "x": 12,
+ "y": 42
+ },
+ "id": 11,
+ "options": {
+ "displayMode": "gradient",
+ "minVizHeight": 10,
+ "minVizWidth": 0,
+ "namePlacement": "auto",
+ "orientation": "horizontal",
+ "reduceOptions": {
+ "calcs": [
+ "lastNotNull"
+ ],
+ "fields": "",
+ "values": false
+ },
+ "showUnfilled": true,
+ "valueMode": "color"
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"severity\"\n )",
+ "refId": "A"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "hide": false,
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_active_count\")\n )",
+ "refId": "B"
+ }
+ ],
+ "title": "Risk Severity",
+ "transformations": [
+ {
+ "id": "configFromData",
+ "options": {
+ "configRefId": "B",
+ "mappings": [
+ {
+ "fieldName": "Time",
+ "handlerKey": "__ignore"
+ },
+ {
+ "fieldName": "flow_active_count",
+ "handlerKey": "max"
+ }
+ ]
+ }
+ }
+ ],
+ "type": "bargauge"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 53
+ },
+ "id": 32,
+ "panels": [],
+ "title": "Risks",
+ "type": "row"
+ },
+ {
+ "gridPos": {
+ "h": 24,
+ "w": 24,
+ "x": 0,
+ "y": 54
+ },
+ "id": 12,
+ "libraryPanel": {
+ "uid": "f54c2b02-7c6c-4d3f-90d8-e9d31dee65a5",
+ "name": "Risk"
+ }
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_1_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "XSS Attack"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_2_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SQL Injection"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_3_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "RCE Injection"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_4_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Binary App Transfer"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_5_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Known Proto on Non Std Port"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_6_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Self signed Cert"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_7_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Obsolete TLS v1.1 or older"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_8_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Weak TLS Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_9_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Expired"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_10_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Mismatch"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_11_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious User Agent"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_12_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Numeric IP Address"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_13_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious URL"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_14_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious Header"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_15_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS probably Not Carrying HTTPS"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_16_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious DGA Domain name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_17_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malformed Packet"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_18_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SSH Obsolete Client Version/Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_19_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SSH Obsolete Server Version/Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_20_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SMB Insecure Version"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_21_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Suspicious ESNI Usage"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_22_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unsafe Protocol"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_23_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious DNS Traffic"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_24_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Missing SNI TLS Extension"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_25_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious Content"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_26_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Risky ASN"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_27_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Risky Domain Name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_28_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malicious JA3 Fingerprint"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_29_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malicious SSL Cert/SHA1 Fingerprint"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_30_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Desktop/File-Sharing"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_31_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Uncommon TLS ALPN"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_32_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Validity Too Long"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_33_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Suspicious Extension"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_34_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Fatal Alert"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_35_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious Entropy"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_36_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Clear Text Credentials"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_37_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Large DNS Packet"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_38_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Fragmented DNS Message"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_39_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Text With Non Printable Chars"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_40_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Possible Exploit"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_41_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert About To Expire"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_42_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IDN Domain Name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_43_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Error Code"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_44_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Crawler/Bot"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_45_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Anonymous Subscriber"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_46_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unidirectional Traffic"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_47_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Obsolete Server"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_48_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Periodic Flow"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_49_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Minor Issues"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_50_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TCP Connection Issues"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_51_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Fully Encrypted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_52_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Invalid ALPN/SNI combination"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_53_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malware Host Contacted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_unknown_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown Risk"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 24,
+ "x": 0,
+ "y": 78
+ },
+ "id": 34,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"risks\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Risk",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 88
+ },
+ "id": 29,
+ "panels": [],
+ "title": "Flow (Simplified / Historic)",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "log": 2,
+ "type": "log"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byRegexp",
+ "options": "/flow_breed_.*/"
+ },
+ "properties": [
+ {
+ "id": "custom.hideFrom",
+ "value": {
+ "legend": true,
+ "tooltip": true,
+ "viz": true
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Legit"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "green",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Caution Advised"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "fixedColor": "red",
+ "mode": "fixed"
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "Dont Know"
+ },
+ "properties": [
+ {
+ "id": "color",
+ "value": {
+ "mode": "fixed"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 89
+ },
+ "id": 30,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"breed\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Breed",
+ "transformations": [
+ {
+ "id": "calculateField",
+ "options": {
+ "alias": "Caution Advised",
+ "mode": "reduceRow",
+ "reduce": {
+ "include": [
+ "flow_breed_potentially_dangerous_count breed",
+ "flow_breed_unsafe_count breed",
+ "flow_breed_dangerous_count breed"
+ ],
+ "reducer": "sum"
+ },
+ "replaceFields": false
+ }
+ },
+ {
+ "id": "calculateField",
+ "options": {
+ "alias": "Legit",
+ "mode": "reduceRow",
+ "reduce": {
+ "include": [
+ "flow_breed_acceptable_count breed",
+ "flow_breed_fun_count breed",
+ "flow_breed_safe_count breed"
+ ],
+ "reducer": "sum"
+ }
+ }
+ },
+ {
+ "id": "calculateField",
+ "options": {
+ "alias": "Dont Know",
+ "mode": "reduceRow",
+ "reduce": {
+ "include": [
+ "flow_breed_unrated_count breed",
+ "flow_breed_unknown_count breed"
+ ],
+ "reducer": "sum"
+ }
+ }
+ }
+ ],
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "fillOpacity": 70,
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "lineWidth": 1
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_detected_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Detected"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_guessed_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Guessed"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_not_detected_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Not Detected"
+ },
+ {
+ "id": "thresholds",
+ "value": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "red",
+ "value": 1
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 89
+ },
+ "id": 31,
+ "options": {
+ "colWidth": 0.9,
+ "legend": {
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": false
+ },
+ "rowHeight": 0.9,
+ "showValue": "auto",
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"detection\" and\n (r._field == \"flow_detected_count\" or\n r._field == \"flow_guessed_count\" or\n r._field == \"flow_not_detected_count\")\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Detection",
+ "type": "status-history"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "thresholds"
+ },
+ "custom": {
+ "fillOpacity": 70,
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineWidth": 0,
+ "spanNulls": false
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green"
+ },
+ {
+ "color": "yellow",
+ "value": 1
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_1_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "XSS Attack"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_2_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SQL Injection"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_3_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "RCE Injection"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_4_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Binary App Transfer"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_5_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Known Proto on Non Std Port"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_6_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Self signed Cert"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_7_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Obsolete TLS v1.1 or older"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_8_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Weak TLS Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_9_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Expired"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_10_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Mismatch"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_11_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious User Agent"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_12_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Numeric IP Address"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_13_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious URL"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_14_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious Header"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_15_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS probably Not Carrying HTTPS"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_16_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious DGA Domain name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_17_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malformed Packet"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_18_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SSH Obsolete Client Version/Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_19_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SSH Obsolete Server Version/Cipher"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_20_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "SMB Insecure Version"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_21_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Suspicious ESNI Usage"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_22_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unsafe Protocol"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_23_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious DNS Traffic"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_24_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Missing SNI TLS Extension"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_25_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Suspicious Content"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_26_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Risky ASN"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_27_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Risky Domain Name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_28_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malicious Fingerprint"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_29_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malicious SSL Cert/SHA1 Fingerprint"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_30_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Desktop/File-Sharing"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_31_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Uncommon TLS ALPN"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_32_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert Validity Too Long"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_33_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Suspicious Extension"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_34_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Fatal Alert"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_35_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Suspicious Entropy"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_36_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Clear Text Credentials"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_37_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Large DNS Packet"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_38_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Fragmented DNS Message"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_39_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Text With Non Printable Chars"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_40_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Possible Exploit"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_41_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TLS Cert About To Expire"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_42_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IDN Domain Name"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_43_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Error Code"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_44_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Crawler/Bot"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_45_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Anonymous Subscriber"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_46_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unidirectional Traffic"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_47_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "HTTP Obsolete Server"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_48_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Periodic Flow"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_49_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Minor Issues"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_50_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TCP Connection Issues"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_51_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Fully Encrypted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_52_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Invalid ALPN/SNI combination"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_53_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Malware Host Contacted"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_unknown_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Unknown Risk"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_54_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Binary Transfer Attempt"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_risk_55_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Probing Attempt"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 24,
+ "w": 24,
+ "x": 0,
+ "y": 97
+ },
+ "id": 33,
+ "options": {
+ "alignValue": "left",
+ "legend": {
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "mergeValues": true,
+ "rowHeight": 0.9,
+ "showValue": "auto",
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "pluginVersion": "10.2.0",
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"risks\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Risk",
+ "type": "state-timeline"
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 121
+ },
+ "id": 15,
+ "panels": [],
+ "title": "Layer3 / Layer4",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_l3_ip4_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv4"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_l3_ip6_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "IPv6"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_l3_other_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Other"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 0,
+ "y": 122
+ },
+ "id": 16,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"layer3\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Layer3",
+ "type": "timeseries"
+ },
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisBorderShow": false,
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "percentage",
+ "steps": [
+ {
+ "color": "green"
+ }
+ ]
+ }
+ },
+ "overrides": [
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_l4_icmp_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "ICMP"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_l4_other_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "Other"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_l4_tcp_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "TCP"
+ }
+ ]
+ },
+ {
+ "matcher": {
+ "id": "byName",
+ "options": "flow_l4_udp_count"
+ },
+ "properties": [
+ {
+ "id": "displayName",
+ "value": "UDP"
+ }
+ ]
+ }
+ ]
+ },
+ "gridPos": {
+ "h": 8,
+ "w": 12,
+ "x": 12,
+ "y": 122
+ },
+ "id": 17,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "targets": [
+ {
+ "datasource": {
+ "type": "influxdb",
+ "uid": "${DS_INFLUXDB}"
+ },
+ "query": "from(bucket: \"${ndpid_db_name}\")\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\n |> filter(fn: (r) =>\n r._measurement == \"layer4\"\n )",
+ "refId": "A"
+ }
+ ],
+ "title": "Layer4",
+ "type": "timeseries"
+ }
+ ],
+ "refresh": "10s",
+ "schemaVersion": 38,
+ "tags": [],
+ "templating": {
+ "list": [
+ {
+ "hide": 2,
+ "name": "ndpid_db_name",
+ "query": "${VAR_NDPID_DB_NAME}",
+ "skipUrlSync": false,
+ "type": "constant",
+ "current": {
+ "value": "${VAR_NDPID_DB_NAME}",
+ "text": "${VAR_NDPID_DB_NAME}",
+ "selected": false
+ },
+ "options": [
+ {
+ "value": "${VAR_NDPID_DB_NAME}",
+ "text": "${VAR_NDPID_DB_NAME}",
+ "selected": false
+ }
+ ]
+ }
+ ]
+ },
+ "time": {
+ "from": "now-15m",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "nDPId",
+ "uid": "e57b37c0-d0ba-4f50-9b2d-f83e71ae8c27",
+ "version": 111,
+ "weekStart": ""
+} \ No newline at end of file
diff --git a/examples/c-notifyd/c-notifyd.c b/examples/c-notifyd/c-notifyd.c
new file mode 100644
index 000000000..eb14c6377
--- /dev/null
+++ b/examples/c-notifyd/c-notifyd.c
@@ -0,0 +1,668 @@
+#include <dbus-1.0/dbus/dbus.h>
+#include <signal.h>
+#include <stdint.h>
+#include <syslog.h>
+
+#include "nDPIsrvd.h"
+#include "utstring.h"
+#include "utils.h"
+
+#define SLEEP_TIME_IN_S (3)
+
+struct flow_user_data
+{
+ nDPIsrvd_ull detected_risks;
+};
+
+enum dbus_level
+{
+ DBUS_LOW = 0,
+ DBUS_NORMAL,
+ DBUS_CRITICAL
+};
+
+static char const * const flow_severities[] = {"Low", "Medium", "High", "Severe", "Critical", "Emergency"};
+static char const * const flow_breeds[] = {
+ "Safe", "Acceptable", "Fun", "Unsafe", "Potentially Dangerous", "Tracker\\/Ads", "Dangerous", "Unrated", "???"};
+static char const * const flow_categories[] = {"Unspecified",
+ "Media",
+ "VPN",
+ "Email",
+ "DataTransfer",
+ "Web",
+ "SocialNetwork",
+ "Download",
+ "Game",
+ "Chat",
+ "VoIP",
+ "Database",
+ "RemoteAccess",
+ "Cloud",
+ "Network",
+ "Collaborative",
+ "RPC",
+ "Streaming",
+ "System",
+ "SoftwareUpdate",
+ "Music",
+ "Video",
+ "Shopping",
+ "Productivity",
+ "FileSharing",
+ "ConnCheck",
+ "IoT-Scada",
+ "VirtAssistant",
+ "Cybersecurity",
+ "AdultContent",
+ "Mining",
+ "Malware",
+ "Advertisement",
+ "Banned_Site",
+ "Site_Unavailable",
+ "Allowed_Site",
+ "Antimalware",
+ "Crypto_Currency"};
+
+static uint8_t desired_flow_severities[nDPIsrvd_ARRAY_LENGTH(flow_severities)] = {};
+static uint8_t desired_flow_breeds[nDPIsrvd_ARRAY_LENGTH(flow_breeds)] = {};
+static uint8_t desired_flow_categories[nDPIsrvd_ARRAY_LENGTH(flow_categories)] = {};
+
+static unsigned int id = 0;
+static char const * const application = "nDPIsrvd.notifyd";
+
+static int main_thread_shutdown = 0;
+
+static char * pidfile = NULL;
+static char * serv_optarg = NULL;
+
+#ifdef ENABLE_MEMORY_PROFILING
+void nDPIsrvd_memprof_log_alloc(size_t alloc_size)
+{
+ (void)alloc_size;
+}
+
+void nDPIsrvd_memprof_log_free(size_t free_size)
+{
+ (void)free_size;
+}
+
+void nDPIsrvd_memprof_log(char const * const format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
+ vfprintf(stderr, format, ap);
+ fprintf(stderr, "%s\n", "");
+ va_end(ap);
+}
+#endif
+
+static void send_to_dbus(char const * const icon,
+ char const * const urgency,
+ enum dbus_level level,
+ char const * const summary,
+ char const * const body,
+ int timeout)
+{
+ DBusConnection * connection = dbus_bus_get(DBUS_BUS_SESSION, 0);
+ DBusMessage * message = dbus_message_new_method_call("org.freedesktop.Notifications",
+ "/org/freedesktop/Notifications",
+ "org.freedesktop.Notifications",
+ "Notify");
+ DBusMessageIter iter[4];
+ dbus_message_iter_init_append(message, iter);
+ dbus_message_iter_append_basic(iter, 's', &application);
+ dbus_message_iter_append_basic(iter, 'u', &id);
+ dbus_message_iter_append_basic(iter, 's', &icon);
+ dbus_message_iter_append_basic(iter, 's', &summary);
+ dbus_message_iter_append_basic(iter, 's', &body);
+ dbus_message_iter_open_container(iter, 'a', "s", iter + 1);
+ dbus_message_iter_close_container(iter, iter + 1);
+ dbus_message_iter_open_container(iter, 'a', "{sv}", iter + 1);
+ dbus_message_iter_open_container(iter + 1, 'e', 0, iter + 2);
+ dbus_message_iter_append_basic(iter + 2, 's', &urgency);
+ dbus_message_iter_open_container(iter + 2, 'v', "y", iter + 3);
+ dbus_message_iter_append_basic(iter + 3, 'y', &level);
+ dbus_message_iter_close_container(iter + 2, iter + 3);
+ dbus_message_iter_close_container(iter + 1, iter + 2);
+ dbus_message_iter_close_container(iter, iter + 1);
+ dbus_message_iter_append_basic(iter, 'i', &timeout);
+ dbus_connection_send(connection, message, 0);
+ dbus_connection_flush(connection);
+ dbus_message_unref(message);
+ dbus_connection_unref(connection);
+
+ id++;
+}
+
+static void notify(enum dbus_level level, char const * const summary, int timeout, char const * const body)
+{
+ send_to_dbus("dialog-information", "urgency", level, summary, body, timeout);
+}
+
+__attribute__((format(printf, 4, 5))) static void notifyf(
+ enum dbus_level level, char const * const summary, int timeout, char const * const body_fmt, ...)
+{
+ va_list ap;
+ char buf[BUFSIZ];
+
+ va_start(ap, body_fmt);
+ if (vsnprintf(buf, sizeof(buf), body_fmt, ap) > 0)
+ {
+ notify(level, summary, timeout, buf);
+ }
+ va_end(ap);
+}
+
+static ssize_t get_value_index(char const * const possible_values[],
+ size_t possible_values_size,
+ char const * const needle,
+ size_t needle_len)
+{
+ size_t i;
+
+ for (i = 0; i < possible_values_size; ++i)
+ {
+ if (strncmp(needle, possible_values[i], needle_len) == 0)
+ {
+ break;
+ }
+ }
+
+ if (i == possible_values_size)
+ {
+ return -1;
+ }
+
+ return i;
+}
+
+static void check_value(char const * const possible_values[],
+ size_t possible_values_size,
+ char const * const needle,
+ size_t needle_len)
+{
+ if (get_value_index(possible_values, possible_values_size, needle, needle_len) == -1)
+ {
+ syslog(LOG_DAEMON | LOG_ERR, "BUG: Unknown value: %.*s", (int)needle_len, needle);
+ notifyf(DBUS_CRITICAL, "BUG", 5000, "Unknown value: %.*s", (int)needle_len, needle);
+ }
+}
+
+static enum nDPIsrvd_callback_return notifyd_json_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow)
+{
+ (void)instance;
+ (void)thread_data;
+
+ struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
+ struct flow_user_data * flow_user_data = NULL;
+
+ if (flow != NULL)
+ {
+ flow_user_data = (struct flow_user_data *)flow->flow_user_data;
+ }
+
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detected") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "detection-update") != 0 ||
+ TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "update") != 0)
+ {
+ struct nDPIsrvd_json_token const * const flow_risks = TOKEN_GET_SZ(sock, "ndpi", "flow_risk");
+ struct nDPIsrvd_json_token const * current = NULL;
+ int next_child_index = -1, desired_severity_found = 0;
+ UT_string risks;
+
+ utstring_init(&risks);
+
+ if (flow_risks != NULL)
+ {
+ while ((current = nDPIsrvd_get_next_token(sock, flow_risks, &next_child_index)) != NULL)
+ {
+ nDPIsrvd_ull numeric_risk_value = (nDPIsrvd_ull)-1;
+ size_t flow_risk_key_len = 0;
+ char const * const flow_risk_key = TOKEN_GET_KEY(sock, current, &flow_risk_key_len);
+
+ if (flow_risk_key == NULL || flow_risk_key_len == 0)
+ {
+ continue;
+ }
+
+ if (str_value_to_ull(flow_risk_key, &numeric_risk_value) == CONVERSION_OK && flow_user_data != NULL &&
+ (flow_user_data->detected_risks & (1ull << numeric_risk_value)) == 0)
+ {
+ flow_user_data->detected_risks |= (1ull << (numeric_risk_value - 1));
+
+ char flow_risk_sz[flow_risk_key_len + 1];
+ snprintf(flow_risk_sz, sizeof(flow_risk_sz), "%llu", numeric_risk_value);
+ size_t flow_risk_len = 0;
+ size_t flow_severity_len = 0;
+ char const * const flow_risk_str =
+ TOKEN_GET_VALUE(sock,
+ TOKEN_GET_SZ(sock, "ndpi", "flow_risk", flow_risk_sz, "risk"),
+ &flow_risk_len);
+ char const * const flow_severity_str =
+ TOKEN_GET_VALUE(sock,
+ TOKEN_GET_SZ(sock, "ndpi", "flow_risk", flow_risk_sz, "severity"),
+ &flow_severity_len);
+
+ if (flow_risk_str == NULL || flow_risk_len == 0 || flow_severity_str == NULL ||
+ flow_severity_len == 0)
+ {
+ continue;
+ }
+
+ ssize_t severity_index = get_value_index(flow_severities,
+ nDPIsrvd_ARRAY_LENGTH(flow_severities),
+ flow_severity_str,
+ flow_severity_len);
+ if (severity_index != -1 && desired_flow_severities[severity_index] != 0)
+ {
+ desired_severity_found = 1;
+ }
+ utstring_printf(&risks,
+ "Risk: '%.*s'\n"
+ "Severity: '%.*s'\n",
+ (int)flow_risk_len,
+ flow_risk_str,
+ (int)flow_severity_len,
+ flow_severity_str);
+ check_value(flow_severities,
+ nDPIsrvd_ARRAY_LENGTH(flow_severities),
+ flow_severity_str,
+ flow_severity_len);
+ }
+ }
+ }
+
+ {
+ size_t flow_srcip_len = 0;
+ size_t flow_dstip_len = 0;
+ size_t flow_breed_len = 0;
+ size_t flow_category_len = 0;
+ size_t flow_hostname_len = 0;
+
+ char const * const flow_srcip = TOKEN_GET_VALUE(sock, TOKEN_GET_SZ(sock, "src_ip"), &flow_srcip_len);
+ char const * const flow_dstip = TOKEN_GET_VALUE(sock, TOKEN_GET_SZ(sock, "dst_ip"), &flow_dstip_len);
+ char const * const flow_breed_str =
+ TOKEN_GET_VALUE(sock, TOKEN_GET_SZ(sock, "ndpi", "breed"), &flow_breed_len);
+ char const * const flow_category_str =
+ TOKEN_GET_VALUE(sock, TOKEN_GET_SZ(sock, "ndpi", "category"), &flow_category_len);
+ char const * const flow_hostname =
+ TOKEN_GET_VALUE(sock, TOKEN_GET_SZ(sock, "ndpi", "hostname"), &flow_hostname_len);
+
+ if (flow_breed_str != NULL && flow_breed_len != 0 && flow_category_str != NULL && flow_category_len != 0)
+ {
+ ssize_t breed_index =
+ get_value_index(flow_breeds, nDPIsrvd_ARRAY_LENGTH(flow_breeds), flow_breed_str, flow_breed_len);
+ ssize_t category_index = get_value_index(flow_categories,
+ nDPIsrvd_ARRAY_LENGTH(flow_categories),
+ flow_category_str,
+ flow_category_len);
+
+ if ((breed_index != -1 && desired_flow_breeds[breed_index] != 0) ||
+ (category_index != -1 && desired_flow_categories[category_index] != 0) ||
+ desired_severity_found != 0)
+ {
+ notifyf(DBUS_CRITICAL,
+ "Flow Notification",
+ 5000,
+ "%.*s -> %.*s (%.*s)\nBreed: '%.*s', Category: '%.*s'\n%s",
+ (int)flow_srcip_len,
+ flow_srcip,
+ (int)flow_dstip_len,
+ flow_dstip,
+ (flow_hostname_len > 0 ? (int)flow_hostname_len : 1),
+ (flow_hostname_len > 0 ? flow_hostname : "-"),
+ (int)flow_breed_len,
+ flow_breed_str,
+ (int)flow_category_len,
+ flow_category_str,
+ (utstring_len(&risks) > 0 ? utstring_body(&risks) : "No flow risks detected\n"));
+ }
+
+ check_value(flow_breeds, nDPIsrvd_ARRAY_LENGTH(flow_breeds), flow_breed_str, flow_breed_len);
+ check_value(flow_categories,
+ nDPIsrvd_ARRAY_LENGTH(flow_categories),
+ flow_category_str,
+ flow_category_len);
+ }
+ else if (desired_severity_found != 0)
+ {
+ notifyf(DBUS_CRITICAL,
+ "Risky Flow",
+ 5000,
+ "%.*s -> %.*s (%.*s)\n%s",
+ (int)flow_srcip_len,
+ flow_srcip,
+ (int)flow_dstip_len,
+ flow_dstip,
+ (flow_hostname_len > 0 ? (int)flow_hostname_len : 1),
+ (flow_hostname_len > 0 ? flow_hostname : "-"),
+ utstring_body(&risks));
+ }
+ }
+
+ utstring_done(&risks);
+ }
+
+ return CALLBACK_OK;
+}
+
+static void print_usage(char const * const arg0)
+{
+ static char const usage[] =
+ "Usage: %s "
+ "[-s host] [-C category...] [-B breed...] [-S severity...]\n\n"
+ "\t-s\tDestination where nDPIsrvd is listening on.\n"
+ "\t-C\tDesired nDPI category which fires a notificiation.\n"
+ "\t \tCan be specified multiple times.\n"
+ "\t-B\tDesired nDPI breed which fires a notification.\n"
+ "\t \tCan be specified multiple times.\n"
+ "\t-S\tDesired nDPI risk severity which fires a notification.\n"
+ "\t \tCan be specified multiple times.\n"
+ "\n"
+ "Possible values for `-C': %s\n"
+ "Possible values for `-B': %s\n"
+ "Possible values for `-S': %s\n"
+ "\n";
+
+ UT_string flow_categories_str, flow_breeds_str, flow_severities_str;
+ utstring_init(&flow_categories_str);
+ utstring_init(&flow_breeds_str);
+ utstring_init(&flow_severities_str);
+ for (size_t i = 0; i < nDPIsrvd_ARRAY_LENGTH(flow_categories); ++i)
+ {
+ utstring_printf(&flow_categories_str, "%s, ", flow_categories[i]);
+ }
+ flow_categories_str.d[flow_categories_str.i - 2] = '\0';
+ for (size_t i = 0; i < nDPIsrvd_ARRAY_LENGTH(flow_breeds); ++i)
+ {
+ utstring_printf(&flow_breeds_str, "%s, ", flow_breeds[i]);
+ }
+ flow_breeds_str.d[flow_breeds_str.i - 2] = '\0';
+ for (size_t i = 0; i < nDPIsrvd_ARRAY_LENGTH(flow_severities); ++i)
+ {
+ utstring_printf(&flow_severities_str, "%s, ", flow_severities[i]);
+ }
+ flow_severities_str.d[flow_severities_str.i - 2] = '\0';
+ fprintf(stderr,
+ usage,
+ arg0,
+ utstring_body(&flow_categories_str),
+ utstring_body(&flow_breeds_str),
+ utstring_body(&flow_severities_str));
+ utstring_done(&flow_severities_str);
+ utstring_done(&flow_breeds_str);
+ utstring_done(&flow_categories_str);
+}
+
+static int set_defaults(void)
+{
+ char const * const default_severities[] = {"High", "Severe", "Critical", "Emergency"};
+ char const * const default_breeds[] = {"Unsafe", "Potentially Dangerous", "Dangerous", "Unrated"};
+ char const * const default_categories[] = {"Mining", "Malware", "Banned_Site", "Crypto_Currency"};
+
+ for (size_t i = 0; i < nDPIsrvd_ARRAY_LENGTH(default_severities); ++i)
+ {
+ ssize_t index = get_value_index(flow_severities,
+ nDPIsrvd_ARRAY_LENGTH(flow_severities),
+ default_severities[i],
+ strlen(default_severities[i]));
+ if (index == -1)
+ {
+ return 1;
+ }
+ desired_flow_severities[index] = 1;
+ }
+
+ for (size_t i = 0; i < nDPIsrvd_ARRAY_LENGTH(default_breeds); ++i)
+ {
+ ssize_t index = get_value_index(flow_breeds,
+ nDPIsrvd_ARRAY_LENGTH(flow_breeds),
+ default_breeds[i],
+ strlen(default_breeds[i]));
+ if (index == -1)
+ {
+ return 1;
+ }
+ desired_flow_breeds[index] = 1;
+ }
+
+ for (size_t i = 0; i < nDPIsrvd_ARRAY_LENGTH(default_categories); ++i)
+ {
+ ssize_t index = get_value_index(flow_categories,
+ nDPIsrvd_ARRAY_LENGTH(flow_categories),
+ default_categories[i],
+ strlen(default_categories[i]));
+ if (index == -1)
+ {
+ return 1;
+ }
+ desired_flow_categories[index] = 1;
+ }
+
+ return 0;
+}
+
+static int parse_options(int argc, char ** argv, struct nDPIsrvd_socket * const sock)
+{
+ int opt, force_defaults = 1;
+
+ while ((opt = getopt(argc, argv, "hdp:s:C:B:S:")) != -1)
+ {
+ switch (opt)
+ {
+ case 'd':
+ daemonize_enable();
+ break;
+ case 'p':
+ free(pidfile);
+ pidfile = strdup(optarg);
+ break;
+ case 's':
+ free(serv_optarg);
+ serv_optarg = strdup(optarg);
+ break;
+ case 'C':
+ {
+ ssize_t index =
+ get_value_index(flow_categories, nDPIsrvd_ARRAY_LENGTH(flow_categories), optarg, strlen(optarg));
+ if (index == -1)
+ {
+ fprintf(stderr, "Invalid argument for `-C': %s\n", optarg);
+ return 1;
+ }
+ else
+ {
+ desired_flow_categories[index] = 1;
+ }
+ force_defaults = 0;
+ break;
+ }
+ case 'B':
+ {
+ ssize_t index =
+ get_value_index(flow_breeds, nDPIsrvd_ARRAY_LENGTH(flow_breeds), optarg, strlen(optarg));
+ if (index == -1)
+ {
+ fprintf(stderr, "Invalid argument for `-B': %s\n", optarg);
+ return 1;
+ }
+ else
+ {
+ desired_flow_breeds[index] = 1;
+ }
+ force_defaults = 0;
+ break;
+ }
+ case 'S':
+ {
+ ssize_t index =
+ get_value_index(flow_severities, nDPIsrvd_ARRAY_LENGTH(flow_severities), optarg, strlen(optarg));
+ if (index == -1)
+ {
+ fprintf(stderr, "Invalid argument for `-S': %s\n", optarg);
+ return 1;
+ }
+ else
+ {
+ desired_flow_severities[index] = 1;
+ }
+ force_defaults = 0;
+ break;
+ }
+ default:
+ print_usage(argv[0]);
+ return 1;
+ }
+ }
+
+ if (force_defaults != 0 && set_defaults() != 0)
+ {
+ fprintf(stderr, "%s\n", "BUG: Could not set default values.");
+ syslog(LOG_DAEMON | LOG_ERR, "%s\n", "BUG: Could not set default values.");
+ return 1;
+ }
+
+ if (serv_optarg == NULL)
+ {
+ serv_optarg = strdup(DISTRIBUTOR_UNIX_SOCKET);
+ }
+
+ if (nDPIsrvd_setup_address(&sock->address, serv_optarg) != 0)
+ {
+ syslog(LOG_DAEMON | LOG_ERR, "Could not parse address `%s'", serv_optarg);
+ return 1;
+ }
+
+ if (optind < argc)
+ {
+ syslog(LOG_DAEMON | LOG_ERR, "%s", "Unexpected argument after options");
+ return 1;
+ }
+
+ return 0;
+}
+
+static void sighandler(int signum)
+{
+ switch (signum)
+ {
+ case SIGINT:
+ notify(DBUS_LOW, "nDPIsrvd-notifyd", 3000, "Received SIGINT, shutdown.");
+ break;
+ case SIGTERM:
+ notify(DBUS_LOW, "nDPIsrvd-notifyd", 3000, "Received SIGTERM, shutdown.");
+ break;
+ default:
+ notify(DBUS_LOW, "nDPIsrvd-notifyd", 3000, "Received unknown signal, shutdown.");
+ break;
+ }
+
+ main_thread_shutdown++;
+}
+
+int main(int argc, char ** argv)
+{
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, SIG_IGN);
+
+ openlog("nDPIsrvd-notifyd", LOG_CONS, LOG_DAEMON);
+
+ struct nDPIsrvd_socket * sock =
+ nDPIsrvd_socket_init(0, 0, 0, sizeof(struct flow_user_data), notifyd_json_callback, NULL, NULL);
+ if (sock == NULL)
+ {
+ syslog(LOG_DAEMON | LOG_ERR, "%s", "nDPIsrvd socket memory allocation failed!");
+ return 1;
+ }
+
+ if (parse_options(argc, argv, sock) != 0)
+ {
+ goto failure;
+ }
+
+ if (daemonize_with_pidfile(pidfile) != 0)
+ {
+ return 1;
+ }
+
+ int previous_connect_succeeded = 1;
+ do
+ {
+ if (nDPIsrvd_connect(sock) != CONNECT_OK)
+ {
+ if (main_thread_shutdown != 0)
+ {
+ break;
+ }
+ if (previous_connect_succeeded != 0)
+ {
+ notifyf(DBUS_CRITICAL, "nDPIsrvd-notifyd", 3000, "nDPIsrvd socket connect to %s failed!", serv_optarg);
+ syslog(LOG_DAEMON | LOG_ERR, "nDPIsrvd socket connect to %s failed!", serv_optarg);
+ previous_connect_succeeded = 0;
+ }
+ nDPIsrvd_socket_close(sock);
+ sleep(SLEEP_TIME_IN_S);
+ continue;
+ }
+ previous_connect_succeeded = 1;
+
+ if (nDPIsrvd_set_read_timeout(sock, 3, 0) != 0)
+ {
+ syslog(LOG_DAEMON | LOG_ERR, "nDPIsrvd set read timeout failed: %s", strerror(errno));
+ goto failure;
+ }
+
+ notifyf(DBUS_NORMAL, "nDPIsrvd-notifyd", 3000, "Connected to '%s'", serv_optarg);
+ syslog(LOG_DAEMON | LOG_NOTICE, "%s", "Initialization succeeded.");
+
+ while (main_thread_shutdown == 0)
+ {
+ enum nDPIsrvd_read_return read_ret = nDPIsrvd_read(sock);
+ if (errno == EINTR)
+ {
+ continue;
+ }
+ if (read_ret == READ_TIMEOUT)
+ {
+ continue;
+ }
+ if (read_ret != READ_OK)
+ {
+ notifyf(DBUS_CRITICAL, "nDPIsrvd-notifyd", 3000, "nDPIsrvd socket read from %s failed!", serv_optarg);
+ syslog(LOG_DAEMON | LOG_ERR, "nDPIsrvd socket read from %s failed!", serv_optarg);
+ break;
+ }
+
+ enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse_all(sock);
+ if (parse_ret != PARSE_NEED_MORE_DATA)
+ {
+ syslog(LOG_DAEMON | LOG_ERR,
+ "Could not parse JSON message %s: %.*s\n",
+ nDPIsrvd_enum_to_string(parse_ret),
+ nDPIsrvd_json_buffer_length(sock),
+ nDPIsrvd_json_buffer_string(sock));
+ break;
+ }
+ }
+
+ nDPIsrvd_socket_close(sock);
+ notifyf(DBUS_NORMAL, "nDPIsrvd-notifyd", 3000, "Disconnected from '%s'.", serv_optarg);
+ if (main_thread_shutdown == 0)
+ {
+ sleep(SLEEP_TIME_IN_S);
+ }
+ } while (main_thread_shutdown == 0);
+
+failure:
+ nDPIsrvd_socket_free(&sock);
+ daemonize_shutdown(pidfile);
+ closelog();
+
+ return 0;
+}
diff --git a/examples/c-simple/c-simple.c b/examples/c-simple/c-simple.c
new file mode 100644
index 000000000..514935619
--- /dev/null
+++ b/examples/c-simple/c-simple.c
@@ -0,0 +1,273 @@
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "nDPIsrvd.h"
+
+static int main_thread_shutdown = 0;
+static struct nDPIsrvd_socket * sock = NULL;
+
+#ifdef ENABLE_MEMORY_PROFILING
+void nDPIsrvd_memprof_log_alloc(size_t alloc_size)
+{
+ (void)alloc_size;
+}
+
+void nDPIsrvd_memprof_log_free(size_t free_size)
+{
+ (void)free_size;
+}
+
+void nDPIsrvd_memprof_log(char const * const format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ fprintf(stderr, "%s", "nDPIsrvd MemoryProfiler: ");
+ vfprintf(stderr, format, ap);
+ fprintf(stderr, "%s\n", "");
+ va_end(ap);
+}
+#endif
+
+static void nDPIsrvd_write_flow_info_cb(struct nDPIsrvd_socket const * sock,
+ struct nDPIsrvd_instance const * instance,
+ struct nDPIsrvd_thread_data const * thread_data,
+ struct nDPIsrvd_flow const * flow,
+ void * user_data)
+{
+ (void)sock;
+ (void)instance;
+ (void)user_data;
+
+ fprintf(stderr,
+ "[Thread %2d][Flow %5llu][ptr: "
+#ifdef __LP64__
+ "0x%016llx"
+#else
+ "0x%08lx"
+#endif
+ "][last-seen: %13llu][idle-time: %7llu][time-until-timeout: %7llu]\n",
+ flow->thread_id,
+ flow->id_as_ull,
+#ifdef __LP64__
+ (unsigned long long int)flow,
+#else
+ (unsigned long int)flow,
+#endif
+ flow->last_seen,
+ flow->idle_time,
+ (thread_data != NULL && flow->last_seen + flow->idle_time >= thread_data->most_recent_flow_time
+ ? flow->last_seen + flow->idle_time - thread_data->most_recent_flow_time
+ : 0));
+}
+
+static void nDPIsrvd_verify_flows_cb(struct nDPIsrvd_thread_data const * const thread_data,
+ struct nDPIsrvd_flow const * const flow,
+ void * user_data)
+{
+ (void)user_data;
+
+ if (thread_data != NULL)
+ {
+ if (flow->last_seen + flow->idle_time >= thread_data->most_recent_flow_time)
+ {
+ fprintf(stderr,
+ "Thread %d / %d, Flow %llu verification failed\n",
+ thread_data->thread_key,
+ flow->thread_id,
+ flow->id_as_ull);
+ }
+ else
+ {
+ fprintf(stderr,
+ "Thread %d / %d, Flow %llu verification failed, diff: %llu\n",
+ thread_data->thread_key,
+ flow->thread_id,
+ flow->id_as_ull,
+ thread_data->most_recent_flow_time - flow->last_seen + flow->idle_time);
+ }
+ }
+ else
+ {
+ fprintf(stderr, "Thread [UNKNOWN], Flow %llu verification failed\n", flow->id_as_ull);
+ }
+}
+
+static void sighandler(int signum)
+{
+ struct nDPIsrvd_instance * current_instance;
+ struct nDPIsrvd_instance * itmp;
+ int verification_failed = 0;
+
+ if (signum == SIGUSR1)
+ {
+ nDPIsrvd_flow_info(sock, nDPIsrvd_write_flow_info_cb, NULL);
+
+ HASH_ITER(hh, sock->instance_table, current_instance, itmp)
+ {
+ if (nDPIsrvd_verify_flows(current_instance, nDPIsrvd_verify_flows_cb, NULL) != 0)
+ {
+ fprintf(stderr, "Flow verification failed for instance %d\n", current_instance->alias_source_key);
+ verification_failed = 1;
+ }
+ }
+ if (verification_failed == 0)
+ {
+ fprintf(stderr, "%s\n", "Flow verification succeeded.");
+ }
+ else
+ {
+ /* FATAL! */
+ exit(EXIT_FAILURE);
+ }
+ }
+ else if (main_thread_shutdown == 0)
+ {
+ main_thread_shutdown = 1;
+ }
+}
+
+static enum nDPIsrvd_callback_return simple_json_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow)
+{
+ (void)sock;
+ (void)thread_data;
+
+ if (flow == NULL)
+ {
+ return CALLBACK_OK;
+ }
+
+ struct nDPIsrvd_json_token const * const alias = TOKEN_GET_SZ(sock, "alias");
+ struct nDPIsrvd_json_token const * const source = TOKEN_GET_SZ(sock, "source");
+ if (alias == NULL || source == NULL)
+ {
+ return CALLBACK_ERROR;
+ }
+
+ struct nDPIsrvd_json_token const * const flow_event_name = TOKEN_GET_SZ(sock, "flow_event_name");
+ if (TOKEN_VALUE_EQUALS_SZ(sock, flow_event_name, "new") != 0)
+ {
+ printf("Instance %.*s/%.*s (HT-Key: 0x%x), Thread %d, Flow %llu new\n",
+ nDPIsrvd_get_token_size(sock, alias),
+ nDPIsrvd_get_token_value(sock, alias),
+ nDPIsrvd_get_token_size(sock, source),
+ nDPIsrvd_get_token_value(sock, source),
+ instance->alias_source_key,
+ flow->thread_id,
+ flow->id_as_ull);
+ }
+
+ return CALLBACK_OK;
+}
+
+static void simple_flow_cleanup_callback(struct nDPIsrvd_socket * const sock,
+ struct nDPIsrvd_instance * const instance,
+ struct nDPIsrvd_thread_data * const thread_data,
+ struct nDPIsrvd_flow * const flow,
+ enum nDPIsrvd_cleanup_reason reason)
+{
+ (void)sock;
+ (void)thread_data;
+
+ struct nDPIsrvd_json_token const * const alias = TOKEN_GET_SZ(sock, "alias");
+ struct nDPIsrvd_json_token const * const source = TOKEN_GET_SZ(sock, "source");
+ if (alias == NULL || source == NULL)
+ {
+ /* FATAL! */
+ fprintf(stderr, "BUG: Missing JSON token alias/source.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ char const * const reason_str = nDPIsrvd_enum_to_string(reason);
+ printf("Instance %.*s/%.*s (HT-Key: 0x%x), Thread %d, Flow %llu cleanup, reason: %s\n",
+ nDPIsrvd_get_token_size(sock, alias),
+ nDPIsrvd_get_token_value(sock, alias),
+ nDPIsrvd_get_token_size(sock, source),
+ nDPIsrvd_get_token_value(sock, source),
+ instance->alias_source_key,
+ flow->thread_id,
+ flow->id_as_ull,
+ (reason_str != NULL ? reason_str : "UNKNOWN"));
+
+ if (reason == CLEANUP_REASON_FLOW_TIMEOUT)
+ {
+ /* FATAL! */
+ fprintf(stderr, "Flow %llu timeouted.\n", flow->id_as_ull);
+ exit(EXIT_FAILURE);
+ }
+}
+
+int main(int argc, char ** argv)
+{
+ signal(SIGUSR1, sighandler);
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+
+ sock = nDPIsrvd_socket_init(0, 0, 0, 0, simple_json_callback, NULL, simple_flow_cleanup_callback);
+ if (sock == NULL)
+ {
+ return 1;
+ }
+
+ if (nDPIsrvd_setup_address(&sock->address, (argc > 1 ? argv[1] : "127.0.0.1:7000")) != 0)
+ {
+ return 1;
+ }
+
+ if (nDPIsrvd_connect(sock) != CONNECT_OK)
+ {
+ nDPIsrvd_socket_free(&sock);
+ return 1;
+ }
+
+ if (nDPIsrvd_set_read_timeout(sock, 3, 0) != 0)
+ {
+ return 1;
+ }
+
+ enum nDPIsrvd_read_return read_ret = READ_OK;
+ while (main_thread_shutdown == 0)
+ {
+ read_ret = nDPIsrvd_read(sock);
+ if (errno == EINTR)
+ {
+ continue;
+ }
+ if (read_ret == READ_TIMEOUT)
+ {
+ printf("No data received during the last %llu second(s).\n",
+ (long long unsigned int)sock->read_timeout.tv_sec);
+ continue;
+ }
+ if (read_ret != READ_OK)
+ {
+ break;
+ }
+
+ enum nDPIsrvd_parse_return parse_ret = nDPIsrvd_parse_all(sock);
+ if (parse_ret != PARSE_NEED_MORE_DATA)
+ {
+ printf("Could not parse JSON message %s: %.*s\n",
+ nDPIsrvd_enum_to_string(parse_ret),
+ nDPIsrvd_json_buffer_length(sock),
+ nDPIsrvd_json_buffer_string(sock));
+ break;
+ }
+ }
+
+ if (main_thread_shutdown == 0 && read_ret != READ_OK)
+ {
+ printf("Parse read %s at JSON: %.*s\n",
+ nDPIsrvd_enum_to_string(read_ret),
+ nDPIsrvd_json_buffer_length(sock),
+ nDPIsrvd_json_buffer_string(sock));
+ }
+
+ return 1;
+}
diff --git a/examples/cxx-graph b/examples/cxx-graph
new file mode 160000
+Subproject 68eb1b105d7fde5fa8139d04ba376887da9a7ac
diff --git a/examples/js-rt-analyzer b/examples/js-rt-analyzer
new file mode 160000
+Subproject 87cb7a0af5e25675b7d22be17a4ec4a9f62d671
diff --git a/examples/js-rt-analyzer-frontend b/examples/js-rt-analyzer-frontend
new file mode 160000
+Subproject 6806ef7d13e95a3af2ef722b9c36ad122be1ec4
diff --git a/examples/ndpid_grafana_example.png b/examples/ndpid_grafana_example.png
new file mode 100644
index 000000000..2faa666f0
--- /dev/null
+++ b/examples/ndpid_grafana_example.png
Binary files differ
diff --git a/examples/ndpid_install_and_run.gif b/examples/ndpid_install_and_run.gif
new file mode 100644
index 000000000..c0a7ff95f
--- /dev/null
+++ b/examples/ndpid_install_and_run.gif
Binary files differ
diff --git a/examples/py-flow-dashboard/assets/flow-dash.css b/examples/py-flow-dashboard/assets/flow-dash.css
new file mode 100644
index 000000000..4d813b5ef
--- /dev/null
+++ b/examples/py-flow-dashboard/assets/flow-dash.css
@@ -0,0 +1,3 @@
+body {
+ background: black;
+}
diff --git a/examples/py-flow-dashboard/flow-dash.py b/examples/py-flow-dashboard/flow-dash.py
new file mode 100755
index 000000000..d396e7e97
--- /dev/null
+++ b/examples/py-flow-dashboard/flow-dash.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python3
+
+import multiprocessing
+import os
+import sys
+import time
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket
+import plotly_dash
+
+FLOW_RISK_SEVERE = 4
+FLOW_RISK_HIGH = 3
+FLOW_RISK_MEDIUM = 2
+FLOW_RISK_LOW = 1
+
+def nDPIsrvd_worker_onFlowCleanup(instance, current_flow, global_user_data):
+ _, shared_flow_dict = global_user_data
+
+ flow_key = current_flow.flow_key
+
+ shared_flow_dict['current-flows'] -= 1
+
+ if flow_key not in shared_flow_dict:
+ return True
+
+ shared_flow_dict['total-l4-bytes'] += shared_flow_dict[flow_key]['total-l4-bytes']
+
+ if shared_flow_dict[flow_key]['is_detected'] is True:
+ shared_flow_dict['current-detected-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_guessed'] is True:
+ shared_flow_dict['current-guessed-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_not_detected'] is True:
+ shared_flow_dict['current-not-detected-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_midstream'] is True:
+ shared_flow_dict['current-midstream-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_risky'] > 0:
+ shared_flow_dict['current-risky-flows'] -= 1
+
+ if shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_LOW:
+ shared_flow_dict['current-risky-flows-low'] -= 1
+ elif shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_MEDIUM:
+ shared_flow_dict['current-risky-flows-medium'] -= 1
+ elif shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_HIGH:
+ shared_flow_dict['current-risky-flows-high'] -= 1
+ elif shared_flow_dict[flow_key]['is_risky'] == FLOW_RISK_SEVERE:
+ shared_flow_dict['current-risky-flows-severe'] -= 1
+
+ del shared_flow_dict[current_flow.flow_key]
+
+ return True
+
+def nDPIsrvd_worker_onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ nsock, shared_flow_dict = global_user_data
+
+ shared_flow_dict['total-events'] += 1
+ shared_flow_dict['total-json-bytes'] = nsock.received_bytes
+
+ if 'error_event_name' in json_dict:
+ shared_flow_dict['total-base-events'] += 1
+
+ if 'daemon_event_name' in json_dict:
+ shared_flow_dict['total-daemon-events'] += 1
+
+ if 'packet_event_name' in json_dict and \
+ (json_dict['packet_event_name'] == 'packet' or \
+ json_dict['packet_event_name'] == 'packet-flow'):
+ shared_flow_dict['total-packet-events'] += 1
+
+ if 'flow_id' not in json_dict:
+ return True
+ else:
+ flow_key = json_dict['alias'] + '-' + json_dict['source'] + '-' + str(json_dict['flow_id'])
+
+ if flow_key not in shared_flow_dict:
+ current_flow.flow_key = flow_key
+ shared_flow_dict[flow_key] = mgr.dict()
+ shared_flow_dict[flow_key]['is_detected'] = False
+ shared_flow_dict[flow_key]['is_guessed'] = False
+ shared_flow_dict[flow_key]['is_not_detected'] = False
+ shared_flow_dict[flow_key]['is_midstream'] = False
+ shared_flow_dict[flow_key]['is_risky'] = 0
+ shared_flow_dict[flow_key]['total-l4-bytes'] = 0
+
+ shared_flow_dict[flow_key]['json'] = mgr.dict()
+
+ shared_flow_dict['total-flows'] += 1
+ shared_flow_dict['current-flows'] += 1
+
+ if current_flow.flow_key != flow_key:
+ return False
+
+ if 'flow_src_tot_l4_payload_len' in json_dict and 'flow_dst_tot_l4_payload_len' in json_dict:
+ shared_flow_dict[flow_key]['total-l4-bytes'] = json_dict['flow_src_tot_l4_payload_len'] + \
+ json_dict['flow_dst_tot_l4_payload_len']
+
+ if 'midstream' in json_dict and json_dict['midstream'] != 0:
+ if shared_flow_dict[flow_key]['is_midstream'] is False:
+ shared_flow_dict['total-midstream-flows'] += 1
+ shared_flow_dict['current-midstream-flows'] += 1
+ shared_flow_dict[flow_key]['is_midstream'] = True
+
+ if 'ndpi' in json_dict:
+ shared_flow_dict[flow_key]['json']['ndpi'] = json_dict['ndpi']
+
+ if 'flow_risk' in json_dict['ndpi']:
+ if shared_flow_dict[flow_key]['is_risky'] == 0:
+ shared_flow_dict['total-risky-flows'] += 1
+ shared_flow_dict['current-risky-flows'] += 1
+
+ severity = shared_flow_dict[flow_key]['is_risky']
+ if severity == FLOW_RISK_LOW:
+ shared_flow_dict['current-risky-flows-low'] -= 1
+ elif severity == FLOW_RISK_MEDIUM:
+ shared_flow_dict['current-risky-flows-medium'] -= 1
+ elif severity == FLOW_RISK_HIGH:
+ shared_flow_dict['current-risky-flows-high'] -= 1
+ elif severity == FLOW_RISK_SEVERE:
+ shared_flow_dict['current-risky-flows-severe'] -= 1
+
+ for key in json_dict['ndpi']['flow_risk']:
+ if json_dict['ndpi']['flow_risk'][key]['severity'] == 'Low':
+ severity = max(severity, FLOW_RISK_LOW)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'Medium':
+ severity = max(severity, FLOW_RISK_MEDIUM)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'High':
+ severity = max(severity, FLOW_RISK_HIGH)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'Severe':
+ severity = max(severity, FLOW_RISK_SEVERE)
+ else:
+ raise RuntimeError('Invalid flow risk severity: {}'.format(
+ json_dict['ndpi']['flow_risk'][key]['severity']))
+
+ shared_flow_dict[flow_key]['is_risky'] = severity
+ if severity == FLOW_RISK_LOW:
+ shared_flow_dict['current-risky-flows-low'] += 1
+ elif severity == FLOW_RISK_MEDIUM:
+ shared_flow_dict['current-risky-flows-medium'] += 1
+ elif severity == FLOW_RISK_HIGH:
+ shared_flow_dict['current-risky-flows-high'] += 1
+ elif severity == FLOW_RISK_SEVERE:
+ shared_flow_dict['current-risky-flows-severe'] += 1
+
+ if 'flow_event_name' not in json_dict:
+ return True
+
+ if json_dict['flow_state'] == 'finished' and \
+ json_dict['ndpi']['proto'] != 'Unknown' and \
+ shared_flow_dict[flow_key]['is_detected'] is False:
+ shared_flow_dict['total-detected-flows'] += 1
+ shared_flow_dict['current-detected-flows'] += 1
+ shared_flow_dict[flow_key]['is_detected'] = True
+
+ if json_dict['flow_event_name'] == 'new':
+
+ shared_flow_dict['total-flow-new-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'update':
+
+ shared_flow_dict['total-flow-update-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'analyse':
+
+ shared_flow_dict['total-flow-analyse-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'end':
+
+ shared_flow_dict['total-flow-end-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'idle':
+
+ shared_flow_dict['total-flow-idle-events'] += 1
+
+ elif json_dict['flow_event_name'] == 'guessed':
+
+ shared_flow_dict['total-flow-guessed-events'] += 1
+
+ if shared_flow_dict[flow_key]['is_guessed'] is False:
+ shared_flow_dict['total-guessed-flows'] += 1
+ shared_flow_dict['current-guessed-flows'] += 1
+ shared_flow_dict[flow_key]['is_guessed'] = True
+
+ elif json_dict['flow_event_name'] == 'not-detected':
+
+ shared_flow_dict['total-flow-not-detected-events'] += 1
+
+ if shared_flow_dict[flow_key]['is_not_detected'] is False:
+ shared_flow_dict['total-not-detected-flows'] += 1
+ shared_flow_dict['current-not-detected-flows'] += 1
+ shared_flow_dict[flow_key]['is_not_detected'] = True
+
+ elif json_dict['flow_event_name'] == 'detected' or \
+ json_dict['flow_event_name'] == 'detection-update':
+
+ if json_dict['flow_event_name'] == 'detection-update':
+ shared_flow_dict['total-flow-detection-update-events'] += 1
+ else:
+ shared_flow_dict['total-flow-detected-events'] += 1
+
+ if shared_flow_dict[flow_key]['is_detected'] is False:
+ shared_flow_dict['total-detected-flows'] += 1
+ shared_flow_dict['current-detected-flows'] += 1
+ shared_flow_dict[flow_key]['is_detected'] = True
+
+ if shared_flow_dict[flow_key]['is_guessed'] is True:
+ shared_flow_dict['total-guessed-flows'] -= 1
+ shared_flow_dict['current-guessed-flows'] -= 1
+ shared_flow_dict[flow_key]['is_guessed'] = False
+
+ return True
+
+
+def nDPIsrvd_worker(address, shared_flow_dict):
+ sys.stderr.write('Recv buffer size: {}\n'
+ .format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'
+ .format(address[0]+':'+str(address[1])
+ if type(address) is tuple else address))
+
+ try:
+ while True:
+ try:
+ nsock = nDPIsrvdSocket()
+ nsock.connect(address)
+ nsock.loop(nDPIsrvd_worker_onJsonLineRecvd,
+ nDPIsrvd_worker_onFlowCleanup,
+ (nsock, shared_flow_dict))
+ except nDPIsrvd.SocketConnectionBroken:
+ sys.stderr.write('Lost connection to {} .. reconnecting\n'
+ .format(address[0]+':'+str(address[1])
+ if type(address) is tuple else address))
+ time.sleep(1.0)
+ except KeyboardInterrupt:
+ pass
+
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser()
+ argparser.add_argument('--listen-address', type=str, default='127.0.0.1', help='Plotly listen address')
+ argparser.add_argument('--listen-port', type=str, default=8050, help='Plotly listen port')
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ mgr = multiprocessing.Manager()
+ shared_flow_dict = mgr.dict()
+
+ shared_flow_dict['total-events'] = 0
+ shared_flow_dict['total-flow-new-events'] = 0
+ shared_flow_dict['total-flow-update-events'] = 0
+ shared_flow_dict['total-flow-analyse-events'] = 0
+ shared_flow_dict['total-flow-end-events'] = 0
+ shared_flow_dict['total-flow-idle-events'] = 0
+ shared_flow_dict['total-flow-detected-events'] = 0
+ shared_flow_dict['total-flow-detection-update-events'] = 0
+ shared_flow_dict['total-flow-guessed-events'] = 0
+ shared_flow_dict['total-flow-not-detected-events'] = 0
+ shared_flow_dict['total-packet-events'] = 0
+ shared_flow_dict['total-base-events'] = 0
+ shared_flow_dict['total-daemon-events'] = 0
+
+ shared_flow_dict['total-json-bytes'] = 0
+ shared_flow_dict['total-l4-bytes'] = 0
+ shared_flow_dict['total-flows'] = 0
+ shared_flow_dict['total-detected-flows'] = 0
+ shared_flow_dict['total-risky-flows'] = 0
+ shared_flow_dict['total-midstream-flows'] = 0
+ shared_flow_dict['total-guessed-flows'] = 0
+ shared_flow_dict['total-not-detected-flows'] = 0
+
+ shared_flow_dict['current-flows'] = 0
+ shared_flow_dict['current-detected-flows'] = 0
+ shared_flow_dict['current-midstream-flows'] = 0
+ shared_flow_dict['current-guessed-flows'] = 0
+ shared_flow_dict['current-not-detected-flows'] = 0
+
+ shared_flow_dict['current-risky-flows'] = 0
+ shared_flow_dict['current-risky-flows-severe'] = 0
+ shared_flow_dict['current-risky-flows-high'] = 0
+ shared_flow_dict['current-risky-flows-medium'] = 0
+ shared_flow_dict['current-risky-flows-low'] = 0
+
+ nDPIsrvd_job = multiprocessing.Process(target=nDPIsrvd_worker,
+ args=(address, shared_flow_dict))
+ nDPIsrvd_job.start()
+
+ web_job = multiprocessing.Process(target=plotly_dash.web_worker,
+ args=(shared_flow_dict, args.listen_address, args.listen_port))
+ web_job.start()
+
+ nDPIsrvd_job.join()
+ web_job.terminate()
+ web_job.join()
diff --git a/examples/py-flow-dashboard/plotly_dash.py b/examples/py-flow-dashboard/plotly_dash.py
new file mode 100644
index 000000000..34791d8b5
--- /dev/null
+++ b/examples/py-flow-dashboard/plotly_dash.py
@@ -0,0 +1,415 @@
+import math
+
+import dash
+
+try:
+ from dash import dcc
+except ImportError:
+ import dash_core_components as dcc
+
+try:
+ from dash import html
+except ImportError:
+ import dash_html_components as html
+
+try:
+ from dash import dash_table as dt
+except ImportError:
+ import dash_table as dt
+
+from dash.dependencies import Input, Output, State
+
+import dash_daq as daq
+
+import plotly.graph_objects as go
+
+global shared_flow_dict
+
+app = dash.Dash(__name__)
+
+def generate_box():
+ return {
+ 'display': 'flex', 'flex-direction': 'row',
+ 'background-color': '#082255'
+ }
+
+def generate_led_display(div_id, label_name):
+ return daq.LEDDisplay(
+ id=div_id,
+ label={'label': label_name, 'style': {'color': '#C4CDD5'}},
+ labelPosition='bottom',
+ value='0',
+ backgroundColor='#082255',
+ color='#C4CDD5',
+ )
+
+def generate_gauge(div_id, label_name, max_value=10):
+ return daq.Gauge(
+ id=div_id,
+ value=0,
+ label={'label': label_name, 'style': {'color': '#C4CDD5'}},
+ max=max_value,
+ min=0,
+ )
+
+def build_gauge(key, max_value=100):
+ gauge_max = int(max(max_value,
+ shared_flow_dict[key]))
+ grad_green = [0, int(gauge_max * 1/3)]
+ grad_yellow = [int(gauge_max * 1/3), int(gauge_max * 2/3)]
+ grad_red = [int(gauge_max * 2/3), gauge_max]
+
+ grad_dict = {
+ "gradient":True,
+ "ranges":{
+ "green":grad_green,
+ "yellow":grad_yellow,
+ "red":grad_red
+ }
+ }
+
+ return shared_flow_dict[key], gauge_max, grad_dict
+
+def build_piechart(labels, values, color_map=None):
+ lay = dict(
+ plot_bgcolor = '#082255',
+ paper_bgcolor = '#082255',
+ font={"color": "#fff"},
+ uirevision=True,
+ autosize=True,
+ height=250,
+ margin = {'autoexpand': True, 'b': 0, 'l': 0, 'r': 0, 't': 0, 'pad': 0},
+ width = 500,
+ uniformtext_minsize = 12,
+ uniformtext_mode = 'hide',
+ )
+
+ return go.Figure(layout=lay, data=[go.Pie(labels=labels, values=values, sort=False, marker_colors=color_map, textinfo='percent', textposition='inside')])
+
+COLOR_MAP = {
+ 'piechart-flows': ['rgb(153, 153, 255)', 'rgb(153, 204, 255)', 'rgb(255, 204, 153)', 'rgb(255, 255, 255)'],
+ 'piechart-midstream-flows': ['rgb(255, 255, 153)', 'rgb(153, 153, 255)'],
+ 'piechart-risky-flows': ['rgb(255, 0, 0)', 'rgb(255, 128, 0)', 'rgb(255, 255, 0)', 'rgb(128, 255, 0)', 'rgb(153, 153, 255)'],
+ 'graph-flows': {'Current Active Flows': {'color': 'rgb(153, 153, 255)', 'width': 1},
+ 'Current Risky Flows': {'color': 'rgb(255, 153, 153)', 'width': 3},
+ 'Current Midstream Flows': {'color': 'rgb(255, 255, 153)', 'width': 3},
+ 'Current Guessed Flows': {'color': 'rgb(153, 204, 255)', 'width': 1},
+ 'Current Not-Detected Flows': {'color': 'rgb(255, 204, 153)', 'width': 1},
+ 'Current Unclassified Flows': {'color': 'rgb(255, 255, 255)', 'width': 1},
+ },
+}
+
+def generate_tab_flow():
+ return html.Div([
+ html.Div(children=[
+ dcc.Interval(id="tab-flow-default-interval", interval=1 * 2000, n_intervals=0),
+
+ html.Div(children=[
+
+ dt.DataTable(
+ id='table-info',
+ columns=[{'id': c.lower(), 'name': c, 'editable': False}
+ for c in ['Name', 'Total']],
+ style_header={
+ 'backgroundColor': '#082233',
+ 'color': 'white'
+ },
+ style_data={
+ 'backgroundColor': '#082244',
+ 'color': 'white'
+ },
+ )
+
+ ], style={'display': 'flex', 'flex-direction': 'row'}),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-flows',
+ config={
+ 'displayModeBar': False,
+ },
+ figure=build_piechart(['Detected', 'Guessed', 'Not-Detected', 'Unclassified'],
+ [0, 0, 0, 0], COLOR_MAP['piechart-flows']),
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-midstream-flows',
+ config={
+ 'displayModeBar': False,
+ },
+ figure=build_piechart(['Midstream', 'Not Midstream'],
+ [0, 0], COLOR_MAP['piechart-midstream-flows']),
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-risky-flows',
+ config={
+ 'displayModeBar': False,
+ },
+ figure=build_piechart(['Severy Risk', 'High Risk', 'Medium Risk', 'Low Risk', 'No Risk'],
+ [0, 0], COLOR_MAP['piechart-risky-flows']),
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+ ], style=generate_box()),
+
+ html.Div(children=[
+ dcc.Interval(id="tab-flow-graph-interval", interval=4 * 1000, n_intervals=0),
+ dcc.Store(id="graph-traces"),
+
+ html.Div(children=[
+ dcc.Graph(
+ id="graph-flows",
+ config={
+ 'displayModeBar': True,
+ 'displaylogo': False,
+ },
+ style={'height':'60vh'},
+ ),
+ ], style={'padding': 10, 'flex': 1})
+ ], style=generate_box())
+ ])
+
+def generate_tab_other():
+ return html.Div([
+ html.Div(children=[
+ dcc.Interval(id="tab-other-default-interval", interval=1 * 2000, n_intervals=0),
+
+ html.Div(children=[
+ dcc.Graph(
+ id='piechart-events',
+ config={
+ 'displayModeBar': False,
+ },
+ ),
+ ], style={'padding': 10, 'flex': 1}),
+ ], style=generate_box())
+ ])
+
+TABS_STYLES = {
+ 'height': '34px'
+}
+TAB_STYLE = {
+ 'borderBottom': '1px solid #d6d6d6',
+ 'backgroundColor': '#385285',
+ 'padding': '6px',
+ 'fontWeight': 'bold',
+}
+TAB_SELECTED_STYLE = {
+ 'borderTop': '1px solid #d6d6d6',
+ 'borderBottom': '1px solid #d6d6d6',
+ 'backgroundColor': '#119DFF',
+ 'color': 'white',
+ 'padding': '6px'
+}
+
+app.layout = html.Div([
+ dcc.Tabs(id="tabs-flow-dash", value="tab-flows", children=[
+ dcc.Tab(label="Flow", value="tab-flows", style=TAB_STYLE,
+ selected_style=TAB_SELECTED_STYLE,
+ children=generate_tab_flow()),
+ dcc.Tab(label="Other", value="tab-other", style=TAB_STYLE,
+ selected_style=TAB_SELECTED_STYLE,
+ children=generate_tab_other()),
+ ], style=TABS_STYLES),
+ html.Div(id="tabs-content")
+])
+
+def prettifyBytes(bytes_received):
+ size_names = ['B', 'KB', 'MB', 'GB', 'TB']
+ if bytes_received == 0:
+ i = 0
+ else:
+ i = min(int(math.floor(math.log(bytes_received, 1024))), len(size_names) - 1)
+ p = math.pow(1024, i)
+ s = round(bytes_received / p, 2)
+ return '{:.2f} {}'.format(s, size_names[i])
+
+@app.callback(output=[Output('table-info', 'data'),
+ Output('piechart-flows', 'figure'),
+ Output('piechart-midstream-flows', 'figure'),
+ Output('piechart-risky-flows', 'figure')],
+
+ inputs=[Input('tab-flow-default-interval', 'n_intervals')])
+def tab_flow_update_components(n):
+ return [[{'name': 'JSON Events', 'total': shared_flow_dict['total-events']},
+ {'name': 'JSON Bytes', 'total': prettifyBytes(shared_flow_dict['total-json-bytes'])},
+ {'name': 'Layer4 Bytes', 'total': prettifyBytes(shared_flow_dict['total-l4-bytes'])},
+ {'name': 'Flows', 'total': shared_flow_dict['total-flows']},
+ {'name': 'Risky Flows', 'total': shared_flow_dict['total-risky-flows']},
+ {'name': 'Midstream Flows', 'total': shared_flow_dict['total-midstream-flows']},
+ {'name': 'Guessed Flows', 'total': shared_flow_dict['total-guessed-flows']},
+ {'name': 'Not Detected Flows', 'total': shared_flow_dict['total-not-detected-flows']}],
+ build_piechart(['Detected', 'Guessed', 'Not-Detected', 'Unclassified'],
+ [shared_flow_dict['current-detected-flows'],
+ shared_flow_dict['current-guessed-flows'],
+ shared_flow_dict['current-not-detected-flows'],
+ shared_flow_dict['current-flows']
+ - shared_flow_dict['current-detected-flows']
+ - shared_flow_dict['current-guessed-flows']
+ - shared_flow_dict['current-not-detected-flows']],
+ COLOR_MAP['piechart-flows']),
+ build_piechart(['Midstream', 'Not Midstream'],
+ [shared_flow_dict['current-midstream-flows'],
+ shared_flow_dict['current-flows'] -
+ shared_flow_dict['current-midstream-flows']],
+ COLOR_MAP['piechart-midstream-flows']),
+ build_piechart(['Severe', 'High', 'Medium', 'Low', 'No Risk'],
+ [shared_flow_dict['current-risky-flows-severe'],
+ shared_flow_dict['current-risky-flows-high'],
+ shared_flow_dict['current-risky-flows-medium'],
+ shared_flow_dict['current-risky-flows-low'],
+ shared_flow_dict['current-flows'] -
+ shared_flow_dict['current-risky-flows']],
+ COLOR_MAP['piechart-risky-flows'])]
+
+@app.callback(output=[Output('graph-flows', 'figure'),
+ Output('graph-traces', 'data')],
+ inputs=[Input('tab-flow-graph-interval', 'n_intervals'),
+ Input('tab-flow-graph-interval', 'interval')],
+ state=[State('graph-traces', 'data')])
+def tab_flow_update_graph(n, i, traces):
+ if traces is None:
+ traces = ([], [], [], [], [], [])
+
+ max_bins = 75
+
+ traces[0].append(shared_flow_dict['current-flows'])
+ traces[1].append(shared_flow_dict['current-risky-flows'])
+ traces[2].append(shared_flow_dict['current-midstream-flows'])
+ traces[3].append(shared_flow_dict['current-guessed-flows'])
+ traces[4].append(shared_flow_dict['current-not-detected-flows'])
+ traces[5].append(shared_flow_dict['current-flows']
+ - shared_flow_dict['current-detected-flows']
+ - shared_flow_dict['current-guessed-flows']
+ - shared_flow_dict['current-not-detected-flows'])
+ if len(traces[0]) > max_bins:
+ traces[0] = traces[0][1:]
+ traces[1] = traces[1][1:]
+ traces[2] = traces[2][1:]
+ traces[3] = traces[3][1:]
+ traces[4] = traces[4][1:]
+ traces[5] = traces[5][1:]
+
+ i /= 1000.0
+ x = list(range(max(n - max_bins, 0) * int(i), n * int(i), max(int(i), 0)))
+ if len(x) > 0 and x[0] > 60:
+ x = [round(t / 60, 2) for t in x]
+ x_div = 60
+ x_axis_title = 'Time (min)'
+ else:
+ x_div = 1
+ x_axis_title = 'Time (sec)'
+ min_x = max(0, x[0] if len(x) >= max_bins else 0)
+ max_x = max((max_bins * i) / x_div, x[max_bins - 1] if len(x) >= max_bins else 0)
+
+ lay = dict(
+ plot_bgcolor = '#082255',
+ paper_bgcolor = '#082255',
+ font={"color": "#fff"},
+ xaxis = {
+ 'title': x_axis_title,
+ "showgrid": False,
+ "showline": False,
+ "fixedrange": True,
+ "tickmode": 'linear',
+ "tick0": round(max_bins / x_div, 2),
+ "dtick": round(max_bins / x_div, 2),
+ },
+ yaxis = {
+ 'title': 'Flow Count',
+ "showgrid": False,
+ "showline": False,
+ "zeroline": False,
+ "fixedrange": True,
+ "tickmode": 'linear',
+ "dtick": 10,
+ },
+ uirevision=True,
+ autosize=True,
+ bargap=0.01,
+ bargroupgap=0,
+ hovermode="closest",
+ margin = {'b': 0, 'l': 0, 'r': 0, 't': 30, 'pad': 0},
+ legend = {'borderwidth': 0},
+ )
+
+ fig = go.Figure(layout=lay)
+ fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='#004D80', zeroline=True, zerolinewidth=1, range=[min_x, max_x])
+ fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='#004D80', zeroline=True, zerolinewidth=1)
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[0],
+ name='Current Active Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Active Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[1],
+ name='Current Risky Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Risky Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[2],
+ name='Current Midstream Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Midstream Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[3],
+ name='Current Guessed Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Guessed Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[4],
+ name='Current Not-Detected Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Not-Detected Flows'],
+ ))
+ fig.add_trace(go.Scatter(
+ x=x,
+ y=traces[5],
+ name='Current Unclassified Flows',
+ mode='lines+markers',
+ line=COLOR_MAP['graph-flows']['Current Unclassified Flows'],
+ ))
+
+ return [fig, traces]
+
+@app.callback(output=[Output('piechart-events', 'figure')],
+ inputs=[Input('tab-other-default-interval', 'n_intervals')])
+def tab_other_update_components(n):
+ return [build_piechart(['Base', 'Daemon', 'Packet',
+ 'Flow New', 'Flow Update', 'Flow Analyse', 'Flow End', 'Flow Idle',
+ 'Flow Detection', 'Flow Detection-Updates', 'Flow Guessed', 'Flow Not-Detected'],
+ [shared_flow_dict['total-base-events'],
+ shared_flow_dict['total-daemon-events'],
+ shared_flow_dict['total-packet-events'],
+ shared_flow_dict['total-flow-new-events'],
+ shared_flow_dict['total-flow-update-events'],
+ shared_flow_dict['total-flow-analyse-events'],
+ shared_flow_dict['total-flow-end-events'],
+ shared_flow_dict['total-flow-idle-events'],
+ shared_flow_dict['total-flow-detected-events'],
+ shared_flow_dict['total-flow-detection-update-events'],
+ shared_flow_dict['total-flow-guessed-events'],
+ shared_flow_dict['total-flow-not-detected-events']])]
+
+def web_worker(mp_shared_flow_dict, listen_host, listen_port):
+ global shared_flow_dict
+
+ shared_flow_dict = mp_shared_flow_dict
+
+ try:
+ app.run_server(debug=False, host=listen_host, port=listen_port)
+ except KeyboardInterrupt:
+ pass
diff --git a/examples/py-flow-dashboard/requirements.txt b/examples/py-flow-dashboard/requirements.txt
new file mode 100644
index 000000000..1adede5dc
--- /dev/null
+++ b/examples/py-flow-dashboard/requirements.txt
@@ -0,0 +1,3 @@
+dash
+dash_daq
+Werkzeug==3.0.3
diff --git a/examples/py-flow-info/flow-info.py b/examples/py-flow-info/flow-info.py
new file mode 100755
index 000000000..c5193f9ee
--- /dev/null
+++ b/examples/py-flow-info/flow-info.py
@@ -0,0 +1,641 @@
+#!/usr/bin/env python3
+
+import os
+import math
+import sys
+import time
+import datetime
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket, TermColor
+
+global args
+global whois_db
+
+def set_attr_from_dict(some_object, some_dict, key_and_attr_name, default_value):
+ try:
+ setattr(some_object, key_and_attr_name, some_dict[key_and_attr_name])
+ except KeyError:
+ if default_value is not None and getattr(some_object, key_and_attr_name, None) is None:
+ setattr(some_object, key_and_attr_name, default_value)
+
+def set_attr_if_not_set(some_object, attr_name, value):
+ try:
+ getattr(some_object, attr_name)
+ except AttributeError:
+ setattr(some_object, attr_name, value)
+
+class Stats:
+
+ def __init__(self, nDPIsrvd_sock):
+ self.statusbar_enabled = True
+ self.start_time = time.time()
+ self.nsock = nDPIsrvd_sock
+ self.last_status_length = 0
+ self.avg_xfer_json_bytes = 0.0
+ self.expired_tot_l4_payload_len = 0
+ self.total_flows = 0
+ self.risky_flows = 0
+ self.midstream_flows = 0
+ self.guessed_flows = 0
+ self.not_detected_flows = 0
+ self.current_time = 0.0
+ self.json_lines = 0
+ self.spinner_state = 0
+
+ def disableStatusbar(self):
+ self.statusbar_enabled = False
+
+ def updateSpinner(self):
+ if self.current_time + 0.25 <= time.time():
+ self.spinner_state += 1
+
+ def __getSpinner(self):
+ #spinner_states = ['-', '\\', '|', '/']
+ #spinner_states = ['▉', '▊', '▋', '▌', '▍', '▎', '▏', '▎', '▍', '▌', '▋', '▊', '▉']
+ spinner_states = ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙']
+ #spinner_states = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▁']
+ #spinner_states = ['▖', '▘', '▝', '▗']
+ #spinner_states = ['┤', '┘', '┴', '└', '├', '┌', '┬', '┐']
+ return spinner_states[self.spinner_state % len(spinner_states)]
+
+ def __getDataFromJson(self, json_dict, current_flow):
+ if current_flow is None:
+ return
+
+ set_attr_from_dict(current_flow, json_dict, 'flow_src_tot_l4_payload_len', 0)
+ set_attr_from_dict(current_flow, json_dict, 'flow_dst_tot_l4_payload_len', 0)
+ if 'ndpi' in json_dict:
+ set_attr_from_dict(current_flow, json_dict['ndpi'], 'flow_risk', {})
+ else:
+ set_attr_from_dict(current_flow, {}, 'flow_risk', {})
+ set_attr_from_dict(current_flow, json_dict, 'midstream', 0)
+ set_attr_from_dict(current_flow, json_dict, 'flow_event_name', '')
+ set_attr_if_not_set(current_flow, 'guessed', 0)
+ set_attr_if_not_set(current_flow, 'not_detected', 0)
+
+ if current_flow.flow_event_name == 'detected' or \
+ current_flow.flow_event_name == 'detection-update':
+ current_flow.guessed = 0
+ elif current_flow.flow_event_name == 'guessed':
+ current_flow.guessed = 1
+ elif current_flow.flow_event_name == 'not-detected':
+ current_flow.not_detected = 1
+
+ def update(self, json_dict, current_flow):
+ self.updateSpinner()
+ self.json_lines += 1
+ self.current_time = time.time()
+ self.avg_xfer_json_bytes = self.nsock.received_bytes / (self.current_time - self.start_time)
+ self.__getDataFromJson(json_dict, current_flow)
+
+ def updateOnCleanup(self, current_flow):
+ self.total_flows += 1
+ self.expired_tot_l4_payload_len += current_flow.flow_src_tot_l4_payload_len + current_flow.flow_dst_tot_l4_payload_len
+ self.risky_flows += 1 if len(current_flow.flow_risk) > 0 else 0
+ self.midstream_flows += 1 if current_flow.midstream != 0 else 0
+ self.guessed_flows += 1 if current_flow.guessed != 0 else 0
+ self.not_detected_flows += 1 if current_flow.not_detected != 0 else 0
+
+ def __getStatsFromFlowMgr(self):
+ alias_count = 0
+ source_count = 0
+ flow_count = 0
+ flow_tot_l4_payload_len = 0.0
+ risky = 0
+ midstream = 0
+ guessed = 0
+ not_detected = 0
+
+ instances = self.nsock.flow_mgr.instances
+ for alias in instances:
+ alias_count += 1
+ for source in instances[alias]:
+ source_count += 1
+ for flow_id in instances[alias][source].flows:
+ flow_count += 1
+ current_flow = instances[alias][source].flows[flow_id]
+
+ try:
+ flow_src_tot_l4_payload_len = current_flow.flow_src_tot_l4_payload_len
+ flow_dst_tot_l4_payload_len = current_flow.flow_dst_tot_l4_payload_len
+ flow_risk = current_flow.flow_risk
+ midstream = current_flow.midstream
+ guessed = current_flow.guessed
+ not_detected = current_flow.not_detected
+ except AttributeError:
+ flow_src_tot_l4_payload_len = 0
+ flow_dst_tot_l4_payload_len = 0
+ flow_risk = []
+ midstream = 0
+ guessed = 0
+ not_detected = 0
+
+ flow_tot_l4_payload_len += flow_src_tot_l4_payload_len + flow_dst_tot_l4_payload_len
+ risky += 1 if len(flow_risk) > 0 else 0
+ midstream += 1 if midstream != 0 else 0
+ guessed += 1 if guessed != 0 else 0
+ not_detected = 1 if not_detected != 0 else 0
+
+ return alias_count, source_count, flow_count, \
+ flow_tot_l4_payload_len, \
+ risky, midstream, guessed, not_detected
+
+ @staticmethod
+ def prettifyBytes(bytes_received, is_byte_unit = True):
+ if not is_byte_unit:
+ size_names = ['', 'K', 'M', 'G', 'T']
+ divisor = 1000
+ else:
+ size_names = ['B', 'KiB', 'MiB', 'GiB', 'TiB']
+ divisor = 1024
+
+ if bytes_received == 0:
+ i = 0
+ else:
+ i = min(int(math.floor(math.log(bytes_received, divisor))), len(size_names) - 1)
+ p = math.pow(divisor, i)
+ s = round(bytes_received / p, 2)
+
+ if not is_byte_unit:
+ return '{:.0f}{}'.format(s, ' ' + size_names[i] if len(size_names[i]) > 0 else size_names[i])
+ else:
+ return '{:.2f} {}'.format(s, size_names[i])
+
+ def resetStatus(self):
+ if self.statusbar_enabled is False:
+ return
+
+ sys.stdout.write('\r' + str(' ' * self.last_status_length) + '\r')
+ sys.stdout.flush()
+
+ def printStatus(self):
+ if self.statusbar_enabled is False:
+ return
+
+ alias_count, source_count, flow_count, \
+ tot_l4_payload_len, \
+ risky, midstream, guessed, not_detected = self.__getStatsFromFlowMgr()
+
+ out_str = '\r[n|tot|avg JSONs: {}|{}|{}/s] [tot l4: {}] ' \
+ '[lss|srcs: {}|{}] ' \
+ '[flws|rsky|mdstrm|!dtctd|gssd: {}|{}|{}|{}|{} / {}|{}|{}|{}|{}] [{}]' \
+ ''.format(self.json_lines,
+ Stats.prettifyBytes(self.nsock.received_bytes),
+ Stats.prettifyBytes(self.avg_xfer_json_bytes),
+ Stats.prettifyBytes(tot_l4_payload_len + self.expired_tot_l4_payload_len),
+ alias_count, source_count,
+ flow_count, risky, midstream, not_detected, guessed,
+ flow_count + self.total_flows,
+ risky + self.risky_flows,
+ midstream + self.midstream_flows,
+ not_detected + self.not_detected_flows,
+ guessed + self.guessed_flows,
+ self.__getSpinner())
+ self.last_status_length = len(out_str) - 1 # '\r'
+
+ sys.stdout.write(out_str)
+ sys.stdout.flush()
+
+def prettifyEvent(color_list, whitespaces, text):
+ term_attrs = str()
+ for color in color_list:
+ term_attrs += str(color)
+ fmt = '{}{:>' + str(whitespaces) + '}{}'
+ return fmt.format(term_attrs, text, TermColor.END)
+
+def prettifyTimediff(epoch_ts1, epoch_ts2):
+ dt1 = datetime.datetime.fromtimestamp(epoch_ts1)
+ dt2 = datetime.datetime.fromtimestamp(epoch_ts2)
+ seconds_diff = (dt2 - dt1).total_seconds()
+ return '{:.>4}m{:.>3}s'.format(int(seconds_diff / 60), int(seconds_diff) % 60)
+
+def checkEventFilter(json_dict):
+ flow_events = {'new': args.new, 'end': args.end, 'idle': args.idle,
+ 'guessed': args.guessed, 'detected': args.detected,
+ 'detection-update': args.detection_update,
+ 'not-detected': args.not_detected,
+ 'update': args.update, 'analyse': args.analyse}
+
+ if flow_events[json_dict['flow_event_name']] is True:
+ return True
+
+ if 'ndpi' in json_dict and 'flow_risk' in json_dict['ndpi']:
+ if args.risky is True:
+ return True
+
+ if json_dict['midstream'] != 0:
+ if args.midstream is True:
+ return True
+
+ flow_event_filter_disabled = True
+ for flow_event in list(flow_events.values()) + [args.risky, args.midstream]:
+ if flow_event is True:
+ flow_event_filter_disabled = False
+ break
+ if flow_event_filter_disabled is True:
+ return True
+
+ return False
+
+def whois(ip_str):
+ if ip_str not in whois_db:
+ try:
+ whois_json = ipwhois.ipwhois.IPWhois(ip_str).lookup_whois()
+ whois_db[ip_str] = whois_json['asn_description']
+ except (ipwhois.exceptions.IPDefinedError, dns.resolver.NoResolverConfiguration):
+ return None
+ return whois_db[ip_str]
+
+def onFlowCleanup(instance, current_flow, global_user_data):
+ stats = global_user_data
+ stats.updateOnCleanup(current_flow)
+
+ return True
+
+def limitFloatValue(value, fmt, limit):
+ if float(value) < float(limit) and float(value) > 0.0:
+ return '<' + str(fmt).format(limit)
+ else:
+ return ' ' + str(fmt).format(value)
+
+def onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ stats = global_user_data
+ stats.update(json_dict, current_flow)
+
+ if 'packet_event_id' in json_dict:
+ return True
+
+ stats.resetStatus()
+
+ instance_and_source = ''
+ if args.hide_instance_info is False:
+ instance_and_source += '[{}][{}][{:.>2}] '.format(
+ TermColor.setColorByString(instance.alias),
+ TermColor.setColorByString(instance.source),
+ json_dict['thread_id'] if 'thread_id' in json_dict else '')
+ else:
+ instance_and_source += ' '
+
+ basic_daemon_event_prefix = ''
+ timestamp = ''
+ if args.print_timestamp is True:
+ if 'thread_ts_usec' in json_dict:
+ timestamp += '[{}]'.format(time.strftime('%H:%M:%S',
+ time.localtime(nDPIsrvd.toSeconds(json_dict['thread_ts_usec']))))
+ elif 'global_ts_usec' in json_dict:
+ timestamp += '[{}]'.format(time.strftime('%H:%M:%S',
+ time.localtime(nDPIsrvd.toSeconds(json_dict['global_ts_usec']))))
+
+ first_seen = ''
+ if args.print_first_seen is True:
+ basic_daemon_event_prefix += ' ' * 11
+ if 'flow_first_seen' in json_dict:
+ first_seen = '[' + prettifyTimediff(nDPIsrvd.toSeconds(json_dict['flow_first_seen']),
+ nDPIsrvd.toSeconds(json_dict['thread_ts_usec'])) + ']'
+
+ last_seen = ''
+ if args.print_last_seen is True:
+ basic_daemon_event_prefix += ' ' * 11
+ if current_flow is not None:
+ flow_last_seen = nDPIsrvd.FlowManager.getLastPacketTime(instance, current_flow.flow_id, json_dict)
+ last_seen = '[' + prettifyTimediff(nDPIsrvd.toSeconds(flow_last_seen),
+ nDPIsrvd.toSeconds(json_dict['thread_ts_usec'])) + ']'
+
+ if 'daemon_event_id' in json_dict:
+ if json_dict['daemon_event_name'] == 'status':
+ color = [TermColor.WARNING]
+ daemon_msg = list()
+ daemon_msg += ['[Processed: {} pkts][ZLib][compressions: {}|diff: {} / {}]'.format(
+ json_dict['packets-processed'],
+ json_dict['total-compressions'], json_dict['current-compression-diff'], json_dict['total-compression-diff'])]
+ daemon_msg += ['[Flows][active: {} / {}|skipped: {}|!detected: {}|guessed: {}|' \
+ 'detection-updates: {}|updates: {}]'.format(
+ json_dict['current-active-flows'], json_dict['total-active-flows'],
+ json_dict['total-skipped-flows'],
+ json_dict['total-not-detected-flows'], json_dict['total-guessed-flows'],
+ json_dict['total-detection-updates'], json_dict['total-updates'])]
+ else:
+ color = [TermColor.WARNING, TermColor.BLINK]
+ daemon_msg = list()
+ daemon_msg += [json_dict['daemon_event_name']]
+ for dm in daemon_msg:
+ print('{}{}{} {}: {}'.format(timestamp, basic_daemon_event_prefix, instance_and_source,
+ prettifyEvent(color, 15, 'DAEMON-EVENT'), dm))
+ stats.printStatus()
+ return True
+ if 'error_event_id' in json_dict:
+ print('{}{}{} {}: {} [{}/{}]'.format(timestamp, basic_daemon_event_prefix, instance_and_source,
+ prettifyEvent([TermColor.FAIL, TermColor.BLINK], 15, 'ERROR-EVENT'),
+ json_dict['error_event_name'], json_dict['threshold_n'], json_dict['threshold_n_max']))
+ stats.printStatus()
+ return True
+ elif 'flow_event_id' not in json_dict:
+ stats.printStatus()
+ return True
+
+ if checkEventFilter(json_dict) is False:
+ stats.printStatus()
+ return True
+
+ ndpi_proto_categ_breed = ''
+ next_lines = []
+
+ if 'ndpi' in json_dict:
+ ndpi_proto_categ_breed += ' '
+
+ if 'proto' in json_dict['ndpi']:
+ if args.ignore_protocol is not None:
+ for proto in args.ignore_protocol:
+ if json_dict['ndpi']['proto'].lower().startswith(proto.lower()) is True:
+ stats.printStatus()
+ return True
+ ndpi_proto_categ_breed += '[' + str(json_dict['ndpi']['proto']) + ']'
+
+ if 'proto_by_ip' in json_dict['ndpi']:
+ if args.ignore_ip_protocol is not None:
+ for proto in args.ignore_ip_protocol:
+ if json_dict['ndpi']['proto_by_ip'].lower().startswith(proto.lower()) is True:
+ stats.printStatus()
+ return True
+ ndpi_proto_categ_breed += '[' + str(json_dict['ndpi']['proto_by_ip']) + ']'
+
+ if 'category' in json_dict['ndpi']:
+ if args.ignore_category is not None:
+ for cat in args.ignore_category:
+ if json_dict['ndpi']['category'].lower().startswith(cat.lower()) is True:
+ stats.printStatus()
+ return True
+ ndpi_proto_categ_breed += '[' + str(json_dict['ndpi']['category']) + ']'
+
+ if 'breed' in json_dict['ndpi']:
+ if args.ignore_breed is not None:
+ for breed in args.ignore_breed:
+ if json_dict['ndpi']['breed'].lower().startswith(breed.lower()) is True:
+ stats.printStatus()
+ return True
+ ndpi_proto_categ_breed += '[' + str(json_dict['ndpi']['breed']) + ']'
+
+ if 'flow_risk' in json_dict['ndpi']:
+ severity = 0
+ cnt = 0
+
+ next_lines += ['']
+ for key in json_dict['ndpi']['flow_risk']:
+ next_lines[0] += str(json_dict['ndpi']['flow_risk'][key]['risk']) + ', '
+ if json_dict['ndpi']['flow_risk'][key]['severity'] == 'Low':
+ severity = max(severity, 1)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'Medium':
+ severity = max(severity, 2)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'High':
+ severity = max(severity, 3)
+ elif json_dict['ndpi']['flow_risk'][key]['severity'] == 'Severe':
+ severity = max(severity, 4)
+ cnt += 1
+
+ if severity == 1:
+ color = TermColor.WARNING + TermColor.BOLD
+ elif severity == 2:
+ color = TermColor.WARNING + TermColor.BOLD + TermColor.BLINK
+ elif severity == 3:
+ color = TermColor.FAIL + TermColor.BOLD
+ elif severity == 4:
+ color = TermColor.FAIL + TermColor.BOLD + TermColor.BLINK
+ else:
+ color = ''
+
+ if severity >= args.min_risk_severity:
+ next_lines[0] = '{}{}{}: {}'.format(color, 'RISK', TermColor.END, next_lines[0][:-2])
+ else:
+ del next_lines[0]
+
+ line_suffix = ''
+ flow_event_name = ''
+ flow_active_color = '' if json_dict['flow_state'] == 'finished' else TermColor.BOLD
+ if json_dict['flow_event_name'] == 'guessed':
+ flow_event_name += '{}{:>16}{}'.format(TermColor.HINT + flow_active_color,
+ json_dict['flow_event_name'], TermColor.END)
+ elif json_dict['flow_event_name'] == 'not-detected':
+ flow_event_name += '{}{:>16}{}'.format(TermColor.WARNING + TermColor.BOLD + TermColor.BLINK,
+ json_dict['flow_event_name'], TermColor.END)
+ elif json_dict['flow_event_name'] == 'analyse':
+ flow_event_name += '{}{:>16}{}'.format(TermColor.WARNING,
+ json_dict['flow_event_name'], TermColor.END)
+ if args.print_analyse_results is True:
+ next_lines = [' {:>10}|{:>10}|{:>10}|{:>10}|{:>17}|{:>9}'.format(
+ 'min', 'max', 'avg', 'stddev', 'variance', 'entropy')]
+ next_lines += ['[IAT.........: {}|{}|{}|{}|{}|{}]'.format(
+ limitFloatValue(nDPIsrvd.toSeconds(json_dict['data_analysis']['iat']['min']),
+ '{:>9.3f}', 0.001),
+ limitFloatValue(nDPIsrvd.toSeconds(json_dict['data_analysis']['iat']['max']),
+ '{:>9.3f}', 0.001),
+ limitFloatValue(nDPIsrvd.toSeconds(json_dict['data_analysis']['iat']['avg']),
+ '{:>9.3f}', 0.001),
+ limitFloatValue(nDPIsrvd.toSeconds(json_dict['data_analysis']['iat']['stddev']),
+ '{:>9.3f}', 0.001),
+ limitFloatValue(nDPIsrvd.toSeconds(json_dict['data_analysis']['iat']['var']),
+ '{:>16.3f}', 0.001),
+ limitFloatValue(json_dict['data_analysis']['iat']['ent'],
+ '{:>8.3f}', 0.001)
+ )]
+ next_lines += ['']
+ next_lines[-1] += '[PKTLEN......: {}|{}|{}|{}|{}|{}]'.format(
+ limitFloatValue(json_dict['data_analysis']['pktlen']['min'], '{:>9.3f}', 0.001),
+ limitFloatValue(json_dict['data_analysis']['pktlen']['max'], '{:>9.3f}', 0.001),
+ limitFloatValue(json_dict['data_analysis']['pktlen']['avg'], '{:>9.3f}', 0.001),
+ limitFloatValue(json_dict['data_analysis']['pktlen']['stddev'],
+ '{:>9.3f}', 0.001),
+ limitFloatValue(json_dict['data_analysis']['pktlen']['var'], '{:>16.3f}', 0.001),
+ limitFloatValue(json_dict['data_analysis']['pktlen']['ent'], '{:>8.3f}', 0.001)
+ )
+ next_lines += ['']
+ next_lines[-1] += '[BINS(c->s)..: {}]'.format(','.join([str(n) for n in json_dict['data_analysis']['bins']['c_to_s']]))
+ next_lines += ['']
+ next_lines[-1] += '[BINS(s->c)..: {}]'.format(','.join([str(n) for n in json_dict['data_analysis']['bins']['s_to_c']]))
+ next_lines += ['']
+ next_lines[-1] += '[DIRECTIONS..: {}]'.format(','.join([str(n) for n in json_dict['data_analysis']['directions']]))
+ next_lines += ['']
+ iats = ''
+ for n in json_dict['data_analysis']['iat']['data']:
+ iats += '{:.1f},'.format(n / 1000.0)
+ iats = iats[:-1]
+ next_lines[-1] += '[IATS(ms)....: {}]'.format(iats)
+ next_lines += ['']
+ next_lines[-1] += '[PKTLENS.....: {}]'.format(','.join([str(n) for n in json_dict['data_analysis']['pktlen']['data']]))
+ next_lines += ['']
+ ents = ''
+ for n in json_dict['data_analysis']['entropies']:
+ ents += '{:.1f},'.format(n)
+ ents = ents[:-1]
+ next_lines[-1] += '[ENTROPIES...: {}]'.format(ents)
+ else:
+ if json_dict['flow_event_name'] == 'new':
+ line_suffix = ''
+ if json_dict['midstream'] != 0:
+ line_suffix += ' [{}]'.format(TermColor.WARNING + TermColor.BLINK + 'MIDSTREAM' + TermColor.END)
+ if args.ipwhois is True:
+ src_whois = whois(json_dict['src_ip'].lower())
+ dst_whois = whois(json_dict['dst_ip'].lower())
+ line_suffix += ' ['
+ if src_whois is not None:
+ line_suffix += '{}'.format(src_whois)
+ if dst_whois is not None:
+ if src_whois is not None:
+ line_suffix += ' -> '
+ line_suffix += '{}'.format(dst_whois)
+ if src_whois is None and dst_whois is None:
+ line_suffix += TermColor.WARNING + 'WHOIS empty' + TermColor.END
+ line_suffix += ']'
+ flow_event_name += '{}{:>16}{}'.format(flow_active_color, json_dict['flow_event_name'], TermColor.END)
+
+ if 'ndpi' in json_dict and 'hostname' in json_dict['ndpi']:
+ if args.ignore_hostname is not None:
+ for hostname in args.ignore_hostname:
+ if json_dict['ndpi']['hostname'].lower().endswith(hostname.lower()) is True:
+ stats.printStatus()
+ return True
+ if args.print_hostname is True:
+ line_suffix += '[{}]'.format(json_dict['ndpi']['hostname'])
+
+ if args.skip_empty is True:
+ if json_dict['flow_src_tot_l4_payload_len'] == 0 or json_dict['flow_dst_tot_l4_payload_len'] == 0:
+ stats.printStatus()
+ return True
+
+ if args.print_bytes is True:
+ src_color = ''
+ dst_color = ''
+ tot_color = ''
+ if json_dict['flow_src_tot_l4_payload_len'] >= 1 * 1024 * 1024:
+ tot_color = src_color = TermColor.HINT
+ if json_dict['flow_src_tot_l4_payload_len'] >= 1 * 1024 * 1024 * 1024:
+ src_color += TermColor.BOLD + TermColor.BLINK
+ if json_dict['flow_dst_tot_l4_payload_len'] >= 1 * 1024 * 1024:
+ tot_color = dst_color = TermColor.HINT
+ if json_dict['flow_dst_tot_l4_payload_len'] >= 1 * 1024 * 1024 * 1024:
+ dst_color += TermColor.BOLD + TermColor.BLINK
+ line_suffix += '[' + src_color + Stats.prettifyBytes(json_dict['flow_src_tot_l4_payload_len']) + TermColor.END + ']' \
+ '[' + dst_color + Stats.prettifyBytes(json_dict['flow_dst_tot_l4_payload_len']) + TermColor.END +']' \
+ '[' + tot_color + Stats.prettifyBytes(json_dict['flow_src_tot_l4_payload_len'] + \
+ json_dict['flow_dst_tot_l4_payload_len']) + TermColor.END + ']'
+
+ if args.print_packets is True:
+ line_suffix += '[' + Stats.prettifyBytes(json_dict['flow_src_packets_processed'], False) + ']' \
+ '[' + Stats.prettifyBytes(json_dict['flow_dst_packets_processed'], False) + ']'
+
+ if json_dict['l3_proto'] == 'ip4':
+ print('{}{}{}{}{}: [{:.>6}] [{}][{:.>5}] [{:.>15}]{} -> [{:.>15}]{}{}{}' \
+ ''.format(timestamp, first_seen, last_seen, instance_and_source, flow_event_name,
+ json_dict['flow_id'], json_dict['l3_proto'], json_dict['l4_proto'],
+ json_dict['src_ip'].lower(),
+ '[{:.>5}]'.format(json_dict['src_port']) if 'src_port' in json_dict else '',
+ json_dict['dst_ip'].lower(),
+ '[{:.>5}]'.format(json_dict['dst_port']) if 'dst_port' in json_dict else '',
+ ndpi_proto_categ_breed, line_suffix))
+ elif json_dict['l3_proto'] == 'ip6':
+ print('{}{}{}{}{}: [{:.>6}] [{}][{:.>5}] [{:.>39}]{} -> [{:.>39}]{}{}{}' \
+ ''.format(timestamp, first_seen, last_seen, instance_and_source, flow_event_name,
+ json_dict['flow_id'], json_dict['l3_proto'], json_dict['l4_proto'],
+ json_dict['src_ip'].lower(),
+ '[{:.>5}]'.format(json_dict['src_port']) if 'src_port' in json_dict else '',
+ json_dict['dst_ip'].lower(),
+ '[{:.>5}]'.format(json_dict['dst_port']) if 'dst_port' in json_dict else '',
+ ndpi_proto_categ_breed, line_suffix))
+ else:
+ raise RuntimeError('unsupported l3 protocol: {}'.format(json_dict['l3_proto']))
+
+ for line in next_lines:
+ print('{}{}{}{}{:>18}{}'.format(timestamp, first_seen, last_seen,
+ instance_and_source, '', line))
+
+ stats.printStatus()
+
+ return True
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser('Prettify and print events using the nDPIsrvd Python interface.', True)
+ argparser.add_argument('--no-color', action='store_true', default=False,
+ help='Disable all terminal colors.')
+ argparser.add_argument('--no-statusbar', action='store_true', default=False,
+ help='Disable informational status bar.')
+ argparser.add_argument('--hide-instance-info', action='store_true', default=False,
+ help='Hide instance Alias/Source prefixed every line.')
+ argparser.add_argument('--print-timestamp', action='store_true', default=False,
+ help='Print received event timestamps.')
+ argparser.add_argument('--print-first-seen', action='store_true', default=False,
+ help='Print first seen flow time diff.')
+ argparser.add_argument('--print-last-seen', action='store_true', default=False,
+ help='Print last seen flow time diff.')
+ argparser.add_argument('--print-bytes', action='store_true', default=False,
+ help='Print received/transmitted source/dest bytes for every flow.')
+ argparser.add_argument('--print-packets', action='store_true', default=False,
+ help='Print received/transmitted source/dest packets for every flow.')
+ argparser.add_argument('--skip-empty', action='store_true', default=False,
+ help='Do not print flows that did not carry any layer7 payload.')
+ argparser.add_argument('--guessed', action='store_true', default=False, help='Print only guessed flow events.')
+ argparser.add_argument('--not-detected', action='store_true', default=False, help='Print only undetected flow events.')
+ argparser.add_argument('--detected', action='store_true', default=False, help='Print only detected flow events.')
+ argparser.add_argument('--detection-update', action='store_true', default=False, help='Print only detection-update flow events.')
+ argparser.add_argument('--risky', action='store_true', default=False, help='Print only risky flow events.')
+ argparser.add_argument('--midstream', action='store_true', default=False, help='Print only midstream flow events.')
+ argparser.add_argument('--new', action='store_true', default=False, help='Print only new flow events.')
+ argparser.add_argument('--end', action='store_true', default=False, help='Print only end flow events.')
+ argparser.add_argument('--idle', action='store_true', default=False, help='Print only idle flow events.')
+ argparser.add_argument('--update', action='store_true', default=False, help='Print only update flow events.')
+ argparser.add_argument('--analyse', action='store_true', default=False, help='Print only analyse flow events.')
+ argparser.add_argument('--detection', action='store_true', default=False, help='Print only detected/guessed/not-detected flow events.')
+ argparser.add_argument('--ipwhois', action='store_true', default=False, help='Use Python-IPWhois to print additional location information.')
+ argparser.add_argument('--print-hostname', action='store_true', default=False, help='Print detected hostnames if available.')
+ argparser.add_argument('--print-analyse-results', action='store_true', default=False,
+ help='Print detailed results of analyse events.')
+ argparser.add_argument('--ignore-protocol', action='append', help='Ignore printing lines with a certain protocol.')
+ argparser.add_argument('--ignore-ip-protocol', action='append', help='Ignore printing lines with a certain IP protocol.')
+ argparser.add_argument('--ignore-category', action='append', help='Ignore printing lines with a certain category.')
+ argparser.add_argument('--ignore-breed', action='append', help='Ignore printing lines with a certain breed.')
+ argparser.add_argument('--ignore-hostname', action='append', help='Ignore printing lines with a certain hostname.')
+ argparser.add_argument('--min-risk-severity', action='store', type=int, default=0, help='Print only risks with a risk severity greater or equal to the given argument')
+ args = argparser.parse_args()
+
+ if args.no_color is True:
+ TermColor.disableColor()
+
+ if args.ipwhois is True:
+ import dns, ipwhois
+ whois_db = dict()
+
+ if args.detection is True:
+ args.detected = True
+ args.guessed = True
+ args.not_detected = True
+
+ address = nDPIsrvd.validateAddress(args)
+
+ sys.stderr.write('Recv buffer size: {}\n'.format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'.format(address[0]+':'+str(address[1]) if type(address) is tuple else address))
+
+ nsock = nDPIsrvdSocket()
+ nDPIsrvd.prepareJsonFilter(args, nsock)
+ nsock.connect(address)
+ nsock.timeout(1.0)
+ stats = Stats(nsock)
+
+ if args.no_statusbar is True:
+ stats.disableStatusbar()
+
+ while True:
+ try:
+ nsock.loop(onJsonLineRecvd, onFlowCleanup, stats)
+ except nDPIsrvd.SocketConnectionBroken as err:
+ sys.stderr.write('\n{}\n'.format(err))
+ break
+ except KeyboardInterrupt:
+ print('\n\nKeyboard Interrupt: cleaned up {} flows.'.format(len(nsock.shutdown())))
+ break
+ except nDPIsrvd.SocketTimeout:
+ stats.updateSpinner()
+ stats.resetStatus()
+ stats.printStatus()
diff --git a/examples/py-flow-muliprocess/py-flow-multiprocess.py b/examples/py-flow-muliprocess/py-flow-multiprocess.py
new file mode 100755
index 000000000..3313b156b
--- /dev/null
+++ b/examples/py-flow-muliprocess/py-flow-multiprocess.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+
+import multiprocessing
+import os
+import sys
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket
+
+def mp_worker(unused, shared_flow_dict):
+ import time
+ while True:
+ s = str()
+ n = int()
+
+ for key in shared_flow_dict.keys():
+ try:
+ flow = shared_flow_dict[key]
+ except KeyError:
+ continue
+
+ s += '{}, '.format(str(flow.flow_id))
+ n += 1
+
+ if len(s) == 0:
+ s = '-'
+ else:
+ s = s[:-2]
+
+ print('Flows({}): {}'.format(n, s))
+ time.sleep(1)
+
+
+def nDPIsrvd_worker_onFlowCleanup(instance, current_flow, global_user_data):
+ shared_flow_dict = global_user_data
+
+ del shared_flow_dict[current_flow.flow_id]
+
+ return True
+
+
+def nDPIsrvd_worker_onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ shared_flow_dict = global_user_data
+
+ if 'flow_id' not in json_dict:
+ return True
+
+ shared_flow_dict[current_flow.flow_id] = current_flow
+
+ return True
+
+
+def nDPIsrvd_worker(address, shared_flow_dict):
+ sys.stderr.write('Recv buffer size: {}\n'.format(
+ nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'.format(
+ address[0] + ':' +
+ str(address[1]) if type(address) is tuple else address))
+
+ nsock = nDPIsrvdSocket()
+ nsock.connect(address)
+ nsock.loop(nDPIsrvd_worker_onJsonLineRecvd,
+ nDPIsrvd_worker_onFlowCleanup,
+ shared_flow_dict)
+
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser()
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ mgr = multiprocessing.Manager()
+ shared_flow_dict = mgr.dict()
+
+ nDPIsrvd_job = multiprocessing.Process(
+ target=nDPIsrvd_worker,
+ args=(address, shared_flow_dict))
+ nDPIsrvd_job.start()
+
+ mp_job = multiprocessing.Process(
+ target=mp_worker,
+ args=(None, shared_flow_dict))
+ mp_job.start()
+
+ nDPIsrvd_job.join()
+ mp_job.terminate()
+ mp_job.join()
diff --git a/examples/py-json-stdout/json-stdout.py b/examples/py-json-stdout/json-stdout.py
new file mode 100755
index 000000000..cde22cd9b
--- /dev/null
+++ b/examples/py-json-stdout/json-stdout.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket, TermColor
+
+def onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ print(json_dict)
+ return True
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser('Plain and simple nDPIsrvd JSON event printer with filter capabilities.', True)
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ sys.stderr.write('Recv buffer size: {}\n'.format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'.format(address[0]+':'+str(address[1]) if type(address) is tuple else address))
+
+ nsock = nDPIsrvdSocket()
+ nDPIsrvd.prepareJsonFilter(args, nsock)
+ nsock.connect(address)
+ nsock.loop(onJsonLineRecvd, None, None)
diff --git a/examples/py-machine-learning/keras-autoencoder.py b/examples/py-machine-learning/keras-autoencoder.py
new file mode 100755
index 000000000..a99cc1b2d
--- /dev/null
+++ b/examples/py-machine-learning/keras-autoencoder.py
@@ -0,0 +1,384 @@
+#!/usr/bin/env python3
+
+import base64
+import binascii
+import datetime as dt
+import math
+import matplotlib.animation as ani
+import matplotlib.pyplot as plt
+import multiprocessing as mp
+import numpy as np
+import os
+import queue
+import sys
+
+import tensorflow as tf
+from tensorflow.keras import models, layers, preprocessing
+from tensorflow.keras.layers import Embedding, Masking, Input, Dense
+from tensorflow.keras.models import Model
+from tensorflow.keras.utils import plot_model
+from tensorflow.keras.losses import MeanSquaredError, KLDivergence
+from tensorflow.keras.optimizers import Adam, SGD
+from tensorflow.keras.callbacks import TensorBoard, EarlyStopping
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket, TermColor
+
+INPUT_SIZE = nDPIsrvd.nDPId_PACKETS_PLEN_MAX
+LATENT_SIZE = 8
+TRAINING_SIZE = 512
+EPOCH_COUNT = 3
+BATCH_SIZE = 16
+LEARNING_RATE = 0.000001
+ES_PATIENCE = 3
+PLOT = False
+PLOT_HISTORY = 100
+TENSORBOARD = False
+TB_LOGPATH = 'logs/' + dt.datetime.now().strftime("%Y%m%d-%H%M%S")
+VAE_USE_KLDIV = False
+VAE_USE_SGD = False
+
+def generate_autoencoder():
+ # TODO: The current model does handle *each* packet separatly.
+ # But in fact, depending on the nDPId settings (nDPId_PACKETS_PER_FLOW_TO_SEND), packets can be in relation to each other.
+ # The accuracy may (or may not) improve significantly, but some of changes in the code are required.
+ input_i = Input(shape=(), name='input_i')
+ input_e = Embedding(input_dim=INPUT_SIZE, output_dim=INPUT_SIZE, mask_zero=True, name='input_e')(input_i)
+ masked_e = Masking(mask_value=0.0, name='masked_e')(input_e)
+ encoded_h1 = Dense(4096, activation='relu', name='encoded_h1')(masked_e)
+ encoded_h2 = Dense(2048, activation='relu', name='encoded_h2')(encoded_h1)
+ encoded_h3 = Dense(1024, activation='relu', name='encoded_h3')(encoded_h2)
+ encoded_h4 = Dense(512, activation='relu', name='encoded_h4')(encoded_h3)
+ encoded_h5 = Dense(128, activation='relu', name='encoded_h5')(encoded_h4)
+ encoded_h6 = Dense(64, activation='relu', name='encoded_h6')(encoded_h5)
+ encoded_h7 = Dense(32, activation='relu', name='encoded_h7')(encoded_h6)
+ latent = Dense(LATENT_SIZE, activation='relu', name='latent')(encoded_h7)
+
+ input_l = Input(shape=(LATENT_SIZE), name='input_l')
+ decoder_h1 = Dense(32, activation='relu', name='decoder_h1')(input_l)
+ decoder_h2 = Dense(64, activation='relu', name='decoder_h2')(decoder_h1)
+ decoder_h3 = Dense(128, activation='relu', name='decoder_h3')(decoder_h2)
+ decoder_h4 = Dense(512, activation='relu', name='decoder_h4')(decoder_h3)
+ decoder_h5 = Dense(1024, activation='relu', name='decoder_h5')(decoder_h4)
+ decoder_h6 = Dense(2048, activation='relu', name='decoder_h6')(decoder_h5)
+ decoder_h7 = Dense(4096, activation='relu', name='decoder_h7')(decoder_h6)
+ output_i = Dense(INPUT_SIZE, activation='sigmoid', name='output_i')(decoder_h7)
+
+ encoder = Model(input_e, latent, name='encoder')
+ decoder = Model(input_l, output_i, name='decoder')
+ return KLDivergence() if VAE_USE_KLDIV else MeanSquaredError(), \
+ SGD() if VAE_USE_SGD else Adam(learning_rate=LEARNING_RATE), \
+ Model(input_e, decoder(encoder(input_e)), name='VAE')
+
+def compile_autoencoder():
+ loss, optimizer, autoencoder = generate_autoencoder()
+ autoencoder.compile(loss=loss, optimizer=optimizer, metrics=[])
+ return autoencoder
+
+def get_autoencoder(load_from_file=None):
+ if load_from_file is None:
+ autoencoder = compile_autoencoder()
+ else:
+ autoencoder = models.load_model(load_from_file)
+
+ encoder_submodel = autoencoder.layers[1]
+ decoder_submodel = autoencoder.layers[2]
+ return encoder_submodel, decoder_submodel, autoencoder
+
+def on_json_line(json_dict, instance, current_flow, global_user_data):
+ if 'packet_event_name' not in json_dict:
+ return True
+
+ if json_dict['packet_event_name'] != 'packet' and \
+ json_dict['packet_event_name'] != 'packet-flow':
+ return True
+
+ shutdown_event, training_event, padded_pkts, print_dots = global_user_data
+ if shutdown_event.is_set():
+ return False
+
+ try:
+ buf = base64.b64decode(json_dict['pkt'], validate=True)
+ except binascii.Error as err:
+ sys.stderr.write('\nBase64 Exception: {}\n'.format(str(err)))
+ sys.stderr.write('Affected JSON: {}\n'.format(str(json_dict)))
+ sys.stderr.flush()
+ return False
+
+ # Generate decimal byte buffer with valus from 0-255
+ int_buf = []
+ for v in buf:
+ int_buf.append(int(v))
+
+ mat = np.array([int_buf], dtype='float64')
+
+ # Normalize the values
+ mat = mat.astype('float64') / 255.0
+
+ # Mean removal
+ matmean = np.mean(mat, dtype='float64')
+ mat -= matmean
+
+ # Pad resulting matrice
+ buf = preprocessing.sequence.pad_sequences(mat, padding="post", maxlen=INPUT_SIZE, truncating='post', dtype='float64')
+ padded_pkts.put(buf[0])
+
+ #print(list(buf[0]))
+
+ if not training_event.is_set():
+ sys.stdout.write('.' * print_dots)
+ sys.stdout.flush()
+ print_dots = 1
+ else:
+ print_dots += 1
+
+ return True
+
+def ndpisrvd_worker(address, shared_shutdown_event, shared_training_event, shared_packet_list):
+ nsock = nDPIsrvdSocket()
+
+ try:
+ nsock.connect(address)
+ print_dots = 1
+ nsock.loop(on_json_line, None, (shared_shutdown_event, shared_training_event, shared_packet_list, print_dots))
+ except nDPIsrvd.SocketConnectionBroken as err:
+ sys.stderr.write('\nnDPIsrvd-Worker Socket Error: {}\n'.format(err))
+ except KeyboardInterrupt:
+ sys.stderr.write('\n')
+ except Exception as err:
+ sys.stderr.write('\nnDPIsrvd-Worker Exception: {}\n'.format(str(err)))
+ sys.stderr.flush()
+
+ shared_shutdown_event.set()
+
+def keras_worker(load_model, save_model, shared_shutdown_event, shared_training_event, shared_packet_queue, shared_plot_queue):
+ shared_training_event.set()
+ try:
+ encoder, _, autoencoder = get_autoencoder(load_model)
+ except Exception as err:
+ sys.stderr.write('Could not load Keras model from file: {}\n'.format(str(err)))
+ sys.stderr.flush()
+ encoder, _, autoencoder = get_autoencoder()
+ autoencoder.summary()
+ additional_callbacks = []
+ if TENSORBOARD is True:
+ tensorboard = TensorBoard(log_dir=TB_LOGPATH, histogram_freq=1)
+ additional_callbacks += [tensorboard]
+ early_stopping = EarlyStopping(monitor='val_loss', min_delta=0.0001, patience=ES_PATIENCE, restore_best_weights=True, start_from_epoch=0, verbose=0, mode='auto')
+ additional_callbacks += [early_stopping]
+ shared_training_event.clear()
+
+ try:
+ packets = list()
+ while not shared_shutdown_event.is_set():
+ try:
+ packet = shared_packet_queue.get(timeout=1)
+ except queue.Empty:
+ packet = None
+
+ if packet is None:
+ continue
+
+ packets.append(packet)
+ if len(packets) % TRAINING_SIZE == 0:
+ shared_training_event.set()
+ print('\nGot {} packets, training..'.format(len(packets)))
+ tmp = np.array(packets)
+ history = autoencoder.fit(
+ tmp, tmp, epochs=EPOCH_COUNT, batch_size=BATCH_SIZE,
+ validation_split=0.2,
+ shuffle=True,
+ callbacks=[additional_callbacks]
+ )
+ reconstructed_data = autoencoder.predict(tmp)
+ mse = np.mean(np.square(tmp - reconstructed_data))
+ reconstruction_accuracy = (1.0 / mse)
+ encoded_data = encoder.predict(tmp)
+ latent_activations = encoder.predict(tmp)
+ shared_plot_queue.put((reconstruction_accuracy, history.history['val_loss'], encoded_data[:, 0], encoded_data[:, 1], latent_activations))
+ packets.clear()
+ shared_training_event.clear()
+ except KeyboardInterrupt:
+ sys.stderr.write('\n')
+ except Exception as err:
+ if len(str(err)) == 0:
+ err = 'Unknown'
+ sys.stderr.write('\nKeras-Worker Exception: {}\n'.format(str(err)))
+ sys.stderr.flush()
+
+ if save_model is not None:
+ sys.stderr.write('Saving model to {}\n'.format(save_model))
+ sys.stderr.flush()
+ autoencoder.save(save_model)
+
+ try:
+ shared_shutdown_event.set()
+ except Exception:
+ pass
+
+def plot_animate(i, shared_plot_queue, ax, xs, ys):
+ if not shared_plot_queue.empty():
+ accuracy, loss, encoded_data0, encoded_data1, latent_activations = shared_plot_queue.get(timeout=1)
+ epochs = len(loss)
+ loss_mean = sum(loss) / epochs
+ else:
+ return
+
+ (ax1, ax2, ax3, ax4) = ax
+ (ys1, ys2, ys3, ys4) = ys
+
+ if len(xs) == 0:
+ xs.append(epochs)
+ else:
+ xs.append(xs[-1] + epochs)
+ ys1.append(accuracy)
+ ys2.append(loss_mean)
+
+ xs = xs[-PLOT_HISTORY:]
+ ys1 = ys1[-PLOT_HISTORY:]
+ ys2 = ys2[-PLOT_HISTORY:]
+
+ ax1.clear()
+ ax1.plot(xs, ys1, '-')
+ ax2.clear()
+ ax2.plot(xs, ys2, '-')
+ ax3.clear()
+ ax3.scatter(encoded_data0, encoded_data1, marker='.')
+ ax4.clear()
+ ax4.imshow(latent_activations, cmap='viridis', aspect='auto')
+
+ ax1.set_xlabel('Epoch Count')
+ ax1.set_ylabel('Accuracy')
+ ax2.set_xlabel('Epoch Count')
+ ax2.set_ylabel('Validation Loss')
+ ax3.set_title('Latent Space')
+ ax4.set_title('Latent Space Heatmap')
+ ax4.set_xlabel('Latent Dimensions')
+ ax4.set_ylabel('Datapoints')
+
+def plot_worker(shared_shutdown_event, shared_plot_queue):
+ try:
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2)
+ fig.tight_layout()
+ ax1.set_xlabel('Epoch Count')
+ ax1.set_ylabel('Accuracy')
+ ax2.set_xlabel('Epoch Count')
+ ax2.set_ylabel('Validation Loss')
+ ax3.set_title('Latent Space')
+ ax4.set_title('Latent Space Heatmap')
+ ax4.set_xlabel('Latent Dimensions')
+ ax4.set_ylabel('Datapoints')
+ xs = []
+ ys1 = []
+ ys2 = []
+ ys3 = []
+ ys4 = []
+ ani.FuncAnimation(fig, plot_animate, fargs=(shared_plot_queue, (ax1, ax2, ax3, ax4), xs, (ys1, ys2, ys3, ys4)), interval=1000, cache_frame_data=False)
+ plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05)
+ plt.margins(x=0, y=0)
+ plt.show()
+ except Exception as err:
+ sys.stderr.write('\nPlot-Worker Exception: {}\n'.format(str(err)))
+ sys.stderr.flush()
+ shared_shutdown_event.set()
+ return
+
+if __name__ == '__main__':
+ sys.stderr.write('\b\n***************\n')
+ sys.stderr.write('*** WARNING ***\n')
+ sys.stderr.write('***************\n')
+ sys.stderr.write('\nThis is an unmature Autoencoder example.\n')
+ sys.stderr.write('Please do not rely on any of it\'s output!\n\n')
+
+ argparser = nDPIsrvd.defaultArgumentParser()
+ argparser.add_argument('--load-model', action='store',
+ help='Load a pre-trained model file.')
+ argparser.add_argument('--save-model', action='store',
+ help='Save the trained model to a file.')
+ argparser.add_argument('--training-size', action='store', type=int, default=TRAINING_SIZE,
+ help='Set the amount of captured packets required to start the training phase.')
+ argparser.add_argument('--batch-size', action='store', type=int, default=BATCH_SIZE,
+ help='Set the batch size used for the training phase.')
+ argparser.add_argument('--learning-rate', action='store', type=float, default=LEARNING_RATE,
+ help='Set the (initial) learning rate for the optimizer.')
+ argparser.add_argument('--plot', action='store_true', default=PLOT,
+ help='Show some model metrics using pyplot.')
+ argparser.add_argument('--plot-history', action='store', type=int, default=PLOT_HISTORY,
+ help='Set the history size of Line plots. Requires --plot')
+ argparser.add_argument('--tensorboard', action='store_true', default=TENSORBOARD,
+ help='Enable TensorBoard compatible logging callback.')
+ argparser.add_argument('--tensorboard-logpath', action='store', default=TB_LOGPATH,
+ help='TensorBoard logging path.')
+ argparser.add_argument('--use-sgd', action='store_true', default=VAE_USE_SGD,
+ help='Use SGD optimizer instead of Adam.')
+ argparser.add_argument('--use-kldiv', action='store_true', default=VAE_USE_KLDIV,
+ help='Use Kullback-Leibler loss function instead of Mean-Squared-Error.')
+ argparser.add_argument('--patience', action='store', type=int, default=ES_PATIENCE,
+ help='Epoch value for EarlyStopping. This value forces VAE fitting to if no improvment achieved.')
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ LEARNING_RATE = args.learning_rate
+ TRAINING_SIZE = args.training_size
+ BATCH_SIZE = args.batch_size
+ PLOT = args.plot
+ PLOT_HISTORY = args.plot_history
+ TENSORBOARD = args.tensorboard
+ TB_LOGPATH = args.tensorboard_logpath if args.tensorboard_logpath is not None else TB_LOGPATH
+ VAE_USE_SGD = args.use_sgd
+ VAE_USE_KLDIV = args.use_kldiv
+ ES_PATIENCE = args.patience
+
+ sys.stderr.write('Recv buffer size: {}\n'.format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'.format(address[0]+':'+str(address[1]) if type(address) is tuple else address))
+ sys.stderr.write('PLOT={}, PLOT_HISTORY={}, LEARNING_RATE={}, TRAINING_SIZE={}, BATCH_SIZE={}\n\n'.format(PLOT, PLOT_HISTORY, LEARNING_RATE, TRAINING_SIZE, BATCH_SIZE))
+
+ mgr = mp.Manager()
+
+ shared_training_event = mgr.Event()
+ shared_training_event.clear()
+
+ shared_shutdown_event = mgr.Event()
+ shared_shutdown_event.clear()
+
+ shared_packet_queue = mgr.JoinableQueue()
+ shared_plot_queue = mgr.JoinableQueue()
+
+ nDPIsrvd_job = mp.Process(target=ndpisrvd_worker, args=(
+ address,
+ shared_shutdown_event,
+ shared_training_event,
+ shared_packet_queue
+ ))
+ nDPIsrvd_job.start()
+
+ keras_job = mp.Process(target=keras_worker, args=(
+ args.load_model,
+ args.save_model,
+ shared_shutdown_event,
+ shared_training_event,
+ shared_packet_queue,
+ shared_plot_queue
+ ))
+ keras_job.start()
+
+ if PLOT is True:
+ plot_job = mp.Process(target=plot_worker, args=(shared_shutdown_event, shared_plot_queue))
+ plot_job.start()
+
+ try:
+ shared_shutdown_event.wait()
+ except KeyboardInterrupt:
+ print('\nShutting down worker processess..')
+
+ if PLOT is True:
+ plot_job.terminate()
+ plot_job.join()
+ nDPIsrvd_job.terminate()
+ nDPIsrvd_job.join()
+ keras_job.join(timeout=3)
+ keras_job.terminate()
diff --git a/examples/py-machine-learning/requirements.txt b/examples/py-machine-learning/requirements.txt
new file mode 100644
index 000000000..33cfad38c
--- /dev/null
+++ b/examples/py-machine-learning/requirements.txt
@@ -0,0 +1,7 @@
+joblib
+tensorflow
+scikit-learn
+scipy
+matplotlib
+numpy
+pandas
diff --git a/examples/py-machine-learning/sklearn-random-forest.py b/examples/py-machine-learning/sklearn-random-forest.py
new file mode 100755
index 000000000..07f4049d8
--- /dev/null
+++ b/examples/py-machine-learning/sklearn-random-forest.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python3
+
+import csv
+import joblib
+import matplotlib.pyplot
+import numpy
+import os
+import pandas
+import sklearn
+import sklearn.ensemble
+import sklearn.inspection
+import sys
+import time
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket, TermColor
+
+
+N_DIRS = 0
+N_BINS = 0
+
+ENABLE_FEATURE_IAT = False
+ENABLE_FEATURE_PKTLEN = False
+ENABLE_FEATURE_DIRS = True
+ENABLE_FEATURE_BINS = True
+
+PROTO_CLASSES = None
+
+def getFeatures(json):
+ return [json['flow_src_packets_processed'],
+ json['flow_dst_packets_processed'],
+ json['flow_src_tot_l4_payload_len'],
+ json['flow_dst_tot_l4_payload_len']]
+
+def getFeaturesFromArray(json, expected_len=0):
+ if type(json) is str:
+ dirs = numpy.fromstring(json, sep=',', dtype=int)
+ dirs = numpy.asarray(dirs, dtype=int).tolist()
+ elif type(json) is list:
+ dirs = json
+ else:
+ raise TypeError('Invalid type: {}.'.format(type(json)))
+
+ if expected_len > 0 and len(dirs) != expected_len:
+ raise RuntimeError('Invalid array length; Expected {}, Got {}.'.format(expected_len, len(dirs)))
+
+ return dirs
+
+def getRelevantFeaturesCSV(line):
+ ret = list()
+ ret.extend(getFeatures(line));
+ if ENABLE_FEATURE_IAT is True:
+ ret.extend(getFeaturesFromArray(line['iat_data'], N_DIRS - 1))
+ if ENABLE_FEATURE_PKTLEN is True:
+ ret.extend(getFeaturesFromArray(line['pktlen_data'], N_DIRS))
+ if ENABLE_FEATURE_DIRS is True:
+ ret.extend(getFeaturesFromArray(line['directions'], N_DIRS))
+ if ENABLE_FEATURE_BINS is True:
+ ret.extend(getFeaturesFromArray(line['bins_c_to_s'], N_BINS))
+ ret.extend(getFeaturesFromArray(line['bins_s_to_c'], N_BINS))
+ return [ret]
+
+def getRelevantFeaturesJSON(line):
+ ret = list()
+ ret.extend(getFeatures(line))
+ if ENABLE_FEATURE_IAT is True:
+ ret.extend(getFeaturesFromArray(line['data_analysis']['iat']['data'], N_DIRS - 1))
+ if ENABLE_FEATURE_PKTLEN is True:
+ ret.extend(getFeaturesFromArray(line['data_analysis']['pktlen']['data'], N_DIRS))
+ if ENABLE_FEATURE_DIRS is True:
+ ret.extend(getFeaturesFromArray(line['data_analysis']['directions'], N_DIRS))
+ if ENABLE_FEATURE_BINS is True:
+ ret.extend(getFeaturesFromArray(line['data_analysis']['bins']['c_to_s'], N_BINS))
+ ret.extend(getFeaturesFromArray(line['data_analysis']['bins']['s_to_c'], N_BINS) )
+ return [ret]
+
+def getRelevantFeatureNames():
+ names = list()
+ names.extend(['flow_src_packets_processed', 'flow_dst_packets_processed',
+ 'flow_src_tot_l4_payload_len', 'flow_dst_tot_l4_payload_len'])
+ if ENABLE_FEATURE_IAT is True:
+ for x in range(N_DIRS - 1):
+ names.append('iat_{}'.format(x))
+ if ENABLE_FEATURE_PKTLEN is True:
+ for x in range(N_DIRS):
+ names.append('pktlen_{}'.format(x))
+ if ENABLE_FEATURE_DIRS is True:
+ for x in range(N_DIRS):
+ names.append('dirs_{}'.format(x))
+ if ENABLE_FEATURE_BINS is True:
+ for x in range(N_BINS):
+ names.append('bins_c_to_s_{}'.format(x))
+ for x in range(N_BINS):
+ names.append('bins_s_to_c_{}'.format(x))
+ return names
+
+def plotPermutatedImportance(model, X, y):
+ result = sklearn.inspection.permutation_importance(model, X, y, n_repeats=10, random_state=42, n_jobs=-1)
+ forest_importances = pandas.Series(result.importances_mean, index=getRelevantFeatureNames())
+
+ fig, ax = matplotlib.pyplot.subplots()
+ forest_importances.plot.bar(yerr=result.importances_std, ax=ax)
+ ax.set_title("Feature importances using permutation on full model")
+ ax.set_ylabel("Mean accuracy decrease")
+ fig.tight_layout()
+ matplotlib.pyplot.show()
+
+def isProtoClass(proto_class, line):
+ if type(proto_class) != list or type(line) != str:
+ raise TypeError('Invalid type: {}/{}.'.format(type(proto_class), type(line)))
+
+ s = line.lower()
+
+ for x in range(len(proto_class)):
+ if s.startswith(proto_class[x].lower()) is True:
+ return x + 1
+
+ return 0
+
+def onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ if 'flow_event_name' not in json_dict:
+ return True
+ if json_dict['flow_event_name'] != 'analyse':
+ return True
+
+ if 'ndpi' not in json_dict:
+ return True
+ if 'proto' not in json_dict['ndpi']:
+ return True
+
+ #print(json_dict)
+
+ model, proto_class, disable_colors = global_user_data
+
+ try:
+ X = getRelevantFeaturesJSON(json_dict)
+ y = model.predict(X)
+ p = model.predict_log_proba(X)
+
+ if y[0] <= 0:
+ y_text = 'n/a'
+ else:
+ y_text = proto_class[y[0] - 1]
+
+ color_start = ''
+ color_end = ''
+ pred_failed = False
+ if disable_colors is False:
+ if json_dict['ndpi']['proto'].lower().startswith(y_text) is True:
+ color_start = TermColor.BOLD
+ color_end = TermColor.END
+ elif y_text not in proto_class and \
+ json_dict['ndpi']['proto'].lower() not in proto_class:
+ pass
+ else:
+ pred_failed = True
+ color_start = TermColor.WARNING + TermColor.BOLD
+ color_end = TermColor.END
+
+ probs = str()
+ for i in range(len(p[0])):
+ if json_dict['ndpi']['proto'].lower().startswith(proto_class[i - 1]) and disable_colors is False:
+ probs += '{}{:>2.1f}{}, '.format(TermColor.BOLD + TermColor.BLINK if pred_failed is True else '',
+ p[0][i], TermColor.END)
+ elif i == y[0]:
+ probs += '{}{:>2.1f}{}, '.format(color_start, p[0][i], color_end)
+ else:
+ probs += '{:>2.1f}, '.format(p[0][i])
+ probs = probs[:-2]
+
+ print('DPI Engine detected: {}{:>24}{}, Predicted: {}{:>24}{}, Probabilities: {}'.format(
+ color_start, json_dict['ndpi']['proto'].lower(), color_end,
+ color_start, y_text, color_end, probs))
+
+ if pred_failed is True:
+ pclass = isProtoClass(args.proto_class, json_dict['ndpi']['proto'].lower())
+ if pclass == 0:
+ msg = 'false positive'
+ else:
+ msg = 'false negative'
+
+ print('{:>46} {}{}{}'.format('[-]', TermColor.FAIL + TermColor.BOLD + TermColor.BLINK, msg, TermColor.END))
+
+ except Exception as err:
+ print('Got exception `{}\'\nfor json: {}'.format(err, json_dict))
+
+ return True
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser()
+ argparser.add_argument('--load-model', action='store',
+ help='Load a pre-trained model file.')
+ argparser.add_argument('--save-model', action='store',
+ help='Save the trained model to a file.')
+ argparser.add_argument('--csv', action='store',
+ help='Input CSV file generated with nDPIsrvd-analysed.')
+ argparser.add_argument('--proto-class', action='append', required=False,
+ help='nDPId protocol class of interest used for training and prediction. ' +
+ 'Can be specified multiple times. Example: tls.youtube')
+ argparser.add_argument('--generate-feature-importance', action='store_true',
+ help='Generates the permutated feature importance with matplotlib.')
+ argparser.add_argument('--enable-iat', action='store_true', default=None,
+ help='Enable packet (I)nter (A)rrival (T)ime for learning and prediction.')
+ argparser.add_argument('--enable-pktlen', action='store_true', default=None,
+ help='Enable layer 4 packet lengths for learning and prediction.')
+ argparser.add_argument('--disable-dirs', action='store_true', default=None,
+ help='Disable packet directions for learning and prediction.')
+ argparser.add_argument('--disable-bins', action='store_true', default=None,
+ help='Disable packet length distribution for learning and prediction.')
+ argparser.add_argument('--disable-colors', action='store_true', default=False,
+ help='Disable any coloring.')
+ argparser.add_argument('--sklearn-jobs', action='store', type=int, default=1,
+ help='Number of sklearn processes during training.')
+ argparser.add_argument('--sklearn-estimators', action='store', type=int, default=1000,
+ help='Number of trees in the forest.')
+ argparser.add_argument('--sklearn-min-samples-leaf', action='store', type=int, default=0.0001,
+ help='The minimum number of samples required to be at a leaf node.')
+ argparser.add_argument('--sklearn-class-weight', default='balanced', const='balanced', nargs='?',
+ choices=['balanced', 'balanced_subsample'],
+ help='Weights associated with the protocol classes.')
+ argparser.add_argument('--sklearn-max-features', default='sqrt', const='sqrt', nargs='?',
+ choices=['sqrt', 'log2'],
+ help='The number of features to consider when looking for the best split.')
+ argparser.add_argument('--sklearn-max-depth', action='store', type=int, default=128,
+ help='The maximum depth of a tree.')
+ argparser.add_argument('--sklearn-verbosity', action='store', type=int, default=0,
+ help='Controls the verbosity of sklearn\'s random forest classifier.')
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ if args.csv is None and args.load_model is None:
+ sys.stderr.write('{}: Either `--csv` or `--load-model` required!\n'.format(sys.argv[0]))
+ sys.exit(1)
+
+ if args.csv is None and args.generate_feature_importance is True:
+ sys.stderr.write('{}: `--generate-feature-importance` requires `--csv`.\n'.format(sys.argv[0]))
+ sys.exit(1)
+
+ if args.proto_class is None or len(args.proto_class) == 0:
+ if args.csv is None and args.load_model is None:
+ sys.stderr.write('{}: `--proto-class` missing, no useful classification can be performed.\n'.format(sys.argv[0]))
+ else:
+ if args.load_model is not None:
+ sys.stderr.write('{}: `--proto-class` set, but you want to load an existing model.\n'.format(sys.argv[0]))
+ sys.exit(1)
+
+ if args.load_model is not None:
+ sys.stderr.write('{}: You are loading an existing model file. ' \
+ 'Some --sklearn-* command line parameters won\'t have any effect!\n'.format(sys.argv[0]))
+
+ if args.enable_iat is not None:
+ sys.stderr.write('{}: `--enable-iat` set, but you want to load an existing model.\n'.format(sys.argv[0]))
+ sys.exit(1)
+ if args.enable_pktlen is not None:
+ sys.stderr.write('{}: `--enable-pktlen` set, but you want to load an existing model.\n'.format(sys.argv[0]))
+ sys.exit(1)
+ if args.disable_dirs is not None:
+ sys.stderr.write('{}: `--disable-dirs` set, but you want to load an existing model.\n'.format(sys.argv[0]))
+ sys.exit(1)
+ if args.disable_bins is not None:
+ sys.stderr.write('{}: `--disable-bins` set, but you want to load an existing model.\n'.format(sys.argv[0]))
+ sys.exit(1)
+
+ ENABLE_FEATURE_IAT = args.enable_iat if args.enable_iat is not None else ENABLE_FEATURE_IAT
+ ENABLE_FEATURE_PKTLEN = args.enable_pktlen if args.enable_pktlen is not None else ENABLE_FEATURE_PKTLEN
+ ENABLE_FEATURE_DIRS = args.disable_dirs if args.disable_dirs is not None else ENABLE_FEATURE_DIRS
+ ENABLE_FEATURE_BINS = args.disable_bins if args.disable_bins is not None else ENABLE_FEATURE_BINS
+ PROTO_CLASSES = args.proto_class
+
+ numpy.set_printoptions(formatter={'float_kind': "{:.1f}".format}, sign=' ')
+ numpy.seterr(divide = 'ignore')
+
+ if args.proto_class is not None:
+ for i in range(len(args.proto_class)):
+ args.proto_class[i] = args.proto_class[i].lower()
+
+ if args.load_model is not None:
+ sys.stderr.write('Loading model from {}\n'.format(args.load_model))
+ model, options = joblib.load(args.load_model)
+ ENABLE_FEATURE_IAT, ENABLE_FEATURE_PKTLEN, ENABLE_FEATURE_DIRS, ENABLE_FEATURE_BINS, args.proto_class = options
+
+ if args.csv is not None:
+ sys.stderr.write('Learning via CSV..\n')
+ with open(args.csv, newline='\n') as csvfile:
+ reader = csv.DictReader(csvfile, delimiter=',', quotechar='"')
+ X = list()
+ y = list()
+
+ for line in reader:
+ N_DIRS = len(getFeaturesFromArray(line['directions']))
+ N_BINS = len(getFeaturesFromArray(line['bins_c_to_s']))
+ break
+
+ for line in reader:
+ try:
+ X += getRelevantFeaturesCSV(line)
+ except RuntimeError as err:
+ print('Runtime Error: `{}\'\non line {}: {}'.format(err, reader.line_num - 1, line))
+ continue
+ except TypeError as err:
+ print('Type Error: `{}\'\non line {}: {}'.format(err, reader.line_num - 1, line))
+ continue
+
+ try:
+ y += [isProtoClass(args.proto_class, line['proto'])]
+ except TypeError as err:
+ X.pop()
+ print('Type Error: `{}\'\non line {}: {}'.format(err, reader.line_num - 1, line))
+ continue
+
+ sys.stderr.write('CSV data set contains {} entries.\n'.format(len(X)))
+
+ if args.load_model is None:
+ model = sklearn.ensemble.RandomForestClassifier(bootstrap=False,
+ class_weight = args.sklearn_class_weight,
+ n_jobs = args.sklearn_jobs,
+ n_estimators = args.sklearn_estimators,
+ verbose = args.sklearn_verbosity,
+ min_samples_leaf = args.sklearn_min_samples_leaf,
+ max_features = args.sklearn_max_features,
+ max_depth = args.sklearn_max_depth
+ )
+ options = (ENABLE_FEATURE_IAT, ENABLE_FEATURE_PKTLEN, ENABLE_FEATURE_DIRS, ENABLE_FEATURE_BINS, args.proto_class)
+ sys.stderr.write('Training model..\n')
+ model.fit(X, y)
+
+ if args.generate_feature_importance is True:
+ sys.stderr.write('Generating feature importance .. this may take some time\n')
+ plotPermutatedImportance(model, X, y)
+
+ if args.save_model is not None:
+ sys.stderr.write('Saving model to {}\n'.format(args.save_model))
+ joblib.dump([model, options], args.save_model)
+
+ print('ENABLE_FEATURE_PKTLEN: {}'.format(ENABLE_FEATURE_PKTLEN))
+ print('ENABLE_FEATURE_BINS..: {}'.format(ENABLE_FEATURE_BINS))
+ print('ENABLE_FEATURE_DIRS..: {}'.format(ENABLE_FEATURE_DIRS))
+ print('ENABLE_FEATURE_IAT...: {}'.format(ENABLE_FEATURE_IAT))
+ print('Map[*] -> [0]')
+ for x in range(len(args.proto_class)):
+ print('Map["{}"] -> [{}]'.format(args.proto_class[x], x + 1))
+
+ sys.stderr.write('Predicting realtime traffic..\n')
+ sys.stderr.write('Recv buffer size: {}\n'.format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'.format(address[0]+':'+str(address[1]) if type(address) is tuple else address))
+ nsock = nDPIsrvdSocket()
+ nsock.connect(address)
+ nsock.loop(onJsonLineRecvd, None, (model, args.proto_class, args.disable_colors))
diff --git a/examples/py-schema-validation/py-schema-validation.py b/examples/py-schema-validation/py-schema-validation.py
new file mode 100755
index 000000000..fea2df4d1
--- /dev/null
+++ b/examples/py-schema-validation/py-schema-validation.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket, TermColor
+
+class Stats:
+
+ def __init__(self):
+ self.lines_processed = 0
+ self.print_dot_every = 10
+ self.print_nmb_every = self.print_dot_every * 5
+
+def onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ validation_done = nDPIsrvd.validateAgainstSchema(json_dict)
+
+ global_user_data.lines_processed += 1
+ if global_user_data.lines_processed % global_user_data.print_dot_every == 0:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+ print_nmb_every = global_user_data.print_nmb_every + (len(str(global_user_data.lines_processed)) * global_user_data.print_dot_every)
+ if global_user_data.lines_processed % print_nmb_every == 0:
+ sys.stdout.write(str(global_user_data.lines_processed))
+ sys.stdout.flush()
+
+ return validation_done
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser()
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ sys.stderr.write('Recv buffer size: {}\n'.format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'.format(address[0]+':'+str(address[1]) if type(address) is tuple else address))
+
+ nDPIsrvd.initSchemaValidator()
+
+ nsock = nDPIsrvdSocket()
+ nsock.connect(address)
+ try:
+ nsock.loop(onJsonLineRecvd, None, Stats())
+ except nDPIsrvd.SocketConnectionBroken as err:
+ sys.stderr.write('\n{}\n'.format(err))
+ except KeyboardInterrupt:
+ print()
diff --git a/examples/py-schema-validation/requirements.txt b/examples/py-schema-validation/requirements.txt
new file mode 100644
index 000000000..d89304b1a
--- /dev/null
+++ b/examples/py-schema-validation/requirements.txt
@@ -0,0 +1 @@
+jsonschema
diff --git a/examples/py-semantic-validation/py-semantic-validation.py b/examples/py-semantic-validation/py-semantic-validation.py
new file mode 100755
index 000000000..9207c3b69
--- /dev/null
+++ b/examples/py-semantic-validation/py-semantic-validation.py
@@ -0,0 +1,373 @@
+#!/usr/bin/env python3
+
+import base64
+import os
+import sys
+
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../../dependencies')
+sys.path.append(os.path.dirname(sys.argv[0]) + '/../share/nDPId')
+sys.path.append(os.path.dirname(sys.argv[0]))
+sys.path.append(sys.base_prefix + '/share/nDPId')
+import nDPIsrvd
+from nDPIsrvd import nDPIsrvdSocket, TermColor
+
+class Stats:
+ KEYS = [ ['init','reconnect','shutdown','status' ], \
+ [ 'new','end','idle','update', ],
+ [ 'analyse' ], \
+ [ 'guessed','detected','detection-update','not-detected' ], \
+ [ 'packet', 'packet-flow'] ]
+ ALL_KEYS = KEYS[0] + KEYS[1] + KEYS[2] + KEYS[3] + KEYS[4]
+
+ def __init__(self, nDPIsrvd_sock):
+ self.nsock = nDPIsrvd_sock
+ self.event_counter = dict()
+ self.resetEventCounter()
+ self.lines_processed = 0
+ self.print_dot_every = 10
+ self.print_nmb_every = self.print_dot_every * 5
+
+ def resetEventCounter(self):
+ for k in Stats.ALL_KEYS:
+ self.event_counter[k] = 0
+
+ def incrementEventCounter(self, json_dict):
+ try:
+ if 'daemon_event_name' in json_dict:
+ self.event_counter[json_dict['daemon_event_name']] += 1
+ if 'flow_event_name' in json_dict:
+ self.event_counter[json_dict['flow_event_name']] += 1
+ if 'packet_event_name' in json_dict:
+ self.event_counter[json_dict['packet_event_name']] += 1
+ except KeyError as e:
+ raise RuntimeError('Semantic validation failed for event counter '
+ 'which received an invalid key: {}'.format(str(e)))
+
+ def verifyEventCounter(self):
+ if self.event_counter['shutdown'] != self.event_counter['init'] or self.event_counter['init'] == 0:
+ return False
+ if self.event_counter['new'] != self.event_counter['end'] + self.event_counter['idle']:
+ return False
+ if self.event_counter['new'] < self.event_counter['detected'] + self.event_counter['not-detected']:
+ return False
+ if self.event_counter['new'] < self.event_counter['guessed'] + self.event_counter['not-detected']:
+ return False
+
+ return True
+
+ def getEventCounterStr(self):
+ retval = str()
+ retval += '-' * 98 + '--\n'
+ for klist in Stats.KEYS:
+ for k in klist:
+ retval += '| {:<16}: {:<4} '.format(k, self.event_counter[k])
+ retval += '\n--' + '-' * 98 + '\n'
+ return retval
+
+class SemanticValidationException(Exception):
+ def __init__(self, current_flow, text):
+ self.text = text
+ self.current_flow = current_flow
+ def __str__(self):
+ if self.current_flow is None:
+ return '{}'.format(self.text)
+ else:
+ return 'Flow ID {}: {}'.format(self.current_flow.flow_id, self.text)
+
+def verifyFlows(nsock, instance):
+ invalid_flows = nsock.verify()
+ if len(invalid_flows) > 0:
+ invalid_flows_str = ''
+ for flow_id in invalid_flows:
+ flow = instance.flows[flow_id]
+ try:
+ l4_proto = flow.l4_proto
+ except AttributeError:
+ l4_proto = 'n/a'
+ invalid_flows_str += '{} proto[{},{}] ts[{} + {} < {}] diff[{}], '.format(flow_id, l4_proto, flow.flow_idle_time,
+ flow.flow_last_seen, flow.flow_idle_time,
+ instance.most_recent_flow_time,
+ instance.most_recent_flow_time -
+ (flow.flow_last_seen + flow.flow_idle_time))
+
+ raise SemanticValidationException(None, 'Flow Manager verification failed for: {}'.format(invalid_flows_str[:-2]))
+
+def onFlowCleanup(instance, current_flow, global_user_data):
+ if type(instance) is not nDPIsrvd.Instance:
+ raise SemanticValidationException(current_flow,
+ 'instance is not of type nDPIsrvd.Instance: ' \
+ '{}'.format(type(instance)))
+ if type(current_flow) is not nDPIsrvd.Flow:
+ raise SemanticValidationException(current_flow,
+ 'current_flow is not of type nDPIsrvd.Flow: ' \
+ '{}'.format(type(current_flow)))
+ if type(global_user_data) is not tuple:
+ raise SemanticValidationException(current_flow,
+ 'global_user_data is not of type tuple: ' \
+ '{}'.format(type(global_user_data)))
+
+ if current_flow.cleanup_reason == nDPIsrvd.FlowManager.CLEANUP_REASON_INVALID:
+ raise SemanticValidationException(current_flow,
+ 'Invalid flow cleanup reason')
+
+ if current_flow.cleanup_reason == nDPIsrvd.FlowManager.CLEANUP_REASON_FLOW_TIMEOUT:
+ raise SemanticValidationException(current_flow,
+ 'Unexpected flow cleanup reason: CLEANUP_REASON_FLOW_TIMEOUT')
+
+ try:
+ l4_proto = current_flow.l4_proto
+ except AttributeError:
+ l4_proto = 'n/a'
+
+ verifyFlows(stats.nsock, instance)
+
+ return True
+
+def onJsonLineRecvd(json_dict, instance, current_flow, global_user_data):
+ _, stats = global_user_data
+ stats.incrementEventCounter(json_dict)
+ verifyFlows(stats.nsock, instance)
+
+ if type(instance) is not nDPIsrvd.Instance:
+ raise SemanticValidationException(current_flow,
+ 'instance is not of type nDPIsrvd.Instance: ' \
+ '{}'.format(type(instance)))
+ if type(current_flow) is not nDPIsrvd.Flow and current_flow is not None:
+ raise SemanticValidationException(current_flow,
+ 'current_flow is not of type nDPIsrvd.Flow: ' \
+ '{}'.format(type(current_flow)))
+ if type(global_user_data) is not tuple:
+ raise SemanticValidationException(current_flow,
+ 'global_user_data is not of type tuple: ' \
+ '{}'.format(type(global_user_data)))
+ if type(stats) is not Stats:
+ raise SemanticValidationException(current_flow,
+ 'stats is not of type Stats: ' \
+ '{}'.format(type(stats)))
+
+ td = instance.getThreadDataFromJSON(json_dict)
+
+ for event_name in ['error_event_name', 'daemon_event_name',
+ 'packet_event_name', 'flow_event_name']:
+ if event_name in json_dict and json_dict[event_name].lower() == 'invalid':
+ raise SemanticValidationException(current_flow,
+ 'Received an invalid event for {}'.format(event_name))
+
+ if td is not None:
+ lowest_possible_flow_id = getattr(td, 'lowest_possible_flow_id', 0)
+ lowest_possible_packet_id = getattr(td, 'lowest_possible_packet_id', 0)
+ else:
+ lowest_possible_flow_id = 0
+ lowest_possible_packet_id = 0
+
+ if current_flow is not None:
+
+ if instance.flows[current_flow.flow_id] != current_flow:
+ raise SemanticValidationException(current_flow,
+ 'FlowManager flow reference != current flow reference: ' \
+ '{} != {}'.format(instance.flows[current_flow.flow_id], current_flow))
+
+ if 'l4_proto' in json_dict:
+ try:
+ l4_proto = current_flow.l4_proto
+ except AttributeError:
+ l4_proto = current_flow.l4_proto = json_dict['l4_proto']
+
+ if l4_proto != json_dict['l4_proto']:
+ raise SemanticValidationException(current_flow, 'Layer4 protocol mismatch: {} != {}'.format(l4_proto, json_dict['l4_proto']))
+ elif json_dict['packet_event_name'] != 'packet-flow':
+ raise SemanticValidationException(current_flow, 'Layer4 protocol not found in JSON')
+
+ flow_last_seen = None
+ if 'flow_src_last_pkt_time' in json_dict or 'flow_dst_last_pkt_time' in json_dict:
+ flow_last_seen = max(json_dict['flow_src_last_pkt_time'], json_dict['flow_dst_last_pkt_time'])
+ if flow_last_seen != current_flow.flow_last_seen:
+ raise SemanticValidationException(current_flow, 'Flow last seen: {} != {}'.format(flow_last_seen,
+ current_flow.flow_last_seen))
+
+ if 'flow_idle_time' in json_dict:
+ if json_dict['flow_idle_time'] != current_flow.flow_idle_time:
+ raise SemanticValidationException(current_flow, 'Flow idle time mismatch: {} != {}'.format(json_dict['flow_idle_time'],
+ current_flow.flow_idle_time))
+
+ if (flow_last_seen is not None and 'flow_idle_time' not in json_dict) or \
+ (flow_last_seen is None and 'flow_idle_time' in json_dict):
+ raise SemanticValidationException(current_flow,
+ 'Got a JSON message with only 2 of 3 keys, ' \
+ 'required for timeout handling: flow_idle_time')
+
+ if 'thread_ts_usec' in json_dict:
+ current_flow.thread_ts_usec = int(json_dict['thread_ts_usec'])
+
+ if 'flow_packet_id' in json_dict:
+ try:
+ if json_dict['flow_packet_id'] != current_flow.flow_packet_id + 1:
+ raise SemanticValidationException(current_flow,
+ 'Invalid flow_packet_id seen, expected {}, got ' \
+ '{}'.format(current_flow.flow_packet_id + 1, json_dict['flow_packet_id']))
+ else:
+ current_flow.flow_packet_id += 1
+ except AttributeError:
+ pass
+
+ try:
+ if current_flow.flow_ended == True:
+ raise SemanticValidationException(current_flow,
+ 'Received JSON message for a flow that already ended/idled.')
+ except AttributeError:
+ pass
+
+ if 'packet_event_name' in json_dict:
+ base64.b64decode(json_dict['pkt'], validate=True)
+
+ if json_dict['packet_event_name'] == 'packet-flow':
+ if lowest_possible_packet_id > json_dict['packet_id']:
+ raise SemanticValidationException(current_flow,
+ 'Invalid packet id for thread {} received: ' \
+ 'expected packet id lesser or equal {}, ' \
+ 'got {}'.format(json_dict['thread_id'],
+ lowest_possible_packet_id,
+ json_dict['packet_id']))
+ if td is not None:
+ td.lowest_possible_packet_id = lowest_possible_packet_id
+
+ if 'flow_id' in json_dict:
+ if current_flow.flow_id != json_dict['flow_id']:
+ raise SemanticValidationException(current_flow,
+ 'Current flow id != JSON dictionary flow id: ' \
+ '{} != {}'.format(current_flow.flow_id, json_dict['flow_id']))
+
+ if 'flow_event_name' in json_dict:
+ try:
+ if current_flow.flow_detection_finished == True and \
+ (json_dict['flow_event_name'] == 'detected' or \
+ json_dict['flow_event_name'] == 'guessed'):
+ raise SemanticValidationException(current_flow,
+ 'Received another detected/guessed event after '
+ 'a flow was already detected')
+
+ if current_flow.flow_detected == True and \
+ json_dict['flow_state'] == 'finished' and \
+ json_dict['ndpi']['proto'] == 'Unknown' and \
+ json_dict['ndpi']['category'] == 'Unknown':
+ raise SemanticValidationException(current_flow,
+ 'Flow detection successfully finished, but '
+ 'flow update indiciates an unknown flow.')
+ except AttributeError:
+ pass
+
+ try:
+ if current_flow.flow_finished == True and \
+ json_dict['flow_event_name'] == 'detection-update':
+ raise SemanticValidationException(current_flow,
+ 'Flow state already finished, but another detection-update received.')
+ except AttributeError:
+ pass
+
+ try:
+ if json_dict['flow_state'] == 'finished':
+ current_flow.flow_finished = True
+ elif json_dict['flow_state'] == 'info' and \
+ current_flow.flow_finished is True:
+ raise SemanticValidationException(current_flow,
+ 'Flow state already finished, but switched back to info state.')
+
+ if current_flow.flow_finished == True and \
+ json_dict['flow_event_name'] != 'analyse' and \
+ json_dict['flow_event_name'] != 'update' and \
+ json_dict['flow_event_name'] != 'idle' and \
+ json_dict['flow_event_name'] != 'end':
+ raise SemanticValidationException(current_flow,
+ 'Flow detection finished, but received another '
+ '{} event'.format(json_dict['flow_event_name']))
+ except AttributeError:
+ pass
+
+ try:
+ if json_dict['flow_first_seen'] > current_flow.thread_ts_usec or \
+ flow_last_seen > current_flow.thread_ts_usec or \
+ json_dict['flow_first_seen'] > flow_last_seen:
+ raise SemanticValidationException(current_flow,
+ 'Last packet timestamp is invalid: ' \
+ 'first_seen({}) <= {} >= last_seen({})'.format(json_dict['flow_first_seen'],
+ current_flow.thread_ts_usec,
+ flow_last_seen))
+ except AttributeError:
+ if json_dict['flow_event_name'] == 'new':
+ pass
+
+ if json_dict['flow_event_name'] == 'end' or \
+ json_dict['flow_event_name'] == 'idle':
+ current_flow.flow_ended = True
+ elif json_dict['flow_event_name'] == 'new':
+ if lowest_possible_flow_id > current_flow.flow_id:
+ raise SemanticValidationException(current_flow,
+ 'JSON dictionary lowest flow id for new flow > current flow id: ' \
+ '{} != {}'.format(lowest_possible_flow_id, current_flow.flow_id))
+ try:
+ if current_flow.flow_new_seen == True:
+ raise SemanticValidationException(current_flow,
+ 'Received flow new event twice.')
+ except AttributeError:
+ pass
+ current_flow.flow_new_seen = True
+ current_flow.flow_packet_id = 0
+ if lowest_possible_flow_id == 0 and td is not None:
+ td.lowest_possible_flow_id = current_flow.flow_id
+ elif json_dict['flow_event_name'] == 'detected' or \
+ json_dict['flow_event_name'] == 'not-detected':
+ try:
+ if current_flow.flow_detection_finished is True:
+ raise SemanticValidationException(current_flow,
+ 'Flow detection already finished, but detected/not-detected event received.')
+ except AttributeError:
+ pass
+ current_flow.flow_detection_finished = True
+ current_flow.flow_detected = True if json_dict['flow_event_name'] == 'detected' else False
+
+ try:
+ if current_flow.flow_new_seen is True and lowest_possible_flow_id > current_flow.flow_id:
+ raise SemanticValidationException(current_flow, 'Lowest flow id for flow > current flow id: ' \
+ '{} > {}'.format(lowest_possible_flow_id, current_flow.flow_id))
+ except AttributeError:
+ pass
+
+ stats.lines_processed += 1
+ if stats.lines_processed % stats.print_dot_every == 0:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+ print_nmb_every = stats.print_nmb_every + (len(str(stats.lines_processed)) * stats.print_dot_every)
+ if stats.lines_processed % print_nmb_every == 0:
+ sys.stdout.write(str(stats.lines_processed))
+ sys.stdout.flush()
+
+ return True
+
+if __name__ == '__main__':
+ argparser = nDPIsrvd.defaultArgumentParser()
+ argparser.add_argument('--strict', action='store_true', default=False, help='Require and validate a full nDPId application lifecycle.')
+ args = argparser.parse_args()
+ address = nDPIsrvd.validateAddress(args)
+
+ sys.stderr.write('Recv buffer size: {}\n'.format(nDPIsrvd.NETWORK_BUFFER_MAX_SIZE))
+ sys.stderr.write('Connecting to {} ..\n'.format(address[0]+':'+str(address[1]) if type(address) is tuple else address))
+
+ nsock = nDPIsrvdSocket()
+ nsock.connect(address)
+ stats = Stats(nsock)
+ try:
+ nsock.loop(onJsonLineRecvd, onFlowCleanup, (args.strict, stats))
+ except nDPIsrvd.SocketConnectionBroken as err:
+ sys.stderr.write('\n{}\n'.format(err))
+ except KeyboardInterrupt:
+ print()
+ except Exception as e:
+ for failed_line in nsock.failed_lines:
+ sys.stderr.write('Affected JSON line: {}\n'.format(failed_line[0]))
+ raise(e)
+
+ sys.stderr.write('\nEvent counter:\n' + stats.getEventCounterStr() + '\n')
+ if args.strict is True:
+ if stats.verifyEventCounter() is False:
+ sys.stderr.write('Event counter verification failed. (`--strict\')\n')
+ sys.exit(1)
diff --git a/examples/yaml-filebeat/filebeat.yml b/examples/yaml-filebeat/filebeat.yml
new file mode 100644
index 000000000..c8428258b
--- /dev/null
+++ b/examples/yaml-filebeat/filebeat.yml
@@ -0,0 +1,28 @@
+filebeat.inputs:
+- type: unix
+ id: "NDPId-logs" # replace this index to your preference
+ max_message_size: 100MiB
+ index: "index-name" # Replace this with your desired index name in Elasticsearch
+ enabled: true
+ path: "/var/run/nDPId.sock" # point nDPId to this Unix Socket (Collector)
+ processors:
+ - script: # execute javascript to remove the first 5-digit-number and also the Newline at the end
+ lang: javascript
+ id: trim
+ source: >
+ function process(event) {
+ event.Put("message", event.Get("message").trim().slice(5));
+ }
+ - decode_json_fields: # Decode the Json output
+ fields: ["message"]
+ process_array: true
+ max_depth: 10
+ target: ""
+ overwrite_keys: true
+ add_error_key: false
+ - drop_fields: # Deletes the Message field, which is the undecoded json (You may comment this out if you need the original message)
+ fields: ["message"]
+ - rename:
+ fields:
+ - from: "source" # Prevents a conflict in Elasticsearch and renames the field
+ to: "Source_Interface" \ No newline at end of file