前面有一篇文章其实已经介绍过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_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 socket 的情况,字段nl_pid 则可以设置为其它的值,如:
pthread_self() << 16 | getpid();
因此字段 nl_pid 实际上未必是进程 ID,它只是用于区分不同的接收者或发送者的一个标识,用户可以根据自己需要设置该字段。
(4) __u32 nl_groups;
绑定时用于指定绑定者所要加入的多播组,这样绑定者就可以接收多播消息,发送 消息时可以用于指定多播组,这样就可以将消息发给多个接收者。这里nl_groups 为32位的无符号整形,所以可以指定32个多播组,每个进程可以加入多个多播组, 因为多播组是通过“或”操作,如果设置为 0,表示调用者不加入任何多播组。这里就是Netlink多播的概念!和通信中的多播概念有点类似。
Netlink是一种内核层与应用层通信的一种机制,比如说在做一个内核模块的时候,往往会需要应用层提供一些配置信息,这时候就可以使用netlink。netlink包括内核层和应用层,内核层注册一个netlink协议,然后定义一些消息类型,之后就可以等待来自应用层的消息了。这篇博文给出一个简单的netlink内核模块使用例子,应用层会在接下来的博文中给出。
内核版本:3.4.39。 不同内核版本可能带来编译问题,可以升级内核来解决。升级3.4.39内核可以参考这一篇博客(https://blog.csdn.net/fuyuande/article/details/79429441)
netlink 内核端代码如下:nlkernel.c
/*
* Description : 内核netlink编程
* Date :20180528
* Author :mason
* Mail : mrsonko@126.com
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <net/ip.h>
#include "nlkernel.h"
static int number;
static struct sock *nlsock = NULL; // netlink 套接字
// netlink消息回复,用于向应用层传递数据
static void netlink_sendto_userapp(unsigned int dst_pid) {
struct sk_buff *skb = NULL;
struct nlmsghdr *nlh = NULL;
int datalen;
int *pnum;
datalen = NLMSG_SPACE(sizeof(int));
skb = alloc_skb(datalen, GFP_KERNEL);
if(!skb)
{
log("alloc skb error.\r\n");
return ;
}
// 数据初始化
nlh = nlmsg_put(skb, 0, 0, 0, sizeof(int), 0);
nlh->nlmsg_pid = 0;
nlh->nlmsg_type = NLKERNEL_GET;
pnum = (int *)NLMSG_DATA(nlh);
*pnum = number;
netlink_unicast(nlsock, skb, dst_pid, MSG_DONTWAIT);
log("netlink send done \r\n");
return;
}
/* netlink消息处理函数 */
static int netlink_kernel_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
int *value = NULL;
switch(nlh->nlmsg_type){
// 设置
case NLKERNEL_SET :
log("kernel receive netlink set msg!\r\n");
value = (int *)NLMSG_DATA(nlh);
number = *value;
break;
// 获取
case NLKERNEL_GET :
log("kernel receive netlink get msg!\r\n");
netlink_sendto_userapp(nlh->nlmsg_pid);
break;
default:
log("unrecognized netlink message type : %u \r\n",nlh->nlmsg_type);
break;
}
return 0;
}
static void netlink_kernel_rcv(struct sk_buff *skb)
{
int res;
res = netlink_rcv_skb(skb, &netlink_kernel_rcv_msg);
return;
}
// 模块入口函数
static int __init nlkernel_init(void) {
log("nlkernel init \r\n");
// 注册netlink协议
nlsock = netlink_kernel_create(&init_net, NETLINK_TEST_MODULE, 0, netlink_kernel_rcv, NULL, THIS_MODULE);
if (!nlsock) {
log("netlink module init fail \r\n");
return -1;
}
return 0;
}
// 模块退出函数
static void __exit nlkernel_exit(void) {
// 注销netlink协议
if(nlsock)
{
netlink_kernel_release(nlsock);
nlsock = NULL;
}
log("nlkernel exit \r\n");
return ;
}
module_init(nlkernel_init)
module_exit(nlkernel_exit)
MODULE_AUTHOR("mason");
MODULE_DESCRIPTION("netlink kernel test");
MODULE_LICENSE("GPL");
nlkernel.h
#ifndef __NLKERNEL_H__
#define __NLKERNEL_H__
#define log(fmt, arg...) printk(KERN_INFO"[bfd] %s:%d "fmt, __FUNCTION__, __LINE__, ##arg)
#ifndef NIPQUAD
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
#endif
#define NETLINK_TEST_MODULE 17 /* 定义 netlink 协议, */
typedef enum netlink_msg_type { /* 定义 netlink 消息类型 */
NLKERNEL_GET = NLMSG_MIN_TYPE +1, /* value : 17 */
NLKERNEL_SET, /* value : 18 */
NLKERNEL_END,
}NETLINK_MSG_TYPE;
#endif
Makefile:
obj-m := nlkernel.o
PWD := $(shell pwd)
KERNEL_DIR := "/usr/src/linux-headers-"$(shell uname -r)/
modules:
@$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
clean:
@rm -rf *.ko *.o *.mod.c *symvers *order *cmd
代码托管在github 上:
git@github.com:FuYuanDe/nlnetlink.git
编译完成后加载模块查看效果:
insmod nlkernel.ko
dmesg
Linux下检测网卡与网线连接状态,使用ioctl向socket发送SIOCETHTOOL
命令字。
link_stat.c
#include <stdio.h> |
之前有一篇文章《Netlink实现Linux内核与用户空间通信》专门介绍了Netlink相比其他内核交互方式的优点以及Netlink的调用方法,并以NETLINK_KOBJECT_UEVENT(内核事件向用户态通知)为例演示了U盘热插拔信息的捕捉,衍生出另一篇文章《Linux下自动检测USB热插拔》,今天尝试用Netlink来捕捉一下网络接口信息,实现的主要功能是实时打印发生变化的网络接口的序列号、上下线状态和接口名称。
为了创建一个 netlink socket,用户需要使用如下参数调用 socket():
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <asm/types.h>
- #include <linux/netlink.h>
- #include <linux/rtnetlink.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <sys/ioctl.h>
- #include <linux/if.h>
- #include <string.h>
- #define BUFLEN 20480
- int main(int argc, char *argv[])
- {
- int fd, retval;
- char buf[BUFLEN] = {0};
- int len = BUFLEN;
- struct sockaddr_nl addr;
- struct nlmsghdr *nh;
- struct ifinfomsg *ifinfo;
- struct rtattr *attr;
- fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
- setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len));
- memset(&addr, 0, sizeof(addr));
- addr.nl_family = AF_NETLINK;
- addr.nl_groups = RTNLGRP_LINK;
- bind(fd, (struct sockaddr*)&addr, sizeof(addr));
- while ((retval = read(fd, buf, BUFLEN)) > 0)
- {
- for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, retval); nh = NLMSG_NEXT(nh, retval))
- {
- if (nh->nlmsg_type == NLMSG_DONE)
- break;
- else if (nh->nlmsg_type == NLMSG_ERROR)
- return;
- else if (nh->nlmsg_type != RTM_NEWLINK)
- continue;
- ifinfo = NLMSG_DATA(nh);
- printf("%u: %s", ifinfo->ifi_index,
- (ifinfo->ifi_flags & IFF_LOWER_UP) ? "up" : "down" );
- attr = (struct rtattr*)(((char*)nh) + NLMSG_SPACE(sizeof(*ifinfo)));
- len = nh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));
- for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
- {
- if (attr->rta_type == IFLA_IFNAME)
- {
- printf(" %s", (char*)RTA_DATA(attr));
- break;
- }
- }
- printf("\n");
- }
- }
- return 0;
- }
https://github.com/FuYuanDe/nlnetlink
转载:
https://blog.csdn.net/fuyuande/article/details/80489988
https://blog.csdn.net/zcabcd123/article/details/8272423
https://blog.csdn.net/sourthstar/article/details/7975999
http://blog.chinaunix.net/uid-20357359-id-1963608.html?page=2