1# SPDX-License-Identifier: GPL-2.0-only 2# This file is part of Scapy 3# See https://scapy.net/ for more information 4# Copyright (C) Gabriel Potter <gabriel[]potter[]fr> 5 6""" 7Interfaces management 8""" 9 10import itertools 11import uuid 12from collections import defaultdict 13 14from scapy.config import conf 15from scapy.consts import WINDOWS, LINUX 16from scapy.utils import pretty_list 17from scapy.utils6 import in6_isvalid 18 19# Typing imports 20import scapy 21from scapy.compat import UserDict 22from typing import ( 23 cast, 24 Any, 25 DefaultDict, 26 Dict, 27 List, 28 NoReturn, 29 Optional, 30 Tuple, 31 Type, 32 Union, 33) 34 35 36class InterfaceProvider(object): 37 name = "Unknown" 38 headers: Tuple[str, ...] = ("Index", "Name", "MAC", "IPv4", "IPv6") 39 header_sort = 1 40 libpcap = False 41 42 def load(self): 43 # type: () -> Dict[str, NetworkInterface] 44 """Returns a dictionary of the loaded interfaces, by their 45 name.""" 46 raise NotImplementedError 47 48 def reload(self): 49 # type: () -> Dict[str, NetworkInterface] 50 """Same than load() but for reloads. By default calls load""" 51 return self.load() 52 53 def _l2socket(self, dev): 54 # type: (NetworkInterface) -> Type[scapy.supersocket.SuperSocket] 55 """Return L2 socket used by interfaces of this provider""" 56 return conf.L2socket 57 58 def _l2listen(self, dev): 59 # type: (NetworkInterface) -> Type[scapy.supersocket.SuperSocket] 60 """Return L2listen socket used by interfaces of this provider""" 61 return conf.L2listen 62 63 def _l3socket(self, dev, ipv6): 64 # type: (NetworkInterface, bool) -> Type[scapy.supersocket.SuperSocket] 65 """Return L3 socket used by interfaces of this provider""" 66 if LINUX and not self.libpcap and dev.name == conf.loopback_name: 67 # handle the loopback case. see troubleshooting.rst 68 if ipv6: 69 from scapy.supersocket import L3RawSocket6 70 return cast(Type['scapy.supersocket.SuperSocket'], L3RawSocket6) 71 else: 72 from scapy.supersocket import L3RawSocket 73 return L3RawSocket 74 return conf.L3socket 75 76 def _is_valid(self, dev): 77 # type: (NetworkInterface) -> bool 78 """Returns whether an interface is valid or not""" 79 return bool((dev.ips[4] or dev.ips[6]) and dev.mac) 80 81 def _format(self, 82 dev, # type: NetworkInterface 83 **kwargs # type: Any 84 ): 85 # type: (...) -> Tuple[Union[str, List[str]], ...] 86 """Returns the elements used by show() 87 88 If a tuple is returned, this consist of the strings that will be 89 inlined along with the interface. 90 If a list of tuples is returned, they will be appended one above the 91 other and should all be part of a single interface. 92 """ 93 mac = dev.mac 94 resolve_mac = kwargs.get("resolve_mac", True) 95 if resolve_mac and conf.manufdb and mac: 96 mac = conf.manufdb._resolve_MAC(mac) 97 index = str(dev.index) 98 return (index, dev.description, mac or "", dev.ips[4], dev.ips[6]) 99 100 def __repr__(self) -> str: 101 """ 102 repr 103 """ 104 return "<InterfaceProvider: %s>" % self.name 105 106 107class NetworkInterface(object): 108 def __init__(self, 109 provider, # type: InterfaceProvider 110 data=None, # type: Optional[Dict[str, Any]] 111 ): 112 # type: (...) -> None 113 self.provider = provider 114 self.name = "" 115 self.description = "" 116 self.network_name = "" 117 self.index = -1 118 self.ip = None # type: Optional[str] 119 self.ips = defaultdict(list) # type: DefaultDict[int, List[str]] 120 self.type = -1 121 self.mac = None # type: Optional[str] 122 self.dummy = False 123 if data is not None: 124 self.update(data) 125 126 def update(self, data): 127 # type: (Dict[str, Any]) -> None 128 """Update info about a network interface according 129 to a given dictionary. Such data is provided by providers 130 """ 131 self.name = data.get('name', "") 132 self.description = data.get('description', "") 133 self.network_name = data.get('network_name', "") 134 self.index = data.get('index', 0) 135 self.ip = data.get('ip', "") 136 self.type = data.get('type', -1) 137 self.mac = data.get('mac', "") 138 self.flags = data.get('flags', 0) 139 self.dummy = data.get('dummy', False) 140 141 for ip in data.get('ips', []): 142 if in6_isvalid(ip): 143 self.ips[6].append(ip) 144 else: 145 self.ips[4].append(ip) 146 147 # An interface often has multiple IPv6 so we don't store 148 # a "main" one, unlike IPv4. 149 if self.ips[4] and not self.ip: 150 self.ip = self.ips[4][0] 151 152 def __eq__(self, other): 153 # type: (Any) -> bool 154 if isinstance(other, str): 155 return other in [self.name, self.network_name, self.description] 156 if isinstance(other, NetworkInterface): 157 return self.__dict__ == other.__dict__ 158 return False 159 160 def __ne__(self, other): 161 # type: (Any) -> bool 162 return not self.__eq__(other) 163 164 def __hash__(self): 165 # type: () -> int 166 return hash(self.network_name) 167 168 def is_valid(self): 169 # type: () -> bool 170 if self.dummy: 171 return False 172 return self.provider._is_valid(self) 173 174 def l2socket(self): 175 # type: () -> Type[scapy.supersocket.SuperSocket] 176 return self.provider._l2socket(self) 177 178 def l2listen(self): 179 # type: () -> Type[scapy.supersocket.SuperSocket] 180 return self.provider._l2listen(self) 181 182 def l3socket(self, ipv6=False): 183 # type: (bool) -> Type[scapy.supersocket.SuperSocket] 184 return self.provider._l3socket(self, ipv6) 185 186 def __repr__(self): 187 # type: () -> str 188 return "<%s %s [%s]>" % (self.__class__.__name__, 189 self.description, 190 self.dummy and "dummy" or (self.flags or "")) 191 192 def __str__(self): 193 # type: () -> str 194 return self.network_name 195 196 def __add__(self, other): 197 # type: (str) -> str 198 return self.network_name + other 199 200 def __radd__(self, other): 201 # type: (str) -> str 202 return other + self.network_name 203 204 205_GlobInterfaceType = Union[NetworkInterface, str] 206 207 208class NetworkInterfaceDict(UserDict[str, NetworkInterface]): 209 """Store information about network interfaces and convert between names""" 210 211 def __init__(self): 212 # type: () -> None 213 self.providers = {} # type: Dict[Type[InterfaceProvider], InterfaceProvider] # noqa: E501 214 super(NetworkInterfaceDict, self).__init__() 215 216 def _load(self, 217 dat, # type: Dict[str, NetworkInterface] 218 prov, # type: InterfaceProvider 219 ): 220 # type: (...) -> None 221 for ifname, iface in dat.items(): 222 if ifname in self.data: 223 # Handle priorities: keep except if libpcap 224 if prov.libpcap: 225 self.data[ifname] = iface 226 else: 227 self.data[ifname] = iface 228 229 def register_provider(self, provider): 230 # type: (type) -> None 231 prov = provider() 232 self.providers[provider] = prov 233 if self.data: 234 # late registration 235 self._load(prov.reload(), prov) 236 237 def load_confiface(self): 238 # type: () -> None 239 """ 240 Reload conf.iface 241 """ 242 # Can only be called after conf.route is populated 243 if not conf.route: 244 raise ValueError("Error: conf.route isn't populated !") 245 conf.iface = get_working_if() # type: ignore 246 247 def _reload_provs(self): 248 # type: () -> None 249 self.clear() 250 for prov in self.providers.values(): 251 self._load(prov.reload(), prov) 252 253 def reload(self): 254 # type: () -> None 255 self._reload_provs() 256 if not conf.route: 257 # routes are not loaded yet. 258 return 259 self.load_confiface() 260 261 def dev_from_name(self, name): 262 # type: (str) -> NetworkInterface 263 """Return the first network device name for a given 264 device name. 265 """ 266 try: 267 return next(iface for iface in self.values() 268 if (iface.name == name or iface.description == name)) 269 except (StopIteration, RuntimeError): 270 raise ValueError("Unknown network interface %r" % name) 271 272 def dev_from_networkname(self, network_name): 273 # type: (str) -> NoReturn 274 """Return interface for a given network device name.""" 275 try: 276 return next(iface for iface in self.values() # type: ignore 277 if iface.network_name == network_name) 278 except (StopIteration, RuntimeError): 279 raise ValueError( 280 "Unknown network interface %r" % 281 network_name) 282 283 def dev_from_index(self, if_index): 284 # type: (int) -> NetworkInterface 285 """Return interface name from interface index""" 286 try: 287 if_index = int(if_index) # Backward compatibility 288 return next(iface for iface in self.values() 289 if iface.index == if_index) 290 except (StopIteration, RuntimeError): 291 if str(if_index) == "1": 292 # Test if the loopback interface is set up 293 return self.dev_from_networkname(conf.loopback_name) 294 raise ValueError("Unknown network interface index %r" % if_index) 295 296 def _add_fake_iface(self, ifname, mac="00:00:00:00:00:00"): 297 # type: (str, str) -> None 298 """Internal function used for a testing purpose""" 299 data = { 300 'name': ifname, 301 'description': ifname, 302 'network_name': ifname, 303 'index': -1000, 304 'dummy': True, 305 'mac': mac, 306 'flags': 0, 307 'ips': ["127.0.0.1", "::"], 308 # Windows only 309 'guid': "{%s}" % uuid.uuid1(), 310 'ipv4_metric': 0, 311 'ipv6_metric': 0, 312 'nameservers': [], 313 } 314 if WINDOWS: 315 from scapy.arch.windows import NetworkInterface_Win, \ 316 WindowsInterfacesProvider 317 318 class FakeProv(WindowsInterfacesProvider): 319 name = "fake" 320 321 self.data[ifname] = NetworkInterface_Win( 322 FakeProv(), 323 data 324 ) 325 else: 326 self.data[ifname] = NetworkInterface(InterfaceProvider(), data) 327 328 def show(self, print_result=True, hidden=False, **kwargs): 329 # type: (bool, bool, **Any) -> Optional[str] 330 """ 331 Print list of available network interfaces in human readable form 332 333 :param print_result: print the results if True, else return it 334 :param hidden: if True, also displays invalid interfaces 335 """ 336 res = defaultdict(list) 337 for iface_name in sorted(self.data): 338 dev = self.data[iface_name] 339 if not hidden and not dev.is_valid(): 340 continue 341 prov = dev.provider 342 res[(prov.headers, prov.header_sort)].append( 343 (prov.name,) + prov._format(dev, **kwargs) 344 ) 345 output = "" 346 for key in res: 347 hdrs, sortBy = key 348 output += pretty_list( 349 res[key], 350 [("Source",) + hdrs], 351 sortBy=sortBy 352 ) + "\n" 353 output = output[:-1] 354 if print_result: 355 print(output) 356 return None 357 else: 358 return output 359 360 def __repr__(self): 361 # type: () -> str 362 return self.show(print_result=False) # type: ignore 363 364 365conf.ifaces = IFACES = ifaces = NetworkInterfaceDict() 366 367 368def get_if_list(): 369 # type: () -> List[str] 370 """Return a list of interface names""" 371 return list(conf.ifaces.keys()) 372 373 374def get_working_if(): 375 # type: () -> Optional[NetworkInterface] 376 """Return an interface that works""" 377 # return the interface associated with the route with smallest 378 # mask (route by default if it exists) 379 routes = conf.route.routes[:] 380 routes.sort(key=lambda x: x[1]) 381 ifaces = (x[3] for x in routes) 382 # First check the routing ifaces from best to worse, 383 # then check all the available ifaces as backup. 384 for ifname in itertools.chain(ifaces, conf.ifaces.values()): 385 try: 386 iface = conf.ifaces.dev_from_networkname(ifname) # type: ignore 387 if iface.is_valid(): 388 return iface 389 except ValueError: 390 pass 391 # There is no hope left 392 try: 393 return conf.ifaces.dev_from_networkname(conf.loopback_name) 394 except ValueError: 395 return None 396 397 398def get_working_ifaces(): 399 # type: () -> List[NetworkInterface] 400 """Return all interfaces that work""" 401 return [iface for iface in conf.ifaces.values() if iface.is_valid()] 402 403 404def dev_from_networkname(network_name): 405 # type: (str) -> NetworkInterface 406 """Return Scapy device name for given network device name""" 407 return conf.ifaces.dev_from_networkname(network_name) 408 409 410def dev_from_index(if_index): 411 # type: (int) -> NetworkInterface 412 """Return interface for a given interface index""" 413 return conf.ifaces.dev_from_index(if_index) 414 415 416def resolve_iface(dev, retry=True): 417 # type: (_GlobInterfaceType, bool) -> NetworkInterface 418 """ 419 Resolve an interface name into the interface 420 """ 421 if isinstance(dev, NetworkInterface): 422 return dev 423 try: 424 return conf.ifaces.dev_from_name(dev) 425 except ValueError: 426 try: 427 return conf.ifaces.dev_from_networkname(dev) 428 except ValueError: 429 pass 430 if not retry: 431 raise ValueError("Interface '%s' not found !" % dev) 432 # Nothing found yet. Reload to detect if it was added recently 433 conf.ifaces.reload() 434 return resolve_iface(dev, retry=False) 435 436 437def network_name(dev): 438 # type: (_GlobInterfaceType) -> str 439 """ 440 Resolves the device network name of a device or Scapy NetworkInterface 441 """ 442 return resolve_iface(dev).network_name 443 444 445def show_interfaces(resolve_mac=True): 446 # type: (bool) -> None 447 """Print list of available network interfaces""" 448 return conf.ifaces.show(resolve_mac) # type: ignore 449