diff --git a/ProxyFilter/V2ray.py b/ProxyFilter/V2ray.py new file mode 100644 index 0000000..189bc76 --- /dev/null +++ b/ProxyFilter/V2ray.py @@ -0,0 +1,234 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- + +from ProxyFilter import baseFunc + +udpObfsList = [ + 'none', + 'srtp', + 'utp', + 'wechat-video', + 'dtls', + 'wireguard' +] + +quicMethodList = [ + 'none', + 'aes-128-gcm', + 'chacha20-poly1305', +] + +v2rayStreamRules = { + 'tcpObject': { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda streamType: streamType == 'tcp', + 'errMsg': 'Unexpected stream type' + }, + 'obfs': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': 'obfsObject' + }, + 'secure': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': 'secureObject' + } + }, + 'kcpObject': { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda streamType: streamType == 'kcp', + 'errMsg': 'Unexpected stream type' + }, + 'seed': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': baseFunc.toStr + }, + 'obfs': { + 'optional': False, + 'default': 'none', + 'type': str, + 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), + 'filter': lambda obfs: obfs in udpObfsList, + 'errMsg': 'Unknown mKCP obfs method' + }, + 'secure': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': 'secureObject' + } + }, + 'wsObject': { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda streamType: streamType == 'ws', + 'errMsg': 'Unexpected stream type' + }, + 'host': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'path': { + 'optional': False, + 'default': '/', + 'type': str, + 'format': baseFunc.toStr + }, + 'ed': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': int, + 'format': baseFunc.toInt, + 'filter': lambda ed: ed > 0, + 'errMsg': 'Illegal Max-Early-Data length' + }, + 'secure': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': 'secureObject' + } + }, + 'h2Object': { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda streamType: streamType == 'h2', + 'errMsg': 'Unexpected stream type' + }, + 'host': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'path': { + 'optional': False, + 'default': '/', + 'type': str, + 'format': baseFunc.toStr + }, + 'secure': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': 'secureObject' + } + }, + 'quicObject': { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda streamType: streamType == 'quic', + 'errMsg': 'Unexpected stream type' + }, + 'method': { + 'optional': False, + 'default': 'none', + 'type': str, + 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), + 'filter': lambda method: method in quicMethodList, + 'errMsg': 'Unknown QUIC method' + }, + 'passwd': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'obfs': { + 'optional': False, + 'default': 'none', + 'type': str, + 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), + 'filter': lambda obfs: obfs in udpObfsList, + 'errMsg': 'Unknown QUIC obfs method' + }, + 'secure': { + 'optional': False, + 'default': {}, + 'type': 'secureObject' + } + }, + 'grpcObject': { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda streamType: streamType == 'grpc', + 'errMsg': 'Unexpected stream type' + }, + 'service': { + 'optional': True, + 'type': str, + 'format': baseFunc.toStr + }, + 'secure': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': 'secureObject' + } + }, + 'obfsObject': { + 'host': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'path': { + 'optional': False, + 'default': '/', + 'type': str, + 'format': baseFunc.toStr + } + }, + 'secureObject': { + 'sni': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'alpn': { + 'optional': False, + 'default': 'h2,http/1.1', + 'type': str, + 'format': baseFunc.toStrTidy, + 'filter': lambda alpn: alpn in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Illegal alpn option' + }, + 'verify': { + 'optional': False, + 'default': True, + 'type': bool, + 'format': baseFunc.toBool + } + } +} diff --git a/ProxyFilter/VLESS.py b/ProxyFilter/VLESS.py new file mode 100644 index 0000000..6b3c694 --- /dev/null +++ b/ProxyFilter/VLESS.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- + +from ProxyFilter import baseFunc +from ProxyFilter import Xray + +vlessMethodList = ['none'] + +vlessFilterRules = { + 'rootObject': { + 'remark': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'server': { + 'optional': True, + 'type': str, + 'format': baseFunc.toStrTidy, + 'filter': baseFunc.isHost, + 'errMsg': 'Illegal server address' + }, + 'port': { + 'optional': True, + 'type': int, + 'format': baseFunc.toInt, + 'filter': baseFunc.isPort, + 'errMsg': 'Illegal port number' + }, + 'method': { + 'optional': False, + 'default': 'none', + 'type': str, + 'format': baseFunc.toStrTidy, + 'filter': lambda method: method in vlessMethodList, + 'errMsg': 'Unknown VLESS method' + }, + 'id': { + 'optional': True, + 'type': str, + 'format': baseFunc.toStr + }, + 'stream': { + 'optional': False, + 'default': { + 'type': 'tcp' + }, + 'type': [ + 'tcpObject', + 'kcpObject', + 'wsObject', + 'h2Object', + 'quicObject', + 'grpcObject', + ] + } + } +} + +def vlessFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: + """ + VLESS节点合法性检查 + + 不合法: + return False, {reason} + + 合法: + return True, { + 'type': 'vless', + ... + } + """ + try: + if not isExtra: # 去除非必要参数 + vlessFilterRules['rootObject'].pop('remark') + for key, obj in Xray.xrayStreamRules.items(): # xray.stream -> vless + vlessFilterRules[key] = obj + status, result = baseFunc.ruleFilter(rawInfo, vlessFilterRules, { + 'type': 'vless' + }) + if not status: # 节点格式错误 + return False, result + stream = result['stream'] + if stream['secure'] is not None and stream['secure']['sni'] == '': # 未指定SNI + if stream['type'] == 'tcp' and stream['obfs'] is not None: + stream['secure']['sni'] = stream['obfs']['host'].split(',')[0] + elif stream['type'] == 'ws': + stream['secure']['sni'] = stream['host'] + elif stream['type'] == 'h2': + stream['secure']['sni'] = stream['host'].split(',')[0] + return True, result + except: + return False, 'Unknown error' diff --git a/ProxyFilter/VMess.py b/ProxyFilter/VMess.py index 866f455..35d58cc 100644 --- a/ProxyFilter/VMess.py +++ b/ProxyFilter/VMess.py @@ -2,6 +2,7 @@ # -*- coding:utf-8 -*- from ProxyFilter import baseFunc +from ProxyFilter import V2ray vmessMethodList = [ 'aes-128-gcm', @@ -11,21 +12,6 @@ vmessMethodList = [ 'zero', ] -udpObfsList = [ - 'none', - 'srtp', - 'utp', - 'wechat-video', - 'dtls', - 'wireguard' -] - -quicMethodList = [ - 'none', - 'aes-128-gcm', - 'chacha20-poly1305', -] - vmessFilterRules = { 'rootObject': { 'remark': { @@ -83,218 +69,6 @@ vmessFilterRules = { 'grpcObject', ] } - }, - 'tcpObject': { - 'type': { - 'optional': True, - 'type': str, - 'indexKey': True, - 'format': baseFunc.toStrTidy, - 'filter': lambda streamType: streamType == 'tcp', - 'errMsg': 'Unexpected stream type' - }, - 'obfs': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'obfsObject' - }, - 'secure': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'secureObject' - } - }, - 'kcpObject': { - 'type': { - 'optional': True, - 'type': str, - 'indexKey': True, - 'format': baseFunc.toStrTidy, - 'filter': lambda streamType: streamType == 'kcp', - 'errMsg': 'Unexpected stream type' - }, - 'seed': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'obfs': { - 'optional': False, - 'default': 'none', - 'type': str, - 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), - 'filter': lambda obfs: obfs in udpObfsList, - 'errMsg': 'Unknown mKCP obfs method' - }, - 'secure': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'secureObject' - } - }, - 'wsObject': { - 'type': { - 'optional': True, - 'type': str, - 'indexKey': True, - 'format': baseFunc.toStrTidy, - 'filter': lambda streamType: streamType == 'ws', - 'errMsg': 'Unexpected stream type' - }, - 'host': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'path': { - 'optional': False, - 'default': '/', - 'type': str, - 'format': baseFunc.toStr - }, - 'ed': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': int, - 'format': baseFunc.toInt, - 'filter': lambda ed: ed > 0, - 'errMsg': 'Illegal Max-Early-Data length' - }, - 'secure': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'secureObject' - } - }, - 'h2Object': { - 'type': { - 'optional': True, - 'type': str, - 'indexKey': True, - 'format': baseFunc.toStrTidy, - 'filter': lambda streamType: streamType == 'h2', - 'errMsg': 'Unexpected stream type' - }, - 'host': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'path': { - 'optional': False, - 'default': '/', - 'type': str, - 'format': baseFunc.toStr - }, - 'secure': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'secureObject' - } - }, - 'quicObject': { - 'type': { - 'optional': True, - 'type': str, - 'indexKey': True, - 'format': baseFunc.toStrTidy, - 'filter': lambda streamType: streamType == 'quic', - 'errMsg': 'Unexpected stream type' - }, - 'method': { - 'optional': False, - 'default': 'none', - 'type': str, - 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), - 'filter': lambda method: method in quicMethodList, - 'errMsg': 'Unknown QUIC method' - }, - 'passwd': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'obfs': { - 'optional': False, - 'default': 'none', - 'type': str, - 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), - 'filter': lambda obfs: obfs in udpObfsList, - 'errMsg': 'Unknown QUIC obfs method' - }, - 'secure': { - 'optional': False, - 'default': {}, - 'type': 'secureObject' - } - }, - 'grpcObject': { - 'type': { - 'optional': True, - 'type': str, - 'indexKey': True, - 'format': baseFunc.toStrTidy, - 'filter': lambda streamType: streamType == 'grpc', - 'errMsg': 'Unexpected stream type' - }, - 'service': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'secure': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'secureObject' - } - }, - 'obfsObject': { - 'host': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'path': { - 'optional': False, - 'default': '/', - 'type': str, - 'format': baseFunc.toStr - } - }, - 'secureObject': { - 'sni': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'alpn': { - 'optional': False, - 'default': 'h2,http/1.1', - 'type': str, - 'format': baseFunc.toStrTidy, - 'filter': lambda alpn: alpn in ['h2', 'http/1.1', 'h2,http/1.1'], - 'errMsg': 'Illegal alpn option' - }, - 'verify': { - 'optional': False, - 'default': True, - 'type': bool, - 'format': baseFunc.toBool - } } } @@ -314,6 +88,8 @@ def vmessFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: try: if not isExtra: # 去除非必要参数 vmessFilterRules['rootObject'].pop('remark') + for key, obj in V2ray.v2rayStreamRules.items(): # v2ray.stream -> vmess + vmessFilterRules[key] = obj status, result = baseFunc.ruleFilter(rawInfo, vmessFilterRules, { 'type': 'vmess' }) diff --git a/ProxyFilter/Xray.py b/ProxyFilter/Xray.py new file mode 100644 index 0000000..900c16b --- /dev/null +++ b/ProxyFilter/Xray.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- + +from ProxyFilter import baseFunc +from ProxyFilter import V2ray + +xrayFlowList = [ + 'xtls-origin', + 'xtls-direct', + 'xtls-splice', +] + +def testFunc(raw): + print(raw) + return False + +xrayStreamRules = V2ray.v2rayStreamRules +xrayStreamRules.pop('secureObject') +xrayStreamRules['tcpObject']['secure']['type'] = ['tlsObject', 'xtlsObject'] +xrayStreamRules['kcpObject']['secure']['type'] = ['tlsObject', 'xtlsObject'] +xrayStreamRules['wsObject']['secure']['type'] = 'tlsObject' +xrayStreamRules['h2Object']['secure']['type'] = 'tlsObject' +xrayStreamRules['quicObject']['secure']['type'] = 'tlsObject' +xrayStreamRules['grpcObject']['secure']['type'] = 'tlsObject' + +xrayStreamRules['tlsObject'] = { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda secureType: secureType == 'tls', + 'errMsg': 'Unexpected secure type' + }, + 'sni': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'alpn': { + 'optional': False, + 'default': 'h2,http/1.1', + 'type': str, + 'format': baseFunc.toStrTidy, + 'filter': lambda alpn: alpn in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Illegal alpn option' + }, + 'verify': { + 'optional': False, + 'default': True, + 'type': bool, + 'format': baseFunc.toBool + } +} + +xrayStreamRules['xtlsObject'] = { + 'type': { + 'optional': True, + 'type': str, + 'indexKey': True, + 'format': baseFunc.toStrTidy, + 'filter': lambda secureType: secureType == 'xtls', + 'errMsg': 'Unexpected secure type' + }, + 'sni': { + 'optional': False, + 'default': '', + 'type': str, + 'format': baseFunc.toStr + }, + 'alpn': { + 'optional': False, + 'default': 'h2,http/1.1', + 'type': str, + 'format': baseFunc.toStrTidy, + 'filter': lambda alpn: alpn in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Illegal alpn option' + }, + 'verify': { + 'optional': False, + 'default': True, + 'type': bool, + 'format': baseFunc.toBool + }, + 'flow': { + 'optional': False, + 'default': 'xtls-direct', + 'type': str, + 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), + 'filter': lambda flow: flow in xrayFlowList, + 'errMsg': 'Unknown XTLS flow method' + }, + 'udp443': { + 'optional': False, + 'default': False, + 'type': bool, + 'format': baseFunc.toBool + } +} diff --git a/ProxyFilter/baseFunc.py b/ProxyFilter/baseFunc.py index a1ed2d1..9f4d6dd 100644 --- a/ProxyFilter/baseFunc.py +++ b/ProxyFilter/baseFunc.py @@ -147,7 +147,9 @@ def __dictCheck(data: dict, objectList: dict, limitRules: dict, keyPrefix: str) subResult = __dictCheck(dataValue, objectList, objectList[valueType], keyName) # 尝试检查子对象 except filterException as reason: errMsg = str(reason) # 捕获抛出信息 - except: + except Exception as reason: + if str(reason)[:10] == 'index-key:': # index-key匹配错误 + errMsg = str(reason)[10:] continue else: # 子对象匹配成功 break @@ -161,19 +163,18 @@ def __dictCheck(data: dict, objectList: dict, limitRules: dict, keyPrefix: str) else: # 检查无误 result[key] = dataValue - if result[key] is not None: # allowNone为False - if 'filter' in option: - errFlag = False - try: - if not option['filter'](result[key]): # 格式检查 - errFlag = True - except: - raise filterException('Filter error in `' + keyName + '`') - else: - if errFlag: - if 'indexKey' in option and option['indexKey']: - raise Exception('Filter index key') - raise filterException(option['errMsg']) + if result[key] is not None and 'filter' in option: # 值不为None且有检查函数 + errFlag = False + try: + if not option['filter'](result[key]): # 格式检查 + errFlag = True + except: + raise filterException('Filter error in `' + keyName + '`') + else: + if errFlag: + if 'indexKey' in option and option['indexKey']: + raise Exception('index-key:' + option['errMsg']) + raise filterException(option['errMsg']) return result diff --git a/ProxyFilter/filter.py b/ProxyFilter/filter.py index 198a0a5..c95e14d 100644 --- a/ProxyFilter/filter.py +++ b/ProxyFilter/filter.py @@ -4,6 +4,7 @@ from ProxyFilter import Shadowsocks from ProxyFilter import ShadowsocksR from ProxyFilter import VMess +from ProxyFilter import VLESS def filte(raw: dict, isExtra: bool = False) -> tuple[bool, str or dict]: """ @@ -28,6 +29,8 @@ def filte(raw: dict, isExtra: bool = False) -> tuple[bool, str or dict]: return ShadowsocksR.ssrFilter(raw, isExtra) elif raw['type'] == 'vmess': return VMess.vmessFilter(raw, isExtra) + elif raw['type'] == 'vless': + return VLESS.vlessFilter(raw, isExtra) else: return False, 'Unknown proxy type' except: diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..fad5101 --- /dev/null +++ b/demo.py @@ -0,0 +1,26 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- + +import ProxyFilter as Filter + +info = { + 'type': 'vless', + 'server': '127.0.0.1', + 'port': '12345', + 'id': 'dnomd343', + 'stream': { + 'type': 'grpc', + 'service': 'dnomd343', + 'secure': { + 'type': 'tls', + 'sni': '', + 'flow': 'xtls-origin', + 'udp443': True + } + } +} + +ret = Filter.filte(info) + +print(ret[0]) +print(ret[1])