Browse Source

refactor: new ProxyBuilder

master
Dnomd343 2 years ago
parent
commit
3f1f3223d7
  1. 3
      .gitignore
  2. 471
      ProxyBuilder/Shadowsocks.py
  3. 114
      ProxyBuilder/ShadowsocksR.py
  4. 2
      ProxyBuilder/__init__.py
  5. 104
      ProxyBuilder/builder.py

3
.gitignore

@ -1 +1,2 @@
**/__pycache__ **/__pycache__/
/.idea/

471
ProxyBuilder/Shadowsocks.py

@ -1,185 +1,226 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
"""
# Shadowsocks构建器
## 节点格式
```json
{
"server": "...",
"port": ...,
"method": "...",
"passwd": "...",
"plugin": pluginObject
}
```
+ server (str) -> 必选, 服务器地址 (IPv4 / IPv6 / Domain)
+ port (int) -> 必选, 服务器端口 (1 ~ 65535)
+ method (str) -> 必选, Shadowsocks加密方式 (`aes-128-gcm`,`aes-192-gcm`,`aes-256-gcm`,
`aes-128-ctr`,`aes-192-ctr`,`aes-256-ctr`,
`aes-128-ocb`,`aes-192-ocb`,`aes-256-ocb`,
`aes-128-ofb`,`aes-192-ofb`,`aes-256-ofb`,
`aes-128-cfb`,`aes-192-cfb`,`aes-256-cfb`,
`aes-128-cfb1`,`aes-192-cfb1`,`aes-256-cfb1`,
`aes-128-cfb8`,`aes-192-cfb8`,`aes-256-cfb8`,
`aes-128-cfb128`,`aes-192-cfb128`,`aes-256-cfb128`,
`camellia-128-cfb`,`camellia-192-cfb`,`camellia-256-cfb`,
`camellia-128-cfb128`,`camellia-192-cfb128`,`camellia-256-cfb128`,
`plain`,`none`,`table`,`rc4`,`rc4-md5`,`rc2-cfb`,`bf-cfb`,
`cast5-cfb`,`des-cfb`,`idea-cfb`,`seed-cfb`,`salsa20`,`salsa20-ctr`,
`xchacha20`,`chacha20`,`chacha20-ietf`,`chacha20-poly1305`,
`chacha20-ietf-poly1305`,`xchacha20-ietf-poly1305`)
+ passwd (str) -> 必选, Shadowsocks连接密码
+ plugin (pluginObject) -> 必选, Shadowsocks连接插件
### pluginObject (None / dict)
无插件: None
带插件:
```json
{
"type": "...",
"param": "..."
}
```
+ type (str) -> 插件名称
+ param (str) -> 插件参数
"""
import json import json
ssMethodList = { # shadowsocks各版本支持的加密方式 ssMethodList = { # Shadowsocks各版本加密方式支持
"ss-python": [ 'ss-python': [
"aes-128-gcm", 'aes-128-gcm',
"aes-192-gcm", 'aes-192-gcm',
"aes-256-gcm", 'aes-256-gcm',
"aes-128-ctr", 'aes-128-ctr',
"aes-192-ctr", 'aes-192-ctr',
"aes-256-ctr", 'aes-256-ctr',
"aes-128-ocb", 'aes-128-ocb',
"aes-192-ocb", 'aes-192-ocb',
"aes-256-ocb", 'aes-256-ocb',
"aes-128-ofb", 'aes-128-ofb',
"aes-192-ofb", 'aes-192-ofb',
"aes-256-ofb", 'aes-256-ofb',
"aes-128-cfb", 'aes-128-cfb',
"aes-192-cfb", 'aes-192-cfb',
"aes-256-cfb", 'aes-256-cfb',
"aes-128-cfb1", 'aes-128-cfb1',
"aes-192-cfb1", 'aes-192-cfb1',
"aes-256-cfb1", 'aes-256-cfb1',
"aes-128-cfb8", 'aes-128-cfb8',
"aes-192-cfb8", 'aes-192-cfb8',
"aes-256-cfb8", 'aes-256-cfb8',
"aes-128-cfb128", 'aes-128-cfb128',
"aes-192-cfb128", 'aes-192-cfb128',
"aes-256-cfb128", 'aes-256-cfb128',
"camellia-128-cfb", 'camellia-128-cfb',
"camellia-192-cfb", 'camellia-192-cfb',
"camellia-256-cfb", 'camellia-256-cfb',
"camellia-128-cfb128", 'camellia-128-cfb128',
"camellia-192-cfb128", 'camellia-192-cfb128',
"camellia-256-cfb128", 'camellia-256-cfb128',
"table", 'table',
"rc4", 'rc4',
"rc4-md5", 'rc4-md5',
"rc2-cfb", 'rc2-cfb',
"bf-cfb", 'bf-cfb',
"cast5-cfb", 'cast5-cfb',
"des-cfb", 'des-cfb',
"idea-cfb", 'idea-cfb',
"seed-cfb", 'seed-cfb',
"salsa20", 'salsa20',
"xchacha20", 'xchacha20',
"chacha20", 'chacha20',
"chacha20-ietf", 'chacha20-ietf',
"chacha20-poly1305", 'chacha20-poly1305',
"chacha20-ietf-poly1305", 'chacha20-ietf-poly1305',
"xchacha20-ietf-poly1305", 'xchacha20-ietf-poly1305',
], ],
"ss-python-legacy": [ 'ss-python-legacy': [
"aes-128-ctr", 'aes-128-ctr',
"aes-192-ctr", 'aes-192-ctr',
"aes-256-ctr", 'aes-256-ctr',
"aes-128-ofb", 'aes-128-ofb',
"aes-192-ofb", 'aes-192-ofb',
"aes-256-ofb", 'aes-256-ofb',
"aes-128-cfb", 'aes-128-cfb',
"aes-192-cfb", 'aes-192-cfb',
"aes-256-cfb", 'aes-256-cfb',
"aes-128-cfb1", 'aes-128-cfb1',
"aes-192-cfb1", 'aes-192-cfb1',
"aes-256-cfb1", 'aes-256-cfb1',
"aes-128-cfb8", 'aes-128-cfb8',
"aes-192-cfb8", 'aes-192-cfb8',
"aes-256-cfb8", 'aes-256-cfb8',
"camellia-128-cfb", 'camellia-128-cfb',
"camellia-192-cfb", 'camellia-192-cfb',
"camellia-256-cfb", 'camellia-256-cfb',
"table", 'table',
"rc4", 'rc4',
"rc4-md5", 'rc4-md5',
"rc2-cfb", 'rc2-cfb',
"bf-cfb", 'bf-cfb',
"cast5-cfb", 'cast5-cfb',
"des-cfb", 'des-cfb',
"idea-cfb", 'idea-cfb',
"seed-cfb", 'seed-cfb',
"salsa20", 'salsa20',
"salsa20-ctr", 'salsa20-ctr',
"chacha20", 'chacha20',
], ],
"ss-libev": [ 'ss-libev': [
"aes-128-gcm", 'aes-128-gcm',
"aes-192-gcm", 'aes-192-gcm',
"aes-256-gcm", 'aes-256-gcm',
"aes-128-ctr", 'aes-128-ctr',
"aes-192-ctr", 'aes-192-ctr',
"aes-256-ctr", 'aes-256-ctr',
"aes-128-cfb", 'aes-128-cfb',
"aes-192-cfb", 'aes-192-cfb',
"aes-256-cfb", 'aes-256-cfb',
"camellia-128-cfb", 'camellia-128-cfb',
"camellia-192-cfb", 'camellia-192-cfb',
"camellia-256-cfb", 'camellia-256-cfb',
"rc4", 'rc4',
"rc4-md5", 'rc4-md5',
"bf-cfb", 'bf-cfb',
"salsa20", 'salsa20',
"chacha20", 'chacha20',
"chacha20-ietf", 'chacha20-ietf',
"chacha20-ietf-poly1305", 'chacha20-ietf-poly1305',
"xchacha20-ietf-poly1305", 'xchacha20-ietf-poly1305',
], ],
"ss-libev-legacy": [ 'ss-libev-legacy': [
"aes-128-ctr", 'aes-128-ctr',
"aes-192-ctr", 'aes-192-ctr',
"aes-256-ctr", 'aes-256-ctr',
"aes-128-cfb", 'aes-128-cfb',
"aes-192-cfb", 'aes-192-cfb',
"aes-256-cfb", 'aes-256-cfb',
"camellia-128-cfb", 'camellia-128-cfb',
"camellia-192-cfb", 'camellia-192-cfb',
"camellia-256-cfb", 'camellia-256-cfb',
"table", 'table',
"rc4", 'rc4',
"rc4-md5", 'rc4-md5',
"rc2-cfb", 'rc2-cfb',
"bf-cfb", 'bf-cfb',
"cast5-cfb", 'cast5-cfb',
"des-cfb", 'des-cfb',
"idea-cfb", 'idea-cfb',
"seed-cfb", 'seed-cfb',
"salsa20", 'salsa20',
"chacha20", 'chacha20',
"chacha20-ietf", 'chacha20-ietf',
], ],
"ss-rust": [ 'ss-rust': [
"aes-128-gcm", 'aes-128-gcm',
"aes-256-gcm", 'aes-256-gcm',
"plain", 'plain',
"none", 'none',
"chacha20-ietf-poly1305", 'chacha20-ietf-poly1305',
] ]
} }
def __baseJSON(proxyInfo, socksPort): # 生成JSON基本结构 def __baseConfig(proxyInfo: dict, socksPort: int) -> dict: # 生成基本配置
jsonContent = { config = {
'server': proxyInfo['server'], 'server': proxyInfo['server'],
'server_port': int(proxyInfo['port']), 'server_port': proxyInfo['port'],
'local_address': '127.0.0.1', 'local_address': '127.0.0.1',
'local_port': int(socksPort), 'local_port': socksPort,
'password': proxyInfo['password'],
'method': proxyInfo['method'], 'method': proxyInfo['method'],
'password': proxyInfo['passwd'],
} }
if proxyInfo['plugin'] != '': if proxyInfo['plugin'] is not None: # 带插件
jsonContent['plugin'] = proxyInfo['plugin'] config['plugin'] = proxyInfo['plugin']['type']
jsonContent['plugin_opts'] = proxyInfo['pluginParam'] config['plugin_opts'] = proxyInfo['plugin']['param']
return jsonContent return config
def __pluginUdpCheck(plugin, pluginParam): # 插件是否使用UDP通讯 def __pluginWithUdp(plugin: str, pluginParam: str) -> bool: # 插件是否使用UDP通讯
if plugin == '': # 无插件 if plugin in ['obfs-local', 'simple-tls', 'ck-client', 'gq-client',
return False 'mtt-client', 'rabbit-plugin', 'gun-plugin']: # 不使用UDP通讯的插件
noUdpPlugin = [ # 不使用UDP通讯的插件
'obfs-local',
'simple-tls',
'ck-client',
'gq-client',
'mtt-client',
'rabbit-plugin',
'gun-plugin',
]
onlyUdpPlugin = [ # 仅使用UDP通讯的插件
'kcptun-client',
'qtun-client',
]
if plugin in noUdpPlugin:
return False return False
if plugin in onlyUdpPlugin: if plugin in ['v2ray-plugin', 'xray-plugin', 'gost-plugin']:
return True if 'mode=quic' not in pluginParam.split(';'): # 非quic模式不使用UDP通讯
if plugin == 'v2ray-plugin' or plugin == 'xray-plugin' or plugin == 'gost-plugin':
if not 'mode=quic' in pluginParam.split(';'):
return False return False
return True # 默认假定占用UDP return True # 默认假定占用UDP
def __ssPython(proxyInfo, socksPort, isLegacy = False): # ss-python配置文件生成 def __ssPython(proxyInfo: dict, socksPort: int, isLegacy: bool = False) -> tuple[dict, str]: # ss-python配置生成
jsonContent = __baseJSON(proxyInfo, socksPort) config = __baseConfig(proxyInfo, socksPort)
specialMethods = [ mbedtlsMethods = [
'aes-128-cfb128', 'aes-128-cfb128',
'aes-192-cfb128', 'aes-192-cfb128',
'aes-256-cfb128', 'aes-256-cfb128',
@ -187,46 +228,80 @@ def __ssPython(proxyInfo, socksPort, isLegacy = False): # ss-python配置文件
'camellia-192-cfb128', 'camellia-192-cfb128',
'camellia-256-cfb128', 'camellia-256-cfb128',
] ]
if isLegacy == False: # 仅新版本支持 if not isLegacy: # 新版本特性
if jsonContent['method'] in specialMethods: if config['method'] in mbedtlsMethods: # mbedtls库流加密
jsonContent['method'] = 'mbedtls:' + jsonContent['method'] config['method'] = 'mbedtls:' + config['method']
if jsonContent['method'] == 'idea-cfb' or jsonContent['method'] == 'seed-cfb': if config['method'] in ['idea-cfb', 'seed-cfb']: # 仅openssl旧版本支持
jsonContent['extra_opts'] = '--libopenssl=libcrypto.so.1.0.0' config['extra_opts'] = '--libopenssl=libcrypto.so.1.0.0'
if proxyInfo['udp'] != True: if not proxyInfo['udp']:
jsonContent['no_udp'] = True config['no_udp'] = True # 关闭UDP代理
if isLegacy == True: config['shadowsocks'] = 'ss-python-legacy-local' if isLegacy else 'ss-python-local'
jsonContent['shadowsocks'] = 'ss-python-legacy-local' return config, 'ss-bootstrap-local'
else:
jsonContent['shadowsocks'] = 'ss-python-local' def __ssLibev(proxyInfo: dict, socksPort: int, isLegacy: bool = False) -> tuple[dict, str]: # ss-libev配置生成
return jsonContent, 'ss-bootstrap-local' config = __baseConfig(proxyInfo, socksPort)
if proxyInfo['udp']:
def __ssLibev(proxyInfo, socksPort, isLegacy = False): # ss-libev配置文件生成 config['mode'] = 'tcp_and_udp'
jsonContent = __baseJSON(proxyInfo, socksPort) return config, 'ss-libev-legacy-local' if isLegacy else 'ss-libev-local'
if proxyInfo['udp'] == True:
jsonContent['mode'] = 'tcp_and_udp'
if isLegacy == True:
return jsonContent, 'ss-libev-legacy-local'
else:
return jsonContent, 'ss-libev-local'
def __ssRust(proxyInfo, socksPort): # ss-rust配置文件生成 def __ssRust(proxyInfo: dict, socksPort: int) -> tuple[dict, str]: # ss-rust配置生成
jsonContent = __baseJSON(proxyInfo, socksPort) config = __baseConfig(proxyInfo, socksPort)
if proxyInfo['udp'] == True: if proxyInfo['udp']:
jsonContent['mode'] = 'tcp_and_udp' config['mode'] = 'tcp_and_udp'
return jsonContent, 'ss-rust-local' return config, 'ss-rust-local'
def load(proxyInfo, socksPort, configFile): # Shadowsocks配置载入 def __ssFormatCheck(proxyInfo: dict) -> bool: # Shadowsocks参数检查
proxyInfo['udp'] = not __pluginUdpCheck(proxyInfo['plugin'], proxyInfo['pluginParam']) if 'server' not in proxyInfo or not isinstance(proxyInfo['server'], str): # server -> str
if proxyInfo['method'] in ssMethodList['ss-libev']: return False
jsonContent, ssFile = __ssLibev(proxyInfo, socksPort) if 'port' not in proxyInfo or not isinstance(proxyInfo['port'], int): # port -> int
return False
if 'method' not in proxyInfo or not isinstance(proxyInfo['method'], str): # method -> str
return False
if 'passwd' not in proxyInfo or not isinstance(proxyInfo['passwd'], str): # passwd -> str
return False
if 'plugin' not in proxyInfo:
return False
plugin = proxyInfo['plugin']
if isinstance(plugin, dict): # plugin -> dict / None
if 'type' not in plugin or not isinstance(plugin['type'], str): # plugin.type -> str
return False
if 'param' not in plugin or not isinstance(plugin['param'], str): # plugin.param -> str
return False
elif plugin is not None:
return False
return True
def load(proxyInfo: dict, socksPort: int, configFile: str) -> tuple[list or None, str or None, dict or None]:
"""
Shadowsocks配置载入
proxyInfo: 节点信息
socksPort: 本地通讯端口
configFile: 配置文件路径
节点有误:
return None, None, None
载入成功:
return startCommand, fileContent, envVar
"""
if not __ssFormatCheck(proxyInfo): # 参数有误
return None, None, None
if proxyInfo['plugin'] is None: # 无插件时启用UDP
proxyInfo['udp'] = True
else:
proxyInfo['udp'] = not __pluginWithUdp( # 获取插件UDP冲突状态
proxyInfo['plugin']['type'], proxyInfo['plugin']['param']
)
if proxyInfo['method'] in ssMethodList['ss-libev']: # 按序匹配客户端
config, ssFile = __ssLibev(proxyInfo, socksPort)
elif proxyInfo['method'] in ssMethodList['ss-libev-legacy']: elif proxyInfo['method'] in ssMethodList['ss-libev-legacy']:
jsonContent, ssFile = __ssLibev(proxyInfo, socksPort, isLegacy = True) config, ssFile = __ssLibev(proxyInfo, socksPort, isLegacy = True)
elif proxyInfo['method'] in ssMethodList['ss-python']: elif proxyInfo['method'] in ssMethodList['ss-python']:
jsonContent, ssFile = __ssPython(proxyInfo, socksPort) config, ssFile = __ssPython(proxyInfo, socksPort)
elif proxyInfo['method'] in ssMethodList['ss-python-legacy']: elif proxyInfo['method'] in ssMethodList['ss-python-legacy']:
jsonContent, ssFile = __ssPython(proxyInfo, socksPort, isLegacy = True) config, ssFile = __ssPython(proxyInfo, socksPort, isLegacy = True)
elif proxyInfo['method'] in ssMethodList['ss-rust']: elif proxyInfo['method'] in ssMethodList['ss-rust']:
jsonContent, ssFile = __ssRust(proxyInfo, socksPort) config, ssFile = __ssRust(proxyInfo, socksPort)
else: else:
return None, None # 匹配不到加密方式 return None, None, None # 匹配加密方式
return [ ssFile, '-c', configFile ], json.dumps(jsonContent) return [ssFile, '-c', configFile], json.dumps(config), {}

114
ProxyBuilder/ShadowsocksR.py

@ -1,9 +1,59 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding:utf-8 -*- # -*- coding:utf-8 -*-
"""
# ShadowsocksR构建器
## 节点格式
```json
{
"server": "...",
"port": ...,
"method": "...",
"passwd": "...",
"protocol": "...",
"protocolParam": "...",
"obfs": "...",
"obfsParam": "..."
}
```
+ server (str) -> 必选, 服务器地址 (IPv4 / IPv6 / Domain)
+ port (int) -> 必选, 服务器端口 (1 ~ 65535)
+ method (str) -> 必选, ShadowsocksR加密方式 (`aes-128-cfb`,`aes-192-cfb`,`aes-256-cfb`,
`aes-128-cfb1`,`aes-192-cfb1`,`aes-256-cfb1`,
`aes-128-cfb8`,`aes-192-cfb8`,`aes-256-cfb8`,
`aes-128-ctr`,`aes-192-ctr`,`aes-256-ctr`,
`aes-128-gcm`,`aes-192-gcm`,`aes-256-gcm`,
`aes-128-ofb`,`aes-192-ofb`,`aes-256-ofb`,
`camellia-128-cfb`,`camellia-192-cfb`,`camellia-256-cfb`,
`none`,`table`,`rc4`,`rc4-md5`,`rc4-md5-6`,`bf-cfb`,
`cast5-cfb`,`des-cfb`,`idea-cfb`,`seed-cfb`,`rc2-cfb`,
`salsa20`,`xsalsa20`,`chacha20`,`xchacha20`,`chacha20-ietf`)
+ passwd (str) -> 必选, ShadowsocksR连接密码
+ protocol (str) -> 必选, ShadowsocksR连接协议 (`origin`,`verify_sha1`,`verify_simple`,`verify_deflate`,
`auth_simple`,`auth_sha1`,`auth_sha1_v2`,`auth_sha1_v4`,
`auth_aes128`,`auth_aes128_md5`,`auth_aes128_sha1`,
`auth_chain_a`,`auth_chain_b`,`auth_chain_c`,
`auth_chain_d`,`auth_chain_e`,`auth_chain_f`)
+ protocolParam (str) -> 必选, ShadowsocksR协议参数
+ obfs (str) -> 必选, ShadowsocksR混淆方式 (`plain`,`http_post`,`http_simple`,`tls_simple`,
`tls1.2_ticket_auth`,`tls1.2_ticket_fastauth`,`random_head`)
+ obfsParam (str) -> 必选, ShadowsocksR混淆参数
"""
import json import json
ssrMethodList = [ # 加密方法 ssrMethodList = [ # ShadowsocksR加密方式
"aes-128-cfb", "aes-128-cfb",
"aes-192-cfb", "aes-192-cfb",
"aes-256-cfb", "aes-256-cfb",
@ -43,7 +93,7 @@ ssrMethodList = [ # 加密方法
"chacha20-ietf", "chacha20-ietf",
] ]
ssrProtocolList = [ # 协议 ssrProtocolList = [ # ShadowsocksR协议列表
"origin", "origin",
"verify_sha1", "verify_sha1",
"verify_simple", "verify_simple",
@ -63,7 +113,7 @@ ssrProtocolList = [ # 协议
"auth_chain_f", "auth_chain_f",
] ]
ssrObfsList = [ # 混淆方式 ssrObfsList = [ # ShadowsocksR混淆方式
"plain", "plain",
"http_post", "http_post",
"http_simple", "http_simple",
@ -73,22 +123,56 @@ ssrObfsList = [ # 混淆方式
"random_head", "random_head",
] ]
def load(proxyInfo, socksPort, configFile): # ShadowsocksR配置载入 def __ssrFormatCheck(proxyInfo: dict) -> bool: # ShadowsocksR参数检查
if not proxyInfo['method'] in ssrMethodList: if 'server' not in proxyInfo or not isinstance(proxyInfo['server'], str): # server -> str
return None, None # 匹配不到加密方法 return False
if not proxyInfo['protocol'] in ssrProtocolList: if 'port' not in proxyInfo or not isinstance(proxyInfo['port'], int): # port -> int
return None, None # 匹配不到协议 return False
if not proxyInfo['obfs'] in ssrObfsList: if 'method' not in proxyInfo or not isinstance(proxyInfo['method'], str): # method -> str
return None, None # 匹配不到混淆方式 return False
return [ 'ssr-local', '-c', configFile ], json.dumps({ if 'passwd' not in proxyInfo or not isinstance(proxyInfo['passwd'], str): # passwd -> str
return False
if 'protocol' not in proxyInfo or not isinstance(proxyInfo['protocol'], str): # protocol -> str
return False
if 'protocolParam' not in proxyInfo or not isinstance(proxyInfo['protocolParam'], str): # protocolParam -> str
return False
if 'obfs' not in proxyInfo or not isinstance(proxyInfo['obfs'], str): # obfs -> str
return False
if 'obfsParam' not in proxyInfo or not isinstance(proxyInfo['obfsParam'], str): # obfsParam -> str
return False
return True
def load(proxyInfo: dict, socksPort: int, configFile: str) -> tuple[list or None, str or None, dict or None]:
"""
ShadowsocksR配置载入
proxyInfo: 节点信息
socksPort: 本地通讯端口
configFile: 配置文件路径
节点有误:
return None, None, None
载入成功:
return startCommand, fileContent, envVar
"""
if not __ssrFormatCheck(proxyInfo): # 参数有误
return None, None, None
if not proxyInfo['method'] in ssrMethodList: # 匹配不到加密方法
return None, None, None
if not proxyInfo['protocol'] in ssrProtocolList: # 匹配不到协议
return None, None, None
if not proxyInfo['obfs'] in ssrObfsList: # 匹配不到混淆方式
return None, None, None
config = {
'server': proxyInfo['server'], 'server': proxyInfo['server'],
'server_port': int(proxyInfo['port']), 'server_port': proxyInfo['port'],
'local_address': '127.0.0.1', 'local_address': '127.0.0.1',
'local_port': int(socksPort), 'local_port': socksPort,
'password': proxyInfo['password'], 'password': proxyInfo['passwd'],
'method': proxyInfo['method'], 'method': proxyInfo['method'],
'protocol': proxyInfo['protocol'], 'protocol': proxyInfo['protocol'],
'protocol_param': proxyInfo['protocolParam'], 'protocol_param': proxyInfo['protocolParam'],
'obfs': proxyInfo['obfs'], 'obfs': proxyInfo['obfs'],
'obfs_param': proxyInfo['obfsParam'] 'obfs_param': proxyInfo['obfsParam']
}) }
return ['ssr-local', '-c', configFile], json.dumps(config), {}

2
ProxyBuilder/__init__.py

@ -3,4 +3,4 @@
from ProxyBuilder.builder import * from ProxyBuilder.builder import *
__all__ = [ 'build', 'check', 'destroy' ] __all__ = ['build', 'check', 'destroy']

104
ProxyBuilder/builder.py

@ -13,13 +13,17 @@ from ProxyBuilder import ShadowsocksR
libcPaths = [ libcPaths = [
'/usr/lib64/libc.so.6', # CentOS '/usr/lib64/libc.so.6', # CentOS
'/lib/libc.musl-x86_64.so.1', # Alpine
'/lib/i386-linux-gnu/libc.so.6', # Debian / Ubuntu '/lib/i386-linux-gnu/libc.so.6', # Debian / Ubuntu
'/lib/x86_64-linux-gnu/libc.so.6', '/lib/x86_64-linux-gnu/libc.so.6',
'/lib/aarch64-linux-gnu/libc.so.6', '/lib/aarch64-linux-gnu/libc.so.6',
'/lib/libc.musl-x86_64.so.1', # Alpine
] ]
def __checkPortAvailable(port): # 检测端口可用性 def __checkPortAvailable(port: int) -> bool: # 检测端口可用性
ipv4_tcp = None
ipv4_udp = None
ipv6_tcp = None
ipv6_udp = None
try: try:
ipv4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ipv4_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ipv4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) ipv4_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@ -50,8 +54,8 @@ def __checkPortAvailable(port): # 检测端口可用性
ipv6_udp.close() ipv6_udp.close()
except: pass except: pass
def __genTaskFlag(length = 16): # 生成任务代号 def __genTaskFlag(length: int = 16) -> str: # 生成任务代号
flag = "" flag = ''
for i in range(0, length): for i in range(0, length):
tmp = random.randint(0, 15) tmp = random.randint(0, 15)
if tmp >= 10: if tmp >= 10:
@ -60,8 +64,8 @@ def __genTaskFlag(length = 16): # 生成任务代号
flag += str(tmp) # 0 ~ 9 flag += str(tmp) # 0 ~ 9
return flag return flag
def __getAvailablePort(rangeStart, rangeEnd): # 获取一个空闲端口 def __getAvailablePort(rangeStart: int, rangeEnd: int) -> int or None: # 获取一个空闲端口
if rangeStart > rangeEnd: if rangeStart > rangeEnd or rangeStart < 0 or rangeEnd > 65535:
return None return None
while True: while True:
port = random.randint(rangeStart, rangeEnd) # 随机选取 port = random.randint(rangeStart, rangeEnd) # 随机选取
@ -69,8 +73,9 @@ def __getAvailablePort(rangeStart, rangeEnd): # 获取一个空闲端口
return port return port
time.sleep(0.1) # 100ms time.sleep(0.1) # 100ms
def build(proxyInfo, configDir, portRangeStart = 1024, portRangeEnd = 65535): def build(proxyInfo: dict, configDir: str,
''' portRangeStart: int = 1024, portRangeEnd: int = 65535) -> tuple[bool or None, str or dict]:
"""
创建代理节点客户端 创建代理节点客户端
程序内部错误: 程序内部错误:
@ -86,45 +91,54 @@ def build(proxyInfo, configDir, portRangeStart = 1024, portRangeEnd = 65535):
'file': configFile, 'file': configFile,
'process': process 'process': process
} }
''' """
taskFlag = __genTaskFlag() # 生成测试标志 taskFlag = __genTaskFlag() # 生成测试标志
socksPort = __getAvailablePort(portRangeStart, portRangeEnd) # 获取Socks5测试端口 socksPort = __getAvailablePort(portRangeStart, portRangeEnd) # 获取Socks5测试端口
if not 'type' in proxyInfo: # 未指定节点类型 if 'type' not in proxyInfo: # 未指定节点类型
return False, 'Proxy type not specified' return False, 'Proxy type not specified'
proxyType = proxyInfo['type'] # 节点类型 proxyType = proxyInfo['type'] # 节点类型
proxyInfo.pop('type') proxyInfo.pop('type')
configFile = configDir + '/' + taskFlag + '.json' # 配置文件路径
if proxyType == 'ss': # Shadowsocks节点 if proxyType == 'ss': # Shadowsocks节点
startCommand, fileContent = Shadowsocks.load(proxyInfo, socksPort, configFile) clientObj = Shadowsocks
elif proxyType == 'ssr': # ShadowsocksR节点 elif proxyType == 'ssr': # ShadowsocksR节点
startCommand, fileContent = ShadowsocksR.load(proxyInfo, socksPort, configFile) clientObj = ShadowsocksR
else: # 未知类型 else: # 未知类型
return False, 'Unknown proxy type' return False, 'Unknown proxy type'
if startCommand == None: # 格式出错
configFile = configDir + '/' + taskFlag + '.json' # 配置文件路径
startCommand, fileContent, envVar = clientObj.load(proxyInfo, socksPort, configFile) # 载入配置
if startCommand is None: # 格式出错
return False, 'Format error with ' + str(proxyType) return False, 'Format error with ' + str(proxyType)
try: try:
with open(configFile, 'w') as fileObject: with open(configFile, 'w') as fileObject: # 保存配置文件
fileObject.write(fileContent) # 保存配置文件 fileObject.write(fileContent)
except: # 配置文件写入失败 except: # 配置文件写入失败
return None, "Unable write to file " + str(configFile) return None, "Unable write to file " + str(configFile)
process = None
try: # 子进程形式启动 try: # 子进程形式启动
for libcPath in libcPaths: for libcPath in libcPaths:
if os.path.exists(libcPath): # 定位libc.so文件 if os.path.exists(libcPath): # 定位libc.so文件
break break
exitWithMe = lambda: ctypes.CDLL(libcPath).prctl(1, 15) # SIGTERM process = subprocess.Popen( # 启动子进程
process = subprocess.Popen(startCommand, startCommand,
env = envVar,
stdout = subprocess.DEVNULL, stdout = subprocess.DEVNULL,
stderr = subprocess.DEVNULL, stderr = subprocess.DEVNULL,
preexec_fn = exitWithMe) # 子进程跟随退出 preexec_fn = lambda: ctypes.CDLL(libcPath).prctl(1, 15) # 子进程跟随退出
)
except: except:
try: try:
process = subprocess.Popen(startCommand, process = subprocess.Popen( # prctl失败 回退正常启动
startCommand,
env = envVar,
stdout = subprocess.DEVNULL, stdout = subprocess.DEVNULL,
stderr = subprocess.DEVNULL) # prctl失败 回退正常启动 stderr = subprocess.DEVNULL
except: pass )
if not 'process' in vars(): # 启动失败 except:
pass
if process is None: # 启动失败
return None, 'Subprocess start failed by `' + ' '.join(startCommand) + '`' return None, 'Subprocess start failed by `' + ' '.join(startCommand) + '`'
return True, { # 返回连接参数 return True, { # 返回连接参数
@ -134,8 +148,8 @@ def build(proxyInfo, configDir, portRangeStart = 1024, portRangeEnd = 65535):
'process': process 'process': process
} }
def check(client): def check(client: dict) -> bool or None:
''' """
检查客户端是否正常运行 检查客户端是否正常运行
检测出错: return None 检测出错: return None
@ -143,44 +157,34 @@ def check(client):
工作异常: return False 工作异常: return False
工作正常: return True 工作正常: return True
''' """
if client == None:
return None
try: try:
if client['process'].poll() != None: return client['process'].poll() is None
return False # 死亡
else:
return True # 正常
except: except:
return None # 异常 return None # 异常
def destroy(client): def destroy(client: dict) -> bool:
''' """
结束客户端并清理 结束客户端并清理
销毁异常: return None 销毁异常: return False
客户端退出: return True 销毁成功: return True
''' """
if client == None:
return None
try: try:
process = client['process'] process = client['process']
if process.poll() == None: # 未死亡 if process.poll() is None: # 未死亡
process.terminate() # SIGTERM while process.poll() is None: # 等待退出
while process.poll() == None: # 等待退出 process.terminate() # SIGTERM
time.sleep(1) time.sleep(0.2)
process.terminate()
except: except:
return None return False
try: try:
file = client['file'] file = client['file']
if os.path.exists(file) and os.path.isfile(file): if os.path.exists(file) and os.path.isfile(file):
os.remove(file) # 删除配置文件 os.remove(file) # 删除配置文件
else: return True # 销毁成功
return None
except: except:
return None pass
return False
return True # 销毁完成

Loading…
Cancel
Save