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")])
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)
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
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)
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.")
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()
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.")
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)
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)
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)
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)
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()
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
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.")
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
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
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
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
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
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)
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
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)