Linux底层网络编程--ARP,PING等

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/cjnewstar111/article/details/8070021

一linux系统中获取网卡信息

        获取网卡信息有两种方法。一种是读取系统文件。另外一种是通过系统API进行获取。

          1、  读取系统文件

        程序中通过读取/proc/net/dev文件即可以读取到系统中的所有网卡信息。该文件的内容

大致如下:

          容易发现左边红的方框中的就是主机的所有网络接口。然后读取该文件,解析出这些接口就可以了。

          2、  通过系统API进行获取

        主要是使用两个结构struct ifconf 和 struct ifreq。Ifconf结构主要有两个成员,一个是用来表示长度的,还有一个是指向struct ifreq的指针。通过宏ifc_buf和ifc_req来分别访问。具体步骤如下。首先创建一个socket套接字(SOCK_DGRAME类型),然后再通过ioctl(sock_fd, SIOCGCONF, &ifconf);这样就获得了所有的网卡信息。然后再通过操作irconf,把ifc_req指向的ifreq数组中的网卡信息取出来就可以了。

二、获取IP地址、MAC地址等

        这个方法和上面的API类似。使用ioctl(sock_fd, SIOCGADDR, &ifreq);来获取ip的信息,该ip信息保存在ifreq结构中。使用ioctl(sock_fd, SIOCGHWADDR, &ifreq);来获取硬件MAC地址信息。

        使用举例:

        /*使用API接口获取指定网卡的物理地址*/
void GetEthMAC(uint8 * eth_name , uint8 *mac)
{
      struct ifreq eth_if;
      int sock_fd;
      strncpy(eth_if.ifr_name,(char *)eth_name,sizeof(eth_if.ifr_name));
      sock_fd=socket(AF_INET,SOCK_DGRAM,0);
      ioctl(sock_fd,SIOCGIFHWADDR,&eth_if);
      int i;
      for(i=0;i<6;i++)
      {
            mac[i]=eth_if.ifr_hwaddr.sa_data[i];
      }
}
/*使用API接口获取指定网卡的IP地址*/
void GetEthIP(uint8 * eth_name,uint8 *ip_addr)
{
      struct ifreq eth_if;
      int sock_fd;
      strncpy(eth_if.ifr_name,(char *)eth_name,sizeof(eth_if.ifr_name));
      sock_fd=socket(AF_INET,SOCK_DGRAM,0);
      ioctl(sock_fd,SIOCGIFADDR,&eth_if);
      int i;
      for(i=2;i<6;i++)
      {
           ip_addr[i-2]=eth_if.ifr_addr.sa_data[i];
      }
}

三、获取网关的地址 

        通过读取系统文件可以获得网卡信息,难点在于对文件的处理。通过/proc/net/route文件即可以获取网关的地址。该文件的内容如下:

 

         红色方框中的即为网关地址,但这是一个整形的网关地址。

         使用举例:

/*proc方法获取网关地址*/
void GetGateWayIP(uint8 *ip_addr)
{
      char inf[100];
      FILE *file_fd;
      uint8 high=0,low=0,value;
      int i;
      file_fd = fopen("/proc/net/route","r");
      if(file_fd==NULL)
      {
            printf("can not open /proc/net/route\n");
      }
      else
     {
             while(!feof(file_fd))
             {
                     memset(inf,0,sizeof(inf));
                     fgets(inf,100,file_fd);
                     if(inf[5]=='0'&&inf[6]=='0'&&inf[7]=='0'&&inf[8]=='0'&&inf[9]=='0'&&inf[10]=='0'&&inf[11]=='0'&&inf[12]=='0')
                     {
                              for(i=20;i>=14;i-=2)
                              {
                                         if(inf[i]>=65)
                                                 high = inf[i]-55;
                                         else
                                                 high = inf[i]-48;
                                         if(inf[i+1]>=65)
                                                 low = inf[i+1]-55;
                                         else
                                                 low = inf[i+1]-48;
                                        value = high*16+low;
                                       ip_addr[10-i/2] = value;
                              }
                              break;
                       }
              }
      }
}

四、底层网络编程

        这里的底层是相对于TCP/IP系统的应用层来说的。应用层的网络编程可以不用关注底层的实现细节,这些对于编程者来说都是透明的。而底层的编程主要是和TCP/IP协议紧密关联的。比如设置IP头信息,设置TCP、UDP头信息,设置ICMP数据包,ARP地址解析协议等等。这一切能够实现的基础,就是SOCK_RAW——原始套接字。原始套接字的使用对于学习和掌握TCP/IP协议时非常有帮助的。总的来说,有两种用法:

socket(AF_INET, SOCK_RAW, IPPROTO_TCP);      // IPPROTO_UDP;  IPPOTO_ICMP等

socket(AF_PACKET, SOCK_RAW, ETH_P_ALL);     // ETH_P_ARP;  ETH_P_RARP等

这里的第一类用法主要用在组装传输层的数据包上使用。比如组装一个ICMP数据包,用来实现ping程序。这一层次的调用不关心ip数据头的信息,只关心ip头以上的信息。至于ip数据头以及以太网数据头的信息由系统来组装。使用sockadr_in结构来指明组装的ip头。但是注意此时的sockaddr_in结构中的端口号是不会起作用的。另外还需要非常注意的是这种方式收取到的数据是没有被剥离ip头部的。必须手动进行剥离,才能读取到需要的数据。

         使用这种方法虽然由系统自己组装了IP头,然而还是可以通过一定的方法来强制的设置IP头的。该方法就是setsockopt方法。例如下面所列举的方法几乎可以设置任何的IP头部数据:

setsockopt (socket_fd, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(ttl));   //设置TTL

setsockopt (socket_fd, IPPROTO_IP, IP_TOS, (char *)&tos, sizeof(tos)); //设置TOS服务

setsockopt (socket_fd, IPPROTO_IP, IP_OPTIONS, ***, ***);        //设置IP选项

setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&bflag, sizeof(bflag); //设置IP标志位

所以,总的来说,使用第一种原始套接字的方法,除了无法完成数据链路层的编程控制之外,几乎可以做任何IP层及以上的事情,所以这种套接字的作用是十分强大的。

         第二种套接字方法,对于发送的每一个数据包,需要从头到尾由程序员进行包装。包括以太网数据头。使用一个叫做sockaddr_ll(数据链路层通用头结构)的结构头来发送数据。该结构体需要指明协议名字以及使用哪个index来发送即可。如下所示填充该数据结构:

         Sockaddr_ll eth;

         eth.sll_family = PF_PACKET;

         eth.sll_ifindex = if_nametoindex(“eth0”);

用这种方法实现ARP地址解析是一个非常不错的选择。但是很多时候我们不需要自己动手组装一个以太网数据包,因为这是一件很困难的事,所以这种方法一般用来发送ARP或者RARP数据包以及用来侦听网卡信息,监控所有发送至本机的数据包之用。

五、linux网络底层编程实例

1)  ARP地址解析协议发送接收程序

/*
*   作者:newstar
*   用于简单的实现arp地址解析协议
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <linux/if.h>
#include <sys/ioctl.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>

//获取硬件网卡的相应信息
void GetEthInfor(char *name , char *MAC_addr , struct in_addr * IP_addr);
//arp包的结构定义
struct ARP_PACKET
{
       //以太网首部
 unsigned char dest_mac[6]; //6字节
 unsigned char sorce_mac[6];//6字节
 unsigned short type;       //2字节
  //arp——内容
 unsigned short hw_type;   //2字节:硬件地址类型     0x0001 表示mac地址
 unsigned short pro_type;  //2字节:软件地址类型    0x0806 表示IPV4地址
 unsigned char hw_len;     //1字节:硬件地址长度  
 unsigned char pro_len;    //1字节:软件地址长度
 unsigned short op;        //2字节:操作类型           0x0001表示ARP请求;0x0002表示ARP应答
 unsigned char from_mac[6];//6字节
 unsigned char from_ip[4]; //4字节
 unsigned char to_mac[6];  //6字节
 unsigned char to_ip[4];   //4字节
 unsigned char padding[18];//18字节:填充字节,因为以太网数据最少要46字节
};
//主函数
int main()
{
 int i = 0;
 int fd = 0;
 int num=0;
 unsigned char MAC_ADDR[6];
 struct in_addr IP_ADDR;
 struct ARP_PACKET arp_pk={0};
 struct sockaddr_ll eth_info;
 
      
 

 //第一步:获取指定网卡的信息(MAC地址和IP地址)
 GetEthInfor("eth0",MAC_ADDR,&IP_ADDR);
   /*printf("The MAC_addr is:");
 for(i =0 ;i<6;i++)
    printf("%4X",MAC_ADDR[i]); 
 printf("\n");
    printf("the IP is:%s\n",inet_ntoa(IP_ADDR));*/
      
    //第二步:填充ARP数据包的内容
 for(i=0;i<6;i++)                 //填充以太网首部的目的mac地址
 {
  arp_pk.dest_mac[i]=0XFF;      
 }
  
 for(i=0;i<6;i++)                 //填充以太网首部的源mac地址
 {
  arp_pk.sorce_mac[i]=MAC_ADDR[i];
 }
   arp_pk.type = htons(0x0806);    //填充以太网首部的侦类型
   arp_pk.hw_type = htons(0x0001); //填充硬件地址类型:0x0001表示的是MAC地址
   arp_pk.pro_type = htons(0x0800);//填充协议地址类型:0x0800表示的是IP地址
   arp_pk.hw_len = 6;              //填充硬件地址长度
   arp_pk.pro_len = 4;             //填充协议地址长度
   arp_pk.op = htons(0x0001);      //填充操作类型:0x0001表示ARP请求

   for(i=0;i<6;i++)                 //填充源mac地址
   {
  arp_pk.from_mac[i]=MAC_ADDR[i];
   }
   for(i=0;i<4;i++)                 //填充源IP地址
   {
  arp_pk.from_ip[i]=(inet_ntoa(IP_ADDR))[i];
   }
   for(i=0;i<6;i++)                 //填充欲获取的目的mac地址
   {
    arp_pk.to_mac[i]=0X00;
   }
  
   arp_pk.to_ip[0]=0X0A;        //填充想要装换为MAC地址的IP地址。可以使用命令行参数来做
   arp_pk.to_ip[1]=0X40;
   arp_pk.to_ip[2]=0X39;
   arp_pk.to_ip[3]=0X0A;
 
 //第三步:填充sockaddr_ll eth_info结构
    eth_info.sll_family = PF_PACKET;
 eth_info.sll_ifindex = if_nametoindex("eth0");
 //printf("number is:%d\n",eth_info.sll_family);
 
 //第四步:创建原始套接字
 fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));  //疑问:为什么这里需要htons函数进行转换
 if(fd<0)
 {
  printf("socket SOCK_RAW failed!\n");
  exit(1);
 }

 //第五步:发送ARP数据包
 num = sendto(fd , &arp_pk , sizeof(struct ARP_PACKET) , 0 ,(struct sockaddr*)(&eth_info),sizeof(eth_info));
 if(num<0)
 {
  printf("sendto failed!\n");
  exit(1);
 }
        //第六步:接受ARP应答
 num = recvfrom(fd , &arp_pk , sizeof(struct ARP_PACKET) ,0,NULL,0);

 if(num<0)
 {
  printf("rcvfrom failed!\n");
  exit(1);
 }
 else
 {
  printf("I receive %d bytes!\n",num);
  printf("the mac  is:");
  for(i=0;i<6;i++)
  {
   printf("%4X ",arp_pk.from_mac[i]);
  }
  printf("\n");
 }

 close(fd);
 return 0;
}

void GetEthInfor(char *name ,  char *MAC_addr , struct in_addr * IP_addr)
{
 struct ifreq  eth;  //够结构用于存放最初多获取的接口信息
   int fd;             //用于创建套接字
 int temp=0;         //用于验证接口调用
 int i=0;            //用于循环
 
 strncpy(eth.ifr_name,name,sizeof(struct ifreq)-1);
 fd = socket(AF_INET,SOCK_DGRAM,0);
 if(fd<0)
 {
   printf("socket failed!\n");
   exit(1);
 }
        //获取并且保存和打印指定的物理接口MAC地址信息
 temp = ioctl(fd,SIOCGIFHWADDR,&eth);
 if(temp<0)
 {
  printf("ioctl--get hardware addr failed!\n");
  exit(1);
        }
 strncpy(MAC_addr,eth.ifr_hwaddr.sa_data,6);
 /*printf("The MAC_addr is:");
 for(i =0 ;i<6;i++)
  printf("%4X",(unsigned char)eth.ifr_hwaddr.sa_data[i]); 
 printf("\n");*/
       
        //获取并且保存和打印指定的物理接口IP地址信息
   temp = ioctl(fd,SIOCGIFADDR,&eth);
 if(temp<0)
 {
  printf("ioctl--get hardware addr failed!\n");
  exit(1);
        }
 memcpy(IP_addr ,&(((struct sockaddr_in *)(&eth.ifr_addr))->sin_addr),4);
 //关闭套接口
   close(fd);
}

2)  Ping程序

        未完待续。。。

3)  路径MTU测试程序

        未完待续。。。

4)  网络ip设备扫描程序

        未完待续。。。

猜你喜欢

转载自blog.csdn.net/cjnewstar111/article/details/8070021