Skip to content

Instantly share code, notes, and snippets.

@mrogaski
Created May 15, 2014 17:36
Show Gist options
  • Save mrogaski/dfbcea979f3248d1194a to your computer and use it in GitHub Desktop.
Save mrogaski/dfbcea979f3248d1194a to your computer and use it in GitHub Desktop.
Wireshark TCP session state summarization
--[[
tcp_state.lua - Find TCP HALF_OPEN and HALF_CLOSE conditions.
Copyright (C) 2014 Mark Rogaski <mrogaski@pobox.com>
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, see <http://www.gnu.org/licenses/>.
Usage:
tshark -q <other options> -X lua_script:tcp_close.lua -X lua_script1:<open|close|half_open|close> -r <trace>
--]]
local args = {...}
local options = {}
local flag_filter = false
for i, v in ipairs(args) do
if (v == 'open') then
options.open = true
flag_filter = true
elseif (v == 'half_open') then
options.half_open = true
flag_filter = true
elseif (v == 'close') then
options.close = true
flag_filter = true
elseif (v == 'half_close') then
options.half_close = true
flag_filter = true
end
end
if (not flag_filter) then
-- Include all states if no arguments are supplied.
options = { open = true, half_open = true, close = true, half_close = true }
end
local filters = {}
for k, v in pairs(options) do table.insert(filters, k) end
print('')
print('Filters: ' .. table.concat(filters, ' '))
print('')
print('Connection List:')
print('')
do
local decode = {}
decode['ip'] = Field.new('ip')
decode['ip_src'] = Field.new('ip.src')
decode['ip_dst'] = Field.new('ip.dst')
decode['tcp_srcport'] = Field.new('tcp.srcport')
decode['tcp_dstport'] = Field.new('tcp.dstport')
decode['ip_proto'] = Field.new('ip.proto')
decode['ip_id'] = Field.new('ip.id')
decode['tcp_flags_syn'] = Field.new('tcp.flags.syn')
decode['tcp_flags_ack'] = Field.new('tcp.flags.ack')
decode['tcp_flags_fin'] = Field.new('tcp.flags.fin')
decode['tcp_flags_reset'] = Field.new('tcp.flags.reset')
local flow_tab = {}
--
-- Flow class
--
local TkFlow = {}
-- Class method to decode the flow 5-tuple.
function TkFlow.flow_tuple(decode)
if (not decode.ip()) then return end
local proto = tonumber(tostring(decode.ip_proto()))
local s_addr = proto == 6 and tostring(decode.ip_src()) or nil
local s_port = proto == 6 and tonumber(tostring(decode.tcp_srcport())) or nil
local d_addr = proto == 6 and tostring(decode.ip_dst()) or nil
local d_port = proto == 6 and tonumber(tostring(decode.tcp_dstport())) or nil
return s_addr, s_port, d_addr, d_port, proto
end
-- Class method to indicate if the direction is a reverse of the index tuple.
function TkFlow.is_reversed(decode)
local s_addr, s_port, d_addr, d_port, proto = TkFlow.flow_tuple(decode)
if (proto ~= 6) then return end
if (s_port > d_port or (s_port == d_port and s_addr <= d_addr)) then
return false
else
return true
end
end
-- Class method to generate a canonical key.
function TkFlow.generate_key(decode)
local s_addr, s_port, d_addr, d_port, proto = TkFlow.flow_tuple(decode)
if (proto ~= 6) then return end
if (TkFlow.is_reversed(decode)) then
return string.format('%s:%s - %s:%s', d_addr, d_port, s_addr, s_port)
else
return string.format('%s:%s - %s:%s', s_addr, s_port, d_addr, d_port)
end
end
function TkFlow:new(decode)
local self = setmetatable({}, { __index = TkFlow })
local a_addr, a_port, z_addr, z_port, proto = TkFlow.flow_tuple(decode)
local key = TkFlow.generate_key(decode)
-- Return nil for non-TCP flows.
if (proto ~= 6) then return end
-- Initialize the state.
local rev = TkFlow.is_reversed(decode)
self['host'] = {}
self.host[0 + (rev and 1 or 0)] = { addr = a_addr, port = a_port,
syn = false, syn_ack = false,
fin = false, fin_ack = false,
rst = false }
self.host[1 - (rev and 1 or 0)] = { addr = z_addr, port = z_port,
syn = false, syn_ack = false,
fin = false, fin_ack = false,
rst = false }
return self
end
function TkFlow:advance(decode)
local rev = TkFlow.is_reversed(decode)
local tx = self.host[0 + (rev and 1 or 0)]
local rx = self.host[1 - (rev and 1 or 0)]
local flag_syn = tonumber(tostring(decode.tcp_flags_syn())) == 1 and true or false
local flag_ack = tonumber(tostring(decode.tcp_flags_ack())) == 1 and true or false
local flag_fin = tonumber(tostring(decode.tcp_flags_fin())) == 1 and true or false
local flag_rst = tonumber(tostring(decode.tcp_flags_reset())) == 1 and true or false
if (flag_syn) then
tx.syn = true
elseif (flag_fin) then
tx.fin = true
end
if (flag_ack) then
if (rx.fin) then
tx.fin_ack = true
elseif (rx.syn) then
tx.syn_ack = true
end
end
if (flag_rst) then
tx.rst = true
end
end
function TkFlow:state()
local state = 'INIT'
local a = self.host[0]
local z = self.host[1]
if (a.rst or z.rst) then
return 'CLOSE'
elseif (a.syn or z.syn) then
if (a.syn and a.syn_ack and z.syn and z.syn_ack) then
if (a.fin or z.fin) then
if (a.fin and a.fin_ack and z.fin and z.fin_ack) then
return 'CLOSE'
else
return 'HALF_CLOSE'
end
end
else
return 'HALF_OPEN'
end
end
return 'OPEN'
end
local function init_listener()
local tap = Listener.new('frame')
local function process_packet(decode, pinfo)
local key = TkFlow.generate_key(decode)
if (key == nil) then return end
if (flow_tab[key] == nil) then
flow_tab[key] = TkFlow:new(decode)
end
local flow = flow_tab[key]
flow:advance(decode)
end
function tap.reset()
end
function tap.packet(pinfo, tvb, ip)
process_packet(decode, pinfo)
end
function tap.draw()
local count = 0
for k, v in pairs(flow_tab) do
local state = v:state()
if ((state == 'OPEN' and options.open) or
(state == 'HALF_OPEN' and options.half_open) or
(state == 'CLOSE' and options.close) or
(state == 'HALF_CLOSE' and options.half_close)) then
print(string.format('%s := %s', k, v:state()))
count = count + 1
end
end
print('')
print(count .. ' connection(s) matched.')
print('')
end
end
init_listener()
end
@mrogaski
Copy link
Author

Note that the filtering options are only available in 1.11.x.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment