linux內核netfilter連接跟蹤的hash算法


linux內核netfilter連接跟蹤的hash算法

 

linux內核中的netfilter是一款強大的基於狀態的防火牆,具有連接跟蹤(conntrack)的實現。conntracknetfilter的核心,許多增強的功能,例如,地址轉換(NAT),基於內容的業務識別(l7 layer-7 module)都是基於連接跟蹤。然而,netfilter的性能還有很多值得改進的地方。

netfilter的連接跟蹤的hash算法是在Bob Jenkinslookup2.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);

}

 

這是一個對於ipv6ipv4hash求值的通用實現。struct nf_conntrack_tuple是一個通用的連接的四元組,同時用於ipv4ipv6tcpudpsctpicmp協議,所以,其定義比較復雜。可以把它理解為源地址,源端口,目的地址,目的端口。

#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;

};

有些混亂,就是源地址和目的地址,protonumdir不知道為什么這么定義?

 

 

上面的hash算法在僅用於ipv4時,可以進行優化。jhash函數是通用的hash函數,上面的目的是把ipv6的長串字符hash為一個32位整數,而ipv4的情況下,可以不用。

 

最后,使用%運算,這是非常低效的,Bob Jenkins專門指出了這一點。由於table的大小都為2的次方,所以,可以使用&的算法。

 

另外,我認為Bob Jenkins的算法是對於通用的數字的hash算法,對於tcp連接這樣比較特殊的數字的hash,使用這么復雜的算法,是否有意義?簡單的加法運算是否更有效率?

 

lookup3.clookup2.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;

}

優化的版本,對332位整數進行hash運算,得到一個32位數字。

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

{

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

}

232位整數進行hash運算,得到一個32位數字。

 

static inline u32 jhash_1word(u32 a, u32 initval)

{

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

}

132位整數進行hash運算,得到一個32位數字。

 

 

#define mix(a,b,c) /
{ /
  a -= c;  a ^= rot(c, 4);  c += b; /
  b -= a;  b ^= rot(a, 6);  a += c; /
  c -= b;  c ^= rot(b, 8);  b += a; /
  a -= c;  a ^= rot(c,16);  c += b; /
  b -= a;  b ^= rot(a,19);  a += c; /
  c -= b;  c ^= rot(b, 4);  b += a; /
}

 

#define final(a,b,c) /
{ /
  c ^= b; c -= rot(b,14); /
  a ^= c; a -= rot(c,11); /
  b ^= a; b -= rot(a,25); /
  c ^= b; c -= rot(b,16); /
  a ^= c; a -= rot(c,4);  /
  b ^= a; b -= rot(a,14); /
  c ^= b; c -= rot(b,24); /
}

 

上面的兩個宏這是lookup3.c的核心hash算法,hash的基礎。

 

uint32_t hashword(
const uint32_t *k,                   /* the key, an array of uint32_t values */
size_t          length,               /* the length of the key, in uint32_ts */
uint32_t        initval)         /* the previous hash, or an arbitrary value */
{
  uint32_t a,b,c;
 
  /* Set up the internal state */
  a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval;
 
  /*------------------------------------------------- handle most of the key */
  while (length > 3)
  {
    a += k[0];
    b += k[1];
    c += k[2];
    mix(a,b,c);
    length -= 3;
    k += 3;
  }
 
  /*------------------------------------------- handle the last 3 uint32_t's */
  switch(length)                     /* all the case statements fall through */
  { 
  case 3 : c+=k[2];
  case 2 : b+=k[1];
  case 1 : a+=k[0];
    final(a,b,c);
  case 0:     /* case 0: nothing left to add */
    break;
  }
  /*------------------------------------------------------ report the result */
  return c;
}
 

hashword是通用的hash算法,用於計算任意cpu架構,任意長度的字符串的hash值。

 

不斷的把輸入的串k,每隔3位進行mix,直到完畢。返回final

 

對於ipv4的話,可以直接把源地址,目的地址,(源端口<< 16)|目的端口,這三個整數進行final,得到hash值。

 

對於ip地址和端口號的特點,這種復雜的算法是否真的有更好的hash效果,我持懷疑態度。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM