六、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操作。