commit cde56494ec80e478b0d8c3c6e44eb8583180c7fd Author: noisedestroyers Date: Sun Aug 3 20:20:55 2025 -0400 first working diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9cc3981 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + + { + "name": "BIG pcap", + "type": "debugpy", + "request": "launch", + "program": "airstream.py", + "console": "integratedTerminal", + "args": "--pcap FSTDaircraft.pcapng -s all" + } + ] +} \ No newline at end of file diff --git a/0.csv b/0.csv new file mode 100644 index 0000000..b0250cf --- /dev/null +++ b/0.csv @@ -0,0 +1,165 @@ +Src IP,Src Port,Dst IP,Dst Port,Proto,Pkts,Bytes,Duration,Overview:Avg Size,Overview:Avg TimeΔ,Overview:Time 1σ,Overview:Pkt/s,Overview:B/s,Chapter10:Channels,Chapter10:Seq Errors,Chapter10:Pkt/s,Ch10Analog:Channels,Ch10Analog:Data Gaps,Ch10Analog:Overrange,Ch10Analog:Underrange,Ch10Analog:Pkt/s,Ch10TMATS:Versions,Ch10TMATS:Config Changes,Ch10TMATS:G-Records,Ch10TMATS:R-Records,Ch10TMATS:M-Records,Ch10PCM:Channels,Ch10PCM:Sync Errors,Ch10PCM:Bit Slips,Ch10PCM:Minor Frames,Ch10PCM:Major Frames,Ch10Time:Time Sources,Ch10Time:Format Changes,Ch10Time:Leap Seconds,Ch10Time:Discontinuities,Ch10Time:Max Delta,IENA:Key Fields,IENA:Seq Gaps,IENA:Pkt/s,PTP:Sync,PTP:Follow Up,PTP:Delay Req,PTP:Delay Resp,PTP:Announce,PTPSync:Sync Msgs,PTPSync:Follow-ups,PTPSync:Two-step,PTPSync:One-step,PTPSync:Clock IDs,PTPSync:Avg Interval,PTPAnnounce:Announce,PTPAnnounce:GM Changes,PTPAnnounce:Priority Changes,PTPAnnounce:Grandmasters,PTPAnnounce:Clock Classes,PTPDelay:Delay Req,PTPDelay:Delay Resp,PTPDelay:PDelay Req,PTPDelay:PDelay Resp,PTPDelay:Avg RTT +192.168.6.194,49153,239.1.2.10,8400,UDP,4610,949660,46.09,206.0,0.01,0.000132,100.0,20604.6,0,0,100.0,0,0,0,0,100.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.142,49153,239.1.2.10,8400,UDP,4609,949454,46.08,206.0,0.01,0.000207,100.0,20604.5,0,0,100.0,0,0,0,0,100.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,100.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.161,49153,239.1.2.10,8400,UDP,461,592846,46.02,1286.0,0.100043,0.000233,10.0,12882.4,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.146,49154,239.1.2.10,8400,UDP,2446,3154768,46.08,1289.8,0.018847,0.004598,53.1,68462.9,0,0,53.1,0,0,0,0,53.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.133,49153,239.1.2.10,8400,UDP,2305,2964230,46.08,1286.0,0.02,0.000119,50.0,64328.0,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.191,49153,239.1.2.10,8400,UDP,2305,2964230,46.08,1286.0,0.02,0.000146,50.0,64328.1,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.210,49153,239.1.2.10,8400,UDP,2304,2962944,46.08,1286.0,0.020009,0.000436,50.0,64300.0,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.151,49153,239.1.2.10,8400,UDP,2304,2962944,46.06,1286.0,0.02,0.000102,50.0,64328.2,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.220,49153,239.1.2.10,8400,UDP,2305,2964230,46.08,1286.0,0.02,0.000103,50.0,64328.3,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.146,49153,239.1.2.10,8400,UDP,2304,2962944,46.06,1286.0,0.02,0.000147,50.0,64328.3,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.145,49154,239.1.2.10,8400,UDP,2445,3153482,46.06,1289.8,0.018846,0.004591,53.1,68464.9,0,0,53.1,0,0,0,0,53.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.131,49154,239.1.2.10,8400,UDP,2445,3152730,46.06,1289.5,0.018846,0.004601,53.1,68448.6,0,0,53.1,0,0,0,0,53.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,53.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.149,49153,239.1.2.10,8400,UDP,2304,2962944,46.06,1286.0,0.02,0.00011,50.0,64328.2,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.113,49153,239.1.2.10,8400,UDP,2304,2962944,46.06,1286.0,0.02,0.000104,50.0,64328.2,0,0,50.0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,50.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.184,49153,239.1.2.10,8400,UDP,461,592846,46.0,1286.0,0.1,0.00011,10.0,12888.0,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.173,49154,239.1.2.10,8400,UDP,1063,1368898,46.05,1287.8,0.043362,0.016909,23.1,29726.3,0,0,23.1,0,0,0,0,23.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,23.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.165,49153,239.1.2.10,8400,UDP,461,592846,46.0,1286.0,0.1,0.000123,10.0,12888.0,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.214,49153,239.1.2.10,8400,UDP,461,592846,46.0,1286.0,0.1,0.000122,10.0,12888.0,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.169,49153,239.1.2.10,8400,UDP,460,591560,45.902,1286.0,0.100003,0.000129,10.0,12887.6,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.150,49153,239.1.2.10,8400,UDP,461,592846,46.0,1286.0,0.099999,8.2e-05,10.0,12888.0,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.112,49153,239.1.2.10,8400,UDP,461,592846,46.0,1286.0,0.099999,8.5e-05,10.0,12888.0,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.133,49153,239.1.2.10,8400,UDP,461,592846,46.0,1286.0,0.099999,8.4e-05,10.0,12888.0,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.181,49153,239.1.2.10,8400,UDP,460,591560,45.9,1286.0,0.1,0.00012,10.0,12888.1,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.217,49153,239.1.2.10,8400,UDP,460,591560,45.9,1286.0,0.099999,9.6e-05,10.0,12888.1,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.148,49153,239.1.2.10,8400,UDP,450,578700,45.9,1286.0,0.102227,0.047141,9.8,12607.9,0,0,9.8,0,0,0,0,9.8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.145,49153,239.1.2.10,8400,UDP,460,591560,45.9,1286.0,0.1,8.8e-05,10.0,12888.0,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.115,49153,239.1.2.10,8400,UDP,413,531118,45.9,1286.0,0.111407,0.034757,9.0,11571.3,0,0,9.0,0,0,0,0,9.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.168,49153,239.1.2.10,8400,UDP,428,550408,45.9,1286.0,0.107494,0.028905,9.3,11991.5,0,0,9.3,0,0,0,0,9.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.166,49153,239.1.2.10,8400,UDP,460,591560,45.9,1286.0,0.099999,0.00011,10.0,12888.1,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.129,49153,239.1.2.10,8400,UDP,418,537548,45.9,1286.0,0.110072,0.030921,9.1,11711.3,0,0,9.1,0,0,0,0,9.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.197,49153,239.1.2.10,8400,UDP,414,532404,45.9,1286.0,0.111138,0.034442,9.0,11599.2,0,0,9.0,0,0,0,0,9.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.163,49153,239.1.2.10,8400,UDP,460,591560,45.9,1286.0,0.1,0.000105,10.0,12888.1,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.161,49153,239.1.2.10,8400,UDP,460,591560,45.9,1286.0,0.1,9.1e-05,10.0,12888.1,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.200,49153,239.1.2.10,8400,UDP,460,591560,45.9,1286.0,0.1,9e-05,10.0,12888.1,0,0,10.0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.164,49153,239.1.2.10,8400,UDP,433,556838,45.9,1286.0,0.10625,0.072155,9.4,12131.6,0,0,9.4,0,0,0,0,9.4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.165,49153,239.1.2.10,8400,UDP,430,552980,45.9,1286.0,0.106993,0.069639,9.4,12047.5,0,0,9.4,0,0,0,0,9.4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.192,49153,239.1.2.10,8400,UDP,449,577414,45.9,1286.0,0.102455,0.015464,9.8,12579.8,0,0,9.8,0,0,0,0,9.8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.162,49153,239.1.2.10,8400,UDP,230,47380,45.801,206.0,0.200005,0.002143,5.0,1034.5,0,0,5.0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.150,49153,239.1.2.10,8400,UDP,230,47380,45.797,206.0,0.199987,0.002347,5.0,1034.6,0,0,5.0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.175,49153,239.1.2.10,8400,UDP,229,47174,45.798,206.0,0.200868,0.013255,5.0,1030.0,0,0,5.0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.130,49153,239.1.2.10,8400,UDP,230,47380,45.798,206.0,0.199993,0.002432,5.0,1034.5,0,0,5.0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.164,49153,239.1.2.10,8400,UDP,230,295780,45.806,1286.0,0.200026,0.003326,5.0,6457.2,0,0,5.0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.155,49153,239.1.2.10,8400,UDP,209,268774,45.79,1286.0,0.220145,0.064008,4.6,5869.7,0,0,4.6,0,0,0,0,4.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.113,319,224.0.1.129,319,UDP,4,344,36.519,86.0,12.172929,2.167395,0.1,9.4,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10.106.30.117,320,224.0.1.129,320,UDP,591,56506,45.669,95.6,0.077406,0.089672,12.9,1237.3,0,0,12.9,0,0,0,0,12.9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12.9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10.106.30.117,319,224.0.1.129,319,UDP,46,3956,45.002,86.0,1.000047,0.021243,1.0,87.9,0,0,1.0,0,0,0,0,1.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.126,319,224.0.1.129,319,UDP,6,516,36.521,86.0,7.304178,3.883921,0.2,14.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.118,49153,239.1.2.10,8400,UDP,403,518258,44.901,1286.0,0.111694,0.100134,9.0,11542.3,0,0,9.0,0,0,0,0,9.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,9.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.164,319,224.0.1.129,319,UDP,7,602,43.182,86.0,7.197,4.365507,0.2,13.9,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.212,49153,239.1.2.10,8400,UDP,23,29578,44.003,1286.0,2.000114,0.001948,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.166,49153,239.1.2.10,8400,UDP,23,29578,44.001,1286.0,2.000055,0.001313,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.110,49153,239.1.2.10,8400,UDP,23,29578,44.0,1286.0,2.00001,0.002433,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.130,49153,239.1.2.10,8400,UDP,23,29578,44.002,1286.0,2.00009,0.002297,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.148,49153,239.1.2.10,8400,UDP,23,29578,44.001,1286.0,2.000052,0.001085,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.160,49154,239.1.2.10,8400,UDP,161,216246,45.116,1343.1,0.281972,0.395227,3.6,4793.2,0,0,3.6,0,0,0,0,3.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.149,49153,239.1.2.10,8400,UDP,23,29578,43.998,1286.0,1.999914,0.002068,0.5,672.3,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.163,319,224.0.1.129,319,UDP,6,516,34.713,86.0,6.942651,3.544789,0.2,14.9,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.213,49153,239.1.2.10,8400,UDP,23,29578,43.997,1286.0,1.999857,0.001747,0.5,672.3,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.218,319,224.0.1.129,319,UDP,4,344,39.372,86.0,13.123877,2.928612,0.1,8.7,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.216,319,224.0.1.129,319,UDP,7,602,32.702,86.0,5.45035,4.434816,0.2,18.4,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.146,319,224.0.1.129,319,UDP,7,602,45.086,86.0,7.514355,4.53908,0.2,13.4,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.151,319,224.0.1.129,319,UDP,9,774,42.44,86.0,5.304996,3.348239,0.2,18.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.161,319,224.0.1.129,319,UDP,6,516,42.609,86.0,8.521825,5.146987,0.1,12.1,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.161,319,224.0.1.129,319,UDP,5,430,44.269,86.0,11.067135,1.661814,0.1,9.7,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.220,319,224.0.1.129,319,UDP,8,688,35.465,86.0,5.066482,3.870116,0.2,19.4,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.112,49153,239.1.2.10,8400,UDP,23,29578,44.0,1286.0,1.999991,7.3e-05,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10.106.30.115,5010,239.0.1.133,5010,UDP,46,7084,45.027,154.0,1.000609,0.047616,1.0,157.3,0,0,1.0,0,0,0,0,1.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.191,319,224.0.1.129,319,UDP,5,430,44.046,86.0,11.011573,3.299827,0.1,9.8,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.158,319,224.0.1.129,319,UDP,4,344,44.299,86.0,14.766266,1.152442,0.1,7.8,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.167,319,224.0.1.129,319,UDP,6,516,34.878,86.0,6.975532,3.498272,0.2,14.8,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.111,49153,239.1.2.10,8400,UDP,23,29578,43.998,1286.0,1.999913,0.001061,0.5,672.3,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.132,319,224.0.1.129,319,UDP,6,516,41.051,86.0,8.210203,4.501499,0.1,12.6,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.111,319,224.0.1.129,319,UDP,5,430,35.432,86.0,8.85792,5.805048,0.1,12.1,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.154,319,224.0.1.129,319,UDP,5,430,32.009,86.0,8.002179,5.179198,0.2,13.4,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.169,319,224.0.1.129,319,UDP,4,344,32.731,86.0,10.910293,4.105734,0.1,10.5,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.148,319,224.0.1.129,319,UDP,7,602,38.779,86.0,6.463108,4.364559,0.2,15.5,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.110,319,224.0.1.129,319,UDP,10,860,41.203,86.0,4.578104,4.472113,0.2,20.9,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.164,319,224.0.1.129,319,UDP,6,516,43.599,86.0,8.719832,5.640652,0.1,11.8,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.154,49153,239.1.2.10,8400,UDP,23,29578,44.004,1286.0,2.000198,0.002351,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.178,49153,239.1.2.10,8400,UDP,23,29578,44.002,1286.0,2.000079,0.001387,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.111,49153,239.1.2.10,8400,UDP,23,29578,43.999,1286.0,1.999934,0.002509,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.159,49153,239.1.2.10,8400,UDP,23,29578,43.999,1286.0,1.999962,0.002397,0.5,672.2,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.209,319,224.0.1.129,319,UDP,7,602,36.511,86.0,6.085097,4.606922,0.2,16.5,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.203,319,224.0.1.129,319,UDP,5,430,38.222,86.0,9.555579,5.983865,0.1,11.2,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.184,319,224.0.1.129,319,UDP,5,430,26.359,86.0,6.589804,6.192937,0.2,16.3,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.212,319,224.0.1.129,319,UDP,6,516,40.598,86.0,8.11954,4.426785,0.1,12.7,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.206,319,224.0.1.129,319,UDP,11,946,40.448,86.0,4.044849,4.201274,0.3,23.4,0,0,0.3,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.181,319,224.0.1.129,319,UDP,10,860,38.016,86.0,4.223996,3.347284,0.3,22.6,0,0,0.3,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.166,319,224.0.1.129,319,UDP,5,430,34.495,86.0,8.623754,4.090838,0.1,12.5,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.172,319,224.0.1.129,319,UDP,8,688,32.613,86.0,4.659041,4.890259,0.2,21.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.162,49153,239.1.2.10,8400,UDP,22,28292,34.5,1286.0,1.642867,7.070268,0.6,820.1,0,0,0.6,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.163,49153,239.1.2.10,8400,UDP,20,25720,34.402,1286.0,1.810637,7.432277,0.6,747.6,0,0,0.6,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.211,49153,239.1.2.10,8400,UDP,20,25720,34.402,1286.0,1.810622,7.432305,0.6,747.6,0,0,0.6,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.219,49153,239.1.2.10,8400,UDP,19,24434,34.303,1286.0,1.905726,7.635845,0.6,712.3,0,0,0.6,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.127,319,224.0.1.129,319,UDP,9,774,37.052,86.0,4.631533,3.237616,0.2,20.9,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.130,319,224.0.1.129,319,UDP,6,516,32.863,86.0,6.572551,4.912905,0.2,15.7,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.208,319,224.0.1.129,319,UDP,5,430,34.182,86.0,8.545524,5.282449,0.1,12.6,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.171,319,224.0.1.129,319,UDP,6,516,41.104,86.0,8.220866,2.382756,0.1,12.6,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.150,319,224.0.1.129,319,UDP,6,516,41.261,86.0,8.2523,5.698831,0.1,12.5,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.207,319,224.0.1.129,319,UDP,6,516,34.052,86.0,6.810426,6.546633,0.2,15.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.174,319,224.0.1.129,319,UDP,5,430,34.86,86.0,8.714951,5.557099,0.1,12.3,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.125,319,224.0.1.129,319,UDP,6,516,39.781,86.0,7.956158,5.051183,0.2,13.0,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.144,319,224.0.1.129,319,UDP,6,516,37.44,86.0,7.487997,4.005579,0.2,13.8,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.118,319,224.0.1.129,319,UDP,9,774,40.929,86.0,5.116173,5.082163,0.2,18.9,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.165,319,224.0.1.129,319,UDP,8,688,38.251,86.0,5.464416,4.903232,0.2,18.0,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.194,319,224.0.1.129,319,UDP,6,516,39.78,86.0,7.955976,3.919858,0.2,13.0,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.146,319,224.0.1.129,319,UDP,8,688,31.662,86.0,4.523099,5.358957,0.3,21.7,0,0,0.3,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.159,319,224.0.1.129,319,UDP,8,688,39.157,86.0,5.593842,4.170994,0.2,17.6,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.168,319,224.0.1.129,319,UDP,7,602,37.178,86.0,6.196341,5.103783,0.2,16.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.131,319,224.0.1.129,319,UDP,6,516,39.8,86.0,7.960082,4.150032,0.2,13.0,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.201,319,224.0.1.129,319,UDP,5,430,28.865,86.0,7.216178,4.7987,0.2,14.9,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.166,319,224.0.1.129,319,UDP,7,602,39.891,86.0,6.648537,5.122631,0.2,15.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.189,319,224.0.1.129,319,UDP,5,430,37.371,86.0,9.342736,6.498406,0.1,11.5,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.111,319,224.0.1.129,319,UDP,7,602,29.697,86.0,4.949463,3.767283,0.2,20.3,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.149,319,224.0.1.129,319,UDP,5,430,37.973,86.0,9.493294,6.103851,0.1,11.3,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.155,319,224.0.1.129,319,UDP,5,430,36.731,86.0,9.182694,7.523286,0.1,11.7,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.145,319,224.0.1.129,319,UDP,5,430,28.512,86.0,7.128005,4.238535,0.2,15.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.204,319,224.0.1.129,319,UDP,6,516,39.097,86.0,7.819482,4.543602,0.2,13.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.129,319,224.0.1.129,319,UDP,5,430,29.332,86.0,7.332979,4.682394,0.2,14.7,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.134,319,224.0.1.129,319,UDP,7,602,31.445,86.0,5.240851,4.621927,0.2,19.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.169,319,224.0.1.129,319,UDP,4,344,33.292,86.0,11.097321,2.343491,0.1,10.3,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.112,319,224.0.1.129,319,UDP,8,688,34.032,86.0,4.861688,2.928379,0.2,20.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.128,49153,239.1.2.10,8400,UDP,19,24434,37.998,1286.0,2.111021,0.471804,0.5,643.0,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.152,319,224.0.1.129,319,UDP,4,344,34.766,86.0,11.588596,2.929211,0.1,9.9,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.150,319,224.0.1.129,319,UDP,3,258,25.091,86.0,12.545264,2.439569,0.1,10.3,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.128,319,224.0.1.129,319,UDP,5,430,28.112,86.0,7.028085,6.929328,0.2,15.3,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.142,319,224.0.1.129,319,UDP,7,602,37.558,86.0,6.259691,4.739393,0.2,16.0,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.141,319,224.0.1.129,319,UDP,6,516,35.817,86.0,7.163342,5.686353,0.2,14.4,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.173,319,224.0.1.129,319,UDP,5,430,38.1,86.0,9.524951,6.434794,0.1,11.3,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.214,319,224.0.1.129,319,UDP,6,516,32.909,86.0,6.581718,4.919554,0.2,15.7,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.130,319,224.0.1.129,319,UDP,6,516,36.64,86.0,7.328085,6.200818,0.2,14.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.125,319,224.0.1.129,319,UDP,5,430,24.249,86.0,6.062152,5.320428,0.2,17.7,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.210,319,224.0.1.129,319,UDP,4,344,36.685,86.0,12.228269,2.177304,0.1,9.4,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.149,319,224.0.1.129,319,UDP,7,602,36.026,86.0,6.0043,2.980242,0.2,16.7,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.142,319,224.0.1.129,319,UDP,4,344,27.776,86.0,9.258506,6.406249,0.1,12.4,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.152,319,224.0.1.129,319,UDP,4,344,32.056,86.0,10.685259,5.635126,0.1,10.7,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.165,319,224.0.1.129,319,UDP,4,344,36.327,86.0,12.109128,1.960391,0.1,9.5,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.162,319,224.0.1.129,319,UDP,6,516,33.397,86.0,6.679302,4.771083,0.2,15.5,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.1.1,319,224.0.1.129,319,UDP,6,516,32.838,86.0,6.567656,3.786976,0.2,15.7,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +0.0.0.0,68,255.255.255.255,67,UDP,3,1050,34.652,350.0,17.326135,7.850539,0.1,30.3,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.178,319,224.0.1.129,319,UDP,4,344,25.87,86.0,8.623368,3.750274,0.2,13.3,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.115,319,224.0.1.129,319,UDP,4,344,27.657,86.0,9.21893,2.212154,0.1,12.4,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.133,319,224.0.1.129,319,UDP,4,344,30.422,86.0,10.140536,7.76124,0.1,11.3,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.142,49153,239.1.2.10,8400,UDP,18,23148,35.999,1286.0,2.117566,0.484844,0.5,643.0,0,0,0.5,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.213,319,224.0.1.129,319,UDP,4,344,32.566,86.0,10.855309,3.223699,0.1,10.6,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.200,319,224.0.1.129,319,UDP,5,430,32.271,86.0,8.067727,5.309976,0.2,13.3,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.160,319,224.0.1.129,319,UDP,4,344,28.477,86.0,9.492486,7.880677,0.1,12.1,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.217,319,224.0.1.129,319,UDP,4,344,29.98,86.0,9.993465,4.654799,0.1,11.5,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.185,319,224.0.1.129,319,UDP,3,258,28.938,86.0,14.469163,1.31987,0.1,8.9,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.133,319,224.0.1.129,319,UDP,5,430,28.069,86.0,7.017292,4.175806,0.2,15.3,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.175,319,224.0.1.129,319,UDP,4,344,24.667,86.0,8.222474,6.770674,0.2,13.9,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.122,319,224.0.1.129,319,UDP,7,602,35.159,86.0,5.85981,5.678298,0.2,17.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.151,319,224.0.1.129,319,UDP,5,430,33.587,86.0,8.396728,4.989681,0.1,12.8,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.112,319,224.0.1.129,319,UDP,6,516,30.133,86.0,6.026505,5.83794,0.2,17.1,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.143,319,224.0.1.129,319,UDP,5,430,25.358,86.0,6.339618,4.699212,0.2,17.0,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.170,319,224.0.1.129,319,UDP,6,516,19.706,86.0,3.941216,2.849791,0.3,26.2,0,0,0.3,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.131,319,224.0.1.129,319,UDP,7,602,28.437,86.0,4.739563,4.642557,0.2,21.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.197,319,224.0.1.129,319,UDP,4,344,29.762,86.0,9.920579,3.661659,0.1,11.6,0,0,0.1,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.145,319,224.0.1.129,319,UDP,5,430,30.823,86.0,7.705734,3.558499,0.2,14.0,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.148,319,224.0.1.129,319,UDP,5,430,30.277,86.0,7.56925,4.641159,0.2,14.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.192,319,224.0.1.129,319,UDP,5,430,27.889,86.0,6.972166,5.219693,0.2,15.4,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.7.128,319,224.0.1.129,319,UDP,4,344,18.901,86.0,6.300387,6.09843,0.2,18.2,0,0,0.2,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +10.106.30.117,0,224.0.0.22,0,2,3,180,4.488,60.0,2.243994,3.032118,0.7,40.1,0,0,0.7,0,0,0,0,0.7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +192.168.6.1,5353,224.0.0.251,5353,UDP,4,344,1.005,86.0,0.334957,0.578808,4.0,342.3,0,0,4.0,0,0,0,0,4.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4.0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 diff --git a/1 PTPGM.pcapng b/1 PTPGM.pcapng new file mode 100644 index 0000000..80bc023 Binary files /dev/null and b/1 PTPGM.pcapng differ diff --git a/FSTDaircraft.pcapng b/FSTDaircraft.pcapng new file mode 100644 index 0000000..4862294 Binary files /dev/null and b/FSTDaircraft.pcapng differ diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/airstream.py b/airstream.py new file mode 100755 index 0000000..2b32bef --- /dev/null +++ b/airstream.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 + +import argparse + +from scapy.all import get_if_list + +from frametypes import FRAME_TYPES +from core.analyzer import PacketFlowAnalyzer + + +def main(): + p = argparse.ArgumentParser(description="Airstream - Packet Flow Analyzer") + p.add_argument('-p', '--pcap', help='PCAP file') + p.add_argument('-i', '--interface', help='Network interface') + p.add_argument('-c', '--count', type=int, default=100, help='Packet count (default: 100)') + p.add_argument('-l', '--list-interfaces', action='store_true', help='List interfaces') + p.add_argument('-o', '--output', help='CSV output file') + p.add_argument('-s', '--stats', nargs='+', choices=list(FRAME_TYPES.keys()) + ['all'], + default=['overview'], help='Statistics types to use (default: overview, use "all" for all types)') + + args = p.parse_args() + + if args.list_interfaces: + print("Interfaces:", *get_if_list(), sep='\n ') + return + + if not (args.pcap or args.interface): + p.error("Specify --pcap or --interface") + + # Handle 'all' option + if 'all' in args.stats: + selected_stats = list(FRAME_TYPES.keys()) + stats_classes = list(FRAME_TYPES.values()) + else: + selected_stats = args.stats + stats_classes = [FRAME_TYPES[s] for s in args.stats] + + print(f"Using stats: {', '.join(selected_stats)}") + + analyzer = PacketFlowAnalyzer(stats_classes) + + try: + if args.pcap: + analyzer.analyze_pcap(args.pcap) + else: + analyzer.analyze_live(args.interface, args.count) + + analyzer.print_summary() + + if args.output: + analyzer.summary().to_csv(args.output, index=False) + print(f"Saved: {args.output}") + + except KeyboardInterrupt: + print("\nInterrupted") + except Exception as e: + print(f"Error: {e}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/airstream_pyshark.py b/airstream_pyshark.py new file mode 100755 index 0000000..29f0171 --- /dev/null +++ b/airstream_pyshark.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +""" +Airstream PyShark Proof of Concept +=================================== + +Alternative implementation using PyShark for enhanced protocol support. + +Key Features: +- Uses Wireshark dissectors for comprehensive protocol decoding +- Supports custom Lua dissectors automatically +- Provides Wireshark display filter support +- Can decode any protocol that Wireshark understands + +Requirements: +- pip install pyshark +- Wireshark/tshark must be installed on the system + +Usage: + ./airstream_pyshark.py -p capture.pcap + ./airstream_pyshark.py -i eth0 -c 100 + ./airstream_pyshark.py -p capture.pcap --filter "tcp.port==80" +""" + +import argparse +import sys +from typing import List + +try: + import pyshark +except ImportError: + print("Error: pyshark not installed. Run: pip install pyshark") + sys.exit(1) + +from pyshark_poc.analyzer import PySharkAnalyzer +from pyshark_poc.stats import STATS_TYPES + + +def get_interfaces(): + """Get list of available network interfaces.""" + try: + # PyShark doesn't provide interface listing directly + # Use tshark command + import subprocess + result = subprocess.run(['tshark', '-D'], capture_output=True, text=True) + if result.returncode == 0: + interfaces = [] + for line in result.stdout.strip().split('\n'): + # Parse lines like "1. eth0" + parts = line.split('. ', 1) + if len(parts) == 2: + interfaces.append(parts[1].split(' ')[0]) + return interfaces + except: + pass + return ["Unable to list interfaces - tshark may not be installed"] + + +def main(): + parser = argparse.ArgumentParser( + description="Airstream PyShark - Enhanced packet flow analyzer with Wireshark dissector support", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + Analyze PCAP file: + %(prog)s -p capture.pcap + + Live capture with filter: + %(prog)s -i eth0 -c 1000 --filter "tcp.port==443" + + Use BPF filter for capture: + %(prog)s -i eth0 --bpf "port 80 or port 443" + + Export to CSV: + %(prog)s -p capture.pcap -o results.csv + +PyShark Advantages: + - Full Wireshark protocol decoding + - Custom Lua dissector support + - Advanced display filters + - Automatic field extraction + """ + ) + + # Input sources + input_group = parser.add_mutually_exclusive_group() + input_group.add_argument('-p', '--pcap', help='PCAP file to analyze') + input_group.add_argument('-i', '--interface', help='Network interface for live capture') + + # Capture options + parser.add_argument('-c', '--count', type=int, default=100, + help='Number of packets to capture (default: 100)') + + # Filtering options + parser.add_argument('--filter', '--display-filter', dest='display_filter', + help='Wireshark display filter (e.g., "tcp.port==80")') + parser.add_argument('--bpf', '--bpf-filter', dest='bpf_filter', + help='BPF capture filter for live capture (e.g., "port 80")') + + # Output options + parser.add_argument('-o', '--output', help='CSV output file') + + # Statistics options + parser.add_argument('-s', '--stats', nargs='+', + choices=list(STATS_TYPES.keys()) + ['all'], + default=['overview'], + help='Statistics types to use (default: overview)') + + # Utility options + parser.add_argument('-l', '--list-interfaces', action='store_true', + help='List available network interfaces') + parser.add_argument('-v', '--verbose', action='store_true', + help='Verbose output') + + args = parser.parse_args() + + # Handle interface listing + if args.list_interfaces: + print("Available interfaces:") + for iface in get_interfaces(): + print(f" {iface}") + return + + # Validate input + if not (args.pcap or args.interface): + parser.error("Specify either --pcap or --interface") + + # Handle statistics selection + if 'all' in args.stats: + selected_stats = list(STATS_TYPES.keys()) + stats_classes = list(STATS_TYPES.values()) + else: + selected_stats = args.stats + stats_classes = [STATS_TYPES[s] for s in args.stats] + + print(f"Using stats: {', '.join(selected_stats)}") + + # Create analyzer + analyzer = PySharkAnalyzer(stats_classes) + + try: + # Perform analysis + if args.pcap: + analyzer.analyze_pcap(args.pcap, display_filter=args.display_filter) + else: + analyzer.analyze_live( + args.interface, + args.count, + display_filter=args.display_filter, + bpf_filter=args.bpf_filter + ) + + # Print results + analyzer.print_summary() + + # Show protocol distribution if verbose + if args.verbose: + print("\nProtocol distribution:") + protocol_summary = analyzer.get_protocol_summary() + if not protocol_summary.empty: + print(protocol_summary.to_string(index=False)) + + # Save to CSV if requested + if args.output: + df = analyzer.summary() + df.to_csv(args.output, index=False) + print(f"\nSaved results to: {args.output}") + + except KeyboardInterrupt: + print("\nInterrupted by user") + except Exception as e: + print(f"Error: {e}") + if args.verbose: + import traceback + traceback.print_exc() + sys.exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e9dd75f --- /dev/null +++ b/core/__init__.py @@ -0,0 +1,5 @@ +from core.analyzer import PacketFlowAnalyzer +from core.models import FlowKey +from core.stats import MultiStats + +__all__ = ['PacketFlowAnalyzer', 'FlowKey', 'MultiStats'] \ No newline at end of file diff --git a/core/__pycache__/__init__.cpython-313.pyc b/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..5cc4dc6 Binary files /dev/null and b/core/__pycache__/__init__.cpython-313.pyc differ diff --git a/core/__pycache__/analyzer.cpython-313.pyc b/core/__pycache__/analyzer.cpython-313.pyc new file mode 100644 index 0000000..857c759 Binary files /dev/null and b/core/__pycache__/analyzer.cpython-313.pyc differ diff --git a/core/__pycache__/models.cpython-313.pyc b/core/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..a063dca Binary files /dev/null and b/core/__pycache__/models.cpython-313.pyc differ diff --git a/core/__pycache__/stats.cpython-313.pyc b/core/__pycache__/stats.cpython-313.pyc new file mode 100644 index 0000000..cfd7a1a Binary files /dev/null and b/core/__pycache__/stats.cpython-313.pyc differ diff --git a/core/analyzer.py b/core/analyzer.py new file mode 100644 index 0000000..8ef7f85 --- /dev/null +++ b/core/analyzer.py @@ -0,0 +1,72 @@ +from collections import defaultdict +from typing import Optional, List, Type + +import pandas as pd +from scapy.all import PcapReader, IP, TCP, UDP, sniff +from tabulate import tabulate + +from frametypes import FrameTypeInterface, FRAME_TYPES +from core.models import FlowKey +from core.stats import MultiStats + + +class PacketFlowAnalyzer: + def __init__(self, stats_classes: List[Type[FrameTypeInterface]] = None): + if stats_classes is None: + stats_classes = [FRAME_TYPES['overview']] + self.stats_classes = stats_classes + self.flows = defaultdict(lambda: MultiStats(stats_classes)) + + def _get_flow_key(self, pkt) -> Optional[FlowKey]: + if not pkt.haslayer(IP): + return None + + ip = pkt[IP] + if pkt.haslayer(TCP): + return FlowKey(ip.src, pkt[TCP].sport, ip.dst, pkt[TCP].dport, "TCP") + elif pkt.haslayer(UDP): + return FlowKey(ip.src, pkt[UDP].sport, ip.dst, pkt[UDP].dport, "UDP") + else: + return FlowKey(ip.src, 0, ip.dst, 0, str(ip.proto)) + + def _process(self, pkt): + key = self._get_flow_key(pkt) + if key: + self.flows[key].add(float(pkt.time), len(pkt), pkt) + + def analyze_pcap(self, file: str): + print(f"Analyzing: {file}") + for pkt in PcapReader(file): + self._process(pkt) + print(f"Found {len(self.flows)} flows") + + def analyze_live(self, iface: str, count: int = 100): + print(f"Capturing {count} packets on {iface}") + sniff(iface=iface, prn=self._process, count=count) + print(f"Found {len(self.flows)} flows") + + def summary(self) -> pd.DataFrame: + rows = [] + for k, multi_stats in self.flows.items(): + row = { + 'Src IP': k.src_ip, + 'Src Port': k.src_port, + 'Dst IP': k.dst_ip, + 'Dst Port': k.dst_port, + 'Proto': k.protocol + } + if k.extended_type: + row['Type'] = k.extended_type + row.update(multi_stats.get_combined_summary()) + rows.append(row) + return pd.DataFrame(rows) + + def print_summary(self): + df = self.summary() + if df.empty: + print("No flows detected") + return + + print(f"\n{len(df)} flows:") + print(tabulate(df, headers='keys', tablefmt='plain', showindex=False)) + print(f"\nTotals: {df['Pkts'].sum()} packets, {df['Bytes'].sum()} bytes") \ No newline at end of file diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..2225248 --- /dev/null +++ b/core/models.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass +from typing import Optional + + +@dataclass(frozen=True) +class FlowKey: + src_ip: str + src_port: int + dst_ip: str + dst_port: int + protocol: str + extended_type: Optional[str] = None # For extended frame types like IENA, Chapter 10, etc. \ No newline at end of file diff --git a/core/stats.py b/core/stats.py new file mode 100644 index 0000000..1582df7 --- /dev/null +++ b/core/stats.py @@ -0,0 +1,31 @@ +from typing import Dict, List, Any, Type + +from scapy.all import Packet + +from frametypes import FrameTypeInterface + + +class MultiStats: + """Container for multiple stats instances.""" + def __init__(self, stats_classes: List[Type[FrameTypeInterface]]): + self.stats_instances = [cls() for cls in stats_classes] + self.stats_classes = stats_classes + + def add(self, timestamp: float, size: int, packet: Packet): + for stats in self.stats_instances: + stats.add(timestamp, size, packet) + + def get_combined_summary(self) -> Dict[str, Any]: + """Combine summaries from all stats instances.""" + combined = {} + for i, stats in enumerate(self.stats_instances): + summary = stats.get_summary_dict() + class_name = self.stats_classes[i].__name__.replace('Stats', '') + # Add prefix to avoid column name conflicts + for key, value in summary.items(): + if key in ['Pkts', 'Bytes', 'Duration']: # Common columns + if i == 0: # Only include once + combined[key] = value + else: + combined[f"{class_name}:{key}"] = value + return combined \ No newline at end of file diff --git a/frametypes/__init__.py b/frametypes/__init__.py new file mode 100644 index 0000000..a878c9c --- /dev/null +++ b/frametypes/__init__.py @@ -0,0 +1,44 @@ +"""Frame type implementations for packet analysis.""" + +from .base import FrameTypeInterface +from .overview import Overview +from .chapter10 import Chapter10Stats +from .ch10_analog import Ch10AnalogStats +from .ch10_tmats import Ch10TMATSStats +from .ch10_pcm import Ch10PCMStats +from .ch10_time import Ch10TimeStats +from .iena import IENAStats +from .ptp import PTPStats +from .ptp_sync import PTPSyncStats +from .ptp_announce import PTPAnnounceStats +from .ptp_delay import PTPDelayStats + +__all__ = [ + 'FrameTypeInterface', + 'Overview', + 'Chapter10Stats', + 'Ch10AnalogStats', + 'Ch10TMATSStats', + 'Ch10PCMStats', + 'Ch10TimeStats', + 'IENAStats', + 'PTPStats', + 'PTPSyncStats', + 'PTPAnnounceStats', + 'PTPDelayStats', +] + +# Registry of available frame type classes +FRAME_TYPES = { + 'overview': Overview, + 'chapter10': Chapter10Stats, + 'ch10-analog': Ch10AnalogStats, + 'ch10-tmats': Ch10TMATSStats, + 'ch10-pcm': Ch10PCMStats, + 'ch10-time': Ch10TimeStats, + 'iena': IENAStats, + 'ptp': PTPStats, + 'ptp-sync': PTPSyncStats, + 'ptp-announce': PTPAnnounceStats, + 'ptp-delay': PTPDelayStats +} \ No newline at end of file diff --git a/frametypes/__pycache__/__init__.cpython-313.pyc b/frametypes/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..2b85b6e Binary files /dev/null and b/frametypes/__pycache__/__init__.cpython-313.pyc differ diff --git a/frametypes/__pycache__/base.cpython-313.pyc b/frametypes/__pycache__/base.cpython-313.pyc new file mode 100644 index 0000000..d4510c9 Binary files /dev/null and b/frametypes/__pycache__/base.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ch10_analog.cpython-313.pyc b/frametypes/__pycache__/ch10_analog.cpython-313.pyc new file mode 100644 index 0000000..7033ec1 Binary files /dev/null and b/frametypes/__pycache__/ch10_analog.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ch10_pcm.cpython-313.pyc b/frametypes/__pycache__/ch10_pcm.cpython-313.pyc new file mode 100644 index 0000000..94db1f4 Binary files /dev/null and b/frametypes/__pycache__/ch10_pcm.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ch10_time.cpython-313.pyc b/frametypes/__pycache__/ch10_time.cpython-313.pyc new file mode 100644 index 0000000..be134b4 Binary files /dev/null and b/frametypes/__pycache__/ch10_time.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ch10_tmats.cpython-313.pyc b/frametypes/__pycache__/ch10_tmats.cpython-313.pyc new file mode 100644 index 0000000..54dca0f Binary files /dev/null and b/frametypes/__pycache__/ch10_tmats.cpython-313.pyc differ diff --git a/frametypes/__pycache__/chapter10.cpython-313.pyc b/frametypes/__pycache__/chapter10.cpython-313.pyc new file mode 100644 index 0000000..2e1d650 Binary files /dev/null and b/frametypes/__pycache__/chapter10.cpython-313.pyc differ diff --git a/frametypes/__pycache__/iena.cpython-313.pyc b/frametypes/__pycache__/iena.cpython-313.pyc new file mode 100644 index 0000000..f6e56bc Binary files /dev/null and b/frametypes/__pycache__/iena.cpython-313.pyc differ diff --git a/frametypes/__pycache__/overview.cpython-313.pyc b/frametypes/__pycache__/overview.cpython-313.pyc new file mode 100644 index 0000000..6fb2db6 Binary files /dev/null and b/frametypes/__pycache__/overview.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ptp.cpython-313.pyc b/frametypes/__pycache__/ptp.cpython-313.pyc new file mode 100644 index 0000000..306311d Binary files /dev/null and b/frametypes/__pycache__/ptp.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ptp_announce.cpython-313.pyc b/frametypes/__pycache__/ptp_announce.cpython-313.pyc new file mode 100644 index 0000000..020ffd9 Binary files /dev/null and b/frametypes/__pycache__/ptp_announce.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ptp_delay.cpython-313.pyc b/frametypes/__pycache__/ptp_delay.cpython-313.pyc new file mode 100644 index 0000000..6631146 Binary files /dev/null and b/frametypes/__pycache__/ptp_delay.cpython-313.pyc differ diff --git a/frametypes/__pycache__/ptp_sync.cpython-313.pyc b/frametypes/__pycache__/ptp_sync.cpython-313.pyc new file mode 100644 index 0000000..4af8e3a Binary files /dev/null and b/frametypes/__pycache__/ptp_sync.cpython-313.pyc differ diff --git a/frametypes/base.py b/frametypes/base.py new file mode 100644 index 0000000..88173d2 --- /dev/null +++ b/frametypes/base.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod +from typing import Dict, List, Any +from scapy.all import Packet + + +class FrameTypeInterface(ABC): + def __init__(self): + self.name: str = "" + self.frames_list = [] + self.bytes = 0 + + def get_name(self): + """Return the interface's name""" + return self.name + + @abstractmethod + def add(self, timestamp: float, size: int, packet: Packet): + """Add a packet to the statistics.""" + pass + + @abstractmethod + def get_summary_dict(self) -> Dict[str, Any]: + """Return a dictionary of statistics for the tabular report.""" + pass + + @abstractmethod + def get_column_definitions(self) -> List[tuple]: + """Return column definitions as [(column_name, format_spec), ...]""" + pass \ No newline at end of file diff --git a/frametypes/ch10_analog.py b/frametypes/ch10_analog.py new file mode 100644 index 0000000..c63d764 --- /dev/null +++ b/frametypes/ch10_analog.py @@ -0,0 +1,56 @@ +from collections import defaultdict +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class Ch10AnalogStats(FrameTypeInterface): + """Chapter 10 Analog Data Statistics""" + def __init__(self): + super().__init__() + self.name = "Chapter 10 Analog" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.channel_ids = set() + self.sample_counts = defaultdict(int) + self.data_gaps = 0 + self.overrange_count = 0 + self.underrange_count = 0 + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # In real implementation, decode Ch10 analog headers + # Extract channel ID, sample count, check for over/underrange + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Bytes': self.bytes, + 'Duration': round(duration, 3), + 'Channels': len(self.channel_ids), + 'Data Gaps': self.data_gaps, + 'Overrange': self.overrange_count, + 'Underrange': self.underrange_count, + 'Pkt/s': round(self.count / duration, 1) if duration > 0 else 0 + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Bytes', 'd'), + ('Duration', '.3f'), + ('Channels', 'd'), + ('Data Gaps', 'd'), + ('Overrange', 'd'), + ('Underrange', 'd'), + ('Pkt/s', '.1f') + ] \ No newline at end of file diff --git a/frametypes/ch10_pcm.py b/frametypes/ch10_pcm.py new file mode 100644 index 0000000..90461ff --- /dev/null +++ b/frametypes/ch10_pcm.py @@ -0,0 +1,54 @@ +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class Ch10PCMStats(FrameTypeInterface): + """Chapter 10 PCM (Pulse Code Modulation) Data Statistics""" + def __init__(self): + super().__init__() + self.name = "Chapter 10 PCM" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.channel_ids = set() + self.frame_sync_errors = 0 + self.bit_slip_events = 0 + self.minor_frame_count = 0 + self.major_frame_count = 0 + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # In real implementation, decode PCM frame structure + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Bytes': self.bytes, + 'Duration': round(duration, 3), + 'Channels': len(self.channel_ids), + 'Sync Errors': self.frame_sync_errors, + 'Bit Slips': self.bit_slip_events, + 'Minor Frames': self.minor_frame_count, + 'Major Frames': self.major_frame_count + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Bytes', 'd'), + ('Duration', '.3f'), + ('Channels', 'd'), + ('Sync Errors', 'd'), + ('Bit Slips', 'd'), + ('Minor Frames', 'd'), + ('Major Frames', 'd') + ] \ No newline at end of file diff --git a/frametypes/ch10_time.py b/frametypes/ch10_time.py new file mode 100644 index 0000000..05d0d34 --- /dev/null +++ b/frametypes/ch10_time.py @@ -0,0 +1,52 @@ +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class Ch10TimeStats(FrameTypeInterface): + """Chapter 10 Time Data Statistics""" + def __init__(self): + super().__init__() + self.name = "Chapter 10 Time" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.time_sources = set() + self.time_format_changes = 0 + self.leap_second_events = 0 + self.time_discontinuities = 0 + self.max_time_delta = 0 + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # In real implementation, decode time packets + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Bytes': self.bytes, + 'Time Sources': len(self.time_sources), + 'Format Changes': self.time_format_changes, + 'Leap Seconds': self.leap_second_events, + 'Discontinuities': self.time_discontinuities, + 'Max Delta': round(self.max_time_delta, 6) + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Bytes', 'd'), + ('Time Sources', 'd'), + ('Format Changes', 'd'), + ('Leap Seconds', 'd'), + ('Discontinuities', 'd'), + ('Max Delta', '.6f') + ] \ No newline at end of file diff --git a/frametypes/ch10_tmats.py b/frametypes/ch10_tmats.py new file mode 100644 index 0000000..8faefb2 --- /dev/null +++ b/frametypes/ch10_tmats.py @@ -0,0 +1,52 @@ +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class Ch10TMATSStats(FrameTypeInterface): + """Chapter 10 TMATS (Telemetry Attributes Transfer Standard) Statistics""" + def __init__(self): + super().__init__() + self.name = "Chapter 10 TMATS" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.tmats_versions = set() + self.configuration_changes = 0 + self.g_records = 0 # Data source records + self.r_records = 0 # Tape/storage records + self.m_records = 0 # Multiplexing/modulation records + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # In real implementation, parse TMATS records + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Bytes': self.bytes, + 'Versions': len(self.tmats_versions), + 'Config Changes': self.configuration_changes, + 'G-Records': self.g_records, + 'R-Records': self.r_records, + 'M-Records': self.m_records + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Bytes', 'd'), + ('Versions', 'd'), + ('Config Changes', 'd'), + ('G-Records', 'd'), + ('R-Records', 'd'), + ('M-Records', 'd') + ] \ No newline at end of file diff --git a/frametypes/chapter10.py b/frametypes/chapter10.py new file mode 100644 index 0000000..c69ee22 --- /dev/null +++ b/frametypes/chapter10.py @@ -0,0 +1,57 @@ +from collections import defaultdict +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class Chapter10Stats(FrameTypeInterface): + """General Chapter 10 Statistics""" + def __init__(self): + super().__init__() + self.name = "Chapter 10" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.channel_ids = set() + self.data_types = defaultdict(int) + self.sequence_errors = 0 + self.last_sequence = {} + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # Simulate Chapter 10 specific processing + # In real implementation, decode Chapter 10 headers + if packet: + # Example: extract channel ID and data type from payload + # channel_id = extract_channel_id(packet) + # data_type = extract_data_type(packet) + # sequence = extract_sequence_number(packet) + pass + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Bytes': self.bytes, + 'Duration': round(duration, 3), + 'Channels': len(self.channel_ids), + 'Seq Errors': self.sequence_errors, + 'Pkt/s': round(self.count / duration, 1) if duration > 0 else 0 + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Bytes', 'd'), + ('Duration', '.3f'), + ('Channels', 'd'), + ('Seq Errors', 'd'), + ('Pkt/s', '.1f') + ] \ No newline at end of file diff --git a/frametypes/iena.py b/frametypes/iena.py new file mode 100644 index 0000000..7b0a2e6 --- /dev/null +++ b/frametypes/iena.py @@ -0,0 +1,53 @@ +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class IENAStats(FrameTypeInterface): + """IENA Protocol Statistics""" + def __init__(self): + super().__init__() + self.name = "IENA" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.key_fields = set() + self.sequence_gaps = 0 + self.last_sequence = None + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # Simulate IENA specific processing + if packet: + # Example: extract IENA key field and sequence + # key_field = extract_iena_key(packet) + # sequence = extract_iena_sequence(packet) + pass + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Bytes': self.bytes, + 'Duration': round(duration, 3), + 'Key Fields': len(self.key_fields), + 'Seq Gaps': self.sequence_gaps, + 'Pkt/s': round(self.count / duration, 1) if duration > 0 else 0 + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Bytes', 'd'), + ('Duration', '.3f'), + ('Key Fields', 'd'), + ('Seq Gaps', 'd'), + ('Pkt/s', '.1f') + ] \ No newline at end of file diff --git a/frametypes/overview.py b/frametypes/overview.py new file mode 100644 index 0000000..31a8566 --- /dev/null +++ b/frametypes/overview.py @@ -0,0 +1,64 @@ +import statistics +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class Overview(FrameTypeInterface): + def __init__(self): + self.name = "Overview" + self.count = 0 + self.first_time = None + self.last_time = None + self.Tdeltas = [] + self.bytes = 0 + self.frames_list = [] + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + self.frames_list.append(packet) + + if self.first_time is None: + self.first_time = timestamp + else: + self.Tdeltas.append(timestamp - self.last_time) + self.last_time = timestamp + + @property + def duration(self): return (self.last_time or 0) - (self.first_time or 0) + @property + def avg_size(self): return self.bytes / self.count if self.count else 0 + @property + def avg_delta(self): return statistics.mean(self.Tdeltas) if self.Tdeltas else 0 + @property + def std_delta(self): return statistics.stdev(self.Tdeltas) if len(self.Tdeltas) > 1 else 0 + @property + def pkt_rate(self): return self.count / self.duration if self.duration > 0 else 0 + @property + def byte_rate(self): return self.bytes / self.duration if self.duration > 0 else 0 + + def get_summary_dict(self) -> Dict[str, Any]: + return { + 'Pkts': self.count, + 'Bytes': self.bytes, + 'Duration': round(self.duration, 3), + 'Avg Size': round(self.avg_size, 1), + 'Avg TimeΔ': round(self.avg_delta, 6), + 'Time 1σ': round(self.std_delta, 6), + 'Pkt/s': round(self.pkt_rate, 1), + 'B/s': round(self.byte_rate, 1) + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Bytes', 'd'), + ('Duration', '.3f'), + ('Avg Size', '.1f'), + ('Avg Delta', '.6f'), + ('Std Delta', '.6f'), + ('Pkt/s', '.1f'), + ('B/s', '.1f') + ] \ No newline at end of file diff --git a/frametypes/ptp.py b/frametypes/ptp.py new file mode 100644 index 0000000..4a61daf --- /dev/null +++ b/frametypes/ptp.py @@ -0,0 +1,56 @@ +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class PTPStats(FrameTypeInterface): + """General PTP Statistics""" + def __init__(self): + super().__init__() + self.name = "PTP" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.sync_messages = 0 + self.follow_up_messages = 0 + self.delay_req_messages = 0 + self.delay_resp_messages = 0 + self.announce_messages = 0 + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # Simulate PTP message type counting + if packet: + # Example: decode PTP message type + # msg_type = extract_ptp_message_type(packet) + pass + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Duration': round(duration, 3), + 'Sync': self.sync_messages, + 'Follow Up': self.follow_up_messages, + 'Delay Req': self.delay_req_messages, + 'Delay Resp': self.delay_resp_messages, + 'Announce': self.announce_messages + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Duration', '.3f'), + ('Sync', 'd'), + ('Follow Up', 'd'), + ('Delay Req', 'd'), + ('Delay Resp', 'd'), + ('Announce', 'd') + ] \ No newline at end of file diff --git a/frametypes/ptp_announce.py b/frametypes/ptp_announce.py new file mode 100644 index 0000000..3bec279 --- /dev/null +++ b/frametypes/ptp_announce.py @@ -0,0 +1,51 @@ +from collections import defaultdict +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class PTPAnnounceStats(FrameTypeInterface): + """PTP Announce Message Statistics""" + def __init__(self): + super().__init__() + self.name = "PTP Announce" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.announce_count = 0 + self.grandmaster_changes = 0 + self.priority_changes = 0 + self.grandmaster_ids = set() + self.clock_classes = defaultdict(int) + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # In real implementation, decode PTP announce messages + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + return { + 'Pkts': self.count, + 'Announce': self.announce_count, + 'GM Changes': self.grandmaster_changes, + 'Priority Changes': self.priority_changes, + 'Grandmasters': len(self.grandmaster_ids), + 'Clock Classes': len(self.clock_classes) + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Announce', 'd'), + ('GM Changes', 'd'), + ('Priority Changes', 'd'), + ('Grandmasters', 'd'), + ('Clock Classes', 'd') + ] \ No newline at end of file diff --git a/frametypes/ptp_delay.py b/frametypes/ptp_delay.py new file mode 100644 index 0000000..aabc802 --- /dev/null +++ b/frametypes/ptp_delay.py @@ -0,0 +1,53 @@ +import statistics +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class PTPDelayStats(FrameTypeInterface): + """PTP Delay Request/Response Statistics""" + def __init__(self): + super().__init__() + self.name = "PTP Delay" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.delay_req_count = 0 + self.delay_resp_count = 0 + self.pdelay_req_count = 0 + self.pdelay_resp_count = 0 + self.round_trip_times = [] + self.asymmetry_values = [] + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + self.last_time = timestamp + + # In real implementation, match delay requests with responses + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + avg_rtt = statistics.mean(self.round_trip_times) if self.round_trip_times else 0 + return { + 'Pkts': self.count, + 'Delay Req': self.delay_req_count, + 'Delay Resp': self.delay_resp_count, + 'PDelay Req': self.pdelay_req_count, + 'PDelay Resp': self.pdelay_resp_count, + 'Avg RTT': round(avg_rtt * 1000, 3) # Convert to ms + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Delay Req', 'd'), + ('Delay Resp', 'd'), + ('PDelay Req', 'd'), + ('PDelay Resp', 'd'), + ('Avg RTT', '.3f') + ] \ No newline at end of file diff --git a/frametypes/ptp_sync.py b/frametypes/ptp_sync.py new file mode 100644 index 0000000..204c4cc --- /dev/null +++ b/frametypes/ptp_sync.py @@ -0,0 +1,58 @@ +import statistics +from typing import Dict, List, Any +from scapy.all import Packet + +from .base import FrameTypeInterface + + +class PTPSyncStats(FrameTypeInterface): + """PTP Sync Message Statistics""" + def __init__(self): + super().__init__() + self.name = "PTP Sync" + self.count = 0 + self.bytes = 0 + self.first_time = None + self.last_time = None + self.sync_count = 0 + self.follow_up_count = 0 + self.two_step_count = 0 + self.one_step_count = 0 + self.sync_intervals = [] + self.clock_ids = set() + + def add(self, timestamp: float, size: int, packet: Packet): + self.count += 1 + self.bytes += size + if self.first_time is None: + self.first_time = timestamp + else: + if self.last_time and self.sync_count > 0: + self.sync_intervals.append(timestamp - self.last_time) + self.last_time = timestamp + + # In real implementation, decode PTP sync messages + + def get_summary_dict(self) -> Dict[str, Any]: + duration = (self.last_time or 0) - (self.first_time or 0) + avg_interval = statistics.mean(self.sync_intervals) if self.sync_intervals else 0 + return { + 'Pkts': self.count, + 'Sync Msgs': self.sync_count, + 'Follow-ups': self.follow_up_count, + 'Two-step': self.two_step_count, + 'One-step': self.one_step_count, + 'Clock IDs': len(self.clock_ids), + 'Avg Interval': round(avg_interval, 6) + } + + def get_column_definitions(self) -> List[tuple]: + return [ + ('Pkts', 'd'), + ('Sync Msgs', 'd'), + ('Follow-ups', 'd'), + ('Two-step', 'd'), + ('One-step', 'd'), + ('Clock IDs', 'd'), + ('Avg Interval', '.6f') + ] \ No newline at end of file diff --git a/lua_dissectors/README.md b/lua_dissectors/README.md new file mode 100644 index 0000000..7cb859e --- /dev/null +++ b/lua_dissectors/README.md @@ -0,0 +1,64 @@ +# Lua Dissectors for Airstream PyShark + +This directory contains example Lua dissectors that can be used with Wireshark/tshark to decode custom protocols in PyShark. + +## Installation + +1. Copy the Lua dissector files to your Wireshark plugins directory: + - **Linux/Mac**: `~/.local/lib/wireshark/plugins/` or `~/.config/wireshark/plugins/` + - **Windows**: `%APPDATA%\Wireshark\plugins\` + +2. Restart Wireshark/tshark or reload Lua plugins (Ctrl+Shift+L in Wireshark) + +3. The dissectors will automatically be available to PyShark + +## Example Custom Protocol Dissector + +The `example_custom_protocol.lua` demonstrates: +- Creating a custom protocol dissector +- Defining protocol fields +- Parsing packet structure +- Registering for specific UDP ports +- Heuristic dissection + +## Using with PyShark + +Once installed, PyShark will automatically use these dissectors: + +```python +import pyshark + +# Capture with custom dissector +capture = pyshark.FileCapture('capture.pcap') +for packet in capture: + if hasattr(packet, 'custom'): + print(f"Custom packet: {packet.custom.msg_type}") +``` + +## Creating Your Own Dissectors + +1. Copy `example_custom_protocol.lua` as a template +2. Modify the protocol name, fields, and parsing logic +3. Register for appropriate ports or use heuristic detection +4. Place in Wireshark plugins directory + +## Benefits for Airstream + +Custom Lua dissectors enable: +- Decoding proprietary protocols (IENA, Chapter 10, etc.) +- Adding metadata extraction +- Protocol-specific statistics +- Enhanced filtering capabilities + +## Testing Dissectors + +Test your dissector in Wireshark GUI first: +1. Open a capture file +2. Check if protocol appears in packet list +3. Verify field extraction in packet details +4. Use display filters like `custom.msg_type == 1` + +Then use with airstream_pyshark.py: +```bash +./airstream_pyshark.py -p capture.pcap --filter "custom" +``` \ No newline at end of file diff --git a/lua_dissectors/example_custom_protocol.lua b/lua_dissectors/example_custom_protocol.lua new file mode 100644 index 0000000..80c3b17 --- /dev/null +++ b/lua_dissectors/example_custom_protocol.lua @@ -0,0 +1,97 @@ +-- Example Custom Protocol Dissector for Wireshark/PyShark +-- Place this file in your Wireshark plugins directory: +-- Linux/Mac: ~/.local/lib/wireshark/plugins/ or ~/.wireshark/plugins/ +-- Windows: %APPDATA%\Wireshark\plugins\ +-- +-- This dissector will automatically be available to PyShark via tshark + +-- Create protocol +local custom_proto = Proto("custom", "Custom Airstream Protocol") + +-- Define fields +local fields = { + magic = ProtoField.uint32("custom.magic", "Magic Number", base.HEX), + version = ProtoField.uint8("custom.version", "Version", base.DEC), + msg_type = ProtoField.uint8("custom.msg_type", "Message Type", base.DEC, { + [1] = "Data", + [2] = "Control", + [3] = "Status", + [4] = "Telemetry" + }), + length = ProtoField.uint16("custom.length", "Payload Length", base.DEC), + sequence = ProtoField.uint32("custom.sequence", "Sequence Number", base.DEC), + timestamp = ProtoField.uint64("custom.timestamp", "Timestamp", base.DEC), + payload = ProtoField.bytes("custom.payload", "Payload") +} + +-- Add fields to protocol +custom_proto.fields = fields + +-- Dissector function +function custom_proto.dissector(buffer, pinfo, tree) + -- Validate minimum length + if buffer:len() < 20 then + return + end + + -- Check magic number (example: 0xABCD1234) + local magic = buffer(0,4):uint() + if magic ~= 0xABCD1234 then + return -- Not our protocol + end + + -- Set protocol column in packet list + pinfo.cols.protocol:set("CUSTOM") + + -- Create subtree for our protocol + local subtree = tree:add(custom_proto, buffer(), "Custom Airstream Protocol") + + -- Add fields to tree + subtree:add(fields.magic, buffer(0,4)) + subtree:add(fields.version, buffer(4,1)) + + local msg_type = buffer(5,1):uint() + subtree:add(fields.msg_type, buffer(5,1)) + + subtree:add(fields.length, buffer(6,2)) + subtree:add(fields.sequence, buffer(8,4)) + subtree:add(fields.timestamp, buffer(12,8)) + + -- Add payload if present + local payload_len = buffer(6,2):uint() + if buffer:len() >= 20 + payload_len then + subtree:add(fields.payload, buffer(20, payload_len)) + end + + -- Update info column with summary + local type_names = {[1]="Data", [2]="Control", [3]="Status", [4]="Telemetry"} + local type_name = type_names[msg_type] or "Unknown" + pinfo.cols.info:set(string.format("%s (Seq=%d, Len=%d)", + type_name, + buffer(8,4):uint(), + payload_len)) +end + +-- Register dissector for specific UDP port +local udp_port = DissectorTable.get("udp.port") +udp_port:add(9999, custom_proto) -- Listen on UDP port 9999 + +-- Also register as heuristic dissector for UDP +local function heuristic_checker(buffer, pinfo, tree) + -- Check if this might be our protocol + if buffer:len() < 20 then + return false + end + + -- Check magic number + local magic = buffer(0,4):uint() + if magic == 0xABCD1234 then + custom_proto.dissector(buffer, pinfo, tree) + return true + end + + return false +end + +-- Register heuristic dissector +custom_proto:register_heuristic("udp", heuristic_checker) \ No newline at end of file diff --git a/pyshark_poc/README.md b/pyshark_poc/README.md new file mode 100644 index 0000000..68e1b28 --- /dev/null +++ b/pyshark_poc/README.md @@ -0,0 +1,146 @@ +# PyShark Proof of Concept for Airstream + +This is an alternative implementation of Airstream using PyShark instead of Scapy. PyShark leverages Wireshark's powerful dissector engine for comprehensive protocol support. + +## Key Advantages + +### 1. **Full Wireshark Protocol Support** +- Automatically uses ALL installed Wireshark dissectors +- Supports 2000+ protocols out of the box +- Better decoding for complex protocols (PTP, IENA, Chapter 10) + +### 2. **Custom Dissector Support** +- Any Lua dissector installed in Wireshark works automatically +- See `lua_dissectors/` for examples +- No code changes needed to support new protocols + +### 3. **Advanced Filtering** +- Full Wireshark display filter syntax +- BPF capture filters for performance +- Protocol-specific field access + +## Installation + +```bash +# Install PyShark (requires tshark/Wireshark) +pip install pyshark + +# On macOS +brew install wireshark + +# On Ubuntu/Debian +sudo apt-get install tshark + +# On RHEL/CentOS +sudo yum install wireshark +``` + +## Usage + +```bash +# Basic PCAP analysis +./airstream_pyshark.py -p capture.pcap + +# Live capture with filter +./airstream_pyshark.py -i eth0 -c 1000 --filter "tcp.port==443" + +# Use BPF filter for efficient capture +./airstream_pyshark.py -i eth0 --bpf "port 80 or port 443" + +# Export results to CSV +./airstream_pyshark.py -p capture.pcap -o results.csv + +# Use PTP-specific statistics +./airstream_pyshark.py -p ptp_traffic.pcap -s ptp +``` + +## Architecture + +``` +airstream_pyshark.py # Main entry point (CLI) +pyshark_poc/ +├── __init__.py # Package initialization +├── analyzer.py # PySharkAnalyzer class +├── models.py # Data models (FlowKey) +├── stats.py # Statistics classes +└── README.md # This file + +lua_dissectors/ # Custom Wireshark dissectors +├── example_custom_protocol.lua +└── README.md +``` + +## Performance Comparison + +| Aspect | Scapy | PyShark | +|--------|-------|---------| +| Packet Parsing Speed | Faster | Slower (XML overhead) | +| Protocol Support | Limited | Comprehensive | +| Custom Dissectors | Python only | Lua + C | +| Memory Usage | Lower | Higher | +| Dependencies | Python only | Requires tshark | + +## When to Use PyShark + +✅ **Use PyShark when:** +- You need comprehensive protocol decoding +- Working with proprietary protocols +- Need Wireshark's advanced filtering +- Protocol accuracy is critical + +❌ **Use Scapy when:** +- Performance is critical +- Need packet crafting/modification +- Minimal dependencies required +- Simple protocol analysis + +## Custom Protocol Support + +To add custom protocol support: + +1. Create a Lua dissector (see `lua_dissectors/example_custom_protocol.lua`) +2. Install in Wireshark plugins directory +3. PyShark automatically uses it + +Example accessing custom fields: +```python +# After installing custom dissector +capture = pyshark.FileCapture('custom_protocol.pcap') +for packet in capture: + if hasattr(packet, 'custom'): + print(f"Message type: {packet.custom.msg_type}") + print(f"Sequence: {packet.custom.sequence}") +``` + +## Limitations + +1. **Performance**: Slower than Scapy due to XML parsing overhead +2. **Dependencies**: Requires Wireshark/tshark installation +3. **Read-only**: Cannot modify or craft packets +4. **Platform-specific**: tshark paths may vary + +## Future Enhancements + +- [ ] Parallel packet processing +- [ ] Caching for improved performance +- [ ] Integration with existing frametypes +- [ ] Protocol-specific analyzers +- [ ] Real-time streaming analysis +- [ ] Custom field extractors + +## Testing + +```bash +# Test with sample PCAP +./airstream_pyshark.py -p "1 PTPGM.pcapng" + +# List available interfaces +./airstream_pyshark.py -l + +# Verbose mode for debugging +./airstream_pyshark.py -p capture.pcap -v +``` + +## Conclusion + +This PyShark implementation provides a powerful alternative when comprehensive protocol support is needed. While it trades performance for functionality, it enables analysis of complex protocols that would be difficult to implement in pure Python. \ No newline at end of file diff --git a/pyshark_poc/__init__.py b/pyshark_poc/__init__.py new file mode 100644 index 0000000..020dbda --- /dev/null +++ b/pyshark_poc/__init__.py @@ -0,0 +1,20 @@ +""" +PyShark-based proof of concept for Airstream packet analyzer. + +This module provides an alternative implementation using PyShark +to leverage Wireshark's dissector capabilities. +""" + +from .analyzer import PySharkAnalyzer +from .models import FlowKey +from .stats import MultiStats, BaseStats, OverviewStats, PTPStats, STATS_TYPES + +__all__ = [ + 'PySharkAnalyzer', + 'FlowKey', + 'MultiStats', + 'BaseStats', + 'OverviewStats', + 'PTPStats', + 'STATS_TYPES' +] \ No newline at end of file diff --git a/pyshark_poc/__pycache__/__init__.cpython-313.pyc b/pyshark_poc/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..90d52d2 Binary files /dev/null and b/pyshark_poc/__pycache__/__init__.cpython-313.pyc differ diff --git a/pyshark_poc/__pycache__/analyzer.cpython-313.pyc b/pyshark_poc/__pycache__/analyzer.cpython-313.pyc new file mode 100644 index 0000000..11b6fbf Binary files /dev/null and b/pyshark_poc/__pycache__/analyzer.cpython-313.pyc differ diff --git a/pyshark_poc/__pycache__/models.cpython-313.pyc b/pyshark_poc/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..b170e7a Binary files /dev/null and b/pyshark_poc/__pycache__/models.cpython-313.pyc differ diff --git a/pyshark_poc/__pycache__/stats.cpython-313.pyc b/pyshark_poc/__pycache__/stats.cpython-313.pyc new file mode 100644 index 0000000..ddf46b9 Binary files /dev/null and b/pyshark_poc/__pycache__/stats.cpython-313.pyc differ diff --git a/pyshark_poc/analyzer.py b/pyshark_poc/analyzer.py new file mode 100644 index 0000000..b2df4a8 --- /dev/null +++ b/pyshark_poc/analyzer.py @@ -0,0 +1,187 @@ +import pyshark +from collections import defaultdict +from typing import Optional, List, Type, Union +import pandas as pd +from tabulate import tabulate + +from .models import FlowKey +from .stats import MultiStats, BaseStats, STATS_TYPES + + +class PySharkAnalyzer: + """Packet flow analyzer using PyShark for Wireshark dissector support.""" + + def __init__(self, stats_classes: Optional[List[Type[BaseStats]]] = None): + if stats_classes is None: + stats_classes = [STATS_TYPES['overview']] + self.stats_classes = stats_classes + self.flows = defaultdict(lambda: MultiStats(stats_classes)) + self.packet_count = 0 + + def _get_flow_key(self, packet) -> Optional[FlowKey]: + """Extract flow key from PyShark packet.""" + try: + # Check for IP layer + if not hasattr(packet, 'ip'): + return None + + src_ip = packet.ip.src + dst_ip = packet.ip.dst + protocol = packet.transport_layer if hasattr(packet, 'transport_layer') else 'IP' + + # Get ports based on protocol + src_port = 0 + dst_port = 0 + + if hasattr(packet, 'tcp'): + src_port = int(packet.tcp.srcport) + dst_port = int(packet.tcp.dstport) + protocol = 'TCP' + elif hasattr(packet, 'udp'): + src_port = int(packet.udp.srcport) + dst_port = int(packet.udp.dstport) + protocol = 'UDP' + + # Check for extended protocol types + extended_type = None + if hasattr(packet, 'ptp'): + extended_type = 'PTP' + # Add more protocol detection here as needed + + return FlowKey(src_ip, src_port, dst_ip, dst_port, protocol, extended_type) + + except AttributeError: + return None + + def _process_packet(self, packet): + """Process a single packet.""" + key = self._get_flow_key(packet) + if key: + # Get timestamp and size + timestamp = float(packet.sniff_timestamp) if hasattr(packet, 'sniff_timestamp') else 0 + size = int(packet.length) if hasattr(packet, 'length') else 0 + + self.flows[key].add(timestamp, size, packet) + self.packet_count += 1 + + def analyze_pcap(self, file: str, display_filter: Optional[str] = None): + """Analyze packets from a PCAP file.""" + print(f"Analyzing: {file}") + if display_filter: + print(f"Filter: {display_filter}") + + try: + # Use FileCapture for PCAP files + capture = pyshark.FileCapture( + file, + display_filter=display_filter, + use_json=True, # Use JSON output for better performance + include_raw=False # Don't include raw packet data + ) + + # Process packets + for packet in capture: + self._process_packet(packet) + # Show progress every 1000 packets + if self.packet_count % 1000 == 0: + print(f" Processed {self.packet_count} packets...") + + capture.close() + print(f"Found {len(self.flows)} flows from {self.packet_count} packets") + + except Exception as e: + print(f"Error analyzing PCAP: {e}") + + def analyze_live(self, interface: str, count: int = 100, + display_filter: Optional[str] = None, + bpf_filter: Optional[str] = None): + """Capture and analyze packets from a live interface.""" + print(f"Capturing {count} packets on {interface}") + if display_filter: + print(f"Display filter: {display_filter}") + if bpf_filter: + print(f"BPF filter: {bpf_filter}") + + try: + # Use LiveCapture for live capture + capture = pyshark.LiveCapture( + interface=interface, + display_filter=display_filter, + bpf_filter=bpf_filter, + use_json=True, + include_raw=False + ) + + # Capture packets + capture.sniff(packet_count=count) + + # Process captured packets + for packet in capture: + self._process_packet(packet) + + capture.close() + print(f"Found {len(self.flows)} flows from {self.packet_count} packets") + + except Exception as e: + print(f"Error during live capture: {e}") + + def summary(self) -> pd.DataFrame: + """Generate summary DataFrame of all flows.""" + rows = [] + for key, multi_stats in self.flows.items(): + row = { + 'Src IP': key.src_ip, + 'Src Port': key.src_port, + 'Dst IP': key.dst_ip, + 'Dst Port': key.dst_port, + 'Proto': key.protocol + } + if key.extended_type: + row['Type'] = key.extended_type + row.update(multi_stats.get_combined_summary()) + rows.append(row) + + # Sort by packet count descending + df = pd.DataFrame(rows) + if not df.empty and 'Pkts' in df.columns: + df = df.sort_values('Pkts', ascending=False) + return df + + def print_summary(self): + """Print formatted summary of flows.""" + df = self.summary() + if df.empty: + print("No flows detected") + return + + print(f"\n{len(df)} flows:") + print(tabulate(df, headers='keys', tablefmt='plain', showindex=False)) + + if 'Pkts' in df.columns and 'Bytes' in df.columns: + print(f"\nTotals: {df['Pkts'].sum()} packets, {df['Bytes'].sum()} bytes") + + def get_protocol_summary(self) -> pd.DataFrame: + """Get summary grouped by protocol.""" + df = self.summary() + if df.empty: + return df + + # Group by protocol + protocol_summary = df.groupby('Proto').agg({ + 'Pkts': 'sum', + 'Bytes': 'sum' + }).reset_index() + + return protocol_summary + + def apply_wireshark_filter(self, display_filter: str): + """ + Apply a Wireshark display filter to the analysis. + This demonstrates PyShark's ability to use Wireshark's filtering. + """ + filtered_flows = defaultdict(lambda: MultiStats(self.stats_classes)) + + # This would require re-processing with the filter + # Shown here as an example of the capability + print(f"Note: To apply Wireshark filters, re-analyze with display_filter parameter") + return filtered_flows \ No newline at end of file diff --git a/pyshark_poc/models.py b/pyshark_poc/models.py new file mode 100644 index 0000000..8d73a8e --- /dev/null +++ b/pyshark_poc/models.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from typing import Optional + + +@dataclass(frozen=True) +class FlowKey: + """Flow identifier for network traffic analysis.""" + src_ip: str + src_port: int + dst_ip: str + dst_port: int + protocol: str + extended_type: Optional[str] = None # For extended frame types like IENA, Chapter 10, etc. \ No newline at end of file diff --git a/pyshark_poc/stats.py b/pyshark_poc/stats.py new file mode 100644 index 0000000..11e8fe4 --- /dev/null +++ b/pyshark_poc/stats.py @@ -0,0 +1,145 @@ +from typing import Dict, List, Any, Type, Optional +import time + + +class BaseStats: + """Base statistics class for packet flow analysis.""" + + def __init__(self): + self.packet_count = 0 + self.byte_count = 0 + self.first_timestamp = None + self.last_timestamp = None + self.packet_sizes = [] + self.inter_arrival_times = [] + self.last_packet_time = None + + def add(self, timestamp: float, size: int, packet: Any): + """Add a packet to statistics.""" + self.packet_count += 1 + self.byte_count += size + self.packet_sizes.append(size) + + if self.first_timestamp is None: + self.first_timestamp = timestamp + self.last_timestamp = timestamp + + if self.last_packet_time is not None: + delta = timestamp - self.last_packet_time + self.inter_arrival_times.append(delta) + self.last_packet_time = timestamp + + # Call protocol-specific handler + self._process_packet(packet) + + def _process_packet(self, packet: Any): + """Override in subclasses for protocol-specific processing.""" + pass + + def get_summary_dict(self) -> Dict[str, Any]: + """Get summary statistics as dictionary.""" + duration = (self.last_timestamp - self.first_timestamp) if self.first_timestamp and self.last_timestamp else 0 + + summary = { + 'Pkts': self.packet_count, + 'Bytes': self.byte_count, + 'Duration': round(duration, 3) if duration else 0, + 'Avg Size': round(sum(self.packet_sizes) / len(self.packet_sizes), 1) if self.packet_sizes else 0, + } + + if self.inter_arrival_times: + avg_delta = sum(self.inter_arrival_times) / len(self.inter_arrival_times) + summary['Avg TimeΔ'] = round(avg_delta, 6) + + # Calculate standard deviation + if len(self.inter_arrival_times) > 1: + mean = avg_delta + variance = sum((x - mean) ** 2 for x in self.inter_arrival_times) / len(self.inter_arrival_times) + std_dev = variance ** 0.5 + summary['Time 1σ'] = round(std_dev, 6) + else: + summary['Time 1σ'] = 0 + else: + summary['Avg TimeΔ'] = 0 + summary['Time 1σ'] = 0 + + if duration > 0: + summary['Pkt/s'] = round(self.packet_count / duration, 1) + summary['B/s'] = round(self.byte_count / duration, 1) + else: + summary['Pkt/s'] = 0 + summary['B/s'] = 0 + + return summary + + +class OverviewStats(BaseStats): + """Overview statistics for general packet analysis.""" + pass + + +class PTPStats(BaseStats): + """PTP-specific statistics.""" + + def __init__(self): + super().__init__() + self.ptp_message_types = {} + + def _process_packet(self, packet: Any): + """Process PTP-specific packet information.""" + # Check if packet has PTP layer + if hasattr(packet, 'ptp'): + try: + msg_type = packet.ptp.v2_messagetype if hasattr(packet.ptp, 'v2_messagetype') else 'unknown' + self.ptp_message_types[msg_type] = self.ptp_message_types.get(msg_type, 0) + 1 + except: + pass + + def get_summary_dict(self) -> Dict[str, Any]: + summary = super().get_summary_dict() + # Add PTP-specific metrics + if self.ptp_message_types: + summary['PTP Types'] = str(self.ptp_message_types) + return summary + + +class MultiStats: + """Container for multiple stats instances.""" + + def __init__(self, stats_classes: Optional[List[Type[BaseStats]]] = None): + if stats_classes is None: + stats_classes = [OverviewStats] + self.stats_instances = [cls() for cls in stats_classes] + self.stats_classes = stats_classes + + def add(self, timestamp: float, size: int, packet: Any): + """Add packet to all stats instances.""" + for stats in self.stats_instances: + stats.add(timestamp, size, packet) + + def get_combined_summary(self) -> Dict[str, Any]: + """Combine summaries from all stats instances.""" + combined = {} + for i, stats in enumerate(self.stats_instances): + summary = stats.get_summary_dict() + class_name = self.stats_classes[i].__name__.replace('Stats', '') + + # Add prefix to avoid column name conflicts + for key, value in summary.items(): + if key in ['Pkts', 'Bytes', 'Duration']: # Common columns + if i == 0: # Only include once + combined[key] = value + else: + # Only add prefix if we have multiple stats classes + if len(self.stats_classes) > 1: + combined[f"{class_name}:{key}"] = value + else: + combined[key] = value + return combined + + +# Stats registry for easy lookup +STATS_TYPES = { + 'overview': OverviewStats, + 'ptp': PTPStats, +} \ No newline at end of file