1/*
2 * This module implements decoding of the ATA over Ethernet (AoE) protocol
3 * according to the following specification:
4 * http://support.coraid.com/documents/AoEr11.txt
5 *
6 * Copyright (c) 2014 The TCPDUMP project
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#define NETDISSECT_REWORKED
33#ifdef HAVE_CONFIG_H
34#include "config.h"
35#endif
36
37#include <tcpdump-stdinc.h>
38
39#include "interface.h"
40#include "extract.h"
41#include "addrtoname.h"
42#include "ether.h"
43
44static const char tstr[] = " [|aoe]";
45static const char cstr[] = " (corrupt)";
46
47#define AOE_V1 1
48#define ATA_SECTOR_SIZE 512
49
50#define AOEV1_CMD_ISSUE_ATA_COMMAND        0
51#define AOEV1_CMD_QUERY_CONFIG_INFORMATION 1
52#define AOEV1_CMD_MAC_MASK_LIST            2
53#define AOEV1_CMD_RESERVE_RELEASE          3
54
55static const struct tok cmdcode_str[] = {
56	{ AOEV1_CMD_ISSUE_ATA_COMMAND,        "Issue ATA Command"        },
57	{ AOEV1_CMD_QUERY_CONFIG_INFORMATION, "Query Config Information" },
58	{ AOEV1_CMD_MAC_MASK_LIST,            "MAC Mask List"            },
59	{ AOEV1_CMD_RESERVE_RELEASE,          "Reserve/Release"          },
60	{ 0, NULL }
61};
62
63#define AOEV1_COMMON_HDR_LEN    10U /* up to but w/o Arg                */
64#define AOEV1_ISSUE_ARG_LEN     12U /* up to but w/o Data               */
65#define AOEV1_QUERY_ARG_LEN      8U /* up to but w/o Config String      */
66#define AOEV1_MAC_ARG_LEN        4U /* up to but w/o Directive 0        */
67#define AOEV1_RESERVE_ARG_LEN    2U /* up to but w/o Ethernet address 0 */
68#define AOEV1_MAX_CONFSTR_LEN 1024U
69
70#define AOEV1_FLAG_R 0x08
71#define AOEV1_FLAG_E 0x04
72
73static const struct tok aoev1_flag_str[] = {
74	{ AOEV1_FLAG_R, "Response" },
75	{ AOEV1_FLAG_E, "Error"    },
76	{ 0x02,         "MBZ-0x02" },
77	{ 0x01,         "MBZ-0x01" },
78	{ 0, NULL }
79};
80
81static const struct tok aoev1_errcode_str[] = {
82	{ 1, "Unrecognized command code" },
83	{ 2, "Bad argument parameter"    },
84	{ 3, "Device unavailable"        },
85	{ 4, "Config string present"     },
86	{ 5, "Unsupported version"       },
87	{ 6, "Target is reserved"        },
88	{ 0, NULL }
89};
90
91#define AOEV1_AFLAG_E 0x40
92#define AOEV1_AFLAG_D 0x10
93#define AOEV1_AFLAG_A 0x02
94#define AOEV1_AFLAG_W 0x01
95
96static const struct tok aoev1_aflag_str[] = {
97	{ 0x08,          "MBZ-0x08" },
98	{ AOEV1_AFLAG_E, "Ext48"    },
99	{ 0x06,          "MBZ-0x06" },
100	{ AOEV1_AFLAG_D, "Device"   },
101	{ 0x04,          "MBZ-0x04" },
102	{ 0x03,          "MBZ-0x03" },
103	{ AOEV1_AFLAG_A, "Async"    },
104	{ AOEV1_AFLAG_W, "Write"    },
105	{ 0, NULL }
106};
107
108static const struct tok aoev1_ccmd_str[] = {
109	{ 0, "read config string"        },
110	{ 1, "test config string"        },
111	{ 2, "test config string prefix" },
112	{ 3, "set config string"         },
113	{ 4, "force set config string"   },
114	{ 0, NULL }
115};
116
117static const struct tok aoev1_mcmd_str[] = {
118	{ 0, "Read Mac Mask List" },
119	{ 1, "Edit Mac Mask List" },
120	{ 0, NULL }
121};
122
123static const struct tok aoev1_merror_str[] = {
124	{ 1, "Unspecified Error"  },
125	{ 2, "Bad DCmd directive" },
126	{ 3, "Mask list full"     },
127	{ 0, NULL }
128};
129
130static const struct tok aoev1_dcmd_str[] = {
131	{ 0, "No Directive"                      },
132	{ 1, "Add mac address to mask list"      },
133	{ 2, "Delete mac address from mask list" },
134	{ 0, NULL }
135};
136
137static const struct tok aoev1_rcmd_str[] = {
138	{ 0, "Read reserve list"      },
139	{ 1, "Set reserve list"       },
140	{ 2, "Force set reserve list" },
141	{ 0, NULL }
142};
143
144static void
145aoev1_issue_print(netdissect_options *ndo,
146                  const u_char *cp, const u_int len)
147{
148	const u_char *ep = cp + len;
149
150	if (len < AOEV1_ISSUE_ARG_LEN)
151		goto corrupt;
152	/* AFlags */
153	ND_TCHECK2(*cp, 1);
154	ND_PRINT((ndo, "\n\tAFlags: [%s]", bittok2str(aoev1_aflag_str, "none", *cp)));
155	cp += 1;
156	/* Err/Feature */
157	ND_TCHECK2(*cp, 1);
158	ND_PRINT((ndo, ", Err/Feature: %u", *cp));
159	cp += 1;
160	/* Sector Count (not correlated with the length) */
161	ND_TCHECK2(*cp, 1);
162	ND_PRINT((ndo, ", Sector Count: %u", *cp));
163	cp += 1;
164	/* Cmd/Status */
165	ND_TCHECK2(*cp, 1);
166	ND_PRINT((ndo, ", Cmd/Status: %u", *cp));
167	cp += 1;
168	/* lba0 */
169	ND_TCHECK2(*cp, 1);
170	ND_PRINT((ndo, "\n\tlba0: %u", *cp));
171	cp += 1;
172	/* lba1 */
173	ND_TCHECK2(*cp, 1);
174	ND_PRINT((ndo, ", lba1: %u", *cp));
175	cp += 1;
176	/* lba2 */
177	ND_TCHECK2(*cp, 1);
178	ND_PRINT((ndo, ", lba2: %u", *cp));
179	cp += 1;
180	/* lba3 */
181	ND_TCHECK2(*cp, 1);
182	ND_PRINT((ndo, ", lba3: %u", *cp));
183	cp += 1;
184	/* lba4 */
185	ND_TCHECK2(*cp, 1);
186	ND_PRINT((ndo, ", lba4: %u", *cp));
187	cp += 1;
188	/* lba5 */
189	ND_TCHECK2(*cp, 1);
190	ND_PRINT((ndo, ", lba5: %u", *cp));
191	cp += 1;
192	/* Reserved */
193	ND_TCHECK2(*cp, 2);
194	cp += 2;
195	/* Data */
196	if (len > AOEV1_ISSUE_ARG_LEN)
197		ND_PRINT((ndo, "\n\tData: %u bytes", len - AOEV1_ISSUE_ARG_LEN));
198	return;
199
200corrupt:
201	ND_PRINT((ndo, "%s", cstr));
202	ND_TCHECK2(*cp, ep - cp);
203	return;
204trunc:
205	ND_PRINT((ndo, "%s", tstr));
206}
207
208static void
209aoev1_query_print(netdissect_options *ndo,
210                  const u_char *cp, const u_int len)
211{
212	const u_char *ep = cp + len;
213	uint16_t cslen;
214
215	if (len < AOEV1_QUERY_ARG_LEN)
216		goto corrupt;
217	/* Buffer Count */
218	ND_TCHECK2(*cp, 2);
219	ND_PRINT((ndo, "\n\tBuffer Count: %u", EXTRACT_16BITS(cp)));
220	cp += 2;
221	/* Firmware Version */
222	ND_TCHECK2(*cp, 2);
223	ND_PRINT((ndo, ", Firmware Version: %u", EXTRACT_16BITS(cp)));
224	cp += 2;
225	/* Sector Count */
226	ND_TCHECK2(*cp, 1);
227	ND_PRINT((ndo, ", Sector Count: %u", *cp));
228	cp += 1;
229	/* AoE/CCmd */
230	ND_TCHECK2(*cp, 1);
231	ND_PRINT((ndo, ", AoE: %u, CCmd: %s", (*cp & 0xF0) >> 4,
232	          tok2str(aoev1_ccmd_str, "Unknown (0x02x)", *cp & 0x0F)));
233	cp += 1;
234	/* Config String Length */
235	ND_TCHECK2(*cp, 2);
236	cslen = EXTRACT_16BITS(cp);
237	cp += 2;
238	if (cslen > AOEV1_MAX_CONFSTR_LEN || AOEV1_QUERY_ARG_LEN + cslen > len)
239		goto corrupt;
240	/* Config String */
241	ND_TCHECK2(*cp, cslen);
242	if (cslen) {
243		ND_PRINT((ndo, "\n\tConfig String (length %u): ", cslen));
244		if (fn_printn(ndo, cp, cslen, ndo->ndo_snapend))
245			goto trunc;
246	}
247	return;
248
249corrupt:
250	ND_PRINT((ndo, "%s", cstr));
251	ND_TCHECK2(*cp, ep - cp);
252	return;
253trunc:
254	ND_PRINT((ndo, "%s", tstr));
255}
256
257static void
258aoev1_mac_print(netdissect_options *ndo,
259                const u_char *cp, const u_int len)
260{
261	const u_char *ep = cp + len;
262	uint8_t dircount, i;
263
264	if (len < AOEV1_MAC_ARG_LEN)
265		goto corrupt;
266	/* Reserved */
267	ND_TCHECK2(*cp, 1);
268	cp += 1;
269	/* MCmd */
270	ND_TCHECK2(*cp, 1);
271	ND_PRINT((ndo, "\n\tMCmd: %s", tok2str(aoev1_mcmd_str, "Unknown (0x%02x)", *cp)));
272	cp += 1;
273	/* MError */
274	ND_TCHECK2(*cp, 1);
275	ND_PRINT((ndo, ", MError: %s", tok2str(aoev1_merror_str, "Unknown (0x%02x)", *cp)));
276	cp += 1;
277	/* Dir Count */
278	ND_TCHECK2(*cp, 1);
279	dircount = *cp;
280	cp += 1;
281	ND_PRINT((ndo, ", Dir Count: %u", dircount));
282	if (AOEV1_MAC_ARG_LEN + dircount * 8 > len)
283		goto corrupt;
284	/* directives */
285	for (i = 0; i < dircount; i++) {
286		/* Reserved */
287		ND_TCHECK2(*cp, 1);
288		cp += 1;
289		/* DCmd */
290		ND_TCHECK2(*cp, 1);
291		ND_PRINT((ndo, "\n\t DCmd: %s", tok2str(aoev1_dcmd_str, "Unknown (0x%02x)", *cp)));
292		cp += 1;
293		/* Ethernet Address */
294		ND_TCHECK2(*cp, ETHER_ADDR_LEN);
295		ND_PRINT((ndo, ", Ethernet Address: %s", etheraddr_string(ndo, cp)));
296		cp += ETHER_ADDR_LEN;
297	}
298	return;
299
300corrupt:
301	ND_PRINT((ndo, "%s", cstr));
302	ND_TCHECK2(*cp, ep - cp);
303	return;
304trunc:
305	ND_PRINT((ndo, "%s", tstr));
306}
307
308static void
309aoev1_reserve_print(netdissect_options *ndo,
310                    const u_char *cp, const u_int len)
311{
312	const u_char *ep = cp + len;
313	uint8_t nmacs, i;
314
315	if (len < AOEV1_RESERVE_ARG_LEN || (len - AOEV1_RESERVE_ARG_LEN) % ETHER_ADDR_LEN)
316		goto corrupt;
317	/* RCmd */
318	ND_TCHECK2(*cp, 1);
319	ND_PRINT((ndo, "\n\tRCmd: %s", tok2str(aoev1_rcmd_str, "Unknown (0x%02x)", *cp)));
320	cp += 1;
321	/* NMacs (correlated with the length) */
322	ND_TCHECK2(*cp, 1);
323	nmacs = *cp;
324	cp += 1;
325	ND_PRINT((ndo, ", NMacs: %u", nmacs));
326	if (AOEV1_RESERVE_ARG_LEN + nmacs * ETHER_ADDR_LEN != len)
327		goto corrupt;
328	/* addresses */
329	for (i = 0; i < nmacs; i++) {
330		ND_PRINT((ndo, "\n\tEthernet Address %u: %s", i, etheraddr_string(ndo, cp)));
331		cp += ETHER_ADDR_LEN;
332	}
333	return;
334
335corrupt:
336	ND_PRINT((ndo, "%s", cstr));
337	ND_TCHECK2(*cp, ep - cp);
338	return;
339trunc:
340	ND_PRINT((ndo, "%s", tstr));
341}
342
343/* cp points to the Ver/Flags octet */
344static void
345aoev1_print(netdissect_options *ndo,
346            const u_char *cp, const u_int len)
347{
348	const u_char *ep = cp + len;
349	uint8_t flags, command;
350	void (*cmd_decoder)(netdissect_options *, const u_char *, const u_int);
351
352	if (len < AOEV1_COMMON_HDR_LEN)
353		goto corrupt;
354	/* Flags */
355	flags = *cp & 0x0F;
356	ND_PRINT((ndo, ", Flags: [%s]", bittok2str(aoev1_flag_str, "none", flags)));
357	cp += 1;
358	if (! ndo->ndo_vflag)
359		return;
360	/* Error */
361	ND_TCHECK2(*cp, 1);
362	if (flags & AOEV1_FLAG_E)
363		ND_PRINT((ndo, "\n\tError: %s", tok2str(aoev1_errcode_str, "Invalid (%u)", *cp)));
364	cp += 1;
365	/* Major */
366	ND_TCHECK2(*cp, 2);
367	ND_PRINT((ndo, "\n\tMajor: 0x%04x", EXTRACT_16BITS(cp)));
368	cp += 2;
369	/* Minor */
370	ND_TCHECK2(*cp, 1);
371	ND_PRINT((ndo, ", Minor: 0x%02x", *cp));
372	cp += 1;
373	/* Command */
374	ND_TCHECK2(*cp, 1);
375	command = *cp;
376	cp += 1;
377	ND_PRINT((ndo, ", Command: %s", tok2str(cmdcode_str, "Unknown (0x%02x)", command)));
378	/* Tag */
379	ND_TCHECK2(*cp, 4);
380	ND_PRINT((ndo, ", Tag: 0x%08x", EXTRACT_32BITS(cp)));
381	cp += 4;
382	/* Arg */
383	cmd_decoder =
384		command == AOEV1_CMD_ISSUE_ATA_COMMAND        ? aoev1_issue_print :
385		command == AOEV1_CMD_QUERY_CONFIG_INFORMATION ? aoev1_query_print :
386		command == AOEV1_CMD_MAC_MASK_LIST            ? aoev1_mac_print :
387		command == AOEV1_CMD_RESERVE_RELEASE          ? aoev1_reserve_print :
388		NULL;
389	if (cmd_decoder != NULL)
390		cmd_decoder(ndo, cp, len - AOEV1_COMMON_HDR_LEN);
391	return;
392
393corrupt:
394	ND_PRINT((ndo, "%s", cstr));
395	ND_TCHECK2(*cp, ep - cp);
396	return;
397trunc:
398	ND_PRINT((ndo, "%s", tstr));
399}
400
401void
402aoe_print(netdissect_options *ndo,
403          const u_char *cp, const u_int len)
404{
405	const u_char *ep = cp + len;
406	uint8_t ver;
407
408	ND_PRINT((ndo, "AoE length %u", len));
409
410	if (len < 1)
411		goto corrupt;
412	/* Ver/Flags */
413	ND_TCHECK2(*cp, 1);
414	ver = (*cp & 0xF0) >> 4;
415	/* Don't advance cp yet: low order 4 bits are version-specific. */
416	ND_PRINT((ndo, ", Ver %u", ver));
417
418	switch (ver) {
419		case AOE_V1:
420			aoev1_print(ndo, cp, len);
421			break;
422	}
423	return;
424
425corrupt:
426	ND_PRINT((ndo, "%s", cstr));
427	ND_TCHECK2(*cp, ep - cp);
428	return;
429trunc:
430	ND_PRINT((ndo, "%s", tstr));
431}
432
433