diff --git a/ProxyBuilder/builder.py b/ProxyBuilder/builder.py index 9a5fcb7..bfc1ac7 100644 --- a/ProxyBuilder/builder.py +++ b/ProxyBuilder/builder.py @@ -36,12 +36,18 @@ def __checkPortAvailable(port): # 检测端口可用性 return True # IPv4 TCP / IPv4 UDP / IPv6 TCP / IPv6 UDP 均无占用 except: return False - finally: - try: # 关闭socket - if ipv4_tcp: ipv4_tcp.close() - if ipv4_udp: ipv4_udp.close() - if ipv6_tcp: ipv6_tcp.close() - if ipv6_udp: ipv6_udp.close() + finally: # 关闭socket + try: + ipv4_tcp.close() + except: pass + try: + ipv4_udp.close() + except: pass + try: + ipv6_tcp.close() + except: pass + try: + ipv6_udp.close() except: pass def __genTaskFlag(length = 16): # 生成任务代号 @@ -63,11 +69,28 @@ def __getAvailablePort(rangeStart, rangeEnd): # 获取一个空闲端口 return port time.sleep(0.1) # 100ms -def build(proxyInfo, configDir): # 构建代理节点连接 - taskFlag = __genTaskFlag() - socksPort = __getAvailablePort(1024, 65535) # Socks5测试端口 - if not 'type' in proxyInfo: - return None +def build(proxyInfo, configDir, portRangeStart = 1024, portRangeEnd = 65535): + ''' + 创建代理节点客户端 + + 程序内部错误: + return None, {reason} + + 代理节点无效: + return False, {reason} + + 代理工作正常: + return True, { + 'flag': taskFlag, + 'port': socksPort, + 'file': configFile, + 'process': process + } + ''' + taskFlag = __genTaskFlag() # 生成测试标志 + socksPort = __getAvailablePort(portRangeStart, portRangeEnd) # 获取Socks5测试端口 + if not 'type' in proxyInfo: # 未指定节点类型 + return False, 'Proxy type not specified' proxyType = proxyInfo['type'] # 节点类型 proxyInfo.pop('type') @@ -77,19 +100,16 @@ def build(proxyInfo, configDir): # 构建代理节点连接 elif proxyType == 'ssr': # ShadowsocksR节点 startCommand, fileContent = ShadowsocksR.load(proxyInfo, socksPort, configFile) else: # 未知类型 - print("Unknown proxy type") - return None + return False, 'Unknown proxy type' if startCommand == None: # 格式出错 - print("Format error with " + proxyType) - return None + return False, 'Format error with ' + str(proxyType) try: with open(configFile, 'w') as fileObject: fileObject.write(fileContent) # 保存配置文件 - except: - print("Unable write to file " + configFile) - return None # 配置文件写入失败 + except: # 配置文件写入失败 + return None, "Unable write to file " + str(configFile) - try: + try: # 子进程形式启动 for libcPath in libcPaths: if os.path.exists(libcPath): # 定位libc.so文件 break @@ -99,37 +119,68 @@ def build(proxyInfo, configDir): # 构建代理节点连接 stderr = subprocess.DEVNULL, preexec_fn = exitWithMe) # 子进程跟随退出 except: - print("WARNING: Subprocess may become a Orphan Process") - process = subprocess.Popen(startCommand, - stdout = subprocess.DEVNULL, - stderr = subprocess.DEVNULL) # prctl失败 回退正常启动 + try: + process = subprocess.Popen(startCommand, + stdout = subprocess.DEVNULL, + stderr = subprocess.DEVNULL) # prctl失败 回退正常启动 + except: pass + if not 'process' in vars(): # 启动失败 + return None, 'Subprocess start failed by `' + ' '.join(startCommand) + '`' - return { # 返回连接参数 + return True, { # 返回连接参数 'flag': taskFlag, 'port': socksPort, 'file': configFile, - 'process': process, - 'pid': process.pid, + 'process': process } -def check(taskInfo): # 检查客户端是否正常 - if taskInfo == None: - return False - process = taskInfo['process'] - if process.poll() != None: - return False # 死亡 - else: - return True # 正常 - -def destroy(taskInfo): # 结束客户端并清理 - if taskInfo == None: - return - process = taskInfo['process'] - if process.poll() == None: # 未死亡 - process.terminate() # SIGTERM - while process.poll() == None: # 等待退出 - time.sleep(1) - process.terminate() +def check(client): + ''' + 检查客户端是否正常运行 + + 检测出错: return None + + 工作异常: return False + + 工作正常: return True + ''' + if client == None: + return None + try: + if client['process'].poll() != None: + return False # 死亡 + else: + return True # 正常 + except: + return None # 异常 + +def destroy(client): + ''' + 结束客户端并清理 + + 销毁异常: return None + + 客户端退出: return True + ''' + if client == None: + return None try: - os.remove(taskInfo['file']) # 删除配置文件 - except: pass + process = client['process'] + if process.poll() == None: # 未死亡 + process.terminate() # SIGTERM + while process.poll() == None: # 等待退出 + time.sleep(1) + process.terminate() + except: + return None + + try: + file = client['file'] + if os.path.exists(file) and os.path.isfile(file): + os.remove(file) # 删除配置文件 + else: + return None + except: + return None + + return True # 销毁完成 diff --git a/demo.py b/demo.py index ea3941c..4266aa8 100644 --- a/demo.py +++ b/demo.py @@ -1,50 +1,81 @@ #!/usr/bin/python # -*- coding:utf-8 -*- +import os import time -import socket -import requests import ProxyBuilder as Builder import ProxyChecker as Checker -testInfo = { - 'type': 'ss', - 'server': '127.0.0.1', - 'port': 12345, - 'password': 'dnomd343', - 'method': 'aes-256-ctr', - 'plugin': '', - 'pluginParam': '', +workDir = '/tmp/ProxyC' + +ssTest = { + 'tag': 'f43c9bae21ae8693', + 'check': [ + 'http' + ], + 'info': { + 'type': 'ss', + 'server': '127.0.0.1', + 'port': 12345, + 'password': 'dnomd343', + 'method': 'aes-256-ctr', + 'plugin': '', + 'pluginParam': '', + } +} + +ssrTest = { + 'tag': 'f43c9bae21ae8693', + 'check': [ + 'http' + ], + 'info': { + 'type': 'ssr', + "server": "127.0.0.1", + "port": 23456, + "password": "dnomd343", + "method": "table", + "protocol": "auth_aes128_md5", + "protocolParam": "", + "obfs": "tls1.2_ticket_auth", + "obfsParam": "" + } } -# testInfo = { -# 'type': 'ssr', -# "server": "127.0.0.1", -# "port": 23456, -# "password": "dnomd343", -# "method": "table", -# "protocol": "auth_aes128_md5", -# "protocolParam": "", -# "obfs": "tls1.2_ticket_auth", -# "obfsParam": "" -# } - -print("start") -print(testInfo) -task = Builder.build(testInfo, '/tmp/ProxyC') -print(task) -time.sleep(1) -if Builder.check(task) == False: - print("error exit") - Builder.destroy(task) -else: - print("http check") - health, delay = Checker.httpCheck(task['port']) +def loadDir(folderPath): # 创建文件夹 + try: + if os.path.exists(folderPath): # 文件 / 文件夹 存在 + if not os.path.isdir(folderPath): # 文件 + return False # 无法创建 + else: # 不存在 + os.makedirs(folderPath) # 递归创建文件夹 + return True # 文件夹正常 + except: + return False + +def proxyTest(rawInfo, startDelay = 1, destroyDelay = 0.5): + if loadDir(workDir) == False: # 工作文件夹无效 + return None + if not 'info' in rawInfo: + return None + status, client = Builder.build(rawInfo['info'], workDir) + if status != True: + print(client) + return None + time.sleep(startDelay) + if Builder.check(client) != True: + print("client error") + return None + health, httpDelay = Checker.httpCheck(client['port']) print("health = " + str(health)) - if delay < 0: - print("error") + if httpDelay < 0: + print("http error") else: - print("delay = " + format(delay, '.2f') + 'ms') - Builder.destroy(task) + print("delay = " + format(httpDelay, '.2f') + 'ms') + if Builder.destroy(client) != True: + print("client destroy error") + time.sleep(destroyDelay) print("done") + +proxyTest(ssrTest) diff --git a/test.py b/test.py index 8fbf9e6..88520af 100644 --- a/test.py +++ b/test.py @@ -23,9 +23,9 @@ def startTest(testList): print("server unexpected exit") continue print(field['caption'] + ' => ', end = '') - client = Builder.build(field['proxyInfo'], '/tmp/ProxyC') + status, client = Builder.build(field['proxyInfo'], '/tmp/ProxyC') time.sleep(0.5) # 等待初始化完成 - if not Builder.check(client): + if Builder.check(client) != True: print("client unexpected exit") # 客户端启动失败 else: print(format(Checker.httpPing(client['port']), '.2f') + 'ms')