Use diffs for iptables restore instead of all rules
This patch changes our iptables logic to generate a delta of
iptables commands (inserts + deletes) to get from the current
iptables state to the new state. This will significantly reduce
the amount of data that we have to shell out to iptables-restore
on every call (and reduce the amount of data iptables-restore has
to parse).
We no longer have to worry about preserving counters since
we are adding and deleting specific rules, so the rule modification
code got a nice cleanup to get rid of the old rule matching.
This also gives us a new method of functionally testing that we are
generating rules in the correct manner. After applying new rules
once, a subsequent call should always have no work to do. The new
functional tests added leverage that property heavily and should
protect us from regressions in how rules are formed.
Performance metrics relative to HEAD~1:
+====================================+============+=======+
| Scenario | This patch | HEAD~1|
|------------------------------------|------------|-------|
| 200 VMs*22 rules existing - startup| | |
| _modify_rules| 0.67s | 1.05s |
| _apply_synchronized| 1.87s | 2.89s |
|------------------------------------|------------|-------|
| 200 VMs*22 rules existing - add VM | | |
| _modify_rules| 0.68s | 1.05s |
| _apply_synchronized| 2.07s | 2.92s |
|------------------------------------+------------+-------+
|200 VMs*422 rules existing - startup| | |
| _modify_rules| 5.43s | 8.17s |
| _apply_synchronized| 12.77s |28.00s |
|------------------------------------|------------|-------|
|200 VMs*422 rules existing - add VM | | |
| _modify_rules| 6.41s | 8.33s |
| _apply_synchronized| 33.09s |33.80s |
+------------------------------------+------------+-------+
The _apply_synchronized times seem to converge when dealing
with ~85k rules. In the profile I can see that both approaches
seem to wait on iptables-restore for approximately the same
amount of time so it could be hitting the performance limits
of iptables-restore.
DocImpact
Partial-Bug: #1502297
Change-Id: Ia6470c85b6b71979006ffe5da9095fdcce3122c1
(cherry picked from commit f066e46bb7
)
This commit is contained in:
parent
4713775df2
commit
fb55693713
|
@ -167,7 +167,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
|
|||
self._enable_netfilter_for_bridges()
|
||||
# each security group has it own chains
|
||||
self._setup_chains()
|
||||
self.iptables.apply()
|
||||
return self.iptables.apply()
|
||||
|
||||
def update_port_filter(self, port):
|
||||
LOG.debug("Updating device (%s) filter", port['device'])
|
||||
|
@ -178,7 +178,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
|
|||
self._remove_chains()
|
||||
self._set_ports(port)
|
||||
self._setup_chains()
|
||||
self.iptables.apply()
|
||||
return self.iptables.apply()
|
||||
|
||||
def remove_port_filter(self, port):
|
||||
LOG.debug("Removing device (%s) filter", port['device'])
|
||||
|
@ -189,7 +189,7 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
|
|||
self._remove_chains()
|
||||
self._unset_ports(port)
|
||||
self._setup_chains()
|
||||
self.iptables.apply()
|
||||
return self.iptables.apply()
|
||||
|
||||
def _add_accept_rule_port_sec(self, port, direction):
|
||||
self._update_port_sec_rules(port, direction, add=True)
|
||||
|
@ -212,7 +212,10 @@ class IptablesFirewallDriver(firewall.FirewallDriver):
|
|||
|
||||
def _setup_chains_apply(self, ports, unfiltered_ports):
|
||||
self._add_chain_by_name_v4v6(SG_CHAIN)
|
||||
for port in ports.values():
|
||||
# sort by port so we always do this deterministically between
|
||||
# agent restarts and don't cause unnecessary rule differences
|
||||
for pname in sorted(ports):
|
||||
port = ports[pname]
|
||||
self._setup_chain(port, firewall.INGRESS_DIRECTION)
|
||||
self._setup_chain(port, firewall.EGRESS_DIRECTION)
|
||||
self.iptables.ipv4['filter'].add_rule(SG_CHAIN, '-j ACCEPT')
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
import collections
|
||||
import contextlib
|
||||
import difflib
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
@ -180,7 +181,8 @@ class IptablesTable(object):
|
|||
self.remove_chains.add(name)
|
||||
|
||||
# first, add rules to remove that have a matching chain name
|
||||
self.remove_rules += [r for r in self.rules if r.chain == name]
|
||||
self.remove_rules += [str(r) for r in self.rules
|
||||
if r.chain == name]
|
||||
|
||||
# next, remove rules from list that have a matching chain name
|
||||
self.rules = [r for r in self.rules if r.chain != name]
|
||||
|
@ -188,7 +190,7 @@ class IptablesTable(object):
|
|||
if not wrap:
|
||||
jump_snippet = '-j %s' % name
|
||||
# next, add rules to remove that have a matching jump chain
|
||||
self.remove_rules += [r for r in self.rules
|
||||
self.remove_rules += [str(r) for r in self.rules
|
||||
if jump_snippet in r.rule]
|
||||
else:
|
||||
jump_snippet = '-j %s-%s' % (self.wrap_name, name)
|
||||
|
@ -244,9 +246,9 @@ class IptablesTable(object):
|
|||
self.wrap_name,
|
||||
comment=comment))
|
||||
if not wrap:
|
||||
self.remove_rules.append(IptablesRule(chain, rule, wrap, top,
|
||||
self.wrap_name,
|
||||
comment=comment))
|
||||
self.remove_rules.append(str(IptablesRule(chain, rule, wrap,
|
||||
top, self.wrap_name,
|
||||
comment=comment)))
|
||||
except ValueError:
|
||||
LOG.warn(_LW('Tried to remove rule that was not there:'
|
||||
' %(chain)r %(rule)r %(wrap)r %(top)r'),
|
||||
|
@ -418,7 +420,7 @@ class IptablesManager(object):
|
|||
if self.iptables_apply_deferred:
|
||||
return
|
||||
|
||||
self._apply()
|
||||
return self._apply()
|
||||
|
||||
def _apply(self):
|
||||
lock_name = 'iptables'
|
||||
|
@ -438,33 +440,50 @@ class IptablesManager(object):
|
|||
def _apply_synchronized(self):
|
||||
"""Apply the current in-memory set of iptables rules.
|
||||
|
||||
This will blow away any rules left over from previous runs of the
|
||||
same component of Nova, and replace them with our current set of
|
||||
rules. This happens atomically, thanks to iptables-restore.
|
||||
This will create a diff between the rules from the previous runs
|
||||
and replace them with the current set of rules.
|
||||
This happens atomically, thanks to iptables-restore.
|
||||
|
||||
Returns a list of the changes that were sent to iptables-save.
|
||||
"""
|
||||
s = [('iptables', self.ipv4)]
|
||||
if self.use_ipv6:
|
||||
s += [('ip6tables', self.ipv6)]
|
||||
|
||||
all_commands = [] # variable to keep track all commands for return val
|
||||
for cmd, tables in s:
|
||||
args = ['%s-save' % (cmd,), '-c']
|
||||
args = ['%s-save' % (cmd,)]
|
||||
if self.namespace:
|
||||
args = ['ip', 'netns', 'exec', self.namespace] + args
|
||||
all_tables = self.execute(args, run_as_root=True)
|
||||
all_lines = all_tables.split('\n')
|
||||
save_output = self.execute(args, run_as_root=True)
|
||||
all_lines = save_output.split('\n')
|
||||
commands = []
|
||||
# Traverse tables in sorted order for predictable dump output
|
||||
for table_name in sorted(tables):
|
||||
table = tables[table_name]
|
||||
# isolate the lines of the table we are modifying
|
||||
start, end = self._find_table(all_lines, table_name)
|
||||
all_lines[start:end] = self._modify_rules(
|
||||
all_lines[start:end], table, table_name)
|
||||
|
||||
args = ['%s-restore' % (cmd,), '-c']
|
||||
old_rules = all_lines[start:end]
|
||||
# generate the new table state we want
|
||||
new_rules = self._modify_rules(old_rules, table, table_name)
|
||||
# generate the iptables commands to get between the old state
|
||||
# and the new state
|
||||
changes = _generate_path_between_rules(old_rules, new_rules)
|
||||
if changes:
|
||||
# if there are changes to the table, we put on the header
|
||||
# and footer that iptables-save needs
|
||||
commands += (['# Generated by iptables_manager'] +
|
||||
['*%s' % table_name] + changes +
|
||||
['COMMIT', '# Completed by iptables_manager'])
|
||||
if not commands:
|
||||
continue
|
||||
all_commands += commands
|
||||
args = ['%s-restore' % (cmd,), '-n']
|
||||
if self.namespace:
|
||||
args = ['ip', 'netns', 'exec', self.namespace] + args
|
||||
try:
|
||||
self.execute(args, process_input='\n'.join(all_lines),
|
||||
# always end with a new line
|
||||
commands.append('')
|
||||
self.execute(args, process_input='\n'.join(commands),
|
||||
run_as_root=True)
|
||||
except RuntimeError as r_error:
|
||||
with excutils.save_and_reraise_exception():
|
||||
|
@ -478,28 +497,30 @@ class IptablesManager(object):
|
|||
except AttributeError:
|
||||
# line error wasn't found, print all lines instead
|
||||
log_start = 0
|
||||
log_end = len(all_lines)
|
||||
log_end = len(commands)
|
||||
log_lines = ('%7d. %s' % (idx, l)
|
||||
for idx, l in enumerate(
|
||||
all_lines[log_start:log_end],
|
||||
commands[log_start:log_end],
|
||||
log_start + 1)
|
||||
)
|
||||
LOG.error(_LE("IPTablesManager.apply failed to apply the "
|
||||
"following set of iptables rules:\n%s"),
|
||||
'\n'.join(log_lines))
|
||||
LOG.debug("IPTablesManager.apply completed with success")
|
||||
LOG.debug("IPTablesManager.apply completed with success. %d iptables "
|
||||
"commands were issued", len(all_commands))
|
||||
return all_commands
|
||||
|
||||
def _find_table(self, lines, table_name):
|
||||
if len(lines) < 3:
|
||||
# length only <2 when fake iptables
|
||||
return (0, 0)
|
||||
try:
|
||||
start = lines.index('*%s' % table_name) - 1
|
||||
start = lines.index('*%s' % table_name)
|
||||
except ValueError:
|
||||
# Couldn't find table_name
|
||||
LOG.debug('Unable to find table %s', table_name)
|
||||
return (0, 0)
|
||||
end = lines[start:].index('COMMIT') + start + 2
|
||||
end = lines[start:].index('COMMIT') + start + 1
|
||||
return (start, end)
|
||||
|
||||
def _find_rules_index(self, lines):
|
||||
|
@ -518,172 +539,92 @@ class IptablesManager(object):
|
|||
|
||||
return rules_index
|
||||
|
||||
def _find_last_entry(self, filter_map, match_str):
|
||||
# find last matching entry
|
||||
try:
|
||||
return filter_map[match_str][-1]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def _modify_rules(self, current_lines, table, table_name):
|
||||
# Chains are stored as sets to avoid duplicates.
|
||||
# Sort the output chains here to make their order predictable.
|
||||
unwrapped_chains = sorted(table.unwrapped_chains)
|
||||
chains = sorted(table.chains)
|
||||
remove_chains = table.remove_chains
|
||||
rules = table.rules
|
||||
remove_rules = table.remove_rules
|
||||
|
||||
if not current_lines:
|
||||
fake_table = ['# Generated by iptables_manager',
|
||||
'*' + table_name, 'COMMIT',
|
||||
'# Completed by iptables_manager']
|
||||
current_lines = fake_table
|
||||
# we don't want to change any rules that don't belong to us so we start
|
||||
# the new_filter with these rules
|
||||
new_filter = [line.strip() for line in current_lines
|
||||
if self.wrap_name not in line]
|
||||
|
||||
# Fill old_filter with any chains or rules we might have added,
|
||||
# they could have a [packet:byte] count we want to preserve.
|
||||
# Fill new_filter with any chains or rules without our name in them.
|
||||
old_filter, new_filter = [], []
|
||||
for line in current_lines:
|
||||
(old_filter if self.wrap_name in line else
|
||||
new_filter).append(line.strip())
|
||||
# generate our list of chain names
|
||||
our_chains = [':%s-%s' % (self.wrap_name, name) for name in chains]
|
||||
|
||||
old_filter_map = make_filter_map(old_filter)
|
||||
new_filter_map = make_filter_map(new_filter)
|
||||
# the unwrapped chains (e.g. neutron-filter-top) may already exist in
|
||||
# the new_filter since they aren't marked by the wrap_name so we only
|
||||
# want to add them if they arent' already there
|
||||
our_chains += [':%s' % name for name in unwrapped_chains
|
||||
if not any(':%s' % name in s for s in new_filter)]
|
||||
|
||||
rules_index = self._find_rules_index(new_filter)
|
||||
|
||||
all_chains = [':%s' % name for name in unwrapped_chains]
|
||||
all_chains += [':%s-%s' % (self.wrap_name, name) for name in chains]
|
||||
|
||||
# Iterate through all the chains, trying to find an existing
|
||||
# match.
|
||||
our_chains = []
|
||||
for chain in all_chains:
|
||||
chain_str = str(chain).strip()
|
||||
|
||||
old = self._find_last_entry(old_filter_map, chain_str)
|
||||
if not old:
|
||||
dup = self._find_last_entry(new_filter_map, chain_str)
|
||||
new_filter = [s for s in new_filter if chain_str not in s.strip()]
|
||||
|
||||
# if no old or duplicates, use original chain
|
||||
if old or dup:
|
||||
chain_str = str(old or dup)
|
||||
else:
|
||||
# add-on the [packet:bytes]
|
||||
chain_str += ' - [0:0]'
|
||||
|
||||
our_chains += [chain_str]
|
||||
|
||||
# Iterate through all the rules, trying to find an existing
|
||||
# match.
|
||||
our_rules = []
|
||||
bot_rules = []
|
||||
for rule in rules:
|
||||
rule_str = str(rule).strip()
|
||||
# Further down, we weed out duplicates from the bottom of the
|
||||
# list, so here we remove the dupes ahead of time.
|
||||
|
||||
old = self._find_last_entry(old_filter_map, rule_str)
|
||||
if not old:
|
||||
dup = self._find_last_entry(new_filter_map, rule_str)
|
||||
new_filter = [s for s in new_filter if rule_str not in s.strip()]
|
||||
|
||||
# if no old or duplicates, use original rule
|
||||
if old or dup:
|
||||
rule_str = str(old or dup)
|
||||
# backup one index so we write the array correctly
|
||||
if not old:
|
||||
rules_index -= 1
|
||||
else:
|
||||
# add-on the [packet:bytes]
|
||||
rule_str = '[0:0] ' + rule_str
|
||||
our_top_rules = []
|
||||
our_bottom_rules = []
|
||||
for rule in table.rules:
|
||||
rule_str = str(rule)
|
||||
# similar to the unwrapped chains, there are some rules that belong
|
||||
# to us but they don't have the wrap name. we want to remove them
|
||||
# from the new_filter and then add them in the right location in
|
||||
# case our new rules changed the order.
|
||||
# (e.g. '-A FORWARD -j neutron-filter-top')
|
||||
new_filter = [s for s in new_filter if rule_str not in s]
|
||||
|
||||
if rule.top:
|
||||
# rule.top == True means we want this rule to be at the top.
|
||||
our_rules += [rule_str]
|
||||
our_top_rules += [rule_str]
|
||||
else:
|
||||
bot_rules += [rule_str]
|
||||
our_bottom_rules += [rule_str]
|
||||
|
||||
our_rules += bot_rules
|
||||
our_chains_and_rules = our_chains + our_top_rules + our_bottom_rules
|
||||
|
||||
new_filter[rules_index:rules_index] = our_rules
|
||||
new_filter[rules_index:rules_index] = our_chains
|
||||
|
||||
def _strip_packets_bytes(line):
|
||||
# strip any [packet:byte] counts at start or end of lines
|
||||
if line.startswith(':'):
|
||||
# it's a chain, for example, ":neutron-billing - [0:0]"
|
||||
line = line.split(':')[1]
|
||||
line = line.split(' - [', 1)[0]
|
||||
elif line.startswith('['):
|
||||
# it's a rule, for example, "[0:0] -A neutron-billing..."
|
||||
line = line.split('] ', 1)[1]
|
||||
line = line.strip()
|
||||
return line
|
||||
|
||||
seen_chains = set()
|
||||
|
||||
def _weed_out_duplicate_chains(line):
|
||||
# ignore [packet:byte] counts at end of lines
|
||||
if line.startswith(':'):
|
||||
line = _strip_packets_bytes(line)
|
||||
if line in seen_chains:
|
||||
return False
|
||||
else:
|
||||
seen_chains.add(line)
|
||||
|
||||
# Leave it alone
|
||||
return True
|
||||
|
||||
seen_rules = set()
|
||||
|
||||
def _weed_out_duplicate_rules(line):
|
||||
if line.startswith('['):
|
||||
line = _strip_packets_bytes(line)
|
||||
if line in seen_rules:
|
||||
return False
|
||||
else:
|
||||
seen_rules.add(line)
|
||||
|
||||
# Leave it alone
|
||||
return True
|
||||
# locate the position immediately after the existing chains to insert
|
||||
# our chains and rules
|
||||
rules_index = self._find_rules_index(new_filter)
|
||||
new_filter[rules_index:rules_index] = our_chains_and_rules
|
||||
|
||||
def _weed_out_removes(line):
|
||||
# We need to find exact matches here
|
||||
# remove any rules or chains from the filter that were slated
|
||||
# for removal
|
||||
if line.startswith(':'):
|
||||
line = _strip_packets_bytes(line)
|
||||
for chain in remove_chains:
|
||||
if chain == line:
|
||||
remove_chains.remove(chain)
|
||||
return False
|
||||
elif line.startswith('['):
|
||||
line = _strip_packets_bytes(line)
|
||||
for rule in remove_rules:
|
||||
rule_str = _strip_packets_bytes(str(rule))
|
||||
if rule_str == line:
|
||||
remove_rules.remove(rule)
|
||||
return False
|
||||
|
||||
chain = line[1:]
|
||||
if chain in table.remove_chains:
|
||||
table.remove_chains.remove(chain)
|
||||
return False
|
||||
else:
|
||||
if line in table.remove_rules:
|
||||
table.remove_rules.remove(line)
|
||||
return False
|
||||
# Leave it alone
|
||||
return True
|
||||
|
||||
seen_lines = set()
|
||||
|
||||
# TODO(kevinbenton): remove this function and the next one. They are
|
||||
# just oversized brooms to sweep bugs under the rug!!! We generate the
|
||||
# rules and we shouldn't be generating duplicates.
|
||||
def _weed_out_duplicates(line):
|
||||
if line in seen_lines:
|
||||
thing = 'chain' if line.startswith(':') else 'rule'
|
||||
LOG.warning(_LW("Duplicate iptables %(thing)s detected. This "
|
||||
"may indicate a bug in the the iptables "
|
||||
"%(thing)s generation code. Line: %(line)s"),
|
||||
{'thing': thing, 'line': line})
|
||||
return False
|
||||
seen_lines.add(line)
|
||||
# Leave it alone
|
||||
return True
|
||||
|
||||
# We filter duplicates. Go through the chains and rules, letting
|
||||
# the *last* occurrence take precedence since it could have a
|
||||
# non-zero [packet:byte] count we want to preserve. We also filter
|
||||
# out anything in the "remove" list.
|
||||
new_filter.reverse()
|
||||
new_filter = [line for line in new_filter
|
||||
if _weed_out_duplicate_chains(line) and
|
||||
_weed_out_duplicate_rules(line) and
|
||||
if _weed_out_duplicates(line) and
|
||||
_weed_out_removes(line)]
|
||||
new_filter.reverse()
|
||||
|
||||
# flush lists, just in case we didn't find something
|
||||
remove_chains.clear()
|
||||
for rule in remove_rules:
|
||||
remove_rules.remove(rule)
|
||||
# flush lists, just in case a rule or chain marked for removal
|
||||
# was already gone. (chains is a set, rules is a list)
|
||||
table.remove_chains.clear()
|
||||
table.remove_rules = []
|
||||
|
||||
return new_filter
|
||||
|
||||
|
@ -735,27 +676,73 @@ class IptablesManager(object):
|
|||
return acc
|
||||
|
||||
|
||||
def make_filter_map(filter_list):
|
||||
filter_map = collections.defaultdict(list)
|
||||
for data in filter_list:
|
||||
# strip any [packet:byte] counts at start or end of lines,
|
||||
# for example, chains look like ":neutron-foo - [0:0]"
|
||||
# and rules look like "[0:0] -A neutron-foo..."
|
||||
if data.startswith('['):
|
||||
key = data.rpartition('] ')[2]
|
||||
elif data.endswith(']'):
|
||||
key = data.rsplit(' [', 1)[0]
|
||||
if key.endswith(' -'):
|
||||
key = key[:-2]
|
||||
def _generate_path_between_rules(old_rules, new_rules):
|
||||
"""Generates iptables commands to get from old_rules to new_rules.
|
||||
|
||||
This function diffs the two rule sets and then calculates the iptables
|
||||
commands necessary to get from the old rules to the new rules using
|
||||
insert and delete commands.
|
||||
"""
|
||||
old_by_chain = _get_rules_by_chain(old_rules)
|
||||
new_by_chain = _get_rules_by_chain(new_rules)
|
||||
old_chains, new_chains = set(old_by_chain.keys()), set(new_by_chain.keys())
|
||||
# all referenced chains should be declared at the top before rules.
|
||||
|
||||
# NOTE(kevinbenton): sorting and grouping chains is for determinism in
|
||||
# tests. iptables doesn't care about the order here
|
||||
statements = [':%s - [0:0]' % c for c in sorted(new_chains - old_chains)]
|
||||
sg_chains = []
|
||||
other_chains = []
|
||||
for chain in sorted(old_chains | new_chains):
|
||||
if '-sg-' in chain:
|
||||
sg_chains.append(chain)
|
||||
else:
|
||||
# things like COMMIT, *filter, and *nat land here
|
||||
other_chains.append(chain)
|
||||
|
||||
for chain in other_chains + sg_chains:
|
||||
statements += _generate_chain_diff_iptables_commands(
|
||||
chain, old_by_chain[chain], new_by_chain[chain])
|
||||
# unreferenced chains get the axe
|
||||
for chain in sorted(old_chains - new_chains):
|
||||
statements += ['-X %s' % chain]
|
||||
return statements
|
||||
|
||||
|
||||
def _get_rules_by_chain(rules):
|
||||
by_chain = collections.defaultdict(list)
|
||||
for line in rules:
|
||||
if line.startswith(':'):
|
||||
chain = line[1:].split(' ', 1)[0]
|
||||
# even though this is a default dict, we need to manually add
|
||||
# chains to ensure that ones without rules are included because
|
||||
# they might be a jump reference
|
||||
if chain not in by_chain:
|
||||
by_chain[chain] = []
|
||||
elif line.startswith('-A'):
|
||||
chain = line[3:].split(' ', 1)[0]
|
||||
by_chain[chain].append(line)
|
||||
return by_chain
|
||||
|
||||
|
||||
def _generate_chain_diff_iptables_commands(chain, old_chain_rules,
|
||||
new_chain_rules):
|
||||
# keep track of the old index because we have to insert rules
|
||||
# in the right position
|
||||
old_index = 1
|
||||
statements = []
|
||||
for line in difflib.ndiff(old_chain_rules, new_chain_rules):
|
||||
if line.startswith('?'):
|
||||
# skip ? because that's a guide string for intraline differences
|
||||
continue
|
||||
filter_map[key].append(data)
|
||||
# regular IP(v6) entries are translated into /32s or /128s so we
|
||||
# include a lookup without the CIDR here to match as well
|
||||
for cidr in ('/32', '/128'):
|
||||
if cidr in key:
|
||||
alt_key = key.replace(cidr, '')
|
||||
filter_map[alt_key].append(data)
|
||||
# return a regular dict so readers don't accidentally add entries
|
||||
return dict(filter_map)
|
||||
elif line.startswith('-'): # line deleted
|
||||
statements.append('-D %s %d' % (chain, old_index))
|
||||
# since we are removing a line from the old rules, we
|
||||
# backup the index by 1
|
||||
old_index -= 1
|
||||
elif line.startswith('+'): # line added
|
||||
# strip the chain name since we have to add it before the index
|
||||
rule = line[5:].split(' ', 1)[-1]
|
||||
# rule inserted at this position
|
||||
statements.append('-I %s %d %s' % (chain, old_index, rule))
|
||||
old_index += 1
|
||||
return statements
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
import copy
|
||||
|
||||
from neutron.agent.linux import iptables_firewall
|
||||
from neutron.agent import securitygroups_rpc as sg_cfg
|
||||
|
@ -82,3 +83,112 @@ class IptablesFirewallTestCase(base.BaseSudoTestCase):
|
|||
self.src_port_desc['port_security_enabled'] = False
|
||||
self.firewall.update_port_filter(self.src_port_desc)
|
||||
self.client.assert_ping(self.server.ip)
|
||||
|
||||
def test_rule_application_converges(self):
|
||||
sg_rules = [{'ethertype': 'IPv4', 'direction': 'egress'},
|
||||
{'ethertype': 'IPv6', 'direction': 'egress'},
|
||||
{'ethertype': 'IPv4', 'direction': 'ingress',
|
||||
'source_ip_prefix': '0.0.0.0/0', 'protocol': 'icmp'},
|
||||
{'ethertype': 'IPv6', 'direction': 'ingress',
|
||||
'source_ip_prefix': '0::0/0', 'protocol': 'ipv6-icmp'}]
|
||||
# make sure port ranges converge on all protocols with and without
|
||||
# port ranges (prevents regression of bug 1502924)
|
||||
for proto in ('tcp', 'udp', 'icmp'):
|
||||
for version in ('IPv4', 'IPv6'):
|
||||
if proto == 'icmp' and version == 'IPv6':
|
||||
proto = 'ipv6-icmp'
|
||||
base = {'ethertype': version, 'direction': 'ingress',
|
||||
'protocol': proto}
|
||||
sg_rules.append(copy.copy(base))
|
||||
base['port_range_min'] = 50
|
||||
base['port_range_max'] = 50
|
||||
sg_rules.append(copy.copy(base))
|
||||
base['port_range_max'] = 55
|
||||
sg_rules.append(copy.copy(base))
|
||||
base['source_port_range_min'] = 60
|
||||
base['source_port_range_max'] = 60
|
||||
sg_rules.append(copy.copy(base))
|
||||
base['source_port_range_max'] = 65
|
||||
sg_rules.append(copy.copy(base))
|
||||
|
||||
# add some single-host rules to prevent regression of bug 1502917
|
||||
sg_rules.append({'ethertype': 'IPv4', 'direction': 'ingress',
|
||||
'source_ip_prefix': '77.77.77.77/32'})
|
||||
sg_rules.append({'ethertype': 'IPv6', 'direction': 'ingress',
|
||||
'source_ip_prefix': 'fe80::1/128'})
|
||||
self.firewall.update_security_group_rules(
|
||||
self.FAKE_SECURITY_GROUP_ID, sg_rules)
|
||||
self.firewall.prepare_port_filter(self.src_port_desc)
|
||||
# after one prepare call, another apply should be a NOOP
|
||||
self.assertEqual([], self.firewall.iptables._apply())
|
||||
|
||||
orig_sg_rules = copy.copy(sg_rules)
|
||||
for proto in ('tcp', 'udp', 'icmp'):
|
||||
for version in ('IPv4', 'IPv6'):
|
||||
if proto == 'icmp' and version == 'IPv6':
|
||||
proto = 'ipv6-icmp'
|
||||
# make sure firewall is in converged state
|
||||
self.firewall.update_security_group_rules(
|
||||
self.FAKE_SECURITY_GROUP_ID, orig_sg_rules)
|
||||
self.firewall.update_port_filter(self.src_port_desc)
|
||||
sg_rules = copy.copy(orig_sg_rules)
|
||||
|
||||
# remove one rule and add another to make sure it results in
|
||||
# exactly one delete and insert
|
||||
sg_rules.pop(0 if version == 'IPv4' else 1)
|
||||
sg_rules.append({'ethertype': version, 'direction': 'egress',
|
||||
'protocol': proto})
|
||||
self.firewall.update_security_group_rules(
|
||||
self.FAKE_SECURITY_GROUP_ID, sg_rules)
|
||||
result = self.firewall.update_port_filter(self.src_port_desc)
|
||||
deletes = [r for r in result if r.startswith('-D ')]
|
||||
creates = [r for r in result if r.startswith('-I ')]
|
||||
self.assertEqual(1, len(deletes))
|
||||
self.assertEqual(1, len(creates))
|
||||
# quick sanity check to make sure the insert was for the
|
||||
# correct proto
|
||||
self.assertIn('-p %s' % proto, creates[0])
|
||||
# another apply should be a NOOP if the right rule was removed
|
||||
# and the new one was inserted in the correct position
|
||||
self.assertEqual([], self.firewall.iptables._apply())
|
||||
|
||||
def test_rule_ordering_correct(self):
|
||||
sg_rules = [
|
||||
{'ethertype': 'IPv4', 'direction': 'egress', 'protocol': 'tcp',
|
||||
'port_range_min': i, 'port_range_max': i}
|
||||
for i in range(50, 61)
|
||||
]
|
||||
self.firewall.update_security_group_rules(
|
||||
self.FAKE_SECURITY_GROUP_ID, sg_rules)
|
||||
self.firewall.prepare_port_filter(self.src_port_desc)
|
||||
self._assert_sg_out_tcp_rules_appear_in_order(sg_rules)
|
||||
# remove a rule and add a new one
|
||||
sg_rules.pop(5)
|
||||
sg_rules.insert(8, {'ethertype': 'IPv4', 'direction': 'egress',
|
||||
'protocol': 'tcp', 'port_range_min': 400,
|
||||
'port_range_max': 400})
|
||||
self.firewall.update_security_group_rules(
|
||||
self.FAKE_SECURITY_GROUP_ID, sg_rules)
|
||||
self.firewall.prepare_port_filter(self.src_port_desc)
|
||||
self._assert_sg_out_tcp_rules_appear_in_order(sg_rules)
|
||||
|
||||
# reverse all of the rules (requires lots of deletes and inserts)
|
||||
sg_rules = list(reversed(sg_rules))
|
||||
self.firewall.update_security_group_rules(
|
||||
self.FAKE_SECURITY_GROUP_ID, sg_rules)
|
||||
self.firewall.prepare_port_filter(self.src_port_desc)
|
||||
self._assert_sg_out_tcp_rules_appear_in_order(sg_rules)
|
||||
|
||||
def _assert_sg_out_tcp_rules_appear_in_order(self, sg_rules):
|
||||
outgoing_rule_pref = '-A %s-o%s' % (self.firewall.iptables.wrap_name,
|
||||
self.src_port_desc['device'][3:13])
|
||||
rules = [
|
||||
r for r in self.firewall.iptables.get_rules_for_table('filter')
|
||||
if r.startswith(outgoing_rule_pref)
|
||||
]
|
||||
# we want to ensure the rules went in in the same order we sent
|
||||
indexes = [rules.index('%s -p tcp -m tcp --dport %s -j RETURN' %
|
||||
(outgoing_rule_pref, i['port_range_min']))
|
||||
for i in sg_rules]
|
||||
# all indexes should be in order with no unexpected rules in between
|
||||
self.assertEqual(range(indexes[0], indexes[-1] + 1), indexes)
|
||||
|
|
|
@ -35,18 +35,21 @@ IPTABLES_ARG = {'bn': iptables_manager.binary_name,
|
|||
|
||||
NAT_TEMPLATE = ('# Generated by iptables_manager\n'
|
||||
'*nat\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':POSTROUTING - [0:0]\n'
|
||||
':PREROUTING - [0:0]\n'
|
||||
':neutron-postrouting-bottom - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-POSTROUTING - [0:0]\n'
|
||||
':%(bn)s-PREROUTING - [0:0]\n'
|
||||
':%(bn)s-float-snat - [0:0]\n'
|
||||
':%(bn)s-snat - [0:0]\n'
|
||||
'[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n'
|
||||
'[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n'
|
||||
'[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat\n'
|
||||
'[0:0] -A %(bn)s-snat -j '
|
||||
'-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
|
||||
'-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
|
||||
'-I POSTROUTING 2 -j neutron-postrouting-bottom\n'
|
||||
'-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
|
||||
'-I neutron-postrouting-bottom 1 -j %(bn)s-snat\n'
|
||||
'-I %(bn)s-snat 1 -j '
|
||||
'%(bn)s-float-snat\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n')
|
||||
|
@ -55,17 +58,20 @@ NAT_DUMP = NAT_TEMPLATE % IPTABLES_ARG
|
|||
|
||||
FILTER_TEMPLATE = ('# Generated by iptables_manager\n'
|
||||
'*filter\n'
|
||||
':FORWARD - [0:0]\n'
|
||||
':INPUT - [0:0]\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':neutron-filter-top - [0:0]\n'
|
||||
':%(bn)s-FORWARD - [0:0]\n'
|
||||
':%(bn)s-INPUT - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-local - [0:0]\n'
|
||||
'[0:0] -A FORWARD -j neutron-filter-top\n'
|
||||
'[0:0] -A OUTPUT -j neutron-filter-top\n'
|
||||
'[0:0] -A neutron-filter-top -j %(bn)s-local\n'
|
||||
'[0:0] -A INPUT -j %(bn)s-INPUT\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A FORWARD -j %(bn)s-FORWARD\n'
|
||||
'-I FORWARD 1 -j neutron-filter-top\n'
|
||||
'-I FORWARD 2 -j %(bn)s-FORWARD\n'
|
||||
'-I INPUT 1 -j %(bn)s-INPUT\n'
|
||||
'-I OUTPUT 1 -j neutron-filter-top\n'
|
||||
'-I OUTPUT 2 -j %(bn)s-OUTPUT\n'
|
||||
'-I neutron-filter-top 1 -j %(bn)s-local\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n')
|
||||
|
||||
|
@ -74,18 +80,21 @@ FILTER_DUMP = FILTER_TEMPLATE % IPTABLES_ARG
|
|||
FILTER_WITH_RULES_TEMPLATE = (
|
||||
'# Generated by iptables_manager\n'
|
||||
'*filter\n'
|
||||
':FORWARD - [0:0]\n'
|
||||
':INPUT - [0:0]\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':neutron-filter-top - [0:0]\n'
|
||||
':%(bn)s-FORWARD - [0:0]\n'
|
||||
':%(bn)s-INPUT - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-filter - [0:0]\n'
|
||||
':%(bn)s-local - [0:0]\n'
|
||||
'[0:0] -A FORWARD -j neutron-filter-top\n'
|
||||
'[0:0] -A OUTPUT -j neutron-filter-top\n'
|
||||
'[0:0] -A neutron-filter-top -j %(bn)s-local\n'
|
||||
'[0:0] -A INPUT -j %(bn)s-INPUT\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A FORWARD -j %(bn)s-FORWARD\n'
|
||||
'-I FORWARD 1 -j neutron-filter-top\n'
|
||||
'-I FORWARD 2 -j %(bn)s-FORWARD\n'
|
||||
'-I INPUT 1 -j %(bn)s-INPUT\n'
|
||||
'-I OUTPUT 1 -j neutron-filter-top\n'
|
||||
'-I OUTPUT 2 -j %(bn)s-OUTPUT\n'
|
||||
'-I neutron-filter-top 1 -j %(bn)s-local\n'
|
||||
'%(filter_rules)s'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n')
|
||||
|
@ -93,19 +102,22 @@ FILTER_WITH_RULES_TEMPLATE = (
|
|||
COMMENTED_NAT_DUMP = (
|
||||
'# Generated by iptables_manager\n'
|
||||
'*nat\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':POSTROUTING - [0:0]\n'
|
||||
':PREROUTING - [0:0]\n'
|
||||
':neutron-postrouting-bottom - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-POSTROUTING - [0:0]\n'
|
||||
':%(bn)s-PREROUTING - [0:0]\n'
|
||||
':%(bn)s-float-snat - [0:0]\n'
|
||||
':%(bn)s-snat - [0:0]\n'
|
||||
'[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n'
|
||||
'[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n'
|
||||
'[0:0] -A neutron-postrouting-bottom '
|
||||
'-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
|
||||
'-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
|
||||
'-I POSTROUTING 2 -j neutron-postrouting-bottom\n'
|
||||
'-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
|
||||
'-I neutron-postrouting-bottom 1 '
|
||||
'-m comment --comment "%(snat_out_comment)s" -j %(bn)s-snat\n'
|
||||
'[0:0] -A %(bn)s-snat -j '
|
||||
'-I %(bn)s-snat 1 -j '
|
||||
'%(bn)s-float-snat\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n' % IPTABLES_ARG)
|
||||
|
@ -160,9 +172,9 @@ class IptablesCommentsTestCase(base.BaseTestCase):
|
|||
def test_add_filter_rule(self):
|
||||
iptables_args = {}
|
||||
iptables_args.update(IPTABLES_ARG)
|
||||
filter_rules = ('[0:0] -A %(bn)s-filter -j DROP\n'
|
||||
'[0:0] -A %(bn)s-INPUT -s 0/0 -d 192.168.0.2 -j '
|
||||
'%(bn)s-filter\n' % iptables_args)
|
||||
filter_rules = ('-I %(bn)s-INPUT 1 -s 0/0 -d 192.168.0.2 -j '
|
||||
'%(bn)s-filter\n-I %(bn)s-filter 1 -j DROP\n'
|
||||
% iptables_args)
|
||||
iptables_args['filter_rules'] = filter_rules
|
||||
filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % iptables_args
|
||||
|
||||
|
@ -170,20 +182,20 @@ class IptablesCommentsTestCase(base.BaseTestCase):
|
|||
mangle_dump = _generate_mangle_dump(IPTABLES_ARG)
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(raw_dump + COMMENTED_NAT_DUMP +
|
||||
mangle_dump + filter_dump_mod),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump_mod + mangle_dump +
|
||||
COMMENTED_NAT_DUMP + raw_dump),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(raw_dump + COMMENTED_NAT_DUMP +
|
||||
mangle_dump + FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + mangle_dump +
|
||||
COMMENTED_NAT_DUMP + raw_dump),
|
||||
run_as_root=True
|
||||
),
|
||||
None),
|
||||
|
@ -212,18 +224,23 @@ class IptablesCommentsTestCase(base.BaseTestCase):
|
|||
def _generate_mangle_dump(iptables_args):
|
||||
return ('# Generated by iptables_manager\n'
|
||||
'*mangle\n'
|
||||
':FORWARD - [0:0]\n'
|
||||
':INPUT - [0:0]\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':POSTROUTING - [0:0]\n'
|
||||
':PREROUTING - [0:0]\n'
|
||||
':%(bn)s-FORWARD - [0:0]\n'
|
||||
':%(bn)s-INPUT - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-POSTROUTING - [0:0]\n'
|
||||
':%(bn)s-PREROUTING - [0:0]\n'
|
||||
':%(bn)s-mark - [0:0]\n'
|
||||
'[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n'
|
||||
'[0:0] -A INPUT -j %(bn)s-INPUT\n'
|
||||
'[0:0] -A FORWARD -j %(bn)s-FORWARD\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n'
|
||||
'[0:0] -A %(bn)s-PREROUTING -j %(bn)s-mark\n'
|
||||
'-I FORWARD 1 -j %(bn)s-FORWARD\n'
|
||||
'-I INPUT 1 -j %(bn)s-INPUT\n'
|
||||
'-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
|
||||
'-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
|
||||
'-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
|
||||
'-I %(bn)s-PREROUTING 1 -j %(bn)s-mark\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n' % iptables_args)
|
||||
|
||||
|
@ -231,13 +248,16 @@ def _generate_mangle_dump(iptables_args):
|
|||
def _generate_raw_dump(iptables_args):
|
||||
return ('# Generated by iptables_manager\n'
|
||||
'*raw\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':PREROUTING - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-PREROUTING - [0:0]\n'
|
||||
'[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
|
||||
'-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n' % iptables_args)
|
||||
|
||||
|
||||
MANGLE_DUMP = _generate_mangle_dump(IPTABLES_ARG)
|
||||
RAW_DUMP = _generate_raw_dump(IPTABLES_ARG)
|
||||
|
||||
|
@ -272,25 +292,25 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
|
||||
def _extend_with_ip6tables_filter(self, expected_calls, filter_dump):
|
||||
expected_calls.insert(2, (
|
||||
mock.call(['ip6tables-save', '-c'],
|
||||
mock.call(['ip6tables-save'],
|
||||
run_as_root=True),
|
||||
''))
|
||||
expected_calls.insert(3, (
|
||||
mock.call(['ip6tables-restore', '-c'],
|
||||
mock.call(['ip6tables-restore', '-n'],
|
||||
process_input=filter_dump,
|
||||
run_as_root=True),
|
||||
None))
|
||||
expected_calls.extend([
|
||||
(mock.call(['ip6tables-save', '-c'],
|
||||
(mock.call(['ip6tables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['ip6tables-restore', '-c'],
|
||||
(mock.call(['ip6tables-restore', '-n'],
|
||||
process_input=filter_dump,
|
||||
run_as_root=True),
|
||||
None)])
|
||||
|
||||
def _test_add_and_remove_chain_custom_binary_name_helper(self, use_ipv6):
|
||||
bn = ("abcdef" * 5)
|
||||
bn = ("xbcdef" * 5)
|
||||
|
||||
self.iptables = iptables_manager.IptablesManager(
|
||||
binary_name=bn,
|
||||
|
@ -311,26 +331,26 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
mangle_dump = _generate_mangle_dump(iptables_args)
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(raw_dump + nat_dump + mangle_dump +
|
||||
filter_dump_mod),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump_mod + mangle_dump +
|
||||
nat_dump + raw_dump),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(raw_dump + nat_dump + mangle_dump +
|
||||
filter_dump),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump + mangle_dump +
|
||||
nat_dump + raw_dump),
|
||||
run_as_root=True),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
raw_dump + filter_dump_ipv6)
|
||||
filter_dump_ipv6 + raw_dump)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -349,7 +369,7 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
self._test_add_and_remove_chain_custom_binary_name_helper(True)
|
||||
|
||||
def _test_empty_chain_custom_binary_name_helper(self, use_ipv6):
|
||||
bn = ("abcdef" * 5)[:16]
|
||||
bn = ("xbcdef" * 5)[:16]
|
||||
|
||||
self.iptables = iptables_manager.IptablesManager(
|
||||
binary_name=bn,
|
||||
|
@ -360,7 +380,7 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
|
||||
filter_dump = FILTER_TEMPLATE % iptables_args
|
||||
|
||||
filter_rules = ('[0:0] -A %(bn)s-filter -s 0/0 -d 192.168.0.2\n'
|
||||
filter_rules = ('-I %(bn)s-filter 1 -s 0/0 -d 192.168.0.2\n'
|
||||
% iptables_args)
|
||||
iptables_args['filter_rules'] = filter_rules
|
||||
filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % iptables_args
|
||||
|
@ -371,26 +391,26 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
mangle_dump = _generate_mangle_dump(iptables_args)
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(raw_dump + nat_dump + mangle_dump +
|
||||
filter_dump_mod),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump_mod + mangle_dump +
|
||||
nat_dump + raw_dump),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(raw_dump + nat_dump + mangle_dump +
|
||||
filter_dump),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump + mangle_dump +
|
||||
nat_dump + raw_dump),
|
||||
run_as_root=True),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
raw_dump + filter_dump)
|
||||
filter_dump + raw_dump)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -418,26 +438,26 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % IPTABLES_ARG
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
filter_dump_mod),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump_mod + MANGLE_DUMP +
|
||||
NAT_DUMP + RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
|
||||
RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
RAW_DUMP + FILTER_DUMP)
|
||||
FILTER_DUMP + RAW_DUMP)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -462,36 +482,36 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
|
||||
iptables_args = {}
|
||||
iptables_args.update(IPTABLES_ARG)
|
||||
filter_rules = ('[0:0] -A %(bn)s-filter -j DROP\n'
|
||||
'[0:0] -A %(bn)s-INPUT -s 0/0 -d 192.168.0.2 -j '
|
||||
'%(bn)s-filter\n' % iptables_args)
|
||||
filter_rules = ('-I %(bn)s-INPUT 1 -s 0/0 -d 192.168.0.2 -j '
|
||||
'%(bn)s-filter\n-I %(bn)s-filter 1 -j DROP\n'
|
||||
% iptables_args)
|
||||
iptables_args['filter_rules'] = filter_rules
|
||||
filter_dump_mod = FILTER_WITH_RULES_TEMPLATE % iptables_args
|
||||
|
||||
raw_dump = RAW_DUMP % IPTABLES_ARG
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
filter_dump_mod),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump_mod + MANGLE_DUMP +
|
||||
NAT_DUMP + RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
|
||||
RAW_DUMP),
|
||||
run_as_root=True
|
||||
),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
raw_dump + FILTER_DUMP)
|
||||
FILTER_DUMP + raw_dump)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -533,19 +553,22 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
|
||||
filter_dump_mod = ('# Generated by iptables_manager\n'
|
||||
'*filter\n'
|
||||
':FORWARD - [0:0]\n'
|
||||
':INPUT - [0:0]\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':neutron-filter-top - [0:0]\n'
|
||||
':%(wrap)s - [0:0]\n'
|
||||
':%(bn)s-FORWARD - [0:0]\n'
|
||||
':%(bn)s-INPUT - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-local - [0:0]\n'
|
||||
'[0:0] -A FORWARD -j neutron-filter-top\n'
|
||||
'[0:0] -A OUTPUT -j neutron-filter-top\n'
|
||||
'[0:0] -A neutron-filter-top -j %(bn)s-local\n'
|
||||
'[0:0] -A INPUT -j %(bn)s-INPUT\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A FORWARD -j %(bn)s-FORWARD\n'
|
||||
'[0:0] -A %(bn)s-INPUT -s 0/0 -d 192.168.0.2 -j '
|
||||
'-I FORWARD 1 -j neutron-filter-top\n'
|
||||
'-I FORWARD 2 -j %(bn)s-FORWARD\n'
|
||||
'-I INPUT 1 -j %(bn)s-INPUT\n'
|
||||
'-I OUTPUT 1 -j neutron-filter-top\n'
|
||||
'-I OUTPUT 2 -j %(bn)s-OUTPUT\n'
|
||||
'-I neutron-filter-top 1 -j %(bn)s-local\n'
|
||||
'-I %(bn)s-INPUT 1 -s 0/0 -d 192.168.0.2 -j '
|
||||
'%(wrap)s\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n'
|
||||
|
@ -554,26 +577,26 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
raw_dump = RAW_DUMP % IPTABLES_ARG
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
filter_dump_mod),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(filter_dump_mod + MANGLE_DUMP +
|
||||
NAT_DUMP + RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP +
|
||||
NAT_DUMP + RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
raw_dump + FILTER_DUMP)
|
||||
FILTER_DUMP + raw_dump)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -606,6 +629,11 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
mangle_dump_mod = (
|
||||
'# Generated by iptables_manager\n'
|
||||
'*mangle\n'
|
||||
':FORWARD - [0:0]\n'
|
||||
':INPUT - [0:0]\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':POSTROUTING - [0:0]\n'
|
||||
':PREROUTING - [0:0]\n'
|
||||
':%(bn)s-FORWARD - [0:0]\n'
|
||||
':%(bn)s-INPUT - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
|
@ -613,37 +641,37 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
':%(bn)s-PREROUTING - [0:0]\n'
|
||||
':%(bn)s-mangle - [0:0]\n'
|
||||
':%(bn)s-mark - [0:0]\n'
|
||||
'[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n'
|
||||
'[0:0] -A INPUT -j %(bn)s-INPUT\n'
|
||||
'[0:0] -A FORWARD -j %(bn)s-FORWARD\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n'
|
||||
'[0:0] -A %(bn)s-PREROUTING -j %(bn)s-mark\n'
|
||||
'[0:0] -A %(bn)s-PREROUTING -j MARK --set-xmark 0x1/%(mark)s\n'
|
||||
'-I FORWARD 1 -j %(bn)s-FORWARD\n'
|
||||
'-I INPUT 1 -j %(bn)s-INPUT\n'
|
||||
'-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
|
||||
'-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
|
||||
'-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
|
||||
'-I %(bn)s-PREROUTING 1 -j %(bn)s-mark\n'
|
||||
'-I %(bn)s-PREROUTING 2 -j MARK --set-xmark 0x1/%(mark)s\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n' % IPTABLES_ARG)
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + mangle_dump_mod +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + mangle_dump_mod +
|
||||
NAT_DUMP + RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP +
|
||||
NAT_DUMP + RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
RAW_DUMP + FILTER_DUMP)
|
||||
FILTER_DUMP + RAW_DUMP)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -678,6 +706,9 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
|
||||
nat_dump_mod = ('# Generated by iptables_manager\n'
|
||||
'*nat\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':POSTROUTING - [0:0]\n'
|
||||
':PREROUTING - [0:0]\n'
|
||||
':neutron-postrouting-bottom - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-POSTROUTING - [0:0]\n'
|
||||
|
@ -685,43 +716,42 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
':%(bn)s-float-snat - [0:0]\n'
|
||||
':%(bn)s-nat - [0:0]\n'
|
||||
':%(bn)s-snat - [0:0]\n'
|
||||
'[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A POSTROUTING -j %(bn)s-POSTROUTING\n'
|
||||
'[0:0] -A POSTROUTING -j neutron-postrouting-bottom\n'
|
||||
'[0:0] -A neutron-postrouting-bottom -j %(bn)s-snat\n'
|
||||
'[0:0] -A %(bn)s-snat -j %(bn)s-float-snat\n'
|
||||
'[0:0] -A %(bn)s-PREROUTING -d 192.168.0.3 -j '
|
||||
'-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
|
||||
'-I POSTROUTING 1 -j %(bn)s-POSTROUTING\n'
|
||||
'-I POSTROUTING 2 -j neutron-postrouting-bottom\n'
|
||||
'-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
|
||||
'-I neutron-postrouting-bottom 1 -j %(bn)s-snat\n'
|
||||
'-I %(bn)s-PREROUTING 1 -d 192.168.0.3 -j '
|
||||
'%(bn)s-nat\n'
|
||||
'[0:0] -A %(bn)s-nat -p tcp --dport 8080 -j '
|
||||
'-I %(bn)s-nat 1 -p tcp --dport 8080 -j '
|
||||
'REDIRECT --to-port 80\n'
|
||||
'-I %(bn)s-snat 1 -j %(bn)s-float-snat\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n'
|
||||
% IPTABLES_ARG)
|
||||
'# Completed by iptables_manager\n' % IPTABLES_ARG)
|
||||
|
||||
raw_dump = RAW_DUMP % IPTABLES_ARG
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + nat_dump_mod + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP +
|
||||
nat_dump_mod + RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + nat_dump + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP + nat_dump +
|
||||
RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
raw_dump + FILTER_DUMP)
|
||||
FILTER_DUMP + raw_dump)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -760,37 +790,39 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
|
||||
raw_dump_mod = ('# Generated by iptables_manager\n'
|
||||
'*raw\n'
|
||||
':OUTPUT - [0:0]\n'
|
||||
':PREROUTING - [0:0]\n'
|
||||
':%(bn)s-OUTPUT - [0:0]\n'
|
||||
':%(bn)s-PREROUTING - [0:0]\n'
|
||||
':%(bn)s-raw - [0:0]\n'
|
||||
'[0:0] -A PREROUTING -j %(bn)s-PREROUTING\n'
|
||||
'[0:0] -A OUTPUT -j %(bn)s-OUTPUT\n'
|
||||
'[0:0] -A %(bn)s-PREROUTING -j CT --notrack\n'
|
||||
'-I OUTPUT 1 -j %(bn)s-OUTPUT\n'
|
||||
'-I PREROUTING 1 -j %(bn)s-PREROUTING\n'
|
||||
'-I %(bn)s-PREROUTING 1 -j CT --notrack\n'
|
||||
'COMMIT\n'
|
||||
'# Completed by iptables_manager\n'
|
||||
% IPTABLES_ARG)
|
||||
|
||||
expected_calls_and_values = [
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(raw_dump_mod + NAT_DUMP + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
|
||||
raw_dump_mod),
|
||||
run_as_root=True),
|
||||
None),
|
||||
(mock.call(['iptables-save', '-c'],
|
||||
(mock.call(['iptables-save'],
|
||||
run_as_root=True),
|
||||
''),
|
||||
(mock.call(['iptables-restore', '-c'],
|
||||
process_input=(RAW_DUMP + NAT_DUMP + MANGLE_DUMP +
|
||||
FILTER_DUMP),
|
||||
(mock.call(['iptables-restore', '-n'],
|
||||
process_input=(FILTER_DUMP + MANGLE_DUMP + NAT_DUMP +
|
||||
RAW_DUMP),
|
||||
run_as_root=True),
|
||||
None),
|
||||
]
|
||||
if use_ipv6:
|
||||
self._extend_with_ip6tables_filter(expected_calls_and_values,
|
||||
RAW_DUMP + FILTER_DUMP)
|
||||
FILTER_DUMP + RAW_DUMP)
|
||||
|
||||
tools.setup_mock_calls(self.execute, expected_calls_and_values)
|
||||
|
||||
|
@ -1012,43 +1044,6 @@ class IptablesManagerStateFulTestCase(base.BaseTestCase):
|
|||
def test_get_traffic_counters_with_zero_with_ipv6(self):
|
||||
self._test_get_traffic_counters_with_zero_helper(True)
|
||||
|
||||
def _test_find_last_entry(self, find_str):
|
||||
filter_list = [':neutron-filter-top - [0:0]',
|
||||
':%(bn)s-FORWARD - [0:0]',
|
||||
':%(bn)s-INPUT - [0:0]',
|
||||
':%(bn)s-local - [0:0]',
|
||||
':%(wrap)s - [0:0]',
|
||||
':%(bn)s-OUTPUT - [0:0]',
|
||||
'[0:0] -A FORWARD -j neutron-filter-top',
|
||||
'[0:0] -A OUTPUT -j neutron-filter-top'
|
||||
% IPTABLES_ARG]
|
||||
filter_map = iptables_manager.make_filter_map(filter_list)
|
||||
return self.iptables._find_last_entry(filter_map, find_str)
|
||||
|
||||
def test_find_last_entry_old_dup(self):
|
||||
find_str = '-A OUTPUT -j neutron-filter-top'
|
||||
match_str = '[0:0] -A OUTPUT -j neutron-filter-top'
|
||||
ret_str = self._test_find_last_entry(find_str)
|
||||
self.assertEqual(ret_str, match_str)
|
||||
|
||||
def test_find_last_entry_none(self):
|
||||
find_str = 'neutron-filter-NOTFOUND'
|
||||
ret_str = self._test_find_last_entry(find_str)
|
||||
self.assertIsNone(ret_str)
|
||||
|
||||
def test_make_filter_map_cidr_stripping(self):
|
||||
filter_rules = ('[0:0] -A OUTPUT -j DROP',
|
||||
'[0:0] -A INPUT -d 192.168.0.2/32 -j DROP',
|
||||
'[0:0] -A INPUT -d 1234:31::001F/128 -j DROP',
|
||||
'OUTPUT - [0:0]')
|
||||
filter_map = iptables_manager.make_filter_map(filter_rules)
|
||||
# make sure /128 works without CIDR
|
||||
self.assertEqual(filter_rules[2],
|
||||
filter_map['-A INPUT -d 1234:31::001F -j DROP'][0])
|
||||
# make sure /32 works without CIDR
|
||||
self.assertEqual(filter_rules[1],
|
||||
filter_map['-A INPUT -d 192.168.0.2 -j DROP'][0])
|
||||
|
||||
|
||||
class IptablesManagerStateLessTestCase(base.BaseTestCase):
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue