正如iptables可以通过netlink将规则配置到Netfilter的HOOK点一样,我也希望实现一个fstables通过netlink将规则配置在FSHOOK的特定HOOK点。
于是我实现了一个Demo,但并没有完成,因为后面遇到了问题,我不希望现在就解决问题,而是希望把问题描述一番。
代码在sandfs_with_no_ebpf的devel分支:
https://github.com/marywangran/sandfs_with_no_ebpf/tree/devel
可以看到,增加了一个client目录,里面是一个python程序,它用来模拟fstables的功能:
import os
import socket
import struct
NETLINK_FSHOOK = 31
SANDFS_LOOKUP = 0
SANDFS_OPEN = 1
SANDFS_CLOSE = 2
SANDFS_READ = 3
SANDFS_WRITE = 4
# TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, NETLINK_FSHOOK)
sock.bind((os.getpid(), RTMGRP_LINK))
name = bytes('test1'.encode('utf-8'))
opt = 'A'
hooknum = SANDFS_READ
uid = 1000
path = bytes('N/A'.encode('utf-8'))
pos = -1
count = 10
buf = bytes('N/A'.encode('utf-8'))
data = struct.pack("@32sBII32sII32s", name, opt, hooknum, uid, path, pos, count, buf);
sock.sendto(data, (0, 0))
非常简单,就是把一个matches结构体通过socket灌进内核,内核通过netlink套接字来接收它:
static void rule_nl_recv_msg(struct sk_buff *skb)
{
struct rule_match *match;
struct vfs_rule *rule;
char *head;
char name[DESC_MAX] = {0};
int err = 0;
head = (char *)skb->data;
strncpy(name, head, DESC_MAX);
head += DESC_MAX;
match = (struct rule_match *)head;
printk(KERN_INFO "##### opt:%c hook:%x uid:%x path:%s pos:%x count:%x buff:%s\n",
match->option,
match->hooknum,
match->uid,
match->path,
match->pos,
match->count,
match->buff);
if (match->option == 'A') {
rule = kzalloc(sizeof(struct vfs_rule), GFP_KERNEL);
rule->hooknum = match->hooknum;
strcpy(&rule->name[0], name);
memcpy(&rule->match, match, sizeof(struct rule_match));
err = sandfs_register_hook(rule);
} else if (match->option == 'D') {
rule = sandfs_unregister_hook(match->hooknum, name);
if (rule) {
kfree(rule);
}
}
}
逻辑非常简单,即根据配置信息生成一个vfs_rule结构体,插入对应HOOK的链表,即调用 sandfs_register_hook 。
现在问题来了,vfs_rule的func回调函数如何实现?虽然用户态可以灌下来策略和配置,但没有办法灌下来函数啊!
我试着重构FS_HOOK:
inline int FS_HOOK(unsigned int hook, struct sandfs_args *args, void *priv)
{
int err = FS_ACCEPT;
@@ -54,9 +90,11 @@ inline int FS_HOOK(unsigned int hook, struct sandfs_args *args, void *priv)
read_lock(&lock);
list_for_each_entry(rule, &list, list) {
- if (!rule->func)
- continue;
- err = rule->func(hook, args, priv, &handled);
+ if (!rule->func) {
+ err = gen_func(rule, args, priv, &handled);
+ } else {
+ err = rule->func(hook, args, priv, &handled);
+ }
if (handled == 1) {
read_unlock(&lock);
return err;
我增加了一个通用的gen_func函数:
static int gen_func(struct vfs_rule *rule, struct sandfs_args *args, void *priv, int *handled)
{
int err = FS_ACCEPT;
#if 0
struct rule_match match;
int num;
struct cred *cred;
kuid_t id;
unsigned int plen;
match = rule->match;
num = args->num_args;
if (num >= 2) {
cred = (struct cred *)largs->args[SANDFS_IDX_CRED].value;
id = cred->uid;
plen = largs->args[SANDFS_IDX_PATH].size;
path = (char *)largs->args[SANDFS_IDX_PATH].value;
if (num == 2 && match->uid == id.val && !strncmp(path, match->path, plen)) {
err = FS_DROP;
*handled = 1;
}
}
if (num >= 4) {
}
if (num >= 5) {
}
#endif
return err;
}
但总觉得长得不好看,于是我#if 0掉了它!
嗯,是的,看来必须采用iptables的做法了,事先将各个match分别注册进内核,然后将rule紧密排列。
匹配进行的时候,顺序扫描这些排列着的rules,然后根据每一个match的func去进行匹配。就像iptables的xt_ematch_foreach宏那样。
你会发现,紧密排列的这些rules其实就是一张表,所以叫做iptables,ip6tables,arptables…
如果我也这么实现,才配叫做fstables…不过我觉得离目标很近了,不自觉中我的gen_func已经在向ipt_do_table靠拢了。
不过,这种tables有个弊端,那就是tables的内存是静态分配的,新增加一条规则所牵扯的动作如下:
- 分配新的连续内存,大小为所有已经rules的大小加上新rule的总大小。
- 将已有的rules拷贝进新内存。
- 将新rule追加到新内存已有rules之后。
- 释放旧内存。
如果规则特别多,tables就会非常大,这是一个非常耗时的操作,于是我在想用一种动态内存的方式搞定match匹配的问题。
浙江温州皮鞋湿,下雨进水不会胖。