netlink通信——读取路由表获取通信网卡IP

读取路由表获取通信网卡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代码:

c 复制代码
route_sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)

第一个参数是协议族,AF_NETLINK即是netlink协议族。第二个参数一般固定为SOCK_RAW,第三个参数是netlink协议族,NETLINK_ROUTE表示获取或管理路由表信息。

sockaddr_nl结构体为netlink地址结构:

c 复制代码
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 */
};

其初始化:

c 复制代码
	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

c 复制代码
#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);  // 根据索引值获取网络接口名,如eth0
	fd = 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 address
			case 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
				// interface
			case RTA_OIF:  // Output interface index
				if (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 Socket
	if ((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表示接收消息者的进程ID

	bind(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

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

c 复制代码
CFLAGS= -g -Werror
CC=gcc

all:
	$(CC) get_ip.c $(CFLAGS) -o netlink_getip

clean:
	rm -rf netlink_getip

文章参考:

https://github.com/praveenkmurthy/Raw-Sockets

https://onestraw.github.io/linux/netlink-message/

欢迎交流。

相关推荐
炫酷的伊莉娜31 分钟前
【计算机网络】数据链路层(作业)
网络·计算机网络·数据链路层
xy1899031 分钟前
http 状态码主要有哪些?【面试】
网络·网络协议·http
来世做春风嘛43 分钟前
第二十一章 网络编程
服务器·网络·php
做跨境的红姐1 小时前
为什么网络爬虫广泛使用HTTP代理?
tcp/ip·安全·ip
Tony11541 小时前
华为路由器静态路由配置(eNSP模拟实验)
网络·华为·智能路由器
斐夷所非2 小时前
RIP 路由 3 个定时器的工作流程和 4 种防环方法
网络
速盾cdn3 小时前
速盾:cdn发展历程
服务器·网络·安全
阿龍17873 小时前
Qt中udp指令,大小端,帧头帧尾实际示例
网络·c++·qt·网络协议·udp
爱喝矿泉水的猛男4 小时前
无线传感器网络(物联网通信技术)期末考试2024年真题
网络·arm开发·物联网·wsn·物联网通信技术
梦中北山4 小时前
JWT安全分析
开发语言·网络·安全·web安全·php