现在使用NETLINK进行内核与上层应用之间的通信,我们不说别的了,直接上代码把:
内核空间的:
//内核端
#include <linux/init.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <net/netlink.h>
#define NETLINK_TEST 25
#define MAX_MSGSIZE 1024
int err;
struct sock *nl_sk = NULL;
void nl_data_ready(struct sk_buff *__skb)
{
printk("nl_data_ready has been called %s\n") ;
struct sk_buff *skb;
struct nlmsghdr *nlh;
char str[100];
skb = skb_get (__skb);
if(skb->len >= NLMSG_SPACE(0))
{
nlh = nlmsg_hdr(skb);
//这一部分,我们也可以看分明了NLMSG_DATA,是越过了消息头部,移动到了消息体位置
memcpy(str, NLMSG_DATA(nlh), sizeof(str));
//将消息体内容复制到了str当中,然后输出
printk("Message received:%s\n",str) ;
}
}
// Initialize netlink
int netlink_init(void)
{
struct netlink_kernel_cfg cfg = {
.input = nl_data_ready,
};
//cfg只需要设定回调函数,该函数在每次有消息传递给内核的时候自动调用
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
if(!nl_sk){
printk(KERN_ERR "my_net_link: create netlink socket error.\n");
return 1;
}
printk("my_net_link_3: create netlink socket ok.\n");
return 0;
}
//当然在该驱动卸载的时候,进行自动调用sock_release
static void netlink_exit(void)
{
if(nl_sk != NULL){
sock_release(nl_sk->sk_socket);
}
printk("my_net_link: self module exited\n");
}
module_init(netlink_init);
module_exit(netlink_exit);
内核空间的Makefile
obj-m := main.o
all:
make -C /lib/modules/`uname -r`/build SUBDIRS=$(PWD) modules
clean:
make -C /lib/modules/`uname -r`/build SUBDIRS=$(PWD) clean
PS:
这里,在内核空间主要也就一部分,就是netlink_kernel_create,在这个函数中注册回调函数,该回调函数在有应用使用netlink向 内核发送消息时自动调用
nlmsg_hdr获取netlink的数据包中netlink_header的头部地址
NLMSG_DATA 的作用就是根据netlink的头部地址,进行消息头部的偏移,然后进行获取消息体,真正消息体的地址
用户空间的源代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <asm/types.h>
#include <linux/socket.h>
#include <linux/netlink.h>
#include <unistd.h>
#include<errno.h>
#define USER_NETLINK_CMD 25
#define MAXMSGLEN 1024
typedef enum error_e {
NET_ERROR,
NET_OK,
NET_PARAM,
NET_MEM,
NET_SOCK,
} netlink_err;
typedef enum module_e {
HELLO_CMD = 1,
} netlink_module;
typedef enum type_e {
HELLO_SET,
HELLO_GET,
} netlink_type;
typedef struct usr_sock_h {
int sock;
struct sockaddr_nl dest;
struct sockaddr_nl src;
} netlink_sock;
int netlink_sock_init(netlink_sock *netlink_s, int module, int protocol);
int netlink_sock_deinit(netlink_sock *netlink_s);
int netlink_send(netlink_sock *netlink_s, int type, char *sbuf, int slen, char *rbuf, int *rlen);
int netlink_sock_init(netlink_sock *netlink_s, int module, int protocol)
{
// 创建套接字,需要注意的是第二个参数要么是SOCK_RAW 或者是SOCK_DGRAM
netlink_s->sock = socket(PF_NETLINK, SOCK_RAW, protocol);
if(netlink_s->sock < 0)
return NET_SOCK;
// 在发送过程中我们需要进行发送方源地址的填充
memset(&netlink_s->src, 0 ,sizeof(netlink_s->src));
// nl_family必须是AF_NETLINK
netlink_s->src.nl_family = AF_NETLINK;
// nl_pid 应该是getpid(),也就是发送方的pid
netlink_s->src.nl_pid = module;
netlink_s->src.nl_groups = 0;
//将sockfd也即netlink_s->sock与特定地址,也即进程号进行绑定
if(bind(netlink_s->sock, (struct sockaddr *)&netlink_s->src, sizeof(netlink_s->src)) < 0)
return NET_SOCK;
netlink_s->dest.nl_family = AF_NETLINK;
//设定目的地址,也即nl_pid是目的进程号,如果是0的话,那么表示目的进程是内核
netlink_s->dest.nl_pid = 0;
//不加入任何组播
netlink_s->dest.nl_groups = 0;
return NET_OK;
}
int netlink_send(netlink_sock *netlink_s, int type, char *sbuf, int slen, char *rbuf, int *rlen)
{
//必须初始化为0,如果没有初始化的话,可能no buffer avaliable
struct msghdr msg={0};
struct nlmsghdr *nlhdr = NULL;
struct iovec iov;
int ret;
//申请空间,该空间应该由两部分,一部分是由消息头部组成,另一部分则是消息本身
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAXMSGLEN));
if(NULL == nlhdr)
return NET_MEM;
// NLMSG_DATA表示的是越过消息头部,移动到消息体位置的指针
memcpy(NLMSG_DATA(nlhdr), sbuf,slen);
//这一部分当然就是设置消息头部,或者说发送方的地址
nlhdr->nlmsg_len = NLMSG_SPACE(slen);
nlhdr->nlmsg_pid = netlink_s->src.nl_pid;
nlhdr->nlmsg_type = type;
nlhdr->nlmsg_flags = 0;
iov.iov_base = (void *)nlhdr; //发送内容,其中包括发送源地址 NLMSG_DATA(nlhdr)之后才是真正的内容
iov.iov_len = nlhdr->nlmsg_len;
//发送的目的地址dest的pid是0,表示发送到内核
msg.msg_name = (void *)&(netlink_s->dest);
msg.msg_namelen = sizeof(netlink_s->dest);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
ret = sendmsg(netlink_s->sock, &msg, 0);
if(ret < 0)
{
printf("Send fail %s\n",strerror(errno));
goto error;
}
free(nlhdr);
return NET_OK;
error:
free(nlhdr);
return NET_SOCK;
}
int netlink_sock_deinit(netlink_sock *netlink_s)
{
close(netlink_s->sock);
memset(netlink_s, 0, sizeof(netlink_sock));
return NET_OK;
}
int parse_ret(int ret)
{
switch(ret)
{
case NET_OK:
return ret;
case NET_ERROR:
printf("error\n");
goto exit_p;
case NET_MEM:
printf("Memory error:%s\n",strerror(errno));
goto exit_p;
case NET_PARAM:
printf("Param error:%s\n",strerror(errno));
goto exit_p;
case NET_SOCK:
printf("Socket error:%s\n",strerror(errno));
goto exit_p;
default:break;
}
exit_p:
return NET_ERROR;
}
int main(int argc, char **argv)
{
netlink_sock h_sock;
char rbuf[1024];
char sbuf[1024];
int ret, type, slen = 0, rlen = 0;
ret = netlink_sock_init(&h_sock,getpid(), USER_NETLINK_CMD);
if(NET_OK != parse_ret(ret))
goto exit_p;
bzero(&rbuf, sizeof(rbuf));
bzero(&sbuf, sizeof(sbuf));
type = HELLO_SET;
strcpy(sbuf,"this is from ring3");
slen = strlen(sbuf);
ret = netlink_send(&h_sock, type, sbuf, slen, rbuf, &rlen);
if(ret ==NET_OK)
{
printf("send succefully:%s\n", sbuf);
}
exit_p:
netlink_sock_deinit(&h_sock);
return 0;
}
PS:
这一部分,要做的三件事,其一
1.创建套接字,但必须注意的是SOCK_RAW或者是SOCK_DGRAM
2.bind操作,也即将当前进程的PID和创建的套接字进行绑定,根据这个套接字进行消息的发送和接收
3.使用sendmsg函数进行消息的发送,这里面需要注意的是我们在发送过程中,消息的构造
msg.msg_name:指明的是发送地址
msg.msg_namelen:指明的是发送地址的长度
而msg.iov_base:指的是发送内容,然而该部分主要由两部分组成,一部分是消息头部,一部分是消息体,消息头部中指明的是发送方的地址
也即iov_base中把消息头部和消息体放在一起,进行发送