ARP协议图文详解,包含完整测试代码

目录

1.ARP协议简介

2.ARP工作原理

2.1 局域网通信

2.2 跨网段通信

3.ARP协议解析

3.1 ARP报文结构

3.2 ARP请求

3.3 ARP响应

4.ARP攻击

4.1 ARP欺骗

4.2 ARP投毒

4.3 如何预防ARP攻击?

5.ARP编程示例

5.1 ARP请求示例代码

5.2 ARP响应示例代码

6.Linux ARP调试

6.1 查看ARP表

6.2 手动添加ARP表项

6.3 删除ARP表项


1.ARP协议简介

ARP(Address Resolution Protocol)协议是一种在局域网中解析MAC地址的协议。

当主机要向局域网中的另一台主机发送数据时,需要知道目标主机的MAC地址。ARP协议就是用来解析目标主机的MAC地址的。主机会广播一个ARP请求包,请求目标主机回应自己的MAC地址。目标主机接收到请求后会返回一个ARP响应包,包括自己的MAC地址。这样,请求主机就可以通过MAC地址向目标主机发送数据了。

2.ARP工作原理

图 1 ARP工作原理

2.1 局域网通信

局域网主机A和主机B通信,如果双方原来没有通信过,主机A只知道主机B的IP地址,不知道主机B的MAC地址,此时主机A和主机B无法正常通信。

局域网通信的基础通信双方都得知道对方的MAC地址,MAC地址通常是存储在主机的ARP表中。

此时主机A会尝试去获取主机B的MAC地址,获取的MAC地址的方式是发送ARP请求(以广播形式发送),主机B如果收到ARP请求会回复ARP响应给主机A。

主机A收到响应后会把主机B的IP和MAC地址记录在ARP表中,此时主机A能够发送数据给主机B,主机B因为收到主机A的ARP请求,通过ARP请求知道主机A的MAC地址,把主机A的IP和MAC地址记录在ARP表中,主机B也能给主机A发送数据,正阳主机A和主机B就能正常通信。

2.2 跨网段通信

主机A要访问一个公网主机,由于主机A和公网主机IP地址不再同一网段,此时需要用到路由功能,路由功能后续章节会详细介绍。

我们只要记住一个核心点,路由功能核心作用就是找到去往目的IP的网关IP地址。

网关IP地址必须和主机A处于同一局域网。找到网关IP地址的目的是为了获取网关MAC地址,获取到网关MAC地址,可以通过网关MAC地址把数据包发送给网关,让网关转发数据包至公网。

如果通过网关IP获取到网关MAC地址可以参考局域网通信。

跨网段通信流程:

步骤1:通过路由查找到网关IP地址

步骤2:如果不知道网关MAC地址,需要通过ARP协议获取到网关MAC地址。

步骤3:把网关MAC地址填入以太网报文头部,将数据包发给网关。

步骤4:网关转发数据包至公网。

3.ARP协议解析

3.1 ARP报文结构

图 2 ARP报文结构 

  • 硬件类型:2字节,表示使用的网络类型,例如以太网、令牌环等。
  • 协议类型:2字节,表示使用的协议类型,例如IPv4、IPX等。
  • 硬件地址长度:1字节,表示硬件地址的长度,例如以太网地址长度为6字节。
  • 协议地址长度:1字节,表示协议地址的长度,例如IPv4地址长度为4字节。
  • 操作码:2字节,表示ARP请求(数值为1)或ARP响应(数值为2)。
  • 发送方硬件地址:6字节,表示发送方的硬件地址。
  • 发送方协议地址:4字节,表示发送方的协议地址。
  • 目标硬件地址:6字节,表示目标的硬件地址。
  • 目标协议地址:4字节,表示目标的协议地址。

3.2 ARP请求

图 3 ARP请求报文

 ARP请求各个字段如何填充如上图:

硬件类型:1,ARPHRD_ETHER类型

协议类型:0x8000,IPv4协议

硬件地址长度:6字节,MAC地址长度

协议地址长度:4字节,IP地址长度

操作码:1,ARP请求码

发送方MAC地址:14:23:0a:39:e3:75

发送方IP地址:192.168.1.1

目的MAC地址:00:00:00:00:00:00

目的IP地址:192.168.1.19

3.3 ARP响应

 图 4 ARP响应报文

 ARP响应各个字段如何填充如上图:

硬件类型:1,ARPHRD_ETHER类型

协议类型:0x8000,IPv4协议

硬件地址长度:6字节,MAC地址长度

协议地址长度:4字节,IP地址长度

操作码:2,ARP响应

发送方MAC地址:00:93:37:5b:d9:2f

发送方IP地址:192.168.1.19

目的MAC地址:14:23:0a:39:e3:75

目的IP地址:192.168.1.1

4.ARP攻击

 图 5 ARP攻击

ARP攻击(Address Resolution Protocol attack)是一种网络攻击方式,它利用ARP协议进行攻击,通过伪造或欺骗网络中的ARP响应包,将合法网络设备的IP地址映射到攻击者所控制的设备上,从而使攻击者可以窃取或篡改网络中的数据流量。

ARP攻击可以分为两种类型,一种是ARP欺骗(ARP Spoofing),另一种是ARP投毒(ARP Poisoning)。

4.1 ARP欺骗

攻击者会向网络中的设备发送伪造的ARP响应包,使其将攻击者所控制的设备的MAC地址当作目标设备的MAC地址,从而使攻击者可以拦截、篡改或窃取网络中的数据流量。

4.2 ARP投毒

攻击者会向网络中的设备发送大量的伪造ARP响应包,从而使网络设备的ARP缓存被攻击者所控制的设备所替换,这样一来,攻击者就可以直接访问目标设备,而无需再进行ARP欺骗攻击。

4.3 如何预防ARP攻击?

a.使用静态ARP表:将网络设备的IP地址和MAC地址的映射关系手动输入到静态ARP表中,可以防止攻击者篡改ARP响应包。

b.使用端口安全(Port Security):在交换机上配置端口安全,限制每个端口能够连接的MAC地址数量,可以有效地防止ARP攻击。

c.使用ARP防火墙(ARP Firewall):ARP防火墙可以监控网络流量中的ARP请求和响应包,并根据预设的规则进行过滤和阻断,从而有效地防止ARP攻击。

d.加密网络流量:使用加密技术(如SSL、TLS等)对网络流量进行加密,可以防止数据被窃听和篡改,从而有效地保护网络安全。

5.ARP编程示例

5.1 ARP请求示例代码

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <linux/in.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>

uint8_t src_mac[ETH_ALEN] = {0x08,0x00,0x27,0x06,0x38,0xba};
uint8_t nexthop_mac[ETH_ALEN] = {0x08,0x00,0x27,0xc1,0xdf,0xea};

#define LOCAL_IP "192.168.1.18"
#define PEER_IP "192.168.1.19"

#define MAX_BUF_SIZE (2048)
#define IP_ADDR_LEN (4)

struct _arphdr {
        __be16          ar_hrd;
        __be16          ar_pro;
        unsigned char   ar_hln;
        unsigned char   ar_pln;
        __be16          ar_op;

        unsigned char           ar_sha[ETH_ALEN];
        unsigned char           ar_sip[4];
        unsigned char           ar_tha[ETH_ALEN];
        unsigned char           ar_tip[4];
};

uint32_t create_pack(char *buf) {
    struct ethhdr *eh = (struct ethhdr *)buf;
    memcpy(eh->h_dest, nexthop_mac, ETH_ALEN);
    memcpy(eh->h_source, src_mac, ETH_ALEN);
    eh->h_proto = htons(ETH_P_ARP);

    struct _arphdr *ah = (struct _arphdr *)(buf + ETH_HLEN);
        ah->ar_hrd = htons(ARPHRD_ETHER);
    ah->ar_pro = htons(ETH_P_IP);
        ah->ar_hln = ETH_ALEN;
    ah->ar_pln = IP_ADDR_LEN;
        ah->ar_op = htons(ARPOP_REQUEST);

    memcpy(ah->ar_sha, src_mac, ETH_ALEN);
    uint32_t src_ip = inet_addr(LOCAL_IP);
    memcpy(ah->ar_sip, &src_ip, IP_ADDR_LEN);

    memcpy(ah->ar_tha, nexthop_mac, ETH_ALEN);
    uint32_t peer_ip = inet_addr(PEER_IP);
    memcpy(ah->ar_tip, &peer_ip, IP_ADDR_LEN);

    return ETH_HLEN + sizeof(struct _arphdr);
}

int main(int argc , char *argv[]) {
    int ret;
    int sockfd;
    char send_buf[MAX_BUF_SIZE] = {0};
    struct sockaddr_ll local;
    struct sockaddr_ll peer;

    sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
    if (sockfd == -1) {
        perror("socket error");
        return -1;
    }

    bzero(&peer, sizeof(struct sockaddr_ll));
    peer.sll_family = PF_PACKET;
    peer.sll_protocol = htons(ETH_P_ARP);
    peer.sll_ifindex = 2;
    peer.sll_hatype = ARPHRD_ETHER;
    peer.sll_pkttype = PACKET_OTHERHOST;
    peer.sll_halen = ETH_ALEN;
    memcpy(peer.sll_addr, nexthop_mac, ETH_ALEN);

    uint32_t slen = create_pack(send_buf);

    while(1) {
        ret = sendto(sockfd, send_buf, slen, 0, (struct sockaddr *)&peer, sizeof(peer));
        if (ret <= 0) {
            printf("sendto ret:%d, errno:%d(%s)\n", ret, errno, strerror(errno));
            break;
        }

        sleep(1);
    }

    close(sockfd);

    return 0;
}

5.2 ARP响应示例代码

#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/in.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>

uint8_t src_mac[ETH_ALEN] = {0x08,0x00,0x27,0xc1,0xdf,0xea};
uint8_t nexthop_mac[ETH_ALEN] = {0x08,0x00,0x27,0x06,0x38,0xba};
#define LOCAL_IP "192.168.1.19"
#define PEER_IP "192.168.1.18"

#define IP_ADDR_LEN (4)
#define MAX_BUF_SIZE (2048)

struct _arphdr {
    __be16              ar_hrd;
    __be16              ar_pro;
    unsigned char       ar_hln;
    unsigned char       ar_pln;
    __be16              ar_op;

    unsigned char               ar_sha[ETH_ALEN];
    unsigned char               ar_sip[4];
    unsigned char               ar_tha[ETH_ALEN];
    unsigned char               ar_tip[4];
};

void print_arp(char *msg, const char *buf) {
    struct _arphdr *ah = (struct _arphdr *)(buf + ETH_HLEN);
    printf("%s\n"
            "硬件类型:%u\n"
            "协议类型:%u\n"
            "硬件地址长度:%u\n"
            "协议地址长度:%u\n"
            "操作码:%u\n"
            "发送方硬件地址:%02hx:%02hx:%02hx:%02hx:%02hx:%02hx\n"
            "发送方协议地址:%02hx.%02hx.%02hx.%02hx\n"
            "接收方硬件地址:%02hx:%02hx:%02hx:%02hx:%02hx:%02hx\n"
            "接收方协议地址:%02hx.%02hx.%02hx.%02hx\n",
            msg,
            ntohs(ah->ar_hrd),
            ntohs(ah->ar_pro),
            ah->ar_hln,
            ah->ar_pln,
            ntohs(ah->ar_op),
            ah->ar_sha[0], ah->ar_sha[1], ah->ar_sha[2], ah->ar_sha[3], ah->ar_sha[4], ah->ar_sha[5],
            ah->ar_sip[0], ah->ar_sip[1], ah->ar_sip[2], ah->ar_sip[3],
            ah->ar_tha[0], ah->ar_tha[1], ah->ar_tha[2], ah->ar_tha[3], ah->ar_tha[4], ah->ar_tha[5],
            ah->ar_tip[0], ah->ar_tip[1], ah->ar_tip[2], ah->ar_tip[3]);
}

int parse_pack(char *msg, char *buf) {
    print_arp(msg, buf);
    return 0;
}

uint32_t create_pack(char *sbuf, char *rbuf) {
    struct ethhdr *eh = (struct ethhdr *)sbuf;
    memcpy(eh->h_dest, nexthop_mac, ETH_ALEN);
    memcpy(eh->h_source, src_mac, ETH_ALEN);
    eh->h_proto = htons(ETH_P_ARP);

    struct _arphdr *rah = (struct _arphdr *)(rbuf + ETH_HLEN);
    struct _arphdr *ah = (struct _arphdr *)(sbuf + ETH_HLEN);
        ah->ar_hrd = htons(ARPHRD_ETHER);
    ah->ar_pro = htons(ETH_P_IP);
        ah->ar_hln = ETH_ALEN;
    ah->ar_pln = IP_ADDR_LEN;
        ah->ar_op = htons(ARPOP_REPLY);

    memcpy(ah->ar_sha, src_mac, ETH_ALEN);
    uint32_t src_ip = inet_addr(LOCAL_IP);
    memcpy(ah->ar_sip, &src_ip, IP_ADDR_LEN);
    memcpy(ah->ar_tha, rah->ar_sha, ETH_ALEN);
    memcpy(ah->ar_tip, rah->ar_sip, IP_ADDR_LEN);

    return ETH_HLEN + sizeof(struct _arphdr);
}

bool isvalid(const char *buf) {
    struct ethhdr *eh = (struct ethhdr *)buf;
    if (ntohs(eh->h_proto) == ETH_P_ARP) return true;
    return false;
}

int main(int argc , char *argv[]) {
    int ret;
    int sockfd;
    char send_buf[MAX_BUF_SIZE] = {0};
    char recv_buf[MAX_BUF_SIZE] = {0};
    struct sockaddr_ll peer;
    socklen_t peerlen = 0;

    sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
    if (sockfd == -1) {
        perror("socket error");
        return -1;
    }

    bzero(&peer, sizeof(struct sockaddr_ll));
    peer.sll_family = PF_PACKET;
    peer.sll_protocol = htons(ETH_P_ARP);
    peer.sll_ifindex = 2;
    peer.sll_hatype = ARPHRD_ETHER;
    peer.sll_pkttype = PACKET_OTHERHOST;
    peer.sll_halen = ETH_ALEN;
    memcpy(peer.sll_addr, nexthop_mac, ETH_ALEN);

    while(1) {
        memset(recv_buf, 0, MAX_BUF_SIZE);
        ret = recvfrom(sockfd, recv_buf, MAX_BUF_SIZE, 0, NULL, NULL);
        if (ret <= 0) {
            printf("recvfrom ret:%d error\n", ret);
            break;
        } else {
            bool bret = isvalid(recv_buf);
            printf("ret:%d, bret:%d\n", ret, bret);
            if (bret == false) {
                continue;
            }
            parse_pack("recv buf", recv_buf);

            memset(send_buf, 0, MAX_BUF_SIZE);
            int slen = create_pack(send_buf, recv_buf);
            slen += 18;
            printf("sendbuf len:%d\n", slen);
            ret = sendto(sockfd, send_buf, slen, 0, (struct sockaddr *)&peer, sizeof(peer));
            if (ret <= 0) {
                printf("sendto ret:%d error:%d(%s)\n", ret, errno, strerror(errno));
                break;
            }
        }
    }

    close(sockfd);

    return 0;
}

6.Linux ARP调试

6.1 查看ARP表

ip neigh

6.2 手动添加ARP表项

ip neigh add 192.168.1.19 dev enp0s3 lladdr 08:00:27:c1:df:ea

6.3 删除ARP表项

ip neigh del 192.168.1.19 dev enp0s3 lladdr 08:00:27:c1:df:ea

如果觉得本文对你有帮助,希望给个一键三连,支持博主,谢谢!