def test_read_table(self): state = fiptables.TableState() with mock.patch('calico.felix.futils.check_call'): state.read_table(IPV4, "blah") futils.check_call.assert_called_with(["iptables", "--wait", "--list-rules", "--table", "blah"]) with mock.patch('calico.felix.futils.check_call'): state.read_table(IPV6, "blah") futils.check_call.assert_called_with(["ip6tables", "--wait", "--list-rules", "--table", "blah"])
def test_load_table_no_chain(self): """ Test that loading a rule in a chain which does not exist fails. """ state = fiptables.TableState() data = "\n".join(["-P INPUT ACCEPT\n", "-A no-chain -j felix-INPUT\n"]) state.read_table = mock.Mock(return_value = data) with self.assertRaisesRegexp(fiptables.UnrecognisedIptablesField, "chain which does not exist"): state.load_table(IPV4, "filter")
def test_load_table_bad_flag(self): """ Test that loading a rule with an unknown flag fails. """ state = fiptables.TableState() data = "\n".join(["-P INPUT ACCEPT\n", "-A INPUT -x felix-INPUT\n"]) state.read_table = mock.Mock(return_value = data) with self.assertRaisesRegexp(fiptables.UnrecognisedIptablesField, "Unable to parse"): state.load_table(IPV4, "filter")
def test_negative_fields(self): """ Test negative fields """ lines = [ "-N felix-from-blah", "-A felix-from-blah ! -d 1.2.3.4/32 -j DROP", "-A felix-from-blah ! -d 1.2.3.4/32 -p tcp -j DROP", "-A felix-from-blah ! -d 1.2.3.4/32 ! -p udp -j DROP", "-A felix-from-blah -m mark ! --mark 0x1 -j DROP"] data = "\n".join(lines) state = fiptables.TableState() state.read_table = mock.Mock(return_value = data) table = state.load_table(IPV4, "filter") chain = table.chains["felix-from-blah"] # Check that lot got parsed as expected. self.assertTrue(len(chain.rules), 4) rule = fiptables.Rule(IPV4) rule.dst = "!1.2.3.4/32" rule.target = "DROP" self.assertEqual(chain.rules[0], rule) rule.protocol = "tcp" self.assertEqual(chain.rules[1], rule) rule.protocol = "!udp" self.assertEqual(chain.rules[2], rule) rule = fiptables.Rule(IPV4) rule.match = "mark" rule.parameters["mark"] = "!0x1" rule.target = "DROP" self.assertEqual(chain.rules[3], rule) # Now convert back again, and compare. Lines are different here in that # we have (a) removed the chain specification; and (b) reordered # fields. lines = [ "! -d 1.2.3.4/32 -j DROP", "! -d 1.2.3.4/32 -p tcp -j DROP", "! -d 1.2.3.4/32 ! -p udp -j DROP", "-m mark -j DROP ! --mark 0x1"] for loop in range(0,4): actual = " ".join(chain.rules[loop].generate_fields()) self.assertEqual(actual, lines[loop])
def test_load_table(self): state = fiptables.TableState() data = "\n".join(["-P INPUT ACCEPT\n", "-P FORWARD ACCEPT\n", "-P OUTPUT ACCEPT\n", "-N felix-FORWARD\n", "-N felix-FROM-ENDPOINT\n", "-N felix-INPUT\n", "-N felix-TO-ENDPOINT\n", "-N felix-from-19f8308f-81\n", "-N felix-from-e6d6a9a9-37\n", "-N felix-to-19f8308f-81\n", "-N felix-to-e6d6a9a9-37\n", "-A INPUT -j felix-INPUT\n", "-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT\n", "-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT\n", "-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT\n", "-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT\n", "-A INPUT -j nova-api-INPUT\n", "-A FORWARD -j felix-FORWARD\n", "-A FORWARD -d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT\n", "-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT\n", "-A FORWARD -i virbr0 -o virbr0 -j ACCEPT\n", "-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable\n", "-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable\n", "-A FORWARD -j nova-filter-top\n", "-A FORWARD -j nova-api-FORWARD\n"]) state.read_table = mock.Mock(return_value = data) table = state.load_table(IPV4, "filter") # Check that the state contains what we expect. self.assertEqual(len(table.chains), 11) # Check a rules is present as expected. rule = fiptables.Rule(IPV4, "ACCEPT") rule.protocol = "udp" rule.in_interface = "virbr0" rule.match = "udp" rule.parameters["dport"] = "53" self.assertEqual(str(table.chains["INPUT"].rules[1]), "-p udp -i virbr0 -m udp -j ACCEPT --dport 53") self.assertEqual(table.chains["INPUT"].rules[1], rule)
def test_load_table_rule_not_known(self): """ Test that loading a rule which we cannot understand is still valid. """ state = fiptables.TableState() data = "\n".join(["-P INPUT ACCEPT\n", "-A INPUT -i eth0 -p tcp --some-thing x y -j DROP"]) state.read_table = mock.Mock(return_value = data) table = state.load_table(IPV4, "filter") self.assertEqual(str(table.chains["INPUT"].rules[0]), "-p tcp -i eth0 -j DROP --some-thing x y") rule = fiptables.Rule(IPV4, "DROP") rule.protocol = "tcp" rule.in_interface = "eth0" rule.parameters["some-thing"] = "x y" self.assertEqual(rule, table.chains["INPUT"].rules[0])
def __init__(self, config_path, context): # Get some configuration. self.config = Config(config_path) # Complete logging initialisation, now we have config. common.complete_logging(self.config.LOGFILE, self.config.LOGLEVFILE, self.config.LOGLEVSYS, self.config.LOGLEVSCR) # We have restarted and set up logs - tell the world. log.error("Felix starting (version: %s)", pkg_resources.get_distribution('calico')) # The ZeroMQ context for this Felix. self.zmq_context = context # The hostname of the machine on which this Felix is running. self.hostname = self.config.HOSTNAME # The sockets owned by this Felix, keyed off their socket type. self.sockets = {} # The endpoints managed by this Felix, keyed off their UUID. self.endpoints = {} # Set of UUIDs of endpoints that need to be retried (the interface did # not exist when the ENDPOINTCREATED was received). self.ep_retry = set() # Properties for handling resynchronization. # # resync_id is a UUID for the resync, passed on the API. It ensures # that we can correlate ENDPOINTCREATED requests with resyncs. If this # field is None, then no resync is in progress, and neither resync_recd # nor resync_expected is meaningful. self.resync_id = None # resync_recd counts all of the ENDPOINTCREATED requests received for # this resync, so we know when they have all arrived. self.resync_recd = None # resync_expected is the number of ENDPOINTCREATED requests that are # going to be sent for this resync, as reported in the resync # response. This is None if that response has not yet been received. self.resync_expected = None # resync_time is always defined once the first resync has been sent. It # is the time, in integer milliseconds since the epoch, of the sending # of the last resync. This is used to detect when it is time for # another resync. Note that integers in python automatically convert # from 32 to 64 bits when they are too large, and so we do not have to # worry about overflowing for many thousands of years yet. self.resync_time = None # Interface prefix. Only present after first resync response received. self.iface_prefix = None # Build a dispatch table for handling various messages. self.handlers = { Message.TYPE_HEARTBEAT: self.handle_heartbeat, Message.TYPE_EP_CR: self.handle_endpointcreated, Message.TYPE_EP_UP: self.handle_endpointupdated, Message.TYPE_EP_RM: self.handle_endpointdestroyed, Message.TYPE_RESYNC: self.handle_resyncstate, Message.TYPE_GET_ACL: self.handle_getaclstate, Message.TYPE_ACL_UPD: self.handle_aclupdate, } # Initiate our connections. self.connect_to_plugin() self.connect_to_acl_manager() # Grab a new iptables state. self.iptables_state = fiptables.TableState() # Begin full endpoint resync. We do not resync ACLs, since we resync # the ACLs for each endpoint when we are get an ENDPOINTCREATED in the # endpoint resync (and doing it now when we don't know of any endpoints # would just be a noop anyway). self.resync_endpoints()
def test_insert_rule(self): """ Test insert_rule """ data = "\n".join(["-N blah", "-A blah -j original"]) state = fiptables.TableState() state.read_table = mock.Mock(return_value = data) table = state.get_table(IPV4, "filter") chain = table.get_chain("blah") self.assertTrue(len(chain.rules), 1) # Set up a handy list of arguments base_args = ["iptables", "--wait", "--table", "filter"] # Put a new rule at the start rule = fiptables.Rule(IPV4, "rule1") chain.insert_rule(rule) self.assertTrue(len(chain.rules), 2) self.assertTrue(chain.rules[0].target, "rule1") self.assertTrue(chain.rules[1].target, "original") args = copy(base_args) args.extend(["--insert", "blah", "1", "-j", "rule1"]) self.assertEqual(len(table.ops), 1) self.assertEqual(table.ops[0], args) # Put in a copy of the original rule, forcing position. rule = fiptables.Rule(IPV4, "original") chain.insert_rule(rule) self.assertTrue(len(chain.rules), 3) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") args = copy(base_args) args.extend(["--insert", "blah", "1", "-j", "original"]) self.assertEqual(len(table.ops), 2) self.assertEqual(table.ops[1], args) # Now add another copy. Does not get added, as already there. rule = fiptables.Rule(IPV4, "original") chain.insert_rule(rule) self.assertTrue(len(chain.rules), 3) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertEqual(len(table.ops), 2) # Add rule1 again - doesn't get added as not forcing position. rule = fiptables.Rule(IPV4, "rule1") chain.insert_rule(rule, 0, False) self.assertTrue(len(chain.rules), 3) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertEqual(len(table.ops), 2) chain.insert_rule(rule, 1, False) self.assertTrue(len(chain.rules), 3) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertEqual(len(table.ops), 2) chain.insert_rule(rule, 2, False) self.assertTrue(len(chain.rules), 3) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertEqual(len(table.ops), 2) chain.insert_rule(rule, fiptables.RULE_POSN_LAST, False) self.assertTrue(len(chain.rules), 3) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertEqual(len(table.ops), 2) # Now use POSN_LAST but forcing position rule = fiptables.Rule(IPV4, "original") chain.insert_rule(rule, fiptables.RULE_POSN_LAST) self.assertTrue(len(chain.rules), 3) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertEqual(len(table.ops), 2) rule = fiptables.Rule(IPV4, "rule1") chain.insert_rule(rule, fiptables.RULE_POSN_LAST) self.assertTrue(len(chain.rules), 4) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertTrue(chain.rules[3].target, "rule1") args = copy(base_args) args.extend(["--append", "blah", "-j", "rule1"]) self.assertEqual(len(table.ops), 3) self.assertEqual(table.ops[2], args) rule = fiptables.Rule(IPV4, "rule1") chain.insert_rule(rule, fiptables.RULE_POSN_LAST, True) self.assertTrue(len(chain.rules), 4) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertTrue(chain.rules[3].target, "rule1") self.assertEqual(len(table.ops), 3) rule = fiptables.Rule(IPV4, "new") chain.insert_rule(rule, fiptables.RULE_POSN_LAST, False) self.assertTrue(len(chain.rules), 5) self.assertTrue(chain.rules[0].target, "original") self.assertTrue(chain.rules[1].target, "rule1") self.assertTrue(chain.rules[2].target, "original") self.assertTrue(chain.rules[3].target, "rule1") self.assertTrue(chain.rules[4].target, "original") args = copy(base_args) args.extend(["--append", "blah", "-j", "new"]) self.assertEqual(len(table.ops), 4) self.assertEqual(table.ops[3], args)
def test_apply(self): """ Test apply method. """ data = "\n".join(["-P INPUT ACCEPT", "-P FORWARD ACCEPT", "-P OUTPUT ACCEPT", "-N felix-FORWARD", "-N felix-FROM-ENDPOINT", "-N felix-INPUT", "-N felix-TO-ENDPOINT", "-N felix-from-19f8308f-81", "-N felix-from-e6d6a9a9-37", "-N felix-to-19f8308f-81", "-N felix-to-e6d6a9a9-37", "-A INPUT -j felix-INPUT", "-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT", "-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT", "-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT", "-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT", "-A INPUT -j nova-api-INPUT", "-A FORWARD -j felix-FORWARD", "-A FORWARD -d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT", "-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT", "-A FORWARD -i virbr0 -o virbr0 -j ACCEPT", "-A FORWARD -o virbr0 -j REJECT --reject-with icmp-port-unreachable", "-A FORWARD -i virbr0 -j REJECT --reject-with icmp-port-unreachable", "-A FORWARD -j nova-filter-top", "-A FORWARD -j nova-api-FORWARD"]) state = fiptables.TableState() state.read_table = mock.Mock(return_value = data) table = state.get_table(IPV4, "filter") # Build up a list of rules as they will be after changes. rules = [] rules.append(fiptables.Rule(IPV4)) line = "-j felix-FORWARD" rules[0].parse_fields(line, line.split()) # New rule to be added at location 1 rules.append(fiptables.Rule(IPV4, "somewhere")) rules[1].protocol = "udp" rules[1].in_interface = "blah" rules[1].out_interface = "stuff" rules[1].match = "udp" rules[1].parameters["dport"] = "53" line = "-d 192.168.122.0/24 -o virbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT" rules.append(fiptables.Rule(IPV4)) rules[2].parse_fields(line, line.split()) chain = table.get_chain("FORWARD") self.assertEqual(len(chain.rules), 8) chain.insert_rule(rules[1], 1) chain.truncate_rules(3) # Verify that the rules are as expected. self.assertEqual(len(chain.rules), 3) self.assertEqual(rules, chain.rules) # Now test apply. with mock.patch('calico.felix.futils.multi_call') as mock_call: state.apply() self.assertEqual(mock_call.call_count, 1) ops = [] op = ["iptables", "--wait", "--table", "filter", "--insert", "FORWARD", "2"] op.extend(rules[1].generate_fields()) ops.append(op) for loop in range(0,6): ops.append(["iptables", "--wait", "--table", "filter", "--delete", "FORWARD", "4"]) self.assertEqual(mock_call.call_count, 1) mock_call.assert_called_with(ops) # Apply again - this is a noop, as states match. with mock.patch('calico.felix.futils.multi_call') as mock_call: state.apply() self.assertEqual(mock_call.call_count, 0)