diff --git a/Basis/Functions.py b/Basis/Functions.py index 2e6e141..25da675 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -19,7 +19,7 @@ def genFlag(length: int = 12) -> str: # generate random task flag return flag -def getAvailablePort(rangeStart: int = 41952, rangeEnd: int = 65535) -> int: # get a available port +def getAvailablePort(rangeStart: int = 1024, rangeEnd: int = 65535, waitTime: int = 10) -> int: # get available port if rangeStart > rangeEnd or rangeStart < 1 or rangeEnd > 65535: raise RuntimeError('invalid port range') while True: @@ -27,7 +27,7 @@ def getAvailablePort(rangeStart: int = 41952, rangeEnd: int = 65535) -> int: # if checkPortStatus(port): logging.debug('get new port -> %i' % port) return port - time.sleep(0.1) # wait for 100ms + time.sleep(waitTime / 1000) # ms -> s (default 10ms) def checkPortStatus(port: int) -> bool: # check if the port is occupied diff --git a/Basis/Logger.py b/Basis/Logger.py index 383cee2..764bf9e 100644 --- a/Basis/Logger.py +++ b/Basis/Logger.py @@ -6,8 +6,8 @@ import logging from colorlog import ColoredFormatter logFile = 'runtime.log' -logLevel = logging.DEBUG -# logLevel = logging.WARNING +# logLevel = logging.DEBUG +logLevel = logging.WARNING dateFormat = '%Y-%m-%d %H:%M:%S' logFormat = '[%(asctime)s] [%(levelname)s] %(message)s (%(module)s.%(funcName)s)' logging.basicConfig( diff --git a/Tester/Shadowsocks.py b/Tester/Shadowsocks.py index bf6ca62..4dca299 100644 --- a/Tester/Shadowsocks.py +++ b/Tester/Shadowsocks.py @@ -4,6 +4,7 @@ import os import json import base64 +import itertools from Builder import Shadowsocks from Basis.Logger import logging from Basis.Process import Process @@ -82,75 +83,74 @@ def ssPythonLegacy(proxyInfo: dict, isUdp: bool) -> tuple[dict, list]: def loadPassword(method: str) -> str: - b64 = lambda x: base64.b64encode(x.encode(encoding = 'utf-8')).decode(encoding = 'utf-8') - if not method.startswith('2022-blake3-'): + b64 = lambda x: base64.b64encode(x.encode(encoding = 'utf-8')).decode(encoding = 'utf-8') # base64 encode + if not method.startswith('2022-blake3-'): # normal method return genFlag(length = 8) - if method == '2022-blake3-aes-128-gcm': + if method == '2022-blake3-aes-128-gcm': # 2022-blake3-aes-128-gcm use 16 byte length password return b64(genFlag(length = 16)) return b64(genFlag(length = 32)) # three other 2022-blake3-* methods use 32 byte length password -def testConnection(serverType: str, clientType: str, method: str) -> dict: - proxyInfo = { - 'server': settings['serverBind'], - 'port': getAvailablePort(), - 'method': method, - 'passwd': loadPassword(method), - 'plugin': None - } - socksInfo = { - 'addr': settings['clientBind'], - 'port': getAvailablePort() - } - - ssClientLoad = { +def loadClient(ssType: str, configFile: str, proxyInfo: dict, socksInfo: dict) -> Process: + ssConfig, ssClient = { # generate client start command and its config file 'ss-rust': Shadowsocks.ssRust, 'ss-libev': Shadowsocks.ssLibev, 'ss-python': Shadowsocks.ssPython, 'ss-python-legacy': Shadowsocks.ssPythonLegacy - }[clientType] - ssConfig, ssClient = ssClientLoad(proxyInfo, socksInfo, isUdp = False) - clientFile = os.path.join(settings['workDir'], '%s_%s_%s' % (serverType, clientType, method) + '_client.json') - client = Process(settings['workDir'], cmd = ssClient + ['-c', clientFile], file = { + }[ssType](proxyInfo, socksInfo, isUdp = False) + clientFile = os.path.join(settings['workDir'], configFile) + return Process(settings['workDir'], cmd = ssClient + ['-c', clientFile], file = { # load client process 'path': clientFile, 'content': json.dumps(ssConfig) }, isStart = False) - ssServerLoad = { + +def loadServer(ssType: str, configFile: str, proxyInfo: dict) -> Process: + ssConfig, ssServer = { # generate server start command and its config file 'ss-rust': ssRust, 'ss-libev': ssLibev, 'ss-python': ssPython, 'ss-python-legacy': ssPythonLegacy - }[serverType] - ssConfig, ssServer = ssServerLoad(proxyInfo, isUdp = False) - serverFile = os.path.join(settings['workDir'], '%s_%s_%s' % (serverType, clientType, method) + '_server.json') - server = Process(settings['workDir'], cmd = ssServer + ['-c', serverFile], file = { + }[ssType](proxyInfo, isUdp = False) + serverFile = os.path.join(settings['workDir'], configFile) + return Process(settings['workDir'], cmd = ssServer + ['-c', serverFile], file = { # load server process 'path': serverFile, 'content': json.dumps(ssConfig) }, isStart = False) - testInfo = { + +def loadTest(serverType: str, clientType: str, method: str) -> dict: + proxyInfo = { # connection info + 'server': settings['serverBind'], + 'port': getAvailablePort(), + 'method': method, + 'passwd': loadPassword(method), + 'plugin': None + } + socksInfo = { # socks5 interface for test + 'addr': settings['clientBind'], + 'port': getAvailablePort() + } + configName = '%s_%s_%s' % (serverType, clientType, method) # prefix of config file name + testInfo = { # release test info 'title': 'Shadowsocks test: {%s <- %s -> %s}' % (serverType, method, clientType), - 'socks': socksInfo, - 'client': client, - 'server': server, + 'client': loadClient(clientType, configName + '_client.json', proxyInfo, socksInfo), + 'server': loadServer(serverType, configName + '_server.json', proxyInfo), + 'socks': socksInfo, # exposed socks5 interface } logging.debug('New shadowsocks test connection -> %s' % testInfo) return testInfo -def load(isExtra: bool = False) -> list: - result = [] - if isExtra: - for ssServer in ssMethods: - for method in ssMethods[ssServer]: - for ssClient in ssMethods: - if method not in ssMethods[ssClient]: continue - result.append(testConnection(ssServer, ssClient, method)) - else: - for method in ssAllMethods: - for ssType in ssMethods: +def load(isExtra: bool = False): + if not isExtra: # just test basic connection + for method in ssAllMethods: # test every method for once + for ssType in ssMethods: # found the client which support this method if method not in ssMethods[ssType]: continue - result.append(testConnection(ssType, ssType, method)) - break - return result + yield loadTest(ssType, ssType, method) # ssType <-- method --> ssType + break # don't need other client + return + for ssServer in ssMethods: # traverse all shadowsocks type as server + for method, ssClient in itertools.product(ssMethods[ssServer], ssMethods): # supported methods and clients + if method not in ssMethods[ssClient]: continue + yield loadTest(ssServer, ssClient, method) # ssServer <-- method --> ssClient diff --git a/test.py b/test.py index a014f99..6da4fbc 100755 --- a/test.py +++ b/test.py @@ -3,22 +3,22 @@ import time import requests +from threading import Thread from Tester import Shadowsocks from Basis.Logger import logging -from Basis.Functions import networkStatus + +testDelay = 1 # wait 1s before request test +threadNum = 128 # thread number def test(testObj: dict) -> None: logging.warning(testObj['title']) - logging.debug('network status -> %s' % networkStatus()) testObj['client'].start() testObj['server'].start() - - time.sleep(1) - + time.sleep(testDelay) errFlag = False try: request = requests.get( - 'http://baidu.com', + 'http://iserv.scutbot.cn', proxies = { 'http': 'socks5://127.0.0.1:%i' % testObj['socks']['port'], 'https': 'socks5://127.0.0.1:%i' % testObj['socks']['port'], @@ -36,19 +36,34 @@ def test(testObj: dict) -> None: testObj['server'].quit() if errFlag: logging.warning('client info') - logging.error('command -> ' + str(testObj['client'].cmd)) - logging.error('envVar -> ' + str(testObj['client'].env)) - logging.error('file -> ' + str(testObj['client'].file)) + 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 -> ' + str(testObj['server'].cmd)) - logging.error('envVar -> ' + str(testObj['server'].env)) - logging.error('file -> ' + str(testObj['server'].file)) + 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)) -testList = Shadowsocks.load(isExtra = True) -for testObject in testList: - test(testObject) +threads = [] +ss = Shadowsocks.load(isExtra = True) + +while True: + try: + for i in range(threadNum): + thread = Thread(target = test, args = (next(ss),)) + thread.start() + threads.append(thread) + for thread in threads: + thread.join() + threads.clear() + except StopIteration: + break +for thread in threads: + thread.join() + +logging.critical('test complete')