multiscan_pcap_player.py
Go to the documentation of this file.
1 """
2  multiScan test emulator to parse pcapng files recorded from multiScan and replay the UDP packages
3  to emulate a local multiScan lidar.
4 
5  The UDP Sender sends packets via UDP over localhost, Port:2115
6 
7  Usage:
8 
9  pip install scapy
10  pip install pypcapfile
11  pip install python-pcapng
12 
13 """
14 
15 import argparse
16 import json
17 import socket
18 import sys
19 import time
20 
21 from pcapng import FileScanner
22 from pcapng.blocks import EnhancedPacket #, InterfaceDescription, SectionHeader
23 
24 import scapy.all
25 import scapy.packet
26 from scapy.layers.l2 import Ether
27 
28 # Force a delay by active polling. Brute-force alternative if timing in time.sleep is not accurate enough
29 def forced_delay(seconds):
30  timestamp_end = time.perf_counter() + seconds
31  while time.perf_counter() < timestamp_end:
32  pass
33 
34 # Returns the payload starting from STX = '\x02\x02\x02\x02' (or unmodified payload if STX not found)
35 def extractMessageStart(payload):
36  stx_index = payload.find(b'\x02\x02\x02\x02')
37  if stx_index > 0:
38  payload = payload[stx_index:]
39  return payload
40 
41 # Filter pcap blocks by src ip, dst ip and port: send a pcap block if its src ip, dst ip and port is found in these lists (or all if this list empty), default: empty
42 class PcapFilter:
43  def __init__(self, src_ip = [], dst_ip = [], port = [], proto = []):
44  self.src_ip = src_ip # optional filter pcap blocks by src ip: send a pcap block if its src ip is found in this list (or all if this list empty), default: empty
45  self.dst_ip = dst_ip # optional filter pcap blocks by dst ip: send a pcap block if its dst ip is found in this list (or all if this list empty), default: empty
46  self.port = port # optional filter pcap blocks by ip port: send a pcap block if its ip port is found in this list (or all if this list empty), default: empty
47  self.proto = proto # optional filter pcap blocks by protocoll ("TCP", UDP. or "IP"): send a pcap block if its protocol name is found in this list (or all if this list empty), default: empty
48 
49 # Container for decoded pcap blocks, containing the payload, timestamp, ip port, src ip and dst ip
51  def __init__(self, rawblock = None, timestamp = 0.0):
52  self.timestamp = timestamp
53  self.payload = bytes()
54  self.dst_ip = ""
55  self.src_ip = ""
56  self.dst_port = 0
57  self.proto = ""
58  if rawblock is not None:
59  self.payload = bytes(rawblock.payload)
60  self.dst_ip = rawblock.underlayer.dst
61  self.src_ip = rawblock.underlayer.src
62  self.proto = rawblock.name
63  if "dport" in rawblock.underlayer.payload.fields:
64  self.dst_port = rawblock.underlayer.payload.fields["dport"]
65  if rawblock.name == "IP":
66  self.dst_ip = rawblock.dst
67  self.src_ip = rawblock.src
68  def print(self):
69  payload_start_hex_str = "".join("\\x{:02x}".format(payload_byte) for payload_byte in self.payload[:4])
70  return "{} byte payload {}..., timestamp={}, src_ip={}, dst_ip={}, port={}".format(len(self.payload), payload_start_hex_str, self.timestamp, self.src_ip, self.dst_ip, self.dst_port)
71 
72 def readPcapngFile(pcap_filename, pcap_filter, verbose):
73  pcap_decoded_blocks = []
74  payload_length_accumulated_since_stx = 0
75  with open(pcap_filename, 'rb') as pcap_file:
76  pcap_scanner = FileScanner(pcap_file)
77  for block_cnt, block in enumerate(pcap_scanner):
78  if isinstance(block, EnhancedPacket):
79  # Decode a single pcap block
80  if block.captured_len != block.packet_len:
81  print("## multiscan_pcap_player block {}: {} byte block truncated to {} bytes".format(block_cnt, block.packet_len, block.captured_len))
82  block_data = Ether(block.packet_data)
83  block_decoded = block_data
84  for n in range(0,10):
85  if isinstance(block_decoded.payload, scapy.packet.Raw):
86  break
87  elif isinstance(block_decoded.payload, scapy.packet.Packet):
88  block_decoded = block_decoded.payload
89  else:
90  break
91  # if len(block_decoded.payload) <= 0:
92  # print("## multiscan_pcap_player block {}: empty payload ignored in data {}".format(block_cnt, block_data))
93  # if not isinstance(block_decoded.payload, scapy.packet.Raw):
94  # print("## multiscan_pcap_player block {}: block_decoded.payload = {} is no instance of scapy.packet.Raw".format(block_cnt, block_decoded.payload))
95  # print("block {}: timestamp = {}".format(block_cnt, block.timestamp))
96  # print("block {}: packet_data = {}".format(block_cnt, block.packet_data))
97  # print("block {}: payload = {}".format(block_cnt, block_decoded.payload))
98 
99  # Decode payload
100  if isinstance(block_decoded.payload, scapy.packet.Raw) and len(block_decoded.payload) > 0:
101  pcap_decoded_block = PcapDecodedBlock(block_decoded, block.timestamp)
102  if len(pcap_filter.src_ip) > 0 and pcap_decoded_block.src_ip not in pcap_filter.src_ip:
103  continue # wrong source ip
104  if len(pcap_filter.dst_ip) > 0 and pcap_decoded_block.dst_ip not in pcap_filter.dst_ip:
105  continue # wrong destination ip
106  if pcap_decoded_block.dst_port > 0 and len(pcap_filter.port) > 0 and pcap_decoded_block.dst_port not in pcap_filter.port:
107  continue # wrong port
108  if len(pcap_filter.proto) > 0 and pcap_decoded_block.proto not in pcap_filter.proto:
109  continue; # wrong protocol
110  if pcap_decoded_block.payload.find(b'\x02\x02\x02\x02') >= 0:
111  payload_length_accumulated_since_stx = 0
112  payload_length_accumulated_since_stx = payload_length_accumulated_since_stx + len(pcap_decoded_block.payload)
113  if verbose > 0:
114  print("pcap block {}: {}, {} byte since stx".format(block_cnt, pcap_decoded_block.print(), payload_length_accumulated_since_stx))
115  if len(pcap_decoded_block.payload) < 64000:
116  pcap_decoded_block.payload = extractMessageStart(pcap_decoded_block.payload)
117  pcap_decoded_blocks.append(pcap_decoded_block)
118  return pcap_decoded_blocks
119 
120 def readJsonFile(json_filename, verbose):
121  pcap_blocks = []
122  with open(json_filename, "r") as file_stream:
123  json_blocks = json.load(file_stream)
124  for json_block in json_blocks:
125  if len(json_block) == 2:
126  pcap_block = PcapDecodedBlock()
127  pcap_block.timestamp = json_block[0]
128  pcap_block.payload = bytes.fromhex(json_block[1])
129  pcap_blocks.append(pcap_block)
130  return pcap_blocks
131 
132 if __name__ == "__main__":
133 
134  pcap_filename = "" # default: read upd packets rom pcapng-file
135  json_filename = "" # alternative: read upd packets from json-file
136  save_udp_jsonfile = "" # save upd packets to json-file
137  udp_port = -1 # 2115 # UDP port to send msgpack datagrams (-1 for udp port from pcapng file)
138  udp_send_rate = 0 # send rate in msgpacks per second, 240 for multiScan, or 0 to send corresponding to pcap-timestamps, or udp_send_rate > 1000 for max. rate
139  udp_prompt = 0 # prompt for key after sending each udp packet (debugging only)
140  udp_dst_ip = "<broadcast>"
141  num_repetitions = 1
142  verbose = 0
143  max_seconds = 3600.0
144 
145  arg_parser = argparse.ArgumentParser()
146  arg_parser.add_argument("--pcap_filename", help="read upd packets rom pcapng-file", default=pcap_filename, type=str)
147  arg_parser.add_argument("--json_filename", help="read upd packets from json-file", default=json_filename, type=str)
148  arg_parser.add_argument("--save_udp_jsonfile", help="save upd packets to json-file", default=save_udp_jsonfile, type=str)
149  arg_parser.add_argument("--udp_port", help="dst udp port, or -1 for udp port from pcapng file)", default=udp_port, type=int)
150  arg_parser.add_argument("--send_rate", help="udp send rate in msgpacks per second, 240 for multiScan, or 0 to send by pcap-timestamps, or > 10000 for max. rate", default=udp_send_rate, type=int)
151  arg_parser.add_argument("--prompt", help="prompt for key after sending each udp packet (debugging only)", default=udp_prompt, type=int)
152  arg_parser.add_argument("--dst_ip", help="udp destination ip, e.g. 127.0.0.1 or <broadcast>", default=udp_dst_ip, type=str)
153  arg_parser.add_argument("--repeat", help="number of repetitions", default=num_repetitions, type=int)
154  arg_parser.add_argument("--verbose", help="print verbose messages", default=verbose, type=int)
155  arg_parser.add_argument("--filter", help="enable pcap filter by name, e.g. pcap_filter_multiscan_hildesheim for src_ip=192.168.0.1, dst_ip=192.168.0.100", default="", type=str)
156  arg_parser.add_argument("--max_seconds", help="max seconds to play", default=max_seconds, type=float)
157 
158  cli_args = arg_parser.parse_args()
159  pcap_filename = cli_args.pcap_filename
160  json_filename = cli_args.json_filename
161  save_udp_jsonfile = cli_args.save_udp_jsonfile
162  udp_port = cli_args.udp_port
163  udp_send_rate = cli_args.send_rate
164  udp_prompt = cli_args.prompt
165  udp_dst_ip = cli_args.dst_ip
166  num_repetitions = cli_args.repeat
167  verbose = cli_args.verbose
168  max_seconds = cli_args.max_seconds
169 
170  # Optional filter pcap blocks by src ip, dst ip and port: send a pcap block if its src ip, dst ip and port is found in these lists (or all if this list empty), default: empty
171  pcap_filter = PcapFilter()
172  if cli_args.filter == "pcap_filter_multiscan_hildesheim": # pcapng filter multiscan Hildesheim: src_ip=192.168.0.1, dst_ip=192.168.0.100, ports 2115 (scandata) and 7503 (imu)
173  pcap_filter = PcapFilter([ "192.168.0.1" ], [ "192.168.0.100" ], [ 2115, 7503 ], [ "UDP", "IP" ] )
174 
175  # Read and parse pcap file, extract udp raw data
176  pcap_blocks = []
177  if pcap_filename != "":
178  print("multiscan_pcap_player: reading pcapfile \"{}\" ...".format(pcap_filename))
179  pcap_blocks = readPcapngFile(pcap_filename, pcap_filter, verbose)
180  elif json_filename != "":
181  pcap_blocks = readJsonFile(json_filename, verbose)
182  else:
183  print("## ERROR multiscan_pcap_player: neither pcapng of json file specified, use option --pcap_filename or --json_filename to configure inputfile with upd packets")
184  if len(pcap_blocks) == 0:
185  print("## ERROR multiscan_pcap_player: no udp packets found, aborting.")
186  sys.exit(-1)
187  print("multiscan_pcap_player: sending {} udp packets ...".format(len(pcap_blocks)))
188 
189  # Init upd sender
190  udp_sender_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # UDP socket
191  udp_sender_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # Enable broadcasting mode
192  print("multiscan_pcap_player: sending on udp port {}, send_rate={}".format(udp_port, udp_send_rate))
193 
194  # Send udp raw data
195  save_udp_json_blocks = []
196  timestamp_end = time.perf_counter() + max_seconds
197  udp_port_last = udp_port
198  for repeat_cnt in range(num_repetitions):
199  if time.perf_counter() >= timestamp_end:
200  break
201  send_timestamp = 0
202  for block_cnt, pcap_block in enumerate(pcap_blocks):
203  block_payload = pcap_block.payload
204  block_timestamp = pcap_block.timestamp
205  dst_udp_port = 0
206  if udp_port >= 0:
207  dst_udp_port = udp_port # send to configured udp port
208  elif pcap_block.dst_port > 0:
209  dst_udp_port = pcap_block.dst_port # send to udp port from pcapng file
210  elif udp_port_last > 0:
211  dst_udp_port = udp_port_last # udp port = 0: fragmented ip packet, send to udp port of previous udp packet
212  else:
213  continue # invalid udp port
214  udp_port_last = dst_udp_port
215  # Send block_payload
216  if verbose > 0 or udp_prompt > 0:
217  payload_hex_str = "".join("\\x{:02x}".format(payload_byte) for payload_byte in block_payload[:4])
218  print("pcap message {}: sending {} byte {}... (udp {}:{})".format(block_cnt, len(block_payload), payload_hex_str, udp_dst_ip, dst_udp_port))
219  if udp_prompt > 0:
220  payload_hex_str = "".join("{:02x}".format(payload_byte) for payload_byte in block_payload)
221  if len(payload_hex_str) > 32:
222  payload_hex_str = payload_hex_str[0:32] + "..."
223  if block_payload.find(b'\x02\x02\x02\x02') >= 0:
224  time.sleep(0.1)
225  input("pcap message {}: press ENTER to send {} byte {} >".format(block_cnt, len(block_payload), payload_hex_str))
226  else:
227  print("pcap message {}: sending {} byte {} ...".format(block_cnt, len(block_payload), payload_hex_str))
228  # udp_sender_socket.sendto(block_payload, ('<broadcast>', dst_udp_port))
229  # udp_sender_socket.sendto(block_payload, ('127.0.0.1', dst_udp_port))
230  udp_sender_socket.sendto(block_payload, (udp_dst_ip, dst_udp_port))
231  if save_udp_jsonfile:
232  payload_hex_str = "".join("{:02x}".format(payload_byte) for payload_byte in block_payload)
233  save_udp_json_blocks.append((block_timestamp, payload_hex_str))
234  if udp_prompt > 0:
235  print("pcap message {}: {} byte sent".format(block_cnt, len(block_payload), udp_dst_ip, dst_udp_port))
236  # payload_hex_str = "".join("\\x{:02x}".format(payload_byte) for payload_byte in block_payload)
237  # print("block_payload dump ({} byte): {}".format(len(block_payload), payload_hex_str))
238  # Send next message with delay from pcap timestamps or with configured rate
239  if time.perf_counter() >= timestamp_end:
240  break
241  if send_timestamp > 0 and block_timestamp > send_timestamp and udp_send_rate < 10000:
242  if udp_send_rate <= 0: # delay from pcap timestamps
243  delay = block_timestamp - send_timestamp
244  else: # delay from configured rate
245  delay = 1.0 / udp_send_rate
246  time.sleep(delay)
247  # else: # brute force delay, for performance tests on 2. PC only
248  # forced_delay(2.0e-4)
249  send_timestamp = block_timestamp
250  if save_udp_jsonfile:
251  with open(save_udp_jsonfile, "w") as file_stream:
252  json.dump(save_udp_json_blocks, file_stream, indent=2)
253  print(f"multiscan_pcap_player: udp packets saved to file {save_udp_jsonfile}")
254 
255  print("multiscan_pcap_player finished.")
bytes
uint8_t bytes[2]
Definition: msgpack11.cpp:76
multiscan_pcap_player.PcapDecodedBlock.dst_port
dst_port
Definition: multiscan_pcap_player.py:56
multiscan_pcap_player.PcapDecodedBlock.proto
proto
Definition: multiscan_pcap_player.py:57
roswrap::console::print
ROSCONSOLE_DECL void print(FilterBase *filter, void *logger, Level level, const char *file, int line, const char *function, const char *fmt,...) ROSCONSOLE_PRINTF_ATTRIBUTE(7
Don't call this directly. Use the ROS_LOG() macro instead.
multiscan_pcap_player.PcapFilter.proto
proto
Definition: multiscan_pcap_player.py:47
multiscan_pcap_player.forced_delay
def forced_delay(seconds)
Definition: multiscan_pcap_player.py:29
multiscan_pcap_player.PcapDecodedBlock.print
def print(self)
Definition: multiscan_pcap_player.py:68
multiscan_pcap_player.readPcapngFile
def readPcapngFile(pcap_filename, pcap_filter, verbose)
Definition: multiscan_pcap_player.py:72
multiscan_pcap_player.extractMessageStart
def extractMessageStart(payload)
Definition: multiscan_pcap_player.py:35
multiscan_pcap_player.PcapFilter.__init__
def __init__(self, src_ip=[], dst_ip=[], port=[], proto=[])
Definition: multiscan_pcap_player.py:43
multiscan_pcap_player.PcapFilter.src_ip
src_ip
Definition: multiscan_pcap_player.py:44
multiscan_pcap_player.readJsonFile
def readJsonFile(json_filename, verbose)
Definition: multiscan_pcap_player.py:120
multiscan_pcap_player.PcapDecodedBlock.src_ip
src_ip
Definition: multiscan_pcap_player.py:55
multiscan_pcap_player.PcapDecodedBlock.dst_ip
dst_ip
Definition: multiscan_pcap_player.py:54
multiscan_pcap_player.PcapDecodedBlock.timestamp
timestamp
Definition: multiscan_pcap_player.py:52
multiscan_pcap_player.PcapFilter.dst_ip
dst_ip
Definition: multiscan_pcap_player.py:45
multiscan_pcap_player.PcapDecodedBlock
Definition: multiscan_pcap_player.py:50
multiscan_pcap_player.PcapDecodedBlock.__init__
def __init__(self, rawblock=None, timestamp=0.0)
Definition: multiscan_pcap_player.py:51
multiscan_pcap_player.PcapFilter
Definition: multiscan_pcap_player.py:42
multiscan_pcap_player.PcapFilter.port
port
Definition: multiscan_pcap_player.py:46
multiscan_pcap_player.PcapDecodedBlock.payload
payload
Definition: multiscan_pcap_player.py:53


sick_scan_xd
Author(s): Michael Lehning , Jochen Sprickerhof , Martin Günther
autogenerated on Fri Oct 25 2024 02:47:09