linux内核netfilter连接跟踪的hash算法

linux内核中的netfilter是一款强大的基于状态的防火墙,具有连接跟踪(conntrack)的实现。conntrack是netfilter的核心,许多增强的功能,例如,地址转换(NAT),基于内容的业务识别(l7, layer-7 module)都是基于连接跟踪。然而,netfilter的性能还有很多值得改进的地方。

netfilter的连接跟踪的hash算法是在Bob Jenkins的lookup2.c基础上的改进实现,Bob Jenkins已经推出lookup3.c的实现,见地址:http://burtleburtle.net/bob/hash/和http://burtleburtle.net/bob/c/lookup3.c

netfilter中的hash求值的代码如下:

static u_int32_t __hash_conntrack(const struct nf_conntrack_tuple *tuple,

unsigned int size, unsigned int rnd)

{

unsigned int a, b;

a = jhash((void *)tuple->src.u3.all, sizeof(tuple->src.u3.all),

((tuple->src.l3num) << 16) | tuple->dst.protonum);

b = jhash((void *)tuple->dst.u3.all, sizeof(tuple->dst.u3.all),

(tuple->src.u.all << 16) | tuple->dst.u.all);

return jhash_2words(a, b, rnd) % size;

}

static inline u_int32_t hash_conntrack(const struct nf_conntrack_tuple *tuple)

{

return __hash_conntrack(tuple, nf_conntrack_htable_size,

nf_conntrack_hash_rnd);

}

这是一个对于ipv6和ipv4的hash求值的通用实现。struct nf_conntrack_tuple是一个通用的连接的四元组,同时用于ipv4和ipv6,tcp,udp,sctp,icmp协议,所以,其定义比较复杂。可以把它理解为源地址,源端口,目的地址,目的端口。

#define NF_CT_TUPLE_L3SIZE 4

union nf_conntrack_man_l3proto {

u_int32_t all[NF_CT_TUPLE_L3SIZE];

u_int32_t ip;

u_int32_t ip6[4];

};

其实这就是ip地址。

union nf_conntrack_man_proto

{

/* Add other protocols here. */

u_int16_t all;

struct {

u_int16_t port;

} tcp;

struct {

u_int16_t port;

} udp;

struct {

u_int16_t id;

} icmp;

struct {

u_int16_t port;

} sctp;

};

这就是端口。

struct nf_conntrack_man

{

union nf_conntrack_man_l3proto u3;

union nf_conntrack_man_proto u;

/* Layer 3 protocol */

u_int16_t l3num;

};

目的地址和端口,l3num不知道是什么东西?

struct nf_conntrack_tuple

{

struct nf_conntrack_man src;

/* These are the parts of the tuple which are fixed. */

struct {

union {

u_int32_t all[NF_CT_TUPLE_L3SIZE];

u_int32_t ip;

u_int32_t ip6[4];

} u3;

union {

/* Add other protocols here. */

u_int16_t all;

struct {

u_int16_t port;

} tcp;

struct {

u_int16_t port;

} udp;

struct {

u_int8_t type, code;

} icmp;

struct {

u_int16_t port;

} sctp;

} u;

/* The protocol. */

u_int8_t protonum;

/* The direction (for tuplehash) */

u_int8_t dir;

} dst;

};

有些混乱,就是源地址和目的地址,protonum和dir不知道为什么这么定义?

上面的hash算法在仅用于ipv4时,可以进行优化。jhash函数是通用的hash函数,上面的目的是把ipv6的长串字符hash为一个32位整数,而ipv4的情况下,可以不用。

最后,使用%运算,这是非常低效的,Bob Jenkins专门指出了这一点。由于table的大小都为2的次方,所以,可以使用&的算法。

另外,我认为Bob Jenkins的算法是对于通用的数字的hash算法,对于tcp连接这样比较特殊的数字的hash,使用这么复杂的算法,是否有意义?简单的加法运算是否更有效率?

lookup3.c与lookup2.c有很大的不同。lookup3.c中,使用了final宏,和mix宏分开。而lookup2.c中没有使用final宏。

linux下的修改过的hash函数:

static inline u32 jhash(const void *key, u32 length, u32 initval)

通用的hash函数,对任意长度的key字符串进行hash运算,得到一个32位数字。

static inline u32 jhash2(u32 *k, u32 length, u32 initval)

优化的版本,对任意长度的32位整数进行hash运算,得到一个32位数字。

static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval)

{

a += JHASH_GOLDEN_RATIO;

b += JHASH_GOLDEN_RATIO;

c += initval;

__jhash_mix(a, b, c);

return c;

}

优化的版本,对3个32位整数进行hash运算,得到一个32位数字。

static inline u32 jhash_2words(u32 a, u32 b, u32 initval)

{

return jhash_3words(a, b, 0, initval);

}

对2个32位整数进行hash运算,得到一个32位数字。

static inline u32 jhash_1word(u32 a, u32 initval)

{

return jhash_3words(a, 0, 0, initval);

}

对1个32位整数进行hash运算,得到一个32位数字。

linux内核netfilter连接跟踪的hash算法

相关推荐