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 msghdrstruct 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;
    }
































發佈了64 篇原創文章 · 獲贊 69 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章