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.mzph.cn/news/714008.shtml

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

相关文章

GaussDB跨云容灾:实现跨地域的数据库高可用能力

背景 金融、银行业等对数据的安全有着较高的要求&#xff0c;同城容灾建设方案&#xff0c;在绝大多数场景下可以保证业务数据的安全性&#xff0c;但是在极端情况下&#xff0c;如遇不可抗力因素等&#xff0c;要保证数据的安全性&#xff0c;就需要采取跨地域的容灾方案。 …

Dell R730 2U服务器实践3:安装英伟达上代专业AI训练Nvidia P4计算卡

Dell R730是一款非常流行的服务器&#xff0c;2U的机箱可以放入两张显卡&#xff0c;这次先用一张英伟达上代专业级AI训练卡&#xff1a;P4卡做实验&#xff0c;本文记录安装过程。 简洁步骤&#xff1a; 打开机箱将P4显卡插在4号槽位关闭机箱安装驱动 详细步骤&#xff1a; 对…

网络工程师学习笔记——VRP配置命令大全

VRP是Versatile Routing Platform的简称&#xff0c;它是华为公司数据通信产品的通用网络操作系统。它以IP业务为核心&#xff0c;采用组件化的体系结构&#xff0c;在实现丰富功能特性的同时&#xff0c;还提供了基于应用的可裁剪和可扩展的功能&#xff0c;使得路由器和交换机…

计算机网络物理层知识点总结

本篇博客是基于谢希仁编写的《计算机网络》和王道考研视频总结出来的知识点&#xff0c;本篇总结的主要知识点是第二章的物理层。上一章的传送门&#xff1a;计算机网络体系结构-CSDN博客 通信基础 物理层概念 物理层解决如何在连接各种计算机的传输媒体上传输数据比特流&am…

【Kubernetes】k8s中容器之间、pod之间如何进行网络通信?

目录 PodKubernetes 网络模型同一Pod上的容器之间进行通信同一Node上的不同Pod之间进行通信不同Node上的Pod之间进行通信Service参考 Pod 首先来回顾一下Pod&#xff1a; Pod 是用于构建应用程序的最小可部署对象。单个 Pod 代表集群中正在运行的工作负载&#xff0c;并封装一…

C++初阶篇----类与对象上卷

目录 引言1.面向过程和面向对象初步认识2.类的引入3.类的定义3.1声明与定义全部放在类体中3.2声明与定义分离 4.类的访问限定符及封装4.1访问限定符4.2封装 5.类的作用域6.类的实例化类是对对象进行描述一个类&#xff08;一个类型变量&#xff09;可以实例化出多个对象 7.类对…

Day12-【Java SE进阶】JDK8新特性:Lambda表达式、方法引用、常见算法、正则表达式、异常

一、JDK8新特性 1.Lambda表达式 Lambda表达式是JDK 8开始新增的一种语法形式;作用:用于简化名内部类的代码写法。 注意:Lambda表达式并不是说能简化全部匿名内部类的写法&#xff0c;只能简化函数式接口的匿名内部类。 有且仅有一个抽象方法的接口。注意:将来我们见到的大部…

分布式事务简介

分布式事务简介&#xff0c;通过组内分享学习到的知识&#xff0c;并进行讨论。 主要内容 分布式事务简介 分布式事务是指跨越多个数据库或服务的一系列操作&#xff0c;这些数据库或服务可能分布在网络的不同节点上&#xff0c;它们共同组成一个完整的逻辑工作单元&#xf…

GEE必须会教程—蒸散发数据时间序列分析与下载

今天带来的有关蒸散发数据的下载代码&#xff0c;蒸散发数据在气象气候&#xff0c;农业干旱监测等领域应用广泛&#xff0c;那么在GEE上如何方便快捷获取蒸散发数据呢&#xff1f;今天跟着小编分享代码&#xff0c;快来学习吧&#xff01;&#xff01; A.定义研究区域 //定义…

【前端素材】推荐优质数据统计后台管理系统网页Cleopatra.平台模板(附源码)

一、需求分析 在线后台管理系统是指供管理员或运营人员使用的Web应用程序&#xff0c;用于管理和监控网站、应用程序或系统的运行和数据。它通常包括一系列工具和功能&#xff0c;用于管理用户、内容、权限、数据等。下面是关于在线后台管理系统的详细分析&#xff1a; 1、功…

ssh简介以及 windows 安装ssh教程

SSH&#xff08;Secure Shell&#xff09;是一种网络协议&#xff0c;用于计算机之间的加密登录和其他安全网络服务。通过 SSH&#xff0c;用户可以安全地访问远程计算机&#xff0c;执行命令、传输文件等操作。SSH 使用公钥加密技术&#xff0c;确保数据传输的安全性。本文将从…

TypeScript 哲学 - 2、Narrowing

四种类型守卫 1、truthiness narrowing 2、 3、 4、 control flow analysis

C语言:结构体(自定义类型)知识点(包括结构体内存对齐的热门知识点)

和黛玉学编程呀&#xff0c;大家一起努力呀............. 结构体类型的声明 回顾一下 struct tag { member-list; }variable-list; 创建和初始化 我们知道&#xff0c;在C语言中&#xff0c;对于一些数据是必须初始化的&#xff0c;但是结构体怎么创建并且初始化呢&#xff1…

【计算机网络】TCP 如何实现可靠传输

TCP通过三次握手建立连接&#xff0c;四次挥手释放连接&#xff0c;确保连接建立和连接释放的可靠。 序列号、检验和、确认应答信号、重发机制、连接管理、窗口控制、流量控制、拥塞控制 标准回答 可靠传输就是通过TCP连接传送的数据是没有差错、不会丢失、不重复并且按序到达的…

springboot+vue+mysql项目使用的常用注解

实体类常用注解 Data Data 是一个 Lombok 提供的注解&#xff0c;使用 Data 注解可以简化代码&#xff0c;使代码更加简洁易读。 作用&#xff1a;自动为类生成常用的方法&#xff0c;包括 getter、setter、equals、hashCode 和 toString 等需要加Lombok的依赖 <depende…

rk3568-一种基于wifi的网络环境搭建方案

前言&#xff1a; PC--Ubuntu--开发板 三者之间的网络互相ping通很重要&#xff0c;尤其是ubuntu和开发板互ping成功最关键&#xff0c;关系到nfs&#xff0c;tftp等常用的开发手段。现在大多数开发板都带有wifi芯片&#xff0c;现在提供一种方案可以三个设备无线地搭建网络环境…

Open3D0.14.1编译、安装、demo使用教程

写在前面 本文内容 Open3D在0.15版之前&#xff0c;没有提供编译好的包&#xff0c;要使用C版本必须自己编译&#xff0c;本文是Open3D0.14.1在Windows下和Linux(Ubuntu1804)下的编译、使用教程&#xff1b; Open3D其他版本的编译和使用相关教程见 各个版本的Open3D、PCL的编译…

STL常见容器(map/multimap容器)---C++

STL常见容器目录&#xff1a; 8.map/ multimap容器8.1 map基本概念8.2 map构造和赋值8.3 map大小和交换8.4 map插入和删除8.5 map查找和统计8.6 map容器排序8.6.1 内置类型排序8.6.2 自定义类型排序8.6.3 自定义和内置类型混合排序 8.map/ multimap容器 两者基本一致&#xff…

用node写后端环境运行时报错Port 3000 is already in use

解决方法:关闭之前运行的3000端口,操作如下 1.WindowR输入cmd确定,打开命令面板 2.查看本机端口详情 netstat -ano|findstr "3000" 3.清除3000端口 taskkill -pid 41640 -f 最后再重新npm start即可,这里要看你自己项目中package.joson的启动命令是什…

Flink:动态表 / 时态表 / 版本表 / 普通表 概念区别澄清

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…