Home | History | Annotate | Download | only in contrib
      1 % PNIO RTC layer test campaign
      2 
      3 + Syntax check
      4 = Import the PNIO RTC layer
      5 from scapy.contrib.pnio import *
      6 from scapy.contrib.pnio_rtc import *
      7 
      8 
      9 + Check PNIORealTimeIOxS
     10 
     11 = PNIORealTimeIOxS default values
     12 raw(PNIORealTimeIOxS()) == b'\x80'
     13 
     14 = Check no payload is dissected (only padding)
     15 * In order for the PNIORealTime to dissect correctly all the data buffer, data field must strictly dissect what they know as being of themselves
     16 p = PNIORealTimeIOxS(b'\x40\x01\x02')
     17 p == PNIORealTimeIOxS(dataState='bad', instance='device') / conf.padding_layer(b'\x01\x02')
     18 
     19 
     20 + Check PNIORealTimeRawData
     21 
     22 = PNIORealTimeRawData default values
     23 raw(PNIORealTimeRawData(config={'length': 5})) == b'\x00\x00\x00\x00\x00'
     24 
     25 = PNIORealTimeRawData must always be the same configured length
     26 raw(PNIORealTimeRawData(load='ABC', config={'length': 5})) == b'ABC\x00\x00'
     27 
     28 = PNIORealTimeRawData may be truncated
     29 raw(PNIORealTimeRawData(load='ABCDEF', config={'length': 5})) == b'ABCDE'
     30 
     31 = Check that the dissected payload is an PNIORealTimeIOxS (IOPS)
     32 p = PNIORealTimeRawData(b'ABCDE\x80\x01\x02', config={'length': 5})
     33 p == PNIORealTimeRawData(load=b'ABCDE', config={'length': 5}) / PNIORealTimeIOxS() / conf.padding_layer(b'\x01\x02')
     34 
     35 = PNIORealTimeRawData is capable of dissected uncomplete packets
     36 p = PNIORealTimeRawData('ABC', config={'length': 5})
     37 p == PNIORealTimeRawData(load=b'ABC', config={'length': 5})
     38 
     39 
     40 + Check Profisafe
     41 
     42 = Profisafe default values
     43 raw(Profisafe(config={'length': 7, 'CRC': 3})) == b'\0\0\0\0\0\0\0'
     44 
     45 = Profisafe must always be the same configured length
     46 raw(Profisafe(load=b'AB', config={'length': 7, 'CRC': 3})) == b'AB\0\0\0\0\0'
     47 
     48 = Profisafe load may be truncated
     49 raw(Profisafe(load=b'ABCDEF', config={'length': 7, 'CRC': 3})) == b'ABC\0\0\0\0'
     50 
     51 = Check that the dissected payload is an PNIORealTimeIOxS (IOPS)
     52 p = Profisafe(b'ABC\x20\x12\x34\x56\x80\x01\x02', config={'length': 7, 'CRC': 3})
     53 p == Profisafe(load=b'ABC', Control_Status=0x20, CRC=0x123456, config={'length': 7, 'CRC': 3}) / PNIORealTimeIOxS() / conf.padding_layer(b'\x01\x02')
     54 
     55 = Profisafe with a CRC-32
     56 raw(Profisafe(load=b'ABC', Control_Status=0x33, CRC=0x12345678, config={'length': 8, 'CRC': 4})) == b'ABC\x33\x12\x34\x56\x78'
     57 
     58 = Profisafe is capable of dissected uncomplete packets
     59 p = Profisafe('AB', config={'length': 7, 'CRC': 3})
     60 p == Profisafe(load=b'AB', Control_Status=0, CRC=0)
     61 
     62 
     63 + Check PNIORealTime layer
     64 
     65 = PNIORealTime default values
     66 raw(PNIORealTime()) == b'\0' * 40 + b'\0\0\x35\0'
     67 
     68 = PNIORealTime default values under an UDP packet
     69 raw(UDP(sport=0x1234) / ProfinetIO(frameID=0x8002) / PNIORealTime()) == hex_bytes('12348892001a00008002') + b'\0' * 12 + b'\0\0\x35\0'
     70 
     71 = PNIORealTime simple packet
     72 * a simple data packet with a raw profinet data and its IOPS, an IOCS and a Profisafe data and its IOPS. 15B data length, 1B padding (20 - 15 -4)
     73 raw(PNIORealTime(len=20, dataLen=15, cycleCounter=0x1234, dataStatus='redundancy+validData+no_problem', transferStatus=3,
     74   data=[
     75       PNIORealTimeRawData(load=b'\x01\x02\x03\x04', config={'length': 5}) / PNIORealTimeIOxS(),
     76       PNIORealTimeIOxS(dataState='bad'),
     77       Profisafe(load=b'\x05\x06', Control_Status=0x20, CRC=0x12345678, config={'length': 7, 'CRC': 4}) / PNIORealTimeIOxS()
     78       ]
     79   )) == hex_bytes('0102030400800005062012345678800012342603')
     80 
     81 = PNIORealTime dissects to PNIORealTimeRawData when no config is available
     82 p = PNIORealTime(hex_bytes('0102030400800005062012345678800012342603'))
     83 p == PNIORealTime(len=20, dataLen=15, cycleCounter=0x1234, dataStatus='redundancy+validData+no_problem', transferStatus=3, padding=b'\0',
     84   data=[
     85       PNIORealTimeRawData(load=hex_bytes('010203040080000506201234567880'))
     86       ]
     87   )
     88 
     89 = PNIORealTime dissection is configurable
     90 * Usually, the configuration is not given manually, but using PNIORealTime.analyse_data() on a list of Packets which analyses and updates the configuration
     91 pnio_update_config({
     92   ('06:07:08:09:0a:0b', '00:01:02:03:04:05'): [
     93     (-15, PNIORealTimeRawData, {'length': 5}),
     94     (-8, Profisafe, {'length': 7, 'CRC': 4}),
     95     ]
     96   })
     97 p = Ether(hex_bytes('000102030405060708090a0b889280020102030400800005062012345678800012342603'))
     98 p == Ether(dst='00:01:02:03:04:05', src='06:07:08:09:0a:0b') / ProfinetIO(frameID=0x8002) / PNIORealTime(
     99   len=20, dataLen=15, cycleCounter=0x1234, dataStatus='redundancy+validData+no_problem', transferStatus=3, padding=b'\0',
    100   data=[
    101       PNIORealTimeRawData(load=b'\x01\x02\x03\x04\0', config={'length': 5}) / PNIORealTimeIOxS(),
    102       PNIORealTimeIOxS(dataState='bad'),
    103       Profisafe(load=b'\x05\x06', Control_Status=0x20, CRC=0x12345678, config={'length': 7, 'CRC': 4}) / PNIORealTimeIOxS()
    104       ]
    105   )
    106 
    107 = PNIORealTime - Analyse data
    108 # Possible thanks to https://github.com/ITI/ICS-Security-Tools/blob/master/pcaps/profinet/profinet.pcap
    109 
    110 bind_layers(UDP, ProfinetIO, dport=0x8894)
    111 bind_layers(UDP, ProfinetIO, sport=0x8894)
    112 
    113 packets = [Ether(hex_bytes('0090274ee3fc000991442017080045000128000c00004011648f0a0a00810a0a00968894061e011444c604022800100000000000a0de976cd111827100010003015a0100a0de976cd111827100a02442df7ddbabbaec1d005443b2500b01630abafd0100000001000000000000000500ffffffffbc000000000000000000a80000004080000000000000a80000008009003c0100000a0000000000000000000000000000000000000000000000010000f840000000680000000000000000000000000000000000000000000000000030002c0100000100000000000200000000000100020001000000010003ffff010a0001ffff814000010001ffff814000310018010000010000000000010001ffff814000010001ffff814000320018010000010000000000010000000000010001000100000001')),
    114            Ether(hex_bytes('0009914420170090274ee3fc0800450000c0b28800008011727a0a0a00960a0a0081061e889400ac689504000800100000000000a0de976cd111827100010003015a0100a0de976cd111827100a02442df7ddbabbaec1d005443b2500b01630abafd0000000001000000000000000500ffffffff54000000000040800000400000004080000000000000400000000009003c0100000a0000000000000000000000000000000000000000000000010000f84000008000000000000000000000000000000000000000000000000000'))]
    115            
    116 analysed_data = PNIORealTime.analyse_data(packets)
    117 assert len(analysed_data) == 2
    118 x = analysed_data[('00:09:91:44:20:17', '00:90:27:4e:e3:fc')]
    119 assert len(x) == 2
    120 assert x[0][1] == PNIORealTimeRawData
    121 assert x[0][0] == -262
    122 assert x[0][2]["length"] == 87
    123 
    124 ProfinetIO._overload_fields[UDP].pop("sport")
    125 ProfinetIO._overload_fields[UDP]["dport"] = 0x8892