文件描述符是低层的输入和输出接口。通过本文可以了解更多关于套接字 I/O 控制 (ioctl) 命令的内容,以及如何使用它们完成各种网络相关的操作.操作系统为套接字、路由表、ARP 表、全局网络参数和接口提供了相应的控制操作方式。本文适用于对IPv4和IPv6堆栈网络级操作感兴趣的 AIX® Version 5.3 开发人员。 描述符可以表示到设备、管道或套接字的连接,这些连接用于与另一个进程或普通文件进行通信。I/O 控制 (ioctl) 函数调用可以用来对特殊文件的基础设备参数进行操作。它们可以完成与打开的文件描述符相关联的控制功能。这些命令涉及文件、流、普通数据链路控制以及其他各种设备 本文将讨论 AIX® Version 5.3 中提供的与网络操作和套接字相关的命令。在下列文件中列出了与套接字相关的命令和结构:
sys/ioctl.h
net/if_arp.h
net/if.h
net/netopt.h
netinet/in.h
应用程序开发人员可以使用这些命令,并且在 AIX Version 5.3 的文档中对这些命令进行了详细的描述。本文说明了有关IPv6地址和IPv4 堆栈的常用命令的典型用法。
一、 什么是ioctl。 ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数 如下: int ioctl(int fd, ind cmd, …); 其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和 cmd的意义相关的。 ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。 二、 ioctl 命令码 在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事 情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。 命令码的组织是有一些讲究的, 因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的 命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。 所以在Linux核心中是这样定义一个命令码的: ____________________________________ | 设备类型 | 序列号 | 方向 |数据尺寸| |----------|--------|------|--------| | 8 bit | 8 bit |2 bit |8~14 bit| |----------|--------|------|--------| 这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以 Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从 命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数 据传送方向和数据传输尺寸。 三、 cmd 下面的清单介绍了一些最重要的结构,使用 ioctl 套接字命令时常常用到这些结构。 清单 1. struct ifreq (/usr/include/net/if.h
/* Interface request structure used for socket
* ioctl's. All interface ioctl's must have parameter
* definitions which begin with ifr_name. The
* remainder may be interface specific.
*/
struct ifreq {
#ifndef IFNAMSIZ
#define IFNAMSIZ 16
#endif
char ifr_name[IFNAMSIZ];
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
__ulong32_t ifru_flags;
int ifru_metric;
caddr_t ifru_data;
u_short ifru_site6;
__ulong32_t ifru_mtu;
int ifru_baudrate;
} ifr_ifru;
Following macros are provided for convenience
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-to-p link */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_metric /* metric */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_site6 ifr_ifru.ifru_site6 /* IPv6 site index */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu of interface */
#define ifr_isno ifr_ifru.ifru_data /* pointer to if_netopts */
#define ifr_baudrate ifr_ifru.ifru_baudrate /* baudrate of interface */
};
|
清单 2. struct ifconf (/usr/include/net/if.h)
/*
* Structure used in SIOCGIFCONF request.
* Used to retrieve interface configuration
* for machine (useful for programs which
* must know all networks accessible).
*/
struct ifconf {
int ifc_len; /* size of associated buffer */
union {
caddr_t ifcu_buf;
struct ifreq *ifcu_req;
} ifc_ifcu;
Following macros are provided for convenience
#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */
#define ifc_req ifc_ifcu.ifcu_req /* array of structures returned */
};
|
下面的表 1 介绍了一些接口检索命令。该信息来源于 IBM System p™ 和 AIX。
ioctl 命令 | 描述 | |
---|---|---|
SIOCGSIZIFCONF | 获得获取 SIOCGIFCONF 返回的所有接口的配置信息所需的内存。
| |
SIOCGIFADDR SIOCSIFADDR | SIOCGIFADDR 获取接口地址,而 SIOCSIFADDR 设置接口地址。ifr.ifr_addr 字段返回地址。
| |
SIOCGIFCONF | 返回系统中配置的所有接口的配置信息。
|
下面的代码段可以获取用来填充所有配置接口的信息所需的缓冲区大小。
清单 3. 使用 SIOCGSIZIFCONF 获得所需的缓冲区大小
/*Function to get the size needed to allocate for buffers*/ int get_interface_size(int sd){ int ret,size; ret=ioctl(sd,SIOCGSIZIFCONF,&size); if(ret==-1){ perror("Error getting size of interface :"); return ret; } return size; } main { struct ifconf ifc; int sd; sd=socket(AF_INET6,SOCK_DGRAM,0); printf("Size of memory needed = %d/n",get_interface_size(sd)); } |
上面代码的输出是:
$> ./myprog Size of memory needed = 628 |
SIOCGIFCONF 和 SIOCGIFADDR 命令可以用来检索接口的地址。SIOCGIFCONF 可以用来获取所有配置接口的信息,SIOCGIFADDR 可以用来检索特定接口的地址。
/*This function uses loops to find out buffer size instead of SIOCGSIZIFCONF allocates the buffer and gets the information list*/ int alloc_buffer_size(int sd,struct ifconf *ifc){ int ret=-1,bsz=sizeof(struct ifreq); int prevsz=bsz; ifc->ifc_req=NULL; ifc->ifc_len=bsz; do{ ifc->ifc_req=(struct ifreq *)realloc(ifc->ifc_req,bsz); if(!ifc->ifc_req){ perror("Malloc failed :"); return ret; } ifc->ifc_len=bsz; ret=ioctl(sd,SIOCGIFCONF,(caddr_t)ifc); if(ret==-1){ perror("Error getting size of interface :"); return ret; } if(prevsz==ifc->ifc_len) break; else{ bsz*=2; prevsz=(0==ifc->ifc_len ? bsz : ifc->ifc_len) ; } }while(1); ifc->ifc_req=(struct ifreq *)realloc(ifc->ifc_req,prevsz); return ifc->ifc_len; } |
在使用 ifreq
结构中的配置信息填充了列表之后,可以对其进行遍历以检索所需的数据。根据调用之后该结构中填充的套接字地址的长度,可以对这个指针进行移动。下面的清单 5 显示了上面代码的列表中获得的系统中可用的 IP。
#define MAX(x,y) ((x) > (y) ? (x) : (y)) #define SIZE(p) MAX((p).sa_len, sizeof(p)) /* This function prints all the configured IPs on the * system given the ifconf structure allocated previously*/ void print_ips(struct ifconf *ifc){ char *cp,*cplim,addr[INET6_ADDRSTRLEN]; struct ifreq *ifr=ifc->ifc_req; cp=(char *)ifc->ifc_req; cplim=cp+ifc->ifc_len; for(;cp<cplim;cp+=(sizeof(ifr->ifr_name) + SIZE(ifr->ifr_addr))){ /* NOTE: You cannot just increment cp with sizeof(struct ifreq) * as structures returned are of different length. */ ifr=(struct ifreq *)cp; printf("%s :",ifr->ifr_name); getip(ifr,addr); printf("%s/n",addr); } return ; } main { struct ifconf ifc; int sd; sd=socket(AF_INET6,SOCK_DGRAM,0); alloc_buffer_size(sd,&ifc); print_ips(&ifc); } |
上面代码的输出是:
$> ./myprog en0 :6.3.6.0 en0 :9.182.192.169 en0 :fe80::209:6bff:feeb:70b2 sit0 :1.4.4.0 sit0 :::9.182.192.169 lo0 :24.3.0.0 lo0 :127.0.0.1 lo0 :::1 |
/* Given a ifreq structure this function returns its IP address */ void getip(struct ifreq *ifr,char *addr){ struct sockaddr *sa; sa=(struct sockaddr *)&(ifr->ifr_addr); switch(sa->sa_family){ case AF_INET6: inet_ntop(AF_INET6,(struct in6_addr *)&(((struct sockaddr_in6 *)sa)->sin6_addr), addr,INET6_ADDRSTRLEN); break; default : strcpy(addr,inet_ntoa(((struct sockaddr_in *)sa)->sin_addr)); } return; } main { char netaddr[INET_ADDRSTRLEN]; int sd; sd=socket(AF_INET,SOCK_DGRAM,0); strcpy(ifr.ifr_name,"en0"); if((ioctl(sd,SIOCGIFADDR,(caddr_t)&ifr,sizeof(struct ifreq)))<0) perror("Error : "); getip(&ifr,netaddr); printf("%s/n",netaddr); } |
上面代码的输出是:
$./myprog 9.182.192.35 |
下面的表 2 介绍了接口标志和一些属性检索命令。
ioctl 命令 | 描述 | |
---|---|---|
SIOCGIFFLAGS SIOCSIFFLAGS | SIOCGIFFLAGS 可以获取接口标志。 SIOCSIFFLAGS 可以设置接口标志。
|
在使用 SIOCGIFCONF 获取了接口列表之后,您可以使用 SIOCGIFFLAGS-SIOCSIFFLAGS 对来获取和设置接口的属性。接口标志表示为 IFF_XXX。有关完整的列表,请参阅文件 /usr/include/net/if.h。
下面的示例使用一对接口标志来检索其当前状态,并检查环回。也可以使用其他选项。
for(;cp<cplim;){ ifr=(struct ifreq *)cp; cp+=(sizeof(ifr->ifr_name) + SIZE(ifr->ifr_addr)); printf("%-9s ",ifr->ifr_name); ret=ioctl(sd,SIOCGIFFLAGS,(caddr_t)ifr); if(ret==-1){ perror("Error getting flags for interface :"); return ; } if((ifr->ifr_flags)&IFF_UP) printf("UP"); else printf("DOWN"); if((ifr->ifr_flags)&IFF_LOOPBACK) printf(",Loopback"); printf("/n"); } |
上面代码的输出是:
$> ./myprog en0 UP en0 UP en0 UP sit0 UP sit0 UP lo0 UP,Loopback lo0 UP,Loopback lo0 UP,Loopback |
Partial output of ifconfig before setting the flag sit0: flags=4900041<UP,RUNNING,LINK0,64BIT> inet6 ::9.182.192.169/96 Code to set the interface flag int set_interface_flag(int sd,short flag,char *interface){ struct ifreq ifr; int oldflags; strcpy(ifr.ifr_name,interface); if((ioctl(sd,SIOCGIFFLAGS,(caddr_t)&ifr))==-1){ perror("Error getting interface flags :"); return -1; } printf("Setting new interface flag 0X%x/n",flag); ifr.ifr_flags|=flag; if((ioctl(sd,SIOCSIFFLAGS,(caddr_t)&ifr))==-1){ perror("Error setting interface flags :"); return -1; } return 0; } main { int sd; sd=socket(AF_INET6,SOCK_DGRAM,0); set_interface_flag(sd,IFF_NOTRAILERS,"sit0"); } |
上面代码的输出是:
You must have permission to change the flag $./myprog Setting new interface flag 0X20 $ifconfig -a ............. ............. sit0: flags=4900061<UP,NOTRAILERS ,RUNNING,LINK0,64BIT> inet6 ::9.182.192.169/96 ............. ............. |
表 3 介绍了一些用于网络优化的命令。
ioctl 命令 | 描述 | ||
---|---|---|---|
SIOCGIFMTU | SIOCGIFMTU 可以获取接口的最大传输单元 (MTU)。
这个 MTU 值存储在 ifr.ifr_mtu 字段中。 | ||
SIOCGNETOPT SIOCGNETOPT1 | SIOCGNETOPT 可以获取一个网络选项的值。
SIOCGNETOPT1 可以获取当前值、缺省值和网络选项的范围。
|
ret=ioctl(sd,SIOCGIFMTU,ifr); if(ret==-1){ perror("Error getting mtu for interface :"); return ; } printf(" %d/n",ifr->ifr_mtu); |
SIOCGNETOPT1 给出当前值、缺省值和网络选项的范围。
/*Function to get the network options*/ int print_network_options(int sd){ int ret; struct optreq1 oreq; oreq.getnext=1; while((ioctl(sd,SIOCGNETOPT1,(caddr_t)&oreq))!=-1) printf("%s = %s/n",oreq.name,oreq.data); return 0; } |
上面代码的输出是:
$> ./myprog arpqsize = 12 arpt_killc = 20 arptab_bsiz = 7 arptab_nb = 149 ........ ........ ........ ........ ifsize = 256 inet_stack_size = 16 ip6_defttl = 64 ........ |
在 AIX 中,您还可以使用 no
命令来管理各种网络优化参数。有关更详细的信息,请参阅相应的 man 页面。
清单 11. AIX 计算机上“no”命令的输出
$> no -a|more arpqsize = 12 arpt_killc = 20 arptab_bsiz = 7 arptab_nb = 149 bcastping = 0 clean_partial_conns = 0 delayack = 0 delayackports = {} dgd_packets_lost = 3 dgd_ping_time = 5 dgd_retry_time = 5 directed_broadcast = 0 extendednetstats = 0 fasttimo = 200........
四、小结 ioctl 命令的功能非常强大,您可以使用它们来访问或修改网络(或其他)设备的可配置参数。它们使用了各种各样的数据结构,应该使用正确的数据来填充这些数据结构,以便实现预期的结果。