阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

在Linux的连接跟踪(nf_conntrack)中缓存私有数据省去每次查找

186次阅读
没有评论

共计 28752 个字符,预计需要花费 72 分钟才能阅读完成。

前面说过很多次,conntrack 作为一中连接跟踪机制,如果它本身是可扩展的,那么将会是多么令人激动的一件事,当你看了 N 多文档代码之后,你发现它确实是可以扩展的,但是却没有感到激动,因为你可能发现:

1. 它可以注册一个 account 扩展,但是计数机制却很原始;
2. 我希望增加一个新型的扩展,却不得不重新编译内核;

怎么办?我曾经很生气地默默指责过当初实现这个的人,想当然的认为将扩展本身也做成可扩展的,而不是写死几个特定的扩展将是一个多么容易的事,我一直憋着没有去做这个实现,就是因为觉得它太简单,在工作中也确实需要一个新的扩展,然而既有的扩展类型中没有,为了不重新编译内核,我只好盗用了 acct 扩展。采用了一个 OO 中典型的封装方法:

struct my_ext {
    struct orig_ext;
    char info[0];
};


是时候改变一些事态了。基于下面几个原因,在周六的早上,我突然决定在周末完成它:
1. 外部因素:好不容易感冒了,作为一个羸弱的人,我不希望得到别人的同情,只需要获得周末的安静,感冒发烧是最好的选择;
2. 内部因素:年终总结完了,工作计划也确定了,后面是个收网的过程,稳为重,不需要太激进,因此也就没有什么技术上不可控的因素,心理安了,事就可以开始做了;
可能我又要笑话自己了,不就写个简单的模块么?怎么搞得跟诸葛孔明布阵一样 … 如此感性且主观一人怎么就 …
      不管怎么讲,这个模块看起来确实是简单的。然而一旦做起来,发现有两个比较严重的问题:
1. 反射内省问题
如果 conntrack 的 extend 有 128 个 slot,每个 slot 里面放一个私有数据。问题是,程序怎么知道哪个 slot 里面有哪个数据。程序有能力存储,但是程序自己却不知道这一点 … 这就是一个怪圈,你必须让数据成为自描述的,或者就规定死第 n 号 slot 必须放路由项,第 m 个 slot 必须放 socket… 现有的 nf conntrack 模块使用了后一种方法,即枚举 nf_ct_ext_id 做的事。
      可是我还是想随机选择 slot,这样更加灵活。自描述的数据结构也看了不好,ASN.1 太复杂,且内核数据更多的不是标识属性,而是定义一种行为,google 的 protocol buffer 也不是很合适,需要定义太多的回调函数来完成反射自省.. 后来我想了一个办法,那就是定义个索引蓝图,标识“slot 索引的索引”,而不是标识具体 slot 的位置。
      这就需要定义一个新的枚举,定义蓝图:

enum idx_idx{
ROUTE,
SOCKET,
AND_SO_ON,
IDX_IDX_NUM
};

然后定义一个数组来标识真正的索引:

int idx[IDX_IDX_NUM];

定义一个 bitmap 来表示 slot 的使用情况即可,具体的做法可以看代码,一目则了然。
2. 内存寻址问题
内核内存是宝贵的,不是说物理内存用不起,而是它的虚拟地址空间也是有限的,因此建议使用 64 位系统,如果是 32 位系统,如果希望内核保存比较大的数据结构,请在编译的时候按照 2G/2G 或者 1G/3G 来拆分地址空间,前者情况用户和内核各自占据 2G,后者的话内核占 3G,用户仅仅占 1G。
      也许就是因为存在这个内存问题,Linux 的 nf conntrack 限制了 extend 的内存使用,其最大长度字段数据类型是 u8。由于我知道我的系统,所以我将其改为了 u16。你必须要知道的是,nf connrtack 的 extend 内存使用时是连续的,你不能采用一个 sizeof(char *)大小的空间保存一个指针,然后这个指针指向一个超级大的连续空间 … 但是为什么不能呢?还是因为代码的普适问题,我了解我的系统,所以我可以使用保存指针的做法。另外我还保留了数组的方式,总之,数组和指针分工是明确的,数组用于 extend 的寻址,而指针用于数据的获取。
      代码包括一个框架和一个测试程序,内核还是 2.6.32 amd64,已经在 github 上了:https://github.com/marywangran/extension-of-nf_conntrack-ext
      还是在这里贴一份备份,怕哪天 github 被 wall 了 …

修改 include/net/netfilter/nf_conntrack_extend.h:

 

--- nf_conntrack_extend.h.orig  2014-03-29 12:55:26.000000000 +0800
+++ nf_conntrack_extend.h   2015-01-15 17:28:39.000000000 +0800
@@ -3,13 +3,17 @@

 #include <net/netfilter/nf_conntrack.h>

+#define NFCT_EXT_EXT
+
 enum nf_ct_ext_id
 {
    NF_CT_EXT_HELPER,
    NF_CT_EXT_NAT,
    NF_CT_EXT_ACCT,
    NF_CT_EXT_ECACHE,
-   NF_CT_EXT_NEW,
+#ifdef NFCT_EXT_EXT
+   NF_CT_EXT_EXT,
+#endif
    NF_CT_EXT_NUM,
 };

@@ -17,13 +21,21 @@
 #define NF_CT_EXT_NAT_TYPE struct nf_conn_nat
 #define NF_CT_EXT_ACCT_TYPE struct nf_conn_counter
 #define NF_CT_EXT_ECACHE_TYPE struct nf_conntrack_ecache
-#define NF_CT_EXT_NEW_TYPE struct nf_conntrack_new
+#ifdef NFCT_EXT_EXT
+#define NF_CT_EXT_EXT_TYPE struct nf_conntrack_ext
+#endif

 /* Extensions: optional stuff which isn't permanently in struct. */
 struct nf_ct_ext {
    struct rcu_head rcu;
+#ifdef NFCT_EXT_EXT
+   /* 内存不再是个事儿 */
+   u16 offset[NF_CT_EXT_NUM];
+   u16 len;
+#else
    u8 offset[NF_CT_EXT_NUM];
    u8 len;
+#endif
    char data[0];
 };

@@ -80,10 +92,18 @@
    unsigned int flags;

    /* Length and min alignment. */
+#ifdef NFCT_EXT_EXT
+   /* 内存不再是个事儿 */
+   u16 len;
+   u16 align;
+   /* initial size of nf_ct_ext. */
+   u16 alloc_size;
+#else
    u8 len;
    u8 align;
    /* initial size of nf_ct_ext. */
    u8 alloc_size;
+#endif
 };

 int nf_ct_extend_register(struct nf_ct_ext_type *type);

 

增加 include/net/netfilter/nf_conntrack_ext.h:

 

/*
 * (C) 2015 marywangran <marywangran@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#ifndef _NF_CONNTRACK_EXT_H
#define _NF_CONNTRACK_EXT_H
#include <net/net_namespace.h>
#include <linux/netfilter/nf_conntrack_common.h>
#include <linux/netfilter/nf_conntrack_tuple_common.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_extend.h>

#define MAX_EXT_SLOTS	8	
#define BITINT	1


struct nf_conntrack_ext {
	/* 必须有一个数组用于自省或者反射 */
	int	bits_idx[MAX_EXT_SLOTS];
	int	bits[BITINT];
	char *slot[MAX_EXT_SLOTS];
};


int nf_ct_exts_add(const struct nf_conn *ct, void *ext);

void *nf_ct_exts_get(const struct nf_conn *ct, int idx); 

void nf_ct_exts_remove(const struct nf_conn *ct, int idx);

struct nf_conntrack_ext *nf_conn_exts_find(const struct nf_conn *ct);

struct nf_conntrack_ext *nf_conn_exts_add(struct nf_conn *ct, gfp_t gfp);
extern int nf_conntrack_exts_init();
extern void nf_conntrack_exts_fini();

#endif /* _NF_CONNTRACK_EXT_H */

 

修改 net/netfilter/nf_conntrack_core.c:

 

--- nf_conntrack_core.c.orig    2014-03-29 13:00:17.000000000 +0800
+++ nf_conntrack_core.c 2015-01-15 17:01:28.000000000 +0800
@@ -42,6 +42,10 @@
 #include <net/netfilter/nf_conntrack_extend.h>
 #include <net/netfilter/nf_conntrack_acct.h>
 #include <net/netfilter/nf_conntrack_ecache.h>
+#ifdef NFCT_EXT_EXT
+/* 引入 extend 的 extend 头文件 */
+#include <net/netfilter/nf_conntrack_ext.h>
+#endif
 #include <net/netfilter/nf_nat.h>
 #include <net/netfilter/nf_nat_core.h>

@@ -644,8 +648,11 @@
    }

    nf_ct_acct_ext_add(ct, GFP_ATOMIC);
-
    nf_ct_ecache_ext_add(ct, GFP_ATOMIC);
+#ifdef NFCT_EXT_EXT
+   /* 在创建 conntrack 的时候初始化 extend 的 extend */
+   nf_conn_exts_add(ct, GFP_ATOMIC);
+#endif

    spin_lock_bh(&nf_conntrack_lock);
    exp = nf_ct_find_expectation(net, tuple);
@@ -1130,6 +1137,10 @@

    nf_ct_free_hashtable(net->ct.hash, net->ct.hash_vmalloc,
                 net->ct.htable_size);
+#ifdef NFCT_EXT_EXT
+   /* 析构 extend 的 extend */
+   nf_conntrack_exts_fini();
+#endif
    nf_conntrack_ecache_fini(net);
    nf_conntrack_acct_fini(net);
    nf_conntrack_expect_fini(net);
@@ -1344,9 +1355,19 @@
    ret = nf_conntrack_ecache_init(net);
    if (ret < 0)
        goto err_ecache;
+#ifdef NFCT_EXT_EXT
+   /* 注册 extend 的 extend */
+   ret = nf_conntrack_exts_init();
+   if (ret < 0)
+       goto err_exts;
+#endif

    return 0;

+#ifdef NFCT_EXT_EXT
+err_exts:
+   nf_conntrack_ecache_fini(net);
+#endif
 err_ecache:
    nf_conntrack_acct_fini(net);
 err_acct:

 

增加 net/netfilter/nf_conntrack_ext.c:

 

/* conntrack 扩展的扩展实现文件. */

/*
 * conntrack 扩展的扩展实现文件.
 * 技术核心:*		1. 位图
 *		2. 索引的索引数组(外部维护的一个‘蓝图’)
 * (C) 2015 marywangran <marywangran@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <net/netfilter/nf_conntrack_extend.h>
#include <net/netfilter/nf_conntrack_ext.h>

/* 这个 spin lock 应该和每一个 ext 绑定而不是全局的!*/
static DEFINE_SPINLOCK(nfct_ext_lock);

static struct nf_ct_ext_type ext_extend __read_mostly = {.len	= sizeof(struct nf_conntrack_ext),
	.align	= __alignof__(struct nf_conntrack_ext),
	.id	= NF_CT_EXT_EXT,
	.flags		= NF_CT_EXT_F_PREALLOC,
};

/* 
 * 增加一个数据到 extend 的 extend
 * 注意:需要自己在外部维护一个关于索引的索引的数组
 **/
int nf_ct_exts_add(const struct nf_conn *ct, void *ext)
{
	int ret_idx = -1;
	struct nf_conntrack_ext *exts = NULL;

	if (!ext) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (!exts) {goto out;}
	spin_lock(&nfct_ext_lock);
	ret_idx = find_first_zero_bit(exts->bits, MAX_EXT_SLOTS);
	if (ret_idx > MAX_EXT_SLOTS) {
		ret_idx = -1;
		spin_unlock(&nfct_ext_lock);
		goto out;
	}
	if (exts->slot[ret_idx]) {
		ret_idx = -1;
		spin_unlock(&nfct_ext_lock);
		goto out;
	}
	set_bit(ret_idx, exts->bits);
	exts->slot[ret_idx] = (char *)ext;
	spin_unlock(&nfct_ext_lock);
out:
	return ret_idx;
};
EXPORT_SYMBOL(nf_ct_exts_add);

/*
 * 根据 ID 的 index 获取保存在 conntrack 上的数据
 **/
void *nf_ct_exts_get(const struct nf_conn *ct, int idx)
{
	char *ret = NULL;
	struct nf_conntrack_ext *exts;

	if (idx > MAX_EXT_SLOTS || idx < 0) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (!exts) {goto out;}
	spin_lock(&nfct_ext_lock);
	if (! test_bit(idx, exts->bits)) {spin_unlock(&nfct_ext_lock);
		goto out;
	}
	ret = exts->slot[idx];
	spin_unlock(&nfct_ext_lock);
out:
	return (void *)ret;
}
EXPORT_SYMBOL(nf_ct_exts_get);

/*
 * 根据 ID 的 index 删除保存在 conntrack 上的数据
 **/
void nf_ct_exts_remove(const struct nf_conn *ct, int idx)
{
	struct nf_conntrack_ext *exts;
	if (idx > MAX_EXT_SLOTS || idx < 0) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (!exts) {goto out;}

	spin_lock(&nfct_ext_lock);
	if (! test_bit(idx, exts->bits)) {spin_unlock(&nfct_ext_lock);
		goto out;
	}
	clear_bit(idx, exts->bits);
	exts->slot[idx] = NULL;
	spin_unlock(&nfct_ext_lock);
out:
	return;
};
EXPORT_SYMBOL(nf_ct_exts_remove);

struct nf_conntrack_ext *nf_conn_exts_find(const struct nf_conn *ct)
{return nf_ct_ext_find(ct, NF_CT_EXT_EXT);
}
EXPORT_SYMBOL(nf_conn_exts_find);

struct nf_conntrack_ext *nf_conn_exts_add(struct nf_conn *ct, gfp_t gfp)
{
	struct nf_conntrack_ext *exts;

	exts = nf_ct_ext_add(ct, NF_CT_EXT_EXT, gfp);
	if (!exts) {printk("failed to add extensions area");
		return NULL;
	}

	/* 初始化 */
	{
		int i;
		for (i = 0; i < MAX_EXT_SLOTS; i++) {exts->bits_idx[i] = -1;
			exts->slot[i] = NULL;
		}
	}
	return exts;
}
EXPORT_SYMBOL(nf_conn_exts_add);

int nf_conntrack_exts_init()
{
	int ret;

	ret = nf_ct_extend_register(&ext_extend);
	if (ret < 0) {printk("nf_conntrack_ext: Unable to register extension\n");
		goto out;
	}
	printk("nf_conntrack_ext: register extension OK\n");

	return 0;
out:
	return ret;
}

void nf_conntrack_exts_fini()
{nf_ct_extend_unregister(&ext_extend);
}

 

测试程序nf_conntrack_private_data_auto_save_restore.c:

 

#include <linux/module.h>  
#include <linux/skbuff.h>  
#include <net/tcp.h>
#include <net/netfilter/nf_conntrack_ext.h>
  
MODULE_AUTHOR("marywangran");  
MODULE_LICENSE("GPL");  

/*
 * 必须定义一个用于自省的数组索引
 * 否则就会陷入“数据 - 元数据 - 元元数据 - 元元元数据...”的无限自指怪圈!* 这也是 AI 所面临的问题:自我意识是根本:being 知道某件事,并且 being 知道“being 知道某件事”,* 并且 being 知道“being 知道‘being 知道某件事’”...
 */
enum ext_idx_idx {
	CONN_ORIG_ROUTE,
	CONN_REPLY_ROUTE,
	CONN_SOCK, 
	CONN_AND_SO_ON, 
	NUM
};

static inline void
nf_ext_put_sock(struct sock *sk)
{if ((sk->sk_protocol == IPPROTO_TCP) && (sk->sk_state == TCP_TIME_WAIT)){inet_twsk_put(inet_twsk(sk));
	} else {sock_put(sk);
	}
}

static void
nf_ext_destructor(struct sk_buff *skb)
{
	struct sock *sk = skb->sk;
	skb->sk = NULL;
	skb->destructor = NULL;
	if (sk) {nf_ext_put_sock(sk);
	}
}

/* 缓存 socket 的 HOOK 函数 */
static unsigned int ipv4_conntrack_save_sock (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked) {goto out;}
	if ((ip_hdr(skb)->protocol != IPPROTO_UDP) && 
					(ip_hdr(skb)->protocol != IPPROTO_TCP)) {goto out;}
	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 缓存 socket,注意,只有 INPUT 的恢复缓存 socket 才有比较大的意义 */
		if (exts->bits_idx[CONN_SOCK] == -1) {if (skb->sk == NULL){goto out;}
			if ((ip_hdr(skb)->protocol == IPPROTO_TCP) && skb->sk->sk_state != TCP_ESTABLISHED) {goto out;}
			exts->bits_idx[CONN_SOCK] = nf_ct_exts_add(ct, skb->sk);
		}
	} 
out:
	return NF_ACCEPT;
}

/* 缓存路由项的 HOOK 函数 */
static unsigned int ipv4_conntrack_save_dst (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{  
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked) {goto out;}
	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 缓存路由。注意,有两个方向。IP 无方向,两个方向路由都要缓存 */
		int dir = CTINFO2DIR(ctinfo);  
		int idx = (dir == IP_CT_DIR_ORIGINAL)?CONN_ORIG_ROUTE:CONN_REPLY_ROUTE;
		if (exts->bits_idx[idx] == -1) {struct dst_entry *dst = skb_dst(skb);
			if (dst) {dst_hold(dst); 
				exts->bits_idx[idx] = nf_ct_exts_add(ct, dst);
			}
		} 
	} 
out:
	return NF_ACCEPT;  
}  

/* 获取缓存 socket 的 HOOK 函数 */
static unsigned int ipv4_conntrack_restore_sock (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{  
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked){goto out;}
	if ((ip_hdr(skb)->protocol != IPPROTO_UDP) && 
			(ip_hdr(skb)->protocol != IPPROTO_TCP)) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 获取缓存的 socket */
		if (exts->bits_idx[CONN_SOCK] != -1) {struct sock *sk = (struct sock *)nf_ct_exts_get(ct, exts->bits_idx[CONN_SOCK]);
			if (sk) {if ((ip_hdr(skb)->protocol == IPPROTO_TCP) && sk->sk_state != TCP_ESTABLISHED) {goto out;}
				if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt))) {goto out;}
				skb_orphan(skb);
				skb->sk = sk;
				/* 曾经在上面 atomic inc 了引用计数,等到转交给下任 owner 的时候,一定要 put */
				skb->destructor = nf_ext_destructor;
			}
		}
	}
out:
	return NF_ACCEPT;
}
  
/* 获取缓存路由项的 HOOK 函数 */
static unsigned int ipv4_conntrack_restore_dst (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{  
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 获取缓存的路由 */
		int dir = CTINFO2DIR(ctinfo);  
		int idx = (dir == IP_CT_DIR_ORIGINAL)?CONN_ORIG_ROUTE:CONN_REPLY_ROUTE;
		if (exts->bits_idx[idx] != -1) {struct dst_entry *dst = (struct dst_entry *)nf_ct_exts_get(ct, exts->bits_idx[idx]);
			if (dst) {dst_hold(dst);
				skb_dst_set(skb, dst);
			}
		}  
	} 
out:
	return NF_ACCEPT;  
}  

/*
 * 总体图景:* OUTPUT:缓存 socket
 * INPUT:恢复 socket
 *
 * POSTROUTING|INPUT:缓存路由
 * PREROUTING:恢复路由
 */
static struct nf_hook_ops ipv4_conn_cache_ops[] __read_mostly = {  
	{  
		.hook           = ipv4_conntrack_save_dst,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_POST_ROUTING,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},  
	{  
		.hook           = ipv4_conntrack_save_sock,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_LOCAL_OUT,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},  
	{  
		.hook           = ipv4_conntrack_save_dst,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_LOCAL_IN,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},
	{  
		.hook           = ipv4_conntrack_restore_sock,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_LOCAL_IN,  
		.priority       = NF_IP_PRI_CONNTRACK + 2,  
	},
	{  
		.hook           = ipv4_conntrack_restore_dst,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_PRE_ROUTING,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},  
};  
  
static int __init cache_dst_and_sock_demo_init(void)  
{  
	int ret;  
	ret = nf_register_hooks(ipv4_conn_cache_ops, ARRAY_SIZE(ipv4_conn_cache_ops));  
	if (ret) {goto out;;}
	return 0;
out:	
	return ret;  
}  
  
static void __exit cache_dst_and_sock_demo_fini(void)  
{nf_unregister_hooks(ipv4_conn_cache_ops, ARRAY_SIZE(ipv4_conn_cache_ops));  
}  
  
module_init(cache_dst_and_sock_demo_init);  
module_exit(cache_dst_and_sock_demo_fini);

在测试程序中,我缓存了路由项以及到达本机数据包的 socket,这样仅仅查询到 conntrack 就可以直接将路由和 socket 取出来了,取值的过程由于存在索引数组和索引的索引数组,因此就是数组下标寻址,不再需要查询。

前面说过很多次,conntrack 作为一中连接跟踪机制,如果它本身是可扩展的,那么将会是多么令人激动的一件事,当你看了 N 多文档代码之后,你发现它确实是可以扩展的,但是却没有感到激动,因为你可能发现:

1. 它可以注册一个 account 扩展,但是计数机制却很原始;
2. 我希望增加一个新型的扩展,却不得不重新编译内核;

怎么办?我曾经很生气地默默指责过当初实现这个的人,想当然的认为将扩展本身也做成可扩展的,而不是写死几个特定的扩展将是一个多么容易的事,我一直憋着没有去做这个实现,就是因为觉得它太简单,在工作中也确实需要一个新的扩展,然而既有的扩展类型中没有,为了不重新编译内核,我只好盗用了 acct 扩展。采用了一个 OO 中典型的封装方法:

struct my_ext {
    struct orig_ext;
    char info[0];
};


是时候改变一些事态了。基于下面几个原因,在周六的早上,我突然决定在周末完成它:
1. 外部因素:好不容易感冒了,作为一个羸弱的人,我不希望得到别人的同情,只需要获得周末的安静,感冒发烧是最好的选择;
2. 内部因素:年终总结完了,工作计划也确定了,后面是个收网的过程,稳为重,不需要太激进,因此也就没有什么技术上不可控的因素,心理安了,事就可以开始做了;
可能我又要笑话自己了,不就写个简单的模块么?怎么搞得跟诸葛孔明布阵一样 … 如此感性且主观一人怎么就 …
      不管怎么讲,这个模块看起来确实是简单的。然而一旦做起来,发现有两个比较严重的问题:
1. 反射内省问题
如果 conntrack 的 extend 有 128 个 slot,每个 slot 里面放一个私有数据。问题是,程序怎么知道哪个 slot 里面有哪个数据。程序有能力存储,但是程序自己却不知道这一点 … 这就是一个怪圈,你必须让数据成为自描述的,或者就规定死第 n 号 slot 必须放路由项,第 m 个 slot 必须放 socket… 现有的 nf conntrack 模块使用了后一种方法,即枚举 nf_ct_ext_id 做的事。
      可是我还是想随机选择 slot,这样更加灵活。自描述的数据结构也看了不好,ASN.1 太复杂,且内核数据更多的不是标识属性,而是定义一种行为,google 的 protocol buffer 也不是很合适,需要定义太多的回调函数来完成反射自省.. 后来我想了一个办法,那就是定义个索引蓝图,标识“slot 索引的索引”,而不是标识具体 slot 的位置。
      这就需要定义一个新的枚举,定义蓝图:

enum idx_idx{
ROUTE,
SOCKET,
AND_SO_ON,
IDX_IDX_NUM
};

然后定义一个数组来标识真正的索引:

int idx[IDX_IDX_NUM];

定义一个 bitmap 来表示 slot 的使用情况即可,具体的做法可以看代码,一目则了然。
2. 内存寻址问题
内核内存是宝贵的,不是说物理内存用不起,而是它的虚拟地址空间也是有限的,因此建议使用 64 位系统,如果是 32 位系统,如果希望内核保存比较大的数据结构,请在编译的时候按照 2G/2G 或者 1G/3G 来拆分地址空间,前者情况用户和内核各自占据 2G,后者的话内核占 3G,用户仅仅占 1G。
      也许就是因为存在这个内存问题,Linux 的 nf conntrack 限制了 extend 的内存使用,其最大长度字段数据类型是 u8。由于我知道我的系统,所以我将其改为了 u16。你必须要知道的是,nf connrtack 的 extend 内存使用时是连续的,你不能采用一个 sizeof(char *)大小的空间保存一个指针,然后这个指针指向一个超级大的连续空间 … 但是为什么不能呢?还是因为代码的普适问题,我了解我的系统,所以我可以使用保存指针的做法。另外我还保留了数组的方式,总之,数组和指针分工是明确的,数组用于 extend 的寻址,而指针用于数据的获取。
      代码包括一个框架和一个测试程序,内核还是 2.6.32 amd64,已经在 github 上了:https://github.com/marywangran/extension-of-nf_conntrack-ext
      还是在这里贴一份备份,怕哪天 github 被 wall 了 …

修改 include/net/netfilter/nf_conntrack_extend.h:

 

--- nf_conntrack_extend.h.orig  2014-03-29 12:55:26.000000000 +0800
+++ nf_conntrack_extend.h   2015-01-15 17:28:39.000000000 +0800
@@ -3,13 +3,17 @@

 #include <net/netfilter/nf_conntrack.h>

+#define NFCT_EXT_EXT
+
 enum nf_ct_ext_id
 {
    NF_CT_EXT_HELPER,
    NF_CT_EXT_NAT,
    NF_CT_EXT_ACCT,
    NF_CT_EXT_ECACHE,
-   NF_CT_EXT_NEW,
+#ifdef NFCT_EXT_EXT
+   NF_CT_EXT_EXT,
+#endif
    NF_CT_EXT_NUM,
 };

@@ -17,13 +21,21 @@
 #define NF_CT_EXT_NAT_TYPE struct nf_conn_nat
 #define NF_CT_EXT_ACCT_TYPE struct nf_conn_counter
 #define NF_CT_EXT_ECACHE_TYPE struct nf_conntrack_ecache
-#define NF_CT_EXT_NEW_TYPE struct nf_conntrack_new
+#ifdef NFCT_EXT_EXT
+#define NF_CT_EXT_EXT_TYPE struct nf_conntrack_ext
+#endif

 /* Extensions: optional stuff which isn't permanently in struct. */
 struct nf_ct_ext {
    struct rcu_head rcu;
+#ifdef NFCT_EXT_EXT
+   /* 内存不再是个事儿 */
+   u16 offset[NF_CT_EXT_NUM];
+   u16 len;
+#else
    u8 offset[NF_CT_EXT_NUM];
    u8 len;
+#endif
    char data[0];
 };

@@ -80,10 +92,18 @@
    unsigned int flags;

    /* Length and min alignment. */
+#ifdef NFCT_EXT_EXT
+   /* 内存不再是个事儿 */
+   u16 len;
+   u16 align;
+   /* initial size of nf_ct_ext. */
+   u16 alloc_size;
+#else
    u8 len;
    u8 align;
    /* initial size of nf_ct_ext. */
    u8 alloc_size;
+#endif
 };

 int nf_ct_extend_register(struct nf_ct_ext_type *type);

 

增加 include/net/netfilter/nf_conntrack_ext.h:

 

/*
 * (C) 2015 marywangran <marywangran@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#ifndef _NF_CONNTRACK_EXT_H
#define _NF_CONNTRACK_EXT_H
#include <net/net_namespace.h>
#include <linux/netfilter/nf_conntrack_common.h>
#include <linux/netfilter/nf_conntrack_tuple_common.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_extend.h>

#define MAX_EXT_SLOTS	8	
#define BITINT	1


struct nf_conntrack_ext {
	/* 必须有一个数组用于自省或者反射 */
	int	bits_idx[MAX_EXT_SLOTS];
	int	bits[BITINT];
	char *slot[MAX_EXT_SLOTS];
};


int nf_ct_exts_add(const struct nf_conn *ct, void *ext);

void *nf_ct_exts_get(const struct nf_conn *ct, int idx); 

void nf_ct_exts_remove(const struct nf_conn *ct, int idx);

struct nf_conntrack_ext *nf_conn_exts_find(const struct nf_conn *ct);

struct nf_conntrack_ext *nf_conn_exts_add(struct nf_conn *ct, gfp_t gfp);
extern int nf_conntrack_exts_init();
extern void nf_conntrack_exts_fini();

#endif /* _NF_CONNTRACK_EXT_H */

 

修改 net/netfilter/nf_conntrack_core.c:

 

--- nf_conntrack_core.c.orig    2014-03-29 13:00:17.000000000 +0800
+++ nf_conntrack_core.c 2015-01-15 17:01:28.000000000 +0800
@@ -42,6 +42,10 @@
 #include <net/netfilter/nf_conntrack_extend.h>
 #include <net/netfilter/nf_conntrack_acct.h>
 #include <net/netfilter/nf_conntrack_ecache.h>
+#ifdef NFCT_EXT_EXT
+/* 引入 extend 的 extend 头文件 */
+#include <net/netfilter/nf_conntrack_ext.h>
+#endif
 #include <net/netfilter/nf_nat.h>
 #include <net/netfilter/nf_nat_core.h>

@@ -644,8 +648,11 @@
    }

    nf_ct_acct_ext_add(ct, GFP_ATOMIC);
-
    nf_ct_ecache_ext_add(ct, GFP_ATOMIC);
+#ifdef NFCT_EXT_EXT
+   /* 在创建 conntrack 的时候初始化 extend 的 extend */
+   nf_conn_exts_add(ct, GFP_ATOMIC);
+#endif

    spin_lock_bh(&nf_conntrack_lock);
    exp = nf_ct_find_expectation(net, tuple);
@@ -1130,6 +1137,10 @@

    nf_ct_free_hashtable(net->ct.hash, net->ct.hash_vmalloc,
                 net->ct.htable_size);
+#ifdef NFCT_EXT_EXT
+   /* 析构 extend 的 extend */
+   nf_conntrack_exts_fini();
+#endif
    nf_conntrack_ecache_fini(net);
    nf_conntrack_acct_fini(net);
    nf_conntrack_expect_fini(net);
@@ -1344,9 +1355,19 @@
    ret = nf_conntrack_ecache_init(net);
    if (ret < 0)
        goto err_ecache;
+#ifdef NFCT_EXT_EXT
+   /* 注册 extend 的 extend */
+   ret = nf_conntrack_exts_init();
+   if (ret < 0)
+       goto err_exts;
+#endif

    return 0;

+#ifdef NFCT_EXT_EXT
+err_exts:
+   nf_conntrack_ecache_fini(net);
+#endif
 err_ecache:
    nf_conntrack_acct_fini(net);
 err_acct:

 

增加 net/netfilter/nf_conntrack_ext.c:

 

/* conntrack 扩展的扩展实现文件. */

/*
 * conntrack 扩展的扩展实现文件.
 * 技术核心:*		1. 位图
 *		2. 索引的索引数组(外部维护的一个‘蓝图’)
 * (C) 2015 marywangran <marywangran@126.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <net/netfilter/nf_conntrack_extend.h>
#include <net/netfilter/nf_conntrack_ext.h>

/* 这个 spin lock 应该和每一个 ext 绑定而不是全局的!*/
static DEFINE_SPINLOCK(nfct_ext_lock);

static struct nf_ct_ext_type ext_extend __read_mostly = {.len	= sizeof(struct nf_conntrack_ext),
	.align	= __alignof__(struct nf_conntrack_ext),
	.id	= NF_CT_EXT_EXT,
	.flags		= NF_CT_EXT_F_PREALLOC,
};

/* 
 * 增加一个数据到 extend 的 extend
 * 注意:需要自己在外部维护一个关于索引的索引的数组
 **/
int nf_ct_exts_add(const struct nf_conn *ct, void *ext)
{
	int ret_idx = -1;
	struct nf_conntrack_ext *exts = NULL;

	if (!ext) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (!exts) {goto out;}
	spin_lock(&nfct_ext_lock);
	ret_idx = find_first_zero_bit(exts->bits, MAX_EXT_SLOTS);
	if (ret_idx > MAX_EXT_SLOTS) {
		ret_idx = -1;
		spin_unlock(&nfct_ext_lock);
		goto out;
	}
	if (exts->slot[ret_idx]) {
		ret_idx = -1;
		spin_unlock(&nfct_ext_lock);
		goto out;
	}
	set_bit(ret_idx, exts->bits);
	exts->slot[ret_idx] = (char *)ext;
	spin_unlock(&nfct_ext_lock);
out:
	return ret_idx;
};
EXPORT_SYMBOL(nf_ct_exts_add);

/*
 * 根据 ID 的 index 获取保存在 conntrack 上的数据
 **/
void *nf_ct_exts_get(const struct nf_conn *ct, int idx)
{
	char *ret = NULL;
	struct nf_conntrack_ext *exts;

	if (idx > MAX_EXT_SLOTS || idx < 0) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (!exts) {goto out;}
	spin_lock(&nfct_ext_lock);
	if (! test_bit(idx, exts->bits)) {spin_unlock(&nfct_ext_lock);
		goto out;
	}
	ret = exts->slot[idx];
	spin_unlock(&nfct_ext_lock);
out:
	return (void *)ret;
}
EXPORT_SYMBOL(nf_ct_exts_get);

/*
 * 根据 ID 的 index 删除保存在 conntrack 上的数据
 **/
void nf_ct_exts_remove(const struct nf_conn *ct, int idx)
{
	struct nf_conntrack_ext *exts;
	if (idx > MAX_EXT_SLOTS || idx < 0) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (!exts) {goto out;}

	spin_lock(&nfct_ext_lock);
	if (! test_bit(idx, exts->bits)) {spin_unlock(&nfct_ext_lock);
		goto out;
	}
	clear_bit(idx, exts->bits);
	exts->slot[idx] = NULL;
	spin_unlock(&nfct_ext_lock);
out:
	return;
};
EXPORT_SYMBOL(nf_ct_exts_remove);

struct nf_conntrack_ext *nf_conn_exts_find(const struct nf_conn *ct)
{return nf_ct_ext_find(ct, NF_CT_EXT_EXT);
}
EXPORT_SYMBOL(nf_conn_exts_find);

struct nf_conntrack_ext *nf_conn_exts_add(struct nf_conn *ct, gfp_t gfp)
{
	struct nf_conntrack_ext *exts;

	exts = nf_ct_ext_add(ct, NF_CT_EXT_EXT, gfp);
	if (!exts) {printk("failed to add extensions area");
		return NULL;
	}

	/* 初始化 */
	{
		int i;
		for (i = 0; i < MAX_EXT_SLOTS; i++) {exts->bits_idx[i] = -1;
			exts->slot[i] = NULL;
		}
	}
	return exts;
}
EXPORT_SYMBOL(nf_conn_exts_add);

int nf_conntrack_exts_init()
{
	int ret;

	ret = nf_ct_extend_register(&ext_extend);
	if (ret < 0) {printk("nf_conntrack_ext: Unable to register extension\n");
		goto out;
	}
	printk("nf_conntrack_ext: register extension OK\n");

	return 0;
out:
	return ret;
}

void nf_conntrack_exts_fini()
{nf_ct_extend_unregister(&ext_extend);
}

 

测试程序nf_conntrack_private_data_auto_save_restore.c:

 

#include <linux/module.h>  
#include <linux/skbuff.h>  
#include <net/tcp.h>
#include <net/netfilter/nf_conntrack_ext.h>
  
MODULE_AUTHOR("marywangran");  
MODULE_LICENSE("GPL");  

/*
 * 必须定义一个用于自省的数组索引
 * 否则就会陷入“数据 - 元数据 - 元元数据 - 元元元数据...”的无限自指怪圈!* 这也是 AI 所面临的问题:自我意识是根本:being 知道某件事,并且 being 知道“being 知道某件事”,* 并且 being 知道“being 知道‘being 知道某件事’”...
 */
enum ext_idx_idx {
	CONN_ORIG_ROUTE,
	CONN_REPLY_ROUTE,
	CONN_SOCK, 
	CONN_AND_SO_ON, 
	NUM
};

static inline void
nf_ext_put_sock(struct sock *sk)
{if ((sk->sk_protocol == IPPROTO_TCP) && (sk->sk_state == TCP_TIME_WAIT)){inet_twsk_put(inet_twsk(sk));
	} else {sock_put(sk);
	}
}

static void
nf_ext_destructor(struct sk_buff *skb)
{
	struct sock *sk = skb->sk;
	skb->sk = NULL;
	skb->destructor = NULL;
	if (sk) {nf_ext_put_sock(sk);
	}
}

/* 缓存 socket 的 HOOK 函数 */
static unsigned int ipv4_conntrack_save_sock (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked) {goto out;}
	if ((ip_hdr(skb)->protocol != IPPROTO_UDP) && 
					(ip_hdr(skb)->protocol != IPPROTO_TCP)) {goto out;}
	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 缓存 socket,注意,只有 INPUT 的恢复缓存 socket 才有比较大的意义 */
		if (exts->bits_idx[CONN_SOCK] == -1) {if (skb->sk == NULL){goto out;}
			if ((ip_hdr(skb)->protocol == IPPROTO_TCP) && skb->sk->sk_state != TCP_ESTABLISHED) {goto out;}
			exts->bits_idx[CONN_SOCK] = nf_ct_exts_add(ct, skb->sk);
		}
	} 
out:
	return NF_ACCEPT;
}

/* 缓存路由项的 HOOK 函数 */
static unsigned int ipv4_conntrack_save_dst (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{  
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked) {goto out;}
	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 缓存路由。注意,有两个方向。IP 无方向,两个方向路由都要缓存 */
		int dir = CTINFO2DIR(ctinfo);  
		int idx = (dir == IP_CT_DIR_ORIGINAL)?CONN_ORIG_ROUTE:CONN_REPLY_ROUTE;
		if (exts->bits_idx[idx] == -1) {struct dst_entry *dst = skb_dst(skb);
			if (dst) {dst_hold(dst); 
				exts->bits_idx[idx] = nf_ct_exts_add(ct, dst);
			}
		} 
	} 
out:
	return NF_ACCEPT;  
}  

/* 获取缓存 socket 的 HOOK 函数 */
static unsigned int ipv4_conntrack_restore_sock (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{  
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked){goto out;}
	if ((ip_hdr(skb)->protocol != IPPROTO_UDP) && 
			(ip_hdr(skb)->protocol != IPPROTO_TCP)) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 获取缓存的 socket */
		if (exts->bits_idx[CONN_SOCK] != -1) {struct sock *sk = (struct sock *)nf_ct_exts_get(ct, exts->bits_idx[CONN_SOCK]);
			if (sk) {if ((ip_hdr(skb)->protocol == IPPROTO_TCP) && sk->sk_state != TCP_ESTABLISHED) {goto out;}
				if (unlikely(!atomic_inc_not_zero(&sk->sk_refcnt))) {goto out;}
				skb_orphan(skb);
				skb->sk = sk;
				/* 曾经在上面 atomic inc 了引用计数,等到转交给下任 owner 的时候,一定要 put */
				skb->destructor = nf_ext_destructor;
			}
		}
	}
out:
	return NF_ACCEPT;
}
  
/* 获取缓存路由项的 HOOK 函数 */
static unsigned int ipv4_conntrack_restore_dst (unsigned int hooknum,  
                                      struct sk_buff *skb,  
                                      const struct net_device *in,  
                                      const struct net_device *out,  
                                      int (*okfn)(struct sk_buff *))  
{  
	struct nf_conn *ct;  
	enum ip_conntrack_info ctinfo;  
	struct nf_conntrack_ext *exts;
	ct = nf_ct_get(skb, &ctinfo);  
	if (!ct || ct == &nf_conntrack_untracked) {goto out;}

	exts = nf_conn_exts_find(ct);
	if (exts) {  
		/* 获取缓存的路由 */
		int dir = CTINFO2DIR(ctinfo);  
		int idx = (dir == IP_CT_DIR_ORIGINAL)?CONN_ORIG_ROUTE:CONN_REPLY_ROUTE;
		if (exts->bits_idx[idx] != -1) {struct dst_entry *dst = (struct dst_entry *)nf_ct_exts_get(ct, exts->bits_idx[idx]);
			if (dst) {dst_hold(dst);
				skb_dst_set(skb, dst);
			}
		}  
	} 
out:
	return NF_ACCEPT;  
}  

/*
 * 总体图景:* OUTPUT:缓存 socket
 * INPUT:恢复 socket
 *
 * POSTROUTING|INPUT:缓存路由
 * PREROUTING:恢复路由
 */
static struct nf_hook_ops ipv4_conn_cache_ops[] __read_mostly = {  
	{  
		.hook           = ipv4_conntrack_save_dst,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_POST_ROUTING,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},  
	{  
		.hook           = ipv4_conntrack_save_sock,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_LOCAL_OUT,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},  
	{  
		.hook           = ipv4_conntrack_save_dst,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_LOCAL_IN,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},
	{  
		.hook           = ipv4_conntrack_restore_sock,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_LOCAL_IN,  
		.priority       = NF_IP_PRI_CONNTRACK + 2,  
	},
	{  
		.hook           = ipv4_conntrack_restore_dst,  
		.owner          = THIS_MODULE,  
		.pf             = NFPROTO_IPV4,  
		.hooknum        = NF_INET_PRE_ROUTING,  
		.priority       = NF_IP_PRI_CONNTRACK + 1,  
	},  
};  
  
static int __init cache_dst_and_sock_demo_init(void)  
{  
	int ret;  
	ret = nf_register_hooks(ipv4_conn_cache_ops, ARRAY_SIZE(ipv4_conn_cache_ops));  
	if (ret) {goto out;;}
	return 0;
out:	
	return ret;  
}  
  
static void __exit cache_dst_and_sock_demo_fini(void)  
{nf_unregister_hooks(ipv4_conn_cache_ops, ARRAY_SIZE(ipv4_conn_cache_ops));  
}  
  
module_init(cache_dst_and_sock_demo_init);  
module_exit(cache_dst_and_sock_demo_fini);

在测试程序中,我缓存了路由项以及到达本机数据包的 socket,这样仅仅查询到 conntrack 就可以直接将路由和 socket 取出来了,取值的过程由于存在索引数组和索引的索引数组,因此就是数组下标寻址,不再需要查询。

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-20发表,共计28752字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中