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中,这两个放到后面讲解

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐