mirror of
https://github.com/vel21ripn/nDPI.git
synced 2026-05-02 00:40:17 +00:00
The main goal of a DPI engine is usually to determine "what", i.e. which types of traffic flow on the network. However the applications using DPI are often interested also in "who", i.e. which "user/subscriber" generated that traffic. The association between a flow and a subscriber is usually done via some kind of DHCP/GTP/RADIUS/NAT mappings. In all these cases the key element of the flow used to identify the user is the source ip address. That usually happens for the vast majority of the traffic. However, depending on the protocols involved and on the position on the net where the traffic is captured, the source ip address might have been changed/anonymized. In that case, that address is useless for any flow-username association. Example: iCloud Private Relay traffic captured between the exit relay and the server. See the picture at page 5 on: https://www.apple.com/privacy/docs/iCloud_Private_Relay_Overview_Dec2021.PDF This commit adds new generic flow risk `NDPI_ANONYMOUS_SUBSCRIBER` hinting that the ip addresses shouldn't be used to identify the user associated with the flow. As a first example of this new feature, the entire list of the relay ip addresses used by Private Relay is added. A key point to note is that list is NOT used for flow classification (unlike all the other ip lists present in nDPI) but only for setting this new flow risk. TODO: IPv6
1679 lines
56 KiB
Lua
1679 lines
56 KiB
Lua
--
|
|
-- (C) 2017-21 - ntop.org
|
|
--
|
|
-- This plugin is part of nDPI (https://github.com/ntop/nDPI)
|
|
--
|
|
-- 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.
|
|
--
|
|
|
|
function bit(p)
|
|
return 2 ^ p -- 0-based indexing
|
|
end
|
|
|
|
|
|
local ndpi_proto = Proto("ndpi", "nDPI Protocol Interpreter")
|
|
ndpi_proto.fields = {}
|
|
|
|
local ndpi_fds = ndpi_proto.fields
|
|
ndpi_fds.network_protocol = ProtoField.new("nDPI Network Protocol", "ndpi.protocol.network", ftypes.UINT8, nil, base.DEC)
|
|
ndpi_fds.application_protocol = ProtoField.new("nDPI Application Protocol", "ndpi.protocol.application", ftypes.UINT8, nil, base.DEC)
|
|
ndpi_fds.name = ProtoField.new("nDPI Protocol Name", "ndpi.protocol.name", ftypes.STRING)
|
|
ndpi_fds.flow_risk = ProtoField.new("nDPI Flow Risk", "ndpi.flow_risk", ftypes.UINT64, nil, base.HEX)
|
|
ndpi_fds.flow_score = ProtoField.new("nDPI Flow Score", "ndpi.flow_score", ftypes.UINT32)
|
|
|
|
|
|
local flow_risks = {}
|
|
-- Wireshark/Lua doesn't handle 64 bit integer very well, so we split the risk mask into two 32 bit integer values
|
|
local num_bits_flow_risks = 32
|
|
flow_risks[0] = ProtoField.bool("ndpi.flow_risk.unused0", "Reserved", num_bits_flow_risks, nil, bit(0), "nDPI Flow Risk: Reserved bit")
|
|
flow_risks[1] = ProtoField.bool("ndpi.flow_risk.xss_attack", "XSS attack", num_bits_flow_risks, nil, bit(1), "nDPI Flow Risk: XSS attack")
|
|
flow_risks[2] = ProtoField.bool("ndpi.flow_risk.sql_injection", "SQL injection", num_bits_flow_risks, nil, bit(2), "nDPI Flow Risk: SQL injection")
|
|
flow_risks[3] = ProtoField.bool("ndpi.flow_risk.rce_injection", "RCE injection", num_bits_flow_risks, nil, bit(3), "nDPI Flow Risk: RCE injection")
|
|
flow_risks[4] = ProtoField.bool("ndpi.flow_risk.binary_application_transfer", "Binary application transfer", num_bits_flow_risks, nil, bit(4), "nDPI Flow Risk: Binary application transfer")
|
|
flow_risks[5] = ProtoField.bool("ndpi.flow_risk.known_protocol_on_non_standard_port", "Known protocol on non standard port", num_bits_flow_risks, nil, bit(5), "nDPI Flow Risk: Known protocol on non standard port")
|
|
flow_risks[6] = ProtoField.bool("ndpi.flow_risk.self_signed_certificate", "Self-signed Certificate", num_bits_flow_risks, nil, bit(6), "nDPI Flow Risk: Self-signed Certificate")
|
|
flow_risks[7] = ProtoField.bool("ndpi.flow_risk.obsolete_tls_version", "Obsolete TLS version (< 1.1)", num_bits_flow_risks, nil, bit(7), "nDPI Flow Risk: Obsolete TLS version (< 1.1)")
|
|
flow_risks[8] = ProtoField.bool("ndpi.flow_risk.weak_tls_cipher", "Weak TLS cipher", num_bits_flow_risks, nil, bit(8), "nDPI Flow Risk: Weak TLS cipher")
|
|
flow_risks[9] = ProtoField.bool("ndpi.flow_risk.tls_expired_certificate", "TLS Expired Certificate", num_bits_flow_risks, nil, bit(9), "nDPI Flow Risk: TLS Expired Certificate")
|
|
flow_risks[10] = ProtoField.bool("ndpi.flow_risk.tls_certificate_mismatch", "TLS Certificate Mismatch", num_bits_flow_risks, nil, bit(10), "nDPI Flow Risk: TLS Certificate Mismatch")
|
|
flow_risks[11] = ProtoField.bool("ndpi.flow_risk.http_suspicious_user_agent", "HTTP Suspicious User-Agent", num_bits_flow_risks, nil, bit(11), "nDPI Flow Risk: HTTP Suspicious User-Agent")
|
|
flow_risks[12] = ProtoField.bool("ndpi.flow_risk.http_numeric_ip_address", "HTTP Numeric IP Address", num_bits_flow_risks, nil, bit(12), "nDPI Flow Risk: HTTP Numeric IP Address")
|
|
flow_risks[13] = ProtoField.bool("ndpi.flow_risk.http_suspicious_url", "HTTP Suspicious URL", num_bits_flow_risks, nil, bit(13), "nDPI Flow Risk: HTTP Suspicious URL")
|
|
flow_risks[14] = ProtoField.bool("ndpi.flow_risk.http_suspicious_header", "HTTP Suspicious Header", num_bits_flow_risks, nil, bit(14), "nDPI Flow Risk: HTTP Suspicious Header")
|
|
flow_risks[15] = ProtoField.bool("ndpi.flow_risk.tls_probably_not_https", "TLS (probably) not carrying HTTPS", num_bits_flow_risks, nil, bit(15), "nDPI Flow Risk: TLS (probably) not carrying HTTPS")
|
|
flow_risks[16] = ProtoField.bool("ndpi.flow_risk.suspicious_dga", "Suspicious DGA domain name", num_bits_flow_risks, nil, bit(16), "nDPI Flow Risk: Suspicious DGA domain name")
|
|
flow_risks[17] = ProtoField.bool("ndpi.flow_risk.malformed_packet", "Malformed packet", num_bits_flow_risks, nil, bit(17), "nDPI Flow Risk: Malformed packet")
|
|
flow_risks[18] = ProtoField.bool("ndpi.flow_risk.ssh_obsolete_client", "SSH Obsolete Client Version/Cipher", num_bits_flow_risks, nil, bit(18), "nDPI Flow Risk: SSH Obsolete Client Version/Cipher")
|
|
flow_risks[19] = ProtoField.bool("ndpi.flow_risk.ssh_obsolete_server", "SSH Obsolete Server Version/Cipher", num_bits_flow_risks, nil, bit(19), "nDPI Flow Risk: SSH Obsolete Server Version/Cipher")
|
|
flow_risks[20] = ProtoField.bool("ndpi.flow_risk.smb_insecure_version", "SMB Insecure Version", num_bits_flow_risks, nil, bit(20), "nDPI Flow Risk: SMB Insecure Version")
|
|
flow_risks[21] = ProtoField.bool("ndpi.flow_risk.tls_suspicious_esni", "TLS Suspicious ESNI Usage", num_bits_flow_risks, nil, bit(21), "nDPI Flow Risk: TLS Suspicious ESNI Usage")
|
|
flow_risks[22] = ProtoField.bool("ndpi.flow_risk.unsafe_protocol", "Unsafe Protocol", num_bits_flow_risks, nil, bit(22), "nDPI Flow Risk: Unsafe Protocol")
|
|
flow_risks[23] = ProtoField.bool("ndpi.flow_risk.suspicious_dns_traffic", "Suspicious DNS traffic", num_bits_flow_risks, nil, bit(23), "nDPI Flow Risk: Suspicious DNS traffic")
|
|
flow_risks[24] = ProtoField.bool("ndpi.flow_risk.sni_tls_extension_missing", "SNI TLS extension was missing", num_bits_flow_risks, nil, bit(24), "nDPI Flow Risk: SNI TLS extension was missing")
|
|
flow_risks[25] = ProtoField.bool("ndpi.flow_risk.http_suspicious_content", "HTTP suspicious content", num_bits_flow_risks, nil, bit(25), "nDPI Flow Risk: HTTP suspicious content")
|
|
flow_risks[26] = ProtoField.bool("ndpi.flow_risk.risky_asn", "Risky ASN", num_bits_flow_risks, nil, bit(26), "nDPI Flow Risk: Risky ASN")
|
|
flow_risks[27] = ProtoField.bool("ndpi.flow_risk.risky_domain_name", "Risky domain name", num_bits_flow_risks, nil, bit(27), "nDPI Flow Risk: Risky domain name")
|
|
flow_risks[28] = ProtoField.bool("ndpi.flow_risk.possibly_malicious_ja3", "Possibly Malicious JA3 Fingerprint", num_bits_flow_risks, nil, bit(28), "nDPI Flow Risk: Possibly Malicious JA3 Fingerprint")
|
|
flow_risks[29] = ProtoField.bool("ndpi.flow_risk.possibly_malicious_ssl_certificate_sha1", "Possibly Malicious SSL Certificate SHA1 Fingerprint", num_bits_flow_risks, nil, bit(29), "nDPI Flow Risk: Possibly Malicious SSL Certificate SHA1 Fingerprint")
|
|
flow_risks[30] = ProtoField.bool("ndpi.flow_risk.desktop_file_sharing_session", "Desktop/File Sharing Session", num_bits_flow_risks, nil, bit(30), "nDPI Flow Risk: Desktop/File Sharing Session")
|
|
flow_risks[31] = ProtoField.bool("ndpi.flow_risk.uncommon_tls_alpn", "Uncommon TLS ALPN", num_bits_flow_risks, nil, bit(31), "nDPI Flow Risk: Uncommon TLS ALPN")
|
|
-- Restart bitmask from 0!
|
|
flow_risks[32] = ProtoField.bool("ndpi.flow_risk.cert_validity_too_long", "TLS certificate validity longer than 13 months", num_bits_flow_risks, nil, bit(0), "nDPI Flow Risk: TLS certificate validity longer than 13 months")
|
|
flow_risks[33] = ProtoField.bool("ndpi.flow_risk.suspicious_extension", "TLS suspicious extension", num_bits_flow_risks, nil, bit(1), "nDPI Flow Risk: TLS suspicious extension")
|
|
flow_risks[34] = ProtoField.bool("ndpi.flow_risk.fatal_alert", "TLS fatal alert detected", num_bits_flow_risks, nil, bit(2), "nDPI Flow Risk: TLS fatal alert")
|
|
flow_risks[35] = ProtoField.bool("ndpi.flow_risk.suspicious_entropy", "Suspicious entropy", num_bits_flow_risks, nil, bit(3), "nDPI Flow Risk: suspicious entropy")
|
|
flow_risks[36] = ProtoField.bool("ndpi.flow_risk.clear_text_credentials", "Cleat-Text credentials", num_bits_flow_risks, nil, bit(4), "nDPI Flow Risk: cleat-text credentials")
|
|
flow_risks[37] = ProtoField.bool("ndpi.flow_risk.dns_large_packet", "DNS large packet", num_bits_flow_risks, nil, bit(5), "nDPI Flow Risk: DNS packet is larger than 512 bytes")
|
|
flow_risks[38] = ProtoField.bool("ndpi.flow_risk.dns_fragmented", "DNS fragmented", num_bits_flow_risks, nil, bit(6), "nDPI Flow Risk: DNS message is fragmented")
|
|
flow_risks[39] = ProtoField.bool("ndpi.flow_risk.invalid_characters", "Invalid characters", num_bits_flow_risks, nil, bit(7), "nDPI Flow Risk: Text contains non-printable characters")
|
|
flow_risks[40] = ProtoField.bool("ndpi.flow_risk.possible_exploit", "Possible Exploit", num_bits_flow_risks, nil, bit(8), "nDPI Flow Risk: Possible exploit detected")
|
|
flow_risks[41] = ProtoField.bool("ndpi.flow_risk.cert_about_to_expire", "TLS cert about to expire", num_bits_flow_risks, nil, bit(9), "nDPI Flow Risk: TLS certificate about to expire")
|
|
flow_risks[42] = ProtoField.bool("ndpi.flow_risk.punycode_idn", "IDN Domain Name", num_bits_flow_risks, nil, bit(10), "nDPI Flow Risk: IDN Domain Name")
|
|
flow_risks[43] = ProtoField.bool("ndpi.flow_risk.error_code_detected", "Error Code Detected", num_bits_flow_risks, nil, bit(11), "nDPI Flow Risk: Error Code Detected")
|
|
flow_risks[44] = ProtoField.bool("ndpi.flow_risk.crawler_bot", "Crawler/Bot Detected", num_bits_flow_risks, nil, bit(12), "nDPI Flow Risk: Crawler/Bot Detected")
|
|
flow_risks[45] = ProtoField.bool("ndpi.flow_risk.anonymous_subscriber", "Anonymous Subscriber", num_bits_flow_risks, nil, bit(13), "nDPI Flow Risk: Anonymous Subscriber")
|
|
|
|
-- Last one: keep in sync the bitmask when adding new risks!!
|
|
flow_risks[64] = ProtoField.new("Unused", "ndpi.flow_risk.unused", ftypes.UINT32, nil, base.HEX, bit(32) - bit(13))
|
|
|
|
for _,v in pairs(flow_risks) do
|
|
ndpi_fds[#ndpi_fds + 1] = v
|
|
end
|
|
|
|
local ntop_proto = Proto("ntop", "ntop Extensions")
|
|
ntop_proto.fields = {}
|
|
|
|
local ntop_fds = ntop_proto.fields
|
|
ntop_fds.client_nw_rtt = ProtoField.new("TCP client network RTT (msec)", "ntop.latency.client_rtt", ftypes.FLOAT, nil, base.NONE)
|
|
ntop_fds.server_nw_rtt = ProtoField.new("TCP server network RTT (msec)", "ntop.latency.server_rtt", ftypes.FLOAT, nil, base.NONE)
|
|
ntop_fds.appl_latency_rtt = ProtoField.new("Application Latency RTT (msec)", "ntop.latency.appl_rtt", ftypes.FLOAT, nil, base.NONE)
|
|
|
|
local f_eth_source = Field.new("eth.src")
|
|
local f_eth_trailer = Field.new("eth.trailer")
|
|
local f_vlan_trailer = Field.new("vlan.trailer")
|
|
local f_vlan_id = Field.new("vlan.id")
|
|
local f_arp_opcode = Field.new("arp.opcode")
|
|
local f_arp_sender_mac = Field.new("arp.src.hw_mac")
|
|
local f_arp_target_mac = Field.new("arp.dst.hw_mac")
|
|
local f_dns_query_name = Field.new("dns.qry.name")
|
|
local f_dns_ret_code = Field.new("dns.flags.rcode")
|
|
local f_dns_response = Field.new("dns.flags.response")
|
|
local f_udp_len = Field.new("udp.length")
|
|
local f_tcp_header_len = Field.new("tcp.hdr_len")
|
|
local f_ip_len = Field.new("ip.len")
|
|
local f_ip_hdr_len = Field.new("ip.hdr_len")
|
|
local f_tls_server_name = Field.new("tls.handshake.extensions_server_name")
|
|
local f_tcp_flags = Field.new('tcp.flags')
|
|
local f_tcp_retrans = Field.new('tcp.analysis.retransmission')
|
|
local f_tcp_ooo = Field.new('tcp.analysis.out_of_order')
|
|
local f_tcp_lost_segment = Field.new('tcp.analysis.lost_segment') -- packet drop ?
|
|
local f_rpc_xid = Field.new('rpc.xid')
|
|
local f_rpc_msgtyp = Field.new('rpc.msgtyp')
|
|
local f_user_agent = Field.new('http.user_agent')
|
|
local f_dhcp_request_item = Field.new('dhcp.option.request_list_item')
|
|
|
|
local ndpi_protos = {}
|
|
local ndpi_flows = {}
|
|
local num_ndpi_flows = 0
|
|
|
|
local arp_stats = {}
|
|
local mac_stats = {}
|
|
local vlan_stats = {}
|
|
local vlan_found = false
|
|
|
|
local dns_responses_ok = {}
|
|
local dns_responses_error = {}
|
|
local dns_client_queries = {}
|
|
local dns_server_responses = {}
|
|
local dns_queries = {}
|
|
|
|
local syn = {}
|
|
local synack = {}
|
|
local lower_ndpi_flow_id = 0
|
|
local lower_ndpi_flow_volume = 0
|
|
|
|
local compute_flows_stats = true
|
|
local max_num_entries = 10
|
|
local max_num_flows = 50
|
|
|
|
local num_top_dns_queries = 0
|
|
local max_num_dns_queries = 50
|
|
|
|
local tls_server_names = {}
|
|
local tot_tls_flows = 0
|
|
|
|
local http_ua = {}
|
|
local tot_http_ua_flows = 0
|
|
|
|
local flows = {}
|
|
local tot_flows = 0
|
|
|
|
local flows_with_risks = {}
|
|
|
|
local dhcp_fingerprints = {}
|
|
|
|
local min_nw_client_RRT = {}
|
|
local min_nw_server_RRT = {}
|
|
local max_nw_client_RRT = {}
|
|
local max_nw_server_RRT = {}
|
|
local min_appl_RRT = {}
|
|
local max_appl_RRT = {}
|
|
|
|
local first_payload_ts = {}
|
|
local first_payload_id = {}
|
|
|
|
local rpc_ts = {}
|
|
|
|
local num_pkts = 0
|
|
local last_processed_packet_number = 0
|
|
local max_latency_discard = 5000 -- 5 sec
|
|
local max_appl_lat_discard = 15000 -- 15 sec
|
|
local debug = false
|
|
|
|
local dump_timeseries = false
|
|
|
|
local dump_file = "/tmp/wireshark-influx.txt"
|
|
local file
|
|
|
|
-- ##############################################
|
|
|
|
function string.contains(String,Start)
|
|
if type(String) ~= 'string' or type(Start) ~= 'string' then
|
|
return false
|
|
end
|
|
return(string.find(String,Start,1) ~= nil)
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function string.starts(String,Start)
|
|
if type(String) ~= 'string' or type(Start) ~= 'string' then
|
|
return false
|
|
end
|
|
return string.sub(String,1,string.len(Start))==Start
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function string.ends(String,End)
|
|
if type(String) ~= 'string' or type(End) ~= 'string' then
|
|
return false
|
|
end
|
|
return End=='' or string.sub(String,-string.len(End))==End
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function round(num, idp)
|
|
return tonumber(string.format("%." .. (idp or 0) .. "f", num))
|
|
end
|
|
|
|
function formatPctg(p)
|
|
local p = round(p, 1)
|
|
|
|
if(p < 1) then return("< 1 %") end
|
|
|
|
return p.." %"
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
string.split = function(s, p)
|
|
local temp = {}
|
|
local index = 0
|
|
local last_index = string.len(s)
|
|
|
|
while true do
|
|
local i, e = string.find(s, p, index)
|
|
|
|
if i and e then
|
|
local next_index = e + 1
|
|
local word_bound = i - 1
|
|
table.insert(temp, string.sub(s, index, word_bound))
|
|
index = next_index
|
|
else
|
|
if index > 0 and index <= last_index then
|
|
table.insert(temp, string.sub(s, index, last_index))
|
|
elseif index == 0 then
|
|
temp = nil
|
|
end
|
|
break
|
|
end
|
|
end
|
|
|
|
return temp
|
|
end
|
|
|
|
-- ##############################################
|
|
|
|
function shortenString(name, max_len)
|
|
max_len = max_len or 24
|
|
if(string.len(name) < max_len) then
|
|
return(name)
|
|
else
|
|
return(string.sub(name, 1, max_len).."...")
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
-- Convert bytes to human readable format
|
|
function bytesToSize(bytes)
|
|
if(bytes == nil) then
|
|
return("0")
|
|
else
|
|
precision = 2
|
|
kilobyte = 1024;
|
|
megabyte = kilobyte * 1024;
|
|
gigabyte = megabyte * 1024;
|
|
terabyte = gigabyte * 1024;
|
|
|
|
bytes = tonumber(bytes)
|
|
if((bytes >= 0) and (bytes < kilobyte)) then
|
|
return round(bytes, precision) .. " Bytes";
|
|
elseif((bytes >= kilobyte) and (bytes < megabyte)) then
|
|
return round(bytes / kilobyte, precision) .. ' KB';
|
|
elseif((bytes >= megabyte) and (bytes < gigabyte)) then
|
|
return round(bytes / megabyte, precision) .. ' MB';
|
|
elseif((bytes >= gigabyte) and (bytes < terabyte)) then
|
|
return round(bytes / gigabyte, precision) .. ' GB';
|
|
elseif(bytes >= terabyte) then
|
|
return round(bytes / terabyte, precision) .. ' TB';
|
|
else
|
|
return round(bytes, precision) .. ' Bytes';
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function pairsByKeys(t, f)
|
|
local a = {}
|
|
|
|
-- io.write(debug.traceback().."\n")
|
|
for n in pairs(t) do table.insert(a, n) end
|
|
table.sort(a, f)
|
|
local i = 0 -- iterator variable
|
|
local iter = function () -- iterator function
|
|
i = i + 1
|
|
if a[i] == nil then return nil
|
|
else return a[i], t[a[i]]
|
|
end
|
|
end
|
|
return iter
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function pairsByValues(t, f)
|
|
local a = {}
|
|
for n in pairs(t) do table.insert(a, n) end
|
|
table.sort(a, function(x, y) return f(t[x], t[y]) end)
|
|
local i = 0 -- iterator variable
|
|
local iter = function () -- iterator function
|
|
i = i + 1
|
|
if a[i] == nil then return nil
|
|
else return a[i], t[a[i]]
|
|
end
|
|
end
|
|
return iter
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function asc(a,b) return (a < b) end
|
|
function rev(a,b) return (a > b) end
|
|
|
|
-- ###############################################
|
|
|
|
local function BitOR(a,b)--Bitwise or
|
|
local p,c=1,0
|
|
while a+b>0 do
|
|
local ra,rb=a%2,b%2
|
|
if ra+rb>0 then c=c+p end
|
|
a,b,p=(a-ra)/2,(b-rb)/2,p*2
|
|
end
|
|
return c
|
|
end
|
|
|
|
local function BitNOT(n)
|
|
local p,c=1,0
|
|
while n>0 do
|
|
local r=n%2
|
|
if r<1 then c=c+p end
|
|
n,p=(n-r)/2,p*2
|
|
end
|
|
return c
|
|
end
|
|
|
|
local function BitAND(a,b)--Bitwise and (portable edition)
|
|
local p,c=1,0
|
|
while a>0 and b>0 do
|
|
local ra,rb=a%2,b%2
|
|
if ra+rb>1 then c=c+p end
|
|
a,b,p=(a-ra)/2,(b-rb)/2,p*2
|
|
end
|
|
return c
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function ndpi_proto.init()
|
|
ndpi_protos = { }
|
|
ndpi_flows = { }
|
|
|
|
num_ndpi_flows = 0
|
|
lower_ndpi_flow_id = 0
|
|
lower_ndpi_flow_volume = 0
|
|
num_pkts = 0
|
|
last_processed_packet_number = 0
|
|
|
|
-- ARP
|
|
arp_stats = { }
|
|
|
|
-- MAC
|
|
mac_stats = { }
|
|
|
|
-- VLAN
|
|
vlan_stats = { }
|
|
vlan_found = false
|
|
|
|
-- TCP
|
|
syn = {}
|
|
synack = {}
|
|
|
|
-- TLS
|
|
tls_server_names = {}
|
|
tot_tls_flows = 0
|
|
|
|
-- HTTP
|
|
http_ua = {}
|
|
tot_http_ua_flows = 0
|
|
|
|
-- Flows
|
|
flows = {}
|
|
tot_flows = 0
|
|
|
|
-- Risks
|
|
flows_with_risks = {}
|
|
|
|
-- DHCP
|
|
dhcp_fingerprints = {}
|
|
|
|
-- DNS
|
|
dns_responses_ok = {}
|
|
dns_responses_error = {}
|
|
dns_client_queries = {}
|
|
dns_server_responses = {}
|
|
top_dns_queries = {}
|
|
num_top_dns_queries = 0
|
|
|
|
-- TCP analysis
|
|
num_tcp_retrans = 0
|
|
num_tcp_ooo = 0
|
|
num_tcp_lost_segment = 0
|
|
tcp_retrans = {}
|
|
tcp_ooo = {}
|
|
tcp_lost_segment = {}
|
|
|
|
-- Network RRT
|
|
min_nw_client_RRT = {}
|
|
min_nw_server_RRT = {}
|
|
max_nw_client_RRT = {}
|
|
max_nw_server_RRT = {}
|
|
|
|
-- Application Latency
|
|
min_nw_client_RRT = {}
|
|
min_nw_server_RRT = {}
|
|
max_nw_client_RRT = {}
|
|
max_nw_server_RRT = {}
|
|
min_appl_RRT = {}
|
|
max_appl_RRT = {}
|
|
first_payload_ts = {}
|
|
first_payload_id = {}
|
|
|
|
-- RPC
|
|
rpc_ts = {}
|
|
|
|
if(dump_timeseries) then
|
|
file = assert(io.open(dump_file, "a"))
|
|
print("Writing to "..dump_file.."\n")
|
|
print('Load data with:\ncurl -i -XPOST "http://localhost:8086/write?db=wireshark" --data-binary @/tmp/wireshark-influx.txt\n')
|
|
end
|
|
end
|
|
|
|
function slen(str)
|
|
local i = 1
|
|
local len = 0
|
|
local zero = string.char(0)
|
|
|
|
for i = 1, 16 do
|
|
local c = str:sub(i,i)
|
|
|
|
if(c ~= zero) then
|
|
len = len + 1
|
|
else
|
|
break
|
|
end
|
|
end
|
|
|
|
return(str:sub(1, len))
|
|
end
|
|
|
|
-- Print contents of `tbl`, with indentation.
|
|
-- You can call it as tprint(mytable)
|
|
-- The other two parameters should not be set
|
|
function tprint(s, l, i)
|
|
l = (l) or 1000; i = i or "";-- default item limit, indent string
|
|
if (l<1) then io.write("ERROR: Item limit reached.\n"); return l-1 end;
|
|
local ts = type(s);
|
|
if (ts ~= "table") then io.write(i..' '..ts..' '..tostring(s)..'\n'); return l-1 end
|
|
io.write(i..' '..ts..'\n');
|
|
for k,v in pairs(s) do
|
|
local indent = ""
|
|
|
|
if(i ~= "") then
|
|
indent = i .. "."
|
|
end
|
|
indent = indent .. tostring(k)
|
|
|
|
l = tprint(v, l, indent);
|
|
if (l < 0) then break end
|
|
end
|
|
|
|
return l
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function getstring(finfo)
|
|
local ok, val = pcall(tostring, finfo)
|
|
if not ok then val = "(unknown)" end
|
|
return val
|
|
end
|
|
|
|
local function getval(finfo)
|
|
local ok, val = pcall(tostring, finfo)
|
|
if not ok then val = nil end
|
|
return val
|
|
end
|
|
|
|
function dump_pinfo(pinfo)
|
|
local fields = { all_field_infos() }
|
|
for ix, finfo in ipairs(fields) do
|
|
-- output = output .. "\t[" .. ix .. "] " .. finfo.name .. " = " .. getstring(finfo) .. "\n"
|
|
--print(finfo.name .. "\n")
|
|
print("\t[" .. ix .. "] " .. finfo.name .. " = " .. getstring(finfo) .. "\n")
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
|
|
function initARPEntry(mac)
|
|
if(arp_stats[mac] == nil) then
|
|
arp_stats[mac] = { request_sent=0, request_rcvd=0, response_sent=0, response_rcvd=0 }
|
|
end
|
|
end
|
|
|
|
function dissectARP(isRequest, src_mac, dst_mac)
|
|
if(isRequest == 1) then
|
|
-- ARP Request
|
|
initARPEntry(src_mac)
|
|
arp_stats[src_mac].request_sent = arp_stats[src_mac].request_sent + 1
|
|
|
|
initARPEntry(dst_mac)
|
|
arp_stats[dst_mac].request_rcvd = arp_stats[dst_mac].request_rcvd + 1
|
|
else
|
|
-- ARP Response
|
|
initARPEntry(src_mac)
|
|
arp_stats[src_mac].response_sent = arp_stats[src_mac].response_sent + 1
|
|
|
|
initARPEntry(dst_mac)
|
|
arp_stats[dst_mac].response_rcvd = arp_stats[dst_mac].response_rcvd + 1
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function abstime_diff(a, b)
|
|
return(tonumber(a)-tonumber(b))
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function arp_dissector(tvb, pinfo, tree)
|
|
local arp_opcode = f_arp_opcode()
|
|
|
|
if(arp_opcode ~= nil) then
|
|
-- ARP
|
|
local isRequest = getval(arp_opcode)
|
|
local src_mac = getval(f_arp_sender_mac())
|
|
local dst_mac = getval(f_arp_target_mac())
|
|
dissectARP(isRequest, src_mac, dst_mac)
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function vlan_dissector(tvb, pinfo, tree)
|
|
local vlan_id = f_vlan_id()
|
|
if(vlan_id ~= nil) then
|
|
vlan_id = tonumber(getval(vlan_id))
|
|
|
|
if(vlan_stats[vlan_id] == nil) then vlan_stats[vlan_id] = 0 end
|
|
vlan_stats[vlan_id] = vlan_stats[vlan_id] + 1
|
|
vlan_found = true
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function mac_dissector(tvb, pinfo, tree)
|
|
local src_mac = tostring(pinfo.dl_src)
|
|
local src_ip = tostring(pinfo.src)
|
|
if(mac_stats[src_mac] == nil) then mac_stats[src_mac] = {} end
|
|
mac_stats[src_mac][src_ip] = 1
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function tls_dissector(tvb, pinfo, tree)
|
|
local tls_server_name = f_tls_server_name()
|
|
if(tls_server_name ~= nil) then
|
|
tls_server_name = getval(tls_server_name)
|
|
|
|
if(tls_server_names[tls_server_name] == nil) then
|
|
tls_server_names[tls_server_name] = 0
|
|
end
|
|
|
|
tls_server_names[tls_server_name] = tls_server_names[tls_server_name] + 1
|
|
tot_tls_flows = tot_tls_flows + 1
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function http_dissector(tvb, pinfo, tree)
|
|
local user_agent = f_user_agent()
|
|
if(user_agent ~= nil) then
|
|
local srckey = tostring(pinfo.src)
|
|
|
|
user_agent = getval(user_agent)
|
|
|
|
if(http_ua[user_agent] == nil) then
|
|
http_ua[user_agent] = { }
|
|
tot_http_ua_flows = tot_http_ua_flows + 1
|
|
end
|
|
|
|
if(http_ua[user_agent][srckey] == nil) then
|
|
http_ua[user_agent][srckey] = 1
|
|
-- io.write("Adding ["..user_agent.."] @ "..srckey.."\n")
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function timeseries_dissector(tvb, pinfo, tree)
|
|
if(pinfo.dst_port ~= 0) then
|
|
local rev_key = getstring(pinfo.dst)..":"..getstring(pinfo.dst_port).."-"..getstring(pinfo.src)..":"..getstring(pinfo.src_port)
|
|
local k
|
|
|
|
if(flows[rev_key] ~= nil) then
|
|
flows[rev_key][2] = flows[rev_key][2] + pinfo.len
|
|
k = rev_key
|
|
else
|
|
local key = getstring(pinfo.src)..":"..getstring(pinfo.src_port).."-"..getstring(pinfo.dst)..":"..getstring(pinfo.dst_port)
|
|
|
|
k = key
|
|
if(flows[key] == nil) then
|
|
flows[key] = { pinfo.len, 0 } -- src -> dst / dst -> src
|
|
tot_flows = tot_flows + 1
|
|
else
|
|
flows[key][1] = flows[key][1] + pinfo.len
|
|
end
|
|
end
|
|
|
|
--k = pinfo.curr_proto..","..k
|
|
|
|
local bytes = flows[k][1]+flows[k][2]
|
|
local row
|
|
|
|
-- Prometheus
|
|
-- row = "wireshark {metric=\"bytes\", flow=\""..k.."\"} ".. bytes .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
|
|
-- Influx
|
|
row = "wireshark,flow="..k.." bytes=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
row = "wireshark,ndpi="..ndpi.protocol_name.." bytes=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
row = "wireshark,host="..getstring(pinfo.src).." sent=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
row = "wireshark,host="..getstring(pinfo.dst).." rcvd=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
-- print(row)
|
|
|
|
file:flush()
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function risk_dissector(tvb, pinfo, tree)
|
|
if(pinfo.dst_port ~= 0) then
|
|
local rev_key = getstring(pinfo.dst)..":"..getstring(pinfo.dst_port).."-"..getstring(pinfo.src)..":"..getstring(pinfo.src_port)
|
|
local k
|
|
|
|
if(flows[rev_key] ~= nil) then
|
|
flows[rev_key][2] = flows[rev_key][2] + pinfo.len
|
|
k = rev_key
|
|
else
|
|
local key = getstring(pinfo.src)..":"..getstring(pinfo.src_port).."-"..getstring(pinfo.dst)..":"..getstring(pinfo.dst_port)
|
|
|
|
k = key
|
|
if(flows[key] == nil) then
|
|
flows[key] = { pinfo.len, 0 } -- src -> dst / dst -> src
|
|
tot_flows = tot_flows + 1
|
|
else
|
|
flows[key][1] = flows[key][1] + pinfo.len
|
|
end
|
|
end
|
|
|
|
--k = pinfo.curr_proto..","..k
|
|
|
|
local bytes = flows[k][1]+flows[k][2]
|
|
local row
|
|
|
|
-- Prometheus
|
|
-- row = "wireshark {metric=\"bytes\", flow=\""..k.."\"} ".. bytes .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
|
|
-- Influx
|
|
row = "wireshark,flow="..k.." bytes=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
row = "wireshark,ndpi="..ndpi.protocol_name.." bytes=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
row = "wireshark,host="..getstring(pinfo.src).." sent=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
row = "wireshark,host="..getstring(pinfo.dst).." rcvd=".. pinfo.len .. " ".. (tonumber(pinfo.abs_ts)*10000).."00000"
|
|
file:write(row.."\n")
|
|
|
|
-- print(row)
|
|
|
|
file:flush()
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function dhcp_dissector(tvb, pinfo, tree)
|
|
local req_item = f_dhcp_request_item()
|
|
|
|
if(req_item ~= nil) then
|
|
local srckey = tostring(f_eth_source())
|
|
local req_table = { f_dhcp_request_item() }
|
|
local fingerprint = ""
|
|
|
|
for k,v in pairs(req_table) do
|
|
fingerprint = fingerprint .. string.format("%02X", v.value)
|
|
end
|
|
|
|
dhcp_fingerprints[srckey] = fingerprint
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function dns_dissector(tvb, pinfo, tree)
|
|
local dns_response = f_dns_response()
|
|
if(dns_response ~= nil) then
|
|
local dns_ret_code = f_dns_ret_code()
|
|
local dns_response = tonumber(getval(dns_response))
|
|
local srckey = tostring(pinfo.src)
|
|
local dstkey = tostring(pinfo.dst)
|
|
local dns_query_name = f_dns_query_name()
|
|
dns_query_name = getval(dns_query_name)
|
|
|
|
if(dns_response == 0) then
|
|
-- DNS Query
|
|
if(dns_client_queries[srckey] == nil) then dns_client_queries[srckey] = 0 end
|
|
dns_client_queries[srckey] = dns_client_queries[srckey] + 1
|
|
|
|
if(dns_query_name ~= nil) then
|
|
if(top_dns_queries[dns_query_name] == nil) then
|
|
top_dns_queries[dns_query_name] = 0
|
|
num_top_dns_queries = num_top_dns_queries + 1
|
|
|
|
if(num_top_dns_queries > max_num_dns_queries) then
|
|
-- We need to harvest the flow with least packets beside this new one
|
|
for k,v in pairsByValues(dns_client_queries, asc) do
|
|
if(k ~= dns_query_name) then
|
|
table.remove(ndpi_flows, k)
|
|
num_top_dns_queries = num_top_dns_queries - 1
|
|
|
|
if(num_top_dns_queries == (2*max_num_entries)) then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
top_dns_queries[dns_query_name] = top_dns_queries[dns_query_name] + 1
|
|
end
|
|
else
|
|
-- DNS Response
|
|
if(dns_server_responses[srckey] == nil) then dns_server_responses[srckey] = 0 end
|
|
dns_server_responses[srckey] = dns_server_responses[srckey] + 1
|
|
|
|
if(dns_ret_code ~= nil) then
|
|
dns_ret_code = getval(dns_ret_code)
|
|
|
|
if((dns_query_name ~= nil) and (dns_ret_code ~= nil)) then
|
|
dns_ret_code = tonumber(dns_ret_code)
|
|
|
|
if(debug) then print("[".. srckey .." -> ".. dstkey .."] "..dns_query_name.."\t"..dns_ret_code) end
|
|
|
|
if(dns_ret_code == 0) then
|
|
if(dns_responses_ok[srckey] == nil) then dns_responses_ok[srckey] = 0 end
|
|
dns_responses_ok[srckey] = dns_responses_ok[srckey] + 1
|
|
else
|
|
if(dns_responses_error[srckey] == nil) then dns_responses_error[srckey] = 0 end
|
|
dns_responses_error[srckey] = dns_responses_error[srckey] + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function rpc_dissector(tvb, pinfo, tree)
|
|
local _rpc_xid = f_rpc_xid()
|
|
local _rpc_msgtyp = f_rpc_msgtyp()
|
|
|
|
if((_rpc_xid ~= nil) and (_rpc_msgtyp ~= nil)) then
|
|
local xid = getval(_rpc_xid)
|
|
local msgtyp = getval(_rpc_msgtyp)
|
|
|
|
if(msgtyp == "0") then
|
|
rpc_ts[xid] = pinfo.abs_ts
|
|
else
|
|
if(rpc_ts[xid] ~= nil) then
|
|
local appl_latency = abstime_diff(pinfo.abs_ts, rpc_ts[xid]) * 1000
|
|
|
|
if((appl_latency > 0) and (appl_latency < max_appl_lat_discard)) then
|
|
local ntop_subtree = tree:add(ntop_proto, tvb(), "ntop")
|
|
ntop_subtree:add(ntop_fds.appl_latency_rtt, appl_latency)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function tcp_dissector(tvb, pinfo, tree)
|
|
local _tcp_retrans = f_tcp_retrans()
|
|
local _tcp_ooo = f_tcp_ooo()
|
|
local _tcp_lost_segment = f_tcp_lost_segment()
|
|
|
|
if(_tcp_retrans ~= nil) then
|
|
local key = getstring(pinfo.src)..":"..getstring(pinfo.src_port).." -> "..getstring(pinfo.dst)..":"..getstring(pinfo.dst_port)
|
|
num_tcp_retrans = num_tcp_retrans + 1
|
|
if(tcp_retrans[key] == nil) then tcp_retrans[key] = 0 end
|
|
tcp_retrans[key] = tcp_retrans[key] + 1
|
|
end
|
|
|
|
if(_tcp_ooo ~= nil) then
|
|
local key = getstring(pinfo.src)..":"..getstring(pinfo.src_port).." -> "..getstring(pinfo.dst)..":"..getstring(pinfo.dst_port)
|
|
num_tcp_ooo = num_tcp_ooo + 1
|
|
if(tcp_ooo[key] == nil) then tcp_ooo[key] = 0 end
|
|
tcp_ooo[key] = tcp_ooo[key] + 1
|
|
end
|
|
|
|
if(_tcp_lost_segment ~= nil) then
|
|
local key = getstring(pinfo.src)..":"..getstring(pinfo.src_port).." -> "..getstring(pinfo.dst)..":"..getstring(pinfo.dst_port)
|
|
num_tcp_lost_segment = num_tcp_lost_segment + 1
|
|
if(tcp_lost_segment[key] == nil) then tcp_lost_segment[key] = 0 end
|
|
tcp_lost_segment[key] = tcp_lost_segment[key] + 1
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
function latency_dissector(tvb, pinfo, tree)
|
|
local _tcp_flags = f_tcp_flags()
|
|
local udp_len = f_udp_len()
|
|
|
|
if((_tcp_flags ~= nil) or (udp_len ~= nil)) then
|
|
local key
|
|
local rtt_debug = false
|
|
local tcp_flags
|
|
local tcp_header_len
|
|
local ip_len
|
|
local ip_hdr_len
|
|
|
|
if(udp_len == nil) then
|
|
tcp_flags = f_tcp_flags().value
|
|
tcp_header_len = f_tcp_header_len()
|
|
ip_len = f_ip_len()
|
|
ip_hdr_len = f_ip_hdr_len()
|
|
end
|
|
|
|
if(((ip_len ~= nil) and (tcp_header_len ~= nil) and (ip_hdr_len ~= nil))
|
|
or (udp_len ~= nil)
|
|
) then
|
|
local payloadLen
|
|
|
|
if(udp_len == nil) then
|
|
ip_len = tonumber(getval(ip_len))
|
|
tcp_header_len = tonumber(getval(tcp_header_len))
|
|
ip_hdr_len = tonumber(getval(ip_hdr_len))
|
|
|
|
payloadLen = ip_len - tcp_header_len - ip_hdr_len
|
|
else
|
|
payloadLen = tonumber(getval(udp_len))
|
|
end
|
|
|
|
if(payloadLen > 0) then
|
|
local key = getstring(pinfo.src).."_"..getstring(pinfo.src_port).."_"..getstring(pinfo.dst).."_"..getstring(pinfo.dst_port)
|
|
local revkey = getstring(pinfo.dst).."_"..getstring(pinfo.dst_port).."_"..getstring(pinfo.src).."_"..getstring(pinfo.src_port)
|
|
|
|
if(first_payload_ts[revkey] ~= nil) then
|
|
local appl_latency = abstime_diff(pinfo.abs_ts, first_payload_ts[revkey]) * 1000
|
|
|
|
if((appl_latency > 0) and (appl_latency < max_appl_lat_discard)
|
|
-- The trick below is used to set only the first latency packet
|
|
and ((first_payload_id[revkey] == nil) or (first_payload_id[revkey] == pinfo.number))
|
|
) then
|
|
local ntop_subtree = tree:add(ntop_proto, tvb(), "ntop")
|
|
local server = getstring(pinfo.src)
|
|
if(rtt_debug) then print("==> Appl Latency @ "..pinfo.number..": "..appl_latency) end
|
|
|
|
ntop_subtree:add(ntop_fds.appl_latency_rtt, appl_latency)
|
|
first_payload_id[revkey] = pinfo.number
|
|
|
|
if(min_appl_RRT[server] == nil) then
|
|
min_appl_RRT[server] = appl_latency
|
|
else
|
|
min_appl_RRT[server] = math.min(min_appl_RRT[server], appl_latency)
|
|
end
|
|
|
|
if(max_appl_RRT[server] == nil) then
|
|
max_appl_RRT[server] = appl_latency
|
|
else
|
|
max_appl_RRT[server] = math.max(max_appl_RRT[server], appl_latency)
|
|
end
|
|
|
|
-- first_payload_ts[revkey] = nil
|
|
end
|
|
else
|
|
if(first_payload_ts[key] == nil) then first_payload_ts[key] = pinfo.abs_ts end
|
|
end
|
|
end
|
|
end
|
|
|
|
tcp_flags = tonumber(tcp_flags)
|
|
|
|
if(tcp_flags == 2) then
|
|
-- SYN
|
|
key = getstring(pinfo.src).."_"..getstring(pinfo.src_port).."_"..getstring(pinfo.dst).."_"..getstring(pinfo.dst_port)
|
|
if(rtt_debug) then print("SYN @ ".. pinfo.abs_ts.." "..key) end
|
|
syn[key] = pinfo.abs_ts
|
|
elseif(tcp_flags == 18) then
|
|
-- SYN|ACK
|
|
key = getstring(pinfo.dst).."_"..getstring(pinfo.dst_port).."_"..getstring(pinfo.src).."_"..getstring(pinfo.src_port)
|
|
if(rtt_debug) then print("SYN|ACK @ ".. pinfo.abs_ts.." "..key) end
|
|
synack[key] = pinfo.abs_ts
|
|
if(syn[key] ~= nil) then
|
|
local diff = abstime_diff(synack[key], syn[key]) * 1000 -- msec
|
|
|
|
if(rtt_debug) then print("Server RTT --> ".. diff .. " msec") end
|
|
|
|
if(diff <= max_latency_discard) then
|
|
local ntop_subtree = tree:add(ntop_proto, tvb(), "ntop")
|
|
ntop_subtree:add(ntop_fds.server_nw_rtt, diff)
|
|
-- Do not delete the key below as it's used when a user clicks on a packet
|
|
-- syn[key] = nil
|
|
|
|
local server = getstring(pinfo.src)
|
|
if(min_nw_server_RRT[server] == nil) then
|
|
min_nw_server_RRT[server] = diff
|
|
else
|
|
min_nw_server_RRT[server] = math.min(min_nw_server_RRT[server], diff)
|
|
end
|
|
|
|
if(max_nw_server_RRT[server] == nil) then
|
|
max_nw_server_RRT[server] = diff
|
|
else
|
|
max_nw_server_RRT[server] = math.max(max_nw_server_RRT[server], diff)
|
|
end
|
|
end
|
|
end
|
|
elseif(tcp_flags == 16) then
|
|
-- ACK
|
|
key = getstring(pinfo.src).."_"..getstring(pinfo.src_port).."_"..getstring(pinfo.dst).."_"..getstring(pinfo.dst_port)
|
|
if(rtt_debug) then print("ACK @ ".. pinfo.abs_ts.." "..key) end
|
|
|
|
if(synack[key] ~= nil) then
|
|
local diff = abstime_diff(pinfo.abs_ts, synack[key]) * 1000 -- msec
|
|
if(rtt_debug) then print("Client RTT --> ".. diff .. " msec") end
|
|
|
|
if(diff <= max_latency_discard) then
|
|
local ntop_subtree = tree:add(ntop_proto, tvb(), "ntop")
|
|
ntop_subtree:add(ntop_fds.client_nw_rtt, diff)
|
|
|
|
-- Do not delete the key below as it's used when a user clicks on a packet
|
|
synack[key] = nil
|
|
|
|
local client = getstring(pinfo.src)
|
|
if(min_nw_client_RRT[client] == nil) then
|
|
min_nw_client_RRT[client] = diff
|
|
else
|
|
min_nw_client_RRT[client] = math.min(min_nw_client_RRT[client], diff)
|
|
end
|
|
|
|
if(max_nw_client_RRT[client] == nil) then
|
|
max_nw_client_RRT[client] = diff
|
|
else
|
|
max_nw_client_RRT[client] = math.max(max_nw_client_RRT[client], diff)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
function hasbit(x, p)
|
|
return x % (p + p) >= p
|
|
end
|
|
|
|
-- the dissector function callback
|
|
function ndpi_proto.dissector(tvb, pinfo, tree)
|
|
-- Wireshark dissects the packet twice. We ignore the first
|
|
-- run as on that step the packet is still undecoded
|
|
-- The trick below avoids to process the packet twice
|
|
|
|
if(pinfo.visited == true) then
|
|
local eth_trailer = {f_eth_trailer()}
|
|
local vlan_trailer = {f_vlan_trailer()}
|
|
|
|
-- nDPI trailer is usually the (only one) ethernet trailer.
|
|
-- But, depending on Wireshark configuration and on L2 protocols, the
|
|
-- situation may be more complex. Let's try to handle the most common cases:
|
|
-- 1) with (multiple) ethernet trailers, nDPI trailer is usually the last one
|
|
-- 2) with VLAN encapsulation, nDPI trailer is usually recognized as vlan trailer
|
|
if(eth_trailer[#eth_trailer] ~= nil or
|
|
vlan_trailer[#vlan_trailer] ~= nil) then
|
|
|
|
local ndpi_trailer
|
|
if (eth_trailer[#eth_trailer] ~= nil) then
|
|
ndpi_trailer = getval(eth_trailer[#eth_trailer])
|
|
else
|
|
ndpi_trailer = getval(vlan_trailer[#vlan_trailer])
|
|
end
|
|
local magic = string.sub(ndpi_trailer, 1, 11)
|
|
|
|
if(magic == "19:68:09:24") then
|
|
local ndpikey, srckey, dstkey, flowkey
|
|
local elems = string.split(string.sub(ndpi_trailer, 12), ":")
|
|
local ndpi_subtree = tree:add(ndpi_proto, tvb(), "nDPI Protocol")
|
|
local str_score = elems[14]..elems[15]
|
|
local flow_score = tonumber(str_score, 16) -- 16 = HEX
|
|
local len = tvb:len()
|
|
local flow_risk = tvb(len-30, 8):uint64() -- UInt64 object!
|
|
local name = ""
|
|
local flow_risk_tree
|
|
|
|
for i=16,31 do
|
|
name = name .. string.char(tonumber(elems[i], 16))
|
|
end
|
|
|
|
ndpi_subtree:add(ndpi_fds.network_protocol, tvb(len-34, 2))
|
|
ndpi_subtree:add(ndpi_fds.application_protocol, tvb(len-32, 2))
|
|
|
|
flow_risk_tree = ndpi_subtree:add(ndpi_fds.flow_risk, tvb(len-30, 8))
|
|
if (flow_risk ~= UInt64(0, 0)) then
|
|
local rev_key = getstring(pinfo.dst)..":"..getstring(pinfo.dst_port).." - "..getstring(pinfo.src)..":"..getstring(pinfo.src_port)
|
|
|
|
if(flows_with_risks[rev_key] == nil) then
|
|
local key = getstring(pinfo.src)..":"..getstring(pinfo.src_port).." - "..getstring(pinfo.dst)..":"..getstring(pinfo.dst_port)
|
|
|
|
if(flows_with_risks[key] == nil) then
|
|
flows_with_risks[key] = flow_score
|
|
end
|
|
end
|
|
|
|
for i=0,63 do
|
|
if flow_risks[i] ~= nil then
|
|
-- Wireshark/Lua doesn't handle 64 bit integer very well, so we split the risk mask into two 32 bit integer values
|
|
flow_risk_tree:add(flow_risks[i], tvb(len - (i < 32 and 26 or 30), 4))
|
|
end
|
|
|
|
end
|
|
flow_risk_tree:add(flow_risks[64], tvb(len - 30, 4))
|
|
end
|
|
|
|
ndpi_subtree:add(ndpi_fds.flow_score, tvb(len-22, 2))
|
|
ndpi_subtree:add(ndpi_fds.name, tvb(len-20, 16))
|
|
|
|
if(flow_score > 0) then
|
|
local level
|
|
if(flow_score <= 10) then -- NDPI_SCORE_RISK_LOW
|
|
level = PI_NOTE
|
|
elseif(flow_score <= 50) then -- NDPI_SCORE_RISK_MEDIUM
|
|
level = PI_WARN
|
|
else
|
|
level = PI_ERROR
|
|
end
|
|
|
|
ndpi_subtree:add_expert_info(PI_MALFORMED, PI_WARN, "Non zero score")
|
|
end
|
|
|
|
if(application_protocol ~= 0) then
|
|
-- Set protocol name in the wireshark protocol column (if not Unknown)
|
|
pinfo.cols.protocol = name
|
|
--print(network_protocol .. "/" .. application_protocol .. "/".. name)
|
|
end
|
|
|
|
if(compute_flows_stats) then
|
|
ndpikey = tostring(slen(name))
|
|
|
|
if(ndpi_protos[ndpikey] == nil) then ndpi_protos[ndpikey] = 0 end
|
|
ndpi_protos[ndpikey] = ndpi_protos[ndpikey] + pinfo.len
|
|
|
|
srckey = tostring(pinfo.src)
|
|
dstkey = tostring(pinfo.dst)
|
|
|
|
flowkey = srckey.." / "..dstkey.."\t["..ndpikey.."]"
|
|
if(ndpi_flows[flowkey] == nil) then
|
|
ndpi_flows[flowkey] = 0
|
|
num_ndpi_flows = num_ndpi_flows + 1
|
|
|
|
if(num_ndpi_flows > max_num_flows) then
|
|
-- We need to harvest the flow with least packets beside this new one
|
|
local tot_removed = 0
|
|
|
|
for k,v in pairsByValues(ndpi_flows, asc) do
|
|
if(k ~= flowkey) then
|
|
ndpi_flows[k] = nil -- Remove entry
|
|
num_ndpi_flows = num_ndpi_flows + 1
|
|
if(num_ndpi_flows == (2*max_num_entries)) then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
ndpi_flows[flowkey] = ndpi_flows[flowkey] + pinfo.len
|
|
end
|
|
end
|
|
end -- nDPI
|
|
|
|
latency_dissector(tvb, pinfo, tree)
|
|
tcp_dissector(tvb, pinfo, tree)
|
|
end
|
|
|
|
-- ###########################################
|
|
|
|
-- As we do not need to add fields to the dissection
|
|
-- there is no need to process the packet multiple times
|
|
if(pinfo.visited == true) then return end
|
|
|
|
num_pkts = num_pkts + 1
|
|
if((num_pkts > 1) and (pinfo.number == 1)) then return end
|
|
|
|
if(last_processed_packet_number < pinfo.number) then
|
|
last_processed_packet_number = pinfo.number
|
|
end
|
|
|
|
-- print(num_pkts .. " / " .. pinfo.number .. " / " .. last_processed_packet_number)
|
|
|
|
if(true) then
|
|
local srckey = tostring(pinfo.src)
|
|
local dstkey = tostring(pinfo.dst)
|
|
--print("Processing packet "..pinfo.number .. "["..srckey.." / "..dstkey.."]")
|
|
end
|
|
|
|
if(dump_timeseries) then
|
|
timeseries_dissector(tvb, pinfo, tree)
|
|
end
|
|
|
|
mac_dissector(tvb, pinfo, tree)
|
|
arp_dissector(tvb, pinfo, tree)
|
|
vlan_dissector(tvb, pinfo, tree)
|
|
tls_dissector(tvb, pinfo, tree)
|
|
http_dissector(tvb, pinfo, tree)
|
|
dhcp_dissector(tvb, pinfo, tree)
|
|
dns_dissector(tvb, pinfo, tree)
|
|
rpc_dissector(tvb, pinfo, tree)
|
|
end
|
|
|
|
register_postdissector(ndpi_proto)
|
|
|
|
-- ###############################################
|
|
|
|
local function flow_score_dialog_menu()
|
|
local win = TextWindow.new("nDPI Flow Risks");
|
|
local label = ""
|
|
local i
|
|
|
|
for k,v in pairsByValues(flows_with_risks, asc) do
|
|
if(label == "") then
|
|
label = "Flows with positive score value:\n"
|
|
end
|
|
|
|
label = label .. "- " .. k .." [score: ".. v .."]\n"
|
|
end
|
|
|
|
if(label == "") then
|
|
label = "No flows with score > 0 found"
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function ndpi_dialog_menu()
|
|
local win = TextWindow.new("nDPI Protocol Statistics");
|
|
local label = ""
|
|
local i
|
|
|
|
if(ndpi_protos ~= {}) then
|
|
local tot = 0
|
|
label = "nDPI Protocol Breakdown\n"
|
|
label = label .. "-----------------------\n"
|
|
|
|
for _,v in pairs(ndpi_protos) do
|
|
tot = tot + v
|
|
end
|
|
|
|
i = 0
|
|
for k,v in pairsByValues(ndpi_protos, rev) do
|
|
local pctg = formatPctg((v * 100) / tot)
|
|
label = label .. string.format("%-32s\t\t%s\t", k, bytesToSize(v)).. "\t["..pctg.."]\n"
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
-- #######
|
|
|
|
label = label .. "\nTop nDPI Flows\n"
|
|
label = label .. "-----------\n"
|
|
i = 0
|
|
for k,v in pairsByValues(ndpi_flows, rev) do
|
|
local pctg = formatPctg((v * 100) / tot)
|
|
label = label .. string.format("%-48s\t%s", k, bytesToSize(v)).. "\t["..pctg.."]\n"
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function arp_dialog_menu()
|
|
local win = TextWindow.new("ARP Statistics");
|
|
local label = ""
|
|
local _stats
|
|
local found = false
|
|
local tot_arp_pkts = 0
|
|
|
|
_stats = {}
|
|
for k,v in pairs(arp_stats) do
|
|
if(k ~= "Broadcast") then
|
|
_stats[k] = v.request_sent + v.request_rcvd + v.response_sent + v.response_rcvd
|
|
tot_arp_pkts = tot_arp_pkts + _stats[k]
|
|
found = true
|
|
end
|
|
end
|
|
|
|
if(not found) then
|
|
label = "No ARP Traffic detected"
|
|
else
|
|
label = "Top ARP Senders/Receivers\n\nMAC Address\tTot Pkts\tPctg\tARP Breakdown\n"
|
|
i = 0
|
|
for k,v in pairsByValues(_stats, rev) do
|
|
local s = arp_stats[k]
|
|
local pctg = formatPctg((v * 100) / tot_arp_pkts)
|
|
local str = k .. "\t" .. v .. "\t" .. pctg .. "\t" .. "[sent: ".. (s.request_sent + s.response_sent) .. "][rcvd: ".. (s.request_rcvd + s.response_rcvd) .. "]\n"
|
|
label = label .. str
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function vlan_dialog_menu()
|
|
local win = TextWindow.new("VLAN Statistics");
|
|
local label = ""
|
|
local _macs
|
|
local num_hosts = 0
|
|
|
|
if(vlan_found) then
|
|
i = 0
|
|
label = "VLAN\tPackets\n"
|
|
for k,v in pairsByValues(vlan_stats, rev) do
|
|
local pctg = formatPctg((v * 100) / last_processed_packet_number)
|
|
label = label .. k .. "\t" .. v .. " pkts [".. pctg .."]\n"
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
else
|
|
label = "No VLAN traffic found"
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function ip_mac_dialog_menu()
|
|
local win = TextWindow.new("IP-MAC Statistics");
|
|
local label = ""
|
|
local _macs, _manufacturers
|
|
local num_hosts = 0
|
|
|
|
_macs = {}
|
|
_manufacturers = {}
|
|
for mac,v in pairs(mac_stats) do
|
|
local num = 0
|
|
local m = string.split(mac, "_")
|
|
local manuf
|
|
|
|
if(m == nil) then
|
|
m = string.split(mac, ":")
|
|
|
|
manuf = m[1]..":"..m[2]..":"..m[3]
|
|
else
|
|
manuf = m[1]
|
|
end
|
|
|
|
for a,b in pairs(v) do
|
|
num = num +1
|
|
end
|
|
|
|
_macs[mac] = num
|
|
if(_manufacturers[manuf] == nil) then _manufacturers[manuf] = 0 end
|
|
_manufacturers[manuf] = _manufacturers[manuf] + 1
|
|
num_hosts = num_hosts + num
|
|
end
|
|
|
|
if(num_hosts > 0) then
|
|
i = 0
|
|
label = label .. "MAC\t\t# Hosts\tPercentage\n"
|
|
for k,v in pairsByValues(_macs, rev) do
|
|
local pctg = formatPctg((v * 100) / num_hosts)
|
|
label = label .. k .. "\t" .. v .. "\t".. pctg .."\n"
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
i = 0
|
|
label = label .. "\n\nManufacturer\t# Hosts\tPercentage\n"
|
|
for k,v in pairsByValues(_manufacturers, rev) do
|
|
local pctg = formatPctg((v * 100) / num_hosts)
|
|
label = label .. k .. "\t\t" .. v .. "\t".. pctg .."\n"
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
else
|
|
label = label .. "\nIP-MAC traffic found"
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function dns_dialog_menu()
|
|
local win = TextWindow.new("DNS Statistics");
|
|
local label = ""
|
|
local tot = 0
|
|
local _dns = {}
|
|
|
|
for k,v in pairs(dns_responses_ok) do
|
|
_dns[k] = v
|
|
tot = tot + v
|
|
end
|
|
|
|
for k,v in pairs(dns_responses_error) do
|
|
if(_dns[k] == nil) then _dns[k] = 0 end
|
|
_dns[k] = _dns[k] + v
|
|
tot = tot + v
|
|
end
|
|
|
|
if(tot > 0) then
|
|
i = 0
|
|
label = label .. "DNS Server\t\t# Responses\n"
|
|
for k,v in pairsByValues(_dns, rev) do
|
|
local pctg = formatPctg((v * 100) / tot)
|
|
local ok = dns_responses_ok[k]
|
|
local err = dns_responses_error[k]
|
|
|
|
if(ok == nil) then ok = 0 end
|
|
if(err == nil) then err = 0 end
|
|
label = label .. string.format("%-20s\t%s\n", shortenString(k), v .. "\t[ok: "..ok.."][error: "..err.."][".. pctg .."]")
|
|
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
i = 0
|
|
label = label .. "\n\nTop DNS Clients\t# Queries\n"
|
|
for k,v in pairsByValues(dns_client_queries, rev) do
|
|
local pctg = formatPctg((v * 100) / tot)
|
|
label = label .. string.format("%-20s\t%s\n", shortenString(k), v .. "\t["..pctg.."]")
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
i = 0
|
|
label = label .. "\n\nTop DNS Resolvers\t# Responses\n"
|
|
for k,v in pairsByValues(dns_server_responses, rev) do
|
|
local pctg = formatPctg((v * 100) / tot)
|
|
label = label .. string.format("%-20s\t%s\n", shortenString(k), v .. "\t["..pctg.."]")
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
i = 0
|
|
label = label .. "\n\nTop DNS Queries\t\t\t# Queries\n"
|
|
for k,v in pairsByValues(top_dns_queries, rev) do
|
|
local pctg = formatPctg((v * 100) / tot)
|
|
label = label .. string.format("%-32s\t%s\n", shortenString(k,32), v .. "\t["..pctg.."]")
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
else
|
|
label = label .. "\nNo DNS traffic found"
|
|
end
|
|
|
|
win:set(label)
|
|
|
|
|
|
-- add buttons to clear text window and to enable editing
|
|
win:add_button("Clear", function() win:clear() end)
|
|
--win:add_button("Enable edit", function() win:set_editable(true) end)
|
|
|
|
-- print "closing" to stdout when the user closes the text windw
|
|
--win:set_atclose(function() print("closing") end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function rtt_dialog_menu()
|
|
local win = TextWindow.new("Network Latency");
|
|
local label = ""
|
|
local tot = 0
|
|
local i
|
|
|
|
i = 0
|
|
label = label .. "Client\t\tMin/Max RTT\n"
|
|
for k,v in pairsByValues(min_nw_client_RRT, rev) do
|
|
label = label .. string.format("%-20s\t%.3f / %.3f msec\n", shortenString(k), v, max_nw_client_RRT[k])
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
i = 0
|
|
label = label .. "\nServer\t\tMin RTT\n"
|
|
for k,v in pairsByValues(min_nw_server_RRT, rev) do
|
|
label = label .. string.format("%-20s\t%.3f / %.3f msec\n", shortenString(k), v, max_nw_server_RRT[k])
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function appl_rtt_dialog_menu()
|
|
local win = TextWindow.new("Application Latency");
|
|
local label = ""
|
|
local tot = 0
|
|
local i
|
|
|
|
i = 0
|
|
label = label .. "Server\t\tMin Application RTT\n"
|
|
for k,v in pairsByValues(min_appl_RRT, rev) do
|
|
label = label .. string.format("%-20s\t%.3f / %.3f msec\n", shortenString(k), v, max_appl_RRT[k])
|
|
if(i == max_num_entries) then break else i = i + 1 end
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function http_ua_dialog_menu()
|
|
local win = TextWindow.new("HTTP User Agent");
|
|
local label = ""
|
|
local tot = 0
|
|
local i
|
|
|
|
if(tot_http_ua_flows > 0) then
|
|
i = 0
|
|
label = label .. "Client\t\tUser Agent\n"
|
|
for k,v in pairsByKeys(http_ua, rev) do
|
|
local ips = ""
|
|
for k1,v1 in pairs(v) do
|
|
if(ips ~= "") then ips = ips .. "," end
|
|
ips = ips .. k1
|
|
end
|
|
|
|
-- label = label .. string.format("%-32s", shortenString(k,32)).."\t"..ips.."\n"
|
|
label = label .. ips.."\t"..k.."\n"
|
|
if(i == 50) then break else i = i + 1 end
|
|
end
|
|
else
|
|
label = "No HTTP User agents detected"
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function flows_ua_dialog_menu()
|
|
local win = TextWindow.new("Flows");
|
|
local label = ""
|
|
local tot = 0
|
|
local i
|
|
|
|
if(tot_flows > 0) then
|
|
i = 0
|
|
label = label .. "Flow\t\t\t\t\tA->B\tB->A\n"
|
|
for k,v in pairsByKeys(flows, rev) do
|
|
label = label .. k.."\t"..v[1].."\t"..v[2].."\n"
|
|
--label = label .. k.."\n"
|
|
if(i == 50) then break else i = i + 1 end
|
|
end
|
|
else
|
|
label = "No flows detected"
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function dhcp_dialog_menu()
|
|
local win = TextWindow.new("DHCP Fingerprinting");
|
|
local label = ""
|
|
local tot = 0
|
|
local i
|
|
local fingeprints = {
|
|
['017903060F77FC'] = 'iOS',
|
|
['017903060F77FC5F2C2E'] = 'MacOS',
|
|
['0103060F775FFC2C2E2F'] = 'MacOS',
|
|
['0103060F775FFC2C2E'] = 'MacOS',
|
|
['0603010F0C2C51452B1242439607'] = 'HP LaserJet',
|
|
['01032C06070C0F16363A3B45122B7751999A'] = 'HP LaserJet',
|
|
['0103063633'] = 'Windows',
|
|
['0103060F1F212B2C2E2F79F9FC'] = 'Windows',
|
|
['010F03062C2E2F1F2179F92B'] = 'Windows',
|
|
['0103060C0F1C2A'] = 'Linux',
|
|
['011C02030F06770C2C2F1A792A79F921FC2A'] = 'Linux',
|
|
['0102030F060C2C'] = 'Apple AirPort',
|
|
['01792103060F1C333A3B77'] = 'Android',
|
|
}
|
|
|
|
if(dhcp_fingerprints ~= {}) then
|
|
i = 0
|
|
|
|
for k,v in pairsByValues(dhcp_fingerprints, rev) do
|
|
local os = fingeprints[v]
|
|
|
|
if(os ~= nil) then
|
|
local os = " ["..os.."]"
|
|
|
|
if(i == 0) then
|
|
label = label .. "Client\t\tKnown Fingerprint\n"
|
|
end
|
|
|
|
label = label .. k.."\t"..v..os.."\n"
|
|
if(i == 50) then break else i = i + 1 end
|
|
end
|
|
end
|
|
|
|
i = 0
|
|
for k,v in pairsByValues(dhcp_fingerprints, rev) do
|
|
local os = fingeprints[v]
|
|
|
|
if(os == nil) then
|
|
if(i == 0) then
|
|
label = label .. "\n\nClient\t\tUnknown Fingerprint\n"
|
|
end
|
|
|
|
label = label .. k.."\t"..v.."\n"
|
|
if(i == 50) then break else i = i + 1 end
|
|
end
|
|
end
|
|
|
|
|
|
|
|
else
|
|
label = "No DHCP fingerprints detected"
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function tls_dialog_menu()
|
|
local win = TextWindow.new("TLS Server Contacts");
|
|
local label = ""
|
|
local tot = 0
|
|
local i
|
|
|
|
if(tot_tls_flows > 0) then
|
|
i = 0
|
|
label = label .. "TLS Server\t\t\t\t# Flows\n"
|
|
for k,v in pairsByValues(tls_server_names, rev) do
|
|
local pctg
|
|
|
|
v = tonumber(v)
|
|
pctg = formatPctg((v * 100) / tot_tls_flows)
|
|
label = label .. string.format("%-32s", shortenString(k,32)).."\t"..v.." [".. pctg.." %]\n"
|
|
if(i == 50) then break else i = i + 1 end
|
|
end
|
|
else
|
|
label = "No TLS server certificates detected"
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
local function tcp_dialog_menu()
|
|
local win = TextWindow.new("TCP Packets Analysis");
|
|
local label = ""
|
|
|
|
label = label .. "Total Retransmissions : "..num_tcp_retrans.."\n"
|
|
if(num_tcp_retrans > 0) then
|
|
i = 0
|
|
label = label .. "-----------------------------\n"
|
|
for k,v in pairsByValues(tcp_retrans, rev) do
|
|
label = label .. string.format("%-48s", shortenString(k,48)).."\t"..v.."\n"
|
|
if(i == 10) then break else i = i + 1 end
|
|
end
|
|
end
|
|
|
|
label = label .. "\nTotal Out-of-Order : "..num_tcp_ooo.."\n"
|
|
if(num_tcp_ooo > 0) then
|
|
i = 0
|
|
label = label .. "-----------------------------\n"
|
|
for k,v in pairsByValues(tcp_ooo, rev) do
|
|
label = label .. string.format("%-48s", shortenString(k,48)).."\t"..v.."\n"
|
|
if(i == 10) then break else i = i + 1 end
|
|
end
|
|
end
|
|
|
|
label = label .. "\nTotal Lost Segment : "..num_tcp_lost_segment.."\n"
|
|
if(num_tcp_lost_segment > 0) then
|
|
i = 0
|
|
label = label .. "-----------------------------\n"
|
|
for k,v in pairsByValues(tcp_lost_segment, rev) do
|
|
label = label .. string.format("%-48s", shortenString(k,48)).."\t"..v.."\n"
|
|
if(i == 10) then break else i = i + 1 end
|
|
end
|
|
end
|
|
|
|
win:set(label)
|
|
win:add_button("Clear", function() win:clear() end)
|
|
end
|
|
|
|
-- ###############################################
|
|
|
|
register_menu("ntop/ARP", arp_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/DHCP", dhcp_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/DNS", dns_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/HTTP UA", http_ua_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/Flows", flows_ua_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/IP-MAC", ip_mac_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/TLS", tls_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/TCP Analysis", tcp_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/VLAN", vlan_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/Latency/Network", rtt_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/Latency/Application", appl_rtt_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
|
|
-- ###############################################
|
|
|
|
if(compute_flows_stats) then
|
|
register_menu("ntop/nDPI", ndpi_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
register_menu("ntop/nDPI Flow Score", flow_score_dialog_menu, MENU_TOOLS_UNSORTED)
|
|
end
|