netlink机制详解

Netlink机制

1.1 概述

Netlink是Linux内核和用户空间之间的通信机制,用于传递网络配置和管理信息。它是一个双向的、全双工的套接字接口,支持多种协议,最常用于网络子系统。
在早期的 Linux 版本中,网络配置主要通过 ioctl 系统调用完成。然而,随着网络协议栈和配置需求的复杂化,ioctl 方式显得笨重且不够灵活。Netlink 机制应运而生,提供了一种高效、灵活的内核和用户空间通信方式,特别适用于网络配置和管理。

1.2 数据结构体

1.2.1 struct sockaddr_nl

struct sockaddr_nl用于表示 Netlink 地址。这是一个用于 Netlink 套接字绑定和发送消息的地址结构体。
struct sockaddr_nl {
    sa_family_t nl_family;  // 地址族,必须是 AF_NETLINK
    unsigned short nl_pad;  // 填充字段,必须设置为 0
    pid_t nl_pid;          // 发送方的进程 ID;目标是内核则为0
    __u32 nl_groups;      // 多播组掩码
};

a) nl_pid
单播下bind时,常设置为当前进程的PID;
发送消息时,目标是内核则设置为 0,目标是用户态且为单播则设置为目标进程的PID。
b) nl_groups
多播下bind时,用于表示套接字加入的多播组(多播组掩码);
发送消息时,目标是内核则设置为 0,目标是用户态且为多播则设置为多播族ID。

1.2.2 struct nlmsghdr

struct nlmsghdr是 Netlink 消息头,用于描述 Netlink 消息的元数据。

struct nlmsghdr {
    __u32 nlmsg_len;   // 整个消息的长度,包括消息头和消息数据
    __u16 nlmsg_type;  // 消息类型,用户可以自定义
    __u16 nlmsg_flags;  // 额外标志,例如 NLM_F_REQUEST 表示请求消息
    __u32 nlmsg_seq;   // 序列号,用于匹配请求和响应
    __u32 nlmsg_pid;   // 发送方的进程 ID
};

常用操作函数如下:
a) nlmsg_put
(1)功能:创建并初始化一个 Netlink 消息头。
(2)原型:

struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int payload, int flags);

(3)参数:
- skb:指向要添加消息的 socket 缓冲区。
- portid:发送进程的端口 ID(通常是进程的 PID)。
- seq:消息的序列号。
- type:消息类型。
- payload:消息负载的大小。
- flags:消息标志。
(4)返回值:指向消息头的指针,或者在失败时返回 NULL。
(5)示例:

struct nlmsghdr *nlh;
nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, payload_size, 0);

b) nlmsg_data
(1)功能:获取 Netlink 消息头之后的数据部分的指针。
(2)原型:

void *nlmsg_data(const struct nlmsghdr *nlh);

(3)参数:
- nlh:指向struct nlmsghdr结构体。
(4)返回值:指向消息数据的指针。
(5)示例:

char *data;
data = (char *)nlmsg_data(nlh);

c) nlmsg_len
(1)功能:获取 Netlink 消息的有效负载长度。
(2)原型:

int nlmsg_len(const struct nlmsghdr *nlh);

(3)参数:
- nlh:指向struct nlmsghdr结构体。
(4)返回值:消息负载的长度。
(5)示例:

int len;
len = nlmsg_len(nlh);

d) nlmsg_next
(1)功能:获取下一个 Netlink 消息头。
(2)原型:

struct nlmsghdr *nlmsg_next(const struct nlmsghdr *nlh, int *remaining);

(3)参数:
- nlh:指向当前的 struct nlmsghdr 结构体。
- remaining:指向剩余消息长度的指针。
(4)返回值:指向下一个消息头的指针。
(5)示例:

struct nlmsghdr *next_nlh;
next_nlh = nlmsg_next(nlh, &remaining);

e) nlmsg_ok
(1)功能:检查 Netlink 消息是否有效。
(2)原型:

int nlmsg_ok(const struct nlmsghdr *nlh, int remaining);

(3)参数:
- nlh:指向struct nlmsghdr结构体。
- remaining:剩余消息长度。
(4)返回值:如果消息有效,返回非零值,否则返回零。
(5)示例:

if (!nlmsg_ok(nlh, remaining)) {
      printk(KERN_ERR "Invalid nlmsg\n");
      return;
   }

f) nlmsg_hdr
(1)功能:从数据缓冲区中获取 Netlink 消息头。
(3)原型:

struct nlmsghdr *nlmsg_hdr(const struct nlmsgerr *nlmsg);

(3)参数:
- nlmsg:指向数据缓冲区。
(4)返回值:指向消息头的指针。
(5)示例:
struct nlmsghdr *nlh;
nlh = nlmsg_hdr(skb->data);
g) 完整示例
展示如何在内核态接收和解析用户态发送的 Netlink 消息:

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

#define NETLINK_USER 31

struct sock *nl_sk = NULL;

void my_netlink_recv_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh;
    int pid;
    char *msg;
    int msg_len;

    nlh = (struct nlmsghdr *)skb->data;
    if (!nlmsg_ok(nlh, skb->len)) {
        printk(KERN_ERR "Received an invalid nlmsg.\n");
        return;
    }

    pid = nlh->nlmsg_pid;     // 获取发送进程的 PID
    msg_len = nlmsg_len(nlh);
    msg = (char *)nlmsg_data(nlh);

    // 解析并处理消息
    // 例如,可以根据消息内容执行不同的操作

    // 发送响应消息回用户态
    send_msg_to_user(pid, "Hello from kernel", strlen("Hello from kernel"));
}

void send_msg_to_user(int pid, char *msg, int msg_len) {
    struct sk_buff *skb_out;
    struct nlmsghdr *nlh;
    int res;

    skb_out = nlmsg_new(msg_len, 0);

    nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_len, 0);

    memcpy(nlmsg_data(nlh), msg, msg_len);
    NETLINK_CB(skb_out).dst_group = 0; // Not in multicast group

    res = nlmsg_unicast(nl_sk, skb_out, pid);
  }

h) 总结

  • struct nlmsghdr是 Netlink 消息的头部结构体,包含消息的元数据。
  • 常用操作函数包括nlmsg_put、nlmsg_data、nlmsg_len、nlmsg_next、nlmsg_ok和 nlmsg_hdr。
  • 使用这些函数可以创建、解析和管理 Netlink 消息。
  • 内核态通过回调函数接收用户态发送的消息,并使用这些操作函数解析消息内容。

1.2.3 struct msghdr

struct msghdr用于描述发送和接收消息时的缓冲区和其他相关信息。

struct msghdr {
    void *msg_name;           // 指向目标地址结构体的指针(例如 sockaddr_nl)
    int msg_namelen;           // 地址的长度
    struct iovec *msg_iov;       // 指向 IO 向量数组的指针,描述了消息缓冲区
    __kernel_size_t msg_iovlen;  // IO 向量数组的长度
    void *msg_control;          // 指向辅助数据(如控制消息)的指针
    __kernel_size_t msg_controllen;  // 辅助数据的长度
    unsigned int msg_flags;      // 消息标志
};

1.2.4 Netlink 消息数据格式

Netlink 消息数据格式通常由一个 Netlink 消息头(struct nlmsghdr)和可选的消息数据组成。消息数据的具体格式取决于消息类型和应用场景。通常,Netlink 消息的结构如下:

1.2.5 struct netlink_kernel_cfg

struct netlink_kernel_cfg {
	unsigned int	groups;
	unsigned int	flags;
	void		    (*input)(struct sk_buff *skb);
	struct mutex	*cb_mutex;
	void		    (*bind)(int group);
};

重要字段详解
(1)input
-描述:接收消息时的回调函数。
-类型:void (*input)(struct sk_buff *skb)
-用途:当 Netlink 套接字收到消息时,会调用这个回调函数处理消息。
(2)cb_mutex
-描述:回调函数的互斥锁。
-类型: struct mutex *
-用途:在并发环境中保护回调函数的执行,避免竞态条件。
(3)bind

  • 描述:绑定 Netlink 套接字时的回调函数。
  • 类型:int (*bind)(struct net *net, int group)
  • 用途:在绑定套接字时执行特定操作,例如验证权限或初始化资源。
    (4)flags:
  • 描述:其他标志位。
  • 类型:unsigned int
  • 用途:用于配置 Netlink 套接字的额外行为,例如是否允许广播消息。

1.2.6 struct netlink_table

struct netlink_table是 Linux 内核中与 Netlink 套接字相关的数据结构之一,用于定义和管理各种 Netlink 套接字类型(family),每种类型对应一种特定的功能。
a) netlink_table结构体定义如下:

struct netlink_table {
    struct nl_portid_hash   hash;
    struct hlist_head       mc_list;
    struct listeners __rcu   *listeners;
    unsigned int           flags;
    unsigned int           groups;
    struct mutex           *cb_mutex;
    struct module          *module;
    void                   (*bind)(int group);
    int                   registered;
};

b) 成员变量解释
在 Linux 3.10 版本的内核代码中,struct netlink_table 结构体有所不同。下面是该结构体的详细解释,包括各个成员的定义及其作用。

  1. struct nl_portid_hash hash
    • 作用:用于存储和查找 Netlink 套接字绑定的端口 ID。
    • 详细说明:nl_portid_hash 是一个哈希表,用于管理和查找与特定端口 ID 关联的 Netlink 套接字。这样可以高效地根据端口 ID 查找对应的套接字。
  2. struct hlist_head mc_list
    • 作用:用于管理多播(multicast)组成员列表。
    • 详细说明:mc_list 是一个哈希链表头,用于存储所有订阅了某个多播组的 Netlink 套接字。这使得多播消息可以有效地分发给所有订阅该组的套接字。
  3. struct listeners __rcu *listeners
    • 作用:用于存储监听(listener)信息。
    • 详细说明:listeners 是一个指向 listeners 结构体的 RCU(Read-Copy Update)指针,存储所有注册为监听特定组的 Netlink 套接字。RCU 机制允许高效的并发访问和更新。
  4. unsigned int flags
    • 作用:存储与 Netlink family 相关的标志位。
    • 详细说明:flags 字段用于存储与特定 Netlink family 相关的各种标志。这些标志位可以用来控制 Netlink family 的行为或状态。
  5. unsigned int groups
    • 作用:表示支持的多播组数。
    • 详细说明:groups 字段指示该 Netlink family 支持的多播组的数量。多播组用于将消息广播给订阅了这些组的所有 Netlink 套接字。
  6. struct mutex *cb_mutex
    • 作用:用于保护回调函数的互斥锁。
    • 详细说明:cb_mutex 是一个指向互斥锁的指针,用于在执行与该 Netlink family 相关的回调函数时保护它们免受并发访问的影响。确保回调函数的执行是线程安全的。
  7. struct module *module
    • 作用:指向注册该 Netlink family 的内核模块。
    • 详细说明:module 字段保存指向包含该 Netlink family 代码的内核模块的指针。这样可以确保在模块卸载时进行适当的清理操作。
  8. void (*bind)(int group)
    • 作用:绑定回调函数。
    • 详细说明:bind 是一个函数指针,指向用于绑定到特定多播组的回调函数。当一个套接字绑定到某个多播组时,会调用这个回调函数。
  9. int registered
    • 作用:指示该 Netlink family 是否已注册。
    • 详细说明:registered 字段是一个标志位,指示该 Netlink family 是否已成功注册。它用于跟踪注册状态,以便在需要时进行适当的初始化或清理。
      c) 总结
      struct netlink_table 在 Linux 3.10 内核中的定义用于管理不同类型的 Netlink 套接字及其相关操作。每个成员变量都有特定的用途,用于管理 Netlink 套接字的哈希表、多播组、监听信息、回调函数等。通过这些字段,内核能够高效地处理 Netlink 通信,确保消息的正确传递和处理。
      d) 作用
      struct netlink_table 的主要作用是管理和维护特定 Netlink family 的信息和操作函数。具体包括以下几个方面:
  10. 管理 Netlink 套接字:
    • 使用哈希表和自旋锁来管理和保护对 Netlink 套接字的访问和操作。
  11. 注册和注销 Netlink family:
    • 提供函数指针用于在注册和注销 Netlink family 时执行特定操作(如 cb_add 和 cb_del)。
  12. 消息分发和处理:
    • 通过回调函数处理 Netlink 消息的分发和处理。
  13. 属性验证:
    • 使用 nla_policy 来验证 Netlink 消息中的属性,确保消息的合法性和正确性。

1.2.7 struct pernet_operations

struct pernet_operations {
    struct list_head  list;
    int            (*init)(struct net *net);
    void           (*exit)(struct net *net);
    void          (*exit_batch)(struct list_head *net_exit_list);
    int            *id;
    size_t          size;
};

字段详细解释

  1. list:
    • 类型:struct list_head
    • 含义:链表头,用于将所有的 pernet_operations 结构链接在一起。内核通过这个链表来管理所有注册的网络命名空间操作。
  2. init:
    • 类型:int (*)(struct net *net)
    • 含义:初始化函数指针。当创建一个新的网络命名空间时,内核会调用这个函数来执行特定的初始化操作。
    • 参数:
      • net:指向新创建的网络命名空间结构体的指针。
    • 返回值:返回 0 表示成功,非 0 值表示失败。
  3. exit:
    • 类型:void (*)(struct net *net)
    • 含义:退出函数指针。当销毁一个网络命名空间时,内核会调用这个函数来执行清理操作。
    • 参数:
      • net:指向即将销毁的网络命名空间结构体的指针。
  4. exit_batch:
    • 类型:void (*)(struct list_head *net_exit_list)
    • 含义:批量退出函数指针。用于批量处理多个网络命名空间的销毁,通常在内存管理和资源释放时更为高效。
    • 参数:
      • net_exit_list:指向包含要销毁的网络命名空间的链表头。
  5. id:
    • 类型:int *
    • 含义:指向一个 ID 变量的指针,用于标识特定的操作。这个 ID 可以用于在不同网络命名空间之间区分特定的资源或数据。
  6. size:
    • 类型:size_t
    • 含义:需要分配的内存大小。用于指定在每个网络命名空间中为私有数据分配的内存大小。
      使用示例:
      struct pernet_operations 是一个用于定义与网络命名空间相关操作的结构体。通过实现和注册这个结构体,开发者可以在创建和销毁网络命名空间时执行特定的初始化和清理操作。这使得管理和隔离网络子系统中的资源更加灵活和高效。

1.3 用户态套接字操作

1.3.1 创建Netlink套接字

在 Linux 中,通过socket函数可以创建一个 Netlink 套接字

int socket(int domain, int type, int protocol);

e) 参数解释
(1)domain
地址族,指定通信协议的家族。对于 Netlink 套接字,这个参数应设置为 AF_NETLINK。
(2)type
套接字类型,Netlink 套接字通常使用 SOCK_RAW 或 SOCK_DGRAM:

  • SOCK_RAW:提供原始的网络层访问,允许在网络层发送和接收数据。
  • SOCK_DGRAM:提供数据报(无连接)的通信方式。
    -在概念上SOCK_RAW和SOCK_DGRAM类型有所不同,但在 Netlink 实现中,这两者的实际行为和功能是相同的。
    (3)protocol
    指定使用的 Netlink 协议。常见的协议有:
  - NETLINK_ROUTE:路由和网络配置。
  - NETLINK_USERSOCK:用户定义协议。
  - NETLINK_FIREWALL:防火墙配置。
  - NETLINK_NFLOG:Netfilter/iptables 日志。
  - NETLINK_GENERIC:通用 Netlink 协议。

f) 示例代码
(1)创建 Netlink 套接字:

int sock_fd;
sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER);

这里创建了一个 Netlink 套接字,AF_NETLINK 指定地址族,SOCK_RAW 指定套接字类型,NETLINK_USER 指定使用用户定义的 Netlink 协议(协议号为 31,用户空间自定义的 Netlink 协议通常使用 16-31 之间的值)。

1.3.2 绑定 Netlink 套接字

在 Linux 中,通过bind()函数可以将Netlink 套接字绑定指定的nl_pid或nl_groups。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

示例代码如下:

struct sockaddr_nl src_addr;
memset(&src_addr, 0, sizeof(src_addr)) ;
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid()// 单播时设置为本进程pid
src_addr.nl_groups = 0// 组播ID

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

绑定套接字到本地地址,nl_pid设为当前进程的 PID,表示此 Netlink 套接字与当前进程关联。

1.3.3 发送Netlink消息到内核

在 Linux 中,通过sendmsg()函数可以将Netlink消息发送到内核套接字。

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

a) 初始化目的地址:

struct sockaddr_nl dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0;  // 发送到内核时pid和组播ID都为0
dest_addr.nl_groups = 0;

b) 准备 Netlink 消息:

struct nlmsghdr *nlh;
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();
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Hello, Netlink!");

创建并初始化 Netlink 消息,包括设置消息头部信息和消息数据。
c) 发送 Netlink 消息:

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&dest_addr;   // msg_name存储目的信息
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;                  // msg_iov存储消息数据
msg.msg_iovlen = 1;

sendmsg(sock_fd, &msg, 0) ;

1.3.4 从内核接收 Netlink 消息

在 Linux 中,通过recvmsg ()函数可以从内核套接字接收到Netlink消息。

ssize_t recvmsg(int sockfd, const struct msghdr *msg, int flags);

示例代码如下:

struct msghdr *msg;
/* 从内核接收到msg消息,可从中解析出发送端 */
recvmsg(sock_fd, &msg, 0);

1.3.5 connect的使用及意义

在 Linux 中,通过recvmsg ()函数可以从内核套接字接收到Netlink消息。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect函数在 Netlink 套接字中的使用是合法的,但其意义与传统的 TCP 套接字不同。在 Netlink 套接字上调用connect主要有以下几个作用:
a) 绑定目标地址
通过connect,将 Netlink 套接字绑定到内核,后续的发送操作将自动使用这个目标地址,无需每次发送时显式指定目标地址。
b) 发送操作简化
一旦通过connect绑定目标地址,后续的send或sendmsg调用将可不再需要显式提供目标地址,简化了代码。
c) 示例代码

// 初始化目的地址结构体
struct sockaddr_nl dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0;      // 发送到内核
dest_addr.nl_groups = 0;   // 目的地址不加入任何组

// 使用 connect 绑定目标地址
connect(sock_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr)) ;
 
// 分配 Netlink 消息缓冲区// 初始化 msghdr 结构体
struct nlmsghdr *nlh;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

// 发送 Netlink 消息
sendmsg(sock_fd, &msg, 0);

在这个示例中,通过 connect 绑定目标地址后,发送消息时不再需要提供目标地址,从而简化了 sendmsg 的调用。
1.3.6 收发函数的特点
a) 使用sendmsg和recvmsg函数

  • 无需connect:可以直接指定消息的目标地址。
  • 更灵活:能够处理复杂的消息结构和控制信息。
    示例代码:
struct sockaddr_nl dest_addr;
struct msghdr msg;
struct iovec iov;
struct nlmsghdr *nlh = ...; // 已初始化的 Netlink 消息头部

// 设置目标地址为内核
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // 目标是内核
dest_addr.nl_groups = 0; // 不加入任何组

// 初始化 msghdr
memset(&msg, 0, sizeof(msg));
msg.msg_name = &dest_addr;
msg.msg_namelen = sizeof(dest_addr);
iov.iov_base = nlh;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

// 发送消息
sendmsg(sock_fd, &msg, 0);

// 接收消息
recvmsg(sock_fd, &msg, 0);

b) 使用sendto和recvfrom函数

  • 无需connect:可以在函数调用时指定目标地址和源地址。
  • 较简单的消息传递:适合简单的消息传递,不涉及复杂的控制信息。
    示例代码:
struct sockaddr_nl dest_addr;
struct nlmsghdr *nlh = ...; // 已初始化的 Netlink 消息头部

// 设置目标地址为内核
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // 目标是内核
dest_addr.nl_groups = 0; // 不加入任何组

// 发送消息
sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));

// 接收消息
recvfrom(sock_fd, nlh, nlh->nlmsg_len, 0, NULL, NULL);

c) 使用send和recv函数

  • 需要connect:必须进行connect操作才能指定目的地址,否则无法确定消息的目标。
  • 适合简单通信:适用于简单的、连接式通信场景。
    示例代码:
struct sockaddr_nl dest_addr;
struct nlmsghdr *nlh = ...; // 已初始化的 Netlink 消息头部

// 设置目标地址为内核
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // 目标是内核
dest_addr.nl_groups = 0; // 不加入任何组

// 连接到目标地址
connect(sock_fd, (struct sockaddr*)&dest_addr, sizeof(dest_addr));

// 发送消息
send(sock_fd, nlh, nlh->nlmsg_len, 0);

// 接收消息
recv(sock_fd, nlh, NLMSG_SPACE(MAX_PAYLOAD), 0);

d) 关键点总结

  • sendmsg和recvmsg:通过msghdr结构体可以在消息中携带目标地址信息,因此不需要connect。这种方式非常灵活,可以处理复杂的消息结构,包括多段消息和控制信息。
  • sendto和recvfrom:函数参数中可以指定目标地址和源地址,因此也不需要connect。适合简单的单次消息传递。
  • send和recv:这两个函数无法在调用时指定目标地址和源地址,因此必须进行connect操作以绑定目标地址。这使得它们更适合持续的、连接式的通信场景,但灵活性不如前两者。

1.4 内核态套接字操作

1.4.1 创建Netlink套接字

netlink_kernel_create是一个用于在内核中创建 Netlink 套接字的函数。它允许内核模块与用户空间进程通过 Netlink 协议进行通信。
a) netlink_kernel_create函数原型

struct sock *netlink_kernel_create(struct net *net, int protocol, struct netlink_kernel_cfg *cfg);

b) 参数详解
(1)struct net *net

  • 描述:指定 Netlink 套接字所属于的网络命名空间(network namespace)。通常使用全局的init_net来表示默认的网络命名空间。
  • 类型: struct net
  • 用途:允许在不同的网络命名空间中创建 Netlink 套接字,这对于网络虚拟化和容器化场景非常有用。
    (2)int protocol(每个protocol对应一个内核套接字)
  • 描述:指定 Netlink 套接字所使用的协议。
  • 类型:int
  • 用途:用于区分不同的 Netlink 协议。常见的协议类型包括:
 - NETLINK_ROUTE:用于路由相关信息。
 - NETLINK_USERSOCK:用于用户自定义的 Netlink 通信。
 - NETLINK_GENERIC:用于通用的 Netlink 协议。

各类型在内核态会有对应创建的内核套接字,用于与用户态指定类型的套接字进行数据收发。
(3)struct netlink_kernel_cfg *cfg
-描述:指向配置结构体struct netlink_kernel_cfg的指针,用于配置 Netlink 套接字的行为。
-类型:struct netlink_kernel_cfg *
-用途:提供回调函数和其他配置选项,用于定制 Netlink 套接字的行为。
c) 示例代码
以下是一个创建 Netlink 套接字并注册接收回调函数的示例代码:
在这里插入图片描述

d) 总结

  • netlink_kernel_create函数用于在内核中创建Netlink 套接字。
  • 该函数需要三个参数:网络命名空间指针、协议类型和配置结构体指针。
  • 重要的配置结构体struct netlink_kernel_cfg包含接收消息的回调函数、互斥锁等。
  • 通过netlink_kernel_create函数,内核模块可以与用户空间进程进行通信,处理并发送 Netlink 消息。

1.4.2 从用户态接收Netlink消息

在Linux内核态中,netlink_kernel_create函数在创建Netlink 套接字时会通过struct netlink_kernel_cfg注册一个接收消息的回调函数。
当用户态发送netlink消息至内核态后,netlink_sendmsg函数处理来自用户空间的 Netlink 消息,并将其发送到指定的目标(内核或其他 Netlink 套接字)中进一步处理:
netlink_sendmsg-> nlmsg_unicast/ nlmsg_multicast-> netlink_getsockbyportid-> nlk_sk(sk)->netlink_rcv

1.4.3 发送Netlink消息至用户态

nlmsg_unicast和nlmsg_multicast是两个用于在内核态通过 Netlink 套接字向用户态发送消息的函数。它们分别用于单播(发送给一个特定进程)和多播(发送给一组进程)。
(1)nlmsg_unicast:用于将 Netlink 消息发送给一个特定的用户态进程。
(2)nlmsg_multicast:用于将 Netlink 消息发送给一个或多个多播组中的用户态进程。

1.4.3.1单播消息–nlmsg_unicast

nlmsg_unicast用于将 Netlink 消息单播发送给指定的用户态进程。
a) 原型

int nlmsg_unicast(struct sock *sk, struct sk_buff *skb, u32 portid);

b) 参数

  • sk:指向 Netlink 套接字的指针,即通过 netlink_kernel_create 创建的 struct sock。
  • skb:指向要发送的 socket 缓冲区 struct sk_buff。
  • portid:目标用户进程的端口 ID,通常是进程的 PID。
    c) 返回值
  • 0:成功。- 负值:错误代码。

1.4.3.2 组播消息–nlmsg_multicast

nlmsg_multicast用于将 Netlink 消息多播发送给一个或多个用户态进程。
a) 原型

int nlmsg_multicast(struct sock *sk, struct sk_buff *skb, u32 portid, 
unsigned int group, gfp_t flags);

b) 参数

  • sk:指向 Netlink 套接字的指针,即通过 netlink_kernel_create 创建的 struct sock。
  • skb:指向要发送的 socket 缓冲区 struct sk_buff。
  • portid:发送消息的进程端口 ID,通常是进程的 PID,或者为 0 表示由内核发送。
  • group:目标多播组的标志,表示消息要发送到哪个多播组。
  • flags:分配内存时使用的标志,例如 GFP_KERNEL。
    c) 返回值
  • 0:成功。-负值:错误代码。
  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值