作者: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,决定先研究一下哪个