Linux设备模型(七) - Netlink

一,什么是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博客

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PCB:多CAN口的信号转接板

背景 在测试多路CAN口时&#xff0c;需要频繁更换接口引脚&#xff0c;从而接入CAN收发器。为了提升测试效率&#xff0c;可以设计一个简易多路CAN收发器转接板。PCB板子一端是40脚母口&#xff0c;另一端是10路CAN螺钉式接线端子&#xff0c;自带电池减少接线。 分配空闲时间…

网络编程:基于TCP和UDP的服务器、客户端

1.基于TCP通信服务器 程序代码&#xff1a; 1 #include<myhead.h>2 #define SER_IP "192.168.126.121"//服务器IP3 #define SER_PORT 8888//服务器端口号4 int main(int argc, const char *argv[])5 {6 //1.创建用于监听的套接字7 int sfd-1;8 sf…

Sora:开启视频生成新时代的强大人工智能模型

目录 一、Sora模型的诞生与意义 二、Sora模型的技术特点与创新 三、Sora模型的应用前景与影响 四、面临的挑战与未来发展 1、技术挑战 2、道德和伦理问题 3、计算资源需求 4、未来发展方向 随着信息技术的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;已成为…

Unity(第九部)物体类

拿到物体的某些数据 using System.Collections; using System.Collections.Generic; using UnityEngine;public class game : MonoBehaviour {// Start is called before the first frame updatevoid Start(){//拿到当前脚本所挂载的游戏物体//GameObject go this.gameObject;…

Python算法100例-2.10 马克思手稿中的数学题

完整源代码项目地址&#xff0c;关注博主私信源代码后可获取 1.问题描述2.问题分析3.算法设计4.确定程序框架5.完整的程序6.运行结果 1&#xff0e;问题描述 马克思手稿中有一道趣味数学问题&#xff1a;有30个人&#xff0c;其中有男人、女人和小孩&#xff0c;他们在同一家…

C语言基础18 循环

们可能需要多次执行同一块代码。一般情况下&#xff0c;语句是按顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次执行一个语句或语句组&#xff0…

小红书的几种赚钱方式解读

小红书的七种变现方式&#xff1a; 1.通过小红书蒲公英平台接广告&#xff0c;粉丝数量大于1000的用户可以开通。单条笔记的广告费用从几百元到几十万不等。 2.开设小红书专栏&#xff0c;粉丝数量大于1万的用户可以开通。 3.进行私域变现&#xff0c;将小红书的咨询引导到微信…

Java 小项目开发日记 03(文章分类接口的开发)

Java 小项目开发日记 03&#xff08;文章分类接口的开发&#xff09; 项目目录 配置文件&#xff08;pom.xml&#xff09; <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocat…

内核打印应用程序出错信息,DEBUG_USER

前言 在 Linux 系统中&#xff0c;运行一个应用程序&#xff0c;突然提示段错误&#xff0c;并停止运行 # ./crash.out Segmentation fault如果这个时候操作系统能多提示点错误信息&#xff0c;那将会缩短我们 debug 的时间。 core dump 就是一个办法&#xff0c;可以查看我…

javaWeb学习04

AOP核心概念: 连接点: JoinPoint, 可以被AOP控制的方法 通知: Advice 指哪些重复的逻辑&#xff0c;也就是共性功能(最终体现为一个方法) 切入点: PointCut, 匹配连接点的条件&#xff0c;通知仅会在切入点方法执行时被应用 目标对象: Target, 通知所应用的对象 通知类…

nginx(三)实现反向代理客户端 IP透传

正常情况下&#xff0c;客户端去访问代理服务器&#xff0c;然后代理服务器再取访问真实服务器&#xff0c;在真实服务器上&#xff0c;只能显示代理服务器的ip地址&#xff0c;而不显示客户端的ip地址&#xff0c;如果想让客户端的ip地址也能在真实服务端看见&#xff0c;这一…

数仓开发环境链接

这里写目录标题 1开发工具链接大数据组件1.1 启动hiveserver21.2配置DataGrip连接1.3测试使用 2 环境问题排查思路 1开发工具链接大数据组件 1.1 启动hiveserver2 数仓开发工具datagrip 需要用到JDBC协议链接到Hive&#xff0c;需要启动hiveserver2。 cd /opt/module/hive h…

string 类 经典习题之数字字符相加

题目&#xff1a; 给定两个字符串形式的非负整数 num1 和num2 &#xff0c;计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库&#xff08;比如 BigInteger&#xff09;&#xff0c; 也不能直接将输入的字符串转换为整数形式。 题目来源&#xff1…

进程2月29日

题目&#xff1a;要求将当前路径下&#xff0c;所有文件的权限及最后一次的访问时间提取出来&#xff0c;写入到file.txt中。&#xff08;提示:opendir readir stat -->提取出来的数据写入到file.txt中&#xff09; 代码&#xff1a; #include <stdio.h> #include &…

C语言:数据在内存中的存储

C语言&#xff1a;数据在内存中的存储 整数存储原码、反码、补码转换规则数据与内存的关系 大小端字节序浮点数存储IEEE 754标准存储过程取用过程 数据的存储范围 整数存储 原码、反码、补码 整数的2进制表示方法有三种&#xff0c;即原码、反码和补码 三种表示方法均有符号位…

使用Python操作SQLite数据库

大家好&#xff0c;在数据涌现的今天&#xff0c;数据库已成为生活中不可或缺的工具。Python作为一种流行的编程语言&#xff0c;内置了多种用于操作数据库的库&#xff0c;其中之一就是SQLite。SQLite是一种轻量级的关系型数据库管理系统&#xff0c;它在Python中的应用非常广…

http模块学习

http模块 客户端&#xff1a;负责消费资源的电脑 服务器&#xff1a;负责对外提供网络资源的电脑&#xff0c;与普通电脑的区别就在于服务器上 安装了web服务器软件。 http模块是Node.js官方提供用来 创建web服务器的模块&#xff0c;通过http模块提供的http.createServer()方…

java之Bean对象

1. 什么是Bean&#xff1f; Bean被实例化的&#xff0c;是被Spring框架所管理的Java对象。 Spring容器会自动完成Bean的实例化。将所创建的的Bean自动注入到Ioc容器中以供调用。 spring框架中 IOC容器中管理的对象就是Bean对象 2. 第三方bean Bean 因为第三方bean&#xff0…

找不到mfc140.dll怎么办?教你五种mfc140.dll丢失的解决方法

当计算机系统中mfc140.dll文件丢失时&#xff0c;可能会引发一系列运行问题&#xff0c;影响到系统的正常功能及应用程序的稳定执行。具体来说&#xff0c;由于mfc140.dll是Microsoft Visual C Redistributable Package的重要组成部分&#xff0c;它的缺失会导致依赖于该动态链…

Unity 游戏设计模式:工厂模式

本文由 简悦 SimpRead 转码&#xff0c; 原文地址 mp.weixin.qq.com 工厂模式是一种创建型设计模式&#xff0c;它提供了一种封装对象实例化过程的方式&#xff0c;使得客户端代码与具体类的实现解耦。 在 C# 的游戏设计中&#xff0c;模式有以下作用&#xff1a; 对象的创建…