linux netlink详解2-netlink通信详解[通俗易懂]

linux netlink详解2-netlink通信详解[通俗易懂]1主要数据定义位于:linux-4.9.73\include\uapi\linux\netlink.h1.1structnlmsghdr表示netlink消息报头。netlink消息同TCP/UDP消息一样,也需要遵循协议要求的格式,每个netlink消息的开头是固定长度的netlink报

大家好,欢迎来到IT知识分享网。

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

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/28146.html

(0)
上一篇 2023-10-02 15:33
下一篇 2023-10-03 17:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信