Netlink机制详解

更多文章请多关注个人网站:http://www.readbk.net,谢谢浏览!

机制原理:

Netlink 是一种特殊的 socket,它是 Linux 所特有的,由于传送的消息是暂存在socket接收缓存中,并不被接收者立即处理,所以netlink是一种异步通信机制。 系统调用和 ioctl 则是同步通信机制。

用户空间进程可以通过标准socket API来实现消息的发送、接收,在Linux中,有很多用户空间和内核空间的交互都是通过Netlink机制完成的,在Linux3.0的内核版本中定义了下面的21个用于Netlink通信的宏,其中默认的最大值为32.我这里重点关注的是IPv6路由部分的通信过程。

  在include/linux/`netlink.h文件中定义了下面的用于通信的宏!

#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_RDMA        20

#define MAX_LINKS 32

用户态可以使用标准的 socket APIs  socket(), bind(), sendmsg(), recvmsg()   close()  等函数就能很容易地使用  netlink socket ,我们在用户空间可以直接通过 socket 函数来使用 Netlink 通信,例如可以通过下面的方式:

sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

说明:第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,可以是自己在netlink.h中定义的,也可以是内核中已经定义好的。上面的例子使用主要是路由的Netlink协议。也可以是上面21中协议类型的其中之一。

NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。

对于每一个netlink协议类型,可以使用多播的概念,最多可以有 32个多播组,每一个多播组用一个位表示,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 */
};

说明:sa_family_t      nl_family; //一般为AF_NETLINK,

      unsigned short   nl_pad;  //字段 nl_pad 当前没有使用,因此要总是设置为 0,

      __u32            nl_pid;   //绑定时用于指定绑定者的进程号,发送消息时用于指定接收进程号,如果希望内核处理多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这相当于 netlink socket 的本地地址。但是,对于一个netlink socket 的情况,字段 nl_pid 则可以设置为其它的值,如:pthread_self() << 16 | getpid();因此字段 nl_pid 实际上未必是进程 ID,它只是用于区分不同的接收者或发送者的一个标识,用户可以根据自己需要设置该字段。

      __u32   nl_groups;  //绑定时用于指定绑定者所要加入的多播组,这样绑定者就可以接收多播消息,发送 消息时可以用于指定多播组,这样就可以将消息发给多个接收者。这里nl_groups 为32位的无符号整形,所以可以指定32个多播组,每个进程可以加入多个多播组, 因为多播组是通过“或”操作,如果设置为 0,表示调用者不加入任何多播组。这里就是Netlink多播的概念!和通信中的多播概念有点类似。 

Bind的调用方式如下!

struct sockaddr_nl snl;
memset (&snl, 0, sizeof snl);
snl.nl_family = AF_NETLINK;
snl.nl_groups = groups;
ret = bind (sock, (struct sockaddr *) &snl, sizeof snl);
其中 sock 为前面的  socket  调用返回的文件描述符,参数 snl  struct sockaddr_nl  类型的地址。   为了发送一个  netlink  消息给内核或其他用户态应用,需要填充目标  netlink socket  地址   ,此时,字段  nl_pid   nl_groups  分别表示接收消息者的进程  ID  与多播组。如果字段  nl_pid  设置为  0 ,表示消息接收者为内核或多播组,如果  nl_groups  0 ,表示该消息为单播消息,否则表示多播消息。用户态使用函数  sendmsg  发送  netlink  消息时还需要引用结构  struct msghdr struct nlmsghdr   struct iovec ,结构  struct msghdr  的定义如下:

struct msghdr {
    void    *    msg_name;    /* Socket name            */
    int        msg_namelen;    /* Length of name        */
    struct iovec *    msg_iov;    /* Data blocks            */
    __kernel_size_t    msg_iovlen;    /* Number of blocks        */
    void     *    msg_control;    /* Per protocol magic (eg BSD file descriptor passing) */
    __kernel_size_t    msg_controllen;    /* Length of cmsg list */
    unsigned    msg_flags;
};
使用方法如下:

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&(snl);
msg.msg_namelen = sizeof(snl);

struct nlmsghdr 为 netlink socket 自己的消息头,因此它也被称为netlink 控制块。应用层在向内核发送 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 PID */
};
字段  nlmsg_len  指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,字段  nlmsg_type  用于应用内部定义消息的类型,它对 netlink  内核实现是透明的,因此大部分情况下设置为  0 ,字段  nlmsg_seq   nlmsg_pid  用于应用追踪消息,前者表示顺序号,后者为消息来源进程  ID 。字段  nlmsg_flags  用于设置消息标志,可用的标志包括下面的宏定义: kernel/include/linux/netlink.c

/* Flags values */

#define NLM_F_REQUEST        1    /* It is request message.     */
#define NLM_F_MULTI        2    /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK        4    /* Reply with ack, with zero or error code */
#define NLM_F_ECHO        8    /* Echo this request         */

/* 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        */

标志NLM_F_REQUEST用于表示消息是一个请求,所有应用首先发起的消息都应设置该标志。

标志NLM_F_MULTI 用于指示该消息是一个多部分消息的一部分,后续的消息可以通过宏NLMSG_NEXT来获得。

宏NLM_F_ACK表示该消息是前一个请求消息的响应,顺序号与进程ID可以把请求与响应关联起来。

标志NLM_F_ECHO表示该消息是相关的一个包的回传。

标志NLM_F_ROOT 被许多 netlink 协议的各种数据获取操作使用,该标志指示被请求的数据表应当整体返回用户应用,而不是一个条目一个条

地返回。有该标志的请求通常导致响应消息设置 NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,因此,需要在字段

nlmsg_type 中指定协议类型。

标志 NLM_F_MATCH 表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。

标志 NLM_F_ATOMIC 指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。

标志 NLM_F_DUMP 未实现。

标志 NLM_F_REPLACE 用于取代在数据表中的现有条目。

标志 NLM_F_EXCL_ 用于和 CREATE 和 APPEND 配合使用,如果条目已经存在,将失败。

标志 NLM_F_CREATE 指示应当在指定的表中创建一个条目。

标志 NLM_F_APPEND 指示在表末尾添加新的条目。

内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些设置),

下面是在调用sendmsg函数之前的各个结构体的赋值操作:

struct nlmsghdr *n 
  struct iovec iov = { (void*) n, n->nlmsg_len };
  struct msghdr msg = {(void*) &snl, sizeof snl, &iov, 1, NULL, 0, 0};
其中 snl   struct sockaddr_nl snl; 在结构体 struct msghdr 中包含有 struct iovec 结构,其实就是我们要传输的数据块,它为一个指针,定义了数据块的基址和长度。

struct iovec
{
    void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */
    __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
上面的数据结构全部初始化以后就可以调用 sendmsg 函数进行发送操作了。

status = sendmsg (sock, &msg, 0);
其中 sock 就是我们创建的 sock 套接字, msg 就是上面结构体 struct msghdr 的实例。如果我们需要返回一个 ACK 消息,可以对 flags 标志进行设置如下:

/* Request an acknowledgement by setting NLM_F_ACK */
  n->nlmsg_flags |= NLM_F_ACK;
使用下面的函数进行接收处理时, status; 为返回的状态,这里可能的结果为:

  1. #define NLMSG_NOOP        0x1    /* Nothing.        */
    #define NLMSG_ERROR        0x2    /* Error        */
    #define NLMSG_DONE        0x3    /* End of a dump    */
    #define NLMSG_OVERRUN        0x4    /* Data lost    
    int status;
    char buf[4096];
          struct iovec iov = { buf, sizeof buf };
          struct sockaddr_nl snl;
          struct msghdr msg = { (void*)&snl, sizeof snl, &iov, 1, NULL, 0, 0};
          struct nlmsghdr *h;
          status = recvmsg (sock, &msg, 0);

    在linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:

    #define NLMSG_ALIGNTO       4

    #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

    /*宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值*/

    #define NLMSG_HDRLEN         ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))

    #define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(NLMSG_HDRLEN))

    /*宏NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。它一般用于分配消息缓存*/

    #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

    /*宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值,它也用于分配消息缓存*/

    #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

    /*宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏*/

    #define NLMSG_NEXT(nlh,len)         ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \

                                           (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

    /*宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用*/

    #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \

                                   (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \

                                   (nlh)->nlmsg_len <= (len))

    /*宏NLMSG_OK(nlh,len)用于判断消息是否有len这么长*/

    #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

    /*宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度*/

    在/kernel/net/netlink/af_netlink.c文件中定义了netlink套接字的结构体,

    struct netlink_sock {
        /* struct sock has to be the first member of netlink_sock */
        struct sock        sk;
        u32            pid; //内核自己的pid,=0
        u32            dst_pid;
        u32            dst_group;//目的组
        u32            flags;
        u32            subscriptions;
        u32            ngroups;// 组数量
        unsigned long        *groups; //组号
        unsigned long        state;
        wait_queue_head_t    wait;// 进程在接收数据包时等待队列
        struct netlink_callback    *cb;
        spinlock_t        cb_lock;
        void            (*data_ready)(struct sock *sk, int bytes); ///内核态接收到用户态信息后的处理函数
        struct module        *module;
    };
    af_netlink.c文件中我们可以看到netlink协议的注册
  2. static struct proto netlink_proto = {
        .name     = "NETLINK",
        .owner     = THIS_MODULE,
        .obj_size = sizeof(struct netlink_sock),
    };
    static int __init netlink_proto_init(void)函数中会调用注册协议的函数,对netlink协议进行注册,其中,netlink_proto就是上面的struct proto netlink_proto协议。
  3. int err = proto_register(&netlink_proto, 0);
    在内核中接收的数据和存储发送的数据都是放在了skb_buff的结构体中
  4. struct netlink_skb_parms
    {
        struct ucred        creds;        /* Skb credentials    */
        __u32            pid;
        __u32            dst_pid;
        __u32            dst_group;
        kernel_cap_t        eff_cap;
        __u32            loginuid;    /* Login (audit) uid */
    };
    使用下面的宏获取skb_bff中的数据部分
  5. #define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))


    实例:

    这里我以路由中的netlink为例,看一下内核中的处理流程是怎么样的!在/kernel/net/core/rtnetlink.c文件中,有一个接收从用户空间过来的Netlink消息的函数。
  6. static void rtnetlink_rcv(struct sock *sk, int len)
    {
        unsigned int qlen = 0;
    
        do {
            rtnl_lock();
            netlink_run_queue(sk, &qlen, &rtnetlink_rcv_msg);
            up(&rtnl_sem);
    
            netdev_run_todo();
        } while (qlen);
    }
    上面的内核函数就是用来接收用户路由方面Netlink消息的,当我们使用route命令添加一条路由时,就会调用该函数接收。该函数是再netlink的初始化是注册的。同样在rtnetlink.c文件中。
  7. void __init rtnetlink_init(void)
    {
        int i;
    
        rtattr_max = 0;
        for (i = 0; i < ARRAY_SIZE(rta_max); i++)
            if (rta_max[i] > rtattr_max)
                rtattr_max = rta_max[i];
        rta_buf = kmalloc(rtattr_max * sizeof(struct rtattr *), GFP_KERNEL);
        if (!rta_buf)
            panic("rtnetlink_init: cannot allocate rta_buf\n");
    
        rtnl = netlink_kernel_create(NETLINK_ROUTE, RTNLGRP_MAX, rtnetlink_rcv,
         THIS_MODULE);//在创建内核的netlink时,注册了路由netlink的接收函数,rtnetlink_rcv.
        if (rtnl == NULL)
            panic("rtnetlink_init: cannot initialize rtnetlink\n");
        netlink_set_nonroot(NETLINK_ROUTE, NL_NONROOT_RECV);
        register_netdevice_notifier(&rtnetlink_dev_notifier);
        rtnetlink_links[PF_UNSPEC] = link_rtnetlink_table;
        rtnetlink_links[PF_PACKET] = link_rtnetlink_table;
    }
    netlink_kernel_create函数中,可以看到内核接收用户空间传过来的消息的接收函数,
  8. struct sock *
    netlink_kernel_create(int unit, unsigned int groups,
                          void (*input)(struct sock *sk, int len),
                          struct module *module)
    {
        struct socket *sock;
        struct sock *sk;
        struct netlink_sock *nlk;
    
        if (!nl_table)
            return NULL;
    
        if (unit<0 || unit>=MAX_LINKS)
            return NULL;
    
        if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
            return NULL;
    
        if (__netlink_create(sock, unit) < 0)
            goto out_sock_release;
    
        sk = sock->sk;
        sk->sk_data_ready = netlink_data_ready;
        if (input)
            nlk_sk(sk)->data_ready = input;//设置内核接收Netlink消息的函数,这里就是前面的rtnetlink_rcv函数
    
        if (netlink_insert(sk, 0))
            goto out_sock_release;
    
        nlk = nlk_sk(sk); //取得sock嵌入的netlink_sock结构体
        nlk->flags |= NETLINK_KERNEL_SOCKET;
    
        netlink_table_grab();
        nl_table[unit].groups = groups < 32 ? 32 : groups;
        nl_table[unit].module = module;
        nl_table[unit].registered = 1;// 更新netlink_table结构体信息,每中协议对应一个netlink_
    table结构
        netlink_table_ungrab();
    
        return sk;
    
    out_sock_release:
        sock_release(sock);
        return NULL;
    }
    到此,内核创建netlink到接收用户空间发送过来消息整个流程就清晰了。那当我们添加一条新路由时,在接收函数rtnetlink_rcv中的循环中,会从一个队列中调用实际的接收处理函数,这里为rtnetlink_rcv_msg函数。
  9. /**
     * nelink_run_queue - Process netlink receive queue.
     * @sk: Netlink socket containing the queue
     * @qlen: Place to store queue length upon entry
     * @cb: Callback function invoked for each netlink message found
     *
     * Processes as much as there was in the queue upon entry and invokes
     * a callback function for each netlink message found. The callback
     * function may refuse a message by returning a negative error code
     * but setting the error pointer to 0 in which case this function
     * returns with a qlen != 0.
     *
     * qlen must be initialized to 0 before the initial entry, afterwards
     * the function may be called repeatedly until qlen reaches 0.
     */
    void netlink_run_queue(struct sock *sk, unsigned int *qlen,
             int (*cb)(struct sk_buff *, struct nlmsghdr *, int *))
    {
        struct sk_buff *skb;
    
        if (!*qlen || *qlen > skb_queue_len(&sk->sk_receive_queue))
            *qlen = skb_queue_len(&sk->sk_receive_queue);
    
        for (; *qlen; (*qlen)--) {
            skb = skb_dequeue(&sk->sk_receive_queue);
            if (netlink_rcv_skb(skb, cb)) {
                if (skb->len)
                    skb_queue_head(&sk->sk_receive_queue, skb);
                else {
                    kfree_skb(skb);
                    (*qlen)--;
                }
                break;
            }
    
            kfree_skb(skb);
        }
    }

    下面是rtnetlink_rcv_msg()函数的实现,对netlink消息进行相应的处理。其中有一个数据结构

      struct rtnetlink_link *link; 其定义如下:是两个不同的处理函数

    struct rtnetlink_link
    {
        int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);
        int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);
    };
    /* Process one rtnetlink message. */
    
    static __inline__ int
    rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, int *errp)
    {
        struct rtnetlink_link *link;
        struct rtnetlink_link *link_tab;
        int sz_idx, kind;
        int min_len;
        int family;
        int type;
        int err;
    
        /* Only requests are handled by kernel now */
        if (!(nlh->nlmsg_flags&NLM_F_REQUEST))
            return 0;
        type = nlh->nlmsg_type;
        /* A control message: ignore them */
        if (type < RTM_BASE)
            return 0;
        /* Unknown message: reply with EINVAL */
        if (type > RTM_MAX)
            goto err_inval;
        type -= RTM_BASE;
        /* All the messages must have at least 1 byte length */
        if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(struct rtgenmsg)))
            return 0;
        family = ((struct rtgenmsg*)NLMSG_DATA(nlh))->rtgen_family;
        if (family >= NPROTO) {
            *errp = -EAFNOSUPPORT;
            return -1;
        }
    
        link_tab = rtnetlink_links[family];//根据用户空间传过来的不同德family类型,调用不同的处理函数,这里以路由为例的话为AF_ROUTE或者AF_NETLINK
        if (link_tab == NULL)
            link_tab = rtnetlink_links[PF_UNSPEC];
        link = &link_tab[type]; //根据不同的type调用不同的处理函数。这里的type为RTM_NEWROUTE
    
        sz_idx = type>>2;
        kind = type&3;
    
        if (kind != 2 && security_netlink_recv(skb)) {
            *errp = -EPERM;
            return -1;
        }
    
        if (kind == 2 && nlh->nlmsg_flags&NLM_F_DUMP) {
            if (link->dumpit == NULL)
                link = &(rtnetlink_links[PF_UNSPEC][type]);
    
            if (link->dumpit == NULL)
                goto err_inval;
    
            if ((*errp = netlink_dump_start(rtnl, skb, nlh,
                            link->dumpit, NULL)) != 0) {
                return -1;
            }
    
            netlink_queue_skip(nlh, skb);
            return -1;
        }
    
        memset(rta_buf, 0, (rtattr_max * sizeof(struct rtattr *)));
    
        min_len = rtm_min[sz_idx];
        if (nlh->nlmsg_len < min_len)
            goto err_inval;
    
        if (nlh->nlmsg_len > min_len) {
            int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(min_len);
            struct rtattr *attr = (void*)nlh + NLMSG_ALIGN(min_len);
    
            while (RTA_OK(attr, attrlen)) {
                unsigned flavor = attr->rta_type;
                if (flavor) {
                    if (flavor > rta_max[sz_idx])
                        goto err_inval;
                    rta_buf[flavor-1] = attr;
                }
                attr = RTA_NEXT(attr, attrlen);
            }
        }
    
        if (link->doit == NULL)
            link = &(rtnetlink_links[PF_UNSPEC][type]);
        if (link->doit == NULL)
            goto err_inval;
        err = link->doit(skb, nlh, (void *)&rta_buf[0]);//此处调用RTM_NEWROUTE,对应的route处理函数,也就是下面的inet6_rtm_newroute函数。
    
        *errp = err;
        return err;
    
    err_inval:
        *errp = -EINVAL;
        return -1;
    }
    int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
    {
        struct rtmsg *r = NLMSG_DATA(nlh);
        struct in6_rtmsg rtmsg;
    
        if (inet6_rtm_to_rtmsg(r, arg, &rtmsg))
            return -EINVAL;
        return ip6_route_add(&rtmsg, nlh, arg, &NETLINK_CB(skb));
    }
    inet6_rtm_newroute函数通过下面的数组进行了相应的注册处理,所以上面的link->doit(skb, nlh, (void *)&rta_buf[0])就是根据下面的这个调用的。
  10. static struct rtnetlink_link inet6_rtnetlink_table[RTM_NR_MSGTYPES] = {
        [RTM_GETLINK - RTM_BASE] = { .dumpit    = inet6_dump_ifinfo, },
        [RTM_NEWADDR - RTM_BASE] = { .doit    = inet6_rtm_newaddr, },
        [RTM_DELADDR - RTM_BASE] = { .doit    = inet6_rtm_deladdr, },
        [RTM_GETADDR - RTM_BASE] = { .dumpit    = inet6_dump_ifaddr, },
        [RTM_GETMULTICAST - RTM_BASE] = { .dumpit = inet6_dump_ifmcaddr, },
        [RTM_GETANYCAST - RTM_BASE] = { .dumpit    = inet6_dump_ifacaddr, },
        [RTM_NEWROUTE - RTM_BASE] = { .doit    = inet6_rtm_newroute, },
        [RTM_DELROUTE - RTM_BASE] = { .doit    = inet6_rtm_delroute, },
        [RTM_GETROUTE - RTM_BASE] = { .doit    = inet6_rtm_getroute,
                     .dumpit    = inet6_dump_fib, },
    };

    相关的结构体:

    内核中所有的netlink套接字存储在一个全局的哈新表中,该结构定义如下

    static struct netlink_table *nl_table;其中每个协议对应一个哈希表,所有的同一种协议的数

    据报散列在同哈希表中

    下面为一种协议所连接的哈希表结构:

    struct netlink_table {

             struct nl_pid_hash hash; // 根据pid进行HASH的netlink sock链表, 相当于客户端链表

             struct hlist_head mc_list; // 多播的sock链表

             unsigned int nl_nonroot; // 监听者标志

             unsigned int groups; // 每个netlink的协议类型可以定义多个组, 8的倍数,最小是32

             struct module *module;

             int registered;

    };最大可有MAX_LINKS(32)个表,处理不同协议类型的netlink套接口, 注意由于是自身的通信, 本机

    同时作为服务器和客户端, 服务端需要一个套接口对应, 每个客户端也要有一个套接口对应, 多个客户端的套接口形成一个链表.

    struct hlist_head *table; // 链表节点,每个桶中协议的sock连入其中,根据哈希值可得确定
    的sock
        unsigned long rehash_time; // 重新计算HASH的时间间隔
    
        unsigned int mask;
        unsigned int shift;
    
        unsigned int entries; // 链表节点数
        unsigned int max_shift; // 最大幂值
        u32 rnd; // 随机数
    };
    kernel/include/linux/Net.h
  11. struct proto_ops {
        int        family;
        struct module    *owner;
        int        (*release) (struct socket *sock);
        int        (*bind)     (struct socket *sock,
                     struct sockaddr *myaddr,
                     int sockaddr_len);
        int        (*connect) (struct socket *sock,
                     struct sockaddr *vaddr,
                     int sockaddr_len, int flags);
        int        (*socketpair)(struct socket *sock1,
                     struct socket *sock2);
        int        (*accept) (struct socket *sock,
                     struct socket *newsock, int flags);
        int        (*getname) (struct socket *sock,
                     struct sockaddr *addr,
                     int *sockaddr_len, int peer);
        unsigned int    (*poll)     (struct file *file, struct socket *sock,
                     struct poll_table_struct *wait);
        int        (*ioctl) (struct socket *sock, unsigned int cmd,
                     unsigned long arg);
        int        (*listen) (struct socket *sock, int len);
        int        (*shutdown) (struct socket *sock, int flags);
        int        (*setsockopt)(struct socket *sock, int level,
                     int optname, char __user *optval, int optlen);
        int        (*getsockopt)(struct socket *sock, int level,
                     int optname, char __user *optval, int __user *optlen);
        int        (*sendmsg) (struct kiocb *iocb, struct socket *sock,//netlink套接字实际的发送与接收函数
                     struct msghdr *m, size_t total_len);
        int        (*recvmsg) (struct kiocb *iocb, struct socket *sock,
                     struct msghdr *m, size_t total_len,
                     int flags);
        int        (*mmap)     (struct file *file, struct socket *sock,
                     struct vm_area_struct * vma);
        ssize_t        (*sendpage) (struct socket *sock, struct page *page,
                     int offset, size_t size, int flags);
    };

    下面我们看看,当我们使用route命令添加一个新的路由是,这个函数的调用顺序是怎么样的。下面是主要的函数;

    Dput()

    sys_sendmsg()//内核的接受函数

    new_inode()

    netlink_sendmsg//内核态接收用户态发送的数据

    rtnetlink_rcv()

    netlink_run_queue()

    rtnetlink_rcv_msg()

    inet6_rtm_newroute()

    在kernel/net/netlink/af_netlink.c文件中,内核态接收用户态发送的数据,在netlink_sendskb函数中调用sock的队列,执行相应的netlink接收函数

    static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock,
                 struct msghdr *msg, size_t len)
    {
        struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
        struct sock *sk = sock->sk;
        struct netlink_sock *nlk = nlk_sk(sk);
        struct sockaddr_nl *addr=msg->msg_name;
        u32 dst_pid;
        u32 dst_group;
        struct sk_buff *skb;
        int err;
        struct scm_cookie scm;
    
        if (msg->msg_flags&MSG_OOB)
            return -EOPNOTSUPP;
    
        if (NULL == siocb->scm)
            siocb->scm = &scm;
        err = scm_send(sock, msg, siocb->scm);
        if (err < 0)
            return err;
    
        if (msg->msg_namelen) {
            if (addr->nl_family != AF_NETLINK)
                return -EINVAL;
            dst_pid = addr->nl_pid;
            dst_group = ffs(addr->nl_groups);
            if (dst_group && !netlink_capable(sock, NL_NONROOT_SEND))
                return -EPERM;
        } else {
            dst_pid = nlk->dst_pid;
            dst_group = nlk->dst_group;
        }
    
        if (!nlk->pid) {
            err = netlink_autobind(sock);
            if (err)
                goto out;
        }
    
        err = -EMSGSIZE;
        if (len > sk->sk_sndbuf - 32)
            goto out;
        err = -ENOBUFS;
        skb = alloc_skb(len, GFP_KERNEL);// 分配一个sk_buff结构,将msghdr结构转化为sk_buff结构
        if (skb==NULL)
            goto out;
    
        NETLINK_CB(skb).pid    = nlk->pid;//填写本地的pid信息
        NETLINK_CB(skb).dst_pid = dst_pid;
        NETLINK_CB(skb).dst_group = dst_group;
        NETLINK_CB(skb).loginuid = audit_get_loginuid(current->audit_context);
        memcpy(NETLINK_CREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
    
        /* What can I do? Netlink is asynchronous, so that
         we will have to save current capabilities to
         check them, when this message will be delivered
         to corresponding kernel module. --ANK (980802)
         */
    
        err = -EFAULT;
    //数据拷贝进sk_buff中
        if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len)) {
            kfree_skb(skb);
            goto out;
        }
    
        err = security_netlink_send(sk, skb);
        if (err) {
            kfree_skb(skb);
            goto out;
        }
    
        if (dst_group) {
            atomic_inc(&skb->users);
            netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL);
        }
        err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
    
    out:
        return err;
    }
































  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值