Exemple #1
0
def read_routes6():
    """Return a list of IPv6 routes than can be used by Scapy."""

    # Call netstat to retrieve IPv6 routes
    fd_netstat = os.popen("netstat -rn -f inet6")

    # List interfaces IPv6 addresses
    lifaddr = in6_getifaddr()
    if not lifaddr:
        return []

    # Routes header information
    got_header = False
    mtu_present = False
    prio_present = False

    # Parse the routes
    routes = []
    for line in fd_netstat.readlines():

        # Parse the routes header and try to identify extra columns
        if not got_header:
            if "Destination" == line[:11]:
                got_header = True
                mtu_present = "Mtu" in line
                prio_present = "Prio" in line
            continue

        # Parse a route entry according to the operating system
        splitted_line = line.split()
        if OPENBSD or NETBSD:
            index = 5 + mtu_present + prio_present
            if len(splitted_line) < index:
                warning("Not enough columns in route entry !")
                continue
            destination, next_hop, flags = splitted_line[:3]
            dev = splitted_line[index]
        else:
            # FREEBSD or DARWIN
            if len(splitted_line) < 4:
                warning("Not enough columns in route entry !")
                continue
            destination, next_hop, flags, dev = splitted_line[:4]

        # XXX: TODO: add metrics for unix.py (use -e option on netstat)
        metric = 1

        # Check flags
        if not "U" in flags:  # usable route
            continue
        if "R" in flags:  # Host or net unreachable
            continue
        if "m" in flags:  # multicast address
            # Note: multicast routing is handled in Route6.route()
            continue

        # Replace link with the default route in next_hop
        if "link" in next_hop:
            next_hop = "::"

        # Default prefix length
        destination_plen = 128

        # Extract network interface from the zone id
        if '%' in destination:
            destination, dev = destination.split('%')
            if '/' in dev:
                # Example: fe80::%lo0/64 ; dev = "lo0/64"
                dev, destination_plen = dev.split('/')
        if '%' in next_hop:
            next_hop, dev = next_hop.split('%')

        # Ensure that the next hop is a valid IPv6 address
        if not in6_isvalid(next_hop):
            # Note: the 'Gateway' column might contain a MAC address
            next_hop = "::"

        # Modify parsed routing entries
        # Note: these rules are OS specific and may evolve over time
        if destination == "default":
            destination, destination_plen = "::", 0
        elif '/' in destination:
            # Example: fe80::/10
            destination, destination_plen = destination.split('/')
        if '/' in dev:
            # Example: ff02::%lo0/32 ; dev = "lo0/32"
            dev, destination_plen = dev.split('/')

        # Check route entries parameters consistency
        if not in6_isvalid(destination):
            warning("Invalid destination IPv6 address in route entry !")
            continue
        try:
            destination_plen = int(destination_plen)
        except:
            warning("Invalid IPv6 prefix length in route entry !")
            continue
        if in6_ismlladdr(destination) or in6_ismnladdr(destination):
            # Note: multicast routing is handled in Route6.route()
            continue

        if LOOPBACK_NAME in dev:
            # Handle ::1 separately
            cset = ["::1"]
            next_hop = "::"
        else:
            # Get possible IPv6 source addresses
            devaddrs = (x for x in lifaddr if x[2] == dev)
            cset = construct_source_candidate_set(destination, destination_plen, devaddrs)

        if len(cset):
            routes.append((destination, destination_plen, next_hop, dev, cset, metric))

    fd_netstat.close()
    return routes
Exemple #2
0
def read_routes6():
    """Return a list of IPv6 routes than can be used by Scapy."""

    # Call netstat to retrieve IPv6 routes
    fd_netstat = os.popen("netstat -rn -f inet6")

    # List interfaces IPv6 addresses
    lifaddr = in6_getifaddr()
    if not lifaddr:
        return []

    # Routes header information
    got_header = False
    mtu_present = False
    prio_present = False

    # Parse the routes
    routes = []
    for line in fd_netstat.readlines():

        # Parse the routes header and try to identify extra columns
        if not got_header:
            if "Destination" == line[:11]:
                got_header = True
                mtu_present = "Mtu" in line
                prio_present = "Prio" in line
            continue

        # Parse a route entry according to the operating system
        splitted_line = line.split()
        if OPENBSD or NETBSD:
            index = 5 + mtu_present + prio_present
            if len(splitted_line) < index:
                warning("Not enough columns in route entry !")
                continue
            destination, next_hop, flags = splitted_line[:3]
            dev = splitted_line[index]
        else:
            # FREEBSD or DARWIN
            if len(splitted_line) < 4:
                warning("Not enough columns in route entry !")
                continue
            destination, next_hop, flags, dev = splitted_line[:4]

        # XXX: TODO: add metrics for unix.py (use -e option on netstat)
        metric = 1

        # Check flags
        if "U" not in flags:  # usable route
            continue
        if "R" in flags:  # Host or net unreachable
            continue
        if "m" in flags:  # multicast address
            # Note: multicast routing is handled in Route6.route()
            continue

        # Replace link with the default route in next_hop
        if "link" in next_hop:
            next_hop = "::"

        # Default prefix length
        destination_plen = 128

        # Extract network interface from the zone id
        if '%' in destination:
            destination, dev = destination.split('%')
            if '/' in dev:
                # Example: fe80::%lo0/64 ; dev = "lo0/64"
                dev, destination_plen = dev.split('/')
        if '%' in next_hop:
            next_hop, dev = next_hop.split('%')

        # Ensure that the next hop is a valid IPv6 address
        if not in6_isvalid(next_hop):
            # Note: the 'Gateway' column might contain a MAC address
            next_hop = "::"

        # Modify parsed routing entries
        # Note: these rules are OS specific and may evolve over time
        if destination == "default":
            destination, destination_plen = "::", 0
        elif '/' in destination:
            # Example: fe80::/10
            destination, destination_plen = destination.split('/')
        if '/' in dev:
            # Example: ff02::%lo0/32 ; dev = "lo0/32"
            dev, destination_plen = dev.split('/')

        # Check route entries parameters consistency
        if not in6_isvalid(destination):
            warning("Invalid destination IPv6 address in route entry !")
            continue
        try:
            destination_plen = int(destination_plen)
        except Exception:
            warning("Invalid IPv6 prefix length in route entry !")
            continue
        if in6_ismlladdr(destination) or in6_ismnladdr(destination):
            # Note: multicast routing is handled in Route6.route()
            continue

        if LOOPBACK_NAME in dev:
            # Handle ::1 separately
            cset = ["::1"]
            next_hop = "::"
        else:
            # Get possible IPv6 source addresses
            devaddrs = (x for x in lifaddr if x[2] == dev)
            cset = construct_source_candidate_set(destination,
                                                  destination_plen,
                                                  devaddrs)  # noqa: E501

        if len(cset):
            routes.append((destination, destination_plen, next_hop, dev, cset,
                           metric))  # noqa: E501

    fd_netstat.close()
    return routes
Exemple #3
0
    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]
Exemple #4
0
    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]
Exemple #5
0
    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]