首页 > 代码库 > Cloud Foundry中warden的网络设计实现——iptable规则配置

Cloud Foundry中warden的网络设计实现——iptable规则配置

       

        在Cloud Foundry v2版本中,该平台使用warden技术来实现用户应用实例运行的资源控制与隔离。


        简要的介绍下warden,就是dea_ng如果需要运行用户应用实例(本文暂不考虑warden container提供staging打包环境),则发送相应请求给warden server,由warden server来创建warden container,并在warden container内部运行应用实例,而warden container的具体实现中使用cgroups等内核虚拟化技术,保证了container内进程组的资源控制与隔离。warden的架构与实现可以参考我的博文:Cloud Foundry中warden的架构与实现。



warden网络架构图


        warden container的功能可以认为与传统的虚拟机类似,只是传统的虚拟机通过Hypervisor等技术来实现资源的控制与隔离,而warden container通过虚拟化内核来达到该目的。而在网络方面,warden container技术创建出一块虚拟网卡,专门供warden container内部使用,另外为warden container内部的虚拟网卡在warden server所在的宿主机上也配对了一块虚拟网卡,充当container的外部网关。仅仅创建出两块虚拟网卡,原则上可以保证物理上的“连通”,但是却很难做到网络间通信的“联通”,故在物理资源以及虚拟物理资源完备的情况,warden server在宿主机上通过iptables设计并添加了多条规则,保证整个网络的通信可以满足warden container的要求,同时又不影响宿主机的通信。


        以下是warden server所在宿主机的物理环境、iptables所在的网络层、操作系统、用户应用层之间的关系图:


warden container网络通信方式


        在Cloud Foundry中,由于warden container用来运行用户应用实例,而应用用户实例与外界的通信在Cloud Foundry中目前无外乎两种:
  1. 用户应用实例作为一个web server,提供HTTP访问服务;
  2. 用户应用实例使用service服务时,建立与service instance的连接。
        当然有些Cloud Foundry平台开发者肯定会认为用户应用实例,可以作为一个client端,通过url访问其他应用实例或者访问云外的资源。在这里需要申明的是,首先Cloud Foundry集群默认对应用实例不暴露ip;其次,假设Cloud Foundry内部不存在或者不能连接一个强大的DNS server,用来解析外部资源的url。

        本文即将以上两点作为主要研究的内容,进行warden所在宿主机的网络分析,集中于iptables一块。


warden iptables配置之net.sh文件

       
        在warden的实现过程中,关于iptables的配置主要包括两个部分:
  1. 启动warden server时,调用文件warden/warden/root/linux/net.sh,制定warden server所在宿主机的部分iptables规则;
  2. 在创建warden container的时候,调用文件warden/warden/root/linux/skeleton/net.sh,制定由于warden container创建后需要添加的iptables规则(包括仅仅创建一个warden container的iptables设置,以及为一个warden container做端口映射时的iptables设置)。

warden server之net.sh

       

setup_filter

       
        关于warden/warden/root/linux/net.sh,可以看该shell脚本中最为关键的两个方法setup_filter以及setup_nat。以下是setup_filter的源码实现:
function setup_filter() {
  teardown_filter

  # Create or flush forward chain
  iptables -N ${filter_forward_chain} 2> /dev/null || iptables -F ${filter_forward_chain}
  iptables -A ${filter_forward_chain} -j DROP

  # Create or flush default chain
  iptables -N ${filter_default_chain} 2> /dev/null || iptables -F ${filter_default_chain}

  # Always allow established connections to warden containers
  iptables -A ${filter_default_chain} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

  for n in ${ALLOW_NETWORKS}; do
    if [ "$n" == "" ]
    then
      break
    fi

    iptables -A ${filter_default_chain} --destination "$n" --jump RETURN
  done

  for n in ${DENY_NETWORKS}; do
    if [ "$n" == "" ]
    then
      break
    fi

    iptables -A ${filter_default_chain} --destination "$n" --jump DROP
  done

  iptables -A ${filter_default_chain} --jump REJECT

  # Accept packets related to previously established connections
  iptables -I INPUT -m state --state ESTABLISHED,RELATED --jump ACCEPT -m comment --comment 'related-traffic'

  # Reject traffic from containers to host
  if [ "$ALLOW_HOST_ACCESS" != "true" ]; then
    iptables -A INPUT -s ${POOL_NETWORK} --jump REJECT -m comment --comment 'block-traffic-to-host'
  fi

  # Forward outbound traffic via ${filter_forward_chain}
  iptables -A FORWARD -i w-+ --jump ${filter_forward_chain}

  # Forward inbound traffic immediately
  default_interface=$(ip route show | grep default | cut -d' ' -f5 | head -1)
  iptables -I ${filter_forward_chain} -i $default_interface --jump ACCEPT
}


        以下首先逐步分析每个部分iptables的制定的意义,随后将各iptables个则串联起来分析其功能。关于iptables在内核中的简要流程图如下图:


以下逐步分析filter_setup方法:

       1. 在该net.sh文件中,teardown_filter方法所做的工作是:清除某些warden相关的iptable链以及规则。随后的即创建两条链:filter_forward_chain以及filter_forward_chain,加入这两条链先前存在的话,清楚该两条链之后再创建。

       2. 对于已经和warden container建立完毕的连接,iptables都执行ACCEPT操作,代码即:
  # Always allow established connections to warden containers
  iptables -A ${filter_default_chain} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
        由注释可以发现,该规则添加在filter_default_chain链中,当匹配条件为:该数据包表示为是在已建立的连接上传输时,对该数据包采取ACCEPT操作。该规则从功能的角度很好理解,也就是:假如warden container内部发起一个请求,向外建立一个连接,则在该连接上返回数据包时,始终接受这样的数据包。然而,我始终对该规则的底层实现,抱有很大的疑惑。待下文分析了更多的warden 网络背景知识之后,再进行进一步的阐述。

       3.在filter_default_chain中,对于ALLOW_NETWORKS中的网络主机地址,始终采取ACCEPT操作;而对于DENY_NETWORKS中的网络主机地址,使用采取DROP操作。在此,可以简要说明一下在iptables中DROP与REJECT的区别。DROP:直接采取丢弃数据包的操作;REJECT:对于原来的数据包采取丢弃措施,随后向数据包的发送方,返回一个类似于ICMP错误的信息包。

        4.之后的shell代码为:iptables -A ${filter_default_chain} --jump REJECT,由于iptables中一个数<br
据包在同一个链中的规则匹配将从第一条开始匹配,遇到匹配成功则执行相应操作,否则继续向下执行,因此该规则表明这条脸中之前都匹配不成的数据包,内核会采取REJECT操作,将数据包丢弃,并返回一个错误数据包。到此,filter_default_chain中的规则设置基本完毕。

        5.在INPUT链中添加规则,所谓INPUT链,即当网卡发现接收到的网络数据包的目的IP,与网卡的IP一致时,将该数据包放入INPUT链执行规则处理,此处的代码如下:
  # Accept packets related to previously established connections
  iptables -I INPUT -m state --state ESTABLISHED,RELATED --jump ACCEPT -m comment --comment 'related-traffic'

  # Reject traffic from containers to host
  if [ "$ALLOW_HOST_ACCESS" != "true" ]; then
    iptables -A INPUT -s ${POOL_NETWORK} --jump REJECT -m comment --comment 'block-traffic-to-host'
  fi
        在INPUT链中设定规则,保证在warden宿主机上原先与外部建立的连接上,发送来的数据包,都将采取ACCEPT操作。其次通过ALLOW_HOST_ACCESS变量来确定是否允许warden container与warden server所在宿主机之间的通信,若该变量为假,则对于从POOL_NETWORKS中发送来的数据包,都采取REJECT操作,保证warden container内部不能访问warden server所在的宿主机。到此,对于INPUT链的规则制定已经完成。

        6.制定iptables中的FORWARD链,所谓FOWARD
创建warden container之net.sh链,即当网络发现接收的网络数据包的目的IP,与该网卡的IP不一致时,则认为需要为该数据包执行转发(FORWARD)操作,将该数据包放入FORWARD链执行规则处理,此处的代码如下:
  # Forward outbound traffic via ${filter_forward_chain}
  iptables -A FORWARD -i w-+ --jump ${filter_forward_chain}

  # Forward inbound traffic immediately
  default_interface=$(ip route show | grep default | cut -d' ' -f5 | head -1)
  iptables -I ${filter_forward_chain} -i $default_interface --jump ACCEPT
        其中第一条规则表示:对于从网络设备接口名字为‘w-+’(正则表达式,也就是为warden container而虚拟出的虚拟网卡的设备名字)发来,同时又需要执行转发工作的数据包,将该数据包跳转至filter_forward_chain链。这样保证了,所有从warden container发出,又不是发往宿主机的网卡的数据包,进入FORWARD链后直接进入filter_forward_chain链。
 
        而后的规则表明,在filter_forward_chain中的数据包,只要是来自于default_interface,都将采取ACCEPT操作。也就是说,当发现filter_forward_chain中的数据包的来源于warden server所在宿主机的物理网卡时,立即对该数据包采取ACCEPT操作。

        到目前为止,这个net.sh文件中的filter_setup方法已经分步骤分析完毕,然后关于整个执行流,读者可能仍然会有不小的疑惑,比如什么时候,绑定filter_default_chain链,使得数据包进入该链,等等。稍后分析完另一个net.sh文件之后,再统一阐述。


setup_nat

      
         以下简单分析setup_nat方法:
function setup_nat() {
     teardown_nat

      # Create prerouting chain
     iptables -t nat -N ${nat_prerouting_chain} 2> /dev/null || true

      # Bind chain to PREROUTING
     (iptables -t nat -S PREROUTING | grep -q "\-j ${nat_prerouting_chain}\b") ||
            iptables -t nat -A PREROUTING             --jump ${nat_prerouting_chain}

      # Bind chain to OUTPUT (for traffic originating from same host)
     (iptables -t nat -S OUTPUT | grep -q "\-j ${nat_prerouting_chain}\b") ||
            iptables -t nat -A OUTPUT             --out-interface "lo"             --jump ${nat_prerouting_chain}

      # Create postrouting chain
      iptables -t nat -N ${nat_postrouting_chain} 2> /dev/null || true
      # Bind chain to POSTROUTING
      (iptables -t nat -S POSTROUTING | grep -q "\-j ${nat_postrouting_chain}\b") ||
            iptables -t nat -A POSTROUTING             --jump ${nat_postrouting_chain}

      # Enable NAT for traffic coming from containers
      (iptables -t nat -S ${nat_postrouting_chain} | grep -q "\-j SNAT\b") ||
            iptables -t nat -A ${nat_postrouting_chain}             --source ${POOL_NETWORK}             --jump SNAT             --to $(external_ip)
}

        该部分的内容比较容易理解:

        1.首先创建nat_prerouting_chain 链,随后将所有的PREROUTING链中的数据包都跳转至nat_prerouting_chain;

        2.随后将链绑定在nat_prerouting_chain上,保证通过lo网络设备的数据包,都跳转到该链;

        3.创建nat_postrouting_chain链,保证将默认的POSTROUTING链中的所有数据包都跳转至创建的该链;

        4.为从warden container中发出的数据包都采取SNAT处理,使得数据包的源IP都替换为warden server宿主机的IP。


        其实在完成以上这两步之后,还执行了以下代码:

 # Enable forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward

        加以保证转发工作可以有效,具体内容可以查看:/proc/sys/net/ipv4/*的各部分作用。



创建warden container之net.sh

       

        前篇分析的是,warden server启动的时候,对warden server所在的宿主机进行的iptbales设置,在整个过程中,不涉及任何的container。而这一章节,将介绍分析warden server在启动某个具体warden container的时候,再对warden server所在的宿主机设置iptables规则。该文件地址如下:warden/warden/root/linux/skeleton/net.sh。


setup_filter


        首先分析setup_filter方法,源码如下:

function setup_filter() {
       teardown_filter

       # Create instance chain
       iptables -N ${filter_instance_chain}
       iptables -A ${filter_instance_chain}            --goto ${filter_default_chain}

       # Bind instance chain to forward chain
       iptables -I ${filter_forward_chain} 2            --in-interface ${network_host_iface}            --goto ${filter_instance_chain}

       # Create instance log chain
       iptables -N ${filter_instance_log_chain}
       iptables -A ${filter_instance_log_chain}            -p tcp -m conntrack --ctstate NEW,UNTRACKED,INVALID -j LOG --log-prefix "${filter_instance_chain} "

       iptables -A ${filter_instance_log_chain}            --jump RETURN
}

        1. 该方法创建了关于instance的链,随后,将该warden container instance链内的所有数据包都跳至filter_default_chain,也就是在前一章节中提到的filter_default_chain;

        2.绑定该warden container instance的FORWARD链至filter_foward链,也就是说,所有进入filter_foward_chain的数据包,根据数据包从哪个网络接口设备来,进入相应的filter_instance_chain链。也就是说,从network_host_iface网络设备“接收”到的数据包(network_host_iface即为network_container_iface="w-${id}-1"),都转至filter_instance_chain。

        3.创建log chain。



setup_nat

       

        在创建warden container的时候,也会设置nat,源码如下:

function setup_nat() {
      teardown_nat
      # Create instance chain
      iptables -t nat -N ${nat_instance_chain}
      # Bind instance chain to prerouting chain
      iptables -t nat -A ${nat_prerouting_chain}              --jump ${nat_instance_chain}
}

        简要分析即为:创建相应的nat_instance_chain,随后将nat_prerouting_chain中的数据包都跳转至该nat_instance_chain。


net_in之端口映射实现

       

        使得Cloud Foundry内warden container的用户应用实例可以为外部提供web服务,那么warden container和warden server所在宿主机进行一个端口映射操作,也就是warden server提供的net_in接口,真正的执行部分,即为创建容器的该net.sh文件中的in参数执行。


        以下是net_in具体操作的源码实现:

 "in")
       if [ -z "${HOST_PORT:-}" ]; then
           echo "Please specify HOST_PORT..." 1>&2
           exit 1
       fi
       if [ -z "${CONTAINER_PORT:-}" ]; then
           echo "Please specify CONTAINER_PORT..." 1>&2
           exit 1
       fi
       iptables -t nat -A ${nat_instance_chain}            --protocol tcp            --destination "${external_ip}"            --destination-port "${HOST_PORT}"            --jump DNAT            --to-destination "${network_container_ip}:${CONTAINER_PORT}"
        ;;

        可见该代码块中最为重要的部分为,创建DNAT规则转换的部分。也就是,在相应的instance规则链中,对于TCP数据包,如果该数据包的目的IP和目的PORT为warden server所在宿主机的IP和映射的PORT,则执行DNAT转换,转换为warden container IP和warden container的端口。


总结

      

        以上便是对Cloud Foundry v2中warden的网络设计之iptables规则制定,做了简要的分析。然而,在这之中,有很多的设计仅仅是留了接口,并未真正实现。



另,感谢浙江大学VLIS实验室的实习生高相林的共同研究。


转载请注明出处。

这篇文档更多出于我本人的理解,肯定在一些地方存在不足和错误。希望本文能够对接触Cloud Foundry v2中warden模块的人有些帮助,如果你对这方面感兴趣,并有更好的想法和建议,也请联系我。

我的邮箱:shlallen@zju.edu.cn
新浪微博:@莲子弗如清