绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
Netfilter 地址转换的实现(3)
2020-05-26 15:39:37

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


  1. /*将curr_tuple赋值给的new_tuple*/
  2. *tuple = *orig_tuple;
  3.         find_best_ips_proto(tuple, range, conntrack, maniptype);
复制代码

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

  1. static void
  2. find_best_ips_proto(struct ip_conntrack_tuple *tuple,
  3.                     const struct ip_nat_range *range,
  4.                     const struct ip_conntrack *conntrack,
  5.                     enum ip_nat_manip_type maniptype)
  6. {
  7.         u_int32_t *var_ipp;
  8.         /* Host order */
  9.         u_int32_t minip, maxip, j;

  10.         if (!(range->flags & IP_NAT_RANGE_MAP_IPS))
  11.                 return;
  12.         
  13.         if (maniptype == IP_NAT_MANIP_SRC)
  14.                 var_ipp = &tuple->src.ip;
  15.         else
  16.                 var_ipp = &tuple->dst.ip;

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

  22.         minip = ntohl(range->min_ip);
  23.         maxip = ntohl(range->max_ip);
  24.         j = jhash_2words(tuple->src.ip, tuple->dst.ip, 0);
  25.         *var_ipp = htonl(minip + j %% (maxip - minip + 1));
  26. }
复制代码

如果是源地址转换,var_ipp指针指向tuple中的源地址:

  1. var_ipp = &tuple->src.ip;
复制代码

然后,用range中的转换后地址替换它:

  1.         if (range->min_ip == range->max_ip) {
  2.                 *var_ipp = range->min_ip;
  3.                 return;
  4.         }
复制代码

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

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

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

  7.                 /* 设置状态标志位 */
  8.                 if (maniptype == IP_NAT_MANIP_SRC)
  9.                         conntrack->status |= IPS_SRC_NAT;
  10.                 else
  11.                         conntrack->status |= IPS_DST_NAT;
  12.         }
复制代码

我们忽略了太多的细节,如指定协议后的端口转换、转换后地址为地址池的情况等等,但是,这并不影响我们了解整个NAT的全过程,还是继续我们的例子:一个192.186.0.1至100.100.100.100的WEB访问,被修改为100.100.100.1的连接跟随表被修改的流程如下图所示:

在修改了数据包对应的连接跟踪表后,函数将返回至ip_nat_fn中,在函数的后一句,调用了nat_packet修改数据包的来源地址:

  1. /* Do packet manipulations according to ip_nat_setup_info. */
  2. unsigned int nat_packet(struct ip_conntrack *ct,
  3.                         enum ip_conntrack_info ctinfo,
  4.                         unsigned int hooknum,
  5.                         struct sk_buff **pskb)
  6. {
  7.         enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
  8.         unsigned long statusbit;
  9.         enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);
  10.         /*根据转换类型,设置状态标位位*/
  11.         if (mtype == IP_NAT_MANIP_SRC)
  12.                 statusbit = IPS_SRC_NAT;
  13.         else
  14.                 statusbit = IPS_DST_NAT;

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

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

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

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

  1. static int
  2. manip_pkt(u_int16_t proto,
  3.           struct sk_buff **pskb,
  4.           unsigned int iphdroff,
  5.           const struct ip_conntrack_tuple *target,
  6.           enum ip_nat_manip_type maniptype)
  7. {
  8.         struct iphdr *iph;
  9.         
  10.         /*修改数据包,置相应标志位*/
  11.         (*pskb)->nfcache |= NFC_ALTERED;
  12.         if (!skb_ip_make_writable(pskb, iphdroff + sizeof(*iph)))
  13.                 return 0;
  14.         /*取得IP首部*/
  15.         iph = (void *)(*pskb)->data + iphdroff;

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

  20.         iph = (void *)(*pskb)->data + iphdroff;
  21.         
  22.         /*源地址转换,修改IP包中的来源地址*/
  23.         if (maniptype == IP_NAT_MANIP_SRC) {
  24.                 iph->check = ip_nat_cheat_check(~iph->saddr, target->src.ip,
  25.                                                 iph->check);
  26.                 iph->saddr = target->src.ip;
  27.         } else {
  28.                 iph->check = ip_nat_cheat_check(~iph->daddr, target->dst.ip,
  29.                                                 iph->check);
  30.                 iph->daddr = target->dst.ip;
  31.         }
  32.         return 1;
  33. }
复制代码

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

应答的包

对于应答的数据包,同样会进入ip_nat_fn函数,当判断了该数据包的连接状态,除了ICMP协议的应答需要特需处理外,数据包同样会进入nat_packet函数:

  1. switch (ctinfo) {
  2. ……
  3. }
  4. ……
  5. return nat_packet(ct, ctinfo, hooknum, pskb);
复制代码

函数取得对应连接跟踪表的tuple,因为连接表中有转换前的地址信息,所以这里取反,用取反的tuple中的目的地址(即原来的来源地址)修改数据包。这样,整个NAT就建立起来了:

  1. /* Do packet manipulations according to ip_nat_setup_info. */
  2. unsigned int nat_packet(struct ip_conntrack *ct,
  3.                         enum ip_conntrack_info ctinfo,
  4.                         unsigned int hooknum,
  5.                         struct sk_buff **pskb)
  6. {
  7.         enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
  8.         unsigned long statusbit;
  9.         enum ip_nat_manip_type mtype = HOOK2MANIP(hooknum);

  10.         if (mtype == IP_NAT_MANIP_SRC)
  11.                 statusbit = IPS_SRC_NAT;
  12.         else
  13.                 statusbit = IPS_DST_NAT;

  14.         /* Invert if this is reply dir. */
  15.         if (dir == IP_CT_DIR_REPLY)
  16.                 statusbit ^= IPS_NAT_MASK;

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

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

  22.                 if (!manip_pkt(target.dst.protonum, pskb, 0, &target, mtype))
  23.                         return NF_DROP;
  24.         }
  25.         return NF_ACCEPT;
  26. }
复制代码

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

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


文章来源CU社区:    Netfilter 地址转换的实现


分享好友

分享这个小栈给你的朋友们,一起进步吧。

内核源码
创建时间:2020-05-18 13:36:55
内核源码精华帖内容汇总
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~