UNP Chapter 17 - 路由套接口
17.1. 概述
在路由器接口中支持三种类型的操作
1. 进程能通过写路由套接口向内核发消息。
2. 进程能在路由套接口上从内核读消息,这是核心通知进程已收到一个ICMP重定向消息并进行了处理的方式。
3. 进程可以用sysctl函数得到路由表或列出所有已配置的接口。
17.2. 数据链路套接口地址结构
在路由套接口上返回的一些消息中包含数据链路套接口地址结构,他在<net/if_dl.h>定义
每个接口都有一个唯一的大于0的索引号。sdl_data成员包含名字和链路层地址,名字从sdl_data[0]开始,而且不以空字符终止。链路层地址从名字后面的sdl_nlen字节开始。这个头文件定义了下面这个宏以返回指向链路层地址的指针:
#define LLADDR(s) ((caddr_t) ((s)->sdf_data + (s)->sdl_nlen))
这些套接口地址结构是可变长度的
17.3. 读和写
进程在创建路由套接口后,可以通过写或读套接口向内核发命令或从核心读信息
通过路由套接口交换的结构有三种:rt_msghdr, if_msghdr, ifa_msghdr
struct rt_msghdr /* from <net/route.h> */ { u_short rtm_msglen; /* to skip over non-understood messages */ u_char rtm-version; /* future binary compatibility */ u_char rtm_type; /* message type */ u_short rtm_index; /* index for associated ifp */ int rtm_flags; /* flags, incl, ken & message, e.g. , DONE */ int rtm_addrs; /* bitmask identifying sockaddrs in msg */ pid_t rtm_pid; /* identify sender */ int rtm_seq; /* for sender to identify action */ int rtm_errno; /* why failed */ int rtm_use; /* from rtentry */ u_long rtm_inits; /* which metrics we are initializing */ struct rt_metrics rtm_rmx; /* metrics themselves */ }; struct if_msghdr /* from <net/if.h> */ { u_short ifm_msglen; /* to skip over non-understood messages */ u_char ifm_version; /* future binary compatibility */ u_char ifm_type; /* message type */ int ifm_addr; /* like rtm_addrs */ int ifm_flags; /* value of if_flags */ u_short ifm_index; /* index for associated ifp */ struct if_data ifm_data; /* statistics and other data about if */ }; struct ifa_msghdr /* from <net/if.h> { u_short ifam_msglen; /* to skip over non-understood messages */ u_char ifam_version; /* future binary compatibility */ u_char ifam_type; /* message type */ int ifam_addrs; /* like rtm_addrs */ int ifam_flags; /* value of ifa_flags */ u_short ifam_index; /* index of associated ifp */ int ifam_metric; /* value of ifa_metric *? }
17.4. sysctl操作
我们对路由套接口的主要兴趣是用sysctl函数检查路由表和接口表
#include <sys/param.h> #include <sys/sysctl.h> int sysctl(int * name, u_int namelen, void * oldp, size_t * oldlenp, void * newp, size_t newlen); //返回: 成功为0,出错为-1
这个函数使用类似SNMP(简单网络管理协议)MIB(管理信息库)的名字
参数name是指定名字的一个整数数组,namelen是数组中的元素数目。数组的第一个元素指明请求被发往内核的哪个子系统,第二个参数指明这个子系统的某个部分,依次类推。要取一个值,oldp需指向一个缓冲区,以让内核存放该值。oldlenp是一个值-结果参数:调用函数时oldlenp指向的值是缓冲区的大小,返回的值是内核在缓冲区中返回的数据量,如果缓冲区不够大,就返回ENOMEM错误。作为一个特例,oldp可以是一个空指针而oldlenp是一个非空指针,内核确定这个调用本应返回的数据量,并通过oldlenp返回这个值。要设置一个新值,newp需指向一个大小为newlen的缓冲区,如果没有指定新值,newp应为一个空指针,newlen应为0
我们感兴趣的是网络子系统,通过把名字数组的第一个元素设为CTL_NET来指定,第二个元素可以是:
AF_INET
AF_LINK
AF_ROUTE
AF_UNSPEC
现在提供一个sysctl的简单例子,这个例子使用网际协议检查UDP校验和是否打开
#include "unproute.h" #include <netinet/udp.h> #include <netinet/ip_var.h> #include <netinet/udp_var.h> /* for UDPCTL_xxx constants */ int main(int argc, char * * argv) { int mib[4], val; size_t len; mib[0] = CTL_NET; mib[1] = AF_INET; mib[2] = IPPROTO_UDP; mib[3] = UDPCTL_CHECKSUM; len = sizeof(val); Sysctl(mib, 4, &val, &len, NULL, 0); /* 因为只取变量而不是存入变量,所以把sysctl的newp参数设为空指针,newlen参数设为0 */ printf("udp checksum flag: %d \n", val); exit(0); }
17.5. get_ifi_info函数
用sysctl代替SIOCGIFCONF ioctl的get_ifi_info函数新版本
首先给出函数net_rt_iflist,这个函数用NET_RT_IFLIST命令调用sysctl,返回指定地址族的接口列表
#include "unproute.h" char * net_rt_iflist(int family, int flags, size_t * lenp) { int mib[6]; char * buf; mib[0] = CTL_NET; mib[1] = AF_ROUTE; mib[2] = 0; mib[3] = family; /* only addresses of this family */ mib[4] = NET_RT_IFLIST; mib[5] = flags; /* interface index, or 0 */ if(sysctl(mib, 6, NULL, lenp, NULL, 0) < 0) /* 第一次调用sysctl时第三个参数为空,在lenp指向的变量中返回存放所有结构信息要用的缓冲区的大小 */ return(NULL); if((buf = malloc( * lenp)) == NULL) /* 给缓冲区分配空间 */ return(NULL); if(sysctl(mib, 6, buf, lemp, NULL, 0) < 0) /* 第二次调用sysctl,这次第三个参数非空,这次lenp指向的变量将返回存放在缓冲区中的信息量,这个变量是调用者分配的,指向这个缓冲区的指针也返回给调用者 */ { free(buf); return(NULL); } return(buf); }
下面是get_ifi_info函数
struct ifi_info * get_ifi_info(int family, int doaliases) { int flags; char * buf, * next, * lim; size_t len; struct if_msghdr * ifm; struct ifa_msghdr * ifam; struct sockaddr * sa, * rti_info[RTAX_MAX]; struct sockaddr_dl * sdl; struct ifi_info * ifi, * ifisave, * ifihead, * * ifipnext; buf = Net_rt_iflist(family, 0, &len); /* 声明局部变量,然后调用net_rt_iflist函数 */ ifihead = NULL; ifipnext = &ifihead; lim = buf + len; for(next = buf; next < lim; next += ifm->ifm_msglen) {/* for循环对sysctl返回的缓冲区中的每个路由消息进行处理,我们假定每个消息是一个if_msghdr结构,并查看其ifm_type会员 */ ifm = (struct if_msghdr *)next; if(ifm->ifm_type == RTM_IFINFO) {/* 给每个接口返回一个RTM_IFINFO结构,如果接口不在工作则将其忽略 */ if(((flags = ifm->ifm_flags) & IFF_UP) == 0) continue; /* ignore if interface not up */ sa = (struct sockaddr *)(ifm + 1); /* sa指向if_msghdr结构后的第一个套接口地址结构 */ get_rtaddrs(ifm->ifm_addrs, sa, rti_info); /* get_rtaddrs函数根据出现的是哪一种套接口地址结构来初始化rti_info数组 */ if((sa = rti_info[RTAX_IFP]) != NULL) { ifi = Calloc(1, sizeof(struct ifi_info)); /* 如果该接口名有对应的套接口地址结构,我们就分配一个ifi_info结构存放接口标志*/ * ifipnext = ifi; /* prev points to this new one */ ifipnext = &ifi->ifi_next; /* ptr to next one goes here */ ifi->ifi_flags = flags; if(sa->sa_family == AF_LINK) /* 这个套接口地址结构所期望的地址族为AF_LINK,表示它是一个数据链路socket地址结构 */ { sdl = (struct sockaddr_dl *) sa; if(sdl->sdl_nlen > 0) /* 如果sdl_nlen成员不为0,则把接口名拷贝到ifi_info结构中 */ snprintf(ifi->ifi_name, IFI_NAME, " % * s", sdl->sdl_nlen, &sdl->sdl_data[0]); else /* 否则把含有这个接口索引的字符串存为接口名 */ snprintf(ifi->ifi_name, IFI_NAME, "index %d", sdl->sdl_index); if( (ifi->ifi_nlen = sdl->sdl_alen) > 0) /* 如果sdl_alen成员不为0,则把硬件地址拷贝到ifi_info结构中,其长度在ifi_hlen中返回 */ memcpy(ifi->ifi_haddr, LLADDR(sdl), min(IFI_HADDR, sdl->sdl_alen)); } } } else if(ifm->ifm_type == RTM_NEWADDR) /* sysctl对该接口的每个地址返回一个RTM_NEWADDR消息:包括主地址和所有别名地址 */ { if(ifi->ifi_addr) /* already have an IP addr for i/f */ {/* 如果已经给该接口填写了IP地址,那么我们是在进行对别名地址的处理。在这种情况下,如果调用者想要别名地址,就必须给另外一个ifi_info结构分配内存,拷贝已填写的字段,然后填入已返回的各个别名地址 */ if(doaliases == 0) continue; /* we have a new IP addr for existing interface */ ifisave = ifi; ifi = Calloc(1, sizeof(sturct ifi_info)); * ifipnext = ifi; /* prev points to this new one */ ifipnext = &ifi->ifi_next; /* ptr to next one goes here */ ifi->ifi_flags = ifisave->ifi_flags; ifi->ifi_hlen = ifisave->ifi_hlen; memcpy(ifi->ifi_name, ifisave->ifi_name, IFI_NAME); memcpy(ifi->ifi_haddr, ifisave->ifi_haddr, IFI_HADDR); } ifam = (sturct ifa_msghdr *) next; sa = (struct sockaddr * )(ifam + 1); get_rtaddrs(ifam->ifam_addrs, sa, rti_info); if((sa = rti_info[RTAX_IFA]) != NULL) { ifi->ifi_addr = Calloc(1, sa->sa_len); memcpy(ifi->ifi_addr, sa, sa->sa_len); } /* 如果接口支持广播,则返回广播地址 */ if((flags & IFF_BROADCAST) && (sa = rti_info[RTAX_BRD]) != NULL) { ifi->ifi_brdaddr = Calloc(1, sa->sa_len); memcpy(ifi->ifi_brdaddr, sa, sa->sa_len); } /* 如果接口是一个点对点接口,则返回目的地址 */ if((flags & IFF_POINTOPOINT) && (sa = rti_info[RTAX_BRD]) != NULL) { ifi->ifi_dstaddr = Calloc(1, sa->sa_len); memcpy(ifi->ifi_dstaddr, sa, sa->sa_len); } } else err_quit("unexpected message type %d", ifm->ifm_type); } /* "ifihead" points to the first structure in the linked list */ return(ifihead); /* ptr to first structure in linked list */ }
17.6. 接口名和索引函数
#include <net/if.h> unsigned int if_nametoindex(const char * ifname); // 返回:成功时为正的接口索引,出错时为0 char * if_indextoname(unsigned int ifindex, char * ifname); // 返回: 成功时为指向接口名的指针,出错时为NULL struct if_nameindex * if_nameindex(void); //返回: 成功时为非空指针,出错时为NULL void if_freenameindex(struct if_nameindex * ptr);
if_nametoindex返回名为ifname的接口的索引
if_indextoname对给定的ifindex返回一个指向其接口名的指针
ifname参数指向一个大小为IFNAMSIZ头文件中定义的缓冲区,调用者必须分配这个缓冲区以保存结果,成功时这个指针也是函数的返回值
if_nameindex返回一个指向if_nameindex结构的数组的指针
struct if_nameindex { unsigned int if_index; /* 1,2... */ char * if_name; /* null terninated name: "le0", ... */ };
数组的最后一项是一个index为0,if_name为空指针的结构。这个数组和数组中各元素指向的名字所用的内存是动态分配的,调用if_freenameindex可释放这些内存
下面使用路由套接口提供这四个函数的一个实现
// if_nametoindex函数 #include "unpifi.h" #include "unproute.h" unsigned int if_nametoindex(const char * name) { unsigned int index; char * buf, * next, * lim; size_t len; struct if_msghdr * ifm; struct sockaddr * sa, * rti_info[RTAX_MAX]; struct sockaddr_dl * sdl; if((buf = net_rt_iflist(0, 0, &len)) == NULL) /* net_rt_iflist函数返回接口列表 */ return(0); lim = buf + len; for(next = buf; next < lim; next +=ifm->ifm_msglen) { ifm = (struct if_msghdr *)next; if(lfm->ifm_type == RTM_IFINFO) { sa = (struct sockaddr *) (ifm + 1); get_rtaddr(ifm->ifm_addrs, sa, rti_info); if((sa = rti_info[RTAX_IFP]) != NULL) { if(sa->sa_family == AF_LINK) { sdl = (struct sockaddr_dl * ) sa; if(strncmp(&sdl->sdl_data[0], name, sdl->sdl_len) == 0) { index = sdl->sdl_index; /* save before free() */ free(buf); return(index); } } } } } free(buf); return(0); /* no match for name */ }
// if_indextoname函数 #include "unpifi.h" #include "unproute.h" char * if_indextoname(unsigned int index, char * name) { char * buf, * next, * lim; size_t len; struct if_msghdr * ifm; struct sockaddr * sa, * rti_info[RTAX_MAX]; struct sockaddr_dl * sdl; if( (buf = net_rt_iflist(0, index, &len)) == NULL) return(NULL); lim = buf + len; for(next = buf; next < lim; next += ifm->ifm_msglen) { lim = (struct if_msghdr *) next; if(ifm->ifm_type == RTM_IFINFO) { sa = (struct sockaddr *) ( ifm + 1 ); get_rtaddrs(ifm->ifm_addrs, sa, rti_info); if( (sa=rti_info[RTAX_IFP]) != NULL) { if(sa->sa_family == AF_LINK) { sdl = (struct sockaddr_dl *) sa; if(sdl->sdl_index == index) { strncpy(name, sdl->sdl_data, sdl->sdl_len); name[sdl->sdl_nlen] = 0; /* null terminate */ free(buf); return(name); } } } } } free(buf); return(0); /* no match for name */ }
// if_nameindex函数 // 返回一个inf_nameindex结构数组,它包含所有的接口名和索引 #include "unpifi.h" #include "unproute.h" struct if_nameindex * if_nameindex(void) { char * buf, * next, * lim; size_t len; struct if_msghdr * ifm; struct sockaddr * sa, * rti_info[RTAX_MAX]; struct sockaddr * sdl; struct if_nameindex * result, * ifptr; char * nameptr; if((buf = net_rt_iflist(0, 0, &len)) == NULL) /* 调用net_rt_iflist函数返回接口列表 */ return(NULL); /* 用返回的大小作为将分配的缓冲区的大小,以存放返回的if_nameindex结构数组 */ /* 这是一个过高的估计,但要比遍历两遍接口表简单:一次是计算接口的数目和名字总共使用的空间大小,另一次是填写信息 */ /* 从这个缓冲区的开头往前(正向)构建if_nameindex数组,从缓冲区末尾往后(反向)存放接口名 */ if((result = malloc(len)) == NULL) /* overestimate */ return(NULL); ifptr = result; namptr = (char*) result + len; /* name start at end of buffer */ lim = buf + len; for(next = buf; next < lim; next += ifm->ifm_msglen) { ifm = (struct if_msghdr *) next; if( ifm->ifm_type == RTM_IFINFO) {/* 在所有消息中查找RTM_INFO消息以及紧接着的数据链路套接口地址结构 */ sa = (struct sockaddr *) (ifm+1); get_rtaddrs(ifm->addrs, sa, rti_info); if((sa = rti_info[RTAX_IFP]) != NULL) { if(sa->sa_family == AF_LINK) { sdl = (struct sockaddr_dl *) sa; namptr -= sdl->sdl_nlen + 1; strncpy(nameptr, &sdl->sdl_data[0], sdl->sdl_nlen); namptr[sdl->sdl_nlen] = 0; /* null terminate */ ifptr->if_name = namptr; ifptr->if_index = sdl->sdl_index; ifptr++; } } } } ifptr->if_name = NULL; /* mark end of array of structs */ ifptr->if_index = 0; /* 把数组的最后一项的if_name置为空,索引置为0 */ free(buf); return(result); /* caller can free() this when done */ }
// if_freenameindex函数 // 它释放给if_nameindex结构数组和其中的名字分配的内存 void if_freenameindex(struct if_nameindex * ptr) { free(ptr); /* 这个函数很简单,因为我们把结构数组和名字存放在同一个缓冲区中。 如果对每个名字都调用malloc,释放这些内存时就将不得不遍历整个数组,给每个名字释放内存,然后释放数组本身 */