unix.py revision 35f97efc53839f6366918c251dba5df2ac553c8d
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"""
7Common customizations for all Unix-like operating systems other than Linux
8"""
9
10import sys,os,struct,socket,time
11from fcntl import ioctl
12import socket
13
14from scapy.error import warning, log_interactive
15import scapy.config
16import scapy.utils
17from scapy.utils6 import in6_getscope, construct_source_candidate_set
18from scapy.utils6 import in6_isvalid, in6_ismlladdr, in6_ismnladdr
19from scapy.arch.consts import FREEBSD, NETBSD, OPENBSD, SOLARIS, LOOPBACK_NAME
20from scapy.arch import get_if_addr
21from scapy.config import conf
22
23
24##################
25## Routes stuff ##
26##################
27
28def _guess_iface_name(netif):
29    """
30    We attempt to guess the name of interfaces that are truncated from the
31    output of ifconfig -l.
32    If there is only one possible candidate matching the interface name then we
33    return it.
34    If there are none or more, then we return None.
35    """
36    with os.popen('%s -l' % conf.prog.ifconfig) as fdesc:
37        ifaces = fdesc.readline().strip().split(' ')
38    matches = [iface for iface in ifaces if iface.startswith(netif)]
39    if len(matches) == 1:
40        return matches[0]
41    return None
42
43
44def read_routes():
45    if SOLARIS:
46        f=os.popen("netstat -rvn") # -f inet
47    elif FREEBSD:
48        f=os.popen("netstat -rnW") # -W to handle long interface names
49    else:
50        f=os.popen("netstat -rn") # -f inet
51    ok = 0
52    mtu_present = False
53    prio_present = False
54    routes = []
55    pending_if = []
56    for l in f.readlines():
57        if not l:
58            break
59        l = l.strip()
60        if l.find("----") >= 0: # a separation line
61            continue
62        if not ok:
63            if l.find("Destination") >= 0:
64                ok = 1
65                mtu_present = "Mtu" in l
66                prio_present = "Prio" in l
67                refs_present = "Refs" in l
68            continue
69        if not l:
70            break
71        if SOLARIS:
72            lspl = l.split()
73            if len(lspl) == 10:
74                dest,mask,gw,netif,mxfrg,rtt,ref,flg = lspl[:8]
75            else: # missing interface
76                dest,mask,gw,mxfrg,rtt,ref,flg = lspl[:7]
77                netif=None
78        else:
79            rt = l.split()
80            dest,gw,flg = rt[:3]
81            netif = rt[4 + mtu_present + prio_present + refs_present]
82        if flg.find("Lc") >= 0:
83            continue
84        if dest == "default":
85            dest = 0L
86            netmask = 0L
87        else:
88            if SOLARIS:
89                netmask = scapy.utils.atol(mask)
90            elif "/" in dest:
91                dest,netmask = dest.split("/")
92                netmask = scapy.utils.itom(int(netmask))
93            else:
94                netmask = scapy.utils.itom((dest.count(".") + 1) * 8)
95            dest += ".0"*(3-dest.count("."))
96            dest = scapy.utils.atol(dest)
97        if not "G" in flg:
98            gw = '0.0.0.0'
99        if netif is not None:
100            try:
101                ifaddr = get_if_addr(netif)
102                routes.append((dest,netmask,gw,netif,ifaddr))
103            except OSError as exc:
104                if exc.message == 'Device not configured':
105                    # This means the interface name is probably truncated by
106                    # netstat -nr. We attempt to guess it's name and if not we
107                    # ignore it.
108                    guessed_netif = _guess_iface_name(netif)
109                    if guessed_netif is not None:
110                        ifaddr = get_if_addr(guessed_netif)
111                        routes.append((dest, netmask, gw, guessed_netif, ifaddr))
112                    else:
113                        warning("Could not guess partial interface name: %s" % netif)
114                else:
115                    raise
116        else:
117            pending_if.append((dest,netmask,gw))
118    f.close()
119
120    # On Solaris, netstat does not provide output interfaces for some routes
121    # We need to parse completely the routing table to route their gw and
122    # know their output interface
123    for dest,netmask,gw in pending_if:
124        gw_l = scapy.utils.atol(gw)
125        max_rtmask,gw_if,gw_if_addr, = 0,None,None
126        for rtdst,rtmask,_,rtif,rtaddr in routes[:]:
127            if gw_l & rtmask == rtdst:
128                if rtmask >= max_rtmask:
129                    max_rtmask = rtmask
130                    gw_if = rtif
131                    gw_if_addr = rtaddr
132        if gw_if:
133            routes.append((dest,netmask,gw,gw_if,gw_if_addr))
134        else:
135            warning("Did not find output interface to reach gateway %s" % gw)
136
137    return routes
138
139############
140### IPv6 ###
141############
142
143def _in6_getifaddr(ifname):
144    """
145    Returns a list of IPv6 addresses configured on the interface ifname.
146    """
147
148    # Get the output of ifconfig
149    try:
150        f = os.popen("%s %s" % (conf.prog.ifconfig, ifname))
151    except OSError,msg:
152        log_interactive.warning("Failed to execute ifconfig.")
153        return []
154
155    # Iterate over lines and extract IPv6 addresses
156    ret = []
157    for line in f:
158        if "inet6" in line:
159            addr = line.rstrip().split(None, 2)[1] # The second element is the IPv6 address
160        else:
161            continue
162        if '%' in line: # Remove the interface identifier if present
163            addr = addr.split("%", 1)[0]
164
165        # Check if it is a valid IPv6 address
166        try:
167            socket.inet_pton(socket.AF_INET6, addr)
168        except:
169            continue
170
171        # Get the scope and keep the address
172        scope = in6_getscope(addr)
173        ret.append((addr, scope, ifname))
174
175    return ret
176
177def in6_getifaddr():
178    """
179    Returns a list of 3-tuples of the form (addr, scope, iface) where
180    'addr' is the address of scope 'scope' associated to the interface
181    'iface'.
182
183    This is the list of all addresses of all interfaces available on
184    the system.
185    """
186
187    # List all network interfaces
188    if OPENBSD:
189        try:
190            f = os.popen("%s" % conf.prog.ifconfig)
191        except OSError,msg:
192	    log_interactive.warning("Failed to execute ifconfig.")
193	    return []
194
195        # Get the list of network interfaces
196        splitted_line = []
197        for l in f:
198            if "flags" in l:
199                iface = l.split()[0].rstrip(':')
200                splitted_line.append(iface)
201
202    else: # FreeBSD, NetBSD or Darwin
203        try:
204	    f = os.popen("%s -l" % conf.prog.ifconfig)
205        except OSError,msg:
206	    log_interactive.warning("Failed to execute ifconfig.")
207	    return []
208
209        # Get the list of network interfaces
210        splitted_line = f.readline().rstrip().split()
211
212    ret = []
213    for i in splitted_line:
214	ret += _in6_getifaddr(i)
215    return ret
216
217
218def read_routes6():
219    """Return a list of IPv6 routes than can be used by Scapy."""
220
221    # Call netstat to retrieve IPv6 routes
222    fd_netstat = os.popen("netstat -rn -f inet6")
223
224    # List interfaces IPv6 addresses
225    lifaddr = in6_getifaddr()
226    if not lifaddr:
227        return []
228
229    # Routes header information
230    got_header = False
231    mtu_present = False
232    prio_present = False
233
234    # Parse the routes
235    routes = []
236    for line in fd_netstat.readlines():
237
238        # Parse the routes header and try to identify extra columns
239        if not got_header:
240            if "Destination" == line[:11]:
241                got_header = True
242                mtu_present = "Mtu" in line
243                prio_present = "Prio" in line
244            continue
245
246        # Parse a route entry according to the operating system
247        splitted_line = line.split()
248        if OPENBSD or NETBSD:
249            index = 5 + mtu_present + prio_present
250            if len(splitted_line) < index:
251                warning("Not enough columns in route entry !")
252                continue
253            destination, next_hop, flags = splitted_line[:3]
254            dev = splitted_line[index]
255        else:
256            # FREEBSD or DARWIN
257            if len(splitted_line) < 4:
258                warning("Not enough columns in route entry !")
259                continue
260            destination, next_hop, flags, dev = splitted_line[:4]
261
262        # Check flags
263        if not "U" in flags:  # usable route
264            continue
265        if "R" in flags:  # Host or net unreachable
266            continue
267        if "m" in flags:  # multicast address
268            # Note: multicast routing is handled in Route6.route()
269            continue
270
271        # Replace link with the default route in next_hop
272        if "link" in next_hop:
273            next_hop = "::"
274
275        # Default prefix length
276        destination_plen = 128
277
278        # Extract network interface from the zone id
279        if '%' in destination:
280            destination, dev = destination.split('%')
281            if '/' in dev:
282                # Example: fe80::%lo0/64 ; dev = "lo0/64"
283                dev, destination_plen = dev.split('/')
284        if '%' in next_hop:
285            next_hop, dev = next_hop.split('%')
286
287        # Ensure that the next hop is a valid IPv6 address
288        if not in6_isvalid(next_hop):
289            # Note: the 'Gateway' column might contain a MAC address
290            next_hop = "::"
291
292        # Modify parsed routing entries
293        # Note: these rules are OS specific and may evolve over time
294        if destination == "default":
295            destination, destination_plen = "::", 0
296        elif '/' in destination:
297            # Example: fe80::/10
298            destination, destination_plen = destination.split('/')
299        if '/' in dev:
300            # Example: ff02::%lo0/32 ; dev = "lo0/32"
301            dev, destination_plen = dev.split('/')
302
303        # Check route entries parameters consistency
304        if not in6_isvalid(destination):
305            warning("Invalid destination IPv6 address in route entry !")
306            continue
307        try:
308            destination_plen = int(destination_plen)
309        except:
310            warning("Invalid IPv6 prefix length in route entry !")
311            continue
312        if in6_ismlladdr(destination) or in6_ismnladdr(destination):
313            # Note: multicast routing is handled in Route6.route()
314            continue
315
316        if LOOPBACK_NAME in dev:
317            # Handle ::1 separately
318            cset = ["::1"]
319            next_hop = "::"
320        else:
321            # Get possible IPv6 source addresses
322            devaddrs = filter(lambda x: x[2] == dev, lifaddr)
323            cset = construct_source_candidate_set(destination, destination_plen, devaddrs, LOOPBACK_NAME)
324
325        if len(cset):
326            routes.append((destination, destination_plen, next_hop, dev, cset))
327
328    fd_netstat.close()
329    return routes
330