1.概述
我们经常用ioctl获取所在主机全部网络的接口信息(包括,接口地址,是否支持广播,是否支持多播等)。
2.ioctl函数
int ioctl(int fd, int request, ... /* void *arg */);
第三个参数依赖于 request。
和网络相关的 请求有以下几类:
套接字操作
文件操作
接口操作
ARP高速缓冲操作
路由表操作
流系统操作
ioctl请求request参数及arg地址必须指向的数据类型,总结如下
2.1 接口配置
涉及结构
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. */ 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; struct sockaddr ifru_dstaddr; struct sockaddr ifru_broadaddr; struct sockaddr ifru_netmask; struct sockaddr ifru_hwaddr; short int ifru_flags; int ifru_ivalue; int ifru_mtu; struct ifmap ifru_map; char ifru_slave[IFNAMSIZ]; /* Just fits the size */ char ifru_newname[IFNAMSIZ]; __caddr_t ifru_data; } ifr_ifru; }; # define ifr_name ifr_ifrn.ifrn_name /* interface name */ # define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */ # define ifr_addr ifr_ifru.ifru_addr /* address */ # define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */ # define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ # define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */ # define ifr_flags ifr_ifru.ifru_flags /* flags */ # define ifr_metric ifr_ifru.ifru_ivalue /* metric */ # define ifr_mtu ifr_ifru.ifru_mtu /* mtu */ # define ifr_map ifr_ifru.ifru_map /* device map */ # define ifr_slave ifr_ifru.ifru_slave /* slave device */ # define ifr_data ifr_ifru.ifru_data /* for use by interface */ # define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */ # define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */ # define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */ # define ifr_newname ifr_ifru.ifru_newname /* New name */
使用示例
/* include get_ifi_info1 */ #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, idx = 0, hlen = 0; char *ptr, *buf, lastname[IFNAMSIZ], *cptr, *haddr, *sdlname; struct ifconf ifc; struct ifreq *ifr, ifrcopy; struct sockaddr_in *sinptr; struct sockaddr_in6 *sin6ptr; sockfd = Socket(AF_INET, SOCK_DGRAM, 0); lastlen = 0; len = 100 * sizeof(struct ifreq); /* initial buffer size guess */ for ( ; ; ) { buf = Malloc(len); ifc.ifc_len = len; ifc.ifc_buf = buf; if (ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) { // 因为ifc->buf空间不够大,ioctl不会报错,只会截断输出并返回正确,所以只有获取两次buf,并当len稳定不变时,才说明获得了所有数据 if (errno != EINVAL || lastlen != 0) err_sys("ioctl error"); } else { if (ifc.ifc_len == lastlen) break; /* success, len has not changed */ lastlen = ifc.ifc_len; } len += 10 * sizeof(struct ifreq); /* increment */ free(buf); } ifihead = NULL; ifipnext = &ifihead; lastname[0] = 0; sdlname = NULL; /* end get_ifi_info1 */ /* include get_ifi_info2 */ for (ptr = buf; ptr < buf + ifc.ifc_len; ) { ifr = (struct ifreq *) ptr; 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; } 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; printf("ifr_name : %s\n", ifr->ifr_name); if ( (cptr = strchr(ifr->ifr_name, ':')) != NULL) *cptr = 0; /* replace colon with 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; Ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy); flags = ifrcopy.ifr_flags; if ((flags & IFF_UP) == 0) continue; /* ignore if interface not up */ /* end get_ifi_info2 */ /* include get_ifi_info3 */ ifi = Calloc(1, sizeof(struct 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; /* IFI_xxx values */ #if defined(SIOCGIFMTU) && defined(HAVE_STRUCT_IFREQ_IFR_MTU) Ioctl(sockfd, SIOCGIFMTU, &ifrcopy); ifi->ifi_mtu = ifrcopy.ifr_mtu; #else ifi->ifi_mtu = 0; #endif memcpy(ifi->ifi_name, ifr->ifr_name, IFI_NAME); ifi->ifi_name[IFI_NAME-1] = '\0'; /* If the sockaddr_dl is from a different interface, ignore it */ if (sdlname == NULL || strcmp(sdlname, ifr->ifr_name) != 0) idx = hlen = 0; ifi->ifi_index = idx; ifi->ifi_hlen = hlen; if (ifi->ifi_hlen > IFI_HADDR) ifi->ifi_hlen = IFI_HADDR; if (hlen) memcpy(ifi->ifi_haddr, haddr, ifi->ifi_hlen); /* end get_ifi_info3 */ /* include get_ifi_info4 */ switch (ifr->ifr_addr.sa_family) { case AF_INET: sinptr = (struct sockaddr_in *) &ifr->ifr_addr; 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; case AF_INET6: sin6ptr = (struct sockaddr_in6 *) &ifr->ifr_addr; ifi->ifi_addr = Calloc(1, sizeof(struct sockaddr_in6)); memcpy(ifi->ifi_addr, sin6ptr, sizeof(struct sockaddr_in6)); #ifdef SIOCGIFDSTADDR if (flags & IFF_POINTOPOINT) { Ioctl(sockfd, SIOCGIFDSTADDR, &ifrcopy); sin6ptr = (struct sockaddr_in6 *) &ifrcopy.ifr_dstaddr; ifi->ifi_dstaddr = Calloc(1, sizeof(struct sockaddr_in6)); memcpy(ifi->ifi_dstaddr, sin6ptr, sizeof(struct sockaddr_in6)); } #endif break; default: break; } } free(buf); return(ifihead); /* pointer to first structure in linked list */ } /* end get_ifi_info4 */
6. 操作ARP缓存
使用 arpreq 结构
<net/if_arp.h>
/* ARP ioctl request. */ struct arpreq { struct sockaddr arp_pa; /* Protocol address. */ struct sockaddr arp_ha; /* Hardware address. */ int arp_flags; /* Flags. */ struct sockaddr arp_netmask; /* Netmask (only for proxy arps). */ char arp_dev[16]; };
arp_pa是一个含有IP地址的网际套接字地址结构,arp_ha是一个通用套接字结构,它的sa_family为 AF_UNSPEC,sa_data中含有硬件地址。
ioctl没办法列出ARP缓存所有表项。而arp 程序(指定 -a 时),通过读取内存(/dev/kmem)获得ARP高速缓存的当前内容。
int main(int argc, char **argv) { int sockfd; struct ifi_info *ifi; unsigned char *ptr; struct arpreq arpreq; struct sockaddr_in *sin; sockfd = Socket(AF_INET, SOCK_DGRAM, 0); for (ifi = get_ifi_info(AF_INET, 0); ifi != NULL; ifi = ifi->ifi_next) { printf("%s: ", Sock_ntop(ifi->ifi_addr, sizeof(struct sockaddr_in))); sin = (struct sockaddr_in *) &arpreq.arp_pa; memcpy(sin, ifi->ifi_addr, sizeof(struct sockaddr_in)); if (ioctl(sockfd, SIOCGARP, &arpreq) < 0) { err_ret("ioctl SIOCGARP"); continue; } 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)); // 大端,高位在前 } exit(0); }
7.路由操作
ioctl虽然有读写路由表的请求项,但是通常路由操作是通过路由套接字,而非ioctl.
ioctl没办法获得整个路由表, netstat 程序通过读取内核的内存 (/dev/kmem)获得整个路由表。