天天看点

用户空间与内核的交互---NETLINK

   关于netlink,相信玩过上层开发的人并不陌生,尤其是经常穿梭于用户空间和内核之间的开发,前面我们说过在用户空间与内核空间的交互有ioctl 、proc 等,而netlink又有它独特的地位和作用.

  我开始接触netlink是开始于去实现一个恢复出厂值的按键.当然先要实现一个中断,然后再去通知上层做相应的恢复配置文件的操作,当时是参考了前辈的代码,稍作修改,才搞定,对于当时的netlink的广播消息还是相当的费解^^. 在后来就是看了《深入理解linux网络技术内幕》中有提到,但是篇幅很少,当时从网上来看,从实际的应用来看netlink不容易忽视. 

   Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。  

   Netlink套接字可以使用标准的套接字APIs来创建。socket(), bind(), sendmsg(), recvmsg() 和 close()很容易地应用到 netlink socket。

   netlink包含于头文件linux/netlink.h中.

Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:

1,为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。

2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接 收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。

3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。

4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在 后面的文章中将介绍这一机制的使用。

5.内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。

6.netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用

 当然netlink有单播和广播之分,下面我们就先用实际例子为引导来分析:

userspace :

一个简单的应用程序:

点击(此处)折叠或打开

// userspace :

#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>

struct u_packet_info

{

    struct nlmsghdr hdr;

    char buf[10];

};

#if 1

int

main (int argc, char **argv)

    int fd,len1,len2;

    struct sockaddr_nl src_addr,dst_addr;

    struct nlmsghdr *mymsg, mymsg2 ;

    struct msghdr msg;

      struct u_packet_info *info;

     char *buf;

    /*test my netlink :socket */

    fd = socket(AF_NETLINK,SOCK_RAW,NETLINK_MYCTRL);

    if(fd 0)

    {

        printf("create netlink_myctrl socket fail !n");

        return -1;

    }

        memset(&src_addr,0,sizeof(src_addr));

        src_addr.nl_family = AF_NETLINK;

        src_addr.nl_pid = getpid();

        src_addr.nl_groups = 0;

    if(bind(fd,(struct sockaddr *)&src_addr,sizeof(src_addr)) != 0)

        perror("bind errorn");

         memset(&dst_addr,0,sizeof(dst_addr));

          dst_addr.nl_family = AF_NETLINK;

        dst_addr.nl_pid = 0;

        dst_addr.nl_groups = 0; //not in multicast

    mymsg =(struct nlmsghdr *)malloc(NLMSG_SPACE(20));

    info =(struct u_packet_info *)malloc(sizeof(struct u_packet_info ));

    mymsg->nlmsg_len = NLMSG_LENGTH(20);

    mymsg->nlmsg_flags = 0;

    mymsg->nlmsg_type = NETLINK_MYCTRL;

    mymsg->nlmsg_pid = src_addr.nl_pid;

    strcpy(NLMSG_DATA(mymsg), "Hello you!");

     memset(&msg,0,sizeof(msg));

    //send to kernel

    sendto(fd,mymsg,mymsg->nlmsg_len,0,(struct sockaddr *) &dst_addr,sizeof(dst_addr));

    free(mymsg);

   while(1)

      len2 = sizeof(struct sockaddr_nl);

      len1 = recvfrom(fd, info ,sizeof(struct u_packet_info),0,(struct sockaddr *)&dst_addr,(socklen_t *)&len2);

     if(len1 > 0)

     {

         printf("------------------%d-----recv ok !!!n",info->hdr.nlmsg_pid);

        printf("------------------%s-----recv ok !!!n",info->buf);

        free(info);

        return 0;

     }

}

#endif

这里我们关于头文件include/linux/netlink.h  ,可以自定义我们netlink协议.

#define   NETLINK_MYCTRL  23

#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

#define MAX_LINKS 32        

struct net;

struct sockaddr_nl

    sa_family_t    nl_family;    /* AF_NETLINK    */

    unsigned short    nl_pad;        /* zero        */

    __u32        nl_pid;        /* port ID    */

           __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 port ID */

从头文件里我们知道其实内核已经支持了一些常用的协议.

对于用户空间使用netlink我们直接调用socket API即可.  

 fd = socket(AF_NETLINK,SOCK_RAW,NETLINK_MYCTRL);  

关于socket的几个参数这里不过多解释. 上面例子我们是为了向内核发送一个消息,内核处理后,发送回来,我们来接收. 下面就看看内核代码: 

#include linux/kernel.h>

#include linux/module.h>

#include linux/types.h>

#include linux/sched.h>

#include net/sock.h>

int my_pid;

struct sock * myctrlnl;

static void netlink_kernel_rcv(struct sk_buff *skb)

    struct nlmsghdr *nlh = NULL;

    nlh = nlmsg_hdr(skb);

            if(nlh->nlmsg_type == NETLINK_MYCTRL){

                my_pid = nlh->nlmsg_pid; //record pid

                printk("NETLINK_TEST_U_PID: user_proc.pid = %dn",my_pid);

                printk("%s: received netlink message payload:%sn", __FUNCTION__, (char*)NLMSG_DATA(nlh));

        skb = skb_clone(skb, GFP_KERNEL);

        if (skb == NULL)

                return;

             nlh = nlmsg_hdr(skb);

            nlh->nlmsg_pid =5;

            memcpy((char *)NLMSG_DATA(nlh),"yes,we do!",20);

            netlink_unicast(myctrlnl, skb, my_pid, MSG_DONTWAIT);

 }

void __init mynetlink_init(void)

    /*test my netlink */

    myctrlnl = netlink_kernel_create(&init_net, NETLINK_MYCTRL, MYCTRLNLGRP_MAX, netlink_kernel_rcv,

     NULL, THIS_MODULE);

    if (myctrlnl == NULL)

        panic("ctrlnetlink_init: cannot initialize myctrlnetlinkn");

这里是接收到用户空间消息后,打印pid ,和传递的数据负载.  然后修改后,在传递回用户空间. 这里有个小插曲就是关于要用skb_clone.

这里参考的是内核代码: 

static void nl_fib_input(struct sk_buff *skb)

    struct net *net;

    struct fib_result_nl *frn;

    struct nlmsghdr *nlh;

    struct fib_table *tb;

    u32 pid;

    net = sock_net(skb->sk);

    if (skb->len NLMSG_SPACE(0) || skb->len nlh->nlmsg_len ||

     nlh->nlmsg_len NLMSG_LENGTH(sizeof(*frn)))

        return;

    skb = skb_clone(skb, GFP_KERNEL);

    if (skb == NULL)

    frn = (struct fib_result_nl *) NLMSG_DATA(nlh);

    tb = fib_get_table(net, frn->tb_id_in);

    nl_fib_lookup(frn, tb);

    pid = NETLINK_CB(skb).pid; /* pid of sending process */

    NETLINK_CB(skb).pid = 0; /* from kernel */

    NETLINK_CB(skb).dst_group = 0; /* unicast */

    netlink_unicast(net->ipv4.fibnl, skb, pid, MSG_DONTWAIT);

有时候我们不仅仅要自己修改传递过来的skb,或许我们想自己新创建一个:

static int send_to_kernel(void)

    struct sk_buff *skb;

    struct nlmsghdr* nlh;    

    char msg[6] ="hello";

    char *msg1;

    int size,pid;

    unsigned char *b;

       printk("----here send from kernel :%dn",my_pid);

       size_t payload = sizeof(*msg);

    //计算skb分配长度

        size = NLMSG_SPACE(20);

    //分配skb

    skb = alloc_skb(size, GFP_ATOMIC);

    if (NULL == skb )

    {     

        printk(" skb malloc error.\n");

    nlh = NLMSG_PUT (skb, 0, 0, 0, 20);

    memcpy((char *)NLMSG_DATA(nlh),"nihao",20);

    nlh->nlmsg_len = NLMSG_LENGTH(20);

    NETLINK_CB(skb).dst_group = 0; /* unicast */    

    netlink_unicast(myctrlnl, skb, my_pid, MSG_DONTWAIT);

    return 0;    

nlmsg_failure:

    kfree_skb (skb);

    return -1;

内核里使用netlink需要调用 netlink_kernel_create函数,不同内核版本参数有变动,这里是用的2.6.32.60 

很多时候我们不想这么麻烦的传递一个东西,先是从用户空间传递一个消息(主要是为了pid ,相对于单播) ,然后内核才能传递给用户空间特定的进程. 我们有时候会需要不理会用户空间,直接从内核传递到用户空间,然后在做处理,那么这里就需要到netlink的广播功能.  

那么相对单播广播其实需要改动的地方也不大 :

对于内核部分我们只需要修改:

        NETLINK_CB(skb).pid =0;

        NETLINK_CB(skb).dst_group = MYCTRLNLGRP_TEST;

        netlink_broadcast(myctrlnl,skb,0,MYCTRLNLGRP_TEST,GFP_ATOMIC);

我们同样需要在netlink头文件里定义我们的广播协议:

enum myrlnetlink_groups {

    MYCTRLNLGRP_NONE,

#define MYCTRLNLGRP_NONE        MYCTRLNLGRP_NONE

        MYCTRLNLGRP_TEST,

#define MYCTRLNLGRP_TEST MYCTRLNLGRP_TEST

        __MYRLNLGRP_MAX

#define MYRLNLGRP_MAX    (__MYRLNLGRP_MAX - 1)

那么我们用户空间的程序需要修改些什么呢?  

src_addr.nl_groups = MYCTRLNLGRP_TEST; 

这里我们并没有关注太多的细节,而是直接拿来个例子.当然更关注的是如何应用,至于其他的深入分析待以后更加了解后在说吧.只是为了学习netlink一个小小的开始.

继续阅读