什么是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.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 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,},...... }