netlink原理及应用

什么是netlink

netlink是一种基于网络的通信机制,允许内核内部、内核与用户态应用之间甚至用户态应用之间进行通信;netlink的主要作用是内核与用户态之间通信;它的思想是,基于BSD的socket使用网络框架在内核和用户态之间进行通信;

为什么要有netlink

内核中有其他一些方法可以实现用户空间和内核通信,如procfs、sysfs和ioctrl等;netlink相比于这些方法,有以下优势:

  • 任何一方都不需要轮询;如果通过文件通信,用户态应用需要不断检查是否有新消息到达;
  • netlink使用简单,它是基于socket的,可以使用socket api;
  • 只需要在netlink协议族中新增加一个协议;使用netlink的内核部分可以采用模块的方式实现,之后使用socket api进行通信;
  • 内核可以直接向用户层发送信息,而无需用户层事先请求;
  • netlink支持单播、组播;内核模块可以把消息发送到一个多播组;

数据结构

struct sockaddr_nl

netlink是基于网络的,使用socket通信;类似于其它网络协议,每个netlink socket都需要分配一个地址;struct sockaddr_nl表示netlink地址;

struct sockaddr_nl {__kernel_sa_family_t	nl_family;	/* AF_NETLINK	*/unsigned short	nl_pad;		/* zero		*/__u32		nl_pid;		/* port ID	*/__u32		nl_groups;	/* multicast groups mask */
};
  • nl_family,固定为AF_NETLINK,表示netlink协议族;

netlink协议族包含多个协议,最大值32;理论上32以内未被占用的协议号,可以用于自定义netlink协议,但这种方法并不规范,对于未来更新内核版本兼容性不友好;更加合适的方法,是在generic netlink协议族中,添加子协议,如nl80211就是generic netlink的一个子协议;

#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	/* Unused number, formerly ip_queue		*/
#define NETLINK_SOCK_DIAG	4	/* 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 NETLINK_CRYPTO		21	/* Crypto layer */
#define NETLINK_SMC		22	/* SMC monitoring */#define NETLINK_INET_DIAG	NETLINK_SOCK_DIAG#define MAX_LINKS 32	
  • nl_pid,socket的唯一标识符;对内核自身来说,该字段是0,而用户空间的应用程序通常使用其线程组ID;netlink并没有要求该字段是进程ID,它可以是任何值,只需要保证其唯一性;使用线程组ID不过是方便而已;nl_pid是一个单播地址;
  • nl_groups,多播组掩码,每个bit表示一个多播组;每个netlink协议族最多支持32个多播组;

netlink内核核心函数

netlink_kernel_create

内核创建netlink socket;

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);
}
  • net,表示网络命令空间;
  • uint,表示netlink子协议族,如:
#define NETLINK_ROUTE		0	/* Routing/device hook				*/
#define NETLINK_GENERIC		16
  • cfg,netlink kernel创建socket的可选参数;其中,input是该内核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);
};

netlink消息格式

netlink消息由两部分组成:消息头和消息体;消息头固定为16字节,消息体长度可变;
image.png

消息头

消息头定义如下:

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 port ID */
};
  • nlmsg_len,整个消息的长度,包括消息头;
  • nlmsg_type,消息类型,netlink定义一下四种通用消息类型
#define NLMSG_NOOP		0x1	/* Nothing.		*/
#define NLMSG_ERROR		0x2	/* Error		*/
#define NLMSG_DONE		0x3	/* End of a dump	*/
#define NLMSG_OVERRUN		0x4	/* Data lost		*/#define NLMSG_MIN_TYPE		0x10	/* < 0x10: reserved control messages */
  • nlmsg_flags,消息标志;如NLM_F_REQUEST
/* Flags values */#define NLM_F_REQUEST		0x01	/* It is request message. 	*/
#define NLM_F_MULTI		0x02	/* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK		0x04	/* Reply with ack, with zero or error code */
#define NLM_F_ECHO		0x08	/* Echo this request 		*/
#define NLM_F_DUMP_INTR		0x10	/* Dump was inconsistent due to sequence change */
#define NLM_F_DUMP_FILTERED	0x20	/* Dump was filtered as requested *//* 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		*//* Flags for ACK message */
#define NLM_F_CAPPED	0x100	/* request was capped */
#define NLM_F_ACK_TLVS	0x200	/* extended ACK TVLs were included */
  • nlmsg_seq,消息序列号,表示一系列消息之间在时间上的前后关系;也可以通过request消息和ack消息使用相同的序列号,保证消息不丢失;
  • nlmsg_pid,消息发送者的port id;

消息体

netlink协议并没有严格要求消息体的格式,可以发送任意消息;但一般标准做法,消息体是用nlattr,即属性,采用tlv的形式;消息体组织形式如下:
image.png

struct 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;
};

netlink协议族组织形式

netlink协议族、子协议族、子协议、命令,组织结构如下:
image.png

如何新增netlink子协议族

如何将自定义netlink协议加入到netlink协议族中,于NETLINK_GENERIC同一级别?只需定义一个netlink协议号即可,由于netlink对消息体格式不做强制要求,可以传输简单的字符串;实际使用中,不建议这样做,但作为学习,可以简单的这样操作;实际使用中增加自定义netlink协议,建议加入到NETLINK_GENERIC协议族中,类似nl80211这样;
下面代码,是直接在netlink中直接加入新的协议,定义协议号为30;内核中新增一个模块,处理该协议的消息;应用程序通过该协议,和内核通信;简单起见,直接传输字符串;应用程序先向内核发送一条消息,内核收到消息后进行回复;

内核代码

内核代码如下:


#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            125MODULE_LICENSE("GPL");struct sock *nlsk = NULL;
extern struct net init_net;int send_usrmsg(char *pbuf, uint16_t len, uint32_t pid)
{struct sk_buff *nl_skb;struct nlmsghdr *nlh;int ret;/* Allocate a new netlink message */nl_skb = nlmsg_new(len + 1, GFP_ATOMIC);if(!nl_skb){printk("\nError:netlink alloc failure.\n\n");return -1;}/* Add a new netlink message to an skbpid是0,说明是从内核发送的*/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;}/* copy payload */memcpy(nlmsg_data(nlh), pbuf, len);ret = netlink_unicast(nlsk, nl_skb, pid, MSG_DONTWAIT);return ret;
}static void netlink_rcv_msg(struct sk_buff *skb)
{struct nlmsghdr *nlh = NULL;char *umsg = NULL;char *kmsg = "Hello user's program.";if(skb->len >= nlmsg_total_size(0)){nlh = nlmsg_hdr(skb);umsg = NLMSG_DATA(nlh);if(umsg){printk("kernel recv from user space: %s\n", umsg);send_usrmsg(kmsg, strlen(kmsg), nlh->nlmsg_pid);}}
}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);
#
#Desgin of Netlink
#
MODULE_NAME :=nl_test_kernel
obj-m:=$(MODULE_NAME).oKERNELDIR ?=/lib/modules/$(shell uname -r)/build
PWD :=$(shell pwd)all:$(MAKE) -C $(KERNELDIR) M=$(PWD)clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

nl_test_kernel.cMakefile放到同一目录下;直接make,编译生成nl_test_kernel.ko
insmod nl_test_kernel.ko,将该模块加载到内核中;内核现在就可以处理NETLINK_TEST的消息了;
image.png

应用程序代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>#define NETLINK_TEST    30
#define MSG_LEN         125
#define MAX_PLOAD       125typedef struct _user_msg_info
{struct nlmsghdr hdr;char  msg[MSG_LEN];
} user_msg_info;int main(int argc, char **argv)
{int skfd;int ret;user_msg_info u_info;socklen_t len;struct nlmsghdr *nlh = NULL;struct sockaddr_nl saddr, daddr;char *umsg = "Hello Netlink protocol.";/* 创建NETLINK socket */skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);if(skfd == -1){perror("\nError:Create socket error.\n");return -1;}memset(&saddr, 0, sizeof(saddr));saddr.nl_family = AF_NETLINK; //AF_NETLINKsaddr.nl_pid = getpid();  //端口号(port ID)saddr.nl_groups = 0;if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0){perror("\nError:bind() error.\n");close(skfd);return -1;}memset(&daddr, 0, sizeof(daddr));daddr.nl_family = AF_NETLINK;daddr.nl_pid = 0; // to kerneldaddr.nl_groups = 0;nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));memset(nlh, 0, sizeof(struct nlmsghdr));nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);nlh->nlmsg_flags = 0;nlh->nlmsg_type = 0;nlh->nlmsg_seq = 0;nlh->nlmsg_pid = saddr.nl_pid; //self portmemcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));if(!ret){perror("\nError:sendto error.\n");close(skfd);exit(-1);}printf("\nApplication-->Send to kernel:%s\n\n", umsg);memset(&u_info, 0, sizeof(u_info));len = sizeof(struct sockaddr_nl);ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);if(!ret){perror("\nError:recv form kernel error.\n");close(skfd);exit(-1);}printf("\nApplication-->From kernel:%s\n\n", u_info.msg);close(skfd);free((void *)nlh);return 0;
}

gcc -o nl_test_user nl_test_user.c

测试结果

image.png

如何新增自定义netlink协议

如何在NETLINK_GENERIC中新增netlink协议?
参考nl80211

模块初始化时,通过genl_register_family注册通用netlink协议族,将命令以及处理函数进行注册;

/* initialisation/exit functions */int __init nl80211_init(void)
{int err;err = genl_register_family(&nl80211_fam);if (err)return err;err = netlink_register_notifier(&nl80211_netlink_notifier);if (err)goto err_out;return 0;err_out:genl_unregister_family(&nl80211_fam);return err;
}
/*** genl_register_family - register a generic netlink family* @family: generic netlink family** Registers the specified family after validating it first. Only one* family may be registered with the same family name or identifier.** The family's ops, multicast groups and module pointer must already* be assigned.** Return 0 on success or a negative error code.*/
int genl_register_family(struct genl_family *family)
static const struct genl_ops nl80211_ops[] = {{.cmd = NL80211_CMD_GET_WIPHY,.doit = nl80211_get_wiphy,.dumpit = nl80211_dump_wiphy,.done = nl80211_dump_wiphy_done,.policy = nl80211_policy,/* can be retrieved by unprivileged users */.internal_flags = NL80211_FLAG_NEED_WIPHY |NL80211_FLAG_NEED_RTNL,},{.cmd = NL80211_CMD_SET_WIPHY,.doit = nl80211_set_wiphy,.policy = nl80211_policy,.flags = GENL_UNS_ADMIN_PERM,.internal_flags = NL80211_FLAG_NEED_RTNL,},......
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/512771.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据存储】大端存储||小端存储(超详细解析,小白一看就懂!!!)

目录 一、前言 二、什么是低地址、高地址 &#xff1f; 三、什么是数据的高位和低位 &#xff1f; 四、什么是大小端存储&#xff1f; &#x1f349; 小端存储详解 &#x1f352; 大端存储详解 五、为什么会有大小端存储&#xff1f; &#x1f34d;大端存储的优点 &#…

Java多线程——CyclicBarrier 与 CountDownLatch 区别,如何线程间数据交换?

目录 引出CyclicBarrier 与 CountDownLatch 区别线程间数据交换&#xff1f;Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 Java多线程——CyclicBarrier 与 CountDownLatch 区别&#xff0c;如何线程间数据交换&#xff1f;…

开发知识点-前端-layUI

layui layertabletable render <script type"text/html" id"buttonTpl">{{# if(d.check true){ }}<button class"layui-btn layui-btn-xs">已审核</button>{{# } else { }}<button class"layui-btn layui-btn-prim…

Frontend - Boostrap 消息弹窗

目录 一、下载 &#xff08;一&#xff09;中文官网 &#xff08;二&#xff09;bootstrap v3 依赖 jQuery 插件 二、解压并安装 &#xff08;一&#xff09;解压 1. 压缩包解压 2. 简化文件 &#xff08;二&#xff09;安装 三、配置 &#xff08;一&#xff09;bas…

【prompt五】CoCoOP:Conditional Prompt Learning for Vision-Language Models

motivation 随着像CLIP这样强大的预训练视觉语言模型的兴起,研究如何使这些模型适应下游数据集变得至关重要。最近提出的一种名为上下文优化(CoOp)的方法将提示学习(nlp的最新趋势)的概念引入视觉领域,以适应预训练的视觉语言模型。具体来说,CoOp将提示中的上下文单词转换为…

【数据库-黑马笔记】基础-SQL

本文参考b站黑马数据库视频,总结详细全面的笔记 ,可结合视频观看1~23集 MYSQL 的基础知识框架如下 一、MYSQL概述 1、数据库相关概念 2、MYSQL的安装及启动 下载过程请自行观看黑马视频进行下载 下面说明启动和停止方式: ①、win+r 然后输入services.msc 然后在打开…

vue实现xml,sql,JSON自动格式化高亮

实现xml&#xff0c;json&#xff0c;sql代码组件格式化高亮&#xff1a; 需要下载的依赖&#xff1a; <template><div class"box"><div class"top" v-if"flag"><span class"text">Theme:</span><…

Vue 3的Composition API和vue2的不同之处

Vue 3的Composition API是Vue.js框架的一个重要更新&#xff0c;它提供了一种新的组件逻辑组织和复用方式。在Vue 2中&#xff0c;我们通常使用Options API&#xff08;data、methods、computed等&#xff09;来组织组件的逻辑&#xff0c;但这种组织方式在处理复杂组件时可能会…

springboot-异步、定时、邮件任务

一、异步任务 1、创建项目 2、创建一个service包 3、创建一个类AsyncService 异步处理还是非常常用的&#xff0c;比如我们在网站上发送邮件&#xff0c;后台会去发送邮件&#xff0c;此时前台会造成响应不动&#xff0c;直到邮件发送完毕&#xff0c;响应才会成功&#xff…

Arduino串口控制舵机机械臂

Arduino nano作为主控板&#xff0c;控制由四个SG90舵机组成的机械臂&#xff0c;原先想着用四个电位计控制舵机转动&#xff0c;结果舵机一直抖动&#xff0c;索性就使用串口类似at指令控制舵机转动。使用的串口中断&#xff0c;通信的数据也是 字母数字 的格式&#xff0c;字…

AI大模型的预训练、迁移和中间件编程

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。…

Docker部署前后端服务示例

使用Docker部署js前端 1.创建Dockerfile 在项目跟目录下创建Dockerfile文件&#xff1a; # 使用nginx作为基础镜像 FROM nginx:1.19.1# 指定工作空间 WORKDIR /data/web# 将 yarn build 打包后的build文件夹添加到工作空间 ADD build build# 将项目必要文件添加到工作空间&a…