1# Guillaume Valadon <guillaume@valadon.net>
2
3"""
4Scapy *BSD native support - core
5"""
6
7from __future__ import absolute_import
8from scapy.config import conf
9from scapy.error import Scapy_Exception, warning
10from scapy.data import ARPHDR_LOOPBACK, ARPHDR_ETHER
11from scapy.arch.common import get_if, get_bpf_pointer
12from scapy.consts import LOOPBACK_NAME
13
14from scapy.arch.bpf.consts import *
15
16import os
17import socket
18import fcntl
19import struct
20
21from ctypes import cdll, cast, pointer, POINTER, Structure
22from ctypes import c_int, c_ulong, c_char_p
23from ctypes.util import find_library
24from scapy.modules.six.moves import range
25
26
27# ctypes definitions
28
29LIBC = cdll.LoadLibrary(find_library("libc"))
30LIBC.ioctl.argtypes = [c_int, c_ulong, c_char_p]
31LIBC.ioctl.restype = c_int
32
33
34# Addresses manipulation functions
35
36def get_if_raw_addr(ifname):
37    """Returns the IPv4 address configured on 'ifname', packed with inet_pton."""
38
39    # Get ifconfig output
40    try:
41        fd = os.popen("%s %s" % (conf.prog.ifconfig, ifname))
42    except OSError as msg:
43        warning("Failed to execute ifconfig: (%s)", msg)
44        return b"\0\0\0\0"
45
46    # Get IPv4 addresses
47    addresses = [l for l in fd if l.find("netmask") >= 0]
48    if not addresses:
49        warning("No IPv4 address found on %s !", ifname)
50        return b"\0\0\0\0"
51
52    # Pack the first address
53    address = addresses[0].split(' ')[1]
54    return socket.inet_pton(socket.AF_INET, address)
55
56
57def get_if_raw_hwaddr(ifname):
58    """Returns the packed MAC address configured on 'ifname'."""
59
60    NULL_MAC_ADDRESS = b'\x00'*6
61
62    # Handle the loopback interface separately
63    if ifname == LOOPBACK_NAME:
64        return (ARPHDR_LOOPBACK, NULL_MAC_ADDRESS)
65
66    # Get ifconfig output
67    try:
68        fd = os.popen("%s %s" % (conf.prog.ifconfig, ifname))
69    except OSError as msg:
70        raise Scapy_Exception("Failed to execute ifconfig: (%s)" % msg)
71
72    # Get MAC addresses
73    addresses = [l for l in fd.readlines() if l.find("ether") >= 0 or
74                                              l.find("lladdr") >= 0 or
75                                              l.find("address") >= 0]
76    if not addresses:
77        raise Scapy_Exception("No MAC address found on %s !" % ifname)
78
79    # Pack and return the MAC address
80    mac = addresses[0].split(' ')[1]
81    mac = [chr(int(b, 16)) for b in mac.split(':')]
82    return (ARPHDR_ETHER, ''.join(mac))
83
84
85# BPF specific functions
86
87def get_dev_bpf():
88    """Returns an opened BPF file object"""
89
90    # Get the first available BPF handle
91    for bpf in range(0, 8):
92        try:
93            fd = os.open("/dev/bpf%i" % bpf, os.O_RDWR)
94            return (fd, bpf)
95        except OSError as err:
96            continue
97
98    raise Scapy_Exception("No /dev/bpf handle is available !")
99
100
101def attach_filter(fd, iface, bpf_filter_string):
102    """Attach a BPF filter to the BPF file descriptor"""
103
104    # Retrieve the BPF byte code in decimal
105    command = "%s -i %s -ddd -s 1600 '%s'" % (conf.prog.tcpdump, iface, bpf_filter_string)
106    try:
107        f = os.popen(command)
108    except OSError as msg:
109        raise Scapy_Exception("Failed to execute tcpdump: (%s)" % msg)
110
111    # Convert the byte code to a BPF program structure
112    lines = f.readlines()
113    if lines == []:
114        raise Scapy_Exception("Got an empty BPF filter from tcpdump !")
115
116    bp = get_bpf_pointer(lines)
117    # Assign the BPF program to the interface
118    ret = LIBC.ioctl(c_int(fd), BIOCSETF, cast(pointer(bp), c_char_p))
119    if ret < 0:
120        raise Scapy_Exception("Can't attach the BPF filter !")
121
122
123# Interface manipulation functions
124
125def get_if_list():
126    """Returns a list containing all network interfaces."""
127
128    # Get ifconfig output
129    try:
130        fd = os.popen("%s -a" % conf.prog.ifconfig)
131    except OSError as msg:
132        raise Scapy_Exception("Failed to execute ifconfig: (%s)" % msg)
133
134    # Get interfaces
135    interfaces = [line[:line.find(':')] for line in fd.readlines()
136                                        if ": flags" in line.lower()]
137    return interfaces
138
139
140def get_working_ifaces():
141    """
142    Returns an ordered list of interfaces that could be used with BPF.
143    Note: the order mimics pcap_findalldevs() behavior
144    """
145
146    # Only root is allowed to perform the following ioctl() call
147    if os.getuid() != 0:
148        return []
149
150    # Test all network interfaces
151    interfaces = []
152    for ifname in get_if_list():
153
154        # Unlike pcap_findalldevs(), we do not care of loopback interfaces.
155        if ifname == LOOPBACK_NAME:
156            continue
157
158        # Get interface flags
159        try:
160            result = get_if(ifname, SIOCGIFFLAGS)
161        except IOError as msg:
162            warning("ioctl(SIOCGIFFLAGS) failed on %s !", ifname)
163            continue
164
165        # Convert flags
166        ifflags = struct.unpack("16xH14x", result)[0]
167        if ifflags & 0x1:  # IFF_UP
168
169            # Get a BPF handle
170            fd, _ = get_dev_bpf()
171            if fd is None:
172                raise Scapy_Exception("No /dev/bpf are available !")
173
174            # Check if the interface can be used
175            try:
176                fcntl.ioctl(fd, BIOCSETIF, struct.pack("16s16x", ifname.encode()))
177                interfaces.append((ifname, int(ifname[-1])))
178            except IOError as err:
179                pass
180
181            # Close the file descriptor
182            os.close(fd)
183
184    # Sort to mimic pcap_findalldevs() order
185    interfaces.sort(lambda ifname_left_and_ifid_left,
186                        ifname_right_and_ifid_right: ifname_left_and_ifid_left[1]-ifname_right_and_ifid_right[1])
187    return interfaces
188
189
190def get_working_if():
191    """Returns the first interface than can be used with BPF"""
192
193    ifaces = get_working_ifaces()
194    if not ifaces:
195        # A better interface will be selected later using the routing table
196        return LOOPBACK_NAME
197    return ifaces[0][0]
198