Skip to content

Commit

Permalink
Support In-situ Operation Administration and Maintenance Protocol (IOAM)
Browse files Browse the repository at this point in the history
1.support CMCC IOAM over ipv4/ipv6 + tcp/udp/vxlan
2.add CMCC IOAM unit tests
  • Loading branch information
jingfeihu committed Jan 25, 2024
1 parent d71014a commit d5d74ed
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 8 deletions.
33 changes: 33 additions & 0 deletions scapy/contrib/ioam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# scapy.contrib.description = In-situ Operation Administration and Maintenance Protocol (IOAM)
# scapy.contrib.status = loads

'''
https://datatracker.ietf.org/doc/html/rfc8321
IPv4.proto=186, IPv6.NextHeader=0x0 && IPv6.HBH.option_type=0x11
IOAMoIP = eth/ipv4/ioam/payload
IOAMoUDP = eth/ipv4/ioam/udp/payload
IOAMoTCP = eth/ipv4/ioam/tcp/payload
IOAMoVxLAN = o_eth/o_ipv4/ioam/o_udp/vxlan/i_eth/i_ipv4/i_tcp/payload
'''

import socket
from scapy.packet import Packet, bind_layers
from scapy.fields import BitField, ByteField
from scapy.layers.inet import IP, TCP, UDP

IPPROTO_IOAM = 186

class IOAM(Packet):
name = 'IOAM'
fields_desc = [
BitField('flow_id', 0, 20),
BitField('color_val', 0, 1),
BitField('delay_en', 0, 1),
BitField('color_en', 0, 1),
BitField('reserved', 0, 1),
ByteField('next_protocol', 0),
]

bind_layers(IP, IOAM, proto=IPPROTO_IOAM)
bind_layers(IOAM, TCP, next_protocol=socket.IPPROTO_TCP)
bind_layers(IOAM, UDP, next_protocol=socket.IPPROTO_UDP)
26 changes: 18 additions & 8 deletions scapy/layers/inet.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,11 +749,16 @@ def post_build(self, p, pay):
dataofs = (dataofs << 4) | orb(p[12]) & 0x0f
p = p[:12] + chb(dataofs & 0xff) + p[13:]
if self.chksum is None:
if isinstance(self.underlayer, IP):
ck = in4_chksum(socket.IPPROTO_TCP, self.underlayer, p)
selfdown = self.underlayer
from scapy.contrib.ioam import IOAM
if isinstance(self.underlayer, IOAM):
selfdown = self.underlayer.underlayer

if isinstance(selfdown, IP):
ck = in4_chksum(socket.IPPROTO_TCP, selfdown, p)
p = p[:16] + struct.pack("!H", ck) + p[18:]
elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p) # noqa: E501
elif conf.ipv6_enabled and isinstance(selfdown, scapy.layers.inet6.IPv6) or isinstance(selfdown, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, selfdown, p) # noqa: E501
p = p[:16] + struct.pack("!H", ck) + p[18:]
else:
log_runtime.info(
Expand Down Expand Up @@ -821,14 +826,19 @@ def post_build(self, p, pay):
tmp_len = len(p)
p = p[:4] + struct.pack("!H", tmp_len) + p[6:]
if self.chksum is None:
if isinstance(self.underlayer, IP):
ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p)
selfdown = self.underlayer
from scapy.contrib.ioam import IOAM
if isinstance(self.underlayer, IOAM):
selfdown = self.underlayer.underlayer

if isinstance(selfdown, IP):
ck = in4_chksum(socket.IPPROTO_UDP, selfdown, p)
# According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
if ck == 0:
ck = 0xFFFF
p = p[:6] + struct.pack("!H", ck) + p[8:]
elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) # noqa: E501
elif isinstance(selfdown, scapy.layers.inet6.IPv6) or isinstance(selfdown, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, selfdown, p) # noqa: E501
# According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
if ck == 0:
ck = 0xFFFF
Expand Down
18 changes: 18 additions & 0 deletions scapy/layers/inet6.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ class _IPv6ExtHdr(_IPv6GuessPayload, Packet):
0x04: "Tunnel Encapsulation Limit",
0x05: "Router Alert",
0x06: "Quick-Start",
0x11: "IOAM",
0xc2: "Jumbo Payload",
0xc9: "Home Address Option"}

Expand Down Expand Up @@ -782,6 +783,22 @@ def alignment_delta(self, curpos): # alignment requirement : 2n+0
def extract_padding(self, p):
return b"", p

from scapy.contrib.ioam import IOAM
class HBHOptIOAM(Packet): # IPv6 Hop-By-Hop Option
name = "HBHOptIOAM"
fields_desc = [_OTypeField("otype", 0x11, _hbhopts),
ByteField("optlen", 4),
PacketField("ioam", None, IOAM)]

def alignment_delta(self, curpos): # alignment requirement : 4n+2
x = 4
y = 0
delta = x * ((curpos - y + x - 1) // x) + y - curpos
return delta

def extract_padding(self, p):
return b"", p


class Jumbo(Packet): # IPv6 Hop-By-Hop Option
name = "Jumbo Payload"
Expand Down Expand Up @@ -818,6 +835,7 @@ def extract_padding(self, p):
_hbhoptcls = {0x00: Pad1,
0x01: PadN,
0x05: RouterAlert,
0x11: HBHOptIOAM,
0xC2: Jumbo,
0xC9: HAO}

Expand Down
139 changes: 139 additions & 0 deletions test/contrib/ioam.uts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
% IOAM unit test

#
# execute test:
# > test/run_tests -P "load_contrib('ioam')" -t test/contrib/ioam.uts
#

+ IOAM testsuit


= Build & Dissect, IOAM Over IPv4

def check_ioam(pkt:Packet):
protocols = [0, socket.IPPROTO_UDP, socket.IPPROTO_TCP]
assert pkt[IOAM].flow_id == 100
assert pkt[IOAM].color_val == 1
assert pkt[IOAM].delay_en == 0
assert pkt[IOAM].color_en == 1
assert (((not (pkt.haslayer(UDP) or pkt.haslayer(UDP))) and (pkt[IOAM].next_protocol == 0)) or
(pkt.haslayer(HBHOptIOAM) and (pkt[IOAM].next_protocol == 0)) or
(pkt.haslayer(UDP) and (pkt[IOAM].next_protocol == socket.IPPROTO_UDP)) or
(pkt.haslayer(TCP) and (pkt[IOAM].next_protocol == socket.IPPROTO_TCP)))

ioam = IOAM(flow_id=100, color_val=1, delay_en=0, color_en=1)
o_eth = Ether(src="b6:18:00:11:11:00", dst="b6:18:00:22:22:00")
o_ip4 = IP(src="198.1.1.17", dst="198.1.2.18", ttl=63)
o_ip6 = IPv6(src="2000::1", dst="5000::2", hlim=63)
o_udp = UDP(sport=4196, dport=9028)
o_tcp = TCP(sport=4196, dport=9028)
o_vxlan = VXLAN(vni=5000)
i_eth = Ether(dst='b6:18:00:88:88:00', src='b6:18:00:99:99:00')
i_ip4 = IP(src='192.168.1.5', dst='192.168.6.9', ttl=128)
i_tcp = TCP(sport=6677, dport=8899)
payload = Raw('a'*64)

pkt=o_eth/o_ip4/ioam/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IP].proto == IPPROTO_IOAM
assert pkt[IP].chksum == 0xebc5
check_ioam(pkt)


= Build & Dissect, IOAM Over IPv4 UDP

pkt=o_eth/o_ip4/ioam/o_udp/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IP].proto == IPPROTO_IOAM
assert pkt[IP].chksum == 0xebbd
check_ioam(pkt)
assert pkt[UDP].sport == 4196
assert pkt[UDP].dport == 9028
assert pkt[UDP].chksum == 0x1064


= Build & Dissect, IOAM Over IPv4 TCP

pkt=o_eth/o_ip4/ioam/o_tcp/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IP].proto == IPPROTO_IOAM
assert pkt[IP].chksum == 0xebb1
check_ioam(pkt)
assert pkt[TCP].sport == 4196
assert pkt[TCP].dport == 9028
assert pkt[TCP].chksum == 0xa0a8


= Build & Dissect, IOAM Over IPv4 VXLAN

pkt=o_eth/o_ip4/ioam/UDP(sport=4196, dport=4789)/o_vxlan/i_eth/i_ip4/i_tcp/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IP].proto == IPPROTO_IOAM
assert pkt[IP].chksum == 0xeb7f
check_ioam(pkt)
assert pkt[UDP].sport == 4196
assert pkt[UDP].dport == 4789
assert pkt[UDP].chksum == 0xaaf2
assert pkt[VXLAN].vni == 5000


= Build & Dissect, IOAM Over IPv6

pkt=o_eth/o_ip6/IPv6ExtHdrHopByHop(nh=59, options=[HBHOptIOAM(ioam=ioam)])/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IPv6].nh == 0
assert pkt[IPv6ExtHdrHopByHop].nh == 59
assert pkt[HBHOptIOAM].otype == 0x11
assert pkt[HBHOptIOAM].optlen == 4
check_ioam(pkt)


= Build & Dissect, IOAM Over IPv6 UDP

pkt=o_eth/o_ip6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_UDP, options=[HBHOptIOAM(ioam=ioam)])/o_udp/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IPv6].nh == 0
assert pkt[IPv6ExtHdrHopByHop].nh == socket.IPPROTO_UDP
assert pkt[HBHOptIOAM].otype == 0x11
assert pkt[HBHOptIOAM].optlen == 4
check_ioam(pkt)
assert pkt[UDP].sport == 4196
assert pkt[UDP].dport == 9028
assert pkt[UDP].chksum == 0x2f87


= Build & Dissect, IOAM Over IPv6 TCP

pkt=o_eth/o_ip6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_TCP, options=[HBHOptIOAM(ioam=ioam)])/o_tcp/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IPv6].nh == 0
assert pkt[IPv6ExtHdrHopByHop].nh == socket.IPPROTO_TCP
assert pkt[HBHOptIOAM].otype == 0x11
assert pkt[HBHOptIOAM].optlen == 4
check_ioam(pkt)
assert pkt[TCP].sport == 4196
assert pkt[TCP].dport == 9028
assert pkt[TCP].chksum == 0xbfcb


= Build & Dissect, IOAM Over IPv6 VXLAN

pkt=o_eth/o_ip6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_UDP, options=[HBHOptIOAM(ioam=ioam)])/UDP(sport=4196, dport=4789)/o_vxlan/i_eth/i_ip4/i_tcp/payload
pkt=Ether(raw(pkt))
# pkt.show2()
assert pkt[IPv6].nh == 0
assert pkt[IPv6ExtHdrHopByHop].nh == socket.IPPROTO_UDP
assert pkt[HBHOptIOAM].otype == 0x11
assert pkt[HBHOptIOAM].optlen == 4
check_ioam(pkt)
assert pkt[UDP].sport == 4196
assert pkt[UDP].dport == 4789
assert pkt[UDP].chksum == 0xca15
assert pkt[VXLAN].vni == 5000

0 comments on commit d5d74ed

Please sign in to comment.