diff --git a/Basis/Api.py b/Basis/Api.py index 9cc84d9..506c5c3 100644 --- a/Basis/Api.py +++ b/Basis/Api.py @@ -15,17 +15,20 @@ webApi = Flask(__name__) # init flask server def formatProxy(raw: str or dict) -> dict: - from ProxyFilter import filte + from Filter import Filter from ProxyDecoder import decode if type(raw) == str: raw = decode(raw) if raw is None: raise RuntimeError('decode error') - print(raw) - status, raw = filte(raw, isExtra = True) - if not status: + try: + return { + 'type': raw['type'], + 'name': raw['info']['remark'] if 'remark' in raw['info'] else '', + 'info': Filter(raw['type'], raw['info']) + } + except: raise RuntimeError('filter error') - return raw def jsonResponse(data: dict) -> Response: # return json mime diff --git a/Basis/Filter.py b/Basis/Filter.py new file mode 100644 index 0000000..fdc81a5 --- /dev/null +++ b/Basis/Filter.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy + +filterObject = { + 'optional': { + 'type': bool, + 'optional': True, # `optional` is not force require + 'default': False, # disable `optional` option in default + 'allowNone': False, # `optional` couldn't be None + }, + 'default': { + 'type': any, # skip type check + 'optional': True, # `default` is not force require + 'default': None, + 'allowNone': True, # `default` can be None + }, + 'allowNone': { + 'type': bool, + 'optional': True, # `allowNone` is not force require + 'default': False, # disable `allowNone` option in default + 'allowNone': False, # `allowNone` couldn't be None + }, + 'type': { + 'type': [any, type, list, dict], + 'optional': False, # `type` is force require + 'allowNone': False, # `type` couldn't be None + }, + 'multiSub': { + 'type': bool, + 'optional': True, # `multiSub` is not force require + 'default': False, # disable `multiSub` option in default + 'allowNone': False, # `multiSub` couldn't be None + }, + 'indexKey': { + 'type': str, + 'optional': True, # `indexKey` is not force require + 'default': 'type', + 'allowNone': False, # `indexKey` couldn't be None + }, + 'format': { + 'type': any, + 'optional': True, # `format` is not force require + 'default': lambda x: x, # don't change anything + 'allowNone': False, # `format` couldn't be None + }, + 'filter': { + 'type': any, + 'optional': True, # `filter` is not force require + 'default': lambda x: True, # always pass filter + 'allowNone': False, # `filter` couldn't be None + }, + 'errMsg': { + 'type': str, + 'optional': True, # `errMsg` is not force require + 'default': 'Filter error', + 'allowNone': False, # `errMsg` couldn't be None + } +} + +for field in filterObject: + filterObject[field]['errMsg'] = 'Invalid `%s` key' % field + filterObject[field]['format'] = filterObject['format']['default'] # return same value + filterObject[field]['filter'] = filterObject['filter']['default'] # always return True + + +def Filter(raw: dict, rules: dict) -> dict: + if type(raw) != dict: + raise RuntimeError('Invalid input for filter') + data = {} + raw = copy.deepcopy(raw) + rules = copy.deepcopy(rules) + for key, rule in rules.items(): + # pretreatment process (raw --[copy / default value]--> data) + if key not in raw: # key not exist + if not rule['optional']: # force require key not exist + raise RuntimeError('Missing `%s` field' % key) + data[key] = rule['default'] # set default value + else: # key exist + data[key] = raw[key] + # format process (data --[format]--> data) + if data[key] is None: # key content is None + if not rule['allowNone']: # key is not allow None + raise RuntimeError('Field `%s` shouldn\'t be None' % key) + continue # skip following process + try: + data[key] = rule['format'](data[key]) # run format + except: + raise RuntimeError(rule['errMsg']) # format error + # filter process (data --[type check (& filter check)]--> pass / non-pass) + if type(rule['type']) == type: # str / int / bool / ... + rule['type'] = [rule['type']] # str -> [str] / int -> [int] / ... + if type(rule['type']) == list: # [str, int, bool, ...] + if data[key] == any and any in rule['type']: # special case -> skip type filter + pass + elif type(data[key]) not in rule['type']: # type not in allow list + raise RuntimeError('Invalid `%s` field' % key) + elif type(rule['type']) == dict: # check subObject + if type(data[key]) != dict: + raise RuntimeError('Invalid sub object in `%s`' % key) # subObject content should be dict + if not rule['multiSub']: # single subObject + subRules = rule['type'] + else: # multi subObject + if rule['indexKey'] not in data[key]: # confirm index key exist + raise RuntimeError('Index key `%s` not found in `%s`' % (rule['indexKey'], key)) + subType = data[key][rule['indexKey']].lower() + if subType not in rule['type']: # confirm subObject rule exist + raise RuntimeError('Unknown index `%s` in key `%s`' % (subType, key)) + subRules = rule['type'][subType] + try: + data[key] = Filter(data[key], subRules) + except RuntimeError as exp: + raise RuntimeError('%s (in `%s`)' % (exp, key)) # add located info + continue + elif rule['type'] != any: # type == any -> skip type filter + raise RuntimeError('Unknown `type` in rules') + if not rule['filter'](data[key]): # run filter + raise RuntimeError(rule['errMsg']) + return data + + +def rulesFilter(rules: dict) -> dict: + result = {} + for key, rule in rules.items(): # filter by basic rules + result[key] = Filter(rule, filterObject) + return result + + +filterObject = rulesFilter(filterObject) # self-format diff --git a/Basis/Functions.py b/Basis/Functions.py index ffb0565..daefe4f 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import re import time import uuid import psutil @@ -10,8 +11,38 @@ from IPy import IP from Basis.Logger import logging +def isIpAddr(ipAddr: str) -> bool: + try: + if '/' in ipAddr: # filter CIDR + return False + if '.' not in ipAddr and ':' not in ipAddr: # not IPv4 or IPv6 + return False + IP(ipAddr) # try to convert to IP address + return True # valid IP address + except: + return False + + +def isDomain(domain: str) -> bool: + try: + domainRegex = r'^(?=^.{3,255}$)[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62}(\.[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62})+$' + return re.search(domainRegex, domain) is not None # regex matching + except: # unexpected error + return False + + +def isHost(host: str) -> bool: + return isIpAddr(host) or isDomain(host) # IPv4 / IPv6 / Domain + + +def isPort(port: int) -> bool: + if type(port) != int: + return False + return port in range(1, 65536) # 1 ~ 65535 + + def md5Sum(data: str, encode: str = 'utf-8') -> str: - return hashlib.md5(data.encode(encoding = encode)).hexdigest() + return hashlib.md5(data.encode(encoding = encode)).hexdigest() # MD5 hash def hostFormat(host: str, v6Bracket: bool = False) -> str: @@ -42,6 +73,40 @@ def genUUID() -> str: # generate uuid v5 )) +def toInt(raw) -> int: + try: + return int(raw) + except: + raise RuntimeError('Unable convert to int') + + +def toStr(raw) -> str: + if raw is None: + raise RuntimeError('None could not convert to str') + if isinstance(raw, bytes): # bytes -> str + return str(raw, encoding = 'utf-8') + try: + return str(raw) + except: + raise RuntimeError('Unable convert to str') + + +def toStrTidy(raw) -> str: + return toStr(raw).strip().lower() # with trim and lower + + +def toBool(raw) -> bool: + if isinstance(raw, (bool, int, float)): + return bool(raw) + try: + raw = toStr(raw).strip().lower() + if raw in ['true', 'false']: + return True if raw == 'true' else False + return int(raw) != 0 + except: + raise RuntimeError('Unable convert to bool') + + def getAvailablePort(rangeStart: int = 1024, rangeEnd: int = 65535, waitTime: int = 10) -> int: # get available port if rangeStart > rangeEnd or rangeStart < 1 or rangeEnd > 65535: raise RuntimeError('Invalid port range') diff --git a/Dockerfile b/Dockerfile index cf82d47..699f252 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,17 +6,6 @@ ARG GO17_IMG="golang:1.17-alpine3.16" ARG GO18_IMG="golang:1.18-alpine3.16" ARG PYTHON_IMG="python:3.10-alpine3.16" -# Compile upx (under gcc10) -FROM ${ALPINE_IMG} AS upx -ENV UPX_VERSION="3.96" -RUN sed -i 's/v3.\d\d/v3.15/' /etc/apk/repositories && \ - apk add bash build-base perl ucl-dev zlib-dev -RUN wget https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-src.tar.xz && \ - tar xf upx-${UPX_VERSION}-src.tar.xz -WORKDIR ./upx-${UPX_VERSION}-src/ -RUN make all && mkdir -p /upx/bin/ && mv ./src/upx.out /upx/bin/upx && \ - mkdir -p /upx/lib/ && cd /usr/lib/ && cp -d ./libgcc_s.so* ./libstdc++.so* ./libucl.so* /upx/lib/ - # Download build-base FROM ${ALPINE_IMG} AS build-base WORKDIR /apk/ @@ -24,6 +13,12 @@ RUN apk add build-base | grep -oE 'Installing \S+' | cut -b 12- > ./build-base RUN chmod +x ./build-base && cat ./build-base | xargs -n1 apk fetch && \ sed -i 's/^/ \/apk\/&/g;s/$/&-*.apk/g;1i\apk add' ./build-base && sed -i ':a;N;s/\n//g;ba' ./build-base +# Compile numpy +FROM ${PYTHON_IMG} AS numpy +WORKDIR /wheels/ +COPY --from=build-base /apk/ /apk/ +RUN /apk/build-base && pip wheel numpy + # Compile gevent FROM ${PYTHON_IMG} AS gevent WORKDIR /wheels/ @@ -31,12 +26,6 @@ RUN apk add libffi-dev COPY --from=build-base /apk/ /apk/ RUN /apk/build-base && pip wheel gevent -# Compile numpy -FROM ${PYTHON_IMG} AS numpy -WORKDIR /wheels/ -COPY --from=build-base /apk/ /apk/ -RUN /apk/build-base && pip wheel numpy - # Build python wheels FROM ${PYTHON_IMG} AS wheels WORKDIR /wheels/ @@ -46,6 +35,17 @@ RUN /apk/build-base && pip wheel colorlog flask IPy psutil pysocks requests sals COPY --from=gevent /wheels/*.whl /wheels/ COPY --from=numpy /wheels/*.whl /wheels/ +# Compile upx (under gcc10) +FROM ${ALPINE_IMG} AS upx +ENV UPX_VERSION="3.96" +RUN sed -i 's/v3.\d\d/v3.15/' /etc/apk/repositories && \ + apk add bash build-base perl ucl-dev zlib-dev +RUN wget https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-src.tar.xz && \ + tar xf upx-${UPX_VERSION}-src.tar.xz +WORKDIR ./upx-${UPX_VERSION}-src/ +RUN make -C ./src/ && mkdir -p /upx/bin/ && mv ./src/upx.out /upx/bin/upx && \ + mkdir -p /upx/lib/ && cd /usr/lib/ && cp -d ./libgcc_s.so* ./libstdc++.so* ./libucl.so* /upx/lib/ + # Compile shadowsocks-rust FROM ${RUST_IMG} AS ss-rust ENV SS_RUST="1.15.0-alpha.8" @@ -59,7 +59,7 @@ RUN cargo build --target-dir ./ --release --bin sslocal --bin ssserver \ mv ./release/sslocal /tmp/ss-rust-local && mv ./release/ssserver /tmp/ss-rust-server && \ strip /tmp/ss-rust-* COPY --from=upx /upx/ /usr/ -RUN upx -9 /tmp/ss-rust-* +RUN ls /tmp/ss-rust-* | xargs -P0 -n1 upx -9 # Compile shadowsocks-libev FROM ${ALPINE_IMG} AS ss-libev @@ -93,8 +93,8 @@ RUN sed -i 's/MutableMapping/abc.MutableMapping/' ./shadowsocks/lru_cache.py && mv ../../lib/ /packages/ssr-python/ # shadowsocks-python (latest version) WORKDIR ../shadowsocks/ -RUN git checkout master -RUN sed -i 's/if addr is/if addr ==/g' ./shadowsocks/common.py && \ +RUN git checkout master && \ + sed -i 's/if addr is/if addr ==/g' ./shadowsocks/common.py && \ sed -i 's/and ip is not/and ip !=/g' ./shadowsocks/common.py && \ sed -i 's/if len(block) is/if len(block) ==/g' ./shadowsocks/common.py && \ sed -i 's/MutableMapping/abc.MutableMapping/' ./shadowsocks/lru_cache.py && \ @@ -166,15 +166,15 @@ RUN git clone https://github.com/shadowsocks/qtun.git && \ WORKDIR ./simple-obfs/ RUN git submodule update --init --recursive && \ ./autogen.sh && ./configure --disable-documentation && make && \ - mv ./src//obfs-local ./src//obfs-server /plugins/ + mv ./src/obfs-local ./src/obfs-server /plugins/ # Compile qtun WORKDIR ../qtun/ RUN cargo update RUN cargo build --target-dir ./ --release && \ - mv ./release//qtun-client ./release//qtun-server /plugins/ && \ + mv ./release/qtun-client ./release/qtun-server /plugins/ && \ strip /plugins/* COPY --from=upx /upx/ /usr/ -RUN upx -9 /plugins/qtun-* +RUN ls /plugins/qtun-* | xargs -P0 -n1 upx -9 # Compile sip003 plugins (part2 -> go1.16) FROM ${GO16_IMG} AS plugin-2 @@ -232,7 +232,7 @@ RUN go mod download -x RUN env CGO_ENABLED=0 go build -v -o gun-plugin -trimpath -ldflags "-s -w" ./cmd/sip003/ && \ mv ./gun-plugin /plugins/ COPY --from=upx /upx/ /usr/ -RUN upx -9 /plugins/* +RUN ls /plugins/* | xargs -P0 -n1 upx -9 # Compile sip003 plugins (part3 -> go1.17) FROM ${GO17_IMG} AS plugin-3 @@ -260,38 +260,32 @@ RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-X main.version=$(git desc env CGO_ENABLED=0 go build -v -trimpath -ldflags "-X main.version=$(git describe --tags) -s -w" ./cmd/ck-server && \ mv ./ck-client ./ck-server /plugins/ COPY --from=upx /upx/ /usr/ -RUN upx -9 /plugins/* +RUN ls /plugins/* | xargs -P0 -n1 upx -9 # Combine sip003 plugins FROM ${ALPINE_IMG} AS plugin -COPY --from=plugin-1 /plugins/ /release/ -COPY --from=plugin-2 /plugins/ /release/ -COPY --from=plugin-3 /plugins/ /release/ +COPY --from=plugin-1 /plugins/ /plugins/ +COPY --from=plugin-2 /plugins/ /plugins/ +COPY --from=plugin-3 /plugins/ /plugins/ -# Compile v2fly-core +# Compile xray-core and v2fly-core FROM ${GO18_IMG} AS v2ray -ENV V2RAY_VERSION="4.45.2" -RUN wget https://github.com/v2fly/v2ray-core/archive/refs/tags/v${V2RAY_VERSION}.tar.gz && \ - tar xf v${V2RAY_VERSION}.tar.gz -WORKDIR ./v2ray-core-${V2RAY_VERSION}/ -RUN go mod download -x -RUN env CGO_ENABLED=0 go build -v -o v2ray -trimpath -ldflags "-s -w" ./main && \ - env CGO_ENABLED=0 go build -v -o v2ctl -trimpath -ldflags "-s -w" -tags confonly ./infra/control/main && \ - mv ./v2ctl ./v2ray /tmp/ -COPY --from=upx /upx/ /usr/ -RUN upx -9 /tmp/v2* - -# Compile xray-core -FROM ${GO18_IMG} AS xray ENV XRAY_VERSION="1.5.9" +ENV V2FLY_VERSION="4.45.2" RUN wget https://github.com/XTLS/Xray-core/archive/refs/tags/v${XRAY_VERSION}.tar.gz && \ tar xf v${XRAY_VERSION}.tar.gz +RUN wget https://github.com/v2fly/v2ray-core/archive/refs/tags/v${V2FLY_VERSION}.tar.gz && \ + tar xf v${V2FLY_VERSION}.tar.gz WORKDIR ./Xray-core-${XRAY_VERSION}/ RUN go mod download -x RUN env CGO_ENABLED=0 go build -v -o xray -trimpath -ldflags "-s -w" ./main && \ mv ./xray /tmp/ +WORKDIR ../v2ray-core-${V2FLY_VERSION}/ +RUN go mod download -x +RUN env CGO_ENABLED=0 go build -v -o v2ray -trimpath -ldflags "-s -w" ./main && \ + mv ./v2ray /tmp/ COPY --from=upx /upx/ /usr/ -RUN upx -9 /tmp/xray +RUN ls /tmp/*ray | xargs -P0 -n1 upx -9 # Compile trojan-go FROM ${GO17_IMG} AS trojan-go @@ -320,29 +314,23 @@ RUN cmake .. -DENABLE_MYSQL=OFF -DSYSTEMD_SERVICE=OFF && make && \ strip /tmp/trojan COPY --from=trojan-go /tmp/trojan-go /tmp/ -# Compile gost-v3 -FROM ${GO18_IMG} AS gost-v3 -RUN apk add git -RUN git clone https://github.com/go-gost/gost.git -WORKDIR ./gost/ -RUN go mod download -x -RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w" ./cmd/gost && \ - mv ./gost /tmp/gost-v3 -COPY --from=upx /upx/ /usr/ -RUN upx -9 /tmp/gost-v3 - -# Compile gost -FROM ${GO17_IMG} AS gost +# Compile gost and gost-v3 +FROM ${GO18_IMG} AS gost ENV GOST_VERSION="2.11.2" -RUN wget https://github.com/ginuerzh/gost/archive/refs/tags/v${GOST_VERSION}.tar.gz && \ +RUN apk add git +RUN git clone https://github.com/go-gost/gost.git ./gost-v3/ && \ + wget https://github.com/ginuerzh/gost/archive/refs/tags/v${GOST_VERSION}.tar.gz && \ tar xf v${GOST_VERSION}.tar.gz WORKDIR ./gost-${GOST_VERSION}/ RUN go mod download -x RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w" ./cmd/gost && \ mv ./gost /tmp/ +WORKDIR ../gost-v3/ +RUN go mod download -x +RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w" ./cmd/gost && \ + mv ./gost /tmp/gost-v3 COPY --from=upx /upx/ /usr/ -RUN upx -9 /tmp/gost -COPY --from=gost-v3 /tmp/gost-v3 /tmp/ +RUN ls /tmp/gost* | xargs -P0 -n1 upx -9 # Compile brook FROM ${GO16_IMG} AS brook @@ -370,14 +358,6 @@ RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w \ COPY --from=upx /upx/ /usr/ RUN upx -9 /tmp/clash -# Compile caddy -FROM ${GO18_IMG} AS caddy -RUN go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest -RUN xcaddy build --with github.com/caddyserver/forwardproxy@caddy2=github.com/klzgrad/forwardproxy@naive && \ - mv ./caddy /tmp/ -COPY --from=upx /upx/ /usr/ -RUN upx -9 /tmp/caddy - # Download naiveproxy FROM ${ALPINE_IMG} AS naiveproxy ENV NAIVE_VERSION="v103.0.5060.53-3" @@ -391,7 +371,6 @@ RUN echo -e "while read FILE_NAME;do\nwget https://github.com/klzgrad/naiveproxy sh naiveproxy.sh COPY --from=build-base /apk/ /apk/ RUN /apk/build-base && strip /tmp/naive -COPY --from=caddy /tmp/caddy /tmp/ # Compile open-snell FROM ${GO17_IMG} AS snell @@ -406,7 +385,7 @@ RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags \ "-s -w -X 'github.com/icpz/open-snell/constants.Version=${SNELL_VERSION}'" ./cmd/snell-server && \ mv ./snell-client ./snell-server /tmp/ COPY --from=upx /upx/ /usr/ -RUN upx -9 /tmp/snell-* +RUN ls /tmp/snell-* | xargs -P0 -n1 upx -9 # Compile hysteria FROM ${GO17_IMG} AS hysteria @@ -490,16 +469,14 @@ RUN mkdir -p /asset/usr/local/lib/python${PYTHON}/site-packages/ && \ tar xf /packages.tar.gz -C /asset/usr/local/lib/python${PYTHON}/site-packages/ COPY --from=openssl /tmp/libcrypto.so* /asset/lib/ COPY --from=shadowsocks /release/ /asset/usr/bin/ -COPY --from=plugin /release/ /asset/usr/bin/ -COPY --from=v2ray /tmp/v2* /asset/usr/bin/ -COPY --from=xray /tmp/xray /asset/usr/bin/ +COPY --from=plugin /plugins/ /asset/usr/bin/ +COPY --from=v2ray /tmp/*ray /asset/usr/bin/ COPY --from=trojan /tmp/trojan* /asset/usr/bin/ COPY --from=gost /tmp/gost* /asset/usr/bin/ COPY --from=brook /tmp/brook /asset/usr/bin/ COPY --from=clash /tmp/clash /asset/usr/bin/ COPY --from=snell /tmp/snell-* /asset/usr/bin/ COPY --from=hysteria /tmp/hysteria /asset/usr/bin/ -COPY --from=naiveproxy /tmp/caddy /asset/usr/bin/ COPY --from=naiveproxy /tmp/naive /asset/usr/bin/ COPY --from=relaybaton /tmp/relaybaton /asset/usr/bin/ COPY --from=pingtunnel /tmp/pingtunnel /asset/usr/bin/ diff --git a/Filter/Brook.py b/Filter/Brook.py new file mode 100644 index 0000000..2ef8da3 --- /dev/null +++ b/Filter/Brook.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import toInt, toStr, toStrTidy, toBool + +secureObject = rulesFilter({ + 'verify': { + 'optional': True, + 'default': True, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid verify option' + } +}) + +originObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'origin', + 'errMsg': 'Invalid Origin stream type' + }, + 'uot': { + 'optional': True, + 'default': False, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid UoT option' + } +}) + +wsObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'ws', + 'errMsg': 'Invalid WebSocket stream type' + }, + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid WebSocket host' + }, + 'path': { + 'optional': True, + 'default': '/ws', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid WebSocket path' + }, + 'raw': { + 'optional': True, + 'default': False, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid raw option' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': secureObject, + 'errMsg': 'Invalid secure options' + } +}) + +brookObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'passwd': { + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid password content' + }, + 'stream': { + 'optional': True, + 'default': { + 'type': 'origin' + }, + 'multiSub': True, + 'type': { + 'origin': originObject, + 'ws': wsObject, + }, + 'errMsg': 'Invalid Brook stream' + } +}) + + +def brookFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + proxyInfo = Filter(proxyInfo, brookObject) # run filter + stream = proxyInfo['stream'] + if stream['type'] == 'ws' and stream['host'] == '': + stream['host'] = proxyInfo['server'] # fill host option in WebSocket stream + return proxyInfo diff --git a/Filter/Hysteria.py b/Filter/Hysteria.py new file mode 100644 index 0000000..48f65cc --- /dev/null +++ b/Filter/Hysteria.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Constant import hysteriaProtocols +from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool + +hysteriaObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'protocol': { + 'optional': True, + 'default': 'udp', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in hysteriaProtocols, + 'errMsg': 'Unknown Hysteria protocol' + }, + + 'obfs': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid obfs content' + }, + 'passwd': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid password content' + }, + + 'up': { + 'optional': True, + 'default': 10, + 'type': int, + 'format': toInt, + 'filter': lambda i: i > 0, + 'errMsg': 'Invalid upload speed option' + }, + 'down': { + 'optional': True, + 'default': 50, + 'type': int, + 'format': toInt, + 'filter': lambda i: i > 0, + 'errMsg': 'Invalid download speed option' + }, + 'sni': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid SNI content' + }, + 'alpn': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid alpn option' + }, + 'verify': { + 'optional': True, + 'default': True, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid verify option' + } +}) + + +def hysteriaFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + proxyInfo = Filter(proxyInfo, hysteriaObject) # run filter + if proxyInfo['sni'] == '' and not isIpAddr(proxyInfo['server']): + proxyInfo['sni'] = proxyInfo['server'] + return proxyInfo diff --git a/Filter/Plugin.py b/Filter/Plugin.py new file mode 100644 index 0000000..559676e --- /dev/null +++ b/Filter/Plugin.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Basis.Filter import rulesFilter +from Basis.Constant import pluginClients +from Basis.Functions import toStr, toStrTidy + +pluginAlias = { + 'obfs-local': {'obfs', 'simple-obfs'}, + 'simple-tls': {'tls', 'simple-tls'}, + 'v2ray-plugin': {'v2ray'}, + 'xray-plugin': {'xray'}, + 'kcptun-client': {'kcptun'}, + 'gost-plugin': {'gost'}, + 'ck-client': {'ck', 'cloak'}, + 'gq-client': {'gq', 'goquiet', 'go-quiet'}, + 'mtt-client': {'mtt', 'mos-tls-tunnel'}, + 'rabbit-plugin': {'rabbit', 'rabbit-tcp'}, + 'qtun-client': {'qtun'}, + 'gun-plugin': {'gun'}, +} + +pluginObject = rulesFilter({ + 'type': { + 'type': str, + 'format': lambda s: pluginFormat(toStrTidy(s)), + 'filter': lambda s: s in pluginClients, + 'errMsg': 'Unknown SIP003 plugin' + }, + 'param': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid SIP003 param' + } +}) + + +def loadAlias() -> None: + for plugin in pluginAlias: + for alias in copy.copy(pluginAlias[plugin]): + pluginAlias[plugin].update({ # better compatibility + alias + '-local', alias + '-plugin', + alias + '-client', alias + '-server', + }) + + +def pluginFormat(pluginName: str) -> str: + pluginName = pluginName.replace('_', '-') + for plugin, alias in pluginAlias.items(): + if pluginName in alias: + return plugin + return pluginName # alias not found + + +loadAlias() diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py new file mode 100644 index 0000000..72ca1ed --- /dev/null +++ b/Filter/Shadowsocks.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Filter.Plugin import pluginObject +from Basis.Constant import ssAllMethods +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import toInt, toStr, toStrTidy + +ssObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'method': { + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in ssAllMethods, + 'errMsg': 'Unknown Shadowsocks method' + }, + 'passwd': { + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid password content' + }, + 'plugin': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': pluginObject, + 'errMsg': 'Invalid plugin options' + } +}) + + +def ssFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + return Filter(proxyInfo, ssObject) # run filter diff --git a/Filter/ShadowsocksR.py b/Filter/ShadowsocksR.py new file mode 100644 index 0000000..3877d59 --- /dev/null +++ b/Filter/ShadowsocksR.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import toInt, toStr, toStrTidy +from Basis.Constant import ssrMethods, ssrProtocols, ssrObfuscations + + +def ssrProtocolFormat(protocol: str) -> str: + protocol = toStr(protocol).strip().lower().replace('-', '_') + return 'origin' if protocol == '' else protocol # '' -> origin + + +def ssrObfsFormat(obfs: str) -> str: + obfs = toStr(obfs).strip().lower().replace('-', '_') + return 'plain' if obfs == '' else obfs # '' -> plain + + +ssrObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'method': { + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in ssrMethods, + 'errMsg': 'Unknown ShadowsocksR method' + }, + 'passwd': { + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid password content' + }, + 'protocol': { + 'optional': True, + 'default': 'origin', + 'type': str, + 'format': ssrProtocolFormat, + 'filter': lambda s: s in ssrProtocols, + 'errMsg': 'Unknown ShadowsocksR protocol' + }, + 'protocolParam': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid ShadowsocksR protocol param' + }, + 'obfs': { + 'optional': True, + 'default': 'plain', + 'type': str, + 'format': ssrObfsFormat, + 'filter': lambda s: s in ssrObfuscations, + 'errMsg': 'Unknown ShadowsocksR obfuscation' + }, + 'obfsParam': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid ShadowsocksR obfuscation param' + } +}) + + +def ssrFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + proxyInfo = Filter(proxyInfo, ssrObject) # run filter + if proxyInfo['protocol'] == 'origin': # origin without param + proxyInfo['protocolParam'] = '' + if proxyInfo['obfs'] == 'plain': # plain without param + proxyInfo['obfsParam'] = '' + return proxyInfo diff --git a/Filter/Trojan.py b/Filter/Trojan.py new file mode 100644 index 0000000..f4ca221 --- /dev/null +++ b/Filter/Trojan.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Filter import Xray +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import toInt, toStr, toStrTidy + +trojanObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'passwd': { + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid password content' + }, + 'stream': { + 'optional': True, + 'default': { + 'type': 'tcp' + }, + 'multiSub': True, + 'type': { + 'tcp': Xray.tcpObject, + 'kcp': Xray.kcpObject, + 'ws': Xray.wsObject, + 'h2': Xray.h2Object, + 'quic': Xray.quicObject, + 'grpc': Xray.grpcObject, + }, + 'errMsg': 'Invalid Trojan stream' + } +}) + + +def trojanFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + proxyInfo = Filter(proxyInfo, trojanObject) # run filter + Xray.addSni(proxyInfo) # add SNI option + return proxyInfo diff --git a/Filter/TrojanGo.py b/Filter/TrojanGo.py new file mode 100644 index 0000000..27689b2 --- /dev/null +++ b/Filter/TrojanGo.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Filter.Plugin import pluginObject +from Basis.Constant import trojanGoMethods +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool + +ssObject = rulesFilter({ + 'method': { + 'optional': True, + 'default': 'aes-128-gcm', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in trojanGoMethods, + 'errMsg': 'Unknown Shadowsocks method' + }, + 'passwd': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid Shadowsocks password' + } +}) + +wsObject = rulesFilter({ + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid WebSocket host' + }, + 'path': { + 'optional': True, + 'default': '/', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid WebSocket path' + } +}) + +trojanGoObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'passwd': { + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid password content' + }, + 'sni': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid SNI content' + }, + 'alpn': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space + 'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Invalid alpn option' + }, + 'verify': { + 'optional': True, + 'default': True, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid verify option' + }, + 'ws': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': wsObject, + 'errMsg': 'Invalid WebSocket options' + }, + 'ss': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': ssObject, + 'errMsg': 'Invalid Shadowsocks options' + }, + 'plugin': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': pluginObject, + 'errMsg': 'Invalid plugin options' + } +}) + + +def trojanGoFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + proxyInfo = Filter(proxyInfo, trojanGoObject) # run filter + if proxyInfo['sni'] == '' and not isIpAddr(proxyInfo['server']): + proxyInfo['sni'] = proxyInfo['server'] + return proxyInfo diff --git a/Filter/V2ray.py b/Filter/V2ray.py new file mode 100644 index 0000000..a7d9063 --- /dev/null +++ b/Filter/V2ray.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis.Filter import rulesFilter +from Basis.Constant import quicMethods, udpObfuscations +from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool + +tlsObject = rulesFilter({ + 'sni': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid SNI content' + }, + 'alpn': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space + 'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Invalid alpn option' + }, + 'verify': { + 'optional': True, + 'default': True, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid verify option' + } +}) + +obfsObject = rulesFilter({ + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid obfs host' + }, + 'path': { + 'optional': True, + 'default': '/', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid obfs path' + } +}) + +tcpObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'tcp', + 'errMsg': 'Invalid TCP stream type' + }, + 'obfs': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': obfsObject, + 'errMsg': 'Invalid obfsObject' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' + } +}) + +kcpObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'kcp', + 'errMsg': 'Invalid mKCP stream type' + }, + 'seed': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid mKCP seed' + }, + 'obfs': { + 'optional': True, + 'default': 'none', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in udpObfuscations, + 'errMsg': 'Unknown mKCP obfs method' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' + } +}) + +wsObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'ws', + 'errMsg': 'Invalid WebSocket stream type' + }, + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid WebSocket host' + }, + 'path': { + 'optional': True, + 'default': '/', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid WebSocket path' + }, + 'ed': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': int, + 'format': toInt, + 'filter': lambda i: i > 0, + 'errMsg': 'Illegal Max-Early-Data length' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' + } +}) + +h2Object = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'h2', + 'errMsg': 'Invalid HTTP/2 stream type' + }, + 'host': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid HTTP/2 host' + }, + 'path': { + 'optional': True, + 'default': '/', + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid HTTP/2 path' + }, + 'secure': { + 'optional': True, + 'default': {}, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' + } +}) + +quicObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'quic', + 'errMsg': 'Invalid QUIC stream type' + }, + 'method': { + 'optional': True, + 'default': 'none', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in quicMethods, + 'errMsg': 'Unknown QUIC method' + }, + 'passwd': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid QUIC password' + }, + 'obfs': { + 'optional': True, + 'default': 'none', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in udpObfuscations, + 'errMsg': 'Unknown QUIC obfs method' + }, + 'secure': { + 'optional': True, + 'default': {}, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' + } +}) + +grpcObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'grpc', + 'errMsg': 'Invalid gRPC stream type' + }, + 'service': { + 'type': str, + 'format': lambda s: toStr(s).strip(), + 'errMsg': 'Invalid service content' + }, + 'mode': { + 'optional': True, + 'default': 'gun', + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s in ['gun', 'multi'], + 'errMsg': 'Unknown gRPC mode' + }, + 'secure': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' + } +}) + + +def addSni(proxyInfo: dict) -> None: + stream = proxyInfo['stream'] + if stream['secure'] is None or stream['secure']['sni'] != '': # don't need to set SNI + return + if not isIpAddr(proxyInfo['server']): + stream['secure']['sni'] = proxyInfo['server'] # set SNI as server address (domain case) + sniContent = '' + if stream['type'] == 'tcp' and stream['obfs'] is not None: # obfs host in TCP stream + sniContent = stream['obfs']['host'].split(',')[0] + elif stream['type'] == 'ws': # WebSocket host + sniContent = stream['host'] + elif stream['type'] == 'h2': # HTTP/2 host + sniContent = stream['host'].split(',')[0] + if sniContent != '': + stream['secure']['sni'] = sniContent # overwrite SNI content diff --git a/Filter/VLESS.py b/Filter/VLESS.py new file mode 100644 index 0000000..b8dbd2c --- /dev/null +++ b/Filter/VLESS.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Filter import Xray +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import toInt, toStrTidy + +vlessObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'method': { + 'optional': True, + 'default': 'none', + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'none', + 'errMsg': 'Unknown VLESS method' + }, + 'id': { + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid VLESS ID' + }, + 'stream': { + 'optional': True, + 'default': { + 'type': 'tcp' + }, + 'multiSub': True, + 'type': { + 'tcp': Xray.tcpObject, + 'kcp': Xray.kcpObject, + 'ws': Xray.wsObject, + 'h2': Xray.h2Object, + 'quic': Xray.quicObject, + 'grpc': Xray.grpcObject, + }, + 'errMsg': 'Invalid VLESS stream' + } +}) + + +def vlessFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + proxyInfo = Filter(proxyInfo, vlessObject) # run filter + Xray.addSni(proxyInfo) # add SNI option + return proxyInfo diff --git a/Filter/VMess.py b/Filter/VMess.py new file mode 100644 index 0000000..ffc0ffa --- /dev/null +++ b/Filter/VMess.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Filter import V2ray +from Basis.Constant import vmessMethods +from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import toInt, toStrTidy + +vmessObject = rulesFilter({ + 'server': { + 'type': str, + 'format': toStrTidy, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'method': { + 'optional': True, + 'default': 'auto', + 'type': str, + 'format': lambda s: toStrTidy(s).replace('_', '-'), + 'filter': lambda s: s in vmessMethods, + 'errMsg': 'Unknown VMess method' + }, + 'id': { + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid VMess ID' + }, + 'aid': { + 'optional': True, + 'default': 0, + 'type': int, + 'format': toInt, + 'filter': lambda i: i in range(0, 65536), # 0 ~ 65535 + 'errMsg': 'Invalid VMess alter ID' + }, + 'stream': { + 'optional': True, + 'default': { + 'type': 'tcp' + }, + 'multiSub': True, + 'type': { + 'tcp': V2ray.tcpObject, + 'kcp': V2ray.kcpObject, + 'ws': V2ray.wsObject, + 'h2': V2ray.h2Object, + 'quic': V2ray.quicObject, + 'grpc': V2ray.grpcObject, + }, + 'errMsg': 'Invalid VMess stream' + } +}) + + +def vmessFilter(proxyInfo: dict) -> dict: + proxyInfo = copy.deepcopy(proxyInfo) + proxyInfo = Filter(proxyInfo, vmessObject) # run filter + V2ray.addSni(proxyInfo) # add SNI option + return proxyInfo diff --git a/Filter/Xray.py b/Filter/Xray.py new file mode 100644 index 0000000..f014bdb --- /dev/null +++ b/Filter/Xray.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import copy +from Filter import V2ray +from Basis.Filter import rulesFilter +from Basis.Constant import xtlsFlows +from Basis.Functions import toStrTidy, toBool + + +def xtlsFlowFormat(flow: str) -> str: + flow = flow.replace('_', '-') + xtlsFlowAlias = { + 'xtls-origin': {'origin', 'xtls-rprx-origin'}, + 'xtls-direct': {'direct', 'xtls-rprx-direct'}, + 'xtls-splice': {'splice', 'xtls-rprx-splice'}, + } + for xtlsFlow, alias in xtlsFlowAlias.items(): + if flow in alias: + return xtlsFlow + return flow # alias not found + + +tlsObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'tls', + 'errMsg': 'Invalid TLS secure type' + }, + 'sni': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid SNI content' + }, + 'alpn': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space + 'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Invalid alpn option' + }, + 'verify': { + 'optional': True, + 'default': True, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid verify option' + } +}) + +xtlsObject = rulesFilter({ + 'type': { + 'type': str, + 'format': toStrTidy, + 'filter': lambda s: s == 'xtls', + 'errMsg': 'Invalid XTLS secure type' + }, + 'sni': { + 'optional': True, + 'default': '', + 'type': str, + 'format': toStrTidy, + 'errMsg': 'Invalid SNI content' + }, + 'alpn': { + 'optional': True, + 'default': None, + 'allowNone': True, + 'type': str, + 'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space + 'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'], + 'errMsg': 'Invalid alpn option' + }, + 'verify': { + 'optional': True, + 'default': True, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid verify option' + }, + 'flow': { + 'optional': True, + 'default': 'xtls-direct', + 'type': str, + 'format': lambda s: xtlsFlowFormat(toStrTidy(s)), + 'filter': lambda s: s in xtlsFlows, + 'errMsg': 'Unknown XTLS flow' + }, + 'udp443': { + 'optional': True, + 'default': False, + 'type': bool, + 'format': toBool, + 'errMsg': 'Invalid udp/443 option' + } +}) + +secureRule_1 = { # None / tlsObject / xtlsObject + 'optional': True, + 'default': { + 'type': 'tls' + }, + 'allowNone': True, + 'multiSub': True, + 'type': { + 'tls': tlsObject, + 'xtls': xtlsObject, + }, + 'errMsg': 'Invalid secure options' +} + +secureRule_2 = { # None / tlsObject + 'optional': True, + 'default': { + 'type': 'tls' + }, + 'allowNone': True, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' +} + +secureRule_3 = { # tlsObject + 'optional': True, + 'default': { + 'type': 'tls' + }, + 'type': tlsObject, + 'errMsg': 'Invalid secure options' +} + +tcpObject = rulesFilter({ + **copy.deepcopy(V2ray.tcpObject), + 'secure': secureRule_1 # None / tlsObject / xtlsObject +}) + +kcpObject = rulesFilter({ + **copy.deepcopy(V2ray.kcpObject), + 'secure': secureRule_1 # None / tlsObject / xtlsObject +}) + +wsObject = rulesFilter({ + **copy.deepcopy(V2ray.wsObject), + 'secure': secureRule_2 # None / tlsObject +}) + +h2Object = rulesFilter({ + **copy.deepcopy(V2ray.h2Object), + 'secure': secureRule_3 # tlsObject +}) + +quicObject = rulesFilter({ + **copy.deepcopy(V2ray.quicObject), + 'secure': secureRule_3 # tlsObject +}) + +grpcObject = rulesFilter({ + **copy.deepcopy(V2ray.grpcObject), + 'secure': secureRule_2 # None / tlsObject +}) + +addSni = V2ray.addSni diff --git a/Filter/__init__.py b/Filter/__init__.py new file mode 100644 index 0000000..4b571b5 --- /dev/null +++ b/Filter/__init__.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Filter.Brook import brookFilter +from Filter.VMess import vmessFilter +from Filter.VLESS import vlessFilter +from Filter.Trojan import trojanFilter +from Filter.Shadowsocks import ssFilter +from Filter.ShadowsocksR import ssrFilter +from Filter.TrojanGo import trojanGoFilter +from Filter.Hysteria import hysteriaFilter + +filterEntry = { + 'ss': ssFilter, + 'ssr': ssrFilter, + 'vmess': vmessFilter, + 'vless': vlessFilter, + 'trojan': trojanFilter, + 'trojan-go': trojanGoFilter, + 'brook': brookFilter, + 'hysteria': hysteriaFilter, +} + +def Filter(proxyType: str, proxyInfo: dict) -> dict: + if proxyType not in filterEntry: + raise RuntimeError('Unknown proxy type') + return filterEntry[proxyType](proxyInfo) diff --git a/ProxyFilter/Brook.py b/ProxyFilter/Brook.py deleted file mode 100644 index 5ba774a..0000000 --- a/ProxyFilter/Brook.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import baseFunc - -brookFilterRules = { - 'rootObject': { - 'remark': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'server': { - 'optional': True, - 'type': str, - 'format': baseFunc.toHost, - 'filter': baseFunc.isHost, - 'errMsg': 'Illegal server address' - }, - 'port': { - 'optional': True, - 'type': int, - 'format': baseFunc.toInt, - 'filter': baseFunc.isPort, - 'errMsg': 'Illegal port number' - }, - 'passwd': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'ws': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'wsObject' - } - }, - 'wsObject': { - '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' - } - }, - 'secureObject': { - 'verify': { - 'optional': False, - 'default': True, - 'type': bool, - 'format': baseFunc.toBool - } - } -} - -def filte(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: - """ - Brook节点合法性检查 - - 不合法: - return False, {reason} - - 合法: - return True, { - 'type': 'brook', - ... - } - """ - try: - if not isExtra: # 去除非必要参数 - brookFilterRules['rootObject'].pop('remark') - status, result = baseFunc.ruleFilter(rawInfo, brookFilterRules, {}) - if not status: # 节点格式错误 - return False, result - if result['ws'] is not None and result['ws']['host'] == '': - result['ws']['host'] = result['server'] - return True, result - except: - return False, 'Unknown error' diff --git a/ProxyFilter/Hysteria.py b/ProxyFilter/Hysteria.py deleted file mode 100644 index 5498f5b..0000000 --- a/ProxyFilter/Hysteria.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import baseFunc - -hysteriaFilterRules = { - 'rootObject': { - 'remark': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'server': { - 'optional': True, - 'type': str, - 'format': baseFunc.toHost, - 'filter': baseFunc.isHost, - 'errMsg': 'Illegal server address' - }, - 'port': { - 'optional': True, - 'type': int, - 'format': baseFunc.toInt, - 'filter': baseFunc.isPort, - 'errMsg': 'Illegal port number' - }, - 'protocol': { - 'optional': False, - 'default': 'udp', - 'type': str, - 'format': baseFunc.toStr, - 'filter': lambda protocol: protocol in ['udp', 'wechat-video', 'faketcp'], - 'errMsg': 'Unknown Hysteria protocol' - }, - 'obfs': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'auth': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'sni': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'alpn': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'verify': { - 'optional': False, - 'default': True, - 'type': bool, - 'format': baseFunc.toBool - } - } -} - -def filte(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: - """ - Hysteria节点合法性检查 - - 不合法: - return False, {reason} - - 合法: - return True, { - 'type': 'hysteria', - ... - } - """ - try: - if not isExtra: # 去除非必要参数 - hysteriaFilterRules['rootObject'].pop('remark') - return baseFunc.ruleFilter(rawInfo, hysteriaFilterRules, {}) - except: - return False, 'Unknown error' diff --git a/ProxyFilter/Plugin.py b/ProxyFilter/Plugin.py deleted file mode 100644 index 41ca8cc..0000000 --- a/ProxyFilter/Plugin.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -pluginList = [ # 插件列表 - 'obfs-local', - 'simple-tls', - 'v2ray-plugin', - 'xray-plugin', - 'kcptun-client', - 'gost-plugin', - 'ck-client', - 'gq-client', - 'mtt-client', - 'rabbit-plugin', - 'qtun-client', - 'gun-plugin' -] - -pluginAlias = { # 插件别名 - 'obfs-local': [ - 'obfs', - 'obfs-plugin', - 'obfs-client', - 'obfs-server', - 'simple-obfs', - ], - 'simple-tls': [ - 'tls-local', - 'tls-client', - 'tls-server', - 'tls-plugin', - 'simple-tls-local', - 'simple-tls-client', - 'simple-tls-server', - 'simple-tls-plugin', - ], - 'v2ray-plugin': [ - 'v2ray', - 'v2ray-local', - 'v2ray-client', - 'v2ray-server', - ], - 'xray-plugin': [ - 'xray', - 'xray-local', - 'xray-client', - 'xray-server', - ], - 'kcptun-client': [ - 'kcptun', - 'kcptun-local', - 'kcptun-server', - 'kcptun-plugin', - ], - 'gost-plugin': [ - 'gost', - 'gost-local', - 'gost-client', - 'gost-server', - ], - 'ck-client': [ - 'ck', - 'ck-local', - 'ck-server', - 'ck-plugin', - 'cloak', - 'cloak-local', - 'cloak-client', - 'cloak-server', - 'cloak-plugin', - ], - 'gq-client': [ - 'gq', - 'gq-local', - 'gq-server', - 'gq-plugin', - 'goquiet', - 'goquiet-local', - 'goquiet-client', - 'goquiet-server', - 'goquiet-plugin', - ], - 'mtt-client': [ - 'mtt', - 'mtt-local', - 'mtt-server', - 'mtt-plugin', - 'mos-tls-tunnel', - 'mos-tls-tunnel-local', - 'mos-tls-tunnel-client', - 'mos-tls-tunnel-server', - 'mos-tls-tunnel-plugin', - ], - 'rabbit-plugin': [ - 'rabbit', - 'rabbit-tcp', - 'rabbit-local', - 'rabbit-client', - 'rabbit-server', - ], - 'qtun-client': [ - 'qtun', - 'qtun-local', - 'qtun-server', - 'qtun-plugin', - ], - 'gun-plugin': [ - 'gun', - 'gun-local', - 'gun-client', - 'gun-server', - ] -} - -def isPlugin(plugin: str) -> bool: # 插件是否合法 - return plugin in pluginList - -def pluginFormat(plugin: str) -> str: # 插件格式化 - plugin = plugin.replace('_', '-').lower().strip() - if plugin not in pluginList: # 非标插件名 - for pluginName in pluginAlias: - if plugin in pluginAlias[pluginName]: # 匹配别名列表 - return pluginName - return plugin # 匹配不到时返回原值 diff --git a/ProxyFilter/Shadowsocks.py b/ProxyFilter/Shadowsocks.py deleted file mode 100644 index 257f871..0000000 --- a/ProxyFilter/Shadowsocks.py +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import Plugin -from ProxyFilter import baseFunc - -ssMethodList = [ # 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' -] - -ssFilterRules = { - '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': True, - 'type': str, - 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), - 'filter': lambda method: method in ssMethodList, - 'errMsg': 'Unknown Shadowsocks method' - }, - 'passwd': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'plugin': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'pluginObject' - } - }, - 'pluginObject': { - 'type': { - 'optional': True, - 'type': str, - 'format': lambda pluginType: Plugin.pluginFormat(baseFunc.toStrTidy(pluginType)), - 'filter': Plugin.isPlugin, - 'errMsg': 'Unknown SIP003 plugin' - }, - 'param': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - } - } -} - -def ssFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: - """ - Shadowsocks节点合法性检查 - - 不合法: - return False, {reason} - - 合法: - return True, { - 'type': 'ss', - ... - } - """ - try: - if not isExtra: # 去除非必要参数 - ssFilterRules['rootObject'].pop('remark') - return baseFunc.ruleFilter(rawInfo, ssFilterRules, {}) - except: - return False, 'Unknown error' diff --git a/ProxyFilter/ShadowsocksR.py b/ProxyFilter/ShadowsocksR.py deleted file mode 100644 index 4d598f5..0000000 --- a/ProxyFilter/ShadowsocksR.py +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import baseFunc - -ssrMethodList = [ # 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', -] - -ssrProtocolList = [ # 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', -] - -ssrObfsList = [ # ShadowsocksR混淆方式 - 'plain', - 'http_post', - 'http_simple', - 'tls_simple', - 'tls1.2_ticket_auth', - 'tls1.2_ticket_fastauth', - 'random_head', -] - -def __ssrProtocol(protocol) -> str: - protocol = baseFunc.toStrTidy(protocol).replace('-', '_') - if protocol == '': - return 'origin' - return protocol - -def __ssrObfs(obfs) -> str: - obfs = baseFunc.toStrTidy(obfs).replace('-', '_') - if obfs == '': - return 'plain' - return obfs - -ssrFilterRules = { - 'rootObject': { - 'remark': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'group': { - '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': True, - 'type': str, - 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), - 'filter': lambda method: method in ssrMethodList, - 'errMsg': 'Unknown ShadowsocksR method' - }, - 'passwd': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'protocol': { - 'optional': False, - 'default': 'origin', - 'type': str, - 'format': __ssrProtocol, - 'filter': lambda protocol: protocol in ssrProtocolList, - 'errMsg': 'Unknown ShadowsocksR protocol' - }, - 'protocolParam': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'obfs': { - 'optional': False, - 'default': 'plain', - 'type': str, - 'format': __ssrObfs, - 'filter': lambda obfs: obfs in ssrObfsList, - 'errMsg': 'Unknown ShadowsocksR obfs' - }, - 'obfsParam': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - } - } -} - -def ssrFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: - """ - ShadowsocksR节点合法性检查 - - 不合法: - return False, {reason} - - 合法: - return True, { - 'type': 'ssr', - ... - } - """ - try: - if not isExtra: # 去除非必要参数 - ssrFilterRules['rootObject'].pop('remark') - ssrFilterRules['rootObject'].pop('group') - status, result = baseFunc.ruleFilter(rawInfo, ssrFilterRules, {}) - if not status: # 节点格式错误 - return False, result - if result['protocol'] == 'origin': # origin无参数 - result['protocolParam'] = '' - if result['obfs'] == 'plain': # plain无参数 - result['obfsParam'] = '' - return True, result - except: - return False, 'Unknown error' diff --git a/ProxyFilter/Trojan.py b/ProxyFilter/Trojan.py deleted file mode 100644 index cc2e8a5..0000000 --- a/ProxyFilter/Trojan.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import baseFunc -from ProxyFilter import Xray - -trojanFilterRules = { - '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' - }, - 'passwd': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'stream': { - 'optional': False, - 'default': { - 'type': 'tcp' - }, - 'type': [ - 'tcpObject', - 'kcpObject', - 'wsObject', - 'h2Object', - 'quicObject', - 'grpcObject', - ] - } - } -} - -def trojanFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: - """ - Trojan节点合法性检查 - - 不合法: - return False, {reason} - - 合法: - return True, { - 'type': 'trojan', - ... - } - """ - try: - if not isExtra: # 去除非必要参数 - trojanFilterRules['rootObject'].pop('remark') - for key, obj in Xray.xrayStreamRules.items(): # xray.stream -> trojan - trojanFilterRules[key] = obj - status, result = baseFunc.ruleFilter(rawInfo, trojanFilterRules, {}) - if not status: # 节点格式错误 - return False, result - Xray.addSni(result) - return True, result - except: - return False, 'Unknown error' diff --git a/ProxyFilter/TrojanGo.py b/ProxyFilter/TrojanGo.py deleted file mode 100644 index de0b532..0000000 --- a/ProxyFilter/TrojanGo.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import baseFunc -from ProxyFilter import Plugin - -trojanGoFilterRules = { - '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' - }, - 'passwd': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'sni': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'alpn': { - 'optional': False, - 'default': None, - 'allowNone': True, - '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 - }, - 'ws': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'wsObject' - }, - 'ss': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'ssObject' - }, - 'plugin': { - 'optional': False, - 'default': None, - 'allowNone': True, - 'type': 'pluginObject' - } - }, - 'ssObject': { - 'method': { - 'optional': False, - 'default': 'AES-128-GCM', - 'type': str, - 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-').upper(), - 'filter': lambda method: method in ['AES-128-GCM', 'AES-256-GCM', 'CHACHA20-IETF-POLY1305'], - 'errMsg': 'Unknown Shadowsocks method' - }, - 'passwd': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - }, - 'wsObject': { - 'host': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - }, - 'path': { - 'optional': False, - 'default': '/', - 'type': str, - 'format': baseFunc.toStr - } - }, - 'pluginObject': { - 'type': { - 'optional': True, - 'type': str, - 'format': lambda pluginType: Plugin.pluginFormat(baseFunc.toStrTidy(pluginType)), - 'filter': Plugin.isPlugin, - 'errMsg': 'Unknown SIP003 plugin' - }, - 'param': { - 'optional': False, - 'default': '', - 'type': str, - 'format': baseFunc.toStr - } - } -} - -def trojanGoFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: - """ - Trojan-Go节点合法性检查 - - 不合法: - return False, {reason} - - 合法: - return True, { - 'type': 'trojan-go', - ... - } - """ - try: - if not isExtra: # 去除非必要参数 - trojanGoFilterRules['rootObject'].pop('remark') - return baseFunc.ruleFilter(rawInfo, trojanGoFilterRules, {}) - except: - return False, 'Unknown error' diff --git a/ProxyFilter/V2ray.py b/ProxyFilter/V2ray.py deleted file mode 100644 index 2cc1b13..0000000 --- a/ProxyFilter/V2ray.py +++ /dev/null @@ -1,260 +0,0 @@ -#!/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': {}, - '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 - }, - 'mode': { - 'optional': False, - 'default': 'gun', - 'type': str, - 'format': baseFunc.toStrTidy, - 'filter': lambda mode: mode in ['gun', 'multi'], - 'errMsg': 'Unknown gRPC mode' - }, - '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': None, - 'allowNone': True, - '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 - } - } -} - -def addSni(info: dict) -> None: - stream = info['stream'] - if stream['secure'] is None or stream['secure']['sni'] != '': # 未指定SNI - return - if baseFunc.isDomain(info['server']): - stream['secure']['sni'] = info['server'] - - sniContent = '' - if stream['type'] == 'tcp' and stream['obfs'] is not None: - sniContent = stream['obfs']['host'].split(',')[0] - elif stream['type'] == 'ws': - sniContent = stream['host'] - elif stream['type'] == 'h2': - sniContent = stream['host'].split(',')[0] - - if sniContent != '': - stream['secure']['sni'] = sniContent diff --git a/ProxyFilter/VLESS.py b/ProxyFilter/VLESS.py deleted file mode 100644 index 1a87779..0000000 --- a/ProxyFilter/VLESS.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/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, {}) - if not status: # 节点格式错误 - return False, result - Xray.addSni(result) - return True, result - except: - return False, 'Unknown error' diff --git a/ProxyFilter/VMess.py b/ProxyFilter/VMess.py deleted file mode 100644 index 3cb8dfd..0000000 --- a/ProxyFilter/VMess.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import baseFunc -from ProxyFilter import V2ray - -vmessMethodList = [ - 'aes-128-gcm', - 'chacha20-poly1305', - 'auto', - 'none', - 'zero', -] - -vmessFilterRules = { - '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': 'auto', - 'type': str, - 'format': lambda s: baseFunc.toStrTidy(s).replace('_', '-'), - 'filter': lambda method: method in vmessMethodList, - 'errMsg': 'Unknown VMess method' - }, - 'id': { - 'optional': True, - 'type': str, - 'format': baseFunc.toStr - }, - 'aid': { - 'optional': False, - 'default': 0, - 'type': int, - 'format': baseFunc.toInt, - 'filter': lambda aid: aid in range(0, 65536), # 0 ~ 65535 - 'errMsg': 'Illegal alter Id' - }, - 'stream': { - 'optional': False, - 'default': { - 'type': 'tcp' - }, - 'type': [ - 'tcpObject', - 'kcpObject', - 'wsObject', - 'h2Object', - 'quicObject', - 'grpcObject', - ] - } - } -} - -def vmessFilter(rawInfo: dict, isExtra: bool) -> tuple[bool, str or dict]: - """ - VMess节点合法性检查 - - 不合法: - return False, {reason} - - 合法: - return True, { - 'type': 'vmess', - ... - } - """ - 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, {}) - if not status: # 节点格式错误 - return False, result - V2ray.addSni(result) - return True, result - except: - return False, 'Unknown error' diff --git a/ProxyFilter/Xray.py b/ProxyFilter/Xray.py deleted file mode 100644 index a9f8e6d..0000000 --- a/ProxyFilter/Xray.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -import copy - -from ProxyFilter import baseFunc -from ProxyFilter import V2ray - -xrayFlowList = [ - 'xtls-origin', - 'xtls-direct', - 'xtls-splice', -] - -xrayStreamRules = copy.deepcopy(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['tcpObject']['secure']['default'] = {'type': 'tls'} -xrayStreamRules['kcpObject']['secure']['default'] = {'type': 'tls'} -xrayStreamRules['wsObject']['secure']['default'] = {'type': 'tls'} -xrayStreamRules['h2Object']['secure']['default'] = {'type': 'tls'} -xrayStreamRules['quicObject']['secure']['default'] = {'type': 'tls'} -xrayStreamRules['grpcObject']['secure']['default'] = {'type': 'tls'} - -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': None, - 'allowNone': True, - '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': None, - 'allowNone': True, - '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 - } -} - -addSni = V2ray.addSni diff --git a/ProxyFilter/__init__.py b/ProxyFilter/__init__.py deleted file mode 100644 index 7198490..0000000 --- a/ProxyFilter/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter.filter import * - -__all__ = ['filte'] diff --git a/ProxyFilter/baseFunc.py b/ProxyFilter/baseFunc.py deleted file mode 100644 index 65a00c9..0000000 --- a/ProxyFilter/baseFunc.py +++ /dev/null @@ -1,301 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -import re -import IPy -import copy - -def isIpAddr(content: str) -> bool: - """ - 判断是否为IP地址(IPv4 / IPv6) - - 域名: return True - - 非域名: return False - - """ - try: - if content.find('/') != -1: # filter CIDR - return False - if content.find('.') == -1 and content.find(':') == -1: - return False - IPy.IP(content) - return True # IP地址合法 - except: - return False - - -def isDomain(content: str) -> bool: - """ - 判断是否为域名 - - 域名: return True - - 非域名: return False - - """ - try: - return re.search( # 域名匹配 - r'^(?=^.{3,255}$)[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62}(\.[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62})+$', content - ) is not None - except: # 异常错误 - return False - - -def isHost(host: str) -> bool: - """ - 判断host是否合法 - - IPv4 / IPv6 / Domain - - 合法: return True - - 不合法: return False - - """ - if isIpAddr(host) or isDomain(host): - return True - return False - - -def isPort(port: int) -> bool: - """ - 判断端口是否合法 - - 1 ~ 65535 - - 合法: return True - - 不合法: return False - - """ - try: - if 1 <= port <= 65535: # 1 ~ 65535 - return True - except: # illegal - pass - return False - - -def toInt(raw) -> int: # change to int - if isinstance(raw, (int, float)): # int / float -> int - return int(raw) - elif isinstance(raw, bytes): # bytes -> str - raw = str(raw, encoding = 'utf-8') - elif not isinstance(raw, str): - raise Exception('type not allowed') - try: - return int(raw) - except: - raise Exception('not a integer') - - -def toStr(raw) -> str: # change to str - if raw is None: - return '' - elif isinstance(raw, str): - return raw - elif isinstance(raw, bytes): - return str(raw, encoding = 'utf-8') - else: - raise Exception('type not allowed') - - -def toBool(raw) -> bool: # change to bool - if isinstance(raw, bool): - return raw - if isinstance(raw, int): - raw = str(raw) - elif isinstance(raw, bytes): - raw = str(raw, encoding = 'utf-8') - elif not isinstance(raw, str): - raise Exception('type not allowed') - raw = raw.strip().lower() - if raw == 'true': - return True - elif raw == 'false': - return False - else: - try: - raw = int(raw) - return raw != 0 - except: - raise Exception('not a boolean') - - -def toStrTidy(raw) -> str: # change to str with trim and lower - return toStr(raw).strip().lower() - - -def toHost(raw) -> str: # format to IP address or domain - raw = toStrTidy(raw) - if raw[:1] == '[' and raw[-1:] == ']': # [IPv6] - raw = raw[1:-1] - return raw - - -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'] # 补全可选值 - - allowNone = False - if 'allowNone' in option and option['allowNone']: # 允许为None - allowNone = True - - if not (data[key] is None and allowNone): # 忽略允许None且为None的情况 - if 'format' in option: # 预处理数据 - try: - data[key] = option['format'](data[key]) - except Exception as reason: - raise filterException('Illegal `' + keyName + '`: ' + str(reason)) # 格式化错误 - - # 检查value类型 - if data[key] is None: # 值为None - if not allowNone: # 不允许为None - raise filterException('Unexpected None in `' + keyName + '`') - result[key] = None - else: - dataValue = copy.deepcopy(data[key]) - if isinstance(option['type'], (str, list)) and not isinstance(dataValue, dict): # 子对象下必为dict - raise filterException('Illegal `' + keyName + '`: should be dictionary') - if isinstance(option['type'], str): # 单子对象 - result[key] = __dictCheck(dataValue, objectList, objectList[option['type']], keyName) # 检查子对象 - elif isinstance(option['type'], list): # 多子对象 - subResult = None - errMsg = None - for valueType in option['type']: # 遍历子Object - try: - subResult = __dictCheck(dataValue, objectList, objectList[valueType], keyName) # 尝试检查子对象 - except filterException as reason: - errMsg = str(reason) # 捕获抛出信息 - except Exception as reason: - if str(reason)[:10] == 'index-key:': # index-key匹配错误 - errMsg = str(reason)[10:] - continue - else: # 子对象匹配成功 - break - if subResult is None: # 无匹配子级 - if errMsg is not None: # 存在子级异常信息 - raise filterException(errMsg) - raise filterException('Error in `' + keyName + '` option') - result[key] = subResult - elif not isinstance(data[key], option['type']): # 类型不匹配 - raise filterException('Illegal `' + keyName + '` option') - else: # 检查无误 - result[key] = dataValue - - 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 - - -def __ruleCheck(ruleSet: dict) -> None: # 规则集合法性检查 - if 'rootObject' not in ruleSet: # 根对象检查 - raise Exception('Miss root object') - for objName in ruleSet: # 遍历全部对象 - if objName[-6:] != 'Object': # 对象名必须以Object结尾 - raise Exception('Illegal object name `' + objName + '`') - for key, option in ruleSet[objName].items(): - keyName = '`' + objName + '.' + key + '`' - if 'optional' not in option or not isinstance(option['optional'], bool): # optional检查 - raise Exception('Illegal optional in ' + keyName) - if not option['optional'] and 'default' not in option: # optional为False时应有default - raise Exception('Miss default value in ' + keyName) - - allowNone = False - if 'allowNone' in option and not isinstance(option['allowNone'], bool): - raise Exception('Illegal allowNone in ' + keyName) - if 'allowNone' in option and option['allowNone']: - allowNone = True - - if 'type' not in option: # type值缺失 - raise Exception('Miss type in ' + keyName) - if not isinstance(option['type'], (type, str, list)): # type为变量类型 / str / list - raise Exception('Illegal type in ' + keyName) - if isinstance(option['type'], str) and option['type'] not in ruleSet: # 子Object未定义 - raise Exception('Object `' + option['type'] + '` not found in ' + keyName) - if isinstance(option['type'], list): - for subObjName in option['type']: - if not isinstance(subObjName, str): - raise Exception('Type list must be str in ' + keyName) - if subObjName not in ruleSet: # 子Object未定义 - raise Exception('Object `' + subObjName + '` not found in ' + keyName) - - if 'default' in option: - if option['default'] is None: # default值检查 - if not allowNone: - raise Exception(keyName + ' can\'t be None') - else: - if isinstance(option['type'], type): # type - if not isinstance(option['default'], option['type']): - raise Exception('Error default type in ' + keyName) - else: # str / list - if not isinstance(option['default'], dict): - raise Exception('Default type should be dict in ' + keyName) - - if 'format' in option and not callable(option['format']): # format必须为函数 - raise Exception('Format option must be a function in ' + keyName) - if isinstance(option['type'], type) and 'format' not in option: # 指定变量类型时需有format函数 - raise Exception('Miss format in ' + keyName) - - if 'filter' in option: - if 'errMsg' not in option: # filter与errMsg同时存在 - raise Exception('Miss errMsg option in ' + keyName) - if not callable(option['filter']): # filter必须为函数 - raise Exception('Filter option must be a function in ' + keyName) - if not isinstance(option['type'], type): - raise Exception('Overage filter option in ' + keyName) - if 'errMsg' in option and not isinstance(option['errMsg'], str): # errMsg必须为str - raise Exception('Error message must be str in ' + keyName) - - if 'indexKey' in option and not isinstance(option['indexKey'], bool): # indexKey必为bool - raise Exception('Illegal indexKey in ' + keyName) - - -def ruleFilter(rawData: dict, ruleSet: dict, header: dict) -> tuple[bool, dict or str]: - """ - 使用规则集检查原始数据 - - 原始数据错误: - return False, {reason} - - 原始数据无误: - return True, {dist} - """ - try: - __ruleCheck(ruleSet) # 检查规则集 - except Exception as reason: - return False, 'Filter rules -> ' + str(reason) # 规则集有误 - - data = copy.deepcopy(rawData) - try: - data = __dictCheck(data, ruleSet, ruleSet['rootObject'], '') # 开始检查 - except filterException as reason: # 节点格式错误 - return False, str(reason) - except: - return False, 'Format error' - else: - return True, {**header, **data} diff --git a/ProxyFilter/filter.py b/ProxyFilter/filter.py deleted file mode 100644 index c4713b0..0000000 --- a/ProxyFilter/filter.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/python -# -*- coding:utf-8 -*- - -from ProxyFilter import Shadowsocks -from ProxyFilter import ShadowsocksR -from ProxyFilter import VMess -from ProxyFilter import VLESS -from ProxyFilter import Trojan -from ProxyFilter import TrojanGo -from ProxyFilter import Brook -from ProxyFilter import Hysteria - -def filte(raw: dict, isExtra: bool = False) -> tuple[bool, str or dict]: - """ - 代理信息过滤并格式化 - - 参数无效: - return False, {reason} - - 参数有效: - return True, { - 'type': '...', - '...': '...', - ... - } - """ - try: - if 'type' not in raw: - return False, 'Missing `type` option' - if raw['type'] == 'ss': - status, raw['info'] = Shadowsocks.ssFilter(raw['info'], isExtra) - elif raw['type'] == 'ssr': - status, raw['info'] = ShadowsocksR.ssrFilter(raw['info'], isExtra) - elif raw['type'] == 'vmess': - status, raw['info'] = VMess.vmessFilter(raw['info'], isExtra) - elif raw['type'] == 'vless': - status, raw['info'] = VLESS.vlessFilter(raw['info'], isExtra) - elif raw['type'] == 'trojan': - status, raw['info'] = Trojan.trojanFilter(raw['info'], isExtra) - elif raw['type'] == 'trojan-go': - status, raw['info'] = TrojanGo.trojanGoFilter(raw['info'], isExtra) - elif raw['type'] == 'brook': - status, raw['info'] = Brook.filte(raw['info'], isExtra) - elif raw['type'] == 'hysteria': - status, raw['info'] = Hysteria.filte(raw['info'], isExtra) - else: - return False, 'Unknown proxy type' - return status, raw - except: - pass - return False, 'Unknown error' diff --git a/docs/ProxyObject/Brook.md b/docs/ProxyObject/Brook.md index d2c1fae..b78c67f 100644 --- a/docs/ProxyObject/Brook.md +++ b/docs/ProxyObject/Brook.md @@ -82,7 +82,7 @@ ### raw + 类型:*bool* -+ 说明:是否直接传输原始数据(即`--withoutBrookProtocol`) ++ 说明:传输原始数据(即 `--withoutBrookProtocol` ) + 缺省:`False` + 限制:无 diff --git a/docs/ProxyObject/Trojan.md b/docs/ProxyObject/Trojan.md index 1cae395..a6816fc 100644 --- a/docs/ProxyObject/Trojan.md +++ b/docs/ProxyObject/Trojan.md @@ -275,7 +275,7 @@ + 类型:*str* + 说明:TLS握手SNI字段 -+ 缺省:`obfsObject.host[0]` / `wsObject.host` / `h2Object.host[0]` / `空` ++ 缺省:`空` + 限制:无 ### alpn @@ -309,7 +309,7 @@ + 类型:*str* + 说明:TLS握手SNI字段 -+ 缺省:`obfsObject.host[0]` / `wsObject.host` / `h2Object.host[0]` / `空` ++ 缺省:`空` + 限制:无 ### alpn diff --git a/docs/ProxyObject/VLESS.md b/docs/ProxyObject/VLESS.md index 2607192..b42efe3 100644 --- a/docs/ProxyObject/VLESS.md +++ b/docs/ProxyObject/VLESS.md @@ -283,7 +283,7 @@ + 类型:*str* + 说明:TLS握手SNI字段 -+ 缺省:`obfsObject.host[0]` / `wsObject.host` / `h2Object.host[0]` / `空` ++ 缺省:`空` + 限制:无 ### alpn @@ -317,7 +317,7 @@ + 类型:*str* + 说明:TLS握手SNI字段 -+ 缺省:`obfsObject.host[0]` / `wsObject.host` / `h2Object.host[0]` / `空` ++ 缺省:`空` + 限制:无 ### alpn diff --git a/docs/ProxyObject/VMess.md b/docs/ProxyObject/VMess.md index 5f32bf2..e9adcc1 100644 --- a/docs/ProxyObject/VMess.md +++ b/docs/ProxyObject/VMess.md @@ -72,7 +72,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -104,7 +104,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -144,7 +144,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -176,9 +176,9 @@ ### secure -+ 类型:[*secureObject*](#secureobject) ++ 类型:[*tlsObject*](#tlsobject) + 说明:TLS加密选项 -+ 缺省:`None` ++ 缺省:`tlsObject` + 限制:无 ## quicObject @@ -216,9 +216,9 @@ ### secure -+ 类型:[*secureObject*](#secureobject) ++ 类型:[*tlsObject*](#tlsobject) + 说明:TLS加密选项 -+ 缺省:`secureObject` ++ 缺省:`tlsObject` + 限制:无 ## grpcObject @@ -248,7 +248,7 @@ ### secure -+ 类型:*None* / [*secureObject*](#secureobject) ++ 类型:*None* / [*tlsObject*](#tlsobject) + 说明:TLS加密选项 + 缺省:`None` + 限制:无 @@ -276,7 +276,7 @@ + 缺省:`/` + 限制:无 -## secureObject +## tlsObject ``` { @@ -290,7 +290,7 @@ + 类型:*str* + 说明:TLS握手SNI字段 -+ 缺省:`obfsObject.host[0]` / `wsObject.host` / `h2Object.host[0]` / `空` ++ 缺省:`空` + 限制:无 ### alpn