diff --git a/jmbase/jmbase/twisted_utils.py b/jmbase/jmbase/twisted_utils.py index f85a1ad..8a8f507 100644 --- a/jmbase/jmbase/twisted_utils.py +++ b/jmbase/jmbase/twisted_utils.py @@ -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() diff --git a/jmclient/jmclient/client_protocol.py b/jmclient/jmclient/client_protocol.py index 692e9a3..c8ecf88 100644 --- a/jmclient/jmclient/client_protocol.py +++ b/jmclient/jmclient/client_protocol.py @@ -86,7 +86,9 @@ class BIP78ClientProtocol(BaseClientProtocol): else: netconfig = {"port": 80, "tor_control_host": jcg("PAYJOIN", "tor_control_host"), - "tor_control_port": jcg("PAYJOIN", "tor_control_port")} + "tor_control_port": jcg("PAYJOIN", "tor_control_port"), + "onion_serving_host": jcg("PAYJOIN", "onion_serving_host"), + "onion_serving_port": jcg("PAYJOIN", "onion_serving_port")} d = self.callRemote(commands.BIP78ReceiverInit, netconfig=netconfig) self.defaultCallbacks(d) diff --git a/jmclient/jmclient/configure.py b/jmclient/jmclient/configure.py index c0a7279..c13cbb0 100644 --- a/jmclient/jmclient/configure.py +++ b/jmclient/jmclient/configure.py @@ -374,15 +374,22 @@ max_additional_fee_contribution = default # transaction; note it is decimal, not integer. min_fee_rate = 1.1 -# for payjoins to hidden service endpoints, the socks5 configuration: +# for payjoins as sender (i.e. client) to hidden service endpoints, +# the socks5 configuration: onion_socks5_host = localhost onion_socks5_port = 9050 -# for payjoin onion service creation, the tor control configuration: +# for payjoin onion service creation: +# the tor control configuration: tor_control_host = localhost # or, to use a UNIX socket # control_host = unix:/var/run/tor/control tor_control_port = 9051 +# the host/port actually serving the hidden service +# (note the *virtual port*, that the client uses, +# is hardcoded to 80): +onion_serving_host = localhost +onion_serving_port = 8080 # in some exceptional case the HS may be SSL configured, # this feature is not yet implemented in code, but here for the diff --git a/jmdaemon/jmdaemon/daemon_protocol.py b/jmdaemon/jmdaemon/daemon_protocol.py index 7091576..5daf409 100644 --- a/jmdaemon/jmdaemon/daemon_protocol.py +++ b/jmdaemon/jmdaemon/daemon_protocol.py @@ -274,6 +274,8 @@ class BIP78ServerProtocol(HTTPPassThrough): self.serving_port = int(netconfig["port"]) self.tor_control_host = netconfig["tor_control_host"] self.tor_control_port = int(netconfig["tor_control_port"]) + self.onion_serving_host=netconfig["onion_serving_host"] + self.onion_serving_port=int(netconfig["onion_serving_port"]) self.bip78_rr = BIP78ReceiverResource(self.info_callback, self.shutdown_callback, self.post_request_handler) @@ -283,8 +285,9 @@ class BIP78ServerProtocol(HTTPPassThrough): self.onion_hostname_callback, self.tor_control_host, self.tor_control_port, - self.serving_port, - self.shutdown_callback) + self.onion_serving_host, + self.onion_serving_port, + shutdown_callback=self.shutdown_callback) # this call will start bringing up the HS; when it's finished, # it will fire the `onion_hostname_callback`, or if it fails, # it'll fire the `setup_error_callback`.