Skip to content

Commit 1aa5e9b

Browse files
authored
Update RSVP module with a new SESSION object, docstrings, & tests (#5026)
1 parent fda6032 commit 1aa5e9b

2 files changed

Lines changed: 134 additions & 19 deletions

File tree

scapy/contrib/rsvp.py

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from scapy.compat import chb
1313
from scapy.packet import Packet, bind_layers
1414
from scapy.fields import BitField, ByteEnumField, ByteField, FieldLenField, \
15-
IPField, ShortField, StrLenField, XByteField, XShortField
15+
IPField, IntField, ShortField, StrLenField, XByteField, XShortField
1616
from scapy.layers.inet import IP, checksum
1717

1818
rsvpmsgtypes = {0x01: "Path",
@@ -26,6 +26,9 @@
2626

2727

2828
class RSVP(Packet):
29+
"""Common RSVP message header. The header is followed by this message's objects,
30+
which chains to further RSVP_Object instances, forming the sequence of RSVP objects.
31+
"""
2932
name = "RSVP"
3033
fields_desc = [BitField("Version", 1, 4),
3134
BitField("Flags", 1, 4),
@@ -36,6 +39,9 @@ class RSVP(Packet):
3639
ShortField("Length", None)]
3740

3841
def post_build(self, p, pay):
42+
"""Append payload bytes to the header, and patch in Length/checksum if unset.
43+
Returns the fully packet message bytes.
44+
"""
3945
p += pay
4046
if self.Length is None:
4147
tmp_len = len(p)
@@ -47,7 +53,7 @@ def post_build(self, p, pay):
4753
return p
4854

4955

50-
rsvptypes = {0x01: "Session",
56+
rsvptypes = {0x01: "SESSION",
5157
0x03: "HOP",
5258
0x04: "INTEGRITY",
5359
0x05: "TIME_VALUES",
@@ -126,13 +132,22 @@ def post_build(self, p, pay):
126132

127133

128134
class RSVP_Object(Packet):
135+
"""Common RSVP object header. Followed by exactly one RSVP object data structure,
136+
Dissection naturally chains into the next object if more bytes remain.
137+
"""
129138
name = "RSVP_Object"
130139
fields_desc = [ShortField("Length", 4),
131140
ByteEnumField("Class", 0x01, rsvptypes),
132141
ByteField("C_Type", 1)]
133142

134143
def guess_payload_class(self, payload):
135-
if self.Class == 0x03:
144+
"""Pick the data class to dissect based on Class.
145+
Falls back to a generic container.
146+
"""
147+
if self.Class == 0x01:
148+
if self.C_Type == 0x07:
149+
return RSVP_SESSION
150+
elif self.Class == 0x03:
136151
return RSVP_HOP
137152
elif self.Class == 0x05:
138153
return RSVP_Time
@@ -147,49 +162,95 @@ def guess_payload_class(self, payload):
147162

148163

149164
class RSVP_Data(Packet):
165+
"""Defines a generic/unknown RSVP object data structure for any Class value
166+
that doesn't have a dedicated class implemented yet.
167+
"""
150168
name = "Data"
151169
overload_fields = {RSVP_Object: {"Class": 0x01}}
152-
fields_desc = [StrLenField("Data", "", length_from=lambda pkt:pkt.underlayer.Length - 4)] # noqa: E501
170+
fields_desc = [
171+
StrLenField(
172+
"Data",
173+
"",
174+
length_from=lambda pkt: pkt.underlayer.Length - 4
175+
),
176+
]
177+
178+
def default_payload_class(self, payload):
179+
return RSVP_Object
180+
181+
182+
class RSVP_SESSION(Packet):
183+
"""SESSION LSP_TUNNEL_IPV4 object data structure, RFC 3209 section 4.6.1.1.
184+
Identifies the session for which this message is sent.
185+
"""
186+
name = "SESSION"
187+
overload_fields = {RSVP_Object: {"Class": 0x01, "C_Type": 0x07}}
188+
fields_desc = [IPField("dest_addr", "0.0.0.0"),
189+
ShortField("reserved", 0),
190+
ShortField("tunnel_id", 0),
191+
IPField("ext_tunnel_id", "0.0.0.0")]
153192

154193
def default_payload_class(self, payload):
155194
return RSVP_Object
156195

157196

158197
class RSVP_HOP(Packet):
198+
"""RSVP_HOP object data structure, RFC 2205 section A.2.
199+
Identifies the IP address and the Logical Interface Handle (LIH)
200+
of the interface this message was sent from.
201+
"""
159202
name = "HOP"
160203
overload_fields = {RSVP_Object: {"Class": 0x03}}
161204
fields_desc = [IPField("neighbor", "0.0.0.0"),
162-
BitField("inface", 1, 32)]
205+
IntField("inface", 1)]
163206

164207
def default_payload_class(self, payload):
165208
return RSVP_Object
166209

167210

168211
class RSVP_Time(Packet):
212+
"""TIME_VALUES object RFC 2205 section A.4.
213+
Carries the refresh period a sender will use to resend PATH/RESV messages,
214+
which the receiver can use to determine when to tear down
215+
the reservation if no further messages are received.
216+
"""
169217
name = "Time Val"
170218
overload_fields = {RSVP_Object: {"Class": 0x05}}
171-
fields_desc = [BitField("refresh", 1, 32)]
219+
fields_desc = [IntField("refresh", 1)]
172220

173221
def default_payload_class(self, payload):
174222
return RSVP_Object
175223

176224

177225
class RSVP_SenderTSPEC(Packet):
226+
"""SENDER_TSPEC object RFC 2210 section 3.1.
227+
Carries the sender's traffic specification for the reservation.
228+
"""
178229
name = "Sender_TSPEC"
179230
overload_fields = {RSVP_Object: {"Class": 0x0c}}
180-
fields_desc = [ByteField("Msg_Format", 0),
181-
ByteField("reserve", 0),
182-
ShortField("Data_Length", 4),
183-
ByteField("Srv_hdr", 1),
184-
ByteField("reserve2", 0),
185-
ShortField("Srv_Length", 4),
186-
StrLenField("Tokens", "", length_from=lambda pkt:pkt.underlayer.Length - 12)] # noqa: E501
231+
fields_desc = [
232+
ByteField("Msg_Format", 0),
233+
ByteField("reserve", 0),
234+
ShortField("Data_Length", 4),
235+
ByteField("Srv_hdr", 1),
236+
ByteField("reserve2", 0),
237+
ShortField("Srv_Length", 4),
238+
StrLenField(
239+
"Tokens",
240+
"",
241+
length_from=lambda pkt: pkt.underlayer.Length - 12
242+
),
243+
]
187244

188245
def default_payload_class(self, payload):
189246
return RSVP_Object
190247

191248

192249
class RSVP_LabelReq(Packet):
250+
"""LABEL_REQUEST object RFC 3209 section 4.2.
251+
Requests that a label be allocated for this LSP, and indicates
252+
which layer 3 protocol the label will carry.
253+
"""
193254
name = "Label Req"
194255
overload_fields = {RSVP_Object: {"Class": 0x13}}
195256
fields_desc = [ShortField("reserve", 1),
@@ -200,18 +261,29 @@ def default_payload_class(self, payload):
200261

201262

202263
class RSVP_SessionAttrb(Packet):
264+
"""SESSION_ATTRIBUTE object RFC 3209 section 4.7.
265+
Carries session data, mainly used for setup/recovery
266+
priority and human readable session name.
267+
"""
203268
name = "Session_Attribute"
204269
overload_fields = {RSVP_Object: {"Class": 0xCF}}
205-
fields_desc = [ByteField("Setup_priority", 1),
206-
ByteField("Hold_priority", 1),
207-
ByteField("flags", 1),
208-
FieldLenField("Name_length", None, length_of="Name"),
209-
StrLenField("Name", "", length_from=lambda pkt:pkt.Name_length), # noqa: E501
210-
]
270+
fields_desc = [
271+
ByteField("Setup_priority", 1),
272+
ByteField("Hold_priority", 1),
273+
ByteField("flags", 1),
274+
FieldLenField("Name_length", None, length_of="Name"),
275+
StrLenField(
276+
"Name",
277+
"",
278+
length_from=lambda pkt: pkt.Name_length
279+
),
280+
]
211281

212282
def default_payload_class(self, payload):
213283
return RSVP_Object
214284

215285

286+
# Decode IP packets with protocol number 46 as RSVP packets,
287+
# and RSVP packets as RSVP_Object packets.
216288
bind_layers(IP, RSVP, {"proto": 46})
217289
bind_layers(RSVP, RSVP_Object)

test/contrib/rsvp.uts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,51 @@ pkt = Ether(raw(pkt))
99
assert RSVP_SessionAttrb in pkt
1010
assert pkt.Name == b"test"
1111

12+
# TODO: Add more objects to default build
13+
1214
= Master dissection
1315

1416
pkt = Ether(b"\x00\x90\x92\x9d\x94\x01\x00\xd0c\xc3\xb8G\x08\x00E\x00\x00\x80\x8ad\x00\x00\xff.\x8c\xe7\xd2\x00\x00\x02\xd2\x00\x00\x01\x10\x02\xeb\xfa\xff\x00\x00l\x00\x10\x01\x07\x10\x02\x02\x02\x00\x00\x00\x01\x11\x03\x03\x03\x00\x0c\x03\x01\xd2\x00\x00\x02\x00\x00\x00\x00\x00\x08\x05\x01\x00\x00u0\x00\x08\x08\x01\x00\x00\x00\x12\x00$\t\x02\x00\x00\x00\x07\x05\x00\x00\x06\x7f\x00\x00\x05I\x18\x96\x80Dz\x00\x00\x7f\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0c\n\x07\x11\x03\x03\x03\x00\x00'\x11\x00\x08\x10\x01\x00\x00\x00\x10\x03\x06-\xad")
17+
# Header
18+
assert RSVP in pkt
19+
header = pkt[RSVP]
20+
assert header.Version == 1
21+
assert header.Class == 2
22+
assert header.TTL == 255
23+
assert header.Length == 108
24+
25+
# Session (C-type 7) object
26+
assert RSVP_SESSION in pkt
27+
session = pkt[RSVP_SESSION]
28+
assert session.dest_addr == "16.2.2.2"
29+
assert session.reserved == 0
30+
assert session.tunnel_id == 1
31+
assert session.ext_tunnel_id == "17.3.3.3"
32+
33+
# HOP object
34+
assert RSVP_HOP in pkt
35+
hop = pkt[RSVP_HOP]
36+
assert hop.neighbor == "210.0.0.2"
37+
assert hop.inface == 0
38+
39+
# TIME_VALUES object
1540
assert RSVP_Time in pkt
1641
assert pkt[RSVP_Time].refresh == 30000
42+
43+
# TODO: add the rest of the objects to master dissector once they are no longer generic
44+
45+
+ RSVP_SESSION
46+
47+
= RSVP_SESSION, build and dissect round-trip standalone
48+
49+
pkt = RSVP_Object() / RSVP_SESSION(dest_addr="10.0.0.1", reserved=6, tunnel_id=0, ext_tunnel_id="1.1.1.1")
50+
raw_pkt = raw(pkt)
51+
pkt2 = RSVP_Object(raw_pkt)
52+
assert RSVP_SESSION in pkt2
53+
assert pkt2.Class == 0x01
54+
assert pkt2.C_Type == 0x07
55+
assert pkt2.dest_addr == "10.0.0.1"
56+
assert pkt2.reserved == 6
57+
assert pkt2.tunnel_id == 0
58+
assert pkt2.ext_tunnel_id == "1.1.1.1"
59+
assert bytes(pkt2) == raw_pkt

0 commit comments

Comments
 (0)