12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379 |
- from operator import itemgetter
- from math import sqrt, ceil, log
- import os
- import time
- import ID2TLib.libpcapreader as pr
- import matplotlib
- matplotlib.use('Agg')
- import matplotlib.pyplot as plt
- from ID2TLib.PcapFile import PcapFile
- from ID2TLib.StatsDatabase import StatsDatabase
- from ID2TLib.IPv4 import IPAddress
- class Statistics:
- def __init__(self, pcap_file: PcapFile):
- """
- Creates a new Statistics object.
- :param pcap_file: A reference to the PcapFile object
- """
- # Fields
- self.pcap_filepath = pcap_file.pcap_file_path
- self.pcap_proc = None
- self.do_extra_tests = False
- # Create folder for statistics database if required
- self.path_db = pcap_file.get_db_path()
- path_dir = os.path.dirname(self.path_db)
- if not os.path.isdir(path_dir):
- os.makedirs(path_dir)
- # Class instances
- self.stats_db = StatsDatabase(self.path_db)
- def load_pcap_statistics(self, flag_write_file: bool, flag_recalculate_stats: bool, flag_print_statistics: bool):
- """
- Loads the PCAP statistics for the file specified by pcap_filepath. If the database is not existing yet, the
- statistics are calculated by the PCAP file processor and saved into the newly created database. Otherwise the
- statistics are gathered directly from the existing database.
- :param flag_write_file: Indicates whether the statistics should be written addiotionally into a text file (True)
- or not (False)
- :param flag_recalculate_stats: Indicates whether eventually existing statistics should be recalculated
- :param flag_print_statistics: Indicates whether the gathered basic statistics should be printed to the terminal
- """
- # Load pcap and get loading time
- time_start = time.clock()
- # Inform user about recalculation of statistics and its reason
- if flag_recalculate_stats:
- print("Flag -r/--recalculate found. Recalculating statistics.")
- # Recalculate statistics if database does not exist OR param -r/--recalculate is provided
- if (not self.stats_db.get_db_exists()) or flag_recalculate_stats:
- self.pcap_proc = pr.pcap_processor(self.pcap_filepath, str(self.do_extra_tests))
- self.pcap_proc.collect_statistics()
- self.pcap_proc.write_to_database(self.path_db)
- outstring_datasource = "by PCAP file processor."
- else:
- outstring_datasource = "from statistics database."
- # Load statistics from database
- self.file_info = self.stats_db.get_file_info()
- time_end = time.clock()
- print("Loaded file statistics in " + str(time_end - time_start)[:4] + " sec " + outstring_datasource)
- # Write statistics if param -e/--export provided
- if flag_write_file:
- self.write_statistics_to_file()
- # Print statistics if param -s/--statistics provided
- if flag_print_statistics:
- self.print_statistics()
- def get_file_information(self):
- """
- Returns a list of tuples, each containing a information of the file.
- :return: a list of tuples, each consisting of (description, value, unit), where unit is optional.
- """
- return [("Pcap file", self.pcap_filepath),
- ("Packets", self.get_packet_count(), "packets"),
- ("Capture length", self.get_capture_duration(), "seconds"),
- ("Capture start", self.get_pcap_timestamp_start()),
- ("Capture end", self.get_pcap_timestamp_end())]
- def get_general_file_statistics(self):
- """
- Returns a list of tuples, each containing a file statistic.
- :return: a list of tuples, each consisting of (description, value, unit).
- """
- return [("Avg. packet rate", self.file_info['avgPacketRate'], "packets/sec"),
- ("Avg. packet size", self.file_info['avgPacketSize'], "kbytes"),
- ("Avg. packets sent", self.file_info['avgPacketsSentPerHost'], "packets"),
- ("Avg. bandwidth in", self.file_info['avgBandwidthIn'], "kbit/s"),
- ("Avg. bandwidth out", self.file_info['avgBandwidthOut'], "kbit/s")]
- @staticmethod
- def write_list(desc_val_unit_list, func, line_ending="\n"):
- """
- Takes a list of tuples (statistic name, statistic value, unit) as input, generates a string of these three values
- and applies the function func on this string.
- Before generating the string, it identifies text containing a float number, casts the string to a
- float and rounds the value to two decimal digits.
- :param desc_val_unit_list: The list of tuples consisting of (description, value, unit)
- :param func: The function to be applied to each generated string
- :param line_ending: The formatting string to be applied at the end of each string
- """
- for entry in desc_val_unit_list:
- # Convert text containing float into float
- (description, value) = entry[0:2]
- if isinstance(value, str) and "." in value:
- try:
- value = float(value)
- except ValueError:
- pass # do nothing -> value was not a float
- # round float
- if isinstance(value, float):
- value = round(value, 4)
- # write into file
- if len(entry) == 3:
- unit = entry[2]
- func(description + ":\t" + str(value) + " " + unit + line_ending)
- else:
- func(description + ":\t" + str(value) + line_ending)
- def print_statistics(self):
- """
- Prints the basic file statistics to the terminal.
- """
- print("\nPCAP FILE INFORMATION ------------------------------")
- Statistics.write_list(self.get_file_information(), print, "")
- print("\nGENERAL FILE STATISTICS ----------------------------")
- Statistics.write_list(self.get_general_file_statistics(), print, "")
- print("\n")
- def calculate_entropy(self, frequency:list, normalized:bool = False):
- """
- Calculates entropy and normalized entropy of list of elements that have specific frequency
- :param frequency: The frequency of the elements.
- :param normalized: Calculate normalized entropy
- :return: entropy or (entropy, normalized entropy)
- """
- entropy, normalizedEnt, n = 0, 0, 0
- sumFreq = sum(frequency)
- for i, x in enumerate(frequency):
- p_x = float(frequency[i] / sumFreq)
- if p_x > 0:
- n += 1
- entropy += - p_x * log(p_x, 2)
- if normalized:
- if log(n)>0:
- normalizedEnt = entropy/log(n, 2)
- return entropy, normalizedEnt
- else:
- return entropy
- def calculate_complement_packet_rates(self, pps):
- """
- Calculates the complement packet rates of the background traffic packet rates for each interval.
- Then normalize it to maximum boundary, which is the input parameter pps
- :return: normalized packet rates for each time interval.
- """
- result = self.process_db_query(
- "SELECT lastPktTimestamp,pktsCount FROM interval_statistics ORDER BY lastPktTimestamp")
- # print(result)
- bg_interval_pps = []
- complement_interval_pps = []
- intervalsSum = 0
- if result:
- # Get the interval in seconds
- for i, row in enumerate(result):
- if i < len(result) - 1:
- intervalsSum += ceil((int(result[i + 1][0]) * 10 ** -6) - (int(row[0]) * 10 ** -6))
- interval = intervalsSum / (len(result) - 1)
- # Convert timestamp from micro to seconds, convert packet rate "per interval" to "per second"
- for row in result:
- bg_interval_pps.append((int(row[0]) * 10 ** -6, int(row[1] / interval)))
- # Find max PPS
- maxPPS = max(bg_interval_pps, key=itemgetter(1))[1]
- for row in bg_interval_pps:
- complement_interval_pps.append((row[0], int(pps * (maxPPS - row[1]) / maxPPS)))
- return complement_interval_pps
- def get_tests_statistics(self):
- """
- Writes the calculated basic defects tests statistics into a file.
- """
- # self.stats_db._process_user_defined_query output is list of tuples, thus, we ned [0][0] to access data
- def count_frequncy(valuesList):
- values, frequency = [] , []
- for x in valuesList:
- if x in values:
- frequency[values.index(x)] += 1
- else:
- values.append(x)
- frequency.append(1)
- return values, frequency
- ####### Payload Tests #######
- sumPayloadCount = self.stats_db._process_user_defined_query("SELECT sum(payloadCount) FROM interval_statistics")
- pktCount = self.stats_db._process_user_defined_query("SELECT packetCount FROM file_statistics")
- if sumPayloadCount and pktCount:
- payloadRatio=0
- if(pktCount[0][0]!=0):
- payloadRatio = float(sumPayloadCount[0][0] / pktCount[0][0] * 100)
- else:
- payloadRatio = -1
- ####### TCP checksum Tests #######
- incorrectChecksumCount = self.stats_db._process_user_defined_query("SELECT sum(incorrectTCPChecksumCount) FROM interval_statistics")
- correctChecksumCount = self.stats_db._process_user_defined_query("SELECT avg(correctTCPChecksumCount) FROM interval_statistics")
- if incorrectChecksumCount and correctChecksumCount:
- incorrectChecksumRatio=0
- if(incorrectChecksumCount[0][0] + correctChecksumCount[0][0])!=0:
- incorrectChecksumRatio = float(incorrectChecksumCount[0][0] / (incorrectChecksumCount[0][0] + correctChecksumCount[0][0] ) * 100)
- else:
- incorrectChecksumRatio = -1
- ####### IP Src & Dst Tests #######
- result = self.stats_db._process_user_defined_query("SELECT ipAddress,pktsSent,pktsReceived FROM ip_statistics")
- data, srcFrequency, dstFrequency = [], [], []
- if result:
- for row in result:
- srcFrequency.append(row[1])
- dstFrequency.append(row[2])
- ipSrcEntropy, ipSrcNormEntropy = self.calculate_entropy(srcFrequency, True)
- ipDstEntropy, ipDstNormEntropy = self.calculate_entropy(dstFrequency, True)
- newIPCount = self.stats_db._process_user_defined_query("SELECT newIPCount FROM interval_statistics")
- ipNovelsPerInterval, ipNovelsPerIntervalFrequency = count_frequncy(newIPCount)
- ipNoveltyDistEntropy = self.calculate_entropy(ipNovelsPerIntervalFrequency)
- ####### Ports Tests #######
- port0Count = self.stats_db._process_user_defined_query("SELECT SUM(portCount) FROM ip_ports WHERE portNumber = 0")
- if not port0Count[0][0]:
- port0Count = 0
- else:
- port0Count = port0Count[0][0]
- reservedPortCount = self.stats_db._process_user_defined_query(
- "SELECT SUM(portCount) FROM ip_ports WHERE portNumber IN (100,114,1023,1024,49151,49152,65535)")# could be extended
- if not reservedPortCount[0][0]:
- reservedPortCount = 0
- else:
- reservedPortCount = reservedPortCount[0][0]
- ####### TTL Tests #######
- result = self.stats_db._process_user_defined_query("SELECT ttlValue,SUM(ttlCount) FROM ip_ttl GROUP BY ttlValue")
- data, frequency = [], []
- for row in result:
- frequency.append(row[1])
- ttlEntropy, ttlNormEntropy = self.calculate_entropy(frequency,True)
- newTTLCount = self.stats_db._process_user_defined_query("SELECT newTTLCount FROM interval_statistics")
- ttlNovelsPerInterval, ttlNovelsPerIntervalFrequency = count_frequncy(newTTLCount)
- ttlNoveltyDistEntropy = self.calculate_entropy(ttlNovelsPerIntervalFrequency)
- ####### Window Size Tests #######
- result = self.stats_db._process_user_defined_query("SELECT winSize,SUM(winCount) FROM tcp_win GROUP BY winSize")
- data, frequency = [], []
- for row in result:
- frequency.append(row[1])
- winEntropy, winNormEntropy = self.calculate_entropy(frequency, True)
- newWinSizeCount = self.stats_db._process_user_defined_query("SELECT newWinSizeCount FROM interval_statistics")
- winNovelsPerInterval, winNovelsPerIntervalFrequency = count_frequncy(newWinSizeCount)
- winNoveltyDistEntropy = self.calculate_entropy(winNovelsPerIntervalFrequency)
- ####### ToS Tests #######
- result = self.stats_db._process_user_defined_query(
- "SELECT tosValue,SUM(tosCount) FROM ip_tos GROUP BY tosValue")
- data, frequency = [], []
- for row in result:
- frequency.append(row[1])
- tosEntropy, tosNormEntropy = self.calculate_entropy(frequency, True)
- newToSCount = self.stats_db._process_user_defined_query("SELECT newToSCount FROM interval_statistics")
- tosNovelsPerInterval, tosNovelsPerIntervalFrequency = count_frequncy(newToSCount)
- tosNoveltyDistEntropy = self.calculate_entropy(tosNovelsPerIntervalFrequency)
- ####### MSS Tests #######
- result = self.stats_db._process_user_defined_query(
- "SELECT mssValue,SUM(mssCount) FROM tcp_mss GROUP BY mssValue")
- data, frequency = [], []
- for row in result:
- frequency.append(row[1])
- mssEntropy, mssNormEntropy = self.calculate_entropy(frequency, True)
- newMSSCount = self.stats_db._process_user_defined_query("SELECT newMSSCount FROM interval_statistics")
- mssNovelsPerInterval, mssNovelsPerIntervalFrequency = count_frequncy(newMSSCount)
- mssNoveltyDistEntropy = self.calculate_entropy(mssNovelsPerIntervalFrequency)
- result = self.stats_db._process_user_defined_query("SELECT SUM(mssCount) FROM tcp_mss WHERE mssValue > 1460")
- # The most used MSS < 1460. Calculate the ratio of the values bigger that 1460.
- if not result[0][0]:
- result = 0
- else:
- result = result[0][0]
- bigMSS = (result / sum(frequency)) * 100
- output = []
- if self.do_extra_tests:
- output = [("Payload ratio", payloadRatio, "%"),
- ("Incorrect TCP checksum ratio", incorrectChecksumRatio, "%")]
- output = output + [("# IP addresses", sum([x[0] for x in newIPCount]), ""),
- ("IP Src Entropy", ipSrcEntropy, ""),
- ("IP Src Normalized Entropy", ipSrcNormEntropy, ""),
- ("IP Dst Entropy", ipDstEntropy, ""),
- ("IP Dst Normalized Entropy", ipDstNormEntropy, ""),
- ("IP Novelty Distribution Entropy", ipNoveltyDistEntropy, ""),
- ("# TTL values", sum([x[0] for x in newTTLCount]), ""),
- ("TTL Entropy", ttlEntropy, ""),
- ("TTL Normalized Entropy", ttlNormEntropy, ""),
- ("TTL Novelty Distribution Entropy", ttlNoveltyDistEntropy, ""),
- ("# WinSize values", sum([x[0] for x in newWinSizeCount]), ""),
- ("WinSize Entropy", winEntropy, ""),
- ("WinSize Normalized Entropy", winNormEntropy, ""),
- ("WinSize Novelty Distribution Entropy", winNoveltyDistEntropy, ""),
- ("# ToS values", sum([x[0] for x in newToSCount]), ""),
- ("ToS Entropy", tosEntropy, ""),
- ("ToS Normalized Entropy", tosNormEntropy, ""),
- ("ToS Novelty Distribution Entropy", tosNoveltyDistEntropy, ""),
- ("# MSS values", sum([x[0] for x in newMSSCount]), ""),
- ("MSS Entropy", mssEntropy, ""),
- ("MSS Normalized Entropy", mssNormEntropy, ""),
- ("MSS Novelty Distribution Entropy", mssNoveltyDistEntropy, ""),
- ("======================","","")]
- # Reasoning the statistics values
- if self.do_extra_tests:
- if payloadRatio > 80:
- output.append(("WARNING: Too high payload ratio", payloadRatio, "%."))
- if payloadRatio < 30:
- output.append(("WARNING: Too low payload ratio", payloadRatio, "% (Injecting attacks that are carried out in the packet payloads is not recommmanded)."))
- if incorrectChecksumRatio > 5:
- output.append(("WARNING: High incorrect TCP checksum ratio",incorrectChecksumRatio,"%."))
- if ipSrcNormEntropy > 0.65:
- output.append(("WARNING: High IP source normalized entropy",ipSrcNormEntropy,"."))
- if ipSrcNormEntropy < 0.2:
- output.append(("WARNING: Low IP source normalized entropy", ipSrcNormEntropy, "."))
- if ipDstNormEntropy > 0.65:
- output.append(("WARNING: High IP destination normalized entropy", ipDstNormEntropy, "."))
- if ipDstNormEntropy < 0.2:
- output.append(("WARNING: Low IP destination normalized entropy", ipDstNormEntropy, "."))
- if ttlNormEntropy > 0.65:
- output.append(("WARNING: High TTL normalized entropy", ttlNormEntropy, "."))
- if ttlNormEntropy < 0.2:
- output.append(("WARNING: Low TTL normalized entropy", ttlNormEntropy, "."))
- if ttlNoveltyDistEntropy < 1:
- output.append(("WARNING: Too low TTL novelty distribution entropy", ttlNoveltyDistEntropy,
- "(The distribution of the novel TTL values is suspicious)."))
- if winNormEntropy > 0.6:
- output.append(("WARNING: High Window Size normalized entropy", winNormEntropy, "."))
- if winNormEntropy < 0.1:
- output.append(("WARNING: Low Window Size normalized entropy", winNormEntropy, "."))
- if winNoveltyDistEntropy < 4:
- output.append(("WARNING: Low Window Size novelty distribution entropy", winNoveltyDistEntropy,
- "(The distribution of the novel Window Size values is suspicious)."))
- if tosNormEntropy > 0.4:
- output.append(("WARNING: High ToS normalized entropy", tosNormEntropy, "."))
- if tosNormEntropy < 0.1:
- output.append(("WARNING: Low ToS normalized entropy", tosNormEntropy, "."))
- if tosNoveltyDistEntropy < 0.5:
- output.append(("WARNING: Low ToS novelty distribution entropy", tosNoveltyDistEntropy,
- "(The distribution of the novel ToS values is suspicious)."))
- if mssNormEntropy > 0.4:
- output.append(("WARNING: High MSS normalized entropy", mssNormEntropy, "."))
- if mssNormEntropy < 0.1:
- output.append(("WARNING: Low MSS normalized entropy", mssNormEntropy, "."))
- if mssNoveltyDistEntropy < 0.5:
- output.append(("WARNING: Low MSS novelty distribution entropy", mssNoveltyDistEntropy,
- "(The distribution of the novel MSS values is suspicious)."))
- if bigMSS > 50:
- output.append(("WARNING: High ratio of MSS > 1460", bigMSS, "% (High fragmentation rate in Ethernet)."))
- if port0Count > 0:
- output.append(("WARNING: Port number 0 is used in ",port0Count,"packets (awkward-looking port)."))
- if reservedPortCount > 0:
- output.append(("WARNING: Reserved port numbers are used in ",reservedPortCount,"packets (uncommonly-used ports)."))
- return output
- def write_statistics_to_file(self):
- """
- Writes the calculated basic statistics into a file.
- """
- def _write_header(title: str):
- """
- Writes the section header into the open file.
- :param title: The section title
- """
- target.write("====================== \n")
- target.write(title + " \n")
- target.write("====================== \n")
- target = open(self.pcap_filepath + ".stat", 'w')
- target.truncate()
- _write_header("PCAP file information")
- Statistics.write_list(self.get_file_information(), target.write)
- _write_header("General statistics")
- Statistics.write_list(self.get_general_file_statistics(), target.write)
- _write_header("Tests statistics")
- Statistics.write_list(self.get_tests_statistics(), target.write)
- target.close()
- def get_capture_duration(self):
- """
- :return: The duration of the capture in seconds
- """
- return self.file_info['captureDuration']
- def get_pcap_timestamp_start(self):
- """
- :return: The timestamp of the first packet in the PCAP file
- """
- return self.file_info['timestampFirstPacket']
- def get_pcap_timestamp_end(self):
- """
- :return: The timestamp of the last packet in the PCAP file
- """
- return self.file_info['timestampLastPacket']
- def get_pps_sent(self, ip_address: str):
- """
- Calculates the sent packets per seconds for a given IP address.
- :param ip_address: The IP address whose packets per second should be calculated
- :return: The sent packets per seconds for the given IP address
- """
- packets_sent = self.stats_db.process_db_query("SELECT pktsSent from ip_statistics WHERE ipAddress=?", False,
- (ip_address,))
- capture_duration = float(self.get_capture_duration())
- return int(float(packets_sent) / capture_duration)
- def get_pps_received(self, ip_address: str):
- """
- Calculate the packets per second received for a given IP address.
- :param ip_address: The IP address used for the calculation
- :return: The number of packets per second received
- """
- packets_received = self.stats_db.process_db_query("SELECT pktsReceived FROM ip_statistics WHERE ipAddress=?",
- False,
- (ip_address,))
- capture_duration = float(self.get_capture_duration())
- return int(float(packets_received) / capture_duration)
- def get_packet_count(self):
- """
- :return: The number of packets in the loaded PCAP file
- """
- return self.file_info['packetCount']
- def get_most_used_ip_address(self):
- """
- :return: The IP address/addresses with the highest sum of packets sent and received
- """
- return self.process_db_query("most_used(ipAddress)")
- def get_ttl_distribution(self, ipAddress: str):
- result = self.process_db_query('SELECT ttlValue, ttlCount from ip_ttl WHERE ipAddress="' + ipAddress + '"')
- result_dict = {key: value for (key, value) in result}
- return result_dict
- def get_mss_distribution(self, ipAddress: str):
- result = self.process_db_query('SELECT mssValue, mssCount from tcp_mss WHERE ipAddress="' + ipAddress + '"')
- result_dict = {key: value for (key, value) in result}
- return result_dict
- def get_win_distribution(self, ipAddress: str):
- result = self.process_db_query('SELECT winSize, winCount from tcp_win WHERE ipAddress="' + ipAddress + '"')
- result_dict = {key: value for (key, value) in result}
- return result_dict
- def get_tos_distribution(self, ipAddress: str):
- result = self.process_db_query('SELECT tosValue, tosCount from ip_tos WHERE ipAddress="' + ipAddress + '"')
- result_dict = {key: value for (key, value) in result}
- return result_dict
- def get_random_ip_address(self, count: int = 1):
- """
- :param count: The number of IP addreses to return
- :return: A randomly chosen IP address from the dataset or iff param count is greater than one, a list of randomly
- chosen IP addresses
- """
- if count == 1:
- return self.process_db_query("random(all(ipAddress))")
- else:
- ip_address_list = []
- for i in range(0, count):
- ip_address_list.append(self.process_db_query("random(all(ipAddress))"))
- return ip_address_list
- def get_mac_address(self, ipAddress: str):
- """
- :return: The MAC address used in the dataset for the given IP address.
- """
- return self.process_db_query('macAddress(ipAddress=' + ipAddress + ")")
- def get_most_used_mss(self, ipAddress: str):
- """
- :param ipAddress: The IP address whose used MSS should be determined
- :return: The TCP MSS value used by the IP address, or if the IP addresses never specified a MSS,
- then None is returned
- """
- mss_value = self.process_db_query('SELECT mssValue from tcp_mss WHERE ipAddress="' + ipAddress + '" ORDER BY mssCount DESC LIMIT 1')
- if isinstance(mss_value, int):
- return mss_value
- else:
- return None
- def get_most_used_ttl(self, ipAddress: str):
- """
- :param ipAddress: The IP address whose used TTL should be determined
- :return: The TTL value used by the IP address, or if the IP addresses never specified a TTL,
- then None is returned
- """
- ttl_value = self.process_db_query(
- 'SELECT ttlValue from ip_ttl WHERE ipAddress="' + ipAddress + '" ORDER BY ttlCount DESC LIMIT 1')
- if isinstance(ttl_value, int):
- return ttl_value
- else:
- return None
- def get_in_degree(self):
- """
- determines the in-degree for each ipAddress, i.e. for every IP the count of ipAddresses it has received packets from
- :return: a list, each entry consists of one IPAddress and its associated in-degree
- """
- in_degree_raw = self.stats_db._process_user_defined_query(
- "SELECT ipAddressA, Count(DISTINCT ipAddressB) FROM ip_ports JOIN conv_statistics_stateless ON ipAddress = ipAddressA WHERE portDirection=\'in\' AND portNumber = portA GROUP BY ipAddress " +
- "UNION " +
- "SELECT ipAddressB, Count(DISTINCT ipAddressA) FROM ip_ports JOIN conv_statistics_stateless ON ipAddress = ipAddressB WHERE portDirection=\'in\' AND portNumber = portB GROUP BY ipAddress")
- #Because of the structure of the database, there could be 2 entries for the same IP Address, therefore accumulate their sums
- in_degree = self.filter_multiples(in_degree_raw)
- return in_degree
- def get_out_degree(self):
- """
- determines the out-degree for each ipAddress, i.e. for every IP the count of ipAddresses it has sent packets to
- :return: a list, each entry consists of one IPAddress and its associated out-degree
- """
-
- out_degree_raw = self.stats_db._process_user_defined_query(
- "SELECT ipAddressA, Count(DISTINCT ipAddressB) FROM ip_ports JOIN conv_statistics_stateless ON ipAddress = ipAddressA WHERE portDirection=\'out\' AND portNumber = portA GROUP BY ipAddress " +
- "UNION " +
- "SELECT ipAddressB, Count(DISTINCT ipAddressA) FROM ip_ports JOIN conv_statistics_stateless ON ipAddress = ipAddressB WHERE portDirection=\'out\' AND portNumber = portB GROUP BY ipAddress")
- #Because of the structure of the database, there could be 2 entries for the same IP Address, therefore accumulate their sums
- out_degree = self.filter_multiples(out_degree_raw)
- return out_degree
- def get_overall_degree(self):
- """
- determines the overall-degree for each ipAddress, i.e. for every IP the count of ipAddresses it has sent packets to
- :return: a list, each entry consists of one IPAddress and its associated overall-degree
- """
- out_degrees = self.get_out_degree()
- in_degrees = self.get_in_degree()
- overall_degrees = []
- processed = {} # Dict, taking an IP Address and returning True, if the IP has already been processed and added to overall_degree
- # initialize values of the dict for in_degrees, this is important for error-free checking whether there are not processed IPs
- # for out_degrees this can be done without an additional loop
- for inD in in_degrees:
- processed[inD[0]] = False
- for outD in out_degrees:
- ip_out = outD[0]
- processed[ip_out] = False
- # add the sum of degrees for all IPs that appear in both lists
- for inD in in_degrees:
- ip_in = inD[0]
- if ip_out == ip_in:
- # same IPAddress -> append sum of degrees
- overall_degrees.append((ip_out, outD[1] + inD[1]))
- processed[ip_out] = True
- if not processed[ip_out]:
- # if IP only appears in out_degree list -> just append the value
- overall_degrees.append(outD)
- processed[outD[0]] = True
-
- # add remaining IPs, which did not appear in out_degree
- for inD in in_degrees:
- if not processed[inD[0]]:
- overall_degrees.append(inD)
- return overall_degrees
- def filter_multiples(self, entries):
- """
- helper function, for get_out_degree and get_in_degree
- filters the given list for duplicate IpAddresses and, if duplciates are present, accumulates their values
- :param entries: list, each entry consists of an ipAddress and a numeric value
- :return: a filtered list, without duplicate ipAddresses
- """
- filtered_entries = []
- done = []
- for p1 in entries:
- added = False
- if p1 in done:
- continue
- for p2 in entries:
- if p1[0] == p2[0] and p1 != p2:
- filtered_entries.append((p1[0], p1[1] + p2[1]))
- done.append(p1)
- done.append(p2)
- added = True
- break
- if not added:
- filtered_entries.append(p1)
- return filtered_entries
- def get_avg_delay_local_ext(self):
- """
- Calculates the average delay of a packet for external and local communication, based on the tcp handshakes
- :return: tuple consisting of avg delay for local and external communication, (local, external)
- """
- conv_delays = self.stats_db._process_user_defined_query("SELECT ipAddressA, ipAddressB, avgDelay FROM conv_statistics")
- if(conv_delays):
- external_conv = []
- local_conv = []
- for conv in conv_delays:
- IPA = IPAddress.parse(conv[0])
- IPB = IPAddress.parse(conv[1])
- #split into local and external conversations
- if(not IPA.is_private() or not IPB.is_private()):
- external_conv.append(conv)
- else:
- local_conv.append(conv)
-
- # calculate avg local and external delay by summing up the respective delays and dividing them by the number of conversations
- avg_delay_external = 0.0
- avg_delay_local = 0.0
- if(local_conv):
- for conv in local_conv:
- avg_delay_local += conv[2]
- avg_delay_local = (avg_delay_local/len(local_conv)) * 0.001 #ms
- else:
- # no local conversations in statistics found
- avg_delay_local = 0.06
- if(external_conv):
- for conv in external_conv:
- avg_delay_external += conv[2]
- avg_delay_external = (avg_delay_external/len(external_conv)) * 0.001 #ms
- else:
- # no external conversations in statistics found
- avg_delay_external = 0.15
- else:
- #if no statistics were found, use these numbers
- avg_delay_external = 0.15
- avg_delay_local = 0.06
- return avg_delay_local, avg_delay_external
- def get_statistics_database(self):
- """
- :return: A reference to the statistics database object
- """
- return self.stats_db
- def process_db_query(self, query_string_in: str, print_results: bool = False):
- """
- Executes a string identified previously as a query. This can be a standard SQL SELECT/INSERT query or a named
- query.
- :param query_string_in: The query to be processed
- :param print_results: Indicates whether the results should be printed to terminal
- :return: The result of the query
- """
- return self.stats_db.process_db_query(query_string_in, print_results)
- def is_query(self, value: str):
- """
- Checks whether the given string is a standard SQL query (SELECT, INSERT) or a named query.
- :param value: The string to be checked
- :return: True if the string is recognized as a query, otherwise False.
- """
- if not isinstance(value, str):
- return False
- else:
- return (any(x in value.lower().strip() for x in self.stats_db.get_all_named_query_keywords()) or
- any(x in value.lower().strip() for x in self.stats_db.get_all_sql_query_keywords()))
- def calculate_standard_deviation(self, lst):
- """
- Calculates the standard deviation of a list of numbers.
- :param lst: The list of numbers to calculate its SD.
- """
- num_items = len(lst)
- mean = sum(lst) / num_items
- differences = [x - mean for x in lst]
- sq_differences = [d ** 2 for d in differences]
- ssd = sum(sq_differences)
- variance = ssd / num_items
- sd = sqrt(variance)
- return sd
- def plot_statistics(self, format: str = 'pdf'): #'png'
- """
- Plots the statistics associated with the dataset.
- :param format: The format to be used to save the statistics diagrams.
- """
- def plot_distribution(queryOutput, title, xLabel, yLabel, file_ending: str):
- plt.gcf().clear()
- graphx, graphy = [], []
- for row in queryOutput:
- graphx.append(row[0])
- graphy.append(row[1])
- plt.autoscale(enable=True, axis='both')
- plt.title(title)
- plt.xlabel(xLabel)
- plt.ylabel(yLabel)
- width = 0.1
- plt.xlim([0, max(graphx)])
- plt.grid(True)
- plt.bar(graphx, graphy, width, align='center', linewidth=1, color='red', edgecolor='red')
- out = self.pcap_filepath.replace('.pcap', '_plot-' + title + file_ending)
- plt.savefig(out,dpi=500)
- return out
- def plot_ttl(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT ttlValue, SUM(ttlCount) FROM ip_ttl GROUP BY ttlValue")
- title = "TTL Distribution"
- xLabel = "TTL Value"
- yLabel = "Number of Packets"
- if queryOutput:
- return plot_distribution(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_mss(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT mssValue, SUM(mssCount) FROM tcp_mss GROUP BY mssValue")
- title = "MSS Distribution"
- xLabel = "MSS Value"
- yLabel = "Number of Packets"
- if queryOutput:
- return plot_distribution(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_win(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT winSize, SUM(winCount) FROM tcp_win GROUP BY winSize")
- title = "Window Size Distribution"
- xLabel = "Window Size"
- yLabel = "Number of Packets"
- if queryOutput:
- return plot_distribution(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_protocol(file_ending: str):
- plt.gcf().clear()
- result = self.stats_db._process_user_defined_query(
- "SELECT protocolName, SUM(protocolCount) FROM ip_protocols GROUP BY protocolName")
- if (result):
- graphx, graphy = [], []
- for row in result:
- graphx.append(row[0])
- graphy.append(row[1])
- plt.autoscale(enable=True, axis='both')
- plt.title("Protocols Distribution")
- plt.xlabel('Protocols')
- plt.ylabel('Number of Packets')
- width = 0.1
- plt.xlim([0, len(graphx)])
- plt.grid(True)
- # Protocols' names on x-axis
- x = range(0,len(graphx))
- my_xticks = graphx
- plt.xticks(x, my_xticks)
- plt.bar(x, graphy, width, align='center', linewidth=1, color='red', edgecolor='red')
- out = self.pcap_filepath.replace('.pcap', '_plot-protocol' + file_ending)
- plt.savefig(out,dpi=500)
- return out
- else:
- print("Error plot protocol: No protocol values found!")
- def plot_port(file_ending: str):
- plt.gcf().clear()
- result = self.stats_db._process_user_defined_query(
- "SELECT portNumber, SUM(portCount) FROM ip_ports GROUP BY portNumber")
- graphx, graphy = [], []
- for row in result:
- graphx.append(row[0])
- graphy.append(row[1])
- plt.autoscale(enable=True, axis='both')
- plt.title("Ports Distribution")
- plt.xlabel('Ports Numbers')
- plt.ylabel('Number of Packets')
- width = 0.1
- plt.xlim([0, max(graphx)])
- plt.grid(True)
- plt.bar(graphx, graphy, width, align='center', linewidth=1, color='red', edgecolor='red')
- out = self.pcap_filepath.replace('.pcap', '_plot-port' + file_ending)
- plt.savefig(out,dpi=500)
- return out
- # This distribution is not drawable for big datasets
- def plot_ip_src(file_ending: str):
- plt.gcf().clear()
- result = self.stats_db._process_user_defined_query(
- "SELECT ipAddress, pktsSent FROM ip_statistics")
- graphx, graphy = [], []
- for row in result:
- graphx.append(row[0])
- graphy.append(row[1])
- plt.autoscale(enable=True, axis='both')
- plt.title("Source IP Distribution")
- plt.xlabel('Source IP')
- plt.ylabel('Number of Packets')
- width = 0.1
- plt.xlim([0, len(graphx)])
- plt.grid(True)
- # IPs on x-axis
- x = range(0, len(graphx))
- my_xticks = graphx
- plt.xticks(x, my_xticks, rotation='vertical', fontsize=5)
- plt.tight_layout()
- # limit the number of xticks
- plt.locator_params(axis='x', nbins=20)
- plt.bar(x, graphy, width, align='center', linewidth=1, color='red', edgecolor='red')
- out = self.pcap_filepath.replace('.pcap', '_plot-ip-src' + file_ending)
- plt.savefig(out, dpi=500)
- return out
- # This distribution is not drawable for big datasets
- def plot_ip_dst(file_ending: str):
- plt.gcf().clear()
- result = self.stats_db._process_user_defined_query(
- "SELECT ipAddress, pktsReceived FROM ip_statistics")
- graphx, graphy = [], []
- for row in result:
- graphx.append(row[0])
- graphy.append(row[1])
- plt.autoscale(enable=True, axis='both')
- plt.title("Destination IP Distribution")
- plt.xlabel('Destination IP')
- plt.ylabel('Number of Packets')
- width = 0.1
- plt.xlim([0, len(graphx)])
- plt.grid(True)
- # IPs on x-axis
- x = range(0, len(graphx))
- my_xticks = graphx
- plt.xticks(x, my_xticks, rotation='vertical', fontsize=5)
- plt.tight_layout()
- # limit the number of xticks
- plt.locator_params(axis='x', nbins=20)
- plt.bar(x, graphy, width, align='center', linewidth=1, color='red', edgecolor='red')
- out = self.pcap_filepath.replace('.pcap', '_plot-ip-dst' + file_ending)
- plt.savefig(out, dpi=500)
- return out
- def plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending: str):
- plt.gcf().clear()
- graphx, graphy = [], []
- for row in queryOutput:
- graphx.append(row[0])
- graphy.append(row[1])
- plt.autoscale(enable=True, axis='both')
- plt.title(title)
- plt.xlabel(xLabel)
- plt.ylabel(yLabel)
- width = 0.5
- plt.xlim([0, len(graphx)])
- plt.grid(True)
- # timestamp on x-axis
- x = range(0, len(graphx))
- # limit the number of xticks
- plt.locator_params(axis='x', nbins=20)
- plt.bar(x, graphy, width, align='center', linewidth=1, color='red', edgecolor='red')
- out = self.pcap_filepath.replace('.pcap', '_plot-' + title + file_ending)
- plt.savefig(out, dpi=500)
- return out
- def plot_interval_pktCount(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, pktsCount FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "Packet Rate"
- xLabel = "Time Interval"
- yLabel = "Number of Packets"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_ip_src_ent(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, ipSrcEntropy FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "Source IP Entropy"
- xLabel = "Time Interval"
- yLabel = "Entropy"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_ip_dst_ent(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, ipDstEntropy FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "Destination IP Entropy"
- xLabel = "Time Interval"
- yLabel = "Entropy"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_new_ip(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, newIPCount FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "IP Novelty Distribution"
- xLabel = "Time Interval"
- yLabel = "Novel values count"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_new_port(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, newPortCount FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "Port Novelty Distribution"
- xLabel = "Time Interval"
- yLabel = "Novel values count"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_new_ttl(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, newTTLCount FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "TTL Novelty Distribution"
- xLabel = "Time Interval"
- yLabel = "Novel values count"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_new_tos(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, newToSCount FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "ToS Novelty Distribution"
- xLabel = "Time Interval"
- yLabel = "Novel values count"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_new_win_size(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, newWinSizeCount FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "Window Size Novelty Distribution"
- xLabel = "Time Interval"
- yLabel = "Novel values count"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_new_mss(file_ending: str):
- queryOutput = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, newMSSCount FROM interval_statistics ORDER BY lastPktTimestamp")
- title = "MSS Novelty Distribution"
- xLabel = "Time Interval"
- yLabel = "Novel values count"
- if queryOutput:
- return plot_interval_statistics(queryOutput, title, xLabel, yLabel, file_ending)
- def plot_interval_ip_dst_cum_ent(file_ending: str):
- plt.gcf().clear()
- result = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, ipDstCumEntropy FROM interval_statistics ORDER BY lastPktTimestamp")
- graphx, graphy = [], []
- for row in result:
- graphx.append(row[0])
- graphy.append(row[1])
- # If entropy was not calculated do not plot the graph
- if graphy[0] != -1:
- plt.autoscale(enable=True, axis='both')
- plt.title("Destination IP Cumulative Entropy")
- # plt.xlabel('Timestamp')
- plt.xlabel('Time Interval')
- plt.ylabel('Entropy')
- plt.xlim([0, len(graphx)])
- plt.grid(True)
- # timestamp on x-axis
- x = range(0, len(graphx))
- # my_xticks = graphx
- # plt.xticks(x, my_xticks, rotation='vertical', fontsize=5)
- # plt.tight_layout()
- # limit the number of xticks
- plt.locator_params(axis='x', nbins=20)
- plt.plot(x, graphy, 'r')
- out = self.pcap_filepath.replace('.pcap', '_plot-interval-ip-dst-cum-ent' + file_ending)
- plt.savefig(out, dpi=500)
- return out
- def plot_interval_ip_src_cum_ent(file_ending: str):
- plt.gcf().clear()
- result = self.stats_db._process_user_defined_query(
- "SELECT lastPktTimestamp, ipSrcCumEntropy FROM interval_statistics ORDER BY lastPktTimestamp")
- graphx, graphy = [], []
- for row in result:
- graphx.append(row[0])
- graphy.append(row[1])
- # If entropy was not calculated do not plot the graph
- if graphy[0] != -1:
- plt.autoscale(enable=True, axis='both')
- plt.title("Source IP Cumulative Entropy")
- # plt.xlabel('Timestamp')
- plt.xlabel('Time Interval')
- plt.ylabel('Entropy')
- plt.xlim([0, len(graphx)])
- plt.grid(True)
- # timestamp on x-axis
- x = range(0, len(graphx))
- # my_xticks = graphx
- # plt.xticks(x, my_xticks, rotation='vertical', fontsize=5)
- # plt.tight_layout()
- # limit the number of xticks
- plt.locator_params(axis='x', nbins=20)
- plt.plot(x, graphy, 'r')
- out = self.pcap_filepath.replace('.pcap', '_plot-interval-ip-src-cum-ent' + file_ending)
- plt.savefig(out, dpi=500)
- return out
- def plot_in_degree(file_ending: str):
- """
- Creates a Plot, visualizing the in-degree for every IP Address
- :param file_ending: The file extension for the output file containing the plot, e.g. "pdf"
- :return: A filepath to the file containing the created plot
- """
- plt.gcf().clear()
- # retrieve data
- in_degree = self.get_in_degree()
- if(in_degree):
- graphx, graphy = [], []
- for entry in in_degree:
- # degree values
- graphx.append(entry[1])
- # IP labels
- graphy.append(entry[0])
- # set labels
- plt.title("Indegree per IP Address")
- plt.ylabel('IpAddress')
- plt.xlabel('Indegree')
- #set width of the bars
- width = 0.3
- # set scalings
- plt.figure(figsize=(int(len(graphx))/20 + 5, int(len(graphy)/5) + 5)) # these proportions just worked well
- #set limits of the axis
- plt.ylim([0, len(graphy)])
- plt.xlim([0, max(graphx) + 10])
- # display numbers at each bar
- for i, v in enumerate(graphx):
- plt.text(v + 1, i + .1, str(v), color='blue', fontweight='bold')
- # display grid for better visuals
- plt.grid(True)
- # plot the bar
- labels = graphy
- graphy = list(range(len(graphx)))
- plt.barh(graphy, graphx, width, align='center', linewidth=1, color='red', edgecolor='red')
- plt.yticks(graphy, labels)
- out = self.pcap_filepath.replace('.pcap', '_in_degree' + file_ending)
- plt.tight_layout()
- plt.savefig(out,dpi=500)
- return out
- else:
- print("Error: No statistics Information for plotting out-degrees found")
- def plot_out_degree(file_ending: str):
- """
- Creates a Plot, visualizing the out-degree for every IP Address
- :param file_ending: The file extension for the output file containing the plot, e.g. "pdf"
- :return: A filepath to the file containing the created plot
- """
- plt.gcf().clear()
- # retrieve data
- out_degree = self.get_out_degree()
- if(out_degree):
- graphx, graphy = [], []
- for entry in out_degree:
- # degree values
- graphx.append(entry[1])
- # IP labels
- graphy.append(entry[0])
- # set labels
- plt.title("Outdegree per IP Address")
- plt.ylabel('IpAddress')
- plt.xlabel('Outdegree')
- #set width of the bars
- width = 0.3
- # set scalings
- plt.figure(figsize=(int(len(graphx))/20 + 5, int(len(graphy)/5) + 5)) # these proportions just worked well
- #set limits of the axis
- plt.ylim([0, len(graphy)])
- plt.xlim([0, max(graphx) + 10])
- # display numbers at each bar
- for i, v in enumerate(graphx):
- plt.text(v + 1, i + .1, str(v), color='blue', fontweight='bold')
- # display grid for better visuals
- plt.grid(True)
- # plot the bar
- labels = graphy
- graphy = list(range(len(graphx)))
- plt.barh(graphy, graphx, width, align='center', linewidth=1, color='red', edgecolor='red')
- plt.yticks(graphy, labels)
- out = self.pcap_filepath.replace('.pcap', '_out_degree' + file_ending)
- plt.tight_layout()
- plt.savefig(out,dpi=500)
- return out
- else:
- print("Error: No statistics Information for plotting out-degrees found")
- def plot_overall_degree(file_ending: str):
- """
- Creates a Plot, visualizing the overall-degree for every IP Address
- :param file_ending: The file extension for the output file containing the plot, e.g. "pdf"
- :return: A filepath to the file containing the created plot
- """
- plt.gcf().clear()
- # retrieve data
- overall_degree = self.get_overall_degree()
- if(overall_degree):
- graphx, graphy = [], []
- for entry in overall_degree:
- # degree values
- graphx.append(entry[1])
- # IP labels
- graphy.append(entry[0])
- # set labels
- plt.title("Overalldegree per IP Address")
- plt.ylabel('IpAddress')
- plt.xlabel('Overalldegree')
- #set width of the bars
- width = 0.3
- # set scalings
- plt.figure(figsize=(int(len(graphx))/20 + 5, int(len(graphy)/5) + 5)) # these proportions just worked well
- #set limits of the axis
- plt.ylim([0, len(graphy)])
- plt.xlim([0, max(graphx) + 10])
- # display numbers at each bar
- for i, v in enumerate(graphx):
- plt.text(v + 1, i + .1, str(v), color='blue', fontweight='bold')
- # display grid for better visuals
- plt.grid(True)
- # plot the bar
- labels = graphy
- graphy = list(range(len(graphx)))
- plt.barh(graphy, graphx, width, align='center', linewidth=1, color='red', edgecolor='red')
- plt.yticks(graphy, labels)
- out = self.pcap_filepath.replace('.pcap', '_overall_degree' + file_ending)
- plt.tight_layout()
- plt.savefig(out,dpi=500)
- return out
- else:
- print("Error: No statistics Information for plotting overall-degrees found")
- def plot_big_comm_interval_stat(attr:str, table:str, title:str, xlabel:str, suffix:str):
- """
- Plots the desired statistc per connection as horizontal bar plot.
- Included are 'half-open' connections, where only one packet is exchanged.
- The given statistics table has to have at least the attributes 'ipAddressA', 'portA', 'ipAddressB',
- 'portB' and the specified additional attribute.
- Note: there may be cutoff/scaling problems within the plot if there is too little data.
- :param attr: The desired statistic, named with respect to its attribute in the given statistics table
- :param table: The statistics table
- :param title: The title of the created plot
- :param xlabel: The name of the x-axis of the created plot
- :param suffix: The suffix of the created file, including file extension
- :return: A filepath to the file containing the created plot
- """
- plt.gcf().clear()
- result = self.stats_db._process_user_defined_query(
- "SELECT ipAddressA, portA, ipAddressB, portB, %s FROM %s" % (attr, table))
- if (result):
- graphy, graphx = [], []
- # plot data in descending order
- result = sorted(result, key=lambda row: row[4])
- # compute plot data
- for i, row in enumerate(result):
- addr1, addr2 = "%s:%d" % (row[0], row[1]), "%s:%d" % (row[2], row[3])
- # adjust the justification of strings to improve appearance
- len_max = max(len(addr1), len(addr2))
- addr1 = addr1.ljust(len_max)
- addr2 = addr2.ljust(len_max)
- # add plot data
- graphy.append("%s\n%s" % (addr1, addr2))
- graphx.append(row[4])
- # compute plot height in inches
- dist_mult_height, dist_mult_width = 0.55, 0.07 # these values turned out to work well
- plt_height, plt_width = len(graphy) * dist_mult_height, max(graphx) * dist_mult_width
- title_distance = 1 + 0.012*52.8/plt_height # orginally, a good title distance turned out to be 1.012 with a plot height of 52.8
- # have x axis and its label appear at the top (instead of bottom)
- fig, ax = plt.subplots()
- ax.xaxis.tick_top()
- ax.xaxis.set_label_position("top")
- # set additional plot parameters
- plt.title(title, y=title_distance)
- plt.xlabel(xlabel)
- plt.ylabel('Connection')
- width = 0.5
- plt.grid(True)
- plt.gca().margins(y=0) # removes the space between data and x-axis within the plot
- plt.gcf().set_size_inches(plt_width, plt_height) # set plot size
- # plot the above data, first use plain numbers as graphy to maintain sorting
- plt.barh(range(len(graphy)), graphx, width, align='center', linewidth=1, color='red', edgecolor='red')
- # now change the y numbers to the respective address labels
- plt.yticks(range(len(graphy)), graphy)
- # try to use tight layout to cut off unnecessary space
- try:
- plt.tight_layout(pad=4)
- except ValueError:
- pass
- # save created figure
- out = self.pcap_filepath.replace('.pcap', suffix)
- plt.savefig(out, dpi=500)
- return out
- def plot_packets_per_connection(file_ending: str):
- """
- Plots the total number of exchanged packets per connection.
- :param file_ending: The file extension for the output file containing the plot
- :return: A filepath to the file containing the created plot
- """
- title = 'Number of exchanged packets per connection'
- suffix = '_plot-PktCount per Connection Distribution' + file_ending
- # plot data and return outpath
- return plot_big_comm_interval_stat("pktsCount", "conv_statistics_stateless", title, "Number of packets", suffix)
- def plot_avg_pkts_per_comm_interval(file_ending: str):
- """
- Plots the average number of exchanged packets per communication interval for every connection.
- :param file_ending: The file extension for the output file containing the plot
- :return: A filepath to the file containing the created plot
- """
- title = 'Average number of exchanged packets per communication interval'
- suffix = '_plot-Avg PktCount Communication Interval Distribution' + file_ending
- # plot data and return outpath
- return plot_big_comm_interval_stat("avgPktCount", "comm_interval_statistics" ,title, "Number of packets", suffix)
- def plot_avg_time_between_comm_interval(file_ending: str):
- """
- Plots the average time between the communication intervals of every connection.
- :param file_ending: The file extension for the output file containing the plot
- :return: A filepath to the file containing the created plot
- """
- title = 'Average time between communication intervals in seconds'
- suffix = '_plot-Avg Time Between Communication Intervals Distribution' + file_ending
- # plot data and return outpath
- return plot_big_comm_interval_stat("avgTimeBetweenIntervals", "comm_interval_statistics", title, 'Average time between intervals', suffix)
-
- ttl_out_path = plot_ttl('.' + format)
- mss_out_path = plot_mss('.' + format)
- win_out_path = plot_win('.' + format)
- protocol_out_path = plot_protocol('.' + format)
- plot_interval_pktCount = plot_interval_pktCount('.' + format)
- plot_interval_ip_src_ent = plot_interval_ip_src_ent('.' + format)
- plot_interval_ip_dst_ent = plot_interval_ip_dst_ent('.' + format)
- plot_interval_ip_src_cum_ent = plot_interval_ip_src_cum_ent('.' + format)
- plot_interval_ip_dst_cum_ent = plot_interval_ip_dst_cum_ent('.' + format)
- plot_interval_new_ip = plot_interval_new_ip('.' + format)
- plot_interval_new_port = plot_interval_new_port('.' + format)
- plot_interval_new_ttl = plot_interval_new_ttl('.' + format)
- plot_interval_new_tos = plot_interval_new_tos('.' + format)
- plot_interval_new_win_size = plot_interval_new_win_size('.' + format)
- plot_interval_new_mss = plot_interval_new_mss('.' + format)
- plot_packets_per_connection_out = plot_packets_per_connection('.' + format)
- plot_out_degree = plot_out_degree('.' + format)
- plot_in_degree = plot_in_degree('.' + format)
- plot_overall_degree = plot_overall_degree('.' + format)
- plot_avg_pkts_per_comm_interval_out = plot_avg_pkts_per_comm_interval('.' + format)
- plot_avg_time_between_comm_interval_out = plot_avg_time_between_comm_interval('.' + format)
- ## Time consuming plot
- # port_out_path = plot_port('.' + format)
- ## Not drawable for too many IPs
- # ip_src_out_path = plot_ip_src('.' + format)
- # ip_dst_out_path = plot_ip_dst('.' + format)
- print("Saved plots in the input PCAP directory.")
|