def _is_interface_routable(self, ifname, routes): """ And interface is able to support routes if: - It exists. - It is not DOWN or ABSENT. - It is not IPv4/6 disabled (corresponding to the routes). """ ifstate = self.interfaces.get(ifname) if not ifstate: return False iface_up = ifstate.get(Interface.STATE) not in NON_UP_STATES if iface_up: ipv4_state = ifstate.get(Interface.IPV4, {}) ipv4_disabled = ipv4_state.get(InterfaceIPv4.ENABLED) is False if ipv4_disabled and any( not is_ipv6_address(r.destination) for r in routes ): return False ipv6_state = ifstate.get(Interface.IPV6, {}) ipv6_disabled = ipv6_state.get(InterfaceIPv6.ENABLED) is False if ipv6_disabled and any( is_ipv6_address(r.destination) for r in routes ): return False return True return False
def _validate(self): if (len(self._config_servers) > 2 and any(is_ipv6_address(n) for n in self._config_servers) and any(not is_ipv6_address(n) for n in self._config_servers)): raise NmstateNotImplementedError( "Three or more nameservers are only supported when using " "either IPv4 or IPv6 nameservers but not both.")
def validate_routes(desired_state, current_state): """ A route has several requirements it must comply with: - Next-hop interface must be provided - The next-hop interface must: - Exist and be up (no down/absent) - Have the relevant IPv4/6 stack enabled. """ for iface_name, routes in desired_state.config_iface_routes.items(): if not routes: continue desired_iface_state = desired_state.interfaces.get(iface_name) current_iface_state = current_state.interfaces.get(iface_name) if desired_iface_state or current_iface_state: _assert_iface_is_up(desired_iface_state, current_iface_state) if any(is_ipv6_address(route.destination) for route in routes): _assert_iface_ipv6_enabled( desired_iface_state, current_iface_state ) if any(not is_ipv6_address(route.destination) for route in routes): _assert_iface_ipv4_enabled( desired_iface_state, current_iface_state ) else: raise NmstateRouteWithNoInterfaceError(str(routes))
def is_ipv6(self): if self.ip_from: return is_ipv6_address(self.ip_from) elif self.ip_to: return is_ipv6_address(self.ip_to) else: logging.warning( f"Neither {RouteRule.IP_FROM} nor {RouteRule.IP_TO} " "is defined, treating it a IPv4 route rule") return False
def _index_route_rule_by_route_table(self): index_rules_v4 = defaultdict(list) index_rules_v6 = defaultdict(list) for rule in self._config_route_rules: entry = RouteRuleEntry(rule) if is_ipv6_address(entry.ip_from) or is_ipv6_address(entry.ip_to): index_rules_v6[entry.route_table].append(entry) else: index_rules_v4[entry.route_table].append(entry) for rules in index_rules_v4.values(): rules.sort() for rules in index_rules_v6.values(): rules.sort() return index_rules_v4, index_rules_v6
def _validate_routes( iface_route_sets, iface_enable_states, ipv4_enable_states, ipv6_enable_states, ): """ Check whether user desire routes next hop to: * down/absent interface * Non-exit interface * IPv4/IPv6 disabled """ for iface_name, route_set in iface_route_sets.items(): if not route_set: continue iface_enable_state = iface_enable_states.get(iface_name) if iface_enable_state is None: raise NmstateValueError("Cannot set route to non-exist interface") if iface_enable_state != InterfaceState.UP: raise NmstateValueError( "Cannot set route to {} interface".format(iface_enable_state) ) # Interface is already check, so the ip enable status should be defined ipv4_enabled = ipv4_enable_states[iface_name] ipv6_enabled = ipv6_enable_states[iface_name] for route_obj in route_set: if iplib.is_ipv6_address(route_obj.destination): if not ipv6_enabled: raise NmstateValueError( "Cannot set IPv6 route when IPv6 is disabled" ) elif not ipv4_enabled: raise NmstateValueError( "Cannot set IPv4 route when IPv4 is disabled" )
def _save_dns_metadata(desired_state, current_state, ipv4_iface, ipv6_iface, servers, searches): index = 0 searches_saved = False for server in servers: iface_name = None if iplib.is_ipv6_address(server): iface_name = ipv6_iface family = Interface.IPV6 else: iface_name = ipv4_iface family = Interface.IPV4 if not iface_name: raise NmstateValueError( "Failed to find suitable interface for saving DNS " "name servers: %s" % server) _include_name_only_iface_state(desired_state, current_state, [iface_name]) iface_state = desired_state.interfaces[iface_name] if family not in iface_state: iface_state[family] = {} if DNS_METADATA not in iface_state[family]: iface_state[family][DNS_METADATA] = { DNS.SERVER: [server], DNS.SEARCH: [] if searches_saved else searches, DNS_METADATA_PRIORITY: nm.dns.DNS_PRIORITY_STATIC_BASE + index, } else: iface_state[family][DNS_METADATA][DNS.SERVER].append(server) searches_saved = True index += 1
def _attach_route_metadata(iface_state, routes): _init_iface_route_metadata(iface_state, Interface.IPV4) _init_iface_route_metadata(iface_state, Interface.IPV6) for route in routes: if iplib.is_ipv6_address(route.destination): iface_state[Interface.IPV6][ROUTES].append(route.to_dict()) else: iface_state[Interface.IPV4][ROUTES].append(route.to_dict())
def ip_rule_exist_in_os(ip_from, ip_to, priority, table): expected_rule = locals() logging.debug("Checking ip rule for {}".format(expected_rule)) cmds = ["ip"] if (ip_from and iplib.is_ipv6_address(ip_from)) or ( ip_to and iplib.is_ipv6_address(ip_to)): cmds.append("-6") if ip_from and "/" not in ip_from: ip_from = iplib.to_ip_address_full(ip_from) if ip_to and "/" not in ip_to: ip_to = iplib.to_ip_address_full(ip_to) result = cmdlib.exec_cmd(cmds + ["--json", "rule"]) logging.debug(f"Current ip rules in OS: {result[1]}") assert result[0] == 0 current_rules = json.loads(result[1]) found = True for rule in current_rules: if rule.get("src") == "all" or rule.get("dst") == "all": continue if rule.get("table") == "main": rule["table"] = f"{iplib.KERNEL_MAIN_ROUTE_TABLE_ID}" logging.debug(f"Checking ip rule is OS: {rule}") found = True if ip_from and ip_from != iplib.to_ip_address_full( rule["src"], rule.get("srclen")): found = False continue if ip_to and ip_to != iplib.to_ip_address_full(rule["dst"], rule.get("dstlen")): found = False continue if priority is not None and rule["priority"] != priority: found = False continue if table is not None and rule["table"] != f"{table}": found = False continue if found: break if not found: logging.debug(f"Failed to find expected ip rule: {expected_rule}") assert found
def validate_dns(state): """ Only support at most 2 name servers now: https://nmstate.atlassian.net/browse/NMSTATE-220 """ dns_servers = ( state.get(DNS.KEY, {}).get(DNS.CONFIG, {}).get(DNS.SERVER, []) ) if len(dns_servers) > 3: logging.warning( "The libc resolver may not support more than 3 nameservers." ) if ( len(dns_servers) > 2 and any(is_ipv6_address(n) for n in dns_servers) and any(not is_ipv6_address(n) for n in dns_servers) ): raise NmstateNotImplementedError( "Three or more nameservers are only supported when using " "either IPv4 or IPv6 nameservers but not both." )
def _get_default_route_config(gateway, metric, default_table_id, iface_name): if iplib.is_ipv6_address(gateway): destination = IPV6_DEFAULT_GATEWAY_DESTINATION else: destination = IPV4_DEFAULT_GATEWAY_DESTINATION return { Route.TABLE_ID: default_table_id, Route.DESTINATION: destination, Route.NEXT_HOP_INTERFACE: iface_name, Route.NEXT_HOP_ADDRESS: gateway, Route.METRIC: metric, }
def _get_gateway(setting_ip): gateway = setting_ip.props.gateway if iplib.is_ipv6_address(gateway): destination = IPV6_DEFAULT_GATEWAY_DESTINATION else: destination = IPV4_DEFAULT_GATEWAY_DESTINATION return { Route.TABLE_ID: setting_ip.get_route_table(), Route.DESTINATION: destination, Route.NEXT_HOP_ADDRESS: gateway, Route.METRIC: setting_ip.get_route_metric(), }
def _route_is_next_hop_to_dynamic_ip_iface(self, route): ifname = route.next_hop_interface if not ifname: return False iface_state = self.interfaces.get(ifname) if not iface_state: return False if is_ipv6_address(route.destination): ip_state = iface_state.get(Interface.IPV6) return ip_state and (ip_state.get(InterfaceIPv6.AUTOCONF) or ip_state.get(InterfaceIPv6.DHCP)) else: ip_state = iface_state.get(Interface.IPV4, {}) return ip_state.get(InterfaceIPv4.DHCP, False)
def _add_specfic_route(setting_ip, route): destination, prefix_len = route[Route.DESTINATION].split("/") prefix_len = int(prefix_len) if iplib.is_ipv6_address(destination): family = socket.AF_INET6 else: family = socket.AF_INET metric = route.get(Route.METRIC, Route.USE_DEFAULT_METRIC) next_hop = route[Route.NEXT_HOP_ADDRESS] ip_route = NM.IPRoute.new(family, destination, prefix_len, next_hop, metric) table_id = route.get(Route.TABLE_ID, Route.USE_DEFAULT_ROUTE_TABLE) ip_route.set_attribute(NM_ROUTE_TABLE_ATTRIBUTE, GLib.Variant.new_uint32(table_id)) # Duplicate route entry will be ignored by libnm. setting_ip.add_route(ip_route)
def _route_is_valid(route_obj, iface_enable_states, ipv4_enable_states, ipv6_enable_states): """ Return False when route is next hop to any of these interfaces: * Interface not in InterfaceState.UP state. * Interface does not exists. * Interface has IPv4/IPv6 disabled. """ iface_name = route_obj.next_hop_interface iface_enable_state = iface_enable_states.get(iface_name) if iface_enable_state != InterfaceState.UP: return False if iplib.is_ipv6_address(route_obj.destination): if not ipv6_enable_states.get(iface_name): return False else: if not ipv4_enable_states.get(iface_name): return False return True
def _generate_route_rule_metadata(desired_state): routes_v4 = [] routes_v6 = [] for routes in desired_state.config_iface_routes.values(): for route in routes: if iplib.is_ipv6_address(route.destination): routes_v6.append(route) else: routes_v4.append(route) _generate_route_rule_per_stack_metadata( Interface.IPV4, desired_state.config_route_table_rules_v4, routes_v4, desired_state, ) _generate_route_rule_per_stack_metadata( Interface.IPV6, desired_state.config_route_table_rules_v6, routes_v6, desired_state, )
def is_ipv6(self): return is_ipv6_address(self.destination)
def gen_metadata(self, ifaces, route_state): """ Return DNS configure targeting to store as metadata of interface. Data structure returned is: { iface_name: { Interface.IPV4: { DNS.SERVER: dns_servers, DNS.SEARCH: dns_searches, }, Interface.IPV6: { DNS.SERVER: dns_servers, DNS.SEARCH: dns_searches, }, } } """ iface_metadata = {} if not self._config_servers and not self._config_searches: return iface_metadata ipv4_iface, ipv6_iface = self._find_ifaces_for_name_servers( ifaces, route_state ) if ipv4_iface == ipv6_iface: iface_metadata = { ipv4_iface: { Interface.IPV4: {DNS.SERVER: [], DNS.SEARCH: []}, Interface.IPV6: {DNS.SERVER: [], DNS.SEARCH: []}, }, } else: if ipv4_iface: iface_metadata[ipv4_iface] = { Interface.IPV4: {DNS.SERVER: [], DNS.SEARCH: []}, } if ipv6_iface: iface_metadata[ipv6_iface] = { Interface.IPV6: {DNS.SERVER: [], DNS.SEARCH: []}, } index = 0 searches_saved = False for server in self._config_servers: iface_name = None if is_ipv6_address(server): iface_name = ipv6_iface family = Interface.IPV6 else: iface_name = ipv4_iface family = Interface.IPV4 if not iface_name: raise NmstateValueError( "Failed to find suitable interface for saving DNS " "name servers: %s" % server ) iface_dns_metada = iface_metadata[iface_name][family] iface_dns_metada[DNS.SERVER].append(server) iface_dns_metada.setdefault(DnsState.PRIORITY_METADATA, index) if not searches_saved: iface_dns_metada[DNS.SEARCH] = self._config_searches searches_saved = True index += 1 return iface_metadata