mirror of https://github.com/dnomd343/ProxyC
dnomd343
2 years ago
40 changed files with 665 additions and 465 deletions
@ -1,11 +0,0 @@ |
|||||
#!/usr/bin/env python3 |
|
||||
# -*- coding: utf-8 -*- |
|
||||
|
|
||||
import compileall |
|
||||
from Basis.Logger import logging |
|
||||
|
|
||||
|
|
||||
def startCompile(dirRange: str = '/') -> None: |
|
||||
for optimize in [-1, 1, 2]: |
|
||||
compileall.compile_dir(dirRange, quiet = 1, maxlevels = 256, optimize = optimize) |
|
||||
logging.warning('Python optimize compile -> %s (level = %i)' % (dirRange, optimize)) |
|
@ -0,0 +1,26 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
class buildException(Exception): # for build error |
||||
|
def __init__(self, reason): |
||||
|
self.reason = reason |
||||
|
|
||||
|
|
||||
|
class filterException(Exception): # for filter error |
||||
|
def __init__(self, reason): |
||||
|
self.reason = reason |
||||
|
|
||||
|
|
||||
|
class processException(Exception): # for process error |
||||
|
def __init__(self, reason): |
||||
|
self.reason = reason |
||||
|
|
||||
|
|
||||
|
class managerException(Exception): # for manager error |
||||
|
def __init__(self, reason): |
||||
|
self.reason = reason |
||||
|
|
||||
|
|
||||
|
class checkException(Exception): # for check error |
||||
|
def __init__(self, reason): |
||||
|
self.reason = reason |
@ -0,0 +1,159 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
import os |
||||
|
import time |
||||
|
import requests |
||||
|
from threading import Thread |
||||
|
from Basis.Logger import logging |
||||
|
from Basis.Constant import WorkDir, TestHost, TestSite |
||||
|
from Basis.Functions import md5Sum, genFlag, hostFormat, checkPortStatus |
||||
|
|
||||
|
Settings = { |
||||
|
'workDir': WorkDir, |
||||
|
'site': TestSite, |
||||
|
'serverBind': '', |
||||
|
'clientBind': '', |
||||
|
'host': '', |
||||
|
'cert': '', |
||||
|
'key': '', |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def loadBind(serverV6: bool = False, clientV6: bool = False) -> None: |
||||
|
Settings['serverBind'] = '::1' if serverV6 else '127.0.0.1' |
||||
|
Settings['clientBind'] = '::1' if clientV6 else '127.0.0.1' |
||||
|
|
||||
|
|
||||
|
def waitPort(port: int, times: int = 50, 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 * 50 => 5s |
||||
|
return False # timeout |
||||
|
|
||||
|
|
||||
|
def genCert(host: str, certInfo: dict, remark: str = 'ProxyC') -> None: # generate self-signed certificate |
||||
|
certOrgInfo = ['--organization', remark, '--organizationUnit', remark] # organization info |
||||
|
logging.critical('Load self-signed certificate') |
||||
|
os.system('mkdir -p %s' % Settings['workDir']) # make sure that work directory exist |
||||
|
|
||||
|
# create CA data at first (by mad) |
||||
|
logging.critical('Creating CA certificate and key...') |
||||
|
os.system(' '.join( # generate CA certificate and privkey |
||||
|
['mad', 'ca', '--ca', certInfo['caCert'], '--key', certInfo['caKey'], '--commonName', remark] + certOrgInfo |
||||
|
)) |
||||
|
|
||||
|
# generate private key and sign certificate |
||||
|
logging.critical('Signing certificate...') |
||||
|
os.system(' '.join(['mad', 'cert', '--domain', host] + [ # generate certificate and privkey, then signed by CA |
||||
|
'--ca', certInfo['caCert'], '--ca_key', certInfo['caKey'], |
||||
|
'--cert', certInfo['cert'], '--key', certInfo['key'], |
||||
|
] + certOrgInfo)) |
||||
|
|
||||
|
# install CA certificate and record self-signed cert info |
||||
|
logging.critical('Installing CA certificate...') |
||||
|
os.system('cat %s >> /etc/ssl/certs/ca-certificates.crt' % certInfo['caCert']) # add into system's trust list |
||||
|
|
||||
|
|
||||
|
def loadCert(host: str = TestHost, certId: str = '') -> None: # load certificate |
||||
|
newCert = (certId == '') |
||||
|
certId = genFlag(length = 8) if certId == '' else certId |
||||
|
certInfo = { |
||||
|
'caCert': os.path.join(Settings['workDir'], 'proxyc_%s_ca.pem' % certId), |
||||
|
'caKey': os.path.join(Settings['workDir'], 'proxyc_%s_ca_key.pem' % certId), |
||||
|
'cert': os.path.join(Settings['workDir'], 'proxyc_%s_cert.pem' % certId), |
||||
|
'key': os.path.join(Settings['workDir'], 'proxyc_%s_cert_key.pem' % certId), |
||||
|
} |
||||
|
if newCert: |
||||
|
genCert(host, certInfo) # generate new certificate |
||||
|
Settings['host'] = host |
||||
|
Settings['cert'] = certInfo['cert'] |
||||
|
Settings['key'] = certInfo['key'] |
||||
|
logging.warning('Certificate load complete -> ID = %s' % certId) |
||||
|
|
||||
|
|
||||
|
def httpCheck(socksInfo: dict, url: str, testId: str, timeout: int = 10) -> None: |
||||
|
socksProxy = 'socks5://%s:%i' % (hostFormat(socksInfo['addr'], v6Bracket = True), socksInfo['port']) |
||||
|
try: |
||||
|
logging.debug('[%s] Http request via %s' % (testId, socksProxy)) |
||||
|
request = requests.get(url, timeout = timeout, proxies = { # http request via socks5 |
||||
|
'http': socksProxy, |
||||
|
'https': socksProxy, |
||||
|
}) |
||||
|
request.raise_for_status() # throw error when server return 4xx or 5xx (don't actually need) |
||||
|
logging.info('[%s] %s -> ok' % (testId, socksProxy)) |
||||
|
except Exception as exp: |
||||
|
logging.error('[%s] %s -> error\n%s' % (testId, socksProxy, exp)) # show detail of error reason |
||||
|
raise RuntimeError('Http request via socks5 failed') |
||||
|
|
||||
|
|
||||
|
def runTest(testInfo: dict, testUrl: str, testSelect: set or None, delay: int = 1) -> None: |
||||
|
testId = md5Sum(testInfo['caption'])[:12] # generate test ID |
||||
|
if testSelect is not None: # testSelect is None -> run all test |
||||
|
if testId not in testSelect: # skip unselected task |
||||
|
return |
||||
|
logging.warning('[%s] %s' % (testId, testInfo['caption'])) # show caption |
||||
|
logging.debug('[%s] Server ID -> %s | Client ID -> %s' % ( |
||||
|
testId, testInfo['server'].id, testInfo['client'].id |
||||
|
)) |
||||
|
testInfo['server'].id = testId + '-server' |
||||
|
testInfo['client'].id = testId + '-client' |
||||
|
|
||||
|
# build server and client and wait them start |
||||
|
testInfo['server'].start() # start test server |
||||
|
if waitPort(testInfo['interface']['port']): # wait for server |
||||
|
logging.debug('[%s] Test server start complete' % testId) |
||||
|
testInfo['client'].start() # start test client |
||||
|
if waitPort(testInfo['socks']['port']): # wait for client |
||||
|
logging.debug('[%s] Test client start complete' % testId) |
||||
|
|
||||
|
# start test process |
||||
|
try: |
||||
|
logging.debug('[%s] Test process start' % testId) |
||||
|
time.sleep(delay) # delay a short time before check |
||||
|
httpCheck(testInfo['socks'], testUrl, testId) # run http request test |
||||
|
testInfo['client'].quit() # clean up client |
||||
|
testInfo['server'].quit() # clean up server |
||||
|
except: |
||||
|
testInfo['client'].quit() |
||||
|
testInfo['server'].quit() |
||||
|
logging.warning('[%s] Client info' % testId) |
||||
|
logging.error('[%(id)s-server]\n▲ CMD => %(cmd)s\n▲ ENV => %(env)s\n▲ FILE => %(file)s\n%(output)s' % { |
||||
|
'id': testId, |
||||
|
'cmd': testInfo['client'].cmd, |
||||
|
'env': testInfo['client'].env, |
||||
|
'file': testInfo['client'].file, |
||||
|
'output': '-' * 96 + '\n' + testInfo['client'].output + '-' * 96, |
||||
|
}) |
||||
|
logging.warning('[%s] Server info' % testId) |
||||
|
logging.error('[%(id)s-client]\n▲ CMD => %(cmd)s\n▲ ENV => %(env)s\n▲ FILE => %(file)s\n%(output)s' % { |
||||
|
'id': testId, |
||||
|
'cmd': testInfo['server'].cmd, |
||||
|
'env': testInfo['server'].env, |
||||
|
'file': testInfo['server'].file, |
||||
|
'output': '-' * 96 + '\n' + testInfo['server'].output + '-' * 96, |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
def Test(testIter: iter, threadNum: int, testUrl: str, testFilter: set or None = None) -> None: |
||||
|
threads = [] |
||||
|
while True: # infinite loop |
||||
|
try: |
||||
|
for thread in threads: |
||||
|
if thread.is_alive(): # skip running thread |
||||
|
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() |
@ -1,14 +0,0 @@ |
|||||
#!/usr/bin/env python3 |
|
||||
# -*- coding: utf-8 -*- |
|
||||
|
|
||||
from Basis.Constant import WorkDir |
|
||||
|
|
||||
Settings = { |
|
||||
'workDir': WorkDir, |
|
||||
'site': 'www.bing.com', |
|
||||
'serverBind': '', |
|
||||
'clientBind': '', |
|
||||
'host': '', |
|
||||
'cert': '', |
|
||||
'key': '', |
|
||||
} |
|
@ -0,0 +1,8 @@ |
|||||
|
version: 'v0.1' |
||||
|
loglevel: 'INFO' |
||||
|
dir: '/tmp/ProxyC' |
||||
|
dns: null |
||||
|
api: |
||||
|
port: 7839 |
||||
|
path: '/' |
||||
|
token: '' |
@ -1,41 +1,140 @@ |
|||||
#!/usr/bin/env python3 |
#!/usr/bin/env python3 |
||||
# -*- coding: utf-8 -*- |
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
import sys |
||||
|
import copy |
||||
import time |
import time |
||||
import _thread |
import _thread |
||||
from Basis import DnsProxy |
import argparse |
||||
|
import compileall |
||||
|
from Basis import Constant |
||||
|
from Basis.Exception import checkException |
||||
|
|
||||
|
|
||||
|
def mainArgParse(rawArgs: list) -> argparse.Namespace: |
||||
|
mainParser = argparse.ArgumentParser(description = 'Start running API server') |
||||
|
mainParser.add_argument('--log', type = str, default = Constant.LogLevel, help = 'output log level') |
||||
|
mainParser.add_argument('--dns', type = str, default = Constant.DnsServer, nargs = '+', help = 'specify dns server') |
||||
|
mainParser.add_argument('--port', type = int, default = Constant.ApiPort, help = 'port for running') |
||||
|
mainParser.add_argument('--path', type = str, default = Constant.ApiPath, help = 'root path for api server') |
||||
|
mainParser.add_argument('--token', type = str, default = Constant.ApiToken, help = 'token for api server') |
||||
|
mainParser.add_argument('--thread', type = int, default = Constant.CheckThread, help = 'number of check thread') |
||||
|
mainParser.add_argument('-v', '--version', help = 'show version', action = 'store_true') |
||||
|
return mainParser.parse_args(rawArgs) |
||||
|
|
||||
|
|
||||
|
def testArgParse(rawArgs: list) -> argparse.Namespace: |
||||
|
testParser = argparse.ArgumentParser(description = 'Test that each function is working properly') |
||||
|
testParser.add_argument('PROTOCOL', type = str, help = 'test protocol name') |
||||
|
testParser.add_argument('-a', '--all', help = 'test extra shadowsocks items', action = 'store_true') |
||||
|
testParser.add_argument('-6', '--ipv6', help = 'test on ipv6 network', action = 'store_true') |
||||
|
testParser.add_argument('--debug', help = 'enable debug log level', action = 'store_true') |
||||
|
testParser.add_argument('--url', type = str, default = 'http://baidu.com', help = 'http request url') |
||||
|
testParser.add_argument('--cert', type = str, default = '', help = 'specify the certificate id') |
||||
|
testParser.add_argument('--thread', type = int, default = 16, help = 'thread number in check process') |
||||
|
testParser.add_argument('--select', type = str, nargs = '+', help = 'select id list for test') |
||||
|
return testParser.parse_args(rawArgs) |
||||
|
|
||||
|
|
||||
|
testArgs = None |
||||
|
testMode = False |
||||
|
inputArgs = copy.copy(sys.argv) |
||||
|
if len(inputArgs) >= 0: # remove first arg (normally file name) |
||||
|
inputArgs.pop(0) |
||||
|
if len(inputArgs) != 0 and inputArgs[0].lower() == 'test': # test mode |
||||
|
inputArgs.pop(0) # remove `test` |
||||
|
if len(inputArgs) == 0 or inputArgs[0].startswith('-'): # no protocol is specified |
||||
|
inputArgs = ['all'] + inputArgs |
||||
|
testArgs = testArgParse(inputArgs) |
||||
|
Constant.LogLevel = 'debug' if testArgs.debug else 'warning' |
||||
|
testMode = True |
||||
|
else: |
||||
|
mainArgs = mainArgParse(inputArgs) |
||||
|
if mainArgs.version: # output version and exit |
||||
|
print('ProxyC version -> %s' % Constant.Version) |
||||
|
sys.exit(0) |
||||
|
Constant.LogLevel = mainArgs.log # overwrite global options |
||||
|
Constant.DnsServer = mainArgs.dns |
||||
|
Constant.ApiPort = mainArgs.port |
||||
|
Constant.ApiPath = mainArgs.path |
||||
|
Constant.ApiToken = mainArgs.token |
||||
|
Constant.CheckThread = mainArgs.thread |
||||
|
|
||||
|
|
||||
|
from Tester import testEntry |
||||
from Basis.Check import Check |
from Basis.Check import Check |
||||
|
from Basis import Api, DnsProxy |
||||
from Basis.Logger import logging |
from Basis.Logger import logging |
||||
from Basis.Manager import Manager |
from Basis.Manager import Manager |
||||
from Basis.Api import startServer |
from Basis.Test import Test, loadBind, loadCert |
||||
from Basis.Constant import Version |
|
||||
from Basis.Compile import startCompile |
|
||||
from concurrent.futures import ThreadPoolExecutor |
from concurrent.futures import ThreadPoolExecutor |
||||
|
|
||||
# dnsServers = None |
|
||||
dnsServers = ['223.5.5.5', '119.28.28.28'] |
def pythonCompile(dirRange: str = '/') -> None: # python optimize compile |
||||
|
for optimize in [-1, 1, 2]: |
||||
|
compileall.compile_dir(dirRange, quiet = 1, optimize = optimize) |
||||
|
logging.warning('Python optimize compile -> %s (level = %i)' % (dirRange, optimize)) |
||||
|
|
||||
|
|
||||
def runCheck(taskId: str, taskInfo: dict) -> None: |
def runCheck(taskId: str, taskInfo: dict) -> None: |
||||
checkResult = Check(taskId, taskInfo) |
success = True |
||||
|
checkResult = {} |
||||
|
try: |
||||
|
checkResult = Check(taskId, taskInfo) # check by task info |
||||
logging.warning('[%s] Task finish' % taskId) |
logging.warning('[%s] Task finish' % taskId) |
||||
Manager.finishTask(taskId, checkResult) |
except checkException as exp: |
||||
|
success = False |
||||
|
logging.error('[%s] Task error -> %s' % (taskId, exp)) |
||||
|
except: |
||||
|
success = False |
||||
|
logging.error('[%s] Task error -> Unknown error' % taskId) |
||||
|
finally: |
||||
|
if not success: # got some error in check process |
||||
|
taskInfo.pop('check') |
||||
|
checkResult = { |
||||
|
**taskInfo, |
||||
|
'success': False, |
||||
|
} |
||||
|
Manager.finishTask(taskId, checkResult) # commit check result |
||||
|
|
||||
|
|
||||
def loopCheck(threadNum: int = 16) -> None: |
def loop(threadNum: int) -> None: |
||||
threadPool = ThreadPoolExecutor(max_workers = threadNum) |
logging.warning('Loop check start -> %i threads' % threadNum) |
||||
|
threadPool = ThreadPoolExecutor(max_workers = threadNum) # init thread pool |
||||
while True: |
while True: |
||||
try: |
try: |
||||
taskId, taskInfo = Manager.popTask() |
taskId, taskInfo = Manager.popTask() # pop a task |
||||
logging.warning('[%s] Load new task' % taskId) |
logging.warning('[%s] Load new task' % taskId) |
||||
except: # no more task |
except: # no more task |
||||
time.sleep(2) |
time.sleep(2) |
||||
continue |
continue |
||||
threadPool.submit(runCheck, taskId, taskInfo) |
threadPool.submit(runCheck, taskId, taskInfo) # submit into thread pool |
||||
|
|
||||
|
|
||||
|
if testMode: # test mode |
||||
|
loadBind(serverV6 = testArgs.ipv6, clientV6 = testArgs.ipv6) # ipv4 / ipv6 (127.0.0.1 / ::1) |
||||
|
loadCert(certId = testArgs.cert) # cert config |
||||
|
logging.critical('TEST ITEM: %s' % testArgs.PROTOCOL) |
||||
|
logging.critical('SELECT: ' + str(testArgs.select)) |
||||
|
logging.critical('URL: %s' % testArgs.url) |
||||
|
logging.critical('THREAD NUMBER: %i' % testArgs.thread) |
||||
|
logging.critical('-' * 32 + ' TEST START ' + '-' * 32) |
||||
|
if testArgs.PROTOCOL == 'all': # run all test items |
||||
|
for item in testEntry: |
||||
|
if item == ('ss' if testArgs.all else 'ss-all'): # skip ss / ss-all |
||||
|
continue |
||||
|
logging.critical('TEST ITEM -> ' + item) |
||||
|
Test(testEntry[item], testArgs.thread, testArgs.url, testArgs.select) |
||||
|
else: # run single item |
||||
|
if testArgs.PROTOCOL == 'ss' and testArgs.all: # test shadowsocks extra items |
||||
|
testItem = 'ss-all' |
||||
|
Test(testEntry[testArgs.PROTOCOL], testArgs.thread, testArgs.url, testArgs.select) |
||||
|
logging.critical('-' * 32 + ' TEST COMPLETE ' + '-' * 32) |
||||
|
sys.exit(0) # test complete |
||||
|
|
||||
|
|
||||
logging.warning('ProxyC starts running (%s)' % Version) |
logging.warning('ProxyC starts running (%s)' % Constant.Version) |
||||
_thread.start_new_thread(startCompile, ('/usr', )) # python compile (generate .pyc file) |
_thread.start_new_thread(pythonCompile, ('/usr',)) # python compile (generate .pyc file) |
||||
_thread.start_new_thread(DnsProxy.start, (dnsServers, 53)) # start dns server |
_thread.start_new_thread(DnsProxy.start, (Constant.DnsServer, 53)) # start dns server |
||||
_thread.start_new_thread(loopCheck, ()) # start loop check |
_thread.start_new_thread(loop, (Constant.CheckThread, )) # start check loop |
||||
startServer(apiToken = '') # start api server |
Api.startServer() # start api server |
||||
|
@ -1,66 +0,0 @@ |
|||||
#!/usr/bin/env python3 |
|
||||
# -*- coding: utf-8 -*- |
|
||||
|
|
||||
import sys |
|
||||
import Tester |
|
||||
from Basis.Logger import logging |
|
||||
|
|
||||
threadNum = 16 |
|
||||
testItem = None |
|
||||
testFilter = None |
|
||||
testUrl = 'http://baidu.com' |
|
||||
helpMsg = ''' |
|
||||
./test.py [ITEM] [OPTIONS] |
|
||||
|
|
||||
[ITEM]: ss / 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 |
|
||||
--ipv6 test on ipv6 network |
|
||||
--help show this message |
|
||||
''' |
|
||||
|
|
||||
|
|
||||
def getArg(field: str) -> str or None: |
|
||||
try: |
|
||||
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(',')) |
|
||||
|
|
||||
isV6 = '--ipv6' in sys.argv |
|
||||
Tester.loadBind(serverV6 = isV6, clientV6 = isV6) # ipv4 / ipv6 (127.0.0.1 / ::1) |
|
||||
Tester.loadCert('proxyc.net', 'ProxyC') # default cert config |
|
||||
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: |
|
||||
if testItem == 'ss' and '--all' in sys.argv: |
|
||||
testItem = 'ss-all' |
|
||||
Tester.test(Tester.entry[testItem], threadNum, testUrl, testFilter) |
|
||||
else: |
|
||||
for item in Tester.entry: |
|
||||
if item == ('ss' if '--all' in sys.argv else 'ss-all'): # skip ss / ss-all |
|
||||
continue |
|
||||
logging.critical('TEST ITEM -> ' + item) |
|
||||
Tester.test(Tester.entry[item], threadNum, testUrl, testFilter) |
|
||||
logging.critical('-------------------------------- TEST COMPLETE --------------------------------') |
|
Loading…
Reference in new issue