UNP Chapter 16 - ioctl操作

 

16.1. 概述

在传统上ioctl函数是用于那些普遍使用,但不适合归入其他类别的任何特性的系统接口。

网络程序中ioctl常用于在程序启动时获得主机上所有接口的信息:接口的地址,接口是否支持广播,是否支持多播,等等。

 

16.2. ioctl函数

这个函数影响由fd参数指向的打开的文件

#include<unistd.h>
int ioctl(int fd, int request, ... /* void * arg */); // 返回: 成功返回0,出错返回-1

第三个参数总是一个指针,但指针的类型依赖于request(请求)

我们可以把和网络有关的请求分为6类:

1. 套接口操作
2. 文件操作
3. 接口操作
4. ARP高速缓存操作
5. 路由表操作
6. 流系统

 

16.3. 套接口操作

有三种ioctl请求是明确针对套接口的,他们都要求ioctl的第三个参数是一个指向整数的指针。

SIOCATMARK:如果套接口的读指针当前在带外标志上,则通过第三个参数指向的整数返回一个非零值,否则返回零。

SIOCSPGRP:通过第三个参数指向的整数返回为接收来自这个套接口的SIGIO或SIGURG信号而设置的进程ID或进程组ID。这和fcntl的F_GETOWN相同。

SIOCGPGRP:用第三个参数指向的整数设置进程ID或进程组ID以接收这个套接口的SIGIO或SIGURG信号。这个fcntl的F_SETOWN相同。

 

16.4. 文件操作

请求以FIO开始,除套接口外,它们对其他某些类型的文件可能适用,在这里只讨论适用于套接口的请求,下面的五种请求都要求ioctl的第三个参数指向一个整数

FIONBIO:套接口的非阻塞标志会根据ioctl的第三个参数指向的值是否为零而清除或设置。这个请求和用fcntl的F_SETFL命令设置和清除O_NONBLOCK文件状态标志效果相同

FIOASYNC:这个标志根据ioctl的第三个参数指向的值是否为零决定清除或接收套接口上的异步I/O信号(SIGIO),这个标志和用fcntl的F_SETFL命令设置和清除O_AYNC文件状态标志效果相同

FIONREAD:在ioctl的第三个参数指向的整数里返回套接口接收缓冲区中当前的字节数。这种功能在文件、管道和终端上都能用。

FIOSETOWN:在套接口上等价于SIOCSPGRP

FIOGETOWN:在套接口上等价于SIOCGPGRP

 

16.5. 接口配置

很多处理网络接口的程序的第一步是从内核获取系统中配置的所有接口。这是通过SIOCGIFCONF请求来实现的,它使用了ifconf结构,ifconf又用了ifreq结构。

struct ifconf
{
int ifc_len; /* size of buffer, value-result */
union
{
caddr_t ifcu_buf; /* input from user -> kernel */
struct ifreq * ifcu_req; /* return from kernel -> user */
} ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf /* buffer address */
#define ifc_req ifc_ifcu.ifcu_req /* array of structures returned */
#define IFNAMSIZ 16
struct ifreq
{
char ifr_name[IFNAMSIZ]; /* interface name, e.g. "le0" */
union
{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
short ifru_flags;
int ifru_metric;
caddr_t ifru_data;
} ifr_ifru;
};
#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 */

在调用ioctl之前分配一个缓冲区和一个ifconf结构,然后初始化后者。在图16。3中展示了这样的一副图像,其中假定缓冲区的大小为1024字节,ioctl的第三个参数指向ifconf结构

如果假定内核返回两个ifreq结构,我们在ioctl返回时会得到图16.4所示的结构。阴影区域已被ioctl修改,缓冲区填入了两个结构,ifconf结构的ifc_len成员被更新以反映缓冲区中存放的信息数量,在这幅图中假设每个ifreq结构占用32个字节。

 

16.6. get_ifi_info函数

因为有很多程序需要知道在系统中的所有接口,所以我们将开发一个名为get_ifi_info的函数,它返回一个结构的链表,每个结构对应一个当前的状态为"up"的接口。

我们首先在一个新的名为unpifi.h的头文件中定义ifi_info结构

/* Our own header for the programs that need interface configuration info. include this file, instead of "unp.h" */
#ifndef __unp_ifi_h
#define __unp_ifi_h
#include "unp.h"
#include <net/if.h>
#define IFI_NAME 16 /* same as IFNAMSIZ in <net/if.h> */
#define IFI_HADDR 8 /* allow for 64-bit EUI-64 in future */
sturct ifi_info
{/* 我们函数返回一个这些结构的链表,其中每个结构的ifi_next成员指向下一个结构 */
char ifi_name[IFI_NAME]; /* interface name, null terminated */
u_char ifi_haddr[IFI_HADDR]; /* hardware address */
u_short ifi_hlen; /* #bytes in hardware address: 0, 6, 8 */
short ifi_flags; /* IFI_xxx constants from <net/if.h> */
short ifi_myflags; /* our own IFI_xxx flags */
struct sockaddr * ifi_addr; /* primary address */
struct sockaddr * ifi_brdaddr; /* broadcast address */
struct sockaddr * ifi_dstaddr; /* destination address */
struct ifi_info * ifi_next; /* next of these structures */
};
#define IFI_ALIAS 1 /* ifi_addr is an alias */
/* function prototypes */
struct ifi_info * get_ifi_info(int, int);
struct ifi_info * Get_ifi_info(int, int);
void free_ifi_info(struct ifi_info *);
#endif /* __unp_ifi_h */

在给出get_ifi_info函数的实现之前,我们先来看一个简单的程序,该程序调用这个函数,然后输出所有信息。这个程序是ifconfig程序的一个简化版本

#include "unp.h"
int main(int argc, char * * argv)
{
struct ifi_info * ifi, * ifihead;
struct sockaddr * sa;
u_char * ptr;
int i, family, doaliases;
if(argc != 3)
err_quit("usage: prifinfo <inet4|inet6> <doaliases>"");
if(strcmp(argv[1], "inet4") == 0)
family = AF_INET;
#ifedf IPV6
else if(strcmp(argv[1], "inet6") == 0)
family = AF_INET6;
#endif
else
err_quit("invalid<address-family>");
doaliases = atoi(argv[2]);
/* for循环,调用一次get_ifi_info后遍历返回的所有ifi_info结构 */
for(ifihead = ifi = Get_ifi_info(famliy, doaliases); ifi != NULL; ifi = ifi->ifi_next)
{
printf("%s: <", ifi->ifi_name);
if(ifi->ifi_flags & IFF_UP) printf("UP");
if(ifi->ifi_flags & IFF_BROADCAST) printf("BCAST");
if(ifi->ifi_flags & IFF_MULTICAST) printf("MCAST");
if(ifi->ifi_flags & IFF_LOOPBACK) printf("LOOP");
if(ifi->ifi_flags & IFF_POINTOPOINT) printf("P2P");
printf("> \n");
if((i = ifi->ifi_hlen) > 0)
{
ptr = ifi->ifi_haddr;
do
{
printf("%s%x", (i == ifi->ifi_hlen) ? " " : ":", * ptr++);
} while(--i > 0);
printf{"\n"};
}
if((sa = ifi->ifi_addr) != NULL)
printf("IP addr: %s \n", Sock_ntop_host(sa, sizeof( * sa)));
if((sa = ifi->ifi_brdaddr) != NULL)
printf("broadcast addr: %s \n", Sock_ntop_host(sa,sizeof( * sa)));
if((sa = ifi->ifi_dstaddr) != NULL)
printf("destination addr: %s \n", Sock_ntop_host(sa, sizeof( * sa)));
}
free_ifi_info(ifihead);
exit(0);
}

下面给出用SIOCGIFCONF ioctl实现的get_ifi_info

#include "unpifi.h"
struct ifi_info * get_ifi_info(int family, int doaliases)
{
struct ifi_info * ifi, * ifihead, * * ifipnext;
int sockfd, len, lastlen, flags, myflags;
char * ptr, * buf, lastname[IFNAMSIZ], * cptr;
struct ifconf ifc;
struct ifreq * ifr, ifrcopy;
struct sockaddr_in * sinptr;
sockfd = Socket(AF_INET, SOCK_DGRAM, 0); /* 创建一个用于ioctl的UDP套接口 */
lastlen = 0; /* 把lastlen初始化成0 */
len = 100 * sizeof(struct ifreq); /* initial buffer size guess */
for( ; ; )
{
buf = Malloc(len); /* 分配一个缓冲区,开始时为100个ifreq结构的空间 */
ifc.ifc_len = len;
ifc.ifc_buf = buf;
if(ioctl(sockfd, SIOCGIFCONF, &ifc) < 0)
{/* 如果ioctl返回一个EINVAL错误,而且还没有得到过一个成功的返回(也就是lastlen还是0),那么我们还没有分配一个足够大的缓冲区,继续进行循环 */
if(errno != EINVAL || lastlen != 0)
err_sys("ioctl error");
}
else
{/* 如果ioctl返回成功,而且返回的长度等于lastlen,那么这个长度没有改变(及缓冲区足够大),于是break出这个循环,因为已经得到了所有信息 */
if(ifc.ifc_len == lastlen)
break; /* success, len has not changed */
lastlen = ifc.ifc_len;
}
/* 每循环一次,把缓冲区的大小增加能多存放10个ifreq结构的空间 */
len += 10 * sizeof(struct ifreq); /* increment */
free(buf);
}
ifihead = NULL; /* 因为将要返回一个指向ifi_info结构的链表头的指针,所以用两个变量ifihead和ifipnext在建立链表时保存指针 */
ifipnext = &ifihead;
lastname[0] = 0;
for(ptr = buf; ptr < buf + ifc.ifc_len; ) /* 在遍历所有ifreq结构时,ifr指向每个结构,然后我们增加ptr指向下一个结构 */
{
ifr = (struct ifreq *)ptr;
#ifdef HAVE_SOCKADDR_SA_LEN
len = max(sizeof(struct sockaddr), ifr->ifr_addr.sa_len);
#else
switch(ifr->ifr_addr.sa_family)
{
#ifdef IPV6
case AF_INET6:
len = sizeof(struct sockaddr_in6);
break;
#endif
case AF_INET;
default:
len = sizeof(struct sockaddr);
break;
}
#endif /* HAVE_SOCKADDR_SA_LEN */
ptr += sizeof(ifr->ifr_name) + len; /* for next one in buffer */
if(ifr->ifr_addr.sa_family != family) /* 忽略所有不是调用者期望的地址族的地址 */
continue; /* ignore if not desired address family */
myflags = 0; /* 检测在该接口上可能存在的任何别名地址,也就是赋给这个接口的其它地址 */
if((cptr = strchr(ifr->ifr_name, ':' )) != NULL)
* cptr = 0; /* replace colon will null */
if(strncmp(lastname, ifr->ifr_name, IFNAMSIZ) == 0)
{
if(doaliases == 0)
continue; /* already processed this interface */
myflags = IFI_ALIAS;
}
memcpy(lastname, ifr->ifr_name, IFNAMSIZ);
ifrcopy = * ifr; /* 发出一个SIOCGIFFLAGS ioctl请求取接口标志。ioctl的第三个参数是一个指向ifreq结构的指针,结构中包含要获取标志的接口名。在发出ioctl之前对ifreq结构做一个拷贝,因为如果不这样做的话,这个请求会覆盖接口的IP地址,因为它们都同一联合的成员。如果接口不在工作,就忽略掉 */
ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy);
flags = ifrcopy.ifr_flags;
if((flags & IFF_UP) ==0)
continue; /* ignore if interface not up */
ifi = Calloc(1, sizeof(struct ifi_info)); /* 给ifi_info结构分配内存,并把它加到正在建立的链表的末尾 */
* ifipnext = ifi; /* prev points to this new one */
ifipnext = &ifi->ifi_next; /* pointer to next one goes here */
ifi->ifi_flags = flags; /* IFF_xxx values */
ifi->ifi_myflags = myflags; /* IFF_xxx values */
memcpy(ifi->ifi_name, ifr->ifr_name, IFI_NAME);
ifi->ifi_name[IFI_NAME-1] = '\0';
switch(ifr->ifr_addr.sa_family)
{
case AF_INET:
sinptr = (struct sockaddr_in *)&fir->ifr_addr;
if(ifi->ifi_addr == NULL)
{
ifi->ifi_addr = Calloc(1, sizeof(struct sockaddr_in));
memcpy(ifi->ifi_addr, sinptr, sizeof(struct sockaddr_in));
#ifdef SIOCGIFBRDADDR
if(flags & IFF_BROADCAST)
{
ioctl(sockfd, SIOCGIFBRDADDR, &ifrcopy);
sinptr = (struct sockaddr_in *)&ifrcopy.ifr_broadaddr;
ifi->ifi_brdaddr = Calloc(1, sizeof(struct sockaddr_in));
memcpy(ifi->ifi_brdaddr, sinptr, sizeof(struct sockaddr_in));
}
#endif
#ifdef SIOCGIFDSTADDR
if(flags & IFF_POINTOPOINT)
{
ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy);
sinptr = (struct sockaddr_in *)&ifrcopy.ifr_dstaddr;
ifi->ifi_dstaddr = Calloc(1, sizeof(struct sockaddr_in));
memcpy(ifi->ifi_dstaddr, sinptr, sizeof(struct sockaddr_in));
}
#endif
}
break;
default:
break;
}
}
free(buf);
return(ifihead); /* pointer to first structure in linked list */
}

/* free_ifi_info函数,它以一个由get_ifi_info返回的指针做按数,释放所有动态分配的内存 */
void free_ifi_info(struct ifi_info * ifihead)
{
struct ifi_info * ifi, * ifihead;
for(ifi= ifihead; ifi != NULL; ifi = ifinext)
{
if(ifi->ifi_addr != NULL)
free(ifi->ifi_addr);
if(ifi->ifi_brdaddr != NULL)
free(ifi->ifi_brdaddr);
if(ifi->ifi_dstaddr != NULL)
free(ifi->ifi_dstaddr);
ifinext = ifi->ifi_next; /* cant fetch ifi_next after free() */
free(ifi); /* the ifi_info{} itself */
}
}


16.7. 接口操作

SIOCGIFCONF请求返回每个已配置接口的名字和套接口地址结构。请求的get版本(SIOCGxxx)一般由netstat程序使用,而set版本(SIOCSxxx)一般由ifconfig程序使用。任何用户都可以获取接口信息,但要想设置这些信息必须有超级用户权限。

SIOCGIFADDR

SIOCSIFADDR

SIOCGIFFLAGS

SIOCSIFFLAGS

SIOCGIFDSTADDR

SIOCSIFDSTADDR

SIOCGIFBRDADDR

SIOCSIFBRDADDR

SIOCGIFNETMASK

SIOCSIFNETMASK

SIOCGIFMETRIC

SIOCSIFMETRIC: 用ifr_metric成员设置接口的路由测度。

 

16.8. ARP高速缓存操作

ARP高速缓存也是由ioctl函数操作的。这些请求使用一个arpreq结构,它在<net/if_arp.h>头文件中定义

struct arpreq
{
struct sockaddr arp_pa; /* protocol address */
struct sockaddr arp_ha; /* hardware address */
int arp_flags; /* flags */
};
#define ATF_INUSE 0x01 /* entry in use */
#define ATF_COM 0x02 /* completed entry (hardware addr valid) */
#define ATF_PERM 0x04 /* permanent entry */
#define ATF_PUBL 0x08 /* published entry (respond for other host) */

ioctl的第三个参数必须指向这些结构之一,它支持下面的三种请求:

SIOCSARP:把新项加到ARP高速缓存中或修改一个已有项。

SIOCDARP:从ARP高速缓存中删除一项,调用者指定要删除项的网际套接口地址。

SIOCGARP: 从ARP高速缓存中取一项,调用者指定所取项的网际套接口地址,对应的以太网地址会和标志一起返回。

#include "unp.h"
#include <net/if_arp.h>
int main(int argc, char * * argv)
{
int family, sockfd;
char str[INET6_ADDRSTRLEN];
char * * pptr;
unsigned char * ptr;
struct arpreq arpreq;
struct sockaddr_in * sin;
pptr = my_addrs(&family); /* 调用my_addrs获取主机的所有IP地址 */
for( ; *pptr != NULL; pptr++) /* 每个地址进行循环 */
{
printf("%s: ", inet_ntop(family, * pptr, str, sizeof(str))); /* 用inet_ntop输出IP地址 */
switch(family)
{
case AF_INET:
sockfd = Socket(AF_INET, SOCK_DGRAM, 0)
sin = (struct sockaddr_in *)&arpreq.arp_pa;
bzero(sin, sizeof(struct sockaddr_in));
sin->sin_family = AF_INET;
memcpy(&sin->sin_addr, * pptr, sizeof(struct in_addr));
ioctl(sockfd, SIOCGARP, &arpreq);
ptr = &arpreq.arp_ha.sa_data[0];
printf("%x: %x: %x: %x: %x: %x \n", * ptr, * (ptr+1), * (ptr+2), * (ptr+3), * (ptr+4), * (ptr+5));
break;
default:
err_quit("unsupported address family: %d", family);
}
}
exit(0);
}

 

16.9. 路由表操作

有两种ioctl请求用来操作路由表。这两个请求要求ioctl的第三个参数必须是一个指向rtentry结构的指针,这个结构在<net/route.h>头文件中定义,这些请求一般有route程序发出,只有超级用户才能发出这些请求。

SIOCADDRT:向路由表中加一项

SIOCDELRT:从路由表中删去一项

 

16.10. 小结



posted on 2012-02-14 15:31  s7vens  阅读(938)  评论(0编辑  收藏  举报