def test_longest_prefix(self): self.assertEqual(futils.find_longest_prefix([]), None) self.assertEqual(futils.find_longest_prefix(["a"]), "a") self.assertEqual(futils.find_longest_prefix(["a", ""]), "") self.assertEqual(futils.find_longest_prefix(["a", "ab"]), "a") self.assertEqual(futils.find_longest_prefix(["ab", "ab"]), "ab") self.assertEqual(futils.find_longest_prefix(["ab", "ab", "abc"]), "ab") self.assertEqual(futils.find_longest_prefix(["abc", "ab", "ab"]), "ab") self.assertEqual(futils.find_longest_prefix(["ab", "cd"]), "") self.assertEqual(futils.find_longest_prefix(["tapabcd", "tapacdef"]), "tapa")
def _calculate_update(self, ifaces): """ Calculates the iptables update to rewrite our chains. To avoid traversing lots of dispatch rules to find the right one, we build a tree of chains. Currently, the tree can only be two layers deep: a root chain and a layer of leaves. Interface names look like this: "prefix1234abc". The "prefix" part is always the same so we ignore it. We call "1234abc", the "suffix". The root chain contains two sorts of rules: * where there are multiple interfaces whose suffixes start with the same character, it contains a rule that matches on that prefix of the "suffix"(!) and directs the packet to a leaf chain for that prefix. * as an optimization, if there is only one interface whose suffix starts with a given character, it contains a dispatch rule for that exact interface name. For example, if we have interface names "tapA1" "tapB1" "tapB2", we'll get (in pseudo code): Root chain: if interface=="tapA1" then goto chain for endpoint tapA1 if interface.startswith("tapB") then goto leaf chain for prefix "tapB" tapB leaf chain: if interface=="tapB1" then goto chain for endpoint tapB1 if interface=="tapB2" then goto chain for endpoint tapB2 :param set[str] ifaces: The list of interfaces to generate a dispatch chain for. :returns Tuple: to_delete, deps, updates, new_leaf_chains: * set of leaf chains that are no longer needed for deletion * chain dependency dict. * chain updates dict. * complete set of leaf chains that are now required. """ # iptables update fragments/dependencies for the root chains. updates = defaultdict(list) root_to_upds = updates[self.chain_to_root] root_from_upds = updates[self.chain_from_root] dependencies = defaultdict(set) root_to_deps = dependencies[self.chain_to_root] root_from_deps = dependencies[self.chain_from_root] # Separate the interface names by their prefixes so we can count them # and decide whether to program a leaf chain or not. interfaces_by_prefix = defaultdict(set) iface_prefix = find_longest_prefix(ifaces) for iface in ifaces: ep_suffix = iface[len(iface_prefix):] prefix = ep_suffix[:1] interfaces_by_prefix[prefix].add(iface) # Spin through the interfaces by prefix. Either add them directly # to the root chain or create a leaf and add them there. new_leaf_chains = set() for prefix, interfaces in interfaces_by_prefix.iteritems(): use_root_chain = len(interfaces) == 1 if use_root_chain: # Optimization: there's only one interface with this prefix, # don't program a leaf chain. disp_to_chain = self.chain_to_root disp_from_chain = self.chain_from_root to_deps = root_to_deps from_deps = root_from_deps to_upds = root_to_upds from_upds = root_from_upds else: # There's more than one interface with this prefix, program # a leaf chain. disp_to_chain = self.chain_to_leaf + "-" + prefix disp_from_chain = self.chain_from_leaf + "-" + prefix to_upds = updates[disp_to_chain] from_upds = updates[disp_from_chain] to_deps = dependencies[disp_to_chain] from_deps = dependencies[disp_from_chain] new_leaf_chains.add(disp_from_chain) new_leaf_chains.add(disp_to_chain) # Root chain depends on its leaves. root_from_deps.add(disp_to_chain) root_to_deps.add(disp_from_chain) # Point root chain at prefix chain. iface_match = iface_prefix + prefix + "+" root_from_upds.append( "--append %s --in-interface %s --goto %s" % (self.chain_from_root, iface_match, disp_from_chain) ) root_to_upds.append( "--append %s --out-interface %s --goto %s" % (self.chain_to_root, iface_match, disp_to_chain) ) # Common processing, add the per-endpoint rules to whichever # chain we decided above. for iface in interfaces: # Add rule to leaf or global chain to direct traffic to the # endpoint-specific one. Note that we use --goto, which means # that the endpoint-specific chain will return to our parent # rather than to this chain. ep_suffix = interface_to_chain_suffix(self.config, iface) to_chain_name = CHAIN_TO_PREFIX + ep_suffix from_chain_name = CHAIN_FROM_PREFIX + ep_suffix from_upds.append("--append %s --in-interface %s --goto %s" % (disp_from_chain, iface, from_chain_name)) from_deps.add(from_chain_name) to_upds.append("--append %s --out-interface %s --goto %s" % (disp_to_chain, iface, to_chain_name)) to_deps.add(to_chain_name) if not use_root_chain: # Add a default drop to the end of the leaf chain. # Add a default drop to the end of the leaf chain. from_upds.extend( self.end_of_chain_rules(disp_from_chain, "From")) to_upds.extend( self.end_of_chain_rules(disp_to_chain, "To")) # Both TO and FROM chains end with a DROP so that interfaces that # we don't know about yet can't bypass our rules. root_from_upds.extend( self.end_of_chain_rules(self.chain_from_root, "From")) root_to_upds.extend( self.end_of_chain_rules(self.chain_to_root, "To")) chains_to_delete = self.programmed_leaf_chains - new_leaf_chains _log.debug("New chains: %s; to delete: %s", new_leaf_chains, chains_to_delete) return chains_to_delete, dependencies, updates, new_leaf_chains