实现Arp报文发送和接收

实现Arp报文发送和接收

继上次实现了 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;

strlcpy(request.ifr_name, ifname, sizeof(request.ifr_name) - 1);

/* 将硬件接口和BPF文件描述符绑定 */

int resp = ioctl(fd, BIOCSETIF, &request);

if (resp < 0) {

perror("BIOCSETIF failed: ");

return -1;

}

/* 返回附加接口下的数据链接层的类型, 也就是返回我们绑定的硬件接口(en0)支持的数据层类型 */

u_int type;

if (ioctl(fd, BIOCGDLT, &type) < 0) {

perror("BIOCGDLT failed: ");

return -1;

}

if (type != DLT_EN10MB) {

// 如果不是支持 10MB 的网卡

printf("unsupported datalink type\n");

return -1;

}

/* 启用即时模式, 启用即时模式后,读取数据包后立即返回。否则, 读取将阻塞, 直到内核 buffer 变满或发生超时 */

int enable = 1;

if (ioctl(fd, BIOCIMMEDIATE, &enable) < 0) {

perror("BIOCSIMMEDIATE failed: ");

return -1;

}

return 0;

}

DNS 解析

/* 根据域名或IP地址获取实际 IP地址, 并写入到 sockaddr_in 结构体中 */

struct sockaddr_in getsockaddrbyhost(const char *host) {

hostent *h = gethostbyname(host);

struct sockaddr_in addr;

bzero(&addr, sizeof(addr));

addr.sin_family = AF_INET;

addr.sin_addr = *(in_addr *)(h->h_addr);

return addr;

}

获取本机 IP地址和 MAC地址

int getAddrs(struct sockaddr_in *protocolAddr, u_char *hardwareAddr) {

struct ifaddrs *addrs, *addr;

struct sockaddr_dl hardwareDl;

/* getifaddrs 会返回当前计算机网络接口的信息, 可以看作它会把 ifconfig 命令的内容返给你 */

if (getifaddrs(&addrs) < 0) {

perror("[getifaddrs]");

return -1;

}

addr = addrs;

/* 这里我固定了获取网卡一(en0)的地址 */

while (addr) {

if (strcmp("en0", addr->ifa_name) == 0 && addr->ifa_addr->sa_family == AF_INET)

{

memcpy(protocolAddr, (struct sockaddr_in *)(addr->ifa_addr), sizeof(struct sockaddr_in));

}

if (strcmp("en0", addr->ifa_name) == 0 && addr->ifa_addr->sa_family == AF_LINK)

{

memcpy(&hardwareDl, (struct sockaddr_dl *)(addr->ifa_addr), sizeof(struct sockaddr_dl));

}

addr = addr->ifa_next;

}

freeifaddrs(addrs);

if (!protocolAddr || !hardwareAddr)

{

LOG_D(TAG, "not get ifaddrs");

return -1;

}

memcpy(hardwareAddr, LLADDR(&hardwareDl), hardwareDl.sdl_alen);

return 0;

}

发送 ARP 报文

void arp(const char *host) {

/* 获取目标机器的IP地址 */

sockaddr_in targetaddr = getsockaddrbyhost(host);

LOG_D(TAG, "target: %s", inet_ntoa(targetaddr.sin_addr));

/* 获取本机的IP地址和MAC地址 */

struct sockaddr_in protocolAddr;

struct sockaddr_dl hardwarAddr;

u_char senderHardwareAddress[ETHER_ADDR_LEN];

if (getAddrs(&protocolAddr, senderHardwareAddress) < 0) {

perror("[getAddrs]");

exit(1);

}

/* ether_header: 14, arp_header: 28 */

int etherSize = 14;

int arpSize = 28;

int packSize = etherSize + arpSize;

char buf[packSize];

bzero(buf, sizeof(buf));

/* 填充以太网头部 */

ether_header_t *eaddr = (ether_header_t *)buf;

static const u_char etherBroadcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

// 目标MAC地址设为广播地址

memcpy(eaddr->ether_dhost, etherBroadcast, 6);

// 帧类型设为 ARP

eaddr->ether_type = htons(ETHERTYPE_ARP);

/* 填充 ARP 请求 */

struct arphdr *arphdr = (struct arphdr *)(buf + etherSize);

// 硬件类型

arphdr->ar_hrd = htons(ARPHRD_ETHER);

// 协议类型

arphdr->ar_pro = htons(ETHERTYPE_IP);

// 硬件地址长度

arphdr->ar_hln = sizeof(senderHardwareAddress);

// 协议地址长度

arphdr->ar_pln = sizeof(targetaddr.sin_addr);

// 操作码 ARPOP_REQUEST 表示请求

arphdr->ar_op = htons(ARPOP_REQUEST);

int offset = sizeof(arphdr->ar_hrd) +

sizeof(arphdr->ar_pro) +

sizeof(arphdr->ar_op) +

sizeof(arphdr->ar_hln) +

sizeof(arphdr->ar_pln) + etherSize;

// 源硬件地址

memcpy(buf + offset, senderHardwareAddress, ETHER_ADDR_LEN);

offset += ETHER_ADDR_LEN;

// 源协议地址

memcpy(buf + offset, &(protocolAddr.sin_addr), 4);

offset += 4;

// 目标硬件地址

memset(buf + offset, 0, ETHER_ADDR_LEN);

offset += ETHER_ADDR_LEN;

// 目标协议地址

memcpy(buf + offset, &(targetaddr.sin_addr), 4);

/* 输出 ARP 请求 */

outputArp(arphdr);

/* 打开 BPF 设备并设置 */

int bfd = openBpf();

if (bfd < 0) {

LOG_D(TAG, "[openBpf] failed");

exit(1);

}

setupBpf(bfd, "en0");

/* 写入数据 */

ssize_t writed = write(bfd, buf, packSize);

if (writed < 0) {

perror("writev failed.");

} else {

LOG_D(TAG, "writed %d", writed);

/* 写入成功之后读取数据 */

readBpf(bfd);

}

close(bfd);

}

读取 ARP 报文

void readBpf(int fd) {

int bufSize;

/* Returns the required buffer length for reads on bpf files */

if (ioctl(fd, BIOCGBLEN, &bufSize) < 0) {

perror("BIOCGBLEN failed: ");

exit(1);

}

LOG_D(TAG, "BIO Buffer: %d", bufSize);

char re[bufSize];

int finish = 1;

while (finish) {

/* 从 BPF 设备中读取数据 */

ssize_t readed = read(fd, re, bufSize);

if (readed < 0) {

perror("read failed.");

break;

}

else if (readed == 0) {

LOG_D(TAG, "read end.");

break;

}

LOG_D(TAG, "read %d bytes data.", readed);

/* 接收的数据的头部是 bpf_hdr */

const struct bpf_hdr *bpfHeader = (struct bpf_hdr *)re;

LOG_D(TAG, "bpf header tstamp: %", bpfHeader->bh_tstamp);

LOG_D(TAG, "bpf header len: %d", bpfHeader->bh_hdrlen);

LOG_D(TAG, "bpf header data len: %d", bpfHeader->bh_datalen);

LOG_D(TAG, "bpf header cap len: %d", bpfHeader->bh_caplen);

/* 从 re 中取出以太网头部 */

ether_header_t *eaddr = (ether_header_t *)(re + bpfHeader->bh_hdrlen);

u_short etherType = ntohs(eaddr->ether_type);

if (etherType == ETHERTYPE_ARP) {

LOG_D(TAG, "Received ARP");

/* 从 re 中取出ARP数据 */

const struct arphdr *arp = (struct arphdr *)(re + bpfHeader->bh_hdrlen + sizeof(ether_header_t));

/* 由于会收到很多局域网中其他设备发出的 ARP 请求, 所以只接收第一次的 Reply, 表示是对我们发出的 Request 的响应. 更严谨的应该根据 Reply 包中的目标ip地址和目标mac地址是不是我们的地址来过滤 */

if (arp->ar_op == ntohs(ARPOP_REPLY)) {

LOG_D(TAG, "Received ARP Reply");

outputArp(arp);

finish = 0;

}

}

}

}

结果

arp 192.168.31.1

target: 192.168.31.1

Hardware type: 1

Protocol type: 2048

Opereation code: 1

Hardware address len: 6

Protocol address len: 4

Source hardware address: 0x88000000:0xe9000000:0xfe000000:0x53000000:0xed000000:0x16000000

Source ip address: 192.168.31.77

Dest hardware address: 0:0:0:0:0:0

Dest ip address: 192.168.31.1

writed 42

BIO Buffer: 4096

Received ARP

Received ARP

Received ARP Reply

Hardware type: 1

Protocol type: 2048

Opereation code: 2

Hardware address len: 6

Protocol address len: 4

Source hardware address: 0x28000000:0x6c000000:0x7000000:0x3c000000:0xca000000:0x8d000000

Source ip address: 192.168.31.1

Dest hardware address: 0x88000000:0xe9000000:0xfe000000:0x53000000:0xed000000:0x16000000

Dest ip address: 192.168.31.77

Request

Reply

完整源码

https://github.com/stefanJi/NetUtitily/blob/master/src/arp.cpp

相关推荐

摄像头软件软件有哪些好用 十款常用摄像头软件软件排行榜
别人如何对我,我便如何对别人,这句古语怎么说的?
英国beat365官方登录

别人如何对我,我便如何对别人,这句古语怎么说的?

📅 07-22 👁️ 5398
防水喷雾可以维持多久
365bet在线娱乐场

防水喷雾可以维持多久

📅 07-16 👁️ 5148