相比较第17章用ioctl函数获取整个路由表,利用sysctl函数也是可以做到而且无需超级用户权限。
1、路由套接字上支持3种类型的操作
i、进程可以通过写出到路由套接字而往内核发送消息。路径的增加和删除采用这种操作实现。
ii、进程可以通过从路由套接字读入来自内核接收消息。内核采用这种操作通知进程已收到并处理一个ICMP重定向消息,或者请求外部路由进程解析一个路径。
iii、进程可以使用sysctl函数倾泻出路由表或列出所有已配置的接口。
数据链路套接字地址结构(定义在net/if_dl.h文件中,我的机器上没有这文件,所以本章的程序都无法编译通过,需要的可以参考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, ect. from <net/if_types.h> 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
2、介绍路由套接字的相关信息
i、通过路由套接字交换的消息类型(见书上P383)
ii、在路由消息中用于指称套接字地址结构的常值(见书上P385)
iii、获取并输出一个路由表项的代码实现(没有用sysctl函数实现)
以下程序通过命令参数取得一个ipv4点分十进制数地址,并就这个地址向内核发送一个RTM_GET消息。内核在它的ipv4路由表中查找这个地址,并作为一个RTM_GET消息返回相应路由表项的信息。先见图,再见代码。
#include "unproute.h" #define BUFLEN (sizeof(struct rt_msghdr) + 512) /* sizeof(struct sockaddr_in6) * 8 = 192 */ #define SEQ 9999 int main(int argc, char **argv) { int sockfd; char *buf; pid_t pid; ssize_t n; struct rt_msghdr *rtm;//rt_msghdr、if_msghdr、ifa_msghdr、ifma_msghdr和if_announcemsghdr的具体定义在书上P384,图18-3所示 struct sockaddr *sa, *rti_info[RTAX_MAX]; struct sockaddr_in *sin; if (argc != 2) err_quit("usage: getrt <IPaddress>"); sockfd = Socket(AF_ROUTE, SOCK_RAW, 0); /* need superuser privileges */ buf = Calloc(1, BUFLEN); /* and initialized to 0 */ rtm = (struct rt_msghdr *) buf; rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in); rtm->rtm_version = RTM_VERSION; rtm->rtm_type = RTM_GET; rtm->rtm_addrs = RTA_DST; rtm->rtm_pid = pid = getpid();//获取进程ID,并赋值 rtm->rtm_seq = SEQ; sin = (struct sockaddr_in *) (rtm + 1);//在buf的第二个缓存位置构造一个rt_msghdr结构 sin->sin_len = sizeof(struct sockaddr_in); sin->sin_family = AF_INET; Inet_pton(AF_INET, argv[1], &sin->sin_addr); Write(sockfd, rtm, rtm->rtm_msglen);//很关键哈,这是向内核传递消息 do { n = Read(sockfd, rtm, BUFLEN); } while (rtm->rtm_type != RTM_GET || rtm->rtm_seq != SEQ || rtm->rtm_pid != pid); rtm = (struct rt_msghdr *) buf; sa = (struct sockaddr *) (rtm + 1); get_rtaddrs(rtm->rtm_addrs, sa, rti_info);//此函数将构造指向路由消息中各个套接字地址结构的指针数组,由于篇幅,此处不再给出,具体见书上P389 if ((sa = rti_info[RTAX_DST]) != NULL) printf("dest: %s\n", Sock_ntop_host(sa, sa->sa_len)); if ((sa = rti_info[RTAX_GATEWAY]) != NULL) printf("gateway: %s\n", Sock_ntop_host(sa, sa->sa_len)); if ((sa = rti_info[RTAX_NETMASK]) != NULL) printf("netmask: %s\n", Sock_masktop(sa, sa->sa_len)); if ((sa = rti_info[RTAX_GENMASK]) != NULL) printf("genmask: %s\n", Sock_masktop(sa, sa->sa_len)); exit(0); }
3、sysctl函数及其使用(上述程序创建一个AF_ROUTE域raw socket,需要root权限,使用此函数不限用户权限)
#include<sys/param.h> #include<sys/sysctl.h> //oldp供内核存放值的buffer,oldlenp代表buffer大小(值-结果参数) int sysctl(int *name, u_int namelen, void* oldp, size_t* oldlenp, void* newp, size_t newlen);//若成功则为0,若出错则为-1
上述函数中的形参还需要着重强调一下name,name参数是指定名字的一个整数数组,namelen参数指定该数组中的元素数目。该数组中的第一个指定本请求定向到内核的哪个子系统。第二个及其后元素逐次细化指定该子系统的某个部分。
当name数组的第二个元素为AF_ROUTE时,第三个元素(协议号)总是为0(因为AF_ROUTE族不像比如说AF_INET族那样其中有协议),第四个元素是一个地址族,第五和第六级指定做什么。如下图所示:
name[] | 返回IPv4路由表 | 返回IPv4ARP高速缓存 | 返回IPv6路由表 | 返回接口清单 |
0 | CTL_NET | CTL_NET | CTL_NET | CTL_NET |
1 | AF_ROUTE | AF_ROUTE | AF_ROUTE | AF_ROUTE |
2 | 0 | 0 | 0 | 0 |
3 | AF_INET | AF_INET | AF_INET6 | 0 |
4 | NET_RT_DUMP | NET_RT_FLAGS | NET_RT_DUMP | NET_RT_IFLIST |
5 | 0 | RTF_LLINFO | 0 | 0 |
NET_RT_DUMP返回由name[3]指定的地址族的路由表。如果所指定的地址族为0,那么返回所有地址族的路由表。
NET_RT_FLAGS返回由name[3]指定的地址族的路由表,但是仅限于那些所带标志(若干个RTF_xxx常值的逻辑或)与name[5]指定的标志相匹配的路由表项。路由表中所有ARP高速缓存表项均设置了RTF_LLINFO标志位。
NET_RT_IFLIST返回所有已配置接口的信息。如果name[5]不为0,它就是某个接口的索引号,于是仅仅返回该接口的信息。已赋予每个接口的所有地址也同时返回,不过如果name[3]不为0,那么仅限于返回指定地址族的地址。
利用sysctl函数实现char* net_rt_iflist(int family,int flags,size_t *lenp);代码如下:
#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)//首先获取buffer大小 return(NULL); if ( (buf = malloc(*lenp)) == NULL)//再根据lenp的大小分配内存 return(NULL); if (sysctl(mib, 6, buf, lenp, NULL, 0) < 0) {//通过内核返回在buffer中信息获取网络、路由、接口信息 free(buf); return(NULL); } return(buf); }
由于本人所装ubuntu14.04实现不了本章的程序,所以此处利用sysctl函数实现get_ifi_info函数不再给出,具体见书上P395-P396。
4、接口名字和索引函数(用于需要描述一个接口的场合,为ipv6 API引入,也适用于ipv4)
每个接口都有一个唯一的名字和一个唯一的正值索引(0从不用作索引)。
#include<net/if.h> unsigned in 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);
上述四个接口名字和索引函数均通过net_rt_iflist函数实现,具体实现见书上P397-P400。
以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。