部落格:blog.focus-linux.net linuxfocus.blog.chinaunix.net
本文的copyleft歸[email protected]所有,使用GPL釋出,可以自由拷貝,轉載。但轉載請保持文檔的完整性,注明原作者及原連結,嚴禁用于任何商業用途。
======================================================================================================
在前面的netfilter源碼學習中,我學習了netfilter的基本架構,其這部分代碼看起來還是比較簡單的。後來在zhangyd6這位朋友的提示下,想起自己居然遺漏了netfilter作為防火牆的主要功能NAT。這部分代碼應該還比較有意思。今天開始看這部分代碼。
注:tuple即為五元組,源位址,目的位址,源端口,目的端口,和協定。
NAT規則的源代碼位于net/ipv4/netfilter/nf_nat_rule.c中。在函數nf_nat_rule_init注冊了兩個NAT的target。
ret = xt_register_target(&ipt_snat_reg);
if (ret != 0)
goto unregister_table;
ret = xt_register_target(&ipt_dnat_reg);
goto unregister_snat;
以ipt_snat_reg為例:
static struct xt_target ipt_snat_reg __read_mostly = {
.name = "SNAT",
.target = ipt_snat_target, //SNAT target的執行函數
.targetsize = sizeof(struct nf_nat_multi_range_compat),
.table = "nat",
.hooks = (1 NF_INET_POST_ROUTING) | (1 NF_INET_LOCAL_IN),
.checkentry = ipt_snat_checkentry, //添加SNAT規則的檢查函數
.family = AF_INET,
};
先看簡單的ipt_snat_checkentry
static int ipt_snat_checkentry(const struct xt_tgchk_param *par)
{
/*
這個為NAT的範圍,即IP,port等。雖然該結構體的名字為multi,但是目前隻支援一個range
該結構體的含義看其定義很明顯,這裡就說明了
*/
const struct nf_nat_multi_range_compat *mr = par->targinfo;
//隻支援一個範圍。
/* Must be a valid range */
if (mr->rangesize != 1) {
pr_info("SNAT: multiple ranges no longer supported\n");
return -EINVAL;
}
return 0;
}
Ok,那麼下面進入關鍵的函數ipt_snat_target
static unsigned int
ipt_snat_target(struct sk_buff *skb, const struct xt_action_param *par)
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
/* SNAT隻能應用于POST_ROUTING和LOCAL IN */
NF_CT_ASSERT(par->hooknum == NF_INET_POST_ROUTING ||
par->hooknum == NF_INET_LOCAL_IN);
/*
得到conn track的資訊,conn track為netfilter的一個基礎。留在以後學習。
目前我們隻需要知道netfilter儲存了資料包的連接配接資訊。
*/
ct = nf_ct_get(skb, &ctinfo);
/* Connection must be valid and new. */
這裡對conn進行了驗證。
要做SNAT,必須是建立的連接配接——很明顯的道理。
但是有的7層應用,可能需要多條相關的conn,這時就需要IP_CT_RELATED。
NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
ctinfo == IP_CT_RELATED IP_CT_IS_REPLY));
NF_CT_ASSERT(par->out != NULL);
return nf_nat_setup_info(ct, &mr->range[0], IP_NAT_MANIP_SRC);
進入nf_nat_setup_inifo
unsigned int
nf_nat_setup_info(struct nf_conn *ct,
const struct nf_nat_range *range,
enum nf_nat_manip_type maniptype)
struct net *net = nf_ct_net(ct);
struct nf_conntrack_tuple curr_tuple, new_tuple;
struct nf_conn_nat *nat;
int have_to_hash = !(ct->status & IPS_NAT_DONE_MASK);
/* nat helper or nfctnetlink also setup binding */
對于netfilter的nf_conn,其使用一個struct nf_ct_ext的結構來儲存各種extension資訊,如NAT。
這裡嘗試從ct中獲得nat。
注意ct中可以儲存多個extension
nat = nfct_nat(ct);
if (!nat) {
/*
沒有NAT的extension資訊,那麼就申請一個extension結構并儲存在ct中
*/
nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);
if (nat == NULL) {
pr_debug("failed to add NAT extension\n");
return NF_ACCEPT;
}
NF_CT_ASSERT(maniptype == IP_NAT_MANIP_SRC ||
maniptype == IP_NAT_MANIP_DST);
BUG_ON(nf_nat_initialized(ct, maniptype));
/* What we've got will look like inverse of reply. Normally
this is what is in the conntrack, except for prior
manipulations (future optimization: if num_manips == 0,
orig_tp =
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */
這裡要注意一點:通過reply方向的tuple資訊,來得到目前的tuple資訊,即發送方向的tuple。
那麼為什麼不直接使用ct的IP_CT_DIR_ORIGINAL的tuple資訊呢。
根據上面的注釋所說,一般情況下可以使用ORIGINAL方向的tuple資訊。但是如果num_manips不為0,
那麼original方向的tuple資訊就不能使用。因為original的資訊被修改了??
nf_ct_invert_tuplepr(&curr_tuple,
&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
得到SNAT後的tuple。get_unique_tuple留在後面學習。
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
/* SNAT後的tuple與之前的tuple不等,即tuple發生了變化 */
struct nf_conntrack_tuple reply;
/* Alter conntrack table so will recognize replies. */
/* tuple變化了,是以需要更新conntrack中的REPLY方向的tuple */
nf_ct_invert_tuplepr(&reply, &new_tuple);
nf_conntrack_alter_reply(ct, &reply);
/* Non-atomic: we own this at the moment. */
if (maniptype == IP_NAT_MANIP_SRC)
ct->status |= IPS_SRC_NAT;
else
ct->status |= IPS_DST_NAT;
/* Place in source hash if this is the first time. */
如注釋所說,第一次做NAT時将,ORIGINAL方向的tuple加入到hash表中。
雖然這裡隻是将tuple加入到hash表中,但是實際上我們可以從tuple得到conntrack結構。
不知道以後會不會有這樣的操作?
if (have_to_hash) {
unsigned int srchash;
srchash = hash_by_src(net, nf_ct_zone(ct),
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
spin_lock_bh(&nf_nat_lock);
/* nf_conntrack_alter_reply might re-allocate exntension aera */
nat = nfct_nat(ct);
nat->ct = ct;
hlist_add_head_rcu(&nat->bysource,
&net->ipv4.nat_bysource[srchash]);
spin_unlock_bh(&nf_nat_lock);
/* It's done. */
if (maniptype == IP_NAT_MANIP_DST)
set_bit(IPS_DST_NAT_DONE_BIT, &ct->status);
else
set_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);
return NF_ACCEPT;
明天再看get_unique_tuple,另外,似乎不把conn track搞定,看這部分代碼會有困惑。我來看看conn track,決定先研究一下哪個