File: ppp.py

package info (click to toggle)
python-dpkt 1.9.8-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,104 kB
  • sloc: python: 14,911; makefile: 23
file content (188 lines) | stat: -rw-r--r-- 4,674 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# $Id: ppp.py 65 2010-03-26 02:53:51Z dugsong $
# -*- coding: utf-8 -*-
"""Point-to-Point Protocol."""
from __future__ import absolute_import

import struct

from . import dpkt

# XXX - finish later

# http://www.iana.org/assignments/ppp-numbers
PPP_IP = 0x21  # Internet Protocol
PPP_IP6 = 0x57  # Internet Protocol v6

# Protocol field compression
PFC_BIT = 0x01


class PPP(dpkt.Packet):
    """Point-to-Point Protocol.

    Point-to-Point Protocol (PPP) is a data link layer (layer 2) communication protocol between two routers directly
    without any host or any other networking in between. It can provide connection authentication, transmission
    encryption and data compression.

    Note: This class is subclassed in PPPoE

    Attributes:
        __hdr__: Header fields of PPP.
            addr: (int): Address. 0xFF, standard broadcast address. (1 byte)
            cntrl: (int): Control. 0x03, unnumbered data. (1 byte)
            p: (int): Protocol. PPP ID of embedded data. (1 byte)
    """

    __hdr__ = (
        ('addr', 'B', 0xff),
        ('cntrl', 'B', 3),
        ('p', 'B', PPP_IP),
    )
    _protosw = {}

    @classmethod
    def set_p(cls, p, pktclass):
        cls._protosw[p] = pktclass

    @classmethod
    def get_p(cls, p):
        return cls._protosw[p]

    def unpack(self, buf):
        dpkt.Packet.unpack(self, buf)
        if self.p & PFC_BIT == 0:
            try:
                self.p = struct.unpack('>H', buf[2:4])[0]
            except struct.error:
                raise dpkt.NeedData
            self.data = self.data[1:]
        try:
            self.data = self._protosw[self.p](self.data)
            setattr(self, self.data.__class__.__name__.lower(), self.data)
        except (KeyError, struct.error, dpkt.UnpackError):
            pass

    def pack_hdr(self):
        try:
            if self.p > 0xff:
                return struct.pack('>BBH', self.addr, self.cntrl, self.p)
            return dpkt.Packet.pack_hdr(self)
        except struct.error as e:
            raise dpkt.PackError(str(e))


def __load_protos():
    g = globals()
    for k, v in g.items():
        if k.startswith('PPP_'):
            name = k[4:]
            modname = name.lower()
            try:
                mod = __import__(modname, g, level=1)
                PPP.set_p(v, getattr(mod, name))
            except (ImportError, AttributeError):
                continue


def _mod_init():
    """Post-initialization called when all dpkt modules are fully loaded"""
    if not PPP._protosw:
        __load_protos()


def test_ppp():
    # Test protocol compression
    s = b"\xff\x03\x21"
    p = PPP(s)
    assert p.p == 0x21

    s = b"\xff\x03\x00\x21"
    p = PPP(s)
    assert p.p == 0x21


def test_ppp_short():
    s = b"\xff\x03\x00"

    import pytest
    pytest.raises(dpkt.NeedData, PPP, s)


def test_packing():
    p = PPP()
    assert p.pack_hdr() == b"\xff\x03\x21"

    p.p = 0xc021  # LCP
    assert p.pack_hdr() == b"\xff\x03\xc0\x21"


def test_ppp_classmethods():
    import pytest

    class TestProto(dpkt.Packet):
        pass

    proto_number = 123

    # asserting that this proto is not currently added
    with pytest.raises(KeyError):
        PPP.get_p(proto_number)

    PPP.set_p(proto_number, TestProto)

    assert PPP.get_p(proto_number) == TestProto

    # we need to reset the class, or impact other tests
    del PPP._protosw[proto_number]


def test_unpacking_exceptions():
    from dpkt import ip

    from binascii import unhexlify
    buf_ppp = unhexlify(
        'ff'  # addr
        '03'  # cntrl
        '21'  # p (PPP_IP)
    )
    buf_ip = unhexlify(
        '45'    # _v_hl
        '00'    # tos
        '0014'  # len
        '0000'  # id
        '0000'  # off
        '80'    # ttl
        '06'    # p
        'd47e'  # sum
        '11111111'  # src
        '22222222'  # dst
    )

    buf = buf_ppp + buf_ip
    ppp = PPP(buf)
    assert hasattr(ppp, 'ip')
    assert isinstance(ppp.data, ip.IP)
    assert bytes(ppp) == buf


def test_ppp_packing_error():
    import pytest

    # addr is a 1-byte field, so this will overflow when packing
    ppp = PPP(p=257, addr=1234)
    with pytest.raises(dpkt.PackError):
        ppp.pack_hdr()


def test_proto_loading():
    # test that failure to load protocol handlers isn't catastrophic
    standard_protos = PPP._protosw
    # delete existing protos
    PPP._protosw = {}
    assert not PPP._protosw

    # inject a new global variable to be picked up
    globals()['PPP_NON_EXISTENT_PROTO'] = "FAIL"
    _mod_init()
    # we should get the same answer as if NON_EXISTENT_PROTO didn't exist
    assert PPP._protosw == standard_protos