ad initial pylokinet wrapper

pull/236/head
Jeff Becker 5 years ago
parent 6064ff5a68
commit e5792087cc
No known key found for this signature in database
GPG Key ID: F357B3B42F6F9B05

@ -0,0 +1,112 @@
#
# 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.bytes()
def bdecode(bytestring):
buf = BytesIO()
buf.write(bytestring)
return BCodec(buf).decode()

@ -0,0 +1,86 @@
#!/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 lokinet import rc
import random
class RCHolder:
_dir = '/tmp/lokinet_nodedb'
_rc_files = list()
def __init__(self):
if os.path.exists(self._dir):
os.path.walk(self._dir, lambda _, _, f : self._load_subdir(f), None)
else:
os.mkdir(self._dir)
def validate_then_put(self, body):
if not rc.validate(body):
return False
k = rc.get_pubkey(body)
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 _load_subdir(self, files):
for f in files:
path = os.path.join(self._dir, f)
self._add_rc(path)
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 bad_request(msg):
return "400 Bad Request", [("Content-Type", "text/plain"), ['{}'.format(msg)]]
def status_ok(msg):
return "200 OK", [("Content-Type", "text/plain"), ['{}'.format(msg)]]
def handle_rc_upload(body):
holder = RCHolder()
if not holder.validate_then_put(body):
return bad_request('invalid rc')
return status_ok("rc accepted")
def serve_random_rc():
holder = RCHolder()
if holder.empty():
return '404 Not Found', [("Content-Type", "application/octect-stream")]
else:
return '200 OK', [("Content-Type", "application/octect-stream"), [holder.serve_random()]]
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)
elif method.upper() == "GET":
if environ.get("PATH_INFO") == "/bootstrap.signed":
return serve_random_rc()
elif environ.get("PATH_INFO") == "/ping":
return status_ok("pong")
else:
bad_request("bad path")
else:
return bad_request("invalid request")

@ -0,0 +1,167 @@
#!/usr/bin/env python3
#
# lokinet runtime wrapper
#
from ctypes import *
import configparser
import signal
import time
import threading
import os
import sys
import requests
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))
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
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()
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,21 @@
from lokinet import bencode
import pysodium
def validate(data):
rc = bencode.bdecode(data)
if 'z' not in rc or 'k' not in rc:
return False
sig = rc['z']
rc['z'] = '\x00' * 32
buf = bencode.bencode(rc)
try:
pysodium.crypto_sign_verify_detached(sig, buf, rc['k'])
except:
return False
else:
return True
def get_pubkey(data):
rc = bencode.bdecode(data)
if 'k' in rc:
return rc['k'].encode('hex')

@ -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",
requirements=["pysodium"],
packages=find_packages())
Loading…
Cancel
Save