root/scapy/route6.py

Revision 921:a44f8b9b2473, 9.5 kB (checked in by Phil <phil@secdev.org>, 4 months ago)

Split IPv6 route management

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