netlink 是 Linux 系统里用户态程序、内核模块之间的一种 IPC 方式,特别是用户态程序和内核模块之间的 IPC 通信。比如在 Linux 终端里常用的 ip 命令,就是使用 netlink 去跟内核进行通信的。
TL,DR 使用 Go 通过 netlink 向内核模块发送消息,内核模块响应一条消息,源代码:内核模块,Go 程序。
内核模块
注册 netlink
在 insmod custom-netlink.ko
加载内核模块时,注册自定义的 netlink 处理函数。在 rmmod custom_netlink
卸载内核模块时,则注销自定义的 netlink 处理函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#define NETLINK_CUSTOM 31
static int __init custom_nl_init(void)
{
//This is for 3.6 kernels and above.
struct netlink_kernel_cfg cfg = {
.input = custom_nl_recv_msg,
};
nl_sk = netlink_kernel_create(&init_net, NETLINK_CUSTOM, &cfg);
......
return 0;
}
static void __exit custom_nl_exit(void)
{
netlink_kernel_release(nl_sk);
printk(KERN_INFO "[-] unregistered custom_netlink module.\n");
}
module_init(custom_nl_init);
module_exit(custom_nl_exit);
|
接收 netlink 消息
消息格式如下:
1
2
3
4
5
6
|
0 4 8
+-+-+-+-+-+-+-+-+
|msg len| data |
+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+
|
前 4 个字节代表后面的数据长度,后跟着一段字符串。为了防止在打印字符串的时候内存越界了,将字符串的最后一个字符置为 \0
。
打印字符串后,将该字符响应回去。
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
|
enum
{
nlresp_result_unspec,
nlresp_result_ok,
nlresp_result_invalid
};
static void custom_nl_recv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
unsigned char *nl_data;
unsigned char *msg;
__u32 msg_size;
nlh = nlmsg_hdr(skb);
nl_data = (unsigned char *)NLMSG_DATA(nlh);
msg_size = *(__u32*)nl_data;
if (msg_size > 1024) {
custom_nl_send_msg(nlh, nlresp_result_invalid, NULL, 0);
return;
}
msg = nl_data+4;
msg[msg_size-1] = '\0';
printk(KERN_INFO "[Y] [custom netlink] receive msg from user: %s\n", msg);
custom_nl_send_msg(nlh, nlresp_result_ok, msg, msg_size);
}
|
响应 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
|
typedef struct
{
__u32 result;
unsigned char data[0];
} custom_nl_resp_data_t;
static int custom_nl_send_msg(struct nlmsghdr *nlh, __u32 result, const unsigned char *data, __u32 data_size)
{
custom_nl_resp_data_t *resp;
struct nlmsghdr *nlh_resp;
struct sk_buff *skb;
int pid = nlh->nlmsg_pid, res = -1;
const unsigned char *resp_msg = "Echo from kernel: ";
data_size += 4+18; // 4 是 result 的长度,18 是 resp_msg 的长度
resp = kzalloc(data_size + 18, GFP_KERNEL);
memcpy(resp->data, resp_msg, 18); // 复制 resp_msg
memcpy(resp->data + 18, data, data_size-4); // 复制 Go 程序发送过来的字符串
resp->result = result;
skb = nlmsg_new(data_size, GFP_KERNEL); // 内核自动释放内存,不需要手动释放
if (!skb)
goto out;
nlh_resp = nlmsg_put(skb, pid, nlh->nlmsg_seq, NLMSG_DONE, data_size, 0);
memcpy(NLMSG_DATA(nlh_resp), resp, data_size); // 填充 netlink 消息内容
res = nlmsg_unicast(nl_sk, skb, pid); // 将消息响应给指定 pid 的程序
out:
kfree(resp); // 用后释放内存
return res;
}
|
Go 程序
发起 netlink 连接
使用跟内核模块一致的 netlink family 发起连接。
1
2
3
4
5
|
const (
netlinkCustom = 31
)
conn, err := netlink.Dial(netlinkCustom, nil)
|
发送 netlink 消息
封装 netlink 消息,并将提供的字符串复制到消息中。
1
2
3
4
5
6
7
8
9
10
11
|
// msg 是前面提供的字符串
data := make([]byte, 4+len(msg)+1)
nlenc.PutUint32(data[:4], uint32(len(msg)+1))
copy(data[4:], msg)
fmt.Println("Send to kernel:", msg)
var nlmsg netlink.Message
nlmsg.Data = data
msgs, err := conn.Execute(nlmsg)
|
接收 netlink 消息
按如下格式接收消息:
1
2
3
4
5
6
|
0 4 8
+-+-+-+-+-+-+-+-+
| result| data |
+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+
|
前 4 个字节是内核模块响应的结果,后跟着一段字符串。
1
2
3
4
5
6
7
|
msgs, err := conn.Execute(nlmsg)
......
nlmsg = msgs[0]
......
fmt.Println(string(nlmsg.Data[4:]))
|
实验效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
➜ kernel-module-fun make
make -C /lib/modules/4.19.0/build M=/root/Projects/kernel-module-fun modules
make[1]: Entering directory '/usr/src/linux-headers-4.19.0'
CC [M] /root/Projects/kernel-module-fun/custom-netlink.o
Building modules, stage 2.
MODPOST 4 modules
CC /root/Projects/kernel-module-fun/custom-netlink.mod.o
LD [M] /root/Projects/kernel-module-fun/custom-netlink.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.19.0'
➜ kernel-module-fun insmod custom-netlink.ko
➜ kernel-module-fun ./custom-netlink/custom-netlink
Send to kernel: Hello from Go
Echo from kernel: Hello from Go
➜ kernel-module-fun rmmod custom_netlink
➜ kernel-module-fun dmesg
[2424642.636765] [+] registered custom_netlink module!
[2424644.929387] [Y] [custom netlink] receive msg from user: Hello from Go
[2424647.822184] [-] unregistered custom_netlink module.
|
小结
使用 netlink 进行用户态程序和内核模块的 IPC 通信,能够动态地变更内核模块的配置,甚至动态地去开启、关闭内核模块里的功能。比如在收到配置后,再挂载 netfilter hook。
使用 netlink 动态传配置的功能,可以取代 insmod
传参这一笨拙的传配置方式。
P.S. go-iproute:使用 Go 基于 netlink 实现的 iproute2 工具库。