def __init__(self, profile_id, ip_version, iptables_updater, ipset_mgr):
        super(ProfileRules, self).__init__(qualifier=profile_id)
        assert profile_id is not None

        self.id = profile_id
        self.ip_version = ip_version
        self._ipset_mgr = ipset_mgr
        self._iptables_updater = iptables_updater
        self._ipset_refs = RefHelper(self, ipset_mgr, self._on_ipsets_acquired)

        # Latest profile update - a profile dictionary.
        self._pending_profile = None
        # Currently-programmed profile dictionary.
        self._profile = None

        # State flags.
        self._notified_ready = False
        self._cleaned_up = False
        self._dead = False
        self._dirty = True

        self.chain_names = {
            "inbound": profile_to_chain_name("inbound", profile_id),
            "outbound": profile_to_chain_name("outbound", profile_id),
        }
        _log.info("Profile %s has chain names %s",
                  profile_id, self.chain_names)
Example #2
0
    def __init__(self, profile_id, ip_version, iptables_updater, ipset_mgr):
        super(ProfileRules, self).__init__(qualifier=profile_id)
        assert profile_id is not None

        self.id = profile_id
        self.ip_version = ip_version
        self._ipset_mgr = ipset_mgr
        self._iptables_updater = iptables_updater
        self._ipset_refs = RefHelper(self, ipset_mgr, self._on_ipsets_acquired)

        # Latest profile update - a profile dictionary.
        self._pending_profile = None
        # Currently-programmed profile dictionary.
        self._profile = None

        # State flags.
        self._notified_ready = False
        self._cleaned_up = False
        self._dead = False
        self._dirty = True

        self.chain_names = {
            "inbound": profile_to_chain_name("inbound", profile_id),
            "outbound": profile_to_chain_name("outbound", profile_id),
        }
        _log.info("Profile %s has chain names %s", profile_id,
                  self.chain_names)
Example #3
0
def _build_to_or_from_chain(endpoint_id, profile_ids, chain_name, direction, expected_mac=None):
    # Ensure the MARK is set to 0 when we start so that unmatched packets will
    # be dropped.
    chain = ["--append %s --jump MARK --set-mark 0" % chain_name]
    if expected_mac:
        _log.debug("Policing source MAC: %s", expected_mac)
        chain.append(
            "--append %s --match mac ! --mac-source %s --jump DROP "
            '--match comment --comment "Incorrect source MAC"' % (chain_name, expected_mac)
        )
    # Jump to each profile in turn.  The profile will do one of the
    # following:
    # * DROP the packet; in which case we won't see it again.
    # * RETURN the packet with MARK==1, indicating it accepted the packet. In
    #   which case, we RETURN and skip further profiles.
    # * RETURN the packet with MARK==0, indicating it did not match the packet.
    #   In which case, we carry on and process the next profile.
    deps = set()
    for profile_id in profile_ids:
        profile_chain = profile_to_chain_name(direction, profile_id)
        deps.add(profile_chain)
        chain.append("--append %s --jump %s" % (chain_name, profile_chain))
        # If the profile accepted the packet, it sets MARK==1.  Immediately
        # RETURN the packet to signal that it's been accepted.
        chain.append(
            "--append %s --match mark --mark 1/1 "
            '--match comment --comment "Profile accepted packet" '
            "--jump RETURN" % chain_name
        )

    # Default drop rule.
    chain.append(commented_drop_fragment(chain_name, "Default DROP if no match (endpoint %s):" % endpoint_id))
    return chain, deps
Example #4
0
    def __init__(self, profile_id, ip_version, iptables_updater, ipset_mgr):
        super(ProfileRules, self).__init__(qualifier=profile_id)
        assert profile_id is not None

        self.id = profile_id
        self.ip_version = ip_version
        self.ipset_mgr = ipset_mgr
        self._iptables_updater = iptables_updater
        self.notified_ready = False

        self.ipset_refs = RefHelper(self, ipset_mgr, self._maybe_update)

        self._profile = None
        """
        :type dict|None: filled in by first update.  Reset to None on delete.
        """
        self.dead = False

        self.chain_names = {
            "inbound": profile_to_chain_name("inbound", profile_id),
            "outbound": profile_to_chain_name("outbound", profile_id),
        }
        _log.info("Profile %s has chain names %s",
                  profile_id, self.chain_names)
Example #5
0
    def __init__(self, profile_id, ip_version, iptables_updater, ipset_mgr):
        super(ProfileRules, self).__init__(qualifier=profile_id)
        assert profile_id is not None

        self.id = profile_id
        self.ip_version = ip_version
        self.ipset_mgr = ipset_mgr
        self._iptables_updater = iptables_updater
        self.notified_ready = False

        self.ipset_refs = RefHelper(self, ipset_mgr, self._maybe_update)

        self._profile = None
        """
        :type dict|None: filled in by first update.  Reset to None on delete.
        """
        self.dead = False

        self.chain_names = {
            "inbound": profile_to_chain_name("inbound", profile_id),
            "outbound": profile_to_chain_name("outbound", profile_id),
        }
        _log.info("Profile %s has chain names %s",
                  profile_id, self.chain_names)
Example #6
0
def _build_to_or_from_chain(endpoint_id,
                            profile_ids,
                            chain_name,
                            direction,
                            expected_mac=None):
    # Ensure the MARK is set to 0 when we start so that unmatched packets will
    # be dropped.
    chain = ["--append %s --jump MARK --set-mark 0" % chain_name]
    if expected_mac:
        _log.debug("Policing source MAC: %s", expected_mac)
        chain.append('--append %s --match mac ! --mac-source %s --jump DROP '
                     '--match comment --comment "Incorrect source MAC"' %
                     (chain_name, expected_mac))
    # Jump to each profile in turn.  The profile will do one of the
    # following:
    # * DROP the packet; in which case we won't see it again.
    # * RETURN the packet with MARK==1, indicating it accepted the packet. In
    #   which case, we RETURN and skip further profiles.
    # * RETURN the packet with MARK==0, indicating it did not match the packet.
    #   In which case, we carry on and process the next profile.
    deps = set()
    for profile_id in profile_ids:
        profile_chain = profile_to_chain_name(direction, profile_id)
        deps.add(profile_chain)
        chain.append("--append %s --jump %s" % (chain_name, profile_chain))
        # If the profile accepted the packet, it sets MARK==1.  Immediately
        # RETURN the packet to signal that it's been accepted.
        chain.append('--append %s --match mark --mark 1/1 '
                     '--match comment --comment "Profile accepted packet" '
                     '--jump RETURN' % chain_name)

    # Default drop rule.
    chain.append(
        commented_drop_fragment(
            chain_name,
            "Default DROP if no match (endpoint %s):" % endpoint_id))
    return chain, deps
Example #7
0
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac,
                        profile_ids):
    to_chain_name, from_chain_name = chain_names(suffix)

    # First build the chain that manages packets to the interface.
    # Common chain prefixes.  Allow IPv6 ICMP and conntrack rules.
    to_chain = ["--flush %s" % to_chain_name]
    if ip_version == 6:
        #  In ipv6 only, there are 6 rules that need to be created first.
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 130
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 131
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 132
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp router-advertisement
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-solicitation
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-advertisement
        #
        #  These rules are ICMP types 130, 131, 132, 134, 135 and 136.
        for icmp_type in ["130", "131", "132", "134", "135", "136"]:
            to_chain.append("--append %s --jump RETURN "
                            "--protocol ipv6-icmp "
                            "--icmpv6-type %s" % (to_chain_name, icmp_type))
    to_chain.append("--append %s --match conntrack --ctstate INVALID "
                    "--jump DROP" % to_chain_name)
    to_chain.append("--append %s --match conntrack "
                    "--ctstate RELATED,ESTABLISHED --jump RETURN" %
                    to_chain_name)

    # Jump to each profile in turn.  The profile will do one of the
    # following:
    # * DROP the packet; in which case we won't see it again.
    # * RETURN without MARKing the packet; indicates that the packet was
    #   accepted.
    # * RETURN with a mark on the packet, indicates reaching the end
    #   of the chain.
    to_deps = set()
    for profile_id in profile_ids:
        profile_in_chain = profile_to_chain_name("inbound", profile_id)
        to_deps.add(profile_in_chain)
        to_chain.append("--append %s --jump MARK --set-mark 0" %
                        to_chain_name)
        to_chain.append("--append %s --jump %s" %
                        (to_chain_name, profile_in_chain))
        to_chain.append('--append %s --match mark ! --mark 1/1 '
                        '--match comment --comment "No mark means profile '
                        'accepted packet" --jump RETURN' % to_chain_name)

    # Default drop rule.
    to_chain.append(commented_drop_fragment(to_chain_name,
                                            "Endpoint %s:" % endpoint_id))

    # Now the chain that manages packets from the interface...
    from_chain = ["--flush %s" % from_chain_name]
    if ip_version == 6:
        # In ipv6 only, allow all ICMP traffic from this endpoint to anywhere.
        from_chain.append("--append %s --protocol ipv6-icmp --jump RETURN" %
                          from_chain_name)

    # Conntrack rules.
    from_chain.append("--append %s --match conntrack --ctstate INVALID "
                      "--jump DROP" % from_chain_name)
    from_chain.append("--append %s --match conntrack "
                      "--ctstate RELATED,ESTABLISHED --jump RETURN" %
                      from_chain_name)

    if ip_version == 4:
        from_chain.append("--append %s --protocol udp --sport 68 --dport 67 "
                          "--jump RETURN" % from_chain_name)
    else:
        assert ip_version == 6
        from_chain.append("--append %s --protocol udp --sport 546 --dport 547 "
                          "--jump RETURN" % from_chain_name)

    # Combined anti-spoofing and jump to profile rules.  The only way to
    # get to a profile chain is to have the correct IP and MAC address.
    from_deps = set()
    for profile_id in profile_ids:
        profile_out_chain = profile_to_chain_name("outbound", profile_id)
        from_deps.add(profile_out_chain)
        from_chain.append("--append %s --jump MARK --set-mark 0" %
                          from_chain_name)
        for ip in local_ips:
            if "/" in ip:
                cidr = ip
            else:
                cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip
            # Jump to each profile in turn.  The profile will do one of the
            # following:
            # * DROP the packet; in which case we won't see it again.
            # * RETURN without MARKing the packet; indicates that the packet
            #   was accepted.
            # * RETURN with a mark on the packet, indicates reaching the end
            #   of the chain.
            from_chain.append("--append %s --src %s --match mac "
                              "--mac-source %s --jump %s" %
                              (from_chain_name, cidr, mac.upper(),
                               profile_out_chain))
        from_chain.append('--append %s --match mark ! --mark 1/1 '
                          '--match comment --comment "No mark means profile '
                          'accepted packet" --jump RETURN' %
                          from_chain_name)

    # Final default DROP if no profile RETURNed or no MAC matched.
    drop_frag = commented_drop_fragment(from_chain_name,
                                        "Default DROP if no match (endpoint %s):" %
                                        endpoint_id)
    from_chain.append(drop_frag)

    updates = {to_chain_name: to_chain, from_chain_name: from_chain}
    deps = {to_chain_name: to_deps, from_chain_name: from_deps}
    return updates, deps
Example #8
0
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac,
                        profile_ids):
    to_chain_name, from_chain_name = chain_names(suffix)

    # First build the chain that manages packets to the interface.
    # Common chain prefixes.
    to_chain = []

    # Jump to each profile in turn.  The profile will do one of the
    # following:
    # * DROP the packet; in which case we won't see it again.
    # * RETURN without MARKing the packet; indicates that the packet was
    #   accepted.
    # * RETURN with a mark on the packet, indicates reaching the end
    #   of the chain.
    to_deps = set()
    for profile_id in profile_ids:
        profile_in_chain = profile_to_chain_name("inbound", profile_id)
        to_deps.add(profile_in_chain)
        to_chain.append("--append %s --jump MARK --set-mark 0" %
                        to_chain_name)
        to_chain.append("--append %s --jump %s" %
                        (to_chain_name, profile_in_chain))
        to_chain.append('--append %s --match mark ! --mark 1/1 '
                        '--match comment --comment "No mark means profile '
                        'accepted packet" --jump RETURN' % to_chain_name)

    # Default drop rule.
    to_chain.append(commented_drop_fragment(to_chain_name,
                                            "Endpoint %s:" % endpoint_id))

    # Now the chain that manages packets from the interface...
    from_chain = []

    # Combined anti-spoofing and jump to profile rules.  The only way to
    # get to a profile chain is to have the correct IP and MAC address.
    from_deps = set()
    for profile_id in profile_ids:
        profile_out_chain = profile_to_chain_name("outbound", profile_id)
        from_deps.add(profile_out_chain)
        from_chain.append("--append %s --jump MARK --set-mark 0" %
                          from_chain_name)
        for ip in local_ips:
            if "/" in ip:
                cidr = ip
            else:
                cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip
            # Jump to each profile in turn.  The profile will do one of the
            # following:
            # * DROP the packet; in which case we won't see it again.
            # * RETURN without MARKing the packet; indicates that the packet
            #   was accepted.
            # * RETURN with a mark on the packet, indicates reaching the end
            #   of the chain.
            from_chain.append("--append %s --src %s --match mac "
                              "--mac-source %s --jump %s" %
                              (from_chain_name, cidr, mac.upper(),
                               profile_out_chain))
        from_chain.append('--append %s --match mark ! --mark 1/1 '
                          '--match comment --comment "No mark means profile '
                          'accepted packet" --jump RETURN' %
                          from_chain_name)

    # Final default DROP if no profile RETURNed or no MAC matched.
    drop_frag = commented_drop_fragment(from_chain_name,
                                        "Default DROP if no match (endpoint %s):" %
                                        endpoint_id)
    from_chain.append(drop_frag)

    updates = {to_chain_name: to_chain, from_chain_name: from_chain}
    deps = {to_chain_name: to_deps, from_chain_name: from_deps}
    return updates, deps
Example #9
0
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac,
                        profile_ids):
    to_chain_name, from_chain_name = chain_names(suffix)

    # First build the chain that manages packets to the interface.
    # Common chain prefixes.
    to_chain = []

    # Jump to each profile in turn.  The profile will do one of the
    # following:
    # * DROP the packet; in which case we won't see it again.
    # * RETURN without MARKing the packet; indicates that the packet was
    #   accepted.
    # * RETURN with a mark on the packet, indicates reaching the end
    #   of the chain.
    to_deps = set()
    for profile_id in profile_ids:
        profile_in_chain = profile_to_chain_name("inbound", profile_id)
        to_deps.add(profile_in_chain)
        to_chain.append("--append %s --jump MARK --set-mark 0" % to_chain_name)
        to_chain.append("--append %s --jump %s" %
                        (to_chain_name, profile_in_chain))
        to_chain.append('--append %s --match mark ! --mark 1/1 '
                        '--match comment --comment "No mark means profile '
                        'accepted packet" --jump RETURN' % to_chain_name)

    # Default drop rule.
    to_chain.append(
        commented_drop_fragment(to_chain_name, "Endpoint %s:" % endpoint_id))

    # Now the chain that manages packets from the interface...
    from_chain = []

    # Combined anti-spoofing and jump to profile rules.  The only way to
    # get to a profile chain is to have the correct IP and MAC address.
    from_deps = set()
    for profile_id in profile_ids:
        profile_out_chain = profile_to_chain_name("outbound", profile_id)
        from_deps.add(profile_out_chain)
        from_chain.append("--append %s --jump MARK --set-mark 0" %
                          from_chain_name)
        for ip in local_ips:
            if "/" in ip:
                cidr = ip
            else:
                cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip
            # Jump to each profile in turn.  The profile will do one of the
            # following:
            # * DROP the packet; in which case we won't see it again.
            # * RETURN without MARKing the packet; indicates that the packet
            #   was accepted.
            # * RETURN with a mark on the packet, indicates reaching the end
            #   of the chain.
            from_chain.append(
                "--append %s --src %s --match mac "
                "--mac-source %s --jump %s" %
                (from_chain_name, cidr, mac.upper(), profile_out_chain))
        from_chain.append('--append %s --match mark ! --mark 1/1 '
                          '--match comment --comment "No mark means profile '
                          'accepted packet" --jump RETURN' % from_chain_name)

    # Final default DROP if no profile RETURNed or no MAC matched.
    drop_frag = commented_drop_fragment(
        from_chain_name,
        "Default DROP if no match (endpoint %s):" % endpoint_id)
    from_chain.append(drop_frag)

    updates = {to_chain_name: to_chain, from_chain_name: from_chain}
    deps = {to_chain_name: to_deps, from_chain_name: from_deps}
    return updates, deps
Example #10
0
 def test_profile_to_chain_name(self):
     self.assertEqual(profile_to_chain_name("inbound", "prof1"),
                      "felix-p-prof1-i")
     self.assertEqual(profile_to_chain_name("outbound", "prof1"),
                      "felix-p-prof1-o")
Example #11
0
 def test_profile_to_chain_name(self):
     self.assertEqual(profile_to_chain_name("inbound", "prof1"),
                      "felix-p-prof1-i")
     self.assertEqual(profile_to_chain_name("outbound", "prof1"),
                      "felix-p-prof1-o")
Example #12
0
def _get_endpoint_rules(suffix, iface, ip_version, local_ips, mac, profile_id):
    to_chain_name, from_chain_name = chain_names(suffix)

    to_chain = ["--flush %s" % to_chain_name]
    if ip_version == 6:
        #  In ipv6 only, there are 6 rules that need to be created first.
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 130
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 131
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 132
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp router-advertisement
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-solicitation
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-advertisement
        #
        #  These rules are ICMP types 130, 131, 132, 134, 135 and 136, and can
        #  be created on the command line with something like :
        #     ip6tables -A plw -j RETURN --protocol ipv6-icmp --icmpv6-type 130
        for icmp_type in ["130", "131", "132", "134", "135", "136"]:
            to_chain.append("--append %s --jump RETURN "
                            "--protocol ipv6-icmp "
                            "--icmpv6-type %s" % (to_chain_name, icmp_type))
    to_chain.append("--append %s --match conntrack --ctstate INVALID "
                    "--jump DROP" % to_chain_name)
    to_chain.append("--append %s --match conntrack "
                    "--ctstate RELATED,ESTABLISHED --jump RETURN" %
                    to_chain_name)
    assert profile_id, "Profile ID should be set, not %s" % profile_id
    profile_in_chain = profile_to_chain_name("inbound", profile_id)
    to_chain.append("--append %s --goto %s" %
                    (to_chain_name, profile_in_chain))
    to_deps = set([profile_in_chain])

    # Now the chain that manages packets from the interface...
    from_chain = ["--flush %s" % from_chain_name]
    if ip_version == 6:
        # In ipv6 only, allows all ICMP traffic from this endpoint to anywhere.
        from_chain.append("--append %s --protocol ipv6-icmp" % from_chain_name)

    # Conntrack rules.
    from_chain.append("--append %s --match conntrack --ctstate INVALID "
                      "--jump DROP" % from_chain_name)
    from_chain.append("--append %s --match conntrack "
                      "--ctstate RELATED,ESTABLISHED --jump RETURN" %
                      from_chain_name)

    if ip_version == 4:
        from_chain.append("--append %s --protocol udp --sport 68 --dport 67 "
                          "--jump RETURN" % from_chain_name)
    else:
        assert ip_version == 6
        from_chain.append("--append %s --protocol udp --sport 546 --dport 547 "
                          "--jump RETURN" % from_chain_name)

    # Anti-spoofing rules.  Only allow traffic from known (IP, MAC) pairs to
    # get to the profile chain, drop other traffic.
    profile_out_chain = profile_to_chain_name("outbound", profile_id)
    from_deps = set([profile_out_chain])
    for ip in local_ips:
        if "/" in ip:
            cidr = ip
        else:
            cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip
        # Note use of --goto rather than --jump; this means that when the
        # profile chain returns, it will return the chain that called us, not
        # this chain.
        from_chain.append(
            "--append %s --src %s --match mac --mac-source %s "
            "--goto %s" %
            (from_chain_name, cidr, mac.upper(), profile_out_chain))
    from_chain.append(
        commented_drop_fragment(from_chain_name, "Anti-spoof DROP:"))

    updates = {to_chain_name: to_chain, from_chain_name: from_chain}
    deps = {to_chain_name: to_deps, from_chain_name: from_deps}
    return updates, deps
Example #13
0
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac,
                        profile_id):
    to_chain_name, from_chain_name = chain_names(suffix)

    to_chain = ["--flush %s" % to_chain_name]
    if ip_version == 6:
        #  In ipv6 only, there are 6 rules that need to be created first.
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 130
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 131
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 132
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp router-advertisement
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-solicitation
        #  RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-advertisement
        #
        #  These rules are ICMP types 130, 131, 132, 134, 135 and 136, and can
        #  be created on the command line with something like :
        #     ip6tables -A plw -j RETURN --protocol ipv6-icmp --icmpv6-type 130
        for icmp_type in ["130", "131", "132", "134", "135", "136"]:
            to_chain.append("--append %s --jump RETURN "
                            "--protocol ipv6-icmp "
                            "--icmpv6-type %s" % (to_chain_name, icmp_type))
    to_chain.append("--append %s --match conntrack --ctstate INVALID "
                    "--jump DROP" % to_chain_name)
    to_chain.append("--append %s --match conntrack "
                    "--ctstate RELATED,ESTABLISHED --jump RETURN" %
                    to_chain_name)
    assert profile_id, "Profile ID should be set, not %s" % profile_id
    profile_in_chain = profile_to_chain_name("inbound", profile_id)
    to_chain.append("--append %s --goto %s" %
                    (to_chain_name, profile_in_chain))
    # This drop rule is not hittable, but it gives us a place to stash the
    # comment with our ID.
    to_chain.append(commented_drop_fragment(to_chain_name,
                                            "Endpoint %s:" % endpoint_id))
    to_deps = set([profile_in_chain])

    # Now the chain that manages packets from the interface...
    from_chain = ["--flush %s" % from_chain_name]
    if ip_version == 6:
        # In ipv6 only, allows all ICMP traffic from this endpoint to anywhere.
        from_chain.append("--append %s --protocol ipv6-icmp --jump RETURN" %
                          from_chain_name)

    # Conntrack rules.
    from_chain.append("--append %s --match conntrack --ctstate INVALID "
                      "--jump DROP" % from_chain_name)
    from_chain.append("--append %s --match conntrack "
                      "--ctstate RELATED,ESTABLISHED --jump RETURN" %
                      from_chain_name)

    if ip_version == 4:
        from_chain.append("--append %s --protocol udp --sport 68 --dport 67 "
                          "--jump RETURN" % from_chain_name)
    else:
        assert ip_version == 6
        from_chain.append("--append %s --protocol udp --sport 546 --dport 547 "
                          "--jump RETURN" % from_chain_name)

    # Anti-spoofing rules.  Only allow traffic from known (IP, MAC) pairs to
    # get to the profile chain, drop other traffic.
    profile_out_chain = profile_to_chain_name("outbound", profile_id)
    from_deps = set([profile_out_chain])
    for ip in local_ips:
        if "/" in ip:
            cidr = ip
        else:
            cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip
        # Note use of --goto rather than --jump; this means that when the
        # profile chain returns, it will return the chain that called us, not
        # this chain.
        from_chain.append("--append %s --src %s --match mac --mac-source %s "
                          "--goto %s" % (from_chain_name, cidr,
                                         mac.upper(), profile_out_chain))
    drop_frag = commented_drop_fragment(from_chain_name,
                                        "Anti-spoof DROP (endpoint %s):" %
                                        endpoint_id)
    from_chain.append(drop_frag)

    updates = {to_chain_name: to_chain, from_chain_name: from_chain}
    deps = {to_chain_name: to_deps, from_chain_name: from_deps}
    return updates, deps