1# This file is part of Scapy 2# See http://www.secdev.org/projects/scapy for more information 3# Copyright (C) Philippe Biondi <phil@secdev.org> 4# This program is published under a GPLv2 license 5 6""" 7Generators and packet meta classes. 8""" 9 10################ 11# Generators # 12################ 13 14from __future__ import absolute_import 15 16from functools import reduce 17import operator 18import os 19import random 20import re 21import socket 22import struct 23import subprocess 24import types 25import warnings 26 27import scapy 28from scapy.error import Scapy_Exception 29from scapy.consts import WINDOWS 30import scapy.modules.six as six 31 32from scapy.modules.six.moves import range 33 34from scapy.compat import ( 35 Any, 36 Dict, 37 Generic, 38 Iterator, 39 List, 40 Optional, 41 Tuple, 42 Type, 43 TypeVar, 44 Union, 45 _Generic_metaclass, 46 cast, 47) 48 49try: 50 import pyx 51except ImportError: 52 pass 53 54_T = TypeVar("_T") 55 56 57@six.add_metaclass(_Generic_metaclass) 58class Gen(Generic[_T]): 59 __slots__ = [] # type: List[str] 60 61 def __iter__(self): 62 # type: () -> Iterator[_T] 63 return iter([]) 64 65 def __iterlen__(self): 66 # type: () -> int 67 return sum(1 for _ in iter(self)) 68 69 70def _get_values(value): 71 # type: (Any) -> Any 72 """Generate a range object from (start, stop[, step]) tuples, or 73 return value. 74 75 """ 76 if (isinstance(value, tuple) and (2 <= len(value) <= 3) and 77 all(hasattr(i, "__int__") for i in value)): 78 # We use values[1] + 1 as stop value for (x)range to maintain 79 # the behavior of using tuples as field `values` 80 return range(*((int(value[0]), int(value[1]) + 1) + 81 tuple(int(v) for v in value[2:]))) 82 return value 83 84 85class SetGen(Gen[_T]): 86 def __init__(self, values, _iterpacket=1): 87 # type: (Any, int) -> None 88 self._iterpacket = _iterpacket 89 if isinstance(values, (list, BasePacketList)): 90 self.values = [_get_values(val) for val in values] 91 else: 92 self.values = [_get_values(values)] 93 94 def __iter__(self): 95 # type: () -> Iterator[Any] 96 for i in self.values: 97 if (isinstance(i, Gen) and 98 (self._iterpacket or not isinstance(i, BasePacket))) or ( 99 isinstance(i, (range, types.GeneratorType))): 100 for j in i: 101 yield j 102 else: 103 yield i 104 105 def __len__(self): 106 # type: () -> int 107 return self.__iterlen__() 108 109 def __repr__(self): 110 # type: () -> str 111 return "<SetGen %r>" % self.values 112 113 114class Net(Gen[str]): 115 """Network object from an IP address or hostname and mask""" 116 name = "Net" # type: str 117 family = socket.AF_INET # type: int 118 max_mask = 32 # type: int 119 120 @classmethod 121 def name2addr(cls, name): 122 # type: (str) -> str 123 try: 124 return next( 125 addr_port[0] 126 for family, _, _, _, addr_port in 127 socket.getaddrinfo(name, None, cls.family) 128 if family == cls.family 129 ) 130 except socket.error: 131 if re.search("(^|\\.)[0-9]+-[0-9]+($|\\.)", name) is not None: 132 raise Scapy_Exception("Ranges are no longer accepted in %s()" % 133 cls.__name__) 134 raise 135 136 @classmethod 137 def ip2int(cls, addr): 138 # type: (str) -> int 139 return cast(int, struct.unpack( 140 "!I", socket.inet_aton(cls.name2addr(addr)) 141 )[0]) 142 143 @staticmethod 144 def int2ip(val): 145 # type: (int) -> str 146 return socket.inet_ntoa(struct.pack('!I', val)) 147 148 def __init__(self, net, stop=None): 149 # type: (str, Union[None, str]) -> None 150 if "*" in net: 151 raise Scapy_Exception("Wildcards are no longer accepted in %s()" % 152 self.__class__.__name__) 153 if stop is None: 154 try: 155 net, mask = net.split("/", 1) 156 except ValueError: 157 self.mask = self.max_mask # type: Union[None, int] 158 else: 159 self.mask = int(mask) 160 self.net = net # type: Union[None, str] 161 inv_mask = self.max_mask - self.mask 162 self.start = self.ip2int(net) >> inv_mask << inv_mask 163 self.count = 1 << inv_mask 164 self.stop = self.start + self.count - 1 165 else: 166 self.start = self.ip2int(net) 167 self.stop = self.ip2int(stop) 168 self.count = self.stop - self.start + 1 169 self.net = self.mask = None 170 171 def __str__(self): 172 # type: () -> str 173 return next(iter(self), "") 174 175 def __iter__(self): 176 # type: () -> Iterator[str] 177 # Python 2 won't handle huge (> sys.maxint) values in range() 178 for i in range(self.count): 179 yield self.int2ip(self.start + i) 180 181 def __len__(self): 182 # type: () -> int 183 return self.count 184 185 def __iterlen__(self): 186 # type: () -> int 187 # for compatibility 188 return len(self) 189 190 def choice(self): 191 # type: () -> str 192 return self.int2ip(random.randint(self.start, self.stop)) 193 194 def __repr__(self): 195 # type: () -> str 196 if self.mask is not None: 197 return '%s("%s/%d")' % ( 198 self.__class__.__name__, 199 self.net, 200 self.mask, 201 ) 202 return '%s("%s", "%s")' % ( 203 self.__class__.__name__, 204 self.int2ip(self.start), 205 self.int2ip(self.stop), 206 ) 207 208 def __eq__(self, other): 209 # type: (Any) -> bool 210 if isinstance(other, str): 211 return self == self.__class__(other) 212 if not isinstance(other, Net): 213 return False 214 if self.family != other.family: 215 return False 216 return (self.start == other.start) and (self.stop == other.stop) 217 218 def __ne__(self, other): 219 # type: (Any) -> bool 220 # Python 2.7 compat 221 return not self == other 222 223 def __hash__(self): 224 # type: () -> int 225 return hash(("scapy.Net", self.family, self.start, self.stop)) 226 227 def __contains__(self, other): 228 # type: (Any) -> bool 229 if isinstance(other, int): 230 return self.start <= other <= self.stop 231 if isinstance(other, str): 232 return self.__class__(other) in self 233 if type(other) is not self.__class__: 234 return False 235 return cast( 236 bool, 237 (self.start <= other.start <= other.stop <= self.stop), 238 ) 239 240 241class OID(Gen[str]): 242 name = "OID" 243 244 def __init__(self, oid): 245 # type: (str) -> None 246 self.oid = oid 247 self.cmpt = [] 248 fmt = [] 249 for i in oid.split("."): 250 if "-" in i: 251 fmt.append("%i") 252 self.cmpt.append(tuple(map(int, i.split("-")))) 253 else: 254 fmt.append(i) 255 self.fmt = ".".join(fmt) 256 257 def __repr__(self): 258 # type: () -> str 259 return "OID(%r)" % self.oid 260 261 def __iter__(self): 262 # type: () -> Iterator[str] 263 ii = [k[0] for k in self.cmpt] 264 while True: 265 yield self.fmt % tuple(ii) 266 i = 0 267 while True: 268 if i >= len(ii): 269 return 270 if ii[i] < self.cmpt[i][1]: 271 ii[i] += 1 272 break 273 else: 274 ii[i] = self.cmpt[i][0] 275 i += 1 276 277 def __iterlen__(self): 278 # type: () -> int 279 return reduce(operator.mul, (max(y - x, 0) + 1 for (x, y) in self.cmpt), 1) # noqa: E501 280 281 282###################################### 283# Packet abstract and base classes # 284###################################### 285 286class Packet_metaclass(_Generic_metaclass): 287 def __new__(cls, # type: ignore 288 name, # type: str 289 bases, # type: Tuple[type, ...] 290 dct # type: Dict[str, Any] 291 ): 292 # type: (...) -> Type['scapy.packet.Packet'] 293 if "fields_desc" in dct: # perform resolution of references to other packets # noqa: E501 294 current_fld = dct["fields_desc"] # type: List[Union['scapy.fields.Field'[Any, Any], Packet_metaclass]] # noqa: E501 295 resolved_fld = [] # type: List['scapy.fields.Field'[Any, Any]] 296 for fld_or_pkt in current_fld: 297 if isinstance(fld_or_pkt, Packet_metaclass): 298 # reference to another fields_desc 299 for pkt_fld in fld_or_pkt.fields_desc: # type: ignore 300 resolved_fld.append(pkt_fld) 301 else: 302 resolved_fld.append(fld_or_pkt) 303 else: # look for a fields_desc in parent classes 304 resolved_fld = [] 305 for b in bases: 306 if hasattr(b, "fields_desc"): 307 resolved_fld = b.fields_desc # type: ignore 308 break 309 310 if resolved_fld: # perform default value replacements 311 final_fld = [] # type: List['scapy.fields.Field'[Any, Any]] 312 names = [] 313 for f in resolved_fld: 314 if f.name in names: 315 war_msg = ( 316 "Packet '%s' has a duplicated '%s' field ! " 317 "If you are using several ConditionalFields, have " 318 "a look at MultipleTypeField instead ! This will " 319 "become a SyntaxError in a future version of " 320 "Scapy !" % ( 321 name, f.name 322 ) 323 ) 324 warnings.warn(war_msg, SyntaxWarning) 325 names.append(f.name) 326 if f.name in dct: 327 f = f.copy() 328 f.default = dct[f.name] 329 del(dct[f.name]) 330 final_fld.append(f) 331 332 dct["fields_desc"] = final_fld 333 334 dct.setdefault("__slots__", []) 335 for attr in ["name", "overload_fields"]: 336 try: 337 dct["_%s" % attr] = dct.pop(attr) 338 except KeyError: 339 pass 340 newcls = type.__new__(cls, name, bases, dct) 341 # Note: below can't be typed because we use attributes 342 # created dynamically.. 343 newcls.__all_slots__ = set( # type: ignore 344 attr 345 for cls in newcls.__mro__ if hasattr(cls, "__slots__") 346 for attr in cls.__slots__ 347 ) 348 349 newcls.aliastypes = ( # type: ignore 350 [newcls] + getattr(newcls, "aliastypes", []) 351 ) 352 353 if hasattr(newcls, "register_variant"): 354 newcls.register_variant() # type: ignore 355 for f in newcls.fields_desc: # type: ignore 356 if hasattr(f, "register_owner"): 357 f.register_owner(newcls) 358 if newcls.__name__[0] != "_": 359 from scapy import config 360 config.conf.layers.register(newcls) 361 return newcls 362 363 def __getattr__(self, attr): 364 # type: (str) -> 'scapy.fields.Field'[Any, Any] 365 for k in self.fields_desc: # type: ignore 366 if k.name == attr: 367 return k # type: ignore 368 raise AttributeError(attr) 369 370 def __call__(cls, 371 *args, # type: Any 372 **kargs # type: Any 373 ): 374 # type: (...) -> 'scapy.packet.Packet' 375 if "dispatch_hook" in cls.__dict__: 376 try: 377 cls = cls.dispatch_hook(*args, **kargs) # type: ignore 378 except Exception: 379 from scapy import config 380 if config.conf.debug_dissector: 381 raise 382 cls = config.conf.raw_layer # type: ignore 383 i = cls.__new__( 384 cls, # type: ignore 385 cls.__name__, 386 cls.__bases__, 387 cls.__dict__ 388 ) 389 i.__init__(*args, **kargs) 390 return i # type: ignore 391 392 393# Note: see compat.py for an explanation 394 395class Field_metaclass(_Generic_metaclass): 396 def __new__(cls, # type: ignore 397 name, # type: str 398 bases, # type: Tuple[type, ...] 399 dct # type: Dict[str, Any] 400 ): 401 # type: (...) -> Type['scapy.fields.Field'[Any, Any]] 402 dct.setdefault("__slots__", []) 403 newcls = super(Field_metaclass, cls).__new__(cls, name, bases, dct) 404 return newcls 405 406 407PacketList_metaclass = Field_metaclass 408 409 410class BasePacket(Gen['scapy.packet.Packet']): 411 __slots__ = [] # type: List[str] 412 413 414############################# 415# Packet list base class # 416############################# 417 418class BasePacketList(Gen[_T]): 419 __slots__ = [] # type: List[str] 420 421 422class _CanvasDumpExtended(object): 423 def canvas_dump(self, **kwargs): 424 # type: (**Any) -> 'pyx.canvas.canvas' 425 pass 426 427 def psdump(self, filename=None, **kargs): 428 # type: (Optional[str], **Any) -> None 429 """ 430 psdump(filename=None, layer_shift=0, rebuild=1) 431 432 Creates an EPS file describing a packet. If filename is not provided a 433 temporary file is created and gs is called. 434 435 :param filename: the file's filename 436 """ 437 from scapy.config import conf 438 from scapy.utils import get_temp_file, ContextManagerSubprocess 439 canvas = self.canvas_dump(**kargs) 440 if filename is None: 441 fname = get_temp_file(autoext=kargs.get("suffix", ".eps")) 442 canvas.writeEPSfile(fname) 443 if WINDOWS and conf.prog.psreader is None: 444 os.startfile(fname) 445 else: 446 with ContextManagerSubprocess(conf.prog.psreader): 447 subprocess.Popen([conf.prog.psreader, fname]) 448 else: 449 canvas.writeEPSfile(filename) 450 print() 451 452 def pdfdump(self, filename=None, **kargs): 453 # type: (Optional[str], **Any) -> None 454 """ 455 pdfdump(filename=None, layer_shift=0, rebuild=1) 456 457 Creates a PDF file describing a packet. If filename is not provided a 458 temporary file is created and xpdf is called. 459 460 :param filename: the file's filename 461 """ 462 from scapy.config import conf 463 from scapy.utils import get_temp_file, ContextManagerSubprocess 464 canvas = self.canvas_dump(**kargs) 465 if filename is None: 466 fname = get_temp_file(autoext=kargs.get("suffix", ".pdf")) 467 canvas.writePDFfile(fname) 468 if WINDOWS and conf.prog.pdfreader is None: 469 os.startfile(fname) 470 else: 471 with ContextManagerSubprocess(conf.prog.pdfreader): 472 subprocess.Popen([conf.prog.pdfreader, fname]) 473 else: 474 canvas.writePDFfile(filename) 475 print() 476 477 def svgdump(self, filename=None, **kargs): 478 # type: (Optional[str], **Any) -> None 479 """ 480 svgdump(filename=None, layer_shift=0, rebuild=1) 481 482 Creates an SVG file describing a packet. If filename is not provided a 483 temporary file is created and gs is called. 484 485 :param filename: the file's filename 486 """ 487 from scapy.config import conf 488 from scapy.utils import get_temp_file, ContextManagerSubprocess 489 canvas = self.canvas_dump(**kargs) 490 if filename is None: 491 fname = get_temp_file(autoext=kargs.get("suffix", ".svg")) 492 canvas.writeSVGfile(fname) 493 if WINDOWS and conf.prog.svgreader is None: 494 os.startfile(fname) 495 else: 496 with ContextManagerSubprocess(conf.prog.svgreader): 497 subprocess.Popen([conf.prog.svgreader, fname]) 498 else: 499 canvas.writeSVGfile(filename) 500 print() 501