|  | 
|  | 1 | +# scapy.contrib.description = Inband Flow Analyzer Protocol (IFA) | 
|  | 2 | +# scapy.contrib.status = loads | 
|  | 3 | + | 
|  | 4 | +''' | 
|  | 5 | +Inband Flow Analyzer Protocol (IFA) | 
|  | 6 | +
 | 
|  | 7 | +References: | 
|  | 8 | +https://datatracker.ietf.org/doc/html/draft-kumar-ippm-ifa-08 | 
|  | 9 | +''' | 
|  | 10 | + | 
|  | 11 | +import struct | 
|  | 12 | +import socket | 
|  | 13 | +from scapy.data import IP_PROTOS | 
|  | 14 | +from scapy.layers.l2 import Ether, GRE | 
|  | 15 | +from scapy.layers.inet import IP, TCP, UDP | 
|  | 16 | +from scapy.layers.inet6 import IPv6 | 
|  | 17 | +from scapy.layers.vxlan import VXLAN | 
|  | 18 | +from scapy.contrib.geneve import GENEVE | 
|  | 19 | +from scapy.packet import Packet, bind_layers | 
|  | 20 | +from scapy.fields import BitField, BitEnumField, FlagsField, \ | 
|  | 21 | +    ByteField, ByteEnumField, ShortField, IntField, PacketListField | 
|  | 22 | + | 
|  | 23 | +IPPROTO_IFA = 131 | 
|  | 24 | +IP_PROTOS[IPPROTO_IFA] = 'IFA' | 
|  | 25 | + | 
|  | 26 | +_ifa_flags = [ | 
|  | 27 | +    'C',    # Checksum | 
|  | 28 | +    'TA',   # Turn Around | 
|  | 29 | +    'I',    # Inband | 
|  | 30 | +    'TS',   # Tail Stamp | 
|  | 31 | +    'MF',   # Metadata Fragment | 
|  | 32 | +    'R',    # Reserved | 
|  | 33 | +    'R',    # Reserved | 
|  | 34 | +    'R',    # Reserved | 
|  | 35 | +] | 
|  | 36 | + | 
|  | 37 | +_ifa_action = [ | 
|  | 38 | +    'R',    # Reserved | 
|  | 39 | +    'R',    # Reserved | 
|  | 40 | +    'R',    # Reserved | 
|  | 41 | +    'R',    # Reserved | 
|  | 42 | +    'R',    # Reserved | 
|  | 43 | +    'R',    # Reserved | 
|  | 44 | +    'C',    # Color bit to mark the packet | 
|  | 45 | +    'L',    # Loss bit to measure packet loss | 
|  | 46 | +] | 
|  | 47 | + | 
|  | 48 | +_ifa_speed = { | 
|  | 49 | +    0: '10Gbps', | 
|  | 50 | +    1: '25Gbps', | 
|  | 51 | +    2: '40Gbps', | 
|  | 52 | +    3: '50Gbps', | 
|  | 53 | +    4: '100Gbps', | 
|  | 54 | +    5: '200Gbps', | 
|  | 55 | +    6: '400Gbps', | 
|  | 56 | +} | 
|  | 57 | + | 
|  | 58 | + | 
|  | 59 | +class IFA(Packet): | 
|  | 60 | +    name = 'IFA' | 
|  | 61 | +    fields_desc = [ | 
|  | 62 | +        BitField('ver', 3, 4), | 
|  | 63 | +        BitField('gns', 0, 4), | 
|  | 64 | +        ByteEnumField("nexthdr", 0, IP_PROTOS), | 
|  | 65 | +        FlagsField("flags", 0, 8, _ifa_flags), | 
|  | 66 | +        ByteField('maxlen', 255), | 
|  | 67 | +    ] | 
|  | 68 | + | 
|  | 69 | + | 
|  | 70 | +class IFAMd(Packet): | 
|  | 71 | +    name = 'IFAMd' | 
|  | 72 | +    fields_desc = [ | 
|  | 73 | +        BitField('lns', 0, 4), | 
|  | 74 | +        BitField('device_id', 0, 20), | 
|  | 75 | +        ByteField('ttl', 0), | 
|  | 76 | +        BitEnumField('speed', 0, 4, _ifa_speed), | 
|  | 77 | +        BitField('ecn', 0, 2), | 
|  | 78 | +        BitField('qid', 0, 6), | 
|  | 79 | +        BitField('rx_sec', 0, 20), | 
|  | 80 | +        ShortField('dport', 0), | 
|  | 81 | +        ShortField('sport', 0), | 
|  | 82 | +        IntField('rx_nsec', 0), | 
|  | 83 | +        IntField('latency', 0), | 
|  | 84 | +        IntField('qbytes', 0), | 
|  | 85 | +        ShortField('rsvd0', 0), | 
|  | 86 | +        ShortField('qcells', 0), | 
|  | 87 | +        IntField('rsvd1', 0), | 
|  | 88 | +    ] | 
|  | 89 | + | 
|  | 90 | +    def extract_padding(self, s): | 
|  | 91 | +        return "", s | 
|  | 92 | + | 
|  | 93 | + | 
|  | 94 | +class IFAMdHdr(Packet): | 
|  | 95 | +    name = 'IFAMdHdr' | 
|  | 96 | +    fields_desc = [ | 
|  | 97 | +        ByteField('request', 0), | 
|  | 98 | +        FlagsField("action", 0, 8, _ifa_action), | 
|  | 99 | +        ByteField('hoplmt', 128), | 
|  | 100 | +        ByteField('curlen', 0), | 
|  | 101 | +        PacketListField("mdstack", None, IFAMd, | 
|  | 102 | +                        length_from=lambda pkt: pkt.curlen * 4) | 
|  | 103 | +    ] | 
|  | 104 | + | 
|  | 105 | +    def post_build(self, p, pay): | 
|  | 106 | +        mdlen = (len(p) - 4) // 4 | 
|  | 107 | +        if self.curlen != mdlen: | 
|  | 108 | +            p = p[:3] + struct.pack("!B", mdlen) + p[4:] | 
|  | 109 | +        return p + pay | 
|  | 110 | + | 
|  | 111 | +    def guess_payload_class(self, payload): | 
|  | 112 | +        if isinstance(self.underlayer, UDP): | 
|  | 113 | +            if self.underlayer.dport in [4789, 4790]: | 
|  | 114 | +                return VXLAN | 
|  | 115 | +            elif self.underlayer.dport == 6081: | 
|  | 116 | +                return GENEVE | 
|  | 117 | +        elif isinstance(self.underlayer, GRE): | 
|  | 118 | +            if self.underlayer.proto == 0x6558: | 
|  | 119 | +                return Ether | 
|  | 120 | +            if self.underlayer.proto == 0x0800: | 
|  | 121 | +                return IP | 
|  | 122 | +            if self.underlayer.proto == 0x86dd: | 
|  | 123 | +                return IPv6 | 
|  | 124 | +        return Packet.guess_payload_class(self, payload) | 
|  | 125 | + | 
|  | 126 | + | 
|  | 127 | +bind_layers(IP, IFA, proto=IPPROTO_IFA) | 
|  | 128 | +bind_layers(IPv6, IFA, nh=IPPROTO_IFA) | 
|  | 129 | +bind_layers(IFA, TCP, nexthdr=socket.IPPROTO_TCP) | 
|  | 130 | +bind_layers(IFA, UDP, nexthdr=socket.IPPROTO_UDP) | 
|  | 131 | +bind_layers(IFA, GRE, nexthdr=socket.IPPROTO_GRE) | 
0 commit comments