87 lines
3.0 KiB
Python
87 lines
3.0 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
PTP Packet class for IEEE1588 PTP frame parsing
|
||
|
|
"""
|
||
|
|
|
||
|
|
import struct
|
||
|
|
from typing import Dict, Optional
|
||
|
|
|
||
|
|
try:
|
||
|
|
from scapy.layers.inet import IP, UDP
|
||
|
|
except ImportError:
|
||
|
|
print("Error: scapy library not found. Install with: pip install scapy")
|
||
|
|
exit(1)
|
||
|
|
|
||
|
|
|
||
|
|
class PTPPacket:
|
||
|
|
"""Represents an IEEE1588 PTP packet"""
|
||
|
|
|
||
|
|
def __init__(self, packet):
|
||
|
|
"""
|
||
|
|
Initialize PTP packet from raw scapy packet
|
||
|
|
|
||
|
|
Args:
|
||
|
|
packet: Raw scapy packet
|
||
|
|
"""
|
||
|
|
self.raw_packet = packet
|
||
|
|
|
||
|
|
# Extract basic packet info
|
||
|
|
self.timestamp = float(packet.time)
|
||
|
|
|
||
|
|
# Extract IP/UDP info if available
|
||
|
|
if packet.haslayer(IP) and packet.haslayer(UDP):
|
||
|
|
ip_layer = packet[IP]
|
||
|
|
udp_layer = packet[UDP]
|
||
|
|
|
||
|
|
self.src_ip = ip_layer.src
|
||
|
|
self.dst_ip = ip_layer.dst
|
||
|
|
self.src_port = udp_layer.sport
|
||
|
|
self.dst_port = udp_layer.dport
|
||
|
|
self.payload = bytes(udp_layer.payload)
|
||
|
|
else:
|
||
|
|
self.src_ip = ""
|
||
|
|
self.dst_ip = ""
|
||
|
|
self.src_port = 0
|
||
|
|
self.dst_port = 0
|
||
|
|
self.payload = bytes()
|
||
|
|
|
||
|
|
# Parse PTP header
|
||
|
|
self.ptp_header = self._parse_ptp_header()
|
||
|
|
|
||
|
|
def _parse_ptp_header(self) -> Optional[Dict]:
|
||
|
|
"""Parse PTP header from payload"""
|
||
|
|
if len(self.payload) < 34: # Minimum PTP header size
|
||
|
|
return None
|
||
|
|
|
||
|
|
try:
|
||
|
|
message_type = self.payload[0] & 0x0F
|
||
|
|
version = (self.payload[1] >> 4) & 0x0F
|
||
|
|
message_length = struct.unpack('>H', self.payload[2:4])[0]
|
||
|
|
domain_number = self.payload[4]
|
||
|
|
flags = struct.unpack('>H', self.payload[6:8])[0]
|
||
|
|
correction_field = struct.unpack('>Q', self.payload[8:16])[0]
|
||
|
|
source_port_id = self.payload[20:30].hex()
|
||
|
|
sequence_id = struct.unpack('>H', self.payload[30:32])[0]
|
||
|
|
control_field = self.payload[32]
|
||
|
|
log_message_interval = struct.unpack('b', self.payload[33:34])[0]
|
||
|
|
|
||
|
|
message_types = {
|
||
|
|
0x0: 'Sync', 0x1: 'Delay_Req', 0x2: 'Pdelay_Req', 0x3: 'Pdelay_Resp',
|
||
|
|
0x8: 'Follow_Up', 0x9: 'Delay_Resp', 0xA: 'Pdelay_Resp_Follow_Up',
|
||
|
|
0xB: 'Announce', 0xC: 'Signaling', 0xD: 'Management'
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
'message_type': message_types.get(message_type, f'Unknown({message_type})'),
|
||
|
|
'version': version,
|
||
|
|
'message_length': message_length,
|
||
|
|
'domain_number': domain_number,
|
||
|
|
'flags': f'0x{flags:04X}',
|
||
|
|
'correction_field': correction_field,
|
||
|
|
'source_port_id': source_port_id,
|
||
|
|
'sequence_id': sequence_id,
|
||
|
|
'control_field': control_field,
|
||
|
|
'log_message_interval': log_message_interval
|
||
|
|
}
|
||
|
|
except (struct.error, IndexError):
|
||
|
|
return None
|