2 -- Packet dissector for the UDP-based Minetest protocol
3 -- Copy this to $HOME/.wireshark/plugins/
8 -- Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>
10 -- This program is free software; you can redistribute it and/or modify
11 -- it under the terms of the GNU General Public License as published by
12 -- the Free Software Foundation; either version 2 of the License, or
13 -- (at your option) any later version.
15 -- This program is distributed in the hope that it will be useful,
16 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
17 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 -- GNU General Public License for more details.
20 -- You should have received a copy of the GNU General Public License along
21 -- with this program; if not, write to the Free Software Foundation, Inc.,
22 -- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
27 -- Part 1: Client command dissectors (TOSERVER_*)
28 -- Part 2: Server command dissectors (TOCLIENT_*)
29 -- Part 3: Wrapper protocol subdissectors
30 -- Part 4: Wrapper protocol main dissector
31 -- Part 5: Utility functions
36 --------------------------------------------
38 -- Client command dissectors (TOSERVER_*) --
39 --------------------------------------------
41 minetest_client_commands = {}
42 minetest_client_obsolete = {}
47 local f_ser_fmt = ProtoField.uint8("minetest.client.init_ser_version",
48 "Maximum serialization format version", base.DEC)
49 local f_player_name = ProtoField.stringz("minetest.client.init_player_name", "Player Name")
50 local f_password = ProtoField.stringz("minetest.client.init_password", "Password")
51 local f_version = ProtoField.uint16("minetest.client.init_version", "Version", base.DEC)
53 minetest_client_commands[0x10] = {
54 "INIT", -- Command name
55 53, -- Minimum message length including code
56 { f_ser_fmt, -- List of fields [optional]
60 function(buffer, pinfo, tree, t) -- Dissector function [optional]
61 t:add(f_ser_fmt, buffer(2,1))
62 t:add(f_player_name, buffer(3,20))
63 t:add(f_password, buffer(23,28))
64 t:add(f_version, buffer(51,2))
71 minetest_client_commands[0x11] = { "INIT2", 2 }
73 -- TOSERVER_GETBLOCK (obsolete)
75 minetest_client_commands[0x20] = { "GETBLOCK", 2 }
76 minetest_client_obsolete[0x20] = true
78 -- TOSERVER_ADDNODE (obsolete)
80 minetest_client_commands[0x21] = { "ADDNODE", 2 }
81 minetest_client_obsolete[0x21] = true
83 -- TOSERVER_REMOVENODE (obsolete)
85 minetest_client_commands[0x22] = { "REMOVENODE", 2 }
86 minetest_client_obsolete[0x22] = true
91 local f_x = ProtoField.int32("minetest.client.playerpos_x", "Position X", base.DEC)
92 local f_y = ProtoField.int32("minetest.client.playerpos_y", "Position Y", base.DEC)
93 local f_z = ProtoField.int32("minetest.client.playerpos_z", "Position Z", base.DEC)
94 local f_speed_x = ProtoField.int32("minetest.client.playerpos_speed_x", "Speed X", base.DEC)
95 local f_speed_y = ProtoField.int32("minetest.client.playerpos_speed_y", "Speed Y", base.DEC)
96 local f_speed_z = ProtoField.int32("minetest.client.playerpos_speed_z", "Speed Z", base.DEC)
97 local f_pitch = ProtoField.int32("minetest.client.playerpos_pitch", "Pitch", base.DEC)
98 local f_yaw = ProtoField.int32("minetest.client.playerpos_yaw", "Yaw", base.DEC)
100 minetest_client_commands[0x23] = {
102 { f_x, f_y, f_z, f_speed_x, f_speed_y, f_speed_z, f_pitch, f_yaw },
103 function(buffer, pinfo, tree, t)
104 t:add(f_x, buffer(2,4))
105 t:add(f_y, buffer(6,4))
106 t:add(f_z, buffer(10,4))
107 t:add(f_speed_x, buffer(14,4))
108 t:add(f_speed_y, buffer(18,4))
109 t:add(f_speed_z, buffer(22,4))
110 t:add(f_pitch, buffer(26,4))
111 t:add(f_yaw, buffer(30,4))
116 -- TOSERVER_GOTBLOCKS
119 local f_count = ProtoField.uint8("minetest.client.gotblocks_count", "Count", base.DEC)
120 local f_block = ProtoField.bytes("minetest.client.gotblocks_block", "Block", base.DEC)
121 local f_x = ProtoField.int16("minetest.client.gotblocks_x", "Block position X", base.DEC)
122 local f_y = ProtoField.int16("minetest.client.gotblocks_y", "Block position Y", base.DEC)
123 local f_z = ProtoField.int16("minetest.client.gotblocks_z", "Block position Z", base.DEC)
125 minetest_client_commands[0x24] = {
127 { f_count, f_block, f_x, f_y, f_z },
128 function(buffer, pinfo, tree, t)
129 t:add(f_count, buffer(2,1))
130 local count = buffer(2,1):uint()
131 if minetest_check_length(buffer, 3 + 6*count, t) then
132 pinfo.cols.info:append(" * " .. count)
134 for index = 0, count - 1 do
135 local pos = 3 + 6*index
136 local t2 = t:add(f_block, buffer(pos, 6))
137 t2:set_text("Block, X: " .. buffer(pos, 2):int()
138 .. ", Y: " .. buffer(pos + 2, 2):int()
139 .. ", Z: " .. buffer(pos + 4, 2):int())
140 t2:add(f_x, buffer(pos, 2))
141 t2:add(f_y, buffer(pos + 2, 2))
142 t2:add(f_z, buffer(pos + 4, 2))
149 -- TOSERVER_DELETEDBLOCKS
153 local f_count = ProtoField.uint8("minetest.client.deletedblocks_count", "Count", base.DEC)
154 local f_block = ProtoField.bytes("minetest.client.deletedblocks_block", "Block", base.DEC)
155 local f_x = ProtoField.int16("minetest.client.deletedblocks_x", "Block position X", base.DEC)
156 local f_y = ProtoField.int16("minetest.client.deletedblocks_y", "Block position Y", base.DEC)
157 local f_z = ProtoField.int16("minetest.client.deletedblocks_z", "Block position Z", base.DEC)
159 minetest_client_commands[0x25] = {
161 { f_count, f_block, f_x, f_y, f_z },
162 function(buffer, pinfo, tree, t)
163 t:add(f_count, buffer(2,1))
164 local count = buffer(2,1):uint()
165 if minetest_check_length(buffer, 3 + 6*count, t) then
166 pinfo.cols.info:append(" * " .. count)
168 for index = 0, count - 1 do
169 local pos = 3 + 6*index
170 local t2 = t:add(f_block, buffer(pos, 6))
171 t2:set_text("Block, X: " .. buffer(pos, 2):int()
172 .. ", Y: " .. buffer(pos + 2, 2):int()
173 .. ", Z: " .. buffer(pos + 4, 2):int())
174 t2:add(f_x, buffer(pos, 2))
175 t2:add(f_y, buffer(pos + 2, 2))
176 t2:add(f_z, buffer(pos + 4, 2))
183 -- TOSERVER_ADDNODE_FROM_INVENTORY (obsolete)
185 minetest_client_commands[0x26] = { "ADDNODE_FROM_INVENTORY", 2 }
186 minetest_client_obsolete[0x26] = true
188 -- TOSERVER_CLICK_OBJECT
197 local f_button = ProtoField.uint8("minetest.client.click_object_button", "Button", base.DEC, vs_button)
198 local f_blockpos_x = ProtoField.int16("minetest.client.click_object_blockpos_x", "Block position X", base.DEC)
199 local f_blockpos_y = ProtoField.int16("minetest.client.click_object_blockpos_y", "Block position Y", base.DEC)
200 local f_blockpos_z = ProtoField.int16("minetest.client.click_object_blockpos_z", "Block position Z", base.DEC)
201 local f_id = ProtoField.int16("minetest.client.click_object_id", "ID", base.DEC)
202 local f_item = ProtoField.uint16("minetest.client.click_object_item", "Item", base.DEC)
204 minetest_client_commands[0x27] = {
206 { f_button, f_blockpos_x, f_blockpos_y, f_blockpos_z, f_id, f_item },
207 function(buffer, pinfo, tree, t)
208 t:add(f_button, buffer(2,1))
209 t:add(f_blockpos_x, buffer(3,2))
210 t:add(f_blockpos_y, buffer(5,2))
211 t:add(f_blockpos_z, buffer(7,2))
212 t:add(f_id, buffer(9,2))
213 t:add(f_item, buffer(11,2))
218 -- TOSERVER_GROUND_ACTION
222 [0] = "Start digging",
224 [2] = "Stop digging",
225 [3] = "Digging completed"
228 local f_action = ProtoField.uint8("minetest.client.ground_action", "Action", base.DEC, vs_action)
229 local f_nodepos_undersurface_x = ProtoField.int16(
230 "minetest.client.ground_action_nodepos_undersurface_x",
231 "Node position (under surface) X")
232 local f_nodepos_undersurface_y = ProtoField.int16(
233 "minetest.client.ground_action_nodepos_undersurface_y",
234 "Node position (under surface) Y")
235 local f_nodepos_undersurface_z = ProtoField.int16(
236 "minetest.client.ground_action_nodepos_undersurface_z",
237 "Node position (under surface) Z")
238 local f_nodepos_abovesurface_x = ProtoField.int16(
239 "minetest.client.ground_action_nodepos_abovesurface_x",
240 "Node position (above surface) X")
241 local f_nodepos_abovesurface_y = ProtoField.int16(
242 "minetest.client.ground_action_nodepos_abovesurface_y",
243 "Node position (above surface) Y")
244 local f_nodepos_abovesurface_z = ProtoField.int16(
245 "minetest.client.ground_action_nodepos_abovesurface_z",
246 "Node position (above surface) Z")
247 local f_item = ProtoField.uint16("minetest.client.ground_action_item", "Item")
249 minetest_client_commands[0x28] = {
252 f_nodepos_undersurface_x,
253 f_nodepos_undersurface_y,
254 f_nodepos_undersurface_z,
255 f_nodepos_abovesurface_x,
256 f_nodepos_abovesurface_y,
257 f_nodepos_abovesurface_z,
259 function(buffer, pinfo, tree, t)
260 t:add(f_action, buffer(2,1))
261 t:add(f_nodepos_undersurface_x, buffer(3,2))
262 t:add(f_nodepos_undersurface_y, buffer(5,2))
263 t:add(f_nodepos_undersurface_z, buffer(7,2))
264 t:add(f_nodepos_abovesurface_x, buffer(9,2))
265 t:add(f_nodepos_abovesurface_y, buffer(11,2))
266 t:add(f_nodepos_abovesurface_z, buffer(13,2))
267 t:add(f_item, buffer(15,2))
272 -- TOSERVER_RELEASE (obsolete)
274 minetest_client_commands[0x29] = { "RELEASE", 2 }
275 minetest_client_obsolete[0x29] = true
277 -- TOSERVER_SIGNTEXT (old signs)
278 -- TODO: Test this or mark obsolete
281 local f_blockpos_x = ProtoField.int16("minetest.client.signtext_blockpos_x", "Block position X", base.DEC)
282 local f_blockpos_y = ProtoField.int16("minetest.client.signtext_blockpos_y", "Block position Y", base.DEC)
283 local f_blockpos_z = ProtoField.int16("minetest.client.signtext_blockpos_z", "Block position Z", base.DEC)
284 local f_id = ProtoField.int16("minetest.client.signtext_id", "ID", base.DEC)
285 local f_textlen = ProtoField.uint16("minetest.client.signtext_textlen", "Text length", base.DEC)
286 local f_text = ProtoField.string("minetest.client.signtext_text", "Text")
288 minetest_client_commands[0x30] = {
290 { f_blockpos_x, f_blockpos_y, f_blockpos_z, f_id, f_textlen, f_text },
291 function(buffer, pinfo, tree, t)
292 t:add(f_blockpos_x, buffer(2,2))
293 t:add(f_blockpos_y, buffer(4,2))
294 t:add(f_blockpos_z, buffer(6,2))
295 t:add(f_id, buffer(8,2))
296 t:add(f_textlen, buffer(10,2))
297 local textlen = buffer(10,2):uint()
298 if minetest_check_length(buffer, 12 + textlen, t) then
299 t:add(f_text, buffer, buffer(12,textlen))
305 -- TOSERVER_INVENTORY_ACTION
308 local f_action = ProtoField.string("minetest.client.inventory_action", "Action")
310 minetest_client_commands[0x31] = {
311 "INVENTORY_ACTION", 2,
313 function(buffer, pinfo, tree, t)
314 t:add(f_action, buffer(2, buffer:len() - 2))
319 -- TOSERVER_CHAT_MESSAGE
322 local f_length = ProtoField.uint16("minetest.client.chat_message_length", "Length", base.DEC)
323 local f_message = ProtoField.string("minetest.client.chat_message", "Message")
325 minetest_client_commands[0x32] = {
327 { f_length, f_message },
328 function(buffer, pinfo, tree, t)
329 t:add(f_length, buffer(2,2))
330 local textlen = buffer(2,2):uint()
331 if minetest_check_length(buffer, 4 + textlen*2, t) then
332 t:add(f_message, minetest_convert_utf16(buffer(4, textlen*2), "Converted chat message"))
338 -- TOSERVER_SIGNNODETEXT
341 local f_pos_x = ProtoField.int16("minetest.client.signnodetext_pos_x", "Block position X", base.DEC)
342 local f_pos_y = ProtoField.int16("minetest.client.signnodetext_pos_y", "Block position Y", base.DEC)
343 local f_pos_z = ProtoField.int16("minetest.client.signnodetext_pos_z", "Block position Z", base.DEC)
344 local f_textlen = ProtoField.uint16("minetest.client.signnodetext_textlen", "Text length", base.DEC)
345 local f_text = ProtoField.string("minetest.client.signnodetext_text", "Text")
347 minetest_client_commands[0x33] = {
349 { f_pos_x, f_pos_y, f_pos_z, f_textlen, f_text },
350 function(buffer, pinfo, tree, t)
351 t:add(f_pos_x, buffer(2,2))
352 t:add(f_pos_y, buffer(4,2))
353 t:add(f_pos_z, buffer(6,2))
354 t:add(f_textlen, buffer(8,2))
355 local textlen = buffer(8,2):uint()
356 if minetest_check_length(buffer, 10 + textlen, t) then
357 t:add(f_text, buffer(10, textlen))
363 -- TOSERVER_CLICK_ACTIVEOBJECT
371 local f_button = ProtoField.uint8("minetest.client.click_activeobject_button", "Button", base.DEC, vs_button)
372 local f_id = ProtoField.uint16("minetest.client.click_activeobject_id", "ID", base.DEC)
373 local f_item = ProtoField.uint16("minetest.client.click_activeobject_item", "Item", base.DEC)
375 minetest_client_commands[0x34] = {
376 "CLICK_ACTIVEOBJECT", 7,
377 { f_button, f_id, f_item },
378 function(buffer, pinfo, tree, t)
379 t:add(f_button, buffer(2,1))
380 t:add(f_id, buffer(3,2))
381 t:add(f_item, buffer(5,2))
389 local f_amount = ProtoField.uint8("minetest.client.damage_amount", "Amount", base.DEC)
391 minetest_client_commands[0x35] = {
394 function(buffer, pinfo, tree, t)
395 t:add(f_amount, buffer(2,1))
403 local f_old_password = ProtoField.string("minetest.client.password_old", "Old password")
404 local f_new_password = ProtoField.string("minetest.client.password_new", "New password")
406 minetest_client_commands[0x36] = {
408 { f_old_password, f_new_password },
409 function(buffer, pinfo, tree, t)
410 t:add(f_old_password, buffer(2,28))
411 t:add(f_new_password, buffer(30,28))
416 -- TOSERVER_PLAYERITEM
419 local f_item = ProtoField.uint16("minetest.client.playeritem_item", "Wielded item")
421 minetest_client_commands[0x37] = {
424 function(buffer, pinfo, tree, t)
425 t:add(f_item, buffer(2,2))
432 minetest_client_commands[0x38] = { "RESPAWN", 2 }
437 --------------------------------------------
439 -- Server command dissectors (TOCLIENT_*) --
440 --------------------------------------------
442 minetest_server_commands = {}
443 minetest_server_obsolete = {}
448 local f_version = ProtoField.uint8("minetest.server.init_version", "Deployed version", base.DEC)
449 local f_pos_x = ProtoField.int16("minetest.server.init_pos_x", "Position X", base.DEC)
450 local f_pos_y = ProtoField.int16("minetest.server.init_pos_y", "Position Y", base.DEC)
451 local f_pos_z = ProtoField.int16("minetest.server.init_pos_x", "Position Z", base.DEC)
452 local f_map_seed = ProtoField.uint64("minetest.server.init_map_seed", "Map seed", base.DEC)
454 minetest_server_commands[0x10] = {
456 { f_version, f_pos_x, f_pos_y, f_pos_z, f_map_seed },
457 function(buffer, pinfo, tree, t)
458 t:add(f_version, buffer(2,1))
459 t:add(f_pos_x, buffer(3,2))
460 t:add(f_pos_y, buffer(5,2))
461 t:add(f_pos_z, buffer(7,2))
462 t:add(f_map_seed, buffer(9,8))
467 -- TOCLIENT_BLOCKDATA
470 local f_x = ProtoField.int16("minetest.server.blockdata_x", "Block position X", base.DEC)
471 local f_y = ProtoField.int16("minetest.server.blockdata_y", "Block position Y", base.DEC)
472 local f_z = ProtoField.int16("minetest.server.blockdata_z", "Block position Z", base.DEC)
473 local f_data = ProtoField.bytes("minetest.server.blockdata_block", "Serialized MapBlock")
475 minetest_server_commands[0x20] = {
477 { f_x, f_y, f_z, f_data },
478 function(buffer, pinfo, tree, t)
479 t:add(f_x, buffer(2,2))
480 t:add(f_y, buffer(4,2))
481 t:add(f_z, buffer(6,2))
482 t:add(f_data, buffer(8, buffer:len() - 8))
490 local f_x = ProtoField.int16("minetest.server.addnode_x", "Position X", base.DEC)
491 local f_y = ProtoField.int16("minetest.server.addnode_y", "Position Y", base.DEC)
492 local f_z = ProtoField.int16("minetest.server.addnode_z", "Position Z", base.DEC)
493 local f_data = ProtoField.bytes("minetest.server.addnode_node", "Serialized MapNode")
495 minetest_server_commands[0x21] = {
497 { f_x, f_y, f_z, f_data },
498 function(buffer, pinfo, tree, t)
499 t:add(f_x, buffer(2,2))
500 t:add(f_y, buffer(4,2))
501 t:add(f_z, buffer(6,2))
502 t:add(f_data, buffer(8, buffer:len() - 8))
507 -- TOCLIENT_REMOVENODE
510 local f_x = ProtoField.int16("minetest.server.removenode_x", "Position X", base.DEC)
511 local f_y = ProtoField.int16("minetest.server.removenode_y", "Position Y", base.DEC)
512 local f_z = ProtoField.int16("minetest.server.removenode_z", "Position Z", base.DEC)
514 minetest_server_commands[0x22] = {
517 function(buffer, pinfo, tree, t)
518 t:add(f_x, buffer(2,2))
519 t:add(f_y, buffer(4,2))
520 t:add(f_z, buffer(6,2))
525 -- TOCLIENT_PLAYERPOS (obsolete)
527 minetest_server_commands[0x23] = { "PLAYERPOS", 2 }
528 minetest_server_obsolete[0x23] = true
530 -- TOCLIENT_PLAYERINFO
533 local f_count = ProtoField.uint16("minetest.server.playerinfo_count", "Count", base.DEC)
534 local f_player = ProtoField.bytes("minetest.server.playerinfo_player", "Player", base.DEC)
535 local f_peer_id = ProtoField.uint16("minetest.server.playerinfo_peer_id", "Peer ID", base.DEC)
536 local f_name = ProtoField.string("minetest.server.playerinfo_name", "Name")
538 minetest_server_commands[0x24] = {
540 { f_count, f_player, f_peer_id, f_name },
541 function(buffer, pinfo, tree, t)
544 for pos = 2, buffer:len() - 22, 22 do -- does lua have integer division?
547 t:add(f_count, count):set_generated()
548 t:set_len(2 + 22 * count)
549 pinfo.cols.info:append(" * " .. count)
550 for index = 0, count - 1 do
551 local pos = 2 + 22 * index
552 local t2 = t:add(f_player, buffer(pos, 22))
553 t2:set_text("Player, ID: " .. buffer(pos, 2):uint()
554 .. ", Name: " .. buffer(pos + 2, 20):string())
555 t2:add(f_peer_id, buffer(pos, 2))
556 t2:add(f_name, buffer(pos + 2, 20))
562 -- TOCLIENT_OPT_BLOCK_NOT_FOUND (obsolete)
564 minetest_server_commands[0x25] = { "OPT_BLOCK_NOT_FOUND", 2 }
565 minetest_server_obsolete[0x25] = true
567 -- TOCLIENT_SECTORMETA (obsolete)
569 minetest_server_commands[0x26] = { "SECTORMETA", 2 }
570 minetest_server_obsolete[0x26] = true
572 -- TOCLIENT_INVENTORY
575 local f_inventory = ProtoField.string("minetest.server.inventory", "Inventory")
577 minetest_server_commands[0x27] = {
580 function(buffer, pinfo, tree, t)
581 t:add(f_inventory, buffer(2, buffer:len() - 2))
586 -- TOCLIENT_OBJECTDATA
589 local f_player_count = ProtoField.uint16("minetest.server.objectdata_player_count",
590 "Count of player positions", base.DEC)
591 local f_player = ProtoField.bytes("minetest.server.objectdata_player", "Player position")
592 local f_peer_id = ProtoField.uint16("minetest.server.objectdata_player_peer_id", "Peer ID")
593 local f_x = ProtoField.int32("minetest.server.objectdata_player_x", "Position X", base.DEC)
594 local f_y = ProtoField.int32("minetest.server.objectdata_player_y", "Position Y", base.DEC)
595 local f_z = ProtoField.int32("minetest.server.objectdata_player_z", "Position Z", base.DEC)
596 local f_speed_x = ProtoField.int32("minetest.server.objectdata_player_speed_x", "Speed X", base.DEC)
597 local f_speed_y = ProtoField.int32("minetest.server.objectdata_player_speed_y", "Speed Y", base.DEC)
598 local f_speed_z = ProtoField.int32("minetest.server.objectdata_player_speed_z", "Speed Z", base.DEC)
599 local f_pitch = ProtoField.int32("minetest.server.objectdata_player_pitch", "Pitch", base.DEC)
600 local f_yaw = ProtoField.int32("minetest.server.objectdata_player_yaw", "Yaw", base.DEC)
601 local f_block_count = ProtoField.uint16("minetest.server.objectdata_block_count",
602 "Count of blocks", base.DEC)
604 minetest_server_commands[0x28] = {
606 { f_player_count, f_player, f_peer_id, f_x, f_y, f_z,
607 f_speed_x, f_speed_y, f_speed_z,f_pitch, f_yaw,
609 function(buffer, pinfo, tree, t)
612 local player_count_pos = 2
613 local player_count = buffer(player_count_pos, 2):uint()
614 t:add(f_player_count, buffer(player_count_pos, 2))
616 local block_count_pos = player_count_pos + 2 + 34 * player_count
617 if not minetest_check_length(buffer, block_count_pos + 2, t) then
621 for index = 0, player_count - 1 do
622 pos = player_count_pos + 2 + 34 * index
623 t2 = t:add(f_player, buffer(pos, 34))
624 t2:set_text("Player position, ID: " .. buffer(pos, 2):uint())
625 t2:add(f_peer_id, buffer(pos, 2))
626 t2:add(f_x, buffer(pos + 2, 4))
627 t2:add(f_y, buffer(pos + 6, 4))
628 t2:add(f_z, buffer(pos + 10, 4))
629 t2:add(f_speed_x, buffer(pos + 14, 4))
630 t2:add(f_speed_y, buffer(pos + 18, 4))
631 t2:add(f_speed_z, buffer(pos + 22, 4))
632 t2:add(f_pitch, buffer(pos + 26, 4))
633 t2:add(f_yaw, buffer(pos + 30, 4))
636 local block_count = buffer(block_count_pos, 2):uint()
637 t:add(f_block_count, buffer(block_count_pos, 2))
639 -- TODO: dissect blocks.
640 -- NOTE: block_count > 0 is obsolete. (?)
642 pinfo.cols.info:append(" * " .. (player_count + block_count))
647 -- TOCLIENT_TIME_OF_DAY
650 local f_time = ProtoField.uint16("minetest.server.time_of_day", "Time", base.DEC)
652 minetest_server_commands[0x29] = {
655 function(buffer, pinfo, tree, t)
656 t:add(f_time, buffer(2,2))
661 -- TOCLIENT_CHAT_MESSAGE
664 local f_length = ProtoField.uint16("minetest.server.chat_message_length", "Length", base.DEC)
665 local f_message = ProtoField.string("minetest.server.chat_message", "Message")
667 minetest_server_commands[0x30] = {
669 { f_length, f_message },
670 function(buffer, pinfo, tree, t)
671 t:add(f_length, buffer(2,2))
672 local textlen = buffer(2,2):uint()
673 if minetest_check_length(buffer, 4 + textlen*2, t) then
674 t:add(f_message, minetest_convert_utf16(buffer(4, textlen*2), "Converted chat message"))
680 -- TOCLIENT_ACTIVE_OBJECT_REMOVE_ADD
683 local f_removed_count = ProtoField.uint16(
684 "minetest.server.active_object_remove_add_removed_count",
685 "Count of removed objects", base.DEC)
686 local f_removed = ProtoField.bytes(
687 "minetest.server.active_object_remove_add_removed",
689 local f_removed_id = ProtoField.uint16(
690 "minetest.server.active_object_remove_add_removed_id",
693 local f_added_count = ProtoField.uint16(
694 "minetest.server.active_object_remove_add_added_count",
695 "Count of added objects", base.DEC)
696 local f_added = ProtoField.bytes(
697 "minetest.server.active_object_remove_add_added",
699 local f_added_id = ProtoField.uint16(
700 "minetest.server.active_object_remove_add_added_id",
702 local f_added_type = ProtoField.uint8(
703 "minetest.server.active_object_remove_add_added_type",
705 local f_added_init_length = ProtoField.uint32(
706 "minetest.server.active_object_remove_add_added_init_length",
707 "Initialization data length", base.DEC)
708 local f_added_init_data = ProtoField.bytes(
709 "minetest.server.active_object_remove_add_added_init_data",
710 "Initialization data")
712 minetest_server_commands[0x31] = {
713 "ACTIVE_OBJECT_REMOVE_ADD", 6,
714 { f_removed_count, f_removed, f_removed_id,
715 f_added_count, f_added, f_added_id,
716 f_added_type, f_added_init_length, f_added_init_data },
717 function(buffer, pinfo, tree, t)
720 local removed_count_pos = 2
721 local removed_count = buffer(removed_count_pos, 2):uint()
722 t:add(f_removed_count, buffer(removed_count_pos, 2))
724 local added_count_pos = removed_count_pos + 2 + 2 * removed_count
725 if not minetest_check_length(buffer, added_count_pos + 2, t) then
729 -- Loop through removed active objects
730 for index = 0, removed_count - 1 do
731 pos = removed_count_pos + 2 + 2 * index
732 t2 = t:add(f_removed, buffer(pos, 2))
733 t2:set_text("Removed object, ID = " .. buffer(pos, 2):uint())
734 t2:add(f_removed_id, buffer(pos, 2))
737 local added_count = buffer(added_count_pos, 2):uint()
738 t:add(f_added_count, buffer(added_count_pos, 2))
740 -- Loop through added active objects
741 pos = added_count_pos + 2
742 for index = 0, added_count - 1 do
743 if not minetest_check_length(buffer, pos + 7, t) then
747 local init_length = buffer(pos + 3, 4):uint()
748 if not minetest_check_length(buffer, pos + 7 + init_length, t) then
752 t2 = t:add(f_added, buffer(pos, 7 + init_length))
753 t2:set_text("Added object, ID = " .. buffer(pos, 2):uint())
754 t2:add(f_added_id, buffer(pos, 2))
755 t2:add(f_added_type, buffer(pos + 2, 1))
756 t2:add(f_added_init_length, buffer(pos + 3, 4))
757 t2:add(f_added_init_data, buffer(pos + 7, init_length))
759 pos = pos + 7 + init_length
762 pinfo.cols.info:append(" * " .. (removed_count + added_count))
767 -- TOCLIENT_ACTIVE_OBJECT_MESSAGES
770 local f_object_count = ProtoField.uint16(
771 "minetest.server.active_object_messages_object_count",
772 "Count of objects", base.DEC)
773 local f_object = ProtoField.bytes(
774 "minetest.server.active_object_messages_object",
776 local f_object_id = ProtoField.uint16(
777 "minetest.server.active_object_messages_id",
779 local f_message_length = ProtoField.uint16(
780 "minetest.server.active_object_messages_message_length",
781 "Message length", base.DEC)
782 local f_message = ProtoField.bytes(
783 "minetest.server.active_object_messages_message",
786 minetest_server_commands[0x32] = {
787 "ACTIVE_OBJECT_MESSAGES", 2,
788 { f_object_count, f_object, f_object_id, f_message_length, f_message },
789 function(buffer, pinfo, tree, t)
790 local t2, count, pos, message_length
794 while pos < buffer:len() do
795 if not minetest_check_length(buffer, pos + 4, t) then
798 message_length = buffer(pos + 2, 2):uint()
799 if not minetest_check_length(buffer, pos + 4 + message_length, t) then
803 pos = pos + 4 + message_length
806 pinfo.cols.info:append(" * " .. count)
807 t:add(f_object_count, count):set_generated()
810 while pos < buffer:len() do
811 message_length = buffer(pos + 2, 2):uint()
813 t2 = t:add(f_object, buffer(pos, 4 + message_length))
814 t2:set_text("Object, ID = " .. buffer(pos, 2):uint())
815 t2:add(f_object_id, buffer(pos, 2))
816 t2:add(f_message_length, buffer(pos + 2, 2))
817 t2:add(f_message, buffer(pos + 4, message_length))
819 pos = pos + 4 + message_length
828 local f_hp = ProtoField.uint8("minetest.server.hp", "Hitpoints", base.DEC)
830 minetest_server_commands[0x33] = {
833 function(buffer, pinfo, tree, t)
834 t:add(f_hp, buffer(2,1))
839 -- TOCLIENT_MOVE_PLAYER
842 local f_x = ProtoField.int32("minetest.server.move_player_x", "Position X", base.DEC)
843 local f_y = ProtoField.int32("minetest.server.move_player_y", "Position Y", base.DEC)
844 local f_z = ProtoField.int32("minetest.server.move_player_z", "Position Z", base.DEC)
845 local f_pitch = ProtoField.int32("minetest.server.move_player_pitch", "Pitch", base.DEC)
846 local f_yaw = ProtoField.int32("minetest.server.move_player_yaw", "Yaw", base.DEC)
847 local f_garbage = ProtoField.bytes("minetest.server.move_player_garbage", "Garbage")
849 minetest_server_commands[0x34] = {
850 "MOVE_PLAYER", 18, -- actually 22, but see below
851 { f_x, f_y, f_z, f_pitch, f_yaw, f_garbage },
852 function(buffer, pinfo, tree, t)
853 t:add(f_x, buffer(2, 4))
854 t:add(f_y, buffer(6, 4))
855 t:add(f_z, buffer(10, 4))
857 -- Compatibility note:
858 -- Up to 2011-08-23, there was a bug in Minetest that
859 -- caused the server to serialize the pitch and yaw
860 -- with 2 bytes each instead of 4, creating a
861 -- malformed message.
862 if buffer:len() >= 22 then
863 t:add(f_pitch, buffer(14, 4))
864 t:add(f_yaw, buffer(18, 4))
866 t:add(f_garbage, buffer(14, 4))
867 t:add_expert_info(PI_MALFORMED, PI_WARN, "Malformed pitch and yaw, possibly caused by a serialization bug in Minetest")
873 -- TOCLIENT_ACCESS_DENIED
876 local f_reason_length = ProtoField.uint16("minetest.server.access_denied_reason_length", "Reason length", base.DEC)
877 local f_reason = ProtoField.string("minetest.server.access_denied_reason", "Reason")
879 minetest_server_commands[0x35] = {
881 { f_reason_length, f_reason },
882 function(buffer, pinfo, tree, t)
883 t:add(f_reason_length, buffer(2,2))
884 local reason_length = buffer(2,2):uint()
885 if minetest_check_length(buffer, 4 + reason_length * 2, t) then
886 t:add(f_reason, minetest_convert_utf16(buffer(4, reason_length * 2), "Converted reason message"))
892 -- TOCLIENT_PLAYERITEM
895 local f_count = ProtoField.uint16(
896 "minetest.server.playeritem_count",
897 "Count of players", base.DEC)
898 local f_player = ProtoField.bytes(
899 "minetest.server.playeritem_player",
901 local f_peer_id = ProtoField.uint16(
902 "minetest.server.playeritem_peer_id",
904 local f_item_length = ProtoField.uint16(
905 "minetest.server.playeritem_item_length",
906 "Item information length", base.DEC)
907 local f_item = ProtoField.string(
908 "minetest.server.playeritem_item",
911 minetest_server_commands[0x36] = {
913 { f_count, f_player, f_peer_id, f_item_length, f_item },
914 function(buffer, pinfo, tree, t)
915 local count, index, pos, item_length
917 count = buffer(2,2):uint()
918 pinfo.cols.info:append(" * " .. count)
919 t:add(f_count, buffer(2,2))
922 for index = 0, count - 1 do
923 if not minetest_check_length(buffer, pos + 4, t) then
926 item_length = buffer(pos + 2, 2):uint()
927 if not minetest_check_length(buffer, pos + 4 + item_length, t) then
931 local t2 = t:add(f_player, buffer(pos, 4 + item_length))
932 t2:set_text("Player, ID: " .. buffer(pos, 2):uint())
933 t2:add(f_peer_id, buffer(pos, 2))
934 t2:add(f_item_length, buffer(pos + 2, 2))
935 t2:add(f_item, buffer(pos + 4, item_length))
937 pos = pos + 4 + item_length
943 -- TOCLIENT_DEATHSCREEN
946 local vs_set_camera_point_target = {
951 local f_set_camera_point_target = ProtoField.uint8(
952 "minetest.server.deathscreen_set_camera_point_target",
953 "Set camera point target", base.DEC, vs_set_camera_point_target)
954 local f_camera_point_target_x = ProtoField.int32(
955 "minetest.server.deathscreen_camera_point_target_x",
956 "Camera point target X", base.DEC)
957 local f_camera_point_target_y = ProtoField.int32(
958 "minetest.server.deathscreen_camera_point_target_y",
959 "Camera point target Y", base.DEC)
960 local f_camera_point_target_z = ProtoField.int32(
961 "minetest.server.deathscreen_camera_point_target_z",
962 "Camera point target Z", base.DEC)
964 minetest_server_commands[0x37] = {
966 { f_set_camera_point_target, f_camera_point_target_x,
967 f_camera_point_target_y, f_camera_point_target_z},
968 function(buffer, pinfo, tree, t)
969 t:add(f_set_camera_point_target, buffer(2,1))
970 t:add(f_camera_point_target_x, buffer(3,4))
971 t:add(f_camera_point_target_y, buffer(7,4))
972 t:add(f_camera_point_target_z, buffer(11,4))
980 ------------------------------------
982 -- Wrapper protocol subdissectors --
983 ------------------------------------
985 -- minetest.control dissector
988 local p_control = Proto("minetest.control", "Minetest Control")
990 local vs_control_type = {
997 local f_control_type = ProtoField.uint8("minetest.control.type", "Control Type", base.DEC, vs_control_type)
998 local f_control_ack = ProtoField.uint16("minetest.control.ack", "ACK sequence number", base.DEC)
999 local f_control_peerid = ProtoField.uint8("minetest.control.peerid", "New peer ID", base.DEC)
1000 p_control.fields = { f_control_type, f_control_ack, f_control_peerid }
1002 local data_dissector = Dissector.get("data")
1004 function p_control.dissector(buffer, pinfo, tree)
1005 local t = tree:add(p_control, buffer(0,1))
1006 t:add(f_control_type, buffer(0,1))
1008 pinfo.cols.info = "Control message"
1011 if buffer(0,1):uint() == 0 then
1014 t:add(f_control_ack, buffer(1,2))
1015 pinfo.cols.info = "Ack " .. buffer(1,2):uint()
1016 elseif buffer(0,1):uint() == 1 then
1019 t:add(f_control_peerid, buffer(1,2))
1020 pinfo.cols.info = "Set peer ID " .. buffer(1,2):uint()
1021 elseif buffer(0,1):uint() == 2 then
1022 pinfo.cols.info = "Ping"
1023 elseif buffer(0,1):uint() == 3 then
1024 pinfo.cols.info = "Disco"
1027 data_dissector:call(buffer(pos):tvb(), pinfo, tree)
1031 -- minetest.client dissector
1032 -- minetest.server dissector
1034 -- Defines the minetest.client or minetest.server Proto. These two protocols
1035 -- are created by the same function because they are so similar.
1036 -- Parameter: proto: the Proto object
1037 -- Parameter: this_peer: "Client" or "Server"
1038 -- Parameter: other_peer: "Server" or "Client"
1039 -- Parameter: commands: table of command information, built above
1040 -- Parameter: obsolete: table of obsolete commands, built above
1041 function minetest_define_client_or_server_proto(is_client)
1042 -- Differences between minetest.client and minetest.server
1043 local proto_name, this_peer, other_peer, empty_message_info
1044 local commands, obsolete
1046 proto_name = "minetest.client"
1047 this_peer = "Client"
1048 other_peer = "Server"
1049 empty_message_info = "Empty message / Connect"
1050 commands = minetest_client_commands -- defined in Part 1
1051 obsolete = minetest_client_obsolete -- defined in Part 1
1053 proto_name = "minetest.server"
1054 this_peer = "Server"
1055 other_peer = "Client"
1056 empty_message_info = "Empty message"
1057 commands = minetest_server_commands -- defined in Part 2
1058 obsolete = minetest_server_obsolete -- defined in Part 2
1061 -- Create the protocol object.
1062 local proto = Proto(proto_name, "Minetest " .. this_peer .. " to " .. other_peer)
1064 -- Create a table vs_command that maps command codes to command names.
1065 local vs_command = {}
1066 local code, command_info
1067 for code, command_info in pairs(commands) do
1068 local command_name = command_info[1]
1069 vs_command[code] = "TO" .. other_peer:upper() .. "_" .. command_name
1072 -- Field definitions
1073 local f_command = ProtoField.uint16(proto_name .. ".command", "Command", base.HEX, vs_command)
1074 local f_empty = ProtoField.bool(proto_name .. ".empty", "Is empty", BASE_NONE)
1075 proto.fields = { f_command, f_empty }
1077 -- Add command-specific fields to the protocol
1078 for code, command_info in pairs(commands) do
1079 local command_fields = command_info[3]
1080 if command_fields ~= nil then
1082 for index, field in ipairs(command_fields) do
1083 table.insert(proto.fields, field)
1088 -- minetest.client or minetest.server dissector function
1089 function proto.dissector(buffer, pinfo, tree)
1090 local t = tree:add(proto, buffer)
1092 pinfo.cols.info = this_peer
1094 if buffer:len() == 0 then
1096 t:add(f_empty, 1):set_generated()
1097 pinfo.cols.info:append(": " .. empty_message_info)
1099 elseif minetest_check_length(buffer, 2, t) then
1100 -- Get the command code.
1101 t:add(f_command, buffer(0,2))
1102 local code = buffer(0,2):uint()
1103 local command_info = commands[code]
1104 if command_info == nil then
1105 -- Error: Unknown command.
1106 pinfo.cols.info:append(": Unknown command")
1107 t:add_expert_info(PI_UNDECODED, PI_WARN, "Unknown " .. this_peer .. " to " .. other_peer .. " command")
1109 -- Process a known command
1110 local command_name = command_info[1]
1111 local command_min_length = command_info[2]
1112 local command_fields = command_info[3]
1113 local command_dissector = command_info[4]
1114 if minetest_check_length(buffer, command_min_length, t) then
1115 pinfo.cols.info:append(": " .. command_name)
1116 if command_dissector ~= nil then
1117 command_dissector(buffer, pinfo, tree, t)
1120 if obsolete[code] then
1121 t:add_expert_info(PI_REQUEST_CODE, PI_WARN, "Obsolete command.")
1128 minetest_define_client_or_server_proto(true) -- minetest.client
1129 minetest_define_client_or_server_proto(false) -- minetest.server
1131 -- minetest.split dissector
1134 local p_split = Proto("minetest.split", "Minetest Split Message")
1136 local f_split_seq = ProtoField.uint16("minetest.split.seq", "Sequence number", base.DEC)
1137 local f_split_chunkcount = ProtoField.uint16("minetest.split.chunkcount", "Chunk count", base.DEC)
1138 local f_split_chunknum = ProtoField.uint16("minetest.split.chunknum", "Chunk number", base.DEC)
1139 local f_split_data = ProtoField.bytes("minetest.split.data", "Split message data")
1140 p_split.fields = { f_split_seq, f_split_chunkcount, f_split_chunknum, f_split_data }
1142 function p_split.dissector(buffer, pinfo, tree)
1143 local t = tree:add(p_split, buffer(0,6))
1144 t:add(f_split_seq, buffer(0,2))
1145 t:add(f_split_chunkcount, buffer(2,2))
1146 t:add(f_split_chunknum, buffer(4,2))
1147 t:add(f_split_data, buffer(6))
1148 pinfo.cols.info:append(" " .. buffer(0,2):uint() .. " chunk " .. buffer(4,2):uint() .. "/" .. buffer(2,2):uint())
1155 -------------------------------------
1157 -- Wrapper protocol main dissector --
1158 -------------------------------------
1160 -- minetest dissector
1163 local p_minetest = Proto("minetest", "Minetest")
1165 local minetest_id = 0x4f457403
1167 [minetest_id] = "Valid"
1182 local f_id = ProtoField.uint32("minetest.id", "ID", base.HEX, vs_id)
1183 local f_peer = ProtoField.uint16("minetest.peer", "Peer", base.DEC, vs_peer)
1184 local f_channel = ProtoField.uint8("minetest.channel", "Channel", base.DEC)
1185 local f_type = ProtoField.uint8("minetest.type", "Type", base.DEC, vs_type)
1186 local f_seq = ProtoField.uint16("minetest.seq", "Sequence number", base.DEC)
1187 local f_subtype = ProtoField.uint8("minetest.subtype", "Subtype", base.DEC, vs_type)
1189 p_minetest.fields = { f_id, f_peer, f_channel, f_type, f_seq, f_subtype }
1191 local data_dissector = Dissector.get("data")
1192 local control_dissector = Dissector.get("minetest.control")
1193 local client_dissector = Dissector.get("minetest.client")
1194 local server_dissector = Dissector.get("minetest.server")
1195 local split_dissector = Dissector.get("minetest.split")
1197 function p_minetest.dissector(buffer, pinfo, tree)
1199 -- Add Minetest tree item and verify the ID
1200 local t = tree:add(p_minetest, buffer(0,8))
1201 t:add(f_id, buffer(0,4))
1202 if buffer(0,4):uint() ~= minetest_id then
1203 t:add_expert_info(PI_UNDECODED, PI_WARN, "Invalid ID, this is not a Minetest packet")
1207 -- ID is valid, so replace packet's shown protocol
1208 pinfo.cols.protocol = "Minetest"
1209 pinfo.cols.info = "Minetest"
1211 -- Set the other header fields
1212 t:add(f_peer, buffer(4,2))
1213 t:add(f_channel, buffer(6,1))
1214 t:add(f_type, buffer(7,1))
1215 t:set_text("Minetest, Peer: " .. buffer(4,2):uint() .. ", Channel: " .. buffer(6,1):uint())
1217 local reliability_info
1218 if buffer(7,1):uint() == 3 then
1220 reliability_info = "Seq=" .. buffer(8,2):uint()
1222 t:add(f_seq, buffer(8,2))
1223 t:add(f_subtype, buffer(10,1))
1226 -- Unreliable message
1227 reliability_info = "Unrel"
1231 if buffer(pos,1):uint() == 0 then
1232 -- Control message, possibly reliable
1233 control_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1234 elseif buffer(pos,1):uint() == 1 then
1235 -- Original message, possibly reliable
1236 if buffer(4,2):uint() == 1 then
1237 server_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1239 client_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1241 elseif buffer(pos,1):uint() == 2 then
1242 -- Split message, possibly reliable
1243 if buffer(4,2):uint() == 1 then
1244 pinfo.cols.info = "Server: Split message"
1246 pinfo.cols.info = "Client: Split message"
1248 split_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1249 elseif buffer(pos,1):uint() == 3 then
1250 -- Doubly reliable message??
1251 t:add_expert_info(PI_MALFORMED, PI_ERROR, "Reliable message wrapped in reliable message")
1253 data_dissector:call(buffer(pos+1):tvb(), pinfo, tree)
1256 pinfo.cols.info:append(" (" .. reliability_info .. ")")
1260 -- FIXME Is there a way to let the dissector table check if the first payload bytes are 0x4f457403?
1261 DissectorTable.get("udp.port"):add(30000, p_minetest)
1262 DissectorTable.get("udp.port"):add(30001, p_minetest)
1268 -----------------------
1270 -- Utility functions --
1271 -----------------------
1273 -- Checks if a (sub-)Tvb is long enough to be further dissected.
1274 -- If it is long enough, sets the dissector tree item length to min_len
1275 -- and returns true. If it is not long enough, adds expert info to the
1276 -- dissector tree and returns false.
1277 -- Parameter: tvb: the Tvb
1278 -- Parameter: min_len: required minimum length
1279 -- Parameter: t: dissector tree item
1280 -- Returns: true if tvb:len() >= min_len, false otherwise
1281 function minetest_check_length(tvb, min_len, t)
1282 if tvb:len() >= min_len then
1286 -- Tvb:reported_length_remaining() has been added in August 2011
1287 -- and is not yet widely available, disable for the time being
1288 -- TODO: uncomment at a later date
1289 -- TODO: when uncommenting this, also re-check if other parts of
1290 -- the dissector could benefit from reported_length_remaining
1291 --elseif tvb:reported_length_remaining() >= min_len then
1292 -- t:add_expert_info(PI_UNDECODED, PI_INFO, "Only part of this packet was captured, unable to decode.")
1296 t:add_expert_info(PI_MALFORMED, PI_ERROR, "Message is too short")
1301 -- Takes a Tvb or TvbRange (i.e. part of a packet) that
1302 -- contains a UTF-16 string and returns a TvbRange containing
1303 -- string converted to ASCII. Any characters outside the range
1304 -- 0x20 to 0x7e are replaced by a question mark.
1305 -- Parameter: tvb: Tvb or TvbRange that contains the UTF-16 data
1306 -- Parameter: name: will be the name of the newly created Tvb.
1307 -- Returns: New TvbRange containing the ASCII string.
1308 -- TODO: Handle surrogates (should only produce one question mark)
1309 -- TODO: Remove this when Wireshark supports UTF-16 strings natively.
1310 function minetest_convert_utf16(tvb, name)
1311 local hex, pos, char
1313 for pos = 0, tvb:len() - 2, 2 do
1314 char = tvb(pos, 2):uint()
1315 if (char >= 0x20) and (char <= 0x7e) then
1316 hex = hex .. string.format(" %02x", char)
1322 -- This is a hack to avoid a failed assertion in tvbuff.c
1323 -- (function: ensure_contiguous_no_exception)
1324 return ByteArray.new("00"):tvb(name):range(0,0)
1326 return ByteArray.new(hex):tvb(name):range()