读取路由表获取通信网卡IP是什么意思呢?且听我一一道来…
下面是我虚拟机两个网卡的IP,很明显两个网卡是不同网段的,我的物理机网卡网段是192.168.1.0/24,与我物理机和外网通信的网卡是ens160,即192.168.31.0/24网段,ens192不能和物理机与外网通信。
我的程序运行是这样的:
运行了两次,第一次是读取路由表获取到192.168.1.4(我主机)的路由,然后根据路由确定用哪个网卡进行通信,网卡ens160网络设置的是NAT,所以和物理机不在一个网段但是可以通信。第二次是读取路由表获得到主机192.168.2.3的路由,然后根据路由确定用哪个网卡进行通信,可以看到这次网卡选择的是ens192(192.168.2.1)。虽然局域网中没有192.168.2.3这个主机,但不影响我说明本程序的功能:读取路由表获取到目的主机的路由,根据路由选择用哪个网卡进行通信。
程序流程如下:
其中创建Routing Socket代码:
route_sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)
第一个参数是协议族,AF_NETLINK即是netlink协议族。第二个参数一般固定为SOCK_RAW,第三个参数是netlink协议族,NETLINK_ROUTE表示获取或管理路由表信息。
sockaddr_nl结构体为netlink地址结构:
struct sockaddr_nl {__kernel_sa_family_t nl_family; /* AF_NETLINK */unsigned short nl_pad; /* zero */__u32 nl_pid; /* port ID */__u32 nl_groups; /* multicast groups mask */
};
其初始化:
bzero(&sa, sizeof(sa));// nl_pady一般为0// nl_groups == 0 表示该消息为单播sa.nl_family = AF_NETLINK;sa.nl_pid = getpid(); // nl_pid表示接收消息者的进程ID
netlink消息结构大概如下:
上面的图对于理解代码很重要,关于构造netlink消息、发送netlink消息等可以直接看代码,中有注释。
netlink_getip.h
#include <stdio.h>
#include <stdlib.h>
#include <bits/sockaddr.h>
#include <asm/types.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#include <unistd.h>
#include <string.h>#define BUFFER_LENGTH 8192
typedef struct rt_request
{struct nlmsghdr nl;struct rtmsg rt;char payload[BUFFER_LENGTH];
} rt_request;uint32_t fetch_interface_ip(uint32_t if_index)
{int family;struct ifreq ifreq;char host[256] ={ 0 }, if_name[256] ={ 0 };uint32_t src_addr;int fd;if_indextoname(if_index, if_name); // 根据索引值获取网络接口名,如eth0fd = socket(AF_INET, SOCK_DGRAM, 0);if (fd < 0){perror("socket()");exit(EXIT_FAILURE);}memset(&ifreq, 0, sizeof ifreq);strncpy(ifreq.ifr_name, if_name, IFNAMSIZ);if (ioctl(fd, SIOCGIFADDR, &ifreq) != 0) // 获取接口ip{/* perror(name); */return -1; /* ignore */}switch (family = ifreq.ifr_addr.sa_family){case AF_UNSPEC:// return;return -1; /* ignore */case AF_INET:case AF_INET6:getnameinfo(&ifreq.ifr_addr, sizeof ifreq.ifr_addr, host, sizeof host,0, 0, NI_NUMERICHOST);break;default:sprintf(host, "unknown (family: %d)", family);}inet_pton(AF_INET, host, &src_addr);close(fd);return src_addr;
}void formRequest(rt_request* req)
{bzero(req, sizeof(req));
/*
struct nlmsghdr 为 netlink socket 自己的消息头,
这用于多路复用和多路分解 netlink 定义的所有协议类型以及其它一些控制,
netlink 的内核实现将利用这个消息头来多路复用和多路分解已经其它的一些控制,
因此它也被称为netlink 控制块。因此,应用在发送 netlink 消息时必须提供该消息头。
*/req->nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));req->nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; // NLM_F_REQUEST表示消息是一个请求req->nl.nlmsg_type = RTM_GETROUTE; // nlmsg_type消息内容// 填充rtmsg结构体,即路由表管理结构体,对于上面的RTM_GETROUTE操作来说,只需要定义下面两个内容req->rt.rtm_family = AF_INET;req->rt.rtm_table = RT_TABLE_MAIN;}void sendRequest(int sock_fd, struct sockaddr_nl *pa, rt_request* req)
{struct msghdr msg; // sendmsg和recvmsg的参数,描述发送消息和接收消息的结构体struct iovec iov; // iovec结构体用于描述一个数据缓冲区int rtn;bzero(pa, sizeof(pa));pa->nl_family = AF_NETLINK;bzero(&msg, sizeof(msg));msg.msg_name = pa;msg.msg_namelen = sizeof(*pa);iov.iov_base = (void *) req;iov.iov_len = req->nl.nlmsg_len;msg.msg_iov = &iov;msg.msg_iovlen = 1;while (1){if ((rtn = sendmsg(sock_fd, &msg, 0)) < 0){if (errno == EINTR)continue;else{printf("Error: Unable to send NetLink message:%s\n",strerror(errno));exit(1);}}break;}}int receiveReply(int sock_fd, char* response_buffer)
{char* p;int nll, rtl, rtn;struct nlmsghdr *nlp;struct rtmsg *rtp;bzero(response_buffer, BUFFER_LENGTH);p = response_buffer;nll = 0;while (1){if ((rtn = recv(sock_fd, p, BUFFER_LENGTH - nll, 0)) < 0){if (errno == EINTR)continue;else{printf("Failed to read from NetLink Socket: %s\n",strerror(errno));exit(1);}}nlp = (struct nlmsghdr*) p;if (nlp->nlmsg_type == NLMSG_DONE)break;p += rtn;nll += rtn;}return nll;
}uint32_t readReply(char *response, int nll, in_addr_t dst_address)
{struct nlmsghdr *nlp = NULL;struct rtmsg *rtp = NULL;struct rtattr *rtap = NULL;int rtl = 0, found_route = 0, default_route = 0;uint32_t route_addr, net_mask;uint32_t if_index = -1;nlp = (struct nlmsghdr*) response;for (; NLMSG_OK(nlp, nll); nlp = NLMSG_NEXT(nlp, nll)) // NLMSG_OK:检查nlh地址是否是一条完整的消息{ // NLMSG_NEXT:当前消息地址,返回下一个消息地址rtp = (struct rtmsg *) NLMSG_DATA(nlp); // NLMSG_DATA:从nlh首地址向后移动到data起始位置if (rtp->rtm_table != RT_TABLE_MAIN)continue;// RTM_RTA:输入route message指针,返回route第一个属性首地址rtap = (struct rtattr *) RTM_RTA(rtp); // rtattr结构体封装可选路由信息的通用结构,用于表示 Netlink 消息的属性rtl = RTM_PAYLOAD(nlp); // RTM_PAYLOAD:即rtmsg层封装的数据长度,相当于TCP数据包去掉IP报头和TCP报头长度得到TCP数据部分长度found_route = 0;default_route = 1;for (; RTA_OK(rtap, rtl); rtap = RTA_NEXT(rtap, rtl)) // RTA_OK:判断一个属性rta是否正确{ // RTA_NEXT:先对attrlen减去rta属性内容的全部长度,然后返回下一个rtattr的首地址switch (rtap->rta_type){// destination IPv4 addresscase RTA_DST:default_route = 0;route_addr = *((uint32_t*) RTA_DATA (rtap));net_mask = 0xFFFFFFFF;net_mask <<= (32 - rtp->rtm_dst_len);net_mask = ntohl(net_mask);if (route_addr == (dst_address & net_mask))found_route = 1;else if (route_addr == 0)default_route = 1;break;// unique ID associated with the network// interfacecase RTA_OIF: // Output interface indexif (found_route || default_route)if_index = *((uint32_t*) RTA_DATA (rtap));break;default:break;}}if (found_route)break;}return if_index;}
// Netlink分层模型及消息格式:https://onestraw.github.io/linux/netlink-message/
uint32_t getLocalIPAddress(in_addr_t dst_address)
{int route_sock_fd = -1, res_len = 0;struct sockaddr_nl sa, pa; // sa为消息接收者的 netlink 地址uint32_t if_index;rt_request req = {0};char response_payload[BUFFER_LENGTH] = {0};// Open Routing Socketif ((route_sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1){printf("Error: Failed to open routing socket: %s\n", strerror(errno));exit(1);}bzero(&sa, sizeof(sa));// nl_groups == 0 表示该消息为单播sa.nl_family = AF_NETLINK;sa.nl_pid = getpid(); // nl_pid表示接收消息者的进程IDbind(route_sock_fd, (struct sockaddr*) &sa, sizeof(sa));formRequest(&req); // 构造netlink消息sendRequest(route_sock_fd, &pa, &req); // 发送消息res_len = receiveReply(route_sock_fd, response_payload); // 接收消息if_index = readReply(response_payload, res_len, dst_address); // 从接收的消息中获取if(network interface)close(route_sock_fd);return fetch_interface_ip(if_index); // 从if_index获取接口ip
}
get_ip.c
#include "netlink_getip.h"int main(int argc, char** argv)
{char dst[256] = {0}, host[256] = {0};if (argc != 2){printf("Usage: ./rawhttpget ip\n");exit(1);}strncpy(dst, argv[1], 256);// netlink通信uint32_t src_address = getLocalIPAddress(inet_addr(dst));printf("Src Address: %s\n", inet_ntop(AF_INET, &src_address, host, 256));return 0;
}
Makefile
CFLAGS= -g -Werror
CC=gccall:$(CC) get_ip.c $(CFLAGS) -o netlink_getipclean:rm -rf netlink_getip
文章参考:
https://github.com/praveenkmurthy/Raw-Sockets
https://onestraw.github.io/linux/netlink-message/
欢迎交流。