1、一、Netlink 介绍前面有一篇文章其实已经介绍过 Netlink 方面的知识,还有一个内核和用户空间之间的一个交互例子,这篇文章主要是更细节和基础的知识介绍! Netlink 是一种特殊的 socket,它是 Linux 所特有的,由于传送的消息是暂存在 socket 接收缓存中,并不被接收者立即处理,所以 netlink 是一种异步通信机制。 系统调用和 ioctl 则是同步通信机制。用户空间进程可以通过标准 socket API 来实现消息的发送、接收,在Linux 中,有很多用户空间和内核空间的交互都是通过 Netlink 机制完成的,在 Linux3.0 的内核版本中定义了下面的
2、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 /* Firewa
3、lling 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
4、 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 N
5、ETLINK_DM (DM Events) */#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */#define NETLINK_ECRYPTFS 19#define NETLINK_RDMA 20#define MAX_LINKS 32二、Socket API用户态可以使用标准的 socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 等函数就能很容易地使用 netlink socket,我们在用户空间可以直接通过 socket 函数来使用 Netlink 通信,例如
6、可以通过下面的方式:1、socketsock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);说明:第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用 netlink,第二个参数必须是 SOCK_RAW 或SOCK_DGRAM, 第三个参数指定 netlink 协议类型,可以是自己在 netlink.h 中定义的,也可以是内核中已经定义好的。上面的例子使用主要是路由的Netlink 协议。也可以是上面 21 中协议类型的其中之一。NETLINK_GENERIC 是一个通用的协议类
7、型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。对于每一个 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_gr
8、oups; /* multicast groups mask */;说明:1) sa_family_t nl_family; 一般为 AF_NETLINK,2) unsigned short nl_pad; 字段 nl_pad 当前没有使用,因此要总是设置为 0。3) _u32 nl_pid; 绑定时用于指定绑定者的进程号,发送消息时用于指定接收进程号,如果希望内核处理多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这相当于 netlink socket 的本地地址。但是,对于一个 netlink
9、socket 的情况,字段 nl_pid 则可以设置为其它的值,如:pthread_self() nlmsg_len ;struct msghdr msg = (void*) 其中 snl 为 struct sockaddr_nl snl;在结构体 struct msghdr 中包含有struct iovec 结构,其实就是我们要传输的数据块,它为一个指针,定义了数据块的基址和长度。struct iovecvoid _user * iov_base; /* BSD uses caddr_t (1003.1g requires void *) */_kernel_size_t iov_len;
10、/* Must be size_t (1003.1g) */;上面的数据结构全部初始化以后就可以调用 sendmsg 函数进行发送操作了。status = sendmsg (sock, 其中 sock 就是我们创建的 sock 套接字,msg 就是上面结构体 struct msghdr 的实例。如果我们需要返回一个 ACK 消息,可以对 flags 标志进行设置如下:/* Request an acknowledgement by setting NLM_F_ACK */n-nlmsg_flags |= NLM_F_ACK;4、recvmsg使用下面的函数进行接收处理时,status;为返回的
11、状态,这里可能的结果为:#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 buf4096;struct iovec iov = buf, sizeof buf ;struct sockaddr_nl snl;struct msghdr msg = (void*)struct nlmsghdr *h;status
12、 = recvmsg (sock, 在 linux/netlink.h 中定义了一些方便对消息进行处理的宏,这些宏包括:#define NLMSG_ALIGNTO 4#define NLMSG_ALIGN(len) ( (len)+NLMSG_ALIGNTO-1) u32 pid; /内核自己的 pid,=0u32 dst_pid;u32 dst_group;/目的组u32 flags;u32 subscriptions;u32 ngroups;/ 组数量unsigned long *groups; /组号unsigned long state;wait_queue_head_t wait;/
13、 进程在接收数据包时等待队列struct netlink_callback *cb;spinlock_t cb_lock;void (*data_ready)(struct sock *sk, int bytes); /内核态接收到用户态信息后的处理函数struct module *module;5、netlink 协议注册在 af_netlink.c 文件中我们可以看到 netlink 协议的注册static struct proto netlink_proto = .name = “NETLINK“,.owner = THIS_MODULE,.obj_size = sizeof(struc
14、t netlink_sock),;在 static int _init netlink_proto_init(void)函数中会调用注册协议的函数,对 netlink 协议进行注册,其中,netlink_proto 就是上面的 struct proto netlink_proto 协议。int err = proto_register(三、内核中的处理流程这里我以路由中的 netlink 为例,看一下内核中的处理流程是怎么样的!1、 skb在内核中接收的数据和存储发送的数据都是放在了 skb_buff 的结构体中struct netlink_skb_parmsstruct ucred cred
15、s; /* Skb credentials */_u32 pid;_u32 dst_pid;_u32 dst_group;kernel_cap_t eff_cap;_u32 loginuid; /* Login (audit) uid */;使用下面的宏获取 skb_bff 中的数据部分#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)do rtnl_lock();netlink_run_queue(sk, up(netdev_run_todo(); while (qlen);上面的内核函数就是用来接收用户路由方面 Netlink 消息的
16、,当我们使用route 命令添加一条路由时,就会调用该函数接收。该函数是在 netlink 的初始化是注册的。同样在 rtnetlink.c 文件中。void _init rtnetlink_init(void)int i;rtattr_max = 0;for (i = 0; i rtattr_max)rtattr_max = rta_maxi;rta_buf = kmalloc(rtattr_max * sizeof(struct rtattr *), GFP_KERNEL);if (!rta_buf)panic(“rtnetlink_init: cannot allocate rta_bu
17、fn“);/在创建内核的 netlink 时,注册了路由 netlink 的接收函数,rtnetlink_rcv.rtnl = netlink_kernel_create(NETLINK_ROUTE, RTNLGRP_MAX, rtnetlink_rcv,THIS_MODULE);if (rtnl = NULL)panic(“rtnetlink_init: cannot initialize rtnetlinkn“);netlink_set_nonroot(NETLINK_ROUTE, NL_NONROOT_RECV);register_netdevice_notifier(rtnetlink
18、_linksPF_UNSPEC = link_rtnetlink_table;rtnetlink_linksPF_PACKET = link_rtnetlink_table;在 netlink_kernel_create 函数中,可以看到内核接收用户空间传过来的消息的接收函数,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 *s
19、k;struct netlink_sock *nlk;if (!nl_table)return NULL;if (unit=MAX_LINKS)return NULL;if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, if (_netlink_create(sock, unit) sk;sk-sk_data_ready = netlink_data_ready;if (input)nlk_sk(sk)-data_ready = input; /设置内核接收 Netlink 消息的函数,这里就是前面的 rtnetlink_rcv 函数if (n
20、etlink_insert(sk, 0)goto out_sock_release;nlk = nlk_sk(sk); /取得 sock 嵌入的 netlink_sock 结构体nlk-flags |= NETLINK_KERNEL_SOCKET;netlink_table_grab();nl_tableunit.groups = groups skb_queue_len(for (; *qlen; (*qlen)-) skb = skb_dequeue(if (netlink_rcv_skb(skb, cb) if (skb-len)skb_queue_head(else kfree_skb
21、(skb);(*qlen)-;break;kfree_skb(skb);下面是 rtnetlink_rcv_msg()函数的实现,对 netlink 消息进行相应的处理。其中有一个数据结构 struct rtnetlink_link *link; 其定义如下:是两个不同的处理函数struct rtnetlink_linkint (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);/* Process one rtn
22、etlink message. */static _inline_ intrtnetlink_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_flagstype = nl
23、h-nlmsg_type;/* A control message: ignore them */if (type RTM_MAX)goto err_inval;type -= RTM_BASE;/* All the messages must have at least 1 byte length */if (nlh-nlmsg_len rtgen_family;if (family = NPROTO) *errp = -EAFNOSUPPORT;return -1;link_tab = rtnetlink_linksfamily; /根据用户空间传过来的不同德 family 类型,调用不同
24、的处理函数,这里以路由为例的话为 AF_ROUTE 或者 AF_NETLINKif (link_tab = NULL)link_tab = rtnetlink_linksPF_UNSPEC;link = /根据不同的 type 调用不同的处理函数。这里的 type为 RTM_NEWROUTEsz_idx = type2;kind = typeif (kind != 2 return -1;if (kind = 2 if (link-dumpit = NULL)goto err_inval;if (*errp = netlink_dump_start(rtnl, skb, nlh, link-d
25、umpit, NULL) != 0)return -1;netlink_queue_skip(nlh, skb);return -1;memset(rta_buf, 0, (rtattr_max * sizeof(struct rtattr *);min_len = rtm_minsz_idx;if (nlh-nlmsg_len nlmsg_len min_len) int attrlen = nlh-nlmsg_len - NLMSG_ALIGN(min_len);struct rtattr *attr = (void*)nlh + NLMSG_ALIGN(min_len);while (R
26、TA_OK(attr, attrlen) unsigned flavor = attr-rta_type;if (flavor) if (flavor rta_maxsz_idx)goto err_inval;rta_bufflavor-1 = attr;attr = RTA_NEXT(attr, attrlen);if (link-doit = NULL)link = if (link-doit = NULL)goto err_inval;err = link-doit(skb, nlh, (void *) /此处调用RTM_NEWROUTE,对应的 route 处理函数,也就是下面的 in
27、et6_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, return ip6_route_add(inet6_rtm_newroute 函数通过下面的数组进行了相应的注册
28、处理,所以上面的 link-doit(skb, nlh, (void *)相关的结构体:内核中所有的 netlink 套接字存储在一个全局的哈新表中,该结构定义如下 static struct netlink_table *nl_table;其中每个协议对应一个哈希表,所有的同一种协议的数据报散列在同哈希表中,下面为一种协议所连接的哈希表结构: struct netlink_table struct nl_pid_hash hash; / 根据 pid 进行 HASH 的 netlink sock 链表, 相当于客户端链表struct hlist_head mc_list; / 多播的 soc
29、k 链表unsigned int nl_nonroot; / 监听者标志unsigned int groups; / 每个 netlink 的协议类型可以定义多个组, 8 的倍数,最小是 32struct module *module;int registered;最大可有 MAX_LINKS(32)个表,处理不同协议类型的 netlink 套接口, 注意由于是自身的通信, 本机同时作为服务器和客户端, 服务端需要一个套接口对应, 每个客户端也要有一个套接口对应, 多个客户端的套接口形成一个链表。struct hlist_head * table; / 链表节点,每个桶中协议的 sock 连入
30、其中,根据哈希值可得确定的 sockunsigned long rehash_time; / 重新计算 HASH 的时间间隔unsigned int mask;unsigned int shift;unsigned int entries; / 链表节点数unsigned int max_shift; / 最大幂值u32 rnd; / 随机数;在 kernel/include/linux/Net.h 中struct proto_ops int family;struct module *owner;int (*release)(struct socket *sock);int (*bind)(
31、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)(st
32、ruct 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
33、 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);/netlink 套接字实际的发送与接收函数int (*sendmsg)(struct kiocb *iocb, struct socket *sock, s
34、truct 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
35、, int flags);4、 route 命令添加路由下面我们看看,当我们使用 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 函数中调用 s
36、ock 的队列,执行相应的 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
37、;struct sk_buff *skb;int err;struct scm_cookie scm;if (msg-msg_flagsif (NULL = siocb-scm)siocb-scm = err = scm_send(sock, msg, siocb-scm);if (err msg_namelen) if (addr-nl_family != AF_NETLINK)return -EINVAL;dst_pid = addr-nl_pid;dst_group = ffs(addr-nl_groups);if (dst_group else dst_pid = nlk-dst_pi
38、d;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 信息NET
39、LINK_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), /* What can I do? Netlink is asynchronous, so that we will have to save current capabilities to check them, when this message will be d
40、elivered 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(netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL);err = netlink_unicast(sk, skb, dst_pid, msg-msg_flagsout:return err;