source: trunk/src/allmydata/test/test_tor_provider.py @ 9b6067d

Last change on this file since 9b6067d was 9b6067d, checked in by Itamar Turner-Trauring <itamar@…>, at 2021-05-11T17:18:03Z

Make test match reality, and fix corresponding bug.

  • Property mode set to 100644
File size: 26.5 KB
Line 
1"""
2Ported to Python 3.
3"""
4from __future__ import absolute_import
5from __future__ import division
6from __future__ import print_function
7from __future__ import unicode_literals
8
9from future.utils import PY2
10if PY2:
11    from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min  # noqa: F401
12
13import os
14from twisted.trial import unittest
15from twisted.internet import defer, error
16from six.moves import StringIO
17from six import ensure_str
18import mock
19from ..util import tor_provider
20from ..scripts import create_node, runner
21from foolscap.eventual import flushEventualQueue
22
23def mock_txtorcon(txtorcon):
24    return mock.patch("allmydata.util.tor_provider._import_txtorcon",
25                      return_value=txtorcon)
26
27def mock_tor(tor):
28    return mock.patch("allmydata.util.tor_provider._import_tor",
29                      return_value=tor)
30
31def make_cli_config(basedir, *argv):
32    parent = runner.Options()
33    cli_config = create_node.CreateNodeOptions()
34    cli_config.parent = parent
35    cli_config.parseOptions(argv)
36    cli_config["basedir"] = basedir
37    cli_config.stdout = StringIO()
38    return cli_config
39
40class TryToConnect(unittest.TestCase):
41    def test_try(self):
42        reactor = object()
43        txtorcon = mock.Mock()
44        tor_state = object()
45        d = defer.succeed(tor_state)
46        txtorcon.build_tor_connection = mock.Mock(return_value=d)
47        ep = object()
48        stdout = StringIO()
49        with mock.patch("allmydata.util.tor_provider.clientFromString",
50                        return_value=ep) as cfs:
51            d = tor_provider._try_to_connect(reactor, "desc", stdout, txtorcon)
52        r = self.successResultOf(d)
53        self.assertIs(r, tor_state)
54        cfs.assert_called_with(reactor, "desc")
55        txtorcon.build_tor_connection.assert_called_with(ep)
56
57    def test_try_handled_error(self):
58        reactor = object()
59        txtorcon = mock.Mock()
60        d = defer.fail(error.ConnectError("oops"))
61        txtorcon.build_tor_connection = mock.Mock(return_value=d)
62        ep = object()
63        stdout = StringIO()
64        with mock.patch("allmydata.util.tor_provider.clientFromString",
65                        return_value=ep) as cfs:
66            d = tor_provider._try_to_connect(reactor, "desc", stdout, txtorcon)
67        r = self.successResultOf(d)
68        self.assertIs(r, None)
69        cfs.assert_called_with(reactor, "desc")
70        txtorcon.build_tor_connection.assert_called_with(ep)
71        self.assertEqual(stdout.getvalue(),
72                         "Unable to reach Tor at 'desc': "
73                         "An error occurred while connecting: oops.\n")
74
75    def test_try_unhandled_error(self):
76        reactor = object()
77        txtorcon = mock.Mock()
78        d = defer.fail(ValueError("oops"))
79        txtorcon.build_tor_connection = mock.Mock(return_value=d)
80        ep = object()
81        stdout = StringIO()
82        with mock.patch("allmydata.util.tor_provider.clientFromString",
83                        return_value=ep) as cfs:
84            d = tor_provider._try_to_connect(reactor, "desc", stdout, txtorcon)
85        f = self.failureResultOf(d)
86        self.assertIsInstance(f.value, ValueError)
87        self.assertEqual(str(f.value), "oops")
88        cfs.assert_called_with(reactor, "desc")
89        txtorcon.build_tor_connection.assert_called_with(ep)
90        self.assertEqual(stdout.getvalue(), "")
91
92class LaunchTor(unittest.TestCase):
93    def _do_test_launch(self, tor_executable):
94        reactor = object()
95        private_dir = "private"
96        txtorcon = mock.Mock()
97        tpp = mock.Mock
98        tpp.tor_protocol = mock.Mock()
99        txtorcon.launch_tor = mock.Mock(return_value=tpp)
100
101        with mock.patch("allmydata.util.tor_provider.allocate_tcp_port",
102                        return_value=999999):
103            d = tor_provider._launch_tor(reactor, tor_executable, private_dir,
104                                         txtorcon)
105        tor_control_endpoint, tor_control_proto = self.successResultOf(d)
106        self.assertIs(tor_control_proto, tpp.tor_protocol)
107
108    def test_launch(self):
109        return self._do_test_launch(None)
110    def test_launch_executable(self):
111        return self._do_test_launch("mytor")
112
113class ConnectToTor(unittest.TestCase):
114    def _do_test_connect(self, endpoint, reachable):
115        reactor = object()
116        txtorcon = object()
117        args = []
118        if endpoint:
119            args = ["--tor-control-port=%s" % endpoint]
120        cli_config = make_cli_config("basedir", "--listen=tor", *args)
121        stdout = cli_config.stdout
122        expected_port = "tcp:127.0.0.1:9151"
123        if endpoint:
124            expected_port = endpoint
125        tor_state = mock.Mock
126        tor_state.protocol = object()
127        tried = []
128        def _try_to_connect(reactor, port, stdout, txtorcon):
129            tried.append( (reactor, port, stdout, txtorcon) )
130            if not reachable:
131                return defer.succeed(None)
132            if port == expected_port: # second one on the list
133                return defer.succeed(tor_state)
134            return defer.succeed(None)
135
136        with mock.patch("allmydata.util.tor_provider._try_to_connect",
137                        _try_to_connect):
138            d = tor_provider._connect_to_tor(reactor, cli_config, txtorcon)
139        if not reachable:
140            f = self.failureResultOf(d)
141            self.assertIsInstance(f.value, ValueError)
142            self.assertEqual(str(f.value),
143                             "unable to reach any default Tor control port")
144            return
145        successful_port, tor_control_proto = self.successResultOf(d)
146        self.assertEqual(successful_port, expected_port)
147        self.assertIs(tor_control_proto, tor_state.protocol)
148        expected = [(reactor, "unix:/var/run/tor/control", stdout, txtorcon),
149                    (reactor, "tcp:127.0.0.1:9051", stdout, txtorcon),
150                    (reactor, "tcp:127.0.0.1:9151", stdout, txtorcon),
151                    ]
152        if endpoint:
153            expected = [(reactor, endpoint, stdout, txtorcon)]
154        self.assertEqual(tried, expected)
155
156    def test_connect(self):
157        return self._do_test_connect(None, True)
158    def test_connect_endpoint(self):
159        return self._do_test_connect("tcp:other:port", True)
160    def test_connect_unreachable(self):
161        return self._do_test_connect(None, False)
162
163
164class CreateOnion(unittest.TestCase):
165    def test_no_txtorcon(self):
166        with mock.patch("allmydata.util.tor_provider._import_txtorcon",
167                        return_value=None):
168            d = tor_provider.create_config("reactor", "cli_config")
169            f = self.failureResultOf(d)
170            self.assertIsInstance(f.value, ValueError)
171            self.assertEqual(str(f.value),
172                             "Cannot create onion without txtorcon. "
173                             "Please 'pip install tahoe-lafs[tor]' to fix this.")
174    def _do_test_launch(self, executable):
175        basedir = self.mktemp()
176        os.mkdir(basedir)
177        private_dir = os.path.join(basedir, "private")
178        os.mkdir(private_dir)
179        reactor = object()
180        args = ["--listen=tor", "--tor-launch"]
181        if executable:
182            args.append("--tor-executable=%s" % executable)
183        cli_config = make_cli_config(basedir, *args)
184        protocol = object()
185        launch_tor = mock.Mock(return_value=defer.succeed(("control_endpoint",
186                                                           protocol)))
187        txtorcon = mock.Mock()
188        ehs = mock.Mock()
189        # This appears to be a native string in the real txtorcon object...
190        ehs.private_key = ensure_str("privkey")
191        ehs.hostname = "ONION.onion"
192        txtorcon.EphemeralHiddenService = mock.Mock(return_value=ehs)
193        ehs.add_to_tor = mock.Mock(return_value=defer.succeed(None))
194        ehs.remove_from_tor = mock.Mock(return_value=defer.succeed(None))
195
196        with mock_txtorcon(txtorcon):
197            with mock.patch("allmydata.util.tor_provider._launch_tor",
198                            launch_tor):
199                with mock.patch("allmydata.util.tor_provider.allocate_tcp_port",
200                                return_value=999999):
201                    d = tor_provider.create_config(reactor, cli_config)
202        tahoe_config_tor, tor_port, tor_location = self.successResultOf(d)
203
204        launch_tor.assert_called_with(reactor, executable,
205                                      os.path.abspath(private_dir), txtorcon)
206        txtorcon.EphemeralHiddenService.assert_called_with("3457 127.0.0.1:999999")
207        ehs.add_to_tor.assert_called_with(protocol)
208        ehs.remove_from_tor.assert_called_with(protocol)
209
210        expected = {"launch": "true",
211                    "onion": "true",
212                    "onion.local_port": "999999",
213                    "onion.external_port": "3457",
214                    "onion.private_key_file": os.path.join("private",
215                                                           "tor_onion.privkey"),
216                    }
217        if executable:
218            expected["tor.executable"] = executable
219        self.assertEqual(tahoe_config_tor, expected)
220        self.assertEqual(tor_port, "tcp:999999:interface=127.0.0.1")
221        self.assertEqual(tor_location, "tor:ONION.onion:3457")
222        fn = os.path.join(basedir, tahoe_config_tor["onion.private_key_file"])
223        with open(fn, "rb") as f:
224            privkey = f.read()
225        self.assertEqual(privkey, b"privkey")
226
227    def test_launch(self):
228        return self._do_test_launch(None)
229    def test_launch_executable(self):
230        return self._do_test_launch("mytor")
231
232    def test_control_endpoint(self):
233        basedir = self.mktemp()
234        os.mkdir(basedir)
235        private_dir = os.path.join(basedir, "private")
236        os.mkdir(private_dir)
237        reactor = object()
238        cli_config = make_cli_config(basedir, "--listen=tor")
239        protocol = object()
240        connect_to_tor = mock.Mock(return_value=defer.succeed(("goodport",
241                                                               protocol)))
242        txtorcon = mock.Mock()
243        ehs = mock.Mock()
244        ehs.private_key = b"privkey"
245        ehs.hostname = "ONION.onion"
246        txtorcon.EphemeralHiddenService = mock.Mock(return_value=ehs)
247        ehs.add_to_tor = mock.Mock(return_value=defer.succeed(None))
248        ehs.remove_from_tor = mock.Mock(return_value=defer.succeed(None))
249
250        with mock_txtorcon(txtorcon):
251            with mock.patch("allmydata.util.tor_provider._connect_to_tor",
252                            connect_to_tor):
253                with mock.patch("allmydata.util.tor_provider.allocate_tcp_port",
254                                return_value=999999):
255                    d = tor_provider.create_config(reactor, cli_config)
256        tahoe_config_tor, tor_port, tor_location = self.successResultOf(d)
257
258        connect_to_tor.assert_called_with(reactor, cli_config, txtorcon)
259        txtorcon.EphemeralHiddenService.assert_called_with("3457 127.0.0.1:999999")
260        ehs.add_to_tor.assert_called_with(protocol)
261        ehs.remove_from_tor.assert_called_with(protocol)
262
263        expected = {"control.port": "goodport",
264                    "onion": "true",
265                    "onion.local_port": "999999",
266                    "onion.external_port": "3457",
267                    "onion.private_key_file": os.path.join("private",
268                                                           "tor_onion.privkey"),
269                    }
270        self.assertEqual(tahoe_config_tor, expected)
271        self.assertEqual(tor_port, "tcp:999999:interface=127.0.0.1")
272        self.assertEqual(tor_location, "tor:ONION.onion:3457")
273        fn = os.path.join(basedir, tahoe_config_tor["onion.private_key_file"])
274        with open(fn, "rb") as f:
275            privkey = f.read()
276        self.assertEqual(privkey, b"privkey")
277
278
279_None = object()
280class FakeConfig(dict):
281    def get_config(self, section, option, default=_None, boolean=False):
282        if section != "tor":
283            raise ValueError(section)
284        value = self.get(option, default)
285        if value is _None:
286            raise KeyError
287        return value
288
289    def get_config_path(self, *args):
290        return os.path.join(self.get("basedir", "basedir"), *args)
291
292
293class EmptyContext(object):
294    def __init__(self):
295        pass
296    def __enter__(self):
297        pass
298    def __exit__(self, type, value, traceback):
299        pass
300
301class Provider(unittest.TestCase):
302    def test_build(self):
303        tor_provider.create("reactor", FakeConfig())
304
305    def test_handler_disabled(self):
306        p = tor_provider.create("reactor", FakeConfig(enabled=False))
307        self.assertEqual(p.get_tor_handler(), None)
308
309    def test_handler_no_tor(self):
310        with mock_tor(None):
311            p = tor_provider.create("reactor", FakeConfig())
312        self.assertEqual(p.get_tor_handler(), None)
313
314    def test_handler_launch_no_txtorcon(self):
315        with mock_txtorcon(None):
316            p = tor_provider.create("reactor", FakeConfig(launch=True))
317        self.assertEqual(p.get_tor_handler(), None)
318
319    @defer.inlineCallbacks
320    def test_handler_launch(self):
321        reactor = object()
322        tor = mock.Mock()
323        txtorcon = mock.Mock()
324        handler = object()
325        tor.control_endpoint_maker = mock.Mock(return_value=handler)
326        tor.add_context = mock.Mock(return_value=EmptyContext())
327        with mock_tor(tor):
328            with mock_txtorcon(txtorcon):
329                p = tor_provider.create(reactor, FakeConfig(launch=True))
330        h = p.get_tor_handler()
331        self.assertIs(h, handler)
332        tor.control_endpoint_maker.assert_called_with(p._make_control_endpoint,
333                                                      takes_status=True)
334
335        # make sure Tor is launched just once, the first time an endpoint is
336        # requested, and never again. The clientFromString() function is
337        # called once each time.
338
339        ep_desc = object()
340        launch_tor = mock.Mock(return_value=defer.succeed((ep_desc,None)))
341        ep = object()
342        cfs = mock.Mock(return_value=ep)
343        with mock.patch("allmydata.util.tor_provider._launch_tor", launch_tor):
344            with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
345                d = p._make_control_endpoint(reactor,
346                                             update_status=lambda status: None)
347                yield flushEventualQueue()
348                self.assertIs(self.successResultOf(d), ep)
349                launch_tor.assert_called_with(reactor, None,
350                                              os.path.join("basedir", "private"),
351                                              txtorcon)
352                cfs.assert_called_with(reactor, ep_desc)
353
354        launch_tor2 = mock.Mock(return_value=defer.succeed((ep_desc,None)))
355        cfs2 = mock.Mock(return_value=ep)
356        with mock.patch("allmydata.util.tor_provider._launch_tor", launch_tor2):
357            with mock.patch("allmydata.util.tor_provider.clientFromString", cfs2):
358                d2 = p._make_control_endpoint(reactor,
359                                              update_status=lambda status: None)
360                yield flushEventualQueue()
361                self.assertIs(self.successResultOf(d2), ep)
362                self.assertEqual(launch_tor2.mock_calls, [])
363                cfs2.assert_called_with(reactor, ep_desc)
364
365    def test_handler_socks_endpoint(self):
366        """
367        If not configured otherwise, the Tor provider returns a Socks-based
368        handler.
369        """
370        tor = mock.Mock()
371        handler = object()
372        tor.socks_endpoint = mock.Mock(return_value=handler)
373        ep = object()
374        cfs = mock.Mock(return_value=ep)
375        reactor = object()
376
377        with mock_tor(tor):
378            p = tor_provider.create(reactor,
379                                    FakeConfig(**{"socks.port": "ep_desc"}))
380            with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
381                h = p.get_tor_handler()
382        cfs.assert_called_with(reactor, "ep_desc")
383        tor.socks_endpoint.assert_called_with(ep)
384        self.assertIs(h, handler)
385
386    def test_handler_socks_unix_endpoint(self):
387        """
388        ``socks.port`` can be configured as a UNIX client endpoint.
389        """
390        tor = mock.Mock()
391        handler = object()
392        tor.socks_endpoint = mock.Mock(return_value=handler)
393        ep = object()
394        cfs = mock.Mock(return_value=ep)
395        reactor = object()
396
397        with mock_tor(tor):
398            p = tor_provider.create(reactor,
399                                    FakeConfig(**{"socks.port": "unix:path"}))
400            with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
401                h = p.get_tor_handler()
402        cfs.assert_called_with(reactor, "unix:path")
403        tor.socks_endpoint.assert_called_with(ep)
404        self.assertIs(h, handler)
405
406    def test_handler_socks_tcp_endpoint(self):
407        """
408        ``socks.port`` can be configured as a UNIX client endpoint.
409        """
410        tor = mock.Mock()
411        handler = object()
412        tor.socks_endpoint = mock.Mock(return_value=handler)
413        ep = object()
414        cfs = mock.Mock(return_value=ep)
415        reactor = object()
416
417        with mock_tor(tor):
418            p = tor_provider.create(reactor,
419                                    FakeConfig(**{"socks.port": "tcp:127.0.0.1:1234"}))
420            with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
421                h = p.get_tor_handler()
422        cfs.assert_called_with(reactor, "tcp:127.0.0.1:1234")
423        tor.socks_endpoint.assert_called_with(ep)
424        self.assertIs(h, handler)
425
426    def test_handler_control_endpoint(self):
427        tor = mock.Mock()
428        handler = object()
429        tor.control_endpoint = mock.Mock(return_value=handler)
430        ep = object()
431        cfs = mock.Mock(return_value=ep)
432        reactor = object()
433
434        with mock_tor(tor):
435            p = tor_provider.create(reactor,
436                                    FakeConfig(**{"control.port": "ep_desc"}))
437            with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
438                h = p.get_tor_handler()
439        self.assertIs(h, handler)
440        cfs.assert_called_with(reactor, "ep_desc")
441        tor.control_endpoint.assert_called_with(ep)
442
443    def test_handler_default(self):
444        tor = mock.Mock()
445        handler = object()
446        tor.default_socks = mock.Mock(return_value=handler)
447
448        with mock_tor(tor):
449            p = tor_provider.create("reactor", FakeConfig())
450            h = p.get_tor_handler()
451        self.assertIs(h, handler)
452        tor.default_socks.assert_called_with()
453
454class ProviderListener(unittest.TestCase):
455    def test_listener(self):
456        """Does the Tor Provider object's get_listener() method correctly
457        convert the [tor] section of tahoe.cfg into an
458        endpoint/descriptor?
459        """
460        tor = mock.Mock()
461        handler = object()
462        tor.socks_endpoint = mock.Mock(return_value=handler)
463        reactor = object()
464
465        with mock_tor(tor):
466            p = tor_provider.create(reactor,
467                                    FakeConfig(**{"onion.local_port": "321"}))
468        fake_ep = object()
469        with mock.patch("allmydata.util.tor_provider.TCP4ServerEndpoint",
470                        return_value=fake_ep) as e:
471            endpoint_or_description = p.get_listener()
472        self.assertIs(endpoint_or_description, fake_ep)
473        self.assertEqual(e.mock_calls, [mock.call(reactor, 321,
474                                                  interface="127.0.0.1")])
475
476class Provider_CheckOnionConfig(unittest.TestCase):
477    def test_default(self):
478        # default config doesn't start an onion service, so it should be
479        # happy both with and without txtorcon
480
481        p = tor_provider.create("reactor", FakeConfig())
482        p.check_onion_config()
483
484        with mock_txtorcon(None):
485            p = tor_provider.create("reactor", FakeConfig())
486            p.check_onion_config()
487
488    def test_no_txtorcon(self):
489        with mock_txtorcon(None):
490            with self.assertRaises(ValueError) as ctx:
491                tor_provider.create("reactor", FakeConfig(onion=True))
492            self.assertEqual(
493                str(ctx.exception),
494                "Cannot create onion without txtorcon. "
495                "Please 'pip install tahoe-lafs[tor]' to fix."
496            )
497
498    def test_no_launch_no_control(self):
499        """
500        onion=true but no way to launch/connect to tor
501        """
502        with self.assertRaises(ValueError) as ctx:
503            tor_provider.create("reactor", FakeConfig(onion=True))
504        self.assertEqual(
505            str(ctx.exception),
506            "[tor] onion = true, but we have neither "
507            "launch=true nor control.port="
508        )
509
510    def test_onion_no_local_port(self):
511        """
512        onion=true but no local_port configured is an error
513        """
514        with self.assertRaises(ValueError) as ctx:
515            tor_provider.create("reactor", FakeConfig(onion=True, launch=True))
516        self.assertEqual(
517            str(ctx.exception),
518            "[tor] onion = true, "
519            "but onion.local_port= is missing"
520        )
521
522    def test_onion_no_external_port(self):
523        """
524        onion=true but no external_port configured is an error
525        """
526        with self.assertRaises(ValueError) as ctx:
527            tor_provider.create("reactor",
528                                FakeConfig(onion=True, launch=True,
529                                           **{"onion.local_port": "x",
530                                           }))
531        self.assertEqual(
532            str(ctx.exception),
533            "[tor] onion = true, but onion.external_port= is missing"
534        )
535
536    def test_onion_no_private_key_file(self):
537        """
538        onion=true but no private_key_file configured is an error
539        """
540        with self.assertRaises(ValueError) as ctx:
541            tor_provider.create("reactor",
542                                FakeConfig(onion=True, launch=True,
543                                           **{"onion.local_port": "x",
544                                              "onion.external_port": "y",
545                                           }))
546        self.assertEqual(
547            str(ctx.exception),
548            "[tor] onion = true, but onion.private_key_file= is missing"
549        )
550
551    def test_ok(self):
552        p = tor_provider.create("reactor",
553                                FakeConfig(onion=True, launch=True,
554                                           **{"onion.local_port": "x",
555                                              "onion.external_port": "y",
556                                              "onion.private_key_file": "z",
557                                           }))
558        p.check_onion_config()
559
560
561class Provider_Service(unittest.TestCase):
562    def test_no_onion(self):
563        reactor = object()
564        p = tor_provider.create(reactor, FakeConfig(onion=False))
565        with mock.patch("allmydata.util.tor_provider._Provider._start_onion") as s:
566            p.startService()
567        self.assertEqual(s.mock_calls, [])
568        self.assertEqual(p.running, True)
569
570        p.stopService()
571        self.assertEqual(p.running, False)
572
573    @defer.inlineCallbacks
574    def test_launch(self):
575        basedir = self.mktemp()
576        os.mkdir(basedir)
577        fn = os.path.join(basedir, "keyfile")
578        with open(fn, "w") as f:
579            f.write("private key")
580        reactor = object()
581        cfg = FakeConfig(basedir=basedir, onion=True, launch=True,
582                         **{"onion.local_port": 123,
583                            "onion.external_port": 456,
584                            "onion.private_key_file": "keyfile",
585                            })
586
587        txtorcon = mock.Mock()
588        with mock_txtorcon(txtorcon):
589            p = tor_provider.create(reactor, cfg)
590        tor_state = mock.Mock()
591        tor_state.protocol = object()
592        ehs = mock.Mock()
593        ehs.add_to_tor = mock.Mock(return_value=defer.succeed(None))
594        ehs.remove_from_tor = mock.Mock(return_value=defer.succeed(None))
595        txtorcon.EphemeralHiddenService = mock.Mock(return_value=ehs)
596        launch_tor = mock.Mock(return_value=defer.succeed((None,tor_state.protocol)))
597        with mock.patch("allmydata.util.tor_provider._launch_tor",
598                        launch_tor):
599            d = p.startService()
600            yield flushEventualQueue()
601        self.successResultOf(d)
602        self.assertIs(p._onion_ehs, ehs)
603        self.assertIs(p._onion_tor_control_proto, tor_state.protocol)
604        launch_tor.assert_called_with(reactor, None,
605                                      os.path.join(basedir, "private"), txtorcon)
606        txtorcon.EphemeralHiddenService.assert_called_with("456 127.0.0.1:123",
607                                                           b"private key")
608        ehs.add_to_tor.assert_called_with(tor_state.protocol)
609
610        yield p.stopService()
611        ehs.remove_from_tor.assert_called_with(tor_state.protocol)
612
613    @defer.inlineCallbacks
614    def test_control_endpoint(self):
615        basedir = self.mktemp()
616        os.mkdir(basedir)
617        fn = os.path.join(basedir, "keyfile")
618        with open(fn, "w") as f:
619            f.write("private key")
620        reactor = object()
621        cfg = FakeConfig(basedir=basedir, onion=True,
622                         **{"control.port": "ep_desc",
623                            "onion.local_port": 123,
624                            "onion.external_port": 456,
625                            "onion.private_key_file": "keyfile",
626                            })
627
628        txtorcon = mock.Mock()
629        with mock_txtorcon(txtorcon):
630            p = tor_provider.create(reactor, cfg)
631        tor_state = mock.Mock()
632        tor_state.protocol = object()
633        txtorcon.build_tor_connection = mock.Mock(return_value=tor_state)
634        ehs = mock.Mock()
635        ehs.add_to_tor = mock.Mock(return_value=defer.succeed(None))
636        ehs.remove_from_tor = mock.Mock(return_value=defer.succeed(None))
637        txtorcon.EphemeralHiddenService = mock.Mock(return_value=ehs)
638        tcep = object()
639        cfs = mock.Mock(return_value=tcep)
640        with mock.patch("allmydata.util.tor_provider.clientFromString", cfs):
641            d = p.startService()
642            yield flushEventualQueue()
643        self.successResultOf(d)
644        self.assertIs(p._onion_ehs, ehs)
645        self.assertIs(p._onion_tor_control_proto, tor_state.protocol)
646        cfs.assert_called_with(reactor, "ep_desc")
647        txtorcon.build_tor_connection.assert_called_with(tcep)
648        txtorcon.EphemeralHiddenService.assert_called_with("456 127.0.0.1:123",
649                                                           b"private key")
650        ehs.add_to_tor.assert_called_with(tor_state.protocol)
651
652        yield p.stopService()
653        ehs.remove_from_tor.assert_called_with(tor_state.protocol)
Note: See TracBrowser for help on using the repository browser.