MiNode/minode/i2p/controller.py

101 lines
3.2 KiB
Python

# -*- coding: utf-8 -*-
import base64
import logging
import os
import socket
import threading
import time
from .util import receive_line, pub_from_priv
class I2PController(threading.Thread):
def __init__(self, state, host='127.0.0.1', port=7656, dest_priv=b''):
super().__init__(name='I2P Controller')
self.state = state
self.host = host
self.port = port
self.nick = b'MiNode_' + base64.b16encode(os.urandom(4)).lower()
while True:
try:
self.s = socket.create_connection((self.host, self.port))
break
except ConnectionRefusedError:
logging.error(
'Error while connecting to I2P SAM bridge. Retrying.')
time.sleep(10)
self.version_reply = []
self.init_connection()
if dest_priv:
self.dest_priv = dest_priv
self.dest_pub = pub_from_priv(dest_priv)
else:
self.dest_priv = b''
self.dest_pub = b''
self.generate_destination()
self.create_session()
def _receive_line(self):
line = receive_line(self.s)
# logging.debug('I2PController <- %s', line)
return line
def _send(self, command):
# logging.debug('I2PController -> %s', command)
self.s.sendall(command)
def init_connection(self):
self._send(b'HELLO VERSION MIN=3.0 MAX=3.3\n')
self.version_reply = self._receive_line().split()
assert b'RESULT=OK' in self.version_reply
def generate_destination(self):
if b'VERSION=3.0' in self.version_reply:
# We will now receive old DSA_SHA1 destination :(
self._send(b'DEST GENERATE\n')
else:
self._send(b'DEST GENERATE SIGNATURE_TYPE=EdDSA_SHA512_Ed25519\n')
reply = self._receive_line().split()
for par in reply:
if par.startswith(b'PUB='):
self.dest_pub = par.replace(b'PUB=', b'')
if par.startswith(b'PRIV='):
self.dest_priv = par.replace(b'PRIV=', b'')
assert self.dest_priv
def create_session(self):
self._send(
b'SESSION CREATE STYLE=STREAM ID=' + self.nick
+ b' inbound.length=' + str(self.state.i2p_tunnel_length).encode()
+ b' outbound.length=' + str(self.state.i2p_tunnel_length).encode()
+ b' DESTINATION=' + self.dest_priv + b'\n')
reply = self._receive_line().split()
if b'RESULT=OK' not in reply:
logging.warning(reply)
logging.warning(
'We could not create I2P session, retrying in 5 seconds.')
time.sleep(5)
self.create_session()
def run(self):
self.s.settimeout(1)
while True:
if not self.state.shutting_down:
try:
msg = self._receive_line().split(b' ')
if msg[0] == b'PING':
self._send(b'PONG ' + msg[1] + b'\n')
except socket.timeout:
pass
else:
logging.debug('Shutting down I2P Controller')
self.s.close()
break