Linux的网关信息保存在路由表中,获取网关实际上就是路由表的查询。
用户空间获取网关地址
有两种方法,一个是从/proc/net/route中读取,这是最简单,最直接的,route命令就是这么做的,可以参考net-tools包中route的源码实现。
另一种是用Netlink来实现。利用NETLINK_ROUTE(rtnetlink.c: Routing netlink socket interface)的RTM_GETROUTE指令查找路由,这是从网上找的代码,在Debian (2.6.26内核)下测试通过。
C++代码
- #include <arpa/inet.h> //for in_addr
- #include <linux/rtnetlink.h> //for rtnetlink
- #include <net/if.h> //for IF_NAMESIZ, route_info
- #include <stdlib.h> //for malloc(), free()
- #include <string.h> //for strstr(), memset()
- #include <string>
- #define BUFSIZE 8192
- 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, BUFSIZE - msgLen, 0)) < 0)
- {
- perror("SOCK READ: ");
- return -1;
- }
- nlHdr = (struct nlmsghdr *)bufPtr;
- //检查header是否有效
- 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;
- }
- //分析返回的路由信息
- void parseRoutes(struct nlmsghdr *nlHdr, struct route_info *rtInfo,char *gateway)
- {
- struct rtmsg *rtMsg;
- struct rtattr *rtAttr;
- int rtLen;
- char *tempBuf = NULL;
- struct in_addr dst;
- struct in_addr gate;
- tempBuf = (char *)malloc(100);
- rtMsg = (struct rtmsg *)NLMSG_DATA(nlHdr);
- // If the route is not for AF_INET or does not belong to main routing table
- //then return.
- if((rtMsg->rtm_family != AF_INET) || (rtMsg->rtm_table != RT_TABLE_MAIN))
- return;
- 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;
- }
- }
- dst.s_addr = rtInfo->dstAddr;
- if (strstr((char *)inet_ntoa(dst), "0.0.0.0"))
- {
- printf("oif:%s",rtInfo->ifName);
- gate.s_addr = rtInfo->gateWay;
- sprintf(gateway, (char *)inet_ntoa(gate));
- printf("%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));
- }
- free(tempBuf);
- return;
- }
- int get_gateway(char *gateway)
- {
- struct nlmsghdr *nlMsg;
- struct rtmsg *rtMsg;
- struct route_info *rtInfo;
- char msgBuf[BUFSIZE];
- int sock, len, msgSeq = 0;
- if((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0)
- {
- perror("Socket Creation: ");
- return -1;
- }
- memset(msgBuf, 0, BUFSIZE);
- 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 -1;
- }
- if((len = readNlSock(sock, msgBuf, msgSeq, getpid())) < 0) {
- printf("Read From Socket Failed…\n");
- return -1;
- }
- rtInfo = (struct route_info *)malloc(sizeof(struct route_info));
- for(;NLMSG_OK(nlMsg,len);nlMsg = NLMSG_NEXT(nlMsg,len)){
- memset(rtInfo, 0, sizeof(struct route_info));
- parseRoutes(nlMsg, rtInfo,gateway);
- }
- free(rtInfo);
- close(sock);
- return 0;
- }
- int main()
- {
- char buff[256];
- get_gateway(buff);
- return 0;
- }
内核空间获取网关地址
用户空间的实现,其本质上是内核空间的支持,因此内核空间获取应该更直接点。我参考了NETLINK_ROUTE中的实现来做,即执行一个从本机IP到外网IP的路由查询,获得的路由记录中自然包括网关地址,主要用到ip_route_output_key()函数。下面是我的代码:
C++代码
- …
- extern struct net init_net;
- …
- inline void printIP(__u32 uip)
- {
- printk(NIPQUAD_FMT,NIPQUAD(uip));
- }
- …
- int xxxxxx()
- {
- …
- int err;
- struct rtable * rt = NULL;
- struct flowi fl = {
- .nl_u = {
- .ip4_u = {
- .daddr = 0,
- .saddr = 0,
- .tos = 0,
- },
- },
- .oif = 0,
- };
- fl.nl_u.ip4_u.daddr = in_aton("182.168.1.1");
- fl.nl_u.ip4_u.saddr = in_aton("192.168.0.186");
- err = ip_route_output_key(&init_net, &rt, &fl);
- if(rt)
- {
- if(rt->idev&&rt->idev->dev&&rt->idev->dev->name)
- printk(" if:%s\n",rt->idev->dev->name);
- printk(" gw:");
- printIP(rt->rt_gateway);
- printk("\n dst:");
- printIP(rt->rt_dst);
- printk("\n src:");
- printIP(rt->rt_src);
- printk("\n");
- }
- else
- printk("rt = NULL!\n");
- …
- }
暂时只找到这种实现方式,有新的发现再来更新:)