MiNode/minode/main.py

328 lines
11 KiB
Python
Raw Normal View History

2016-06-30 08:11:33 +00:00
# -*- coding: utf-8 -*-
2022-09-22 22:54:12 +00:00
"""Functions for starting the program"""
2017-06-04 10:11:19 +00:00
import argparse
import base64
2016-06-30 08:11:33 +00:00
import csv
import logging
import multiprocessing
import os
2016-06-30 08:11:33 +00:00
import pickle
2016-10-15 14:12:09 +00:00
import signal
2016-06-30 08:11:33 +00:00
import socket
2021-03-09 14:40:59 +00:00
from . import i2p, shared
from .advertiser import Advertiser
from .manager import Manager
from .listener import Listener
2016-06-30 08:11:33 +00:00
def handler(s, f): # pylint: disable=unused-argument
2022-09-22 22:54:12 +00:00
"""Signal handler"""
2016-10-15 14:12:09 +00:00
logging.info('Gracefully shutting down MiNode')
shared.shutting_down = True
2017-06-04 10:11:19 +00:00
def parse_arguments():
2022-09-22 22:54:12 +00:00
"""Parsing arguments"""
2017-06-04 10:11:19 +00:00
parser = argparse.ArgumentParser()
parser.add_argument('-p', '--port', help='Port to listen on', type=int)
parser.add_argument('--host', help='Listening host')
2021-03-08 15:06:07 +00:00
parser.add_argument(
'--debug', action='store_true', help='Enable debug logging')
2017-06-04 10:11:19 +00:00
parser.add_argument('--data-dir', help='Path to data directory')
2021-03-08 15:06:07 +00:00
parser.add_argument(
'--no-incoming', action='store_true',
help='Do not listen for incoming connections')
parser.add_argument(
'--no-outgoing', action='store_true',
help='Do not send outgoing connections')
parser.add_argument(
'--no-ip', action='store_true', help='Do not use IP network')
parser.add_argument(
'--trusted-peer', help='Specify a trusted peer we should connect to')
parser.add_argument(
'--connection-limit', type=int, help='Maximum number of connections')
parser.add_argument(
'--i2p', action='store_true', help='Enable I2P support (uses SAMv3)')
parser.add_argument(
'--i2p-tunnel-length', type=int, help='Length of I2P tunnels')
parser.add_argument(
'--i2p-sam-host', help='Host of I2P SAMv3 bridge')
parser.add_argument(
'--i2p-sam-port', type=int, help='Port of I2P SAMv3 bridge')
parser.add_argument(
'--i2p-transient', action='store_true',
help='Generate new I2P destination on start')
2017-06-04 10:11:19 +00:00
args = parser.parse_args()
if args.port:
shared.listening_port = args.port
if args.host:
shared.listening_host = args.host
2017-06-04 10:11:19 +00:00
if args.debug:
shared.log_level = logging.DEBUG
if args.data_dir:
dir_path = args.data_dir
if not dir_path.endswith('/'):
dir_path += '/'
shared.data_directory = dir_path
if args.no_incoming:
shared.listen_for_connections = False
if args.no_outgoing:
shared.send_outgoing_connections = False
if args.no_ip:
shared.ip_enabled = False
2017-06-04 10:11:19 +00:00
if args.trusted_peer:
2017-06-09 18:41:33 +00:00
if len(args.trusted_peer) > 50:
# I2P
shared.trusted_peer = (args.trusted_peer.encode(), 'i2p')
else:
colon_count = args.trusted_peer.count(':')
if colon_count == 0:
shared.trusted_peer = (args.trusted_peer, 8444)
if colon_count == 1:
addr = args.trusted_peer.split(':')
shared.trusted_peer = (addr[0], int(addr[1]))
if colon_count >= 2:
# IPv6 <3
addr = args.trusted_peer.split(']:')
addr[0] = addr[0][1:]
shared.trusted_peer = (addr[0], int(addr[1]))
2017-06-04 10:11:19 +00:00
if args.connection_limit:
shared.connection_limit = args.connection_limit
2017-06-09 18:41:33 +00:00
if args.i2p:
shared.i2p_enabled = True
2017-06-11 05:55:53 +00:00
if args.i2p_tunnel_length:
shared.i2p_tunnel_length = args.i2p_tunnel_length
if args.i2p_sam_host:
shared.i2p_sam_host = args.i2p_sam_host
if args.i2p_sam_port:
shared.i2p_sam_port = args.i2p_sam_port
if args.i2p_transient:
shared.i2p_transient = True
2017-06-04 10:11:19 +00:00
2017-07-20 16:22:59 +00:00
def load_data():
2022-09-22 22:54:12 +00:00
"""Loads initial nodes and data, stored in files between sessions"""
2016-06-30 08:11:33 +00:00
try:
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.data_directory, 'objects.pickle'), 'br'
) as src:
shared.objects = pickle.load(src)
except FileNotFoundError:
pass # first start
except Exception:
logging.warning(
'Error while loading objects from disk.', exc_info=True)
2016-06-30 08:11:33 +00:00
try:
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.data_directory, 'nodes.pickle'), 'br'
) as src:
shared.node_pool = pickle.load(src)
except FileNotFoundError:
pass
except Exception:
logging.warning('Error while loading nodes from disk.', exc_info=True)
2016-06-30 08:11:33 +00:00
try:
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.data_directory, 'i2p_nodes.pickle'), 'br'
) as src:
shared.i2p_node_pool = pickle.load(src)
except FileNotFoundError:
pass
except Exception:
logging.warning('Error while loading nodes from disk.', exc_info=True)
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.source_directory, 'core_nodes.csv'),
'r', newline=''
) as src:
reader = csv.reader(src)
2016-06-30 08:11:33 +00:00
shared.core_nodes = {tuple(row) for row in reader}
shared.node_pool.update(shared.core_nodes)
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.source_directory, 'i2p_core_nodes.csv'),
'r', newline=''
) as f:
reader = csv.reader(f)
shared.i2p_core_nodes = {(row[0].encode(), 'i2p') for row in reader}
shared.i2p_node_pool.update(shared.i2p_core_nodes)
2017-07-20 16:22:59 +00:00
def bootstrap_from_dns():
2022-09-22 22:54:12 +00:00
"""Addes addresses of bootstrap servers to known nodes"""
2017-07-20 16:22:59 +00:00
try:
for item in socket.getaddrinfo('bootstrap8080.bitmessage.org', 80):
shared.unchecked_node_pool.add((item[4][0], 8080))
2021-03-08 15:06:07 +00:00
logging.debug(
'Adding %s to unchecked_node_pool'
' based on DNS bootstrap method', item[4][0])
2017-07-20 16:22:59 +00:00
for item in socket.getaddrinfo('bootstrap8444.bitmessage.org', 80):
shared.unchecked_node_pool.add((item[4][0], 8444))
2021-03-08 15:06:07 +00:00
logging.debug(
'Adding %s to unchecked_node_pool'
' based on DNS bootstrap method', item[4][0])
except Exception:
logging.info('Error during DNS bootstrap', exc_info=True)
2017-07-20 16:22:59 +00:00
def start_ip_listener():
2022-09-22 22:54:12 +00:00
"""Starts `.listener.Listener`"""
2017-07-20 16:22:59 +00:00
listener_ipv4 = None
listener_ipv6 = None
if socket.has_ipv6:
try:
2021-03-08 15:06:07 +00:00
listener_ipv6 = Listener(
shared.listening_host,
shared.listening_port, family=socket.AF_INET6)
2017-07-20 16:22:59 +00:00
listener_ipv6.start()
except socket.gaierror as e:
if e.errno == -9:
logging.info('IPv6 is not supported.')
2021-03-08 15:06:07 +00:00
except Exception:
logging.info(
2021-03-08 15:06:07 +00:00
'Error while starting IPv6 listener on port %s',
shared.listening_port, exc_info=True)
2017-07-20 16:22:59 +00:00
try:
listener_ipv4 = Listener(shared.listening_host, shared.listening_port)
listener_ipv4.start()
except OSError as e:
2017-07-20 16:22:59 +00:00
if listener_ipv6:
logging.info(
2021-03-08 15:06:07 +00:00
'Error while starting IPv4 listener on port %s.'
' However the IPv6 one seems to be working'
' and will probably accept IPv4 connections.', # 48 on macos
2023-08-14 02:53:20 +00:00
shared.listening_port, exc_info=e.errno not in (48, 98))
2017-07-20 16:22:59 +00:00
else:
logging.warning(
2021-03-08 15:06:07 +00:00
'Error while starting IPv4 listener on port %s.'
'You will not receive incoming connections.'
' Please check your port configuration',
shared.listening_port, exc_info=True)
2016-06-30 08:11:33 +00:00
2017-06-09 18:41:33 +00:00
2017-07-20 16:22:59 +00:00
def start_i2p_listener():
2022-09-22 22:54:12 +00:00
"""Starts I2P threads"""
2017-07-20 16:22:59 +00:00
# Grab I2P destinations from old object file
for obj in shared.objects.values():
if obj.object_type == shared.i2p_dest_obj_type:
2021-03-08 15:06:07 +00:00
shared.i2p_unchecked_node_pool.add((
base64.b64encode(obj.object_payload, altchars=b'-~'), 'i2p'))
2017-07-20 16:22:59 +00:00
dest_priv = b''
if not shared.i2p_transient:
try:
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.data_directory, 'i2p_dest_priv.key'), 'br'
) as src:
dest_priv = src.read()
2017-07-20 16:22:59 +00:00
logging.debug('Loaded I2P destination private key.')
except FileNotFoundError:
pass
2021-03-09 14:40:59 +00:00
except Exception:
logging.info(
2021-03-09 14:40:59 +00:00
'Error while loading I2P destination private key.',
exc_info=True)
logging.info(
'Starting I2P Controller and creating tunnels. This may take a while.')
i2p_controller = i2p.I2PController(
shared, shared.i2p_sam_host, shared.i2p_sam_port, dest_priv)
2017-07-20 16:22:59 +00:00
i2p_controller.start()
shared.i2p_dest_pub = i2p_controller.dest_pub
shared.i2p_session_nick = i2p_controller.nick
2021-03-08 15:06:07 +00:00
logging.info('Local I2P destination: %s', shared.i2p_dest_pub.decode())
logging.info('I2P session nick: %s', shared.i2p_session_nick.decode())
2017-07-20 16:22:59 +00:00
logging.info('Starting I2P Listener')
2021-03-09 14:40:59 +00:00
i2p_listener = i2p.I2PListener(shared, i2p_controller.nick)
2017-07-20 16:22:59 +00:00
i2p_listener.start()
if not shared.i2p_transient:
try:
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.data_directory, 'i2p_dest_priv.key'), 'bw'
) as src:
src.write(i2p_controller.dest_priv)
2017-07-20 16:22:59 +00:00
logging.debug('Saved I2P destination private key.')
2021-03-08 15:06:07 +00:00
except Exception:
logging.warning(
'Error while saving I2P destination private key.',
exc_info=True)
2017-07-20 16:22:59 +00:00
try:
2021-03-08 15:06:07 +00:00
with open(
os.path.join(shared.data_directory, 'i2p_dest.pub'), 'bw'
) as src:
src.write(shared.i2p_dest_pub)
2017-07-20 16:22:59 +00:00
logging.debug('Saved I2P destination public key.')
2021-03-08 15:06:07 +00:00
except Exception:
logging.warning(
'Error while saving I2P destination public key.', exc_info=True)
2017-07-20 16:22:59 +00:00
def main():
2022-09-22 22:54:12 +00:00
"""Script entry point"""
2017-07-20 16:22:59 +00:00
signal.signal(signal.SIGINT, handler)
signal.signal(signal.SIGTERM, handler)
parse_arguments()
2021-03-08 15:06:07 +00:00
logging.basicConfig(
level=shared.log_level,
format='[%(asctime)s] [%(levelname)s] %(message)s')
2017-07-20 16:22:59 +00:00
logging.info('Starting MiNode')
2021-03-08 15:06:07 +00:00
logging.info('Data directory: %s', shared.data_directory)
2017-07-20 16:22:59 +00:00
if not os.path.exists(shared.data_directory):
2017-06-09 18:41:33 +00:00
try:
2017-07-20 16:22:59 +00:00
os.makedirs(shared.data_directory)
2021-03-08 15:06:07 +00:00
except Exception:
logging.warning(
'Error while creating data directory in: %s',
shared.data_directory, exc_info=True)
2017-06-09 18:41:33 +00:00
2017-07-20 16:22:59 +00:00
load_data()
if shared.ip_enabled and not shared.trusted_peer:
bootstrap_from_dns()
if shared.i2p_enabled:
2021-03-08 15:06:07 +00:00
# We are starting it before cleaning expired objects
# so we can collect I2P destination objects
2017-07-20 16:22:59 +00:00
start_i2p_listener()
2017-08-23 16:43:52 +00:00
for vector in set(shared.objects):
if not shared.objects[vector].is_valid():
2017-08-23 16:58:22 +00:00
if shared.objects[vector].is_expired():
2021-03-08 15:06:07 +00:00
logging.debug(
'Deleted expired object: %s',
base64.b16encode(vector).decode())
2017-08-23 16:58:22 +00:00
else:
2021-03-08 15:06:07 +00:00
logging.warning(
'Deleted invalid object: %s',
base64.b16encode(vector).decode())
2017-08-23 16:43:52 +00:00
del shared.objects[vector]
manager = Manager()
manager.start()
advertiser = Advertiser()
advertiser.start()
2017-01-14 12:45:39 +00:00
if shared.listen_for_connections:
2017-07-20 16:22:59 +00:00
start_ip_listener()
2016-06-30 08:11:33 +00:00
if __name__ == '__main__':
multiprocessing.set_start_method('spawn')
2016-06-30 08:11:33 +00:00
main()