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地址。
Chassis Subtype 的字段
Chassis Subtype TLV 包含以下几个字段:
- TLV 类型(Type):表示这是一个 Chassis Subtype TLV。
- TLV 长度(Length):表示该 TLV 的总长度。
- 子类型(Subtype):指定 Chassis Subtype 的类型,常见的子类型包括:
- 1:MAC 地址:表示使用设备的 MAC 地址作为标识。
- 2:网络地址:使用网络地址(如 IP 地址)作为标识。
- 3:主机名:使用设备的主机名作为标识。
- 4:UUID:使用通用唯一标识符(UUID)作为标识。
- 5:描述:使用一个描述性字符串作为标识。
- 子类型值(Subtype Value):具体的标识值,根据子类型不同,这个值也不同。例如,如果子类型是 MAC 地址,那么子类型值就是一个具体的 MAC 地址。
使用示例
假设我们有一台设备,其 MAC 地址为 00:1A:2B:3C:4D:5E
。它将使用 Chassis Subtype TLV 来广播它的 MAC 地址。
TLV 结构:
- Type: 1 (Chassis Subtype)
- Length: 7
- Subtype: 1 (MAC Address)
- Subtype Value: 00:1A:2B:3C:4D:5E
自定义字段
除了标准的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 = True
thread.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 header
while i < len(payload):
tlv_type = (payload[i] >> 1) & 0x7F
tlv_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_len
self.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 socket
if ((sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {
perror("socket");
exit(EXIT_FAILURE);
}
// Get the interface index
memset(&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 interface
if (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 address
uint8_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 TLV
uint8_t chassisID[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
addTLV(lldpdu, &offset, 1, sizeof(chassisID), chassisID);
// Add Port ID TLV
uint8_t portID[] = { 'E', 't', 'h', '0' };
addTLV(lldpdu, &offset, 2, sizeof(portID), portID);
// Add TTL TLV
uint8_t ttl[] = { 0x00, 0x78 }; // 120 seconds
addTLV(lldpdu, &offset, 3, sizeof(ttl), ttl);
// Add End TLV
addTLV(lldpdu, &offset, 0, 0, NULL);
// Construct Ethernet frame
memcpy(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_ll
memset(&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 packet
if (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功能,希望对相关技术的学习和应用有所帮助。