Skip to content

Commit

Permalink
Support Inband Flow Analyzer (IFA)
Browse files Browse the repository at this point in the history
1.support IFA over ipv4/ipv6 + udp/tcp/vxlan/gre/geneve
2.add IFA unit tests
  • Loading branch information
jingfeihu committed Dec 30, 2024
1 parent 1346522 commit 4687de0
Show file tree
Hide file tree
Showing 4 changed files with 383 additions and 8 deletions.
131 changes: 131 additions & 0 deletions scapy/contrib/ifa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# scapy.contrib.description = Inband Flow Analyzer Protocol (IFA)
# scapy.contrib.status = loads

'''
Inband Flow Analyzer Protocol (IFA)
References:
https://datatracker.ietf.org/doc/html/draft-kumar-ippm-ifa-08
'''

import struct
import socket
from scapy.data import IP_PROTOS
from scapy.layers.l2 import Ether, GRE
from scapy.layers.inet import IP, TCP, UDP
from scapy.layers.inet6 import IPv6
from scapy.layers.vxlan import VXLAN
from scapy.contrib.geneve import GENEVE
from scapy.packet import Packet, bind_layers
from scapy.fields import BitField, BitEnumField, FlagsField, \
ByteField, ByteEnumField, ShortField, IntField, PacketListField

IPPROTO_IFA = 131
IP_PROTOS[IPPROTO_IFA] = 'IFA'

_ifa_flags = [
'C', # Checksum
'TA', # Turn Around
'I', # Inband
'TS', # Tail Stamp
'MF', # Metadata Fragment
'R', # Reserved
'R', # Reserved
'R', # Reserved
]

_ifa_action = [
'R', # Reserved
'R', # Reserved
'R', # Reserved
'R', # Reserved
'R', # Reserved
'R', # Reserved
'C', # Color bit to mark the packet
'L', # Loss bit to measure packet loss
]

_ifa_speed = {
0: '10Gbps',
1: '25Gbps',
2: '40Gbps',
3: '50Gbps',
4: '100Gbps',
5: '200Gbps',
6: '400Gbps',
}


class IFA(Packet):
name = 'IFA'
fields_desc = [
BitField('ver', 3, 4),
BitField('gns', 0, 4),
ByteEnumField("nexthdr", 0, IP_PROTOS),
FlagsField("flags", 0, 8, _ifa_flags),
ByteField('maxlen', 255),
]


class IFAMd(Packet):
name = 'IFAMd'
fields_desc = [
BitField('lns', 0, 4),
BitField('device_id', 0, 20),
ByteField('ttl', 0),
BitEnumField('speed', 0, 4, _ifa_speed),
BitField('ecn', 0, 2),
BitField('qid', 0, 6),
BitField('rx_sec', 0, 20),
ShortField('dport', 0),
ShortField('sport', 0),
IntField('rx_nsec', 0),
IntField('latency', 0),
IntField('qbytes', 0),
ShortField('rsvd0', 0),
ShortField('qcells', 0),
IntField('rsvd1', 0),
]

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


class IFAMdHdr(Packet):
name = 'IFAMdHdr'
fields_desc = [
ByteField('request', 0),
FlagsField("action", 0, 8, _ifa_action),
ByteField('hoplmt', 128),
ByteField('curlen', 0),
PacketListField("mdstack", None, IFAMd,
length_from=lambda pkt: pkt.curlen * 4)
]

def post_build(self, p, pay):
mdlen = (len(p) - 4) // 4
if self.curlen != mdlen:
p = p[:3] + struct.pack("!B", mdlen) + p[4:]
return p + pay

def guess_payload_class(self, payload):
if isinstance(self.underlayer, UDP):
if self.underlayer.dport in [4789, 4790]:
return VXLAN
elif self.underlayer.dport == 6081:
return GENEVE
elif isinstance(self.underlayer, GRE):
if self.underlayer.proto == 0x6558:
return Ether
if self.underlayer.proto == 0x0800:
return IP
if self.underlayer.proto == 0x86dd:
return IPv6
return Packet.guess_payload_class(self, payload)


bind_layers(IP, IFA, proto=IPPROTO_IFA)
bind_layers(IPv6, IFA, nh=IPPROTO_IFA)
bind_layers(IFA, TCP, nexthdr=socket.IPPROTO_TCP)
bind_layers(IFA, UDP, nexthdr=socket.IPPROTO_UDP)
bind_layers(IFA, GRE, nexthdr=socket.IPPROTO_GRE)
36 changes: 28 additions & 8 deletions scapy/layers/inet.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,11 +757,15 @@ 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)
_underlayer = self.underlayer
from scapy.contrib.ifa import IFA
if isinstance(self.underlayer, IFA):
_underlayer = self.underlayer.underlayer
if isinstance(_underlayer, IP):
ck = in4_chksum(socket.IPPROTO_TCP, _underlayer, 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(_underlayer, scapy.layers.inet6.IPv6) or isinstance(_underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_TCP, _underlayer, p) # noqa: E501
p = p[:16] + struct.pack("!H", ck) + p[18:]
else:
log_runtime.info(
Expand Down Expand Up @@ -814,6 +818,12 @@ def mysummary(self):
else:
return self.sprintf("TCP %TCP.sport% > %TCP.dport% %TCP.flags%")

def guess_payload_class(self, payload):
from scapy.contrib.ifa import IFA, IFAMdHdr
if isinstance(self.underlayer, IFA):
return IFAMdHdr
return Packet.guess_payload_class(self, payload)


class UDP(Packet):
name = "UDP"
Expand All @@ -829,14 +839,18 @@ 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)
_underlayer = self.underlayer
from scapy.contrib.ifa import IFA
if isinstance(self.underlayer, IFA):
_underlayer = self.underlayer.underlayer
if isinstance(_underlayer, IP):
ck = in4_chksum(socket.IPPROTO_UDP, _underlayer, 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(_underlayer, scapy.layers.inet6.IPv6) or isinstance(_underlayer, scapy.layers.inet6._IPv6ExtHdr): # noqa: E501
ck = scapy.layers.inet6.in6_chksum(socket.IPPROTO_UDP, _underlayer, 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 Expand Up @@ -870,6 +884,12 @@ def mysummary(self):
else:
return self.sprintf("UDP %UDP.sport% > %UDP.dport%")

def guess_payload_class(self, payload):
from scapy.contrib.ifa import IFA, IFAMdHdr
if isinstance(self.underlayer, IFA):
return IFAMdHdr
return Packet.guess_payload_class(self, payload)


# RFC 4884 ICMP extensions
_ICMP_classnums = {
Expand Down
6 changes: 6 additions & 0 deletions scapy/layers/l2.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,12 @@ def post_build(self, p, pay):
p = p[:4] + chb((c >> 8) & 0xff) + chb(c & 0xff) + p[6:]
return p

def guess_payload_class(self, payload):
from scapy.contrib.ifa import IFA, IFAMdHdr
if isinstance(self.underlayer, IFA):
return IFAMdHdr
return Packet.guess_payload_class(self, payload)


class GRE_PPTP(GRE):

Expand Down
Loading

0 comments on commit 4687de0

Please sign in to comment.