1 /* packet-netlink-generic.c
2 * Dissector for Linux Generic Netlink.
4 * Copyright (c) 2017, Peter Wu <peter@lekensteyn.nl>
6 * Wireshark - Network traffic analyzer
7 * By Gerald Combs <gerald@wireshark.org>
8 * Copyright 1998 Gerald Combs
10 * SPDX-License-Identifier: GPL-2.0-or-later
15 #include <epan/packet.h>
16 #include <wsutil/array.h>
17 #include "packet-netlink.h"
21 * https://wiki.linuxfoundation.org/networking/generic_netlink_howto#message-format
22 * include/uapi/linux/netlink.h
23 * include/uapi/linux/genetlink.h
25 * For the meaning of fields in genlmsghdr, see genlmsg_put in
26 * net/netlink/genetlink.c, note that it has no user-specific message header
27 * (genl_ctrl.hdr_size==0).
30 void proto_register_netlink_generic(void);
31 void proto_reg_handoff_netlink_generic(void);
34 /* Values parsed from the attributes (only valid in this packet). */
36 const uint8_t *family_name
;
39 /* from include/uapi/linux/genetlink.h */
42 WS_CTRL_CMD_NEWFAMILY
,
43 WS_CTRL_CMD_DELFAMILY
,
44 WS_CTRL_CMD_GETFAMILY
,
48 WS_CTRL_CMD_NEWMCAST_GRP
,
49 WS_CTRL_CMD_DELMCAST_GRP
,
50 WS_CTRL_CMD_GETMCAST_GRP
,
51 WS_CTRL_CMD_GETPOLICY
,
54 enum ws_genl_ctrl_attr
{
56 WS_CTRL_ATTR_FAMILY_ID
,
57 WS_CTRL_ATTR_FAMILY_NAME
,
62 WS_CTRL_ATTR_MCAST_GROUPS
,
64 WS_CTRL_ATTR_OP_POLICY
,
68 enum ws_genl_ctrl_op_attr
{
69 WS_CTRL_ATTR_OP_UNSPEC
,
71 WS_CTRL_ATTR_OP_FLAGS
,
74 enum ws_genl_ctrl_group_attr
{
75 WS_CTRL_ATTR_MCAST_GRP_UNSPEC
,
76 WS_CTRL_ATTR_MCAST_GRP_NAME
,
77 WS_CTRL_ATTR_MCAST_GRP_ID
,
80 #define WS_GENL_ID_CTRL 0x10
81 #define GENL_CTRL_NAME "nlctrl"
83 static const value_string genl_ctrl_cmds
[] = {
84 { WS_CTRL_CMD_UNSPEC
, "CTRL_CMD_UNSPEC" },
85 { WS_CTRL_CMD_NEWFAMILY
, "CTRL_CMD_NEWFAMILY" },
86 { WS_CTRL_CMD_DELFAMILY
, "CTRL_CMD_DELFAMILY" },
87 { WS_CTRL_CMD_GETFAMILY
, "CTRL_CMD_GETFAMILY" },
88 { WS_CTRL_CMD_NEWOPS
, "CTRL_CMD_NEWOPS" },
89 { WS_CTRL_CMD_DELOPS
, "CTRL_CMD_DELOPS" },
90 { WS_CTRL_CMD_GETOPS
, "CTRL_CMD_GETOPS" },
91 { WS_CTRL_CMD_NEWMCAST_GRP
, "CTRL_CMD_NEWMCAST_GRP" },
92 { WS_CTRL_CMD_DELMCAST_GRP
, "CTRL_CMD_DELMCAST_GRP" },
93 { WS_CTRL_CMD_GETMCAST_GRP
, "CTRL_CMD_GETMCAST_GRP" },
94 { WS_CTRL_CMD_GETPOLICY
, "CTRL_CMD_GETPOLICY" },
98 static const value_string genl_ctrl_attr_vals
[] = {
99 { WS_CTRL_ATTR_UNSPEC
, "CTRL_ATTR_UNSPEC" },
100 { WS_CTRL_ATTR_FAMILY_ID
, "CTRL_ATTR_FAMILY_ID" },
101 { WS_CTRL_ATTR_FAMILY_NAME
, "CTRL_ATTR_FAMILY_NAME" },
102 { WS_CTRL_ATTR_VERSION
, "CTRL_ATTR_VERSION" },
103 { WS_CTRL_ATTR_HDRSIZE
, "CTRL_ATTR_HDRSIZE" },
104 { WS_CTRL_ATTR_MAXATTR
, "CTRL_ATTR_MAXATTR" },
105 { WS_CTRL_ATTR_OPS
, "CTRL_ATTR_OPS" },
106 { WS_CTRL_ATTR_MCAST_GROUPS
, "CTRL_ATTR_MCAST_GROUPS" },
107 { WS_CTRL_ATTR_POLICY
, "CTRL_ATTR_POLICY" },
108 { WS_CTRL_ATTR_OP_POLICY
, "CTRL_ATTR_OP_POLICY" },
109 { WS_CTRL_ATTR_OP
, "CTRL_ATTR_OP" },
113 static const value_string genl_ctrl_op_attr_vals
[] = {
114 { WS_CTRL_ATTR_OP_UNSPEC
, "CTRL_ATTR_OP_UNSPEC" },
115 { WS_CTRL_ATTR_OP_ID
, "CTRL_ATTR_OP_ID" },
116 { WS_CTRL_ATTR_OP_FLAGS
, "CTRL_ATTR_OP_FLAGS" },
120 static const value_string genl_ctrl_group_attr_vals
[] = {
121 { WS_CTRL_ATTR_MCAST_GRP_UNSPEC
, "CTRL_ATTR_MCAST_GRP_UNSPEC" },
122 { WS_CTRL_ATTR_MCAST_GRP_NAME
, "CTRL_ATTR_MCAST_GRP_NAME" },
123 { WS_CTRL_ATTR_MCAST_GRP_ID
, "CTRL_ATTR_MCAST_GRP_ID" },
127 static dissector_handle_t netlink_generic
;
128 static dissector_handle_t netlink_generic_ctrl
;
129 static dissector_table_t genl_dissector_table
;
131 static int proto_netlink_generic
;
133 static int hf_genl_cmd
;
134 static int hf_genl_ctrl_attr
;
135 static int hf_genl_ctrl_cmd
;
136 static int hf_genl_ctrl_family_id
;
137 static int hf_genl_ctrl_family_name
;
138 static int hf_genl_ctrl_group_id
;
139 static int hf_genl_ctrl_group_name
;
140 static int hf_genl_ctrl_groups_attr
;
141 static int hf_genl_ctrl_hdrsize
;
142 static int hf_genl_ctrl_maxattr
;
143 static int hf_genl_ctrl_op_flags
;
144 static int hf_genl_ctrl_op_flags_admin_perm
;
145 static int hf_genl_ctrl_op_flags_cmd_cap_do
;
146 static int hf_genl_ctrl_op_flags_cmd_cap_dump
;
147 static int hf_genl_ctrl_op_flags_cmd_cap_haspol
;
148 static int hf_genl_ctrl_op_flags_uns_admin_perm
;
149 static int hf_genl_ctrl_op_id
;
150 static int hf_genl_ctrl_ops_attr
;
151 static int hf_genl_ctrl_version
;
152 static int hf_genl_family_id
;
153 static int hf_genl_reserved
;
154 static int hf_genl_version
;
156 static int ett_netlink_generic
;
157 static int ett_genl_ctrl_attr
;
158 static int ett_genl_ctrl_ops
;
159 static int ett_genl_ctrl_ops_attr
;
160 static int ett_genl_ctrl_op_flags
;
161 static int ett_genl_ctrl_groups
;
162 static int ett_genl_ctrl_groups_attr
;
163 static int ett_genl_nested_attr
;
166 * Maps family IDs (integers) to family names (strings) within a capture file.
168 static wmem_map_t
*genl_family_map
;
170 static int * const genl_ctrl_op_flags_fields
[] = {
171 &hf_genl_ctrl_op_flags_admin_perm
,
172 &hf_genl_ctrl_op_flags_cmd_cap_do
,
173 &hf_genl_ctrl_op_flags_cmd_cap_dump
,
174 &hf_genl_ctrl_op_flags_cmd_cap_haspol
,
175 &hf_genl_ctrl_op_flags_uns_admin_perm
,
180 dissect_genl_ctrl_ops_attrs(tvbuff_t
*tvb
, void *data _U_
, struct packet_netlink_data
*nl_data
, proto_tree
*tree
, int nla_type
, int offset
, int len
)
182 enum ws_genl_ctrl_op_attr type
= (enum ws_genl_ctrl_op_attr
) nla_type
;
183 proto_tree
*ptree
= proto_tree_get_parent_tree(tree
);
187 case WS_CTRL_ATTR_OP_UNSPEC
:
189 case WS_CTRL_ATTR_OP_ID
:
191 proto_tree_add_item_ret_uint(tree
, hf_genl_ctrl_op_id
, tvb
, offset
, 4, nl_data
->encoding
, &value
);
192 proto_item_append_text(tree
, ": %u", value
);
193 proto_item_append_text(ptree
, ", id=%u", value
);
197 case WS_CTRL_ATTR_OP_FLAGS
:
200 /* XXX it would be nice if the flag names are appended to the tree */
201 proto_tree_add_bitmask_with_flags_ret_uint64(tree
, tvb
, offset
, hf_genl_ctrl_op_flags
,
202 ett_genl_ctrl_op_flags
, genl_ctrl_op_flags_fields
, nl_data
->encoding
, BMT_NO_FALSE
, &op_flags
);
203 proto_item_append_text(tree
, ": 0x%08x", (uint32_t)op_flags
);
204 proto_item_append_text(ptree
, ", flags=0x%08x", (uint32_t)op_flags
);
214 dissect_genl_ctrl_groups_attrs(tvbuff_t
*tvb
, void *data _U_
, struct packet_netlink_data
*nl_data
, proto_tree
*tree
, int nla_type
, int offset
, int len
)
216 enum ws_genl_ctrl_group_attr type
= (enum ws_genl_ctrl_group_attr
) nla_type
;
217 proto_tree
*ptree
= proto_tree_get_parent_tree(tree
);
219 const uint8_t *strval
;
222 case WS_CTRL_ATTR_MCAST_GRP_UNSPEC
:
224 case WS_CTRL_ATTR_MCAST_GRP_NAME
:
225 proto_tree_add_item_ret_string(tree
, hf_genl_ctrl_group_name
, tvb
, offset
, len
, ENC_ASCII
, wmem_packet_scope(), &strval
);
226 proto_item_append_text(tree
, ": %s", strval
);
227 proto_item_append_text(ptree
, ", name=%s", strval
);
230 case WS_CTRL_ATTR_MCAST_GRP_ID
:
232 proto_tree_add_item_ret_uint(tree
, hf_genl_ctrl_group_id
, tvb
, offset
, 4, nl_data
->encoding
, &value
);
233 proto_item_append_text(tree
, ": %u", value
);
234 proto_item_append_text(ptree
, ", id=%u", value
);
244 dissect_genl_ctrl_attrs(tvbuff_t
*tvb
, void *data
, struct packet_netlink_data
*nl_data
, proto_tree
*tree
, int nla_type
, int offset
, int len
)
246 enum ws_genl_ctrl_attr type
= (enum ws_genl_ctrl_attr
) nla_type
;
247 genl_ctrl_info_t
*info
= (genl_ctrl_info_t
*) data
;
251 case WS_CTRL_CMD_UNSPEC
:
253 case WS_CTRL_ATTR_FAMILY_ID
:
255 proto_tree_add_item_ret_uint(tree
, hf_genl_ctrl_family_id
, tvb
, offset
, 2, nl_data
->encoding
, &value
);
256 proto_item_append_text(tree
, ": %#x", value
);
257 info
->family_id
= value
;
261 case WS_CTRL_ATTR_FAMILY_NAME
:
262 proto_tree_add_item_ret_string(tree
, hf_genl_ctrl_family_name
, tvb
, offset
, len
, ENC_ASCII
, wmem_packet_scope(), &info
->family_name
);
263 proto_item_append_text(tree
, ": %s", info
->family_name
);
266 case WS_CTRL_ATTR_VERSION
:
268 proto_tree_add_item_ret_uint(tree
, hf_genl_ctrl_version
, tvb
, offset
, 4, nl_data
->encoding
, &value
);
269 proto_item_append_text(tree
, ": %u", value
);
273 case WS_CTRL_ATTR_HDRSIZE
:
275 proto_tree_add_item_ret_uint(tree
, hf_genl_ctrl_hdrsize
, tvb
, offset
, 4, nl_data
->encoding
, &value
);
276 proto_item_append_text(tree
, ": %u", value
);
280 case WS_CTRL_ATTR_MAXATTR
:
282 proto_tree_add_item_ret_uint(tree
, hf_genl_ctrl_maxattr
, tvb
, offset
, 4, nl_data
->encoding
, &value
);
283 proto_item_append_text(tree
, ": %u", value
);
287 case WS_CTRL_ATTR_OPS
:
288 offset
= dissect_netlink_attributes_array(tvb
, hf_genl_ctrl_ops_attr
, ett_genl_ctrl_ops
, ett_genl_ctrl_ops_attr
, info
, nl_data
, tree
, offset
, len
, dissect_genl_ctrl_ops_attrs
);
290 case WS_CTRL_ATTR_MCAST_GROUPS
:
291 offset
= dissect_netlink_attributes_array(tvb
, hf_genl_ctrl_groups_attr
, ett_genl_ctrl_groups
, ett_genl_ctrl_groups_attr
, info
, nl_data
, tree
, offset
, len
, dissect_genl_ctrl_groups_attrs
);
293 case WS_CTRL_ATTR_POLICY
:
294 case WS_CTRL_ATTR_OP_POLICY
:
295 case WS_CTRL_ATTR_OP
:
303 dissect_genl_ctrl(tvbuff_t
*tvb
, packet_info
*pinfo _U_
, proto_tree
*tree _U_
, void *data
)
305 genl_info_t
*genl_info
= (genl_info_t
*) data
;
306 genl_ctrl_info_t info
;
314 info
.family_name
= NULL
;
316 offset
= dissect_genl_header(tvb
, genl_info
, genl_info
->nl_data
, hf_genl_ctrl_cmd
);
318 /* Return if command has no payload */
319 if (!tvb_reported_length_remaining(tvb
, offset
))
322 dissect_netlink_attributes_to_end(tvb
, hf_genl_ctrl_attr
, ett_genl_ctrl_attr
, &info
, genl_info
->nl_data
, genl_info
->genl_tree
, offset
, dissect_genl_ctrl_attrs
);
325 * Remember association of dynamic ID with the family name such that
326 * future packets can be linked to a protocol.
327 * Do not allow overwriting our control protocol.
329 if (info
.family_id
&& info
.family_id
!= WS_GENL_ID_CTRL
&& info
.family_name
) {
330 wmem_map_insert(genl_family_map
, GUINT_TO_POINTER(info
.family_id
), wmem_strdup(wmem_file_scope(), info
.family_name
));
333 return tvb_captured_length(tvb
);
336 int dissect_genl_header(tvbuff_t
*tvb
, genl_info_t
*genl_info
, struct packet_netlink_data
*nl_data
, int hf_cmd
)
341 hf_cmd
= hf_genl_cmd
;
343 proto_tree_add_item(genl_info
->genl_tree
, hf_cmd
, tvb
, offset
, 1, ENC_NA
);
345 /* XXX Family dissectors may want to know this */
346 proto_tree_add_item(genl_info
->genl_tree
, hf_genl_version
, tvb
, offset
, 1, ENC_NA
);
348 proto_tree_add_item(genl_info
->genl_tree
, hf_genl_reserved
, tvb
, offset
, 2, nl_data
->encoding
);
354 dissect_netlink_generic(tvbuff_t
*tvb
, packet_info
*pinfo
, proto_tree
*tree
, void *data
)
356 struct packet_netlink_data
*nl_data
= (struct packet_netlink_data
*) data
;
358 proto_tree
*nlmsg_tree
;
359 proto_item
*pi
, *pi_type
;
360 const char *family_name
;
364 DISSECTOR_ASSERT(nl_data
&& nl_data
->magic
== PACKET_NETLINK_MAGIC
);
366 col_set_str(pinfo
->cinfo
, COL_PROTOCOL
, "Netlink generic");
367 col_clear(pinfo
->cinfo
, COL_INFO
);
369 pi
= proto_tree_add_item(tree
, proto_netlink_generic
, tvb
, 0, -1, ENC_NA
);
370 nlmsg_tree
= proto_item_add_subtree(pi
, ett_netlink_generic
);
372 /* Netlink message header (nlmsghdr) */
373 offset
= dissect_netlink_header(tvb
, nlmsg_tree
, offset
, nl_data
->encoding
, hf_genl_family_id
, &pi_type
);
374 family_name
= (const char *)wmem_map_lookup(genl_family_map
, GUINT_TO_POINTER(nl_data
->type
));
375 proto_item_append_text(pi_type
, " (%s)", family_name
? family_name
: "Unknown");
377 /* Populate info from Generic Netlink message header (genlmsghdr) */
378 info
.nl_data
= nl_data
;
379 info
.genl_tree
= nlmsg_tree
;
380 info
.cmd
= tvb_get_uint8(tvb
, offset
);
382 /* Optional user-specific message header and optional message payload. */
383 next_tvb
= tvb_new_subset_remaining(tvb
, offset
);
386 /* Invoke subdissector with genlmsghdr present. */
387 ret
= dissector_try_string_with_data(genl_dissector_table
, family_name
, next_tvb
, pinfo
, tree
, true, &info
);
393 /* No subdissector added the genl header, do it now. */
394 offset
= dissect_genl_header(next_tvb
, &info
, nl_data
, -1);
395 if (tvb_reported_length_remaining(tvb
, offset
)) {
396 next_tvb
= tvb_new_subset_remaining(tvb
, offset
);
397 call_data_dissector(next_tvb
, pinfo
, tree
);
406 /* Add fixed family entry (0x10 maps to "nlctrl"). */
407 wmem_map_insert(genl_family_map
, GUINT_TO_POINTER(WS_GENL_ID_CTRL
), GENL_CTRL_NAME
);
411 proto_register_netlink_generic(void)
413 static hf_register_info hf
[] = {
414 { &hf_genl_ctrl_op_id
,
415 { "Operation ID", "genl.ctrl.op_id",
416 FT_UINT32
, BASE_DEC
, NULL
, 0x00,
419 { &hf_genl_ctrl_op_flags
,
420 { "Operation Flags", "genl.ctrl.op_flags",
421 FT_UINT32
, BASE_HEX
, NULL
, 0x00,
424 { &hf_genl_ctrl_op_flags_admin_perm
,
425 { "GENL_ADMIN_PERM", "genl.ctrl.op_flags.admin_perm",
426 FT_BOOLEAN
, 32, NULL
, 0x00000001,
429 { &hf_genl_ctrl_op_flags_cmd_cap_do
,
430 { "GENL_CMD_CAP_DO", "genl.ctrl.op_flags.cmd_cap_do",
431 FT_BOOLEAN
, 32, NULL
, 0x00000002,
434 { &hf_genl_ctrl_op_flags_cmd_cap_dump
,
435 { "GENL_CMD_CAP_DUMP", "genl.ctrl.op_flags.cmd_cap_dump",
436 FT_BOOLEAN
, 32, NULL
, 0x00000004,
439 { &hf_genl_ctrl_op_flags_cmd_cap_haspol
,
440 { "GENL_CMD_CAP_HASPOL", "genl.ctrl.op_flags.cmd_cap_haspol",
441 FT_BOOLEAN
, 32, NULL
, 0x00000008,
444 { &hf_genl_ctrl_op_flags_uns_admin_perm
,
445 { "GENL_UNS_ADMIN_PERM", "genl.ctrl.op_flags.uns_admin_perm",
446 FT_BOOLEAN
, 32, NULL
, 0x00000010,
449 { &hf_genl_ctrl_group_name
,
450 { "Group Name", "genl.ctrl.group_name",
451 FT_STRINGZ
, BASE_NONE
, NULL
, 0x0,
454 { &hf_genl_ctrl_group_id
,
455 { "Group ID", "genl.ctrl.group_id",
456 FT_UINT32
, BASE_HEX
, NULL
, 0x0,
459 { &hf_genl_ctrl_family_id
,
460 { "Family ID", "genl.ctrl.family_id",
461 FT_UINT16
, BASE_HEX
, NULL
, 0x0,
464 { &hf_genl_ctrl_family_name
,
465 { "Family Name", "genl.ctrl.family_name",
466 FT_STRINGZ
, BASE_NONE
, NULL
, 0x0,
469 { &hf_genl_ctrl_version
,
470 { "Version", "genl.ctrl.version",
471 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
472 "Family-specific version number", HFILL
}
474 { &hf_genl_ctrl_hdrsize
,
475 { "Header Size", "genl.ctrl.hdrsize",
476 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
477 "Size of family-specific header", HFILL
}
479 { &hf_genl_ctrl_maxattr
,
480 { "Maximum Attributes", "genl.ctrl.maxattr",
481 FT_UINT32
, BASE_DEC
, NULL
, 0x0,
482 "Maximum number of attributes", HFILL
}
484 { &hf_genl_ctrl_ops_attr
,
485 { "Type", "genl.ctrl.ops_attr",
486 FT_UINT16
, BASE_DEC
, VALS(genl_ctrl_op_attr_vals
), NLA_TYPE_MASK
,
489 { &hf_genl_ctrl_groups_attr
,
490 { "Type", "genl.ctrl.groups_attr",
491 FT_UINT16
, BASE_DEC
, VALS(genl_ctrl_group_attr_vals
), NLA_TYPE_MASK
,
495 { "Command", "genl.ctrl.cmd",
496 FT_UINT8
, BASE_DEC
, VALS(genl_ctrl_cmds
), 0x0,
497 "Generic Netlink command", HFILL
}
499 { &hf_genl_ctrl_attr
,
500 { "Type", "genl.ctrl_attr",
501 FT_UINT16
, BASE_DEC
, VALS(genl_ctrl_attr_vals
), NLA_TYPE_MASK
,
504 { &hf_genl_family_id
,
505 { "Family ID", "genl.family_id",
506 FT_UINT8
, BASE_HEX
, NULL
, 0x00,
510 { "Command", "genl.cmd",
511 FT_UINT8
, BASE_DEC
, NULL
, 0x00,
512 "Generic Netlink command", HFILL
}
515 { "Family Version", "genl.version",
516 FT_UINT8
, BASE_DEC
, NULL
, 0x00,
517 "Family-specific version", HFILL
}
520 { "Reserved", "genl.reserved",
521 FT_NONE
, BASE_NONE
, NULL
, 0x00,
526 static int *ett
[] = {
527 &ett_netlink_generic
,
530 &ett_genl_ctrl_ops_attr
,
531 &ett_genl_ctrl_op_flags
,
532 &ett_genl_ctrl_groups
,
533 &ett_genl_ctrl_groups_attr
,
534 &ett_genl_nested_attr
,
537 proto_netlink_generic
= proto_register_protocol("Linux Generic Netlink protocol", "genl", "genl");
538 proto_register_field_array(proto_netlink_generic
, hf
, array_length(hf
));
539 proto_register_subtree_array(ett
, array_length(ett
));
541 netlink_generic
= register_dissector("genl", dissect_netlink_generic
, proto_netlink_generic
);
542 netlink_generic_ctrl
= register_dissector("genl_ctrl", dissect_genl_ctrl
, proto_netlink_generic
);
543 genl_dissector_table
= register_dissector_table(
545 "Linux Generic Netlink family name",
546 proto_netlink_generic
, FT_STRING
,
547 STRING_CASE_SENSITIVE
550 genl_family_map
= wmem_map_new_autoreset(wmem_epan_scope(), wmem_file_scope(), g_direct_hash
, g_direct_equal
);
552 register_init_routine(genl_init
);
556 proto_reg_handoff_netlink_generic(void)
558 dissector_add_string("genl.family", GENL_CTRL_NAME
, netlink_generic_ctrl
);
559 dissector_add_uint("netlink.protocol", WS_NETLINK_GENERIC
, netlink_generic
);
563 * Editor modelines - https://www.wireshark.org/tools/modelines.html
568 * indent-tabs-mode: t
571 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
572 * :indentSize=8:tabSize=8:noTabs=false: