#前言

手动输入的参数有网卡名源IP目标IP,它们定义在程序开头。如需调整其他ARP参数,只需对程序适当位置修改即可。

本程序着重在于功能实现的学习,故没有考虑像制作成工具一样的编写,尽量保证代码简洁。

需要先熟悉ARP包中的各个字段再来编写程序。


进一步改写为组包工具,可以自由设定源IP、目标IP以及源MAC:Linux ARP请求组包工具 C语言socket


程序基本分为三部分

构造以太网帧头部

构造ARP包内容

构造sockaddr_ll地址结构

其中以太网帧头部和ARP请求内容(也就是网络中传输的一个ARP包完整内容)共同存储在一个buffer中,构造好buffer之后通过socket发送出去即可。

#代码实现

#include <stdio.h>      
#include <stdlib.h>  
#include <string.h>
#include <unistd.h>

#include <sys/socket.h>     
#include <sys/ioctl.h>      
#include <arpa/inet.h>      

#include <linux/if.h>
#include <linux/if_packet.h> 
#include <linux/if_ether.h>     
#include <linux/if_arp.h>       

#define IPV4_LENGTH 4
#define Dev "wlan0"     //网卡名
#define buffer_len 60   //ARP请求包大小为60B,,抓包时会抓到一些42B的包,这是抓包软件没有显示18B的Padding字段,Padding全0填充在包的末尾
unsigned char sender_ip[4] = {192,168,1,33};    //ARP请求的源IP
unsigned char target_ip[4] = {192,168,1,1};     //ARP请求的目标IP
/*ARP包结构*/
/*字段顺序不可更改,发包时是直接将buffer发出*/
struct arp_head
{
    unsigned short hardware_type;   //硬件类型#1:Ethernet
	unsigned short protocol_type;   //协议类型#0x0800:IPv4
    unsigned char hardware_size;    //MAC地址长度#6
    unsigned char protocol_size;    //IP地址长度#4
    unsigned short opcode;          //ARP类型#1:request;2:reply
    unsigned char sender_mac[ETH_ALEN];    //源MAC地址
    unsigned char sender_ip[IPV4_LENGTH];  //源IP地址
    unsigned char target_mac[ETH_ALEN];    //目标MAC地址
    unsigned char target_ip[IPV4_LENGTH];  //目标IP地址
};

int main()
{
    //创建buffer
    unsigned char buffer[buffer_len];  
    memset(buffer, 0, buffer_len);
    //创建以太网头部指针,指向buffer
    struct ethhdr *eth_req = (struct ethhdr*)buffer;
    //创建ARP包指针,指向buffer的后46字节,因为以太网头包含:2*6B(MAC地址)+2B(协议地址)=14B
    struct arp_head *arp_req = (struct arp_head*)(buffer+14);
    //创建sockaddr_ll结构地址
    struct sockaddr_ll sock_addr;
    //创建socket
    /***int socket(int __domain, int __type, int __protocol)
     *** __domain
     * PF_PACKET指示为二层协议簇
     * 使用AF_PACKET也可,socket.h中有#define AF_PACKET PF_PACKET
     *** __type
     * 使用PF_PACKET的后,__type只能选择SOCK_RAW或者SOCK_DGRAM
     * 其中SOCK_RAW可以自己构造帧头,SOCK_DGRAM不行
     * 帧头使用sockaddr_ll结构体构建,这个结构体在if_packet.h中
     * 有一些资料这里选择的是SOCK_PACKET,这个类型目前已经被建议弃用
     *** __protocol
     * ETH_P_ARP意味着我们仅仅接受ARP类型
     * 如果是ETH_P_ALL就意味着我们接受所有类型帧
     * 更多选项参看if_ether.h中定义
     */
    int sock_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
    if(sock_fd == -1){
        perror("socket()");
        exit(-1);
    }
    /**获取网卡等需要的信息
     * ifreq结构体可以用于设置或者获取网卡等相关信息,定义在if.h中
     * 配合ioctl()一起使用
     * ioctl()的具体参数用法和系统实现相关,不是通用的,具体参见ioctls.h
     * 以下获取的信息都会保存在ifreq不同字段之中
     */ 
    struct ifreq ifr;

    /*根据网卡设备名获取Index*/
    strcpy(ifr.ifr_name, Dev);
    if(ioctl(sock_fd, SIOCGIFINDEX, &ifr) == -1)
    {
        perror("SIOCGIFINDEX");
        exit(-1);
    }
    int ifindex = ifr.ifr_ifindex;
    printf("网卡索引为:%d\n",ifindex);

    /*获取网卡设备MAC地址*/
    if(ioctl(sock_fd, SIOCGIFHWADDR, &ifr) == -1)
    {
        perror("SIOCGIFHWADDR");
        exit(-1);
    }

    /*将MAC地址写入所需结构*/
    for(int i=0;i<6;i++)
    {
        //以太网帧的目标MAC,即广播MAC,全1
        eth_req->h_dest[i] = (unsigned char)0xff;
        //ARP请求包目标MAC,全0
        arp_req->target_mac[i] = (unsigned char)0x00;
        //以太网帧源MAC,即本机MAC
        //ifr_hwaddr是sockaddr结构体格式
        eth_req->h_source[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
        //ARP请求包源MAC,即本机MAC
        arp_req->sender_mac[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
        //sockaddr中的MAC,也是本地MAC
        sock_addr.sll_addr[i] = (unsigned char)ifr.ifr_hwaddr.sa_data[i];
    }
    
    /*打印MAC地址*/
    printf("网卡MAC地址: %02X:%02X:%02X:%02X:%02X:%02X\n",
            eth_req->h_source[0],
            eth_req->h_source[1],
            eth_req->h_source[2],
            eth_req->h_source[3],
            eth_req->h_source[4],
            eth_req->h_source[5]);

    /*完善sockaddr_ll结构体*/
    sock_addr.sll_family = PF_PACKET;  
    sock_addr.sll_protocol = htons(ETH_P_ARP);
    sock_addr.sll_ifindex = ifindex;
    sock_addr.sll_hatype = htons(ARPHRD_ETHER);
    sock_addr.sll_halen = ETH_ALEN;

    /*完善以太网帧头*/
    eth_req->h_proto = htons(ETH_P_ARP);

    /*完善ARP包头*/
    arp_req->hardware_type = htons(0x01);
    arp_req->protocol_type = htons(ETH_P_IP);
    arp_req->hardware_size = ETH_ALEN;
    arp_req->protocol_size = IPV4_LENGTH;
    arp_req->opcode = htons(ARPOP_REQUEST);
    memcpy(arp_req->sender_ip,sender_ip,IPV4_LENGTH);
    memcpy(arp_req->target_ip,target_ip,IPV4_LENGTH);

    /*发送ARP请求*/
    if(sendto(sock_fd, buffer, 60, 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) == -1)
    {
        perror("sendto()");
        exit(-1);
    }
    printf("发送ARP请求包:");
    for(int i=0;i<60;i++)
    {
        if(i%16==0)
            printf("\n\t");
        printf("%02X ",buffer[i]);
    }
    close(sock_fd);
    return 0;
}

#运行结果

#结果抓包

192.168.1.1是网段路由器地址,192.168.1.33是虚构的一个地址。

虽然192.168.1.33是虚构的主机地址,但MAC地址是本机真实的,所以192.168.1.1还是给我们回复了一个ARP。 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐