/src/wireshark/epan/dissectors/packet-netlink-ovs_datapath.c
Line | Count | Source |
1 | | /* packet-netlink-ovs_datapath.c |
2 | | * Routines for Open vSwitch datapath netlink protocol dissection |
3 | | * Copyright 2026, Red Hat Inc. |
4 | | * By Timothy Redaelli <tredaelli@redhat.com> |
5 | | * |
6 | | * Wireshark - Network traffic analyzer |
7 | | * By Gerald Combs <gerald@wireshark.org> |
8 | | * Copyright 1998 Gerald Combs |
9 | | * |
10 | | * SPDX-License-Identifier: GPL-2.0-or-later |
11 | | */ |
12 | | |
13 | | /* ovs_datapath manages Open vSwitch datapaths via Generic Netlink |
14 | | * |
15 | | * Relevant Linux kernel header file: |
16 | | * include/uapi/linux/openvswitch.h |
17 | | */ |
18 | | |
19 | | #include "config.h" |
20 | | |
21 | | #include <epan/packet.h> |
22 | | |
23 | | #include "packet-netlink.h" |
24 | | |
25 | | void proto_register_netlink_ovs_datapath(void); |
26 | | void proto_reg_handoff_netlink_ovs_datapath(void); |
27 | | |
28 | | /* from <include/uapi/linux/openvswitch.h> prefixed with WS_ */ |
29 | | enum ws_ovs_dp_cmd { |
30 | | WS_OVS_DP_CMD_UNSPEC, |
31 | | WS_OVS_DP_CMD_NEW, |
32 | | WS_OVS_DP_CMD_DEL, |
33 | | WS_OVS_DP_CMD_GET, |
34 | | WS_OVS_DP_CMD_SET, |
35 | | }; |
36 | | |
37 | | enum ws_ovs_dp_attr { |
38 | | WS_OVS_DP_ATTR_UNSPEC, |
39 | | WS_OVS_DP_ATTR_NAME, |
40 | | WS_OVS_DP_ATTR_UPCALL_PID, |
41 | | WS_OVS_DP_ATTR_STATS, |
42 | | WS_OVS_DP_ATTR_MEGAFLOW_STATS, |
43 | | WS_OVS_DP_ATTR_USER_FEATURES, |
44 | | WS_OVS_DP_ATTR_PAD, |
45 | | WS_OVS_DP_ATTR_MASKS_CACHE_SIZE, |
46 | | WS_OVS_DP_ATTR_PER_CPU_PIDS, |
47 | | WS_OVS_DP_ATTR_IFINDEX, |
48 | | }; |
49 | | |
50 | | static const value_string ws_ovs_dp_commands_vals[] = { |
51 | | { WS_OVS_DP_CMD_UNSPEC, "OVS_DP_CMD_UNSPEC" }, |
52 | | { WS_OVS_DP_CMD_NEW, "OVS_DP_CMD_NEW" }, |
53 | | { WS_OVS_DP_CMD_DEL, "OVS_DP_CMD_DEL" }, |
54 | | { WS_OVS_DP_CMD_GET, "OVS_DP_CMD_GET" }, |
55 | | { WS_OVS_DP_CMD_SET, "OVS_DP_CMD_SET" }, |
56 | | { 0, NULL } |
57 | | }; |
58 | | |
59 | | static const value_string ws_ovs_dp_attr_vals[] = { |
60 | | { WS_OVS_DP_ATTR_UNSPEC, "OVS_DP_ATTR_UNSPEC" }, |
61 | | { WS_OVS_DP_ATTR_NAME, "OVS_DP_ATTR_NAME" }, |
62 | | { WS_OVS_DP_ATTR_UPCALL_PID, "OVS_DP_ATTR_UPCALL_PID" }, |
63 | | { WS_OVS_DP_ATTR_STATS, "OVS_DP_ATTR_STATS" }, |
64 | | { WS_OVS_DP_ATTR_MEGAFLOW_STATS, "OVS_DP_ATTR_MEGAFLOW_STATS" }, |
65 | | { WS_OVS_DP_ATTR_USER_FEATURES, "OVS_DP_ATTR_USER_FEATURES" }, |
66 | | { WS_OVS_DP_ATTR_PAD, "OVS_DP_ATTR_PAD" }, |
67 | | { WS_OVS_DP_ATTR_MASKS_CACHE_SIZE, "OVS_DP_ATTR_MASKS_CACHE_SIZE" }, |
68 | | { WS_OVS_DP_ATTR_PER_CPU_PIDS, "OVS_DP_ATTR_PER_CPU_PIDS" }, |
69 | | { WS_OVS_DP_ATTR_IFINDEX, "OVS_DP_ATTR_IFINDEX" }, |
70 | | { 0, NULL } |
71 | | }; |
72 | | |
73 | | struct netlink_ovs_dp_info { |
74 | | packet_info *pinfo; |
75 | | }; |
76 | | |
77 | | static dissector_handle_t netlink_ovs_dp_handle; |
78 | | |
79 | | static int proto_netlink_ovs_dp; |
80 | | |
81 | | static int hf_ovs_dp_commands; |
82 | | static int hf_ovs_dp_dp_ifindex; |
83 | | static int hf_ovs_dp_attr; |
84 | | static int hf_ovs_dp_name; |
85 | | static int hf_ovs_dp_upcall_pid; |
86 | | static int hf_ovs_dp_stats_n_hit; |
87 | | static int hf_ovs_dp_stats_n_missed; |
88 | | static int hf_ovs_dp_stats_n_lost; |
89 | | static int hf_ovs_dp_stats_n_flows; |
90 | | static int hf_ovs_dp_megaflow_stats_n_mask_hit; |
91 | | static int hf_ovs_dp_megaflow_stats_n_masks; |
92 | | static int hf_ovs_dp_megaflow_stats_n_cache_hit; |
93 | | static int hf_ovs_dp_user_features; |
94 | | static int hf_ovs_dp_user_features_unaligned; |
95 | | static int hf_ovs_dp_user_features_vport_pids; |
96 | | static int hf_ovs_dp_user_features_tc_recirc; |
97 | | static int hf_ovs_dp_user_features_per_cpu; |
98 | | static int hf_ovs_dp_masks_cache_size; |
99 | | static int hf_ovs_dp_ifindex; |
100 | | |
101 | | static int ett_ovs_dp; |
102 | | static int ett_ovs_dp_attrs; |
103 | | static int ett_ovs_dp_stats; |
104 | | static int ett_ovs_dp_megaflow_stats; |
105 | | static int ett_ovs_dp_user_features; |
106 | | |
107 | | static int * const ovs_dp_user_features_fields[] = { |
108 | | &hf_ovs_dp_user_features_unaligned, |
109 | | &hf_ovs_dp_user_features_vport_pids, |
110 | | &hf_ovs_dp_user_features_tc_recirc, |
111 | | &hf_ovs_dp_user_features_per_cpu, |
112 | | NULL |
113 | | }; |
114 | | |
115 | | static int |
116 | | dissect_ovs_dp_attrs(tvbuff_t *tvb, void *data, |
117 | | struct packet_netlink_data *nl_data, proto_tree *tree, |
118 | | int nla_type, int offset, int len) |
119 | 0 | { |
120 | 0 | enum ws_ovs_dp_attr type = (enum ws_ovs_dp_attr) nla_type; |
121 | 0 | struct netlink_ovs_dp_info *info = (struct netlink_ovs_dp_info *) data; |
122 | 0 | const uint8_t *str; |
123 | 0 | proto_item *pi; |
124 | 0 | proto_tree *ptree; |
125 | 0 | uint32_t value; |
126 | |
|
127 | 0 | switch (type) { |
128 | 0 | case WS_OVS_DP_ATTR_NAME: |
129 | 0 | DISSECTOR_ASSERT(info); |
130 | 0 | proto_tree_add_item_ret_string(tree, hf_ovs_dp_name, |
131 | 0 | tvb, offset, len, ENC_ASCII | ENC_NA, |
132 | 0 | info->pinfo->pool, &str); |
133 | 0 | proto_item_append_text(tree, ": %s", str); |
134 | 0 | return 1; |
135 | | |
136 | 0 | case WS_OVS_DP_ATTR_UPCALL_PID: |
137 | 0 | proto_tree_add_item_ret_uint(tree, hf_ovs_dp_upcall_pid, tvb, |
138 | 0 | offset, 4, nl_data->encoding, &value); |
139 | 0 | proto_item_append_text(tree, ": %u", value); |
140 | 0 | return 1; |
141 | | |
142 | 0 | case WS_OVS_DP_ATTR_STATS: |
143 | | /* struct ovs_dp_stats: n_hit(u64), n_missed(u64), |
144 | | * n_lost(u64), n_flows(u64) = 32 bytes */ |
145 | 0 | if (len == 32) { |
146 | 0 | int off = offset; |
147 | 0 | pi = proto_tree_add_subtree(tree, tvb, offset, len, |
148 | 0 | ett_ovs_dp_stats, NULL, "Datapath Statistics"); |
149 | 0 | ptree = proto_item_add_subtree(pi, ett_ovs_dp_stats); |
150 | 0 | proto_tree_add_item(ptree, hf_ovs_dp_stats_n_hit, |
151 | 0 | tvb, off, 8, nl_data->encoding); |
152 | 0 | off += 8; |
153 | 0 | proto_tree_add_item(ptree, hf_ovs_dp_stats_n_missed, |
154 | 0 | tvb, off, 8, nl_data->encoding); |
155 | 0 | off += 8; |
156 | 0 | proto_tree_add_item(ptree, hf_ovs_dp_stats_n_lost, |
157 | 0 | tvb, off, 8, nl_data->encoding); |
158 | 0 | off += 8; |
159 | 0 | proto_tree_add_item(ptree, hf_ovs_dp_stats_n_flows, |
160 | 0 | tvb, off, 8, nl_data->encoding); |
161 | 0 | return 1; |
162 | 0 | } |
163 | 0 | return 0; |
164 | | |
165 | 0 | case WS_OVS_DP_ATTR_MEGAFLOW_STATS: |
166 | | /* struct ovs_dp_megaflow_stats: n_mask_hit(u64), |
167 | | * n_masks(u32), pad0(u32), n_cache_hit(u64), |
168 | | * pad1(u64) = 32 bytes. Accept >= 16 for forward compat. */ |
169 | 0 | if (len >= 16) { |
170 | 0 | int off = offset; |
171 | 0 | pi = proto_tree_add_subtree(tree, tvb, offset, len, |
172 | 0 | ett_ovs_dp_megaflow_stats, NULL, "Megaflow Statistics"); |
173 | 0 | ptree = proto_item_add_subtree(pi, |
174 | 0 | ett_ovs_dp_megaflow_stats); |
175 | 0 | proto_tree_add_item(ptree, |
176 | 0 | hf_ovs_dp_megaflow_stats_n_mask_hit, |
177 | 0 | tvb, off, 8, nl_data->encoding); |
178 | 0 | off += 8; |
179 | 0 | proto_tree_add_item(ptree, |
180 | 0 | hf_ovs_dp_megaflow_stats_n_masks, |
181 | 0 | tvb, off, 4, nl_data->encoding); |
182 | 0 | off += 4; |
183 | 0 | off += 4; /* pad0 */ |
184 | 0 | if (off + 8 <= offset + len) { |
185 | 0 | proto_tree_add_item(ptree, |
186 | 0 | hf_ovs_dp_megaflow_stats_n_cache_hit, |
187 | 0 | tvb, off, 8, nl_data->encoding); |
188 | 0 | } |
189 | 0 | return 1; |
190 | 0 | } |
191 | 0 | return 0; |
192 | | |
193 | 0 | case WS_OVS_DP_ATTR_USER_FEATURES: |
194 | 0 | proto_tree_add_bitmask(tree, tvb, offset, |
195 | 0 | hf_ovs_dp_user_features, ett_ovs_dp_user_features, |
196 | 0 | ovs_dp_user_features_fields, nl_data->encoding); |
197 | 0 | return 1; |
198 | | |
199 | 0 | case WS_OVS_DP_ATTR_MASKS_CACHE_SIZE: |
200 | 0 | proto_tree_add_item_ret_uint(tree, hf_ovs_dp_masks_cache_size, |
201 | 0 | tvb, offset, 4, nl_data->encoding, &value); |
202 | 0 | proto_item_append_text(tree, ": %u", value); |
203 | 0 | return 1; |
204 | | |
205 | 0 | case WS_OVS_DP_ATTR_PER_CPU_PIDS: |
206 | | /* Array of u32 PIDs, one per CPU */ |
207 | 0 | for (int i = 0; i + 4 <= len; i += 4) { |
208 | 0 | proto_tree_add_item(tree, hf_ovs_dp_upcall_pid, tvb, |
209 | 0 | offset + i, 4, nl_data->encoding); |
210 | 0 | } |
211 | 0 | return 1; |
212 | | |
213 | 0 | case WS_OVS_DP_ATTR_IFINDEX: |
214 | 0 | proto_tree_add_item_ret_uint(tree, hf_ovs_dp_ifindex, tvb, |
215 | 0 | offset, 4, nl_data->encoding, &value); |
216 | 0 | proto_item_append_text(tree, ": %u", value); |
217 | 0 | return 1; |
218 | | |
219 | 0 | default: |
220 | 0 | return 0; |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | static int |
225 | | dissect_netlink_ovs_datapath(tvbuff_t *tvb, packet_info *pinfo, |
226 | | proto_tree *tree, void *data) |
227 | 0 | { |
228 | 0 | genl_info_t *genl_info = (genl_info_t *) data; |
229 | 0 | struct netlink_ovs_dp_info info; |
230 | 0 | proto_tree *nlmsg_tree; |
231 | 0 | proto_item *pi; |
232 | 0 | int offset; |
233 | |
|
234 | 0 | DISSECTOR_ASSERT(genl_info); |
235 | |
|
236 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "ovs_datapath"); |
237 | 0 | col_clear(pinfo->cinfo, COL_INFO); |
238 | |
|
239 | 0 | offset = dissect_genl_header(tvb, genl_info, genl_info->nl_data, |
240 | 0 | hf_ovs_dp_commands); |
241 | | |
242 | | /* OVS header (dp_ifindex) */ |
243 | 0 | if (tvb_reported_length_remaining(tvb, offset) < 4) |
244 | 0 | return offset; |
245 | | |
246 | 0 | pi = proto_tree_add_item(tree, proto_netlink_ovs_dp, tvb, offset, |
247 | 0 | -1, ENC_NA); |
248 | 0 | nlmsg_tree = proto_item_add_subtree(pi, ett_ovs_dp); |
249 | |
|
250 | 0 | proto_tree_add_item(nlmsg_tree, hf_ovs_dp_dp_ifindex, tvb, offset, |
251 | 0 | 4, genl_info->nl_data->encoding); |
252 | 0 | offset += 4; |
253 | |
|
254 | 0 | if (!tvb_reported_length_remaining(tvb, offset)) |
255 | 0 | return offset; |
256 | | |
257 | 0 | info.pinfo = pinfo; |
258 | 0 | offset = dissect_netlink_attributes_to_end(tvb, hf_ovs_dp_attr, |
259 | 0 | ett_ovs_dp_attrs, &info, genl_info->nl_data, |
260 | 0 | nlmsg_tree, offset, dissect_ovs_dp_attrs); |
261 | |
|
262 | 0 | return offset; |
263 | 0 | } |
264 | | |
265 | | void |
266 | | proto_register_netlink_ovs_datapath(void) |
267 | 16 | { |
268 | 16 | static hf_register_info hf[] = { |
269 | 16 | { &hf_ovs_dp_commands, |
270 | 16 | { "Command", "ovs_datapath.cmd", |
271 | 16 | FT_UINT8, BASE_DEC, |
272 | 16 | VALS(ws_ovs_dp_commands_vals), 0x00, |
273 | 16 | NULL, HFILL } |
274 | 16 | }, |
275 | 16 | { &hf_ovs_dp_dp_ifindex, |
276 | 16 | { "Datapath ifindex", "ovs_datapath.dp_ifindex", |
277 | 16 | FT_INT32, BASE_DEC, NULL, 0x00, NULL, HFILL } |
278 | 16 | }, |
279 | 16 | { &hf_ovs_dp_attr, |
280 | 16 | { "Attribute type", "ovs_datapath.attr_type", |
281 | 16 | FT_UINT16, BASE_DEC, VALS(ws_ovs_dp_attr_vals), |
282 | 16 | NLA_TYPE_MASK, NULL, HFILL } |
283 | 16 | }, |
284 | 16 | { &hf_ovs_dp_name, |
285 | 16 | { "Name", "ovs_datapath.name", |
286 | 16 | FT_STRINGZ, BASE_NONE, NULL, 0x00, NULL, HFILL } |
287 | 16 | }, |
288 | 16 | { &hf_ovs_dp_upcall_pid, |
289 | 16 | { "Upcall PID", "ovs_datapath.upcall_pid", |
290 | 16 | FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } |
291 | 16 | }, |
292 | 16 | { &hf_ovs_dp_stats_n_hit, |
293 | 16 | { "Flows hit", "ovs_datapath.stats.n_hit", |
294 | 16 | FT_UINT64, BASE_DEC, NULL, 0x00, NULL, HFILL } |
295 | 16 | }, |
296 | 16 | { &hf_ovs_dp_stats_n_missed, |
297 | 16 | { "Flows missed", "ovs_datapath.stats.n_missed", |
298 | 16 | FT_UINT64, BASE_DEC, NULL, 0x00, NULL, HFILL } |
299 | 16 | }, |
300 | 16 | { &hf_ovs_dp_stats_n_lost, |
301 | 16 | { "Flows lost", "ovs_datapath.stats.n_lost", |
302 | 16 | FT_UINT64, BASE_DEC, NULL, 0x00, NULL, HFILL } |
303 | 16 | }, |
304 | 16 | { &hf_ovs_dp_stats_n_flows, |
305 | 16 | { "Number of flows", "ovs_datapath.stats.n_flows", |
306 | 16 | FT_UINT64, BASE_DEC, NULL, 0x00, NULL, HFILL } |
307 | 16 | }, |
308 | 16 | { &hf_ovs_dp_megaflow_stats_n_mask_hit, |
309 | 16 | { "Mask hits", |
310 | 16 | "ovs_datapath.megaflow_stats.n_mask_hit", |
311 | 16 | FT_UINT64, BASE_DEC, NULL, 0x00, NULL, HFILL } |
312 | 16 | }, |
313 | 16 | { &hf_ovs_dp_megaflow_stats_n_masks, |
314 | 16 | { "Number of masks", |
315 | 16 | "ovs_datapath.megaflow_stats.n_masks", |
316 | 16 | FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } |
317 | 16 | }, |
318 | 16 | { &hf_ovs_dp_megaflow_stats_n_cache_hit, |
319 | 16 | { "Cache hits", |
320 | 16 | "ovs_datapath.megaflow_stats.n_cache_hit", |
321 | 16 | FT_UINT64, BASE_DEC, NULL, 0x00, NULL, HFILL } |
322 | 16 | }, |
323 | 16 | { &hf_ovs_dp_user_features, |
324 | 16 | { "User features", "ovs_datapath.user_features", |
325 | 16 | FT_UINT32, BASE_HEX, NULL, 0x00, NULL, HFILL } |
326 | 16 | }, |
327 | 16 | { &hf_ovs_dp_user_features_unaligned, |
328 | 16 | { "Unaligned NL attributes", |
329 | 16 | "ovs_datapath.user_features.unaligned", |
330 | 16 | FT_BOOLEAN, 32, NULL, 0x00000001, NULL, HFILL } |
331 | 16 | }, |
332 | 16 | { &hf_ovs_dp_user_features_vport_pids, |
333 | 16 | { "Multiple PIDs per vport", |
334 | 16 | "ovs_datapath.user_features.vport_pids", |
335 | 16 | FT_BOOLEAN, 32, NULL, 0x00000002, NULL, HFILL } |
336 | 16 | }, |
337 | 16 | { &hf_ovs_dp_user_features_tc_recirc, |
338 | 16 | { "TC offload recirc sharing", |
339 | 16 | "ovs_datapath.user_features.tc_recirc", |
340 | 16 | FT_BOOLEAN, 32, NULL, 0x00000004, NULL, HFILL } |
341 | 16 | }, |
342 | 16 | { &hf_ovs_dp_user_features_per_cpu, |
343 | 16 | { "Per-CPU upcall dispatch", |
344 | 16 | "ovs_datapath.user_features.per_cpu", |
345 | 16 | FT_BOOLEAN, 32, NULL, 0x00000008, NULL, HFILL } |
346 | 16 | }, |
347 | 16 | { &hf_ovs_dp_masks_cache_size, |
348 | 16 | { "Masks cache size", |
349 | 16 | "ovs_datapath.masks_cache_size", |
350 | 16 | FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } |
351 | 16 | }, |
352 | 16 | { &hf_ovs_dp_ifindex, |
353 | 16 | { "Interface index", "ovs_datapath.ifindex", |
354 | 16 | FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } |
355 | 16 | }, |
356 | 16 | }; |
357 | | |
358 | 16 | static int *ett[] = { |
359 | 16 | &ett_ovs_dp, |
360 | 16 | &ett_ovs_dp_attrs, |
361 | 16 | &ett_ovs_dp_stats, |
362 | 16 | &ett_ovs_dp_megaflow_stats, |
363 | 16 | &ett_ovs_dp_user_features, |
364 | 16 | }; |
365 | | |
366 | 16 | proto_netlink_ovs_dp = proto_register_protocol( |
367 | 16 | "Linux ovs_datapath (Open vSwitch Datapath) protocol", |
368 | 16 | "ovs_datapath", "ovs_datapath"); |
369 | 16 | proto_register_field_array(proto_netlink_ovs_dp, hf, |
370 | 16 | array_length(hf)); |
371 | 16 | proto_register_subtree_array(ett, array_length(ett)); |
372 | | |
373 | 16 | netlink_ovs_dp_handle = register_dissector("ovs_datapath", |
374 | 16 | dissect_netlink_ovs_datapath, proto_netlink_ovs_dp); |
375 | 16 | } |
376 | | |
377 | | void |
378 | | proto_reg_handoff_netlink_ovs_datapath(void) |
379 | 16 | { |
380 | 16 | dissector_add_string("genl.family", "ovs_datapath", |
381 | 16 | netlink_ovs_dp_handle); |
382 | 16 | } |
383 | | |
384 | | /* |
385 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
386 | | * |
387 | | * Local variables: |
388 | | * c-basic-offset: 8 |
389 | | * tab-width: 8 |
390 | | * indent-tabs-mode: t |
391 | | * End: |
392 | | * |
393 | | * vi: set shiftwidth=8 tabstop=8 noexpandtab: |
394 | | * :indentSize=8:tabSize=8:noTabs=false: |
395 | | */ |