UNP卷一chapter18 路由套接字

相比较第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),刚开始学习网络编程,如有不正确之处请大家多多指正。

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80473813