Skip to content

Commit 84a8dde

Browse files
committed
[INT] support P4.org INT layer
1.support INT MX/MD mode. 2.support INT over IPv4/IPv6+TCP/UDP/VXLAN/GRE/GENEVE. 3.add 28 testcases for INT.
1 parent d71014a commit 84a8dde

File tree

4 files changed

+1014
-1
lines changed

4 files changed

+1014
-1
lines changed

scapy/contrib/geneve.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
import struct
1616

17-
from scapy.fields import BitField, XByteField, XShortEnumField, X3BytesField, StrLenField, PacketListField
17+
from scapy.fields import BitField, XByteField, XShortEnumField, X3BytesField, \
18+
StrLenField, PacketField, PacketListField, MultipleTypeField
1819
from scapy.packet import Packet, bind_layers
1920
from scapy.layers.inet import IP, UDP
2021
from scapy.layers.inet6 import IPv6
@@ -49,6 +50,43 @@ def post_build(self, p, pay):
4950
p = p[:3] + struct.pack("!B", (p[3] & 0x3) | (tmp_len & 0x1f)) + p[4:]
5051
return p + pay
5152

53+
@classmethod
54+
def dispatch_hook(cls, _pkt=None, *args, **kargs):
55+
if _pkt and len(_pkt) >= 2:
56+
classid = struct.unpack("!H", _pkt[:2])[0]
57+
if classid == 0x0103:
58+
return GeneveOptINT
59+
return cls
60+
61+
from scapy.contrib.int import INTMetaMd, INTMetaMx
62+
class GeneveOptINT(Packet):
63+
name = "Geneve Option INT"
64+
fields_desc = [
65+
XShortEnumField("classid", 0x0000, CLASS_IDS),
66+
XByteField("type", 0x03),
67+
BitField("reserved", 0, 3),
68+
BitField("length", 1, 5),
69+
MultipleTypeField([
70+
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
71+
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
72+
PacketField('metadata', None, INTMetaMd)
73+
),
74+
]
75+
76+
def post_build(self, pkt, pay):
77+
tmp_len = len(self.metadata) // 4
78+
old_value = struct.unpack("B", pkt[3:4])[0]
79+
new_value = (old_value & 0b11100000) | (tmp_len & 0b00011111)
80+
pkt = pkt[:3] + struct.pack("B", new_value) + pkt[4:]
81+
return pkt + pay
82+
83+
@classmethod
84+
def dispatch_hook(cls, _pkt=None, *args, **kargs):
85+
if _pkt and len(_pkt) >= 2:
86+
classid = struct.unpack("!H", _pkt[:2])[0]
87+
if classid != 0x0103:
88+
return GeneveOptions
89+
return cls
5290

5391
class GENEVE(Packet):
5492
name = "GENEVE"

scapy/contrib/int.py

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# scapy.contrib.description = Inband Network Telemetry Protocol (INT)
2+
# scapy.contrib.status = loads
3+
4+
'''
5+
Inband Network Telemetry Protocol (INT)
6+
7+
References:
8+
https://staging.p4.org/p4-spec/docs/INT_v2_1.pdf
9+
https://staging.p4.org/p4-spec/docs/telemetry_report_v2_0.pdf
10+
https://github.com/p4lang/p4-applications
11+
12+
Example Packet Formate:
13+
INT-MX mode:
14+
INToGre = Ether/IP/GRE/INTShimGre/INTMetaMx/Raw
15+
INToTCP = Ether/IP/TCP/INTShimTcpUdp/INTMetaMx/Raw
16+
INToUDP = Ether/IP/UDP/INTShimTcpUdp/INTMetaMx/Raw
17+
INToVXLAN = Ether/IP/UDP/VXLAN/INTShimVxlan/INTMetaMx/Raw
18+
INToGENEVE = Ether/IP/UDP/GENEVE/GeneveOptINT/INTMetaMx/Raw
19+
20+
INT-MD mode:
21+
INToGre = Ether/IP/GRE/INTShimGre/INTMetaMd/INTMetaHop/Raw
22+
INToTCP = Ether/IP/TCP/INTShimTcpUdp/INTMetaMd/INTMetaHop/Raw
23+
INToUDP = Ether/IP/UDP/INTShimTcpUdp/INTMetaMd/INTMetaHop/Raw
24+
INToVXLAN = Ether/IP/UDP/VXLAN/INTShimVxlan/INTMetaMd/INTMetaHop/Raw
25+
INToGENEVE = Ether/IP/UDP/GENEVE/GeneveOptINT/INTMetaMd/INTMetaHop/Raw
26+
'''
27+
28+
import struct
29+
import socket
30+
from scapy.packet import Packet, bind_layers
31+
from scapy.fields import BitField, BitEnumField, FlagsField, ByteField, \
32+
ShortField, IntField, LongField, FieldLenField, ConditionalField, \
33+
MultipleTypeField, PacketField, PacketListField
34+
from scapy.layers.l2 import GRE
35+
from scapy.layers.inet import IP, TCP, UDP
36+
from scapy.layers.vxlan import VXLAN
37+
38+
INT_PRI_MASK = 0x80
39+
INT_L4_DPORT = 0x4568
40+
INT_GRE_PROTOCOL = 0x4569
41+
INT_VXLAN_PROTOCOL = 0x82
42+
INT_GENEVE_CLASSID = 0x0103
43+
44+
_INT_TYPE = {
45+
1: 'INT-MD',
46+
2: 'INT-DST',
47+
3: 'INT-MX',
48+
}
49+
50+
_INT_GRE = {
51+
0: 'Original packet with GRE',
52+
1: 'Original packet without GRE',
53+
}
54+
55+
_INT_GPE = {
56+
0: 'Original packet used VXLAN GPE encapsulation',
57+
1: 'Original packet used VXLAN encapsulation',
58+
}
59+
60+
_INT_INSTR_BITMAP = [
61+
'checksum',
62+
'reserved14',
63+
'reserved13',
64+
'reserved12',
65+
'reserved11',
66+
'reserved10',
67+
'reserved9',
68+
'buf_info',
69+
'tx_info',
70+
'l2_intf',
71+
'egr_ts',
72+
'igr_ts',
73+
'que_info',
74+
'latency',
75+
'l1_intf',
76+
'node_id',
77+
]
78+
79+
class INTMetaHop(Packet):
80+
name = 'INTMetaHop'
81+
fields_desc = [
82+
ConditionalField(
83+
IntField('node_id', 0),
84+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 0))
85+
),
86+
ConditionalField(
87+
ShortField('igr_l1_intf', 0),
88+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 1))
89+
),
90+
ConditionalField(
91+
ShortField('egr_l1_intf', 0),
92+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 1))
93+
),
94+
ConditionalField(
95+
IntField('latency', 0),
96+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 2))
97+
),
98+
ConditionalField(
99+
BitField('que_id', 0, 8),
100+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 3))
101+
),
102+
ConditionalField(
103+
BitField('que_occupy', 0, 24),
104+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 3))
105+
),
106+
ConditionalField(
107+
LongField('igr_ts', 0),
108+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 4))
109+
),
110+
ConditionalField(
111+
LongField('egr_ts', 0),
112+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 5))
113+
),
114+
ConditionalField(
115+
IntField('igr_l2_intf', 0),
116+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 6))
117+
),
118+
ConditionalField(
119+
IntField('egr_l2_intf', 0),
120+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 6))
121+
),
122+
ConditionalField(
123+
IntField('egr_tx_info', 0),
124+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 7))
125+
),
126+
ConditionalField(
127+
BitField('buf_id', 0, 8),
128+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 8))
129+
),
130+
ConditionalField(
131+
BitField('buf_occupy', 0, 24),
132+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 8))
133+
),
134+
ConditionalField(
135+
IntField('reserved9', 0xffffffff),
136+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 9))
137+
),
138+
ConditionalField(
139+
IntField('reserved10', 0xffffffff),
140+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 10))
141+
),
142+
ConditionalField(
143+
IntField('reserved11', 0xffffffff),
144+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 11))
145+
),
146+
ConditionalField(
147+
IntField('reserved12', 0xffffffff),
148+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 12))
149+
),
150+
ConditionalField(
151+
IntField('reserved13', 0xffffffff),
152+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 13))
153+
),
154+
ConditionalField(
155+
IntField('reserved14', 0xffffffff),
156+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 14))
157+
),
158+
ConditionalField(
159+
IntField('checksum', 0xffffffff),
160+
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 15))
161+
),
162+
]
163+
164+
def extract_padding(self, s):
165+
return "", s
166+
167+
class INTMetaMx(Packet):
168+
name = 'INTMetaMx'
169+
fields_desc = [
170+
BitField('version', 0, 4),
171+
BitField('discard', 0, 1),
172+
BitField('reserved1', 0, 27),
173+
FlagsField('instr_bitmap', 0xfe00, 16, _INT_INSTR_BITMAP),
174+
ShortField('ds_id', 0),
175+
ShortField('ds_instr', 0),
176+
ShortField('ds_flags', 0),
177+
]
178+
179+
def extract_padding(self, s):
180+
return "", s
181+
182+
class INTMetaMd(Packet):
183+
name = 'INTMetaMd'
184+
fields_desc = [
185+
BitField('version', 0, 4),
186+
BitField('discard', 0, 1),
187+
BitField('exceed_mht', 0, 1),
188+
BitField('exceed_mtu', 0, 1),
189+
BitField('reserved0', 0, 12),
190+
BitField('hop_len', 0, 5),
191+
BitField('hop_left', 0, 8),
192+
FlagsField('instr_bitmap', 0xfe00, 16, _INT_INSTR_BITMAP),
193+
ShortField('ds_id', 0),
194+
ShortField('ds_instr', 0),
195+
ShortField('ds_flags', 0),
196+
PacketListField('meta_hops', [], INTMetaHop,
197+
length_from=lambda pkt: pkt.parent.length * 4 - 12),
198+
]
199+
200+
def post_build(self, pkt, pay):
201+
if self.meta_hops != None:
202+
tmp_len = len(self.meta_hops[0]) // 4
203+
old_value = struct.unpack("B", pkt[2:3])[0]
204+
new_value = (old_value & 0b11100000) | (tmp_len & 0b00011111)
205+
pkt = pkt[:2] + struct.pack("B", new_value) + pkt[3:]
206+
return pkt + pay
207+
208+
def extract_padding(self, s):
209+
return "", s
210+
211+
class INTShimTcpUdp(Packet):
212+
name = 'INTShimTcpUdp'
213+
fields_desc = [
214+
BitEnumField('type', 1, 4, _INT_TYPE),
215+
BitField('npt', 0, 2),
216+
BitField('reserved1', 0, 2),
217+
FieldLenField('length', None, length_of="metadata", adjust=lambda pkt, x: x // 4, fmt="B"),
218+
ConditionalField(ByteField('reserved3', 0), lambda pkt: pkt.npt == 0),
219+
ConditionalField(BitField('dscp', 0, 6), lambda pkt: pkt.npt == 0),
220+
ConditionalField(BitField('reserved4', 0, 2), lambda pkt: pkt.npt == 0),
221+
ConditionalField(ShortField('l4_dport', 0), lambda pkt: pkt.npt == 1),
222+
ConditionalField(ByteField('ip_proto', 0), lambda pkt: pkt.npt == 2),
223+
ConditionalField(ByteField('reserved5', 0), lambda pkt: pkt.npt == 2),
224+
MultipleTypeField([
225+
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
226+
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
227+
PacketField('metadata', None, INTMetaMd)
228+
),
229+
]
230+
231+
class INTShimGre(Packet):
232+
name = 'INTShimGre'
233+
fields_desc = [
234+
BitEnumField('type', 1, 4, _INT_TYPE),
235+
BitEnumField('gre', 0, 1, _INT_GRE),
236+
BitField('reserved0', 0, 3),
237+
FieldLenField('length', None, length_of="metadata", adjust=lambda pkt, x: x // 4, fmt="B"),
238+
ShortField('gre_proto', 0),
239+
MultipleTypeField([
240+
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
241+
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
242+
PacketField('metadata', None, INTMetaMd)
243+
),
244+
]
245+
246+
class INTShimVxlan(Packet):
247+
name = 'INTShimVxlan'
248+
fields_desc = [
249+
BitEnumField('type', 1, 4, _INT_TYPE),
250+
BitField('reserved2', 0, 4),
251+
FieldLenField('length', None, length_of="metadata", adjust=lambda pkt, x: x // 4, fmt="B"),
252+
BitEnumField('gpe', 0, 1, _INT_GPE),
253+
BitField('reserved6', 0, 7),
254+
ByteField('vxlan_proto', 0),
255+
MultipleTypeField([
256+
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
257+
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
258+
PacketField('metadata', None, INTMetaMd)
259+
),
260+
]
261+
262+
bind_layers(UDP, INTShimTcpUdp, dport=INT_L4_DPORT)
263+
bind_layers(TCP, INTShimTcpUdp, dport=INT_L4_DPORT)
264+
bind_layers(GRE, INTShimGre, proto=INT_GRE_PROTOCOL)
265+
bind_layers(VXLAN, INTShimVxlan, NextProtocol=INT_VXLAN_PROTOCOL)

scapy/layers/inet.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,12 @@ def mysummary(self):
806806
else:
807807
return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%")
808808

809+
def guess_payload_class(self, payload):
810+
from scapy.contrib.int import INTShimTcpUdp, INT_PRI_MASK
811+
if ((isinstance(self.underlayer, IP) and ((self.underlayer.tos & INT_PRI_MASK) == INT_PRI_MASK)) or
812+
(isinstance(self.underlayer, scapy.layers.inet6.IPv6) and ((self.underlayer.tc & INT_PRI_MASK) == INT_PRI_MASK))):
813+
return INTShimTcpUdp
814+
return Packet.guess_payload_class(self, payload)
809815

810816
class UDP(Packet):
811817
name = "UDP"
@@ -862,6 +868,12 @@ def mysummary(self):
862868
else:
863869
return self.sprintf("UDP %UDP.sport% > %UDP.dport%")
864870

871+
def guess_payload_class(self, payload):
872+
from scapy.contrib.int import INTShimTcpUdp, INT_PRI_MASK
873+
if ((isinstance(self.underlayer, IP) and ((self.underlayer.tos & INT_PRI_MASK) == INT_PRI_MASK)) or
874+
(isinstance(self.underlayer, scapy.layers.inet6.IPv6) and ((self.underlayer.tc & INT_PRI_MASK) == INT_PRI_MASK))):
875+
return INTShimTcpUdp
876+
return Packet.guess_payload_class(self, payload)
865877

866878
icmptypes = {0: "echo-reply",
867879
3: "dest-unreach",

0 commit comments

Comments
 (0)