Skip to content

Commit f93e029

Browse files
committed
Support In-situ Operation Administration and Maintenance (IOAM)
1.support CMCC IOAM over ipv4/ipv6 + tcp/udp/vxlan 2.add CMCC IOAM unit tests
1 parent d71014a commit f93e029

File tree

4 files changed

+231
-8
lines changed

4 files changed

+231
-8
lines changed

scapy/contrib/ioam.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# scapy.contrib.description = In-situ Operation Administration and Maintenance (IOAM)
2+
# scapy.contrib.status = loads
3+
4+
'''
5+
In-situ Operation Administration and Maintenance (IOAM)
6+
7+
Notice:
8+
This is China Mobile Communications Corporation (CMCC) IOAM, instead of Cisco IOAM
9+
10+
IOAM Shim Reference:
11+
https://datatracker.ietf.org/doc/rfc9486/
12+
https://datatracker.ietf.org/doc/html/rfc8321
13+
14+
IOAM Report Packet Reference:
15+
https://datatracker.ietf.org/doc/draft-ietf-netconf-udp-notif/12/
16+
17+
IOAM layer identifier:
18+
IPv4.proto == 186
19+
IPv6.NextHeader == 0x0 && IPv6.HBH.option_type == 0x11
20+
21+
Example Packet Format:
22+
IOAMoIPv4 = Ether/IP/IOAM/Payload
23+
IOAMoIPv4UDP = Ether/IP/IOAM/UDP/Payload
24+
IOAMoIPv4TCP = Ether/IP/IOAM/TCP/Payload
25+
IOAMoIPv4VxLAN = Ether/IP/IOAM/UDP/VXLAN/Ether/IP/TCP/Payload
26+
IOAMoIPv6IP = Ether/IPv6/IPv6ExtHdrHopByHop(nh=59, options=[HBHOptIOAM(ioam=ioam)])/Payload # noqa: E501
27+
IOAMoIPv6UDP = Ether/IPv6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_UDP, options=[HBHOptIOAM(ioam=IOAM)])/UDP/Payload # noqa: E501
28+
IOAMoIPv6TCP = Ether/IPv6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_TCP, options=[HBHOptIOAM(ioam=IOAM)])/TCP/Payload # noqa: E501
29+
IOAMoIPv6VxLAN = Ether/IPv6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_UDP, options=[HBHOptIOAM(ioam=IOAM)])/UDP/VXLAN/Ether/IP/TCP/Payload # noqa: E501
30+
'''
31+
32+
import socket
33+
from scapy.packet import Packet, bind_layers
34+
from scapy.fields import BitField, ByteField
35+
from scapy.layers.inet import IP, TCP, UDP
36+
37+
IPPROTO_IOAM = 186
38+
39+
40+
class IOAM(Packet):
41+
name = 'IOAM'
42+
fields_desc = [
43+
BitField('flow_id', 0, 20),
44+
BitField('color_val', 0, 1),
45+
BitField('delay_en', 0, 1),
46+
BitField('color_en', 0, 1),
47+
BitField('reserved', 0, 1),
48+
ByteField('next_protocol', 0),
49+
]
50+
51+
52+
bind_layers(IP, IOAM, proto=IPPROTO_IOAM)
53+
bind_layers(IOAM, TCP, next_protocol=socket.IPPROTO_TCP)
54+
bind_layers(IOAM, UDP, next_protocol=socket.IPPROTO_UDP)

scapy/layers/inet.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -749,11 +749,16 @@ def post_build(self, p, pay):
749749
dataofs = (dataofs << 4) | orb(p[12]) & 0x0f
750750
p = p[:12] + chb(dataofs & 0xff) + p[13:]
751751
if self.chksum is None:
752-
if isinstance(self.underlayer, IP):
753-
ck = in4_chksum(socket.IPPROTO_TCP, self.underlayer, p)
752+
selfdown = self.underlayer
753+
from scapy.contrib.ioam import IOAM
754+
if isinstance(self.underlayer, IOAM):
755+
selfdown = self.underlayer.underlayer
756+
757+
if isinstance(selfdown, IP):
758+
ck = in4_chksum(socket.IPPROTO_TCP, selfdown, p)
754759
p = p[:16] + struct.pack("!H", ck) + p[18:]
755-
elif conf.ipv6_enabled and isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
756-
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, self.underlayer, p) # noqa: E501
760+
elif conf.ipv6_enabled and isinstance(selfdown, scapy.layers.inet6.IPv6) or isinstance(selfdown, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
761+
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, selfdown, p) # noqa: E501
757762
p = p[:16] + struct.pack("!H", ck) + p[18:]
758763
else:
759764
log_runtime.info(
@@ -821,14 +826,19 @@ def post_build(self, p, pay):
821826
tmp_len = len(p)
822827
p = p[:4] + struct.pack("!H", tmp_len) + p[6:]
823828
if self.chksum is None:
824-
if isinstance(self.underlayer, IP):
825-
ck = in4_chksum(socket.IPPROTO_UDP, self.underlayer, p)
829+
selfdown = self.underlayer
830+
from scapy.contrib.ioam import IOAM
831+
if isinstance(self.underlayer, IOAM):
832+
selfdown = self.underlayer.underlayer
833+
834+
if isinstance(selfdown, IP):
835+
ck = in4_chksum(socket.IPPROTO_UDP, selfdown, p)
826836
# According to RFC768 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
827837
if ck == 0:
828838
ck = 0xFFFF
829839
p = p[:6] + struct.pack("!H", ck) + p[8:]
830-
elif isinstance(self.underlayer, scapy.layers.inet6.IPv6) or isinstance(self.underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
831-
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, self.underlayer, p) # noqa: E501
840+
elif isinstance(selfdown, scapy.layers.inet6.IPv6) or isinstance(selfdown, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
841+
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, selfdown, p) # noqa: E501
832842
# According to RFC2460 if the result checksum is 0, it should be set to 0xFFFF # noqa: E501
833843
if ck == 0:
834844
ck = 0xFFFF

scapy/layers/inet6.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
LongField,
4848
MACField,
4949
MayEnd,
50+
PacketField,
5051
PacketLenField,
5152
PacketListField,
5253
ShortEnumField,
@@ -674,6 +675,7 @@ class _IPv6ExtHdr(_IPv6GuessPayload, Packet):
674675
0x04: "Tunnel Encapsulation Limit",
675676
0x05: "Router Alert",
676677
0x06: "Quick-Start",
678+
0x11: "IOAM",
677679
0xc2: "Jumbo Payload",
678680
0xc9: "Home Address Option"}
679681

@@ -783,6 +785,23 @@ def extract_padding(self, p):
783785
return b"", p
784786

785787

788+
class HBHOptIOAM(Packet): # IPv6 Hop-By-Hop Option
789+
from scapy.contrib.ioam import IOAM
790+
name = "HBHOptIOAM"
791+
fields_desc = [_OTypeField("otype", 0x11, _hbhopts),
792+
ByteField("optlen", 4),
793+
PacketField("ioam", None, IOAM)]
794+
795+
def alignment_delta(self, curpos): # alignment requirement : 4n+2
796+
x = 4
797+
y = 0
798+
delta = x * ((curpos - y + x - 1) // x) + y - curpos
799+
return delta
800+
801+
def extract_padding(self, p):
802+
return b"", p
803+
804+
786805
class Jumbo(Packet): # IPv6 Hop-By-Hop Option
787806
name = "Jumbo Payload"
788807
fields_desc = [_OTypeField("otype", 0xC2, _hbhopts),
@@ -818,6 +837,7 @@ def extract_padding(self, p):
818837
_hbhoptcls = {0x00: Pad1,
819838
0x01: PadN,
820839
0x05: RouterAlert,
840+
0x11: HBHOptIOAM,
821841
0xC2: Jumbo,
822842
0xC9: HAO}
823843

test/contrib/ioam.uts

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
% IOAM unit test
2+
3+
#
4+
# execute test:
5+
# > test/run_tests -P "load_contrib('ioam')" -t test/contrib/ioam.uts
6+
#
7+
8+
+ IOAM testsuit
9+
10+
11+
= Build & Dissect, IOAM Over IPv4
12+
13+
def check_ioam(pkt:Packet):
14+
protocols = [0, socket.IPPROTO_UDP, socket.IPPROTO_TCP]
15+
assert pkt[IOAM].flow_id == 100
16+
assert pkt[IOAM].color_val == 1
17+
assert pkt[IOAM].delay_en == 0
18+
assert pkt[IOAM].color_en == 1
19+
assert (((not (pkt.haslayer(UDP) or pkt.haslayer(UDP))) and (pkt[IOAM].next_protocol == 0)) or
20+
(pkt.haslayer(HBHOptIOAM) and (pkt[IOAM].next_protocol == 0)) or
21+
(pkt.haslayer(UDP) and (pkt[IOAM].next_protocol == socket.IPPROTO_UDP)) or
22+
(pkt.haslayer(TCP) and (pkt[IOAM].next_protocol == socket.IPPROTO_TCP)))
23+
24+
ioam = IOAM(flow_id=100, color_val=1, delay_en=0, color_en=1)
25+
o_eth = Ether(src="b6:18:00:11:11:00", dst="b6:18:00:22:22:00")
26+
o_ip4 = IP(src="198.1.1.17", dst="198.1.2.18", ttl=63)
27+
o_ip6 = IPv6(src="2000::1", dst="5000::2", hlim=63)
28+
o_udp = UDP(sport=4196, dport=9028)
29+
o_tcp = TCP(sport=4196, dport=9028)
30+
o_vxlan = VXLAN(vni=5000)
31+
i_eth = Ether(dst='b6:18:00:88:88:00', src='b6:18:00:99:99:00')
32+
i_ip4 = IP(src='192.168.1.5', dst='192.168.6.9', ttl=128)
33+
i_tcp = TCP(sport=6677, dport=8899)
34+
payload = Raw('a'*64)
35+
36+
pkt=o_eth/o_ip4/ioam/payload
37+
pkt=Ether(raw(pkt))
38+
# pkt.show2()
39+
assert pkt[IP].proto == IPPROTO_IOAM
40+
assert pkt[IP].chksum == 0xebc5
41+
check_ioam(pkt)
42+
43+
44+
= Build & Dissect, IOAM Over IPv4 UDP
45+
46+
pkt=o_eth/o_ip4/ioam/o_udp/payload
47+
pkt=Ether(raw(pkt))
48+
# pkt.show2()
49+
assert pkt[IP].proto == IPPROTO_IOAM
50+
assert pkt[IP].chksum == 0xebbd
51+
check_ioam(pkt)
52+
assert pkt[UDP].sport == 4196
53+
assert pkt[UDP].dport == 9028
54+
assert pkt[UDP].chksum == 0x1064
55+
56+
57+
= Build & Dissect, IOAM Over IPv4 TCP
58+
59+
pkt=o_eth/o_ip4/ioam/o_tcp/payload
60+
pkt=Ether(raw(pkt))
61+
# pkt.show2()
62+
assert pkt[IP].proto == IPPROTO_IOAM
63+
assert pkt[IP].chksum == 0xebb1
64+
check_ioam(pkt)
65+
assert pkt[TCP].sport == 4196
66+
assert pkt[TCP].dport == 9028
67+
assert pkt[TCP].chksum == 0xa0a8
68+
69+
70+
= Build & Dissect, IOAM Over IPv4 VXLAN
71+
72+
pkt=o_eth/o_ip4/ioam/UDP(sport=4196, dport=4789)/o_vxlan/i_eth/i_ip4/i_tcp/payload
73+
pkt=Ether(raw(pkt))
74+
# pkt.show2()
75+
assert pkt[IP].proto == IPPROTO_IOAM
76+
assert pkt[IP].chksum == 0xeb7f
77+
check_ioam(pkt)
78+
assert pkt[UDP].sport == 4196
79+
assert pkt[UDP].dport == 4789
80+
assert pkt[UDP].chksum == 0xaaf2
81+
assert pkt[VXLAN].vni == 5000
82+
83+
84+
= Build & Dissect, IOAM Over IPv6
85+
86+
pkt=o_eth/o_ip6/IPv6ExtHdrHopByHop(nh=59, options=[HBHOptIOAM(ioam=ioam)])/payload
87+
pkt=Ether(raw(pkt))
88+
# pkt.show2()
89+
assert pkt[IPv6].nh == 0
90+
assert pkt[IPv6ExtHdrHopByHop].nh == 59
91+
assert pkt[HBHOptIOAM].otype == 0x11
92+
assert pkt[HBHOptIOAM].optlen == 4
93+
check_ioam(pkt)
94+
95+
96+
= Build & Dissect, IOAM Over IPv6 UDP
97+
98+
pkt=o_eth/o_ip6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_UDP, options=[HBHOptIOAM(ioam=ioam)])/o_udp/payload
99+
pkt=Ether(raw(pkt))
100+
# pkt.show2()
101+
assert pkt[IPv6].nh == 0
102+
assert pkt[IPv6ExtHdrHopByHop].nh == socket.IPPROTO_UDP
103+
assert pkt[HBHOptIOAM].otype == 0x11
104+
assert pkt[HBHOptIOAM].optlen == 4
105+
check_ioam(pkt)
106+
assert pkt[UDP].sport == 4196
107+
assert pkt[UDP].dport == 9028
108+
assert pkt[UDP].chksum == 0x2f87
109+
110+
111+
= Build & Dissect, IOAM Over IPv6 TCP
112+
113+
pkt=o_eth/o_ip6/IPv6ExtHdrHopByHop(nh=socket.IPPROTO_TCP, options=[HBHOptIOAM(ioam=ioam)])/o_tcp/payload
114+
pkt=Ether(raw(pkt))
115+
# pkt.show2()
116+
assert pkt[IPv6].nh == 0
117+
assert pkt[IPv6ExtHdrHopByHop].nh == socket.IPPROTO_TCP
118+
assert pkt[HBHOptIOAM].otype == 0x11
119+
assert pkt[HBHOptIOAM].optlen == 4
120+
check_ioam(pkt)
121+
assert pkt[TCP].sport == 4196
122+
assert pkt[TCP].dport == 9028
123+
assert pkt[TCP].chksum == 0xbfcb
124+
125+
126+
= Build & Dissect, IOAM Over IPv6 VXLAN
127+
128+
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
129+
pkt=Ether(raw(pkt))
130+
# pkt.show2()
131+
assert pkt[IPv6].nh == 0
132+
assert pkt[IPv6ExtHdrHopByHop].nh == socket.IPPROTO_UDP
133+
assert pkt[HBHOptIOAM].otype == 0x11
134+
assert pkt[HBHOptIOAM].optlen == 4
135+
check_ioam(pkt)
136+
assert pkt[UDP].sport == 4196
137+
assert pkt[UDP].dport == 4789
138+
assert pkt[UDP].chksum == 0xca15
139+
assert pkt[VXLAN].vni == 5000

0 commit comments

Comments
 (0)