main_monitor_simulator.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. import os
  2. import logging
  3. import argparse
  4. import time
  5. import socket
  6. import random
  7. import requests
  8. import json
  9. import struct
  10. import multiprocessing
  11. from multiprocessing import Process, Queue
  12. import re
  13. from ipv4 import IPv4Address, IPv4Network
  14. import numpy as np
  15. from pypacker import psocket
  16. from pypacker import ppcap
  17. from pypacker.layer12.ethernet import Ethernet
  18. from pypacker.layer3 import ip
  19. from pypacker.layer4 import tcp
  20. logging.basicConfig(format="%(levelname)s (%(funcName)s): %(message)s")
  21. logger = logging.getLogger("pra_framework")
  22. logger.setLevel(logging.DEBUG)
  23. CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
  24. FILE_NODES = CURRENT_DIR + "/simulated_nodes.txt"
  25. RESPONSETYPE_NONE = 0
  26. RESPONSETYPE_TCP_RST = 1
  27. RESPONSETYPE_TCP_SYNACK = 2
  28. RESPONSETYPE_ICMP = 4
  29. RESPONSETYPES_TCP = {RESPONSETYPE_TCP_RST, RESPONSETYPE_TCP_SYNACK}
  30. RESPONSE_TYPES_DESCR = {RESPONSETYPE_NONE: "NONE",
  31. RESPONSETYPE_TCP_RST: "RST",
  32. RESPONSETYPE_TCP_SYNACK: "SYNACK",
  33. RESPONSETYPE_ICMP: "ICMP"}
  34. packipv4 = struct.Struct(">I").pack
  35. split_tab = re.compile("\t").split
  36. #cert = ("ssl/simulator/simulator_cert.pem", "ssl/simulator/simulator_key.pem")
  37. requests.packages.urllib3.disable_warnings()
  38. def store_packets(packets, filename="exception_packets.pcap"):
  39. writer = ppcap.Writer(filename=filename)
  40. for bts in packets:
  41. writer.write(bts)
  42. writer.close()
  43. class MonitorSimulator(object):
  44. """
  45. Simulates monitor nodes on a host system which in turn send attack information to a central CIDS.
  46. The simulation can be done using a virtual interface:
  47. modprobe dummy (optional if already loaded)
  48. ip link set name eth10 dev dummy0
  49. ip link show eth10
  50. ifconfig eth10 txqueuelen 10000
  51. Optimazation of /etc/sysctl.conf should be done.
  52. """
  53. def __init__(self,
  54. # total amount of monitors (monitor_ips + random generated)
  55. amount_monitors=10000,
  56. cidr_bits=0,
  57. # total amount of addresses which give scanner feedback
  58. amount_non_monitors=10000,
  59. # monitor IP addresses as ["1.2.3.4", ...]
  60. monitor_ips=[],
  61. seed=123456789,
  62. url_tracing="https://localhost:443",
  63. interface_name="lo",
  64. buffer_size=500000,
  65. cluster_monitors=False,
  66. # probability that a monitor returns a relevant scanner feedback (RST, SYN/ACK)
  67. prop_mon_scannerfeedback=0.8,
  68. prop_nwdrop=0.0,
  69. file_blacklist=None):
  70. logger.debug("monitors=%d, non monitors=%d, scanner feedback=%f, nw drop=%f, cluster=%d" % (amount_monitors,
  71. amount_non_monitors,
  72. prop_mon_scannerfeedback,
  73. prop_nwdrop,
  74. cluster_monitors))
  75. self._amount_monitors = amount_monitors
  76. self._amount_non_monitors = amount_non_monitors
  77. self._prop_mon_scannerfeedback = prop_mon_scannerfeedback
  78. self._cluster_monitors = cluster_monitors
  79. self._prop_nwdrop = prop_nwdrop
  80. self._blacklist_nw_objs = []
  81. self._blacklist_int_sub8 = set()
  82. if file_blacklist is not None:
  83. self._blacklist_nw_objs, self._blacklist_int_sub8 = self.read_blacklist(file_blacklist)
  84. self._rand = random.Random()
  85. self._rand.seed(a=seed, version=1)
  86. self._rand_nwloss = random.Random()
  87. self._rand_nwloss.seed(a=seed, version=1)
  88. logger.debug("TraCINg url=%s, interface=%s" % (url_tracing, interface_name))
  89. logger.debug("Initiating Queue, size: %d" % buffer_size)
  90. self._queue = Queue(maxsize=buffer_size)
  91. # Group response probabilities
  92. self._responsetypes = [
  93. RESPONSETYPE_NONE,
  94. RESPONSETYPE_TCP_RST,
  95. RESPONSETYPE_TCP_SYNACK,
  96. RESPONSETYPE_ICMP]
  97. self._file_identified_monitors = "./output_dir_testing/identified_monitors.csv"
  98. self._listening_ips = {}
  99. # TODO: adjust this for different subnets
  100. # this has to match _cidr_bits_stage1 in main_attack.py
  101. # /0
  102. self._host_addresses_mask = 0xFFFFFFFF
  103. self._host_addresses_mask >>= cidr_bits
  104. logger.debug("Max host IP addresses (host mask): %X" % self._host_addresses_mask)
  105. self._initiate_listening_ips(monitor_ips)
  106. logger.debug("total amount of listening addresses=%d (w/ + w/o feedback)" % len(self._listening_ips))
  107. self._sockets_read = []
  108. self._sockets_write = []
  109. self._is_running = False
  110. # TODO: adjust this on other platforms
  111. self._read_processes_amount = 2
  112. self._read_processes = []
  113. socket_read = psocket.SocketHndl(iface_name=interface_name,
  114. timeout=10,
  115. buffersize_recv=2 ** 29)
  116. self._sockets_read.append(socket_read)
  117. logger.debug("creating %d processes for reading packets" % self._read_processes_amount)
  118. for cnt in range(self._read_processes_amount):
  119. proc = Process(target=self._packet_collect_cycler, args=(cnt + 1,
  120. socket_read,
  121. self._queue,
  122. self._listening_ips))
  123. self._read_processes.append(proc)
  124. requests_session = requests.Session()
  125. #adapter = requests.adapters.HTTPAdapter(pool_connections=10000, pool_maxsize=10000)
  126. #requests_session.mount('https://', adapter)
  127. self._attack_reaction_processes = []
  128. # TODO: adjust this on other platforms
  129. self._attack_reaction_processes_amount = 5
  130. logger.debug("creating %d processes for attack reaction" % self._attack_reaction_processes_amount)
  131. for cnt in range(self._attack_reaction_processes_amount):
  132. socket_write = psocket.SocketHndl(iface_name=interface_name,
  133. timeout=60 * 60 * 24 * 7,
  134. #buffersize_send=2 ** 23)
  135. buffersize_send=2 ** 29)
  136. self._sockets_write.append(socket_write)
  137. attack_reaction_process = Process(
  138. target=self._attack_reaction_cycler,
  139. args=(cnt + 1,
  140. socket_write,
  141. self._queue,
  142. requests_session,
  143. url_tracing,
  144. self._listening_ips))
  145. self._attack_reaction_processes.append(attack_reaction_process)
  146. def start(self):
  147. if self._is_running:
  148. return
  149. logger.debug("starting collection and reaction logic")
  150. self._is_running = True
  151. for process in self._attack_reaction_processes:
  152. process.start()
  153. time.sleep(2)
  154. for proc in self._read_processes:
  155. proc.start()
  156. def stop(self):
  157. if not self._is_running:
  158. return
  159. self._is_running = False
  160. for sock in self._sockets_read + self._sockets_write:
  161. sock.close()
  162. self._queue.close()
  163. for proc in self._read_processes:
  164. # don't wait for process to finish, just terminate
  165. proc.terminate()
  166. for proc in self._attack_reaction_processes:
  167. # don't wait for process to finish, just terminate
  168. proc.terminate()
  169. def _initiate_listening_ips(self, monitor_ips):
  170. """
  171. Initiate IP addresses which trigger alerts and/or give scanner feedback.
  172. monitor_ips -- monitor IPs to be added as "a.b.c.d"
  173. """
  174. # apply reaction on custom defined monitors
  175. if len(monitor_ips) > 0:
  176. logger.debug("listening IP addresses were given explicitly, adding %d" % len(monitor_ips))
  177. for ip in monitor_ips:
  178. self._listening_ips[IPv4Address(ip_str=ip).packed] = [RESPONSETYPE_TCP_RST, True]
  179. if not self._cluster_monitors:
  180. #logger.info("creating %d random IPs" % self._amount_monitors)
  181. self._create_random_ips(self._amount_monitors, is_monitor_ip=True)
  182. else:
  183. #logger.info("creating %d random clustered IPs" % self._amount_monitors)
  184. #self._create_clustered_monitors(self._amount_monitors)
  185. self._create_clustered_monitors_distr(self._amount_monitors)
  186. logger.debug("total amount of monitors=%d" % len(self._listening_ips))
  187. if self._amount_non_monitors > 0:
  188. # target = current amount + non_monitor_ips * probability_nonmonitor
  189. target_amount = len(self._listening_ips) + self._amount_non_monitors
  190. logger.info("creating %d non monitor feedback IPs" % self._amount_non_monitors)
  191. if target_amount > self._host_addresses_mask:
  192. raise Exception("!!!! too many addresses to create! %d >= %d" % (target_amount, self._host_addresses_mask))
  193. self._create_random_ips(target_amount, is_monitor_ip=False)
  194. amount_mon = sum([1 for _, resp_mon in self._listening_ips.items() if resp_mon[1]])
  195. amount_nonmon = sum([1 for _, resp_mon in self._listening_ips.items() if not resp_mon[1]])
  196. logger.info("sanity check: monitors=%d, non monitors=%d" % (amount_mon, amount_nonmon))
  197. self._set_feedback_types()
  198. def check_matches(self):
  199. """
  200. Check if every match in ./output_dir_testing/identified_monitors.csv is
  201. really a monitor.
  202. """
  203. fd = open(self._file_identified_monitors, "r")
  204. monitor_found = 0
  205. monitor_false_positive_no_monitor = 0
  206. monitor_false_positive_unknown = 0
  207. # skip header
  208. fd.readline()
  209. all_ips = set()
  210. for line in fd:
  211. ip_str = split_tab(line)[0]
  212. try:
  213. ips = IPv4Network(nw_ip_str_prefix=ip_str).hosts if "/" in line else [IPv4Address(ip_str=ip_str).packed]
  214. except TypeError:
  215. continue
  216. except Exception as ex:
  217. logger.warning("something went wrong while checking IP address=%r" % ip_str)
  218. print(ex)
  219. break
  220. for ip_bytes in ips:
  221. if ip_bytes in all_ips:
  222. logger.warning("allready counted, duplicate? from file=%s, converted=%r" %
  223. (ip_str, IPv4Address(ip_bytes=ip_bytes)))
  224. all_ips.add(ip_bytes)
  225. try:
  226. assert self._listening_ips[ip_bytes][1] is True
  227. monitor_found += 1
  228. except KeyError:
  229. monitor_false_positive_unknown += 1
  230. if monitor_false_positive_unknown % 100 == 0:
  231. logger.debug("%d: unknown to simulator: %r" % (monitor_false_positive_unknown,
  232. IPv4Address(ip_bytes=ip_bytes)))
  233. except AssertionError:
  234. monitor_false_positive_no_monitor += 1
  235. logger.debug("known to simulator but not a monitor: %r" % IPv4Address(ip_bytes=ip_bytes))
  236. logger.info("correctly identified monitors: %d" % monitor_found)
  237. logger.info("unknown to simulator (false positive): %d" % monitor_false_positive_unknown)
  238. logger.info("found but not monitor (false positive): %d" % monitor_false_positive_no_monitor)
  239. fd.close()
  240. def _set_feedback_types(self):
  241. """
  242. Set the type of feedback given on scanner level.
  243. """
  244. logger.debug("setting feedback types")
  245. randrange = self._rand.randrange
  246. rand_0_1 = self._rand.random
  247. for key, _ in self._listening_ips.items():
  248. #logger.debug(ip_bytes)
  249. # non monitor = give feedback in any case, monitor = feedback based on probability
  250. feedback_prop = self._prop_mon_scannerfeedback if self._listening_ips[key][1] else 1.0
  251. is_feedback = rand_0_1() <= feedback_prop
  252. # feedback: RST or SYN/ACK
  253. self._listening_ips[key][0] = self._responsetypes[randrange(1, 3)] if is_feedback else RESPONSETYPE_NONE
  254. @staticmethod
  255. def read_blacklist(filename):
  256. """
  257. return -- list of blacklisted IP network objects
  258. """
  259. logger.debug("Reading blacklist from: %s" % filename)
  260. addresses_nw_obj = []
  261. addresses_int_sub8 = set()
  262. total_addresses = 0
  263. fd = open(filename, "r")
  264. for line in fd:
  265. try:
  266. if line[0] == "#":
  267. continue
  268. nw = IPv4Network(nw_ip_str_prefix=line)
  269. total_addresses += nw.num_addresses
  270. addresses_nw_obj.append(nw)
  271. addresses_int_sub8.add(nw.ip_int >> 24)
  272. except:
  273. pass
  274. fd.close()
  275. logger.debug("Blacklist address groups=%d, addresses=%d, sub8=%d" %
  276. (len(addresses_nw_obj), total_addresses, len(addresses_int_sub8)))
  277. return addresses_nw_obj, addresses_int_sub8
  278. def _create_random_ips(self, target_amount, is_monitor_ip=True):
  279. """
  280. Create random even distributed IP addresses.
  281. target_amount -- Add addresses until this size is reached
  282. is_monitor_ip -- Add addresses of type monitor if True, add non monitor otherwise
  283. """
  284. logger.debug("creating %d random IP addresses, monitor=%r" % (target_amount, is_monitor_ip))
  285. randrange = self._rand.randrange
  286. rand_0_1 = self._rand.random
  287. #feedback_prop = self._prop_mon_scannerfeedback if is_monitor_ip else 1.0
  288. cnt = 0
  289. while len(self._listening_ips) < target_amount:
  290. cnt += 1
  291. ip_num = randrange(0, self._host_addresses_mask)
  292. ip_bytes = packipv4(ip_num)
  293. # don't overwrite old values
  294. #if ip_bytes in self._listening_ips:
  295. # continue
  296. if (ip_num >> 24) in self._blacklist_int_sub8:
  297. #logger.debug("checking: %r" % ip_bytes)
  298. for ip_nw in self._blacklist_nw_objs:
  299. if ip_num & ip_nw.ip_int == ip_nw.ip_int:
  300. #logger.debug("skipping: %r" % ip_nw)
  301. continue
  302. #logger.debug(ip_bytes)
  303. # feedback: RST or SYN/ACK
  304. #print(ip_bytes)
  305. self._listening_ips[ip_bytes] = [None, is_monitor_ip]
  306. def _create_clustered_monitors(self, target_amount):
  307. """
  308. Create clustered IP addresses by generating sequential addresses using
  309. pareto and exponential distributions.
  310. target_amount -- target amount of IP addresses for _listening_ips
  311. """
  312. logger.info("creating %d random clustered IPs" % self._amount_monitors)
  313. # classical pareto by "scale=m=1"
  314. #s = np.random.pareto(a, 10000) + m
  315. # shape = a = 1.x, scale = 1 (not adjusted), location = 1 (min value 1)
  316. # mean = sum/length = shape * scale / (shape - 1)
  317. # -> a=2.1: mean=~1.9
  318. # -> a=1.1: mean=~10
  319. # -> a=1.01: mean=~100
  320. pareto = np.random.pareto
  321. pareto_shape = 1.01
  322. exponential = np.random.exponential
  323. # mean=scale
  324. exponential_scale = 200
  325. current_ip = 0
  326. ip_max = self._host_addresses_mask - 10000
  327. if ip_max < 0:
  328. logger.warning("address range too small: %d<%d, will not create any monitors" %
  329. (self._host_addresses_mask, 10000))
  330. return
  331. while len(self._listening_ips) < target_amount and current_ip < ip_max:
  332. sequential_add = int(pareto(pareto_shape) + 1)
  333. logger.debug(sequential_add)
  334. for ip in range(current_ip, current_ip + sequential_add):
  335. ip_bytes = packipv4(ip)
  336. self._listening_ips[ip_bytes] = [None, True]
  337. current_ip += sequential_add + int(exponential(scale=exponential_scale))
  338. if len(self._listening_ips) < target_amount:
  339. logger.warning("could not create enough monitors (to small range?) %d < %d" %
  340. (len(self._listening_ips), target_amount))
  341. def _packet_collect_cycler(self, procnum, sockethndl, queue, ip_whitelist):
  342. """
  343. Collects packets and puts them into the Queue if the destination IP address
  344. matches an address in ip_whitelist.
  345. sockethndl -- A SocketHandler to read bytes from
  346. queue -- A queue to write bytes to if an destination IP addres matches
  347. ip_whitelist -- A dictionary posing a whitelist
  348. """
  349. logger.debug("starting listening process Nr. %d" % procnum)
  350. psock_recv = sockethndl._socket_recv.recv
  351. queue_put = queue.put
  352. cnt = 0
  353. last_cnt = 0
  354. while True:
  355. try:
  356. bts = psock_recv(64)
  357. cnt += 1
  358. # logger.debug("...")
  359. # time.sleep(1)
  360. if not bts[14 + 16: 14 + 16 + 4] in ip_whitelist:
  361. continue
  362. #logger.debug("!!!!!!! got a packet")
  363. queue_put(bts)
  364. except socket.timeout:
  365. # logger.debug("read timeout..")
  366. if last_cnt != cnt:
  367. logger.debug("collector %d: amount of probes to NW=%d" % (procnum, cnt))
  368. last_cnt = cnt
  369. #continue
  370. def _attack_reaction_cycler(self, procnum, socket_write, queue, requests_session, url_tracing, listening_ips):
  371. """
  372. Examines packets in the buffer and reacts on SYN-pings for
  373. specific target IPs. Possible reactions are: sending attack
  374. events to TraCINg and/or feedback on scanner level
  375. """
  376. logger.debug("starting reaction process No. %d" % procnum)
  377. cnt = 0
  378. while True:
  379. bts = queue.get()
  380. try:
  381. packet = Ethernet(bts)
  382. except:
  383. logger.warning("could not parse received packet:\n%r" % bts)
  384. store_packets(bts)
  385. continue
  386. ip_bytes = packet[ip.IP].dst
  387. # this is a monitor, send event to TraCINg
  388. if listening_ips[ip_bytes][1]:
  389. cnt += 1
  390. try:
  391. #if packet.body_handler.dst not in listening_ips:
  392. # continue
  393. p_ip = packet.ip
  394. p_tcp = p_ip.tcp
  395. if cnt % 500 == 0:
  396. logger.debug("%d> %d: monitor was scanned: %s:%d -> %s:%d" %
  397. (procnum, cnt, p_ip.src_s, p_tcp.sport, p_ip.dst_s, p_tcp.dport))
  398. """
  399. Inform TraCINg about an attack.
  400. """
  401. post_data_dict = {
  402. "sensor": {
  403. "name": "monitorsimulation_" + packet[ip.IP].dst_s,
  404. "type": "Honeypot"},
  405. "src": {
  406. "ip": packet[ip.IP].src_s,
  407. "port": "%d" % packet[tcp.TCP].sport},
  408. "dst": {
  409. "ip": packet[ip.IP].dst_s,
  410. "port": "%d" % packet[tcp.TCP].dport},
  411. "type": 11,
  412. "log": "Predefined Log",
  413. "md5sum": "7867de13bf22a7f3e3559044053e33e7",
  414. "date": ("%d" % time.time())
  415. }
  416. #headers = {"Content-type": "application/x-www-form-urlencoded",
  417. # "Accept": "text/plain"}
  418. post_data = json.dumps(post_data_dict)
  419. # logger.debug("POST data:")
  420. # logger.debug(post_data)
  421. requests_session.post(url=url_tracing, data=post_data, verify=False, stream=False)
  422. # response = conn.getresponse()
  423. #logger.debug(response)
  424. #conn = http.client.HTTPSConnection(self._tracing_host, self._tracing_port)
  425. #conn.request("POST", self._tracing_path, body=post_data)
  426. #logger.warning("attack event sent to TraCINg, monitor: %s" % packet[ip.IP].dst_s)
  427. except Exception as ex:
  428. logger.warning("could not inform TraCINg: %r" % ex)
  429. try:
  430. responsetype = listening_ips[ip_bytes][0]
  431. if responsetype == RESPONSETYPE_NONE:
  432. continue
  433. elif responsetype in RESPONSETYPES_TCP:
  434. tcp_packet = packet.body_handler.body_handler
  435. tcp_packet.ack = tcp_packet.seq + 1
  436. tcp_packet.seq = 12345
  437. tcp_packet.flags = tcp.TH_SYN | tcp.TH_ACK if responsetype == RESPONSETYPE_TCP_SYNACK else tcp.TH_RST
  438. packet.reverse_all_address()
  439. #socket_write.send(packet.bin())
  440. socket_write.send(packet.bin(update_auto_fields=False))
  441. elif responsetype == RESPONSETYPE_ICMP:
  442. # TODO: ICMP indicates unreachable hosts, ignore
  443. pass
  444. else:
  445. logger.warning("unknown response type for %s: %r" % (packet.ip.dst_s, responsetype))
  446. except Exception as ex:
  447. logger.warning("could not send scanner feedback: %r" % ex)
  448. logger.debug("reaction cycler is terminating")
  449. if __name__ == "__main__":
  450. parser = argparse.ArgumentParser()
  451. parser.add_argument("-i", "--interface", help="Interface to listen on", default="eth10")
  452. parser.add_argument("-m", "--monitors", help="Amount of monitors to be simulated", type=int, default=1000)
  453. parser.add_argument("-r", "--cidrbits", help="CIDR bits of simulated network", type=int, default=0)
  454. parser.add_argument("-c", "--cluster", help="Create clustered monitors", type=bool, default=False)
  455. parser.add_argument("-f", "--monitorfeedback", help="Probability for monitor feedback", type=float, default=0.9)
  456. parser.add_argument("-n", "--nonmonitors", help="Amount of non-monitors to be simulated", type=int, default=0)
  457. parser.add_argument("-d", "--nwdrop", help="Probability for network drops", type=float, default=0)
  458. parser.add_argument("-b", "--buffersize", help="Buffer to be used for storing received packets", type=int, default=1000000)
  459. parser.add_argument("-s", "--seed", help="Seed to be used to distribute nodes", type=int, default=123456789)
  460. parser.add_argument("-u", "--url", help="HTTPS URL of TraCINg to send events to", default="https://localhost:443")
  461. args = parser.parse_args()
  462. monitor_ips_init = []
  463. logger.info("amount of CPUs: %d" % multiprocessing.cpu_count())
  464. monitorsimulator = MonitorSimulator(
  465. amount_monitors=args.monitors,
  466. cidr_bits=args.cidrbits,
  467. amount_non_monitors=args.nonmonitors,
  468. monitor_ips=monitor_ips_init,
  469. interface_name=args.interface,
  470. buffer_size=args.buffersize,
  471. url_tracing=args.url,
  472. cluster_monitors=args.cluster,
  473. prop_mon_scannerfeedback=args.monitorfeedback,
  474. prop_nwdrop=args.nwdrop)
  475. input("press enter to continue")
  476. monitorsimulator.start()
  477. print("")
  478. user_input = None
  479. while user_input != "quit":
  480. user_input = input("enter 'quit' to quit, 'check' to compare monitors to those in %s\n" % monitorsimulator._file_identified_monitors)
  481. if user_input == "check":
  482. monitorsimulator.check_matches()
  483. else:
  484. logger.debug("%d" % monitorsimulator._queue.qsize())
  485. print("")
  486. logger.debug("stopping simulation")
  487. monitorsimulator.stop()