Netlink Basic Knowledge

Netlink Basic Knowledge

henry Lv4

一、基础知识

1. recv 相关

在 Linux 网络编程中,recv() 及其相关函数用于接收数据。除了 recv(),还有多个变体和类似函数,适用于不同的场景,如 阻塞/非阻塞模式、消息边界保留、带外数据等

1️⃣ recv()(标准接收函数)

1
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • 适用于:TCP/UDP 套接字 接收数据。
  • 主要参数:
    • sockfd:套接字描述符。
    • buf:用于存储接收数据的缓冲区。
    • lenbuf 的大小。
    • flags
      • 0(默认)普通接收。
      • MSG_DONTWAIT(非阻塞)。
      • MSG_PEEK(窥探数据,但不移除)。
      • MSG_WAITALL(等到缓冲区填满)。
  • 返回值: 接收到的字节数,返回 0 表示 连接关闭-1 表示 错误(可用 errno 诊断)。

2️⃣ recvfrom()(用于 UDP/无连接套接字)

1
2
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
  • 适用于: UDP 套接字(无连接)。
  • 额外参数:
    • src_addr:存储发送方地址(适用于 UDP)。
    • addrlensrc_addr 结构体的大小。
  • 区别:
    • recv() 只用于 面向连接(TCP) 的 socket。
    • recvfrom() 适用于 无连接(UDP),可以获取 数据来源地址

示例(UDP 服务器):

1
2
3
4
5
6
char buffer[1024];
struct sockaddr_in cli_addr;
socklen_t addr_len = sizeof(cli_addr);

ssize_t recv_len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&cli_addr, &addr_len);

3️⃣ recvmsg()(高级接收,多缓冲区支持)

1
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
  • 适用于: 接收带外数据(控制消息)、多缓冲区(scatter-gather I/O)
  • 参数 msghdr 结构体包含:
    • msg_name:可选,存储发送方地址。
    • msg_iovI/O 向量数组,可接收多个缓冲区的数据。
    • msg_control:用于接收 辅助数据(如文件描述符传递)。
  • 适用场景:
    • 接收带外数据(OOB)。
    • 接收多个缓冲区数据(scatter-gather I/O)。
    • 用于 SO_PASSCRED 选项(传递进程凭证)。

示例(接收多个缓冲区的数据):

1
2
3
4
5
6
7
8
9
10
11
12
struct iovec iov[2];
char buf1[256], buf2[256];
iov[0].iov_base = buf1;
iov[0].iov_len = sizeof(buf1);
iov[1].iov_base = buf2;
iov[1].iov_len = sizeof(buf2);

struct msghdr msg = {0};
msg.msg_iov = iov;
msg.msg_iovlen = 2;

ssize_t recv_len = recvmsg(sockfd, &msg, 0);

4️⃣ read()(通用读取,与 recv() 类似)

1
ssize_t read(int fd, void *buf, size_t count);
  • 适用于: 任何 文件描述符(包括 sockets)。
  • 区别:
    • read() 没有 flags 参数,适用于 默认阻塞模式
    • recv() 适用于 socket,可以使用 MSG_* 选项。

示例:

1
2
char buffer[1024];
ssize_t len = read(sockfd, buffer, sizeof(buffer));

5️⃣ recv_flags 选项

标志 描述
MSG_DONTWAIT 非阻塞接收
MSG_PEEK 窥探数据,不删除缓冲区数据
MSG_WAITALL 直到缓冲区填满
MSG_TRUNC 允许接收大于 buf 的数据
MSG_OOB 接收带外数据

示例(窥探数据但不移除):

1
recv(sockfd, buffer, sizeof(buffer), MSG_PEEK);

2. send 相关

1️⃣ send()

send() 是用于通过 TCPUDP 套接字发送数据的基本函数。

1
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd:要发送数据的套接字文件描述符。
  • buf:指向要发送的数据的缓冲区。
  • len:要发送的数据的长度。
  • flags:控制发送操作的标志(通常设置为 0)。

返回值:

  • 成功时,返回实际发送的字节数。
  • 失败时,返回 -1,并设置 errno

适用场景:

  • 用于 TCPUDP 套接字发送数据。
  • 对于 UDP,它不会保证数据的可靠传输。
  • 对于 TCP,它会在传输层处理数据的分段、重传等。

2️⃣ sendto()

sendto() 用于发送数据包,它不仅适用于 TCPUDP 套接字,还能用在 原始套接字UNIX 域套接字 等场景中。sendto() 允许指定目的地地址。

1
2
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
  • sockfd:套接字文件描述符。
  • buf:要发送的数据缓冲区。
  • len:要发送的数据长度。
  • flags:标志。
  • dest_addr:指向目标地址的指针。对于 UDP,这是目标 IP 地址和端口;对于 原始套接字,这可能是目标的物理地址等。
  • addrlen:目标地址的长度。

返回值:

  • 成功时,返回发送的字节数。
  • 失败时,返回 -1,并设置 errno

适用场景:

  • UDP 套接字或 原始套接字,因为它允许你指定目的地址。
  • 适用于 单播广播多播 等情况。

3️⃣ sendmsg()

sendmsg() 是一个比 sendto() 更灵活的函数,允许发送更加复杂的消息,尤其是当你需要控制消息头、IOV(向量)等时。它常用于 Netlink 或其他高级套接字编程。

1
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  • sockfd:套接字文件描述符。
  • msg:指向 msghdr 结构的指针。msghdr 结构包含消息数据、目标地址、消息的 iov 等信息。
  • flags:控制发送操作的标志。

返回值:

  • 成功时,返回实际发送的字节数。
  • 失败时,返回 -1,并设置 errno

适用场景:

  • 用于需要发送复杂消息的情况,如 NetlinkUNIX 域套接字,或者需要 控制信息(如 sendmsg() 用于发送带有多个 IOV 的消息)。

4️⃣ sendmmsg()

sendmmsg()sendmsg() 的扩展,允许一次发送多个消息。这个函数通常用于提高网络传输效率,尤其在发送大量消息时。

1
int sendmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen, int flags);
  • sockfd:套接字文件描述符。
  • msgvec:指向 mmsghdr 结构数组的指针。每个 mmsghdr 结构包含一个消息的发送信息。
  • vlenmsgvec 数组的长度。
  • flags:标志。

mmsghdr 结构:

1
2
3
4
struct mmsghdr {
struct msghdr msg_hdr; // 每个消息的描述
unsigned int msg_len; // 该消息发送的字节数
};

返回值:

  • 成功时,返回发送的消息数。
  • 失败时,返回 -1,并设置 errno

适用场景:

  • 用于高效地批量发送多个消息,通常在高性能网络应用中使用。

5️⃣ sendfile()

sendfile() 是一个高效的文件传输函数,它直接在内核空间中进行文件到套接字的数据传输,不需要将数据从内核复制到用户空间。它用于 TCP 套接字,可以有效地提高文件发送效率。

1
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

参数:

  • out_fd:输出文件描述符(通常是套接字)。
  • in_fd:输入文件描述符(通常是打开的文件)。
  • offset:从输入文件的哪个位置开始发送数据。
  • count:要发送的字节数。

返回值:

  • 成功时,返回实际发送的字节数。
  • 失败时,返回 -1,并设置 errno

适用场景:

  • 高效地将文件内容传输到套接字,通常用于通过网络传输大文件。

这一部分内容主要来自

https://i.blackhat.com/Asia-24/Presentations/Asia-24-Ma-LinkDoor-A-Hidden-Attack.pdf

Netlink 中存在的漏洞,导致其作为 Android 的子系统可能会使 Android 变得不安全,所以这篇文章算是很好的介绍了 netlink 在安卓下的攻击利用。

nipaste_2025-02-10_20-49-4

Netlink 架构图如上,其中需要注意的是Generic NetlinkClassic Netlink 是 Linux 内核中两种不同的 Netlink 实现方式,它们都用于内核与用户空间之间的通信,但在设计和使用上有显著的区别。

主要区别

特性 Classic Netlink Generic Netlink
协议类型 固定(如 NETLINK_ROUTE 动态(使用 NETLINK_GENERIC
扩展性 较差 较好(支持动态注册家族)
消息结构 简单 复杂(支持属性机制)
多路复用 不支持 支持(通过家族和命令)
适用场景 固定功能模块 动态扩展功能模块

nipaste_2025-02-10_20-50-4

上图为 Classic Netlink 下常见的数据包传输格式。

nipaste_2025-02-10_20-51-4

Classic Netlink 实现中,存在一个比较严重的问题就是,对从用户空间传递的数据缺乏足够有效的检查,从而导致存在各种问题。

nipaste_2025-02-10_20-52-1

上图给出了 Netlink 从上至下在三个角度下可能存在攻击面,这可以帮助我们在审计代码的过程中注意这些提到的方面。

nipaste_2025-02-10_20-52-3

同理,这张图里面介绍了一些自下而上可能存在的一些问题。

nipaste_2025-02-10_20-53-4

上图为 Generic Netlink 下常见的数据包传输格式,下图给出了 genlmsghdrnlattr 结构体的相关说明。

nipaste_2025-02-10_20-54-3

nipaste_2025-02-10_20-54-4

攻击利用面如下,分别自上而下和自下而上两个角度阐述了在 Generic Netlink 下可能存在的攻击面。

nipaste_2025-02-10_20-55-2

nipaste_2025-02-10_20-55-4

三、Netlink基础概念

Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,同时它也以用于进程间通信(Netlink 更多用于内核通信,进程之间通信更多使用Unix域套接字)。

在一般情况下,用户态和内核态通信会使用:

传统的Ioctl、sysfs属性文件或者procfs属性文件,这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信

而Netlink是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信提供了一组特殊的API接口,用户态则基于socket API,内核发送的数据会保存在接收进程socket 的接收缓存中,由接收进程处理。

Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:

  1. 为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。
  2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
  3. 使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。
  4. netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在 后面的文章中将介绍这一机制的使用。
  5. 内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
  6. netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。

目前 netlink 协议族支持32种协议类型,它们定义在 include/uapi/linux/netlink.h 中:

nipaste_2025-02-10_14-50-5

用户态应用使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close()。

使用 netlink 的应用必须包含头文件 linux/netlink.h。 socket 需要的头文件也必不可少 sys/socket.h。Netlink通信跟常用UDP Socket通信类似,struct sockaddr_nl是netlink通信地址,跟普通socket struct sockaddr_in类似。

struct sockaddr_nl

1
2
3
4
5
6
struct sockaddr_nl {
__kernel_sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* zero */
__u32 nl_pid; /* port ID */
__u32 nl_groups; /* multicast groups mask */
};

struct nlmsghdr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* struct nlmsghdr - fixed format metadata header of Netlink messages
* @nlmsg_len: Length of message including header
* @nlmsg_type: Message content type
* @nlmsg_flags: Additional flags
* @nlmsg_seq: Sequence number
* @nlmsg_pid: Sending process port ID
*/
struct nlmsghdr {
__u32 nlmsg_len;
__u16 nlmsg_type;
__u16 nlmsg_flags;
__u32 nlmsg_seq;
__u32 nlmsg_pid;
};

nlmsg_type:消息状态,内核在include/uapi/linux/netlink.h中定义了以下4种通用的消息类型,它们分别是:

1
2
3
4
5
6
#define NLMSG_NOOP		0x1	/* Nothing.		*/
#define NLMSG_ERROR 0x2 /* Error */
#define NLMSG_DONE 0x3 /* End of a dump */
#define NLMSG_OVERRUN 0x4 /* Data lost */

#define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */

nlmsg_flags:消息标记,它们用以表示消息的类型,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* Flags values */

#define NLM_F_REQUEST 0x01 /* It is request message. */
#define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */
#define NLM_F_ECHO 0x08 /* Receive resulting notifications */
#define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */
#define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */

/* Modifiers to GET request */
#define NLM_F_ROOT 0x100 /* specify tree root */
#define NLM_F_MATCH 0x200 /* return all matching */
#define NLM_F_ATOMIC 0x400 /* atomic GET */
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)

/* Modifiers to NEW request */
#define NLM_F_REPLACE 0x100 /* Override existing */
#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */
#define NLM_F_CREATE 0x400 /* Create, if it does not exist */
#define NLM_F_APPEND 0x800 /* Add to end of list */

struct msghdr

user_msghdr msghdr 是 Linux 中用于描述消息传递的结构体(用户空间的 msghdr 结构体),主要用于实现系统调用 sendmsgrecvmsg,支持更复杂的消息传递功能。通过 msghdr,可以指定消息内容、目标地址、附加数据(如文件描述符)、标志等

1
2
3
4
5
6
7
8
9
struct msghdr {
void *msg_name; // 指向目标地址的指针
socklen_t msg_namelen; // 地址长度
struct iovec *msg_iov; // 指向数据块的指针数组
size_t msg_iovlen; // 数据块数组的大小
void *msg_control; // 附加数据(辅助数据,如文件描述符传递)
size_t msg_controllen; // 附加数据的大小
int msg_flags; // 接收时返回的标志
};

msg_name 和 msg_namelen

  • 作用
    • 指定消息的目标地址(对于发送)。
    • 接收消息的源地址(对于接收)。
  • 详细
    • 典型用例是套接字地址(如 struct sockaddr_in)。
    • 若设置为 NULL 或长度为 0,则表示不关心地址信息。

msg_iov 和 msg_iovlen

  • 作用

    • 描述消息的数据内容。
  • 详细

    • msg_iov 是一个指向 struct iovec 数组的指针,每个 iovec 描述一段内存区域。
    • msg_iovlen 指定数组中元素的数量。
    • 通过这种方式,可以高效地发送或接收分散存储在多个内存区域的数据(称为 “scatter-gather I/O”)。

    struct iovec 定义

    1
    2
    3
    4
    struct iovec {
    void *iov_base; // 数据的起始地址
    size_t iov_len; // 数据的长度
    };

msg_control 和 msg_controllen

  • 作用

    • 用于传递或接收附加数据,称为控制消息(control message)。
  • 详细

    • msg_control 是一个缓冲区,存储附加数据(如传递文件描述符、设置消息优先级)。
    • msg_controllen 指定缓冲区大小。
    • 附加数据的内容通常是一个 cmsghdr 结构体。

    struct cmsghdr 定义

    1
    2
    3
    4
    5
    6
    struct cmsghdr {
    size_t cmsg_len; // 控制消息的总长度
    int cmsg_level; // 控制消息所属的协议级别(如 SOL_SOCKET)
    int cmsg_type; // 控制消息的类型(如 SCM_RIGHTS)
    // 后面紧跟附加数据
    };

msg_flags

  • 作用
    • 接收消息时返回的标志,用于指示消息的状态或行为。
  • 常见标志
    • MSG_EOR:表示消息的结束。
    • MSG_TRUNC:消息被截断。
    • MSG_CTRUNC:控制数据被截断。

使用流程

1. 创建套接字

1
int skfd = socket(AF_NETLINK, SOCK_RAW, netlink_type)

第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,如前面讲的用户自定义协议类型NETLINK_MYTEST, NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。

内核预定义的协议类型有:

nipaste_2025-02-10_14-50-5

2. 绑定套接字

1
2
3
4
5
6
7
8
9
10
struct sockaddr_nl 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;
}

fd为前面的 socket 调用返回的文件描述符,参数 nladdr 为 struct sockaddr_nl 类型的地址。为了发送一个 netlink 消息给内核或其他用户态应用,需要填充目标 netlink socket 地址,此时,字段 nl_pid 和 nl_groups 分别表示接收消息者的进程 ID 与多播组。

如果字段 nl_pid 设置为 0,表示消息接收者为内核或多播组,如果 nl_groups 为 0,表示该消息为单播消息,否则表示多播消息

3. 构建发送消息

使用函数 sendmsg 发送 netlink 消息时还需要引用结构 struct msghdrstruct nlmsghdrstruct iovec,结构 struct msghdr 需如下设置:

1
2
3
4
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);

其中 nladdr 为消息接收者的 netlink 地址,struct nlmsghdrnetlink socket 自己的消息头,这用于多路复用和多路分解 netlink 定义的所有协议类型以及其它一些控制,netlink 的内核实现将利用这个消息头来多路复用和多路分解已经其它的一些控制,因此它也被称为netlink 控制块。因此,应用在发送 netlink 消息时必须提供该消息头。

1
2
3
4
5
6
7
8
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */
__u16 nlmsg_type; /* Message type*/
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};

字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,字段 nlmsg_type 用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0,字段 nlmsg_flags 用于设置消息标志,可用的标志包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Flags values */
#define NLM_F_REQUEST 1 /* It is request message. */
#define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK 4 /* Reply with ack, with zero or error code */
#define NLM_F_ECHO 8 /* Echo this request */
/* Modifiers to GET request */
#define NLM_F_ROOT 0x100 /* specify tree root */
#define NLM_F_MATCH 0x200 /* return all matching */
#define NLM_F_ATOMIC 0x400 /* atomic GET */
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)
/* Modifiers to NEW request */
#define NLM_F_REPLACE 0x100 /* Override existing */
#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */
#define NLM_F_CREATE 0x400 /* Create, if it does not exist */
#define NLM_F_APPEND 0x800 /* Add to end of list */
  • 标志NLM_F_REQUEST用于表示消息是一个请求,所有应用首先发起的消息都应设置该标志。
  • 标志NLM_F_MULTI 用于指示该消息是一个多部分消息的一部分,后续的消息可以通过宏NLMSG_NEXT来获得。
  • 宏NLM_F_ACK表示该消息是前一个请求消息的响应,顺序号与进程ID可以把请求与响应关联起来。
  • 标志NLM_F_ECHO表示该消息是相关的一个包的回传。
  • 标志NLM_F_ROOT 被许多 netlink 协议的各种数据获取操作使用,该标志指示被请求的数据表应当整体返回用户应用,而不是一个条目一个条目地返回。有该标志的请求通常导致响应消息设置 NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,因此,需要在字段 nlmsg_type 中指定协议类型。
  • 标志 NLM_F_MATCH 表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。
  • 标志 NLM_F_ATOMIC 指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。
  • 标志 NLM_F_DUMP 未实现。
  • 标志 NLM_F_REPLACE 用于取代在数据表中的现有条目。
  • 标志 NLM_F_EXCL_ 用于和 CREATE 和 APPEND 配合使用,如果条目已经存在,将失败。
  • 标志 NLM_F_CREATE 指示应当在指定的表中创建一个条目。
  • 标志 NLM_F_APPEND 指示在表末尾添加新的条目。

内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。下面是一个示例:

1
2
3
4
5
6
7
8
#define MAX_MSGSIZE 1024
char buffer[] = "An example message";
struct nlmsghdr nlhdr;
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
strcpy(NLMSG_DATA(nlhdr),buffer);
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));
nlhdr->nlmsg_pid = getpid(); /* self pid */
nlhdr->nlmsg_flags = 0;

结构 struct iovec 用于把多个消息通过一次系统调用来发送,下面是该结构使用示例:

1
2
3
4
5
struct iovec iov;
iov.iov_base = (void *)nlhdr;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

在完成以上步骤后,消息就可以通过下面语句直接发送:

1
sendmsg(fd, &msg, 0);

应用接收消息时需要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,然后填充消息头,添完后就可以直接调用函数 recvmsg() 来接收。

4. 接收回传信息

应用接收消息时需要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,然后填充消息头,添完后就可以直接调用函数 recvmsg() 来接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
#define MAX_NL_MSG_LEN 1024
struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov;
struct nlmsghdr * nlhdr;
nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);
iov.iov_base = (void *)nlhdr;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
recvmsg(fd, &msg, 0);

注意:fd为socket调用打开的netlink socket描述符,在消息接收后,nlhdr指向接收到的消息的消息头,nladdr保存了接收到的消息的目标地址,宏NLMSG_DATA(nlhdr)返回指向消息的数据部分的指针。

宏定义相关

linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:

1
2
#define NLMSG_ALIGNTO   4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值。

1
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))

NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。它一般用于分配消息缓存。

1
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值,它也用于分配消息缓存。

1
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏。

1
2
#define NLMSG_NEXT(nlh,len)      ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))

NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用。

1
2
3
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))

NLMSG_OK(nlh,len)用于判断消息是否有len这么长。

1
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

NLMSG_PAYLOAD(nlh,len)用于返回payload的长度,函数close用于关闭打开的netlink socket。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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协议类型
cfg:cfg存放的是netlink内核配置参数(如下)
*/

/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff *skb);
int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group);
void (*release) (struct sock *sk, unsigned long *groups);
};

单播netlink_unicast() 和 多播netlink_broadcast()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 发送单播消息 */
extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
/*
ssk: netlink socket
skb: 内核skb buff
portid:通信的端口号
nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用定时睡眠
*/

/* 发送多播消息 */
extern 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:端口id
group: 是所有目标多播组对应掩码的"OR"操作的合值。
allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone
*/

Netlink 内核API

netlink的内核实现在.c文件net/core/af_netlink.c中,内核模块要想使用 netlink,也必须包含头文件 linux /netlink.h。内核使用netlink需要专门的API,这完全不同于用户态应用对 netlink 的使用。

增加新的netlink协议类型,用户仅需增加如下定义到 linux/netlink.h 就可以:

1
#define NETLINK_MYTEST  17

只要增加这个定义之后,用户就可以在内核的任何地方引用该协议,在内核中,为了创建一个netlink socket用户需要调用如下函数:

1
2
struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
  • struct net *net:指向网络命名空间的指针。通常传入 &init_net(表示初始网络命名空间)。
  • int unit:指定 Netlink 协议类型(如 NETLINK_USERNETLINK_ROUTE 等)。
  • struct netlink_kernel_cfg *cfg:配置结构体,用于指定 Netlink socket 的行为。

netlink_kernel_create 是 Linux 内核中的一个函数,用于在内核模块中创建一个 Netlink socket,以便与用户空间程序进行通信。它是内核空间实现 Netlink 通信的关键函数之一。作用如下:

  • 该函数用于在内核中创建一个 Netlink socket
  • 允许内核模块通过 Netlink 协议与用户空间程序通信
  • 内核模块可以通过这个 socket 接收来自用户空间的消息,并向用户空间发送消息。

返回值:

  • 成功时返回一个指向 struct sock 的指针(表示创建的 Netlink socket)。
  • 失败时返回 NULL

struct netlink_kernel_cfg 结构体

netlink_kernel_cfg 用于配置 Netlink socket 的行为,其定义如下:

1
2
3
4
5
6
7
struct netlink_kernel_cfg {
unsigned int groups; // 多播组掩码
void (*input)(struct sk_buff *skb); // 接收消息的回调函数
struct mutex *cb_mutex; // 回调函数的互斥锁
void (*bind)(int group); // 绑定多播组的回调函数
bool (*compare)(struct net *net, struct sock *sk); // 比较函数
};
  • input最重要的字段,指定接收消息的回调函数。当用户空间发送消息到内核时,内核会调用这个回调函数处理消息
  • groups:指定多播组掩码,用于加入多播组。
  • cb_mutex:可选字段,用于保护回调函数的互斥锁。
  • bindcompare:可选字段,用于高级配置。

当有消 息到达这个netlink socket时,该input函数指针就会被引用,一个 input 函数的使用案例大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void input (struct sock *sk, int len)
{
struct sk_buff *skb;
struct nlmsghdr *nlh = NULL;
u8 *data = NULL;
while ((skb = skb_dequeue(&sk->receive_queue)) != NULL)
{
/* process netlink message pointed by skb->data */
nlh = (struct nlmsghdr *)skb->data;
data = NLMSG_DATA(nlh);
/* process netlink message with header pointed by
* nlh and data pointed by data
*/
}
}

用于将消息单播(一对一)发送到指定的用户空间进程。

1
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
  • struct sock *sk:Netlink socket(由 netlink_kernel_create 创建)。
  • struct sk_buff *skb:要发送的消息(封装在 sk_buff 中)。
  • u32 pid:目标用户空间进程的 PID。
  • int nonblock:是否非阻塞发送(通常为 0,表示阻塞)。

返回值:

  • 成功时返回发送的字节数。
  • 失败时返回错误码。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct sk_buff *skb;
struct nlmsghdr *nlh;

// 分配 sk_buff
skb = nlmsg_new(NLMSG_ALIGN(1024), GFP_KERNEL);
if (!skb) {
printk(KERN_ERR "Failed to allocate skb\n");
return -ENOMEM;
}

// 填充消息头
nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, 1024, 0);
strcpy(nlmsg_data(nlh), "Hello, User!");

// 发送消息
int ret = netlink_unicast(nl_sk, skb, user_pid, 0);
if (ret < 0) {
printk(KERN_ERR "Failed to send message\n");
}

用于将消息广播(一对多)发送到所有监听指定 Netlink 协议的用户空间进程。

1
int netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation);
  • struct sock *sk:Netlink socket(由 netlink_kernel_create 创建)。
  • struct sk_buff *skb:要发送的消息(封装在 sk_buff 中)。
  • u32 pid:发送方的 PID(通常为 0)。
  • u32 group:目标多播组。
  • gfp_t allocation:内存分配标志(如 GFP_KERNEL)。

返回值:

  • 成功时返回 0。
  • 失败时返回错误码。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct sk_buff *skb;
struct nlmsghdr *nlh;

// 分配 sk_buff
skb = nlmsg_new(NLMSG_ALIGN(1024), GFP_KERNEL);
if (!skb) {
printk(KERN_ERR "Failed to allocate skb\n");
return -ENOMEM;
}

// 填充消息头
nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, 1024, 0);
strcpy(nlmsg_data(nlh), "Broadcast message!");

// 广播消息
int ret = netlink_broadcast(nl_sk, skb, 0, 1, GFP_KERNEL);
if (ret < 0) {
printk(KERN_ERR "Failed to broadcast message\n");
}

用于释放由 netlink_kernel_create 创建的 Netlink socket。

1
void netlink_kernel_release(struct sock *sk);
  • struct sock *sk:要释放的 Netlink socket。

示例:

1
2
3
4
if (nl_sk) {
netlink_kernel_release(nl_sk);
printk(KERN_INFO "Netlink socket released\n");
}

(1) 用户态程序 test.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#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;
struct iovec iov;
struct msghdr msg;
user_msg_info u_info;
socklen_t len;
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl 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));

msg.msg_iov = &iov;
msg.msg_iovlen = 1;
iov.iov_base = nlh;
iov.iov_len = NLMSG_SPACE(MAX_PLOAD);
ret = sendmsg(skfd, &msg, 0);
if (!ret)
{
perror("sendto error\n");
close(skfd);
exit(-1);
}
// printf("send kernel:%s\n", umsg);
printf("send kernel:%s\n", (char *)NLMSG_DATA(nlh));

// method 1 --> recvfrom
// 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);

// method 2 --> recvmsg
char buffer[1024] = {0};
nlh = (struct nlmsghdr *)buffer;

memset(&msg, 0, sizeof(msg));
msg.msg_name = &saddr; //(optional)
msg.msg_namelen = sizeof(saddr); //(optional)
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

iov.iov_base = nlh;
iov.iov_len = sizeof(buffer);
ret = recvmsg(skfd, &msg, 0);

if (!ret)
{
perror("recv form kernel error\n");
close(skfd);
exit(-1);
}

// printf("from kernel:%s\n", u_info.msg);
printf("from kernel:%s\n", (char *)NLMSG_DATA(nlh));
close(skfd);

return 0;
}

(2) Netlink 内核模块代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#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("henry");
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);
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);

(3)Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#
#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

目录结构:

1
2
3
4
5
test->
test.c
test_netlink->
netlink_test.c
Makefile

确保有以上三个文件之后执行如下命令:

1
2
3
4
5
6
7
8
9
10
11
12
test/test_netlink$ make
test/test_netlink$ sudo insmod netlink_test.ko
test$ gcc -g test.c
test$ ./a.out
send kernel:hello netlink!!
from kernel:hello users!!!
/test$ sudo dmesg | grep netlink
[ 17.766945] Initializing XFRM netlink socket
[41855.787147] netlink_test: loading out-of-tree module taints kernel.
[41855.787473] netlink_test: module verification failed: signature and/or required key missing - tainting kernel
[41855.802213] test_netlink_init
[41971.242423] kernel recv from user: hello netlink!!

五、Poc and Exploitation

Reference:https://i.blackhat.com/Asia-24/Presentations/Asia-24-Ma-LinkDoor-A-Hidden-Attack.pdf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
int main(int argc, char **argv)
{
int skfd;
int ret;
socklen_t len;
struct nlmsghdr *nlh= NULL;
struct sockaddr_nl saddr, daddr;
uint32_t nlh_size = 0;
char payload[PAYLOAD_SIZE] = {0};
char receive[RECEIVE_SIZE] = {0};

skfd = Socket(AF_NETLINK, SOCK_RAW, NETLINK_ID);

...

memset(&saddr, 0, sizeof(saddr));
saddr.nl_family=AF_NETLINK;//AF_NETLINK
saddr.nl_pid = NETLINK_PID; // port ID
saddr.nl_groups =0;
ret = bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr))
...

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

...

nlh_size = NLMSG_SPACE(PAYLOAD_SIZE);
nlh = (struct nlmsghdr *)malloc(nlh_size);
...

memset(nlh, 0, nlh_size);
/* fill in struct nlmsghdr */
nlh->nlmsg_len = nlh_size;
nlh->nlmsg_type = 0;
nlh->nlmsg_flags=0;
nlh->nlmsg_seq =0;
nlh->nlmsg_pid = saddr.nl_pid; // self port

/* fill in payload */
memcpy(NLMSG_DATA(nlh), payload, PAYLOAD_SIZE);
ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
...
ret = recvfrom(skfd, receive, RECEIVE_SIZE, 0, (struct sockaddr *)&daddr, &len);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
int genl_send_msg(int sock_fd, u_int16_t family_id, u_int32_t nlmsg_pid, 
u_int8_t genl_cmd, u_int8_t genl_version,
u_int16_t nla_type, void *nla_data, int nla_len)
{
struct nlattr *na;
struct sockaddr_nl dst_addr;
int r, buflen;
char *buf;
msgtemplate_t msg;

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

msg.nhl.nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
msg.nhl.nlmsg_type = family_id;
msg.nhl.nlmsg_flags = NLM_F_REQUEST;
msg.nhl.nlmsg_seq = 0;
msg.nhl.nlmsg_pid = nlmsg_pid;
msg.gnhl.cmd = genl_cmd;
msg.gnhl.version = genl_version;
na = (struct nlattr *) GENLMSG_DATA(&msg);
na->nla_type = nla_type;
na->nla_len = nla_len + NLA_HDRLEN;
memcpy(NLA_DATA(na), nla_data, nla_len);

msg.nhl.nlmsg_len = NLMSG_ALIGN(na->nla_len);
buf = (char *) &msg;
buflen = msg.nhl.nlmsg_len;

while ((r = sendto(sock_fd, buf, buflen, 0, (struct sockaddr *) &dst_addr, sizeof(dst_addr))) < buflen) {
if (r > 0) {
buf += r;
buflen -= r;
} else if (errno != EAGAIN) {
return -1;
}
}

return 0;
}


static int genl_get_family_id(int sock_fd, char *family_name)
{
msgtemplate_t ans;
int id, rc;
struct nlattr *na;
int rep_len;

rc = genl_send_msg(sock_fd, GENL_ID_CTRL, 0, CTRL_CMD_GETFAMILY, 1, CTRL_ATTR_FAMILY_NAME, (void *)family_name, strlen(family_name) + 1);
rep_len = recv(sock_fd, &ans, sizeof(ans), 0);
...

na = (struct nlattr *) GENLMSG_DATA(&ans);
na = (struct nlattr *) ((char *) na + NLA_ALIGN(na->nla_len));

if (na->nla_type == CTRL_ATTR_FAMILY_ID) {
id = *(__u16 *) NLA_DATA(na);
} else {
id = 0;
}

return id;
}

void genl_rcv_msg(int family_id, int sock_fd, char *buf)
{
int ret;
struct msgtemplate msg;
struct nlattr *na;

ret = recv(sock_fd, &msg, sizeof(msg), 0);
...

if (msg.nhl.nlmsg_type == family_id && family_id != 0) {
na = (struct nlattr *) GENLMSG_DATA(&msg);
strncpy(buf, (char *) NLA_DATA(na), MAX_MSG_SIZE);
}
}

int main(int argc, char **argv)
{
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
int sock_fd, retval;
int family_id = 0;
char *attr_payload = NULL;

sock_fd = Socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);

...

memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = NETLINK_PID;
src_addr.nl_groups = 0;
retval = bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr));

...

family_id = genl_get_family_id(sock_fd, GENL_FAMILY_NAME);

attr_payload = (char *)malloc(MAx_MSG_SIZE);

memset(attr_payload, O, MAX_MSG_SIZE);
*(int32_t *)attr_payload = Oxff;
retval = genl_send_msg(sock_fd, family_id, NETLINK_PID, GENL_CMD, GENL_VERSION,
ATTR_TYPE, (void *)attr_payload, sizeof(int32_t));

...

memset(attr_payload, O, MAX_MSG_SIZE);
genl_rcv_msg(family_id, sock_fd, attr_payload);

}
  • Title: Netlink Basic Knowledge
  • Author: henry
  • Created at : 2025-02-10 21:14:23
  • Updated at : 2025-02-10 21:32:43
  • Link: https://henrymartin262.github.io/2025/02/10/netlink/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments