aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Deri <deri@ntop.org>2017-05-23 23:31:43 +0200
committerLuca Deri <deri@ntop.org>2017-05-23 23:31:43 +0200
commitc723f7e668b78894896d7afbfb4f9ec894e8c485 (patch)
treebaabbdaa003e9929097f4da66b82d8a49144a065
parent1a20029c8254f53e00850667c1cb531347c1690f (diff)
Added VLAN / ARP stats
-rw-r--r--wireshark/ndpi.lua397
1 files changed, 340 insertions, 57 deletions
diff --git a/wireshark/ndpi.lua b/wireshark/ndpi.lua
index 789b46f54..1a7c400a8 100644
--- a/wireshark/ndpi.lua
+++ b/wireshark/ndpi.lua
@@ -11,7 +11,7 @@ local fds = ndpi_proto.fields
fds.network_protocol = ProtoField.new("nDPI Network Protocol", "ndpi.protocol.network", ftypes.UINT8, nil, base.DEC)
fds.application_protocol = ProtoField.new("nDPI Application Protocol", "ndpi.protocol.application", ftypes.UINT8, nil, base.DEC)
-fds.name = ProtoField.new("nDPI Protocol Name", "ndpi.protocol.name", ftypes.STRING)
+fds.name = ProtoField.new("nDPI Protocol Name", "ndpi.protocol.name", ftypes.STRING)
local f_eth_trailer = Field.new("eth.trailer")
@@ -19,6 +19,10 @@ local ndpi_protos = {}
local ndpi_flows = {}
local num_ndpi_flows = 0
+local arp_stats = {}
+local vlan_stats = {}
+local vlan_found = false
+
local lower_ndpi_flow_id = 0
local lower_ndpi_flow_volume = 0
@@ -26,9 +30,53 @@ local compute_flows_stats = true
local max_num_entries = 10
local max_num_flows = 50
+local num_pkts = 0
+local last_processed_packet_number = 0
+
+local debug = false
+
+-- ##############################################
+
+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 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
+
+-- ###############################################
-- Convert bytes to human readable format
function bytesToSize(bytes)
@@ -44,13 +92,13 @@ function bytesToSize(bytes)
bytes = tonumber(bytes)
if((bytes >= 0) and (bytes < kilobyte)) then
return round(bytes, precision) .. " Bytes";
- elseif((bytes >= kilobyte) and (bytes < megabyte)) then
+ elseif((bytes >= kilobyte) and (bytes < megabyte)) then
return round(bytes / kilobyte, precision) .. ' KB';
- elseif((bytes >= megabyte) and (bytes < gigabyte)) then
+ elseif((bytes >= megabyte) and (bytes < gigabyte)) then
return round(bytes / megabyte, precision) .. ' MB';
- elseif((bytes >= gigabyte) and (bytes < terabyte)) then
+ elseif((bytes >= gigabyte) and (bytes < terabyte)) then
return round(bytes / gigabyte, precision) .. ' GB';
- elseif(bytes >= terabyte) then
+ elseif(bytes >= terabyte) then
return round(bytes / terabyte, precision) .. ' TB';
else
return round(bytes, precision) .. ' Bytes';
@@ -58,6 +106,8 @@ function bytesToSize(bytes)
end
end
+-- ###############################################
+
function pairsByValues(t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
@@ -72,21 +122,68 @@ function pairsByValues(t, f)
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 = {}
+ 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 = { }
+
+ -- VLAN
+ vlan_stats = { }
+ vlan_found = false
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)
@@ -94,69 +191,213 @@ function slen(str)
len = len + 1
else
break
- end
+ 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)
+ local mac
+
+ -- print(num_pkts)
+ 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
+
+-- ###############################################
+
-- the dissector function callback
function ndpi_proto.dissector(tvb, pinfo, tree)
- local pktlen = tvb:len()
- local eth_trailer = f_eth_trailer()
- local magic = tostring(tvb(pktlen-28,4))
-
- if(magic == "19680924") then
- local ndpi_subtree = tree:add(ndpi_proto, tvb(), "nDPI Protocol")
- local network_protocol = tvb(pktlen-24,2)
- local application_protocol = tvb(pktlen-22,2)
- local name = tvb(pktlen-20,16)
- local name_str = name:string(ENC_ASCII)
- local ndpikey, srckey, dstkey, flowkey
-
- ndpi_subtree:add(fds.network_protocol, network_protocol)
- ndpi_subtree:add(fds.application_protocol, application_protocol)
- ndpi_subtree:add(fds.name, name)
-
- local pname = ""..application_protocol
- if(pname ~= "0000") then
- -- Set protocol name in the wireshark protocol column (if not Unknown)
- pinfo.cols.protocol = name_str
+ -- 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 == false) 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)
+
+ -- ############# ARP / VLAN #############
+ local offset = 12
+ local eth_proto = tostring(tvb(offset,2))
+
+ if(eth_proto == "8100") then
+ local vlan_id = BitAND(tonumber(tostring(tvb(offset+2,2))), 0xFFF)
+
+ 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
+
+ while(eth_proto == "8100") do
+ offset = offset + 4
+ eth_proto = tostring(tvb(offset,2))
+ end
+
+ if(eth_proto == "0806") then
+ -- ARP
+ local isRequest = tonumber(tvb(21,1))
+ --print(eth_proto.." ["..tostring(pinfo.dl_src).." / ".. tostring(pinfo.dl_dst) .."] [" .. tostring(pinfo.src).." -> "..tostring(pinfo.dst).."]")
+ dissectARP(isRequest, tostring(pinfo.dl_src), tostring(pinfo.dl_dst))
+ else
+ -- ############# 2 nDPI Dissection #############
+
+ if(false) then
+ local srckey = tostring(pinfo.src)
+ local dstkey = tostring(pinfo.dst)
+ print("Processing packet "..pinfo.number .. "["..srckey.." / "..dstkey.."]")
end
- if(compute_flows_stats) then
- ndpikey = tostring(slen(name_str))
-
- 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.." ["..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
- table.remove(ndpi_flows, k)
- tot_removed = tot_removed + 1
- if(tot_removed == max_num_entries) then
- break
+ local pktlen = tvb:len()
+ local eth_trailer = f_eth_trailer()
+ local magic = tostring(tvb(pktlen-28,4))
+
+ if(magic == "19680924") then
+ local ndpi_subtree = tree:add(ndpi_proto, tvb(), "nDPI Protocol")
+ local network_protocol = tvb(pktlen-24,2)
+ local application_protocol = tvb(pktlen-22,2)
+ local name = tvb(pktlen-20,16)
+ local name_str = name:string(ENC_ASCII)
+ local ndpikey, srckey, dstkey, flowkey
+
+ ndpi_subtree:add(fds.network_protocol, network_protocol)
+ ndpi_subtree:add(fds.application_protocol, application_protocol)
+ ndpi_subtree:add(fds.name, name)
+
+ local pname = ""..application_protocol
+ if(pname ~= "0000") then
+ -- Set protocol name in the wireshark protocol column (if not Unknown)
+ pinfo.cols.protocol = name_str
+ end
+
+ if(compute_flows_stats) then
+ ndpikey = tostring(slen(name_str))
+
+ 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.." ["..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
+ table.remove(ndpi_flows, k)
+ tot_removed = tot_removed + 1
+ if(tot_removed == max_num_entries) then
+ break
+ end
end
end
- end
+ end
end
+
+ ndpi_flows[flowkey] = ndpi_flows[flowkey] + pinfo.len
end
+ end -- nDPI
+
+ if(debug) then
+ local fields = { }
+ local _fields = { all_field_infos() }
+
+ fields['pinfo.number'] = pinfo.number
- ndpi_flows[flowkey] = ndpi_flows[flowkey] + pinfo.len
+ for k,v in pairs(_fields) do
+ local value = getstring(v)
+ if(value ~= nil) then
+ fields[v.name] = value
+ end
+ end
+
+ for k,v in pairs(fields) do
+ print(k.." = "..v)
+ end
end
end
end
@@ -165,7 +406,6 @@ register_postdissector(ndpi_proto)
-- ###############################################
-
local function ndpi_dialog_menu()
local win = TextWindow.new("nDPI Protocol Statistics");
local label = ""
@@ -196,6 +436,49 @@ local function ndpi_dialog_menu()
end
end
+-- ###############################################
+
if(compute_flows_stats) then
register_menu("nDPI", ndpi_dialog_menu, MENU_STAT_UNSORTED)
end
+
+-- ###############################################
+
+local function arp_dialog_menu()
+ local win = TextWindow.new("ARP Statistics");
+ local label
+ local _stats
+ local found = false
+
+ _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
+ 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"
+ for k,v in pairsByValues(_stats, rev) do
+ local s = arp_stats[k]
+ local pctg = formatPctg((v * 100) / last_processed_packet_number)
+ 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
+ end
+ end
+
+ if(vlan_found) then
+ label = label .. "\n\nVLAN\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"
+ end
+ end
+
+ win:set(label)
+end
+
+register_menu("ARP / VLAN", arp_dialog_menu, MENU_STAT_UNSORTED)