From 8f9468ba694b07d74b261070fef0691e74ffc4f1 Mon Sep 17 00:00:00 2001
From: Phil <phil@secdev.org>
Date: Fri, 12 Sep 2008 17:57:20 +0200
Subject: [PATCH] Split IPv6 route management

---
 scapy/all.py          |   2 +
 scapy/arch/linux.py   |   2 +-
 scapy/config.py       |   1 +
 scapy/layers/inet6.py | 331 ------------------------------------------
 scapy/route6.py       | 271 ++++++++++++++++++++++++++++++++++
 scapy/utils6.py       |  57 ++++++++
 6 files changed, 332 insertions(+), 332 deletions(-)
 create mode 100644 scapy/route6.py

diff --git a/scapy/all.py b/scapy/all.py
index dd4f84b3..c76d08b5 100644
--- a/scapy/all.py
+++ b/scapy/all.py
@@ -20,6 +20,8 @@ from asn1packet import *
 
 from utils import *
 from route import *
+from utils6 import *
+from route6 import *
 from sendrecv import *
 from supersocket import *
 from volatile import *
diff --git a/scapy/arch/linux.py b/scapy/arch/linux.py
index fed3efc5..5f99507a 100644
--- a/scapy/arch/linux.py
+++ b/scapy/arch/linux.py
@@ -250,7 +250,7 @@ def read_routes6():
             cset = ['::1']
         else:
             devaddrs = filter(lambda x: x[2] == dev, lifaddr)
-            cset = construct_source_candidate_set(d, dp, devaddrs)
+            cset = scapy.utils6.construct_source_candidate_set(d, dp, devaddrs)
         
         if len(cset) != 0:
             routes.append((d, dp, nh, dev, cset))
diff --git a/scapy/config.py b/scapy/config.py
index bb8d1d01..a213cd2f 100644
--- a/scapy/config.py
+++ b/scapy/config.py
@@ -293,6 +293,7 @@ extensions_paths: path or list of paths where extensions are to be looked for
     debug_match = 0
     wepkey = ""
     route = None # Filed by route.py
+    route6 = None # Filed by route6.py
     auto_fragment = 1
     debug_dissector = 0
     color_theme = themes.DefaultTheme()
diff --git a/scapy/layers/inet6.py b/scapy/layers/inet6.py
index f23db6ee..115f3278 100644
--- a/scapy/layers/inet6.py
+++ b/scapy/layers/inet6.py
@@ -42,314 +42,6 @@ def get_cls(name, fallback_cls):
     return globals().get(name, fallback_cls)
 
 
-
-#############################################################################
-#############################################################################
-###                      Routing/Interfaces stuff                         ###
-#############################################################################
-#############################################################################
-
-def construct_source_candidate_set(addr, plen, laddr):
-    """
-    Given all addresses assigned to a specific interface ('laddr' parameter),
-    this function returns the "candidate set" associated with 'addr/plen'.
-    
-    Basically, the function filters all interface addresses to keep only those
-    that have the same scope as provided prefix.
-    
-    This is on this list of addresses that the source selection mechanism 
-    will then be performed to select the best source address associated
-    with some specific destination that uses this prefix.
-    """
-
-    cset = []
-    if in6_isgladdr(addr):
-        cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr)
-    elif in6_islladdr(addr):
-        cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr)
-    elif in6_issladdr(addr):
-        cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr)
-    elif in6_ismaddr(addr):
-        if in6_ismnladdr(addr):
-            cset = [('::1', 16, LOOPBACK_NAME)]
-        elif in6_ismgladdr(addr):
-            cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr)
-        elif in6_ismlladdr(addr):
-            cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr)
-        elif in6_ismsladdr(addr):
-            cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr)
-    elif addr == '::' and plen == 0:
-        cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr)
-    cset = map(lambda x: x[0], cset)
-    return cset            
-
-def get_source_addr_from_candidate_set(dst, candidate_set):
-    """
-    This function implement a limited version of source address selection
-    algorithm defined in section 5 of RFC 3484. The format is very different
-    from that described in the document because it operates on a set 
-    of candidate source address for some specific route.
-    
-    Rationale behind the implementation is to be able to make the right 
-    choice for a 6to4 destination when both a 6to4 address and a IPv6 native
-    address are available for that interface.
-    """
-    
-    if len(candidate_set) == 0:
-        # Should not happen
-        return None
-    
-    if in6_isaddr6to4(dst):
-        tmp = filter(lambda x: in6_isaddr6to4(x), candidate_set)
-        if len(tmp) != 0:
-            return tmp[0]
-
-    return candidate_set[0]
-
-class Route6:
-
-    def __init__(self):
-        self.invalidate_cache()
-        self.resync()
-
-    def invalidate_cache(self):
-        self.cache = {}
-
-    def flush(self):
-        self.invalidate_cache()
-        self.routes = []
-
-    def resync(self):
-        # TODO : At the moment, resync will drop existing Teredo routes
-        #        if any. Change that ...
-        self.invalidate_cache()
-        self.routes = read_routes6()
-        if self.routes == []:
-             log_loading.info("No IPv6 support in kernel")
-        
-    def __repr__(self):
-        rtlst = [('Destination', 'Next Hop', "iface", "src candidates")]
-
-        for net,msk,gw,iface,cset in self.routes:
-            rtlst.append(('%s/%i'% (net,msk), gw, iface, ", ".join(cset)))
-
-        colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst))
-        fmt = "  ".join(map(lambda x: "%%-%ds"%x, colwidth))
-        rt = "\n".join(map(lambda x: fmt % x, rtlst))
-
-        return rt
-
-
-    # Unlike Scapy's Route.make_route() function, we do not have 'host' and 'net'
-    # parameters. We only have a 'dst' parameter that accepts 'prefix' and 
-    # 'prefix/prefixlen' values.
-    # WARNING: Providing a specific device will at the moment not work correctly.
-    def make_route(self, dst, gw=None, dev=None):
-        """Internal function : create a route for 'dst' via 'gw'.
-        """
-        prefix, plen = (dst.split("/")+["128"])[:2]
-        plen = int(plen)
-
-        if gw is None:
-            gw = "::"
-        if dev is None:
-            dev, ifaddr, x = self.route(gw)
-        else:
-            # TODO: do better than that
-            # replace that unique address by the list of all addresses
-            lifaddr = in6_getifaddr()             
-            devaddrs = filter(lambda x: x[2] == dev, lifaddr)
-            ifaddr = construct_source_candidate_set(prefix, plen, devaddrs)
-
-        return (prefix, plen, gw, dev, ifaddr)
-
-    
-    def add(self, *args, **kargs):
-        """Ex:
-        add(dst="2001:db8:cafe:f000::/56")
-        add(dst="2001:db8:cafe:f000::/56", gw="2001:db8:cafe::1")
-        add(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1", dev="eth0")
-        """
-        self.invalidate_cache()
-        self.routes.append(self.make_route(*args, **kargs))
-
-
-    def delt(self, dst, gw=None):
-        """ Ex: 
-        delt(dst="::/0") 
-        delt(dst="2001:db8:cafe:f000::/56") 
-        delt(dst="2001:db8:cafe:f000::/56", gw="2001:db8:deca::1") 
-        """
-        tmp = dst+"/128"
-        dst, plen = tmp.split('/')[:2]
-        dst = in6_ptop(dst)
-        plen = int(plen)
-        l = filter(lambda x: in6_ptop(x[0]) == dst and x[1] == plen, self.routes)
-        if gw:
-            gw = in6_ptop(gw)
-            l = filter(lambda x: in6_ptop(x[0]) == gw, self.routes)
-        if len(l) == 0:
-            warning("No matching route found")
-        elif len(l) > 1:
-            warning("Found more than one match. Aborting.")
-        else:
-            i=self.routes.index(l[0])
-            self.invalidate_cache()
-            del(self.routes[i])
-        
-    def ifchange(self, iff, addr):
-        the_addr, the_plen = (addr.split("/")+["128"])[:2]
-        the_plen = int(the_plen)
-
-        naddr = inet_pton(socket.AF_INET6, the_addr)
-        nmask = in6_cidr2mask(the_plen)
-        the_net = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr))
-        
-        for i in range(len(self.routes)):
-            net,plen,gw,iface,addr = self.routes[i]
-            if iface != iff:
-                continue
-            if gw == '::':
-                self.routes[i] = (the_net,the_plen,gw,iface,the_addr)
-            else:
-                self.routes[i] = (net,the_plen,gw,iface,the_addr)
-        self.invalidate_cache()
-        ip6_neigh_cache.flush()
-
-    def ifdel(self, iff):
-        """ removes all route entries that uses 'iff' interface. """
-        new_routes=[]
-        for rt in self.routes:
-            if rt[3] != iff:
-                new_routes.append(rt)
-        self.invalidate_cache()
-        self.routes = new_routes
-
-
-    def ifadd(self, iff, addr):
-        """
-        Add an interface 'iff' with provided address into routing table.
-        
-        Ex: ifadd('eth0', '2001:bd8:cafe:1::1/64') will add following entry into 
-            Scapy6 internal routing table:
-
-            Destination           Next Hop  iface  Def src @
-            2001:bd8:cafe:1::/64  ::        eth0   2001:bd8:cafe:1::1
-
-            prefix length value can be omitted. In that case, a value of 128
-            will be used.
-        """
-        addr, plen = (addr.split("/")+["128"])[:2]
-        addr = in6_ptop(addr)
-        plen = int(plen)
-        naddr = inet_pton(socket.AF_INET6, addr)
-        nmask = in6_cidr2mask(plen)
-        prefix = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr))
-        self.invalidate_cache()
-        self.routes.append((prefix,plen,'::',iff,[addr]))
-
-    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")
-        l = dst.find("-")
-        while l >= 0:
-            m = (dst[l:]+":").find(":")
-            dst = dst[:l]+dst[l+m:]
-            l = 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
-
-        # 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]
-
-        pathes = []
-
-        # 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 in self.routes:
-            if dev is not None and iface != dev:
-                continue
-            if in6_isincluded(dst, p, plen):
-                pathes.append((plen, (iface, cset, gw)))
-            elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])):
-                pathes.append((plen, (iface, cset, gw)))
-                
-        if not pathes:
-            warning("No route found for IPv6 destination %s (no default route?)" % dst)
-            return (LOOPBACK_NAME, "::", "::") # XXX Linux specific
-
-        pathes.sort()
-        pathes.reverse()
-
-        best_plen = pathes[0][0]
-        pathes = filter(lambda x: x[0] == best_plen, pathes)
-
-        res = []
-        for p in pathes: # Here we select best source address for every route
-            tmp = p[1]
-            srcaddr = get_source_addr_from_candidate_set(dst, p[1][1])
-            if srcaddr is not None:
-                res.append((p[0], (tmp[0], srcaddr, tmp[2])))
-
-        # 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
-        #  XXX TODO : in a _near_ future, include metric in the game
-
-        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 = filter(lambda x: in6_isaddr6to4(x[1][1]), res)
-            elif in6_ismaddr(dst) or in6_islladdr(dst):
-                # TODO : I'm sure we are not covering all addresses. Check that
-                tmp = filter(lambda x: x[1][0] == conf.iface6, res)
-
-            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][1]
-
-        return res[0][1]
-
-
-
 ##########################
 ## Neighbor cache stuff ##
 ##########################
@@ -5123,26 +4815,3 @@ bind_layers(IPv6,      UDP,      nh = socket.IPPROTO_UDP )
 bind_layers(IP,        IPv6,     proto = socket.IPPROTO_IPV6 )
 bind_layers(IPv6,      IPv6,     nh = socket.IPPROTO_IPV6 )
 
-#############################################################################
-###                          Conf overloading                             ###
-#############################################################################
-
-def get_working_if6():
-    """
-    try to guess the best interface for conf.iface6 by looking for the 
-    one used by default route if any.
-    """
-    res = conf.route6.route("::/0")
-    if res:
-        iff, gw, addr = res
-        return iff
-    return get_working_if()
-
-conf.route6 = Route6()
-conf.iface6 = get_working_if6()
-
-if __name__ == '__main__':
-    interact(mydict=globals(), mybanner="IPv6 enabled")
-else:
-    import __builtin__
-    __builtin__.__dict__.update(globals())
diff --git a/scapy/route6.py b/scapy/route6.py
new file mode 100644
index 00000000..3cdf9ab2
--- /dev/null
+++ b/scapy/route6.py
@@ -0,0 +1,271 @@
+## This file is part of Scapy
+## See http://www.secdev.org/projects/scapy for more informations
+## Copyright (C) Philippe Biondi <phil@secdev.org>
+## This program is published under a GPLv2 license
+
+## Copyright (C) 2005  Guillaume Valadon <guedou@hongo.wide.ad.jp>
+##                     Arnaud Ebalard <arnaud.ebalard@eads.net>
+
+
+#############################################################################
+#############################################################################
+###                      Routing/Interfaces stuff                         ###
+#############################################################################
+#############################################################################
+
+import socket
+from config import conf
+from utils6 import *
+from arch import *
+
+
+class Route6:
+
+    def __init__(self):
+        self.invalidate_cache()
+        self.resync()
+
+    def invalidate_cache(self):
+        self.cache = {}
+
+    def flush(self):
+        self.invalidate_cache()
+        self.routes = []
+
+    def resync(self):
+        # TODO : At the moment, resync will drop existing Teredo routes
+        #        if any. Change that ...
+        self.invalidate_cache()
+	self.routes = read_routes6()
+	if self.routes == []:
+	     log_loading.info("No IPv6 support in kernel")
+        
+    def __repr__(self):
+        rtlst = [('Destination', 'Next Hop', "iface", "src candidates")]
+
+        for net,msk,gw,iface,cset in self.routes:
+	    rtlst.append(('%s/%i'% (net,msk), gw, iface, ", ".join(cset)))
+
+        colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst))
+        fmt = "  ".join(map(lambda x: "%%-%ds"%x, colwidth))
+        rt = "\n".join(map(lambda x: fmt % x, rtlst))
+
+        return rt
+
+
+    # Unlike Scapy's Route.make_route() function, we do not have 'host' and 'net'
+    # parameters. We only have a 'dst' parameter that accepts 'prefix' and 
+    # 'prefix/prefixlen' values.
+    # WARNING: Providing a specific device will at the moment not work correctly.
+    def make_route(self, dst, gw=None, dev=None):
+        """Internal function : create a route for 'dst' via 'gw'.
+        """
+        prefix, plen = (dst.split("/")+["128"])[:2]
+        plen = int(plen)
+
+        if gw is None:
+            gw = "::"
+        if dev is None:
+            dev, ifaddr, x = self.route(gw)
+        else:
+            # TODO: do better than that
+            # replace that unique address by the list of all addresses
+            lifaddr = in6_getifaddr()             
+            devaddrs = filter(lambda x: x[2] == dev, lifaddr)
+            ifaddr = construct_source_candidate_set(prefix, plen, devaddrs)
+
+        return (prefix, plen, gw, dev, ifaddr)
+
+    
+    def add(self, *args, **kargs):
+        """Ex:
+        add(dst="2001:db8:cafe:f000::/56")
+        add(dst="2001:db8:cafe:f000::/56", gw="2001:db8:cafe::1")
+        add(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1", dev="eth0")
+        """
+        self.invalidate_cache()
+        self.routes.append(self.make_route(*args, **kargs))
+
+
+    def delt(self, dst, gw=None):
+        """ Ex: 
+        delt(dst="::/0") 
+        delt(dst="2001:db8:cafe:f000::/56") 
+        delt(dst="2001:db8:cafe:f000::/56", gw="2001:db8:deca::1") 
+        """
+        tmp = dst+"/128"
+        dst, plen = tmp.split('/')[:2]
+        dst = in6_ptop(dst)
+        plen = int(plen)
+        l = filter(lambda x: in6_ptop(x[0]) == dst and x[1] == plen, self.routes)
+        if gw:
+            gw = in6_ptop(gw)
+            l = filter(lambda x: in6_ptop(x[0]) == gw, self.routes)
+        if len(l) == 0:
+            warning("No matching route found")
+        elif len(l) > 1:
+            warning("Found more than one match. Aborting.")
+        else:
+            i=self.routes.index(l[0])
+            self.invalidate_cache()
+            del(self.routes[i])
+        
+    def ifchange(self, iff, addr):
+        the_addr, the_plen = (addr.split("/")+["128"])[:2]
+        the_plen = int(the_plen)
+
+        naddr = inet_pton(socket.AF_INET6, the_addr)
+        nmask = in6_cidr2mask(the_plen)
+        the_net = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr))
+        
+        for i in range(len(self.routes)):
+            net,plen,gw,iface,addr = self.routes[i]
+            if iface != iff:
+                continue
+            if gw == '::':
+                self.routes[i] = (the_net,the_plen,gw,iface,the_addr)
+            else:
+                self.routes[i] = (net,the_plen,gw,iface,the_addr)
+        self.invalidate_cache()
+        ip6_neigh_cache.flush()
+
+    def ifdel(self, iff):
+        """ removes all route entries that uses 'iff' interface. """
+        new_routes=[]
+        for rt in self.routes:
+            if rt[3] != iff:
+                new_routes.append(rt)
+        self.invalidate_cache()
+        self.routes = new_routes
+
+
+    def ifadd(self, iff, addr):
+        """
+        Add an interface 'iff' with provided address into routing table.
+        
+        Ex: ifadd('eth0', '2001:bd8:cafe:1::1/64') will add following entry into 
+            Scapy6 internal routing table:
+
+            Destination           Next Hop  iface  Def src @
+            2001:bd8:cafe:1::/64  ::        eth0   2001:bd8:cafe:1::1
+
+            prefix length value can be omitted. In that case, a value of 128
+            will be used.
+        """
+        addr, plen = (addr.split("/")+["128"])[:2]
+        addr = in6_ptop(addr)
+        plen = int(plen)
+        naddr = inet_pton(socket.AF_INET6, addr)
+        nmask = in6_cidr2mask(plen)
+        prefix = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr))
+        self.invalidate_cache()
+        self.routes.append((prefix,plen,'::',iff,[addr]))
+
+    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")
+        l = dst.find("-")
+        while l >= 0:
+            m = (dst[l:]+":").find(":")
+            dst = dst[:l]+dst[l+m:]
+            l = 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
+
+        # 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]
+
+        pathes = []
+
+        # 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 in self.routes:
+            if dev is not None and iface != dev:
+                continue
+            if in6_isincluded(dst, p, plen):
+                pathes.append((plen, (iface, cset, gw)))
+            elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])):
+                pathes.append((plen, (iface, cset, gw)))
+                
+        if not pathes:
+            warning("No route found for IPv6 destination %s (no default route?)" % dst)
+            return (LOOPBACK_NAME, "::", "::") # XXX Linux specific
+
+        pathes.sort()
+        pathes.reverse()
+
+        best_plen = pathes[0][0]
+        pathes = filter(lambda x: x[0] == best_plen, pathes)
+
+        res = []
+        for p in pathes: # Here we select best source address for every route
+            tmp = p[1]
+            srcaddr = get_source_addr_from_candidate_set(dst, p[1][1])
+            if srcaddr is not None:
+                res.append((p[0], (tmp[0], srcaddr, tmp[2])))
+
+        # 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
+        #  XXX TODO : in a _near_ future, include metric in the game
+
+        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 = filter(lambda x: in6_isaddr6to4(x[1][1]), res)
+            elif in6_ismaddr(dst) or in6_islladdr(dst):
+                # TODO : I'm sure we are not covering all addresses. Check that
+                tmp = filter(lambda x: x[1][0] == conf.iface6, res)
+
+            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][1]
+
+        return res[0][1]
+
+conf.route6 = Route6()
+
+_res = conf.route6.route("::/0")
+if _res:
+    iff, gw, addr = _res
+    conf.iface6 = iff
+del(_res)
+
diff --git a/scapy/utils6.py b/scapy/utils6.py
index c3d67e38..51965428 100644
--- a/scapy/utils6.py
+++ b/scapy/utils6.py
@@ -12,6 +12,63 @@ from data import *
 from utils import *
 
 
+def construct_source_candidate_set(addr, plen, laddr):
+    """
+    Given all addresses assigned to a specific interface ('laddr' parameter),
+    this function returns the "candidate set" associated with 'addr/plen'.
+    
+    Basically, the function filters all interface addresses to keep only those
+    that have the same scope as provided prefix.
+    
+    This is on this list of addresses that the source selection mechanism 
+    will then be performed to select the best source address associated
+    with some specific destination that uses this prefix.
+    """
+
+    cset = []
+    if in6_isgladdr(addr):
+	cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr)
+    elif in6_islladdr(addr):
+	cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr)
+    elif in6_issladdr(addr):
+	cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr)
+    elif in6_ismaddr(addr):
+	if in6_ismnladdr(addr):
+	    cset = [('::1', 16, LOOPBACK_NAME)]
+	elif in6_ismgladdr(addr):
+	    cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr)
+	elif in6_ismlladdr(addr):
+	    cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr)
+	elif in6_ismsladdr(addr):
+	    cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr)
+    elif addr == '::' and plen == 0:
+	cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr)
+    cset = map(lambda x: x[0], cset)
+    return cset            
+
+def get_source_addr_from_candidate_set(dst, candidate_set):
+    """
+    This function implement a limited version of source address selection
+    algorithm defined in section 5 of RFC 3484. The format is very different
+    from that described in the document because it operates on a set 
+    of candidate source address for some specific route.
+    
+    Rationale behind the implementation is to be able to make the right 
+    choice for a 6to4 destination when both a 6to4 address and a IPv6 native
+    address are available for that interface.
+    """
+    
+    if len(candidate_set) == 0:
+	# Should not happen
+	return None
+    
+    if in6_isaddr6to4(dst):
+	tmp = filter(lambda x: in6_isaddr6to4(x), candidate_set)
+	if len(tmp) != 0:
+	    return tmp[0]
+
+    return candidate_set[0]
+
 
 def find_ifaddr2(addr, plen, laddr):
     dstAddrType = in6_getAddrType(addr)
-- 
GitLab