diff options
Diffstat (limited to 'proto_lldp.c')
-rw-r--r-- | proto_lldp.c | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/proto_lldp.c b/proto_lldp.c new file mode 100644 index 0000000..22b7684 --- /dev/null +++ b/proto_lldp.c @@ -0,0 +1,469 @@ +/* + * netsniff-ng - the packet sniffing beast + * Copyright 2012, 2013 Tobias Klauser <tklauser@distanz.ch> + * Subject to the GPL, version 2. + */ + +#include <stdint.h> +#include <arpa/inet.h> /* for inet_ntop() */ +#include <netinet/in.h> /* for ntohs()/ntohl() */ + +#include "built_in.h" +#include "oui.h" +#include "pkt_buff.h" +#include "proto.h" +#include "protos.h" +#include "xutils.h" + +#define EXTRACT_16BIT(x) ntohs(*((uint16_t *) (x))) +#define EXTRACT_32BIT(x) ntohl(*((uint32_t *) (x))) + +#define LLDP_TLV_TYPE(tlv) (((tlv) & 0xFE00) >> 9) +#define LLDP_TLV_LENGTH(tlv) ((tlv) & 0x01FF) + +/* + * LLDP TLV types + */ +#define LLDP_TLV_END 0 +#define LLDP_TLV_CHASSIS_ID 1 +#define LLDP_TLV_PORT_ID 2 +#define LLDP_TLV_TTL 3 +#define LLDP_TLV_PORT_DESC 4 +#define LLDP_TLV_SYSTEM_NAME 5 +#define LLDP_TLV_SYSTEM_DESC 6 +#define LLDP_TLV_SYSTEM_CAP 7 +#define LLDP_TLV_MGMT_ADDR 8 +#define LLDP_TLV_ORG_SPECIFIC 127 + +/* + * Chassis ID subtypes + */ +#define LLDP_CHASSIS_SUBTYPE_CHASSIS 1 +#define LLDP_CHASSIS_SUBTYPE_IF_ALIAS 2 +#define LLDP_CHASSIS_SUBTYPE_PORT 3 +#define LLDP_CHASSIS_SUBTYPE_MAC_ADDR 4 +#define LLDP_CHASSIS_SUBTYPE_NET_ADDR 5 +#define LLDP_CHASSIS_SUBTYPE_IF_NAME 6 +#define LLDP_CHASSIS_SUBTYPE_LOCAL 7 + +/* + * Port ID subtypes + */ +#define LLDP_PORT_SUBTYPE_IF_ALIAS 1 +#define LLDP_PORT_SUBTYPE_PORT_COMP 2 +#define LLDP_PORT_SUBTYPE_MAC_ADDR 3 +#define LLDP_PORT_SUBTYPE_NET_ADDR 4 +#define LLDP_PORT_SUBTYPE_IF_NAME 5 +#define LLDP_PORT_SUBTYPE_AGENT_CIRC_ID 6 +#define LLDP_PORT_SUBTYPE_LOCAL 7 + +/* + * System capabilits bit masks + */ +#define LLDP_SYSTEM_CAP_OTHER (1 << 0) +#define LLDP_SYSTEM_CAP_REPEATER (1 << 1) +#define LLDP_SYSTEM_CAP_BRIDGE (1 << 2) +#define LLDP_SYSTEM_CAP_WLAN_AP (1 << 3) +#define LLDP_SYSTEM_CAP_ROUTER (1 << 4) +#define LLDP_SYSTEM_CAP_TELEPHONE (1 << 5) +#define LLDP_SYSTEM_CAP_DOCSIS (1 << 6) +#define LLDP_SYSTEM_CAP_STATION_ONLY (1 << 7) + +/* + * Interface number subtypes (for Management addres TLV) + */ +#define LLDP_IFACE_NUM_SUBTYPE_UNKNOWN 1 +#define LLDP_IFACE_NUM_SUBTYPE_IF_INDEX 2 +#define LLDP_IFACE_NUM_SUBTYPE_SYS_PORT 3 + +/* + * IANA address family numbers (only the ones we actually use) + * http://www.iana.org/assignments/address-family-numbers/address-family-numbers.txt + * + * TODO: Move these into own header if there are other users? + */ +#define IANA_AF_IPV4 1 +#define IANA_AF_IPV6 2 +#define IANA_AF_802 6 + +static int lldp_print_net_addr(const uint8_t *addr, size_t addrlen) +{ + uint8_t af; + char buf[64]; + + if (addrlen < 1) + return -EINVAL; + + af = *addr++; + addrlen--; + switch (af) { + case IANA_AF_IPV4: + if (addrlen < 4) + return -EINVAL; + inet_ntop(AF_INET, addr, buf, sizeof(buf)); + tprintf("%s", buf); + break; + case IANA_AF_IPV6: + if (addrlen < 16) + return -EINVAL; + inet_ntop(AF_INET6, addr, buf, sizeof(buf)); + tprintf("%s", buf); + break; + case IANA_AF_802: + if (addrlen < 6) + return -EINVAL; + tprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + break; + default: + tprintf("unknown address family"); + break; + } + + return 0; +} + +static void lldp_print_cap(uint16_t cap) +{ + unsigned int prev = 0; + + if (cap & LLDP_SYSTEM_CAP_OTHER) + tprintf("%sOther", prev++ ? ", " : ""); + if (cap & LLDP_SYSTEM_CAP_REPEATER) + tprintf("%sRepeater", prev++ ? ", " : ""); + if (cap & LLDP_SYSTEM_CAP_BRIDGE) + tprintf("%sBridge", prev++ ? ", " : ""); + if (cap & LLDP_SYSTEM_CAP_WLAN_AP) + tprintf("%sWLAN AP", prev++ ? ", " : ""); + if (cap & LLDP_SYSTEM_CAP_ROUTER) + tprintf("%sRouter", prev++ ? ", " : ""); + if (cap & LLDP_SYSTEM_CAP_TELEPHONE) + tprintf("%sTelephone", prev++ ? ", " : ""); + if (cap & LLDP_SYSTEM_CAP_DOCSIS) + tprintf("%sDOCSIS", prev++ ? ", " : ""); + if (cap & LLDP_SYSTEM_CAP_STATION_ONLY) + tprintf("%sStation only", prev++ ? ", " : ""); +} + +static void lldp(struct pkt_buff *pkt) +{ + unsigned int n_tlv = 0; + uint8_t subtype, mgmt_alen, mgmt_oidlen; + uint16_t tlv_hdr; + unsigned int tlv_type, tlv_len; + unsigned int len; + uint8_t *tlv_info_str; + uint16_t sys_cap, en_cap; + uint32_t oui; + + len = pkt_len(pkt); + if (len == 0) + return; + + tprintf(" [ LLDP "); + + while (len >= sizeof(tlv_hdr)) { + tlv_hdr = EXTRACT_16BIT(pkt_pull(pkt, sizeof(tlv_hdr))); + tlv_type = LLDP_TLV_TYPE(tlv_hdr); + tlv_len = LLDP_TLV_LENGTH(tlv_hdr); + + len -= sizeof(tlv_hdr); + + if (tlv_type == LLDP_TLV_END && tlv_len == 0) { + /* Chassis ID, Port ID and TTL are mandatory */ + if (n_tlv < 3) + goto out_invalid; + else + break; + } + if (len < tlv_len) + goto out_invalid; + + switch (tlv_type) { + case LLDP_TLV_CHASSIS_ID: + /* + * The mandatory chassis ID shall be the first TLV and + * shall appear exactly once. + */ + if (n_tlv != 0) + goto out_invalid; + + tprintf("Chassis ID"); + + if (tlv_len < 2) + goto out_invalid; + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + goto out_invalid; + + subtype = *tlv_info_str++; + tprintf(" (Subtype %u => ", subtype); + + switch (subtype) { + case LLDP_CHASSIS_SUBTYPE_MAC_ADDR: + if (tlv_len < 7) + goto out_invalid; + + tprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + tlv_info_str[0], tlv_info_str[1], + tlv_info_str[2], tlv_info_str[3], + tlv_info_str[4], tlv_info_str[5]); + break; + case LLDP_CHASSIS_SUBTYPE_NET_ADDR: + if (lldp_print_net_addr(tlv_info_str, tlv_len)) + goto out_invalid; + break; + case LLDP_CHASSIS_SUBTYPE_CHASSIS: + case LLDP_CHASSIS_SUBTYPE_IF_ALIAS: + case LLDP_CHASSIS_SUBTYPE_PORT: + case LLDP_CHASSIS_SUBTYPE_IF_NAME: + case LLDP_CHASSIS_SUBTYPE_LOCAL: + tputs_safe((const char *) tlv_info_str, tlv_len - 1); + break; + default: + tprintf("Reserved"); + break; + } + + tprintf(")"); + break; + case LLDP_TLV_PORT_ID: + /* + * The mandatory port ID shall be the second TLV and + * shall appear exactly once. + */ + if (n_tlv != 1) + goto out_invalid; + + tprintf(", Port ID"); + + if (tlv_len < 2) + goto out_invalid; + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + goto out_invalid; + + subtype = *tlv_info_str++; + tprintf(" (Subtype %u => ", subtype); + + switch (subtype) { + case LLDP_PORT_SUBTYPE_MAC_ADDR: + if (tlv_len < 7) + goto out_invalid; + + tprintf("%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", + tlv_info_str[0], tlv_info_str[1], + tlv_info_str[2], tlv_info_str[3], + tlv_info_str[4], tlv_info_str[5]); + break; + case LLDP_PORT_SUBTYPE_NET_ADDR: + if (lldp_print_net_addr(tlv_info_str, tlv_len)) + goto out_invalid; + break; + case LLDP_PORT_SUBTYPE_IF_ALIAS: + case LLDP_PORT_SUBTYPE_PORT_COMP: + case LLDP_PORT_SUBTYPE_IF_NAME: + case LLDP_PORT_SUBTYPE_AGENT_CIRC_ID: + case LLDP_PORT_SUBTYPE_LOCAL: + tputs_safe((const char *) tlv_info_str, tlv_len - 1); + break; + default: + tprintf("Reserved"); + break; + } + + tprintf(")"); + break; + case LLDP_TLV_TTL: + /* + * The mandatory TTL shall be the third TLV and + * shall appear exactly once. + */ + if (n_tlv != 2) + goto out_invalid; + + tprintf(", TTL"); + + if (tlv_len != 2) + goto out_invalid; + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + goto out_invalid; + + tprintf(" (%u)", EXTRACT_16BIT(tlv_info_str)); + break; + case LLDP_TLV_PORT_DESC: + tprintf(", Port desc ("); + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + tprintf("none"); + else + tputs_safe((const char *) tlv_info_str, tlv_len); + + tprintf(")"); + break; + case LLDP_TLV_SYSTEM_NAME: + tprintf(", Sys name ("); + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + tprintf("none"); + else + tputs_safe((const char *) tlv_info_str, tlv_len); + + tprintf(")"); + break; + case LLDP_TLV_SYSTEM_DESC: + tprintf(", Sys desc ("); + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + tprintf("none"); + else + tputs_safe((const char *) tlv_info_str, tlv_len); + + tprintf(")"); + break; + case LLDP_TLV_SYSTEM_CAP: + tprintf(", Sys Cap"); + + if (tlv_len != 4) + goto out_invalid; + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + goto out_invalid; + + sys_cap = EXTRACT_16BIT(tlv_info_str); + tlv_info_str += sizeof(uint32_t); + en_cap = EXTRACT_16BIT(tlv_info_str); + + tprintf(" ("); + lldp_print_cap(sys_cap); + tprintf(")"); + tprintf(" Ena Cap ("); + lldp_print_cap(en_cap); + tprintf(")"); + break; + case LLDP_TLV_MGMT_ADDR: + tprintf(", Mgmt Addr ("); + + if (tlv_len < 9 || tlv_len > 167) + goto out_invalid; + + tlv_info_str = pkt_pull(pkt, tlv_len); + if (tlv_info_str == NULL) + goto out_invalid; + + mgmt_alen = *tlv_info_str; + tlv_info_str++; + if (tlv_len - 1 < mgmt_alen) + goto out_invalid; + + if (lldp_print_net_addr(tlv_info_str, mgmt_alen)) + goto out_invalid; + tlv_info_str += mgmt_alen; + + tprintf(", Iface Subtype %d/", *tlv_info_str); + switch (*tlv_info_str) { + case LLDP_IFACE_NUM_SUBTYPE_IF_INDEX: + tprintf("ifIndex"); + break; + case LLDP_IFACE_NUM_SUBTYPE_SYS_PORT: + tprintf("System Port Number"); + break; + default: + tprintf("Unknown"); + break; + } + + tlv_info_str++; + tprintf(", Iface Number %u", EXTRACT_32BIT(tlv_info_str)); + + tlv_info_str += 4; + mgmt_oidlen = *tlv_info_str; + if (tlv_len - mgmt_alen - sizeof(uint32_t) - 3 < mgmt_oidlen) + goto out_invalid; + if (mgmt_oidlen > 0) { + tprintf(", OID "); + tputs_safe((const char *) tlv_info_str + 1, mgmt_oidlen); + } + + tprintf(")"); + break; + case LLDP_TLV_ORG_SPECIFIC: + tprintf(", Org specific"); + + if (tlv_len < 4) + goto out_invalid; + + tlv_info_str = pkt_pull(pkt, 4); + if (tlv_info_str == NULL) + goto out_invalid; + + oui = ntohl(*((uint32_t *) tlv_info_str)); + subtype = oui & 0xff; + oui >>= 8; + tprintf(" (OUI %s, Subtype %u)", lookup_vendor_str(oui), + subtype); + + /* Just eat it up, we don't know how to interpret it */ + pkt_pull(pkt, tlv_len - 4); + break; + default: + tprintf(", Unknown TLV %u", tlv_type); + pkt_pull(pkt, tlv_len); + break; + } + + n_tlv++; + } + + len -= tlv_len; + + tprintf(" ]\n"); + return; + +out_invalid: + tprintf(" %sINVALID%s ]\n", colorize_start_full(black, red), + colorize_end()); +} + +static void lldp_less(struct pkt_buff *pkt) +{ + unsigned int len, n_tlv = 0; + unsigned int tlv_type, tlv_len; + uint16_t tlv_hdr; + + len = pkt_len(pkt); + + while (len >= sizeof(tlv_hdr)) { + tlv_hdr = EXTRACT_16BIT(pkt_pull(pkt, sizeof(tlv_hdr))); + tlv_type = LLDP_TLV_TYPE(tlv_hdr); + tlv_len = LLDP_TLV_LENGTH(tlv_hdr); + + n_tlv++; + len -= sizeof(tlv_hdr); + + if (tlv_type == LLDP_TLV_END || tlv_len == 0) + break; + if (len < tlv_len) + break; + + pkt_pull(pkt, tlv_len); + + len -= tlv_len; + } + + tprintf(" %u TLV%s", n_tlv, n_tlv == 1 ? "" : "s"); +} + +struct protocol lldp_ops = { + .key = 0x88cc, + .print_full = lldp, + .print_less = lldp_less, +}; |