def route(self, dst, dev=None): """ Provide best route to IPv6 destination address, based on Scapy6 internal routing table content. When a set of address is passed (e.g. 2001:db8:cafe:*::1-5) an address of the set is used. Be aware of that behavior when using wildcards in upper parts of addresses ! If 'dst' parameter is a FQDN, name resolution is performed and result is used. if optional 'dev' parameter is provided a specific interface, filtering is performed to limit search to route associated to that interface. """ # Transform "2001:db8:cafe:*::1-5:0/120" to one IPv6 address of the set dst = dst.split("/")[0] savedst = dst # In case following inet_pton() fails dst = dst.replace("*", "0") idx = dst.find("-") while idx >= 0: m = (dst[idx:] + ":").find(":") dst = dst[:idx] + dst[idx + m:] idx = dst.find("-") try: inet_pton(socket.AF_INET6, dst) except socket.error: dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0] # TODO : Check if name resolution went well # Use the default interface while dealing with link-local addresses if dev is None and (in6_islladdr(dst) or in6_ismlladdr(dst)): dev = conf.iface # Deal with dev-specific request for cache search k = dst if dev is not None: k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) # noqa: E501 if k in self.cache: return self.cache[k] paths = [] # TODO : review all kinds of addresses (scope and *cast) to see # if we are able to cope with everything possible. I'm convinced # it's not the case. # -- arnaud for p, plen, gw, iface, cset, me in self.routes: if dev is not None and iface != dev: continue if in6_isincluded(dst, p, plen): paths.append((plen, me, (iface, cset, gw))) elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])): # noqa: E501 paths.append((plen, me, (iface, cset, gw))) if not paths: warning( "No route found for IPv6 destination %s (no default route?)", dst) # noqa: E501 return (scapy.consts.LOOPBACK_INTERFACE, "::", "::") # Sort with longest prefix first paths.sort(reverse=True, key=lambda x: x[0]) best_plen = paths[0][0] paths = [x for x in paths if x[0] == best_plen] res = [] for p in paths: # Here we select best source address for every route tmp = p[2] srcaddr = get_source_addr_from_candidate_set(dst, tmp[1]) if srcaddr is not None: res.append((p[0], p[1], (tmp[0], srcaddr, tmp[2]))) if res == []: warning( "Found a route for IPv6 destination '%s', but no possible source address.", dst) # noqa: E501 return (scapy.consts.LOOPBACK_INTERFACE, "::", "::") # Tie-breaker: Metrics paths.sort(key=lambda x: x[1]) paths = [i for i in paths if i[1] == paths[0][1]] # Symptom : 2 routes with same weight (our weight is plen) # Solution : # - dst is unicast global. Check if it is 6to4 and we have a source # 6to4 address in those available # - dst is link local (unicast or multicast) and multiple output # interfaces are available. Take main one (conf.iface6) # - if none of the previous or ambiguity persists, be lazy and keep # first one if len(res) > 1: tmp = [] if in6_isgladdr(dst) and in6_isaddr6to4(dst): # TODO : see if taking the longest match between dst and # every source addresses would provide better results tmp = [x for x in res if in6_isaddr6to4(x[2][1])] elif in6_ismaddr(dst) or in6_islladdr(dst): # TODO : I'm sure we are not covering all addresses. Check that tmp = [x for x in res if x[2][0] == conf.iface6] if tmp: res = tmp # Fill the cache (including dev-specific request) k = dst if dev is not None: k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) # noqa: E501 self.cache[k] = res[0][2] return res[0][2]
def route(self, dst="", dev=None, verbose=conf.verb): # type: (str, Optional[Any], int) -> Tuple[str, str, str] """ Provide best route to IPv6 destination address, based on Scapy internal routing table content. When a set of address is passed (e.g. ``2001:db8:cafe:*::1-5``) an address of the set is used. Be aware of that behavior when using wildcards in upper parts of addresses ! If 'dst' parameter is a FQDN, name resolution is performed and result is used. if optional 'dev' parameter is provided a specific interface, filtering is performed to limit search to route associated to that interface. """ dst = dst or "::/0" # Enable route(None) to return default route # Transform "2001:db8:cafe:*::1-5:0/120" to one IPv6 address of the set dst = dst.split("/")[0] savedst = dst # In case following inet_pton() fails dst = dst.replace("*", "0") idx = dst.find("-") while idx >= 0: m = (dst[idx:] + ":").find(":") dst = dst[:idx] + dst[idx + m:] idx = dst.find("-") try: inet_pton(socket.AF_INET6, dst) except socket.error: dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0] # TODO : Check if name resolution went well # Choose a valid IPv6 interface while dealing with link-local addresses if dev is None and (in6_islladdr(dst) or in6_ismlladdr(dst)): dev = conf.iface # default interface # Check if the default interface supports IPv6! if dev not in self.ipv6_ifaces and self.ipv6_ifaces: tmp_routes = [route for route in self.routes if route[3] != conf.iface] default_routes = [route for route in tmp_routes if (route[0], route[1]) == ("::", 0)] ll_routes = [route for route in tmp_routes if (route[0], route[1]) == ("fe80::", 64)] if default_routes: # Fallback #1 - the first IPv6 default route dev = default_routes[0][3] elif ll_routes: # Fallback #2 - the first link-local prefix dev = ll_routes[0][3] else: # Fallback #3 - the loopback dev = conf.loopback_name warning("The conf.iface interface (%s) does not support IPv6! " "Using %s instead for routing!" % (conf.iface, dev)) # Deal with dev-specific request for cache search k = dst if dev is not None: k = dst + "%%" + dev if k in self.cache: return self.cache[k] paths = [] # type: List[Tuple[int, int, Tuple[str, List[str], str]]] # TODO : review all kinds of addresses (scope and *cast) to see # if we are able to cope with everything possible. I'm convinced # it's not the case. # -- arnaud for p, plen, gw, iface, cset, me in self.routes: if dev is not None and iface != dev: continue if in6_isincluded(dst, p, plen): paths.append((plen, me, (iface, cset, gw))) elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])): # noqa: E501 paths.append((plen, me, (iface, cset, gw))) if not paths: if dst == "::1": return (conf.loopback_name, "::1", "::") else: if verbose: warning("No route found for IPv6 destination %s " "(no default route?)", dst) return (conf.loopback_name, "::", "::") # Sort with longest prefix first then use metrics as a tie-breaker paths.sort(key=lambda x: (-x[0], x[1])) best_plen = (paths[0][0], paths[0][1]) paths = [x for x in paths if (x[0], x[1]) == best_plen] res = [] # type: List[Tuple[int, int, Tuple[str, str, str]]] for path in paths: # we select best source address for every route tmp_c = path[2] srcaddr = get_source_addr_from_candidate_set(dst, tmp_c[1]) if srcaddr is not None: res.append((path[0], path[1], (tmp_c[0], srcaddr, tmp_c[2]))) if res == []: warning("Found a route for IPv6 destination '%s', but no possible source address.", dst) # noqa: E501 return (conf.loopback_name, "::", "::") # Symptom : 2 routes with same weight (our weight is plen) # Solution : # - dst is unicast global. Check if it is 6to4 and we have a source # 6to4 address in those available # - dst is link local (unicast or multicast) and multiple output # interfaces are available. Take main one (conf.iface) # - if none of the previous or ambiguity persists, be lazy and keep # first one if len(res) > 1: tmp = [] # type: List[Tuple[int, int, Tuple[str, str, str]]] if in6_isgladdr(dst) and in6_isaddr6to4(dst): # TODO : see if taking the longest match between dst and # every source addresses would provide better results tmp = [x for x in res if in6_isaddr6to4(x[2][1])] elif in6_ismaddr(dst) or in6_islladdr(dst): # TODO : I'm sure we are not covering all addresses. Check that tmp = [x for x in res if x[2][0] == conf.iface] if tmp: res = tmp # Fill the cache (including dev-specific request) k = dst if dev is not None: k = dst + "%%" + dev self.cache[k] = res[0][2] return res[0][2]
def route(self, dst, dev=None): """ Provide best route to IPv6 destination address, based on Scapy6 internal routing table content. When a set of address is passed (e.g. 2001:db8:cafe:*::1-5) an address of the set is used. Be aware of that behavior when using wildcards in upper parts of addresses ! If 'dst' parameter is a FQDN, name resolution is performed and result is used. if optional 'dev' parameter is provided a specific interface, filtering is performed to limit search to route associated to that interface. """ # Transform "2001:db8:cafe:*::1-5:0/120" to one IPv6 address of the set dst = dst.split("/")[0] savedst = dst # In case following inet_pton() fails dst = dst.replace("*", "0") idx = dst.find("-") while idx >= 0: m = (dst[idx:] + ":").find(":") dst = dst[:idx] + dst[idx + m:] idx = dst.find("-") try: inet_pton(socket.AF_INET6, dst) except socket.error: dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0] # TODO : Check if name resolution went well # Use the default interface while dealing with link-local addresses if dev is None and (in6_islladdr(dst) or in6_ismlladdr(dst)): dev = conf.iface # Deal with dev-specific request for cache search k = dst if dev is not None: k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) # noqa: E501 if k in self.cache: return self.cache[k] paths = [] # TODO : review all kinds of addresses (scope and *cast) to see # if we are able to cope with everything possible. I'm convinced # it's not the case. # -- arnaud for p, plen, gw, iface, cset, me in self.routes: if dev is not None and iface != dev: continue if in6_isincluded(dst, p, plen): paths.append((plen, me, (iface, cset, gw))) elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])): # noqa: E501 paths.append((plen, me, (iface, cset, gw))) if not paths: warning("No route found for IPv6 destination %s (no default route?)", dst) # noqa: E501 return (scapy.consts.LOOPBACK_INTERFACE, "::", "::") # Sort with longest prefix first paths.sort(reverse=True, key=lambda x: x[0]) best_plen = paths[0][0] paths = [x for x in paths if x[0] == best_plen] res = [] for p in paths: # Here we select best source address for every route tmp = p[2] srcaddr = get_source_addr_from_candidate_set(dst, tmp[1]) if srcaddr is not None: res.append((p[0], p[1], (tmp[0], srcaddr, tmp[2]))) if res == []: warning("Found a route for IPv6 destination '%s', but no possible source address.", dst) # noqa: E501 return (scapy.consts.LOOPBACK_INTERFACE, "::", "::") # Tie-breaker: Metrics paths.sort(key=lambda x: x[1]) paths = [i for i in paths if i[1] == paths[0][1]] # Symptom : 2 routes with same weight (our weight is plen) # Solution : # - dst is unicast global. Check if it is 6to4 and we have a source # 6to4 address in those available # - dst is link local (unicast or multicast) and multiple output # interfaces are available. Take main one (conf.iface6) # - if none of the previous or ambiguity persists, be lazy and keep # first one if len(res) > 1: tmp = [] if in6_isgladdr(dst) and in6_isaddr6to4(dst): # TODO : see if taking the longest match between dst and # every source addresses would provide better results tmp = [x for x in res if in6_isaddr6to4(x[2][1])] elif in6_ismaddr(dst) or in6_islladdr(dst): # TODO : I'm sure we are not covering all addresses. Check that tmp = [x for x in res if x[2][0] == conf.iface6] if tmp: res = tmp # Fill the cache (including dev-specific request) k = dst if dev is not None: k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) # noqa: E501 self.cache[k] = res[0][2] return res[0][2]