def main(): parser = argparse.ArgumentParser() parser.add_argument('--ctrlport', type=int, help='default: 9051', default=9051) args = parser.parse_args() with Controller.from_port(port=args.ctrlport) as controller: controller.authenticate() for desc in parse_file('/var/lib/tor/data/cached-consensus'): ip = 'v4' if is_valid_ipv4_address(desc.address) else 'v6' desc_versions[desc.fingerprint] = [ desc.address, desc.or_port, ip, desc.version ] for desc in parse_file('/var/lib/tor/data2/cached-consensus'): ip = 'v4' if is_valid_ipv4_address(desc.address) else 'v6' desc_versions[desc.fingerprint] = [ desc.address, desc.or_port, ip, desc.version ] orconn_listener = functools.partial(orconn_event, controller) controller.add_event_listener(orconn_listener, EventType.ORCONN) while True: try: time.sleep(1) except KeyboardInterrupt: break
def from_remote(timeout = 60): """ Reads and parses tor's latest fallback directories `from gitweb.torproject.org <https://gitweb.torproject.org/tor.git/plain/src/or/fallback_dirs.inc>`_. Note that while convenient, this reliance on GitWeb means you should alway call with a fallback, such as... :: try: fallback_directories = stem.descriptor.remote.from_remote() except IOError: fallback_directories = stem.descriptor.remote.from_cache() :param int timeout: seconds to wait before timing out the request :returns: **dict** of **str** fingerprints to their :class:`~stem.descriptor.remote.FallbackDirectory` :raises: **IOError** if unable to retrieve the fallback directories """ try: fallback_dir_page = str_tools._to_unicode(urllib.urlopen(GITWEB_FALLBACK_DIR_URL, timeout = timeout).read()) except: exc = sys.exc_info()[1] raise IOError("Unable to download tor's fallback directories from %s: %s" % (GITWEB_FALLBACK_DIR_URL, exc)) # Example of an entry... # # "5.175.233.86:80 orport=443 id=5525D0429BFE5DC4F1B0E9DE47A4CFA169661E33" # " weight=43680", results = {} for line in fallback_dir_page.splitlines(): if line.startswith('"'): addr_line_match = re.match('"([\d\.]+):(\d+) orport=(\d+) id=([\dA-F]{40}).*', line) if addr_line_match: address, dir_port, or_port, fingerprint = addr_line_match.groups() if not connection.is_valid_ipv4_address(address): raise IOError('%s has an invalid address: %s' % (fingerprint, address)) elif not connection.is_valid_port(or_port): raise IOError('%s has an invalid or_port: %s' % (fingerprint, or_port)) elif not connection.is_valid_port(dir_port): raise IOError('%s has an invalid dir_port: %s' % (fingerprint, dir_port)) elif not tor_tools.is_valid_fingerprint(fingerprint): raise IOError('%s has an invalid fingerprint: %s' % (fingerprint, fingerprint)) results[fingerprint] = FallbackDirectory( address = address, or_port = int(or_port), dir_port = int(dir_port), fingerprint = fingerprint, ) return results
def orconn_event(controller, relays, event): if event.status == ORStatus.CLOSED: fingerprint = event.endpoint_fingerprint print ("%-12s %s" % (event.reason, fingerprint), end='') relay = controller.get_network_status(fingerprint, None) if (relay): if is_valid_ipv4_address(relay.address): ip = 'v4' else: ip = 'v6' version = relay.version if version == None: try: desc = controller.get_server_descriptor(fingerprint) version = desc.tor_version except Exception as Exc: version = 'error' print (" %15s %5i %s %s" % (relay.address, relay.or_port, ip, version)) else: print ('', flush=True)
def __init__(self, address: str, or_port: Union[int, str], dir_port: Union[int, str], fingerprint: str, nickname: str, orport_v6: Tuple[str, int]) -> None: identifier = '%s (%s)' % (fingerprint, nickname) if nickname else fingerprint if not connection.is_valid_ipv4_address(address): raise ValueError('%s has an invalid IPv4 address: %s' % (identifier, address)) elif not connection.is_valid_port(or_port): raise ValueError('%s has an invalid ORPort: %s' % (identifier, or_port)) elif not connection.is_valid_port(dir_port): raise ValueError('%s has an invalid DirPort: %s' % (identifier, dir_port)) elif not tor_tools.is_valid_fingerprint(fingerprint): raise ValueError('%s has an invalid fingerprint: %s' % (identifier, fingerprint)) elif nickname and not tor_tools.is_valid_nickname(nickname): raise ValueError('%s has an invalid nickname: %s' % (fingerprint, nickname)) if orport_v6: if not isinstance(orport_v6, tuple) or len(orport_v6) != 2: raise ValueError('%s orport_v6 should be a two value tuple: %s' % (identifier, str(orport_v6))) elif not connection.is_valid_ipv6_address(orport_v6[0]): raise ValueError('%s has an invalid IPv6 address: %s' % (identifier, orport_v6[0])) elif not connection.is_valid_port(orport_v6[1]): raise ValueError('%s has an invalid IPv6 port: %s' % (identifier, orport_v6[1])) self.address = address self.or_port = int(or_port) self.dir_port = int(dir_port) self.fingerprint = fingerprint self.nickname = nickname self.orport_v6 = (orport_v6[0], int(orport_v6[1])) if orport_v6 else None
def exits_can_exit_to(self, host, port): ''' Return exits that can MOST LIKELY exit to the given host:port. **host** can be a hostname, but be warned that we will resolve it locally and use the first (arbitrary/unknown order) result when checking exit policies, which is different than what other parts of the code may do (leaving it up to the exit to resolve the name). An exit can only MOST LIKELY not just because of the above DNS disconnect, but also because fundamentally our Tor client is most likely using microdescriptors which do not have full information about exit policies. ''' c = self._controller if not is_valid_ipv4_address(host) and not is_valid_ipv6_address(host): # It certainly isn't perfect trying to guess if an exit can connect # to an ipv4/6 address based on the DNS result we got locally. But # it's the best we can do. # # Also, only use the first ipv4/6 we get even if there is more than # one. host = resolve(host)[0] assert is_valid_ipv4_address(host) or is_valid_ipv6_address(host) exits = [] for exit in self.exits: # If we have the exit policy already, easy if exit.exit_policy: policy = exit.exit_policy else: # Otherwise ask Tor for the microdescriptor and assume the exit # won't work if the desc isn't available try: fp = exit.fingerprint policy = c.get_microdescriptor(fp).exit_policy except DescriptorUnavailable as e: log.debug(e) continue # There's a weird KeyError we sometimes hit when checking # policy.can_exit_to()... so catch that and log about it. Maybe # someday it can be fixed? try: if policy is not None and policy.can_exit_to(port=port): exits.append(exit) except KeyError as e: log.exception('Got that KeyError in stem again...: %s', e) continue return exits
def connections(): global controller args = StaticInfo.args control_port = StaticInfo.control_port controller = StaticInfo.controller if not (controller.is_alive()): return desc = controller.get_network_status(default = None) pid = StaticInfo.pid policy = controller.get_exit_policy() relays = {} # address => [orports...] for desc in controller.get_network_statuses(): relays.setdefault(desc.address, []).append(desc.or_port) exit_connections = {} # port => [connections] for conn in get_connections(resolver = args.resolver, process_pid = pid): global categories if conn.protocol == 'udp': continue if conn.local_port in controller.get_ports(Listener.OR, []): categories['INBOUND_OR'].append(conn) elif conn.local_port in controller.get_ports(Listener.DIR, []): categories['INBOUND_DIR'].append(conn) elif conn.local_port in controller.get_ports(Listener.CONTROL, []): categories['INBOUND_CONTROL'].append(conn) elif conn.remote_port in relays.get(conn.remote_address, []): categories['OUTBOUND'].append(conn) elif policy.can_exit_to(conn.remote_address, conn.remote_port): categories['EXIT'].append(conn) exit_connections.setdefault(conn.remote_port, []).append(conn) else: categories['OUTBOUND'].append(conn) circ = controller.get_circuits([]) for conn in circ: categories['CIRCUIT'].append(conn) if exit_connections: total_ipv4, total_ipv6 = 0, 0 for port in sorted(exit_connections): connections = exit_connections[port] ipv4_count = len([conn for conn in connections if is_valid_ipv4_address(conn.remote_address)]) ipv6_count = len(connections) - ipv4_count total_count = len(connections) total_ipv4, total_ipv6 = total_ipv4 + ipv4_count, total_ipv6 + ipv6_count usage = port_usage(port) label = '%s (%s)' % (port, usage) if usage else port
def from_cache(): """ Provides fallback directory information cached with Stem. Unlike :func:`~stem.descriptor.remote.FallbackDirectory.from_remote` this doesn't have any system requirements, and is faster too. Only drawback is that these fallback directories are only as up to date as the Stem release we're using. :returns: **dict** of **str** fingerprints to their :class:`~stem.descriptor.remote.FallbackDirectory` """ conf = stem.util.conf.Config() conf.load(CACHE_PATH) results = {} for fingerprint in set([key.split('.')[0] for key in conf.keys()]): if fingerprint in ('tor_commit', 'stem_commit'): continue attr = {} for attr_name in ('address', 'or_port', 'dir_port', 'orport6_address', 'orport6_port'): key = '%s.%s' % (fingerprint, attr_name) attr[attr_name] = conf.get(key) if not attr[attr_name] and not attr_name.startswith('orport6_'): raise IOError("'%s' is missing from %s" % (key, CACHE_PATH)) if not connection.is_valid_ipv4_address(attr['address']): raise IOError("'%s.address' was an invalid IPv4 address (%s)" % (fingerprint, attr['address'])) elif not connection.is_valid_port(attr['or_port']): raise IOError("'%s.or_port' was an invalid port (%s)" % (fingerprint, attr['or_port'])) elif not connection.is_valid_port(attr['dir_port']): raise IOError("'%s.dir_port' was an invalid port (%s)" % (fingerprint, attr['dir_port'])) elif attr['orport6_address'] and not connection.is_valid_ipv6_address(attr['orport6_address']): raise IOError("'%s.orport6_address' was an invalid IPv6 address (%s)" % (fingerprint, attr['orport6_address'])) elif attr['orport6_port'] and not connection.is_valid_port(attr['orport6_port']): raise IOError("'%s.orport6_port' was an invalid port (%s)" % (fingerprint, attr['orport6_port'])) if attr['orport6_address'] and attr['orport6_port']: orport_v6 = (attr['orport6_address'], int(attr['orport6_port'])) else: orport_v6 = None results[fingerprint] = FallbackDirectory( address = attr['address'], or_port = int(attr['or_port']), dir_port = int(attr['dir_port']), fingerprint = fingerprint, orport_v6 = orport_v6, ) return results
def from_cache(): """ Provides fallback directory information cached with Stem. Unlike :func:`~stem.descriptor.remote.FallbackDirectory.from_remote` this doesn't have any system requirements, and is faster too. Only drawback is that these fallback directories are only as up to date as the Stem release we're using. :returns: **dict** of **str** fingerprints to their :class:`~stem.descriptor.remote.FallbackDirectory` """ conf = stem.util.conf.Config() conf.load(CACHE_PATH) results = {} for fingerprint in set([key.split('.')[0] for key in conf.keys()]): if fingerprint in ('tor_commit', 'stem_commit'): continue attr = {} for attr_name in ('address', 'or_port', 'dir_port', 'orport6_address', 'orport6_port'): key = '%s.%s' % (fingerprint, attr_name) attr[attr_name] = conf.get(key) if not attr[attr_name] and not attr_name.startswith('orport6_'): raise IOError("'%s' is missing from %s" % (key, CACHE_PATH)) if not connection.is_valid_ipv4_address(attr['address']): raise IOError("'%s.address' was an invalid IPv4 address (%s)" % (fingerprint, attr['address'])) elif not connection.is_valid_port(attr['or_port']): raise IOError("'%s.or_port' was an invalid port (%s)" % (fingerprint, attr['or_port'])) elif not connection.is_valid_port(attr['dir_port']): raise IOError("'%s.dir_port' was an invalid port (%s)" % (fingerprint, attr['dir_port'])) elif attr['orport6_address'] and not connection.is_valid_ipv6_address(attr['orport6_address']): raise IOError("'%s.orport6_address' was an invalid IPv6 address (%s)" % (fingerprint, attr['orport6_address'])) elif attr['orport6_port'] and not connection.is_valid_port(attr['orport6_port']): raise IOError("'%s.orport6_port' was an invalid port (%s)" % (fingerprint, attr['orport6_port'])) if attr['orport6_address'] and attr['orport6_port']: orport_v6 = (attr['orport6_address'], int(attr['orport6_port'])) else: orport_v6 = None results[fingerprint] = FallbackDirectory( address = attr['address'], or_port = int(attr['or_port']), dir_port = int(attr['dir_port']), fingerprint = fingerprint, orport_v6 = orport_v6, ) return results
def _parse(self): if self.type not in ('server', 'client'): raise stem.ProtocolError("Transport type should either be 'server' or 'client': %s" % self) if not connection.is_valid_ipv4_address(self.address) and \ not connection.is_valid_ipv6_address(self.address): raise stem.ProtocolError("Transport address isn't a valid IPv4 or IPv6 address: %s" % self) if not connection.is_valid_port(self.port): raise stem.ProtocolError('Transport port is invalid: %s' % self) self.port = int(self.port)
def can_exit_to(self, host, port): ''' Returns if this relay can MOST LIKELY exit to the given host:port. **host** can be a hostname, but be warned that we will resolve it locally and use the first (arbitrary/unknown order) result when checking exit policies, which is different than what other parts of the code may do (leaving it up to the exit to resolve the name). ''' if not self.exit_policy: return False assert isinstance(host, str) assert isinstance(port, int) if not is_valid_ipv4_address(host) and not is_valid_ipv6_address(host): # It certainly isn't perfect trying to guess if an exit can connect # to an ipv4/6 address based on the DNS result we got locally. But # it's the best we can do. # # Also, only use the first ipv4/6 we get even if there is more than # one. host = resolve(host)[0] assert is_valid_ipv4_address(host) or is_valid_ipv6_address(host) return self.exit_policy.can_exit_to(host, port)
def _parse(self): if self.type not in ('server', 'client'): raise stem.ProtocolError( "Transport type should either be 'server' or 'client': %s" % self) if not connection.is_valid_ipv4_address(self.address) and \ not connection.is_valid_ipv6_address(self.address): raise stem.ProtocolError( "Transport address isn't a valid IPv4 or IPv6 address: %s" % self) if not connection.is_valid_port(self.port): raise stem.ProtocolError('Transport port is invalid: %s' % self) self.port = int(self.port)
def from_remote(timeout=60): """ Reads and parses tor's latest fallback directories `from gitweb.torproject.org <https://gitweb.torproject.org/tor.git/plain/src/or/fallback_dirs.inc>`_. Note that while convenient, this reliance on GitWeb means you should alway call with a fallback, such as... :: try: fallback_directories = stem.descriptor.remote.from_remote() except IOError: fallback_directories = stem.descriptor.remote.from_cache() :param int timeout: seconds to wait before timing out the request :returns: **dict** of **str** fingerprints to their :class:`~stem.descriptor.remote.FallbackDirectory` :raises: **IOError** if unable to retrieve the fallback directories """ try: fallback_dir_page = str_tools._to_unicode( urllib.urlopen(GITWEB_FALLBACK_DIR_URL, timeout=timeout).read()) except: exc = sys.exc_info()[1] raise IOError( "Unable to download tor's fallback directories from %s: %s" % (GITWEB_FALLBACK_DIR_URL, exc)) # Example of an entry... # # "5.175.233.86:80 orport=443 id=5525D0429BFE5DC4F1B0E9DE47A4CFA169661E33" # " ipv6=[2a03:b0c0:0:1010::a4:b001]:9001" # " weight=43680", results, attr = {}, {} for line in fallback_dir_page.splitlines(): if line.startswith('"'): addr_line_match = re.match( '"([\d\.]+):(\d+) orport=(\d+) id=([\dA-F]{40}).*', line) ipv6_line_match = re.match('" ipv6=\[([\da-f:]+)\]:(\d+)"', line) if addr_line_match: address, dir_port, or_port, fingerprint = addr_line_match.groups( ) if not connection.is_valid_ipv4_address(address): raise IOError('%s has an invalid IPv4 address: %s' % (fingerprint, address)) elif not connection.is_valid_port(or_port): raise IOError('%s has an invalid or_port: %s' % (fingerprint, or_port)) elif not connection.is_valid_port(dir_port): raise IOError('%s has an invalid dir_port: %s' % (fingerprint, dir_port)) elif not tor_tools.is_valid_fingerprint(fingerprint): raise IOError('%s has an invalid fingerprint: %s' % (fingerprint, fingerprint)) attr = { 'address': address, 'or_port': int(or_port), 'dir_port': int(dir_port), 'fingerprint': fingerprint, } elif ipv6_line_match: address, port = ipv6_line_match.groups() if not connection.is_valid_ipv6_address(address): raise IOError('%s has an invalid IPv6 address: %s' % (fingerprint, address)) elif not connection.is_valid_port(port): raise IOError( '%s has an invalid ORPort for its IPv6 endpoint: %s' % (fingerprint, port)) attr['orport_v6'] = (address, int(port)) elif line.startswith('" weight=') and 'fingerprint' in attr: results[attr.get('fingerprint')] = FallbackDirectory( address=attr.get('address'), or_port=attr.get('or_port'), dir_port=attr.get('dir_port'), fingerprint=attr.get('fingerprint'), orport_v6=attr.get('orport_v6'), ) attr = {} return results
def main(): ctrlport = 9051 resolver = 'proc' parser = argparse.ArgumentParser() parser.add_argument("--ctrlport", help="default: " + str(ctrlport)) parser.add_argument("--resolver", help="default: " + resolver) args = parser.parse_args() if args.ctrlport: ctrlport = int(args.ctrlport) if args.resolver: resolver= str(args.resolver) with Controller.from_port(port=ctrlport) as controller: controller.authenticate() try: ControlPort = int(controller.get_conf("ControlPort")) ORPort = None ORPort6 = None DirPort = None DirPort6 = None for address, port in controller.get_listeners(Listener.OR): if is_valid_ipv4_address(address): ORPort = port else: ORPort6 = port for address, port in controller.get_listeners(Listener.DIR): if is_valid_ipv4_address(address): DirPort = port else: DirPort6 = port except Exception as Exc: print ("Woops, control ports aren't configured") print (Exc) return # we will ignore changes of relays during the runtime of this script # relays = {} for s in controller.get_network_statuses(): relays.setdefault(s.address, []).append(s.or_port) MaxOpened = {} # hold the maximum amount of opened ports MaxClosed = {} # hold the maximum amount of closed ports MaxAll = {} # hold the maximum amount of overall ports Curr = {} # the current network connections of Tor # avoid useless calculation of mean immediately after start # first = 1 while True: # read in all allowed exit ports # exit_ports = [] for filename in glob.glob("/etc/tor/torrc.d/*") + (glob.glob("/etc/tor/*")): if os.path.isfile(filename): inputfile = open(filename) lines = inputfile.readlines() inputfile.close() for line in lines: if line.startswith("ExitPolicy accept "): for word in line.split(): if '*:' in word: # do consider classX ports port = int (word.split(':')[1]) exit_ports.append(port) try: t1 = time.time() Prev = Curr.copy() Curr.clear() pid = controller.get_info("process/pid") connections = get_connections(resolver=resolver, process_pid=pid,process_name='tor') t2 = time.time() policy = controller.get_exit_policy() for conn in connections: laddr, raddr = conn.local_address, conn.remote_address lport, rport = conn.local_port, conn.remote_port # ignore incoming connections # if (lport == ORPort and laddr == '5.9.158.75') or (lport == ORPort6 and laddr == '2a01:4f8:190:514a::2'): continue if (lport == DirPort and laddr == '5.9.158.75') or (lport == DirPort6 and laddr == '2a01:4f8:190:514a::2'): continue if raddr in relays: if rport in relays[raddr]: continue if not policy.can_exit_to(raddr, rport): continue # store the connections itself instead just counting them here # b/c we have to calculate the diff of 2 sets later # Curr.setdefault(rport, []).append(str(lport)+':'+raddr) if first == 1: Prev = Curr.copy() dt = t2-t1 os.system('clear') print (" port # opened closed max ( %s:%s, %i conns %.2f sec ) " % (resolver, ctrlport, len(connections), dt)) lines = 0; ports = set(list(Curr.keys()) + list(Prev.keys()) + list(MaxAll.keys())) for port in sorted(ports): if port in Prev: p = set(Prev[port]) else: p = set({}) if port in Curr: c = set(Curr[port]) else: c = set({}) n_curr = len(c) n_opened = len(c-p) n_closed = len(p-c) MaxAll.setdefault(port, 0) MaxOpened.setdefault(port, 0) MaxClosed.setdefault(port, 0) if first == 0: if MaxAll[port] < n_curr: MaxAll[port] = n_curr if MaxOpened[port] < n_opened: MaxOpened[port] = n_opened if MaxClosed[port] < n_closed: MaxClosed[port] = n_closed stri = " %5i %5i %6i %6i %6i %6i %6i (%s)" % (port, n_curr, n_opened, n_closed, MaxAll[port], MaxOpened[port], MaxClosed[port], port_usage(port)) print (stri.replace(' 0', ' ')) lines += 1 if lines % 5 == 0: print first = 0 except KeyboardInterrupt: break
def main(): ctrlport = 9051 resolver = 'proc' #handler = logging.FileHandler('/tmp/stem_debug') #handler.setFormatter(logging.Formatter( #fmt = '%(asctime)s [%(levelname)s] %(message)s', #datefmt = '%m/%d/%Y %H:%M:%S', #)) #log = stem.util.log.get_logger() #log.setLevel(logging.DEBUG) #log.addHandler(handler) #stem.util.connection.LOG_CONNECTION_RESOLUTION = True parser = argparse.ArgumentParser() parser.add_argument("--ctrlport", help="default: " + str(ctrlport)) parser.add_argument("--resolver", help="default: " + resolver) args = parser.parse_args() if args.ctrlport: ctrlport = int(args.ctrlport) if args.resolver: resolver= str(args.resolver) with Controller.from_port(port=ctrlport) as controller: controller.authenticate() try: ControlPort = int(controller.get_conf("ControlPort")) ORPort = None ORPort6 = None DirPort = None DirPort6 = None for address, port in controller.get_listeners(Listener.OR): if is_valid_ipv4_address(address): ORPort = port else: ORPort6 = port for address, port in controller.get_listeners(Listener.DIR): if is_valid_ipv4_address(address): DirPort = port else: DirPort6 = port except Exception as Exc: print ("Woops, control ports aren't configured") return # our version, uptime and flags # version = str(controller.get_version()).split()[0] uptime = 0 flags = '' try: descriptor = controller.get_server_descriptor() uptime = descriptor.uptime flags = controller.get_network_status(relay=descriptor.fingerprint).flags except Exception as Exc: print (Exc) print (" %s %s %s" % (version, datetime.timedelta(seconds=uptime), " ".join(flags))) policy = controller.get_exit_policy() pid = controller.get_info("process/pid") connections = get_connections(resolver=resolver,process_pid=pid,process_name='tor') print (" resolver=%s pid=%s conns=%i" % (resolver, pid, len(connections))) relaysOr = {} relaysDir = {} for s in controller.get_network_statuses(): relaysOr.setdefault(s.address, []).append(s.or_port) relaysDir.setdefault(s.address, []).append(s.dir_port) # classify network connections by port and relationship to the Tor relay # ports_int = {} ports_ext = {} def inc_ports (ports, t): v4, v6 = ports.get(t,(0,0)) if conn.is_ipv6: ports[t] = (v4, v6+1) else: ports[t] = (v4+1, v6) def inc_ports_int (description): t = (description) inc_ports (ports_int, t) def inc_ports_ext (description): t = (description, rport) inc_ports (ports_ext, t) # classify each connection # for conn in connections: if conn.protocol == 'udp': continue laddr, raddr = conn.local_address, conn.remote_address lport, rport = conn.local_port, conn.remote_port if raddr in relaysOr: if (lport == ORPort and not conn.is_ipv6) or (lport == ORPort6 and conn.is_ipv6): inc_ports_int('ORPort <= relay') elif (lport == DirPort and not conn.is_ipv6) or (lport == DirPort6 and conn.is_ipv6): inc_ports_int('DirPort <= relay') elif rport in relaysOr[raddr]: inc_ports_int('=> relay ORPort') elif rport in relaysDir[raddr]: inc_ports_int('=> relay DirPort') else: # a system hosts beside a Tor relay another service too # inc_ports_ext ('=> relay port') elif policy.can_exit_to(raddr, rport): inc_ports_ext ('=> exit') else: if (lport == ORPort and not conn.is_ipv6) or (lport == ORPort6 and conn.is_ipv6): inc_ports_int('ORPort <= outer') elif (lport == DirPort and not conn.is_ipv6) or (lport == DirPort6 and conn.is_ipv6): inc_ports_int('DirPort <= outer') elif lport == ControlPort: inc_ports_int('CtrlPort <= local') else: inc_ports_ext ('=> non exit port') print () print (' description port ipv4 ipv6 servicename') print (' ----------------- ----- ---- ---- -------------') sum4 = 0 sum6 = 0 for t in sorted(ports_int): description = t v4, v6 = ports_int[t] sum4 += v4 sum6 += v6 print (" %-17s %5s %5s %5s" % (description, '', str(v4) if v4 > 0 else '', str(v6) if v6 > 0 else '')) print ("") exit4 = 0 exit6 = 0 for t in sorted(ports_ext): description, port = t v4, v6 = ports_ext[t] sum4 += v4 sum6 += v6 if description == '=> exit': exit4 += v4 exit6 += v6 print (" %-17s %5i %5s %5s %s" % (description, port, str(v4) if v4 > 0 else '', str(v6) if v6 > 0 else '', port_usage(port))) print ("") print (" %17s %5s %5i %5i" % ('sum', '', sum4, sum6)) print (" %17s %5s %5i %5i" % ('exits among them', '', exit4, exit6))
def main(): parser = argparse.ArgumentParser() parser.add_argument('--ctrlport', type=int, help='default: 9051', default=9051) parser.add_argument('--resolver', help='default: autodetect', default='') args = parser.parse_args() with Controller.from_port(port=args.ctrlport) as controller: controller.authenticate() try: ORPort = None ORPort6 = None for address, port in controller.get_listeners(Listener.OR): if is_valid_ipv4_address(address): ORPort = port else: ORPort6 = port except Exception as Exc: print('Woops, control ports aren\'t configured') print(Exc) return relays = {} # address => [orports...] relays = parse_consensus(relays, '/var/lib/tor/data/cached-consensus') relays = parse_consensus(relays, '/var/lib/tor/data2/cached-consensus') MaxOpened = {} # hold the maximum amount of opened ports MaxClosed = {} # hold the maximum amount of closed ports MaxAll = {} # hold the maximum amount of overall ports Curr = {} # the current network connections of Tor # avoid useless calculation of mean immediately after start # first = True while True: # read in all allowed exit ports # exit_ports = [] for filename in glob.glob('/etc/tor/torrc.d/*') + ( glob.glob('/etc/tor/*')): if os.path.isfile(filename): inputfile = open(filename) lines = inputfile.readlines() inputfile.close() for line in lines: if line.startswith('ExitPolicy *accept '): accept = line.split()[2] if ':' in accept: port = accept.split(':')[1] if '-' in port: min = port.split('-')[0] max = port.split('-')[1] for port in range(int(min), int(max)): exit_ports.append(port) else: exit_ports.append(port) try: t1 = time.time() pid = controller.get_info('process/pid') connections = get_connections(resolver=args.resolver, process_pid=pid, process_name='tor') t2 = time.time() policy = controller.get_exit_policy() if not first: Prev = Curr.copy() Curr.clear() my_ipv4 = '5.9.158.75' my_ipv6 = ipaddress.IPv6Address( '2a01:4f8:190:514a::2').exploded for conn in connections: laddr, raddr = conn.local_address, conn.remote_address lport, rport = conn.local_port, conn.remote_port # ignore incoming connections # if conn.is_ipv6: if lport == ORPort6: if laddr == my_ipv6: continue else: if lport == ORPort: if laddr == my_ipv4: continue if raddr in relays: if rport in relays[raddr]: continue if not policy.can_exit_to(raddr, rport): continue # store the connections itself instead just counting them here # b/c we have to calculate the diff of 2 sets later too # Curr.setdefault(rport, []).append(str(lport) + ':' + raddr) dt = t2 - t1 os.system('clear') print( ' port # opened closed max ( %s:%s, %i conns %.2f sec )' % (args.resolver, args.ctrlport, len(connections), dt)) if first: Prev = Curr.copy() ports = sorted( set( list(Curr.keys()) + list(Prev.keys()) + list(MaxAll.keys()))) for port in ports: c = set({}) p = set({}) if port in Prev: p = set(Prev[port]) if port in Curr: c = set(Curr[port]) n_curr = len(c) n_opened = len(c - p) n_closed = len(p - c) MaxAll.setdefault(port, 0) MaxOpened.setdefault(port, 0) MaxClosed.setdefault(port, 0) if not first: if MaxAll[port] < n_curr: MaxAll[port] = n_curr if MaxOpened[port] < n_opened: MaxOpened[port] = n_opened if MaxClosed[port] < n_closed: MaxClosed[port] = n_closed stri = ' %5i %5i %6i %6i %6i %6i %6i (%s)' % ( port, n_curr, n_opened, n_closed, MaxAll[port], MaxOpened[port], MaxClosed[port], port_usage(port)) print(stri.replace(' 0', ' ')) first = False except KeyboardInterrupt: break except Exception: continue
def _parse_v2(fallback_dir_page): # Example of an entry... # # "5.9.110.236:9030 orport=9001 id=0756B7CD4DFC8182BE23143FAC0642F515182CEB" # " ipv6=[2a01:4f8:162:51e2::2]:9001" # /* nickname=rueckgrat */ # /* extrainfo=1 */ results, attr = {}, {} for line in fallback_dir_page.splitlines(): addr_line_match = re.match( '"([\d\.]+):(\d+) orport=(\d+) id=([\dA-F]{40}).*', line) nickname_match = re.match('/\* nickname=(\S+) \*/', line) has_extrainfo_match = re.match('/\* extrainfo=([0-1]) \*/', line) ipv6_line_match = re.match('" ipv6=\[([\da-f:]+)\]:(\d+)"', line) if addr_line_match: address, dir_port, or_port, fingerprint = addr_line_match.groups( ) if not connection.is_valid_ipv4_address(address): raise IOError('%s has an invalid IPv4 address: %s' % (fingerprint, address)) elif not connection.is_valid_port(or_port): raise IOError('%s has an invalid or_port: %s' % (fingerprint, or_port)) elif not connection.is_valid_port(dir_port): raise IOError('%s has an invalid dir_port: %s' % (fingerprint, dir_port)) elif not tor_tools.is_valid_fingerprint(fingerprint): raise IOError('%s has an invalid fingerprint: %s' % (fingerprint, fingerprint)) attr = { 'address': address, 'or_port': int(or_port), 'dir_port': int(dir_port), 'fingerprint': fingerprint, } elif ipv6_line_match: address, port = ipv6_line_match.groups() if not connection.is_valid_ipv6_address(address): raise IOError('%s has an invalid IPv6 address: %s' % (fingerprint, address)) elif not connection.is_valid_port(port): raise IOError( '%s has an invalid ORPort for its IPv6 endpoint: %s' % (fingerprint, port)) attr['orport_v6'] = (address, int(port)) elif nickname_match: nickname = nickname_match.group(1) if not tor_tools.is_valid_nickname(nickname): raise IOError('%s has an invalid nickname: %s' % (fingerprint, nickname)) attr['nickname'] = nickname elif has_extrainfo_match: attr['has_extrainfo'] = has_extrainfo_match.group(1) == '1' results[attr.get('fingerprint')] = FallbackDirectory( address=attr.get('address'), or_port=attr.get('or_port'), dir_port=attr.get('dir_port'), fingerprint=attr.get('fingerprint'), nickname=attr.get('nickname'), has_extrainfo=attr.get('has_extrainfo', False), orport_v6=attr.get('orport_v6'), ) attr = {} return results
def _parse_v1(fallback_dir_page): # Example of an entry... # # "5.175.233.86:80 orport=443 id=5525D0429BFE5DC4F1B0E9DE47A4CFA169661E33" # " ipv6=[2a03:b0c0:0:1010::a4:b001]:9001" # " weight=43680", # TODO: this method can be removed once gitweb provides a v2 formatted document results, attr = {}, {} for line in fallback_dir_page.splitlines(): if line.startswith('"'): addr_line_match = re.match( '"([\d\.]+):(\d+) orport=(\d+) id=([\dA-F]{40}).*', line) ipv6_line_match = re.match('" ipv6=\[([\da-f:]+)\]:(\d+)"', line) if addr_line_match: address, dir_port, or_port, fingerprint = addr_line_match.groups( ) if not connection.is_valid_ipv4_address(address): raise IOError('%s has an invalid IPv4 address: %s' % (fingerprint, address)) elif not connection.is_valid_port(or_port): raise IOError('%s has an invalid or_port: %s' % (fingerprint, or_port)) elif not connection.is_valid_port(dir_port): raise IOError('%s has an invalid dir_port: %s' % (fingerprint, dir_port)) elif not tor_tools.is_valid_fingerprint(fingerprint): raise IOError('%s has an invalid fingerprint: %s' % (fingerprint, fingerprint)) attr = { 'address': address, 'or_port': int(or_port), 'dir_port': int(dir_port), 'fingerprint': fingerprint, } elif ipv6_line_match: address, port = ipv6_line_match.groups() if not connection.is_valid_ipv6_address(address): raise IOError('%s has an invalid IPv6 address: %s' % (fingerprint, address)) elif not connection.is_valid_port(port): raise IOError( '%s has an invalid ORPort for its IPv6 endpoint: %s' % (fingerprint, port)) attr['orport_v6'] = (address, int(port)) elif line.startswith('" weight=') and 'fingerprint' in attr: results[attr.get('fingerprint')] = FallbackDirectory( address=attr.get('address'), or_port=attr.get('or_port'), dir_port=attr.get('dir_port'), fingerprint=attr.get('fingerprint'), orport_v6=attr.get('orport_v6'), ) attr = {} return results
def main(args=None): parser = argparse.ArgumentParser() parser.add_argument('--ctrlport', help='default: 9051 or 9151') parser.add_argument('--resolver', help='default: autodetected') args = parser.parse_args(args) control_port = int(args.ctrlport) if args.ctrlport else 'default' controller = stem.connection.connect(control_port=('127.0.0.1', control_port)) if not controller: return desc = controller.get_network_status(default=None) pid = controller.get_pid() print( HEADER_LINE.format( version=str(controller.get_version()).split()[0], uptime=stem.util.str_tools.short_time_label( time.time() - stem.util.system.start_time(pid)), flags=', '.join(desc.flags if desc else ['none']), )) policy = controller.get_exit_policy() relays = {} # address => [orports...] for desc in controller.get_network_statuses(): relays.setdefault(desc.address, []).append(desc.or_port) # categorize our connections categories = collections.OrderedDict(( (INBOUND_ORPORT, []), (INBOUND_DIRPORT, []), (INBOUND_CONTROLPORT, []), (OUTBOUND_ORPORT, []), (OUTBOUND_EXIT, []), (OUTBOUND_UNKNOWN, []), )) exit_connections = {} # port => [connections] for conn in get_connections(resolver=args.resolver, process_pid=pid): if conn.protocol == 'udp': continue if conn.local_port in controller.get_ports(Listener.OR, []): categories[INBOUND_ORPORT].append(conn) elif conn.local_port in controller.get_ports(Listener.DIR, []): categories[INBOUND_DIRPORT].append(conn) elif conn.local_port in controller.get_ports(Listener.CONTROL, []): categories[INBOUND_CONTROLPORT].append(conn) elif conn.remote_port in relays.get(conn.remote_address, []): categories[OUTBOUND_ORPORT].append(conn) elif policy.can_exit_to(conn.remote_address, conn.remote_port): categories[OUTBOUND_EXIT].append(conn) exit_connections.setdefault(conn.remote_port, []).append(conn) else: categories[OUTBOUND_UNKNOWN].append(conn) print(DIV) print(COLUMN % ('Type', 'IPv4', 'IPv6')) print(DIV) total_ipv4, total_ipv6 = 0, 0 for label, connections in categories.items(): if len(connections) == 0: continue ipv4_count = len([ conn for conn in connections if is_valid_ipv4_address(conn.remote_address) ]) ipv6_count = len(connections) - ipv4_count total_ipv4, total_ipv6 = total_ipv4 + ipv4_count, total_ipv6 + ipv6_count print(COLUMN % (label, ipv4_count, ipv6_count)) print(DIV) print(COLUMN % ('Total', total_ipv4, total_ipv6)) print(DIV) print('') if exit_connections: print(DIV) print(COLUMN % ('Exit Port', 'IPv4', 'IPv6')) print(DIV) total_ipv4, total_ipv6 = 0, 0 for port in sorted(exit_connections): connections = exit_connections[port] ipv4_count = len([ conn for conn in connections if is_valid_ipv4_address(conn.remote_address) ]) ipv6_count = len(connections) - ipv4_count total_ipv4, total_ipv6 = total_ipv4 + ipv4_count, total_ipv6 + ipv6_count usage = port_usage(port) label = '%s (%s)' % (port, usage) if usage else port print(COLUMN % (label, ipv4_count, ipv6_count)) print(DIV) print(COLUMN % ('Total', total_ipv4, total_ipv6)) print(DIV) print('')