一.概述                                                  

以太网的arp数据包结构:

arp结构op操作参数:1为请求,2为应答。

常用的数据结构如下:

.物理地址结构位于netpacket/packet.h

 struct sockaddr_ll
 {
     unsigned short int sll_family;
     unsigned short int sll_protocol;
     int sll_ifindex;
     unsigned short int sll_hatype;
     unsigned char sll_pkttype;
     unsigned char sll_halen;
     unsigned ];
 };

sll_ifindex是网络(网卡)接口索引,代表从这个接口收发数据包

.网络(网卡)接口数据结构位于net/if.h

 struct ifreq
 {
 # define IFHWADDRLEN
 # define IFNAMSIZ    IF_NAMESIZE
     union
       {
     char ifrn_name[IFNAMSIZ];    /* Interface name, e.g. "en0".  */
       } ifr_ifrn;

     union
       {
     struct sockaddr ifru_addr;
     struct sockaddr ifru_dstaddr;
     struct sockaddr ifru_broadaddr;
     struct sockaddr ifru_netmask;
     struct sockaddr ifru_hwaddr;
     short int ifru_flags;
     int ifru_ivalue;
     int ifru_mtu;
     struct ifmap ifru_map;
     char ifru_slave[IFNAMSIZ];    /* Just fits the size */
     char ifru_newname[IFNAMSIZ];
     __caddr_t ifru_data;
       } ifr_ifru;
 };

该结构里面包含2个union,第一个是接口名,如:eth0,wlan0等。可以通过ioctl()函数来获取对应的接口信息,ip地址,mac地址,接口索引等。

.以太网首部结构位于net/ethernet.h

 struct ether_header
 {
   u_int8_t  ether_dhost[ETH_ALEN];    /* destination eth addr    */
   u_int8_t  ether_shost[ETH_ALEN];    /* source ether addr    */
   u_int16_t ether_type;                /* packet type ID field    */
 } __attribute__ ((__packed__));

ether_type帧类型:常见的有IP,ARP,RARP,都有对应的宏定义。

.arp包结构位于netinet/if_ether.h

 struct    ether_arp {
     struct    arphdr ea_hdr;        /* fixed-size header */
     u_int8_t arp_sha[ETH_ALEN];    /* sender hardware address */
     u_int8_t arp_spa[];        /* sender protocol address */
     u_int8_t arp_tha[ETH_ALEN];    /* target hardware address */
     u_int8_t arp_tpa[];        /* target protocol address */
 };
 #define    arp_hrd    ea_hdr.ar_hrd
 #define    arp_pro    ea_hdr.ar_pro
 #define    arp_hln    ea_hdr.ar_hln
 #define    arp_pln    ea_hdr.ar_pln
 #define    arp_op    ea_hdr.ar_op

上面的ether_arp结构还包含一个arp首部,位于net/if_arp.h

 struct arphdr
 {
     unsigned short int ar_hrd;        /* Format of hardware address.  */
     unsigned short int ar_pro;        /* Format of protocol address.  */
     unsigned char ar_hln;        /* Length of hardware address.  */
     unsigned char ar_pln;        /* Length of protocol address.  */
     unsigned short int ar_op;        /* ARP opcode (command).  */
 }

二.arp请求代码                                      

 /**
  * @file arp_request.c
  */

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/if_ether.h>
 #include <net/ethernet.h>
 #include <net/if_arp.h>
 #include <net/if.h>
 #include <netpacket/packet.h>

 /* 以太网帧首部长度 */
 #define ETHER_HEADER_LEN sizeof(struct ether_header)
 /* 整个arp结构长度 */
 #define ETHER_ARP_LEN sizeof(struct ether_arp)
 /* 以太网 + 整个arp结构长度 */
 #define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN
 /* IP地址长度 */
 #define IP_ADDR_LEN 4
 /* 广播地址 */
 #define BROADCAST_ADDR {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}

 void err_exit(const char *err_msg)
 {
     perror(err_msg);
     exit();
 }

 /* 填充arp包 */
 struct ether_arp *fill_arp_packet(const unsigned char *src_mac_addr, const char *src_ip, const char *dst_ip)
 {
     struct ether_arp *arp_packet;
     struct in_addr src_in_addr, dst_in_addr;
     unsigned char dst_mac_addr[ETH_ALEN] = BROADCAST_ADDR;

     /* IP地址转换 */
     inet_pton(AF_INET, src_ip, &src_in_addr);
     inet_pton(AF_INET, dst_ip, &dst_in_addr);

     /* 整个arp包 */
     arp_packet = (struct ether_arp *)malloc(ETHER_ARP_LEN);
     arp_packet->arp_hrd = htons(ARPHRD_ETHER);
     arp_packet->arp_pro = htons(ETHERTYPE_IP);
     arp_packet->arp_hln = ETH_ALEN;
     arp_packet->arp_pln = IP_ADDR_LEN;
     arp_packet->arp_op = htons(ARPOP_REQUEST);
     memcpy(arp_packet->arp_sha, src_mac_addr, ETH_ALEN);
     memcpy(arp_packet->arp_tha, dst_mac_addr, ETH_ALEN);
     memcpy(arp_packet->arp_spa, &src_in_addr, IP_ADDR_LEN);
     memcpy(arp_packet->arp_tpa, &dst_in_addr, IP_ADDR_LEN);

     return arp_packet;
 }

 /* arp请求 */
 void arp_request(const char *if_name, const char *dst_ip)
 {
     struct sockaddr_ll saddr_ll;
     struct ether_header *eth_header;
     struct ether_arp *arp_packet;
     struct ifreq ifr;
     char buf[ETHER_ARP_PACKET_LEN];
     unsigned char src_mac_addr[ETH_ALEN];
     unsigned char dst_mac_addr[ETH_ALEN] = BROADCAST_ADDR;
     char *src_ip;
     int sock_raw_fd, ret_len, i;

     )
         err_exit("socket()");

     bzero(&saddr_ll, sizeof(struct sockaddr_ll));
     bzero(&ifr, sizeof(struct ifreq));
     /* 网卡接口名 */
     memcpy(ifr.ifr_name, if_name, strlen(if_name));

     /* 获取网卡接口索引 */
     )
         err_exit("ioctl() get ifindex");
     saddr_ll.sll_ifindex = ifr.ifr_ifindex;
     saddr_ll.sll_family = PF_PACKET;

     /* 获取网卡接口IP */
     )
         err_exit("ioctl() get ip");
     src_ip = inet_ntoa(((struct sockaddr_in *)&(ifr.ifr_addr))->sin_addr);
     printf("local ip:%s\n", src_ip);

     /* 获取网卡接口MAC地址 */
     if (ioctl(sock_raw_fd, SIOCGIFHWADDR, &ifr))
         err_exit("ioctl() get mac");
     memcpy(src_mac_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);
     printf("local mac");
     ; i < ETH_ALEN; i++)
         printf(":%02x", src_mac_addr[i]);
     printf("\n");

     bzero(buf, ETHER_ARP_PACKET_LEN);
     /* 填充以太首部 */
     eth_header = (struct ether_header *)buf;
     memcpy(eth_header->ether_shost, src_mac_addr, ETH_ALEN);
     memcpy(eth_header->ether_dhost, dst_mac_addr, ETH_ALEN);
     eth_header->ether_type = htons(ETHERTYPE_ARP);
     /* arp包 */
     arp_packet = fill_arp_packet(src_mac_addr, src_ip, dst_ip);
     memcpy(buf + ETHER_HEADER_LEN, arp_packet, ETHER_ARP_LEN);

     /* 发送请求 */
     ret_len = sendto(sock_raw_fd, buf, ETHER_ARP_PACKET_LEN, , (struct sockaddr *)&saddr_ll, sizeof(struct sockaddr_ll));
     )
         printf("sendto() ok!!!\n");

     close(sock_raw_fd);
 }

 int main(int argc, const char *argv[])
 {
     )
     {
         printf(]);
         exit();
     }

     arp_request(argv[], argv[]);

     ;
 }

流程:命令行接收网卡接口名和要请求的目标IP地址,传入arp_request()函数。用PF_PACKET选项创建ARP类型的原始套接字。用ioctl()函数通过网卡接口名来获取该接口对应的mac地址,ip地址,接口索引。接口索引填充到物理地址sockaddr_ll里面。然后填充以太首部,源地址对应刚刚的网卡接口mac地址,目标地址填广播地址(第28行定义的宏)。以太首部帧类型是ETHERTYPE_ARP,代表arp类型。接着填充arp数据包结构,同样要填充源/目标的ip地址和mac地址,arp包的操作选项填写ARPOP_REQUEST,代表请求操作。填充完成后发送到刚刚的物理地址sockaddr_ll。

三.接收arp数据包                                  

 /**
  * @file arp_recv.c
  */

 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 #include <netinet/if_ether.h>
 #include <net/if_arp.h>
 #include <net/ethernet.h>

 /* 以太网帧首部长度 */
 #define ETHER_HEADER_LEN sizeof(struct ether_header)
 /* 整个arp结构长度 */
 #define ETHER_ARP_LEN sizeof(struct ether_arp)
 /* 以太网 + 整个arp结构长度 */
 #define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN
 /* IP地址长度 */
 #define IP_ADDR_LEN 4

 void err_exit(const char *err_msg)
 {
     perror(err_msg);
     exit();
 }

 int main(void)
 {
     struct ether_arp *arp_packet;
     char buf[ETHER_ARP_PACKET_LEN];
     int sock_raw_fd, ret_len, i;

     )
         err_exit("socket()");

     )
     {
         bzero(buf, ETHER_ARP_PACKET_LEN);
         ret_len = recv(sock_raw_fd, buf, ETHER_ARP_PACKET_LEN, );
         )
         {
             /* 剥去以太头部 */
             arp_packet = (struct ether_arp *)(buf + ETHER_HEADER_LEN);
             /* arp操作码为2代表arp应答 */
             )
             {
                 printf("==========================arp replay======================\n");
                 printf("from ip:");
                 ; i < IP_ADDR_LEN; i++)
                     printf(".%u", arp_packet->arp_spa[i]);
                 printf("\nfrom mac");
                 ; i < ETH_ALEN; i++)
                     printf(":%02x", arp_packet->arp_sha[i]);
                 printf("\n");
             }
         }
     }

     close(sock_raw_fd);
     ;
 }

流程:创建ARP类型的原始套接字。直接调用接收函数,会收到网卡接收的arp数据包,判断收到的arp包操作是arp应答,操作码是2。然后剥去以太首部,取出源mac地址和ip地址!!!

四.实验                                                  

为了更直观,我们打开wireshark一起观察,我这里是wlan环境,监听wlan0。原始套接字要以root身份运行,先运行arp_recv,然后运行arp_request发送arp请求:

wireshark结果:

上面可以看到,第一条数据包询问谁是192.168.0.1,然后第二条数据包发送了一个回复,可以看到wireshark里面Opcode:reply(2)。源ip和mac地址跟我们自己的接收程序一样。

linux原始套接字(1)-arp请求与接收的更多相关文章

  1. linux原始套接字(2)-icmp请求与接收

    一.概述                                                    上一篇arp请求使用的是链路层的原始套接字.icmp封装在ip数据报里面,所以icmp请 ...

  2. Linux原始套接字实现分析---转

    http://blog.chinaunix.net/uid-27074062-id-3388166.html 本文从IPV4协议栈原始套接字的分类入手,详细介绍了链路层和网络层原始套接字的特点及其内核 ...

  3. Linux原始套接字抓取底层报文

    1.原始套接字使用场景 我们平常所用到的网络编程都是在应用层收发数据,每个程序只能收到发给自己的数据,即每个程序只能收到来自该程序绑定的端口的数据.收到的数据往往只包括应用层数据,原有的头部信息在传递 ...

  4. 关于linux 原始套接字编程

    关于linux 网络编程最权威的书是<<unix网络编程>>,但是看这本书时有些内容你可能理解的不是很深刻,或者说只知其然而不知其所以然,那么如果你想搞懂的话那么我建议你可以看 ...

  5. linux原始套接字(4)-构造IP_UDP

    一.概述                                                    同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...

  6. linux原始套接字(3)-构造IP_TCP发送与接收

    一.概述                                                    tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...

  7. UNIX网络编程——原始套接字的魔力【续】

    如何从链路层直接发送数据帧 上一篇里面提到的是从链路层"收发"数据,该篇是从链路层发送数据帧. 上一节我们主要研究了如何从链路层直接接收数据帧,可以通过bind函数来将原始套接字绑 ...

  8. Linux网络编程——原始套接字编程

    原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据.区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有 ...

  9. 原始套接字--arp相关

    arp请求示例 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <un ...

随机推荐

  1. CMS

    一.任务简介: 开发简单的CMS.在数据库中创建新闻数据库表news,包含(题目.作者.日期.正文等字段):创建HTML模板文件:读取数据库所有数据的信息,并使用新闻信息 替换模板文件中的占位符,从而 ...

  2. js的动态加载、缓存、更新以及复用(三)

    总体思路 1.  建立一个js服务,该服务实现通用js文件的加载.依赖.缓存.更新以及复用. 2.  各个项目如果使用通用js,可(bi)以(xu)使用js服务实现加载. 3.  Js服务只提供通用的 ...

  3. jQuery Tocify 定位导航

    Tocify是一个jQuery插件,能够动态的生成文章目录,Tocify可以随意的设置Twitter Bootstrap 或者 jQueryUI Themeroller支持的可选动画和jQuery的显 ...

  4. 通过Map 3D API读取线状要素的节点坐标

    By Daniel Du 在Map 3D中可以使用Create from Geometry命令把AutoCAD实体转换成Map 3D中的FDO要素,比如可以把AutoCAD的polyline转换成FD ...

  5. Vault 不同版本的API的异同

    大家知道,Autodesk Vault 2014有几个版本,依次为( Basic, Workgroup, Professional),不同版本的功能不相同,关于Vault产品功能的不同之处可以在Vau ...

  6. Sharepoint学习笔记—习题系列--70-576习题解析 -(Q4-Q5)

    Question 4 You are designing a SharePoint 2010 application to store 50 GB of digital assets, includi ...

  7. XMPP实现登陆注销功能

    XMPP框架的下载与导入等问题请参照 —— XMPP框架的分析.导入及问题解决 DEMO ——XMPP即时通讯(已导入框架)密码:3a7n 这篇我们利用XMPP框架来实现一下登陆功能,先来介绍一下XM ...

  8. iOS: setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name.

     这里指抛出一个假设:   如 果你在 storyboard中, 通过 Ctrl - Drag 方式声明了一个 @property , 但你又觉得 在 Ctrl - Drag 时 ,命名的proper ...

  9. iOS开发-生成随机数

    有时候我们需要在程序中生成随机数,但是在Objective-c中并没有提供相应的函数,好在C中提供了rand().srand().random().arc4random()几个函数.那么怎么使用呢?下 ...

  10. 【代码笔记】iOS-登陆单例

    一,工程图. 二,代码. UserInfo.h #import <Foundation/Foundation.h> @interface UserInfo : NSObject + (id ...