如何利用Delphi通过MAC地址获取网络设备的IP地址?
用Delphi从MAC地址获取对应IP地址的实现方案
核心逻辑
通过MAC查IP,本质是发送标准ARP请求数据包(目标MAC为待查询设备的MAC,以广播形式发送),再监听网络中的ARP响应,从中提取对应IP。注意:ARP属于链路层协议,不能通过UDP/TCP套接字发送,必须用原始套接字直接构造链路层数据包。
Delphi实现步骤
1. 构造标准ARP请求结构
标准ARP请求的数据包分为以太网帧头和ARP数据段两部分,完全匹配路由器发送的合法ARP包格式:
- 以太网帧头:
- 目标MAC:广播地址
FF:FF:FF:FF:FF:FF - 源MAC:本地网卡的MAC地址
- 类型:
0x0806(标识ARP协议)
- 目标MAC:广播地址
- ARP数据段:
- 硬件类型:
0x0001(以太网) - 协议类型:
0x0800(IPv4) - 硬件地址长度:
6 - 协议地址长度:
4 - 操作码:
0x0001(ARP请求) - 发送端MAC:本地网卡MAC
- 发送端IP:本地网卡IP
- 目标端MAC:待查询的设备MAC
- 目标端IP:
0.0.0.0(反向查询时IP未知)
- 硬件类型:
2. 用原始套接字发送请求并监听响应
调用Windows API创建原始套接字,设置权限后发送构造好的ARP包,再监听网络接口上的ARP响应,筛选出目标设备返回的数据包,提取对应IP。
核心代码示例
需引入WinSock2和Windows单元:
type TEthernetHeader = packed record DestMAC: array[0..5] of Byte; SrcMAC: array[0..5] of Byte; EtherType: Word; end; TARPHeader = packed record HardwareType: Word; ProtocolType: Word; HardwareAddrLen: Byte; ProtocolAddrLen: Byte; Operation: Word; SenderMAC: array[0..5] of Byte; SenderIP: array[0..3] of Byte; TargetMAC: array[0..5] of Byte; TargetIP: array[0..3] of Byte; end; TARPPacket = packed record Ethernet: TEthernetHeader; ARP: TARPHeader; end; // 构造ARP请求数据包 function BuildARPRequest(const LocalMAC, TargetMAC: array of Byte; LocalIP: DWORD): TARPPacket; begin // 填充以太网帧头 FillChar(Ethernet.DestMAC, 6, $FF); // 广播MAC Move(LocalMAC[0], Ethernet.SrcMAC, 6); Ethernet.EtherType := htons($0806); // ARP协议标识 // 填充ARP数据段 ARP.HardwareType := htons($0001); ARP.ProtocolType := htons($0800); ARP.HardwareAddrLen := 6; ARP.ProtocolAddrLen := 4; ARP.Operation := htons($0001); // ARP请求操作码 Move(LocalMAC[0], ARP.SenderMAC, 6); ARP.SenderIP[0] := (LocalIP shr 24) and $FF; ARP.SenderIP[1] := (LocalIP shr 16) and $FF; ARP.SenderIP[2] := (LocalIP shr 8) and $FF; ARP.SenderIP[3] := LocalIP and $FF; Move(TargetMAC[0], ARP.TargetMAC, 6); FillChar(ARP.TargetIP, 4, 0); // 目标IP未知,填充0 end; // 发送ARP请求并获取目标IP function GetIPFromMAC(const TargetMAC: array of Byte; const LocalIP: DWORD; LocalMAC: array of Byte): string; var Sock: TSocket; WSAData: TWSAData; Addr: TSockAddr; Packet: TARPPacket; RecvBuf: array[0..1023] of Byte; RecvLen: Integer; ResponseARP: ^TARPHeader; begin Result := ''; if WSAStartup(MAKEWORD(2, 2), WSAData) <> 0 then Exit; try // 创建原始套接字 Sock := WSASocket(AF_INET, SOCK_RAW, IPPROTO_RAW, nil, 0, WSA_FLAG_OVERLAPPED); if Sock = INVALID_SOCKET then Exit; try // 允许发送广播包 var BroadcastOpt: BOOL := True; setsockopt(Sock, SOL_SOCKET, SO_BROADCAST, @BroadcastOpt, SizeOf(BroadcastOpt)); // 禁用系统自动添加IP头 var HDRIncl: BOOL := True; setsockopt(Sock, IPPROTO_IP, IP_HDRINCL, @HDRIncl, SizeOf(HDRIncl)); // 构造ARP请求包 Packet := BuildARPRequest(LocalMAC, TargetMAC, LocalIP); // 设置广播发送地址 FillChar(Addr, SizeOf(Addr), 0); Addr.sin_family := AF_INET; Addr.sin_addr.S_addr := INADDR_BROADCAST; Addr.sin_port := 0; // 发送ARP请求 sendto(Sock, @Packet, SizeOf(Packet), 0, @Addr, SizeOf(Addr)); // 设置接收超时(5秒) var Timeout: Integer := 5000; setsockopt(Sock, SOL_SOCKET, SO_RCVTIMEO, @Timeout, SizeOf(Timeout)); // 监听ARP响应 while True do begin RecvLen := recvfrom(Sock, @RecvBuf, SizeOf(RecvBuf), 0, nil, nil); if RecvLen <= 0 then Break; // 判断是否为ARP数据包 var EtherHeader: ^TEthernetHeader := @RecvBuf; if ntohs(EtherHeader.EtherType) = $0806 then begin ResponseARP := @RecvBuf[SizeOf(TEthernetHeader)]; // 筛选目标设备的ARP响应 if (ntohs(ResponseARP.Operation) = $0002) and // ARP响应操作码 (CompareMem(@ResponseARP.SenderMAC, @TargetMAC, 6)) then begin // 格式化IP地址 Result := Format('%d.%d.%d.%d', [ ResponseARP.SenderIP[0], ResponseARP.SenderIP[1], ResponseARP.SenderIP[2], ResponseARP.SenderIP[3] ]); Break; end; end; end; finally closesocket(Sock); end; finally WSACleanup; end; end;
关键注意事项
- 程序需以管理员权限运行,原始套接字需要系统权限。
- 要正确获取本地网卡的MAC和IP,可通过
GetAdaptersInfoAPI实现。 - 你之前的错误在于用UDP发送ARP包:UDP是传输层协议,ARP属于链路层,必须直接构造链路层数据包发送。
内容的提问来源于stack exchange,提问作者bluscape
相关产品推荐
相关产品推荐

