UNIX网络编程卷一 学习笔记 第十七章 ioctl操作

ioctl函数传统上一直作为那些不适合归入现有已定义类别的特性的系统接口。POSIX正在通过创建特定的包装函数来代替ioctl函数的某些功能,以取而代之的是那些已被POSIX标准化的函数。例如,Unix终端接口传统上使用ioctl函数访问,而POSIX为终端创造了12个新函数,如tcgetattr函数获取终端属性、tcflush函数用于丢弃待处理输入和输出。类似地,POSIX替换了一个用于网络的ioctl函数请求:新的sockatmark函数取代ioctl的SIOCATMARK参数。但与网络编程相关且依赖于实现的特性保留的ioctl函数请求仍然很多,它们用于获取接口信息、访问路由表、访问ARP高速缓存等。

ioctl函数影响由fd参数引用的打开文件描述符:
在这里插入图片描述
第三个参数总是一个指针,其类型依赖于request参数。

4.4 BSD把第二个参数定义为unsigned long而非int,这不成问题,因为用作这个参数的常值由头文件定义,只要这些常值在作用域内(如使用了ioctl函数的程序include了unistd.h头文件),那么用的就是正确类型的常值。

只写实现把第三个参数指定为void *,而非ANSI C省略号记法。ANSI C中,省略号表示参数列表中的可变部分。

POSIX未对定义iotcl函数原型的头文件进行标准化,许多系统在unistd.h头文件中定义它,但传统的BSD系统在sys/ioctl.h头文件中定义它。

我们可以把和网络相关的请求分为6类:
1.套接字操作。

2.文件操作。

3.接口操作。

4.ARP高速缓存操作。

5.路由表操作。

6.流系统。

某些ioctl函数操作和某些fcntl函数操作功能重叠(如把套接字设置为非阻塞),而且某些操作可用ioctl函数以多种方式指定(如设置套接字的进程组属主)。

以下是request参数的取值及其对应的arg指针参数指向的类型:
在这里插入图片描述
明确用于套接字操作的request参数:
1.SIOCATMARK:如果套接字的读指针位于带外标记,就通过第三个参数指向的整数返回一个非0值,否则返回0值。POSIX用sockatmark函数替换本请求。

2.SIOCGPGRP:通过第三个参数指向的整数返回本套接字的进程ID或进程组ID,该ID指定对本套接字的SIGIO或SIGURG信号的接收进程。本请求和fcntl函数的F_GETOWN命令等效,且POSIX标准化的是fcntl操作。

3.SIOCSPGRP:把本套接字的进程ID或进程组ID设置成由第三个参数指向的整数,该ID指定本套接字的SIGIO或SIGURG信号的接收进程。本请求和fcntl函数的F_SETOWN命令等效,且POSIX标准化的是fcntl操作。

用于套接字操作,且可能还适用于某些特定类型文件的request参数:
1.FIONBIO:根据iotcl函数的第三个参数指向一个0值或非0值,可清除或设置本套接字的非阻塞式IO标志。本请求和O_NONBLOCK文件状态标志等效,可通过fcntl函数的F_SETFL命令清除或设置O_NONBLOCK标志。

2.FIOASYNC:根据ioctl函数的第三个参数指向一个0值或非0值,可清除或设置本套接字的信号驱动异步IO标志。本请求决定是否会接收针对本套接字的异步IO信号(SIGIO)。本请求和O_ASYNC文件状态标志等效,可通过fcntl函数的F_SETFL命令清除或设置O_ASYNC标志。

3.FIONREAD:通过由ioctl函数的第三个参数指向的整数返回当前在本套接字接收缓冲区中的字节数。本特性也适用于文件、管道、终端。

4.FIOSETOWN:对于套接字等价于SIOCSPGRP。

5.FIOGETOWN:对于套接字等价于SIOCGPGRP。

需要处理网络接口的许多程序首先就要从内核获取配置在系统中的所有接口,本任务由SIOCGIFCONF请求完成,它使用ifconf结构,ifconf结构中包含ifreq结构,以下是这两个结构的定义:
在这里插入图片描述
调用ioctl前我们要先分配一个缓冲区和一个ifconf结构,然后初始化ifconf结构,下图是初始化ifconf结构的结果,其中假设缓冲区大小为1024字节,ioctl函数的第三个参数指向该结构:
在这里插入图片描述
假设内核返回2个ifreq结构,在ioctl函数返回时该ifconf结构的值如下:
在这里插入图片描述
如上图,阴影区域是被ioctl函数修改过的部分,缓冲区中天赋了2个ifreq结构,ifconf结构的ifc_len成员也被更新,以指示存放在缓冲区中的信息量,上图假设每个ifreq结构占32字节。

ifreq结构中含有一个联合,而#define隐藏了这些字段是联合的成员这一事实。有些系统往ifr_ifru联合中增添了许多依赖于实现的成员。

我们开发一个名为get_ifi_info的函数,它返回一个结构链表,其中每个结构对应当前处于up状态的接口,我们将使用ioctl函数的SIOCGIFCONF参数实现这个函数。

FreeBSD提供了一个实现类似功能的函数,名为getifaddrs。

但搜索FreeBSD 4.8的整个源代码树(指在软件开发中,代码文件和目录组织结构形成的一种层次化的树状结构),发现有12个程序使用SIOCGIFCONF参数调用ioctl函数以确定存在的接口。

为开发get_ifi_info函数,我们先编写一个名为为unpifi.h的头文件,其中定义一些结构:

/* 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 */struct ifi_info {char ifi_name[IFI_NAME];    /* inter face 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 */
// 此函数获取一个ifi_info结构组成的链表
struct ifi_info *get_ifi_info(int, int);
struct ifi_info *Get_ifi_info(int, int);
// 用于存放ifi_info结构和其中所含套接字地址结构的内存空间都是动态获取的,此函数用来释放动态获取的内存空间
void free_ifi_info(struct ifi_info *);#endif /* __unp_ifi_h */

在给出get_ini_info函数的实现前,先给出一个调用该函数并输出所有信息的程序,该程序是ifconfig程序的微型版本:

#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;} else if (strcmp(argv[1], "inet6") == 0) {family = AF_INET6;} 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("<");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");// 如果无法得到硬件地址,则ifi_hlen成员值为0if ((i = ifi->ifi_hlen) > 0) {ptr = ifi->ifi_haddr;do {// 将硬件地址显示为16进制数形式,每个十六进制数用冒号分隔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);}
}

在主机macosx上执行以上代码:
在这里插入图片描述
上图中,第一个命令行参数inet4指定IPv4地址,第二个命令行参数0指定不返回地址别名。在MacOS X系统上,我们用这种方法无法得到以太网接口的硬件地址。

多宿主机的传统定义是具有多个接口的主机,如两个以太网链路或一个以太网链路加一个点到点链路。每个接口必须有唯一的一个IPv4地址。计量一个主机的接口数是否大于1个以确定它是否是多宿主机时,环回接口不计在内。

按照约定,地址127.0.0.1赋予环回接口,任何发送到这个IP地址的分组在内部被环送回来作为IP模块的输入,因此这个分组不会出现在网络上。我们在同一主机上测试客户和服务器程序时常用该地址。该地址为人所知的名字是INADDR_LOOPBACK。

网络127.0.0.1/8上任何地址都可以赋予环回接口,但127.0.0.1是最常用的,往往由系统自动配置。

路由器按照定义是多宿的,因为它把到达某个接口的分组转发到另一个接口。而多宿主机不必是一个路由器,除非它转发分组。一个多宿主机不应仅仅因为拥有多个接口而认定是一个路由器,除非它被配置成作为路由器(通常由系统管理员开启某个配置选项)。

但多宿这一说法已变得更一般化,包括两种情形:
1.拥有多个接口的主机是多宿的,每个接口必须有各自的IP地址,未指定网络地址的接口允许出现在点到点链路上。这是传统的定义。

2.较新的主机具备把多个IP地址赋予单个物理接口的能力。除第一个IP地址(主地址)外的每个额外IP地址称为该接口的一个别名地址或逻辑接口地址。通常别名地址与主地址共享同一个子网地址,主机主机ID不同。但别名地址也可能具有完全不同于主地址的网络地址或子网地址。

可见多宿主机的定义是具有多个IP层可见接口(除了环回接口)的主机,不关心这些接口是物理的还是逻辑的。

网桥是一种用于连接多个局域网(LAN)的设备(数据链路层设备),它基于MAC地址来转发数据帧。网桥的主要功能是将数据帧从一个接口转发到另一个接口,以便在不同的局域网之间实现通信。以太网交换机在数据链路层上工作,它可以被视为一种高级形式的网桥,它在功能上类似于网桥,但通常具有更多的接口和更高的性能。

给予网络负荷极高的某个服务器主机到同一个以太网交换机的多个物理连接,并把这些连接汇聚成一个更高带宽的逻辑连接是常见的,但这样的主机不能因为拥有多个物理接口而被认为是多宿的,因为在IP层看来它们的单个逻辑接口。

在另一个上下文中,有多个连接通达因特网的网络也称为多宿的,例如有些网点有两个而非一个通达因特网的连接,以此提供因特网接入的备份能力。SCTP协议能通过多宿网点获益。

如果给以太网接口en1增设3个别名地址(它们的主机ID分别为79、80、81),并且把第二个命令行参数改为1,会得到以下结果:
在这里插入图片描述
在第十八章(18-16)中,我们会给出另一个get_ifi_info函数的实现,该实现可以很容易地获取硬件地址,在FreeBSD上运行该版本程序,可得到以下结果:
在这里插入图片描述
上图中我们指示程序输出别名地址,发现以太网接口de1定义了一个主机ID为93的别名。

以下是以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, 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_in *sin6ptr;// 创建一个用于ioctl函数的UDP套接字,也可用TCP套接字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;for (ptr = buf; ptr < buf + ifc.ifc_len; ) {ifr = (struct ifreq *)ptr;// 处理为套接字地址结构提供长度字段的较新系统
#ifdef HAVE_SOCKADDR_SA_LENlen = max(sizeof(struct sockaddr), ifr->ifr_addr.sa_len);
// 处理不提供套接字地址结构长度字段的较老系统
// 图17-2指出ifreq结构中包含的套接字地址结构是一个通用套接字地址结构
// 但在较新系统中它可以是任何类型的套接字地址结构
// 事实上,4.4 BSD还会为每个接口返回一个数据链路套接字地址结构
#elseswitch (ifr->ifr_addr.sa_family) {
// ifreq结构中的联合把返回地址定义为通用的16字节sockaddr结构
// 它对于IPv4的16字节sockaddr_in结构是够了
// 对于IPv6的24字节sockaddr_in6结构却太小
// 因此,如果套接字地址结构中有长度成员,就使用该成员,就像以上使用max函数的语句
#ifdef IPV6case AF_INET6:len = sizeof(struct sockaddr_in6);break;
#endifcase 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;}
#endifif (ifr->ifr_addr.sa_family != family) {continue;    /* ignore if not desired address family */}// 处理别名地址myflags = 0;// Solaris用于别名地址的接口名中有一个冒号// 而4.4 BSD不在名字上区分别名地址和主地址if ((cptr = strchr(ifr->ifr_name, ':')) != NULL) {*cptr = 0;    /* replace colon with null */}// 把最近处理过的接口名存入lastname// 在与当前遍历到的接口名字比较时,若有冒号则只比较到冒号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);// 获取接口标志// 不能用当前遍历到的ifreq结构调用ioctl// 因为接口标志和IP地址在ifreq结构中是同一个联合的不同成员ifrcopy = *ifr;    // 这里ifrcopy结构中的接口名字段就被赋值为当前遍历到的接口名// 之后其他ioctl函数的接口操作请求都对应该接口名关联的接口Ioctl(sockfd, SIOCGIFFLAGS, &ifrcopy);flags = ifrcopy.ifr_flags;if ((flags & IFF_UP) == 0) {continue;    /* ignore if interface not up */}// 动态分配一个ifi_info结构,并将它加到链表末尾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_info结构中ifi->ifi_flags = flags;    /* IFF_xxx values */ifi->ifi_myflags = myflags;    /* IFI_xxx values */// 把MTU复制到当前ifi_info结构中
#if defined(SIOCGIFMTU) && defined(HAVESTRUCT_IFREQ_IFR_MTU)Ioctl(sockfd, SIOCGIFMTU, &ifrcopy);ifi->ifi_mtu = ifrcopy.ifr_mtu;
#elseifi->ifi_mtu = 0;
#endif// 把接口名复制到当前ifi_info结构中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_info结构中ifi->ifi_index = idx;ifi->ifi_hlen = hlen;if (ifi->ifi_hlen > IFI_HADDR) {ifi->ifi_hlen = IFI_HADDR;}// 如果有硬件地址,把接口硬件地址复制到当前ifi_info结构中if (hlen) {memcpy(ifi->ifi_haddr, haddr, ifi->ifi_hlen);}switch (ifr->ifr_addr.sa_family) {case AF_INET:// 把由最初的SIOCGIFCONF请求返回的IP复制到当前ifi_info结构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));// 如果当前接口支持广播,用SIOCGIFBRDADDR调用ioctl获取它的广播地址
#ifdef SIOCGIFBRDADDRif (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 SIOCGIFDSTADDRif (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));}
#endifbreak;// IPv6与IPv4情况类似,但IPv6不支持广播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 SIOCGIFDSTADDRif (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));}
#endifbreak;default:break;}}free(buf);return ifihead;    /* pointer to first structure in linked list */
}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 */}
}

以上程序中获取SIOCGIFCONF请求的结果时使用的是循环,这是因为,对于SIOCGIFOCNF请求,有些实现在缓冲区大小不足以存放结果时不返回错误,而是截断并返回成功,这意味着要知道缓冲区是否足够大的唯一方法是:发出请求,记下返回的长度,用更大的缓冲区发出请求,比较返回的长度和刚记下的长度,如果相同,我们的缓冲区才足够大。源自Berkeley的实现在缓冲区太小时不返回错误,结果会被截断成缓冲区的可用大小,即使返回的长度小于缓冲区大小时,我们也不能肯定成功,因为源自Berkeley的实现在剩下的空间装不下另一个结构时返回的长度会小于缓冲区长度。Solaris 2.5在返回长度将大于等于缓冲区长度时返回EINVAL错误。

有些实现提供了用于返回接口数目的名为SIOCGIFNUM的请求,它使应用能在发出SIOCGIFCONF请求前分配足够的缓冲区,但这个新请求尚未被广泛实现。

随着Web的增长,为SIOCGIFCONF请求返回的结果预分配一个固定长度的缓冲区这一做法也成了问题,因为大的Web服务器把越来越多的别名地址赋予单个接口,例如,Solaris 2.5对于每个接口可赋予的别名地址数限制为256,Solaris 2.6则把该限制增加到8192。使用大量别名地址的网站已经发现使用固定大小缓冲区获取接口信息的程序开始工作失常。尽管Solaris在缓冲区太小时返回错误,但很多程序只是分配固定大小的缓冲区,发出ioctl请求,却不处理可能返回的错误,导致进程可能意外死亡。

ioctl函数的SIOCGIFOCNF请求为每个已配置接口返回其名字及一个套接字地址结构,我们接着可以发出其他接口类请求设置或获取每个接口的其他特征,这些请求的get版本(SIOCGxxx)通常由netstat程序发出,set版本(SIOCSxxx)通常由ifconfig程序发出。任何用户都能获取接口信息,但设置接口信息需要超级用户权限。

这些接口操作请求接受或返回一个ifreq结构中的信息,而这个结构的地址则作为ioctl函数的第三个参数指定。接口总是以其名字标识,在ifreq结构的ifr_name成员中指定,如le0、lo0、ppp0等。

这些接口操作请求中许多使用套接字地址结构在应用进程和内核间指定或返回具体接口的IP地址或地址掩码,对于IPv4,这个地址或掩码存放在一个网际网套接字地址结构的sin_addr成员中;对于IPv6,它存放在IPv6套接字地址结构的sin6_addr成员中。

ioctl函数的通用接口请求,许多实现中还加入了其他请求:
1.SIOCGIFADDR:在ifr_addr成员中返回单播地址。

2.SIOCSIFADDR:用ifr_addr成员设置接口地址,这个接口的初始化函数也被调用。

3.SIOCGIFFLAGS:在ifr_flags成员中返回接口标志,这些标志的名字格式为IFF_xxx,它们定义在头文件net/if.h中。这些标志指示接口是否处于在工状态(IFF_UP)、是否是一个点到点接口(IFF_POINTOPOINT)、是否支持广播(IFF_BROADCAST)等。

4.SIOCSIFFLAGS:用ifr_flags成员设置接口标志。

5.SIOCGIFDSTADDR:在ifr_dstaddr成员中返回点到点地址。

6.SIOCSIFDSTADDR:用ifr_dstaddr成员设置点到点地址。

7.SIOCGIFBRDADDR:在ifr_broadaddr成员中返回广播地址。应用必须先获取接口标志,然后发出正确请求:广播接口发出本请求,点对点接口应发出SIOCGIFDSTADDR请求。

8.SIOCSIFBRDADDR:用ifr_broadaddr成员设置广播地址。

9.SIOCGIFNETMASK:在ifr_addr成员中返回子网掩码。

10.SIOCSIFNETMASK:用ifr_addr成员设置子网掩码。

11.SIOCGIFMETRIC:在ifr_metric成员返回接口测度。接口测度由内核为每个接口维护,使用它的是路由守护进程,接口测度被routed加到跳数上(使得某接口更不被看好,RIP使用跳数(Hop Count)作为度量(Metric)来衡量到达目的网络的距离)。

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

ARP高速缓存也通过ioctl函数操纵,但使用路由域套接字的系统往往改用路由套接字访问ARP高速缓存。ioctl函数的ARP高速缓存请求使用arpreq结构,它定义在头文件net/if_arp.h中:
在这里插入图片描述
ioctl函数的ARP高速缓存请求:
1.SIOCSARP:把一个新表项加入ARP高速缓存,或修改其中已经存在的一个表项。arp_pa成员是一个含有IP地址的网际套接字地址结构;arp_ha成员是一个通用套接字地址结构,它的sa_family值为AF_UNSPEC,sa_data中含有硬件地址(如6字节以太网地址)。应用可指定ATF_PERM和ATF_PUBL标志,但另外两个标志由内核设置。

2.SIOCDARP:从ARP高速缓存中删除一个表项。调用者指定要删除表项的网际网地址。

3.SIOCGARP:从ARP高速缓存中获取一个表项。调用者指定网际网地址,相应的硬件地址随标志一起返回。

只有超级用户才能增加或删除ARP高速缓存表项。以上3个请求通常由arp程序发出。

一些较新系统不支持与ARP相关的ioctl函数请求,而改用路由套接字执行这些ARP操作。

ioctl函数没办法列出ARP高速缓存中的所有表项,当指定-a选项执行arp命令(列出ARP高速缓存中所有表项)时,大多版本的arp程序通过读取内核的内存(通过/dev/kmem文件,在许多UNIX系统中,该文件被用于访问系统内核的虚拟地址空间)获得ARP高速缓存的当前内容。

使用get_ifi_info函数返回一个主机的所有IP地址,然后对每个IP地址发出一个SIOCGARP请求获取它的硬件地址:

#include "unpifi.h"
#include <net/if_arp.h>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);// 调用get_ifi_info获取本机所有IPv4地址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);
}

在hpux主机上运行以上程序:
在这里插入图片描述
有些系统提供用于操纵路由表的ioctl函数请求,这些请求的第3个ioctl函数参数是指向rtentry结构的指针,该结构定义在net/route.h头文件中,这些请求通常由route程序发出,只有超级用户才能发出这些请求。在支持路由套接字的系统中,这些请求改由路由套接字执行。

操纵路由表的ioctl函数请求:
1.SIOCADDRT:往路由表中增加一个表项。

2.SIOCDELRT:从路由表中删除一个表项。

ioctl函数不能列出路由表中所有表项,这个操作通常由netstat程序在指定-r选项时执行,netstat程序通过读取内核的内存(/dev/kmem)获得整个路由表。

上面说过,由SIOCGIFBRDADDR请求返回的广播地址是通过ifreq结构的ifr_broadaddr成员返回的,但TCPv2一书的173页中,我们注意到该广播地址是在ifr_dstaddr成员中返回的,这里没有问题,因为union的前3个成员都是套接字地址结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/255198.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

wps,word缩印怎么设置

目录 wps&#xff0c;word缩印怎么设置 宋体 字号5&#xff08;注意&#xff1a;非五号字&#xff09;字体缩放80&#xff05;&#xff0c;字体间距-2 分为4栏 wps&#xff0c;word缩印怎么设置 宋体 字号5&#xff08;注意&#xff1a;非五号字&#xff09;字体缩放80&am…

缩印技巧你知多少?缩印怎么设置?

当打印word文档或者Excel表格时&#xff0c;如果花费很多时间去编辑格式&#xff0c;又或者需要把内容尽可能花最少的纸张页数打印出来&#xff0c;那么&#xff0c;有什么办法可以快速打印出来呢&#xff1f;缩印就可以帮到忙&#xff0c;缩印怎么设置&#xff1f;小编整理了w…

手机常识汇总

目录 一、手机历史介绍 第一代模拟制式手机(1G) 什么是模拟网? 模拟网络与数字网络的区别 数字通信与模拟通信相比具有明显的优点: 第二代数字手机(2G) 什么是“GSM” 什么是 “CDMA”? GSM 数字机和模拟手机话音相比 什么是“GSM/CDMA 双模机”? 什么是“TDMA”…

chatgpt赋能python:Python中的绝对值函数

Python中的绝对值函数 在Python编程语言中&#xff0c;绝对值函数是一个非常重要和常用的函数。它可以帮助我们快速地计算一个数的绝对值&#xff0c;而不需要手动使用if语句来处理。在本文中&#xff0c;我们将介绍Python中的绝对值函数&#xff0c;并且讨论它的一些应用。 …

微信H5页面点击直接跳转app-微信开放标签

开发微信H5项目时&#xff0c;需要从h5直接跳转至app&#xff0c;绞尽脑汁调研一番后&#xff0c;发现微信开放标签能实现&#xff0c;but 环境配置和测试流程真的很复杂&#xff0c;真的配置四小时&#xff0c;开发仅需30分钟&#xff0c;经过不断踩坑终于上线&#xff0c;踩过…

微信浏览器跳转app解决方案

微信浏览器跳转app解决方案 新版本微信浏览器中&#xff0c;已禁用打开其他APP应用&#xff0c;只支持打开微信合作商 APP应用&#xff0c;所以无法通过微信浏览器直接唤醒其他APP应用。列举微信浏览器唤醒APP的2种解决方案&#xff1a; 方案一&#xff1a;通过Url 跳转到H5…

uniapp 微信授权,微信分享,微信支付,微信跳转app集成

更新提示 &#xff01;&#xff01;&#xff01;&#xff01; 更新提示 &#xff01;&#xff01;&#xff01;&#xff01; 更新提示 &#xff01;&#xff01;&#xff01;&#xff01; 在7.12号微信推出不能强制获取用户信息才能使用。再授权之前得明确告知用户。经过我的…

如何将两个微信小程序合并_微信小程序--如何在两个页面之间传值

先看一下本周的部分的设计图 在这里插入图片描述 在这里插入图片描述

注意力机制

注意力机制&#xff1a;我们会把我们的焦点聚焦在比较重要的事物上。 对于一个模型而言&#xff08;CNN、LSTM&#xff09;&#xff0c;很难决定什么是重要的&#xff0c;什么是不重要的&#xff0c;由此注意力机制诞生了。 对于一张热力图而言&#xff0c;我们不难发现人类的…

Python篇——数据结构与算法(第四部分:希尔排序及其讨论、计数排序、桶排序、基数排序)

1、希尔排序 希尔排序&#xff08;shell sort&#xff09;是一种分组插入排序算法首先取一个整数d1n/2&#xff0c;将元素分为d1个组&#xff0c;每组相邻两元素之间距离为d1&#xff0c;在各组内进行直接插入排序取第二个整数d2d1/2&#xff0c;重复上述分组排序过程&#xf…

手把手QQ机器人制作教程,根据官方接口进行开发,基于Python语言制作的详细教程(更新中)

第 1 课、注册 QQ 开放平台账户 QQ开放平台官方地址&#xff1a;https://q.qq.com/#/app/bot QQ开放平台包含&#xff1a;QQ机器人、QQ小程序、QQ小游戏&#xff0c;我们这边选择QQ机器人。 机器人类型&#xff1a;设置私域机器人或者公域机器人&#xff0c;当然公域机器人对…

【0基础QQ机器人开发】基于go-cqhttp的QQ机器人开发教程,仅供自学

文章目录 一、本文目的:二、实现历程:三、开发过程1.准备工作1.cq-http的下载地址:[Releases Mrs4s/go-cqhttp (github.com)](https://github.com/Mrs4s/go-cqhttp/releases)2.python环境的配置 2.程序配置3.python程序开发4.常用API拓展 API 及与 OneBot 标准有略微差异的 AP…

【最新】半小时教你制作出属于自己的QQ机器人【保姆级】

目录 前言QQ机器人功能展示一、安装nonebot2安装步骤建立一个新的机器人项目 二、安装go-cqhttp安装步骤修改配置 三、使用 前言 相信大家都有在QQ群见过QQ机器人&#xff0c;可以玩游戏、推送当日天气情况等。本文将基于nonebot2和go-cqhttp构建一个自定义的QQ机器人。 QQ机…

如何在linux上使用QQ(在终端上使用qq) mojo-qq

如何在linux终端上使用QQ 效果展示 介绍irc irc的历史非常悠久&#xff0c;那都是上个世界别人用来聊天的了&#xff0c;算是我接触到的最早的及时聊天 以下是来自google的简介 Internet Relay Chat (IRC) is an application layer protocol that facilitates communicatio…

QQ机器人

一、介绍 qqbot 是一个用 python 实现的、基于腾讯 SmartQQ 协议的 QQ 机器人&#xff0c;可运行在 Linux 、 Windows 和 Mac OSX 平台下。 本项目 github 地址&#xff1a; https://github.com/pandolia/qqbot 你可以通过扩展 qqbot 来实现&#xff1a; 监控、收集 QQ 消息自动…

实现一个QQ助手

一、准备工作 下载go-cqhttp&#xff0c;下载自己需要的版本&#xff0c;我是在ubuntu上搭建&#xff0c;我下载的是go-cqhttp_1.0.0-bata4_linux_amd64.deb 二、流程 2.1、生成配置文件 切换到下载路径&#xff0c;并执行如下命令&#xff1a; sudo dpkg -i go-cqhttp_1.0…

基于node.js和oicq的qq机器人 制作回顾分析笔记

目录 1 文章简介 2 项目介绍 3 qq机器人的登录部分 3.1 模块的调用 3.2 登录配置文件 3.3 登录部分 4. 普通非指令功能 4.1 自动复读 4.2 自助禁言 4.3 来点颜色 4.4 回复功能 5. 指令功能 5.1 删除图片 5.2 禁言 5.3 解除禁言 5.4 查看帮助 5.5 群白名单 5.6…

浙大知识图谱基础:学习笔记

0 基础知识 知识图谱中&#xff0c;知识的结构化表示主要有符号表示和向量表示两类方法。符号表示包括&#xff1a;一阶谓词逻辑&#xff0c;语义网络&#xff0c;描述逻辑和框架系统等。当前主要采用基于图的符号化知识表示&#xff0c;最常用的是有向标记图。 有向标记图分为…

识别在线视频中的歌曲并下载音乐

问题&#xff1a;视频中的歌曲觉得很好听&#xff0c;但又不知道是什么歌曲&#xff0c;如何解决&#xff1f; 1、在chrome商店中找到aha music 插件。 2、安装. 3、打开需要识别的视频网站&#xff0c;点击aha music按钮。 4、当找到该歌曲时&#xff0c;点击。 5、按F12 在…

小程序简单实现搜歌、听歌

这篇文章用了两个网易云音乐的接口&#xff08;不清楚是否是官方的&#xff09;&#xff0c;附上官方接口链接: 网易云音乐API / 本文所用接口&#xff1a; 1、http://musicapi.leanapp.cn/search 2、http://neteasecloudmusicapi.zhaoboy.com/song/url 效果图 相关代码如下 先…