2016-06-30 10:11:33 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
2022-09-23 00:54:12 +02:00
|
|
|
"""Functions for starting the program"""
|
2017-06-04 12:11:19 +02:00
|
|
|
import argparse
|
2017-07-01 15:05:06 +02:00
|
|
|
import base64
|
2016-06-30 10:11:33 +02:00
|
|
|
import logging
|
2017-07-01 15:05:06 +02:00
|
|
|
import multiprocessing
|
2016-06-30 10:29:52 +02:00
|
|
|
import os
|
2016-10-15 16:12:09 +02:00
|
|
|
import signal
|
2016-06-30 10:11:33 +02:00
|
|
|
import socket
|
|
|
|
|
2023-03-28 06:01:29 +02:00
|
|
|
try:
|
|
|
|
import socks
|
|
|
|
except ImportError:
|
|
|
|
socks = None
|
|
|
|
|
2021-03-09 15:40:59 +01:00
|
|
|
from . import i2p, shared
|
|
|
|
from .advertiser import Advertiser
|
|
|
|
from .manager import Manager
|
|
|
|
from .listener import Listener
|
2016-06-30 10:11:33 +02:00
|
|
|
|
|
|
|
|
2021-08-02 19:04:08 +02:00
|
|
|
def handler(s, f): # pylint: disable=unused-argument
|
2022-09-23 00:54:12 +02:00
|
|
|
"""Signal handler"""
|
2016-10-15 16:12:09 +02:00
|
|
|
logging.info('Gracefully shutting down MiNode')
|
|
|
|
shared.shutting_down = True
|
|
|
|
|
|
|
|
|
2023-08-26 23:46:42 +02:00
|
|
|
def parse_arguments(): # pylint: disable=too-many-branches,too-many-statements
|
2022-09-23 00:54:12 +02:00
|
|
|
"""Parsing arguments"""
|
2017-06-04 12:11:19 +02:00
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument('-p', '--port', help='Port to listen on', type=int)
|
2017-07-01 15:05:06 +02:00
|
|
|
parser.add_argument('--host', help='Listening host')
|
2021-03-08 16:06:07 +01:00
|
|
|
parser.add_argument(
|
|
|
|
'--debug', action='store_true', help='Enable debug logging')
|
2017-06-04 12:11:19 +02:00
|
|
|
parser.add_argument('--data-dir', help='Path to data directory')
|
2021-03-08 16:06:07 +01: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 12:11:19 +02:00
|
|
|
|
2023-03-28 06:01:29 +02:00
|
|
|
if socks is not None:
|
|
|
|
parser.add_argument(
|
|
|
|
'--socks-proxy',
|
|
|
|
help='SOCKS proxy address in the form <HOST>:<PORT>')
|
|
|
|
parser.add_argument(
|
|
|
|
'--tor', action='store_true',
|
|
|
|
help='The SOCKS proxy is tor, use 127.0.0.1:9050 if not specified')
|
|
|
|
|
2017-06-04 12:11:19 +02:00
|
|
|
args = parser.parse_args()
|
|
|
|
if args.port:
|
|
|
|
shared.listening_port = args.port
|
2017-07-01 15:05:06 +02:00
|
|
|
if args.host:
|
|
|
|
shared.listening_host = args.host
|
2017-06-04 12:11:19 +02: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
|
2017-07-01 15:05:06 +02:00
|
|
|
if args.no_ip:
|
|
|
|
shared.ip_enabled = False
|
2017-06-04 12:11:19 +02:00
|
|
|
if args.trusted_peer:
|
2023-03-28 06:01:29 +02:00
|
|
|
if len(args.trusted_peer
|
|
|
|
) > 50 and not args.trusted_peer.endswith('onion'):
|
2017-06-09 20:41:33 +02:00
|
|
|
# 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 12:11:19 +02:00
|
|
|
if args.connection_limit:
|
|
|
|
shared.connection_limit = args.connection_limit
|
2017-06-09 20:41:33 +02:00
|
|
|
if args.i2p:
|
|
|
|
shared.i2p_enabled = True
|
2017-06-11 07:55:53 +02: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
|
2017-07-20 16:12:32 +02:00
|
|
|
if args.i2p_transient:
|
|
|
|
shared.i2p_transient = True
|
2017-06-04 12:11:19 +02:00
|
|
|
|
2023-03-28 06:01:29 +02:00
|
|
|
if socks is None:
|
|
|
|
return
|
|
|
|
if args.tor:
|
|
|
|
shared.tor = True
|
|
|
|
if not args.socks_proxy:
|
|
|
|
shared.socks_proxy = ('127.0.0.1', 9050)
|
|
|
|
if args.socks_proxy:
|
|
|
|
addr = args.socks_proxy.split(':')
|
|
|
|
shared.socks_proxy = (addr[0], int(addr[1]))
|
|
|
|
|
2017-06-04 12:11:19 +02:00
|
|
|
|
2017-07-20 18:22:59 +02:00
|
|
|
def bootstrap_from_dns():
|
2023-08-19 02:02:19 +02:00
|
|
|
"""Addes addresses of bootstrap servers to core nodes"""
|
2017-07-20 18:22:59 +02:00
|
|
|
try:
|
2023-08-19 02:02:19 +02:00
|
|
|
for port in (8080, 8444):
|
|
|
|
for item in socket.getaddrinfo(
|
|
|
|
'bootstrap{}.bitmessage.org'.format(port), 80,
|
|
|
|
proto=socket.IPPROTO_TCP
|
|
|
|
):
|
|
|
|
try:
|
|
|
|
addr = item[4][0]
|
|
|
|
socket.inet_pton(item[0], addr)
|
|
|
|
except (TypeError, socket.error):
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
shared.core_nodes.add((addr, port))
|
|
|
|
except socket.gaierror:
|
|
|
|
logging.info('Failed to do a DNS query')
|
2021-03-08 16:06:07 +01:00
|
|
|
except Exception:
|
2021-08-02 18:08:08 +02:00
|
|
|
logging.info('Error during DNS bootstrap', exc_info=True)
|
2017-07-20 18:22:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
def start_ip_listener():
|
2022-09-23 00:54:12 +02:00
|
|
|
"""Starts `.listener.Listener`"""
|
2017-07-20 18:22:59 +02:00
|
|
|
listener_ipv4 = None
|
|
|
|
listener_ipv6 = None
|
|
|
|
|
|
|
|
if socket.has_ipv6:
|
2017-01-14 13:42:15 +01:00
|
|
|
try:
|
2021-03-08 16:06:07 +01:00
|
|
|
listener_ipv6 = Listener(
|
|
|
|
shared.listening_host,
|
|
|
|
shared.listening_port, family=socket.AF_INET6)
|
2017-07-20 18:22:59 +02:00
|
|
|
listener_ipv6.start()
|
2022-09-23 07:11:07 +02:00
|
|
|
except socket.gaierror as e:
|
|
|
|
if e.errno == -9:
|
|
|
|
logging.info('IPv6 is not supported.')
|
2021-03-08 16:06:07 +01:00
|
|
|
except Exception:
|
2021-08-02 18:08:08 +02:00
|
|
|
logging.info(
|
2021-03-08 16:06:07 +01:00
|
|
|
'Error while starting IPv6 listener on port %s',
|
|
|
|
shared.listening_port, exc_info=True)
|
2017-07-20 18:22:59 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
listener_ipv4 = Listener(shared.listening_host, shared.listening_port)
|
|
|
|
listener_ipv4.start()
|
2021-08-02 18:08:08 +02:00
|
|
|
except OSError as e:
|
2017-07-20 18:22:59 +02:00
|
|
|
if listener_ipv6:
|
2021-08-02 18:08:08 +02:00
|
|
|
logging.info(
|
2021-03-08 16:06:07 +01:00
|
|
|
'Error while starting IPv4 listener on port %s.'
|
|
|
|
' However the IPv6 one seems to be working'
|
2021-08-02 18:08:08 +02:00
|
|
|
' and will probably accept IPv4 connections.', # 48 on macos
|
2023-08-14 04:53:20 +02:00
|
|
|
shared.listening_port, exc_info=e.errno not in (48, 98))
|
2017-07-20 18:22:59 +02:00
|
|
|
else:
|
2021-08-02 18:08:08 +02:00
|
|
|
logging.warning(
|
2021-03-08 16:06:07 +01: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 10:11:33 +02:00
|
|
|
|
2017-06-09 20:41:33 +02:00
|
|
|
|
2017-07-20 18:22:59 +02:00
|
|
|
def start_i2p_listener():
|
2022-09-23 00:54:12 +02:00
|
|
|
"""Starts I2P threads"""
|
2017-07-20 18:22:59 +02: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 16:06:07 +01:00
|
|
|
shared.i2p_unchecked_node_pool.add((
|
|
|
|
base64.b64encode(obj.object_payload, altchars=b'-~'), 'i2p'))
|
2017-07-20 18:22:59 +02:00
|
|
|
|
|
|
|
dest_priv = b''
|
|
|
|
|
|
|
|
if not shared.i2p_transient:
|
|
|
|
try:
|
2021-03-08 16:06:07 +01:00
|
|
|
with open(
|
|
|
|
os.path.join(shared.data_directory, 'i2p_dest_priv.key'), 'br'
|
|
|
|
) as src:
|
|
|
|
dest_priv = src.read()
|
2017-07-20 18:22:59 +02:00
|
|
|
logging.debug('Loaded I2P destination private key.')
|
2022-09-23 07:16:49 +02:00
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
2021-03-09 15:40:59 +01:00
|
|
|
except Exception:
|
2021-08-02 18:08:08 +02:00
|
|
|
logging.info(
|
2021-03-09 15:40:59 +01: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 18:22:59 +02:00
|
|
|
i2p_controller.start()
|
|
|
|
|
|
|
|
shared.i2p_dest_pub = i2p_controller.dest_pub
|
|
|
|
shared.i2p_session_nick = i2p_controller.nick
|
|
|
|
|
2021-03-08 16:06:07 +01: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 18:22:59 +02:00
|
|
|
|
|
|
|
logging.info('Starting I2P Listener')
|
2021-03-09 15:40:59 +01:00
|
|
|
i2p_listener = i2p.I2PListener(shared, i2p_controller.nick)
|
2017-07-20 18:22:59 +02:00
|
|
|
i2p_listener.start()
|
|
|
|
|
|
|
|
if not shared.i2p_transient:
|
|
|
|
try:
|
2021-03-08 16:06:07 +01: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 18:22:59 +02:00
|
|
|
logging.debug('Saved I2P destination private key.')
|
2021-03-08 16:06:07 +01:00
|
|
|
except Exception:
|
|
|
|
logging.warning(
|
|
|
|
'Error while saving I2P destination private key.',
|
|
|
|
exc_info=True)
|
2017-07-20 18:22:59 +02:00
|
|
|
|
|
|
|
try:
|
2021-03-08 16:06:07 +01:00
|
|
|
with open(
|
|
|
|
os.path.join(shared.data_directory, 'i2p_dest.pub'), 'bw'
|
|
|
|
) as src:
|
|
|
|
src.write(shared.i2p_dest_pub)
|
2017-07-20 18:22:59 +02:00
|
|
|
logging.debug('Saved I2P destination public key.')
|
2021-03-08 16:06:07 +01:00
|
|
|
except Exception:
|
|
|
|
logging.warning(
|
|
|
|
'Error while saving I2P destination public key.', exc_info=True)
|
2017-07-20 18:22:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
2022-09-23 00:54:12 +02:00
|
|
|
"""Script entry point"""
|
2017-07-20 18:22:59 +02:00
|
|
|
signal.signal(signal.SIGINT, handler)
|
|
|
|
signal.signal(signal.SIGTERM, handler)
|
|
|
|
|
|
|
|
parse_arguments()
|
|
|
|
|
2021-03-08 16:06:07 +01:00
|
|
|
logging.basicConfig(
|
|
|
|
level=shared.log_level,
|
|
|
|
format='[%(asctime)s] [%(levelname)s] %(message)s')
|
2017-07-20 18:22:59 +02:00
|
|
|
logging.info('Starting MiNode')
|
|
|
|
|
2021-03-08 16:06:07 +01:00
|
|
|
logging.info('Data directory: %s', shared.data_directory)
|
2017-07-20 18:22:59 +02:00
|
|
|
if not os.path.exists(shared.data_directory):
|
2017-06-09 20:41:33 +02:00
|
|
|
try:
|
2017-07-20 18:22:59 +02:00
|
|
|
os.makedirs(shared.data_directory)
|
2021-03-08 16:06:07 +01:00
|
|
|
except Exception:
|
|
|
|
logging.warning(
|
|
|
|
'Error while creating data directory in: %s',
|
|
|
|
shared.data_directory, exc_info=True)
|
2017-06-09 20:41:33 +02:00
|
|
|
|
2017-07-20 18:22:59 +02:00
|
|
|
if shared.ip_enabled and not shared.trusted_peer:
|
|
|
|
bootstrap_from_dns()
|
|
|
|
|
|
|
|
if shared.i2p_enabled:
|
2021-03-08 16:06:07 +01:00
|
|
|
# We are starting it before cleaning expired objects
|
|
|
|
# so we can collect I2P destination objects
|
2017-07-20 18:22:59 +02:00
|
|
|
start_i2p_listener()
|
|
|
|
|
2017-07-01 15:05:06 +02:00
|
|
|
manager = Manager()
|
|
|
|
manager.start()
|
|
|
|
|
|
|
|
advertiser = Advertiser()
|
|
|
|
advertiser.start()
|
|
|
|
|
2017-01-14 13:45:39 +01:00
|
|
|
if shared.listen_for_connections:
|
2017-07-20 18:22:59 +02:00
|
|
|
start_ip_listener()
|
2017-01-14 13:42:15 +01:00
|
|
|
|
2016-06-30 10:11:33 +02:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2017-07-01 15:05:06 +02:00
|
|
|
multiprocessing.set_start_method('spawn')
|
2016-06-30 10:11:33 +02:00
|
|
|
main()
|