Exemplo n.º 1
0
    def test_int_validator(self):
        self.protocol.answers.append('config/names=\nfoo Integer')
        self.protocol.answers.append({'foo': '123'})
        conf = TorConfig(self.protocol)

        conf.foo = 2.33
        conf.save()
        self.assertEqual(conf.foo, 2)

        conf.foo = '1'
        conf.save()
        self.assertEqual(conf.foo, 1)

        conf.foo = '-100'
        conf.save()
        self.assertEqual(conf.foo, -100)

        conf.foo = 0
        conf.save()
        self.assertEqual(conf.foo, 0)

        conf.foo = '0'
        conf.save()
        self.assertEqual(conf.foo, 0)

        for value in ('no', 'Not a value', None):
            try:
                conf.foo = value
            except (ValueError, TypeError):
                pass
            else:
                self.fail("No excpetion thrown")
Exemplo n.º 2
0
    def test_save_no_protocol(self):

        conf = TorConfig()
        conf.HiddenServices = [
            HiddenService(conf, '/fake/path', ['80 127.0.0.1:1234'])
        ]
        conf.save()
Exemplo n.º 3
0
 def test_hidden_service_parse_error(self):
     conf = TorConfig(FakeControlProtocol(['config/names=']))
     try:
         conf._setup_hidden_services('''FakeHiddenServiceKey=foo''')
         self.fail()
     except RuntimeError, e:
         self.assertTrue('parse' in str(e))
Exemplo n.º 4
0
    def test_no_tor_binary(self):
        """FIXME: do I really need all this crap in here?"""
        from txtorcon import torconfig
        oldone = torconfig.find_tor_binary
        self.transport = proto_helpers.StringTransport()
        config = TorConfig()
        d = None

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        try:
            self.protocol = FakeControlProtocol([])
            torconfig.find_tor_binary = lambda: None
            trans = FakeProcessTransport()
            trans.protocol = self.protocol
            self.othertrans = trans
            creator = functools.partial(Connector(), self.protocol,
                                        self.transport)
            try:
                d = launch_tor(config,
                               FakeReactor(self, trans, lambda x: None),
                               connection_creator=creator)
                self.fail()

            except TorNotFound:
                pass  # success!
        finally:
            torconfig.find_tor_binary = oldone

        return d
Exemplo n.º 5
0
 def test_invalid_parser(self):
     self.protocol.answers.append(
         'config/names=\nSomethingExciting NonExistantParserType\nOK')
     TorConfig(self.protocol)
     errs = self.flushLoggedErrors()
     self.assertEqual(len(errs), 1)
     self.assertTrue('NonExistantParserType' in str(errs[0]))
Exemplo n.º 6
0
    def test_ephemeral_remove_not_ok(self):
        protocol = FakeControlProtocol([])
        config = TorConfig(protocol)

        eph_d = EphemeralOnionService.create(
            Mock(),
            config,
            ports=["80 127.0.0.1:80"],
        )

        cmd, d = protocol.commands[0]
        self.assertEqual(u"ADD_ONION NEW:BEST Port=80,127.0.0.1:80", cmd)

        d.callback("PrivateKey={}\nServiceID={}".format(_test_private_key_blob, _test_onion_id))
        cb = protocol.events['HS_DESC']

        for x in range(6):
            cb('UPLOAD {} UNKNOWN hsdir_{}'.format(_test_onion_id, x))

        for x in range(6):
            cb('UPLOADED {} UNKNOWN hsdir_{}'.format(_test_onion_id, x))

        hs = yield eph_d
        remove_d = hs.remove()
        cmd, d = protocol.commands[-1]
        self.assertEqual(u"DEL_ONION {}".format(_test_onion_id), cmd)
        d.callback('bad stuff')
        with self.assertRaises(RuntimeError):
            yield remove_d
Exemplo n.º 7
0
    def test_set_ports(self):
        protocol = FakeControlProtocol([])
        config = TorConfig(protocol)
        hsdir = self.mktemp()
        os.mkdir(hsdir)
        with open(join(hsdir, 'hs_ed25519_secret_key'), 'wb') as f:
            f.write(b'\x01\x02\x03\x04')
        with open(join(hsdir, 'hostname'), 'w') as f:
            f.write('{}.onion'.format(_test_onion_id))

        hs_d = FilesystemOnionService.create(
            Mock(),
            config,
            hsdir=hsdir,
            ports=["80 127.0.0.1:4321"],
            version=3,
        )

        # arrange HS_DESC callbacks so we get the hs instance back
        cb = protocol.events['HS_DESC']
        cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id))
        cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id))

        hs = yield hs_d
        hs.ports = ["443 127.0.0.1:443"]
        self.assertEqual(1, len(hs.ports))
Exemplo n.º 8
0
 def setUp(self):
     from txtorcon import endpoints
     endpoints._global_tor_config = None
     del endpoints._global_tor_lock
     endpoints._global_tor_lock = defer.DeferredLock()
     self.reactor = FakeReactorTcp(self)
     self.protocol = FakeControlProtocol([])
     self.protocol.event_happened('INFO', 'something craaaaaaazy')
     self.protocol.event_happened(
         'INFO',
         'connection_dir_client_reached_eof(): Uploaded rendezvous '
         'descriptor (status 200 ("Service descriptor (v2) stored"))'
     )
     self.config = TorConfig(self.protocol)
     self.protocol.answers.append(
         'config/names=\nHiddenServiceOptions Virtual\nControlPort LineList'
     )
     self.protocol.answers.append('HiddenServiceOptions')
     # why do i have to pass a dict for this V but not this ^
     self.protocol.answers.append({'ControlPort': '37337'})
     self.patcher = patch(
         'txtorcon.controller.find_tor_binary',
         return_value='/not/tor'
     )
     self.patcher.start()
Exemplo n.º 9
0
def setupCollector(tor_process_protocol):
    def setup_complete(port):
        print("Exposed collector Tor hidden service on httpo://%s"
              % port.onion_uri)

    tempfile.tempdir = os.path.join(_repo_dir, 'tmp')
    if not os.path.isdir(tempfile.gettempdir()):
        os.makedirs(tempfile.gettempdir())
    _temp_dir = tempfile.mkdtemp()

    if config.main.tor_datadir is None:
        log.warn("Option 'tor_datadir' in oonib.conf is unspecified!")
        log.msg("Creating tmp directory in current directory for datadir.")
        log.debug("Using %s" % _temp_dir)
        datadir = _temp_dir
    else:
        datadir = config.main.tor_datadir

    torconfig = TorConfig(tor_process_protocol.tor_protocol)
    public_port = 80
    # XXX there is currently a bug in txtorcon that prevents data_dir from
    # being passed properly. Details on the bug can be found here:
    # https://github.com/meejah/txtorcon/pull/22
    hs_endpoint = TCPHiddenServiceEndpoint(reactor, torconfig, public_port,
                                           data_dir=datadir)
    hidden_service = hs_endpoint.listen(reportingBackend)
    hidden_service.addCallback(setup_complete)
    hidden_service.addErrback(txSetupFailed)
Exemplo n.º 10
0
 def setUp(self):
     reactor = Mock()
     proto = Mock()
     directlyProvides(proto, ITorControlProtocol)
     self.cfg = TorConfig()
     self.cfg.HidServAuth = ["existing.onion some_token"]
     self.tor = Tor(reactor, proto, _tor_config=self.cfg)
Exemplo n.º 11
0
    def test_parse_user_path(self, ftb):
        # this makes sure we expand users and symlinks in
        # hiddenServiceDir args. see Issue #77

        # make sure we have a valid thing from get_global_tor without
        # actually launching tor
        config = TorConfig()
        config.post_bootstrap = defer.succeed(config)
        from txtorcon import torconfig
        torconfig._global_tor_config = None
        get_global_tor(
            self.reactor,
            _tor_launcher=lambda react, config, prog: defer.succeed(config)
        )
        ep = serverFromString(
            self.reactor,
            'onion:88:localPort=1234:hiddenServiceDir=~/blam/blarg'
        )
        # would be nice to have a fixed path here, but then would have
        # to run as a known user :/
        # maybe using the docker stuff to run integration tests better here?
        self.assertEqual(
            os.path.expanduser('~/blam/blarg'),
            ep.hidden_service_dir
        )
Exemplo n.º 12
0
    def test_onion_keys(self):
        # FIXME test without crapping on filesystem
        self.protocol.answers.append('HiddenServiceDir=/fake/path\n')
        d = tempfile.mkdtemp()

        try:
            with open(os.path.join(d, 'hostname'), 'w') as f:
                f.write('public')
            with open(os.path.join(d, 'private_key'), 'w') as f:
                f.write('private')
            with open(os.path.join(d, 'client_keys'), 'w') as f:
                f.write('client-name hungry\ndescriptor-cookie omnomnom\n')

            conf = TorConfig(self.protocol)
            hs = HiddenService(conf, d, [])

            self.assertEqual(hs.hostname, 'public')
            self.assertEqual(hs.private_key, 'private')
            self.assertEqual(len(hs.client_keys), 1)
            self.assertEqual(hs.client_keys[0].name, 'hungry')
            self.assertEqual(hs.client_keys[0].cookie, 'omnomnom')
            self.assertEqual(hs.client_keys[0].key, None)

        finally:
            shutil.rmtree(d, ignore_errors=True)
Exemplo n.º 13
0
 def test_immediate_hiddenservice_append(self):
     '''issue #88. we check that a .append(hs) works on a blank TorConfig'''
     conf = TorConfig()
     hs = HiddenService(conf, '/dev/null', ['80 127.0.0.1:1234'])
     conf.HiddenServices.append(hs)
     self.assertEqual(len(conf.HiddenServices), 1)
     self.assertEqual(conf.HiddenServices[0], hs)
Exemplo n.º 14
0
 def test_iteration(self):
     conf = TorConfig()
     conf.SOCKSPort = 9876
     conf.save()
     x = list(conf)
     self.assertEqual(x, ['SOCKSPort'])
     conf.save()
Exemplo n.º 15
0
    def test_set_dir(self):
        protocol = FakeControlProtocol([])
        config = TorConfig(protocol)
        hsdir0 = self.mktemp()
        os.mkdir(hsdir0)
        hsdir1 = self.mktemp()
        os.mkdir(hsdir1)

        with open(join(hsdir0, "hostname"), "w") as f:
            f.write('{}.onion'.format(_test_onion_id))

        hs_d = FilesystemOnionService.create(
            Mock(),
            config,
            hsdir=hsdir0,
            ports=["80 127.0.0.1:4321"],
            version=3,
        )

        # arrange HS_DESC callbacks so we get the hs instance back
        cb = protocol.events['HS_DESC']
        cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id))
        cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id))

        hs = yield hs_d

        hs.dir = hsdir1
        self.assertEqual(hs.dir, hsdir1)
Exemplo n.º 16
0
def startTor():
    def updates(prog, tag, summary):
        print("%d%%: %s" % (prog, summary))

    tempfile.tempdir = os.path.join(_repo_dir, 'tmp')
    if not os.path.isdir(tempfile.gettempdir()):
        os.makedirs(tempfile.gettempdir())
    _temp_dir = tempfile.mkdtemp()

    torconfig = TorConfig()
    torconfig.SocksPort = config.main.socks_port
    if config.main.tor2webmode:
        torconfig.Tor2webMode = 1
        torconfig.CircuitBuildTimeout = 60
    if config.main.tor_datadir is None:
        log.warn("Option 'tor_datadir' in oonib.conf is unspecified!")
        log.msg("Creating tmp directory in current directory for datadir.")
        log.debug("Using %s" % _temp_dir)
        datadir = _temp_dir
    else:
        datadir = config.main.tor_datadir
    torconfig.DataDirectory = datadir
    torconfig.save()
    if config.main.tor_binary is not None:
        d = launch_tor(torconfig, reactor,
                       tor_binary=config.main.tor_binary,
                       progress_updates=updates)
    else:
        d = launch_tor(torconfig, reactor, progress_updates=updates)
    d.addCallback(setupCollector, datadir)
    if ooniBouncer:
        d.addCallback(setupBouncer, datadir)
    d.addErrback(txSetupFailed)
Exemplo n.º 17
0
    def test_ephemeral_bad_return_value(self):
        protocol = FakeControlProtocol([])
        config = TorConfig(protocol)
        progress_messages = []

        def progress(*args):
            progress_messages.append(args)
        eph_d = EphemeralOnionService.create(
            Mock(),
            config,
            ports=["80 127.0.0.1:80"],
            progress=progress,
            private_key=DISCARD,
        )

        cmd, d = protocol.commands[0]
        self.assertEqual(u"ADD_ONION NEW:BEST Port=80,127.0.0.1:80 Flags=DiscardPK", cmd)

        d.callback("BadKey=nothing")

        def check(f):
            self.assertIn("Expected ADD_ONION to return ServiceID", str(f.value))
            return None
        eph_d.addCallbacks(self.fail, check)
        return eph_d
Exemplo n.º 18
0
    def test_tor_connection_fails(self):
        """
        We fail to connect once, and then successfully connect --
        testing whether we're retrying properly on each Bootstrapped
        line from stdout.
        """

        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        class Connector:
            count = 0

            def __call__(self, proto, trans):
                self.count += 1
                if self.count < 2:
                    return defer.fail(error.CannotListenError(None, None, None))

                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo')
        d.addCallback(self.setup_complete_fails)
        return self.assertFailure(d, Exception)
Exemplo n.º 19
0
    def test_tor_version_v3_progress(self):
        protocol = FakeControlProtocol([])
        config = TorConfig(protocol)
        hsdir = self.mktemp()
        os.mkdir(hsdir)
        with open(join(hsdir, "hostname"), "w") as f:
            f.write('{}.onion'.format(_test_onion_id))

        def my_progress(a, b, c):
            pass

        eph_d = FilesystemOnionService.create(
            Mock(),
            config,
            hsdir,
            ports=["80 127.0.0.1:80"],
            progress=my_progress,
            version=3,
        )

        # arrange HS_DESC callbacks so we get the hs instance back
        cb = protocol.events['HS_DESC']
        cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id))
        cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id))

        yield eph_d
Exemplo n.º 20
0
    def test_tor_connection_user_data_dir(self):
        """
        Test that we don't delete a user-supplied data directory.
        """

        config = TorConfig()
        config.OrPort = 1234

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')

        my_dir = tempfile.mkdtemp(prefix='tortmp')
        config.DataDirectory = my_dir
        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo')

        def still_have_data_dir(proto, tester):
            proto.cleanup()  # FIXME? not really unit-testy as this is sort of internal function
            tester.assertTrue(os.path.exists(my_dir))
            delete_file_or_tree(my_dir)

        d.addCallback(still_have_data_dir, self)
        d.addErrback(self.fail)
        return d
Exemplo n.º 21
0
    def test_tor_connection_default_control_port(self):
        """
        Confirm a default control-port is set if not user-supplied.
        """

        config = TorConfig()

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')

        def check_control_port(proto, tester):
            ## ensure ControlPort was set to a default value
            tester.assertEquals(config.ControlPort, 9052)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d
Exemplo n.º 22
0
    def test_tor_connection_user_control_port(self):
        """
        Confirm we use a user-supplied control-port properly
        """

        config = TorConfig()
        config.OrPort = 1234
        config.ControlPort = 4321

        class Connector:
            def __call__(self, proto, trans):
                proto._set_valid_events('STATUS_CLIENT')
                proto.makeConnection(trans)
                proto.post_bootstrap.callback(proto)
                return proto.post_bootstrap

        def on_protocol(proto):
            proto.outReceived('Bootstrapped 90%\n')
            proto.outReceived('Bootstrapped 100%\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(Connector(), self.protocol, self.transport)
        d = launch_tor(config, FakeReactor(self, trans, on_protocol), connection_creator=creator, tor_binary='/bin/echo')

        def check_control_port(proto, tester):
            ## we just want to ensure launch_tor() didn't mess with
            ## the controlport we set
            tester.assertEquals(config.ControlPort, 4321)

        d.addCallback(check_control_port, self)
        d.addErrback(self.fail)
        return d
Exemplo n.º 23
0
 def test_float_parser_error(self):
     self.protocol.answers.append('config/names=\nfoo Float\nOK')
     self.protocol.answers.append({'foo': '1.23fff'})
     TorConfig(self.protocol)
     errs = self.flushLoggedErrors(ValueError)
     self.assertEqual(len(errs), 1)
     self.assertTrue(isinstance(errs[0].value, ValueError))
Exemplo n.º 24
0
    def test_successful_launch_tcp_control(self, ftb):
        """
        full end-to-end test of a launch, faking things out at a "lower
        level" than most of the other tests
        """
        trans = FakeProcessTransport()

        def on_protocol(proto):
            pass

        reactor = FakeReactor(self, trans, on_protocol, [1, 2, 3])

        def connect_tcp(host, port, factory, timeout=0, bindAddress=None):
            addr = Mock()
            factory.doStart()
            proto = factory.buildProtocol(addr)
            tpp = proto._wrappedProtocol
            tpp.add_event_listener = self._fake_event_listener
            tpp.queue_command = self._fake_queue
            proto.makeConnection(Mock())
            return proto

        reactor.connectTCP = connect_tcp

        config = TorConfig()

        tor = yield launch(reactor,
                           _tor_config=config,
                           control_port='1234',
                           timeout=30)
        self.assertTrue(isinstance(tor, Tor))
Exemplo n.º 25
0
    def test_options_hidden(self):
        self.protocol.answers.append(
            'HiddenServiceDir=/fake/path\nHiddenServicePort=80 127.0.0.1:1234\n'
        )

        conf = TorConfig(self.protocol)
        self.assertTrue('HiddenServiceOptions' not in conf.config)
        self.assertEqual(len(conf.HiddenServices), 1)

        self.assertTrue(not conf.needs_save())
        conf.hiddenservices.append(
            HiddenService(conf, '/some/dir', '80 127.0.0.1:2345', 'auth', 2))
        conf.hiddenservices[0].ports.append('443 127.0.0.1:443')
        self.assertTrue(conf.needs_save())
        conf.save()

        self.assertEqual(len(self.protocol.sets), 7)
        self.assertEqual(self.protocol.sets[0],
                         ('HiddenServiceDir', '/fake/path'))
        self.assertEqual(self.protocol.sets[1],
                         ('HiddenServicePort', '80 127.0.0.1:1234'))
        self.assertEqual(self.protocol.sets[2],
                         ('HiddenServicePort', '443 127.0.0.1:443'))
        self.assertEqual(self.protocol.sets[3],
                         ('HiddenServiceDir', '/some/dir'))
        self.assertEqual(self.protocol.sets[4],
                         ('HiddenServicePort', '80 127.0.0.1:2345'))
        self.assertEqual(self.protocol.sets[5], ('HiddenServiceVersion', '2'))
        self.assertEqual(self.protocol.sets[6],
                         ('HiddenServiceAuthorizeClient', 'auth'))
Exemplo n.º 26
0
    def test_parse_relative_path(self):
        # this makes sure we convert a relative path to absolute
        # hiddenServiceDir args. see Issue #77

        # make sure we have a valid thing from get_global_tor without
        # actually launching tor
        config = TorConfig()
        config.post_bootstrap = defer.succeed(config)
        from txtorcon import torconfig
        torconfig._global_tor_config = None
        get_global_tor(
            self.reactor,
            _tor_launcher=lambda react, config, prog: defer.succeed(config))

        orig = os.path.realpath('.')
        try:
            with util.TempDir() as t:
                t = str(t)
                os.chdir(t)
                os.mkdir(os.path.join(t, 'foo'))
                hsdir = os.path.join(t, 'foo', 'blam')
                os.mkdir(hsdir)

                ep = serverFromString(
                    self.reactor,
                    'onion:88:localPort=1234:hiddenServiceDir=foo/blam')
                self.assertEqual(os.path.realpath(hsdir),
                                 ep.hidden_service_dir)

        finally:
            os.chdir(orig)
Exemplo n.º 27
0
 def test_add_hidden_service_to_empty_config(self):
     conf = TorConfig()
     h = HiddenService(conf, '/fake/path', ['80 127.0.0.1:1234'], '', 3)
     conf.hiddenservices.append(h)
     self.assertEqual(len(conf.hiddenservices), 1)
     self.assertEqual(h, conf.hiddenservices[0])
     self.assertTrue(conf.needs_save())
Exemplo n.º 28
0
    def test_stealth_auth(self):
        '''
        make sure we produce a HiddenService instance with stealth-auth
        lines if we had authentication specified in the first place.
        '''

        config = TorConfig(self.protocol)
        ep = TCPHiddenServiceEndpoint(self.reactor,
                                      config,
                                      123,
                                      '/dev/null',
                                      stealth_auth=['alice', 'bob'])

        # make sure listen() correctly configures our hidden-serivce
        # with the explicit directory we passed in above
        d = ep.listen(NoOpProtocolFactory())

        def foo(fail):
            print "ERROR", fail

        d.addErrback(foo)
        port = yield d
        self.assertEqual(1, len(config.HiddenServices))
        self.assertEqual(config.HiddenServices[0].dir, '/dev/null')
        self.assertEqual(config.HiddenServices[0].authorize_client[0],
                         'stealth alice,bob')
        self.assertEqual(None, ep.onion_uri)
        config.HiddenServices[0].hostname = 'oh my'
        self.assertEqual('oh my', ep.onion_uri)
Exemplo n.º 29
0
    def test_tor_produces_stderr_output(self):
        config = TorConfig()
        config.OrPort = 1234
        config.SocksPort = 9999

        def connector(proto, trans):
            proto._set_valid_events('STATUS_CLIENT')
            proto.makeConnection(trans)
            proto.post_bootstrap.callback(proto)
            return proto.post_bootstrap

        def on_protocol(proto):
            proto.errReceived('Something went horribly wrong!\n')

        trans = FakeProcessTransport()
        trans.protocol = self.protocol
        self.othertrans = trans
        creator = functools.partial(connector, self.protocol, self.transport)
        d = launch_tor(config,
                       FakeReactor(self, trans, on_protocol),
                       connection_creator=creator,
                       tor_binary='/bin/echo')
        d.addCallback(self.fail)  # should't get callback
        d.addErrback(self.setup_fails_stderr)
        return d
Exemplo n.º 30
0
    def test_tor_version_v3_progress_await_all(self):
        protocol = FakeControlProtocol([])
        config = TorConfig(protocol)
        hsdir = self.mktemp()
        os.mkdir(hsdir)
        with open(join(hsdir, "hostname"), "w") as f:
            f.write('{}.onion'.format(_test_onion_id))

        class Bad(Exception):
            pass

        def my_progress(a, b, c):
            raise Bad("it's bad")

        eph_d = FilesystemOnionService.create(
            Mock(),
            config,
            hsdir,
            ports=["80 127.0.0.1:80"],
            progress=my_progress,
            version=3,
            await_all_uploads=True,
        )

        # arrange HS_DESC callbacks so we get the hs instance back
        cb = protocol.events['HS_DESC']
        cb('UPLOAD {} UNKNOWN hsdir0'.format(_test_onion_id))
        cb('UPLOADED {} UNKNOWN hsdir0'.format(_test_onion_id))

        yield eph_d
        errs = self.flushLoggedErrors(Bad)
        self.assertEqual(3, len(errs))  # because there's a "100%" one too