Passed
Pull Request — master (#371)
by
unknown
02:20
created

IPv4.pack()   A

Complexity

Conditions 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 20
ccs 0
cts 0
cp 0
rs 9.4285
cc 2
crap 6
1
"""Basic Network packet types.
2
3
Defines and Implements Basic Network packet types , such as Ethertnet and LLDP.
4
"""
5
6 1
from pyof.foundation.base import GenericStruct
7 1
from pyof.foundation.basic_types import (
8 1
    BinaryData, HWAddress, IPAddress, UBInt8, UBInt16)
9
from pyof.foundation.exceptions import PackException
10 1
11
__all__ = ('Ethernet', 'GenericTLV', 'IPv4', 'TLVWithSubType', 'LLDP')
12
13 1
14
class Ethernet(GenericStruct):
15
    """Ethernet "struct".
16
17
    Objects of this class represents an ethernet packet. It contains the
18
    'Ethernet header', composed by destination (MAC), source (MAC), type
19
    (EtherType)[1] and the payload of the packet, as binary data.
20
21
    This class does not consider the Ethernet 'Preamble' or the 'CRC'.
22
23
    There is also a get_hash method, that hashes the binary representation of
24
    the object so we can have a unique representation of the ethernet packet,
25
    so we can keep a track of ethernet packets being flooded over the network.
26
27
    [1] EtherTypes:
28
    http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml#ieee-802-numbers-1
29
    """
30
31 1
    #: destination (:class:`HWAddress`): The final destination MAC address.
32
    destination = HWAddress()
33 1
    #: source (:class:`HWAddress`): The source MAC address of the packet.
34
    source = HWAddress()
35 1
    #: ether_type (:class:`UBInt16`): The EtherType of the packet.
36
    ether_type = UBInt16()
37 1
    #: data (:class:`BinaryData`): The content of the packet in binary format.
38
    data = BinaryData()
39 1
40
    def __init__(self, destination=None, source=None, ether_type=None,
41
                 data=b''):
42
        """Create an instance and set its attributes."""
43
        super().__init__()
44
        self.destination = destination
45
        self.source = source
46
        self.ether_type = ether_type
47
        self.data = data
48 1
49
    def get_hash(self):
50
        """Return a hash that identifies this instance."""
51
        return hash(self.pack())
52
53 1
54
class GenericTLV(GenericStruct):
55
    """TLV structure of LLDP packets.
56
57
    This is a Type, Length and Value (TLV) struct.
58
59
    The LLDP/TLV definition states that the Type field have 7 bits, while
60
    the length have 9 bits. The Value must be between 0-511 octets.
61
62
    Internally, on the instances of this class, the Type is a integer
63
    (0-127) and the Length is dynamically calculated based on the current
64
    type and value.
65
    """
66 1
67
    def __init__(self, tlv_type=127, value=BinaryData()):
68 1
        """Create an instance and set its attributes."""
69 1
        super().__init__()
70 1
        self.tlv_type = tlv_type
71
        self._value = value
72 1
73
    @property
74
    def value(self):
75 1
        """Return the value stored by GenericTLV."""
76
        return self._value
77 1
78
    @property
79
    def length(self):
80 1
        """Struct length in bytes."""
81
        return len(self.value.pack())
82 1
83
    @property
84
    def header(self):
85
        """Header of the TLV Packet.
86
87
        The header is composed by the Type (7 bits) and Length (9 bits),
88
        summing up 16 bits. To achieve that, we need to do some bitshift
89
        operations.
90 1
        """
91
        return UBInt16(((self.tlv_type & 127) << 9) | (self.length & 511))
92 1
93
    def pack(self, value=None):
94
        """Pack the TLV in a binary representation.
95
96
        Returns:
97
            bytes: Binary representation of the struct object.
98
99
        Raises:
100
            :exc:`~.exceptions.ValidationError`: If validation fails.
101 1
        """
102 1
        if value is None:
103 1
            output = self.header.pack()
104 1
            output += self.value.pack()
105
            return output
106
107
        elif isinstance(value, type(self)):
108
            return value.pack()
109
        else:
110
            msg = "{} is not an instance of {}".format(value,
111
                                                       type(self).__name__)
112
            raise PackException(msg)
113 1
114
    def unpack(self, buffer, offset=0):
115
        """Unpack a binary message into this object's attributes.
116
117
        Unpack the binary value *buff* and update this object attributes based
118
        on the results.
119
120
        Args:
121
            buff (bytes): Binary data package to be unpacked.
122
            offset (int): Where to begin unpacking.
123
124
        Raises:
125
            Exception: If there is a struct unpacking error.
126 1
        """
127 1
        header = UBInt16()
128 1
        header.unpack(buffer[offset:offset+2])
129 1
        self.tlv_type = header.value >> 9
130 1
        length = header.value & 511
131 1
        begin, end = offset + 2, offset + 2 + length
132
        self._value = BinaryData(buffer[begin:end])
133 1
134
    def get_size(self, value=None):
135
        """Return struct size."""
136
        if isinstance(value, type(self)):
137
            return value.get_size()
138
139
        return 2 + self.length
140
141 1
142
class IPv4(GenericStruct):
143
    """IPv4 packet "struct".
144
145
    Contains all fields of an IP version 4 packet header, plus the upper layer
146
    content as binary data.
147
    Some of the fields were merged together because of their size being
148
    inferior to 8 bits.
149 1
    """
150
151 1
    # IP protocol version + Internet Header Length (words)
152 1
    _version_ihl = UBInt8()
153 1
    # Differentiated Services Code Point (ToS - Type of Service) +
154
    # Explicit Congestion Notification
155 1
    _dscp_ecn = UBInt8()
156
    # IP packet length (bytes)
157
    length = UBInt16()
158
    # Packet ID - common to all fragments
159
    identification = UBInt16()
160
    # Fragmentation flags + fragmentation offset
161 1
    _flags_offset = UBInt16()
162
    # Packet time-to-live
163
    ttl = UBInt8()
164
    # Upper layer protocol number
165
    protocol = UBInt8()
166
    # Header checksum
167
    checksum = UBInt16()
168
    # Source address
169
    source = IPAddress()
170
    # Destination address
171
    destination = IPAddress()
172
    # IP Options - up to 320 bits, always padded to 32 bits
173
    options = BinaryData()
174
    # Packet data
175
    data = BinaryData()
176
177
    def __init__(self, version=4, ihl=5, dscp=0, ecn=0, length=0, # noqa
178
                 identification=0, flags=0, offset=0, ttl=255, protocol=0,
179
                 checksum=0, source="0.0.0.0", destination="0.0.0.0",
180
                 options=b'', data=b''):
181
        """Create the Packet and set instance attributes."""
182
        super().__init__()
183
        self.version = version
184
        self.ihl = ihl
185 1
        self.dscp = dscp
186
        self.ecn = ecn
187
        self.length = length
188
        self.identification = identification
189
        self.flags = flags
190
        self.offset = offset
191
        self.ttl = ttl
192
        self.protocol = protocol
193
        self.checksum = checksum
194 1
        self.source = source
195 1
        self.destination = destination
196
        self.options = options
197 1
        self.data = data
198
199
    def _update_checksum(self):
200 1
        """Update the packet checksum to enable integrity check."""
201
        source_list = [int(octet) for octet in self.source.split(".")]
202
        destination_list = [int(octet) for octet in
203
                            self.destination.split(".")]
204
        source_upper = (source_list[0] << 8) + source_list[1]
205
        source_lower = (source_list[2] << 8) + source_list[3]
206
        destination_upper = (destination_list[0] << 8) + destination_list[1]
207
        destination_lower = (destination_list[2] << 8) + destination_list[3]
208
209
        block_sum = ((self._version_ihl << 8 | self._dscp_ecn) + self.length +
210
                     self.identification + self._flags_offset +
211
                     (self.ttl << 8 | self.protocol) + source_upper +
212
                     source_lower + destination_upper + destination_lower)
213
214
        while block_sum > 65535:
215
            carry = block_sum >> 16
216
            block_sum = (block_sum & 65535) + carry
217
218
        self.checksum = ~block_sum & 65535
219
220
    def pack(self, value=None):
221
        """Pack the struct in a binary representation.
222
223
        Merge some fields to ensure correct packing.
224
        """
225
        # Set the correct IHL based on options size
226
        if self.options:
227
            self.ihl += int(len(self.options) / 4)
228
229
        # Set the correct packet length based on header length and data
230
        self.length = int(self.ihl * 4 + len(self.data))
231
232
        self._version_ihl = self.version << 4 | self.ihl
233
        self._dscp_ecn = self.dscp << 2 | self.ecn
234
        self._flags_offset = self.flags << 13 | self.offset
235
236
        # Set the checksum field before packing
237
        self._update_checksum()
238
239
        return super().pack()
240
241
    def unpack(self, buff, offset=0):
242
        """Unpack a binary struct into this object's attributes.
243
244
        Return the values instead of the lib's basic types.
245
        """
246
        super().unpack(buff, offset)
247
248
        self.version = self._version_ihl.value >> 4
249
        self.ihl = self._version_ihl.value & 15
250
        self.dscp = self._dscp_ecn.value >> 2
251
        self.ecn = self._dscp_ecn.value & 3
252
        self.length = self.length.value
253
        self.identification = self.identification.value
254
        self.flags = self._flags_offset.value >> 13
255
        self.offset = self._flags_offset.value & 8191
256
        self.ttl = self.ttl.value
257
        self.protocol = self.protocol.value
258
        self.checksum = self.checksum.value
259
        self.source = self.source.value
260
        self.destination = self.destination.value
261
262
        if self.ihl > 5:
263
            options_size = (self.ihl - 5) * 4
264
            self.data = self.options.value[options_size:]
265
            self.options = self.options.value[:options_size]
266
        else:
267
            self.data = self.options.value
268
            self.options = b''
269
270
271
class TLVWithSubType(GenericTLV):
272
    """Modify the :class:`GenericTLV` to a Organization Specific TLV structure.
273
274
    Beyond the standard TLV (type, length, value), we can also have a more
275
    specific structure, with the *value* field being splitted into a *sub_type*
276
    field and a new "*sub_value*" field.
277
    """
278
279
    def __init__(self, tlv_type=1, sub_type=7, sub_value=BinaryData()):
280
        """Create an instance and set its attributes."""
281
        super().__init__(tlv_type)
282
        self.sub_type = sub_type
283
        self.sub_value = sub_value
284
285
    @property
286
    def value(self):
287
        """Return sub type and sub value as binary data."""
288
        binary = UBInt8(self.sub_type).pack() + self.sub_value.pack()
289
        return BinaryData(binary)
290
291
    def unpack(self, buffer, offset=0):
292
        """Unpack a binary message into this object's attributes.
293
294
        Unpack the binary value *buff* and update this object attributes based
295
        on the results.
296
297
        Args:
298
            buff (bytes): Binary data package to be unpacked.
299
            offset (int): Where to begin unpacking.
300
301
        Raises:
302
            Exception: If there is a struct unpacking error.
303
        """
304
        header = UBInt16()
305
        header.unpack(buffer[offset:offset+2])
306
        self.tlv_type = header.value >> 9
307
        length = header.value & 511
308
        begin, end = offset + 2, offset + 2 + length
309
        sub_type = UBInt8()
310
        sub_type.unpack(buffer[begin:begin+1])
311
        self.sub_type = sub_type.value
312
        self.sub_value = BinaryData(buffer[begin+1:end])
313
314
315
class LLDP(GenericStruct):
316
    """LLDP class.
317
318
    Build a LLDP packet with TLVSubtype and Generic Subtypes.
319
320
    It contains a chassis_id TLV, a port_id TLV, a TTL (Time to live) and
321
    another TVL to represent the end of the LLDP Packet.
322
    """
323
324
    chassis_id = TLVWithSubType(tlv_type=1, sub_type=7)
325
    port_id = TLVWithSubType(tlv_type=2, sub_type=7)
326
    #: TTL time is given in seconds, between 0 and 65535
327
    ttl = GenericTLV(tlv_type=3, value=UBInt16(120))
328
    # We are not using list of tlvs for now
329
    # tlvs = ListOfTLVs()
330
    end = GenericTLV(tlv_type=0)
331