|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding:utf-8 -*-
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
ssMethodList = { # Shadowsocks各版本加密方式支持
|
|
|
|
'ss-python': [
|
|
|
|
'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',
|
|
|
|
'table',
|
|
|
|
'rc4',
|
|
|
|
'rc4-md5',
|
|
|
|
'rc2-cfb',
|
|
|
|
'bf-cfb',
|
|
|
|
'cast5-cfb',
|
|
|
|
'des-cfb',
|
|
|
|
'idea-cfb',
|
|
|
|
'seed-cfb',
|
|
|
|
'salsa20',
|
|
|
|
'xchacha20',
|
|
|
|
'chacha20',
|
|
|
|
'chacha20-ietf',
|
|
|
|
'chacha20-poly1305',
|
|
|
|
'chacha20-ietf-poly1305',
|
|
|
|
'xchacha20-ietf-poly1305',
|
|
|
|
],
|
|
|
|
'ss-python-legacy': [
|
|
|
|
'aes-128-ctr',
|
|
|
|
'aes-192-ctr',
|
|
|
|
'aes-256-ctr',
|
|
|
|
'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',
|
|
|
|
'camellia-128-cfb',
|
|
|
|
'camellia-192-cfb',
|
|
|
|
'camellia-256-cfb',
|
|
|
|
'table',
|
|
|
|
'rc4',
|
|
|
|
'rc4-md5',
|
|
|
|
'rc2-cfb',
|
|
|
|
'bf-cfb',
|
|
|
|
'cast5-cfb',
|
|
|
|
'des-cfb',
|
|
|
|
'idea-cfb',
|
|
|
|
'seed-cfb',
|
|
|
|
'salsa20',
|
|
|
|
'salsa20-ctr',
|
|
|
|
'chacha20',
|
|
|
|
],
|
|
|
|
'ss-libev': [
|
|
|
|
'aes-128-gcm',
|
|
|
|
'aes-192-gcm',
|
|
|
|
'aes-256-gcm',
|
|
|
|
'aes-128-ctr',
|
|
|
|
'aes-192-ctr',
|
|
|
|
'aes-256-ctr',
|
|
|
|
'aes-128-cfb',
|
|
|
|
'aes-192-cfb',
|
|
|
|
'aes-256-cfb',
|
|
|
|
'camellia-128-cfb',
|
|
|
|
'camellia-192-cfb',
|
|
|
|
'camellia-256-cfb',
|
|
|
|
'rc4',
|
|
|
|
'rc4-md5',
|
|
|
|
'bf-cfb',
|
|
|
|
'salsa20',
|
|
|
|
'chacha20',
|
|
|
|
'chacha20-ietf',
|
|
|
|
'chacha20-ietf-poly1305',
|
|
|
|
'xchacha20-ietf-poly1305',
|
|
|
|
],
|
|
|
|
'ss-libev-legacy': [
|
|
|
|
'aes-128-ctr',
|
|
|
|
'aes-192-ctr',
|
|
|
|
'aes-256-ctr',
|
|
|
|
'aes-128-cfb',
|
|
|
|
'aes-192-cfb',
|
|
|
|
'aes-256-cfb',
|
|
|
|
'camellia-128-cfb',
|
|
|
|
'camellia-192-cfb',
|
|
|
|
'camellia-256-cfb',
|
|
|
|
'table',
|
|
|
|
'rc4',
|
|
|
|
'rc4-md5',
|
|
|
|
'rc2-cfb',
|
|
|
|
'bf-cfb',
|
|
|
|
'cast5-cfb',
|
|
|
|
'des-cfb',
|
|
|
|
'idea-cfb',
|
|
|
|
'seed-cfb',
|
|
|
|
'salsa20',
|
|
|
|
'chacha20',
|
|
|
|
'chacha20-ietf',
|
|
|
|
],
|
|
|
|
'ss-rust': [
|
|
|
|
'aes-128-gcm',
|
|
|
|
'aes-256-gcm',
|
|
|
|
'plain',
|
|
|
|
'none',
|
|
|
|
'chacha20-ietf-poly1305',
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
def __baseConfig(proxyInfo: dict, socksPort: int) -> dict: # 生成基本配置
|
|
|
|
config = {
|
|
|
|
'server': proxyInfo['server'],
|
|
|
|
'server_port': proxyInfo['port'],
|
|
|
|
'local_address': '127.0.0.1',
|
|
|
|
'local_port': socksPort,
|
|
|
|
'method': proxyInfo['method'],
|
|
|
|
'password': proxyInfo['passwd'],
|
|
|
|
}
|
|
|
|
if proxyInfo['plugin'] is not None: # 带插件
|
|
|
|
config['plugin'] = proxyInfo['plugin']['type']
|
|
|
|
config['plugin_opts'] = proxyInfo['plugin']['param']
|
|
|
|
return config
|
|
|
|
|
|
|
|
def __pluginWithUdp(plugin: str, pluginParam: str) -> bool: # 插件是否使用UDP通讯
|
|
|
|
if plugin in ['obfs-local', 'simple-tls', 'ck-client', 'gq-client',
|
|
|
|
'mtt-client', 'rabbit-plugin', 'gun-plugin']: # 不使用UDP通讯的插件
|
|
|
|
return False
|
|
|
|
if plugin in ['v2ray-plugin', 'xray-plugin', 'gost-plugin']:
|
|
|
|
if 'mode=quic' not in pluginParam.split(';'): # 非quic模式不使用UDP通讯
|
|
|
|
return False
|
|
|
|
return True # 默认假定占用UDP
|
|
|
|
|
|
|
|
def __ssPython(proxyInfo: dict, socksPort: int,
|
|
|
|
isUdp: bool, isLegacy: bool = False) -> tuple[dict, str]: # ss-python配置生成
|
|
|
|
config = __baseConfig(proxyInfo, socksPort)
|
|
|
|
mbedtlsMethods = [
|
|
|
|
'aes-128-cfb128',
|
|
|
|
'aes-192-cfb128',
|
|
|
|
'aes-256-cfb128',
|
|
|
|
'camellia-128-cfb128',
|
|
|
|
'camellia-192-cfb128',
|
|
|
|
'camellia-256-cfb128',
|
|
|
|
]
|
|
|
|
if not isLegacy: # 新版本特性
|
|
|
|
if config['method'] in mbedtlsMethods: # mbedtls库流加密
|
|
|
|
config['method'] = 'mbedtls:' + config['method']
|
|
|
|
if config['method'] in ['idea-cfb', 'seed-cfb']: # 仅openssl旧版本支持
|
|
|
|
config['extra_opts'] = '--libopenssl=libcrypto.so.1.0.0'
|
|
|
|
if not isUdp:
|
|
|
|
config['no_udp'] = True # 关闭UDP代理
|
|
|
|
config['shadowsocks'] = 'ss-python-legacy-local' if isLegacy else 'ss-python-local'
|
|
|
|
return config, 'ss-bootstrap-local'
|
|
|
|
|
|
|
|
def __ssLibev(proxyInfo: dict, socksPort: int,
|
|
|
|
isUdp: bool, isLegacy: bool = False) -> tuple[dict, str]: # ss-libev配置生成
|
|
|
|
config = __baseConfig(proxyInfo, socksPort)
|
|
|
|
if isUdp:
|
|
|
|
config['mode'] = 'tcp_and_udp'
|
|
|
|
return config, 'ss-libev-legacy-local' if isLegacy else 'ss-libev-local'
|
|
|
|
|
|
|
|
def __ssRust(proxyInfo: dict, socksPort: int, isUdp: bool) -> tuple[dict, str]: # ss-rust配置生成
|
|
|
|
config = __baseConfig(proxyInfo, socksPort)
|
|
|
|
if isUdp:
|
|
|
|
config['mode'] = 'tcp_and_udp'
|
|
|
|
return config, 'ss-rust-local'
|
|
|
|
|
|
|
|
def __ssFormatCheck(proxyInfo: dict) -> bool: # Shadowsocks参数检查
|
|
|
|
if 'server' not in proxyInfo or not isinstance(proxyInfo['server'], str): # server -> str
|
|
|
|
return False
|
|
|
|
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'] # plugin -> dict / None
|
|
|
|
if isinstance(plugin, dict):
|
|
|
|
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
|
|
|
|
isUdp = True
|
|
|
|
else:
|
|
|
|
isUdp = not __pluginWithUdp( # 获取插件UDP冲突状态
|
|
|
|
proxyInfo['plugin']['type'], proxyInfo['plugin']['param']
|
|
|
|
)
|
|
|
|
if proxyInfo['method'] in ssMethodList['ss-libev']: # 按序匹配客户端
|
|
|
|
config, ssFile = __ssLibev(proxyInfo, socksPort, isUdp)
|
|
|
|
elif proxyInfo['method'] in ssMethodList['ss-libev-legacy']:
|
|
|
|
config, ssFile = __ssLibev(proxyInfo, socksPort, isUdp, isLegacy = True)
|
|
|
|
elif proxyInfo['method'] in ssMethodList['ss-python']:
|
|
|
|
config, ssFile = __ssPython(proxyInfo, socksPort, isUdp)
|
|
|
|
elif proxyInfo['method'] in ssMethodList['ss-python-legacy']:
|
|
|
|
config, ssFile = __ssPython(proxyInfo, socksPort, isUdp, isLegacy = True)
|
|
|
|
elif proxyInfo['method'] in ssMethodList['ss-rust']:
|
|
|
|
config, ssFile = __ssRust(proxyInfo, socksPort, isUdp)
|
|
|
|
else:
|
|
|
|
return None, None, None # 无匹配加密方式
|
|
|
|
return [ssFile, '-c', configFile], json.dumps(config), {}
|