from whad.common.monitors import WhadMonitor
from scapy.all import conf
from scapy.layers.bluetooth4LE import *
from scapy.utils import PcapWriter,PcapReader
from scapy.error import Scapy_Exception
from os.path import exists
from whad.common.pcap import patch_pcap_metadata, extract_pcap_metadata
from time import time
from os import stat, remove
from stat import S_ISFIFO
from threading import Lock
import logging
logger = logging.getLogger(__name__)
[docs]
class PcapWriterMonitor(WhadMonitor):
"""
PcapWriterMonitor.
Monitor allowing to export the traffic transmitted and received by the
targeted connector to a PCAP file with appropriate header.
:Usage:
>>> monitor = PcapWriterMonitor("mypcapfile.pcap")
>>> monitor.attach(connector)
>>> monitor.start()
"""
def __init__(self, pcap_file, monitor_reception=True, monitor_transmission=True, append=True):
super().__init__(monitor_reception, monitor_transmission)
self._pcap_file = pcap_file
self._writer = None
self._formatter = None
self._reference_time = None
self._start_time = None
self._nb_pkts_written = 0
self._writer_lock = Lock()
self._append = append
@property
def packets_written(self):
'''Return the number of packets already written
'''
return self._nb_pkts_written
[docs]
def setup(self):
# First, we check if the pcap already exists
existing_pcap_file = exists(self._pcap_file)
sync = False
# If it exists, we have two cases:
# - it is a named pipe and we have to provide sync=True to PcapWriter
# - it is an already existing pcap file and we have to provide append=True to PcapWriter
if existing_pcap_file:
# Checks if it is a named FIFO
if S_ISFIFO(stat(self._pcap_file).st_mode):
logger.info("[i] Named pipe %s detected, syncing.", self._pcap_file)
sync = True
elif not self._append:
logger.info("[i] PCAP file %s exists, overwriting.", self._pcap_file)
remove(self._pcap_file)
existing_pcap_file = False
else:
# Checks if it is an already existing pcap file.
logger.info("[i] PCAP file %s exists, appending new packets.", self._pcap_file)
try:
# We collect the first packet timestamp to use it as reference time
self._start_time = PcapReader(self._pcap_file).read_packet().time * 1000000
except (EOFError, Scapy_Exception):
# Pcap is empty, remove it and open a new one
remove(self._pcap_file)
existing_pcap_file = False
# Instantiate the PCAP Writer with the appropriate parameters
self._writer_lock.acquire()
self._writer = PcapWriter(
self._pcap_file,
append=existing_pcap_file and not sync,
sync=sync
)
self._writer_lock.release()
# Checks if there is a scapy packet formatter associated with the connector.
# A formatter allows to describe manually how to build the packet, it is mainly
# useful to populate a relevant header for PCAP export.
self._formatter = self.default_formatter
if (
hasattr(self._connector, "format") and
callable(getattr(self._connector, "format"))
):
self._formatter = getattr(self._connector, "format")
[docs]
def close(self):
# Acquire lock on writer
self._writer_lock.acquire()
if hasattr(self, "_writer") and self._writer is not None:
# Close writer
try:
self._writer.close()
patch_pcap_metadata(self._pcap_file, self._connector.domain)
except BrokenPipeError:
pass
# Mark writer as not available anymore
self._writer = None
# Release lock on writer
self._writer_lock.release()
[docs]
def process_packet(self, packet):
# Acquire lock on writer lock acquire multithread error
self._writer_lock.acquire()
if self._processing:
# Note the current local clock timestamp in us
now = time() * 1000000
packet, timestamp = self._formatter(packet)
# Relative time synchronization
if timestamp is None:
timestamp = now
# Process accurate timestamp if available, else use local clock
if self._reference_time is None:
if self._start_time is None:
self._reference_time = (now, timestamp)
else:
self._reference_time = (self._start_time, timestamp - (now - self._start_time))
timestamp = now
else:
timestamp = self._reference_time[0] + (timestamp - self._reference_time[1])
# Convert timestamp to second (float)
packet.time = timestamp / 1000000
try:
if self._writer is not None:
# Write packet
self._writer.write(packet)
self._nb_pkts_written += 1
else:
# We are trying to write to a closed PCAP monitor,
# issue a warning message
logger.warning('cannot write to PCAP: file has already been closed')
except BrokenPipeError:
pass
# Release lock
self._writer_lock.release()