Netfilter源码分析--6、NAT

六、NAT

6.1、简介

网络地址转换源(NAT)分为(Source NAT,SNAT)和目的NAT(Destination NAT, DNAT)2种不同的类型。SNAT是指修改数据包的源地址(改变连接的源IP)。SNAT会在数据包送出之前的最后一刻做好转工作。地址伪装 (Masquerading)是SNAT的一种特殊形式。DNAT 是指修改数据包的目标地址(改变连接的目的IP)。DNAT 总是在数据包进入以后立即转发,端口转发,负载平衡和透明代理都属于DNAT。下面介绍下NAT的流程:

6.1 NAT机制流程

这个流程需要说明的是,如果这是一个新的连接跟踪,那么hook操作会操作NAT表,寻找对应的规则进行处理,如果这个连接不是新的连接,即连接跟踪已经记录并且NAT已经更改过连接的源地址,那么hook操作会直接调用nf_nat_packet来修改报文的目的地址,这个不同于DNAT,它都是在SNAT中完成。也就是说netfilter中所有的NAT操作都可以通过一次NAT设置完成,也就是说NAT会自动帮我们判断另一个方向的报文。

进一步分析nf_nat_rule_find的工作流程如下:

 

6.2、重要数据结构

1、两种不同的对IP操作类型

enum nf_nat_manip_type

{

       IP_NAT_MANIP_SRC,//修改报文的源地址

       IP_NAT_MANIP_DST//修改报文的目的地址

};

1、  NAT的范围

主要是IP地址的范围和端口的范围。

struct nf_nat_range

{

       /* Set to OR of flags above. */

       unsigned int flags;

 

       /* Inclusive: network order. */

       __be32 min_ip, max_ip;//IP地址范围

 

       /* Inclusive: network order */

       union nf_conntrack_man_proto min, max;//端口范围

};

2、  内嵌在nf_conn的结构

此结构体内嵌在成员变量struct nf_ct_ext *ext中。

struct nf_conn_nat

{

       struct hlist_node bysource;

       struct nf_nat_seq seq[IP_CT_DIR_MAX];

       struct nf_conn *ct;

       union nf_conntrack_nat_help help;

#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \

    defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)

       int masq_index;

#endif

};

 

3、  协议相关结构

与连接跟踪机制类似,NAT也定义了一些与协议相关的操作。

struct nf_nat_protocol

{

       /* 协议号 */

       unsigned int protonum;

       struct module *me;

       /* 根据sk_buff和maniptype来设定tuple.

          Return true if succeeded. */

       bool (*manip_pkt)(struct sk_buff *skb,

                       unsigned int iphdroff,

                       const struct nf_conntrack_tuple *tuple,

                       enum nf_nat_manip_type maniptype);

       /* 判断所操作部分的范围 */

       bool (*in_range)(const struct nf_conntrack_tuple *tuple,

                      enum nf_nat_manip_type maniptype,

                      const union nf_conntrack_man_proto *min,

                      const union nf_conntrack_man_proto *max);

       /* 将协议相关部分设置到tuple当中*/

       bool (*unique_tuple)(struct nf_conntrack_tuple *tuple,

                          const struct nf_nat_range *range,

                          enum nf_nat_manip_type maniptype,

                          const struct nf_conn *ct);

 

       int (*range_to_nlattr)(struct sk_buff *skb,

                            const struct nf_nat_range *range);

       int (*nlattr_to_range)(struct nlattr *tb[],

                            struct nf_nat_range *range);

};

6.3、重要函数分析

/*此函数的主要作用就是根据iptable的设置NAT信息,为连接跟踪选择合适的ip

*和端口,并更改NAT对应的连接跟踪

*/

static void

get_unique_tuple(struct nf_conntrack_tuple *tuple,

               const struct nf_conntrack_tuple *orig_tuple,

               const struct nf_nat_range *range,

               struct nf_conn *ct,

               enum nf_nat_manip_type maniptype)

{

       struct net *net = nf_ct_net(ct);

       const struct nf_nat_protocol *proto;

       /* 1) If this srcip/proto/src-proto-part is currently mapped,

          and that same mapping gives a unique tuple within the given

          range, use that.

          This is only required for source (ie. NAT/masq) mappings.

          So far, we don't do local source mappings, so multiple

          manips not an issue.  */

       if (maniptype == IP_NAT_MANIP_SRC &&

           !(range->flags & IP_NAT_RANGE_PROTO_RANDOM)) {

              if (find_appropriate_src(net, orig_tuple, tuple, range)) {

                     pr_debug("get_unique_tuple: Found current src map\n");

                     if (!nf_nat_used_tuple(tuple, ct))

                            return;

              }

       }

 

       /*在给定范围内选择合适的ip */

       *tuple = *orig_tuple;

       find_best_ips_proto(tuple, range, ct, maniptype);

 

       /* 3) The per-protocol part of the manip is made to map into

          the range to make a unique tuple. */

 

       rcu_read_lock();

/*选择相关协议,确定协议相关部分*/

       proto = __nf_nat_proto_find(orig_tuple->dst.protonum);

 

       /* 如果iptables设定了随机选择端口*/

       if (range->flags & IP_NAT_RANGE_PROTO_RANDOM) {

              proto->unique_tuple(tuple, range, maniptype, ct);

              goto out;

       }

 

       /* 如果IPTABLES指定了端口的范围*/

       if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||

            proto->in_range(tuple, maniptype, &range->min, &range->max)) &&

           !nf_nat_used_tuple(tuple, ct))

              goto out;

 

       /* 对端口没有做任何指定 */

       proto->unique_tuple(tuple, range, maniptype, ct);

out:

       rcu_read_unlock();

}

6.4、应用实例

我们假设应用层的iptables有如下的NAT设置:

       iptables –t nat –A POSTROUTING –o eth0 –s192.168.0.0/24 –j SNAT –to 11.11.11.11

这条命令的意义在于将私用ip段:192.168.0.0/24,封包的源地址都改为公用ip:11.11.11.11,这个IP为NAT主机的ip。

现在我们假设有一台私用ip为:192.168.18.135的机器用临时端口45678来访问218.247.215.238的80端口。则连接跟踪建立如下的连接

ORIGINAL: 192.168.18.135.45678->218.247.215.238.80

REPLY: 218.247.215.238.80->192.168.18.135.45678

设定了SNAT后,我在POSTROUTING进入NAT的hook操作,并用SNAT的target做相应的处理,进入6.1流程的nf_nat_fn后,我们发现这个连接是一个新的连接,则遍历NAT表,发现设置了如上描述的规则,并且此私用IP符合我们的设定的私用ip范围,故对此报文做SNAT处理,在流程图6.2的nf_nat_setup_info

中,我们调用get_unique_tuple获得一个唯一的tuple(符合上面iptables所设定的),我们通过find_best_ips_proto在指定的ip范围内寻找合适的ip,本例中只使用了唯一的公用IP:11.11.11.11,则合适的IP就为这个。在协议相关的成员函数unique_tuple中我们找到一个端口,同时判断新的tuple是否在连接跟中存在,如果存在,在换另一个端口,这样一个新的tuple被建立起来了他为:

REPLY: 218.247.215.238.80-11.11.11.11.56789(这个端口为NAT主机上的端口)

同时我们更改连接跟踪的REPLY tuple为如上的tuple,这样连接跟踪建立的新连接如下:

ORIGINAL: 192.168.18.135.45678->218.247.215.238.80

REPLY: 218.247.215.238.80-11.11.11.11.56789

这个封包发送到主机218.247.215.238后,218.247.215.238后发送一个确认的数据包,连接为:

218.247.215.238.80-11.11.11.11.56789

NAT主机11.11.11.11收到这个封包后,连接跟踪发现这个连接已经被记录,不做新的记录,同时设定ctinfo为:IP_CT_RELATED+IP_CT_IS_REPLY,NAT发现这个数据包的信息为这个时,直接调用nf_nat_packet,而不是去查NAT表所对应的规则,nf_nat_packet

中会有连接的相反方向,即本例中为REPLY封包,相反为ORINGAL,修改相应的报文。即将数据包中的11.11.11.11.56789再次替换成192.168.18.135.45678,NAT主机将这个数据包转发给192.168.18.135,这就完成了一次SNAT操作。

 

作者: jazy333   发布时间: 2011-01-05