diff --git a/include/Host.h b/include/Host.h index 463c0fde64..6a3d940706 100644 --- a/include/Host.h +++ b/include/Host.h @@ -370,7 +370,9 @@ class Host : public GenericHashEntry, public HostAlertableEntity, public Score, virtual u_int16_t getNumActiveContactsAsServer() { return 0; }; inline TcpPacketStats* getTcpPacketSentStats() { return(stats->getTcpPacketSentStats()); } inline TcpPacketStats* getTcpPacketRcvdStats() { return(stats->getTcpPacketRcvdStats()); } - + virtual void addContactedDomainName(char* domain_name) {}; + virtual u_int32_t getDomainNamesCardinality() { return 0; }; + virtual void resetDomainNamesCardinality() {}; virtual NetworkStats* getNetworkStats(int16_t networkId) { return(NULL); }; inline Country* getCountryStats() { return country; }; diff --git a/include/HostStats.h b/include/HostStats.h index fbc50b7961..d1cab92aa4 100644 --- a/include/HostStats.h +++ b/include/HostStats.h @@ -109,6 +109,9 @@ class HostStats: public GenericTrafficElement { virtual u_int16_t getNumActiveContactsAsClient() { return 0; } virtual u_int16_t getNumActiveContactsAsServer() { return 0; } virtual void resetTopSitesData() {}; + virtual void addContactedDomainName(char* domain_name) {} + virtual u_int32_t getDomainNamesCardinality() {return (u_int32_t)-1; } + virtual void resetDomainNamesCardinality() {} inline void incSentStats(u_int num_pkts, u_int pkt_len) { sent_stats.incStats(num_pkts, pkt_len); }; inline void incRecvStats(u_int num_pkts, u_int pkt_len) { recv_stats.incStats(num_pkts, pkt_len); }; diff --git a/include/LocalHost.h b/include/LocalHost.h index 2ee581253d..423aa454e9 100644 --- a/include/LocalHost.h +++ b/include/LocalHost.h @@ -102,6 +102,9 @@ class LocalHost : public Host, public SerializableElement { virtual u_int16_t getNumActiveContactsAsClient() { return stats->getNumActiveContactsAsClient(); }; virtual u_int16_t getNumActiveContactsAsServer() { return stats->getNumActiveContactsAsServer(); }; virtual void reloadPrefs(); + virtual void addContactedDomainName(char* domain_name) { stats->addContactedDomainName(domain_name); } + virtual u_int32_t getDomainNamesCardinality() { return stats->getDomainNamesCardinality(); } + virtual void resetDomainNamesCardinality() { stats->resetDomainNamesCardinality(); } virtual void deserialize(json_object *obj); virtual void serialize(json_object *obj, DetailsLevel details_level) { return Host::serialize(obj, details_level); }; diff --git a/include/LocalHostStats.h b/include/LocalHostStats.h index e4c6ccf608..32d10772ad 100644 --- a/include/LocalHostStats.h +++ b/include/LocalHostStats.h @@ -37,6 +37,9 @@ class LocalHostStats: public HostStats { /* Estimate of the number of critical servers used by this host */ Cardinality num_dns_servers, num_smtp_servers, num_ntp_servers; + /* Estimate of the number of different Domain Names contacted */ + Cardinality num_contacted_domain_names; + /* Estimate the number of contacted hosts using HyperLogLog */ struct ndpi_hll hll_contacted_hosts; double old_hll_value, new_hll_value, hll_delta_value; @@ -76,6 +79,9 @@ class LocalHostStats: public HostStats { virtual void deserialize(json_object *obj); virtual void lua(lua_State* vm, bool mask_host, DetailsLevel details_level); virtual void resetTopSitesData(); + virtual void addContactedDomainName(char* domain_name) { num_contacted_domain_names.addElement(domain_name,strlen(domain_name)); } + virtual u_int32_t getDomainNamesCardinality() { return num_contacted_domain_names.getEstimate(); } + virtual void resetDomainNamesCardinality() { num_contacted_domain_names.reset(); } virtual void luaDNS(lua_State *vm, bool verbose) { if(dns) dns->lua(vm, verbose); } virtual void luaHTTP(lua_State *vm) { if(http) http->lua(vm); } diff --git a/include/host_alerts/DomainNamesContactsAlert.h b/include/host_alerts/DomainNamesContactsAlert.h new file mode 100644 index 0000000000..5b16c495b8 --- /dev/null +++ b/include/host_alerts/DomainNamesContactsAlert.h @@ -0,0 +1,45 @@ +/* + * + * (C) 2013-21 - ntop.org + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DOMAIN_NAMES_CONTACTS_ALERT_H_ +#define _DOMAIN_NAMES_CONTACTS_ALERT_H_ + +#include "ntop_includes.h" + +class DomainNamesContactsAlert : public ServerContactsAlert { + private: + u_int32_t num_domain_names; + u_int16_t domain_names_threshold; + + ndpi_serializer* getAlertJSON(ndpi_serializer* serializer); + + public: + static HostAlertType getClassType() { return { host_alert_domain_names_contacts, alert_category_network }; } + + DomainNamesContactsAlert(HostCheck *c, Host *f, risk_percentage cli_pctg, u_int32_t _num_domain_names,u_int16_t _domain_names_threshold); + ~DomainNamesContactsAlert() {}; + + + HostAlertType getAlertType() const { return getClassType(); } + u_int8_t getAlertScore() const { return SCORE_LEVEL_NOTICE; }; +}; + +#endif /* _DOMAIN_NAMES_CONTACTS_ALERT_H_ */ diff --git a/include/host_alerts_includes.h b/include/host_alerts_includes.h index 7efaa8bd3d..6000e16198 100644 --- a/include/host_alerts_includes.h +++ b/include/host_alerts_includes.h @@ -38,6 +38,7 @@ #include "host_alerts/DangerousHostAlert.h" #include "host_alerts/RemoteConnectionAlert.h" #include "host_alerts/ScoreAnomalyAlert.h" +#include "host_alerts/DomainNamesContactsAlert.h" /* Pro Alerts - do NOT use #ifdef as alerts must always be available */ diff --git a/include/host_checks/DomainNamesContacts.h b/include/host_checks/DomainNamesContacts.h new file mode 100644 index 0000000000..9b6bc5bf73 --- /dev/null +++ b/include/host_checks/DomainNamesContacts.h @@ -0,0 +1,48 @@ +/* + * + * (C) 2013-21 - ntop.org + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef _DOMAIN_NAMES_CONTACTS_H_ +#define _DOMAIN_NAMES_CONTACTS_H_ + +#include "ntop_includes.h" + +class DomainNamesContacts : public ServerContacts { +private: + u_int16_t domain_names_threshold; + HostAlertType getAlertType() const { return DomainNamesContactsAlert::getClassType(); }; + u_int32_t getContactedServers(Host *h) const { return h->getDomainNamesCardinality(); } + DomainNamesContactsAlert *allocAlert(HostCheck *c, Host *h, risk_percentage cli_pctg, u_int64_t _num_domain_names, u_int64_t _domain_names_threshold) { + return new DomainNamesContactsAlert(c, h, cli_pctg, _num_domain_names,_domain_names_threshold); + }; + +public: + DomainNamesContacts(); + ~DomainNamesContacts() {}; + + void periodicUpdate(Host *h, HostAlert *engaged_alert); + bool loadConfiguration(json_object *config); + + HostCheckID getID() const { return host_check_domain_names_contacts; } + std::string getName() const { return(std::string("domain_names_contacts")); } + +}; + +#endif /* _DOMAIN_NAMES_CONTACTS_H_ */ diff --git a/include/host_checks_includes.h b/include/host_checks_includes.h index f560f9d92e..5ce084c2f2 100644 --- a/include/host_checks_includes.h +++ b/include/host_checks_includes.h @@ -41,6 +41,7 @@ #include "host_checks/DangerousHost.h" #include "host_checks/RemoteConnection.h" +#include "host_checks/DomainNamesContacts.h" #ifdef NTOPNG_PRO diff --git a/include/ntop_typedefs.h b/include/ntop_typedefs.h index 5f51365de5..79473b04f4 100644 --- a/include/ntop_typedefs.h +++ b/include/ntop_typedefs.h @@ -482,7 +482,7 @@ typedef enum { host_alert_flow_flood = 4, host_alert_syn_scan = 5, host_alert_syn_flood = 6, - host_alert_available_01 = 7, /* Available, can be used */ + host_alert_domain_names_contacts = 7, host_alert_p2p_traffic = 8, host_alert_dns_traffic = 9, host_alert_flows_anomaly = 10, @@ -521,6 +521,7 @@ typedef enum { host_check_remote_connection, host_check_dangerous_host, host_check_ntp_traffic, + host_check_domain_names_contacts, NUM_DEFINED_HOST_CHECKS, /* Leave it as last member */ } HostCheckID; diff --git a/scripts/locales/en.lua b/scripts/locales/en.lua index 14cad6c84e..844ec9669f 100644 --- a/scripts/locales/en.lua +++ b/scripts/locales/en.lua @@ -639,6 +639,7 @@ local lang = { ["too_many_hosts_title"] = "Double Max Hosts", ["unknown_contacted_peers"] = "Too many Peers contacted by %{host} %{host_category}.", ["x_alerts"] = "%{num} alerts", + ["host_alert_domain_names_contacts"]= "Too many Domain Names contacted", }, ["alerts_dashboard"] = { ["abort_add_filter"] = "Abort add filter?", @@ -885,6 +886,7 @@ local lang = { ["x_lost"] = "%{lost} Lost", ["x_ooo"] = "%{ooo} Out-of-Order", ["x_retx"] = "%{retx} Retransmissions", + ["host_alert_domain_names_contacts"]= "Trigger an alert when the number of contacted Domain Names exceed a certain threshold", ["tooltips"] = { ["top_addresses"] = "Mac Addresses with most alerts", ["top_alerts"] = "Most seen alerts", @@ -946,6 +948,8 @@ local lang = { ["threshold_type"] = "Callback", ["thresholds_single_source"] = "%{source} %{alt_name} Configuration", ["throughput"] = "Throughput Alert", + ["domain_names_contacts_title"]= "Domain Names Contacts Alert", + ["domain_names_contacts_description"]="Trigger an alert when the number of contacted Domain Names is greater then a certain threshold" }, ["appliance"] = { ["capture_interfaces"] = "Capture Interfaces", diff --git a/scripts/lua/modules/alert_definitions/host/host_alert_domain_names_contacts.lua b/scripts/lua/modules/alert_definitions/host/host_alert_domain_names_contacts.lua new file mode 100644 index 0000000000..cad45122b3 --- /dev/null +++ b/scripts/lua/modules/alert_definitions/host/host_alert_domain_names_contacts.lua @@ -0,0 +1,63 @@ +--(C) 2019-21 - ntop.org + + +--############################################## + +local host_alert_keys = require "host_alert_keys" +package.path = dirs.installdir .. "/scripts/lua/modules/?.lua;" .. package.path +local alert_creators = require "alert_creators" +local json = require("dkjson") +--Import the classes library. +local classes = require "classes" +--Make sure to import the Superclass! +local alert = require "alert" + +--############################################## + +local host_alert_domain_names_contacts = classes.class(alert) + +--############################################## + +host_alert_domain_names_contacts.meta = { +alert_key = host_alert_keys.host_alert_domain_names_contacts, +i18n_title = "alerts_dashboard.threashold_cross", +icon = "fas fa-fw fa-arrow-circle-up", + +} + +--############################################## + +--@brief Prepare an alert table used to generate the alert +--@param one_param The first alert param +--@param another_param The second alert param +--@return A table with the alert built +function host_alert_domain_names_contacts:init() +--Call the parent constructor +self.super:init() + +end + +--####################################################### + +-- @brief Format an alert into a human-readable string +-- @param ifid The integer interface id of the generated alert +-- @param alert The alert description table, including alert data such as the generating entity, timestamp, granularity, type +-- @param alert_type_params Table `alert_type_params` as built in the `:init` method +-- @return A human-readable string +function host_alert_domain_names_contacts.format(ifid, alert, alert_type_params) + local alert_consts = require("alert_consts") + local entity = alert_consts.formatHostAlert(ifid, alert["ip"], alert["vlan_id"]) + local value = alert_type_params.value + + if(value == nil) then value = 0 end + + return i18n("alert_messages.host_alert_domain_names_contacts", { + entity = entity, + value = string.format("%u", math.ceil(value or 0)), + threshold = alert_type_params.threshold or 0, + }) +end + +--####################################################### + +return host_alert_domain_names_contacts diff --git a/scripts/lua/modules/alert_keys/host_alert_keys.lua b/scripts/lua/modules/alert_keys/host_alert_keys.lua index bfbe5b67e2..aa5448a039 100644 --- a/scripts/lua/modules/alert_keys/host_alert_keys.lua +++ b/scripts/lua/modules/alert_keys/host_alert_keys.lua @@ -12,7 +12,7 @@ local host_alert_keys = { host_alert_flow_flood = 4, host_alert_syn_scan = 5, host_alert_syn_flood = 6, - host_alert_available_01 = 7, -- Available, can be used + host_alert_domain_names_contacts = 7, host_alert_p2p_traffic = 8, host_alert_dns_traffic = 9, host_alert_flows_anomaly = 10, diff --git a/scripts/lua/modules/check_definitions/host/domain_names_contacts.lua b/scripts/lua/modules/check_definitions/host/domain_names_contacts.lua new file mode 100644 index 0000000000..c517c3205a --- /dev/null +++ b/scripts/lua/modules/check_definitions/host/domain_names_contacts.lua @@ -0,0 +1,45 @@ +-- +-- (C) 2019-21 - ntop.org +-- + +local checks = require("checks") +local host_alert_keys = require "host_alert_keys" + +local domain_names_contacts = { + -- Script category + category = checks.check_categories.network, + + default_enabled = false, + alert_id = host_alert_keys.host_alert_domain_names_contacts, + + -- The default threshold value. The format is specific of the + -- "threshold_cross" input builder + default_value = { + operator = "gt", + threshold = 250, + }, + + -- Allow user script configuration from the GUI + gui = { + -- Localization strings, from the "locales" directory of the plugin + i18n_title = "alerts_thresholds_config.domain_names_contacts_title", + i18n_description = "alerts_thresholds_config.domain_names_contacts_description", + + -- Specific parameters of this input builder + i18n_field_unit = checks.field_units.contacts, + + -- The input builder to use to draw the gui + input_builder = "threshold_cross", + + -- max allowed threshold value + field_max = 65535, + -- min allowed threshold value + field_min = 1, + -- threshold check operator. "gt" for ">", "lt" for "<" + field_operator = "gt"; + } +} + +-- ################################################################# + +return domain_names_contacts diff --git a/src/Flow.cpp b/src/Flow.cpp index 751f3f2e41..71a8c185d9 100644 --- a/src/Flow.cpp +++ b/src/Flow.cpp @@ -98,6 +98,8 @@ Flow::Flow(NetworkInterface *_iface, iface->findFlowHosts(_vlanId, _observation_point_id, _cli_mac, _cli_ip, &cli_host, _srv_mac, _srv_ip, &srv_host); PROFILING_SUB_SECTION_EXIT(iface, 7); + char *domain_name = ndpiFlow ? ndpi_get_flow_name(ndpiFlow) : NULL; + if(cli_host) { NetworkStats *network_stats = cli_host->getNetworkStats(cli_host->get_local_network_id()); @@ -106,6 +108,7 @@ Flow::Flow(NetworkInterface *_iface, cli_ip_addr = cli_host->get_ip(); cli_host->incCliContactedHosts(_srv_ip); cli_host->incCliContactedPorts(_srv_port); + if(domain_name!=NULL) cli_host->addContactedDomainName(domain_name); } else { /* Client host has not been allocated, let's keep the info in an IpAddress */ if((cli_ip_addr = new (std::nothrow) IpAddress(*_cli_ip))) cli_ip_addr->reloadBlacklist(iface->get_ndpi_struct()); diff --git a/src/HostChecksLoader.cpp b/src/HostChecksLoader.cpp index 3cf6463d69..9c483ceaaf 100644 --- a/src/HostChecksLoader.cpp +++ b/src/HostChecksLoader.cpp @@ -61,6 +61,7 @@ void HostChecksLoader::registerChecks() { if((fcb = new DNSTraffic())) registerCheck(fcb); if((fcb = new RemoteConnection())) registerCheck(fcb); if((fcb = new DangerousHost())) registerCheck(fcb); + if((fcb = new DomainNamesContacts())) registerCheck(fcb); #ifdef NTOPNG_PRO if((fcb = new ScoreAnomaly())) registerCheck(fcb); diff --git a/src/LocalHost.cpp b/src/LocalHost.cpp index d95f92f1ff..2ed782defd 100644 --- a/src/LocalHost.cpp +++ b/src/LocalHost.cpp @@ -234,6 +234,7 @@ void LocalHost::lua_contacts_stats(lua_State *vm) const { lua_push_uint32_table_entry(vm, "dns", stats->getDNSContactCardinality()); lua_push_uint32_table_entry(vm, "smtp", stats->getSMTPContactCardinality()); lua_push_uint32_table_entry(vm, "ntp", stats->getNTPContactCardinality()); + lua_push_uint32_table_entry(vm, "domain_names", stats->getDomainNamesCardinality()); lua_pushstring(vm, "server_contacts"); lua_insert(vm, -2); diff --git a/src/LocalHostStats.cpp b/src/LocalHostStats.cpp index 1af6188818..8b7a27060f 100644 --- a/src/LocalHostStats.cpp +++ b/src/LocalHostStats.cpp @@ -50,6 +50,7 @@ LocalHostStats::LocalHostStats(Host *_host) : HostStats(_host) { num_dns_servers.init(5); num_smtp_servers.init(5); num_ntp_servers.init(5); + num_contacted_domain_names.init(4); } /* *************************************** */ @@ -71,6 +72,7 @@ LocalHostStats::LocalHostStats(LocalHostStats &s) : HostStats(s) { num_dns_servers.init(5); num_smtp_servers.init(5); num_ntp_servers.init(5); + num_contacted_domain_names.init(4); } /* *************************************** */ diff --git a/src/host_alerts/DomainNamesContactsAlert.cpp b/src/host_alerts/DomainNamesContactsAlert.cpp new file mode 100644 index 0000000000..e207e9f6cd --- /dev/null +++ b/src/host_alerts/DomainNamesContactsAlert.cpp @@ -0,0 +1,44 @@ +/* + * + * (C) 2013-21 - ntop.org + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "host_alerts_includes.h" + +/* ***************************************************** */ + +DomainNamesContactsAlert::DomainNamesContactsAlert(HostCheck *c, Host *f, risk_percentage cli_pctg, u_int32_t _num_domain_names, u_int16_t _domain_names_threshold) : ServerContactsAlert (c, f, cli_pctg,_num_domain_names,_domain_names_threshold) { + + num_domain_names = _num_domain_names; + domain_names_threshold=_domain_names_threshold; +}; + +/* ***************************************************** */ + +ndpi_serializer* DomainNamesContactsAlert::getAlertJSON(ndpi_serializer* serializer) { + if(serializer == NULL) + return NULL; + + ndpi_serialize_string_uint64(serializer, "num_domain_names", num_domain_names); + ndpi_serialize_string_uint64(serializer, "threshold", domain_names_threshold); + + return serializer; +} + +/* ***************************************************** */ diff --git a/src/host_checks/DomainNamesContacts.cpp b/src/host_checks/DomainNamesContacts.cpp new file mode 100644 index 0000000000..3b5d786777 --- /dev/null +++ b/src/host_checks/DomainNamesContacts.cpp @@ -0,0 +1,60 @@ +/* + * + * (C) 2013-21 - ntop.org + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include "ntop_includes.h" +#include "host_checks_includes.h" + +/* ***************************************************** */ + +DomainNamesContacts::DomainNamesContacts() : ServerContacts() { + domain_names_threshold=(u_int16_t)-1; +}; + +/* ***************************************************** */ + +void DomainNamesContacts::periodicUpdate(Host *h, HostAlert *engaged_alert) { + HostAlert *alert = engaged_alert; + u_int32_t num_domain_names = 0; + + + if((num_domain_names = h->getDomainNamesCardinality()) > domain_names_threshold ) { + if (!alert) alert = allocAlert(this, h, CLIENT_FAIR_RISK_PERCENTAGE, num_domain_names,domain_names_threshold); + if (alert) h->triggerAlert(alert); + } + + h->resetDomainNamesCardinality(); + +} + +bool DomainNamesContacts::loadConfiguration(json_object *config) { + json_object *json_threshold; + + HostCheck::loadConfiguration(config); /* Parse parameters in common */ + + if(json_object_object_get_ex(config, "threshold", &json_threshold)) + domain_names_threshold = (u_int16_t)json_object_get_int64(json_threshold); + + // ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s %u", json_object_to_json_string(config), ntp_bytes_threshold); + + return(true); +} + +/* ***************************************************** */