Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Simple Two-Way Active Measurement Protocol (STAMP) #3742

Merged
merged 2 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
306 changes: 306 additions & 0 deletions scapy/contrib/stamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
# SPDX-License-Identifier: GPL-2.0-only
# This file is part of Scapy
# See https://scapy.net/ for more information
# Copyright (C) Carmine Scarpitta <carmine.scarpitta@uniroma2.it>

# scapy.contrib.description = Simple Two-Way Active Measurement Protocol (STAMP)
# scapy.contrib.status = loads

"""
STAMP (Simple Two-Way Active Measurement Protocol) - RFC 8762.

References:
* `Simple Two-Way Active Measurement Protocol [RFC 8762]
<https://www.rfc-editor.org/rfc/rfc8762.html>`_
* `Simple Two-Way Active Measurement Protocol Optional Extensions [RFC 8972]
<https://www.rfc-editor.org/rfc/rfc8972.html>`_
"""

from scapy import config
from scapy.base_classes import Packet_metaclass
from scapy.layers.inet import UDP
from scapy.layers.ntp import TimeStampField
from scapy.packet import Packet, bind_layers
from scapy.fields import (
BitEnumField,
BitField,
ByteEnumField,
ByteField,
FlagsField,
IntField,
MultipleTypeField,
NBytesField,
PacketField,
PacketListField,
ShortField,
StrLenField,
UTCTimeField
)


_sync_types = {
0: 'No External Synchronization for the Time Source',
1: 'Clock Synchronized to UTC using an External Source'
}

_timestamp_types = {
0: 'NTP 64-bit Timestamp Format',
1: 'PTPv2 Truncated Timestamp Format'
}

_stamp_tlvs = {

p-l- marked this conversation as resolved.
Show resolved Hide resolved
}


class ErrorEstimate(Packet):
"""
The Error Estimate specifies the estimate of the error and
synchronization. The format of the Error Estimate field
(defined in Section 4.1.2 of `RFC 4656
<https://www.rfc-editor.org/rfc/rfc4656.html>`_) is reported below::

0 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|S|Z| Scale | Multiplier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

``S`` is interpreted as follows:

+-------+-------------------------------------------------------+
| Value | Description |
+-------+-------------------------------------------------------+
| 0 | there is no notion of external synchronization for |
| | the time source |
+-------+-------------------------------------------------------+
| 1 | the party generating the timestamp has a clock that |
| | is synchronized to UTC using an external source |
+-------+-------------------------------------------------------+

``Z`` is interpreted as follows (defined in Section 2.3 of `RFC 8186
<https://www.rfc-editor.org/rfc/rfc8186.html>`_):

+-------+---------------------------------------+
| Value | Description |
+-------+---------------------------------------+
| 0 | NTP 64-bit format of a timestamp |
+-------+---------------------------------------+
| 1 | PTPv2 truncated format of a timestamp |
+-------+---------------------------------------+

``Scale`` and ``Multiplier`` are linked by the following relationship::

ErrorEstimate = Multiplier*2^(-32)*2^Scale (in seconds)


References:
* `A One-way Active Measurement Protocol (OWAMP) [RFC 4656]
<https://www.rfc-editor.org/rfc/rfc4656.html>`_
* `Support of the IEEE 1588 Timestamp Format in a Two-Way Active
Measurement Protocol (TWAMP) [RFC 8186]
<https://www.rfc-editor.org/rfc/rfc8186.html>`_
"""

name = 'Error Estimate'
fields_desc = [
BitEnumField('S', 0, 1, _sync_types),
BitEnumField('Z', 0, 1, _timestamp_types),
BitField('scale', 0, 6),
ByteField('multiplier', 1),
]

def guess_payload_class(self, payload):
# type: (str) -> Packet_metaclass
# Trick to tell scapy that the remaining bytes of the currently
# dissected string is not a payload of this packet but of some other
# underlayer packet
return config.conf.padding_layer


class STAMPTestTLV(Packet):
"""
The STAMP Test TLV defined in Section 4 of [RFC 8972] provides a flexible
extension mechanism for optional informational elements.

The TLV Format in a STAMP Test packet is reported below::

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAMP TLV Flags| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ Value ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


+-------+---------+-------------------------------------------------+
| Field | Description |
+-----------------+-------------------------------------------------+
| STAMP TLV Flags | 8-bit field; for the details about the STAMP |
| | TLV Flags Format, see RFC 8972 |
+-----------------+-------------------------------------------------+
| Type | characterizes the interpretation of the Value |
| | field |
+-----------------+-------------------------------------------------+
| Length | the length of the Value field in octets |
+-----------------+-------------------------------------------------+
| Value | interpreted according to the value of the Type |
| | field |
+-----------------+-------------------------------------------------+


References:
* `Simple Two-Way Active Measurement Protocol Optional Extensions
[RFC 8972] <https://www.rfc-editor.org/rfc/rfc8972.html>`_
"""

name = 'STAMP Test Packet - Generic TLV'
fields_desc = [
FlagsField('flags', 0, 8, "UMIRRRRR"),
ByteEnumField('type', None, _stamp_tlvs),
ShortField('len', 0),
StrLenField('value', '', length_from=lambda pkt: pkt.len),
]

def extract_padding(self, p):
return b"", p

registered_stamp_tlv = {}

@classmethod
def register_variant(cls):
cls.registered_stamp_tlv[cls.type.default] = cls

@classmethod
def dispatch_hook(cls, pkt=None, *args, **kargs):
if pkt:
tmp_type = ord(pkt[1:2])
return cls.registered_stamp_tlv.get(tmp_type, cls)
return cls


class STAMPSessionSenderTestUnauthenticated(Packet):
"""
Extended STAMP Session-Sender Test Packet in Unauthenticated Mode.

The format (defined in Section 3 of `RFC 8972
<https://www.rfc-editor.org/rfc/rfc8972.html>`_) is shown below::

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Error Estimate | SSID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| |
| MBZ (28 octets) |
| |
| |
| |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ TLVs ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

References:
* `Simple Two-Way Active Measurement Protocol Optional Extensions
[RFC 8972] <https://www.rfc-editor.org/rfc/rfc8972.html>`_
"""
name = 'STAMP Session-Sender Test'
fields_desc = [
IntField('seq', 0),
MultipleTypeField(
[
(TimeStampField('ts', 0),
lambda pkt:pkt.err_estimate.Z == 0)
],
UTCTimeField('ts', 0, fmt='Q')
),
PacketField('err_estimate', ErrorEstimate(), ErrorEstimate),
ShortField('ssid', 1),
NBytesField('mbz', 0, 28), # 28 bytes MBZ
PacketListField('tlv_objects', [], STAMPTestTLV,
length_from=lambda pkt: pkt.parent.len - 8 - 44),
]


class STAMPSessionReflectorTestUnauthenticated(Packet):
"""
Extended STAMP Session-Reflector Test Packet in Unauthenticated Mode.

The format (defined in Section 3 of `RFC 8972
<https://www.rfc-editor.org/rfc/rfc8972.html>`_) is shown below::

0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Error Estimate | SSID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Receive Timestamp |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Session-Sender Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Session-Sender Timestamp |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Session-Sender Error Estimate | MBZ |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Ses-Sender TTL | MBZ |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
~ TLVs ~
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

References:
* `Simple Two-Way Active Measurement Protocol Optional Extensions
[RFC 8972] <https://www.rfc-editor.org/rfc/rfc8972.html>`_
"""
name = 'STAMP Session-Reflector Test'
fields_desc = [
IntField('seq', 0),
MultipleTypeField(
[
(TimeStampField('ts', 0),
lambda pkt:pkt.err_estimate.Z == 0),
],
UTCTimeField('ts', 0, fmt='Q')
),
PacketField('err_estimate', ErrorEstimate(), ErrorEstimate),
ShortField('ssid', 1),
MultipleTypeField(
[
(TimeStampField('ts_rx', 0),
lambda pkt:pkt.err_estimate.Z == 0)
],
UTCTimeField('ts_rx', 0, fmt='Q')
),
IntField('seq_sender', 0),
MultipleTypeField(
[
(TimeStampField('ts_sender', 0),
lambda pkt:pkt.err_estimate_sender.Z == 0)
],
UTCTimeField('ts_sender', 0, fmt='Q')
),
PacketField('err_estimate_sender', ErrorEstimate(), ErrorEstimate),
ShortField('mbz1', 0),
ByteField('ttl_sender', 255),
NBytesField('mbz2', 0, 3), # 3 bytes MBZ
PacketListField('tlv_objects', [], STAMPTestTLV,
length_from=lambda pkt: pkt.parent.len - 8 - 44),
]


bind_layers(UDP, STAMPSessionSenderTestUnauthenticated, dport=862)
p-l- marked this conversation as resolved.
Show resolved Hide resolved
bind_layers(UDP, STAMPSessionReflectorTestUnauthenticated, sport=862)
88 changes: 88 additions & 0 deletions test/contrib/stamp.uts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
% STAMP regression tests for Scapy

# More information at http://www.secdev.org/projects/UTscapy/

# Type the following command to launch start the tests:
# $ test/run_tests -t test/contrib/stamp.uts

############
# STAMP
############

+ STAMP tests

= Load module

load_contrib("stamp")

= Test STAMP Session-Sender Test (Unauthenticated)
~ stamp-session-sender-test

created = STAMPSessionSenderTestUnauthenticated(
seq=0x1234,
ts=1234.5678,
err_estimate=ErrorEstimate(
S=1,
Z=0,
scale=0x12,
multiplier=0x34
),
ssid=1357
)
assert raw(created) == b'\x00\x00\x12\x34\x00\x00\x04\xD2\x91\x5B\x57\x3E\x92\x34\x05\x4D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
parsed = STAMPSessionSenderTestUnauthenticated(raw(created))
assert parsed.seq == 0x1234
assert parsed.ts == 1234.5678
assert parsed.err_estimate.S == 1
assert parsed.err_estimate.Z == 0
assert parsed.err_estimate.scale == 0x12
assert parsed.err_estimate.multiplier == 0x34
assert parsed.ssid == 1357
assert parsed.mbz == 0
assert not parsed.tlv_objects

= Test STAMP Session-Reflector Test (Unauthenticated)
~ stamp-session-reflector-test

created = STAMPSessionReflectorTestUnauthenticated(
seq=0x1234,
ts=1234.5678,
err_estimate=ErrorEstimate(
S=1,
Z=0,
scale=0x12,
multiplier=0x34
),
ssid=1357,
ts_rx=4321.8765,
seq_sender=0x4321,
ts_sender=2143.6587,
err_estimate_sender=ErrorEstimate(
S=0,
Z=0,
scale=0x21,
multiplier=0x43
),
ttl_sender=111
)
assert raw(created) == b'\x00\x00\x12\x34\x00\x00\x04\xD2\x91\x5B\x57\x3E\x92\x34\x05\x4D\x00\x00\x10\xE1\xE0\x62\x4D\xD2\x00\x00\x43\x21\x00\x00\x08\x5F\xA8\xA0\x90\x2D\x21\x43\x00\x00\x6F\x00\x00\x00'
parsed = STAMPSessionReflectorTestUnauthenticated(raw(created))
assert parsed.seq == 0x1234
assert parsed.ts == 1234.5678
assert parsed.err_estimate.S == 1
assert parsed.err_estimate.Z == 0
assert parsed.err_estimate.scale == 0x12
assert parsed.err_estimate.multiplier == 0x34
assert parsed.ssid == 1357
assert parsed.ts_rx == 4321.8765
assert parsed.seq_sender == 0x4321
assert parsed.ts_sender == 2143.6587
assert parsed.err_estimate_sender.S == 0
assert parsed.err_estimate_sender.Z == 0
assert parsed.err_estimate_sender.scale == 0x21
assert parsed.err_estimate_sender.multiplier == 0x43
assert parsed.mbz1 == 0
assert parsed.ttl_sender == 111
assert parsed.mbz2 == 0
assert not parsed.tlv_objects