1 ------------------------------------------
2 -- acme_file_reader.lua
3 -- Author: Hadriel Kaplan (hadrielk at yahoo dot com)
6 ------------------------------------------
8 This is a Wireshark Lua-based capture file reader.
9 This "capture file" reader reads message logs from Acme Packet (now Oracle) Session Border Controllers,
10 such as sipmsg.log files. There are several variants of the log file format, as well as some changes that
11 can happen based on how the log file is generated and retrieved; for example if it's generated through a
12 'tail' command, or FTP'ed by a FTP client which adds carriage-returns. This Lua file reader tries to handle
15 Note: this script wasn't written to be super-efficient, nor clever. When you've been writing Lua for a while
16 you get used to writing in a different, more elegant fashion than this script is; but other people find it
17 hard to read such Lua code, so I've tried to keep this simpler.
20 -handles sipmsg type logs, sipdns type logs, algd type logs
21 -handles both IPv4 and IPv6, for both UDP and TCP
22 -reads sipmsg logs from 3800, 4250, 4500, 9200, 6300 SBCs
23 -handles logs with extra carriage-returns and linefeeds, such as from certain FTP'ed cases
24 -handles logs generated/copied from a 'tail' command on the SBC ACLI
25 -handles MBCD messages in logs, and puts their decoded ascii description in comments in Wireshark
28 -for very large logs (many megabytes), it takes a long time (many minutes)
29 -creates fake IP and UDP/TCP headers, which might be misleading
30 -has to guess sometimes, though it hasn't guessed wrong yet as far as I know
33 - make it use Struct.tohex/fromhex now that we have the Struct library in Wireshark
34 - make it use a linux cooked-mode pseudo-header (see https://gitlab.com/wireshark/wireshark/-/wikis/SLL)
35 - make it use preferences, once I write C-code for Wireshark to do that :)
36 - rewrite some of the pattern searches to use real regex/PCRE instead?
38 Example SIP over UDP message:
39 Aug 26 19:25:10.685 On [5:0]2.1.1.1:5060 received from 2.1.2.115:5060
40 REGISTER sip:2.1.1.1:5060 SIP/2.0
41 Via: SIP/2.0/UDP 2.1.2.115:5060;branch=z9hG4bK6501441021660x81000412
42 From: <sip:public_115@2.1.1.1:5060>;tag=520052-7015560x81000412
43 To: <sip:public_115@2.1.1.1:5060>
44 Call-ID: 680192-4234150x81000412@2.1.2.115
46 Contact: <sip:public_115@2.1.2.115:5060;transport=udp>
49 Authorization: Digest username="public_115",realm="empirix.com",uri="sip:2.1.1.1",response="5d61837cc54dc27018a40f2532e622de",nonce="430f6ff09ecd8c3fdfc5430b6e7e437a4cf77057",algorithm=md5
53 ----------------------------------------
55 2007-03-06 13:38:48.037 OPENED
56 2007-03-06 13:38:48.037 OPENED
57 2007-03-06 13:38:48.037 OPENED
58 Mar 6 13:38:54.959 On [1:0]135.25.29.135:5060 received from 192.168.109.138:65471
59 OPTIONS sip:135.25.29.135 SIP/2.0
60 Accept: application/sdp
61 User-Agent: ABS GW v5.1.0
63 From: sip:192.168.109.138;tag=a2a090ade36bb108da70b0c8f7ba02e9
64 Contact: sip:192.168.109.138
65 Call-ID: 8c0296da4a0d9f4d97e1389cd28d2352@172.16.6.114
66 CSeq: 347517161 OPTIONS
67 Via: SIP/2.0/UDP 192.168.109.138;branch=z9hG4bK21feac80fe9a63c1cf2988baa2af0849
72 ----------------------------------------
73 Another SIP over UDP (from 9200):
75 Jun 8 14:34:22.599 UDP[3:0]10.102.131.194:5060 OPENED
76 Jun 8 14:34:22.616 UDP[6:0]10.102.130.185:5060 OPENED
77 Jun 8 14:34:49.416 On [6:0]10.102.130.185:5060 received from 10.102.130.150:5060
78 REGISTER sip:csp.noklab.net SIP/2.0
79 Via: SIP/2.0/UDP 192.168.1.100:5060;branch=z9hG4bK26b7a48d
80 From: sip:34903@csp.noklab.net
81 To: sip:34903@csp.noklab.net
82 Call-ID: 003094c3-a0160002-23aa7e86-29e5808d@192.168.1.100
85 Contact: <sip:34903@192.168.1.100:5060>
90 ----------------------------------------
92 Example SIP over TCP message (note it ends in the middle of a header name):
93 Jan 12 00:03:54.700 On 172.25.96.200:8194 received from 172.25.32.28:5060
95 From: Unavailable <sip:Unavailable@172.25.96.200:5060;user=phone>;tag=1200822480
96 To: 24001900011 <sip:0011@172.25.32.28:5060;user=phone>;tag=03c86c0b27df1b1254aeccbc000
97 Call-ID: 7f6b0887-1d313896-1511da31-b045@144.229.136.111
99 ----------------------------------------
101 Example SIP Pre and Post-NAT messages:
102 Post-NAT from private<realm=e911core> encoded:
103 SIP/2.0 302 Moved Temporarily
104 Call-ID: SD27o9f04-fcc63aa885c83e22a1be64cfc210b55e-vjvtv00
106 From: <sip:7866932005@127.1.0.100:5060;user=phone;e911core=TSD5051AEPCORE-dnamt76v6nm04;CKE=BSLD-5cuduig6t52l2;e911vpn=TSD5051AEPVPN-7gdq13vt8fi59>;tag=SD27o9f04-10000000-0-1424021314
107 To: <sip:911@127.0.0.100;user=phone;CKE=BSLD-8blt7m3dhnj17>;tag=10280004-0-1239441202
108 Via: SIP/2.0/UDP 127.254.254.1:5060;branch=z9hG4bK5i4ue300dgrdras7q281.1
111 Contact: <sip:1111119999@127.0.0.100:5060;e911core=TSD5051AEPCORE-5n86t36uuma01>
114 ----------------------------------------
115 Pre-NAT to private<realm=e911core> decode:
116 ACK sip:911@127.0.0.100;user=phone;CKE=BSLD-8blt7m3dhnj17 SIP/2.0
117 Via: SIP/2.0/UDP 127.254.254.1:5060;branch=z9hG4bK5i4ue300dgrdras7q281.1
118 Call-ID: SD27o9f04-fcc63aa885c83e22a1be64cfc210b55e-vjvtv00
120 From: <sip:7866932005@127.1.0.100:5060;user=phone;e911core=TSD5051AEPCORE-dnamt76v6nm04;CKE=BSLD-5cuduig6t52l2;e911vpn=TSD5051AEPVPN-7gdq13vt8fi59>;tag=SD27o9f04-10000000-0-1424021314
121 To: <sip:911@127.0.0.100;user=phone;CKE=BSLD-8blt7m3dhnj17>;tag=10280004-0-1239441202
125 ----------------------------------------
128 Nov 1 23:03:12.811 On 10.21.232.194:1122 received from 10.21.199.204:53
129 DNS Response 3916 flags=8503 q=1 ans=0 auth=1 add=0 net-ttl=0
130 Q:NAPTR 7.6.5.4.3.2.1.0.1.2.e164
131 NS:SOA e164 ttl=0 netnumber01
132 rname=user.netnumber01
133 ser=223 ref=0 retry=0 exp=0 minttl=0
135 0000: 0f 4c 85 03 00 01 00 00 00 01 00 00 01 37 01 36 .L...........7.6
136 0010: 01 35 01 34 01 33 01 32 01 31 01 30 01 31 01 32 .5.4.3.2.1.0.1.2
137 0020: 04 65 31 36 34 00 00 23 00 01 04 65 31 36 34 00 .e164..#...e164.
138 0030: 00 06 00 01 00 00 00 00 00 33 0b 6e 65 74 6e 75 .........3.netnu
139 0040: 6d 62 65 72 30 31 00 04 75 73 65 72 0b 6e 65 74 mber01..user.net
140 0050: 6e 75 6d 62 65 72 30 31 00 00 00 00 df 00 00 00 number01........
141 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 .............
143 ----------------------------------------
144 Example MGCP message (note the IP/UDP headers are in the hex):
145 Mar 1 14:37:26.683 On [0:803]172.16.84.141:2427 sent to 172.16.74.100:2427
147 0000: 00 04 00 00 00 01 00 02 00 00 03 23 0a ad 00 c9 ...........#....
148 0010: 45 00 00 a8 23 36 00 00 3c 11 63 fd ac 10 54 8d E...#6..<.c...T.
149 0020: ac 10 4a 64 09 7b 09 7b 00 94 16 c2 32 35 30 20 ..Jd.{.{....250
151 250 55363 Connection Deleted
152 P: PS=6551, OS=1048160, PR=6517, OR=1042720, PL=0, JI=1, LA=5, PC/RPS=6466, PC/ROS=1034560, PC/RPL=0, PC/RJI=0
154 ----------------------------------------
155 Example MBCD message:
156 Mar 1 14:37:26.672 On 127.0.0.1:2946 sent to 127.0.0.1:2944
157 0000: ac 3e fd a8 01 01 77 36 9e 00 37 10 0c 34 4c bc .>....w6..7..4L.
158 0010: 00 30 23 0c 34 4c bc 00 11 33 00 0e 35 00 04 00 .0#.4L...3..5...
159 0020: 00 00 00 30 00 04 00 00 00 00 23 0c 34 4c bd 00 ...0......#.4L..
160 0030: 11 33 00 0e 35 00 04 00 00 00 00 30 00 04 00 00 .3..5......0....
162 Transaction = 24589982 {
163 Context = 204754108 {
164 Subtract = 204754108 {
170 Subtract = 204754109 {
178 ----------------------------------------
180 ]]----------------------------------------
182 -- debug printer, set DEBUG to true to enable printing debug info
183 -- set DEBUG2 to true to enable really verbose printing
184 local DEBUG
, DEBUG2
= true, false
186 local dprint
= function() end
187 local dprint2
= function() end
188 if DEBUG
or DEBUG2
then
189 dprint
= function(...)
190 print(table.concat({"Lua:", ...}," "))
198 -- this should be done as a preference setting
199 local ALWAYS_UDP
= true
202 local fh
= FileHandler
.new("Oracle Acme Packet logs", "acme",
203 "A file reader for Oracle Acme Packet message logs such as sipmsg.log","rs")
206 -- There are certain things we have to create fake state/data for, because they
207 -- don't exist in the log file for example to create IP headers we have to create
208 -- fake identification field values, and to create timestamps we have to guess the
209 -- year (and in some cases month/day as well), and for TCP we have to create fake
210 -- connection info, such as sequence numbers. We can't simply have a global static
211 -- variable holding such things, because Wireshark reads the file sequentially at
212 -- first, but then calls seek_read for random packets again and we don't want to
213 -- re-create the fake info again because it will be wrong. So we need to create it
214 -- for each packet and remember what we created for each packet, so that seek_read
215 -- gets the same values. We could store the variables in a big table, keyed by the
216 -- specific header info line for each one; but instead we'll key it off of the file
217 -- position number, since read() sets it for Wireshark and seek_read() gets it from
218 -- Wireshark. So we'll have a set of global statics used during read(), but the
219 -- actual per-packet values will be stored in a table indexed/keyed by the file
220 -- position number. A separate table holds TCP peer connection info as described
223 -- I said above that this state is "global", but really it can't be global to this
224 -- whole script file, because more than one file can be opened for reading at the
225 -- same time. For example if the user presses the reload button, the capture file
226 -- will be opened for reading before the previous (same) one is closed. So we have
227 -- to store state per-file. The good news is Wireshark gives us a convenient way to
228 -- do that, using the CaptureInfo.private_table attribute/member. We can save a Lua
229 -- table with whatever contents we want, to this private_table member, and get it
230 -- later during the other read/seek_read/cose function calls.
232 -- So to store this per-file state, we're going to use Lua class objects. They're
233 -- just Lua tables that have functions and meta-functions and can be treated like
234 -- objects in terms of syntax/behavior.
237 local State_mt
= { __index
= State
}
240 local new_class
= { -- the new instance
241 -- stuff we need to keep track of to cerate fake info
249 -- the following table holds per-packet info
250 -- the key index will be a number - the file position - but it won't be an array type table (too sparse).
251 -- Each packet's entry is a table holding the "static" variables for that packet; this sub-table will be
252 -- an array style instead of hashmap, to reduce size/performance
253 -- This table needs to be cleared whenever the file is closed/opened.
256 -- the following local table holds TCP peer "connection" info, which is basically
257 -- TCP control block (TCB) type information; this is needed to create and keep track
258 -- of fake TCP sockets/headers for messages that went over TCP, for example for fake
259 -- sequence number info.
260 -- The key index for this is the local+remote ip:port strings concatenated.
261 -- The value is a sub-table, array style, holding the most recent sequence numbers.
262 -- This whole table needs to be cleared whenever the file is closed/opened.
266 setmetatable( new_class
, State_mt
) -- all instances share the same metatable
270 -- the indices for the State.packets{} variable sub-tables
276 -- the indices for the State.tcb{} sub-tables
278 local TREMOTE_SEQ
= 2
281 local char
= string.char
282 local floor = math
.floor
284 -- takes a Lua number and converts it into a 2-byte string binary (network order)
286 local function dec2bin16(num
)
287 return Struct
.pack(">I2",num
)
290 -- takes a Lua number and converts it into a 4-byte string binary (network order)
291 local function dec2bin32(num
)
292 return Struct
.pack(">I4",num
)
296 -- function to skip log info before/between/after messages
297 local delim
= "^%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-%-$"
298 -- words that must be found to be skipped. "File ..." is found in 9200 logs)
299 local skiplist
= { " OPENED", " CLOSED", " STARTED", " STOPPED", "^File ", delim
}
300 -- pre/post NAT entries
301 local pre_nat_header_pattern
= "^Pre%-NAT to private<realm=([^>]+)> decode:\r?$"
302 local post_nat_header_pattern
= "^Post%-NAT from private<realm=([^>]+)> encoded:\r?$"
304 local function skip_ahead(file
, line
, position
)
306 local found
= #line
== 0 -- will be false unless the line is empty
307 for i
, word
in ipairs(skiplist
) do
308 if line
:find(word
) then
314 position
= file
:seek()
316 if not line
then return nil end
317 elseif line
:find(pre_nat_header_pattern
) or line
:find(post_nat_header_pattern
) then
318 -- skip the whole message
322 until line
:find(delim
)
325 return line
, position
328 -- following pattern grabs month, day, hour, min, sec, millisecs
329 local header_time_pattern
= "^(%u%l%l) ?(%d%d?) (%d%d?):(%d%d):(%d%d)%.(%d%d%d) On "
330 -- tail'ed file has no month/day
331 local header_tail_time_pattern
= "^(%d%d):(%d%d)%.(%d%d%d) On "
333 -- grabs local and remote IPv4:ports (not phy/vlan), and words in between (i.e., "sent to" or "received from")
334 local header_address_pattern
= "(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?):(%d+) (%l+ %l+) (%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?):(%d+) ?\r?$"
335 -- grabs local and remote IPv6:ports (not phy/vlan), and words in between (i.e., "sent to" or "received from")
336 local header_v6address_pattern
= "%[([:%x]+)%]:(%d+) (%l+ %l+) %[([:%x]+)%]:(%d+) ?\r?$"
338 -- grabs phy/vlan info
339 local header_phy_pattern
= "%[(%d+):(%d+)%]"
343 local function get_direction(phrase
)
344 if #phrase
== 7 and phrase
:find("sent to") then
346 elseif #phrase
== 13 and phrase
:find("received from") then
349 dprint("direction phrase not found")
353 -- monthlist table for getting month number value from 3-char name (number is table index)
354 local monthlist
= {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}
356 -- Compute the difference in seconds between local time and UTC
357 -- from http://lua-users.org/wiki/TimeZone
358 local function get_timezone()
359 local now
= os
.time()
360 return os
.difftime(now
, os
.time(os
.date("!*t", now
)))
362 local timezone
= get_timezone()
364 function State
:get_timestamp(line
, file_position
, seeking
)
365 local i
, line_pos
, month
, day
, hour
, min, sec
, milli
= line
:find(header_time_pattern
)
371 -- we've seen this packet before, just go get the saved timestamp
372 sec
= self
.packets
[file_position
][TTIME
]
374 dprint("failed to get saved timestamp for packet at position:", file_position
)
380 -- find the month's number
381 for index
, name
in ipairs(monthlist
) do
382 if month
== name
then
387 if type(month
) ~= "number" then return end
390 hour
= tonumber(hour
)
393 milli
= tonumber(milli
)
395 if not day
or not hour
or not min or not sec
or not milli
then
396 dprint("timestamp could not be determined")
400 -- we don't know what year the log file was created, so we have to guess
401 -- if we guess the current system year, then a log of December loaded in January will appear wrong,
402 -- as will a log file which lasts over new year
403 -- so we're going to check the current system month, and if it's less than the log file's then we'll
404 -- assume the log file started last year; if the system month is larger or equal, then we'll assume the log
405 -- file is of this year. We only do this checking once per file.
406 if self
.tyear
== 0 then
407 local curr_year
, curr_month
= tonumber(os
.date("%Y")), tonumber(os
.date("%m"))
408 if curr_month
< month
then
410 if curr_year
> 0 then
411 curr_year
= curr_year
- 1
414 self
.tyear
= curr_year
416 -- XXX - but for purposes of testing, we just force the year to
417 -- 2014, so that we can compare the result of this code reading
418 -- an Acme log with the result of the pcapng reader reading a
419 -- pcapng file with the same packets - the time stamps in
420 -- pcapng files are times since the Epoch, so the year is known
424 -- if this message's month is less than previous message's, then year wrapped
425 if month
< self
.tmonth
then
426 self
.tyear
= self
.tyear
+ 1
430 local timet
= os
.time({ ["year"] = self
.tyear
, ["month"] = month
, ["day"] = day
, ["hour"] = hour
, ["min"] = min, ["sec"] = sec
})
432 dprint("timestamp conversion failed")
435 timet
= timet
+ timezone
438 self
.nstime
= NSTime(timet
, milli
* 1000000)
439 self
.packets
[file_position
][TTIME
] = self
.nstime
441 -- For Lua 5.3 and later, make sure we have an integer via a method
442 -- that also works on Lua 5.1. (os.date doesn't handle fractional seconds.)
443 timet
= timet
+ math
.floor(milli
/1000)
444 dprint2("found time of ", os
.date("%c",timet
), " with value=",timet
)
446 return self
.nstime
, line_pos
449 -- get_tail_time() gets a fictitious timestamp starting from 19:00:00 on Dec 31, 1969, and incrementing based
450 -- on the minutes/secs/millisecs seen (i.e., if the minute wrapped then hour increases by 1, etc.).
451 -- this is needed for tail'ed log files, since they don't show month/day/hour
452 function State
:get_tail_time(line
, file_position
, seeking
)
453 local i
, line_pos
, min, sec
, milli
= line
:find(header_tail_time_pattern
)
454 if not min then return end
457 -- we've seen this packet before, just go get the saved timestamp
458 sec
= self
.packets
[file_position
][TTIME
]
460 dprint("failed to get saved timestamp for packet at position:", file_position
)
468 milli
= tonumber(milli
)
470 if not min or not sec
or not milli
then
471 dprint("timestamp could not be determined")
475 -- get difference in time
476 local tmin
, tsec
, tmilli
, nstime
= self
.tmin
, self
.tsec
, self
.tmilli
, self
.nstime
477 local ttime
= nstime
.secs
479 -- min, sec, milli are what the log says this tail'ed packet is
480 -- tmin, tsec, tmilli are what we got from last packet
481 -- nstime is the unix time of that, and ttime is the seconds of that unix time
483 -- if minutes wrapped, or they're equal but seconds wrapped, then handle it as if in the next hour
484 if (min < tmin
) or (min == tmin
and sec
< tsec
) or (min == tmin
and sec
== tsec
and milli
< tmilli
) then
485 -- something wrapped, calculate difference as if in next hour
486 ttime
= ttime
+ (((min * 60) + sec
+ 3600) - ((tmin
* 60) + tsec
))
488 ttime
= ttime
+ (((min * 60) + sec
) - ((tmin
* 60) + tsec
))
490 self
.tmin
, self
.tsec
, self
.tmilli
= min, sec
, milli
491 self
.nstime
= NSTime(ttime
, milli
* 1000000)
492 self
.packets
[file_position
][TTIME
] = self
.nstime
494 return self
.nstime
, line_pos
498 ["0"]=0, ["1"]=1, ["2"]=2, ["3"]=3, ["4"]=4, ["5"]=5, ["6"]=6, ["7"]=7, ["8"]=8, ["9"]=9, ["a"]=10, ["b"]=11, ["c"]=12, ["d"]=13, ["e"]=14, ["f"]=15,
499 ["00"]=0, ["01"]=1, ["02"]=2, ["03"]=3, ["04"]=4, ["05"]=5, ["06"]=6, ["07"]=7, ["08"]=8, ["09"]=9, ["0a"]=10, ["0b"]=11, ["0c"]=12, ["0d"]=13, ["0e"]=14, ["0f"]=15,
500 ["10"]=16, ["11"]=17, ["12"]=18, ["13"]=19, ["14"]=20, ["15"]=21, ["16"]=22, ["17"]=23, ["18"]=24, ["19"]=25, ["1a"]=26, ["1b"]=27, ["1c"]=28, ["1d"]=29, ["1e"]=30, ["1f"]=31,
501 ["20"]=32, ["21"]=33, ["22"]=34, ["23"]=35, ["24"]=36, ["25"]=37, ["26"]=38, ["27"]=39, ["28"]=40, ["29"]=41, ["2a"]=42, ["2b"]=43, ["2c"]=44, ["2d"]=45, ["2e"]=46, ["2f"]=47,
502 ["30"]=48, ["31"]=49, ["32"]=50, ["33"]=51, ["34"]=52, ["35"]=53, ["36"]=54, ["37"]=55, ["38"]=56, ["39"]=57, ["3a"]=58, ["3b"]=59, ["3c"]=60, ["3d"]=61, ["3e"]=62, ["3f"]=63,
503 ["40"]=64, ["41"]=65, ["42"]=66, ["43"]=67, ["44"]=68, ["45"]=69, ["46"]=70, ["47"]=71, ["48"]=72, ["49"]=73, ["4a"]=74, ["4b"]=75, ["4c"]=76, ["4d"]=77, ["4e"]=78, ["4f"]=79,
504 ["50"]=80, ["51"]=81, ["52"]=82, ["53"]=83, ["54"]=84, ["55"]=85, ["56"]=86, ["57"]=87, ["58"]=88, ["59"]=89, ["5a"]=90, ["5b"]=91, ["5c"]=92, ["5d"]=93, ["5e"]=94, ["5f"]=95,
505 ["60"]=96, ["61"]=97, ["62"]=98, ["63"]=99, ["64"]=100, ["65"]=101, ["66"]=102, ["67"]=103, ["68"]=104, ["69"]=105, ["6a"]=106, ["6b"]=107, ["6c"]=108, ["6d"]=109, ["6e"]=110, ["6f"]=111,
506 ["70"]=112, ["71"]=113, ["72"]=114, ["73"]=115, ["74"]=116, ["75"]=117, ["76"]=118, ["77"]=119, ["78"]=120, ["79"]=121, ["7a"]=122, ["7b"]=123, ["7c"]=124, ["7d"]=125, ["7e"]=126, ["7f"]=127,
507 ["80"]=128, ["81"]=129, ["82"]=130, ["83"]=131, ["84"]=132, ["85"]=133, ["86"]=134, ["87"]=135, ["88"]=136, ["89"]=137, ["8a"]=138, ["8b"]=139, ["8c"]=140, ["8d"]=141, ["8e"]=142, ["8f"]=143,
508 ["90"]=144, ["91"]=145, ["92"]=146, ["93"]=147, ["94"]=148, ["95"]=149, ["96"]=150, ["97"]=151, ["98"]=152, ["99"]=153, ["9a"]=154, ["9b"]=155, ["9c"]=156, ["9d"]=157, ["9e"]=158, ["9f"]=159,
509 ["a0"]=160, ["a1"]=161, ["a2"]=162, ["a3"]=163, ["a4"]=164, ["a5"]=165, ["a6"]=166, ["a7"]=167, ["a8"]=168, ["a9"]=169, ["aa"]=170, ["ab"]=171, ["ac"]=172, ["ad"]=173, ["ae"]=174, ["af"]=175,
510 ["b0"]=176, ["b1"]=177, ["b2"]=178, ["b3"]=179, ["b4"]=180, ["b5"]=181, ["b6"]=182, ["b7"]=183, ["b8"]=184, ["b9"]=185, ["ba"]=186, ["bb"]=187, ["bc"]=188, ["bd"]=189, ["be"]=190, ["bf"]=191,
511 ["c0"]=192, ["c1"]=193, ["c2"]=194, ["c3"]=195, ["c4"]=196, ["c5"]=197, ["c6"]=198, ["c7"]=199, ["c8"]=200, ["c9"]=201, ["ca"]=202, ["cb"]=203, ["cc"]=204, ["cd"]=205, ["ce"]=206, ["cf"]=207,
512 ["d0"]=208, ["d1"]=209, ["d2"]=210, ["d3"]=211, ["d4"]=212, ["d5"]=213, ["d6"]=214, ["d7"]=215, ["d8"]=216, ["d9"]=217, ["da"]=218, ["db"]=219, ["dc"]=220, ["dd"]=221, ["de"]=222, ["df"]=223,
513 ["e0"]=224, ["e1"]=225, ["e2"]=226, ["e3"]=227, ["e4"]=228, ["e5"]=229, ["e6"]=230, ["e7"]=231, ["e8"]=232, ["e9"]=233, ["ea"]=234, ["eb"]=235, ["ec"]=236, ["ed"]=237, ["ee"]=238, ["ef"]=239,
514 ["f0"]=240, ["f1"]=241, ["f2"]=242, ["f3"]=243, ["f4"]=244, ["f5"]=245, ["f6"]=246, ["f7"]=247, ["f8"]=248, ["f9"]=249, ["fa"]=250, ["fb"]=251, ["fc"]=252, ["fd"]=253, ["fe"]=254, ["ff"]=255
517 local function iptobytes(ipaddr
)
518 local bytes
= { ipaddr
:match("(%d+)%.(%d+)%.(%d+)%.(%d+)") }
519 if not #bytes
== 4 then
520 dprint("failed to get ip address bytes for '", ipaddr
, "'")
524 for i
, byte
in ipairs(bytes
) do
525 ip
= ip
.. char(tonumber(byte
))
530 local function hexword2bin(word
)
532 return char(hexbin
[word
:sub(1,2)], hexbin
[word
:sub(3,4)])
533 elseif #word
== 3 then
534 return char(hexbin
[word
:sub(1,1)], hexbin
[word
:sub(2,3)])
535 elseif #word
< 3 then
536 return char(0, hexbin
[word
])
541 -- convert this 2620:0:60:8ac::102 to its 16-byte binary (=8 of 2-byte words)
543 local function ipv6tobytes(ipaddr
)
544 -- start with all 16 bytes being zeroes
545 local words
= { "\00\00", "\00\00", "\00\00", "\00\00", "\00\00", "\00\00", "\00\00", "\00\00" }
546 -- now walk from front of ipv6 address string replacing byte numbers above;
547 -- if we hit a "::", then jump to end and do it in reverse
548 local colon_s
, colon_e
= ipaddr
:find("::%x")
550 -- there's a double-colon, so split the string and do the end first, backwards
551 -- get each chunk first
553 local index
, wordix
= 1, NUMWORDS
554 for w
in string.gmatch(ipaddr
:sub(colon_e
- 1), ":(%x+)") do
555 t
[index
] = hexword2bin(w
)
558 for ix
=index
-1, 1, -1 do
559 words
[wordix
] = t
[ix
]
562 ipaddr
= ipaddr
:sub(1, colon_s
)
566 for w
in string.gmatch(ipaddr
, "(%x+):?") do
567 words
[i
] = hexword2bin(w
)
571 if not #words
== NUMWORDS
then
572 dprint("failed to get IPv6 address bytes for '", ipaddr
, "'")
576 return table.concat(words
)
579 -- calculates checksum as done for IP, TCP, UDP
580 local function checksum(chunk
)
582 -- take every 2-byte value and add them up
583 for one
, two
in chunk
:gmatch("(.)(.)") do
584 sum
= sum
+ (string.byte(one
) * 256) + (string.byte(two
))
585 while floor(sum
/ 65536) > 0 do
586 -- add carry/overflow value
587 sum
= (sum
% 65536) + (floor(sum
/ 65536))
591 -- now get one's complement of that
594 -- and return it as a 2-byte string
595 return dec2bin16(sum
)
598 ----------------------------------------
599 -- protocol type number
600 local PROTO_UDP
= "\17"
601 local PROTO_TCP
= "\06"
605 -- both type enums and header lengths
609 ----------------------------------------
610 -- Packet creation/serialization occurs using a Lua class object model
611 -- There's a single base class 'Packet' which has data/methods every packet type has
612 -- 'RawPacket' and 'DataPacket' both derive from 'Packet'.
613 -- 'RawPacket' is for packets which the log file has the raw IP/UDP headers for,
614 -- such as ALG log messages (MGCP/NCS). Since the IP headers are in them, we use those.
615 -- 'DataPacket' is for packets which the log file only has payload data for, and
616 -- we need to create fake IP/UDP or IP/TCP headers for.
617 -- 'BinPacket' and'AsciiPacket' both derive from 'DataPacket'.
618 -- 'BinPacket' is for binary-style logged packets, such as MBCD or DNS, while
619 -- 'AsciiPacket' is for ascii-style ones such as SIP.
620 -- 'DnsPacket' derives from 'BinPacket', for DNS-style logs.
622 -- Each class has a read_data() method, which reads in the packet data, builds the packet,
623 -- and sets the Wireshark buffer. Some classes have a get_data() method which read_data()
624 -- calls, to get the payload data before building a fake packet.
626 -- The base Packet class has a get_hex_data() and get_ascii_data() methods, to get the payload
627 -- in either form, and those base methods are called by get_data() or read_data() of derived
630 -- For performance reasons, packet data is read line-by-line into a table (called bufftbl),
631 -- which is concatenated at the end. This avoids Lua building interim strings and garbage
632 -- collecting them. But it makes the code uglier. The get_data()/get_hex_data()/get_ascii_data()
633 -- methods read into this table they get passed, while the read_data() functions handle managing
636 ----------------------------------------
637 ----------------------------------------
638 -- The base Packet class, from which others derive
639 -- all Packets have a ptype, timestamp, source and dest address:port, and data
642 local Packet_mt
= { __index
= Packet
}
644 function Packet
.new(state
, timestamp
, direction
, source_ip
, source_port
, dest_ip
, dest_port
, ptype
, ttype
, file_position
)
645 local new_class
= { -- the new instance
647 ["timestamp"] = timestamp
,
648 ["direction"] = direction
,
649 ["source_ip"] = source_ip
,
650 ["source_port"] = source_port
,
651 ["dest_ip"] = dest_ip
,
652 ["dest_port"] = dest_port
,
655 ["file_position"] = file_position
657 setmetatable( new_class
, Packet_mt
) -- all instances share the same metatable
661 function Packet
:set_comment(comment
)
662 self
["comment"] = comment
665 function Packet
:set_wslua_fields(frame
)
666 frame
.time
= self
.timestamp
667 frame
.rec_type
= wtap_rec_types
.PACKET
668 frame
.flags
= wtap_presence_flags
.TS
-- for timestamp
670 frame
.comment
= self
.comment
675 local packet_hexline_pattern
= "^ %x%x%x0: %x%x"
676 function Packet
:get_hex_data(file
, line
, bufftbl
, index
)
679 dprint2("Packet:get_hex_data() called")
681 for word
in line
:gmatch("(%x%x) ") do
682 bufftbl
[index
] = char(hexbin
[word
])
684 if ((index
- start
) % 16) == 0 then break end
687 until not line
or not line
:find(packet_hexline_pattern
)
689 return index
- start
, line
692 function Packet
:get_ascii_data(file
, line
, bufftbl
, index
, only_newline
)
693 local bufflen
= 0 -- keep tally of total length of payload
694 local found_delim
= true
696 dprint2("Packet:get_ascii_data() called")
698 bufftbl
[index
] = line
699 bufflen
= bufflen
+ #line
701 -- sanity check if line has "\r" at end, and if so only add \n
702 if line
:find("\r",-1,true) then
703 bufftbl
[index
+1] = "\n"
704 bufflen
= bufflen
+ 1
705 dprint2("Found carriage-return at end of line")
706 elseif only_newline
then
707 -- only add a newline
708 bufftbl
[index
+1] = "\n"
709 bufflen
= bufflen
+ 1
711 bufftbl
[index
+1] = "\r\n"
712 bufflen
= bufflen
+ 2
716 -- read next line now
724 until line
:find(delim
)
726 -- get rid of last \r\n, if we found a dashed delimiter, as it's not part of packet
728 bufflen
= bufflen
- bufftbl
[index
-1]:len()
729 bufftbl
[index
-1] = nil
732 dprint2("Packet:get_ascii_data() returning", bufflen
)
736 ----------------------------------------
737 -- RawPacket class, for packets that the log file contains the whole IP header for, such as algd logs
740 local RawPacket_mt
= { __index
= RawPacket
}
741 setmetatable( RawPacket
, Packet_mt
) -- make RawPacket inherit from Packet
743 function RawPacket
.new(...)
744 local new_class
= Packet
.new(...) -- the new instance
745 setmetatable( new_class
, RawPacket_mt
) -- all instances share the same metatable
749 function RawPacket
:read_data(file
, frame
, line
, seeking
)
750 local bufftbl
= {} -- table to hold data bytes
751 local index
= 1 -- start at first slot in array
753 -- need to skip "Packet:" line and first 0000: line, it's internal junk
757 dprint2("RawPacket:read_data() getting hex from line='", line
, "'")
758 local bufflen
, line
= self
:get_hex_data(file
, line
, bufftbl
, index
)
759 if not bufflen
or bufflen
< 21 then
760 dprint("error getting binary data")
764 -- add remainder as more packet data, but first delete overlap
765 -- see if frag bits are set in IP header, to see if UDP/TCP header exists
766 if self
.ptype
== IPv4
then
767 -- grab byte with frag flags and first byte of offset
768 local flag
= string.byte(bufftbl
[7]) -- converts binary character to number
769 local frag_offset
= flag
% 32 -- masks off upper 3 bits
770 frag_offset
= (frag_offset
* 256) + string.byte(bufftbl
[8])
771 flag
= floor(flag
/ 224) -- shift right
772 flag
= flag
% 2 -- mask upper bits
773 if flag
== 1 or frag_offset
> 0 then
774 -- we have a fragmented IPv4 packet, so no proto header
775 -- only save first 20 bytes (the IP header)
776 for i
=bufflen
, 21, -1 do
781 -- only save first 20 + proto size bytes
783 if bufftbl
[10] == PROTO_UDP
then
785 elseif bufftbl
[10] == PROTO_TCP
then
788 dprint("failed to fix raw packet overlap")
791 for i
=bufflen
, save
+1, -1 do
799 -- now read in rest of message, if any
800 -- first skip extra empty newline
805 bufflen
= bufflen
+ self
:get_ascii_data(file
, line
, bufftbl
, bufflen
+1, true)
807 frame
.data
= table.concat(bufftbl
)
812 ----------------------------------------
813 -- DataPacket class, for packets that the log file contains just the payload data for
815 local DataPacket
= {}
816 local DataPacket_mt
= { __index
= DataPacket
}
817 setmetatable( DataPacket
, Packet_mt
) -- make DataPacket inherit from Packet
819 function DataPacket
.new(...)
820 local new_class
= Packet
.new(...) -- the new instance
821 setmetatable( new_class
, DataPacket_mt
) -- all instances share the same metatable
825 function DataPacket
:set_tcbkey(key
)
830 function DataPacket
:build_ipv4_hdr(bufflen
, proto
, seeking
)
831 local len
= bufflen
+ 20 -- 20 byte IPv4 header size
833 -- figure out the ip identification value
836 ip_ident
= self
.state
.packets
[self
.file_position
][IP_IDENT
]
838 -- increment ident value
839 self
.state
.ip_ident
= self
.state
.ip_ident
+ 1
840 if self
.state
.ip_ident
== 65536 then
841 self
.state
.ip_ident
= 1
843 ip_ident
= self
.state
.ip_ident
844 -- save it for future seeking
845 self
.state
.packets
[self
.file_position
][IP_IDENT
] = ip_ident
848 -- use a table to concatenate as it's slightly faster that way
850 "\69\00", -- 1=ipv4 and 20 byte header length
851 dec2bin16(len
), -- 2=packet length bytes
852 dec2bin16(ip_ident
), -- 3=ident field bytes
853 "\00\00\64", -- 4=flags/fragment offset, ttl
855 "\00\00", -- 6=checksum (using zero for now)
856 iptobytes(self
.source_ip
), -- 7=source ip
857 iptobytes(self
.dest_ip
) -- 8=dest ip
860 -- calc IPv4 header checksum, and set its value
861 hdrtbl
[6] = checksum(table.concat(hdrtbl
))
863 return table.concat(hdrtbl
)
866 function DataPacket
:build_ipv6_hdr(bufflen
, proto
)
867 -- use a table to concatenate as it's slightly faster that way
869 "\96\00\00\00", -- 1=ipv6 version, class, label
870 dec2bin16(bufflen
), -- 2=packet length bytes
871 proto
.. "\64", -- 4=proto, ttl
872 ipv6tobytes(self
.source_ip
), -- 5=source ip
873 ipv6tobytes(self
.dest_ip
) -- 6=dest ip
875 return table.concat(hdrtbl
)
878 -- calculates TCP/UDP header checksums with pseudo-header info
879 function DataPacket
:calc_header_checksum(bufftbl
, bufflen
, hdrtbl
, proto
)
880 -- first create pseudo IP header
881 if self
.ptype
== IPv4
then
883 iptobytes(self
.source_ip
), -- 1=source ip
884 iptobytes(self
.dest_ip
), -- 2=dest ip
887 dec2bin16(bufflen
) -- payload length bytes
889 bufftbl
[1] = table.concat(iphdrtbl
)
890 elseif self
.ptype
== IPv6
then
892 ipv6tobytes(self
.source_ip
), -- 1=source ip
893 ipv6tobytes(self
.dest_ip
), -- 2=dest ip
895 dec2bin16(bufflen
), -- payload length bytes
896 "\00\00\00", -- zeros
899 bufftbl
[1] = table.concat(iphdrtbl
)
902 -- and pseudo TCP or UDP header
903 bufftbl
[2] = table.concat(hdrtbl
)
905 -- see if payload is odd length
907 if bufflen
% 2 == 1 then
908 -- odd number of payload bytes, add zero byte at end
909 odd
= true -- remember to undo this
910 bufftbl
[#bufftbl
+1] = "\00"
913 local result
= checksum(table.concat(bufftbl
))
915 -- remove pseudo-headers
919 bufftbl
[#bufftbl
] = nil
926 function DataPacket
:build_udp_hdr(bufflen
, bufftbl
)
927 local len
= bufflen
+ 8 -- 8 for size of UDP header
929 dec2bin16(self
.source_port
), -- 1=source port bytes
930 dec2bin16(self
.dest_port
), -- 2=dest port bytes
931 dec2bin16(len
), -- 3=payload length bytes
932 "\00\00" -- 4=checksum
935 -- calc udp checksum (only done for IPv6)
936 hdrtbl
[4] = self
:calc_header_checksum(bufftbl
, len
, hdrtbl
, PROTO_UDP
)
938 return table.concat(hdrtbl
)
942 function DataPacket
:build_tcp_hdr(bufflen
, bufftbl
, seeking
)
943 local len
= bufflen
+ 20 -- 20 for size of TCP header
945 local local_seq
, remote_seq
947 local_seq
= self
.state
.packets
[self
.file_position
][LOCAL_SEQ
]
948 remote_seq
= self
.state
.packets
[self
.file_position
][REMOTE_SEQ
]
950 -- find socket/tcb info for this "stream", create if not found
951 if not self
.state
.tcb
[self
.tcbkey
] then
953 self
.state
.tcb
[self
.tcbkey
] = {}
956 self
.state
.packets
[self
.file_position
][LOCAL_SEQ
] = 1
957 self
.state
.packets
[self
.file_position
][REMOTE_SEQ
] = 1
958 -- set tcb to next sequence numbers, so that the correct "side"
959 -- acknowledges receiving these bytes
960 if self
.direction
== SENT
then
961 -- this packet is being sent, so local sequence increases next time
962 self
.state
.tcb
[self
.tcbkey
][TLOCAL_SEQ
] = bufflen
+1
963 self
.state
.tcb
[self
.tcbkey
][TREMOTE_SEQ
] = 1
965 -- this packet is being received, so remote sequence increases next time
966 -- and local side will acknowldge it next time
967 self
.state
.tcb
[self
.tcbkey
][TLOCAL_SEQ
] = 1
968 self
.state
.tcb
[self
.tcbkey
][TREMOTE_SEQ
] = bufflen
+1
971 -- stream already exists, so send the current tcb seqs and update for next time
972 if self
.direction
== SENT
then
973 -- this packet is being sent, so local sequence increases next time
974 local_seq
= self
.state
.tcb
[self
.tcbkey
][TLOCAL_SEQ
]
975 remote_seq
= self
.state
.tcb
[self
.tcbkey
][TREMOTE_SEQ
]
976 self
.state
.tcb
[self
.tcbkey
][TLOCAL_SEQ
] = local_seq
+ bufflen
978 -- this packet is being received, so the "local" seq number of the packet is the remote's seq really
979 local_seq
= self
.state
.tcb
[self
.tcbkey
][TREMOTE_SEQ
]
980 remote_seq
= self
.state
.tcb
[self
.tcbkey
][TLOCAL_SEQ
]
981 -- and remote seq needs to increase next time (remember local_seq is TREMOTE_SEQ)
982 self
.state
.tcb
[self
.tcbkey
][TREMOTE_SEQ
] = local_seq
+ bufflen
984 self
.state
.packets
[self
.file_position
][LOCAL_SEQ
] = local_seq
985 self
.state
.packets
[self
.file_position
][REMOTE_SEQ
] = remote_seq
990 dec2bin16(self
.source_port
), -- 1=source port bytes
991 dec2bin16(self
.dest_port
), -- 2=dest port bytes
992 dec2bin32(local_seq
), -- 3=sequence
993 dec2bin32(remote_seq
), -- 4=ack number
994 "\80\16\255\255", -- 5=offset, flags, window size
995 "\00\00", -- 6=checksum
996 "\00\00" -- 7=urgent pointer
1000 hdrtbl
[6] = self
:calc_header_checksum(bufftbl
, len
, hdrtbl
, PROTO_TCP
)
1002 return table.concat(hdrtbl
)
1005 function DataPacket
:build_packet(bufftbl
, bufflen
, seeking
)
1006 dprint2("DataPacket:build_packet() called with ptype=",self
.ptype
)
1007 if self
.ptype
== IPv4
then
1008 if self
.ttype
== UDP
then
1009 bufftbl
[2] = self
:build_udp_hdr(bufflen
)
1010 bufftbl
[1] = self
:build_ipv4_hdr(bufflen
+ 8, PROTO_UDP
, seeking
)
1011 elseif self
.ttype
== TCP
then
1012 bufftbl
[2] = self
:build_tcp_hdr(bufflen
, bufftbl
, seeking
)
1013 bufftbl
[1] = self
:build_ipv4_hdr(bufflen
+ 20, PROTO_TCP
, seeking
)
1015 elseif self
.ptype
== IPv6
then
1016 -- UDP for IPv6 requires checksum calculation, so we can't avoid more work
1017 if self
.ttype
== UDP
then
1018 bufftbl
[2] = self
:build_udp_hdr(bufflen
, bufftbl
)
1019 bufftbl
[1] = self
:build_ipv6_hdr(bufflen
+ 8, PROTO_UDP
)
1020 elseif self
.ttype
== TCP
then
1021 bufftbl
[2] = self
:build_tcp_hdr(bufflen
, bufftbl
, seeking
)
1022 bufftbl
[1] = self
:build_ipv6_hdr(bufflen
+ 20, PROTO_TCP
)
1025 dprint("DataPacket:build_packet: invalid packet type (neither IPv4 nor IPv6)")
1029 return table.concat(bufftbl
)
1032 -- for performance, we read each line into a table and concatenate it at end
1033 -- but it makes this code super ugly
1034 function DataPacket
:read_data(file
, frame
, line
, seeking
)
1035 local bufftbl
= { "", "" } -- 2 slots for ip and udp/tcp headers
1036 local index
= 3 -- start at third slot in array
1037 local comment
-- for any packet comments
1039 dprint2("DataPacket: read_data(): calling get_data")
1040 local bufflen
= self
:get_data(file
, line
, bufftbl
, index
)
1042 dprint("DataPacket: error getting ascii or binary data")
1046 local buff
= self
:build_packet(bufftbl
, bufflen
, seeking
)
1054 ----------------------------------------
1055 -- BinPacket class, for packets that the log file contains binary payload data for, such as MBCD
1057 local BinPacket
= {}
1058 local BinPacket_mt
= { __index
= BinPacket
}
1059 setmetatable( BinPacket
, DataPacket_mt
) -- make BinPacket inherit from DataPacket
1061 function BinPacket
.new(...)
1062 local new_class
= DataPacket
.new(...) -- the new instance
1063 setmetatable( new_class
, BinPacket_mt
) -- all instances share the same metatable
1067 function BinPacket
:get_comment_data(file
, line
, stop_pattern
)
1070 while line
and not line
:find(stop_pattern
) do
1072 comments
[#comments
+1] = line
1073 comments
[#comments
+1] = "\r\n"
1078 if #comments
> 0 then
1079 -- get rid of extra "\r\n"
1080 comments
[#comments
] = nil
1081 self
:set_comment(table.concat(comments
))
1087 function BinPacket
:get_data(file
, line
, bufftbl
, index
)
1088 local is_alg
= false
1090 local bufflen
, line
= self
:get_hex_data(file
, line
, bufftbl
, index
)
1092 -- now eat rest of message until delimiter or end of file
1093 -- we'll put them in comments
1094 line
= self
:get_comment_data(file
, line
, delim
)
1096 -- return the bufflen, which is the same as number of table entries we made
1100 ----------------------------------------
1101 -- DnsPacket class, for DNS packets (which are binary but with comments at top)
1103 local DnsPacket
= {}
1104 local DnsPacket_mt
= { __index
= DnsPacket
}
1105 setmetatable( DnsPacket
, BinPacket_mt
) -- make DnsPacket inherit from BinPacket
1107 function DnsPacket
.new(...)
1108 local new_class
= BinPacket
.new(...) -- the new instance
1109 setmetatable( new_class
, DnsPacket_mt
) -- all instances share the same metatable
1113 local binpacket_start_pattern
= "^ 0000: %x%x %x%x %x%x %x%x %x%x %x%x %x%x %x%x "
1114 function DnsPacket
:get_data(file
, line
, bufftbl
, index
)
1115 -- it's UDP regardless of what parse_header() thinks
1118 -- comments are at top instead of bottom of message
1119 line
= self
:get_comment_data(file
, line
, binpacket_start_pattern
)
1121 local bufflen
, line
= self
:get_hex_data(file
, line
, bufftbl
, index
)
1123 -- now eat rest of message until delimiter or end of file
1124 while line
and not line
:find(delim
) do
1128 -- return the bufflen, which is the same as number of table entries we made
1132 ----------------------------------------
1133 -- AsciiPacket class, for packets that the log file contains ascii payload data for
1135 local AsciiPacket
= {}
1136 local AsciiPacket_mt
= { __index
= AsciiPacket
}
1137 setmetatable( AsciiPacket
, DataPacket_mt
) -- make AsciiPacket inherit from DataPacket
1139 function AsciiPacket
.new(...)
1140 local new_class
= DataPacket
.new(...) -- the new instance
1141 setmetatable( new_class
, AsciiPacket_mt
) -- all instances share the same metatable
1145 function AsciiPacket
:get_data(file
, line
, bufftbl
, index
)
1146 return self
:get_ascii_data(file
, line
, bufftbl
, index
)
1150 ----------------------------------------
1151 -- To determine packet type, we peek at the first line of 'data' following the log
1152 -- message header. Its pattern determines the Packet object type.
1153 -- The following are the patterns we look for; if it doesn't match one of these,
1154 -- then it's an AsciiPacket:
1155 local packet_patterns
= {
1156 { "^ 0000: %x%x %x%x %x%x %x%x %x%x %x%x %x%x %x%x ", BinPacket
},
1157 { "^Packet:$", RawPacket
},
1158 { "^DNS Query %d+ flags=%d+ q=%d+ ans=%d+", DnsPacket
},
1159 { "^DNS Response %d+ flags=%d+ q=%d+ ans=%d+", DnsPacket
}
1161 -- indeces for above
1162 local PP_PATTERN
= 1
1165 local function get_packet_class(line
)
1166 for i
, t
in ipairs(packet_patterns
) do
1167 if line
:find(t
[PP_PATTERN
]) then
1168 dprint2("got class type=",i
)
1172 dprint2("got class type AsciiPacket")
1176 ----------------------------------------
1177 -- parses header line
1178 -- returns nil on failure
1179 -- the header lines look like this:
1180 -- Aug 10 14:30:11.134 On [1:544]10.201.145.237:5060 received from 10.210.1.193:5060
1181 -- this one has no phy/vlan info in brackets:
1182 -- Mar 6 13:39:06.122 On 127.0.0.1:2945 sent to 127.0.0.1:2944
1183 -- this one is IPv6:
1184 -- Aug 10 14:30:11.140 On [3:0][2620:0:60:8ac::102]:5060 sent to [2620:0:60:8ab::12]:5060
1185 -- this is from a tail'ed log output:
1186 -- 52:22.434 On [0:0]205.152.56.211:5060 received from 205.152.56.75:5060
1187 local loopback_pattern
= "^127%.0%.0%.%d+$"
1188 local function parse_header(state
, file
, line
, file_position
, seeking
)
1191 -- verify we've seen this packet before
1192 if not state
.packets
[file_position
] then
1193 dprint("parse_header: packet at file position ", file_position
, " not saved previously")
1197 -- first time through, create sub-table for the packet
1198 state
.packets
[file_position
] = {}
1201 -- get time info, and line match ending position
1202 local timestamp
, line_pos
= state
:get_timestamp(line
, file_position
, seeking
)
1203 if not timestamp
then
1204 -- see if it's a tail'ed log instead
1205 timestamp
, line_pos
= state
:get_tail_time(line
, file_position
, seeking
)
1208 if not timestamp
then
1209 dprint("parse_header: could not parse time portion")
1213 local ptype
, ttype
= IPv4
, UDP
1215 -- get phy/vlan if present
1216 -- first skip past time portion
1217 local phy
, vlan
, i
, j
, k
1218 line_pos
= line_pos
+ 1
1219 i
, j
, phy
, vlan
= line
:find(header_phy_pattern
, line_pos
)
1222 vlan
= tonumber(vlan
)
1223 line_pos
= j
-- skip past this portion for next match
1225 -- if there's no phy/vlan info, then assume it's TCP (unless it's loopback address we'll check later)
1229 -- get addresses and direction
1230 local local_ip
, local_port
, direction
, remote_ip
, remote_port
= line
:match(header_address_pattern
, line_pos
)
1231 if not local_ip
then
1233 local_ip
, local_port
, direction
, remote_ip
, remote_port
= line
:match(header_v6address_pattern
, line_pos
)
1234 if not local_ip
then
1235 dprint("parse_header: could not parse address portion")
1241 if local_ip
:find(loopback_pattern
) and remote_ip
:find(loopback_pattern
) then
1242 -- internal loopback packets never have phy/vlan but are always UDP messages (for all intents)
1246 -- override above decisions based on configuration
1251 direction
= get_direction(direction
)
1252 if direction
== nil then
1253 dprint("parse_header: failed to convert direction")
1257 local source_ip
, source_port
, dest_ip
, dest_port
= local_ip
, local_port
, remote_ip
, remote_port
1258 if direction
== RECV
then
1260 source_ip
, source_port
, dest_ip
, dest_port
= remote_ip
, remote_port
, local_ip
, local_port
1263 source_port
= tonumber(source_port
)
1264 dest_port
= tonumber(dest_port
)
1266 -- peek at next line to determine packet type
1267 local position
= file
:seek()
1269 dprint2("parse_header: peeking at line='", line
, "'")
1270 packet_class
= get_packet_class(line
)
1271 file
:seek("set", position
) -- go back
1273 dprint2("parse_header calling packet_class.new with:",
1274 tostring(timestamp
), direction
, source_ip
, source_port
,
1275 dest_ip
, dest_port
, ptype
, ttype
, file_position
)
1277 local packet
= packet_class
.new(state
, timestamp
, direction
, source_ip
, source_port
, dest_ip
, dest_port
, ptype
, ttype
, file_position
)
1279 dprint("parse_header: parser failed to create Packet object")
1282 if ttype
== TCP
then
1283 -- if the packet is tcp type, then set the key for TCB table lookup
1284 packet
:set_tcbkey(table.concat({ "[", local_ip
, "]:", local_port
, "->[", remote_ip
, "]:", remote_port
}))
1291 ----------------------------------------
1292 -- file handling functions for Wireshark to use
1294 -- The read_open is called by Wireshark once per file, to see if the file is this reader's type.
1295 -- It passes in (1) a File and (2) CaptureInfo object to this function
1296 -- Since there is no exact magic sequence to search for, we have to use heuristics to guess if the file
1297 -- is our type or not, which we do by parsing a message header.
1298 -- Since Wireshark uses the file cursor position for future reading of this file, we also have to seek back to the beginning
1299 -- so that our normal read() function works correctly.
1300 local function read_open(file
, capture
)
1301 dprint2("read_open called")
1302 -- save current position to return later
1303 local position
= file
:seek()
1305 local line
= file
:read()
1306 if not line
then return false end
1308 dprint2("read_open: got this line begin:\n'", line
, "'")
1310 line
, position
= skip_ahead(file
, line
, position
)
1311 if not line
then return false end
1313 dprint2("read_open: got this line after skip:\n'", line
, "', with position=", position
)
1315 local state
= State
.new()
1317 if parse_header(state
, file
, line
, position
) then
1318 dprint2("read_open success")
1320 file
:seek("set",position
)
1322 capture
.time_precision
= wtap_filetypes
.TSPREC_MSEC
-- for millisecond precision
1323 capture
.encap
= wtap
.RAW_IP
-- whole file is raw IP format
1324 capture
.snapshot_length
= 0 -- unknown snaplen
1325 capture
.comment
= "Oracle Acme Packet SBC message log"
1326 capture
.os
= "VxWorks or Linux"
1327 capture
.hardware
= "Oracle Acme Packet SBC"
1329 -- reset state variables
1330 capture
.private_table
= State
.new()
1332 dprint2("read_open returning true")
1336 dprint2("read_open returning false")
1340 ----------------------------------------
1341 -- this is used by both read() and seek_read()
1342 local function read_common(funcname
, file
, capture
, frame
, position
, seeking
)
1343 dprint2(funcname
, "read_common called")
1344 local state
= capture
.private_table
1347 dprint(funcname
, "error getting capture state")
1351 local line
= file
:read()
1353 dprint(funcname
, "hit end of file")
1356 line
, position
= skip_ahead(file
, line
, position
)
1358 if file
:read(0) ~= nil then
1359 dprint(funcname
, "did not hit end of file after skipping but ending anyway")
1361 dprint2(funcname
, "hit end of file after skipping")
1366 dprint2(funcname
, ": parsing line='", line
, "'")
1367 local phdr
= parse_header(state
, file
, line
, position
, seeking
)
1369 dprint(funcname
, "failed to parse header")
1375 dprint2(funcname
,": calling class object's read_data()")
1376 phdr
:read_data(file
, frame
, line
, seeking
)
1378 if not phdr
:set_wslua_fields(frame
) then
1379 dprint(funcname
, "failed to set Wireshark packet header info")
1383 dprint2(funcname
, "read_common returning position")
1387 ----------------------------------------
1388 -- Wireshark/tshark calls read() for each frame/record in the file
1389 -- It passes in (1) a File, (2) CaptureInfo, and (3) a FrameInfo object to this function
1390 -- It expects in return the file offset position the record starts at,
1391 -- or nil/false if there's an error or end-of-file is reached.
1392 -- The offset position is used later: wireshark remembers it and gives
1393 -- it to seek_read() at various random times
1394 local function read(file
, capture
, frame
)
1395 dprint2("read called")
1396 local position
= file
:seek()
1397 position
= read_common("read", file
, capture
, frame
, position
)
1398 if not position
then
1399 if file
:read(0) ~= nil then
1400 dprint("read failed to call read_common")
1402 dprint2("read: reached end of file")
1409 ----------------------------------------
1410 -- Wireshark/tshark calls seek_read() for each frame/record in the file, at random times
1411 -- It passes in (1) File, (2) CaptureInfo, (3) FrameInfo, and (4) the offset position number
1412 -- It expects in return true for successful parsing, or nil/false if there's an error.
1413 local function seek_read(file
, capture
, frame
, offset
)
1414 dprint2("seek_read called")
1415 file
:seek("set",offset
)
1416 if not read_common("seek_read", file
, capture
, frame
, offset
, true) then
1417 dprint("seek_read failed to call read_common")
1423 ----------------------------------------
1424 -- Wireshark/tshark calls read_close() when it's closing the file completely
1425 -- It passes in (1) a File and (2) CaptureInfo object to this function
1426 -- this is a good opportunity to clean up any state you may have created during
1428 -- In our case there *is* state to reset, but we only saved it in
1429 -- the capture.private_table, so Wireshark will clean it up for us.
1430 local function read_close(file
, capture
)
1431 dprint2("read_close called")
1435 ----------------------------------------
1436 -- An often unused function, Wireshark calls this when the sequential walk-through is over
1437 -- It passes in (1) a File and (2) CaptureInfo object to this function
1438 -- (i.e., no more calls to read(), only to seek_read()).
1439 -- In our case there *is* some state to reset, but we only saved it in
1440 -- the capture.private_table, so Wireshark will clean it up for us.
1441 local function seq_read_close(file
, capture
)
1442 dprint2("seq_read_close called")
1446 -- set above functions to the FileHandler
1447 fh
.read_open
= read_open
1449 fh
.seek_read
= seek_read
1450 fh
.read_close
= read_close
1451 fh
.seq_read_close
= seq_read_close
1452 fh
.extensions
= "log" -- this is just a hint
1454 -- and finally, register the FileHandler!
1455 register_filehandler(fh
)