通信组件 --- netlink 原理及应用

news/2024/9/19 15:46:36/文章来源:https://www.cnblogs.com/god-of-death/p/18355708

什么是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字节,消息体长度可变;

消息头

消息头定义如下:

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的形式;消息体组织形式如下:

 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协议族、子协议族、子协议、命令,组织结构如下:

 

如何新增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的消息了;

应用程序代码

#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 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

测试结果

如何新增自定义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/782918.html

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

相关文章

向量数据库和异常数据

书接上文:https://www.cnblogs.com/k4n5ha0/p/18314781 最近学习机器学习期间,了解到了向量数据库:1)可以将文本向量化存储(如上图,将不同语句向量化) 2)在 检索向量的时间复杂 和 对比向量相似度的时间复杂度(例如余弦相似度)充分调优 3)可以调用TPU、GPU等硬件加…

二维差分学习备忘录

二维差分为什么我为OI泪目?因为我菜得离谱......引入 一维差分用来O(1)修改区间,配合上一维前缀和就是O(N)的查询区间和。 差分为前缀和的逆运算。 二维差分同理。 接下来这道题就用二维差分来解决。 \(例题:地毯>>\) 地毯 题目描述 在 \(n\times n\) 的格子上有 \(m\…

4.自定义的信号和槽

自定义信号 1.写到signal下 2.返回void 3.需要声明,不需要实现 4.可以有参数,可以重载 自定义槽函数 1.返回void 2.需要声明,也需要实现 3.可以有参数,可以重载 4.写到pulic slot下或者pulic或者全局函数 触发自定义的信号 1.emit 自定义信号 案例:下课后,老师触发饿了信…

Windows日志致盲与绕过

前言 在渗透测试过程中,Windows日志往往会记录系统上的敏感操作,如添加用户、写计划任务、远程登录执行等操作,对于会审计的安全运维人员或者网络管理员来说,简直就是脱掉衣服走路,被看的一清二楚。对于比较高阶的渗透操作中,要讲究的是动作无感化,来无影去无踪。而对于…

java maven项目配置及IDEA配置

一:maven作用项目管理:编译、测试、打包、部署、运行,这一套流程都可以用maven来管理。管理jar包:也就是上述提到的问题。管理插件:开发过程中会需要使用各种插件。3maven环境变量配置和Java一样,也需要配置maven环境变量。环境变量的作用在于: 可以让该程序在任何路径下…

Go并发原语之SingleFlight

引入 如下图所示,可能存在来自桌面端和移动端的用户有 1000 的并发请求,同一时刻来访问的获取文章列表的接口,获取前 20 条信息,如果这时服务直接去访问 redis 出现 cache miss, 那就会去请求 1000 次数据库,这时可能会给数据库带来较大的压力(这里的1000 只是一个例子,…

词向量的转换理解以及与真实的关系

实际上就是去除该矩阵的某一行。该矩阵实际上就是一个有N个词的300维向量,或者说该矩阵就是一个完整的词向量词汇表。而这个词汇表是通过交叉熵损失最小来构造的。 即归根到底是“在特定语料库中(包含context中共现概率),以数学方法计算输入产生哪些输出”最复合“实际情况…

盘点国内外有哪些软件测试认证

在软件测试行业,技术实力固然重要,但手握权威认证更能为职业发展增添砝码。无论你是刚入行的新人,还是经验丰富的测试工程师,获取一张含金量高的软件测试认证都能让你的职场之路更加平坦。那么,国内外有哪些值得考取的软件测试认证呢?让我们一起来盘点。 你是否正在为选择…

用友金蝶怎么选

财务知识——用友和金蝶选择谁?

概率论沉思录:合情推理

最近蔻享学术主办了每周一次的《概率论沉思录》读书会活动,恰好我也正在读该书中译版,通过该活动我了解到了不同学科的老师(数学/物理/统计/计算机)对这本书的不同理解,而我自己对该书的理解也在这个过程中逐渐深入了。于是准备每周都持续更新一下我的读书笔记。本书作者是…

八重终结 Phase 4 狱火焚天

Ending Time Octet 【八重终结】 Phase 4 Inferno Combusts 【狱火焚天】 PV on Bilibili Part 1 联合动画 Part 1 Part 2 完整音乐 涉及 AU Murder Time Trio Former Time Trio Karmatic Time Trio Chaotic Time Trio Phase 3 Chaotic Time Trio Phase 4 归冢余妄个人曲 传说之…

【grpcurl】使用grpcurl测试GRPC服务

一、场景由于我们需要访问GRPC服务的方法,便于我们进行测试,所以我们开启了grpc服务的反射机制二、安装grpcurlhttps://github.com/fullstorydev/grpcurlhttps://github.com/fullstorydev/grpcurl/releases 下载对应环境的包即可sudo dpkg -i grpcurl_1.9.1_linux_amd64.debu…