搭建内核态与用户态通信机制

Netlink相对于其他的通信机制具有以下优点:

  1. 使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。
  2. Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。
  3. Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。
  4. Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。

内核态代码: 

#include <linux/module.h>
#include <linux/crypto.h>

#include <linux/sched.h>
#include <net/netlink.h>
#include <linux/security.h>
#include <net/net_namespace.h>
#include <linux/mutex.h>

#include <linux/kernel.h>
#include <linux/if.h>
#include <linux/timer.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
#include <linux/if_arp.h>
#include <linux/list.h>

#include <linux/string.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

#include "netlink_drv.h"

static struct sock *nlsk;

static int send_id_to_user(struct sock *ntsk, int pid, unsigned char id, int ret)
{
    struct sk_buff* pskb;
    struct nlmsghdr* nlhnew;
    struct nldata_chipid* pstdata;
	int err;

        
    pskb = nlmsg_new(sizeof(struct nldata_chipid), GFP_KERNEL);
    if (pskb == NULL) {
        printk( KERN_WARNING "send_id_to_user \r\n");
        return -ENOMEM;
    }

    nlhnew = nlmsg_put(pskb, 0, 0, NTLK_CHIPID, sizeof(pstdata), 0);
    if (nlhnew == NULL) {
        printk( KERN_WARNING "send_id_to_user \r\n");
        return -1;
    }
	
    pstdata = nlmsg_data(nlhnew);
	memset(pstdata, 0x0, sizeof(pstdata));
    pstdata->chipid = id;
    pstdata->ret = ret;
    nlmsg_end(pskb, nlhnew);
    
    err = netlink_unicast(ntsk, pskb, pid, MSG_DONTWAIT);
    if (err < 0) {
        printk( KERN_WARNING "send_id_to_user error:%d \r\n", err);
        return err;
    }
    return 0;
}

static void netlink_msg_rcv(struct sk_buff *skb)
{
    struct nlmsghdr *nlh;
    int type, i, errnum = 0; 

    nlh = nlmsg_hdr(skb);

    if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len) {
        printk( KERN_WARNING "netlink_msg_rcv error len \r\n");
        return;
    }

    type = nlh->nlmsg_type;
    if (type > NTLK_MAX) {
        printk( KERN_WARNING "netlink_msg_rcv rcv error type:%d\r\n", type);
        return;
    }
    switch(type) {
        /*type自定义,根据用户态传下来的type做相应处理,然后条用api将信息传回用户态 例如传id*/
        char id;
        send_id_to_user(nlsk, pid, id, ret);
    }

    if (errnum != 0 ) {
        printk( KERN_WARNING "netlink_msg_rcv return :%d \r\n", errnum);
        return;
    }
    return;
}


static int __init netlink_init(void)
{
    printk("netlink_kernel init\n");
    struct netlink_kernel_cfg cfg = {
       .input  = netlink_msg_rcv,
    };
    nlsk = netlink_kernel_create(&init_net, 23, &cfg);
    if (!nlsk) {
        
        printk( "netlink_kernel_create return null\r\n");
        
        return -ENOMEM;
    }
        
    return 0;
}


static void __exit netlink_exit(void)
{
	 netlink_kernel_release(nlsk);
}


module_init(netlink_init);
module_exit(netlink_exit);
MODULE_LICENSE("GPL");

用户态代码:

#include <sys/socket.h>
#include <sys/time.h>
#include <linux/netlink.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

struct id_userdata{
    struct nlmsghdr hdr;
    unsigned char id;
    unsigned int ret;
};

int user_netlink_init(void)
{
    int ifd;
    int iOpt = 1;
    struct sockaddr_nl addr;    
  
    ifd = socket(PF_NETLINK, SOCK_DGRAM, 23);
    if(ifd < 0) {
        printf("%s\n", strerror(errno));
         return -1;  
    }   

    setsockopt(ifd, SOL_SOCKET, SO_REUSEADDR, &iOpt, sizeof(iOpt));

    addr.nl_family = PF_NETLINK;  
    addr.nl_pad    = 0;  
    addr.nl_pid    = getpid();  
    addr.nl_groups = 0;  
    if(bind(ifd, (struct sockaddr*)&addr, sizeof(addr)) < 0){
        printf("bind < 0\n");
        close(ifd);
        return -2;  
    }  
    //set_nonblocking(ifd);
    return ifd;
}

int user_netlink_recv_data(int socketfd, void *data, int send_len, int recv_len)
{
    int ret;  
    int recvcount = 0;
    
    struct sockaddr_nl st_peer_addr;
    st_peer_addr.nl_family = AF_NETLINK;  
    st_peer_addr.nl_pad = 0;       /*always set to zero*/  
    st_peer_addr.nl_pid = 0;       /*kernel's pid is zero*/  
    st_peer_addr.nl_groups = 0;    /*multicast groups mask, if unicast set to zero*/  

    /*recv message */
    ret = sendto(socketfd, data, send_len, 0, (struct sockaddr*)&st_peer_addr, sizeof(st_peer_addr));  
    if(ret < 0) { 
        return -1;
    }
    struct timeval tv_out;
    tv_out.tv_sec = 3;
    tv_out.tv_usec = 0;
    setsockopt(socketfd,SOL_SOCKET,SO_RCVTIMEO,&tv_out, sizeof(tv_out));
    ret = recvfrom(socketfd, data, recv_len, 0 , NULL, NULL);
    return ret;
}



int id_get(unsigned char* chipid)
{
    int ret;
    int iskfd = 0;
    struct id_userdata stprdata;
    iskfd = user_netlink_init();
    if (iskfd <= 0) {
        return iskfd;
    }
    memset(&stprdata, 0x00, sizeof(stprdata));

    stprdata.hdr.nlmsg_len   = sizeof(stprdata);  //NLMSG_LENGTH(sizeof(netlink_notify_s))
    stprdata.hdr.nlmsg_flags = 0;                         
    stprdata.hdr.nlmsg_type  = NTLK_ID;       /*自定义的type*/                 
    stprdata.hdr.nlmsg_pid   = getpid();      /* set sender  PID*/  


    ret = user_netlink_recv_data(iskfd, &stprdata, sizeof(stprdata), sizeof(stprdata));
    if (ret > 0) {
        if (stprdata.ret != 0) {
            close(iskfd);
            return -3;
        }
        else{
            *chipid = stprdata.id;
        }	
    }
    else{
        close(iskfd);
        return -4;
    }
    close(iskfd);
    return 0;
}

注:23为自定义协议类型,netlink.h头文件中定义了MAX_LINKS 32,最大不能超过32内核态与用户态代码协议类型须一致,并且内核态代码必须保证先加载成功,否则自定义的协议类型在用户态创建socket会返回失败;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、imp2源码 整个源码包含三个文件:imp2_k.c, imp2_u.c和imp2.h. 其中imp2_k.c为内核模块的源代码,imp2_u.c为应用程序,即测试代码,imp2.h为两个源文件都需要引用的头文件。其整体的功能是:注册一种新的netlink协议,并注册一个新的NF hook函数。当有ping包发往当前主机或者经过当前主机转发时,内核用户发送ping包的源IP和目的IP。各个文件的简单分析见下文。 1. imp2.h 该文件主要是定义了一种新的Netlink协议类型NL_IMP2(31)。新的协议类型的选值不能和当前内核中已经定义的netlink协议类型重复。定义了基于该协议类型的消息类型,内核根据接收到消息的不同类型,进行不同的处理:IMP2_U_PID和IMP2_CLOSE分别为请求和关闭。IMP2_K_MSG代表内核空间发送的消息。 该头文件的源码如下: 2. imp2_k.c 该程序为内核模块程序。其完成的功能如下: (1)创建一种新的Netlink协议NL_IMP2,并注册该协议的回调函数kernel_receive。但用户空间通过建立且协议类型为NL_IMP2的socket套接字并调用sendto,sendmsg函数发送数据时,传送到内核空间的数据由kernel_receive进行处理。该函数主要是记录用户进程的ID,用于随后发送数据的时候指定目的。 (2)在Netfilter的hook点NF_IP_PRE_ROUTING注册hook函数get_icmp,对经过该hook点的ping包进行处理。get_icmp首先判断是否是ping包,如果不是,直接Accept。如果是,则记录该包的源IP和目的IP,然后调用send_to_user,将记录的信息发送给kernel_recieve函数中记录的用户进程ID。 该文件的源码如下: 3. imp2_u.c 该程序为用户空间的测试程序。该程序包括以下功能: (1)生成NL_IMP2协议的socket.然后通过调用sendto发送IMP2_U_PID类型的请求信息给内核。然后等待接受内核发回的信息。记住:仅当有ping包经过内核的NF时,内核才会向用户进程发送信息。 (2)当用户进程通过Ctrl+C来结束该程序时,调用信号处理函数sig_int,向内核发送IMP2_CLOSE的消息,结束socket。 该文件的源码如下: 二、编译和测试 1. 整个源文件编译的Makefile如下: all: gcc -O2 -DMODULE -D__KERNEL__ -W -Wstrict-prototypes -Wmissing-prototypes -isystem /lib/modules/`uname -r`/build/include -c -o imp2_k.o imp2_k.c gcc imp2_u.c -o imp2_u install: insmod imp2_k.o uninstall: rmmod imp2_k clean: rm -f imp2_k.o imp2_u 2. 加载内核模块,并执行测试程序。 #make install #./imp2_u 当没有ping包时,终端一直处于等待输出状。通过另一台主机(192.168.1.100)向当前主机(192.168.1.101)发送ping包,则终端已经有输出: #./imp2_u [root@localhost imp2]# ./imp2_u src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101 src: 192.168.1.100, dest: 192.168.1.101

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值