Michael Altfield's gravatar

Check if Tor Onion Service is alive (python stem get_hidden_service_descriptor)

This article will show how to check if a given Onion Service (ie some .onion address) is alive or dead.

Check If Tor .onion is Alive or Dead

Why?

Lots of Onion Services are “here today, gone tomorrow”. If you encountered a large list of Onion Services and want to quickly check to see which are still alive, the best way to do that is by querying the Tor network’s Hidden Service Directory (HSDir).

Of course, you could just paste the .onion into your trusty Tor Browser and press <enter> — but that’s only going to work for Onion Services hosting HTTP servers on port 80 or 443. It won’t, for example, tell you if the Onion Service is alive if it runs an XMPP server. Or an IMAP server. Or an SSH server.

No. Rather than trying to query the Onion Service on all possible ports, it’s best to query the Tor Hidden Service Directory (HSDir).

HS What?

Onion Services (by default) publish a “Hidden Service Descriptor” to the Tor network’s distributed Hidden Service Directory (HDir) every 3 hours.

The Hidden Service Descriptor returned by the HDir contains the metadata necessary for a client to connect to the Onion Service.

If a given Onion Service’s Hidden Service Descriptor cannot be found, then the Onion Service is likely dead.

Assumptions

At the time of writing, this guide was tested against the following software and versions:

  1. Debian 12
  2. Tor 0.4.7.16
  3. Python 3.11.2
  4. python3-stem 1.8.1-2.1

ⓘ Note that v2 Onion Services (22-characters long) were deprecated by the Tor Project in Jun 2021. They no longer work.

v3 Onion Services (62-characters long) have replaced them.

To query Hidden Service Descriptors for v3 Onion Services, you’ll need stem >= v1.8, which added HiddenServiceDescriptorV3() support

If you’re using a different OS or software versions, then adaptations to the below commands may be necessary.

Check Onion Service

This section will show how you can check to see if a given Onion Service is alive.

Prereqs

Before you can query the Tor network, you first must install Tor and start the Tor daemon.

Execute the following commands to install (and start) Tor.

# install Tor
sudo apt-get install tor

# star the Tor daemon
sudo systemctl start tor

Before you can use the stem (Tor) python module to query the Tor network, you first must install python and the stem python module.

Execute the following commands to install python and the required stem python module:

# install python
sudo apt-get install python3

# install the stem python module
sudo apt-get install python3-stem

Before you can use stem to query the Tor network, you must modify the tor config to enable a Control Port — where stem will be able to interact directly (and “control”) the tor daemon (as opposed to simply use Tor to make Internet connections).

Execute the followig commands to uncomment the line “#ControlPort 9051” from the ‘/etc/tor/torrc‘ config file (just delete the leading hash character [#] and save the file):

# make a backup of the tor config file
sudo cp /etc/tor/torrc /etc/tor/torrc.bak

# uncomment the line "ControlPort 9051"
sudo vim /etc/tor/torrc

After editing the ‘/etc/tor/torrc‘ config file (eg in ‘vim‘), you should see the following difference

user@disp6307:~$ sudo diff /etc/tor/torrc.bak /etc/tor/torrc
56c56
< #ControlPort 9051
---
> ControlPort 9051
user@disp6307:~$ 

Finally, restart the tor daemon to apply the change:

# restart the tor daemon
sudo systemctl restart tor

The python

Execute ‘sudo python3‘ to open a python interpreter shell as root

user@disp6307:~$ sudo python3
Python 3.11.2 (main, Nov 30 2024, 21:22:50) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

At the prompt, import our required ‘Controller‘ object from the ‘stem‘ python module

>>> from stem.control import Controller
>>> 

At the prompt, create a new instance of the ‘Controller‘ object. Make sure the port number here (eg ‘9051‘) matches the number following ‘ControlPort‘ in the ‘/etc/tor/torrc‘ file that you uncommented above.

>>> controller = Controller.from_port(port=9051)
>>> 

At the prompt, tell the new controller to connect to your local Tor daemon using the ‘authenticate()‘ method:

>>> controller.authenticate()
>>> 

At the prompt, confirm that the controller’s connection to the Tor daemon is working using the ‘is_alive()‘ method:

>>> controller.is_alive()
True
>>> 

Now you can ask the controller to get the Hidden Service Descriptor for a given Onion Service — just strip-off the ‘.onion‘ at the end of your Onion Service. In this example, we’ll use the DuckDuckGo Onion Serivce duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion:

>>> controller.get_hidden_service_descriptor( 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad' )
<stem.descriptor.hidden_service.HiddenServiceDescriptorV2 object at 0x7b3ddf115710>
>>> 

If you didn’t get an error, then the Onion Service is alive!

For comparison, here’s a nonsense Onion Service that does *not* exist (and returns a ProtocolError Exception):

>>> controller.get_hidden_service_descriptor( 'thisverylikelydoesnotexistandihopeitdoesnotexistfortestd' )
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3/dist-packages/stem/control.py", line 489, in wrapped
    return func(self, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/stem/control.py", line 2188, in get_hidden_service_descriptor
    raise stem.ProtocolError('HSFETCH returned unexpected response code: %s' % response.code)
stem.ProtocolError: HSFETCH returned unexpected response code: 513
>>> 

Scripted Example

Recently I was searching for an XMPP server that was hosted on an Onion Service. Unfortunately, the majority of the XMPP server lists that I found were very out-of-date, and I needed a way to check a list of dozens of Onion Services to see which were still operational and which were dead.

So I wrote the following python script, and saved it as ‘get_hidden_service_descriptor.py

from stem.control import Controller
from stem.descriptor.hidden_service import HiddenServiceDescriptorV3

onions = ['2n3tvihf4n27pqyqdtcqywl33kbjuv2kj3eeq6qvbtud57jwiaextmid.onion', '32qywqnlnqzbry42nmotr47ebts3k6lhiwfob6xniosmepz2tsnsx7ad.onion', '4colmnerbjz3xtsjmqogehtpbt5upjzef57huilibbq3wfgpsylub7yd.onion', '6voaf7iamjpufgwoulypzwwecsm2nu7j5jpgadav2rfqixmpl4d65kid.onion', '6w5iasklrbr2kw53zqrsjktgjapvjebxodoki3gjnmvb4dvcbmz7n3qd.onion', '7drfpncjeom3svqkyjitif26ezb3xvmtgyhgplcvqa7wwbb4qdbsjead.onion', 'ae3w7fkzr3elfwsk6mhittjj7e7whme2tumdrhw3dfumy2hsiwomc3yd.onion', 'chillingguw3yu2rmrkqsog4554egiry6fmy264l5wblyadds3c2lnyd.onion', 'fzdx522fvinbaqgwxdet45wryluchpplrkkzkry33um5tufkjd3wdaqd.onion', 'gku6irp4e65ikfkbrdx576zz6biapv37vv2cmklo2qyrtobugwz5iaad.onion', 'gois4b6fahhrlsieupl56xd6ya226m33abzuv26vgfpuvv44wf6vbdad.onion', 'j4dhkkxfcsvzvh3p5djkmuehhgd6t6l7wmzih6b4ss744hegwkiae7ad.onion', 'jabjabdea2eewo3gzfurscj2sjqgddptwumlxi3wur57rzf5itje2rid.onion', 'jaswtrycaot3jzkr7znje4ebazzvbxtzkyyox67frgvgemwfbzzi6uqd.onion', 'jeirlvruhz22jqduzixi6li4xyoweytqglwjons4mbuif76fgslg5uad.onion', 'jukrlvyhgguiedqswc5lehrag2fjunfktouuhi4wozxhb6heyzvshuyd.onion', 'mrbenqxl345o4u7yaln25ayzz5ut6ab3kteulzqusinjdx6oh7obdlad.onion', 'nixnet54icmeh25qsmcsereuoareofzevjqjnw3kki6oxxey3jonwwyd.onion', 'qawb5xl3mxiixobjsw2d45dffngyyacp4yd3wjpmhdrazwvt4ytxvayd.onion', 'qwikoouqore6hxczat3gwbe2ixjpllh3yuhaecixyenprbn6r54mglqd.onion', 'qwikxxeiw4kgmml6vjw2bsxtviuwjce735dunai2djhu6q7qbacq73id.onion', 'razpihro3mgydaiykvxwa44l57opvktqeqfrsg3vvwtmvr2srbkcihyd.onion', 'rurcblzhmdk22kttfkel2zduhyu3r6to7knyc7wiorzrx5gw4c3lftad.onion', 'szd7r26dbcrrrn4jthercrdypxfdmzzrysusyjohn4mpv2zbwcgmeqqd.onion', 'xdkriz6cn2avvcr2vks5lvvtmfojz2ohjzj4fhyuka55mvljeso2ztqd.onion', 'xiynxwxxpw7olq76uhrbvx2ts3i7jagqnqix7arfbknmleuoiwsmt5yd.onion', 'xmppccwrohw3lmfap6e3quep2yzx3thewkfhw4vptb5gwgnkttlq2vyd.onion', 'ynnuxkbbiy5gicdydekpihmpbqd4frruax2mqhpc35xqjxp5ayvrjuqd.onion', 'yxkc2uu3rlwzzhxf2thtnzd7obsdd76vtv7n34zwald76g5ogbvjbbqd.onion']

controller = Controller.from_port(port=9051)
controller.authenticate()
if controller.is_alive():
  print( "INFO: Confirmed that Tor is working." )

print( "INFO: Trying get_hidden_service_descriptor()" )
for onion in onions:
    print( onion )
    try:
        desc = controller.get_hidden_service_descriptor(onion)
        print( "\tOK" )
    except Exception as e:
        print( "\tNo Descriptor Found" )

Here’s an example execution, which shows that 7 out of 29 of the Onion Services are dead.

user@disp6307:~$ time sudo python3 get_hidden_service_descriptor.py 
INFO: Confirmed that Tor is working.
INFO: Trying get_hidden_service_descriptor()
2n3tvihf4n27pqyqdtcqywl33kbjuv2kj3eeq6qvbtud57jwiaextmid.onion
	OK
32qywqnlnqzbry42nmotr47ebts3k6lhiwfob6xniosmepz2tsnsx7ad.onion
	OK
4colmnerbjz3xtsjmqogehtpbt5upjzef57huilibbq3wfgpsylub7yd.onion
	OK
6voaf7iamjpufgwoulypzwwecsm2nu7j5jpgadav2rfqixmpl4d65kid.onion
	OK
6w5iasklrbr2kw53zqrsjktgjapvjebxodoki3gjnmvb4dvcbmz7n3qd.onion
	No Descriptor Found
7drfpncjeom3svqkyjitif26ezb3xvmtgyhgplcvqa7wwbb4qdbsjead.onion
	No Descriptor Found
ae3w7fkzr3elfwsk6mhittjj7e7whme2tumdrhw3dfumy2hsiwomc3yd.onion
	OK
chillingguw3yu2rmrkqsog4554egiry6fmy264l5wblyadds3c2lnyd.onion
	OK
fzdx522fvinbaqgwxdet45wryluchpplrkkzkry33um5tufkjd3wdaqd.onion
	OK
gku6irp4e65ikfkbrdx576zz6biapv37vv2cmklo2qyrtobugwz5iaad.onion
	OK
gois4b6fahhrlsieupl56xd6ya226m33abzuv26vgfpuvv44wf6vbdad.onion
	OK
j4dhkkxfcsvzvh3p5djkmuehhgd6t6l7wmzih6b4ss744hegwkiae7ad.onion
	OK
jabjabdea2eewo3gzfurscj2sjqgddptwumlxi3wur57rzf5itje2rid.onion
	OK
jaswtrycaot3jzkr7znje4ebazzvbxtzkyyox67frgvgemwfbzzi6uqd.onion
	OK
jeirlvruhz22jqduzixi6li4xyoweytqglwjons4mbuif76fgslg5uad.onion
	OK
jukrlvyhgguiedqswc5lehrag2fjunfktouuhi4wozxhb6heyzvshuyd.onion
	No Descriptor Found
mrbenqxl345o4u7yaln25ayzz5ut6ab3kteulzqusinjdx6oh7obdlad.onion
	No Descriptor Found
nixnet54icmeh25qsmcsereuoareofzevjqjnw3kki6oxxey3jonwwyd.onion
	OK
qawb5xl3mxiixobjsw2d45dffngyyacp4yd3wjpmhdrazwvt4ytxvayd.onion
	No Descriptor Found
qwikoouqore6hxczat3gwbe2ixjpllh3yuhaecixyenprbn6r54mglqd.onion
	OK
qwikxxeiw4kgmml6vjw2bsxtviuwjce735dunai2djhu6q7qbacq73id.onion
	OK
razpihro3mgydaiykvxwa44l57opvktqeqfrsg3vvwtmvr2srbkcihyd.onion
	No Descriptor Found
rurcblzhmdk22kttfkel2zduhyu3r6to7knyc7wiorzrx5gw4c3lftad.onion
	OK
szd7r26dbcrrrn4jthercrdypxfdmzzrysusyjohn4mpv2zbwcgmeqqd.onion
	OK
xdkriz6cn2avvcr2vks5lvvtmfojz2ohjzj4fhyuka55mvljeso2ztqd.onion
	OK
xiynxwxxpw7olq76uhrbvx2ts3i7jagqnqix7arfbknmleuoiwsmt5yd.onion
	OK
xmppccwrohw3lmfap6e3quep2yzx3thewkfhw4vptb5gwgnkttlq2vyd.onion
	OK
ynnuxkbbiy5gicdydekpihmpbqd4frruax2mqhpc35xqjxp5ayvrjuqd.onion
	No Descriptor Found
yxkc2uu3rlwzzhxf2thtnzd7obsdd76vtv7n34zwald76g5ogbvjbbqd.onion
	OK

real	0m53.311s
user	0m0.005s
sys	0m0.006s
user@disp6307:~$ 

Limitations

This section will explain limitations to the information outlined in this article, as well as suggest potential improvements.

PublishHidServDescriptors

This method won’t work if the Onion Service has set ‘PublishHidServDescriptors‘ to ‘0‘.

But if an Onion Service doesn’t publish its Hidden Service Descriptors, then it’s not alive anyway — since clients won’t be able to obtain the necessary metadata from the Tor network to connect to it.

Further Reading

  1. torproject.org Official Tor Project Website
  2. stem.torproject.org Official Stem Project Website
  3. forum.torproject.org Official Tor Project Support Forums
  4. stem.torproject.org/tutorials/over_the_river.html Stem Tutorial for Hidden Services
  5. https://github.com/torproject/stem/issues/96 Bug in stem where get_hidden_service_descriptor() returns a v2 descriptor

Related Posts

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>