Linux之socket编程:ioctl函数介绍(ifconfig例子)
ioctl函数一直作为那些不适合归入其他精细定义类别的特性的系统接口(即特殊的接口),POSIX一直致力拜托处于标准化过程中的特定功能的ioctl接口,如使用新的函数sockatmark函数取代SIOCATMARK,但是与网络相关并且依赖于实现的特性保留的ioctl请求为数依然不少,主要用于获取接口信息、访问路由表、访问ARP高速缓存等等。函数的原型如下,其中返回0表示成功,返回-1表示失败。r.
ioctl函数一直作为那些不适合归入其他精细定义类别的特性的系统接口(即特殊的接口),POSIX一直致力拜托处于标准化过程中的特定功能的ioctl接口,如使用新的函数sockatmark函数取代SIOCATMARK,但是与网络相关并且依赖于实现的特性保留的ioctl请求为数依然不少,主要用于获取接口信息、访问路由表、访问ARP高速缓存等等。
函数的原型如下,其中返回0表示成功,返回-1表示失败。request参数为请求的类型,根据类型不同后面的参数类型也不同。
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
这里主要介绍下与接口有关的操作请求,在Linux中要获取接口的索引、名字、标志、IP地址、物理地址、网卡映射、MTU等等。这些信息定义在struct ifreq结构体中,ifreq(interface request),其定义在net/if.h中,主要成员如下:
struct ifreq
{
# define IFHWADDRLEN 6
# define IFNAMSIZ IF_NAMESIZE
union
{
char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */
} ifr_ifrn;
union
{
struct sockaddr ifru_addr; /*网卡的IP地址*/
struct sockaddr ifru_dstaddr; /*点到点的地址*/
struct sockaddr ifru_broadaddr; /*广播地址*/
struct sockaddr ifru_netmask; /*子网掩码地址*/
struct sockaddr ifru_hwaddr; /*硬件地址,即Mac地址*/
short int ifru_flags; /*网卡接口标志*/
int ifru_ivalue; /*这个值根据请求类型来定,可以为metric、网卡index、带宽、队列长度等*/
int ifru_mtu; /*MTU值*/
struct ifmap ifru_map; /*网卡映射信息*/
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ]; /*网卡新的名字*/
__caddr_t ifru_data;
} ifr_ifru;
};
其中常用的IOtype类型有如下,网络中都是以SIOC开头要是获取信息的话为SIOCG,设置参数的时候为SIOCS,常用的如下:
附加有SIOGIFTXQLEN(获取发送队列的长度),SIOCGIFMAP(获取网络设备地址映射空间)这两个也可以设置。其中SIOCGIFCONF可以获取设备上的所有接口,即接口列表。其结构定义如下,其中ifc_len为buff的长度,ifcu_req为获取到的网卡信息的开头指针。在获取列表的时候需要注意的是其buf的长度要足够大,能容纳下所有的接口信息。返回时信息如图,ifc_len指出了获取到的ifc_req的长度,而ifcu_req中的值为网络的名字以及IP地址。
struct ifconf
{
int ifc_len; /* Size of buffer. */
union
{
__caddr_t ifcu_buf;
struct ifreq *ifcu_req;
} ifc_ifcu;
};
# define ifc_buf ifc_ifcu.ifcu_buf /* Buffer address. */
# define ifc_req ifc_ifcu.ifcu_req /* Array of structures. */
# define _IOT_ifconf _IOT(_IOTS(struct ifconf),1,0,0,0,0) /* not right */
#endif /* Misc. */
下面的代码就是使用ioctl来模拟ifconfig来获取设备上网卡的信息,其中网卡数据包的统计信息可以直接读取/proc/net/dev文件,里面会有显示的。ioctl的方法暂时没有找到,如果有的话请大神指点下。
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
#include<string.h>
#include<net/if.h>
#include<sys/ioctl.h>
#include<netinet/in.h>/*struct sockaddr_in定义的头文件*/
#include<arpa/inet.h>
#define IFI_NAME 16
#define IFI_HADDR 8
typedef struct ifi_info ifi_info_st;
struct ifi_info{
char name[IFI_NAME];
short index;
short mtu;
u_char haddr[IFI_HADDR];
u_short hlen;
short flags;
short myflags;
int Metric;
struct sockaddr *addr;
struct sockaddr *brdaddr;
struct sockaddr *dstaddr;
struct sockaddr *netmask;
struct ifmap *if_map;
ifi_info_st *next;
};
/*释放开辟的空间*/
void free_mem(void*ifihead)
{
ifi_info_st *ifi = (ifi_info_st*)ifihead,*ifinext;
while(ifi){
ifinext = ifi->next;
if(ifi->addr != NULL)
free(ifi->addr);
if(ifi->brdaddr != NULL)
free(ifi->brdaddr);
if(ifi->dstaddr != NULL)
free(ifi->dstaddr);
if(ifi->netmask != NULL)
free(ifi->dstaddr);
if(ifi->if_map != NULL)
free(ifi->if_map);
free(ifi);
ifi = ifinext;
}
}
/*获取所有接口的信息*/
ifi_info_st* get_ifi_info(int family,int doaliases)
{
ifi_info_st *ifi,*ifihead,**ifinext;
int sockfd,len,lastlen,flags,myflags,idx = 0,hlen = 0,ret ;
char *ptr,*buf,lastname[IFI_NAME],*cptr,*haddr,*sdlname;
struct ifconf ifc;
struct ifreq *ifr,ifrcopy;
struct sockaddr sinptr;
/*socket的类型可以是TCP也可以是UDP或其它的*/
sockfd = socket(AF_INET,SOCK_DGRAM,0);
/*试探获取buf的长度*/
lastlen = 0;
len = 100*sizeof(struct ifreq);
for(;;){
buf = malloc(len);
if(buf == NULL){perror("malloc failed");close(sockfd);exit(-1);}
ifc.ifc_len = len;
ifc.ifc_buf = buf;
if(ioctl(sockfd,SIOCGIFCONF,&ifc)<0){
perror("ioctl failed\n");
}else{
if(ifc.ifc_len == lastlen){
break;
}
lastlen = ifc.ifc_len;
}
len += 10*sizeof(struct ifreq);
free(buf);
}
ifihead = NULL;
ifinext = &ifihead;
lastname[0] = 0;
/*解析各个网络接口、以及获取网络接口中的其它信息*/
for(ptr = buf;ptr < buf + ifc.ifc_len;){
ifr = (struct ifreq*)ptr;
len = sizeof(struct sockaddr);
ptr += sizeof(struct ifreq);
if(ifr->ifr_addr.sa_family != family)
continue;
myflags = 0;
if((cptr = strchr(ifr->ifr_name,':')) != NULL)
*cptr = 0;
if(strncmp(lastname,ifr->ifr_name,IFI_NAME) == 0){
myflags = 1;
if(doaliases == 0){continue;}
}
memcpy(lastname,ifr->ifr_name,IFI_NAME);
/*获取flag,参见的UP、BOARDCAT等*/
ifrcopy = *ifr;
ioctl(sockfd,SIOCGIFFLAGS,&ifrcopy);
flags = ifrcopy.ifr_flags;
if((flags & IFF_UP) == 0)
continue;
ifi = calloc(1,sizeof(ifi_info_st));
//memset(ifi,0,sizeof(ifi_info_st));
*ifinext = ifi;
ifinext = &ifi->next;
ifi->flags = flags;
ifi->myflags = myflags;
/*获取MTU*/
ioctl(sockfd,SIOCGIFMTU,&ifrcopy);
ifi->mtu = ifrcopy.ifr_mtu;
memcpy(ifi->name,ifr->ifr_name,sizeof(ifr->ifr_name));
ifi->name[IFI_NAME-1] = 0;
sinptr = ifr->ifr_addr;
ifi->addr = calloc(1,sizeof(struct sockaddr));
memcpy(ifi->addr,&sinptr,sizeof(struct sockaddr));
/*获取广播地址*/
if(flags & IFF_BROADCAST){
ioctl(sockfd,SIOCGIFBRDADDR,&ifrcopy);
sinptr = ifrcopy.ifr_broadaddr;
ifi->brdaddr = calloc(1,sizeof(struct sockaddr));
memcpy(ifi->brdaddr,&sinptr,sizeof(struct sockaddr));
}
/*获取metric的值,但是这里获取到的是0??请求指点*/
ret = ioctl(sockfd,SIOCGIFMETRIC,&ifrcopy);
if(ret == -1){perror("ioctl SIOCGIFMETRIC failed");}
ifi->Metric = ifrcopy.ifr_metric;
/*获取硬件地址*/
ret = ioctl(sockfd,SIOCGIFHWADDR,&ifrcopy);
if(ret == -1){perror("ioctl SIOCGIFHWADDR failed");}
memcpy(ifi->haddr,ifrcopy.ifr_hwaddr.sa_data,6/*sizeof(ifrcopy.ifr_hwaddr.sa_data)*/);
ifi->hlen = 6;
/*获取子网掩码*/
ret = ioctl(sockfd,SIOCGIFNETMASK,&ifrcopy);
if(ret == -1){perror("ioctl SIOCGIFNETMASK failed");}
else{
ifi->netmask = calloc(1,sizeof(struct sockaddr));
memcpy(ifi->netmask,&ifrcopy.ifr_netmask,sizeof(struct sockaddr));
}
/*获取网卡的映射参数,有开始地址、结束地址、基地址、中断、DMA、端口等
*这些参数在网卡驱动中有维持,在struct net_device中保存着
*/
ret = ioctl(sockfd,SIOCGIFMAP,&ifrcopy);
if(ret == -1){perror("get interface map failed");}
else{
ifi->if_map = calloc(1,sizeof(struct ifmap));
if(!ifi->if_map){perror("calloc failed");}else{
memcpy(ifi->if_map,&ifrcopy.ifr_map,sizeof(struct ifmap));
}
}
#if 0
if(flags & IFF_POINTOPOINT){
ioctl(sockfd,SIOCGDSTADDR,&ifrcopy);
sinptr = ifrcopy.ifr_broadaddr;
ifi->brdaddr = calloc(1,sizeof(struct sockaddr));
memcpy(ifi->brdaddr,&sinptr,sizeof(struct sockaddr));
}
#endif
}
free(buf);
return ifihead;
}
/*把获取到的数据转换为点分十进制的字符串类型*/
char* convert_ip(void*s)
{
static char buf[32];
memset(buf,0,sizeof(buf));
struct sockaddr_in *p = (struct sockaddr_in*)s;
inet_ntop(AF_INET,&p->sin_addr,buf,sizeof(buf));
return buf;
}
/*获取网卡信息以及打印*/
int main(int argc ,char* argv[])
{
ifi_info_st *ifi,*ifihead;
struct sockaddr *sa;
u_char *ptr;
int i,family,doaliases;
if(argc != 3){printf("usage:prifinfo<inet4 | inet6> <doaliases>\n");exit(-1);}
if(strcmp(argv[1],"inet4") == 0){
family = AF_INET;
}else if(strcmp(argv[1],"inet6") == 0){
family = AF_INET6;
}else{
printf("invalid <address-family>\n");
exit(-1);
}
doaliases = atoi(argv[2]);
for(ifihead = ifi = get_ifi_info(family,doaliases);ifi != NULL;ifi = ifi->next){
printf("%s: ",ifi->name);
if(ifi->index != 0){printf("(%d) ",ifi->index);}
//printf("<");
if(ifi->flags & IFF_UP) printf("UP ");
if(ifi->flags & IFF_BROADCAST) printf("BROADCAST ");
if(ifi->flags & IFF_DEBUG) printf("DEBUG ");
if(ifi->flags & IFF_LOOPBACK) printf("LOOPBACK ");
if(ifi->flags & IFF_POINTOPOINT) printf("POINTOPOINT ");
if(ifi->flags & IFF_RUNNING) printf("RUNNING ");
if(ifi->flags & IFF_MULTICAST) printf("MULTICAST ");
if(ifi->flags & IFF_PORTSEL) printf("PORTSEL ");
if(ifi->flags & IFF_AUTOMEDIA) printf("AUTOMEDIA ");
if(ifi->flags & IFF_DYNAMIC) printf("DYNAMIC ");
printf("MTU:%d Metric:%d\n",ifi->mtu,ifi->Metric);
//printf(">\n");
if((i = ifi->hlen) > 0){
ptr = ifi->haddr;
printf("HWaddr ");
do{
printf("%s%02x",(i == ifi->hlen)?" ": ":",*ptr++);
}while(--i > 0);printf("\n");
}
if((sa = ifi->addr) != NULL){
printf("addr:%s ",convert_ip(sa));
}
if((sa = ifi->brdaddr) != NULL){
printf("Bcast:%s ",convert_ip(sa));
}
if((sa = ifi->dstaddr) != NULL){
printf("destination addr:%s\n",convert_ip(sa));
}
if((sa = ifi->netmask) != NULL){
printf(" Mask:%s\n",convert_ip(sa));
}
}
free_mem(ifihead);
return 0;
}
效果如下图,这里的metric获取和ifconfig的不一致,欢迎大神指点。这里只是为了讲解网络中的ioctl函数的使用,ifconfig的源码实现可以查看busbox中的。数据包的统计可以读取proc/net/dev文件内容
最后贴下和网络映射有关的结构体,struct ifmap。这个结构体获取到的信息是网卡net_device中的信息。
struct ifmap
{
unsigned long int mem_start;
unsigned long int mem_end;
unsigned short int base_addr;
unsigned char irq;
unsigned char dma;
unsigned char port;
/* 3 bytes spare */
};
其中ioctl函数中type的类型还有别的,比如操作ARP和路由的,如下。但是操作ARP和路由基本上使用路由域套接字来实现。
其中数据结构的定义在net/if_arp.h,以及net/toute.h中,这两个放到后面讲解
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)