作者:gfree.wind@gmail.com
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
本文的copyleft归gfree.wind@gmail.com所有,使用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);
-
if (ret != 0)
- 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;
-
const struct nf_nat_multi_range_compat *mr = par->targinfo;
/* 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,决定先研究一下哪个