• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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