Files
docker-testnet/testnet/testnet.py
2024-09-02 23:51:41 -04:00

209 lines
6.7 KiB
Python

import os
import random
import string
import tarfile
import zipfile
import tempfile
import time
import requests
from testnet import i2pcontrol
def rand_string(length=8):
"""Generate lame random hexdigest string"""
return "".join([random.choice(string.hexdigits) for _ in range(length)])
class I2pd(object):
"""i2pd node object"""
def __init__(self, container, netname):
container.reload()
self.id = container.id[:12]
self.container = container
self.netname = netname
self.ip = container.attrs['NetworkSettings']['Networks'][
self.netname]['IPAddress']
self.URLS = {
'Control': "https://{}:7650".format(self.ip),
'SAM': "{}:7656".format(self.ip),
'Webconsole': "http://{}:7070".format(self.ip),
'Proxies': {
'HTTP': "{}:4444".format(self.ip),
'Socks': "{}:4447".format(self.ip),
},
}
self.control = i2pcontrol.I2PControl(self.URLS['Control'])
self._tunnels = {}
@property
def tunnels(self):
"""Cached tunnels info"""
if not self._tunnels: self.get_tunnels_info()
return self._tunnels
def get_tunnels_info(self):
"""Fetch tunnels info from i2pcontrol"""
try:
self._tunnels = self.control.request("ClientServicesInfo",
{"I2PTunnel": ""})['result']['I2PTunnel']
except requests.exceptions.ConnectionError:
pass
def info(self):
"""Fetch info from i2pcontrol"""
try:
return self.control.request("RouterInfo",
i2pcontrol.INFO_METHODS["RouterInfo"])['result']
except requests.exceptions.ConnectionError:
return {}
def info_str(self):
"""String with verbose i2pd info"""
info = self.info()
if not info:
return "{}\t{}\tNOT READY".format(self.id, self.ip)
return "\t".join([
self.id, self.ip,
i2pcontrol.STATUS[info["i2p.router.net.status"]],
str(info["i2p.router.net.tunnels.successrate"]) + "%",
"{}/{}".format(
info["i2p.router.netdb.knownpeers"],
info["i2p.router.netdb.activepeers"]
),
"{}/{}".format(
info["i2p.router.net.total.received.bytes"],
info["i2p.router.net.total.sent.bytes"]
),
str(info["i2p.router.net.tunnels.participating"]),
])
def add_tunnel(self, name, options):
"""Add I2P tunnel"""
args_str = ""
for k, v in options.items():
args_str += "{} = {}\n".format(k, v)
cmd = '/bin/sh -c \'printf "\n[{}]\n{}\n"'.format(name, args_str) + \
' >> /home/i2pd/data/tunnels.conf\''
self.container.exec_run(cmd)
self.container.exec_run("kill -HUP 1")
self._tunnels = {}
def tunnel_destinations(self):
"""Get b32 destinations"""
destinations = []
for x in self.container.logs().split(b"\r\n"):
if b"New private keys file" in x:
destinations.append(x.split(b" ")[-2].decode())
return destinations
def __str__(self):
return "i2pd node: {} IP: {}".format(self.id, self.ip)
class Testnet(object):
"""Testnet object"""
I2P_IMAGE = "geti2p/i2p-testnet"
NETNAME = 'i2ptestnet'
NODES = {}
DEFAULT_ARGS = ""
#" --netid=7 " \
#" --i2pcontrol.enabled=true --i2pcontrol.address=0.0.0.0 --http.address=0.0.0.0 --loglevel=50"
SEED_FILE = os.path.join(tempfile.gettempdir(), 'seed.zip')
def __init__(self, docker_client):
self.cli = docker_client
for cont in self.cli.containers.list(filters={"label": "i2pd"}):
self.NODES[cont.id[:12]] = I2pd(cont, self.NETNAME)
def create_network(self):
"""Create isolated docker network"""
self.cli.networks.create(self.NETNAME, driver="bridge", internal=True)
def remove_network(self):
"""Remove docker network"""
self.cli.networks.get(self.NETNAME).remove()
def run_i2pd(self, args=None, with_seed=True, floodfill=False):
"""Start i2pd"""
i2pd_args, labels = self.DEFAULT_ARGS, ["i2pd"]
if args: i2pd_args += args
if floodfill:
i2pd_args += " -Drouter.floodfillParticipant=true "
labels += ["floodfill"]
if with_seed:
i2pd_args += "" # " --reseed.zipfile=/seed.zip "
cont = self.cli.containers.run(self.I2P_IMAGE, i2pd_args,
volumes=["{}:/seed.zip".format(self.SEED_FILE)],
labels=labels, network=self.NETNAME, detach=True, tty=True, publish_all_ports=True)
else:
labels += ["noseed"]
cont = self.cli.containers.run(self.I2P_IMAGE, i2pd_args,
labels=labels, network=self.NETNAME, detach=True, tty=True, publish_all_ports=True)
self.NODES[cont.id[:12]] = I2pd(cont, self.NETNAME)
return cont.id[:12]
def remove_i2pd(self, cid):
"""Stop and remove i2pd"""
try:
node = self.NODES.pop(cid)
except KeyError:
return
node.container.stop()
node.container.remove()
def make_seed(self, cid):
print("creating a reseed file")
"""creates a zip reseed file"""
ri_path = "/home/i2pd/data/router.info"
with tempfile.TemporaryFile() as fp:
while True:
try:
arc_data = self.NODES[cid].container.get_archive(ri_path)[0].read()
break
except:
time.sleep(0.1)
fp.write(arc_data)
fp.seek(0)
ri_file = tarfile.open(fileobj=fp, mode='r')\
.extractfile("router.info").read()
tf = tempfile.mkstemp()[1]
with open(tf, 'wb') as f: f.write(ri_file)
zf = zipfile.ZipFile(self.SEED_FILE, "w")
zf.write(tf, "ri.dat")
zf.close()
os.remove(tf)
def print_info(self):
"""Print testnet statistics"""
if self.NODES:
print("\t".join([
"CONTAINER", "IP", "STATUS", "SUCC RATE", "PEERS K/A",
"BYTES S/R", "PART. TUNNELS"
]))
for n in self.NODES.values():
print(n.info_str())
else:
print("Testnet is not running")
def stop(self):
"""Stop nodes and reseeder"""
for n in self.NODES.values():
n.container.stop()
n.container.remove()
os.remove(self.SEED_FILE)
self.NODES.clear()