Generic netlink编程入门

通过generic netlink可以实现内核和用户空间的通信,genetlink是通过family来管理的(哈希表),genl_ctrl 是一个特殊的Family, 它是由Generic Netlink自己注册和实现,并用来查询Family列表、管理各个Family的添加、删除等事件的。用户空间先根据family
name请求到相应的family ID,而后进行相互沟通。
static struct genl_family
genl_ctrl = {
.id = GENL_ID_CTRL,
.name = "nlctrl",
.version = 0x2,
.maxattr = CTRL_ATTR_MAX,
.netnsok = true,
};
static struct genl_ops genl_ctrl_ops = {
.cmd = CTRL_CMD_GETFAMILY,
.doit = ctrl_getfamily,
.dumpit = ctrl_dumpfamily,
.policy = ctrl_policy,
};
static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
[CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 },
[CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING, .len = L_NAMSIZ - 1 },
};
genl_kern.c
#include <net/genetlink.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
/*netlink attributes 可以通过枚举索引找到对应的类型
*用户空间应用程序要传递这样的信息*/

enum {
DOC_EXMPL_A_UNSPEC,
DOC_EXMPL_A_MSG,
__DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)

/*atribute policy就是定义各个属性的具体类型,参见net/netlink.h*/
static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {
        [DOC_EXMPL_A_MSG] = {.type = NLA_NUL_STRING},
};

#define VERSION_NR 1

//generic netlink family 定义
static struct genl_family doc_exmpl_genl_family = {
        .id = GENL_ID_GENERATE,
        .hdrsize = 0,
        .name = "CONTROL_EXMPL",
        .version = VERSION_NR,
        .maxattr = DOC_EXMPL_A_MAX,
};

/*定义命令类型,用户空间以此来表明需要执行的命令*/
enum{
DOC_EXMPL_C_UNSPEC,
DOC_EXMPL_C_ECHO,
__DOC_EXMPL_C_MAX,
};
#define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1)

//echo command handler,接收一个msg并回复
int doc_exmpl_echo(struct sk_buff *skb2, struct genl_info *info){
        struct nlattr *na;
        struct sk_buff *skb;
        int rc;
        void *msg_hdr;
        char *data;
        if(info == NULL)
                goto error;
        //对于每个属性,genl_info的域attrs可以索引到具体结构,里面有payload
        na = info->attrs[DOC_EXMPL_A_MSG];
        if(na){
                data = (char *)nla_data(na);
                if(!data) printk("Receive data error!\n");
                else printk("Recv:%s\n",data);
        }else{
                printk("No info->attrs %d\n",DOC_EXMPL_A_MSG);
        }

        skb = genlmsg_new(NLMSG_GOODSIZE,GFP_KERNEL);
        if(!skb) goto error;

        /*构建消息头,函数原型是
        genlmsgput(struct sk_buff *,int pid,int seq_number,
                struct genl_family *,int flags,u8 command_index);
        */
        msg_hdr = genlmsg_put(skb,0,info->snd_seq+1,&doc_exmpl_genl_family,
                                0,DOC_EXMPL_C_ECHO);
        if(msg_hdr == NULL){
                rc = -ENOMEM;
                goto error;
        }

        //填充具体的netlink attribute:DOC_EXMPL_A_MSG,这是实际要传的数据
        rc = nla_put_string(skb,DOC_EXMPL_A_MSG,"Hello World from kernel space!");
        if(rc != 0) goto error;

        genlmsg_end(skb,msg_hdr);//消息构建完成
        //单播发送给用户空间的某个进程
        rc = genlmsg_unicast(genl_info_net(info),skb,info->snd_pid);
        if(rc != 0){
                printk("Unicast to process:%d failed!\n",info->snd_pid);
                goto error;
        }
        return 0;

        error:
        printk("Error occured in doc_echo!\n");
        return 0;
}

//将命令command echo和具体的handler对应起来
static struct genl_ops doc_exmpl_genl_ops_echo = {
        .cmd = DOC_EXMPL_C_ECHO,
        .flags = 0,
        .policy = doc_exmpl_genl_policy,
        .doit = doc_exmpl_echo,
        .dumpit = NULL,
};

//内核入口,注册generic netlink family/operations
static int __init genKernel_init(void) {
         int rc;
         printk("Generic Netlink Example Module inserted.\n");

         rc = genl_register_family(&doc_exmpl_genl_family);
         if (rc != 0) {
                  goto failure;
         }
         rc = genl_register_ops(&doc_exmpl_genl_family,&doc_exmpl_genl_ops_echo);
         if (rc != 0) {
         printk("Register ops: %i\n",rc);
         genl_unregister_family(&doc_exmpl_genl_family);
         goto failure;
         }
         return 0;

        failure:
         printk("Error occured while inserting generic netlink example module\n");
         return -1;
}

static void __exit genKernel_exit(void) {
         int ret;
         printk("Generic Netlink Example Module unloaded.\n");

         ret = genl_unregister_ops(&doc_exmpl_genl_family,&doc_exmpl_genl_ops_echo);
         if(ret != 0) {
                  printk("Unregister ops failed: %i\n",ret);
                  return;
         }
         ret = genl_unregister_family(&doc_exmpl_genl_family);
         if(ret !=0) {
                  printk("Unregister family failed:%i\n",ret);
         }
}

module_init(genKernel_init);
module_exit(genKernel_exit);
MODULE_LICENSE("GPL");

genl_user.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <signal.h>

#include <linux/genetlink.h>

//宏定义:根据generic netlink msg的具体构造定位
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
#define GENLMSG_PAYLOAD(glh) (NLMSG_PAYLOAD(glh, 0) - GENL_HDRLEN)
#define NLA_DATA(na) ((void *)((char*)(na) + NLA_HDRLEN))

#define MESSAGE_TO_KERNEL "Hello World from user space!"

int nl_fd;
struct sockaddr_nl nl_address;
int nl_family_id;
int len;
struct nlattr *nl_na;
struct { //
    struct nlmsghdr n;
    struct genlmsghdr g;
    char buf[256];
} nl_request_msg, nl_response_msg;

int main(void) {
        nl_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
        if (nl_fd < 0) {
                perror("socket()");
                return -1;
        }

        memset(&nl_address, 0, sizeof(nl_address));
        nl_address.nl_family = AF_NETLINK;
        nl_address.nl_groups = 0;

        if (bind(nl_fd, (struct sockaddr *) &nl_address, sizeof(nl_address)) < 0) {
                perror("bind()");
                close(nl_fd);
                return -1;
        }

        nl_request_msg.n.nlmsg_type = GENL_ID_CTRL;//这是内核中genl_ctl的id
        nl_request_msg.n.nlmsg_flags = NLM_F_REQUEST;
        nl_request_msg.n.nlmsg_seq = 0;
        nl_request_msg.n.nlmsg_pid = getpid();
        nl_request_msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
        //Populate the payload's "family header" : which in our case is genlmsghdr
        nl_request_msg.g.cmd = CTRL_CMD_GETFAMILY;
        nl_request_msg.g.version = 0x1;
        //Populate the payload's "netlink attributes"
        nl_na = (struct nlattr *) GENLMSG_DATA(&nl_request_msg);//其实就相当于在nl_request_msg 的buf域中构造一个nla

        nl_na->nla_type = CTRL_ATTR_FAMILY_NAME;
        nl_na->nla_len = strlen("CONTROL_EXMPL") + 1 + NLA_HDRLEN;
        strcpy(NLA_DATA(nl_na), "CONTROL_EXMPL"); //Family name length can be upto 16 chars including \0

        nl_request_msg.n.nlmsg_len += NLMSG_ALIGN(nl_na->nla_len);

        memset(&nl_address, 0, sizeof(nl_address));
        nl_address.nl_family = AF_NETLINK;

        len= sendto(nl_fd, (char *) &nl_request_msg, nl_request_msg.n.nlmsg_len,
                       0, (struct sockaddr *) &nl_address, sizeof(nl_address));
        if (len != nl_request_msg.n.nlmsg_len) {
                perror("sendto()");
                close(nl_fd);
                return -1;
        }

        len= recv(nl_fd, &nl_response_msg, sizeof(nl_response_msg), 0);
        if (len < 0) {
                perror("recv()");
                return -1;
        }

        if (!NLMSG_OK((&nl_response_msg.n), len)) {
                fprintf(stderr, "family ID request : invalid message\n");
                return -1;
        }
        if (nl_response_msg.n.nlmsg_type == NLMSG_ERROR) { //error
                fprintf(stderr, "family ID request : receive error\n");
                return -1;
        }

        //解析出attribute中的family id
        nl_na = (struct nlattr *) GENLMSG_DATA(&nl_response_msg);
        nl_na = (struct nlattr *) ((char *) nl_na + NLA_ALIGN(nl_na->nla_len));
        if (nl_na->nla_type == CTRL_ATTR_FAMILY_ID) {
                nl_family_id = *(__u16 *) NLA_DATA(nl_na);//第一次通信就是为了得到需要的family ID
        }

        memset(&nl_request_msg, 0, sizeof(nl_request_msg));
        memset(&nl_response_msg, 0, sizeof(nl_response_msg));

        nl_request_msg.n.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
        nl_request_msg.n.nlmsg_type = nl_family_id;
        nl_request_msg.n.nlmsg_flags = NLM_F_REQUEST;
        nl_request_msg.n.nlmsg_seq = 60;
        nl_request_msg.n.nlmsg_pid = getpid();
        nl_request_msg.g.cmd = 1; //corresponds to DOC_EXMPL_C_ECHO;

        nl_na = (struct nlattr *) GENLMSG_DATA(&nl_request_msg);
        nl_na->nla_type = 1; // corresponds to DOC_EXMPL_A_MSG
        nl_na->nla_len = sizeof(MESSAGE_TO_KERNEL)+NLA_HDRLEN; //Message length
        memcpy(NLA_DATA(nl_na), MESSAGE_TO_KERNEL, sizeof(MESSAGE_TO_KERNEL));
        nl_request_msg.n.nlmsg_len += NLMSG_ALIGN(nl_na->nla_len);

        memset(&nl_address, 0, sizeof(nl_address));
        nl_address.nl_family = AF_NETLINK;

        len = sendto(nl_fd, (char *) &nl_request_msg, nl_request_msg.n.nlmsg_len,
                        0, (struct sockaddr *) &nl_address, sizeof(nl_address));
        if (len != nl_request_msg.n.nlmsg_len) {
                perror("sendto()");
                close(nl_fd);
                return -1;
        }
        printf("Sent to kernel: %s\n",MESSAGE_TO_KERNEL);

        len = recv(nl_fd, &nl_response_msg, sizeof(nl_response_msg), 0);
        if (len < 0) {
                perror("recv()");
                return -1;
        }

         //异常处理
        if (nl_response_msg.n.nlmsg_type == NLMSG_ERROR) { //Error
        printf("Error while receiving reply from kernel: NACK Received\n");
                close(nl_fd);
                return -1;
        }
        if (len < 0) {
                printf("Error while receiving reply from kernel\n");
                close(nl_fd);
                return -1;
        }
        if (!NLMSG_OK((&nl_response_msg.n), len)) {
                printf("Error while receiving reply from kernel: Invalid Message\n");
                close(nl_fd);
                return -1;
        }

        //解析收到的来自内核的reply
        len = GENLMSG_PAYLOAD(&nl_response_msg.n);
        nl_na = (struct nlattr *) GENLMSG_DATA(&nl_response_msg);
        printf("Kernel replied: %s\n",(char *)NLA_DATA(nl_na));

        close(nl_fd);
        return 0;
}

参考: 1. http://people.ee.ethz.ch/~arkeller/linux/multi/kernel_user_space_howto-3.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值