netlink是一种用于用户空间进程与内核间通信的方法,也可以用于用户进程之间的通信(IPC)。
netlink和ioctl比较相似,都能从用户空间向内核空间通信,
但netlink是一种异步通信机制,而ioctl是同步通信机制。且ioctl不能从内核向用户空间发送消息。
下面我们结合实例来进一步了解netlink。我们的例子是用户态发送一串数据给内核态,内核接收到后,返回"I am from kernel!"给用户态程序。
内核态(以内核版本3.13为例)
先来了解下内核中netlink相关的数据结构和函数。
使用netlink_kernel_create()函数创建内核套接字服务,函数类型:
-
static inline struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
函数返回创建的struct sock对象指针。struct sock (定义在include\linux\net.h) 结构是内核网络系统中最常见的结构之一。它的使用基本贯穿L2,L3,L4层,而且是各层之间的一个联系,无论发送还是接收的数据包都要被存到sock结构中的缓冲队列中。
函数参数net是网络命名空间,如果没自定义网络命名空间的话,就使用内核默认的init_net。
unit是netlink类型,内核已经定义了21个(在include/uapi/linux/netlink.h中),如果要增加我们自己的netlink类型,就必须定义新的值,但要注意,新的值必须小于MAX_LINKS(32)
cfg是netlink的配置信息。该结构体定义如下:
-
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);
-
};
其中最重要的是input域,该函数用于处理用户空间发送到内核方向的数据。内核接收到数据包后,会传给input函数。
来看代码:
-
#include <linux/init.h>
-
#include <linux/module.h>
-
#include <linux/timer.h>
-
#include <linux/time.h>
-
#include <linux/types.h>
-
#include <net/sock.h>
-
#include <net/netlink.h>
-
#include <linux/string.h>
-
-
#define NETLINK_TEST 25
-
#define MAX_MSGSIZE 1024
-
-
struct sock *nl_sk = NULL;
-
-
void send_msg(char *message, int pid)
-
{
-
struct sk_buff *skb;
-
struct nlmsghdr *nlh;
-
int len = NLMSG_SPACE(MAX_MSGSIZE);
-
-
if (!message || !nl_sk) {
-
return;
-
}
-
printk(KERN_INFO "pid:%d\n", pid);
-
skb = alloc_skb(len, GFP_KERNEL);
-
if (!skb) {
-
printk(KERN_ERR "send_msg:alloc_skb error\n");
-
return;
-
}
-
nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
-
NETLINK_CB(skb).portid = 0;
-
NETLINK_CB(skb).dst_group = 0;
-
strcpy(NLMSG_DATA(nlh), message);
-
printk(KERN_INFO "my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
-
netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
-
}
-
-
void recv_nlmsg(struct sk_buff *skb)
-
{
-
int pid;
-
struct nlmsghdr *nlh = nlmsg_hdr(skb);
-
-
if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
-
return;
-
-
printk(KERN_INFO "Message received:%s\n", (char*)NLMSG_DATA(nlh));
-
pid = nlh->nlmsg_pid;
-
send_msg("I am from kernel!", pid);
-
}
-
-
struct netlink_kernel_cfg nl_kernel_cfg = {
-
.groups = 0,
-
.flags = 0,
-
.input = recv_nlmsg,
-
.cb_mutex = NULL,
-
.bind = NULL,
-
.compare = NULL,
-
};
-
-
int netlink_init(void)
-
{
-
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nl_kernel_cfg);
-
if (!nl_sk) {
-
printk(KERN_ERR "my_net_link: create netlink socket error.\n");
-
return 1;
-
}
-
printk(KERN_INFO "netlink_init: create netlink socket ok.\n");
-
return 0;
-
}
-
-
static void netlink_exit(void)
-
{
-
if (nl_sk != NULL)
-
sock_release(nl_sk->sk_socket);
-
-
printk(KERN_INFO "my_net_link: self module exited\n");
-
}
-
-
module_init(netlink_init);
-
module_exit(netlink_exit);
-
MODULE_AUTHOR("vv1133");
-
MODULE_LICENSE("GPL");
程序中,加载模块时,调用netlink_kernel_create()创建套接字。卸载模块时,调用sock_release()释放套接字。
netlink数据包处理函数是recv_nlmsg(),首先通过nlmsg_hdr()函数获取到netlink报头,netlink的消息报头由include/uapi/linux/netlink.h中的nlmsghdr定义:
-
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 */
-
};
我们通过NLMSG_DATA获取netlink的payload并打印出来。注意,netlink的payload一般使用TLV格式,即“类型-长度-值”,但这里我们只传递一个固定的字符串,简单起见就不用这个格式了。然后,通过alloc_skb()函数创建一个新的skb,并用nlmsg_put()函数填入netlink结构,填充要发送的字符串。最后调用netlink_unicast()发送数据。
用户态
对于用户态的程序,netlink通信与一般的socket通信类似。只是要用sendmsg()和recvmsg()代替send()/write()和recv()/read()
具体步骤如下:
1. 创建套接字
用户空间使用系统调用socket()创建netlink套接字,需要将域名设置成AF_NETLINK,类型必须是SOCK_RAW或SOCK_DGRAM,协议设置成我们的自定义netlink协议NETLINK_TEST。
2. 将本地套接字与源地址绑定
由于在netlink报头里要设置端口号,我们必须使用bind()函数将指定的端口与socket绑定。
3. 初始化msghdr
a. 设置目的地址信息
-
dest_addr.nl_family = AF_NETLINK;
-
dest_addr.nl_pid = 0;
-
dest_addr.nl_groups = 0;
b. 填充netlink报头,即nlmsghdr结构体
-
nlh = nlmsg_hdr(skb);
-
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
-
nlh->nlmsg_pid = getpid();
-
nlh->nlmsg_flags = 0;
c. 将缓冲区向量iovec与消息进行绑定,指向消息头。
-
iov.iov_base = (void *)nlh;
-
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
d. 填充msghdr结构体
-
struct msghdr msg;
-
msg.msg_name = (void *)&dest_addr;
-
msg.msg_namelen = sizeof(dest_addr);
-
msg.msg_iov = &iov;
-
msg.msg_iovlen = 1;
4. 调用sendmsg向内核发送消息,recvmsg从内核获取消息。
来看代码:
-
#include <sys/stat.h>
-
#include <unistd.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <sys/socket.h>
-
#include <sys/types.h>
-
#include <string.h>
-
#include <asm/types.h>
-
#include <linux/netlink.h>
-
#include <linux/socket.h>
-
#include <errno.h>
-
-
#define MAX_PAYLOAD 1024
-
#define NETLINK_TEST 25
-
-
int main(int argc, char* argv[])
-
{
-
int state;
-
struct sockaddr_nl src_addr, dest_addr;
-
struct nlmsghdr *nlh = NULL;
-
struct iovec iov;
-
struct msghdr msg;
-
int sock_fd, retval;
-
int state_smg = 0;
-
-
// Create a socket
-
sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
-
if(sock_fd == -1){
-
printf("error getting socket: %s", strerror(errno));
-
return -1;
-
}
-
-
// To prepare binding
-
memset(&src_addr, 0, sizeof(src_addr));
-
src_addr.nl_family = AF_NETLINK;
-
src_addr.nl_pid = 100; //设置源端端口号
-
src_addr.nl_groups = 0;
-
-
//Bind
-
retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
-
if (retval < 0) {
-
printf("bind failed: %s", strerror(errno));
-
close(sock_fd);
-
return -1;
-
}
-
-
// To orepare create mssage
-
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
-
if (!nlh) {
-
printf("malloc nlmsghdr error!\n");
-
close(sock_fd);
-
return -1;
-
}
-
-
memset(&dest_addr,0,sizeof(dest_addr));
-
dest_addr.nl_family = AF_NETLINK;
-
dest_addr.nl_pid = 0; //设置目的端口号
-
dest_addr.nl_groups = 0;
-
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
-
nlh->nlmsg_pid = 100; //设置源端口
-
nlh->nlmsg_flags = 0;
-
strcpy(NLMSG_DATA(nlh), "Hello from client!"); //设置消息体
-
iov.iov_base = (void *)nlh;
-
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
-
-
//Create mssage
-
memset(&msg, 0, sizeof(msg));
-
msg.msg_name = (void *)&dest_addr;
-
msg.msg_namelen = sizeof(dest_addr);
-
msg.msg_iov = &iov;
-
msg.msg_iovlen = 1;
-
-
//send message
-
printf("state_smg\n");
-
state_smg = sendmsg(sock_fd,&msg,0);
-
if (state_smg == -1) {
-
printf("get error sendmsg = %s\n",strerror(errno));
-
}
-
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
-
-
//receive message
-
printf("waiting received!\n");
-
state = recvmsg(sock_fd, &msg, 0);
-
if (state < 0) {
-
printf("state<1");
-
}
-
printf("Received message: %s\n", (char*)NLMSG_DATA(nlh));
-
-
close(sock_fd);
-
return 0;
-
}
运行程序:
加载模块
运行客户端程序
-
# ./client
-
state_smg
-
waiting
-
Received message: I am from
查看内核打印
-
# dmesg
-
[ 545.278040] my_net_link: self module exited
-
[ 679.799672] PPP MPPE Compression module registered
-
[ 4368.567435] netlink_init: create netlink socket ok.
-
[ 4379.239327] Message received:Hello from
-
[ 4379.239331] pid:100
-
[ 4379.239333] my_net_link:send message 'I am from kernel!'.
卸载模块
文中的代码可以在下载。
阅读(2206) | 评论(0) | 转发(0) |