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