Esempio n. 1
0
 def test_interface_exists(self):
     with mock.patch("os.path.exists", autospec=True) as m_exists:
         m_exists.return_value = False
         self.assertFalse(devices.interface_exists("tap1234"))
         m_exists.return_value = True
         self.assertTrue(devices.interface_exists("tap1234"))
     self.assertEqual(m_exists.mock_calls,
                      [mock.call("/sys/class/net/tap1234"),
                       mock.call("/sys/class/net/tap1234")])
Esempio n. 2
0
 def test_interface_exists(self):
     with mock.patch("os.path.exists", autospec=True) as m_exists:
         m_exists.return_value = False
         self.assertFalse(devices.interface_exists("tap1234"))
         m_exists.return_value = True
         self.assertTrue(devices.interface_exists("tap1234"))
     self.assertEqual(m_exists.mock_calls,
                      [mock.call("/sys/class/net/tap1234"),
                       mock.call("/sys/class/net/tap1234")])
Esempio n. 3
0
    def test_interface_exists(self):
        tap = "tap" + str(uuid.uuid4())[:11]

        with mock.patch('os.path.exists', return_value=True):
            self.assertTrue(devices.interface_exists(tap))
            os.path.exists.assert_called_with("/sys/class/net/" + tap)

        with mock.patch('os.path.exists', return_value=False):
            self.assertFalse(devices.interface_exists(tap))
            os.path.exists.assert_called_with("/sys/class/net/" + tap)
Esempio n. 4
0
    def _configure_interface(self):
        """
        Applies sysctls and routes to the interface.
        """
        try:
            if self.ip_type == IPV4:
                devices.configure_interface_ipv4(self._iface_name)
                nets_key = "ipv4_nets"
            else:
                ipv6_gw = self.endpoint.get("ipv6_gateway", None)
                devices.configure_interface_ipv6(self._iface_name, ipv6_gw)
                nets_key = "ipv6_nets"

            ips = set()
            for ip in self.endpoint.get(nets_key, []):
                ips.add(futils.net_to_ip(ip))
            devices.set_routes(self.ip_type, ips, self._iface_name,
                               self.endpoint["mac"])

        except (IOError, FailedSystemCall, CalledProcessError):
            if not devices.interface_exists(self._iface_name):
                _log.info("Interface %s for %s does not exist yet",
                          self._iface_name, self.endpoint_id)
            elif not devices.interface_up(self._iface_name):
                _log.info("Interface %s for %s is not up yet",
                          self._iface_name, self.endpoint_id)
            else:
                # OK, that really should not happen.
                _log.exception("Failed to configure interface %s for %s",
                               self._iface_name, self.endpoint_id)
                raise
Esempio n. 5
0
    def _configure_interface(self):
        """
        Applies sysctls and routes to the interface.
        """
        try:
            if self.ip_type == IPV4:
                devices.configure_interface_ipv4(self._iface_name)
                nets_key = "ipv4_nets"
            else:
                ipv6_gw = self.endpoint.get("ipv6_gateway", None)
                devices.configure_interface_ipv6(self._iface_name, ipv6_gw)
                nets_key = "ipv6_nets"

            ips = set()
            for ip in self.endpoint.get(nets_key, []):
                ips.add(futils.net_to_ip(ip))
            devices.set_routes(self.ip_type, ips,
                               self._iface_name,
                               self.endpoint["mac"])

        except (IOError, FailedSystemCall, CalledProcessError):
            if not devices.interface_exists(self._iface_name):
                _log.info("Interface %s for %s does not exist yet",
                          self._iface_name, self.endpoint_id)
            elif not devices.interface_up(self._iface_name):
                _log.info("Interface %s for %s is not up yet",
                          self._iface_name, self.endpoint_id)
            else:
                # Interface flapped back up after we failed?
                _log.warning("Failed to configure interface %s for %s",
                             self._iface_name, self.endpoint_id)
Esempio n. 6
0
def _configure_ipip_device(config):
    """Creates and enables the IPIP tunnel device.
    :raises FailedSystemCall on failure.
    """
    if not devices.interface_exists(IP_IN_IP_DEV_NAME):
        # Make sure the IP-in-IP device exists; since we use the global
        # device, this command actually creates it as a side-effect of
        # initialising the kernel module rather than explicitly creating
        # it.
        _log.info("Tunnel device didn't exist; creating.")
        futils.check_call(["ip", "tunnel", "add", IP_IN_IP_DEV_NAME,
                           "mode", "ipip"])
    futils.check_call(["ip", "link", "set", IP_IN_IP_DEV_NAME, "mtu",
                       str(config.IP_IN_IP_MTU)])
    if not devices.interface_up(IP_IN_IP_DEV_NAME):
        _log.info("Tunnel device wasn't up; enabling.")
        futils.check_call(["ip", "link", "set", IP_IN_IP_DEV_NAME, "up"])
    # Allow an IP address to be added to the tunnel.  This is useful to
    # allow the host to have an IP on a private IPIP network so that it can
    # originate traffic and have it routed correctly.
    _log.info("Setting IPIP device IP to %s", config.IP_IN_IP_ADDR)
    tunnel_addrs = [config.IP_IN_IP_ADDR] if config.IP_IN_IP_ADDR else []
    devices.set_interface_ips(futils.IPV4, IP_IN_IP_DEV_NAME,
                              set(tunnel_addrs))
    _log.info("Configured IPIP device.")
Esempio n. 7
0
 def _deconfigure_interface(self):
     """
     Removes routes from the interface.
     """
     try:
         devices.set_routes(self.ip_type, set(), self._iface_name, None)
     except FailedSystemCall as e:
         if "Cannot find device" in e.stderr:
             # Deleted under our feet - so the rules are gone.
             _log.info("Interface %s for %s already deleted",
                       self._iface_name, self.combined_id)
         else:
             # Since there are lots of potential races here with trying to
             # check status and interfaces flapping, log and continue.
             _log.exception(
                 "Failed to remove routes from interface %s "
                 "for %s, but interface appears to still be "
                 "present. Assuming the interface flapped.",
                 self._iface_name, self.combined_id)
     except IOError:
         if not devices.interface_exists(self._iface_name):
             # Deleted under our feet - so the rules are gone.
             _log.info("Interface %s for %s already deleted",
                       self._iface_name, self.combined_id)
         else:
             # Since there are lots of potential races here with trying to
             # check status and interfaces flapping, log and continue.
             _log.exception(
                 "Failed to remove routes from interface %s "
                 "for %s, but interface appears to still be "
                 "present. Assuming the interface flapped.",
                 self._iface_name, self.combined_id)
     else:
         _log.info("Interface %s deconfigured", self._iface_name)
         super(WorkloadEndpoint, self)._deconfigure_interface()
Esempio n. 8
0
def _configure_ipip_device(config):
    """Creates and enables the IPIP tunnel device.
    :raises FailedSystemCall on failure.
    """
    if not devices.interface_exists(IP_IN_IP_DEV_NAME):
        # Make sure the IP-in-IP device exists; since we use the global
        # device, this command actually creates it as a side-effect of
        # initialising the kernel module rather than explicitly creating
        # it.
        _log.info("Tunnel device didn't exist; creating.")
        futils.check_call(
            ["ip", "tunnel", "add", IP_IN_IP_DEV_NAME, "mode", "ipip"])
    futils.check_call([
        "ip", "link", "set", IP_IN_IP_DEV_NAME, "mtu",
        str(config.IP_IN_IP_MTU)
    ])
    if not devices.interface_up(IP_IN_IP_DEV_NAME):
        _log.info("Tunnel device wasn't up; enabling.")
        futils.check_call(["ip", "link", "set", IP_IN_IP_DEV_NAME, "up"])
    # Allow an IP address to be added to the tunnel.  This is useful to
    # allow the host to have an IP on a private IPIP network so that it can
    # originate traffic and have it routed correctly.
    _log.info("Setting IPIP device IP to %s", config.IP_IN_IP_ADDR)
    tunnel_addrs = [config.IP_IN_IP_ADDR] if config.IP_IN_IP_ADDR else []
    devices.set_interface_ips(futils.IPV4, IP_IN_IP_DEV_NAME,
                              set(tunnel_addrs))
    _log.info("Configured IPIP device.")
Esempio n. 9
0
    def _configure_interface(self, mac_changed=True):
        """
        Applies sysctls and routes to the interface.

        :param: bool mac_changed: Has the MAC address changed since it was last
                     configured? If so, we reconfigure ARP for the interface in
                     IPv4 (ARP does not exist for IPv6, which uses neighbour
                     solicitation instead).
        """
        try:
            if self.ip_type == IPV4:
                devices.configure_interface_ipv4(self._iface_name)
                reset_arp = mac_changed
            else:
                ipv6_gw = self.endpoint.get("ipv6_gateway", None)
                devices.configure_interface_ipv6(self._iface_name, ipv6_gw)
                reset_arp = False

            ips = set()
            for ip in self.endpoint.get(self.nets_key, []):
                ips.add(futils.net_to_ip(ip))
            devices.set_routes(self.ip_type, ips, self._iface_name, self.endpoint["mac"], reset_arp=reset_arp)

        except (IOError, FailedSystemCall):
            if not devices.interface_exists(self._iface_name):
                _log.info("Interface %s for %s does not exist yet", self._iface_name, self.combined_id)
            elif not devices.interface_up(self._iface_name):
                _log.info("Interface %s for %s is not up yet", self._iface_name, self.combined_id)
            else:
                # Interface flapped back up after we failed?
                _log.warning("Failed to configure interface %s for %s", self._iface_name, self.combined_id)
Esempio n. 10
0
    def remove(self, iptables_state):
        """
        Delete a programmed endpoint. Remove the routes, then the rules.
        """
        if devices.interface_exists(self.interface):
            for type in (futils.IPV4, futils.IPV6):
                try:
                    ips = devices.list_interface_ips(type, self.interface)
                    for ip in ips:
                        devices.del_route(type, ip, self.interface)
                except futils.FailedSystemCall:
                    # There is a window where the interface gets deleted under
                    # our feet. If it has gone now, ignore the error, otherwise
                    # rethrow it.
                    if devices.interface_exists(self.interface):
                        raise
                    break

        frules.del_rules(iptables_state, self.suffix, futils.IPV4)
        frules.del_rules(iptables_state, self.suffix, futils.IPV6)
Esempio n. 11
0
    def test_interface_exists(self):
        tap = "tap" + str(uuid.uuid4())[:11]

        args = []
        retcode = 1
        stdout = ""
        stderr = "Device \"%s\" does not exist." % tap
        err = futils.FailedSystemCall("From test", args, retcode, stdout, stderr)

        with mock.patch('calico.felix.futils.check_call', side_effect=err):
            self.assertFalse(devices.interface_exists(tap))
            futils.check_call.assert_called_with(["ip", "link", "list", tap])

        with mock.patch('calico.felix.futils.check_call'):
            self.assertTrue(devices.interface_exists(tap))
            futils.check_call.assert_called_with(["ip", "link", "list", tap])

        stderr = "Another error."
        err = futils.FailedSystemCall("From test", args, retcode, stdout, stderr)
        with mock.patch('calico.felix.futils.check_call', side_effect=err):
            with self.assertRaises(futils.FailedSystemCall):
                devices.interface_exists(tap)
Esempio n. 12
0
 def _deconfigure_interface(self):
     """
     Removes routes from the interface.
     """
     try:
         devices.set_routes(self.ip_type, set(), self._iface_name, None)
     except (IOError, FailedSystemCall):
         if not devices.interface_exists(self._iface_name):
             # Deleted under our feet - so the rules are gone.
             _log.debug("Interface %s for %s deleted", self._iface_name, self.combined_id)
         else:
             # An error deleting the rules. Log and continue.
             _log.exception("Cannot delete rules for interface %s for %s", self._iface_name, self.combined_id)
Esempio n. 13
0
    def _configure_interface(self):
        """
        Applies sysctls and routes to the interface.
        """
        if not self._device_is_up:
            _log.debug("Device is known to be down, skipping attempt to "
                       "configure it.")
            return
        try:
            if self.ip_type == IPV4:
                devices.configure_interface_ipv4(self._iface_name)
                reset_arp = self._mac_changed
            else:
                ipv6_gw = self.endpoint.get("ipv6_gateway", None)
                devices.configure_interface_ipv6(self._iface_name, ipv6_gw)
                reset_arp = False

            ips = set()
            for ip in self.endpoint.get(self.nets_key, []):
                ips.add(futils.net_to_ip(ip))
            for nat_map in self.endpoint.get(nat_key(self.ip_type), []):
                ips.add(nat_map['ext_ip'])
            devices.set_routes(self.ip_type,
                               ips,
                               self._iface_name,
                               self.endpoint.get("mac"),
                               reset_arp=reset_arp)

        except (IOError, FailedSystemCall) as e:
            if not devices.interface_exists(self._iface_name):
                _log.info("Interface %s for %s does not exist yet",
                          self._iface_name, self.combined_id)
            elif not devices.interface_up(self._iface_name):
                _log.info("Interface %s for %s is not up yet",
                          self._iface_name, self.combined_id)
            else:
                # Either the interface flapped back up after the failure (in
                # which case we'll retry when the event reaches us) or there
                # was a genuine failure due to bad data or some other factor.
                #
                # Since the former is fairly common, we log at warning level
                # rather than error, which avoids false positives.
                _log.warning(
                    "Failed to configure interface %s for %s: %r.  "
                    "Either the interface is flapping or it is "
                    "misconfigured.", self._iface_name, self.combined_id, e)
        else:
            _log.info("Interface %s configured", self._iface_name)
            super(WorkloadEndpoint, self)._configure_interface()
Esempio n. 14
0
    def test_interface_exists(self):
        tap = "tap" + str(uuid.uuid4())[:11]

        args = []
        retcode = 1
        stdout = ""
        stderr = "Device \"%s\" does not exist." % tap
        err = futils.FailedSystemCall("From test", args, retcode, stdout,
                                      stderr)

        with mock.patch('calico.felix.futils.check_call', side_effect=err):
            self.assertFalse(devices.interface_exists(tap))
            futils.check_call.assert_called_with(["ip", "link", "list", tap])

        with mock.patch('calico.felix.futils.check_call'):
            self.assertTrue(devices.interface_exists(tap))
            futils.check_call.assert_called_with(["ip", "link", "list", tap])

        stderr = "Another error."
        err = futils.FailedSystemCall("From test", args, retcode, stdout,
                                      stderr)
        with mock.patch('calico.felix.futils.check_call', side_effect=err):
            with self.assertRaises(futils.FailedSystemCall):
                devices.interface_exists(tap)
Esempio n. 15
0
 def _deconfigure_interface(self):
     """
     Removes routes from the interface.
     """
     try:
         devices.set_routes(self.ip_type, set(), self._iface_name, None)
     except (IOError, FailedSystemCall):
         if not devices.interface_exists(self._iface_name):
             # Deleted under our feet - so the rules are gone.
             _log.debug("Interface %s for %s deleted", self._iface_name,
                        self.combined_id)
         else:
             # An error deleting the rules. Log and continue.
             _log.exception("Cannot delete rules for interface %s for %s",
                            self._iface_name, self.combined_id)
Esempio n. 16
0
    def _configure_interface(self):
        """
        Applies sysctls and routes to the interface.
        """
        if not self._device_is_up:
            _log.debug("Device is known to be down, skipping attempt to "
                       "configure it.")
            return
        try:
            if self.ip_type == IPV4:
                devices.configure_interface_ipv4(self._iface_name)
                reset_arp = self._mac_changed
            else:
                ipv6_gw = self.endpoint.get("ipv6_gateway", None)
                devices.configure_interface_ipv6(self._iface_name, ipv6_gw)
                reset_arp = False

            ips = set()
            for ip in self.endpoint.get(self.nets_key, []):
                ips.add(futils.net_to_ip(ip))
            for nat_map in self.endpoint.get(nat_key(self.ip_type), []):
                ips.add(nat_map['ext_ip'])
            devices.set_routes(self.ip_type, ips,
                               self._iface_name,
                               self.endpoint["mac"],
                               reset_arp=reset_arp)

        except (IOError, FailedSystemCall) as e:
            if not devices.interface_exists(self._iface_name):
                _log.info("Interface %s for %s does not exist yet",
                          self._iface_name, self.combined_id)
            elif not devices.interface_up(self._iface_name):
                _log.info("Interface %s for %s is not up yet",
                          self._iface_name, self.combined_id)
            else:
                # Either the interface flapped back up after the failure (in
                # which case we'll retry when the event reaches us) or there
                # was a genuine failure due to bad data or some other factor.
                #
                # Since the former is fairly common, we log at warning level
                # rather than error, which avoids false positives.
                _log.warning("Failed to configure interface %s for %s: %r.  "
                             "Either the interface is flapping or it is "
                             "misconfigured.", self._iface_name,
                             self.combined_id, e)
        else:
            _log.info("Interface %s configured", self._iface_name)
            self._device_in_sync = True
Esempio n. 17
0
def _configure_ipip_device(config):
    """Creates and enables the IPIP tunnel device.
    :raises FailedSystemCall on failure.
    """
    if not devices.interface_exists(IP_IN_IP_DEV_NAME):
        # Make sure the IP-in-IP device exists; since we use the global
        # device, this command actually creates it as a side-effect of
        # initialising the kernel module rather than explicitly creating
        # it.
        _log.info("Tunnel device didn't exist; creating.")
        futils.check_call(["ip", "tunnel", "add", IP_IN_IP_DEV_NAME,
                           "mode", "ipip"])
    futils.check_call(["ip", "link", "set", IP_IN_IP_DEV_NAME, "mtu",
                       str(config.IP_IN_IP_MTU)])
    if not devices.interface_up(IP_IN_IP_DEV_NAME):
        _log.info("Tunnel device wasn't up; enabling.")
        futils.check_call(["ip", "link", "set", IP_IN_IP_DEV_NAME, "up"])
    _log.info("Configured IPIP device.")
Esempio n. 18
0
    def _configure_interface(self):
        """
        Applies sysctls and routes to the interface.

        :param: bool mac_changed: Has the MAC address changed since it was last
                     configured? If so, we reconfigure ARP for the interface in
                     IPv4 (ARP does not exist for IPv6, which uses neighbour
                     solicitation instead).
        """
        try:
            if self.ip_type == IPV4:
                devices.configure_interface_ipv4(self._iface_name)
                reset_arp = self._mac_changed
            else:
                ipv6_gw = self.endpoint.get("ipv6_gateway", None)
                devices.configure_interface_ipv6(self._iface_name, ipv6_gw)
                reset_arp = False

            ips = set()
            for ip in self.endpoint.get(self.nets_key, []):
                ips.add(futils.net_to_ip(ip))
            devices.set_routes(self.ip_type,
                               ips,
                               self._iface_name,
                               self.endpoint["mac"],
                               reset_arp=reset_arp)

        except (IOError, FailedSystemCall):
            if not devices.interface_exists(self._iface_name):
                _log.info("Interface %s for %s does not exist yet",
                          self._iface_name, self.combined_id)
            elif not devices.interface_up(self._iface_name):
                _log.info("Interface %s for %s is not up yet",
                          self._iface_name, self.combined_id)
            else:
                # Interface flapped back up after we failed?
                _log.warning("Failed to configure interface %s for %s",
                             self._iface_name, self.combined_id)
        else:
            _log.info("Interface %s configured", self._iface_name)
            self._device_in_sync = True
            self._device_has_been_in_sync = True
Esempio n. 19
0
    def _apply_endpoint_update(self):
        pending_endpoint = self._pending_endpoint
        if pending_endpoint == self.endpoint:
            _log.debug("Endpoint hasn't changed, nothing to do")
            return

        # Calculate the set of IPs that we had before this update.  Needed on
        # the update and delete code paths below.
        if self.endpoint:
            old_ips = set(
                futils.net_to_ip(n)
                for n in self.endpoint.get(self.nets_key, []))
            old_nat_mappings = self.endpoint.get(self.nat_key, [])
        else:
            old_ips = set()
            old_nat_mappings = []
        all_old_ips = old_ips | set([n["ext_ip"] for n in old_nat_mappings])

        if pending_endpoint:
            # Update/create.
            if pending_endpoint.get('mac') != self._mac:
                # Either we have not seen this MAC before, or it has changed.
                _log.debug("Endpoint MAC changed to %s",
                           pending_endpoint.get("mac"))
                self._mac = pending_endpoint.get('mac')
                self._mac_changed = True
                # MAC change requires refresh of iptables rules and ARP table.
                self._iptables_in_sync = False
                self._device_in_sync = False

            new_iface_name = pending_endpoint["name"]
            # Interface renames are handled in the EndpointManager by
            # simulating a delete then an add.  We shouldn't see one here.
            assert (self.endpoint is None
                    or self._iface_name == new_iface_name), (
                        "Unexpected change of interface name.")
            if self.endpoint is None:
                # This is the first time we have seen the endpoint, so extract
                # the interface name and endpoint ID.
                self._iface_name = new_iface_name
                self._suffix = interface_to_chain_suffix(
                    self.config, self._iface_name)
                _log.debug("Learned interface name/suffix: %s/%s",
                           self._iface_name, self._suffix)
                # First time through, need to program everything.
                self._iptables_in_sync = False
                self._device_in_sync = False
                if self._device_is_up is None:
                    _log.debug("Learned interface name, checking if device "
                               "is up.")
                    self._device_is_up = (
                        devices.interface_exists(self._iface_name)
                        and devices.interface_up(self._iface_name))

            # Check if the profile ID or IP addresses have changed, requiring
            # a refresh of the dataplane.
            profile_ids = set(pending_endpoint.get("profile_ids", []))
            if profile_ids != self._explicit_profile_ids:
                # Profile ID update requires iptables update but not device
                # update.
                _log.debug(
                    "Profile IDs changed from %s to %s, need to update "
                    "iptables", self._rules_ref_helper.required_refs,
                    profile_ids)
                self._explicit_profile_ids = profile_ids
                self._iptables_in_sync = False
                self._profile_ids_dirty = True

            # Check for changes to values that require a device update.
            if self.endpoint:
                if self.endpoint.get("state") != pending_endpoint.get("state"):
                    _log.debug("Desired interface state updated.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                new_ips = set(
                    futils.net_to_ip(n)
                    for n in pending_endpoint.get(self.nets_key, []))
                if old_ips != new_ips:
                    # IP addresses have changed, need to update the routing
                    # table.
                    _log.debug("IP addresses changed, need to update routing")
                    self._device_in_sync = False
                new_nat_mappings = pending_endpoint.get(self.nat_key, [])
                if old_nat_mappings != new_nat_mappings:
                    _log.debug("NAT mappings have changed, refreshing.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                all_new_ips = new_ips | set(
                    [n["ext_ip"] for n in new_nat_mappings])
                if all_old_ips != all_new_ips:
                    # Ensure we clean up any conntrack entries for IPs that
                    # have been removed.
                    _log.debug("Set of all IPs changed from %s to %s",
                               all_old_ips, all_new_ips)
                    self._removed_ips |= all_old_ips
                    self._removed_ips -= all_new_ips
        else:
            # Delete of the endpoint.  Need to resync everything.
            self._profile_ids_dirty = True
            self._iptables_in_sync = False
            self._device_in_sync = False
            self._removed_ips |= all_old_ips

        self.endpoint = pending_endpoint
        self._endpoint_update_pending = False
        self._pending_endpoint = None
Esempio n. 20
0
    def _apply_endpoint_update(self):
        pending_endpoint = self._pending_endpoint
        if pending_endpoint == self.endpoint:
            _log.debug("Endpoint hasn't changed, nothing to do")
            return

        if pending_endpoint:
            # Update/create.
            if pending_endpoint['mac'] != self._mac:
                # Either we have not seen this MAC before, or it has changed.
                _log.debug("Endpoint MAC changed to %s",
                           pending_endpoint["mac"])
                self._mac = pending_endpoint['mac']
                self._mac_changed = True
                # MAC change requires refresh of iptables rules and ARP table.
                self._iptables_in_sync = False
                self._device_in_sync = False

            if self.endpoint is None:
                # This is the first time we have seen the endpoint, so extract
                # the interface name and endpoint ID.
                self._iface_name = pending_endpoint["name"]
                self._suffix = interface_to_suffix(self.config,
                                                   self._iface_name)
                _log.debug("Learned interface name/suffix: %s/%s",
                           self._iface_name, self._suffix)
                # First time through, need to program everything.
                self._iptables_in_sync = False
                self._device_in_sync = False
                if self._device_is_up is None:
                    _log.debug("Learned interface name, checking if device "
                               "is up.")
                    self._device_is_up = (
                        devices.interface_exists(self._iface_name)
                        and devices.interface_up(self._iface_name))

            # Check if the profile ID or IP addresses have changed, requiring
            # a refresh of the dataplane.
            profile_ids = set(pending_endpoint.get("profile_ids", []))
            if profile_ids != self.rules_ref_helper.required_refs:
                # Profile ID update required iptables update but not device
                # update.
                _log.debug("Profile IDs changed, need to update iptables")
                self._iptables_in_sync = False

            # Check for changes to values that require a device update.
            if self.endpoint:
                if self.endpoint.get("state") != pending_endpoint.get("state"):
                    _log.debug("Desired interface state updated.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                if (self.endpoint[self.nets_key] !=
                        pending_endpoint[self.nets_key]):
                    # IP addresses have changed, need to update the routing
                    # table.
                    _log.debug("IP addresses changed, need to update routing")
                    self._device_in_sync = False
                for key in "ipv4_nat", "ipv6_nat":
                    if (self.endpoint.get(key, None) != pending_endpoint.get(
                            key, None)):
                        _log.debug("NAT mappings have changed, refreshing.")
                        self._device_in_sync = False
                        self._iptables_in_sync = False
        else:
            # Delete of the endpoint.  Need to resync everything.
            profile_ids = set()
            self._iptables_in_sync = False
            self._device_in_sync = False

        # Note: we don't actually need to wait for the activation to finish
        # due to the dependency management in the iptables layer.
        self.rules_ref_helper.replace_all(profile_ids)

        self.endpoint = pending_endpoint
        self._endpoint_update_pending = False
        self._pending_endpoint = None
Esempio n. 21
0
    def program_endpoint(self, iptables_state):
        """
        Given an endpoint, make the programmed state match the desired state,
        setting up rules and creating chains and ipsets, but not putting
        content into the ipsets (leaving that for frules.update_acls).

        Note that if acl_data is none, we have not received any ACLs, and so
        we just leave the ACLs in place until we do. If there are none because
        this is a new endpoint, then we leave the endpoint with all routing
        disabled until we know better.

        The logic here is that we should create the routes and basic rules, but
        not the ACLs - leaving the ACLs as they were or with no access
        permitted if none. That is because we have the information for the
        former (routes and IP addresses for the endpoint) but not the latter
        (ACLs). However this split only makes sense at the point where the ACLs
        must have a default rule of "deny", so when issue39 is fully resolved
        this method should only be called when the ACLs are available too.

        Returns True if the endpoint needs to be retried (because the tap
        interface does not exist yet).
        """

        # Declare some utility functions
        def add_routes(routes, type):
            for route in routes:
                log.info("Add route to %s address %s for interface %s", type,
                         route, self.interface)
                devices.add_route(type, route, self.interface, self.mac)

        def remove_routes(routes, type):
            for route in routes:
                log.info(
                    "Remove extra %s route to address %s for interface %s",
                    type, route, self.interface)
                devices.del_route(type, route, self.interface)

        if not devices.interface_exists(self.interface):
            if self.state == Endpoint.STATE_ENABLED:
                log.error("Unable to configure non-existent interface %s",
                          self.interface)
                return True
            else:
                # No interface, but disabled. This is not an error, and there
                # is nothing to do.
                log.debug("Interface missing when disabling endpoint %s",
                          self.uuid)
                return False

        # If the interface is down, we can't configure it.
        if not devices.interface_up(self.interface):
            log.error("Unable to configure interface %s: interface is down.",
                      self.interface)
            return True

        # Configure the interface.
        if self.state == Endpoint.STATE_ENABLED:
            devices.configure_interface(self.interface)

            # Build up list of addresses that should be present
            ipv4_intended = set([
                addr.ip.encode('ascii') for addr in self.addresses
                if addr.type is futils.IPV4
            ])
            ipv6_intended = set([
                addr.ip.encode('ascii') for addr in self.addresses
                if addr.type is futils.IPV6
            ])
        else:
            # Disabled endpoint; we should remove all the routes.
            ipv4_intended = set()
            ipv6_intended = set()

        ipv4_existing = devices.list_interface_ips(futils.IPV4, self.interface)
        ipv6_existing = devices.list_interface_ips(futils.IPV6, self.interface)

        # Determine the addresses that won't be changed.
        unchanged = ((ipv4_intended & ipv4_existing) |
                     (ipv6_intended & ipv6_existing))

        log.debug("Already got routes for %s for interface %s", unchanged,
                  self.interface)

        #*********************************************************************#
        #* Add and remove routes. Add any route we need but don't have, and  *#
        #* remove any route we have but don't need. These operations are     *#
        #* fast because they operate on sets.                                *#
        #*********************************************************************#
        add_routes(ipv4_intended - ipv4_existing, futils.IPV4)
        add_routes(ipv6_intended - ipv6_existing, futils.IPV6)
        remove_routes(ipv4_existing - ipv4_intended, futils.IPV4)
        remove_routes(ipv6_existing - ipv6_intended, futils.IPV6)

        #*********************************************************************#
        #* Set up the rules for this endpoint, not including ACLs. Note that *#
        #* if the endpoint is disabled, then it has no permitted addresses,  *#
        #* so it cannot send any data.                                       *#
        #*********************************************************************#
        frules.set_ep_specific_rules(iptables_state, self.suffix,
                                     self.interface, futils.IPV4,
                                     ipv4_intended, self.mac)
        frules.set_ep_specific_rules(iptables_state, self.suffix,
                                     self.interface, futils.IPV6,
                                     ipv6_intended, self.mac)

        #*********************************************************************#
        #* If we have just disabled / enabled an endpoint, we may need to    *#
        #* enable / disable incoming traffic. update_acls makes this         *#
        #* decision.                                                         *#
        #*********************************************************************#
        self.update_acls()

        return False
Esempio n. 22
0
    def _apply_endpoint_update(self):
        pending_endpoint = self._pending_endpoint
        if pending_endpoint == self.endpoint:
            _log.debug("Endpoint hasn't changed, nothing to do")
            return

        # Calculate the set of IPs that we had before this update.  Needed on
        # the update and delete code paths below.
        if self.endpoint:
            old_ips = set(futils.net_to_ip(n) for n in
                          self.endpoint.get(self.nets_key, []))
            old_nat_mappings = self.endpoint.get(self.nat_key, [])
        else:
            old_ips = set()
            old_nat_mappings = []
        all_old_ips = old_ips | set([n["ext_ip"] for n in old_nat_mappings])

        if pending_endpoint:
            # Update/create.
            if pending_endpoint['mac'] != self._mac:
                # Either we have not seen this MAC before, or it has changed.
                _log.debug("Endpoint MAC changed to %s",
                           pending_endpoint["mac"])
                self._mac = pending_endpoint['mac']
                self._mac_changed = True
                # MAC change requires refresh of iptables rules and ARP table.
                self._iptables_in_sync = False
                self._device_in_sync = False

            if self.endpoint is None:
                # This is the first time we have seen the endpoint, so extract
                # the interface name and endpoint ID.
                self._iface_name = pending_endpoint["name"]
                self._suffix = interface_to_suffix(self.config,
                                                   self._iface_name)
                _log.debug("Learned interface name/suffix: %s/%s",
                           self._iface_name, self._suffix)
                # First time through, need to program everything.
                self._iptables_in_sync = False
                self._device_in_sync = False
                if self._device_is_up is None:
                    _log.debug("Learned interface name, checking if device "
                               "is up.")
                    self._device_is_up = (
                        devices.interface_exists(self._iface_name) and
                        devices.interface_up(self._iface_name)
                    )

            # Check if the profile ID or IP addresses have changed, requiring
            # a refresh of the dataplane.
            profile_ids = set(pending_endpoint.get("profile_ids", []))
            if profile_ids != self._explicit_profile_ids:
                # Profile ID update requires iptables update but not device
                # update.
                _log.debug("Profile IDs changed from %s to %s, need to update "
                           "iptables", self._rules_ref_helper.required_refs,
                           profile_ids)
                self._explicit_profile_ids = profile_ids
                self._iptables_in_sync = False
                self._profile_ids_dirty = True

            # Check for changes to values that require a device update.
            if self.endpoint:
                if self.endpoint.get("state") != pending_endpoint.get("state"):
                    _log.debug("Desired interface state updated.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                new_ips = set(futils.net_to_ip(n) for n in
                              pending_endpoint.get(self.nets_key, []))
                if old_ips != new_ips:
                    # IP addresses have changed, need to update the routing
                    # table.
                    _log.debug("IP addresses changed, need to update routing")
                    self._device_in_sync = False
                new_nat_mappings = pending_endpoint.get(self.nat_key, [])
                if old_nat_mappings != new_nat_mappings:
                    _log.debug("NAT mappings have changed, refreshing.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                all_new_ips = new_ips | set([n["ext_ip"] for n in
                                             new_nat_mappings])
                if all_old_ips != all_new_ips:
                    # Ensure we clean up any conntrack entries for IPs that
                    # have been removed.
                    _log.debug("Set of all IPs changed from %s to %s",
                               all_old_ips, all_new_ips)
                    self._removed_ips |= all_old_ips
                    self._removed_ips -= all_new_ips
        else:
            # Delete of the endpoint.  Need to resync everything.
            self._profile_ids_dirty = True
            self._iptables_in_sync = False
            self._device_in_sync = False
            self._removed_ips |= all_old_ips

        self.endpoint = pending_endpoint
        self._endpoint_update_pending = False
        self._pending_endpoint = None
Esempio n. 23
0
def install_global_rules(config, v4_filter_updater, v6_filter_updater,
                         v4_nat_updater):
    """
    Set up global iptables rules. These are rules that do not change with
    endpoint, and are expected never to change (such as the rules that send all
    traffic through the top level Felix chains).

    This method therefore :

    - ensures that all the required global tables are present;
    - applies any changes required.
    """

    # The interface matching string; for example, if interfaces start "tap"
    # then this string is "tap+".
    iface_match = config.IFACE_PREFIX + "+"

    # If enabled, create the IP-in-IP device
    if config.IP_IN_IP_ENABLED:
        _log.info("IP-in-IP enabled, ensuring device exists.")
        if not devices.interface_exists(IP_IN_IP_DEV_NAME):
            # Make sure the IP-in-IP device exists; since we use the global
            # device, this command actually creates it as a side-effect of
            # initialising the kernel module rather than explicitly creating
            # it.
            _log.info("Tunnel device didn't exist; creating.")
            futils.check_call(["ip", "tunnel", "add", IP_IN_IP_DEV_NAME,
                               "mode", "ipip"])
        if not devices.interface_up(IP_IN_IP_DEV_NAME):
            _log.info("Tunnel device wasn't up; enabling.")
            futils.check_call(["ip", "link", "set", IP_IN_IP_DEV_NAME, "up"])

    # The IPV4 nat table first. This must have a felix-PREROUTING chain.
    nat_pr = []
    if config.METADATA_IP is not None:
        # Need to expose the metadata server on a link-local.
        #  DNAT tcp -- any any anywhere 169.254.169.254
        #              tcp dpt:http to:127.0.0.1:9697
        nat_pr.append("--append " + CHAIN_PREROUTING + " "
                      "--protocol tcp "
                      "--dport 80 "
                      "--destination 169.254.169.254/32 "
                      "--jump DNAT --to-destination %s:%s" %
                      (config.METADATA_IP, config.METADATA_PORT))
    v4_nat_updater.rewrite_chains({CHAIN_PREROUTING: nat_pr}, {}, async=False)
    v4_nat_updater.ensure_rule_inserted(
        "PREROUTING --jump %s" % CHAIN_PREROUTING, async=False)

    # Now the filter table. This needs to have calico-filter-FORWARD and
    # calico-filter-INPUT chains, which we must create before adding any
    # rules that send to them.
    for iptables_updater, hosts_set in [(v4_filter_updater, HOSTS_IPSET_V4),
                                        # FIXME support IP-in-IP for IPv6.
                                        (v6_filter_updater, None)]:
        if hosts_set and config.IP_IN_IP_ENABLED:
            hosts_set_name = hosts_set.set_name
            hosts_set.ensure_exists()
        else:
            hosts_set_name = None
        if iptables_updater is v4_filter_updater:
            input_chain, input_deps = _build_input_chain(
                iface_match=iface_match,
                metadata_addr=config.METADATA_IP,
                metadata_port=config.METADATA_PORT,
                dhcp_src_port=68,
                dhcp_dst_port=67,
                ipv6=False,
                default_action=config.DEFAULT_INPUT_CHAIN_ACTION,
                hosts_set_name=hosts_set_name,
            )
        else:
            input_chain, input_deps = _build_input_chain(
                iface_match=iface_match,
                metadata_addr=None,
                metadata_port=None,
                dhcp_src_port=546,
                dhcp_dst_port=547,
                ipv6=True,
                default_action=config.DEFAULT_INPUT_CHAIN_ACTION,
                hosts_set_name=hosts_set_name,
            )
        forward_chain, forward_deps = _build_forward_chain(iface_match)

        iptables_updater.rewrite_chains(
            {
                CHAIN_FORWARD: forward_chain,
                CHAIN_INPUT: input_chain
            },
            {
                CHAIN_FORWARD: forward_deps,
                CHAIN_INPUT: input_deps,
            },
            async=False)

        iptables_updater.ensure_rule_inserted(
            "INPUT --jump %s" % CHAIN_INPUT,
            async=False)
        iptables_updater.ensure_rule_inserted(
            "FORWARD --jump %s" % CHAIN_FORWARD,
            async=False)
Esempio n. 24
0
    def _apply_endpoint_update(self):
        pending_endpoint = self._pending_endpoint
        if pending_endpoint == self.endpoint:
            _log.debug("Endpoint hasn't changed, nothing to do")
            return

        if pending_endpoint:
            # Update/create.
            if pending_endpoint["mac"] != self._mac:
                # Either we have not seen this MAC before, or it has changed.
                _log.debug("Endpoint MAC changed to %s", pending_endpoint["mac"])
                self._mac = pending_endpoint["mac"]
                self._mac_changed = True
                # MAC change requires refresh of iptables rules and ARP table.
                self._iptables_in_sync = False
                self._device_in_sync = False

            if self.endpoint is None:
                # This is the first time we have seen the endpoint, so extract
                # the interface name and endpoint ID.
                self._iface_name = pending_endpoint["name"]
                self._suffix = interface_to_suffix(self.config, self._iface_name)
                _log.debug("Learned interface name/suffix: %s/%s", self._iface_name, self._suffix)
                # First time through, need to program everything.
                self._iptables_in_sync = False
                self._device_in_sync = False
                if self._device_is_up is None:
                    _log.debug("Learned interface name, checking if device " "is up.")
                    self._device_is_up = devices.interface_exists(self._iface_name) and devices.interface_up(
                        self._iface_name
                    )

            # Check if the profile ID or IP addresses have changed, requiring
            # a refresh of the dataplane.
            profile_ids = set(pending_endpoint.get("profile_ids", []))
            if profile_ids != self.rules_ref_helper.required_refs:
                # Profile ID update required iptables update but not device
                # update.
                _log.debug("Profile IDs changed, need to update iptables")
                self._iptables_in_sync = False

            # Check for changes to values that require a device update.
            if self.endpoint:
                if self.endpoint.get("state") != pending_endpoint.get("state"):
                    _log.debug("Desired interface state updated.")
                    self._device_in_sync = False
                    self._iptables_in_sync = False
                if self.endpoint[self.nets_key] != pending_endpoint[self.nets_key]:
                    # IP addresses have changed, need to update the routing
                    # table.
                    _log.debug("IP addresses changed, need to update routing")
                    self._device_in_sync = False
        else:
            # Delete of the endpoint.  Need to resync everything.
            profile_ids = set()
            self._iptables_in_sync = False
            self._device_in_sync = False

        # Note: we don't actually need to wait for the activation to finish
        # due to the dependency management in the iptables layer.
        self.rules_ref_helper.replace_all(profile_ids)

        self.endpoint = pending_endpoint
        self._endpoint_update_pending = False
        self._pending_endpoint = None
Esempio n. 25
0
def install_global_rules(config, v4_filter_updater, v6_filter_updater,
                         v4_nat_updater):
    """
    Set up global iptables rules. These are rules that do not change with
    endpoint, and are expected never to change (such as the rules that send all
    traffic through the top level Felix chains).

    This method therefore :

    - ensures that all the required global tables are present;
    - applies any changes required.
    """

    # The interface matching string; for example, if interfaces start "tap"
    # then this string is "tap+".
    iface_match = config.IFACE_PREFIX + "+"

    # If enabled, create the IP-in-IP device
    if config.IP_IN_IP_ENABLED:
        _log.info("IP-in-IP enabled, ensuring device exists.")
        if not devices.interface_exists(IP_IN_IP_DEV_NAME):
            # Make sure the IP-in-IP device exists; since we use the global
            # device, this command actually creates it as a side-effect of
            # initialising the kernel module rather than explicitly creating
            # it.
            _log.info("Tunnel device didn't exist; creating.")
            futils.check_call(
                ["ip", "tunnel", "add", IP_IN_IP_DEV_NAME, "mode", "ipip"])
        if not devices.interface_up(IP_IN_IP_DEV_NAME):
            _log.info("Tunnel device wasn't up; enabling.")
            futils.check_call(["ip", "link", "set", IP_IN_IP_DEV_NAME, "up"])

    # The IPV4 nat table first. This must have a felix-PREROUTING chain.
    nat_pr = []
    if config.METADATA_IP is not None:
        # Need to expose the metadata server on a link-local.
        #  DNAT tcp -- any any anywhere 169.254.169.254
        #              tcp dpt:http to:127.0.0.1:9697
        nat_pr.append("--append " + CHAIN_PREROUTING + " "
                      "--protocol tcp "
                      "--dport 80 "
                      "--destination 169.254.169.254/32 "
                      "--jump DNAT --to-destination %s:%s" %
                      (config.METADATA_IP, config.METADATA_PORT))
    v4_nat_updater.rewrite_chains({CHAIN_PREROUTING: nat_pr}, {}, async=False)
    v4_nat_updater.ensure_rule_inserted("PREROUTING --jump %s" %
                                        CHAIN_PREROUTING,
                                        async=False)

    # Now the filter table. This needs to have calico-filter-FORWARD and
    # calico-filter-INPUT chains, which we must create before adding any
    # rules that send to them.
    for iptables_updater, hosts_set in [
        (v4_filter_updater, HOSTS_IPSET_V4),
            # FIXME support IP-in-IP for IPv6.
        (v6_filter_updater, None)
    ]:
        if hosts_set and config.IP_IN_IP_ENABLED:
            hosts_set_name = hosts_set.set_name
            hosts_set.ensure_exists()
        else:
            hosts_set_name = None
        if iptables_updater is v4_filter_updater:
            input_chain, input_deps = _build_input_chain(
                iface_match=iface_match,
                metadata_addr=config.METADATA_IP,
                metadata_port=config.METADATA_PORT,
                dhcp_src_port=68,
                dhcp_dst_port=67,
                ipv6=False,
                default_action=config.DEFAULT_INPUT_CHAIN_ACTION,
                hosts_set_name=hosts_set_name,
            )
        else:
            input_chain, input_deps = _build_input_chain(
                iface_match=iface_match,
                metadata_addr=None,
                metadata_port=None,
                dhcp_src_port=546,
                dhcp_dst_port=547,
                ipv6=True,
                default_action=config.DEFAULT_INPUT_CHAIN_ACTION,
                hosts_set_name=hosts_set_name,
            )
        forward_chain, forward_deps = _build_forward_chain(iface_match)

        iptables_updater.rewrite_chains(
            {
                CHAIN_FORWARD: forward_chain,
                CHAIN_INPUT: input_chain
            }, {
                CHAIN_FORWARD: forward_deps,
                CHAIN_INPUT: input_deps,
            },
            async=False)

        iptables_updater.ensure_rule_inserted("INPUT --jump %s" % CHAIN_INPUT,
                                              async=False)
        iptables_updater.ensure_rule_inserted("FORWARD --jump %s" %
                                              CHAIN_FORWARD,
                                              async=False)