linux netlink詳解2-netlink通信詳解


1主要數據

定義位於:linux-4.9.73\include\uapi\linux\netlink.h

1.1 struct nlmsghdr

表示netlink消息報頭。netlink消息同TCP/UDP消息一樣,也需要遵循協議要求的格式,每個netlink消息的開頭是固定長度的netlink報頭,報頭后才是實際的載荷。netlink報頭一共占16個字節,具體內容即同struct nlmsghdr中定義的一樣。

1 struct nlmsghdr {
2     __u32        nlmsg_len;    /* Length of message including header */
3     __u16        nlmsg_type;    /* Message content */
4     __u16        nlmsg_flags;    /* Additional flags */
5     __u32        nlmsg_seq;    /* Sequence number */
6     __u32        nlmsg_pid;    /* Sending process port ID */
7 };

(1)nlmsg_len:整個netlink消息的長度(包含消息頭);

(2)nlmsg_type:消息狀態,內核在include/uapi/linux/netlink.h中定義了以下4種通用的消息類型,它們分別是:

1 NLMSG_NOOP:不執行任何動作,必須將該消息丟棄;
2 NLMSG_ERROR:消息發生錯誤;
3 NLMSG_DONE:標識分組消息的末尾;
4 NLMSG_OVERRUN:緩沖區溢出,表示某些消息已經丟失。

除了這4種類型的消息以外,不同的netlink協議也可以自行添加自己所特有的消息類型,但是內核定義了類型保留宏(#define NLMSG_MIN_TYPE 0x10),即小於該值的消息類型值由內核保留,不可用。

(3)nlmsg_flags:消息標記,它們用以表示消息的類型,同樣定義在include/uapi/linux/netlink.h中:

 1 #define NLM_F_REQUEST        1    /* It is request message.     */
 2 #define NLM_F_MULTI        2    /* Multipart message, terminated by NLMSG_DONE */
 3 #define NLM_F_ACK        4    /* Reply with ack, with zero or error code */
 4 #define NLM_F_ECHO        8    /* Echo this request         */
 5 #define NLM_F_DUMP_INTR        16    /* Dump was inconsistent due to sequence change */
 6  
 7 /* Modifiers to GET request */
 8 #define NLM_F_ROOT    0x100    /* specify tree    root    */
 9 #define NLM_F_MATCH    0x200    /* return all matching    */
10 #define NLM_F_ATOMIC    0x400    /* atomic GET        */
11 #define NLM_F_DUMP    (NLM_F_ROOT|NLM_F_MATCH)
12  
13 /* Modifiers to NEW request */
14 #define NLM_F_REPLACE    0x100    /* Override existing        */
15 #define NLM_F_EXCL    0x200    /* Do not touch, if it exists    */
16 #define NLM_F_CREATE    0x400    /* Create, if it does not exist    */
17 #define NLM_F_APPEND    0x800    /* Add to end of list        */
(4)nlmsg_seq:消息序列號,用以將消息排隊,有些類似TCP協議中的序號(不完全一樣),但是netlink的這個字段是可選的,不強制使用;
(5)nlmsg_pid:發送端口的ID號,對於內核來說該值就是0,對於用戶進程來說就是其socket所綁定的ID號。
1.2 struct msghdr

 表示socket消息數據包結構。

1 struct msghdr {
2     void        *msg_name;    /* ptr to socket address structure */
3     int        msg_namelen;    /* size of socket address structure */
4     struct iov_iter    msg_iter;    /* data */
5     void        *msg_control;    /* ancillary data */
6     __kernel_size_t    msg_controllen;    /* ancillary data buffer length */
7     unsigned int    msg_flags;    /* flags on received message */
8     struct kiocb    *msg_iocb;    /* ptr to iocb for async requests */
9 };
應用層向內核傳遞消息可以使用sendto()或sendmsg()函數,其中sendmsg函數需要應用程序手動封裝msghdr消息結構,而sendto()函數則會由內核代為分配。其中
(1)msg_name:指向數據包的目的地址;
(2)msg_namelen:目的地址數據結構的長度;
(3)msg_iov:消息包的實際數據塊,定義如下:
1 struct iovec
2 {
3     void *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */
4     __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
5 };
iov_base:消息包實際載荷的首地址;
iov_len:消息實際載荷的長度。
(4)msg_control:消息的輔助數據;
(5)msg_controllen:消息輔助數據的大小;
(6)msg_flags:接收消息的標識。
對於該結構,我們更需要關注的是前三個變量參數,對於netlink數據包來說其中msg_name指向的就是目的sockaddr_nl地址結構實例的首地址,iov_base指向的就是消息實體中的nlmsghdr消息頭的地址,而iov_len賦值為nlmsghdr中的nlmsg_len即可(消息頭+實際數據)。
1.3 netlink消息處理宏
 1 #define NLMSG_ALIGNTO    4U
 2 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )/* 對len執行4字節對齊 */
 3 #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))/* netlink消息頭長度 */
 4 #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)                                /* netlink消息載荷len加上消息頭 */
 5 #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))                            /* 對netlink消息全長執行字節對齊 */
 6 #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))/* 獲取netlink消息實際載荷位置 */
 7 #define NLMSG_NEXT(nlh,len)     ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
 8                   (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))/*取得下一個消息的首地址,同時len也減少為剩余消息的總長度 */
 9 #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
10                (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
11                (nlh)->nlmsg_len <= (len))                                            /* 驗證消息的長度 */
12 #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))/* 返回PAYLOAD的長度 */

 Linux為了處理netlink消息方便,在 include/uapi/linux/netlink.h中定義了以上消息處理宏,用於各種場合。對於Netlink消息來說,處理如下格式見linux-4.9.73\include\net\netlink.h:

/* ========================================================================
 *         Netlink Messages and Attributes Interface (As Seen On TV)
 * ------------------------------------------------------------------------
 *                          Messages Interface
 * ------------------------------------------------------------------------
 *
 * Message Format:
 *    <--- nlmsg_total_size(payload)  --->
 *    <-- nlmsg_msg_size(payload) ->
 *   +----------+- - -+-------------+- - -+-------- - -
 *   | nlmsghdr | Pad |   Payload   | Pad | nlmsghdr
 *   +----------+- - -+-------------+- - -+-------- - -
 *   nlmsg_data(nlh)---^                   ^
 *   nlmsg_next(nlh)-----------------------+
 *
 * Payload Format:
 *    <---------------------- nlmsg_len(nlh) --------------------->
 *    <------ hdrlen ------>       <- nlmsg_attrlen(nlh, hdrlen) ->
 *   +----------------------+- - -+--------------------------------+
 *   |     Family Header    | Pad |           Attributes           |
 *   +----------------------+- - -+--------------------------------+
 *   nlmsg_attrdata(nlh, hdrlen)---^
 *

首先最上層,一個netlink消息有netlink消息頭和netlink消息載荷組成,它們之間存在內存對齊的pad留空空間;然后往下一級消息的實際載荷又可分為family頭級具體的消息屬性,其中family頭針對不同協議種類的netlink定義各部相同;到最底層消息屬性又分為消息屬性頭和實際的消息載荷。

2 應用層向內核發送消息

應用層向內核發送消息是通過函數sendmsg,定義位於:linux-4.9.73\net\socket.c,對應的系統調用如下:

2.1 函數sendmsg

 1 SYSCALL_DEFINE3(sendmsg, int, fd, struct user_msghdr __user *, msg, unsigned int, flags)
 2 {
 3     if (flags & MSG_CMSG_COMPAT)
 4         return -EINVAL;
 5     return __sys_sendmsg(fd, msg, flags);
 6 }
 7 
 8 long __sys_sendmsg(int fd, struct user_msghdr __user *msg, unsigned flags)
 9 {
10     int fput_needed, err;
11     struct msghdr msg_sys;
12     struct socket *sock;
13 
14     sock = sockfd_lookup_light(fd, &err, &fput_needed);//通過fd描述符找到對應的socket套接字結構實例
15     if (!sock)
16         goto out;
17 
18     err = ___sys_sendmsg(sock, msg, &msg_sys, flags, NULL, 0);//----- 詳解1
19 
20     fput_light(sock->file, fput_needed);
21 out:
22     return err;
23 }

 詳解1:傳入的參數中第三個和最后一個需要關注一下,其中第三個它是一個內核版的socket消息數據包結構,同應用層的略有不同,定義如下:

1 struct msghdr {
2     void        *msg_name;    /* 同應用層 */
3     int        msg_namelen;    /* 同應用層 */
4     struct iov_iter    msg_iter;    /*為msg_iov和msg_iovlen的合體 */
5     void        *msg_control;    /* 同應用層 */
6     __kernel_size_t    msg_controllen;    /* 同應用層 */
7     unsigned int    msg_flags;    /* 同應用層 */
8     struct kiocb    *msg_iocb;    /*用於異步請求*/
9 };

函數__sendmsg參數是一個struct used_address結構體指針,這個結構體定義如下:

1 struct used_address {
2     struct sockaddr_storage name;
3     unsigned int name_len;
4 };

這里的name字段用來存儲消息的地址,name_len字段是消息地址的長度,它們同struct msghdr結構體的前兩個字段一致。該結構體主要用與sendmmsg系統調用(用於同事時向一個socket地址發送多個數據包,可以避免重復的網絡security檢查,從而提高發送效率)保存多個數據包的目的地址。現在這里設置為NULL,表示不使用。

2.2  函數__sendmsg

 1 static int ___sys_sendmsg(struct socket *sock, struct user_msghdr __user *msg,
 2              struct msghdr *msg_sys, unsigned int flags,
 3              struct used_address *used_address,
 4              unsigned int allowed_msghdr_flags)
 5 {
 6     struct compat_msghdr __user *msg_compat =
 7         (struct compat_msghdr __user *)msg;
 8     struct sockaddr_storage address;
 9     struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;------ 詳解1
10     unsigned char ctl[sizeof(struct cmsghdr) + 20]
11         __attribute__ ((aligned(sizeof(__kernel_size_t))));
12     /* 20 is size of ipv6_pktinfo */
13     unsigned char *ctl_buf = ctl;
14     int ctl_len;
15     ssize_t err;
16 
17     msg_sys->msg_name = &address;
18 
19     if (MSG_CMSG_COMPAT & flags)----- 詳解2
20         err = get_compat_msghdr(msg_sys, msg_compat, NULL, &iov);
21     else
22         err = copy_msghdr_from_user(msg_sys, msg, NULL, &iov);
23     if (err < 0)
24         return err;
25 
26     err = -ENOBUFS;
27 
28     if (msg_sys->msg_controllen > INT_MAX)
29         goto out_freeiov;
30     flags |= (msg_sys->msg_flags & allowed_msghdr_flags);
31     ctl_len = msg_sys->msg_controllen;
32     if ((MSG_CMSG_COMPAT & flags) && ctl_len) {
33         err =
34             cmsghdr_from_user_compat_to_kern(msg_sys, sock->sk, ctl,
35                              sizeof(ctl));
36         if (err)
37             goto out_freeiov;
38         ctl_buf = msg_sys->msg_control;
39         ctl_len = msg_sys->msg_controllen;
40     } else if (ctl_len) {
41         if (ctl_len > sizeof(ctl)) {
42             ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL);
43             if (ctl_buf == NULL)
44                 goto out_freeiov;
45         }
46         err = -EFAULT;
47         /*
48          * Careful! Before this, msg_sys->msg_control contains a user pointer.
49          * Afterwards, it will be a kernel pointer. Thus the compiler-assisted
50          * checking falls down on this.
51          */
52         if (copy_from_user(ctl_buf,
53                    (void __user __force *)msg_sys->msg_control,
54                    ctl_len))
55             goto out_freectl;
56         msg_sys->msg_control = ctl_buf;
57     }
58     msg_sys->msg_flags = flags;//保存用戶傳遞的flag標識
59 
60     if (sock->file->f_flags & O_NONBLOCK)//如果當前的socket已經被配置為非阻塞模式則置位MSG_DONTWAIT標識
61         msg_sys->msg_flags |= MSG_DONTWAIT;
62     /*
63      * If this is sendmmsg() and current destination address is same as
64      * previously succeeded address, omit asking LSM's decision.
65      * used_address->name_len is initialized to UINT_MAX so that the first
66      * destination address never matches.
67      */   ------------------ 詳解3 
68     if (used_address && msg_sys->msg_name &&
69         used_address->name_len == msg_sys->msg_namelen &&
70         !memcmp(&used_address->name, msg_sys->msg_name,
71             used_address->name_len)) {
72         err = sock_sendmsg_nosec(sock, msg_sys);
73         goto out_freectl;
74     }
75     err = sock_sendmsg(sock, msg_sys);
76     /*
77      * If this is sendmmsg() and sending to current destination address was
78      * successful, remember it.
79      */
80     if (used_address && err >= 0) {----------- 詳解4
81         used_address->name_len = msg_sys->msg_namelen;
82         if (msg_sys->msg_name)
83             memcpy(&used_address->name, msg_sys->msg_name,
84                    used_address->name_len);
85     }
86 
87 out_freectl:
88     if (ctl_buf != ctl)
89         sock_kfree_s(sock->sk, ctl_buf, ctl_len);
90 out_freeiov:
91     kfree(iov);
92     return err;
93 }

詳解1:這里的iovstack數組是用來加速用戶數據拷貝的(這里假定用戶數據的iovec個數通常不會超過UIO_FASTIOV個,如果超過會通過kmalloc分配內存)。

詳解2:判斷flag中是否設置了32bit修正標識,從前文中系統調用的入口處已經可以看出了,這里顯然不會設置該標識位,所以這里調用copy_msghdr_from_user函數將用戶空間傳入的消息(struct user_msghdr __user *msg)安全的拷貝到內核空間中(struct msghdr *msg_sys),函數定義如下:

 1 static int copy_msghdr_from_user(struct msghdr *kmsg,
 2                  struct user_msghdr __user *umsg,
 3                  struct sockaddr __user **save_addr,
 4                  struct iovec **iov)
 5 {
 6     struct sockaddr __user *uaddr;
 7     struct iovec __user *uiov;
 8     size_t nr_segs;
 9     ssize_t err;
10         /*調用access_ok檢查用戶數據的有效性,調用__get_user函數執行單數據的復制操作(並沒有復制數據包內容)*/
11     if (!access_ok(VERIFY_READ, umsg, sizeof(*umsg)) ||
12         __get_user(uaddr, &umsg->msg_name) ||
13         __get_user(kmsg->msg_namelen, &umsg->msg_namelen) ||
14         __get_user(uiov, &umsg->msg_iov) ||
15         __get_user(nr_segs, &umsg->msg_iovlen) ||
16         __get_user(kmsg->msg_control, &umsg->msg_control) ||
17         __get_user(kmsg->msg_controllen, &umsg->msg_controllen) ||
18         __get_user(kmsg->msg_flags, &umsg->msg_flags))
19         return -EFAULT;
20 
21     if (!uaddr)
22         kmsg->msg_namelen = 0;
23 
24     if (kmsg->msg_namelen < 0)
25         return -EINVAL;
26 
27     if (kmsg->msg_namelen > sizeof(struct sockaddr_storage))
28         kmsg->msg_namelen = sizeof(struct sockaddr_storage);
29 
30     if (save_addr)//
31         *save_addr = uaddr;
32 
33     if (uaddr && kmsg->msg_namelen) {
34         if (!save_addr) { -------------- 詳解1
35             err = move_addr_to_kernel(uaddr, kmsg->msg_namelen,
36                           kmsg->msg_name);
37             if (err < 0)
38                 return err;
39         }
40     } else {
41         kmsg->msg_name = NULL;
42         kmsg->msg_namelen = 0;
43     }
44 
45     if (nr_segs > UIO_MAXIOV)
46         return -EMSGSIZE;
47 
48     kmsg->msg_iocb = NULL;
49 
50     return import_iovec(save_addr ? READ : WRITE, uiov, nr_segs,------- 詳解2
51                 UIO_FASTIOV, iov, &kmsg->msg_iter);
52 }

函數copy_msghdr_from_user詳解1:如果用戶消息中存在目的地址且入參save_addr為空(當前情景中正好就是這類情況),就調用move_addr_to_kernel()函數將消息地址拷貝到內核kmsg的結構中,否則將kmsg中的目的地址和長度字段置位空。接下來判斷消息實際載荷iovec結構的個數,這里UIO_MAXIOV值定義為1024,也就是說消息數據iovec結構的最大個數不能超過這個值。

函數copy_msghdr_from_user 詳解2:調用import_iovec()函數開始執行實際數據從用戶態向內核態的拷貝動作(注意這里並沒有拷貝用戶空間實際消息載荷數據,僅僅檢查了用戶地址有效性並拷貝了長度等字段),在拷貝完成后,&kmsg->msg_iter中的數據初始化情況如下:

int type:WRITE;
size_t iov_offset:初始化為0;
size_t count:所有iovec結構數據的總長度(即iov->iov_len的總和);
const struct iovec *iov:首個iov結構指針;
unsigned long nr_segs:iovec結構的個數。

函數__sendmsg詳解3:根據傳入的used_address指針判斷當前發送消息的目的地址是否同它記錄的一致,如果一致則調用sock_sendmsg_nosec()函數發送數據,否則調用sock_sendmsg()函數發送數據,sock_sendmsg()其實最終也是通過調用sock_sendmsg_nosec()來發送數據的,它們的區別就在於是否調用安全檢查函數,如下:

1 int sock_sendmsg(struct socket *sock, struct msghdr *msg)
2 {
3     int err = security_socket_sendmsg(sock, msg,
4                       msg_data_left(msg));
5 
6     return err ?: sock_sendmsg_nosec(sock, msg);
7 }

函數函數__sendmsg詳解4:在sendmmsg系統調用每一次發送多個消息時,由於發送的目的地一般都是一致的,所以只需要在發送第一個消息爆時執行檢查就可以了,通過這種策略就可以加速數據的發送。最后,在發送完數據后,如果傳入的used_address指針非空,就會將本次成功發送數據的目的地址記錄下來,供下次發送數據比較。

2.3 函數sock_sendmsg_nosec

1 static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
2 {
3     int ret = sock->ops->sendmsg(sock, msg, msg_data_left(msg));
4     BUG_ON(ret == -EIOCBQUEUED);
5     return ret;
6 }

這里調用了socket所綁定協議特有的數據發送鈎子函數,其中最后一個參數為msg->msg_iter->count,即消息實際載荷的總長度。在前一篇文章中已經看到了對於netlink類型的套接字來說該函數被注冊為netlink_sendmsg()。

函數netlink_sendmsg:

 1 static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 2 {
 3     struct sock *sk = sock->sk;
 4     struct netlink_sock *nlk = nlk_sk(sk);
 5     DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);//定義了一個struct sockaddr_nl *addr指針,它指向了msg->msg_name表示消息的目的地址(會做地址長度檢查)
 6     u32 dst_portid;
 7     u32 dst_group;
 8     struct sk_buff *skb;
 9     int err;
10     struct scm_cookie scm;
11     u32 netlink_skb_flags = 0;
12 
13     if (msg->msg_flags&MSG_OOB)
14         return -EOPNOTSUPP;
15 
16     err = scm_send(sock, msg, &scm, true);//發送消息輔助數據
17     if (err < 0)
18         return err;
19 
20     if (msg->msg_namelen) {--------------- 詳解1
21         err = -EINVAL;
22         if (addr->nl_family != AF_NETLINK)
23             goto out;
24         dst_portid = addr->nl_pid;
25         dst_group = ffs(addr->nl_groups);
26         err =  -EPERM;
27         if ((dst_group || dst_portid) &&
28             !netlink_allowed(sock, NL_CFG_F_NONROOT_SEND))
29             goto out;
30         netlink_skb_flags |= NETLINK_SKB_DST;
31     } else {
32         dst_portid = nlk->dst_portid;
33         dst_group = nlk->dst_group;
34     }
35 
36     if (!nlk->bound) {  --------------------- 詳解2
37         err = netlink_autobind(sock);
38         if (err)
39             goto out;
40     } else {
41         /* Ensure nlk is hashed and visible. */
42         smp_rmb();
43     }
44 
45     err = -EMSGSIZE;
46     if (len > sk->sk_sndbuf - 32)------------------- 詳解3
47         goto out;
48     err = -ENOBUFS;
49     skb = netlink_alloc_large_skb(len, dst_group);
50     if (skb == NULL)
51         goto out;
52 
53     NETLINK_CB(skb).portid    = nlk->portid;  ------------------ 詳解4
54     NETLINK_CB(skb).dst_group = dst_group;
55     NETLINK_CB(skb).creds    = scm.creds;
56     NETLINK_CB(skb).flags    = netlink_skb_flags;
57 
58     err = -EFAULT;
59     if (memcpy_from_msg(skb_put(skb, len), msg, len)) {
60         kfree_skb(skb);
61         goto out;
62     }
63 
64     err = security_netlink_send(sk, skb);----------------- 詳解5
65     if (err) {
66         kfree_skb(skb);
67         goto out;
68     }
69 
70     if (dst_group) {
71         atomic_inc(&skb->users);
72         netlink_broadcast(sk, skb, dst_portid, dst_group, GFP_KERNEL);//組播方式
73     }
74     err = netlink_unicast(sk, skb, dst_portid, msg->msg_flags&MSG_DONTWAIT);//單播方式
75 
76 out:
77     scm_destroy(&scm);
78     return err;
79 }

 詳解1:這里如果用戶指定了netlink消息的目的地址,則對其進行校驗,然后判斷當前netlink協議的NL_CFG_F_NONROOT_SEND標識是否設置,如果設置了該標識則允許非root用戶發送組播,對於NETLINK_ROUTE類型的netlink套接字,並沒有設置該標識,表明非root用戶不能發送組播消息;然后設置NETLINK_SKB_DST標識。如果用戶沒有指定netlink消息的目的地址,則使用netlink套接字默認的(該值默認為0,會在調用connect系統調用時在netlink_connect()中被賦值為用戶設置的值)。注意這里dst_group經過ffs的處理后轉化為組播地址位數(找到最低有效位)。

詳解2:接下來判斷當前的netlink套接字是否被綁定過,如果沒有綁定過這里調用netlink_autobind()進行動態綁定,該函數在前一篇文章中已經分析

詳解3:接下來判斷需要發送的數據是否過長(長於發送緩存大小),然后通過netlink_alloc_large_skb分配skb結構(傳入的參數為消息載荷的長度以及組播地址)。

詳解4:在成功創建skb結構之后,這里就開始初始化它,這里使用到了skb中的擴展cb字段(char cb[48] __aligned(8),一共48個字節用於存放netlink的地址和標識相關的附加信息足夠了),同時使用宏NETLINK_CB來操作這些字段。netlink將skb的cb字段強制定義為struct netlink_skb_parms結構:

1 struct netlink_skb_parms {
2     struct scm_creds    creds;        /* Skb credentials    */
3     __u32            portid;
4     __u32            dst_group;
5     __u32            flags;
6     struct sock        *sk;
7 };
其中portid表示原端套接字所綁定的id,dst_group表示消息目的組播地址,flag為標識,sk指向原端套接字的sock結構。
這里首先將套接字綁定的portid賦值到skb得cb字段中、同時設置組播地址的數量以及netlink_skb標識(這里是已經置位NETLINK_SKB_DST)。接下來調用最關鍵的調用memcpy_from_msg拷貝數據,它首先調用skb_put調整skb->tail指針,然后執行copy_from_iter(data, len, &msg->msg_iter)將數據從msg->msg_iter中傳輸到skb->data中(這是第一次內存拷貝動作!將用戶空間數據直接拷貝到內核skb中)。

詳解5:調用security_netlink_send()執行security檢查,最后如果是組播發送則調用netlink_broadcast()發送消息,否則調用netlink_unicast()發送單播消息。

2.4 函數netlink_unicast()

發送單播消息。

 1 int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
 2             u32 portid, int nonblock)
 3 {
 4     struct sock *sk;
 5     int err;
 6     long timeo;
 7 
 8     skb = netlink_trim(skb, gfp_any());---------- 詳解1
 9 
10     timeo = sock_sndtimeo(ssk, nonblock); ---------------- 詳解2
11 retry:
12     sk = netlink_getsockbyportid(ssk, portid);--------------- 詳解3
13     if (IS_ERR(sk)) {
14         kfree_skb(skb);
15         return PTR_ERR(sk);
16     }
17     if (netlink_is_kernel(sk))-------------- 詳解4
18         return netlink_unicast_kernel(sk, skb, ssk); ------------ 詳解5
19 
20     if (sk_filter(sk, skb)) {
21         err = skb->len;
22         kfree_skb(skb);
23         sock_put(sk);
24         return err;
25     }
26 
27     err = netlink_attachskb(sk, skb, &timeo, ssk);
28     if (err == 1)
29         goto retry;
30     if (err)
31         return err;
32 
33     return netlink_sendskb(sk, skb);
34 }

詳解1:調用netlink_trim()重新裁剪skb的數據區的大小,這可能會clone出一個新的skb結構同時重新分配skb->data的內存空間(這就出現了第三次的內存拷貝動作!),當然如果原本skb中多余的內存數據區非常小或者該內存空間是在vmalloc空間中的就不會執行上述操作,我們現在跟隨的情景上下文中就是后一種情況,並不會重新分配空間。

詳解2:記下發送超時等待時間,如果已經設置了MSG_DONTWAIT標識,則等待時間為0,否則返回sk->sk_sndtimeo(該值在sock初始化時由sock_init_data()函數賦值為MAX_SCHEDULE_TIMEOUT)。

詳解3:接下來調用netlink_getsockbyportid根據目的portid號和原端sock結構查找目的端的sock結構。其定義如下:

 1 static struct sock *netlink_getsockbyportid(struct sock *ssk, u32 portid)
 2 {
 3     struct sock *sock;
 4     struct netlink_sock *nlk;
 5   /*調用netlink_lookup執行查找工作,查找的命名空間和協議號同原端sock,它會從nl_table[protocol]的哈希表中找到已經注冊的目的端sock套接字。找到以后執行校驗,如若找到的socket已經connect了,則它的目的portid必須是原端的portid*/
 6     sock = netlink_lookup(sock_net(ssk), ssk->sk_protocol, portid);
 7     if (!sock)
 8         return ERR_PTR(-ECONNREFUSED);
 9 
10     /* Don't bother queuing skb if kernel socket has no input function */
11     nlk = nlk_sk(sock);
12     if (sock->sk_state == NETLINK_CONNECTED &&
13         nlk->dst_portid != nlk_sk(ssk)->portid) {
14         sock_put(sock);
15         return ERR_PTR(-ECONNREFUSED);
16     }
17     return sock;
18 }

詳解4:判斷目的的netlink socket是否是內核的netlink socket,如果目的地址是內核空間,則調用netlink_unicast_kernel向內核進行單播,入參是目的sock、原端sock和數據skb。目前目的地址是內核。

注意對比以下4.1節。

函數netlink_is_kernel定義如下:

1 static inline int netlink_is_kernel(struct sock *sk)
2 {
3     return nlk_sk(sk)->flags & NETLINK_F_KERNEL_SOCKET;
4 }

詳解5:函數netlink_unicast_kernel定義如下:

檢查目標netlink套接字是否注冊了netlink_rcv()接收函數,如果沒有則直接丟棄該數據包,否則繼續發送流程,這里首先設置一些標識:
skb->sk = sk;     /* 將目的sock賦值給skb->sk指針 */
skb->destructor = netlink_skb_destructor;   /* 注冊destructor鈎子函數 */
NETLINK_CB(skb).sk = ssk;   /* 將原端的sock保存早skb的cb擴展字段中 */
最后就調用了nlk->netlink_rcv(skb)函數將消息送到內核中的目的netlink套接字中了。在前一篇文章中已經看到在內核注冊netlink套接字的時候已經將其接收函數注冊到了netlink_rcv中:
 1 static int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb,
 2                   struct sock *ssk)
 3 {
 4     int ret;
 5     struct netlink_sock *nlk = nlk_sk(sk);
 6 
 7     ret = -ECONNREFUSED;
 8     if (nlk->netlink_rcv != NULL) {
 9         ret = skb->len;
10         netlink_skb_set_owner_r(skb, sk);
11         NETLINK_CB(skb).sk = ssk;
12         netlink_deliver_tap_kernel(sk, ssk, skb);
13         nlk->netlink_rcv(skb);
14         consume_skb(skb);
15     } else {
16         kfree_skb(skb);
17     }
18     sock_put(sk);
19     return ret;
20 }

 netlink_rcv的定義賦值:

1 struct sock *
2 __netlink_kernel_create(struct net *net, int unit, struct module *module,
3             struct netlink_kernel_cfg *cfg)
4 {
5     ......
6     if (cfg && cfg->input)
7         nlk_sk(sk)->netlink_rcv = cfg->input;
8   ...
9 }

對於NETLINK_ROUTE類型的套接字來說就是rtnetlink_rcv了,netlink_rcv()鈎子函數會接收並解析用戶傳下來的數據,不同類型的netlink協議各不相同,這里就不進行分析了。至此應用層下發單播的netlink數據就下發完成了。???待分析

2.5 函數netlink_broadcast()

發送組播消息。定義如下:

1 int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,
2               u32 group, gfp_t allocation)
3 {
4     return netlink_broadcast_filtered(ssk, skb, portid, group, allocation,
5         NULL, NULL);
6 }

函數netlink_broadcast_filtered

 1 int netlink_broadcast_filtered(struct sock *ssk, struct sk_buff *skb, u32 portid,
 2     u32 group, gfp_t allocation,
 3     int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data),
 4     void *filter_data)
 5 {
 6     struct net *net = sock_net(ssk);
 7     struct netlink_broadcast_data info;
 8     struct sock *sk;
 9 
10     skb = netlink_trim(skb, allocation);
11        //初始化netlink組播數據結構netlink_broadcast_data
12     info.exclude_sk = ssk;
13     info.net = net;
14     info.portid = portid;
15     info.group = group;//保存了目的組播地址
16     info.failure = 0;
17     info.delivery_failure = 0;
18     info.congested = 0;
19     info.delivered = 0;
20     info.allocation = allocation;
21     info.skb = skb;
22     info.skb2 = NULL;
23     info.tx_filter = filter;
24     info.tx_data = filter_data;
25 
26     /* While we sleep in clone, do not allow to change socket list */
27 
28     netlink_lock_table();
29 
30     sk_for_each_bound(sk, &nl_table[ssk->sk_protocol].mc_list)//從nl_table[ssk->sk_protocol].mc_list里邊查找加入組播組的socket
31         do_one_broadcast(sk, &info);//依次發送組播數據
32 
33     consume_skb(skb);
34 
35     netlink_unlock_table();
36 
37     if (info.delivery_failure) {
38         kfree_skb(info.skb2);
39         return -ENOBUFS;
40     }
41     consume_skb(info.skb2);
42 
43     if (info.delivered) {
44         if (info.congested && gfpflags_allow_blocking(allocation))
45             yield();
46         return 0;
47     }
48     return -ESRCH;
49 }

函數do_one_broadcast

 1 static void do_one_broadcast(struct sock *sk,
 2                     struct netlink_broadcast_data *p)
 3 {
 4     struct netlink_sock *nlk = nlk_sk(sk);
 5     int val;
 6        //做必要的檢查
 7     if (p->exclude_sk == sk)
 8         return;
 9     ...
10     val = netlink_broadcast_deliver(sk, p->skb2);//對目的sock發送數據skb
11     ...
12 }

函數netlink_broadcast_deliver

 1 static int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
 2 {
 3     struct netlink_sock *nlk = nlk_sk(sk);
 4 
 5     if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
 6         !test_bit(NETLINK_S_CONGESTED, &nlk->state)) {
 7         netlink_skb_set_owner_r(skb, sk);
 8         __netlink_sendskb(sk, skb);
 9         return atomic_read(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1);
10     }
11     return -1;
12 }
13 
14 static int __netlink_sendskb(struct sock *sk, struct sk_buff *skb)
15 {
16     int len = skb->len;
17 
18     netlink_deliver_tap(skb);
19 
20     skb_queue_tail(&sk->sk_receive_queue, skb);//將要發送的skb添加到目的sock的接收隊列末尾
21     sk->sk_data_ready(sk);//通知鈎子函數,告知目的sock有數據到達,執行處理流程    ----- 補充1
22     return len;
23 }

補充1:對於內核的netlink來說內核netlink的創建函數中已經將其注冊為:

 1 struct sock *
 2 __netlink_kernel_create(struct net *net, int unit, struct module *module,
 3             struct netlink_kernel_cfg *cfg)
 4 {
 5     ......
 6     sk->sk_data_ready = netlink_data_ready;
 7     ......
 8 }
 9 
10 
11 static void netlink_data_ready(struct sock *sk)
12 {
13     BUG();
14 }

非常明顯了,內核netlink套接字是無論如何也不應該接收到組播消息的。但是對於應用層netlink套接字,該sk_data_ready()鈎子函數在初始化netlink函數sock_init_data()中被注冊為sock_def_readable(),這個函數待分析。

3 內核接收應用層的消息

當進程有數據發送過來時,內核部分會接收數據,上送的包是struct sk_buff *skb,我們可以通過netlink提供的一系列操作函數來獲取消息頭以及數據。
消息頭 = nlmsg_hdr(skb);
消息數據 = NLMSG_DATA(nlh);

4 內核向應用層發送消息

4.1 內核發送單播消息

內核可以通過nlmsg_unicast()函數向應用層發送單播消息,由各個netlink協議負責調用,也有的協議是直接調用netlink_unicast()函數,其實nlmsg_unicast()也僅是netlink_unicast()的一個封裝而已:

函數nlmsg_unicast定義位於:linux-4.9.73\include\net\Netlink.h

 1 static inline int nlmsg_unicast(struct sock *sk, struct sk_buff *skb, u32 portid)
 2 {
 3     int err;
 4 
 5     err = netlink_unicast(sk, skb, portid, MSG_DONTWAIT);//這里以非阻塞(MSG_DONTWAIT)的形式向應用層發送消息,這時的portid為應用層套接字所綁定的id號
 6     if (err > 0)
 7         err = 0;
 8 
 9     return err;
10 }

4.2 函數netlink_unicast

注意區分對比上述2.4節詳解4的flow。

 1 int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
 2             u32 portid, int nonblock)
 3 {
 4     struct sock *sk;
 5     int err;
 6     long timeo;
 7 
 8     skb = netlink_trim(skb, gfp_any());
 9 
10     timeo = sock_sndtimeo(ssk, nonblock);
11 retry:
12     sk = netlink_getsockbyportid(ssk, portid);
13     if (IS_ERR(sk)) {
14         kfree_skb(skb);
15         return PTR_ERR(sk);
16     }
17     if (netlink_is_kernel(sk))
18         return netlink_unicast_kernel(sk, skb, ssk);//應用層向內核發送消息
19         /*以下為內核向應用層發送消息的flow*/
20     if (sk_filter(sk, skb)) {//首先sk_filter執行防火牆的過濾,確保可以發送以后調用netlink_attachskb將要發送的skb綁定到netlink sock上
21         err = skb->len;
22         kfree_skb(skb);
23         sock_put(sk);
24         return err;
25     }
26 
27     err = netlink_attachskb(sk, skb, &timeo, ssk);---------------- 詳解1
28     if (err == 1)//若執行netlink_attachskb()的返回值為1,就會再次嘗試發送操作
29         goto retry;
30     if (err)
31         return err;
32 
33     return netlink_sendskb(sk, skb); ------------------- 詳解2
34 }

詳解1:調用函數netlink_attachskb將要發送的skb綁定到netlink sock上:

如果目的sock的接收緩沖區剩余的的緩存大小小於已經提交的數據量,或者標志位已經置位了阻塞標識NETLINK_CONGESTED,這表明數據不可以立即的送到目的端的接收緩存中。因此,在原端不是內核socket且沒有設置非阻塞標識的情況下會定義一個等待隊列並等待指定的時間並返回1,否則直接丟棄該skb數據包並返回失敗。

 1 int netlink_attachskb(struct sock *sk, struct sk_buff *skb,
 2               long *timeo, struct sock *ssk)
 3 {
 4     struct netlink_sock *nlk;
 5 
 6     nlk = nlk_sk(sk);
 7 
 8     if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
 9          test_bit(NETLINK_S_CONGESTED, &nlk->state))) {
10         DECLARE_WAITQUEUE(wait, current);
11         if (!*timeo) {
12             if (!ssk || netlink_is_kernel(ssk))
13                 netlink_overrun(sk);
14             sock_put(sk);
15             kfree_skb(skb);
16             return -EAGAIN;
17         }
18 
19         __set_current_state(TASK_INTERRUPTIBLE);
20         add_wait_queue(&nlk->wait, &wait);
21 
22         if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
23              test_bit(NETLINK_S_CONGESTED, &nlk->state)) &&
24             !sock_flag(sk, SOCK_DEAD))
25             *timeo = schedule_timeout(*timeo);
26 
27         __set_current_state(TASK_RUNNING);
28         remove_wait_queue(&nlk->wait, &wait);
29         sock_put(sk);
30 
31         if (signal_pending(current)) {
32             kfree_skb(skb);
33             return sock_intr_errno(*timeo);
34         }
35         return 1;
36     }
37     netlink_skb_set_owner_r(skb, sk);//目的端的接收緩存區空間足夠,就會調用netlink_skb_set_owner_r進行綁定
38     return 0;
39 }

函數netlink_unicast詳解2:調用netlink_sendskb()執行發送操作

 1 int netlink_sendskb(struct sock *sk, struct sk_buff *skb)
 2 {
 3     int len = __netlink_sendskb(sk, skb);
 4 
 5     sock_put(sk);
 6     return len;
 7 }
 8 /*這里又一次回到了__netlink_sendskb函數執行發送流程*/
 9 static int __netlink_sendskb(struct sock *sk, struct sk_buff *skb)
10 {
11     int len = skb->len;
12 
13     netlink_deliver_tap(skb);
14 
15     skb_queue_tail(&sk->sk_receive_queue, skb);
16     sk->sk_data_ready(sk);
17     return len;
18 }

這里的sk_data_ready()鈎子函數在初始化netlink函數sock_init_data()中被注冊為sock_def_readable():

 1 static void sock_def_readable(struct sock *sk)
 2 {
 3     struct socket_wq *wq;
 4  
 5     rcu_read_lock();
 6     wq = rcu_dereference(sk->sk_wq);
 7     if (wq_has_sleeper(wq))
 8         wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI |
 9                         POLLRDNORM | POLLRDBAND);
10     sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);//喚醒目的接收端socket的等待隊列,這樣應用層套接字就可以接收並處理消息了
11     rcu_read_unlock();
12 }

4.3 內核向應用層發送組播消息

內核發送多播消息是通過函數nlmsg_multicast(),詳細分析見上文2.5節,不再重復。

 1 static inline int nlmsg_multicast(struct sock *sk, struct sk_buff *skb,
 2                   u32 portid, unsigned int group, gfp_t flags)
 3 {
 4     int err;
 5 
 6     NETLINK_CB(skb).dst_group = group;
 7 
 8     err = netlink_broadcast(sk, skb, portid, group, flags);
 9     if (err > 0)
10         err = 0;
11 
12     return err;
13 }

5 應用層接收內核的消息

應用層通過API recvmsg接收內核的消息,其對應的系統調用如下:

 1 SYSCALL_DEFINE3(recvmsg, int, fd, struct user_msghdr __user *, msg,
 2         unsigned int, flags)
 3 {
 4     if (flags & MSG_CMSG_COMPAT)
 5         return -EINVAL;
 6     return __sys_recvmsg(fd, msg, flags);
 7 }
 8 
 9 long __sys_recvmsg(int fd, struct user_msghdr __user *msg, unsigned flags)
10 {
11     int fput_needed, err;
12     struct msghdr msg_sys;
13     struct socket *sock;
14 
15     sock = sockfd_lookup_light(fd, &err, &fput_needed);//同2節的發送,也是通過fd描述符查找對應的套接字socket結構
16     if (!sock)
17         goto out;
18 
19     err = ___sys_recvmsg(sock, msg, &msg_sys, flags, 0);
20 
21     fput_light(sock->file, fput_needed);
22 out:
23     return err;
24 }

5.1 函數___sys_recvmsg

 1 static int ___sys_recvmsg(struct socket *sock, struct user_msghdr __user *msg,
 2              struct msghdr *msg_sys, unsigned int flags, int nosec)
 3 {
 4     struct compat_msghdr __user *msg_compat =
 5         (struct compat_msghdr __user *)msg;
 6     struct iovec iovstack[UIO_FASTIOV];
 7     struct iovec *iov = iovstack;//定義了一個大小為8的iovstack數組緩存,用來加速消息處理
 8     unsigned long cmsg_ptr;
 9     int len;
10     ssize_t err;
11 
12     /* kernel mode address */
13     struct sockaddr_storage addr;
14 
15     /* user mode address pointers */
16     struct sockaddr __user *uaddr;
17     int __user *uaddr_len = COMPAT_NAMELEN(msg);//獲取用戶空間的地址長度字段的地址
18 
19     msg_sys->msg_name = &addr;
20 
21     if (MSG_CMSG_COMPAT & flags)
22         err = get_compat_msghdr(msg_sys, msg_compat, &uaddr, &iov);
23     else
24         err = copy_msghdr_from_user(msg_sys, msg, &uaddr, &iov);//拷貝用戶態msg中的數據到內核態msg_sys中 ------------- 詳解1
25     if (err < 0)
26         return err;
27 
28     cmsg_ptr = (unsigned long)msg_sys->msg_control;
29     msg_sys->msg_flags = flags & (MSG_CMSG_CLOEXEC|MSG_CMSG_COMPAT);
30 
31     /* We assume all kernel code knows the size of sockaddr_storage */
32     msg_sys->msg_namelen = 0;//將地址的長度字段清零
33     /*根據nosec的值是否為0而調用sock_recvmsg_nosec()或sock_recvmsg()函數接收數據,nosec在recvmsg系統調用傳入的為0,在recvmmsg系統能夠調用接收多個消息時傳入已經接受的消息個數*/
34     if (sock->file->f_flags & O_NONBLOCK)
35         flags |= MSG_DONTWAIT;
36     err = (nosec ? sock_recvmsg_nosec : sock_recvmsg)(sock, msg_sys, flags);------------ 詳解2
37     if (err < 0)
38         goto out_freeiov;
39     len = err;
40     //len保存了接收到數據的長度,然后將消息地址信息從內核空間拷貝到用戶空間
41     if (uaddr != NULL) {
42         err = move_addr_to_user(&addr,
43                     msg_sys->msg_namelen, uaddr,
44                     uaddr_len);
45         if (err < 0)
46             goto out_freeiov;
47     }
48     err = __put_user((msg_sys->msg_flags & ~MSG_CMSG_COMPAT),
49              COMPAT_FLAGS(msg));
50     if (err)
51         goto out_freeiov;
52     if (MSG_CMSG_COMPAT & flags)
53         err = __put_user((unsigned long)msg_sys->msg_control - cmsg_ptr,
54                  &msg_compat->msg_controllen);//將flag復制到用戶空間
55     else
56         err = __put_user((unsigned long)msg_sys->msg_control - cmsg_ptr,
57                  &msg->msg_controllen);//將消息輔助數據等復制到用戶空間
58     if (err)
59         goto out_freeiov;
60     err = len;
61 
62 out_freeiov:
63     kfree(iov);
64     return err;
65 }

 詳解1:調用copy_msghdr_from_user拷貝用戶態msg中的數據到內核態msg_sys中。當然這里主要是為了接收內核的消息,用戶空間並沒有什么實際的數據,這里最主要的作用就是確定用戶需要接收多少數據量。注意第三個參數已經不再是NULL了,而是指向了uaddr指針的地址。函數copy_msghdr_from_user:

 1 static int copy_msghdr_from_user(struct msghdr *kmsg,
 2                  struct user_msghdr __user *umsg,
 3                  struct sockaddr __user **save_addr,
 4                  struct iovec **iov)
 5 {
 6     struct sockaddr __user *uaddr;
 7     struct iovec __user *uiov;
 8     size_t nr_segs;
 9     ssize_t err;
10 
11     if (!access_ok(VERIFY_READ, umsg, sizeof(*umsg)) ||
12         __get_user(uaddr, &umsg->msg_name) ||
13         __get_user(kmsg->msg_namelen, &umsg->msg_namelen) ||
14         __get_user(uiov, &umsg->msg_iov) ||
15         __get_user(nr_segs, &umsg->msg_iovlen) ||
16         __get_user(kmsg->msg_control, &umsg->msg_control) ||
17         __get_user(kmsg->msg_controllen, &umsg->msg_controllen) ||
18         __get_user(kmsg->msg_flags, &umsg->msg_flags))
19         return -EFAULT;
20 
21     if (!uaddr)
22         kmsg->msg_namelen = 0;
23 
24     if (kmsg->msg_namelen < 0)
25         return -EINVAL;
26 
27     if (kmsg->msg_namelen > sizeof(struct sockaddr_storage))
28         kmsg->msg_namelen = sizeof(struct sockaddr_storage);
29 
30     if (save_addr)
31         *save_addr = uaddr;
32 
33     if (uaddr && kmsg->msg_namelen) {
34         if (!save_addr) {
35             err = move_addr_to_kernel(uaddr, kmsg->msg_namelen,
36                           kmsg->msg_name);
37             if (err < 0)
38                 return err;
39         }
40     } else {
41         kmsg->msg_name = NULL;
42         kmsg->msg_namelen = 0;
43     }
44 
45     if (nr_segs > UIO_MAXIOV)
46         return -EMSGSIZE;
47 
48     kmsg->msg_iocb = NULL;
49 
50     return import_iovec(save_addr ? READ : WRITE, uiov, nr_segs,
51                 UIO_FASTIOV, iov, &kmsg->msg_iter);
52 }

傳入的uaddr指針被指向了用戶空間msg->msg_name地址處,然后內核也不再會調用move_addr_to_kernel將用戶空間的消息地址字段拷貝到內核空間了(因為根本沒必要了),然后以READ的方式調用import_iovec()函數,它會檢查用戶空間的消息數據地址是否可以寫入,然后根據用戶需要接收的msg_iovlen長度封裝kmsg->msg_iter結構。

函數___sys_recvmsg 詳解2:recvmmsg()就是sock_recvmsg_nosec()的一個封裝而已,只不過會增加security檢查

 1 int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags)
 2 {
 3     int err = security_socket_recvmsg(sock, msg, msg_data_left(msg), flags);
 4 
 5     return err ?: sock_recvmsg_nosec(sock, msg, flags);
 6 }
 7 
 8 static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg,
 9                      int flags)
10 {
11     return sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);//調用了接收套接字所在協議的recvmsg接收鈎子函數,對於netlink就是netlink_recvmsg()函數
12 }

5.2 函數netlink_recvmsg

 1 static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
 2                int flags)
 3 {
 4     struct scm_cookie scm;
 5     struct sock *sk = sock->sk;
 6     struct netlink_sock *nlk = nlk_sk(sk);
 7     int noblock = flags&MSG_DONTWAIT;
 8     size_t copied;
 9     struct sk_buff *skb, *data_skb;
10     int err, ret;
11 
12     if (flags&MSG_OOB)
13         return -EOPNOTSUPP;
14 
15     copied = 0;
16         /*從接收socket的緩存中接收消息並通過skb返回,如果設置了MSG_DONTWAIT則在接收隊列中沒有消息時立即返回,否則會阻塞等待。*/
17     skb = skb_recv_datagram(sk, flags, noblock, &err);
18     if (skb == NULL)
19         goto out;
20 
21     data_skb = skb;
22 
23 #ifdef CONFIG_COMPAT_NETLINK_MESSAGES
24     if (unlikely(skb_shinfo(skb)->frag_list)) {
25         /*
26          * If this skb has a frag_list, then here that means that we
27          * will have to use the frag_list skb's data for compat tasks
28          * and the regular skb's data for normal (non-compat) tasks.
29          *
30          * If we need to send the compat skb, assign it to the
31          * 'data_skb' variable so that it will be used below for data
32          * copying. We keep 'skb' for everything else, including
33          * freeing both later.
34          */
35         if (flags & MSG_CMSG_COMPAT)
36             data_skb = skb_shinfo(skb)->frag_list;
37     }
38 #endif
39 
40     /* Record the max length of recvmsg() calls for future allocations */
41     nlk->max_recvmsg_len = max(nlk->max_recvmsg_len, len);//更新了最長的的接收數據長度
42     nlk->max_recvmsg_len = min_t(size_t, nlk->max_recvmsg_len,
43                      SKB_WITH_OVERHEAD(32768));
44 
45     copied = data_skb->len;
46     if (len < copied) {////判斷如果獲取到的skb數據長度大於大於本次接收緩存的最大長度,則設置MSG_TRUNC標識,並將本次需要接收數據量設置為接收緩存的長度
47         msg->msg_flags |= MSG_TRUNC;
48         copied = len;
49     }
50 
51     skb_reset_transport_header(data_skb);
52     err = skb_copy_datagram_msg(data_skb, 0, msg, copied);//將skb中的實際數據拷貝到msg消息中---------- 詳解1
53 
54     if (msg->msg_name) {
55         DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);//拷貝完成后這里開始初始化地址結構
56         addr->nl_family = AF_NETLINK;//將family這是為AF_NETLINK地址族
57         addr->nl_pad    = 0;
58         addr->nl_pid    = NETLINK_CB(skb).portid;//設置portid號為保存在原端skb擴展cb字段中的portid,對於這里接收內核發送的skb消息來說本字段為0
59         addr->nl_groups    = netlink_group_mask(NETLINK_CB(skb).dst_group);//設置組播地址,----------- 詳解2
60         msg->msg_namelen = sizeof(*addr);
61     }
62 
63     if (nlk->flags & NETLINK_F_RECV_PKTINFO)//如果設置了NETLINK_RECV_PKTINFO標識則將輔助消息頭拷貝到用戶空間
64         netlink_cmsg_recv_pktinfo(msg, skb);
65     if (nlk->flags & NETLINK_F_LISTEN_ALL_NSID)
66         netlink_cmsg_listen_all_nsid(sk, msg, skb);
67 
68     memset(&scm, 0, sizeof(scm));
69     scm.creds = *NETLINK_CREDS(skb);
70     if (flags & MSG_TRUNC)//判斷是否設置了MSG_TRUNC標識
71         copied = data_skb->len;//如果設置了就重新設置copied為本次取出的skb中獲取數據的長度(特別注意!)
72 
73     skb_free_datagram(sk, skb);//釋放skb消息包
74 
75     if (nlk->cb_running &&
76         atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2) {
77         ret = netlink_dump(sk);
78         if (ret) {
79             sk->sk_err = -ret;
80             sk->sk_error_report(sk);
81         }
82     }
83 
84     scm_recv(sock, msg, &scm, flags);
85 out:
86     netlink_rcv_wake(sk);
87     return err ? : copied;//返回接收數據長度
88 }

詳解1:調用skb_copy_datagram_msg()函數將skb中的實際數據拷貝到msg消息中(這里進行了一次數據拷貝動作,將skb中的數據直接拷貝到msg指向的用戶空間地址處)。

詳解2:在拷貝完成后這里開始初始化地址結構,這里將family這是為AF_NETLINK地址族,然后設置portid號為保存在原端skb擴展cb字段中的portid,對於這里接收內核發送的skb消息來說本字段為0,然后設置組播地址,該值在前文中內核調用nlmsg_multicast()發送組播消息時設置(對於單播來說就為0),netlink_group_mask()函數將組播地址的位號轉換為實際的組播地址(mask),然后這是msg的地址長度為nl_addr的長度。

參考博文:https://blog.csdn.net/luckyapple1028/article/details/50936563


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM