linux内核通信-netlink.docx_第1页
linux内核通信-netlink.docx_第2页
linux内核通信-netlink.docx_第3页
linux内核通信-netlink.docx_第4页
linux内核通信-netlink.docx_第5页
已阅读5页,还剩25页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

一、Netlink介绍前面有一篇文章其实已经介绍过Netlink方面的知识,还有一个内核和用户空间之间的一个交互例子,这篇文章主要是更细节和基础的知识介绍! Netlink 是一种特殊的 socket,它是 Linux 所特有的,由于传送的消息是暂存在socket接收缓存中,并不被接收者立即处理,所以netlink是一种异步通信机制。系统调用和 ioctl 则是同步通信机制。用户空间进程可以通过标准socketAPI来实现消息的发送、接收,在Linux中,有很多用户空间和内核空间的交互都是通过Netlink机制完成的,在Linux3.0的内核版本中定义了下面的21个用于Netlink通信的宏,其中默认的最大值为32.我这里重点关注的是IPv6路由部分的通信过程。 在include/linux/netlink.h文件中定义了下面的用于通信的宏!#define NETLINK_ROUTE0/* Routing/device hook*/#define NETLINK_UNUSED1/* Unused number*/#define NETLINK_USERSOCK 2/* Reserved for user mode socket protocols*/#define NETLINK_FIREWALL 3/* Firewalling hook*/#define NETLINK_INET_DIAG4/* INET socket monitoring*/#define NETLINK_NFLOG5/* netfilter/iptables ULOG */#define NETLINK_XFRM 6/* ipsec */#define NETLINK_SELINUX7/* SELinux event notifications */#define NETLINK_ISCSI8/* Open-iSCSI */#define NETLINK_AUDIT9/* auditing */#define NETLINK_FIB_LOOKUP10#define NETLINK_CONNECTOR11#define NETLINK_NETFILTER12/* netfilter subsystem */#define NETLINK_IP6_FW13#define NETLINK_DNRTMSG14/* DECnet routing messages */#define NETLINK_KOBJECT_UEVENT15/* Kernel messages to userspace */#define NETLINK_GENERIC16 /* leave room for NETLINK_DM (DM Events) */#define NETLINK_SCSITRANSPORT 18/* SCSI Transports */#define NETLINK_ECRYPTFS19#define NETLINK_RDMA20#define MAX_LINKS 32二、Socket API用户态可以使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 等函数就能很容易地使用 netlink socket,我们在用户空间可以直接通过socket函数来使用Netlink通信,例如可以通过下面的方式: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是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。对于每一个netlink协议类型,可以使用多播的概念,最多可以有 32个多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多播消息的应用而言,大大地降低了系统调用的次数。下面介绍一下主要的数据结构:struct sockaddr_nl sa_family_tnl_family;/* AF_NETLINK*/unsigned shortnl_pad; /* zero*/_u32nl_pid;/* port ID*/_u32nl_groups; /* multicast groups mask */;说明:1)sa_family_tnl_family; 一般为AF_NETLINK,2)unsigned shortnl_pad; 字段 nl_pad 当前没有使用,因此要总是设置为 0。3) _u32nl_pid;绑定时用于指定绑定者的进程号,发送消息时用于指定接收进程号,如果希望内核处理多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。传递给 bind 函数的地址的 nl_pid 字段应当设置为本进程的进程 ID,这相当于 netlink socket 的本地地址。但是,对于一个netlink socket 的情况,字段 nl_pid 则可以设置为其它的值,如:pthread_self() nlmsg_len ;struct msghdr msg = (void*) &snl, sizeof snl, &iov, 1, NULL, 0, 0;其中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; /* Must be size_t (1003.1g) */;上面的数据结构全部初始化以后就可以调用sendmsg函数进行发送操作了。status = sendmsg (sock, &msg, 0);其中sock就是我们创建的sock套接字,msg就是上面结构体struct msghdr的实例。如果我们需要返回一个ACK消息,可以对flags标志进行设置如下:/* Request an acknowledgement by setting NLM_F_ACK */n-nlmsg_flags |= NLM_F_ACK;4、recvmsg使用下面的函数进行接收处理时,status;为返回的状态,这里可能的结果为:#define NLMSG_NOOP0x1/* Nothing.*/#define NLMSG_ERROR0x2/* Error*/#define NLMSG_DONE0x3/* End of a dump*/#define NLMSG_OVERRUN0x4/* Data lostint status;char buf4096;struct iovec iov = buf, sizeof buf ;struct sockaddr_nl snl;struct msghdr msg = (void*)&snl, sizeof snl, &iov, 1, NULL, 0, 0;struct nlmsghdr *h;status = recvmsg (sock, &msg, 0);在linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:#define NLMSG_ALIGNTO4#define NLMSG_ALIGN(len) ( (len)+NLMSG_ALIGNTO-1) & (NLMSG_ALIGNTO-1) )/*宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值*/#define NLMSG_HDRLEN(int) NLMSG_ALIGN(sizeof(struct nlmsghdr)#define NLMSG_LENGTH(len) (len)+NLMSG_ALIGN(NLMSG_HDRLEN)/*宏NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。它一般用于分配消息缓存*/#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)/*宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值,它也用于分配消息缓存*/#define NLMSG_DATA(nlh) (void*)(char*)nlh) + NLMSG_LENGTH(0)/*宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏*/#define NLMSG_NEXT(nlh,len)(len) -= NLMSG_ALIGN(nlh)-nlmsg_len), (struct nlmsghdr*)(char*)(nlh) + NLMSG_ALIGN(nlh)-nlmsg_len)/*宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用*/#define NLMSG_OK(nlh,len) (len) = (int)sizeof(struct nlmsghdr) & (nlh)-nlmsg_len = sizeof(struct nlmsghdr) & (nlh)-nlmsg_len nlmsg_len - NLMSG_SPACE(len)/*宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度*/在/kernel/net/netlink/af_netlink.c文件中定义了netlink套接字的结构体struct netlink_sock /* struct sock has to be the first member of netlink_sock */struct socksk;u32pid; /内核自己的pid,=0u32dst_pid;u32dst_group;/目的组u32flags;u32subscriptions;u32ngroups;/ 组数量unsigned long*groups; /组号unsigned longstate;wait_queue_head_twait;/ 进程在接收数据包时等待队列struct netlink_callback*cb;spinlock_tcb_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(struct netlink_sock),;在static int _init netlink_proto_init(void)函数中会调用注册协议的函数,对netlink协议进行注册,其中,netlink_proto就是上面的struct proto netlink_proto协议。int err = proto_register(&netlink_proto, 0);三、内核中的处理流程这里我以路由中的netlink为例,看一下内核中的处理流程是怎么样的!1、skb在内核中接收的数据和存储发送的数据都是放在了skb_buff的结构体中struct netlink_skb_parmsstruct ucred creds;/* Skb credentials */_u32pid;_u32dst_pid;_u32dst_group;kernel_cap_teff_cap;_u32loginuid;/* Login (audit) uid */;使用下面的宏获取skb_bff中的数据部分#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&(skb)-cb)2、接收在/kernel/net/core/rtnetlink.c文件中,有一个接收从用户空间过来的Netlink消息的函数。static void rtnetlink_rcv(struct sock *sk, int len)unsigned int qlen = 0;do rtnl_lock();netlink_run_queue(sk, &qlen, &rtnetlink_rcv_msg);up(&rtnl_sem);netdev_run_todo(); while (qlen);上面的内核函数就是用来接收用户路由方面Netlink消息的,当我们使用route命令添加一条路由时,就会调用该函数接收。该函数是在netlink的初始化是注册的。同样在rtnetlink.c文件中。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_bufn);/在创建内核的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_dev_notifier);rtnetlink_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 *sk;struct netlink_sock *nlk;if (!nl_table)return NULL;if (unit=MAX_LINKS)return NULL;if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock)return NULL;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 (netlink_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(&sk-sk_receive_queue)*qlen = skb_queue_len(&sk-sk_receive_queue);for (; *qlen; (*qlen)-) skb = skb_dequeue(&sk-sk_receive_queue);if (netlink_rcv_skb(skb, cb) if (skb-len)skb_queue_head(&sk-sk_receive_queue, skb);else kfree_skb(skb);(*qlen)-;break;kfree_skb(skb);下面是rtnetlink_rcv_msg()函数的实现,对netlink消息进行相应的处理。其中有一个数据结构struct rtnetlink_link *link; 其定义如下:是两个不同的处理函数struct rtnetlink_linkint (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);/* Process one rtnetlink 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_flags&NLM_F_REQUEST)return 0;type = nlh-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类型,调用不同的处理函数,这里以路由为例的话为AF_ROUTE或者AF_NETLINKif (link_tab = NULL)link_tab = rtnetlink_linksPF_UNSPEC;link = &link_tabtype; /根据不同的type调用不同的处理函数。这里的type为RTM_NEWROUTEsz_idx = type2;kind = type&3;if (kind != 2 & security_netlink_recv(skb) *errp = -EPERM;return -1;if (kind = 2 & nlh-nlmsg_flags&NLM_F_DUMP) if (link-dumpit = NULL)link = &(rtnetlink_linksPF_UNSPECtype);if (link-dumpit = NULL)goto err_inval;if (*errp = netlink_dump_start(rtnl, skb, nlh, link-dumpit, NULL) != 0)return -1;netlink_queue_skip(nlh, skb);return -1;memset(rta_buf, 0, (rtattr_max * sizeof(struct rtattr *);min_len = rtm_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 (RTA_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 = &(rtnetlink_linksPF_UNSPECtype);if (link-doit = NULL)goto err_inval;err = link-doit(skb, nlh, (void *)&rta_buf0); /此处调用RTM_NEWROUTE,对应的route处理函数,也就是下面的inet6_rtm_newroute函数。*errp = err;return err;err_inval:*errp = -EINVAL;return -1;int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)struct rtmsg *r = NLMSG_DATA(nlh);struct in6_rtmsg rtmsg;if (inet6_rtm_to_rtmsg(r, arg, &rtmsg)return -EINVAL;return ip6_route_add(&rtmsg, nlh, arg, &NETLINK_CB(skb);inet6_rtm_newroute函数通过下面的数组进行了相应的注册处理,所以上面的link-doit(skb, nlh, (void *)&rta_buf0)就是根据下面的这个调用的。static struct rtnetlink_link inet6_rtnetlink_tableRTM_NR_MSGTYPES = RTM_GETLINK - RTM_BASE = .dumpit= inet6_dump_ifinfo, ,RTM_NEWADDR - RTM_BASE = .doit= inet6_rtm_newaddr, ,RTM_DELADDR - RTM_BASE = .doit= inet6_rtm_deladdr, ,RTM_GETADDR - RTM_BASE = .dumpit= inet6_dump_ifaddr, ,RTM_GETMULTICAST - RTM_BASE = .dumpit = inet6_dump_ifmcaddr, ,RTM_GETANYCAST - RTM_BASE = .dumpit= inet6_dump_ifacaddr, ,RTM_NEWROUTE - RTM_BASE = .doit= inet6_rtm_newroute, ,RTM_DELROUTE - RTM_BASE = .doit= inet6_rtm_delroute, ,RTM_GETROUTE - RTM_BASE = .doit= inet6_rtm_getroute, .dumpit= inet6_dump_fib, ,;相关的结构体:内核中所有的netlink套接字存储在一个全局的哈新表中,该结构定义如下 static struct netlink_table *nl_table;其中每个协议对应一个哈希表,所有的同一种协议的数据报散列在同哈希表中,下面为一种协议所连接的哈希表结构: struct netlink_table struct nl_pid_hash hash; / 根据pid进行HASH的netlink sock链表, 相当于客户端链表struct hlist_head mc_list; / 多播的sock链表unsigned int nl_nonroot; / 监听者标志unsigned int groups; / 每个netlink的协议类型可以定义多个组, 8的倍数,最小是32struct module *module;int registered;最大可有MAX_LINKS(32)个表,处理不同协议类型的netlink套接口, 注意由于是自身的通信, 本机同时作为服务器和客户

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论