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

分享好友

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

ip_nat_out

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

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

  14.                 if (!*pskb)
  15.                         return NF_STOLEN;
  16.         }

  17.         return ip_nat_fn(hooknum, pskb, in, out, okfn);
  18. }
复制代码



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

  1. static unsigned int
  2. ip_nat_fn(unsigned int hooknum,
  3.           struct sk_buff **pskb,
  4.           const struct net_device *in,
  5.           const struct net_device *out,
  6.           int (*okfn)(struct sk_buff *))
  7. {
  8.         struct ip_conntrack *ct;
  9.         enum ip_conntrack_info ctinfo;
  10.         struct ip_nat_info *info;
  11.         /* maniptype通过调用HOOK2MAINIP宏判断Hook点,指明转换类型是源地址转换还是目的地址转换,为0(IP_NAT_MANIP_SRC)表示源地址转换,为1(IP_NAT_MANIP_DST)表示目的地址转换 */
  12.         enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);

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

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

  18.         /* If we had a hardware checksum before, it's now invalid */
  19.         if ((*pskb)->ip_summed == CHECKSUM_HW)
  20.                 if (skb_checksum_help(*pskb, (out == NULL)))
  21.                         return NF_DROP;
  22.         
  23. /*取得数据包的连接状态*/
  24.         ct = ip_conntrack_get(*pskb, &ctinfo);
  25.         /* 如果找不到对应连接,则应该直接放行它,而不再对其进行转换处理,特别地,ICMP重定向报文将会被丢弃*/
  26.         if (!ct) {
  27.                 /* Exception: ICMP redirect to new connection (not in
  28.                    hash table yet).  We must not let this through, in
  29.                    case we're doing NAT to the same network. */
  30.                 if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
  31.                         struct icmphdr _hdr, *hp;

  32.                         hp = skb_header_pointer(*pskb,
  33.                                                 (*pskb)->nh.iph->ihl*4,
  34.                                                 sizeof(_hdr), &_hdr);
  35.                         if (hp != NULL &&
  36.                             hp->type == ICMP_REDIRECT)
  37.                                 return NF_DROP;
  38.                 }
  39.                 return NF_ACCEPT;
  40.         }

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

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

  58.                         /* LOCAL_IN hook doesn't have a chain!  */
  59.                         if (hooknum == NF_IP_LOCAL_IN)
  60.                                 ret = alloc_null_binding(ct, info, hooknum);
  61.                         else
  62.                                 ret = ip_nat_rule_find(pskb, hooknum,
  63.                                                        in, out, ct,
  64.                                                        info);

  65.                         if (ret != NF_ACCEPT) {
  66.                                 return ret;
  67.                         }
  68.                 } else
  69.                         DEBUGP("Already setup manip %%%%s for ct %%%%p\n",
  70.                                maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
  71.                                ct);
  72.                 break;

  73.         default:
  74.                 /* ESTABLISHED */
  75.                 IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
  76.                              || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
  77.                 info = &ct->nat.info;
  78.         }

  79.         IP_NF_ASSERT(info);
  80.         return nat_packet(ct, ctinfo, hooknum, pskb);
  81. }
复制代码



我们假设这是一个刚刚进入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函数将会被调用,以作进一步处理。

ip_nat_rule_find

ip_nat_rule_find 函数,从名称上我们就可以看出,它的含义是“NAT 规则查找”,它是一个规则匹配函数,其重要的工作,就是调用ipt_do_table函数进行规则的检测,这个函数我们在包过滤一章已经对它进行了详细的分析,当ipt_do_table函数发现数据包匹配一条源地址转换的规则时,则会调用SNAT模块的target函数。

  1. int ip_nat_rule_find(struct sk_buff **pskb,
  2.                      unsigned int hooknum,
  3.                      const struct net_device *in,
  4.                      const struct net_device *out,
  5.                      struct ip_conntrack *ct,
  6.                      struct ip_nat_info *info)
  7. {
  8.         int ret;

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

  11.         if (ret == NF_ACCEPT) {
  12.                 if (!ip_nat_initialized(ct, HOOK2MANIP(hooknum)))
  13.                         /* NUL mapping */
  14.                         ret = alloc_null_binding(ct, info, hooknum);
  15.         }
  16.         return ret;
  17. }
复制代码



前面我们已经讨论过,SNAT注册的的target函数是ipt_snat_target,这个函数,首先取得规则中“转换后地址”的信息,然后将工作交给ip_nat_setup_info进一步处理:

  1. /* Source NAT */
  2. static unsigned int ipt_snat_target(struct sk_buff **pskb,
  3.                                     const struct net_device *in,
  4.                                     const struct net_device *out,
  5.                                     unsigned int hooknum,
  6.                                     const void *targinfo,
  7.                                     void *userinfo)
  8. {
  9.         struct ip_conntrack *ct;
  10.         enum ip_conntrack_info ctinfo;
  11.         /*取得规则中的target部份*/
  12.         const struct ip_nat_multi_range_compat *mr = targinfo;

  13.         IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
  14.         
  15. /*取得数据包的连接*/
  16.         ct = ip_conntrack_get(*pskb, &ctinfo);

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

  21.         return ip_nat_setup_info(ct, &mr->range[0], hooknum);
  22. }
复制代码



ip_nat_setup_info

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

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

  10.         IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
  11.                      || hooknum == NF_IP_POST_ROUTING
  12.                      || hooknum == NF_IP_LOCAL_IN
  13.                      || hooknum == NF_IP_LOCAL_OUT);
  14.         BUG_ON(ip_nat_initialized(conntrack, maniptype));

  15.         /* What we've got will look like inverse of reply. Normally
  16.            this is what is in the conntrack, except for prior
  17.            manipulations (future optimization: if num_manips == 0,
  18.            orig_tp =
  19.            conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */
  20.         invert_tuplepr(&curr_tuple,
  21.                        &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);

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

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

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

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

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

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

  48.         return NF_ACCEPT;
  49. }
复制代码



这个函数转换来转换去,让人头大,慢慢抽丝拨茧先。首先调用invert_tuplepr取得一个当前数据包对应的replay tuple,然后对其取反得到一个curr_tuple,接着调用get_unique_tuple函数:

  1. static void
  2. get_unique_tuple(struct ip_conntrack_tuple *tuple,
  3.                  const struct ip_conntrack_tuple *orig_tuple,
  4.                  const struct ip_nat_range *range,
  5.                  struct ip_conntrack *conntrack,
  6.                  enum ip_nat_manip_type maniptype)
  7. {
  8.         struct ip_nat_protocol *proto
  9.                 = ip_nat_find_proto(orig_tuple->dst.protonum);

  10.         /* 1) If this srcip/proto/src-proto-part is currently mapped,
  11.            and that same mapping gives a unique tuple within the given
  12.            range, use that.

  13.            This is only required for source (ie. NAT/masq) mappings.
  14.            So far, we don't do local source mappings, so multiple
  15.            manips not an issue.  */
  16.         if (maniptype == IP_NAT_MANIP_SRC) {
  17.                 if (find_appropriate_src(orig_tuple, tuple, range)) {
  18.                         DEBUGP("get_unique_tuple: Found current src map\n");
  19.                         if (!ip_nat_used_tuple(tuple, conntrack))
  20.                                 return;
  21.                 }
  22.         }

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

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

  29.         /* Only bother mapping if it's not already in range and unique */
  30.         if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)
  31.              || proto->in_range(tuple, maniptype, &range->min, &range->max))
  32.             && !ip_nat_used_tuple(tuple, conntrack))
  33.                 return;

  34.         /* Last change: get protocol to try to obtain unique tuple. */
  35.         proto->unique_tuple(tuple, range, maniptype, conntrack);
  36. }
复制代码


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


分享好友

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

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

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

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

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

技术专家

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