前面有一篇文章其实已经介绍过Netlink方面的知识,还有一个内核和用户空间之间的一个交互例子,这篇文章主要是更细节和基础的知识介绍!

Netlink是一种特殊的socket,它是Linux所特有的,由于传送的消息是暂存在socket接收缓存中,并不被接收者立即处理,所以netlink是一种异步通信机制。系统调用和ioctl则是同步通信机制。

用户空间进程可以通过标准socket API来实现消息的发送、接收,在Linux中,有很多用户空间和内核空间的交互都是通过Netlink机制完成的,在Linux3.0的内核版本中定义了下面的21个用于Netlink通信的宏,其中默认的最大值为32.我这里重点关注的是IPv6路由部分的通信过程。

在include/linux/`netlink.h文件中定义了下面的用于通信的宏!

#define NETLINK_ROUTE        0    /* Routing/device hook                */

#define NETLINK_UNUSED        1    /* Unused number                */

#define NETLINK_USERSOCK    2    /* Reserved for user mode socket protocols     */

#define NETLINK_FIREWALL    3    /* Firewalling hook                */

#define NETLINK_INET_DIAG    4    /* INET socket monitoring            */

#define NETLINK_NFLOG        5    /* netfilter/iptables ULOG */

#define NETLINK_XFRM        6    /* ipsec */

#define NETLINK_SELINUX        7    /* SELinux event notifications */

#define NETLINK_ISCSI        8    /* Open-iSCSI */

#define NETLINK_AUDIT        9    /* auditing */

#define NETLINK_FIB_LOOKUP    10

#define NETLINK_CONNECTOR    11

#define NETLINK_NETFILTER    12    /* netfilter subsystem */

#define NETLINK_IP6_FW        13

#define NETLINK_DNRTMSG        14    /* DECnet routing messages */

#define NETLINK_KOBJECT_UEVENT    15    /* Kernel messages to userspace */

#define NETLINK_GENERIC        16

/* leave room for NETLINK_DM (DM Events) */

#define NETLINK_SCSITRANSPORT    18    /* SCSI Transports */

#define NETLINK_ECRYPTFS    19

#define NETLINK_RDMA        20

#define MAX_LINKS 32

用户态可以使用标准的socket APIs,socket(), bind(), sendmsg(), recvmsg()和close()等函数就能很容易地使用netlink socket,我们在用户空间可以直接通过socket函数来使用Netlink通信,例如可以通过下面的方式:

sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

说明:第一个参数必须是AF_NETLINK或PF_NETLINK,在Linux中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM,

第三个参数指定netlink协议类型,可以是自己在netlink.h中定义的,也可以是内核中已经定义好的。上面的例子使用主要是路由的Netlink协议。也可以是上面21中协议类型的其中之一。

NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。

对于每一个netlink协议类型,可以使用多播的概念,最多可以有32个多播组,每一个多播组用一个位表示,netlink的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多播消息的应用而言,大大地降低了系统调用的次数。

下面介绍一下主要的数据结构:

struct sockaddr_nl {

sa_family_t    nl_family;    /* AF_NETLINK    */

unsigned short    nl_pad;        /* zero        */

__u32        nl_pid;        /* port ID    */

__u32        nl_groups;    /* multicast groups mask */

};

说明:(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()

<< 16 | getpid();

因此字段nl_pid实际上未必是进程ID,它只是用于区分不同的接收者或发送者的一个标识,用户可以根据自己需要设置该字段。

(4)__u32nl_groups;绑定时用于指定绑定者所要加入的多播组,这样绑定者就可以接收多播消息,发送消息时可以用于指定多播组,这样就可以将消息发给多个接收者。这里nl_groups为32位的无符号整形,所以可以指定32个多播组,每个进程可以加入多个多播组,因为多播组是通过“或”操作,如果设置为0,表示调用者不加入任何多播组。这里就是Netlink多播的概念!和通信中的多播概念有点类似。

Bind的调用方式如下!

struct sockaddr_nl snl;

memset (&snl, 0, sizeof snl);

snl.nl_family = AF_NETLINK;

snl.nl_groups = groups;

ret = bind (sock, (struct sockaddr *) &snl, sizeof snl);

其中sock为前面的socket调用返回的文件描述符,参数snl为struct

sockaddr_nl类型的地址。

为了发送一个netlink消息给内核或其他用户态应用,需要填充目标netlink socket地址

,此时,字段nl_pid和nl_groups分别表示接收消息者的进程ID与多播组。如果字段nl_pid设置为0,表示消息接收者为内核或多播组,如果nl_groups为0,表示该消息为单播消息,否则表示多播消息。用户态使用函数sendmsg发送netlink消息时还需要引用结构struct msghdr、struct nlmsghdr和struct iovec,结构struct msghdr的定义如下:

struct msghdr {

void    *    msg_name;    /* Socket name            */

int        msg_namelen;    /* Length of name        */

struct iovec *    msg_iov;    /* Data blocks            */

__kernel_size_t    msg_iovlen;    /* Number of blocks        */

void     *    msg_control;    /* Per protocol magic (eg BSD file descriptor passing) */

__kernel_size_t    msg_controllen;    /* Length of cmsg list */

unsigned    msg_flags;

};

使用方法如下:

struct msghdr msg;

memset(&msg, 0, sizeof(msg));

msg.msg_name = (void *)&(snl);

msg.msg_namelen = sizeof(snl);

struct nlmsghdr为netlink socket自己的消息头,因此它也被称为netlink控制块。应用层在向内核发送netlink消息时必须提供该控制头。

消息头。

struct nlmsghdr

{

__u32        nlmsg_len;    /* Length of message including header */

__u16        nlmsg_type;    /* Message content */

__u16        nlmsg_flags;    /* Additional flags */

__u32        nlmsg_seq;    /* Sequence number */

__u32        nlmsg_pid;    /* Sending process PID */

};

字段nlmsg_len指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,字段nlmsg_type用于应用内部定义消息的类型,它对netlink内核实现是透明的,因此大部分情况下设置为0,字段nlmsg_seq和nlmsg_pid用于应用追踪消息,前者表示顺序号,后者为消息来源进程ID。字段nlmsg_flags用于设置消息标志,可用的标志包括下面的宏定义:kernel/include/linux/netlink.c

/* Flags values */

#define NLM_F_REQUEST        1    /* It is request message.     */

#define NLM_F_MULTI        2    /* Multipart message, terminated by NLMSG_DONE */

#define NLM_F_ACK        4    /* Reply with ack, with zero or error code */

#define NLM_F_ECHO        8    /* Echo this request         */

/* Modifiers to GET request */

#define NLM_F_ROOT    0x100    /* specify tree    root    */

#define NLM_F_MATCH    0x200    /* return all matching    */

#define NLM_F_ATOMIC    0x400    /* atomic GET        */

#define NLM_F_DUMP    (NLM_F_ROOT|NLM_F_MATCH)

/* Modifiers to NEW request */

#define NLM_F_REPLACE    0x100    /* Override existing        */

#define NLM_F_EXCL    0x200    /* Do not touch, if it exists    */

#define NLM_F_CREATE    0x400    /* Create, if it does not exist    */

#define NLM_F_APPEND    0x800    /* Add to end of list        */

标志NLM_F_REQUEST用于表示消息是一个请求,所有应用首先发起的消息都应设置该标志。

标志NLM_F_MULTI用于指示该消息是一个多部分消息的一部分,后续的消息可以通过宏NLMSG_NEXT来获得。

宏NLM_F_ACK表示该消息是前一个请求消息的响应,顺序号与进程ID可以把请求与响应关联起来。

标志NLM_F_ECHO表示该消息是相关的一个包的回传。

标志NLM_F_ROOT被许多netlink协议的各种数据获取操作使用,该标志指示被请求的数据表应当整体返回用户应用,而不是一个条目一个条

地返回。有该标志的请求通常导致响应消息设置NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,因此,需要在字段

nlmsg_type中指定协议类型。

标志NLM_F_MATCH表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。

标志NLM_F_ATOMIC指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。

标志NLM_F_DUMP未实现。

标志NLM_F_REPLACE用于取代在数据表中的现有条目。

标志NLM_F_EXCL_用于和CREATE和APPEND配合使用,如果条目已经存在,将失败。

标志NLM_F_CREATE指示应当在指定的表中创建一个条目。

标志NLM_F_APPEND指示在表末尾添加新的条目。

内核需要读取和修改这些标志,对于一般的使用,用户把它设置为0就可以,只是一些高级应用(如netfilter和路由daemon需要它进行一些设置),

下面是在调用sendmsg函数之前的各个结构体的赋值操作:

struct nlmsghdr *n

struct iovec iov = { (void*) n, n->nlmsg_len };

struct msghdr msg = {(void*) &snl, sizeof snl, &iov, 1, NULL, 0, 0};

其中snl为struct sockaddr_nl snl;在结构体struct

msghdr中包含有struct iovec结构,其实就是我们要传输的数据块,它为一个指针,定义了数据块的基址和长度。

struct iovec

{

void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */

__kernel_size_t iov_len; /* Must be size_t (1003.1g) */

};

上面的数据结构全部初始化以后就可以调用sendmsg函数进行发送操作了。

status = sendmsg (sock, &msg, 0);

其中sock就是我们创建的sock套接字,msg就是上面结构体struct

msghdr的实例。如果我们需要返回一个ACK消息,可以对flags标志进行设置如下:

/* Request an acknowledgement by setting NLM_F_ACK */

n->nlmsg_flags |= NLM_F_ACK;

使用下面的函数进行接收处理时,status;为返回的状态,这里可能的结果为:

#define NLMSG_NOOP        0x1    /* Nothing.        */

#define NLMSG_ERROR        0x2    /* Error        */

#define NLMSG_DONE        0x3    /* End of a dump    */

#define NLMSG_OVERRUN        0x4    /* Data lost

int status;

char buf[4096];

struct iovec iov = { buf, sizeof buf };

struct sockaddr_nl snl;

struct msghdr msg = { (void*)&snl, sizeof snl, &iov, 1, NULL, 0, 0};

struct nlmsghdr *h;

status = recvmsg (sock, &msg, 0);

在linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:

#define NLMSG_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 <= (len))

/*宏NLMSG_OK(nlh,len)用于判断消息是否有len这么长*/

#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len -

NLMSG_SPACE((len)))

/*宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度*/

在/kernel/net/netlink/af_netlink.c文件中定义了netlink套接字的结构体,

struct netlink_sock {

/* struct sock has to be the first member of netlink_sock */

struct sock        sk;

u32            pid; //内核自己的pid,=0

u32            dst_pid;

u32            dst_group;//目的组

u32            flags;

u32            subscriptions;

u32            ngroups;// 组数量

unsigned long        *groups; //组号

unsigned long        state;

wait_queue_head_t    wait;// 进程在接收数据包时等待队列

struct netlink_callback    *cb;

spinlock_t        cb_lock;

void            (*data_ready)(struct sock *sk, int bytes); ///内核态接收到用户态信息后的处理函数

struct module        *module;

};

在af_netlink.c文件中我们可以看到netlink协议的注册

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);

在内核中接收的数据和存储发送的数据都是放在了skb_buff的结构体中

struct netlink_skb_parms

{

struct ucred        creds;        /* Skb credentials    */

__u32            pid;

__u32            dst_pid;

__u32            dst_group;

kernel_cap_t        eff_cap;

__u32            loginuid;    /* Login (audit) uid */

};

使用下面的宏获取skb_bff中的数据部分

#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))

af_netlink_Linux Netlink通信机制详解(上)相关推荐

  1. React Native通信机制详解

    http://blog.cnbang.net/tech/2698/ React Native是facebook刚开源的框架,可以用javascript直接开发原生APP,先不说这个框架后续是否能得到大 ...

  2. android系统(63)---Jobscheduler运行机制详解

    android之Jobscheduler运行机制详解 如果想在将来达到一定条件下执行某项任务时,可以在一个实现了JobService的子类的onStartJob方法中执行这项任务,使用JobInfo的 ...

  3. pcie握手机制_【博文连载】PCIe扫盲——Ack/Nak 机制详解(一)

    原标题:[博文连载]PCIe扫盲--Ack/Nak 机制详解(一) 前面在数据链路层入门的文章中简单地提到过Ack/Nak机制的原理和作用,接下来的几篇文章中将对Ack/Nak机制进行详细地介绍. A ...

  4. Android JNI使用方法,JNI机制详解

    Android JNI使用方法,JNI机制详解 JNI的出现使得开发者既可以利用Java语言跨平台.类库丰 富.开发便捷等特点,又可以利用Native语言的高效. JNI是JVM实现中的一部分,因此N ...

  5. session机制详解

    session机制详解 摘要:虽然session机制在web应用程序中被采用已经很长时间了,但是仍然有很多人不清楚 session机制的本质,以至不能正确的应用这一技术.本文将详细讨论session的 ...

  6. Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(二)

    文章大纲 引言 一.DirectFragment 1.当选中DirectoryFragment中RecyclerView的Item时 2.选中DirectoryFragment中RecyclerVie ...

  7. 【CTO讲堂】SaaS云端语音通信架构详解

    为了帮助IT从业者职业之路拥有更多收获,在诸多C粉的殷切期待下,由 CTO俱乐部打造的CTO线上讲堂自登场以来获得大家好评.本期邀请容联七陌CTO张杨带来"SaaS云端语音通信架构详解&qu ...

  8. HTTP缓存机制详解

    HTTP缓存机制详解 一. 前言 二. 缓存的介绍 什么是缓存? 为什么要使用缓存? 1. 减少冗余的数据传输 2. 缓解带宽瓶颈 3. 破坏瞬间拥塞 4. 降低距离时延 三. 缓存有效性 命中和未命 ...

  9. 学习QT之信号槽机制详解

    学习QT之信号槽机制详解 一.Qt信号槽机制 概念:信号槽是Qt框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如:按钮检测到自己被点击了一下,它就会发出一个信号(sig ...

最新文章

  1. 写出下列数学式对应的python表达式_Python程序设计课后习题答案-第一单元
  2. Java URL处理
  3. iOS开发 Xcode8中遇到的问题及改动
  4. hashmap 循环取出所有值 取出特定的值 两种方法
  5. C++学习—— mutable和 extern
  6. 以VMware workstation15.5.2pro为例如何新建虚拟机,安装镜像文件(最新)
  7. 记录一次StackOverflowError问题
  8. Linux下C++开发系列(一)序——我是如何开始linux下C++开发的
  9. 图书租借管理系统java_阅览室图书租借管理系统下载
  10. 短视频剪辑如何入门?短视频剪辑常用的配音软件
  11. 数字集成电路设计流程
  12. Excel使用技巧 查找重复值
  13. 企业用企业邮箱的好处是什么?企业邮箱支持哪些功能?
  14. 奥马电器赵国栋转让股份兜底钱包金融展期项目,奥马获国资输血
  15. 【阿里巴巴Java编程规范学习 五】MySQL数据库规约
  16. 如何沿法线方向挤出面
  17. linux 开头是减号的文件名 如何进行文件操作 删除复制移动
  18. 邮件多面手!Foxmail 6.5正式版新体验
  19. 多人网络游戏服务器开发基础学习笔记 I:基本知识 | 游戏设计模式 | 网游服务器层次结构 | 游戏对象序列化 | 游戏 RPC 框架 | 帧同步和状态同步
  20. 2023年2月的十篇深度学习论文推荐

热门文章

  1. 完美海报设计的4个技巧
  2. 海外精致办公名片展示样机|智能贴图 Mockup名片模板
  3. UI设计师必收藏的上百种配色方案专辑!
  4. android 字体倒影,Android笔记:图片倒影、ReflectionImage
  5. python迭代器与生成器_python的迭代器与生成器实例详解
  6. videojs中文文档详解_你的项目需要一个高质量README文档!
  7. Python爬虫项目---批量从搜款网下载主图、颜色图详情图
  8. Docker的网络配置:创建网桥
  9. Build desktop apps for Windows PCs
  10. 图解读写锁:pthead_rwlock_t