Skip to content

Commit 3c72f1a

Browse files
committed
Add support for PTP protocol based on IEEE 1588-2008
1 parent 92925da commit 3c72f1a

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

scapy/config.py

+1
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,7 @@ class Conf(ConfClass):
10571057
'ppi',
10581058
'ppp',
10591059
'pptp',
1060+
'ptp_v2',
10601061
'radius',
10611062
'rip',
10621063
'rtp',

scapy/layers/ptp_v2.py

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

test/scapy/layers/ptp_v2.uts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
% PTP regression tests for Scapy
2+
3+
+ Basic tests
4+
5+
= specific haslayer and getlayer implementations for PTP
6+
~ haslayer getlayer PTP
7+
pkt = IP() / UDP() / PTP()
8+
assert PTP in pkt
9+
assert pkt.haslayer(PTP)
10+
assert isinstance(pkt[PTP], PTP)
11+
assert isinstance(pkt.getlayer(PTP), PTP)
12+
13+
+ Packet dissection tests
14+
15+
= Sync packet dissection
16+
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'
17+
pkt = PTP(s)
18+
assert pkt.transportSpecific == 1
19+
assert pkt.messageType == 0
20+
assert pkt.reserved1 == 0
21+
assert pkt.version == 2
22+
assert pkt.messageLength == 44
23+
assert pkt.domainNumber == 123
24+
assert pkt.reserved2 == 0
25+
assert pkt.flags == None
26+
assert pkt.correctionField == 0
27+
assert pkt.reserved3 == 0
28+
assert pkt.clockIdentity == 0x8063ffff0009ba
29+
assert pkt.portNumber == 1
30+
assert pkt.sequenceId == 116
31+
assert pkt.controlField == 0
32+
assert pkt.logMessageInterval == 0
33+
assert pkt.originTimestamp_seconds == 1169232218
34+
assert pkt.originTimestamp_nanoseconds == 174389936
35+
36+
= Delay_Req packet dissection
37+
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'
38+
pkt = PTP(s)
39+
assert pkt.messageType == 0x1
40+
assert pkt.controlField == 0x1
41+
42+
= Pdelay_Req packet dissection
43+
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'
44+
pkt = PTP(s)
45+
assert pkt.messageType == 0x2
46+
assert pkt.controlField == 0x5
47+
48+
= Pdelay_Resp packet dissection
49+
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'
50+
pkt = PTP(s)
51+
assert pkt.messageType == 0x3
52+
assert pkt.controlField == 0x5
53+
assert pkt.requestReceiptTimestamp_seconds == 1169232218
54+
assert pkt.requestReceiptTimestamp_nanoseconds == 174389936
55+
56+
= Follow_Up packet dissection
57+
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'
58+
pkt = PTP(s)
59+
assert pkt.messageType == 0x8
60+
assert pkt.controlField == 0x2
61+
assert pkt.preciseOriginTimestamp_seconds == 1169232218
62+
assert pkt.preciseOriginTimestamp_nanoseconds == 174389936
63+
64+
= Delay_Resp packet dissection
65+
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'
66+
pkt = PTP(s)
67+
assert pkt.messageType == 0x9
68+
assert pkt.controlField == 0x3
69+
assert pkt.receiveTimestamp_seconds == 1169232218
70+
assert pkt.receiveTimestamp_nanoseconds == 174389936
71+
72+
= Pdelay_Resp_Follow packet dissection
73+
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'
74+
pkt = PTP(s)
75+
assert pkt.messageType == 0xA
76+
assert pkt.controlField == 0x5
77+
assert pkt.responseOriginTimestamp_seconds == 1169232218
78+
assert pkt.responseOriginTimestamp_nanoseconds == 174389936
79+
80+
= Announce packet dissection
81+
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'
82+
pkt = PTP(s)
83+
assert pkt.messageType == 0xB
84+
assert pkt.messageLength == 64
85+
assert pkt.controlField == 0x5
86+
assert pkt.currentUtcOffset == 0
87+
assert pkt.reserved4 == 0
88+
assert pkt.grandmasterPriority1 == 96
89+
assert pkt.grandmasterClockClass == 0
90+
assert pkt.grandmasterClockAccuracy == 0x0
91+
assert pkt.grandmasterClockVariance == 128
92+
assert pkt.grandmasterPriority2 == 99
93+
assert pkt.grandmasterIdentity == 0xffff0009baf82100
94+
assert pkt.stepsRemoved == 128
95+
assert pkt.timeSource == 0x80

0 commit comments

Comments
 (0)