pull/230/head
Ryan Tharp 5 years ago
commit 2e4ad12a87

@ -202,7 +202,7 @@ coverage-config: clean
$(COVERAGE_CONFIG_CMD)
coverage: coverage-config
$(MAKE) -C $(BUILD_ROOT) -j 12
$(MAKE) -C $(BUILD_ROOT)
$(TEST_EXE) || true # continue even if tests fail
mkdir -p "$(COVERAGE_OUTDIR)"
ifeq ($(CLANG),OFF)

@ -0,0 +1,4 @@
*.egg-info
v
__pycache__
dist

@ -0,0 +1,111 @@
#
# super freaking dead simple wicked awesome bencode library
#
from io import BytesIO
class BCodec:
encoding = 'utf-8'
def __init__(self, fd):
self._fd = fd
def _write_bytestring(self, bs):
self._fd.write('{}:'.format(len(bs)).encode('ascii'))
self._fd.write(bs)
def _write_list(self, l):
self._fd.write(b'l')
for item in l:
self.encode(item)
self._fd.write(b'e')
def _write_dict(self, d):
self._fd.write(b'd')
keys = list(d.keys())
keys.sort()
for k in keys:
if isinstance(k, str):
self._write_bytestring(k.encode(self.encoding))
elif isinstance(k, bytes):
self._write_bytestring(k)
else:
self._write_bytestring('{}'.format(k).encode(self.encoding))
self.encode(d[k])
self._fd.write(b'e')
def _write_int(self, i):
self._fd.write('i{}e'.format(i).encode(self.encoding))
def encode(self, obj):
if isinstance(obj, dict):
self._write_dict(obj)
elif isinstance(obj, list):
self._write_list(obj)
elif isinstance(obj, int):
self._write_int(obj)
elif isinstance(obj, str):
self._write_bytestring(obj.encode(self.encoding))
elif isinstance(obj, bytes):
self._write_bytestring(obj)
elif hasattr(obj, bencode):
obj.bencode(self._fd)
else:
raise ValueError("invalid object type")
def _readuntil(self, delim):
b = bytes()
while True:
ch = self._fd.read(1)
if ch == delim:
return b
b += ch
def _decode_list(self):
l = list()
while True:
b = self._fd.read(1)
if b == b'e':
return l
l.append(self._decode(b))
def _decode_dict(self):
d = dict()
while True:
ch = self._fd.read(1)
if ch == b'e':
return d
k = self._decode_bytestring(ch)
d[k] = self.decode()
def _decode_int(self):
return int(self._readuntil(b'e'), 10)
def _decode_bytestring(self, ch):
ch += self._readuntil(b':')
l = int(ch, base=10)
return self._fd.read(l)
def _decode(self, ch):
if ch == b'd':
return self._decode_dict()
elif ch == b'l':
return self._decode_list()
elif ch == b'i':
return self._decode_int()
elif ch in [b'0',b'1',b'2',b'3',b'4',b'5',b'6',b'7',b'8',b'9']:
return self._decode_bytestring(ch)
else:
raise ValueError(ch)
def decode(self):
return self._decode(self._fd.read(1))
def bencode(obj):
buf = BytesIO()
b = BCodec(buf)
b.encode(obj)
return buf.getvalue()
def bdecode(bytestring):
buf = BytesIO(bytestring)
return BCodec(buf).decode()

@ -0,0 +1,91 @@
#!/usr/bin/env python3
#
# python wsgi application for managing many lokinet instances
#
__doc__ = """lokinet bootserv wsgi app
run me with via gunicorn pylokinet.bootserv:app
"""
import os
from pylokinet import rc
import random
class RCHolder:
_dir = '/tmp/lokinet_nodedb/'
_rc_files = list()
def __init__(self):
if os.path.exists(self._dir):
for root, _, files in os.walk(self._dir):
for f in files:
self._add_rc(os.path.join(root, f))
else:
os.mkdir(self._dir)
def validate_then_put(self, body):
if not rc.validate(body):
return False
k = rc.get_pubkey(body)
print(k)
if k is None:
return False
with open(os.path.join(self._dir, k), "wb") as f:
f.write(body)
return True
def _add_rc(self, fpath):
self._rc_files.append(fpath)
def serve_random(self):
with open(random.choice(self._rc_files), 'rb') as f:
return f.read()
def empty(self):
return len(self._rc_files) == 0
def handle_rc_upload(body, respond):
holder = RCHolder()
if holder.validate_then_put(body):
respond("200 OK", [("Content-Type", "text/plain")])
return ["rc accepted".encode('ascii')]
else:
respond("400 Bad Request", [("Content-Type", "text/plain")])
return ["bad rc".encode('ascii')]
def serve_random_rc():
holder = RCHolder()
if holder.empty():
return None
else:
return holder.serve_random()
def response(status, msg, respond):
respond(status, [("Content-Type", "text/plain"), ("Content-Length", "{}".format(len(msg)))])
return [msg.encode("utf-8")]
def app(environ, start_response):
request_body_size = int(environ.get("CONTENT_LENGTH", 0))
method = environ.get("REQUEST_METHOD")
if method.upper() == "PUT" and request_body_size > 0:
rcbody = environ.get("wsgi.input").read(request_body_size)
return handle_rc_upload(rcbody, start_response)
elif method.upper() == "GET":
if environ.get("PATH_INFO") == "/bootstrap.signed":
resp = serve_random_rc()
if resp is not None:
start_response('200 OK', [("Content-Type", "application/octet-stream")])
return [resp]
else:
return response('404 Not Found', 'no RCs', start_response)
elif environ.get("PATH_INFO") == "/ping":
return response('200 OK', 'pong', start_response)
else:
return response('400 Bad Request', 'invalid path', start_response)
else:
return response('405 Method Not Allowed', 'method not allowed', start_response)

@ -0,0 +1,195 @@
#!/usr/bin/env python3
#
# lokinet runtime wrapper
#
from ctypes import *
import configparser
import signal
import time
import threading
import os
import sys
import requests
from pylokinet import rc
lib_file = os.path.join(os.path.realpath('.'), 'liblokinet-shared.so')
def log(msg):
sys.stderr.write("lokinet: {}\n".format(msg))
sys.stderr.flush()
class LokiNET(threading.Thread):
lib = None
ctx = 0
failed = False
up = False
asRouter = True
def configure(self, lib, conf, ip=None, port=None, ifname=None):
log("configure lib={} conf={}".format(lib, conf))
if not os.path.exists(os.path.dirname(conf)):
os.mkdir(os.path.dirname(conf))
try:
self.lib = CDLL(lib)
except OSError as ex:
log("failed to load library: {}".format(ex))
return False
if self.lib.llarp_ensure_config(conf.encode('utf-8'), os.path.dirname(conf).encode('utf-8'), True, self.asRouter):
config = configparser.ConfigParser()
config.read(conf)
log('overwrite ip="{}" port="{}" ifname="{}"'.format(ip, port, ifname))
if ip:
config['router']['public-address'] = '{}'.format(ip)
if port:
config['router']['public-port'] = '{}'.format(port)
if ifname and port:
config['bind'] = {
ifname: '{}'.format(port)
}
with open(conf, "w") as f:
config.write(f)
self.ctx = self.lib.llarp_main_init(conf.encode('utf-8'))
else:
return False
return self.lib.llarp_main_setup(self.ctx) == 0
def inform_fail(self):
"""
inform lokinet crashed
"""
self.failed = True
self._inform()
def inform_up(self):
self.up = True
self._inform()
def _inform(self):
"""
inform waiter
"""
def wait_for_up(self, timeout):
"""
wait for lokinet to go up for :timeout: seconds
:return True if we are up and running otherwise False:
"""
# return self._up.wait(timeout)
def signal(self, sig):
if self.ctx and self.lib:
self.lib.llarp_main_signal(self.ctx, int(sig))
def run(self):
# self._up.acquire()
self.up = True
code = self.lib.llarp_main_run(self.ctx)
log("llarp_main_run exited with status {}".format(code))
if code:
self.inform_fail()
self.up = False
# self._up.release()
def close(self):
if self.lib and self.ctx:
self.lib.llarp_main_free(self.ctx)
def getconf(name, fallback=None):
return name in os.environ and os.environ[name] or fallback
def main(args):
log("going up")
root = getconf("LOKINET_ROOT")
if root is None:
print("LOKINET_ROOT was not set")
return
rc_callback = getconf("LOKINET_SUBMIT_URL")
if rc_callback is None:
print("LOKINET_SUBMIT_URL was not set")
return
bootstrap = getconf("LOKINET_BOOTSTRAP_URL")
if bootstrap is None:
print("LOKINET_BOOTSTRAP_URL was not set")
lib = getconf("LOKINET_LIB", lib_file)
timeout = int(getconf("LOKINET_TIMEOUT", "5"))
ping_interval = int(getconf("LOKINET_PING_INTERVAL", "60"))
ping_callback = getconf("LOKINET_PING_URL")
ip = getconf("LOKINET_IP")
port = getconf("LOKINET_PORT")
ifname = getconf("LOKINET_IFNAME")
if ping_callback is None:
print("LOKINET_PING_URL was not set")
return
conf = os.path.join(root, "daemon.ini")
loki = LokiNET()
log("bootstrapping...")
try:
r = requests.get(bootstrap)
if r.status_code == 404:
log("bootstrap gave no RCs, we are the seed node")
elif r.status_code != 200:
raise Exception("http {}".format(r.status_code))
else:
data = r.content
if rc.validate(data):
log("valid RC obtained")
with open(os.path.join(root, "bootstrap.signed"), "wb") as f:
f.write(data)
else:
raise Exception("invalid RC")
except Exception as ex:
log("failed to bootstrap: {}".format(ex))
loki.close()
return
if loki.configure(lib, conf, ip, port, ifname):
log("configured")
loki.start()
try:
log("waiting for spawn")
while timeout > 0:
time.sleep(1)
if loki.failed:
log("failed")
break
log("waiting {}".format(timeout))
timeout -= 1
if loki.up:
log("submitting rc")
try:
with open(os.path.join(root, 'self.signed'), 'rb') as f:
r = requests.put(rc_callback, data=f.read(), headers={"content-type": "application/octect-stream"})
log('submit rc reply: HTTP {}'.format(r.status_code))
except Exception as ex:
log("failed to submit rc: {}".format(ex))
loki.signal(signal.SIGINT)
time.sleep(2)
else:
while loki.up:
time.sleep(ping_interval)
try:
r = requests.get(ping_callback)
log("ping reply: HTTP {}".format(r.status_code))
except Exception as ex:
log("failed to submit ping: {}".format(ex))
else:
log("failed to go up")
loki.signal(signal.SIGINT)
except KeyboardInterrupt:
loki.signal(signal.SIGINT)
time.sleep(2)
finally:
loki.close()
else:
loki.close()
main(sys.argv[1:])

@ -0,0 +1,23 @@
from pylokinet import bencode
import pysodium
import binascii
def validate(data):
rc = bencode.bdecode(data)
if b'z' not in rc or b'k' not in rc:
return False
sig = rc[b'z']
rc[b'z'] = b'\x00' * 64
buf = bencode.bencode(rc)
try:
k = rc[b'k']
pysodium.crypto_sign_verify_detached(sig, buf, k)
except:
return False
else:
return True
def get_pubkey(data):
rc = bencode.bdecode(data)
if b'k' in rc:
return binascii.hexlify(rc[b'k']).decode('ascii')

@ -0,0 +1,27 @@
# lokinet cluster setup
clustering lokinet with python 3
# python3 setup.py install
## bootserv
bootserv is a bootstrap server for accepting and serving RCs
$ gunicorn -b 0.0.0.0:8000 pylokinet.bootserv:app
## pylokinet instance
obtain `liblokinet-shared.so` from a lokinet build
run (root):
# export LOKINET_ROOT=/tmp/lokinet-instance/
# export LOKINET_LIB=/path/to/liblokinet-shared.so
# export LOKINET_BOOTSTRAP_URL=http://bootserv.ip.address.here:8000/bootstrap.signed
# export LOKINET_PING_URL=http://bootserv.ip.address.here:8000/ping
# export LOKINET_SUBMIT_URL=http://bootserv.ip.address.here:8000/
# export LOKINET_IP=public.ip.goes.here
# export LOKINET_PORT=1090
# export LOKINET_IFNAME=eth0
# python3 -m pylokinet.instance

@ -0,0 +1,14 @@
from setuptools import setup, find_packages
setup(
name="pylokinet",
version="0.0.1",
license="ZLIB",
author="jeff",
author_email="jeff@i2p.rocks",
description="lokinet python bindings",
url="https://github.com/loki-project/loki-network",
install_requires=["pysodium", "requests"],
packages=find_packages())

@ -47,6 +47,7 @@ namespace llarp
{
if(msg.questions.size() == 0)
return false;
// always hook ptr for ranges we own
if(msg.questions[0].qtype == dns::qTypePTR)
{
huint32_t ip;
@ -54,8 +55,10 @@ namespace llarp
return false;
return m_OurRange.Contains(ip);
}
else if(msg.questions[0].qtype == dns::qTypeA)
else if(msg.questions[0].qtype == dns::qTypeA
|| msg.questions[0].qtype == dns::qTypeCNAME)
{
// hook for forward dns or cname when using snode tld
return msg.questions[0].qname.find(".snode.")
== (msg.questions[0].qname.size() - 7);
}
@ -90,6 +93,20 @@ namespace llarp
msg.AddNXReply();
}
}
else if(msg.questions[0].qtype == dns::qTypeCNAME)
{
if(msg.questions[0].qname == "random.snode"
|| msg.questions[0].qname == "random.snode.")
{
RouterID random;
if(GetRouter()->GetRandomGoodRouter(random))
msg.AddCNAMEReply(random.ToString(), 1);
else
msg.AddNXReply();
}
else
msg.AddNXReply();
}
else if(msg.questions[0].qtype == dns::qTypeA)
{
// forward dns for snode

@ -204,7 +204,10 @@ namespace llarp
return false;
}
std::string qname = msg.questions[0].qname;
if(msg.questions[0].qtype == 15)
if(msg.questions[0].qtype == dns::qTypeCNAME)
{
}
else if(msg.questions[0].qtype == dns::qTypeMX)
{
// mx record
llarp::service::Address addr;
@ -214,7 +217,21 @@ namespace llarp
msg.AddNXReply();
reply(msg);
}
else if(msg.questions[0].qtype == 1)
else if(msg.questions[0].qtype == dns::qTypeCNAME)
{
if(msg.questions[0].qname == "random.snode"
|| msg.questions[0].qname == "random.snode.")
{
RouterID random;
if(Router()->GetRandomGoodRouter(random))
msg.AddCNAMEReply(random.ToString(), 1);
else
msg.AddNXReply();
}
else
msg.AddNXReply();
}
else if(msg.questions[0].qtype == dns::qTypeA)
{
// forward dns
llarp::service::Address addr;
@ -254,7 +271,7 @@ namespace llarp
reply(msg);
}
else if(msg.questions[0].qtype == 12)
else if(msg.questions[0].qtype == dns::qTypePTR)
{
// reverse dns
huint32_t ip = {0};
@ -300,6 +317,7 @@ namespace llarp
// always hook mx records
if(msg.questions[0].qtype == llarp::dns::qTypeMX)
return true;
// always hook random.snode
if(msg.questions[0].qname == "random.snode"
|| msg.questions[0].qname == "random.snode.")
return true;
@ -309,6 +327,7 @@ namespace llarp
// always hook .snode
if(addr.FromString(msg.questions[0].qname, ".snode"))
return true;
// hook any ranges we own
if(msg.questions[0].qtype == llarp::dns::qTypePTR)
{
huint32_t ip = {0};

@ -22,7 +22,8 @@ namespace llarp
dialect = other.dialect;
pubkey = other.pubkey;
memcpy(ip.s6_addr, other.ip.s6_addr, 16);
port = other.port;
port = other.port;
version = other.version;
return *this;
}

@ -32,13 +32,12 @@ namespace llarp
}
AddressInfo(const AddressInfo& other)
: IBEncodeMessage()
: IBEncodeMessage(other.version)
, rank(other.rank)
, dialect(other.dialect)
, pubkey(other.pubkey)
, port(other.port)
{
port = other.port;
version = other.version;
memcpy(ip.s6_addr, other.ip.s6_addr, 16);
}

@ -51,12 +51,34 @@ namespace llarp
return bencode_end(buf);
}
static bool
bdecode_ip_string(llarp_buffer_t* buf, in6_addr& ip)
{
char tmp[128] = {0};
llarp_buffer_t strbuf;
if(!bencode_read_string(buf, &strbuf))
return false;
if(strbuf.sz >= sizeof(tmp))
return false;
memcpy(tmp, strbuf.base, strbuf.sz);
tmp[strbuf.sz] = 0;
return inet_pton(AF_INET6, tmp, &ip.s6_addr[0]) == 1;
}
bool
ExitInfo::DecodeKey(__attribute__((unused)) llarp_buffer_t k,
__attribute__((unused)) llarp_buffer_t* buf)
ExitInfo::DecodeKey(llarp_buffer_t k, llarp_buffer_t* buf)
{
bool read = false;
// TODO: implement me
if(!BEncodeMaybeReadDictEntry("k", pubkey, read, k, buf))
return false;
if(!BEncodeMaybeReadDictInt("v", version, read, k, buf))
return false;
if(llarp_buffer_eq(k, "a"))
return bdecode_ip_string(buf, address);
if(llarp_buffer_eq(k, "b"))
return bdecode_ip_string(buf, netmask);
return read;
}

@ -46,11 +46,17 @@ struct TryConnectJob
{
}
~TryConnectJob()
{
}
void
Failed()
{
llarp::LogInfo("session to ", llarp::RouterID(rc.pubkey), " closed");
link->CloseSessionTo(rc.pubkey);
// delete this
router->pendingEstablishJobs.erase(rc.pubkey);
}
void
@ -101,6 +107,7 @@ static void
on_try_connecting(void *u)
{
TryConnectJob *j = static_cast< TryConnectJob * >(u);
j->Attempt();
}
@ -155,14 +162,17 @@ namespace llarp
{
if(!link->IsCompatable(remote))
continue;
auto itr = pendingEstablishJobs.emplace(
remote.pubkey,
std::make_unique< TryConnectJob >(remote, link.get(), numretries,
this));
TryConnectJob *job = itr.first->second.get();
// try establishing async
logic->queue_job({job, &on_try_connecting});
return true;
std::unique_ptr< TryConnectJob > j = std::make_unique< TryConnectJob >(
remote, link.get(), numretries, this);
auto itr = pendingEstablishJobs.emplace(remote.pubkey, std::move(j));
if(itr.second)
{
// only try establishing if we inserted a new element
TryConnectJob *job = itr.first->second.get();
// try establishing async
logic->queue_job({job, &on_try_connecting});
return true;
}
}
return false;
}
@ -461,12 +471,12 @@ namespace llarp
{
llarp::async_verify_context *ctx =
static_cast< llarp::async_verify_context * >(job->user);
ctx->router->pendingEstablishJobs.erase(job->rc.pubkey);
auto router = ctx->router;
llarp::PubKey pk(job->rc.pubkey);
router->FlushOutboundFor(pk, router->GetLinkWithSessionByPubkey(pk));
delete ctx;
router->pendingVerifyRC.erase(pk);
router->pendingEstablishJobs.erase(pk);
}
void

@ -258,10 +258,10 @@ namespace llarp
bool
RouterContact::Verify(llarp::Crypto *crypto, llarp_time_t now) const
{
static const NetID networkNetID;
if(netID != networkNetID)
if(netID != NetID::DefaultValue())
{
llarp::LogError("netid missmatch: '", netID, "' != '", networkNetID, "'");
llarp::LogError("netid missmatch: '", netID, "' != '",
NetID::DefaultValue(), "'");
return false;
}
if(IsExpired(now))

@ -162,6 +162,22 @@ namespace llarp
return last_updated < other.last_updated;
}
friend std::ostream &
operator<<(std::ostream &out, const RouterContact &rc)
{
out << "[RouterContact k=" << rc.pubkey;
out << " updated=" << rc.last_updated;
out << " netid=" << rc.netID;
out << " v=" << rc.version;
out << " ai=[ ";
for(const auto &addr : rc.addrs)
out << addr << " ";
out << " ] xi=[ ";
for(const auto &xi : rc.exits)
out << xi << " ";
return out << " ] e=" << rc.enckey << " z=" << rc.signature << " ]";
}
bool
Read(const char *fname);

@ -17,7 +17,6 @@ namespace llarp
// handles messages on the routing level
struct IMessageHandler
{
virtual bool
HandleObtainExitMessage(const ObtainExitMessage *msg,
llarp::Router *r) = 0;

@ -14,7 +14,6 @@ struct RCTest : public ::testing::Test
: crypto(llarp::Crypto::sodium{}), oldval(llarp::NetID::DefaultValue())
{
llarp::NetID::DefaultValue() = llarp::NetID(DEF_VALUE);
rc.Clear();
}
~RCTest()
@ -22,18 +21,22 @@ struct RCTest : public ::testing::Test
llarp::NetID::DefaultValue() = oldval;
}
RC_t rc;
llarp::Crypto crypto;
const llarp::NetID oldval;
};
TEST_F(RCTest, TestSignVerify)
{
llarp::NetID netid(DEF_VALUE);
RC_t rc;
SecKey_t encr;
SecKey_t sign;
crypto.encryption_keygen(encr);
crypto.identity_keygen(sign);
rc.enckey = encr.toPublic();
rc.pubkey = sign.toPublic();
ASSERT_TRUE(rc.netID == netid);
ASSERT_TRUE(rc.netID == llarp::NetID::DefaultValue());
ASSERT_TRUE(rc.Sign(&crypto, sign));
ASSERT_TRUE(rc.Verify(&crypto, llarp::time_now_ms()));
}

Loading…
Cancel
Save