-
Notifications
You must be signed in to change notification settings - Fork 1
/
readpcap
executable file
·282 lines (246 loc) · 9.06 KB
/
readpcap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
#! /usr/bin/env luajit
--
-- readpcap
-- Copyright (C) 2016-2017 Adrian Perez <aperez@igalia.com>
--
-- Distributed under terms of the Apache License 2.0.
--
local env_debug = os.getenv("READPCAP_DEBUG")
local function debug(fmt, ...) end
if env_debug ~= nil and env_debug ~= "0" then
debug = function (fmt, ...) io.stderr:write(fmt:format(...)) end
end
local pcap = require("examples.pcap")
local ndpi = require("ndpi")
local bit = require("bit")
local ffi = require("ffi")
local C = ffi.C
ffi.cdef [[
uint16_t ntohs (uint16_t);
uint32_t ntohl (uint32_t);
]]
local uint16_ptr_t = ffi.typeof("uint16_t*")
local function rd16(address)
return ffi.cast(uint16_ptr_t, address)[0]
end
local uint32_ptr_t = ffi.typeof("uint32_t*")
local function rd32(address)
return ffi.cast(uint32_ptr_t, address)[0]
end
local ipv4_address_format = "%d.%d.%d.%d"
local function format_ip(ipaddr)
return ipv4_address_format:format(bit.band(bit.rshift(ipaddr, 24), 0xFF),
bit.band(bit.rshift(ipaddr, 16), 0xFF),
bit.band(bit.rshift(ipaddr, 8), 0xFF),
bit.band(ipaddr, 0xFF))
end
local ETH_HEADER_SIZE = 14
local ETH_TYPE_OFFSET = 12
local ETH_TYPE_IPv4 = 0x0800
local ETH_TYPE_IPv6 = 0x86DD
local ETH_TYPE_VLAN = 0x8100
local IPv4_VER_IHL_OFFSET = 0
local IPv4_DSCP_ECN_OFFSET = 1
local IPv4_LEN_OFFSET = 2
local IPv4_FRAGID_OFFSET = 4
local IPv4_FLAGS_OFFSET = 6
local IPv4_TTL_OFFSET = 8
local IPv4_PROTO_OFFSET = 9
local IPv4_CHECKSUM_OFFSET = 10
local IPv4_SRC_ADDR_OFFSET = 12
local IPv4_DST_ADDR_OFFSET = 16
local IPv4_PROTO_TCP = 6
local IPv4_PROTO_UDP = 17
local TCP_HEADER_SIZE = 20
local TCP_SRC_PORT_OFFSET = 0
local TCP_DST_PORT_OFFSET = 2
local UDP_HEADER_SIZE = 8
local UDP_SRC_PORT_OFFSET = 0
local UDP_DST_PORT_OFFSET = 2
local TICK_RESOLUTION = 1000
local NUM_ROOTS = 512
local input_file = arg[1] or "examples/lamernews.pcap"
local input = pcap.file(input_file)
local link_type = input:data_link()
-- Instantiate a detection_module and enable all protocols
local detector = ndpi.detection_module(TICK_RESOLUTION)
detector:set_protocol_bitmask(ndpi.protocol_bitmask():set_all())
local num_packets, num_bytes, num_total_bytes, num_vlan = 0, 0, 0, 0
local pcap_start, pcap_end = nil, 0
local function calculate_type_and_offset(header, data)
if link_type == pcap.DLT_NULL then
if C.ntohl(rd32(data)) == 2 then
return ETH_TYPE_IPv4, 4
else
return ETH_TYPE_IPv6, 4
end
elseif link_type == pcap.DLT_EN10MB then
return C.ntohs(rd16(data + ETH_TYPE_OFFSET)), ETH_HEADER_SIZE
elseif link_type == pcap.DLT_LINUX_SLL then
return (lshift(data[14], 8) + data[15]), 16
else
return nil, nil
end
end
local flow_table = {}
local function get_flow(vlan_id, data, ip_offset)
local ver = data[ip_offset + IPv4_VER_IHL_OFFSET]
local ihl = bit.band(ver, 0x0F) * 4
local ver = bit.rshift(ver, 4)
debug("--- ver=%d, ihl=%d ---\n", ver, ihl)
if ver ~= 4 then
return nil
end
local length = C.ntohs(rd16(data + ip_offset + IPv4_LEN_OFFSET))
local proto = data[ip_offset + IPv4_PROTO_OFFSET]
local lo_ip = C.ntohl(rd32(data + ip_offset + IPv4_SRC_ADDR_OFFSET))
local hi_ip = C.ntohl(rd32(data + ip_offset + IPv4_DST_ADDR_OFFSET))
debug(" length: %d (%d)\n", length, length + ip_offset)
debug(" proto: %#x (TCP: %s, UDP: %s)\n", proto,
proto == IPv4_PROTO_TCP, proto == IPv4_PROTO_UDP)
debug(" src: %s\n", format_ip(lo_ip))
debug(" dst: %s\n", format_ip(hi_ip))
if lo_ip > hi_ip then
lo_ip, hi_ip = hi_ip, lo_ip
end
local payload_length = length - ihl
assert(payload_length >= 0)
local lo_port, hi_port = 0, 0
if proto == IPv4_PROTO_TCP or proto == IPv4_PROTO_UDP then
lo_port = C.ntohs(rd16(data + ip_offset + ihl + TCP_SRC_PORT_OFFSET))
hi_port = C.ntohs(rd16(data + ip_offset + ihl + TCP_DST_PORT_OFFSET))
debug("srcport: %d\n", lo_port)
debug("dstport: %d\n", hi_port)
if lo_port > hi_port then
lo_port, hi_port = hi_port, lo_port
end
end
local key = string.format("%04x%08x%08x%04x%04x%04x", vlan_id or 0,
lo_ip, hi_ip, proto, lo_port, hi_port)
debug(" flowid: %s\n", key)
local flow = flow_table[key]
if flow == nil then
flow = {
ndpi_flow = ndpi.flow();
ndpi_src_id = ndpi.id();
ndpi_dst_id = ndpi.id();
proto = proto;
vlan_id = vlan_id;
lo_ip = lo_ip;
hi_ip = hi_ip;
lo_port = lo_port;
hi_port = hi_port;
last_seen = 0;
num_packets = 0;
num_bytes = 0;
detected = ndpi.protocol.PROTOCOL_UNKNOWN;
master = ndpi.protocol.PROTOCOL_UNKNOWN;
}
flow_table[key] = flow
end
local src_id, dst_id = flow.ndpi_src_id, flow.ndpi_dst_id
if (lo_ip ~= flow.lo_ip or hi_ip ~= flow.hi_ip or
lo_port ~= flow.lo_port or hi_port ~= flow.hi_port)
then
src_id, dst_id = dst_id, src_id
end
return flow, src_id, dst_id, length
end
local function handle_packet(header, data)
local time = header.ts_sec * TICK_RESOLUTION + header.ts_usec / (1000000 / TICK_RESOLUTION)
if pcap_start == nil then
pcap_start = time
end
if pcap_end > time then
io.stderr:write("Timestamp (", tostring(time), "): bug in pcap file, repairing\n")
time = pcap_end
else
pcap_end = time
end
local eth_type, ip_offset = calculate_type_and_offset(header, data)
debug("[1;1m*** eth_type=%#x, ip_offset=%d[0;0m\n", eth_type, ip_offset)
if eth_type == nil then
return false
end
local vlan_id = nil
while true do
if eth_type == ETH_TYPE_VLAN then
vlan_id = C.ntohs(rd16(data + ip_offset))
eth_type = C.ntohs(rd16(data + ip_offset + 2))
ip_offset = ip_offset + 4
else
break
end
end
if vlan_id ~= nil then
num_vlan = num_vlan + 1
end
if eth_type ~= ETH_TYPE_IPv4 then
return false
end
local flow, src_id, dst_id, ip_size = get_flow(vlan_id, data, ip_offset)
if flow == nil then
return false
end
flow.num_packets = flow.num_packets + 1
flow.num_bytes = flow.num_bytes + header.orig_len
flow.last_seen = time
if flow.detected ~= ndpi.protocol.PROTOCOL_UNKNOWN then
if detector:extra_dissection_possible(flow.ndpi_flow) then
detector:process_packet(flow.ndpi_flow,
data + ip_offset,
ip_size,
time,
src_id,
dst_id)
end
return true
end
flow.master, flow.detected = detector:process_packet(flow.ndpi_flow,
data + ip_offset,
ip_size,
time,
src_id,
dst_id)
if flow.detected == ndpi.protocol.PROTOCOL_UNKNOWN and
((proto == IPv4_PROTO_UDP and flow.num_packets > 8) or
(proto == IPv4_PROTO_TCP and flow.num_packets > 10))
then
flow.master, flow.detected = detector:guess_undetected_protocol(flow.proto,
flow.lo_ip,
flow.lo_port,
flow.hi_ip,
flow.hi_port)
end
return true
end
for header, data in input:packets() do
num_packets = num_packets + 1
num_total_bytes = num_total_bytes + header.orig_len
if handle_packet(header, data) then
num_bytes = num_bytes + header.orig_len
end
end
print("nDPI version: " .. ndpi.lib_version.major .. "." .. ndpi.lib_version.minor)
print("Input: " .. input_file)
print("Flows:")
local num_flows, detected_flows = 0, 0
local line_format = "%15s:%-5d - %15s:%-5d pkts: %3d bytes: %8d %s"
for flowid, flow in pairs(flow_table) do
num_flows = num_flows + 1
if flow.detected ~= ndpi.protocol.PROTOCOL_UNKNOWN then
detected_flows = detected_flows + 1
end
local l7proto = ndpi.protocol[flow.detected]:match("^[^_]+_(.*)$")
if flow.master ~= ndpi.protocol.PROTOCOL_UNKNOWN then
l7proto = l7proto .. "/" .. ndpi.protocol[flow.master]:match("^[^_]+_(.*)$")
end
print(line_format:format(format_ip(flow.lo_ip), flow.lo_port,
format_ip(flow.hi_ip), flow.hi_port,
flow.num_packets,
flow.num_bytes,
l7proto))
end
print("Packets: " .. num_packets .. " (VLAN-tagged: " .. num_vlan .. ")")
print("Bytes: " .. num_total_bytes .. " (inspected: " .. num_bytes .. ")")
print("Flows: " .. num_flows .. " (identified: " .. detected_flows .. ")")