From 91ba0efbb461654bb537d9b3ed0a5df1e8ed5495 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Mon, 1 Aug 2022 00:27:53 +0800 Subject: [PATCH] update: enhanced testing experience --- Tester/Brook.py | 8 +-- Tester/Hysteria.py | 8 +-- Tester/Plugin.py | 6 +- Tester/Settings.py | 12 ++++ Tester/Shadowsocks.py | 8 +-- Tester/ShadowsocksR.py | 9 ++- Tester/Trojan.py | 10 ++- Tester/TrojanGo.py | 8 +-- Tester/V2ray.py | 5 +- Tester/VLESS.py | 8 +-- Tester/VMess.py | 8 +-- Tester/Xray.py | 6 +- Tester/__init__.py | 113 ++++++++++++++++++++++++++--- test.py | 157 +++++++++++++---------------------------- 14 files changed, 198 insertions(+), 168 deletions(-) create mode 100644 Tester/Settings.py diff --git a/Tester/Brook.py b/Tester/Brook.py index 02b4017..7059fcd 100644 --- a/Tester/Brook.py +++ b/Tester/Brook.py @@ -4,12 +4,10 @@ import copy import itertools from Builder import Brook -from Tester import Settings from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import genFlag -from Basis.Functions import hostFormat -from Basis.Functions import getAvailablePort +from Tester.Settings import Settings +from Basis.Functions import hostFormat, genFlag, getAvailablePort def originStream(isUot: bool) -> dict: @@ -68,7 +66,7 @@ def loadTest(stream: dict) -> dict: clientCommand, _, _ = Brook.load(proxyInfo, socksInfo, '') serverCommand = ['brook', '--debug', '--listen', ':'] + stream['command'](proxyInfo) testInfo = { # release test info - 'title': 'Brook test: ' + stream['caption'], + 'caption': 'Brook test: ' + stream['caption'], 'client': Process(Settings['workDir'], cmd = clientCommand, isStart = False), 'server': Process(Settings['workDir'], cmd = serverCommand, isStart = False), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/Hysteria.py b/Tester/Hysteria.py index 112481d..64e3124 100644 --- a/Tester/Hysteria.py +++ b/Tester/Hysteria.py @@ -4,14 +4,12 @@ import os import json import itertools -from Tester import Settings from Builder import Hysteria from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import genFlag -from Basis.Functions import hostFormat +from Tester.Settings import Settings from Basis.Methods import hysteriaProtocols -from Basis.Functions import getAvailablePort +from Basis.Functions import hostFormat, genFlag, getAvailablePort def loadServer(configFile: str, hysteriaConfig: dict) -> Process: @@ -71,7 +69,7 @@ def loadTest(protocol: str, isObfs: bool, isAuth: bool) -> dict: 'config': [proxyInfo['passwd']] } testInfo = { - 'title': caption, + 'caption': caption, 'client': loadClient(configName + '_client.json', proxyInfo, socksInfo), 'server': loadServer(configName + '_server.json', serverConfig), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/Plugin.py b/Tester/Plugin.py index faaa63e..7f2fa80 100644 --- a/Tester/Plugin.py +++ b/Tester/Plugin.py @@ -4,13 +4,11 @@ import os import re import json -from Tester import Settings from Basis.Logger import logging from Basis.Process import Process from Basis.Methods import plugins -from Basis.Functions import genFlag -from Basis.Functions import hostFormat -from Basis.Functions import getAvailablePort +from Tester.Settings import Settings +from Basis.Functions import genFlag, hostFormat, getAvailablePort pluginParams = { diff --git a/Tester/Settings.py b/Tester/Settings.py new file mode 100644 index 0000000..72378d2 --- /dev/null +++ b/Tester/Settings.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +Settings = { + 'workDir': '/tmp/ProxyC', + 'serverBind': '127.0.0.1', + 'clientBind': '127.0.0.1', + 'site': 'www.bing.com', + 'host': '343.re', + 'cert': '/etc/ssl/certs/343.re/fullchain.pem', + 'key': '/etc/ssl/certs/343.re/privkey.pem', +} diff --git a/Tester/Shadowsocks.py b/Tester/Shadowsocks.py index 0491e89..6f86b69 100644 --- a/Tester/Shadowsocks.py +++ b/Tester/Shadowsocks.py @@ -6,14 +6,12 @@ import json import base64 import itertools from Tester import Plugin -from Tester import Settings from Builder import Shadowsocks from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import md5Sum -from Basis.Functions import genFlag -from Basis.Functions import getAvailablePort +from Tester.Settings import Settings from Basis.Methods import ssMethods, ssAllMethods +from Basis.Functions import md5Sum, genFlag, getAvailablePort def loadConfig(proxyInfo: dict) -> dict: # load basic config option @@ -126,7 +124,7 @@ def loadTest(serverType: str, clientType: str, method: str, plugin: dict or None configName += '_%s_%s' % (plugin['type'], md5Sum(plugin['type'] + plugin['caption'])[:8]) pluginText = '' if plugin is None else (' [%s -> %s]' % (plugin['type'], plugin['caption'])) testInfo = { # release test info - 'title': 'Shadowsocks test: {%s <- %s -> %s}%s' % (serverType, method, clientType, pluginText), + 'caption': 'Shadowsocks test: {%s <- %s -> %s}%s' % (serverType, method, clientType, pluginText), 'client': loadClient(clientType, configName + '_client.json', {**proxyInfo, **pluginClient}, socksInfo), 'server': loadServer(serverType, configName + '_server.json', {**proxyInfo, **pluginServer}), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/ShadowsocksR.py b/Tester/ShadowsocksR.py index 50303bd..7e53393 100644 --- a/Tester/ShadowsocksR.py +++ b/Tester/ShadowsocksR.py @@ -3,12 +3,11 @@ import os import json -from Tester import Settings -from Basis.Logger import logging from Builder import ShadowsocksR +from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import genFlag -from Basis.Functions import getAvailablePort +from Tester.Settings import Settings +from Basis.Functions import genFlag, getAvailablePort from Basis.Methods import ssrMethods, ssrProtocols, ssrObfuscations @@ -56,7 +55,7 @@ def loadTest(method: str, protocol: str, obfs: str) -> dict: } configName = 'ssr_%s_%s_%s' % (method, protocol, obfs) # prefix of config file name testInfo = { # release test info - 'title': 'ShadowsocksR test: method = %s | protocol = %s | obfs = %s' % (method, protocol, obfs), + 'caption': 'ShadowsocksR test: method = %s | protocol = %s | obfs = %s' % (method, protocol, obfs), 'client': loadClient(configName + '_client.json', proxyInfo, socksInfo), 'server': loadServer(configName + '_server.json', proxyInfo), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/Trojan.py b/Tester/Trojan.py index 19019a2..0a23103 100644 --- a/Tester/Trojan.py +++ b/Tester/Trojan.py @@ -5,13 +5,11 @@ import os import json from Tester import Xray from Builder import Trojan -from Tester import Settings from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import md5Sum from Basis.Methods import xtlsFlows -from Basis.Functions import genFlag -from Basis.Functions import getAvailablePort +from Tester.Settings import Settings +from Basis.Functions import md5Sum, genFlag, getAvailablePort def loadServer(configFile: str, proxyInfo: dict, streamConfig: dict, xtlsFlow: str or None) -> Process: @@ -72,7 +70,7 @@ def loadBasicTest(tcpTlsStream: dict) -> dict: 'content': json.dumps(trojanConfig) }, isStart = False) testInfo = { # release test info - 'title': 'Trojan test: basic connection', + 'caption': 'Trojan test: basic connection', 'client': loadClient('trojan_basic_client.json', proxyInfo, socksInfo), 'server': trojanServer, 'socks': socksInfo, # exposed socks5 address @@ -102,7 +100,7 @@ def loadTest(stream: dict) -> dict: xtlsFlow = xtlsFlow.replace('splice', 'direct') # XTLS on server should use xtls-rprx-direct flow configName = 'trojan_%s' % (md5Sum(stream['caption'])[:8]) testInfo = { # release test info - 'title': 'Trojan test: %s' % stream['caption'], + 'caption': 'Trojan test: %s' % stream['caption'], 'client': loadClient(configName + '_client.json', proxyInfo, socksInfo), 'server': loadServer(configName + '_server.json', proxyInfo, stream['server'], xtlsFlow), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/TrojanGo.py b/Tester/TrojanGo.py index fe28dcb..79c818e 100644 --- a/Tester/TrojanGo.py +++ b/Tester/TrojanGo.py @@ -4,14 +4,12 @@ import os import json from Tester import Plugin -from Tester import Settings from Builder import TrojanGo from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import md5Sum -from Basis.Functions import genFlag +from Tester.Settings import Settings from Basis.Methods import trojanGoMethods -from Basis.Functions import getAvailablePort +from Basis.Functions import md5Sum, genFlag, getAvailablePort def loadServer(configFile: str, proxyInfo: dict) -> Process: @@ -76,7 +74,7 @@ def loadTest(wsObject: dict or None, ssObject: dict or None, plugin: dict or Non ('' if wsObject is None else ' (with websocket)') + \ ('' if plugin is None else ' [%s -> %s]' % (plugin['type'], plugin['caption'])) testInfo = { # release test info - 'title': testTitle, + 'caption': testTitle, 'client': loadClient(configName + '_client.json', {**proxyInfo, **pluginClient}, socksInfo), 'server': loadServer(configName + '_server.json', {**proxyInfo, **pluginServer}), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/V2ray.py b/Tester/V2ray.py index 4e0d5b3..275791f 100644 --- a/Tester/V2ray.py +++ b/Tester/V2ray.py @@ -3,10 +3,9 @@ import copy import itertools -from Tester import Settings from Basis.Functions import genFlag -from Basis.Methods import quicMethods -from Basis.Methods import udpObfuscations +from Tester.Settings import Settings +from Basis.Methods import quicMethods, udpObfuscations httpConfig = { 'version': '1.1', diff --git a/Tester/VLESS.py b/Tester/VLESS.py index 0f786b4..f59a8bd 100644 --- a/Tester/VLESS.py +++ b/Tester/VLESS.py @@ -5,13 +5,11 @@ import os import json from Tester import Xray from Builder import VLESS -from Tester import Settings from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import md5Sum from Basis.Methods import xtlsFlows -from Basis.Functions import genUUID -from Basis.Functions import getAvailablePort +from Tester.Settings import Settings +from Basis.Functions import md5Sum, genUUID, getAvailablePort def loadServer(configFile: str, proxyInfo: dict, streamConfig: dict, xtlsFlow: str or None) -> Process: @@ -63,7 +61,7 @@ def loadTest(stream: dict) -> dict: xtlsFlow = xtlsFlow.replace('splice', 'direct') # XTLS on server should use xtls-rprx-direct flow configName = 'vless_%s' % (md5Sum(stream['caption'])[:8]) testInfo = { # release test info - 'title': 'VLESS test: %s' % stream['caption'], + 'caption': 'VLESS test: %s' % stream['caption'], 'client': loadClient(configName + '_client.json', proxyInfo, socksInfo), 'server': loadServer(configName + '_server.json', proxyInfo, stream['server'], xtlsFlow), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/VMess.py b/Tester/VMess.py index 2f5e125..ea90b47 100644 --- a/Tester/VMess.py +++ b/Tester/VMess.py @@ -6,14 +6,12 @@ import json import itertools from Tester import V2ray from Builder import VMess -from Tester import Settings from Builder import pathEnv from Basis.Logger import logging from Basis.Process import Process -from Basis.Functions import md5Sum -from Basis.Functions import genUUID +from Tester.Settings import Settings from Basis.Methods import vmessMethods -from Basis.Functions import getAvailablePort +from Basis.Functions import md5Sum, genUUID, getAvailablePort def loadServer(configFile: str, proxyInfo: dict, streamConfig: dict) -> Process: # load server process @@ -63,7 +61,7 @@ def loadTest(method: str, aid: int, stream: dict) -> dict: } configName = 'vmess_%s_%i_%s' % (method, aid, md5Sum(stream['caption'])[:8]) testInfo = { # release test info - 'title': 'VMess test: %s [security = %s | alterId = %i]' % (stream['caption'], method, aid), + 'caption': 'VMess test: %s [security = %s | alterId = %i]' % (stream['caption'], method, aid), 'client': loadClient(configName + '_client.json', proxyInfo, socksInfo), 'server': loadServer(configName + '_server.json', proxyInfo, stream['server']), 'socks': socksInfo, # exposed socks5 address diff --git a/Tester/Xray.py b/Tester/Xray.py index e75c212..8aeb761 100644 --- a/Tester/Xray.py +++ b/Tester/Xray.py @@ -4,11 +4,9 @@ import copy import itertools from Tester import V2ray -from Tester import Settings -from Basis.Methods import xtlsFlows from Basis.Functions import genFlag -from Basis.Methods import quicMethods -from Basis.Methods import udpObfuscations +from Tester.Settings import Settings +from Basis.Methods import xtlsFlows, quicMethods, udpObfuscations loadConfig = V2ray.loadConfig tcpStream = V2ray.tcpStream diff --git a/Tester/__init__.py b/Tester/__init__.py index 72378d2..23c3738 100644 --- a/Tester/__init__.py +++ b/Tester/__init__.py @@ -1,12 +1,109 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -Settings = { - 'workDir': '/tmp/ProxyC', - 'serverBind': '127.0.0.1', - 'clientBind': '127.0.0.1', - 'site': 'www.bing.com', - 'host': '343.re', - 'cert': '/etc/ssl/certs/343.re/fullchain.pem', - 'key': '/etc/ssl/certs/343.re/privkey.pem', +import time +import requests +from threading import Thread +from Basis.Logger import logging +from Basis.Functions import md5Sum, hostFormat, checkPortStatus + +from Tester import Brook +from Tester import VMess +from Tester import VLESS +from Tester import Trojan +from Tester import TrojanGo +from Tester import Hysteria +from Tester import Shadowsocks +from Tester import ShadowsocksR + +testEntry = { + 'ss': Shadowsocks.load(), + 'ss-all': Shadowsocks.load(isExtra = True), + 'ssr': ShadowsocksR.load(), + 'vmess': VMess.load(), + 'vless': VLESS.load(), + 'trojan': Trojan.load(), + 'trojan-go': TrojanGo.load(), + 'brook': Brook.load(), + 'hysteria': Hysteria.load(), } + + +def waitPort(port: int, times: int = 100, delay: int = 100) -> bool: # wait until port occupied + for i in range(times): + if not checkPortStatus(port): # port occupied + return True + time.sleep(delay / 1000) # default wait 100ms + return False # timeout + + +def httpCheck(socksInfo: dict, url: str, timeout: int = 10): + socksProxy = 'socks5://%s:%i' % (hostFormat(socksInfo['addr'], v6Bracket = True), socksInfo['port']) + try: + proxy = { + 'http': socksProxy, + 'https': socksProxy, + } + request = requests.get(url, timeout = timeout, proxies = proxy) + request.raise_for_status() + logging.info('%s -> ok' % socksProxy) + except Exception as exp: + logging.error('%s -> error' % socksProxy) + logging.error('requests exception\n' + str(exp)) + raise RuntimeError('socks5 test failed') + + +def runTest(testInfo: dict, testUrl: str, testFilter: set or None, delay: int = 1) -> None: + testInfo['hash'] = md5Sum(testInfo['caption'])[:12] + if testFilter is not None and testInfo['hash'] not in testFilter: return + logging.warning('[%s] %s' % (testInfo['hash'], testInfo['caption'])) + testInfo['server'].start() # start test server + if waitPort(testInfo['interface']['port']): # wait for server + logging.debug('server start complete') + testInfo['client'].start() # start test client + if waitPort(testInfo['socks']['port']): # wait for client + logging.debug('client start complete') + try: + logging.debug('start test process') + time.sleep(delay) + httpCheck(testInfo['socks'], testUrl) + except: + # client debug info + logging.warning('client info') + logging.error('command -> %s' % testInfo['client'].cmd) + logging.error('envVar -> %s' % testInfo['client'].env) + logging.error('file -> %s' % testInfo['client'].file) + logging.warning('client capture output') + logging.error('\n%s' % testInfo['client'].output) + # server debug info + logging.warning('server info') + logging.error('command -> %s' % testInfo['server'].cmd) + logging.error('envVar -> %s' % testInfo['server'].env) + logging.error('file -> %s' % testInfo['server'].file) + logging.warning('server capture output') + logging.error('\n%s' % testInfo['server'].output) + finally: + testInfo['client'].quit() + testInfo['server'].quit() + + +def test(testIter: iter, threadNum: int, testUrl: str, testFilter: set or None = None): + threads = [] + while True: # infinite loop + try: + for thread in threads: + if thread.is_alive(): continue + threads.remove(thread) # remove dead thread + if len(threads) < threadNum: + for i in range(threadNum - len(threads)): # start threads within limit + thread = Thread( # create new thread + target = runTest, + args = (next(testIter), testUrl, testFilter) + ) + thread.start() + threads.append(thread) # record thread info + time.sleep(0.1) + except StopIteration: # traverse completed + break + for thread in threads: # wait until all threads exit + thread.join() diff --git a/test.py b/test.py index 12190a4..91ab020 100755 --- a/test.py +++ b/test.py @@ -1,118 +1,59 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -import time -import requests -from threading import Thread - -from Tester import Brook -from Tester import VMess -from Tester import VLESS -from Tester import Trojan -from Tester import TrojanGo -from Tester import Hysteria -from Tester import Shadowsocks -from Tester import ShadowsocksR - +import sys +import Tester +from Tester import testEntry from Basis.Logger import logging -from Basis.Functions import hostFormat -from Basis.Functions import checkPortStatus +threadNum = 16 +testItem = None +testFilter = None +testUrl = 'http://baidu.com' +helpMsg = ''' + ./test.py [ITEM] [OPTIONS] -def waitForStart(port: int, times: int = 100, delay: int = 100) -> bool: - for i in range(times): - if not checkPortStatus(port): # port occupied - return True - time.sleep(delay / 1000) # default wait 100ms - return False # timeout + [ITEM]: ss / ss-all / ssr / vmess / vless / trojan / trojan-go / brook / hysteria + [OPTIONS]: + --thread NUM thread number + --url URL http check url + --filter ID1[,ID2...] test the specified id + --all test extra shadowsocks items + --help show this message +''' -def test(testObj: dict) -> None: - logging.warning(testObj['title']) - testObj['server'].start() - time.sleep(0.2) - testObj['client'].start() - if waitForStart(testObj['interface']['port']): - logging.debug('server start complete') - if waitForStart(testObj['socks']['port']): - logging.debug('client start complete') - logging.debug('start test process') - time.sleep(1) - errFlag = False - socks5 = '%s:%i' % ( - hostFormat(testObj['socks']['addr'], v6Bracket = True), - testObj['socks']['port'] - ) +def getArg(field: str) -> str or None: try: - request = requests.get( - 'http://iserv.scutbot.cn', - proxies = { - 'http': 'socks5://' + socks5, - 'https': 'socks5://' + socks5, - }, - timeout = 10 - ) - request.raise_for_status() - logging.info('socks5 %s -> ok' % socks5) - except Exception as exp: - logging.error('socks5 %s -> error' % socks5) - logging.error('requests exception\n' + str(exp)) - errFlag = True - - testObj['client'].quit() - testObj['server'].quit() - if errFlag: - logging.warning('client info') - logging.error('command -> %s' % testObj['client'].cmd) - logging.error('envVar -> %s' % testObj['client'].env) - logging.error('file -> %s' % testObj['client'].file) - logging.warning('client capture output') - logging.error('\n' + str(testObj['client'].output)) - logging.warning('server info') - logging.error('command -> %s' % testObj['server'].cmd) - logging.error('envVar -> %s' % testObj['server'].env) - logging.error('file -> %s' % testObj['server'].file) - logging.warning('server capture output') - logging.error('\n' + str(testObj['server'].output)) - - -def runTest(testIter: iter, threadNum: int): - threads = [] - while True: # infinite loop - try: - for thread in threads: - if thread.is_alive(): continue - threads.remove(thread) # remove dead thread - if len(threads) < threadNum: - for i in range(threadNum - len(threads)): # start threads within limit - thread = Thread(target=test, args=(next(testIter),)) # create new thread - thread.start() - threads.append(thread) # record thread info - time.sleep(0.1) - except StopIteration: # traverse completed - break - for thread in threads: # wait until all threads exit - thread.join() - - -ss = Shadowsocks.load(isExtra = True) -# ss = Shadowsocks.load(isExtra = False) -ssr = ShadowsocksR.load() -vmess = VMess.load() -vless = VLESS.load() -trojan = Trojan.load() -trojanGo = TrojanGo.load() -brook = Brook.load() -hysteria = Hysteria.load() - -logging.critical('test start') -runTest(ss, 64) -runTest(ssr, 64) -runTest(vmess, 64) -runTest(vless, 64) -runTest(trojan, 64) -runTest(trojanGo, 64) -runTest(brook, 64) -runTest(hysteria, 64) -logging.critical('test complete') + index = sys.argv.index(field) + return sys.argv[index + 1] + except: + return None + +if '--help' in sys.argv: + print(helpMsg) + sys.exit(0) +if len(sys.argv) > 1 and not sys.argv[1].startswith('--'): + testItem = sys.argv[1] +if getArg('--url') is not None: + testUrl = getArg('--url') +if getArg('--thread') is not None: + threadNum = int(getArg('--thread')) +if getArg('--filter') is not None: + testFilter = set(getArg('--filter').split(',')) + +logging.critical('test item: ' + ('all' if testItem is None else testItem)) +logging.critical('filter: %s' % testFilter) +logging.critical('url: ' + testUrl) +logging.critical('thread number: %i' % threadNum) +logging.critical('TEST START') +if testItem is not None: + Tester.test(testEntry[testItem], threadNum, testUrl, testFilter) +else: + for item in testEntry: + if item == ('ss' if '--all' in sys.argv else 'ss-all'): # skip ss / ss-all + continue + logging.critical('TEST ITEM -> ' + item) + Tester.test(testEntry[item], threadNum, testUrl, testFilter) +logging.critical('TEST COMPLETE')