淘宝客静态网站网站申请
主要参考了《深入Linux内核架构》和《精通Linux内核网络》相关章节
文章目录
内核netLink套接字
Netlink协议基础
netlink是一种基于网络的机制,允许在内核内部以及内核与用户层之间进行通信。最早在内核2.2引入,旨在替代笨拙的IOCTL,IOCTL不能从内核向用户空间发送异步消息,而且必须定义IOCTL号。
Netlink协议定义在RFC3549中。以前是可以编译成模块,现在直接集成到内核了。与profs和sysfs相比,有一些优势如下:
- IOCTL处理程序不能从内核向用户空间发送异步消息,而Netlink套接字则可以。
- 用户与内核间的通信方式,不需要轮询,用户空间应用程序打开套接字,调用recvmsg(),如果没有来自内核的消息,就进入阻塞状态。
- 内核可以主动向用户空间发送异步消息,而不需要用户空间来触发。
- 支持组播传输。
/proc/net/netlink文件中包含了当前活动的netlink连接信息。
代码位于net/netlink中:af_netlink.c af_netlink.h diag.c genetlink.c
除这些文件外,还有几个头文件。事实上,最常用的是af_netlink模块。它提供了Netlink内核套接字API,而genetlink模块提供了新的通用Netlink APl。使用它创建Netlink消息更容易。监视接模块diag ( diag.c)提供的API用于读写有关Netlink套接字的信息。
要在用户空间中创建Netlink套接字,可使用系统调用socket()。Netlink套接字可以是SOCK_RAW套接字,也可以是SOCK_DGRAM套接字。

struct sockaddr_nl
它表示Netlink套接字的地址。与socket绑定
struct sockaddr_nl {__kernel_sa_family_t nl_family; /* 始终为AF_NETLINK */unsigned short nl_pad; /* zero */__u32 nl_pid; /* 端口号 */__u32 nl_groups; /* 组播组掩码 */
};
-
nl_pid: Netlink套接字的单播地址。对于内核Netlink套接字,该值应为0。
用户空间应用程序有时会将nl_pid设置为其进程ID ( pid )。在用户空间应用程序中,如果你显式地将nl_pid设置为0或根本不设置它,之后再调用bind(),则内核方法netlink_autobind()将给nl_pid赋值,会尝试将其设置为当前线程的进程ID。如果你要在用户空间中创建两个套接字,且没有调用bind(),就得确保nl_pid是唯一的。Netlink套接字不仅用于网络子系统,还可用于其他子系统,如SELinux , audit 、uevent等。rtnelink套接字是专用于联网的Netlink套接字,用于路由消息、邻接消息、链路消息和其他网络子系统消息。
net-tools和iproute2
iproute2包基于Netlink套接字,在2.1.7节中,提供了一个在iproute2中使用Netlink套接字的示例。net-tools包投入使用的时间较为久远,未来可能会被摒弃。这里之所以提及它,旨在说明,它虽然可以代替iproute2,但功能和威力都远不及后者。
有两个用于控制TCP/IP联网和处理网络设备的包:net-tools和iproute2。
iproute2包包含如下命令
- ip:用于管理网络表和网络接口
- tc:用于流量控制管理
- ss:用于转储套接字统计信息
- lnstat:用于转储Linux网络统计信息
- brigde:用于管理网桥地址和设备
iproute2包主要基于通过netlink套接字从用户空间向内核发送请示并获取应答。
net-tools包是基于IOCTL的,也有些主要命令:
- ifconfig
- arp
- routenet
- stat
- hostname
内核netlink套接字
在内核网络栈中,可创建多种Netlink套接字。每种内核套接字都可处理不同类型的消息。
当创建socket时, 协议类型参数选择的是NETLINK_ROUTE, 得到的socket是rtnetlink_socket,
rtnelink套接字是专用于link net的Netlink套接字,用于路由消息、邻接消息、链路消息和其他网络子系统消息。

创建
请注意,rtnetlink套接字支持网络命名空间。网络命名空间对象(结构net)包含一个名为rtnl的成员( rtnetlink套接字)。在方法rtnetlink_net_init()中,调用netlink_kernel_create()创建rtnetlink套接字后,将其赋给了相应网络命名空间对象的rtnl指针。
来看看netlink_kernel_create()的原型。
static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
{return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
}
-
第1个参数net 为网络命名空间。
-
第2个参数为Netlink协议。
(例如,NETLINK_ROUTE表示rtnetlink消息,NETLINK_XFRM表示IPsec子系统,NETLINK_AUDIT表示审计子系统)
有20多种Netlink协议,但总数不能超过32(MAX_LINKS ),这是开发通用Netlink协议的原因之一。Netlink协议的完整清单可在include/uapi/linux/netlink.h中找到。 -
第3个参数是一个netlink_kernel_cfg引用。这个结构包含用于创建Netlink套接字的可选参数
-
/* optional Netlink kernel configuration parameters */ struct netlink_kernel_cfg {unsigned int groups;unsigned int flags;void (*input)(struct sk_buff *skb);struct mutex *cb_mutex;int (*bind)(struct net *net, int group);void (*unbind)(struct net *net, int group);bool (*compare)(struct net *net, struct sock *sk); };
-
成员input用于指定回调函数。如果netlink_kernel_cfg的成员input为NULL,内核套接字将无法接收来自用户空间的数据(虽然能够从内核向用户空间发送数据)。对于rtnetlink内核套接字,方法rtnetlink_rcv()将被指定为input回调函数。这样,通过rtnelink套接字从用户空间发送的数据将由rtnetlink_rcv()进行处理。
-
方法netlink_kernel_create()调用方法netlink_insert(),在nl_table表中创建一个条目。对nl_table表的访问由读写锁nl_table_lock进行保护。要在这个表中进行查找,可调用方法netlink_lookup(),并指定协议和端口号。

注册回调函数
**要为特定消息类型注册回调函数,可调用rtnl_register()。**在网络内核代码的多个地方,都注册了这样的回调函数。
- 例如,在rtnetlink_init()中,为一些消息注册了回调函数,如RTM_NEWLINK(新建链路)、RTM_DELLINK(删除链路)、RTM_GETROUTE(转储路由表)等。
- 在netcore/neighbour.c中,为RTM_NEWNEIGH(创建新邻居)、RTM_DELNEIGH(删除邻居)、RTM_GETNEIGHTBL(转储邻居表)等消息注册了回调函数。
- 在FIB代码( ip_fib_init())、组播代码( ip_mr_init() )、IPv6代码和其他地方,还为其他类型的消息注册了回调函数。
使用Netlink内核套接字,首先需要注册它,具体内核源码如下
net\core\rtnetlink.c
void rtnl_register(int protocol, int msgtype,rtnl_doit_func doit, rtnl_dumpit_func dumpit,rtnl_calcit_func calcit)
{if (__rtnl_register(protocol, msgtype, doit, dumpit, calcit) < 0)panic("Unable to register rtnetlink message handler, ""protocol = %d, message type = %d\n",protocol, msgtype);
}
EXPORT_SYMBOL_GPL(rtnl_register);
- 第1个参数是协议簇(如果不针对任何协议,则将其设置为PF_UNSPEC)。完整的协议簇清单请参阅include/linux/socket.h。
- 第2个参数是Netlink消息类型,如RTM_NEWLINK或RTM_NEWNEIGH。rtnelink协议添加了一些专用的Netlink消息类型。完整的消息类型清单请参阅include/uapi/linux/rtnetlink.h,
- 最后3个参数为回调函数doit 、 dumpit和calcit。这些回调函数指定了为处理消息而要执行的操作(通常只指定其中的一个)。
回调函数doit用于指定添加、删除、修改等操作,回调函数dumpit用于检索信息,回调函数calcit用于计算缓冲区大小。rtnetlink模块中有一个名为rtnl_msg_handlers的表。这个表将协议号用作索引。其中的每个条目本身都是一个表,并将消息类型作为其索引。表中的每个元素都是一个rtnl_link实例——由指向上述3个回调函数的指针组成的结构。使用rtnl_register()注册回调函数时,指定的回调函数将被加人到这个表中。
rtnelink消息是使用rtmsg_ifinfo()发送的。例如,在dev_open()中,使用下面的代码创建一条新链路: rtmsg_ifinfo(RTM_NEWLINK,dev,IFF_UP|IFF_RUNNING)。在方法rtmsg_ifinfo()中,首先调用方法nlmsg_new()来分配一个大小合适的sk_buff。然后创建两个对象:Netlink消息报头( nlmsghdr )和ifinfomsg对象。后者紧跟在Netlink消息报头后面。这两个对象由方法rtnl_fill_ifinfo()初始化。接下来,调用rtnl_notify()来发送数据包。数据包的实际发送工作是由通用Netlink方法nlmsg_notify()完成的。
Netlink消息报头
Netlink消息必须采用特定的格式,此格式是在RFC 3549中进行规定的。Netlink消息的开头是长度固定的Netlink报头,其后是有效载荷。
Netlink报头
struct nlmsghdr {__u32 nlmsg_len; /* Length of message including header */__u16 nlmsg_type; /* 消息类型 */__u16 nlmsg_flags; /* Additional flags */__u32 nlmsg_seq; // 序列号,用于排列消息。与有些第4层传输协议不同,Netlink并未要求必须使用序列号。// 发送端口的ID。对于从内核发送的消息,nlmsg_pid为0;对于从用户空间发送的消息,可将nlmsg_pid设置为发送消息的用户空间应用程序的进程ID。__u32 nlmsg_pid;
};
-
nlmsg_type:消息类型。有如下4种基本的Netlink消息类型。
- NLMSG_NOOP:不执行任何操作,必须将消息丢弃。
- NLMSG_ERROR:发生了错误。
- NLMSG_DONE:标识由多部分组成的消息的末尾。
- NLMSG_OVERRUN:缓冲区溢出通知,表示发生了错误,数据已丢失。
- 然而,协议簇可添加自己的Netlink消息类型。例如,rtnetlink协议簇添加了RTM_NEWLINK、RTM_DELLINK、RTM_NEWROUTE等众多的消息类型,详情请参阅include/uapi/linux/ rtnetlink.h。要查看rtnelink协议簇添加的所有Netlink消息类型以及每种消息类型的详细说明,请参阅man 7 rtnetlink。请注意,小于NLMSG_MIN_TYPE(Ox10)的消息类型值保留,用于控制消息,因此不可用。
-
nlmsg_flags字段的可能取值如下。
- NLM_F_REQUEST:消息为请求消息。
- NLM_F_MULTI:消息由多部分组成。由多部分组成的消息用于转储表。消息的长度通常不超过一页( PAGE_SIZE),因此大型消息会被划分成多条小型消息,并对每条消息都设置NLM_F_MULTI标志。但最后一条消息除外。对这条消息设置标志NLMSG_DONE。
- NLM_F_ACK:希望接收方使用ACK对消息进行应答。Netlink ACK消息是由方法netlink_ack() ( net/netlink/af_netlink.c )发送的。
- NLM_F_DUMP:检索有关表/条目的信息。
- NLM_F_ROOT:指定树根。
- NLM_F_MATCH:返回所有匹配的条目。
- NLM_F_ATOMIC:这个标志已摈弃。
下面的标志是针对条目创建的说明符。
- NLM_F_REPLACE:覆盖既有条目。
- NLM_F_EXCL:保留既有条目不动。
- NLM_F_CREATE:创建条目(如果它不存在)。
- NLM_F_APPEND:在列表末尾添加条目。
- NLM_F_ECHO:回应当前请求。
这里只列出了最常用的标志,完整的标志清单请参阅include/uapi/linux/netlink.h。
有效载荷
紧跟在报头后面的是有效载荷。
- **Netlink消息的有效载荷是一组使用格式“类型-长度-值”(TLV)表示的属性。**在TLV中,类型和长度字段的长度固定,通常为1~4字节,而值字段的长度是可变的。TLV表示法还被用于网络代码的其他地方,如IPv6(请参阅RFC2460 )。TLV的灵活性让未来的扩展实现起来更容易。其属性可以嵌套,让你能够创建复杂的属性树结构。
- Netlink属性头由结构nlattr定义。
/** <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->* +---------------------+- - -+- - - - - - - - - -+- - -+* | Header | Pad | Payload | Pad |* | (struct nlattr) | ing | | ing |* +---------------------+- - -+- - - - - - - - - -+- - -+* <-------------- nlattr->nla_len -------------->*/struct nlattr {__u16 nla_len; // 长度__u16 nla_type;
};
-
nla_type:属性的类型。
nla_type的可能取值包括
- NLA_U32(表示32位无符号整数)、NLA_STRING(表示变长字符串)、NLA_NESTED(表示嵌套属性)、NLA_UNSPEC(表示类型和长度未知)等。完整的属性类型列表请参阅include/net/netlink.h。
所有的Netlink属性都必须与4字节边界( NLA_ALIGNTO)对齐。
每个协议簇都可定义属性有效性策略,即对收到的属性的期望。这种有效性策略用nla_policy对象表示。事实上,结构nla_policy内容与结构nlattr完全相同。
struct nla_policy {u16 type;u16 len; };
属性有效性策略是一个nla_policy对象数组。这个数组将属性号用作索引。对于每个属性(定长属性除外),如果nla_policy对象的len值为0,就不执行有效性检查。如果属性的类型为字符串(如NLA_STRING),则len值应为字符串的最大长度(不包括末尾的NULL字节);如果属性的类型为NLA_UNSPEC(或未知),应将len设置为属性有效载荷的长度;如果属性的类型为NLA_FLAG,将不使用len(其中的原因在于,属性存在就表示true,属性不存在就表示false )。
NETLINK_ROUTE消息
rtnetlink (NETLINK_ROUTE)消息(协议)并非仅限于网络路由选择子系统消息,还包括邻接子系统消息﹑接口设置消息、防火墙消息.Netlink排队消息、策略路由消息以及众多其他类型的rtnetlink消息。
NETLINK_ROUTE消息分为多个消息簇:
- LINK(网络接口)
- ADDR(网络地址)
- ROUTE(路由选择消息)
- NEIGH(邻接子系统消息)
- RULE(策略路由规则)
- QDISC(排队准则)
- TCLASS(流量类别)
- ACTION(数据包操作API,参见net/sched/act_api.c )
- NEIGHTBL(邻接表)
- ADDRLABEL(地址标记)
每个消息簇都分为3类,分别用于创建、删除和检索信息。因此,路由选择消息包含用于创·建路由的消息类型RTM_NEWROUTE、用于删除路由的消息类型RTM_DELROUTE以及用于检索路由的消息类型RTM_GETROUTE。对于LINK消息簇,除用于创建、删除和信息检索的消息类型外,还有用于修改链路的消息类型:RTM_SETLINK。
iproute2通过netlink操作路由的例子
下面从Netlink协议的角度出发,看看添加和删除路由选择条目时在内核中发生的情况。
要在路由选择表中添加路由选择条目,可运行类似于下面的命令。
ip route add 192.168.2.11 via 192.168.2.20
- 这个命令通过rtnetlink套接字,从用户空间发送一条添加路由选择条目的Netlink消息(RTM_NEWROUTE)。这条消息由rtnetlink内核套接字接收,并由方法rtnetlink_rcv()处理。
- 最终,添加路由选择条目的工作是通过调用netipv4/fib_frontend.c中的inet_rtm_newroute()完成的。接下来,由方法fib_table_insert()完成插入转发信息库( FIB,即路由选择数据库)的工作。
- 然而,插入路由选择表并不只是fib_table_insert()的唯一任务。它还需要通知所有注册了RTM_NEWROUTE消息的侦听者。如何通知呢?
- 在插入新路由选择条目时,调用方法rtmsg_fib(),并将RTM_NEWROUTE作为参数。方法rtmsg_fib()创建一条Netlink消息,并通过调用rtnl_notify()来发送它,从而通知加入了RTNLGRP_IPV4_ROUTE组播组的所有侦听者。可在内核注册这些RTNLGRP_IPV4_ROUTE侦听者,也可在用户空间中注册( iproute2就是这样做的),还可在路由选择守护程序(如xorp)中注册。稍后你将看到,iproute2的用户空间守护程序是如何加入各种rtnelink组播组的。
删除路由选择条目时,情况极其相似。
- 在插入新路由选择条目时,调用方法rtmsg_fib(),并将RTM_NEWROUTE作为参数。方法rtmsg_fib()创建一条Netlink消息,并通过调用rtnl_notify()来发送它,从而通知加入了RTNLGRP_IPV4_ROUTE组播组的所有侦听者。可在内核注册这些RTNLGRP_IPV4_ROUTE侦听者,也可在用户空间中注册( iproute2就是这样做的),还可在路由选择守护程序(如xorp)中注册。稍后你将看到,iproute2的用户空间守护程序是如何加入各种rtnelink组播组的。
要删除前面添加的路由选择条目,可执行如下命令。
ip route del 192.168.2.11
- 这个命令通过rtnetlink套接字,从用户空间发送一条删除路由选择条目的Netlink 消息( RTM_DELROUTE)。这条消息也由rtnetlink内核套接字接收,并由回调函数rtnetlink_rcv()处理。
- 最终,删除路由选择条目的工作是通过调用net/ipv4/fib_frontend.c中的回调函数inet_rtmdelroute()完成的。接下来,由fib_table_delete()完成从FIB中删除的工作。它调用rtmsg_fib().但将RTM_DELROUTE消息作为参数。
可像下面这样使用iproute2命令ip来监视网络事件。ip monitor route
- 例如,如果你打开一个终端窗口,并在其中运行命令ip monitor route,再打开另一个终端窗口,并在其中运行命令ip route add 192.168.1.10 via 192.168.2.200,则在第一个终端窗口中将看到如下输出:192.168.1.10 via 192.168.2.200 dev em1。而当你在第二个终端窗口中运行命令ip route del 192.168.1.10时,第一个终端窗口中将出现如下文本:Deleted 192.168.1.10 via192.168.2.200 dev em1。
执行命令ip monitor route时,将启动一个守护程序,它将打开一个Netlink套接字,**并加入RTNLGRP_IPV4_ROUTE组播组。**这样,像前面的示例那样添加/删除路由时,将导致如下结果:使用rtnl_notify()发送的消息将被守护程序接收,并显示在终端窗口中。
可以以这种方式加人其他组播组。
- 例如,要加入RTNLGRP_LINK组播组,可运行命令ip monitor link。
- 添加/删除链路时,这个守护程序将接收来自内核的Netlink消息。因此,如果你打开一个终端窗口,并在其中运行命令ip monitor link,再打开另一个终端窗口,并通过执行命令vconfig add eth1 200添加一个VLAN接口,将在第一个终端窗口中看到类似于下面的内容。
4: eth1.200@eth1: <BROADCAST,MLUILTICAST> mtu 1500 qdisc noop state DOwN
link/ether 00:eo:4C: 53:44:58 brd ff:ff:ff:ff:ff:ff - 如果你在第一个终端窗口中使用命令brctl addbr mybr添加一个网桥,将在第一个终端窗口中看到类似于下面的内容。
5: mybr: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DONN
link/ether a2:7c:be:62:b5:b6 brd ff:ff:ff:ff:ff:ff
- 添加/删除链路时,这个守护程序将接收来自内核的Netlink消息。因此,如果你打开一个终端窗口,并在其中运行命令ip monitor link,再打开另一个终端窗口,并通过执行命令vconfig add eth1 200添加一个VLAN接口,将在第一个终端窗口中看到类似于下面的内容。
至此,你知道了Netlink消息是什么以及如何创建和处理它们,还知道如何处理Netlink套接字。接下来,你将学习开发通用Netlink簇(在内核2.6.15中首次引入)的原因及其Linux实现。
Netlink协议的一个缺点是,协议簇数不能超过32(MAX_LINKS)个。这是开发通用Netlink簇的主要原因之一——旨在支持添加更多的协议簇。它就像是Netlink多路复用器,使用单个Netlink协议簇(NETLINK_GENERIC)。通用Netlink协议以Netlink协议为基础,并使用其API。
通用Netlink消息
通用Netlink消息的开头是一个Netlink报头,接下来是通用Netlink消息报头以及可选的用户特定报头,然后才是可选的有效载荷,如下图所示。
struct genlmsghdr {__u8 cmd;__u8 version;__u16 reserved; };
cmd:通用Netlink消息类型。你注册的每个通用簇都将添加自己的命令。
version:可用于提供版本控制支持。版本号的作用是:能够在不破坏向后兼容性的情况下修改消息的格式。
reserved:保留,供以后使用。
为通用Netlink消息分配缓冲区的工作由下面的方法完成。sk_buff *genlmsg_new(size_t payload,gfp_t flags)
它实际上是一个nlmsg_new()包装器。使用genlmsg_new()分配缓冲区后,调用genlmsg_put()来创建通用Netlink报头。它是一个genlmsghdr实例。单播通用Netlink消息是使用genlmsg_unicast()发送的。它实际上是一个nlmsg_unicast()包装器。发送组播通用Netlink消息的方式有如下两种。
genlmsg_multicast(): 此方法将消息发送到默认网络命名空间net_init()。
qenlmsg multicast allns():此方法将消息发送到所有网络命名空间。
套接字的监视接口
CRIU(Checkpoint/Restore In Userspace)运行在linux操作系统上的一个软件工具,其功能是在用户空间实现Checkpoint/Restore功能。使用这个工具,你可以冻结一个正在运行的程序,并且checkpoint它到一系列的文件,然后你就可以使用这些文件在任何主机重新恢复这个程序到被冻结的那个点(白话就是实现对已运行程序的备份和恢复)。所以criu通常被用在程序或者容器的热迁移、快照、远程调试等。CRIU 起初是Virtuozzo的一个项目,随着开源社区的帮助,现在也被整合到OpenVZ(它是 Virtuozzo 的开源版本), LXC/LXD, Docker, Podman等软件项目里。
Netlink套接字sock_diag提供了一个基于Netlink的子系统,可用于获取有关套接字的信息。在内核中添加它旨在在Linux用户空间中支持检查点/恢复功能(CRIU)。要支持这项功能,还需要有关套接字的其他数据。 例如,/procfs并没有指出UNIX域套接字(AF_UNIX)的对等体。然而,若要支持检查点/恢复,则必须有这种信息。通过/proc无法导出这种额外信息,而修改procfs条目也并非总是可行的选择,因为这样做可能破坏用户空间应用程序。
Netlink套接字sock_diag提供了一个API,让你能够访问这种额外数据。 CRIU项目和实用工具ss都使用了这个API。为进程建立检查点(将进程的状态存储到文件系统)后,如果不使用sock_diag,将无法重建其UNIX域套接字,因为你不知道对等体都有谁。
**为支持工具ss使用的监视接口,开发了一种基于Netlink 的内核套接字——NETLINK_SOCK_DIAG。**iproute2包中的工具ss让你能够获取套接字的统计信息,其方式类似于Netstat。相比于其他工具,它显示的TCP和状态信息更多。
创建Netlink内核套接字sock_diag的方式如下。
static int __net_init diag_net_init(struct net *net)
{struct netlink_kernel_cfg cfg = {.groups = SKNLGRP_MAX,.input = sock_diag_rcv,.bind = sock_diag_bind,.flags = NL_CFG_F_NONROOT_RECV,};net->diag_nlsk = netlink_kernel_create(net, NETLINK_SOCK_DIAG, &cfg);return net->diag_nlsk == NULL ? -ENOMEM : 0;
}
sock_diag模块包含一个名为sock_diag_handlers的表,其中包含一系列sock_diag_handler对象。这个表将协议号用作索引(完整的协议号清单请参阅include/linux/socket.h)。
每个需要在此表中添加套接字监视接口条目的协议都会首先定义一个处理程序,然后再调用sock_diag_register(),并指定其处理程序。
本章介绍了Netlink套接字。它提供了一种在用户空间和内核间进行通信的机制,被广泛用于网络子系统。在此你见到了一些Netlink套接字使用示例。另外,本章还讨论了Netlink消息以及它们是被如何创建和处理的。本章探讨的另一个重要主题是通用Netlink套接字,介绍了其优点和用途。
实际使用例子
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>#define NETLINK_TEST 30 // 未被使用的协议号
#define MSG_LEN 125
#define USER_PORT 100MODULE_LICENSE("GPL");
MODULE_AUTHOR("vico");
MODULE_DESCRIPTION("netlink protocol example");struct sock *nlsk = NULL;
extern struct net init_net;int send_usrmsg(char *pbuf, uint16_t len)
{struct sk_buff *nl_skb;struct nlmsghdr *nlh;int ret;/* 创建sk_buff 空间 */nl_skb = nlmsg_new(len, GFP_ATOMIC);if(!nl_skb){printk("\nError:netlink alloc failure.\n\n");return -1;}/* 设置netlink消息头部 */nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);if(nlh == NULL){printk("\nError:nlmsg_put failaure. \n\n");nlmsg_free(nl_skb);return -1;}/* 拷贝数据发送 */memcpy(nlmsg_data(nlh), pbuf, len);ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);return ret;
}static void netlink_rcv_msg(struct sk_buff *skb)
{struct nlmsghdr *nlh = NULL;char *umsg = NULL;char *kmsg = "Hello World!";if(skb->len >= nlmsg_total_size(0)){nlh = nlmsg_hdr(skb);umsg = NLMSG_DATA(nlh);if(umsg){printk("kernel recv from user: %s\n", umsg);send_usrmsg(kmsg, strlen(kmsg));}}
}struct netlink_kernel_cfg cfg = {.input = netlink_rcv_msg, /* set recv callback */
};int test_netlink_init(void)
{/* create netlink socket */nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);if(nlsk == NULL){printk("\nError:netlink_kernel_create error !\n");return -1;}printk("\ntest_netlink_init\n");return 0;
}void test_netlink_exit(void)
{if (nlsk){netlink_kernel_release(nlsk); /* release ..*/nlsk = NULL;}printk("test_netlink_exit!\n");
}module_init(test_netlink_init);
module_exit(test_netlink_exit);