pyof.v0x04.common.flow_match   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 453
Duplicated Lines 0 %

Test Coverage

Coverage 97.09%

Importance

Changes 0
Metric Value
wmc 31
eloc 180
dl 0
loc 453
ccs 167
cts 172
cp 0.9709
rs 9.92
c 0
b 0
f 0

16 Methods

Rating   Name   Duplication   Size   Complexity  
A OxmTLV.unpack() 0 22 2
A Match._complete_last_byte() 0 7 2
A OxmTLV._get_oxm_field_int() 0 19 4
A Match.pack() 0 9 3
A Match._update_match_length() 0 3 1
A OxmTLV.__init__() 0 20 1
A OxmTLV._unpack_oxm_field() 0 16 2
A Match.__init__() 0 17 1
A OxmTLV._update_length() 0 8 1
A Match.get_size() 0 8 3
A ListOfOxmHeader.__init__() 0 8 1
A OxmMatchFields.__init__() 0 7 1
A Match.unpack() 0 8 2
A OxmExperimenterHeader.__init__() 0 10 1
A OxmTLV.pack() 0 19 3
A Match.get_field() 0 18 3
1
"""Match strucutre and related enums.
2
3
An OpenFlow match is composed of a flow match header and a sequence of zero or
4
more flow match fields.
5
"""
6
# System imports
7 1
from enum import Enum, IntEnum
8 1
from math import ceil
9
10
# Local source tree imports
11 1
from pyof.foundation.base import GenericStruct
12 1
from pyof.foundation.basic_types import (
13
    BinaryData, FixedTypeList, Pad, UBInt8, UBInt16, UBInt32)
14 1
from pyof.foundation.exceptions import PackException, UnpackException
15
16 1
__all__ = ('Ipv6ExtHdrFlags', 'ListOfOxmHeader', 'Match', 'MatchType',
17
           'OxmClass', 'OxmExperimenterHeader', 'OxmMatchFields',
18
           'OxmOfbMatchField', 'OxmTLV', 'VlanId')
19
20
21 1
class Ipv6ExtHdrFlags(Enum):
22
    """Bit definitions for IPv6 Extension Header pseudo-field."""
23
24
    #: "No next header" encountered.
25 1
    OFPIEH_NONEXT = 1 << 0
26
    #: Encrypted Sec Payload header present.
27 1
    OFPIEH_ESP = 1 << 1
28
    #: Authentication header present.
29 1
    OFPIEH_AUTH = 1 << 2
30
    #: 1 or 2 dest headers present.
31 1
    OFPIEH_DEST = 1 << 3
32
    #: Fragment header present.
33 1
    OFPIEH_FRAG = 1 << 4
34
    #: Router header present.
35 1
    OFPIEH_ROUTER = 1 << 5
36
    #: Hop-by-hop header present.
37 1
    OFPIEH_HOP = 1 << 6
38
    #: Unexpected repeats encountered.
39 1
    OFPIEH_UNREP = 1 << 7
40
    #: Unexpected sequencing encountered.
41 1
    OFPIEH_UNSEQ = 1 << 8
42
43
44 1
class OxmOfbMatchField(IntEnum):
45
    """OXM Flow match field types for OpenFlow basic class.
46
47
    A switch is not required to support all match field types, just those
48
    listed in the Table 10. Those required match fields don’t need to be
49
    implemented in the same table lookup. The controller can query the switch
50
    about which other fields it supports.
51
    """
52
53
    #: Switch input port.
54 1
    OFPXMT_OFB_IN_PORT = 0
55
    #: Switch physical input port.
56 1
    OFPXMT_OFB_IN_PHY_PORT = 1
57
    #: Metadata passed between tables.
58 1
    OFPXMT_OFB_METADATA = 2
59
    #: Ethernet destination address.
60 1
    OFPXMT_OFB_ETH_DST = 3
61
    #: Ethernet source address.
62 1
    OFPXMT_OFB_ETH_SRC = 4
63
    #: Ethernet frame type.
64 1
    OFPXMT_OFB_ETH_TYPE = 5
65
    #: VLAN id.
66 1
    OFPXMT_OFB_VLAN_VID = 6
67
    #: VLAN priority.
68 1
    OFPXMT_OFB_VLAN_PCP = 7
69
    #: IP DSCP (6 bits in ToS field).
70 1
    OFPXMT_OFB_IP_DSCP = 8
71
    #: IP ECN (2 bits in ToS field).
72 1
    OFPXMT_OFB_IP_ECN = 9
73
    #: IP protocol.
74 1
    OFPXMT_OFB_IP_PROTO = 10
75
    #: IPv4 source address.
76 1
    OFPXMT_OFB_IPV4_SRC = 11
77
    #: IPv4 destination address.
78 1
    OFPXMT_OFB_IPV4_DST = 12
79
    #: TCP source port.
80 1
    OFPXMT_OFB_TCP_SRC = 13
81
    #: TCP destination port.
82 1
    OFPXMT_OFB_TCP_DST = 14
83
    #: UDP source port.
84 1
    OFPXMT_OFB_UDP_SRC = 15
85
    #: UDP destination port.
86 1
    OFPXMT_OFB_UDP_DST = 16
87
    #: SCTP source port.
88 1
    OFPXMT_OFB_SCTP_SRC = 17
89
    #: SCTP destination port.
90 1
    OFPXMT_OFB_SCTP_DST = 18
91
    #: ICMP type.
92 1
    OFPXMT_OFB_ICMPV4_TYPE = 19
93
    #: ICMP code.
94 1
    OFPXMT_OFB_ICMPV4_CODE = 20
95
    #: ARP opcode.
96 1
    OFPXMT_OFB_ARP_OP = 21
97
    #: ARP source IPv4 address.
98 1
    OFPXMT_OFB_ARP_SPA = 22
99
    #: ARP target IPv4 address.
100 1
    OFPXMT_OFB_ARP_TPA = 23
101
    #: ARP source hardware address.
102 1
    OFPXMT_OFB_ARP_SHA = 24
103
    #: ARP target hardware address.
104 1
    OFPXMT_OFB_ARP_THA = 25
105
    #: IPv6 source address.
106 1
    OFPXMT_OFB_IPV6_SRC = 26
107
    #: IPv6 destination address.
108 1
    OFPXMT_OFB_IPV6_DST = 27
109
    #: IPv6 Flow Label
110 1
    OFPXMT_OFB_IPV6_FLABEL = 28
111
    #: ICMPv6 type.
112 1
    OFPXMT_OFB_ICMPV6_TYPE = 29
113
    #: ICMPv6 code.
114 1
    OFPXMT_OFB_ICMPV6_CODE = 30
115
    #: Target address for ND.
116 1
    OFPXMT_OFB_IPV6_ND_TARGET = 31
117
    #: Source link-layer for ND.
118 1
    OFPXMT_OFB_IPV6_ND_SLL = 32
119
    #: Target link-layer for ND.
120 1
    OFPXMT_OFB_IPV6_ND_TLL = 33
121
    #: MPLS label.
122 1
    OFPXMT_OFB_MPLS_LABEL = 34
123
    #: MPLS TC.
124 1
    OFPXMT_OFB_MPLS_TC = 35
125
    #: MPLS BoS bit.
126 1
    OFPXMT_OFP_MPLS_BOS = 36
127
    #: PBB I-SID.
128 1
    OFPXMT_OFB_PBB_ISID = 37
129
    #: Logical Port Metadata.
130 1
    OFPXMT_OFB_TUNNEL_ID = 38
131
    #: IPv6 Extension Header pseudo-field
132 1
    OFPXMT_OFB_IPV6_EXTHDR = 39
133
134
135 1
class MatchType(IntEnum):
136
    """Indicates the match structure in use.
137
138
    The match type is placed in the type field at the beginning of all match
139
    structures. The "OpenFlow Extensible Match" type corresponds to OXM TLV
140
    format described below and must be supported by all OpenFlow switches.
141
    Extensions that define other match types may be published on the ONF wiki.
142
    Support for extensions is optional
143
    """
144
145
    #: Deprecated
146 1
    OFPMT_STANDARD = 0
147
    #: OpenFlow Extensible Match
148 1
    OFPMT_OXM = 1
149
150
151 1
class OxmClass(IntEnum):
152
    """OpenFlow Extensible Match (OXM) Class IDs.
153
154
    The high order bit differentiate reserved classes from member classes.
155
    Classes 0x0000 to 0x7FFF are member classes, allocated by ONF.
156
    Classes 0x8000 to 0xFFFE are reserved classes, reserved for
157
    standardisation.
158
    """
159
160
    #: Backward compatibility with NXM
161 1
    OFPXMC_NXM_0 = 0x0000
162
    #: Backward compatibility with NXM
163 1
    OFPXMC_NXM_1 = 0x0001
164
    #: Basic class for OpenFlow
165 1
    OFPXMC_OPENFLOW_BASIC = 0x8000
166
    #: Experimenter class
167 1
    OFPXMC_EXPERIMENTER = 0xFFFF
168
169
170 1
class VlanId(IntEnum):
171
    """Indicates conditions of the Vlan.
172
173
    The VLAN id is 12-bits, so we can use the entire 16 bits to indicate
174
    special conditions.
175
    """
176
177
    #: Bit that indicate that a VLAN id is set.
178 1
    OFPVID_PRESENT = 0x1000
179
    #: No VLAN id was set
180 1
    OFPVID_NONE = 0x0000
181
182
183
# Classes
184
185 1
class OxmTLV(GenericStruct):
186
    """Oxm (OpenFlow Extensible Match) TLV."""
187
188 1
    oxm_class = UBInt16(enum_ref=OxmClass)
189 1
    oxm_field_and_mask = UBInt8()
190 1
    oxm_length = UBInt8()
191 1
    oxm_value = BinaryData()
192
193 1
    def __init__(self, oxm_class=OxmClass.OFPXMC_OPENFLOW_BASIC,
194
                 oxm_field=None, oxm_hasmask=False, oxm_value=None):
195
        """Create an OXM TLV struct with the optional parameters below.
196
197
        Args:
198
            oxm_class (OxmClass): Match class: member class or reserved class
199
            oxm_field (OxmMatchFields, OxmOfbMatchField): Match field within
200
                the class
201
            oxm_hasmask (bool): Set if OXM include a bitmask in payload
202
            oxm_value (bytes): OXM Payload
203
204
        """
205 1
        super().__init__()
206 1
        self.oxm_class = oxm_class
207 1
        self.oxm_field_and_mask = None
208 1
        self.oxm_length = None
209 1
        self.oxm_value = oxm_value
210
        # Attributes that are not packed
211 1
        self.oxm_field = oxm_field
212 1
        self.oxm_hasmask = oxm_hasmask
213
214 1
    def unpack(self, buff, offset=0):
215
        """Unpack the buffer into a OxmTLV.
216
217
        Args:
218
            buff (bytes): The binary data to be unpacked.
219
            offset (int): If we need to shift the beginning of the data.
220
221
        """
222 1
        super().unpack(buff, offset)
223
        # Recover field from field_and_hasmask.
224 1
        try:
225 1
            self.oxm_field = self._unpack_oxm_field()
226 1
        except ValueError as exception:
227 1
            raise UnpackException(exception)
228
229
        # The last bit of field_and_mask is oxm_hasmask
230 1
        self.oxm_hasmask = (self.oxm_field_and_mask & 1) == 1  # as boolean
231
232
        # Unpack oxm_value that has oxm_length bytes
233 1
        start = offset + 4  # 4 bytes: class, field_and_mask and length
234 1
        end = start + self.oxm_length
235 1
        self.oxm_value = buff[start:end]
236
237 1
    def _unpack_oxm_field(self):
238
        """Unpack oxm_field from oxm_field_and_mask.
239
240
        Returns:
241
            :class:`OxmOfbMatchField`, int: oxm_field from oxm_field_and_mask.
242
243
        Raises:
244
            ValueError: If oxm_class is OFPXMC_OPENFLOW_BASIC but
245
                :class:`OxmOfbMatchField` has no such integer value.
246
247
        """
248 1
        field_int = self.oxm_field_and_mask >> 1
249
        # We know that the class below requires a subset of the ofb enum
250 1
        if self.oxm_class == OxmClass.OFPXMC_OPENFLOW_BASIC:
251 1
            return OxmOfbMatchField(field_int)
252 1
        return field_int
253
254 1
    def _update_length(self):
255
        """Update length field.
256
257
        Update the oxm_length field with the packed payload length.
258
259
        """
260 1
        payload = type(self).oxm_value.pack(self.oxm_value)
261 1
        self.oxm_length = len(payload)
262
263 1
    def pack(self, value=None):
264
        """Join oxm_hasmask bit and 7-bit oxm_field."""
265 1
        if value is not None:
266 1
            return value.pack()
267
268
        # Set oxm_field_and_mask instance attribute
269
        # 1. Move field integer one bit to the left
270 1
        try:
271 1
            field_int = self._get_oxm_field_int()
272 1
        except ValueError as exception:
273 1
            raise PackException(exception)
274 1
        field_bits = field_int << 1
275
        # 2. hasmask bit
276 1
        hasmask_bit = self.oxm_hasmask & 1
277
        # 3. Add hasmask bit to field value
278 1
        self.oxm_field_and_mask = field_bits + hasmask_bit
279
280 1
        self._update_length()
281 1
        return super().pack(value)
282
283 1
    def _get_oxm_field_int(self):
284
        """Return a valid integer value for oxm_field.
285
286
        Used while packing.
287
288
        Returns:
289
            int: valid oxm_field value.
290
291
        Raises:
292
            ValueError: If :attribute:`oxm_field` is bigger than 7 bits or
293
                should be :class:`OxmOfbMatchField` and the enum has no such
294
                value.
295
296
        """
297 1
        if self.oxm_class == OxmClass.OFPXMC_OPENFLOW_BASIC:
298 1
            return OxmOfbMatchField(self.oxm_field).value
299 1
        if not isinstance(self.oxm_field, int) or self.oxm_field > 127:
300 1
            raise ValueError('oxm_field above 127: "{self.oxm_field}".')
301 1
        return self.oxm_field
302
303
304 1
class OxmMatchFields(FixedTypeList):
305
    """Generic Openflow EXtensible Match header.
306
307
    Abstract class that can be instantiated as Match or OxmExperimenterHeader.
308
309
    """
310
311 1
    def __init__(self, items=None):
312
        """Initialize ``items`` attribute.
313
314
        Args:
315
            items (OxmHeader): Instance or a list of instances.
316
        """
317 1
        super().__init__(pyof_class=OxmTLV, items=items)
318
319
320 1
class Match(GenericStruct):
321
    """Describes the flow match header structure.
322
323
    These are the fields to match against flows.
324
325
    The :attr:`~match_type` field is set to :attr:`~MatchType.OFPMT_OXM` and
326
    :attr:`length` field is set to the actual length of match structure
327
    including all match fields. The payload of the OpenFlow match is a set of
328
    OXM Flow match fields.
329
330
    """
331
332
    #: One of OFPMT_*
333 1
    match_type = UBInt16(enum_ref=MatchType)
334
    #: Length of Match (excluding padding)
335 1
    length = UBInt16()
336 1
    oxm_match_fields = OxmMatchFields()
337
338 1
    def __init__(self, match_type=MatchType.OFPMT_OXM, oxm_match_fields=None):
339
        """Describe the flow match header structure.
340
341
        Args:
342
            match_type (MatchType): One of OFPMT_* (MatchType) items.
343
            length (int): Length of Match (excluding padding) followed by
344
                          Exactly (length - 4) (possibly 0) bytes containing
345
                          OXM TLVs, then exactly ((length + 7)/8*8 - length)
346
                          (between 0 and 7) bytes of all-zero bytes.
347
            oxm_fields (OxmMatchFields): Sample description.
348
349
        """
350 1
        super().__init__()
351 1
        self.match_type = match_type
352 1
        self.oxm_match_fields = oxm_match_fields or OxmMatchFields()
353
354 1
        self._update_match_length()
355
356 1
    def _update_match_length(self):
357
        """Update the match length field."""
358 1
        self.length = super().get_size()
359
360 1
    def pack(self, value=None):
361
        """Pack and complete the last byte by padding."""
362 1
        if isinstance(value, Match):
363 1
            return value.pack()
364 1
        if value is None:
365 1
            self._update_match_length()
366 1
            packet = super().pack()
367 1
            return self._complete_last_byte(packet)
368
        raise PackException(f'Match can\'t unpack "{value}".')
369
370 1
    def _complete_last_byte(self, packet):
371
        """Pad until the packet length is a multiple of 8 (bytes)."""
372 1
        padded_size = self.get_size()
373 1
        padding_bytes = padded_size - len(packet)
374 1
        if padding_bytes > 0:
375 1
            packet += Pad(padding_bytes).pack()
376 1
        return packet
377
378 1
    def get_size(self, value=None):
379
        """Return the packet length including the padding (multiple of 8)."""
380 1
        if isinstance(value, Match):
381 1
            return value.get_size()
382 1
        if value is None:
383 1
            current_size = super().get_size()
384 1
            return ceil(current_size / 8) * 8
385
        raise ValueError(f'Invalid value "{value}" for Match.get_size()')
386
387 1
    def unpack(self, buff, offset=0):
388
        """Discard padding bytes using the unpacked length attribute."""
389 1
        begin = offset
390 1
        for name, value in list(self.get_class_attributes())[:-1]:
391 1
            size = self._unpack_attribute(name, value, buff, begin)
392 1
            begin += size
393 1
        self._unpack_attribute('oxm_match_fields', type(self).oxm_match_fields,
394
                               buff[:offset+self.length], begin)
395
396 1
    def get_field(self, field_type):
397
        """Return the value for the 'field_type' field in oxm_match_fields.
398
399
        Args:
400
            field_type (~pyof.v0x04.common.flow_match.OxmOfbMatchField,
401
                        ~pyof.v0x04.common.flow_match.OxmMatchFields):
402
                The type of the OXM field you want the value.
403
404
        Returns:
405
            The integer number of the 'field_type' if it exists. Otherwise
406
            return None.
407
408
        """
409 1
        for field in self.oxm_match_fields:
410 1
            if field.oxm_field == field_type:
411 1
                return field.oxm_value
412
413
        return None
414
415
416 1
class OxmExperimenterHeader(GenericStruct):
417
    """Header for OXM experimenter match fields."""
418
419
    #: oxm_class = OFPXMC_EXPERIMENTER
420 1
    oxm_header = UBInt32(OxmClass.OFPXMC_EXPERIMENTER,
421
                         enum_ref=OxmClass)
422
    #: Experimenter ID which takes the same form as in struct
423
    #:     ofp_experimenter_header
424 1
    experimenter = UBInt32()
425
426 1
    def __init__(self, experimenter=None):
427
        """Initialize ``experimenter`` attribute.
428
429
        Args:
430
            experimenter (int): Experimenter ID which takes the same form as
431
              in struct ofp_experimenter_header
432
433
        """
434
        super().__init__()
435
        self.experimenter = experimenter
436
437
438 1
class ListOfOxmHeader(FixedTypeList):
439
    """List of Openflow Extensible Match header instances.
440
441
    Represented by instances of OxmHeader.
442
443
    """
444
445 1
    def __init__(self, items=None):
446
        """Initialize ``items`` attribute.
447
448
        Args:
449
            items (OxmHeader): Instance or a list of instances.
450
451
        """
452
        super().__init__(pyof_class=OxmTLV, items=items)
453