2021-07-27 18:20:01 +00:00
|
|
|
"""
|
|
|
|
Integration tests for I2P support.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import sys
|
|
|
|
from os.path import join, exists
|
2023-03-27 18:06:16 +00:00
|
|
|
from os import mkdir, environ
|
2021-07-27 18:20:01 +00:00
|
|
|
from time import sleep
|
2023-03-27 18:06:16 +00:00
|
|
|
from shutil import which
|
2023-05-04 20:54:14 +00:00
|
|
|
from uuid import uuid4
|
|
|
|
from subprocess import check_call
|
2021-07-27 18:20:01 +00:00
|
|
|
|
|
|
|
from eliot import log_call
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
import pytest_twisted
|
|
|
|
|
|
|
|
from . import util
|
|
|
|
|
|
|
|
from twisted.python.filepath import (
|
|
|
|
FilePath,
|
|
|
|
)
|
2023-05-04 20:54:14 +00:00
|
|
|
from twisted.internet.error import ProcessExitedAlready, ProcessTerminated
|
2021-07-27 18:20:01 +00:00
|
|
|
from allmydata.test.common import (
|
|
|
|
write_introducer,
|
|
|
|
)
|
2023-05-08 19:09:34 +00:00
|
|
|
from allmydata.client import read_config
|
2023-04-14 21:44:52 +00:00
|
|
|
|
2021-07-27 18:20:01 +00:00
|
|
|
|
|
|
|
if which("docker") is None:
|
|
|
|
pytest.skip('Skipping I2P tests since Docker is unavailable', allow_module_level=True)
|
2021-07-30 15:06:28 +00:00
|
|
|
# Docker on Windows machines sometimes expects Windows-y Docker images, so just
|
|
|
|
# don't bother.
|
|
|
|
if sys.platform.startswith('win'):
|
|
|
|
pytest.skip('Skipping I2P tests on Windows', allow_module_level=True)
|
2021-07-27 18:20:01 +00:00
|
|
|
|
|
|
|
|
2021-07-30 15:08:56 +00:00
|
|
|
@pytest.fixture
|
2021-07-27 18:20:01 +00:00
|
|
|
def i2p_network(reactor, temp_dir, request):
|
|
|
|
"""Fixture to start up local i2pd."""
|
2023-04-27 14:23:06 +00:00
|
|
|
proto = util._MagicTextProtocol("ephemeral keys", "i2pd")
|
2023-05-04 20:54:14 +00:00
|
|
|
container_name = str(uuid4())
|
2021-07-27 18:20:01 +00:00
|
|
|
reactor.spawnProcess(
|
|
|
|
proto,
|
|
|
|
which("docker"),
|
|
|
|
(
|
2023-05-04 20:54:14 +00:00
|
|
|
"docker", "run", "-p", "7656:7656",
|
|
|
|
f"--name={container_name}",
|
|
|
|
"purplei2p/i2pd:release-2.45.1",
|
2021-07-27 18:20:01 +00:00
|
|
|
# Bad URL for reseeds, so it can't talk to other routers.
|
|
|
|
"--reseed.urls", "http://localhost:1/",
|
2023-02-06 22:48:32 +00:00
|
|
|
# Make sure we see the "ephemeral keys message"
|
|
|
|
"--log=stdout",
|
|
|
|
"--loglevel=info"
|
2021-07-27 18:20:01 +00:00
|
|
|
),
|
2023-03-27 18:06:16 +00:00
|
|
|
env=environ,
|
2021-07-27 18:20:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def cleanup():
|
2023-05-04 20:54:14 +00:00
|
|
|
check_call(["docker", "container", "kill", container_name])
|
2021-07-27 18:20:01 +00:00
|
|
|
request.addfinalizer(cleanup)
|
|
|
|
|
2021-07-29 14:02:02 +00:00
|
|
|
util.block_with_timeout(proto.magic_seen, reactor, timeout=30)
|
|
|
|
|
2021-07-27 18:20:01 +00:00
|
|
|
|
2021-07-30 15:08:56 +00:00
|
|
|
@pytest.fixture
|
2021-07-27 18:20:01 +00:00
|
|
|
@log_call(
|
|
|
|
action_type=u"integration:i2p:introducer",
|
|
|
|
include_args=["temp_dir", "flog_gatherer"],
|
|
|
|
include_result=False,
|
|
|
|
)
|
|
|
|
def i2p_introducer(reactor, temp_dir, flog_gatherer, request):
|
|
|
|
intro_dir = join(temp_dir, 'introducer_i2p')
|
|
|
|
print("making introducer", intro_dir)
|
|
|
|
|
|
|
|
if not exists(intro_dir):
|
|
|
|
mkdir(intro_dir)
|
|
|
|
done_proto = util._ProcessExitedProtocol()
|
|
|
|
util._tahoe_runner_optional_coverage(
|
|
|
|
done_proto,
|
|
|
|
reactor,
|
|
|
|
request,
|
|
|
|
(
|
|
|
|
'create-introducer',
|
|
|
|
'--listen=i2p',
|
|
|
|
intro_dir,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
pytest_twisted.blockon(done_proto.done)
|
|
|
|
|
|
|
|
# over-write the config file with our stuff
|
2023-04-14 21:27:19 +00:00
|
|
|
config = read_config(intro_dir, "tub.port")
|
|
|
|
config.set_config("node", "nickname", "introducer_i2p")
|
|
|
|
config.set_config("node", "web.port", "4563")
|
|
|
|
config.set_config("node", "log_gatherer.furl", flog_gatherer)
|
2021-07-27 18:20:01 +00:00
|
|
|
|
|
|
|
# "tahoe run" is consistent across Linux/macOS/Windows, unlike the old
|
|
|
|
# "start" command.
|
2023-04-27 14:23:06 +00:00
|
|
|
protocol = util._MagicTextProtocol('introducer running', "introducer")
|
2021-07-27 18:20:01 +00:00
|
|
|
transport = util._tahoe_runner_optional_coverage(
|
|
|
|
protocol,
|
|
|
|
reactor,
|
|
|
|
request,
|
|
|
|
(
|
|
|
|
'run',
|
|
|
|
intro_dir,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
def cleanup():
|
|
|
|
try:
|
|
|
|
transport.signalProcess('TERM')
|
|
|
|
util.block_with_timeout(protocol.exited, reactor)
|
|
|
|
except ProcessExitedAlready:
|
|
|
|
pass
|
|
|
|
request.addfinalizer(cleanup)
|
|
|
|
|
|
|
|
pytest_twisted.blockon(protocol.magic_seen)
|
|
|
|
return transport
|
|
|
|
|
|
|
|
|
2021-07-30 15:08:56 +00:00
|
|
|
@pytest.fixture
|
2021-07-27 18:20:01 +00:00
|
|
|
def i2p_introducer_furl(i2p_introducer, temp_dir):
|
|
|
|
furl_fname = join(temp_dir, 'introducer_i2p', 'private', 'introducer.furl')
|
|
|
|
while not exists(furl_fname):
|
|
|
|
print("Don't see {} yet".format(furl_fname))
|
|
|
|
sleep(.1)
|
|
|
|
furl = open(furl_fname, 'r').read()
|
|
|
|
return furl
|
|
|
|
|
|
|
|
|
2023-05-08 17:54:48 +00:00
|
|
|
@pytest_twisted.ensureDeferred
|
|
|
|
async def test_i2p_service_storage(reactor, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl):
|
|
|
|
carol = await _create_anonymous_node(reactor, 'carol_i2p', 8008, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl)
|
|
|
|
dave = await _create_anonymous_node(reactor, 'dave_i2p', 8009, request, temp_dir, flog_gatherer, i2p_network, i2p_introducer_furl)
|
|
|
|
await util.await_client_ready(carol, minimum_number_of_servers=2, timeout=60)
|
|
|
|
await util.await_client_ready(dave, minimum_number_of_servers=2, timeout=60)
|
|
|
|
|
2021-07-27 18:20:01 +00:00
|
|
|
# ensure both nodes are connected to "a grid" by uploading
|
|
|
|
# something via carol, and retrieve it using dave.
|
|
|
|
gold_path = join(temp_dir, "gold")
|
|
|
|
with open(gold_path, "w") as f:
|
|
|
|
f.write(
|
|
|
|
"The object-capability model is a computer security model. A "
|
|
|
|
"capability describes a transferable right to perform one (or "
|
|
|
|
"more) operations on a given object."
|
|
|
|
)
|
|
|
|
# XXX could use treq or similar to POST these to their respective
|
|
|
|
# WUIs instead ...
|
|
|
|
|
|
|
|
proto = util._CollectOutputProtocol()
|
|
|
|
reactor.spawnProcess(
|
|
|
|
proto,
|
|
|
|
sys.executable,
|
|
|
|
(
|
|
|
|
sys.executable, '-b', '-m', 'allmydata.scripts.runner',
|
2021-07-30 15:27:41 +00:00
|
|
|
'-d', join(temp_dir, 'carol_i2p'),
|
2021-07-27 18:20:01 +00:00
|
|
|
'put', gold_path,
|
2023-03-27 18:06:16 +00:00
|
|
|
),
|
|
|
|
env=environ,
|
2021-07-27 18:20:01 +00:00
|
|
|
)
|
2023-05-08 17:54:48 +00:00
|
|
|
await proto.done
|
2021-07-27 18:20:01 +00:00
|
|
|
cap = proto.output.getvalue().strip().split()[-1]
|
|
|
|
print("TEH CAP!", cap)
|
|
|
|
|
|
|
|
proto = util._CollectOutputProtocol(capture_stderr=False)
|
|
|
|
reactor.spawnProcess(
|
|
|
|
proto,
|
|
|
|
sys.executable,
|
|
|
|
(
|
|
|
|
sys.executable, '-b', '-m', 'allmydata.scripts.runner',
|
2021-07-30 15:27:41 +00:00
|
|
|
'-d', join(temp_dir, 'dave_i2p'),
|
2021-07-27 18:20:01 +00:00
|
|
|
'get', cap,
|
2023-03-27 18:06:16 +00:00
|
|
|
),
|
|
|
|
env=environ,
|
2021-07-27 18:20:01 +00:00
|
|
|
)
|
2023-05-08 17:54:48 +00:00
|
|
|
await proto.done
|
2021-07-27 18:20:01 +00:00
|
|
|
|
|
|
|
dave_got = proto.output.getvalue().strip()
|
|
|
|
assert dave_got == open(gold_path, 'rb').read().strip()
|
|
|
|
|
|
|
|
|
2023-05-08 17:54:48 +00:00
|
|
|
async def _create_anonymous_node(reactor, name, control_port, request, temp_dir, flog_gatherer, i2p_network, introducer_furl):
|
2021-07-27 18:20:01 +00:00
|
|
|
node_dir = FilePath(temp_dir).child(name)
|
|
|
|
web_port = "tcp:{}:interface=localhost".format(control_port + 2000)
|
|
|
|
|
|
|
|
print("creating", node_dir.path)
|
|
|
|
node_dir.makedirs()
|
|
|
|
proto = util._DumpOutputProtocol(None)
|
|
|
|
reactor.spawnProcess(
|
|
|
|
proto,
|
|
|
|
sys.executable,
|
|
|
|
(
|
|
|
|
sys.executable, '-b', '-m', 'allmydata.scripts.runner',
|
|
|
|
'create-node',
|
|
|
|
'--nickname', name,
|
|
|
|
'--introducer', introducer_furl,
|
|
|
|
'--hide-ip',
|
2023-05-08 19:09:34 +00:00
|
|
|
'--listen=i2p',
|
|
|
|
'--shares-needed', '1',
|
|
|
|
'--shares-happy', '1',
|
|
|
|
'--shares-total', '2',
|
|
|
|
"--webport=" + web_port,
|
2021-07-27 18:20:01 +00:00
|
|
|
node_dir.path,
|
2023-03-27 18:06:16 +00:00
|
|
|
),
|
|
|
|
env=environ,
|
2021-07-27 18:20:01 +00:00
|
|
|
)
|
2023-05-08 17:54:48 +00:00
|
|
|
await proto.done
|
2021-07-27 18:20:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Which services should this client connect to?
|
|
|
|
write_introducer(node_dir, "default", introducer_furl)
|
2023-05-08 19:09:34 +00:00
|
|
|
|
|
|
|
config = read_config(node_dir.path, "tub.port")
|
|
|
|
config.set_config("node", "log_gatherer.furl", flog_gatherer)
|
|
|
|
|
|
|
|
print("running...")
|
2023-05-08 17:54:48 +00:00
|
|
|
node = await util._run_node(reactor, node_dir.path, request, None)
|
2021-07-27 18:20:01 +00:00
|
|
|
print("okay, launched")
|
2023-05-08 17:54:48 +00:00
|
|
|
return node
|