toxygen_wrapper/wrapper_tests/support_testing.py

967 lines
35 KiB
Python
Raw Normal View History

2022-09-26 00:58:57 +00:00
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import argparse
2022-11-17 14:18:51 +00:00
import contextlib
2022-11-23 20:02:29 +00:00
import inspect
2022-11-17 14:18:51 +00:00
import json
2022-09-26 00:58:57 +00:00
import logging
2022-11-17 14:18:51 +00:00
import os
import re
import select
2022-10-08 18:01:52 +00:00
import shutil
2022-10-13 14:15:18 +00:00
import socket
2022-11-17 14:18:51 +00:00
import sys
import time
import traceback
2022-09-26 00:58:57 +00:00
import unittest
2022-11-17 14:18:51 +00:00
from ctypes import *
2022-09-26 00:58:57 +00:00
from random import Random
2023-07-14 14:07:08 +00:00
import functools
2022-11-17 14:18:51 +00:00
2022-09-26 00:58:57 +00:00
random = Random()
2022-10-10 14:33:01 +00:00
try:
import coloredlogs
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
# https://pypi.org/project/coloredlogs/
except ImportError as e:
coloredlogs = False
2022-10-23 22:14:35 +00:00
try:
import stem
except ImportError as e:
stem = False
2022-11-05 01:10:35 +00:00
try:
import nmap
except ImportError as e:
nmap = False
2022-10-10 14:33:01 +00:00
2022-09-26 00:58:57 +00:00
import wrapper
2023-12-11 06:38:55 +00:00
import wrapper.toxcore_enums_and_consts as enums
2022-11-17 14:18:51 +00:00
from wrapper_tests.support_http import bAreWeConnected
from wrapper_tests.support_onions import (is_valid_fingerprint,
lIntroductionPoints,
oGetStemController,
sMapaddressResolv, sTorResolve)
2022-09-26 00:58:57 +00:00
try:
from user_data.settings import get_user_config_path
except ImportError:
get_user_config_path = None
# LOG=util.log
global LOG
2022-10-29 18:44:01 +00:00
LOG = logging.getLogger()
2022-09-26 00:58:57 +00:00
def LOG_ERROR(l): print('ERRORc: '+l)
def LOG_WARN(l): print('WARNc: ' +l)
def LOG_INFO(l): print('INFOc: ' +l)
def LOG_DEBUG(l): print('DEBUGc: '+l)
def LOG_TRACE(l): pass # print('TRACE+ '+l)
try:
from trepan.api import debug
2022-11-17 14:18:51 +00:00
from trepan.interfaces import server as Mserver
2022-09-26 00:58:57 +00:00
except:
2022-10-28 02:33:36 +00:00
# print('trepan3 TCP server NOT available.')
pass
2022-09-26 00:58:57 +00:00
else:
2022-10-28 02:33:36 +00:00
# print('trepan3 TCP server available.')
2022-09-26 00:58:57 +00:00
def trepan_handler(num=None, f=None):
connection_opts={'IO': 'TCP', 'PORT': 6666}
intf = Mserver.ServerInterface(connection_opts=connection_opts)
dbg_opts = { 'interface': intf }
2022-10-23 22:14:35 +00:00
print(f'Starting TCP server listening on port 6666.')
2022-09-26 00:58:57 +00:00
debug(dbg_opts=dbg_opts)
return
# self._audio_thread.isAlive
iTHREAD_TIMEOUT = 1
iTHREAD_SLEEP = 1
2022-10-18 00:16:56 +00:00
iTHREAD_JOINS = 8
2022-11-17 14:18:51 +00:00
iNODES = 6
2022-09-26 00:58:57 +00:00
lToxSamplerates = [8000, 12000, 16000, 24000, 48000]
lToxSampleratesK = [8, 12, 16, 24, 48]
lBOOLEANS = [
'local_discovery_enabled',
'udp_enabled',
'ipv6_enabled',
2022-10-18 00:16:56 +00:00
'trace_enabled',
2022-09-26 00:58:57 +00:00
'compact_mode',
'allow_inline',
'notifications',
'sound_notifications',
2022-10-01 19:01:20 +00:00
'calls_sound',
2022-09-26 00:58:57 +00:00
'hole_punching_enabled',
'dht_announcements_enabled',
'save_history',
'download_nodes_list'
'core_logging',
]
2022-10-08 18:01:52 +00:00
sDIR = os.environ.get('TMPDIR', '/tmp')
sTOX_VERSION = "1000002018"
bHAVE_NMAP = shutil.which('nmap')
bHAVE_JQ = shutil.which('jq')
bHAVE_BASH = shutil.which('bash')
2022-10-23 22:14:35 +00:00
bHAVE_TORR = shutil.which('tor-resolve')
2022-09-26 00:58:57 +00:00
2022-10-11 09:33:39 +00:00
lDEAD_BS = [
2023-07-14 14:07:08 +00:00
# Failed to resolve "tox3.plastiras.org"
2022-10-26 08:58:09 +00:00
"tox3.plastiras.org",
2023-07-14 14:07:08 +00:00
'tox.kolka.tech',
2023-07-17 02:36:30 +00:00
# here and gone
'122-116-39-151.hinet-ip.hinet.net',
2023-07-14 14:07:08 +00:00
# IPs that do not reverse resolve
'49.12.229.145',
"46.101.197.175",
'114.35.245.150',
'172.93.52.70',
'195.123.208.139',
'205.185.115.131',
# IPs that do not rreverse resolve
'yggnode.cf', '188.225.9.167',
'85-143-221-42.simplecloud.ru', '85.143.221.42',
# IPs that do not ping
'104.244.74.69', 'tox.plastiras.org',
'195.123.208.139',
'gt.sot-te.ch', '32.226.5.82',
# suspicious IPs
'tox.abilinski.com', '172.103.164.250', '172.103.164.250.tpia.cipherkey.com',
2022-10-11 09:33:39 +00:00
]
2023-07-14 14:07:08 +00:00
2022-09-26 00:58:57 +00:00
def assert_main_thread():
2022-10-27 06:50:47 +00:00
from PyQt5 import QtCore, QtWidgets
from qtpy.QtWidgets import QApplication
2022-09-26 00:58:57 +00:00
# this "instance" method is very useful!
app_thread = QtWidgets.QApplication.instance().thread()
curr_thread = QtCore.QThread.currentThread()
if app_thread != curr_thread:
raise RuntimeError('attempt to call MainWindow.append_message from non-app thread')
@contextlib.contextmanager
def ignoreStdout():
devnull = os.open(os.devnull, os.O_WRONLY)
old_stdout = os.dup(1)
sys.stdout.flush()
os.dup2(devnull, 1)
os.close(devnull)
try:
yield
finally:
os.dup2(old_stdout, 1)
os.close(old_stdout)
@contextlib.contextmanager
def ignoreStderr():
devnull = os.open(os.devnull, os.O_WRONLY)
old_stderr = os.dup(2)
sys.stderr.flush()
os.dup2(devnull, 2)
os.close(devnull)
try:
yield
finally:
os.dup2(old_stderr, 2)
os.close(old_stderr)
2022-11-13 14:34:02 +00:00
def clean_booleans(oArgs):
for key in lBOOLEANS:
if not hasattr(oArgs, key): continue
val = getattr(oArgs, key)
if type(val) == bool: continue
if val in ['False', 'false', '0']:
setattr(oArgs, key, False)
else:
setattr(oArgs, key, True)
2022-11-23 20:02:29 +00:00
2022-09-26 00:58:57 +00:00
def on_log(iTox, level, filename, line, func, message, *data):
# LOG.debug(repr((level, filename, line, func, message,)))
tox_log_cb(level, filename, line, func, message)
def tox_log_cb(level, filename, line, func, message, *args):
"""
* @param level The severity of the log message.
* @param filename The source file from which the message originated.
* @param line The source line from which the message originated.
* @param func The function from which the message originated.
* @param message The log message.
* @param user_data The user data pointer passed to tox_new in options.
"""
if type(func) == bytes:
func = str(func, 'utf-8')
message = str(message, 'UTF-8')
filename = str(filename, 'UTF-8')
if filename == 'network.c':
if line == 660: return
# root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket
if line == 944: return
i = message.find('07 = GET_NODES')
if i > 0:
return
if filename == 'TCP_common.c': return
i = message.find(' | ')
if i > 0:
message = message[:i]
# message = filename +'#' +str(line) +':'+func +' '+message
name = 'core'
# old level is meaningless
level = 10 # LOG.level
# LOG._log(LOG.level, f"{level}: {message}", list())
i = message.find('(0: OK)')
if i > 0:
level = 10 # LOG.debug
else:
i = message.find('(1: ')
if i > 0:
level = 30 # LOG.warn
else:
level = 20 # LOG.info
2022-11-23 20:02:29 +00:00
2022-09-26 00:58:57 +00:00
o = LOG.makeRecord(filename, level, func, line, message, list(), None)
2022-10-01 19:01:20 +00:00
# LOG.handle(o)
LOG_TRACE(f"{level}: {func}{line} {message}")
2022-09-26 00:58:57 +00:00
return
elif level == 1:
LOG.critical(f"{level}: {message}")
elif level == 2:
LOG.error(f"{level}: {message}")
elif level == 3:
LOG.warn(f"{level}: {message}")
elif level == 4:
LOG.info(f"{level}: {message}")
elif level == 5:
LOG.debug(f"{level}: {message}")
else:
2022-10-01 19:01:20 +00:00
LOG_TRACE(f"{level}: {message}")
2022-09-26 00:58:57 +00:00
def vAddLoggerCallback(tox_options, callback=None):
if callback is None:
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
tox_options._options_pointer,
POINTER(None)())
tox_options.self_logger_cb = None
return
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p)
tox_options.self_logger_cb = c_callback(callback)
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
tox_options._options_pointer,
tox_options.self_logger_cb)
def get_video_indexes():
# Linux
return [str(l[5:]) for l in os.listdir('/dev/') if l.startswith('video')]
def get_audio():
2022-10-28 02:33:36 +00:00
with ignoreStderr():
import pyaudio
oPyA = pyaudio.PyAudio()
2022-11-23 20:02:29 +00:00
2022-09-26 00:58:57 +00:00
input_devices = output_devices = 0
for i in range(oPyA.get_device_count()):
device = oPyA.get_device_info_by_index(i)
if device["maxInputChannels"]:
input_devices += 1
if device["maxOutputChannels"]:
output_devices += 1
# {'index': 21, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 64, 'maxOutputChannels': 64, 'defaultLowInputLatency': 0.008707482993197279, 'defaultLowOutputLatency': 0.008707482993197279, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0}
audio = {'input': oPyA.get_default_input_device_info()['index'] if input_devices else -1,
'output': oPyA.get_default_output_device_info()['index'] if output_devices else -1,
'enabled': input_devices and output_devices}
return audio
2023-12-11 06:38:55 +00:00
def oToxygenToxOptions(oArgs):
data = None
tox_options = wrapper.tox.Tox.options_new()
if oArgs.proxy_type:
tox_options.contents.proxy_type = int(oArgs.proxy_type)
tox_options.contents.proxy_host = bytes(oArgs.proxy_host, 'UTF-8')
tox_options.contents.proxy_port = int(oArgs.proxy_port)
tox_options.contents.udp_enabled = False
else:
tox_options.contents.udp_enabled = oArgs.udp_enabled
if not os.path.exists('/proc/sys/net/ipv6'):
oArgs.ipv6_enabled = False
else:
tox_options.contents.ipv6_enabled = oArgs.ipv6_enabled
tox_options.contents.tcp_port = int(oArgs.tcp_port)
tox_options.contents.dht_announcements_enabled = oArgs.dht_announcements_enabled
tox_options.contents.hole_punching_enabled = oArgs.hole_punching_enabled
# overrides
tox_options.contents.local_discovery_enabled = False
tox_options.contents.experimental_thread_safety = False
# REQUIRED!!
if oArgs.ipv6_enabled and not os.path.exists('/proc/sys/net/ipv6'):
LOG.warning('Disabling IPV6 because /proc/sys/net/ipv6 does not exist' + repr(oArgs.ipv6_enabled))
tox_options.contents.ipv6_enabled = False
else:
tox_options.contents.ipv6_enabled = bool(oArgs.ipv6_enabled)
if data: # load existing profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
tox_options.contents.savedata_data = c_char_p(data)
tox_options.contents.savedata_length = len(data)
else: # create new profile
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE']
tox_options.contents.savedata_data = None
tox_options.contents.savedata_length = 0
#? tox_options.contents.log_callback = LOG
if tox_options._options_pointer:
# LOG.debug("Adding logging to tox_options._options_pointer ")
vAddLoggerCallback(tox_options, on_log)
else:
LOG.warning("No tox_options._options_pointer " +repr(tox_options._options_pointer))
return tox_options
2023-07-14 14:07:08 +00:00
def oMainArgparser(_=None, iMode=0):
2022-10-28 02:33:36 +00:00
# 'Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0'
2022-09-26 00:58:57 +00:00
if not os.path.exists('/proc/sys/net/ipv6'):
bIpV6 = 'False'
else:
bIpV6 = 'True'
lIpV6Choices=[bIpV6, 'False']
2023-07-14 14:07:08 +00:00
sNodesJson = os.path.join(os.environ['HOME'], '.config', 'tox', 'DHTnodes.json')
if not os.path.exists(sNodesJson): sNodesJson = ''
logfile = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'toxygen.log')
if not os.path.exists(sNodesJson): logfile = ''
2022-10-26 08:58:09 +00:00
parser = argparse.ArgumentParser(add_help=True)
2022-09-26 00:58:57 +00:00
parser.add_argument('--proxy_host', '--proxy-host', type=str,
2023-07-14 14:07:08 +00:00
# oddball - we want to use '' as a setting
default='0.0.0.0',
2022-09-26 00:58:57 +00:00
help='proxy host')
parser.add_argument('--proxy_port', '--proxy-port', default=0, type=int,
help='proxy port')
parser.add_argument('--proxy_type', '--proxy-type', default=0, type=int,
choices=[0,1,2],
help='proxy type 1=http, 2=socks')
2023-07-14 14:07:08 +00:00
parser.add_argument('--tcp_port', '--tcp-port', default=0, type=int,
help='tcp port')
2022-09-26 00:58:57 +00:00
parser.add_argument('--udp_enabled', type=str, default='True',
choices=['True', 'False'],
help='En/Disable udp')
parser.add_argument('--ipv6_enabled', type=str, default=bIpV6,
choices=lIpV6Choices,
help=f"En/Disable ipv6 - default {bIpV6}")
2022-10-26 08:58:09 +00:00
parser.add_argument('--trace_enabled',type=str,
default='True' if os.environ.get('DEBUG') else 'False',
choices=['True','False'],
help='Debugging from toxcore logger_trace or env DEBUG=1')
2022-09-26 00:58:57 +00:00
parser.add_argument('--download_nodes_list', type=str, default='False',
choices=['True', 'False'],
help='Download nodes list')
parser.add_argument('--nodes_json', type=str,
2023-07-14 14:07:08 +00:00
default=sNodesJson)
2022-09-26 00:58:57 +00:00
parser.add_argument('--network', type=str,
2023-07-14 14:07:08 +00:00
choices=['main', 'local'],
2022-10-23 22:14:35 +00:00
default='main')
2022-09-26 00:58:57 +00:00
parser.add_argument('--download_nodes_url', type=str,
default='https://nodes.tox.chat/json')
2023-07-14 14:07:08 +00:00
parser.add_argument('--logfile', default=logfile,
2022-10-26 08:58:09 +00:00
help='Filename for logging - start with + for stdout too')
2022-09-26 00:58:57 +00:00
parser.add_argument('--loglevel', default=logging.INFO, type=int,
# choices=[logging.info,logging.trace,logging.debug,logging.error]
help='Threshold for logging (lower is more) default: 20')
2022-10-28 02:33:36 +00:00
parser.add_argument('--mode', type=int, default=iMode,
2022-10-26 08:58:09 +00:00
choices=[0,1,2],
2022-09-26 00:58:57 +00:00
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
2023-07-14 14:07:08 +00:00
parser.add_argument('--hole_punching_enabled',type=str,
default='False', choices=['True','False'],
help='En/Enable hole punching')
parser.add_argument('--dht_announcements_enabled',type=str,
default='True', choices=['True','False'],
help='En/Disable DHT announcements')
2022-09-26 00:58:57 +00:00
return parser
2022-10-23 22:14:35 +00:00
def vSetupLogging(oArgs):
global LOG
2022-11-13 14:34:02 +00:00
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
logging._defaultFormatter.default_msec_format = ''
2022-10-26 08:58:09 +00:00
add = None
2022-09-26 00:58:57 +00:00
kwargs = dict(level=oArgs.loglevel,
format='%(levelname)-8s %(message)s')
if oArgs.logfile:
2022-10-26 08:58:09 +00:00
add = oArgs.logfile.startswith('+')
sub = oArgs.logfile.startswith('-')
if add or sub:
oArgs.logfile = oArgs.logfile[1:]
2022-09-26 00:58:57 +00:00
kwargs['filename'] = oArgs.logfile
2022-11-23 20:02:29 +00:00
2022-10-23 22:14:35 +00:00
if coloredlogs:
# https://pypi.org/project/coloredlogs/
2022-10-26 08:58:09 +00:00
aKw = dict(level=oArgs.loglevel,
logger=LOG,
2022-11-13 14:34:02 +00:00
stream=sys.stdout,
2022-10-26 08:58:09 +00:00
fmt='%(name)s %(levelname)s %(message)s'
)
coloredlogs.install(**aKw)
2022-11-13 14:34:02 +00:00
if oArgs.logfile:
2022-10-26 08:58:09 +00:00
oHandler = logging.FileHandler(oArgs.logfile)
LOG.addHandler(oHandler)
2022-10-23 22:14:35 +00:00
else:
logging.basicConfig(**kwargs)
2022-10-26 08:58:09 +00:00
if add:
oHandler = logging.StreamHandler(sys.stdout)
LOG.addHandler(oHandler)
2022-11-23 20:02:29 +00:00
2022-10-23 22:14:35 +00:00
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
2022-09-26 00:58:57 +00:00
2022-10-23 22:14:35 +00:00
def setup_logging(oArgs):
global LOG
if coloredlogs:
aKw = dict(level=oArgs.loglevel,
logger=LOG,
fmt='%(name)s %(levelname)s %(message)s')
if oArgs.logfile:
oFd = open(oArgs.logfile, 'wt')
setattr(oArgs, 'log_oFd', oFd)
aKw['stream'] = oFd
coloredlogs.install(**aKw)
if oArgs.logfile:
oHandler = logging.StreamHandler(stream=sys.stdout)
LOG.addHandler(oHandler)
else:
aKw = dict(level=oArgs.loglevel,
format='%(name)s %(levelname)-4s %(message)s')
if oArgs.logfile:
aKw['filename'] = oArgs.logfile
logging.basicConfig(**aKw)
logging._defaultFormatter = logging.Formatter(datefmt='%m-%d %H:%M:%S')
logging._defaultFormatter.default_time_format = '%m-%d %H:%M:%S'
logging._defaultFormatter.default_msec_format = ''
2022-09-26 00:58:57 +00:00
2022-10-23 22:14:35 +00:00
LOG.setLevel(oArgs.loglevel)
2022-10-26 08:58:09 +00:00
# LOG.trace = lambda l: LOG.log(0, repr(l))
2022-10-23 22:14:35 +00:00
LOG.info(f"Setting loglevel to {oArgs.loglevel!s}")
2022-09-26 00:58:57 +00:00
def signal_handler(num, f):
from trepan.api import debug
2022-11-17 14:18:51 +00:00
from trepan.interfaces import server as Mserver
2022-09-26 00:58:57 +00:00
connection_opts={'IO': 'TCP', 'PORT': 6666}
intf = Mserver.ServerInterface(connection_opts=connection_opts)
dbg_opts = {'interface': intf}
LOG.info('Starting TCP server listening on port 6666.')
debug(dbg_opts=dbg_opts)
return
def merge_args_into_settings(args, settings):
if args:
2022-11-17 14:18:51 +00:00
if not hasattr(args, 'audio'):
LOG.warn('No audio ' +repr(args))
settings['audio'] = getattr(args, 'audio')
if not hasattr(args, 'video'):
LOG.warn('No video ' +repr(args))
settings['video'] = getattr(args, 'video')
2022-09-26 00:58:57 +00:00
for key in settings.keys():
# proxy_type proxy_port proxy_host
not_key = 'not_' +key
if hasattr(args, key):
val = getattr(args, key)
if type(val) == bytes:
# proxy_host - ascii?
# filenames - ascii?
val = str(val, 'UTF-8')
settings[key] = val
elif hasattr(args, not_key):
val = not getattr(args, not_key)
settings[key] = val
clean_settings(settings)
return
2022-11-17 14:18:51 +00:00
def clean_settings(self):
# failsafe to ensure C tox is bytes and Py settings is str
# overrides
self['mirror_mode'] = False
# REQUIRED!!
if not os.path.exists('/proc/sys/net/ipv6'):
LOG.warn('Disabling IPV6 because /proc/sys/net/ipv6 does not exist')
self['ipv6_enabled'] = False
if 'proxy_type' in self and self['proxy_type'] == 0:
self['proxy_host'] = ''
self['proxy_port'] = 0
if 'proxy_type' in self and self['proxy_type'] != 0 and \
'proxy_host' in self and self['proxy_host'] != '' and \
'proxy_port' in self and self['proxy_port'] != 0:
if 'udp_enabled' in self and self['udp_enabled']:
# We don't currently support UDP over proxy.
LOG.info("UDP enabled and proxy set: disabling UDP")
self['udp_enabled'] = False
if 'local_discovery_enabled' in self and self['local_discovery_enabled']:
LOG.info("local_discovery_enabled enabled and proxy set: disabling local_discovery_enabled")
self['local_discovery_enabled'] = False
if 'dht_announcements_enabled' in self and self['dht_announcements_enabled']:
LOG.info("dht_announcements_enabled enabled and proxy set: disabling dht_announcements_enabled")
self['dht_announcements_enabled'] = False
if 'auto_accept_path' in self and \
type(self['auto_accept_path']) == bytes:
self['auto_accept_path'] = str(self['auto_accept_path'], 'UTF-8')
LOG.debug("Cleaned settings")
2022-09-26 00:58:57 +00:00
def lSdSamplerates(iDev):
try:
import sounddevice as sd
except ImportError:
return []
samplerates = (32000, 44100, 48000, 96000, )
device = iDev
supported_samplerates = []
for fs in samplerates:
try:
sd.check_output_settings(device=device, samplerate=fs)
except Exception as e:
# LOG.debug(f"Sample rate not supported {fs}" +' '+str(e))
pass
else:
supported_samplerates.append(fs)
return supported_samplerates
2023-12-06 16:32:06 +00:00
def _get_nodes_path(oArgs):
if oArgs and hasattr(oArgs, 'nodes_json') and \
oArgs.nodes_json and os.path.isfile(oArgs.nodes_json):
2022-10-08 18:01:52 +00:00
default = oArgs.nodes_json
elif get_user_config_path:
default = os.path.join(get_user_config_path(), 'toxygen_nodes.json')
else:
# Windwoes
default = os.path.join(os.getenv('HOME'), '.config', 'tox', 'toxygen_nodes.json')
2022-09-26 00:58:57 +00:00
LOG.debug("_get_nodes_path: " +default)
return default
2022-10-08 18:01:52 +00:00
DEFAULT_NODES_COUNT = 8
2022-09-26 00:58:57 +00:00
2022-10-13 14:15:18 +00:00
global aNODES
aNODES = {}
2022-11-17 14:18:51 +00:00
2023-07-14 14:07:08 +00:00
# @functools.lru_cache(maxsize=12) TypeError: unhashable type: 'Namespace'
2022-10-13 14:15:18 +00:00
def generate_nodes(oArgs=None,
2022-10-07 04:47:34 +00:00
nodes_count=DEFAULT_NODES_COUNT,
ipv='ipv4',
udp_not_tcp=True):
2022-10-13 14:15:18 +00:00
global aNODES
sKey = ipv
sKey += ',0' if udp_not_tcp else ',1'
2022-10-23 22:14:35 +00:00
if sKey in aNODES and aNODES[sKey]:
return aNODES[sKey]
2023-12-06 16:32:06 +00:00
sFile = _get_nodes_path(oArgs)
2022-10-23 22:14:35 +00:00
assert os.path.exists(sFile), sFile
lNodes = generate_nodes_from_file(sFile,
nodes_count=nodes_count,
2023-12-06 16:32:06 +00:00
ipv=ipv,
udp_not_tcp=udp_not_tcp)
2022-10-23 22:14:35 +00:00
assert lNodes
aNODES[sKey] = lNodes
2022-10-13 14:15:18 +00:00
return aNODES[sKey]
2022-10-07 04:47:34 +00:00
2022-10-08 18:01:52 +00:00
aNODES_CACHE = {}
2022-10-07 04:47:34 +00:00
def generate_nodes_from_file(sFile,
nodes_count=DEFAULT_NODES_COUNT,
ipv='ipv4',
udp_not_tcp=True,
):
"""https://github.com/TokTok/c-toxcore/issues/469
I had a conversation with @irungentoo on IRC about whether we really need to call tox_bootstrap() when having UDP disabled and why. The answer is yes, because in addition to TCP relays (tox_add_tcp_relay()), toxcore also needs to know addresses of UDP onion nodes in order to work correctly. The DHT, however, is not used when UDP is disabled. tox_bootstrap() function resolves the address passed to it as argument and calls onion_add_bs_node_path() and DHT_bootstrap() functions. Although calling DHT_bootstrap() is not really necessary as DHT is not used, we still need to resolve the address of the DHT node in order to populate the onion routes with onion_add_bs_node_path() call.
"""
2022-10-11 09:33:39 +00:00
global aNODES_CACHE
2022-11-23 20:02:29 +00:00
2022-10-23 22:14:35 +00:00
key = ipv
2022-10-08 18:01:52 +00:00
key += ',0' if udp_not_tcp else ',1'
if key in aNODES_CACHE:
sorted_nodes = aNODES_CACHE[key]
2022-10-07 04:47:34 +00:00
else:
2022-10-08 18:01:52 +00:00
if not os.path.exists(sFile):
LOG.error("generate_nodes_from_file file not found " +sFile)
return []
try:
with open(sFile, 'rt') as fl:
json_nodes = json.loads(fl.read())['nodes']
except Exception as e:
LOG.error(f"generate_nodes_from_file error {sFile}\n{e}")
return []
else:
LOG.debug("generate_nodes_from_file " +sFile)
2022-10-07 04:47:34 +00:00
2022-10-08 18:01:52 +00:00
if udp_not_tcp:
nodes = [(node[ipv], node['port'], node['public_key'],) for
node in json_nodes if node[ipv] != 'NONE' \
2022-10-23 22:14:35 +00:00
and node["status_udp"] in [True, "true"]
2022-10-08 18:01:52 +00:00
]
else:
nodes = []
elts = [(node[ipv], node['tcp_ports'], node['public_key'],) \
for node in json_nodes if node[ipv] != 'NONE' \
2022-10-23 22:14:35 +00:00
and node["status_tcp"] in [True, "true"]
2022-10-08 18:01:52 +00:00
]
2022-10-23 22:14:35 +00:00
for (ipv, ports, public_key,) in elts:
2022-10-08 18:01:52 +00:00
for port in ports:
2022-10-23 22:14:35 +00:00
nodes += [(ipv, port, public_key)]
if not nodes:
LOG.warn(f'empty generate_nodes from {sFile} {json_nodes!r}')
return []
2022-10-11 09:33:39 +00:00
sorted_nodes = nodes
2022-10-08 18:01:52 +00:00
aNODES_CACHE[key] = sorted_nodes
2022-10-11 09:33:39 +00:00
random.shuffle(sorted_nodes)
2022-09-26 00:58:57 +00:00
if nodes_count is not None and len(sorted_nodes) > nodes_count:
sorted_nodes = sorted_nodes[-nodes_count:]
2022-10-07 04:47:34 +00:00
LOG.debug(f"generate_nodes_from_file {sFile} len={len(sorted_nodes)}")
2022-09-26 00:58:57 +00:00
return sorted_nodes
def tox_bootstrapd_port():
port = 33446
sFile = '/etc/tox-bootstrapd.conf'
if os.path.exists(sFile):
with open(sFile, 'rt') as oFd:
for line in oFd.readlines():
if line.startswith('port = '):
port = int(line[7:])
return port
2023-07-14 14:07:08 +00:00
def bootstrap_local(elts, lToxes, oArgs=None):
2022-09-26 00:58:57 +00:00
if os.path.exists('/run/tox-bootstrapd/tox-bootstrapd.pid'):
LOG.debug('/run/tox-bootstrapd/tox-bootstrapd.pid')
iRet = True
else:
iRet = os.system("netstat -nle4|grep -q :33")
if iRet > 0:
2022-10-23 22:14:35 +00:00
LOG.warn(f'bootstraping local No local DHT running')
LOG.info(f'bootstraping local')
2023-07-14 14:07:08 +00:00
return bootstrap_udp(elts, lToxes, oArgs)
2022-10-26 08:58:09 +00:00
2022-11-13 14:34:02 +00:00
def lDNSClean(l):
2023-07-14 14:07:08 +00:00
global lDEAD_BS
# list(set(l).difference(set(lDEAD_BS)))
return [elt for elt in l if elt not in lDEAD_BS]
2022-10-29 18:44:01 +00:00
def lExitExcluder(oArgs, iPort=9051):
"""
https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py
"""
if not stem:
LOG.warn('please install the stem Python package')
return ''
LOG.debug('lExcludeExitNodes')
try:
controller = oGetStemController(log_level=10)
# generator
relays = controller.get_server_descriptors()
except Exception as e:
LOG.error(f'Failed to get relay descriptors {e}')
return None
if controller.is_set('ExcludeExitNodes'):
LOG.info('ExcludeExitNodes is in use already.')
return None
exit_excludelist=[]
LOG.debug("Excluded exit relays:")
for relay in relays:
if relay.exit_policy.is_exiting_allowed() and not relay.contact:
if is_valid_fingerprint(relay.fingerprint):
exit_excludelist.append(relay.fingerprint)
LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint)
else:
LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint)
try:
controller.set_conf('ExcludeExitNodes', exit_excludelist)
LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist))
except Exception as e:
LOG.exception('ExcludeExitNodes ' +str(e))
return exit_excludelist
2023-07-14 14:07:08 +00:00
aHOSTS = {}
@functools.lru_cache(maxsize=20)
2022-10-23 22:14:35 +00:00
def sDNSLookup(host):
2023-07-14 14:07:08 +00:00
global aHOSTS
2022-10-23 22:14:35 +00:00
ipv = 0
if host in lDEAD_BS:
2023-07-14 14:07:08 +00:00
# LOG.warn(f"address skipped because in lDEAD_BS {host}")
2022-10-23 22:14:35 +00:00
return ''
2023-07-14 14:07:08 +00:00
if host in aHOSTS:
return aHOSTS[host]
2022-10-23 22:14:35 +00:00
try:
s = host.replace('.','')
int(s)
2023-07-14 14:07:08 +00:00
ipv = 4
2022-10-23 22:14:35 +00:00
except:
try:
s = host.replace(':','')
int(s)
ipv = 6
2023-07-14 14:07:08 +00:00
except: pass
2022-11-23 20:02:29 +00:00
2022-10-23 22:14:35 +00:00
if ipv > 0:
2023-07-14 14:07:08 +00:00
# LOG.debug(f"v={ipv} IP address {host}")
2022-10-23 22:14:35 +00:00
return host
2022-10-26 08:58:09 +00:00
2023-07-14 14:07:08 +00:00
LOG.debug(f"sDNSLookup {host}")
2022-10-26 08:58:09 +00:00
ip = ''
2022-10-29 18:44:01 +00:00
if host.endswith('.tox') or host.endswith('.onion'):
if False and stem:
2022-10-26 08:58:09 +00:00
ip = sMapaddressResolv(host)
if ip: return ip
2022-11-23 20:02:29 +00:00
2022-10-26 08:58:09 +00:00
ip = sTorResolve(host)
if ip: return ip
2022-11-23 20:02:29 +00:00
2022-10-23 22:14:35 +00:00
if not bHAVE_TORR:
LOG.warn(f"onion address skipped because no tor-resolve {host}")
return ''
try:
2022-11-17 14:18:51 +00:00
sout = f"/tmp/TR{os.getpid()}.log"
i = os.system(f"tor-resolve -4 {host} > {sout}")
2022-10-23 22:14:35 +00:00
if not i:
LOG.warn(f"onion address skipped because tor-resolve on {host}")
return ''
2022-11-17 14:18:51 +00:00
ip = open(sout, 'rt').read()
2022-10-23 22:14:35 +00:00
if ip.endswith('failed.'):
LOG.warn(f"onion address skipped because tor-resolve failed on {host}")
return ''
LOG.debug(f"onion address tor-resolve {ip} on {host}")
return ip
except:
pass
else:
try:
ip = socket.gethostbyname(host)
2023-07-14 14:07:08 +00:00
LOG.debug(f"host={host} gethostbyname IP address {ip}")
if ip:
aHOSTS[host] = ip
return ip
# drop through
2022-10-23 22:14:35 +00:00
except:
2022-10-26 08:58:09 +00:00
# drop through
pass
2022-11-23 20:02:29 +00:00
2022-10-23 22:14:35 +00:00
if ip == '':
try:
2022-11-17 14:18:51 +00:00
sout = f"/tmp/TR{os.getpid()}.log"
2023-07-14 14:07:08 +00:00
i = os.system(f"dig {host} +timeout=15|grep ^{host}|sed -e 's/.* //'> {sout}")
2022-10-23 22:14:35 +00:00
if not i:
2022-10-26 08:58:09 +00:00
LOG.warn(f"address skipped because dig failed on {host}")
2022-10-23 22:14:35 +00:00
return ''
2022-11-17 14:18:51 +00:00
ip = open(sout, 'rt').read().strip()
2022-10-23 22:14:35 +00:00
LOG.debug(f"address dig {ip} on {host}")
2023-07-14 14:07:08 +00:00
aHOSTS[host] = ip
2022-10-23 22:14:35 +00:00
return ip
except:
ip = host
2022-10-26 08:58:09 +00:00
LOG.debug(f'sDNSLookup {host} -> {ip}')
2023-07-14 14:07:08 +00:00
if ip and ip != host:
aHOSTS[host] = ip
2022-10-23 22:14:35 +00:00
return ip
2023-07-14 14:07:08 +00:00
def bootstrap_udp(lelts, lToxes, oArgs=None):
2022-11-13 14:34:02 +00:00
lelts = lDNSClean(lelts)
2023-07-14 14:07:08 +00:00
socket.setdefaulttimeout(15.0)
for oTox in lToxes:
2022-11-13 14:34:02 +00:00
random.shuffle(lelts)
2023-07-14 14:07:08 +00:00
if hasattr(oTox, 'oArgs'):
oArgs = oTox.oArgs
if hasattr(oArgs, 'contents') and oArgs.contents.proxy_type != 0:
lelts = lelts[:1]
# LOG.debug(f'bootstrap_udp DHT bootstraping {oTox.name} {len(lelts)}')
2022-11-13 14:34:02 +00:00
for largs in lelts:
2023-07-14 14:07:08 +00:00
assert len(largs) == 3
2022-10-11 09:33:39 +00:00
host, port, key = largs
2023-07-14 14:07:08 +00:00
assert host; assert port; assert key
if host in lDEAD_BS: continue
2022-10-23 22:14:35 +00:00
ip = sDNSLookup(host)
if not ip:
2023-07-14 14:07:08 +00:00
LOG.warn(f'bootstrap_udp to host={host} port={port} did not resolve ip={ip}')
2022-10-26 08:58:09 +00:00
continue
2022-11-23 20:02:29 +00:00
2022-10-11 09:33:39 +00:00
if type(port) == str:
port = int(port)
2022-09-26 00:58:57 +00:00
try:
2022-11-13 14:34:02 +00:00
assert len(key) == 64, key
2023-07-14 14:07:08 +00:00
# NOT ip
oRet = oTox.bootstrap(host,
2022-10-11 09:33:39 +00:00
port,
2022-10-23 22:14:35 +00:00
key)
2022-09-26 00:58:57 +00:00
except Exception as e:
2023-07-14 14:07:08 +00:00
if oArgs is None or (
hasattr(oArgs, 'contents') and oArgs.contents.proxy_type == 0):
pass
# LOG.error(f'bootstrap_udp failed to host={host} port={port} {e}')
2022-09-26 00:58:57 +00:00
continue
if not oRet:
2023-07-14 14:07:08 +00:00
LOG.warn(f'bootstrap_udp failed to {host} : {oRet}')
2023-12-11 06:38:55 +00:00
elif oTox.self_get_connection_status() != enums.TOX_CONNECTION['NONE']:
2023-07-14 14:07:08 +00:00
LOG.info(f'bootstrap_udp to {host} connected')
2022-10-13 14:15:18 +00:00
break
2022-09-26 00:58:57 +00:00
else:
2023-07-14 14:07:08 +00:00
# LOG.debug(f'bootstrap_udp to {host} not connected')
2022-10-23 22:14:35 +00:00
pass
2022-09-26 00:58:57 +00:00
2023-07-14 14:07:08 +00:00
def bootstrap_tcp(lelts, lToxes, oArgs=None):
2022-11-13 14:34:02 +00:00
lelts = lDNSClean(lelts)
for oTox in lToxes:
2023-07-14 14:07:08 +00:00
if hasattr(oTox, 'oArgs'): oArgs = oTox.oArgs
2022-11-13 14:34:02 +00:00
random.shuffle(lelts)
2023-07-14 14:07:08 +00:00
# LOG.debug(f'bootstrap_tcp bootstapping {oTox.name} {len(lelts)}')
2022-11-13 14:34:02 +00:00
for (host, port, key,) in lelts:
2023-07-14 14:07:08 +00:00
assert host; assert port;assert key
if host in lDEAD_BS: continue
2022-10-23 22:14:35 +00:00
ip = sDNSLookup(host)
if not ip:
2023-07-14 14:07:08 +00:00
LOG.warn(f'bootstrap_tcp to {host} did not resolve ip={ip}')
2022-10-23 22:14:35 +00:00
# continue
ip = host
2022-10-29 18:44:01 +00:00
if host.endswith('.onion') and stem:
l = lIntroductionPoints(host)
if not l:
LOG.warn(f'bootstrap_tcp to {host} has no introduction points')
2022-11-13 14:34:02 +00:00
continue
2022-10-23 22:14:35 +00:00
if type(port) == str:
port = int(port)
2022-10-13 14:15:18 +00:00
try:
2022-11-13 14:34:02 +00:00
assert len(key) == 64, key
oRet = oTox.add_tcp_relay(ip,
2022-10-23 22:14:35 +00:00
port,
key)
2022-09-26 00:58:57 +00:00
except Exception as e:
2023-12-07 19:38:26 +00:00
# The address could not be resolved to an IP address, or the IP address passed was invalid.
2023-12-06 16:32:06 +00:00
LOG.warn(f'bootstrap_tcp to {host} : ' +str(e))
2022-09-26 00:58:57 +00:00
continue
if not oRet:
2023-07-14 14:07:08 +00:00
LOG.warn(f'bootstrap_tcp failed to {host} : {oRet}')
2023-12-06 16:32:06 +00:00
elif hasattr(oTox, 'mycon_time') and oTox.mycon_time == 1:
2023-07-14 14:07:08 +00:00
LOG.info(f'bootstrap_tcp to {host} not yet connected last=1')
2023-12-06 16:32:06 +00:00
elif hasattr(oTox, 'mycon_status') and oTox.mycon_status is False:
2023-07-14 14:07:08 +00:00
LOG.info(f'bootstrap_tcp to {host} not True' \
+f" last={int(oTox.mycon_time)}" )
2023-12-11 06:38:55 +00:00
elif oTox.self_get_connection_status() != enums.TOX_CONNECTION['NONE']:
2023-12-07 19:38:26 +00:00
LOG.info(f'bootstrap_tcp to {host} connected' )
2022-10-13 14:15:18 +00:00
break
2022-09-26 00:58:57 +00:00
else:
2023-12-07 19:38:26 +00:00
# LOG.debug(f'bootstrap_tcp to {host} but not connected'
2023-12-06 16:32:06 +00:00
# +f" last={int(oTox.mycon_time)}" )
2022-10-23 22:14:35 +00:00
pass
2023-12-07 19:38:26 +00:00
2022-11-05 01:10:35 +00:00
def iNmapInfoNmap(sProt, sHost, sPort, key=None, environ=None, cmd=''):
2022-10-28 02:33:36 +00:00
if sHost in ['-', 'NONE']: return 0
2022-11-05 01:10:35 +00:00
if not nmap: return 0
nmps = nmap.PortScanner
if sProt in ['socks', 'socks5', 'tcp4']:
prot = 'tcp'
cmd = f" -Pn -n -sT -p T:{sPort}"
else:
prot = 'udp'
cmd = f" -Pn -n -sU -p U:{sPort}"
LOG.debug(f"iNmapInfoNmap cmd={cmd}")
sys.stdout.flush()
o = nmps().scan(hosts=sHost, arguments=cmd)
aScan = o['scan']
ip = list(aScan.keys())[0]
2022-11-17 14:18:51 +00:00
state = aScan[ip][prot][sPort]['state']
2022-11-05 01:10:35 +00:00
LOG.info(f"iNmapInfoNmap: to {sHost} {state}")
return 0
2022-11-23 20:02:29 +00:00
2022-11-05 01:10:35 +00:00
def iNmapInfo(sProt, sHost, sPort, key=None, environ=None, cmd='nmap'):
if sHost in ['-', 'NONE']: return 0
2022-10-27 06:50:47 +00:00
sFile = os.path.join("/tmp", f"{sHost}.{os.getpid()}.nmap")
if sProt in ['socks', 'socks5', 'tcp4']:
2022-11-05 01:10:35 +00:00
cmd += f" -Pn -n -sT -p T:{sPort} {sHost} | grep /tcp "
2022-10-27 06:50:47 +00:00
else:
2022-11-05 01:10:35 +00:00
cmd += f" -Pn -n -sU -p U:{sPort} {sHost} | grep /udp "
LOG.debug(f"iNmapInfo cmd={cmd}")
sys.stdout.flush()
2023-12-06 16:32:06 +00:00
iRet = os.system(cmd +f" >{sFile} 2>&1 ")
2022-11-05 01:10:35 +00:00
LOG.debug(f"iNmapInfo cmd={cmd} iRet={iRet}")
2022-10-27 06:50:47 +00:00
if iRet != 0:
return iRet
assert os.path.exists(sFile), sFile
with open(sFile, 'rt') as oFd:
l = oFd.readlines()
assert len(l)
2022-11-05 01:10:35 +00:00
l = [line for line in l if line and not line.startswith('WARNING:')]
2022-10-27 06:50:47 +00:00
s = '\n'.join([s.strip() for s in l])
2022-11-05 01:10:35 +00:00
LOG.info(f"iNmapInfo: to {sHost}\n{s}")
2022-10-27 06:50:47 +00:00
return 0
2023-12-06 16:32:06 +00:00
2023-12-11 06:38:55 +00:00
# bootstrap_iNmapInfo(lElts, self._args, sProt)
2022-11-05 01:10:35 +00:00
def bootstrap_iNmapInfo(lElts, oArgs, protocol="tcp4", bIS_LOCAL=False, iNODES=iNODES, cmd='nmap'):
2022-10-18 00:16:56 +00:00
if not bIS_LOCAL and not bAreWeConnected():
LOG.warn(f"bootstrap_iNmapInfo not local and NOT CONNECTED")
return True
2023-07-14 14:07:08 +00:00
if os.environ['USER'] != 'root':
2023-12-06 16:32:06 +00:00
LOG.warn(f"bootstrap_iNmapInfo not ROOT USER={os.environ['USER']}")
cmd = 'sudo ' +cmd
2023-12-07 19:38:26 +00:00
2022-10-18 00:16:56 +00:00
lRetval = []
2023-12-06 16:32:06 +00:00
LOG.info(f"bootstrap_iNmapInfo testing nmap={nmap} len={len(lElts[:iNODES])}")
2022-10-18 00:16:56 +00:00
for elts in lElts[:iNODES]:
2022-10-23 22:14:35 +00:00
host, port, key = elts
ip = sDNSLookup(host)
if not ip:
2023-12-06 16:32:06 +00:00
LOG.info(f"bootstrap_iNmapInfo to {host} did not resolve ip={ip}")
2022-10-23 22:14:35 +00:00
continue
if type(port) == str:
port = int(port)
2022-10-18 00:16:56 +00:00
iRet = -1
try:
2022-11-05 01:10:35 +00:00
if not nmap:
iRet = iNmapInfo(protocol, ip, port, key, cmd=cmd)
else:
iRet = iNmapInfoNmap(protocol, ip, port, key)
2022-10-18 00:16:56 +00:00
if iRet != 0:
2022-10-23 22:14:35 +00:00
LOG.warn('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
2022-10-18 00:16:56 +00:00
lRetval += [False]
else:
2023-12-06 16:32:06 +00:00
LOG.info('iNmapInfo to ' +repr(host) +' retval=' +str(iRet))
2022-10-18 00:16:56 +00:00
lRetval += [True]
except Exception as e:
2022-11-05 01:10:35 +00:00
LOG.exception('iNmapInfo to {host} : ' +str(e)
)
2022-10-18 00:16:56 +00:00
lRetval += [False]
return any(lRetval)
2022-09-26 00:58:57 +00:00
def caseFactory(cases):
"""We want the tests run in order."""
if len(cases) > 1:
2022-11-23 20:02:29 +00:00
ordered_cases = sorted(cases, key=lambda f: inspect.findsource(f)[1])
2022-09-26 00:58:57 +00:00
else:
ordered_cases = cases
return ordered_cases
def suiteFactory(*testcases):
"""We want the tests run in order."""
linen = lambda f: getattr(tc, f).__code__.co_firstlineno
lncmp = lambda a, b: linen(a) - linen(b)
test_suite = unittest.TestSuite()
for tc in testcases:
test_suite.addTest(unittest.makeSuite(tc, sortUsing=lncmp))
return test_suite