diff --git a/Makefile b/Makefile index 7c55dc7..343a61f 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,8 @@ install:: test:: echo src/${MOD}/check_digests.py TOR_CONTROLLER_PASSWORD=${PASS} src/${MOD}/check_digests.py + echo src/${MOD}/connection_resolution.py + sudo env TOR_CONTROLLER_PASSWORD=${PASS} src/${MOD}/connection_resolution.py # broken because this site fails: http://128.31.0.39:9131/tor/status-vote # ${PYTHON_EXE_MSYS} src/${MOD}/compare_flags.py # cant use from make: waits for the cmdline to to terminate diff --git a/README.md b/README.md index 1201234..b7aad4c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,13 @@ and maatuska, with a special interest in the 'Running' flag. https://stem.torproject.org/tutorials/examples/compare_flags.html +### connection_resolution Connection Resolution + +Connection information is a useful tool for learning more about network +applications like Tor. Our stem.util.connection.get_connections() function +provides an easy method for accessing this information. + + ### exit_used Exit Used Determine The Exit You're Using diff --git a/src/stem_examples/check_digests.py b/src/stem_examples/check_digests.py index 286a378..512d825 100755 --- a/src/stem_examples/check_digests.py +++ b/src/stem_examples/check_digests.py @@ -122,5 +122,3 @@ if __name__ == '__main__': LOG.exception(f"Exception in iMain {e}") i = 1 sys.exit(i) - - diff --git a/src/stem_examples/compare_flags.py b/src/stem_examples/compare_flags.py index 88a0869..0f6eebd 100755 --- a/src/stem_examples/compare_flags.py +++ b/src/stem_examples/compare_flags.py @@ -18,7 +18,7 @@ import stem.descriptor import stem.descriptor.remote import stem.directory -from tor_controller import set_socks_proxy +from tor_controller import set_socks_proxy, unset_socks_proxy def iMain(): # Query all authority votes asynchronously. @@ -70,4 +70,10 @@ def iMain(): if __name__ == '__main__': set_socks_proxy() - sys.exit(iMain()) + try: + i = iMain() + except KeyboardInterrupt as e: + i = 0 + finally: + unset_socks_proxy() + sys.exit(i) diff --git a/src/stem_examples/connection_resolution.py b/src/stem_examples/connection_resolution.py new file mode 100644 index 0000000..2d75a13 --- /dev/null +++ b/src/stem_examples/connection_resolution.py @@ -0,0 +1,77 @@ +__doc__ = """Connection Resolution + +Connection information is a useful tool for learning more about network +applications like Tor. Our stem.util.connection.get_connections() function +provides an easy method for accessing this information, with a few caveats... + +Connection resolvers are platform specific. We support several platforms but +not all. + +By default Tor runs with a feature called DisableDebuggerAttachment. This +prevents debugging applications like gdb from analyzing Tor unless it is run as +root. Unfortunately this also alters the permissions of the Tor process /proc +contents breaking numerous system tools (including our resolvers). To use this +function you need to either run as root (discouraged) or add +DisableDebuggerAttachment 0 to your torrc. + +Please note that if you operate an exit relay it is highly discouraged for you +to look at or record this information. Not only is doing so eavesdropping, but +likely also a violation of wiretap laws. + +With that out of the way, how do you look up this information? Below is a +simple script that dumps Tor's present connections. + +https://stem.torproject.org/tutorials/east_of_the_sun.html +""" + +import os +import sys +import logging + +from stem.util.connection import get_connections, system_resolvers +from stem.util.system import pid_by_name + +LOG = logging.getLogger() + +def iMain (lArgs=None): + resolvers = system_resolvers() + + if not resolvers: + LOG.error("Stem doesn't support any connection resolvers on our platform.") + return 1 + + picked_resolver = resolvers[0] # lets just opt for the first + LOG.info("Our platform supports connection resolution via: %s (picked %s)" % (', '.join(resolvers), picked_resolver)) + + tor_pids = pid_by_name('tor', multiple = True) + + if not tor_pids: + LOG.warn("Unable to get tor's pid. Is it running?") + return 1 + + if len(tor_pids) > 1: + LOG.info("You're running %i instances of tor, picking the one with pid %i" % (len(tor_pids), tor_pids[0])) + else: + LOG.info("Tor is running with pid %i" % tor_pids[0]) + + LOG.info("Connections:\n") + + for conn in get_connections(picked_resolver, process_pid = tor_pids[0], process_name = 'tor'): + LOG.info(" %s:%s => %s:%s" % (conn.local_address, conn.local_port, conn.remote_address, conn.remote_port)) + return 0 + +if __name__ == '__main__': + from stem_examples.stem_utils import vsetup_logging + if os.environ.get('DEBUG', ''): + log_level = 10 + else: + log_level = 20 + vsetup_logging(LOG, log_level) + try: + i = iMain(sys.argv[1:]) + except KeyboardInterrupt as e: + i = 0 + except Exception as e: + LOG.exception(f"Exception {e}") + i = 1 + sys.exit(i) diff --git a/src/stem_examples/exit_used.py b/src/stem_examples/exit_used.py index 63f0c0a..520b007 100755 --- a/src/stem_examples/exit_used.py +++ b/src/stem_examples/exit_used.py @@ -75,6 +75,7 @@ if __name__ == '__main__': except KeyboardInterrupt as e: i = 0 except Exception as e: + LOG.exception(f"Exception {e}") i = 1 sys.exit(i) diff --git a/src/stem_examples/introduction_points.py b/src/stem_examples/introduction_points.py index bf5824f..ac95b18 100755 --- a/src/stem_examples/introduction_points.py +++ b/src/stem_examples/introduction_points.py @@ -26,7 +26,6 @@ import logging from stem.control import Controller from stem_examples.tor_controller import get_controller -from stem_examples.stem_utils import vsetup_logging LOG = logging.getLogger() @@ -76,6 +75,7 @@ def lMain(lArgs=None, timeout=None): return lRetval if __name__ == '__main__': + from stem_examples.stem_utils import vsetup_logging if os.environ.get('DEBUG', ''): log_level = 10 else: @@ -86,6 +86,7 @@ if __name__ == '__main__': if l: print(l) i = 0 except KeyboardInterrupt as e: + LOG.exception(f"Exception {e}") i = 0 except Exception as e: i = 1 diff --git a/src/stem_examples/mappaddress.py b/src/stem_examples/mappaddress.py index 8edd72d..0e1a40e 100755 --- a/src/stem_examples/mappaddress.py +++ b/src/stem_examples/mappaddress.py @@ -19,7 +19,7 @@ import os from stem import StreamStatus from stem.control import EventType, Controller -from tor_controller import set_socks_proxy +# from tor_controller import set_socks_proxy, unset_socks_proxy from stem_examples.tor_controller import get_controller from stem_examples.stem_utils import vsetup_logging diff --git a/src/stem_examples/outdated_relays.py b/src/stem_examples/outdated_relays.py index 72eca93..d3719f6 100755 --- a/src/stem_examples/outdated_relays.py +++ b/src/stem_examples/outdated_relays.py @@ -16,17 +16,15 @@ import logging from stem.descriptor.remote import DescriptorDownloader from stem.version import Version -from tor_controller import set_socks_proxy +from tor_controller import set_socks_proxy, unset_socks_proxy LOG = logging.getLogger() def iMain(): - set_socks_proxy() downloader = DescriptorDownloader(use_mirrors=True) count, with_contact = 0, 0 elts = downloader.get_server_descriptors() LOG.info(f"Checking for outdated relays len server_descriptors={len(list(elts))}...") - print("") for desc in elts: if desc.tor_version < Version('0.2.3.0'): @@ -36,20 +34,18 @@ def iMain(): LOG.info(' %-15s %s' % (desc.tor_version, desc.contact.decode("utf-8", "replace"))) with_contact += 1 - print("") LOG.info("%i outdated relays found, %i had contact information" % (count, with_contact)) # http://vt5hknv6sblkgf22.onion/tutorials/examples/outdated_relays.htmlhttp://vt5hknv6sblkgf22.onion/tutorials/examples/outdated_relays.html return 0 if __name__ == '__main__': + set_socks_proxy() from stem_examples.stem_utils import vsetup_logging - LOG = logging.getLogger() if os.environ.get('DEBUG', ''): log_level = 10 else: log_level = 20 vsetup_logging(LOG, log_level) - LOG.setLevel(logging.DEBUG) try: l = iMain() if l: print(l) @@ -57,5 +53,8 @@ if __name__ == '__main__': except KeyboardInterrupt as e: i = 0 except Exception as e: + LOG.exception(f"Exception {e}") i = 1 + finally: + unset_socks_proxy() sys.exit(i) diff --git a/src/stem_examples/relay_connections.py b/src/stem_examples/relay_connections.py index 0ccd602..087cd4a 100755 --- a/src/stem_examples/relay_connections.py +++ b/src/stem_examples/relay_connections.py @@ -171,6 +171,7 @@ if __name__ == '__main__': except KeyboardInterrupt as e: i = 0 except Exception as e: + LOG.exception(f"Exception {e}") i = 1 sys.exit(i) diff --git a/src/stem_examples/tor_bootstrap_check.py b/src/stem_examples/tor_bootstrap_check.py index e089e29..50f0898 100755 --- a/src/stem_examples/tor_bootstrap_check.py +++ b/src/stem_examples/tor_bootstrap_check.py @@ -12,10 +12,14 @@ tor is at. import sys import os import re +import logging +import socket from stem.connection import connect from stem_examples.tor_controller import get_controller +LOG = logging.getLogger() + def iMain(lArgs=None): password = os.environ.get('TOR_CONTROLLER_PASSWORD') if os.path.exists('/run/tor/control'): @@ -23,10 +27,6 @@ def iMain(lArgs=None): else: controller = get_controller(password=password, port=9051) -# controller.connect() - - bootstrap_status = controller.get_info("status/bootstrap-phase") - ## Possible answer, if network cable has been removed: ## 250-status/bootstrap-phase=WARN BOOTSTRAP PROGRESS=80 TAG=conn_or SUMMARY="Connecting to the Tor network" WARNING="No route to host" REASON=NOROUTE COUNT=26 RECOMMENDATION=warn @@ -38,18 +38,23 @@ def iMain(lArgs=None): ## TODO: parse the messages above. - LOG.info(format(bootstrap_status)) + try: + bootstrap_status = controller.get_info("status/bootstrap-phase") + LOG.info(format(bootstrap_status)) - progress_percent = re.match('.* PROGRESS=([0-9]+).*', bootstrap_status) - exit_code = int(progress_percent.group(1)) + progress_percent = re.match('.* PROGRESS=([0-9]+).*', bootstrap_status) + exit_code = int(progress_percent.group(1)) - controller.close() - - return exit_code + controller.close() + except socket.error as e: + # Error while receiving a control message (SocketClosed): received exception "read of closed file" + return 0 + except Exception as e: + raise + return 0 if __name__ == '__main__': from stem_examples.stem_utils import vsetup_logging - LOG = logging.getLogger() if os.environ.get('DEBUG', ''): log_level = 10 else: @@ -57,12 +62,10 @@ if __name__ == '__main__': vsetup_logging(LOG, log_level) LOG.setLevel(logging.DEBUG) try: - l = iMain() - if l: print(l) - i = 0 + i = iMain() except KeyboardInterrupt as e: i = 0 except Exception as e: + LOG.exception(f"Exception {e}") i = 1 sys.exit(i) - diff --git a/src/stem_examples/tor_controller.py b/src/stem_examples/tor_controller.py index 560f021..ceede89 100755 --- a/src/stem_examples/tor_controller.py +++ b/src/stem_examples/tor_controller.py @@ -12,7 +12,7 @@ def set_socks_proxy(SOCKS5_PROXY_HOST='127.0.0.1', SOCKS5_PROXY_PORT=9050): try: import socks # you need to install pysocks (see above) # Remove this if you don't plan to "deactivate" the proxy later - default_socket = socket.socket + socks._socket = socket.socket # Set up a proxy socks.set_default_proxy(socks.SOCKS5, SOCKS5_PROXY_HOST, SOCKS5_PROXY_PORT) @@ -21,6 +21,14 @@ def set_socks_proxy(SOCKS5_PROXY_HOST='127.0.0.1', SOCKS5_PROXY_PORT=9050): return False return True +def unset_socks_proxy(): + import socks # you need to install pysocks (see above) + # Remove this if you don't plan to "deactivate" the proxy later + socks.socket = socket._socket + + # Set up a proxy + socks.set_default_proxy(socks.SOCKS5, None, None) + def get_controller(password=None, address='127.0.0.1', port=9051, unix='/run/tor/control'): if unix and os.path.exists(unix): # print(unix) diff --git a/stem_examples.txt b/stem_examples.txt index b8cfa05..8fc33ca 100644 --- a/stem_examples.txt +++ b/stem_examples.txt @@ -3,28 +3,74 @@ == stem_examples tor testing == This is a Python doctest file that is executable documentation. +stem_examples is a set of small scripts that tell you about your +running tor instance. -Pass the controller password if needed as an environment variable: +Onionoo is a web-based protocol to learn about currently running Tor +relays and bridges. Onionoo itself was not designed as a service for +human beings---at least not directly. Onionoo provides the data for +other applications and websites which in turn present Tor network +status information to humans: https://metrics.torproject.org/onionoo.html + +You can see the status of tor relays at https://torstatus.rueckgr.at/ +The code for that site is at https://github.com/paulchen/torstatus +You can get a list of exit relays that are marked bad with: +wget --post-data='SR=FBadExit&SO=Asc&FBadExit=1' 'https://torstatus.rueckgr.at/' + +It is assumed that you are running a tor that has its torrc configured with: + +ControlPort 127.0.0.1:9051 + +and/or + +ControlSocket /run/tor/control +ControlSocketsGroupWritable 1 + +We can authenticate with a password. To set a password first get its hash... + +% tor --hash-password "my_password" +16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF + +and use that for the HashedControlPassword in your torrc. + +HashedControlPassword 16:E600ADC1B52C80BB6022A0E999A7734571A451EB6AE50FED489B72E3DF + +so that you have some security on the Control connection. +Pass the controller password to these scripts as an environment variable: >>> import os >>> assert os.environ['TOR_CONTROLLER_PASSWORD'] +If you are using /run/tor/control you will also need to run the scripts as the user +that has rw access to that socket, usually tor or debian-tor. + Add our code to the PYTHONPATH >>> import sys >>> sys.path.append(os.path.join(os.getcwd(), 'src', 'stem_examples')) -We'll need the settings defined in {{{/usr/local/etc/testforge/testforge.yml}}} +We'll used the settings defined in {{{/usr/local/etc/testforge/testforge.yml}}} +If you don't have one, make it with the settings from your torrc: >>> print("yaml", file=sys.stderr) >>> import yaml - >>> sFacts = open('/usr/local/etc/testforge/testforge.yml').read() - >>> assert sFacts - >>> dFacts = yaml.safe_load(sFacts) + >>> try: + ... sFacts = open('/usr/local/etc/testforge/testforge.yml').read() + ... except: + ... dFacts = { + ... HTTPS_PROXYHOST: "127.0.0.1", + ... HTTPS_PROXYPORT: 9128, + ... HTTPS_PROXYTYPE: "http", + ... SOCKS_PROXYHOST: "127.0.0.1", + ... SOCKS_PROXYPORT: 9050, + ... SOCKS_PROXYTYPE: "socks5", + ... } + ... else: + ... assert sFacts + ... dFacts = yaml.safe_load(sFacts) FixMe: use the settings for the ports and directories below. >>> import os - >>> os.environ['http_proxy'] = 'http://'+dFacts['HTTP_PROXYHOST']+':'+str(dFacts['HTTP_PROXYPORT']) >>> os.environ['https_proxy'] = 'http://'+dFacts['HTTPS_PROXYHOST']+':'+str(dFacts['HTTPS_PROXYPORT']) >>> os.environ['socks_proxy'] = 'socks5://'+dFacts['SOCKS_PROXYHOST']+':'+str(dFacts['SOCKS_PROXYPORT']) @@ -40,16 +86,17 @@ We test 3 known hidden services: Facebook, DuckDuckGo and . >>> lKNOWN_ONIONS = [ ... 'facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd', # facebook ... 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad', # ddg - ... 'zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad', # hks ... ] We wil expect to get back the hidden service version, the descriptor-lifetime and then the descriptor-signing-key-cert: >>> introduction_points.iMain(lKNOWN_ONIONS) #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - hs-descriptor 3 + 0 + +hs-descriptor 3 descriptor-lifetime ... - + ### exit_used Determine The Exit You're Using @@ -69,11 +116,31 @@ The following provides a summary of your relay's inbound and outbound connection You must be root or tor to run this: relay_connections.iMain(["--ctrlport", "9051"]) -## outdated_relays +## connection_resolution Connection Resolution + +Connection information is a useful tool for learning more about network +applications like Tor. Our stem.util.connection.get_connections() function +provides an easy method for accessing this information. + + >>> print("connection_resolution", file=sys.stderr) + >>> import connection_resolution + >>> connection_resolution.iMain() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + 0 + + INFO Our platform supports connection resolution via: ... + + +## outdated_relays List Outdated Relays + +Time marches on. Tor makes new releases, and at some point needs to drop +support for old ones. Below is the script we used on ticket 9476 to reach out +to relay operators that needed to upgrade. >>> print("outdated_relays", file=sys.stderr) >>> import outdated_relays >>> outdated_relays.iMain() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + 0 + Checking for outdated relays ... @@ -85,18 +152,9 @@ relay_connections.iMain(["--ctrlport", "9051"]) A script by adrelanos@riseup.net to check what percentage of boostrapping tor is at. This fails under doctest but not from the cmdline ->> tor_bootstrap_check.iMain() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE -NOTICE ... - - - control_port = stem.socket.ControlPort(address, port) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - File "/usr/local/lib/python3.11/site-packages/stem/socket.py", line 503, in __init__ - self.connect() - File "/usr/local/lib/python3.11/site-packages/stem/socket.py", line 172, in connect - self._socket = self._make_socket() - ^^^^^^^^^^^^^^^^^^^ - File "/usr/local/lib/python3.11/site-packages/stem/socket.py", line 538, in _make_socket - raise stem.SocketError(exc) -stem.SocketError: Socket error: 0x01: General SOCKS server failure + >>> tor_bootstrap_check.iMain() #doctest: +ELLIPSIS +NORMALIZE_WHITESPACE + 0 + + NOTICE ... +