mirror of
https://github.com/ntop/ntopng.git
synced 2026-05-06 03:45:26 +00:00
4792 lines
167 KiB
C++
4792 lines
167 KiB
C++
/*
|
|
*
|
|
* (C) 2013-20 - 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 */
|
|
|
|
const ndpi_protocol Flow::ndpiUnknownProtocol = { NDPI_PROTOCOL_UNKNOWN,
|
|
NDPI_PROTOCOL_UNKNOWN,
|
|
NDPI_PROTOCOL_CATEGORY_UNSPECIFIED };
|
|
//#define DEBUG_DISCOVERY
|
|
//#define DEBUG_UA
|
|
|
|
/* *************************************** */
|
|
|
|
Flow::Flow(NetworkInterface *_iface,
|
|
u_int16_t _vlanId, 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) : GenericHashEntry(_iface) {
|
|
periodic_stats_update_partial = NULL;
|
|
viewFlowStats = NULL;
|
|
vlanId = _vlanId, protocol = _protocol, cli_port = _cli_port, srv_port = _srv_port;
|
|
cli_host = srv_host = NULL;
|
|
good_tls_hs = true, flow_dropped_counts_increased = false, vrfId = 0;
|
|
srcAS = dstAS = prevAdjacentAS = nextAdjacentAS = 0;
|
|
flow_score = cli_score = srv_score = 0;
|
|
alert_status_info = alert_status_info_shadow = NULL;
|
|
alert_type = alert_none;
|
|
alert_level = alert_level_none;
|
|
alerted_status = status_normal;
|
|
peers_score_accounted = false;
|
|
status_infos = NULL;
|
|
|
|
detection_completed = false;
|
|
extra_dissection_completed = false;
|
|
ndpiDetectedProtocol = ndpiUnknownProtocol;
|
|
doNotExpireBefore = iface->getTimeLastPktRcvd() + DONT_NOT_EXPIRE_BEFORE_SEC;
|
|
periodic_update_ctr = 0;
|
|
|
|
#ifdef HAVE_NEDGE
|
|
last_conntrack_update = 0;
|
|
marker = MARKER_NO_ACTION;
|
|
#endif
|
|
|
|
icmp_info = _icmp_info ? new (std::nothrow) ICMPinfo(*_icmp_info) : NULL;
|
|
|
|
ndpiFlow = NULL, cli_id = srv_id = NULL;
|
|
cli_ebpf = srv_ebpf = NULL;
|
|
json_info = NULL, tlv_info = NULL, twh_over = twh_ok = false,
|
|
dissect_next_http_packet = false,
|
|
host_server_name = NULL;
|
|
bt_hash = NULL;
|
|
|
|
operating_system = os_unknown;
|
|
src2dst_tcp_flags = 0, dst2src_tcp_flags = 0, last_update_time.tv_sec = 0, last_update_time.tv_usec = 0,
|
|
bytes_thpt = 0, goodput_bytes_thpt = 0, top_bytes_thpt = 0, top_pkts_thpt = 0;
|
|
bytes_thpt_cli2srv = 0, goodput_bytes_thpt_cli2srv = 0;
|
|
bytes_thpt_srv2cli = 0, goodput_bytes_thpt_srv2cli = 0;
|
|
pkts_thpt = 0, pkts_thpt_cli2srv = 0, pkts_thpt_srv2cli = 0;
|
|
top_bytes_thpt = 0, top_goodput_bytes_thpt = 0, applLatencyMsec = 0;
|
|
packet_payload_match.payload = NULL, packet_payload_match.payload_len = 0;
|
|
external_alert = NULL;
|
|
trigger_immediate_periodic_update = false;
|
|
pending_lua_call_protocol_detected = false;
|
|
next_lua_call_periodic_update = 0;
|
|
|
|
last_db_dump.partial = NULL;
|
|
last_db_dump.first_seen = last_db_dump.last_seen = 0;
|
|
memset(&protos, 0, sizeof(protos));
|
|
memset(&flow_device, 0, sizeof(flow_device));
|
|
|
|
PROFILING_SUB_SECTION_ENTER(iface, "Flow::Flow: iface->findFlowHosts", 7);
|
|
iface->findFlowHosts(_vlanId, _cli_mac, _cli_ip, &cli_host, _srv_mac, _srv_ip, &srv_host);
|
|
PROFILING_SUB_SECTION_EXIT(iface, 7);
|
|
|
|
if(cli_host) {
|
|
NetworkStats *network_stats = cli_host->getNetworkStats(cli_host->get_local_network_id());
|
|
cli_host->incUses();
|
|
cli_host->incNumFlows(last_seen, true, srv_host, this);
|
|
if(network_stats) network_stats->incNumFlows(last_seen, true);
|
|
cli_ip_addr = cli_host->get_ip();
|
|
} else { /* Client host has not been allocated, let's keep the info in an IpAddress */
|
|
if((cli_ip_addr = new (std::nothrow) IpAddress(*_cli_ip)))
|
|
cli_ip_addr->reloadBlacklist(iface->get_ndpi_struct());
|
|
}
|
|
|
|
if(srv_host) {
|
|
NetworkStats *network_stats = srv_host->getNetworkStats(srv_host->get_local_network_id());
|
|
srv_host->incUses();
|
|
srv_host->incNumFlows(last_seen, false, cli_host, this);
|
|
if(network_stats) network_stats->incNumFlows(last_seen, false);
|
|
srv_ip_addr = srv_host->get_ip();
|
|
} else { /* 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());
|
|
}
|
|
|
|
memset(&custom_app, 0, sizeof(custom_app));
|
|
|
|
#ifdef NTOPNG_PRO
|
|
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()));
|
|
}
|
|
|
|
counted_in_aggregated_flow = status_counted_in_aggregated_flow = false;
|
|
#endif
|
|
|
|
passVerdict = true, quota_exceeded = false;
|
|
has_malicious_cli_signature = has_malicious_srv_signature = false;
|
|
#ifdef ALERTED_FLOWS_DEBUG
|
|
iface_alert_inc = iface_alert_dec = false;
|
|
#endif
|
|
if(_first_seen > _last_seen) _first_seen = _last_seen;
|
|
first_seen = _first_seen, last_seen = _last_seen;
|
|
bytes_thpt_trend = trend_unknown, pkts_thpt_trend = trend_unknown;
|
|
//bytes_rate = new TimeSeries<float>(4096);
|
|
|
|
synTime.tv_sec = synTime.tv_usec = 0,
|
|
ackTime.tv_sec = ackTime.tv_usec = 0,
|
|
synAckTime.tv_sec = synAckTime.tv_usec = 0,
|
|
rttSec = 0, cli2srv_window = srv2cli_window = 0,
|
|
c2sFirstGoodputTime.tv_sec = c2sFirstGoodputTime.tv_usec = 0;
|
|
memset(&ip_stats_s2d, 0, sizeof(ip_stats_s2d)), memset(&ip_stats_d2s, 0, sizeof(ip_stats_d2s));
|
|
memset(&tcp_seq_s2d, 0, sizeof(tcp_seq_s2d)), memset(&tcp_seq_d2s, 0, sizeof(tcp_seq_d2s));
|
|
memset(&clientNwLatency, 0, sizeof(clientNwLatency)), memset(&serverNwLatency, 0, sizeof(serverNwLatency));
|
|
|
|
if(iface->isPacketInterface() && !iface->isSampledTraffic()) {
|
|
cli2srvPktTime = new (std::nothrow) InterarrivalStats();
|
|
srv2cliPktTime = new (std::nothrow) InterarrivalStats();
|
|
} else {
|
|
cli2srvPktTime = NULL;
|
|
srv2cliPktTime = NULL;
|
|
}
|
|
|
|
#ifdef NTOPNG_PRO
|
|
#ifndef HAVE_NEDGE
|
|
trafficProfile = NULL;
|
|
#else
|
|
cli2srv_in = cli2srv_out = srv2cli_in = srv2cli_out = DEFAULT_SHAPER_ID;
|
|
memset(&flowShaperIds, 0, sizeof(flowShaperIds));
|
|
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.app_protocol = NDPI_PROTOCOL_IP_ICMP,
|
|
ndpiDetectedProtocol.master_protocol = NDPI_PROTOCOL_UNKNOWN;
|
|
setDetectedProtocol(ndpiDetectedProtocol);
|
|
break;
|
|
|
|
case IPPROTO_ICMPV6:
|
|
ndpiDetectedProtocol.app_protocol = NDPI_PROTOCOL_IP_ICMPV6,
|
|
ndpiDetectedProtocol.master_protocol = NDPI_PROTOCOL_UNKNOWN;
|
|
setDetectedProtocol(ndpiDetectedProtocol);
|
|
break;
|
|
|
|
default:
|
|
setDetectedProtocol(ndpi_guess_undetected_protocol(iface->get_ndpi_struct(), NULL, protocol, 0, 0, 0, 0));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::allocDPIMemory() {
|
|
if((ndpiFlow = (ndpi_flow_struct*)calloc(1, iface->get_flow_size())) == NULL)
|
|
throw "Not enough memory";
|
|
|
|
if((cli_id = calloc(1, iface->get_size_id())) == NULL)
|
|
throw "Not enough memory";
|
|
|
|
if((srv_id = calloc(1, iface->get_size_id())) == NULL)
|
|
throw "Not enough memory";
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::freeDPIMemory() {
|
|
if(ndpiFlow) { ndpi_free_flow(ndpiFlow); ndpiFlow = NULL; }
|
|
if(cli_id) { free(cli_id); cli_id = NULL; }
|
|
if(srv_id) { free(srv_id); srv_id = NULL; }
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
Flow::~Flow() {
|
|
#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
|
|
if(cli_host)
|
|
cli_host->decUses();
|
|
else if(cli_ip_addr) /* Dynamically allocated only when cli_host was NULL */
|
|
delete cli_ip_addr;
|
|
|
|
if(srv_host)
|
|
srv_host->decUses();
|
|
else if(srv_ip_addr) /* Dynamically allocated only when srv_host was NULL */
|
|
delete srv_ip_addr;
|
|
|
|
if(viewFlowStats) delete(viewFlowStats);
|
|
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(cli_ebpf) delete cli_ebpf;
|
|
if(srv_ebpf) delete srv_ebpf;
|
|
|
|
if(cli2srvPktTime) delete cli2srvPktTime;
|
|
if(srv2cliPktTime) delete srv2cliPktTime;
|
|
|
|
if(isHTTP()) {
|
|
if(protos.http.last_method) free(protos.http.last_method);
|
|
if(protos.http.last_url) free(protos.http.last_url);
|
|
if(protos.http.last_content_type) free(protos.http.last_content_type);
|
|
} 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);
|
|
} 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(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.ja3.client_hash) free(protos.tls.ja3.client_hash);
|
|
if(protos.tls.ja3.server_hash) free(protos.tls.ja3.server_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);
|
|
}
|
|
|
|
if(bt_hash) free(bt_hash);
|
|
|
|
if(status_infos) {
|
|
for(int i=0; i<BITMAP_NUM_BITS; i++) {
|
|
if(status_infos[i].script_key)
|
|
free(status_infos[i].script_key);
|
|
}
|
|
|
|
free(status_infos);
|
|
}
|
|
|
|
if(packet_payload_match.payload)
|
|
free(packet_payload_match.payload);
|
|
|
|
freeDPIMemory();
|
|
if(icmp_info) delete(icmp_info);
|
|
if(external_alert) free(external_alert);
|
|
if(alert_status_info) free(alert_status_info);
|
|
if(alert_status_info_shadow) free(alert_status_info_shadow);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
u_int16_t Flow::getStatsProtocol() const {
|
|
u_int16_t stats_protocol;
|
|
|
|
if(ndpiDetectedProtocol.app_protocol != NDPI_PROTOCOL_UNKNOWN
|
|
&& !ndpi_is_subprotocol_informative(NULL, ndpiDetectedProtocol.master_protocol))
|
|
stats_protocol = ndpiDetectedProtocol.app_protocol;
|
|
else
|
|
stats_protocol = ndpiDetectedProtocol.master_protocol;
|
|
|
|
return(stats_protocol);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* This function is called as soon as the protocol detection is
|
|
* completed. See processExtraDissectedInformation for a later callback. */
|
|
void Flow::processDetectedProtocol() {
|
|
u_int16_t l7proto;
|
|
u_int16_t stats_protocol;
|
|
|
|
if(ndpiFlow == NULL)
|
|
return;
|
|
|
|
stats_protocol = getStatsProtocol();
|
|
|
|
/* Update the active flows stats */
|
|
if(cli_host) cli_host->incnDPIFlows(stats_protocol);
|
|
if(srv_host) srv_host->incnDPIFlows(stats_protocol);
|
|
iface->incnDPIFlows(stats_protocol);
|
|
|
|
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 == NULL)) {
|
|
Utils::sanitizeHostName((char*)ndpiFlow->host_server_name);
|
|
|
|
if(ndpi_is_proto(ndpiDetectedProtocol, NDPI_PROTOCOL_HTTP)) {
|
|
char *double_column = strrchr((char*)ndpiFlow->host_server_name, ':');
|
|
|
|
if(double_column) double_column[0] = '\0';
|
|
}
|
|
|
|
/*
|
|
Host server name equals the Host: HTTP header field.
|
|
*/
|
|
host_server_name = strdup((char*)ndpiFlow->host_server_name);
|
|
}
|
|
|
|
switch(l7proto) {
|
|
case NDPI_PROTOCOL_BITTORRENT:
|
|
if(bt_hash == NULL)
|
|
setBittorrentHash((char*)ndpiFlow->protos.bittorrent.hash);
|
|
break;
|
|
|
|
case NDPI_PROTOCOL_MDNS:
|
|
/*
|
|
The statement below can create issues sometimes as devices publish
|
|
themselves with varisous names depending on the context (**)
|
|
*/
|
|
if(ndpiFlow->protos.mdns.answer[0] != '\0' && !protos.mdns.answer)
|
|
protos.mdns.answer = strdup(ndpiFlow->protos.mdns.answer);
|
|
break;
|
|
|
|
case NDPI_PROTOCOL_DNS:
|
|
/* See Flow::processDNSPacket for dissection */
|
|
break;
|
|
|
|
case NDPI_PROTOCOL_TOR:
|
|
case NDPI_PROTOCOL_TLS:
|
|
if(protos.tls.client_requested_server_name
|
|
&& cli_host
|
|
&& cli_host->isLocalHost())
|
|
cli_host->incrVisitedWebSite(protos.tls.client_requested_server_name);
|
|
break;
|
|
|
|
case NDPI_PROTOCOL_HTTP:
|
|
case NDPI_PROTOCOL_HTTP_PROXY:
|
|
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) {
|
|
if(ndpiFlow->protos.http.detected_os[0] != '\0')
|
|
cli_host->inlineSetOSDetail((char*)ndpiFlow->protos.http.detected_os);
|
|
|
|
if(cli_host->isLocalHost())
|
|
cli_host->incrVisitedWebSite(host_server_name);
|
|
}
|
|
}
|
|
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() {
|
|
bool free_ndpi_memory = true; /* Possibly set to false if the flow is DNS and all the packets need to be dissected */
|
|
|
|
if(ndpiFlow) {
|
|
u_int16_t l7proto;
|
|
|
|
l7proto = ndpi_get_lower_proto(ndpiDetectedProtocol);
|
|
|
|
switch(l7proto) {
|
|
|
|
case NDPI_PROTOCOL_SSH:
|
|
if(protos.ssh.client_signature == NULL)
|
|
protos.ssh.client_signature = strdup(ndpiFlow->protos.ssh.client_signature);
|
|
if(protos.ssh.server_signature == NULL)
|
|
protos.ssh.server_signature = strdup(ndpiFlow->protos.ssh.server_signature);
|
|
|
|
if(protos.ssh.hassh.client_hash == NULL
|
|
&& ndpiFlow->protos.ssh.hassh_client[0] != '\0') {
|
|
protos.ssh.hassh.client_hash = strdup(ndpiFlow->protos.ssh.hassh_client);
|
|
updateHASSH(true /* As client */);
|
|
}
|
|
|
|
if(protos.ssh.hassh.server_hash == NULL
|
|
&& ndpiFlow->protos.ssh.hassh_server[0] != '\0') {
|
|
protos.ssh.hassh.server_hash = strdup(ndpiFlow->protos.ssh.hassh_server);
|
|
updateHASSH(false /* As server */);
|
|
}
|
|
break;
|
|
|
|
case NDPI_PROTOCOL_TLS:
|
|
protos.tls.tls_version = ndpiFlow->protos.stun_ssl.ssl.ssl_version;
|
|
|
|
protos.tls.notBefore = ndpiFlow->protos.stun_ssl.ssl.notBefore,
|
|
protos.tls.notAfter = ndpiFlow->protos.stun_ssl.ssl.notAfter;
|
|
|
|
if((protos.tls.client_requested_server_name == NULL)
|
|
&& (ndpiFlow->protos.stun_ssl.ssl.client_requested_server_name[0] != '\0')) {
|
|
protos.tls.client_requested_server_name = strdup(ndpiFlow->protos.stun_ssl.ssl.client_requested_server_name);
|
|
}
|
|
|
|
if((protos.tls.server_names == NULL)
|
|
&& (ndpiFlow->protos.stun_ssl.ssl.server_names != NULL))
|
|
protos.tls.server_names = strdup(ndpiFlow->protos.stun_ssl.ssl.server_names);
|
|
|
|
if((protos.tls.client_alpn == NULL)
|
|
&& (ndpiFlow->protos.stun_ssl.ssl.alpn != NULL))
|
|
protos.tls.client_alpn = strdup(ndpiFlow->protos.stun_ssl.ssl.alpn);
|
|
|
|
if((protos.tls.client_tls_supported_versions == NULL)
|
|
&& (ndpiFlow->protos.stun_ssl.ssl.tls_supported_versions != NULL))
|
|
protos.tls.client_tls_supported_versions = strdup(ndpiFlow->protos.stun_ssl.ssl.tls_supported_versions);
|
|
|
|
if((protos.tls.issuerDN == NULL) && (ndpiFlow->protos.stun_ssl.ssl.issuerDN != NULL))
|
|
protos.tls.issuerDN= strdup(ndpiFlow->protos.stun_ssl.ssl.issuerDN);
|
|
|
|
if((protos.tls.subjectDN == NULL) && (ndpiFlow->protos.stun_ssl.ssl.subjectDN != NULL))
|
|
protos.tls.subjectDN= strdup(ndpiFlow->protos.stun_ssl.ssl.subjectDN);
|
|
|
|
if((protos.tls.ja3.client_hash == NULL) && (ndpiFlow->protos.stun_ssl.ssl.ja3_client[0] != '\0')) {
|
|
protos.tls.ja3.client_hash = strdup(ndpiFlow->protos.stun_ssl.ssl.ja3_client);
|
|
updateCliJA3();
|
|
}
|
|
|
|
if((protos.tls.ja3.server_hash == NULL) && (ndpiFlow->protos.stun_ssl.ssl.ja3_server[0] != '\0')) {
|
|
protos.tls.ja3.server_hash = strdup(ndpiFlow->protos.stun_ssl.ssl.ja3_server);
|
|
protos.tls.ja3.server_unsafe_cipher = ndpiFlow->protos.stun_ssl.ssl.server_unsafe_cipher;
|
|
protos.tls.ja3.server_cipher = ndpiFlow->protos.stun_ssl.ssl.server_cipher;
|
|
updateSrvJA3();
|
|
}
|
|
break;
|
|
|
|
case NDPI_PROTOCOL_DNS:
|
|
/* Don't free the memory, let the nDPI dissection run for DNS. See Method Flow::processDNSPacket */
|
|
if(getInterface()->isPacketInterface())
|
|
free_ndpi_memory = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Free the nDPI memory */
|
|
if(free_ndpi_memory)
|
|
freeDPIMemory();
|
|
|
|
/*
|
|
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(const u_char *ip_packet, u_int16_t ip_len, u_int64_t packet_time,
|
|
u_int8_t *payload, u_int16_t payload_len) {
|
|
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,
|
|
(struct ndpi_id_struct*) cli_id,
|
|
(struct ndpi_id_struct*) srv_id);
|
|
|
|
detected = ndpi_is_protocol_detected(iface->get_ndpi_struct(), proto_id);
|
|
|
|
if(!detected && hasDissectedTooManyPackets()) {
|
|
endProtocolDissection();
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
if(detected && (proto_id.app_protocol == NDPI_PROTOCOL_UNKNOWN))
|
|
printf("Detected: %d/%d\n", proto_id.master_protocol, proto_id.app_protocol);
|
|
#endif
|
|
|
|
#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(detected) {
|
|
updateProtocol(proto_id);
|
|
setProtocolDetectionCompleted();
|
|
setMatchedPacketPayload(payload, payload_len);
|
|
}
|
|
|
|
if(detection_completed && !needsExtraDissection())
|
|
setExtraDissectionCompleted();
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::setMatchedPacketPayload(u_int8_t *payload, u_int16_t payload_len) {
|
|
if((payload_len > 0) && (packet_payload_match.payload_len == 0)) {
|
|
if((packet_payload_match.payload = (u_int8_t*)malloc(payload_len*sizeof(u_int8_t))) != NULL) {
|
|
memcpy(packet_payload_match.payload, payload, payload_len);
|
|
packet_payload_match.payload_len = payload_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* 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())
|
|
return;
|
|
|
|
/* Instruct nDPI to continue the dissection
|
|
See https://github.com/ntop/ntopng/commit/30f52179d9f7a1eb774534def93d55c77d6070bc#diff-20b1df29540b6de59ceb6c6d2f3afdb5R387
|
|
*/
|
|
// ndpiFlow->protos.dns.num_answers = 0;
|
|
// ndpiFlow->host_server_name[0] = '\0';
|
|
ndpiFlow->check_extra_packets = 1, ndpiFlow->max_extra_packets_to_check = 10;
|
|
|
|
proto_id = ndpi_detection_process_packet(iface->get_ndpi_struct(), ndpiFlow,
|
|
ip_packet, ip_len, packet_time,
|
|
(struct ndpi_id_struct*) cli_id, (struct ndpi_id_struct*) srv_id);
|
|
|
|
/*
|
|
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') {
|
|
if(ndpiFlow->protos.dns.is_query) {
|
|
char *q = strdup((const char*)ndpiFlow->host_server_name);
|
|
|
|
if(q) {
|
|
protos.dns.invalid_chars_in_query = false;
|
|
|
|
for(int i = 0; q[i] != '\0'; i++) {
|
|
if(!isprint(q[i])) {
|
|
q[i] = '?';
|
|
protos.dns.invalid_chars_in_query = true;
|
|
}
|
|
}
|
|
|
|
setDNSQuery(q);
|
|
protos.dns.last_query_type = ndpiFlow->protos.dns.query_type;
|
|
}
|
|
} else { /* this is a response... */
|
|
if(ntop->getPrefs()->decode_dns_responses()) {
|
|
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);
|
|
protos.dns.last_return_code = ndpiFlow->protos.dns.reply_code;
|
|
#endif
|
|
|
|
if(ndpiFlow->protos.dns.reply_code == 0) {
|
|
if(ndpiFlow->protos.dns.num_answers > 0) {
|
|
if(at != NULL) {
|
|
// ntop->getTrace()->traceEvent(TRACE_NORMAL, "[DNS] %s <-> %s", name, (char*)ndpiFlow->host_server_name);
|
|
ntop->getRedis()->setResolvedAddress(name, (char*)ndpiFlow->host_server_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#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
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* 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() {
|
|
if(!detection_completed) {
|
|
u_int8_t proto_guessed;
|
|
|
|
updateProtocol(ndpi_detection_giveup(iface->get_ndpi_struct(), ndpiFlow, 1, &proto_guessed));
|
|
setProtocolDetectionCompleted();
|
|
}
|
|
|
|
if(!extra_dissection_completed)
|
|
setExtraDissectionCompleted();
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* Manually set a protocol on the flow and terminate the dissection. */
|
|
void Flow::setDetectedProtocol(ndpi_protocol proto_id) {
|
|
updateProtocol(proto_id);
|
|
setProtocolDetectionCompleted();
|
|
|
|
endProtocolDissection();
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* Called when the extra dissection on the flow is completed. */
|
|
void Flow::setExtraDissectionCompleted() {
|
|
if(extra_dissection_completed)
|
|
return;
|
|
|
|
if(!detection_completed) {
|
|
ntop->getTrace()->traceEvent(TRACE_ERROR, "Bad internal status: setExtraDissectionCompleted called before setDetectedProtocol");
|
|
return;
|
|
}
|
|
|
|
if((protocol == IPPROTO_TCP) || (protocol == IPPROTO_UDP)) {
|
|
/* 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. */
|
|
if(ndpiDetectedProtocol.category == NDPI_PROTOCOL_CATEGORY_UNSPECIFIED /* Override only if unspecified */
|
|
&& get_cli_ip_addr()->get_ipv4() && get_srv_ip_addr()->get_ipv4() /* Only IPv4 is supported */) {
|
|
ndpi_fill_ip_protocol_category(iface->get_ndpi_struct(),
|
|
get_cli_ip_addr()->get_ipv4(), get_srv_ip_addr()->get_ipv4(),
|
|
&ndpiDetectedProtocol);
|
|
stats.setDetectedProtocol(&ndpiDetectedProtocol);
|
|
}
|
|
}
|
|
|
|
processExtraDissectedInformation();
|
|
|
|
extra_dissection_completed = true;
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::updateProtocol(ndpi_protocol proto_id) {
|
|
/* NOTE: in order to avoid inconsistent states, only overwrite the
|
|
* protocools if UNKNOWN. */
|
|
if(ndpiDetectedProtocol.master_protocol == NDPI_PROTOCOL_UNKNOWN)
|
|
ndpiDetectedProtocol.master_protocol = proto_id.master_protocol;
|
|
|
|
if(ndpiDetectedProtocol.app_protocol == NDPI_PROTOCOL_UNKNOWN)
|
|
ndpiDetectedProtocol.app_protocol = proto_id.app_protocol;
|
|
|
|
/* 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)
|
|
ndpiDetectedProtocol.category = proto_id.category;
|
|
|
|
#ifdef NTOPNG_PRO
|
|
#ifdef HAVE_NEDGE
|
|
updateFlowShapers(true);
|
|
#else
|
|
updateProfile();
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* Called to update the flow protocol and possibly advance the flow to
|
|
* the protocol_detected state. */
|
|
void Flow::setProtocolDetectionCompleted() {
|
|
if(detection_completed)
|
|
return;
|
|
|
|
stats.setDetectedProtocol(&ndpiDetectedProtocol);
|
|
processDetectedProtocol();
|
|
|
|
detection_completed = true;
|
|
|
|
#ifdef BLACKLISTED_FLOWS_DEBUG
|
|
if(ndpiDetectedProtocol.category == CUSTOM_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]",
|
|
isBlacklistedClient(), isBlacklistedServer(), get_protocol_category_name());
|
|
ntop->getTrace()->traceEvent(TRACE_NORMAL, "%s", buf);
|
|
}
|
|
#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) const {
|
|
char buf1[32], buf2[32], buf3[32], buf4[32], buf5[32], pbuf[32], tcp_buf[64];
|
|
buf[0] = '\0';
|
|
|
|
#if defined(NTOPNG_PRO) && defined(SHAPER_DEBUG)
|
|
char shapers[64];
|
|
|
|
TrafficShaper *cli2srv_in = flowShaperIds.cli2srv.ingress;
|
|
TrafficShaper *cli2srv_out = flowShaperIds.cli2srv.egress;
|
|
TrafficShaper *srv2cli_in = flowShaperIds.srv2cli.ingress;
|
|
TrafficShaper *srv2cli_out = flowShaperIds.srv2cli.egress;
|
|
|
|
if(iface->is_bridge_interface()) {
|
|
snprintf(shapers, sizeof(shapers),
|
|
"[pass_verdict: %s] "
|
|
"[shapers: cli2srv=%u/%u, srv2cli=%u/%u] "
|
|
"[cli2srv_ingress shaping_enabled: %i max_rate: %lu] "
|
|
"[cli2srv_egress shaping_enabled: %i max_rate: %lu] "
|
|
"[srv2cli_ingress shaping_enabled: %i max_rate: %lu] "
|
|
"[srv2cli_egress shaping_enabled: %i max_rate: %lu] ",
|
|
passVerdict ? "PASS" : "DROP",
|
|
flowShaperIds.cli2srv.ingress ? flowShaperIds.cli2srv.ingress->get_shaper_id() : DEFAULT_SHAPER_ID,
|
|
flowShaperIds.cli2srv.egress ? flowShaperIds.cli2srv.egress->get_shaper_id() : DEFAULT_SHAPER_ID,
|
|
flowShaperIds.srv2cli.ingress ? flowShaperIds.srv2cli.ingress->get_shaper_id() : DEFAULT_SHAPER_ID,
|
|
flowShaperIds.srv2cli.egress ? flowShaperIds.srv2cli.egress->get_shaper_id() : DEFAULT_SHAPER_ID,
|
|
cli2srv_in->shaping_enabled(), cli2srv_in->get_max_rate_kbit_sec(),
|
|
cli2srv_out->shaping_enabled(), cli2srv_out->get_max_rate_kbit_sec(),
|
|
srv2cli_in->shaping_enabled(), srv2cli_in->get_max_rate_kbit_sec(),
|
|
srv2cli_out->shaping_enabled(), srv2cli_out->get_max_rate_kbit_sec()
|
|
);
|
|
} else
|
|
shapers[0] = '\0';
|
|
|
|
#endif
|
|
|
|
tcp_buf[0] = '\0';
|
|
if(protocol == IPPROTO_TCP) {
|
|
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 > %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.master_protocol, ndpiDetectedProtocol.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::dumpFlow(const struct timeval *tv, NetworkInterface *dumper, bool no_time_left) {
|
|
bool rc = false;
|
|
|
|
if(ntop->getPrefs()->is_runtime_flows_dump_enabled() /* Check if dump has been disabled at runtime from a UI preference */
|
|
&& (ntop->getPrefs()->do_dump_flows() /* This check if flows dump has been statically enabled from the CLI */
|
|
#ifndef HAVE_NEDGE
|
|
|| ntop->get_export_interface()
|
|
#endif
|
|
)) {
|
|
if(!ntop->getPrefs()->is_tiny_flows_export_enabled() && isTiny()) {
|
|
#ifdef TINY_FLOWS_DEBUG
|
|
ntop->getTrace()->traceEvent(TRACE_NORMAL,
|
|
"Skipping tiny flow dump "
|
|
"[flow key: %u]"
|
|
"[packets current/max: %i/%i] "
|
|
"[bytes current/max: %i/%i].",
|
|
key(),
|
|
get_packets(),
|
|
ntop->getPrefs()->get_max_num_packets_per_tiny_flow(),
|
|
get_bytes(),
|
|
ntop->getPrefs()->get_max_num_bytes_per_tiny_flow());
|
|
|
|
#endif
|
|
return(rc);
|
|
}
|
|
|
|
if(!idle()) {
|
|
if((dumper->getIfType() == interface_type_PCAP_DUMP && !dumper->read_from_pcap_dump_done())
|
|
|| tv->tv_sec - get_first_seen() < CONST_DB_DUMP_FREQUENCY
|
|
|| tv->tv_sec - get_partial_last_seen() < CONST_DB_DUMP_FREQUENCY) {
|
|
return(rc);
|
|
}
|
|
} else {
|
|
/* Flow idle, i.e., ready to be purged, are always dumped */
|
|
}
|
|
|
|
if(!update_partial_traffic_stats_db_dump())
|
|
return rc; /* Partial stats update has failed */
|
|
|
|
/* Check for bytes, and not for packets, as with nprobeagent
|
|
there are not packet counters, just bytes. */
|
|
if(!get_partial_bytes())
|
|
return rc; /* Nothing to dump */
|
|
|
|
#ifdef NTOPNG_PRO
|
|
if(ntop->getPro()->has_valid_license() && ntop->getPrefs()->is_enterprise_m_edition())
|
|
dumper->aggregatePartialFlow(tv, this);
|
|
#endif
|
|
|
|
dumper->dumpFlow(last_seen, this, no_time_left);
|
|
|
|
#ifndef HAVE_NEDGE
|
|
if(ntop->get_export_interface()) {
|
|
char *json = serialize(false);
|
|
|
|
if(json) {
|
|
ntop->get_export_interface()->export_data(json);
|
|
free(json);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
rc = true;
|
|
}
|
|
|
|
return(rc);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::setDropVerdict() {
|
|
#if defined(HAVE_NEDGE)
|
|
if((iface->getIfType() == interface_type_NETFILTER) && (passVerdict == true))
|
|
((NetfilterInterface *) iface)->setPolicyChanged();
|
|
#endif
|
|
|
|
passVerdict = false;
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
#ifdef HAVE_NEDGE
|
|
void Flow::incFlowDroppedCounters() {
|
|
if(!flow_dropped_counts_increased) {
|
|
if(cli_host) {
|
|
cli_host->incNumDroppedFlows();
|
|
if(cli_host->getMac()) cli_host->getMac()->incNumDroppedFlows();
|
|
}
|
|
|
|
#ifdef NTOPNG_PRO
|
|
HostPools *h = iface ? iface->getHostPools() : NULL;
|
|
u_int16_t cli_pool = NO_HOST_POOL_ID;
|
|
|
|
if(h) {
|
|
cli_pool = cli_host ? cli_host->get_host_pool() : NO_HOST_POOL_ID;
|
|
|
|
if(cli_pool != NO_HOST_POOL_ID)
|
|
h->incPoolNumDroppedFlows(cli_pool);
|
|
}
|
|
#endif
|
|
|
|
/* Increasing stats on the server is pointless.
|
|
If a flow is dropped, the server doesn't even see it,
|
|
it is just the client that gets a drop. */
|
|
flow_dropped_counts_increased = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* *************************************** */
|
|
|
|
/* 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). */
|
|
void Flow::hosts_periodic_stats_update(NetworkInterface *iface, Host *cli_host, Host *srv_host,
|
|
PartializableFlowTrafficStats *partial, bool first_partial, const struct timeval *tv) const {
|
|
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;
|
|
int16_t cli_network_id = cli_host->get_local_network_id();
|
|
int16_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;
|
|
|
|
if(cli_network_id >= 0 && (cli_network_id == srv_network_id))
|
|
cli_and_srv_in_same_subnet = true;
|
|
|
|
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", vlanId);
|
|
#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 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());
|
|
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());
|
|
}
|
|
|
|
// 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());
|
|
}
|
|
}
|
|
|
|
if(!peers_score_accounted && idle()) {
|
|
/* The flow went idle to quickly as the run_min_flows_tasks was not
|
|
* called. Account the score using a separate counter. */
|
|
cli_host->getScore()->incIdleFlowScore(getCliScore());
|
|
srv_host->getScore()->incIdleFlowScore(getSrvScore());
|
|
}
|
|
}
|
|
|
|
switch(get_protocol()) {
|
|
case IPPROTO_TCP:
|
|
Flow::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());
|
|
Flow::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 != os_unknown) {
|
|
if(cli_host
|
|
&& !(get_cli_ip_addr()->isBroadcastAddress()
|
|
|| get_cli_ip_addr()->isMulticastAddress()))
|
|
cli_host->setOS(operating_system);
|
|
}
|
|
/* Don't break, let's process also HTTP_PROXY */
|
|
case NDPI_PROTOCOL_HTTP_PROXY:
|
|
if(srv_host
|
|
&& 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());
|
|
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_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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
/* *************************************** */
|
|
|
|
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 >= 1000 /* Do not update when less than 1 second (1000 msec) */) {
|
|
// bps
|
|
float bytes_msec_cli2srv = ((float)(diff_sent_bytes*1000))/tdiff_msec;
|
|
float bytes_msec_srv2cli = ((float)(diff_rcvd_bytes*1000))/tdiff_msec;
|
|
float bytes_msec = bytes_msec_cli2srv + bytes_msec_srv2cli;
|
|
|
|
float goodput_bytes_msec_cli2srv = ((float)(diff_sent_goodput_bytes*1000))/tdiff_msec;
|
|
float goodput_bytes_msec_srv2cli = ((float)(diff_rcvd_goodput_bytes*1000))/tdiff_msec;
|
|
float goodput_bytes_msec = goodput_bytes_msec_cli2srv + goodput_bytes_msec_srv2cli;
|
|
|
|
/* Just to be safe */
|
|
if(bytes_msec < 0) bytes_msec = 0;
|
|
if(bytes_msec_cli2srv < 0) bytes_msec_cli2srv = 0;
|
|
if(bytes_msec_srv2cli < 0) bytes_msec_srv2cli = 0;
|
|
if(goodput_bytes_msec < 0) goodput_bytes_msec = 0;
|
|
if(goodput_bytes_msec_cli2srv < 0) goodput_bytes_msec_cli2srv = 0;
|
|
if(goodput_bytes_msec_srv2cli < 0) goodput_bytes_msec_srv2cli = 0;
|
|
|
|
if((bytes_msec > 0) || iface->isPacketInterface()) {
|
|
// refresh trend stats for the overall throughput
|
|
if(bytes_thpt < bytes_msec) bytes_thpt_trend = trend_up;
|
|
else if(bytes_thpt > bytes_msec) bytes_thpt_trend = trend_down;
|
|
else bytes_thpt_trend = trend_stable;
|
|
|
|
// refresh goodput stats for the overall throughput
|
|
if(goodput_bytes_thpt < goodput_bytes_msec) goodput_bytes_thpt_trend = trend_up;
|
|
else if(goodput_bytes_thpt > goodput_bytes_msec) goodput_bytes_thpt_trend = trend_down;
|
|
else goodput_bytes_thpt_trend = trend_stable;
|
|
|
|
#if DEBUG_TREND
|
|
u_int64_t diff_bytes = diff_sent_bytes + diff_rcvd_bytes;
|
|
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[msec: %.1f][bytes: %lu][bits_thpt: %.4f Mbps]",
|
|
bytes_msec, diff_bytes, (bytes_thpt*8)/((float)(1024*1024)));
|
|
#endif
|
|
|
|
// update the old values with the newly calculated ones
|
|
bytes_thpt_cli2srv = bytes_msec_cli2srv;
|
|
bytes_thpt_srv2cli = bytes_msec_srv2cli;
|
|
goodput_bytes_thpt_cli2srv = goodput_bytes_msec_cli2srv;
|
|
goodput_bytes_thpt_srv2cli = goodput_bytes_msec_srv2cli;
|
|
|
|
bytes_thpt = bytes_msec, goodput_bytes_thpt = goodput_bytes_msec;
|
|
if(top_bytes_thpt < bytes_thpt) top_bytes_thpt = bytes_thpt;
|
|
if(top_goodput_bytes_thpt < goodput_bytes_thpt) top_goodput_bytes_thpt = goodput_bytes_thpt;
|
|
|
|
#ifdef NTOPNG_PRO
|
|
throughputTrend.update(bytes_thpt), goodputTrend.update(goodput_bytes_thpt);
|
|
thptRatioTrend.update(((double)(goodput_bytes_msec*100))/(double)bytes_msec);
|
|
|
|
#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_cli2srv() + get_goodput_bytes_srv2cli())))/(float)(get_bytes_cli2srv() + get_bytes_srv2cli()));
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
// pps
|
|
float pkts_msec_cli2srv = ((float)(diff_sent_packets*1000))/tdiff_msec;
|
|
float pkts_msec_srv2cli = ((float)(diff_rcvd_packets*1000))/tdiff_msec;
|
|
float pkts_msec = pkts_msec_cli2srv + pkts_msec_srv2cli;
|
|
|
|
/* Just to be safe */
|
|
if(pkts_msec < 0) pkts_msec = 0;
|
|
if(pkts_msec_cli2srv < 0) pkts_msec_cli2srv = 0;
|
|
if(pkts_msec_srv2cli < 0) pkts_msec_srv2cli = 0;
|
|
|
|
if(pkts_thpt < pkts_msec) pkts_thpt_trend = trend_up;
|
|
else if(pkts_thpt > pkts_msec) pkts_thpt_trend = trend_down;
|
|
else pkts_thpt_trend = trend_stable;
|
|
|
|
pkts_thpt_cli2srv = pkts_msec_cli2srv;
|
|
pkts_thpt_srv2cli = pkts_msec_srv2cli;
|
|
pkts_thpt = pkts_msec;
|
|
if(top_pkts_thpt < pkts_thpt) top_pkts_thpt = 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, pkts_thpt);
|
|
#endif
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::periodic_stats_update(void *user_data) {
|
|
periodic_stats_update_user_data_t *periodic_stats_update_user_data = (periodic_stats_update_user_data_t*) user_data;
|
|
struct timeval *tv = periodic_stats_update_user_data->tv;
|
|
bool first_partial;
|
|
PartializableFlowTrafficStats partial;
|
|
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();
|
|
|
|
|
|
Mac *cli_mac = get_cli_host() ? get_cli_host()->getMac() : NULL;
|
|
Mac *srv_mac = get_srv_host() ? get_srv_host()->getMac() : NULL;
|
|
|
|
hosts_periodic_stats_update(getInterface(), cli_host, srv_host, &partial, first_partial, tv);
|
|
|
|
if(cli_host && srv_host) {
|
|
if(diff_sent_bytes || diff_rcvd_bytes) {
|
|
/* Update L2 Device stats */
|
|
if(srv_mac) {
|
|
#ifdef HAVE_NEDGE
|
|
srv_mac->incSentStats(tv->tv_sec, diff_rcvd_packets, diff_rcvd_bytes);
|
|
srv_mac->incRcvdStats(tv->tv_sec, diff_sent_packets, diff_sent_bytes);
|
|
#endif
|
|
|
|
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);
|
|
|
|
}
|
|
}
|
|
|
|
if(cli_mac) {
|
|
#ifdef HAVE_NEDGE
|
|
cli_mac->incSentStats(tv->tv_sec, diff_sent_packets, diff_sent_bytes);
|
|
cli_mac->incRcvdStats(tv->tv_sec, diff_rcvd_packets, diff_rcvd_bytes);
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
#ifdef NTOPNG_PRO
|
|
if(ntop->getPro()->has_valid_license()) {
|
|
|
|
#ifndef HAVE_NEDGE
|
|
if(trafficProfile)
|
|
trafficProfile->incBytes(diff_sent_bytes + diff_rcvd_bytes);
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Check and possibly enqueue host remote-to-remote alerts */
|
|
// TODO migrate to lua
|
|
if(!cli_host->isLocalHost() && !srv_host->isLocalHost()
|
|
&& get_cli_ip_addr()->isNonEmptyUnicastAddress()
|
|
&& get_srv_ip_addr()->isNonEmptyUnicastAddress()
|
|
&& !cli_host->setRemoteToRemoteAlerts()) {
|
|
iface->getAlertsQueue()->pushRemoteToRemoteAlert(cli_host);
|
|
}
|
|
} /* Closes if(cli_host && srv_host) */
|
|
|
|
/* Non-Packet interfaces (e.g., ZMQ) have flow throughput stats updated as soon as the flow is received.
|
|
This makes throughput more precise as it is averaged on a timespan which is last-first switched. */
|
|
if(iface->isPacketInterface() && last_update_time.tv_sec > 0) {
|
|
float tdiff_msec = Utils::msTimevalDiff(tv, &last_update_time);
|
|
updateThroughputStats(tdiff_msec,
|
|
diff_sent_packets, diff_sent_bytes, diff_sent_goodput_bytes,
|
|
diff_rcvd_packets, diff_rcvd_bytes, diff_rcvd_goodput_bytes);
|
|
|
|
}
|
|
|
|
memcpy(&last_update_time, tv, sizeof(struct timeval));
|
|
GenericHashEntry::periodic_stats_update(user_data);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::periodic_dump_check(const struct timeval *tv, bool no_time_left) {
|
|
/* Viewed interfaces don't dump flows, their flows are dumped by the overlying ViewInterface.
|
|
ViewInterface dump their flows in another thread, not this one. */
|
|
dumpFlow(tv, iface->isViewed() ? iface->viewedBy() : iface, no_time_left);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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, 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.app_protocol != NDPI_PROTOCOL_UNKNOWN
|
|
&& !ndpi_is_subprotocol_informative(NULL, ndpiDetectedProtocol.master_protocol))
|
|
hp->incPoolStats(tv->tv_sec, cli_host_pool_id, ndpiDetectedProtocol.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.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.master_protocol,
|
|
diff_sent_packets, diff_sent_bytes, diff_rcvd_packets, diff_rcvd_bytes);
|
|
cli_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.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.app_protocol != NDPI_PROTOCOL_UNKNOWN
|
|
&& !ndpi_is_subprotocol_informative(NULL, ndpiDetectedProtocol.master_protocol))
|
|
hp->incPoolStats(tv->tv_sec, srv_host_pool_id, ndpiDetectedProtocol.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.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.master_protocol,
|
|
diff_rcvd_packets, diff_rcvd_bytes, diff_sent_packets, diff_sent_bytes);
|
|
srv_host->incQuotaEnforcementStats(tv->tv_sec, ndpiDetectedProtocol.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 IpAddress *_cli_ip, const IpAddress *_srv_ip,
|
|
u_int16_t _cli_port, u_int16_t _srv_port,
|
|
u_int16_t _vlanId, 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();
|
|
|
|
#if 0
|
|
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(_vlanId != vlanId)
|
|
return(false);
|
|
|
|
if(_protocol != protocol)
|
|
return(false);
|
|
|
|
if(icmp_info && !icmp_info->equal(_icmp_info))
|
|
return(false);
|
|
|
|
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;
|
|
return(true);
|
|
} 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;
|
|
return(true);
|
|
} else
|
|
return(false);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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::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;
|
|
|
|
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_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(details_level >= details_high) {
|
|
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(vrfId) lua_push_uint64_table_entry(vm, "vrfId", vrfId);
|
|
lua_push_uint64_table_entry(vm, "vlan", get_vlan_id());
|
|
|
|
if(srcAS) lua_push_int32_table_entry(vm, "src_as", srcAS);
|
|
if(dstAS) lua_push_int32_table_entry(vm, "dst_as", dstAS);
|
|
if(prevAdjacentAS) lua_push_int32_table_entry(vm, "prev_adjacent_as", prevAdjacentAS);
|
|
if(nextAdjacentAS)lua_push_int32_table_entry(vm, "next_adjacent_as", nextAdjacentAS);
|
|
|
|
lua_get_protocols(vm);
|
|
|
|
#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 */);
|
|
|
|
#ifdef NTOPNG_PRO
|
|
lua_push_int32_table_entry(vm, "score", getScore());
|
|
#endif
|
|
|
|
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() ? (json_bool)1 : (json_bool)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(bt_hash) lua_push_str_table_entry(vm, "bittorrent_hash", bt_hash);
|
|
lua_push_str_table_entry(vm, "info", getFlowInfo() ? getFlowInfo() : (char*)"");
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef HAVE_NEDGE
|
|
lua_push_uint64_table_entry(vm, "marker", marker);
|
|
|
|
if(cli_host && srv_host) {
|
|
/* Shapers */
|
|
lua_push_uint64_table_entry(vm,
|
|
"shaper.cli2srv_ingress",
|
|
flowShaperIds.cli2srv.ingress ? flowShaperIds.cli2srv.ingress->get_shaper_id() : DEFAULT_SHAPER_ID);
|
|
lua_push_uint64_table_entry(vm,
|
|
"shaper.cli2srv_egress",
|
|
flowShaperIds.cli2srv.egress ? flowShaperIds.cli2srv.egress->get_shaper_id() : DEFAULT_SHAPER_ID);
|
|
lua_push_uint64_table_entry(vm,
|
|
"shaper.srv2cli_ingress",
|
|
flowShaperIds.srv2cli.ingress ? flowShaperIds.srv2cli.ingress->get_shaper_id() : DEFAULT_SHAPER_ID);
|
|
lua_push_uint64_table_entry(vm,
|
|
"shaper.srv2cli_egress",
|
|
flowShaperIds.srv2cli.egress ? flowShaperIds.srv2cli.egress->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(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 (!has_json_info)
|
|
lua_push_str_table_entry(vm, "moreinfo.json", "{}");
|
|
|
|
if(cli_ebpf) cli_ebpf->lua(vm, true);
|
|
if(srv_ebpf) srv_ebpf->lua(vm, false);
|
|
|
|
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 */);
|
|
}
|
|
}
|
|
|
|
if(alert_status_info)
|
|
lua_push_str_table_entry(vm, "status_info", alert_status_info);
|
|
}
|
|
|
|
lua_get_status(vm);
|
|
|
|
// 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());
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
u_int32_t Flow::key() {
|
|
u_int32_t k = cli_port + srv_port + vlanId + protocol;
|
|
|
|
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();
|
|
|
|
return(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 _protocol) {
|
|
u_int32_t k = _cli_port + _srv_port + _vlan_id + _protocol;
|
|
|
|
if(_cli) k += _cli -> key();
|
|
if(_srv) k += _srv -> key();
|
|
|
|
return(k);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::set_hash_entry_id(u_int assigned_hash_entry_id) {
|
|
hash_entry_id = assigned_hash_entry_id;
|
|
};
|
|
|
|
/* *************************************** */
|
|
|
|
u_int Flow::get_hash_entry_id() const {
|
|
return hash_entry_id;
|
|
};
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::set_hash_entry_state_idle() {
|
|
if(cli_host)
|
|
cli_host->decNumFlows(last_seen, true, srv_host, this);
|
|
|
|
if(srv_host)
|
|
srv_host->decNumFlows(last_seen, false, cli_host, this);
|
|
|
|
if(isFlowAlerted()) {
|
|
iface->decNumAlertedFlows(this);
|
|
#ifdef ALERTED_FLOWS_DEBUG
|
|
iface_alert_dec = true;
|
|
#endif
|
|
}
|
|
|
|
GenericHashEntry::set_hash_entry_state_idle();
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
bool Flow::is_hash_entry_state_idle_transition_ready() const {
|
|
if(!periodic_stats_update_partial /* waiting for Flow::periodic_stats_update first execution... */
|
|
/* ... and sure all traffic has been seen by Flow::periodic_stats_update */
|
|
|| periodic_stats_update_partial->get_packets() < stats.get_packets()
|
|
|| periodic_stats_update_partial->get_bytes() < stats.get_bytes())
|
|
return false;
|
|
|
|
#ifdef HAVE_NEDGE
|
|
if(iface->getIfType() == interface_type_NETFILTER)
|
|
return(isNetfilterIdleFlow());
|
|
#endif
|
|
|
|
if(protocol == IPPROTO_TCP) {
|
|
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()))
|
|
/* Flows won't expire if less than DONT_NOT_EXPIRE_BEFORE_SEC old */
|
|
&& iface->getTimeLastPktRcvd() > doNotExpireBefore
|
|
&& isIdle(MAX_TCP_FLOW_IDLE)) {
|
|
/* ntop->getTrace()->traceEvent(TRACE_NORMAL, "[TCP] Early flow expire"); */
|
|
return(true);
|
|
}
|
|
}
|
|
|
|
return(isIdle(iface->getFlowMaxIdle()));
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::periodic_hash_entry_state_update(void *user_data) {
|
|
periodic_ht_state_update_user_data_t *htstats = (periodic_ht_state_update_user_data_t*)user_data;
|
|
struct timeval *tv = htstats->tv;
|
|
bool no_time_left = htstats->no_time_left;
|
|
|
|
switch(get_state()) {
|
|
case hash_entry_state_allocated:
|
|
case hash_entry_state_flow_notyetdetected:
|
|
/* Nothing to do here */
|
|
break;
|
|
|
|
case hash_entry_state_flow_protocoldetected:
|
|
pending_lua_call_protocol_detected = true;
|
|
set_hash_entry_state_active();
|
|
break;
|
|
|
|
case hash_entry_state_active:
|
|
if(next_lua_call_periodic_update == 0) next_lua_call_periodic_update = tv->tv_sec + FLOW_LUA_CALL_PERIODIC_UPDATE_SECS;
|
|
periodic_dump_check(tv, no_time_left); /* TODO: move it in a lua script; NOTE: this call can take a long time! */
|
|
/* Don't change state: purgeIdle() will do */
|
|
break;
|
|
|
|
case hash_entry_state_idle:
|
|
postFlowSetIdle(tv);
|
|
periodic_dump_check(tv, no_time_left); /* TODO: move it in a lua script; NOTE: this call can take a long time! */
|
|
break;
|
|
}
|
|
|
|
/* Now that the states in the finite state machine have been moved forward, it is time to check and
|
|
possibly perform lua calls on the flow. */
|
|
if(!htstats->skip_user_scripts)
|
|
performLuaCalls(tv, htstats);
|
|
|
|
GenericHashEntry::periodic_hash_entry_state_update(user_data);
|
|
|
|
/* NOTE: only count entries for which we don't skip_user_scripts */
|
|
if((!htstats->skip_user_scripts) && htstats->thstats && ((htstats->cur_entries % 128) == 0)) {
|
|
/* NOTE: need to also update the total entries as they may
|
|
* have changed since the last update */
|
|
htstats->tot_entries = get_hash_table()->getNumEntries() + get_hash_table()->getNumIdleEntries();
|
|
|
|
htstats->thstats->setCurrentProgress(
|
|
htstats->cur_entries * 100 / htstats->tot_entries);
|
|
}
|
|
|
|
htstats->cur_entries++;
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::sumStats(nDPIStats *ndpi_stats, FlowStats *status_stats) {
|
|
ndpi_protocol detected_protocol = get_detected_protocol();
|
|
|
|
ndpi_stats->incStats(0, detected_protocol.master_protocol,
|
|
get_packets_cli2srv(), get_bytes_cli2srv(),
|
|
get_packets_srv2cli(), get_bytes_srv2cli());
|
|
|
|
if(detected_protocol.app_protocol != detected_protocol.master_protocol
|
|
&& detected_protocol.app_protocol != NDPI_PROTOCOL_UNKNOWN)
|
|
ndpi_stats->incStats(0, detected_protocol.app_protocol,
|
|
get_packets_cli2srv(), get_bytes_cli2srv(),
|
|
get_packets_srv2cli(), get_bytes_srv2cli());
|
|
|
|
status_stats->incStats(getStatusBitmap(), protocol);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
char* Flow::serialize(bool use_labels) {
|
|
json_object *my_object;
|
|
char *rsp = NULL;
|
|
|
|
ntop->getPrefs()->set_json_symbolic_labels_format(use_labels);
|
|
|
|
my_object = flow2json();
|
|
|
|
if(my_object != NULL) {
|
|
/* JSON string */
|
|
rsp = strdup(json_object_to_json_string(my_object));
|
|
|
|
ntop->getTrace()->traceEvent(TRACE_DEBUG, "Emitting Flow: %s", rsp);
|
|
|
|
/* Free memory */
|
|
json_object_put(my_object);
|
|
}
|
|
|
|
return(rsp);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
json_object* Flow::flow2json() {
|
|
json_object *my_object;
|
|
char buf[64], jsonbuf[64], *c;
|
|
time_t t;
|
|
const IpAddress *cli_ip = get_cli_ip_addr(), *srv_ip = get_srv_ip_addr();
|
|
|
|
if((my_object = json_object_new_object()) == NULL) return(NULL);
|
|
|
|
if(ntop->getPrefs()->do_dump_flows_on_es()
|
|
|| ntop->getPrefs()->do_dump_flows_on_ls()
|
|
) {
|
|
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);
|
|
|
|
if(ntop->getPrefs()->do_dump_flows_on_ls()) {
|
|
/* Add current timestamp differently for Logstash, in case of delay
|
|
* Note: Logstash generates it's own @timestamp field on input
|
|
*/
|
|
json_object_object_add(my_object,"ntop_timestamp",json_object_new_string(buf));
|
|
}
|
|
|
|
if(ntop->getPrefs()->do_dump_flows_on_es()) {
|
|
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()));
|
|
}
|
|
/* json_object_object_add(my_object, "@version", json_object_new_int(1)); */
|
|
|
|
// MAC addresses are set only when dumping to ES to optimize space consumption
|
|
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(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))));
|
|
}
|
|
}
|
|
|
|
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(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.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.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))));
|
|
}
|
|
|
|
if(protocol == IPPROTO_TCP)
|
|
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(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((u_int32_t)get_partial_first_seen()));
|
|
json_object_object_add(my_object, Utils::jsonLabel(LAST_SWITCHED, "LAST_SWITCHED", jsonbuf, sizeof(jsonbuf)),
|
|
json_object_new_int((u_int32_t)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(protocol == IPPROTO_TCP) {
|
|
json_object_object_add(my_object, Utils::jsonLabel(CLIENT_NW_LATENCY_MS, "CLIENT_NW_LATENCY_MS", jsonbuf, sizeof(jsonbuf)),
|
|
json_object_new_double(toMs(&clientNwLatency)));
|
|
json_object_object_add(my_object, Utils::jsonLabel(SERVER_NW_LATENCY_MS, "SERVER_NW_LATENCY_MS", jsonbuf, sizeof(jsonbuf)),
|
|
json_object_new_double(toMs(&serverNwLatency)));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
#ifdef NTOPNG_PRO
|
|
#ifndef HAVE_NEDGE
|
|
// Traffic profile information, if any
|
|
if(trafficProfile && trafficProfile->getName())
|
|
json_object_object_add(my_object, "PROFILE", json_object_new_string(trafficProfile->getName()));
|
|
#endif
|
|
#endif
|
|
if(ntop->getPrefs() && ntop->getPrefs()->get_instance_name())
|
|
json_object_object_add(my_object, "NTOPNG_INSTANCE_NAME",
|
|
json_object_new_string(ntop->getPrefs()->get_instance_name()));
|
|
if(iface && iface->get_name())
|
|
json_object_object_add(my_object, "INTERFACE", json_object_new_string(iface->get_name()));
|
|
|
|
if(isDNS() && protos.dns.last_query)
|
|
json_object_object_add(my_object, "DNS_QUERY", json_object_new_string(protos.dns.last_query));
|
|
|
|
if(isHTTP()) {
|
|
if(host_server_name && host_server_name[0] != '\0')
|
|
json_object_object_add(my_object, "HTTP_HOST", json_object_new_string(host_server_name));
|
|
if(protos.http.last_url && protos.http.last_url[0] != '0')
|
|
json_object_object_add(my_object, "HTTP_URL", json_object_new_string(protos.http.last_url));
|
|
if(protos.http.last_method && protos.http.last_method[0] != '\0')
|
|
json_object_object_add(my_object, "HTTP_METHOD", json_object_new_string(protos.http.last_method));
|
|
if(protos.http.last_return_code > 0)
|
|
json_object_object_add(my_object, "HTTP_RET_CODE", json_object_new_int((u_int32_t)protos.http.last_return_code));
|
|
}
|
|
|
|
if(bt_hash)
|
|
json_object_object_add(my_object, "BITTORRENT_HASH", json_object_new_string(bt_hash));
|
|
|
|
if(isTLS() && protos.tls.client_requested_server_name)
|
|
json_object_object_add(my_object, "TLS_SERVER_NAME",
|
|
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",
|
|
json_object_new_boolean(isPassVerdict() ? (json_bool)1 : (json_bool)0));
|
|
#endif
|
|
|
|
if(cli_ebpf) cli_ebpf->getJSONObject(my_object, true);
|
|
if(srv_ebpf) srv_ebpf->getJSONObject(my_object, false);
|
|
|
|
if(ntop->getPrefs()->do_dump_extended_json()) {
|
|
const char *info;
|
|
|
|
/* Add items usually dumped on nIndex (useful for debugging) */
|
|
|
|
json_object_object_add(my_object, "FLOW_TIME", json_object_new_int(last_seen));
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
info = getFlowInfo();
|
|
if (info)
|
|
json_object_object_add(my_object, "INFO", json_object_new_string(info));
|
|
|
|
#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_ID", json_object_new_int(iface->get_id()));
|
|
json_object_object_add(my_object, "STATUS", json_object_new_int((u_int8_t)getPredominantStatus()));
|
|
}
|
|
|
|
return(my_object);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* Create a JSON in the alerts format
|
|
* Using the nDPI json serializer instead of jsonc for faster speed (~2.5x) */
|
|
void Flow::flow2alertJson(ndpi_serializer *s, time_t now) {
|
|
ndpi_serializer json_info;
|
|
u_int32_t buflen;
|
|
const char *info;
|
|
char buf[64];
|
|
|
|
ndpi_init_serializer(&json_info, ndpi_serialization_format_json);
|
|
|
|
/* AlertsManager::storeFlowAlert requires a string */
|
|
info = getFlowInfo();
|
|
ndpi_serialize_string_string(&json_info, "info", info ? info : "");
|
|
ndpi_serialize_string_string(&json_info, "status_info", alert_status_info ? alert_status_info : "");
|
|
ndpi_serialize_string_string(s, "alert_json", ndpi_serializer_get_buffer(&json_info, &buflen));
|
|
ndpi_term_serializer(&json_info);
|
|
|
|
ndpi_serialize_string_int32(s, "ifid", iface->get_id());
|
|
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, "is_flow_alert", true);
|
|
ndpi_serialize_string_int64(s, "alert_tstamp", now);
|
|
ndpi_serialize_string_int64(s, "flow_status", alerted_status);
|
|
ndpi_serialize_string_int32(s, "alert_type", alert_type);
|
|
ndpi_serialize_string_int32(s, "alert_severity", alert_level);
|
|
|
|
ndpi_serialize_string_int32(s, "vlan_id", get_vlan_id());
|
|
ndpi_serialize_string_int32(s, "proto", protocol);
|
|
ndpi_serialize_string_string(s, "proto.ndpi", get_detected_protocol_name(buf, sizeof(buf)));
|
|
ndpi_serialize_string_int32(s, "l7_master_proto", ndpiDetectedProtocol.master_protocol);
|
|
ndpi_serialize_string_int32(s, "l7_proto", ndpiDetectedProtocol.app_protocol);
|
|
|
|
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_string(s, "cli_addr", 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());
|
|
if(cli_host) {
|
|
ndpi_serialize_string_string(s, "cli_country", cli_host->get_country(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_string(s, "srv_addr", 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());
|
|
if(srv_host) {
|
|
ndpi_serialize_string_string(s, "srv_country", srv_host->get_country(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());
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
#ifdef HAVE_NEDGE
|
|
|
|
bool Flow::isNetfilterIdleFlow() const {
|
|
/*
|
|
Note that on netfilter interfaces we never observe the
|
|
FIN/RST flags as they have been offloaded to kernel
|
|
|
|
Hence on netfilter interfaces flows are purged only for
|
|
inactivity based on lastSeen updates
|
|
*/
|
|
|
|
if(last_conntrack_update > 0) {
|
|
/*
|
|
- At latest every MIN_CONNTRACK_UPDATE the scan is performed
|
|
- the conntrack scan time that we assume is less than MIN_CONNTRACK_UPDATE
|
|
- in the worst case this method is called when iface->getTimeLastPktRcvd()
|
|
is almost MIN_CONNTRACK_UPDATE past the last scan
|
|
|
|
Thuis in total we assume that every 3*MIN_CONNTRACK_UPDATE
|
|
seconds an active flow should have been updated
|
|
by conntrack
|
|
*/
|
|
if(iface->getTimeLastPktRcvd() > (last_conntrack_update + (3 * MIN_CONNTRACK_UPDATE)))
|
|
return(true);
|
|
|
|
return(false);
|
|
} else {
|
|
/* if an conntrack update hasn't been seen for this flow
|
|
we use the standard idleness check */
|
|
return(isIdle(iface->getFlowMaxIdle()));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::housekeep(time_t t) {
|
|
if(((t - get_last_seen()) > 5 /* sec */)
|
|
&& iface->get_ndpi_struct() && get_ndpi_flow()) {
|
|
endProtocolDissection();
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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()))
|
|
return(false);
|
|
*first_partial = true;
|
|
} else
|
|
*first_partial = false;
|
|
|
|
stats.get_partial(*dst, fts);
|
|
|
|
return(true);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
/* NOTE: this is only called by the ViewInterface */
|
|
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);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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() == CUSTOM_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]",
|
|
isBlacklistedClient(), isBlacklistedServer(), get_protocol_category_name());
|
|
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::isTLSProto() {
|
|
u_int16_t lower = ndpi_get_lower_proto(ndpiDetectedProtocol);
|
|
|
|
return(
|
|
(lower == NDPI_PROTOCOL_TLS) ||
|
|
(lower == NDPI_PROTOCOL_MAIL_IMAPS) ||
|
|
(lower == NDPI_PROTOCOL_MAIL_SMTPS) ||
|
|
(lower == NDPI_PROTOCOL_MAIL_POPS)
|
|
);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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) {
|
|
bool update_iat = true;
|
|
|
|
payload_len *= iface->getScalingFactor();
|
|
updateSeen();
|
|
|
|
/*
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
if((applLatencyMsec == 0) && (payload_len > 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::updateInterfaceLocalStats(bool src2dst_direction, u_int num_pkts, u_int pkt_len) {
|
|
const IpAddress *from = src2dst_direction ? get_cli_ip_addr() : get_srv_ip_addr();
|
|
const IpAddress *to = src2dst_direction ? get_srv_ip_addr() : get_cli_ip_addr();
|
|
int16_t from_id = 0;
|
|
int16_t to_id = 0;
|
|
|
|
iface->incLocalStats(num_pkts, pkt_len,
|
|
from ? from->isLocalHost(&from_id) : false,
|
|
to ? to->isLocalHost(&to_id) : false);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void 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;
|
|
|
|
double thp_delta_time;
|
|
|
|
if(new_flow)
|
|
/* Average between last and first seen */
|
|
thp_delta_time = difftime(last_seen, first_seen);
|
|
else
|
|
/* Average of the latest update, that is between the new and the previous last_seen */
|
|
thp_delta_time = difftime(last_seen, get_last_seen());
|
|
|
|
#if 0
|
|
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[first: %u][last: %u][get_last_seen: %u][%u][%u][bytes : %u][thpt: %.2f]",
|
|
first_seen, last_seen,
|
|
get_last_seen(),
|
|
last_seen - first_seen,
|
|
last_seen - get_last_seen(),
|
|
in_bytes + out_bytes,
|
|
((in_bytes + out_bytes) / thp_delta_time) / 1024 / 1024 * 8);
|
|
#endif
|
|
|
|
updateSeen(last_seen);
|
|
|
|
/*
|
|
The throughput is updated roughly by estimating
|
|
the average throughput. This prevents
|
|
having flows with seemingly zero throughput.
|
|
*/
|
|
updateThroughputStats(thp_delta_time * 1000,
|
|
in_pkts, in_bytes, 0,
|
|
out_pkts, out_bytes, 0);
|
|
|
|
if(cli2srv_direction) {
|
|
stats.incStats(true, in_pkts, in_bytes, in_goodput_bytes);
|
|
stats.incStats(false, out_pkts, out_bytes, out_goodput_bytes);
|
|
ip_stats_s2d.pktFrag += in_fragments, ip_stats_d2s.pktFrag += out_fragments;
|
|
} else {
|
|
stats.incStats(true, out_pkts, out_bytes, out_goodput_bytes);
|
|
stats.incStats(false, in_pkts, in_bytes, in_goodput_bytes);
|
|
ip_stats_s2d.pktFrag += out_fragments, ip_stats_d2s.pktFrag += in_fragments;
|
|
}
|
|
|
|
if(bytes_thpt == 0 && last_seen >= first_seen + 1) {
|
|
/* Do a fist estimation while waiting for the periodic activities */
|
|
bytes_thpt = (get_bytes_cli2srv() + get_bytes_srv2cli()) / (float)(last_seen - first_seen),
|
|
pkts_thpt = (get_packets_cli2srv() + get_packets_srv2cli()) / (float)(last_seen - first_seen);
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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::updateTcpFlags(const struct bpf_timeval *when,
|
|
u_int8_t flags, bool src2dst_direction) {
|
|
NetworkStats *cli_network_stats = NULL, *srv_network_stats = NULL;
|
|
/* Only packet-interfaces see every segment. Non-packet-interfaces
|
|
have cumulative flags */
|
|
bool cumulative_flags = !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;
|
|
|
|
iface->incFlagStats(flags, cumulative_flags);
|
|
|
|
if(cli_host) {
|
|
cli_host->incFlagStats(src2dst_direction, flags, cumulative_flags);
|
|
cli_network_stats = cli_host->getNetworkStats(cli_host->get_local_network_id());
|
|
}
|
|
if(srv_host) {
|
|
srv_host->incFlagStats(!src2dst_direction, flags, cumulative_flags);
|
|
srv_network_stats = srv_host->getNetworkStats(srv_host->get_local_network_id());
|
|
}
|
|
|
|
/* Update syn alerts counters. In case of cumulative flags, the AND is used as possibly other flags can be present */
|
|
if((!cumulative_flags && flags_3wh == TH_SYN)
|
|
|| (cumulative_flags && (flags_3wh & 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);
|
|
}
|
|
|
|
/* Update synack alerts counter. In case of cumulative flags, the AND is used as possibly other flags can be present */
|
|
if((!cumulative_flags && (flags_3wh == (TH_SYN|TH_ACK)))
|
|
|| (cumulative_flags && ((flags_3wh & (TH_SYN|TH_ACK)) == (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((flags & TH_FIN) && (((src2dst_tcp_flags | dst2src_tcp_flags) & TH_FIN) != TH_FIN))
|
|
iface->getTcpFlowStats()->incFin();
|
|
|
|
/* The update below must be after the above check */
|
|
if(src2dst_direction)
|
|
src2dst_tcp_flags |= flags;
|
|
else
|
|
dst2src_tcp_flags |= flags;
|
|
|
|
if(cumulative_flags) {
|
|
if(!twh_over) {
|
|
if((src2dst_tcp_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)
|
|
&& ((dst2src_tcp_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)))
|
|
twh_ok = twh_over = true,
|
|
iface->getTcpFlowStats()->incEstablished();
|
|
}
|
|
} else {
|
|
if(!twh_over) {
|
|
if(flags_3wh == TH_SYN) {
|
|
if(synTime.tv_sec == 0) memcpy(&synTime, when, sizeof(struct timeval));
|
|
} else if(flags_3wh == (TH_SYN|TH_ACK)) {
|
|
if((synAckTime.tv_sec == 0) && (synTime.tv_sec > 0)) {
|
|
memcpy(&synAckTime, when, sizeof(struct timeval));
|
|
timeval_diff(&synTime, (struct timeval*)when, &serverNwLatency, 1);
|
|
/* Sanity check */
|
|
if(serverNwLatency.tv_sec > 5)
|
|
memset(&serverNwLatency, 0, sizeof(serverNwLatency));
|
|
else if(srv_host)
|
|
srv_host->updateRoundTripTime(Utils::timeval2ms(&serverNwLatency));
|
|
}
|
|
} 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((ackTime.tv_sec == 0) && (synAckTime.tv_sec > 0)) {
|
|
memcpy(&ackTime, when, sizeof(struct timeval));
|
|
timeval_diff(&synAckTime, (struct timeval*)when, &clientNwLatency, 1);
|
|
|
|
/* Sanity check */
|
|
if(clientNwLatency.tv_sec > 5)
|
|
memset(&clientNwLatency, 0, sizeof(clientNwLatency));
|
|
else if(cli_host)
|
|
cli_host->updateRoundTripTime(Utils::timeval2ms(&clientNwLatency));
|
|
|
|
setRtt();
|
|
|
|
twh_ok = true;
|
|
iface->getTcpFlowStats()->incEstablished();
|
|
}
|
|
goto not_yet;
|
|
} else {
|
|
not_yet:
|
|
twh_over = true;
|
|
|
|
#if 0
|
|
if(!twh_ok) {
|
|
char buf[256];
|
|
ntop->getTrace()->traceEvent(TRACE_WARNING, "[flags: %u][src2dst: %u] not ok %s", flags, src2dst_direction ? 1 : 0, print(buf, sizeof(buf)));
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
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, u_short 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;
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
const char* Flow::getFlowInfo() {
|
|
if(!isMaskedFlow()) {
|
|
if(isDNS() && protos.dns.last_query)
|
|
return protos.dns.last_query;
|
|
|
|
else if(isHTTP() && protos.http.last_url)
|
|
return protos.http.last_url;
|
|
|
|
else if(isTLS() && protos.tls.client_requested_server_name)
|
|
return protos.tls.client_requested_server_name;
|
|
|
|
else if(bt_hash)
|
|
return bt_hash;
|
|
|
|
else if(host_server_name)
|
|
return host_server_name;
|
|
|
|
else if(isSSH()) {
|
|
if(protos.ssh.server_signature)
|
|
return protos.ssh.server_signature;
|
|
else if(protos.ssh.client_signature)
|
|
return protos.ssh.client_signature;
|
|
}
|
|
}
|
|
|
|
return (char*)"";
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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::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;
|
|
|
|
int16_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 debug = false;
|
|
u_int32_t cnt_keep_alive = 0, cnt_lost = 0, cnt_ooo = 0, cnt_retx = 0;
|
|
|
|
#ifdef HAVE_NEDGE
|
|
return;
|
|
#endif
|
|
|
|
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][last: %u][next: %u]", tcp_seq_s2d.last, tcp_seq_s2d.next);
|
|
|
|
if(window > 0) srv2cli_window = window; /* Note the window is reverted */
|
|
if(tcp_seq_s2d.next > 0) {
|
|
if((tcp_seq_s2d.next != seq_num) /* If equal, seq_num is the expected seq_num as determined with prev. segment */
|
|
&& (tcp_seq_s2d.next != (seq_num - 1))) {
|
|
if((seq_num == 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(tcp_seq_s2d.last == seq_num) {
|
|
if (tcp_seq_s2d.next != tcp_seq_s2d.last) {
|
|
cnt_retx++;
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[src2dst] Packet retransmission");
|
|
}
|
|
} else if((tcp_seq_s2d.last > seq_num)
|
|
&& (seq_num < tcp_seq_s2d.next)) {
|
|
cnt_lost++;
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[src2dst] Packet lost [last: %u][act: %u]", tcp_seq_s2d.last, seq_num);
|
|
} else {
|
|
cnt_ooo++;
|
|
update_last_seqnum = ((seq_num - 1) > tcp_seq_s2d.last) ? true : false;
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[src2dst] Packet OOO [last: %u][act: %u]", tcp_seq_s2d.last, seq_num);
|
|
}
|
|
}
|
|
}
|
|
|
|
tcp_seq_s2d.next = next_seq_num;
|
|
if(update_last_seqnum) tcp_seq_s2d.last = seq_num;
|
|
} else {
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src][last: %u][next: %u]", tcp_seq_d2s.last, tcp_seq_d2s.next);
|
|
|
|
if(window > 0) cli2srv_window = window; /* Note the window is reverted */
|
|
if(tcp_seq_d2s.next > 0) {
|
|
if((tcp_seq_d2s.next != seq_num)
|
|
&& (tcp_seq_d2s.next != (seq_num-1))) {
|
|
if((seq_num == 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(tcp_seq_d2s.last == seq_num) {
|
|
if (tcp_seq_d2s.next != tcp_seq_d2s.last) {
|
|
cnt_retx++;
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] Packet retransmission");
|
|
}
|
|
// bytes
|
|
} else if((tcp_seq_d2s.last > seq_num)
|
|
&& (seq_num < tcp_seq_d2s.next)) {
|
|
cnt_lost++;
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] Packet lost [last: %u][act: %u]", tcp_seq_d2s.last, seq_num);
|
|
} else {
|
|
cnt_ooo++;
|
|
update_last_seqnum = ((seq_num - 1) > tcp_seq_d2s.last) ? true : false;
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] [last: %u][next: %u]", tcp_seq_d2s.last, tcp_seq_d2s.next);
|
|
if(debug) ntop->getTrace()->traceEvent(TRACE_WARNING, "[dst2src] Packet OOO [last: %u][act: %u]", tcp_seq_d2s.last, seq_num);
|
|
}
|
|
}
|
|
}
|
|
|
|
tcp_seq_d2s.next = next_seq_num;
|
|
if(update_last_seqnum) tcp_seq_d2s.last = seq_num;
|
|
}
|
|
|
|
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 && cli_ebpf && cli_ebpf->process_info_set)
|
|
return cli_ebpf->process_info.pid;
|
|
|
|
if(!client && srv_ebpf && srv_ebpf->process_info_set)
|
|
return srv_ebpf->process_info.pid;
|
|
|
|
return NO_PID;
|
|
};
|
|
|
|
/* *************************************** */
|
|
|
|
u_int32_t Flow::getFatherPid(bool client) {
|
|
if(client && cli_ebpf && cli_ebpf->process_info_set)
|
|
return cli_ebpf->process_info.father_pid;
|
|
|
|
if(!client && srv_ebpf && srv_ebpf->process_info_set)
|
|
return srv_ebpf->process_info.father_pid;
|
|
|
|
return NO_PID;
|
|
};
|
|
|
|
/* *************************************** */
|
|
|
|
u_int32_t Flow::get_uid(bool client) const {
|
|
#ifdef WIN32
|
|
return NO_UID;
|
|
#else
|
|
if(client && cli_ebpf && cli_ebpf->process_info_set)
|
|
return cli_ebpf->process_info.uid;
|
|
|
|
if(!client && srv_ebpf && srv_ebpf->process_info_set)
|
|
return srv_ebpf->process_info.uid;
|
|
|
|
return NO_UID;
|
|
#endif
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
char* Flow::get_proc_name(bool client) {
|
|
if(client && cli_ebpf && cli_ebpf->process_info_set)
|
|
return cli_ebpf->process_info.process_name;
|
|
|
|
if(!client && srv_ebpf && srv_ebpf->process_info_set)
|
|
return srv_ebpf->process_info.process_name;
|
|
|
|
return NULL;
|
|
};
|
|
|
|
/* *************************************** */
|
|
|
|
char* Flow::get_user_name(bool client) {
|
|
if(client && cli_ebpf && cli_ebpf->process_info_set)
|
|
return cli_ebpf->process_info.uid_name;
|
|
|
|
if(!client && srv_ebpf && srv_ebpf->process_info_set)
|
|
return srv_ebpf->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) {
|
|
int i, j, n = 0;
|
|
char bittorrent_hash[41];
|
|
|
|
for(i=0, j = 0; i<20; i++) {
|
|
u_char c = hash[i] & 0xFF;
|
|
sprintf(&bittorrent_hash[j], "%02x", 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 > 47) {
|
|
char *bt_proto = ndpi_strnstr((const char *)&payload[20],
|
|
"BitTorrent protocol", payload_len-20);
|
|
|
|
if(bt_proto)
|
|
setBittorrentHash(&bt_proto[27]);
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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::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(!isThreeWayHandshakeOK())
|
|
; /* Useless to compute http stats as client and server could be swapped */
|
|
else if(src2dst_direction) {
|
|
char *space;
|
|
dissect_next_http_packet = true;
|
|
|
|
/* 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;
|
|
|
|
if(protos.http.last_method) free(protos.http.last_method);
|
|
if((protos.http.last_method = (char*)malloc(l + 1)) != NULL) {
|
|
strncpy(protos.http.last_method, payload, l);
|
|
protos.http.last_method[l] = '\0';
|
|
}
|
|
|
|
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) free(protos.http.last_url);
|
|
if((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) {
|
|
/* TODO: move into nDPI */
|
|
end[0] = '\0';
|
|
ua++;
|
|
|
|
if(strstr(ua, "iPad") || strstr(ua, "iPod") || strstr(ua, "iPhone"))
|
|
operating_system = os_ios;
|
|
else if(strstr(ua, "Android"))
|
|
operating_system = os_android;
|
|
else if(strstr(ua, "Airport"))
|
|
operating_system = os_apple_airport;
|
|
else if(strstr(ua, "Macintosh") || strstr(ua, "OS X"))
|
|
operating_system = os_macos;
|
|
else if(strstr(ua, "Windows"))
|
|
operating_system = os_windows;
|
|
else if(strcasestr(ua, "Linux") || strstr(ua, "Debian") || strstr(ua, "Ubuntu"))
|
|
operating_system = os_linux;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} 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 = false;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Detect content type in response header
|
|
char buf[sizeof(HTTP_CONTENT_TYPE_HEADER) + HTTP_MAX_CONTENT_TYPE_LENGTH];
|
|
const char * s = payload;
|
|
size_t len = payload_len;
|
|
|
|
for (int i=0; i<HTTP_MAX_HEADER_LINES && len > 2; i++) {
|
|
const char * newline = (const char *) memchr(s, '\n', len);
|
|
|
|
if((!newline) || (newline - s < 2) || (*(newline - 1) != '\r')) break;
|
|
|
|
size_t linesize = newline - s + 1;
|
|
const char * terminator = (const char *) memchr(s, ';', linesize);
|
|
size_t effsize = terminator ? (terminator - s) : (linesize - 2);
|
|
|
|
if(effsize < sizeof(buf)) {
|
|
strncpy(buf, s, effsize);
|
|
buf[effsize] = '\0';
|
|
|
|
if(strstr(buf, HTTP_CONTENT_TYPE_HEADER) == buf) {
|
|
const char * ct = buf + sizeof(HTTP_CONTENT_TYPE_HEADER) - 1;
|
|
|
|
if(protos.http.last_content_type) free(protos.http.last_content_type);
|
|
protos.http.last_content_type = strdup(ct);
|
|
// ntop->getTrace()->traceEvent(TRACE_NORMAL, "LAST CONTENT TYPE: '%s'", protos.http.last_content_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
len -= linesize;
|
|
s = newline + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
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;
|
|
}
|
|
|
|
memcpy(&rsp, &payload[i], sizeof(rsp));
|
|
data_len = ntohs(rsp.data_len), rsp_type = ntohs(rsp.rsp_type);
|
|
|
|
/* Skip lenght for strings >= 32 with head length */
|
|
name = &_name[((data_len <= 32) || (_name[0] >= '0'))? 0 : 1];
|
|
|
|
#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();
|
|
|
|
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)) {
|
|
char *txt = (char*)&payload[i+sizeof(rsp)], txt_buf[256];
|
|
u_int16_t off = 0;
|
|
|
|
while(off < data_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;
|
|
|
|
strncpy(txt_buf, &txt[off], txt_len);
|
|
txt_buf[txt_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[2] & 0x80) /* NetBIOS Response */ || ((payload[2] & 0x78) == 0x28 /* NetBIOS Registration */))
|
|
&& (payload_len >= 12)
|
|
&& (ndpi_netbios_name_interpret((char*)&payload[12], payload_len - 12, 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);
|
|
}
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
#ifdef HAVE_NEDGE
|
|
|
|
bool Flow::isPassVerdict() const {
|
|
if(!passVerdict)
|
|
return(false);
|
|
|
|
if(cli_host && srv_host)
|
|
return((!quota_exceeded)
|
|
&& (!(cli_host->dropAllTraffic() || srv_host->dropAllTraffic()))
|
|
&& (!isBlacklistedFlow()));
|
|
else
|
|
return(true);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
bool Flow::checkPassVerdict(const struct tm *now) {
|
|
if(!passVerdict)
|
|
return(false);
|
|
|
|
if(!isDetectionCompleted())
|
|
return(true); /* Always pass until detection is completed */
|
|
|
|
recheckQuota(now);
|
|
return isPassVerdict();
|
|
}
|
|
|
|
#endif
|
|
|
|
/* *************************************** */
|
|
|
|
#ifdef HAVE_NEDGE
|
|
|
|
bool Flow::updateDirectionShapers(bool src2dst_direction, TrafficShaper **ingress_shaper, TrafficShaper **egress_shaper) {
|
|
bool verdict = true;
|
|
|
|
if(cli_host && srv_host) {
|
|
if(src2dst_direction) {
|
|
*ingress_shaper = srv_host->get_ingress_shaper(ndpiDetectedProtocol),
|
|
*egress_shaper = cli_host->get_egress_shaper(ndpiDetectedProtocol);
|
|
|
|
if(*ingress_shaper) srv2cli_in = (*ingress_shaper)->get_shaper_id();
|
|
if(*egress_shaper) cli2srv_out = (*egress_shaper)->get_shaper_id();
|
|
|
|
} else {
|
|
*ingress_shaper = cli_host->get_ingress_shaper(ndpiDetectedProtocol),
|
|
*egress_shaper = srv_host->get_egress_shaper(ndpiDetectedProtocol);
|
|
|
|
if(*ingress_shaper) cli2srv_in = (*ingress_shaper)->get_shaper_id();
|
|
if(*egress_shaper) srv2cli_out = (*egress_shaper)->get_shaper_id();
|
|
}
|
|
|
|
if((*ingress_shaper && (*ingress_shaper)->shaping_enabled() && (*ingress_shaper)->get_max_rate_kbit_sec() == 0)
|
|
|| (*egress_shaper && (*egress_shaper)->shaping_enabled() && (*egress_shaper)->get_max_rate_kbit_sec() == 0))
|
|
verdict = false;
|
|
} else
|
|
*ingress_shaper = *egress_shaper = NULL;
|
|
|
|
return verdict;
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::updateFlowShapers(bool first_update) {
|
|
bool cli2srv_verdict, srv2cli_verdict;
|
|
bool old_verdict = passVerdict;
|
|
bool new_verdict;
|
|
u_int16_t old_cli2srv_in = cli2srv_in,
|
|
old_cli2srv_out = cli2srv_out,
|
|
old_srv2cli_in = srv2cli_in,
|
|
old_srv2cli_out = srv2cli_out;
|
|
|
|
/* Re-compute the verdict */
|
|
cli2srv_verdict = updateDirectionShapers(true, &flowShaperIds.cli2srv.ingress, &flowShaperIds.cli2srv.egress);
|
|
srv2cli_verdict = updateDirectionShapers(false, &flowShaperIds.srv2cli.ingress, &flowShaperIds.srv2cli.egress);
|
|
new_verdict = (cli2srv_verdict && srv2cli_verdict);
|
|
|
|
if(ntop->getPrefs()->are_device_protocol_policies_enabled() && cli_host && srv_host && new_verdict) {
|
|
/* NOTE: this must be handled differently to only consider actual peers direction */
|
|
if((cli_host->getDeviceAllowedProtocolStatus(ndpiDetectedProtocol, true /* client */) != device_proto_allowed) ||
|
|
(srv_host->getDeviceAllowedProtocolStatus(ndpiDetectedProtocol, false /* server */) != device_proto_allowed))
|
|
new_verdict = false;
|
|
}
|
|
|
|
/* Set the new verdict */
|
|
passVerdict = new_verdict;
|
|
|
|
if((!first_update) && (iface->getIfType() == interface_type_NETFILTER) &&
|
|
(((old_verdict != passVerdict)) ||
|
|
(old_cli2srv_in != cli2srv_in) ||
|
|
(old_cli2srv_out != cli2srv_out) ||
|
|
(old_srv2cli_in != srv2cli_in) ||
|
|
(old_srv2cli_out != srv2cli_out)))
|
|
((NetfilterInterface *) iface)->setPolicyChanged();
|
|
|
|
#ifdef SHAPER_DEBUG
|
|
{
|
|
char buf[1024];
|
|
|
|
ntop->getTrace()->traceEvent(TRACE_NORMAL, "[SHAPERS] %s", print(buf, sizeof(buf)));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::recheckQuota(const struct tm *now) {
|
|
bool above_quota = false;
|
|
|
|
if(cli_host && srv_host) {
|
|
L7PolicySource_t cli_src, srv_src;
|
|
|
|
if((above_quota = cli_host->checkQuota(ndpiDetectedProtocol, &cli_src, now)))
|
|
srv_src = policy_source_default;
|
|
else if((above_quota = srv_host->checkQuota(ndpiDetectedProtocol, &srv_src, now)))
|
|
;
|
|
|
|
/* Use temporary values to guard against partial changes */
|
|
cli_quota_source = cli_src, srv_quota_source = srv_src;
|
|
}
|
|
|
|
quota_exceeded = above_quota;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* ***************************************************** */
|
|
|
|
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);
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
#ifdef HAVE_NEDGE
|
|
void Flow::setPacketsBytes(time_t now, u_int32_t s2d_pkts, u_int32_t d2s_pkts,
|
|
u_int64_t s2d_bytes, u_int64_t d2s_bytes) {
|
|
u_int16_t eth_proto = ETHERTYPE_IP;
|
|
u_int overhead = 0;
|
|
bool nf_existing_flow;
|
|
|
|
/* netfilter (depending on configured timeouts) could expire a flow before than
|
|
ntopng. This heuristics attempt to detect such events.
|
|
|
|
Basically, if netfilter is sending counters for a new flow and ntopng
|
|
already have an existing flow matching the same 5-tuple, we sum counters
|
|
rather than overwriting them.
|
|
|
|
A complete solution would require the registration of a netfilter callback
|
|
and the detection of event NFCT_T_DESTROY.
|
|
*/
|
|
nf_existing_flow = !(get_packets_cli2srv() > s2d_pkts || get_bytes_cli2srv() > s2d_bytes
|
|
|| get_packets_srv2cli() > d2s_pkts || get_bytes_srv2cli() > d2s_bytes);
|
|
|
|
updateSeen();
|
|
|
|
/*
|
|
We need to set last_conntrack_update even with 0 packtes/bytes
|
|
as this function has been called only within netfilter through
|
|
the conntrack handler, and thus the flow is still alive.
|
|
*/
|
|
last_conntrack_update = now;
|
|
|
|
iface->_incStats(isIngress2EgressDirection(), now, eth_proto,
|
|
getStatsProtocol(), get_protocol_category(),
|
|
protocol,
|
|
nf_existing_flow ? s2d_bytes - get_bytes_cli2srv() : s2d_bytes,
|
|
nf_existing_flow ? s2d_pkts - get_packets_cli2srv() : s2d_pkts,
|
|
overhead);
|
|
|
|
iface->_incStats(!isIngress2EgressDirection(), now, eth_proto,
|
|
getStatsProtocol(), get_protocol_category(),
|
|
protocol,
|
|
nf_existing_flow ? d2s_bytes - get_bytes_srv2cli() : d2s_bytes,
|
|
nf_existing_flow ? d2s_pkts - get_packets_srv2cli() : d2s_pkts,
|
|
overhead);
|
|
|
|
if(nf_existing_flow) {
|
|
stats.setStats(true, s2d_pkts, s2d_bytes, 0);
|
|
stats.setStats(false, d2s_pkts, d2s_bytes, 0);
|
|
} else {
|
|
stats.incStats(true, s2d_pkts, s2d_bytes, 0);
|
|
stats.incStats(false, d2s_pkts, d2s_bytes, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* ***************************************************** */
|
|
|
|
void Flow::setParsedeBPFInfo(const ParsedeBPF * const ebpf, bool src2dst_direction) {
|
|
bool client_process = true;
|
|
ParsedeBPF *cur = NULL;
|
|
bool update_ok = true;
|
|
|
|
if(!ebpf)
|
|
return;
|
|
|
|
if(!iface->hasSeenEBPFEvents())
|
|
iface->setSeenEBPFEvents();
|
|
|
|
if(ebpf->isServerInfo())
|
|
client_process = false;
|
|
|
|
if(!src2dst_direction)
|
|
client_process = !client_process;
|
|
|
|
if(client_process) {
|
|
if(!cli_ebpf)
|
|
cur = cli_ebpf = new (std::nothrow) ParsedeBPF(*ebpf);
|
|
else
|
|
update_ok = cli_ebpf->update(ebpf);
|
|
} else { /* server_process */
|
|
if(!srv_ebpf)
|
|
cur = srv_ebpf = new (std::nothrow) ParsedeBPF(*ebpf);
|
|
else
|
|
update_ok = srv_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(cur->container_info.data_type == container_info_data_type_k8s
|
|
&& !iface->hasSeenPods()
|
|
&& cur->container_info.data.k8s.pod)
|
|
iface->setSeenPods();
|
|
}
|
|
|
|
updateCliJA3();
|
|
updateSrvJA3();
|
|
updateHASSH(true /* AS client */);
|
|
updateHASSH(false /* AS server */);
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
void Flow::updateCliJA3() {
|
|
if(cli_host && isTLS() && protos.tls.ja3.client_hash) {
|
|
cli_host->getJA3Fingerprint()->update(protos.tls.ja3.client_hash,
|
|
cli_ebpf ? cli_ebpf->process_info.process_name : NULL);
|
|
|
|
has_malicious_cli_signature |= ntop->isMaliciousJA3Hash(protos.tls.ja3.client_hash);
|
|
}
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
void Flow::updateSrvJA3() {
|
|
if(srv_host && isTLS() && protos.tls.ja3.server_hash) {
|
|
srv_host->getJA3Fingerprint()->update(protos.tls.ja3.server_hash,
|
|
srv_ebpf ? srv_ebpf->process_info.process_name : NULL);
|
|
|
|
has_malicious_srv_signature |= ntop->isMaliciousJA3Hash(protos.tls.ja3.server_hash);
|
|
}
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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;
|
|
ParsedeBPF *pebpf = as_client ? cli_ebpf : srv_ebpf;
|
|
Fingerprint *fp;
|
|
|
|
if(h && hassh && hassh[0] != '\0' && (fp = h->getHASSHFingerprint()))
|
|
fp->update(hassh, pebpf ? pebpf->process_info.process_name : NULL);
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
/* Called when a flow is set_idle */
|
|
void Flow::postFlowSetIdle(const struct timeval *tv) {
|
|
Host *cli_h = get_cli_host(), *srv_h = get_srv_host();
|
|
|
|
if(status_map.get() != status_normal) {
|
|
#if 0
|
|
char buf[256];
|
|
printf("%s status=%d\n", print(buf, sizeof(buf)), status);
|
|
#endif
|
|
|
|
if(cli_h) {
|
|
cli_h->setMisbehavingFlowsStatusMap(status_map, true);
|
|
cli_h->incNumMisbehavingFlows(true);
|
|
cli_h->getScore()->incValue(cli_score);
|
|
}
|
|
|
|
if(srv_h) {
|
|
srv_h->setMisbehavingFlowsStatusMap(status_map, false);
|
|
srv_h->incNumMisbehavingFlows(false);
|
|
srv_h->getScore()->incValue(srv_score);
|
|
}
|
|
|
|
iface->decNumMisbehavingFlows();
|
|
}
|
|
|
|
if(isFlowAlerted()) {
|
|
if(cli_h) cli_h->decNumAlertedFlows();
|
|
if(srv_h) srv_h->decNumAlertedFlows();
|
|
}
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
void Flow::fillZmqFlowCategory(const ParsedFlow *zflow, ndpi_protocol *res) const {
|
|
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(cli_ip && srv_ip && cli_ip->isIPv4()) {
|
|
if(ndpi_fill_ip_protocol_category(ndpi_struct, cli_ip->get_ipv4(), srv_ip->get_ipv4(), res))
|
|
return;
|
|
}
|
|
|
|
switch(ndpi_get_lower_proto(*res)) {
|
|
case NDPI_PROTOCOL_DNS:
|
|
dst_name = zflow->dns_query;
|
|
break;
|
|
case NDPI_PROTOCOL_HTTP_PROXY:
|
|
case NDPI_PROTOCOL_HTTP:
|
|
dst_name = zflow->http_site;
|
|
break;
|
|
case NDPI_PROTOCOL_TLS:
|
|
dst_name = zflow->tls_server_name;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if(dst_name) {
|
|
int rc;
|
|
ndpi_protocol_match_result tmp;
|
|
ndpi_protocol_category_t c;
|
|
|
|
/* Match for custom protocols (protos.txt) */
|
|
if((rc = ndpi_match_string_subprotocol(ndpi_struct, (char*)dst_name, strlen(dst_name), &tmp, 1 /* host match */)) != 0) {
|
|
if(rc >= NDPI_MAX_SUPPORTED_PROTOCOLS) {
|
|
/* If the protocol is greater than NDPI_MAX_SUPPORTED_PROTOCOLS, it means it is
|
|
a custom protocol so the application protocol received from nprobe can be
|
|
overridden */
|
|
if(res->master_protocol == NDPI_PROTOCOL_UNKNOWN)
|
|
res->master_protocol = res->app_protocol;
|
|
|
|
res->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) == 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", getPredominantStatus());
|
|
lua_push_uint64_table_entry(vm, "status_map", status_map.get());
|
|
|
|
statusInfosLua(vm);
|
|
lua_pushstring(vm, "status_infos");
|
|
lua_insert(vm, -2);
|
|
lua_settable(vm, -3);
|
|
|
|
if(isFlowAlerted()) {
|
|
lua_push_bool_table_entry(vm, "flow.alerted", true);
|
|
lua_push_uint64_table_entry(vm, "alerted_status", alerted_status);
|
|
}
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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.app_protocol != NDPI_PROTOCOL_UNKNOWN)
|
|
|| iface->is_ndpi_enabled()
|
|
|| 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)));
|
|
} else
|
|
lua_push_str_table_entry(vm, "proto.ndpi", (char*)CONST_TOO_EARLY);
|
|
|
|
lua_push_uint64_table_entry(vm, "proto.ndpi_id", ndpiDetectedProtocol.app_protocol);
|
|
lua_push_uint64_table_entry(vm, "proto.master_ndpi_id", ndpiDetectedProtocol.master_protocol);
|
|
lua_push_str_table_entry(vm, "proto.ndpi_breed", get_protocol_breed_name());
|
|
|
|
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());
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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, "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 {
|
|
// overall throughput stats
|
|
lua_push_float_table_entry(vm, "top_throughput_bps", top_bytes_thpt);
|
|
lua_push_float_table_entry(vm, "throughput_bps", 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", pkts_thpt);
|
|
lua_push_uint64_table_entry(vm, "throughput_trend_pps", pkts_thpt_trend);
|
|
|
|
// throughput stats cli2srv and srv2cli breakdown
|
|
lua_push_float_table_entry(vm, "throughput_cli2srv_bps", bytes_thpt_cli2srv);
|
|
lua_push_float_table_entry(vm, "throughput_srv2cli_bps", bytes_thpt_srv2cli);
|
|
lua_push_float_table_entry(vm, "throughput_cli2srv_pps", pkts_thpt_cli2srv);
|
|
lua_push_float_table_entry(vm, "throughput_srv2cli_pps", pkts_thpt_srv2cli);
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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());
|
|
} 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_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_str_table_entry(vm, client ? "cli.mac" : "srv.mac", Utils::formatMac(h->get_mac(), buf, sizeof(buf)));
|
|
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_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)));
|
|
}
|
|
}
|
|
|
|
if(h_ip)
|
|
lua_push_bool_table_entry(vm, client ? "cli.private" : "srv.private", h_ip->isPrivateAddress());
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
/* 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) {
|
|
const IpAddress *cli_ip = get_cli_ip_addr();
|
|
const IpAddress *srv_ip = get_srv_ip_addr();
|
|
char buf[32];
|
|
|
|
lua_newtable(vm);
|
|
|
|
if(cli_ip) lua_push_str_table_entry(vm, "cli.ip", get_cli_ip_addr()->print(buf, sizeof(buf)));
|
|
if(srv_ip) lua_push_str_table_entry(vm, "srv.ip", get_srv_ip_addr()->print(buf, sizeof(buf)));
|
|
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", cli_host ? cli_host->isLocalHost() : false);
|
|
lua_push_bool_table_entry(vm, "srv.localhost", srv_host ? srv_host->isLocalHost() : false);
|
|
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.app_protocol));
|
|
lua_push_str_table_entry(vm, "proto.ndpi_cat", get_protocol_category_name());
|
|
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());
|
|
if(getFlowInfo()) lua_push_str_table_entry(vm, "info", getFlowInfo());
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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());
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
void Flow::lua_get_tcp_stats(lua_State *vm) const {
|
|
lua_newtable(vm);
|
|
|
|
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, "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());
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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_first_seen());
|
|
lua_push_bool_table_entry(vm, "twh_over", twh_over);
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
void Flow::lua_device_protocol_allowed_info(lua_State *vm) {
|
|
DeviceProtoStatus cli_ps, srv_ps;
|
|
bool cli_allowed, srv_allowed;
|
|
|
|
lua_newtable(vm);
|
|
|
|
if(!cli_host || !srv_host)
|
|
return;
|
|
|
|
cli_ps = cli_host->getDeviceAllowedProtocolStatus(get_detected_protocol(), true);
|
|
srv_ps = srv_host->getDeviceAllowedProtocolStatus(get_detected_protocol(), false);
|
|
cli_allowed = (cli_ps == device_proto_allowed);
|
|
srv_allowed = (srv_ps == device_proto_allowed);
|
|
|
|
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", (cli_ps == device_proto_forbidden_app) ? ndpiDetectedProtocol.app_protocol : ndpiDetectedProtocol.master_protocol);
|
|
|
|
lua_push_bool_table_entry(vm, "srv.allowed", srv_allowed);
|
|
if(!srv_allowed)
|
|
lua_push_int32_table_entry(vm, "srv.disallowed_proto", (srv_ps == device_proto_forbidden_app) ? ndpiDetectedProtocol.app_protocol : ndpiDetectedProtocol.master_protocol);
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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_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_int32_table_entry(vm, "protos.tls.notBefore", protos.tls.notBefore);
|
|
lua_push_int32_table_entry(vm, "protos.tls.notAfter", protos.tls.notAfter);
|
|
}
|
|
|
|
if(protos.tls.ja3.client_hash) {
|
|
lua_push_str_table_entry(vm, "protos.tls.ja3.client_hash", protos.tls.ja3.client_hash);
|
|
|
|
if(has_malicious_cli_signature)
|
|
lua_push_bool_table_entry(vm, "protos.tls.ja3.client_malicious", true);
|
|
}
|
|
|
|
if(protos.tls.ja3.server_hash) {
|
|
lua_push_str_table_entry(vm, "protos.tls.ja3.server_hash", protos.tls.ja3.server_hash);
|
|
lua_push_str_table_entry(vm, "protos.tls.ja3.server_unsafe_cipher",
|
|
cipher_weakness2str(protos.tls.ja3.server_unsafe_cipher));
|
|
lua_push_int32_table_entry(vm, "protos.tls.ja3.server_cipher",
|
|
protos.tls.ja3.server_cipher);
|
|
|
|
if(has_malicious_srv_signature)
|
|
lua_push_bool_table_entry(vm, "protos.tls.ja3.server_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::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", 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(host_server_name)
|
|
lua_push_str_table_entry(vm, "protos.http.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.invalid_chars_in_query)
|
|
lua_push_bool_table_entry(vm, "protos.dns.invalid_chars_in_query", protos.dns.invalid_chars_in_query);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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);
|
|
|
|
lua_push_float_table_entry(vm, "tcp.nw_latency.client", toMs(&clientNwLatency));
|
|
lua_push_float_table_entry(vm, "tcp.nw_latency.server", toMs(&serverNwLatency));
|
|
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)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
FlowLuaCallExecStatus Flow::performLuaCall(FlowLuaCall flow_lua_call, const struct timeval *tv,
|
|
periodic_ht_state_update_user_data_t *periodic_ht_state_update_user_data) {
|
|
const char *lua_call_fn_name = NULL;
|
|
FlowAlertCheckLuaEngine *acle = getLuaVMUservalue(periodic_ht_state_update_user_data->vm, flow_acle);
|
|
|
|
if(flow_lua_call != flow_lua_call_idle
|
|
&& ntop->getGlobals()->isShutdownRequested())
|
|
return flow_lua_call_exec_status_not_executed_shutdown_in_progress; /* Only flow_lua_call_idle go through during a shutdown */
|
|
|
|
if(!acle)
|
|
return flow_lua_call_exec_status_not_executed_vm_not_allocated;
|
|
|
|
lua_State *L = acle->getState();
|
|
acle->setFlow(this);
|
|
|
|
switch(flow_lua_call) {
|
|
case flow_lua_call_protocol_detected:
|
|
lua_call_fn_name = FLOW_LUA_CALL_PROTOCOL_DETECTED_FN_NAME;
|
|
break;
|
|
case flow_lua_call_periodic_update:
|
|
lua_call_fn_name = FLOW_LUA_CALL_PERIODIC_UPDATE_FN_NAME;
|
|
break;
|
|
case flow_lua_call_idle:
|
|
lua_call_fn_name = FLOW_LUA_CALL_IDLE_FN_NAME;
|
|
break;
|
|
default:
|
|
return flow_lua_call_exec_status_not_executed_unknown_call;
|
|
}
|
|
|
|
int num_args = 3;
|
|
|
|
/* Call the function */
|
|
lua_getglobal(L, lua_call_fn_name); /* Called function */
|
|
lua_pushinteger(L, protocol);
|
|
lua_pushinteger(L, ndpiDetectedProtocol.master_protocol);
|
|
lua_pushinteger(L, ndpiDetectedProtocol.app_protocol);
|
|
|
|
if(flow_lua_call == flow_lua_call_periodic_update) {
|
|
/* pass the periodic_update counter as the second argument,
|
|
* needed to determine which periodic scripts to call. */
|
|
lua_pushinteger(L, periodic_update_ctr++);
|
|
num_args++;
|
|
}
|
|
|
|
if(acle->pcall(num_args,
|
|
1 /* 1 result, true if the call has been executed
|
|
or false if there was not time left,
|
|
depending on the deadline passed as argument */)) {
|
|
bool executed = lua_toboolean(acle->getState(), -1);
|
|
lua_pop(acle->getState(), -1);
|
|
|
|
if(executed) {
|
|
acle->incSuccessfulPcalls(flow_lua_call);
|
|
return flow_lua_call_exec_status_ok;
|
|
} else {
|
|
switch(get_state()) {
|
|
case hash_entry_state_idle:
|
|
acle->incSkippedPcalls(flow_lua_call);
|
|
break;
|
|
default:
|
|
acle->incPendingPcalls(flow_lua_call);
|
|
break;
|
|
}
|
|
return flow_lua_call_exec_status_not_executed_no_time_left;
|
|
}
|
|
} else
|
|
return flow_lua_call_exec_status_not_executed_script_failure;
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
void Flow::performLuaCalls(const struct timeval *tv, periodic_ht_state_update_user_data_t *periodic_ht_state_update_user_data) {
|
|
HashEntryState cur_state = get_state();
|
|
FlowLuaCallExecStatus protocol_detected_exec_status = flow_lua_call_exec_status_ok,
|
|
periodic_update_exec_status = flow_lua_call_exec_status_ok,
|
|
idle_exec_status = flow_lua_call_exec_status_ok;
|
|
|
|
/* Check if it is time to call the protocol detected */
|
|
if(pending_lua_call_protocol_detected
|
|
&& (protocol_detected_exec_status = performLuaCall(flow_lua_call_protocol_detected, tv, periodic_ht_state_update_user_data)) == flow_lua_call_exec_status_ok)
|
|
pending_lua_call_protocol_detected = false;
|
|
|
|
/* Check if it is time to call the periodic update. Need to make sure this is not called BEFORE
|
|
the protocol detected has been called */
|
|
if(cur_state >= hash_entry_state_active /* This check guarantees no periodic update is called before protocol detected */
|
|
&& (trigger_immediate_periodic_update ||
|
|
(next_lua_call_periodic_update > 0 /* 0 means not-yet-set */
|
|
&& next_lua_call_periodic_update <= tv->tv_sec))
|
|
&& (periodic_update_exec_status = performLuaCall(flow_lua_call_periodic_update, tv, periodic_ht_state_update_user_data)) == flow_lua_call_exec_status_ok) {
|
|
next_lua_call_periodic_update = tv->tv_sec + FLOW_LUA_CALL_PERIODIC_UPDATE_SECS; /* Set the time of the new periodic call */
|
|
if(trigger_immediate_periodic_update) trigger_immediate_periodic_update = false; /* Reset if necessary */
|
|
}
|
|
|
|
/* Check if it is time to call the idle. There is no need to change any boolean here to remember
|
|
the call has been performed: each flow in idle state is visited exactly once. */
|
|
if(cur_state == hash_entry_state_idle)
|
|
idle_exec_status = performLuaCall(flow_lua_call_idle, tv, periodic_ht_state_update_user_data);
|
|
|
|
/* If a lua call hasn't been executed due to no time left (deadline approaching)
|
|
then we set this information also into the user data. */
|
|
if((protocol_detected_exec_status == flow_lua_call_exec_status_not_executed_no_time_left
|
|
|| periodic_update_exec_status == flow_lua_call_exec_status_not_executed_no_time_left
|
|
|| idle_exec_status == flow_lua_call_exec_status_not_executed_no_time_left)
|
|
&& !periodic_ht_state_update_user_data->no_time_left) {
|
|
periodic_ht_state_update_user_data->no_time_left = true;
|
|
}
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
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);
|
|
}
|
|
|
|
/* ***************************************************** */
|
|
|
|
bool Flow::triggerAlert(FlowStatus status, AlertType atype, AlertLevel severity, const char *alert_json) {
|
|
bool first_alert = !isFlowAlerted();
|
|
bool cli_thresh = false, srv_thresh = false;
|
|
Host *cli_h = get_cli_host(), *srv_h = get_srv_host();
|
|
time_t when = time(NULL);
|
|
|
|
/* Note: triggerAlert is called by flow.lua only after all the flow
|
|
* status are processed (once every 5 seconds), so it is safe to use the shadow */
|
|
if(alert_status_info_shadow)
|
|
free(alert_status_info_shadow);
|
|
alert_status_info_shadow = alert_status_info;
|
|
|
|
alert_status_info = alert_json ? strdup(alert_json) : NULL;
|
|
alerted_status = status;
|
|
alert_level = severity;
|
|
alert_type = atype;
|
|
|
|
if(cli_h) cli_thresh = cli_h->incFlowAlertHits(when);
|
|
if(srv_h) srv_thresh = srv_h->incFlowAlertHits(when);
|
|
|
|
if(first_alert) {
|
|
/* This is the first alert for the flow, increment the counters */
|
|
if(!idle()) {
|
|
/* If idle() and not alerted, the interface
|
|
* counter for active alerted flows is not incremented as
|
|
* it means the purgeIdle() has traversed this flow and marked
|
|
* it as state_idle before it was alerted */
|
|
iface->incNumAlertedFlows(this);
|
|
#ifdef ALERTED_FLOWS_DEBUG
|
|
iface_alert_inc = true;
|
|
#endif
|
|
}
|
|
|
|
if(cli_h) {
|
|
cli_h->incNumAlertedFlows();
|
|
cli_h->incTotalAlerts(alert_type);
|
|
}
|
|
|
|
if(srv_h) {
|
|
srv_h->incNumAlertedFlows();
|
|
srv_h->incTotalAlerts(alert_type);
|
|
}
|
|
}
|
|
|
|
/* Success - alert is dumped/notified from lua */
|
|
return (cli_thresh || srv_thresh) && !getInterface()->read_from_pcap_dump() ? false : true;
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
#ifdef NTOPNG_PRO
|
|
static void incPeerScorePcap(Host *h, u_int16_t score_inc) {
|
|
u_int32_t old_score = h->getScore()->getValue();
|
|
u_int32_t new_score = old_score + score_inc;
|
|
|
|
if(new_score >= (u_int16_t)-1)
|
|
new_score = (u_int16_t)-1;
|
|
|
|
h->getScore()->incValue(new_score);
|
|
h->getScore()->refreshValue();
|
|
}
|
|
#endif
|
|
|
|
/* *************************************** */
|
|
|
|
bool Flow::setStatus(FlowStatus status, u_int16_t flow_inc, u_int16_t cli_inc,
|
|
u_int16_t srv_inc, const char*script_key) {
|
|
if(status_map.get() == status_normal) {
|
|
/* First misbehaving status */
|
|
iface->incNumMisbehavingFlows();
|
|
}
|
|
|
|
if(!status_map.issetBit(status)) {
|
|
status_map.setBit(status);
|
|
|
|
#ifdef NTOPNG_PRO
|
|
if(ntop->getPrefs()->is_enterprise_m_edition()) {
|
|
flow_score += flow_inc;
|
|
cli_score += cli_inc;
|
|
srv_score += srv_inc;
|
|
|
|
if(!status_infos)
|
|
status_infos = (StatusInfo*) calloc(BITMAP_NUM_BITS, sizeof(StatusInfo));
|
|
|
|
if(status_infos && (status < BITMAP_NUM_BITS)) {
|
|
if(!status_infos[status].script_key)
|
|
status_infos[status].script_key = strdup(script_key);
|
|
|
|
status_infos[status].score = flow_inc;
|
|
}
|
|
|
|
if(iface->read_from_pcap_dump() && !iface->reproducePcapOriginalSpeed()) {
|
|
/* Periodic scripts (e.g. minute.lua) are not executed while reading a
|
|
* PCAP file. Increment the peers score here. */
|
|
if(cli_host) incPeerScorePcap(cli_host, cli_inc);
|
|
if(srv_host) incPeerScorePcap(srv_host, srv_inc);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return(true);
|
|
}
|
|
|
|
return(false);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::clearStatus(FlowStatus status) {
|
|
status_map.clearBit(status);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::setExternalAlert(json_object *a) {
|
|
if(!external_alert) {
|
|
/* In order to avoid concurrency issues with the getter, at most
|
|
* 1 pending external alert is supported. */
|
|
external_alert = strdup(json_object_to_json_string(a));
|
|
|
|
/* Manually trigger a periodic update to process the alert */
|
|
trigger_immediate_periodic_update = true;
|
|
}
|
|
|
|
json_object_put(a);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::luaRetrieveExternalAlert(lua_State *vm) {
|
|
if(external_alert) {
|
|
lua_pushstring(vm, external_alert);
|
|
|
|
/* Must delete the data to avoid returning it in the next call */
|
|
free(external_alert);
|
|
external_alert = NULL;
|
|
} else
|
|
lua_pushnil(vm);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
FlowStatus Flow::getPredominantStatus() const {
|
|
int i;
|
|
u_int16_t max_score = 0;
|
|
FlowStatus status = status_normal;
|
|
|
|
if(status_infos) {
|
|
for(i=1 /* 0 is normal */; i<BITMAP_NUM_BITS; i++) {
|
|
if(status_map.issetBit(i)) {
|
|
if (status_infos[i].score > max_score) {
|
|
status = (FlowStatus)i;
|
|
max_score = status_infos[i].score;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return(status);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
u_int16_t Flow::getAlertedStatusScore() {
|
|
if(!status_infos)
|
|
return(0);
|
|
|
|
return(status_infos[alerted_status].score);
|
|
}
|
|
|
|
/* *************************************** */
|
|
|
|
void Flow::statusInfosLua(lua_State* vm) const {
|
|
int i;
|
|
|
|
lua_newtable(vm);
|
|
|
|
if(status_infos) {
|
|
for(i=0; i<BITMAP_NUM_BITS; i++) {
|
|
if(status_map.issetBit(i)) {
|
|
lua_newtable(vm);
|
|
|
|
if(status_infos[i].script_key)
|
|
lua_push_str_table_entry(vm, "user_script", status_infos[i].script_key);
|
|
|
|
lua_push_int32_table_entry(vm, "score", status_infos[i].score);
|
|
|
|
lua_pushinteger(vm, i);
|
|
lua_insert(vm, -2);
|
|
lua_settable(vm, -3);
|
|
}
|
|
}
|
|
}
|
|
}
|