如何在C中构造和ARP请求包
How to structure and ARP request packet in C
今天我花了更多时间来了解 ARP 数据包。为了理解它的结构,我尝试使用 libpcap
在 C 中自己构建一个。我构建了一个简单的 ARP 请求数据包,并使用 pcap_inject
函数发送数据包。此函数 returns 发送的字节数。
当我调试我的代码时,我发现我的数据包有 42 个字节长。我在 Internet 上搜索了一下,但找不到告诉我这是否适合 ARP 请求的大小的答案。甚至 wikipedia entry 也让我有些困惑。我发现了这个 post。来自用户提供的答案:
- If the ARP message is to be sent in an untagged frame then the frame overhead itself is 18 bytes. That would result in a frame of
28+18=46 bytes without padding. Additional 18 bytes of padding are
necessary in this case to bloat the frame to the 64 byte length.
- If the ARP message is to be sent in an 802.1Q-tagged frame then the frame overhead is 22 bytes, resulting in the total frame size of
28+22=50 bytes. In this case, the padding needs to be 14 bytes long.
- If the ARP message is to be sent in a double-tagged frame then the frame overhead is 26 bytes, resulting in the total frame size of 54
bytes. In this case, the padding needs to be 10 bytes long.
我的问题是在这种情况下我必须做什么。我是否必须使用填充?
下面是我 post 我的数据包的结构。
#define ETH_P_ARP 0x0806 /* Address Resolution packet */
#define ARP_HTYPE_ETHER 1 /* Ethernet ARP type */
#define ARP_PTYPE_IPv4 0x0800 /* Internet Protocol packet */
/* Ethernet frame header */
typedef struct {
uint8_t dest_addr[ETH_ALEN]; /* Destination hardware address */
uint8_t src_addr[ETH_ALEN]; /* Source hardware address */
uint16_t frame_type; /* Ethernet frame type */
} ether_hdr;
/* Ethernet ARP packet from RFC 826 */
typedef struct {
uint16_t htype; /* Format of hardware address */
uint16_t ptype; /* Format of protocol address */
uint8_t hlen; /* Length of hardware address */
uint8_t plen; /* Length of protocol address */
uint16_t op; /* ARP opcode (command) */
uint8_t sha[ETH_ALEN]; /* Sender hardware address */
uint32_t spa; /* Sender IP address */
uint8_t tha[ETH_ALEN]; /* Target hardware address */
uint32_t tpa; /* Target IP address */
} arp_ether_ipv4;
最后我只是按照下面的顺序复制每个结构成员并发送数据包:
void packageARP(unsigned char *buffer, ether_hdr *frameHeader, arp_ether_ipv4 *arp_packet, size_t *bufferSize) {
unsigned char *cp;
size_t packet_size;
cp = buffer;
packet_size = sizeof(frameHeader->dest_addr)
+ sizeof(frameHeader->src_addr)
+ sizeof(frameHeader->frame_type)
+ sizeof(arp_packet->htype)
+ sizeof(arp_packet->ptype)
+ sizeof(arp_packet->hlen)
+ sizeof(arp_packet->plen)
+ sizeof(arp_packet->op)
+ sizeof(arp_packet->sha)
+ sizeof(arp_packet->spa)
+ sizeof(arp_packet->tha)
+ sizeof(arp_packet->tpa);
/*
* Copy the Ethernet frame header to the buffer.
*/
memcpy(cp, &(frameHeader->dest_addr), sizeof(frameHeader->dest_addr));
cp += sizeof(frameHeader->dest_addr);
memcpy(cp, &(frameHeader->src_addr), sizeof(frameHeader->src_addr));
cp += sizeof(frameHeader->src_addr);
/* Normal Ethernet-II framing */
memcpy(cp, &(frameHeader->frame_type), sizeof(frameHeader->frame_type));
cp += sizeof(frameHeader->frame_type);
/*
* Add the ARP data.
*/
memcpy(cp, &(arp_packet->htype), sizeof(arp_packet->htype));
cp += sizeof(arp_packet->htype);
memcpy(cp, &(arp_packet->ptype), sizeof(arp_packet->ptype));
cp += sizeof(arp_packet->ptype);
memcpy(cp, &(arp_packet->hlen), sizeof(arp_packet->hlen));
cp += sizeof(arp_packet->hlen);
memcpy(cp, &(arp_packet->plen), sizeof(arp_packet->plen));
cp += sizeof(arp_packet->plen);
memcpy(cp, &(arp_packet->op), sizeof(arp_packet->op));
cp += sizeof(arp_packet->op);
memcpy(cp, &(arp_packet->sha), sizeof(arp_packet->sha));
cp += sizeof(arp_packet->sha);
memcpy(cp, &(arp_packet->spa), sizeof(arp_packet->spa));
cp += sizeof(arp_packet->spa);
memcpy(cp, &(arp_packet->tha), sizeof(arp_packet->tha));
cp += sizeof(arp_packet->tha);
memcpy(cp, &(arp_packet->tpa), sizeof(arp_packet->tpa));
cp += sizeof(arp_packet->tpa);
*bufferSize = packet_size;
}
这是构造 ARP 请求数据包的正确方法吗?
这是正确的结构 -- 除了 C 编译器可以自由插入填充以确保结构成员位于最有效的边界。特别是,spa
和 tpa
不在自然的 32 位边界(由于前面的 6 字节 MAC 地址字段),因此编译器可能想要插入两个字节的填充在每个之前。
如果您使用的是 gcc,您可以确保 __attribute__((packed))
不会发生这种情况:
struct {
[fields]
} __attribute__((packed)) arp_ether_ipv4;
其他编译器可能有不同但等效的机制(例如 #pragma
指令)。
ARP 负载应为 28 字节。添加 14 字节的以太网 header,总共有 42 个字节。正如您引用的那样,802.1Q (VLAN) header 插入额外的 4 个字节,而 "double-tagged" 帧(在 Internet 服务提供商之外不常见)将添加 2 X 4 = 8 个字节。如果您使用的是普通端点计算机,通常不会添加这些 header。 IT 部门会将您的交换机配置为根据需要自动 insert/remove 这些 header。
您的网络 driver 将 自动 将 42 字节填充到 64 字节(以太网最小数据包大小)。 64 实际上是 60 + 4 字节以太网 FCS [帧校验和]。 (你引用的 post 显然在他们的计算中包括了 4 字节的 FCS,这就是为什么他们的数字看起来很糟糕。)
此外,不要忘记对所有 uint16_t
和 uint32_t
字段使用网络字节顺序:(ntohs
和 ntohl
)
今天我花了更多时间来了解 ARP 数据包。为了理解它的结构,我尝试使用 libpcap
在 C 中自己构建一个。我构建了一个简单的 ARP 请求数据包,并使用 pcap_inject
函数发送数据包。此函数 returns 发送的字节数。
当我调试我的代码时,我发现我的数据包有 42 个字节长。我在 Internet 上搜索了一下,但找不到告诉我这是否适合 ARP 请求的大小的答案。甚至 wikipedia entry 也让我有些困惑。我发现了这个 post。来自用户提供的答案:
- If the ARP message is to be sent in an untagged frame then the frame overhead itself is 18 bytes. That would result in a frame of 28+18=46 bytes without padding. Additional 18 bytes of padding are necessary in this case to bloat the frame to the 64 byte length.
- If the ARP message is to be sent in an 802.1Q-tagged frame then the frame overhead is 22 bytes, resulting in the total frame size of 28+22=50 bytes. In this case, the padding needs to be 14 bytes long.
- If the ARP message is to be sent in a double-tagged frame then the frame overhead is 26 bytes, resulting in the total frame size of 54 bytes. In this case, the padding needs to be 10 bytes long.
我的问题是在这种情况下我必须做什么。我是否必须使用填充?
下面是我 post 我的数据包的结构。
#define ETH_P_ARP 0x0806 /* Address Resolution packet */
#define ARP_HTYPE_ETHER 1 /* Ethernet ARP type */
#define ARP_PTYPE_IPv4 0x0800 /* Internet Protocol packet */
/* Ethernet frame header */
typedef struct {
uint8_t dest_addr[ETH_ALEN]; /* Destination hardware address */
uint8_t src_addr[ETH_ALEN]; /* Source hardware address */
uint16_t frame_type; /* Ethernet frame type */
} ether_hdr;
/* Ethernet ARP packet from RFC 826 */
typedef struct {
uint16_t htype; /* Format of hardware address */
uint16_t ptype; /* Format of protocol address */
uint8_t hlen; /* Length of hardware address */
uint8_t plen; /* Length of protocol address */
uint16_t op; /* ARP opcode (command) */
uint8_t sha[ETH_ALEN]; /* Sender hardware address */
uint32_t spa; /* Sender IP address */
uint8_t tha[ETH_ALEN]; /* Target hardware address */
uint32_t tpa; /* Target IP address */
} arp_ether_ipv4;
最后我只是按照下面的顺序复制每个结构成员并发送数据包:
void packageARP(unsigned char *buffer, ether_hdr *frameHeader, arp_ether_ipv4 *arp_packet, size_t *bufferSize) {
unsigned char *cp;
size_t packet_size;
cp = buffer;
packet_size = sizeof(frameHeader->dest_addr)
+ sizeof(frameHeader->src_addr)
+ sizeof(frameHeader->frame_type)
+ sizeof(arp_packet->htype)
+ sizeof(arp_packet->ptype)
+ sizeof(arp_packet->hlen)
+ sizeof(arp_packet->plen)
+ sizeof(arp_packet->op)
+ sizeof(arp_packet->sha)
+ sizeof(arp_packet->spa)
+ sizeof(arp_packet->tha)
+ sizeof(arp_packet->tpa);
/*
* Copy the Ethernet frame header to the buffer.
*/
memcpy(cp, &(frameHeader->dest_addr), sizeof(frameHeader->dest_addr));
cp += sizeof(frameHeader->dest_addr);
memcpy(cp, &(frameHeader->src_addr), sizeof(frameHeader->src_addr));
cp += sizeof(frameHeader->src_addr);
/* Normal Ethernet-II framing */
memcpy(cp, &(frameHeader->frame_type), sizeof(frameHeader->frame_type));
cp += sizeof(frameHeader->frame_type);
/*
* Add the ARP data.
*/
memcpy(cp, &(arp_packet->htype), sizeof(arp_packet->htype));
cp += sizeof(arp_packet->htype);
memcpy(cp, &(arp_packet->ptype), sizeof(arp_packet->ptype));
cp += sizeof(arp_packet->ptype);
memcpy(cp, &(arp_packet->hlen), sizeof(arp_packet->hlen));
cp += sizeof(arp_packet->hlen);
memcpy(cp, &(arp_packet->plen), sizeof(arp_packet->plen));
cp += sizeof(arp_packet->plen);
memcpy(cp, &(arp_packet->op), sizeof(arp_packet->op));
cp += sizeof(arp_packet->op);
memcpy(cp, &(arp_packet->sha), sizeof(arp_packet->sha));
cp += sizeof(arp_packet->sha);
memcpy(cp, &(arp_packet->spa), sizeof(arp_packet->spa));
cp += sizeof(arp_packet->spa);
memcpy(cp, &(arp_packet->tha), sizeof(arp_packet->tha));
cp += sizeof(arp_packet->tha);
memcpy(cp, &(arp_packet->tpa), sizeof(arp_packet->tpa));
cp += sizeof(arp_packet->tpa);
*bufferSize = packet_size;
}
这是构造 ARP 请求数据包的正确方法吗?
这是正确的结构 -- 除了 C 编译器可以自由插入填充以确保结构成员位于最有效的边界。特别是,spa
和 tpa
不在自然的 32 位边界(由于前面的 6 字节 MAC 地址字段),因此编译器可能想要插入两个字节的填充在每个之前。
如果您使用的是 gcc,您可以确保 __attribute__((packed))
不会发生这种情况:
struct {
[fields]
} __attribute__((packed)) arp_ether_ipv4;
其他编译器可能有不同但等效的机制(例如 #pragma
指令)。
ARP 负载应为 28 字节。添加 14 字节的以太网 header,总共有 42 个字节。正如您引用的那样,802.1Q (VLAN) header 插入额外的 4 个字节,而 "double-tagged" 帧(在 Internet 服务提供商之外不常见)将添加 2 X 4 = 8 个字节。如果您使用的是普通端点计算机,通常不会添加这些 header。 IT 部门会将您的交换机配置为根据需要自动 insert/remove 这些 header。
您的网络 driver 将 自动 将 42 字节填充到 64 字节(以太网最小数据包大小)。 64 实际上是 60 + 4 字节以太网 FCS [帧校验和]。 (你引用的 post 显然在他们的计算中包括了 4 字节的 FCS,这就是为什么他们的数字看起来很糟糕。)
此外,不要忘记对所有 uint16_t
和 uint32_t
字段使用网络字节顺序:(ntohs
和 ntohl
)