2017.05.23学习ARP协议好几天了。今天终于把在Linux上的ARP请求程序完成了。中间经历了好多坎坷。弄不出来搞的我十分紧张。终于在16:30分解决掉遇到的所有问题了。
下面简述下我的经历和存留的疑问。直接上代码,分析代码的问题。
**2017.06.04这下又无法淡定了。同样的程序我就加了一行
printf("mac=%02x:%02x:%02x:%02x:%02x:%02x\n", local_mac[0],local_mac[1], local_mac[2], local_mac[3],local_mac[4],local_mac[5]);
然后sendto又出现了invalid arguements的报错信息。再次删除这句printf语句,程序又可以正常执行了。
还请大神帮忙看看。小弟感激不尽。。**。
**2017.06.21补充。sendto()报错的问题终于解决了。彻底解决了。现在加上我的笔记。
arp发送和接受的socket有两种方法。不同的方法对应着不同的流程。
/* 先填充arp包头, padding使用bzero或者memset清空一下 */
1、sockfd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ARP) );
根据man 2 socket中的内容,这种方法已经不推荐使用了,但是继续使用也是可以的。
流程如下:
struct sockaddr sa;
bzero(&sa, sizeof(sa));
sa.sa_family = AF_INET; //指定地址族,要和socket()函数中使用的相同。
strcpy(sa.sa_data, “eth0”); //指定发送的接口。
sendto(sockfd, &arp_pck, sizeof(arp_pck), 0, &sa, sizeof(sa));
2、或者
sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
struct sockaddr_ll sa_ll;
bzero(&sa_ll, sizeof(sa_ll)); //之前忘记使用bzero清空,也会导致sendto报“invalid argument”错误。
sa_ll.sll_family = PF_PACKET,
sa_ll.sll_ifindex = if_nametoindex(“eth0”);
sendto(sockfd, &arp_pck, sizeof(arp_pck), 0, (struct sockaddr*)&sa_ll, sizeof(sa_ll) );
总结:sendto()报“invalid argument”带来的错误引出的思考和注意点。
1、 socket()函数的使用要注意,尤其是原始套接字。
2、 对于不同的socket()方法,使用的结构体不同。struct sockaddr和struct sockadd_ll。其数据成员的填充方式不同。
3、 上面提到的结构体,定义之后一定要进行清内存操作。否则也会导致sendto传入的参数错误。
**2017.06.21
链接:[http://pan.baidu.com/s/1pLFMjmN](http://pan.baidu.com/s/1pLFMjmN) 密码:yplq
MAC.h获取指定网卡的IP地址和MAC地址
#ifndef _MAC_H
#define _MAC_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
extern int get_eth_MAC(char *eth_name, unsigned char *MAC);
extern int get_eth_IP(char *eth_name, unsigned char *IP);
extern int get_eth_broadaddr(char *eth_name, unsigned char *broadaddr);
#endif
MAC.c暂时能用,还有简写。(可以加入一个选项参数,选择获取ip或者MAC)
#include "MAC.h"
int get_eth_MAC(char *eth_name, unsigned char *MAC)
{
struct ifreq ifr;
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd <0)
{
perror("socket");
return sock_fd;
}
strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );
int ret_ioctl = ioctl(sock_fd, SIOCGIFHWADDR, &ifr);
if(ret_ioctl <0)
{
perror("ioctl");
return ret_ioctl;
}
int i = 0;
for(i = 0 ; i <14; i++)
{
printf("%02x\t",(unsigned char)ifr.ifr_hwaddr.sa_data[i]);
}
printf("\n");
memcpy(MAC, ifr.ifr_hwaddr.sa_data, 6);
close(sock_fd);
return 0;
}
int get_eth_IP(char *eth_name, unsigned char *IP)
{
struct ifreq ifr;
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd <0)
{
perror("socket");
return sock_fd;
}
strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );
int ret_ioctl = ioctl(sock_fd, SIOCGIFADDR, &ifr);
if(ret_ioctl <0)
{
perror("ioctl");
return ret_ioctl;
}
int i = 0;
for(i = 0; i <14; i++)
{
printf("%d\t", (unsigned char)ifr.ifr_addr.sa_data[i]);
}
printf("\n");
memcpy(IP, ifr.ifr_addr.sa_data+2, 4);
close(sock_fd);
return 0;
}
int get_eth_broadaddr(char *eth_name, unsigned char *broadaddr)
{
struct ifreq ifr;
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd <0)
{
perror("socket");
return sock_fd;
}
strncpy(ifr.ifr_name,(char *)eth_name, sizeof(ifr.ifr_name) );
int ret_ioctl = ioctl(sock_fd, SIOCGIFBRDADDR, &ifr);
int i = 0;
for(i = 0; i <14; i++)
{
printf("%d\t", (unsigned char)ifr.ifr_broadaddr.sa_data[i]);
}
printf("\n");
memcpy(broadaddr, ifr.ifr_broadaddr.sa_data+2, 4);
close(sock_fd);
return 0;
}
main.c实现ARP请求的代码
#include "MAC.h"
#include
#pragma pack(1)
typedef struct
{
//DLC HEADER
unsigned char dlc_dst_mac[6];
unsigned char dlc_src_mac[6];
unsigned short dlc_frame;
//ARP PACKET
unsigned short arp_hwtype;
unsigned short arp_protype;
unsigned char arp_hwlen;
unsigned char arp_prolen;
unsigned short arp_op;
unsigned char arp_src_mac[6];
unsigned char arp_src_ip[4];
unsigned char arp_dst_mac[6];
unsigned char arp_dst_ip[4];
unsigne char arp_padding[18];
}arp_packet;
int main()
{
printf("sizeof(arp_packet)=%d\n", sizeof(arp_packet));
//fill ARP packet
arp_packet arp_pck;
unsigned char brd_mac[6] = {0xff,0xff,0xff,0xff,0xff,0xff}; //广播地址
unsigned char dst_ip[4] = {192,168,1,110}; //目标ip
unsigned char local_mac[6] = {0};
unsigned char local_ip[4] = {0};
get_eth_MAC("eth0", local_mac);
get_eth_IP("eth0", local_ip);
//DLC HEADER填充
memcpy(arp_pck.dlc_src_mac, local_mac, 6);
memcpy(arp_pck.dlc_dst_mac, brd_mac, 6);
arp_pck.dlc_frame = htons(ETH_P_ARP);
//arp 包填充
arp_pck.arp_hwtype = htons(0x0001);
arp_pck.arp_protype = htons(ETH_P_IP);
arp_pck.arp_hwlen = 6;
arp_pck.arp_prolen = 4;
arp_pck.arp_opr = htons(0x0001);
memcpy(arp_pck.arp_src_mac, local_mac, 6);
memcpy(arp_pck.arp_src_ip, local_ip, 4);
memcpy(arp_pck.arp_dst_mac, brd_mac, 6);
memcpy(arp_pck.arp_dst_ip, dst_ip, 4);
//struct sockaddr_ll
struct sockaddr_ll eth_in;
bzero(ð_in, sizeof(eth_in);
eth_in.sll_family = PF_PACKET;
printf("index=%d\n", eth_in.sll_ifindex = if_nametoindex("eth0") );
//raw_socket
int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
if(sockfd <0)
{
perror("socket");
return 0;
}
printf("sockfd=%d\n", sockfd);
int ret = sendto(sockfd, &arp_pck, sizeof(arp_pck), 0, (struct sockaddr *)ð_in, sizeof(eth_in) );
if(ret <0)
{
perror("sendto");
printf("errno=%d\n", errno);
}
return 0;
}
接下来就说说为了让程序能够正确运行经历的心酸历程:
1、关于sockaddr的困惑。
通过ioctl()函数可以获取指定网卡的IP和MAC值。参见博客。Linux底层网络编程–ARP,PING等
观察struct ifreq结构体代码片段:
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;
# define ifr_name ifr_ifrn.ifrn_name /* interface name */
# define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
# define ifr_addr ifr_ifru.ifru_addr /* address */
# define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
# define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
# define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
# define ifr_flags ifr_ifru.ifru_flags /* flags */
# define ifr_metric ifr_ifru.ifru_ivalue /* metric */
# define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
# define ifr_map ifr_ifru.ifru_map /* device map */
# define ifr_slave ifr_ifru.ifru_slave /* slave device */
# define ifr_data ifr_ifru.ifru_data /* for use by interface */
# define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
# define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
# define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */
# define ifr_newname ifr_ifru.ifru_newname /* New name */
发现ifr_name,ifr_addr,ifr_hwaddr等都是sockaddr结构体类型的数据:
#include
struct sockaddr
{
unsigned short sa_family; // 2 bytes address family, AF_xxx
char sa_data[14]; // 14 bytes of protocol address
};
// IPv4 AF_INET sockets:
struct sockaddr_in
{
short sin_family; // 2 bytes e.g. AF_INET, AF_INET6
unsigned short sin_port; // 2 bytes e.g. htons(3490)
struct in_addr sin_addr; // 4 bytes see struct in_addr, below
char sin_zero[8]; // 8 bytes zero this if you want to
};
struct in_addr
{
unsigned long s_addr; // 4 bytes load with inet_pton()
};
想IP地址,MAC,子网掩码等信息全都保存在sockaddr结构体的sa_data[14]成员中。这里我产生了疑问,这个sa_data[14]中ip、MAC、NETMASK是怎样的存储结构呢?所以在MAC.c代码中使用printf打印了sa_data[14]的信息。(要注意的是sa_data是char型数据,打印时类型强转成了unsigned char)。打印结果如下:
可以看到sa_data[0~5]存放的是MAC。sa_data[2~5]存放的是IP地址、广播地址。ioctl还有一些其他的REQUEST我没试。
2、蛋疼的问题(还没搞清楚)
编译时报错结构体成员中没有*成员。
3、struct sockaddr_ll头文件未包含报错。头文件为”linux/if_packet.h”
使用命令查找头文件grep -R “^struct sockaddr_ll” find / -name include -print
。
4、原始套接字出错
使用AF_INET时,错误。
int sockfd = socket(AF_INET, SOCK_RAW, htons(ETH_P_ARP) );
使用PF_PACKET或AF_PACKET都可以。实际上PF_PACKET和AF_INET数值相同,但是意义不同。PF_PACKET表示协议族,AF_PACKET表示地址族。
int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP) );
//也可以使用int sockfd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ARP) );
通过man socket可知要使用AF_PACKET:
5、sendto报错:invalid arguments,errno=22.
这个问题真是找了我老长时间。使用sendto在操作原始套接字和UDP套接字时,要用指导对端的地址。
于是定位到struct sockaddr_ll结构体赋值语句。发现问题其实不在此处。参见博客(本人真的是感谢这位博主,如果不是您的播客,我遇到的问题不知道什么时候才能查出来)linux原始套接字(1)-arp请求与接收
读过分析这篇博客的“ARP请求代码”发现博主直接使用数组来构造ARP包。42这个数字引起了我的注意。
因为在博客【linux环境编程】 ARP编程
这里明确写了还要18个填充字节。
char buf[42];
我已开始觉得会不会是结构体arp_packet长度过长。用了sizeof()一看结果是60。我又以为会不会是编译器默认4字节对齐,还特意加了一句#pragma pack(1)。发现sizeof()结果还是60。我就不淡定了。然后自己一计算6+6+2 +2+2+1+1+2+6+4+6+4+6+4=60。我尝试者把arp_padding[18]注释掉。结果sendto不再报错,程序运行无误。wireshark抓包结果如下。虚拟机给主机发送ARP包。
作为一个网络编程菜鸟,还要太多主要积累。特写出此经历,大家共勉。谢谢阅读。
2017.06.21添加的笔记内容解答了大部分我遇到的问题。