UNP Chapter 17 - 路由套接口
17.1. 概述
在路由器接口中支持三种类型的操作
1. 进程能通过写路由套接口向内核发消息。
2. 进程能在路由套接口上从内核读消息,这是核心通知进程已收到一个ICMP重定向消息并进行了处理的方式。
3. 进程可以用sysctl函数得到路由表或列出所有已配置的接口。
17.2. 数据链路套接口地址结构
在路由套接口上返回的一些消息中包含数据链路套接口地址结构,他在<net/if_dl.h>定义
struct sockaddr_dl
{
uint8_t sdl_len;
sa_family_t sdl_family; /* AF_LINK */
uint16_t sdl_index; /* system assigned index, if > 0 */
uint8_t sdl_type; /* IFT_ETHER, etc. from <net/if_types.h> */
uint8_t sdl_nlen; /* name length, starting in sal_data[0] */
uint8_t sdl_alen; /* link-layer address length */
uint8_t sdl_slen; /* link-layer selector length */
char sdl_data[12]; /* minimum work area, can be larger; contains i/f name and link-layer address */
};
每个接口都有一个唯一的大于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,释放这些内存时就将不得不遍历整个数组,给每个名字释放内存,然后释放数组本身 */
}
17.7. 小结