/src/wireshark/epan/dissectors/packet-nvme-tcp.c
Line | Count | Source |
1 | | /* packet-nvme-tcp.c |
2 | | * Routines for NVM Express over Fabrics(TCP) dissection |
3 | | * Code by Solganik Alexander <solganik@gmail.com> |
4 | | * |
5 | | * Wireshark - Network traffic analyzer |
6 | | * By Gerald Combs <gerald@wireshark.org> |
7 | | * Copyright 1998 Gerald Combs |
8 | | * |
9 | | * SPDX-License-Identifier: GPL-2.0-or-later |
10 | | */ |
11 | | |
12 | | /* |
13 | | * Copyright (C) 2019 Lightbits Labs Ltd. - All Rights Reserved |
14 | | */ |
15 | | |
16 | | /* |
17 | | NVM Express is high speed interface for accessing solid state drives. |
18 | | NVM Express specifications are maintained by NVM Express industry |
19 | | association at http://www.nvmexpress.org. |
20 | | |
21 | | This file adds support to dissect NVM Express over fabrics packets |
22 | | for TCP. This adds very basic support for dissecting commands |
23 | | completions. |
24 | | |
25 | | Current dissection supports dissection of |
26 | | (a) NVMe cmd and cqe |
27 | | (b) NVMe Fabric command and cqe |
28 | | As part of it, it also calculates cmd completion latencies. |
29 | | |
30 | | NVM Express TCP TCP port assigned by IANA that maps to NVMe-oF service |
31 | | TCP port can be found at |
32 | | http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=NVM+Express |
33 | | |
34 | | */ |
35 | | |
36 | | #include "config.h" |
37 | | #include <epan/packet.h> |
38 | | #include <epan/prefs.h> |
39 | | #include <epan/conversation.h> |
40 | | #include <epan/crc32-tvb.h> |
41 | | #include <epan/expert.h> |
42 | | #include <epan/tfs.h> |
43 | | #include <wsutil/array.h> |
44 | | #include "packet-tcp.h" |
45 | | #include "packet-nvme.h" |
46 | | |
47 | | #include "packet-tls.h" |
48 | | |
49 | | static int proto_nvme_tcp; |
50 | | static dissector_handle_t nvmet_tcp_handle; |
51 | | static dissector_handle_t nvmet_tls_handle; |
52 | | |
53 | 16 | #define NVME_TCP_PORT_RANGE "4420,8009" /* IANA registered */ |
54 | | |
55 | 78 | #define NVME_TCP_HEADER_SIZE 8 |
56 | 12 | #define PDU_LEN_OFFSET_FROM_HEADER 4 |
57 | | static range_t *gPORT_RANGE; |
58 | | static bool nvme_tcp_check_hdgst; |
59 | | static bool nvme_tcp_check_ddgst; |
60 | 6 | #define NVME_TCP_DATA_PDU_SIZE 24 |
61 | | /* H2CTermReq/C2HTermReq header: the 8-byte common header, FES at offset 8, |
62 | | * FEI at offset 10 and reserved bytes out to offset 24, where the terminated |
63 | | * PDU's header starts. */ |
64 | 0 | #define NVME_TCP_TERMREQ_PDU_SIZE 24 |
65 | | |
66 | | enum nvme_tcp_pdu_type { |
67 | | nvme_tcp_icreq = 0x0, |
68 | | nvme_tcp_icresp = 0x1, |
69 | | nvme_tcp_h2c_term = 0x2, |
70 | | nvme_tcp_c2h_term = 0x3, |
71 | | nvme_tcp_cmd = 0x4, |
72 | | nvme_tcp_rsp = 0x5, |
73 | | nvme_tcp_h2c_data = 0x6, |
74 | | nvme_tcp_c2h_data = 0x7, |
75 | | nvme_tcp_r2t = 0x9, |
76 | | nvme_tcp_kdreq = 0xa, |
77 | | nvme_tcp_kdresp = 0xb, |
78 | | NVMET_MAX_PDU_TYPE = nvme_tcp_kdresp |
79 | | }; |
80 | | |
81 | | static const value_string nvme_tcp_pdu_type_vals[] = { |
82 | | { nvme_tcp_icreq, "ICReq" }, |
83 | | { nvme_tcp_icresp, "ICResp" }, |
84 | | { nvme_tcp_h2c_term, "H2CTerm" }, |
85 | | { nvme_tcp_c2h_term, "C2HTerm" }, |
86 | | { nvme_tcp_cmd, "CapsuleCommand" }, |
87 | | { nvme_tcp_rsp, "CapsuleResponse" }, |
88 | | { nvme_tcp_h2c_data, "H2CData" }, |
89 | | { nvme_tcp_c2h_data, "C2HData" }, |
90 | | { nvme_tcp_r2t, "Ready To Transfer" }, |
91 | | { nvme_tcp_kdreq, "Kickstart Discovery Request" }, |
92 | | { nvme_tcp_kdresp, "Kickstart Discovery Response" }, |
93 | | { 0, NULL } |
94 | | }; |
95 | | |
96 | | static const value_string nvme_tcp_termreq_fes[] = { |
97 | | {0x0, "Reserved" }, |
98 | | {0x1, "Invalid PDU Header Field" }, |
99 | | {0x2, "PDU Sequence Error" }, |
100 | | {0x3, "Header Digest Error" }, |
101 | | {0x4, "Data Transfer Out of Range" }, |
102 | | {0x5, "R2T Limit Exceeded" }, |
103 | | {0x6, "Unsupported Parameter" }, |
104 | | {0, NULL }, |
105 | | }; |
106 | | |
107 | | enum nvme_tcp_fatal_error_status |
108 | | { |
109 | | NVME_TCP_FES_INVALID_PDU_HDR = 0x01, |
110 | | NVME_TCP_FES_PDU_SEQ_ERR = 0x02, |
111 | | NVME_TCP_FES_HDR_DIGEST_ERR = 0x03, |
112 | | NVME_TCP_FES_DATA_OUT_OF_RANGE = 0x04, |
113 | | NVME_TCP_FES_R2T_LIMIT_EXCEEDED = 0x05, |
114 | | NVME_TCP_FES_DATA_LIMIT_EXCEEDED = 0x05, |
115 | | NVME_TCP_FES_UNSUPPORTED_PARAM = 0x06, |
116 | | }; |
117 | | |
118 | | enum nvme_tcp_pdu_flags { |
119 | | NVME_TCP_F_HDGST = (1 << 0), |
120 | | NVME_TCP_F_DDGST = (1 << 1), |
121 | | NVME_TCP_F_DATA_LAST = (1 << 2), |
122 | | NVME_TCP_F_DATA_SUCCESS = (1 << 3), |
123 | | }; |
124 | | |
125 | | |
126 | | enum nvme_tcp_digest_option { |
127 | | NVME_TCP_HDR_DIGEST_ENABLE = (1 << 0), |
128 | | NVME_TCP_DATA_DIGEST_ENABLE = (1 << 1), |
129 | | }; |
130 | | |
131 | | |
132 | 0 | #define NVME_FABRIC_CMD_SIZE NVME_CMD_SIZE |
133 | 0 | #define NVME_FABRIC_CQE_SIZE NVME_CQE_SIZE |
134 | 4 | #define NVME_TCP_DIGEST_LENGTH 4 |
135 | | |
136 | | struct nvme_tcp_q_ctx { |
137 | | struct nvme_q_ctx n_q_ctx; |
138 | | /* H2CData requests, keyed [frame][cid]; kept apart from |
139 | | * n_q_ctx.data_requests (keyed SGL descriptors, other key shape) */ |
140 | | wmem_tree_t *h2c_requests; |
141 | | }; |
142 | | |
143 | | struct nvme_tcp_cmd_ctx { |
144 | | struct nvme_cmd_ctx n_cmd_ctx; |
145 | | }; |
146 | | |
147 | | void proto_reg_handoff_nvme_tcp(void); |
148 | | void proto_register_nvme_tcp(void); |
149 | | |
150 | | |
151 | | static int hf_nvme_tcp_type; |
152 | | static int hf_nvme_tcp_flags; |
153 | | static int hf_pdu_flags_hdgst; |
154 | | static int hf_pdu_flags_ddgst; |
155 | | static int hf_pdu_flags_data_last; |
156 | | static int hf_pdu_flags_data_success; |
157 | | |
158 | | static int * const nvme_tcp_pdu_flags[] = { |
159 | | &hf_pdu_flags_hdgst, |
160 | | &hf_pdu_flags_ddgst, |
161 | | &hf_pdu_flags_data_last, |
162 | | &hf_pdu_flags_data_success, |
163 | | NULL |
164 | | }; |
165 | | |
166 | | static int hf_nvme_tcp_hdgst; |
167 | | static int hf_nvme_tcp_ddgst; |
168 | | static int hf_nvme_tcp_hlen; |
169 | | static int hf_nvme_tcp_pdo; |
170 | | static int hf_nvme_tcp_plen; |
171 | | static int hf_nvme_tcp_hdgst_status; |
172 | | static int hf_nvme_tcp_ddgst_status; |
173 | | |
174 | | /* NVMe tcp icreq/icresp fields */ |
175 | | static int hf_nvme_tcp_icreq; |
176 | | static int hf_nvme_tcp_icreq_pfv; |
177 | | static int hf_nvme_tcp_icreq_maxr2t; |
178 | | static int hf_nvme_tcp_icreq_hpda; |
179 | | static int hf_nvme_tcp_icreq_digest; |
180 | | static int hf_nvme_tcp_icresp; |
181 | | static int hf_nvme_tcp_icresp_pfv; |
182 | | static int hf_nvme_tcp_icresp_cpda; |
183 | | static int hf_nvme_tcp_icresp_digest; |
184 | | static int hf_nvme_tcp_icresp_maxdata; |
185 | | |
186 | | /* NVMe tcp c2h/h2c termreq fields */ |
187 | | static int hf_nvme_tcp_c2htermreq; |
188 | | static int hf_nvme_tcp_c2htermreq_fes; |
189 | | static int hf_nvme_tcp_c2htermreq_phfo; |
190 | | static int hf_nvme_tcp_c2htermreq_phd; |
191 | | static int hf_nvme_tcp_c2htermreq_upfo; |
192 | | static int hf_nvme_tcp_c2htermreq_reserved; |
193 | | static int hf_nvme_tcp_c2htermreq_data; |
194 | | static int hf_nvme_tcp_h2ctermreq; |
195 | | static int hf_nvme_tcp_h2ctermreq_fes; |
196 | | static int hf_nvme_tcp_h2ctermreq_phfo; |
197 | | static int hf_nvme_tcp_h2ctermreq_phd; |
198 | | static int hf_nvme_tcp_h2ctermreq_upfo; |
199 | | static int hf_nvme_tcp_h2ctermreq_reserved; |
200 | | static int hf_nvme_tcp_h2ctermreq_data; |
201 | | |
202 | | /* NVMe fabrics command */ |
203 | | static int hf_nvme_fabrics_cmd_cid; |
204 | | |
205 | | /* NVMe fabrics command data*/ |
206 | | static int hf_nvme_fabrics_cmd_data; |
207 | | static int hf_nvme_tcp_unknown_data; |
208 | | |
209 | | static int hf_nvme_tcp_r2t_pdu; |
210 | | static int hf_nvme_tcp_r2t_offset; |
211 | | static int hf_nvme_tcp_r2t_length; |
212 | | static int hf_nvme_tcp_r2t_resvd; |
213 | | |
214 | | /* tracking Cmd and its respective CQE */ |
215 | | static int hf_nvme_tcp_cmd_pkt; |
216 | | static int hf_nvme_fabrics_cmd_qid; |
217 | | |
218 | | /* Data response fields */ |
219 | | static int hf_nvme_tcp_data_pdu; |
220 | | static int hf_nvme_tcp_pdu_ttag; |
221 | | static int hf_nvme_tcp_data_pdu_data_offset; |
222 | | static int hf_nvme_tcp_data_pdu_data_length; |
223 | | static int hf_nvme_tcp_data_pdu_data_resvd; |
224 | | |
225 | | static int ett_nvme_tcp; |
226 | | |
227 | | static expert_field ei_nvme_tcp_termreq_short; |
228 | | |
229 | | static unsigned |
230 | | get_nvme_tcp_pdu_len(packet_info *pinfo _U_, |
231 | | tvbuff_t *tvb, |
232 | | int offset, |
233 | | void* data _U_) |
234 | 12 | { |
235 | 12 | return tvb_get_letohl(tvb, offset + PDU_LEN_OFFSET_FROM_HEADER); |
236 | 12 | } |
237 | | |
238 | | static void |
239 | | dissect_nvme_tcp_icreq(tvbuff_t *tvb, |
240 | | packet_info *pinfo, |
241 | | int offset, |
242 | | proto_tree *tree) |
243 | 0 | { |
244 | 0 | proto_item *tf; |
245 | 0 | proto_item *icreq_tree; |
246 | |
|
247 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Initialize Connection Request"); |
248 | 0 | tf = proto_tree_add_item(tree, hf_nvme_tcp_icreq, tvb, offset, 8, ENC_NA); |
249 | 0 | icreq_tree = proto_item_add_subtree(tf, ett_nvme_tcp); |
250 | |
|
251 | 0 | proto_tree_add_item(icreq_tree, hf_nvme_tcp_icreq_pfv, tvb, offset, 2, |
252 | 0 | ENC_LITTLE_ENDIAN); |
253 | 0 | proto_tree_add_item(icreq_tree, hf_nvme_tcp_icreq_hpda, tvb, offset + 2, 1, |
254 | 0 | ENC_NA); |
255 | 0 | proto_tree_add_item(icreq_tree, hf_nvme_tcp_icreq_digest, tvb, offset + 3, |
256 | 0 | 1, ENC_NA); |
257 | 0 | proto_tree_add_item(icreq_tree, hf_nvme_tcp_icreq_maxr2t, tvb, offset + 4, |
258 | 0 | 4, ENC_LITTLE_ENDIAN); |
259 | 0 | } |
260 | | |
261 | | static void |
262 | | dissect_nvme_tcp_icresp(tvbuff_t *tvb, |
263 | | packet_info *pinfo, |
264 | | int offset, |
265 | | proto_tree *tree) |
266 | 0 | { |
267 | 0 | proto_item *tf; |
268 | 0 | proto_item *icresp_tree; |
269 | |
|
270 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "Initialize Connection Response"); |
271 | 0 | tf = proto_tree_add_item(tree, hf_nvme_tcp_icresp, tvb, offset, 8, ENC_NA); |
272 | 0 | icresp_tree = proto_item_add_subtree(tf, ett_nvme_tcp); |
273 | |
|
274 | 0 | proto_tree_add_item(icresp_tree, hf_nvme_tcp_icresp_pfv, tvb, offset, 2, |
275 | 0 | ENC_LITTLE_ENDIAN); |
276 | 0 | proto_tree_add_item(icresp_tree, hf_nvme_tcp_icresp_cpda, tvb, offset + 2, |
277 | 0 | 1, ENC_NA); |
278 | 0 | proto_tree_add_item(icresp_tree, hf_nvme_tcp_icresp_digest, tvb, offset + 3, |
279 | 0 | 1, ENC_NA); |
280 | 0 | proto_tree_add_item(icresp_tree, hf_nvme_tcp_icresp_maxdata, tvb, |
281 | 0 | offset + 4, 4, ENC_LITTLE_ENDIAN); |
282 | 0 | } |
283 | | |
284 | | static struct nvme_tcp_cmd_ctx* |
285 | | bind_cmd_to_qctx(packet_info *pinfo, |
286 | | struct nvme_q_ctx *q_ctx, |
287 | | uint16_t cmd_id) |
288 | 7 | { |
289 | 7 | struct nvme_tcp_cmd_ctx *ctx; |
290 | | |
291 | | /* wireshark will dissect same packet multiple times |
292 | | * when display is refreshed*/ |
293 | 7 | if (!PINFO_FD_VISITED(pinfo)) { |
294 | 7 | ctx = wmem_new0(wmem_file_scope(), struct nvme_tcp_cmd_ctx); |
295 | 7 | nvme_add_cmd_to_pending_list(pinfo, q_ctx, &ctx->n_cmd_ctx, (void*) ctx, |
296 | 7 | cmd_id); |
297 | 7 | } else { |
298 | | /* Already visited this frame */ |
299 | 0 | ctx = (struct nvme_tcp_cmd_ctx*) nvme_lookup_cmd_in_done_list(pinfo, |
300 | 0 | q_ctx, cmd_id); |
301 | | /* if we have already visited frame but haven't found completion yet, |
302 | | * we won't find cmd in done q, so allocate a dummy ctx for doing |
303 | | * rest of the processing. |
304 | | */ |
305 | 0 | if (!ctx) |
306 | 0 | ctx = wmem_new0(wmem_file_scope(), struct nvme_tcp_cmd_ctx); |
307 | 0 | } |
308 | | |
309 | 7 | return ctx; |
310 | 7 | } |
311 | | |
312 | | static void |
313 | | dissect_nvme_tcp_command(tvbuff_t *tvb, |
314 | | packet_info *pinfo, |
315 | | proto_tree *root_tree, |
316 | | proto_tree *nvme_tcp_tree, |
317 | | proto_item *nvme_tcp_ti, |
318 | | struct nvme_tcp_q_ctx *queue, int offset, |
319 | | uint32_t incapsuled_data_size, |
320 | | uint32_t data_offset) |
321 | 7 | { |
322 | 7 | struct nvme_tcp_cmd_ctx *cmd_ctx; |
323 | 7 | uint16_t cmd_id; |
324 | 7 | uint8_t opcode; |
325 | 7 | const char *cmd_string; |
326 | | |
327 | 7 | opcode = tvb_get_uint8(tvb, offset); |
328 | 7 | cmd_id = tvb_get_uint16(tvb, offset + 2, ENC_LITTLE_ENDIAN); |
329 | 7 | cmd_ctx = bind_cmd_to_qctx(pinfo, &queue->n_q_ctx, cmd_id); |
330 | | |
331 | | /* if record did not contain connect command we won't know qid, |
332 | | * so let's guess if this is an admin queue */ |
333 | 7 | if ((queue->n_q_ctx.qid == UINT16_MAX) && !nvme_is_io_queue_opcode(opcode)) |
334 | 7 | queue->n_q_ctx.qid = 0; |
335 | | |
336 | 7 | if (opcode == NVME_FABRIC_OPC) { |
337 | 0 | cmd_ctx->n_cmd_ctx.fabric = true; |
338 | 0 | dissect_nvmeof_fabric_cmd(tvb, pinfo, nvme_tcp_tree, &queue->n_q_ctx, &cmd_ctx->n_cmd_ctx, offset, false); |
339 | 0 | if (cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype == NVME_FCTYPE_CONNECT) |
340 | 0 | queue->n_q_ctx.qid = cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.cnct.qid; |
341 | 0 | cmd_string = get_nvmeof_cmd_string(cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype); |
342 | 0 | proto_item_append_text(nvme_tcp_ti, |
343 | 0 | ", Fabrics Type: %s (0x%02x) Cmd ID: 0x%04x", cmd_string, |
344 | 0 | cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype, cmd_id); |
345 | 0 | if (incapsuled_data_size > 0) { |
346 | 0 | proto_tree *data_tree; |
347 | 0 | proto_item *ti; |
348 | |
|
349 | 0 | ti = proto_tree_add_item(nvme_tcp_tree, hf_nvme_fabrics_cmd_data, tvb, offset, incapsuled_data_size, ENC_NA); |
350 | 0 | data_tree = proto_item_add_subtree(ti, ett_nvme_tcp); |
351 | 0 | dissect_nvmeof_cmd_data(tvb, pinfo, data_tree, offset + NVME_FABRIC_CMD_SIZE + data_offset, &queue->n_q_ctx, &cmd_ctx->n_cmd_ctx, incapsuled_data_size); |
352 | 0 | } |
353 | 0 | return; |
354 | 0 | } |
355 | | |
356 | | /* In case of incapsuled nvme command tcp length is only a header */ |
357 | 7 | proto_item_set_len(nvme_tcp_ti, NVME_TCP_HEADER_SIZE); |
358 | 7 | tvbuff_t *nvme_tvbuff; |
359 | 7 | cmd_ctx->n_cmd_ctx.fabric = false; |
360 | 7 | nvme_tvbuff = tvb_new_subset_remaining(tvb, NVME_TCP_HEADER_SIZE); |
361 | 7 | cmd_string = nvme_get_opcode_string(opcode, queue->n_q_ctx.qid); |
362 | 7 | dissect_nvme_cmd(nvme_tvbuff, pinfo, root_tree, &queue->n_q_ctx, |
363 | 7 | &cmd_ctx->n_cmd_ctx); |
364 | 7 | proto_item_append_text(nvme_tcp_ti, |
365 | 7 | ", NVMe Opcode: %s (0x%02x) Cmd ID: 0x%04x", cmd_string, opcode, |
366 | 7 | cmd_id); |
367 | | |
368 | | /* This is an inline write */ |
369 | 7 | if (incapsuled_data_size > 0) { |
370 | 4 | tvbuff_t *nvme_data; |
371 | | |
372 | 4 | nvme_data = tvb_new_subset_remaining(tvb, offset + |
373 | 4 | NVME_CMD_SIZE + data_offset); |
374 | 4 | dissect_nvme_data_response(nvme_data, pinfo, root_tree, &queue->n_q_ctx, |
375 | 4 | &cmd_ctx->n_cmd_ctx, incapsuled_data_size, true); |
376 | 4 | } |
377 | 7 | } |
378 | | |
379 | | static uint32_t |
380 | | dissect_nvme_tcp_data_pdu(tvbuff_t *tvb, |
381 | | packet_info *pinfo, |
382 | | int offset, |
383 | 2 | proto_tree *tree) { |
384 | 2 | uint32_t data_length; |
385 | 2 | proto_item *tf; |
386 | 2 | proto_item *data_tree; |
387 | | |
388 | 2 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "NVMe"); |
389 | | |
390 | 2 | tf = proto_tree_add_item(tree, hf_nvme_tcp_data_pdu, tvb, offset, |
391 | 2 | NVME_TCP_DATA_PDU_SIZE - NVME_TCP_HEADER_SIZE, ENC_NA); |
392 | 2 | data_tree = proto_item_add_subtree(tf, ett_nvme_tcp); |
393 | | |
394 | 2 | proto_tree_add_item(data_tree, hf_nvme_fabrics_cmd_cid, tvb, offset, 2, |
395 | 2 | ENC_LITTLE_ENDIAN); |
396 | | |
397 | 2 | proto_tree_add_item(data_tree, hf_nvme_tcp_pdu_ttag, tvb, offset + 2, 2, |
398 | 2 | ENC_LITTLE_ENDIAN); |
399 | | |
400 | 2 | proto_tree_add_item(data_tree, hf_nvme_tcp_data_pdu_data_offset, tvb, |
401 | 2 | offset + 4, 4, ENC_LITTLE_ENDIAN); |
402 | | |
403 | 2 | data_length = tvb_get_uint32(tvb, offset + 8, ENC_LITTLE_ENDIAN); |
404 | 2 | proto_tree_add_item(data_tree, hf_nvme_tcp_data_pdu_data_length, tvb, |
405 | 2 | offset + 8, 4, ENC_LITTLE_ENDIAN); |
406 | | |
407 | 2 | proto_tree_add_item(data_tree, hf_nvme_tcp_data_pdu_data_resvd, tvb, |
408 | 2 | offset + 12, 4, ENC_NA); |
409 | | |
410 | 2 | return data_length; |
411 | 2 | } |
412 | | |
413 | | static void |
414 | | dissect_nvme_tcp_c2h_data(tvbuff_t *tvb, |
415 | | packet_info *pinfo, |
416 | | proto_tree *root_tree, |
417 | | proto_tree *nvme_tcp_tree, |
418 | | proto_item *nvme_tcp_ti, |
419 | | struct nvme_tcp_q_ctx *queue, |
420 | | int offset, |
421 | | uint32_t data_offset) |
422 | 0 | { |
423 | 0 | struct nvme_tcp_cmd_ctx *cmd_ctx; |
424 | 0 | uint32_t cmd_id; |
425 | 0 | uint32_t data_length; |
426 | 0 | tvbuff_t *nvme_data; |
427 | 0 | const char *cmd_string; |
428 | |
|
429 | 0 | cmd_id = tvb_get_uint16(tvb, offset, ENC_LITTLE_ENDIAN); |
430 | 0 | data_length = dissect_nvme_tcp_data_pdu(tvb, pinfo, offset, nvme_tcp_tree); |
431 | | |
432 | | /* This can identify our packet uniquely */ |
433 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
434 | 0 | cmd_ctx = (struct nvme_tcp_cmd_ctx*) nvme_lookup_cmd_in_pending_list( |
435 | 0 | &queue->n_q_ctx, cmd_id); |
436 | 0 | if (!cmd_ctx) { |
437 | 0 | proto_tree_add_item(root_tree, hf_nvme_tcp_unknown_data, tvb, offset + 16, |
438 | 0 | data_length, ENC_NA); |
439 | 0 | return; |
440 | 0 | } |
441 | | |
442 | | /* In order to later lookup for command context lets add this command |
443 | | * to data responses */ |
444 | 0 | cmd_ctx->n_cmd_ctx.data_tr_pkt_num[0] = pinfo->num; |
445 | 0 | nvme_add_data_tr_pkt(&queue->n_q_ctx, &cmd_ctx->n_cmd_ctx, cmd_id, pinfo->num); |
446 | 0 | } else { |
447 | 0 | cmd_ctx = (struct nvme_tcp_cmd_ctx*) nvme_lookup_data_tr_pkt(&queue->n_q_ctx, |
448 | 0 | cmd_id, pinfo->num); |
449 | 0 | if (!cmd_ctx) { |
450 | 0 | proto_tree_add_item(root_tree, hf_nvme_tcp_unknown_data, tvb, offset + 16, |
451 | 0 | data_length, ENC_NA); |
452 | 0 | return; |
453 | 0 | } |
454 | 0 | } |
455 | | |
456 | 0 | nvme_publish_to_cmd_link(nvme_tcp_tree, tvb, |
457 | 0 | hf_nvme_tcp_cmd_pkt, &cmd_ctx->n_cmd_ctx); |
458 | |
|
459 | 0 | if (cmd_ctx->n_cmd_ctx.fabric) { |
460 | 0 | cmd_string = get_nvmeof_cmd_string(cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype); |
461 | 0 | proto_item_append_text(nvme_tcp_ti, |
462 | 0 | ", C2HData Fabrics Type: %s (0x%02x), Cmd ID: 0x%04x, Len: %u", |
463 | 0 | cmd_string, cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype, cmd_id, data_length); |
464 | 0 | } else { |
465 | 0 | cmd_string = nvme_get_opcode_string(cmd_ctx->n_cmd_ctx.opcode, |
466 | 0 | queue->n_q_ctx.qid); |
467 | 0 | proto_item_append_text(nvme_tcp_ti, |
468 | 0 | ", C2HData Opcode: %s (0x%02x), Cmd ID: 0x%04x, Len: %u", |
469 | 0 | cmd_string, cmd_ctx->n_cmd_ctx.opcode, cmd_id, data_length); |
470 | 0 | } |
471 | |
|
472 | 0 | nvme_data = tvb_new_subset_remaining(tvb, NVME_TCP_DATA_PDU_SIZE + data_offset); |
473 | |
|
474 | 0 | dissect_nvme_data_response(nvme_data, pinfo, root_tree, &queue->n_q_ctx, |
475 | 0 | &cmd_ctx->n_cmd_ctx, data_length, false); |
476 | |
|
477 | 0 | } |
478 | | |
479 | | static void nvme_tcp_build_cmd_key(uint32_t *frame_num, uint32_t *cmd_id, wmem_tree_key_t *key) |
480 | 0 | { |
481 | 0 | key[0].key = frame_num; |
482 | 0 | key[0].length = 1; |
483 | 0 | key[1].key = cmd_id; |
484 | 0 | key[1].length = 1; |
485 | 0 | key[2].key = NULL; |
486 | 0 | key[2].length = 0; |
487 | 0 | } |
488 | | |
489 | | static void nvme_tcp_add_data_request(packet_info *pinfo, struct nvme_tcp_q_ctx *q_ctx, |
490 | | struct nvme_tcp_cmd_ctx *cmd_ctx, uint16_t cmd_id) |
491 | 0 | { |
492 | 0 | wmem_tree_key_t cmd_key[3]; |
493 | 0 | uint32_t cmd_id_key = cmd_id; |
494 | |
|
495 | 0 | nvme_tcp_build_cmd_key(&pinfo->num, &cmd_id_key, cmd_key); |
496 | 0 | cmd_ctx->n_cmd_ctx.data_req_pkt_num = pinfo->num; |
497 | 0 | cmd_ctx->n_cmd_ctx.data_tr_pkt_num[0] = 0; |
498 | 0 | wmem_tree_insert32_array(q_ctx->h2c_requests, cmd_key, (void *)cmd_ctx); |
499 | 0 | } |
500 | | |
501 | | static struct nvme_tcp_cmd_ctx* nvme_tcp_lookup_data_request(packet_info *pinfo, |
502 | | struct nvme_tcp_q_ctx *q_ctx, |
503 | | uint16_t cmd_id) |
504 | 0 | { |
505 | 0 | wmem_tree_key_t cmd_key[3]; |
506 | 0 | uint32_t cmd_id_key = cmd_id; |
507 | |
|
508 | 0 | nvme_tcp_build_cmd_key(&pinfo->num, &cmd_id_key, cmd_key); |
509 | 0 | return (struct nvme_tcp_cmd_ctx*)wmem_tree_lookup32_array(q_ctx->h2c_requests, cmd_key); |
510 | 0 | } |
511 | | |
512 | | static void |
513 | | dissect_nvme_tcp_h2c_data(tvbuff_t *tvb, |
514 | | packet_info *pinfo, |
515 | | proto_tree *root_tree, |
516 | | proto_tree *nvme_tcp_tree, |
517 | | proto_item *nvme_tcp_ti, |
518 | | struct nvme_tcp_q_ctx *queue, |
519 | | int offset, |
520 | | uint32_t data_offset) |
521 | 2 | { |
522 | 2 | struct nvme_tcp_cmd_ctx *cmd_ctx; |
523 | 2 | uint16_t cmd_id; |
524 | 2 | uint32_t data_length; |
525 | 2 | tvbuff_t *nvme_data; |
526 | 2 | const char *cmd_string; |
527 | | |
528 | 2 | cmd_id = tvb_get_uint16(tvb, offset, ENC_LITTLE_ENDIAN); |
529 | 2 | data_length = dissect_nvme_tcp_data_pdu(tvb, pinfo, offset, nvme_tcp_tree); |
530 | | |
531 | 2 | if (!PINFO_FD_VISITED(pinfo)) { |
532 | 0 | cmd_ctx = (struct nvme_tcp_cmd_ctx*) nvme_lookup_cmd_in_pending_list( |
533 | 0 | &queue->n_q_ctx, cmd_id); |
534 | 0 | if (!cmd_ctx) { |
535 | 0 | proto_tree_add_item(root_tree, hf_nvme_tcp_unknown_data, tvb, offset + 16, |
536 | 0 | data_length, ENC_NA); |
537 | 0 | return; |
538 | 0 | } |
539 | | |
540 | | /* Fill this for "adding data request call, |
541 | | * this will be the key to fetch data request later */ |
542 | 0 | nvme_tcp_add_data_request(pinfo, queue, cmd_ctx, cmd_id); |
543 | 2 | } else { |
544 | 2 | cmd_ctx = nvme_tcp_lookup_data_request(pinfo, queue, cmd_id); |
545 | 2 | if (!cmd_ctx) { |
546 | 0 | proto_tree_add_item(root_tree, hf_nvme_tcp_unknown_data, tvb, offset + 16, |
547 | 0 | data_length, ENC_NA); |
548 | 0 | return; |
549 | 0 | } |
550 | 2 | } |
551 | | |
552 | 2 | nvme_publish_to_cmd_link(nvme_tcp_tree, tvb, |
553 | 2 | hf_nvme_tcp_cmd_pkt, &cmd_ctx->n_cmd_ctx); |
554 | | |
555 | | /* fabrics commands should not have h2cdata*/ |
556 | 2 | if (cmd_ctx->n_cmd_ctx.fabric) { |
557 | 0 | cmd_string = get_nvmeof_cmd_string(cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype); |
558 | 0 | proto_item_append_text(nvme_tcp_ti, |
559 | 0 | ", H2CData Fabrics Type: %s (0x%02x), Cmd ID: 0x%04x, Len: %u", |
560 | 0 | cmd_string, cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype, cmd_id, data_length); |
561 | 0 | proto_tree_add_item(root_tree, hf_nvme_tcp_unknown_data, tvb, offset + 16, |
562 | 0 | data_length, ENC_NA); |
563 | 0 | return; |
564 | 0 | } |
565 | | |
566 | 2 | cmd_string = nvme_get_opcode_string(cmd_ctx->n_cmd_ctx.opcode, |
567 | 2 | queue->n_q_ctx.qid); |
568 | 2 | proto_item_append_text(nvme_tcp_ti, |
569 | 2 | ", H2CData Opcode: %s (0x%02x), Cmd ID: 0x%04x, Len: %u", |
570 | 2 | cmd_string, cmd_ctx->n_cmd_ctx.opcode, cmd_id, data_length); |
571 | | |
572 | 2 | nvme_data = tvb_new_subset_remaining(tvb, NVME_TCP_DATA_PDU_SIZE + data_offset); |
573 | 2 | dissect_nvme_data_response(nvme_data, pinfo, root_tree, &queue->n_q_ctx, |
574 | 2 | &cmd_ctx->n_cmd_ctx, data_length, false); |
575 | 2 | } |
576 | | |
577 | | static void |
578 | | dissect_nvme_tcp_h2ctermreq(tvbuff_t *tvb, packet_info *pinfo, |
579 | | proto_tree *tree, uint32_t packet_len, int offset) |
580 | 0 | { |
581 | 0 | proto_item *tf; |
582 | 0 | proto_item *h2ctermreq_tree; |
583 | 0 | uint16_t fes; |
584 | |
|
585 | 0 | col_set_str(pinfo->cinfo, COL_INFO, |
586 | 0 | "Host to Controller Termination Request"); |
587 | 0 | tf = proto_tree_add_item(tree, hf_nvme_tcp_h2ctermreq, |
588 | 0 | tvb, offset, 8, ENC_NA); |
589 | 0 | h2ctermreq_tree = proto_item_add_subtree(tf, ett_nvme_tcp); |
590 | | |
591 | | /* tcp_dissect_pdus() only guarantees PLEN >= NVME_TCP_HEADER_SIZE, so a |
592 | | * short PLEN would underflow the trailing data length below. */ |
593 | 0 | if (packet_len < NVME_TCP_TERMREQ_PDU_SIZE) { |
594 | 0 | proto_tree_add_expert_format_remaining(h2ctermreq_tree, pinfo, |
595 | 0 | &ei_nvme_tcp_termreq_short, tvb, offset, |
596 | 0 | "PDU length %u is shorter than the %u byte Termination Request header", |
597 | 0 | packet_len, NVME_TCP_TERMREQ_PDU_SIZE); |
598 | 0 | return; |
599 | 0 | } |
600 | | |
601 | 0 | proto_tree_add_item(h2ctermreq_tree, hf_nvme_tcp_h2ctermreq_fes, |
602 | 0 | tvb, offset + 8, 2, ENC_LITTLE_ENDIAN); |
603 | 0 | fes = tvb_get_uint16(tvb, offset + 8, ENC_LITTLE_ENDIAN); |
604 | 0 | switch (fes) { |
605 | 0 | case NVME_TCP_FES_INVALID_PDU_HDR: |
606 | 0 | proto_tree_add_item(h2ctermreq_tree, hf_nvme_tcp_h2ctermreq_phfo, |
607 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
608 | 0 | break; |
609 | 0 | case NVME_TCP_FES_HDR_DIGEST_ERR: |
610 | 0 | proto_tree_add_item(h2ctermreq_tree, hf_nvme_tcp_h2ctermreq_phd, |
611 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
612 | 0 | break; |
613 | 0 | case NVME_TCP_FES_UNSUPPORTED_PARAM: |
614 | 0 | proto_tree_add_item(h2ctermreq_tree, hf_nvme_tcp_h2ctermreq_upfo, |
615 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
616 | 0 | break; |
617 | 0 | default: |
618 | 0 | proto_tree_add_item(h2ctermreq_tree, hf_nvme_tcp_h2ctermreq_reserved, |
619 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
620 | 0 | break; |
621 | 0 | } |
622 | 0 | proto_tree_add_item(h2ctermreq_tree, hf_nvme_tcp_h2ctermreq_data, |
623 | 0 | tvb, offset + NVME_TCP_TERMREQ_PDU_SIZE, |
624 | 0 | packet_len - NVME_TCP_TERMREQ_PDU_SIZE, ENC_NA); |
625 | 0 | } |
626 | | |
627 | | static void |
628 | | dissect_nvme_tcp_c2htermreq(tvbuff_t *tvb, packet_info *pinfo, |
629 | | proto_tree *tree, uint32_t packet_len, int offset) |
630 | 0 | { |
631 | 0 | proto_item *tf; |
632 | 0 | proto_item *c2htermreq_tree; |
633 | 0 | uint16_t fes; |
634 | |
|
635 | 0 | col_set_str(pinfo->cinfo, COL_INFO, |
636 | 0 | "Controller to Host Termination Request"); |
637 | 0 | tf = proto_tree_add_item(tree, hf_nvme_tcp_c2htermreq, |
638 | 0 | tvb, offset, 8, ENC_NA); |
639 | 0 | c2htermreq_tree = proto_item_add_subtree(tf, ett_nvme_tcp); |
640 | | |
641 | | /* tcp_dissect_pdus() only guarantees PLEN >= NVME_TCP_HEADER_SIZE, so a |
642 | | * short PLEN would underflow the trailing data length below. */ |
643 | 0 | if (packet_len < NVME_TCP_TERMREQ_PDU_SIZE) { |
644 | 0 | proto_tree_add_expert_format_remaining(c2htermreq_tree, pinfo, |
645 | 0 | &ei_nvme_tcp_termreq_short, tvb, offset, |
646 | 0 | "PDU length %u is shorter than the %u byte Termination Request header", |
647 | 0 | packet_len, NVME_TCP_TERMREQ_PDU_SIZE); |
648 | 0 | return; |
649 | 0 | } |
650 | | |
651 | 0 | proto_tree_add_item(c2htermreq_tree, hf_nvme_tcp_c2htermreq_fes, tvb, offset + 8, 2, |
652 | 0 | ENC_LITTLE_ENDIAN); |
653 | 0 | fes = tvb_get_uint16(tvb, offset + 8, ENC_LITTLE_ENDIAN); |
654 | 0 | switch (fes) { |
655 | 0 | case NVME_TCP_FES_INVALID_PDU_HDR: |
656 | 0 | proto_tree_add_item(c2htermreq_tree, hf_nvme_tcp_c2htermreq_phfo, |
657 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
658 | 0 | break; |
659 | 0 | case NVME_TCP_FES_HDR_DIGEST_ERR: |
660 | 0 | proto_tree_add_item(c2htermreq_tree, hf_nvme_tcp_c2htermreq_phd, |
661 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
662 | 0 | break; |
663 | 0 | case NVME_TCP_FES_UNSUPPORTED_PARAM: |
664 | 0 | proto_tree_add_item(c2htermreq_tree, hf_nvme_tcp_c2htermreq_upfo, |
665 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
666 | 0 | break; |
667 | 0 | default: |
668 | 0 | proto_tree_add_item(c2htermreq_tree, hf_nvme_tcp_c2htermreq_reserved, |
669 | 0 | tvb, offset + 10, 4, ENC_LITTLE_ENDIAN); |
670 | 0 | break; |
671 | 0 | } |
672 | 0 | proto_tree_add_item(c2htermreq_tree, hf_nvme_tcp_c2htermreq_data, |
673 | 0 | tvb, offset + NVME_TCP_TERMREQ_PDU_SIZE, |
674 | 0 | packet_len - NVME_TCP_TERMREQ_PDU_SIZE, ENC_NA); |
675 | 0 | } |
676 | | |
677 | | static void |
678 | | dissect_nvme_tcp_cqe(tvbuff_t *tvb, |
679 | | packet_info *pinfo, |
680 | | proto_tree *root_tree, |
681 | | proto_tree *nvme_tree, |
682 | | proto_item *ti, |
683 | | struct nvme_tcp_q_ctx *queue, |
684 | | int offset) |
685 | 0 | { |
686 | 0 | struct nvme_tcp_cmd_ctx *cmd_ctx; |
687 | 0 | uint16_t cmd_id; |
688 | 0 | const char *cmd_string; |
689 | |
|
690 | 0 | cmd_id = tvb_get_uint16(tvb, offset + 12, ENC_LITTLE_ENDIAN); |
691 | | |
692 | | /* wireshark will dissect packet several times when display is refreshed |
693 | | * we need to track state changes only once */ |
694 | 0 | if (!PINFO_FD_VISITED(pinfo)) { |
695 | 0 | cmd_ctx = (struct nvme_tcp_cmd_ctx*) nvme_lookup_cmd_in_pending_list( |
696 | 0 | &queue->n_q_ctx, cmd_id); |
697 | 0 | if (!cmd_ctx || cmd_ctx->n_cmd_ctx.cqe_pkt_num) { |
698 | 0 | proto_tree_add_item(nvme_tree, hf_nvme_tcp_unknown_data, tvb, offset, |
699 | 0 | NVME_FABRIC_CQE_SIZE, ENC_NA); |
700 | 0 | return; |
701 | 0 | } |
702 | | |
703 | 0 | cmd_ctx->n_cmd_ctx.cqe_pkt_num = pinfo->num; |
704 | 0 | nvme_add_cmd_cqe_to_done_list(&queue->n_q_ctx, &cmd_ctx->n_cmd_ctx, |
705 | 0 | cmd_id); |
706 | |
|
707 | 0 | } else { |
708 | 0 | cmd_ctx = (struct nvme_tcp_cmd_ctx *) nvme_lookup_cmd_in_done_list(pinfo, |
709 | 0 | &queue->n_q_ctx, cmd_id); |
710 | 0 | if (!cmd_ctx) { |
711 | 0 | proto_tree_add_item(nvme_tree, hf_nvme_tcp_unknown_data, tvb, offset, |
712 | 0 | NVME_FABRIC_CQE_SIZE, ENC_NA); |
713 | 0 | return; |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | 0 | nvme_update_cmd_end_info(pinfo, &cmd_ctx->n_cmd_ctx); |
718 | |
|
719 | 0 | if (cmd_ctx->n_cmd_ctx.fabric) { |
720 | 0 | cmd_string = get_nvmeof_cmd_string(cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype); |
721 | 0 | proto_item_append_text(ti, |
722 | 0 | ", Cqe Fabrics Cmd: %s (0x%02x) Cmd ID: 0x%04x", cmd_string, |
723 | 0 | cmd_ctx->n_cmd_ctx.cmd_ctx.fabric_cmd.fctype , cmd_id); |
724 | |
|
725 | 0 | dissect_nvmeof_fabric_cqe(tvb, pinfo, nvme_tree, &cmd_ctx->n_cmd_ctx, offset); |
726 | 0 | } else { |
727 | 0 | tvbuff_t *nvme_tvb; |
728 | 0 | proto_item_set_len(ti, NVME_TCP_HEADER_SIZE); |
729 | 0 | cmd_string = nvme_get_opcode_string(cmd_ctx->n_cmd_ctx.opcode, |
730 | 0 | queue->n_q_ctx.qid); |
731 | |
|
732 | 0 | proto_item_append_text(ti, ", Cqe NVMe Cmd: %s (0x%02x) Cmd ID: 0x%04x", |
733 | 0 | cmd_string, cmd_ctx->n_cmd_ctx.opcode, cmd_id); |
734 | | /* get incapsuled nvme command */ |
735 | 0 | nvme_tvb = tvb_new_subset_remaining(tvb, NVME_TCP_HEADER_SIZE); |
736 | 0 | dissect_nvme_cqe(nvme_tvb, pinfo, root_tree, &queue->n_q_ctx, &cmd_ctx->n_cmd_ctx); |
737 | 0 | } |
738 | 0 | } |
739 | | |
740 | | static void |
741 | | dissect_nvme_tcp_r2t(tvbuff_t *tvb, |
742 | | packet_info *pinfo, |
743 | | int offset, |
744 | | proto_tree *tree) |
745 | 0 | { |
746 | 0 | proto_item *tf; |
747 | 0 | proto_item *r2t_tree; |
748 | |
|
749 | 0 | tf = proto_tree_add_item(tree, hf_nvme_tcp_r2t_pdu, tvb, offset, -1, |
750 | 0 | ENC_NA); |
751 | 0 | r2t_tree = proto_item_add_subtree(tf, ett_nvme_tcp); |
752 | |
|
753 | 0 | col_append_sep_fstr(pinfo->cinfo, COL_INFO, " | ", "Ready To Transfer"); |
754 | |
|
755 | 0 | proto_tree_add_item(r2t_tree, hf_nvme_fabrics_cmd_cid, tvb, offset, 2, |
756 | 0 | ENC_LITTLE_ENDIAN); |
757 | 0 | proto_tree_add_item(r2t_tree, hf_nvme_tcp_pdu_ttag, tvb, offset + 2, 2, |
758 | 0 | ENC_LITTLE_ENDIAN); |
759 | 0 | proto_tree_add_item(r2t_tree, hf_nvme_tcp_r2t_offset, tvb, offset + 4, 4, |
760 | 0 | ENC_LITTLE_ENDIAN); |
761 | 0 | proto_tree_add_item(r2t_tree, hf_nvme_tcp_r2t_length, tvb, offset + 8, 4, |
762 | 0 | ENC_LITTLE_ENDIAN); |
763 | 0 | proto_tree_add_item(r2t_tree, hf_nvme_tcp_r2t_resvd, tvb, offset + 12, 4, |
764 | 0 | ENC_NA); |
765 | 0 | } |
766 | | |
767 | | static int |
768 | | dissect_nvme_tcp_pdu(tvbuff_t *tvb, |
769 | | packet_info *pinfo, |
770 | | proto_tree *tree, |
771 | | void* data _U_) |
772 | 12 | { |
773 | 12 | conversation_t *conversation; |
774 | 12 | struct nvme_tcp_q_ctx *q_ctx; |
775 | 12 | proto_item *ti; |
776 | 12 | unsigned offset = 0; |
777 | 12 | int nvme_tcp_pdu_offset; |
778 | 12 | proto_tree *nvme_tcp_tree; |
779 | 12 | unsigned packet_type; |
780 | 12 | uint8_t hlen, pdo; |
781 | 12 | uint8_t pdu_flags; |
782 | 12 | uint32_t plen; |
783 | 12 | uint32_t incapsuled_data_size; |
784 | 12 | uint32_t pdu_data_offset = 0; |
785 | | |
786 | 12 | conversation = find_or_create_conversation(pinfo); |
787 | 12 | q_ctx = (struct nvme_tcp_q_ctx *) |
788 | 12 | conversation_get_proto_data(conversation, proto_nvme_tcp); |
789 | | |
790 | 12 | if (!q_ctx) { |
791 | 11 | q_ctx = wmem_new0(wmem_file_scope(), struct nvme_tcp_q_ctx); |
792 | 11 | q_ctx->n_q_ctx.pending_cmds = wmem_tree_new(wmem_file_scope()); |
793 | 11 | q_ctx->n_q_ctx.done_cmds = wmem_tree_new(wmem_file_scope()); |
794 | 11 | q_ctx->h2c_requests = wmem_tree_new(wmem_file_scope()); |
795 | 11 | q_ctx->n_q_ctx.data_responses = wmem_tree_new(wmem_file_scope()); |
796 | | /* Initially set to non-0 so that by default queues are io queues |
797 | | * this is required to be able to dissect correctly even |
798 | | * if we miss connect command*/ |
799 | 11 | q_ctx->n_q_ctx.qid = UINT16_MAX; |
800 | 11 | conversation_add_proto_data(conversation, proto_nvme_tcp, q_ctx); |
801 | 11 | } |
802 | | |
803 | 12 | ti = proto_tree_add_item(tree, proto_nvme_tcp, tvb, 0, -1, ENC_NA); |
804 | 12 | nvme_tcp_tree = proto_item_add_subtree(ti, ett_nvme_tcp); |
805 | | |
806 | 12 | if (q_ctx->n_q_ctx.qid != UINT16_MAX) |
807 | 0 | nvme_publish_qid(nvme_tcp_tree, hf_nvme_fabrics_cmd_qid, |
808 | 0 | q_ctx->n_q_ctx.qid); |
809 | | |
810 | 12 | packet_type = tvb_get_uint8(tvb, offset); |
811 | 12 | proto_tree_add_item(nvme_tcp_tree, hf_nvme_tcp_type, tvb, offset, 1, |
812 | 12 | ENC_NA); |
813 | | |
814 | 12 | pdu_flags = tvb_get_uint8(tvb, offset + 1); |
815 | 12 | proto_tree_add_bitmask_value(nvme_tcp_tree, tvb, offset + 1, hf_nvme_tcp_flags, |
816 | 12 | ett_nvme_tcp, nvme_tcp_pdu_flags, (uint64_t)pdu_flags); |
817 | | |
818 | 12 | hlen = tvb_get_uint8(tvb, offset + 2); |
819 | 12 | proto_tree_add_item(nvme_tcp_tree, hf_nvme_tcp_hlen, tvb, offset + 2, 1, |
820 | 12 | ENC_NA); |
821 | | |
822 | 12 | pdo = tvb_get_uint8(tvb, offset + 3); |
823 | 12 | proto_tree_add_uint(nvme_tcp_tree, hf_nvme_tcp_pdo, tvb, offset + 3, 1, |
824 | 12 | pdo); |
825 | 12 | proto_tree_add_item_ret_uint(nvme_tcp_tree, hf_nvme_tcp_plen, tvb, offset + 4, 4, |
826 | 12 | ENC_LITTLE_ENDIAN, &plen); |
827 | 12 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "NVMe/TCP"); |
828 | | |
829 | 12 | if (pdu_flags & NVME_TCP_F_HDGST) { |
830 | 1 | unsigned hdgst_flags = PROTO_CHECKSUM_NO_FLAGS; |
831 | 1 | uint32_t crc = 0; |
832 | | |
833 | 1 | if (nvme_tcp_check_hdgst) { |
834 | 0 | hdgst_flags = PROTO_CHECKSUM_VERIFY; |
835 | 0 | crc = ~crc32c_tvb_offset_calculate(tvb, 0, hlen, ~0); |
836 | 0 | } |
837 | 1 | proto_tree_add_checksum(nvme_tcp_tree, tvb, hlen, hf_nvme_tcp_hdgst, |
838 | 1 | hf_nvme_tcp_hdgst_status, NULL, pinfo, |
839 | 1 | crc, ENC_NA, hdgst_flags); |
840 | 1 | pdu_data_offset = NVME_TCP_DIGEST_LENGTH; |
841 | 1 | } |
842 | | |
843 | 12 | nvme_tcp_pdu_offset = offset + NVME_TCP_HEADER_SIZE; |
844 | 12 | incapsuled_data_size = plen - hlen - pdu_data_offset; |
845 | | |
846 | | /* check for overflow (invalid packet)*/ |
847 | 12 | if (incapsuled_data_size > tvb_reported_length(tvb)) { |
848 | 0 | proto_tree_add_item(nvme_tcp_tree, hf_nvme_tcp_unknown_data, |
849 | 0 | tvb, NVME_TCP_HEADER_SIZE, -1, ENC_NA); |
850 | 0 | return tvb_reported_length(tvb); |
851 | 0 | } |
852 | | |
853 | 12 | if (pdu_flags & NVME_TCP_F_DDGST) { |
854 | 1 | unsigned ddgst_flags = PROTO_CHECKSUM_NO_FLAGS; |
855 | 1 | uint32_t crc = 0; |
856 | | |
857 | | /* Check that data has enough space (invalid packet) */ |
858 | 1 | if (incapsuled_data_size <= NVME_TCP_DIGEST_LENGTH) { |
859 | 0 | proto_tree_add_item(nvme_tcp_tree, hf_nvme_tcp_unknown_data, |
860 | 0 | tvb, NVME_TCP_HEADER_SIZE, -1, ENC_NA); |
861 | 0 | return tvb_reported_length(tvb); |
862 | 0 | } |
863 | | |
864 | 1 | incapsuled_data_size -= NVME_TCP_DIGEST_LENGTH; |
865 | 1 | if (nvme_tcp_check_ddgst) { |
866 | 0 | ddgst_flags = PROTO_CHECKSUM_VERIFY; |
867 | 0 | crc = ~crc32c_tvb_offset_calculate(tvb, pdo, |
868 | 0 | incapsuled_data_size, ~0); |
869 | 0 | } |
870 | 1 | proto_tree_add_checksum(nvme_tcp_tree, tvb, |
871 | 1 | plen - NVME_TCP_DIGEST_LENGTH, hf_nvme_tcp_ddgst, |
872 | 1 | hf_nvme_tcp_ddgst_status, NULL, pinfo, |
873 | 1 | crc, ENC_NA, ddgst_flags); |
874 | 1 | } |
875 | | |
876 | 12 | switch (packet_type) { |
877 | 0 | case nvme_tcp_icreq: |
878 | 0 | dissect_nvme_tcp_icreq(tvb, pinfo, nvme_tcp_pdu_offset, nvme_tcp_tree); |
879 | 0 | proto_item_set_len(ti, hlen); |
880 | 0 | break; |
881 | 0 | case nvme_tcp_icresp: |
882 | 0 | dissect_nvme_tcp_icresp(tvb, pinfo, nvme_tcp_pdu_offset, nvme_tcp_tree); |
883 | 0 | proto_item_set_len(ti, hlen); |
884 | 0 | break; |
885 | 7 | case nvme_tcp_cmd: |
886 | 7 | dissect_nvme_tcp_command(tvb, pinfo, tree, nvme_tcp_tree, ti, q_ctx, |
887 | 7 | nvme_tcp_pdu_offset, incapsuled_data_size, pdu_data_offset); |
888 | 7 | break; |
889 | 0 | case nvme_tcp_rsp: |
890 | 0 | dissect_nvme_tcp_cqe(tvb, pinfo, tree, nvme_tcp_tree, ti, q_ctx, |
891 | 0 | nvme_tcp_pdu_offset); |
892 | 0 | proto_item_set_len(ti, NVME_TCP_HEADER_SIZE); |
893 | 0 | break; |
894 | 0 | case nvme_tcp_c2h_data: |
895 | 0 | dissect_nvme_tcp_c2h_data(tvb, pinfo, tree, nvme_tcp_tree, ti, q_ctx, |
896 | 0 | nvme_tcp_pdu_offset, pdu_data_offset); |
897 | 0 | proto_item_set_len(ti, NVME_TCP_DATA_PDU_SIZE); |
898 | 0 | break; |
899 | 2 | case nvme_tcp_h2c_data: |
900 | 2 | dissect_nvme_tcp_h2c_data(tvb, pinfo, tree, nvme_tcp_tree, ti, q_ctx, |
901 | 2 | nvme_tcp_pdu_offset, pdu_data_offset); |
902 | 2 | proto_item_set_len(ti, NVME_TCP_DATA_PDU_SIZE); |
903 | 2 | break; |
904 | 0 | case nvme_tcp_r2t: |
905 | 0 | dissect_nvme_tcp_r2t(tvb, pinfo, nvme_tcp_pdu_offset, nvme_tcp_tree); |
906 | 0 | break; |
907 | 0 | case nvme_tcp_h2c_term: |
908 | 0 | dissect_nvme_tcp_h2ctermreq(tvb, pinfo, tree, plen, offset); |
909 | 0 | break; |
910 | 0 | case nvme_tcp_c2h_term: |
911 | 0 | dissect_nvme_tcp_c2htermreq(tvb, pinfo, tree, plen, offset); |
912 | 0 | break; |
913 | 1 | default: |
914 | | // TODO: nvme_tcp_kdreq, nvme_tcp_kdresp |
915 | 1 | proto_tree_add_item(nvme_tcp_tree, hf_nvme_tcp_unknown_data, tvb, |
916 | 1 | offset, plen, ENC_NA); |
917 | 1 | break; |
918 | 12 | } |
919 | | |
920 | 0 | return tvb_reported_length(tvb); |
921 | 12 | } |
922 | | |
923 | | static int |
924 | | dissect_nvme_tcp(tvbuff_t *tvb, |
925 | | packet_info *pinfo, |
926 | | proto_tree *tree, |
927 | | void *data) |
928 | 12 | { |
929 | 12 | col_clear(pinfo->cinfo, COL_INFO); |
930 | 12 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "NVMe/TCP"); |
931 | 12 | tcp_dissect_pdus(tvb, pinfo, tree, true, NVME_TCP_HEADER_SIZE, |
932 | 12 | get_nvme_tcp_pdu_len, dissect_nvme_tcp_pdu, data); |
933 | | |
934 | 12 | return tvb_reported_length(tvb); |
935 | 12 | } |
936 | | |
937 | | static bool |
938 | | test_nvme(packet_info *pinfo _U_, tvbuff_t *tvb, int offset, void *data _U_) |
939 | 15 | { |
940 | | /* This is not the strongest heuristic, but the port is IANA assigned, |
941 | | * so this is not a normal heuristic dissector but simply to distinguish |
942 | | * between NVMe/TCP and NVMe/TLS/TCP, and also to detect PDU starts. |
943 | | */ |
944 | 15 | if (tvb_captured_length_remaining(tvb, offset) < NVME_TCP_HEADER_SIZE) { |
945 | 0 | return false; |
946 | 0 | } |
947 | | |
948 | 15 | if (tvb_get_uint8(tvb, offset) > NVMET_MAX_PDU_TYPE) { |
949 | 3 | return false; |
950 | 3 | } |
951 | | |
952 | 12 | offset += 2; |
953 | 12 | if (tvb_get_uint8(tvb, offset) < NVME_TCP_HEADER_SIZE) { |
954 | | // Header length - we could strengthen by using the PDU type. |
955 | 1 | return false; |
956 | 1 | } |
957 | | |
958 | | // Next byte is PDU Data Offset. Reserved in most types. (Does that |
959 | | // mean zero? That would strengthen the heuristic.) |
960 | | |
961 | 11 | offset += 2; |
962 | 11 | if (tvb_get_uint32(tvb, offset, ENC_LITTLE_ENDIAN) < NVME_TCP_HEADER_SIZE) { |
963 | | // PDU Length (inc. header) - could strengthen by using the PDU type. |
964 | 0 | return false; |
965 | 0 | } |
966 | | |
967 | 11 | return true; |
968 | 11 | } |
969 | | |
970 | | static int |
971 | | dissect_nvme_tcp_heur(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data) |
972 | 15 | { |
973 | | /* NVMe/TCP allows PDUs to span TCP segments (see Figure 5 of the NVMe/TCP |
974 | | * Transport Specification.) Also, some connections are over TLS. |
975 | | * Luckily, the PDU types for NVMe/TCP occupy the first byte, same as |
976 | | * the Content Type for TLS Records, and while these PDU types go to 11, |
977 | | * TLS Content Types start at 20 (and won't change, to enable multiplexing, |
978 | | * see RFC 9443.) |
979 | | * |
980 | | * So if this doesn't look like the start of a NVMe/TCP PDU, reject it. |
981 | | * It might be TLS, or it might be the middle of a PDU. |
982 | | */ |
983 | 15 | if (!test_nvme(pinfo, tvb, 0, data)) { |
984 | 4 | return 0; |
985 | | /* The TLS heuristic dissector should catch the TLS version. */ |
986 | 4 | } |
987 | | |
988 | | /* The start of a PDU. Set the other handle for this connection. |
989 | | * We can call tcp_dissect_pdus safely starting from here. |
990 | | */ |
991 | 11 | conversation_t *conversation = find_or_create_conversation(pinfo); |
992 | 11 | conversation_set_dissector_from_frame_number(conversation, pinfo->num, nvmet_tls_handle); |
993 | | |
994 | 11 | return dissect_nvme_tcp(tvb, pinfo, tree, data); |
995 | 15 | } |
996 | | |
997 | 16 | void proto_register_nvme_tcp(void) { |
998 | | |
999 | 16 | static hf_register_info hf[] = { |
1000 | 16 | { &hf_nvme_tcp_type, |
1001 | 16 | { "Pdu Type", "nvme-tcp.type", |
1002 | 16 | FT_UINT8, BASE_DEC, VALS(nvme_tcp_pdu_type_vals), |
1003 | 16 | 0x0, NULL, HFILL } }, |
1004 | 16 | { &hf_nvme_tcp_flags, |
1005 | 16 | { "Pdu Specific Flags", "nvme-tcp.flags", |
1006 | 16 | FT_UINT8, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
1007 | 16 | { &hf_pdu_flags_hdgst, |
1008 | 16 | { "PDU Header Digest", "nvme-tcp.flags.pdu.hdgst", |
1009 | 16 | FT_BOOLEAN, 8, TFS(&tfs_set_notset), |
1010 | 16 | NVME_TCP_F_HDGST, NULL, HFILL} }, |
1011 | 16 | { &hf_pdu_flags_ddgst, |
1012 | 16 | { "PDU Data Digest", "nvme-tcp.flags.pdu.ddgst", |
1013 | 16 | FT_BOOLEAN, 8, TFS(&tfs_set_notset), |
1014 | 16 | NVME_TCP_F_DDGST, NULL, HFILL} }, |
1015 | 16 | { &hf_pdu_flags_data_last, |
1016 | 16 | { "PDU Data Last", "nvme-tcp.flags.pdu.data_last", |
1017 | 16 | FT_BOOLEAN, 8, TFS(&tfs_set_notset), |
1018 | 16 | NVME_TCP_F_DATA_LAST, NULL, HFILL} }, |
1019 | 16 | { &hf_pdu_flags_data_success, |
1020 | 16 | { "PDU Data Success", "nvme-tcp.flags.pdu.data_success", |
1021 | 16 | FT_BOOLEAN, 8, TFS(&tfs_set_notset), |
1022 | 16 | NVME_TCP_F_DATA_SUCCESS, NULL, HFILL} }, |
1023 | 16 | { &hf_nvme_tcp_hdgst, |
1024 | 16 | { "PDU Header Digest", "nvme-tcp.hdgst", |
1025 | 16 | FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
1026 | 16 | { &hf_nvme_tcp_ddgst, |
1027 | 16 | { "PDU Data Digest", "nvme-tcp.ddgst", |
1028 | 16 | FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
1029 | 16 | { &hf_nvme_tcp_hdgst_status, |
1030 | 16 | { "Header Digest Status", "nvme-tcp.hdgst.status", |
1031 | 16 | FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), |
1032 | 16 | 0x0, NULL, HFILL }}, |
1033 | 16 | { &hf_nvme_tcp_ddgst_status, |
1034 | 16 | { "Data Digest Status", "nvme-tcp.ddgst.status", |
1035 | 16 | FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), |
1036 | 16 | 0x0, NULL, HFILL }}, |
1037 | 16 | { &hf_nvme_tcp_hlen, |
1038 | 16 | { "Pdu Header Length", "nvme-tcp.hlen", |
1039 | 16 | FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1040 | 16 | { &hf_nvme_tcp_pdo, |
1041 | 16 | { "Pdu Data Offset", "nvme-tcp.pdo", |
1042 | 16 | FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1043 | 16 | { &hf_nvme_tcp_plen, |
1044 | 16 | { "Packet Length", "nvme-tcp.plen", |
1045 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1046 | 16 | { &hf_nvme_tcp_icreq, |
1047 | 16 | { "ICReq", "nvme-tcp.icreq", |
1048 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1049 | 16 | { &hf_nvme_tcp_icreq_pfv, |
1050 | 16 | { "Pdu Version Format", "nvme-tcp.icreq.pfv", |
1051 | 16 | FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1052 | 16 | { &hf_nvme_tcp_icreq_maxr2t, |
1053 | 16 | { "Maximum r2ts per request", "nvme-tcp.icreq.maxr2t", |
1054 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1055 | 16 | { &hf_nvme_tcp_icreq_hpda, |
1056 | 16 | { "Host Pdu data alignment", "nvme-tcp.icreq.hpda", |
1057 | 16 | FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1058 | 16 | { &hf_nvme_tcp_icreq_digest, |
1059 | 16 | { "Digest Types Enabled", "nvme-tcp.icreq.digest", |
1060 | 16 | FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1061 | 16 | { &hf_nvme_tcp_icresp, |
1062 | 16 | { "ICResp", "nvme-tcp.icresp", |
1063 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1064 | 16 | { &hf_nvme_tcp_icresp_pfv, |
1065 | 16 | { "Pdu Version Format", "nvme-tcp.icresp.pfv", |
1066 | 16 | FT_UINT16, BASE_DEC, NULL, 0x0, |
1067 | 16 | NULL, HFILL } }, |
1068 | 16 | { &hf_nvme_tcp_icresp_cpda, |
1069 | 16 | { "Controller Pdu data alignment", "nvme-tcp.icresp.cpda", |
1070 | 16 | FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1071 | 16 | { &hf_nvme_tcp_icresp_digest, |
1072 | 16 | { "Digest types enabled", "nvme-tcp.icresp.digest", |
1073 | 16 | FT_UINT8, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1074 | 16 | { &hf_nvme_tcp_icresp_maxdata, |
1075 | 16 | { "Maximum data capsules per r2t supported", "nvme-tcp.icresp.maxdata", |
1076 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, NULL, HFILL } }, |
1077 | | /* NVMe tcp c2h/h2c termreq fields */ |
1078 | 16 | { &hf_nvme_tcp_c2htermreq, |
1079 | 16 | { "C2HTermReq", "nvme-tcp.c2htermreq", |
1080 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1081 | 16 | { &hf_nvme_tcp_c2htermreq_fes, |
1082 | 16 | { "Fatal error status", "nvme-tcp.c2htermreq.fes", |
1083 | 16 | FT_UINT16, BASE_HEX, VALS(nvme_tcp_termreq_fes), |
1084 | 16 | 0x0, NULL, HFILL } }, |
1085 | 16 | { &hf_nvme_tcp_c2htermreq_phfo, |
1086 | 16 | { "PDU header field offset", "nvme-tcp.c2htermreq.phfo", |
1087 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1088 | 16 | { &hf_nvme_tcp_c2htermreq_phd, |
1089 | 16 | { "PDU header digest", "nvme-tcp.c2htermreq.phd", |
1090 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1091 | 16 | { &hf_nvme_tcp_c2htermreq_upfo, |
1092 | 16 | { "Unsupported parameter field offset", "nvme-tcp.c2htermreq.upfo", |
1093 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1094 | 16 | { &hf_nvme_tcp_c2htermreq_reserved, |
1095 | 16 | { "Reserved", "nvme-tcp.c2htermreq.reserved", |
1096 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1097 | 16 | { &hf_nvme_tcp_c2htermreq_data, |
1098 | 16 | { "Terminated PDU header", "nvme-tcp.c2htermreq.data", |
1099 | 16 | FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, |
1100 | 16 | { &hf_nvme_tcp_h2ctermreq, |
1101 | 16 | { "H2CTermReq", "nvme-tcp.h2ctermreq", |
1102 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1103 | 16 | { &hf_nvme_tcp_h2ctermreq_fes, |
1104 | 16 | { "Fatal error status", "nvme-tcp.h2ctermreq.fes", |
1105 | 16 | FT_UINT16, BASE_HEX, VALS(nvme_tcp_termreq_fes), |
1106 | 16 | 0x0, NULL, HFILL } }, |
1107 | 16 | { &hf_nvme_tcp_h2ctermreq_phfo, |
1108 | 16 | { "PDU header field offset", "nvme-tcp.h2ctermreq.phfo", |
1109 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1110 | 16 | { &hf_nvme_tcp_h2ctermreq_phd, |
1111 | 16 | { "PDU header digest", "nvme-tcp.h2ctermreq.phd", |
1112 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1113 | 16 | { &hf_nvme_tcp_h2ctermreq_upfo, |
1114 | 16 | { "Unsupported parameter field offset", "nvme-tcp.h2ctermreq.upfo", |
1115 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1116 | 16 | { &hf_nvme_tcp_h2ctermreq_reserved, |
1117 | 16 | { "Reserved", "nvme-tcp.h2ctermreq.reserved", |
1118 | 16 | FT_UINT32, BASE_HEX, NULL, 0, NULL, HFILL } }, |
1119 | 16 | { &hf_nvme_tcp_h2ctermreq_data, |
1120 | 16 | { "Terminated PDU header", "nvme-tcp.h2ctermreq.data", |
1121 | 16 | FT_NONE, BASE_NONE, NULL, 0, NULL, HFILL } }, |
1122 | 16 | { &hf_nvme_fabrics_cmd_cid, |
1123 | 16 | { "Command ID", "nvme-tcp.cmd.cid", |
1124 | 16 | FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL } }, |
1125 | 16 | { &hf_nvme_tcp_unknown_data, |
1126 | 16 | { "Unknown Data", "nvme-tcp.unknown_data", |
1127 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1128 | | /* NVMe command data */ |
1129 | 16 | { &hf_nvme_fabrics_cmd_data, |
1130 | 16 | { "Data", "nvme-tcp.cmd.data", |
1131 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1132 | 16 | { &hf_nvme_tcp_cmd_pkt, |
1133 | 16 | { "Cmd in", "nvme-tcp.cmd_pkt", |
1134 | 16 | FT_FRAMENUM, BASE_NONE, NULL, 0, |
1135 | 16 | "The Cmd for this transaction is in this frame", HFILL } }, |
1136 | 16 | { &hf_nvme_fabrics_cmd_qid, |
1137 | 16 | { "Cmd Qid", "nvme-tcp.cmd.qid", |
1138 | 16 | FT_UINT16, BASE_HEX, NULL, 0x0, |
1139 | 16 | "Qid on which command is issued", HFILL } }, |
1140 | | /* NVMe TCP data response */ |
1141 | 16 | { &hf_nvme_tcp_data_pdu, |
1142 | 16 | { "NVMe/TCP Data PDU", "nvme-tcp.data", |
1143 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1144 | 16 | { &hf_nvme_tcp_pdu_ttag, |
1145 | 16 | { "Transfer Tag", "nvme-tcp.ttag", |
1146 | 16 | FT_UINT16, BASE_HEX, NULL, 0x0, |
1147 | 16 | "Transfer tag (controller generated)", HFILL } }, |
1148 | 16 | { &hf_nvme_tcp_data_pdu_data_offset, |
1149 | 16 | { "Data Offset", "nvme-tcp.data.offset", |
1150 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
1151 | 16 | "Offset from the start of the command data", HFILL } }, |
1152 | 16 | { &hf_nvme_tcp_data_pdu_data_length, |
1153 | 16 | { "Data Length", "nvme-tcp.data.length", |
1154 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
1155 | 16 | "Length of the data stream", HFILL } }, |
1156 | 16 | { &hf_nvme_tcp_data_pdu_data_resvd, |
1157 | 16 | { "Reserved", "nvme-tcp.data.rsvd", |
1158 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1159 | | /* NVMEe TCP R2T pdu */ |
1160 | 16 | { &hf_nvme_tcp_r2t_pdu, |
1161 | 16 | { "R2T", "nvme-tcp.r2t", |
1162 | 16 | FT_NONE, BASE_NONE, NULL, 0x0, NULL, HFILL } }, |
1163 | 16 | { &hf_nvme_tcp_r2t_offset, |
1164 | 16 | { "R2T Offset", "nvme-tcp.r2t.offset", |
1165 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
1166 | 16 | "Offset from the start of the command data", HFILL } }, |
1167 | 16 | { &hf_nvme_tcp_r2t_length, |
1168 | 16 | { "R2T Length", "nvme-tcp.r2t.length", |
1169 | 16 | FT_UINT32, BASE_DEC, NULL, 0x0, |
1170 | 16 | "Length of the data stream", HFILL } }, |
1171 | 16 | { &hf_nvme_tcp_r2t_resvd, |
1172 | 16 | { "Reserved", "nvme-tcp.r2t.rsvd", |
1173 | 16 | FT_BYTES, BASE_NONE, NULL, 0x0, NULL, HFILL } } |
1174 | 16 | }; |
1175 | | |
1176 | 16 | static int *ett[] = { |
1177 | 16 | &ett_nvme_tcp |
1178 | 16 | }; |
1179 | | |
1180 | 16 | static ei_register_info ei[] = { |
1181 | 16 | { &ei_nvme_tcp_termreq_short, |
1182 | 16 | { "nvme-tcp.termreq_too_short", PI_MALFORMED, PI_ERROR, |
1183 | 16 | "Termination Request PDU is shorter than its header", EXPFILL } }, |
1184 | 16 | }; |
1185 | | |
1186 | 16 | expert_module_t *expert_nvme_tcp; |
1187 | | |
1188 | 16 | proto_nvme_tcp = proto_register_protocol("NVM Express Fabrics TCP", |
1189 | 16 | "NVMe/TCP", "nvme-tcp"); |
1190 | | |
1191 | 16 | proto_register_field_array(proto_nvme_tcp, hf, array_length(hf)); |
1192 | 16 | proto_register_subtree_array(ett, array_length(ett)); |
1193 | | |
1194 | 16 | expert_nvme_tcp = expert_register_protocol(proto_nvme_tcp); |
1195 | 16 | expert_register_field_array(expert_nvme_tcp, ei, array_length(ei)); |
1196 | | |
1197 | | /* These names actually work for their purpose. Note if we're already |
1198 | | * over TLS we don't need to do heuristics (it can't be more TLS instead |
1199 | | * instead, and since we managed to decrypt the TLS we shouldn't have |
1200 | | * missing frames and thus aren't in the middle of a PDU.) |
1201 | | */ |
1202 | 16 | nvmet_tcp_handle = register_dissector("nvme-tcp", dissect_nvme_tcp_heur, |
1203 | 16 | proto_nvme_tcp); |
1204 | 16 | nvmet_tls_handle = register_dissector_with_description("nvme-tls", |
1205 | 16 | "NVMe-over-TCP with TLS", dissect_nvme_tcp, proto_nvme_tcp); |
1206 | 16 | } |
1207 | | |
1208 | 16 | void proto_reg_handoff_nvme_tcp(void) { |
1209 | 16 | module_t *nvme_tcp_module; |
1210 | 16 | nvme_tcp_module = prefs_register_protocol(proto_nvme_tcp, NULL); |
1211 | 16 | range_convert_str(wmem_epan_scope(), &gPORT_RANGE, NVME_TCP_PORT_RANGE, |
1212 | 16 | MAX_TCP_PORT); |
1213 | 16 | prefs_register_range_preference(nvme_tcp_module, |
1214 | 16 | "subsystem_ports", |
1215 | 16 | "Subsystem Ports Range", |
1216 | 16 | "Range of NVMe Subsystem ports" |
1217 | 16 | "(default " NVME_TCP_PORT_RANGE ")", |
1218 | 16 | &gPORT_RANGE, |
1219 | 16 | MAX_TCP_PORT); |
1220 | 16 | prefs_register_bool_preference(nvme_tcp_module, "check_hdgst", |
1221 | 16 | "Validate PDU header digest", |
1222 | 16 | "Whether to validate the PDU header digest or not.", |
1223 | 16 | &nvme_tcp_check_hdgst); |
1224 | 16 | prefs_register_bool_preference(nvme_tcp_module, "check_ddgst", |
1225 | 16 | "Validate PDU data digest", |
1226 | 16 | "Whether to validate the PDU data digest or not.", |
1227 | 16 | &nvme_tcp_check_ddgst); |
1228 | 16 | ssl_dissector_add(0, nvmet_tls_handle); |
1229 | 16 | dissector_add_uint_range("tcp.port", gPORT_RANGE, nvmet_tcp_handle); |
1230 | 16 | dissector_add_uint_range("tls.port", gPORT_RANGE, nvmet_tls_handle); |
1231 | 16 | } |
1232 | | |
1233 | | /* |
1234 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
1235 | | * |
1236 | | * Local variables: |
1237 | | * c-basic-offset: 4 |
1238 | | * tab-width: 8 |
1239 | | * indent-tabs-mode: nil |
1240 | | * End: |
1241 | | * |
1242 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
1243 | | * :indentSize=4:tabSize=8:noTabs=true: |
1244 | | */ |