在写路由器时,涉及到对ARP数据包的传输和接收!本来我是打算将广播,传输,接收一块儿写完之后再来说这块儿的,但是下午突然就没心思了,明天开始要休息五天,所以决定先把目前写了的部分讲述一下吧!
我们先来看看ARP数据包的结构:
一个ARP数据包为42个字节,前14个字节为以太网首部,后28个字节为ARP请求/应答部分。
在看代码之前,我们首先得弄懂,ARP数据包的传输原理。假设A想B发送一个数据包,A并不知道B在哪里,那么A首先会发一个广播的ARP请求,这个网段上的所有计算机(电脑)都会接收到来自A的ARP请求,由于每台计算机(电脑)都有自己唯一的MAC和IP,那么它会分析目的IP是不是自己的IP,如果不是,网卡会自动丢弃数据包。如果B接收到了,经过分析,目的IP是自己的,于是更新自己的ARP高速缓存,记录下A的IP和MAC。然后B就 会回应A一个ARP应答,就是把A的源IP,源MAC变成现在目的IP,和目的MAC,再带上自己的源IP,源MAC,发送给A。当A机接收到ARP应答后,更新自己的ARP高速缓存,即把arp应答中的B机的源IP,源MAC的映射关系记录在高速缓存中。那么现在A机中有B的MAC和IP,B机中也有A的MAC和IP。arp请求和应答过程就结束了。
先来看看写的两个函数接口吧,详解请看注释:
第一个是ARP数据包的传输:
1 void sendArp(int sockfd,char *ip) 2 { 3 int socketfd = sockfd; 4 unsigned char sendmsg[42] = 5 { 6 /*前14位是以太网手部,后28位是ARP请求/应答 7 MAC地址是是由48个字节组成的地址,为方便起见,通常用12位16进制表示, 8 前6位16进制数字由IEEE负责统一分发,后6位16进制数字由各厂商自己负责管理。*/ 9 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //IEEE制定 10 0x00, 0x0c, 0x29, 0x42, 0xae, 0x53, //这是我的MAC地址 11 0x08, 0x06, //这是ARP的协议号,IEEE制定 12 13 0x00, 0x01, //硬件类型0x0001表示以太网 14 0x08, 0x00, //协议类型0x0800表示IP协议 15 0x06, 0x04, //硬件地址(MAC地址)长度为6,协议地址(IP地址)长度为4 16 0x00, 0x0c, 0x29, 0x44, 0xae, 0x53, //发送端的MAC地址 17 192, 168, 44, 153, //发送端的IP地址 18 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //目的MAC地址,先置0,等待填写 19 192, 168, 44, 156 //目的IP地址 20 }; 21 22 int aim_ip; 23 inet_pton(AF_INET, ip, &aim_ip); 24 memcpy(&sendmsg[38],&aim_ip,4); //待填写目的IP地址 25 printf("the aim_ip is %d.%d.%d.%d \n",sendmsg[38],sendmsg[39],sendmsg[40],sendmsg[41]); 26 27 struct sockaddr_ll sll; //原始套接字地址结构 28 struct ifreq ethreq; //网络接口地址 29 30 /*更新自己的ARP高速缓存*/ 31 if(send_msg[41]==156) 32 { 33 strncpy(ethreq.ifr_name, "eth1", IFNAMSIZ); //指定网卡名称 34 memcpy(&send_msg[28],net_interface[2].ip,4); 35 memcpy(&send_msg[6],net_interface[2].mac,6); 36 } 37 if(send_msg[41]==153) 38 { 39 strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ); //指定网卡名称 40 memcpy(&sendmsg[28],net_interface[1].ip,4); 41 memcpy(&sendmsg[6],net_interface[1].mac,6); 42 } 43 44 //把接口索引存入ethreq.ifr_ifindex 45 if( -1 == ioctl(socketfd,SIOCGIFINDEX,(char *)ðreq)) 46 { 47 perror("SIOCGIFINDEX ioctl"); 48 return; 49 } 50 51 bzero(&sll,sizeof(struct sockaddr_ll)); 52 sll.sll_ifindex = ethreq.ifr_ifindex; 53 54 if( -1 == sendto(socketfd, sendmsg, 42, 0 , (struct sockaddr *)&sll, sizeof(sll))) 55 { 56 perror("sendto"); 57 return; 58 } 59 }
第二个是广播ARP请求:
1 void brdcast(int sockfd) 2 { 3 int socketfd = sockfd; 4 unsigned char sendmsg[42] = 5 { 6 /*前14位是以太网手部,后28位是ARP请求/应答 7 MAC地址是是由48个字节组成的地址,为方便起见,通常用12位16进制表示, 8 前6位16进制数字由IEEE负责统一分发,后6位16进制数字由各厂商自己负责管理。*/ 9 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, //IEEE制定 10 0x00, 0x0c, 0x29, 0x42, 0xae, 0x53, //这是我的MAC地址 11 0x08, 0x06, //这是ARP的协议号,IEEE制定 12 13 0x00, 0x01, //硬件类型0x0001表示以太网 14 0x08, 0x00, //协议类型0x0800表示IP协议 15 0x06, 0x04, //硬件地址(MAC地址)长度为6,协议地址(IP地址)长度为4 16 0x00, 0x0c, 0x29, 0x44, 0xae, 0x53, //发送端的MAC地址 17 192, 168, 44, 153, //发送端的IP地址 18 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //目的MAC地址,先置0,等待填写 19 192, 168, 44, 156 //目的IP地址 20 }; 21 22 int tmpi; 23 int tmpj; 24 struct ifreq ethreq; 25 struct sockaddr_ll sll; 26 tmpj = int getinterfaceNumber(); //这里的getinterfaceNumber()会在下面进行解释 27 28 for(tmpi = 1;tmpi <tmpj ;tmpi++) 29 { 30 memcpy(&sendmsg[38],net_interface[2].ip,4); //这里的net_interface会在下面进行解释 31 memcpy(&send_msg[6],net_interface[tmpi].mac,6); 32 memcpy(&send_msg[28],net_interface[tmpi].ip,4); 33 34 //把接口索引存入ethreq.ifr_ifindex 35 if( -1 == ioctl(socketfd,SIOCGIFINDEX,(char *)ðreq)) 36 { 37 perror("SIOCGIFINDEX ioctl"); 38 return; 39 } 40 41 bzero(&sll,sizeof(struct sockaddr_ll)); 42 sll.sll_ifindex = ethreq.ifr_ifindex; 43 44 if( -1 == sendto(socketfd, sendmsg, 42, 0 , (struct sockaddr *)&sll, sizeof(sll))) 45 { 46 perror("sendto"); 47 return; 48 } 49 } 50 return 51 }
在代码里,有出现一些莫名其妙的函数或者结构体,比如net_interface和getinterfaceNumber(),net_interface里存储的是网络接口里读取到的一些信息,比如ip地址,子网掩码,mac地址等等,getinterfaceNumber()的返回值是接入的数量。这些是在另外的一个.c里实现的,我在这里是直接调用的!
具体的网口信息读取的实现方法在我的上一篇博客里有介绍:自己动手写路由器之ioctl获取网络接口信息
由于我的这个路由器刚写没多久,目前只是写了部分接口而已,我是写一点儿记录一点儿,有些代码未经测试我就放上来了,所以若代码里有问题,请指出来,我会进行修改的。当所有工作全部完成之时,我会将整个所编写的路由器代码公布上来的。