Commit d1d9feb6 authored by segfault's avatar segfault

Improve TorManager

parent 52d1b828
......@@ -109,6 +109,7 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
self._is_installed = self.is_installed_file.exists()
self._address = self.private_key.derive_onion_address() if self.IsInstalled else str()
self.tor = TorManager(self.update_transaction_progress)
self.systemd = SystemdManager(self.Name)
self.container = ContainerManager(self.Name, self.state_dir, self.update_transaction_status,
self.update_transaction_progress)
......@@ -120,7 +121,6 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
self.Status = Status.INSTALLING
try:
self.TransactionStatus = _("Starting container to install packages")
self.container.create()
with self.container.run():
self.container.install_packages(self.packages)
......@@ -157,7 +157,7 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
try:
if self.IsPublished:
TorManager(self).stop_hidden_service()
self.tor.stop_hidden_service(self.Address)
self.TransactionStatus = _("Stopping container")
self.container.stop()
......@@ -199,7 +199,8 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
self.on_systemd_service_started()
if not self.IsPublished:
TorManager(self).start_hidden_service()
self.TransactionStatus = _("Creating onion service")
self.tor.start_hidden_service(self.private_key.read(), self.Address, self.virtual_port, self.port)
self.Status = Status.RUNNING
except Exception:
......@@ -207,7 +208,7 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
if self.IsRunning:
self.systemd.stop(self.systemd_service)
if self.IsPublished:
TorManager(self).stop_hidden_service()
self.tor.stop_hidden_service(self.Address)
self.Status = Status.ERROR
raise
......@@ -223,7 +224,7 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
self.systemd.stop(self.systemd_service)
if self.IsPublished:
TorManager(self).stop_hidden_service()
self.tor.stop_hidden_service(self.Address)
self.Status = Status.STOPPED
except ServiceAlreadyStoppedError:
......@@ -316,7 +317,7 @@ class OnionService(DBusObject, metaclass=abc.ABCMeta):
def IsPublished(self) -> bool:
if not self.Address:
return False
return TorManager(self).check_hidden_service_published()
return self.tor.check_hidden_service_published(self.Address)
@property
def Address(self) -> str:
......
......@@ -6,26 +6,20 @@ from contextlib import contextmanager
import stem
from stem.control import Controller, EventType
from onionkit import _
from onionkit.util import process_mainloop_events
# Only required for type hints
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from onionkit.service import OnionService
logger = getLogger()
PUBLICATION_TIMEOUT = 45
class HSException(Exception):
class TorException(Exception):
pass
class TorManager(object):
def __init__(self, onion_service: "OnionService"):
self.onion_service = onion_service
def __init__(self, progress_callback: callable):
self.progress_callback = progress_callback
try:
self.controller = Controller.from_socket_file()
except stem.SocketError:
......@@ -33,6 +27,7 @@ class TorManager(object):
raise
self.controller.authenticate()
self.hs_desc_queue = queue.Queue()
self.publishing = False
@contextmanager
def hs_desc_listener(self):
......@@ -44,17 +39,23 @@ class TorManager(object):
finally:
self.controller.remove_event_listener(hs_desc_listener)
def start_hidden_service(self):
def start_hidden_service(self, key: str, address: str, virtual_port: int, port: int):
self.publishing = True
try:
self._start_hidden_service(key, address, virtual_port, port)
finally:
self.publishing = False
def _start_hidden_service(self, key_content: str, address: str, virtual_port: int, port: int):
if not self.tor_circuit_established():
raise HSException("No Tor circuit established")
raise TorException("No Tor circuit established")
key_content = self.onion_service.private_key.read()
key_type = "RSA1024"
self.onion_service.TransactionStatus = _("Creating onion service")
with self.hs_desc_listener():
response = self.controller.create_ephemeral_hidden_service(
ports={self.onion_service.virtual_port: self.onion_service.port},
ports={virtual_port: port},
key_type=key_type,
key_content=key_content,
discard_key=False,
......@@ -63,16 +64,14 @@ class TorManager(object):
)
if response.service_id:
address = response.service_id + ".onion"
if self.onion_service.Address != address:
raise HSException("Onion address %r does not match precalculated address %r" % (address, self.onion_service.Address))
response_address = response.service_id + ".onion"
if address != response_address:
raise TorException("Onion address %r does not match precalculated address %r" % (response_address, address))
self._wait_for_publication(response)
self.onion_service.TransactionStatus = _("Onion service created")
def stop_hidden_service(self):
self.onion_service.TransactionStatus = "remove hidden service"
self.controller.remove_ephemeral_hidden_service(self.onion_service.Address.replace(".onion", ""))
def stop_hidden_service(self, address: str):
self.controller.remove_ephemeral_hidden_service(address.replace(".onion", ""))
def _wait_for_publication(self, response):
directories_uploaded_to = list()
......@@ -82,7 +81,7 @@ class TorManager(object):
while loop_time - start_time < PUBLICATION_TIMEOUT:
# Uploading a descriptor should take 30 seconds (see Tor ticket #20082)
if loop_time - start_time < 30:
self.onion_service.TransactionProgress = 100 / 30 * (loop_time - start_time)
self.progress_callback(100 / 30 * (loop_time - start_time))
try:
event = self.hs_desc_queue.get(timeout=0)
......@@ -97,7 +96,7 @@ class TorManager(object):
failures.append('%s (%s)' % (event.directory_fingerprint, event.reason))
if len(directories_uploaded_to) == len(failures):
raise HSException('Failed to upload our hidden service descriptor to %s' % ', '.join(failures))
raise TorException('Failed to upload our hidden service descriptor to %s' % ', '.join(failures))
except queue.Empty:
for i in range(100):
process_mainloop_events()
......@@ -105,20 +104,18 @@ class TorManager(object):
loop_time = time.perf_counter()
self.controller.remove_ephemeral_hidden_service(response.service_id)
raise HSException("Timeout reached while trying to upload hidden service descriptor")
raise TorException("Timeout reached while trying to upload hidden service descriptor")
def check_hidden_service_published(self):
def check_hidden_service_published(self, address: str):
# The service id is already in onions/detached when we wait for publication,
# even though we don't know if the publication is successful.
# So we return False if we are currently waiting for publication.
if self.onion_service.TransactionStatus in ("start hidden service", "create hidden service"):
if self.publishing:
return False
service_id = self.onion_service.Address.replace(".onion", "")
controller = Controller.from_socket_file()
controller.authenticate()
service_id = address.replace(".onion", "")
try:
published_service_ids = controller.get_info("onions/detached").split("\n")
published_service_ids = self.controller.get_info("onions/detached").split("\n")
return service_id in published_service_ids
except stem.ProtocolError as e:
if e.args == stem.ProtocolError("GETINFO response didn't have an OK status:\n"
......@@ -127,6 +124,4 @@ class TorManager(object):
logger.error("Got a ProtocolError from stem", exc_info=True)
def tor_circuit_established(self):
controller = Controller.from_socket_file()
controller.authenticate()
return bool(int(controller.get_info("status/circuit-established")))
return bool(int(self.controller.get_info("status/circuit-established")))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment