绑定NETLINK_ROUTE
协议,加入内核提供的RTMGRP_IPV4_ROUTE
广播组,接收监听路由变化消息。
使用 libnl 编写主程序,对于libnl-route
是否提供对rtmsg进行解析的API?尚未研究。本文根据上一篇文章《Netlink分层模型及消息格式 》一步一步解析netlink message。
sock = nl_socket_alloc();
nl_join_groups(sock, RTMGRP_IPV4_ROUTE);
nl_connect(sock, NETLINK_ROUTE);
nl_join_groups()必须在nl_connect()之前调用,因为nl_connect()封装了socket()和bind(),而真正的加入广播组是在bind()本地地址时完成的。
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 inet.h> #include netlink.h> #include rtnetlink.h> #include genl/genl.h> #include genlctrl.h> #include #include 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