LLDP使用技术报告
背景
链路层发现协议(Link Layer Discovery Protocol,LLDP)是一种网络协议,主要用于在以太网网络中发现相邻设备并与其交换信息。LLDP是一种开放的标准,由IEEE 802.1AB定义,能够跨多个厂商的设备进行互操作。它的主要功能是帮助网络管理员识别网络拓扑结构,便于管理和故障排除。
LLDP在网络设备之间周期性地发送和接收LLDP数据单元(LLDPDU),这些数据单元包含设备的配置信息和能力。每个LLDPDU由多个TLV(Type-Length-Value)字段组成,每个字段包含特定类型的信息。
最常用的字段
LLDPDU中的常用TLV字段包括:
- Chassis ID TLV:识别设备底盘的唯一标识符,通常是设备的MAC地址。
- Port ID TLV:标识发送LLDPDU的端口,可以是端口的MAC地址或端口号。
- Time to Live (TTL) TLV:指定LLDP信息的生存时间,单位为秒。
- Port Description TLV:描述端口的信息,如端口名称或类型。
- System Name TLV:设备的主机名。
- System Description TLV:设备的详细描述,包括制造商、型号和操作系统版本等信息。
- System Capabilities TLV:设备的功能和当前启用的功能,如交换机、路由器等。
- Management Address TLV:设备的管理地址,可以是IPv4或IPv6地址。
自定义字段
除了标准的TLV字段外,LLDP还允许使用自定义TLV字段。这些字段可以携带特定的厂商信息或特定应用的配置信息。定义自定义TLV字段时,需要确保Type值在特定范围内,以避免与标准字段冲突。
实验设备
实验设备包括一台PC和一台嵌入式Linux设备。在本次实验中,我们将演示如何在PC端使用Python Qt实现设备发现功能,并在嵌入式Linux设备上使用C语言发送设备信息。
PC端代码 (Python Qt)
import sys
import threading
from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QTextEdit
from scapy.all import *
from scapy.layers.l2 import Ether# Define LLDP layer
class LLDPDU(Packet):name = "LLDPDU"fields_desc = [ByteField("type", 0),ByteField("length", 0),StrLenField("value", "", length_from=lambda pkt:pkt.length)]class SnifferThread(QObject):packet_received = pyqtSignal(str)def start_sniffing(self):thread = threading.Thread(target=self.sniff_packets)thread.daemon = Truethread.start()def sniff_packets(self):print("Starting packet sniffing...")sniff(filter="ether proto 0x88cc", prn=self.process_packet, store=0)print("Packet sniffing stopped.")def process_packet(self, packet):if packet.haslayer(Ether) and packet[Ether].type == 0x88cc:info = f"LLDP packet received from {packet.src}\n"payload = bytes(packet.payload)i = 14 # skip Ethernet headerwhile i < len(payload):tlv_type = (payload[i] >> 1) & 0x7Ftlv_len = (payload[i] & 0x01) << 8 | payload[i + 1]tlv_value = payload[i + 2: i + 2 + tlv_len]info += f"TLV Type: {tlv_type}, Length: {tlv_len}, Value: {tlv_value}\n"i += 2 + tlv_lenself.packet_received.emit(info)class LLDPDiscoveryApp(QMainWindow):def __init__(self):super().__init__()self.setWindowTitle("LLDP Discovery")self.setGeometry(100, 100, 600, 400)self.textEdit = QTextEdit(self)self.setCentralWidget(self.textEdit)self.sniffer_thread = SnifferThread()self.sniffer_thread.packet_received.connect(self.display_packet_info)self.sniffer_thread.start_sniffing()def display_packet_info(self, info):self.textEdit.append(info)def main():app = QApplication(sys.argv)window = LLDPDiscoveryApp()window.show()sys.exit(app.exec_())if __name__ == "__main__":main()
嵌入式Linux设备端代码 (C语言)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//#include <netinet/if_ether.h>
//#include <netpacket/packet.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <sys/ioctl.h>#define MAX_TLV_SIZE 512
#define LLDP_MULTICAST_ADDR "01:80:C2:00:00:0E"typedef struct {uint8_t type;uint8_t length;uint8_t value[MAX_TLV_SIZE];
} TLV;void addTLV(uint8_t *buffer, size_t *offset, uint8_t type, uint8_t length, uint8_t *value) {buffer[*offset] = (type << 1) | ((length >> 8) & 0x01);buffer[*offset + 1] = length & 0xFF;memcpy(&buffer[*offset + 2], value, length);*offset += length + 2;
}void sendLLDPDU(char *interface) {int sockfd;struct ifreq ifr;struct sockaddr_ll sa;uint8_t lldpdu[MAX_TLV_SIZE];uint8_t ether_frame[MAX_TLV_SIZE + ETH_HLEN];size_t offset = 0;// Create a raw socketif ((sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {perror("socket");exit(EXIT_FAILURE);}// Get the interface indexmemset(&ifr, 0, sizeof(ifr));strncpy(ifr.ifr_name, interface, IFNAMSIZ - 1);if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {perror("ioctl");close(sockfd);exit(EXIT_FAILURE);}int ifindex = ifr.ifr_ifindex;// Get the MAC address of the interfaceif (ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) {perror("ioctl");close(sockfd);exit(EXIT_FAILURE);}uint8_t *src_mac = (uint8_t *)ifr.ifr_hwaddr.sa_data;// Set up destination MAC addressuint8_t dst_mac[6];sscanf(LLDP_MULTICAST_ADDR, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",&dst_mac[0], &dst_mac[1], &dst_mac[2],&dst_mac[3], &dst_mac[4], &dst_mac[5]);// Construct LLDPDU// Add Chassis ID TLVuint8_t chassisID[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };addTLV(lldpdu, &offset, 1, sizeof(chassisID), chassisID);// Add Port ID TLVuint8_t portID[] = { 'E', 't', 'h', '0' };addTLV(lldpdu, &offset, 2, sizeof(portID), portID);// Add TTL TLVuint8_t ttl[] = { 0x00, 0x78 }; // 120 secondsaddTLV(lldpdu, &offset, 3, sizeof(ttl), ttl);// Add End TLVaddTLV(lldpdu, &offset, 0, 0, NULL);// Construct Ethernet framememcpy(ether_frame, dst_mac, 6);memcpy(ether_frame + 6, src_mac, 6);ether_frame[12] = 0x88;ether_frame[13] = 0xcc;memcpy(ether_frame + ETH_HLEN, lldpdu, offset);// Set up sockaddr_llmemset(&sa, 0, sizeof(sa));sa.sll_family = AF_PACKET;sa.sll_ifindex = ifindex;sa.sll_protocol = htons(ETH_P_ALL);sa.sll_halen = ETH_ALEN;memcpy(sa.sll_addr, dst_mac, 6);// Send the packetif (sendto(sockfd, ether_frame, offset + ETH_HLEN, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) {perror("sendto");close(sockfd);exit(EXIT_FAILURE);}close(sockfd);
}int main() {char *interface = "eth0";sendLLDPDU(interface);return 0;
}
总结
通过使用LLDP协议,可以有效地发现网络中的相邻设备并交换信息,这对于网络管理和故障排除具有重要意义。本次实验展示了如何在PC和嵌入式设备上实现LLDP功能,希望对相关技术的学习和应用有所帮助。