初试Netlink之unicast
在之前已经了解了好几种内核态与用户态之间数据的交换方法,但那些方法都是单向的(即单工),其关系是一对一的,而Netlink Socket则是一种用于内核与用户空间之间传递信息的特殊IPC,在用户进程中,其以标准的Socket API为内核和用户之间提供了全双工的通信通道,在内核模块中则提供了一类特殊的API。相比TCP/IP socket所使用的AF_INET地址族,Netlink socket则使用AF_NETLINK地址族,每一个Netlink Socket都在Kernel的include/uapi/linux/netlink.h(linux-3.11.0-rc4,关于Netlink部分相关文档,如无注明均以该版本为基准)中定义了相应的protocol type。
在Linux Kernel中,以模块形式去创建Netlink Socket时需要涉及到如下相关的API及数据结构:
1.创建Socket
在include/linux/netlink.h文件中,有如下的定义:
/* 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;
void (*bind)(int group);
bool (*compare)(struct net *net, struct sock *sk);
};
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff *skb);
struct mutex *cb_mutex;
void (*bind)(int group);
bool (*compare)(struct net *net, struct sock *sk);
};
extern struct sock *__netlink_kernel_create(struct net *net, int unit,
struct module *module,
struct netlink_kernel_cfg *cfg);
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);
}
struct module *module,
struct netlink_kernel_cfg *cfg);
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);
}
其中,netlink_kernel_create的第一个参数net一般使用&init_net这个全局量;第二个参数unit可使用include/uapi/linux/netlink.h文件中以NETLINK_开头的宏定义,其对应Netlink支持的协议类型,也可以是自定义的类型(可自行添加相应的宏定义,等会的例子里会使用),而cfg对应上面的可选配置参数结构体(等会例子会使用到input,用于接收到数据时的回调处理,其对应的skb为接收到的数据对应的结构体,其他量在后期使用时再说明)。
2.数据处理
当上面的socket创建好后,其就会进行监听对应协议的数据,当收到数据时会调用上面的input回调函数,而其参数skb为接收到的struct sk_buff结构体,使用include/linux/netlink.h的如下函数获取struct nlmsghdr结构体指针值:
static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb)
{
return (struct nlmsghdr *)skb->data;
}
{
return (struct nlmsghdr *)skb->data;
}
而struct nlmsghdr结构体在include/uapi/linux/netlink.h文件中有如下声明:
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 */
};
__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 */
};
通过同一文件的如下宏:
#define NLMSG_ALIGNTO 4U
#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 <= (len))
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
#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 <= (len))
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
我们可以按需获得相应的参数数据,通过NLMSG_DATA宏即可获得接收到的数据内容。上述内容我们可以从获取到的sk_buff提取nlmsghdr后利用NLMSG_DATA宏来获取接收的数据内容,接下来我们看下如何构建要发送的数据和操作:
通过include/net/netlink.h文件中的如下函数:
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{
return alloc_skb(nlmsg_total_size(payload), flags);
}
{
return alloc_skb(nlmsg_total_size(payload), flags);
}
static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
int type, int payload, int flags)
{
if (unlikely(skb_tailroom(skb) < nlmsg_total_size(payload)))
return NULL;
int type, int payload, int flags)
{
if (unlikely(skb_tailroom(skb) < nlmsg_total_size(payload)))
return NULL;
return __nlmsg_put(skb, portid, seq, type, payload, flags);
}
}
来构建要发送的sk_buff和填充相应的nlmsghdr数据,最终使用include/linux/netlink.h中的如下函数:
extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
或include/net/netlink.h中的如下函数:
static inline int nlmsg_unicast(struct sock *sk, struct sk_buff *skb, u32 portid)
{
int err;
{
int err;
err = netlink_unicast(sk, skb, portid, MSG_DONTWAIT);
if (err > 0)
err = 0;
if (err > 0)
err = 0;
return err;
}
}
进行单播发送数据,在接下来的例子我们调用nlmsg_put时会设置portid为0,该处表示发送进程的端口号(因为是内核,默认设置为0,会与应用层处配置对应),而上面的netlink_unicast的portid为接收进程的端口号(下面的实例会使用传进来的pid),注意这两者的区别。
上面了解了Kernel部分的操作后,下面了解下用户态应用程序的处理所需:
1.创建及绑定socket
使用int socket(int domain, int type, int protocol)函数来创建一个socket,其中domain为AF_NETLINK,type只能为SOCK_RAW或SOCK_DGRAM(因为Netlink是基于数据报的传输,类似UDP),protocol为使用到的Netlink Socket协议(可以是自定义的,在下面例子会使用自定义的)。
在创建好socket后需要将其绑定bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr),其中fd为上面socket的返回值,而nladdr为struct sockaddr_nl结构体类型数据,其在include/uapi/linux/netlink.h文件中有如下声明:
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 */
};
__kernel_sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* zero */
__u32 nl_pid; /* port ID */
__u32 nl_groups; /* multicast groups mask */
};
最终会将其转换为sockaddr类型的指针,相应的在include/linux/socket.h文件中有如下声明:
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
2.数据处理
在上面创建好socket和绑定好本地信息后,接下来要构建发送给接收端的struct msghdr结构体数据,该结构体在include/linux/socket.h有如下声明:
struct msghdr {
void * msg_name; /* Socket name */
int msg_namelen; /* Length of name */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned int msg_flags;
};
void * msg_name; /* Socket name */
int msg_namelen; /* Length of name */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned int msg_flags;
};
对于该结构体,我们需要填充msg_name和msg_iov,具体在下面实例中可以看到填充什么数据,其中iovec结构体在include/uapi/linux/uio.h文件中有声明:
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
在构建好数据后,我们使用如下函数进行数据收发:
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
在上述分析后,接下来我们实例感受下通过Netlink Socket进行内核态与用户态之间进行通信的过程:
对于Kernel部分netlink_unicast.c的源码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <net/sock.h>
#include <net/netlink.h>
#include <linux/module.h>
#include <net/sock.h>
#include <net/netlink.h>
#define NETLINK_TEST 25
#define MAX_PAYLOAD_SIZE 1024
#define MAX_PAYLOAD_SIZE 1024
struct sock *nl_sk = NULL;
void sendnlmsg(int pid)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
char msg[30] = "Say hello from kernel!";
if(!nl_sk){
return;
}
return;
}
skb = nlmsg_new(MAX_PAYLOAD_SIZE, GFP_KERNEL);
if(!skb){
printk(KERN_ERR "nlmsg_new error!\n");
}
nlh = nlmsg_put(skb, 0, 0, 0, MAX_PAYLOAD_SIZE, 0);
if(!skb){
printk(KERN_ERR "nlmsg_new error!\n");
}
nlh = nlmsg_put(skb, 0, 0, 0, MAX_PAYLOAD_SIZE, 0);
memcpy(NLMSG_DATA(nlh), msg, sizeof(msg));
printk("Send message '%s'.\n",(char *)NLMSG_DATA(nlh));
printk("Send message '%s'.\n",(char *)NLMSG_DATA(nlh));
netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
}
}
void nl_data_ready(struct sk_buff *__skb)
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
char str[100];
{
struct sk_buff *skb;
struct nlmsghdr *nlh;
char str[100];
skb = skb_get (__skb);
if(skb->len >= NLMSG_SPACE(0)){
nlh = nlmsg_hdr(skb);
if(skb->len >= NLMSG_SPACE(0)){
nlh = nlmsg_hdr(skb);
memcpy(str, NLMSG_DATA(nlh), sizeof(str));
printk("Message received:%s\n",str) ;
sendnlmsg(nlh->nlmsg_pid);
kfree_skb(skb);
}
}
printk("Message received:%s\n",str) ;
sendnlmsg(nlh->nlmsg_pid);
kfree_skb(skb);
}
}
static int netlink_unicast_init(void)
{
struct netlink_kernel_cfg netlink_kerncfg = {
.input = nl_data_ready,
};
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &netlink_kerncfg);
{
struct netlink_kernel_cfg netlink_kerncfg = {
.input = nl_data_ready,
};
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &netlink_kerncfg);
if(!nl_sk){
printk(KERN_ERR "netlink_unicast_init: Create netlink socket error.\n");
return -1;
}
printk(KERN_ERR "netlink_unicast_init: Create netlink socket error.\n");
return -1;
}
printk("netlink_unicast_init: Create netlink socket ok.\n");
return 0;
}
return 0;
}
static void netlink_unicast_exit(void)
{
if(nl_sk != NULL){
sock_release(nl_sk->sk_socket);
}
{
if(nl_sk != NULL){
sock_release(nl_sk->sk_socket);
}
printk("netlink_unicast_exit!\n");
}
}
module_init(netlink_unicast_init);
module_exit(netlink_unicast_exit);
module_exit(netlink_unicast_exit);
MODULE_AUTHOR("X-SLAM XINU");
MODULE_LICENSE("GPL");
MODULE_LICENSE("GPL");
相应的Makefile内容如下:
obj-m += netlink_unicast.o
CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/guochongxin/xinu/linux-stable/
LINUX_KERNEL_PATH:=/home/guochongxin/xinu/linux-stable/
all:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
而对于用户层应用程序netlink_unicast_app.c文件的源码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/netlink.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/netlink.h>
#define NETLINK_TEST 25
#define MAX_PAYLOAD_SIZE 1024 // maximum payload size
#define MAX_PAYLOAD_SIZE 1024 // maximum payload size
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;
{
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;
}
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(&msg,0,sizeof(msg));
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
src_addr.nl_groups = 0;
memset(&msg,0,sizeof(msg));
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
src_addr.nl_groups = 0;
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;
}
if(retval < 0){
printf("bind failed: %s", strerror(errno));
close(sock_fd);
return -1;
}
// To prepare recvmsg
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD_SIZE));
if(!nlh){
printf("malloc nlmsghdr error!\n");
close(sock_fd);
return -1;
}
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD_SIZE));
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; /* For Linux Kernel */
dest_addr.nl_groups = 0; /* Unicast */
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; /* For Linux Kernel */
dest_addr.nl_groups = 0; /* Unicast */
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD_SIZE);
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Say hello from user application!");
nlh->nlmsg_pid = getpid();
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Say hello from user application!");
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD_SIZE);
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD_SIZE);
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;
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
state_smg = sendmsg(sock_fd, &msg, 0);
if(state_smg == -1){
printf("get error sendmsg = %s\n",strerror(errno));
}
if(state_smg == -1){
printf("get error sendmsg = %s\n",strerror(errno));
}
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD_SIZE));
printf("waiting received!\n");
// Read message from kernel
state = recvmsg(sock_fd, &msg, 0);
if(state < 0){
printf("recvmsg state < 1");
}
printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
close(sock_fd);
printf("waiting received!\n");
// Read message from kernel
state = recvmsg(sock_fd, &msg, 0);
if(state < 0){
printf("recvmsg state < 1");
}
printf("Received message: %s\n",(char *) NLMSG_DATA(nlh));
close(sock_fd);
return 0;
}
}
相应的Makefile文件内容如下:
all:
gcc -o netlink_unicast_app netlink_unicast_app.c
gcc -o netlink_unicast_app netlink_unicast_app.c
clean:
rm -rf *.o netlink_unicast_app
rm -rf *.o netlink_unicast_app
最终的源码文件目录树如下:
/home/guochongxin/xinu/xinu/linux_kernel_driver_l1/netlink_unicast/
├── kernel_module
│ ├── Makefile
│ └── netlink_unicast.c
└── user_application
├── Makefile
└── netlink_unicast_app.c
├── kernel_module
│ ├── Makefile
│ └── netlink_unicast.c
└── user_application
├── Makefile
└── netlink_unicast_app.c
当我们先编译加载内核模块sudo insmod netlink_unicast.ko时,dmesg命令有如下输出:
netlink_unicast_init: Create netlink socket ok.
当我们运行应用程序./netlink_unicast_app时,Terminal有如下输出:
waiting received!
Received message: Say hello from kernel!
Received message: Say hello from kernel!
而当我们再使用dmesg命令查看时会有如下输出:
Message received:Say hello from user application!
Send message 'Say hello from kernel!'.
Send message 'Say hello from kernel!'.
至此,使用Netlink Socket的Unicast方式通信能正常使用了,以后内核态与用户态之间自由交换意见就更容易了,接下来我们会讲到Broadcast等方式的通信,静候更新。
参考网址:http://www.cnblogs.com/D3Hunter/p/3207670.html
http://blog.csdn.net/wangpengqi/article/details/9969599
http://www.cnblogs.com/hoys/archive/2011/04/09/2010788.html
http://binwaheed.blogspot.com/2010/08/after-reading-kernel-source-i-finally.html
http://ycool.com/post/by6vz7j
http://blog.csdn.net/vichie2008/article/details/37568065
http://www.linuxjournal.com/article/7356?page=0,3
http://blog.csdn.net/wangpengqi/article/details/9969599
http://www.cnblogs.com/hoys/archive/2011/04/09/2010788.html
http://binwaheed.blogspot.com/2010/08/after-reading-kernel-source-i-finally.html
http://ycool.com/post/by6vz7j
http://blog.csdn.net/vichie2008/article/details/37568065
http://www.linuxjournal.com/article/7356?page=0,3
评论
发表评论