Linux socket发送ARP请求包 C语言

#前言

手动输入的参数有网卡名源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。 

  • 10
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
ARP(Address Resolution Protocol,地址解析协议)是在TCP/IP网络中常用的一种协议,其功能是通过设备的MAC地址来解析IP地址。Linux上使用C语言来实现ARP功能可以通过socket编程来完成。 C语言中可以使用socket相关的函数来创建一个网络套接字,用于发送和接收ARP请求和响应。首先需要创建一个ARP请求的数据括目的IP地址、源IP地址、以太网帧类型等信息,然后使用socket的sendto函数将数据发送出去。接收ARP响应则需要创建一个监听的socket,使用recvfrom函数接收到ARP响应数据后,可以解析数据的内容,提取出目的IP地址和对应的MAC地址。 在Linux上,可以使用套接字的AF_PACKET类型来进行原始套接字编程,这样可以直接访问网络数据链路层,可以发送和接收以太网帧。通过设置套接字的协议为ETH_P_ARP,可以指定使用ARP协议。 具体的实现需要注意以下几个步骤: 1. 创建一个原始套接字,指定协议类型为ETH_P_ARP。 2. 构建一个ARP请求数据,设置目的IP地址、源IP地址、以太网帧类型等字段。 3. 使用sendto函数发送ARP请求数据。 4. 创建一个用于接收ARP响应的套接字。 5. 使用recvfrom函数接收到ARP响应数据。 6. 解析数据,提取目的IP地址和对应的MAC地址。 在Linux上,可以使用含在net/if_arp.h头文件中的结构体来定义ARP请求和响应的数据格式,将这些字段填充后,就可以使用socket的sendto和recvfrom函数进行发送和接收。 需要注意的是,使用C语言实现ARP功能需要一定的网络编程基础和对TCP/IP协议栈的理解,同时需要理解以太网帧的格式和地址解析协议的工作原理。对于初学者来说,可能需要借助一些网络编程的教程和参考资料来进行学习和实践。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值