什么是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 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.c
和Makefile
放到同一目录下;直接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 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
测试结果
如何新增自定义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, }, ...... }
标签:netlink,nl,struct,nlmsg,NETLINK,---,组件,define From: https://www.cnblogs.com/god-of-death/p/18355708