Skip to content

Commit 48a8efd

Browse files
committed
Support P4.org In-band Network Telemetry (INT)
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 48a8efd

File tree

4 files changed

+1034
-1
lines changed

4 files changed

+1034
-1
lines changed

scapy/contrib/geneve.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@
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
2122
from scapy.layers.l2 import Ether, ETHER_TYPES
23+
from scapy.contrib.int import INTMetaMd, INTMetaMx
2224

2325
CLASS_IDS = {0x0100: "Linux",
2426
0x0101: "Open vSwitch",
@@ -49,6 +51,44 @@ def post_build(self, p, pay):
4951
p = p[:3] + struct.pack("!B", (p[3] & 0x3) | (tmp_len & 0x1f)) + p[4:]
5052
return p + pay
5153

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

5393
class GENEVE(Packet):
5494
name = "GENEVE"

scapy/contrib/int.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
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+
from scapy.packet import Packet, bind_layers
30+
from scapy.fields import BitField, BitEnumField, FlagsField, ByteField, \
31+
ShortField, IntField, LongField, FieldLenField, ConditionalField, \
32+
MultipleTypeField, PacketField, PacketListField
33+
from scapy.layers.l2 import GRE
34+
from scapy.layers.inet import TCP, UDP
35+
from scapy.layers.vxlan import VXLAN
36+
37+
INT_PRI_MASK = 0x80
38+
INT_L4_DPORT = 0x4568
39+
INT_GRE_PROTOCOL = 0x4569
40+
INT_VXLAN_PROTOCOL = 0x82
41+
INT_GENEVE_CLASSID = 0x0103
42+
43+
_INT_TYPE = {
44+
1: 'INT-MD',
45+
2: 'INT-DST',
46+
3: 'INT-MX',
47+
}
48+
49+
_INT_GRE = {
50+
0: 'Original packet with GRE',
51+
1: 'Original packet without GRE',
52+
}
53+
54+
_INT_GPE = {
55+
0: 'Original packet used VXLAN GPE encapsulation',
56+
1: 'Original packet used VXLAN encapsulation',
57+
}
58+
59+
_INT_INSTR_BITMAP = [
60+
'checksum',
61+
'reserved14',
62+
'reserved13',
63+
'reserved12',
64+
'reserved11',
65+
'reserved10',
66+
'reserved9',
67+
'buf_info',
68+
'tx_info',
69+
'l2_intf',
70+
'egr_ts',
71+
'igr_ts',
72+
'que_info',
73+
'latency',
74+
'l1_intf',
75+
'node_id',
76+
]
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+
168+
class INTMetaMx(Packet):
169+
name = 'INTMetaMx'
170+
fields_desc = [
171+
BitField('version', 0, 4),
172+
BitField('discard', 0, 1),
173+
BitField('reserved1', 0, 27),
174+
FlagsField('instr_bitmap', 0xfe00, 16, _INT_INSTR_BITMAP),
175+
ShortField('ds_id', 0),
176+
ShortField('ds_instr', 0),
177+
ShortField('ds_flags', 0),
178+
]
179+
180+
def extract_padding(self, s):
181+
return "", s
182+
183+
184+
class INTMetaMd(Packet):
185+
name = 'INTMetaMd'
186+
fields_desc = [
187+
BitField('version', 0, 4),
188+
BitField('discard', 0, 1),
189+
BitField('exceed_mht', 0, 1),
190+
BitField('exceed_mtu', 0, 1),
191+
BitField('reserved0', 0, 12),
192+
BitField('hop_len', 0, 5),
193+
BitField('hop_left', 0, 8),
194+
FlagsField('instr_bitmap', 0xfe00, 16, _INT_INSTR_BITMAP),
195+
ShortField('ds_id', 0),
196+
ShortField('ds_instr', 0),
197+
ShortField('ds_flags', 0),
198+
PacketListField('meta_hops', [], INTMetaHop,
199+
length_from=lambda pkt: pkt.parent.length * 4 - 12),
200+
]
201+
202+
def post_build(self, pkt, pay):
203+
if self.meta_hops is not None:
204+
tmp_len = len(self.meta_hops[0]) // 4
205+
old_value = struct.unpack("B", pkt[2:3])[0]
206+
new_value = (old_value & 0b11100000) | (tmp_len & 0b00011111)
207+
pkt = pkt[:2] + struct.pack("B", new_value) + pkt[3:]
208+
return pkt + pay
209+
210+
def extract_padding(self, s):
211+
return "", s
212+
213+
214+
class INTShimTcpUdp(Packet):
215+
name = 'INTShimTcpUdp'
216+
fields_desc = [
217+
BitEnumField('type', 1, 4, _INT_TYPE),
218+
BitField('npt', 0, 2),
219+
BitField('reserved1', 0, 2),
220+
FieldLenField('length', None,
221+
length_of="metadata",
222+
adjust=lambda pkt, x: x // 4, fmt="B"),
223+
ConditionalField(ByteField('reserved3', 0), lambda pkt: pkt.npt == 0),
224+
ConditionalField(BitField('dscp', 0, 6), lambda pkt: pkt.npt == 0),
225+
ConditionalField(BitField('reserved4', 0, 2), lambda pkt: pkt.npt == 0),
226+
ConditionalField(ShortField('l4_dport', 0), lambda pkt: pkt.npt == 1),
227+
ConditionalField(ByteField('ip_proto', 0), lambda pkt: pkt.npt == 2),
228+
ConditionalField(ByteField('reserved5', 0), lambda pkt: pkt.npt == 2),
229+
MultipleTypeField([
230+
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
231+
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3), ],
232+
PacketField('metadata', None, INTMetaMd)
233+
),
234+
]
235+
236+
237+
class INTShimGre(Packet):
238+
name = 'INTShimGre'
239+
fields_desc = [
240+
BitEnumField('type', 1, 4, _INT_TYPE),
241+
BitEnumField('gre', 0, 1, _INT_GRE),
242+
BitField('reserved0', 0, 3),
243+
FieldLenField('length', None,
244+
length_of="metadata",
245+
adjust=lambda pkt, x: x // 4, fmt="B"),
246+
ShortField('gre_proto', 0),
247+
MultipleTypeField([
248+
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
249+
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3), ],
250+
PacketField('metadata', None, INTMetaMd)
251+
),
252+
]
253+
254+
255+
class INTShimVxlan(Packet):
256+
name = 'INTShimVxlan'
257+
fields_desc = [
258+
BitEnumField('type', 1, 4, _INT_TYPE),
259+
BitField('reserved2', 0, 4),
260+
FieldLenField('length', None,
261+
length_of="metadata",
262+
adjust=lambda pkt, x: x // 4, fmt="B"),
263+
BitEnumField('gpe', 0, 1, _INT_GPE),
264+
BitField('reserved6', 0, 7),
265+
ByteField('vxlan_proto', 0),
266+
MultipleTypeField([
267+
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
268+
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3), ],
269+
PacketField('metadata', None, INTMetaMd)
270+
),
271+
]
272+
273+
274+
bind_layers(UDP, INTShimTcpUdp, dport=INT_L4_DPORT)
275+
bind_layers(TCP, INTShimTcpUdp, dport=INT_L4_DPORT)
276+
bind_layers(GRE, INTShimGre, proto=INT_GRE_PROTOCOL)
277+
bind_layers(VXLAN, INTShimVxlan, NextProtocol=INT_VXLAN_PROTOCOL)

scapy/layers/inet.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,15 @@ 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
812+
((self.underlayer.tos & INT_PRI_MASK) == INT_PRI_MASK)) or
813+
(isinstance(self.underlayer, scapy.layers.inet6.IPv6) and
814+
((self.underlayer.tc & INT_PRI_MASK) == INT_PRI_MASK))):
815+
return INTShimTcpUdp
816+
return Packet.guess_payload_class(self, payload)
817+
809818

810819
class UDP(Packet):
811820
name = "UDP"
@@ -862,6 +871,15 @@ def mysummary(self):
862871
else:
863872
return self.sprintf("UDP %UDP.sport% > %UDP.dport%")
864873

874+
def guess_payload_class(self, payload):
875+
from scapy.contrib.int import INTShimTcpUdp, INT_PRI_MASK
876+
if ((isinstance(self.underlayer, IP) and
877+
((self.underlayer.tos & INT_PRI_MASK) == INT_PRI_MASK)) or
878+
(isinstance(self.underlayer, scapy.layers.inet6.IPv6) and
879+
((self.underlayer.tc & INT_PRI_MASK) == INT_PRI_MASK))):
880+
return INTShimTcpUdp
881+
return Packet.guess_payload_class(self, payload)
882+
865883

866884
icmptypes = {0: "echo-reply",
867885
3: "dest-unreach",

0 commit comments

Comments
 (0)