1## This file is part of Scapy 2## See http://www.secdev.org/projects/scapy for more informations 3## Copyright (C) Philippe Biondi <phil@secdev.org> 4## This program is published under a GPLv2 license 5 6## Copyright (C) 2005 Guillaume Valadon <guedou@hongo.wide.ad.jp> 7## Arnaud Ebalard <arnaud.ebalard@eads.net> 8 9""" 10Routing and network interface handling for IPv6. 11""" 12 13############################################################################# 14############################################################################# 15### Routing/Interfaces stuff ### 16############################################################################# 17############################################################################# 18 19from __future__ import absolute_import 20import socket 21import scapy.consts 22from scapy.config import conf 23from scapy.utils6 import * 24from scapy.arch import * 25from scapy.pton_ntop import * 26from scapy.error import warning, log_loading 27import scapy.modules.six as six 28 29 30class Route6: 31 32 def __init__(self): 33 self.resync() 34 self.invalidate_cache() 35 36 def invalidate_cache(self): 37 self.cache = {} 38 39 def flush(self): 40 self.invalidate_cache() 41 self.routes = [] 42 43 def resync(self): 44 # TODO : At the moment, resync will drop existing Teredo routes 45 # if any. Change that ... 46 self.invalidate_cache() 47 self.routes = read_routes6() 48 if self.routes == []: 49 log_loading.info("No IPv6 support in kernel") 50 51 def __repr__(self): 52 rtlst = [] 53 54 for net, msk, gw, iface, cset, metric in self.routes: 55 rtlst.append(('%s/%i'% (net,msk), gw, (iface if isinstance(iface, six.string_types) else iface.name), ", ".join(cset) if len(cset) > 0 else "", str(metric))) 56 57 return pretty_list(rtlst, 58 [('Destination', 'Next Hop', "Iface", "Src candidates", "Metric")], 59 sortBy = 1) 60 61 # Unlike Scapy's Route.make_route() function, we do not have 'host' and 'net' 62 # parameters. We only have a 'dst' parameter that accepts 'prefix' and 63 # 'prefix/prefixlen' values. 64 # WARNING: Providing a specific device will at the moment not work correctly. 65 def make_route(self, dst, gw=None, dev=None): 66 """Internal function : create a route for 'dst' via 'gw'. 67 """ 68 prefix, plen = (dst.split("/")+["128"])[:2] 69 plen = int(plen) 70 71 if gw is None: 72 gw = "::" 73 if dev is None: 74 dev, ifaddr, x = self.route(gw) 75 else: 76 # TODO: do better than that 77 # replace that unique address by the list of all addresses 78 lifaddr = in6_getifaddr() 79 devaddrs = [x for x in lifaddr if x[2] == dev] 80 ifaddr = construct_source_candidate_set(prefix, plen, devaddrs) 81 82 return (prefix, plen, gw, dev, ifaddr, 1) 83 84 85 def add(self, *args, **kargs): 86 """Ex: 87 add(dst="2001:db8:cafe:f000::/56") 88 add(dst="2001:db8:cafe:f000::/56", gw="2001:db8:cafe::1") 89 add(dst="2001:db8:cafe:f000::/64", gw="2001:db8:cafe::1", dev="eth0") 90 """ 91 self.invalidate_cache() 92 self.routes.append(self.make_route(*args, **kargs)) 93 94 95 def delt(self, dst, gw=None): 96 """ Ex: 97 delt(dst="::/0") 98 delt(dst="2001:db8:cafe:f000::/56") 99 delt(dst="2001:db8:cafe:f000::/56", gw="2001:db8:deca::1") 100 """ 101 tmp = dst+"/128" 102 dst, plen = tmp.split('/')[:2] 103 dst = in6_ptop(dst) 104 plen = int(plen) 105 l = [x for x in self.routes if in6_ptop(x[0]) == dst and x[1] == plen] 106 if gw: 107 gw = in6_ptop(gw) 108 l = [x for x in self.routes if in6_ptop(x[2]) == gw] 109 if len(l) == 0: 110 warning("No matching route found") 111 elif len(l) > 1: 112 warning("Found more than one match. Aborting.") 113 else: 114 i=self.routes.index(l[0]) 115 self.invalidate_cache() 116 del(self.routes[i]) 117 118 def ifchange(self, iff, addr): 119 the_addr, the_plen = (addr.split("/")+["128"])[:2] 120 the_plen = int(the_plen) 121 122 naddr = inet_pton(socket.AF_INET6, the_addr) 123 nmask = in6_cidr2mask(the_plen) 124 the_net = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr)) 125 126 for i, route in enumerate(self.routes): 127 net, plen, gw, iface, addr, metric = route 128 if iface != iff: 129 continue 130 if gw == '::': 131 self.routes[i] = (the_net,the_plen,gw,iface,[the_addr],metric) 132 else: 133 self.routes[i] = (net,plen,gw,iface,[the_addr],metric) 134 self.invalidate_cache() 135 conf.netcache.in6_neighbor.flush() 136 137 def ifdel(self, iff): 138 """ removes all route entries that uses 'iff' interface. """ 139 new_routes=[] 140 for rt in self.routes: 141 if rt[3] != iff: 142 new_routes.append(rt) 143 self.invalidate_cache() 144 self.routes = new_routes 145 146 147 def ifadd(self, iff, addr): 148 """ 149 Add an interface 'iff' with provided address into routing table. 150 151 Ex: ifadd('eth0', '2001:bd8:cafe:1::1/64') will add following entry into 152 Scapy6 internal routing table: 153 154 Destination Next Hop iface Def src @ Metric 155 2001:bd8:cafe:1::/64 :: eth0 2001:bd8:cafe:1::1 1 156 157 prefix length value can be omitted. In that case, a value of 128 158 will be used. 159 """ 160 addr, plen = (addr.split("/")+["128"])[:2] 161 addr = in6_ptop(addr) 162 plen = int(plen) 163 naddr = inet_pton(socket.AF_INET6, addr) 164 nmask = in6_cidr2mask(plen) 165 prefix = inet_ntop(socket.AF_INET6, in6_and(nmask,naddr)) 166 self.invalidate_cache() 167 self.routes.append((prefix,plen,'::',iff,[addr],1)) 168 169 def route(self, dst, dev=None): 170 """ 171 Provide best route to IPv6 destination address, based on Scapy6 172 internal routing table content. 173 174 When a set of address is passed (e.g. 2001:db8:cafe:*::1-5) an address 175 of the set is used. Be aware of that behavior when using wildcards in 176 upper parts of addresses ! 177 178 If 'dst' parameter is a FQDN, name resolution is performed and result 179 is used. 180 181 if optional 'dev' parameter is provided a specific interface, filtering 182 is performed to limit search to route associated to that interface. 183 """ 184 # Transform "2001:db8:cafe:*::1-5:0/120" to one IPv6 address of the set 185 dst = dst.split("/")[0] 186 savedst = dst # In case following inet_pton() fails 187 dst = dst.replace("*","0") 188 l = dst.find("-") 189 while l >= 0: 190 m = (dst[l:]+":").find(":") 191 dst = dst[:l]+dst[l+m:] 192 l = dst.find("-") 193 194 try: 195 inet_pton(socket.AF_INET6, dst) 196 except socket.error: 197 dst = socket.getaddrinfo(savedst, None, socket.AF_INET6)[0][-1][0] 198 # TODO : Check if name resolution went well 199 200 # Deal with dev-specific request for cache search 201 k = dst 202 if dev is not None: 203 k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) 204 if k in self.cache: 205 return self.cache[k] 206 207 pathes = [] 208 209 # TODO : review all kinds of addresses (scope and *cast) to see 210 # if we are able to cope with everything possible. I'm convinced 211 # it's not the case. 212 # -- arnaud 213 for p, plen, gw, iface, cset, me in self.routes: 214 if dev is not None and iface != dev: 215 continue 216 if in6_isincluded(dst, p, plen): 217 pathes.append((plen, me, (iface, cset, gw))) 218 elif (in6_ismlladdr(dst) and in6_islladdr(p) and in6_islladdr(cset[0])): 219 pathes.append((plen, me, (iface, cset, gw))) 220 221 if not pathes: 222 warning("No route found for IPv6 destination %s (no default route?)", dst) 223 return (scapy.consts.LOOPBACK_INTERFACE, "::", "::") 224 225 # Sort with longest prefix first 226 pathes.sort(reverse=True, key=lambda x: x[0]) 227 228 best_plen = pathes[0][0] 229 pathes = [x for x in pathes if x[0] == best_plen] 230 231 res = [] 232 for p in pathes: # Here we select best source address for every route 233 tmp = p[2] 234 srcaddr = get_source_addr_from_candidate_set(dst, tmp[1]) 235 if srcaddr is not None: 236 res.append((p[0], p[1], (tmp[0], srcaddr, tmp[2]))) 237 238 if res == []: 239 warning("Found a route for IPv6 destination '%s', but no possible source address.", dst) 240 return (scapy.consts.LOOPBACK_INTERFACE, "::", "::") 241 242 # Tie-breaker: Metrics 243 pathes.sort(key=lambda x: x[1]) 244 pathes = [i for i in pathes if i[1] == pathes[0][1]] 245 246 # Symptom : 2 routes with same weight (our weight is plen) 247 # Solution : 248 # - dst is unicast global. Check if it is 6to4 and we have a source 249 # 6to4 address in those available 250 # - dst is link local (unicast or multicast) and multiple output 251 # interfaces are available. Take main one (conf.iface6) 252 # - if none of the previous or ambiguity persists, be lazy and keep 253 # first one 254 255 if len(res) > 1: 256 tmp = [] 257 if in6_isgladdr(dst) and in6_isaddr6to4(dst): 258 # TODO : see if taking the longest match between dst and 259 # every source addresses would provide better results 260 tmp = [x for x in res if in6_isaddr6to4(x[2][1])] 261 elif in6_ismaddr(dst) or in6_islladdr(dst): 262 # TODO : I'm sure we are not covering all addresses. Check that 263 tmp = [x for x in res if x[2][0] == conf.iface6] 264 265 if tmp: 266 res = tmp 267 268 # Fill the cache (including dev-specific request) 269 k = dst 270 if dev is not None: 271 k = dst + "%%" + (dev if isinstance(dev, six.string_types) else dev.pcap_name) 272 self.cache[k] = res[0][2] 273 274 return res[0][2] 275 276conf.route6 = Route6() 277try: 278 conf.iface6 = conf.route6.route("::/0")[0] 279except: 280 pass 281