网络程序(特别是服务器程序)经常在程序启动执行后使用ioctl获取所在主机全部网络接口的信息,包括:接口地址、是否支持广播、是否支持多播,等等。所以此部分的ioctl及源码的学习可将其视为一个ifconfig、arp命令的实现过程。
1、ioctl函数(POSIX未对它进行标准化,巨伤唉!)
#include<unistd.h>
int ioctl(int fd, int request, .../*void arg*/);//返回:若成功则为0,若出错则为-1
//其中第三个参数总是一个指针,但指针的类型依赖于request参数
网络相关的请求(request)划分为6类:
套接字操作、文件操作、接口操作、ARP高速缓存操作、路由表操作、流系统
书上P366图17-1列出了相关ioctl请求的request参数及arg地址必须指向的数据类型。此处不再码出,太懒啦!
2、套接字操作和文件操作
属套接字的request:
SIOCATMARK:如果本套接字的读指针当前位于带外标记(还不太了解唉!),那就通过由第三个参数指向的整数返回一个非0值;否则返回一个0值。
SIOCGPGRP:通过由第三个参数指向的整数返回本套接字的进程ID或进程组ID,该ID指定针对本套接字的SIGIO或SIGURG信号的接收进程。本请求和fcntl的F_GETOWN命令等效,但是POSIX标准化的是fcntl操作。
SIOCSPGRP:把本套接字的进程ID或进程组ID设置成由第三个参数指向的整数,该ID指定针对本套接字的SIGIO和SIGURG信号的接收进程。本请求和fcntl的F_SETOWN命令等效,但是POSIX标准化的是fcntl操作。
属文件操作的request:
FIONBIO:根据ioctl的第三个参数指向一个0值或非0值,可清除或设置本套接字的非阻塞式I/O标志。本请求和O_NONBLOCK文件状态标志等效,可以通过fcntl的F_SETFL命令清除或设置该标志。
FIOASYNC:根据ioctl的第三个参数指向一个0值或非0值,可清除或设置本套接字的信号驱动异步I/O标志,它决定是否收取针对本套接字的异步I/O信号(SIGIO)。本请求和O_ASYNC文件状态标志等效,而可以通过fcntl的F_SETFL命令清除或设置该标志。
FIONREAD:通过由ioctl的第三个参数指向的整数返回当前在本套接字接收缓冲区中的字节数。
FIOSETOWN:对于套接字和SIOCSPGRP等效。
FIOGETOWN:对于套接字和SIOCGPGRP等效。
3、接口配置及get_ifi_info函数
处理网络接口的许多程序的初始步骤,即从内核获取配置在系统中的所有接口。此处使用SIOCGIFCONF请求完成,使用ifconf结构,ifconf又使用ifreq结构,详情代码(放在头文件net/if.h里面啦)见书上P368。
get_ifi_info函数(此处使用SIOCGIFCONF ioctl实现),代码如下(将其看作是一个ifconfig命令的实现过程)
#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 */
struct ifi_info {
char ifi_name[IFI_NAME]; /* interface name, null-terminated */
short ifi_index; /* interface index */
short ifi_mtu; /* interface MTU */
u_char ifi_haddr[IFI_HADDR]; /* hardware address */
u_short ifi_hlen; /* # bytes in hardware address: 0, 6, 8 */
short ifi_flags; /* IFF_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 */
/* 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) {
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;
#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 */
#ifdef HAVE_SOCKADDR_DL_STRUCT
/* assumes that AF_LINK precedes AF_INET or AF_INET6 */
if (ifr->ifr_addr.sa_family == AF_LINK) {
struct sockaddr_dl *sdl = (struct sockaddr_dl *)&ifr->ifr_addr;
sdlname = ifr->ifr_name;
idx = sdl->sdl_index;
haddr = sdl->sdl_data + sdl->sdl_nlen;
hlen = sdl->sdl_alen;
}
#endif
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 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 */
/* include free_ifi_info */
void
free_ifi_info(struct ifi_info *ifihead)
{
struct ifi_info *ifi, *ifinext;
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; /* can't fetch ifi_next after free() */
free(ifi); /* the ifi_info{} itself */
}
}
/* end free_ifi_info */
struct ifi_info *
Get_ifi_info(int family, int doaliases)
{
struct ifi_info *ifi;
if ((ifi = get_ifi_info(family, doaliases)) == NULL)
err_quit("get_ifi_info error");
return(ifi);
}
最终在main函数中实现读取数据的功能
#include "unpifi.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;
#ifdef IPv6
else if (strcmp(argv[1], "inet6") == 0)
family = AF_INET6;
#endif
else
err_quit("invalid <address-family>");
doaliases = atoi(argv[2]);
for (ifihead = ifi = Get_ifi_info(family, doaliases);
ifi != NULL; ifi = ifi->ifi_next) {
printf("%s: ", ifi->ifi_name);
if (ifi->ifi_index != 0)
printf("(%d) ", ifi->ifi_index);
printf("<");
/* *INDENT-OFF* */
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");
/* *INDENT-ON* */
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 (ifi->ifi_mtu != 0)
printf(" MTU: %d\n", ifi->ifi_mtu);
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);
}
4、查看ARP表及route表的相关命令
arp -a(反正我是没看着)
netstat -r(这个netstat,真是强大,必用哈!)
以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。