Skip to content

Commit

Permalink
[INT] support P4.org INT layer
Browse files Browse the repository at this point in the history
1.support INT MX/MD mode.
2.support INT over IPv4/IPv6+TCP/UDP/VXLAN/GRE/GENEVE.
3.add 28 testcases for INT.
  • Loading branch information
jingfeihu committed Jan 25, 2024
1 parent d71014a commit 84a8dde
Show file tree
Hide file tree
Showing 4 changed files with 1,014 additions and 1 deletion.
40 changes: 39 additions & 1 deletion scapy/contrib/geneve.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@

import struct

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

@classmethod
def dispatch_hook(cls, _pkt=None, *args, **kargs):
if _pkt and len(_pkt) >= 2:
classid = struct.unpack("!H", _pkt[:2])[0]
if classid == 0x0103:
return GeneveOptINT
return cls

from scapy.contrib.int import INTMetaMd, INTMetaMx
class GeneveOptINT(Packet):
name = "Geneve Option INT"
fields_desc = [
XShortEnumField("classid", 0x0000, CLASS_IDS),
XByteField("type", 0x03),
BitField("reserved", 0, 3),
BitField("length", 1, 5),
MultipleTypeField([
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
PacketField('metadata', None, INTMetaMd)
),
]

def post_build(self, pkt, pay):
tmp_len = len(self.metadata) // 4
old_value = struct.unpack("B", pkt[3:4])[0]
new_value = (old_value & 0b11100000) | (tmp_len & 0b00011111)
pkt = pkt[:3] + struct.pack("B", new_value) + pkt[4:]
return pkt + pay

@classmethod
def dispatch_hook(cls, _pkt=None, *args, **kargs):
if _pkt and len(_pkt) >= 2:
classid = struct.unpack("!H", _pkt[:2])[0]
if classid != 0x0103:
return GeneveOptions
return cls

class GENEVE(Packet):
name = "GENEVE"
Expand Down
265 changes: 265 additions & 0 deletions scapy/contrib/int.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# scapy.contrib.description = Inband Network Telemetry Protocol (INT)
# scapy.contrib.status = loads

'''
Inband Network Telemetry Protocol (INT)
References:
https://staging.p4.org/p4-spec/docs/INT_v2_1.pdf
https://staging.p4.org/p4-spec/docs/telemetry_report_v2_0.pdf
https://github.com/p4lang/p4-applications
Example Packet Formate:
INT-MX mode:
INToGre = Ether/IP/GRE/INTShimGre/INTMetaMx/Raw
INToTCP = Ether/IP/TCP/INTShimTcpUdp/INTMetaMx/Raw
INToUDP = Ether/IP/UDP/INTShimTcpUdp/INTMetaMx/Raw
INToVXLAN = Ether/IP/UDP/VXLAN/INTShimVxlan/INTMetaMx/Raw
INToGENEVE = Ether/IP/UDP/GENEVE/GeneveOptINT/INTMetaMx/Raw
INT-MD mode:
INToGre = Ether/IP/GRE/INTShimGre/INTMetaMd/INTMetaHop/Raw
INToTCP = Ether/IP/TCP/INTShimTcpUdp/INTMetaMd/INTMetaHop/Raw
INToUDP = Ether/IP/UDP/INTShimTcpUdp/INTMetaMd/INTMetaHop/Raw
INToVXLAN = Ether/IP/UDP/VXLAN/INTShimVxlan/INTMetaMd/INTMetaHop/Raw
INToGENEVE = Ether/IP/UDP/GENEVE/GeneveOptINT/INTMetaMd/INTMetaHop/Raw
'''

import struct
import socket
from scapy.packet import Packet, bind_layers
from scapy.fields import BitField, BitEnumField, FlagsField, ByteField, \
ShortField, IntField, LongField, FieldLenField, ConditionalField, \
MultipleTypeField, PacketField, PacketListField
from scapy.layers.l2 import GRE
from scapy.layers.inet import IP, TCP, UDP
from scapy.layers.vxlan import VXLAN

INT_PRI_MASK = 0x80
INT_L4_DPORT = 0x4568
INT_GRE_PROTOCOL = 0x4569
INT_VXLAN_PROTOCOL = 0x82
INT_GENEVE_CLASSID = 0x0103

_INT_TYPE = {
1: 'INT-MD',
2: 'INT-DST',
3: 'INT-MX',
}

_INT_GRE = {
0: 'Original packet with GRE',
1: 'Original packet without GRE',
}

_INT_GPE = {
0: 'Original packet used VXLAN GPE encapsulation',
1: 'Original packet used VXLAN encapsulation',
}

_INT_INSTR_BITMAP = [
'checksum',
'reserved14',
'reserved13',
'reserved12',
'reserved11',
'reserved10',
'reserved9',
'buf_info',
'tx_info',
'l2_intf',
'egr_ts',
'igr_ts',
'que_info',
'latency',
'l1_intf',
'node_id',
]

class INTMetaHop(Packet):
name = 'INTMetaHop'
fields_desc = [
ConditionalField(
IntField('node_id', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 0))
),
ConditionalField(
ShortField('igr_l1_intf', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 1))
),
ConditionalField(
ShortField('egr_l1_intf', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 1))
),
ConditionalField(
IntField('latency', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 2))
),
ConditionalField(
BitField('que_id', 0, 8),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 3))
),
ConditionalField(
BitField('que_occupy', 0, 24),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 3))
),
ConditionalField(
LongField('igr_ts', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 4))
),
ConditionalField(
LongField('egr_ts', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 5))
),
ConditionalField(
IntField('igr_l2_intf', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 6))
),
ConditionalField(
IntField('egr_l2_intf', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 6))
),
ConditionalField(
IntField('egr_tx_info', 0),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 7))
),
ConditionalField(
BitField('buf_id', 0, 8),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 8))
),
ConditionalField(
BitField('buf_occupy', 0, 24),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 8))
),
ConditionalField(
IntField('reserved9', 0xffffffff),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 9))
),
ConditionalField(
IntField('reserved10', 0xffffffff),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 10))
),
ConditionalField(
IntField('reserved11', 0xffffffff),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 11))
),
ConditionalField(
IntField('reserved12', 0xffffffff),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 12))
),
ConditionalField(
IntField('reserved13', 0xffffffff),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 13))
),
ConditionalField(
IntField('reserved14', 0xffffffff),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 14))
),
ConditionalField(
IntField('checksum', 0xffffffff),
lambda pkt:pkt.parent.instr_bitmap & (0x1 << (15 - 15))
),
]

def extract_padding(self, s):
return "", s

class INTMetaMx(Packet):
name = 'INTMetaMx'
fields_desc = [
BitField('version', 0, 4),
BitField('discard', 0, 1),
BitField('reserved1', 0, 27),
FlagsField('instr_bitmap', 0xfe00, 16, _INT_INSTR_BITMAP),
ShortField('ds_id', 0),
ShortField('ds_instr', 0),
ShortField('ds_flags', 0),
]

def extract_padding(self, s):
return "", s

class INTMetaMd(Packet):
name = 'INTMetaMd'
fields_desc = [
BitField('version', 0, 4),
BitField('discard', 0, 1),
BitField('exceed_mht', 0, 1),
BitField('exceed_mtu', 0, 1),
BitField('reserved0', 0, 12),
BitField('hop_len', 0, 5),
BitField('hop_left', 0, 8),
FlagsField('instr_bitmap', 0xfe00, 16, _INT_INSTR_BITMAP),
ShortField('ds_id', 0),
ShortField('ds_instr', 0),
ShortField('ds_flags', 0),
PacketListField('meta_hops', [], INTMetaHop,
length_from=lambda pkt: pkt.parent.length * 4 - 12),
]

def post_build(self, pkt, pay):
if self.meta_hops != None:
tmp_len = len(self.meta_hops[0]) // 4
old_value = struct.unpack("B", pkt[2:3])[0]
new_value = (old_value & 0b11100000) | (tmp_len & 0b00011111)
pkt = pkt[:2] + struct.pack("B", new_value) + pkt[3:]
return pkt + pay

def extract_padding(self, s):
return "", s

class INTShimTcpUdp(Packet):
name = 'INTShimTcpUdp'
fields_desc = [
BitEnumField('type', 1, 4, _INT_TYPE),
BitField('npt', 0, 2),
BitField('reserved1', 0, 2),
FieldLenField('length', None, length_of="metadata", adjust=lambda pkt, x: x // 4, fmt="B"),
ConditionalField(ByteField('reserved3', 0), lambda pkt: pkt.npt == 0),
ConditionalField(BitField('dscp', 0, 6), lambda pkt: pkt.npt == 0),
ConditionalField(BitField('reserved4', 0, 2), lambda pkt: pkt.npt == 0),
ConditionalField(ShortField('l4_dport', 0), lambda pkt: pkt.npt == 1),
ConditionalField(ByteField('ip_proto', 0), lambda pkt: pkt.npt == 2),
ConditionalField(ByteField('reserved5', 0), lambda pkt: pkt.npt == 2),
MultipleTypeField([
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
PacketField('metadata', None, INTMetaMd)
),
]

class INTShimGre(Packet):
name = 'INTShimGre'
fields_desc = [
BitEnumField('type', 1, 4, _INT_TYPE),
BitEnumField('gre', 0, 1, _INT_GRE),
BitField('reserved0', 0, 3),
FieldLenField('length', None, length_of="metadata", adjust=lambda pkt, x: x // 4, fmt="B"),
ShortField('gre_proto', 0),
MultipleTypeField([
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
PacketField('metadata', None, INTMetaMd)
),
]

class INTShimVxlan(Packet):
name = 'INTShimVxlan'
fields_desc = [
BitEnumField('type', 1, 4, _INT_TYPE),
BitField('reserved2', 0, 4),
FieldLenField('length', None, length_of="metadata", adjust=lambda pkt, x: x // 4, fmt="B"),
BitEnumField('gpe', 0, 1, _INT_GPE),
BitField('reserved6', 0, 7),
ByteField('vxlan_proto', 0),
MultipleTypeField([
(PacketField('metadata', None, INTMetaMd), lambda pkt: pkt.type == 1),
(PacketField('metadata', None, INTMetaMx), lambda pkt: pkt.type == 3),],
PacketField('metadata', None, INTMetaMd)
),
]

bind_layers(UDP, INTShimTcpUdp, dport=INT_L4_DPORT)
bind_layers(TCP, INTShimTcpUdp, dport=INT_L4_DPORT)
bind_layers(GRE, INTShimGre, proto=INT_GRE_PROTOCOL)
bind_layers(VXLAN, INTShimVxlan, NextProtocol=INT_VXLAN_PROTOCOL)
12 changes: 12 additions & 0 deletions scapy/layers/inet.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,12 @@ def mysummary(self):
else:
return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%")

def guess_payload_class(self, payload):
from scapy.contrib.int import INTShimTcpUdp, INT_PRI_MASK
if ((isinstance(self.underlayer, IP) and ((self.underlayer.tos & INT_PRI_MASK) == INT_PRI_MASK)) or
(isinstance(self.underlayer, scapy.layers.inet6.IPv6) and ((self.underlayer.tc & INT_PRI_MASK) == INT_PRI_MASK))):
return INTShimTcpUdp
return Packet.guess_payload_class(self, payload)

class UDP(Packet):
name = "UDP"
Expand Down Expand Up @@ -862,6 +868,12 @@ def mysummary(self):
else:
return self.sprintf("UDP %UDP.sport% > %UDP.dport%")

def guess_payload_class(self, payload):
from scapy.contrib.int import INTShimTcpUdp, INT_PRI_MASK
if ((isinstance(self.underlayer, IP) and ((self.underlayer.tos & INT_PRI_MASK) == INT_PRI_MASK)) or
(isinstance(self.underlayer, scapy.layers.inet6.IPv6) and ((self.underlayer.tc & INT_PRI_MASK) == INT_PRI_MASK))):
return INTShimTcpUdp
return Packet.guess_payload_class(self, payload)

icmptypes = {0: "echo-reply",
3: "dest-unreach",
Expand Down
Loading

0 comments on commit 84a8dde

Please sign in to comment.