linux內核netfilter之ip conntrack模塊的作用舉例--nat和REDIRECT為例
修改應用層協議控制包使用了ip_conntrack,iptables的REDIRECT target也使用了ip_conntrack,另外包括iptables的state模塊也是如此,使用ip_conntrack,可見ip_conntrack的重要性,ip_conntrack的一個無比重要的作用是實現nat,可以說REDIRECT target和對諸如ftp的修改以實現server回連client最終都落實到了nat上,比如,所謂的REDIRECT就是內置一個nat規則,將符合matchs的包nat到本機的特定端口,這個和iptables的nat表原理是一樣的,不同的是,nat表的配置是顯式的nat,而REDIRECT和ip_nat_ftp是隱式的nat而已。它們都是nat,都依賴於原始的ip_conntrack,因此原始的鏈接流信息並沒有丟失,還是可以得到的,事實上,內核就是通過原始的鏈接流來匹配nat規則的,如果丟棄了原始鏈接流信息,何談匹配!如果一個原始鏈接是a->b,而後不管是顯式的nat還是隱式的REDIRECT以及nat_ftp,將a->b改為了a->c,a->b還是可以得到的,內核正是從a->b的流信息中取得了“要轉換為a->c”這個信息的。
在init_conntrack中有以下邏輯:
conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
conntrack->ct_general.destroy = destroy_conntrack;
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple; //初始化tuple,記錄連接地址端口信息,該tuple在nat後不會被改掉,此謂原始流
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].ctrack = conntrack;
conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple; //repl_tuple初始化成和tuple一樣的,在nat之後就會被改成nat後的地址,端口信息,此謂修改後的流,repl是replace的意思.
conntrack->tuplehash[IP_CT_DIR_REPLY].ctrack = conntrack;
tuple中就包含了原始流的信息,在隨後的nat表的查找後,在alloc_null_binding中初始化conntrack的nat信息,由此就將nat信息和原始流信息統一到了conntrack中了。resolve_normal_ct是ip_conntrack模塊使用的,其最後一句:
skb->nfct = &h->ctrack->infos[*ctinfo];
會將連接信息設置到skb中,以備後面的nat或者REDIRECT使用,在nat中,調用ip_conntrack_get取得這個conntrack。在ip_nat_setup_info中會調用ip_conntrack_alter_reply,後者會改變conntrack->tuplehash[IP_CT_DIR_REPLY].tuple的值為一個新的nat後的值。對於REDIRECT target,netfilter提供了一個getsockopt接口可以取得原始流的信息,該接口就是SO_ORIGINAL_DST,最終調用getorigdst,在getorigdst中有以下邏輯:
struct inet_opt *inet = inet_sk(sk);
struct ip_conntrack_tuple_hash *h;
struct ip_conntrack_tuple tuple;
IP_CT_TUPLE_U_BLANK(&tuple);
tuple.src.ip = inet->rcv_saddr; //原始流的源ip
tuple.src.u.tcp.port = inet->sport; //原始流的源端口
tuple.dst.ip = inet->daddr; //本機重定向後的ip
tuple.dst.u.tcp.port = inet->dport; //本機重定向後的端口
...
h = ip_conntrack_find_get(&tuple, NULL); //在既有鏈接中尋找一個ip_conntrack_tuple_hash的tuple字段和參數tuple一樣的,返回ip_conntrack_tuple_hash結構體
...
ip_conntrack_tuple_hash的定義如下:
struct ip_conntrack_tuple_hash
{
struct list_head list;
struct ip_conntrack_tuple tuple;
struct ip_conntrack *ctrack;
};
現在看一下ip_conntrack_find_get如何找到h,在REDIRECT target中,數據肯定要進入本機,而進入本機就要進入NF_IP_LOCAL_IN鏈,在NF_IP_LOCAL_IN鏈上註冊有一個ip_conntrack_local_in_ops,其HOOK函數為ip_confirm,最終要調用到__ip_conntrack_confirm,__ip_conntrack_confirm有以下邏輯:
unsigned int hash, repl_hash;
...
hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
...
list_prepend(&ip_conntrack_hash[hash], &ct->tuplehash[IP_CT_DIR_ORIGINAL]);
list_prepend(&ip_conntrack_hash[repl_hash], &ct->tuplehash[IP_CT_DIR_REPLY]);
最後的兩行將修改後的contrack信息的hash加入了ip_conntrack_hash表,在getorigdst調用ip_conntrack_find_get的時候,所使用的信息就是修改後的contrack信息,因此最終肯定能找到&ct->tuplehash[IP_CT_DIR_REPLY],而tuplehash[IP_CT_DIR_REPLY]和tuplehash[IP_CT_DIR_ORIGINAL]二者統一到了conntrack中,因此getorigdst的後半段:
sin.sin_port = h->ctrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.tcp.port;
sin.sin_addr.s_addr = h->ctrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
將可以得到原始流的信息。
最後看看redirect的HOOK函數:
static unsigned int redirect_target(...)
{
...
ct = ip_conntrack_get(*pskb, &ctinfo);
...
indev = (struct in_device *)(*pskb)->dev->ip_ptr;
newdst = indev->ifa_list->ifa_local;
newrange = ((struct ip_nat_multi_range)
{ 1, { { mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,
newdst, newdst,
mr->range[0].min, mr->range[0].max } } });
return ip_nat_setup_info(ct, &newrange, hooknum); //重定向到本機的地址轉換
}
__ip_conntrack_confirm函數不僅僅由NF_IP_LOCAL_IN鏈上的operations調用,在POSTROUTING鏈上也有調用,這就是ip_conntrack_out_ops中的HOOK函數ip_refrag,因此不管怎樣,只要發生了地址轉換,總逃不過將新的轉換後的流信息加入到conntrack中。
作罷!
再分享一下我老師大神的人工智能教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智能的隊伍中來!https://blog.csdn.net/jiangjunshow
linux內核netfilter之ip conntrack模塊的作用舉例--nat和REDIRECT為例