路由套接字上支持3种类型的操作
1). 进程能通过写路由套接字向内核发消息。
2). 进程能通过路由套接字从内核读消息。
3). 进程可以用sysctl函数得到路由表或列出所有已配置的接口。
数据链路套接字地址结构
通过路由套接字返回的一些消息中含有作为返回值给出的数据链路套接字地址结构
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 */ uint8_t sdl_nlen; /* name length, starting in sdl_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 */
};
sdl_data成员含有名字和链路层地址(例如以太网接口的48位MAC地址)。
名字从sdl_data[0]开始,而且不以空字符结尾。链路层地址从sdl_data[sdl_nlen]开始
读和写
创建一个路由套接字后,进程可以通过写到该套接字向内核发送命令,通过读自该套接字从内核接收信息。
路由域套接字共有12个路由消息(还有黑体3个是新增的)
通过路由套接字交换的结构有5个类型:rt_msghdr、if_msghdr、ifa_msghdr、ifma_msghdr和if_announcemsghdr
每个结构有相同的前3个成员:本消息的长度、版本和类型。类型成员是上图第一列中的常值之一。
例子:获取并输出一个路由表项
这个程序使用一个IPv4点分十进制数地址作为命令行参数,并就这个地址向内核发送一个RTM_GET消息。
内核在它的IPv4路由表中查找这个地址,并作为一个RTM_GET消息返回相应路由表项的消息。
该程序代码如下
1 /* include getrt1 */ 2 #include "unproute.h" 3 4 #define BUFLEN (sizeof(struct rt_msghdr) + 512) 5 /* sizeof(struct sockaddr_in6) * 8 = 192 */ 6 #define SEQ 9999 7 8 int 9 main(int argc, char **argv) 10 { 11 int sockfd; 12 char *buf; 13 pid_t pid; 14 ssize_t n; 15 struct rt_msghdr *rtm; 16 struct sockaddr *sa, *rti_info[RTAX_MAX]; 17 struct sockaddr_in *sin; 18 19 if (argc != 2) 20 err_quit("usage: getrt <IPaddress>"); 21 22 sockfd = Socket(AF_ROUTE, SOCK_RAW, 0); /* need superuser privileges */ 23 24 buf = Calloc(1, BUFLEN); /* and initialized to 0 */ 25 26 rtm = (struct rt_msghdr *) buf; 27 rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in); 28 rtm->rtm_version = RTM_VERSION; 29 rtm->rtm_type = RTM_GET; 30 rtm->rtm_addrs = RTA_DST; 31 rtm->rtm_pid = pid = getpid(); 32 rtm->rtm_seq = SEQ; 33 34 sin = (struct sockaddr_in *) (rtm + 1); 35 sin->sin_len = sizeof(struct sockaddr_in); 36 sin->sin_family = AF_INET; 37 Inet_pton(AF_INET, argv[1], &sin->sin_addr); 38 39 Write(sockfd, rtm, rtm->rtm_msglen); 40 41 do { 42 n = Read(sockfd, rtm, BUFLEN); 43 } while (rtm->rtm_type != RTM_GET || rtm->rtm_seq != SEQ || 44 rtm->rtm_pid != pid); 45 /* end getrt1 */ 46 47 /* include getrt2 */ 48 rtm = (struct rt_msghdr *) buf; 49 sa = (struct sockaddr *) (rtm + 1); 50 get_rtaddrs(rtm->rtm_addrs, sa, rti_info); 51 if ( (sa = rti_info[RTAX_DST]) != NULL) 52 printf("dest: %s\n", Sock_ntop_host(sa, sa->sa_len)); 53 54 if ( (sa = rti_info[RTAX_GATEWAY]) != NULL) 55 printf("gateway: %s\n", Sock_ntop_host(sa, sa->sa_len)); 56 57 if ( (sa = rti_info[RTAX_NETMASK]) != NULL) 58 printf("netmask: %s\n", Sock_masktop(sa, sa->sa_len)); 59 60 if ( (sa = rti_info[RTAX_GENMASK]) != NULL) 61 printf("genmask: %s\n", Sock_masktop(sa, sa->sa_len)); 62 63 exit(0); 64 } 65 /* end getrt2 */
17 创建一个AF_ROUTE域的原始套接字(SOCK_RAW)
18-25 分配一个缓冲区。在该缓冲区上构造一个rt_msghdr结构:天界我们的请求,并存放我们的进程ID和选定的序列号。
26-29 紧跟着rt_msghdr结构,我们构造一个sockaddr_in结构。
30-34 write构造号的消息到内核调用read等待应答。因为其他进程也可能打开路由套接字,而且内核给所有路由套接字都传送一个全部路由消息的副本。
于是我们必须检查消息的类型、序列号和进程ID,以确保收到的消息正是我们所等待的应答。
35-36 rtm指向rt_msghdr结构,sa指向接在其后的第一个套接字地址结构
37 rtm_addrs是一个数位掩码,指出接在rt_msghdr结构之后的是8个可能的套接字地址结构中的哪几个。
get_rtaddrs函数以该掩码和指向第一个套接字地址结构的指针为参数(sa)为参数,在rti_info数组中填入指向相应套接字地址结构的指针
下面是get_rtaddrs函数
1 #include "unproute.h" 2 3 /* 4 * Round up 'a' to next multiple of 'size', which must be a power of 2 5 */ 6 #define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a)) 7 8 /* 9 * Step to next socket address structure; 10 * if sa_len is 0, assume it is sizeof(u_long). 11 */ 12 #define NEXT_SA(ap) ap = (SA *) \ 13 ((caddr_t) ap + (ap->sa_len ? ROUNDUP(ap->sa_len, sizeof (u_long)) : \ 14 sizeof(u_long))) 15 16 void 17 get_rtaddrs(int addrs, SA *sa, SA **rti_info) 18 { 19 int i; 20 21 for (i = 0; i < RTAX_MAX; i++) { 22 if (addrs & (1 << i)) { 23 rti_info[i] = sa; 24 NEXT_SA(sa); 25 } else 26 rti_info[i] = NULL; 27 } 28 }
下面是sock_maskop,它返回可通过路由套接字返回的那两种掩码的表达字符串。
1 #include "unproute.h" 2 3 const char * 4 sock_masktop(SA *sa, socklen_t salen) 5 { 6 static char str[INET6_ADDRSTRLEN]; 7 unsigned char *ptr = &sa->sa_data[2]; 8 9 if (sa->sa_len == 0) 10 return("0.0.0.0"); 11 else if (sa->sa_len == 5) 12 snprintf(str, sizeof(str), "%d.0.0.0", *ptr); 13 else if (sa->sa_len == 6) 14 snprintf(str, sizeof(str), "%d.%d.0.0", *ptr, *(ptr+1)); 15 else if (sa->sa_len == 7) 16 snprintf(str, sizeof(str), "%d.%d.%d.0", *ptr, *(ptr+1), *(ptr+2)); 17 else if (sa->sa_len == 8) 18 snprintf(str, sizeof(str), "%d.%d.%d.%d", 19 *ptr, *(ptr+1), *(ptr+2), *(ptr+3)); 20 else 21 snprintf(str, sizeof(str), "(unknown mask, len = %d, family = %d)", 22 sa->sa_len, sa->sa_family); 23 return(str); 24 }
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);
我们对路由套接字的主要兴趣点在于使用sysctl函数检查路由表和接口列表。
name参数是指定名字的一个整数数组,namelen参数指定该数组中的元素数目。该数组中的第一个元素指定本请求定向到内核的哪个子系统,第二个及其后元素逐次系话指定该子系统的某个部分。
下面展示了这样的分成排列
为了获取某个值,oldp参数指向一个供内核存放该值得缓冲区,oldlenp则是一个值-结果参数。
为了设置某个值,newp参数指向一个大小为newlen参数的缓冲区。
该函数可以获取各种系统信息,有文件系统、虚拟内存、内核限制、硬件等各方面的信息。
我们感兴趣的是网络子系统,通过吧name数组的第一个元素设置为CTL_NET来指定,第二个元素可以是以下几种:
AF_INET: 获取或设置影响网际网协议的变量。下一级为使用某个IPPROTO_xxx常值指定的具体协议
AF_LINK: 获取或设置链路层信息,比如PPP接口的数目
AF_ROUTE: 返回路由表或接口列表的信息
AF_UNSPEC: 获取或设置一些套接字层变量,比如套接字发送或接收缓冲区的最大大小
下面给出如果name数组第二个元素为AF_ROUTE时,该数组可能的取值
NET_RT_DUMP返回由name[3]指定的地址族的路由表。如果所指定的地址族为0,那么返回所有地址族的路由表。
NET_RT_FLAGS返回由name[3]指定的地址族的路由表,但是仅限于那些所带标志(若干个RTF_xxx常值的逻辑或)与由name[5]指定的标志相匹配的路由表项。
NET_RT_IFLIST返回所有已配置接口的信息。
例子:判断UDP校检和是否开启
1 #include "unproute.h" 2 #include <netinet/udp.h> 3 #include <netinet/ip_var.h> 4 #include <netinet/udp_var.h> /* for UDPCTL_xxx constants */ 5 6 int 7 main(int argc, char **argv) 8 { 9 int mib[4], val; 10 size_t len; 11 12 mib[0] = CTL_NET; 13 mib[1] = AF_INET; 14 mib[2] = IPPROTO_UDP; 15 mib[3] = UDPCTL_CHECKSUM; 16 17 len = sizeof(val); 18 Sysctl(mib, 4, &val, &len, NULL, 0); 19 printf("udp checksum flag: %d\n", val); 20 21 exit(0); 22 }