Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support P4.org In-band Network Telemetry (INT) #4230

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion scapy/contrib/geneve.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@

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
from scapy.layers.l2 import Ether, ETHER_TYPES
from scapy.contrib.int import INTMetaMd, INTMetaMx

CLASS_IDS = {0x0100: "Linux",
0x0101: "Open vSwitch",
Expand Down Expand Up @@ -49,6 +51,44 @@ 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


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
277 changes: 277 additions & 0 deletions scapy/contrib/int.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
# 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 Format:
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
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 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 is not 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)
18 changes: 18 additions & 0 deletions scapy/layers/inet.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,15 @@ def mysummary(self):
else:
return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%")

def guess_payload_class(self, payload):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its not a good idea to import your contrib layer in guess_payload_class of TCP. Can you maybe move this port to dispatch_hook?

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 +871,15 @@ def mysummary(self):
else:
return self.sprintf("UDP %UDP.sport% > %UDP.dport%")

def guess_payload_class(self, payload):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

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
Loading