86890704fd
todo: add documentation & wireshark dissector
251 lines
7.8 KiB
Python
Executable file
251 lines
7.8 KiB
Python
Executable file
# scapy.contrib.description = MACControl
|
|
# scapy.contrib.status = loads
|
|
|
|
"""
|
|
MACControl
|
|
~~~~~~~~~~
|
|
|
|
:author: Thomas Tannhaeuser, hecke@naberius.de
|
|
:license: GPLv2
|
|
|
|
This module is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This module is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
:description:
|
|
|
|
This module provides Scapy layers for the MACControl protocol messages:
|
|
- Pause
|
|
- Gate
|
|
- Report
|
|
- Register/REQ/ACK
|
|
- Class Based Flow Control
|
|
|
|
normative references:
|
|
- IEEE 802.3x
|
|
|
|
|
|
:NOTES:
|
|
- this is based on the MACControl dissector used by Wireshark
|
|
(https://github.com/wireshark/wireshark/blob/master/epan/dissectors/packet-maccontrol.c)
|
|
|
|
"""
|
|
|
|
from scapy.compat import orb
|
|
from scapy.data import ETHER_TYPES
|
|
from scapy.error import Scapy_Exception
|
|
from scapy.fields import IntField, ByteField, ByteEnumField, ShortField, BitField # noqa: E501
|
|
from scapy.layers.dot11 import Packet
|
|
from scapy.layers.l2 import Ether, Dot1Q, bind_layers
|
|
|
|
MAC_CONTROL_ETHER_TYPE = 0x8808
|
|
ETHER_TYPES['MAC_CONTROL'] = MAC_CONTROL_ETHER_TYPE
|
|
|
|
ETHER_SPEED_MBIT_10 = 0x01
|
|
ETHER_SPEED_MBIT_100 = 0x02
|
|
ETHER_SPEED_MBIT_1000 = 0x04
|
|
|
|
|
|
class MACControl(Packet):
|
|
DEFAULT_DST_MAC = "01:80:c2:00:00:01"
|
|
|
|
OP_CODE_PAUSE = 0x0001
|
|
OP_CODE_GATE = 0x0002
|
|
OP_CODE_REPORT = 0x0003
|
|
OP_CODE_REGISTER_REQ = 0x0004
|
|
OP_CODE_REGISTER = 0x0005
|
|
OP_CODE_REGISTER_ACK = 0x0006
|
|
OP_CODE_CLASS_BASED_FLOW_CONTROL = 0x0101
|
|
|
|
OP_CODES = {
|
|
OP_CODE_PAUSE: 'pause',
|
|
OP_CODE_GATE: 'gate',
|
|
OP_CODE_REPORT: 'report',
|
|
OP_CODE_REGISTER_REQ: 'register req',
|
|
OP_CODE_REGISTER: 'register',
|
|
OP_CODE_REGISTER_ACK: 'register_ack',
|
|
OP_CODE_CLASS_BASED_FLOW_CONTROL: 'class based flow control'
|
|
}
|
|
|
|
'''
|
|
flags used by Register* messages
|
|
'''
|
|
FLAG_REGISTER = 0x01
|
|
FLAG_DEREGISTER = 0x02
|
|
FLAG_ACK = 0x03
|
|
FLAG_NACK = 0x04
|
|
|
|
REGISTER_FLAGS = {
|
|
FLAG_REGISTER: 'register',
|
|
FLAG_DEREGISTER: 'deregister',
|
|
FLAG_ACK: 'ack',
|
|
FLAG_NACK: 'nack'
|
|
}
|
|
|
|
def guess_payload_class(self, payload):
|
|
|
|
try:
|
|
op_code = (orb(payload[0]) << 8) + orb(payload[1])
|
|
return MAC_CTRL_CLASSES[op_code]
|
|
except KeyError:
|
|
pass
|
|
|
|
return Packet.guess_payload_class(self, payload)
|
|
|
|
def _get_underlayers_size(self):
|
|
"""
|
|
get the total size of all under layers
|
|
:return: number of bytes
|
|
"""
|
|
|
|
under_layer = self.underlayer
|
|
|
|
under_layers_size = 0
|
|
|
|
while under_layer and isinstance(under_layer, Dot1Q):
|
|
under_layers_size += 4
|
|
under_layer = under_layer.underlayer
|
|
|
|
if under_layer and isinstance(under_layer, Ether):
|
|
# ether header len + FCS len
|
|
under_layers_size += 14 + 4
|
|
|
|
return under_layers_size
|
|
|
|
def post_build(self, pkt, pay):
|
|
"""
|
|
add padding to the frame if required.
|
|
|
|
note that padding is only added if pay is None/empty. this allows us to add # noqa: E501
|
|
any payload after the MACControl* PDU if needed (piggybacking).
|
|
"""
|
|
|
|
if not pay:
|
|
under_layers_size = self._get_underlayers_size()
|
|
|
|
frame_size = (len(pkt) + under_layers_size)
|
|
|
|
if frame_size < 64:
|
|
return pkt + b'\x00' * (64 - frame_size)
|
|
|
|
return pkt + pay
|
|
|
|
|
|
class MACControlInvalidSpeedException(Scapy_Exception):
|
|
pass
|
|
|
|
|
|
class MACControlPause(MACControl):
|
|
fields_desc = [
|
|
ShortField("_op_code", MACControl.OP_CODE_PAUSE),
|
|
ShortField("pause_time", 0),
|
|
]
|
|
|
|
def get_pause_time(self, speed=ETHER_SPEED_MBIT_1000):
|
|
"""
|
|
get pause time for given link speed in seconds
|
|
|
|
:param speed: select link speed to get the pause time for, must be ETHER_SPEED_MBIT_[10,100,1000] # noqa: E501
|
|
:return: pause time in seconds
|
|
:raises MACControlInvalidSpeedException: on invalid speed selector
|
|
"""
|
|
|
|
try:
|
|
return self.pause_time * {
|
|
ETHER_SPEED_MBIT_10: (0.0000001 * 512),
|
|
ETHER_SPEED_MBIT_100: (0.00000001 * 512),
|
|
ETHER_SPEED_MBIT_1000: (0.000000001 * 512 * 2)
|
|
}[speed]
|
|
except KeyError:
|
|
raise MACControlInvalidSpeedException('Invalid speed selector given. ' # noqa: E501
|
|
'Must be one of ETHER_SPEED_MBIT_[10,100,1000]') # noqa: E501
|
|
|
|
|
|
class MACControlGate(MACControl):
|
|
fields_desc = [
|
|
ShortField("_op_code", MACControl.OP_CODE_GATE),
|
|
IntField("timestamp", 0)
|
|
]
|
|
|
|
|
|
class MACControlReport(MACControl):
|
|
fields_desc = [
|
|
ShortField("_op_code", MACControl.OP_CODE_REPORT),
|
|
IntField("timestamp", 0),
|
|
ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS),
|
|
ByteField('pending_grants', 0)
|
|
]
|
|
|
|
|
|
class MACControlRegisterReq(MACControl):
|
|
fields_desc = [
|
|
ShortField("_op_code", MACControl.OP_CODE_REGISTER_REQ),
|
|
IntField("timestamp", 0),
|
|
ShortField('assigned_port', 0),
|
|
ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS),
|
|
ShortField('sync_time', 0),
|
|
ByteField('echoed_pending_grants', 0)
|
|
]
|
|
|
|
|
|
class MACControlRegister(MACControl):
|
|
fields_desc = [
|
|
ShortField("_op_code", MACControl.OP_CODE_REGISTER),
|
|
IntField("timestamp", 0),
|
|
ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS),
|
|
ShortField('echoed_assigned_port', 0),
|
|
ShortField('echoed_sync_time', 0)
|
|
]
|
|
|
|
|
|
class MACControlRegisterAck(MACControl):
|
|
fields_desc = [
|
|
ShortField("_op_code", MACControl.OP_CODE_REGISTER_ACK),
|
|
IntField("timestamp", 0),
|
|
ByteEnumField('flags', 0, MACControl.REGISTER_FLAGS),
|
|
ShortField('echoed_assigned_port', 0),
|
|
ShortField('echoed_sync_time', 0)
|
|
]
|
|
|
|
|
|
class MACControlClassBasedFlowControl(MACControl):
|
|
fields_desc = [
|
|
ShortField("_op_code", MACControl.OP_CODE_CLASS_BASED_FLOW_CONTROL),
|
|
ByteField("_reserved", 0),
|
|
BitField('c7_enabled', 0, 1),
|
|
BitField('c6_enabled', 0, 1),
|
|
BitField('c5_enabled', 0, 1),
|
|
BitField('c4_enabled', 0, 1),
|
|
BitField('c3_enabled', 0, 1),
|
|
BitField('c2_enabled', 0, 1),
|
|
BitField('c1_enabled', 0, 1),
|
|
BitField('c0_enabled', 0, 1),
|
|
ShortField('c0_pause_time', 0),
|
|
ShortField('c1_pause_time', 0),
|
|
ShortField('c2_pause_time', 0),
|
|
ShortField('c3_pause_time', 0),
|
|
ShortField('c4_pause_time', 0),
|
|
ShortField('c5_pause_time', 0),
|
|
ShortField('c6_pause_time', 0),
|
|
ShortField('c7_pause_time', 0)
|
|
]
|
|
|
|
|
|
MAC_CTRL_CLASSES = {
|
|
MACControl.OP_CODE_PAUSE: MACControlPause,
|
|
MACControl.OP_CODE_GATE: MACControlGate,
|
|
MACControl.OP_CODE_REPORT: MACControlReport,
|
|
MACControl.OP_CODE_REGISTER_REQ: MACControlRegisterReq,
|
|
MACControl.OP_CODE_REGISTER: MACControlRegister,
|
|
MACControl.OP_CODE_REGISTER_ACK: MACControlRegisterAck,
|
|
MACControl.OP_CODE_CLASS_BASED_FLOW_CONTROL: MACControlClassBasedFlowControl # noqa: E501
|
|
}
|
|
|
|
bind_layers(Ether, MACControl, type=MAC_CONTROL_ETHER_TYPE)
|
|
bind_layers(Dot1Q, MACControl, type=MAC_CONTROL_ETHER_TYPE)
|