Skip to content

Commit e986fbc

Browse files
authored
Feature/add-ptp-protocol (#4640)
* Add support for PTP protocol based on IEEE 1588-2008 * Move PTPv2 protocol to scapy.contrib
1 parent 2cb8101 commit e986fbc

File tree

2 files changed

+262
-0
lines changed

2 files changed

+262
-0
lines changed

scapy/contrib/ptp_v2.py

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
# This file is part of Scapy
3+
# See https://scapy.net/ for more information
4+
# Copyright (C) Philippe Biondi <phil@secdev.org>
5+
# Copyright (C) Satveer Brar
6+
7+
# scapy.contrib.description = Precision Time Protocol v2
8+
# scapy.contrib.status = loads
9+
10+
"""
11+
PTP (Precision Time Protocol).
12+
References : IEEE 1588-2008
13+
"""
14+
15+
import struct
16+
17+
from scapy.packet import Packet, bind_layers
18+
from scapy.fields import (
19+
BitEnumField,
20+
BitField,
21+
ByteField,
22+
IntField,
23+
LongField,
24+
ShortField,
25+
ByteEnumField,
26+
FlagsField,
27+
XLongField,
28+
XByteField,
29+
ConditionalField,
30+
)
31+
from scapy.layers.inet import UDP
32+
33+
34+
#############################################################################
35+
# PTPv2
36+
#############################################################################
37+
38+
# IEEE 1588-2008 / Section 13.3.2.2
39+
40+
_message_type = {
41+
0x0: "Sync",
42+
0x1: "Delay_Req",
43+
0x2: "Pdelay_Req",
44+
0x3: "Pdelay_Resp",
45+
0x4: "Reserved",
46+
0x5: "Reserved",
47+
0x6: "Reserved",
48+
0x7: "Reserved",
49+
0x8: "Follow_Up",
50+
0x9: "Delay_Resp",
51+
0xA: "Pdelay_Resp_Follow",
52+
0xB: "Announce",
53+
0xC: "Signaling",
54+
0xD: "Management",
55+
0xE: "Reserved",
56+
0xF: "Reserved"
57+
}
58+
59+
_control_field = {
60+
0x00: "Sync",
61+
0x01: "Delay_Req",
62+
0x02: "Follow_Up",
63+
0x03: "Delay_Resp",
64+
0x04: "Management",
65+
0x05: "All others",
66+
}
67+
68+
_flags = {
69+
0x0001: "alternateMasterFlag",
70+
0x0002: "twoStepFlag",
71+
0x0004: "unicastFlag",
72+
0x0010: "ptpProfileSpecific1",
73+
0x0020: "ptpProfileSpecific2",
74+
0x0040: "reserved",
75+
0x0100: "leap61",
76+
0x0200: "leap59",
77+
0x0400: "currentUtcOffsetValid",
78+
0x0800: "ptpTimescale",
79+
0x1000: "timeTraceable",
80+
0x2000: "frequencyTraceable"
81+
}
82+
83+
84+
class PTP(Packet):
85+
"""
86+
PTP packet based on IEEE 1588-2008 / Section 13.3
87+
"""
88+
89+
name = "PTP"
90+
match_subclass = True
91+
fields_desc = [
92+
BitField("transportSpecific", 0, 4),
93+
BitEnumField("messageType", 0x0, 4, _message_type),
94+
BitField("reserved1", 0, 4),
95+
BitField("version", 2, 4),
96+
ShortField("messageLength", None),
97+
ByteField("domainNumber", 0),
98+
ByteField("reserved2", 0),
99+
FlagsField("flags", 0, 16, _flags),
100+
LongField("correctionField", 0),
101+
IntField("reserved3", 0),
102+
XLongField("clockIdentity", 0),
103+
ShortField("portNumber", 0),
104+
ShortField("sequenceId", 0),
105+
ByteEnumField("controlField", 0, _control_field),
106+
ByteField("logMessageInterval", 0),
107+
ConditionalField(BitField("originTimestamp_seconds", 0, 48),
108+
lambda pkt: pkt.messageType in [0x0, 0x1, 0x2, 0xB]),
109+
ConditionalField(IntField("originTimestamp_nanoseconds", 0),
110+
lambda pkt: pkt.messageType in [0x0, 0x1, 0x2, 0xB]),
111+
ConditionalField(BitField("preciseOriginTimestamp_seconds", 0, 48),
112+
lambda pkt: pkt.messageType == 0x8),
113+
ConditionalField(IntField("preciseOriginTimestamp_nanoseconds", 0),
114+
lambda pkt: pkt.messageType == 0x8),
115+
ConditionalField(BitField("requestReceiptTimestamp_seconds", 0, 48),
116+
lambda pkt: pkt.messageType == 0x3),
117+
ConditionalField(IntField("requestReceiptTimestamp_nanoseconds", 0),
118+
lambda pkt: pkt.messageType == 0x3),
119+
ConditionalField(BitField("receiveTimestamp_seconds", 0, 48),
120+
lambda pkt: pkt.messageType == 0x9),
121+
ConditionalField(IntField("receiveTimestamp_nanoseconds", 0),
122+
lambda pkt: pkt.messageType == 0x9),
123+
ConditionalField(BitField("responseOriginTimestamp_seconds", 0, 48),
124+
lambda pkt: pkt.messageType == 0xA),
125+
ConditionalField(IntField("responseOriginTimestamp_nanoseconds", 0),
126+
lambda pkt: pkt.messageType == 0xA),
127+
ConditionalField(ShortField("currentUtcOffset", 0),
128+
lambda pkt: pkt.messageType == 0xB),
129+
ConditionalField(ByteField("reserved4", 0),
130+
lambda pkt: pkt.messageType == 0xB),
131+
ConditionalField(ByteField("grandmasterPriority1", 0),
132+
lambda pkt: pkt.messageType == 0xB),
133+
ConditionalField(ByteField("grandmasterClockClass", 0),
134+
lambda pkt: pkt.messageType == 0xB),
135+
ConditionalField(XByteField("grandmasterClockAccuracy", 0),
136+
lambda pkt: pkt.messageType == 0xB),
137+
ConditionalField(ShortField("grandmasterClockVariance", 0),
138+
lambda pkt: pkt.messageType == 0xB),
139+
ConditionalField(ByteField("grandmasterPriority2", 0),
140+
lambda pkt: pkt.messageType == 0xB),
141+
ConditionalField(XLongField("grandmasterIdentity", 0),
142+
lambda pkt: pkt.messageType == 0xB),
143+
ConditionalField(ShortField("stepsRemoved", 0),
144+
lambda pkt: pkt.messageType == 0xB),
145+
ConditionalField(XByteField("timeSource", 0),
146+
lambda pkt: pkt.messageType == 0xB)
147+
148+
]
149+
150+
def post_build(self, pkt, pay): # type: (bytes, bytes) -> bytes
151+
"""
152+
Update the messageLength field after building the packet
153+
"""
154+
if self.messageLength is None:
155+
pkt = pkt[:2] + struct.pack("!H", len(pkt)) + pkt[4:]
156+
157+
return pkt + pay
158+
159+
160+
# Layer bindings
161+
162+
bind_layers(UDP, PTP, sport=319, dport=319)
163+
bind_layers(UDP, PTP, sport=320, dport=320)

test/contrib/ptp_v2.uts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
% PTP regression tests for Scapy
2+
3+
#
4+
# Type the following command to launch the tests:
5+
# $ test/run_tests -P "load_contrib('ptp_v2')" -t test/contrib/ptp_v2.uts
6+
7+
+ Basic tests
8+
9+
= specific haslayer and getlayer implementations for PTP
10+
~ haslayer getlayer PTP
11+
pkt = IP() / UDP() / PTP()
12+
assert PTP in pkt
13+
assert pkt.haslayer(PTP)
14+
assert isinstance(pkt[PTP], PTP)
15+
assert isinstance(pkt.getlayer(PTP), PTP)
16+
17+
+ Packet dissection tests
18+
19+
= Sync packet dissection
20+
s = b'\x10\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x00\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0'
21+
pkt = PTP(s)
22+
assert pkt.transportSpecific == 1
23+
assert pkt.messageType == 0
24+
assert pkt.reserved1 == 0
25+
assert pkt.version == 2
26+
assert pkt.messageLength == 44
27+
assert pkt.domainNumber == 123
28+
assert pkt.reserved2 == 0
29+
assert pkt.flags == None
30+
assert pkt.correctionField == 0
31+
assert pkt.reserved3 == 0
32+
assert pkt.clockIdentity == 0x8063ffff0009ba
33+
assert pkt.portNumber == 1
34+
assert pkt.sequenceId == 116
35+
assert pkt.controlField == 0
36+
assert pkt.logMessageInterval == 0
37+
assert pkt.originTimestamp_seconds == 1169232218
38+
assert pkt.originTimestamp_nanoseconds == 174389936
39+
40+
= Delay_Req packet dissection
41+
s= b'\x11\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x01\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0'
42+
pkt = PTP(s)
43+
assert pkt.messageType == 0x1
44+
assert pkt.controlField == 0x1
45+
46+
= Pdelay_Req packet dissection
47+
s= b'\x12\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0'
48+
pkt = PTP(s)
49+
assert pkt.messageType == 0x2
50+
assert pkt.controlField == 0x5
51+
52+
= Pdelay_Resp packet dissection
53+
s= b'\x13\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0'
54+
pkt = PTP(s)
55+
assert pkt.messageType == 0x3
56+
assert pkt.controlField == 0x5
57+
assert pkt.requestReceiptTimestamp_seconds == 1169232218
58+
assert pkt.requestReceiptTimestamp_nanoseconds == 174389936
59+
60+
= Follow_Up packet dissection
61+
s= b'\x18\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x02\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0'
62+
pkt = PTP(s)
63+
assert pkt.messageType == 0x8
64+
assert pkt.controlField == 0x2
65+
assert pkt.preciseOriginTimestamp_seconds == 1169232218
66+
assert pkt.preciseOriginTimestamp_nanoseconds == 174389936
67+
68+
= Delay_Resp packet dissection
69+
s= b'\x19\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x03\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0'
70+
pkt = PTP(s)
71+
assert pkt.messageType == 0x9
72+
assert pkt.controlField == 0x3
73+
assert pkt.receiveTimestamp_seconds == 1169232218
74+
assert pkt.receiveTimestamp_nanoseconds == 174389936
75+
76+
= Pdelay_Resp_Follow packet dissection
77+
s= b'\x1A\x02\x00\x2c\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x00\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0'
78+
pkt = PTP(s)
79+
assert pkt.messageType == 0xA
80+
assert pkt.controlField == 0x5
81+
assert pkt.responseOriginTimestamp_seconds == 1169232218
82+
assert pkt.responseOriginTimestamp_nanoseconds == 174389936
83+
84+
= Announce packet dissection
85+
s= b'\x1b\x02\x00\x40\x7b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\x00\x01\x00\x74\x05\x01\x00\x00\x45\xb1\x11\x5a\x0a\x64\xfa\xb0\x00\x00\x00\x60\x00\x00\x00\x80\x63\xff\xff\x00\x09\xba\xf8\x21\x00\x00\x80\x80'
86+
pkt = PTP(s)
87+
assert pkt.messageType == 0xB
88+
assert pkt.messageLength == 64
89+
assert pkt.controlField == 0x5
90+
assert pkt.currentUtcOffset == 0
91+
assert pkt.reserved4 == 0
92+
assert pkt.grandmasterPriority1 == 96
93+
assert pkt.grandmasterClockClass == 0
94+
assert pkt.grandmasterClockAccuracy == 0x0
95+
assert pkt.grandmasterClockVariance == 128
96+
assert pkt.grandmasterPriority2 == 99
97+
assert pkt.grandmasterIdentity == 0xffff0009baf82100
98+
assert pkt.stepsRemoved == 128
99+
assert pkt.timeSource == 0x80

0 commit comments

Comments
 (0)