在本书中有两个地方都对这个函数进行了介绍,其实还有很多地方需要这个函数。ioclt函数传统上一直作为纳西而不适合归入其他精细定义类别的特性的系统接口。网络程序(特别是服务器程序)经常在程序启动执行后使用ioctl获取所在主机全部网络接口的信心,包括:接口地址、是否支持广播、是否支持多播。
#include <unistd.h> int ioctl(int fd,int request,...../* void *arg /); //返回:若成功则为0.失败则我-1
- 套接字操作
- 文件操作
- 接口操作
- ARP高速缓存操作
- 路由表操作
- 流系统
不但某些ioclt操作和某些fcntl操作功能重叠(譬如把套接字设置为非阻塞),而且某些操作可以使用ioctl以不止一种方式制定(譬如设置套接字的进程组属主)。下表列出了网络相关ioctl请求的request参数以及arg地址必须指向的数据类型。
套接字操作
明确要求套接字ioctl请求有三个,它们都要求ioctl的第三个参数是指向某个整数的一个指针。
- SIOCATMARK:如果本套接字的读指针当前位于带外标记,那就通过由第三个参数指向的帧数放回一个非0值,否则返回一个0值。
- SIOCGPGRP:通过由第三个参数指向的整数返回本套接字的进程ID或进程组ID,该ID指定针对本套接字的SIGIO或SIGURG信号的接受进程。
- SIOCSPGR :本套接字的进程ID或进程组ID设置成由第三个参数指向的整数,该ID指定对本套接字的SIGIO或SIGURG信号的接受进程。
文件操作
以FIO打头的可能还适用于除套接字外某些特定类型的文件。都要求ioctl的第三个参数指向一个帧数。
- FIONBIO:根据ioctl的第三个参数指向一个0值或非0值,可消除或设置本套接字的非阻塞式I/O标志。本请求和O_NONBLOCK文件状态标志等效,而可以通过fcntlde F_SETFL命令清除或设置该标志。
- FIOASYNC:根据ioctl的第三个参数指向一个0值或非0值,可消除或设置本套接字的信号驱动异步I/O标志,它决定是否收取针对本套接字的异步I/O信号。本请求和O_ASYNC文件状态标志等效,而可以通过fcntl的F_SETFL命令清除或设置该标志
- FIONREAD:通过由ioctl的第三个参数指向的整数返回当前在本套接字接受缓冲区中的字节数。
- FIOSETOWN:对于本套接字和SIOCSPGRP等效
- FIOGETOWN:对于套接字和SIOCGPGRP等效。
接口配置
从内核获取配置在系统中的所有接口,由SIOCGIFCONF完成。它使用ifconf结构,ifconf又使用ifreq结构。在使用ioctl前先分配一个缓冲区和一个ifconf结构,然后初始化后者。假设缓冲区大小为1024字节,ioctl的第三个参数指向这个结构。
SIOCGIFCONF请求存在一个严重的问题是:在缓冲区大小不足以存放结果时,一些实现不返回错误,而是截断结果并返回成功(即ioctl的返回值为0)。要知道缓冲区足够大的唯一办法是:发请求,记下返回长度,用更大的缓冲区发请求,比较返回的长度和刚才记下的长度,只有这两个长度相同才说缓冲区足够大。
struct ifconf { //网络配置结构体是一种缓冲区 int ifc_len;//缓冲区ifr_buf的大小 union { char__user *ifcu_buf; //绘冲区指针 struct ifreq__user* ifcu_req;//指向ifreq指针 }ifc_ifcu; }; #define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址 #define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址 #define IFNAMSIZ 16 struct ifreq { #define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC union { char ifrn_name[IFNAMESIZ]; //网络接口名称 }ifr_ifrn; union { struct sockaddr ifru_addr; //本地IP地址 struct sockaddr ifru_dstaddr;//目标IP地址 struct sockaddr ifru_broadaddr;//广播IP地址 struct sockaddr ifru_netmask;//本地子网掩码地址 struct sockaddr ifru_hwaddr;//本地MAC地址 short ifru_flags;//网络接口标记 int ifru_ivalue;//不同的请求含义不同 struct ifmap ifru_map;//网卡地址映射 int ifru_mtu;//最大传输单元 char ifru_slave[IFNAMSIZ];//占位符 char ifru_newname[IFNAMSIZE];//新名称 void __user* ifru_data;//用户数据 struct if_settings ifru_settings;//设备协议设置 }ifr_ifru; } #define ifr_name ifr_ifrn.ifrn_name;//接口名称 #define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC #define ifr_addr ifr_ifru.ifru_addr;//本地IP #define ifr_dstaddr ifr_ifru.dstaddr;//目标IP #define ifr_broadaddr ifr_ifru.broadaddr;//广播IP #define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码 #define ifr_flags ifr_ifru.ifru_flags;//标志 #define ifr_metric ifr_ifru.ifru_ivalue;//接口侧度 #define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元 #define ifr_map ifr_ifru.ifru_map;//设备地址映射 #define ifr_slave ifr_ifru.ifru_slave;//副设备 #define ifr_data ifr_ifru.ifru_data;//接口使用 #define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号 #define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽 #define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度 #define ifr_newname ifr_ifru.ifru_newname;//新名称 #define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置
使用SIOCGIFCONF前ifconf结构的初始化结果
SIOCGIFCONF返回值
接口操作
SIOCGIFCONF请求为每个已配置的接口返回其名字以及一个套接字地址结构。这些请求(get)版本(SIOCGxxx)由netstat程序发出。设置(set)版本(SIOCSxxx)由ifconfig发出。这些请求接受或返回一个ifreq结构中的信息。而这个结构的地址则作为ioctl调用的第三个参数指定。接口总是以其名字标识,在ifreq结构中的ifr_name指定,如;le0、lo0。
相关数据结构
ARP高速缓存
/*ARP高速缓存操作,包含IP地址和硬件地址的映射表 操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录 */ struct arpreq{ struct sockaddr arp_pa;//协议地址 struct sockaddr arp_ha;//硬件地址 int arp_flags;//标记 struct sockaddr arp_netmask;//协议地址的子网掩码 char arp_dev[16];//查询网络接口的名称 }; #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没有办法列出路有表中所有表项。这个操作通常有netstat程序在指定-r标志时完成。
//(2)网卡设备属性ifmap struct ifmap { //网卡设备的映射属性 unsigned long mem_start;//开始地址 unsigned long mem_end;//结束地址 unsigned short base_addr;//基地址 unsigned char irq;//中断号 unsigned char dma;//DMA unsigned char port;//端口 }
获取网络接口信息
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <linux/if.h> #include <arpa/inet.h> #include <linux/sockios.h> /** ioctl函数是与内核交互的一种方法,使用ioctl函数与内核协议栈进行交互 ioctl函数可操作I/O请求,文件请求与网络接口请求 网络接口请求的几个结构体: struct ifreq{ #define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC union{ char ifrn_name[IFNAMESIZ];//网络接口名称 }ifr_ifrn; union{ struct sockaddr ifru_addr;//本地IP地址 struct sockaddr ifru_dstaddr;//目标IP地址 struct sockaddr ifru_broadaddr;//广播IP地址 struct sockaddr ifru_netmask;//本地子网掩码地址 struct sockaddr ifru_hwaddr;//本地MAC地址 short ifru_flags;//网络接口标记 int ifru_ivalue;//不同的请求含义不同 struct ifmap ifru_map;//网卡地址映射 int ifru_mtu;//最大传输单元 char ifru_slave[IFNAMSIZ];//占位符 char ifru_newname[IFNAMSIZE];//新名称 void __user* ifru_data;//用户数据 struct if_settings ifru_settings;//设备协议设置 }ifr_ifru; } #define ifr_name ifr_ifrn.ifrn_name;//接口名称 #define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC #define ifr_addr ifr_ifru.ifru_addr;//本地IP #define ifr_dstaddr ifr_ifru.dstaddr;//目标IP #define ifr_broadaddr ifr_ifru.broadaddr;//广播IP #define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码 #define ifr_flags ifr_ifru.ifru_flags;//标志 #define ifr_metric ifr_ifru.ifru_ivalue;//接口侧度 #define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元 #define ifr_map ifr_ifru.ifru_map;//设备地址映射 #define ifr_slave ifr_ifru.ifru_slave;//副设备 #define ifr_data ifr_ifru.ifru_data;//接口使用 #define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号 #define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽 #define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度 #define ifr_newname ifr_ifru.ifru_newname;//新名称 #define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置 struct ifmap{//网卡设备的映射属性 unsigned long mem_start;//开始地址 unsigned long mem_end;//结束地址 unsigned short base_addr;//基地址 unsigned char irq;//中断号 unsigned char dma;//DMA unsigned char port;//端口 } struct ifconf{//网络配置结构体是一种缓冲区 int ifc_len;//缓冲区ifr_buf的大小 union{ char__user *ifcu_buf;//绘冲区指针 struct ifreq__user* ifcu_req;//指向ifreq指针 }ifc_ifcu; }; #define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址 #define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址 (1)获得配置选项SIOCGIFCONF获得网络接口的配置情况 需要填充struct ifreq中ifr_name变量 (2)其它选项获取填充struct ifreq的ifr_name **/ int main(int argc,char*argv[]){ int s; int err; s=socket(AF_INET,SOCK_DGRAM,0); if(s<0){ perror("socket error"); return; } //传入网络接口序号,获得网络接口的名称 struct ifreq ifr; ifr.ifr_ifindex=2;//获得第2个网络接口的名称 err=ioctl(s,SIOCGIFNAME,&ifr); if(err) perror("index error"); else printf("the %dst interface is:%s ",ifr.ifr_ifindex,ifr.ifr_name); //传入网络接口名称,获得标志 memcpy(ifr.ifr_name,"eth0",5); err=ioctl(s,SIOCGIFFLAGS,&ifr); if(!err) printf("SIOCGIFFLAGS:%d ",ifr.ifr_flags); //获得MTU和MAC err=ioctl(s,SIOCGIFMTU,&ifr); if(!err) printf("SIOCGIFMTU:%d ",ifr.ifr_mtu); //获得MAC地址 err=ioctl(s,SIOCGIFHWADDR,&ifr); if(!err){ unsigned char* hw=ifr.ifr_hwaddr.sa_data; printf("SIOCGIFHWADDR:%02x:%02x:%02x:%02x:%02x:%02x ",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]); } //获得网卡映射参数 命令字SIOCGIFMAP err=ioctl(s,SIOCGIFMAP,&ifr); if(!err) printf("SIOCGIFMAP,mem_start:%d,mem_end:%d,base_addr:%d,ifr_map:%d,dma:%d,port:%d ",ifr.ifr_map.mem_start,ifr.ifr_map.mem_end,ifr.ifr_map.base_addr,ifr.ifr_map.irq,ifr.ifr_map.dma,ifr.ifr_map.port); //获得网卡序号 err=ioctl(s,SIOCGIFINDEX,&ifr); if(!err) printf("SIOCGIFINDEX:%d ",ifr.ifr_ifindex); //获取发送队列的长度 err=ioctl(s,SIOCGIFTXQLEN,&ifr); if(!err) printf("SIOCGIFTXQLEN:%d ",ifr.ifr_qlen); //获取网络接口IP struct sockaddr_in *sin=(struct sockaddr_in*)&ifr.ifr_addr;//保存的是二进制IP char ip[16];//字符数组,存放字符串 memset(ip,0,16); err=ioctl(s,SIOCGIFADDR,&ifr); if(!err){ inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);//转换的字符串保存到ip数组中,第二个参数是要转换的二进制IP指针,第三个参数是转换完成存放IP的缓冲区,最后一个参数是缓冲区的长度 printf("SIOCGIFADDR:%s ",ip); } //查询目标IP地址 err=ioctl(s,SIOCGIFDSTADDR,&ifr); if(!err){ inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16); printf("SIOCGIFDSTADDR:%s ",ip); } //查询子网掩码 err=ioctl(s,SIOCGIFNETMASK,&ifr); if(!err){ inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16); printf("SIOCGIFNETMASK:%s ",ip); } //设置IP地址,设置网络接口 inet_pton(AF_INET,"222.27.253.108",&sin->sin_addr.s_addr);//将字符串IP转换成二进制 err=ioctl(s,SIOCSIFADDR,&ifr);//发送设置本机ip地址请求命令 if(!err){ printf("check IP-----"); memset(&ifr,0,sizeof(ifr)); memcpy(ifr.ifr_name,"eth0",5); ioctl(s,SIOCGIFADDR,&ifr); inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16); printf("%s ",ip); } //得到接口的广播地址 memset(&ifr,0,sizeof(ifr)); memcpy(ifr.ifr_name,"eth0",5); ioctl(s,SIOCGIFBRDADDR,&ifr); struct sockaddr_in *broadcast=(struct sockaddr_in*)&ifr.ifr_broadaddr; //转换成字符串 inet_ntop(AF_INET,&broadcast->sin_addr.s_addr,ip,16);//inet_ntop将二进制IP转换成点分十进制的字符串 printf("BROADCAST IP:%s ",ip); close(s); }
查看arp高速缓存信息
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <net/if_arp.h> #include <string.h> #include <stdlib.h> #include <linux/sockios.h> /** ARP高速缓存操作,包含IP地址和硬件地址的映射表 操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录 struct arpreq{ struct sockaddr arp_pa;//协议地址 struct sockaddr arp_ha;//硬件地址 int arp_flags;//标记 struct sockaddr arp_netmask;//协议地址的子网掩码 char arp_dev[16];//查询网络接口的名称 } **/ //根据IP地址查找硬件地址 int main(int argc,char*argv[]){ int s; int err; struct arpreq arpreq; struct sockaddr_in *addr=(struct sockaddr_in*)&arpreq.arp_pa;//IP地址 s=socket(AF_INET,SOCK_DGRAM,0); if(s<0) perror("socket error"); addr->sin_family=AF_INET; addr->sin_addr.s_addr=inet_addr(argv[1]);//转换成二进制IP if(addr->sin_addr.s_addr==INADDR_NONE) printf("IP地址格式错误 "); strcpy(arpreq.arp_dev,"eth0"); err=ioctl(s,SIOCGARP,&arpreq); if(err==-1){ perror("arp"); return -1; } unsigned char* hw=(unsigned char*)&arpreq.arp_ha.sa_data;//硬件地址 printf("%s ",argv[1]); printf("%02x:%02x:%02x:%02x:%02x:%02x ",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]); close(s); return 0; }
查看网关的MAC。在查看ARP高速缓存时要传入IP地址与接口信息。而获得接口信息要传入接口名ifr_name,如eth0。
要介绍了获得网络接口请求信息,获得网卡设备映射属性、配置网络接口、获得ARP高速缓存等。其它ioctl函数还能对操作文件,操作I/O、操作路由等。最后对于网络接口的操作与ARP高速缓存的操作分别给出了实例。