ntopng/src/Flow.cpp
Alfredo Cardigliano bc610bb3ee Add comments
2025-09-10 15:45:56 +02:00

9378 lines
304 KiB
C++

/*
*
* (C) 2013-25 - 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"
/* static so default is zero-initialization, let's just define it */
// #define DEBUG_DISCOVERY
// #define DEBUG_UA
// #define DEBUG_SCORE
/* #define BLACKLISTED_FLOWS_DEBUG */
/* *************************************** */
Flow::Flow(NetworkInterface *_iface,
int32_t _iface_idx, u_int16_t _vlanId,
u_int16_t _observation_point_id, u_int32_t _private_flow_id,
u_int8_t _protocol, Mac *_cli_mac, IpAddress *_cli_ip,
u_int16_t _cli_port, Mac *_srv_mac, IpAddress *_srv_ip,
u_int16_t _srv_port, const ICMPinfo *const _icmp_info,
time_t _first_seen, time_t _last_seen, u_int8_t *_view_cli_mac,
u_int8_t *_view_srv_mac)
: GenericHashEntry(_iface) {
if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[new] %s", __FILE__);
creation_time = iface->getTimeLastPktRcvd(),
iface_index = _iface_idx,
vlanId = _vlanId, protocol = _protocol, cli_port = _cli_port,
srv_port = _srv_port, privateFlowId = _private_flow_id;
flow_dropped_counts_increased = 0, protocolErrorCode = 0;
srcAS = dstAS = srcPeerAS = dstPeerAS = 0, rttSec = 0;
srcASName = dstASName = NULL;
src2dst_tcp_flags = dst2src_tcp_flags = 0;
collected_qoe.src_to_dst = collected_qoe.dst_to_src = NTOP_QOE_UNKNOWN, has_collected_qoe = 0;
tcp = NULL;
#ifdef NTOPNG_PRO
udp = NULL;
#endif
if(ntop->getPrefs()->fullStatsEnabled() && getInterface()->isPacketInterface()) {
if(protocol == IPPROTO_TCP) {
tcp = (FlowTCP*)calloc(1, sizeof(FlowTCP));
if(tcp != NULL) {
ndpi_init_data_analysis(&tcp->tcpWin.cli_to_srv, 0);
ndpi_init_data_analysis(&tcp->tcpWin.srv_to_cli, 0);
ndpi_init_data_analysis(&tcp->rtt.cli_to_srv, 0);
ndpi_init_data_analysis(&tcp->rtt.srv_to_cli, 0);
}
}
#ifdef NTOPNG_PRO
else {
if(protocol == IPPROTO_UDP) {
udp = (FlowUDP*)calloc(1, sizeof(FlowUDP));
if(udp != NULL) {
ndpi_init_data_analysis(&udp->rtt.cli_min_rtt, 0);
ndpi_init_data_analysis(&udp->rtt.srv_min_rtt, 0);
}
}
}
#endif
}
collection = NULL;
tcp_fingerprint = NULL;
predominant_alert.id = flow_alert_normal,
predominant_alert.category = alert_category_other,
predominant_alert_score = 0;
alert_info.is_cli_attacker =
alert_info.is_cli_victim =
alert_info.is_srv_attacker =
alert_info.is_srv_victim = 0;
alert_info.auto_acknowledge = 1;
pending_alerts = refresh_triggered_alerts = false;
category_list_name_shared_pointer = NULL;
ndpiAddressFamilyProtocol = NULL;
ndpi_confidence = NDPI_CONFIDENCE_UNKNOWN;
clearRisks();
/* Note is_periodic_flow is updated by the updateFlowPeriodicity() call */
detection_completed = 0, non_zero_payload_observed = 0, is_periodic_flow = 0,
extra_dissection_completed = 0,
has_malicious_cli_signature = has_malicious_srv_signature = 0,
iface_flow_accounted = 0, periodicity = 0;
memset(&ndpiDetectedProtocol, 0, sizeof(ndpiDetectedProtocol));
cli2srv_tos = srv2cli_tos = 0;
src2dst_tcp_zero_window = dst2src_tcp_zero_window = 0;
swap_done = swap_requested = 0;
current_c_state = MINOR_NO_STATE;
#ifdef HAVE_NEDGE
last_conntrack_update = 0;
marker = MARKER_NO_ACTION;
#endif
host_server_name = NULL, flow_source = packet_to_flow;
icmp_info = _icmp_info ? new (std::nothrow) ICMPinfo(*_icmp_info) : NULL;
ndpiFlow = NULL, confidence = NDPI_CONFIDENCE_UNKNOWN;
json_info = NULL, tlv_info = NULL, twh_over = 0,
dissect_next_http_packet = 0;
periodic_stats_update_partial = NULL;
bt_hash = NULL, ebpf = NULL, iec104 = NULL, stun_mapped_address = NULL;
twh_over_view = 0, shapers_profile_set = 0;
flow_verdict = 0;
operating_system = ndpi_os_unknown;
last_update_time.tv_sec = 0, last_update_time.tv_usec = 0;
bytes_thpt = 0, goodput_bytes_thpt = 0;
pkts_thpt = 0;
top_bytes_thpt = 0, top_pkts_thpt = 0, top_goodput_bytes_thpt = 0,
applLatencyMsec = 0;
external_alert.json = NULL, external_alert.source = NULL;
trigger_immediate_periodic_update = false;
next_call_periodic_update = 0;
riskInfo = NULL, json_protocol_info = NULL;
alerts_json = NULL, alerts_json_shadow = NULL;
end_reason = NULL;
ndpiFlowRiskName = NULL;
#ifdef NTOPNG_PRO
viewFlowStats = NULL;
#endif
suspicious_dga_domain = NULL;
flow_payload = NULL, flow_payload_len = 0;
last_db_dump.partial = NULL;
last_db_dump.first_seen = last_db_dump.last_seen = 0;
last_db_dump.in_progress = false;
memset(&protos, 0, sizeof(protos));
memset(&flow_device, 0, sizeof(flow_device));
flow_score = 0;
rtp_stream_type = ndpi_multimedia_unknown_flow;
#ifdef NTOPNG_PRO
rtp = NULL;
#endif
cli_ip_addr = srv_ip_addr = NULL;
cli_host = srv_host = NULL;
INTERFACE_PROFILING_SUB_SECTION_ENTER(iface,
"Flow::Flow: iface->findFlowHosts", 7);
iface->findFlowHosts(_iface_idx, _vlanId, _observation_point_id, _private_flow_id,
_cli_mac, _cli_ip, &cli_host, _srv_mac, _srv_ip, &srv_host);
INTERFACE_PROFILING_SUB_SECTION_EXIT(iface, 7);
if(_observation_point_id) {
iface->incObservationPointIdFlows(_observation_point_id);
flow_device.observation_point_id = _observation_point_id;
}
#ifdef NTOPNG_PRO
if(_iface->isViewed()) {
memset(view_cli_mac, 0, sizeof(view_cli_mac));
memset(view_srv_mac, 0, sizeof(view_srv_mac));
if(_view_cli_mac)
memcpy(view_cli_mac, _view_cli_mac, sizeof(view_cli_mac));
if(_view_srv_mac)
memcpy(view_srv_mac, _view_srv_mac, sizeof(view_srv_mac));
}
#endif
/*
Standard Interface
Increase the hosts counter or Init the IP Addresses in case hosts are NULL
*/
if(cli_host) {
cli_host->incUses(), cli_host->incNumFlows(last_seen, true, isTCP());
cli_host->incCliContactedPorts(_srv_port);
cli_ip_addr = cli_host->get_ip();
} else {
/*
View Interface
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());
}
if(srv_host) {
/* Standard Interface */
srv_host->incUses(), srv_host->incNumFlows(last_seen, false, isTCP());
srv_host->incSrvPortsContacts(htons(_cli_port));
srv_host->incSrvHostContacts(_cli_ip);
srv_ip_addr = srv_host->get_ip();
if(cli_host) {
if(srv_ip_addr->isBroadcastAddress() || srv_ip_addr->isMulticastAddress()) {
cli_host->setMACmeaningful();
} else {
if(cli_ip_addr->isIPv4()) {
u_int32_t c_ip = cli_ip_addr->get_ipv4();
u_int32_t d_ip = srv_ip_addr->get_ipv4();
if((ntohl(c_ip) & 0xFFFFFF00) == (ntohl(d_ip) & 0xFFFFFF00))
cli_host->setMACmeaningful(), srv_host->setMACmeaningful();
}
}
}
} else {
/*
View Interface
Server host has not been allocated, let's keep the info in an IpAddress
*/
if((srv_ip_addr = new (std::nothrow) IpAddress(*_srv_ip)))
srv_ip_addr->reloadBlacklist(iface->get_ndpi_struct());
}
/* Update broadcast domain, if destination MAC address is broadcast */
if(_cli_mac && _srv_mac &&
_srv_mac->isBroadcast() /* Broadcast MAC address */
&& get_cli_ip_addr()->isIPv4() &&
get_srv_ip_addr()->isIPv4() /* IPv4 only */
&& !get_srv_ip_addr()->isBroadcastAddress() /* Avoid 255.255.255.255 */)
getInterface()->updateBroadcastDomains(_vlanId, _cli_mac->get_mac(), _srv_mac->get_mac(),
ntohl(_cli_ip->get_ipv4()), ntohl(_srv_ip->get_ipv4()));
memset(&custom_app, 0, sizeof(custom_app));
passVerdict = 1;
dropVerdictReason = DROP_REASON_UNKNOWN;
#ifdef HAVE_NEDGE
quota_exceeded = 0;
HostPools *hp = iface->getHostPools();
routing_table_id = DEFAULT_ROUTING_TABLE_ID;
if(hp) {
if(cli_host)
routing_table_id = hp->getRoutingPolicy(cli_host->get_host_pool());
if(srv_host)
routing_table_id = max_val(routing_table_id, hp->getRoutingPolicy(srv_host->get_host_pool()));
}
#endif
#ifdef ALERTED_FLOWS_DEBUG
iface_alert_inc = iface_alert_dec = false;
#endif
if((_first_seen > _last_seen) || (_first_seen == 0))
_first_seen = _last_seen;
first_seen = _first_seen, last_seen = _last_seen;
bytes_thpt_trend = trend_unknown, pkts_thpt_trend = trend_unknown;
memset(&ip_stats_s2d, 0, sizeof(ip_stats_s2d)),
memset(&ip_stats_d2s, 0, sizeof(ip_stats_d2s));
memset(&customFlowAlert, 0, sizeof(customFlowAlert));
if(ntop->getPrefs()->fullStatsEnabled()
&& iface->isPacketInterface()
&& !iface->isSampledTraffic()) {
/* Is this necessary? */
cli2srvPktTime = new (std::nothrow) InterarrivalStats();
srv2cliPktTime = new (std::nothrow) InterarrivalStats();
#ifdef ENABLE_ENTROPHY_CALCULATION
initial_bytes_entropy.c2s = ndpi_alloc_data_analysis(256);
initial_bytes_entropy.s2c = ndpi_alloc_data_analysis(256);
#endif
} else {
cli2srvPktTime = srv2cliPktTime = NULL;
#ifdef ENABLE_ENTROPHY_CALCULATION
initial_bytes_entropy.c2s = initial_bytes_entropy.s2c = NULL;
#endif
}
c2sFirstGoodputTime.tv_sec = c2sFirstGoodputTime.tv_usec = 0;
#ifdef NTOPNG_PRO
modbus = NULL;
lateral_movement = false;
periodicity_status = periodicity_status_unknown;
#ifndef HAVE_NEDGE
trafficProfile = NULL;
#else
cli_shaper_id = srv_shaper_id = DEFAULT_SHAPER_ID;
memset(&flowShapers, 0, sizeof(flowShapers));
cli_quota_source = srv_quota_source = policy_source_default;
#endif
#endif
/* Reset the initial state */
hash_entry_id = 0;
set_hash_entry_state_allocated();
switch (protocol) {
case IPPROTO_TCP:
case IPPROTO_UDP:
if(iface->is_ndpi_enabled()) allocDPIMemory();
if(protocol == IPPROTO_UDP) set_hash_entry_state_flow_notyetdetected();
break;
case IPPROTO_ICMP:
ndpiDetectedProtocol.proto.app_protocol = NDPI_PROTOCOL_IP_ICMP,
ndpiDetectedProtocol.proto.master_protocol = NDPI_PROTOCOL_UNKNOWN;
/* Use nDPI to check potential flow risks */
if(iface->is_ndpi_enabled()) allocDPIMemory();
set_hash_entry_state_flow_notyetdetected();
break;
case IPPROTO_ICMPV6:
ndpiDetectedProtocol.proto.app_protocol = NDPI_PROTOCOL_IP_ICMPV6,
ndpiDetectedProtocol.proto.master_protocol = NDPI_PROTOCOL_UNKNOWN;
/* Use nDPI to check potential flow risks */
if(iface->is_ndpi_enabled()) allocDPIMemory();
set_hash_entry_state_flow_notyetdetected();
break;
default:
setDetectedProtocol(ndpi_guess_undetected_protocol(iface->get_ndpi_struct(), NULL, protocol), true);
break;
}
computeKey();
deferredInitialization();
}
/* *************************************** */
/*
* This function is used to initialize those info
* that are not really necessary at the creation of the flow
*/
void Flow::deferredInitialization() {
#ifdef HAVE_NEDGE
bool flow_blacklisted = false;
#endif
if(cli_host && srv_host) {
char country[64];
if(cli_host->isLocalHost()) {
srv_host->get_country(country, sizeof(country));
if(country[0] != '\0') cli_host->incCountriesContacts(country);
Mac *srv_mac = srv_host->getMac();
/* Add client gateway */
if(srv_mac && !srv_mac->isNull() && !srv_host->isLocalHost()) {
LocalHost *lh = (LocalHost *)cli_host;
lh->setRouterMac(srv_mac);
}
}
if(srv_host->isLocalHost()) {
cli_host->get_country(country, sizeof(country));
if(country[0] != '\0') srv_host->incCountriesContacts(country);
}
}
if(isBlacklistedClient()) {
#ifdef HAVE_NEDGE
flow_blacklisted = true;
#endif
if(srv_host) srv_host->inc_num_blacklisted_flows(false);
} else if(isBlacklistedServer()) {
#ifdef HAVE_NEDGE
flow_blacklisted = true;
#endif
if(cli_host) cli_host->inc_num_blacklisted_flows(true);
}
#ifdef HAVE_NEDGE
if(!flow_blacklisted) {
AddressTree *at = cli_host->getDynamicBlacklist();
if(at != NULL) {
IpAddress *srv_ip = srv_host->get_ip();
if(at->match(srv_ip, srv_ip->isIPv4() ? 32 : 128)) {
/* The server host is present on the dynamic blacklist */
flow_blacklisted = true;
if(cli_host)
cli_host->inc_num_blacklisted_flows(true);
}
}
}
if(flow_blacklisted)
setDropVerdict(DROP_REASON_BLACKLISTED_FLOW);
#endif
iface->execFlowBeginChecks(this);
}
/* *************************************** */
void Flow::allocDPIMemory() {
if((ndpiFlow = (ndpi_flow_struct *)calloc(1, iface->get_flow_size())) == NULL)
throw "Not enough memory";
}
/* *************************************** */
void Flow::freeDPIMemory() {
if(ndpiFlow) {
if(isDNS()) {
if(ndpiFlow && iface->isPacketInterface()) {
if((stats.get_cli2srv_packets() > 0) && (stats.get_srv2cli_packets() == 0)
&& (ndpiFlow->protos.dns.is_query == 0)) {
request_swap(); /* This flow will be swapped */
swap();
}
}
} else if(/* !isDNS() */ ntop->getPrefs()->is_dns_cache_enabled()) {
if(srv_host) {
/* Standard Interface */
updateServerName(srv_host);
}
}
setRisk(ndpi_flow_risk_bitmap | ndpiFlow->risk);
ndpi_confidence = ndpiFlow->confidence;
if((tcp_fingerprint == NULL) && ndpiFlow->tcp.fingerprint)
setHostTCPFingerprint(ndpiFlow->tcp.fingerprint, ndpiFlow->tcp.os_hint);
ndpi_free_flow(ndpiFlow);
ndpiFlow = NULL;
}
}
/* *************************************** */
Flow::~Flow() {
bool is_oneway_tcp_udp_flow =
(((protocol == IPPROTO_TCP) || (protocol == IPPROTO_UDP)) && isOneWay()) ? true : false;
if(trace_new_delete) ntop->getTrace()->traceEvent(TRACE_NORMAL, "[delete] %s", __FILE__);
if(getUses() != 0 && !ntop->getGlobals()->isShutdown())
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[%s] Deleting flow [%u]",
__FUNCTION__, getUses());
/* Delete triggered alerts for the flow */
for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++)
delete it->second;
accountFlowTraffic(true);
#ifdef ALERTED_FLOWS_DEBUG
if(iface_alert_inc && !iface_alert_dec) {
char buf[256];
ntop->getTrace()->traceEvent(TRACE_WARNING, "[MISMATCH][inc but not dec][alerted: %u] %s",
isFlowAlerted() ? 1 : 0, print(buf, sizeof(buf)));
}
#endif
/*
Get client and server hosts. Use unsafe* methods to get the client and
server also for 'viewed' interfaces. For 'Viewed' interfaces, host pointers
are shared across multiple 'viewed' interfaces and thus they are termed as
unsafe.
IMPORTANT: only call here methods that are safe (e.g., locked or atomic-ed).
It is fundamental to only call
*/
Host *cli_u = getViewSharedClient(), *srv_u = getViewSharedServer();
#ifdef NTOPNG_PRO
if(getInterface()->isViewed()) /* Score decrements done here for 'viewed'
interfaces to avoid races. */
decAllFlowScores();
#endif
if(cli_u) {
cli_u->updateView(cli_ip_addr);
cli_u->decUses(); /* Decrease the number of uses */
cli_u->decNumFlows(get_last_seen(), true, isTCP(), twh_over);
if(is_oneway_tcp_udp_flow) cli_u->incUnidirectionalEgressTCPUDPFlows();
}
if(!cli_host && cli_ip_addr) /* Dynamically allocated only when cli_host was NULL in Flow
constructor (viewed interfaces) */
delete cli_ip_addr;
if(srv_u) {
srv_u->updateView(srv_ip_addr);
srv_u->decUses(); /* Decrease the number of uses */
srv_u->decNumFlows(get_last_seen(), false, isTCP(), twh_over);
if(is_oneway_tcp_udp_flow) {
srv_u->incUnidirectionalIngressTCPUDPFlows();
if(cli_u) {
u_int16_t s_port = ntohs(srv_port);
cli_u->setUnidirectionalTCPUDPNoTXEgressFlow(srv_u->get_ip(), s_port);
srv_u->setContactedTCPUDPServerPortNoTX(s_port);
srv_u->setUnidirectionalTCPUDPNoTXIngressFlow(cli_u->get_ip(),
ntohs(srv_port));
}
}
}
if(!srv_host && srv_ip_addr) /* Dynamically allocated only when srv_host was NULL in Flow
constructor (viewed interfaces) */
delete srv_ip_addr;
/*
Finish deleting other flow data structures
*/
freeDPIMemory();
if(tcp != NULL) {
ndpi_free_data_analysis(&tcp->tcpWin.cli_to_srv, 0);
ndpi_free_data_analysis(&tcp->tcpWin.srv_to_cli, 0);
ndpi_free_data_analysis(&tcp->rtt.cli_to_srv, 0);
ndpi_free_data_analysis(&tcp->rtt.srv_to_cli, 0);
free(tcp);
tcp = NULL;
}
#ifdef NTOPNG_PRO
if(udp != NULL) {
ndpi_free_data_analysis(&udp->rtt.cli_min_rtt, 0);
ndpi_free_data_analysis(&udp->rtt.srv_min_rtt, 0);
free(udp);
udp = NULL;
}
#endif
#ifdef NTOPNG_PRO
if (rtp) delete(rtp);
#endif
if(tcp_fingerprint) free(tcp_fingerprint);
if(riskInfo) free(riskInfo);
if(end_reason) free(end_reason);
if(collection) {
if(collection->wifi.wlan_ssid) free(collection->wifi.wlan_ssid);
free(collection);
}
#ifdef NTOPNG_PRO
if(viewFlowStats) delete (viewFlowStats);
#endif
if(periodic_stats_update_partial) delete (periodic_stats_update_partial);
if(last_db_dump.partial) delete (last_db_dump.partial);
if(json_info) json_object_put(json_info);
if(tlv_info) {
ndpi_term_serializer(tlv_info);
free(tlv_info);
}
if(host_server_name) free(host_server_name);
if(iec104) delete iec104;
#ifdef NTOPNG_PRO
if(modbus) delete modbus;
#endif
if(suspicious_dga_domain) free(suspicious_dga_domain);
if(ndpiAddressFamilyProtocol) free(ndpiAddressFamilyProtocol);
if(ebpf) delete ebpf;
if(cli2srvPktTime) delete cli2srvPktTime;
if(srv2cliPktTime) delete srv2cliPktTime;
#ifdef ENABLE_ENTROPHY_CALCULATION
if(initial_bytes_entropy.c2s)
ndpi_free_data_analysis(initial_bytes_entropy.c2s, 1);
if(initial_bytes_entropy.s2c)
ndpi_free_data_analysis(initial_bytes_entropy.s2c, 1);
#endif
if(flow_payload) free(flow_payload);
if(isHTTP() || isHTTP_PROXY()) {
if(protos.http.last_url) free(protos.http.last_url);
if(protos.http.last_user_agent) free(protos.http.last_user_agent);
if(protos.http.last_server) free(protos.http.last_server);
} else if(isDNS()) {
if(protos.dns.last_query) free(protos.dns.last_query);
if(protos.dns.last_query_shadow) free(protos.dns.last_query_shadow);
if(protos.dns.last_rsp) free(protos.dns.last_rsp);
if(protos.dns.last_rsp_shadow) free(protos.dns.last_rsp_shadow);
} else if(isMDNS()) {
if(protos.mdns.answer) free(protos.mdns.answer);
if(protos.mdns.name) free(protos.mdns.name);
if(protos.mdns.name_txt) free(protos.mdns.name_txt);
if(protos.mdns.ssid) free(protos.mdns.ssid);
} else if(isSSDP()) {
if(protos.ssdp.location) free(protos.ssdp.location);
} else if(isNetBIOS()) {
if(protos.netbios.name) free(protos.netbios.name);
} else if(isSIP()) {
if(protos.sip.call_id) free(protos.sip.call_id);
} else if(isSSH()) {
if(protos.ssh.client_signature) free(protos.ssh.client_signature);
if(protos.ssh.server_signature) free(protos.ssh.server_signature);
if(protos.ssh.hassh.client_hash) free(protos.ssh.hassh.client_hash);
if(protos.ssh.hassh.server_hash) free(protos.ssh.hassh.server_hash);
} else if(isTLS()) {
if(protos.tls.client_requested_server_name)
free(protos.tls.client_requested_server_name);
if(protos.tls.server_names) free(protos.tls.server_names);
if(protos.tls.ja4.client_hash) free(protos.tls.ja4.client_hash);
if(protos.tls.client_alpn) free(protos.tls.client_alpn);
if(protos.tls.client_tls_supported_versions)
free(protos.tls.client_tls_supported_versions);
if(protos.tls.issuerDN) free(protos.tls.issuerDN);
if(protos.tls.subjectDN) free(protos.tls.subjectDN);
} else if(isMining()) {
if(protos.mining.currency) free(protos.mining.currency);
} else if(isDHCP()) {
if(protos.dhcp.name) free(protos.dhcp.name);
} else if(isSIP()) {
if(protos.sip.call_id) free(protos.sip.call_id);
} else if(isSMTP()) {
if(protos.smtp.mail_from) free(protos.smtp.mail_from);
if(protos.smtp.rcpt_to) free(protos.smtp.rcpt_to);
}
if(ndpiFlowRiskName) free(ndpiFlowRiskName);
if(bt_hash) free(bt_hash);
if(stun_mapped_address) free(stun_mapped_address);
if(icmp_info) delete (icmp_info);
if(json_protocol_info) free(json_protocol_info);
if(alerts_json) free(alerts_json);
if(alerts_json_shadow) free(alerts_json_shadow);
if(external_alert.json) json_object_put(external_alert.json);
if(external_alert.source) free(external_alert.source);
if(customFlowAlert.msg) free(customFlowAlert.msg);
}
/* *************************************** */
u_int16_t Flow::getStatsProtocol() const {
u_int16_t stats_protocol;
if(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN &&
!ndpi_is_subprotocol_informative(iface->get_ndpi_struct(), ndpiDetectedProtocol.proto.master_protocol))
stats_protocol = ndpiDetectedProtocol.proto.app_protocol;
else
stats_protocol = ndpiDetectedProtocol.proto.master_protocol;
return (stats_protocol);
}
/* *************************************** */
/* This function is called as soon as the protocol detection is
* completed. See processExtraDissectedInformation for a later check.
* NOTE: does NOT need ndpiFlow
*/
void Flow::processDetectedProtocol(u_int8_t *payload, u_int16_t payload_len) {
u_int16_t l7proto;
u_int16_t stats_protocol;
Host *cli_h = NULL, *srv_h = NULL;
char *domain_name = NULL;
/*
If peers should be swapped, then pointers are inverted.
NOTE: only function pointers are inverted, not pointers in the flow.
*/
get_actual_peers(&cli_h, &srv_h);
stats_protocol = getStatsProtocol();
/* Update the active flows stats */
if(cli_h) cli_h->incnDPIFlows(stats_protocol);
if(srv_h) srv_h->incnDPIFlows(stats_protocol);
iface->incnDPIFlows(stats_protocol);
l7proto = ndpi_get_lower_proto(ndpiDetectedProtocol);
/* Domain Concats Alert */
if(ndpiFlow)
domain_name = ndpi_get_flow_name(ndpiFlow),
confidence = ndpiFlow->confidence;
if(cli_h && domain_name && domain_name[0] != '\0')
cli_h->addContactedDomainName(domain_name);
/* NOTE: UDP flows are updated when the flow ends */
if(tcp != NULL)
updateTCPHostServices(cli_h, srv_h);
else if(isUDP()) {
/* Check direction first */
if(payload && (get_packets() == 1)) {
switch (l7proto) {
case NDPI_PROTOCOL_NTP:
if(payload_len > 1) {
u_int8_t mode = payload[0] & 0x07;
if(mode == 4 /* server -> client */) {
request_swap(); /* This flow will be swapped */
swap();
}
}
break;
case NDPI_PROTOCOL_DNS:
if(payload_len > 4) {
if((payload[3] & 0x80) == 0x80) {
/* server -> client */
request_swap(); /* This flow will be swapped */
swap();
}
}
break;
}
}
} else if(isICMP()) {
if(payload_len > 0) {
u_int8_t icmp_type = payload[0];
if(icmp_type == 0 /* ICMP Echo Reply */) {
/* server -> client */
request_swap(); /* This flow will be swapped */
swap();
}
}
}
if(ndpiFlow && (ndpiFlow->tcp.os_hint != ndpi_os_unknown)) {
Host *h = cli_h ? cli_h : getViewSharedClient() /* View interface */;
if(h != NULL)
h->setnDPIOS(ndpiFlow->tcp.os_hint);
}
}
/* *************************************** */
/* This function is called as soon as the protocol detection is
* completed to process nDPI-dissected data (only for packet interfaces).
* NOTE: needs ndpiFlow
*/
void Flow::processDetectedProtocolData() {
u_int16_t l7proto;
Host *cli_h = NULL, *srv_h = NULL;
/*
Make sure to actual client and server to avoid setting wrong names (e.g.,
set the server name to the client)
https://github.com/ntop/ntopng/issues/5506
*/
get_actual_peers(&cli_h, &srv_h);
if(ndpiFlow == NULL) return;
l7proto = ndpi_get_lower_proto(ndpiDetectedProtocol);
if((l7proto != NDPI_PROTOCOL_DNS)
&& (l7proto != NDPI_PROTOCOL_DHCP) /* host_server_name in DHCP is for the
client name, not the server */
&& (ndpiFlow->host_server_name[0] != '\0') && (!host_server_name)) {
bool skip_host_server_name = false;
Utils::sanitizeHostName((char *)ndpiFlow->host_server_name);
if(ndpi_is_proto(ndpiDetectedProtocol.proto, NDPI_PROTOCOL_HTTP)) {
if(ndpiFlow->http.response_status_code == 200) {
char *double_column = strrchr((char *)ndpiFlow->host_server_name, ':');
if(double_column) double_column[0] = '\0';
} else
skip_host_server_name = true;
}
if(!skip_host_server_name) {
/* Host server name equals the Host: HTTP header field. */
setServerName(strdup((char *)ndpiFlow->host_server_name));
}
}
switch (l7proto) {
case NDPI_PROTOCOL_BITTORRENT:
if(!bt_hash) setBittorrentHash((char *)ndpiFlow->protos.bittorrent.hash,
sizeof(ndpiFlow->protos.bittorrent.hash));
break;
case NDPI_PROTOCOL_MDNS:
/* protos.mdns.{answer,name} already propagated to hosts in
* Flow::hosts_periodic_stats_update */
break;
case NDPI_PROTOCOL_TOR:
case NDPI_PROTOCOL_TLS:
case NDPI_PROTOCOL_QUIC:
if(ndpiFlow->host_server_name[0] != '\0') {
if((ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_DOH_DOT)
&& cli_h
&& cli_h->isLocalHost())
cli_h->incrVisitedWebSite(ndpiFlow->host_server_name);
if(cli_h) cli_h->incContactedService(ndpiFlow->host_server_name);
#if 0
/*
Commented out as it will be eventually set below by (***)
This should prevent misclassifying host names
*/
if(srv_h) srv_h->setServerName(host_server_name);
#endif
}
break;
case NDPI_PROTOCOL_HTTP:
case NDPI_PROTOCOL_HTTP_PROXY:
if(ndpiFlow->http.url) {
if(!protos.http.last_url)
protos.http.last_url = strdup(ndpiFlow->http.url);
if((!protos.http.last_user_agent) && ndpiFlow->http.user_agent)
protos.http.last_user_agent = strdup(ndpiFlow->http.user_agent);
setHTTPMethod(ndpiFlow->http.method);
}
break;
} /* switch */
}
/* *************************************** */
/* This is called only once per Flow, when all the protocol information,
* including extra dissection information (e.g. the TLS certificate), is
* available. */
void Flow::processExtraDissectedInformation() {
if(ndpiFlow) {
if(isSSH()) {
if(protos.ssh.client_signature == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.ssh.client_signature))
protos.ssh.client_signature =
strdup(ndpiFlow->protos.ssh.client_signature);
if(protos.ssh.server_signature == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.ssh.server_signature))
protos.ssh.server_signature =
strdup(ndpiFlow->protos.ssh.server_signature);
if(protos.ssh.hassh.client_hash == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.ssh.hassh_client)) {
protos.ssh.hassh.client_hash =
strdup(ndpiFlow->protos.ssh.hassh_client);
updateHASSH(true /* As client */);
}
if(protos.ssh.hassh.server_hash == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.ssh.hassh_server)) {
protos.ssh.hassh.server_hash =
strdup(ndpiFlow->protos.ssh.hassh_server);
updateHASSH(false /* As server */);
}
} else if(isTLS()) {
protos.tls.tls_version = ndpiFlow->protos.tls_quic.ssl_version;
protos.tls.notBefore = ndpiFlow->protos.tls_quic.notBefore,
protos.tls.notAfter = ndpiFlow->protos.tls_quic.notAfter;
if(protos.tls.client_requested_server_name == NULL &&
!Utils::isEmptyString(ndpiFlow->host_server_name)) {
protos.tls.client_requested_server_name = strdup(ndpiFlow->host_server_name);
/* Now some minor cleanup */
char *c;
if((c = strchr(protos.tls.client_requested_server_name, ',')) != NULL)
c[0] = '\0';
else if((c = strchr(protos.tls.client_requested_server_name, ' ')) !=
NULL)
c[0] = '\0';
}
if(protos.tls.server_names == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.tls_quic.server_names))
protos.tls.server_names = strdup(ndpiFlow->protos.tls_quic.server_names);
if(protos.tls.client_alpn == NULL) {
if(!Utils::isEmptyString(ndpiFlow->protos.tls_quic.negotiated_alpn))
protos.tls.client_alpn = strdup(ndpiFlow->protos.tls_quic.negotiated_alpn);
else if(!Utils::isEmptyString(ndpiFlow->protos.tls_quic.advertised_alpns))
protos.tls.client_alpn = strdup(ndpiFlow->protos.tls_quic.advertised_alpns);
}
if(protos.tls.client_tls_supported_versions == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.tls_quic.tls_supported_versions))
protos.tls.client_tls_supported_versions = strdup(ndpiFlow->protos.tls_quic.tls_supported_versions);
if(protos.tls.issuerDN == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.tls_quic.issuerDN))
protos.tls.issuerDN = strdup(ndpiFlow->protos.tls_quic.issuerDN);
if(protos.tls.subjectDN == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.tls_quic.subjectDN))
protos.tls.subjectDN = strdup(ndpiFlow->protos.tls_quic.subjectDN);
if(protos.tls.ja4.client_hash == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.tls_quic.ja4_client)) {
protos.tls.ja4.client_hash = strdup(ndpiFlow->protos.tls_quic.ja4_client);
updateCliJA4();
}
} else if(isDNS()) {
if(srv_host && (ndpiFlow->protos.dns.reply_code == 0 /* No Error */)) {
/* Now we need to check if the requested IP matches the server host */
if((ndpiFlow->protos.dns.rsp_type == 0x0C /* PTR */) &&
(ndpiFlow->protos.dns.ptr_domain_name[0] != '\0')) {
u_int len = strlen((char *)ndpiFlow->host_server_name);
IpAddress *addr = srv_host->get_ip();
if(len > 13) {
if(addr->isIPv4() &&
(strcmp(&ndpiFlow->host_server_name[len - 13],
".in-addr.arpa") == 0)) {
/* 130.197.62.178.in-addr.arpa */
int a, b, c, d;
if(sscanf(ndpiFlow->host_server_name, "%d.%d.%d.%d", &d, &c,
&b, &a) == 4) {
char buf[32];
u_int32_t ipv4_addr;
snprintf(buf, sizeof(buf), "%u.%u.%u.%u", a, b, c, d);
ipv4_addr = ntohl(inet_addr(buf));
if(addr->equal(ipv4_addr))
srv_host->setResolvedName((char *)ndpiFlow->protos.dns.ptr_domain_name);
else {
/* This is not the right IPv4 host: let's cache it for later */
ntop->getRedis()->setResolvedAddress(buf, (char *)ndpiFlow->protos.dns.ptr_domain_name);
}
}
} else if(strcmp(&ndpiFlow->host_server_name[len - 9],
".ip6.arpa") == 0) {
/* 1.0.0.4.0.6.3.0.0.0.0.0.0.0.0.0.0.d.0.0.2.0.0.0.0.c.0.b.3.0.a.2.ip6.arpa
*/
int a, b;
int i = 15;
char *tmp,
*item = strtok_r(ndpiFlow->host_server_name, ".", &tmp);
struct ndpi_in6_addr ipv6_addr;
while (item != NULL) {
a = strtol(item, NULL, 16);
item = strtok_r(NULL, ".", &tmp);
if(item) {
b = strtol(item, NULL, 16);
ipv6_addr.u6_addr.u6_addr8[i] = (b << 4) + a;
if(--i < 0)
break;
else
item = strtok_r(NULL, ".", &tmp);
}
} /* while */
if(addr->equal(&ipv6_addr))
srv_host->setResolvedName((char *)ndpiFlow->protos.dns.ptr_domain_name);
else {
char buf[64];
/* This is not the right IPv6 host: let's cache it for later
*/
ntop->getRedis()->setResolvedAddress(Utils::intoaV6(ipv6_addr, 128, buf, sizeof(buf)),
(char *)ndpiFlow->protos.dns.ptr_domain_name);
}
}
}
}
}
} else if(isMining()) {
if(protos.mining.currency == NULL &&
!Utils::isEmptyString(ndpiFlow->protos.mining.currency))
protos.mining.currency = strdup(ndpiFlow->protos.mining.currency);
/* ntop->getTrace()->traceEvent(TRACE_NORMAL, "-->>> %s", ndpiFlow->protos.mining.currency); */
} else if(isHTTP() || isHTTP_PROXY()) {
if(protos.http.last_server == NULL &&
!Utils::isEmptyString(ndpiFlow->http.server))
protos.http.last_server = strdup(ndpiFlow->http.server);
if(ndpiFlow->http.response_status_code == 200) {
if(srv_host
&& (ndpiFlow->host_server_name[0] != '\0')
&& (ndpiFlow->http.nat_ip == NULL) /* THis is not a proxy */
)
srv_host->setServerName(host_server_name);
if(ndpiFlow->host_server_name[0] != '\0') {
char *doublecol, delimiter = ':';
/* If <host>:<port> we need to remove ':' */
if((doublecol = (char *)strchr((const char *)ndpiFlow->host_server_name, delimiter)) != NULL)
doublecol[0] = '\0';
if(cli_host) {
cli_host->incContactedService((char *)ndpiFlow->host_server_name);
if(ndpiFlow->http.detected_os)
cli_host->inlineSetOSDetail((char *)ndpiFlow->http.detected_os); /* Learnt from User-Agent */
if(cli_host->isLocalHost())
cli_host->incrVisitedWebSite((char *)ndpiFlow->host_server_name);
}
}
}
} else if(isSTUN()) {
if(stun_mapped_address == NULL &&
ndpiFlow->stun.mapped_address.port != 0) {
IpAddress ip;
char tmp[96], ipb[64];
if(ndpiFlow->stun.mapped_address.is_ipv6)
ip.set((struct in6_addr*)&ndpiFlow->stun.mapped_address.address);
else
ip.set(ndpiFlow->stun.mapped_address.address.v4);
snprintf(tmp, sizeof(tmp),"%s:%u",
ip.print(ipb, sizeof(ipb)), ndpiFlow->stun.mapped_address.port);
stun_mapped_address = strdup(tmp);
}
}
updateSuspiciousDGADomain();
// updateHostBlacklists();
}
#if defined(NTOPNG_PRO)
getInterface()->updateFlowPeriodicity(this); /* <- here we eventually set is_periodic_flow */
if(is_periodic_flow) {
ndpi_risk_enum risk = NDPI_PERIODIC_FLOW;
ndpi_risk r_bitmap = ((ndpi_risk)2) << (risk - 1);
if(get_ndpi_flow() != NULL) {
char msg[32];
snprintf(msg, sizeof(msg), "%u sec", periodicity);
ndpi_set_risk(iface->get_ndpi_struct(), get_ndpi_flow(), NDPI_PERIODIC_FLOW, msg);
}
setRisk(r_bitmap);
}
getInterface()->updateServiceMap(this);
#endif
if(get_ndpi_flow()) {
/* Save riskInfo */
char *out, buf[512];
struct ndpi_flow_struct *f = get_ndpi_flow();
out = ndpi_get_flow_risk_info(f, buf, sizeof(buf), 1 /* JSON */);
if(out != NULL) setJSONRiskInfo(out);
flow_payload = f->flow_payload, flow_payload_len = f->flow_payload_len;
ndpiFlow->flow_payload = NULL; /* ntopng will free this memory not nDPI */
}
/* Read this data before freeing nDPI */
if(get_ndpi_flow() != NULL) {
u_int8_t flow_types = get_ndpi_flow()->flow_multimedia_types;
if(flow_types != ndpi_multimedia_unknown_flow) {
if(flow_types & ndpi_multimedia_audio_flow)
setRTPStreamType(ndpi_multimedia_audio_flow);
else if(flow_types & ndpi_multimedia_video_flow)
setRTPStreamType(ndpi_multimedia_video_flow);
else if(flow_types & ndpi_multimedia_screen_sharing_flow)
setRTPStreamType(ndpi_multimedia_screen_sharing_flow);
}
}
/*
We need to change state here as in Lua scripts we need to know
all metadata available
*/
set_hash_entry_state_flow_protocoldetected();
}
/* *************************************** */
bool Flow::needsExtraDissection() {
ndpi_flow_struct *ndpif;
/* NOTE: do not check hasDissectedTooManyPackets() here, otherwise
* ndpi_detection_giveup won't be called. */
return ((ndpif = get_ndpi_flow())
&& (!extra_dissection_completed)
&& (ndpi_extra_dissection_possible(iface->get_ndpi_struct(), ndpif)));
}
/* *************************************** */
/* Process a packet and advance the flow detection state. */
void Flow::processPacket(bool src2dst_direction,
const struct pcap_pkthdr *h, const u_char *ip_packet,
u_int16_t ip_len, u_int64_t packet_time,
u_int8_t *payload, u_int16_t payload_len,
u_int16_t src_port) {
bool detected;
ndpi_protocol proto_id;
/* Note: do not call endProtocolDissection before
* ndpi_detection_process_packet. In case of early giveup (e.g. sampled
* traffic), nDPI should process at least one packet in order to be able to
* guess the protocol. */
proto_id = ndpi_detection_process_packet(iface->get_ndpi_struct(),
ndpiFlow, ip_packet, ip_len, packet_time, NULL);
if((ndpi_flow_risk_bitmap != 0) && (ndpiFlow->risk == 0)) {
/*
Probably an exception has cleared the risk that was previously
stored in ntopng so we need to rollback also the risk in ntopng
*/
ndpi_flow_risk_bitmap = 0;
}
detected = ndpi_is_protocol_detected(proto_id);
if(!detected && hasDissectedTooManyPackets()) {
/*
Perform a giveup and finalize all additional operations such as
the processing of extra dissection data.
*/
endProtocolDissection(src2dst_direction);
}
#ifdef NTOPNG_PRO
// Update the profile even if the detection is not yet completed.
// Indeed, even if the L7 detection is not yet completed
// the flow already carries information on all the other fields,
// e.g., IP src and DST, vlan, L4 proto, etc
#ifndef HAVE_NEDGE
updateProfile();
#endif
#endif
if(payload_len > 0) non_zero_payload_observed = 1;
if(detected) {
if(ndpiFlow->risk != 0) {
if(srv_host) {
/* Ignore unsafe protocols for broadcast packets (e.g. SMBv1) */
Mac *srv_mac = srv_host->getMac();
if(srv_mac && srv_mac->isBroadcast()) {
ndpi_risk r = ((ndpi_risk)1) << NDPI_UNSAFE_PROTOCOL;
if((ndpiFlow->risk & r) == r)
ndpiFlow->risk &= ~r; /* Clear the bit */
}
}
if(cli_host) {
if(NDPI_ISSET_BIT(ndpiFlow->risk, NDPI_HTTP_CRAWLER_BOT))
cli_host->setCrawlerBotScannerHost();
}
addRisk(ndpiFlow->risk);
}
updateProtocol(proto_id);
setProtocolDetectionCompleted(payload, payload_len, h->ts.tv_sec);
}
/*
Perform other protocol-specific processing before marking the detection as
completed. For example, for the DNS, additional processing on the query is
performed and later used by checks.
*/
if(isDNS())
processDNSPacket(ip_packet, ip_len, packet_time);
else if(isIEC60870())
processIEC60870Packet((htons(src_port) == 2404) ? true : false, payload,
payload_len, h);
if(detection_completed) {
if(!needsExtraDissection()) {
setExtraDissectionCompleted(src2dst_direction);
updateProtocol(proto_id);
}
}
}
/* *************************************** */
extern "C" {
int current_pkt_from_client_to_server(const struct ndpi_detection_module_struct *ndpi_str, const struct ndpi_flow_struct *flow);
};
/* Special handling of DNS which is always performed. */
void Flow::processDNSPacket(const u_char *ip_packet, u_int16_t ip_len,
u_int64_t packet_time) {
ndpi_protocol proto_id;
/* Exits if the flow isn't DNS or it the interface is not a packet-interface */
if((!isDNS())
|| (!getInterface()->isPacketInterface())
|| (ndpiFlow == NULL))
return;
/* Instruct nDPI to continue the dissection
See
https://github.com/ntop/ntopng/commit/30f52179d9f7a1eb774534def93d55c77d6070bc#diff-20b1df29540b6de59ceb6c6d2f3afdb5R387
*/
ndpiFlow->max_extra_packets_to_check = 10;
proto_id = ndpi_detection_process_packet(iface->get_ndpi_struct(), ndpiFlow, ip_packet, ip_len, packet_time, NULL);
/*
A DNS flow won't change to a non-DNS flow. However, this check is
just in case for safety. What can change is the application protocol, e.g.,
a DNS.Google can become DNS.Facebook.
*/
switch (ndpi_get_lower_proto(proto_id)) {
case NDPI_PROTOCOL_DNS:
ndpiDetectedProtocol = proto_id; /* Override! */
if(ndpiFlow->host_server_name[0] != '\0') {
std::string addresses;
if(cli_host && (ndpiFlow->protos.dns.reply_code == 0 /* no Error */)) {
cli_host->incContactedService((char *)ndpiFlow->host_server_name);
cli_host->incrVisitedWebSite((char *)ndpiFlow->host_server_name);
}
for(u_int i=0; i<ndpiFlow->protos.dns.num_rsp_addr; i++) {
char buf[64];
if(ndpiFlow->protos.dns.is_rsp_addr_ipv6[i] == 0)
inet_ntop(AF_INET, &ndpiFlow->protos.dns.rsp_addr[i].ipv4, buf, sizeof(buf));
else
inet_ntop(AF_INET6, &ndpiFlow->protos.dns.rsp_addr[i].ipv6, buf, sizeof(buf));
if(i > 0) addresses += ",";
addresses += buf;
}
if(ndpiFlow->protos.dns.query_type != 0)
protos.dns.last_query_type = ndpiFlow->protos.dns.query_type;
protos.dns.last_return_code = ndpiFlow->protos.dns.reply_code;
if(!ndpiFlow->protos.dns.is_query) {
/* this is a response... */
if(ntop->getPrefs()->is_dns_decoding_enabled()) {
char delimiter = '@', *name = NULL;
char *at = (char *)strchr((const char *)ndpiFlow->host_server_name, delimiter);
/* Consider only positive DNS replies */
if(at != NULL)
name = &at[1], at[0] = '\0';
else if((!strstr((const char *)ndpiFlow->host_server_name, ".in-addr.arpa")) &&
(!strstr((const char *)ndpiFlow->host_server_name, ".ip6.arpa")))
name = (char *)ndpiFlow->host_server_name;
if(name) {
#if 0
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[DNS] %s [query_type: %u][reply_code: %u][is_query: %u][num_queries: %u][num_answers: %u]",
(char*)ndpiFlow->host_server_name,
ndpiFlow->protos.dns.query_type,
ndpiFlow->protos.dns.reply_code,
ndpiFlow->protos.dns.is_query ? 1 : 0,
ndpiFlow->protos.dns.num_queries,
ndpiFlow->protos.dns.num_answers);
#endif
if(ndpiFlow->protos.dns.reply_code == 0) {
if(ndpiFlow->protos.dns.num_answers > 0) {
if(at != NULL) {
// ntop->getTrace()->traceEvent(TRACE_NORMAL, "[_NS] %s <-> %s", name, (char*)ndpiFlow->host_server_name);
ntop->getRedis()->setResolvedAddress(name, (char *)ndpiFlow->host_server_name);
}
}
}
}
}
if(protos.dns.last_return_code == 0 /* NOERROR */)
setDNSQuery(ndpiFlow->host_server_name, (char*)addresses.c_str(), true);
}
}
#ifdef HAVE_NEDGE
updateFlowShapers(false);
#endif
break;
default:
break;
}
#if 0
char buf[256];
ntop->getTrace()->traceEvent(TRACE_ERROR, "%s %s",
ndpiFlow->host_server_name[0] != '\0' ? ndpiFlow->host_server_name : (unsigned char*)"",
print(buf, sizeof(buf)));
#endif
}
/* *************************************** */
/* Special handling of IEC60870 which is always performed. */
void Flow::processIEC60870Packet(bool tx_direction, const u_char *payload,
u_int16_t payload_len,
const struct pcap_pkthdr *h) {
/* Exits if the flow isn't IEC60870 or it the interface is not a
* packet-interface */
if(!isIEC60870() || (!getInterface()->isPacketInterface()) ||
(payload_len < 6))
return;
if(!iec104) iec104 = new (std::nothrow) IEC104Stats();
if(iec104)
iec104->processPacket(this, tx_direction, payload, payload_len, h);
}
/* *************************************** */
/* End the nDPI dissection on a flow. Guess the protocol if not already
* detected. It is safe to call endProtocolDissection() multiple times. */
void Flow::endProtocolDissection(bool src2dst_direction) {
if(!detection_completed) {
u_int8_t proto_guessed;
updateProtocol(ndpi_detection_giveup(iface->get_ndpi_struct(), ndpiFlow,
&proto_guessed));
setRisk(ndpi_flow_risk_bitmap | ndpiFlow->risk);
setProtocolDetectionCompleted(NULL, 0, iface->getTimeLastPktRcvd());
}
if(!extra_dissection_completed) setExtraDissectionCompleted(src2dst_direction);
}
/* *************************************** */
/* Manually set a protocol on the flow and terminate the dissection. */
void Flow::setDetectedProtocol(ndpi_protocol proto_id, bool src2dst_direction) {
updateProtocol(proto_id);
setProtocolDetectionCompleted(NULL, 0, iface->getTimeLastPktRcvd());
endProtocolDissection(src2dst_direction);
}
/* *************************************** */
/* Called when the extra dissection on the flow is completed. */
void Flow::setExtraDissectionCompleted(bool src2dst_direction) {
if(extra_dissection_completed)
return;
if(!detection_completed) {
ntop->getTrace()->traceEvent(TRACE_ERROR,
"Bad internal status: setExtraDissectionCompleted called before "
"setDetectedProtocol");
return;
} else if(needsExtraDissection()) {
u_int8_t proto_guessed;
updateProtocol(ndpi_detection_giveup(iface->get_ndpi_struct(), ndpiFlow,
&proto_guessed));
setRisk(ndpi_flow_risk_bitmap | ndpiFlow->risk);
}
if(ndpiFlow) {
if((protocol == IPPROTO_TCP) || (protocol == IPPROTO_UDP) ||
(protocol == IPPROTO_ICMP) || (protocol == IPPROTO_ICMPV6)) {
/*
nDPI is not allocated for non-TCP non-UDP flows so, in order to
make sure custom cateories are properly populated, function
ndpi_fill_ip_protocol_category must be called explicitly.
*/
u_int16_t p, *p_ptr;
if(get_cli_ip_addr()->get_ipv4() /* && get_srv_ip_addr()->get_ipv4() */)
ndpi_fill_ip_protocol_category(iface->get_ndpi_struct(),
ndpiFlow,
get_cli_ip_addr()->get_ipv4(),
get_srv_ip_addr()->get_ipv4(),
&ndpiDetectedProtocol);
else if(get_cli_ip_addr()->get_ipv6()) /* IPv6 */
ndpi_fill_ipv6_protocol_category(iface->get_ndpi_struct(),
ndpiFlow,
(struct in6_addr *)get_cli_ip_addr()->get_ipv6(),
(struct in6_addr *)get_srv_ip_addr()->get_ipv6(),
&ndpiDetectedProtocol);
p_ptr = (u_int16_t*)&ndpiDetectedProtocol.category;
p = (*p_ptr) & 0xFF; /* See Ntop::nDPILoadHostnameCategory */
ndpiDetectedProtocol.category = (ndpi_protocol_category_t)p;
/* We have used the trick to save in the protocolId both the list name and the protocol */
if(ndpiDetectedProtocol.custom_category_userdata == NULL) {
u_int8_t list_id = (ndpiDetectedProtocol.category & 0xFF00) >> 8; /* See Ntop::nDPILoadHostnameCategory */
ndpiDetectedProtocol.category = (ndpi_protocol_category_t)(ndpiDetectedProtocol.category & 0xFF);
ndpiDetectedProtocol.custom_category_userdata = (void*)ntop->getPersistentCustomListNameById(list_id);
}
stats.setDetectedProtocol(&ndpiDetectedProtocol);
updateHostBlacklists();
}
setErrorCode(ndpi_get_flow_error_code(ndpiFlow));
if(ndpiDetectedProtocol.protocol_by_ip != NDPI_PROTOCOL_UNKNOWN) {
char *p = ndpi_get_proto_name(iface->get_ndpi_struct(),
ndpiDetectedProtocol.protocol_by_ip);
setAddressFamilyProtocol(p);
}
}
/*
NOTE
TCP host services are updated in Flow::processDetectedProtocol
UDP sevices are updated here as we need to know if the flow is
unidirectional and this can be sorted out only after a few packets
as doing it during the flow processing it is too early as nDPI
returns immediately the protocol name (e.g. with DNS) without
waiting the response to be received
*/
if(protocol == IPPROTO_UDP)
updateUDPHostServices(src2dst_direction);
processExtraDissectedInformation();
extra_dissection_completed = 1;
/* 2(**) */
#ifdef NTOPNG_PRO
if(!shapers_profile_set) {
#ifdef HAVE_NEDGE
updateFlowShapers(true);
#else
updateProfile();
#endif
shapers_profile_set = 1;
}
#endif
/* Free the nDPI memory */
freeDPIMemory();
}
/* *************************************** */
void Flow::updateHostBlacklists() {
if(get_custom_category_file()) {
char *cat = get_custom_category_file();
if(isBlacklistedClient()) {
if(cli_host)
cli_host->setBlacklistName(cat); /* Standard Interface */
else if(cli_ip_addr != NULL) {
/*
View interface
Will be updated by ViewInterface::viewed_flows_walker
*/
}
} else if(isBlacklistedServer()) {
if(srv_host)
srv_host->setBlacklistName(cat); /* Standard Interface */
else if(srv_ip_addr != NULL) {
/*
View interface
Will be updated by ViewInterface::viewed_flows_walker
*/
}
}
}
}
/* *************************************** */
void Flow::updateProtocol(ndpi_protocol proto_id) {
u_int16_t *ptr16;
/* NOTE: in order to avoid inconsistent states, only overwrite the
* protocools if UNKNOWN. */
if(ndpiDetectedProtocol.proto.master_protocol == NDPI_PROTOCOL_UNKNOWN)
ndpiDetectedProtocol.proto.master_protocol = proto_id.proto.master_protocol;
if((ndpiDetectedProtocol.proto.app_protocol == NDPI_PROTOCOL_UNKNOWN) ||
(/*
Update the protocols when adding a subprotocol, not when things
are totally different
*/
(ndpiDetectedProtocol.proto.master_protocol ==
ndpiDetectedProtocol.proto.app_protocol) &&
(ndpiDetectedProtocol.proto.app_protocol != proto_id.proto.app_protocol)))
ndpiDetectedProtocol.proto.app_protocol = proto_id.proto.app_protocol;
if(ndpiDetectedProtocol.proto.master_protocol == ndpiDetectedProtocol.proto.app_protocol)
ndpiDetectedProtocol.proto.master_protocol = NDPI_PROTOCOL_UNKNOWN;
ndpiDetectedProtocol.protocol_by_ip = proto_id.protocol_by_ip;
/* NOTE: only overwrite the category if it was not set.
* This prevents overwriting already determined category (e.g. by IP or Host)
*/
if (ndpiDetectedProtocol.category == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED) {
u_int16_t *a = (u_int16_t*)&ndpiDetectedProtocol.category;
u_int16_t *b = (u_int16_t*)&proto_id.category;
*a = *b; /* trick to avoid runtime errors with custom categories */
}
/* trick to avoid runtime errors with custom categories */
ptr16 = (u_int16_t*)&ndpiDetectedProtocol.category;
if ((*ptr16 > 1024) && ((*ptr16 - 1024) == NDPI_PROTOCOL_CATEGORY_MALWARE))
ndpiDetectedProtocol.category = NDPI_PROTOCOL_CATEGORY_MALWARE;
}
/* *************************************** */
/*
* Called to update the flow protocol and possibly advance the flow to
* the protocol_detected state.
*/
void Flow::setProtocolDetectionCompleted(u_int8_t *payload,
u_int16_t payload_len, time_t when_seen) {
if(detection_completed) return;
stats.setDetectedProtocol(&ndpiDetectedProtocol);
/* Process detected protocol and doesn't need ndpiFlow not allocated for
* non-packet interfaces */
processDetectedProtocol(payload, payload_len);
/* Process detected protocol data and needs ndpiFlow only allocated for packet interfaces */
processDetectedProtocolData();
detection_completed = 1;
if(cli_host && srv_host) {
/*
Hosts are NULL for view interfaces and thus they are updated
in Flow::hosts_periodic_stats_update()
*/
updateServerPortsStats(srv_host, &ndpiDetectedProtocol, when_seen);
updateClientContactedPorts(cli_host, &ndpiDetectedProtocol);
}
#ifdef BLACKLISTED_FLOWS_DEBUG
if(ndpiDetectedProtocol.category == NDPI_PROTOCOL_CATEGORY_MALWARE) {
char buf[512];
print(buf, sizeof(buf));
snprintf(&buf[strlen(buf)], sizeof(buf) - strlen(buf),
"Malware category detected. [cli_blacklisted: "
"%u][srv_blacklisted: %u][category: %s][blacklist: %s]",
isBlacklistedClient(), isBlacklistedServer(),
get_protocol_category_name(),
get_custom_category_file() ? get_custom_category_file() : "");
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s", buf);
}
#endif
if(host_server_name != NULL) {
/*
In case the host server name is NOT yet known (e.g. it can
happen with long QUIC Client Hello), we cannot yet set the
shapers/profile and need to wait for 2(**)
*/
#ifdef NTOPNG_PRO
#ifdef HAVE_NEDGE
updateFlowShapers(true);
#else
updateProfile();
#endif
shapers_profile_set = 1;
#endif
}
}
/* *************************************** */
void Flow::setJSONInfo(json_object *json) {
if(json == NULL) return;
if(json_info != NULL) json_object_put(json_info);
json_info = json_object_get(json);
}
/* *************************************** */
void Flow::setTLVInfo(ndpi_serializer *tlv) {
if(tlv == NULL) return;
if(tlv_info != NULL) {
ndpi_term_serializer(tlv_info);
free(tlv_info);
}
tlv_info = tlv;
}
/* *************************************** */
/*
* A faster replacement for inet_ntoa().
*/
char *Flow::intoaV4(unsigned int addr, char *buf, u_short bufLen) {
char *cp, *retStr;
int n;
cp = &buf[bufLen];
*--cp = '\0';
n = 4;
do {
u_int byte = addr & 0xff;
*--cp = byte % 10 + '0';
byte /= 10;
if(byte > 0) {
*--cp = byte % 10 + '0';
byte /= 10;
if(byte > 0) *--cp = byte + '0';
}
*--cp = '.';
addr >>= 8;
} while (--n > 0);
/* Convert the string to srccase */
retStr = (char *)(cp + 1);
return (retStr);
}
/* *************************************** */
u_int64_t Flow::get_current_bytes_cli2srv() const {
int64_t diff = get_bytes_cli2srv() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_cli2srv_bytes() : 0);
/*
We need to do this as due to concurrency issues,
we might have a negative value
*/
return ((diff > 0) ? diff : 0);
};
/* *************************************** */
u_int64_t Flow::get_current_bytes_srv2cli() const {
int64_t diff = get_bytes_srv2cli() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_srv2cli_bytes() : 0);
/*
We need to do this as due to concurrency issues,
we might have a negative value
*/
return ((diff > 0) ? diff : 0);
};
/* *************************************** */
u_int64_t Flow::get_current_goodput_bytes_cli2srv() const {
int64_t diff =
get_goodput_bytes_cli2srv() -
(periodic_stats_update_partial
? periodic_stats_update_partial->get_cli2srv_goodput_bytes()
: 0);
/*
We need to do this as due to concurrency issues,
we might have a negative value
*/
return ((diff > 0) ? diff : 0);
};
/* *************************************** */
u_int64_t Flow::get_current_goodput_bytes_srv2cli() const {
int64_t diff =
get_goodput_bytes_srv2cli() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_srv2cli_goodput_bytes() : 0);
/*
We need to do this as due to concurrency issues,
we might have a negative value
*/
return ((diff > 0) ? diff : 0);
};
/* *************************************** */
u_int64_t Flow::get_current_packets_cli2srv() const {
int64_t diff = get_packets_cli2srv() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_cli2srv_packets() : 0);
/*
We need to do this as due to concurrency issues,
we might have a negative value
*/
return ((diff > 0) ? diff : 0);
};
/* *************************************** */
u_int64_t Flow::get_current_packets_srv2cli() const {
int64_t diff = get_packets_srv2cli() - (periodic_stats_update_partial ? periodic_stats_update_partial->get_srv2cli_packets() : 0);
/*
We need to do this as due to concurrency issues,
we might have a negative value
*/
return ((diff > 0) ? diff : 0);
};
/* ****************************************************** */
char *Flow::printTCPflags(u_int8_t flags, char *const buf, u_int buf_len) {
snprintf(buf, buf_len, "%s%s%s%s%s%s%s%s", (flags & TH_SYN) ? " SYN" : "",
(flags & TH_ACK) ? " ACK" : "", (flags & TH_FIN) ? " FIN" : "",
(flags & TH_RST) ? " RST" : "", (flags & TH_PUSH) ? " PUSH" : "",
(flags & TH_URG) ? " URG" : "", (flags & TH_ECE) ? " ECE" : "",
(flags & TH_CWR) ? " CWR" : "");
if(buf[0] == ' ')
return (&buf[1]);
else
return (buf);
}
/* ****************************************************** */
char *Flow::printTCPState(char *const buf, u_int buf_len) const {
snprintf(buf, buf_len, "%s%s%s%s", isTCPEstablished() ? " est" : "",
isTCPConnecting() ? " conn" : "", isTCPClosed() ? " closed" : "",
isTCPReset() ? " reset" : "");
if(buf[0] == ' ')
return (&buf[1]);
else
return (buf);
}
/* *************************************** */
char* Flow::print(char *buf, u_int buf_len, bool full_report) const {
char buf1[32], buf2[32], buf3[32], buf4[32], buf5[32], pbuf[32], tcp_buf[64];
buf[0] = '\0';
if(!full_report) {
Mac *cli_mac = get_cli_host() ? get_cli_host()->getMac() : NULL;
Mac *srv_mac = get_srv_host() ? get_srv_host()->getMac() : NULL;
char b1[32], b2[32];
snprintf(buf, buf_len, "%s %s:%u [%s] <-> %s:%u [%s][%u/%u pkts][%llu/%llu bytes]",
get_protocol_name(),
get_cli_ip_addr() ? get_cli_ip_addr()->print(buf1, sizeof(buf1)) : "",
ntohs(cli_port),
cli_mac ? cli_mac->print(b1, sizeof(b1)) : "",
get_srv_ip_addr() ? get_srv_ip_addr()->print(buf2, sizeof(buf2)) : "",
ntohs(srv_port),
srv_mac ? srv_mac->print(b2, sizeof(b2)) : "",
get_packets_cli2srv(), get_packets_srv2cli(),
(long long unsigned)get_bytes_cli2srv(),
(long long unsigned)get_bytes_srv2cli());
return(buf);
}
#if defined(NTOPNG_PRO) && defined(SHAPER_DEBUG)
char shapers[64];
if(iface->is_bridge_interface()) {
snprintf(shapers, sizeof(shapers),
"[pass_verdict: %s (%d)] "
"[shapers: cli=%u, srv=%u] "
"[cli shaping_enabled: %i max_rate: %lu] "
"[srv shaping_enabled: %i max_rate: %lu] "
passVerdict ? "PASS" : "DROP",
(int) dropVerdictReason,
flowShapers.cli ? flowShapers.cli->get_shaper_id() : DEFAULT_SHAPER_ID,
flowShapers.srv ? flowShapers.srv->get_shaper_id() : DEFAULT_SHAPER_ID,
flowShapers.cli->shaping_enabled(), flowShapers.cli->get_max_rate_kbit_sec(),
flowShapers.srv->shaping_enabled(), flowShapers.srv->get_max_rate_kbit_sec());
} else
shapers[0] = '\0';
#endif
tcp_buf[0] = '\0';
if(tcp != NULL) {
int len = 0;
if((stats.get_cli2srv_tcp_ooo() + stats.get_srv2cli_tcp_ooo()) > 0)
len += snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[OOO=%u/%u]",
stats.get_cli2srv_tcp_ooo(), stats.get_srv2cli_tcp_ooo());
if((stats.get_cli2srv_tcp_lost() + stats.get_srv2cli_tcp_lost()) > 0)
len +=
snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[Lost=%u/%u]",
stats.get_cli2srv_tcp_lost(), stats.get_srv2cli_tcp_lost());
if((stats.get_cli2srv_tcp_retr() + stats.get_srv2cli_tcp_retr()) > 0)
len +=
snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[Retr=%u/%u]",
stats.get_cli2srv_tcp_retr(), stats.get_srv2cli_tcp_retr());
if((stats.get_cli2srv_tcp_keepalive() +
stats.get_srv2cli_tcp_keepalive()) > 0)
len += snprintf(&tcp_buf[len], sizeof(tcp_buf) - len, "[KeepAlive=%u/%u]",
stats.get_cli2srv_tcp_keepalive(),
stats.get_srv2cli_tcp_keepalive());
}
snprintf(
buf, buf_len,
"%s %s:%u &gt; %s:%u [first: %u][last: %u][proto: %u.%u/%s][cat: "
"%u/%s][device: %u in: %u out:%u]"
"[%u/%u pkts][%llu/%llu bytes][flags src2dst: %s][flags dst2stc: "
"%s][state: %s]"
"%s%s%s"
#if defined(NTOPNG_PRO) && defined(SHAPER_DEBUG)
"%s"
#endif
,
get_protocol_name(),
get_cli_ip_addr() ? get_cli_ip_addr()->print(buf1, sizeof(buf1)) : "",
ntohs(cli_port),
get_srv_ip_addr() ? get_srv_ip_addr()->print(buf2, sizeof(buf2)) : "",
ntohs(srv_port), (u_int32_t)first_seen, (u_int32_t)last_seen,
ndpiDetectedProtocol.proto.master_protocol, ndpiDetectedProtocol.proto.app_protocol,
get_detected_protocol_name(pbuf, sizeof(pbuf)), get_protocol_category(),
get_protocol_category_name(), flow_device.device_ip, flow_device.in_index,
flow_device.out_index, get_packets_cli2srv(), get_packets_srv2cli(),
(long long unsigned)get_bytes_cli2srv(),
(long long unsigned)get_bytes_srv2cli(),
printTCPflags(src2dst_tcp_flags, buf3, sizeof(buf3)),
printTCPflags(dst2src_tcp_flags, buf4, sizeof(buf4)),
printTCPState(buf5, sizeof(buf5)),
(isTLS() && protos.tls.server_names) ? "[" : "",
(isTLS() && protos.tls.server_names) ? protos.tls.server_names : "",
(isTLS() && protos.tls.server_names) ? "]" : ""
#if defined(NTOPNG_PRO) && defined(SHAPER_DEBUG)
,
shapers
#endif
);
return (buf);
}
/* *************************************** */
bool Flow::dump(time_t t, bool last_dump_before_free) {
bool rc = false;
if(!ntop->getPrefs()->is_tiny_flows_export_enabled() && isTiny()) {
#ifdef TINY_FLOWS_DEBUG
char buf[256];
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"Skipping tiny flow dump [flow key: %u]"
"[packets current/max: %i/%i] "
"[bytes current/max: %i/%i]"
": %s",
key(), get_packets(),
ntop->getPrefs()->get_max_num_packets_per_tiny_flow(), get_bytes(),
ntop->getPrefs()->get_max_num_bytes_per_tiny_flow(),
print(buf, sizeof(buf)));
#endif
return (rc);
}
if(!last_dump_before_free) {
if((getInterface()->getIfType() == interface_type_PCAP_DUMP &&
(!getInterface()->read_from_pcap_dump_done())) ||
!timeToPeriodicDump(t) || last_db_dump.in_progress) {
return (rc); /* Don't call too often periodic flow dump */
}
}
/* Add protocol information in the JSON field (if not already set by alerts) */
setProtocolJSONInfo();
getInterface()->dumpFlow(get_last_seen(), this);
return (true);
}
/* *************************************** */
void Flow::setDropVerdict(DropReason reason) {
#if defined(HAVE_NEDGE)
if(iface->getIfType() == interface_type_NETFILTER) {
if(passVerdict == 1)
((NetfilterInterface *)iface)->setPolicyChanged();
fillDynamicPoolBlacklist();
}
#endif
passVerdict = 0;
dropVerdictReason = reason;
}
/* *************************************** */
/* This function is called when deleting the Flow, so if some counter/value
* needs to be updated on flow_end, it's possible to do it from here
*/
void Flow::flow_end_stats_update() {
struct timeval tv;
/* Force last update */
tv.tv_sec = last_seen, tv.tv_usec = 0;
last_update_time.tv_sec = 0; /* Trick to force periodic_stats_update() below */
periodic_stats_update(&tv);
if(isBidirectional()) {
if(isTCP()) {
accountBidirectionalTCPProtocolServices();
} else if(isUDP()) {
accountBidirectionalUDPProtocolServices();
}
}
#ifdef NTOPNG_PRO
QoEType qoe_type = getQoEType();
Host *cli_u = getViewSharedClient(), *srv_u = getViewSharedServer();
AutonomousSystem *cli_as = cli_u ? cli_u->get_as() : NULL;
AutonomousSystem *srv_as = srv_u ? srv_u->get_as() : NULL;
int32_t cli_network_id = 0, srv_network_id = 0;
if (cli_u) cli_u->incQoEStats(qoe_type);
if (srv_u) srv_u->incQoEStats(qoe_type);
if (cli_as) cli_as->incQoEStats(qoe_type);
if (srv_as) {
/* In case cli and srv ASN are the same do not increase, already increased above */
if (cli_as && (cli_as == srv_as)) { ; }
else { srv_as->incQoEStats(qoe_type); }
}
if (cli_u) {
cli_network_id = cli_u->get_local_network_id();
NetworkStats *cli_network_stats = cli_u->getNetworkStats(cli_network_id);
if (cli_network_stats) cli_network_stats->incQoEStats(qoe_type);
}
if (srv_u) {
/* Same as ASN, increase only if they are different networks */
srv_network_id = srv_u->get_local_network_id();
if (srv_network_id != cli_network_id) {
NetworkStats *srv_network_stats = srv_u->getNetworkStats(srv_network_id);
if (srv_network_stats) srv_network_stats->incQoEStats(qoe_type);
}
}
if (iface) iface->incQoEStats(qoe_type);
#endif
}
/* *************************************** */
/*
* Update cli/srv hosts stats
*
* NOTE: this function is periodically executed both on normal interfaces
* and ViewInterface. On ViewInterface, the cli_host and srv_host *do not*
* correspond to the flow hosts (which remain NULL). This is the correct
* place to increment stats on cli/srv hosts and make them work with
* ViewInterfaces.
*
* const is *required* here as the flow must not be modified (as it could go in
* concuncurrency with the subinterfaces).
*
* Called by Flow::periodic_stats_update
*/
void Flow::hosts_periodic_stats_update(NetworkInterface *iface, Host *cli_host,
Host *srv_host,
PartializableFlowTrafficStats *partial,
bool first_partial,
const struct timeval *tv) {
if((!isDNS()) && ntop->getPrefs()->is_dns_cache_enabled()) {
if(!srv_host) {
/* Standard Interface */
/* Handled in Flow::freeDPIMemory() */
} else {
/* View interface (****) */
updateServerName(getViewSharedServer());
}
}
update_pools_stats(iface, cli_host, srv_host, tv, partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes());
if(cli_host && srv_host) {
bool cli_and_srv_in_same_subnet = false;
bool cli_and_srv_in_same_country = false;
VLAN *vl;
int32_t cli_network_id = cli_host->get_local_network_id();
int32_t srv_network_id = srv_host->get_local_network_id();
int16_t stats_protocol =
getStatsProtocol(); /* The protocol (among ndpi master_ and app_) that
is chosen to increase stats */
NetworkStats *cli_network_stats = NULL, *srv_network_stats = NULL;
updateServerPortsStats(srv_host, &ndpiDetectedProtocol, get_first_seen());
updateClientContactedPorts(cli_host, &ndpiDetectedProtocol);
if(cli_network_id >= 0 && (cli_network_id == srv_network_id))
cli_and_srv_in_same_subnet = true;
cli_host->incCliContactedHosts(srv_host->get_ip());
srv_host->incSrvHostContacts(cli_host->get_ip());
if(iface &&
(vl = iface->getVLAN(vlanId, false, false /* NOT an inline call */))) {
/* Note: source and destination hosts have, by definition, the same VLAN
* so the increase is done only one time. */
/* Note: vl will never be null as we're in a flow with that vlan. Hence,
it is guaranteed that at least two hosts exists for that vlan and that
any purge attempt will be prevented. */
#ifdef VLAN_DEBUG
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Increasing VLAN %u stats",
u_int16_t);
#endif
vl->incStats(tv->tv_sec, stats_protocol, partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes());
}
// Update local stats (local vs remote)
// this replaces the old call to Flow::updateInterfaceLocalStats from packet
// processing
iface->incLocalStats(partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(), cli_host->isLocalHost(),
srv_host->isLocalHost());
iface->incLocalStats(partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(), srv_host->isLocalHost(),
cli_host->isLocalHost());
// Update network stats
cli_network_stats = cli_host->getNetworkStats(cli_network_id);
cli_host->incStats(tv->tv_sec, get_protocol(), stats_protocol, get_protocol_category(),
custom_app, partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(), partial->get_cli2srv_goodput_bytes(),
partial->get_srv2cli_packets(), partial->get_srv2cli_bytes(),
partial->get_srv2cli_goodput_bytes(),
srv_host->get_ip()->isNonEmptyUnicastAddress());
// update per-subnet byte counters
if(cli_network_stats) { // only if the network is known and local
if(!cli_and_srv_in_same_subnet) {
cli_network_stats->incEgress(tv->tv_sec, partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(),
srv_host->get_ip()->isBroadcastAddress());
cli_network_stats->incIngress(tv->tv_sec,
partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(),
cli_host->get_ip()->isBroadcastAddress());
} else // client and server ARE in the same subnet
// need to update the inner counter (just one time, will intentionally
// skip this for srv_host)
cli_network_stats->incInner(tv->tv_sec,
partial->get_cli2srv_packets() + partial->get_srv2cli_packets(),
partial->get_cli2srv_bytes() + partial->get_srv2cli_bytes(),
srv_host->get_ip()->isBroadcastAddress() ||
cli_host->get_ip()->isBroadcastAddress());
}
srv_network_stats = srv_host->getNetworkStats(srv_network_id);
srv_host->incStats(tv->tv_sec, get_protocol(), stats_protocol, get_protocol_category(),
custom_app, partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(), partial->get_srv2cli_goodput_bytes(),
partial->get_cli2srv_packets(), partial->get_cli2srv_bytes(),
partial->get_cli2srv_goodput_bytes(),
cli_host->get_ip()->isNonEmptyUnicastAddress());
if(srv_network_stats) {
// local and known server network
if(!cli_and_srv_in_same_subnet) {
srv_network_stats->incIngress(tv->tv_sec,
partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(),
srv_host->get_ip()->isBroadcastAddress());
srv_network_stats->incEgress(tv->tv_sec, partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(),
cli_host->get_ip()->isBroadcastAddress());
}
}
if(cli_host->get_asn() != srv_host->get_asn()) {
AutonomousSystem *cli_as = cli_host ? cli_host->get_as() : NULL,
*srv_as = srv_host ? srv_host->get_as() : NULL;
if(cli_as)
cli_as->incStats(tv->tv_sec, stats_protocol, partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(), getFlowDeviceIP(), getFlowDeviceInIndex(), getFlowDeviceOutIndex());
if(srv_as)
srv_as->incStats(tv->tv_sec, stats_protocol, partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(), partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(), getFlowDeviceIP(), getFlowDeviceInIndex(), getFlowDeviceOutIndex());
}
if(cli_host->get_observation_point_id() &&
srv_host->get_observation_point_id()) {
ObservationPoint *cli_obs_point = cli_host ? cli_host->get_obs_point() : NULL,
*srv_obs_point = srv_host ? srv_host->get_obs_point() : NULL;
if(cli_obs_point)
cli_obs_point->incStats(tv->tv_sec, stats_protocol, partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(), partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes());
if(srv_obs_point)
srv_obs_point->incStats(tv->tv_sec, stats_protocol, partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(), partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes());
}
// Update client DSCP stats
cli_host->incDSCPStats(getCli2SrvDSCP(), partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(),
partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes());
// Update server DSCP stats
srv_host->incDSCPStats(getSrv2CliDSCP(), partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(),
partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes());
// Update Country stats
Country *cli_country_stats = cli_host->getCountryStats();
Country *srv_country_stats = srv_host->getCountryStats();
if(cli_country_stats && srv_country_stats &&
cli_country_stats->equal(srv_country_stats))
cli_and_srv_in_same_country = true;
if(cli_country_stats) {
if(!cli_and_srv_in_same_country) {
cli_country_stats->incEgress(tv->tv_sec, partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(),
srv_host->get_ip()->isBroadcastAddress());
cli_country_stats->incIngress(tv->tv_sec,
partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(),
cli_host->get_ip()->isBroadcastAddress());
} else // client and server ARE in the same country
// need to update the inner counter (just one time, will intentionally
// skip this for srv_host)
cli_country_stats->incInner(tv->tv_sec,
partial->get_cli2srv_packets() + partial->get_srv2cli_packets(),
partial->get_cli2srv_bytes() + partial->get_srv2cli_bytes(),
srv_host->get_ip()->isBroadcastAddress() ||
cli_host->get_ip()->isBroadcastAddress());
}
if(srv_country_stats) {
if(!cli_and_srv_in_same_country) {
srv_country_stats->incIngress(tv->tv_sec,
partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(),
srv_host->get_ip()->isBroadcastAddress());
srv_country_stats->incEgress(tv->tv_sec, partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes(),
cli_host->get_ip()->isBroadcastAddress());
}
}
}
// Update interface DSCP stats
if(iface) {
iface->incDSCPStats(getCli2SrvDSCP(), partial->get_cli2srv_packets(),
partial->get_cli2srv_bytes(),
partial->get_srv2cli_packets(),
partial->get_srv2cli_bytes());
}
switch (get_protocol()) {
case IPPROTO_TCP:
incTcpBadStats(true, cli_host, srv_host, iface, partial->get_cli2srv_tcp_ooo(),
partial->get_cli2srv_tcp_retr(), partial->get_cli2srv_tcp_lost(),
partial->get_cli2srv_tcp_keepalive());
incTcpBadStats(false, cli_host, srv_host, iface, partial->get_srv2cli_tcp_ooo(),
partial->get_srv2cli_tcp_retr(), partial->get_srv2cli_tcp_lost(),
partial->get_srv2cli_tcp_keepalive());
break;
case IPPROTO_ICMP:
if(iface) {
if(partial->get_cli2srv_packets())
iface->incICMPStats(false /* icmp v4 */,
partial->get_cli2srv_packets(),
protos.icmp.cli2srv.icmp_type,
protos.icmp.cli2srv.icmp_code, true);
if(partial->get_srv2cli_packets())
iface->incICMPStats(false /* icmp v4 */,
partial->get_srv2cli_packets(),
protos.icmp.srv2cli.icmp_type,
protos.icmp.srv2cli.icmp_code, true);
}
break;
case IPPROTO_ICMPV6:
if(iface) {
if(partial->get_cli2srv_packets())
iface->incICMPStats(true /* icmp v6 */,
partial->get_cli2srv_packets(),
protos.icmp.cli2srv.icmp_type,
protos.icmp.cli2srv.icmp_code, true);
if(partial->get_srv2cli_packets())
iface->incICMPStats(true /* icmp v6 */,
partial->get_srv2cli_packets(),
protos.icmp.srv2cli.icmp_type,
protos.icmp.srv2cli.icmp_code, true);
}
break;
default:
break;
}
switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
case NDPI_PROTOCOL_HTTP:
if(cli_host && cli_host->getHTTPstats())
cli_host->getHTTPstats()->incStats(true /* Client */,
partial->get_flow_http_stats());
if(srv_host && srv_host->getHTTPstats())
srv_host->getHTTPstats()->incStats(false /* Server */,
partial->get_flow_http_stats());
if(operating_system != ndpi_os_unknown) {
if(cli_host && !(get_cli_ip_addr()->isBroadcastAddress() ||
get_cli_ip_addr()->isMulticastAddress()))
cli_host->setOS(operating_system, os_learning_http_user_agent);
}
/* Don't break, let's process also HTTP_PROXY */
case NDPI_PROTOCOL_HTTP_PROXY:
if(srv_host) {
if((!Utils::isIPAddress(host_server_name))
/* && hasRisk(NDPI_NUMERIC_IP_HOST) */
) {
srv_host->offlineSetHTTPName(host_server_name);
}
if(srv_host->getHTTPstats() && host_server_name &&
isThreeWayHandshakeOK()) {
srv_host->getHTTPstats()->updateHTTPHostRequest(tv->tv_sec, host_server_name, partial->get_num_http_requests(),
partial->get_cli2srv_bytes(), partial->get_srv2cli_bytes());
}
}
break;
case NDPI_PROTOCOL_DNS:
if(cli_host && cli_host->getDNSstats())
cli_host->getDNSstats()->incStats(true /* Client */, partial->get_flow_dns_stats());
if(srv_host && srv_host->getDNSstats())
srv_host->getDNSstats()->incStats(false /* Server */, partial->get_flow_dns_stats());
if(cli_host && srv_host)
cli_host->incDNSContactCardinality(srv_host);
break;
case NDPI_PROTOCOL_MDNS:
if(cli_host) {
if(protos.mdns.answer) cli_host->offlineSetMDNSInfo(protos.mdns.answer);
if(protos.mdns.name) cli_host->offlineSetMDNSName(protos.mdns.name);
if(protos.mdns.name_txt) cli_host->offlineSetMDNSTXTName(protos.mdns.name_txt);
}
break;
case NDPI_PROTOCOL_SSDP:
if(cli_host) {
if(protos.ssdp.location)
cli_host->offlineSetSSDPLocation(protos.ssdp.location);
}
break;
case NDPI_PROTOCOL_NETBIOS:
if(cli_host) {
if(protos.netbios.name)
cli_host->offlineSetNetbiosName(protos.netbios.name);
}
break;
case NDPI_PROTOCOL_DHCP:
if(cli_host) {
if(protos.dhcp.name) {
cli_host->offlineSetDHCPName(protos.dhcp.name);
}
}
break;
case NDPI_PROTOCOL_NTP:
if(cli_host && srv_host) {
cli_host->incNTPContactCardinality(srv_host);
}
break;
case NDPI_PROTOCOL_IP_ICMP:
case NDPI_PROTOCOL_IP_ICMPV6:
if(cli_host && cli_host->getICMPstats()) {
if(partial->get_cli2srv_packets())
cli_host->getICMPstats()->incStats(
partial->get_cli2srv_packets(), protos.icmp.cli2srv.icmp_type,
protos.icmp.cli2srv.icmp_code, true /* Sent */, srv_host);
if(partial->get_srv2cli_packets())
cli_host->getICMPstats()->incStats(
partial->get_srv2cli_packets(), protos.icmp.srv2cli.icmp_type,
protos.icmp.srv2cli.icmp_code, false /* Rcvd */, srv_host);
}
if(srv_host && srv_host->getICMPstats()) {
if(partial->get_cli2srv_packets())
srv_host->getICMPstats()->incStats(
partial->get_cli2srv_packets(), protos.icmp.cli2srv.icmp_type,
protos.icmp.cli2srv.icmp_code, false /* Rcvd */, cli_host);
if(partial->get_srv2cli_packets())
srv_host->getICMPstats()->incStats(
partial->get_srv2cli_packets(), protos.icmp.srv2cli.icmp_type,
protos.icmp.srv2cli.icmp_code, true /* Sent */, cli_host);
}
if(first_partial && icmp_info) {
if(icmp_info->isPortUnreachable()) { // Port unreachable icmpv6/icmpv4
if(srv_host) srv_host->incNumUnreachableFlows(true /* as server */);
if(cli_host) cli_host->incNumUnreachableFlows(false /* as client */);
} else if(icmp_info->isHostUnreachable(protocol)) {
if(srv_host)
srv_host->incNumHostUnreachableFlows(true /* as server */);
if(cli_host)
cli_host->incNumHostUnreachableFlows(false /* as client */);
}
}
break;
case NDPI_PROTOCOL_MAIL_SMTPS:
case NDPI_PROTOCOL_MAIL_SMTP:
if(cli_host && srv_host) {
cli_host->incSMTPContactCardinality(srv_host);
}
break;
case NDPI_PROTOCOL_MAIL_IMAPS:
case NDPI_PROTOCOL_MAIL_IMAP:
if(cli_host && srv_host) {
cli_host->incIMAPContactCardinality(srv_host);
}
break;
case NDPI_PROTOCOL_MAIL_POPS:
case NDPI_PROTOCOL_MAIL_POP:
if(cli_host && srv_host) {
cli_host->incPOPContactCardinality(srv_host);
}
break;
default:
break;
}
if(srv_host && isTLS()) {
#ifdef DEBUG
char buf2[64];
ntop->getTrace()->traceEvent(TRACE_WARNING, "$$$ %s <-> %s [pkts: %u][%s]",
get_srv_ip_addr() ? get_srv_ip_addr()->print(buf2, sizeof(buf2)) : "",
protos.tls.client_requested_server_name,
get_packets(),
protos.tls.server_names ? protos.tls.server_names : "");
#endif
if((protos.tls.server_names != NULL)
/* Ignore hostnames with wildcard or multiple comma-separated values */
&& (strchr(protos.tls.server_names, '*') == NULL)
&& (strchr(protos.tls.server_names, ',') == NULL))
srv_host->offlineSetTLSName(protos.tls.server_names);
else if((protos.tls.client_requested_server_name != NULL)
&& (!hasRisk(NDPI_TLS_CERTIFICATE_MISMATCH)) /* Certificates (if present) do not mismatch */
&& (!Utils::isIPAddress(protos.tls.client_requested_server_name))
&& (get_packets() >= 16) /*
Avoid micro-flows that might be an indication that
the response page is too short and thus that
it might be a denied page or similar
*/
&& (!srv_host->isLocalHost()
/*
As in TLS we cannot check if the connection reported
some mismatches we do not set TLS names for local hosts
that are more subject to naming errors, and that whose
name could be set via other protocols
*/
))
srv_host->offlineSetTLSName(protos.tls.client_requested_server_name); /* (***) */
}
}
/* *************************************** */
void Flow::updateThroughputStats(float tdiff_msec, u_int32_t diff_sent_packets,
u_int64_t diff_sent_bytes,
u_int64_t diff_sent_goodput_bytes,
u_int32_t diff_rcvd_packets,
u_int64_t diff_rcvd_bytes,
u_int64_t diff_rcvd_goodput_bytes) {
if(tdiff_msec == 0) return;
/* In order to avoid overestimating the throughput, we scale small intervals
* to at least one second */
if(tdiff_msec < 1000) tdiff_msec = 1000.;
// bps
float bytes_msec = ((float)((diff_sent_bytes+diff_rcvd_bytes) * 1000)) / tdiff_msec;
float goodput_bytes_msec = ((float)((diff_sent_goodput_bytes+diff_rcvd_goodput_bytes) * 1000)) / tdiff_msec;
/* Just to be safe */
if(bytes_msec < 0) bytes_msec = 0;
if(goodput_bytes_msec < 0) goodput_bytes_msec = 0;
if(bytes_msec > 0) {
// refresh trend stats for the overall throughput
if(get_bytes_thpt() < bytes_msec)
bytes_thpt_trend = trend_up;
else if(get_bytes_thpt() > bytes_msec)
bytes_thpt_trend = trend_down;
else
bytes_thpt_trend = trend_stable;
// refresh goodput stats for the overall throughput
if(get_goodput_bytes_thpt() < goodput_bytes_msec)
goodput_bytes_thpt_trend = trend_up;
else if(get_goodput_bytes_thpt() > goodput_bytes_msec)
goodput_bytes_thpt_trend = trend_down;
else
goodput_bytes_thpt_trend = trend_stable;
// update the old values with the newly calculated ones
bytes_thpt = bytes_msec;
goodput_bytes_thpt = goodput_bytes_msec;
#if DEBUG_TREND
u_int64_t diff_bytes = diff_sent_bytes + diff_rcvd_bytes;
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"[tdiff_msec: %.2f][diff_bytes: %lu][diff_sent_bytes: "
"%lu][diff_rcvd_bytes: %lu][bytes_thpt: %.4f Mbit]",
tdiff_msec, diff_bytes, diff_sent_bytes, diff_rcvd_bytes,
(get_bytes_thpt() * 8)/1000000.);
#endif
if(top_bytes_thpt < get_bytes_thpt())
top_bytes_thpt = get_bytes_thpt();
if(top_goodput_bytes_thpt < get_goodput_bytes_thpt())
top_goodput_bytes_thpt = get_goodput_bytes_thpt();
#ifdef NTOPNG_PRO
throughputTrend.update(get_bytes_thpt()),
goodputTrend.update(get_goodput_bytes_thpt());
thptRatioTrend.update((bytes_msec != 0) ? (((double)(goodput_bytes_msec * 100)) / (double)bytes_msec) : 0);
#ifdef DEBUG_TREND
if((get_goodput_bytes_cli2srv() + get_goodput_bytes_srv2cli()) > 0) {
char buf[256];
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"%s [Goodput long/mid/short %.3f/%.3f/%.3f][ratio: %s][goodput/thpt: %.3f]",
print(buf, sizeof(buf)), goodputTrend.getLongTerm(),
goodputTrend.getMidTerm(), goodputTrend.getShortTerm(),
goodputTrend.getTrendMsg(),
((float)(100 * (get_goodput_bytes))) /
(float)(get_bytes));
}
#endif
#endif
// pps
float pkts_msec = ((float)((diff_sent_packets+ diff_rcvd_packets) * 1000)) / tdiff_msec;
/* Just to be safe */
if(pkts_msec < 0) pkts_msec = 0;
if(get_pkts_thpt() < pkts_msec)
pkts_thpt_trend = trend_up;
else if(get_pkts_thpt() > pkts_msec)
pkts_thpt_trend = trend_down;
else
pkts_thpt_trend = trend_stable;
pkts_thpt = pkts_msec;
if(top_pkts_thpt < get_pkts_thpt()) top_pkts_thpt = get_pkts_thpt();
#if DEBUG_TREND
u_int64_t diff_pkts = diff_sent_packets + diff_rcvd_packets;
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[msec: %.1f][tdiff: %f][pkts: %lu][pkts_thpt: %.2f pps]",
pkts_msec, tdiff_msec, diff_pkts, get_pkts_thpt());
#endif
}
}
/* *************************************** */
/*
* This function is called every second by the purgeIdle function, as well as from other methods
*
* Called by:
* - GenericHash::purgeIdle
* - Flow::flow_end_stats_update (called by GenericHash::purgeIdle -> Flow::housekeep)
* - NetfilterInterface::netfilter_callback (on nEdge, if not flow->get_ndpi_flow())
* - NetworkInterface::dissectPacket -> NetworkInterface::processPacket on thresholds (for large flows)
*/
void Flow::periodic_stats_update(const struct timeval *tv) {
bool first_partial;
PartializableFlowTrafficStats partial;
Host *cli_h = NULL, *srv_h = NULL;
#if 0
char buf[256];
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Periodic flow update: %s",
print(buf, sizeof(buf)));
#endif
if((last_update_time.tv_sec > 0)
&& ((tv->tv_sec - last_update_time.tv_sec) < 3.)) {
return; /* Too early */
}
get_partial_traffic_stats(&periodic_stats_update_partial, &partial, &first_partial);
u_int32_t diff_sent_packets = partial.get_cli2srv_packets();
u_int64_t diff_sent_bytes = partial.get_cli2srv_bytes();
u_int64_t diff_sent_goodput_bytes = partial.get_cli2srv_goodput_bytes();
u_int32_t diff_rcvd_packets = partial.get_srv2cli_packets();
u_int64_t diff_rcvd_bytes = partial.get_srv2cli_bytes();
u_int64_t diff_rcvd_goodput_bytes = partial.get_srv2cli_goodput_bytes();
/*
Do the stats update on the actual peers, i.e.,
peers possibly swapped due to the heuristic
*/
get_actual_peers(&cli_h, &srv_h);
Mac *cli_mac = cli_h ? cli_h->getMac() : NULL;
Mac *srv_mac = srv_h ? srv_h->getMac() : NULL;
hosts_periodic_stats_update(getInterface(), cli_h, srv_h, &partial, first_partial, tv);
if(diff_sent_bytes || diff_rcvd_bytes) {
/* Update L2 Device stats */
if(cli_mac) {
if(diff_rcvd_packets)
cli_mac->incRcvdStats(tv->tv_sec, diff_rcvd_packets, diff_rcvd_bytes);
if(diff_sent_packets)
cli_mac->incSentStats(tv->tv_sec, diff_sent_packets, diff_sent_bytes);
if(ntop->getPrefs()->areMacNdpiStatsEnabled()) {
cli_mac->incnDPIStats(tv->tv_sec, get_protocol_category(),
diff_sent_packets, diff_sent_bytes,
diff_sent_goodput_bytes, diff_rcvd_packets,
diff_rcvd_bytes, diff_rcvd_goodput_bytes);
}
}
if(srv_mac) {
if(diff_rcvd_packets)
srv_mac->incSentStats(tv->tv_sec, diff_rcvd_packets, diff_rcvd_bytes);
if(diff_sent_packets)
srv_mac->incRcvdStats(tv->tv_sec, diff_sent_packets, diff_sent_bytes);
if(ntop->getPrefs()->areMacNdpiStatsEnabled())
srv_mac->incnDPIStats(tv->tv_sec, get_protocol_category(),
diff_rcvd_packets, diff_rcvd_bytes,
diff_rcvd_goodput_bytes, diff_sent_packets,
diff_sent_bytes, diff_sent_goodput_bytes);
}
#ifdef NTOPNG_PRO
#ifndef HAVE_NEDGE
/* Update profile stats */
if (ntop->getPro()->has_valid_license()) {
if(trafficProfile)
trafficProfile->incBytes(diff_sent_bytes + diff_rcvd_bytes);
}
#endif
#endif
}
#ifndef HAVE_NEDGE
/* For nEdge check Flow::setPacketsBytes updates throughput */
if(getFlowSource() != collected_netflow_ipfix) {
/*
In case of NetFlow/IPFIX throughput is computed whenever a new
flow update is received for this flow
by Flow::addFlowStats()
*/
if(last_update_time.tv_sec > 0) {
float tdiff_msec = Utils::msTimevalDiff(tv, &last_update_time);
#ifdef DEBUG
u_int32_t total = diff_sent_bytes+diff_rcvd_bytes;
#endif
updateThroughputStats(tdiff_msec, diff_sent_packets, diff_sent_bytes,
diff_sent_goodput_bytes, diff_rcvd_packets,
diff_rcvd_bytes, diff_rcvd_goodput_bytes);
#ifdef DEBUG
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%.1f ms [%.3f Mbit][%u]", tdiff_msec,
((float)(total*8)) / (tdiff_msec*1000.), total);
#endif
}
}
#endif
memcpy(&last_update_time, tv, sizeof(struct timeval));
GenericHashEntry::periodic_stats_update(tv);
#ifdef HAVE_NEDGE
callFlowUpdate(tv->tv_sec);
if (cli_host && srv_host) {
u_int32_t cli_max_flow_size = cli_host->getMaxFlowSize();
u_int32_t srv_max_flow_size = srv_host->getMaxFlowSize();
if ((cli_max_flow_size && get_bytes() > cli_max_flow_size) ||
(srv_max_flow_size && get_bytes() > srv_max_flow_size))
setDropVerdict(DROP_REASON_FLOW_SIZE_EXCEEDED);
}
#endif
}
/* *************************************** */
/*
* Call actual dump() after checking if flow dump is enabled and supported by the interface
* This is called by Flow::housekeep when status is active or idle
*/
void Flow::dumpCheck(time_t t, bool last_dump_before_free) {
if((ntop->getPrefs()->is_flows_dump_enabled()
#ifdef HAVE_ZMQ
#ifndef HAVE_NEDGE
|| ntop->get_export_interface()
#endif
#endif
)
#ifdef NTOPNG_PRO
&& (getInterface()->isPacketInterface() /* Not a ZMQ interface */
|| (!ntop->getPrefs()->do_dump_flows_direct() /* Direct dump not enabled */))
#endif
) {
dump(t, last_dump_before_free);
}
}
/* *************************************** */
void Flow::update_pools_stats(NetworkInterface *iface, Host *cli_host,
Host *srv_host, const struct timeval *tv,
u_int64_t diff_sent_packets,
u_int64_t diff_sent_bytes,
u_int64_t diff_rcvd_packets,
u_int64_t diff_rcvd_bytes) const {
if(!diff_sent_bytes && !diff_rcvd_bytes) return; /* Nothing to update */
HostPools *hp;
u_int16_t cli_host_pool_id = 0, srv_host_pool_id;
ndpi_protocol_category_t category_id = get_protocol_category();
hp = iface->getHostPools();
if(hp) {
/* Client host */
if(cli_host
#ifdef HAVE_NEDGE
&& cli_host->getMac() &&
(cli_host->getMac()->locate() == located_on_lan_interface)
#endif
) {
cli_host_pool_id = cli_host->get_host_pool();
/* Overall host pool stats */
if(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN &&
!ndpi_is_subprotocol_informative(iface->get_ndpi_struct(), ndpiDetectedProtocol.proto.master_protocol))
hp->incPoolStats(tv->tv_sec, cli_host_pool_id,
ndpiDetectedProtocol.proto.app_protocol, category_id,
diff_sent_packets, diff_sent_bytes, diff_rcvd_packets,
diff_rcvd_bytes);
else
hp->incPoolStats(tv->tv_sec, cli_host_pool_id,
ndpiDetectedProtocol.proto.master_protocol, category_id,
diff_sent_packets, diff_sent_bytes, diff_rcvd_packets,
diff_rcvd_bytes);
#ifdef NTOPNG_PRO
/* Per host quota-enforcement stats */
if(hp->enforceQuotasPerPoolMember(cli_host_pool_id)) {
cli_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.master_protocol, diff_sent_packets,
diff_sent_bytes, diff_rcvd_packets, diff_rcvd_bytes);
cli_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.app_protocol, diff_sent_packets,
diff_sent_bytes, diff_rcvd_packets, diff_rcvd_bytes);
cli_host->incQuotaEnforcementCategoryStats(tv->tv_sec, category_id, diff_sent_bytes, diff_rcvd_bytes);
}
#endif
}
/* Server host */
if(srv_host
#ifdef HAVE_NEDGE
&& srv_host->getMac() &&
(srv_host->getMac()->locate() == located_on_lan_interface)
#endif
) {
srv_host_pool_id = srv_host->get_host_pool();
/* Update server pool stats only if the pool is not equal to the client
* pool */
if(!cli_host || (srv_host_pool_id != cli_host_pool_id)) {
if(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN &&
!ndpi_is_subprotocol_informative(iface->get_ndpi_struct(), ndpiDetectedProtocol.proto.master_protocol))
hp->incPoolStats(tv->tv_sec, srv_host_pool_id,
ndpiDetectedProtocol.proto.app_protocol, category_id,
diff_rcvd_packets, diff_rcvd_bytes,
diff_sent_packets, diff_sent_bytes);
else
hp->incPoolStats(tv->tv_sec, srv_host_pool_id,
ndpiDetectedProtocol.proto.master_protocol, category_id,
diff_rcvd_packets, diff_rcvd_bytes,
diff_sent_packets, diff_sent_bytes);
}
/* When quotas have to be enforced per pool member, stats must be
* increased even if cli and srv are on the same pool */
#ifdef NTOPNG_PRO
if(hp->enforceQuotasPerPoolMember(srv_host_pool_id)) {
srv_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.master_protocol, diff_rcvd_packets,
diff_rcvd_bytes, diff_sent_packets, diff_sent_bytes);
srv_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.proto.app_protocol, diff_rcvd_packets,
diff_rcvd_bytes, diff_sent_packets, diff_sent_bytes);
srv_host->incQuotaEnforcementCategoryStats(tv->tv_sec, category_id, diff_rcvd_bytes, diff_sent_bytes);
}
#endif
}
}
}
/* *************************************** */
bool Flow::equal(const Mac *_src_pkt_mac, const Mac *_dst_pkt_mac,
const IpAddress *_cli_ip, const IpAddress *_srv_ip,
u_int16_t _cli_port, u_int16_t _srv_port, u_int16_t _u_int16_t,
u_int16_t _observation_point_id, u_int32_t _private_flow_id,
u_int8_t _protocol, const ICMPinfo *const _icmp_info,
bool *src2srv_direction) const {
const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();
const Mac *src_mac, *dst_mac;
bool useMacAddressInFlowKey = ntop->getPrefs()->useMacAddressInFlowKey();
#if 0
if(ntohs(_cli_port) == 17446) {
char buf1[64],buf2[64],buf3[64],buf4[64];
ntop->getTrace()->traceEvent(TRACE_WARNING, "[%s][%s][%s][%s]",
cli_ip->print(buf1, sizeof(buf1)),
srv_ip->print(buf2, sizeof(buf2)),
_cli_ip->print(buf3, sizeof(buf3)),
_srv_ip->print(buf4, sizeof(buf4)));
}
#endif
if(getPrivateFlowId() != _private_flow_id) return (false);
if((get_vlan_id() != _u_int16_t)
#ifdef MAKE_OBSERVATION_POINT_KEY
/*
Uncomment the line below if you want the same host
seen from various observation points, to be considered
a unique host */
|| (get_observation_point_id() != _observation_point_id)
#endif
)
return (false);
if(_protocol != protocol) return (false);
#ifdef DONT_MERGE_ICMP_FLOWS
if(icmp_info && !icmp_info->equal(_icmp_info)) return (false);
#endif
if(cli_ip && cli_ip->equal(_cli_ip) && srv_ip && srv_ip->equal(_srv_ip) &&
_cli_port == cli_port && _srv_port == srv_port) {
*src2srv_direction = true, src_mac = _src_pkt_mac, dst_mac = _dst_pkt_mac;
} else if(srv_ip && srv_ip->equal(_cli_ip) && cli_ip &&
cli_ip->equal(_srv_ip) && _srv_port == cli_port &&
_cli_port == srv_port) {
*src2srv_direction = false, src_mac = _dst_pkt_mac, dst_mac = _src_pkt_mac;
cli_ip = get_srv_ip_addr(), srv_ip = get_cli_ip_addr();
} else
return (false);
#ifdef USE_MAC_IN_KEY_WITH_DHCP
/* Check if MAC address needs to be used in flow key */
if((cli_ip->key() == 0) && (srv_ip->key() == 0xFFFFFFFF)) {
useMacAddressInFlowKey = true;
}
#endif
#ifdef HAVE_NEDGE
/*
As with Netfilter we do not have MAC visibility
they should not be used here to avoid invalid
flow search as with Netfilter we see only the
sender MAC
*/
useMacAddressInFlowKey = false;
#endif
if(useMacAddressInFlowKey) {
if(cli_host && src_mac) {
Mac *cli_mac = cli_host->getMac();
if(cli_mac != src_mac) {
#ifdef DEBUG
Mac *srv_mac = srv_host->getMac();
char buf[64], buf0[64], buf1[64], buf2[64], buf3[64], buf4[64];
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s (%s) <-> %s (%s) [%s / %s]",
cli_ip->print(buf, sizeof(buf)), cli_mac->print(buf0, sizeof(buf0)),
srv_ip->print(buf1, sizeof(buf1)),
srv_mac->print(buf2, sizeof(buf2)),
((Mac *)_src_pkt_mac)->print(buf3, sizeof(buf3)),
((Mac *)_dst_pkt_mac)->print(buf4, sizeof(buf4)));
#endif
return (false);
}
}
if(src_mac && dst_mac && cli_host && srv_host) {
Mac *srv_mac = srv_host->getMac();
if(srv_mac != dst_mac) return (false);
}
}
return (true);
}
/* *************************************** */
const char *Flow::cipher_weakness2str(ndpi_cipher_weakness w) const {
switch (w) {
case ndpi_cipher_safe:
return ("safe");
break;
case ndpi_cipher_weak:
return ("weak");
break;
case ndpi_cipher_insecure:
return ("insecure");
break;
}
return (""); /* NOTREACHED */
}
/* *************************************** */
void Flow::luaScore(lua_State *vm) {
u_int32_t tot;
lua_newtable(vm);
lua_push_int32_table_entry(vm, "flow_score", getScore());
/* ***************************************** */
lua_newtable(vm);
for (u_int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
ScoreCategory score_category = (ScoreCategory)i;
char tmp[8];
snprintf(tmp, sizeof(tmp), "%u", i);
lua_push_int32_table_entry(vm, tmp,
stats.get_cli_score(score_category) +
stats.get_srv_score(score_category));
}
lua_pushstring(vm, "host_categories_total");
lua_insert(vm, -2);
lua_settable(vm, -3);
/* ***************************************** */
lua_newtable(vm);
tot = 0;
for (u_int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
ScoreCategory score_category = (ScoreCategory)i;
tot += stats.get_cli_score(score_category);
}
lua_push_int32_table_entry(vm, "client_score", tot);
tot = 0;
for (u_int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
ScoreCategory score_category = (ScoreCategory)i;
tot += stats.get_srv_score(score_category);
}
lua_push_int32_table_entry(vm, "server_score", tot);
lua_pushstring(vm, "host_score_total");
lua_insert(vm, -2);
lua_settable(vm, -3);
/* ***************************************** */
/* Individual alerts score */
lua_newtable(vm);
for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++) {
char tmp[8];
FlowAlert *alert = it->second;
snprintf(tmp, sizeof(tmp), "%u", it->first);
lua_push_int32_table_entry(vm, tmp, alert->getCliScore() + alert->getSrvScore());
}
lua_pushstring(vm, "alert_score");
lua_insert(vm, -2);
lua_settable(vm, -3);
/* ***************************************** */
lua_pushstring(vm, "score");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
/* *************************************** */
void Flow::lua(lua_State *vm, AddressTree *ptree,
DetailsLevel details_level, bool skipNewTable) {
const IpAddress *src_ip = get_cli_ip_addr(), *dst_ip = get_srv_ip_addr();
bool src_match = true, dst_match = true;
bool mask_flow;
bool has_json_info = false;
u_char community_id[200];
char buf[64];
Mac *cli_mac = get_cli_host() ? get_cli_host()->getMac() : NULL;
char *asname = NULL;
u_int32_t asn = 0;
if(ptree) {
if(src_ip) src_match = src_ip->match(ptree);
if(dst_ip) dst_match = dst_ip->match(ptree);
if(!src_match && !dst_match) return;
}
if(!skipNewTable) lua_newtable(vm);
lua_get_ip(vm, true /* Client */);
lua_get_ip(vm, false /* Server */);
lua_push_uint32_table_entry(vm, "vlan", get_vlan_id());
lua_push_int32_table_entry(vm, "iface_index", getInterface() ? getInterface()->get_id() : -1);
lua_get_port(vm, true /* Client */);
lua_get_port(vm, false /* Server */);
mask_flow = isMaskedFlow(); // mask_cli_host || mask_dst_host;
lua_get_bytes(vm);
if (json_protocol_info)
lua_push_str_table_entry(vm, "json_protocol_info", json_protocol_info);
char *json = alerts_json; /* Copying ref as it may be moved to the shadow meantime */
if (json) lua_push_str_table_entry(vm, "json_alert", json);
getSrcAS(&asn, asname);
lua_push_int32_table_entry(vm, "src_as", asn);
lua_push_str_table_entry(vm, "src_as_name", asname ? asname : "");
getDstAS(&asn, asname);
lua_push_int32_table_entry(vm, "dst_as", asn);
lua_push_str_table_entry(vm, "dst_as_name", asname ? asname : "");
if(srcPeerAS != 0) lua_push_int32_table_entry(vm, "src_peer_as", srcPeerAS);
if(dstPeerAS != 0) lua_push_int32_table_entry(vm, "dst_peer_as", dstPeerAS);
lua_snmp_info(vm);
if(details_level >= details_high) {
if(tcp_fingerprint)
lua_push_str_table_entry(vm, "tcp_fingerprint", tcp_fingerprint);
if(swap_done) lua_push_bool_table_entry(vm, "flow_swapped", true);
lua_push_uint32_table_entry(vm, "flow_source", flow_source);
lua_push_bool_table_entry(vm, "cli.allowed_host", src_match);
lua_push_bool_table_entry(vm, "srv.allowed_host", dst_match);
lua_get_info(vm, true /* Client */);
lua_get_info(vm, false /* Server */);
if(collection && collection->vrfId)
lua_push_uint64_table_entry(vm, "vrfId", collection->vrfId);
/* See VLANAddressTree.h for details */
lua_push_uint32_table_entry(vm, "observation_point_id",
get_observation_point_id());
if(collection) {
if(collection->prevAdjacentAS)
lua_push_int32_table_entry(vm, "prev_adjacent_as", collection->prevAdjacentAS);
if(collection->nextAdjacentAS)
lua_push_int32_table_entry(vm, "next_adjacent_as", collection->nextAdjacentAS);
}
lua_tos(vm);
lua_get_protocols(vm);
lua_push_str_table_entry(vm, "community_id",
(char *)getCommunityId(community_id, sizeof(community_id)));
#ifdef NTOPNG_PRO
#ifndef HAVE_NEDGE
if((!mask_flow) && trafficProfile && ntop->getPro()->has_valid_license())
lua_push_str_table_entry(vm, "profile", trafficProfile->getName());
#endif
#endif
lua_get_packets(vm);
lua_get_time(vm);
lua_get_dir_traffic(vm, true /* Client to Server */);
lua_get_dir_traffic(vm, false /* Server to Client */);
lua_get_flow_connection_state(vm);
luaScore(vm);
if(isICMP()) {
lua_newtable(vm);
if(isBidirectional()) {
lua_push_uint64_table_entry(vm, "type", protos.icmp.srv2cli.icmp_type);
lua_push_uint64_table_entry(vm, "code", protos.icmp.srv2cli.icmp_code);
} else {
lua_push_uint64_table_entry(vm, "type", protos.icmp.cli2srv.icmp_type);
lua_push_uint64_table_entry(vm, "code", protos.icmp.cli2srv.icmp_code);
}
if(icmp_info) icmp_info->lua(vm, NULL, iface, get_vlan_id());
lua_pushstring(vm, "icmp");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
lua_push_int32_table_entry(vm, "cli.devtype",
(cli_host && cli_host->getMac())
? cli_host->getMac()->getDeviceType()
: device_unknown);
lua_push_int32_table_entry(vm, "srv.devtype",
(srv_host && srv_host->getMac())
? srv_host->getMac()->getDeviceType()
: device_unknown);
#ifdef HAVE_NEDGE
if(iface->is_bridge_interface()) {
lua_push_bool_table_entry(vm, "verdict.pass", isPassVerdict() ? 1 : 0);
lua_push_int32_table_entry(vm, "verdict.reason", (int) dropVerdictReason);
}
#else
if(!passVerdict) {
lua_push_bool_table_entry(vm, "verdict.pass", 0);
}
#endif
if(get_protocol() == IPPROTO_TCP) lua_get_tcp_info(vm);
if(!mask_flow) {
if(host_server_name)
lua_push_str_table_entry(vm, "host_server_name", host_server_name);
if(suspicious_dga_domain)
lua_push_str_table_entry(vm, "suspicious_dga_domain",
suspicious_dga_domain);
if(bt_hash) lua_push_str_table_entry(vm, "bittorrent_hash", bt_hash);
lua_push_str_table_entry(vm, "info", getFlowInfo(true).c_str());
}
if(stun_mapped_address)
lua_push_str_table_entry(vm, "protos.stun.mapped_ip_address", stun_mapped_address);
if(isDNS() && protos.dns.last_query) {
lua_push_uint64_table_entry(vm, "protos.dns.last_query_type",
protos.dns.last_query_type);
lua_push_uint64_table_entry(vm, "protos.dns.last_return_code",
protos.dns.last_return_code);
}
if(l7_json.length() > 0)
lua_push_str_table_entry(vm, "l7_json", l7_json.c_str());
#ifdef HAVE_NEDGE
lua_push_uint64_table_entry(vm, "marker", marker);
if(cli_host && srv_host) {
/* Shapers */
lua_push_uint64_table_entry(vm, "shaper.cli",
flowShapers.cli ? flowShapers.cli->get_shaper_id() : DEFAULT_SHAPER_ID);
lua_push_uint64_table_entry(vm, "shaper.srv",
flowShapers.srv ? flowShapers.srv->get_shaper_id() : DEFAULT_SHAPER_ID);
/* Quota */
lua_push_str_table_entry(vm, "cli.quota_source",
Utils::policySource2Str(cli_quota_source));
lua_push_str_table_entry(vm, "srv.quota_source",
Utils::policySource2Str(srv_quota_source));
}
#endif
if(!mask_flow) {
if(isHTTP()) lua_get_http_info(vm);
if(isDNS()) lua_get_dns_info(vm);
if(isSSH()) lua_get_ssh_info(vm);
if(isTLS()) lua_get_tls_info(vm);
if(isSIP()) lua_get_sip_info(vm);
}
if(!getInterface()->isPacketInterface()) lua_snmp_info(vm);
lua_push_int32_table_entry(vm, "l7_error_code", getErrorCode());
lua_push_int32_table_entry(vm, "flow_verdict", flow_verdict);
lua_push_bool_table_entry(vm, "periodic_flow",
is_periodic_flow ? true : false);
if(end_reason)
lua_push_str_table_entry(vm, "flow_end_reason", getEndReason());
if(collection && collection->wifi.wlan_ssid) {
char mac_buf[20];
lua_newtable(vm);
lua_push_str_table_entry(vm, "ssid", collection->wifi.wlan_ssid);
lua_push_str_table_entry(vm, "wtp_mac_address",
Utils::formatMac(collection->wifi.wtp_mac_address, mac_buf, sizeof(mac_buf)));
lua_pushstring(vm, "wlan");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
if(isSMTP()
/* Discard SMTP connections that become TLS as the SMTP part is not populated */
&& (!isSMTPS())) {
if(getSMTPMailFrom())
lua_push_str_table_entry(vm, "smtp_mail_from", getSMTPMailFrom());
if(getSMTPRcptTo())
lua_push_str_table_entry(vm, "smtp_rcpt_to", getSMTPRcptTo());
}
if(rtp_stream_type != ndpi_multimedia_unknown_flow) {
if(rtp_stream_type & ndpi_multimedia_audio_flow)
lua_push_str_table_entry(vm, "rtp_stream_type", "audio");
else if(rtp_stream_type & ndpi_multimedia_video_flow)
lua_push_str_table_entry(vm, "rtp_stream_type", "video");
else if(rtp_stream_type & ndpi_multimedia_screen_sharing_flow)
lua_push_str_table_entry(vm, "rtp_stream_type", "screen_share");
}
if(flow_payload != NULL)
lua_push_str_len_table_entry(vm, "flow_payload", flow_payload, flow_payload_len);
if(get_json_info()) {
lua_push_str_table_entry(vm, "moreinfo.json",
json_object_to_json_string(get_json_info()));
has_json_info = true;
} else if(get_tlv_info()) {
ndpi_deserializer deserializer;
if(ndpi_init_deserializer(&deserializer, get_tlv_info()) == 0) {
ndpi_serializer serializer;
if(ndpi_init_serializer(&serializer, ndpi_serialization_format_json) >= 0) {
char *buffer;
u_int32_t buffer_len;
ndpi_deserialize_clone_all(&deserializer, &serializer);
buffer = ndpi_serializer_get_buffer(&serializer, &buffer_len);
if(buffer) {
lua_push_str_table_entry(vm, "moreinfo.json", buffer);
has_json_info = true;
}
ndpi_term_serializer(&serializer);
}
}
}
if(iec104) iec104->lua(vm);
#ifdef NTOPNG_PRO
if(modbus) modbus->lua(vm);
#endif
if(!has_json_info) lua_push_str_table_entry(vm, "moreinfo.json", "{}");
if(ebpf) ebpf->lua(vm);
lua_get_throughput(vm);
/* Interarrival Times */
lua_get_dir_iat(vm, true /* Client to Server */);
lua_get_dir_iat(vm, false /* Server to Client */);
if((!mask_flow) && (details_level >= details_higher)) {
lua_get_geoloc(vm, true /* Client */, true /* Coordinates */,
false /* Country and City */);
lua_get_geoloc(vm, false /* Server */, true /* Coordinates */,
false /* Country and City */);
if(details_level >= details_max) {
lua_get_geoloc(vm, true /* Client */, false /* Coordinates */,
true /* Country and City */);
lua_get_geoloc(vm, false /* Server */, false /* Coordinates */,
true /* Country and City */);
}
}
lua_confidence(vm);
lua_get_risk_info(vm);
#ifdef ENABLE_ENTROPHY_CALCULATION
lua_entropy(vm);
#endif
if(riskInfo)
lua_push_str_table_entry(vm, "riskInfo", riskInfo);
#if defined(NTOPNG_PRO)
lua_get_qoe_score(vm);
#endif
}
lua_get_status(vm);
lua_push_str_table_entry(vm, "proto.ndpi",
detection_completed
? get_detected_protocol_name(buf, sizeof(buf))
: (char *)CONST_TOO_EARLY);
lua_push_str_table_entry(vm, "proto.ndpi_confidence",
ndpi_confidence_get_name(ndpi_confidence));
// this is used to dynamicall update entries in the GUI
lua_push_uint64_table_entry(vm, "ntopng.key", key()); // Key
lua_push_uint64_table_entry(vm, "hash_entry_id", get_hash_entry_id());
if(cli_mac != NULL) {
if(cli_mac->getDHCPfingerprint())
lua_push_str_table_entry(vm,
"dhcp_fingerprint",
cli_mac->getDHCPfingerprint());
}
}
/* *************************************** */
void Flow::lua_confidence(lua_State *vm) {
if(isDPIDetectedFlow())
lua_push_uint32_table_entry(vm, "confidence",
(ndpiConfidence)confidence_dpi);
else
lua_push_uint32_table_entry(vm, "confidence",
(ndpiConfidence)confidence_guessed);
}
/* *************************************** */
void Flow::lua_tos(lua_State *vm) {
lua_newtable(vm);
lua_newtable(vm);
lua_push_int32_table_entry(vm, "DSCP", getCli2SrvDSCP());
lua_push_int32_table_entry(vm, "ECN", getCli2SrvECN());
lua_pushstring(vm, "client");
lua_insert(vm, -2);
lua_settable(vm, -3);
/* *********************** */
lua_newtable(vm);
lua_push_int32_table_entry(vm, "DSCP", getSrv2CliDSCP());
lua_push_int32_table_entry(vm, "ECN", getSrv2CliECN());
lua_pushstring(vm, "server");
lua_insert(vm, -2);
lua_settable(vm, -3);
lua_pushstring(vm, "tos");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
/* *************************************** */
void Flow::lua_get_risk_info(lua_State *vm) {
if(ndpi_flow_risk_bitmap != 0) {
ndpi_risk unhandled_ndpi_risks = ntop->getUnhandledRisks();
if(unhandled_ndpi_risks & ndpi_flow_risk_bitmap) {
/* This flow has some unhandled risks, that is, risks set by nDPI but not
* handled by flow checks */
lua_newtable(vm);
for (u_int i = 0; i < NDPI_MAX_RISK; i++)
if(hasRisk((ndpi_risk_enum)i) && NDPI_ISSET_BIT(unhandled_ndpi_risks, (ndpi_risk_enum)i))
lua_push_uint64_table_entry(vm, ndpi_risk2str((ndpi_risk_enum)i), i);
lua_pushstring(vm, "unhandled_flow_risk");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
}
}
/* *************************************** */
void Flow::setRisk(ndpi_risk risk_bitmap) {
/* Handle OR of risks with no risk */
NDPI_CLR_BIT(risk_bitmap, NDPI_NO_RISK);
if(risk_bitmap == 0) NDPI_SET_BIT(risk_bitmap, NDPI_NO_RISK);
ndpi_flow_risk_bitmap = risk_bitmap;
has_malicious_cli_signature =
NDPI_ISSET_BIT(ndpi_flow_risk_bitmap, NDPI_MALICIOUS_FINGERPRINT);
}
/* *************************************** */
void Flow::addRisk(ndpi_risk risk_bitmap) {
setRisk(ndpi_flow_risk_bitmap | risk_bitmap);
}
/* *************************************** */
bool Flow::hasRisk(ndpi_risk_enum r) const {
if(r < NDPI_MAX_RISK) return NDPI_ISSET_BIT(ndpi_flow_risk_bitmap, r);
return false;
}
/* *************************************** */
/* Returns true if at least one nDPI flow risk is set */
bool Flow::hasRisks() const {
for (int i = 0; i < NDPI_MAX_RISK; i++) {
if(hasRisk((ndpi_risk_enum)i)) return true;
}
return false;
}
/* *************************************** */
void Flow::clearRisks() {
ndpi_flow_risk_bitmap = 0;
NDPI_SET_BIT(ndpi_flow_risk_bitmap, NDPI_NO_RISK);
}
/* *************************************** */
void Flow::computeKey() {
u_int32_t k = cli_port + srv_port + vlanId + protocol + privateFlowId;
#ifdef MAKE_OBSERVATION_POINT_KEY
k += get_observation_point_id();
#endif
if(get_cli_ip_addr()) k += get_cli_ip_addr()->key();
if(get_srv_ip_addr()) k += get_srv_ip_addr()->key();
if(icmp_info) k += icmp_info->key();
#if USE_MAC_IN_KEY_WITH_DHCP
if(get_cli_ip_addr() && get_srv_ip_addr()) {
if((get_cli_ip_addr()->key() == 0) && (get_srv_ip_addr()->key() == 0xFFFFFFFF)) {
/* Add the MAC address of the source host (dst_mac is not necessary as it's FF:FF:FF:FF:FF:FF) */
if(get_cli_host() != NULL) {
Mac *cli_mac = get_cli_host()->getMac();
if(cli_mac != NULL)
k += cli_mac->key();
}
}
}
#endif
flow_key = k;
}
/* *************************************** */
u_int32_t Flow::key(Host *_cli, u_int16_t _cli_port, Host *_srv,
u_int16_t _srv_port, u_int16_t _vlan_id,
u_int16_t _observation_point_id, u_int16_t _protocol) {
u_int32_t k = _cli_port + _srv_port + _vlan_id + _protocol;
#ifdef MAKE_OBSERVATION_POINT_KEY
k += _observation_point_id;
#endif
if(_cli) k += _cli->key();
if(_srv) k += _srv->key();
return (k);
}
/* *************************************** */
void Flow::set_hash_entry_id(u_int32_t assigned_hash_entry_id) {
hash_entry_id = assigned_hash_entry_id;
};
/* *************************************** */
u_int32_t Flow::get_hash_entry_id() const { return (hash_entry_id); };
/* *************************************** */
bool Flow::is_hash_entry_state_idle_transition_ready() {
bool ret = false;
#ifdef EXPIRE_FLOWS_IMMEDIATELY
return (true); /* Debug only */
#endif
#ifdef HAVE_NEDGE
if(iface->getIfType() == interface_type_NETFILTER)
return (isNetfilterIdleFlow());
#endif
if(iface->getIfType() == interface_type_ZMQ ||
iface->getIfType() == interface_type_SYSLOG) {
ret = is_active_entry_now_idle(iface->getFlowMaxIdle());
} else {
if(tcp != NULL) {
u_int8_t tcp_flags = src2dst_tcp_flags | dst2src_tcp_flags;
/*
* The flow is considered idle after a MAX_TCP_FLOW_IDLE
* when RST/FIN are set or when the TWH is not completed.
* This prevents finalized/reset flows, or flows with an imcomplete
* TWH from staying in memory for too long.
*/
if((tcp_flags & TH_FIN
|| tcp_flags & TH_RST
|| ((iface->isPacketInterface()
|| tcp_flags /* If not a packet interfaces, we expect flags to be set to be sure they've been exported */)
&& !isThreeWayHandshakeOK()))
&& is_active_entry_now_idle(iface->getFlowMaxIdle())) {
ret = true;
}
}
if(!ret) ret = is_active_entry_now_idle(iface->getFlowMaxIdle());
}
#if 0
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s() [uses: %u][time: %d][idle: %s]",
__FUNCTION__, getUses(),
(last_seen + iface->getFlowMaxIdle()) - iface->getTimeLastPktRcvd(),
ret ? "true" : "false");
if(ret)
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[%s] Idle flow found", iface->get_name());
#endif
return (ret);
}
/* *************************************** */
void Flow::sumStats(nDPIStats *ndpi_stats, FlowStats *status_stats) {
ndpi_protocol detected_protocol = get_detected_protocol();
/* Increase Application stats */
if(detected_protocol.proto.app_protocol != detected_protocol.proto.master_protocol &&
detected_protocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) {
ndpi_stats->incStats(0, detected_protocol.proto.app_protocol,
get_packets_cli2srv(), get_bytes_cli2srv(),
get_packets_srv2cli(), get_bytes_srv2cli());
ndpi_stats->incFlowsStats(detected_protocol.proto.app_protocol);
} else {
ndpi_stats->incStats(0, detected_protocol.proto.master_protocol,
get_packets_cli2srv(), get_bytes_cli2srv(),
get_packets_srv2cli(), get_bytes_srv2cli());
ndpi_stats->incFlowsStats(detected_protocol.proto.master_protocol);
}
/* Increase Category stats */
ndpi_stats->incCategoryStats(0, get_protocol_category(),
get_bytes_cli2srv(), get_bytes_srv2cli());
status_stats->incStats(getAlertsBitmap(), protocol,
Utils::mapScoreToSeverity(getPredominantAlertScore()),
getCli2SrvDSCP(), getSrv2CliDSCP(), this);
}
/* *************************************** */
char* Flow::serialize(ExportFormat format) {
json_object *my_object;
const char *json;
char *rsp = NULL;
my_object = flow2JSON(format);
if (my_object == NULL)
return NULL;
/* JSON string */
json = json_object_to_json_string(my_object);
// ntop->getTrace()->traceEvent(TRACE_WARNING, "Emitting Flow: %s", json);
if (json)
rsp = strdup(json);
/* Free memory */
json_object_put(my_object);
return (rsp);
}
/* *************************************** */
void Flow::formatECSObserver(json_object *my_object) {
json_object *observer_object;
if((observer_object = json_object_new_object()) != NULL) {
json_object_object_add(observer_object, "product",
json_object_new_string("ntopng"));
json_object_object_add(observer_object, "vendor",
json_object_new_string("ntop"));
if(ntop->getPrefs() && ntop->getPrefs()->get_instance_name())
json_object_object_add(
observer_object, "name",
json_object_new_string(ntop->getPrefs()->get_instance_name()));
json_object_object_add(my_object, "observer", observer_object);
}
}
/* *************************************** */
void Flow::formatECSEvent(json_object *my_object) {
json_object *event_object;
if((event_object = json_object_new_object()) != NULL) {
json_object_object_add(event_object, "risk_score",
json_object_new_int(getScore()));
json_object_object_add(my_object, "event", event_object);
}
}
/* *************************************** */
void Flow::formatECSInterface(json_object *my_object) {
json_object *interface_object;
if((interface_object = json_object_new_object()) != NULL) {
json_object_object_add(interface_object, "id",
json_object_new_int(iface->get_id()));
if(iface && iface->get_name())
json_object_object_add(interface_object, "name",
json_object_new_string(iface->get_name()));
json_object_object_add(my_object, "interface", interface_object);
}
}
/* *************************************** */
void Flow::formatECSExtraInfo(json_object *my_object) {
json_object *interface_object;
if(ntop->getPrefs()->do_dump_extended_json()) {
if((interface_object = json_object_new_object()) != NULL) {
json_object_object_add(my_object, "flow_time",
json_object_new_int(last_seen));
#if defined(NTOPNG_PRO) && !defined(HAVE_NEDGE)
json_object_object_add(my_object, "profile",
json_object_new_string(get_profile_name()));
#endif
json_object_object_add(my_object, "interface", interface_object);
}
}
}
/* *************************************** */
void Flow::formatECSAppProto(json_object *my_object) {
json_object *application_object;
if(isDNS() && protos.dns.last_query) {
if((application_object = json_object_new_object()) != NULL) {
json_object_object_add(application_object, "question.name",
json_object_new_string(protos.dns.last_query));
json_object_object_add(my_object, "dns", application_object);
}
} else if(isTLS() && protos.tls.client_requested_server_name) {
if((application_object = json_object_new_object()) != NULL) {
json_object_object_add(
application_object, "server_name",
json_object_new_string(protos.tls.client_requested_server_name));
json_object_object_add(my_object, "tls", application_object);
}
} else if(isHTTP()) {
if((application_object = json_object_new_object()) != NULL) {
if(protos.http.last_url && protos.http.last_url[0] != '\0')
json_object_object_add(application_object, "request.url",
json_object_new_string(protos.http.last_url));
if(protos.http.last_user_agent && protos.http.last_user_agent[0] != '\0')
json_object_object_add(
application_object, "user_agent",
json_object_new_string(protos.http.last_user_agent));
if(protos.http.last_method != NDPI_HTTP_METHOD_UNKNOWN)
json_object_object_add(application_object, "request.method",
json_object_new_string(ndpi_http_method2str(
protos.http.last_method)));
if(protos.http.last_return_code > 0)
json_object_object_add(
application_object, "response.status_code",
json_object_new_int((u_int32_t)protos.http.last_return_code));
if(protos.http.last_server != NULL)
json_object_object_add(application_object, "response.server",
json_object_new_string(protos.http.last_server));
json_object_object_add(my_object, "http", application_object);
}
} else if(bt_hash) {
if((application_object = json_object_new_object()) != NULL) {
json_object_object_add(application_object, "hash",
json_object_new_string(bt_hash));
json_object_object_add(my_object, "bittorrent", application_object);
}
}
}
/* *************************************** */
void Flow::formatECSNetwork(json_object *my_object, const IpAddress *addr) {
json_object *network_object;
if((network_object = json_object_new_object()) != NULL) {
char buf[128], jsonbuf[64];
u_char community_id[200];
json_object_object_add(network_object,
Utils::jsonLabel(PROTOCOL, "iana_number", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(protocol));
if(((get_packets_cli2srv() + get_packets_srv2cli()) >
NDPI_MIN_NUM_PACKETS) ||
(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN))
json_object_object_add(network_object,
Utils::jsonLabel(L7_PROTO_NAME, "protocol", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(get_detected_protocol_name(buf, sizeof(buf))));
if(tcp != NULL)
json_object_object_add(network_object,
Utils::jsonLabel(TCP_FLAGS, "tcp_flags", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(src2dst_tcp_flags | dst2src_tcp_flags));
json_object_object_add(network_object,
Utils::jsonLabel(FIRST_SWITCHED, "first_seen",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int(get_partial_first_seen()));
json_object_object_add(network_object,
Utils::jsonLabel(LAST_SWITCHED, "last_seen", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(get_partial_last_seen()));
json_object *category_object;
if((category_object = json_object_new_object()) != NULL) {
json_object_object_add(category_object,
Utils::jsonLabel(L7_CATEGORY, "name", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(get_protocol_category_name()));
json_object_object_add(category_object,
Utils::jsonLabel(L7_CATEGORY_ID, "id", jsonbuf, sizeof(jsonbuf)),
json_object_new_int((u_int32_t)get_protocol_category()));
json_object_object_add(network_object, "category", category_object);
}
json_object_object_add(my_object, "community_id",
json_object_new_string((char *)getCommunityId(
community_id, sizeof(community_id))));
#ifdef NTOPNG_PRO
#ifndef HAVE_NEDGE
// Traffic profile information, if any
if(trafficProfile && trafficProfile->getName())
json_object_object_add(network_object, "profile",
json_object_new_string(trafficProfile->getName()));
#endif
#endif
#ifdef HAVE_NEDGE
if(iface && iface->is_bridge_interface()) {
json_object_object_add(my_object, "verdict.pass",
json_object_new_boolean(isPassVerdict() ? (json_bool)1
: (json_bool)0));
json_object_object_add(my_object, "verdict.reason",
json_object_new_int((int) dropVerdictReason));
}
#else
if(!passVerdict)
json_object_object_add(my_object, "pass_verdict",
json_object_new_boolean((json_bool)0));
#endif
if(addr)
json_object_object_add(network_object,
Utils::jsonLabel(IP_PROTOCOL_VERSION, "type", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(addr->isIPv4() ? "ipv4" : "ipv6"));
if(flow_device.device_ip)
json_object_object_add(network_object, "exporter",
json_object_new_string(intoaV4(flow_device.device_ip, buf, sizeof(buf))));
json_object_object_add(network_object, "info",
json_object_new_string(getFlowInfo(false).c_str()));
json_object_object_add(my_object, "network", network_object);
}
}
/* *************************************** */
void Flow::formatECSHost(json_object *my_object, bool is_client,
const IpAddress *addr, Host *host) {
json_object *host_object;
bool use_nat = false;
if((host_object = json_object_new_object()) != NULL) {
char buf[64], jsonbuf[64], *c;
IpAddress tmp_ip;
/* Adding MAC */
if(host && host->getMac() && !host->getMac()->isNull())
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? IN_SRC_MAC : IN_DST_MAC, "mac", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(Utils::formatMac(host ? host->get_mac() : NULL,
buf, sizeof(buf))));
/* Adding IP */
if(addr) {
if(getPreNATSrcIp()) {
tmp_ip.set(htonl(getPreNATSrcIp()));
if(is_client && (getPreNATSrcIp() != getPostNATSrcIp()))
use_nat = true;
} else {
tmp_ip.set(addr);
}
/* With NAT they are IPv4 */
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? (tmp_ip.isIPv4() ? IPV4_SRC_ADDR : IPV6_SRC_ADDR)
: (tmp_ip.isIPv4() ? IPV4_DST_ADDR : IPV6_DST_ADDR),
"ip", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(tmp_ip.print(buf, sizeof(buf))));
/* Custom information elements, Local, Blacklisted, Has Services and
* domain name */
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? SRC_ADDR_LOCAL : DST_ADDR_LOCAL,
"is_local", jsonbuf, sizeof(jsonbuf)),
json_object_new_boolean(addr->isLocalHost()));
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? SRC_ADDR_BLACKLISTED : DST_ADDR_BLACKLISTED,
"is_blacklisted", jsonbuf, sizeof(jsonbuf)),
json_object_new_boolean(addr->isBlacklistedAddress()));
if(get_cli_host()) {
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? SRC_ADDR_SERVICES : DST_ADDR_SERVICES,
"has_services", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(get_cli_host()->getServicesMap()));
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? SRC_NAME : DST_NAME, "domain", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(get_cli_host()->get_visual_name(buf, sizeof(buf))));
}
tmp_ip.reset();
}
if(use_nat) {
json_object *nat = json_object_new_object();
u_int16_t port = 0;
if(is_client) {
tmp_ip.set(getPostNATSrcIp());
port = getPostNATSrcPort();
} else {
tmp_ip.set(getPostNATDstIp());
port = getPostNATDstPort();
}
json_object_object_add(nat, "ip",
json_object_new_string(tmp_ip.print(buf, sizeof(buf))));
json_object_object_add(nat, "port",
json_object_new_int(port));
json_object_object_add(host_object, "nat", nat);
}
/* Geolocation info */
c = host ? host->get_country(buf, sizeof(buf)) : NULL;
if(c) {
json_object *geo = json_object_new_object();
json_object *location = json_object_new_object();
if(geo) {
json_object_object_add(geo, "country_name", json_object_new_string(c));
if(host && location) {
float latitude, longitude;
host->get_geocoordinates(&latitude, &longitude);
json_object_object_add(location, "lon",
json_object_new_double(longitude));
json_object_object_add(location, "lat",
json_object_new_double(latitude));
json_object_object_add(geo, "location", location);
}
json_object_object_add(host_object, "geo", geo);
}
}
u_int16_t port = 0;
if(use_nat)
port = is_client ? getPreNATSrcPort() : getPreNATDstPort();
else
port = is_client ? get_cli_port() : get_srv_port();
/* Type of Service */
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? SRC_TOS : DST_TOS,
"tos", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(getTOS(true)));
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? L4_SRC_PORT : L4_DST_PORT, "port", jsonbuf,
sizeof(jsonbuf)), json_object_new_int(port));
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? IN_PKTS : OUT_PKTS, "packets", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int64(is_client ? get_partial_packets_cli2srv()
: get_partial_packets_srv2cli()));
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? IN_BYTES : OUT_BYTES, "bytes", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int64(is_client ? get_partial_bytes_cli2srv()
: get_partial_bytes_srv2cli()));
json_object_object_add(host_object,
Utils::jsonLabel(TCP_FLAGS, "packets_retransmissions", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int64(is_client ? stats.get_cli2srv_tcp_retr()
: stats.get_srv2cli_tcp_retr()));
json_object_object_add(host_object,
Utils::jsonLabel(TCP_FLAGS, "packets_out_of_order", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int64(is_client ? stats.get_cli2srv_tcp_ooo()
: stats.get_srv2cli_tcp_ooo()));
json_object_object_add(host_object,
Utils::jsonLabel(TCP_FLAGS, "packets_lost", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(is_client ? stats.get_cli2srv_tcp_lost()
: stats.get_srv2cli_tcp_lost()));
if(vlanId > 0)
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? SRC_VLAN : DST_VLAN,
"vlan", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(vlanId));
if(tcp != NULL)
json_object_object_add(host_object,
Utils::jsonLabel(is_client ? CLIENT_NW_LATENCY_MS : SERVER_NW_LATENCY_MS,
"latency", jsonbuf, sizeof(jsonbuf)),
json_object_new_double(tcp->clientRTT3WH/2.));
json_object_object_add(my_object, is_client ? "client" : "server", host_object);
}
}
/* *************************************** */
void Flow::formatECSFlow(json_object *my_object) {
char buf[64];
time_t t;
const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();
struct tm *tm_info;
t = last_seen;
tm_info = gmtime(&t);
/*
strftime in the VS2013 library and earlier are not C99-conformant,
as they do not accept that format-specifier: MSDN VS2013 strftime page
https://msdn.microsoft.com/en-us/library/fe06s4ak.aspx
*/
strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S.0Z", tm_info);
/*
Dumping flows, using ECS Format
https://www.elastic.co/guide/en/ecs/current/index.html
*/
json_object_object_add(my_object, "@timestamp", json_object_new_string(buf));
json_object_object_add(my_object, "type",
json_object_new_string(ntop->getPrefs()->get_es_type()));
/* Formatting Client */
formatECSHost(my_object, true, cli_ip, cli_host);
/* Formatting Server */
formatECSHost(my_object, false, srv_ip, srv_host);
formatECSNetwork(my_object, cli_ip);
formatECSInterface(my_object);
formatECSObserver(my_object);
formatECSEvent(my_object);
formatECSAppProto(my_object);
formatECSExtraInfo(my_object);
if(json_info && json_object_object_length(json_info) > 0)
json_object_object_add(my_object, "json", json_object_get(json_info));
}
/* *************************************** */
void Flow::formatSyslogFlow(json_object *my_object) {
char buf[64], jsonbuf[64];
if(cli_host && cli_host->getMac() && !cli_host->getMac()->isNull())
json_object_object_add(
my_object,
Utils::jsonLabel(IN_SRC_MAC, "IN_SRC_MAC", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(Utils::formatMac(
cli_host ? cli_host->get_mac() : NULL, buf, sizeof(buf))));
if(srv_host && srv_host->getMac() && !srv_host->getMac()->isNull())
json_object_object_add(
my_object,
Utils::jsonLabel(OUT_DST_MAC, "OUT_DST_MAC", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(Utils::formatMac(srv_host ? srv_host->get_mac() : NULL, buf, sizeof(buf))));
if(isTLS() && protos.tls.ja4.client_hash)
json_object_object_add(my_object,
Utils::jsonLabel(JA4C_HASH, "JA4C_HASH", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(protos.tls.ja4.client_hash));
if(isSSH() && protos.ssh.hassh.client_hash)
json_object_object_add(
my_object,
Utils::jsonLabel(HASSHC_HASH, "HASSHC_HASH", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(protos.ssh.hassh.client_hash));
formatGenericFlow(my_object);
}
/* *************************************** */
void Flow::formatGenericFlow(json_object *my_object) {
char buf[64], jsonbuf[64], *c;
u_char community_id[200];
const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();
if(cli_ip) {
if(cli_ip->isIPv4()) {
json_object_object_add(my_object,
Utils::jsonLabel(IPV4_SRC_ADDR, "IPV4_SRC_ADDR", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(cli_ip->print(buf, sizeof(buf))));
} else if(cli_ip->isIPv6()) {
json_object_object_add(my_object,
Utils::jsonLabel(IPV6_SRC_ADDR, "IPV6_SRC_ADDR", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(cli_ip->print(buf, sizeof(buf))));
}
json_object_object_add(my_object,
Utils::jsonLabel(SRC_ADDR_LOCAL, "SRC_ADDR_LOCAL",
jsonbuf, sizeof(jsonbuf)),
json_object_new_boolean(cli_ip->isLocalHost()));
json_object_object_add(my_object,
Utils::jsonLabel(SRC_ADDR_BLACKLISTED, "SRC_ADDR_BLACKLISTED", jsonbuf,
sizeof(jsonbuf)),
json_object_new_boolean(cli_ip->isBlacklistedAddress()));
if(get_cli_host()) {
#ifdef FULL_SERIALIZATION
/* Custom information elements not supported (yet) by nProbe */
json_object_object_add(my_object,
Utils::jsonLabel(SRC_ADDR_SERVICES, "SRC_ADDR_SERVICES", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int(get_cli_host()->getServicesMap()));
#endif
json_object_object_add(my_object,
Utils::jsonLabel(SRC_NAME, "SRC_NAME", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(
get_cli_host()->get_visual_name(buf, sizeof(buf))));
}
}
if(srv_ip) {
if(srv_ip->isIPv4()) {
json_object_object_add(my_object,
Utils::jsonLabel(IPV4_DST_ADDR, "IPV4_DST_ADDR", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(srv_ip->print(buf, sizeof(buf))));
} else if(srv_ip->isIPv6()) {
json_object_object_add(my_object,
Utils::jsonLabel(IPV6_DST_ADDR, "IPV6_DST_ADDR", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(srv_ip->print(buf, sizeof(buf))));
}
json_object_object_add(my_object,
Utils::jsonLabel(DST_ADDR_LOCAL, "DST_ADDR_LOCAL",
jsonbuf, sizeof(jsonbuf)),
json_object_new_boolean(srv_ip->isLocalHost()));
json_object_object_add(my_object,
Utils::jsonLabel(DST_ADDR_BLACKLISTED, "DST_ADDR_BLACKLISTED", jsonbuf,
sizeof(jsonbuf)),
json_object_new_boolean(srv_ip->isBlacklistedAddress()));
if(get_srv_host()) {
/* Custom information elements not supported (yet) by nProbe */
#ifdef FULL_SERIALIZATION
json_object_object_add(my_object,
Utils::jsonLabel(DST_ADDR_SERVICES, "DST_ADDR_SERVICES", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int(get_srv_host()->getServicesMap()));
#endif
json_object_object_add(my_object,
Utils::jsonLabel(SRC_NAME, "DST_NAME", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(
get_srv_host()->get_visual_name(buf, sizeof(buf))));
}
}
json_object_object_add(my_object, Utils::jsonLabel(SRC_TOS, "SRC_TOS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(getTOS(true)));
json_object_object_add(my_object, Utils::jsonLabel(DST_TOS, "DST_TOS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(getTOS(false)));
json_object_object_add(my_object,
Utils::jsonLabel(L4_SRC_PORT, "L4_SRC_PORT", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(get_cli_port()));
json_object_object_add(my_object,
Utils::jsonLabel(L4_DST_PORT, "L4_DST_PORT", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(get_srv_port()));
json_object_object_add(my_object,
Utils::jsonLabel(PROTOCOL, "PROTOCOL", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(protocol));
if(((get_packets_cli2srv() + get_packets_srv2cli()) > NDPI_MIN_NUM_PACKETS)
|| (ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN)) {
json_object_object_add(my_object,
Utils::jsonLabel(L7_PROTO, "L7_PROTO", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(ndpiDetectedProtocol.proto.app_protocol));
json_object_object_add(my_object,
Utils::jsonLabel(L7_PROTO_NAME, "L7_PROTO_NAME", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(get_detected_protocol_name(buf, sizeof(buf))));
}
json_object_object_add(my_object,
Utils::jsonLabel(L7_PROTO_RISK, "L7_PROTO_RISK", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int64((u_int64_t)ndpi_flow_risk_bitmap));
if(ndpiFlowRiskName)
json_object_object_add(my_object,
Utils::jsonLabel(L7_PROTO_RISK_NAME, "L7_PROTO_RISK_NAME", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(ndpiFlowRiskName));
if(end_reason)
json_object_object_add(my_object,
Utils::jsonLabel(FLOW_END_REASON, "FLOW_END_REASON", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(end_reason));
if(collection && collection->wifi.wlan_ssid) {
char mac_buf[20];
json_object_object_add(my_object,
Utils::jsonLabel(WLAN_SSID, "WLAN_SSID", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(collection->wifi.wlan_ssid));
json_object_object_add(my_object,
Utils::jsonLabel(WTP_MAC_ADDRESS, "WTP_MAC_ADDRESS", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(Utils::formatMac(collection->wifi.wtp_mac_address, mac_buf,
sizeof(mac_buf))));
}
json_object_object_add(my_object,
Utils::jsonLabel(TCP_FLAGS, "TCP_FLAGS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(src2dst_tcp_flags | dst2src_tcp_flags));
json_object_object_add(my_object,
Utils::jsonLabel(TCP_FLAGS, "CLIENT_TCP_FLAGS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(src2dst_tcp_flags));
json_object_object_add(my_object,
Utils::jsonLabel(TCP_FLAGS, "SERVER_TCP_FLAGS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(dst2src_tcp_flags));
if(tcp != NULL) {
json_object_object_add(my_object,
Utils::jsonLabel(TCP_FLAGS, "IN_RETRANSMISSIONS",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(stats.get_cli2srv_tcp_retr()));
json_object_object_add(my_object,
Utils::jsonLabel(TCP_FLAGS, "OUT_RETRANSMISSIONS",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(stats.get_srv2cli_tcp_retr()));
json_object_object_add(my_object,
Utils::jsonLabel(TCP_FLAGS, "IN_OUT_OF_ORDER",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(stats.get_cli2srv_tcp_ooo()));
json_object_object_add(my_object,
Utils::jsonLabel(TCP_FLAGS, "OUT_OUT_OF_ORDER",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(stats.get_srv2cli_tcp_ooo()));
json_object_object_add(
my_object,
Utils::jsonLabel(TCP_FLAGS, "IN_LOST", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(stats.get_cli2srv_tcp_lost()));
json_object_object_add(
my_object,
Utils::jsonLabel(TCP_FLAGS, "OUT_LOST", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(stats.get_srv2cli_tcp_lost()));
json_object_object_add(
my_object,
Utils::jsonLabel(APPL_LATENCY_MS, "APPL_LATENCY_MS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(applLatencyMsec));
}
json_object_object_add(my_object, Utils::jsonLabel(IN_PKTS, "IN_PKTS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(get_partial_packets_cli2srv()));
json_object_object_add(my_object,
Utils::jsonLabel(IN_BYTES, "IN_BYTES", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(get_partial_bytes_cli2srv()));
json_object_object_add(my_object,
Utils::jsonLabel(OUT_PKTS, "OUT_PKTS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(get_partial_packets_srv2cli()));
json_object_object_add(my_object,
Utils::jsonLabel(OUT_BYTES, "OUT_BYTES", jsonbuf, sizeof(jsonbuf)),
json_object_new_int64(get_partial_bytes_srv2cli()));
json_object_object_add(my_object,
Utils::jsonLabel(FIRST_SWITCHED, "FIRST_SWITCHED",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int(get_partial_first_seen()));
json_object_object_add(my_object,
Utils::jsonLabel(LAST_SWITCHED, "LAST_SWITCHED",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int(get_partial_last_seen()));
if(json_info && json_object_object_length(json_info) > 0)
json_object_object_add(my_object, "json", json_object_get(json_info));
if(vlanId > 0)
json_object_object_add(my_object,
Utils::jsonLabel(SRC_VLAN, "SRC_VLAN", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(vlanId));
if(tcp != NULL) {
json_object_object_add(my_object,
Utils::jsonLabel(CLIENT_NW_LATENCY_MS, "CLIENT_NW_LATENCY_MS", jsonbuf,
sizeof(jsonbuf)),
json_object_new_double(tcp->clientRTT3WH/2.));
json_object_object_add(my_object,
Utils::jsonLabel(SERVER_NW_LATENCY_MS, "SERVER_NW_LATENCY_MS", jsonbuf,
sizeof(jsonbuf)),
json_object_new_double(tcp->serverRTT3WH/2.));
}
c = cli_host ? cli_host->get_country(buf, sizeof(buf)) : NULL;
if(c) {
json_object *location = json_object_new_array();
json_object_object_add(my_object, "SRC_IP_COUNTRY", json_object_new_string(c));
if(location && cli_host) {
float latitude, longitude;
cli_host->get_geocoordinates(&latitude, &longitude);
json_object_array_add(location, json_object_new_double(longitude));
json_object_array_add(location, json_object_new_double(latitude));
json_object_object_add(my_object, "SRC_IP_LOCATION", location);
}
}
c = srv_host ? srv_host->get_country(buf, sizeof(buf)) : NULL;
if(c) {
json_object *location = json_object_new_array();
json_object_object_add(my_object, "DST_IP_COUNTRY", json_object_new_string(c));
if(location && srv_host) {
float latitude, longitude;
srv_host->get_geocoordinates(&latitude, &longitude);
json_object_array_add(location, json_object_new_double(longitude));
json_object_array_add(location, json_object_new_double(latitude));
json_object_object_add(my_object, "DST_IP_LOCATION", location);
}
}
if(ntop->getPrefs() && ntop->getPrefs()->get_instance_name())
json_object_object_add(
my_object,
Utils::jsonLabel(NTOPNG_INSTANCE_NAME, "NTOPNG_INSTANCE_NAME", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(ntop->getPrefs()->get_instance_name()));
if(iface && iface->get_name())
json_object_object_add(my_object,
Utils::jsonLabel(INTERFACE_NAME, "INTERFACE_NAME",
jsonbuf, sizeof(jsonbuf)),
json_object_new_string(iface->get_name()));
if(isSMTP()
/* Discard SMTP connections that become TLS as the SMTP part is not populated */
&& (!isSMTPS())
) {
if(getSMTPMailFrom())
json_object_object_add(my_object,
Utils::jsonLabel(SMTP_MAIL_FROM, "SMTP_MAIL_FROM", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(getSMTPMailFrom()));
if(getSMTPRcptTo())
json_object_object_add(my_object,
Utils::jsonLabel(SMTP_RCPT_TO, "SMTP_RCPT_TO", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(getSMTPRcptTo()));
}
if(isDNS() && protos.dns.last_query)
json_object_object_add(my_object,
Utils::jsonLabel(DNS_QUERY, "DNS_QUERY", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(protos.dns.last_query));
json_object_object_add(my_object,
Utils::jsonLabel(COMMUNITY_ID, "COMMUNITY_ID", jsonbuf, sizeof(jsonbuf)),
json_object_new_string((char *)getCommunityId(community_id, sizeof(community_id))));
json_object_object_add(my_object,
Utils::jsonLabel(L7_RISK_SCORE, "L7_RISK_SCORE",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int(getScore()));
if(isHTTP()) {
if(host_server_name && host_server_name[0] != '\0')
json_object_object_add(my_object,
Utils::jsonLabel(HTTP_HOST, "HTTP_HOST", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(host_server_name));
if(protos.http.last_url && protos.http.last_url[0] != '\0')
json_object_object_add(my_object,
Utils::jsonLabel(HTTP_URL, "HTTP_URL", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(protos.http.last_url));
if(protos.http.last_user_agent && protos.http.last_user_agent[0] != '\0')
json_object_object_add(my_object,
Utils::jsonLabel(HTTP_USER_AGENT, "HTTP_USER_AGENT", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(protos.http.last_user_agent));
if(protos.http.last_server && protos.http.last_server[0] != '\0')
json_object_object_add(my_object,
Utils::jsonLabel(HTTP_SITE, "HTTP_SITE", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(protos.http.last_server));
if(protos.http.last_method != NDPI_HTTP_METHOD_UNKNOWN)
json_object_object_add(my_object,
Utils::jsonLabel(HTTP_METHOD, "HTTP_METHOD",
jsonbuf, sizeof(jsonbuf)),
json_object_new_string(ndpi_http_method2str(
protos.http.last_method)));
if(protos.http.last_return_code > 0)
json_object_object_add(my_object,
Utils::jsonLabel(HTTP_RET_CODE, "HTTP_RET_CODE", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int((u_int32_t)protos.http.last_return_code));
}
if(protocol == IPPROTO_ICMP) {
json_object_object_add(my_object,
Utils::jsonLabel(ICMP_IPV4_TYPE, "ICMP_IPV4_TYPE", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int((u_int32_t)protos.icmp.cli2srv.icmp_type));
json_object_object_add(my_object,
Utils::jsonLabel(ICMP_IPV4_CODE, "ICMP_IPV4_CODE", jsonbuf,
sizeof(jsonbuf)),
json_object_new_int((u_int32_t)protos.icmp.cli2srv.icmp_type));
}
if(flow_device.device_ip)
json_object_object_add(my_object,
Utils::jsonLabel(EXPORTER_IPV4_ADDRESS, "EXPORTER_IPV4_ADDRESS",
jsonbuf, sizeof(jsonbuf)),
json_object_new_string(
intoaV4(flow_device.device_ip, buf, sizeof(buf))));
if(bt_hash)
json_object_object_add(my_object,
Utils::jsonLabel(BITTORRENT_HASH, "BITTORRENT_HASH",
jsonbuf, sizeof(jsonbuf)),
json_object_new_string(bt_hash));
if(isTLS() && protos.tls.client_requested_server_name)
json_object_object_add(my_object,
Utils::jsonLabel(TLS_SERVER_NAME, "TLS_SERVER_NAME", jsonbuf,
sizeof(jsonbuf)),
json_object_new_string(protos.tls.client_requested_server_name));
#ifdef HAVE_NEDGE
if(iface && iface->is_bridge_interface()) {
json_object_object_add(my_object, "verdict.pass", /* TODO: convert to Utils::jsonLabel(..) */
json_object_new_boolean(isPassVerdict() ? (json_bool)1 : (json_bool)0));
json_object_object_add(my_object, "verdict.reason",
json_object_new_int((int) dropVerdictReason));
}
#else
if(!passVerdict)
json_object_object_add(my_object, "verdict.pass", /* TODO: convert to Utils::jsonLabel(..) */
json_object_new_boolean((json_bool)0));
#endif
if(ebpf) ebpf->getJSONObject(my_object);
if(ntop->getPrefs()->do_dump_extended_json()) {
#ifdef FULL_SERIALIZATION
const char *info;
char buf[128];
json_object_object_add(
my_object,
Utils::jsonLabel(FLOW_TIME, "FLOW_TIME", jsonbuf, sizeof(jsonbuf)),
json_object_new_int(last_seen));
#endif
if(cli_ip) {
if(cli_ip->isIPv4()) {
json_object_object_add(my_object,
Utils::jsonLabel(IP_PROTOCOL_VERSION, "IP_PROTOCOL_VERSION",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int(4));
} else if(cli_ip->isIPv6()) {
json_object_object_add(my_object,
Utils::jsonLabel(IP_PROTOCOL_VERSION, "IP_PROTOCOL_VERSION",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int(6));
}
}
#ifdef FULL_SERIALIZATION
json_object_object_add(my_object,
Utils::jsonLabel(L7_INFO, "INFO", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(getFlowInfo(false).c_str()));
#endif
#ifdef FULL_SERIALIZATION
#if defined(NTOPNG_PRO) && !defined(HAVE_NEDGE)
json_object_object_add(my_object,
Utils::jsonLabel(PROFILE, "PROFILE", jsonbuf, sizeof(jsonbuf)),
json_object_new_string(get_profile_name()));
#endif
#endif
json_object_object_add(my_object,
Utils::jsonLabel(INTERFACE_ID, "INTERFACE_ID",
jsonbuf, sizeof(jsonbuf)),
json_object_new_int(iface->get_id()));
json_object_object_add(
my_object, Utils::jsonLabel(STATUS, "STATUS", jsonbuf, sizeof(jsonbuf)),
json_object_new_int((u_int8_t)getPredominantAlert().id));
}
}
/* *************************************** */
json_object *Flow::flow2JSON(ExportFormat format) {
json_object *my_object;
my_object = json_object_new_object();
if (my_object == NULL)
return NULL;
if (format == export_format_ECS) {
/* Format for ElasticSearch */
formatECSFlow(my_object);
} else if (format == export_format_SYSLOG) {
/* Format for Syslog */
formatSyslogFlow(my_object);
} else {
/* Format for DB */
formatGenericFlow(my_object);
}
return (my_object);
}
/* *************************************** */
u_char *Flow::getCommunityId(u_char *community_id, u_int community_id_len) {
if(cli_host && srv_host) {
IpAddress *c = cli_host->get_ip(), *s = srv_host->get_ip();
u_int8_t icmp_type = 0, icmp_code = 0;
if(c->isIPv4()) {
if(get_protocol() == IPPROTO_ICMP)
icmp_type = protos.icmp.cli2srv.icmp_type,
icmp_code = protos.icmp.cli2srv.icmp_code;
if(ndpi_flowv4_flow_hash(protocol, ntohl(c->get_ipv4()),
ntohl(s->get_ipv4()), get_cli_port(),
get_srv_port(), icmp_type, icmp_code,
community_id, community_id_len) == 0)
return (community_id);
} else {
if(get_protocol() == IPPROTO_ICMPV6)
icmp_type = protos.icmp.cli2srv.icmp_type,
icmp_code = protos.icmp.cli2srv.icmp_code;
if(c->isIPv6()) {
if(ndpi_flowv6_flow_hash(
protocol, (struct ndpi_in6_addr *)c->get_ipv6(),
(struct ndpi_in6_addr *)s->get_ipv6(), get_cli_port(),
get_srv_port(), icmp_type, icmp_code, community_id,
community_id_len) == 0)
return (community_id);
}
}
}
community_id[0] = '\0';
return (community_id);
}
/* *************************************** */
/* Create a JSON in the alerts format
* Using the nDPI json serializer instead of jsonc for faster speed (~2.5x) */
void Flow::alert2JSON(FlowAlert *alert, ndpi_serializer *s) {
char buf[128];
u_char community_id[200];
time_t now = time(NULL);
/*
If the interface is viewed, the id of the view interface is specified as
ifid. This ensures flow alerts of any viewed interface end up in the view
interface, thus giving the user a single point where to look at all the
troubles.
*/
#ifdef NTOPNG_PRO
ndpi_serialize_string_int32(s, "ifid",
iface->isViewed() ? iface->viewedBy()->get_id() : iface->get_id());
#else
ndpi_serialize_string_int32(s, "ifid", iface->get_id());
#endif
ndpi_serialize_string_string(s, "action", "store");
ndpi_serialize_string_int64(s, "first_seen", get_first_seen());
ndpi_serialize_string_int32(s, "score", getScore());
ndpi_serialize_string_boolean(s, "require_attention", !alert->autoAck());
ndpi_serialize_string_boolean(s, "is_flow_alert", true);
ndpi_serialize_string_int64(s, "tstamp", now);
ndpi_serialize_string_int64(s, "alert_id", alert->getAlertType().id);
ndpi_serialize_string_int64(s, "alert_category", alert->getAlertType().category);
ndpi_serialize_string_boolean(s, "is_cli_attacker", alert->isCliAttacker());
ndpi_serialize_string_boolean(s, "is_cli_victim", alert->isCliVictim());
ndpi_serialize_string_boolean(s, "is_srv_attacker", alert->isSrvAttacker());
ndpi_serialize_string_boolean(s, "is_srv_victim", alert->isSrvVictim());
// alert_entity MUST be in sync with alert_consts.lua flow alert entity
ndpi_serialize_string_int32(s, "entity_id", alert_entity_flow);
ndpi_serialize_string_string(s, "entity_val", "flow");
// flows don't have any pool for now
ndpi_serialize_string_int32(s, "pool_id", NO_HOST_POOL_ID);
/* See VLANAddressTree.h for details */
ndpi_serialize_string_int32(s, "vlan_id", get_vlan_id());
ndpi_serialize_string_int32(s, "observation_point_id",
get_observation_point_id());
ndpi_serialize_string_int32(s, "proto", get_protocol());
if(hasRisks())
ndpi_serialize_string_uint64(s, "flow_risk_bitmap", ndpi_flow_risk_bitmap);
/* All the statuses set */
char status_buf[64];
ndpi_serialize_string_string(s, "alerts_map", alerts_map.toHexString(status_buf, sizeof(status_buf)));
/* nDPI data */
ndpi_serialize_string_string(s, "proto.ndpi",
detection_completed ? get_detected_protocol_name(buf, sizeof(buf))
: (char *)CONST_TOO_EARLY);
ndpi_serialize_string_int32(s, "l7_master_proto",
detection_completed ? ndpiDetectedProtocol.proto.master_protocol : -1);
ndpi_serialize_string_int32(s, "l7_proto",
detection_completed ? ndpiDetectedProtocol.proto.app_protocol : -1);
ndpi_serialize_string_int32(s, "l7_cat", get_protocol_category());
if(isDNS()) ndpi_serialize_string_string(s, "dns_last_query", getDNSQuery());
ndpi_serialize_string_int64(s, "cli2srv_bytes", get_bytes_cli2srv());
ndpi_serialize_string_int64(s, "cli2srv_packets", get_packets_cli2srv());
ndpi_serialize_string_int64(s, "srv2cli_bytes", get_bytes_srv2cli());
ndpi_serialize_string_int64(s, "srv2cli_packets", get_packets_srv2cli());
ndpi_serialize_string_int32(s, "ip_version", get_cli_ip_addr()->getVersion());
ndpi_serialize_string_string(s, "cli_ip",
get_cli_ip_addr()->print(buf, sizeof(buf)));
ndpi_serialize_string_boolean(s, "cli_blacklisted", isBlacklistedClient());
ndpi_serialize_string_int32(s, "cli_port", get_cli_port());
ndpi_serialize_string_int32(s, "cli_location", getCliLocation());
if(cli_host) {
cli_host->serialize_geocoordinates(s, "cli_");
ndpi_serialize_string_string(s, "cli_name",
cli_host->get_visual_name(buf, sizeof(buf)));
ndpi_serialize_string_string(s, "cli_os",
cli_host->getOSDetail(buf, sizeof(buf)));
ndpi_serialize_string_int32(s, "cli_asn", cli_host->get_asn());
ndpi_serialize_string_boolean(s, "cli_localhost", cli_host->isLocalHost());
ndpi_serialize_string_int32(s, "cli_host_pool_id",
cli_host->get_host_pool());
ndpi_serialize_string_uint32(s, "cli_network",
(u_int32_t)cli_host->get_local_network_id());
}
ndpi_serialize_string_string(s, "srv_ip",
get_srv_ip_addr()->print(buf, sizeof(buf)));
ndpi_serialize_string_boolean(s, "srv_blacklisted", isBlacklistedServer());
ndpi_serialize_string_int32(s, "srv_port", get_srv_port());
ndpi_serialize_string_int32(s, "srv_location", getSrvLocation());
if(srv_host) {
srv_host->serialize_geocoordinates(s, "srv_");
ndpi_serialize_string_string(s, "srv_name",
srv_host->get_visual_name(buf, sizeof(buf)));
ndpi_serialize_string_string(s, "srv_os",
srv_host->getOSDetail(buf, sizeof(buf)));
ndpi_serialize_string_int32(s, "srv_asn", srv_host->get_asn());
ndpi_serialize_string_boolean(s, "srv_localhost", srv_host->isLocalHost());
ndpi_serialize_string_int32(s, "srv_host_pool_id",
srv_host->get_host_pool());
ndpi_serialize_string_uint32(s, "srv_network",
(u_int32_t)srv_host->get_local_network_id());
}
ndpi_serialize_string_string(s, "probe_ip", Utils::intoaV4(getFlowDeviceIP(), buf, sizeof(buf)));
ndpi_serialize_string_int32(s, "input_snmp", getFlowDeviceInIndex());
ndpi_serialize_string_int32(s, "output_snmp", getFlowDeviceOutIndex());
ndpi_serialize_string_string(s, "community_id",
(char *)getCommunityId(community_id, sizeof(community_id)));
if(protos.tls.ja4.client_hash)
ndpi_serialize_string_string(s, "ja4_client_hash", protos.tls.ja4.client_hash);
if(getErrorCode() != 0)
ndpi_serialize_string_uint32(s, "l7_error_code", getErrorCode());
ndpi_serialize_string_string(s, "info", getFlowInfo(false).c_str());
char *json = alerts_json; /* Copying ref as it may be moved to the shadow meantime */
ndpi_serialize_string_string(s, "json", json ? json : "");
}
/* *************************************** */
void Flow::decAllFlowScores() {
Host *cli_u = getViewSharedClient(), *srv_u = getViewSharedServer();
for (int i = 0; i < MAX_NUM_SCORE_CATEGORIES; i++) {
ScoreCategory score_category = (ScoreCategory)i;
u_int16_t cli_score_val = stats.get_cli_score(score_category);
u_int16_t srv_score_val = stats.get_srv_score(score_category);
#ifdef NTOPNG_PRO
if(getViewInterfaceFlowStats()) {
/*
If this flow belong to a view, the actual score value is the one
registered in the partializable stats of the view.
*/
cli_score_val = getViewInterfaceFlowStats()->getPartializableStats()->get_cli_score(score_category);
srv_score_val = getViewInterfaceFlowStats()->getPartializableStats()->get_srv_score(score_category);
}
#endif
if(cli_u && cli_score_val)
cli_u->decScoreValue(cli_score_val, score_category, true /* as client */);
if(srv_u && srv_score_val)
srv_u->decScoreValue(srv_score_val, score_category, false /* as server */);
}
/*
Perform other operations to decrease counters increased by flow user script
hooks (we're in the same thread)
*/
if(isFlowAlerted()) {
bool dec_stats = false;
iface->decNumAlertedFlows(this, Utils::mapScoreToSeverity(getPredominantAlertScore()));
#ifdef NTOPNG_PRO
if(!getInterface()->isViewed() /* Always for non-viewed interfaces (increments are
always performed and in the same thread) */
/*
For viewed interfaces, do the decrement only if previously
incremented. A previous increment can fail when the
view flows queue is full and enqueues fail.
*/
|| (getViewInterfaceFlowStats() && getViewInterfaceFlowStats()->getPartializableStats()->get_is_flow_alerted())) {
dec_stats = true;
}
#else
dec_stats = true;
#endif
if(dec_stats) {
if(cli_u) cli_u->decNumAlertedFlows(true /* As client */);
if(srv_u) srv_u->decNumAlertedFlows(false /* As server */);
}
#ifdef ALERTED_FLOWS_DEBUG
iface_alert_dec = true;
#endif
}
}
/* *************************************** */
/*
This method is executed in the thread which processes packets/flows
so it must be ultra-fast. Do NOT perform any time-consuming operation here.
This is called by GenericHash::purgeIdle, in some cases there is a state
transition after calling this (see purgeIdle), in other cases the transition
is done in this class (e.g. set_hash_entry_state_flow_notyetdetected)
*/
void Flow::housekeep(time_t t) {
switch (get_state()) {
case hash_entry_state_allocated:
/* This code can be executed multiple times, until there is a call to
* set_hash_entry_state_flow_notyetdetected */
case hash_entry_state_flow_notyetdetected:
/*
Possibly the time to giveup and end the protocol dissection.
This happens when a flow with an incomplete TWH stops receiving packets
for example.
Giveup is handled differently, depending on whether we are processing
pcap files or not:
- When not reading from pcap files, before giving up, we wait at least 5
seconds since the last flow packet
- When reading from pcap files, we wait until all the packets in the
file have been processed before ending the dissection
*/
if(iface->get_ndpi_struct() && get_ndpi_flow()) {
if(likely(!getInterface()->read_from_pcap_dump())) {
/* NOT processing pcap files, at most 5 seconds since the last packet */
if((t - get_last_seen()) > 5 /* sec */) endProtocolDissection(true);
} else {
/* pcap files - wait until all the file has been processed */
if(getInterface()->read_from_pcap_dump_done())
endProtocolDissection(true);
}
}
/*
If a condition above has determined the flow trasnition to the protocol
detected state, don't break, continue so to execute detection completed
checks.
*/
if(get_state() != hash_entry_state_flow_protocoldetected)
break;
case hash_entry_state_flow_protocoldetected:
if(!is_swap_requested()) /* The flow will be swapped, hook execution will
occur on the swapped flow. */
iface->execProtocolDetectedChecks(this);
break;
case hash_entry_state_active:
/*
The hook for periodicUpdate is checked when increasing flow stats inline
to guarantee timely execution.
hookPeriodicUpdateCheck(t);
*/
dumpCheck(t, false /* NOT the last dump before delete */);
/*
Swap requested but not yet performed (no more packets seen).
In case of interfaces processing pcap files, if the processing of the
pcap file is completed, i.e. read_from_pcap_dump_done() is true, then
there will be no more packets so the flow swap won't take place. For
this reason, the callbacks are executed on the original flow that should
have been swapped but actually it is not.
*/
if(getInterface()->read_from_pcap_dump_done()
&& is_swap_requested() && !is_swap_done())
iface->execProtocolDetectedChecks(this);
break;
case hash_entry_state_idle:
/* This code is executed once, as the flow is removed from the hash table
* after calling housekeep in this state */
if(is_swap_requested() &&
!is_swap_done()) /* Swap requested but never performed (no more
packets seen) */
iface->execProtocolDetectedChecks(this);
if(!is_swap_requested() /* Swap not requested */
|| (is_swap_requested() &&
!is_swap_done())) /* Or requested but never performed (no more
packets seen) */ {
iface->execFlowEndChecks(this);
}
dumpCheck(t, true /* LAST dump before delete */);
flow_end_stats_update();
#ifdef NTOPNG_PRO
if(cli_host && srv_host) {
u_int32_t cli_net_id = cli_host->get_local_network_id(),
srv_net_id = srv_host->get_local_network_id();
if(cli_net_id != (u_int32_t)-1 && srv_net_id != (u_int32_t)-1 &&
cli_net_id != srv_net_id) {
NetworkStats *cli_network_stats = iface->getNetworkStats(cli_net_id),
*srv_network_stats = iface->getNetworkStats(srv_net_id);
if(cli_network_stats)
cli_network_stats->incTrafficBetweenNets(
srv_net_id, get_bytes_cli2srv(), get_bytes_srv2cli());
if(srv_network_stats)
srv_network_stats->incTrafficBetweenNets(
cli_net_id, get_bytes_srv2cli(), get_bytes_cli2srv());
#ifdef DEBUG
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"Cli Network ID: %u | Srv Network ID: "
"%u | Bytes: %lu | Num Loc Nets: %u",
cli_net_id, srv_net_id, get_bytes(),
ntop->getNumLocalNetworks());
#endif
}
}
#endif
/*
Score decrements MUST be performed here as this is the same thread of
checks execution where scores are increased. NOTE: for view interfaces,
decrement are performed in ~Flow to avoid races.
*/
#ifdef NTOPNG_PRO
if(!getInterface()->isViewed()) decAllFlowScores();
#else
decAllFlowScores();
#endif
switch (protocol) {
case IPPROTO_TCP:
if(cli_host && ((getTcpFlagsCli2Srv() == TH_SYN) || (!non_zero_payload_observed)))
cli_host->incIncompleteFlows();
break;
case IPPROTO_UDP:
if(cli_host && (get_packets_srv2cli() == 0) /* unidirectional flow */
&& srv_ip_addr && srv_ip_addr->isNonEmptyUnicastAddress())
cli_host->incIncompleteFlows();
break;
}
break;
default:
break;
} /* switch */
/*
Check (and possibly enqueue) the flow for processing by a view interface.
Make sure to enqueue the flow to view interfaces AFTER all housekeeping
tasks have been performed. This guarantees any change set by these
operations (e.g., changes in the flow status, flow alerts, etc.) are done
before the flow is propagated to the view.
*/
getInterface()->viewEnqueue(t, this);
}
/* *************************************** */
/**
* Returns partial traffic stats by computing a delta between current flow stats ("stats")
* and the previous snapshot ("dst").
* Note:
* - Delta is returned in "fts"
* - "dst" is updated taking the "stats" value
*/
bool Flow::get_partial_traffic_stats(PartializableFlowTrafficStats **dst,
PartializableFlowTrafficStats *fts,
bool *first_partial) const {
if(!fts || !dst) return (false);
if(!*dst) {
if((*dst = new (std::nothrow) PartializableFlowTrafficStats()) == NULL)
return (false);
*first_partial = true;
} else {
*first_partial = false;
}
stats.get_partial(*dst, fts);
return (true);
}
/* *************************************** */
/* NOTE: this is only called by the ViewInterface */
#ifdef NTOPNG_PRO
bool Flow::get_partial_traffic_stats_view(PartializableFlowTrafficStats *fts,
bool *first_partial) {
if(!fts) return (false);
if(!viewFlowStats) {
if(!(viewFlowStats = new (std::nothrow) ViewInterfaceFlowStats()))
return (false);
*first_partial = true;
} else
*first_partial = false;
stats.get_partial(viewFlowStats->getPartializableStats(), fts);
return (true);
}
#endif
/* *************************************** */
bool Flow::update_partial_traffic_stats_db_dump() {
bool first_partial;
if(!get_partial_traffic_stats(&last_db_dump.partial, &last_db_dump.delta,
&first_partial))
return (false);
if(first_partial)
last_db_dump.first_seen = get_first_seen();
else
last_db_dump.first_seen = last_db_dump.last_seen;
last_db_dump.last_seen = get_last_seen();
return (true);
}
/* *************************************** */
void Flow::updatePacketStats(InterarrivalStats *stats,
const struct timeval *when, bool update_iat) {
if(stats) stats->updatePacketStats((struct timeval *)when, update_iat);
}
/* *************************************** */
bool Flow::isBlacklistedFlow() const {
bool res = (isBlacklistedClient() || isBlacklistedServer() ||
get_protocol_category() == NDPI_PROTOCOL_CATEGORY_MALWARE);
#ifdef BLACKLISTED_FLOWS_DEBUG
if(res) {
char buf[512];
print(buf, sizeof(buf));
snprintf(&buf[strlen(buf)], sizeof(buf) - strlen(buf),
"[cli_blacklisted: %u][srv_blacklisted: %u][category: %s][blacklist: %s]",
isBlacklistedClient(), isBlacklistedServer(),
get_protocol_category_name(),
get_custom_category_file() ? get_custom_category_file() : "");
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s", buf);
}
#endif
return res;
};
/* *************************************** */
bool Flow::isBlacklistedClient() const {
if(cli_host)
return cli_host->isBlacklisted();
else
return get_cli_ip_addr()->isBlacklistedAddress();
}
/* *************************************** */
bool Flow::isBlacklistedServer() const {
if(srv_host)
return srv_host->isBlacklisted();
else
return get_srv_ip_addr()->isBlacklistedAddress();
}
/* *************************************** */
bool Flow::isTLS() const {
return (isProto(NDPI_PROTOCOL_TLS) ||
isProto(NDPI_PROTOCOL_MAIL_IMAPS) ||
isProto(NDPI_PROTOCOL_MAIL_SMTPS) ||
isProto(NDPI_PROTOCOL_MAIL_POPS) ||
isProto(NDPI_PROTOCOL_QUIC));
}
/* *************************************** */
void Flow::callFlowUpdate(time_t t) {
if(get_state() != hash_entry_state_active) return;
/*
Flow update is conditional here as it is only performed every 5 minutes when
the flow is active.
*/
if(next_call_periodic_update == 0)
next_call_periodic_update =
t + FLOW_LUA_CALL_PERIODIC_UPDATE_SECS; /* Set the time of the new
periodic call */
if(trigger_immediate_periodic_update || next_call_periodic_update <= t) {
iface->execPeriodicUpdateChecks(this);
next_call_periodic_update = 0; /* Reset */
if(trigger_immediate_periodic_update)
trigger_immediate_periodic_update = false; /* Reset if necessary */
}
}
/* *************************************** */
/* Enqueue alert to recipients */
bool Flow::enqueueAlertToRecipients(FlowAlert *alert) {
bool first_alert = isFlowAlerted();
bool rv = false;
u_int32_t buflen;
AlertFifoItem *notification;
ndpi_serializer flow_json;
const char *flow_str;
const char *instance_name;
ndpi_init_serializer(&flow_json, ndpi_serialization_format_json);
/* Prepare the JSON, including a JSON specific of this FlowAlertType */
alert2JSON(alert, &flow_json);
if(!first_alert)
ndpi_serialize_string_boolean(&flow_json, "replace_alert", true);
flow_str = ndpi_serializer_get_buffer(&flow_json, &buflen);
notification = new AlertFifoItem();
if(notification) {
notification->alert = flow_str;
notification->score = getPredominantAlertScore();
notification->alert_severity = Utils::mapScoreToSeverity(notification->score);
notification->alert_category = alert->getAlertType().category;
notification->alert_id = alert->getAlertType().id;
notification->flow.cli_host_pool =
cli_host ? cli_host->get_host_pool() : 0;
notification->flow.srv_host_pool =
srv_host ? srv_host->get_host_pool() : 0;
notification->alert_entity = alert_entity_flow;
rv = ntop->recipients_enqueue(notification);
if(!rv)
delete(notification);
if(iface->isSmartRecordingEnabled()
&& (instance_name = iface->getSmartRecordingInstance())) {
char key[256], ip_buf[64];
int expiration = 30*60; /* 30 min */
snprintf(key, sizeof(key), "n2disk.%s.filter.tuple.%s,%s,%u,%u,%u", instance_name,
get_cli_ip_addr()->print(ip_buf, sizeof(ip_buf)),
get_srv_ip_addr()->print(ip_buf, sizeof(ip_buf)),
cli_port, srv_port, protocol);
ntop->getRedis()->set(key, "1", expiration);
}
}
if(!rv)
getInterface()->incNumDroppedAlerts(alert_entity_flow);
ndpi_term_serializer(&flow_json);
return rv;
}
/* *************************************** */
/* Update server -> client in case no traffic was already observed on the server side */
void Flow::updateServerPortsStats(Host *server, ndpi_protocol *proto, time_t when_seen) {
/*
NOTE use the method parameter 'server' instead of
srv_host as with view interfaces it can be invalid
*/
if(get_bytes_srv2cli() == 0) return;
if(server && server->isLocalHost()
// && (get_bytes_srv2cli() == 0)
) {
switch (protocol) {
case IPPROTO_TCP:
if(((src2dst_tcp_flags & TH_SYN) == TH_SYN)
/* Ignore connections refused */
&& ((dst2src_tcp_flags & TH_RST) == 0)
) {
if(vlanId == 0) /* In case the VLAN is 0 set this port to the network interface */
iface->setServerPort(true, ntohs(srv_port), proto);
else { /* Otherwise set this port to the right VLAN */
VLAN *vlan_hash = iface->getVLAN(vlanId, false, false);
if(vlan_hash)
vlan_hash->setServerPort(true, ntohs(srv_port), proto);
}
server->setServerPort(true, ntohs(srv_port), proto, first_seen);
}
break;
case IPPROTO_UDP: {
u_int16_t c_port = ntohs(cli_port);
u_int16_t s_port = ntohs(srv_port);
if(c_port > s_port) /* minimal check, to improve */ {
if(vlanId == 0) /* In case the VLAN is 0 set this port to the network
interface */
iface->setServerPort(false, s_port, proto);
else { /* Otherwise set this port to the right VLAN */
VLAN *vlan_hash = iface->getVLAN(vlanId, false, false);
if(vlan_hash) vlan_hash->setServerPort(false, s_port, proto);
}
server->setServerPort(false, s_port, proto, first_seen);
}
break;
}
}
}
}
/* *************************************** */
void Flow::updateClientContactedPorts(Host *client, ndpi_protocol *proto) {
/*
NOTE use the method parameter 'client' instead of
cli_host as with view interfaces it can be invalid
*/
if(client->isLocalHost()) {
switch (protocol) {
case IPPROTO_TCP:
if((src2dst_tcp_flags & TH_SYN) == TH_SYN)
client->setContactedPort((protocol == IPPROTO_TCP), ntohs(srv_port), proto);
break;
case IPPROTO_UDP: {
u_int16_t c_port = ntohs(cli_port);
u_int16_t s_port = ntohs(srv_port);
if(c_port > s_port) /* minimal check, to improve */
client->setContactedPort(false, s_port, proto);
} break;
}
}
}
/* *************************************** */
/*
* Called by NetworkInterface::processPacket to increase flow stats for each packet
* Note: this is NOT called on nEdge (see Flow::setPacketsBytes called by NetfilterInterface::updateFlowStats)
*/
void Flow::incStats(bool cli2srv_direction, u_int pkt_len, u_int8_t *payload,
u_int payload_len, u_int8_t l4_proto, u_int8_t is_fragment,
u_int16_t tcp_flags, const struct timeval *when,
u_int16_t fragment_extra_overhead) {
bool update_iat = true;
payload_len *= iface->getScalingFactor();
updateSeen(when->tv_sec);
if(fragment_extra_overhead) {
/* Add artificial packet overhead */
stats.incStats(cli2srv_direction, 1, fragment_extra_overhead,
fragment_extra_overhead);
}
callFlowUpdate((when->tv_sec));
/*
Do not update IAT during initial or final 3WH as we want to compute
it only on the main traffic flow and not on connection or tear-down
*/
if((l4_proto == IPPROTO_TCP) && (tcp_flags & (TH_SYN | TH_FIN | TH_RST)))
update_iat = false;
#ifdef ENABLE_ENTROPHY_CALCULATION
if((protocol == IPPROTO_ICMP) &&
(get_packets() < 10) /* Compute only on the first few flow packets */
&& ((protos.icmp.cli2srv.icmp_type == 0) ||
(protos.icmp.cli2srv.icmp_type == 8)) /* Echo Request or Reply */
&& cli2srv_direction && (payload != NULL) && (payload_len > 0)) {
/*
We compute cli->srv entropy to see how much
packets are different and see if they are
repetitions or not
*/
struct ndpi_analyze_struct e;
float res;
ndpi_init_data_analysis(&e, 256);
updateEntropy(&e, payload, payload_len);
res = ndpi_data_entropy(&e);
if(protos.icmp.client_to_server.min_entropy == 0)
protos.icmp.client_to_server.min_entropy =
protos.icmp.client_to_server.max_entropy = res;
else {
if(protos.icmp.client_to_server.min_entropy > res)
protos.icmp.client_to_server.min_entropy = res;
else if(protos.icmp.client_to_server.max_entropy < res)
protos.icmp.client_to_server.max_entropy = res;
}
/* See icmp_utils.is_suspicious_entropy() */
if((protos.icmp.client_to_server.min_entropy < 5) ||
(protos.icmp.client_to_server.max_entropy > 6) ||
((protos.icmp.client_to_server.max_entropy -
protos.icmp.client_to_server.min_entropy) > 0.3)) {
ndpi_risk r = ((ndpi_risk)2) << (NDPI_SUSPICIOUS_ENTROPY - 1);
addRisk(r);
}
#ifdef DEBUG
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Entropy %.23f - %.23f",
protos.icmp.client_to_server.min_entropy,
protos.icmp.client_to_server.max_entropy);
#endif
ndpi_free_data_analysis(&e, 0);
}
#endif
updatePacketStats(cli2srv_direction ? getCli2SrvIATStats() : getSrv2CliIATStats(), when,
update_iat);
stats.incStats(cli2srv_direction, 1, pkt_len, payload_len);
if(cli2srv_direction) {
ip_stats_s2d.pktFrag += is_fragment;
if(cli_host) cli_host->incSentStats(1, pkt_len);
if(srv_host) srv_host->incRecvStats(1, pkt_len);
} else {
ip_stats_d2s.pktFrag += is_fragment;
if(cli_host) cli_host->incRecvStats(1, pkt_len);
if(srv_host) srv_host->incSentStats(1, pkt_len);
/*
Need to reset this bit as nDPI might "forget" to do it in case of
protocol detection with only one packet
*/
ndpi_flow_risk_bitmap &= ~(1UL << NDPI_UNIDIRECTIONAL_TRAFFIC); /* Clear bit */
}
if(payload_len > 0) {
#ifdef ENABLE_ENTROPHY_CALCULATION
if(cli2srv_direction) {
if(get_bytes_cli2srv() < MAX_ENTROPY_BYTES)
updateEntropy(initial_bytes_entropy.c2s, payload, payload_len);
} else {
if(get_bytes_srv2cli() < MAX_ENTROPY_BYTES)
updateEntropy(initial_bytes_entropy.s2c, payload, payload_len);
}
#endif
if(applLatencyMsec == 0) {
if(cli2srv_direction) {
memcpy(&c2sFirstGoodputTime, when, sizeof(struct timeval));
} else {
if(c2sFirstGoodputTime.tv_sec != 0)
applLatencyMsec =
((float)(Utils::timeval2usec((struct timeval *)when) -
Utils::timeval2usec(&c2sFirstGoodputTime))) /
1000;
}
}
}
}
/* *************************************** */
bool Flow::addFlowStats(bool new_flow, bool cli2srv_direction, u_int in_pkts,
u_int in_bytes, u_int in_goodput_bytes, u_int out_pkts,
u_int out_bytes, u_int out_goodput_bytes,
u_int in_fragments, u_int out_fragments,
time_t first_seen, time_t last_seen) {
/* Don't update seen if no traffic has been observed */
if(!(in_bytes || out_bytes || in_pkts || out_pkts))
return(false);
updateSeen(last_seen);
callFlowUpdate(last_seen);
if(cli2srv_direction) {
if(in_pkts) stats.incStats(true, in_pkts, in_bytes, in_goodput_bytes);
if(out_pkts) stats.incStats(false, out_pkts, out_bytes, out_goodput_bytes);
ip_stats_s2d.pktFrag += in_fragments, ip_stats_d2s.pktFrag += out_fragments;
} else {
if(out_pkts) stats.incStats(true, out_pkts, out_bytes, out_goodput_bytes);
if(in_pkts) stats.incStats(false, in_pkts, in_bytes, in_goodput_bytes);
ip_stats_s2d.pktFrag += out_fragments, ip_stats_d2s.pktFrag += in_fragments;
}
#if 0
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[New flow: %s][first: %u][last: %u][get_last_seen: %u][%u][%u][in_bytes: %u][out_bytes: %u][bytes : %u][thpt: %.2f]",
(new_flow ? "YES" : "NO"),
first_seen, last_seen,
get_last_seen(),
last_seen - first_seen,
last_seen - get_last_seen(),
in_bytes,
out_bytes,
in_bytes + out_bytes,
((in_bytes + out_bytes) / difftime(last_seen, first_seen)) / 1024 / 1024 * 8);
#endif
/* New flow, update throughput, otherwise update it in the periodic update */
//if(new_flow)
{
float tdiff_msec = (float)(ndpi_max(1, (last_seen-first_seen))) * 1000.;
if(cli2srv_direction)
updateThroughputStats(tdiff_msec, in_pkts, in_bytes, 0, out_pkts, out_bytes, 0);
else
updateThroughputStats(tdiff_msec, out_pkts, out_bytes, 0, in_pkts, in_bytes, 0);
}
return(true);
}
/* *************************************** */
void Flow::updateTcpSeqIssues(const ParsedFlow *pf) {
stats.incTcpStats(true /* src2dst */, pf->tcp.retr_in_pkts,
pf->tcp.ooo_in_pkts, pf->tcp.lost_in_pkts,
0 /* keepalive not supported */);
stats.incTcpStats(false /* dst2src */, pf->tcp.retr_out_pkts,
pf->tcp.ooo_out_pkts, pf->tcp.lost_out_pkts,
0 /* keepalive not supported */);
}
/* *************************************** */
void Flow::updateTcpWindow(u_int16_t window, bool src2dst_direction) {
/* The update depends on the direction of the flow */
if(window == 0) {
if(src2dst_direction)
src2dst_tcp_zero_window = 1;
else
dst2src_tcp_zero_window = 1;
}
}
/* *************************************** */
void Flow::updateICMPFlood(const struct bpf_timeval *when,
bool src2dst_direction) {
if(cli_host)
cli_host->updateICMPAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateICMPAlertsCounter(when->tv_sec, !src2dst_direction);
}
/* *************************************** */
void Flow::updateDNSFlood(const struct bpf_timeval *when,
bool src2dst_direction) {
if(cli_host)
cli_host->updateDNSAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateDNSAlertsCounter(when->tv_sec, !src2dst_direction);
}
/* *************************************** */
void Flow::updateSNMPFlood(const struct bpf_timeval *when,
bool src2dst_direction) {
if(cli_host)
cli_host->updateSNMPAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateSNMPAlertsCounter(when->tv_sec, !src2dst_direction);
}
/* *************************************** */
void Flow::updateTcpFlags(const struct bpf_timeval *when, u_int8_t flags,
bool src2dst_direction, bool new_flow) {
NetworkStats *cli_network_stats = NULL, *srv_network_stats = NULL;
bool is_packet_interface = getInterface()->isPacketInterface();
/* Flags used for the analysis of the 3WH. Original flags are masked for this
analysis to ignore certain bits such as ECE or CWR which may be present
during a valid 3WH. See https://github.com/ntop/ntopng/issues/3255 */
u_int8_t flags_3wh = flags & TCP_3WH_MASK;
#ifdef DEBUG
char buf[64];
ntop->getTrace()->traceEvent(TRACE_WARNING, "->> %s", printTCPflags(flags_3wh, buf, sizeof(buf)));
#endif
iface->incFlagStats(flags, !is_packet_interface);
if(cli_host) {
cli_host->incFlagStats(src2dst_direction, flags, !is_packet_interface);
cli_network_stats = cli_host->getNetworkStats(cli_host->get_local_network_id());
}
if(srv_host) {
srv_host->incFlagStats(!src2dst_direction, flags, !is_packet_interface);
srv_network_stats = srv_host->getNetworkStats(srv_host->get_local_network_id());
}
if(!is_packet_interface) {
/*
nProbe
Main principles
- With flows we can consider *only* flows with a single flag (e.g. SYN or RST)
Flows with multiple flags set should be ignored for alerts and flow state as we don't know
their number but just their presence
- As flows can be bi-directional, we need to pay attention to direction flags as they might be wrong
*/
/* Update syn alerts counters. In case of cumulative flags, the AND is used as possibly other flags can be present */
/* We have received a SYN flag not observed before for this flow */
if((flags == TH_SYN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_SYN) != TH_SYN)) {
if(cli_host)
cli_host->updateSynAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateSynAlertsCounter(when->tv_sec, !src2dst_direction);
if(cli_network_stats)
cli_network_stats->updateSynAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_network_stats)
srv_network_stats->updateSynAlertsCounter(when->tv_sec, !src2dst_direction);
}
/* We have received a RST flag not observed before for this flow */
if((flags == TH_RST) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_RST) != TH_RST)) {
iface->getTcpFlowStats()->incReset();
if(cli_host)
cli_host->updateRstAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateRstAlertsCounter(when->tv_sec, !src2dst_direction);
}
/* We have received a FIN flag not observed before for this flow */
if((flags == TH_FIN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_FIN) != TH_FIN)) {
if(cli_host)
cli_host->updateFinAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateFinAlertsCounter(when->tv_sec, !src2dst_direction);
iface->getTcpFlowStats()->incFin();
}
/* *** */
if(!twh_over) {
if(flags & (TH_ACK | TH_PUSH | TH_RST | TH_FIN)) {
Host *cli_host = getViewSharedClient();
Host *srv_host = getViewSharedServer();
twh_over = 1, iface->getTcpFlowStats()->incEstablished();
if(cli_host || srv_host) setTwhOverForViewInterface();
}
}
if(src2dst_direction)
src2dst_tcp_flags |= flags;
else
dst2src_tcp_flags |= flags;
/*
Note
for nProbe, calculateConnectionState()
is called in ParserInterface::processFlow
*/
} else {
/* Packet Interface */
/* Update syn alerts counters. In case of cumulative flags, the AND is used as
* possibly other flags can be present */
if(flags_3wh == TH_SYN) {
if(cli_host)
cli_host->updateSynAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateSynAlertsCounter(when->tv_sec, !src2dst_direction);
if(cli_network_stats)
cli_network_stats->updateSynAlertsCounter(when->tv_sec,
src2dst_direction);
if(srv_network_stats)
srv_network_stats->updateSynAlertsCounter(when->tv_sec,
!src2dst_direction);
}
/* Update synack alerts counter. In case of cumulative flags, the AND is used
* as possibly other flags can be present */
if(flags_3wh == (TH_SYN | TH_ACK)) {
if(cli_host)
cli_host->updateSynAckAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateSynAckAlertsCounter(when->tv_sec, !src2dst_direction);
if(cli_network_stats)
cli_network_stats->updateSynAckAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_network_stats)
srv_network_stats->updateSynAckAlertsCounter(when->tv_sec, !src2dst_direction);
}
if((flags & TH_SYN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_SYN) != TH_SYN)) {
iface->getTcpFlowStats()->incSyn();
}
if((flags & TH_RST) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_RST) != TH_RST)) {
iface->getTcpFlowStats()->incReset();
if(cli_host)
cli_host->updateRstAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateRstAlertsCounter(when->tv_sec, !src2dst_direction);
}
if((flags & TH_FIN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_FIN) != TH_FIN)) {
if(cli_host)
cli_host->updateFinAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateFinAlertsCounter(when->tv_sec, !src2dst_direction);
iface->getTcpFlowStats()->incFin();
}
if((flags & (TH_FIN | TH_ACK))
&& (((src2dst_tcp_flags | dst2src_tcp_flags) & (TH_FIN | TH_ACK)) != (TH_FIN | TH_ACK))) {
if(cli_host)
cli_host->updateFinAckAlertsCounter(when->tv_sec, src2dst_direction);
if(srv_host)
srv_host->updateFinAckAlertsCounter(when->tv_sec, !src2dst_direction);
iface->getTcpFlowStats()->incFin();
}
/* *** */
if(src2dst_direction)
src2dst_tcp_flags |= flags;
else
dst2src_tcp_flags |= flags;
calculateConnectionState(false);
if(!twh_over) {
if(flags_3wh == TH_SYN) {
if(tcp != NULL) {
if(tcp->synTime.tv_sec == 0) memcpy(&tcp->synTime, when, sizeof(struct timeval));
}
if((src2dst_tcp_flags & (TH_SYN | TH_ACK)) == (TH_SYN | TH_ACK)) {
/* SYN|ACK arrived before SYN */
request_swap();
}
} else if(flags_3wh == (TH_SYN | TH_ACK)) {
if(tcp != NULL) {
if((tcp->synAckTime.tv_sec == 0) && (tcp->synTime.tv_sec > 0)) {
struct timeval t;
memcpy(&tcp->synAckTime, when, sizeof(struct timeval));
timeval_diff(&tcp->synTime, (struct timeval *)when, &t, 0);
tcp->serverRTT3WH = Utils::timeval2ms(&t);
/* Coherence check */
if(tcp->serverRTT3WH > 5000 /* 5 sec */ )
tcp->serverRTT3WH = 0;
else if(srv_host)
srv_host->updateNetworkRTT(tcp->serverRTT3WH);
}
}
} else if((flags_3wh == TH_ACK) ||
(flags_3wh == (TH_ACK | TH_PUSH)) /* TCP Fast Open may contain data and PSH
in the final TWH ACK */) {
if(tcp != NULL) {
if((tcp->ackTime.tv_sec == 0) && (tcp->synAckTime.tv_sec > 0)) {
struct timeval t;
memcpy(&tcp->ackTime, when, sizeof(struct timeval));
timeval_diff(&tcp->synAckTime, (struct timeval *)when, &t, 0);
tcp->clientRTT3WH = Utils::timeval2ms(&t);
#ifdef DEBUG
ntop->getTrace()->traceEvent(TRACE_WARNING, "Client RTT: %.1f ms", tcp->clientRTT3WH);
#endif
/* Coherence check */
if(tcp->clientRTT3WH > 5000 /* 5 sec */)
tcp->clientRTT3WH = 0;
else if(cli_host)
cli_host->updateNetworkRTT(tcp->clientRTT3WH);
#ifdef DEBUG
ntop->getTrace()->traceEvent(TRACE_WARNING, "Server RTT: %.1f ms", tcp->serverRTT3WH);
#endif
setRTT();
iface->getTcpFlowStats()->incEstablished();
}
}
goto not_yet;
} else {
not_yet:
if(twh_over == 0) {
twh_over = 1;
}
/*
Sometimes nDPI detects the protocol at the first packet
so we're already on the protocol detected slot. This is
is not a good news as we might have protocol detected
when 3WH is not yet completed.
*/
if(get_state() == hash_entry_state_allocated)
set_hash_entry_state_flow_notyetdetected();
}
}
}
}
/* *************************************** */
void Flow::timeval_diff(struct timeval *begin, const struct timeval *end,
struct timeval *result, bool divide_by_two) {
if(end->tv_sec >= begin->tv_sec) {
result->tv_sec = end->tv_sec - begin->tv_sec;
if((end->tv_usec - begin->tv_usec) < 0) {
result->tv_usec = 1000000 + end->tv_usec - begin->tv_usec;
if(result->tv_usec > 1000000) begin->tv_usec = 1000000;
result->tv_sec--;
} else
result->tv_usec = end->tv_usec - begin->tv_usec;
if(divide_by_two) {
result->tv_usec /= 2;
if(result->tv_sec % 2) result->tv_usec += 500000;
result->tv_sec /= 2;
}
} else
result->tv_sec = 0, result->tv_usec = 0;
}
/* *************************************** */
std::string Flow::getFlowInfo(bool isLuaRequest) {
std::string info_field = std::string("");
if(!isMaskedFlow()) {
if(iec104) return (iec104->getFlowInfo());
#ifdef NTOPNG_PRO
if(modbus) return (modbus->getFlowInfo());
#endif
if(isDNS() && protos.dns.last_query) {
info_field = std::string(protos.dns.last_query);
} else if(isHTTP() && protos.http.last_url) {
info_field = std::string(protos.http.last_url);
} else if(isTLS() && protos.tls.client_requested_server_name) {
info_field = std::string(protos.tls.client_requested_server_name);
} else if(isBittorrent() && bt_hash) {
info_field = std::string(bt_hash);
} else if(host_server_name) {
info_field = std::string(host_server_name);
} else if(isSSH()) {
if(protos.ssh.server_signature){
info_field = std::string(protos.ssh.server_signature);
} else if(protos.ssh.client_signature) {
info_field = std::string(protos.ssh.client_signature);
}
} else if(isLuaRequest && hasRisk(NDPI_DESKTOP_OR_FILE_SHARING_SESSION)) {
info_field = std::string("Desktop Sharing");
} else if(isMining() && protos.mining.currency) {
info_field = std::string(protos.mining.currency);
} else if(isSIP()) {
if(protos.sip.call_id)
info_field = std::string(protos.sip.call_id);
} else if(rtp_stream_type != ndpi_multimedia_unknown_flow) {
if(rtp_stream_type & ndpi_multimedia_audio_flow)
info_field = std::string("Audio");
else if(rtp_stream_type & ndpi_multimedia_video_flow)
info_field = std::string("Video");
else if(rtp_stream_type & ndpi_multimedia_screen_sharing_flow)
info_field = std::string("Desktop Sharing");
} else if((get_protocol() == IPPROTO_ICMP) || (get_protocol() == IPPROTO_ICMPV6)) {
char str[32];
if(isBidirectional())
snprintf(str, sizeof(str), "%u,%u", protos.icmp.srv2cli.icmp_type, protos.icmp.srv2cli.icmp_code);
else
snprintf(str, sizeof(str), "%u,%u", protos.icmp.cli2srv.icmp_type, protos.icmp.cli2srv.icmp_code);
info_field = std::string(str);
}
}
return info_field;
}
/* *************************************** */
double Flow::toMs(const struct timeval *t) {
return (((double)t->tv_sec) * 1000 + ((double)t->tv_usec) / 1000);
}
/* *************************************** */
u_int32_t Flow::getNextTcpSeq(u_int8_t tcpFlags, u_int32_t tcpSeqNum,
u_int32_t payloadLen) {
return (tcpSeqNum + ((tcpFlags & TH_SYN) ? 1 : 0) + payloadLen);
}
/* *************************************** */
void Flow::setDHCPHostName(const char* name) {
if((name && name[0]) && (protos.dhcp.name == NULL)) protos.dhcp.name = strdup(name);
}
/* *************************************** */
void Flow::setSIPCallId(const char* name) {
if((name && name[0]) && (protos.sip.call_id == NULL)) protos.sip.call_id = strdup(name);
}
/* *************************************** */
void Flow::incTcpBadStats(bool src2dst_direction, Host *cli, Host *srv,
NetworkInterface *iface, u_int32_t ooo_pkts,
u_int32_t retr_pkts, u_int32_t lost_pkts,
u_int32_t keep_alive_pkts) {
#ifdef HAVE_NEDGE
return;
#endif
if(!ooo_pkts && !retr_pkts && !lost_pkts && !keep_alive_pkts) return;
int32_t cli_network_id = -1, srv_network_id = -1;
u_int32_t cli_asn = (u_int32_t)-1, srv_asn = (u_int32_t)-1;
AutonomousSystem *cli_as = NULL, *srv_as = NULL;
NetworkStats *cli_network_stats = NULL, *srv_network_stats = NULL;
bool cli_and_srv_in_same_subnet = false, cli_and_srv_in_same_as = false;
if(iface) {
if(retr_pkts) iface->incRetransmittedPkts(retr_pkts);
if(lost_pkts) iface->incLostPkts(lost_pkts);
if(ooo_pkts) iface->incOOOPkts(ooo_pkts);
if(keep_alive_pkts) iface->incKeepAlivePkts(keep_alive_pkts);
}
if(cli) {
cli_network_id = cli->get_local_network_id();
cli_network_stats = cli->getNetworkStats(cli_network_id);
cli_asn = cli->get_asn();
cli_as = cli->get_as();
if(src2dst_direction)
cli->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
else
cli->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
}
if(srv) {
srv_network_id = srv->get_local_network_id();
srv_network_stats = srv->getNetworkStats(srv_network_id);
srv_asn = srv->get_asn();
srv_as = srv->get_as();
if(src2dst_direction)
srv->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
else
srv->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
}
if(cli_network_id >= 0 && (cli_network_id == srv_network_id))
cli_and_srv_in_same_subnet = true;
if(cli_network_stats) {
if(!cli_and_srv_in_same_subnet) {
if(src2dst_direction)
cli_network_stats->incEgressTcp(ooo_pkts, retr_pkts, lost_pkts,
keep_alive_pkts);
else
cli_network_stats->incIngressTcp(ooo_pkts, retr_pkts, lost_pkts,
keep_alive_pkts);
} else
cli_network_stats->incInnerTcp(ooo_pkts, retr_pkts, lost_pkts,
keep_alive_pkts);
}
if(srv_network_stats) {
if(!cli_and_srv_in_same_subnet) {
if(src2dst_direction)
srv_network_stats->incIngressTcp(ooo_pkts, retr_pkts, lost_pkts,
keep_alive_pkts);
else
srv_network_stats->incEgressTcp(ooo_pkts, retr_pkts, lost_pkts,
keep_alive_pkts);
}
}
if(cli_asn != (u_int32_t)-1 && (cli_asn == srv_asn))
cli_and_srv_in_same_as = true;
if(!cli_and_srv_in_same_as) {
if(cli_as) {
if(src2dst_direction)
cli_as->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
else
cli_as->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
}
if(srv_as) {
if(src2dst_direction)
srv_as->incRcvdTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
else
srv_as->incSentTcp(ooo_pkts, retr_pkts, lost_pkts, keep_alive_pkts);
}
}
}
/* *************************************** */
void Flow::updateTcpSeqNum(const struct bpf_timeval *when, u_int32_t seq_num,
u_int32_t ack_seq_num, u_int16_t window,
u_int8_t flags, u_int16_t payload_Len,
bool src2dst_direction) {
u_int32_t next_seq_num;
bool update_last_seqnum = true;
bool update_next_seqnum = true;
bool debug = false;
u_int32_t cnt_keep_alive = 0, cnt_lost = 0, cnt_ooo = 0, cnt_retx = 0;
#ifdef HAVE_NEDGE
return;
#endif
if(tcp == NULL) return;
next_seq_num = getNextTcpSeq(flags, seq_num, payload_Len);
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[act: %u][next: %u][next - act (in flight): "
"%d][ack: %u][payload len: %u]",
seq_num, next_seq_num, next_seq_num - seq_num,
ack_seq_num, payload_Len);
if(src2dst_direction) {
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[src2dst][seq: %u][last: %u][next: %u]", seq_num,
tcp->tcp_seq_s2d.last, tcp->tcp_seq_s2d.next);
if(window > 0) tcp->srv2cli_window = window; /* Note the window is reverted */
if((tcp->tcp_seq_s2d.next > 0) &&
/* If equal, seq_num is the expected seq_num as determined with prev. segment */
(tcp->tcp_seq_s2d.next != seq_num) &&
(tcp->tcp_seq_s2d.next != (seq_num - 1))) {
if((seq_num == tcp->tcp_seq_s2d.next - 1) &&
(payload_Len == 0 || payload_Len == 1) &&
((flags & (TH_SYN | TH_FIN | TH_RST)) == 0)) {
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[src2dst] Packet KeepAlive");
cnt_keep_alive++;
} else if(seq_num == tcp->tcp_seq_s2d.last ||
seq_num < tcp->tcp_seq_s2d.last /* older packet retransmitted */) {
if(tcp->tcp_seq_s2d.next != tcp->tcp_seq_s2d.last) {
update_next_seqnum = (seq_num == tcp->tcp_seq_s2d.last);
update_last_seqnum = false;
cnt_retx++;
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[src2dst] Packet retransmission");
}
} else if(seq_num > tcp->tcp_seq_s2d.next) {
cnt_lost++;
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING, "[src2dst] Packet lost [last: %u][act: %u]",
tcp->tcp_seq_s2d.last, seq_num);
} else {
cnt_ooo++;
update_last_seqnum = ((seq_num - 1) > tcp->tcp_seq_s2d.last) ? true : false;
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING, "[src2dst] Packet OOO [last: %u][act: %u]",
tcp->tcp_seq_s2d.last, seq_num);
}
}
if(update_next_seqnum) tcp->tcp_seq_s2d.next = next_seq_num;
if(update_last_seqnum) tcp->tcp_seq_s2d.last = seq_num;
} else {
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[dst2src][seq: %u][last: %u][next: %u]", seq_num,
tcp->tcp_seq_d2s.last, tcp->tcp_seq_d2s.next);
if(window > 0) tcp->cli2srv_window = window; /* Note the window is reverted */
if((tcp->tcp_seq_d2s.next > 0) &&
(tcp->tcp_seq_d2s.next != seq_num) &&
(tcp->tcp_seq_d2s.next != (seq_num - 1))) {
if((seq_num == tcp->tcp_seq_d2s.next - 1) &&
(payload_Len == 0 || payload_Len == 1) &&
((flags & (TH_SYN | TH_FIN | TH_RST)) == 0)) {
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[dst2src] Packet KeepAlive");
cnt_keep_alive++;
} else if(seq_num == tcp->tcp_seq_d2s.last /* last packet retransmitted */ ||
seq_num < tcp->tcp_seq_d2s.last /* older packet retransmitted */) {
if(tcp->tcp_seq_d2s.next != tcp->tcp_seq_d2s.last) {
cnt_retx++;
update_next_seqnum = (seq_num == tcp->tcp_seq_d2s.last);
update_last_seqnum = false;
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[dst2src] Packet retransmission");
}
// bytes
} else if(seq_num > tcp->tcp_seq_d2s.next) {
cnt_lost++;
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] Packet lost [last: %u][act: %u]",
tcp->tcp_seq_d2s.last, seq_num);
} else {
cnt_ooo++;
update_last_seqnum = ((seq_num - 1) > tcp->tcp_seq_d2s.last) ? true : false;
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING,
"[dst2src] [last: %u][next: %u]",
tcp->tcp_seq_d2s.last, tcp->tcp_seq_d2s.next);
if(debug)
ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] Packet OOO [last: %u][act: %u]",
tcp->tcp_seq_d2s.last, seq_num);
}
}
if(update_next_seqnum) tcp->tcp_seq_d2s.next = next_seq_num;
if(update_last_seqnum) tcp->tcp_seq_d2s.last = seq_num;
}
#if 0
static int idx = 0;
ntop->getTrace()->traceEvent(TRACE_WARNING, "%03u %s RETR=%u OOO=%u LOST=%u KPLV=%u", ++idx,
src2dst_direction ? ">" : "<", cnt_retx, cnt_ooo, cnt_lost, cnt_keep_alive);
#endif
if (cnt_lost || cnt_ooo || cnt_retx)
tcp->last_network_issues = when->tv_sec;
if(cnt_keep_alive || cnt_lost || cnt_ooo || cnt_retx) {
stats.incTcpStats(src2dst_direction, cnt_retx, cnt_ooo, cnt_lost,
cnt_keep_alive);
}
}
/* *************************************** */
u_int32_t Flow::getPid(bool client) {
if(client && ebpf && ebpf->process_info_set)
return(ebpf->src_process_info.pid);
if(!client && ebpf && ebpf->process_info_set)
return(ebpf->dst_process_info.pid);
return NO_PID;
};
/* *************************************** */
u_int32_t Flow::getFatherPid(bool client) {
if(client && ebpf && ebpf->process_info_set)
return(ebpf->src_process_info.father_pid);
if(!client && ebpf && ebpf->process_info_set)
return(ebpf->dst_process_info.father_pid);
return NO_PID;
};
/* *************************************** */
u_int32_t Flow::get_uid(bool client) const {
#ifdef WIN32
return NO_UID;
#else
if(client && ebpf && ebpf->process_info_set)
return(ebpf->src_process_info.uid);
if(!client && ebpf && ebpf->process_info_set)
return(ebpf->dst_process_info.uid);
return NO_UID;
#endif
}
/* *************************************** */
char *Flow::get_proc_name(bool client) {
if(client && ebpf && ebpf->process_info_set)
return(ebpf->src_process_info.process_name);
if(!client && ebpf && ebpf->process_info_set)
return(ebpf->dst_process_info.process_name);
return NULL;
};
/* *************************************** */
char *Flow::get_user_name(bool client) {
if(client && ebpf && ebpf->process_info_set)
return(ebpf->src_process_info.uid_name);
if(!client && ebpf && ebpf->process_info_set)
return(ebpf->dst_process_info.uid_name);
return NULL;
}
/* *************************************** */
bool Flow::match(AddressTree *ptree) {
if((get_cli_ip_addr() && get_cli_ip_addr()->match(ptree)) ||
(get_srv_ip_addr() && get_srv_ip_addr()->match(ptree)))
return (true);
else
return (false);
};
/* *************************************** */
void Flow::setBittorrentHash(char *hash, u_int len) {
int i, j, n = 0;
char bittorrent_hash[41];
if(len < 20)
return;
for (i = 0, j = 0; i < 20; i++) {
u_char c = hash[i] & 0xFF;
snprintf(&bittorrent_hash[j], 3, "%02x", (u_int8_t)c);
j += 2, n += c;
}
if(n > 0) bt_hash = strdup(bittorrent_hash);
}
/* *************************************** */
void Flow::dissectBittorrent(char *payload, u_int16_t payload_len) {
/* This dissector is called only for uTP/UDP protocol */
if(payload_len <= 27 + 20)
return;
char *data = &payload[20];
u_int16_t data_len = payload_len - 20;
char *bt_proto = ndpi_strnstr((const char *) data, "BitTorrent protocol",
data_len);
if(bt_proto) {
u_int16_t bt_proto_offset = bt_proto - data;
data = bt_proto;
data_len = data_len - bt_proto_offset;
if(data_len >= 27 + 20) {
u_int l = strnlen(data, 27);
if(l >= 27) {
data = &data[27];
data_len = data_len - 27;
setBittorrentHash(data, data_len);
}
}
}
}
/* *************************************** */
/*
Performs DNS query updates. No more than one update per second is performed to
handle concurrency issues. This is safe in general as it is unlikely to see
more than one query per second for the same DNS flow.
*/
bool Flow::setDNSQuery(char *query_value, char *rsp_addresses, bool copy_memory) {
if((query_value != NULL) && isDNS()) {
time_t last_pkt_rcvd = getInterface()->getTimeLastPktRcvd();
if(!protos.dns.last_query_shadow /* The first time the swap is done */
|| (protos.dns.last_query_update_time + 1 <
last_pkt_rcvd /* Latest swap occurred at least one second ago */)) {
if(protos.dns.last_query_shadow) free(protos.dns.last_query_shadow);
protos.dns.last_query_shadow = protos.dns.last_query;
protos.dns.last_query = copy_memory ? strdup(query_value) : query_value;
#ifdef NTOPNG_PRO
if(getDNSRetCode() == 0 /* NOERROR */)
processHostName(protos.dns.last_query);
#endif
if(protos.dns.last_rsp_shadow) free(protos.dns.last_rsp_shadow);
protos.dns.last_rsp_shadow = protos.dns.last_rsp;
if(rsp_addresses && (rsp_addresses[0] != '\0'))
protos.dns.last_rsp = copy_memory ? strdup(rsp_addresses) : rsp_addresses;
else
protos.dns.last_rsp = NULL;
protos.dns.last_query_update_time = last_pkt_rcvd;
#ifdef DEBUG
if(protos.dns.last_rsp)
ntop->getTrace()->traceEvent(TRACE_NORMAL, "**** %s", protos.dns.last_rsp);
#endif
return(true); /* Swap successful */
}
}
/* Unable to set the DNS query. Too early or not a DNS flow. */
return(false);
}
/* *************************************** */
/*
@brief Update DNS stats for flows received via ZMQ
*/
void Flow::updateDNS(ParsedFlow *zflow) {
if(isDNS()) {
if(zflow->getDNSQuery()) {
if(setDNSQuery(zflow->getDNSQuery(true), NULL, false /* No need to allocate memory */)) {
/* Set successful, query will be freed in the destructor */
setDNSQueryType(zflow->getDNSQueryType());
setDNSRetCode(zflow->getDNSRetCode());
}
}
stats.incDNSQuery(getLastQueryType());
stats.incDNSResp(getDNSRetCode());
}
}
/* *************************************** */
/*
@brief Update TLS stats for flows received via ZMQ
*/
void Flow::updateTLS(ParsedFlow *zflow) {
if(zflow->getTLSserverName()
&& isTLS()
&& (!protos.tls.client_requested_server_name)) {
protos.tls.client_requested_server_name = zflow->getTLSserverName(true);
#ifdef NTOPNG_PRO
processHostName(protos.tls.client_requested_server_name);
#endif
}
}
/* *************************************** */
void Flow::dissectDNS(bool src2dst_direction, char *payload,
u_int16_t payload_len) {
struct ndpi_dns_packet_header dns_header;
u_int8_t payload_offset = get_protocol() == IPPROTO_UDP ? 0 : 2;
if(payload_len + payload_offset < sizeof(dns_header)) return;
memcpy(&dns_header, &payload[payload_offset], sizeof(dns_header));
if((dns_header.flags & 0x8000) == 0x0000)
stats.incDNSQuery(getLastQueryType());
else if((dns_header.flags & 0x8000) == 0x8000)
stats.incDNSResp(getDNSRetCode());
}
/* *************************************** */
void Flow::setICMPTypeCode(u_int16_t icmp_type_code) {
if(icmp_info == NULL)
icmp_info = new (std::nothrow) ICMPinfo();
if(icmp_info != NULL) {
u_int8_t icmp_code = (u_int8_t)(icmp_type_code & 0x00FF);
u_int8_t icmp_type = (u_int8_t)((icmp_type_code >> 8) & 0xFF);
icmp_info->setCode(icmp_code), protos.icmp.cli2srv.icmp_code = icmp_code;
icmp_info->setType(icmp_type), protos.icmp.cli2srv.icmp_type = icmp_type;
}
}
/* *************************************** */
void Flow::updateHTTP(ParsedFlow *zflow) {
if(isHTTP()) {
if(zflow->getHTTPurl())
setHTTPURL(zflow->getHTTPurl(true));
if(zflow->getHTTPuserAgent())
setHTTPUserAgent(zflow->getHTTPuserAgent(true));
if(zflow->getHTTPsite())
setServerName(zflow->getHTTPsite(true));
if(zflow->getHTTPMethod() != NDPI_HTTP_METHOD_UNKNOWN) {
setHTTPMethod(zflow->getHTTPMethod());
const char *http_method = getHTTPMethod();
if(http_method && http_method[0] && http_method[1]) {
switch (http_method[0]) {
case 'P':
switch (http_method[1]) {
case 'O':
stats.incHTTPReqPOST();
break;
case 'U':
stats.incHTTPReqPUT();
break;
default:
stats.incHTTPReqOhter();
break;
}
break;
case 'G':
stats.incHTTPReqGET();
break;
case 'H':
stats.incHTTPReqHEAD();
break;
default:
stats.incHTTPReqOhter();
break;
}
} else
stats.incHTTPReqOhter();
}
setHTTPRetCode(zflow->getHTTPRetCode());
u_int16_t ret_code = getHTTPRetCode();
while (ret_code > 9) ret_code /= 10; /* Take the first digit */
switch (ret_code) {
case 1:
stats.incHTTPResp1xx();
break;
case 2:
stats.incHTTPResp2xx();
break;
case 3:
stats.incHTTPResp3xx();
break;
case 4:
stats.incHTTPResp4xx();
break;
case 5:
stats.incHTTPResp5xx();
break;
}
}
}
/* *************************************** */
void Flow::updateSuspiciousDGADomain() {
if(hasRisk(NDPI_SUSPICIOUS_DGA_DOMAIN) && !suspicious_dga_domain) {
std::string info_field = getFlowInfo(false);
if(!info_field.empty()) {
suspicious_dga_domain = strdup(info_field.c_str());
}
}
}
/* *************************************** */
void Flow::setHTTPMethod(ndpi_http_method m) {
if(protos.http.last_method == NDPI_HTTP_METHOD_UNKNOWN)
protos.http.last_method = m;
}
/* *************************************** */
void Flow::setHTTPMethod(const char *method, ssize_t method_len) {
setHTTPMethod(ndpi_http_str2method(method, method_len));
}
/* *************************************** */
void Flow::dissectHTTP(bool src2dst_direction, char *payload,
u_int16_t payload_len) {
ssize_t host_server_name_len = host_server_name && (host_server_name[0] != '\0') ? strlen(host_server_name) : 0;
if((payload == NULL) || (payload_len == 0)) return;
#if 0 /* Not sure it this is really necessary */
if(!isThreeWayHandshakeOK())
; /* Useless to compute http stats as client and server could be swapped */
else
#endif
if(src2dst_direction) {
char *space;
dissect_next_http_packet = 1;
/* use memchr to prevent possibly non-NULL terminated HTTP requests */
if(payload &&
((space = (char *)memchr(payload, ' ', payload_len - 1)) != NULL)) {
u_int l = space - payload;
bool go_deeper = true;
if(payload_len >= 2) {
switch (payload[0]) {
case 'P':
switch (payload[1]) {
case 'O':
stats.incHTTPReqPOST();
break;
case 'U':
stats.incHTTPReqPUT();
break;
default:
stats.incHTTPReqOhter();
go_deeper = false;
break;
}
break;
case 'G':
stats.incHTTPReqGET();
break;
case 'H':
stats.incHTTPReqHEAD();
break;
default:
stats.incHTTPReqOhter();
go_deeper = false;
break;
}
} else
go_deeper = false;
if(go_deeper) {
char *ua;
setHTTPMethod(payload, l);
payload_len -= (l + 1);
payload = &space[1];
if((space = (char *)memchr(payload, ' ', payload_len)) != NULL) {
l = min_val(space - payload, 512); /* Avoid jumbo URLs */
/* Stop at the first non-printable char of the HTTP URL */
for (u_int i = 0; i < l; i++) {
if(!isprint(payload[i])) {
l = i;
break;
}
}
if(!protos.http.last_url &&
(protos.http.last_url =
(char *)malloc(host_server_name_len + l + 1)) != NULL) {
protos.http.last_url[0] = '\0';
if(host_server_name_len > 0) {
strncat(protos.http.last_url, host_server_name,
host_server_name_len);
}
strncat(protos.http.last_url, payload, l);
}
}
if((ua = ndpi_strnstr(payload, "User-Agent:", payload_len)) != NULL) {
char buf[128];
u_int i;
ua = &ua[11];
while (ua[0] == ' ') ua++;
for (i = 0;
(i < payload_len) && (i < (sizeof(buf) - 1) && (ua[i] != '\r'));
i++)
buf[i] = ua[i];
buf[i] = '\0';
#ifdef DEBUG_UA
ntop->getTrace()->traceEvent(TRACE_WARNING, "[UA] %s", buf);
#endif
/*
https://en.wikipedia.org/wiki/User_agent
Most Web browsers use a User-Agent string value as follows:
Mozilla/[version] ([system and browser information]) [platform]
([platform details]) [extensions]
*/
if((ua = strchr(buf, '(')) != NULL) {
char *end = strchr(buf, ')');
if(end) {
ndpi_os hint;
end[0] = '\0';
ua++;
Utils::getDeviceTypeFromOsDetail(ua, &hint);
if(hint != ndpi_os_unknown)
operating_system = hint;
}
}
}
}
}
} else {
if(dissect_next_http_packet) {
char *space;
// payload[10]=0; ntop->getTrace()->traceEvent(TRACE_WARNING, "[len:
// %u][%s]", payload_len, payload);
dissect_next_http_packet = 0;
if((space = (char *)memchr(payload, ' ', payload_len)) != NULL) {
u_int l = space - payload;
payload_len -= (l + 1);
payload = &space[1];
switch (payload[0]) {
case '1':
stats.incHTTPResp1xx();
break;
case '2':
stats.incHTTPResp2xx();
break;
case '3':
stats.incHTTPResp3xx();
break;
case '4':
stats.incHTTPResp4xx();
break;
case '5':
stats.incHTTPResp5xx();
break;
}
if((space = (char *)memchr(payload, ' ', payload_len)) != NULL) {
char tmp[32];
l = min_val(space - payload, (int)(sizeof(tmp) - 1));
strncpy(tmp, payload, l);
tmp[l] = 0;
protos.http.last_return_code = atoi(tmp);
}
}
}
}
}
/* *************************************** */
void Flow::dissectMDNS(u_int8_t *payload, u_int16_t payload_len) {
u_int16_t answers, i = 0;
PACK_ON
struct mdns_rsp_entry {
u_int16_t rsp_type, rsp_class;
u_int32_t ttl;
u_int16_t data_len;
} PACK_OFF;
if(((payload[2] & 0x80) != 0x80) || (payload_len < 12))
return; /* This is a not MDNS response */
answers = ntohs(*((u_int16_t *)&payload[6])) +
ntohs(*((u_int16_t *)&payload[8])) +
ntohs(*((u_int16_t *)&payload[10]));
payload = &payload[12], payload_len -= 12;
while ((answers > 0) && (i < payload_len)) {
char _name[256], *name;
struct mdns_rsp_entry rsp;
u_int j;
u_int16_t rsp_type, data_len;
DeviceType dtype = device_unknown;
bool first_char = true;
memset(_name, 0, sizeof(_name));
for (j = 0; (i < payload_len) && (j < (sizeof(_name) - 1)); i++) {
if(payload[i] == 0x0) {
i++;
break;
} else if(payload[i] < 32) {
if(j > 0) _name[j++] = '.';
} else if(payload[i] == 0x22) {
_name[j++] = 'a';
_name[j++] = 'r';
_name[j++] = 'p';
_name[j++] = 'a';
i++;
break;
} else if(payload[i] == 0xC0) {
u_int8_t offset;
u_int16_t i_save = i;
u_int8_t num_loops = 0;
const u_int8_t max_nested_loops = 8;
nested_dns_definition:
offset = payload[i + 1] - 12;
i = offset;
if((offset > i) || (i > payload_len) ||
(num_loops > max_nested_loops)) {
#ifdef DEBUG_DISCOVERY
ntop->getTrace()->traceEvent(TRACE_WARNING, "Invalid MDNS packet");
#endif
return; /* Invalid packet */
} else {
/* Pointer back */
while ((i < payload_len) && (payload[i] != 0) &&
(j < (sizeof(_name) - 1))) {
if(payload[i] == 0)
break;
else if(payload[i] == 0xC0) {
num_loops++;
goto nested_dns_definition;
} else if(payload[i] < 32) {
if(j > 0) _name[j++] = '.';
i++;
} else
_name[j++] = payload[i++];
}
if(i_save > 0) {
i = i_save;
i_save = 0;
}
i += 2;
/* ntop->getTrace()->traceEvent(TRACE_NORMAL, "===>>> [%d] %s", i,
* &payload[i-12]); */
break;
}
} else if(!first_char)
_name[j++] = payload[i];
first_char = false;
}
if((i+sizeof(rsp)) >= payload_len)
return; /* packet too short */
memcpy(&rsp, &payload[i], sizeof(rsp));
data_len = ntohs(rsp.data_len), rsp_type = ntohs(rsp.rsp_type);
#if 1
name = _name;
#else
/* Skip lenght for strings >= 32 with head length */
name = &_name[((data_len <= 32) || (_name[0] >= '0')) ? 0 : 1];
#endif
#ifdef DEBUG_DISCOVERY
ntop->getTrace()->traceEvent(TRACE_NORMAL, "===>>> [%u][%s][len=%u]",
ntohs(rsp.rsp_type) & 0xFFFF, name, data_len);
#endif
if(strstr(name, "._device-info._"))
;
else if(strstr(name, "._airplay._") || strstr(name, "._spotify-connect._"))
dtype = device_multimedia;
else if(strstr(name, "_ssh._"))
dtype = device_workstation;
else if(strstr(name, "._daap._") || strstr(name, "_afpovertcp->_") ||
strstr(name, "_adisk._") || strstr(name, "_smb._"))
dtype = device_nas;
else if(strstr(name, "_hap._"))
dtype = device_iot;
else if(strstr(name, "_pdl-datastream._"))
dtype = device_printer;
if((dtype != device_unknown) && cli_host && cli_host->getMac()) {
Mac *m = cli_host->getMac();
cli_host->setDeviceType(dtype);
if(m->getDeviceType() == device_unknown)
m->setDeviceType(dtype);
}
switch (rsp_type) {
case 0x1C: /* AAAA */
case 0x01: /* AA */
case 0x10: /* TXT */
{
int len = strlen(name);
char *c;
if((len > 6) && (strcmp(&name[len - 6], ".local") == 0))
name[len - 6] = 0;
c = strstr(name, "._");
if(c && (c != name) /* Does not begin with... */) c[0] = '\0';
}
if(!protos.mdns.name) protos.mdns.name = strdup(name);
if((rsp_type == 0x10 /* TXT */) && (data_len > 0)) {
u_int16_t base_off = i + sizeof(rsp);
char *txt = (char *)&payload[base_off], txt_buf[256];
u_int16_t off = 0;
while((off < data_len) && ((off+base_off) < payload_len)) {
u_int8_t txt_len = (u_int8_t)txt[off];
if(txt_len < data_len) {
txt_len = min_val(data_len - off, txt_len);
off++;
if(txt_len > 0) {
char *model = NULL;
u_int txt_buf_len = ndpi_min(txt_len, sizeof(txt_buf)-1);
if((base_off+off+txt_buf_len) < payload_len)
break;
strncpy(txt_buf, &txt[off], txt_buf_len);
txt_buf[txt_buf_len] = '\0';
off += txt_len;
#ifdef DEBUG_DISCOVERY
ntop->getTrace()->traceEvent(TRACE_NORMAL, "===>>> [TXT][%s]", txt_buf);
#endif
if(strncmp(txt_buf, "am=", 3 /* Apple Model */) == 0)
model = &txt_buf[3];
else if(strncmp(txt_buf, "model=", 6) == 0)
model = &txt_buf[6];
else if(strncmp(txt_buf, "md=", 3) == 0)
model = &txt_buf[3];
if(model && cli_host) {
Mac *mac = cli_host->getMac();
if(mac) {
mac->inlineSetModel(model);
}
}
if(strncmp(txt_buf, "nm=", 3) == 0)
if(!protos.mdns.name_txt)
protos.mdns.name_txt = strdup(&txt_buf[3]);
if(strncmp(txt_buf, "ssid=", 5) == 0) {
if(!protos.mdns.ssid) protos.mdns.ssid = strdup(&txt_buf[5]);
if(cli_host && cli_host->getMac())
cli_host->getMac()->inlineSetSSID(&txt_buf[5]);
}
}
} else
break;
}
}
#ifdef DEBUG_DISCOVERY
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%u) %u [%s]", answers,
rsp_type, name);
#endif
// return; /* It's enough to decode the first name */
}
i += sizeof(rsp) + data_len, answers--;
}
}
/* *************************************** */
void Flow::dissectSSDP(bool src2dst_direction, char *payload,
u_int16_t payload_len) {
char url[512];
u_int i = 0;
if(payload_len < 6 /* NOTIFY */) return;
if(strncmp(payload, "NOTIFY", 6) == 0) {
payload += 6, payload_len -= 6;
for (; 0 < payload_len - 9 /* strlen("Location:") */;
payload++, payload_len--) {
if(strncasecmp(payload, "Location:", 9)) {
continue;
} else {
payload += 9, payload_len -= 9;
for (;
(payload_len > 0) && (payload[0] != '\n') && (payload[0] != '\r');
payload++, payload_len--) {
if(*payload == ' ') continue;
if(i == sizeof(url) - 1) break;
url[i++] = *payload;
}
url[i] = '\0';
// ntop->getTrace()->traceEvent(TRACE_NORMAL, "[SSDP URL:] %s", url);
if(!protos.ssdp.location) protos.ssdp.location = strdup(url);
break;
}
}
}
}
/* *************************************** */
void Flow::dissectNetBIOS(u_int8_t *payload, u_int16_t payload_len) {
char name[64];
/* Already dissected ? */
if(protos.netbios.name) return;
if((payload_len >= 12) &&
((payload[2] & 0x80) /* NetBIOS Response */ ||
((payload[2] & 0x78) == 0x28 /* NetBIOS Registration */)) &&
(ndpi_netbios_name_interpret((u_char *)&payload[12], payload_len - 12,
(u_char *)name, sizeof(name)) > 0) &&
(!strstr(name, "__MSBROWSE__"))) {
if(name[0] == '*') {
int limit = min_val(payload_len - 57, (int)sizeof(name) - 1);
int i = 0;
while ((i < limit) && (payload[57 + i] != 0x20) &&
isprint(payload[57 + i])) {
name[i] = payload[57 + i];
i++;
}
if((i < limit) &&
(payload[57 + i] != 0x00 /* Not a Workstation/Redirector */))
name[0] = '\0'; /* ignore */
else
name[i] = '\0';
}
#if 0
char buf[32];
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Setting hostname from NetBios [raw=0x%x opcode=0x%x response=0x%x]: ip=%s -> '%s'",
payload[2], (payload[2] & 0x78) >> 3, (payload[2] & 0x80) >> 7,
(*srcHost)->get_ip()->print(buf, sizeof(buf)), name);
#endif
if(name[0]) protos.netbios.name = strdup(name);
}
}
/* ***************************************************** */
bool Flow::isTiny() const {
// if((cli2srv_packets < 3) && (srv2cli_packets == 0))
if((get_packets() <=
ntop->getPrefs()->get_max_num_packets_per_tiny_flow()) ||
(get_bytes() <= ntop->getPrefs()->get_max_num_bytes_per_tiny_flow()))
return (true);
else
return (false);
}
/* ***************************************************** */
void Flow::setParsedeBPFInfo(const ParsedeBPF *const _ebpf,
bool swap_directions) {
ParsedeBPF *cur = NULL;
bool update_ok = true;
if(!_ebpf) return;
if(!iface->hasSeenEBPFEvents()) iface->setSeenEBPFEvents();
if(!ebpf)
cur = ebpf = new (std::nothrow) ParsedeBPF(*_ebpf, swap_directions);
else
update_ok = ebpf->update(_ebpf);
if(!update_ok) {
static bool warning_shown = false;
char *fbuf;
ssize_t fbuf_len = 512;
if(!warning_shown && (fbuf = (char *)malloc(fbuf_len))) {
ntop->getTrace()->traceEvent(TRACE_WARNING, "Identical flow seen across multiple containers? %s",
print(fbuf, fbuf_len));
warning_shown = true;
free(fbuf);
}
}
if(cur && cur->container_info_set) {
if(!iface->hasSeenContainers()) iface->setSeenContainers();
if(!iface->hasSeenPods() &&
((cur->src_container_info.data_type == container_info_data_type_k8s &&
cur->src_container_info.data.k8s.pod) ||
(cur->dst_container_info.data_type == container_info_data_type_k8s &&
cur->dst_container_info.data.k8s.pod)))
iface->setSeenPods();
}
updateCliJA4();
updateHASSH(true /* AS client */);
updateHASSH(false /* AS server */);
}
/* ***************************************************** */
void Flow::updateCliJA4() {
if(cli_host && isTLS() && protos.tls.ja4.client_hash) {
Fingerprint *f = cli_host->getJA4Fingerprint();
if(f)
f->update(protos.tls.ja4.client_hash,
ebpf ? ebpf->src_process_info.process_name : NULL,
has_malicious_cli_signature);
}
}
/* ***************************************************** */
void Flow::updateHASSH(bool as_client) {
if(!isSSH()) return;
Host *h = as_client ? get_cli_host() : get_srv_host();
const char *hassh = as_client ? protos.ssh.hassh.client_hash : protos.ssh.hassh.server_hash;
ProcessInfo *pinfo = NULL;
Fingerprint *fp;
if(ebpf)
pinfo = as_client ? &ebpf->src_process_info : &ebpf->dst_process_info;
if(h
&& hassh
&& (hassh[0] != '\0')
&& (fp = h->getHASSHFingerprint())) {
if(fp)
fp->update(hassh, pinfo ? pinfo->process_name : NULL,
false /* malicious */);
}
}
/* ***************************************************** */
void Flow::fillZMQFlowCategory(ndpi_protocol *res) {
struct ndpi_detection_module_struct *ndpi_struct = iface->get_ndpi_struct();
const char *dst_name = NULL;
const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();
if(ndpiFlow && cli_ip && srv_ip && cli_ip->isIPv4()) {
if(ndpi_fill_ip_protocol_category(ndpi_struct, ndpiFlow,
cli_ip->get_ipv4(),
srv_ip->get_ipv4(), res))
return;
}
switch (ndpi_get_lower_proto(*res)) {
case NDPI_PROTOCOL_DNS:
dst_name = getDNSQuery();
break;
case NDPI_PROTOCOL_HTTP_PROXY:
case NDPI_PROTOCOL_HTTP:
dst_name = getFlowServerInfo();
break;
case NDPI_PROTOCOL_TLS:
dst_name = protos.tls.client_requested_server_name;
break;
default:
break;
}
if(dst_name) {
int rc;
ndpi_protocol_match_result tmp;
ndpi_protocol_category_t c;
ndpi_protocol_breed_t breed;
/* Match for custom protocols (protos.txt) */
if((rc = ndpi_match_string_subprotocol(ndpi_struct, (char *)dst_name,
strlen(dst_name), &tmp)) != 0) {
if(iface->isCustomDPIProtocol(rc)) {
if(res->proto.master_protocol == NDPI_PROTOCOL_UNKNOWN)
res->proto.master_protocol = res->proto.app_protocol;
res->proto.app_protocol = (ndpi_protocol_category_t)rc;
}
}
/* Match for custom categories */
if(ndpi_match_custom_category(ndpi_struct, (char *)dst_name,
strlen(dst_name), &c, &breed) == 0)
res->category = c;
}
}
/* ***************************************************** */
void Flow::lua_get_status(lua_State *vm) const {
lua_push_bool_table_entry(vm, "flow.idle", idle());
lua_push_uint64_table_entry(vm, "flow.status", getPredominantAlert().id);
alerts_map.lua(vm, "alerts_map");
if(isFlowAlerted()) {
lua_push_bool_table_entry(vm, "flow.alerted", true);
lua_push_uint64_table_entry(vm, "predominant_alert", getPredominantAlert().id);
lua_push_uint64_table_entry(vm, "predominant_alert_score", getPredominantAlertScore());
}
}
/* ***************************************************** */
void Flow::lua_get_protocols(lua_State *vm) const {
char buf[64];
lua_push_uint64_table_entry(vm, "proto.l4_id", get_protocol());
lua_push_str_table_entry(vm, "proto.l4", get_protocol_name());
if(((get_packets_cli2srv() + get_packets_srv2cli()) >
NDPI_MIN_NUM_PACKETS) ||
(ndpiDetectedProtocol.proto.app_protocol != NDPI_PROTOCOL_UNKNOWN) ||
(iface->is_ndpi_enabled() && detection_completed) ||
iface->isSampledTraffic() || (iface->getIfType() == interface_type_ZMQ) ||
(iface->getIfType() == interface_type_SYSLOG) ||
(iface->getIfType() == interface_type_ZC_FLOW)) {
lua_push_str_table_entry(vm, "proto.ndpi",
get_detected_protocol_name(buf, sizeof(buf)));
lua_push_uint64_table_entry(vm, "proto.ndpi_id",
ndpiDetectedProtocol.proto.app_protocol);
lua_push_uint64_table_entry(vm, "proto.ndpi_informative_proto",
(!ndpi_is_subprotocol_informative(iface->get_ndpi_struct(),
ndpiDetectedProtocol.proto.master_protocol))
? ndpiDetectedProtocol.proto.app_protocol
: ndpiDetectedProtocol.proto.master_protocol);
lua_push_uint64_table_entry(vm, "proto.master_ndpi_id",
ndpiDetectedProtocol.proto.master_protocol);
} else {
lua_push_str_table_entry(vm, "proto.ndpi", (char *)CONST_TOO_EARLY);
lua_push_int32_table_entry(vm, "proto.ndpi_id", -1);
lua_push_int32_table_entry(vm, "proto.master_ndpi_id", -1);
}
lua_push_str_table_entry(vm, "proto.ndpi_breed", get_protocol_breed_name());
lua_push_bool_table_entry(vm, "proto.is_encrypted", isEncryptedProto());
lua_push_uint64_table_entry(vm, "proto.ndpi_cat_id", get_protocol_category());
lua_push_str_table_entry(vm, "proto.ndpi_cat", get_protocol_category_name());
if(getAddressFamilyProtocol())
lua_push_str_table_entry(vm, "proto.ndpi_address_family",
getAddressFamilyProtocol());
if(get_custom_category_file())
lua_push_str_table_entry(vm, "proto.ndpi_cat_file",
get_custom_category_file());
}
/* ***************************************************** */
void Flow::lua_get_bytes(lua_State *vm) const {
lua_push_uint64_table_entry(vm, "bytes",
get_bytes_cli2srv() + get_bytes_srv2cli());
lua_push_uint64_table_entry(vm, "bytes_sent", get_bytes_cli2srv());
lua_push_uint64_table_entry(vm, "bytes_rcvd", get_bytes_srv2cli());
lua_push_uint64_table_entry(vm, "goodput_bytes",
get_goodput_bytes_cli2srv() + get_goodput_bytes_srv2cli());
lua_push_uint64_table_entry(vm, "bytes.last",
get_current_bytes_cli2srv() + get_current_bytes_srv2cli());
lua_push_uint64_table_entry(vm, "goodput_bytes.last",
get_current_goodput_bytes_cli2srv() +
get_current_goodput_bytes_srv2cli());
}
/* ***************************************************** */
void Flow::lua_get_throughput(lua_State *vm) const {
lua_push_float_table_entry(vm, "average_throughput_bps",
(get_bytes_cli2srv() + get_bytes_srv2cli()) / get_duration());
lua_push_float_table_entry(vm, "average_throughput_pps",
(get_packets_cli2srv() + get_packets_srv2cli()) / get_duration());
// overall throughput stats
lua_push_float_table_entry(vm, "top_throughput_bps", top_bytes_thpt);
lua_push_float_table_entry(vm, "throughput_bps", get_bytes_thpt());
lua_push_uint64_table_entry(vm, "throughput_trend_bps", bytes_thpt_trend);
lua_push_float_table_entry(vm, "top_throughput_pps", top_pkts_thpt);
lua_push_float_table_entry(vm, "throughput_pps", get_pkts_thpt());
lua_push_uint64_table_entry(vm, "throughput_trend_pps", pkts_thpt_trend);
}
/* ***************************************************** */
void Flow::lua_get_dir_traffic(lua_State *vm, bool cli2srv) const {
ndpi_analyze_struct *cur_analyze =
(ndpi_analyze_struct *)stats.get_analize_struct(cli2srv);
const IPPacketStats *cur_ip_stats = cli2srv ? &ip_stats_s2d : &ip_stats_d2s;
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.bytes" : "srv2cli.bytes",
cli2srv ? get_bytes_cli2srv() : get_bytes_srv2cli());
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.goodput_bytes" : "srv2cli.goodput_bytes",
cli2srv ? get_goodput_bytes_cli2srv() : get_goodput_bytes_srv2cli());
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.packets" : "srv2cli.packets",
cli2srv ? get_packets_cli2srv() : get_packets_srv2cli());
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.last" : "srv2cli.last",
cli2srv ? get_current_bytes_cli2srv() : get_current_bytes_srv2cli());
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.pkt_len.min" : "srv2cli.pkt_len.min",
ndpi_data_min(cur_analyze));
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.pkt_len.max" : "srv2cli.pkt_len.max",
ndpi_data_max(cur_analyze));
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.pkt_len.avg" : "srv2cli.pkt_len.avg",
ndpi_data_average(cur_analyze));
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.pkt_len.stddev" : "srv2cli.pkt_len.stddev",
ndpi_data_stddev(cur_analyze));
lua_push_uint64_table_entry(
vm, cli2srv ? "cli2srv.fragments" : "srv2cli.fragments",
cur_ip_stats->pktFrag);
}
/* ***************************************************** */
void Flow::lua_get_dir_iat(lua_State *vm, bool cli2srv) const {
InterarrivalStats *s = cli2srv ? getCli2SrvIATStats() : getSrv2CliIATStats();
if(s) {
lua_newtable(vm);
lua_push_uint64_table_entry(vm, "min", s->getMin());
lua_push_uint64_table_entry(vm, "max", s->getMax());
lua_push_float_table_entry(vm, "avg", s->getAvg());
lua_push_float_table_entry(vm, "stddev", s->getStdDev());
lua_pushstring(vm,
cli2srv ? "interarrival.cli2srv" : "interarrival.srv2cli");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
}
/* ***************************************************** */
void Flow::lua_get_packets(lua_State *vm) const {
lua_push_uint64_table_entry(vm, "packets",
get_packets_cli2srv() + get_packets_srv2cli());
lua_push_uint64_table_entry(vm, "packets.sent", get_packets_cli2srv());
lua_push_uint64_table_entry(vm, "packets.rcvd", get_packets_srv2cli());
lua_push_uint64_table_entry(
vm, "packets.last",
get_current_packets_cli2srv() + get_current_packets_srv2cli());
}
/* ***************************************************** */
void Flow::lua_get_time(lua_State *vm) const {
lua_push_uint64_table_entry(vm, "seen.first", get_first_seen());
lua_push_uint64_table_entry(vm, "seen.last", get_last_seen());
lua_push_uint64_table_entry(vm, "duration", get_duration());
}
/* ***************************************************** */
void Flow::lua_get_ip(lua_State *vm, bool client) const {
char buf[64];
Host *h = client ? get_cli_host() : get_srv_host();
const IpAddress *h_ip = client ? get_cli_ip_addr() : get_srv_ip_addr();
bool mask_host = true;
if(h) {
mask_host = Utils::maskHost(h->isLocalHost());
lua_push_str_table_entry(
vm, client ? "cli.ip" : "srv.ip",
h->get_ip()->printMask(buf, sizeof(buf), h->isLocalHost()));
lua_push_uint64_table_entry(vm, client ? "cli.key" : "srv.key",
mask_host ? 0 : h->key());
if(h->isProtocolServer())
lua_push_bool_table_entry(vm, client ? "cli.protocol_server" : "srv.protocol_server", true);
} else if(h_ip) {
/* Host hasn't been instantiated but we still have the ip address (e.g, in
* viewed interfaces) */
lua_push_str_table_entry(vm, client ? "cli.ip" : "srv.ip",
h_ip->print(buf, sizeof(buf)));
lua_push_uint64_table_entry(vm, client ? "cli.key" : "srv.key",
h_ip->key());
}
if(get_vlan_id())
lua_push_uint64_table_entry(vm, client ? "cli.vlan" : "srv.vlan",
get_vlan_id());
lua_push_bool_table_entry(
vm, client ? "cli.broadmulticast" : "srv.broadmulticast",
h_ip->isBroadMulticastAddress());
}
/* ***************************************************** */
void Flow::lua_get_mac(lua_State *vm, bool client) const {
char buf[24];
Host *h = client ? get_cli_host() : get_srv_host();
if(h) {
Mac *m = h->getMac();
if(m != NULL) {
lua_push_str_table_entry(vm, client ? "cli.mac" : "srv.mac",
m->print(buf, sizeof(buf)));
lua_push_bool_table_entry(vm, client ? "cli.serialize_by_mac" : "srv.serialize_by_mac",
h->serializeByMac());
}
}
}
/* ***************************************************** */
void Flow::lua_get_info(lua_State *vm, bool client) const {
char buf[64];
Host *h = client ? get_cli_host() : get_srv_host();
const IpAddress *h_ip = client ? get_cli_ip_addr() : get_srv_ip_addr();
bool mask_host = true;
if(h) {
mask_host = Utils::maskHost(h->isLocalHost());
if(!mask_host) {
char cb[64], os[64];
lua_push_str_table_entry(vm, client ? "cli.host" : "srv.host",
h->get_visual_name(buf, sizeof(buf)));
lua_push_uint64_table_entry(vm,
client ? "cli.source_id" : "srv.source_id",
0 /* was never set by src->getSourceId()*/);
lua_push_bool_table_entry(vm, client ? "cli.localhost" : "srv.localhost",
h->isLocalHost());
lua_push_bool_table_entry(vm, client ? "cli.systemhost" : "srv.systemhost", h->isSystemHost());
lua_push_bool_table_entry(vm, client ? "cli.blacklisted" : "srv.blacklisted",
client ? isBlacklistedClient() : isBlacklistedServer());
lua_push_bool_table_entry(vm,
client ? "cli.broadcast_domain_host" : "srv.broadcast_domain_host",
h->isBroadcastDomainHost());
lua_push_bool_table_entry(vm, client ? "cli.dhcpHost" : "srv.dhcpHost",
h->isDHCPHost());
lua_push_int32_table_entry(vm,
client ? "cli.network_id" : "srv.network_id",
h->get_local_network_id());
lua_push_uint64_table_entry(vm, client ? "cli.pool_id" : "srv.pool_id",
h->get_host_pool());
lua_push_uint64_table_entry(vm, client ? "cli.asn" : "srv.asn",
h->get_asn());
lua_push_str_table_entry(vm, client ? "cli.country" : "srv.country",
h->get_country(cb, sizeof(cb)));
lua_push_str_table_entry(vm, client ? "cli.os" : "srv.os",
h->getOSDetail(os, sizeof(os)));
}
}
lua_get_mac(vm, client);
if(h_ip) {
lua_push_bool_table_entry(vm, client ? "cli.private" : "srv.private",
h_ip->isPrivateAddress());
lua_push_bool_table_entry(vm, client ? "cli.localhost" : "srv.localhost",
h_ip->isLocalHost());
}
}
/* ***************************************************** */
/* Get minimal flow information.
* NOTE: this is intended to be called only from flow user scripts
* via flow.getInfo(). mask_host/allowed networks are not honored.
*/
void Flow::lua_get_min_info(lua_State *vm) {
char buf[128];
lua_newtable(vm);
lua_push_str_table_entry(vm, "cli.ip",
get_cli_ip_addr()->print(buf, sizeof(buf)));
lua_push_str_table_entry(vm, "srv.ip",
get_srv_ip_addr()->print(buf, sizeof(buf)));
if(get_cli_host() && get_cli_host()->isProtocolServer())
lua_push_bool_table_entry(vm, "cli.protocol_server", true);
else if(get_srv_host() && get_srv_host()->isProtocolServer())
lua_push_bool_table_entry(vm, "srv.protocol_server", true);
lua_push_int32_table_entry(vm, "cli.port", get_cli_port());
lua_push_int32_table_entry(vm, "srv.port", get_srv_port());
lua_push_bool_table_entry(vm, "cli.localhost", getCliLocation() == 1);
lua_push_bool_table_entry(vm, "srv.localhost", getSrvLocation() == 1);
lua_push_int32_table_entry(vm, "duration", get_duration());
lua_push_str_table_entry(vm, "proto.l4", get_protocol_name());
lua_push_str_table_entry(vm, "proto.ndpi",
get_detected_protocol_name(buf, sizeof(buf)));
lua_push_str_table_entry(
vm, "proto.ndpi_app",
ndpi_get_proto_name(iface->get_ndpi_struct(),
ndpiDetectedProtocol.proto.app_protocol));
lua_push_str_table_entry(vm, "proto.ndpi_cat", get_protocol_category_name());
lua_push_uint64_table_entry(vm, "proto.ndpi_cat_id", get_protocol_category());
lua_push_str_table_entry(vm, "proto.ndpi_breed", get_protocol_breed_name());
lua_push_bool_table_entry(vm, "proto.is_encrypted", isEncryptedProto());
lua_push_uint64_table_entry(vm, "cli2srv.bytes", get_bytes_cli2srv());
lua_push_uint64_table_entry(vm, "srv2cli.bytes", get_bytes_srv2cli());
lua_push_uint64_table_entry(vm, "cli2srv.packets", get_packets_cli2srv());
lua_push_uint64_table_entry(vm, "srv2cli.packets", get_packets_srv2cli());
lua_push_str_table_entry(vm, "info", getFlowInfo(true).c_str());
}
/* ***************************************************** */
/*
* Get minimal flow information.
* NOTE: this is intended to be called only from flow user scripts
*/
void Flow::getInfo(ndpi_serializer *serializer) {
char buf[128];
ndpi_serialize_string_string(serializer, "cli.ip",
get_cli_ip_addr()->print(buf, sizeof(buf)));
ndpi_serialize_string_string(serializer, "srv.ip",
get_srv_ip_addr()->print(buf, sizeof(buf)));
if(get_cli_host() && get_cli_host()->isProtocolServer())
ndpi_serialize_string_boolean(serializer, "cli.protocol_server", true);
else if(get_srv_host() && get_srv_host()->isProtocolServer())
ndpi_serialize_string_boolean(serializer, "srv.protocol_server", true);
ndpi_serialize_string_int32(serializer, "cli.port", get_cli_port());
ndpi_serialize_string_int32(serializer, "srv.port", get_srv_port());
ndpi_serialize_string_boolean(serializer, "cli.localhost", getCliLocation() == 1);
ndpi_serialize_string_boolean(serializer, "srv.localhost", getSrvLocation() == 1);
ndpi_serialize_string_int32(serializer, "duration", get_duration());
ndpi_serialize_string_string(serializer, "proto.l4", get_protocol_name());
ndpi_serialize_string_string(serializer, "proto.ndpi",
get_detected_protocol_name(buf, sizeof(buf)));
ndpi_serialize_string_string(
serializer, "proto.ndpi_app",
ndpi_get_proto_name(iface->get_ndpi_struct(),
ndpiDetectedProtocol.proto.app_protocol));
ndpi_serialize_string_string(serializer, "proto.ndpi_cat",
get_protocol_category_name());
ndpi_serialize_string_uint64(serializer, "proto.ndpi_cat_id",
get_protocol_category());
ndpi_serialize_string_string(serializer, "proto.ndpi_breed",
get_protocol_breed_name());
ndpi_serialize_string_uint64(serializer, "cli2srv.bytes",
get_bytes_cli2srv());
ndpi_serialize_string_uint64(serializer, "srv2cli.bytes",
get_bytes_srv2cli());
ndpi_serialize_string_uint64(serializer, "cli2srv.packets",
get_packets_cli2srv());
ndpi_serialize_string_uint64(serializer, "srv2cli.packets",
get_packets_srv2cli());
ndpi_serialize_string_string(serializer, "info", getFlowInfo(true).c_str());
}
/* ***************************************************** */
u_int32_t Flow::getCliTcpIssues() {
return (stats.get_cli2srv_tcp_retr() + stats.get_cli2srv_tcp_ooo() +
stats.get_cli2srv_tcp_lost());
}
/* ***************************************************** */
u_int32_t Flow::getSrvTcpIssues() {
return (stats.get_srv2cli_tcp_retr() + stats.get_srv2cli_tcp_ooo() +
stats.get_srv2cli_tcp_lost());
}
/* ***************************************************** */
double Flow::getCliRetrPercentage() {
if(get_packets_cli2srv() >
10 /* Do not compute retransmissions with too few packets */)
return ((double)stats.get_cli2srv_tcp_retr() /
(double)get_packets_cli2srv());
else
return 0;
}
/* ***************************************************** */
double Flow::getSrvRetrPercentage() {
if(get_packets_srv2cli() >
10 /* Do not compute retransmissions with too few packets */)
return ((double)stats.get_srv2cli_tcp_retr() /
(double)get_packets_srv2cli());
else
return 0;
}
/* ***************************************************** */
void Flow::lua_duration_info(lua_State *vm) {
lua_newtable(vm);
lua_push_uint64_table_entry(vm, "first_seen", get_first_seen());
lua_push_uint64_table_entry(vm, "last_seen", get_last_seen());
lua_push_bool_table_entry(vm, "twh_over", twh_over ? true : false);
}
/* ***************************************************** */
void Flow::lua_snmp_info(lua_State *vm) {
char str[16];
u_int32_t device_ip = htonl(flow_device.device_ip);
inet_ntop(AF_INET, &(device_ip), str, INET_ADDRSTRLEN);
lua_push_str_table_entry(vm, "device_ip", str);
lua_push_uint64_table_entry(vm, "in_index", flow_device.in_index);
lua_push_uint64_table_entry(vm, "out_index", flow_device.out_index);
lua_push_uint64_table_entry(vm, "observation_point_id",
flow_device.observation_point_id);
}
/* ***************************************************** */
void Flow::lua_device_protocol_allowed_info(lua_State *vm) {
bool cli_allowed, srv_allowed;
lua_newtable(vm);
if(!cli_host || !srv_host) return;
cli_allowed = isCliDeviceAllowedProtocol();
srv_allowed = isSrvDeviceAllowedProtocol();
lua_push_int32_table_entry(vm, "cli.devtype",
cli_host->getMac()
? cli_host->getMac()->getDeviceType()
: device_unknown);
lua_push_int32_table_entry(vm, "srv.devtype",
srv_host->getMac()
? srv_host->getMac()->getDeviceType()
: device_unknown);
lua_push_bool_table_entry(vm, "cli.allowed", cli_allowed);
if(!cli_allowed)
lua_push_int32_table_entry(vm, "cli.disallowed_proto",
getCliDeviceDisallowedProtocol());
lua_push_bool_table_entry(vm, "srv.allowed", srv_allowed);
if(!srv_allowed)
lua_push_int32_table_entry(vm, "srv.disallowed_proto",
getSrvDeviceDisallowedProtocol());
}
/* ***************************************************** */
void Flow::lua_get_unicast_info(lua_State *vm) const {
const IpAddress *cli_ip = get_cli_ip_addr();
const IpAddress *srv_ip = get_srv_ip_addr();
lua_newtable(vm);
if(cli_ip)
lua_push_bool_table_entry(vm, "cli.broadmulticast",
cli_ip->isBroadMulticastAddress());
if(srv_ip)
lua_push_bool_table_entry(vm, "srv.broadmulticast",
srv_ip->isBroadMulticastAddress());
}
/* ***************************************************** */
void Flow::lua_get_sip_info(lua_State *vm) const {
if(isSIP()) {
lua_push_str_table_entry(vm, "protos.sip_call_id",
protos.sip.call_id);
}
}
/* ***************************************************** */
void Flow::lua_get_tls_info(lua_State *vm) const {
if(isTLS()) {
lua_push_int32_table_entry(vm, "protos.tls_version",
protos.tls.tls_version);
if(protos.tls.server_names)
lua_push_str_table_entry(vm, "protos.tls.server_names",
protos.tls.server_names);
if(protos.tls.client_alpn)
lua_push_str_table_entry(vm, "protos.tls.client_alpn",
protos.tls.client_alpn);
if(protos.tls.client_tls_supported_versions)
lua_push_str_table_entry(vm, "protos.tls.client_tls_supported_versions",
protos.tls.client_tls_supported_versions);
if(protos.tls.issuerDN)
lua_push_str_table_entry(vm, "protos.tls.issuerDN", protos.tls.issuerDN);
if(protos.tls.subjectDN)
lua_push_str_table_entry(vm, "protos.tls.subjectDN",
protos.tls.subjectDN);
if(protos.tls.client_requested_server_name)
lua_push_str_table_entry(vm, "protos.tls.client_requested_server_name",
protos.tls.client_requested_server_name);
if(protos.tls.notBefore && protos.tls.notAfter) {
lua_push_uint32_table_entry(vm, "protos.tls.notBefore",
protos.tls.notBefore);
lua_push_uint32_table_entry(vm, "protos.tls.notAfter",
protos.tls.notAfter);
}
if(protos.tls.ja4.client_hash) {
lua_push_str_table_entry(vm, "protos.tls.ja4.client_hash",
protos.tls.ja4.client_hash);
if(has_malicious_cli_signature)
lua_push_bool_table_entry(vm, "protos.tls.ja4.client_malicious", true);
}
}
}
/* ***************************************************** */
void Flow::getTLSInfo(ndpi_serializer *serializer) const {
if(isTLS()) {
ndpi_serialize_string_int32(serializer, "tls_version",
protos.tls.tls_version);
if(protos.tls.server_names)
ndpi_serialize_string_string(serializer, "server_names",
protos.tls.server_names);
if(protos.tls.client_alpn)
ndpi_serialize_string_string(serializer, "client_alpn",
protos.tls.client_alpn);
if(protos.tls.client_tls_supported_versions)
ndpi_serialize_string_string(serializer, "client_tls_supported_versions",
protos.tls.client_tls_supported_versions);
if(protos.tls.issuerDN)
ndpi_serialize_string_string(serializer, "issuerDN", protos.tls.issuerDN);
if(protos.tls.subjectDN)
ndpi_serialize_string_string(serializer, "subjectDN",
protos.tls.subjectDN);
if(protos.tls.client_requested_server_name)
ndpi_serialize_string_string(serializer, "client_requested_server_name",
protos.tls.client_requested_server_name);
if(protos.tls.notBefore && protos.tls.notAfter) {
ndpi_serialize_string_int32(serializer, "notBefore",
protos.tls.notBefore);
ndpi_serialize_string_int32(serializer, "notAfter", protos.tls.notAfter);
}
if(protos.tls.ja4.client_hash) {
ndpi_serialize_string_string(serializer, "ja4_client_hash",
protos.tls.ja4.client_hash);
if(has_malicious_cli_signature)
ndpi_serialize_string_boolean(serializer, "ja4_client_malicious", true);
}
}
}
/* ***************************************************** */
void Flow::lua_get_ssh_info(lua_State *vm) const {
if(isSSH()) {
if(protos.ssh.client_signature)
lua_push_str_table_entry(vm, "protos.ssh.client_signature",
protos.ssh.client_signature);
if(protos.ssh.server_signature)
lua_push_str_table_entry(vm, "protos.ssh.server_signature",
protos.ssh.server_signature);
if(protos.ssh.hassh.client_hash)
lua_push_str_table_entry(vm, "protos.ssh.hassh.client_hash",
protos.ssh.hassh.client_hash);
if(protos.ssh.hassh.server_hash)
lua_push_str_table_entry(vm, "protos.ssh.hassh.server_hash",
protos.ssh.hassh.server_hash);
}
}
/* ***************************************************** */
void Flow::getSSHInfo(ndpi_serializer *serializer) const {
if(isSSH()) {
if(protos.ssh.client_signature)
ndpi_serialize_string_string(serializer, "client_signature",
protos.ssh.client_signature);
if(protos.ssh.server_signature)
ndpi_serialize_string_string(serializer, "server_signature",
protos.ssh.server_signature);
if(protos.ssh.hassh.client_hash)
ndpi_serialize_string_string(serializer, "client_hash_hassh",
protos.ssh.hassh.client_hash);
if(protos.ssh.hassh.server_hash)
ndpi_serialize_string_string(serializer, "server_hash_hassh",
protos.ssh.hassh.server_hash);
}
}
/* ***************************************************** */
void Flow::lua_get_http_info(lua_State *vm) const {
if(isHTTP()) {
if(protos.http.last_url) {
lua_push_str_table_entry(vm, "protos.http.last_method",
ndpi_http_method2str(protos.http.last_method));
lua_push_uint64_table_entry(vm, "protos.http.last_return_code",
protos.http.last_return_code);
lua_push_str_table_entry(vm, "protos.http.last_url",
protos.http.last_url);
if(protos.http.last_user_agent)
lua_push_str_table_entry(vm, "protos.http.last_user_agent",
protos.http.last_user_agent);
if(protos.http.last_server)
lua_push_str_table_entry(vm, "protos.http.last_server",
protos.http.last_server);
}
if(host_server_name)
lua_push_str_table_entry(vm, "protos.http.server_name", host_server_name);
}
}
/* ***************************************************** */
void Flow::getHTTPInfo(ndpi_serializer *serializer) const {
if(isHTTP()) {
if(protos.http.last_url) {
ndpi_serialize_string_string(
serializer, "last_method",
ndpi_http_method2str(protos.http.last_method));
ndpi_serialize_string_uint64(serializer, "last_return_code",
protos.http.last_return_code);
ndpi_serialize_string_string(serializer, "last_url",
protos.http.last_url);
ndpi_serialize_string_string(serializer, "last_user_agent",
protos.http.last_user_agent);
ndpi_serialize_string_string(serializer, "last_server",
protos.http.last_server);
}
if(host_server_name)
ndpi_serialize_string_string(serializer, "server_name", host_server_name);
}
}
/* ***************************************************** */
void Flow::lua_get_dns_info(lua_State *vm) const {
if(isDNS()) {
if(protos.dns.last_query) {
lua_push_uint64_table_entry(vm, "protos.dns.last_query_type",
protos.dns.last_query_type);
lua_push_uint64_table_entry(vm, "protos.dns.last_return_code",
protos.dns.last_return_code);
lua_push_str_table_entry(vm, "protos.dns.last_query",
protos.dns.last_query);
if(protos.dns.last_rsp)
lua_push_str_table_entry(vm, "protos.dns.last_rsp", protos.dns.last_rsp);
if(hasInvalidDNSQueryChars())
lua_push_bool_table_entry(vm, "protos.dns.invalid_chars_in_query", true);
}
}
}
/* ***************************************************** */
void Flow::getDNSInfo(ndpi_serializer *serializer) const {
if(isDNS()) {
if(protos.dns.last_query) {
ndpi_serialize_string_int64(serializer, "last_query_type",
protos.dns.last_query_type);
ndpi_serialize_string_int64(serializer, "last_return_code",
protos.dns.last_return_code);
ndpi_serialize_string_string(serializer, "last_query",
protos.dns.last_query);
if(protos.dns.last_rsp) {
std::string array = Utils::list2JsonArray(protos.dns.last_rsp);
ndpi_serialize_string_raw(serializer, "last_rsp_arr",
array.c_str(), array.length());
}
if(hasInvalidDNSQueryChars())
ndpi_serialize_string_boolean(serializer, "invalid_chars_in_query",
true);
}
}
}
/* ***************************************************** */
void Flow::getICMPInfo(ndpi_serializer *serializer) const {
if(isICMP()) {
ndpi_serialize_string_int32(serializer, "type",
isBidirectional()
? protos.icmp.srv2cli.icmp_type
: protos.icmp.cli2srv.icmp_type);
ndpi_serialize_string_int32(serializer, "code",
isBidirectional()
? protos.icmp.srv2cli.icmp_code
: protos.icmp.cli2srv.icmp_code);
}
}
/* ***************************************************** */
void Flow::getMDNSInfo(ndpi_serializer *serializer) const {
if(isMDNS()) {
ndpi_serialize_string_string(serializer, "answer", protos.mdns.answer);
ndpi_serialize_string_string(serializer, "name", protos.mdns.name);
ndpi_serialize_string_string(serializer, "name_txt", protos.mdns.name_txt);
ndpi_serialize_string_string(serializer, "ssid", protos.mdns.ssid);
}
}
/* ***************************************************** */
void Flow::getNetBiosInfo(ndpi_serializer *serializer) const {
if(isNetBIOS()) {
ndpi_serialize_string_string(serializer, "name", protos.netbios.name);
}
}
/* ***************************************************** */
void Flow::getSIPInfo(ndpi_serializer *serializer) const {
if(protos.sip.call_id) {
ndpi_serialize_string_string(serializer, "call_id", protos.sip.call_id);
}
}
/* ***************************************************** */
void Flow::lua_get_tcp_info(lua_State *vm) const {
if(get_protocol() == IPPROTO_TCP) {
lua_push_bool_table_entry(
vm, "tcp.seq_problems",
(stats.get_cli2srv_tcp_retr() || stats.get_cli2srv_tcp_ooo() ||
stats.get_cli2srv_tcp_lost() || stats.get_cli2srv_tcp_keepalive() ||
stats.get_srv2cli_tcp_retr() || stats.get_srv2cli_tcp_ooo() ||
stats.get_srv2cli_tcp_lost() || stats.get_srv2cli_tcp_keepalive())
? true
: false);
if(tcp != NULL) {
lua_push_float_table_entry(vm, "tcp.nw_latency.3wh_client_rtt", tcp->clientRTT3WH);
lua_push_float_table_entry(vm, "tcp.nw_latency.3wh_server_rtt", tcp->serverRTT3WH);
}
lua_push_float_table_entry(vm, "tcp.appl_latency", applLatencyMsec);
lua_push_float_table_entry(vm, "tcp.max_thpt.cli2srv", getCli2SrvMaxThpt());
lua_push_float_table_entry(vm, "tcp.max_thpt.srv2cli", getSrv2CliMaxThpt());
lua_push_uint64_table_entry(vm, "cli2srv.retransmissions",
stats.get_cli2srv_tcp_retr());
lua_push_uint64_table_entry(vm, "cli2srv.out_of_order",
stats.get_cli2srv_tcp_ooo());
lua_push_uint64_table_entry(vm, "cli2srv.lost",
stats.get_cli2srv_tcp_lost());
lua_push_uint64_table_entry(vm, "cli2srv.keep_alive",
stats.get_cli2srv_tcp_keepalive());
lua_push_uint64_table_entry(vm, "srv2cli.retransmissions",
stats.get_srv2cli_tcp_retr());
lua_push_uint64_table_entry(vm, "srv2cli.out_of_order",
stats.get_srv2cli_tcp_ooo());
lua_push_uint64_table_entry(vm, "srv2cli.lost",
stats.get_srv2cli_tcp_lost());
lua_push_uint64_table_entry(vm, "srv2cli.keep_alive",
stats.get_srv2cli_tcp_keepalive());
lua_push_uint64_table_entry(vm, "cli2srv.tcp_flags", src2dst_tcp_flags);
lua_push_uint64_table_entry(vm, "srv2cli.tcp_flags", dst2src_tcp_flags);
lua_push_bool_table_entry(vm, "tcp_established", isTCPEstablished());
lua_push_bool_table_entry(vm, "tcp_connecting", isTCPConnecting());
lua_push_bool_table_entry(vm, "tcp_closed", isTCPClosed());
lua_push_bool_table_entry(vm, "tcp_reset", isTCPReset());
}
}
/* ***************************************************** */
void Flow::lua_get_port(lua_State *vm, bool client) const {
u_int16_t h_port = client ? get_cli_port() : get_srv_port();
lua_push_uint64_table_entry(vm, client ? "cli.port" : "srv.port", h_port);
}
/* ***************************************************** */
void Flow::lua_get_geoloc(lua_State *vm, bool client, bool coords,
bool country_city) const {
Host *h = client ? get_cli_host() : get_srv_host();
float latitude, longitude;
char buf[32];
if(h) {
if(coords) {
h->get_geocoordinates(&latitude, &longitude);
lua_push_float_table_entry(vm, client ? "cli.latitude" : "srv.latitude",
latitude);
lua_push_float_table_entry(vm, client ? "cli.longitude" : "srv.longitude",
longitude);
}
if(country_city) {
lua_push_str_table_entry(vm, client ? "cli.country" : "srv.country",
h->get_country(buf, sizeof(buf)));
lua_push_str_table_entry(vm, client ? "cli.city" : "srv.city",
h->get_city(buf, sizeof(buf)));
}
}
}
/* ***************************************************** */
bool Flow::hasDissectedTooManyPackets() {
u_int32_t num_packets;
if(iface->isSampledTraffic() || (!iface->is_ndpi_enabled()))
/* Cannot reliably process sampled traffic, giveup the dissection */
return (true);
#ifdef HAVE_NEDGE
/* NOTE: in nEdge packet stats are update periodically, so
* we cannot rely on get_packets() */
if(ndpiFlow) /* WARNING: can wrap! */
num_packets = ndpiFlow->num_processed_pkts;
else
num_packets = get_packets();
#else
num_packets = get_packets();
#endif
return ((num_packets >= NDPI_MIN_NUM_PACKETS) && (!needsExtraDissection()));
}
/* ***************************************************** */
void Flow::setNormalToAlertedCounters() {
Host *cli_h = get_cli_host(), *srv_h = get_srv_host();
if(cli_h) {
u_int32_t local_net_id = cli_h->get_local_network_id();
NetworkStats *net_stats = cli_h->getNetworkStats(local_net_id);
AutonomousSystem *cli_as = cli_h ? cli_h->get_as() : NULL;
if(cli_as) cli_as->incNumAlertedFlows(true /* As client */);
if(net_stats) net_stats->incNumAlertedFlows(true /* As client */);
cli_h->incNumAlertedFlows(true /* As client */);
cli_h->incTotalAlerts();
}
if(srv_h) {
u_int32_t local_net_id = srv_h->get_local_network_id();
NetworkStats *net_stats = srv_h->getNetworkStats(local_net_id);
AutonomousSystem *srv_as = srv_h ? srv_h->get_as() : NULL;
if(srv_as) srv_as->incNumAlertedFlows(true /* As client */);
if(net_stats) net_stats->incNumAlertedFlows(false /* As server */);
srv_h->incNumAlertedFlows(false /* As server */);
srv_h->incTotalAlerts();
}
/* Set this into the partializable flow traffic stats as well (necessary for
* view interfaces) */
stats.setFlowAlerted();
#ifdef ALERTED_FLOWS_DEBUG
iface_alert_inc = true;
#endif
}
/* ***************************************************** */
void Flow::setProtocolJSONInfo() {
ndpi_serializer s;
char *json = NULL;
u_int32_t json_len = 0;
if(ndpi_init_serializer(&s, ndpi_serialization_format_json) == -1)
return;
getProtocolJSONInfo(&s);
getCustomFieldsInfo(&s);
getJSONRiskInfo(&s);
#ifdef NTOPNG_PRO
getQoEInfo(&s);
#endif
getVerdictInfo(&s);
json = ndpi_serializer_get_buffer(&s, &json_len);
if(json_protocol_info) free(json_protocol_info);
json_protocol_info = strdup(json ? json : "");
ndpi_term_serializer(&s);
}
/* ***************************************************** */
void Flow::getJSONRiskInfo(ndpi_serializer *serializer) {
if(serializer && riskInfo) {
ndpi_serialize_string_raw(serializer, "flow_risk_info", riskInfo, strlen(riskInfo));
}
}
/* ***************************************************** */
void Flow::getVerdictInfo(ndpi_serializer *serializer) {
if(serializer) {
#ifdef HAVE_NEDGE
ndpi_serialize_start_of_block(serializer, "verdict"); /* Custom fields block */
/* Using int in case more than 0 and 1 value are going to be used*/
ndpi_serialize_string_uint32(serializer, "pass", passVerdict);
ndpi_serialize_string_uint32(serializer, "drop_reason", dropVerdictReason);
ndpi_serialize_end_of_block(serializer);
#endif
}
}
/* ***************************************************** */
void Flow::getCustomFieldsInfo(ndpi_serializer *serializer) {
if(get_tlv_info()) {
ndpi_serialize_start_of_block(serializer, "custom_fields"); /* Custom fields block */
Utils::tlv2serializer(get_tlv_info(), serializer);
ndpi_serialize_end_of_block(serializer);
}
}
/* ***************************************************** */
void Flow::getProtocolJSONInfo(ndpi_serializer *serializer) {
/* Check JSON info != NULL to not override info */
if(serializer == NULL) return;
u_int16_t l7proto = getLowerProtocol();
ndpi_serialize_start_of_block(serializer, "proto"); /* proto block */
/* Adding protocol info; switch the lower application protocol */
switch (l7proto) {
case NDPI_PROTOCOL_DNS:
ndpi_serialize_start_of_block(serializer, "dns");
getDNSInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
case NDPI_PROTOCOL_HTTP:
case NDPI_PROTOCOL_HTTP_PROXY:
ndpi_serialize_start_of_block(serializer, "http");
getHTTPInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
case NDPI_PROTOCOL_TLS:
case NDPI_PROTOCOL_MAIL_IMAPS:
case NDPI_PROTOCOL_MAIL_SMTPS:
case NDPI_PROTOCOL_MAIL_POPS:
case NDPI_PROTOCOL_QUIC:
ndpi_serialize_start_of_block(serializer, "tls");
getTLSInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
case NDPI_PROTOCOL_IP_ICMP:
case NDPI_PROTOCOL_IP_ICMPV6:
ndpi_serialize_start_of_block(serializer, "icmp");
getICMPInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
case NDPI_PROTOCOL_MDNS:
ndpi_serialize_start_of_block(serializer, "mdns");
getMDNSInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
case NDPI_PROTOCOL_NETBIOS:
ndpi_serialize_start_of_block(serializer, "netbios");
getNetBiosInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
case NDPI_PROTOCOL_SIP:
ndpi_serialize_start_of_block(serializer, "sip");
getSIPInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
case NDPI_PROTOCOL_SSH:
ndpi_serialize_start_of_block(serializer, "ssh");
getSSHInfo(serializer);
ndpi_serialize_end_of_block(serializer);
break;
}
if(getErrorCode() != 0)
ndpi_serialize_string_uint32(serializer, "l7_error_code", getErrorCode());
if(getConfidence() != NDPI_CONFIDENCE_UNKNOWN) {
switch (getConfidence()) {
case NDPI_CONFIDENCE_DPI_CACHE:
case NDPI_CONFIDENCE_DPI:
case NDPI_CONFIDENCE_NBPF:
ndpi_serialize_string_uint32(serializer, "confidence",
(ndpiConfidence)confidence_dpi);
break;
default:
ndpi_serialize_string_uint32(serializer, "confidence",
(ndpiConfidence)confidence_guessed);
break;
}
}
ndpi_serialize_end_of_block(serializer); /* proto block */
if(ebpf && ebpf->process_info_set) {
ndpi_serialize_start_of_block(serializer, "process");
if(ebpf->src_process_info.process_name)
ndpi_serialize_string_string(serializer, "src_process_name",
ebpf->src_process_info.process_name);
if(ebpf->dst_process_info.process_name)
ndpi_serialize_string_string(serializer, "dst_process_name",
ebpf->dst_process_info.process_name);
ndpi_serialize_end_of_block(serializer); /* process block */
}
if(getTrafficStats()) {
ndpi_serialize_start_of_block(serializer, "traffic_stats");
if(getTrafficStats()->get_cli2srv_tcp_retr())
// cli2srv.retransmissions
ndpi_serialize_string_uint32(serializer, "cli2srv_retransmissions",
getTrafficStats()->get_cli2srv_tcp_retr());
if(getTrafficStats()->get_cli2srv_tcp_ooo())
// cli2srv.out_of_order
ndpi_serialize_string_uint32(serializer, "cli2srv_out_of_order",
getTrafficStats()->get_cli2srv_tcp_ooo());
if(getTrafficStats()->get_cli2srv_tcp_lost())
// cli2srv.lost
ndpi_serialize_string_uint32(serializer, "cli2srv_lost",
getTrafficStats()->get_cli2srv_tcp_lost());
if(getTrafficStats()->get_srv2cli_tcp_retr())
// srv2cli.retransmissions
ndpi_serialize_string_uint32(serializer, "srv2cli_retransmissions",
getTrafficStats()->get_srv2cli_tcp_retr());
if(getTrafficStats()->get_srv2cli_tcp_ooo())
// srv2cli.out_of_order
ndpi_serialize_string_uint32(serializer, "srv2cli_out_of_orders",
getTrafficStats()->get_srv2cli_tcp_ooo());
if(getTrafficStats()->get_srv2cli_tcp_lost())
// srv2cli.lost
ndpi_serialize_string_uint32(serializer, "srv2cli_lost",
getTrafficStats()->get_srv2cli_tcp_lost());
ndpi_serialize_end_of_block(serializer); /* traffic_stats block */
}
if(protocol == IPPROTO_TCP && applLatencyMsec > 0)
ndpi_serialize_string_float(serializer, "appl_latency", applLatencyMsec, "%.2f");
if(triggered_alerts.size() > 0) {
ndpi_serialize_start_of_block(serializer, "alert_score");
for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++) {
FlowAlert *alert = it->second;
ndpi_serialize_uint32_uint32(serializer, it->first, alert->getCliScore() + alert->getSrvScore());
}
ndpi_serialize_end_of_block(serializer);
}
}
/* ***************************************************** */
void Flow::setAlertInfo(FlowAlert *alert) {
if(!alert) return;
alert_info.is_cli_attacker |= alert->isCliAttacker();
alert_info.is_cli_victim |= alert->isCliVictim();
alert_info.is_srv_attacker |= alert->isSrvAttacker();
alert_info.is_srv_victim |= alert->isSrvVictim();
if (!alert->autoAck())
alert_info.auto_acknowledge = 0;
}
/* ***************************************************** */
void Flow::updateAlertsJSON() {
ndpi_serializer serializer;
char *json = NULL;
u_int32_t json_len = 0;
if (ndpi_init_serializer(&serializer, ndpi_serialization_format_json) == -1)
return;
/* Global info */
ndpi_serialize_string_uint64(&serializer, "ntopng.key", key());
ndpi_serialize_string_uint64(&serializer, "hash_entry_id",
get_hash_entry_id());
getJSONRiskInfo(&serializer);
if (isBlacklistedFlow())
ndpi_serialize_string_string(&serializer, "blacklist",
get_custom_category_file() ? get_custom_category_file() : "");
/* Serialize alerts JSON */
std::string json_map = "{";
bool first = true;
for (std::map<FlowAlertTypeEnum, FlowAlert *>::iterator it = triggered_alerts.begin(); it != triggered_alerts.end(); it++) {
FlowAlert *a = it->second;
const char *j = a->getSerializedAlert();
if (j) {
if (!first) json_map += ",";
json_map += "\"" + std::to_string(it->first) + "\": " + j;
first = false;
}
}
json_map += "}";
ndpi_serialize_string_raw(&serializer, "alerts", json_map.c_str(), json_map.size());
json = ndpi_serializer_get_buffer(&serializer, &json_len);
if(alerts_json) {
/* Moving to shadow as it may be in use by alert2JSON() or lua() */
if (alerts_json_shadow) free(alerts_json_shadow);
alerts_json_shadow = alerts_json;
}
alerts_json = strdup(json ? json : "");
ndpi_term_serializer(&serializer);
}
/* ***************************************************** */
void Flow::setPredominantAlert(FlowAlertType alert_type, u_int16_t score) {
if(predominant_alert_score) {
/* Decrease the value previously increased for the previous alert (if not
* normal) */
iface->decNumAlertedFlows(
this, Utils::mapScoreToSeverity(predominant_alert_score));
}
/* Increase the value for the newly set level (if not normal) */
iface->incNumAlertedFlows(this, Utils::mapScoreToSeverity(score));
/* Update the current predominant alert and score */
predominant_alert = alert_type;
predominant_alert_score = score;
}
/* ***************************************************** */
/*
This method is called to set score and various other values of the flow
Return true if the activities are completed successfully, of false otherwise
*/
bool Flow::setAlertsMap(FlowAlert *alert) {
FlowAlertType alert_type = alert->getAlertType();
ScoreCategory score_category = Utils::mapAlertToScoreCategory(alert_type.category);
u_int16_t cli_inc = alert->getCliScore();
u_int16_t srv_inc = alert->getSrvScore();
u_int16_t flow_inc = cli_inc + srv_inc;
#ifdef DEBUG_SCORE
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"Set alert score: %u (%u/%u) | MAX VALUE: %u",
flow_inc, cli_inc, srv_inc, SCORE_MAX_VALUE);
#endif
/* Alert type safety checks */
if(alert_type.id == flow_alert_normal) {
#ifdef DEBUG_SCORE
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Discarding alert (normal)");
#endif
delete alert;
return false;
}
/* Check if the same alert has been already triggered and
* accounted in the score */
if(alerts_map.isSetBit(alert_type.id)) {
/* In case of the same alert triggered multiple times, we keep the
* first one only, and send out a single notificaiton. */
#ifdef DEBUG_SCORE
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"[%s] Discarding alert type %u (already set)",
iface->get_name(), alert_type.id);
#endif
delete alert;
return false;
}
/* Check host filter and if such alert needs to be discarded
* due to alert exclusions */
Host *cli_h = get_cli_host(), *srv_h = get_srv_host();
#ifdef NTOPNG_PRO
ViewInterface *viewedBy = getInterface()->viewedBy();
if(viewedBy) {
Mac *srcMac = NULL, *dstMac = NULL;
Host *cli_host, *srv_host;
viewedBy->findFlowHosts(getInterfaceIndex(),
get_vlan_id(), get_observation_point_id(),
getPrivateFlowId(), srcMac,
(IpAddress *)get_cli_ip_addr(), &cli_host, dstMac,
(IpAddress *)get_srv_ip_addr(), &srv_host);
if((cli_host && cli_host->isFlowAlertDisabled(alert_type)) ||
(srv_host && srv_host->isFlowAlertDisabled(alert_type))) {
delete alert;
return false;
}
} else
#endif
{
if((cli_h && cli_h->isFlowAlertDisabled(alert_type)) ||
(srv_h && srv_h->isFlowAlertDisabled(alert_type))) {
#ifdef DEBUG_SCORE
ntop->getTrace()->traceEvent(TRACE_NORMAL,
"Discarding alert (host filter)");
#endif
delete alert;
return false;
}
}
if(!isFlowAlerted())
/* This is the first time an alert is set on this flow. The flow was normal
* and now becomes alerted. */
setNormalToAlertedCounters();
/* Set alerts bitmap */
alerts_map.setBit(alert_type.id);
triggered_alerts[alert_type.id] = alert;
setAlertInfo(alert);
/* Update score */
flow_score += flow_inc;
stats.incScore(cli_inc, score_category, true /* as client */);
stats.incScore(srv_inc, score_category, false /* as server */);
if(!getInterface()->isView()) {
/* Note: For views, score increments are done periodically */
if(cli_h)
cli_h->incScoreValue(cli_inc, score_category, true /* as client */);
if(srv_h)
srv_h->incScoreValue(srv_inc, score_category, false /* as server */);
}
/* Check if predominant alert should be updated */
if(!isFlowAlerted() /* Flow is not yet alerted */
|| getPredominantAlertScore() < flow_inc /* The score of the current alerted alert_type is less than the score of this alert_type */) {
#ifdef DEBUG_SCORE
ntop->getTrace()->traceEvent(
TRACE_NORMAL, "[%s] Setting predominant alert (%u) with score %u",
iface->get_name(), alert_type.id, flow_inc);
#endif
setPredominantAlert(alert_type, flow_inc);
#ifdef DEBUG_SCORE
} else {
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[%s] Discarding alert (%u) [score %u <= %u]",
iface->get_name(), alert_type.id, flow_inc, getPredominantAlertScore());
#endif
}
return true;
}
/* *************************************** */
/*
Trigger an alert.
Called by FlowCheck subclasses to trigger a flow alert. Setting sync
will causes the alert (FlowAlert) to be immediatly enqueued to recipients.
Note: alert is released then the flow is released * do NOT free on the caller *
*/
bool Flow::triggerAlert(FlowAlert *alert, bool sync) {
bool res;
if (alert == NULL) return false;
res = setAlertsMap(alert);
if (res) {
pending_alerts = true;
if (sync) flushAlerts();
}
return res;
}
/* *************************************** */
/*
Refresh alert stored in flows (rebuild JSON) as the content of some
of them have been updated (e.g. re-triggered) and the value may be changed
(e.g. the value that exceeded a threshold has increased further)
*/
void Flow::refreshAlert(FlowAlertTypeEnum alert_type) {
FlowAlert *alert = getTriggeredAlert(alert_type);
if (alert) {
alert->refreshAlert();
refresh_triggered_alerts = true;
}
}
/* *************************************** */
/*
Alerts are not flushed immediatly as optimization (unless sync is set in
triggerAlert). If pending_alerts, they are enqueued to recipients, in a single
notification for the predominant one.
*/
void Flow::flushAlerts() {
if (!pending_alerts && !refresh_triggered_alerts) return;
/* Update JSON */
updateAlertsJSON();
if (pending_alerts && !ntop->getPrefs()->dontEmitFlowAlerts()) {
/* Always enqueue the predominant alert, with json info for all alerts */
FlowAlert *alert = triggered_alerts[predominant_alert.id];
/* Enqueue the alert */
iface->enqueueFlowAlert(alert);
}
pending_alerts = refresh_triggered_alerts = false;
}
/* *************************************** */
void Flow::setExternalAlert(json_object *a) {
/* In order to avoid concurrency issues with the getter, at most
* 1 pending external alert is supported. */
if(!external_alert.json) {
json_object *val;
if(!iface->hasSeenExternalAlerts()) iface->setSeenExternalAlerts();
if(json_object_object_get_ex(a, "source", &val))
external_alert.source = strdup(json_object_get_string(val));
external_alert.json = a;
/* Manually trigger a periodic update to process the alert */
trigger_immediate_periodic_update = true;
}
}
/* *************************************** */
void Flow::luaRetrieveExternalAlert(lua_State *vm) {
const char *json = external_alert.json
? json_object_to_json_string(external_alert.json)
: NULL;
if(json)
lua_pushstring(vm, json);
else
lua_pushnil(vm);
}
/* *************************************** */
#ifdef ENABLE_ENTROPHY_CALCULATION
void Flow::updateEntropy(struct ndpi_analyze_struct *e, u_int8_t *payload,
u_int payload_len) {
if(e != NULL) {
for (u_int i = 0; i < payload_len; i++) ndpi_data_add_value(e, payload[i]);
}
}
#endif
/* *************************************** */
#ifdef ENABLE_ENTROPHY_CALCULATION
void Flow::lua_entropy(lua_State *vm) {
if(initial_bytes_entropy.c2s && initial_bytes_entropy.s2c) {
lua_newtable(vm);
lua_push_float_table_entry(vm, "client", getEntropy(true));
lua_push_float_table_entry(vm, "server", getEntropy(false));
if(protocol == IPPROTO_ICMP) {
if(protos.icmp.client_to_server.min_entropy != 0) {
lua_newtable(vm);
lua_push_float_table_entry(vm, "min",
protos.icmp.client_to_server.min_entropy);
lua_push_float_table_entry(vm, "max",
protos.icmp.client_to_server.max_entropy);
lua_pushstring(vm, "icmp");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
}
lua_pushstring(vm, "entropy");
lua_insert(vm, -2);
lua_settable(vm, -3);
}
}
#endif
/* *************************************** */
void Flow::check_swap()
/* NOTE: keep in sync with ZMQParserInterface::preprocessFlow() */{
if(((protocol == IPPROTO_TCP) && ((src2dst_tcp_flags & TH_SYN) == TH_SYN) /* Ignore in case we have seen a SYN */)
|| (get_cli_port() == 0) || (get_srv_port() == 0))
return;
if(!(get_cli_ip_addr()->isNonEmptyUnicastAddress()
/* Everything that is NOT unicast-to-non-unicast needs to be checked */
&& (!get_srv_ip_addr()->isNonEmptyUnicastAddress()))
// && get_cli_port() < 1024 /* Relax this constraint and also apply to
// non-well-known ports such as 8080 */
) {
if(get_cli_port() < get_srv_port()) {
if(protocol == IPPROTO_UDP) /* && (get_cli_port() > 32768) && (get_srv_port() > 32768) */ {
/* We disable UDP swap that might be wrong in particular for probing attempts */
; /* Don't do anything: this might be RTP or similar */
} else {
request_swap(); /* This flow will be swapped */
}
} else {
/* The RDP port is > 1024 hence we need this extra logic */
if((ndpiDetectedProtocol.proto.app_protocol == NDPI_PROTOCOL_RDP) && (get_cli_port() == 3389))
request_swap(); /* This flow will be swapped */
}
}
}
/* *************************************** */
void Flow::setJSONRiskInfo(char *r) {
if(!r) return;
if(riskInfo) free(riskInfo);
riskInfo = strdup(r);
}
/* *************************************** */
void Flow::setEndReason(char *r) {
if(!r) return;
if(end_reason) free(end_reason);
end_reason = strdup(r);
}
/* *************************************** */
char *Flow::getEndReason() { return (end_reason); }
/* *************************************** */
void Flow::setWLANInfo(char *_wlan_ssid, u_int8_t *_wtp_mac_address) {
allocateCollection();
if(collection) {
if(_wlan_ssid) {
if(collection->wifi.wlan_ssid)
free(collection->wifi.wlan_ssid);
collection->wifi.wlan_ssid = strdup(_wlan_ssid);
}
memcpy(collection->wifi.wtp_mac_address, _wtp_mac_address, 6);
}
}
/* *************************************** */
void Flow::setSMTPMailFrom(char *r) {
if(!r) return;
if(protos.smtp.mail_from) free(protos.smtp.mail_from);
protos.smtp.mail_from = strdup(r);
}
/* *************************************** */
char *Flow::getSMTPMailFrom() { return (protos.smtp.mail_from); }
/* *************************************** */
void Flow::setSMTPRcptTo(char *r) {
if(!r) return;
if(protos.smtp.rcpt_to) free(protos.smtp.rcpt_to);
protos.smtp.rcpt_to = strdup(r);
}
/* *************************************** */
char *Flow::getSMTPRcptTo() { return (protos.smtp.rcpt_to); }
/* *************************************** */
void Flow::setFlowRiskName(char *r) {
if(!r) return;
if(ndpiFlowRiskName) free(ndpiFlowRiskName);
ndpiFlowRiskName = strdup(r);
}
/* *************************************** */
char *Flow::getFlowRiskName() { return (ndpiFlowRiskName); }
/* *************************************** */
/* The alert will be triggered by src/flow_checks/CustomFlowLuaScript.cpp */
void Flow::triggerCustomFlowAlert(u_int8_t score, char *msg) {
customFlowAlert.alertTriggered = true, customFlowAlert.score = score;
if(customFlowAlert.msg) {
free(customFlowAlert.msg);
customFlowAlert.msg = NULL;
}
if(msg) customFlowAlert.msg = strdup(msg);
}
/* *************************************** */
void Flow::swap() {
IpAddress *i = cli_ip_addr;
u_int8_t m[6];
u_int8_t f1 = alert_info.is_cli_attacker;
u_int8_t f2 = alert_info.is_cli_victim;
#ifdef ENABLE_ENTROPHY_CALCULATION
struct ndpi_analyze_struct *s = initial_bytes_entropy.c2s;
#endif
TCPSeqNum ts;
InterarrivalStats *is = cli2srvPktTime;
time_t now = time(NULL);
u_int32_t tmp32;
#if 0
char buf[128];
ntop->getTrace()->traceEvent(TRACE_NORMAL, "Swapping %s", print(buf, sizeof(buf)));
#endif
if(cli_host && srv_host) {
/* Not a view interface */
Host *h = cli_host;
cli_host->decNumFlows(now, true /* as client */, isTCP(), twh_over),
srv_host->decNumFlows(now, false /* as server */, isTCP(), twh_over);
cli_host = srv_host, cli_ip_addr = srv_ip_addr;
srv_host = h, srv_ip_addr = i;
cli_host->incNumFlows(now, true /* as client */, isTCP()),
srv_host->incNumFlows(now, false /* as server */, isTCP());
} else {
/* This is a view interface */
if(cli_ip_addr && (cli_host == NULL)
&& srv_ip_addr && (srv_host == NULL)) {
IpAddress *c = cli_ip_addr;
cli_ip_addr = srv_ip_addr;
srv_ip_addr = c;
}
}
Utils::swap16(&cli_port, &srv_port), Utils::swap32(&srcAS, &dstAS), Utils::swap32(&srcPeerAS, &dstPeerAS);
Utils::swap8(&src2dst_tcp_flags, &dst2src_tcp_flags);
#ifdef ENABLE_ENTROPHY_CALCULATION
initial_bytes_entropy.c2s = initial_bytes_entropy.s2c;
initial_bytes_entropy.s2c = s;
#endif
memcpy(m, view_cli_mac, 6);
memcpy(view_cli_mac, view_srv_mac, 6);
memcpy(view_srv_mac, m, 6);
stats.swap();
alert_info.is_cli_attacker = alert_info.is_srv_attacker,
alert_info.is_cli_victim = alert_info.is_srv_victim;
alert_info.is_srv_attacker = f1,
alert_info.is_srv_victim = f2;
if(tcp != NULL) {
memcpy(&ts, &tcp->tcp_seq_s2d, sizeof(TCPSeqNum));
memcpy(&tcp->tcp_seq_d2s, &tcp->tcp_seq_s2d, sizeof(TCPSeqNum));
memcpy(&tcp->tcp_seq_s2d, &ts, sizeof(TCPSeqNum));
Utils::swap16(&tcp->cli2srv_window, &tcp->srv2cli_window);
}
cli2srvPktTime = srv2cliPktTime;
srv2cliPktTime = is;
#ifdef HAVE_NEDGE
TrafficShaper *s1 = flowShapers.cli;
flowShapers.cli = flowShapers.srv;
flowShapers.srv = s1;
#endif
tmp32 = flow_device.in_index;
flow_device.in_index = flow_device.out_index;
flow_device.out_index = tmp32;
if(tcp != NULL) {
u_int32_t tmp = tcp->rtt.last_cli_ack;
tcp->rtt.last_cli_ack = tcp->rtt.last_srv_ack;
tcp->rtt.last_srv_ack = tmp;
}
if(last_db_dump.partial) last_db_dump.partial->swap();
last_db_dump.delta.swap();
if(periodic_stats_update_partial) periodic_stats_update_partial->swap();
c2sFirstGoodputTime.tv_sec = 0;
/*
We do not swap L7 info as if it direction was wrong they were not computed
Same applies with latency counters
*/
swap_done = 1, swap_requested = 0;
}
/* *************************************** */
void Flow::updateTCPHostServices(Host *cli_h, Host *srv_h) {
switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
case NDPI_PROTOCOL_SSH:
case NDPI_PROTOCOL_TLS:
if(tcp) {
if((((src2dst_tcp_flags & TH_SYN) == 0) && ((dst2src_tcp_flags & TH_SYN) != 0))
|| ((((src2dst_tcp_flags|dst2src_tcp_flags) & TH_SYN) == 0) /* No SYN observed */
&& (get_cli_port() < get_srv_port()))) {
request_swap();
}
}
break;
default:
break;
} /* switch */
}
/* *************************************** */
void Flow::updateUDPHostServices(bool src2dst_direction) {
Host *cli_h, *srv_h;
get_actual_peers(&cli_h, &srv_h);
switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
case NDPI_PROTOCOL_DHCP:
if(getConfidence() == NDPI_CONFIDENCE_DPI) {
if(cli_port == htons(67)) {
/* Server -> Client */
if(cli_host && (!cli_host->isBroadcastHost())) {
cli_host->setDhcpServer();
} else if(cli_ip_addr && !cli_ip_addr->isBroadcastAddress()) {
cli_ip_addr->setDhcpServer();
}
} else {
if(srv_host && (!srv_host->isBroadcastHost())) {
srv_host->setDhcpServer();
} else if(srv_ip_addr && !srv_ip_addr->isBroadcastAddress()) {
srv_ip_addr->setDhcpServer();
}
if(ndpiFlow && cli_host)
cli_host->offlineSetDhcpFingerprint(ndpiFlow->protos.dhcp.fingerprint);
}
}
break;
case NDPI_PROTOCOL_DNS:
/* Swap check */
if((!swap_requested)
&& (ndpiFlow != NULL)
&& iface->isPacketInterface()) {
if(ndpiFlow->protos.dns.is_query) {
if(src2dst_direction) {
;
} else {
request_swap();
// ntop->getTrace()->traceEvent(TRACE_NORMAL, "*** SWAP ***");
}
} else {
/* Response */
if(src2dst_direction) {
request_swap();
// ntop->getTrace()->traceEvent(TRACE_NORMAL, "*** SWAP ***");
} else {
;
}
}
}
break;
case NDPI_PROTOCOL_TOR:
case NDPI_PROTOCOL_TLS:
case NDPI_PROTOCOL_QUIC:
if((ndpiDetectedProtocol.proto.app_protocol == NDPI_PROTOCOL_DOH_DOT)
&& cli_h && srv_h && cli_h->isLocalHost())
cli_h->incDohDoTUses(srv_h);
break;
default:
break;
} /* switch */
}
/* *************************************** */
bool Flow::isDPIDetectedFlow() {
switch (getConfidence()) {
case NDPI_CONFIDENCE_DPI_CACHE:
case NDPI_CONFIDENCE_DPI_PARTIAL_CACHE:
case NDPI_CONFIDENCE_DPI:
case NDPI_CONFIDENCE_NBPF:
return(true);
default:
return(false);
}
}
/* **************************************************** */
bool Flow::matchFlowIP(IpAddress *ip, u_int16_t vlan_id) {
if(get_vlan_id() != vlan_id) return(false);
if((!get_cli_ip_addr()) || (!get_srv_ip_addr()))
return(false);
if(get_cli_ip_addr()->equal(ip) || get_srv_ip_addr()->equal(ip))
return(true);
else
return(false);
}
/* **************************************************** */
bool Flow::matchFlowVLAN(u_int16_t vlan_id) {
return(get_vlan_id() == vlan_id ? true : false);
}
/* **************************************************** */
bool Flow::matchFlowDeviceIP(u_int32_t flow_device_ip) {
return(getFlowDeviceIP() == flow_device_ip ? true : false);
}
/* **************************************************** */
bool Flow::matchInIfIdx(u_int32_t in_if_idx) {
return(getFlowDeviceInIndex() == in_if_idx ? true : false);
}
/* **************************************************** */
bool Flow::matchOutIfIdx(u_int32_t out_if_idx) {
return(getFlowDeviceOutIndex() == out_if_idx ? true : false);
}
/* **************************************************** */
void Flow::lua_get_flow_connection_state(lua_State *vm) {
lua_push_uint64_table_entry(vm, "minor_conn_state", getCurrentConnectionState());
lua_push_uint64_table_entry(vm, "major_conn_state", getMajorConnState());
}
/* **************************************************** */
MajorConnectionStates Flow::getMajorConnState() {
MajorConnectionStates major_conn_state = MAJOR_NO_STATE;
switch (getCurrentConnectionState()) {
case S0:
case REJ:
case RSTOS0:
case RSTRH:
case SH:
case SHR:
major_conn_state = ATTEMPTED;
/* code */
break;
case OTH:
case S1:
major_conn_state = ESTABLISHED;
break;
case SF:
case S2:
case S3:
case RSTO:
case RSTR:
major_conn_state = CLOSED;
break;
default:
major_conn_state = MAJOR_NO_STATE;
break;
}
return(major_conn_state);
}
/* **************************************************** */
bool Flow::isTCPFlagSet(u_int8_t tcp_flags, int flag_to_check) {
return((tcp_flags & flag_to_check) != 0);
}
/* **************************************************** */
bool Flow::checkS1ConnState() {
if(tcp == NULL)
return(false);
else
return(current_c_state == S1 || ((isTCPFlagSet(src2dst_tcp_flags,TCP_3WH_MASK)) &&
(isTCPFlagSet(dst2src_tcp_flags,TCP_3WH_MASK))&& /* 3WH OK */
!((isTCPFlagSet(src2dst_tcp_flags, TH_FIN)) && (isTCPFlagSet(src2dst_tcp_flags, TH_ACK))) && /* NO FIN ACK in src2dst */
!(isTCPFlagSet(src2dst_tcp_flags, TH_RST)) && !(isTCPFlagSet(dst2src_tcp_flags, TH_RST)) /* NO RST */
));
}
/* **************************************************** */
MinorConnectionStates Flow::calculateConnectionState(bool is_cumulative) {
if(!isTCP() || !tcp)
return(setCurrentConnectionState(MINOR_NO_STATE));
/* Check S0 or RSTOS0 or REJ or SH */
if((isTCPFlagSet(src2dst_tcp_flags, TH_SYN)) &&
!(isTCPFlagSet(dst2src_tcp_flags, TH_SYN))) {
if(!(isTCPFlagSet(dst2src_tcp_flags, TH_RST))) {
if((isTCPFlagSet(src2dst_tcp_flags, TH_RST))) {
return(setCurrentConnectionState(RSTOS0));
} else {
if((isTCPFlagSet(src2dst_tcp_flags, TH_FIN))) {
return(setCurrentConnectionState(SH));
} else {
return(setCurrentConnectionState(S0));
}
}
} else {
return(setCurrentConnectionState(REJ));
}
}
/* Check RSTRH */
if((isTCPFlagSet(dst2src_tcp_flags, TH_SYN)) &&
(isTCPFlagSet(dst2src_tcp_flags, TH_ACK)) &&
(isTCPFlagSet(dst2src_tcp_flags, TH_RST)) &&
!(isTCPFlagSet(src2dst_tcp_flags, TH_SYN)))
return(setCurrentConnectionState(RSTRH));
/* Check SHR */
if((isTCPFlagSet(dst2src_tcp_flags, TH_SYN)) &&
(isTCPFlagSet(dst2src_tcp_flags, TH_ACK)) &&
(isTCPFlagSet(dst2src_tcp_flags, TH_FIN)) &&
!(isTCPFlagSet(src2dst_tcp_flags, TH_SYN)))
return(setCurrentConnectionState(SHR));
/* Check OTH */
if(!isTCPFlagSet(src2dst_tcp_flags, TH_SYN) &&
!isTCPFlagSet(dst2src_tcp_flags, TH_SYN))
return(setCurrentConnectionState(OTH));
bool is_s1 = (current_c_state == S1) || is_cumulative;
bool is_s2 = (current_c_state == S2);
bool is_s3 = (current_c_state == S3);
/* Check SF */
if(((is_s1) || (is_s2) || (is_s3)) &&
isTCPFlagSet(src2dst_tcp_flags, TH_FIN) &&
isTCPFlagSet(dst2src_tcp_flags, TH_FIN))
return(setCurrentConnectionState(SF));
/* Check S2 */
if((is_s1) &&
isTCPFlagSet(src2dst_tcp_flags, TH_FIN) &&
!isTCPFlagSet(dst2src_tcp_flags, TH_FIN))
return(setCurrentConnectionState(S2));
/* Check S3 */
if((is_s1) &&
!isTCPFlagSet(src2dst_tcp_flags, TH_FIN) &&
isTCPFlagSet(dst2src_tcp_flags, TH_FIN))
return(setCurrentConnectionState(S3));
/* Check RSTO */
if((is_s1) && isTCPFlagSet(src2dst_tcp_flags, TH_RST))
return(setCurrentConnectionState(RSTO));
/* Check RSTR */
if((is_s1) && isTCPFlagSet(dst2src_tcp_flags, TH_RST))
return(setCurrentConnectionState(RSTR));
/* Check S1 */
if(checkS1ConnState())
return(setCurrentConnectionState(S1));
if(getCurrentConnectionState() != MINOR_NO_STATE)
return(getCurrentConnectionState());
return(setCurrentConnectionState(MINOR_NO_STATE));
}
/* **************************************************** */
void Flow::addPrePostNATIPv4(u_int32_t _src_ip_addr_pre_nat,
u_int32_t _dst_ip_addr_pre_nat,
u_int32_t _src_ip_addr_post_nat,
u_int32_t _dst_ip_addr_post_nat) {
allocateCollection();
if(collection) {
collection->nat.src_ip_addr_pre_nat = _src_ip_addr_pre_nat;
collection->nat.dst_ip_addr_pre_nat = _dst_ip_addr_pre_nat;
collection->nat.src_ip_addr_post_nat = _src_ip_addr_post_nat;
collection->nat.dst_ip_addr_post_nat = _dst_ip_addr_post_nat;
}
}
/* **************************************************** */
void Flow::addPrePostNATPort(u_int32_t _src_port_pre_nat,
u_int32_t _dst_port_pre_nat,
u_int32_t _src_port_post_nat,
u_int32_t _dst_port_post_nat) {
allocateCollection();
if(collection) {
collection->nat.src_port_pre_nat = _src_port_pre_nat;
collection->nat.dst_port_pre_nat = _dst_port_pre_nat;
collection->nat.src_port_post_nat = _src_port_post_nat;
collection->nat.dst_port_post_nat = _dst_port_post_nat;
}
}
/* *************************************** */
void Flow::accountBidirectionalTCPProtocolServices() {
if(isThreeWayHandshakeOK()
// && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
&& isTCPReallyBidirectional()) {
Host *cli_h, *srv_h;
get_actual_peers(&cli_h, &srv_h);
for(int i=0; i<2; i++) {
switch (i == 0 ? ndpi_get_lower_proto(ndpiDetectedProtocol) : ndpi_get_upper_proto(ndpiDetectedProtocol)) {
case NDPI_PROTOCOL_MAIL_SMTPS:
case NDPI_PROTOCOL_MAIL_SMTP:
{
if(srv_h) {
if(!srv_h->isSmtpServer()) {
srv_h->setSmtpServer();
ntop->trackAssetChange("SMTP", "setSmtpServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
/* View Interface */
srv_ip_addr->setSmtpServer();
ntop->trackAssetChange("SMTP", "setSmtpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
return; /* Nothing else to do */
}
break;
case NDPI_PROTOCOL_MAIL_IMAPS:
case NDPI_PROTOCOL_MAIL_IMAP:
{
if(srv_h) {
if(!srv_h->isImapServer()) {
srv_h->setImapServer();
ntop->trackAssetChange("IMAP", "setImapServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
/* View Interface */
srv_ip_addr->setImapServer();
ntop->trackAssetChange("IMAP", "setImapServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
return; /* Nothing else to do */
}
break;
case NDPI_PROTOCOL_MAIL_POPS:
case NDPI_PROTOCOL_MAIL_POP:
{
if(srv_h) {
if(!srv_h->isPopServer()) {
srv_h->setPopServer();
ntop->trackAssetChange("POP", "setPopServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
/* View Interface */
srv_ip_addr->setPopServer();
ntop->trackAssetChange("POP", "setPopServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
return; /* Nothing else to do */
}
break;
case NDPI_PROTOCOL_TLS:
/*
TLS on port 443 is considered HTTPS
TODO: check ALPN to double check
*/
if(get_srv_port() != 443) {
return;
}
/* No break here ! */
case NDPI_PROTOCOL_HTTP:
case NDPI_PROTOCOL_HTTP_CONNECT:
case NDPI_PROTOCOL_HTTP_PROXY:
{
if(srv_h) {
if(!srv_h->isHttpServer()) {
srv_h->setHttpServer();
ntop->trackAssetChange("HTTP", "setHttpServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
/* View Interface */
srv_ip_addr->setHttpServer();
ntop->trackAssetChange("HTTP", "setHttpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
return; /* Nothing else to do */
}
break;
case NDPI_PROTOCOL_SSH:
{
if(srv_h) {
if(!srv_h->isSshServer()) {
srv_h->setSshServer();
ntop->trackAssetChange("SSH", "setSshServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
/* View Interface */
srv_ip_addr->setSshServer();
ntop->trackAssetChange("SSH", "setSshServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
return; /* Nothing else to do */
}
break;
case NDPI_PROTOCOL_RDP:
{
if(srv_h) {
if(!srv_h->isRdpServer()) {
srv_h->setRdpServer();
ntop->trackAssetChange("RDP", "setRdpServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
/* View Interface */
srv_ip_addr->setRdpServer();
ntop->trackAssetChange("RDP", "setRdpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
return; /* Nothing else to do */
}
break;
}
}
}
}
/* *************************************** */
void Flow::accountBidirectionalUDPProtocolServices() {
Host *cli_h, *srv_h;
get_actual_peers(&cli_h, &srv_h);
switch (ndpi_get_lower_proto(ndpiDetectedProtocol)) {
case NDPI_PROTOCOL_NTP:
if((getConfidence() == NDPI_CONFIDENCE_DPI)
|| (isTCP() && isTCPEstablished())
|| isUDP()) {
//char buf[256];
if(srv_h) {
if(!srv_h->isNtpServer()) {
srv_h->setNtpServer();
ntop->trackAssetChange("NTP", "setNtpServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
if(!srv_ip_addr->isNtpServer()) {
srv_ip_addr->setNtpServer();
ntop->trackAssetChange("NTP", "setNtpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
}
}
break;
case NDPI_PROTOCOL_DNS:
if((getConfidence() == NDPI_CONFIDENCE_DPI) /* Packet */
|| (ntohs(srv_port) == 53) /* nProbe case: let's be conservative */
) {
// char buf[256];
if(srv_h) {
if(!srv_h->isDnsServer()) {
srv_h->setDnsServer();
ntop->trackAssetChange("DNS", "setDnsServer-3", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
if(!srv_ip_addr->isDnsServer()) {
srv_ip_addr->setDnsServer();
ntop->trackAssetChange("DNS", "setDnsServer-4", NULL, srv_ip_addr, NULL, this, NULL);
}
}
}
break;
case NDPI_PROTOCOL_QUIC:
/*
QUIC on port 443 is considered HTTPS
TODO: check ALPN to double check
*/
if(get_srv_port() == 443) {
if(isBidirectional()
// && (getConfidence() == NDPI_CONFIDENCE_DPI) /* Won't work with flow collection */
) {
if(srv_h) {
if(!srv_h->isHttpServer()) {
srv_h->setHttpServer();
ntop->trackAssetChange("HTTP", "setHttpServer-1", NULL, NULL, srv_h, this, NULL);
}
} else if(srv_ip_addr) {
srv_ip_addr->setHttpServer();
ntop->trackAssetChange("HTTP", "setHttpServer (view)", NULL, srv_ip_addr, NULL, this, NULL);
}
}
}
break;
}
}
/* *************************************** */
/*
Account flow traffic in interface traffic if not yet done
*/
void Flow::accountFlowTraffic(bool src2dst_direction) {
if(!isFlowAccounted()) {
#ifdef DEBUG
char buf[256];
ntop->getTrace()->traceEvent(TRACE_WARNING, "Account %s", print(buf, sizeof(buf)));
#endif
endProtocolDissection(src2dst_direction);
/*
Increment interface counters for those flows that have not
been accounted hence whose traffic has not been accounted
in the corresponding network interface. See
NetworkInterface::processPacket()
*/
iface->incnDPIStats(iface->getTimeLastPktRcvd(),
getStatsProtocol(), get_protocol_category(),
get_bytes_cli2srv(), get_bytes_srv2cli(),
get_packets_cli2srv(), get_packets_srv2cli());
}
}
/* *************************************** */
bool Flow::is_active_entry_now_idle(u_int max_idleness) const {
bool is_expired = (((u_int)(iface->getTimeLastPktRcvd()) > (creation_time + max_idleness)) ? true : false);
/*
This mechanism prevents collected flows with past timestamps
to be purged immediately, thus guaranteeing that they stay in
memory at least max_idleness seconds
*/
if(!is_expired) return(false);
return(GenericHashEntry::is_active_entry_now_idle(max_idleness));
}
/* *************************************** */
/* #define DEBUG_UPDATE 1 */
void Flow::updateServerName(Host *h) {
if(h && (h->has_name_set() == false)) {
struct ndpi_address_cache_item *i;
ndpi_ip_addr_t ip_addr;
h->get_ip()->fillIP(&ip_addr);
#ifdef DEBUG_UPDATE
char buf[64];
ntop->getTrace()->traceEvent(TRACE_NORMAL, "**** Host: %p [%s]", h, srv_ip_addr->print(buf, sizeof(buf)));
#endif
if((i = ndpi_cache_address_find(iface->get_ndpi_struct(), ip_addr)) != NULL) {
#ifdef DEBUG_UPDATE
ntop->getTrace()->traceEvent(TRACE_NORMAL, "**** SET %s", i->hostname);
#endif
h->setServerName(i->hostname);
}
}
}
/* *************************************** */
const char* Flow::getDomainName() {
switch (getLowerProtocol()) {
case NDPI_PROTOCOL_DNS:
if(protos.dns.last_query)
return(ndpi_get_host_domain(iface->get_ndpi_struct(), protos.dns.last_query));
break;
case NDPI_PROTOCOL_HTTP:
case NDPI_PROTOCOL_HTTP_PROXY:
case NDPI_PROTOCOL_TLS:
case NDPI_PROTOCOL_MAIL_IMAPS:
case NDPI_PROTOCOL_MAIL_SMTPS:
case NDPI_PROTOCOL_MAIL_POPS:
case NDPI_PROTOCOL_QUIC:
{
char *s = getFlowServerInfo();
if(s)
return(ndpi_get_host_domain(iface->get_ndpi_struct(), s));
}
break;
case NDPI_PROTOCOL_MDNS:
if(protos.mdns.name)
return(ndpi_get_host_domain(iface->get_ndpi_struct(), protos.mdns.name));
break;
}
return(NULL);
}
/* *************************************** */
void Flow::allocateCollection() {
if(collection != NULL)
return;
else
collection = (FlowCollectionInfo*)calloc(1, sizeof(FlowCollectionInfo));
}
/* *************************************** */
/* Quick check that both sides have (likely) done a 3WH */
bool Flow::isThreeWayHandshakeOK() const {
u_int16_t mask = TH_SYN | TH_ACK;
if(((src2dst_tcp_flags & mask) == mask) && ((dst2src_tcp_flags & mask) == mask))
return(true);
else
return(false);
};
/* *************************************** */
void Flow::setHostTCPFingerprint(char *fp, ndpi_os os_hint) {
if((fp != NULL) && (cli_host || getViewSharedClient())) {
Host *h = cli_host ? cli_host : getViewSharedClient();
#if 0
char buf[64];
ntop->getTrace()->traceEvent(TRACE_WARNING, "->> %s [%s][%s@%d]",
fp, ndpi_print_os_hint(os_hint),
h->get_ip()->print(buf, sizeof(buf)),
vlanId);
#endif
h->setTCPfingerprint(fp, os_hint);
if((os_hint == ndpi_os_unknown)
&& h->isLocalHost()
&& ntop->getPrefs()->areFingerprintStatsEnabled()) {
char buf[64], log[128];
snprintf(log, sizeof(log), "%s,%s",
h->get_ip()->print(buf, sizeof(buf)),
Utils::OS2Str(h->getOS()));
ntop->getTrace()->traceEvent(TRACE_DEBUG, "** Unknown TCP fingerprint %s [%s]",
fp, log);
ntop->getRedis()->hashSet(CONST_STR_UNKNOWN_TCP_FINGERPRINTS,
fp, log);
}
setTCPFingerprint(fp);
}
}
/* *************************************** */
void Flow::setServerName(char *value /* Allocated by caller */) {
if(value == NULL) return;
if (host_server_name) free(host_server_name);
host_server_name = value;
#ifdef NTOPNG_PRO
processHostName(host_server_name);
#endif
}
/* *************************************** */
void Flow::getSrcAS(u_int32_t *as, char *as_name) {
if(!srcAS) {
Host *h = get_cli_host();
if(h) {
srcAS = h->get_asn();
srcASName = h->get_asname();
} else {
u_int32_t asn;
char *asname;
ntop->getGeolocation()->getAS(get_cli_ip_addr(), &asn, &asname);
srcAS = asn;
srcASName = asname;
}
}
*as = srcAS;
as_name = srcASName ? srcASName : (char*) "";
}
/* *************************************** */
void Flow::getDstAS(u_int32_t *as, char *as_name) {
if(!dstAS) {
Host *h = get_srv_host();
if(h) {
dstAS = h->get_asn();
dstASName = h->get_asname();
} else {
u_int32_t asn;
char *asname;
ntop->getGeolocation()->getAS(get_srv_ip_addr(), &asn, &asname);
dstAS = asn;
dstASName = asname;
}
}
*as = dstAS;
as_name = dstASName ? dstASName : (char*) "";
}
/* *************************************** */
TransitAS Flow::getTransitASType() {
u_int32_t src_asn = 0, dst_asn = 0;
char *asname = NULL;
getSrcAS(&src_asn, asname);
getDstAS(&dst_asn, asname);
if (!srcPeerAS && !dstPeerAS) {
return all_flow;
} else if ((src_asn != srcPeerAS) || (dst_asn != dstPeerAS)) {
return transit_flow;
} else if ((src_asn == srcPeerAS) || (dst_asn == dstPeerAS)) {
return direct_flow;
}
return all_flow;
}
/* *************************************** */
/* *************************************** */