zoukankan      html  css  js  c++  java
  • Netlink 介绍(译)

    原文地址:http://people.redhat.com/nhorman/papers/netlink.pdf


     

    译文:

    1 介绍   

      在Linux和Unix的众多发行版中的网络配置功能, 都是编程者事后需求的功能, 导致像添加路由、邻居表条目和配置接口等功能有着很多杂乱的方法, 比如raw socket, ioctl调用以及专门的伪网络协议等方法。在Linux 2.4内核中, 开发者努力实现了一种更标准化的配置网络的方法。这种方法被命名为netlink sockets, 它旨在创建一个适合所有网络控制方面的通信框架,虽然建立的netlink子系统不是完善的, 但是这是一种新的网络配置方法, 也是可靠的基础。此文档旨在介绍如何使用netlink socket族和其实现的协议。

        本文假设读者有C和socket编程的基础。

    2 Netlink 地址族

    2.1 socket创建

      netlink地址族使用标准的BSD socket API作为用户空间程序和内核交互的使者。创建一个netlink套接字和创建其它套接字是类似的方式。

    socket fd=socket(AF_NETLINK, SOCK_RAW, protocol);

      地址族参数总是AF_NETLINK, 并且类型值总是SOCK_RAW, 唯一可变的参数 就是协议protocol域, 此域将会继续添加可选项, 增加了它的可配置性, 下表是protocol的可选项(来自linux-2.6.32 kernel)。

    #define NETLINK_ROUTE           0       /* Routing/device hook                          */
    #define NETLINK_UNUSED          1       /* Unused number                                */
    #define NETLINK_USERSOCK        2       /* Reserved for user mode socket protocols      */
    #define NETLINK_FIREWALL        3       /* Firewalling hook                             */
    #define NETLINK_INET_DIAG       4       /* INET socket monitoring                       */
    #define NETLINK_NFLOG           5       /* netfilter/iptables ULOG */
    #define NETLINK_XFRM            6       /* ipsec */
    #define NETLINK_SELINUX         7       /* SELinux event notifications */
    #define NETLINK_ISCSI           8       /* Open-iSCSI */
    #define NETLINK_AUDIT           9       /* auditing */
    #define NETLINK_FIB_LOOKUP      10
    #define NETLINK_CONNECTOR       11
    #define NETLINK_NETFILTER       12      /* netfilter subsystem */
    #define NETLINK_IP6_FW          13
    #define NETLINK_DNRTMSG         14      /* DECnet routing messages */
    #define NETLINK_KOBJECT_UEVENT  15      /* Kernel messages to userspace */
    #define NETLINK_GENERIC         16
    /* leave room for NETLINK_DM (DM Events) */
    #define NETLINK_SCSITRANSPORT   18      /* SCSI Transports */
    #define NETLINK_ECRYPTFS        19
    
    #define NETLINK_L2TP 20
    
    #if defined(CONFIG_RTL_819X)
    #define NETLINK_RTK_DEBUG       21
    #define NETLINK_RTK_FILTER      22
    #define NETLINK_MULTICAST_DELETE        23
    #define NETLINK_RTK_FB          24
    #define NETLINK_RTK_HW_QOS 25
    #endif

    2.2 发送和接收数据包

      netlink套接字是无连接的, 收发的数据报就表示类似UDP套接字, 发送数据报通过sendto或者sendmsg系统调用, 用recvfrom或者recvmsg接收数据报。 注意, netlink套接字不使用send和recv交互, 这是因为netlink套接字是无连接的。就像UDP套接字, netlink消息是数据报格式, 虽然netlink消息头部有一些机制设计用于编程者增加协议的可靠性, 但仍旧不能保证连接是可靠的。

    2.3 netlink套接字地址结构

      struct sockaddr_nl 是它的地址结构, 用于netlink套接字的接收和发送, 定义如下:

    struct sockaddr_nl
    {
            sa_family_t     nl_family;      /* AF_NETLINK   */
            unsigned short  nl_pad;         /* zero         */
            __u32           nl_pid;         /* port ID      */
            __u32           nl_groups;      /* multicast groups mask */
    };
    • nl_family:此域定义了消息的地址族, 应该总是AF_NETLINK
    • nl_pad   : 总是为0
    • nl_pid  : 一般设置为本进程的pid或者填0理, 如果是进程接收来自内核netlink消息, 此域应为本进程PID,  如果是向内核发送netlink消息, 此域应置0
    • nl_groups :用于指定多播组, 如对接收来自内核的netlink消息来说, 内核可将要发送的消息指定一个多播组, 那么此消息就会发向同一多播组的接收端。而对于进程发送消息来讲, 设置了多播组就只会发送到此多播组的内核接收端。此域是32位, 最多可支持32个多播组。

    3 Netlink 消息格式

       与每个IP消息头一样, netlink消息也有类似的头部, 然而和其他协议不同的是, 编程者需要为每个数据包构建这个头部(一般的TCP/UDP socket都是直接操作报文的payload部分), 这个头部用来保存每个消息和格式的元数据, 这个头部也是netlink协议的基础。

    struct nlmsghdr {
      u32 nlmsg_len;
      u16 nlmsg_type;
      u16 nlmsg_flags;
      u32 nlmsg_seq;
      u32 nlmsg_pid;
    }
    • nlmsg_len:每个netlink头后面跟随者0个或者多个字节的辅助数据, 此域记录了消息的整个长度, 包括了头部在内。
    • nlmsg_type:此域标识了头部后面数据的格式。此域的取值和2.1中的protocol有关。
    • nlmsg_flags:此域标识了消息由谁进行处理和解析, 有如下取值
      NLM_F_REQUEST - 这个标志暗示这是一个请求消息, 它应该被设置到大多数应用程序的初始化消息中。
      NLM_F_ACK - 这个标志暗示对前一个请求消息的回应, 序列号和pid值能够辨别请求的回应报文。
      NLM_F_ECHO - 这个标志表示发出的报文将回响给发送进程一份。
      NLM_F_MULTI - 这个标志表示此消息是多个消息的一部分, 可用宏NLMSG_NEXT获得下一个消息。
      NLM_F_ROOT - 用于请求多个netlink消息, 有此标志的请求消息表示请求回复整个条目表而不是一条, 回复的报文通常是
      NLM_F_MULTI标志的。注意:此标志只适合特定的nlmsg_type才有效。
      NLM_F_ATOMIC - 标志任何通过get-->回复报文的过程都是原子的, 防止在中间有资源改变引起歧义。
      NLM_F_REPLACE - 替换条目表中的一条, 可用于覆盖条目表。
      NLM_F_CREATE - 在条目表中设置一个新的条目(比如添加一条新路由)
      NLM_F_APPEND - 在条目表末尾添加一个条目
      NLM_F_EXCL - 结合了CREATE和APPEND, 如果要添加的条目已存在将返回错误(推荐使用)
    • nlmsg_seq:seq用来联系请求和回复报文, 顺序标志的作用

    • nlmsg_pid:和seq类似

    3.2 netlink 实用宏

    #define NLMSG_ALIGNTO   4

    /* 返回不小于len的以4字节对齐的最近数字 (ex: len=9 --> 返回值为12)*/
    #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

    /* 返回netlink消息头的字节数, 且务必是以4字节对齐的 */
    #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))

    /* 返回'len + netlink消息头长度' 一般len指定为消息的payload长度, 此宏可用来填充nlmsg_len域 */
    #define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(NLMSG_HDRLEN))

    /* 返回值同 NLMSG_LENGTH 宏一样有效, 主要是确保整个netlink消息长度4字节对齐(nlmsg_len域是否最好用此域来填充) */
    #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

    /* 返回指向netlink消息payload处的指针(nlh 参数一般是指向struct nlmsghdr结构的指针) */
    #define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) ----------------------------- | struct nlmsghdr | payload | ----------------------------- | NLMSG_DATA(nlh)

    /* 返回指向下一个netlink消息的指针 */
    /* 很多netlink回应由多个netlink消息组成 */
    *参数:
     nlh --> 指向struct nlmsghdr结构的指针
     len --> 一般为recvmsg函数的返回值(整个消息的长度)
    #define NLMSG_NEXT(nlh,len)      ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), 
                                      (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
    ---------------------------------------------------------
    | struct nlmsghdr | payload | struct nlmsghdr | payload |
    ---------------------------------------------------------
    |                           |            
    nlh                         |
                                NLMSG_NEXT(nlh,len)
    
    |--------------------------len--------------------------|
    *此宏先用len(整个消息的长度)减掉第一个消息的长度, 如果为0, 表示没有下一个消息了, 返回空, 若为非0, 表示后边还有消息, 指针移动指向下一个消息处并返回.
    
    
    /* 确保nlh指向的消息大小不大于len */
    *参数:
     nlh --> 指向struct nlmsghdr结构的指针
     len --> 一般为recvmsg函数的返回值
    #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && 
                               (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && 
                               (nlh)->nlmsg_len <= (len))

     3.3 总结

      下图是netlink消息的内存分布图:

    NETLINK_FIREWALL 协议

      由3可知, netlink消息头nlmsg_type成员的取值和2.1中的protocol有关, 现在就介绍protocol为NETLINK_FIREWALL时的情况。

      这个协议是非常有用的开发协议。首先它是有意被设计来在用户空间来调试iptables模块的架构,这个协议与很多iptables模块相关联。ip-queue模块就是其中一个(详见:http://blog.csdn.net/u010807313/article/details/9236581),在创建此协议的netlink套接字之前都需要先安装相关模块,发往相关模块的报文都会同样发给由NETLINK_FIREWALL协议创建的netlink套接字, 由此实现在用户空间监听流经netfilter的报文的目的。例如:

    iptables -I OUTPUT -j QUEUE -p tcp –destination-port 7551

      上面一条命令表示在OUTPUT链上, 发往端口7551的TCP报文都交给QUEUE链来处理, 而ip-queue模块正好和NETLINK_FIREWALL协议的套接字关联(内核实现的), 所以套接字同样也会收到这样的报文, 实现了监听的目的, 自行修改iptables命令可达到监听多种类型报文的目的。

    4.1 创建和使用

    socket fd=socket(AF_NETLINK, SOCK_RAW, NETLINK_FIREWALL);

      此协议没有使用多播组, 所以地址结构struct sockaddr_nl中的nl_groups应该总是设置为0,并且此协议的套接字不需要bind函数,因为报文只是在进程和内核中传输,所以从进程发向内核的报文struct sockaddr_nl中的nl_pid应该设置为0。

    4.2 消息类型

      NETLINK_FIREWALL协议的套接字有三种消息类型(如3中所述, 成员nlmsg_type的取值),每个消息类型都有它各自的数据结构来描述。

    • IPQM_MODE
    • IPQM_PACKET < 这个是内核向用户空间返回的报文类型 >
    • IPQM_VERDICT
    4.2.1 IPQM_MODE

      此类型是使用NETLINK_FIREWALL协议需要第一个发向内核的包, 内核收到之后才会将匹配的报文从内核发至用户空间的netlink套接字。此报文的数据结构如下, 它是紧随在struct nlmsghdr之后的:

    typedef struct ipq mode msg {
      unsigned char value;
      size t range;
    };

    value的取值有三种:

    • IPQ_COPY_NONE - 不常用, 设置此值发给内核将导致iptables将所有到QUEUE链的报文丢弃。
    • IPQ_COPY_META - 表示我希望内核返回报文的元数据(我理解是struct nlmsghdr + struct ipq_packet_msg 两个头部)部分。
    • IPQ_COPY_PACKET - 表示希望内核返回报文, 报文长度由range控制, 若range为0表示返回整个报文。如果你需要在用户空间分析流经QUEUE链的报文应该设置此项并将range设置为0。

    range:

      此域只在value = IPQ_COPY_PACKET时才有效。

    也就是说, 用户进程使用IPQM_MODE类型的报文告诉内核, 我需要你返回给我的报文是什么样的(不需要 or 要元数据 or 要range长的报文)

    4.2.2 IPQM_PACKET

      这个类型的报文是根据4.2.1之后内核根据需求返回的报文。只要之前设置的value不是IPQ_COPY_NONE, socket就会收到此类型的报文, 结构如下:

    typedef struct ipq packet msg {
      unsigned long packet_id;
      unsigned long mark;
      long timestamp sec;
      long timestamp usec;
      unsigned int hook;
      char indev name[IFNAMSIZ];
      char outdev name[IFNAMSIZ];
      unsigned short hw_protocol;
      unsigned short hw_type;
      unsigned char hw_addrlen;
      unsigned char hw_addr[8];
      size t data len;
      unsigned char payload[0];
    };
    • packet_id - 这个是内核产生的独一无二的标识, 在4.2.3中发送IPQM_VERDICT报文需要。
    • mark - //
    • timestap_sec - 报文抵达时间(秒)
    • timestap_usec - 报文抵达时间(微秒)
    • hook - 报文被重定向到QUEUE的hook number
    • indev_name - //
    • outdev_name - //
    • hw_protocol - 通常是ETH_P_IP
    • hw_type - 通常是ARPHDR_ETHER
    • hw_addrlen - 通常为6
    • hw_addr - 报文的源MAC地址
    • data_len - payload数据长度
    • payload - 柔性数组头部, 指向了payload数据的头部
    4.2.3 IPQM_VERDICT

      此类型的报文是在收到内核的回复报文之后, 用户经过自己的检测, 决定对此报文执行何种操作, IPQM_MODE --> IPQM_PACKET <----> IPQM_VERDICT, 是顺序的过程。也就是说, 只有你向内核发送IPQM_VERDICT说明了报文处理方式之后, 你才能recvmsg下一个到达QUEUE链的报文, 否则recvmsg会一直阻塞。结构如下:

    typedef struct ipq verdict msg {
      unsigned int value;
      unsigned long id;
      size t data_len;
      unsigned char payload;
    };

    value 指示了对报文的处理方式:

    • NF_DROP - 立即丢弃报文
    • NF_ACCEPT - 接收报文(不参与之后的iptables链了)
    • NF_STOLEN - //
    • NF_QUEUE - 不常使用
    • NF_REPEAT - 将报文移入下一个iptables链

    id - 指示了要对哪个报文进行处理, 对应4.2.2的packet_id成员, 这个成员唯一关联了一个进入QUEUE的报文

    data_len - 指verdict报文的payload数据长度, 因为verdict是用户发向内核的, 此域一般设置为0

    payload - //

    5 NETLINK_ROUTE 协议

    5.1 创建和使用

    socket fd=socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

      NETLINK_ROUTE协议是netlink套接字最大且是最成熟的协议, 它有它自己消息的处理宏(类似3.2节), 这些NETLINK_ROUTE的宏是为了添加它的辅助数据段和定制化特别的消息类型而做的。

      每个族都有同样的命名空间和辅助数据结构, 辅助数据结构后又跟着一个或多个消息。

      每个族都包含三个方法, NEW, DEL, GET; 这些方法用于创建、删除和接收路由相关条目。

    5.2 NETLINK_ROUTE消息宏

      NETLINK_ROUTE消息实际有自己的数据结构, 如下所示。

    struct rtattr {
        unsigned short rta_len;
        unsigned short rta_type;
    }

      下面是NETLINK_ROUTE的消息内存布局:

      对于struct rtattr结构, 与netlink消息头struct nlmsghdr结构相似, 有一些宏进行辅助处理, 参考3.2节。

    int RTA OK(struct rtattr *rta, int rtabuflen); - Verify the data
    integrity of the data which succedes this rtattr header.
    • void * RTA DATA(struct rtattr *rta); - Return a pointer to the
    ancilliary data associated with this rtattr header.
    • struct rtattr *RTA NEXT(struct rtattr *rta); - Return a pointer
    to the next rtattr header in the chain.
    • unsigned int RTA PAYLOAD(struct rtattr *rta); - Return the
    length of the ancilliary data associated with the passed rtattr header.
    • unsigned int RTA LENGTH(unsigned int length); - Return the
    aligned length for the passed payload length. This value is assigned to
    the rta len field of the rtattr header
    • unsigned int RTA SPACE(unsigned int length); - Return the
    length of the ancilliary data, when aligned.

    5.3 消息类型

      在使用NETLINK_ROUTE协议的情况下, netlink控制块struct nlmsghdr中的nlmsg_type域标识了多种消息类型,举例如下:

    5.3.1 LINK消息

      LINK消息族允许设置和获取关于系统接口的消息nlmsg_type有如下取值:

    • RTM_NEWLINK  - 创建一个新接口/有一个新接口被创建
    • RTM_DELLINK   - 删除一个接口
    • RTM_GETLINK  - 接收一个接口消息

    每个消息的辅助数据结构是struct ifinfomsg:

    struct ifinfomsg {
        unsigned char ifi_family;
        unsigned short ifi_type;
        int ifi index;
        unsigned int ifi_flags;
        unsigned int ifi_change;
    };
    5.3.2 LINK消息struct rtattr结构rta_type取值

    5.3.3 LINK消息内存布局

    5.4 其它消息类型

      比如ADDR消息, ROUTE消息等和LINK消息类似, 不同的是它们有各自的struct ifinfomsg消息和支持不同的rta_type。  

  • 相关阅读:
    windy数
    微信授权网页登陆,oauth
    Win7/Win2008下IIS配置Asp网站启用父路径的设置方法(已解决)
    Html5学习笔记1 元素 标签 属性
    com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database &#39;user&#39;
    POJ 3978(求素数)
    java内存模型(Java Memory Model)
    【Unity3D自学记录】Unity3D之自制小钟表
    HDU4126Genghis Khan the Conqueror(最小生成树+并查集)
    gcc学习(一)[第二版]
  • 原文地址:https://www.cnblogs.com/Flychown/p/8065649.html
Copyright © 2011-2022 走看看