-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathzabbixagent.lua
228 lines (204 loc) · 10.1 KB
/
zabbixagent.lua
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
local plugin_info = {
version = "2023-08-06.1",
author = "Markku Leiniö",
repository = "https://github.com/markkuleinio/wireshark-zabbix-dissectors",
}
set_plugin_info(plugin_info)
if Dissector.get("zabbix") or Dissector.get("zabbixagent") then
local ignore_text = "Your Wireshark version already contains Zabbix/ZabbixAgent protocol dissector compiled in. " ..
"Therefore the ZabbixAgent dissector in your local Lua script is ignored:\n" ..
debug.getinfo(1, "S").source:sub(2) ..
"\nDelete/move the script to remove this message."
if gui_enabled() then
local win = TextWindow.new("Note from ZabbixAgent Lua dissector")
win:set(ignore_text)
else
print(ignore_text)
end
return
end
local zabbixagent_protocol = Proto("ZabbixAgent", "Zabbix Agent Protocol")
-- for some reason the protocol name is shown in UPPERCASE in Protocol column
-- (and in Proto.name), so let's define a string to override that
local PROTOCOL_NAME = "ZabbixAgent"
local p_header = ProtoField.string("zabbixagent.header", "Header", base.ASCII)
local p_flags = ProtoField.uint8("zabbixagent.flags", "Flags", base.HEX)
local p_data_length = ProtoField.uint32("zabbixagent.len", "Length", base.DEC)
local p_reserved = ProtoField.uint32("zabbixagent.reserved", "Reserved", base.DEC)
local p_request = ProtoField.string("zabbixagent.request", "Requested item", base.ASCII)
local p_response = ProtoField.string("zabbixagent.response", "Response", base.ASCII)
local p_time = ProtoField.float("zabbixagent.time", "Time since the request was sent")
zabbixagent_protocol.fields = {
p_header, p_flags, p_data_length, p_reserved, p_request, p_response, p_time,
}
local default_settings =
{
debug_level = DEBUG,
ports = "10050", -- the default TCP port for Zabbix
reassemble = true, -- whether we try reassembly or not
info_text = true, -- show our own Info column data instead of TCP defaults
ports_in_info = true, -- show TCP ports in Info column
}
local timestamps = {}
local function doDissect_pre40(buffer, pktinfo, tree)
-- dissect a pre-4.0 passive request, no header
local pktlength = buffer:len()
pktinfo.cols.protocol = PROTOCOL_NAME
local PORTS = ""
if default_settings.ports_in_info then
PORTS = " (" .. pktinfo.src_port .. " → " .. pktinfo.dst_port .. ")"
end
local info_text = "Zabbix Passive Agent Request" .. PORTS
if default_settings.info_text then
pktinfo.cols.info = info_text
end
local subtree = tree:add(zabbixagent_protocol, buffer(), info_text)
-- don't include the newline in the field
subtree:add(p_request, buffer(0,pktlength-1))
-- make hash string for the request
local hash_string = tostring(pktinfo.src) .. ":" .. tostring(pktinfo.src_port) ..
"-" .. tostring(pktinfo.dst) .. ":" .. tostring(pktinfo.dst_port)
-- save the request timestamp
timestamps[hash_string] = pktinfo.abs_ts
end
local function doDissect(buffer, pktinfo, tree)
-- dissect the packet, with ZBXD header
pktinfo.cols.protocol = PROTOCOL_NAME
-- get the data length and reserved fields (32-bit little-endian unsigned integers)
local data_length = buffer(5,4):le_uint()
local reserved = buffer(9,4):le_uint()
local LEN = "Len: " .. data_length
local LEN_AND_PORTS = "Len=" .. data_length
if default_settings.ports_in_info then
LEN_AND_PORTS = LEN_AND_PORTS .. " (" .. pktinfo.src_port .. " → " .. pktinfo.dst_port .. ")"
end
-- default texts, overridden later if we recognize the port
local tree_text = "Zabbix Passive Agent, " .. LEN
local info_text = "Zabbix Passive Agent, " .. LEN_AND_PORTS
local is_request = false
local is_response = false
if pktinfo.dst_port == tonumber(default_settings.ports) then
-- this is from server to passive agent
-- note: only matches if there is a single TCP port in the Ports setting
is_request = true
tree_text = "Zabbix Passive Agent Request, " .. LEN
info_text = "Zabbix Passive Agent Request, " .. LEN_AND_PORTS
elseif pktinfo.src_port == tonumber(default_settings.ports) then
-- this is from passive agent to server
-- note: only matches if there is a single TCP port in the Ports setting
is_response = true
tree_text = "Zabbix Passive Agent Response, " .. LEN
info_text = "Zabbix Passive Agent Response, " .. LEN_AND_PORTS
end
if default_settings.info_text then
pktinfo.cols.info = info_text
end
local subtree = tree:add(zabbixagent_protocol, buffer(), tree_text)
subtree:add_le(p_header, buffer(0,4))
subtree:add_le(p_flags, buffer(4,1))
subtree:add_le(p_data_length, buffer(5,4))
subtree:add_le(p_reserved, buffer(9,4))
if is_request then
subtree:add(p_request, buffer(13))
-- make hash string for the request
local hash_string = tostring(pktinfo.src) .. ":" .. tostring(pktinfo.src_port) ..
"-" .. tostring(pktinfo.dst) .. ":" .. tostring(pktinfo.dst_port)
-- save the request timestamp
timestamps[hash_string] = pktinfo.abs_ts
elseif is_response then
subtree:add(p_response, buffer(13))
-- make hash string for the response
local hash_string = tostring(pktinfo.dst) .. ":" .. tostring(pktinfo.dst_port) ..
"-" .. tostring(pktinfo.src) .. ":" .. tostring(pktinfo.src_port)
local request_timestamp = timestamps[hash_string]
if request_timestamp then
local response_time = pktinfo.abs_ts - request_timestamp
subtree:add(p_time, response_time, "Time since request:", string.format("%.6f", response_time), "seconds"):set_generated()
end
else
subtree:add(buffer(13), "(Request or response, port was not matched)")
end
end
function zabbixagent_protocol.dissector(buffer, pktinfo, tree)
local ZBXD_HEADER_LEN = 13
local pktlength = buffer:len()
if pktlength < 4 or buffer(0,4):string() ~= "ZBXD" then
-- no ZBXD signature, so this is an old-style (pre-4.0) server request or a continuation,
-- or maybe this is encrypted, or not Zabbix at all
-- pattern should match the allowed Zabbix item key format, like eth.port[123], with
-- a newline at the end
local pattern = "^[%w-_.,%[%]\"/]+\n$"
if not string.match(buffer(0):string(), pattern) then
-- does not look like a valid pre-4.0 passive request, just return 0 to allow
-- other dissectors to continue
return 0
end
-- otherwise do the dissect with no header present
doDissect_pre40(buffer, pktinfo, tree)
else
-- header was found, so this should be a Zabbix 4.0+ passive agent or
-- a response from pre-4.0 agent
-- get the Zabbix data length (32-bit LE integer)
local data_length = buffer(5,4):le_uint()
local bytes_needed = ZBXD_HEADER_LEN + data_length
if bytes_needed > pktlength and default_settings.reassemble then
-- we need more bytes than is in the current segment, try to get more
pktinfo.desegment_offset = 0
pktinfo.desegment_len = data_length + ZBXD_HEADER_LEN - pktlength
-- dissect anyway to show something if the TCP setting "Allow subdissector to
-- reassemble TCP streams" is disabled
doDissect(buffer, pktinfo, tree)
return
end
-- now we have the data to dissect, let's do it
doDissect(buffer, pktinfo, tree)
end
return
end
function zabbixagent_protocol.init()
-- empty the timestamps table
timestamps = {}
end
local function enableDissector()
DissectorTable.get("tcp.port"):add(default_settings.ports, zabbixagent_protocol)
-- supports also TLS decryption if the session keys are configured in Wireshark
DissectorTable.get("tls.port"):add(default_settings.ports, zabbixagent_protocol)
end
-- call it now, because we're enabled by default
enableDissector()
local function disableDissector()
DissectorTable.get("tcp.port"):remove(default_settings.ports, zabbixagent_protocol)
DissectorTable.get("tls.port"):remove(default_settings.ports, zabbixagent_protocol)
end
-- register our preferences
zabbixagent_protocol.prefs.reassemble = Pref.bool("Reassemble Zabbix Agent messages spanning multiple TCP segments",
default_settings.reassemble, "Whether the Zabbix Agent dissector should reassemble messages " ..
"spanning multiple TCP segments. To use this option, you must also enable \"Allow subdissectors to " ..
"reassemble TCP streams\" in the TCP protocol settings")
zabbixagent_protocol.prefs.info_text = Pref.bool("Show Zabbix protocol data in Info column",
default_settings.info_text, "Disable this to show the default TCP protocol data in the Info column")
zabbixagent_protocol.prefs.ports_in_info = Pref.bool("Show TCP ports in Info column",
default_settings.ports_in_info, "Disable this to have only Zabbix data in the Info column")
zabbixagent_protocol.prefs.ports = Pref.range("Port(s)", default_settings.ports,
"Set the TCP port(s) for Zabbix Agent, default is 10050", 65535)
zabbixagent_protocol.prefs.text = Pref.statictext("This dissector is written in Lua by Markku Leiniö. Version: " .. plugin_info.version, "")
-- the function for handling preferences being changed
function zabbixagent_protocol.prefs_changed()
if default_settings.reassemble ~= zabbixagent_protocol.prefs.reassemble then
default_settings.reassemble = zabbixagent_protocol.prefs.reassemble
-- capture file reload needed
reload()
elseif default_settings.info_text ~= zabbixagent_protocol.prefs.info_text then
default_settings.info_text = zabbixagent_protocol.prefs.info_text
-- capture file reload needed
reload()
elseif default_settings.ports_in_info ~= zabbixagent_protocol.prefs.ports_in_info then
default_settings.ports_in_info = zabbixagent_protocol.prefs.ports_in_info
-- capture file reload needed
reload()
elseif default_settings.ports ~= zabbixagent_protocol.prefs.ports then
disableDissector()
default_settings.ports = zabbixagent_protocol.prefs.ports
enableDissector()
end
end