Changeset 921:a44f8b9b2473

Show
Ignore:
Timestamp:
09/12/08 17:57:20 (4 months ago)
Author:
Phil <phil@secdev.org>
Message:

Split IPv6 route management

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • scapy/all.py

    r846 r921  
    2121from utils import * 
    2222from route import * 
     23from utils6 import * 
     24from route6 import * 
    2325from sendrecv import * 
    2426from supersocket import * 
  • scapy/arch/linux.py

    r920 r921  
    251251        else: 
    252252            devaddrs = filter(lambda x: x[2] == dev, lifaddr) 
    253             cset = construct_source_candidate_set(d, dp, devaddrs) 
     253            cset = scapy.utils6.construct_source_candidate_set(d, dp, devaddrs) 
    254254         
    255255        if len(cset) != 0: 
  • scapy/config.py

    r918 r921  
    294294    wepkey = "" 
    295295    route = None # Filed by route.py 
     296    route6 = None # Filed by route6.py 
    296297    auto_fragment = 1 
    297298    debug_dissector = 0 
  • scapy/layers/inet6.py

    r920 r921  
    4141def get_cls(name, fallback_cls): 
    4242    return globals().get(name, fallback_cls) 
    43  
    44  
    45  
    46 ############################################################################# 
    47 ############################################################################# 
    48 ###                      Routing/Interfaces stuff                         ### 
    49 ############################################################################# 
    50 ############################################################################# 
    51  
    52 def construct_source_candidate_set(addr, plen, laddr): 
    53     """ 
    54     Given all addresses assigned to a specific interface ('laddr' parameter), 
    55     this function returns the "candidate set" associated with 'addr/plen'. 
    56      
    57     Basically, the function filters all interface addresses to keep only those 
    58     that have the same scope as provided prefix. 
    59      
    60     This is on this list of addresses that the source selection mechanism  
    61     will then be performed to select the best source address associated 
    62     with some specific destination that uses this prefix. 
    63     """ 
    64  
    65     cset = [] 
    66     if in6_isgladdr(addr): 
    67         cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr) 
    68     elif in6_islladdr(addr): 
    69         cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr) 
    70     elif in6_issladdr(addr): 
    71         cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr) 
    72     elif in6_ismaddr(addr): 
    73         if in6_ismnladdr(addr): 
    74             cset = [('::1', 16, LOOPBACK_NAME)] 
    75         elif in6_ismgladdr(addr): 
    76             cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr) 
    77         elif in6_ismlladdr(addr): 
    78             cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr) 
    79         elif in6_ismsladdr(addr): 
    80             cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr) 
    81     elif addr == '::' and plen == 0: 
    82         cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr) 
    83     cset = map(lambda x: x[0], cset) 
    84     return cset             
    85  
    86 def get_source_addr_from_candidate_set(dst, candidate_set): 
    87     """ 
    88     This function implement a limited version of source address selection 
    89     algorithm defined in section 5 of RFC 3484. The format is very different 
    90     from that described in the document because it operates on a set  
    91     of candidate source address for some specific route. 
    92      
    93     Rationale behind the implementation is to be able to make the right  
    94     choice for a 6to4 destination when both a 6to4 address and a IPv6 native 
    95     address are available for that interface. 
    96     """ 
    97      
    98     if len(candidate_set) == 0: 
    99         # Should not happen 
    100         return None 
    101      
    102     if in6_isaddr6to4(dst): 
    103         tmp = filter(lambda x: in6_isaddr6to4(x), candidate_set) 
    104         if len(tmp) != 0: 
    105             return tmp[0] 
    106  
    107     return candidate_set[0] 
    108  
    109 class Route6: 
    110  
    111     def __init__(self): 
    112         self.invalidate_cache() 
    113         self.resync() 
    114  
    115     def invalidate_cache(self): 
    116         self.cache = {} 
    117  
    118     def flush(self): 
    119         self.invalidate_cache() 
    120         self.routes = [] 
    121  
    122     def resync(self): 
    123         # TODO : At the moment, resync will drop existing Teredo routes 
    124         #        if any. Change that ... 
    125         self.invalidate_cache() 
    126         self.routes = read_routes6() 
    127         if self.routes == []: 
    128              log_loading.info("No IPv6 support in kernel") 
    129          
    130     def __repr__(self): 
    131         rtlst = [('Destination', 'Next Hop', "iface", "src candidates")] 
    132  
    133         for net,msk,gw,iface,cset in self.routes: 
    134             rtlst.append(('%s/%i'% (net,msk), gw, iface, ", ".join(cset))) 
    135  
    136         colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst)) 
    137         fmt = "  ".join(map(lambda x: "%%-%ds"%x, colwidth)) 
    138         rt = "\n".join(map(lambda x: fmt % x, rtlst)) 
    139  
    140         return rt 
    141  
    142  
    143     # Unlike Scapy's Route.make_route() function, we do not have 'host' and 'net' 
    144     # parameters. We only have a 'dst' parameter that accepts 'prefix' and  
    145     # 'prefix/prefixlen' values. 
    146     # WARNING: Providing a specific device will at the moment not work correctly. 
    147     def make_route(self, dst, gw=None, dev=None): 
    148         """Internal function : create a route for 'dst' via 'gw'. 
    149         """ 
    150         prefix, plen = (dst.split("/")+["128"])[:2] 
    151         plen = int(plen) 
    152  
    153         if gw is None: 
    154             gw = "::" 
    155         if dev is None: 
    156             dev, ifaddr, x = self.route(gw) 
    157         else: 
    158             # TODO: do better than that 
    159             # replace that unique address by the list of all addresses 
    160             lifaddr = in6_getifaddr()              
    161             devaddrs = filter(lambda x: x[2] == dev, lifaddr) 
    162             ifaddr = construct_source_candidate_set(prefix, plen, devaddrs) 
    163  
    164         return (prefix, plen, gw, dev, ifaddr) 
    165  
    166      
    167     def add(self, *args, **kargs): 
    168         """Ex: 
    169         add(dst="2001:db8:cafe:f000::/56") 
    170         add(dst="2001:db8:cafe:f000::/56", gw="2001:db8:cafe::1") 
    171         add(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1", dev="eth0") 
    172         """ 
    173         self.invalidate_cache() 
    174         self.routes.append(self.make_route(*args, **kargs)) 
    175  
    176  
    177     def delt(self, dst, gw=None): 
    178         """ Ex:  
    179         delt(dst="::/0")  
    180         delt(dst="2001:db8:cafe:f000::/56")  
    181         delt(dst="2001:db8:cafe:f000::/56", gw="2001:db8:deca::1")  
    182         """ 
    183         tmp = dst+"/128" 
    184         dst, plen = tmp.split('/')[:2] 
    185         dst = in6_ptop(dst) 
    186         plen = int(plen) 
    187         l = filter(lambda x: in6_ptop(x[0]) == dst and x[1] == plen, self.routes) 
    188         if gw: 
    189             gw = in6_ptop(gw) 
    190             l = filter(lambda x: in6_ptop(x[0]) == gw, self.routes) 
    191         if len(l) == 0: 
    192             warning("No matching route found") 
    193         elif len(l) > 1: 
    194             warning("Found more than one match. Aborting.") 
    195         else: 
    196             i=self.routes.index(l[0]) 
    197             self.invalidate_cache() 
    198             del(self.routes[i]) 
    199          
    200     def ifchange(self, iff, addr): 
    201         the_addr, the_plen = (addr.split("/")+["128"])[:2] 
    202         the_plen = int(the_plen) 
    203  
    204         naddr = inet_pton(socket.AF_INET6, the_addr) 
    205         nmask = in6_cidr2mask(the_plen) 
    206         the_net = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr)) 
    207          
    208         for i in range(len(self.routes)): 
    209             net,plen,gw,iface,addr = self.routes[i] 
    210             if iface != iff: 
    211                 continue 
    212             if gw == '::': 
    213                 self.routes[i] = (the_net,the_plen,gw,iface,the_addr) 
    214             else: 
    215                 self.routes[i] = (net,the_plen,gw,iface,the_addr) 
    216         self.invalidate_cache() 
    217         ip6_neigh_cache.flush() 
    218  
    219     def ifdel(self, iff): 
    220         """ removes all route entries that uses 'iff' interface. """ 
    221         new_routes=[] 
    222         for rt in self.routes: 
    223             if rt[3] != iff: 
    224                 new_routes.append(rt) 
    225         self.invalidate_cache() 
    226         self.routes = new_routes 
    227  
    228  
    229     def ifadd(self, iff, addr): 
    230         """ 
    231         Add an interface 'iff' with provided address into routing table. 
    232          
    233         Ex: ifadd('eth0', '2001:bd8:cafe:1::1/64') will add following entry into  
    234             Scapy6 internal routing table: 
    235  
    236             Destination           Next Hop  iface  Def src @ 
    237             2001:bd8:cafe:1::/64  ::        eth0   2001:bd8:cafe:1::1 
    238  
    239             prefix length value can be omitted. In that case, a value of 128 
    240             will be used. 
    241         """ 
    242         addr, plen = (addr.split("/")+["128"])[:2] 
    243         addr = in6_ptop(addr) 
    244         plen = int(plen) 
    245         naddr = inet_pton(socket.AF_INET6, addr) 
    246         nmask = in6_cidr2mask(plen) 
    247         prefix = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr)) 
    248         self.invalidate_cache() 
    249         self.routes.append((prefix,plen,'::',iff,[addr])) 
    250  
    251     def route(self, dst, dev=None): 
    252         """ 
    253         Provide best route to IPv6 destination address, based on Scapy6  
    254         internal routing table content. 
    255  
    256         When a set of address is passed (e.g. 2001:db8:cafe:*::1-5) an address 
    257         of the set is used. Be aware of that behavior when using wildcards in 
    258         upper parts of addresses ! 
    259  
    260         If 'dst' parameter is a FQDN, name resolution is performed and result 
    261         is used. 
    262  
    263         if optional 'dev' parameter is provided a specific interface, filtering 
    264         is performed to limit search to route associated to that interface. 
    265         """ 
    266         # Transform "2001:db8:cafe:*::1-5:0/120" to one IPv6 address of the set 
    267         dst = dst.split("/")[0] 
    268         savedst = dst # In case following inet_pton() fails  
    269         dst = dst.replace("*","0") 
    270         l = dst.find("-") 
    271         while l >= 0: 
    272             m = (dst[l:]+":").find(":") 
    273             dst = dst[:l]+dst[l+m:] 
    274             l = dst.find("-") 
    275              
    276         try: 
    277             inet_pton(socket.AF_INET6, dst) 
    278         except socket.error: 
    279             dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0] 
    280             # TODO : Check if name resolution went well 
    281  
    282         # Deal with dev-specific request for cache search 
    283         k = dst 
    284         if dev is not None: 
    285             k = dst + "%%" + dev 
    286         if k in self.cache: 
    287             return self.cache[k] 
    288  
    289         pathes = [] 
    290  
    291         # TODO : review all kinds of addresses (scope and *cast) to see 
    292         #        if we are able to cope with everything possible. I'm convinced  
    293         #        it's not the case. 
    294         # -- arnaud 
    295         for p, plen, gw, iface, cset in self.routes: 
    296             if dev is not None and iface != dev: 
    297                 continue 
    298             if in6_isincluded(dst, p, plen): 
    299                 pathes.append((plen, (iface, cset, gw))) 
    300             elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])): 
    301                 pathes.append((plen, (iface, cset, gw))) 
    302                  
    303         if not pathes: 
    304             warning("No route found for IPv6 destination %s (no default route?)" % dst) 
    305             return (LOOPBACK_NAME, "::", "::") # XXX Linux specific 
    306  
    307         pathes.sort() 
    308         pathes.reverse() 
    309  
    310         best_plen = pathes[0][0] 
    311         pathes = filter(lambda x: x[0] == best_plen, pathes) 
    312  
    313         res = [] 
    314         for p in pathes: # Here we select best source address for every route 
    315             tmp = p[1] 
    316             srcaddr = get_source_addr_from_candidate_set(dst, p[1][1]) 
    317             if srcaddr is not None: 
    318                 res.append((p[0], (tmp[0], srcaddr, tmp[2]))) 
    319  
    320         # Symptom  : 2 routes with same weight (our weight is plen) 
    321         # Solution :  
    322         #  - dst is unicast global. Check if it is 6to4 and we have a source  
    323         #    6to4 address in those available 
    324         #  - dst is link local (unicast or multicast) and multiple output 
    325         #    interfaces are available. Take main one (conf.iface6) 
    326         #  - if none of the previous or ambiguity persists, be lazy and keep 
    327         #    first one 
    328         #  XXX TODO : in a _near_ future, include metric in the game 
    329  
    330         if len(res) > 1: 
    331             tmp = [] 
    332             if in6_isgladdr(dst) and in6_isaddr6to4(dst): 
    333                 # TODO : see if taking the longest match between dst and 
    334                 #        every source addresses would provide better results 
    335                 tmp = filter(lambda x: in6_isaddr6to4(x[1][1]), res) 
    336             elif in6_ismaddr(dst) or in6_islladdr(dst): 
    337                 # TODO : I'm sure we are not covering all addresses. Check that 
    338                 tmp = filter(lambda x: x[1][0] == conf.iface6, res) 
    339  
    340             if tmp: 
    341                 res = tmp 
    342                  
    343         # Fill the cache (including dev-specific request) 
    344         k = dst 
    345         if dev is not None: 
    346             k = dst + "%%" + dev 
    347         self.cache[k] = res[0][1] 
    348  
    349         return res[0][1] 
    350  
    35143 
    35244 
     
    51244816bind_layers(IPv6,      IPv6,     nh = socket.IPPROTO_IPV6 ) 
    51254817 
    5126 ############################################################################# 
    5127 ###                          Conf overloading                             ### 
    5128 ############################################################################# 
    5129  
    5130 def get_working_if6(): 
    5131     """ 
    5132     try to guess the best interface for conf.iface6 by looking for the  
    5133     one used by default route if any. 
    5134     """ 
    5135     res = conf.route6.route("::/0") 
    5136     if res: 
    5137         iff, gw, addr = res 
    5138         return iff 
    5139     return get_working_if() 
    5140  
    5141 conf.route6 = Route6() 
    5142 conf.iface6 = get_working_if6() 
    5143  
    5144 if __name__ == '__main__': 
    5145     interact(mydict=globals(), mybanner="IPv6 enabled") 
    5146 else: 
    5147     import __builtin__ 
    5148     __builtin__.__dict__.update(globals()) 
  • scapy/utils6.py

    r920 r921  
    1212from utils import * 
    1313 
     14 
     15def construct_source_candidate_set(addr, plen, laddr): 
     16    """ 
     17    Given all addresses assigned to a specific interface ('laddr' parameter), 
     18    this function returns the "candidate set" associated with 'addr/plen'. 
     19     
     20    Basically, the function filters all interface addresses to keep only those 
     21    that have the same scope as provided prefix. 
     22     
     23    This is on this list of addresses that the source selection mechanism  
     24    will then be performed to select the best source address associated 
     25    with some specific destination that uses this prefix. 
     26    """ 
     27 
     28    cset = [] 
     29    if in6_isgladdr(addr): 
     30        cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr) 
     31    elif in6_islladdr(addr): 
     32        cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr) 
     33    elif in6_issladdr(addr): 
     34        cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr) 
     35    elif in6_ismaddr(addr): 
     36        if in6_ismnladdr(addr): 
     37            cset = [('::1', 16, LOOPBACK_NAME)] 
     38        elif in6_ismgladdr(addr): 
     39            cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr) 
     40        elif in6_ismlladdr(addr): 
     41            cset = filter(lambda x: x[1] == IPV6_ADDR_LINKLOCAL, laddr) 
     42        elif in6_ismsladdr(addr): 
     43            cset = filter(lambda x: x[1] == IPV6_ADDR_SITELOCAL, laddr) 
     44    elif addr == '::' and plen == 0: 
     45        cset = filter(lambda x: x[1] == IPV6_ADDR_GLOBAL, laddr) 
     46    cset = map(lambda x: x[0], cset) 
     47    return cset             
     48 
     49def get_source_addr_from_candidate_set(dst, candidate_set): 
     50    """ 
     51    This function implement a limited version of source address selection 
     52    algorithm defined in section 5 of RFC 3484. The format is very different 
     53    from that described in the document because it operates on a set  
     54    of candidate source address for some specific route. 
     55     
     56    Rationale behind the implementation is to be able to make the right  
     57    choice for a 6to4 destination when both a 6to4 address and a IPv6 native 
     58    address are available for that interface. 
     59    """ 
     60     
     61    if len(candidate_set) == 0: 
     62        # Should not happen 
     63        return None 
     64     
     65    if in6_isaddr6to4(dst): 
     66        tmp = filter(lambda x: in6_isaddr6to4(x), candidate_set) 
     67        if len(tmp) != 0: 
     68            return tmp[0] 
     69 
     70    return candidate_set[0] 
    1471 
    1572