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 |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
import sys |
|||
import copy |
|||
import time |
|||
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 import Api, DnsProxy |
|||
from Basis.Logger import logging |
|||
from Basis.Manager import Manager |
|||
from Basis.Api import startServer |
|||
from Basis.Constant import Version |
|||
from Basis.Compile import startCompile |
|||
from Basis.Test import Test, loadBind, loadCert |
|||
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: |
|||
checkResult = Check(taskId, taskInfo) |
|||
success = True |
|||
checkResult = {} |
|||
try: |
|||
checkResult = Check(taskId, taskInfo) # check by task info |
|||
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: |
|||
threadPool = ThreadPoolExecutor(max_workers = threadNum) |
|||
def loop(threadNum: int) -> None: |
|||
logging.warning('Loop check start -> %i threads' % threadNum) |
|||
threadPool = ThreadPoolExecutor(max_workers = threadNum) # init thread pool |
|||
while True: |
|||
try: |
|||
taskId, taskInfo = Manager.popTask() |
|||
taskId, taskInfo = Manager.popTask() # pop a task |
|||
logging.warning('[%s] Load new task' % taskId) |
|||
except: # no more task |
|||
time.sleep(2) |
|||
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) |
|||
_thread.start_new_thread(startCompile, ('/usr', )) # python compile (generate .pyc file) |
|||
_thread.start_new_thread(DnsProxy.start, (dnsServers, 53)) # start dns server |
|||
_thread.start_new_thread(loopCheck, ()) # start loop check |
|||
startServer(apiToken = '') # start api server |
|||
logging.warning('ProxyC starts running (%s)' % Constant.Version) |
|||
_thread.start_new_thread(pythonCompile, ('/usr',)) # python compile (generate .pyc file) |
|||
_thread.start_new_thread(DnsProxy.start, (Constant.DnsServer, 53)) # start dns server |
|||
_thread.start_new_thread(loop, (Constant.CheckThread, )) # start check loop |
|||
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