1. 程式人生 > > Linux netfilter 學習筆記 之八 ip層netfilter的連線跟蹤模組初始化

Linux netfilter 學習筆記 之八 ip層netfilter的連線跟蹤模組初始化

基於linux2.6.21

在上一節中分析了連線跟蹤模組相關的資料結構,本節就開始分析連線跟蹤模組相關的初始化,下一節理解連線跟蹤模組的hook機制。

在分析連線跟蹤模組程式碼之前,先說明幾點:

1.連線跟蹤模組的helper結構能夠實現期望連線的建立以及相關協議的ALG功能。

2.連線跟蹤為NAT或者狀態防火牆的實現提供了依據

連線跟蹤模組的初始化過程分別在三個地方進行,一個用於註冊連線跟蹤模組相關的hook回撥函式;一個用於建立連線跟蹤項與期望連線項相關的slab快取;一個用於註冊連線跟蹤中協議相關的變數。

1.全域性初始化

1.1 nf_conntrack_init

主要是 nf_conntrack_init進行初始化操作,該函式定義在nf_conntrack_core.c中。

主要完成以下功能:

a.設定nf_conntrack_htable_sizenf_conntrack_max的值

b.nf_conntrack_hash申請記憶體並初始化

c.為連線跟蹤項與期望連線跟蹤項建立slab快取。

 
int __init nf_conntrack_init(void)
{
unsigned int i;
int ret;
 
當nf_conntrack_htable_size的值沒有設定時,則在記憶體小於1GB時,
使用記憶體的1/16384作為hash陣列的最大值;在記憶體大於1GB時
則最大hash陣列的值為8192
*/
if (!nf_conntrack_htable_size) {
nf_conntrack_htable_size
= (((num_physpages << PAGE_SHIFT) / 16384)
   / sizeof(struct list_head));
if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE))
nf_conntrack_htable_size = 8192;
if (nf_conntrack_htable_size < 16)
nf_conntrack_htable_size = 16;
}
 

/*
設定nf_conntrack_max的值,即連線跟蹤項的最大值。
該值取決於nf_conntrack_hash[]陣列的最大值,即為
nf_conntrack_htable_size的8倍。
*/
nf_conntrack_max = 8 * nf_conntrack_htable_size;
 
printk("nf_conntrack version %s (%u buckets, %d max)\n",
       NF_CONNTRACK_VERSION, nf_conntrack_htable_size,
       nf_conntrack_max);
/*
hash連結串列的初始化,申請nf_conntrack_htable_size個hash連結串列
*/
nf_conntrack_hash = alloc_hashtable(nf_conntrack_htable_size,
    &nf_conntrack_vmalloc);
if (!nf_conntrack_hash) {
printk(KERN_ERR "Unable to create nf_conntrack_hash\n");
goto err_out;
}
/*呼叫函式nf_conntrack_register_cache建立連線跟蹤項相關的slab快取*/
ret = nf_conntrack_register_cache(NF_CT_F_BASIC, "nf_conntrack:basic",
  sizeof(struct nf_conn), NULL);
if (ret < 0) {
printk(KERN_ERR "Unable to create nf_conn slab cache\n");
goto err_free_hash;
}
/*建立期望連線跟蹤項相關的slab快取*/
nf_conntrack_expect_cachep = kmem_cache_create("nf_conntrack_expect",
sizeof(struct nf_conntrack_expect),
0, 0, NULL, NULL);
if (!nf_conntrack_expect_cachep) {
printk(KERN_ERR "Unable to create nf_expect slab cache\n");
goto err_free_conntrack_slab;
}
 
/* 將nf_ct_l3protos中每一個成員的變數都設定成nf_conntrack_generic_l3proto
,然後不同的連線跟蹤的三層協議初始化時,即會將相應的
陣列成員的值替換掉*/
write_lock_bh(&nf_conntrack_lock);
        for (i = 0; i < PF_MAX; i++)
nf_ct_l3protos[i] = &nf_conntrack_generic_l3proto;
        write_unlock_bh(&nf_conntrack_lock);
 
/* For use by REJECT target */
ip_ct_attach = __nf_conntrack_attach;
 
/*將nf_conntrack_untracked的使用計數設定為1,使該資料連線項能不被刪除掉*/    
atomic_set(&nf_conntrack_untracked.ct_general.use, 1);
/*設定變數nf_conntrack_untracked的狀態為confirmed,當對一類資料包
不想進行連線跟蹤時,就會新增如下命令,
iptables -t raw -A PREROUTING -d x.x.x.x-j NOTRACK,這樣在資料包首先進入PRE_ROUTING
、OUTPUT鏈時,就會首先進入raw模組對應註冊的hook函式,並將
資料包的nfct指標指向儲存nf_conntrack_untracked的記憶體地址*/
set_bit(IPS_CONFIRMED_BIT, &nf_conntrack_untracked.status);
 
return ret;
 
err_free_conntrack_slab:
nf_conntrack_unregister_cache(NF_CT_F_BASIC);
err_free_hash:
free_conntrack_hash(nf_conntrack_hash, nf_conntrack_vmalloc,
    nf_conntrack_htable_size);
err_out:
return -ENOMEM;
}


1.2init_or_cleanup

nf_conntrack_standalone.c中定義的函式,主要是呼叫上1.1中介紹的函式進行初始化,然後在/proc/net檔案系統中建立相應的檔案以及在/proc/sys/net中建立連線跟蹤模組相關的檔案

/*

nf_conntrack_standalone的初始化與銷燬函式

對於初始化:

1.呼叫nf_conntrack_init進行連線模組相關的初始化,主要是

  設定連線跟蹤數的最大值、為連線跟蹤項或者期望連線跟蹤

  建立slab快取等操作。

2.proc/net目錄下建立連線跟蹤相關的檔案,主要nf_conntracknf_conntrack_expect

  檢視如下:

# cat /proc/net/stat/nf_conntrack 
entries  searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error  expect_new expect_create expect_delete
00000001  00000027 00000b0b 000035a4 000053e5 000049fb 000034d8 000003c3 000003ea 00000000 00000000 00000000 00000000  00000000 00000000 00000000 
00000001  00000000 000008e9 00000067 00007eec 00007d2b 00000132 0000008d 00000067 00000000 00000000 00000000 00000000  00000000 00000000 00000000 
# 
# cat /proc/net/nf_conntrack
/proc/net/nf_conntrack         /proc/net/nf_conntrack_expect
# cat /proc/net/nf_conntrack
ipv4     2 unknown  2 492 src=192.168.1.1 dst=224.0.0.1 [UNREPLIED] src=224.0.0.1 dst=192.168.1.1 use=2
# 
# 
# cat /proc/net/nf_conntrack_expect
# 
# 


3./proc/sys檔案系統中建立連線跟蹤相關的內容。

*/

static int init_or_cleanup(int init)
{
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *proc, *proc_exp, *proc_stat;
#endif
int ret = 0;
 
if (!init) goto cleanup;
 
ret = nf_conntrack_init();
if (ret < 0)
goto cleanup_nothing;
 
#ifdef CONFIG_PROC_FS
proc = proc_net_fops_create("nf_conntrack", 0440, &ct_file_ops);
if (!proc) goto cleanup_init;
 
proc_exp = proc_net_fops_create("nf_conntrack_expect", 0440,
&exp_file_ops);
if (!proc_exp) goto cleanup_proc;
 
proc_stat = create_proc_entry("nf_conntrack", S_IRUGO, proc_net_stat);
if (!proc_stat)
goto cleanup_proc_exp;
 
proc_stat->proc_fops = &ct_cpu_seq_fops;
proc_stat->owner = THIS_MODULE;
#endif
#ifdef CONFIG_SYSCTL
nf_ct_sysctl_header = register_sysctl_table(nf_ct_net_table, 0);
if (nf_ct_sysctl_header == NULL) {
printk("nf_conntrack: can't register to sysctl.\n");
ret = -ENOMEM;
goto cleanup_proc_stat;
}
#endif
 
return ret;
 
 cleanup:
#ifdef CONFIG_SYSCTL
  unregister_sysctl_table(nf_ct_sysctl_header);
 cleanup_proc_stat:
#endif
#ifdef CONFIG_PROC_FS
remove_proc_entry("nf_conntrack", proc_net_stat);
 cleanup_proc_exp:
proc_net_remove("nf_conntrack_expect");
 cleanup_proc:
proc_net_remove("nf_conntrack");
 cleanup_init:
#endif /* CNFIG_PROC_FS */
nf_conntrack_cleanup();
 cleanup_nothing:
return ret;
}
 


2 Ipv4 連線跟蹤模組Hook回撥函式的註冊

Ipv4協議中,註冊的連線跟蹤模組相關的回撥函式有如下幾個:

/*

在連線跟蹤PRE_ROUTING鏈上註冊的hook回撥函式,主要

是分段資料包進行重組。優先順序高於ipv4_conntrack_in

*/

static struct nf_hook_ops ipv4_conntrack_defrag_ops = {
.hook = ipv4_conntrack_defrag,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_PRE_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_DEFRAG,
};
/*PREROUTING鏈上註冊ipv4_conntrack_in,主要是實現為一條資料流
建立連線跟蹤項等操作*/
static struct nf_hook_ops ipv4_conntrack_in_ops = {
.hook = ipv4_conntrack_in,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_PRE_ROUTING,
.priority = NF_IP_PRI_CONNTRACK,
};
/*
在連線跟蹤LOCAL_OUT鏈上註冊的hook回撥函式,主要
是分段資料包進行重組。優先順序高於ipv4_conntrack_in
*/
 
static struct nf_hook_ops ipv4_conntrack_defrag_local_out_ops = {
.hook           = ipv4_conntrack_defrag,
.owner          = THIS_MODULE,
.pf             = PF_INET,
.hooknum        = NF_IP_LOCAL_OUT,
.priority       = NF_IP_PRI_CONNTRACK_DEFRAG,
};
/*LOCALOUT鏈上註冊ipv4_conntrack_in*/
/*LOCAL_OUT鏈上註冊ipv4_conntrack_local,主要是實現為本機發出的
一條資料流建立連線跟蹤項等操作*/
static struct nf_hook_ops ipv4_conntrack_local_out_ops = {
.hook = ipv4_conntrack_local,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_OUT,
.priority = NF_IP_PRI_CONNTRACK,
};
 
/* helpers */
/*
在POST_ROUTING鏈上註冊ipv4_conntrack_help,根據傳遞的資料包,
找到該資料包關聯的連線跟蹤項,然後執行該連線跟蹤
項關聯的helper函式,實現期望連線的建立以及ALG等功能。
*/
static struct nf_hook_ops ipv4_conntrack_helper_out_ops = {
.hook = ipv4_conntrack_help,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_HELPER,
};
/*
在LOCAL_IN鏈上註冊ipv4_conntrack_help,根據傳遞的資料包,
找到該資料包關聯的連線跟蹤項,然後執行該連線跟蹤
項關聯的helper函式,實現期望連線的建立以及ALG等功能。
*/
 
static struct nf_hook_ops ipv4_conntrack_helper_in_ops = {
.hook = ipv4_conntrack_help,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_CONNTRACK_HELPER,
};
 
 
/* Refragmenter; last chance. */
/*
在POST_ROUTING鏈上註冊ipv4_confirm。若傳遞的資料包關聯
的連線跟蹤項還沒有被確認,則執行確認操作
*/
static struct nf_hook_ops ipv4_conntrack_out_ops = {
.hook = ipv4_confirm,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK_CONFIRM,
};
/*
在PRE_ROUTING鏈上註冊ipv4_confirm。若傳遞的資料包關聯
的連線跟蹤項還沒有被確認,則執行確認操作
*/
 
static struct nf_hook_ops ipv4_conntrack_local_in_ops = {
.hook = ipv4_confirm,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_CONNTRACK_CONFIRM,
};
 


然後在nf_conntrack_l3proto_ipv4.cinit_or_cleanup裡,通過nf_register_hook將以上的nf_hook_ops新增到nf_hooks[][]陣列的相應的連結串列中。

3.三層協議、四層協議中連線模組相關的全域性變數定義

3.1 nf_conntrack_l3proto_ipv4 

定義ipv4協議相關的nf_conntrack_l3proto變數,其中比較重要的是ipv4_pkt_to_tuple

ipv4_invert_tupleipv4_tuple_to_nfattr、 ipv4_nfattr_to_tuple。下面一一分析之。

struct nf_conntrack_l3proto nf_conntrack_l3proto_ipv4 = {
.l3proto  = PF_INET,
.name  = "ipv4",
.pkt_to_tuple  = ipv4_pkt_to_tuple,
.invert_tuple  = ipv4_invert_tuple,
.print_tuple  = ipv4_print_tuple,
.print_conntrack = ipv4_print_conntrack,
.prepare  = ipv4_prepare,
.get_features  = ipv4_get_features,
#if defined(CONFIG_NF_CT_NETLINK) || \
    defined(CONFIG_NF_CT_NETLINK_MODULE)
.tuple_to_nfattr = ipv4_tuple_to_nfattr,
.nfattr_to_tuple = ipv4_nfattr_to_tuple,
#endif
.me  = THIS_MODULE,
};


3.1.1 ipv4_pkt_to_tuple

功能:根據ip頭部獲取源ip地址與目的ip地址,並寫入tuple變數中。

static int ipv4_pkt_to_tuple(const struct sk_buff *skb, unsigned int nhoff,
     struct nf_conntrack_tuple *tuple)
{
u_int32_t _addrs[2], *ap;
ap = skb_header_pointer(skb, nhoff + offsetof(struct iphdr, saddr),
sizeof(u_int32_t) * 2, _addrs);
if (ap == NULL)
return 0;
 
tuple->src.u3.ip = ap[0];
tuple->dst.u3.ip = ap[1];
 
return 1;
}


3.1.2 ipv4_invert_tuple

功能:根據原始的tuple的源ip、目的ip值,設定replytuple值,新的tuple值的源、目的ip值與原始的tuple的源ip、目的ip值是反過來的。

static int ipv4_invert_tuple(struct nf_conntrack_tuple *tuple,
   const struct nf_conntrack_tuple *orig)
{
tuple->src.u3.ip = orig->dst.u3.ip;
tuple->dst.u3.ip = orig->src.u3.ip;
 
return 1;
}
 


3.1.3 ipv4_prepare

/*

1. 計算資料包的三層資料部分相對於skb->data的偏移量。

2.獲取四層協議的協議號

*/

static int
ipv4_prepare(struct sk_buff **pskb, unsigned int hooknum, unsigned int *dataoff,
     u_int8_t *protonum)
{
/* Never happen */
if ((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) {
if (net_ratelimit()) {
printk(KERN_ERR "ipv4_prepare: Frag of proto %u (hook=%u)\n",
(*pskb)->nh.iph->protocol, hooknum);
}
return -NF_DROP;
}
*dataoff = (*pskb)->nh.raw - (*pskb)->data + (*pskb)->nh.iph->ihl*4;
/*獲取四層協議號*/
*protonum = (*pskb)->nh.iph->protocol;
 
return NF_ACCEPT;
}
 


3.1.4 ipv4_tuple_to_nfattr

功能:tuple結構中的三層源ip、目的ip地址按照nfnetlink規定的形式進行填充

static int ipv4_tuple_to_nfattr(struct sk_buff *skb,
const struct nf_conntrack_tuple *tuple)
{
NFA_PUT(skb, CTA_IP_V4_SRC, sizeof(u_int32_t),
&tuple->src.u3.ip);
NFA_PUT(skb, CTA_IP_V4_DST, sizeof(u_int32_t),
&tuple->dst.u3.ip);
return 0;
 
nfattr_failure:
return -1;
}


3.1.5 ipv4_nfattr_to_tuple

功能:nfnetlink訊息傳遞過來的變數,轉換成tuple結構中的三層源ip、目的ip地址,

static int ipv4_nfattr_to_tuple(struct nfattr *tb[],
struct nf_conntrack_tuple *t)
{
if (!tb[CTA_IP_V4_SRC-1] || !tb[CTA_IP_V4_DST-1])
return -EINVAL;
 
if (nfattr_bad_size(tb, CTA_IP_MAX, cta_min_ip))
return -EINVAL;
 
t->src.u3.ip =
*(u_int32_t *)NFA_DATA(tb[CTA_IP_V4_SRC-1]);
t->dst.u3.ip =
*(u_int32_t *)NFA_DATA(tb[CTA_IP_V4_DST-1]);
 
return 0;
}
 


然後在nf_conntrack_l3proto_ipv4.cinit_or_cleanup裡,通過nf_conntrack_l3proto_registernf_conntrack_l3proto_ipv4的地址存放在nf_ct_l3protos[PF_INET]

3.2 nf_conntrack_protocol_tcp4

定義tcp協議相關的nf_conntrack_protocol 變數,其中比較重要的是tcp_pkt_to_tuple

 tcp_invert_tuplenf_ct_port_tuple_to_nfattr、 nf_ct_port_nfattr_to_tuple。這幾個函式實現的功能與nf_conntrack_l3proto_ipv4中相應的函式相似,只不過ipv4中針對的是ip地址,此處針對的是埠號而已。

而函式tcp_packet主要是針對tcp協議的狀態變化而定義的,主要用於狀態防火牆的。

struct nf_conntrack_protocol nf_conntrack_protocol_tcp4 =
{
.l3proto = PF_INET,
.proto  = IPPROTO_TCP,
.name  = "tcp",
.pkt_to_tuple  = tcp_pkt_to_tuple,
.invert_tuple  = tcp_invert_tuple,
.print_tuple  = tcp_print_tuple,
.print_conntrack  = tcp_print_conntrack,
.packet  = tcp_packet,
.new  = tcp_new,
.error = tcp_error4,
#if defined(CONFIG_NF_CT_NETLINK) || \
    defined(CONFIG_NF_CT_NETLINK_MODULE)
.to_nfattr = tcp_to_nfattr,
.from_nfattr = nfattr_to_tcp,
.tuple_to_nfattr = nf_ct_port_tuple_to_nfattr,
.nfattr_to_tuple = nf_ct_port_nfattr_to_tuple,
#endif
};
 


然後在nf_conntrack_l3proto_ipv4.cinit_or_cleanup裡,通過nf_conntrack_protocol_registernf_conntrack_protocol_tcp4的地址存放在nf_ct_protos[PF_INET][TCP]

至此,分析完了連線跟蹤模組的初始化程式碼部分。