ioctl( )函数
本函数影响由fd参数引用的一个打开的文件。
#include<unistd.h>
int ioctl( int fd, int request, .../* void *arg */ );
返回0:成功 -1:出错
第三个参数总是一个指针,但指针的类型依赖于request参数。
我们可以把和网络相关的请求划分为6类:
套接口操作
文件操作
接口操作
ARP高速缓存操作
路由表操作
流系统
下表列出了网络相关ioctl请求的request参数以及arg地址必须指向的数据类型:
类别 |
Request |
说明 |
数据类型 |
套 接 口 |
SIOCATMARK SIOCSPGRP SIOCGPGRP |
是否位于带外标记 设置套接口的进程ID或进程组ID 获取套接口的进程ID或进程组ID |
int int int |
文
件
|
FIONBIN FIOASYNC FIONREAD FIOSETOWN FIOGETOWN
|
设置/清除非阻塞I/O标志 设置/清除信号驱动异步I/O标志 获取接收缓存区中的字节数 设置文件的进程ID或进程组ID 获取文件的进程ID或进程组ID |
int int int int int |
接 口
|
SIOCGIFCONF SIOCSIFADDR SIOCGIFADDR SIOCSIFFLAGS SIOCGIFFLAGS SIOCSIFDSTADDR SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCSIFBRDADDR SIOCGIFNETMASK SIOCSIFNETMASK SIOCGIFMETRIC SIOCSIFMETRIC SIOCGIFMTU SIOCxxx |
获取所有接口的清单 设置接口地址 获取接口地址 设置接口标志 获取接口标志 设置点到点地址 获取点到点地址 获取广播地址 设置广播地址 获取子网掩码 设置子网掩码 获取接口的测度 设置接口的测度 获取接口MTU (还有很多取决于系统的实现) |
struct ifconf struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq |
ARP |
SIOCSARP SIOCGARP SIOCDARP |
创建/修改ARP表项 获取ARP表项 删除ARP表项 |
struct arpreq struct arpreq struct arpreq |
路 由 |
SIOCADDRT SIOCDELRT |
增加路径 删除路径 |
struct rtentry struct rtentry |
流 |
I_xxx |
|
|
套接口操作:
明确用于套接口操作的ioctl请求有三个,它们都要求ioctl的第三个参数是指向某个整数的一个指针。
SIOCATMARK: 如果本套接口的的度指针当前位于带外标记,那就通过由第三个参数指向的整数返回一个非0值;否则返回一个0值。POSIX以函数sockatmark替换本请求。
SIOCGPGRP: 通过第三个参数指向的整数返回本套接口的进程ID或进程组ID,该ID指定针对本套接口的SIGIO或SIGURG信号的接收进程。本请求和fcntl的F_GETOWN命令等效,POSIX标准化的是fcntl函数。
SIOCSPGRP: 把本套接口的进程ID或者进程组ID设置成第三个参数指向的整数,该ID指定针对本套接口的SIGIO或SIGURG信号的接收进程,本请求和fcntl的F_SETOWN命令等效,POSIX标准化的是fcntl操作。
文件操作:
以下5个请求都要求ioctl的第三个参数指向一个整数。
FIONBIO: 根据ioctl的第三个参数指向一个0或非0值分别清除或设置本套接口的非阻塞标志。本请求和O_NONBLOCK文件状态标志等效,而该标志通过fcntl的F_SETFL命令清除或设置。
FIOASYNC: 根据iocl的第三个参数指向一个0值或非0值分别清除或设置针对本套接口的信号驱动异步I/O标志,它决定是否收取针对本套接口的异步I/O信号(SIGIO)。本请求和O_ASYNC文件状态标志等效,而该标志可以通过fcntl的F_SETFL命令清除或设置。
FIONREAD: 通过由ioctl的第三个参数指向的整数返回当前在本套接口接收缓冲区中的字节数。本特性同样适用于文件,管道和终端。
FIOSETOWN: 对于套接口和SIOCSPGRP等效。
FIOGETOWN: 对于套接口和SIOCGPGRP等效。
接口配置:
得到系统中所有接口由SIOCGIFCONF请求完成,该请求使用ifconf结构,ifconf又使用ifreq
结构,如下所示:
Struct ifconf{
int ifc_len; // 缓冲区的大小
union{
caddr_t ifcu_buf; // input from user->kernel
struct ifreq *ifcu_req; // return of structures returned
}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 // otner 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结构,然后才初始化后者。如下图
展示了一个ifconf结构的初始化结构,其中缓冲区的大小为1024,ioctl的第三个参数指向
这样一个ifconf结构。
ifc_len |
Ifc_buf |
1024
--------------------->缓存
假设内核返回2个ifreq结构,ioctl返回时通过同一个ifconf结构缓冲区填入了那2个ifreq结构,ifconf结构的ifc_len成员也被更新,以反映存放在缓冲区中的信息量。
接口操作:
SIOCGIFCONF请求为每个已配置的接口返回其名字以及一个套接口地址结构。我们接着可以发出多个接口类其他请求以设置或获取每个接口的其他特征。这些请求的获取(get)版本(SIOCGxxx)通常由netstat程序发出,设置(set)版本(SIGOCSxxx)通常由ifconfig程序发出。任何用户都可以获取接口信息,设置接口信息却要求有超级用户权限。
这些请求汲取或返回一个一个ifreq结构中的信息,而这个结构的地址则作为ioctl调用的第三个参数制定。接口总是以其名标志,在ifreq结构的ifr_name成员中指定,如le0,lo0,ppp0等。
这些请求中有许多使用套接口地址结构在应用进程和内核之间指定或返回具体接口的IP地址或地址掩码。对于IPV4,这个地址或掩码放在一个网际套接口地址结构的sin_addr成员中;对于IPV6,它是一个IPV6套接口地址结构的sin6_addr成员。
SIOCGIFADDR: 在ifr_addr成员中返回单播地址。
SIOCSIFADDR:用ifr_addr成员设置接口地址,这个接口的初始化函数也被调用。
SIOCGIFFLAGS:在ifr_flags成员中返回接口标志。这些接口标志的名字格式为IFF_XXX,在<net/if.h>头文件中定义。举例来说,这些标志指示接口是否处于UP即在工状态(IFF_UP),是否为一个点到点接口(IFF_POINTOPOINT),是否支持广播(IFF_BROADCAST),等等。
SIOCSIFFLAGS:用ifr_flags成员设置接口标志。
SIOCGIFDSTADDR:在ifr_dstaddr成员中返回点到点地址。
SIOCSIFDSTADDR: 在ifr_dstaddr成员中设置点到点地址
SIOCGIFBRDADDR: 在ifr_broadaddr成员中返回广播地址。应用进程必须首先获取接口标志,然后发出正确的请求;对于广播接口为SIOCGIFBRDADDR,对于点到点接口为SIOCGIFDSTADDR
SIOCSIFBRDADDR:用ifr_broadaddr成员设置广播地址。
SIOCGIFNETMASK:在ifr_addr成员中返回子网掩码。
SIOCSIFNETMASK:在ifr_addr成员中设置子网掩码。
SIOCGIFMETRIC:用ifr_metric成员返回接口测度。接口测度由内核为每个接口维护,不过使用他的是路由守护进程routed。接口测度被routed加到跳数上。
SIOCSIFMETRIC:用ifr_metric成员设置接口的路由测度。
ARP高速缓存操作
ARP告诉缓存也通过ioctl函数操纵。使用路由域套接口的系统往往改用路由套接口访问
ARP高速缓存。这些请求使用如下的arpreq结构,定义在<net/if_arp.h>
struct arpreq {
struct sockaddr arp_pa;
struct sockaddr arp_ha;
int arp_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的第三个必须指向某个arpreq结构,操纵ARP高速缓存的ioctl请求有以下三个:
SIOCSARP: 把一个新的表项加入ARP告诉缓存中区,或者修改其中已经存在的一个表项,其中arp_pa是一个含有IP地址的网际套接口地址结构,arp_ha则是一个通用套接口地址结构,他的sa_family值为AF_unspec,sa_data中含有硬件地址(例如6直接的以太网地址)。ATF_PERM和ATF_PUBL这两个标志也可以由应用进程指定。另外两个标志(ATF_INUSE和ATF_COM)则由内核设置。
SIOCDARP: 从ARP告诉缓存中删除一个表项。调用者指定要删除表项的网际地址。
SIOCGARP: 从ARP高速缓存中获取一个表项。调用者指定网际地址,相应的硬件地址(例外以太网地址)随标志一起返回。
只有超级用户才能增加或删除表项。这三个请求通常由arp程序发出。
注意ioctl没有办法列出ARP高速缓存中的所有表项。当指定-a标志执行arp命令时,大多
数版本的arp程序通过读取内核的内存( /dev/kmem )获得ARP高速缓存的当前内容。
路由表操作
有些系统提供2个用于操纵路由表的ioctl请求。这2个请求要求ioctl的第三个参数是指向
某个rtentry结构的一个指针,该结构定义在<net/route.h>头文件中。这些请求通常由route
程序发出。只有超级用户才能发出这些请求。
SIOCADDRT:往路由表中增加一个表项
SIOCDELRT:从路由表中删除一个表项
Ioctl没有办法列出路由表中的所有表项。这个操作通常由netstat程序在指定-r标志自行四
完成。netstat程序通过读取内核的内存 (/dev/kmem)获得整个路由表。用sysctl同样可
以做到。
如下是chinaunix论坛飞灰橙给出的一个例子。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
void err_sys(const char *errmsg);
int main(void)
{
int i, sockfd;
struct ifreq ifr;
struct arpreq arpr;
strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
err_sys("socket");
/* get ip address */
if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1)
err_sys("1-ioctl");
/* get hardware address */
ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1)
err_sys("2-ioctl");
/* output hardware address */
for (i = 0; i < 6; i++) {
unsigned char *mac = (unsigned char *) ifr.ifr_hwaddr.sa_data;
printf("%x", (int) mac[i]);
if (i != 5)
printf("%c", ':');
}
exit(0);
}
void err_sys(const char *errmsg)
{
perror(errmsg);
exit(1);
}