IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Netlink 监听路由变化消息

    一根稻草发表于 2015-10-01 00:00:00
    love 0

    绑定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 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



沪ICP备19023445号-2号
友情链接