一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,ð_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,ð_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*)(ð_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,ð);
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,ð);
if(temp<0)
{
printf("ioctl--get hardware addr failed!\n");
exit(1);
}
memcpy(IP_addr ,&(((struct sockaddr_in *)(ð.ifr_addr))->sin_addr),4);
//关闭套接口
close(fd);
}
2) Ping程序
未完待续。。。
3) 路径MTU测试程序
未完待续。。。
4) 网络ip设备扫描程序
未完待续。。。