IE盒子

搜索
查看: 125|回复: 1

linux netlink 通信方式总结

[复制链接]

3

主题

8

帖子

14

积分

新手上路

Rank: 1

积分
14
发表于 2023-1-18 18:42:32 | 显示全部楼层 |阅读模式
一、介绍

netlink是一种用于用户空间进程与内核间通信的方法,也可以用于用户进程之间的通信(IPC)。
netlink和ioctl比较相似,都能从用户空间向内核空间通信,但netlink是一种异步通信机制,而ioctl是同步通信机制。且ioctl不能从内核向用户空间发送消息。
二、实现原理

1、用户态和内核交换过程

用户态内核
socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)  初始化内核netlink 服务端,注册回调函数
bindcfg.input 消息接收后处理回调函数
sendto/recvfromnetlink_unicast 广播回复消息
用户态和内核的交换如下图所示:


2、关键函数和数据结构

nlmsghdr netlink msg报文头
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_kernel_create 创建netlink内核通信服务端,定义如下,uinit就是魔术字表示使用哪种协议,下面使用的是自定义的魔术字 NETLINK_TEST,通常设置网络参数使用的是NETLINK_ROUTE 魔术字
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_cfg 结构体注册接受到消息后处理回调函数,定义如下:
/* 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;
int                (*bind)(struct net *net, int group);
void                (*unbind)(struct net *net, int group);
bool                (*compare)(struct net *net, struct sock *sk);
};netlink_unicast  netlink消息在内核中都是通过广播的方式发送,根据目的端口号找到发送的客户端
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 portid, int nonblock)三、实例分析

自定义的 NETLINK_TEST 通信协议
用户态测试程序netlink如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

#define NETLINK_TEST    30
#define MSG_LEN            125
#define MAX_PLOAD        125

typedef struct _user_msg_info
{
    struct nlmsghdr hdr;
    char  msg[MSG_LEN];
} user_msg_info;

int main(int argc, char **argv)
{
    int skfd;
    int ret;
    user_msg_info u_info;
    socklen_t len;
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl saddr, daddr; //saddr 表示源端口地址,daddr表示目的端口地址
    char *umsg = "hello netlink!!";

    /* 创建NETLINK socket */
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if(skfd == -1)
    {
        perror("create socket error\n");
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; //AF_NETLINK
    saddr.nl_pid = 100;  //端口号(port ID)
    saddr.nl_groups = 0;
    if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
    {
        perror("bind() error\n");
        close(skfd);
        return -1;
    }

    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0; // to kernel
    daddr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; //self port

    memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg)); // 拷贝发送的数据到报文头指向内存后面
    ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
    if(!ret)
    {
        perror("sendto error\n");
        close(skfd);
        exit(-1);
    }
    printf("send kernel:%s\n", umsg);

    memset(&u_info, 0, sizeof(u_info));
    len = sizeof(struct sockaddr_nl);
    ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
    if(!ret)
    {
        perror("recv form kernel error\n");
        close(skfd);
        exit(-1);
    }

    printf("from kernel:%s\n", u_info.msg);
    close(skfd);

    free((void *)nlh);
    return 0;
}内核模块程序netlink_test.c 代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>

#define NETLINK_TEST     30
#define MSG_LEN            125
#define USER_PORT        100

MODULE_LICENSE("GPL");
MODULE_AUTHOR("xx");
MODULE_DESCRIPTION("netlink example");

struct sock *nlsk = NULL;
extern struct net init_net;

int send_usrmsg(char *pbuf, uint16_t len)
{
    struct sk_buff *nl_skb;
    struct nlmsghdr *nlh;

    int ret;

    /* 创建sk_buff 空间 */
    nl_skb = nlmsg_new(len, GFP_ATOMIC);
    if(!nl_skb)
    {
        printk("netlink alloc failure\n");
        return -1;
    }

    /* 设置netlink消息头部 */
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0); // 填充nlmsg报文头,并最终将报文填充到sk_buff发送出去
    if(nlh == NULL)
    {
        printk("nlmsg_put failaure \n");
        nlmsg_free(nl_skb);
        return -1;
    }

    /* 拷贝数据发送 */
    memcpy(nlmsg_data(nlh), pbuf, len);
    ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT); // 指定端口号,端口号是唯一识别目的地址的标识,广播非阻塞式发送

    return ret;
}

static void netlink_rcv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh = NULL;
    char *umsg = NULL;
    char *kmsg = "hello users!!!";

    if(skb->len >= nlmsg_total_size(0))
    {
        nlh = nlmsg_hdr(skb); // 取出报文头
        umsg = NLMSG_DATA(nlh); // 取出报文内容
        if(umsg)
        {
            printk("kernel recv from user: %s\n", umsg);
            send_usrmsg(kmsg, strlen(kmsg));
        }
    }
}

struct netlink_kernel_cfg cfg = {
        .input  = netlink_rcv_msg, /* set recv callback */
};  

int test_netlink_init(void)
{
    /* create netlink socket */
    nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if(nlsk == NULL)
    {   
        printk("netlink_kernel_create error !\n");
        return -1;
    }   
    printk("test_netlink_init\n");
   
    return 0;
}

void test_netlink_exit(void)
{
    if (nlsk){
        netlink_kernel_release(nlsk); /* release ..*/
        nlsk = NULL;
    }   
    printk("test_netlink_exit!\n");
}

module_init(test_netlink_init);
module_exit(test_netlink_exit);Makefile构建脚本:
#
#Desgin of Netlink
#

MODULE_NAME :=netlink_test
obj-m :=$(MODULE_NAME).o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
        $(MAKE) -C $(KERNELDIR) M=$(PWD)

clean:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean测试代码运行结果:

回复

使用道具 举报

2

主题

10

帖子

17

积分

新手上路

Rank: 1

积分
17
发表于 2025-6-18 09:33:43 | 显示全部楼层
楼主呀,,,您太有才了。。。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表