获取路由表信息
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;
}