Linux netfilter 学习笔记 之十二 ip层netfilter的NAT模块代码分析


本节主要是分析NAT模块相关的hook函数与target函数,主要是理清NAT模块实现的原理等。

 

1.NAT相关的hook函数分析

NAT模块主要是在NF_IP_PREROUTINGNF_IP_POSTROUTINGNF_IP_LOCAL_OUTNF_IP_LOCAL_IN四个节点上进行NAT操作,在上一节中我们知道nat表中只有PREROUTINGPOSTROUTINGLOCAL_OUT三条链,而没有NF_IP_LOCAL_IN链,所以不能创建在LOCAL_IN hook点的SNAT操作。

NAT模块在注册hook函数时又在LOCAL_IN点注册了hook函数,且hook函数也调用了NAT转换的通用处理函数,难道也要对LOCAL_IN的数据包进行NAT转换吗?

其实,在LOCAL_IN注册hook函数主要不是为了进行NAT转换,因为在系统为一个源ipA的转发数据包进行了SNAT后,可能会对源端口获取一个随机的值,这时如果源ipA的数据包要发送给网关时,可能源端口就是刚才NAT转换的那个源端口,此时为了保证连接跟踪项的原始方向的tuple变量的唯一性,就需要在LOCAL_INhook点通过调用NAT转换的通用处理函数,改变源端口值,重新获取一个新的唯一的且未被使用的tuple变量。这应该就是LOCAL_IN也需要hook回调函数的原因吧。

1.1 nf_nat_in

 

这个函数是NAT模块在PRE_ROUTING hook点上注册的回调函数,该函数主要是实现DNAT功能,该函数的定义如下,主要实现如下两个功能:

1. 调用函数ip_nat_fn实现DNAT转换

2.当转换后数据包的目的ip地址改变后,需要调用dst_release,将skbdst_entry的引用减一,然后将skb->dst置为NULL

 

static unsigned int
nf_nat_in(unsigned int hooknum,
  struct sk_buff **pskb,
  const struct net_device *in,
  const struct net_device *out,
  int (*okfn)(struct sk_buff *))
{
unsigned int ret;
__be32 daddr = (*pskb)->nh.iph->daddr;
 
ret = nf_nat_fn(hooknum, pskb, in, out, okfn);
if (ret != NF_DROP && ret != NF_STOLEN &&
    daddr != (*pskb)->nh.iph->daddr) {
dst_release((*pskb)->dst);
(*pskb)->dst = NULL;
}
return ret;
}


 

该函数主要是通过调用nf_nat_fn,该函数是一个通用NAT转换函数,待会着重分析这个函数

1.2 nf_nat_out

这个函数是NAT模块在POST_ROUTING hook点的hook回调函数,该函数实现如下功能:

1. 调用函数ip_nat_fn实现SNAT转换

static unsigned int
nf_nat_out(unsigned int hooknum,
   struct sk_buff **pskb,
   const struct net_device *in,
   const struct net_device *out,
   int (*okfn)(struct sk_buff *))
{
#ifdef CONFIG_XFRM
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
#endif
unsigned int ret;
 
/* root is playing with raw sockets. */
if ((*pskb)->len < sizeof(struct iphdr) ||
    (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
return NF_ACCEPT;
 
ret = nf_nat_fn(hooknum, pskb, in, out, okfn);
#ifdef CONFIG_XFRM
if (ret != NF_DROP && ret != NF_STOLEN &&
    (ct = nf_ct_get(*pskb, &ctinfo)) != NULL) {
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 
if (ct->tuplehash[dir].tuple.src.u3.ip !=
    ct->tuplehash[!dir].tuple.dst.u3.ip
    || ct->tuplehash[dir].tuple.src.u.all !=
       ct->tuplehash[!dir].tuple.dst.u.all
    )
return ip_xfrm_me_harder(pskb) == 0 ? ret : NF_DROP;
}
#endif
return ret;
}


这个函数同样是调用函数nf_nat_fn实现SNAT转换

1.3nf_nat_local_fn

这个函数是NAT模块在OUTPUT hook点的hook回调函数,该函数实现如下功能:

 

功能:实现DNAT转换功能

1. 调用函数ip_nat_fn实现DNAT转换

2.调用ip_route_me_harder,重新进行路由操作(与PRE_ROUTING不同的是,对于 OUTPUThook回调函数,当目的地址改变后,需要在该函数里调用 ip_route_me_harder重新查找路由,而在PRE_ROUTING链中,则是将skb->dst置为空, 然后在数据包往下执行时会自行重新查找路由。OUTPUT链接收的数据均是已经路由 的数据包,且后续调用函数中不会再有查找路由的操作,所以要nf_nat_out里实现路由 查找。)。

 

static unsigned int
nf_nat_local_fn(unsigned int hooknum,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
unsigned int ret;
 
/* root is playing with raw sockets. */
if ((*pskb)->len < sizeof(struct iphdr) ||
    (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
return NF_ACCEPT;
 
ret = nf_nat_fn(hooknum, pskb, in, out, okfn);
if (ret != NF_DROP && ret != NF_STOLEN &&
    (ct = nf_ct_get(*pskb, &ctinfo)) != NULL) {
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 
if (ct->tuplehash[dir].tuple.dst.u3.ip !=
    ct->tuplehash[!dir].tuple.src.u3.ip) {
if (ip_route_me_harder(pskb, RTN_UNSPEC))
ret = NF_DROP;
}
#ifdef CONFIG_XFRM
else if (ct->tuplehash[dir].tuple.dst.u.all !=
 ct->tuplehash[!dir].tuple.src.u.all)
if (ip_xfrm_me_harder(pskb))
ret = NF_DROP;
#endif
}
return ret;
}


这个函数其实也是调用函数nf_nat_fn实现NAT转换的。

1.4 LOCAL_IN hook

NAT模块在NF_LOCAL_INhook回调函数就是直接调用nf_nat_fn,此处需要注意以下信息:

对于NF_LOCAL_IN链来说,因为nat表中并没有INPUT链,所以对于NF_LOCAL_IN点来说,并不会修改数据包的ip地址,也就是调用alloc_null_binding实现NAT转换,最大的可能就是修改数据包的源端口号,以实现数据连接跟踪项的replynf_conntrack_tuple变量是唯一的,且没有被其他连接跟踪项使用。

这也就是为什么需要在NF_LOCAL_IN HOOK点注册HOOK回调函数而又没有在nat表中注册INPUT链的原因。

 

1.5 通用NAT转换函数

对于通用NAT转换函数,最主要的就是函数nf_nat_fn,而nf_nat_fn的实现中涉及了许多的函数,此处我们依依分析之。

1.5.1 nf_nat_fn

该函数主要功能就是实现数据的NAT操作(包括SNATDNAT),具体来说,就是对一个数据流对应的连接跟踪项仅执行一次SNATDNAT,而当数据流对应的连接跟踪项的NAT操作执行完成以后,对于后续的数据包,则直接根据连接跟踪项的reply方向的nf_conntrac_tuple变量的值进行NAT转换,然后将数据再交给协议栈处理。

下面分析这个函数:

功能:实现NAT功能(包括SNAT/DNAT功能)

1.首先判断数据包是否符合要求(必须不是分段的),数据包对应的连接跟踪项是否符合转换要求等

2.对于期望连接来说,对于icmp报文,需要对报文进行NAT转换

3.只对new状态的且未进行NAT转换的连接跟踪项,且不是NF_LOCAL_IN hook点时,调用nf_nat_rule_find进行连接跟踪

项的NAT转换

4.进行了上述3的操作后,则会调用nf_nat_packet对数据包进行NAT转换操作。

 

连接跟踪项的NAT转换只会发生在连接跟踪项刚被创建且还没有进行confirm时,且每个NAT类型只会执行一次NAT转换。

static unsigned int
nf_nat_fn(unsigned int hooknum,
  struct sk_buff **pskb,
  const struct net_device *in,
  const struct net_device *out,
  int (*okfn)(struct sk_buff *))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_nat *nat;
struct nf_nat_info *info;
/* maniptype == SRC for postrouting. */
/*获取NAT类型*/
enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);
 
/* We never see fragments: conntrack defrags on pre-routing
   and local-out, and nf_nat_out protects post-routing. */
NF_CT_ASSERT(!((*pskb)->nh.iph->frag_off
       & htons(IP_MF|IP_OFFSET)));
/*获取该数据包对应的连接跟踪项*/
ct = nf_ct_get(*pskb, &ctinfo);
/* Can't track?  It's not due to stress, or conntrack would
   have dropped it.  Hence it's the user's responsibilty to
   packet filter it out, or implement conntrack/NAT for that
   protocol. 8) --RR */
/*
当数据包没有连接跟踪项,且为icmp_redirect时,返回DROP;
当数据包没有连接跟踪项,且不是icmp_redirect时,返回ACCEPT;
*/   
if (!ct) {
/* Exception: ICMP redirect to new connection (not in
   hash table yet).  We must not let this through, in
   case we're doing NAT to the same network. */
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
struct icmphdr _hdr, *hp;
 
hp = skb_header_pointer(*pskb,
(*pskb)->nh.iph->ihl*4,
sizeof(_hdr), &_hdr);
if (hp != NULL &&
    hp->type == ICMP_REDIRECT)
return NF_DROP;
}
return NF_ACCEPT;
}
 
/* Don't try to NAT if this packet is not conntracked */
/*对于连接跟踪项为nf_conntrack_untracked,则说明不对该数据包进行连接跟踪,此时直接返回ACCEPT*/
if (ct == &nf_conntrack_untracked)
return NF_ACCEPT;
/*当连接跟踪项没有关联的nf_conn_nat变量时,返回ACCEPT*/
nat = nfct_nat(ct);
if (!nat)
return NF_ACCEPT;
/*
对于期望连接original与reply方向的数据包,对于icmp协议的数据包,进行nat操作;
对于期望连接、及非期望连接的NEW状态下的连接跟踪项,只有连接跟踪项的NAT操作没有进行
的情况下才进行NAT转换操作。
a)对于LOCAL_IN的hook点,调用alloc_null_binding进行NAT操作,可能会修改四层协议相关的关键字
b)对于已经确认过却没有进行NAT操作的连接跟踪项,调用alloc_null_binding_confirmed进行NAT操作,只有可能修改
   四层协议相关的关键字。
c)对于其他情况,则通过nf_nat_rule_find,查找iptables的nat表中有没有匹配该数据流的NAT规则,若有则根据
  NAT类型,调用相应的target进行NAT操作(SNAT target 、DNAT target)
*/
switch (ctinfo) {
case IP_CT_RELATED:
case IP_CT_RELATED+IP_CT_IS_REPLY:
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
if (!nf_nat_icmp_reply_translation(ct, ctinfo,
   hooknum, pskb))
return NF_DROP;
else
return NF_ACCEPT;
}
/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
case IP_CT_NEW:
info = &nat->info;
 
/* Seen it before?  This can happen for loopback, retrans,
   or local packets.. */
if (!nf_nat_initialized(ct, maniptype)) {
unsigned int ret;
 
if (unlikely(nf_ct_is_confirmed(ct)))
/* NAT module was loaded late */
ret = alloc_null_binding_confirmed(ct, info,
   hooknum);
else if (hooknum == NF_IP_LOCAL_IN)
/* LOCAL_IN hook doesn't have a chain!  */
ret = alloc_null_binding(ct, info, hooknum);
else
ret = nf_nat_rule_find(pskb, hooknum, in, out,
       ct, info);
 
if (ret != NF_ACCEPT) {
return ret;
}
} else
DEBUGP("Already setup manip %s for ct %p\n",
       maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
       ct);
break;
 
default:
/* ESTABLISHED */
NF_CT_ASSERT(ctinfo == IP_CT_ESTABLISHED ||
     ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
info = &nat->info;
}
 
NF_CT_ASSERT(info);
/*调用nf_nat_packet,根据连接跟踪项的reply tuple变量实现对数据包的NAT操作*/
return nf_nat_packet(ct, ctinfo, hooknum, pskb);
}
 


这个函数主要涉及了函数nf_nat_initializedalloc_null_binding_confirmedalloc_null_bindingnf_nat_rule_findnf_nat_packet,下面我们开始分析这些函数。

 

1.5.1.1 nf_nat_initialized

这个函数主要是判断传递的连接跟踪项,有没有进行过manip类型的NAT转换。

manip的值为IP_NAT_MANIP_SRC,则判断连接跟踪项的status

IPS_SRC_NAT_DONE_BIT位是否为1,若为1,则说明该连接跟踪项已经进行了SNAT转换,不需要再次转换;

对于DNAT的判断与上述SNAT的判断类似,根据这个函数,就可以避免多次对一个连接跟踪项进行SNAT或者DNAT操作。

 

static inline int nf_nat_initialized(struct nf_conn *ct,
     enum nf_nat_manip_type manip)
{
if (manip == IP_NAT_MANIP_SRC)
return test_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);
else
return test_bit(IPS_DST_NAT_DONE_BIT, &ct->status);
}


1.5.1.2 alloc_null_binding_confirmed

这个是针对连接跟踪项已经确认,但是其NAT操作还没有进行的情况。

按照我们的逻辑来说,对连接跟踪项的NAT操作是在连接跟踪项创建之后,且连接跟踪项被确认之前的。

那怎么会出现连接跟踪项已经确认,但是NAT转换还没有进行的呢?

当连接跟踪模块已经加载并且已经工作一段时间后,才加载NAT模块,就会导致这种情况出现。

 

那既然NAT模块是后面加载的,那还有必要对先前已经确认的连接跟踪项进行NAT转换吗?

是这样的,对于先前已经确认的连接跟踪项,虽然已经确认了,但是由于NAT模块没有加载,使其没有添加到by_source[]数组相对应的链表上,而NAT模块在对连接跟踪项进行转换时,是通过将转换后的nf_conntrack_tuple变量,与连接跟踪项上所有的nf_conntrack_tuple变量进行对比来确保转换后的连接跟踪项的唯一性。基于这个原理,如果不把先前已经确认的连接跟踪项通过NAT转换并添加到by_source[]链表上的话,则可能出现已经转换后的连接跟踪项的tuple变量与先前已经确认的连接跟踪项冲突,所以需要将先前已经的确认的连接跟踪项也进行NAT操作,不过这次NAT转换不会修改ip地址,最大的可能就是对源或目的端口进行微调。

 

这个函数还是比较简单的,跟踪hook的类型,确认NAT转换的类型,然后就将range中的ip地址就设置reply方向的nf_conntrack_tuple的对应的ip地址(从这也能看出,没有修改ip地址)。 然后就是调用函数nf_nat_setup_info实现对连接跟踪项的NAT转换操作。

unsigned int
alloc_null_binding_confirmed(struct nf_conn *ct,
     struct nf_nat_info *info,
     unsigned int hooknum)
{
__be32 ip
= (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
   ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip
   : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip);
u_int16_t all
= (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
   ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.all
   : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u.all);
struct nf_nat_range range
= { IP_NAT_RANGE_MAP_IPS, ip, ip, { all }, { all } };
 
DEBUGP("Allocating NULL binding for confirmed %p (%u.%u.%u.%u)\n",
       ct, NIPQUAD(ip));
return nf_nat_setup_info(ct, &range, hooknum);
}


 

函数nf_nat_setup_info就是最终实现NAT转换的最最重要的函数,后面的alloc_null_binding、 masquerade_target,ipt_snat_targetipt_dnat_target最终都是调用这个函数实现连接跟踪项的NAT转换,有必要单独拿出来讲这个函数。

 

1.5.1.3 alloc_null_binding

 

在函数nf_nat_fn里,只有对于LOCAL_IN hook点上,才会调用该函数进行NAT转换的,因为nat表没有LOCAL_IN链,所以在LOCAL_IN链肯定不会匹配NAT转换规则的。但是内核绝不会把一个没用的代码放在那里一直不改的,对于LOCAL_IN hook点,虽然该数据流没有进行nat,但是存在其他三层ip相同数据流进行nat时,将该不需NAT数据流的四层端口号给占用的情况,这就好导致数据连接跟踪项的冲突。为了解决这个问题,就需要调用nf_nat_setup_info为当前不需NAT数据流找到一个唯一的tuple变量(新的唯一tuple变量的值有两种:原来的tuple变量即是唯一的;修改原来tuple变量的四层协议相关的关键字, 得到一个新的唯一的tuple变量。),并将该连接跟踪项添加到by_source[]相对应的链表上,这样在其他数据连接跟踪项进行转换时,就会先将转换后的nf_conntrac_tuple变量与连接跟踪项的确认链表中的值进行比较,在没有冲突的情况下再进行NAT转换。

 

 

这个函数的执行流程与alloc_null_binding_confirmed类似,且最终也是调用nf_nat_setup_info进行连接跟踪项的转换。

inline unsigned int
alloc_null_binding(struct nf_conn *ct,
   struct nf_nat_info *info,
   unsigned int hooknum)
{
/* Force range to this IP; let proto decide mapping for
   per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
   Use reply in case it's already been mangled (eg local packet).
*/
__be32 ip
= (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
   ? ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u3.ip
   : ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.u3.ip);
struct nf_nat_range range
= { IP_NAT_RANGE_MAP_IPS, ip, ip, { 0 }, { 0 } };
 
DEBUGP("Allocating NULL binding for %p (%u.%u.%u.%u)\n",
       ct, NIPQUAD(ip));
return nf_nat_setup_info(ct, &range, hooknum);
}


1.5.1.4 nf_nat_rule_find

 

当连接跟踪项不是以上1.5.1.21.5.1.3这两个类型,且是NEWRELATEDRELATED+REPLY状态的连接跟踪项,则会调用函数nf_nat_rule_find,遍历nat表中对应的规则链:

若找到nat规则,则调用相应的target函数(ipt_snat_target或者ipt_dnat_target)实现连接跟踪项的转换;

若没找到nat规则,则调用 alloc_null_binding确保连接跟踪项的reply方向的nf_conntrack_tuple变量的唯一性,并添加到by_source[]相应的链表中,实现的功能与1.5.1.21.5.1.3中介绍的大致类似。

 

 

功能:实现对数据包关联的连接跟踪项的NAT转换操作。

1.调用ipt_do_table,查找nat表中有没有匹配该连接跟踪项的nat规则,若有则根据NAT类型调用相应的target实现对连接跟踪项的NAT操作(SNAT target DNAT target),且将该连接跟踪项的status值中设置已进行NAT转换标志(关于ipt_do_table函数的分析,请参考如下文章Linux netfilter 学习笔记 之五 ip层netfilter的table中规则的匹配检查)。

2.在调用完ipt_do_table后,该连接跟踪项还没有进行NAT转换,则调用alloc_null_binding进行NAT转换。alloc_null_binding并不会修改连接跟踪项的reply方向的tuple变量的三层ip地址,只有在该连接跟踪项使用的tuple变量值不唯一时,则更新连接跟踪项的reply方向的tuple变量的四层协议相关的关键字(也就是端口号之类的)即可。

int nf_nat_rule_find(struct sk_buff **pskb,
     unsigned int hooknum,
     const struct net_device *in,
     const struct net_device *out,
     struct nf_conn *ct,
     struct nf_nat_info *info)
{
int ret;
 
ret = ipt_do_table(pskb, hooknum, in, out, &nat_table);
 
if (ret == NF_ACCEPT) {
if (!nf_nat_initialized(ct, HOOK2MANIP(hooknum)))
/* NUL mapping */
ret = alloc_null_binding(ct, info, hooknum);
}
return ret;
}


1.5.1.5 nf_nat_packet

nf_nat_fn的最后,会调用函数nf_nat_packet对数据包进行nat转换。

当一个连接跟踪项已经被NAT转换后,后续的数据包则直接进入函数nf_nat_packet,对数据包中的ip地址、端口等进行NAT转换操作。

当一个连接跟踪项刚被被NAT转换后,则其第一个数据包也要接进入函数nf_nat_packet,对数据包中的ip地址、端口等进行NAT转换操作。

 

/* Do packet manipulations according to nf_nat_setup_info. */

/*

功能:实现数据包的NAT操作:

当为SNAT操作,且是reply方向的PREROUTING时,经过下面的异或后同样可以调用manip_pkt,而因为此时为DNAT,因此就实现了De-SNAT

当为DNAT操作,且是reply方向的PREROUTING时,经过下面的异或后同样可以调用manip_pkt,而因为此时为SNAT因此就实现了De-DNAT

当为SNAT操作,且是original方向的POSTROUTING时,则调用manip_pkt执行SNAT操作;

当为DNAT操作,且是original方向的PREROUTING/OUTPUT时,则调用manip_pkt执行DNAT操作。

1.根据hook点设置statusbit的值

2.对于reply方向,需要执行异或操作

3.当连接跟踪项的status变量与statusbit进行位与的结果不为0:

  调用函数manip_pkt根据NAT类型修改数据包的ip地址。

unsigned int nf_nat_packet(struct nf_conn *ct,
   enum ip_conntrack_info ctinfo,
   unsigned int hooknum,
   struct sk_buff **pskb)
{
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
unsigned long statusbit;
enum nf_nat_manip_type mtype = HOOK2MANIP(hooknum);
 
if (mtype == IP_NAT_MANIP_SRC)
statusbit = IPS_SRC_NAT;
else
statusbit = IPS_DST_NAT;
 
/* Invert if this is reply dir. */
if (dir == IP_CT_DIR_REPLY)
statusbit ^= IPS_NAT_MASK;
 
/* Non-atomic: these bits don't change. */
if (ct->status & statusbit) {
struct nf_conntrack_tuple target;
 
/* We are aiming to look like inverse of other direction. */
nf_ct_invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
 
if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
return NF_DROP;
}
return NF_ACCEPT;
}
 


以上把函数nf_nat_fn相关的函数都分析了,现在就分析最最重要的NAT转换函数nf_nat_setup_info

1.5.2 nf_nat_setup_info

这个函数会对4hook点进来的连接跟踪项进行NAT转换,所以这个函数至此SNATDNAT转换,根据HOOK点的类型能够决定转换的类型。该函数最精髓的地方就是调用函数get_unique_tuple,获取一个唯一的且未被其他已经进行NAT转换的连接跟踪项使用的nf_conntrack_tuple变量。当转换成功后,置标记位。

该函数执行的步骤如下:

1.判断传入的hook点是否是NAT相关的hook点,NAT只在PRE_ROUTINGPOST_ROUTINGLOCAL_OUTLOCAL_IN这四个hook点起作用

2.若此时连接跟踪项的status变量中的   IPS_SRC_NAT_DONE_BIT或者IPS_DST_NAT_DONE_BIT位已经被置位了,则打印bug信息,并调用kernel panic

3.根据reply方向的nf_conntrack_tuple结构的变量,获取其反方向的nf_conntrack_tuple结构的变量

4. 调用get_unique_tuple,根据传递的tuple变量,获取一个新的且经过NAT转换的tuple变量,其方向依然是原始方向

5.当新的tuple变量的值与当前的原始方向的tuple变量的值不相等时,进行NAT转换(因为只有在两个值不同时才需要NAT操作):

a)对传递过来的新的tuple变量的值,调用get_unique_tuple,获取该tuple变量反方向的 tuple变量值,即新的reply方向的值

b)调用nf_conntrack_alter_reply将连接跟踪项的reply方向的tuplehash[IP_CT_DIR_REPLY].tuple替换为a)中得到的reply方向的tuple变量,当连接跟踪项不是期望连接项,且还没有创建期望连接时,对根据新的reply反向的tuple变量,在helpers链表中查找新的符合要求的helper变量,并替换调用连接跟踪项中的原来的nf_conntrack_helper变量

c)根据连接跟踪项的NAT类型,设置连接跟踪项的status中相应位(IPS_SRC_NAT/IPS_DST_NAT)

6.若连接跟踪项的当前status变量的IPS_DST_NAT_DONE 与 IPS_SRC_NAT_DONE位均没有置位,则需要将经过NAT操作后的连接跟踪项添加到bysource[]相应的链表中去(调用hash_by_src根据传入的原始方向的tuple变量计算hash值,根据该hash值获取相应的链表bysourece[hash])

 

7. 根据NAT类型,将连接跟踪项的status变量的IPS_DST_NAT_DONE或者IPS_SRC_NAT_DONE位置位。

 

在函数的最后有个置位IPS_DST_NAT_DONE_BITIPS_SRC_NAT_DONE_BIT的操作,这就是为了保证一个数据连接跟踪项

在某一个NAT转换类型(SNATDNAT)上只能初始化一次。

unsigned int
nf_nat_setup_info(struct nf_conn *ct,
  const struct nf_nat_range *range,
  unsigned int hooknum)
{
struct nf_conntrack_tuple curr_tuple, new_tuple;
struct nf_conn_nat *nat = nfct_nat(ct);
struct nf_nat_info *info = &nat->info;
int have_to_hash = !(ct->status & IPS_NAT_DONE_MASK);
enum nf_nat_manip_type maniptype = HOOK2MANIP(hooknum);
 
NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING ||
     hooknum == NF_IP_POST_ROUTING ||
     hooknum == NF_IP_LOCAL_IN ||
     hooknum == NF_IP_LOCAL_OUT);
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) */
nf_ct_invert_tuplepr(&curr_tuple,
     &ct->tuplehash[IP_CT_DIR_REPLY].tuple);
 
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);
 
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct nf_conntrack_tuple reply;
 
/* Alter conntrack table so will recognize replies. */
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. */
if (have_to_hash) {
unsigned int srchash;
 
srchash = hash_by_src(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
write_lock_bh(&nf_nat_lock);
list_add(&info->bysource, &bysource[srchash]);
write_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;
}


nf_ct_invert_tuplepr主要是根据输入的nf_conntrack_tuple变量,获取其反方向的nf_conntrack_tuple变量。

 

1.5.2.1 get_unique_tuple

该函数根据传递的orig_tuplerange变量,得到一个新的tuple,此tupleip地址或者端口号已经进行了NAT转换。

函数的执行步骤如下:

1.当为SNAT,且通过find_appropriate_srcbysource[]相应链表上得到了一个符合要求的原始tuple变量,且在连接跟踪项的确认链表中,没有与该tuple变量相关的连接跟踪项,则可以使用该tuple变量作为SNAT的依据。

 

2.进入此步,则说明需要对传入的连接跟踪项进行NAT,则调用find_best_ips_proto设置传入tuple的源或者目的ip地址

 

3.调用__nf_nat_proto_find查看在nf_nat_protos数组中有没有注册与连接跟踪项的四层协议相关的 nf_nat_protocol变量

 

4.range支持IP_NAT_RANGE_PROTO_RANDOM时,则需要调用四层协议nf_nat_protocol类型变量的unique_tuple,随机选择一个新的四层协议号

 

5.range不支持IP_NAT_RANGE_PROTO_SPECIFIED,则不需要修改四层协议相关的端口号或者其他信息,程序返回

 

6.range支持IP_NAT_RANGE_PROTO_SPECIFIED,且发现tuple的四层协议相关的端口号或者其他信息已经在range变量对应的

  四层相关值的范围之内,且该tuple的相反tuple与连接跟踪项的reply方向的tuple变量不等,则无需再进行 四层协议相关的端口号或者其他值的转换,程序返回

 

7.若以上56均不满足,则调用四层协议nf_nat_protocol类型变量的unique_tuple,对tuple的四层协议相关的端口号等信息进行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 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) {
if (find_appropriate_src(orig_tuple, tuple, range)) {
DEBUGP("get_unique_tuple: Found current src map\n");
if (!(range->flags & IP_NAT_RANGE_PROTO_RANDOM))
if (!nf_nat_used_tuple(tuple, ct))
return;
}
}
 
/* 2) Select the least-used IP/proto combination in the given
   range. */
*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);
 
/* Change protocol info to have some randomization */
if (range->flags & IP_NAT_RANGE_PROTO_RANDOM) {
proto->unique_tuple(tuple, range, maniptype, ct);
goto out;
}
 
/* Only bother mapping if it's not already in range and unique */
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;
 
/* Last change: get protocol to try to obtain unique tuple. */
proto->unique_tuple(tuple, range, maniptype, ct);
out:
rcu_read_unlock();
}


这个函数里有两个重要函数find_appropriate_srcnf_nat_used_tuple

 

1.5.2.1.1 find_appropriate_src

这个函数只会被SNAT转换调用。此处是在已进行NATbysource[]相应的链表中,查找是否有原始tuple值的src ipsrc portl4proto的值与给定的tuplesrc ipsrc portl4proto的值相等的连接跟踪项:

若有,则说明我们大概可以可以用这个已经经过SNAT的连接跟踪项的reply方向的tuple值的取反后的tuple变量作为SNAT的依据,且将该tupledst替换成传入tuple变量的dst值。

 

功能:给定一个tuple变量,判断在已进行SNAT转换,且其nf_conn_nat变量已经在bysource[]的连接跟踪项,是否存在连接跟踪项的原始方向tuple 是否与传入tuple相等:

若有,则说明找到了一个合适的经NAT后的原始tuple项,并存放在result中,程序返回

1.通过hash_by_src获取原始方向tuplehash值为h(此处为假设)

2. bysource[h]链表中查找已进行NAT操作的

 

 

static int
find_appropriate_src(const struct nf_conntrack_tuple *tuple,
     struct nf_conntrack_tuple *result,
     const struct nf_nat_range *range)
{
unsigned int h = hash_by_src(tuple);
struct nf_conn_nat *nat;
struct nf_conn *ct;
 
read_lock_bh(&nf_nat_lock);
list_for_each_entry(nat, &bysource[h], info.bysource) {
ct = (struct nf_conn *)((char *)nat - offsetof(struct nf_conn, data));
if (same_src(ct, tuple)) {
/* Copy source part from reply tuple. */
nf_ct_invert_tuplepr(result,
       &ct->tuplehash[IP_CT_DIR_REPLY].tuple);
result->dst = tuple->dst;
 
if (in_range(result, range)) {
read_unlock_bh(&nf_nat_lock);
return 1;
}
}
}
read_unlock_bh(&nf_nat_lock);
return 0;
}
 


 

same_src的功能是判断传入tuple的源ip及源端口号是否与传入连接跟踪项的原始方向的tuple变量的源ip及源端口号相等。此处只是对原始方向的tuple变量的原始方向的ip地址、原始方向的端口号等以及四层协议号相等,即认为相同,而不管目的ip地址与目的端口号是否相等。

static inline int
same_src(const struct nf_conn *ct,
 const struct nf_conntrack_tuple *tuple)
{
const struct nf_conntrack_tuple *t;
 
t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
return (t->dst.protonum == tuple->dst.protonum &&
t->src.u3.ip == tuple->src.u3.ip &&
t->src.u.all == tuple->src.u.all);
}
 
 


in_range的作用是判断一个tuple中的源ip地址是否在range范围内。

1. nf_nat_range结构 的range变量,支持ip地址的NAT取值范围功能,若tuple中的源地址小于range变量的最小ip地址,或者大于range变量的最大ip地址,则返回0,说明tuple没有在range范围内。

2.进入此步骤后,则说明tuple的源ip地址已经在range范围内了,此时判断range是否增加了协议识别标签:

 若没有添加协议识别标签,则返回tuple值满足range的要求,返回1

 若有添加协议识别标签,且调用四层协议的in_range函数后,也返回1,则说明tuple满足range要求,程序返回1

    若不满足以上两种case中的一个,则返回0.

*/

static int
in_range(const struct nf_conntrack_tuple *tuple,
 const struct nf_nat_range *range)
{
struct nf_nat_protocol *proto;
int ret = 0;
 
/* If we are supposed to map IPs, then we must be in the
   range specified, otherwise let this drag us onto a new src IP. */
if (range->flags & IP_NAT_RANGE_MAP_IPS) {
if (ntohl(tuple->src.u3.ip) < ntohl(range->min_ip) ||
    ntohl(tuple->src.u3.ip) > ntohl(range->max_ip))
return 0;
}
 
rcu_read_lock();
/*根据四层协议号,在nf_nat_protos[]数组中找到相应的nf_nat_protocol变量*/
proto = __nf_nat_proto_find(tuple->dst.protonum);
/*
当range没有标识四层相关的关键字检查标签或者
设置四层检查的标签,且调用相应四层nf_nat_protocol变量的in_range函数后,检查通过时,则返回1
*/
if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||
    proto->in_range(tuple, IP_NAT_MANIP_SRC,
    &range->min, &range->max))
ret = 1;
rcu_read_unlock();
 
return ret;
}
 


 

1.5.2.1.2 nf_nat_used_tuple

 

 

该函数主要是判断已传入tuple取反获取到的reply tuple变量,是否已经被其他连接跟踪项使用了:

 若已经被其他连接跟踪项使用了,则返回TRUE

 若没有被其他连接跟踪项使用,则返回FALSE

int
nf_nat_used_tuple(const struct nf_conntrack_tuple *tuple,
  const struct nf_conn *ignored_conntrack)
{
/* Conntrack tracking doesn't keep track of outgoing tuples; only
   incoming ones.  NAT means they don't have a fixed mapping,
   so we invert the tuple and look for the incoming reply.
 
   We could keep a separate hash if this proves too slow. */
struct nf_conntrack_tuple reply;
 
nf_ct_invert_tuplepr(&reply, tuple);
return nf_conntrack_tuple_taken(&reply, ignored_conntrack);
}


主要是通过函数 nf_conntrack_tuple_taken实现的。

nf_conntrack_tuple_taken是判断连接跟踪项的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某个tuple值与传入值相等,若存在,则返回TRUE,则不能用传入的tuple变量作为NAT的依据;若不存在,返回FALSE,说明可以用传入的tuple变量作为NAT的依据

int
nf_conntrack_tuple_taken(const struct nf_conntrack_tuple *tuple,
 const struct nf_conn *ignored_conntrack)
{
struct nf_conntrack_tuple_hash *h;
 
read_lock_bh(&nf_conntrack_lock);
h = __nf_conntrack_find(tuple, ignored_conntrack);
read_unlock_bh(&nf_conntrack_lock);
 
return h != NULL;
}
 


而函数__nf_conntrack_find才是最终的函数,该函数查找连接跟踪的确认链表中,是否存在连接跟踪项不等于ignored_conntrack,且连接跟踪项的某一个tuple变量与传入的tuple变量相等的情况,若有则说明传入的tuple变量不能作为NAT转换的依据。

struct nf_conntrack_tuple_hash *
__nf_conntrack_find(const struct nf_conntrack_tuple *tuple,
    const struct nf_conn *ignored_conntrack)
{
struct nf_conntrack_tuple_hash *h;
unsigned int hash = hash_conntrack(tuple);
 
list_for_each_entry(h, &nf_conntrack_hash[hash], list) {
if (nf_ct_tuplehash_to_ctrack(h) != ignored_conntrack &&
    nf_ct_tuple_equal(tuple, &h->tuple)) {
NF_CT_STAT_INC(found);
return h;
}
NF_CT_STAT_INC(searched);
}
 
return NULL;
}


 

至此分析完了函数get_unique_tuple,这个函数真的很重要,考虑到了多种case

 

1.5.2.2 nf_conntrack_alter_reply

当获取了一个唯一的nf_conntrack_tuple变量后,就可以调用该函数修改连接跟踪项的reply方向的nf_conntrack_tuple变量了。

功能:根据传递的nf_conntrack_tuple变量,修改ctreply方向的nf_conntrack_tuple值以及helper的值

1.修改输入连接跟踪项的reply方向的nf_conntrack_tuple变量值

2.根据新的reply方向的nf_conntrack_tuple变量,修改连接跟踪项项的helper变量

 

因为我们知道在创建连接跟踪项时,就是根据reply方向的nf_conntrack_tuple变量,在helpers链表中查找的helper变量;当reply方向的nf_conntrack_tuple变量修改后,则肯定需要再次查找helpers,用以找到新的符合条件的helper变量。因此必须在NAT转换完成以后,才能调用连接跟踪模块的helpe相关的回调函数。

这也是为什么SNATHOOK回调函数的优先级高于连接跟踪项的ipv4_conntrack_help hook回调函数的原因。

void nf_conntrack_alter_reply(struct nf_conn *ct,
      const struct nf_conntrack_tuple *newreply)
{
struct nf_conn_help *help = nfct_help(ct);
 
write_lock_bh(&nf_conntrack_lock);
/* Should be unconfirmed, so not in hash table yet */
NF_CT_ASSERT(!nf_ct_is_confirmed(ct));
 
DEBUGP("Altering reply tuple of %p to ", ct);
NF_CT_DUMP_TUPLE(newreply);
 
ct->tuplehash[IP_CT_DIR_REPLY].tuple = *newreply;
if (!ct->master && help && help->expecting == 0)
help->helper = __nf_ct_helper_find(newreply);
write_unlock_bh(&nf_conntrack_lock);
}


至此将nf_nat_setup_info分析完了。下面看下target的定义,其实都差不多,都是调用函数nf_nat_setup_info实现的,还是简单分析一下。

2.target相关的函数分析

2.1 SNAT target

功能:实现SNAT功能

1.调用nf_ct_get,获取传入数据包关联的nf_conn变量

2.此处进行SNAT只是设置连接跟踪项中的reply方向的nf_conntrack_tuple变量,因此:

   对于主连接,仅设置连接跟踪项的状态为NEWSNAT操作,因为对于状态不为NEW的连接跟踪项,其reply方向的nf_conntrack_tuple结构的变量的目的地址和端口号已经修改过了,不需要再次修改了;

   对于期望连接来说,当期望连接刚建立时,其状态仅为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY,所以

   也只对这两种情况的期望连接,进行SNAT操作。

3.调用nf_nat_setup_info,根据targinfo中的地址范围与端口值修改连接跟踪项的reply方向的nf_conntrack_tuple

   变量中的值。

 

执行这个target只是修改了数据包对应的连接跟踪项的reply方向的tuple变量,并没有修改数据包的ip地址,而修改数据包的ip地址是nat模块的hook函数中执行的(在执行了target操作后才会执行,调用函数nf_nat_packet实现)

(疑问:为什么是期望连接时,状态为IP_CT_RELATED或者IP_CT_RELATED+IP_CT_IS_REPLY都认为是起始状态呢?

 状态为IP_CT_RELATED时,认为是新创建的连接跟踪项,我是能理解的,但是IP_CT_RELATED+IP_CT_IS_REPLY

 做为新创建的连接跟踪项的依据,我没有搞懂而且我感觉不会出现状态为IP_CT_RELATED+IP_CT_IS_REPLY

 连接跟踪项

 )

static unsigned int ipt_snat_target(struct sk_buff **pskb,
    const struct net_device *in,
    const struct net_device *out,
    unsigned int hooknum,
    const struct xt_target *target,
    const void *targinfo)
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
const struct nf_nat_multi_range_compat *mr = targinfo;
 
NF_CT_ASSERT(hooknum == NF_IP_POST_ROUTING);
 
ct = nf_ct_get(*pskb, &ctinfo);
 
/* Connection must be valid and new. */
NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
    ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
NF_CT_ASSERT(out);
 
return nf_nat_setup_info(ct, &mr->range[0], hooknum);
}
 


2.2 DNAT target

功能:实现DNAT功能

1.调用nf_ct_get,获取传入数据包关联的nf_conn变量

2.此处进行DNAT只是设置连接跟踪项中的reply方向的nf_conntrack_tuple变量,因此:

   对于主连接,仅设置连接跟踪项的状态为NEWDNAT操作,因为对于状态不为NEW的连接跟踪项,其reply方向的nf_conntrack_tuple结构的变量的目的地址和端口号已经修改过了,不需要再次修改了;

   对于期望连接来说,当期望连接刚建立时,其状态仅为IP_CT_RELATED,才进行DNAT操作。

3.调用nf_nat_setup_info,根据targinfo中的地址范围与端口值修改连接跟踪项的reply方向的nf_conntrack_tuple变量中的值。

 

执行这个target只是修改了数据包对应的连接跟踪项的reply方向的tuple变量,并没有修改数据包的ip地址,而修改数据包的ip地址是nat模块的hook函数中执行的(在执行了target操作后才会执行)

static unsigned int ipt_dnat_target(struct sk_buff **pskb,
    const struct net_device *in,
    const struct net_device *out,
    unsigned int hooknum,
    const struct xt_target *target,
    const void *targinfo)
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
const struct nf_nat_multi_range_compat *mr = targinfo;
 
NF_CT_ASSERT(hooknum == NF_IP_PRE_ROUTING ||
     hooknum == NF_IP_LOCAL_OUT);
 
ct = nf_ct_get(*pskb, &ctinfo);
 
/* Connection must be valid and new. */
NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
 
if (hooknum == NF_IP_LOCAL_OUT &&
    mr->range[0].flags & IP_NAT_RANGE_MAP_IPS)
warn_if_extra_mangle((*pskb)->nh.iph->daddr,
     mr->range[0].min_ip);
 
return nf_nat_setup_info(ct, &mr->range[0], hooknum);
}
 


2.3 xt_target masquerade

 

static struct xt_target masquerade = {

.name = "MASQUERADE",

.family = AF_INET,

.target = masquerade_target,

.targetsize = sizeof(struct ip_nat_multi_range_compat),

.table = "nat",

.hooks = 1 << NF_IP_POST_ROUTING,

.checkentry = masquerade_check,

.me = THIS_MODULE,

};

 

这个target也是实现SNAT转换,但是比SNAT更智能,不需要输入SNAT转换后的源ip地址,可以根据出口设备与下一跳网关ip,找到要转换到的源ip地址,然后再调用nf_nat_setup_info函数,实现连接跟踪项的SNAT转换。

ipt_snat_target相比增加了获取要转换到的源ip地址,主要是根据出口设备与下一跳网关地址,通过调用函数inet_select_addr(关于这个函数,可参看Linux inet_select_addr分析,获取ip地址的。

 

 

static unsigned int
masquerade_target(struct sk_buff **pskb,
  const struct net_device *in,
  const struct net_device *out,
  unsigned int hooknum,
  const struct xt_target *target,
  const void *targinfo)
{
#ifdef CONFIG_NF_NAT_NEEDED
struct nf_conn_nat *nat;
#endif
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
struct ip_nat_range newrange;
const struct ip_nat_multi_range_compat *mr;
struct rtable *rt;
__be32 newsrc;
 
IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
 
ct = ip_conntrack_get(*pskb, &ctinfo);
#ifdef CONFIG_NF_NAT_NEEDED
nat = nfct_nat(ct);
#endif
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED
    || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
 
/* Source address is 0.0.0.0 - locally generated packet that is
 * probably not supposed to be masqueraded.
 */
#ifdef CONFIG_NF_NAT_NEEDED
if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip == 0)
#else
if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip == 0)
#endif
return NF_ACCEPT;
 
mr = targinfo;
rt = (struct rtable *)(*pskb)->dst;
newsrc = inet_select_addr(out, rt->rt_gateway, RT_SCOPE_UNIVERSE);
if (!newsrc) {
printk("MASQUERADE: %s ate my IP address\n", out->name);
return NF_DROP;
}
 
write_lock_bh(&masq_lock);
#ifdef CONFIG_NF_NAT_NEEDED
nat->masq_index = out->ifindex;
#else
ct->nat.masq_index = out->ifindex;
#endif
write_unlock_bh(&masq_lock);
 
/* Transfer from original range. */
newrange = ((struct ip_nat_range)
{ mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,
  newsrc, newsrc,
  mr->range[0].min, mr->range[0].max });
 
/* Hand modified range to generic setup. */
return ip_nat_setup_info(ct, &newrange, hooknum);
}


 

 

至此,将NAT转换相关的所有主要的函数都分析完了。下面进行实例分析下。

3.实例分析

对于nat相关的函数,我们都分析完了,那我们就分别以SNATDNAT两种情况,来分析下数据包在网关中是如何实现地址转换的。

3.1 SNAT

这个就是典型的路由器工作机制,路由器的lan侧设备需要访问到互联网,而又只有路由器上的wan连接存在一个公网地址,此时lanpc发来的数据就需要进行SNAT转换。

3.1.1环境说明

lan1 pc ip192.168.1.123  

route wan ip115.22.112.12       

需要访问的外网的地址为ip 14.17.88.99

网关通过iptables做了SNAT,命令如下:

iptables -t nat -A POSTROUTING -s 192.168.1.132/32 -o wan0 -j SNAT --to-source 115.22.112.12

iptables-t nat -A POSTROUTING -s 192.168.1.0/24 -o wan0 -j MASQUERADE真实的规则应该是这一个,因为真实的网关中,其wanip可能会经常改变。此处分析SNAT时就以上面的命令为准)

3.1.2 数据SNAT转换分析

当第一个lan侧数据进入到路由器的wan接口时,在 PRE_ROUTING 创建一个nf_conn和两个nf_conntrack_tupleorigin reply

其中origin tuple.src=192.168.1.123 origin tuple.dst=14.17.88.99 reply tuple.src=14.17.88.99 reply tuple.dst=192.168.1.3

当查找路由成功,要转发该数据包时,进入到POST_ROUTING链时,进入到NAThook函数时,查看到有SNAT的规则,经过SNAT后,会将tuple里的值修改如下:

其中origin tuple.src=192.168.1.123 origin tuple.dst=14.17.88.99 reply tuple.src=14.17.88.99 reply tuple.dst=115.22.112.12

 

当服务器14.17.88.99回复了一个数据包后(src=14.17.88.99 dst=115.22.112.12),进入到wan侧接口的PRE_ROUTING链时,则在调用其nat相关的hook函数后,会调用函数ip_nat_packet获取到origin tuple值,然后再根据origin tuple,计算出反方向的tuple,即为new_tuple.src = 14.17.88.99 new_tuple.dst = 192.168.1.123,然后就会根据这个新的tuple修改其目的ip地址,修改后的数据包的目的地址即为192.168.1.123 。然后再查找路由,将数据发送到正常的lan口。这就是natDe-SNAT

 

 

3.2 DNAT

即路由器的lan侧设备中,有一个设备要作为server使用,这时候就需要使用dnat了。

3.2.1 环境说明

lan1 pc ip192.168.1.183  

route wan ip115.22.123.12(外网看到的serverip地址)      

外网的地址为ip 14.17.88.22

iptables -t nat -A PREROUTING -i wan0 -j DNAT --to-destination 192.168.1.183

3.2.2 数据的DNAT分析

当外网client发送一个到server的请求数据。(其src ip 14.17.88.22 dst 115.22.123.12

当数据到达路由器的wan0口,进入到PRE_ROUTING时,会先建立一个nf_conn结构,和两个nf_conntrack_tupleorigin reply

其中origin tuple.src=14.17.88.22 origin tuple.dst=115.22.123.12 reply tuple.src=115.22.123.12 reply tuple.dst=14.17.88.22;然后又会进入到PRE_ROUTINGhook点的nat hook中,然后调用nat hook,查找nat表的DNAT规则,刚好找到了我们上面创建的规则,接着就会修改reply tuple。将reply tuple.src=115.22.123.12 reply tuple.dst=14.17.88.22修改为reply tuple.src=192.168.1.183 reply tuple.dst=14.17.88.22,然后再根据修改后的reply tuple,取反获取到新的tuple,即new_tuple.src=14.17.88.22,new_tuple.dst=192.168.1.183,然后就会根据这个tuple值将数据包的目的地址修改为192.168.1.183,接着查找路由,将数据包发送给lanserver

lan侧发送一个回应的报文时(数据包的src192.168.1.183 dst14.17.88.22),然后当数据进入wan0PRE_ROUTING链时,由于查找到的nf_conn没有SNAT标志,

则会继续查找路由,然后forward这个数据包;当数据包到达POST_ROUTING时,根据nf_connflag置位为DNAT,且为reply方向,就会查找origin tuple,然后根据origin

tuple的值,取反得到新的tuplenew_tuple.src=115.22.123.12, new_tuple.dst=14.17.88.22,然后根据这个新的tuple,修改数据包的src地址,修改后的数据包的地址

src=115.22.123.12dst=14.17.88.22,这就是natDe-DNAT功能。

 

 

至此,将NAT转换的大致内容分析完了,在分析的过程中,自己学到了很多,通过这次分析后,再次使用iptables命令,发现比以前熟悉多了,看来理解一件事情的来龙去脉后,就能从一个高度上进行整体的分析了,有了更全面的视角。通过分析netfilter,让我明白了,能在netfilter里实现的功能,尽量不要通过修改协议栈的代码来实现,以保证协议栈的稳定性,对于一个网络程序员来说,尽量少的修改协议栈代码,应尽量使用netfilter来完成大多数的过滤、转换、限制等功能,netfilter真的是太强大了。

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值