summaryrefslogtreecommitdiff
path: root/src/main/lua/pcap
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/lua/pcap')
-rw-r--r--src/main/lua/pcap/KubeProbeFilter.lua93
-rw-r--r--src/main/lua/pcap/extractDnsHosts.lua147
-rw-r--r--src/main/lua/pcap/httpStats.lua117
-rw-r--r--src/main/lua/pcap/tcpDataAmountStats.lua97
-rw-r--r--src/main/lua/pcap/tcpPortStats.lua82
-rw-r--r--src/main/lua/pcap/xServiceStats.lua90
6 files changed, 626 insertions, 0 deletions
diff --git a/src/main/lua/pcap/KubeProbeFilter.lua b/src/main/lua/pcap/KubeProbeFilter.lua
new file mode 100644
index 0000000..a5967e9
--- /dev/null
+++ b/src/main/lua/pcap/KubeProbeFilter.lua
@@ -0,0 +1,93 @@
+--
+-- Try to extract kube-probe related requests.
+--
+
+local newPcapParser = assert(require("pcapit").newPcapParser)
+local newPcapDumper = assert(require("pcapit").newPcapDumper)
+
+local out, log = io.stdout, io.stderr
+local main, onPcapFrame, vapourizeUrlVariables
+
+
+function onPcapFrame( app, it )
+ local srcPort, dstPort = it:trspSrcPort(), it:trspDstPort()
+ local userAgent, reqUri
+ --
+ if dstPort ~= 7012 and srcPort ~= 7012 then return end
+ local trspPayload = it:trspPayload()
+ local httpReqLinePart1, httpReqLinePart2, httpReqLinePart3 =
+ trspPayload:match("^([A-Z/1.0]+) ([^ ]+) ([^ \r\n]+)\r?\n")
+ if httpReqLinePart1 and not httpReqLinePart1:find("^HTTP/1.%d$") then -- assume HTTP request
+ reqUri = httpReqLinePart2
+ userAgent = trspPayload:match("\n[Uu][Ss][Ee][Rr]%-[Aa][Gg][Ee][Nn][Tt]:%s+([^\r\n]+)\r?\n");
+ if userAgent then
+ --if not userAgent:find("^kube%-probe/") then return end -- assume halfrunt
+ --log:write("User-Agent: ".. userAgent .."\n")
+ end
+ elseif httpReqLinePart1 then -- assume HTTP response
+ --out:write(trspPayload)
+ end
+ local srcIp, dstIp = it:netSrcIpStr(), it:netDstIpStr()
+ local connKey = ((srcPort < dstPort)and(srcPort.."\0"..dstPort)or(dstPort.."\0"..srcPort))
+ .."\0"..((srcIp < dstIp)and(srcIp.."\0"..dstIp)or(dstIp.."\0"..srcIp))
+ local conn = app.connections[connKey]
+ if not conn then conn = {isOfInterest=false, pkgs={}} app.connections[connKey] = conn end
+ conn.isOfInterest = (conn.isOfInterest or reqUri == "/houston/server/info")
+ if not conn.isOfInterest then
+ if #conn.pkgs > 3 then -- Throw away all stuff except TCP handshake
+ conn.pkgs = { conn.pkgs[1], conn.pkgs[2], conn.pkgs[3] }
+ end
+ local sec, usec = it:frameArrivalTime()
+ --for k,v in pairs(getmetatable(it))do print("E",k,v)end
+ local pkg = {
+ sec = assert(sec), usec = assert(usec),
+ caplen = it:frameCaplen(), len = it:frameLen(),
+ tcpFlags = (conn.isOfInterest)and(it:tcpFlags())or false,
+ srcPort = srcPort, dstPort = dstPort,
+ trspPayload = trspPayload,
+ rawFrame = it:rawFrame(),
+ }
+ table.insert(conn.pkgs, pkg)
+ else
+ -- Stop memory hogging. Write that stuff to output
+ if #conn.pkgs > 0 then
+ for _, pkg in ipairs(conn.pkgs) do
+ --out:write(string.format("-- PKG 1 %d->%d %d.%09d tcpFlg=0x%04X\n", pkg.srcPort, pkg.dstPort, pkg.sec, pkg.usec, pkg.tcpFlags or 0))
+ --out:write(pkg.trspPayload)
+ --out:write("\n")
+ app.dumper:dump(pkg.sec, pkg.usec, pkg.caplen, pkg.len, pkg.rawFrame, 1, pkg.rawFrame:len())
+ end
+ conn.pkgs = {}
+ end
+ local tcpFlags = it:tcpFlags()
+ local sec, usec = it:frameArrivalTime()
+ local rawFrame = it:rawFrame()
+ --out:write(string.format("-- PKG 2 %d->%d %d.%09d tcpFlg=0x%04X, len=%d\n", srcPort, dstPort, sec, usec, tcpFlags or 0, trspPayload:len()))
+ --out:write(trspPayload)
+ --if trspPayload:byte(trspPayload:len()) ~= 0x0A then out:write("\n") end
+ --out:write("\n")
+ app.dumper:dump(sec, usec, it:frameCaplen(), it:frameLen(), rawFrame, 1, rawFrame:len())
+ end
+end
+
+
+function main()
+ local app = {
+ parser = false,
+ dumper = false,
+ connections = {},
+ }
+ app.parser = newPcapParser{
+ dumpFilePath = "-",
+ onFrame = function(f)onPcapFrame(app, f)end,
+ }
+ app.dumper = newPcapDumper{
+ dumpFilePath = "C:/work/tmp/KubeProbeFilter.out.pcap",
+ }
+ app.parser:resume()
+end
+
+
+main()
+
+
diff --git a/src/main/lua/pcap/extractDnsHosts.lua b/src/main/lua/pcap/extractDnsHosts.lua
new file mode 100644
index 0000000..655586f
--- /dev/null
+++ b/src/main/lua/pcap/extractDnsHosts.lua
@@ -0,0 +1,147 @@
+
+local newPcapParser = assert(require("pcapit").newPcapParser)
+local out, log = io.stdout, io.stderr
+
+local main, onPcapFrame, vapourizeUrlVariables, printResult
+
+
+function main()
+ local app = {
+ parser = false,
+ youngestEpochSec = -math.huge,
+ oldestEpochSec = math.huge,
+ dnsResponses = {},
+ }
+ app.parser = newPcapParser{
+ dumpFilePath = "-",
+ onFrame = function(f)onPcapFrame(app, f)end,
+ }
+ app.parser:resume()
+ printResult(app)
+end
+
+
+function onPcapFrame( app, it )
+ local out = io.stdout
+ local sec, usec = it:frameArrivalTime()
+ sec = sec + (usec/1e6)
+ if sec < app.oldestEpochSec then app.oldestEpochSec = sec end
+ if sec > app.youngestEpochSec then app.youngestEpochSec = sec end
+ --
+ if it:trspSrcPort() == 53 then
+ extractHostnameFromDns(app, it)
+ elseif it:tcpSeqNr() then
+ extractHostnameFromHttpHeaders(app, it)
+ end
+end
+
+
+function extractHostnameFromDns( app, it )
+ local payload = it:trspPayload()
+ local bug = 8 -- TODO looks as lib has a bug and payload is offset by some bytes.
+ local dnsFlags = (payload:byte(bug+3) << 8) | (payload:byte(bug+4))
+ if (dnsFlags & 0x0004) ~= 0 then return end -- ignore error responses
+ local numQuestions = payload:byte(bug+5) << 8 | payload:byte(bug+6)
+ local numAnswers = payload:byte(bug+7) << 8 | payload:byte(bug+8)
+ if numQuestions ~= 1 then
+ log:write("[WARN ] numQuestions ".. numQuestions .."?!?\n")
+ return
+ end
+ if numAnswers == 0 then return end -- empty answers are boring
+ if numAnswers ~= 1 then log:write("[WARN ] dns.count.answers ".. numAnswers .." not supported\n") return end
+ local questionsOffset = bug+13
+ local hostname = payload:match("^([^\0]+)", questionsOffset)
+ hostname = hostname:gsub("^[\r\n]", "") -- TODO WTF?!?
+ hostname = hostname:gsub("[\x04\x02]", ".") -- TODO WTF?!?
+ local answersOffset = bug + 13 + (24 * numQuestions)
+ local ttl = payload:byte(answersOffset+6) << 24 | payload:byte(answersOffset+7) << 16
+ | payload:byte(answersOffset+8) << 8 | payload:byte(answersOffset+9)
+ local dataLen = payload:byte(answersOffset+10) | payload:byte(answersOffset+11)
+ if dataLen ~= 4 then log:write("[WARN ] dns.resp.len ".. dataLen .." not impl\n") return end
+ local ipv4Str = string.format("%d.%d.%d.%d", payload:byte(answersOffset+12), payload:byte(answersOffset+13),
+ payload:byte(answersOffset+14), payload:byte(answersOffset+15))
+ --
+ addEntry(app, ipv4Str, hostname, ttl)
+end
+
+
+function extractHostnameFromHttpHeaders( app, it )
+ local payload = it:trspPayload()
+ local _, beg = payload:find("^([A-Z]+ [^ \r\n]+ HTTP/1%.%d\r?\n)")
+ if not beg then return end
+ beg = beg + 1
+ local httpHost
+ while true do
+ local line
+ local f, t = payload:find("^([^\r\n]+)\r?\n", beg)
+ if not f then return end
+ if not payload:byte(1) == 0x72 or payload:byte(1) == 0x68 then goto nextHdr end
+ line = payload:sub(f, t)
+ httpHost = line:match("^[Hh][Oo][Ss][Tt]:%s*([^\r\n]+)\r?\n$")
+ if not httpHost then goto nextHdr end
+ break
+ ::nextHdr::
+ beg = t
+ end
+ httpHost = httpHost:gsub("^(.+):%d+$", "%1")
+ local dstIp = it:netDstIpStr()
+ if dstIp == httpHost then return end
+ addEntry(app, dstIp, httpHost, false, "via http host header")
+end
+
+
+function addEntry( app, ipv4Str, hostname, ttl, kludge )
+ local key
+ --log:write("addEntry(app, ".. ipv4Str ..", ".. hostname ..")\n")
+ if kludge == "via http host header" then
+ key = ipv4Str .."\0".. hostname .."\0".. "via http host header"
+ else
+ key = ipv4Str .."\0".. hostname .."\0".. ttl
+ end
+ local entry = app.dnsResponses[key]
+ if not entry then
+ entry = { ipv4Str = ipv4Str, hostname = hostname, ttl = ttl, }
+ app.dnsResponses[key] = entry
+ end
+end
+
+
+function printResult( app )
+ local sorted = {}
+ for _, stream in pairs(app.dnsResponses) do
+ table.insert(sorted, stream)
+ end
+ table.sort(sorted, function(a, b)
+ if a.ipv4Str < b.ipv4Str then return true end
+ if a.ipv4Str > b.ipv4Str then return false end
+ return a.hostname < b.hostname
+ end)
+ local dumpDurationSec = app.youngestEpochSec - app.oldestEpochSec
+ local timeFmt = "!%Y-%m-%d_%H:%M:%SZ"
+ out:write("\n")
+ out:write(string.format("# Subject Hostname to IP addresses\n"))
+ out:write(string.format("# Begin %s\n", os.date(timeFmt, math.floor(app.oldestEpochSec))))
+ out:write(string.format("# Duration %.3f seconds\n", dumpDurationSec))
+ out:write("\n")
+ --out:write(" .-- KiB per Second\n")
+ --out:write(" | .-- IP endpoints\n")
+ --out:write(" | | .-- TCP server port\n")
+ --out:write(" | | | .-- TCP Payload (less is better)\n")
+ --out:write(" | | | |\n")
+ --out:write(".--+----. .----+----------------------. .+--. .-+------------\n")
+ for i, elem in ipairs(sorted) do
+ local ipv4Str, hostname, ttl = elem.ipv4Str, elem.hostname, elem.ttl
+ if ttl then
+ out:write(string.format("%-14s %-30s # TTL=%ds", ipv4Str, hostname, ttl))
+ else
+ out:write(string.format("%-14s %-30s # ", ipv4Str, hostname))
+ end
+ out:write("\n")
+ end
+ out:write("\n")
+end
+
+
+main()
+
+
diff --git a/src/main/lua/pcap/httpStats.lua b/src/main/lua/pcap/httpStats.lua
new file mode 100644
index 0000000..ff48bd2
--- /dev/null
+++ b/src/main/lua/pcap/httpStats.lua
@@ -0,0 +1,117 @@
+
+local newPcapParser = assert(require("pcapit").newPcapParser)
+
+local out, log = io.stdout, io.stderr
+local main, onPcapFrame, vapourizeUrlVariables, printHttpRequestStats
+
+
+function main()
+ local app = {
+ parser = false,
+ youngestEpochSec = -math.huge,
+ oldestEpochSec = math.huge,
+ foundHttpRequests = {},
+ }
+ app.parser = newPcapParser{
+ dumpFilePath = "-",
+ onFrame = function(f)onPcapFrame(app, f)end,
+ }
+ app.parser:resume()
+ printHttpRequestStats(app)
+end
+
+
+function onPcapFrame( app, it )
+ local sec, usec = it:frameArrivalTime()
+ local srcPort, dstPort = it:trspSrcPort(), it:trspDstPort()
+ --
+ if sec < app.oldestEpochSec then app.oldestEpochSec = sec end
+ if sec > app.youngestEpochSec then app.youngestEpochSec = sec end
+ --
+ local portOfInterest = 7012
+ if dstPort == portOfInterest then
+ local httpMethod, httpUri =
+ it:trspPayload():match("^([A-Z]+) ([^ ]+) [^ \r\n]+\r?\n")
+ if httpMethod then
+ --out:write(string.format("%5d->%5d %s %s\n", srcPort, dstPort, httpMethod, httpUri))
+ httpUri = vapourizeUrlVariables(app, httpUri)
+ local key = httpUri -- httpMethod .." ".. httpUri
+ local obj = app.foundHttpRequests[key]
+ if not obj then
+ obj = { count=0, httpMethod=false, httpUri=false, }
+ app.foundHttpRequests[key] = obj
+ end
+ obj.count = obj.count + 1
+ obj.httpMethod = httpMethod
+ obj.httpUri = httpUri
+ end
+ elseif srcPort == portOfInterest then
+ local httpStatus, httpPhrase =
+ it:trspPayload():match("^HTTP/%d.%d (%d%d%d) ([^\r\n]*)\r?\n")
+ if httpStatus then
+ --out:write(string.format("%5d<-%5d %s %s\n", srcPort, dstPort, httpStatus, httpPhrase))
+ end
+ end
+end
+
+
+function vapourizeUrlVariables( app, uri )
+ -- A very specific case
+ uri = uri:gsub("^(/houston/users/)%d+(/.*)$", "%1{}%2");
+ if uri:find("^/houston/users/[^/]+/user/.*$") then return uri end
+ --
+ -- Try to do some clever guesses to group URIs wich only differ in variable segments
+ uri = uri:gsub("(/|-)[%dI_-]+/", "%1{}/"):gsub("(/|-)[%dI-]+/", "%1{}/") -- two turns, to also get consecutive number segments
+ uri = uri:gsub("([/-])[%dI_-]+$", "%1{}")
+ uri = uri:gsub("/%d+(%.%w+)$", "/{}%1")
+ uri = uri:gsub("(/|-)[%w%d]+%-[%w%d]+%-[%w%d]+%-[%w%d]+%-[%w%d]+(/?)$", "%1{}%2")
+ uri = uri:gsub("/v%d/", "/v0/") -- Merge all API versions
+ --
+ -- Generify remaining by trimming URIs from right
+ uri = uri:gsub("^(/from%-houston/[^/]+/eagle/nsync/).*$", "%1...")
+ uri = uri:gsub("^(/from%-houston/[^/]+/eagle/fis/information/).*$", "%1...")
+ uri = uri:gsub("^(/from%-houston/[^/]+/eagle/nsync/v%d/push/trillian%-phonebooks%-).*$", "%1...")
+ uri = uri:gsub("^(/from%-houston/[^/]+/eagle/timetable/wait/).*$", "%1...")
+ uri = uri:gsub("^(/houston/service%-instances/).*$", "%1...")
+ uri = uri:gsub("^(/vortex/stillInterested%?vehicleId%=).*$", "%1...")
+ uri = uri:gsub("^(/houston/[^/]+/[^/]+/).*$", "%1...")
+ return uri
+end
+
+
+function printHttpRequestStats( app )
+ local sorted = {}
+ local maxOccurValue = 0
+ local overallCount = 0
+ for _, reqObj in pairs(app.foundHttpRequests) do
+ if reqObj.count > maxOccurValue then maxOccurValue = reqObj.count end
+ overallCount = overallCount + reqObj.count
+ table.insert(sorted, reqObj)
+ end
+ table.sort(sorted, function(a, b)return a.count > b.count end)
+ local dumpDurationSec = app.youngestEpochSec - app.oldestEpochSec
+ local timeFmt = "!%Y-%m-%d_%H:%M:%SZ"
+ out:write("\n")
+ out:write(string.format(" Subject HTTP Request Statistics\n"))
+ out:write(string.format(" Begin %s\n", os.date(timeFmt,app.oldestEpochSec)))
+ out:write(string.format(" Duration %d seconds\n", dumpDurationSec))
+ out:write(string.format("Throughput %.1f HTTP requests per second\n", overallCount / dumpDurationSec))
+ out:write("\n")
+ out:write(" .-- HTTP Requests per Second\n")
+ out:write(" | .-- URI\n")
+ out:write(".--+--. .-+---------\n")
+ local chartWidth = 60
+ local cntPrinted = 0
+ for i, elem in ipairs(sorted) do
+ local count, httpMethod, httpUri = elem.count, elem.httpMethod, elem.httpUri
+ local cntPerSec = math.floor((count / dumpDurationSec)*10+.5)/10
+ out:write(string.format("%7.1f %s\n", cntPerSec, httpUri))
+ cntPrinted = cntPrinted + 1
+ ::nextPort::
+ end
+ out:write("\n")
+end
+
+
+main()
+
diff --git a/src/main/lua/pcap/tcpDataAmountStats.lua b/src/main/lua/pcap/tcpDataAmountStats.lua
new file mode 100644
index 0000000..496687a
--- /dev/null
+++ b/src/main/lua/pcap/tcpDataAmountStats.lua
@@ -0,0 +1,97 @@
+
+local newPcapParser = assert(require("pcapit").newPcapParser)
+
+local main, onPcapFrame, vapourizeUrlVariables, printResult
+
+
+function main()
+ local app = {
+ parser = false,
+ youngestEpochSec = -math.huge,
+ oldestEpochSec = math.huge,
+ nextStreamNr = 1,
+ httpStreams = {},
+ }
+ app.parser = newPcapParser{
+ dumpFilePath = "-",
+ onFrame = function(f)onPcapFrame(app, f)end,
+ }
+ app.parser:resume()
+ printResult(app)
+end
+
+
+function onPcapFrame( app, it )
+ local out = io.stdout
+ --
+ if not it:tcpSeqNr() then return end
+ --
+ --
+ local sec, usec = it:frameArrivalTime()
+ if sec < app.oldestEpochSec then app.oldestEpochSec = sec end
+ if sec > app.youngestEpochSec then app.youngestEpochSec = sec end
+ --
+ local srcIp, dstIp = it:netSrcIpStr(), it:netDstIpStr()
+ local srcPort, dstPort = it:trspSrcPort(), it:trspDstPort()
+ local lowIp = (srcIp < dstIp)and(srcIp)or(dstIp)
+ local higIp = (lowIp == dstIp)and(srcIp)or(dstIp)
+ local lowPort = math.min(srcPort, dstPort)
+ local streamId = lowIp .."\0".. higIp .."\0".. lowPort
+ local stream = app.httpStreams[streamId]
+ if not stream then
+ stream = {
+ srcIp = srcIp, dstIp = dstIp, srcPort = srcPort, dstPort = dstPort,
+ streamNr = app.nextStreamNr, numBytes = 0,
+ }
+ app.nextStreamNr = app.nextStreamNr + 1
+ app.httpStreams[streamId] = stream
+ end
+ local trspPayload = it:trspPayload()
+ stream.numBytes = stream.numBytes + trspPayload:len()
+end
+
+
+function printResult( app )
+ local out = io.stdout
+ local sorted = {}
+ local overalValue, maxValue = 0, 0
+ for _, stream in pairs(app.httpStreams) do
+ if stream.numBytes > maxValue then maxValue = stream.numBytes end
+ overalValue = overalValue + stream.numBytes
+ table.insert(sorted, stream)
+ end
+ table.sort(sorted, function(a, b)return a.numBytes > b.numBytes end)
+ local dumpDurationSec = app.youngestEpochSec - app.oldestEpochSec
+ local overallBytesPerSec = overalValue / dumpDurationSec
+ local maxValuePerSec = maxValue / dumpDurationSec
+ local timeFmt = "!%Y-%m-%d_%H:%M:%SZ"
+ out:write("\n")
+ out:write(string.format(" Subject TCP data throughput\n"))
+ out:write(string.format(" Begin %s\n", os.date(timeFmt,app.oldestEpochSec)))
+ out:write(string.format(" Duration %d seconds\n", dumpDurationSec))
+ out:write(string.format(" Overall %.3f KiB per second (%.3f KiBit per second)\n",
+ overallBytesPerSec/1024, overallBytesPerSec/1024*8))
+ out:write("\n")
+ out:write(" .-- KiB per Second\n")
+ out:write(" | .-- IP endpoints\n")
+ out:write(" | | .-- TCP server port\n")
+ out:write(" | | | .-- TCP Payload (less is better)\n")
+ out:write(" | | | |\n")
+ out:write(".--+----. .----+----------------------. .+--. .-+------------\n")
+ local bar = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
+ for i, elem in ipairs(sorted) do
+ local streamNr, srcIp, dstIp, srcPort, dstPort, numBytes =
+ elem.streamNr, elem.srcIp, elem.dstIp, elem.srcPort, elem.dstPort, elem.numBytes
+ local lowPort = math.min(srcPort, dstPort)
+ local bytesPerSecond = math.floor((numBytes / dumpDurationSec)*10+.5)/10
+ out:write(string.format("%9.3f %-14s %-14s %5d ", bytesPerSecond/1024, srcIp, dstIp, lowPort))
+ local part = bytesPerSecond / maxValuePerSec;
+ out:write(bar:sub(0, math.floor(part * bar:len())))
+ out:write("\n")
+ end
+ out:write("\n")
+end
+
+
+main()
+
diff --git a/src/main/lua/pcap/tcpPortStats.lua b/src/main/lua/pcap/tcpPortStats.lua
new file mode 100644
index 0000000..9038db7
--- /dev/null
+++ b/src/main/lua/pcap/tcpPortStats.lua
@@ -0,0 +1,82 @@
+
+local newPcapParser = assert(require("pcapit").newPcapParser)
+
+local out, log = io.stdout, io.stderr
+local main, onPcapFrame, printStats
+
+
+function main()
+ local app = {
+ parser = false,
+ youngestEpochSec = -math.huge,
+ oldestEpochSec = math.huge,
+ foundPortNumbers = {},
+ }
+ app.parser = newPcapParser{
+ dumpFilePath = "-",
+ onFrame = function(f)onPcapFrame(app, f)end,
+ }
+ app.parser:resume()
+ printStats(app)
+end
+
+
+function onPcapFrame( app, it )
+ local sec, usec = it:frameArrivalTime()
+ local srcPort, dstPort = it:trspSrcPort(), it:trspDstPort()
+ --local srcIp, dstIp = it:netSrcIpStr(), it:netDstIpStr()
+ --local isTcp = (it:tcpSeqNr() ~= nil)
+ --
+ if sec < app.oldestEpochSec then app.oldestEpochSec = sec end
+ if sec > app.youngestEpochSec then app.youngestEpochSec = sec end
+ --
+ if not app.foundPortNumbers[srcPort] then app.foundPortNumbers[srcPort] = 1
+ else app.foundPortNumbers[srcPort] = app.foundPortNumbers[srcPort] + 1 end
+ if not app.foundPortNumbers[dstPort+100000] then app.foundPortNumbers[dstPort+100000] = 1
+ else app.foundPortNumbers[dstPort+100000] = app.foundPortNumbers[dstPort+100000] + 1 end
+end
+
+
+function printStats( app )
+ local sorted = {}
+ local totalPackets, maxOccurValue = 0, 0
+ for port, pkgcnt in pairs(app.foundPortNumbers) do
+ if pkgcnt > maxOccurValue then maxOccurValue = pkgcnt end
+ table.insert(sorted, { port=port, pkgcnt=pkgcnt })
+ totalPackets = totalPackets + pkgcnt
+ end
+ table.sort(sorted, function(a, b)return a.pkgcnt > b.pkgcnt end)
+ local dumpDurationSec = app.youngestEpochSec - app.oldestEpochSec
+ local timeFmt = "!%Y-%m-%d_%H:%M:%SZ"
+ out:write("\n")
+ out:write(string.format(" Subject TCP/UDP stats\n"))
+ out:write(string.format(" Begin %s\n", os.date(timeFmt,app.oldestEpochSec)))
+ out:write(string.format(" Duration %d seconds\n", dumpDurationSec))
+ out:write(string.format("Throughput %.1f packets per second\n", totalPackets / dumpDurationSec))
+ out:write("\n")
+ out:write(" .- TCP/UDP Port\n")
+ out:write(" | .-Direction (Send, Receive)\n")
+ out:write(" | | .- Packets per second\n")
+ out:write(".-+-. | .---+-.\n")
+ local chartWidth = 60
+ for i, elem in ipairs(sorted) do
+ local port, pkgcnt = elem.port, elem.pkgcnt
+ local dir = (port > 100000)and("R")or("S")
+ if port > 100000 then port = port - 100000 end
+ if port > 30000 then goto nextPort end
+ local pkgsPerSec = math.floor((pkgcnt / dumpDurationSec)*10+.5)/10
+ out:write(string.format("%5d %s %7.1f |", port, dir, pkgsPerSec))
+ local barLen = pkgcnt / maxOccurValue
+ --local barLen = (math.log(pkgcnt) / math.log(maxOccurValue))
+ for i=1, chartWidth-1 do
+ out:write((i < (barLen*chartWidth))and("=")or(" "))
+ end
+ out:write("|\n")
+ ::nextPort::
+ end
+ out:write("\n")
+end
+
+
+main()
+
diff --git a/src/main/lua/pcap/xServiceStats.lua b/src/main/lua/pcap/xServiceStats.lua
new file mode 100644
index 0000000..3bc94a4
--- /dev/null
+++ b/src/main/lua/pcap/xServiceStats.lua
@@ -0,0 +1,90 @@
+
+local newPcapParser = assert(require("pcapit").newPcapParser)
+
+local out, log = io.stdout, io.stderr
+local main, onPcapFrame, vapourizeUrlVariables, printStats
+
+
+function main()
+ local app = {
+ parser = false,
+ youngestEpochSec = -math.huge,
+ oldestEpochSec = math.huge,
+ services = {},
+ }
+ app.parser = newPcapParser{
+ dumpFilePath = "-",
+ onFrame = function(f)onPcapFrame(app, f)end,
+ }
+ app.parser:resume()
+ printStats(app)
+end
+
+
+function onPcapFrame( app, it )
+ local sec, usec = it:frameArrivalTime()
+ local srcPort, dstPort = it:trspSrcPort(), it:trspDstPort()
+ --
+ if sec < app.oldestEpochSec then app.oldestEpochSec = sec end
+ if sec > app.youngestEpochSec then app.youngestEpochSec = sec end
+ --
+ local portsOfInterest = {
+ [ 80] = true,
+ [8080] = true,
+ [7012] = true,
+ }
+ --if not portsOfInterest[dstPort] and not portsOfInterest[srcPort] then return end
+ local trspPayload = it:trspPayload()
+ local httpReqLinePart1, httpReqLinePart2, httpReqLinePart3 =
+ trspPayload:match("^([A-Z/1.0]+) ([^ ]+) [^ \r\n]+\r?\n")
+ if not httpReqLinePart1 then return end
+ if httpReqLinePart1:find("^HTTP/1.%d$") then return end
+ --log:write(string.format("%5d->%5d %s %s %s\n", srcPort, dstPort, httpReqLinePart1, httpReqLinePart2, httpReqLinePart3))
+ xService = trspPayload:match("\n[Xx]%-[Ss][Ee][Rr][Vv][Ii][Cc][Ee]:%s+([^\r\n]+)\r?\n");
+ if not xService then return end
+ --log:write("X-Service is '".. xService .."'\n")
+ local obj = app.services[xService]
+ if not obj then
+ app.services[xService] = {
+ xService = xService,
+ count=0,
+ }
+ else
+ assert(xService == obj.xService)
+ obj.count = obj.count + 1
+ end
+end
+
+
+function printStats( app )
+ local sorted = {}
+ local maxOccurValue = 0
+ local overallCount = 0
+ for _, reqObj in pairs(app.services) do
+ if reqObj.count > maxOccurValue then maxOccurValue = reqObj.count end
+ overallCount = overallCount + reqObj.count
+ table.insert(sorted, reqObj)
+ end
+ table.sort(sorted, function(a, b)return a.count > b.count end)
+ local dumpDurationSec = app.youngestEpochSec - app.oldestEpochSec
+ local timeFmt = "!%Y-%m-%d_%H:%M:%SZ"
+ out:write("\n")
+ out:write(string.format(" Subject Pressure by Services\n"))
+ out:write(string.format(" Begin %s\n", os.date(timeFmt,app.oldestEpochSec)))
+ out:write(string.format(" Duration %d seconds\n", dumpDurationSec))
+ out:write(string.format("Matching Requests %.1f (HTTP requests per second)\n", overallCount / dumpDurationSec))
+ out:write("\n")
+ out:write(" .-- HTTP Requests per Second\n")
+ out:write(" | .-- Service\n")
+ out:write(".-+---. .-+-----\n")
+ for i, elem in ipairs(sorted) do
+ local xService, count = elem.xService, elem.count
+ local countPerSecond = math.floor((count / dumpDurationSec)*10+.5)/10
+ out:write(string.format("%7.1f %s\n", countPerSecond, xService))
+ end
+ out:write("\n")
+end
+
+
+main()
+