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

时间:2022-12-25 08:04:14

前面在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