/* * Copyright (c) 2011-2016 Cisco and/or its affiliates. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include cdp_main_t cdp_main; #define DEBUG_TLV_DUMP 0 /* 1=> dump TLV's to stdout while processing them */ /* * ported from an unspecified Cisco cdp implementation. * Compute / return in HOST byte order. 0 => good checksum. */ u16 cdp_checksum (void *p, int count) { u32 sum; u16 i, *data; data = p; sum = 0; while (count > 1) { sum += ntohs (*data); data++; count -= 2; } if (count > 0) sum += *(char *) data; while (sum >> 16) { sum = (sum & 0xFFFF) + (sum >> 16); } i = (i16) sum; return (~i); } /* TLV handler table */ typedef struct { char *name; u32 tlv_id; void *format; void *process; } tlv_handler_t; static tlv_handler_t tlv_handlers[]; /* Display a generic TLV as a set of hex bytes */ static u8 * format_generic_tlv (u8 * s, va_list * va) { cdp_tlv_t *t = va_arg (*va, cdp_tlv_t *); tlv_handler_t *h = &tlv_handlers[t->t]; s = format (s, "%s(%d): %U\n", h->name, t->t, format_hex_bytes, t->v, t->l - sizeof (*t)); return s; } /* Ignore / skip a TLV we don't support */ static cdp_error_t process_generic_tlv (cdp_main_t * cm, cdp_neighbor_t * n, cdp_tlv_t * t) { #if DEBUG_TLV_DUMP > 0 fformat (stdout, "%U", format_generic_tlv, t); #endif return CDP_ERROR_NONE; } /* print a text tlv */ static u8 * format_text_tlv (u8 * s, va_list * va) { cdp_tlv_t *t = va_arg (*va, cdp_tlv_t *); tlv_handler_t *h = &tlv_handlers[t->t]; int i; s = format (s, "%s(%d): ", h->name, t->t); if (t->l >= 4) { for (i = 0; i < (t->l - sizeof (*t)); i++) vec_add1 (s, t->v[i]); } vec_add1 (s, '\n'); return s; } #if DEBUG_TLV_DUMP == 0 /* gcc warning be gone */ CLIB_UNUSED (static cdp_error_t process_text_tlv (cdp_main_t * cm, cdp_neighbor_t * n, cdp_tlv_t * t)); #endif /* process / skip a generic text TLV that we don't support */ static cdp_error_t process_text_tlv (cdp_main_t * cm, cdp_neighbor_t * n, cdp_tlv_t * t) { #if DEBUG_TLV_DUMP > 0 fformat (stdout, "%U\n", format_text_tlv, t); #endif return CDP_ERROR_NONE; } /* per-TLV format function definitions */ #define format_unused_tlv format_generic_tlv #define format_device_name_tlv format_text_tlv #define format_address_tlv format_generic_tlv #define format_port_id_tlv format_text_tlv #define format_capabilities_tlv format_generic_tlv #define format_version_tlv format_text_tlv #define format_platform_tlv format_text_tlv #define format_ipprefix_tlv format_generic_tlv #define format_hello_tlv format_generic_tlv #define format_vtp_domain_tlv format_generic_tlv #define format_native_vlan_tlv format_generic_tlv #define format_duplex_tlv format_generic_tlv #define format_appl_vlan_tlv format_generic_tlv #define format_trigger_tlv format_generic_tlv #define format_power_tlv format_generic_tlv #define format_mtu_tlv format_generic_tlv #define format_trust_tlv format_generic_tlv #define format_cos_tlv format_generic_tlv #define format_sysname_tlv format_generic_tlv #define format_sysobject_tlv format_generic_tlv #define format_mgmt_addr_tlv format_generic_tlv #define format_physical_loc_tlv format_generic_tlv #define format_mgmt_addr2_tlv format_generic_tlv #define format_power_requested_tlv format_generic_tlv #define format_power_available_tlv format_generic_tlv #define format_port_unidirectional_tlv format_generic_tlv #define format_unknown_28_tlv format_generic_tlv #define format_energywise_tlv format_generic_tlv #define format_unknown_30_tlv format_generic_tlv #define format_spare_poe_tlv format_generic_tlv /* tlv ID=0 is a mistake */ static cdp_error_t process_unused_tlv (cdp_main_t * cm, cdp_neighbor_t * n, cdp_tlv_t * t) { return CDP_ERROR_BAD_TLV; } /* list of text TLV's that we snapshoot */ #define foreach_text_to_struct_tlv \ _(device_name,DEBUG_TLV_DUMP) \ _(version,DEBUG_TLV_DUMP) \ _(platform,DEBUG_TLV_DUMP) \ _(port_id,DEBUG_TLV_DUMP) #define _(z, dbg) \ static cdp_error_t process_##z##_tlv (cdp_main_t *cm, cdp_neighbor_t *n, \ cdp_tlv_t *t) \ { \ int i; \ if (dbg) \ fformat (stdout, "%U\n", format_text_tlv, t); \ \ if (n->z) \ vec_set_len (n->z, 0); \ \ for (i = 0; i < (t->l - sizeof (*t)); i++) \ vec_add1 (n->z, t->v[i]); \ \ vec_add1 (n->z, 0); \ \ return CDP_ERROR_NONE; \ } foreach_text_to_struct_tlv #undef _ #define process_address_tlv process_generic_tlv #define process_capabilities_tlv process_generic_tlv #define process_ipprefix_tlv process_generic_tlv #define process_hello_tlv process_generic_tlv #define process_vtp_domain_tlv process_generic_tlv #define process_native_vlan_tlv process_generic_tlv #define process_duplex_tlv process_generic_tlv #define process_appl_vlan_tlv process_generic_tlv #define process_trigger_tlv process_generic_tlv #define process_power_tlv process_generic_tlv #define process_mtu_tlv process_generic_tlv #define process_trust_tlv process_generic_tlv #define process_cos_tlv process_generic_tlv #define process_sysname_tlv process_generic_tlv #define process_sysobject_tlv process_generic_tlv #define process_mgmt_addr_tlv process_generic_tlv #define process_physical_loc_tlv process_generic_tlv #define process_mgmt_addr2_tlv process_generic_tlv #define process_power_requested_tlv process_generic_tlv #define process_power_available_tlv process_generic_tlv #define process_port_unidirectional_tlv process_generic_tlv #define process_unknown_28_tlv process_generic_tlv #define process_energywise_tlv process_generic_tlv #define process_unknown_30_tlv process_generic_tlv #define process_spare_poe_tlv process_generic_tlv static tlv_handler_t tlv_handlers[] = { #define _(a) {#a, CDP_TLV_##a, format_##a##_tlv, process_##a##_tlv}, foreach_cdp_tlv_type #undef _ }; #if DEBUG_TLV_DUMP == 0 CLIB_UNUSED (static u8 * format_cdp_hdr (u8 * s, va_list * va)); #endif static u8 * format_cdp_hdr (u8 * s, va_list * va) { cdp_hdr_t *h = va_arg (*va, cdp_hdr_t *); s = format (s, "version %d, ttl %d(secs), cksum 0x%04x\n", h->version, h->ttl, h->checksum); return s; } static cdp_error_t process_cdp_hdr (cdp_main_t * cm, cdp_neighbor_t * n, cdp_hdr_t * h) { #if DEBUG_TLV_DUMP > 0 fformat (stdout, "%U", format_cdp_hdr, h); #endif if (h->version != 1 && h->version != 2) return CDP_ERROR_PROTOCOL_VERSION; n->ttl_in_seconds = h->ttl; return CDP_ERROR_NONE; } /* scan a cdp packet; header, then tlv's */ static int cdp_packet_scan (cdp_main_t * cm, cdp_neighbor_t * n) { u8 *end, *cur = n->last_rx_pkt; cdp_hdr_t *h; cdp_tlv_t *tlv; cdp_error_t e = CDP_ERROR_NONE; tlv_handler_t *handler; cdp_error_t (*fp) (cdp_main_t *, cdp_neighbor_t *, cdp_tlv_t *); u16 computed_checksum; computed_checksum = cdp_checksum (cur, vec_len (cur)); if (computed_checksum) return CDP_ERROR_CHECKSUM; h = (cdp_hdr_t *) cur; e = process_cdp_hdr (cm, n, h); if (e) return e; // there are no tlvs if (vec_len (n->last_rx_pkt) <= 0) return CDP_ERROR_BAD_TLV; cur = (u8 *) (h + 1); end = n->last_rx_pkt + vec_len (n->last_rx_pkt) - 1; // look ahead 4 bytes (u16 tlv->t + u16 tlv->l) while (cur + 3 <= end) { tlv = (cdp_tlv_t *) cur; tlv->t = ntohs (tlv->t); tlv->l = ntohs (tlv->l); /* tlv length includes t, l and v */ if (tlv->l < 4) return CDP_ERROR_BAD_TLV; cur += tlv->l; if ((cur - 1) > end) return CDP_ERROR_BAD_TLV; /* * Only process known TLVs. In practice, certain * devices send tlv->t = 0xFF, perhaps as an EOF of sorts. */ if (tlv->t < ARRAY_LEN (tlv_handlers)) { handler = &tlv_handlers[tlv->t]; fp = handler->process; e = (*fp) (cm, n, tlv); if (e) return e; } } // did not process all tlvs or none tlv processed if ((cur - 1) != end) return CDP_ERROR_BAD_TLV; return CDP_ERROR_NONE; } /* * cdp input routine */ cdp_error_t cdp_input (vlib_main_t * vm, vlib_buffer_t * b0, u32 bi0) { cdp_main_t *cm = &cdp_main; cdp_neighbor_t *n; uword *p, nbytes; cdp_error_t e; uword last_packet_signature; /* find or create a neighbor pool entry for the (sw) interface upon which we received this pkt */ p = hash_get (cm->neighbor_by_sw_if_index, vnet_buffer (b0)->sw_if_index[VLIB_RX]); if (p == 0) { pool_get (cm->neighbors, n); clib_memset (n, 0, sizeof (*n)); n->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; n->packet_template_index = (u8) ~ 0; hash_set (cm->neighbor_by_sw_if_index, n->sw_if_index, n - cm->neighbors); } else { n = pool_elt_at_index (cm->neighbors, p[0]); } /* * typical clib idiom. Don't repeatedly allocate and free * the per-neighbor rx buffer. Reset its apparent length to zero * and reuse it. */ if (n->last_rx_pkt) vec_set_len (n->last_rx_pkt, 0); /* cdp disabled on this interface, we're done */ if (n->disabled) return CDP_ERROR_DISABLED; /* * Make sure the per-neighbor rx buffer is big enough to hold * the data we're about to copy */ vec_validate (n->last_rx_pkt, vlib_buffer_length_in_chain (vm, b0) - 1); /* * Coalesce / copy e the buffer chain into the per-neighbor * rx buffer */ nbytes = vlib_buffer_contents (vm, bi0, n->last_rx_pkt); ASSERT (nbytes <= vec_len (n->last_rx_pkt)); /* * Compute Jenkins hash of the new packet, decide if we need to * actually parse through the TLV's. CDP packets are all identical, * so unless we time out the peer, we don't need to process the packet. */ last_packet_signature = hash_memory (n->last_rx_pkt, vec_len (n->last_rx_pkt), 0xd00b); if (n->last_packet_signature_valid && n->last_packet_signature == last_packet_signature) { e = CDP_ERROR_CACHE_HIT; } else { /* Actually scan the packet */ e = cdp_packet_scan (cm, n); n->last_packet_signature_valid = 1; n->last_packet_signature = last_packet_signature; } if (e == CDP_ERROR_NONE) { n->last_heard = vlib_time_now (vm); } return e; } /* * setup neighbor hash table */ static clib_error_t * cdp_input_init (vlib_main_t * vm) { cdp_main_t *cm = &cdp_main; cm->vlib_main = vm; cm->vnet_main = vnet_get_main (); cm->neighbor_by_sw_if_index = hash_create (0, sizeof (uword)); return 0; } VLIB_INIT_FUNCTION (cdp_input_init) = { .runs_after = VLIB_INITS("cdp_periodic_init"), }; static u8 * format_cdp_neighbors (u8 * s, va_list * va) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *); cdp_main_t *cm = va_arg (*va, cdp_main_t *); vnet_main_t *vnm = &vnet_main; cdp_neighbor_t *n; vnet_hw_interface_t *hw; s = format (s, "%=25s %=25s %=25s %=10s\n", "Our Port", "Peer System", "Peer Port", "Last Heard"); pool_foreach (n, cm->neighbors) { hw = vnet_get_sup_hw_interface (vnm, n->sw_if_index); if (n->disabled == 0) s = format (s, "%=25v %=25s %=25s %=10.1f\n", hw->name, n->device_name, n->port_id, n->last_heard); } return s; } static clib_error_t * show_cdp (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { cdp_main_t *cm = &cdp_main; if (cm->enabled == 0) vlib_cli_output (vm, "CDP is not enabled..."); else vlib_cli_output (vm, "%U\n", format_cdp_neighbors, vm, cm); return 0; } VLIB_CLI_COMMAND (show_cdp_command, static) = { .path = "show cdp", .short_help = "Show cdp command", .function = show_cdp, }; /* * packet trace format function, very similar to * cdp_packet_scan except that we call the per TLV format * functions instead of the per TLV processing functions */ u8 * cdp_input_format_trace (u8 * s, va_list * args) { CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); cdp_input_trace_t *t = va_arg (*args, cdp_input_trace_t *); u8 *cur; cdp_hdr_t *h; cdp_tlv_t *tlv; tlv_handler_t *handler; u8 *(*fp) (cdp_tlv_t *); cur = t->data; h = (cdp_hdr_t *) cur; s = format (s, "%U", format_cdp_hdr, h); cur = (u8 *) (h + 1); while (cur < t->data + t->len) { tlv = (cdp_tlv_t *) cur; tlv->t = ntohs (tlv->t); tlv->l = ntohs (tlv->l); if (tlv->t >= ARRAY_LEN (tlv_handlers)) { s = format (s, "BAD_TLV\n"); break; } handler = &tlv_handlers[tlv->t]; fp = handler->format; s = format (s, " %U", fp, tlv); /* tlv length includes (t, l) */ cur += tlv->l; } return s; } /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */