文章目录
继上次 自己实现一个 Ping 命令 之后,尝试进入更底层的网络接口层实现局域网的 ARP 报文收发
ARP 协议概述
ARP(Address Resolution Protocol) 地址解析协议是用来通过网络层地址(IP地址)去寻找数据链路层地址(MAC地址)的网络传输协议.
在以太网(Ethernet)协议中规定,同一局域网中的一台主机要和另一台主机进行直接通信,必须要知道目标主机的 MAC 地址。而在 TCP/IP 协议中,网络层和传输层只关心目标主机的IP地址。这就导致在以太网中使用 IP 协议时,数据链路层的以太网协议接到上层IP协议提供的数据中,只包含目的主机的IP地址。于是需要一种方法,根据目的主机的IP地址,获得其MAC地址。这就是 ARP 协议要做的事情。所谓地址解析(address resolution)就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。另外,当发送主机和目的主机不在同一个局域网中时,即便知道对方的MAC地址,两者也不能直接通信,必须经过路由转发才可以。所以此时,发送主机通过ARP协议获得的将不是目的主机的真实MAC地址,而是一台可以通往局域网外的路由器的MAC地址。于是此后发送主机发往目的主机的所有帧,都将发往该路由器,通过它向外发送。这种情况称为委托ARP或ARP代理(ARP Proxy)。—— 地址解析协议
报文格式
以太网首部: net/ethernet.h
typedef struct ether_header {
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目标以太网地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源以太网地址 */
u_short ether_type; /* 帧类型 */
} ether_header_t;
// ETHER_ADDR_LEN 为 6
ARP 请求/应答: net/if_arp.h
struct arphdr {
u_short ar_hrd; /* 硬件类型 format of hardware address */
#define ARPHRD_ETHER 1 /* ethernet hardware format */
#define ARPHRD_IEEE802 6 /* token-ring hardware format */
#define ARPHRD_FRELAY 15 /* frame relay hardware format */
#define ARPHRD_IEEE1394 24 /* IEEE1394 hardware address */
#define ARPHRD_IEEE1394_EUI64 27 /* IEEE1394 EUI-64 */
u_short ar_pro; /* 协议类型 format of protocol address */
u_char ar_hln; /* 硬件地址长度 length of hardware address */
u_char ar_pln; /* 协议地址长度 length of protocol address */
u_short ar_op; /* 操作码 one of: */
#define ARPOP_REQUEST 1 /* request to resolve address */
#define ARPOP_REPLY 2 /* response to previous request */
#define ARPOP_REVREQUEST 3 /* request protocol address given hardware */
#define ARPOP_REVREPLY 4 /* response giving protocol address */
#define ARPOP_INVREQUEST 8 /* request to identify peer */
#define ARPOP_INVREPLY 9 /* response identifying peer */
/*
* The remaining fields are variable in size,
* according to the sizes above.
*/
#ifdef COMMENT_ONLY
u_char ar_sha[]; /* 源硬件地址 sender hardware address */
u_char ar_spa[]; /* 源协议地址 sender protocol address */
u_char ar_tha[]; /* 目标硬件地址 target hardware address */
u_char ar_tpa[]; /* 目标协议地址 target protocol address */
#endif
};
实现
在 Linux 系统上, 可以通过 PF_PACKET
创建由用户态程序收发数据链接层数据的 Packet Socket, 从而发送完全自定义的 ARP 报文。但是在基于 BSD 的系统(比如 MacOS) 上, 是不支持 PF_PACKET
类型的 Socket 的,这时候就要利用 BPF(Berkeley Packet Filter)伯克利包过滤器来实现原始链路层数据的收发. —— BPF
Berkeley Packet Filter
数据包过滤器显示为字符特殊设备 /dev/bpfN
(N为0~N, 一台机器上可能会提供多个 bpf 文件)。打开设备后,必须使用 ioctl
调用并结合 BIOCSETIF
, 将文件描述符绑定到特定的网络接口。给定的接口可以由多个侦听器共享,并且每个描述符下面的过滤器将看到相同的数据包流。— man bpf
打开 BPF 设备
int openBpf()
{
char _buf[32];
int bfd = -1;
int i = 0;
// 查找一个可用的 BPF 设备
for (i = 0; i < 255; i++)
{
snprintf(_buf, sizeof(_buf), "/dev/bpf%u", i);
bfd = open(_buf, O_RDWR);
if (bfd > 0)
{
break;
}
}
return bfd;
}
设置 BPF 文件
int setupBpf(int fd, const char *ifname) {
// ifname 为硬件接口名字, 比如 en0 就代表网卡一
struct ifreq request;