source: trunk/src/allmydata/test/test_node.py

Last change on this file was b440065, checked in by Jean-Paul Calderone <exarkun@…>, at 2023-08-08T15:30:02Z

avoid trying to call os.getuid on windows

  • Property mode set to 100644
File size: 33.4 KB
Line 
1from __future__ import annotations
2
3import base64
4import os
5import stat
6import sys
7import time
8from textwrap import dedent
9import configparser
10
11from hypothesis import (
12    given,
13)
14from hypothesis.strategies import (
15    integers,
16    sets,
17)
18
19from unittest import skipIf
20
21from twisted.python.filepath import (
22    FilePath,
23)
24from twisted.python.runtime import platform
25from twisted.trial import unittest
26from twisted.internet import defer
27
28import foolscap.logging.log
29
30from twisted.application import service
31from allmydata.node import (
32    PortAssignmentRequired,
33    PrivacyError,
34    tub_listen_on,
35    create_tub_options,
36    create_main_tub,
37    create_node_dir,
38    create_default_connection_handlers,
39    create_connection_handlers,
40    config_from_string,
41    read_config,
42    MissingConfigEntry,
43    _tub_portlocation,
44    formatTimeTahoeStyle,
45    UnescapedHashError,
46)
47from allmydata.introducer.server import create_introducer
48from allmydata import client
49
50from allmydata.util import fileutil, iputil
51from allmydata.util.namespace import Namespace
52from allmydata.util.configutil import (
53    ValidConfiguration,
54    UnknownConfigError,
55)
56
57from allmydata.util.i2p_provider import create as create_i2p_provider
58from allmydata.util.tor_provider import create as create_tor_provider
59import allmydata.test.common_util as testutil
60
61from .common import (
62    ConstantAddresses,
63    SameProcessStreamEndpointAssigner,
64    UseNode,
65    superuser,
66)
67
68def port_numbers():
69    return integers(min_value=1, max_value=2 ** 16 - 1)
70
71class LoggingMultiService(service.MultiService):
72    def log(self, msg, **kw):
73        pass
74
75
76# see https://tahoe-lafs.org/trac/tahoe-lafs/ticket/2946
77def testing_tub(reactor, config_data=''):
78    """
79    Creates a 'main' Tub for testing purposes, from config data
80    """
81    basedir = 'dummy_basedir'
82    config = config_from_string(basedir, 'DEFAULT_PORTNUMFILE_BLANK', config_data)
83    fileutil.make_dirs(os.path.join(basedir, 'private'))
84
85    i2p_provider = create_i2p_provider(reactor, config)
86    tor_provider = create_tor_provider(reactor, config)
87    handlers = create_connection_handlers(config, i2p_provider, tor_provider)
88    default_connection_handlers, foolscap_connection_handlers = handlers
89    tub_options = create_tub_options(config)
90
91    main_tub = create_main_tub(
92        config, tub_options, default_connection_handlers,
93        foolscap_connection_handlers, i2p_provider, tor_provider,
94        cert_filename='DEFAULT_CERTFILE_BLANK'
95    )
96    return main_tub
97
98
99class TestCase(testutil.SignalMixin, unittest.TestCase):
100
101    def setUp(self):
102        testutil.SignalMixin.setUp(self)
103        self.parent = LoggingMultiService()
104        # We can use a made-up port number because these tests never actually
105        # try to bind the port.  We'll use a low-numbered one that's likely to
106        # conflict with another service to prove it.
107        self._available_port = 22
108        self.port_assigner = SameProcessStreamEndpointAssigner()
109        self.port_assigner.setUp()
110        self.addCleanup(self.port_assigner.tearDown)
111
112    def _test_location(
113            self,
114            expected_addresses,
115            tub_port=None,
116            tub_location=None,
117            local_addresses=None,
118    ):
119        """
120        Verify that a Tub configured with the given *tub.port* and *tub.location*
121        values generates fURLs with the given addresses in its location hints.
122
123        :param [str] expected_addresses: The addresses which must appear in
124            the generated fURL for the test to pass.  All addresses must
125            appear.
126
127        :param tub_port: If not ``None`` then a value for the *tub.port*
128            configuration item.
129
130        :param tub_location: If not ``None`` then a value for the *tub.port*
131            configuration item.
132
133        :param local_addresses: If not ``None`` then a list of addresses to
134            supply to the system under test as local addresses.
135        """
136        from twisted.internet import reactor
137
138        basedir = self.mktemp()
139        create_node_dir(basedir, "testing")
140        if tub_port is None:
141            # Always configure a usable tub.port address instead of relying on
142            # the automatic port assignment.  The automatic port assignment is
143            # prone to collisions and spurious test failures.
144            _, tub_port = self.port_assigner.assign(reactor)
145
146        config_data = "[node]\n"
147        config_data += "tub.port = {}\n".format(tub_port)
148
149        # If they wanted a certain location, go for it.  This probably won't
150        # agree with the tub.port value we set but that only matters if
151        # anything tries to use this to establish a connection ... which
152        # nothing in this test suite will.
153        if tub_location is not None:
154            config_data += "tub.location = {}\n".format(tub_location)
155
156        if local_addresses is not None:
157            self.patch(iputil, 'get_local_addresses_sync',
158                       lambda: local_addresses)
159
160        tub = testing_tub(reactor, config_data)
161
162        class Foo(object):
163            pass
164
165        furl = tub.registerReference(Foo())
166        for address in expected_addresses:
167            self.assertIn(address, furl)
168
169    def test_location1(self):
170        return self._test_location(expected_addresses=["192.0.2.0:1234"],
171                                   tub_location="192.0.2.0:1234")
172
173    def test_location2(self):
174        return self._test_location(expected_addresses=["192.0.2.0:1234", "example.org:8091"],
175                                   tub_location="192.0.2.0:1234,example.org:8091")
176
177    def test_location_not_set(self):
178        """Checks the autogenerated furl when tub.location is not set."""
179        return self._test_location(
180            expected_addresses=[
181                "127.0.0.1:{}".format(self._available_port),
182                "192.0.2.0:{}".format(self._available_port),
183            ],
184            tub_port=self._available_port,
185            local_addresses=["127.0.0.1", "192.0.2.0"],
186        )
187
188    def test_location_auto_and_explicit(self):
189        """Checks the autogenerated furl when tub.location contains 'AUTO'."""
190        return self._test_location(
191            expected_addresses=[
192                "127.0.0.1:{}".format(self._available_port),
193                "192.0.2.0:{}".format(self._available_port),
194                "example.com:4321",
195            ],
196            tub_port=self._available_port,
197            tub_location="AUTO,example.com:{}".format(self._available_port),
198            local_addresses=["127.0.0.1", "192.0.2.0", "example.com:4321"],
199        )
200
201    def test_tahoe_cfg_utf8(self):
202        basedir = "test_node/test_tahoe_cfg_utf8"
203        fileutil.make_dirs(basedir)
204        f = open(os.path.join(basedir, 'tahoe.cfg'), 'wb')
205        f.write(u"\uFEFF[node]\n".encode('utf-8'))
206        f.write(u"nickname = \u2621\n".encode('utf-8'))
207        f.close()
208
209        config = read_config(basedir, "")
210        self.failUnlessEqual(config.get_config("node", "nickname"),
211                             u"\u2621")
212
213    def test_tahoe_cfg_hash_in_name(self):
214        basedir = "test_node/test_cfg_hash_in_name"
215        nickname = "Hash#Bang!" # a clever nickname containing a hash
216        fileutil.make_dirs(basedir)
217        f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
218        f.write("[node]\n")
219        f.write("nickname = %s\n" % (nickname,))
220        f.close()
221
222        config = read_config(basedir, "")
223        self.failUnless(config.nickname == nickname)
224
225    def test_hash_in_furl(self):
226        """
227        Hashes in furl options are not allowed, resulting in exception.
228        """
229        basedir = self.mktemp()
230        fileutil.make_dirs(basedir)
231        with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
232            f.write("[node]\n")
233            f.write("log_gatherer.furl = lalal#onohash\n")
234
235        config = read_config(basedir, "")
236        with self.assertRaises(UnescapedHashError):
237            config.get_config("node", "log_gatherer.furl")
238
239    def test_missing_config_item(self):
240        """
241        If a config item is missing:
242
243        1. Given a default, return default.
244        2. Otherwise, raise MissingConfigEntry.
245        """
246        basedir = self.mktemp()
247        fileutil.make_dirs(basedir)
248        with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
249            f.write("[node]\n")
250        config = read_config(basedir, "")
251
252        self.assertEquals(config.get_config("node", "log_gatherer.furl", "def"), "def")
253        with self.assertRaises(MissingConfigEntry):
254            config.get_config("node", "log_gatherer.furl")
255
256    def test_missing_config_section(self):
257        """
258        Enumerating a missing section returns empty dict
259        """
260        basedir = self.mktemp()
261        fileutil.make_dirs(basedir)
262        with open(os.path.join(basedir, 'tahoe.cfg'), 'w'):
263            pass
264        config = read_config(basedir, "")
265        self.assertEquals(
266            config.enumerate_section("not-a-section"),
267            {}
268        )
269
270    def test_config_required(self):
271        """
272        Asking for missing (but required) configuration is an error
273        """
274        basedir = u"test_node/test_config_required"
275        config = read_config(basedir, "portnum")
276
277        with self.assertRaises(Exception):
278            config.get_config_from_file("it_does_not_exist", required=True)
279
280    def test_config_items(self):
281        """
282        All items in a config section can be retrieved.
283        """
284        basedir = u"test_node/test_config_items"
285        create_node_dir(basedir, "testing")
286
287        with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
288            f.write(dedent(
289                """
290                [node]
291                nickname = foo
292                timeout.disconnect = 12
293                """
294            ))
295        config = read_config(basedir, "portnum")
296        self.assertEqual(
297            config.items("node"),
298            [("nickname", "foo"),
299             ("timeout.disconnect", "12"),
300            ],
301        )
302        self.assertEqual(
303            config.items("node", [("unnecessary", "default")]),
304            [("nickname", "foo"),
305             ("timeout.disconnect", "12"),
306            ],
307        )
308
309
310    def test_config_items_missing_section(self):
311        """
312        If a default is given for a missing section, the default is used.
313
314        Lacking both default and section, an error is raised.
315        """
316        basedir = self.mktemp()
317        create_node_dir(basedir, "testing")
318
319        with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
320            f.write("")
321
322        config = read_config(basedir, "portnum")
323        with self.assertRaises(configparser.NoSectionError):
324            config.items("nosuch")
325        default = [("hello", "world")]
326        self.assertEqual(config.items("nosuch", default), default)
327
328    @skipIf(platform.isWindows(), "We don't know how to set permissions on Windows.")
329    @skipIf(superuser, "cannot test as superuser with all permissions")
330    def test_private_config_unreadable(self):
331        """
332        Asking for inaccessible private config is an error
333        """
334        basedir = u"test_node/test_private_config_unreadable"
335        create_node_dir(basedir, "testing")
336        config = read_config(basedir, "portnum")
337        config.get_or_create_private_config("foo", "contents")
338        fname = os.path.join(basedir, "private", "foo")
339        os.chmod(fname, 0)
340
341        with self.assertRaises(Exception):
342            config.get_or_create_private_config("foo")
343
344    @skipIf(platform.isWindows(), "We don't know how to set permissions on Windows.")
345    @skipIf(superuser, "cannot test as superuser with all permissions")
346    def test_private_config_unreadable_preexisting(self):
347        """
348        error if reading private config data fails
349        """
350        basedir = u"test_node/test_private_config_unreadable_preexisting"
351        create_node_dir(basedir, "testing")
352        config = read_config(basedir, "portnum")
353        fname = os.path.join(basedir, "private", "foo")
354        with open(fname, "w") as f:
355            f.write("stuff")
356        os.chmod(fname, 0)
357
358        with self.assertRaises(Exception):
359            config.get_private_config("foo")
360
361    def test_private_config_missing(self):
362        """
363        a missing config with no default is an error
364        """
365        basedir = u"test_node/test_private_config_missing"
366        create_node_dir(basedir, "testing")
367        config = read_config(basedir, "portnum")
368
369        with self.assertRaises(MissingConfigEntry):
370            config.get_or_create_private_config("foo")
371
372    def test_private_config(self):
373        basedir = u"test_node/test_private_config"
374        privdir = os.path.join(basedir, "private")
375        fileutil.make_dirs(privdir)
376        f = open(os.path.join(privdir, 'already'), 'wt')
377        f.write("secret")
378        f.close()
379
380        basedir = fileutil.abspath_expanduser_unicode(basedir)
381        config = config_from_string(basedir, "", "")
382
383        self.assertEqual(config.get_private_config("already"), "secret")
384        self.assertEqual(config.get_private_config("not", "default"), "default")
385        self.assertRaises(MissingConfigEntry, config.get_private_config, "not")
386        value = config.get_or_create_private_config("new", "start")
387        self.assertEqual(value, "start")
388        self.assertEqual(config.get_private_config("new"), "start")
389        counter = []
390        def make_newer():
391            counter.append("called")
392            return "newer"
393        value = config.get_or_create_private_config("newer", make_newer)
394        self.assertEqual(len(counter), 1)
395        self.assertEqual(value, "newer")
396        self.assertEqual(config.get_private_config("newer"), "newer")
397
398        value = config.get_or_create_private_config("newer", make_newer)
399        self.assertEqual(len(counter), 1) # don't call unless necessary
400        self.assertEqual(value, "newer")
401
402    @skipIf(superuser, "cannot test as superuser with all permissions")
403    def test_write_config_unwritable_file(self):
404        """
405        Existing behavior merely logs any errors upon writing
406        configuration files; this bad behavior should probably be
407        fixed to do something better (like fail entirely). See #2905
408        """
409        basedir = "test_node/configdir"
410        fileutil.make_dirs(basedir)
411        config = config_from_string(basedir, "", "")
412        with open(os.path.join(basedir, "bad"), "w") as f:
413            f.write("bad")
414        os.chmod(os.path.join(basedir, "bad"), 0o000)
415
416        config.write_config_file("bad", "some value")
417
418        errs = self.flushLoggedErrors(IOError)
419        self.assertEqual(1, len(errs))
420
421    def test_timestamp(self):
422        # this modified logger doesn't seem to get used during the tests,
423        # probably because we don't modify the LogObserver that trial
424        # installs (only the one that twistd installs). So manually exercise
425        # it a little bit.
426        t = formatTimeTahoeStyle("ignored", time.time())
427        self.failUnless("Z" in t)
428        t2 = formatTimeTahoeStyle("ignored", int(time.time()))
429        self.failUnless("Z" in t2)
430
431    def test_secrets_dir(self):
432        basedir = "test_node/test_secrets_dir"
433        create_node_dir(basedir, "testing")
434        self.failUnless(os.path.exists(os.path.join(basedir, "private")))
435
436    def test_secrets_dir_protected(self):
437        if "win32" in sys.platform.lower() or "cygwin" in sys.platform.lower():
438            # We don't know how to test that unprivileged users can't read this
439            # thing.  (Also we don't know exactly how to set the permissions so
440            # that unprivileged users can't read this thing.)
441            raise unittest.SkipTest("We don't know how to set permissions on Windows.")
442        basedir = "test_node/test_secrets_dir_protected"
443        create_node_dir(basedir, "nothing to see here")
444
445        # make sure private dir was created with correct modes
446        privdir = os.path.join(basedir, "private")
447        st = os.stat(privdir)
448        bits = stat.S_IMODE(st[stat.ST_MODE])
449        self.failUnless(bits & 0o001 == 0, bits)
450
451    @defer.inlineCallbacks
452    def test_logdir_is_str(self):
453        from twisted.internet import reactor
454
455        basedir = FilePath(self.mktemp())
456        fixture = UseNode(None, None, basedir, "pb://introducer/furl", {}, reactor=reactor)
457        fixture.setUp()
458        self.addCleanup(fixture.cleanUp)
459
460        ns = Namespace()
461        ns.called = False
462        def call_setLogDir(logdir):
463            ns.called = True
464            self.failUnless(isinstance(logdir, str), logdir)
465        self.patch(foolscap.logging.log, 'setLogDir', call_setLogDir)
466
467        yield fixture.create_node()
468        self.failUnless(ns.called)
469
470    def test_set_config_unescaped_furl_hash(self):
471        """
472        ``_Config.set_config`` raises ``UnescapedHashError`` if the item being set
473        is a furl and the value includes ``"#"`` and does not set the value.
474        """
475        basedir = self.mktemp()
476        new_config = config_from_string(basedir, "", "")
477        with self.assertRaises(UnescapedHashError):
478            new_config.set_config("foo", "bar.furl", "value#1")
479        with self.assertRaises(MissingConfigEntry):
480            new_config.get_config("foo", "bar.furl")
481
482    def test_set_config_new_section(self):
483        """
484        ``_Config.set_config`` can be called with the name of a section that does
485        not already exist to create that section and set an item in it.
486        """
487        basedir = self.mktemp()
488        new_config = config_from_string(basedir, "", "", ValidConfiguration.everything())
489        new_config.set_config("foo", "bar", "value1")
490        self.assertEqual(
491            new_config.get_config("foo", "bar"),
492            "value1"
493        )
494
495    def test_set_config_replace(self):
496        """
497        ``_Config.set_config`` can be called with a section and item that already
498        exists to change an existing value to a new one.
499        """
500        basedir = self.mktemp()
501        new_config = config_from_string(basedir, "", "", ValidConfiguration.everything())
502        new_config.set_config("foo", "bar", "value1")
503        new_config.set_config("foo", "bar", "value2")
504        self.assertEqual(
505            new_config.get_config("foo", "bar"),
506            "value2"
507        )
508
509    def test_set_config_write(self):
510        """
511        ``_Config.set_config`` persists the configuration change so it can be
512        re-loaded later.
513        """
514        # Let our nonsense config through
515        valid_config = ValidConfiguration.everything()
516        basedir = FilePath(self.mktemp())
517        basedir.makedirs()
518        cfg = basedir.child(b"tahoe.cfg")
519        cfg.setContent(b"")
520        new_config = read_config(basedir.path, "", [], valid_config)
521        new_config.set_config("foo", "bar", "value1")
522        loaded_config = read_config(basedir.path, "", [], valid_config)
523        self.assertEqual(
524            loaded_config.get_config("foo", "bar"),
525            "value1",
526        )
527
528    def test_set_config_rejects_invalid_config(self):
529        """
530        ``_Config.set_config`` raises ``UnknownConfigError`` if the section or
531        item is not recognized by the validation object and does not set the
532        value.
533        """
534        # Make everything invalid.
535        valid_config = ValidConfiguration.nothing()
536        new_config = config_from_string(self.mktemp(), "", "", valid_config)
537        with self.assertRaises(UnknownConfigError):
538            new_config.set_config("foo", "bar", "baz")
539        with self.assertRaises(MissingConfigEntry):
540            new_config.get_config("foo", "bar")
541
542
543def _stub_get_local_addresses_sync():
544    """
545    A function like ``allmydata.util.iputil.get_local_addresses_sync``.
546    """
547    return ["LOCAL"]
548
549
550def _stub_allocate_tcp_port():
551    """
552    A function like ``allmydata.util.iputil.allocate_tcp_port``.
553    """
554    return 999
555
556def _stub_none():
557    """
558    A function like ``_stub_allocate_tcp`` or ``_stub_get_local_addresses_sync``
559    but that return an empty list since ``allmydata.node._tub_portlocation`` requires a
560    callable for paramter 1 and 2 counting from 0.
561    """
562    return []
563
564
565class TestMissingPorts(unittest.TestCase):
566    """
567    Test certain ``_tub_portlocation`` error cases for ports setup.
568    """
569    def setUp(self):
570        self.basedir = self.mktemp()
571        create_node_dir(self.basedir, "testing")
572
573    def test_listen_on_zero(self):
574        """
575        ``_tub_portlocation`` raises ``PortAssignmentRequired`` called with a
576        listen address including port 0 and no interface.
577        """
578        config_data = (
579            "[node]\n"
580            "tub.port = tcp:0\n"
581        )
582        config = config_from_string(self.basedir, "portnum", config_data)
583        with self.assertRaises(PortAssignmentRequired):
584            _tub_portlocation(config, _stub_none, _stub_none)
585
586    def test_listen_on_zero_with_host(self):
587        """
588        ``_tub_portlocation`` raises ``PortAssignmentRequired`` called with a
589        listen address including port 0 and an interface.
590        """
591        config_data = (
592            "[node]\n"
593            "tub.port = tcp:0:interface=127.0.0.1\n"
594        )
595        config = config_from_string(self.basedir, "portnum", config_data)
596        with self.assertRaises(PortAssignmentRequired):
597            _tub_portlocation(config, _stub_none, _stub_none)
598
599    def test_parsing_tcp(self):
600        """
601        When ``tub.port`` is given and ``tub.location`` is **AUTO** the port
602        number from ``tub.port`` is used as the port number for the value
603        constructed for ``tub.location``.
604        """
605        config_data = (
606            "[node]\n"
607            "tub.port = tcp:777\n"
608            "tub.location = AUTO\n"
609        )
610        config = config_from_string(self.basedir, "portnum", config_data)
611
612        tubport, tublocation = _tub_portlocation(
613            config,
614            _stub_get_local_addresses_sync,
615            _stub_allocate_tcp_port,
616        )
617        self.assertEqual(tubport, "tcp:777")
618        self.assertEqual(tublocation, b"tcp:LOCAL:777")
619
620    def test_parsing_defaults(self):
621        """
622        parse empty config, check defaults
623        """
624        config_data = (
625            "[node]\n"
626        )
627        config = config_from_string(self.basedir, "portnum", config_data)
628
629        tubport, tublocation = _tub_portlocation(
630            config,
631            _stub_get_local_addresses_sync,
632            _stub_allocate_tcp_port,
633        )
634        self.assertEqual(tubport, "tcp:999")
635        self.assertEqual(tublocation, b"tcp:LOCAL:999")
636
637    def test_parsing_location_complex(self):
638        """
639        location with two options (including defaults)
640        """
641        config_data = (
642            "[node]\n"
643            "tub.location = tcp:HOST:888,AUTO\n"
644        )
645        config = config_from_string(self.basedir, "portnum", config_data)
646
647        tubport, tublocation = _tub_portlocation(
648            config,
649            _stub_get_local_addresses_sync,
650            _stub_allocate_tcp_port,
651        )
652        self.assertEqual(tubport, "tcp:999")
653        self.assertEqual(tublocation, b"tcp:HOST:888,tcp:LOCAL:999")
654
655    def test_parsing_all_disabled(self):
656        """
657        parse config with both port + location disabled
658        """
659        config_data = (
660            "[node]\n"
661            "tub.port = disabled\n"
662            "tub.location = disabled\n"
663        )
664        config = config_from_string(self.basedir, "portnum", config_data)
665
666        res = _tub_portlocation(
667            config,
668            _stub_get_local_addresses_sync,
669            _stub_allocate_tcp_port,
670        )
671        self.assertTrue(res is None)
672
673    def test_empty_tub_port(self):
674        """
675        port povided, but empty is an error
676        """
677        config_data = (
678            "[node]\n"
679            "tub.port = \n"
680        )
681        config = config_from_string(self.basedir, "portnum", config_data)
682
683        with self.assertRaises(ValueError) as ctx:
684            _tub_portlocation(
685                config,
686                _stub_get_local_addresses_sync,
687                _stub_allocate_tcp_port,
688            )
689        self.assertIn(
690            "tub.port must not be empty",
691            str(ctx.exception)
692        )
693
694    def test_empty_tub_location(self):
695        """
696        location povided, but empty is an error
697        """
698        config_data = (
699            "[node]\n"
700            "tub.location = \n"
701        )
702        config = config_from_string(self.basedir, "portnum", config_data)
703
704        with self.assertRaises(ValueError) as ctx:
705            _tub_portlocation(
706                config,
707                _stub_get_local_addresses_sync,
708                _stub_allocate_tcp_port,
709            )
710        self.assertIn(
711            "tub.location must not be empty",
712            str(ctx.exception)
713        )
714
715    def test_disabled_port_not_tub(self):
716        """
717        error to disable port but not location
718        """
719        config_data = (
720            "[node]\n"
721            "tub.port = disabled\n"
722            "tub.location = not_disabled\n"
723        )
724        config = config_from_string(self.basedir, "portnum", config_data)
725
726        with self.assertRaises(ValueError) as ctx:
727            _tub_portlocation(
728                config,
729                _stub_get_local_addresses_sync,
730                _stub_allocate_tcp_port,
731            )
732        self.assertIn(
733            "tub.port is disabled, but not tub.location",
734            str(ctx.exception)
735        )
736
737    def test_disabled_tub_not_port(self):
738        """
739        error to disable location but not port
740        """
741        config_data = (
742            "[node]\n"
743            "tub.port = not_disabled\n"
744            "tub.location = disabled\n"
745        )
746        config = config_from_string(self.basedir, "portnum", config_data)
747
748        with self.assertRaises(ValueError) as ctx:
749            _tub_portlocation(
750                config,
751                _stub_get_local_addresses_sync,
752                _stub_allocate_tcp_port,
753            )
754        self.assertIn(
755            "tub.location is disabled, but not tub.port",
756            str(ctx.exception)
757        )
758
759    def test_tub_location_tcp(self):
760        """
761        If ``reveal-IP-address`` is set to false and ``tub.location`` includes a
762        **tcp** hint then ``_tub_portlocation`` raises `PrivacyError`` because
763        TCP leaks IP addresses.
764        """
765        config = config_from_string(
766            "fake.port",
767            "no-basedir",
768            "[node]\nreveal-IP-address = false\ntub.location=tcp:hostname:1234\n",
769        )
770        with self.assertRaises(PrivacyError) as ctx:
771            _tub_portlocation(
772                config,
773                _stub_get_local_addresses_sync,
774                _stub_allocate_tcp_port,
775            )
776        self.assertEqual(
777            str(ctx.exception),
778            "tub.location includes tcp: hint",
779        )
780
781    def test_tub_location_legacy_tcp(self):
782        """
783        If ``reveal-IP-address`` is set to false and ``tub.location`` includes a
784        "legacy" hint with no explicit type (which means it is a **tcp** hint)
785        then the behavior is the same as for an explicit **tcp** hint.
786        """
787        config = config_from_string(
788            "fake.port",
789            "no-basedir",
790            "[node]\nreveal-IP-address = false\ntub.location=hostname:1234\n",
791        )
792
793        with self.assertRaises(PrivacyError) as ctx:
794            _tub_portlocation(
795                config,
796                _stub_get_local_addresses_sync,
797                _stub_allocate_tcp_port,
798            )
799
800        self.assertEqual(
801            str(ctx.exception),
802            "tub.location includes tcp: hint",
803        )
804
805
806BASE_CONFIG = """
807[tor]
808enabled = false
809[i2p]
810enabled = false
811"""
812
813NOLISTEN = """
814[node]
815tub.port = disabled
816tub.location = disabled
817"""
818
819DISABLE_STORAGE = """
820[storage]
821enabled = false
822"""
823
824ENABLE_STORAGE = """
825[storage]
826enabled = true
827"""
828
829ENABLE_HELPER = """
830[helper]
831enabled = true
832"""
833
834class FakeTub(object):
835    def __init__(self):
836        self.tubID = base64.b32encode(b"foo")
837        self.listening_ports = []
838    def setOption(self, name, value): pass
839    def removeAllConnectionHintHandlers(self): pass
840    def addConnectionHintHandler(self, hint_type, handler): pass
841    def listenOn(self, what):
842        self.listening_ports.append(what)
843    def setLocation(self, location): pass
844    def setServiceParent(self, parent): pass
845
846class Listeners(unittest.TestCase):
847
848    # Randomly allocate a couple distinct port numbers to try out.  The test
849    # never actually binds these port numbers so we don't care if they're "in
850    # use" on the system or not.  We just want a couple distinct values we can
851    # check expected results against.
852    @given(ports=sets(elements=port_numbers(), min_size=2, max_size=2))
853    def test_multiple_ports(self, ports):
854        """
855        When there are multiple listen addresses suggested by the ``tub.port`` and
856        ``tub.location`` configuration, the node's *main* port listens on all
857        of them.
858        """
859        port1, port2 = iter(ports)
860        port = ("tcp:%d:interface=127.0.0.1,tcp:%d:interface=127.0.0.1" %
861                (port1, port2))
862        location = "tcp:localhost:%d,tcp:localhost:%d" % (port1, port2)
863        t = FakeTub()
864        tub_listen_on(None, None, t, port, location)
865        self.assertEqual(t.listening_ports,
866                         ["tcp:%d:interface=127.0.0.1" % port1,
867                          "tcp:%d:interface=127.0.0.1" % port2])
868
869    def test_tor_i2p_listeners(self):
870        """
871        When configured to listen on an "i2p" or "tor" address, ``tub_listen_on``
872        tells the Tub to listen on endpoints supplied by the given Tor and I2P
873        providers.
874        """
875        t = FakeTub()
876
877        i2p_listener = object()
878        i2p_provider = ConstantAddresses(i2p_listener)
879        tor_listener = object()
880        tor_provider = ConstantAddresses(tor_listener)
881
882        tub_listen_on(
883            i2p_provider,
884            tor_provider,
885            t,
886            "listen:i2p,listen:tor",
887            "tcp:example.org:1234",
888        )
889        self.assertEqual(
890            t.listening_ports,
891            [i2p_listener, tor_listener],
892        )
893
894
895class ClientNotListening(unittest.TestCase):
896
897    @defer.inlineCallbacks
898    def test_disabled(self):
899        basedir = "test_node/test_disabled"
900        create_node_dir(basedir, "testing")
901        f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
902        f.write(BASE_CONFIG)
903        f.write(NOLISTEN)
904        f.write(DISABLE_STORAGE)
905        f.close()
906        n = yield client.create_client(basedir)
907        self.assertEqual(n.tub.getListeners(), [])
908
909    @defer.inlineCallbacks
910    def test_disabled_but_storage(self):
911        basedir = "test_node/test_disabled_but_storage"
912        create_node_dir(basedir, "testing")
913        f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
914        f.write(BASE_CONFIG)
915        f.write(NOLISTEN)
916        f.write(ENABLE_STORAGE)
917        f.close()
918        with self.assertRaises(ValueError) as ctx:
919            yield client.create_client(basedir)
920        self.assertIn(
921            "storage is enabled, but tub is not listening",
922            str(ctx.exception),
923        )
924
925    @defer.inlineCallbacks
926    def test_disabled_but_helper(self):
927        basedir = "test_node/test_disabled_but_helper"
928        create_node_dir(basedir, "testing")
929        f = open(os.path.join(basedir, 'tahoe.cfg'), 'wt')
930        f.write(BASE_CONFIG)
931        f.write(NOLISTEN)
932        f.write(DISABLE_STORAGE)
933        f.write(ENABLE_HELPER)
934        f.close()
935        with self.assertRaises(ValueError) as ctx:
936            yield client.create_client(basedir)
937        self.assertIn(
938            "helper is enabled, but tub is not listening",
939            str(ctx.exception),
940        )
941
942class IntroducerNotListening(unittest.TestCase):
943
944    @defer.inlineCallbacks
945    def test_port_none_introducer(self):
946        basedir = "test_node/test_port_none_introducer"
947        create_node_dir(basedir, "testing")
948        with open(os.path.join(basedir, 'tahoe.cfg'), 'wt') as f:
949            f.write("[node]\n")
950            f.write("tub.port = disabled\n")
951            f.write("tub.location = disabled\n")
952        with self.assertRaises(ValueError) as ctx:
953            yield create_introducer(basedir)
954        self.assertIn(
955            "we are Introducer, but tub is not listening",
956            str(ctx.exception),
957        )
958
959class Configuration(unittest.TestCase):
960
961    def setUp(self):
962        self.basedir = self.mktemp()
963        fileutil.make_dirs(self.basedir)
964
965    def test_read_invalid_config(self):
966        with open(os.path.join(self.basedir, 'tahoe.cfg'), 'w') as f:
967            f.write(
968                '[invalid section]\n'
969                'foo = bar\n'
970            )
971        with self.assertRaises(UnknownConfigError) as ctx:
972            read_config(
973                self.basedir,
974                "client.port",
975            )
976
977        self.assertIn(
978            "invalid section",
979            str(ctx.exception),
980        )
981
982    @defer.inlineCallbacks
983    def test_create_client_invalid_config(self):
984        with open(os.path.join(self.basedir, 'tahoe.cfg'), 'w') as f:
985            f.write(
986                '[invalid section]\n'
987                'foo = bar\n'
988            )
989        with self.assertRaises(UnknownConfigError) as ctx:
990            yield client.create_client(self.basedir)
991
992        self.assertIn(
993            "invalid section",
994            str(ctx.exception),
995        )
996
997
998
999class CreateDefaultConnectionHandlersTests(unittest.TestCase):
1000    """
1001    Tests for create_default_connection_handlers().
1002    """
1003
1004    def test_tcp_disabled(self):
1005        """
1006        If tcp is set to disabled, no TCP handler is set.
1007        """
1008        config = config_from_string("", "", dedent("""
1009        [connections]
1010        tcp = disabled
1011        """))
1012        default_handlers = create_default_connection_handlers(
1013            config,
1014            {},
1015        )
1016        self.assertIs(default_handlers["tcp"], None)
Note: See TracBrowser for help on using the repository browser.