| /* |
| * 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. |
| */ |
| /** |
| * @file |
| * @brief LLDP packet parsing implementation |
| */ |
| #include <vnet/lldp/lldp_node.h> |
| #include <vnet/lldp/lldp_protocol.h> |
| #include <vlibmemory/api.h> |
| |
| typedef struct |
| { |
| u32 hw_if_index; |
| u8 chassis_id_len; |
| u8 chassis_id_subtype; |
| u8 portid_len; |
| u8 portid_subtype; |
| u16 ttl; |
| u8 data[0]; /* this contains both chassis id (chassis_id_len bytes) and port |
| id (portid_len bytes) */ |
| } lldp_intf_update_t; |
| |
| static void |
| lldp_rpc_update_peer_cb (const lldp_intf_update_t * a) |
| { |
| ASSERT (os_get_cpu_number () == 0); |
| |
| lldp_intf_t *n = lldp_get_intf (&lldp_main, a->hw_if_index); |
| if (!n) |
| { |
| /* LLDP turned off for this interface, ignore the update */ |
| return; |
| } |
| const u8 *chassis_id = a->data; |
| const u8 *portid = a->data + a->chassis_id_len; |
| |
| if (n->chassis_id) |
| { |
| _vec_len (n->chassis_id) = 0; |
| } |
| vec_add (n->chassis_id, chassis_id, a->chassis_id_len); |
| n->chassis_id_subtype = a->chassis_id_subtype; |
| if (n->port_id) |
| { |
| _vec_len (n->port_id) = 0; |
| } |
| vec_add (n->port_id, portid, a->portid_len); |
| n->port_id_subtype = a->portid_subtype; |
| n->ttl = a->ttl; |
| n->last_heard = vlib_time_now (lldp_main.vlib_main); |
| } |
| |
| static void |
| lldp_rpc_update_peer (u32 hw_if_index, const u8 * chid, u8 chid_len, |
| u8 chid_subtype, const u8 * portid, |
| u8 portid_len, u8 portid_subtype, u16 ttl) |
| { |
| const size_t data_size = |
| sizeof (lldp_intf_update_t) + chid_len + portid_len; |
| u8 data[data_size]; |
| lldp_intf_update_t *u = (lldp_intf_update_t *) data; |
| u->hw_if_index = hw_if_index; |
| u->chassis_id_len = chid_len; |
| u->chassis_id_subtype = chid_subtype; |
| u->ttl = ttl; |
| u->portid_len = portid_len; |
| u->portid_subtype = portid_subtype; |
| clib_memcpy (u->data, chid, chid_len); |
| clib_memcpy (u->data + chid_len, portid, portid_len); |
| vl_api_rpc_call_main_thread (lldp_rpc_update_peer_cb, data, data_size); |
| } |
| |
| lldp_tlv_code_t |
| lldp_tlv_get_code (const lldp_tlv_t * tlv) |
| { |
| return tlv->head.byte1 >> 1; |
| } |
| |
| void |
| lldp_tlv_set_code (lldp_tlv_t * tlv, lldp_tlv_code_t code) |
| { |
| tlv->head.byte1 = (tlv->head.byte1 & 1) + (code << 1); |
| } |
| |
| u16 |
| lldp_tlv_get_length (const lldp_tlv_t * tlv) |
| { |
| return (((u16) (tlv->head.byte1 & 1)) << 8) + tlv->head.byte2; |
| } |
| |
| void |
| lldp_tlv_set_length (lldp_tlv_t * tlv, u16 length) |
| { |
| tlv->head.byte2 = length & ((1 << 8) - 1); |
| if (length > (1 << 8) - 1) |
| { |
| tlv->head.byte1 |= 1; |
| } |
| else |
| { |
| tlv->head.byte1 &= (1 << 8) - 2; |
| } |
| } |
| |
| lldp_main_t lldp_main; |
| |
| static int |
| lldp_packet_scan (u32 hw_if_index, const lldp_tlv_t * pkt) |
| { |
| const lldp_tlv_t *tlv = pkt; |
| |
| #define TLV_VIOLATES_PKT_BOUNDARY(pkt, tlv) \ |
| (((((u8 *)tlv) + sizeof (lldp_tlv_t)) > ((u8 *)pkt + vec_len (pkt))) || \ |
| ((((u8 *)tlv) + lldp_tlv_get_length (tlv)) > ((u8 *)pkt + vec_len (pkt)))) |
| |
| /* first tlv is always chassis id, followed by port id and ttl tlvs */ |
| if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) || |
| LLDP_TLV_NAME (chassis_id) != lldp_tlv_get_code (tlv)) |
| { |
| return LLDP_ERROR_BAD_TLV; |
| } |
| |
| u16 l = lldp_tlv_get_length (tlv); |
| if (l < STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype) + |
| LLDP_MIN_CHASS_ID_LEN || |
| l > STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype) + |
| LLDP_MAX_CHASS_ID_LEN) |
| { |
| return LLDP_ERROR_BAD_TLV; |
| } |
| |
| u8 chid_subtype = ((lldp_chassis_id_tlv_t *) tlv)->subtype; |
| u8 *chid = ((lldp_chassis_id_tlv_t *) tlv)->id; |
| u8 chid_len = l - STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype); |
| |
| tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) + l); |
| |
| if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) || |
| LLDP_TLV_NAME (port_id) != lldp_tlv_get_code (tlv)) |
| { |
| return LLDP_ERROR_BAD_TLV; |
| } |
| l = lldp_tlv_get_length (tlv); |
| if (l < STRUCT_SIZE_OF (lldp_port_id_tlv_t, subtype) + |
| LLDP_MIN_PORT_ID_LEN || |
| l > STRUCT_SIZE_OF (lldp_chassis_id_tlv_t, subtype) + |
| LLDP_MAX_PORT_ID_LEN) |
| { |
| return LLDP_ERROR_BAD_TLV; |
| } |
| |
| u8 portid_subtype = ((lldp_port_id_tlv_t *) tlv)->subtype; |
| u8 *portid = ((lldp_port_id_tlv_t *) tlv)->id; |
| u8 portid_len = l - STRUCT_SIZE_OF (lldp_port_id_tlv_t, subtype); |
| |
| tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) + l); |
| |
| if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) || |
| LLDP_TLV_NAME (ttl) != lldp_tlv_get_code (tlv)) |
| { |
| return LLDP_ERROR_BAD_TLV; |
| } |
| l = lldp_tlv_get_length (tlv); |
| if (l != STRUCT_SIZE_OF (lldp_ttl_tlv_t, ttl)) |
| { |
| return LLDP_ERROR_BAD_TLV; |
| } |
| u16 ttl = ntohs (((lldp_ttl_tlv_t *) tlv)->ttl); |
| tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) + l); |
| while (!TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) && |
| LLDP_TLV_NAME (pdu_end) != lldp_tlv_get_code (tlv)) |
| { |
| switch (lldp_tlv_get_code (tlv)) |
| { |
| #define F(num, type, str) \ |
| case LLDP_TLV_NAME (type): \ |
| /* ignore optional TLV */ \ |
| break; |
| foreach_lldp_optional_tlv_type (F); |
| #undef F |
| default: |
| return LLDP_ERROR_BAD_TLV; |
| } |
| tlv = (lldp_tlv_t *) ((u8 *) tlv + STRUCT_SIZE_OF (lldp_tlv_t, head) + |
| lldp_tlv_get_length (tlv)); |
| } |
| /* last tlv is pdu_end */ |
| if (TLV_VIOLATES_PKT_BOUNDARY (pkt, tlv) || |
| LLDP_TLV_NAME (pdu_end) != lldp_tlv_get_code (tlv) || |
| 0 != lldp_tlv_get_length (tlv)) |
| { |
| return LLDP_ERROR_BAD_TLV; |
| } |
| lldp_rpc_update_peer (hw_if_index, chid, chid_len, chid_subtype, portid, |
| portid_len, portid_subtype, ttl); |
| return LLDP_ERROR_NONE; |
| } |
| |
| lldp_intf_t * |
| lldp_get_intf (lldp_main_t * lm, u32 hw_if_index) |
| { |
| uword *p = hash_get (lm->intf_by_hw_if_index, hw_if_index); |
| |
| if (p) |
| { |
| return pool_elt_at_index (lm->intfs, p[0]); |
| } |
| return NULL; |
| } |
| |
| lldp_intf_t * |
| lldp_create_intf (lldp_main_t * lm, u32 hw_if_index) |
| { |
| |
| uword *p; |
| lldp_intf_t *n; |
| p = hash_get (lm->intf_by_hw_if_index, hw_if_index); |
| |
| if (p == 0) |
| { |
| pool_get (lm->intfs, n); |
| memset (n, 0, sizeof (*n)); |
| n->hw_if_index = hw_if_index; |
| hash_set (lm->intf_by_hw_if_index, n->hw_if_index, n - lm->intfs); |
| } |
| else |
| { |
| n = pool_elt_at_index (lm->intfs, p[0]); |
| } |
| return n; |
| } |
| |
| /* |
| * lldp input routine |
| */ |
| lldp_error_t |
| lldp_input (vlib_main_t * vm, vlib_buffer_t * b0, u32 bi0) |
| { |
| lldp_main_t *lm = &lldp_main; |
| lldp_error_t e; |
| |
| /* find our interface */ |
| vnet_sw_interface_t *sw_interface = vnet_get_sw_interface (lm->vnet_main, |
| vnet_buffer |
| (b0)->sw_if_index |
| [VLIB_RX]); |
| lldp_intf_t *n = lldp_get_intf (lm, sw_interface->hw_if_index); |
| |
| if (!n) |
| { |
| /* lldp disabled on this interface, we're done */ |
| return LLDP_ERROR_DISABLED; |
| } |
| |
| /* Actually scan the packet */ |
| e = lldp_packet_scan (sw_interface->hw_if_index, |
| vlib_buffer_get_current (b0)); |
| |
| return e; |
| } |
| |
| /* |
| * setup function |
| */ |
| static clib_error_t * |
| lldp_init (vlib_main_t * vm) |
| { |
| clib_error_t *error; |
| lldp_main_t *lm = &lldp_main; |
| |
| if ((error = vlib_call_init_function (vm, lldp_template_init))) |
| return error; |
| |
| lm->vlib_main = vm; |
| lm->vnet_main = vnet_get_main (); |
| lm->msg_tx_hold = 4; /* default value per IEEE 802.1AB-2009 */ |
| lm->msg_tx_interval = 30; /* default value per IEEE 802.1AB-2009 */ |
| |
| return 0; |
| } |
| |
| VLIB_INIT_FUNCTION (lldp_init); |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |