From 224006aa90aa69dccb99f2f23f980256bde6e964 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 30 Jul 2022 18:02:15 +0800 Subject: [PATCH] feat: VMess test function --- Basis/Functions.py | 7 ++ Basis/Methods.py | 4 + Builder/V2ray.py | 6 +- Tester/Shadowsocks.py | 4 +- Tester/ShadowsocksR.py | 6 +- Tester/V2ray.py | 233 +++++++++++++++++++++++++++++++++++ Tester/VMess.py | 119 ++++++++++++++++++ docs/ProxyObject/Brook.md | 4 +- docs/ProxyObject/Trojan.md | 4 +- docs/ProxyObject/TrojanGo.md | 4 +- docs/ProxyObject/VLESS.md | 4 +- docs/ProxyObject/VMess.md | 4 +- test.py | 12 +- 13 files changed, 389 insertions(+), 22 deletions(-) create mode 100644 Tester/V2ray.py create mode 100644 Tester/VMess.py diff --git a/Basis/Functions.py b/Basis/Functions.py index f2b3e36..6f748e9 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import time +import uuid import psutil import random import hashlib @@ -32,6 +33,12 @@ def genFlag(length: int = 12) -> str: # generate random task flag return flag +def genUUID() -> str: # generate uuid v5 + return str(uuid.uuid5( + uuid.NAMESPACE_DNS, genFlag(length = 16) + )) + + 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') diff --git a/Basis/Methods.py b/Basis/Methods.py index b8df602..456eaaf 100644 --- a/Basis/Methods.py +++ b/Basis/Methods.py @@ -116,3 +116,7 @@ ssrObfuscations = [ # obfuscations of ShadowsocksR (obfs) # VMess Info vmessMethods = ['aes-128-gcm', 'chacha20-poly1305', 'auto', 'none', 'zero'] + +# v2ray / Xray Info +quicMethods = ['none', 'aes-128-gcm', 'chacha20-poly1305'] +udpObfuscations = ['none', 'srtp', 'utp', 'wechat-video', 'dtls', 'wireguard'] diff --git a/Builder/V2ray.py b/Builder/V2ray.py index a68e95f..47bc8fb 100644 --- a/Builder/V2ray.py +++ b/Builder/V2ray.py @@ -121,7 +121,7 @@ def grpcStream(streamInfo: dict) -> dict: # gRPC stream config grpcObject = { 'serviceName': streamInfo['service'] # gRPC service name } - if streamInfo['mode'] == 'multi': # gRPC multi-mode not work in v2fly-core + if streamInfo['mode'] == 'multi': grpcObject['multiMode'] = True return { 'network': 'grpc', @@ -147,7 +147,7 @@ def loadStream(streamInfo: dict) -> dict: } -def loadConfig(socksInfo: dict, outboundObject: dict) -> dict: # load config by socks and outbound info +def loadConfig(socksInfo: dict, outbound: dict) -> dict: # load config by socks and outbound info return { 'log': { 'loglevel': 'debug' @@ -161,5 +161,5 @@ def loadConfig(socksInfo: dict, outboundObject: dict) -> dict: # load config by 'auth': 'noauth' } }], - 'outbounds': [outboundObject] # outbound without route object + 'outbounds': [outbound] # outbound without route object } diff --git a/Tester/Shadowsocks.py b/Tester/Shadowsocks.py index ef01373..950e517 100644 --- a/Tester/Shadowsocks.py +++ b/Tester/Shadowsocks.py @@ -123,7 +123,7 @@ def loadTest(serverType: str, clientType: str, method: str, plugin: dict or None } socksInfo = { # socks5 interface for test 'addr': settings['clientBind'], - 'port': getAvailablePort() + 'port': getAvailablePort(), } pluginClient = {'plugin': None if plugin is None else plugin['client']} pluginServer = {'plugin': None if plugin is None else plugin['server']} @@ -138,7 +138,7 @@ def loadTest(serverType: str, clientType: str, method: str, plugin: dict or None 'socks': socksInfo, # exposed socks5 address 'interface': { 'addr': proxyInfo['server'], - 'port': proxyInfo['port'] + 'port': proxyInfo['port'], } } if plugin is not None: diff --git a/Tester/ShadowsocksR.py b/Tester/ShadowsocksR.py index 24b3533..0210593 100644 --- a/Tester/ShadowsocksR.py +++ b/Tester/ShadowsocksR.py @@ -27,7 +27,7 @@ def loadServer(configFile: str, proxyInfo: dict) -> Process: # load server proc 'protocol': proxyInfo['protocol'], 'protocol_param': proxyInfo['protocolParam'], 'obfs': proxyInfo['obfs'], - 'obfs_param': proxyInfo['obfsParam'] + 'obfs_param': proxyInfo['obfsParam'], } serverFile = os.path.join(settings['workDir'], configFile) return Process(settings['workDir'], cmd = ['ssr-server', '-vv', '-c', serverFile], file = { @@ -60,7 +60,7 @@ def loadTest(method: str, protocol: str, obfs: str) -> dict: 'addr': settings['clientBind'], 'port': getAvailablePort() } - configName = '%s_%s_%s' % (method, protocol, obfs) # prefix of config file name + 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), 'client': loadClient(configName + '_client.json', proxyInfo, socksInfo), @@ -68,7 +68,7 @@ def loadTest(method: str, protocol: str, obfs: str) -> dict: 'socks': socksInfo, # exposed socks5 address 'interface': { 'addr': proxyInfo['server'], - 'port': proxyInfo['port'] + 'port': proxyInfo['port'], } } logging.debug('New shadowsocksr test -> %s' % testInfo) diff --git a/Tester/V2ray.py b/Tester/V2ray.py new file mode 100644 index 0000000..c0aa35a --- /dev/null +++ b/Tester/V2ray.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +import itertools + +from Basis.Functions import genFlag +from Basis.Methods import quicMethods +from Basis.Methods import udpObfuscations + +settings = { + 'site': 'www.bing.com', + 'host': '343.re', + 'cert': '/etc/ssl/certs/343.re/fullchain.pem', + 'key': '/etc/ssl/certs/343.re/privkey.pem', +} + + +httpConfig = { + 'version': '1.1', + 'status': '200', + 'reason': 'OK', + 'headers': { + 'Content-Type': [ + 'application/octet-stream', + 'video/mpeg' + ], + 'Transfer-Encoding': ['chunked'], + 'Connection': ['keep-alive'], + 'Pragma': 'no-cache', + } +} + +kcpConfig = { + 'mtu': 1350, + 'tti': 20, + 'uplinkCapacity': 5, + 'downlinkCapacity': 20, + 'congestion': False, + 'readBufferSize': 1, + 'writeBufferSize': 1, +} + + +def addSecure(streamConfig: dict) -> dict: + streamConfig['caption'] += ' (with tls)' + streamConfig['info']['secure'] = { # secure options for client + 'sni': settings['host'], + 'alpn': None, + 'verify': True, + } + streamConfig['server']['security'] = 'tls' + streamConfig['server']['tlsSettings'] = { # cert and key for server + 'alpn': ['h2', 'http/1.1'], + 'certificates': [{ + 'certificateFile': settings['cert'], + 'keyFile': settings['key'], + }] + } + return streamConfig + + +def tcpStream(isObfs: bool) -> dict: + return { + 'caption': 'TCP stream' + (' (with obfs)' if isObfs else ''), + 'info': { + 'type': 'tcp', + 'obfs': None if not isObfs else { + 'host': settings['site'], # obfs website + 'path': '/', + }, + 'secure': None, + }, + 'server': { + 'network': 'tcp', + 'tcpSettings': {} if not isObfs else { + 'header': { + 'type': 'http', + 'response': httpConfig, + } + } + } + } + + +def kcpStream(obfs: str) -> dict: + kcpObject = copy.deepcopy(kcpConfig) + kcpObject['seed'] = genFlag(length = 8) # random seed + kcpObject['header'] = { + 'type': obfs + } + return { + 'caption': 'mKCP stream obfs ' + obfs, + 'info': { + 'type': 'kcp', + 'seed': kcpObject['seed'], + 'obfs': obfs, + 'secure': None + }, + 'server': { + 'network': 'kcp', + 'kcpSettings': kcpObject + } + } + + +def wsStream(isEd: bool) -> dict: + path = '/' + genFlag(length = 6) # random websocket path + return { + 'caption': 'WebSocket stream' + (' (Max-Early-Data 2048)' if isEd else ''), + 'info': { + 'type': 'ws', + 'host': settings['host'], + 'path': path, + 'ed': 2048 if isEd else None, + 'secure': None, + }, + 'server': { + 'network': 'ws', + 'wsSettings': {**{ + 'path': path, + 'headers': { + 'Host': settings['host'] + } + }, **({} if not isEd else { + 'maxEarlyData': 2048, + 'earlyDataHeaderName': 'Sec-WebSocket-Protocol' + })} + } + } + + +def h2Stream() -> dict: + path = '/' + genFlag(length = 6) + return { + 'caption': 'HTTP/2 stream', + 'info': { + 'type': 'h2', + 'host': settings['host'], + 'path': path, + 'secure': None, # HTTP/2 force enable tls + }, + 'server': { + 'network': 'http', + 'httpSettings': { + 'host': [settings['host']], + 'path': path + } + } + } + + +def quicStream(method: str, obfs: str) -> dict: + passwd = genFlag(length = 8) + return { + 'caption': 'QUIC stream security = %s | header = %s' % (method, obfs), + 'info': { + 'type': 'quic', + 'method': method, + 'passwd': passwd, + 'obfs': obfs, + 'secure': None, # QUIC force enable tls + }, + 'server': { + 'network': 'quic', + 'quicSettings': { + 'security': method, + 'key': passwd, + 'header': { + 'type': obfs + } + } + } + } + + +def grpcStream(isMulti: bool) -> dict: + service = genFlag(length = 8) + return { + 'caption': 'gRPC stream' + (' (multi mode)' if isMulti else ''), + 'info': { + 'type': 'grpc', + 'service': service, + 'mode': 'multi' if isMulti else 'gun', # gun mode or multi mode + 'secure': None, + }, + 'server': { + 'network': 'grpc', + 'grpcSettings': {**{ + 'serviceName': service + }, **({} if not isMulti else { + 'multiMode': True + })} + } + } + + +def loadStream() -> list: + streams = [] + addStream = lambda x: streams.append(copy.deepcopy(x)) + for isObfs in [False, True]: + addStream(tcpStream(isObfs)) # TCP stream + addStream(addSecure(tcpStream(isObfs))) # TCP stream with TLS + for udpObfs in udpObfuscations: + addStream(kcpStream(udpObfs)) # mKCP stream + addStream(addSecure(kcpStream(udpObfs))) # mKCP stream with TLS + for isEd in [False, True]: + addStream(wsStream(isEd)) # WebSocket stream + addStream(addSecure(wsStream(isEd))) # WebSocket stream with TLS + addStream(addSecure(h2Stream())) # HTTP/2 stream with TLS + for quicMethod, quicObfs in itertools.product(quicMethods, udpObfuscations): + addStream(addSecure(quicStream(quicMethod, quicObfs))) # QUIC stream with TLS + for isMulti in [False, True]: + addStream(grpcStream(isMulti)) # gRPC stream + addStream(addSecure(grpcStream(isMulti))) # gRPC stream with TLS + + # for stream in streams: + # os.system('echo \'%s\' | jq .' % json.dumps(stream)) + + return streams + + +def loadConfig(inbound: dict) -> dict: + return { + 'log': { + 'loglevel': 'debug' + }, + 'inbounds': [inbound], + 'outbounds': [{ + 'protocol': 'freedom', + 'settings': {}, + }] + } diff --git a/Tester/VMess.py b/Tester/VMess.py new file mode 100644 index 0000000..b3ce325 --- /dev/null +++ b/Tester/VMess.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import json +import itertools +from Tester import V2ray +from Builder import VMess +from Basis.Logger import logging +from Basis.Process import Process +from Basis.Functions import md5Sum +from Basis.Functions import genUUID +from Basis.Functions import getAvailablePort + +from Builder import pathEnv +from Basis.Methods import vmessMethods + +settings = { + 'serverBind': '127.0.0.1', + 'clientBind': '127.0.0.1', + # 'serverBind': '::1', + # 'clientBind': '::1', + 'workDir': '/tmp/ProxyC', + 'host': '343.re', + 'cert': '/etc/ssl/certs/343.re/fullchain.pem', + 'key': '/etc/ssl/certs/343.re/privkey.pem', +} + + +def loadServer(configFile: str, proxyInfo: dict, streamConfig: dict) -> Process: # load server process + vmessConfig = V2ray.loadConfig({ + 'protocol': 'vmess', + 'listen': proxyInfo['server'], + 'port': proxyInfo['port'], + 'settings': { + 'clients': [{ # server will auto adapt the method + 'id': proxyInfo['id'], + 'alterId': proxyInfo['aid'], + }] + }, + 'streamSettings': streamConfig + }) + serverFile = os.path.join(settings['workDir'], configFile) + return Process(settings['workDir'], cmd = ['v2ray', '-c', serverFile], file = { + 'path': serverFile, + 'content': json.dumps(vmessConfig) + }, env= { + 'PATH': pathEnv, + 'v2ray.vmess.aead.forced': 'false' # enable non-aead test (aid not 0) + }, isStart = False) + + +def loadClient(configFile: str, proxyInfo: dict, socksInfo: dict) -> Process: # load client process + clientFile = os.path.join(settings['workDir'], configFile) + vmessCommand, vmessConfig, _ = VMess.load(proxyInfo, socksInfo, clientFile) + return Process(settings['workDir'], cmd = vmessCommand, file = { + 'path': clientFile, + 'content': vmessConfig + }, isStart = False) + + +def loadTest(method: str, aid: int, stream: dict) -> dict: + proxyInfo = { # connection info + 'server': settings['serverBind'], + 'port': getAvailablePort(), + 'method': 'auto', + 'id': genUUID(), # random uuid v5 + 'aid': aid, + 'stream': stream['info'] + } + socksInfo = { # socks5 interface for test + 'addr': settings['clientBind'], + 'port': getAvailablePort() + } + configName = 'vmess_%s_%i_%s' % (method, aid, md5Sum(stream['caption'])[:8]) + testInfo = { # release test info + 'title': 'VMess test: security = %s | alterId = %i [%s]' % (method, aid, stream['caption']), + 'client': loadClient(configName + '_client.json', proxyInfo, socksInfo), + 'server': loadServer(configName + '_server.json', proxyInfo, stream['server']), + 'socks': socksInfo, # exposed socks5 address + 'interface': { + 'addr': proxyInfo['server'], + 'port': proxyInfo['port'], + } + } + logging.debug('New vmess test -> %s' % testInfo) + return testInfo + + +def load(): + stream = { + 'caption': 'TCP stream (with tls)', + 'info': { + 'type': 'tcp', + 'obfs': None, + 'secure': { + 'sni': settings['host'], + 'alpn': None, + 'verify': True, + }, + }, + 'server': { + 'network': 'tcp', + 'tcpSettings': {}, + 'security': 'tls', + 'tlsSettings': { + 'alpn': ['h2', 'http/1.1'], + 'certificates': [{ + 'certificateFile': settings['cert'], + 'keyFile': settings['key'], + }] + } + } + } + + # for method, aid in itertools.product(vmessMethods, [0, 64]): + # yield loadTest(method, aid, stream) + for stream in V2ray.loadStream(): + yield loadTest('auto', 0, stream) diff --git a/docs/ProxyObject/Brook.md b/docs/ProxyObject/Brook.md index e2e9a56..39a4c42 100644 --- a/docs/ProxyObject/Brook.md +++ b/docs/ProxyObject/Brook.md @@ -50,14 +50,14 @@ ### host + 类型:*str* -+ 说明:Websocket连接域名 ++ 说明:WebSocket连接域名 + 缺省:`空` + 限制:无 ### path + 类型:*str* -+ 说明:Websocket连接路径 ++ 说明:WebSocket连接路径 + 缺省:`/` + 限制:无 diff --git a/docs/ProxyObject/Trojan.md b/docs/ProxyObject/Trojan.md index 7359f14..1cae395 100644 --- a/docs/ProxyObject/Trojan.md +++ b/docs/ProxyObject/Trojan.md @@ -108,14 +108,14 @@ ### host + 类型:*str* -+ 说明:Websocket连接域名 ++ 说明:WebSocket连接域名 + 缺省:`空` + 限制:无 ### path + 类型:*str* -+ 说明:Websocket连接路径 ++ 说明:WebSocket连接路径 + 缺省:`/` + 限制:无 diff --git a/docs/ProxyObject/TrojanGo.md b/docs/ProxyObject/TrojanGo.md index 0932994..1aec2b0 100644 --- a/docs/ProxyObject/TrojanGo.md +++ b/docs/ProxyObject/TrojanGo.md @@ -89,14 +89,14 @@ ### host + 类型:*str* -+ 说明:Websocket连接域名 ++ 说明:WebSocket连接域名 + 缺省:`空` + 限制:无 ### path + 类型:*str* -+ 说明:Websocket连接路径 ++ 说明:WebSocket连接路径 + 缺省:`/` + 限制:无 diff --git a/docs/ProxyObject/VLESS.md b/docs/ProxyObject/VLESS.md index 8741dca..2607192 100644 --- a/docs/ProxyObject/VLESS.md +++ b/docs/ProxyObject/VLESS.md @@ -116,14 +116,14 @@ ### host + 类型:*str* -+ 说明:Websocket连接域名 ++ 说明:WebSocket连接域名 + 缺省:`空` + 限制:无 ### path + 类型:*str* -+ 说明:Websocket连接路径 ++ 说明:WebSocket连接路径 + 缺省:`/` + 限制:无 diff --git a/docs/ProxyObject/VMess.md b/docs/ProxyObject/VMess.md index 74151a8..5f32bf2 100644 --- a/docs/ProxyObject/VMess.md +++ b/docs/ProxyObject/VMess.md @@ -124,14 +124,14 @@ ### host + 类型:*str* -+ 说明:Websocket连接域名 ++ 说明:WebSocket连接域名 + 缺省:`空` + 限制:无 ### path + 类型:*str* -+ 说明:Websocket连接路径 ++ 说明:WebSocket连接路径 + 缺省:`/` + 限制:无 diff --git a/test.py b/test.py index 7f35217..d8b6b5f 100755 --- a/test.py +++ b/test.py @@ -4,8 +4,11 @@ import time import requests from threading import Thread + +from Tester import VMess from Tester import Shadowsocks from Tester import ShadowsocksR + from Basis.Logger import logging from Basis.Functions import ipFormat from Basis.Functions import checkPortStatus @@ -91,9 +94,10 @@ def runTest(testIter: iter, threadNum: int): ss = Shadowsocks.load(isExtra = True) # ss = Shadowsocks.load(isExtra = False) ssr = ShadowsocksR.load() -logging.critical('test start') - -runTest(ss, 64) -runTest(ssr, 64) +vmess = VMess.load() +logging.critical('test start') +# runTest(ss, 64) +# runTest(ssr, 64) +runTest(vmess, 1) logging.critical('test complete')