MembersMgmtCommAttack.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. from enum import Enum
  2. from random import randint, randrange, choice, uniform
  3. from collections import deque
  4. from scipy.stats import gamma
  5. from lea import Lea
  6. from datetime import datetime
  7. import os
  8. from Attack import BaseAttack
  9. from Attack.AttackParameters import Parameter as Param
  10. from Attack.AttackParameters import ParameterTypes
  11. # from ID2TLib import PcapFile
  12. # from ID2TLib.PcapFile import PcapFile
  13. from ID2TLib.Ports import PortSelectors
  14. class MessageType(Enum):
  15. """
  16. Defines possible botnet message types
  17. """
  18. TIMEOUT = 3
  19. SALITY_NL_REQUEST = 101
  20. SALITY_NL_REPLY = 102
  21. SALITY_HELLO = 103
  22. SALITY_HELLO_REPLY = 104
  23. def is_request(mtype):
  24. return mtype in {MessageType.SALITY_HELLO, MessageType.SALITY_NL_REQUEST}
  25. def is_response(mtype):
  26. return mtype in {MessageType.SALITY_HELLO_REPLY, MessageType.SALITY_NL_REPLY}
  27. class Message():
  28. INVALID_LINENO = -1
  29. """
  30. Defines a compact message type that contains all necessary information.
  31. """
  32. def __init__(self, msg_id: int, src, dst, type_: MessageType, time: float, refer_msg_id: int=-1, line_no = -1):
  33. """
  34. Constructs a message with the given parameters.
  35. :param msg_id: the ID of the message
  36. :param src: something identifiying the source, e.g. ID or configuration
  37. :param dst: something identifiying the destination, e.g. ID or configuration
  38. :param type_: the type of the message
  39. :param time: the timestamp of the message
  40. :param refer_msg_id: the ID this message is a request for or reply to. -1 if there is no related message.
  41. :param line_no: The line number this message appeared in the original file
  42. """
  43. self.msg_id = msg_id
  44. self.src = src
  45. self.dst = dst
  46. self.type = type_
  47. self.time = time
  48. self.refer_msg_id = refer_msg_id
  49. # if similar fields to line_no should be added consider a separate class
  50. self.line_no = line_no
  51. def __str__(self):
  52. str_ = "{0}. at {1}: {2}-->{3}, {4}, refer:{5}".format(self.msg_id, self.time, self.src, self.dst, self.type, self.refer_msg_id)
  53. return str_
  54. from ID2TLib import FileUtils, Generator
  55. from ID2TLib.IPv4 import IPAddress
  56. from ID2TLib.PcapAddressOperations import PcapAddressOperations
  57. from ID2TLib.CommunicationProcessor import CommunicationProcessor
  58. from ID2TLib.Botnet.MessageMapping import MessageMapping
  59. from ID2TLib.PcapFile import PcapFile
  60. from ID2TLib.Statistics import Statistics
  61. class MembersMgmtCommAttack(BaseAttack.BaseAttack):
  62. def __init__(self):
  63. """
  64. Creates a new instance of the Membership Management Communication.
  65. """
  66. # Initialize communication
  67. super(MembersMgmtCommAttack, self).__init__("Membership Management Communication Attack (MembersMgmtCommAttack)",
  68. "Injects Membership Management Communication", "Botnet communication")
  69. # Define allowed parameters and their type
  70. self.supported_params = {
  71. # parameters regarding attack
  72. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  73. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  74. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
  75. Param.PACKETS_LIMIT: ParameterTypes.TYPE_INTEGER_POSITIVE,
  76. Param.ATTACK_DURATION: ParameterTypes.TYPE_INTEGER_POSITIVE,
  77. # use num_attackers to specify number of communicating devices?
  78. Param.NUMBER_INITIATOR_BOTS: ParameterTypes.TYPE_INTEGER_POSITIVE,
  79. # input file containing botnet communication
  80. Param.FILE_CSV: ParameterTypes.TYPE_FILEPATH,
  81. Param.FILE_XML: ParameterTypes.TYPE_FILEPATH,
  82. # the percentage of IP reuse (if total and other is specified, percentages are multiplied)
  83. Param.IP_REUSE_TOTAL: ParameterTypes.TYPE_PERCENTAGE,
  84. Param.IP_REUSE_LOCAL: ParameterTypes.TYPE_PERCENTAGE,
  85. Param.IP_REUSE_EXTERNAL: ParameterTypes.TYPE_PERCENTAGE,
  86. # the user-selected padding to add to every packet
  87. Param.PACKET_PADDING: ParameterTypes.TYPE_PADDING,
  88. # presence of NAT at the gateway of the network
  89. Param.NAT_PRESENT: ParameterTypes.TYPE_BOOLEAN,
  90. # the base PCAP for the TTL distribution
  91. Param.TTL_FROM_CAIDA: ParameterTypes.TYPE_BOOLEAN
  92. }
  93. # create dict with MessageType values for fast name lookup
  94. self.msg_types = {}
  95. for msg_type in MessageType:
  96. self.msg_types[msg_type.value] = msg_type
  97. def init_params(self):
  98. """
  99. Initialize some parameters of this communication-attack using the user supplied command line parameters.
  100. The remaining parameters are implicitly set in the provided data file. Note: the timestamps in the file
  101. have to be sorted in ascending order
  102. :param statistics: Reference to a statistics object.
  103. """
  104. # set class constants
  105. self.DEFAULT_XML_PATH = "resources/MembersMgmtComm_example.xml"
  106. # probability for responder ID to be local if comm_type is mixed
  107. self.PROB_RESPND_IS_LOCAL = 0
  108. # PARAMETERS: initialize with default values
  109. # (values are overwritten if user specifies them)
  110. self.add_param_value(Param.INJECT_AFTER_PACKET, 1 + randint(0, self.statistics.get_packet_count() // 5))
  111. self.add_param_value(Param.PACKETS_PER_SECOND, 0)
  112. self.add_param_value(Param.FILE_XML, self.DEFAULT_XML_PATH)
  113. # Alternatively new attack parameter?
  114. duration = int(float(self._get_capture_duration()))
  115. self.add_param_value(Param.ATTACK_DURATION, duration)
  116. self.add_param_value(Param.NUMBER_INITIATOR_BOTS, 1)
  117. # NAT on by default
  118. self.add_param_value(Param.NAT_PRESENT, True)
  119. # TODO: change 1 to something better
  120. self.add_param_value(Param.IP_REUSE_TOTAL, 1)
  121. self.add_param_value(Param.IP_REUSE_LOCAL, 0.5)
  122. self.add_param_value(Param.IP_REUSE_EXTERNAL, 0.5)
  123. # add default additional padding
  124. self.add_param_value(Param.PACKET_PADDING, 20)
  125. # choose the input PCAP as default base for the TTL distribution
  126. self.add_param_value(Param.TTL_FROM_CAIDA, False)
  127. def generate_attack_pcap(self, context):
  128. """
  129. Injects the packets of this attack into a PCAP and stores it as a temporary file.
  130. :param context: the context of the attack, containing e.g. files that are to be created
  131. :return: a tuple of the number packets injected and the path to the temporary attack PCAP
  132. """
  133. # create the final messages that have to be sent, including all bot configurations
  134. messages = self._create_messages(context)
  135. if messages == []:
  136. return 0, []
  137. # Setup (initial) parameters for packet creation loop
  138. BUFFER_SIZE = 1000
  139. pkt_gen = Generator.PacketGenerator()
  140. padding = self.get_param_value(Param.PACKET_PADDING)
  141. packets = deque(maxlen=BUFFER_SIZE)
  142. total_pkts = 0
  143. limit_packetcount = self.get_param_value(Param.PACKETS_LIMIT)
  144. limit_duration = self.get_param_value(Param.ATTACK_DURATION)
  145. path_attack_pcap = None
  146. msg_packet_mapping = MessageMapping(messages)
  147. # create packets to write to PCAP file
  148. for msg in messages:
  149. # retrieve the source and destination configurations
  150. id_src, id_dst = msg.src["ID"], msg.dst["ID"]
  151. ip_src, ip_dst = msg.src["IP"], msg.dst["IP"]
  152. mac_src, mac_dst = msg.src["MAC"], msg.dst["MAC"]
  153. if msg.type.is_request():
  154. port_src, port_dst = int(msg.src["SrcPort"]), int(msg.dst["DstPort"])
  155. else:
  156. port_src, port_dst = int(msg.src["DstPort"]), int(msg.dst["SrcPort"])
  157. ttl = int(msg.src["TTL"])
  158. # update duration
  159. duration = msg.time - messages[0].time
  160. # if total number of packets has been sent or the attack duration has been exceeded, stop
  161. if ((limit_packetcount is not None and total_pkts >= limit_packetcount) or
  162. (limit_duration is not None and duration >= limit_duration)):
  163. break
  164. # if the type of the message is a NL reply, determine the number of entries
  165. nl_size = 0
  166. if msg.type == MessageType.SALITY_NL_REPLY:
  167. nl_size = randint(1, 25) # what is max NL entries?
  168. # create suitable IP/UDP packet and add to packets list
  169. packet = pkt_gen.generate_mmcom_packet(ip_src=ip_src, ip_dst=ip_dst, ttl=ttl, mac_src=mac_src, mac_dst=mac_dst,
  170. port_src=port_src, port_dst=port_dst, message_type=msg.type, neighborlist_entries=nl_size)
  171. Generator.add_padding(packet, padding,True, True)
  172. packet.time = msg.time
  173. packets.append(packet)
  174. msg_packet_mapping.map_message(msg, packet)
  175. total_pkts += 1
  176. # Store timestamp of first packet (for attack label)
  177. if total_pkts <= 1:
  178. self.attack_start_utime = packets[0].time
  179. elif total_pkts % BUFFER_SIZE == 0: # every 1000 packets write them to the PCAP file (append)
  180. packets = list(packets)
  181. Generator.equal_length(packets, padding = padding)
  182. last_packet = packets[-1]
  183. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  184. packets = deque(maxlen=BUFFER_SIZE)
  185. # if there are unwritten packets remaining, write them to the PCAP file
  186. if len(packets) > 0:
  187. packets = list(packets)
  188. Generator.equal_length(packets, padding = padding)
  189. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  190. last_packet = packets[-1]
  191. # write the mapping to a file
  192. msg_packet_mapping.write_to(context.allocate_file("_mapping.xml"))
  193. # Store timestamp of last packet
  194. self.attack_end_utime = last_packet.time
  195. # Return packets sorted by packet by timestamp and total number of packets (sent)
  196. return total_pkts , path_attack_pcap
  197. def _create_messages(self, context):
  198. """
  199. Creates the messages that are to be injected into the PCAP.
  200. :param context: the context of the attack, containing e.g. files that are to be created
  201. :return: the final messages as a list
  202. """
  203. def add_ids_to_config(ids_to_add: list, existing_ips: list, new_ips: list, bot_configs: dict, idtype:str="local", router_mac:str=""):
  204. """
  205. Creates IP and MAC configurations for the given IDs and adds them to the existing configurations object.
  206. :param ids_to_add: all sorted IDs that have to be configured and added
  207. :param existing_ips: the existing IPs in the PCAP file that should be assigned to some, or all, IDs
  208. :param new_ips: the newly generated IPs that should be assigned to some, or all, IDs
  209. :param bot_configs: the existing configurations for the bots
  210. :param idtype: the locality type of the IDs
  211. :param router_mac: the MAC address of the router in the PCAP
  212. """
  213. ids = ids_to_add.copy()
  214. # macgen only needed, when IPs are new local IPs (therefore creating the object here suffices for the current callers
  215. # to not end up with the same MAC paired with different IPs)
  216. macgen = Generator.MacAddressGenerator()
  217. # assign existing IPs and the corresponding MAC addresses in the PCAP to the IDs
  218. for ip in existing_ips:
  219. random_id = choice(ids)
  220. mac = self.statistics.process_db_query("macAddress(IPAddress=%s)" % ip)
  221. bot_configs[random_id] = {"Type": idtype, "IP": ip, "MAC": mac}
  222. ids.remove(random_id)
  223. # assign new IPs and for local IPs new MACs or for external IPs the router MAC to the IDs
  224. for ip in new_ips:
  225. random_id = choice(ids)
  226. if idtype == "local":
  227. mac = macgen.random_mac()
  228. elif idtype == "external":
  229. mac = router_mac
  230. bot_configs[random_id] = {"Type": idtype, "IP": ip, "MAC": mac}
  231. ids.remove(random_id)
  232. def index_increment(number: int, max: int):
  233. """
  234. Number increment with rollover.
  235. """
  236. if number + 1 < max:
  237. return number + 1
  238. else:
  239. return 0
  240. def assign_realistic_ttls(bot_configs):
  241. '''
  242. Assigns a realisitic ttl to each bot from @param: bot_configs. Uses statistics and distribution to be able
  243. to calculate a realisitc ttl.
  244. :param bot_configs:
  245. :return:
  246. '''
  247. ids = sorted(bot_configs.keys())
  248. for pos,bot in enumerate(ids):
  249. bot_type = bot_configs[bot]["Type"]
  250. # print(bot_type)
  251. if(bot_type == "local"): # Set fix TTL for local Bots
  252. bot_configs[bot]["TTL"] = 128
  253. # Set TTL based on TTL distribution of IP address
  254. else: # Set varying TTl for external Bots
  255. bot_ttl_dist = self.statistics.get_ttl_distribution(bot_configs[bot]["IP"])
  256. if len(bot_ttl_dist) > 0:
  257. source_ttl_prob_dict = Lea.fromValFreqsDict(bot_ttl_dist)
  258. bot_configs[bot]["TTL"] = source_ttl_prob_dict.random()
  259. else:
  260. bot_configs[bot]["TTL"] = self.statistics.process_db_query("most_used(ttlValue)")
  261. def assign_realistic_timestamps(messages: list, external_ids: set, local_ids: set, avg_delay_local:float, avg_delay_external: float, zero_reference:float):
  262. """
  263. Assigns realistic timestamps to a set of messages
  264. :param messages: the set of messages to be updated
  265. :param external_ids: the set of bot ids, that are outside the network, i.e. external
  266. :param local_ids: the set of bot ids, that are inside the network, i.e. local
  267. :avg_delay_local: the avg_delay between the dispatch and the reception of a packet between local computers
  268. :avg_delay_external: the avg_delay between the dispatch and the reception of a packet between a local and an external computer
  269. :zero_reference: the timestamp which is regarded as the beginning of the pcap_file and therefore handled like a timestamp that resembles 0
  270. """
  271. updated_msgs = []
  272. last_response = {} # Dict, takes a tuple of 2 Bot_IDs as a key (requester, responder), returns the time of the last response, the requester received
  273. # necessary in order to make sure, that additional requests are sent only after the response to the last one was received
  274. for msg in messages: # init
  275. last_response[(msg.src, msg.dst)] = -1
  276. # update all timestamps
  277. for req_msg in messages:
  278. if(req_msg in updated_msgs):
  279. # message already updated
  280. continue
  281. # if req_msg.timestamp would be before the timestamp of the response to the last request, req_msg needs to be sent later (else branch)
  282. if last_response[(req_msg.src, req_msg.dst)] == -1 or last_response[(req_msg.src, req_msg.dst)] < (zero_reference + req_msg.time - 0.05):
  283. ## update req_msg timestamp with a variation of up to 50ms
  284. req_msg.time = zero_reference + req_msg.time + uniform(-0.05, 0.05)
  285. updated_msgs.append(req_msg)
  286. else:
  287. req_msg.time = last_response[(req_msg.src, req_msg.dst)] + 0.06 + uniform(-0.05, 0.05)
  288. # update response if necessary
  289. if req_msg.refer_msg_id != -1:
  290. respns_msg = messages[req_msg.refer_msg_id]
  291. # check for local or external communication and update response timestamp with the respective avg delay
  292. if req_msg.src in external_ids or req_msg.dst in external_ids:
  293. #external communication
  294. respns_msg.time = req_msg.time + avg_delay_external + uniform(-0.1*avg_delay_external, 0.1*avg_delay_external)
  295. else:
  296. #local communication
  297. respns_msg.time = req_msg.time + avg_delay_local + uniform(-0.1*avg_delay_local, 0.1*avg_delay_local)
  298. updated_msgs.append(respns_msg)
  299. last_response[(req_msg.src, req_msg.dst)] = respns_msg.time
  300. def assign_ttls_from_caida(bot_configs):
  301. """
  302. Assign realistic TTL values to bots with respect to their IP, based on the CAIDA dataset.
  303. If there exists an entry for a bot's IP, the TTL is chosen based on a distribution over all used TTLs by this IP.
  304. If there is no such entry, the TTL is chosen based on a distribution over all used TTLs and their respective frequency.
  305. :param bot_configs: the existing bot configurations
  306. """
  307. # Mapping IP to ASN: http://www.team-cymru.org/IP-ASN-mapping.html
  308. # Why not assign TTLs for unknown IPs like this?
  309. def get_ip_ttl_distrib():
  310. """
  311. Parses the CSV file containing a mapping between IP and their used TTLs.
  312. :return: returns a dict with the IPs as keys and dicts for their TTL disribution as values
  313. """
  314. ip_based_distrib = {}
  315. with open("resources/CaidaTTL_perIP.csv", "r") as file:
  316. # every line consists of: IP, TTL, Frequency
  317. next(file) # skip CSV header line
  318. for line in file:
  319. ip_addr, ttl, freq = line.split(",")
  320. if ip_addr not in ip_based_distrib:
  321. ip_based_distrib[ip_addr] = {} # the values for ip_based_distrib are dicts with key=TTL, value=Frequency
  322. ip_based_distrib[ip_addr][ttl] = int(freq)
  323. return ip_based_distrib
  324. def get_total_ttl_distrib():
  325. """
  326. Parses the CSV file containing an overview of all used TTLs and their respective frequency.
  327. :return: returns a dict with the TTLs as keys and their frequencies as keys
  328. """
  329. total_ttl_distrib = {}
  330. with open("resources/CaidaTTL_total.csv", "r") as file:
  331. # every line consists of: TTL, Frequency, Fraction
  332. next(file) # skip CSV header line
  333. for line in file:
  334. ttl, freq, _ = line.split(",")
  335. total_ttl_distrib[ttl] = int(freq)
  336. return total_ttl_distrib
  337. # get the TTL distribution for every IP that is available in "resources/CaidaTTL_perIP.csv"
  338. ip_ttl_distrib = get_ip_ttl_distrib()
  339. # build a probability dict for the total TTL distribution
  340. total_ttl_prob_dict = Lea.fromValFreqsDict(get_total_ttl_distrib())
  341. # loop over every bot id and assign a TTL to the respective bot
  342. for bot_id in sorted(bot_configs):
  343. bot_type = bot_configs[bot_id]["Type"]
  344. bot_ip = bot_configs[bot_id]["IP"]
  345. if bot_type == "local":
  346. bot_configs[bot_id]["TTL"] = 128
  347. # if there exists detailed information about the TTL distribution of this IP
  348. elif bot_ip in ip_ttl_distrib:
  349. ip_ttl_freqs = ip_ttl_distrib[bot_ip]
  350. source_ttl_prob_dict = Lea.fromValFreqsDict(ip_ttl_freqs) # build a probability dict from this IP's TTL distribution
  351. bot_configs[bot_id]["TTL"] = source_ttl_prob_dict.random()
  352. # otherwise assign a random TTL based on the total TTL distribution
  353. else:
  354. bot_configs[bot_id]["TTL"] = total_ttl_prob_dict.random()
  355. def move_xml_to_outdir(filepath_xml: str):
  356. """
  357. Moves the XML file at filepath_xml to the output directory of the PCAP
  358. :param filepath_xml: the filepath to the XML file
  359. :return: the new filepath to the XML file
  360. """
  361. pcap_dir = context.get_output_dir()
  362. xml_name = os.path.basename(filepath_xml)
  363. if pcap_dir.endswith("/"):
  364. new_xml_path = pcap_dir + xml_name
  365. else:
  366. new_xml_path = pcap_dir + "/" + xml_name
  367. os.rename(filepath_xml, new_xml_path)
  368. context.add_other_created_file(new_xml_path)
  369. return new_xml_path
  370. # parse input CSV or XML
  371. filepath_xml = self.get_param_value(Param.FILE_XML)
  372. filepath_csv = self.get_param_value(Param.FILE_CSV)
  373. # prefer XML input over CSV input (in case both are given)
  374. if filepath_csv and filepath_xml == self.DEFAULT_XML_PATH:
  375. filepath_xml = FileUtils.parse_csv_to_xml(filepath_csv)
  376. filepath_xml = move_xml_to_outdir(filepath_xml)
  377. abstract_packets = FileUtils.parse_xml(filepath_xml)
  378. # find a good communication mapping in the input file that matches the users parameters
  379. duration = self.get_param_value(Param.ATTACK_DURATION)
  380. number_init_bots = self.get_param_value(Param.NUMBER_INITIATOR_BOTS)
  381. nat = self.get_param_value(Param.NAT_PRESENT)
  382. comm_proc = CommunicationProcessor(abstract_packets, self.msg_types, nat)
  383. comm_intervals = comm_proc.find_interval_most_comm(number_init_bots, duration)
  384. if comm_intervals == []:
  385. print("Error: There is no interval in the given CSV/XML that has enough communication initiating bots.")
  386. return []
  387. comm_interval = comm_intervals[randrange(0, len(comm_intervals))]
  388. # retrieve the mapping information
  389. mapped_ids, packet_start_idx, packet_end_idx = comm_interval["IDs"], comm_interval["Start"], comm_interval["End"]
  390. # print(mapped_ids)
  391. while len(mapped_ids) > number_init_bots:
  392. rm_idx = randrange(0, len(mapped_ids))
  393. del mapped_ids[rm_idx]
  394. # assign the communication processor this mapping for further processing
  395. comm_proc.set_mapping(abstract_packets[packet_start_idx:packet_end_idx+1], mapped_ids)
  396. # print start and end time of mapped interval
  397. # print(abstract_packets[packet_start_idx]["Time"])
  398. # print(abstract_packets[packet_end_idx]["Time"])
  399. # print(mapped_ids)
  400. # determine number of reused local and external IPs
  401. reuse_percent_total = self.get_param_value(Param.IP_REUSE_TOTAL)
  402. reuse_percent_external = self.get_param_value(Param.IP_REUSE_EXTERNAL)
  403. reuse_percent_local = self.get_param_value(Param.IP_REUSE_LOCAL)
  404. reuse_count_external = int(reuse_percent_total * reuse_percent_external * len(mapped_ids))
  405. reuse_count_local = int(reuse_percent_total * reuse_percent_local * len(mapped_ids))
  406. # create locality, IP and MAC configurations for the IDs/Bots
  407. ipgen = Generator.IPGenerator()
  408. pcapops = PcapAddressOperations(self.statistics)
  409. router_mac = pcapops.get_probable_router_mac()
  410. bot_configs = {}
  411. # determine the roles of the IDs in the mapping communication-{initiator, responder}
  412. local_init_ids, external_init_ids, respnd_ids, messages = comm_proc.det_id_roles_and_msgs()
  413. # use these roles to determine which IDs are to be local and which external
  414. local_ids, external_ids = comm_proc.det_ext_and_local_ids()
  415. # retrieve and assign the IPs and MACs for the bots with respect to the given parameters
  416. # (IDs are always added to bot_configs in the same order under a given seed)
  417. number_local_ids, number_external_ids = len(local_ids), len(external_ids)
  418. # assign addresses for local IDs
  419. if number_local_ids > 0:
  420. reuse_count_local = int(reuse_percent_total * reuse_percent_local * number_local_ids)
  421. existing_local_ips = sorted(pcapops.get_existing_local_ips(reuse_count_local))
  422. new_local_ips = sorted(pcapops.get_new_local_ips(number_local_ids - len(existing_local_ips)))
  423. add_ids_to_config(sorted(local_ids), existing_local_ips, new_local_ips, bot_configs)
  424. # assign addresses for external IDs
  425. if number_external_ids > 0:
  426. reuse_count_external = int(reuse_percent_total * reuse_percent_external * number_external_ids)
  427. existing_external_ips = sorted(pcapops.get_existing_external_ips(reuse_count_external))
  428. remaining = len(external_ids) - len(existing_external_ips)
  429. new_external_ips = sorted([ipgen.random_ip() for _ in range(remaining)])
  430. add_ids_to_config(sorted(external_ids), existing_external_ips, new_external_ips, bot_configs, idtype="external", router_mac=router_mac)
  431. # this is the timestamp at which the first packet should be injected, the packets have to be shifted to the beginning of the
  432. # pcap file (INJECT_AT_TIMESTAMP) and then the offset of the packets have to be compensated to start at the given point in time
  433. zero_reference = self.get_param_value(Param.INJECT_AT_TIMESTAMP) - messages[0].time
  434. # calculate the average delay values for local and external responses
  435. avg_delay_local, avg_delay_external = self.statistics.get_avg_delay_local_ext()
  436. #set timestamps
  437. assign_realistic_timestamps(messages, external_ids, local_ids, avg_delay_local, avg_delay_external, zero_reference)
  438. portSelector = PortSelectors.LINUX
  439. # create port configurations for the bots
  440. for bot in bot_configs:
  441. bot_configs[bot]["SrcPort"] = portSelector.select_port_udp()
  442. bot_configs[bot]["DstPort"] = Generator.gen_random_server_port()
  443. # print(local_init_ids)
  444. # print(bot_configs)
  445. # assign realistic TTL for every bot
  446. if self.get_param_value(Param.TTL_FROM_CAIDA):
  447. assign_ttls_from_caida(bot_configs)
  448. else:
  449. assign_realistic_ttls(bot_configs)
  450. # put together the final messages including the full sender and receiver
  451. # configurations (i.e. IP, MAC, port, ...) for easier later use
  452. final_messages = []
  453. messages = sorted(messages, key=lambda msg: msg.time)
  454. new_id = 0
  455. for msg in messages:
  456. type_src, type_dst = bot_configs[msg.src]["Type"], bot_configs[msg.dst]["Type"]
  457. id_src, id_dst = msg.src, msg.dst
  458. # sort out messages that do not have a suitable locality setting
  459. if type_src == "external" and type_dst == "external":
  460. continue
  461. msg.src, msg.dst = bot_configs[id_src], bot_configs[id_dst]
  462. msg.src["ID"], msg.dst["ID"] = id_src, id_dst
  463. msg.msg_id = new_id
  464. new_id += 1
  465. ### Important here to update refers, if needed later?
  466. final_messages.append(msg)
  467. return final_messages
  468. def _get_capture_duration(self):
  469. """
  470. Returns the duration of the input PCAP (since statistics duration seems to be incorrect)
  471. """
  472. ts_date_format = "%Y-%m-%d %H:%M:%S.%f"
  473. ts_first_date = datetime.strptime(self.statistics.get_pcap_timestamp_start(), ts_date_format)
  474. ts_last_date = datetime.strptime(self.statistics.get_pcap_timestamp_end(), ts_date_format)
  475. diff_date = ts_last_date - ts_first_date
  476. duration = "%d.%d" % (diff_date.total_seconds(), diff_date.microseconds)
  477. return duration