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

本文详细介绍Linux环境下网络编程的多种方法,包括获取网卡信息、IP地址、MAC地址及网关地址的具体步骤。此外,还提供了底层网络编程实例,如ARP地址解析协议的发送接收程序,帮助读者深入理解Linux网络编程技术。

一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设备扫描程序

        未完待续。。。

 

<think>嗯,用户这次的问题是关于在Linux系统中实现带有路由追踪功能的ping命令,具体询问如何使用或实现ping-R命令的路由记录功能。用户之前已经提供了三个关于普通ping命令的参考资料,但明显需要更高级的功能。用户应该是个网络管理员或者运维工程师,正在排查网络路径问题。因为普通ping只能检测连通性,而-R参数需要记录数据包路径,这在诊断跨多个路由节点的网络故障时特别有用。从提问的专业性看,用户对网络基础有了解,但可能不熟悉高级工具的组合使用。我注意到几个关键点:用户提供的参考资料[3]提到某些服务器会禁止ping响应,这意味着在实现-R功能时需要考虑ICMP限制情况。同时参考资料[2]解释了ARP获取网关MAC的过程,这对理解路由追踪的底层机制很有帮助。真正的挑战在于:标准ping-R参数其实有局限性——它依赖IP协议的RR选项,但现代路由器普遍禁用此功能。这解释了为什么用户可能需要替代方案。考虑到用户明确要求“实现”,而不仅是使用原生命令,应该提供三种层级的解决方案:原生命令的简便用法(尽管效果有限)、替代工具traceroute的常规用法,以及深度定制的解决方案。需要特别提醒用户权限问题,因为创建RAW套接字需要root权限。另外测试案例的IP示例要通用化,不能使用真实内网地址。对于技术实现部分,Python演示代码要包含超时处理和错误捕获,这是实际运维时最容易忽略的点。相关问题的设计要有递进性:从底层协议原理到可视化工具,最后延伸到生产环境的最佳实践。其中“mtr工具”的推荐很重要,这是多数运维容易忽略的效率工具。</think> 在 Linux 系统中,标准 `ping` 命令并不原生支持等同于 Windows 下 `ping -R` 的 **完整路由记录与回显** 功能。`ping -R` 在 Windows 中尝试**记录并回显往返路径上的所有路由节点 IP**(但常因路由器禁用该特性而失效)。 Linux 下实现类似 **路径追踪 + 连通性检测** 的功能,主要有以下方法: --- ### ⚡ 1. **原生 `ping` 的有限路由记录 (RR) 选项** * **命令**: ```bash ping -R <目标主机或IP> ``` * **作用**:`-R` 选项尝试在 ICMP Echo 请求中**启用 IP 的 "Record Route" (RR) 选项**。 * **限制**: * 最多只能记录 **9 跳**的路由 (IPv4 报头限制)[^1]。 * **绝大多数路由器出于安全和性能考虑,默认丢弃或忽略包含 RR 选项的数据包**,因此通常**不显示或仅显示部分跳数**[^3]。 * **无法显示双向路径**(请求路径可能成功记录少量节点,但响应路径通常无记录)。 * **适用场景**:仅作为实验性尝试,实践中**可靠性极低**。 --- ### 🚀 2. **使用 `traceroute` + `ping` 组合 (推荐方案)** 这是 Linux 下实现"**记录路径 + 检查连通性**"的标准方式: * **步骤 1:执行路由追踪 (查看完整路径)** ```bash traceroute <目标主机或IP> # 或更快的版本 tracepath <目标主机或IP> ``` * `traceroute`/`tracepath` 会**列出到达目标路径上所有路由器的 IP 地址 (跳点)**,显示逐跳延迟,明确显示路径。 * **步骤 2:执行 Ping 测试 (检查端到端连通性)** ```bash ping <目标主机或IP> ``` * **优点**: * 能清晰看到**完整路径**(远超 9 跳)。 * 能准确测量**每跳延迟**和端到端延迟/丢包率。 * 工具成熟且默认安装[^1][^3]。 --- ### 🛠️ 3. **使用 `mtr` (My Traceroute) - 终极工具** `mtr` **整合了 `traceroute` 和 `ping` 的功能**,实时显示路径状况: * **命令**: ```bash mtr <目标主机或IP> # 交互式实时视图 mtr -r <目标主机或IP> # 生成一次性报告 ``` * **功能亮点**: * **实时动态更新**每跳的丢包率(%)、延迟(ms)。 * 同时观测**路径和连通性**。 * **可视化分析**网络瓶颈点。 * 是专业网络诊断的首选工具[^1][^3]。 * **安装** (若未预装): ```bash # Debian/Ubuntu sudo apt install mtr # CentOS/RHEL sudo yum install mtr ``` --- ### 🔧 4. **手动实现 (Python 示例)** 若需编程实现类似 `traceroute + ping` 的功能,可借助 Scapy (需 root): ```python # 安装: sudo pip install scapy from scapy.all import * import time def trace_and_ping(dst): # 执行 Traceroute 获取路径 ans, unans = traceroute(dst, maxttl=30, verbose=False) # 提取路径中的 IP 跳点 hops = [rcv.src for snd, rcv in ans if rcv.src != dst][:-1] if hops: print(f"路径节点: {' -> '.join(hops)}") # 执行 Ping print(f"\nPing {dst} 结果:") ans = sr1(IP(dst=dst)/ICMP(), timeout=2, verbose=False) if ans: print(f"目标可达! 响应时间: {time.time() - snd.time} ms") else: print("目标不可达或超时") # 测试 trace_and_ping("8.8.8.8") ``` --- ### ✅ **总结与建议** 1. **了解 `ping -R` 的局限**:Linux 下的 `-R` 本质依赖 IP RR 选项,**实践中基本不可用**[^1][^3]。 2. **标准方案**: * 先用 `traceroute`/`tracepath` **追踪路径**。 * 再用 `ping` **测试连通性**。 3. **高效方案**:直接使用 `mtr` 进行**集成路径分析 + 连续性测试**。 4. **编程实现**:利用 `scapy` 库组合 TTL 探测与 ICMP 请求 (需 root 权限)。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值