diff options
Diffstat (limited to 'src/plugins/cdp/cdp_input.c')
-rw-r--r-- | src/plugins/cdp/cdp_input.c | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/src/plugins/cdp/cdp_input.c b/src/plugins/cdp/cdp_input.c new file mode 100644 index 00000000000..fa993b84def --- /dev/null +++ b/src/plugins/cdp/cdp_input.c @@ -0,0 +1,504 @@ +/* + * 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/cdp.h> + +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); + + 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_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 *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; + + cur = (u8 *) (h + 1); + + while (cur < n->last_rx_pkt + vec_len (n->last_rx_pkt) - 1) + { + tlv = (cdp_tlv_t *) cur; + tlv->t = ntohs (tlv->t); + tlv->l = ntohs (tlv->l); + if (tlv->t >= ARRAY_LEN (tlv_handlers)) + return CDP_ERROR_BAD_TLV; + handler = &tlv_handlers[tlv->t]; + fp = handler->process; + e = (*fp) (cm, n, tlv); + if (e) + return e; + /* tlv length includes (t, l) */ + cur += tlv->l; + } + + 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); + 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_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) +{ + clib_error_t *error; + cdp_main_t *cm = &cdp_main; + void vnet_cdp_node_reference (void); + + vnet_cdp_node_reference (); + + if ((error = vlib_call_init_function (vm, cdp_periodic_init))) + return error; + + 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); + + +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 %=15s %=25s %=10s\n", + "Our Port", "Peer System", "Peer Port", "Last Heard"); + + /* *INDENT-OFF* */ + pool_foreach (n, cm->neighbors, + ({ + hw = vnet_get_sup_hw_interface (vnm, n->sw_if_index); + + if (n->disabled == 0) + s = format (s, "%=25s %=15s %=25s %=10.1f\n", + hw->name, n->device_name, n->port_id, + n->last_heard); + })); + /* *INDENT-ON* */ + 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; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_cdp_command, static) = { + .path = "show cdp", + .short_help = "Show cdp command", + .function = show_cdp, +}; +/* *INDENT-ON* */ + + +/* + * 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: + */ |