在加载nf_conntrack模块的时候,可以通过参数hashsize控制conntrack的最大数量。
module_param_call(hashsize, nf_conntrack_set_hashsize, param_get_uint,
&nf_conntrack_htable_size, 0600);
如下,指定参数hashsize为1,系统计算出的conntrack最大数量为4096。
# modprobe nf_conntrack hashsize=1
#
# cat /proc/sys/net/netfilter/nf_conntrack_max
4096
处理函数中,模块参数hashsize只能在init_net命名空间设置。由于模块加载时nf_conntrack_hash为空,直接将hashsize的值赋予nf_conntrack_htable_size(1)。
int nf_conntrack_set_hashsize(const char *val, const struct kernel_param *kp)
{
unsigned int hashsize;
int rc;
if (current->nsproxy->net_ns != &init_net)
return -EOPNOTSUPP;
/* On boot, we can set this without any fancy locking. */
if (!nf_conntrack_hash)
return param_set_uint(val, kp);
在模块初始化函数中,由函数nf_ct_alloc_hashtable将对nf_conntrack_htable_size值进行修正,最大conntrack数量设置为哈希大小的8倍,即512*8=4096。
int nf_conntrack_init_start(void)
{
int max_factor = 8;
nf_conntrack_hash = nf_ct_alloc_hashtable(&nf_conntrack_htable_size, 1);
if (!nf_conntrack_hash)
return -ENOMEM;
nf_conntrack_max = max_factor * nf_conntrack_htable_size;
在函数nf_ct_alloc_hashtable中,将模块参数指定的hashsize(sizep),调整为一个页面可容纳的hlist_nulls_head结构的整数倍。即(PAGE_SIZE / sizeof(struct hlist_nulls_head))的整数倍。这样所分配的所有hlist_nulls_head结构将占用完整的页面。
void *nf_ct_alloc_hashtable(unsigned int *sizep, int nulls)
{
struct hlist_nulls_head *hash;
unsigned int nr_slots, i;
if (*sizep > (UINT_MAX / sizeof(struct hlist_nulls_head)))
return NULL;
BUILD_BUG_ON(sizeof(struct hlist_nulls_head) != sizeof(struct hlist_head));
nr_slots = *sizep = roundup(*sizep, PAGE_SIZE / sizeof(struct hlist_nulls_head));
hash = kvcalloc(nr_slots, sizeof(struct hlist_nulls_head), GFP_KERNEL);
if (hash && nulls)
for (i = 0; i < nr_slots; i++)
INIT_HLIST_NULLS_HEAD(&hash[i], i);
return hash;
按照PAGE_SIZE为4K,hlist_nulls_head结构大小为8字节,可得一个页面容纳512个此结构,如下nf_conntrack_buckets的值,512个哈希桶。
# getconf PAGE_SIZE
4096
#
# cat /proc/sys/net/netfilter/nf_conntrack_buckets
512
#
# cat /proc/sys/net/netfilter/nf_conntrack_max
4096
如果在模块加载时没有指定hashsize参数,内核将根据内存大小计算哈希表的大小。nf_conntrack_htable_size等于总内存的1/16k,除以hlist_head结构体的大小。
int nf_conntrack_init_start(void)
{
unsigned long nr_pages = totalram_pages();
int max_factor = 8;
if (!nf_conntrack_htable_size) {
/* Idea from tcp.c: use 1/16384 of memory.
* On i386: 32MB machine has 512 buckets.
* >= 1GB machines have 16384 buckets.
* >= 4GB machines have 65536 buckets.
*/
nf_conntrack_htable_size
= (((nr_pages << PAGE_SHIFT) / 16384)
/ sizeof(struct hlist_head));
对于1G内存,依据以上公式可得:1024 * 1024 * 1024 / 16384 / 8 = 16384; 对于4G内存,可得到哈希表大小为65536。以下规定当内存大于4G时,哈希大小固定为65536。而对于大于1G小于4G内存的情况,哈希大小固定使用16384。最后,nf_conntrack_htable_size的最小值为32。
nf_conntrack_max为nf_conntrack_htable_size值的4倍。
if (nr_pages > (4 * (1024 * 1024 * 1024 / PAGE_SIZE)))
nf_conntrack_htable_size = 65536;
else if (nr_pages > (1024 * 1024 * 1024 / PAGE_SIZE))
nf_conntrack_htable_size = 16384;
if (nf_conntrack_htable_size < 32)
nf_conntrack_htable_size = 32;
/* Use a max. factor of four by default to get the same max as
* with the old struct list_heads. When a table size is given
* we use the old value of 8 to avoid reducing the max.
* entries. */
max_factor = 4;
}
nf_conntrack_hash = nf_ct_alloc_hashtable(&nf_conntrack_htable_size, 1);
if (!nf_conntrack_hash)
return -ENOMEM;
nf_conntrack_max = max_factor * nf_conntrack_htable_size;
内核版本 5.10