From 05d450d445b229d83da03c53731e1853a5f0b625 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Wed, 3 Aug 2022 19:21:11 +0800 Subject: [PATCH 01/18] fix: remove redundant `/` --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index cf82d47..8af67f7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -166,12 +166,12 @@ 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-* From 3e7b2367d54666c25011ab4d2345021fbe19ce98 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Wed, 3 Aug 2022 23:02:07 +0800 Subject: [PATCH 02/18] fix: redundant layer in ss-python build --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8af67f7..567a34b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ From a34db0bfcc6b07262d2f0f6033d27bc3f937265d Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Thu, 4 Aug 2022 20:03:50 +0800 Subject: [PATCH 03/18] build: remove `v2ctl` and `caddy` --- Dockerfile | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index 567a34b..592123f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -268,30 +268,24 @@ COPY --from=plugin-1 /plugins/ /release/ COPY --from=plugin-2 /plugins/ /release/ COPY --from=plugin-3 /plugins/ /release/ -# 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 upx -9 /tmp/*ray # Compile trojan-go FROM ${GO17_IMG} AS trojan-go @@ -370,14 +364,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 +377,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 @@ -491,15 +476,13 @@ RUN mkdir -p /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=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/ From 4fff5bd74149f2b83b982000e647976278fc43b0 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Thu, 4 Aug 2022 20:12:28 +0800 Subject: [PATCH 04/18] fix: v2ray build error --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 592123f..ceaee92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -280,7 +280,7 @@ 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}/ +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/ From b9be638ddb84dff5787f9a083aa3da722aff1126 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Thu, 4 Aug 2022 21:14:31 +0800 Subject: [PATCH 05/18] perf: upx under multi-thread --- Dockerfile | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index ceaee92..50f5897 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 @@ -174,7 +174,7 @@ RUN cargo build --target-dir ./ --release && \ 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,13 +260,13 @@ 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 xray-core and v2fly-core FROM ${GO18_IMG} AS v2ray @@ -285,7 +285,7 @@ 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/*ray +RUN ls /tmp/*ray | xargs -P0 -n1 upx -9 # Compile trojan-go FROM ${GO17_IMG} AS trojan-go @@ -391,7 +391,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 @@ -475,7 +475,7 @@ 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=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/ From e2f824c1c8c5df24fc28a5454d125f6d08018300 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Thu, 4 Aug 2022 21:28:01 +0800 Subject: [PATCH 06/18] perf: combine gost and gost-v3 --- Dockerfile | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 50f5897..699f252 100644 --- a/Dockerfile +++ b/Dockerfile @@ -314,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 From 0c6fe80b99477fbb7b63d30b16088465fbbadbba Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Fri, 5 Aug 2022 12:25:33 +0800 Subject: [PATCH 07/18] feat: filter design --- Basis/Functions.py | 20 +++++++++++++++ Filter/Plugin.py | 5 ++++ Filter/Shadowsocks.py | 56 +++++++++++++++++++++++++++++++++++++++++ Filter/__init__.py | 58 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 Filter/Plugin.py create mode 100644 Filter/Shadowsocks.py create mode 100644 Filter/__init__.py diff --git a/Basis/Functions.py b/Basis/Functions.py index ffb0565..544de97 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -86,3 +86,23 @@ def networkStatus() -> list: # get all network connections }) logging.debug('Network status -> found %i connections' % len(result)) return result + + +def toInt(raw) -> int: + pass + + +def toStr(raw) -> str: + pass + + +def toBool(raw) -> bool: + pass + + +def isHost(raw) -> bool: + pass + + +def isPort(raw) -> bool: + pass diff --git a/Filter/Plugin.py b/Filter/Plugin.py new file mode 100644 index 0000000..2b98da0 --- /dev/null +++ b/Filter/Plugin.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +def pluginFormat(pluginName: str) -> str: + return pluginName diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py new file mode 100644 index 0000000..9702174 --- /dev/null +++ b/Filter/Shadowsocks.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Filter.Plugin import pluginFormat +from Basis.Functions import toInt, toStr +from Basis.Functions import isHost, isPort +from Basis.Constant import ssMethods, pluginClients + +pluginObject = { + 'type': { + 'type': str, + 'format': lambda s: pluginFormat(toStr(s).strip().lower()), + 'filter': lambda s: s in pluginClients, + 'errMsg': 'Unknown SIP003 plugin' + }, + 'param': { + 'optional': False, + 'default': '', + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid SIP003 param' + } +} + +ssObject = { + 'server': { + 'type': str, + 'format': toStr, + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'method': { + 'type': str, + 'format': lambda s: toStr(s).strip().lower().replace('_', '-'), + 'filter': lambda s: s in ssMethods, + 'errMsg': 'Unknown Shadowsocks method' + }, + 'passwd': { + 'type': str, + 'format': toStr, + 'errMsg': 'Invalid password content' + }, + 'plugin': { + 'optional': False, + 'default': None, + 'allowNone': True, + 'type': pluginObject, + 'errMsg': 'Invalid pluginObject' + } +} diff --git a/Filter/__init__.py b/Filter/__init__.py new file mode 100644 index 0000000..50635e4 --- /dev/null +++ b/Filter/__init__.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +xxObject = { # a dict that describe multi-field + 'field_1': { + 'optional': ..., # field force require or not + 'default': ..., # default value when field is not exist (optional == True) + 'allowNone': ..., # whether the value can be None (override the format and filter process) + 'type': ..., # type of field content (in filter process) (python type / dict) + 'multiSub': ..., # whether there are multi subObject (type is dict) + 'indexKey': ..., # index key of subObject (type is dict and multiSub == True) + 'format': ..., # format function (before filter process) (invalid content -> throw error) + 'filter': ..., # filter function -> bool (throw error when return False) + 'errMsg': ..., # raise message if there is something error + }, + 'field_2': { + ... + }, + ... +} + + +default value + + optional: False + + default: None + + allowNone: False + + format: lambda -> return same thing + + type: force require + + multiSub: False + + indexKey: 'type' + + filter: lambda -> return True + + errMsg: 'filter error' + + +pre process + => field not exist + => optional == False -> throw errMsg + => optional == True -> set as default value -> continue + => field exist + => filed is None + => allowNone is False -> throw errMsg + => allowNone is True -> break + => field is not None -> continue + +format process -> set as field value (maybe throw error -> catch and throw errMsg) + +filter process + => type is `python type` -> compare with field type -> filter function check + => type is dict + => multiSub == False -> recursive check + => multiSub == True + => field content is not dict or not include indexKey -> throw error + => others + => indexKey's content not in type (dict) -> throw error + => others -> recursive check + +""" From 435979e76006a545b49a03459c49fa611a995609 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Fri, 5 Aug 2022 13:09:09 +0800 Subject: [PATCH 08/18] feat: self filter object --- Basis/Filter.py | 85 ++++++++++++++++++++++++++++++++++++++++++++++ Filter/__init__.py | 8 +++-- 2 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 Basis/Filter.py diff --git a/Basis/Filter.py b/Basis/Filter.py new file mode 100644 index 0000000..7384495 --- /dev/null +++ b/Basis/Filter.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +filterObject = { + 'optional': { + 'optional': True, # `optional` is not force require + 'default': False, # disable `optional` option in default + 'allowNone': False, # `optional` couldn't be None + 'type': bool, + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `optional` key' + }, + 'default': { + 'optional': True, # `default` is not force require + 'default': None, + 'allowNone': True, # `default` can be None + 'type': any, # skip type check + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `default` key' + }, + 'allowNone': { + 'optional': True, # `allowNone` is not force require + 'default': False, # disable `allowNone` option in default + 'allowNone': False, # `allowNone` couldn't be None + 'type': bool, + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `allowNone` key' + }, + 'type': { + 'optional': False, # `type` is force require + 'allowNone': False, # `type` couldn't be None + 'type': [any, type, list, dict], + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `type` key' + }, + 'multiSub': { + 'optional': True, # `multiSub` is not force require + 'default': False, # disable `multiSub` option in default + 'allowNone': False, # `multiSub` couldn't be None + 'type': bool, + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `multiSub` key' + }, + 'indexKey': { + 'optional': True, # `indexKey` is not force require + 'default': 'type', + 'allowNone': False, # `indexKey` couldn't be None + 'type': str, + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `indexKey` key' + }, + 'format': { + 'optional': True, # `format` is not force require + 'default': lambda x: x, # don't change anything + 'allowNone': False, # `format` couldn't be None + 'type': any, + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `format` key' + }, + 'filter': { + 'optional': True, # `filter` is not force require + 'default': lambda x: True, # always pass filter + 'allowNone': False, # `filter` couldn't be None + 'type': any, + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `filter` key' + }, + 'errMsg': { + 'optional': True, # `errMsg` is not force require + 'default': 'filter error', + 'allowNone': False, # `errMsg` couldn't be None + 'type': str, + 'format': lambda x: x, # return same value + 'filter': lambda b: True, # always return True + 'errMsg': 'Invalid `errMsg` key' + }, +} diff --git a/Filter/__init__.py b/Filter/__init__.py index 50635e4..e0137b1 100644 --- a/Filter/__init__.py +++ b/Filter/__init__.py @@ -7,7 +7,7 @@ xxObject = { # a dict that describe multi-field 'optional': ..., # field force require or not 'default': ..., # default value when field is not exist (optional == True) 'allowNone': ..., # whether the value can be None (override the format and filter process) - 'type': ..., # type of field content (in filter process) (python type / dict) + 'type': ..., # type of field content (in filter process) (any / type / list / dict) 'multiSub': ..., # whether there are multi subObject (type is dict) 'indexKey': ..., # index key of subObject (type is dict and multiSub == True) 'format': ..., # format function (before filter process) (invalid content -> throw error) @@ -46,8 +46,10 @@ pre process format process -> set as field value (maybe throw error -> catch and throw errMsg) filter process - => type is `python type` -> compare with field type -> filter function check - => type is dict + => type is `any` -> filter function check (skip type compare) + => type is `type` -> compare with field type -> filter function check + => type is `list` -> compare every type in list with field -> filter function check + => type is `dict` => multiSub == False -> recursive check => multiSub == True => field content is not dict or not include indexKey -> throw error From 15a7ac72d591f4595f7957f56afef51a233c6441 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 6 Aug 2022 12:04:51 +0800 Subject: [PATCH 09/18] feat: add Filter support --- Basis/Filter.py | 109 ++++++++++++++++++++++++++++-------------- Filter/Shadowsocks.py | 12 +++-- demo.py | 7 +++ 3 files changed, 87 insertions(+), 41 deletions(-) create mode 100755 demo.py diff --git a/Basis/Filter.py b/Basis/Filter.py index 7384495..76bdf7d 100644 --- a/Basis/Filter.py +++ b/Basis/Filter.py @@ -1,85 +1,120 @@ #!/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 - 'type': bool, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `optional` key' }, 'default': { + 'type': any, # skip type check 'optional': True, # `default` is not force require 'default': None, 'allowNone': True, # `default` can be None - 'type': any, # skip type check - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `default` key' }, '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': bool, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `allowNone` key' }, 'type': { + 'type': [any, type, list, dict], 'optional': False, # `type` is force require 'allowNone': False, # `type` couldn't be None - 'type': [any, type, list, dict], - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `type` key' }, 'multiSub': { + 'type': bool, 'optional': True, # `multiSub` is not force require 'default': False, # disable `multiSub` option in default 'allowNone': False, # `multiSub` couldn't be None - 'type': bool, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `multiSub` key' }, 'indexKey': { + 'type': str, 'optional': True, # `indexKey` is not force require 'default': 'type', 'allowNone': False, # `indexKey` couldn't be None - 'type': str, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `indexKey` key' }, '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 - 'type': any, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `format` key' }, 'filter': { + 'type': any, 'optional': True, # `filter` is not force require 'default': lambda x: True, # always pass filter 'allowNone': False, # `filter` couldn't be None - 'type': any, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `filter` key' }, 'errMsg': { + 'type': str, 'optional': True, # `errMsg` is not force require 'default': 'filter error', 'allowNone': False, # `errMsg` couldn't be None - 'type': str, - 'format': lambda x: x, # return same value - 'filter': lambda b: True, # always return True - 'errMsg': 'Invalid `errMsg` key' - }, + } } + +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('Miss `%s` field' % key) + data[key] = rule['default'] # set default value + else: # key exist + data[key] = raw[key] + # format process (data --[format]--> data) + try: + data[key] = rule['format'](data[key]) # run format + except: + raise RuntimeError(rule['errMsg']) # format error + 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 + # 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 not rule['multiSub']: # single subObject + data[key] = Filter(data[key], rule['type']) + else: # multi subObject + # TODO: multi sub filter + pass + 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) # format itself diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py index 9702174..0f038ec 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -4,9 +4,10 @@ from Filter.Plugin import pluginFormat from Basis.Functions import toInt, toStr from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter from Basis.Constant import ssMethods, pluginClients -pluginObject = { +pluginObject = rulesFilter({ 'type': { 'type': str, 'format': lambda s: pluginFormat(toStr(s).strip().lower()), @@ -20,9 +21,9 @@ pluginObject = { 'format': toStr, 'errMsg': 'Invalid SIP003 param' } -} +}) -ssObject = { +ssObject = rulesFilter({ 'server': { 'type': str, 'format': toStr, @@ -53,4 +54,7 @@ ssObject = { 'type': pluginObject, 'errMsg': 'Invalid pluginObject' } -} +}) + +from pprint import pprint +pprint(ssObject, sort_dicts = False) diff --git a/demo.py b/demo.py new file mode 100755 index 0000000..7be03fd --- /dev/null +++ b/demo.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import Filter.Shadowsocks + +# from pprint import pprint +# from Basis.Filter import filterObject +# pprint(filterObject, sort_dicts = False) From ae8401118800d2049cc4e629ced4e3cba0149df3 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 6 Aug 2022 16:38:43 +0800 Subject: [PATCH 10/18] update: complete filter module --- Basis/Filter.py | 22 +++++++++++----- Basis/Functions.py | 61 +++++++++++++++++++++++++++++-------------- Filter/Plugin.py | 55 +++++++++++++++++++++++++++++++++++++- Filter/Shadowsocks.py | 29 ++++---------------- demo.py | 21 ++++++++++++--- 5 files changed, 134 insertions(+), 54 deletions(-) diff --git a/Basis/Filter.py b/Basis/Filter.py index 76bdf7d..36860f5 100644 --- a/Basis/Filter.py +++ b/Basis/Filter.py @@ -54,7 +54,7 @@ filterObject = { 'errMsg': { 'type': str, 'optional': True, # `errMsg` is not force require - 'default': 'filter error', + 'default': 'Filter error', 'allowNone': False, # `errMsg` couldn't be None } } @@ -75,7 +75,7 @@ def Filter(raw: dict, rules: dict) -> dict: # 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('Miss `%s` field' % key) + raise RuntimeError('Missing `%s` field' % key) data[key] = rule['default'] # set default value else: # key exist data[key] = raw[key] @@ -97,11 +97,21 @@ def Filter(raw: dict, rules: dict) -> dict: 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 - data[key] = Filter(data[key], rule['type']) + subRules = rule['type'] else: # multi subObject - # TODO: multi sub filter - pass + if rule['indexKey'] not in data[key]: # confirm index key exist + raise RuntimeError('Index key not found in `%s`' % key) + subType = data[key][rule['indexKey']] + 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') @@ -117,4 +127,4 @@ def rulesFilter(rules: dict) -> dict: return result -filterObject = rulesFilter(filterObject) # format itself +filterObject = rulesFilter(filterObject) # self-format diff --git a/Basis/Functions.py b/Basis/Functions.py index 544de97..b718065 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -10,6 +10,17 @@ from IPy import IP from Basis.Logger import logging +def isHost(raw) -> bool: + # TODO: isHost function + return True + + +def isPort(port: int) -> bool: + if type(port) != int: + return False + return 1 <= port <= 65535 # 1 ~ 65535 + + def md5Sum(data: str, encode: str = 'utf-8') -> str: return hashlib.md5(data.encode(encoding = encode)).hexdigest() @@ -42,6 +53,36 @@ def genUUID() -> str: # generate uuid v5 )) +def toInt(raw) -> int: + try: + return int(raw) + except: + raise RuntimeError('Unable convert to int') + + +def toStr(raw, acceptNone: bool = True) -> str: + if raw is None and acceptNone: # None -> '' + return '' + if isinstance(raw, bytes): # bytes -> str + return str(raw, encoding = 'utf-8') + try: + return str(raw) + except: + raise RuntimeError('Unable convert to str') + + +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') @@ -86,23 +127,3 @@ def networkStatus() -> list: # get all network connections }) logging.debug('Network status -> found %i connections' % len(result)) return result - - -def toInt(raw) -> int: - pass - - -def toStr(raw) -> str: - pass - - -def toBool(raw) -> bool: - pass - - -def isHost(raw) -> bool: - pass - - -def isPort(raw) -> bool: - pass diff --git a/Filter/Plugin.py b/Filter/Plugin.py index 2b98da0..492b500 100644 --- a/Filter/Plugin.py +++ b/Filter/Plugin.py @@ -1,5 +1,58 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import copy +from Basis.Functions import toStr +from Basis.Filter import rulesFilter +from Basis.Constant import pluginClients + +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(toStr(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: - return pluginName + pluginName = pluginName.strip().lower().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 index 0f038ec..1053eee 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -1,27 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from Filter.Plugin import pluginFormat +from Basis.Filter import rulesFilter +from Filter.Plugin import pluginObject +from Basis.Constant import ssAllMethods from Basis.Functions import toInt, toStr from Basis.Functions import isHost, isPort -from Basis.Filter import Filter, rulesFilter -from Basis.Constant import ssMethods, pluginClients - -pluginObject = rulesFilter({ - 'type': { - 'type': str, - 'format': lambda s: pluginFormat(toStr(s).strip().lower()), - 'filter': lambda s: s in pluginClients, - 'errMsg': 'Unknown SIP003 plugin' - }, - 'param': { - 'optional': False, - 'default': '', - 'type': str, - 'format': toStr, - 'errMsg': 'Invalid SIP003 param' - } -}) ssObject = rulesFilter({ 'server': { @@ -39,7 +23,7 @@ ssObject = rulesFilter({ 'method': { 'type': str, 'format': lambda s: toStr(s).strip().lower().replace('_', '-'), - 'filter': lambda s: s in ssMethods, + 'filter': lambda s: s in ssAllMethods, 'errMsg': 'Unknown Shadowsocks method' }, 'passwd': { @@ -48,13 +32,10 @@ ssObject = rulesFilter({ 'errMsg': 'Invalid password content' }, 'plugin': { - 'optional': False, + 'optional': True, 'default': None, 'allowNone': True, 'type': pluginObject, 'errMsg': 'Invalid pluginObject' } }) - -from pprint import pprint -pprint(ssObject, sort_dicts = False) diff --git a/demo.py b/demo.py index 7be03fd..ddaa726 100755 --- a/demo.py +++ b/demo.py @@ -1,7 +1,22 @@ #!/usr/bin/env python -import Filter.Shadowsocks +from pprint import pprint +from Basis.Filter import Filter +from Basis.Filter import filterObject +from Filter.Shadowsocks import ssObject -# from pprint import pprint -# from Basis.Filter import filterObject +# pprint(ssObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) + +ssProxy = { + 'server': '1.1.1.1', + 'port': '12345', + 'method': 'none', + 'passwd': 'dnomd343', + 'plugin': { + 'type': 'obfs', + + } +} +ret = Filter(ssProxy, ssObject) +pprint(ret, sort_dicts = False) From e1816b53cbe017779749eae33c9a6314e34f54aa Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 6 Aug 2022 18:06:03 +0800 Subject: [PATCH 11/18] feat: filter of ShadowsocksR --- Basis/Functions.py | 28 +++++++++++++--- Filter/Shadowsocks.py | 2 +- Filter/ShadowsocksR.py | 74 ++++++++++++++++++++++++++++++++++++++++++ demo.py | 18 ++++++++-- 4 files changed, 114 insertions(+), 8 deletions(-) create mode 100644 Filter/ShadowsocksR.py diff --git a/Basis/Functions.py b/Basis/Functions.py index b718065..923c439 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,9 +11,28 @@ from IPy import IP from Basis.Logger import logging -def isHost(raw) -> bool: - # TODO: isHost function - return True +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: @@ -22,7 +42,7 @@ def isPort(port: int) -> bool: 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: diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py index 1053eee..7b23a54 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -10,7 +10,7 @@ from Basis.Functions import isHost, isPort ssObject = rulesFilter({ 'server': { 'type': str, - 'format': toStr, + 'format': lambda s: toStr(s).strip().lower(), 'filter': isHost, 'errMsg': 'Invalid server address' }, diff --git a/Filter/ShadowsocksR.py b/Filter/ShadowsocksR.py new file mode 100644 index 0000000..807ed26 --- /dev/null +++ b/Filter/ShadowsocksR.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis.Filter import rulesFilter +from Basis.Functions import toInt, toStr +from Basis.Functions import isHost, isPort +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': lambda s: toStr(s).strip().lower(), + 'filter': isHost, + 'errMsg': 'Invalid server address' + }, + 'port': { + 'type': int, + 'format': toInt, + 'filter': isPort, + 'errMsg': 'Invalid port number' + }, + 'method': { + 'type': str, + 'format': lambda s: toStr(s).strip().lower().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' + } +}) diff --git a/demo.py b/demo.py index ddaa726..360f70b 100755 --- a/demo.py +++ b/demo.py @@ -4,8 +4,10 @@ from pprint import pprint from Basis.Filter import Filter from Basis.Filter import filterObject from Filter.Shadowsocks import ssObject +from Filter.ShadowsocksR import ssrObject # pprint(ssObject, sort_dicts = False) +# pprint(ssrObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) ssProxy = { @@ -14,9 +16,19 @@ ssProxy = { 'method': 'none', 'passwd': 'dnomd343', 'plugin': { - 'type': 'obfs', - + 'type': 'obfs' } } -ret = Filter(ssProxy, ssObject) + +ssrProxy = { + 'server': '1.1.1.1', + 'port': 12345, + 'method': 'table', + 'passwd': 'dnomd343', + 'protocol': 'auth_chain-a', + 'obfs': 'http_post' +} + +# ret = Filter(ssProxy, ssObject) +ret = Filter(ssrProxy, ssrObject) pprint(ret, sort_dicts = False) From e28832f45078178407371eb0d3ed062daffcc970 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 6 Aug 2022 20:43:05 +0800 Subject: [PATCH 12/18] feat: filter of VMess --- Basis/Filter.py | 10 +- Basis/Functions.py | 12 +- Filter/Plugin.py | 6 +- Filter/Shadowsocks.py | 6 +- Filter/ShadowsocksR.py | 6 +- Filter/V2ray.py | 239 ++++++++++++++++++++++++++++++++++++++ Filter/VMess.py | 62 ++++++++++ demo.py | 22 +++- docs/ProxyObject/VMess.md | 18 +-- 9 files changed, 353 insertions(+), 28 deletions(-) create mode 100644 Filter/V2ray.py create mode 100644 Filter/VMess.py diff --git a/Basis/Filter.py b/Basis/Filter.py index 36860f5..27e7731 100644 --- a/Basis/Filter.py +++ b/Basis/Filter.py @@ -80,14 +80,14 @@ def Filter(raw: dict, rules: dict) -> dict: else: # key exist data[key] = raw[key] # format process (data --[format]--> data) - try: - data[key] = rule['format'](data[key]) # run format - except: - raise RuntimeError(rule['errMsg']) # format error 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] / ... @@ -104,7 +104,7 @@ def Filter(raw: dict, rules: dict) -> dict: else: # multi subObject if rule['indexKey'] not in data[key]: # confirm index key exist raise RuntimeError('Index key not found in `%s`' % key) - subType = data[key][rule['indexKey']] + 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] diff --git a/Basis/Functions.py b/Basis/Functions.py index 923c439..daefe4f 100644 --- a/Basis/Functions.py +++ b/Basis/Functions.py @@ -38,7 +38,7 @@ def isHost(host: str) -> bool: def isPort(port: int) -> bool: if type(port) != int: return False - return 1 <= port <= 65535 # 1 ~ 65535 + return port in range(1, 65536) # 1 ~ 65535 def md5Sum(data: str, encode: str = 'utf-8') -> str: @@ -80,9 +80,9 @@ def toInt(raw) -> int: raise RuntimeError('Unable convert to int') -def toStr(raw, acceptNone: bool = True) -> str: - if raw is None and acceptNone: # None -> '' - return '' +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: @@ -91,6 +91,10 @@ def toStr(raw, acceptNone: bool = True) -> str: 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) diff --git a/Filter/Plugin.py b/Filter/Plugin.py index 492b500..559676e 100644 --- a/Filter/Plugin.py +++ b/Filter/Plugin.py @@ -2,9 +2,9 @@ # -*- coding: utf-8 -*- import copy -from Basis.Functions import toStr from Basis.Filter import rulesFilter from Basis.Constant import pluginClients +from Basis.Functions import toStr, toStrTidy pluginAlias = { 'obfs-local': {'obfs', 'simple-obfs'}, @@ -24,7 +24,7 @@ pluginAlias = { pluginObject = rulesFilter({ 'type': { 'type': str, - 'format': lambda s: pluginFormat(toStr(s)), + 'format': lambda s: pluginFormat(toStrTidy(s)), 'filter': lambda s: s in pluginClients, 'errMsg': 'Unknown SIP003 plugin' }, @@ -48,7 +48,7 @@ def loadAlias() -> None: def pluginFormat(pluginName: str) -> str: - pluginName = pluginName.strip().lower().replace('_', '-') + pluginName = pluginName.replace('_', '-') for plugin, alias in pluginAlias.items(): if pluginName in alias: return plugin diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py index 7b23a54..9b44301 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -4,13 +4,13 @@ from Basis.Filter import rulesFilter from Filter.Plugin import pluginObject from Basis.Constant import ssAllMethods -from Basis.Functions import toInt, toStr from Basis.Functions import isHost, isPort +from Basis.Functions import toInt, toStr, toStrTidy ssObject = rulesFilter({ 'server': { 'type': str, - 'format': lambda s: toStr(s).strip().lower(), + 'format': toStrTidy, 'filter': isHost, 'errMsg': 'Invalid server address' }, @@ -22,7 +22,7 @@ ssObject = rulesFilter({ }, 'method': { 'type': str, - 'format': lambda s: toStr(s).strip().lower().replace('_', '-'), + 'format': lambda s: toStrTidy(s).replace('_', '-'), 'filter': lambda s: s in ssAllMethods, 'errMsg': 'Unknown Shadowsocks method' }, diff --git a/Filter/ShadowsocksR.py b/Filter/ShadowsocksR.py index 807ed26..749a9b3 100644 --- a/Filter/ShadowsocksR.py +++ b/Filter/ShadowsocksR.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- from Basis.Filter import rulesFilter -from Basis.Functions import toInt, toStr from Basis.Functions import isHost, isPort +from Basis.Functions import toInt, toStr, toStrTidy from Basis.Constant import ssrMethods, ssrProtocols, ssrObfuscations @@ -20,7 +20,7 @@ def ssrObfsFormat(obfs: str) -> str: ssrObject = rulesFilter({ 'server': { 'type': str, - 'format': lambda s: toStr(s).strip().lower(), + 'format': toStrTidy, 'filter': isHost, 'errMsg': 'Invalid server address' }, @@ -32,7 +32,7 @@ ssrObject = rulesFilter({ }, 'method': { 'type': str, - 'format': lambda s: toStr(s).strip().lower().replace('_', '-'), + 'format': lambda s: toStrTidy(s).replace('_', '-'), 'filter': lambda s: s in ssrMethods, 'errMsg': 'Unknown ShadowsocksR method' }, diff --git a/Filter/V2ray.py b/Filter/V2ray.py new file mode 100644 index 0000000..6ba6d27 --- /dev/null +++ b/Filter/V2ray.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis.Filter import rulesFilter +from Basis.Constant import quicMethods, udpObfuscations +from Basis.Functions import 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 tlsObject' + } +}) + +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 tlsObject' + } +}) + +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 tlsObject' + } +}) + +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 tlsObject' + } +}) + +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 tlsObject' + } +}) + +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 tlsObject' + } +}) diff --git a/Filter/VMess.py b/Filter/VMess.py new file mode 100644 index 0000000..b91120f --- /dev/null +++ b/Filter/VMess.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Filter import V2ray +from Basis.Filter import rulesFilter +from Basis.Constant import vmessMethods +from Basis.Functions import isHost, isPort +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': 'Unknown 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' + } +}) + +# TODO: add SNI / ws host / h2 host diff --git a/demo.py b/demo.py index 360f70b..6e8cddd 100755 --- a/demo.py +++ b/demo.py @@ -5,9 +5,11 @@ from Basis.Filter import Filter from Basis.Filter import filterObject from Filter.Shadowsocks import ssObject from Filter.ShadowsocksR import ssrObject +from Filter.VMess import vmessObject # pprint(ssObject, sort_dicts = False) # pprint(ssrObject, sort_dicts = False) +# pprint(vmessObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) ssProxy = { @@ -29,6 +31,24 @@ ssrProxy = { 'obfs': 'http_post' } +vmessProxy = { + 'server': '1.1.1.1', + 'port': b'12345', + 'id': 'c8783403-64d5-4b6d-8cf4-bd3988d01b6c', + 'aid': '64', + 'stream': { + 'type': 'GRPC', + 'service': 'no-gfw', + 'mode': ' multi ', + 'secure': { + 'sni': ' DNOMD343.top', + 'alpn': 'h2, http/1.1', + 'verify': 'False ' + } + } +} + # ret = Filter(ssProxy, ssObject) -ret = Filter(ssrProxy, ssrObject) +# ret = Filter(ssrProxy, ssrObject) +ret = Filter(vmessProxy, vmessObject) pprint(ret, sort_dicts = False) diff --git a/docs/ProxyObject/VMess.md b/docs/ProxyObject/VMess.md index 5f32bf2..fc8b526 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 ``` { From 6281e08dc4295578b666b9d19ed4553411d49d4d Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 6 Aug 2022 22:13:49 +0800 Subject: [PATCH 13/18] feat: filter of VLESS and Trojan --- Basis/Filter.py | 2 +- Filter/Trojan.py | 43 +++++++++++++ Filter/V2ray.py | 12 ++-- Filter/VLESS.py | 51 +++++++++++++++ Filter/VMess.py | 2 +- Filter/Xray.py | 164 +++++++++++++++++++++++++++++++++++++++++++++++ demo.py | 59 ++++++++++++++++- 7 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 Filter/Trojan.py create mode 100644 Filter/VLESS.py create mode 100644 Filter/Xray.py diff --git a/Basis/Filter.py b/Basis/Filter.py index 27e7731..fdc81a5 100644 --- a/Basis/Filter.py +++ b/Basis/Filter.py @@ -103,7 +103,7 @@ def Filter(raw: dict, rules: dict) -> dict: subRules = rule['type'] else: # multi subObject if rule['indexKey'] not in data[key]: # confirm index key exist - raise RuntimeError('Index key not found in `%s`' % key) + 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)) diff --git a/Filter/Trojan.py b/Filter/Trojan.py new file mode 100644 index 0000000..3eb118b --- /dev/null +++ b/Filter/Trojan.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Filter import Xray +from Basis.Filter import rulesFilter +from Basis.Functions import isHost, isPort +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' + } +}) diff --git a/Filter/V2ray.py b/Filter/V2ray.py index 6ba6d27..cd30590 100644 --- a/Filter/V2ray.py +++ b/Filter/V2ray.py @@ -67,7 +67,7 @@ tcpObject = rulesFilter({ 'default': None, 'allowNone': True, 'type': tlsObject, - 'errMsg': 'Invalid tlsObject' + 'errMsg': 'Invalid secure options' } }) @@ -99,7 +99,7 @@ kcpObject = rulesFilter({ 'default': None, 'allowNone': True, 'type': tlsObject, - 'errMsg': 'Invalid tlsObject' + 'errMsg': 'Invalid secure options' } }) @@ -138,7 +138,7 @@ wsObject = rulesFilter({ 'default': None, 'allowNone': True, 'type': tlsObject, - 'errMsg': 'Invalid tlsObject' + 'errMsg': 'Invalid secure options' } }) @@ -167,7 +167,7 @@ h2Object = rulesFilter({ 'optional': True, 'default': {}, 'type': tlsObject, - 'errMsg': 'Invalid tlsObject' + 'errMsg': 'Invalid secure options' } }) @@ -205,7 +205,7 @@ quicObject = rulesFilter({ 'optional': True, 'default': {}, 'type': tlsObject, - 'errMsg': 'Invalid tlsObject' + 'errMsg': 'Invalid secure options' } }) @@ -234,6 +234,6 @@ grpcObject = rulesFilter({ 'default': None, 'allowNone': True, 'type': tlsObject, - 'errMsg': 'Invalid tlsObject' + 'errMsg': 'Invalid secure options' } }) diff --git a/Filter/VLESS.py b/Filter/VLESS.py new file mode 100644 index 0000000..ad7133d --- /dev/null +++ b/Filter/VLESS.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Filter import Xray +from Basis.Filter import rulesFilter +from Basis.Functions import isHost, isPort +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' + } +}) diff --git a/Filter/VMess.py b/Filter/VMess.py index b91120f..f179c73 100644 --- a/Filter/VMess.py +++ b/Filter/VMess.py @@ -31,7 +31,7 @@ vmessObject = rulesFilter({ 'id': { 'type': str, 'format': toStrTidy, - 'errMsg': 'Unknown VMess ID' + 'errMsg': 'Invalid VMess ID' }, 'aid': { 'optional': True, diff --git a/Filter/Xray.py b/Filter/Xray.py new file mode 100644 index 0000000..4102034 --- /dev/null +++ b/Filter/Xray.py @@ -0,0 +1,164 @@ +#!/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 +}) diff --git a/demo.py b/demo.py index 6e8cddd..43d0124 100755 --- a/demo.py +++ b/demo.py @@ -6,10 +6,14 @@ from Basis.Filter import filterObject from Filter.Shadowsocks import ssObject from Filter.ShadowsocksR import ssrObject from Filter.VMess import vmessObject +from Filter.VLESS import vlessObject +from Filter.Trojan import trojanObject # pprint(ssObject, sort_dicts = False) # pprint(ssrObject, sort_dicts = False) # pprint(vmessObject, sort_dicts = False) +# pprint(vlessObject, sort_dicts = False) +# pprint(trojanObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) ssProxy = { @@ -48,7 +52,60 @@ vmessProxy = { } } +vlessProxy = { + 'server': '1.1.1.1', + 'port': r'12345', + 'method': 'NONE', + 'id': ' 3f163adf-5bdd-40d0-b0ec-e47f9bebcac7', + 'stream': { + 'type': 'grpc', + 'service': 'dnomd343', + 'secure': None, + # 'secure': { + # 'type': 'tls', + # 'sni': '23333', + # 'alpn': 'h2', + # 'verify': 0 + # } + # 'secure': { + # 'type': 'xtls', + # 'sni': '23333', + # 'alpn': 'h2', + # 'verify': True, + # 'flow': 'xtls-rprx-direct', + # 'udp443': 0.1 + # } + } +} + +trojanProxy = { + 'server': '1.1.1.1', + 'port': 12345, + 'passwd': b'dnomd343', + 'stream': { + 'type': 'grpc', + 'service': 'dnomd343', + # 'secure': None, + 'secure': { + 'type': 'tls', + 'sni': '23333', + 'alpn': 'h2', + 'verify': 0 + } + # 'secure': { + # 'type': 'xtls', + # 'sni': '23333', + # 'alpn': 'h2', + # 'verify': True, + # 'flow': 'xtls-rprx-direct', + # 'udp443': 0.1 + # } + } +} + # ret = Filter(ssProxy, ssObject) # ret = Filter(ssrProxy, ssrObject) -ret = Filter(vmessProxy, vmessObject) +# ret = Filter(vmessProxy, vmessObject) +# ret = Filter(vlessProxy, vlessObject) +ret = Filter(trojanProxy, trojanObject) pprint(ret, sort_dicts = False) From d0a9a6da674127b57df84716ef7123e87925b3b0 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sat, 6 Aug 2022 23:23:18 +0800 Subject: [PATCH 14/18] feat: filter of Trojan-Go --- Filter/Shadowsocks.py | 2 +- Filter/TrojanGo.py | 107 ++++++++++++++++++++++++++++++++++++++++++ demo.py | 26 +++++++++- 3 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 Filter/TrojanGo.py diff --git a/Filter/Shadowsocks.py b/Filter/Shadowsocks.py index 9b44301..a8001e7 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -36,6 +36,6 @@ ssObject = rulesFilter({ 'default': None, 'allowNone': True, 'type': pluginObject, - 'errMsg': 'Invalid pluginObject' + 'errMsg': 'Invalid plugin options' } }) diff --git a/Filter/TrojanGo.py b/Filter/TrojanGo.py new file mode 100644 index 0000000..123b4f4 --- /dev/null +++ b/Filter/TrojanGo.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis.Filter import rulesFilter +from Filter.Plugin import pluginObject +from Basis.Constant import trojanGoMethods +from Basis.Functions import isHost, isPort +from Basis.Functions import 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' + } +}) diff --git a/demo.py b/demo.py index 43d0124..0b3d3d0 100755 --- a/demo.py +++ b/demo.py @@ -8,12 +8,14 @@ from Filter.ShadowsocksR import ssrObject from Filter.VMess import vmessObject from Filter.VLESS import vlessObject from Filter.Trojan import trojanObject +from Filter.TrojanGo import trojanGoObject # pprint(ssObject, sort_dicts = False) # pprint(ssrObject, sort_dicts = False) # pprint(vmessObject, sort_dicts = False) # pprint(vlessObject, sort_dicts = False) # pprint(trojanObject, sort_dicts = False) +# pprint(trojanGoObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) ssProxy = { @@ -103,9 +105,31 @@ trojanProxy = { } } +trojanGoProxy = { + 'server': '1.1.1.1', + 'port': 12345, + 'passwd': 'dnomd343', + 'sni': '343.re', + 'alpn': ' h2', + 'verify': 'FALSE', + 'ws': { + 'host': 'dnomd343.top', + 'path': '/test', + }, + 'ss': { + 'method': 'chacha20-ietf-poly1305', + 'passwd': 'dnomd343', + }, + 'plugin': { + 'type': 'go-quiet', + 'param': 123 + } +} + # ret = Filter(ssProxy, ssObject) # ret = Filter(ssrProxy, ssrObject) # ret = Filter(vmessProxy, vmessObject) # ret = Filter(vlessProxy, vlessObject) -ret = Filter(trojanProxy, trojanObject) +# ret = Filter(trojanProxy, trojanObject) +ret = Filter(trojanGoProxy, trojanGoObject) pprint(ret, sort_dicts = False) From 9aea632cce4943c0bf2c51e9b8410906cbc9fced Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sun, 7 Aug 2022 02:03:51 +0800 Subject: [PATCH 15/18] feat: filter of Brook --- Filter/Brook.py | 101 ++++++++++++++++++++++++++++++++++++++ demo.py | 20 +++++++- docs/ProxyObject/Brook.md | 2 +- 3 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 Filter/Brook.py diff --git a/Filter/Brook.py b/Filter/Brook.py new file mode 100644 index 0000000..aecde39 --- /dev/null +++ b/Filter/Brook.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis.Filter import rulesFilter +from Basis.Functions import isHost, isPort +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' + } +}) diff --git a/demo.py b/demo.py index 0b3d3d0..941fe5d 100755 --- a/demo.py +++ b/demo.py @@ -9,6 +9,7 @@ from Filter.VMess import vmessObject from Filter.VLESS import vlessObject from Filter.Trojan import trojanObject from Filter.TrojanGo import trojanGoObject +from Filter.Brook import brookObject # pprint(ssObject, sort_dicts = False) # pprint(ssrObject, sort_dicts = False) @@ -16,6 +17,7 @@ from Filter.TrojanGo import trojanGoObject # pprint(vlessObject, sort_dicts = False) # pprint(trojanObject, sort_dicts = False) # pprint(trojanGoObject, sort_dicts = False) +# pprint(brookObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) ssProxy = { @@ -126,10 +128,26 @@ trojanGoProxy = { } } +brookProxy = { + 'server': '1.1.1.1', + 'port': 12345, + 'passwd': 'dnomd343', + 'stream': { + 'type': 'ws', + 'host': '343.re', + 'path': '/test', + 'raw': True, + 'secure': { + 'verify': ' 0' + } + }, +} + # ret = Filter(ssProxy, ssObject) # ret = Filter(ssrProxy, ssrObject) # ret = Filter(vmessProxy, vmessObject) # ret = Filter(vlessProxy, vlessObject) # ret = Filter(trojanProxy, trojanObject) -ret = Filter(trojanGoProxy, trojanGoObject) +# ret = Filter(trojanGoProxy, trojanGoObject) +ret = Filter(brookProxy, brookObject) pprint(ret, sort_dicts = False) 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` + 限制:无 From 9bc21035adb046cce28dbe73be826677f2bb8d21 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sun, 7 Aug 2022 02:25:28 +0800 Subject: [PATCH 16/18] feat: filter of Hysteria --- Filter/Hysteria.py | 86 ++++++++++++++++++++++++++++++++++++++++++++++ demo.py | 18 +++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 Filter/Hysteria.py diff --git a/Filter/Hysteria.py b/Filter/Hysteria.py new file mode 100644 index 0000000..bba338a --- /dev/null +++ b/Filter/Hysteria.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from Basis.Filter import rulesFilter +from Basis.Functions import isHost, isPort +from Basis.Constant import hysteriaProtocols +from Basis.Functions import 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' + } +}) diff --git a/demo.py b/demo.py index 941fe5d..63c138c 100755 --- a/demo.py +++ b/demo.py @@ -10,6 +10,7 @@ from Filter.VLESS import vlessObject from Filter.Trojan import trojanObject from Filter.TrojanGo import trojanGoObject from Filter.Brook import brookObject +from Filter.Hysteria import hysteriaObject # pprint(ssObject, sort_dicts = False) # pprint(ssrObject, sort_dicts = False) @@ -18,6 +19,7 @@ from Filter.Brook import brookObject # pprint(trojanObject, sort_dicts = False) # pprint(trojanGoObject, sort_dicts = False) # pprint(brookObject, sort_dicts = False) +# pprint(hysteriaObject, sort_dicts = False) # pprint(filterObject, sort_dicts = False) ssProxy = { @@ -143,11 +145,25 @@ brookProxy = { }, } +hysteriaProxy = { + 'server': '1.1.1.1', + 'port': 12345, + 'protocol': 'faketcp', + 'obfs': '1234', + 'passwd': 'dnomd343', + 'up': 100, + 'down': 500, + 'sni': '343.re', + 'alpn': 'h3', + 'verify': 'FALSE', +} + # ret = Filter(ssProxy, ssObject) # ret = Filter(ssrProxy, ssrObject) # ret = Filter(vmessProxy, vmessObject) # ret = Filter(vlessProxy, vlessObject) # ret = Filter(trojanProxy, trojanObject) # ret = Filter(trojanGoProxy, trojanGoObject) -ret = Filter(brookProxy, brookObject) +# ret = Filter(brookProxy, brookObject) +ret = Filter(hysteriaProxy, hysteriaObject) pprint(ret, sort_dicts = False) From 53451eebb53b6d7274ad5c4f47bee1fbbf290d61 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sun, 7 Aug 2022 12:36:48 +0800 Subject: [PATCH 17/18] update: release Filter module --- Filter/Brook.py | 12 +++++- Filter/Hysteria.py | 13 ++++++- Filter/Shadowsocks.py | 8 +++- Filter/ShadowsocksR.py | 13 ++++++- Filter/Trojan.py | 10 ++++- Filter/TrojanGo.py | 13 ++++++- Filter/V2ray.py | 19 +++++++++- Filter/VLESS.py | 10 ++++- Filter/VMess.py | 10 ++++- Filter/Xray.py | 2 + Filter/__init__.py | 77 +++++++++++--------------------------- demo.py | 61 ++++++++++++------------------ docs/ProxyObject/Trojan.md | 4 +- docs/ProxyObject/VLESS.md | 4 +- docs/ProxyObject/VMess.md | 2 +- 15 files changed, 148 insertions(+), 110 deletions(-) diff --git a/Filter/Brook.py b/Filter/Brook.py index aecde39..2ef8da3 100644 --- a/Filter/Brook.py +++ b/Filter/Brook.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from Basis.Filter import rulesFilter +import copy from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter from Basis.Functions import toInt, toStr, toStrTidy, toBool secureObject = rulesFilter({ @@ -99,3 +100,12 @@ brookObject = rulesFilter({ '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 index bba338a..48f65cc 100644 --- a/Filter/Hysteria.py +++ b/Filter/Hysteria.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from Basis.Filter import rulesFilter +import copy from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter from Basis.Constant import hysteriaProtocols -from Basis.Functions import toInt, toStr, toStrTidy, toBool +from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool hysteriaObject = rulesFilter({ 'server': { @@ -84,3 +85,11 @@ hysteriaObject = rulesFilter({ '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/Shadowsocks.py b/Filter/Shadowsocks.py index a8001e7..72ca1ed 100644 --- a/Filter/Shadowsocks.py +++ b/Filter/Shadowsocks.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from Basis.Filter import rulesFilter +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({ @@ -39,3 +40,8 @@ ssObject = rulesFilter({ '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 index 749a9b3..3877d59 100644 --- a/Filter/ShadowsocksR.py +++ b/Filter/ShadowsocksR.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from Basis.Filter import rulesFilter +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 @@ -72,3 +73,13 @@ ssrObject = rulesFilter({ '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 index 3eb118b..f4ca221 100644 --- a/Filter/Trojan.py +++ b/Filter/Trojan.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import copy from Filter import Xray -from Basis.Filter import rulesFilter from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter from Basis.Functions import toInt, toStr, toStrTidy trojanObject = rulesFilter({ @@ -41,3 +42,10 @@ trojanObject = rulesFilter({ '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 index 123b4f4..27689b2 100644 --- a/Filter/TrojanGo.py +++ b/Filter/TrojanGo.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -from Basis.Filter import rulesFilter +import copy from Filter.Plugin import pluginObject from Basis.Constant import trojanGoMethods from Basis.Functions import isHost, isPort -from Basis.Functions import toInt, toStr, toStrTidy, toBool +from Basis.Filter import Filter, rulesFilter +from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool ssObject = rulesFilter({ 'method': { @@ -105,3 +106,11 @@ trojanGoObject = rulesFilter({ '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 index cd30590..a7d9063 100644 --- a/Filter/V2ray.py +++ b/Filter/V2ray.py @@ -3,7 +3,7 @@ from Basis.Filter import rulesFilter from Basis.Constant import quicMethods, udpObfuscations -from Basis.Functions import toInt, toStr, toStrTidy, toBool +from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool tlsObject = rulesFilter({ 'sni': { @@ -237,3 +237,20 @@ grpcObject = rulesFilter({ '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 index ad7133d..b8dbd2c 100644 --- a/Filter/VLESS.py +++ b/Filter/VLESS.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import copy from Filter import Xray -from Basis.Filter import rulesFilter from Basis.Functions import isHost, isPort +from Basis.Filter import Filter, rulesFilter from Basis.Functions import toInt, toStrTidy vlessObject = rulesFilter({ @@ -49,3 +50,10 @@ vlessObject = rulesFilter({ '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 index f179c73..ffc0ffa 100644 --- a/Filter/VMess.py +++ b/Filter/VMess.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +import copy from Filter import V2ray -from Basis.Filter import rulesFilter 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({ @@ -59,4 +60,9 @@ vmessObject = rulesFilter({ } }) -# TODO: add SNI / ws host / h2 host + +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 index 4102034..f014bdb 100644 --- a/Filter/Xray.py +++ b/Filter/Xray.py @@ -162,3 +162,5 @@ grpcObject = rulesFilter({ **copy.deepcopy(V2ray.grpcObject), 'secure': secureRule_2 # None / tlsObject }) + +addSni = V2ray.addSni diff --git a/Filter/__init__.py b/Filter/__init__.py index e0137b1..4b571b5 100644 --- a/Filter/__init__.py +++ b/Filter/__init__.py @@ -1,60 +1,27 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -""" -xxObject = { # a dict that describe multi-field - 'field_1': { - 'optional': ..., # field force require or not - 'default': ..., # default value when field is not exist (optional == True) - 'allowNone': ..., # whether the value can be None (override the format and filter process) - 'type': ..., # type of field content (in filter process) (any / type / list / dict) - 'multiSub': ..., # whether there are multi subObject (type is dict) - 'indexKey': ..., # index key of subObject (type is dict and multiSub == True) - 'format': ..., # format function (before filter process) (invalid content -> throw error) - 'filter': ..., # filter function -> bool (throw error when return False) - 'errMsg': ..., # raise message if there is something error - }, - 'field_2': { - ... - }, - ... +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, } - -default value - + optional: False - + default: None - + allowNone: False - + format: lambda -> return same thing - + type: force require - + multiSub: False - + indexKey: 'type' - + filter: lambda -> return True - + errMsg: 'filter error' - - -pre process - => field not exist - => optional == False -> throw errMsg - => optional == True -> set as default value -> continue - => field exist - => filed is None - => allowNone is False -> throw errMsg - => allowNone is True -> break - => field is not None -> continue - -format process -> set as field value (maybe throw error -> catch and throw errMsg) - -filter process - => type is `any` -> filter function check (skip type compare) - => type is `type` -> compare with field type -> filter function check - => type is `list` -> compare every type in list with field -> filter function check - => type is `dict` - => multiSub == False -> recursive check - => multiSub == True - => field content is not dict or not include indexKey -> throw error - => others - => indexKey's content not in type (dict) -> throw error - => others -> recursive check - -""" +def Filter(proxyType: str, proxyInfo: dict) -> dict: + if proxyType not in filterEntry: + raise RuntimeError('Unknown proxy type') + return filterEntry[proxyType](proxyInfo) diff --git a/demo.py b/demo.py index 63c138c..07d6fa3 100755 --- a/demo.py +++ b/demo.py @@ -1,26 +1,7 @@ #!/usr/bin/env python from pprint import pprint -from Basis.Filter import Filter -from Basis.Filter import filterObject -from Filter.Shadowsocks import ssObject -from Filter.ShadowsocksR import ssrObject -from Filter.VMess import vmessObject -from Filter.VLESS import vlessObject -from Filter.Trojan import trojanObject -from Filter.TrojanGo import trojanGoObject -from Filter.Brook import brookObject -from Filter.Hysteria import hysteriaObject - -# pprint(ssObject, sort_dicts = False) -# pprint(ssrObject, sort_dicts = False) -# pprint(vmessObject, sort_dicts = False) -# pprint(vlessObject, sort_dicts = False) -# pprint(trojanObject, sort_dicts = False) -# pprint(trojanGoObject, sort_dicts = False) -# pprint(brookObject, sort_dicts = False) -# pprint(hysteriaObject, sort_dicts = False) -# pprint(filterObject, sort_dicts = False) +from Filter import Filter ssProxy = { 'server': '1.1.1.1', @@ -38,7 +19,9 @@ ssrProxy = { 'method': 'table', 'passwd': 'dnomd343', 'protocol': 'auth_chain-a', - 'obfs': 'http_post' + 'protocolParam': '123', + 'obfs': 'plain', + 'obfsParam': 'ok', } vmessProxy = { @@ -51,7 +34,7 @@ vmessProxy = { 'service': 'no-gfw', 'mode': ' multi ', 'secure': { - 'sni': ' DNOMD343.top', + 'sni': '', 'alpn': 'h2, http/1.1', 'verify': 'False ' } @@ -66,7 +49,7 @@ vlessProxy = { 'stream': { 'type': 'grpc', 'service': 'dnomd343', - 'secure': None, + # 'secure': None, # 'secure': { # 'type': 'tls', # 'sni': '23333', @@ -85,16 +68,18 @@ vlessProxy = { } trojanProxy = { - 'server': '1.1.1.1', + 'server': 'www.dnomd343.top', 'port': 12345, 'passwd': b'dnomd343', 'stream': { - 'type': 'grpc', + # 'type': 'grpc', + 'type': 'h2', + # 'host': '343.re', 'service': 'dnomd343', # 'secure': None, 'secure': { 'type': 'tls', - 'sni': '23333', + 'sni': '', 'alpn': 'h2', 'verify': 0 } @@ -110,10 +95,10 @@ trojanProxy = { } trojanGoProxy = { - 'server': '1.1.1.1', + 'server': '343.re', 'port': 12345, 'passwd': 'dnomd343', - 'sni': '343.re', + 'sni': '', 'alpn': ' h2', 'verify': 'FALSE', 'ws': { @@ -146,24 +131,24 @@ brookProxy = { } hysteriaProxy = { - 'server': '1.1.1.1', + 'server': 'www.343.re', 'port': 12345, 'protocol': 'faketcp', 'obfs': '1234', 'passwd': 'dnomd343', 'up': 100, 'down': 500, - 'sni': '343.re', + 'sni': '', 'alpn': 'h3', 'verify': 'FALSE', } -# ret = Filter(ssProxy, ssObject) -# ret = Filter(ssrProxy, ssrObject) -# ret = Filter(vmessProxy, vmessObject) -# ret = Filter(vlessProxy, vlessObject) -# ret = Filter(trojanProxy, trojanObject) -# ret = Filter(trojanGoProxy, trojanGoObject) -# ret = Filter(brookProxy, brookObject) -ret = Filter(hysteriaProxy, hysteriaObject) +# ret = Filter('ss', ssProxy) +# ret = Filter('ssr', ssrProxy) +# ret = Filter('vmess', vmessProxy) +# ret = Filter('vless', vlessProxy) +# ret = Filter('trojan', trojanProxy) +# ret = Filter('trojan-go', trojanGoProxy) +# ret = Filter('brook', brookProxy) +ret = Filter('hysteria', hysteriaProxy) pprint(ret, sort_dicts = 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 fc8b526..e9adcc1 100644 --- a/docs/ProxyObject/VMess.md +++ b/docs/ProxyObject/VMess.md @@ -290,7 +290,7 @@ + 类型:*str* + 说明:TLS握手SNI字段 -+ 缺省:`obfsObject.host[0]` / `wsObject.host` / `h2Object.host[0]` / `空` ++ 缺省:`空` + 限制:无 ### alpn From 2c39c78c144096b65f2324ca692210c3e89e1227 Mon Sep 17 00:00:00 2001 From: dnomd343 Date: Sun, 7 Aug 2022 12:52:42 +0800 Subject: [PATCH 18/18] update: remove legacy ProxyFilter --- Basis/Api.py | 13 +- ProxyFilter/Brook.py | 93 ----------- ProxyFilter/Hysteria.py | 90 ----------- ProxyFilter/Plugin.py | 124 --------------- ProxyFilter/Shadowsocks.py | 135 ---------------- ProxyFilter/ShadowsocksR.py | 185 ---------------------- ProxyFilter/Trojan.py | 75 --------- ProxyFilter/TrojanGo.py | 138 ----------------- ProxyFilter/V2ray.py | 260 ------------------------------- ProxyFilter/VLESS.py | 85 ---------- ProxyFilter/VMess.py | 99 ------------ ProxyFilter/Xray.py | 110 ------------- ProxyFilter/__init__.py | 6 - ProxyFilter/baseFunc.py | 301 ------------------------------------ ProxyFilter/filter.py | 51 ------ demo.py | 154 ------------------ 16 files changed, 8 insertions(+), 1911 deletions(-) delete mode 100644 ProxyFilter/Brook.py delete mode 100644 ProxyFilter/Hysteria.py delete mode 100644 ProxyFilter/Plugin.py delete mode 100644 ProxyFilter/Shadowsocks.py delete mode 100644 ProxyFilter/ShadowsocksR.py delete mode 100644 ProxyFilter/Trojan.py delete mode 100644 ProxyFilter/TrojanGo.py delete mode 100644 ProxyFilter/V2ray.py delete mode 100644 ProxyFilter/VLESS.py delete mode 100644 ProxyFilter/VMess.py delete mode 100644 ProxyFilter/Xray.py delete mode 100644 ProxyFilter/__init__.py delete mode 100644 ProxyFilter/baseFunc.py delete mode 100644 ProxyFilter/filter.py delete mode 100755 demo.py 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/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/demo.py b/demo.py deleted file mode 100755 index 07d6fa3..0000000 --- a/demo.py +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env python - -from pprint import pprint -from Filter import Filter - -ssProxy = { - 'server': '1.1.1.1', - 'port': '12345', - 'method': 'none', - 'passwd': 'dnomd343', - 'plugin': { - 'type': 'obfs' - } -} - -ssrProxy = { - 'server': '1.1.1.1', - 'port': 12345, - 'method': 'table', - 'passwd': 'dnomd343', - 'protocol': 'auth_chain-a', - 'protocolParam': '123', - 'obfs': 'plain', - 'obfsParam': 'ok', -} - -vmessProxy = { - 'server': '1.1.1.1', - 'port': b'12345', - 'id': 'c8783403-64d5-4b6d-8cf4-bd3988d01b6c', - 'aid': '64', - 'stream': { - 'type': 'GRPC', - 'service': 'no-gfw', - 'mode': ' multi ', - 'secure': { - 'sni': '', - 'alpn': 'h2, http/1.1', - 'verify': 'False ' - } - } -} - -vlessProxy = { - 'server': '1.1.1.1', - 'port': r'12345', - 'method': 'NONE', - 'id': ' 3f163adf-5bdd-40d0-b0ec-e47f9bebcac7', - 'stream': { - 'type': 'grpc', - 'service': 'dnomd343', - # 'secure': None, - # 'secure': { - # 'type': 'tls', - # 'sni': '23333', - # 'alpn': 'h2', - # 'verify': 0 - # } - # 'secure': { - # 'type': 'xtls', - # 'sni': '23333', - # 'alpn': 'h2', - # 'verify': True, - # 'flow': 'xtls-rprx-direct', - # 'udp443': 0.1 - # } - } -} - -trojanProxy = { - 'server': 'www.dnomd343.top', - 'port': 12345, - 'passwd': b'dnomd343', - 'stream': { - # 'type': 'grpc', - 'type': 'h2', - # 'host': '343.re', - 'service': 'dnomd343', - # 'secure': None, - 'secure': { - 'type': 'tls', - 'sni': '', - 'alpn': 'h2', - 'verify': 0 - } - # 'secure': { - # 'type': 'xtls', - # 'sni': '23333', - # 'alpn': 'h2', - # 'verify': True, - # 'flow': 'xtls-rprx-direct', - # 'udp443': 0.1 - # } - } -} - -trojanGoProxy = { - 'server': '343.re', - 'port': 12345, - 'passwd': 'dnomd343', - 'sni': '', - 'alpn': ' h2', - 'verify': 'FALSE', - 'ws': { - 'host': 'dnomd343.top', - 'path': '/test', - }, - 'ss': { - 'method': 'chacha20-ietf-poly1305', - 'passwd': 'dnomd343', - }, - 'plugin': { - 'type': 'go-quiet', - 'param': 123 - } -} - -brookProxy = { - 'server': '1.1.1.1', - 'port': 12345, - 'passwd': 'dnomd343', - 'stream': { - 'type': 'ws', - 'host': '343.re', - 'path': '/test', - 'raw': True, - 'secure': { - 'verify': ' 0' - } - }, -} - -hysteriaProxy = { - 'server': 'www.343.re', - 'port': 12345, - 'protocol': 'faketcp', - 'obfs': '1234', - 'passwd': 'dnomd343', - 'up': 100, - 'down': 500, - 'sni': '', - 'alpn': 'h3', - 'verify': 'FALSE', -} - -# ret = Filter('ss', ssProxy) -# ret = Filter('ssr', ssrProxy) -# ret = Filter('vmess', vmessProxy) -# ret = Filter('vless', vlessProxy) -# ret = Filter('trojan', trojanProxy) -# ret = Filter('trojan-go', trojanGoProxy) -# ret = Filter('brook', brookProxy) -ret = Filter('hysteria', hysteriaProxy) -pprint(ret, sort_dicts = False)