Chinaunix首页 | 论坛 | 博客
  • 博客访问: 69638
  • 博文数量: 12
  • 博客积分: 135
  • 博客等级: 入伍新兵
  • 技术积分: 105
  • 用 户 组: 普通用户
  • 注册时间: 2012-02-29 10:06
文章分类
文章存档

2013年(1)

2012年(11)

我的朋友

分类: 网络与安全

2012-02-29 10:39:17

 |  | 博客 |  |  |  |  |  |  |  |  |  | 
 >  >  > 正文 

[原创] Netfilter 地址转换的实现
作者:  发表于:2009-05-25 20:33:09
【】 【】 【】【】

要过年了,发个贴庆祝新年。 

作者:九贱 
内核版本:2.6.12 
个人主页:

欢迎大家讨论,错误之处,敬请指正。转贴请注明作者及出处。 

[size=5]网络地址转换的实现初步分析[/size] 

网络地址转换(NAT),从本质上来讲,是通过修改IP数据首部中的地址,以实现将一个地址转换成另一个地址的技术。当然,在某些情况下,修改的不仅仅是IP首部的来源或目的地址,还包括其它要素。 
随着接入Internet的计算机数量的不断猛增,IP地址资源也就愈加显得捉襟见肘。目前NAT技术更多地被使用在将一个私网IP地址网段,转换为一个或几个公网IP地址,以实现私网与Internet的互相通讯。 
Netfilter在连接跟踪的基础上,实现了两种类型的地址转换:源地址转换和目的地址转换。顾名思义,源地址转换就是修改IP包中的源地址(或许还有源端口),而目的地址转换,就是修改IP包中的目的地址(同样,或许还有目的端口)。前者通常用于将内网主机私网地址转换为公网地址,访问Internet,后者通常用于将公网IP地址转换为一个或几个私网地址,实现向互联网提供服务。 

[size=4]模块初始化[/size] 

NAT模块对应的源文件是ip_nat_standard.c,同样地,init_or_cleanup是它的初始化函数: 

static int init_or_cleanup(int init)
{
int ret = 0;

need_ip_conntrack();

if (!init) goto cleanup;

/*初始化nat规则*/
ret = ip_nat_rule_init();
if (ret < 0) {
printk("ip_nat_init: can't setup rules.\n");
goto cleanup_nothing;
}
/*初始化nat所需要重要数据结构*/
ret = ip_nat_init();
if (ret < 0) {
printk("ip_nat_init: can't setup rules.\n");
goto cleanup_rule_init;
}
/*注册Hook*/
ret = nf_register_hook(&ip_nat_in_ops);
……
/*卸载各个注册的模块,释放初始化时申请的资源*/
 cleanup:
……
return ret;
}

函数主要完成四个工作: 
[list=1] 
[*]NAT规则表的初始化; 
[*]NAT所需的重要的数据结构的初始化; 
[*]注册Hook; 
[*]完成各清除工作; 
[/list] 

ip_nat_rule_init工作很简单,注册NAT表和两个target:源地址转换(SNAT)和目的地址转换(DNAT): 
int __init ip_nat_rule_init(void)
{
int ret;
/*注册NAT表*/
ret = ipt_register_table(&nat_table, &nat_initial_table.repl);
if (ret != 0)
return ret;
/*注册SNAT Target */
ret = ipt_register_target(&ipt_snat_reg);
if (ret != 0)
goto unregister_table;
/*注册DNAT Target*/
ret = ipt_register_target(&ipt_dnat_reg);
if (ret != 0)
goto unregister_snat;

return ret;
}

这些函数在filter表中已经详细分析过了,读者可以对应结构成员的赋值,自行分析。需要注意的是对SNAT和DNAT两个模块的注册,它们的target处理函数分别是ipt_snat_target和ipt_dnat_target,这个target与包过滤中的target没有质的区别,都是规则的动作部份,只是完成的功能不同罢了——它们的工作是地址转换,而包过滤中的target是拦截、放行之类的。 

规则初始化之外的动作,是在ip_nat_init函数中完成的,这个初始化函数同连接跟踪的初始化非常相似: 
int __init ip_nat_init(void)
{
size_t i;

/* 设置nat的hash表的大小 */
ip_nat_htable_size = ip_conntrack_htable_size;

/*同连接跟踪一样,nat的hash表也要维护一个list_head结构的hash链表*/
bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size);
if (!bysource)
return -ENOMEM;

/* 初始化内建协议 */
WRITE_LOCK(&ip_nat_lock);
for (i = 0; i < MAX_IP_NAT_PROTO; i++)
ip_nat_protos = &ip_nat_unknown_protocol;
ip_nat_protos[IPPROTO_TCP] = &ip_nat_protocol_tcp;
ip_nat_protos[IPPROTO_UDP] = &ip_nat_protocol_udp;
ip_nat_protos[IPPROTO_ICMP] = &ip_nat_protocol_icmp;
WRITE_UNLOCK(&ip_nat_lock);

/*初始化hash表*/
for (i = 0; i < ip_nat_htable_size; i++) {
INIT_LIST_HEAD(&bysource);
}

/* FIXME: Man, this is a hack.   */
IP_NF_ASSERT(ip_conntrack_destroyed == NULL);
ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;

/* Initialize fake conntrack so that NAT will skip it */
ip_conntrack_untracked.status |= IPS_NAT_DONE_MASK;
return 0;
}


Hook的注册,我们已经反复地遇到了,NAT也不例外,它需要在各个关键的Hook点上注册自己的Hook,我们仍假设Linux做为一个网关型设备,关心它在PREROUTING和POSTROUTING两个Hook点上注册的Hook,因为它们完成了最重要的源地址转换和目的地址转换: 
/* 目的地址转换的Hook,在filter包过滤之前进行 */
static struct nf_hook_ops ip_nat_in_ops = {
.hook = ip_nat_in,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_PRE_ROUTING,
.priority = NF_IP_PRI_NAT_DST,
};

/*源地址转换,在filter包过滤之后*/
static struct nf_hook_ops ip_nat_out_ops = {
.hook = ip_nat_out,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_POST_ROUTING,
.priority = NF_IP_PRI_NAT_SRC,
};

[size=4]源地址转换[/size] 
源地址转换注册在NF_IP_POST_ROUTING,数据包在包过滤之后,会进入ip_nat_out函数。在分析这个函数之前,我们需要理解的是,源地址转换如何进行? 
源地址的转换最终要做的工作,就是修改IP包中的源地址,将其替换为iptables添加规则时指定的“转换后地址”,对于绝大多数应用而言,一般是将私网IP地址修改为公网IP地址,然后将数据包发送出去。但是,很自然地,这样修改后,回来的应答数据包没有办法知道它转换之前的样子,也就是不知道真实的来源主机(对于回应包,也就是不知道把数据应答给谁),数据包将被丢弃,所以有必要,维护一张地址转换表,详细记录数据包的转换情况,以使NAT后的数据能交互地传输。 
对于许多无状态检测功能的NAT技术,这个记录转换情况的表就是一张NAT会话表,对于Netfilter而言,已经为进出数据包建立了一张状态跟踪表,自然也就没有必要重新多维护一张表了,也就是,合理地利用状态跟踪表,实现对NAT状态的跟踪和维护。 
回忆一下连接跟踪的情况,当数据包进入连接跟踪后,会建立一个tuple以及相应的replay tuple,而应答的数据包,会查找与之匹配的repaly tuple,——对于源地址转换而言,应答包中的目的地址,将是转换后的地址,而不是真实的地址,所以,为了让应答的数据包能找到对应的replay tuple,很自然地,NAT模块应该修改replaly tuple中的目的地址,以使应答数据包能找到属于自己的replay,如下图。 

所以,源地址转换的主要步骤大致如下: 
1. 数据包进入Hook函数后,进行规则匹配; 
2. 如果所有match都匹备,则进行SNAT模块的动作,即snat 的target模块; 
3. 源地址转换的规则一般是…… -j SNAT –to X.X.X.X,SNAT用规则中预设的转换后地址X.X.X.X,修改连接跟踪表中的replay tuple; 
4. 接着,修改数据包的来源的地址,将它替换成replay tuple中的相应地址,即规则中预设的地址,将其发送出去; 
5. 对于回来的数据包,应该能在状态跟踪表中,查找与之对应的replay tuple,也就能顺藤摸瓜地找到原始的tuple中的信息,将应答包中的目的地址改回来,这样,整个数据传送就得以顺利转发了; 

当然,这些只是最精简单的步骤,因为有许多重要的因素没有考虑到,比如: 
1. 上面的步骤只是对应一个来源IP的一个连接,但是一台主机在同时可能发出N个连接,所以应该有相应的解决办法; 
2. 同样的,转换后地址可能是一段地址池; 
3. 动态协议,如FTP,应该有相应的NAT方法; 

我们还是从最简单的开始,ip_nat_out是SNAT的Hook函数。

[ 本帖最后由 独孤九贱 于 2007-1-27 21:44 编辑 ]


  回复于:2007-01-27 21:32:02

[size=4]ip_nat_out[/size] 

static unsigned int
ip_nat_out(unsigned int hooknum,
   struct sk_buff **pskb,
   const struct net_device *in,
   const struct net_device *out,
   int (*okfn)(struct sk_buff *))
{
/* root is playing with raw sockets. */
if ((*pskb)->len < sizeof(struct iphdr)
    || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
return NF_ACCEPT;

if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
*pskb = ip_ct_gather_frags(*pskb, IP_DEFRAG_NAT_OUT);

if (!*pskb)
return NF_STOLEN;
}

return ip_nat_fn(hooknum, pskb, in, out, okfn);
}

在进行了IP包的长度较验和分片检查之后,函数进入ip_nat_fn,它是整个地址转换的核心函数之一: 

static unsigned int
ip_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 ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
struct ip_nat_info *info;
/* maniptype通过调用HOOK2MAINIP宏判断Hook点,指明转换类型是源地址转换还是目的地址转换,为0(IP_NAT_MANIP_SRC)表示源地址转换,为1(IP_NAT_MANIP_DST)表示目的地址转换 */
enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);

/* 前面函数中已经处理过分片的情况,这里应该不会再出现分片包了. */
IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
       & htons(IP_MF|IP_OFFSET)));

/*因为地址转换会修改数据包,所以这里先初始化将其设置为“未修改”标志,后面进行数据包修改时再来重置这个标志*/
(*pskb)->nfcache |= NFC_UNKNOWN;

/* If we had a hardware checksum before, it's now invalid */
if ((*pskb)->ip_summed == CHECKSUM_HW)
if (skb_checksum_help(*pskb, (out == NULL)))
return NF_DROP;

/*取得数据包的连接状态*/
ct = ip_conntrack_get(*pskb, &ctinfo);
/* 如果找不到对应连接,则应该直接放行它,而不再对其进行转换处理,特别地,ICMP重定向报文将会被丢弃*/
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;
}

/*判断连接状态,调用相应的处理函数*/
switch (ctinfo) {
case IP_CT_RELATED:
case IP_CT_RELATED+IP_CT_IS_REPLY:
if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
if (!icmp_reply_translation(pskb, ct, maniptype,
    CTINFO2DIR(ctinfo)))
return NF_DROP;
else
return NF_ACCEPT;
}
/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
case IP_CT_NEW:
info = &ct->nat.info;

/* 观察这个新建封包是否已经被NAT模块修改过了,如果没有,进一步调用ip_nat_rule_find函数*/
if (!ip_nat_initialized(ct, maniptype)) {
unsigned int ret;

/* LOCAL_IN hook doesn't have a chain!  */
if (hooknum == NF_IP_LOCAL_IN)
ret = alloc_null_binding(ct, info, hooknum);
else
ret = ip_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 */
IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
     || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
info = &ct->nat.info;
}

IP_NF_ASSERT(info);
return nat_packet(ct, ctinfo, hooknum, pskb);
}

我们假设这是一个刚刚进入Linux的数据包,它是一个新建状态的连接,首先调用ip_nat_initialized函数判断它是否已经被地址转换例程修改过,即是否已经设置了相应转换类型的标志位: 
static inline int ip_nat_initialized(struct ip_conntrack *conntrack, 
     enum ip_nat_manip_type manip) 

/*如果是源地址转换,即测试源地址转换位,否则,测试目的地址转换位*/ 
if (manip == IP_NAT_MANIP_SRC) 
return test_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status); 
return test_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status); 


对于一个中转没有被修改过的包,ip_nat_rule_find函数将会被调用,以作进一步处理。 

[size=4]ip_nat_rule_find[/size] 

ip_nat_rule_find 函数,从名称上我们就可以看出,它的含义是“NAT 规则查找”,它是一个规则匹配函数,其最重要的工作,就是调用ipt_do_table函数进行规则的检测,这个函数我们在包过滤一章已经对它进行了详细的分析,当ipt_do_table函数发现数据包匹配一条源地址转换的规则时,则会调用SNAT模块的target函数。 
int ip_nat_rule_find(struct sk_buff **pskb,
     unsigned int hooknum,
     const struct net_device *in,
     const struct net_device *out,
     struct ip_conntrack *ct,
     struct ip_nat_info *info)
{
int ret;

/*NAT规则匹配*/
ret = ipt_do_table(pskb, hooknum, in, out, &nat_table, NULL);

if (ret == NF_ACCEPT) {
if (!ip_nat_initialized(ct, HOOK2MANIP(hooknum)))
/* NUL mapping */
ret = alloc_null_binding(ct, info, hooknum);
}
return ret;
}

前面我们已经讨论过,SNAT注册的的target函数是ipt_snat_target,这个函数,首先取得规则中“转换后地址”的信息,然后将工作交给ip_nat_setup_info进一步处理: 
/* Source NAT */
static unsigned int ipt_snat_target(struct sk_buff **pskb,
    const struct net_device *in,
    const struct net_device *out,
    unsigned int hooknum,
    const void *targinfo,
    void *userinfo)
{
struct ip_conntrack *ct;
enum ip_conntrack_info ctinfo;
/*取得规则中的target部份*/
const struct ip_nat_multi_range_compat *mr = targinfo;

IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);

/*取得数据包的连接*/
ct = ip_conntrack_get(*pskb, &ctinfo);

/* Connection must be valid and new. */
IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED
                    || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
IP_NF_ASSERT(out);

return ip_nat_setup_info(ct, &mr->range[0], hooknum);
}

[size=4]ip_nat_setup_info[/size] 

ip_nat_setup_info 函数是地址转换中又一个非常重要的函数: 

unsigned int
ip_nat_setup_info(struct ip_conntrack *conntrack, /*数据包对应的连接*/
  const struct ip_nat_range range, /*转换后的地址池*/
  unsigned int hooknum) /*Hook点*/
{
struct ip_conntrack_tuple curr_tuple, new_tuple;
struct ip_nat_info *info = &conntrack->nat.info;
int have_to_hash = !(conntrack->status & IPS_NAT_DONE_MASK);
enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);

IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
     || hooknum == NF_IP_POST_ROUTING
     || hooknum == NF_IP_LOCAL_IN
     || hooknum == NF_IP_LOCAL_OUT);
BUG_ON(ip_nat_initialized(conntrack, 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) */
invert_tuplepr(&curr_tuple,
       &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);

get_unique_tuple(&new_tuple, &curr_tuple, range, conntrack, maniptype);

if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct ip_conntrack_tuple reply;

/* Alter conntrack table so will recognize replies. */
invert_tuplepr(&reply, &new_tuple);
ip_conntrack_alter_reply(conntrack, &reply);

/* Non-atomic: we own this at the moment. */
if (maniptype == IP_NAT_MANIP_SRC)
conntrack->status |= IPS_SRC_NAT;
else
conntrack->status |= IPS_DST_NAT;
}

/* Place in source hash if this is the first time. */
if (have_to_hash) {
unsigned int srchash
= hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
      .tuple);
WRITE_LOCK(&ip_nat_lock);
list_add(&info->bysource, &bysource[srchash]);
WRITE_UNLOCK(&ip_nat_lock);
}

/* It's done. */
if (maniptype == IP_NAT_MANIP_DST)
set_bit(IPS_DST_NAT_DONE_BIT, &conntrack->status);
else
set_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);

return NF_ACCEPT;
}

这个函数转换来转换去,让人头大,慢慢抽丝拨茧先。首先调用invert_tuplepr取得一个当前数据包对应的replay tuple,然后对其取反得到一个curr_tuple,接着调用get_unique_tuple函数: 
static void
get_unique_tuple(struct ip_conntrack_tuple *tuple,
 const struct ip_conntrack_tuple *orig_tuple,
 const struct ip_nat_range *range,
 struct ip_conntrack *conntrack,
 enum ip_nat_manip_type maniptype)
{
struct ip_nat_protocol *proto
= ip_nat_find_proto(orig_tuple->dst.protonum);

/* 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 (!ip_nat_used_tuple(tuple, conntrack))
return;
}
}

/* 2) Select the least-used IP/proto combination in the given
   range. */
*tuple = *orig_tuple;
find_best_ips_proto(tuple, range, conntrack, maniptype);

/* 3) The per-protocol part of the manip is made to map into
   the range to make a unique tuple. */

/* 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))
    && !ip_nat_used_tuple(tuple, conntrack))
return;

/* Last change: get protocol to try to obtain unique tuple. */
proto->unique_tuple(tuple, range, maniptype, conntrack);
}

  回复于:2007-01-27 21:44:14

第一个值得关注的是find_appropriate_src函数,它主要是在一个以bysource为首的链表中进行遍历查找,这是做什么?我们初始化NAT的时候,对bysource进行过处始化,所以,到目前为止,这个链表还是空的,我们暂时跳过对这个函数的讨论,函数接下来执行: 

/*将curr_tuple赋值给的new_tuple*/
*tuple = *orig_tuple;
find_best_ips_proto(tuple, range, conntrack, maniptype);

range中包含了规则中转换后地址的信息,find_best_ips_proto函数用转换后地址修改new_tuple: 

static void
find_best_ips_proto(struct ip_conntrack_tuple *tuple,
    const struct ip_nat_range *range,
    const struct ip_conntrack *conntrack,
    enum ip_nat_manip_type maniptype)
{
u_int32_t *var_ipp;
/* Host order */
u_int32_t minip, maxip, j;

if (!(range->flags & IP_NAT_RANGE_MAP_IPS))
return;

if (maniptype == IP_NAT_MANIP_SRC)
var_ipp = &tuple->src.ip;
else
var_ipp = &tuple->dst.ip;

/* Fast path: only one choice. */
if (range->min_ip == range->max_ip) {
*var_ipp = range->min_ip;
return;
}

minip = ntohl(range->min_ip);
maxip = ntohl(range->max_ip);
j = jhash_2words(tuple->src.ip, tuple->dst.ip, 0);
*var_ipp = htonl(minip + j % (maxip - minip + 1));
}

如果是源地址转换,var_ipp指针指向tuple中的源地址: 
var_ipp = &tuple->src.ip;
然后,用range中的转换后地址替换它: 
if (range->min_ip == range->max_ip) {
*var_ipp = range->min_ip;
return;
}

这样,再返回至ip_nat_setup_info函数时,我们已经得到了一个根据规则中转换后地址修改过的new_tuple,接着,一个小判断,以确定确实是被修改过,然后,用它替换连接表中数据包的replay tuple: 

if (!ip_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct ip_conntrack_tuple reply;

/* 根据new tuple取反,得到转换后地址的reply tuple */
invert_tuplepr(&reply, &new_tuple);
/*修改连接跟踪表中的reply tuple*/
ip_conntrack_alter_reply(conntrack, &reply);

/* 设置状态标志位 */
if (maniptype == IP_NAT_MANIP_SRC)
conntrack->status |= IPS_SRC_NAT;
else
conntrack->status |= IPS_DST_NAT;
}

我们忽略了太多的细节,如指定协议后的端口转换、转换后地址为地址池的情况等等,但是,这并不影响我们了解整个NAT的全过程,还是继续我们的例子:一个192.186.0.1至100.100.100.100的WEB访问,被修改为100.100.100.1的连接跟随表被修改的流程如下图所示: 
 
在修改了数据包对应的连接跟踪表后,函数将返回至ip_nat_fn中,在函数的最后一句,调用了nat_packet修改数据包的来源地址: 
/* Do packet manipulations according to ip_nat_setup_info. */
unsigned int nat_packet(struct ip_conntrack *ct,
enum ip_conntrack_info ctinfo,
unsigned int hooknum,
struct sk_buff **pskb)
{
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
unsigned long statusbit;
enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);
/*根据转换类型,设置状态标位位*/
if (mtype == IP_NAT_MANIP_SRC)
statusbit = IPS_SRC_NAT;
else
statusbit = IPS_DST_NAT;

/* 应答的情况,暂时不考虑它 */
if (dir == IP_CT_DIR_REPLY)
statusbit ^= IPS_NAT_MASK;

/* Non-atomic: these bits don't change. */
if (ct->status & statusbit) {
struct ip_conntrack_tuple target;

/* 取得修改后replay tuple,并取反,对于源地址转换,就应该中target中的源地址替换IP包中的源地址 */
invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);
/*根据replay tuple中的地址信息,修改数据包*/
if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
return NF_DROP;
}
return NF_ACCEPT;
}

数据包的修改工作,是在mainip_pkt中完成的: 

static int
manip_pkt(u_int16_t proto,
  struct sk_buff **pskb,
  unsigned int iphdroff,
  const struct ip_conntrack_tuple *target,
  enum ip_nat_manip_type maniptype)
{
struct iphdr *iph;

/*修改数据包,置相应标志位*/
(*pskb)->nfcache |= NFC_ALTERED;
if (!skb_ip_make_writable(pskb, iphdroff + sizeof(*iph)))
return 0;
/*取得IP首部*/
iph = (void *)(*pskb)->data + iphdroff;

/* 高层协议部份暂时不考虑 */
if (!ip_nat_find_proto(proto)->manip_pkt(pskb, iphdroff,
                                         target, maniptype))
return 0;

iph = (void *)(*pskb)->data + iphdroff;

/*源地址转换,修改IP包中的来源地址*/
if (maniptype == IP_NAT_MANIP_SRC) {
iph->check = ip_nat_cheat_check(~iph->saddr, target->src.ip,
iph->check);
iph->saddr = target->src.ip;
} else {
iph->check = ip_nat_cheat_check(~iph->daddr, target->dst.ip,
iph->check);
iph->daddr = target->dst.ip;
}
return 1;
}

这样,转换后的数据就被发送出去了。但是,这只是出去的数据包,绝大多数情况下,回来的数据包将再次进入地址转换模块。 

[size=4]应答的包[/size] 

对于应答的数据包,同样会进入ip_nat_fn函数,当判断了该数据包的连接状态,除了ICMP协议的应答需要特需处理外,数据包同样会进入nat_packet函数: 
switch (ctinfo) {
……
}
……
return nat_packet(ct, ctinfo, hooknum, pskb);

函数取得对应连接跟踪表的tuple,因为连接表中有转换前的地址信息,所以这里取反,用取反的tuple中的目的地址(即原来的来源地址)修改数据包。这样,整个NAT就建立起来了: 
/* Do packet manipulations according to ip_nat_setup_info. */
unsigned int nat_packet(struct ip_conntrack *ct,
enum ip_conntrack_info ctinfo,
unsigned int hooknum,
struct sk_buff **pskb)
{
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
unsigned long statusbit;
enum ip_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 ip_conntrack_tuple target;

/* We are aiming to look like inverse of other direction. */
invert_tuplepr(&target, &ct->tuplehash[!dir].tuple);

if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
return NF_DROP;
}
return NF_ACCEPT;
}

目的地址转换的流程和原理与源地址转换是一样的。呵呵。 

当然,这只是最基本,最简单的流程,做为一个完整的地址转换,存在一个地址对应多条连接(在查块初始化的时候,遇到过初始化hash表,呵呵,就是拿来做这个的),另外,如九贱在连接跟踪的实现中,叙述过动态协议的相关内容,NAT也要对此做相应的处理等等。 

九贱将在下一篇文章,来分析地址转换的这些高级内容。

[ 本帖最后由 独孤九贱 于 2007-1-27 21:57 编辑 ]

  回复于:2007-01-27 23:52:26

谢谢九贱了,呵呵,最近内功修炼的不错嘛^_^

  回复于:2007-01-29 09:47:33

好!!!! 
多谢九贱兄啦

  回复于:2007-01-29 15:38:54

好!!!! 
顶,九贱!

  回复于:2007-01-29 16:15:01

好文一定要顶 
收藏,有时间细细看一下

  回复于:2007-04-27 13:36:16

不错,谢谢! 共享!!

  回复于:2008-01-23 15:57:31

有拜读了你的一篇大作,感觉非常不错,继续关注你其它的帖子!!!!!!!!!!

  回复于:2008-02-23 11:50:47

深入浅出, 简单明了!

  回复于:2008-10-14 16:12:15

非常好,顶一个! 
很佩服九贱读代码的能力~ 
能否说说心得,呵呵。。。

  回复于:2008-10-14 19:22:38

这帖子怎么又被翻出来了!:lol:

  回复于:2009-04-20 16:38:57

好文啊,期待九贱兄一下个大作

  回复于:2009-05-25 20:33:09

很强大!




原文链接:
转载请注明作者名及原文出处




Copyright © 2001-2006 ChinaUnix.net   All Rights Reserved

感谢所有关心和支持过ChinaUnix的朋友们

京ICP证041476号

阅读(2152) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~