def run(): """ Main entry point of the application """ version = get_version() args = parse_arguments() IO.initialize(args.colorless) IO.print(get_main_banner(version)) if not is_linux(): IO.error('run under linux.') return if not is_privileged(): IO.error('run as root.') return args = process_arguments(args) if args is None: return if initialize(args.interface): IO.spacer() menu = MainMenu(version, args.interface, args.gateway_ip, args.gateway_mac, args.netmask) menu.start() cleanup(args.interface)
def _hosts_handler(self, args): """ Handles 'hosts' command-line argument Displays discovered hosts """ table_data = [[ '{}ID{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}IP address{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}MAC address{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}Hostname{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}Status{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL) ]] with self.hosts_lock: for host in self.hosts: table_data.append([ '{}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, self._get_host_id(host, lock=False), IO.Style.RESET_ALL), host.ip, host.mac, host.name, host.pretty_status() ]) table = SingleTable(table_data, 'Hosts') if not args.force and not table.ok: IO.error( 'table does not fit terminal. resize or decrease font size. you can also force the display (--force).' ) return IO.spacer() IO.print(table.table) IO.spacer()
def _limit_handler(self, args): """ Handles 'limit' command-line argument Limits bandwith of host to specified rate """ hosts = self._get_hosts_by_ids(args.id) if hosts is None or len(hosts) == 0: return try: rate = BitRate.from_rate_string(args.rate) except Exception: IO.error('limit rate is invalid.') return direction = self._parse_direction_args(args) discordText = "```" for host in hosts: self.arp_spoofer.add(host) self.limiter.limit(host, direction, rate) self.bandwidth_monitor.add(host) IO.ok('{}{}{r} {} {}limited{r} to {}.'.format( IO.Fore.LIGHTYELLOW_EX, host.ip, Direction.pretty_direction(direction), IO.Fore.LIGHTRED_EX, rate, r=IO.Style.RESET_ALL)) discordText += '{} pada IP {} dilimit menjadi {}\n'.format( Direction.pretty_direction(direction), host.ip, rate) discordText += '```' IO.discord(discordText)
def _scan_handler(self, args): """ Handles 'scan' command-line argument (Re)scans for hosts on the network """ if args.iprange: iprange = self._parse_iprange(args.iprange) if iprange is None: IO.error('invalid ip range.') return else: iprange = None with self.hosts_lock: for host in self.hosts: self._free_host(host) IO.spacer() IO.discord("Scanning...") hosts = self.host_scanner.scan(iprange) hosts = [host for host in hosts if host not in self.hosts] self.hosts_lock.acquire() self.hosts += hosts self.hosts_lock.release() IO.ok('{}{}{} hosts discovered.'.format(IO.Fore.LIGHTYELLOW_EX, len(hosts), IO.Style.RESET_ALL)) IO.spacer() IO.discord("{} hosts terdeteksi".format(len(hosts)))
def _hosts_handler(self, args): """ Handles 'hosts' command-line argument Displays discovered hosts """ table_data = [[ '{}ID{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}IP-Address{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}MAC-Address{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}Hostname{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL), '{}Status{}'.format(IO.Style.BRIGHT, IO.Style.RESET_ALL) ]] for i, host in enumerate(self.hosts): table_data.append([ '{}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, i, IO.Style.RESET_ALL), host.ip, host.mac, host.name if host.name is not None else '', host.pretty_status() ]) table = SingleTable(table_data, 'Hosts') if not table.ok: IO.error( 'table does not fit terminal. resize or decrease font size.') return IO.spacer() IO.print(table.table) IO.spacer()
def _limit_handler(self, args): """ Handles 'limit' command-line argument Limits bandwith of host to specified rate """ hosts = self._get_hosts_by_ids(args.id) rate = NetRate(args.rate) direction = self._parse_direction_args(args) if not rate.is_valid(): IO.error('limit rate is invalid.') return if hosts is not None and len(hosts) > 0: for host in hosts: if not host.spoofed: self.arp_spoofer.add(host) self.limiter.limit(host, direction, rate) IO.ok('{}{}{r} {} {}limited{r} to {}.'.format( IO.Fore.LIGHTYELLOW_EX, host.ip, Direction.pretty_direction(direction), IO.Fore.LIGHTRED_EX, rate, r=IO.Style.RESET_ALL))
def _scan_handler(self, args): """ Handles 'scan' command-line argument (Re)scans for hosts on the network """ if args.iprange: try: if '-' in args.iprange: iprange = list( netaddr.iter_iprange(*args.iprange.split('-'))) else: iprange = list(netaddr.IPNetwork(args.iprange)) except netaddr.core.AddrFormatError: IO.error('ip range invalid.') return else: iprange = None for host in self.hosts: self._free_host(host) IO.spacer() self.hosts = self.host_scanner.scan(iprange) IO.ok('{}{}{} hosts discovered.'.format(IO.Fore.LIGHTYELLOW_EX, len(self.hosts), IO.Style.RESET_ALL)) IO.spacer()
def process_arguments(args): """ Processes the specified command-line arguments, adds them to a named tuple and returns. Executes actions specified in the command line, e.g. flush network settings """ if args.interface is None: interface = netutils.get_default_interface() if interface is None: IO.error('default interface could not be resolved. specify manually (-i).') return else: interface = args.interface if not netutils.exists_interface(interface): IO.error('interface {}{}{} does not exist.'.format(IO.Fore.LIGHTYELLOW_EX, interface, IO.Style.RESET_ALL)) return IO.ok('interface: {}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, interface, IO.Style.RESET_ALL)) if args.gateway is None: gateway_ip = netutils.get_default_gateway() if gateway_ip is None: IO.error('default gateway address could not be resolved. specify manually (-g).') return else: gateway_ip = args.gateway IO.ok('gateway ip: {}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, gateway_ip, IO.Style.RESET_ALL)) gateway_mac = netutils.get_mac_by_ip(interface, gateway_ip) if gateway_mac is None: IO.error('gateway mac address could not be resolved.') return IO.ok('gateway mac: {}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, gateway_mac, IO.Style.RESET_ALL)) if args.netmask is None: netmask = netutils.get_default_netmask(interface) if netmask is None: IO.error('netmask could not be resolved. specify manually (-n).') return else: netmask = args.netmask IO.ok('netmask: {}{}{}'.format(IO.Fore.LIGHTYELLOW_EX, netmask, IO.Style.RESET_ALL)) if args.flush: netutils.flush_network_settings(interface) IO.spacer() IO.ok('flushed network settings') return InitialArguments(interface=interface, gateway_ip=gateway_ip, gateway_mac=gateway_mac, netmask=netmask)
def _add_handler(self, args): """ Handles 'add' command-line argument Adds custom host to host list """ ip = args.ip if not netutils.validate_ip_address(ip): IO.error('invalid ip address.') return if args.mac: mac = args.mac if not netutils.validate_mac_address(mac): IO.error('invalid mac address.') return else: mac = netutils.get_mac_by_ip(self.interface, ip) if mac is None: IO.error('unable to resolve mac address. specify manually (--mac).') return name = None try: host_info = socket.gethostbyaddr(ip) name = None if host_info is None else host_info[0] except socket.herror: pass host = Host(ip, mac, name) if host in self.hosts: IO.error('host does already exist.') return self.hosts.append(host) IO.ok('host added.')
def _get_host_by_id(self, id_): try: identifier = int(id_) except ValueError: IO.error('identifier is not an integer.') return if len(self.hosts) == 0 or identifier not in range(len(self.hosts)): IO.error('no host with id {}{}{}.'.format(IO.Fore.LIGHTYELLOW_EX, identifier, IO.Style.RESET_ALL)) return return self.hosts[identifier]
def initialize(interface): """ Sets up requirements, e.g. IP-Forwarding, 3rd party applications """ if not netutils.create_qdisc_root(interface): IO.spacer() IO.error('qdisc root handle could not be created. maybe flush network settings (--flush).') return False if not netutils.enable_ip_forwarding(): IO.spacer() IO.error('ip forwarding could not be enabled.') return False return True
def _limit_handler(self, args): """ Handles 'limit' command-line argument Limits bandwith of host to specified rate """ hosts = self._get_hosts_by_ids(args.id) rate = args.rate if hosts is not None and len(hosts) > 0: for host in hosts: if not host.spoofed: self.arp_spoofer.add(host) if netutils.validate_netrate_string(rate): self.limiter.limit(host, rate) else: IO.error('limit rate is invalid.') return IO.ok('{}{}{} limited{} to {}.'.format(IO.Fore.LIGHTYELLOW_EX, host.ip, IO.Fore.LIGHTRED_EX, IO.Style.RESET_ALL, rate))
def _get_hosts_by_ids(self, ids_string): if ids_string == 'all': return self.hosts.copy() try: ids = [int(x) for x in ids_string.split(',')] except ValueError: IO.error('\'{}\' are invalid IDs.'.format(ids_string)) return hosts = [] for id_ in ids: if len(self.hosts) == 0 or id_ not in range(len(self.hosts)): IO.error('no host with id {}{}{}.'.format( IO.Fore.LIGHTYELLOW_EX, id_, IO.Style.RESET_ALL)) return if self.hosts[id_] not in hosts: hosts.append(self.hosts[id_]) return hosts
def _get_hosts_by_ids(self, ids_string): if ids_string == 'all': return self.hosts.copy() ids = ids_string.split(',') hosts = set() for id_ in ids: is_mac = netutils.validate_mac_address(id_) is_ip = netutils.validate_ip_address(id_) is_id_ = id_.isdigit() if not is_mac and not is_ip and not is_id_: IO.error('invalid identifier(s): \'{}\'.'.format(ids_string)) return if is_mac or is_ip: found = False for host in self.hosts: if host.mac == id_.lower() or host.ip == id_: found = True hosts.add(host) break if not found: IO.error('no host matching {}{}{}.'.format(IO.Fore.LIGHTYELLOW_EX, id_, IO.Style.RESET_ALL)) return else: id_ = int(id_) if len(self.hosts) == 0 or id_ not in range(len(self.hosts)): IO.error('no host with id {}{}{}.'.format(IO.Fore.LIGHTYELLOW_EX, id_, IO.Style.RESET_ALL)) return hosts.add(self.hosts[id_]) return hosts
def _watch_set_handler(self, args): """ Handles 'watch set' command-line argument Modifies settings of the reconnection reconnection watcher """ if args.attribute.lower() in ('range', 'iprange', 'ip_range'): iprange = self._parse_iprange(args.value) if iprange is not None: self.host_watcher.iprange = iprange else: IO.error('invalid ip range.') elif args.attribute.lower() in ('interval'): if args.value.isdigit(): self.host_watcher.interval = int(args.value) else: IO.error('invalid interval.') else: IO.error('{}{}{} is an invalid settings attribute.'.format( IO.Fore.LIGHTYELLOW_EX, args.attribute, IO.Style.RESET_ALL))
def parse(self, command): """ Parses a given list of arguments """ names = [ x.name for x in (self._flag_commands + self._parameter_commands) ] result_dict = dict.fromkeys(names, None) # indicates whether or not to skip the next command argument skip_next = False for i, arg in enumerate(command): if skip_next: skip_next = False continue if i == 0: # check if the first argument is a subparser for sp in self._subparsers: if sp.identifier == arg: # if subparser present, parse arguments there result = sp.subparser.parse(command[(i + 1):]) if result is not None and sp.handler is not None: # call the subparser's handler if available sp.handler(result) return result # indicates whether or not the argument has been processed is_arg_processed = False for cmd in self._flag_commands: if cmd.identifier == arg: if cmd.type == CommandParser.CommandType.FLAG_COMMAND: # if its a flag, set the flag to true result_dict[cmd.name] = True is_arg_processed = True break elif cmd.type == CommandParser.CommandType.PARAMETERIZED_FLAG_COMMAND: if (len(command) - 1) < (i + 1): # no more command arguments to process IO.error( 'parameter for flag {}{}{} is missing'.format( IO.Fore.LIGHTYELLOW_EX, cmd.name, IO.Style.RESET_ALL)) return # if parameterized flag, set value to next argument value = command[i + 1] result_dict[cmd.name] = value # skip the next argument (already processed) skip_next = True is_arg_processed = True break if not is_arg_processed: for cmd in self._parameter_commands: # parameter command, since a flag could not be found if result_dict[cmd.name] is None: # set parameter value result_dict[cmd.name] = arg is_arg_processed = True break if not is_arg_processed: IO.error('{}{}{} is an unknown command.'.format( IO.Fore.LIGHTYELLOW_EX, arg, IO.Style.RESET_ALL)) return # check if there are any parameters missing for cmd in self._parameter_commands: if result_dict[cmd.name] is None: IO.error('parameter {}{}{} is missing'.format( IO.Fore.LIGHTYELLOW_EX, cmd.name, IO.Style.RESET_ALL)) return # set unspecified flags to False instead of None for cmd in self._flag_commands: if cmd.type == CommandParser.CommandType.FLAG_COMMAND: if result_dict[cmd.name] is None: result_dict[cmd.name] = False result_tuple = collections.namedtuple('ParseResult', sorted(result_dict)) return result_tuple(**result_dict)
def _auto_handler(self, args): def get_bandwidth_results(): with self.hosts_lock: return [ x for x in [(y, self.bandwidth_monitor.get(y)) for y in self.hosts] if x[1] is not None ] def display(stdscr, interval): host_results = get_bandwidth_results() hname_max_len = max([len(x[0].name) for x in host_results]) header_off = [('ID', 5), ('IP address', 18), ('Hostname', hname_max_len + 2), ('Status', 8), ('Current (per s)', 20), ('Total', 16), ('Packets', 0)] y_rst = 1 x_rst = 2 while True: y_off = y_rst x_off = x_rst stdscr.clear() for header in header_off: stdscr.addstr(y_off, x_off, header[0]) x_off += header[1] y_off += 2 x_off = x_rst for host, result in host_results: if not host.limited and int( ValueConverter.byte_to_bit( result.download_total_size.value) ) > max_bit.rate: IO.discord("""``` IP: {} MAC: {} NAME: {} Dilimit menjadi {} karena melebihi batas maksimum {}``` """.format(host.ip, host.mac, host.name, rate, max_bit)) self.limiter.limit(host, 3, rate) result_data = [ str(self._get_host_id(host)), host.ip, host.name, "Limited" if host.limited else "Free", '{}↑ {}↓'.format(result.upload_rate, result.download_rate), '{}↑ {}↓'.format(result.upload_total_size, result.download_total_size), '{}↑ {}↓'.format(result.upload_total_count, result.download_total_count) ] for j, string in enumerate(result_data): stdscr.addstr(y_off, x_off, string) x_off += header_off[j][1] y_off += 1 x_off = x_rst y_off += 2 stdscr.addstr(y_off, x_off, 'press \'ctrl+c\' to exit.') try: stdscr.refresh() time.sleep(interval) host_results = get_bandwidth_results() except KeyboardInterrupt: IO.discord("Monitoring dihentikan...") for host in hosts: self._free_host(host) return hosts = self._get_hosts_by_ids("all") if hosts is not None and len(hosts) > 0: for host in hosts: self._free_host(host) self.arp_spoofer.add(host) self.bandwidth_monitor.add(host) self.host_watcher.add(host) else: IO.error("no hosts to be monitored") return try: max_bit = BitRate.from_rate_string(args.maximum) rate = BitRate.from_rate_string(args.rate) except Exception: IO.error('maximum bit is invalid.') return interval = 0.5 # in s if args.interval: if not args.interval.isdigit(): IO.error('invalid interval.') return interval = int(args.interval) / 1000 # from ms to s try: IO.discord( "Melakukan monitoring pada sistem...\nLimit host menjadi **{}** jika melebihi batas maksimum **{}**" .format(args.rate, args.maximum)) curses.wrapper(display, interval) except curses.error: IO.error('monitor error occurred. maybe terminal too small?')
def _analyze_handler(self, args): hosts = self._get_hosts_by_ids(args.id) if hosts is None or len(hosts) == 0: IO.error('no hosts to be analyzed.') return duration = 30 # in s if args.duration: if not args.duration.isdigit(): IO.error('invalid duration.') return duration = int(args.duration) hosts_to_be_freed = set() host_values = {} for host in hosts: if not host.spoofed: hosts_to_be_freed.add(host) self.arp_spoofer.add(host) self.bandwidth_monitor.add(host) host_result = self.bandwidth_monitor.get(host) host_values[host] = {} host_values[host]['prev'] = (host_result.upload_total_size, host_result.download_total_size) IO.ok('analyzing traffic for {}s.'.format(duration)) time.sleep(duration) error_occurred = False for host in hosts: host_result = self.bandwidth_monitor.get(host) if host_result is None: # host reconnected during analysis IO.error('host reconnected during analysis.') error_occurred = True else: host_values[host]['current'] = ( host_result.upload_total_size, host_result.download_total_size) IO.ok('cleaning up...') for host in hosts_to_be_freed: self._free_host(host) if error_occurred: return upload_chart = BarChart(max_bar_length=29) download_chart = BarChart(max_bar_length=29) for host in hosts: upload_value = host_values[host]['current'][0] - host_values[host][ 'prev'][0] download_value = host_values[host]['current'][1] - host_values[ host]['prev'][1] prefix = '{}{}{} ({}, {})'.format(IO.Fore.LIGHTYELLOW_EX, self._get_host_id(host), IO.Style.RESET_ALL, host.ip, host.name) upload_chart.add_value(upload_value.value, prefix, upload_value) download_chart.add_value(download_value.value, prefix, download_value) upload_table = SingleTable([[upload_chart.get()]], 'Upload') download_table = SingleTable([[download_chart.get()]], 'Download') upload_table.inner_heading_row_border = False download_table.inner_heading_row_border = False IO.spacer() IO.print(upload_table.table) IO.print(download_table.table) IO.spacer()
def locate_bin(name): try: return output_suppressed('which {}'.format(name)).replace('\n', '') except subprocess.CalledProcessError: IO.error('missing util: {}, check your PATH'.format(name))
def _monitor_handler(self, args): """ Handles 'monitor' command-line argument Monitors hosts bandwidth usage """ def get_bandwidth_results(): return [ x for x in [(y, self.bandwidth_monitor.get(y)) for y in self.hosts] if x[1] is not None ] def display(stdscr, interval): host_results = get_bandwidth_results() hname_max_len = max([len(x[0].name) for x in host_results]) header_off = [('ID', 5), ('IP-Address', 18), ('Hostname', hname_max_len + 2), ('Current (per s)', 20), ('Total', 16), ('Packets', 0)] y_rst = 1 x_rst = 2 while True: y_off = y_rst x_off = x_rst stdscr.clear() for header in header_off: stdscr.addstr(y_off, x_off, header[0]) x_off += header[1] y_off += 2 x_off = x_rst for i, (host, result) in enumerate(host_results): result_data = [ str(i), host.ip, host.name, '{}↑ {}↓'.format(result.upload_rate, result.download_rate), '{}↑ {}↓'.format(result.upload_total_size, result.download_total_size), '{}↑ {}↓'.format(result.upload_total_count, result.download_total_count) ] for j, string in enumerate(result_data): stdscr.addstr(y_off, x_off, string) x_off += header_off[j][1] y_off += 1 x_off = x_rst y_off += 2 stdscr.addstr(y_off, x_off, 'press \'ctrl+c\' to exit.') try: stdscr.refresh() time.sleep(interval) host_results = get_bandwidth_results() except KeyboardInterrupt: return interval = 0.5 # in s if args.interval: if not args.interval.isdigit(): IO.error('invalid interval.') return interval = int(args.interval) / 1000 # from ms to s if len(get_bandwidth_results()) == 0: IO.error('no hosts to be monitored.') return try: curses.wrapper(display, interval) except curses.error: IO.error('monitor error occurred. maybe terminal too small?')