linux/macos获取路由信息

netlink

什么是netlink?

netlink是linux提供的用于内核和用户进程之间的通信方式。虽然Netlink主要用于用户空间和内核空间的通信,但是也能用于用户空间的两个进程通信。

一般来说,用户进程和内核空间通信的方式有三种:/proc、ioctl、netlink;前两种是单向的,netlink可以实现双工通信。

每个netlink协议,通常与一个或一组内核服务/组件相关联,如NETLINK_ROUTE用于获取和设置路由与链路信息、NETLINK_KOBJECT_UEVENT用于内核向用户空间的udev进程发送通知等;netlink具有如下特点:

① 支持全双工、异步通信(当然同步也支持)
② 用户空间可使用标准的BSD socket接口(但netlink并没有屏蔽掉协议包的构造与解析过程,推荐使用libnl等第三方库)
③ 在内核空间使用专用的内核API接口
④ 支持多播(因此支持“总线”式通信,可实现消息订阅)
⑤ 在内核端可用于进程上下文与中断上下文

netlink套接字

创建一个netlink套接字:
socket ( AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) ;
AF_NETLINK表示是一个协议族,NETLINK_ROUTE表示协议族中具体的协议(netlink协议族中的协议还有很多,如果某协议族中协议的种类单一,该参数可以为0);
一般我们使用AF_NETLINK都需要指定一个协议,可以使用内核预留的NETLINK_GENERIC(定义在linux/netlink.h中),也可以使用我们自定义的(即定义一个内核中还没占用的数字,注意:自定义协议不一定非要添加到linux/netlink.h中,只要用户态和内核态代码都能找到该定义就行);
在使用netlink进行数据通信时,我们需要自己定义包头(不同于TCP协议,TCP协议会自动填充通信头部信息),有关包头的作用,在后面解释。

重要的数据结构

通信中使用的几个重要的数据结构的关系如下:
在这里插入图片描述

struct msghdr

socket消息的发送和接收函数一般有这几对:recv/send、readv/writev、recvfrom/sendto、recvmsg/sendmsg,前面三对函数各有各的特点功能,而recvmsg/sendmsg就是要囊括前面三对的所有功能,当然还有自己特殊的用途。
msghdr的前两个成员就是为了满足recvfrom/sendto的功能;
中间两个成员msg_iov和msg_iovlen则是为了满足readv/writev的功能;
而最后的msg_flags则是为了满足recv/send中flag的功能;
剩下的msg_control和msg_controllen则是满足recvmsg/sendmsg特有的功能。

struct sockaddr_nl

sockaddr_nl为netlink中保存地址使用的结构体,其作用与sockaddr_in相似,对比如下:
在这里插入图片描述

struct sockaddr_nl
{
    sa_family_t nl_family; /*该字段总是为AF_NETLINK */
    unsigned short nl_pad; /* 目前未用到,填充为0*/
    __u32 nl_pid; /* process pid */
    __u32 nl_groups; /* 如果用户进程希望加入某个多播组,设置该值 */
};
struct nlmsghdr

netlink的消息由消息头和消息体两部分组成,nlmsghdr为保存消息头的结构:

struct nlmsghdr
{
    __u32 nlmsg_len; /* Length of message including header */
    __u16 nlmsg_type; /* Message content */
    __u16 nlmsg_flags; /* Additional flags */
    __u32 nlmsg_seq; /* Sequence number */
    __u32 nlmsg_pid; /* Sending process PID */
};
/*
* nlmsg_type为消息的类型,例举以下四种:
* NLMSG_NOOP 空消息
* NLMSG_ERROR 消息中包含一个错误
* NLMSG_DONE 通过netlink返回多条消息,最后一条消息为NLMSG_DONE
* NLMSG_OVERRUN 扩展使用
*/ 
linux系统下通过Netlink获取内核路由表信息

通过NETLINK_ROUTE协议向内核请求路由表信息,本段代码的业务是通过get_gateway函数输入网卡名称,返回网关地址,其中在parseRoutes函数中可以解析系统完整的路由表信息。

#define RECVMSGSIZE 8192 /* recv buf size */
struct route_info
{
        u_int dstAddr;
        u_int srcAddr;
        u_int gateWay;
        char ifName[IF_NAMESIZE];
};
int readNlSock(int sockFd, char *bufPtr, int seqNum, int pId)
{
        struct nlmsghdr *nlHdr;
        int readLen = 0, msgLen = 0;
        do
        {
                if((readLen = recv(sockFd, bufPtr, RECVMSGSIZE - msgLen, 0)) < 0)
                {
                        perror("SOCK READ: ");
                        return -1;
                }

                nlHdr = (struct nlmsghdr *)bufPtr;
                if((NLMSG_OK(nlHdr, readLen) == 0) || (nlHdr->nlmsg_type == NLMSG_ERROR))
                {
                        perror("Error in recieved packet");
                        return -1;
                }
                if(nlHdr->nlmsg_type == NLMSG_DONE)
                {
                        break;
                }
                else
                {
                        bufPtr += readLen;
                        msgLen += readLen;
                }

                if((nlHdr->nlmsg_flags & NLM_F_MULTI) == 0)
                {
                        break;
                }

        }while((nlHdr->nlmsg_seq != seqNum) || (nlHdr->nlmsg_pid != pId));
        return msgLen;
}
char* parseRoutes(struct nlmsghdr *nlHdr, struct route_info *rtInfo, const char *inter_name , const char * ip_addr)
{
        struct rtmsg *rtMsg;
        struct rtattr *rtAttr;
        int rtLen;
        char *tempBuf = NULL;
        struct in_addr gate;
        int is_find = 0;
        rtMsg = (struct rtmsg *)NLMSG_DATA(nlHdr);
        if((rtMsg->rtm_family != AF_INET) || (rtMsg->rtm_table != RT_TABLE_MAIN))
        {
                return NULL;
        }

        rtAttr = (struct rtattr *)RTM_RTA(rtMsg);
        rtLen = RTM_PAYLOAD(nlHdr);
        for(;RTA_OK(rtAttr,rtLen);rtAttr = RTA_NEXT(rtAttr,rtLen))
        {
                switch(rtAttr->rta_type)
                {
                        case RTA_OIF:
                                if_indextoname(*(int *)RTA_DATA(rtAttr), rtInfo->ifName);
                                break;
                        case RTA_GATEWAY:
                                rtInfo->gateWay = *(u_int *)RTA_DATA(rtAttr);
                                break;
                        case RTA_PREFSRC:
                                rtInfo->srcAddr = *(u_int *)RTA_DATA(rtAttr);
                                break;
                        case RTA_DST:
                                rtInfo->dstAddr = *(u_int *)RTA_DATA(rtAttr);
                                break;
                }
        //      printf("inter_name : %s\n", rtInfo->ifName);
                if(memcmp(rtInfo->ifName, inter_name, strlen(inter_name)) == 0)
                {
                        is_find = 1;
                        break;
                }
        }
        if(is_find)
        {
                char *gateway = NULL; /* here malloc return must be free */
                gate.s_addr = rtInfo->gateWay;
                char *gateway_tmp = (char*)inet_ntoa(gate);
                int len = strlen(gateway_tmp);
                gateway = (char*)malloc(len + 1);
                memcpy(gateway, gateway_tmp, len);
                gateway[len] = 0;
                /*
                printf("oif:%s\n",rtInfo->ifName);
                gate.s_addr = rtInfo->gateWay;
                sprintf(gateway, (char *)inet_ntoa(gate));
                printf("gw%s\n",gateway);
                gate.s_addr = rtInfo->srcAddr;
                printf("src:%s\n",(char *)inet_ntoa(gate));
                gate.s_addr = rtInfo->dstAddr;
                printf("dst:%s\n",(char *)inet_ntoa(gate));
                */
                return gateway;
        }
        return NULL;
}
//if the return value is not null and need free
char *get_gateway(const char *net_interface_name, const char * ip_addr)
{
        struct nlmsghdr *nlMsg;
        struct rtmsg *rtMsg;
        struct route_info *rtInfo;
        char msgBuf[RECVMSGSIZE];
        int sock, len, msgSeq = 0;

        if((sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
        {
                perror("Socket Creation: ");
                return NULL;
        }
        memset(msgBuf, 0, RECVMSGSIZE);
        nlMsg = (struct nlmsghdr *)msgBuf;
        rtMsg = (struct rtmsg *)NLMSG_DATA(nlMsg);
        nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); // Length of message.
        nlMsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
        nlMsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
        nlMsg->nlmsg_seq = msgSeq++; // Sequence of the message packet.
        nlMsg->nlmsg_pid = getpid(); // PID of process sending the request.
        if(send(sock, nlMsg, nlMsg->nlmsg_len, 0) < 0)
        {
                //printf("Write To Socket Failed…\n");
                return NULL;
        }
        if((len = readNlSock(sock, msgBuf, msgSeq, getpid())) < 0)
        {
                //printf("Read From Socket Failed…\n");
                return NULL;
        }
        rtInfo = (struct route_info *)malloc(sizeof(struct route_info));
        char *gateway = NULL;
        for(;NLMSG_OK(nlMsg,len);nlMsg = NLMSG_NEXT(nlMsg,len))
        {
                memset(rtInfo, 0, sizeof(struct route_info));
                if( (gateway = parseRoutes(nlMsg, rtInfo,net_interface_name, ip_addr)) != NULL)
                        break;
        }
        free(rtInfo);
        close(sock);
        return gateway;
}

macos获取路由表信息

注:macos相关源码获取www.opensource.apple.com
以下代码通过修改自macos netstat源码中的route.c部分,在route.c中通过routepr( void )函数获取路由表,并通过np_rtentry( struct rt_msghdr2 *rtm )打印相关信息;
本段代码的业务是通过get_gateway函数输入网卡名称,返回网关地址:

#ifndef ROUNDUP
#define ROUNDUP(a) \
       ((a) > 0 ? (1 + (((a) - 1) | (sizeof(uint32_t) - 1))) : sizeof(uint32_t))
#endif 
typedef union 
{
	uint32_t dummy;		/* Helps align structure. */
	struct	sockaddr u_sa;
	u_short	u_data[128];
} sa_u;
void get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
{
        int i;
        for (i = 0; i < RTAX_MAX; i++) 
		{
            if (addrs & (1 << i)) 
			{
                rti_info[i] = sa;
				sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa);
			} 
			else 
			{
                 rti_info[i] = NULL;
			}
		}
}
char *np_rtentry(struct rt_msghdr2 *rtm, const char* inte_name)
{
	struct sockaddr *sa = (struct sockaddr *)(rtm + 1);
	struct sockaddr *rti_info[RTAX_MAX];
	u_short lastindex = 0xffff;
	static char ifname[IFNAMSIZ + 1];
	get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
	//RTA_DST destination addr
	//get name 
	if (rtm->rtm_index != lastindex) 
	{
		if_indextoname(rtm->rtm_index, ifname);
		//printf("host name : %s\n", ifname);
		lastindex = rtm->rtm_index;
	}
	if( memcmp(ifname, inte_name, strlen(inte_name)) == 0)
	{
		//get gateway
		struct sockaddr_in *sin = (struct sockaddr_in *)rti_info[RTAX_GATEWAY];
		char * gateway = (char* ) malloc(MAXHOSTNAMELEN);  // must be free without
		inet_ntop(AF_INET, &sin->sin_addr.s_addr, gateway, MAXHOSTNAMELEN - 1);
		//printf("gateway : %s\n", gateway);	
		return gateway;
	}
	return NULL;
}
//return value must be free 
char *get_gateway(const char* inter_name, const char* ip_addr)
{
	size_t needed;
	int mib[6];
	char *buf, *next, *lim;
	struct rt_msghdr2 *rtm;
	/printf("Routing tables\n");
	mib[0] = CTL_NET;
	mib[1] = PF_ROUTE;
	mib[2] = 0;
	mib[3] = 0;
	mib[4] = NET_RT_DUMP2;
	mib[5] = 0;
	if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) 
	{
		err(1, "sysctl: net.route.0.0.dump estimate");
	}
	if ((buf = (char*)malloc(needed)) == 0) 
	{
		err(2, "malloc(%lu)", (unsigned long)needed);
	}
	if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) 
	{
		err(1, "sysctl: net.route.0.0.dump");
	}
	char * getway = NULL;
	lim  = buf + needed;
	for (next = buf; next < lim; next += rtm->rtm_msglen) 
	{
		rtm = (struct rt_msghdr2 *)next;
		if((getway = np_rtentry(rtm, inter_name)) != NULL)
		{
			break;
		}
	}
	free(buf);
	return getway;
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值