/* packet-cisco-fp-mim.c * Routines for analyzing Cisco FabricPath MiM (MAC-in-MAA) packets * Copyright 2011, Leonard Tracy * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ /* * https://clnv.s3.amazonaws.com/2016/usa/pdf/BRKDCT-3313.pdf * https://clnv.s3.amazonaws.com/2014/eur/pdf/BRKDCT-2081.pdf */ #include "config.h" #include #include #include #include #include #include void proto_register_mim(void); void proto_reg_handoff_fabricpath(void); static gboolean fp_check_fcs = FALSE; static int proto_fp = -1; static gint ett_mim = -1; static gint ett_hmac = -1; /* Main protocol items */ static int hf_s_hmac = -1; static int hf_d_hmac = -1; static int hf_d_hmac_mc = -1; static int hf_ftag = -1; static int hf_ttl = -1; static int hf_fp_etype = -1; static int hf_fp_1ad_etype = -1; static int hf_fp_1ad_priority = -1; static int hf_fp_1ad_cfi = -1; static int hf_fp_1ad_svid = -1; static int hf_fp_fcs = -1; static int hf_fp_fcs_status = -1; /* HMAC subtrees */ static int hf_swid = -1; static int hf_sswid = -1; static int hf_eid = -1; static int hf_lid = -1; static int hf_ul = -1; static int hf_ig = -1; static int hf_ooodl = -1; static expert_field ei_fp_fcs_bad = EI_INIT; static const true_false_string ig_tfs = { "Group address (multicast/broadcast)", "Individual address (unicast)" }; static const true_false_string ul_tfs = { "Locally administered address (this is NOT the factory default)", "Globally unique address (factory default)" }; static const true_false_string ooodl_tfs = { "Out of order delivery (If DA) or Do not learn (If SA)", "Deliver in order (If DA) or Learn (If SA)" }; static dissector_handle_t eth_withoutfcs_dissector; #define FP_PROTO_COL_NAME "FabricPath" #define FP_PROTO_COL_INFO "Cisco FabricPath MiM Encapsulated Frame" #define FP_FIELD_LEN 3 #define FP_EID_MASK 0x00FCC0 #define FP_3B_EID_MASK 0xFCC000 #define FP_UL_MASK 0x020000 #define FP_IG_MASK 0x010000 #define FP_EID2_MASK 0x00C000 #define FP_RES_MASK 0x002000 #define FP_OOO_MASK 0x001000 #define FP_SWID_MASK 0x000FFF #define FP_BF_LEN 3 #define FP_LID_LEN 2 #define FP_SSWID_LEN 1 #define FP_FTAG_LEN 2 #define FP_FTAG_MASK 0xFFC0 #define FP_TTL_MASK 0x003F #define FP_HMAC_IG_MASK G_GINT64_CONSTANT(0x010000000000) #define FP_HMAC_SWID_MASK G_GINT64_CONSTANT(0x000FFF000000) #define FP_HMAC_SSWID_MASK G_GINT64_CONSTANT(0x000000FF0000) #define FP_HMAC_LID_MASK G_GINT64_CONSTANT(0x00000000FFFF) #define FP_HMAC_LEN 6 #define FP_HEADER_SIZE (16) #define FP_HEADER_WITH_1AD_SIZE (20) /* 0100.0000.0000 */ #define MAC_MC_BC G_GINT64_CONSTANT(0x010000000000) /* proto */ static int dissect_fp_common( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int header_size ); /* * These packets are a bit strange. * * They run over Ethernet, but, instead of a normal 14-octet Ethernet * header, they have a 16-octet or 20-octet header, which happens to * have, in the position occupied by the Type/Length field in an * Ethernet header, the Ethertype value reserved for FabricPath. * * The fields in the positions occupied by the destination and source * MAC addresses in an Ethernet header are occupied by addresses that * are parsed specially, so we want to dissect them differently from * normal MAC addresses. * * The Ethertype field is part of a 4-octet FP tag, which includes * the Ethertype and some additional information. * * So we register as a heuristic dissector, which gets called before * the regular code that checks Ethertypes. */ static gboolean dissect_fp_heur (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_) { guint16 etype = 0; int header_size = 0; if ( ! tvb_bytes_exist( tvb, 12, 2 ) ) return FALSE; etype = tvb_get_ntohs( tvb, 12 ); switch ( etype ) { case ETHERTYPE_DCE: header_size = FP_HEADER_SIZE; break; case ETHERTYPE_IEEE_802_1AD: case ETHERTYPE_VLAN: if ( tvb_bytes_exist( tvb, 16, 2 ) && tvb_get_ntohs( tvb, 16 ) == ETHERTYPE_DCE ) { header_size = FP_HEADER_WITH_1AD_SIZE; break; } /* fall through */ default: return FALSE; } if ( dissect_fp_common( tvb, pinfo, tree, header_size ) > 0 ) { return TRUE; } return FALSE; } static void fp_get_hmac_addr (guint64 hmac, guint16 *swid, guint16 *sswid, guint16 *lid) { if (!swid || !sswid || !lid) { return; } *swid = (guint16) ((hmac & FP_HMAC_SWID_MASK) >> 24); *sswid = (guint16) ((hmac & FP_HMAC_SSWID_MASK) >> 16); *lid = (guint16) (hmac & FP_HMAC_LID_MASK); } static void fp_add_hmac (tvbuff_t *tvb, proto_tree *tree, int offset) { guint16 eid; if (!tree) { return; } eid = tvb_get_ntohs(tvb, offset); eid &= FP_EID_MASK; eid = ((eid & 0x00C0) >> 6) + ((eid & 0xFC00) >> 8); proto_tree_add_uint(tree, hf_eid, tvb, offset, FP_BF_LEN, eid); proto_tree_add_item (tree, hf_ul, tvb, offset, FP_BF_LEN, ENC_NA); proto_tree_add_item (tree, hf_ig, tvb, offset, FP_BF_LEN, ENC_NA); proto_tree_add_item (tree, hf_ooodl, tvb, offset, FP_BF_LEN, ENC_NA); proto_tree_add_item (tree, hf_swid, tvb, offset, FP_BF_LEN, ENC_BIG_ENDIAN); offset += FP_BF_LEN; proto_tree_add_item (tree, hf_sswid, tvb, offset, FP_SSWID_LEN, ENC_BIG_ENDIAN); offset += FP_SSWID_LEN; proto_tree_add_item (tree, hf_lid, tvb, offset, FP_LID_LEN, ENC_BIG_ENDIAN); /*offset += FP_LID_LEN;*/ } /* FabricPath MiM Dissector */ static int dissect_fp_common ( tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, int header_size) { proto_item *ti; proto_tree *fp_tree; proto_tree *fp_addr_tree; tvbuff_t *next_tvb; int offset = 0; int next_tvb_len = 0; int fcs_offset = 0; guint64 hmac_src; guint64 hmac_dst; guint16 sswid = 0; guint16 ssswid = 0; guint16 slid = 0; guint16 dswid = 0; guint16 dsswid = 0; guint16 dlid = 0; guint16 etype = 0; const guint8 *dst_addr = NULL; gboolean dest_as_mac = FALSE; col_set_str( pinfo->cinfo, COL_PROTOCOL, FP_PROTO_COL_NAME ); col_set_str( pinfo->cinfo, COL_INFO, FP_PROTO_COL_INFO ); /* * Outer SA: * - SwitchID ingress FP switch system ID * - SubswitchID is used in some cases of VPC+ * - LID (Local ID) is specific to the implementation * + N7K the LID is generally the port index of the ingress interface * + N5K/N6K LID most of the time will be 0 * + EndnodeID is not currently used * * Outer DA: * - For known SA/DA is taken from MAC table for DMAC * - For broadcast and multicast is the same as DMAC * - For unknown unicast DA is 010f.ffc1.01c0 (flood to vlan) * - For known unicast DA, but unknown SA is 010f.ffc1.02c0 (flood to fabric) */ hmac_dst = tvb_get_ntoh48 (tvb, 0); hmac_src = tvb_get_ntoh48 (tvb, 6); if (hmac_dst & MAC_MC_BC) { dest_as_mac = TRUE; } if (!dest_as_mac) { fp_get_hmac_addr (hmac_dst, &dswid, &dsswid, &dlid); } else { hmac_dst = GUINT64_TO_BE (hmac_dst); /* Get pointer to most sig byte of destination address in network order */ dst_addr = ((const guint8 *) &hmac_dst) + 2; } fp_get_hmac_addr (hmac_src, &sswid, &ssswid, &slid); /* FIXME: Does this make sense??? */ if (tree && PTREE_DATA(tree)->visible) { if (dest_as_mac) { address ether_addr; set_address(ðer_addr, AT_ETHER, 6, dst_addr); ti = proto_tree_add_protocol_format(tree, proto_fp, tvb, 0, header_size, "Cisco FabricPath, Src: %03x.%02x.%04x, Dst: %s", sswid, ssswid, slid, address_with_resolution_to_str(pinfo->pool, ðer_addr)); } else { ti = proto_tree_add_protocol_format(tree, proto_fp, tvb, 0, header_size, "Cisco FabricPath, Src: %03x.%02x.%04x, Dst: %03x.%02x.%04x", sswid, ssswid, slid, dswid, dsswid, dlid); } } else { ti = proto_tree_add_item( tree, proto_fp, tvb, 0, header_size, ENC_NA ); } fp_tree = proto_item_add_subtree( ti, ett_mim ); /* Add dest and source heir. mac */ if (dest_as_mac) { /* MCAST address */ proto_tree_add_ether( fp_tree, hf_d_hmac_mc, tvb, offset, 6, dst_addr); } else { /* Unicast */ ti = proto_tree_add_none_format (fp_tree, hf_d_hmac, tvb, offset, 6, "Destination: %03x.%02x.%04x", dswid, dsswid, dlid); fp_addr_tree = proto_item_add_subtree (ti, ett_hmac); fp_add_hmac (tvb, fp_addr_tree, offset); } offset += FP_HMAC_LEN; ti = proto_tree_add_none_format (fp_tree, hf_s_hmac, tvb, offset, 6, "Source: %03x.%02x.%04x", sswid, ssswid, slid); fp_addr_tree = proto_item_add_subtree (ti, ett_hmac); fp_add_hmac (tvb, fp_addr_tree, offset); offset += FP_HMAC_LEN; etype = tvb_get_ntohs(tvb, offset); switch (etype) { case ETHERTYPE_DCE: proto_tree_add_item(fp_tree, hf_fp_etype, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; break; case ETHERTYPE_IEEE_802_1AD: case ETHERTYPE_VLAN: proto_tree_add_item(fp_tree, hf_fp_1ad_etype, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; proto_tree_add_item(fp_tree, hf_fp_1ad_priority, tvb, offset, 2, ENC_NA); proto_tree_add_item(fp_tree, hf_fp_1ad_cfi, tvb, offset, 2, ENC_NA); proto_tree_add_item(fp_tree, hf_fp_1ad_svid, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; proto_tree_add_item(fp_tree, hf_fp_etype, tvb, offset, 2, ENC_BIG_ENDIAN); offset += 2; break; default: /* The heuristics should prevent us from getting here */ DISSECTOR_ASSERT(0); } proto_tree_add_item (fp_tree, hf_ftag, tvb, offset, FP_FTAG_LEN, ENC_BIG_ENDIAN); proto_tree_add_item (fp_tree, hf_ttl, tvb, offset, FP_FTAG_LEN, ENC_BIG_ENDIAN); /* eval FCS */ fcs_offset = tvb_reported_length(tvb) - 4; if ( tvb_bytes_exist(tvb, fcs_offset, 4 ) ) { if ( fp_check_fcs ) { guint32 fcs = crc32_802_tvb(tvb, fcs_offset); proto_tree_add_checksum(fp_tree, tvb, fcs_offset, hf_fp_fcs, hf_fp_fcs_status, &ei_fp_fcs_bad, pinfo, fcs, ENC_BIG_ENDIAN, PROTO_CHECKSUM_VERIFY); } else { proto_tree_add_checksum(fp_tree, tvb, fcs_offset, hf_fp_fcs, hf_fp_fcs_status, &ei_fp_fcs_bad, pinfo, 0, ENC_BIG_ENDIAN, PROTO_CHECKSUM_NO_FLAGS); } proto_tree_set_appendix(fp_tree, tvb, fcs_offset, 4); } /* call the eth dissector w/o the FCS */ next_tvb_len = tvb_reported_length_remaining( tvb, header_size ) - 4; next_tvb = tvb_new_subset_length( tvb, header_size, next_tvb_len ); /* * We've already verified the replaced CFP checksum * Therefore we call the Ethernet dissector without expecting a FCS */ call_dissector( eth_withoutfcs_dissector, next_tvb, pinfo, tree ); return tvb_captured_length( tvb ); } /* Register the protocol with Wireshark */ void proto_register_mim(void) { static hf_register_info hf[] = { { &hf_s_hmac, { "Source HMAC", "cfp.s_hmac", FT_NONE, BASE_NONE, NULL, 0, "Source Hierarchical MAC", HFILL }}, { &hf_d_hmac, { "Destination HMAC", "cfp.d_hmac", FT_NONE, BASE_NONE, NULL, 0, "Destination Hierarchical MAC", HFILL }}, { &hf_d_hmac_mc, { "MC Destination", "cfp.d_hmac_mc", FT_ETHER, BASE_NONE, NULL, 0, "Multicast Destination Address", HFILL }}, { &hf_fp_etype, { "FP Ethertype", "cfp.etype", FT_UINT16, BASE_HEX, VALS(etype_vals), 0x0, NULL, HFILL }}, { &hf_fp_1ad_etype, { "IEEE 802.1ad Ethertype", "cfp.1ad.etype", FT_UINT16, BASE_HEX, VALS(etype_vals), 0x0, NULL, HFILL }}, { &hf_fp_1ad_priority, { "Priority", "cfp.1ad.priority", FT_UINT16, BASE_DEC, 0, 0xE000, NULL, HFILL }}, { &hf_fp_1ad_cfi, { "DEI", "cfp.1ad.dei", FT_UINT16, BASE_DEC, 0, 0x1000, "Drop Eligibility", HFILL }}, { &hf_fp_1ad_svid, { "ID", "cfp.1ad.id", FT_UINT16, BASE_DEC, 0, 0x0FFF, "Vlan ID", HFILL }}, { &hf_fp_fcs, { "Frame check sequence", "cfp.fcs", FT_UINT32, BASE_HEX, NULL, 0x0, "FabricPath checksum", HFILL }}, { &hf_fp_fcs_status, { "FCS status", "cfp.fcs.status", FT_UINT8, BASE_NONE, VALS(proto_checksum_vals), 0x0, NULL, HFILL }}, { &hf_ftag, { "FTAG", "cfp.ftag", FT_UINT16, BASE_DEC, NULL, FP_FTAG_MASK, "FTAG field identifying forwarding distribution tree.", HFILL }}, { &hf_ttl, { "TTL", "cfp.ttl", FT_UINT16, BASE_DEC, NULL, FP_TTL_MASK, "The remaining hop count for this frame", HFILL }}, { &hf_swid, { "switch-id", "cfp.swid", FT_UINT24, BASE_DEC_HEX, NULL, FP_SWID_MASK, "Switch-id/nickname of switch in FabricPath network", HFILL }}, { &hf_sswid, { "sub-switch-id", "cfp.sswid", FT_UINT8, BASE_DEC_HEX, NULL, 0x0, "Sub-switch-id of switch in FabricPath network", HFILL }}, { &hf_eid, { "End Node ID", "cfp.eid", FT_UINT24, BASE_DEC_HEX, NULL, FP_3B_EID_MASK, "Cisco FabricPath End node ID", HFILL }}, { &hf_lid, { "Source LID", "cfp.lid", FT_UINT16, BASE_DEC_HEX, NULL, 0x0, "Source or Destination Port index on switch in FabricPath network", HFILL }}, { &hf_ul, { "U/L bit", "cfp.ul", FT_BOOLEAN, 24, TFS(&ul_tfs), FP_UL_MASK, "Specifies if this is a locally administered or globally unique (IEEE assigned) address", HFILL }}, { &hf_ig, { "I/G bit", "cfp.ig", FT_BOOLEAN, 24 /* FP_BF_LEN */, TFS(&ig_tfs), FP_IG_MASK, "Specifies if this is an individual (unicast) or group (broadcast/multicast) address", HFILL }}, { &hf_ooodl, { "OOO/DL Bit", "cfp.ooodl", FT_BOOLEAN, 24 /* FP_BF_LEN */, TFS(&ooodl_tfs), FP_OOO_MASK, "Specifies Out of Order Delivery OK in destination address and Do Not Learn when set in source address", HFILL }} }; static gint *ett[] = { &ett_mim, &ett_hmac }; static ei_register_info ei[] = { { &ei_fp_fcs_bad, { "cfp.fcs_bad", PI_CHECKSUM, PI_ERROR, "Bad checksum", EXPFILL }} }; module_t *mim_module; expert_module_t *expert_mim; proto_fp = proto_register_protocol("Cisco FabricPath", "CFP", "cfp"); mim_module = prefs_register_protocol (proto_fp, proto_reg_handoff_fabricpath); prefs_register_obsolete_preference (mim_module, "enable"); prefs_register_bool_preference(mim_module, "check_fcs", "Validate the FabricPath checksum if possible", "Whether to validate the Frame Check Sequence", &fp_check_fcs); proto_register_field_array(proto_fp, hf, array_length(hf)); proto_register_subtree_array(ett, array_length(ett)); expert_mim = expert_register_protocol(proto_fp); expert_register_field_array(expert_mim, ei, array_length(ei)); } void proto_reg_handoff_fabricpath(void) { static gboolean prefs_initialized = FALSE; if (!prefs_initialized) { /* * Using Heuristic dissector (As opposed to * registering the ethertype) in order to * get outer source and destination MAC * before the standard ethernet dissector */ heur_dissector_add ("eth", dissect_fp_heur, "Cisco FabricPath over Ethernet", "fp_eth", proto_fp, HEURISTIC_ENABLE); /* * The FCS in FabricPath frames covers the entire FabricPath frame, * not the encapsulated Ethernet frame, so we don't want to treat * the encapsulated frame as if it had an FCS. */ eth_withoutfcs_dissector = find_dissector_add_dependency( "eth_withoutfcs", proto_fp ); prefs_initialized = TRUE; } } /* * Editor modelines - https://www.wireshark.org/tools/modelines.html * * Local Variables: * c-basic-offset: 2 * tab-width: 8 * indent-tabs-mode: nil * End: * * ex: set shiftwidth=2 tabstop=8 expandtab: * :indentSize=2:tabSize=8:noTabs=true: */