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            125

MODULE_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 skb
        pid是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).o
	

KERNELDIR ?=/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       125

typedef 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_NETLINK
    saddr.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 kernel
    daddr.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 port

    memcpy(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,
	},
    ......
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

docker安装ES和kibana

文章目录 一、安装Elasticsearch1. 安装Elasticsearch2. 安装IK分词器3. elasticsearch-head 监控的插件4. 配置跨域 二、安装kibana 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、安装Elasticsearch 1. 安装Elasticsearch 安装Elasticsearch参…

揭秘麦肯锡的方法:产品经理解决问题指南

您是否想知道世界上最成功的产品经理如何始终如一地提供不仅满足而且超出预期的解决方案&#xff1f;秘密可能就在于世界上最负盛名的咨询公司之一麦肯锡公司所磨练的方法论。本文深入探讨了麦肯锡的问题解决流程&#xff0c;该流程专为希望提升水平的产品经理量身定制。 01. 麦…

java 面试题总结

1锁粗化和锁消除&#xff0c;锁膨胀和锁升级的区别。 https://www.cnblogs.com/xuxinstyle/p/13387778.html .无锁 < 偏向锁 < 轻量级锁 < 重量级锁 &#xff0c;说的时候不要忘记说无锁状态 2.Map 的实现&#xff0c;线程安全的实现 1、ConcurrentHashMap在JDK 1.7…

【AI视野·今日CV 计算机视觉论文速览 第300期】Fri, 1 Mar 2024

AI视野今日CS.CV 计算机视觉论文速览 Fri, 1 Mar 2024 Totally 114 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers DistriFusion: Distributed Parallel Inference for High-Resolution Diffusion Models Authors Muyang Li, Tianle Cai, J…

国创证券|AI手机PC概念拉升,福蓉科技4连板,思泉新材大涨

AI手机PC概念大幅走高&#xff0c;到发稿&#xff0c;思泉新材涨近15%&#xff0c;盘中一度涨停&#xff1b;道明光学、福蓉科技均涨停&#xff0c;诚迈科技涨近8%&#xff0c;亿道信息涨逾6%。 值得注意的是&#xff0c;福蓉科技已接连4个交易日涨停。公司4日晚间发布危险提示…

【c++】STL--List的实现

目录 一. List的数据结构 二. List实现的基本框架 1. list的结点结构类 2. List的迭代器类 正向迭代器 反向迭代器 3. List操作接口的实现 1. 默认成员函数 构造函数 和 析构函数 拷贝构造函数 和 赋值运算符重载 2. 修改相关函数接口 insert 和 erase …

pytorch统计属性

目录 1.normal2. mean, sum, min, max, prod3.argmin, argmax4. topk kthvalue5. compare6.norm 1.normal torch.normal(mean, std, *, generatorNone, outNone) → Tensor返回一个张量&#xff0c;其中的每个元素随机来自独立的标准正态分布。这些分布具有给定的均值和标准差…

12-Linux部署Zookeeper集群

Linux部署Zookeeper集群 简介 ZooKeeper是一个分布式的&#xff0c;开放源码的分布式应用程序协调服务&#xff0c;是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件&#xff0c;提供的功能包括&#xff1a;配置维护、域名服务、分布式同步、组服务等。…

入门LLMs开发 — LangChain

像OpenAI的GPT-4这样的大型语言模型&#xff08;LLMs&#xff09;已经风靡全球。它们可以自动执行各种任务&#xff0c;如回答问题、翻译语言、分析文本等。LLMs是第一种真正感觉像“人工智能”的机器学习类型。 然而&#xff0c;在将LLMs应用于实际产品时仍然存在挑战。特别是…

特氟龙塑料试剂瓶对比普通塑料和玻璃试剂瓶的优势

试剂瓶作为实验室中常备器皿耗材之一&#xff0c;主要用来盛放和储存样品、试剂&#xff0c;根据使用条件不同&#xff0c;也可叫取样瓶、样品瓶、储样瓶、广口瓶等。 根据瓶口口径不同&#xff0c;可分为广口瓶和窄口瓶&#xff0c;广口瓶口径较大&#xff0c;主要用于储存固…

Vue.js 实用技巧:深入理解 Vue.mixin

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

JProfiler详解 JVM性能监测内存泄露分析工具

JProfiler详解 JProfiler简介主要功能特点使用场景注意事项使用案例使用步骤Could not verify ssh-ed25519 host key with fingerprint 问题解决内存泄露分析 JProfiler简介 JProfiler是一款业界领先的Java性能分析工具&#xff0c;由ej-technologies公司开发&#xff0c;专门…

【亲测】注册Claude3教程,解决Claude3被封号无法发送手机验证码

【亲测】注册Claude3教程&#xff1a;解决无法发送手机验证码的问题 Anthropic 今日宣布推出其最新大型语言模型&#xff08;LLM&#xff09;系列——Claude 3&#xff0c;这一系列模型在各种认知任务上树立了新的性能标准。Claude 3 系列包括三个子模型&#xff1a;Claude 3 …

微服务架构SpringCloud(2)

热点参数限流 注&#xff1a;热点参数限流默认是对Springmvc资源无效&#xff1b; 隔离和降级 1.开启feign.sentinel.enabletrue 2.FeignClient(fallbackFactory) 3.创建一个类并实现FallbackFactory接口 4.加入依赖 <!--添加Sentienl依赖--><dependency><gro…

Linux开发工具使用

一、Linux软件包管理器 yum 软件包和软件包管理器, 就好比 "App" 和 "应用商店" &#xff0c;我们现在要安装的yum就是相当于在我们的Linux终端安装一个"应用商店"。 但使用yum时&#xff0c;我们一定要保证主机(虚拟机)网络畅通!这点也非常好理…

【单调栈】Leetcode 739.每日温度

【单调栈】Leetcode 739.每日温度 解法&#xff1a;维护单调栈栈中存的是数组的索引 解法&#xff1a;维护单调栈栈中存的是数组的索引 栈中存的是数组的索引 当新的值比当前栈顶的大&#xff0c;那么就执行出栈-更新result数组-判断当新的值比当前栈顶的大&#xff1f;的循环…

C语言回顾学习

一、数据类型 1.常量 2.float浮点表示 3.字符型 4.char&#xff08;大小写&#xff09; #include <stdio.h> //根据数字输出字符--int值可以直接输出为char int main() {int value;while (1){scanf("%d",&value);if(value<65||value>122){printf(&…

Python的http模块requests

模块简介&#xff1a; requests 库是一个 python中比较有名的 http请求的库&#xff0c;能处理 get,post,put,delete 等 restful请求&#xff0c;能设置 header&#xff0c;cookie,session 等操作&#xff0c;也是作为爬虫的基础库&#xff0c;它目前还不能异步请求,如果要支持…

C语言 BMP图片的旋转与缩放

目录 一、bmp文件头、文件信息头、位图实际数据的数据结构定义 二、源BMP文件信息的读取 三、实际位图数据的旋转、缩放操作 四、生成转换过后的新位图文件 #include <stdlib.h> #ifndef PHOTODEAL_H #define PHOTODEAL_H #pragma pack(1) typedef struct tagBm…

力扣经典题目解析--删除链表的倒数第 N 个结点

原题地址:. - 力扣&#xff08;LeetCode&#xff09; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;h…