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