linux核心netfilter連接配接跟蹤的hash算法
linux核心中的netfilter是一款強大的基于狀态的防火牆,具有連接配接跟蹤(conntrack)的實作。conntrack是netfilter的核心,許多增強的功能,例如,位址轉換(NAT),基于内容的業務識别(l7, layer-7 module)都是基于連接配接跟蹤。然而,netfilter的性能還有很多值得改進的地方。
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;
} udp;
u_int16_t id;
} icmp;
} 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. */
union {
u_int32_t all[NF_CT_TUPLE_L3SIZE];
u_int32_t ip;
u_int32_t ip6[4];
} u3;
/* Add other protocols here. */
u_int16_t all;
struct {
u_int16_t port;
} tcp;
} udp;
u_int8_t type, code;
} icmp;
} 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位數字。
上面的兩個宏這是lookup3.c的核心hash算法,hash的基礎。
hashword是通用的hash算法,用于計算任意cpu架構,任意長度的字元串的hash值。
不斷的把輸入的串k,每隔3位進行mix,直到完畢。傳回final。
對于ipv4的話,可以直接把源位址,目的位址,(源端口<< 16)|目的端口,這三個整數進行final,得到hash值。
對于ip位址和端口号的特點,這種複雜的算法是否真的有更好的hash效果,我持懷疑态度。