diff --git a/quantum/agent/linux/iptables_firewall.py b/quantum/agent/linux/iptables_firewall.py index 8205b52ba2..6077bd2825 100644 --- a/quantum/agent/linux/iptables_firewall.py +++ b/quantum/agent/linux/iptables_firewall.py @@ -28,8 +28,10 @@ LOG = logging.getLogger(__name__) SG_CHAIN = 'sg-chain' INGRESS_DIRECTION = 'ingress' EGRESS_DIRECTION = 'egress' +IP_SPOOF_FILTER = 'ip-spoof-filter' CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i', - EGRESS_DIRECTION: 'o'} + EGRESS_DIRECTION: 'o', + IP_SPOOF_FILTER: 's'} LINUX_DEV_LEN = 14 @@ -94,6 +96,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver): for port in self.filtered_ports.values(): self._remove_chain(port, INGRESS_DIRECTION) self._remove_chain(port, EGRESS_DIRECTION) + self._remove_chain(port, IP_SPOOF_FILTER) self._remove_chain_by_name_v4v6(SG_CHAIN) def _setup_chain(self, port, DIRECTION): @@ -175,15 +178,32 @@ class IptablesFirewallDriver(firewall.FirewallDriver): def _arp_spoofing_rule(self, port): return ['-m mac ! --mac-source %s -j DROP' % port['mac_address']] + def _setup_ip_spoof_filter_chain(self, port, table, addresses, rules): + if len(addresses) == 1: + rules.append('! -s %s -j DROP' % addresses[0]) + elif addresses: + chain_name = self._port_chain_name(port, IP_SPOOF_FILTER) + table.add_chain(chain_name) + for ip in addresses: + table.add_rule(chain_name, '-s %s -j RETURN' % ip) + table.add_rule(chain_name, '-j DROP') + rules.append('-j $%s' % chain_name) + def _ip_spoofing_rule(self, port, ipv4_rules, ipv6_rules): #Note(nati) allow dhcp or RA packet ipv4_rules += ['-p udp --sport 68 --dport 67 -j RETURN'] ipv6_rules += ['-p icmpv6 -j RETURN'] + ipv4_addresses = [] + ipv6_addresses = [] for ip in port['fixed_ips']: if netaddr.IPAddress(ip).version == 4: - ipv4_rules += ['! -s %s -j DROP' % ip] + ipv4_addresses.append(ip) else: - ipv6_rules += ['! -s %s -j DROP' % ip] + ipv6_addresses.append(ip) + self._setup_ip_spoof_filter_chain(port, self.iptables.ipv4['filter'], + ipv4_addresses, ipv4_rules) + self._setup_ip_spoof_filter_chain(port, self.iptables.ipv6['filter'], + ipv6_addresses, ipv6_rules) def _drop_dhcp_rule(self): #Note(nati) Drop dhcp packet from VM diff --git a/quantum/tests/unit/test_iptables_firewall.py b/quantum/tests/unit/test_iptables_firewall.py index aea7e6da4e..d870999442 100644 --- a/quantum/tests/unit/test_iptables_firewall.py +++ b/quantum/tests/unit/test_iptables_firewall.py @@ -839,6 +839,7 @@ class IptablesFirewallTestCase(base.BaseTestCase): call.add_rule('sg-chain', '-j ACCEPT'), call.ensure_remove_chain('ifake_dev'), call.ensure_remove_chain('ofake_dev'), + call.ensure_remove_chain('sfake_dev'), call.ensure_remove_chain('sg-chain'), call.add_chain('sg-chain'), call.add_chain('ifake_dev'), @@ -889,6 +890,7 @@ class IptablesFirewallTestCase(base.BaseTestCase): call.add_rule('sg-chain', '-j ACCEPT'), call.ensure_remove_chain('ifake_dev'), call.ensure_remove_chain('ofake_dev'), + call.ensure_remove_chain('sfake_dev'), call.ensure_remove_chain('sg-chain'), call.add_chain('sg-chain')] @@ -914,3 +916,62 @@ class IptablesFirewallTestCase(base.BaseTestCase): pass self.iptables_inst.assert_has_calls([call.defer_apply_on(), call.defer_apply_off()]) + + def test_ip_spoofing_filter_with_multiple_ips(self): + port = {'device': 'tapfake_dev', + 'mac_address': 'ff:ff:ff:ff', + 'fixed_ips': ['10.0.0.1', 'fe80::1', '10.0.0.2']} + self.firewall.prepare_port_filter(port) + calls = [call.add_chain('sg-fallback'), + call.add_rule('sg-fallback', '-j DROP'), + call.ensure_remove_chain('sg-chain'), + call.add_chain('sg-chain'), + call.add_chain('ifake_dev'), + call.add_rule('FORWARD', + '-m physdev --physdev-is-bridged ' + '--physdev-out tapfake_dev ' + '-j $sg-chain'), + call.add_rule('sg-chain', + '-m physdev --physdev-is-bridged ' + '--physdev-out tapfake_dev ' + '-j $ifake_dev'), + call.add_rule( + 'ifake_dev', '-m state --state INVALID -j DROP'), + call.add_rule( + 'ifake_dev', + '-m state --state ESTABLISHED,RELATED -j RETURN'), + call.add_rule('ifake_dev', '-j $sg-fallback'), + call.add_chain('ofake_dev'), + call.add_rule('FORWARD', + '-m physdev --physdev-is-bridged ' + '--physdev-in tapfake_dev ' + '-j $sg-chain'), + call.add_rule('sg-chain', + '-m physdev --physdev-is-bridged ' + '--physdev-in tapfake_dev ' + '-j $ofake_dev'), + call.add_rule('INPUT', + '-m physdev --physdev-is-bridged ' + '--physdev-in tapfake_dev ' + '-j $ofake_dev'), + call.add_chain('sfake_dev'), + call.add_rule('sfake_dev', '-s 10.0.0.1 -j RETURN'), + call.add_rule('sfake_dev', '-s 10.0.0.2 -j RETURN'), + call.add_rule('sfake_dev', '-j DROP'), + call.add_rule( + 'ofake_dev', '-m mac ! --mac-source ff:ff:ff:ff -j DROP'), + call.add_rule( + 'ofake_dev', + '-p udp --sport 68 --dport 67 -j RETURN'), + call.add_rule('ofake_dev', '-j $sfake_dev'), + call.add_rule( + 'ofake_dev', + '-p udp --sport 67 --dport 68 -j DROP'), + call.add_rule( + 'ofake_dev', '-m state --state INVALID -j DROP'), + call.add_rule( + 'ofake_dev', + '-m state --state ESTABLISHED,RELATED -j RETURN'), + call.add_rule('ofake_dev', '-j $sg-fallback'), + call.add_rule('sg-chain', '-j ACCEPT')] + self.v4filter_inst.assert_has_calls(calls)