【Nova】nova-compute代码学习4-防火墙

前面在nova-compute启动和创建虚拟机中都提到防火墙相关的代码,但是一笔带过了,这次做个详细学习

使用Libvirt驱动时,默认的防火墙是nova.virt.libvirt.firewall.IptablesFirewallDriver,而这个防火墙也会使用到Libvirt内置的nwfilter防火墙,nwfilter防火墙允许我们使用XML格式字符串定义nwfilter,每个nwfilter有自己的唯一名称和UUID,里面可以包含很多filter;每个nwfilter可以被其他nwfilter引用,起到复用的目的;当我们定义了一个nwfilter后,Libvirt会将其内容保存到一个特定目录下的文件内,我使用的Ubuntu在/etc/libvirt/nwfilter下,文件名为<nwfilter名称>.xml.

在启动时初始化虚拟机以及创建虚拟机时会使用到防火墙的setup_basic_filtering和prepare_instance_filter来添加实例的规则, 前者是借助Libvirt的nwfilter防火墙来添加一些基础规则,譬如防mac欺骗、防ip欺骗、防arp欺骗等, 后者使用iptables防火墙来添加一些包括服务提供商规则和安全组规则在内的规则。

我们可以在虚拟机实例内,通过篡改mac地址和手动修改IP地址的方式来进行一些会影响整个系统的行为,譬如元数据是与固定IP绑定的,那我可以通过在实例的客户机内手动修改IP地址的方式来窃取其他实例的元数据,为了防止这些欺骗行为,Libvirt的nwfilter提供了很便捷的方式,不过本质也是借助ebtables、iptables这些来实现的。大家可以试一下, 假设你的实例的固定IP是10.0.0.5,虚拟网络的网关为10.0.0.1,你在客户机内手动配置IP为一个没被使用的IP譬如10.0.0.100,然后你ping网关,你会发现是ping不通的。 如果你使用的nova-network,你可以通过brctl show,查看到br100网桥的端口连接了哪些设备, 其中有不少vnet打头的设备就是Libvirt为虚拟机创建的虚拟网卡, 然后通过ebtables -t nat -L,你就能看到与这些虚拟网卡有关的规则, 下面举一例说明防mac欺骗原理

Bridge table: nat

Bridge chain: PREROUTING, entries: 1, policy: ACCEPT
-i vnet0 -j libvirt-I-vnet0                                  -> 入口是vnet0, 那么跳转至libvirt-I-vnet0链

Bridge chain: OUTPUT, entries: 0, policy: ACCEPT

Bridge chain: POSTROUTING, entries: 1, policy: ACCEPT
-o vnet0 -j libvirt-O-vnet0                                      

Bridge chain: libvirt-I-vnet0, entries: 5, policy: ACCEPT
-j I-vnet0-mac                                               -> 跳转至I-vnet0-mac链
-p IPv4 -j I-vnet0-ipv4-ip
-p IPv4 -j I-vnet0-ipv4
-p ARP -j I-vnet0-arp-mac
-p ARP -j I-vnet0-arp-ip

Bridge chain: libvirt-O-vnet0, entries: 2, policy: ACCEPT        
-p IPv4 -j O-vnet0-ipv4
-p IPv6 -j O-vnet0-ipv6

Bridge chain: I-vnet0-mac, entries: 2, policy: ACCEPT            
-s fa:16:3e:36:52:c2 -j RETURN                               -> 源MAC地址是我们创建虚拟机时指定的MAC地址, 那么RETURN进入下一个过滤规则
-j DROP                                                      -> 如果不匹配, 那么丢弃

Bridge chain: I-vnet0-ipv4-ip, entries: 3, policy: ACCEPT
-p IPv4 --ip-src 0.0.0.0 --ip-proto udp -j RETURN
-p IPv4 --ip-src 10.93.192.8 -j RETURN
-j DROP

Bridge chain: I-vnet0-ipv4, entries: 1, policy: ACCEPT
-p IPv4 --ip-src 0.0.0.0 --ip-dst 255.255.255.255 --ip-proto udp --ip-sport 68 --ip-dport 67 -j ACCEPT

Bridge chain: O-vnet0-ipv4, entries: 1, policy: ACCEPT
-p IPv4 --ip-src 10.93.192.1 --ip-proto udp --ip-sport 67 --ip-dport 68 -j ACCEPT

Bridge chain: O-vnet0-ipv6, entries: 1, policy: ACCEPT
-s fa:16:3e:36:52:c2 -d 33:33:0:0:0:0/ff:ff:0:0:0:0 -j DROP

Bridge chain: I-vnet0-arp-mac, entries: 2, policy: ACCEPT
-p ARP --arp-mac-src fa:16:3e:36:52:c2 -j RETURN
-j DROP

Bridge chain: I-vnet0-arp-ip, entries: 2, policy: ACCEPT
-p ARP --arp-ip-src 10.93.192.8 -j RETURN
-j DROP
nova-network的 防火墙目前只支持对入口进行过滤,也就是只能对访问实例的流量进行过滤,通过iptables -t filter -L我们可以看到这些过滤规则, 不同服务在iptables中创建的链会选择性加上自己的binary名,所以nova-compute创建的很多链名前带有'nova-compute'前缀:

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
nova-compute-INPUT  all  --  anywhere             anywhere                                   
nova-network-INPUT  all  --  anywhere             anywhere            
nova-api-metadat-INPUT  all  --  anywhere             anywhere            

Chain FORWARD (policy ACCEPT) 
target     prot opt source               destination                                       
nova-filter-top  all  --  anywhere             anywhere                                  -> 1. 跳转至nova-filter-top链
nova-compute-FORWARD  all  --  anywhere             anywhere            
nova-network-FORWARD  all  --  anywhere             anywhere            
nova-api-metadat-FORWARD  all  --  anywhere             anywhere            

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         
nova-filter-top  all  --  anywhere             anywhere            
nova-compute-OUTPUT  all  --  anywhere             anywhere            
nova-network-OUTPUT  all  --  anywhere             anywhere            
nova-api-metadat-OUTPUT  all  --  anywhere             anywhere            

Chain nova-compute-FORWARD (1 references)
target     prot opt source               destination         
ACCEPT     udp  --  0.0.0.0              255.255.255.255      udp spt:bootpc dpt:bootps

Chain nova-compute-INPUT (1 references)
target     prot opt source               destination         
ACCEPT     udp  --  0.0.0.0              255.255.255.255      udp spt:bootpc dpt:bootps

Chain nova-compute-OUTPUT (1 references)
target     prot opt source               destination         

Chain nova-compute-inst-124979 (1 references)                                            -> 4.对实例的规则由上至下逐条进行过滤
target     prot opt source               destination                                     
DROP       all  --  anywhere             anywhere             state INVALID
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
nova-compute-provider  all  --  anywhere             anywhere                            -> 服务提供商规则链
ACCEPT     udp  --  10.93.192.1          anywhere             udp spt:bootps dpt:bootpc  -> 接收DHCP服务数据包
ACCEPT     all  --  10.93.192.0/20       anywhere                                        -> 允许同一网络内的实例的互相访问
ACCEPT     icmp --  anywhere             anywhere                                         |
ACCEPT     tcp  --  anywhere             anywhere             multiport dports tcpmux:134 |
ACCEPT     tcp  --  anywhere             anywhere             multiport dports 140:439    |
ACCEPT     udp  --  anywhere             anywhere             multiport dports 1:134      |  安全组规则
ACCEPT     udp  --  anywhere             anywhere             multiport dports 140:439    |
ACCEPT     udp  --  anywhere             anywhere             multiport dports 446:65535  |
ACCEPT     tcp  --  anywhere             anywhere             multiport dports 446:65535  |
nova-compute-sg-fallback  all  --  anywhere             anywhere                         -> 安全组回滚链

Chain nova-compute-inst-127228 (1 references)
target     prot opt source               destination         
DROP       all  --  anywhere             anywhere             state INVALID
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
nova-compute-provider  all  --  anywhere             anywhere            
ACCEPT     udp  --  10.93.192.1          anywhere             udp spt:bootps dpt:bootpc
ACCEPT     all  --  10.93.192.0/20       anywhere            
ACCEPT     icmp --  anywhere             anywhere            
ACCEPT     tcp  --  anywhere             anywhere             multiport dports tcpmux:134
ACCEPT     tcp  --  anywhere             anywhere             multiport dports 140:439
ACCEPT     udp  --  anywhere             anywhere             multiport dports 1:134
ACCEPT     udp  --  anywhere             anywhere             multiport dports 140:439
ACCEPT     udp  --  anywhere             anywhere             multiport dports 446:65535
ACCEPT     tcp  --  anywhere             anywhere             multiport dports 446:65535
nova-compute-sg-fallback  all  --  anywhere             anywhere            

Chain nova-compute-local (1 references)
target     prot opt source               destination         
nova-compute-inst-124979  all  --  anywhere             10.93.192.116                    -> 3.目的地址为实例的固定IP, 跳转至实例链
nova-compute-inst-127228  all  --  anywhere             10.93.192.8             

Chain nova-compute-provider (7 references)
target     prot opt source               destination         

Chain nova-compute-sg-fallback (7 references)
target     prot opt source               destination         
DROP       all  --  anywhere             anywhere            

Chain nova-filter-top (2 references)
target     prot opt source               destination         
nova-compute-local  all  --  anywhere             anywhere                               -> 2.跳转至nova-compute-local链
nova-network-local  all  --  anywhere             anywhere            
nova-api-metadat-local  all  --  anywhere             anywhere            
     

下面是相关的代码分析

class NWFilterFirewall(base_firewall.FirewallDriver):

    def __init__(self, virtapi, get_connection, **kwargs):
        ......
        self._libvirt_get_connection = get_connection
        self.static_filters_configured = False
        self.handle_security_groups = False

    # 通过nwfilter名称查找对应的UUID
    def _get_filter_uuid(self, name):
        try:
            # 如果这个name对应的nwfilter存在, 那么就可以通过Libvirt连接查找到其实例;
            # 找到nwfilter实例后, 就解析其XML格式的内容来找到UUID/uuid节点的text
            flt = self._conn.nwfilterLookupByName(name)
            xml = flt.XMLDesc(0)
            doc = etree.fromstring(xml)
            u = doc.find("./uuid").text
        except Exception as e:
            # 如果nwfilter实例不存在或者其没有定义UUID/uuid,
            # 那么我们生成一个新的UUID
            LOG.debug("Cannot find UUID for filter '%s': '%s'" % (name, e))
            u = uuid.uuid4().hex

        LOG.debug("UUID for filter '%s' is '%s'" % (name, u))
        return u
    
    def nova_no_nd_reflection_filter(self):
        # 获取nova-no-nd-reflection nwfilter的uuid
        uuid = self._get_filter_uuid('nova-no-nd-reflection')
        # 返回nova-no-nd-reflection nwfilter的定义内容:
        # 为了免受IPv6下的乐观的重复地址检测(DAD)影响, 对于IPv6下的二层广播数据包一律丢弃
        return '''<filter name='nova-no-nd-reflection' chain='ipv6'>
                  <!-- no nd reflection -->
                  <!-- drop if destination mac is v6 mcast mac addr and
                       we sent it. -->
                  <uuid>%s</uuid>
                  <rule action='drop' direction='in'>
                      <mac dstmacaddr='33:33:00:00:00:00'
                           dstmacmask='ff:ff:00:00:00:00' srcmacaddr='$MAC'/>
                  </rule>
                  </filter>''' % uuid
    
    # 定义nwfilter, 这里存在一个bug, 从libvirt 1.2.7开始, 不允许对同一个UUID的nwfilter进行多次定义, 所以这里会报异常
    def _define_filter(self, xml):
        if callable(xml):
            xml = xml()
        self._conn.nwfilterDefineXML(xml)
    
    # 定义nwfilter容器, 里面包含多个已定义的nwfilter
    def _filter_container(self, name, filters):
        uuid = self._get_filter_uuid(name)
        xml = '''<filter name='%s' chain='root'>
                   <uuid>%s</uuid>
                   %s
                 </filter>''' % (name, uuid,
                 ''.join(["<filterref filter='%s'/>" % (f,) for f in filters]))
        return xml
    
    # 获取nova-allow-dhcp-server nwfilter的定义内容:
    # 允许DHCP数据包进出
    def nova_dhcp_filter(self):
        uuid = self._get_filter_uuid('nova-allow-dhcp-server')
        return '''<filter name='nova-allow-dhcp-server' chain='ipv4'>
                    <uuid>%s</uuid>
                    <rule action='accept' direction='out'
                          priority='100'>
                      <udp srcipaddr='0.0.0.0'
                           dstipaddr='255.255.255.255'
                           srcportstart='68'
                           dstportstart='67'/>
                    </rule>
                    <rule action='accept' direction='in'
                          priority='100'>
                      <udp srcipaddr='$DHCPSERVER'
                           srcportstart='67'
                           dstportstart='68'/>
                    </rule>
                  </filter>''' % uuid
    
    def _ensure_static_filters(self):
        # 以下这些静态规则只需要定义一次
        if self.static_filters_configured:
            return

        filter_set = ['no-mac-spoofing',
                      'no-ip-spoofing',
                      'no-arp-spoofing']

        # 定义nova-no-nd-reflection nwfilter
        self._define_filter(self.nova_no_nd_reflection_filter())
        filter_set.append('nova-no-nd-reflection')
        # 定义nova-nodhcp nwfilter, 包含对no-mac-spoofing、no-ip-spoofing、no-arp-spoofing
        # 和nova-no-nd-reflection这些nwfilter的引用
        self._define_filter(self._filter_container('nova-nodhcp', filter_set))
        filter_set.append('allow-dhcp-server')
        # 定义nova-base nwfilter, 包含对no-mac-spoofing、no-ip-spoofing、no-arp-spoofing、
        # nova-no-nd-reflection和allow-dhcp-server这些nwfilter的引用
        self._define_filter(self._filter_container('nova-base', filter_set))
        # 定义nova-vpn nwfilter, 包含对allow-dhcp-server的引用
        self._define_filter(self._filter_container('nova-vpn',
                                                   ['allow-dhcp-server']))
        # 定义nova-allow-dhcp-server nwfilter, 允许DHCP数据包进出
        self._define_filter(self.nova_dhcp_filter())

        self.static_filters_configured = True
    
    # 根据实例和是否允许DHCP来获取基础的nwfilter列表
    def get_base_filter_list(self, instance, allow_dhcp):
        if pipelib.is_vpn_image(instance['image_ref']):
            base_filter = 'nova-vpn'
        elif allow_dhcp:
            base_filter = 'nova-base'
        else:
            base_filter = 'nova-nodhcp'
        return [base_filter]
    
    # 获取实例的nwfilter名称
    @staticmethod
    def _instance_filter_name(instance, nic_id=None):
        if not nic_id:
            return 'nova-instance-%s' % (instance['name'])
        return 'nova-instance-%s-%s' % (instance['name'], nic_id)
    
    # 获取实例在某个虚拟网络下的filter参数
    def _get_instance_filter_parameters(self, vif):
        parameters = []

        def format_parameter(parameter, value):
            return ("<parameter name='%s' value='%s'/>" % (parameter, value))

        network = vif['network']
        if not vif['network'] or not vif['network']['subnets']:
            return parameters

        v4_subnets = [s for s in network['subnets'] if s['version'] == 4]
        v6_subnets = [s for s in network['subnets'] if s['version'] == 6]

        for subnet in v4_subnets:
            for ip in subnet['ips']:
                parameters.append(format_parameter('IP', ip['address']))

            dhcp_server = subnet.get_meta('dhcp_server')
            if dhcp_server:
                parameters.append(format_parameter('DHCPSERVER', dhcp_server))
        if CONF.use_ipv6:
            for subnet in v6_subnets:
                gateway = subnet.get('gateway')
                if gateway:
                    ra_server = gateway['address'] + "/128"
                    parameters.append(format_parameter('RASERVER', ra_server))

        if CONF.allow_same_net_traffic:
            for subnet in v4_subnets:
                ipv4_cidr = subnet['cidr']
                net, mask = netutils.get_net_and_mask(ipv4_cidr)
                parameters.append(format_parameter('PROJNET', net))
                parameters.append(format_parameter('PROJMASK', mask))

            if CONF.use_ipv6:
                for subnet in v6_subnets:
                    ipv6_cidr = subnet['cidr']
                    net, prefix = netutils.get_net_and_prefixlen(ipv6_cidr)
                    parameters.append(format_parameter('PROJNET6', net))
                    parameters.append(format_parameter('PROJMASK6', prefix))

        return parameters
    
    # 获取实例在指定虚拟网络下的nwfilter定义内容
    def _get_instance_filter_xml(self, instance, filters, vif):
        nic_id = vif['address'].replace(':', '')
        # 获取实例的虚拟网卡对应的nwfilter名称
        instance_filter_name = self._instance_filter_name(instance, nic_id)
        # 获取实例在指定虚拟网络下的filter参数
        parameters = self._get_instance_filter_parameters(vif)
        uuid = self._get_filter_uuid(instance_filter_name)
        # 构建实例在指定虚拟网络下的nwfilter定义内容
        xml = '''<filter name='%s' chain='root'>''' % instance_filter_name
        xml += '<uuid>%s</uuid>' % uuid
        for f in filters:
            xml += '''<filterref filter='%s'>''' % f
            xml += ''.join(parameters)
            xml += '</filterref>'
        xml += '</filter>'
        return xml

    # 设置基础的nwfilter
    def setup_basic_filtering(self, instance, network_info):
        LOG.info(_('Called setup_basic_filtering in nwfilter'),
                 instance=instance)

        if self.handle_security_groups:
            return

        LOG.info(_('Ensuring static filters'), instance=instance)
        # 定义静态nwfilter
        self._ensure_static_filters()

        # 根据是否允许DHCP, 为实例获取基础的nwfilter列表
        nodhcp_base_filter = self.get_base_filter_list(instance, False)
        dhcp_base_filter = self.get_base_filter_list(instance, True)

        for vif in network_info:
            _base_filter = nodhcp_base_filter
            for subnet in vif['network']['subnets']:
                # FLATDHCP和VLAN模式下dhcp_server不为空,
                # 那么需要使用dhcp_base_filter
                if subnet.get_meta('dhcp_server'):
                    _base_filter = dhcp_base_filter
                    break
            # 定义实例在指定虚拟网络下的nwfilter
            self._define_filter(self._get_instance_filter_xml(instance,
                                                              _base_filter,
                                                              vif))

class IptablesFirewallDriver(FirewallDriver):
    
    def __init__(self, virtapi, **kwargs):
        ......
        self.iptables = linux_net.iptables_manager
        self.instance_info = {}
        self.basically_filtered = False
    
    # 移除provider链的所有规则
    def _purge_provider_fw_rules(self):
        self.iptables.ipv4['filter'].empty_chain('provider')
        if CONF.use_ipv6:
            self.iptables.ipv6['filter'].empty_chain('provider')
    
    # 生成provider链的规则
    def _provider_rules(self):
        ctxt = context.get_admin_context()
        ipv4_rules = []
        ipv6_rules = []
        # 这里其实是从数据库获取provider_fw_rules;
        # 字面上看, 这是服务提供商的防火墙规则, 作用于安全组之上
        # 这个feature应该已经要被移除了
        rules = self._virtapi.provider_fw_rule_get_all(ctxt)
        for rule in rules:
            LOG.debug(_('Adding provider rule: %s'), rule['cidr'])
            # 通过cidr获取是针对IPv4还是IPv6
            version = netutils.get_ip_version(rule['cidr'])
            if version == 4:
                fw_rules = ipv4_rules
            else:
                fw_rules = ipv6_rules

            # 获取规则针对的网络协议
            protocol = rule['protocol']
            if version == 6 and protocol == 'icmp':
                protocol = 'icmpv6'

            args = ['-p', protocol, '-s', rule['cidr']]

            if protocol in ['udp', 'tcp']:
                # 当协议为udp或tcp时, from_port, to_port用于指定目的端口范围
                if rule['from_port'] == rule['to_port']:
                    args += ['--dport', '%s' % (rule['from_port'],)]
                else:
                    args += ['-m', 'multiport',
                             '--dports', '%s:%s' % (rule['from_port'],
                                                    rule['to_port'])]
            elif protocol == 'icmp':
                # 当协议为icmp时, from_port用于指定类型, to_port用于指定编码
                icmp_type = rule['from_port']
                icmp_code = rule['to_port']

                if icmp_type == -1:
                    icmp_type_arg = None
                else:
                    icmp_type_arg = '%s' % icmp_type
                    if not icmp_code == -1:
                        icmp_type_arg += '/%s' % icmp_code

                if icmp_type_arg:
                    if version == 4:
                        args += ['-m', 'icmp', '--icmp-type',
                                 icmp_type_arg]
                    elif version == 6:
                        args += ['-m', 'icmp6', '--icmpv6-type',
                                 icmp_type_arg]
            # 策略是丢弃
            args += ['-j DROP']
            fw_rules += [' '.join(args)]
        return ipv4_rules, ipv6_rules
    
    # 添加provider链的规则
    def _build_provider_fw_rules(self):
        # 在iptables防火墙的filter表中添加provider链
        self.iptables.ipv4['filter'].add_chain('provider')
        if CONF.use_ipv6:
            self.iptables.ipv6['filter'].add_chain('provider')
        # 获取服务提供商的防火墙规则, 并添加至provider链
        ipv4_rules, ipv6_rules = self._provider_rules()
        for rule in ipv4_rules:
            self.iptables.ipv4['filter'].add_rule('provider', rule)

        if CONF.use_ipv6:
            for rule in ipv6_rules:
                self.iptables.ipv6['filter'].add_rule('provider', rule)
    
    @utils.synchronized('iptables', external=True)
    def _do_refresh_provider_fw_rules(self):
        # 先删除后添加, 用于刷新服务提供商的防火墙规则
        self._purge_provider_fw_rules()
        self._build_provider_fw_rules()
    
    def refresh_provider_fw_rules(self):
        # 刷新服务提供商的防火墙规则
        self._do_refresh_provider_fw_rules()
        # 使iptables规则生效
        self.iptables.apply()
        
    # 添加基础规则
    def _do_basic_rules(self, ipv4_rules, ipv6_rules, network_info):
        # 丢弃无效包
        ipv4_rules += ['-m state --state ' 'INVALID -j DROP']
        ipv6_rules += ['-m state --state ' 'INVALID -j DROP']

        # 允许已建立连接的包
        ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
        ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']

        # 跳转到provider链
        ipv4_rules += ['-j $provider']
        ipv6_rules += ['-j $provider']
    
    # 添加DHCP相关规则
    def _do_dhcp_rules(self, ipv4_rules, network_info):
        # 获取IPv4子网列表
        v4_subnets = self._get_subnets(network_info, 4)
        # 获取子网的DHCP服务地址列表
        dhcp_servers = [subnet.get_meta('dhcp_server')
            for subnet in v4_subnets if subnet.get_meta('dhcp_server')]

        for dhcp_server in dhcp_servers:
            if dhcp_server:
                # 接收从DHCP服务发送的响应包
                ipv4_rules.append('-s %s -p udp --sport 67 --dport 68 '
                                  '-j ACCEPT' % (dhcp_server,))
                self.dhcp_create = True
    
    # 添加租户网络相关规则
    def _do_project_network_rules(self, ipv4_rules, ipv6_rules, network_info):
        v4_subnets = self._get_subnets(network_info, 4)
        v6_subnets = self._get_subnets(network_info, 6)
        cidrs = [subnet['cidr'] for subnet in v4_subnets]
        for cidr in cidrs:
            # 接收从所处网络来的的IPv4包
            ipv4_rules.append('-s %s -j ACCEPT' % (cidr,))
        if CONF.use_ipv6:
            cidrv6s = [subnet['cidr'] for subnet in v6_subnets]
            for cidrv6 in cidrv6s:
                # 接收从所处网络来的的IPv6包
                ipv6_rules.append('-s %s -j ACCEPT' % (cidrv6,))
    
    # 添加RA相关规则
    def _do_ra_rules(self, ipv6_rules, network_info):
        v6_subnets = self._get_subnets(network_info, 6)
        gateways_v6 = [subnet['gateway']['address'] for subnet in v6_subnets]

        for gateway_v6 in gateways_v6:
            # 接收来自网关的IPv6 icmp包
            ipv6_rules.append(
                    '-s %s/128 -p icmpv6 -j ACCEPT' % (gateway_v6,))
    
    # 将tcp/udp安全组规则转换为创建对应iptables 规则的参数
    def _build_tcp_udp_rule(self, rule, version):
        # from_port, to_port用于指定端口范围
        if rule['from_port'] == rule['to_port']:
            return ['--dport', '%s' % (rule['from_port'],)]
        else:
            return ['-m', 'multiport',
                    '--dports', '%s:%s' % (rule['from_port'],
                                           rule['to_port'])]
    
    # 将icmp安全组规则转换为创建对应iptables 规则的参数
    def _build_icmp_rule(self, rule, version):
        icmp_type = rule['from_port']
        icmp_code = rule['to_port']

        # 类型为-1的icmp是无效的
        if icmp_type == -1:
            icmp_type_arg = None
        else:
            icmp_type_arg = '%s' % icmp_type
            if not icmp_code == -1:
                # 编码为-1也是无效的, 忽略编码
                icmp_type_arg += '/%s' % icmp_code

        if icmp_type_arg:
            if version == 4:
                return ['-m', 'icmp', '--icmp-type', icmp_type_arg]
            elif version == 6:
                return ['-m', 'icmp6', '--icmpv6-type', icmp_type_arg]

        return []
    
    # 生成实例的规则
    def instance_rules(self, instance, network_info):
        ctxt = context.get_admin_context()
        if isinstance(instance, dict):
            # 将instance字典转换为Instance对象
            instance = instance_obj.Instance._from_db_object(
                ctxt, instance_obj.Instance(), instance, [])

        ipv4_rules = []
        ipv6_rules = []

        # 添加基础规则
        self._do_basic_rules(ipv4_rules, ipv6_rules, network_info)
        # 添加DHCP相关规则
        self._do_dhcp_rules(ipv4_rules, network_info)

        # 允许同网络内的流量, 默认是True
        if CONF.allow_same_net_traffic:
            # 添加租户网络相关规则
            self._do_project_network_rules(ipv4_rules, ipv6_rules,
                                           network_info)
        if CONF.use_ipv6:
            # 添加RA相关规则
            self._do_ra_rules(ipv6_rules, network_info)

        # 获取实例的安全组列表
        security_groups = security_group_obj.SecurityGroupList.get_by_instance(
            ctxt, instance)

        for security_group in security_groups:
            # 获取安全组下的规则
            rules_cls = security_group_rule_obj.SecurityGroupRuleList
            rules = rules_cls.get_by_security_group(ctxt, security_group)

            for rule in rules:
                LOG.debug(_('Adding security group rule: %r'), rule,
                          instance=instance)

                if not rule['cidr']:
                    # 没有cidr的情况下默认是IPv4规则
                    version = 4
                else:
                    # 通过cidr获取是针对IPv4还是IPv6 
                    version = netutils.get_ip_version(rule['cidr'])

                if version == 4:
                    fw_rules = ipv4_rules
                else:
                    fw_rules = ipv6_rules

                protocol = rule['protocol']

                if protocol:
                    protocol = rule['protocol'].lower()

                if version == 6 and protocol == 'icmp':
                    protocol = 'icmpv6'

                args = ['-j ACCEPT']
                if protocol:
                    args += ['-p', protocol]

                if protocol in ['udp', 'tcp']:
                    # 将协议为udp/tcp的安全组规则转换为参数
                    args += self._build_tcp_udp_rule(rule, version)
                elif protocol == 'icmp':
                    # 将协议为icmp的安全组规则转换为参数
                    args += self._build_icmp_rule(rule, version)
                if rule['cidr']:
                    LOG.debug('Using cidr %r', rule['cidr'], instance=instance)
                    # nova-network下安全组只能设置入口规则
                    args += ['-s', str(rule['cidr'])]
                    fw_rules += [' '.join(args)]
                else:
                    # 我们在没有限定cidr的情况下可以设置授信组, 也就是使用该安全组的实例发来的数据包是可以接收的
                    if rule['grantee_group']:
                        insts = (
                            instance_obj.InstanceList.get_by_security_group(
                                ctxt, rule['grantee_group']))
                        for instance in insts:
                            if instance['info_cache']['deleted']:
                                LOG.debug('ignoring deleted cache')
                                continue
                            nw_info = compute_utils.get_nw_info_for_instance(
                                    instance)

                            ips = [ip['address']
                                for ip in nw_info.fixed_ips()
                                    if ip['version'] == version]

                            LOG.debug('ips: %r', ips, instance=instance)
                            for ip in ips:
                                subrule = args + ['-s %s' % ip]
                                fw_rules += [' '.join(subrule)]

                LOG.debug('Using fw_rules: %r', fw_rules, instance=instance)

        # 跳转至sg-fallback链
        ipv4_rules += ['-j $sg-fallback']
        ipv6_rules += ['-j $sg-fallback']

        return ipv4_rules, ipv6_rules
    
    # 获取实例的链名
    def _instance_chain_name(self, instance):
        return 'inst-%s' % (instance['id'],)
    
    # 在iptables防火墙的filter表中为指定链添加规则
    def _add_filters(self, chain_name, ipv4_rules, ipv6_rules):
        for rule in ipv4_rules:
            self.iptables.ipv4['filter'].add_rule(chain_name, rule)

        if CONF.use_ipv6:
            for rule in ipv6_rules:
                self.iptables.ipv6['filter'].add_rule(chain_name, rule)
    
    # 生成规则: 目的地址是指定地址的包跳转至指定的链
    def _create_filter(self, ips, chain_name):
        return ['-d %s -j $%s' % (ip, chain_name) for ip in ips]

    # 生成规则用于跳转至实例规则链
    def _filters_for_instance(self, chain_name, network_info):
        v4_subnets = self._get_subnets(network_info, 4)
        v6_subnets = self._get_subnets(network_info, 6)
        ips_v4 = [ip['address'] for subnet in v4_subnets
                                for ip in subnet['ips']]
        # 生成IPv4规则
        ipv4_rules = self._create_filter(ips_v4, chain_name)

        ipv6_rules = ips_v6 = []
        if CONF.use_ipv6:
            if v6_subnets:
                ips_v6 = [ip['address'] for subnet in v6_subnets
                                        for ip in subnet['ips']]
            # 生成IPv6规则
            ipv6_rules = self._create_filter(ips_v6, chain_name)

        return ipv4_rules, ipv6_rules
    
    def add_filters_for_instance(self, instance, network_info, inst_ipv4_rules,
                                 inst_ipv6_rules):
        # 获取实例的防火墙链名
        chain_name = self._instance_chain_name(instance)
        # 在iptables防火墙的filter表中添加对应的链
        if CONF.use_ipv6:
            self.iptables.ipv6['filter'].add_chain(chain_name)
        self.iptables.ipv4['filter'].add_chain(chain_name)
        # 生成跳转至实例链的规则
        ipv4_rules, ipv6_rules = self._filters_for_instance(chain_name,
                                                            network_info)
        # 在iptables防火墙的filter表的local链中添加以上用于跳转的规则
        self._add_filters('local', ipv4_rules, ipv6_rules)
        # 在iptables防火墙的filter表的实例链中添加实例规则
        self._add_filters(chain_name, inst_ipv4_rules, inst_ipv6_rules)
    
    # 添加实例的防火墙规则
    def prepare_instance_filter(self, instance, network_info):
        self.instance_info[instance['id']] = (instance, network_info)
        # 生成实例在IPv4和IPv6下的防火墙规则
        ipv4_rules, ipv6_rules = self.instance_rules(instance, network_info)
        # 为实例添加规则
        self.add_filters_for_instance(instance, network_info, ipv4_rules,
                                      ipv6_rules)
        LOG.debug(_('Filters added to instance'), instance=instance)
        # 刷新服务提供商的防火墙规则并使之生效, 这里也会使实例规则生效
        self.refresh_provider_fw_rules()
        LOG.debug(_('Provider Firewall Rules refreshed'), instance=instance)
        # 这里亮瞎我的狗眼啊!感觉OpenStack的代码可读性还可以, 但是质量不是很高,
        # 应该是因为开发者众多导致的
        if (self.dhcp_create and not self.dhcp_created):
            # 在iptables防火墙的filter表的INPUT和FORWARD链中添加以下规则
            # 作用我也不是很清楚
            self.iptables.ipv4['filter'].add_rule(
                    'INPUT',
                    '-s 0.0.0.0/32 -d 255.255.255.255/32 '
                    '-p udp -m udp --sport 68 --dport 67 -j ACCEPT')
            self.iptables.ipv4['filter'].add_rule(
                    'FORWARD',
                    '-s 0.0.0.0/32 -d 255.255.255.255/32 '
                    '-p udp -m udp --sport 68 --dport 67 -j ACCEPT')
            self.dhcp_created = True
        # 再次使全部规则生效
        self.iptables.apply()

class IptablesFirewallDriver(base_firewall.IptablesFirewallDriver):
    
    def __init__(self, virtapi, execute=None, **kwargs):
        super(IptablesFirewallDriver, self).__init__(virtapi, **kwargs)
        self.nwfilter = NWFilterFirewall(virtapi, kwargs['get_connection'])
        
    def setup_basic_filtering(self, instance, network_info):
        # 使用Libvirt的nwfilter防火墙设置基础的nwfilter
        self.nwfilter.setup_basic_filtering(instance, network_info)
        # 如果不只是进行基础的过滤
        if not self.basically_filtered:
            LOG.debug(_('iptables firewall: Setup Basic Filtering'),
                      instance=instance)
            # 刷新服务提供商的防火墙规则
            self.refresh_provider_fw_rules()
            self.basically_filtered = True


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值