@ -2,7 +2,8 @@
from zope . interface import implementer
from twisted . internet . error import ReactorNotRunning
from twisted . internet import reactor , defer
from twisted . internet . endpoints import TCP4ClientEndpoint , UNIXClientEndpoint
from twisted . internet . endpoints import ( TCP4ClientEndpoint ,
UNIXClientEndpoint , serverFromString )
from twisted . web . client import Agent , BrowserLikePolicyForHTTPS
import txtorcon
from txtorcon . web import tor_agent
@ -105,6 +106,12 @@ def get_nontor_agent(tls_whitelist=[]):
contextFactory = WhitelistContextFactory ( tls_whitelist ) )
return agent
def config_to_hs_ports ( virtual_port , host , port ) :
# See https://github.com/meejah/txtorcon/blob/0c416cc8fe18b913cd0c7422935885a1bfecf4c0/txtorcon/onion.py#L1320
# for non default config, pass port mapping strings like:
# "80 127.0.0.1:1234"
return " {} {} : {} " . format ( virtual_port , host , port )
class JMHiddenService ( object ) :
""" Wrapper class around the actions needed to
create and serve on a hidden service ; an object of
@ -113,7 +120,8 @@ class JMHiddenService(object):
"""
def __init__ ( self , resource , info_callback , error_callback ,
onion_hostname_callback , tor_control_host ,
tor_control_port , serving_port = None ,
tor_control_port , serving_host , serving_port ,
virtual_port = None ,
shutdown_callback = None ) :
self . site = Site ( resource )
self . site . displayTracebacks = False
@ -124,26 +132,29 @@ class JMHiddenService(object):
# known and is 80 by default)
self . onion_hostname_callback = onion_hostname_callback
self . shutdown_callback = shutdown_callback
if not serving _port:
self . port = 80
if not virtual _port:
self . virtual_ port = 80
else :
self . port = serving _port
self . virtual_ port = virtual _port
self . tor_control_host = tor_control_host
self . tor_control_port = tor_control_port
print ( " got these settings: " , self . port , self . site , self . tor_control_host , self . tor_control_port )
# note that defaults only exist in jmclient
# config object, so no default here:
self . serving_host = serving_host
self . serving_port = serving_port
def start_tor ( self ) :
""" This function executes the workflow
of starting the hidden service and returning its hostname
"""
self . info_callback ( " Attempting to start onion service on port: {} "
" ... " . format ( self . port ) )
" ... " . format ( self . virtual_ port) )
if str ( self . tor_control_host ) . startswith ( ' unix: ' ) :
control_endpoint = UNIXClientEndpoint ( reactor ,
self . tor_control_host [ 5 : ] )
else :
control_endpoint = TCP4ClientEndpoint ( reactor ,
self . tor_control_host , self . tor_control_port )
self . tor_control_host , self . tor_control_port )
d = txtorcon . connect ( reactor , control_endpoint )
d . addCallback ( self . create_onion_ep )
d . addErrback ( self . setup_failed )
@ -158,20 +169,27 @@ class JMHiddenService(object):
def create_onion_ep ( self , t ) :
self . tor_connection = t
return t . create_onion_endpoint ( self . port , private_key = txtorcon . DISCARD )
def onion_listen ( self , onion_ep ) :
return onion_ep . listen ( self . site )
portmap_string = config_to_hs_ports ( self . virtual_port ,
self . serving_host , self . serving_port )
return t . create_onion_service (
ports = [ portmap_string ] , private_key = txtorcon . DISCARD )
def onion_listen ( self , onion ) :
# 'onion' arg is the created EphemeralOnionService object;
# now we know it exists, we start serving the Site on the
# relevant port:
self . onion = onion
serverstring = " tcp: {} :interface= {} " . format ( self . serving_port ,
self . serving_host )
onion_endpoint = serverFromString ( reactor , serverstring )
return onion_endpoint . listen ( self . site )
def print_host ( self , ep ) :
""" Callback fired once the HS is available;
we let the caller know the hidden service onion hostname ,
which is not otherwise available to them :
""" Callback fired once the HS is available
and the site is up ready to receive requests .
The hidden service hostname will be used in the BIP21 uri .
"""
# Note that ep,getHost().onion_port must return the same
# port as we chose in self.port; if not there is an error.
assert ep . getHost ( ) . onion_port == self . port
self . onion_hostname_callback ( ep . getHost ( ) . onion_uri )
self . onion_hostname_callback ( self . onion . hostname )
def shutdown ( self ) :
self . tor_connection . protocol . transport . loseConnection ( )