// Copyright (c) 2018 Arista Networks, Inc. All rights reserved. /* \summary: EtherType protocol for Arista Networks printer */ #ifdef HAVE_CONFIG_H #include #endif #include "netdissect-stdinc.h" #include "netdissect.h" #include "extract.h" /* From Bill Fenner: The Arista timestamp header consists of the following fields: 1. The Arista ethertype (0xd28b) 2. A 2-byte subtype field; 0x01 indicates the timestamp header 3. A 2-byte version field, described below. 4. A 48-bit or 64-bit timestamp field, depending on the contents of the version field This header is then followed by the original ethertype and the remainder of the original packet. 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | dst mac | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | src mac | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ethertype 0xd28b | subtype 0x1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | version | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | timestamp... | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ The two-byte version value is split into 3 fields: 1. The timescale in use. Currently assigned values include: 0 = TAI 1 = UTC 2. The timestamp format and length. Currently assigned values include: 1 = 64-bit timestamp 2 = 48-bit timestamp 3. The hardware info 0 = R/R2 series 1 = R3 series 0 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | timescale | format|hw info| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ See also: https://www.arista.com/assets/data/pdf/Whitepapers/Overview_Arista_Timestamps.pdf */ #define ARISTA_SUBTYPE_TIMESTAMP 0x0001 static const struct tok subtype_str[] = { { ARISTA_SUBTYPE_TIMESTAMP, "Timestamp" }, { 0, NULL } }; static const struct tok ts_timescale_str[] = { { 0, "TAI" }, { 1, "UTC" }, { 0, NULL } }; #define FORMAT_64BIT 0x1 #define FORMAT_48BIT 0x2 static const struct tok ts_format_str[] = { { FORMAT_64BIT, "64-bit" }, { FORMAT_48BIT, "48-bit" }, { 0, NULL } }; static const struct tok hw_info_str[] = { { 0, "R/R2" }, { 1, "R3" }, { 0, NULL } }; static inline void arista_print_date_hms_time(netdissect_options *ndo, uint32_t seconds, uint32_t nanoseconds) { time_t ts; char buf[sizeof("-yyyyyyyyyy-mm-dd hh:mm:ss")]; ts = seconds + (nanoseconds / 1000000000); nanoseconds %= 1000000000; ND_PRINT("%s.%09u", nd_format_time(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", gmtime(&ts)), nanoseconds); } int arista_ethertype_print(netdissect_options *ndo, const u_char *bp, u_int len _U_) { uint16_t subTypeId; u_short bytesConsumed = 0; ndo->ndo_protocol = "arista"; subTypeId = GET_BE_U_2(bp); bp += 2; bytesConsumed += 2; ND_PRINT("SubType %s (0x%04x), ", tok2str(subtype_str, "Unknown", subTypeId), subTypeId); // TapAgg Header Timestamping if (subTypeId == ARISTA_SUBTYPE_TIMESTAMP) { uint64_t seconds; uint32_t nanoseconds; uint8_t ts_timescale = GET_U_1(bp); bp += 1; bytesConsumed += 1; ND_PRINT("Timescale %s (%u), ", tok2str(ts_timescale_str, "Unknown", ts_timescale), ts_timescale); uint8_t ts_format = GET_U_1(bp) >> 4; uint8_t hw_info = GET_U_1(bp) & 0x0f; bp += 1; bytesConsumed += 1; // Timestamp has 32-bit lsb in nanosec and remaining msb in sec ND_PRINT("Format %s (%u), HwInfo %s (%u), Timestamp ", tok2str(ts_format_str, "Unknown", ts_format), ts_format, tok2str(hw_info_str, "Unknown", hw_info), hw_info); switch (ts_format) { case FORMAT_64BIT: seconds = GET_BE_U_4(bp); nanoseconds = GET_BE_U_4(bp + 4); arista_print_date_hms_time(ndo, seconds, nanoseconds); bytesConsumed += 8; break; case FORMAT_48BIT: seconds = GET_BE_U_2(bp); nanoseconds = GET_BE_U_4(bp + 2); seconds += nanoseconds / 1000000000; nanoseconds %= 1000000000; ND_PRINT("%" PRIu64 ".%09u", seconds, nanoseconds); bytesConsumed += 6; break; default: return -1; } } else { return -1; } ND_PRINT(": "); return bytesConsumed; }