From b5f0ecf1ae24ee71cb8061dbd9e5a2197359e37b Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Tue, 22 Feb 2022 19:56:59 +0800 Subject: [PATCH] feat: new filter engine --- ProxyFilter/Plugin.py | 3 + ProxyFilter/Shadowsocks.py | 189 ++++++++++++------------------------- ProxyFilter/baseFunc.py | 59 ++++++++++++ 3 files changed, 120 insertions(+), 131 deletions(-) diff --git a/ProxyFilter/Plugin.py b/ProxyFilter/Plugin.py index 51ed285..41ca8cc 100644 --- a/ProxyFilter/Plugin.py +++ b/ProxyFilter/Plugin.py @@ -112,6 +112,9 @@ pluginAlias = { # 插件别名 ] } +def isPlugin(plugin: str) -> bool: # 插件是否合法 + return plugin in pluginList + def pluginFormat(plugin: str) -> str: # 插件格式化 plugin = plugin.replace('_', '-').lower().strip() if plugin not in pluginList: # 非标插件名 diff --git a/ProxyFilter/Shadowsocks.py b/ProxyFilter/Shadowsocks.py index 21a57ad..f97d0fa 100644 --- a/ProxyFilter/Shadowsocks.py +++ b/ProxyFilter/Shadowsocks.py @@ -1,8 +1,8 @@ #!/usr/bin/python # -*- coding:utf-8 -*- +from ProxyFilter import Plugin from ProxyFilter import baseFunc -from ProxyFilter import Plugin as sip003 ssMethodList = [ # Shadowsocks加密方式 'aes-128-gcm', @@ -56,94 +56,60 @@ ssMethodList = [ # Shadowsocks加密方式 'xchacha20-ietf-poly1305' ] -pluginList = [ # SIP003插件列表 - 'obfs-local', - 'simple-tls', - 'v2ray-plugin', - 'xray-plugin', - 'kcptun-client', - 'gost-plugin', - 'ck-client', - 'gq-client', - 'mtt-client', - 'rabbit-plugin', - 'qtun-client', - 'gun-plugin' -] - -def __ssFill(raw: dict) -> dict: # 补全可选值 - try: - if 'plugin' not in raw: - raw['plugin'] = None - if raw['plugin'] is not None: - if 'param' not in raw['plugin']: - raw['plugin']['param'] = '' - except: - pass - return raw - -def __ssFormat(raw: dict) -> dict: # 容错性格式化 - try: - raw['server'] = raw['server'].strip() - raw['port'] = int(raw['port']) - raw['method'] = raw['method'].replace('_', '-').lower().strip() - if isinstance(raw['plugin'], str): - raw['plugin'] = raw['plugin'].strip() - if raw['plugin'] == '': - raw['plugin'] = None - else: - raw['plugin'] = { - 'type': raw['plugin'], - 'param': '' - } - if raw['plugin'] is not None: - if isinstance(raw['plugin']['type'], str): - raw['plugin']['type'] = raw['plugin']['type'].strip() - if raw['plugin']['type'] in [None, '']: - raw['plugin'] = None - else: - raw['plugin']['type'] = sip003.pluginFormat(raw['plugin']['type']) - except: - pass - return raw - -def __ssParamCheck(raw: dict) -> tuple[bool, str or None]: # 参数检查 - try: - if 'server' not in raw: - return False, 'Missing `server` option' - if 'port' not in raw: - return False, 'Missing `port` option' - if 'method' not in raw: - return False, 'Missing `method` option' - if 'passwd' not in raw: - return False, 'Missing `passwd` option' - if 'plugin' not in raw: - return False, 'Missing `plugin` option' - if raw['plugin'] is not None: - if 'type' not in raw['plugin']: - return False, 'Missing `plugin.type` option' - if 'param' not in raw['plugin']: - return False, 'Missing `plugin.param` option' - - if not isinstance(raw['server'], str): - return False, 'Illegal `server` option' - if not isinstance(raw['port'], int): - return False, 'Illegal `int` option' - if not isinstance(raw['method'], str): - return False, 'Illegal `method` option' - if not isinstance(raw['passwd'], str): - return False, 'Illegal `passwd` option' - if (raw['plugin'] is not None) and (not isinstance(raw['plugin'], dict)): - return False, 'Illegal `plugin` option' - - if raw['plugin'] is not None: - if not isinstance(raw['plugin']['type'], str): - return False, 'Illegal `plugin.type` option' - if not isinstance(raw['plugin']['param'], str): - return False, 'Illegal `plugin.param` option' - except: - return False, 'Unknown error' - return True, None +ssFilterRules = { + 'rootObject': { + 'remark': { + 'optional': False, + 'default': '', + 'type': str + }, + 'server': { + 'optional': True, + 'type': str, + 'format': lambda s: s.lower().strip(), + 'filter': baseFunc.isHost, + 'errMsg': 'Illegal server address' + }, + 'port': { + 'optional': True, + 'type': int, + 'format': lambda i: int(i), + 'filter': baseFunc.isPort, + 'errMsg': 'Illegal port number' + }, + 'method': { + 'optional': True, + 'type': str, + 'format': lambda s: s.replace('_', '-').lower().strip(), + 'filter': lambda method: method in ssMethodList, + 'errMsg': 'Unknown Shadowsocks method' + }, + 'passwd': { + 'optional': True, + 'type': str + }, + 'plugin': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': 'pluginObject' + } + }, + 'pluginObject': { + 'type': { + 'optional': True, + 'type': str, + 'format': Plugin.pluginFormat, + 'filter': Plugin.isPlugin, + 'errMsg': 'Unknown SIP003 plugin' + }, + 'param': { + 'optional': False, + 'default': '', + 'type': str + } + } +} def ssFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: """ @@ -159,47 +125,8 @@ def ssFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: } """ try: - raw = rawInfo - raw = __ssFormat(__ssFill(raw)) # 预处理 - status, reason = __ssParamCheck(raw) # 参数检查 - if not status: # 参数有误 - return False, reason - - result = {'type': 'ss'} - if isExtra: # 携带额外参数 - if 'remark' not in raw: # 补全默认值 - raw['remark'] = '' - if raw['remark'] is None: # 容错格式化 - raw['remark'] = '' - if not isinstance(raw['remark'], str): # 参数检查 - return False, 'Illegal `remark` option' - result['remark'] = raw['remark'] - - if baseFunc.isHost(raw['server']): - result['server'] = raw['server'] # server - else: - return False, 'Illegal `server` option' - if baseFunc.isPort(raw['port']): - result['port'] = raw['port'] # port - else: - return False, 'Illegal `port` option' - if raw['method'] in ssMethodList: - result['method'] = raw['method'] # method - else: - return False, 'Unknown Shadowsocks method' - result['passwd'] = raw['passwd'] # passwd - - if raw['plugin'] is None: - plugin = None - else: - if raw['plugin']['type'] in pluginList: - plugin = { - 'type': raw['plugin']['type'], - 'param': raw['plugin']['param'] - } - else: - return False, 'Unknown sip003 plugin' - result['plugin'] = plugin + if not isExtra: + ssFilterRules['rootObject'].pop('remark') + return baseFunc.rulesFilter(rawInfo, ssFilterRules) except: return False, 'Unknown error' - return True, result diff --git a/ProxyFilter/baseFunc.py b/ProxyFilter/baseFunc.py index e3e81b7..7a7e1d7 100644 --- a/ProxyFilter/baseFunc.py +++ b/ProxyFilter/baseFunc.py @@ -18,6 +18,8 @@ def isHost(host: str) -> bool: IPy.IP(host) if host.find('/') != -1: # filter CIDR return False + if host.find('.') == -1 and host.find(':') == -1: + return False return True # IP地址合法 except: pass @@ -44,3 +46,60 @@ def isPort(port: int) -> bool: except: # illegal pass return False + +class filterException(Exception): # 检测异常 + def __init__(self, reason): + self.reason = reason + +def __dictCheck(data: dict, objectList: dict, limitRules: dict, keyPrefix: str) -> dict: # 递归检查dict + result = {} + for key, option in limitRules.items(): # 遍历规则 + keyName = key if keyPrefix == '' else keyPrefix + '.' + key + + # 检查必选key 补全可选key + if key not in data: + if option['optional']: # 必选 + raise filterException('Missing `' + keyName + '` option') # 必选值缺失 + else: # 可选 + data[key] = option['default'] # 补全可选值 + + if 'format' in option: # 预处理 + data[key] = option['format'](data[key]) + + # 检查value类型 + if 'allowNone' in option and option['allowNone'] and data[key] is None: # 允许为None且值为None + result[key] = None + else: + if isinstance(option['type'], str): + result[key] = __dictCheck(data[key], objectList, objectList[option['type']], keyName) # 检查子对象 + elif not isinstance(data[key], option['type']): # 类型不匹配 + raise filterException('Illegal `' + keyName + '` option') + else: + result[key] = data[key] + + if 'filter' in option and not option['filter'](data[key]): # 格式检查 + raise filterException(option['errMsg']) + else: + result[key] = data[key] + return result + +def rulesFilter(rawData: dict, rulesList: dict) -> tuple[bool, dict or str]: + """ + 规则参数 + optional -> 必选 + default -> optional为False时必选 + type -> 必选 + allowNone -> 可选 + format -> 可选 + filter -> 可选 + errMsg -> filter存在时必选 + """ + data = rawData + try: + data = __dictCheck(data, rulesList, rulesList['rootObject'], '') # 开始检查 + except filterException as reason: # 节点格式错误 + return False, str(reason) + except: + return False, 'Filter error' + else: + return True, data