Linux设备模型(七) - Netlink

news2024/11/18 17:40:33

一,什么是netlink通信机制

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

        Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。

二,netlink通信机制的特点

  • 使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。

  • Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。

  • Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。

  • 使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖

  • Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。

三,用户态常用结构体和接口

1,struct sockaddr_nl 协议套接字

/*套接字结构体*/
struct sockaddr_nl {
__kernel_sa_family_t    nl_family;  /* AF_NETLINK (跟AF_INET对应)*/
unsigned short  nl_pad;     /* zero */
__u32       nl_pid;     /* port ID  (通信端口号)*/
__u32       nl_groups;  /* multicast groups mask */
};

nl_family 制定了协议族,netlink 有自己独立的值:AF_NETLINK,nl_pid 一般取为进程 pid。nl_groups 用以多播,当不需要多播时,该字段为 0。

nl_pid:该属性为发送或接收消息的进程ID,前面我们也说过,Netlink不仅可以实现用户-内核空间的通信还可使现实用户空间两个进程之间,或内核空间两个进程之间的通信。该属性为0时一般适用于如下两种情况:

第一,我们要发送的目的地是内核,即从用户空间发往内核空间时,我们构造的Netlink地址结构体中nl_pid通常情况下都置为0。这里有一点需要跟大家交代一下,在Netlink规范里,PID全称是Port-ID(32bits),其主要作用是用于唯一的标识一个基于netlink的socket通道。通常情况下nl_pid都设置为当前进程的进程号。

第二,从内核发出的多播报文到用户空间时,如果用户空间的进程处在该多播组中,那么其地址结构体中nl_pid也设置为0。

2,netlink的消息格式

Netlink消息由两部分组成:消息头和有效数据载荷,且整个Netlink消息是4字节对齐,一般按主机字节序进行传递。消息头为固定的16字节,消息体长度可变:

//netlink收发是以消息为单位的,每次收发可以包含一个或多个消息(msg)

------------------------------------------------------------------------

|                     单次sendto或者recvfrom 数据部分                      |              

------------------------------------------------------------------------

|                   msg0       |                    msg1      |  msgn    |

------------------------------------------------------------------------

| nlmsghdr | data(携带的数据)   | nlmsghdr | data(携带的数据)   |  ....     |

------------------------------------------------------------------------

3,netlink的消息头

消息头定义在<include/linux/netlink.h>文件里,由结构体nlmsghdr表示:

struct nlmsghdr

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 PID */
};

消息头中各成员属性的解释及说明:
nlmsg_len:整个消息的长度,按字节计算。包括了Netlink消息头本身。

nlmsg_type:消息的类型,即是数据还是控制消息。目前(内核版本2.6.21)Netlink仅支持四种类型的控制消息,如下:
NLMSG_NOOP-空消息,什么也不做;
NLMSG_ERROR-指明该消息中包含一个错误;
NLMSG_DONE-如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。
NLMSG_OVERRUN-暂时没用到。

nlmsg_flags:附加在消息上的额外说明信息,如上面提到的NLM_F_MULTI。摘录如下:
NLM_F_REQUEST
如果消息中有该标记位,说明这是一个请求消息。所有从用户空间到内核空间的消息都要设置该位,否则内核将向用户返回一个EINVAL无效参数的错误
NLM_F_MULTI
消息从用户->内核是同步的立刻完成,而从内核->用户则需要排队。如果内核之前收到过来自用户的消息中有NLM_F_DUMP位为1的消息,那么内核就会向用户空间发送一个由多个Netlink消息组成的链表。除了最后个消息外,其余每条消息中都设置了该位有效。
NLM_F_ACK
该消息是内核对来自用户空间的NLM_F_REQUEST消息的响应
NLM_F_ECHO
如果从用户空间发给内核的消息中该标记为1,则说明用户的应用进程要求内核将用户发给它的每条消息通过单播的形式再发送给用户进程。和我们通常说的“回显”功能类似。

nlmsg_seq:消息序列号。因为Netlink是面向数据报的,所以存在丢失数据的风险,但是Netlink提供了如何确保消息不丢失的机制,让程序开发人员根据其实际需求而实现。消息序列号一般和NLM_F_ACK类型的消息联合使用,如果用户的应用程序需要保证其发送的每条消息都成功被内核收到的话,那么它发送消息时需要用户程序自己设置序号,内核收到该消息后对提取其中的序列号,然后在发送给用户程序回应消息里设置同样的序列号。有点类似于TCP的响应和确认机制。

注意:当内核主动向用户空间发送广播消息时,消息中的该字段总是为0。

nlmsg_pid:当用户空间的进程和内核空间的某个子系统之间通过Netlink建立了数据交换的通道后,Netlink会为每个这样的通道分配一个唯一的数字标识。其主要作用就是将来自用户空间的请求消息和响应消息进行关联。说得直白一点,假如用户空间存在多个用户进程,内核空间同样存在多个进程,Netlink必须提供一种机制用于确保每一对“用户-内核”空间通信的进程之间的数据交互不会发生紊乱。

4,用户态与内核态对数据处理的宏函数

/* 宏 NLMSG_ALIGN(len) 用于得到不小于len且字节对齐的最小数值 */
#define NLMSG_ALIGNTO   4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

/* Netlink 头部长度 */
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))

/* 计算消息数据 len 的真实消息长度(消息体 + 消息头)*/
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)

/* 宏 NLMSG_SPACE(len) 返回不小于 NLMSG_LENGTH(len) 且字节对齐的最小数值 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

/* 宏 NLMSG_DATA(nlh) 用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

/* 宏 NLMSG_NEXT(nlh,len) 用于得到下一个消息的首地址, 同时 len 变为剩余消息的长度 */
#define NLMSG_NEXT(nlh,len)  ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \                  
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

/* 判断消息是否 >len */
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
    (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
    (nlh)->nlmsg_len <= (len))

/* NLMSG_PAYLOAD(nlh,len) 用于返回 payload 的长度*/
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

5,创建套接字

应用层使用接口是标准的 socket API,与UDP通信类似。

int socket(int domain, int type, int protocol)
    domain :使用netlink方式通信时配置为 AF_NETLINK
    type :使用netlink方式通信时配置为 SOCK_RAW
    protocol:自定义的通信协议

netlink 协议类型

#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    /* Unused number, formerly ip_queue        */
#define NETLINK_SOCK_DIAG    4    /* 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 NETLINK_RDMA        20
#define NETLINK_CRYPTO        21    /* Crypto layer */
#define NETLINK_SMC        22    /* SMC monitoring */

#define NETLINK_INET_DIAG    NETLINK_SOCK_DIAG

6,绑定套接字

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    addr :传参时要将转入的struct sockaddr_nl结构体指针变量强转为struct sockaddr *

7,收发送数据

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen)  

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen)

四,内核常用结构体和接口

1,struct sock结构体

套接字结构体

2,struct netlink_kernel_cfg 结构体

/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
unsigned int    groups;
unsigned int    flags;
void        (*input)(struct sk_buff skb);  / *input 回调函数 */一
struct mutex    *cb_mutex;
void        (*bind)(int group);
bool        (*compare)(struct net *net, struct sock *sk);
};

3, struct sk_buf 结构体

套接字缓存,作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递

struct  sk_buff
{
     struct  sk_buff *next;
     struct  sk_buff *prev;
     struct  sock *sock ; //struct sock是socket在网络层的表示,其中存放了网络层的信息
          
     unsigned  int  len; //表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。
     unsigned  int  data_len;  //和len不同,data_len只计算分片中数据的长度
     __u16   mac_len ;  //数路链路层的头长度
     __u16   hdr_len ;  //writable header length of cloned skb
     unsigned  int  truesize ;  //socket buffer(套接字缓存区的大小)
     atomic_t users ;  //对当前的struct sk_buff结构体的引用次数;
     __u32   priority ;  //这个struct sk_buff结构体的优先级
     
     sk_buff_data_t transport_header ;  //传输层头部的偏移量
     sk_buff_data_t network_header ;    //网络层头部的偏移量
     sk_buff_data_t mac_header ;        //数据链路层头部的偏移量
     
     char  *data ;  //socket buffer中数据的起始位置;
     sk_buff_data_t tail ;  //socket buffer中数据的结束位置;
     char  *head ;  //socket buffer缓存区的起始位置;
     sk_buffer_data_t end ;  //socket buffer缓存区的终止位置;
     
     struct  net_device *dev;  //将要发送struct sk_buff结构体的网络设备或struct sk_buff的接收网络设备
     int  iif;   //网络设备的接口索引号;   
     struct  timeval tstamp ;  //用于存放接受的数据包的到达时间;
     
     __u8  local_df : 1 ,   //allow local fragmentaion;
           cloned   : 1 ,  // head may be cloned
           ;
     
     __u8  pkt_type : 3 ,  //数据包的类型;
           fclone   : 2,   // struct sk_buff clone status   
}

struct sk_buff中head, end, data, tail字段的含义

4,netlink_kernel_create

netlink_kernel_create内核函数用于创建内核socket用来与用户态通信

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
   net: net指向所在的网络命名空间, 一般默认传入的是&init_net(不需要定义);  定义在net_namespace.c(extern struct net init_net);
   unit:netlink协议类型,对应用户态创建套接字时的protocol参数,两者需保持一致
   cfg: cfg存放的是netlink内核配置参数

5,单播netlink_unicast()

int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
   ssk: netlink socket
   skb: skb buff 指针
   portid: 通信的端口号,对应用态的端口号
   nonblock:表示该函数是否为非阻塞,如果为1(MSG_DONTWAIT),该函数将在没有接收缓存可利用时立即返回,而如果为0(MSG_WAITALL),该函数在没有接收缓存可利用 定时睡眠

6,多播netlink_broadcast()

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
                 __u32 group, gfp_t allocation);
   ssk: 同上(对应netlink_kernel_create 返回值)、
   skb: 内核skb buff
   portid: 通信的端口号,对应用态的端口号
   group: 是所有目标多播组对应掩码的"OR"操作的合值。
   allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。
                这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone

7, nlmsg_new()

分配一个新的netlink消息

struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
    payload : 分配的大小
    flags:
        进程上下文,可以睡眠:GFP_KERNEL
        进程上下文,不可以睡眠:GFP_ATOMIC
        中断处理程序:GFP_ATOMIC
        软中断:GFP_ATOMIC
        Tasklet:GFP_ATOMIC
        用于DMA的内存,可以睡眠:GFP_DMA | GFP_KERNEL
        用于DMA的内存,不可以睡眠:GFP_DMA |GFP_ATOMIC

8,nlmsg_put()

向skb缓冲区中获取消息头空间并且初始化netlink消息头,入参中的第5个参数为netlink消息头的总空间

struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
                          int type, int payload, int flags)
      portid:与 netlink消息头 中的 nlmsg_pid 对应
      seq:与 netlink消息头 中的 nlmsg_seq 对应
      type:与 netlink消息头 中的 nlmsg_type 对应
      payload:与 netlink消息头 中的 nlmsg_len 对应
      flags:与 netlink消息头 中的 nlmsg_flags 对应

9,skb API

/**
* alloc_skb - allocate a network buffer
* @size: size to allocate
* @priority: allocation mask
*
* This function is a convenient wrapper around __alloc_skb().
*/
static inline struct sk_buff *alloc_skb(unsigned int size,
                    gfp_t priority)
{
    return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
}

static inline void *skb_put_data(struct sk_buff *skb, const void *data,
                 unsigned int len)
{
    void *tmp = skb_put(skb, len);
    memcpy(tmp, data, len);
    return tmp;
}

五,netlink用户态和内核态交互过程

1,socket 通信主要有 2 个操作对象:server 端和 client 端

2,netlink_client - netlink_server 测试程序

user space接收kernel的广播消息,接收到消息后然后再发送消息到kernel。

test netlink client:

#include <sys/types.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>
#include <errno.h>
#include <stdbool.h>
#include <sys/epoll.h>

#define MAX_EPOLL_EVENTS 64
#define NETLINK_T_MSG_LEN 2048
#define NETLINK_USER 31
#define MAX_PAYLOAD 1024 /* maximum payload size*/

int sk_fd = -1;
int mPollHandler = -1;
struct nlmsghdr *nlh = NULL;

int netlink_t_open_socket(int buf_sz, bool passcred) {
    struct sockaddr_nl addr;
    int on = passcred;
    int buf_sz_readback = 0;
    socklen_t optlen = sizeof(buf_sz_readback);
    int s;

    memset(&addr, 0x0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = 0;
    addr.nl_groups = 0xffffffff;

    //s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
    s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_USER);
    if (s < 0) return -1;

    if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_sz, sizeof(buf_sz)) < 0 ||
          getsockopt(s, SOL_SOCKET, SO_RCVBUF, &buf_sz_readback, &optlen) < 0) {
        close(s);
        return -1;
    }
    /* Only if SO_RCVBUF was not effective, try SO_RCVBUFFORCE. Generally, we
     * want to avoid SO_RCVBUFFORCE, because it generates SELinux denials in
     * case we don't have CAP_NET_ADMIN. This is the case, for example, for
     * healthd. */
    if (buf_sz_readback < 2 * buf_sz) {
        if (setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buf_sz, sizeof(buf_sz)) < 0) {
            close(s);
            return -1;
        }
    }

    setsockopt(s, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));

    if (bind(s, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        close(s);
        return -1;
    }

    return s;
}

ssize_t netlink_t_kernel_recv(int socket, void* buffer, size_t length, bool require_group, uid_t* uid) {
    struct iovec iov = {buffer, length};
    struct sockaddr_nl addr;
    char control[CMSG_SPACE(sizeof(struct ucred))];
    struct msghdr hdr = {
        &addr, sizeof(addr), &iov, 1, control, sizeof(control), 0,
    };
    struct ucred* cred;

    *uid = -1;
    ssize_t n = TEMP_FAILURE_RETRY(recvmsg(socket, &hdr, 0));
    if (n <= 0) {
        return n;
    }

    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
    if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) {
        /* ignoring netlink message with no sender credentials */
        goto out;
    }

    cred = (struct ucred*)CMSG_DATA(cmsg);
    *uid = cred->uid;

    if (addr.nl_pid != 0) {
        /* ignore non-kernel */
        goto out;
    }
    if (require_group && addr.nl_groups == 0) {
        /* ignore unicast messages when requested */
        goto out;
    }

    return n;

out:
    /* clear residual potentially malicious data */
    //bzero(buffer, length);
    memset(buffer, 0 , length);
    errno = EIO;
    return -1;
}

static void signal_handler(int signum)
{
    if (sk_fd > 0) {
        close(sk_fd);
    }
    if (nlh != NULL) {
        free(nlh);
        nlh = NULL;
    }
    epoll_ctl(mPollHandler, EPOLL_CTL_DEL, sk_fd, NULL);
    exit(EXIT_SUCCESS);
}

int main(int args, char *argv[])
{
    struct epoll_event ev;
    int i;
    char msg[NETLINK_T_MSG_LEN + 2];
    uid_t uid = -1;
    int n;
    //char *cp;
    int ret;
    struct msghdr msg_info;  //msghdr includes: struct iovec *   msg_iov;
    struct sockaddr_nl dest_addr;
    struct iovec iov;

    int nevents;
    struct epoll_event events[MAX_EPOLL_EVENTS];

    signal(SIGINT, &signal_handler);
    signal(SIGTERM, &signal_handler);

    mPollHandler = epoll_create(MAX_EPOLL_EVENTS);
    if (mPollHandler == -1) {
        printf("Failed to initialize Moto event looper\n");
        return false;
    }

    sk_fd = netlink_t_open_socket(64*1024, true);
    if (sk_fd < 0) {
        printf("fail to open netlink socket fd\n");
        return false;
    }

    //dest addr
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;       /* For Linux Kernel */
    dest_addr.nl_groups = 0;    /* unicast */

    //init send msg
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();  //self pid
    nlh->nlmsg_flags = 0;
    strcpy(NLMSG_DATA(nlh), "Hello this is a msg from userspace");   //put "Hello" into nlh

    iov.iov_base = (void *)nlh;         //iov -> nlh
    iov.iov_len = nlh->nlmsg_len;
    msg_info.msg_name = (void *)&dest_addr;  //msg_name is Socket name: dest
    msg_info.msg_namelen = sizeof(dest_addr);
    msg_info.msg_iov = &iov;                 //msg -> iov
    msg_info.msg_iovlen = 1;


    ev.events = EPOLLIN | EPOLLPRI;
    ev.data.fd = sk_fd;
    if (epoll_ctl(mPollHandler, EPOLL_CTL_ADD, sk_fd, &ev) == -1) {
        close(sk_fd);

        printf("Failed to add epoll data\n");
        return false;
    }

    while (1) {
        nevents = epoll_wait(mPollHandler, events, MAX_EPOLL_EVENTS, -1);
        if (nevents == -1) {
            if (errno != EINTR)
                printf("epoll wait failed %d\n", errno);
            continue;
        }
        //loop read
        for (i = 0; i < nevents; ++i) {
            int fd = events[i].data.fd;
            n = netlink_t_kernel_recv(fd, msg, NETLINK_T_MSG_LEN, true, &uid);
            if (n < 0) {
                printf("epoll callback read error %d\n", errno);
            }

            msg[n] = '\0';
            msg[n + 1] = '\0';
            printf("epoll callback read %d bytes, %s\n", n, msg);

            //send msg
            ret = sendmsg(fd, &msg_info, 0);
            printf("send ret: %d\n", ret);

            //cp = msg;
            //while (*cp) {
            //    printf("get netlink msg %s\n", cp);
            //    while(*cp++)
            //        ;
            //}
        }
    }

    return 0;
}

test netlink server:

#include <linux/module.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <linux/platform_device.h>
#include <linux/of.h>

#define NETLINK_USER 31     //the user defined channel, the key factor
struct sock *nl_sk = NULL;
struct sk_buff *skb = NULL;

static ssize_t test_netlink_send_msg_store(struct device *dev,
                     struct device_attribute *attr,
                     const char *buf,
                     size_t count)
{
    const char *action_string = "netlink msg from kernel ";
    const char *msg_info = "author william";
    size_t len;
    //char *scratch;
    struct netlink_skb_parms *parms;

    if (!buf || count <= 0)
        return -EINVAL;

    //allocate sk buff and fill
    len = strlen(action_string) + strlen(msg_info) + 2;
    skb = alloc_skb(len, GFP_KERNEL);
    if (!skb)
        return count;

    //scratch = skb_put(skb, len);
    //sprintf(scratch, "%s", action_string);
    skb_put_data(skb, action_string, strlen(action_string));
    skb_put_data(skb, msg_info, strlen(msg_info));

    parms = &NETLINK_CB(skb);
    parms->creds.uid = GLOBAL_ROOT_UID;
    parms->creds.gid = GLOBAL_ROOT_GID;
    parms->dst_group = 1;
    parms->portid = 0;

    netlink_broadcast(nl_sk, skb_get(skb), 0 , 1, GFP_KERNEL);
    consume_skb(skb);

    return count;
}

/*
* ATTRIBUTES:
*
*/
static DEVICE_ATTR(send_msg, S_IWUSR | S_IRUGO,
           NULL,
           test_netlink_send_msg_store);

static struct attribute *test_netlink_attrs[] = {
    &dev_attr_send_msg.attr,
    NULL,
};
ATTRIBUTE_GROUPS(test_netlink);

static const struct of_device_id test_netlink_of_match[] = {
    { .compatible = "test-netlink", },
    { },
};
MODULE_DEVICE_TABLE(of, test_netlink_of_match);

static void test_netlink_recv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh;


    //for receiving...
    nlh = (struct nlmsghdr*)skb->data;
    printk("Netlink received msg payload: %s\n",(char*)nlmsg_data(nlh));
}

static int test_netlink_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct netlink_kernel_cfg cfg = {
        .input = test_netlink_recv_msg,
    };

    dev_info(dev, "%s\n", __func__);

    //allocate netlink socket
    nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
    if(!nl_sk)
    {
        dev_err(dev, "%s, Error creating socket.\n", __func__);
        return -1;
    }

    //if (!netlink_has_listeners(nl_sk, 1))
    //  return -1;

    return 0;
}

static struct platform_driver netlink_device_driver = {
    .probe      = test_netlink_probe,
    .driver     = {
        .name   = "test-netlink",
        .of_match_table = test_netlink_of_match,
        .dev_groups = test_netlink_groups,
    }
};

static int __init test_netlink_init(void)
{
    return platform_driver_register(&netlink_device_driver);
}

static void __exit test_netlink_exit(void)
{
    netlink_kernel_release(nl_sk);
    platform_driver_unregister(&netlink_device_driver);
}

module_init(test_netlink_init);
module_exit(test_netlink_exit);
MODULE_LICENSE("GPL");

测试:

netlink client

netlink server

/sys/devices/platform/soc/soc:m_netlink_server # echo 1 > send_msg

参考链接:

内核通信之 Netlink 源码分析和实例分析-腾讯云开发者社区-腾讯云

Linux驱动-Netlink通信_linux netlink-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1479002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

助力智能化农田作物除草,基于YOLOv7【tiny/l/x】不同系列参数模型开发构建农田作物场景下玉米苗、杂草检测识别分析系统

在我们前面的系列博文中&#xff0c;关于田间作物场景下的作物、杂草检测已经有过相关的开发实践了&#xff0c;结合智能化的设备可以实现只能除草等操作&#xff0c;玉米作物场景下的杂草检测我们则少有涉及&#xff0c;这里本文的主要目的就是想要基于YOLOv7系列的模型来开发…

论文阅读:基于超像素的图卷积语义分割(图结构数据)

#Superpixel-based Graph Convolutional Network for Semantic Segmentation github链接 引言 GNN模型根据节点特征周围的边来训练节点特征&#xff0c;并获得最终的节点嵌入。通过利用具有不同滤波核的二维卷积对来自附近节点的信息进行整合&#xff0c;给定超像素方法生成的…

【RK3568】

RK3568 一、配置IP地址二、开发板连接网络&#xff0c;电脑连接无线&#xff0c;都正常上网三、GCC四、CMake1. CMakeLists.txt2. main.cpp3.build 五、传输文件 一、配置IP地址 vi /etc/init.d/S99z_ip //权限chmod ax S99z_ip /***********************************/ case &q…

瑞吉苍穹外卖如何拓展?已经经过不同公司多轮面试。项目中会问到哪些问题?以及问题如何解决?

别催了&#xff0c;别催了&#xff0c;先收藏吧。 作者大大正在加班加点完成。 文章会尽快发布&#xff0c;关注收藏&#xff0c;尽请期待。 想要加入并查阅作者的知识库可以联系作者 不要白嫖&#xff0c;通过后&#xff0c;附上关注和收藏截图。 已有众多小伙伴加入 目前…

Python中的os库

一.OS库简介 OS是Operating System的简写&#xff0c;即操作系统。 OS库是一个操作系统接口模块&#xff0c;提供一些方便使用操作系统相关功能的函数。 二.OS库常用函数 2.1文件和目录 2.1.1&#xff1a;os.getcwd() 作用&#xff1a;返回当前工作目录&#xff0c;结果是…

WebCPM:首个开源的交互式网页搜索中文问答模型

论文题目&#xff1a;WEBCPM: Interactive Web Search for Chinese Long-form Question Answering   论文日期&#xff1a;2023/05/23(ACL 2023)   论文地址&#xff1a;https://arxiv.org/abs/2305.06849   GitHub地址&#xff1a;https://arxiv.org/abs/2305.06849 文章…

【前端素材】推荐优质后台管理系统cassie平台模板(附源码)

一、需求分析 1、系统定义 后台管理系统是一种用于管理网站、应用程序或系统的管理界面&#xff0c;通常由管理员和工作人员使用。它提供了访问和控制网站或应用程序后台功能的工具和界面&#xff0c;使其能够管理用户、内容、数据和其他各种功能。 2、功能需求 后台管理系…

nginx使用详解--动静分离

什么是动静分离&#xff1f; 为了提高网站的响应速度&#xff0c;减轻程序服务器&#xff08;Tomcat&#xff0c;Jboss等&#xff09;的负载&#xff0c;对于静态资源&#xff0c;如图片、js、css等文件&#xff0c;可以在反向代理服务器中进行缓存&#xff0c;这样浏览器在请…

亿道信息发布两款升级款全加固笔记本电脑

2022年5月19日&#xff0c;加固手持终端。加固平板电脑、加固笔记本电脑专业设计商和制造商&#xff0c;以及加固型移动计算机软硬件整体定制解决方案提供商亿道信息&#xff0c;宣布对其两款广受欢迎的加固笔记本电脑产品EM-X14U和EM-X15U进行重大升级。新发布的两款升级款全加…

C语言:自定义类型 - 结构体 联合体 枚举

C语言&#xff1a;自定义类型 - 结构体 & 联合体 & 枚举 结构体结构体声明结构体创建匿名结构体初始化 结构体的重命名结构体访问内存对齐位段 联合体枚举 在C语言中&#xff0c;自定义类型是指程序员可以通过一系列的定义和说明来创建的新的数据类型。这些自定义类型可…

NPN型三极管与PNP型三极管基本原理

NPN型三极管与PNP型三极管基本原理 文章目录 NPN型三极管与PNP型三极管基本原理一、三极管二、结构三、工作原理四、基本应用五、总计 一、三极管 三极管是电子电路中最基本、最常见、重要的器件&#xff0c;其主要功能是对电流的放大和开关作用&#xff0c;从半导体结构上可以…

PostgreSQL从入门到精通教程 - 第45讲:poc-tpcc测试

PostgreSQL从小白到专家&#xff0c;是从入门逐渐能力提升的一个系列教程&#xff0c;内容包括对PG基础的认知、包括安装使用、包括角色权限、包括维护管理、、等内容&#xff0c;希望对热爱PG、学习PG的同学们有帮助&#xff0c;欢迎持续关注CUUG PG技术大讲堂。 第45讲&#…

共同应对共享挑战:2023 Open Source Congress 报告(中文版)

开源社受邀参加了 2023 年 7 月底在瑞士日内瓦举办的 2023 Open Source Congress&#xff0c;并发布了一篇会议速览&#xff1b;之后 LFAPAC 发布了会议总结报告&#xff08;英文版&#xff09;&#xff0c;开源社国际接轨工作组的翻译志愿者们群策群力地翻译了该总结报告。 【…

10-Linux部署ElasticSearch

Linux部署ElasticSearch 简介 全文搜索属于最常见的需求&#xff0c;开源的 Elasticsearch &#xff08;以下简称 es&#xff09;是目前全文搜索引擎的首选。 它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。 Elasticsearch简称es&…

本地写的Bash脚本,Linux端运行报错:/bin/bash^M: bad interpreter: No such file or directory

背景 在本地写了个Bash Shell脚本&#xff0c;但上传到Linux端后加完权限执行时报错&#xff1a; &#xff08;脚本名&#xff1a;script.sh&#xff09; -bash: ./script.sh: /bin/bash^M: bad interpreter: No such file or directory 分析 这个错误通常是由于脚本文件的行…

【kubernetes】关于云原生之k8s集群中pod的容器资源限制和三种探针

目录 一、关于pod容器的资源限制 1.1资源限制的单位 CPU 资源单位 内存 资源单位 二、关于QOS服务质量&#xff08;pod的调度和驱逐有限制&#xff09; 2.1QoS服务质量分类 guaranteed验证 burstable验证 besteffort验证 2.2驱逐顺序 三、关于pod容器的三种探针 3.…

【数据结构与算法】回溯法解题20240301

这里写目录标题 一、78. 子集1、nums [1,2,3]为例把求子集抽象为树型结构2、回溯三部曲 二、90. 子集 II1、本题搜索的过程抽象成树形结构如下&#xff1a; 三、39. 组合总和1、回溯三部曲2、剪枝优化 四、LCR 082. 组合总和 II1、思路2、树形结构如图所示&#xff1a;3、回溯…

命令行启动mongodb服务器的问题及解决方案 -- Unrecognized option: storage.journal

目录 mongodb命令行启动问题 -- Unrecognized option: storage.journal问题日志&#xff1a;问题截图&#xff1a;问题来源&#xff1a;错误原因&#xff1a;解决方式&#xff1a; mongodb命令行启动问题 – Unrecognized option: storage.journal 同样是格式出问题的问题分析和…

视频在线压缩

video2edit 一款免费的在线视频编辑软件&#xff0c;可以进行视频合并、视频剪辑、视频压缩以及转换视频格式等。 链接地址&#xff1a;在线视频编辑器和转换器 - 编辑&#xff0c;转换和压缩视频文件 打开视频压缩页面&#xff0c;上传想要压缩视频&#xff0c;支持MP4&…

SpringCloud搭建微服务之Consul服务注册与发现

1. Consul介绍 Consul是由HashiCorp公司使用Go语言开发的一款开源工具&#xff0c;主要用于实现分布式系统的服务发现和服务配置&#xff0c;其内置了服务注册与发现框架、分布式一致性协议实现、健康检查、Key-Value存储、多数据中心方案。Consul具有高可移植性&#xff0c;支…