From 102b61175ccb8a8e16444b25b319b8e3806eba1c Mon Sep 17 00:00:00 2001 From: Toni Uhlig Date: Tue, 26 Jan 2021 19:07:16 +0100 Subject: All colors are beautiful for py-flow-info. * instance alias is now mandatory (will be required for future use-cases) Signed-off-by: Toni Uhlig --- dependencies/nDPIsrvd.py | 49 +++++++++++++++++++++++++++++++++++++- examples/py-flow-info/flow-info.py | 20 ++++++++++++---- nDPId.c | 25 +++++++++++++++---- 3 files changed, 83 insertions(+), 11 deletions(-) diff --git a/dependencies/nDPIsrvd.py b/dependencies/nDPIsrvd.py index bdc7497ab..d39858ede 100644 --- a/dependencies/nDPIsrvd.py +++ b/dependencies/nDPIsrvd.py @@ -8,6 +8,12 @@ import os import scapy.all import stat import socket +try: + from colorama import Back, Fore, Style + USE_COLORAMA=True +except ModuleNotFoundError: + print('Python module colorama not found, using fallback.') + USE_COLORAMA=False DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 7000 @@ -34,11 +40,52 @@ FLOW_EVENTS = [ ('Invalid','invalid'), ('New','new'), ('End','end'), ('Idle','id ('Detected','detected'), ('Detection-Update','detection-update'), ('Not-Detected','not-detected') ] class TermColor: + HINT = '\033[33m' WARNING = '\033[93m' FAIL = '\033[91m' BOLD = '\033[1m' END = '\033[0m' - BLINK = "\x1b[5m" + BLINK = '\x1b[5m' + + if USE_COLORAMA is True: + COLOR_TUPLES = [ (Fore.BLUE, [Back.RED, Back.MAGENTA, Back.WHITE]), + (Fore.CYAN, [Back.MAGENTA, Back.RED, Back.WHITE]), + (Fore.GREEN, [Back.YELLOW, Back.RED, Back.MAGENTA, Back.WHITE]), + (Fore.MAGENTA, [Back.CYAN, Back.BLUE, Back.WHITE]), + (Fore.RED, [Back.GREEN, Back.BLUE, Back.WHITE]), + (Fore.WHITE, [Back.BLACK, Back.MAGENTA, Back.RED, Back.BLUE]), + (Fore.YELLOW, [Back.RED, Back.CYAN, Back.BLUE, Back.WHITE]), + (Fore.LIGHTBLUE_EX, [Back.LIGHTRED_EX, Back.RED]), + (Fore.LIGHTCYAN_EX, [Back.LIGHTMAGENTA_EX, Back.MAGENTA]), + (Fore.LIGHTGREEN_EX, [Back.LIGHTYELLOW_EX, Back.YELLOW]), + (Fore.LIGHTMAGENTA_EX, [Back.LIGHTCYAN_EX, Back.CYAN]), + (Fore.LIGHTRED_EX, [Back.LIGHTGREEN_EX, Back.GREEN]), + (Fore.LIGHTWHITE_EX, [Back.LIGHTBLACK_EX, Back.BLACK]), + (Fore.LIGHTYELLOW_EX, [Back.LIGHTRED_EX, Back.RED]) ] + + @staticmethod + def calcColorHash(string): + h = 0 + for char in string: + h += ord(char) + return h + + @staticmethod + def getColorsByHash(string): + h = TermColor.calcColorHash(string) + tuple_index = h % len(TermColor.COLOR_TUPLES) + bg_tuple_index = h % len(TermColor.COLOR_TUPLES[tuple_index][1]) + return (TermColor.COLOR_TUPLES[tuple_index][0], + TermColor.COLOR_TUPLES[tuple_index][1][bg_tuple_index]) + + @staticmethod + def setColorByString(string): + if USE_COLORAMA is True: + fg_color, bg_color = TermColor.getColorsByHash(string) + color_hash = TermColor.calcColorHash(string) + return '{}{}{}{}{}'.format(Style.BRIGHT, fg_color, bg_color, string, Style.RESET_ALL) + else: + return '{}{}{}'.format(TermColor.BOLD, string, TermColor.END) class nDPIsrvdSocket: def __init__(self): diff --git a/examples/py-flow-info/flow-info.py b/examples/py-flow-info/flow-info.py index 81087cc8f..c13e9cead 100755 --- a/examples/py-flow-info/flow-info.py +++ b/examples/py-flow-info/flow-info.py @@ -41,9 +41,19 @@ def parse_json_str(json_str): else TermColor.FAIL + TermColor.BOLD + TermColor.BLINK + 'RISK' + TermColor.END, ndpi_frisk[:-2]) + instance_and_source = '' + instance_and_source += '[{}]'.format(TermColor.setColorByString(j['alias'])) + instance_and_source += '[{}]'.format(TermColor.setColorByString(j['source'])) + + flow_event_name = '' + if nDPIdEvent.FlowEventName == 'guessed' or nDPIdEvent.FlowEventName == 'undetected': + flow_event_name += '{}{:>16}{}'.format(TermColor.HINT, nDPIdEvent.FlowEventPrettyName, TermColor.END) + else: + flow_event_name += '{:>16}'.format(nDPIdEvent.FlowEventPrettyName) + if j['l3_proto'] == 'ip4': - print('{:>16}: [{:.>6}] [{}][{:.>5}] [{:.>15}]{} -> [{:.>15}]{} {}' \ - ''.format(nDPIdEvent.FlowEventPrettyName, + print('{} {}: [{:.>6}] [{}][{:.>5}] [{:.>15}]{} -> [{:.>15}]{} {}' \ + ''.format(instance_and_source, flow_event_name, j['flow_id'], j['l3_proto'], j['l4_proto'], j['src_ip'].lower(), '[{:.>5}]'.format(j['src_port']) if 'src_port' in j else '', @@ -51,8 +61,8 @@ def parse_json_str(json_str): '[{:.>5}]'.format(j['dst_port']) if 'dst_port' in j else '', ndpi_proto_categ)) elif j['l3_proto'] == 'ip6': - print('{:>16}: [{:.>6}] [{}][{:.>5}] [{:.>39}]{} -> [{:.>39}]{} {}' \ - ''.format(nDPIdEvent.FlowEventPrettyName, + print('{} {}: [{:.>6}] [{}][{:.>5}] [{:.>39}]{} -> [{:.>39}]{} {}' \ + ''.format(instance_and_source, flow_event_name, j['flow_id'], j['l3_proto'], j['l4_proto'], j['src_ip'].lower(), '[{:.>5}]'.format(j['src_port']) if 'src_port' in j else '', @@ -63,7 +73,7 @@ def parse_json_str(json_str): raise RuntimeError('unsupported l3 protocol: {}'.format(j['l3_proto'])) if len(ndpi_frisk) > 0: - print('{:>18}{}'.format('', ndpi_frisk)) + print('{} {:>18}{}'.format(instance_and_source, '', ndpi_frisk)) if __name__ == '__main__': diff --git a/nDPId.c b/nDPId.c index 1eff2b81c..433ebd65b 100644 --- a/nDPId.c +++ b/nDPId.c @@ -940,10 +940,7 @@ static void jsonize_basic(struct nDPId_reader_thread * const reader_thread) ndpi_serialize_string_int32(&workflow->ndpi_serializer, "thread_id", reader_thread->array_index); ndpi_serialize_string_uint32(&workflow->ndpi_serializer, "packet_id", workflow->packets_captured); ndpi_serialize_string_string(&workflow->ndpi_serializer, "source", pcap_file_or_interface); - if (instance_alias != NULL) - { - ndpi_serialize_string_string(&workflow->ndpi_serializer, "alias", instance_alias); - } + ndpi_serialize_string_string(&workflow->ndpi_serializer, "alias", instance_alias); } static void jsonize_daemon(struct nDPId_reader_thread * const reader_thread, enum daemon_event event) @@ -2554,7 +2551,9 @@ static int parse_options(int argc, char ** argv) "\t-p\tWrite the daemon PID to the given file path.\n" "\t-u\tChange UID to the numeric value of user.\n" "\t-g\tChange GID to the numeric value of group.\n" - "\t-a\tSet an optional name of this daemon instance which will be part of every JSON message.\n" + "\t-a\tSet an alias name of this daemon instance which will be part of every JSON message.\n" + "\t \tThis value is required for correct flow handling of multiple instances and should be unique.\n" + "\t \tDefaults to your hostname.\n" "\t-o\t(Carefully) Tune some daemon options. See subopts below.\n\n"; while ((opt = getopt(argc, argv, "hi:IEP:lc:dp:u:g:a:o:")) != -1) @@ -2688,6 +2687,22 @@ static int validate_options(char const * const arg0) { int retval = 0; + if (instance_alias == NULL) { + char hname[256]; + + errno = 0; + if (gethostname(hname, sizeof(hname)) != 0) { + fprintf(stderr, "%s: Could not retrieve your hostname: %s\n", arg0, strerror(errno)); + retval = 1; + } else { + instance_alias = strdup(hname); + fprintf(stderr, + "%s: No instance alias given, using your hostname '%s'\n", arg0, instance_alias); + if (instance_alias == NULL) { + retval = 1; + } + } + } if (max_flows_per_thread < 128 || max_flows_per_thread > nDPId_MAX_FLOWS_PER_THREAD) { fprintf(stderr, -- cgit v1.2.3