mirror of https://github.com/oxen-io/lokinet
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
7.6 KiB
Python
264 lines
7.6 KiB
Python
#!/usr/bin/env python3
|
|
|
|
from http.server import ThreadingHTTPServer as Server
|
|
from http.server import BaseHTTPRequestHandler as BaseHandler
|
|
|
|
|
|
bootstrapFromURL = True
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
bootstrapFromURL = False
|
|
|
|
import ctypes
|
|
from ctypes.util import find_library
|
|
|
|
import selectors
|
|
import socket
|
|
|
|
import os
|
|
|
|
class ResultStruct(ctypes.Structure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
("err", ctypes.c_int),
|
|
("local_address", ctypes.c_char * 256),
|
|
("local_port", ctypes.c_int),
|
|
("stream_id", ctypes.c_int)
|
|
]
|
|
|
|
def __repr__(self):
|
|
return "<Result err={} addr={} port={} id={}>".format(self.err, self.local_address, self.local_port, self.stream_id)
|
|
|
|
|
|
class LNContext(ctypes.Structure):
|
|
pass
|
|
|
|
class Context:
|
|
"""
|
|
wrapper around liblokinet
|
|
"""
|
|
|
|
def __init__(self, debug=False):
|
|
self._ln = ctypes.CDLL(find_library("lokinet"))
|
|
self._c = ctypes.CDLL(find_library("c"))
|
|
self._ln.lokinet_context_new.restype = ctypes.POINTER(LNContext)
|
|
self._ln.lokinet_address.restype = ctypes.c_char_p
|
|
self._ln.lokinet_address.argtypes = (ctypes.POINTER(LNContext), )
|
|
self._ln.lokinet_outbound_stream.restype = ctypes.POINTER(ResultStruct)
|
|
self._ln.lokinet_outbound_stream.argtypes = (ctypes.POINTER(ResultStruct), ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(LNContext))
|
|
self._ctx = self._ln.lokinet_context_new()
|
|
self._addrmap = dict()
|
|
self._debug = debug
|
|
lvl = 'none'
|
|
if self._debug:
|
|
lvl = 'debug'
|
|
self._ln.lokinet_log_level(ctypes.create_string_buffer(lvl.encode('ascii')))
|
|
|
|
|
|
def free(self, ptr):
|
|
self._c.free(ptr)
|
|
|
|
def add_bootstrap(self, data):
|
|
ptr = ctypes.create_string_buffer(data)
|
|
ptrlen = ctypes.c_size_t(len(data))
|
|
return self.ln_call("lokinet_add_bootstrap_rc", ptr, ptrlen)
|
|
|
|
def wait_for_ready(self, ms):
|
|
return self.ln_call("lokinet_wait_for_ready", ms) == 0
|
|
|
|
def ready(self):
|
|
return self.ln_call("lokinet_status") == 0
|
|
|
|
def addr(self):
|
|
return self._ln.lokinet_address(self._ctx).decode('ascii')
|
|
|
|
def expose(self, port):
|
|
return self.ln_call('lokinet_inbound_stream', port)
|
|
|
|
def ln_call(self, funcname, *args):
|
|
args += (self._ctx,)
|
|
if self._debug:
|
|
print("call {}{}".format(funcname, args))
|
|
return self._ln[funcname](*args)
|
|
|
|
def expose(self, port):
|
|
port = int(port)
|
|
print("exposing loopback port: {}".format(port))
|
|
return self.ln_call("lokinet_inbound_stream", port)
|
|
|
|
def start(self):
|
|
return self.ln_call("lokinet_context_start")
|
|
|
|
def stop(self):
|
|
self.ln_call("lokinet_context_stop")
|
|
|
|
def hasAddr(self, addr):
|
|
return addr in self._addrmap
|
|
|
|
def putAddr(self, addr, val):
|
|
self._addrmap[addr] = val
|
|
|
|
def getAddr(self, addr):
|
|
if addr in self._addrmap:
|
|
return self._addrmap[addr]
|
|
|
|
def delAddr(self, addr):
|
|
if addr in self._addrmap:
|
|
del self._addrmap[addr]
|
|
|
|
def __del__(self):
|
|
self.stop()
|
|
self._ln_call("lokinet_context_free")
|
|
|
|
def set_netid(self, netid):
|
|
self._ln.lokinet_set_netid(ctypes.create_string_buffer(netid.encode('ascii')))
|
|
|
|
class Stream:
|
|
|
|
def __init__(self, ctx):
|
|
self._ctx = ctx
|
|
self._id = None
|
|
|
|
def connect(self, remote):
|
|
result = ResultStruct()
|
|
self._ctx.ln_call("lokinet_outbound_stream", ctypes.cast(ctypes.addressof(result), ctypes.POINTER(ResultStruct)), ctypes.create_string_buffer(remote.encode()), ctypes.c_char_p(0))
|
|
|
|
if result.err:
|
|
print(result.err)
|
|
return
|
|
addr = result.local_address.decode('ascii')
|
|
port = result.local_port
|
|
self._id = result.stream_id
|
|
print("connection to {} made via {}:{} via {}".format(remote, addr, port, self._id))
|
|
return addr, port
|
|
|
|
|
|
def close(self):
|
|
if self._id is not None:
|
|
self._ctx.ln_call("lokinet_close_stream", self._id)
|
|
|
|
def read_and_forward_or_close(readfd, writefd):
|
|
read = 0
|
|
while True:
|
|
data = os.read(readfd, 1024)
|
|
read += len(data)
|
|
if data and len(data) > 0:
|
|
writefd.write(data)
|
|
writefd.flush()
|
|
return True
|
|
else:
|
|
return read > 0
|
|
|
|
ctx = None
|
|
|
|
|
|
class Handler(BaseHandler):
|
|
|
|
def do_CONNECT(self):
|
|
self.connect(self.path)
|
|
|
|
def connect(self, host):
|
|
global ctx
|
|
if not ctx.ready():
|
|
self.send_error(501)
|
|
return
|
|
|
|
if not ctx.hasAddr(host):
|
|
stream = Stream(ctx)
|
|
result = None
|
|
try:
|
|
result = stream.connect(host)
|
|
except:
|
|
pass
|
|
if not result:
|
|
self.send_error(503)
|
|
return
|
|
ctx.putAddr(host, result)
|
|
|
|
sock = socket.socket()
|
|
try:
|
|
sock.connect(ctx.getAddr(host))
|
|
except:
|
|
ctx.delAddr(host)
|
|
self.send_error(504)
|
|
return
|
|
|
|
self.send_response_only(200)
|
|
self.end_headers()
|
|
sel = selectors.DefaultSelector()
|
|
sock.setblocking(False)
|
|
sockfd = sock.makefile('rwb')
|
|
sel.register(self.rfile.fileno(), selectors.EVENT_READ, lambda x : read_and_forward_or_close(x, sockfd))
|
|
sel.register(sock.fileno(), selectors.EVENT_READ, lambda x : read_and_forward_or_close(x, self.wfile))
|
|
while True:
|
|
events = sel.select(1)
|
|
if not events:
|
|
continue
|
|
for key, mask in events:
|
|
if not key.data(key.fileobj):
|
|
sel.unregister(self.rfile)
|
|
sel.unregister(sock)
|
|
sel.close()
|
|
return
|
|
|
|
import os
|
|
import sys
|
|
from argparse import ArgumentParser as AP
|
|
|
|
ap = AP()
|
|
ap.add_argument("--ip", type=str, help="ip to bind to", default="127.0.0.1")
|
|
ap.add_argument("--port", type=int, help="port to bind to", default=3000)
|
|
ap.add_argument("--expose", type=int, help="expose a port to loopback")
|
|
ap.add_argument("--bootstrap", type=str, help="bootstrap file", default="bootstrap.signed")
|
|
ap.add_argument("--netid", type=str, help="override network id")
|
|
ap.add_argument("--debug", action="store_const", const=True, default=False, help="enable verose logging")
|
|
if bootstrapFromURL:
|
|
ap.add_argument("--bootstrap-url", type=str, help="bootstrap from remote url", default="https://seed.lokinet.org/lokinet.signed")
|
|
|
|
args = ap.parse_args()
|
|
addr = (args.ip, args.port)
|
|
server = Server(addr, Handler)
|
|
|
|
ctx = Context(args.debug)
|
|
|
|
if args.netid is not None:
|
|
print("overriding netid: {}".format(args.netid))
|
|
ctx.set_netid(args.netid)
|
|
|
|
if os.path.exists(args.bootstrap):
|
|
with open(args.bootstrap, 'rb') as f:
|
|
if ctx.add_bootstrap(f.read()) == 0:
|
|
print("loaded {}".format(args.bootstrap))
|
|
|
|
if args.bootstrap_url is not None:
|
|
print('getting bootstrap info from {}'.format(args.bootstrap_url))
|
|
resp = requests.get(args.bootstrap_url)
|
|
if resp.status_code == 200 and ctx.add_bootstrap(resp.content) == 0:
|
|
pass
|
|
else:
|
|
print("failed")
|
|
|
|
print("starting up...")
|
|
if ctx.start() != 0:
|
|
print("failed to start")
|
|
ctx.stop()
|
|
sys.exit(-1)
|
|
|
|
id = None
|
|
|
|
try:
|
|
while not ctx.wait_for_ready(500):
|
|
print("waiting for lokinet...")
|
|
lokiaddr = ctx.addr()
|
|
print("we are {}".format(lokiaddr))
|
|
if args.expose:
|
|
id = ctx.expose(args.expose)
|
|
print("exposed {}:{}".format(lokiaddr, args.expose))
|
|
print("serving on {}:{}".format(*addr))
|
|
server.serve_forever()
|
|
finally:
|
|
if id is not None:
|
|
ctx.ln_call("lokinet_close_stream", id)
|
|
ctx.stop()
|