Linux系統如何平滑生效NAT BUGFIX

2021-06-17 18:25:09 字數 3669 閱讀 7116

在《

linux系統如何平滑生效nat》中,**有兩處問題。這只是目前發現的,沒有發現的還有很多很多,這就是我為何不一開始把**搞複雜的原因。

注意以下的**:

if (!nf_nat_initialized(ct, maniptype))  else

//nat已經設定進conn的情況

pf_debug("already setup manip %s for ct %p\n",

maniptype == ip_nat_manip_src ? "src" : "dst",

ct);

在nat已經設定進conn的情況下,僅僅列印了一行日誌,而判斷nat是否已經設定進conn的nf_nat_initialized實現正是兩個bit位,不同的hook點判斷位不同,而在我的原始實現中,是清除了alloc_null_binding設定的bit位,進而如果是alloc_null_binding將nat設定到了conn,也算沒有匹配到nat規則,最終繼續匹配,這樣就達到了「如果開始沒有配置任何nat時已經confirm了資料流,當配置好nat後能瞬時生效」的目的。

然而卻存在乙個問題,那就是當原來的nat規則改變了的時候,無法瞬時生效新nat規則的問題。這個問題比較難以解決,我先放置一邊,即使不考慮這個這個問題,光上述的**也有問題。何必在alloc_null_binding之後清除bit位呢?直接把「繼續查詢的邏輯放在那個else列印日誌的位置不就可以了麼?因此我還原了$k/net/ipv4/netfilter/nf_nat_standalone.c檔案的修改,也就是說nf_nat_rule_find函式不必再修改,而只需要修改nf_nat_fn,在else列印日誌的地方新增乙個goto即可,goto到if (!nf_nat_initialized(ct, maniptype))判斷的下一行,其餘的修改保持不變。

現在可以考慮已經是established狀態的conntrack的新nat規則及時生效的問題了,剛才之所以說它比較難,是因為這需要讓nf_nat_fn函式知道什麼時候有一條新的nat規則代替了舊的,而這種行為對於計算機而言,最終無非落實到的就是一系列的查詢與比較操作,這種事情做下來的話,還不如不再區分new與established狀態,乾脆每乙個資料報都執行一次nf_nat_rule_find算了,最終將完全失效linux nat實現的有狀態語義以及效率。因此這種高度策略化的配置應該由呼叫者來決定,所以我的修改方案就是增加乙個sysctl引數,如果使用者管理員想即使生效被改變的nat,那麼就將該引數設定為非0,以此來改變nat規則的匹配行為:

static unsigned int

nf_nat_fn(unsigned int hooknum,

struct sk_buff *skb,

const struct net_device *in,

const struct net_device *out,

int (*okfn)(struct sk_buff *))

}switch (ctinfo)

/* fall thru... (only icmps can be ip_ct_is_reply) */

//只要沒有返回包到來,能保證一直都是new

case ip_ct_new:

or local packets.. */

if (!nf_nat_initialized(ct, maniptype))

//以上沒有優化!!優化點在於:只有在非alloc_null_binding呼叫成功的情況

//下才會嘗試更新tuple的chain位置,否則不做無用功!

} else

break

default:

/* established */

nf_ct_assert(ctinfo == ip_ct_established ||

ctinfo == (ip_ct_established+ip_ct_is_reply));

//對於estableshed狀態的連線在sysctl引數為1的狀態下,強行進行nat規則查詢!

if ((ctinfo == ip_ct_established && ctinfo != (ip_ct_established+ip_ct_is_reply)) &&

nf_nat_slowpath)

...}

如果想對於已經生效nat的資料流改變nat策略的話,請設定sysctl_nf_nat_slowpath為1,持續$max_time後,將其設定回0。這是為何?因為不能破壞linux nat的原始邏輯以及不能影響效率!那麼max_time該怎麼選擇呢?當然是所有conntrack超時時間的最長者+10了,因為如果這麼長時間沒有資料報,conntrack超時被釋放,下乙個到來的包就是new了,如果恰好在這段時間期間有包,直接生效新的nat,加上5到10秒時間作為使用者態程式的時鐘校正,故而max_time設定的不多也不少。還有問題!

conntrack預設最長的時間是tcp的establish狀態,5天之久,這也太久了,因此就將所有的conntrack超時時間都縮短,最長的時間縮短為120秒,同時將nf_ct_tcp_be_liberal和nf_ct_tcp_loose兩個sysctl引數設定為1,撤銷tcp的詳細語義。

以上**的hlist_nulls_del_rcu,nf_conntrack_hash_insert等鍊錶操作涉及到了增和刪,一定要有鎖保護,但是我的**中沒有,這就是乙個明顯的bug,因此需要nf_conntrack_lock鎖的保護:

if (nf_ct_is_confirmed(ct)) {

struct net *net = nf_ct_net(ct);

spin_lock_bh(&nf_conntrack_lock);

//如果匹配到了新的規則,則更新tuple在chian中的位置。

hlist_nulls_del_rcu(&ct->tuplehash[ip_ct_dir_original].hnnode);

hlist_nulls_del_rcu(&ct->tuplehash[ip_ct_dir_reply].hnnode);

//如果不進行這個del and reinsert操作,那麼就會出現返回包無法被轉換

//成原始包的情形!

nf_conntrack_hash_insert(ct);

spin_unlock_bh(&nf_conntrack_lock);

...

本來我還想做的更完美一些的,就是說將delete/insert操作全部pending到乙個rcu序列裡面,因為我怕在delete和insert之間的空隙,同一流的資料報進入協議棧的conntrack_in,發生了find操作,這樣就會認為沒有找到tuple,進而重建乙個new狀態的conntrack,所以我想用rcu鎖進行保護,保證在沒有任何執行緒find這個雜湊的時候,才進行delete/insert操作,畢竟find操作真的是rcu鎖保護著呢!然而很快我就發現自己杞人憂天多此一舉了,如果發生上述的情況,新建立的new狀態的conntrack在離開協議棧的時候是不會被成功confirm的,因為在confirm的時候會進行一次find,如果已經find到了,就drop!如果進入confirm的時候,find之前,被delete的node還沒有insert進去怎麼辦?如果是我最初的那個版本,就完蛋了,然而剛剛進行了bugfix,不是有nf_conntrack_lock保護嗎,所以上面的情況是不會發生的。

Linux系統如何平滑生效NAT BUGFIX

在 linux系統如何平滑生效nat 中,有兩處問題。這只是目前發現的,沒有發現的還有很多很多,這就是我為何不一開始把 搞複雜的原因。注意以下的 if nf nat initialized ct,maniptype else nat已經設定進conn的情況 pf debug already setu...

Linux系統如何平滑生效NAT BUGFIX

在 linux系統如何平滑生效nat 中,有兩處問題。這只是目前發現的,沒有發現的還有很多很多,這就是我為何不一開始把 搞複雜的原因。注意以下的 if nf nat initialized ct,maniptype else nat已經設定進conn的情況 pf debug already setu...

Linux系統如何平滑生效NAT BUGFIX

在 linux系統如何平滑生效nat 中,有兩處問題。這只是目前發現的,沒有發現的還有很多很多,這就是我為何不一開始把 搞複雜的原因。注意以下的 if nf nat initialized ct,maniptype else nat已經設定進conn的情況 pf debug already setu...