linux 内核中Netlink
目录
套接字Netlink地址 sockaddr_nl
协议簇
常使用的宏
内核常用的函数
创建流程
Netlink套接字
uevent内核事件
套接字监视接口
demo
Netlink套接字接口最初是Linux内核2.2引入的,作用用户空间进程与内核间通信方法。
相对于ioctl,sysfs,proc的优势
优势:
- IOCTL处理程序不能从内核向用户空间发送异步消息,而Netlink套接字则可以。
- 用户与内核间的通信方式,不需要轮询,用户空间应用程序打开套接字,调用recvmsg(),如果没有来自内核的消息,就进入阻塞状态。
- 内核可以主动向用户空间发送异步消息,而不需要用户空间来触发。
- 支持组播传输。
命令iproute2包含命令(ip tc ss lnstat bridge)主要使用netlink套接字从用户空间向内核空间发送请求并获得应答。
套接字Netlink地址 sockaddr_nl
struct sockaddr_nl {__kernel_sa_family_tnl_family;/* AF_NETLINK*/unsigned shortnl_pad;/* zero*/__u32nl_pid;/* port ID*/__u32nl_groups;/* multicast groups mask */};
消息头
struct nlmsghdr {__u32nlmsg_len;/* Length of message including header */__u16nlmsg_type;/* Message content */__u16nlmsg_flags;/* Additional flags */__u32nlmsg_seq;/* Sequence number */__u32nlmsg_pid;/* Sending process port ID */};
协议簇
#define NETLINK_ROUTE0/* Routing/device hook*/#define NETLINK_UNUSED1/* Unused number*/#define NETLINK_USERSOCK2/* Reserved for user mode socket protocols */#define NETLINK_FIREWALL3/* Unused number, formerly ip_queue*/#define NETLINK_SOCK_DIAG4/* socket monitoring*/#define NETLINK_NFLOG5/* netfilter/iptables ULOG */#define NETLINK_XFRM6/* ipsec */#define NETLINK_SELINUX7/* SELinux event notifications */#define NETLINK_ISCSI8/* Open-iSCSI */#define NETLINK_AUDIT9/* auditing */#define NETLINK_FIB_LOOKUP10#define NETLINK_CONNECTOR11#define NETLINK_NETFILTER12/* netfilter subsystem */#define NETLINK_IP6_FW13#define NETLINK_DNRTMSG14/* DECnet routing messages */#define NETLINK_KOBJECT_UEVENT15/* Kernel messages to userspace */#define NETLINK_GENERIC16/* leave room for NETLINK_DM (DM Events) */#define NETLINK_SCSITRANSPORT18/* SCSI Transports */#define NETLINK_ECRYPTFS19#define NETLINK_RDMA20#define NETLINK_CRYPTO21/* Crypto layer */#define NETLINK_SMC22/* SMC monitoring */
常使用的宏
#define NLMSG_ALIGNTO4U#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len nlmsg_len - NLMSG_SPACE((len)))
内核常用的函数
//内核创建socketstatic inline struct sock * netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)//nlmsg_new - Allocate a new netlink messagestatic inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)//Add a new netlink message to an skbstatic inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int payload, int flags)//单播extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);//多播extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid, __u32 group, gfp_t allocation);
创建流程
Netlink套接字
在内核网络栈中,可以创建多种Netlink套接字,每种套接字处理不同类型消息。如NETLINK_ROUTE消息的套接字创建过程
static int __net_init rtnetlink_net_init(struct net *net){struct sock *sk;struct netlink_kernel_cfg cfg = {.groups= RTNLGRP_MAX,.input= rtnetlink_rcv,.cb_mutex= &rtnl_mutex,.flags= NL_CFG_F_NONROOT_RECV,.bind= rtnetlink_bind,};sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);if (!sk)return -ENOMEM;net->rtnl = sk;return 0;}
- 在netlink_kernel_create种第二个参数 NETLINK_ROUTE表示rtnetlink消息,此外还有NETLINK_XFRM表示IPsec子系统,NETLINK_AUDIT表示审计子系统。
- 成员input函数用于指定回调函数,rtnetlink_rcv用于接收用户空间的信息。
uevent内核事件
只需要从内核向用户发送数据即可,初始化为
static int uevent_net_init(struct net *net){struct uevent_sock *ue_sk;struct netlink_kernel_cfg cfg = {.groups= 1,.input = uevent_net_rcv,.flags= NL_CFG_F_NONROOT_RECV};ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);if (!ue_sk)return -ENOMEM;ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);if (!ue_sk->sk) {pr_err("kobject_uevent: unable to create netlink socket!\n");kfree(ue_sk);return -ENODEV;}...}
套接字监视接口
//支持SS 监视接口 NETLINK_SOCK_DIAGstatic int __net_init diag_net_init(struct net *net){struct netlink_kernel_cfg cfg = {.groups= SKNLGRP_MAX,.input= sock_diag_rcv,.bind= sock_diag_bind,.flags= NL_CFG_F_NONROOT_RECV,};net->diag_nlsk = netlink_kernel_create(net, NETLINK_SOCK_DIAG, &cfg);return net->diag_nlsk == NULL ? -ENOMEM : 0;}
demo
内核程序实例
#include #include #include #include #include #include #include #define NETLINK_USER 22#define USER_MSG (NETLINK_USER + 1)#define USER_PORT 50static struct sock *netlinkfd = NULL;int send_msg(int8_t *pbuf, uint16_t len){ struct sk_buff *nl_skb; struct nlmsghdr *nlh; int ret; //创建sk_buffer nl_skb = nlmsg_new(len, GFP_ATOMIC); if(!nl_skb) { printk("nlmsg_new error\n"); return -1; } //设置netlink头 nlh = nlmsg_put(nl_skb, 0, 0, USER_MSG, len, 0); if(nlh == NULL) { printk("netlink header error\n"); nlmsg_free(nl_skb); return -1; } //拷贝数据 memcpy(nlmsg_data(nlh), pbuf, len); //单播发送数据 ret = netlink_unicast(netlinkfd, nl_skb, USER_PORT, MSG_DONTWAIT); return ret;}//接收数据static void recv_cb(struct sk_buff *skb){ struct nlmsghdr *nlh = NULL; void *data = NULL; //打印接收数据的长度 printk("recv_datalen:%u\n", skb->len); if(skb->len >= nlmsg_total_size(0)) { nlh = nlmsg_hdr(skb); //宏 获取数据 data = NLMSG_DATA(nlh); if(data) { printk("kernel receive data: %s\n", (int8_t *)data); //将数据发送给用户 send_msg(data, nlmsg_len(nlh)); } }} //cfg参数 注册了inputstruct netlink_kernel_cfg cfg = { .input = recv_cb,};//初始化static int __init netlink_init(void){ //创建socket netlinkfd = netlink_kernel_create(&init_net, USER_MSG, &cfg); if(!netlinkfd) { printk(KERN_ERR "create a netlink socket error!\n"); return -1; } printk("init netlink ok!\n"); return 0;}//退出static void __exit netlink_exit(void){ sock_release(netlinkfd->sk_socket); printk(KERN_DEBUG "netlink exit\n!");}module_init(netlink_init);module_exit(netlink_exit);MODULE_LICENSE("GPL"); MODULE_AUTHOR("wy"); MODULE_DESCRIPTION("netlink");
参考
https://course.0voice.com/v1/course/intro?courseId=2&agentId=0