概述
Netlink的通信地址和协议
一般来说用户空间和内核空间的通信方式有三种:/proc、ioctl、Netlink。而前两种都是单向的,而Netlink可以实现双工通信。
所有socket之间的通信,必须有个地址结构,Netlink也不例外。我们最熟悉的就是IPV4的地址了,netlink的地址结构如下:
struct sockaddr_nl
{
sa_family_t nl_family; //必须为AF_NETLINK或者PF_NETLINK
unsigned short nl_pad; //必须为0
__u32 nl_pid; //通信端口
__u32 nl_groups; //组播掩码
};
本质上,nl_pid就是netlink的通信地址(双方约定好)。除了通信地址,netlink还提供“协议”来标示通信实体,在创建socket的时候,需要指定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 /* Firewalling hook */
#define NETLINK_INET_DIAG 4 /* INET 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
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary data buffer len */
int msg_flags; /* flags (unused) */
};
struct sockaddr_nl
{
sa_family_t nl_family; /*该字段总是为AF_NETLINK */
unsigned short nl_pad; /* 目前未用到,填充为0*/
__u32 nl_pid; /* process pid */
__u32 nl_groups; /* multicast groups mask */
};
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 PID */
};
协议号一般作为测试可以直接使用NETLINK_GENERIC
通信端口一般原则如下:
生成式1:选择应用程序的pid作为nl_pid的值。nl_pid = getpid();
生成式2:这种方式下,同一进程内的不同线程都可以为同一Netlink 协议类型申请自己特有的socket。实际上,即使在一个线程内,创建多个基于相同协议类型的Netlink Socket也是可能的。然而,开发者需要在如何生成唯一nl_pid上更具创造性。nl_pid = pthread_self() << 16 | getpid();
netlink采用协议号+通信端口的方式构建自己的地址体系。
nlmsg_type:消息状态,内核在include/uapi/linux/netlink.h中定义了以下4种通用的消息类型,它们分别是
#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:消息标记,它们用以表示消息的类型,如下
/* Flags values */
#define NLM_F_REQUEST 1 /* It is request message. */
#define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK 4 /* Reply with ack, with zero or error code */
#define NLM_F_ECHO 8 /* Echo this request */
#define NLM_F_DUMP_INTR 16 /* Dump was inconsistent due to sequence change */
/* 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 */
用户态接口
用户态创建、销毁socket的过程:
1、 用socket函数创建,socket(PF_NETLINK, SOCK_DGRAM, NETLINK_XXX);第一个参数必须是PF_NETLINK或者AF_NETLINK,第二个参数用SOCK_DGRAM和SOCK_RAW都没问题,第三个参数就是netlink的协议号。
2、 用bind函数绑定自己的地址。
3、 用close关闭套接字。
发送Netlink message
如果消息是发往内核的,nl_pid和nl_groups字段都应该置0。
如果是发往另一个进程的单播消息, nl_pid应该是目标进程的pid而nl_groups字段置0(假设系统采用生成式1计算nl_pid)。
如果是发往一个或多个多播组的消息,所有目标多播组对应的掩码应该执行"OR"操作后填入nl_groups字段。
按如下方式,向sendmsg()API 所需要的 msghdr结构提供目标Netlink 地址。
struct msghdr msg;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
Netlink Socket 还需要有自己的消息头部。这是为了为所有Netlink协议类型提供一个公共基础。
由于Linux内核中的Netlink核心假设如下头部在每个Netlink message中的存在,用户必须为每个发送的Netlink message提供这个头部。
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */ //消息总长度
__u16 nlmsg_type; /* Message type*/ //消息类型
__u16 nlmsg_flags; /* Additional flags */ //附加控制
__u32 nlmsg_seq; /* Sequence number */ //序列号
__u32 nlmsg_pid; /* Sending process PID */ //发送方的pid
};
struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1; /* iovec数目 */
接收Netlink message
接收进程需要分配足够大的缓冲区来存放Netlink message(包括消息头部消息负载)。然后需要填写如下的struct msghdr,并调用标准的recvmsg()来接收Netlink message(此处假设nth指向缓冲区)
struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recvmsg(fd, &msg, 0);
内核空间使用的Netlink API
内核空间的Netlink API是由Netlink核心在net/core/af_netlink.c文件提供的。内核使用与用户空间不同的API。内核模块可以调用这些API来操纵Netlink Socket,并与用户空间程序通讯。若不打算利用已有的Netlink协议类型,用户必须通过在netlink.h中添加常量来添加自己的协议。
创建socket
static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
/* net: net指向所在的网络命名空间, 一般默认传入的是&init_net(不需要定义); 定义在net_namespace.c(extern struct net init_net);
unit:netlink协议类型
cfg: cfg存放的是netlink内核配置参数(如下)
*/
/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff *skb); /* input 回调函数 */
struct mutex *cb_mutex;
void (*bind)(int group);
bool (*compare)(struct net *net, struct sock *sk);
};
函数指针input,指向一个回调函数,该函数在有消息到达Netink Socket时被调用。
在内核创建了一个类型为NETLINK_GENERIC的Netlink Socket后,无论何时用户空间向内核发送一条类型为NETLINK_GENERIC的Netlink message时,之前调用netlink_kernel_create()时通过input参数注册的回调函数被调用。
input()函数是在由发送进程所激发的sendmeg()系统调用的上下文环境中执行的。如果对该Netlink message的处理速度很快的话,在input()函数中执行对消息的处理是没有问题的。但是如果对该Netlink message的处理是耗时操作,为了避免阻止其他系统调用"陷入"内核,应该将处理操作移出input()函数。这种情况下可以使用一个内核线程来无限循环的完成下述操作。
使用 skb = skb_recv_datagram(nl_sk),其中nl_sk是 netlink_kernel_create()返回的Netlink Socket。然后,处理由skb->data所指向的netlink message。
内核线程在nl_sk中没有Netlink message时睡眠。因此,在回调函数input()中,只需要唤醒睡眠的内核进程,如下:
void input (struct sock *sk, int len)
{
wake_up_interruptible(sk->sleep);
}
从内核发送Netlink message
/* 发送单播消息 */
extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
/*
ssk: netlink socket
skb: skb buff 指针
portid: 通信的端口号
nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用定时睡眠
*/
/* 发送多播消息 */
extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
__u32 group, gfp_t allocation);
/*
ssk: 同上(对应netlink_kernel_create 返回值)、
skb: 内核skb buff
portid: 端口id
group: 是所有目标多播组对应掩码的"OR"操作的合值。
allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone
*/
关闭socket
sock_release(nl_sk->socket);
netlink常用宏:
#define NLMSG_ALIGNTO 4U
/* 宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值 */
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
/* Netlink 头部长度 */
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/* 计算消息数据len的真实消息长度(消息体 + 消息头)*/
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
/* 宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
/* 宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
/* 宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址, 同时len 变为剩余消息的长度 */
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len),
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
/* 判断消息是否 >len */
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) &&
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) &&
(nlh)->nlmsg_len <= (len))
/* NLMSG_PAYLOAD(nlh,len) 用于返回payload的长度*/
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
实例
内核态:
/* ker_space.c */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <linux/netlink.h>
#define MAX_MSGSIZE 64
#define NETLINK_TEST 30
#define USER_PID 100
static struct sock *netlinkfd;
static void sendData(char *data, int pid, int len)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
/* 分配sk_buff */
skb = alloc_skb(NLMSG_SPACE(len), GFP_KERNEL);
if(!skb)
{
printk("alloc_skb error!n");
return;
}
nlh = nlmsg_put(skb, 0, 0, 0, len, 0);
NETLINK_CB(skb).portid = 0;
NETLINK_CB(skb).dst_group = 0;
memcpy(NLMSG_DATA(nlh), data, len); /* 拷贝数据到netlink msg的数据部分 */
netlink_unicast(netlinkfd, skb, pid, MSG_DONTWAIT); /* 内核态发包 */
}
static void recvData(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
int pid = 0;
char *data;
char *sendBuf = "Hello user, I am kernel!";
if(skb->len >= NLMSG_SPACE(0))
{
nlh = nlmsg_hdr(skb);
data = NLMSG_DATA(nlh);
printk("msg from user is:%sn", data ? data : "");
pid = nlh->nlmsg_pid; /* 获取用户pid */
sendData(sendBuf, pid, strlen(sendBuf));
}
}
static int netlink_start(void)
{
struct netlink_kernel_cfg cfg = {
.input = recvData, /* 收包回调 */
};
/* 内核态创建socket */
netlinkfd = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
if (!netlinkfd)
{
printk("create netlink socket failed!n");
return -1;
}
printk("create netlink socket success!n");
return 0;
}
static void netlink_stop(void)
{
if (netlinkfd)
{
netlink_kernel_release(netlinkfd);
}
printk("netlink stop!n");
}
module_init(netlink_start);
module_exit(netlink_stop);
MODULE_LICENSE("GPL");
用户态1(使用sendto和recvfrom):
/* 使用sendto和recvfrom和内核通信 */
/* user_space.c */
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/netlink.h>
#include <errno.h>
#define MAX_MSGSIZE 64
#define NETLINK_TEST 30
#define USER_PID 100
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct msghdr msg;
struct iovec iov;
int sock_fd;
int main()
{
int sendlen = 0;
int ret = 0;
socklen_t addrlen = sizeof(struct sockaddr_nl);
/* create socket, protocol type:NETLINK_TEST */
sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
if (sock_fd < 0)
{
perror("create netlink socket error");
return -1;
}
/* set source(for send) socket addr:nl_family + nl_pid */
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = USER_PID;//getpid(); /* self pid */
src_addr.nl_groups = 0; /* not in mcast groups */
/* bind addr to socket */
bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
/* set dst(for recv) socket addr:nl_family + nl_pid */
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; /* For Linux Kernel */
dest_addr.nl_groups = 0; /* unicast */
/* alloc nlmsghdr */
nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
/* Fill the netlink message header */
nlh->nlmsg_len = NLMSG_SPACE(MAX_MSGSIZE);
nlh->nlmsg_type = 0;
nlh->nlmsg_flags = 0;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = src_addr.nl_pid; /* pid */
/* Fill in the netlink message payload */
strcpy(NLMSG_DATA(nlh), "Hello kernel!");
ret = sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_nl));
if(!ret)
{
perror("sendto errorn");
close(sock_fd);
exit(-1);
}
#if 0
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
sendlen = sendmsg(sock_fd, &msg, 0);
if (sendlen < 0)
{
perror("send error");
close(sock_fd);
return;
}
/* Read message from kernel */
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
recvmsg(sock_fd, &msg, 0);
printf("received message from kernel: %s/n", NLMSG_DATA(nlh));
#endif
if( recvfrom(sock_fd, nlh, NLMSG_SPACE(MAX_MSGSIZE), 0, (struct sockaddr *)&src_addr, (socklen_t *)&addrlen) < 0 )
{
printf("recvmsg error!n");
close(sock_fd);
return -1;
}
printf("received message from kernel: %sn", (char *)NLMSG_DATA(nlh));
/* Close Netlink Socket */
close(sock_fd);
return 0;
}
用户态2(使用sendmsg和recvmsg):
/* 使用sendmsg和recvmsg和内核通信 */
/* user_space_rx.c */
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>
#include <errno.h>
#define MAX_MSGSIZE 64
#define NETLINK_TEST 30
#define USER_PID 100
int netlink_create_socket(void)
{
//create a socket, protocol NETLINK_TEST
return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
}
int netlink_bind(int sock_fd)
{
struct sockaddr_nl addr;
memset(&addr, 0, sizeof(struct sockaddr_nl));
addr.nl_family = AF_NETLINK;
addr.nl_pid = USER_PID;
addr.nl_groups = 0;
return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl));
}
int netlink_send_message(int sock_fd, const unsigned char *message, int len,
unsigned int pid, unsigned int group)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl dest_addr;
struct iovec iov;
struct msghdr msg;
if (!message)
{
return -1;
}
/* create message */
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len));
if( !nlh )
{
perror("malloc");
return -2;
}
nlh->nlmsg_len = NLMSG_SPACE(len);
nlh->nlmsg_pid = USER_PID;
nlh->nlmsg_flags = 0;
memcpy(NLMSG_DATA(nlh), message, len); /* copy data */
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;
memset(&dest_addr, 0, sizeof(struct sockaddr_nl));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = pid;
dest_addr.nl_groups = group;
/* set msghdr */
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* send message */
if( sendmsg(sock_fd, &msg, 0) < 0 )
{
printf("send error!n");
free(nlh);
return -3;
}
free(nlh);
return 0;
}
int
netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl source_addr;
struct iovec iov;
struct msghdr msg;
if( !message || !len )
{
return -1;
}
//create message
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
if( !nlh )
{
perror("malloc");
return -2;
}
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_MSGSIZE);
memset(&source_addr, 0, sizeof(struct sockaddr_nl));
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = (void *)&source_addr;
msg.msg_namelen = sizeof(struct sockaddr_nl);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if ( recvmsg(sock_fd, &msg, 0) < 0 )
{
printf("recvmsg error!n");
return -3;
}
*len = nlh->nlmsg_len - NLMSG_SPACE(0);
memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);
free(nlh);
return 0;
}
int main(int argc, char **argv)
{
int sock_fd;
char buf[MAX_MSGSIZE];
int len;
if( argc < 2)
{
printf("enter message!n");
exit(EXIT_FAILURE);
}
sock_fd = netlink_create_socket();
if(sock_fd == -1)
{
printf("socket error!n");
return -1;
}
if( netlink_bind(sock_fd) < 0 )
{
perror("bind");
close(sock_fd);
exit(EXIT_FAILURE);
}
netlink_send_message(sock_fd, argv[1], strlen(argv[1]) + 1, 0, 0);
if( netlink_recv_message(sock_fd, buf, &len) == 0 )
{
printf("recv:%s len:%dn", buf, len);
}
close(sock_fd);
return 0;
}
Makefile:
USER_APP = user_space
USER_OBJ = user_space.o
USER_APP_RX = user_space_rx
USER_OBJ_RX = user_space_rx.o
KER_APP = ker_space
obj-m += ker_space.o
KERNEL_PATH = /lib/modules/$(shell uname -r)/build/
all: $(USER_APP) $(USER_APP_RX) $(KER_APP)
$(USER_APP): $(USER_OBJ)
gcc $(USER_OBJ) -o $(USER_APP)
$(USER_APP_RX): $(USER_OBJ_RX)
gcc $(USER_OBJ_RX) -o $(USER_APP_RX)
$(KER_APP):
make -C $(KERNEL_PATH) M=$(PWD) modules
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
rm -f *.o user_space user_space_rx
其他比较好的例子:
Linux Netlink 编程 :https://www.jianshu.com/p/073bcd9c3b08
最后
以上就是光亮盼望为你收集整理的Linux 24 内核和用户通信之netlink的全部内容,希望文章能够帮你解决Linux 24 内核和用户通信之netlink所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复