Browse Source

feat: plugin test process

master
dnomd343 2 years ago
parent
commit
24e42c294e
  1. 5
      Basis/Functions.py
  2. 18
      Basis/Methods.py
  3. 5
      Builder/Shadowsocks.py
  4. 16
      Dockerfile
  5. 311
      Tester/Plugin.py
  6. 45
      Tester/Shadowsocks.py
  7. 8
      test.py

5
Basis/Functions.py

@ -4,9 +4,14 @@
import time
import psutil
import random
import hashlib
from Basis.Logger import logging
def md5Sum(data: str, encode: str = 'UTF-8') -> str:
return hashlib.md5(data.encode(encoding = encode)).hexdigest()
def genFlag(length: int = 12) -> str: # generate random task flag
flag = ''
for i in range(0, length):

18
Basis/Methods.py

@ -91,3 +91,21 @@ ssrObfuscations = [ # obfuscations of ShadowsocksR (obfs)
'plain', 'http_post', 'http_simple', 'random_head',
'tls_simple', 'tls1.2_ticket_auth', 'tls1.2_ticket_fastauth',
]
plugin = {
'simple-obfs': ['obfs-local', 'obfs-server'],
'simple-tls': ['simple-tls'],
'v2ray': ['v2ray-plugin'],
'xray': ['xray-plugin'],
'kcptun': ['kcptun-client', 'kcptun-server'],
'gost': ['gost-plugin'],
'cloak': ['ck-client', 'ck-server'],
'go-quiet': ['gq-client', 'gq-server'],
'mos-tls-tunnel': ['mtt-client', 'mtt-server'],
'rabbit': ['rabbit-plugin', 'rabbit'],
'qtun': ['qtun-client', 'qtun-server'],
'gun': ['gun-plugin'],
}
plugin = {x: [plugin[x][0], plugin[x][1 if len(plugin[x]) == 2 else 0]] for x in plugin}
plugin = {x: {'client': plugin[x][0], 'server': plugin[x][1]} for x in plugin} # format plugin info

5
Builder/Shadowsocks.py

@ -83,11 +83,10 @@ def load(proxyInfo: dict, socksInfo: dict, configFile: str) -> tuple[list, str,
for client in ssMethods: # traverse all shadowsocks client
if proxyInfo['method'] not in ssMethods[client]:
continue
ssLoadConfig = {
ssConfig, ssClient = {
'ss-rust': ssRust,
'ss-libev': ssLibev,
'ss-python': ssPython,
'ss-python-legacy': ssPythonLegacy
}[client]
ssConfig, ssClient = ssLoadConfig(proxyInfo, socksInfo, isUdp) # generate config file
}[client](proxyInfo, socksInfo, isUdp) # generate config file
return ssClient + ['-c', configFile], json.dumps(ssConfig), {} # tuple[command, fileContent, envVar]

16
Dockerfile

@ -132,8 +132,8 @@ RUN BZIP2=-9 tar czf /packages.tar.gz ./site-packages/
FROM rust:1.62-alpine3.16 AS plugin-1
RUN \
apk add git && mkdir /plugins/ && \
git clone https://github.com/shadowsocks/simple-obfs.git && \
git clone https://github.com/shadowsocks/qtun.git
git clone https://github.com/shadowsocks/qtun.git && \
git clone https://github.com/shadowsocks/simple-obfs.git
# Compile simple-obfs
RUN \
apk add autoconf automake build-base libev-dev libtool linux-headers && \
@ -174,14 +174,12 @@ RUN \
mv ./kcptun-client ./kcptun-server /plugins/
# Compile gost-plugin
RUN \
cd ./gost-plugin/ && \
git checkout ${GOST_PLUGIN} -b build && \
cd ./gost-plugin/ && git checkout ${GOST_PLUGIN} && \
env CGO_ENABLED=0 go build -trimpath -ldflags "-X main.VERSION=$(git describe --tags) -s -w" && \
mv ./gost-plugin /plugins/
# Compile GoQuiet
RUN \
cd ./GoQuiet/ && \
go mod init github.com/cbeuw/GoQuiet && \
cd ./GoQuiet/ && go mod init github.com/cbeuw/GoQuiet && \
env CGO_ENABLED=0 go build -trimpath -ldflags "-X main.version=$(git describe --tags) -s -w" ./cmd/gq-client && \
env CGO_ENABLED=0 go build -trimpath -ldflags "-X main.version=$(git describe --tags) -s -w" ./cmd/gq-server && \
mv ./gq-client ./gq-server /plugins/
@ -213,6 +211,7 @@ RUN upx -9 /plugins/*
# Compile sip003 plugins (part3 -> go1.17)
FROM golang:1.17-alpine3.16 AS plugin-3
ENV SIMPLE_TLS="v0.7.0"
ENV CLOAK="v2.6.0"
RUN \
apk add git && mkdir /plugins/ && \
git clone https://github.com/cbeuw/Cloak.git && \
@ -220,8 +219,7 @@ RUN \
git clone https://github.com/IrineSistiana/simple-tls.git
# Compile simple-tls
RUN \
cd ./simple-tls/ && \
git checkout ${SIMPLE_TLS} -b build && \
cd ./simple-tls/ && git checkout ${SIMPLE_TLS} && \
sed -i 's/version = "unknown\/dev"/version = "'$(git describe --tags)'"/g' main.go && \
env CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" && \
mv ./simple-tls /plugins/
@ -232,7 +230,7 @@ RUN \
mv ./xray-plugin /plugins/
# Compile Cloak
RUN \
cd ./Cloak/ && \
cd ./Cloak/ && git checkout ${CLOAK} && \
env CGO_ENABLED=0 go build -trimpath -ldflags "-X main.version=$(git describe --tags) -s -w" ./cmd/ck-client && \
env CGO_ENABLED=0 go build -trimpath -ldflags "-X main.version=$(git describe --tags) -s -w" ./cmd/ck-server && \
mv ./ck-client ./ck-server /plugins/

311
Tester/Plugin.py

@ -0,0 +1,311 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import re
import json
from Basis.Logger import logging
from Basis.Methods import plugin
from Basis.Process import Process
from Basis.Functions import genFlag, getAvailablePort
settings = {
'serverBind': '127.0.0.1',
'workDir': '/tmp/ProxyC'
}
pluginParams = {
'SITE': 'www.bing.com',
'PATH': '/test',
'HOST': '343.re',
'CERT': '/etc/ssl/certs/343.re/fullchain.pem',
'KEY': '/etc/ssl/certs/343.re/privkey.pem',
'PASSWD': 'dnomd343',
}
pluginConfig = {
'simple-obfs': {
'http mode': [
'obfs=http',
'obfs=http;obfs-host=${SITE}',
],
'tls mode': [
'obfs=tls',
'obfs=tls;obfs-host=${SITE}',
],
'http mode (with uri)': [
'obfs=http',
'obfs=http;obfs-host=${SITE};obfs-uri=${PATH}',
],
'http mode (POST method)': [
'obfs=http',
'obfs=http;http-method=POST;obfs-host=${SITE}',
],
},
'simple-tls': {
'http mode': [
's;n=${HOST};cert=${CERT};key=${KEY}',
'n=${HOST}',
],
'websocket mode': [
's;n=${HOST};cert=${CERT};key=${KEY};ws;ws-path=${PATH}',
'n=${HOST};ws;ws-path=${PATH}',
],
'http mode (with mux)': [
's;cert=${CERT};key=${KEY};n=${HOST}',
'n=${HOST};mux=8',
],
'http mode (with auth key)': [
's;n=${HOST};cert=${CERT};key=${KEY};auth=${PASSWD}',
'n=${HOST};auth=${PASSWD}',
],
},
'v2ray': {
'websocket mode': [
'server',
'',
],
'websocket mode (with tls)': [
'server;tls;host=${HOST};cert=${CERT};key=${KEY}',
'tls;host=${HOST}',
],
'websocket mode (with path)': [
'server;path=${PATH}',
'path=${PATH}',
],
'quic mode': [
'server;mode=quic;host=${HOST};cert=${CERT};key=${KEY}',
'mode=quic;host=${HOST}',
],
},
'xray': {
'websocket mode': [
'server',
'',
],
'websocket mode (with tls)': [
'server;tls;host=${HOST};cert=${CERT};key=${KEY}',
'tls;host=${HOST}',
],
'websocket mode (with path)': [
'server;path=${PATH}',
'path=${PATH}',
],
'quic mode': [
'server;mode=quic;host=${HOST};cert=${CERT};key=${KEY}',
'mode=quic;host=${HOST}',
],
'grpc mode': [
'server;mode=grpc',
'mode=grpc',
],
'grpc mode (with tls)': [
'server;tls;mode=grpc;host=${HOST};cert=${CERT};key=${KEY}',
'tls;mode=grpc;host=${HOST}',
],
},
'kcptun': {
'basic mode': [
'', '' # aka fast mode
],
'with nocomp': [
'nocomp', 'nocomp'
],
'with key': [
'key=${PASSWD}', 'key=${PASSWD}'
],
'with multi conn': [
'conn=8', 'conn=8'
],
},
'gost': {
'ws mode': [
'server;mode=ws',
'mode=ws',
],
'mws mode': [
'server;mode=mws',
'mode=mws;mux=1',
],
'tls mode': [
'server;cert=${CERT};key=${KEY};mode=tls',
'serverName=${HOST};mode=tls',
],
'mtls mode': [
'server;cert=${CERT};key=${KEY};mode=mtls',
'serverName=${HOST};mode=mtls;mux=1',
],
'xtls mode': [
'server;cert=${CERT};key=${KEY};mode=xtls',
'serverName=${HOST};mode=xtls',
],
'h2 mode': [
'server;cert=${CERT};key=${KEY};mode=h2',
'serverName=${HOST};mode=h2',
],
'wss mode': [
'server;cert=${CERT};key=${KEY};mode=wss',
'serverName=${HOST};mode=wss',
],
'mwss mode': [
'server;cert=${CERT};key=${KEY};mode=mwss',
'serverName=${HOST};mode=mwss;mux=1',
],
'quic mode': [
'server;cert=${CERT};key=${KEY};mode=quic',
'serverName=${HOST};mode=quic',
],
'grpc mode': [
'server;cert=${CERT};key=${KEY};mode=grpc',
'serverName=${HOST};mode=grpc',
],
},
'cloak': {},
'go-quiet': {
'chrome fingerprint': [
os.path.join(settings['workDir'], 'go-quiet_config_${RANDOM}.json'),
'ServerName=${SITE};key=${PASSWD};TicketTimeHint=300;Browser=chrome',
],
'firefox fingerprint': [
os.path.join(settings['workDir'], 'go-quiet_config_${RANDOM}.json'),
'ServerName=${SITE};key=${PASSWD};TicketTimeHint=300;Browser=firefox',
],
},
'mos-tls-tunnel': {
'basic mode': [
'cert=${CERT};key=${KEY}',
'n=${HOST}',
],
'basic mode (with mux)': [
'cert=${CERT};key=${KEY};mux',
'n=${HOST};mux',
],
'wss mode': [
'wss;cert=${CERT};key=${KEY}',
'wss;n=${HOST}',
],
'wss mode (with path)': [
'wss;cert=${CERT};key=${KEY};wss-path=${PATH}',
'wss;n=${HOST};wss-path=${PATH}',
],
'wss mode (with mux)': [
'wss;cert=${CERT};key=${KEY};mux',
'wss;n=${HOST};mux',
],
},
'rabbit': {
'basic mode': [
'${RABBIT_PORT}',
'serviceAddr=127.0.0.1:${RABBIT_PORT};password=${PASSWD};tunnelN=6' # emulate SIP003 (ipv4 localhost)
],
},
'qtun': {
'basic mode': [
'cert=${CERT};key=${KEY}',
'host=${HOST}',
],
},
'gun': {
'basic mode': [
'server:cleartext',
'client:cleartext',
],
'basic mode (with tls)': [
'server:${CERT}:${KEY}',
'client:${HOST}',
],
},
}
def kcptunLoad() -> None:
for kcptunMode in ['fast', 'fast2', 'fast3', 'normal', 'manual']: # traverse kcptun modes
pluginConfig['kcptun'][kcptunMode + ' mode'] = ['mode=' + kcptunMode, 'mode=' + kcptunMode]
for kcptunCrypt in ['aes', 'aes-128', 'aes-192', 'salsa20', 'blowfish',
'twofish', 'cast5', '3des', 'tea', 'xtea', 'xor', 'none']: # traverse kcptun crypt
pluginConfig['kcptun']['with %s crypt' % kcptunCrypt] = ['crypt=' + kcptunCrypt, 'crypt=' + kcptunCrypt]
def cloakLoad() -> None:
ckKey = os.popen('ck-server -key').read() # generate public and private key for cloak
pluginParams['CK_PUBLIC'] = re.search(r'\s+(\S+)$', ckKey.split('\n')[0])[1]
pluginParams['CK_PRIVATE'] = re.search(r'\s+(\S+)$', ckKey.split('\n')[1])[1]
pluginParams['CK_UID'] = re.search(r'\s+(\S+)\n', os.popen('ck-server -uid').read())[1] # generate uid for clock
logging.info('generate clock uid -> %s' % pluginParams['CK_UID'])
logging.info('generate clock key -> %s (Public) | %s (Private)' % (
pluginParams['CK_PUBLIC'], pluginParams['CK_PRIVATE']
))
ckPrefix = 'UID=${CK_UID};PublicKey=${CK_PUBLIC};ServerName=${SITE};' # cloak plugin's basic command
ckConfigPath = os.path.join(settings['workDir'], 'cloak_config_${RANDOM}.json') # clock server's config
for ckMethod in ['plain', 'aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305']: # traverse cloak encrypt methods
pluginConfig['cloak']['%s method' % ckMethod] = [
ckConfigPath, ckPrefix + 'EncryptionMethod=' + ckMethod
]
for ckBrowser in ['chrome', 'firefox']: # traverse cloak browser fingerprints
pluginConfig['cloak']['%s fingerprint' % ckBrowser] = [
ckConfigPath, ckPrefix + 'EncryptionMethod=plain;BrowserSig=' + ckBrowser
]
pluginConfig['cloak']['single connection'] = [ # disable connection multiplexing
ckConfigPath, ckPrefix + 'EncryptionMethod=plain;NumConn=0'
]
def ssInject(server: Process, pluginInfo: dict) -> Process:
if pluginInfo['type'] == 'cloak':
ckConfig = paramFill(json.dumps({
'BypassUID': ['${CK_UID}'],
'RedirAddr': '${SITE}',
'PrivateKey': '${CK_PRIVATE}'
}))
server.setFile(server.file + [{ # add cloak config file
'path': pluginInfo['server']['param'],
'content': ckConfig
}])
elif pluginInfo['type'] == 'go-quiet':
server.setFile(server.file + [{ # add gq-quiet config file
'path': pluginInfo['server']['param'],
'content': paramFill(json.dumps({'key': '${PASSWD}'}))
}])
elif pluginInfo['type'] == 'rabbit': # hijack rabbit plugin config
ssConfig = json.loads(server.file[0]['content']) # modify origin config
ssConfig.pop('plugin') # remove plugin option
ssConfig.pop('plugin_opts')
rabbitBind = ('[%s]' if ':' in ssConfig['server'] else '%s') % ssConfig['server'] # ipv4 / [ipv6]
rabbitPort = ssConfig['server_port']
ssConfig['server'] = '127.0.0.1' # SIP003 use ipv4 localhost for communication
ssConfig['server_port'] = int(pluginInfo['server']['param']) # aka ${RABBIT_PORT}
server.file[0]['content'] = json.dumps(ssConfig)
server.setCmd(['sh', '-c', paramFill(
'rabbit -mode s -password ${PASSWD} -rabbit-addr %s:%s' % (rabbitBind, rabbitPort) # start rabbit-tcp
) + ' &\nexec ' + ' '.join(server.cmd)]) # shadowsocks as main process (rabbit as sub process)
return server
def paramFill(param: str) -> str:
if '${RANDOM}' in param: # refresh RANDOM field
pluginParams['RANDOM'] = genFlag(length = 8)
for field in pluginParams:
param = param.replace('${%s}' % field, pluginParams[field]) # fill ${XXX} field
return param
def load():
cloakLoad() # init cloak config
kcptunLoad() # init kcptun config
for pluginType in pluginConfig:
for pluginTest, pluginTestInfo in pluginConfig[pluginType].items(): # traverse all plugin test item
if pluginType == 'rabbit':
pluginParams['RABBIT_PORT'] = str(getAvailablePort()) # allocate port before rabbit plugin start
yield {
'type': pluginType,
'caption': pluginTest,
'server': { # plugin info for server
'type': plugin[pluginType]['server'],
'param': paramFill(pluginTestInfo[0]),
},
'client': { # plugin info for client
'type': plugin[pluginType]['client'],
'param': paramFill(pluginTestInfo[1]),
},
'inject': ssInject # for some special plugins (only server part)
}

45
Tester/Shadowsocks.py

@ -5,9 +5,11 @@ import os
import json
import base64
import itertools
from Tester import Plugin
from Builder import Shadowsocks
from Basis.Logger import logging
from Basis.Process import Process
from Basis.Functions import md5Sum
from Basis.Methods import ssMethods, ssAllMethods
from Basis.Functions import genFlag, getAvailablePort
@ -31,15 +33,6 @@ def loadConfig(proxyInfo: dict) -> dict: # load basic config option
return config
def pluginUdp(plugin: str, pluginParam: str) -> bool: # whether the plugin uses UDP
if plugin in ['obfs-local', 'simple-tls', 'ck-client', 'gq-client', 'mtt-client', 'rabbit-plugin']:
return False # UDP is not used
if plugin in ['v2ray-plugin', 'xray-plugin', 'gost-plugin']:
if 'mode=quic' not in pluginParam.split(';'): # non-quic mode does not use UDP
return False
return True # UDP is assumed by default
def ssRust(proxyInfo: dict, isUdp: bool) -> tuple[dict, list]:
config = loadConfig(proxyInfo)
if isUdp: # proxy UDP traffic
@ -97,7 +90,7 @@ def loadClient(ssType: str, configFile: str, proxyInfo: dict, socksInfo: dict) -
'ss-libev': Shadowsocks.ssLibev,
'ss-python': Shadowsocks.ssPython,
'ss-python-legacy': Shadowsocks.ssPythonLegacy
}[ssType](proxyInfo, socksInfo, isUdp = False)
}[ssType](proxyInfo, socksInfo, isUdp = False) # disable udp in test mode
clientFile = os.path.join(settings['workDir'], configFile)
return Process(settings['workDir'], cmd = ssClient + ['-c', clientFile], file = { # load client process
'path': clientFile,
@ -111,7 +104,7 @@ def loadServer(ssType: str, configFile: str, proxyInfo: dict) -> Process:
'ss-libev': ssLibev,
'ss-python': ssPython,
'ss-python-legacy': ssPythonLegacy
}[ssType](proxyInfo, isUdp = False)
}[ssType](proxyInfo, isUdp = False) # disable udp in test mode
serverFile = os.path.join(settings['workDir'], configFile)
return Process(settings['workDir'], cmd = ssServer + ['-c', serverFile], file = { # load server process
'path': serverFile,
@ -119,42 +112,62 @@ def loadServer(ssType: str, configFile: str, proxyInfo: dict) -> Process:
}, isStart = False)
def loadTest(serverType: str, clientType: str, method: str) -> dict:
def loadTest(serverType: str, clientType: str, method: str, plugin: dict or None = None) -> 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()
}
pluginClient = {'plugin': None if plugin is None else plugin['client']}
pluginServer = {'plugin': None if plugin is None else plugin['server']}
configName = '%s_%s_%s' % (serverType, clientType, method) # prefix of config file name
if plugin is not None:
configName += '_%s_%s' % (plugin['type'], md5Sum(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}' % (serverType, method, clientType),
'client': loadClient(clientType, configName + '_client.json', proxyInfo, socksInfo),
'server': loadServer(serverType, configName + '_server.json', proxyInfo),
'title': '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
'interface': {
'addr': proxyInfo['server'],
'port': proxyInfo['port']
}
}
if plugin is not None:
testInfo['server'] = plugin['inject'](testInfo['server'], plugin)
logging.debug('New shadowsocks test -> %s' % testInfo)
return testInfo
def load(isExtra: bool = False):
pluginTest = []
pluginIter = Plugin.load()
while True:
try:
pluginTest.append(next(pluginIter)) # export data of plugin generator
except StopIteration:
break
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
yield loadTest(ssType, ssType, method) # ssType <-- method --> ssType
break # don't need other client
for ssType in ssMethods: # test plugin for every shadowsocks project
yield loadTest(ssType, ssType, ssMethods[ssType][0], pluginTest[0])
ssType = list(ssMethods.keys())[0] # choose the first one
for plugin in pluginTest[1:]: # test every plugin (except the first one that has been checked)
yield loadTest(ssType, ssType, ssMethods[ssType][0], plugin)
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
for ssType, plugin in itertools.product(ssMethods, pluginTest): # test every plugin with different ss project
yield loadTest(ssType, ssType, ssMethods[ssType][0], plugin)

8
test.py

@ -28,6 +28,8 @@ def test(testObj: dict) -> None:
logging.debug('server start complete')
logging.debug('start test process')
time.sleep(1)
errFlag = False
try:
request = requests.get(
@ -87,11 +89,5 @@ logging.critical('test start')
runTest(ss, 64)
runTest(ssr, 64)
# ssThread = Thread(target=runTest, args=(ss, 64))
# ssrThread = Thread(target=runTest, args=(ssr, 64))
# ssThread.start()
# ssrThread.start()
# ssThread.join()
# ssrThread.join()
logging.critical('test complete')

Loading…
Cancel
Save