相关文章推荐
酷酷的紫菜  ·  中华人民共和国司法部·  1 年前    · 
爱运动的小狗  ·  一周最龙华| ...·  2 年前    · 
威武的茶叶  ·  张锦课题组@北京大学·  2 年前    · 

一、netlink通信机制

netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。

目前,用户应用程序和内核通信的常用方式如下:

  • 系统调用:常见的有 write、read、ioctl 等等,它需要应用程序主动向内核写入或读取数据,是一种同步的单工数据传输方式
  • /proc文件系统:同 系统调用 类似
  • 异步IO:可以通过编写驱动代码,使得内核在某些时刻主动向应用程序发送信号,但无法传输大量数据。
  • netlink:是一种同步或者异步的数据传输方式,使用 netlink 在应用程序和内核建立起连接后即可进行双向数据发送。
  • 1、netlink的优点

  • 支持全双工、异步通信
  • 用户空间使用标准的socket接口即可进行通信
  • 在内核端可用于进程上下文与中断上下文
  • 2、netlink的应用

    目前 linux内核 已经使用 netlink 实现了多种功能应用,关于功能使用后面会有部分讲解。下面罗列出几个常用的功能,如下:

  • 获取或修改路由信息
  • 监听TCP协议数据报文
  • netfilter子系统
  • 内核事件向用户态通知
  • netlink 有一定的格式要求,是通过发送 消息 来完成数据传输的。那么它就需要 消息格式 协议流程 。说得简单一些,就是 netlink 是一种通信方式,是内核提供的一种功能。另一方面则是因为 netlink 用就是 socket套接字 那一套编程接口,所以十分像是网络协议。

    二、netlink接口与数据接口

    用户态应用使用标准的 socket API有sendto(),recvfrom(); sendmsg(), recvmsg()

    netlink 可以直接使用 socket套接字 的标准接口直接进行代码编写,也就是说 socket 、 bind 等接口都可以直接使用。
    netlink 的编程与 UDP 编程类似,但在网络编程中我们是使用 IP地址 + 端口号 进行寻址的,而 netlink 则是通过 协议类型+进程ID 进行寻址的,其中 协议类型 会在调用 socket接口 的时候指定。
    netlink 基本的编程步骤如下:

  • 使用 socket 声明套接字
  • 使用 bind 绑定 本地地址 到套接字
  • 构造 消息
  • 发送或接收 消息,而在 netlink 中有两套发送及接收数据的接口,分别是 sendto和recvfrom、sendmsg和recvmsg
  • 1、netlink用户态常用的数据结构

    struct sockaddr_nl 是netlink通信地址,跟普通socket struct sockaddr_in类似。

    (1)、struct sockaddr_nl结构

    struct sockaddr_nl {
         __kernel_sa_family_t    nl_family;  //一般为AF_NETLINK
         unsigned short          nl_pad;     //无需填充
         __u32                   nl_pid;     //与内核通信的进程的进程ID,0 则代表地址为内核
         __u32                   nl_groups;  //多播组号,netlink支持多播
    

    (2)、struct nlmsghd 结构

    /* struct nlmsghd 是netlink消息头*/
    struct nlmsghdr {
        __u32       nlmsg_len;  /* Length of message including header */
        __u16       nlmsg_type; /* Message content */
        __u16       nlmsg_flags;    /* Additional flags */
        __u32       nlmsg_seq;  /* Sequence number */
        __u32       nlmsg_pid;  /* Sending process port ID */
    

    1)nlmsg_len:整个netlink消息的长度(包含消息头);
    2)nlmsg_type:消息状态,内核在include/uapi/linux/netlink.h中定义了以下4种通用的消息类型,它们分别是:

    #define NLMSG_NOOP      0x1 /* Nothing.     */
    #define NLMSG_ERROR     0x2 /* Error        */
    #define NLMSG_DONE      0x3 /* End of a dump    */
    #define NLMSG_OVERRUN       0x4 /* Data lost        */
    #define NLMSG_MIN_TYPE      0x10    /* < 0x10: reserved control messages */
    /*NLMSG_NOOP:不执行任何动作,必须将该消息丢弃;
    NLMSG_ERROR:消息发生错误;
    NLMSG_DONE:标识分组消息的末尾;
    NLMSG_OVERRUN:缓冲区溢出,表示某些消息已经丢失。
    NLMSG_MIN_TYPEK:预留 */

    3)nlmsg_flags:消息标记,它们用以表示消息的类型,如下:

    /* Flags values */
    #define NLM_F_REQUEST        0x01    /* It is request message.     */
    #define NLM_F_MULTI        0x02    /* Multipart message, terminated by NLMSG_DONE */
    #define NLM_F_ACK        0x04    /* Reply with ack, with zero or error code */
    #define NLM_F_ECHO        0x08    /* Echo this request         */
    #define NLM_F_DUMP_INTR        0x10    /* Dump was inconsistent due to sequence change */
    #define NLM_F_DUMP_FILTERED    0x20    /* Dump was filtered as requested */
    /* Modifiers to GET request */
    #define NLM_F_ROOT    0x100    /* specify tree    root    */
    #define NLM_F_MATCH    0x200    /* return all matching    */
    #define NLM_F_ATOMIC    0x400    /* atomic GET        */
    #define NLM_F_DUMP    (NLM_F_ROOT|NLM_F_MATCH)
    /* Modifiers to NEW request */
    #define NLM_F_REPLACE    0x100    /* Override existing        */
    #define NLM_F_EXCL    0x200    /* Do not touch, if it exists    */
    #define NLM_F_CREATE    0x400    /* Create, if it does not exist    */
    #define NLM_F_APPEND    0x800    /* Add to end of list        */
    /* Modifiers to DELETE request */
    #define NLM_F_NONREC    0x100    /* Do not delete recursively    */
    /* Flags for ACK message */
    #define NLM_F_CAPPED    0x100    /* request was capped */
    #define NLM_F_ACK_TLVS    0x200    /* extended ACK TVLs were included */

    4)nlmsg_seq:消息序列号,用以将消息排队,有些类似TCP协议中的序号(不完全一样),但是netlink的这个字段是可选的,不强制使用;
    5)nlmsg_pid:发送端口的ID号,对于内核来说该值就是0,对于用户进程来说就是其socket所绑定的ID号。

    (3)、struct msghdr 结构体

    struct iovec {                    /* Scatter/gather array items */
         void  *iov_base;              /* Starting address */
         size_t iov_len;               /* Number of bytes to transfer */
      /* iov_base: iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,
        以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度 (即有多少个buff)
     struct msghdr {
         void         *msg_name;       /* optional address */
         socklen_t     msg_namelen;    /* size of address */
         struct iovec *msg_iov;        /* scatter/gather array */
         size_t        msg_iovlen;     /* # elements in msg_iov */
         void         *msg_control;    /* ancillary data, see below */
         size_t        msg_controllen; /* ancillary data buffer len */
         int           msg_flags;      /* flags on received message */
     /* msg_name: 数据的目的地址,网络包指向sockaddr_in, netlink则指向sockaddr_nl;
        msg_namelen: msg_name 所代表的地址长度
        msg_iov: 指向的是缓冲区数组
        msg_iovlen: 缓冲区数组长度
        msg_control: 辅助数据,控制信息(发送任何的控制信息)
        msg_controllen: 辅助信息长度
        msg_flags: 消息标识
    

    三、RTnetlink接口与数据接口

    netlink 是 Linux 内核中用于内核与用户空间之间进行通信的一种机制。它允许用户空间程序与内核模块进行通信,用于配置网络参数、查询状态信息、接收通知等。Netlink 使用一种类似于套接字的 API 进行通信,提供了一种可靠的、高效的机制来传输大量的异步数据,比如路由表、网络接口信息、套接字选项等。Netlink 被广泛应用于 Linux 系统中的网络配置、路由管理、网络监控等方面。

    RTNetlink 则是 Netlink 的一个子协议,用于处理与路由相关的信息。RTNetlink 可以通过 Netlink 机制来获取、配置和管理路由表、路由策略、邻居信息等路由相关的数据。它是 Linux 系统中管理网络路由的标准接口,通过 RTNetlink,用户空间程序可以实现动态地添加、删除、修改路由表中的路由条目,以及监听路由表的变化。

    总的来说,Netlink 是 Linux 内核与用户空间进行通信的机制,而 RTNetlink 是 Netlink 的一个子协议,用于处理与路由相关的信息。通过 RTNetlink,用户空间程序可以与内核进行高效地路由管理。

    1、RTNetlink用户态常用数据结构

    (1)、struct sockaddr_nl结构

    ​ 与Netlink相同。

    (2)、struct nlmsghd 结构

    ​ 其与Netlink主要的区别在于nlmsg_type有很多其它可选消息类型,在rtnetlink.h中包含了很多消息类型:

    /* Types of messages */
    enum {
        RTM_BASE    = 16,
    #define RTM_BASE    RTM_BASE
        RTM_NEWLINK    = 16,
    #define RTM_NEWLINK    RTM_NEWLINK
        RTM_DELLINK,
    #define RTM_DELLINK    RTM_DELLINK
        RTM_GETLINK,
    #define RTM_GETLINK    RTM_GETLINK
        RTM_SETLINK,
    #define RTM_SETLINK    RTM_SETLINK
        RTM_NEWADDR    = 20,
    #define RTM_NEWADDR    RTM_NEWADDR
        RTM_DELADDR,
    #define RTM_DELADDR    RTM_DELADDR
        RTM_GETADDR,
    #define RTM_GETADDR    RTM_GETADDR
        RTM_NEWROUTE    = 24,
    #define RTM_NEWROUTE    RTM_NEWROUTE
        RTM_DELROUTE,
    #define RTM_DELROUTE    RTM_DELROUTE
        RTM_GETROUTE,
    #define RTM_GETROUTE    RTM_GETROUTE
        RTM_NEWNEIGH    = 28,
    #define RTM_NEWNEIGH    RTM_NEWNEIGH
        RTM_DELNEIGH,
    #define RTM_DELNEIGH    RTM_DELNEIGH
        RTM_GETNEIGH,
    #define RTM_GETNEIGH    RTM_GETNEIGH
        RTM_NEWRULE    = 32,
    #define RTM_NEWRULE    RTM_NEWRULE
        RTM_DELRULE,
    #define RTM_DELRULE    RTM_DELRULE
        RTM_GETRULE,
    #define RTM_GETRULE    RTM_GETRULE
        RTM_NEWQDISC    = 36,
    #define RTM_NEWQDISC    RTM_NEWQDISC
        RTM_DELQDISC,
    #define RTM_DELQDISC    RTM_DELQDISC
        RTM_GETQDISC,
    #define RTM_GETQDISC    RTM_GETQDISC
        RTM_NEWTCLASS    = 40,
    #define RTM_NEWTCLASS    RTM_NEWTCLASS
        RTM_DELTCLASS,
    #define RTM_DELTCLASS    RTM_DELTCLASS
        RTM_GETTCLASS,
    #define RTM_GETTCLASS    RTM_GETTCLASS
        RTM_NEWTFILTER    = 44,
    #define RTM_NEWTFILTER    RTM_NEWTFILTER
        RTM_DELTFILTER,
    #define RTM_DELTFILTER    RTM_DELTFILTER
        RTM_GETTFILTER,
    #define RTM_GETTFILTER    RTM_GETTFILTER
        RTM_NEWACTION    = 48,
    #define RTM_NEWACTION   RTM_NEWACTION
        RTM_DELACTION,
    #define RTM_DELACTION   RTM_DELACTION
        RTM_GETACTION,
    #define RTM_GETACTION   RTM_GETACTION
        RTM_NEWPREFIX    = 52,
    #define RTM_NEWPREFIX    RTM_NEWPREFIX
        RTM_GETMULTICAST = 58,
    #define RTM_GETMULTICAST RTM_GETMULTICAST
        RTM_GETANYCAST    = 62,
    #define RTM_GETANYCAST    RTM_GETANYCAST
        RTM_NEWNEIGHTBL    = 64,
    #define RTM_NEWNEIGHTBL    RTM_NEWNEIGHTBL
        RTM_GETNEIGHTBL    = 66,
    
    
    
    
    
        
    #define RTM_GETNEIGHTBL    RTM_GETNEIGHTBL
        RTM_SETNEIGHTBL,
    #define RTM_SETNEIGHTBL    RTM_SETNEIGHTBL
        RTM_NEWNDUSEROPT = 68,
    #define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT
        RTM_NEWADDRLABEL = 72,
    #define RTM_NEWADDRLABEL RTM_NEWADDRLABEL
        RTM_DELADDRLABEL,
    #define RTM_DELADDRLABEL RTM_DELADDRLABEL
        RTM_GETADDRLABEL,
    #define RTM_GETADDRLABEL RTM_GETADDRLABEL
        RTM_GETDCB = 78,
    #define RTM_GETDCB RTM_GETDCB
        RTM_SETDCB,
    #define RTM_SETDCB RTM_SETDCB
        RTM_NEWNETCONF = 80,
    #define RTM_NEWNETCONF RTM_NEWNETCONF
        RTM_DELNETCONF,
    #define RTM_DELNETCONF RTM_DELNETCONF
        RTM_GETNETCONF = 82,
    #define RTM_GETNETCONF RTM_GETNETCONF
        RTM_NEWMDB = 84,
    #define RTM_NEWMDB RTM_NEWMDB
        RTM_DELMDB = 85,
    #define RTM_DELMDB RTM_DELMDB
        RTM_GETMDB = 86,
    #define RTM_GETMDB RTM_GETMDB
        RTM_NEWNSID = 88,
    #define RTM_NEWNSID RTM_NEWNSID
        RTM_DELNSID = 89,
    #define RTM_DELNSID RTM_DELNSID
        RTM_GETNSID = 90,
    #define RTM_GETNSID RTM_GETNSID
        RTM_NEWSTATS = 92,
    #define RTM_NEWSTATS RTM_NEWSTATS
        RTM_GETSTATS = 94,
    #define RTM_GETSTATS RTM_GETSTATS
        RTM_NEWCACHEREPORT = 96,
    #define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT
        RTM_NEWCHAIN = 100,
    #define RTM_NEWCHAIN RTM_NEWCHAIN
        RTM_DELCHAIN,
    #define RTM_DELCHAIN RTM_DELCHAIN
        RTM_GETCHAIN,
    #define RTM_GETCHAIN RTM_GETCHAIN
        RTM_NEWNEXTHOP = 104,
    #define RTM_NEWNEXTHOP    RTM_NEWNEXTHOP
        RTM_DELNEXTHOP,
    #define RTM_DELNEXTHOP    RTM_DELNEXTHOP
        RTM_GETNEXTHOP,
    #define RTM_GETNEXTHOP    RTM_GETNEXTHOP
        RTM_NEWLINKPROP = 108,
    #define RTM_NEWLINKPROP    RTM_NEWLINKPROP
        RTM_DELLINKPROP,
    #define RTM_DELLINKPROP    RTM_DELLINKPROP
        RTM_GETLINKPROP,
    #define RTM_GETLINKPROP    RTM_GETLINKPROP
        RTM_NEWVLAN = 112,
    #define RTM_NEWNVLAN    RTM_NEWVLAN
        RTM_DELVLAN,
    #define RTM_DELVLAN    RTM_DELVLAN
        RTM_GETVLAN,
    #define RTM_GETVLAN    RTM_GETVLAN
        RTM_NEWNEXTHOPBUCKET = 116,
    #define RTM_NEWNEXTHOPBUCKET    RTM_NEWNEXTHOPBUCKET
        RTM_DELNEXTHOPBUCKET,
    #define RTM_DELNEXTHOPBUCKET    RTM_DELNEXTHOPBUCKET
        RTM_GETNEXTHOPBUCKET,
    #define RTM_GETNEXTHOPBUCKET    RTM_GETNEXTHOPBUCKET
        __RTM_MAX,
    #define RTM_MAX        (((__RTM_MAX + 3) & ~3) - 1)
    

    (3)、struct rtmsg结构体

    struct rtmsg
        unsigned char rtm_family; // 路由项所属协议族,如AF_INET、AF_INET6;
         // 路由项目的地址掩码长度,特别注意是掩码长度,和属性中的RTA_DST配合表示路由项的目的网络
        unsigned char rtm_dst_len;
        unsigned char rtm_src_len; // 源地址掩码长度
        unsigned char rtm_tos; // 路由项的服务质量,对应IP首部的服务字段
        // 路由项所述路由表ID,由于只有1个字节,所以取值最大为255,因此用户态能够配置256个路由表,
        // 但是内核实际上并不是只能保存256个路由表,内核对此是没有限制的;
        unsigned char rtm_table; /* 路由表选取 */
        unsigned char rtm_protocol;    // 路由协议,含义见下方
        unsigned char rtm_scope; // 路由项的作用域,含义见下方    
        unsigned char rtm_type;    // 路由项类型,含义见下方
        unsigned rtm_flags;// 路由标记
    

    1)rtm_table:路由表ID,内核有如下规划,从表ID的定义可以看出,当支持策略路由时(多路由表),用户态程序能够新建的表的ID范围为1~252。

    RT_TABLE_UNSPEC    0    //未知,当操作该路由表时,内核默认会操作main表
    RT_TABLE_DEFAULT    253    //default表,用于策略路由的默认路由表
    RT_TABLE_MAIN    254    //main表,主路由表,不指定具体要操作的表时就是main表
    RT_TABLE_LOCAL    255    //local表,该表维护了本机IP地址相关的路由,由内核自己维护,用户态无法操作

    2)rtm_protocol:路由协议,表示的是路由项是如何产生的,可取的值如下,其中常见的是RTPROT_KERNEL和RTPROT_STAIC。

    RTPROT_UNSPEC    //未知
    RTPROT_REDIRECT    //该路由项是通过ICMP重定向消息添加的
    RTPROT_KERNEL    //该路由项是由内核自己添加的,local表的路由大多数属于这一类
    RTPROT_BOOTUP    //该路由项是在启动时通过boot协议添加的
    RTPROT_STAIC    //该路由项是由管理员静态添加的,用户态配置的路由属于这一类

    3)rtm_scope:路由项的作用域,**表示的是本机距离该路由项表示的目的地址的距离。**如下,值(0, 200)之间的数字是可以由管理员自己配置的。值越大,距离本机越近。所以叫它作用域其实是不利于理解的。

    RT_SCOPE_UNIVERSE    0    //表示目的地和本机距离超过了1跳
    RT_SCOPE_SITE    200    
    RT_SCOPE_LINK    253    //表示目的地位于和本机直接相连的网络,如同一局域网内的其它主机地址
    RT_SCOPE_HOST    254    //表示目的地是本地地址
    RT_SCOPE_NOWHERE    255    //表示目的地不存在,这是个保留值

    4)rtm_type:路由表项的类型,它一定程度上决定了数据包匹配该路由表项后应该采取的动作。

    enum {
        RTN_UNSPEC,
        RTN_UNICAST,        /* Gateway or direct route    */
        RTN_LOCAL,        /* Accept locally        */
        RTN_BROADCAST,        /* Accept locally as broadcast,
                       send as broadcast */
        RTN_ANYCAST,        /* Accept locally as broadcast,
                       but send as unicast */
        RTN_MULTICAST,        /* Multicast route        */
        RTN_BLACKHOLE,        /* Drop                */
        RTN_UNREACHABLE,    /* Destination is unreachable   */
        RTN_PROHIBIT,        /* Administratively prohibited    */
        RTN_THROW,        /* Not in this table        */
        RTN_NAT,        /* Translate this address    */
        RTN_XRESOLVE,        /* Use external resolver    */
        __RTN_MAX
    
    RTN_UNSPEC    //一个未初始化的值
    RTN_LOCAL    //该路由项的目的地址是主机本地地址
    RTN_UNICAST    //该路由是一条到直连或者非直连目的地址的单播路由,添加路由时如果不指定路由类型,那么默认取该值
    RTN_MULTICAST    //该该路由项的目的地址是一个多播地址
    RTN_BROADCAST    //该该路由项的目的地址是一个广播地址
    RTN_ANYCAST    //IPv4中不使用
    RTN_UNREACHABLE    //路由结果是不可达,会给源地址回复主机不可达ICMP报文
    RTN_BLACKHOLE    //路由结果是不可达,会悄悄的丢弃数据包,不做任何回应
    RTN_PROHIBIT    //路由结果为不可达,会给源地址回复"communication administratively prohibited"类型的ICMP报文
    RTN_THROW    //路由结果为不可达,会给源地址回复网络不可达ICMP报文

    (4)、struct rtattr结构体

    struct rtattr {
        unsigned short    rta_len;
        unsigned short    rta_type;
         /* Data follows */
    

    所包含的结构体还有很多,具体结构体的使用根据nlmsg_type消息类型进行选择的,但基本都是以rtattr结构体结尾,所以RTNetlink的基本消息构成为:

    nlmsghdr + option_msg + rtattr

  • nlmsghdr 选择对应的消息类型;
  • option_msg就是根据nlmsghdr 中nlmsg_type消息类型来确定使用的消息头;
  • rtattr主要是具体的消息内容。
  • 2、RTNetlink消息类型

    (1)、RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK

    创建或者删除一个特定的网络接口,或者从一个特定的网络接口上获得信息。这些消息含有一个ifinfomsg类型的结构,紧跟在后面的是一系列的rtattr结构。

    /*****************************************************************
     *      Link layer specific messages.
     ****/
    /* struct ifinfomsg
     * passes link level specific information, not dependent
     * on network protocol.
     struct ifinfomsg {
        unsigned char  ifi_family; /* AF_UNSPEC */
        unsigned short ifi_type;   /* Device type */
        int            ifi_index;  /* Interface index */
        unsigned int
    
    
    
    
        
       ifi_flags;  /* Device flags  */
        unsigned int   ifi_change; /* change mask */
    * ifi_family: 接口地址类型
    * ifi_type: 设备类型
    * ifi_index: 是结构唯一的索引
    * ifi_flags:  设备标志,可以看netdevice 结构
    * ifi_change: 保留值,通常设置为0xFFFFFFFF
    ifi_type代表硬件设备的类型:
        ARPHRD_ETHER                   10M以太网
        ARPHRD_PPP                     PPP拨号
        ARPHRDLOOPBACK                 环路设备
    ifi_flags包含设备的一些标志:
        IFF_UP                            接口正在运行
        IFF_BROADCAST                     有效的广播地址集
        IFF_DEBUG                         内部调试标志
        IFF_LOOPBACK                      这是自环接口
        IFF_POINTOPOINT                   这是点到点的链路设备
        IFF_RUNNING                       资源已分配
        IFF_NOARP                         无arp协议,没有设置第二层目的地址
        IFF_PROMISC                       接口为杂凑(promiscuous)模式
        IFF_NOTRAILERS                    避免使用trailer
        IFF_ALLMULTI                      接收所有组播(multicast)报文
        IFF_MASTER                        主负载平衡群(bundle)
        IFF_SLAVE                         从负载平衡群(bundle)
        IFF_MULTICAST                     支持组播(multicast)
        IFF_PORTSEL                       可以通过ifmap选择介质(media)类型
        IFF_AUTOMEDIA                     自动选择介质
        IFF_DYNAMIC                       接口关闭时丢弃地址
                Routing attributes(rtattr部分属性,rta_type)
    rta_type         value type         description
     ──────────────────────────────────────────────────────────
    IFLA_UNSPEC      -                  未说明,未指定的数据
    IFLA_ADDRESS     hardware address   L2硬件地址
    IFLA_BROADCAST   hardware address   L2广播地址.
    IFLA_IFNAME      asciiz string      char型设备名.
    IFLA_MTU         unsigned int       MTU of the device.
    IFLA_LINK        int                Link type.
    IFLA_QDISC       asciiz string      Queueing discipline.
    IFLA_STATS       see below          struct rtnl_link_stats的设备信息
    //用来获取ifinfomsg后面的rtattr结构
    #define IFLA_RTA(r)  ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
    

    (2)、RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR

    添加、删除或者接收一个和接口相关的IP地址的信息。这些消息含有一个ifaddrmsg类型的结构,紧跟在后面的是一系列的rtattr结构。

    struct ifaddrmsg {
        unsigned char ifa_family;    /* Address type */
        unsigned char ifa_prefixlen; /* Prefixlength of address */
        unsigned char ifa_flags;     /* Address flags */
        unsigned char ifa_scope;     /* Address scope */
        int           ifa_index;     /* Interface index */
    * ifa_family: 地址类型(通常为AF_INET or AF_INET6))
    * ifa_prefixlen: 地址的地址掩码长度,如果改地址定义在这个family
    * ifa_flags:
    * ifa_scope: 地址的作用域
    * ifa_index:  接口索引与接口地址关联
                Attributes (rtattr部分属性,rta_type)
    rta_type        value type             description
    ─────────────────────────────────────────────────────────────
    IFA_UNSPEC      -                      unspecified.
    IFA_ADDRESS     raw protocol address   接口地址 interface address
    IFA_LOCAL       raw protocol address   本地地址 local address
    IFA_LABEL       asciiz string          接口名称 name of the interface
    IFA_BROADCAST   raw protocol address   广播 broadcast address.
    IFA_ANYCAST     raw protocol address   anycast address
    IFA_CACHEINFO   struct ifa_cacheinfo   Address information.
    

    (3)、RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE

    创建,删除或者获取网络设备的路由信息;这些消息包含一个rtmsg结构,其后跟数目可选的rtattr结构。
    对于RTM_GETROUTE,设置rtm_dst_len以及rtm_src_len为0表示获取指定路由表的所有条目(entries)。
    其它的成员,除了rtm_table、rtm_protocol,0是通配符。

    struct rtmsg结构体参考上面内容。

    对应的rta_type如下:

    rta_type        value type         description
    ──────────────────────────────────────────────────────────────
    RTA_UNSPEC      -                  ignored.
    RTA_DST         protocol address   Route destination address.   /* 目的 */
    RTA_SRC         protocol address   Route source address.        /* 源地址 */
    RTA_IIF         int                Input interface index.        /* 输入设备 index */
    RTA_OIF         int                Output interface index.
    RTA_GATEWAY     protocol address   The gateway of the route        /* 网关 */
    RTA_PRIORITY    int                Priority of route.            /* 优先级 */
    RTA_PREFSRC
    RTA_METRICS     int                Route metric                    /* 路由metric 值*/
    RTA_MULTIPATH
    RTA_PROTOINFO
    RTA_FLOW
    RTA_CACHEINFO
    RTA_SESSION, /* no longer used */
    RTA_MP_ALGO, /* no longer used */
    RTA_TABLE,
    RTA_MARK,
    RTA_MFC_STATS,
    RTA_VIA,
    RTA_NEWDST,
    RTA_PREF,
    RTA_ENCAP_TYPE,
    RTA_ENCAP,
    RTA_EXPIRES,
    RTA_PAD,
    RTA_UID,
    RTA_TTL_PROPAGATE,
    RTA_IP_PROTO,
    RTA_SPORT,
    RTA_DPORT,
    RTA_NH_ID,

    3、RTNetlink的具体使用实例

    /*********************************************************
    * Filename: nl_netinfo.c
    * Author: zhangwj
    * Date:
    * Descripte:
    * Email:
    * Warnning:
    **********************************************************/
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <sys/epoll.h>
    #include <linux/netlink.h>
    #include <linux/rtnetlink.h>
    #include <linux/route.h>
    #include <errno.h>
    #define EPOLL_LISTEN_MAX_CNT    256
    #define EPOLL_LISTEN_TIMEOUT    500
    int g_nlfd = -1;
    int g_epollfd = -1;
    void parse_rtattr(struct rtattr **tb, int max, struct rtattr *attr, int len)
        for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
            if (attr->rta_type <= max) {
                tb[attr->rta_type] = attr;
    void nl_netroute_handle(struct nlmsghdr *nlh)
        int len;
        struct rtattr *tb[RTA_MAX + 1];
        struct rtmsg *rt;
        char tmp[256];
        bzero(tb, sizeof(tb));
        rt = NLMSG_DATA(nlh);
        len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*rt));
        parse_rtattr(tb, RTA_MAX, RTM_RTA(rt), len);
        printf("%s: ", (nlh->nlmsg_type==RTM_NEWROUTE)?"NEWROUT":"DELROUT");
        if (tb[RTA_DST] != NULL) {
            inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_DST]), tmp, sizeof(tmp));
            printf("DST: %s ", tmp);
        if (tb[RTA_SRC] != NULL) {
            inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_SRC]), tmp, sizeof(tmp));
            printf("SRC: %s ", tmp);
        if (tb[RTA_GATEWAY] != NULL) {
            inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_GATEWAY]), tmp, sizeof(tmp));
            printf("GATEWAY: %s ", tmp);
        printf("\n");
    void nl_netifinfo_handle(struct nlmsghdr *nlh)
        int len;
        struct rtattr *tb[IFLA_MAX + 1];
        struct ifinfomsg *ifinfo;
        bzero(tb, sizeof(tb));
        ifinfo = NLMSG_DATA(nlh);
        len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));
        parse_rtattr(tb, IFLA_MAX, IFLA_RTA (ifinfo), len);
        printf("%s: %s ", (nlh->nlmsg_type==RTM_NEWLINK) ? "NEWLINK" : "DELLINK", (ifinfo->ifi_flags & IFF_UP) ? "up" : "down");
        if(tb[IFLA_IFNAME]) {
            printf("%s", RTA_DATA(tb[IFLA_IFNAME]));
        printf("\n");
    void nl_netifaddr_handle(struct nlmsghdr *nlh)
        int len;
        struct rtattr *tb[IFA_MAX + 1];
        struct ifaddrmsg *ifaddr;
        char tmp[256];
        bzero(tb, sizeof(tb));
        ifaddr = NLMSG_DATA(nlh);
        len =nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifaddr));
        parse_rtattr(tb, IFA_MAX, IFA_RTA (ifaddr), len);
        printf("%s ", (nlh->nlmsg_type == RTM_NEWADDR)? "NEWADDR":"DELADDR");
        if (tb[IFA_LABEL] != NULL) {
            printf("%s ", RTA_DATA(tb[IFA_LABEL]));
        if (tb[IFA_ADDRESS] != NULL) {
            inet_ntop(ifaddr->ifa_family, RTA_DATA(tb[IFA_ADDRESS]), tmp, sizeof(tmp));
            printf("%s ", tmp);
        printf("\n");
    void nl_netlink_handle(int fd)
        int r_size;
        socklen_t len = 0;
        char buff[2048] = {0};
        struct sockaddr_nl addr;
        struct nlmsghdr *nlh;
        while(1)
            len = sizeof(addr);
            r_size = recvfrom(fd, (void *)buff, sizeof(buff), 0, (struct sockaddr *)&addr, &len);
            nlh = (struct nlmsghdr *)buff;
            for(; NLMSG_OK(nlh, r_size); nlh = NLMSG_NEXT(nlh, r_size))
                switch(nlh->nlmsg_type) {
                case NLMSG_DONE:
                case NLMSG_ERROR:
                    break;
                case RTM_NEWLINK:     /* */
                case RTM_DELLINK:
                    nl_netifinfo_handle(nlh);
                    break;
                case RTM_NEWADDR:
                case RTM_DELADDR:    /* */
                    nl_netifaddr_handle(nlh);
                    break;
                case RTM_NEWROUTE:
                case RTM_DELROUTE:   /* */
                    nl_netroute_handle(nlh);
                    break;
                default:
                    break;
    void epoll_event_handle(void)
        int i = 0;
        int fd_cnt = 0;
        int sfd;
        struct epoll_event events[EPOLL_LISTEN_MAX_CNT];
        memset(events, 0, sizeof(events));
        while(1)
            /* wait epoll event */
            fd_cnt = epoll_wait(g_epollfd, events, EPOLL_LISTEN_MAX_CNT, EPOLL_LISTEN_TIMEOUT);
            for(i = 0; i < fd_cnt; i++)
                sfd = events[i].data.fd;
                if(events[i].events & EPOLLIN)
                    if (sfd == g_nlfd)
                        nl_netlink_handle(sfd);
    int epoll_add_fd(int fd)
        struct epoll_event ev;
        ev.data.fd = fd;
        ev.events = EPOLLIN | EPOLLET;
        if (epoll_ctl(g_epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
            perror("epoll add fd error");
            return -1;
        printf("epoll add fd[%d] success\n", fd);
        return 0;
    int init_epoll_fd()
        int epollfd = -1;
        epollfd = epoll_create(EPOLL_LISTEN_MAX_CNT);
        if (epollfd < 0) {
            perror("epoll create failure!...");
            return -1;
        g_epollfd = epollfd;
        printf("epool create fd [%d] success\n", epollfd);
        return g_epollfd;
    int init_nl_sockfd()
        int ret = 0;
        int nlfd = -1;
        struct sockaddr_nl sa;
        /* open a netlink fd */
        nlfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
        if (nlfd < 0) {
            perror("create netlink socket failure");
            return -1;
        memset(&sa, 0, sizeof(sa));
        sa.nl_family = AF_NETLINK;
        sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
        /* bind netlink */
        ret = bind(nlfd, (struct sockaddr *)&sa, sizeof(sa));
        if (ret < 0) {
            perror("bind nlfd error");
            close(nlfd);
            return -1;
        if (epoll_add_fd(nlfd)) {
            close(nlfd);
            return -1;
        g_nlfd = nlfd;
        printf("netlink create fd [%d] success\n", nlfd);
        return nlfd;
    int main(int argc, char **argv)
        if (init_epoll_fd() < 0) { /* 创建epoll 监听fd */
            return -1;
        if (init_nl_sockfd() < 0) { /* 创建netlink */
            return -1;
        /* 循环接收处理 */
        epoll_event_handle();
        return 0;
    

    四、RTNetlink添加路由的实例

    vnet_netlink_add_ip4_route_vrf (void *dst, u8 dst_len, void *gw, u32 o_if, u32 vrf_id)
      vnet_netlink_msg_t m;
      struct rtmsg rtm = { 0 };
      u8 dflt[4] = { 0 };
      clib_error_t *err = 0;
      rtm.rtm_family = AF_INET;
      rtm.rtm_table = RT_TABLE_MAIN;
      rtm.rtm_type = RTN_UNICAST;
      rtm.rtm_dst_len = dst_len;
      //初始化nlmsghd,填充消息类型为RTM_NEWROUTE
      vnet_netlink_msg_init (&m, RTM_NEWROUTE,
                 NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE,
                 &rtm, sizeof (struct rtmsg));
      if (gw != NULL)
        //add包含网关信息的rta
        vnet_netlink_msg_add_rtattr (&m, RTA_GATEWAY, gw, 4);
      if (o_if != ~0)
           //add包含出口设备的网口索引
        vnet_netlink_msg_add_rtattr (&m, RTA_OIF, &o_if, 4);
      if (vrf_id != ~0)
        //add包含路由表的rta
        vnet_netlink_msg_add_rtattr (&m, RTA_TABLE, &vrf_id, 4);
      //add包含路由地址的rta
      vnet_netlink_msg_add_rtattr (&m, RTA_DST, dst ? dst : dflt, 4);
      err = vnet_netlink_msg_send (&m, NULL);
      if (err)
        err = clib_error_return (0, "add ip4 route %U", format_clib_error, err);
      return err;
    

    通过netlink下发其它网口配置可以参考iproute2源码。