如何在C#中构建RARP数据包并响应服务器RARP请求?
在C#中构建RARP响应数据包的完整方案
我明白你现在的困境——RARP确实是个几乎被遗忘的协议,相关资料少得可怜,不过别担心,我可以给你梳理清楚在C#里构建RARP响应包的核心步骤和代码实现。
首先得明确RARP数据包的底层结构,这是构建响应的核心:RARP封装在以太网帧中,以太帧类型为0x8035,而RARP包本身的固定结构如下:
- 硬件类型(2字节,以太网对应
0x0001) - 协议类型(2字节,IPv4对应
0x0800) - 硬件地址长度(1字节,MAC地址是6)
- 协议地址长度(1字节,IPv4地址是4)
- 操作码(2字节,
1表示请求,2表示响应) - 发送端硬件地址(6字节)
- 发送端协议地址(4字节)
- 目标硬件地址(6字节)
- 目标协议地址(4字节)
接下来我们用原生C# Socket来实现监听请求和发送响应的逻辑,步骤如下:
1. 定义数据包结构体
用结构体来映射以太网帧头和RARP包,注意设置Pack=1避免字节对齐问题:
using System; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct EthernetHeader { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] DestinationMac; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] SourceMac; public ushort EtherType; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct RarpPacket { public ushort HardwareType; public ushort ProtocolType; public byte HardwareAddrLength; public byte ProtocolAddrLength; public ushort OpCode; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] SenderHardwareAddr; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] SenderProtocolAddr; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public byte[] TargetHardwareAddr; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] TargetProtocolAddr; }
2. 结构体与字节数组互转工具
因为Socket操作的是字节流,需要把结构体和字节数组互相转换:
public static byte[] StructToBytes(object obj) { int size = Marshal.SizeOf(obj); byte[] buffer = new byte[size]; IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.StructureToPtr(obj, ptr, true); Marshal.Copy(ptr, buffer, 0, size); Marshal.FreeHGlobal(ptr); return buffer; } public static T BytesToStruct<T>(byte[] buffer) where T : struct { int size = Marshal.SizeOf(typeof(T)); IntPtr ptr = Marshal.AllocHGlobal(size); Marshal.Copy(buffer, 0, ptr, size); T obj = (T)Marshal.PtrToStructure(ptr, typeof(T)); Marshal.FreeHGlobal(ptr); return obj; }
3. 监听RARP请求并发送响应
核心逻辑是用原始Socket捕获以太网帧,过滤RARP请求,然后构造响应包发送:
public static void StartRarpResponder() { // 必须以管理员权限运行! using (Socket rawSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Raw)) { // 绑定到服务器的网卡IP,替换成你的实际IP IPAddress localIp = IPAddress.Parse("192.168.1.10"); rawSocket.Bind(new IPEndPoint(localIp, 0)); // 配置Socket以接收完整的以太网帧头部 rawSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true); byte[] receiveBuffer = new byte[4096]; Console.WriteLine("Listening for RARP requests..."); while (true) { int bytesReceived = rawSocket.Receive(receiveBuffer); if (bytesReceived < Marshal.SizeOf<EthernetHeader>() + Marshal.SizeOf<RarpPacket>()) continue; // 解析以太网帧头 EthernetHeader ethHeader = BytesToStruct<EthernetHeader>(receiveBuffer); ushort etherType = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(ethHeader.EtherType, 0)); // 过滤非RARP帧 if (etherType != 0x8035) continue; // 解析RARP请求包 int rarpOffset = Marshal.SizeOf<EthernetHeader>(); RarpPacket rarpRequest = BytesToStruct<RarpPacket>(receiveBuffer.AsSpan(rarpOffset).ToArray()); ushort opCode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(rarpRequest.OpCode, 0)); // 只处理RARP请求(操作码=1) if (opCode != 1) continue; // 检查请求的目标MAC是否为全0(根据你的需求) bool isTargetMacAllZero = Array.TrueForAll(rarpRequest.TargetHardwareAddr, b => b == 0); if (!isTargetMacAllZero) continue; // 替换成你的服务器MAC地址 byte[] serverMac = new byte[] { 0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E }; // 根据你的解析方法获取要分配的IP地址 IPAddress assignedIp = IPAddress.Parse("192.168.1.100"); // 构造RARP响应包 RarpPacket rarpReply = new RarpPacket { HardwareType = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)0x0001)), ProtocolType = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)0x0800)), HardwareAddrLength = 6, ProtocolAddrLength = 4, OpCode = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)2)), // 响应操作码 SenderHardwareAddr = serverMac, SenderProtocolAddr = localIp.GetAddressBytes(), TargetHardwareAddr = rarpRequest.SenderHardwareAddr, // 请求端的MAC TargetProtocolAddr = assignedIp.GetAddressBytes() }; // 构造响应的以太网帧头 EthernetHeader replyEthHeader = new EthernetHeader { DestinationMac = rarpRequest.SenderHardwareAddr, SourceMac = serverMac, EtherType = BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)0x8035)) }; // 拼接帧头和RARP包 byte[] ethBytes = StructToBytes(replyEthHeader); byte[] rarpBytes = StructToBytes(rarpReply); byte[] replyPacket = new byte[ethBytes.Length + rarpBytes.Length]; Buffer.BlockCopy(ethBytes, 0, replyPacket, 0, ethBytes.Length); Buffer.BlockCopy(rarpBytes, 0, replyPacket, ethBytes.Length, rarpBytes.Length); // 发送响应 rawSocket.Send(replyPacket); Console.WriteLine($"Sent RARP reply to MAC {BitConverter.ToString(rarpRequest.SenderHardwareAddr).Replace("-", ":")}, assigned IP: {assignedIp}"); } } }
关键注意事项
- 权限要求:程序必须以管理员身份运行,因为原始Socket需要系统级权限。
- 字节序处理:RARP所有多字节字段都使用网络字节序(大端),所以一定要用
IPAddress.HostToNetworkOrder和IPAddress.NetworkToHostOrder进行转换。 - MAC地址获取:你可以通过
System.Net.NetworkInformation.NetworkInterface类自动获取服务器网卡的MAC,避免手动硬编码。 - 替代方案:如果手动拼接字节太繁琐,也可以使用PacketDotNet库(它内置了RARP数据包的解析和构建逻辑),不过原生实现更可控,适合你这种硬性要求的场景。
内容的提问来源于stack exchange,提问作者kdun0508
相关产品推荐
相关产品推荐

