Netlink 监听路由变化消息

3 分钟读完

绑定NETLINK_ROUTE协议,加入内核提供的RTMGRP_IPV4_ROUTE 广播组,接收监听路由变化消息。

使用 libnl 编写主程序,对于libnl-route是否提供对rtmsg进行解析的API?尚未研究。本文根据上一篇文章《Netlink分层模型及消息格式 》一步一步解析netlink message。

  • 创建 nl_sock

    sock = nl_socket_alloc();

  • 加入广播组 RTMGRP_IPV4_ROUTE

    nl_join_groups(sock, RTMGRP_IPV4_ROUTE);

  • 创建并绑定真实的socket

    nl_connect(sock, NETLINK_ROUTE);

nl_join_groups()必须在nl_connect()之前调用,因为nl_connect()封装了socket()和bind(),而真正的加入广播组是在bind()本地地址时完成的。

  • 设置回调函数callback (nlmsg处理函数)

    nl_socket_modify_cb(sock, NL_CB_MSG_IN, NL_CB_CUSTOM, parse_nlmsg, NULL);

该函数会将 callback函数parse_nlmsg注册到nl_sock->s_cb->cb_set[]上 .
NL_CB_MSG_IN 表示每收到一个数据包,都会调用callback函数
NL_CB_CUSTOM 表示收到数据包后,调用用户指定的回函调用(parse_nlmsg()即这里定义的回调函数),如果使用NL_CB_DEBUG,回调函数会设置成 nl_msg_dump(nlmsg, stdout); (实际上是nl_msg_in_handler_debug() ) 输出数据包基本信息及原始数据。

函数原型如下

/**
 * Modify the callback handler associated with the socket
 * @arg sk              Netlink socket.
 * @arg type            which type callback to set
 * @arg kind            kind of callback
 * @arg func            callback function
 * @arg arg             argument to be passed to callback function
 *
 * @see nl_cb_set
 */
int nl_socket_modify_cb(struct nl_sock *sk, enum nl_cb_type type,
						enum nl_cb_kind kind, nl_recvmsg_msg_cb_t func,
						void *arg)
{
		return nl_cb_set(sk->s_cb, type, kind, func, arg);
}

回调函数类型及含义如下

/**
 * Callback types
 * @ingroup cb
 */
enum nl_cb_type {
		/** Message is valid */
		NL_CB_VALID,
		/** Last message in a series of multi part messages received */
		NL_CB_FINISH,
		/** Report received that data was lost */
		NL_CB_OVERRUN,
		/** Message wants to be skipped */
		NL_CB_SKIPPED,
		/** Message is an acknowledge */
		NL_CB_ACK,
		/** Called for every message received */
		NL_CB_MSG_IN,
		/** Called for every message sent out except for nl_sendto() */
		NL_CB_MSG_OUT,
		/** Message is malformed and invalid */
		NL_CB_INVALID,
		/** Called instead of internal sequence number checking */
		NL_CB_SEQ_CHECK,
		/** Sending of an acknowledge message has been requested */
		NL_CB_SEND_ACK,
		/** Flag NLM_F_DUMP_INTR is set in message */
		NL_CB_DUMP_INTR,
		__NL_CB_TYPE_MAX,
};

回调函数属性

/**
 * Callback kinds
 * @ingroup cb
 */
enum nl_cb_kind {
		/** Default handlers (quiet) */
		NL_CB_DEFAULT,
		/** Verbose default handlers (error messages printed) */
		NL_CB_VERBOSE,
		/** Debug handlers for debugging */
		NL_CB_DEBUG,
		/** Customized handler specified by the user */
		NL_CB_CUSTOM,
		__NL_CB_KIND_MAX,
}; 

nl_socket_modify_cb 最终在调用过程中的nl_cb_set函数中,有如下设置

if (kind == NL_CB_CUSTOM) {
        cb->cb_set[type] = func;
        cb->cb_args[type] = arg;
} else {
        cb->cb_set[type] = cb_def[type][kind];
        cb->cb_args[type] = arg;
} 		
  • 接收消息
nl_recvmsgs_default(sock);
阻塞,每到来一个消息数据包,就调用nl_sock->s_cb中注册好的callback函数。
  • 解析nl_msg,分如下三个层次

    int parse_nlmsg(struct nl_msg *, void *); void parse_rtmsg(struct nlmsghdr *nlhdr); void parse_rtm_rtattr(struct rtattr *rta, int len);

  • 代码实例

/*
    gcc rt_listen.c `pkg-config --cflags --libs libnl-3.0` 
*/
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <stdio.h>
#include <string.h>

int parse_nlmsg(struct nl_msg *, void *);
void parse_rtmsg(struct nlmsghdr *nlhdr);
void parse_rtm_type(struct rtmsg *rtm);
void parse_rtm_scope(struct rtmsg *rtm);
void parse_rtm_table(struct rtmsg *rtm);
void parse_rtm_rtattr(struct rtattr *rta, int len);

int main()
{
	struct nl_sock *sock;
	sock = nl_socket_alloc();

	nl_join_groups(sock, RTMGRP_IPV4_ROUTE);

	nl_connect(sock, NETLINK_ROUTE);

	nl_socket_modify_cb(sock, NL_CB_MSG_IN, 
                            NL_CB_CUSTOM, 
                            //NL_CB_DEBUG, 
                            //NL_CB_DEFAULT, 
                            //NL_CB_VERBOSE, 
                            parse_nlmsg, NULL);

	while (1)
		nl_recvmsgs_default(sock);
	return 0;
}

int parse_nlmsg(struct nl_msg *nlmsg, void *arg)
{
	printf("[+%s]\n", __FUNCTION__);
//	nl_msg_dump(nlmsg, stdout);

	struct nlmsghdr *nlhdr;
	struct rtmsg *rtm;
	int len;
	nlhdr = nlmsg_hdr(nlmsg);
	len = nlhdr->nlmsg_len;	// + NLMSG_HDRLEN;

	for (nlhdr; NLMSG_OK(nlhdr, len); nlhdr = NLMSG_NEXT(nlhdr, len)) {
		switch (nlhdr->nlmsg_type) {
		case RTM_NEWROUTE:
			printf("RTM_NEWROUTE\n");
			parse_rtmsg(nlhdr);
			break;
		case RTM_DELROUTE:
			printf("RTM_DELROUTE\n");
			parse_rtmsg(nlhdr);
			break;
		default:
			printf("nlmsg_type:%d\n\n", nlhdr->nlmsg_type);
			break;
		}
	}
	return 0;
}

void parse_rtmsg(struct nlmsghdr *nlhdr)
{
	struct rtmsg *rtm = (struct rtmsg *)NLMSG_DATA(nlhdr);
	parse_rtm_type(rtm);
	parse_rtm_scope(rtm);
	parse_rtm_table(rtm);
	parse_rtm_rtattr(RTM_RTA(rtm), RTM_PAYLOAD(nlhdr));
}


#define PTYPE(type)    \
	printf("\ttype : %s\n", type)

void parse_rtm_type(struct rtmsg *rtm)
{
	switch (rtm->rtm_type) {
	case RTN_UNICAST:
		PTYPE("unicast");
                break;
	case RTN_UNSPEC:
		PTYPE("specified");
		break;
	case RTN_BROADCAST:
		PTYPE("broadcast");
		break;
	case RTN_LOCAL:
		PTYPE("local");
		break;
	case RTN_NAT:
		PTYPE("NAT");
		break;
	}
}

#define PSCOPE(scope)  \
        printf("\tscope : %s\n", scope)

void parse_rtm_scope(struct rtmsg *rtm)
{
	switch (rtm->rtm_scope) {
	case RT_SCOPE_UNIVERSE:
		PSCOPE("global");
		break;
	case RT_SCOPE_SITE:
		PSCOPE("AS local");
		break;
	case RT_SCOPE_LINK:
		PSCOPE("link local");
		break;
	case RT_SCOPE_HOST:
		PSCOPE("host local");
		break;
	case RT_SCOPE_NOWHERE:
		PSCOPE("none (no destination)");
		break;
	}
}

#define PTABLE(table)  \
        printf("\trouting table : %s\n", table)

void parse_rtm_table(struct rtmsg *rtm)
{
	switch (rtm->rtm_table) {
	case RT_TABLE_UNSPEC:
                PTABLE("unspecified");
		break;
	case RT_TABLE_DEFAULT:
                PTABLE("default");
		break;
	case RT_TABLE_MAIN:
                PTABLE("main");
		break;
	case RT_TABLE_LOCAL:
                PTABLE("local");
		break;
	}
}

#define PADDR(str, addr)   \
        printf("\t %s : %s", str, \
        inet_ntoa((*(struct in_addr *)RTA_DATA(rta))));

#define PIF(str, rta)    \
        printf("\t %s : %u", str, \
        (*(uint32_t *)RTA_DATA(rta)));

void parse_rtm_rtattr(struct rtattr *rta, int len)
{
	for (rta; len > 0 && RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
		switch (rta->rta_type) {
		case RTA_GATEWAY:
			PADDR("gateway", rta);
			break;
		case RTA_DST:
			PADDR("destination", rta);
			break;
		case RTA_SRC:
			PADDR("source", rta);
			break;
		case RTA_IIF:
			PIF("input interface", rta);
			break;
		case RTA_OIF:
			PIF("output interface", rta);
			break;
		}
	}
	printf("\n");
}
  • 测试

编译上述代码,执行。用类似下面的命令增加删除路由(必须保证执行成功,即路由表变化,才能收到netlink广播消息)

route add -net 30.30.30.0 netmask 255.255.255.0 gw 20.20.20.21
route del -net 30.30.30.0 netmask 255.255.255.0 gw 20.20.20.21
  • 参考

https://github.com/rcatolino/nlroute-state-watch

留下评论