Browse Source

Merge branch 'dev'

master
dnomd343 2 years ago
parent
commit
367138ec68
  1. 13
      Basis/Api.py
  2. 130
      Basis/Filter.py
  3. 67
      Basis/Functions.py
  4. 123
      Dockerfile
  5. 111
      Filter/Brook.py
  6. 95
      Filter/Hysteria.py
  7. 58
      Filter/Plugin.py
  8. 47
      Filter/Shadowsocks.py
  9. 85
      Filter/ShadowsocksR.py
  10. 51
      Filter/Trojan.py
  11. 116
      Filter/TrojanGo.py
  12. 256
      Filter/V2ray.py
  13. 59
      Filter/VLESS.py
  14. 68
      Filter/VMess.py
  15. 166
      Filter/Xray.py
  16. 27
      Filter/__init__.py
  17. 93
      ProxyFilter/Brook.py
  18. 90
      ProxyFilter/Hysteria.py
  19. 124
      ProxyFilter/Plugin.py
  20. 135
      ProxyFilter/Shadowsocks.py
  21. 185
      ProxyFilter/ShadowsocksR.py
  22. 75
      ProxyFilter/Trojan.py
  23. 138
      ProxyFilter/TrojanGo.py
  24. 260
      ProxyFilter/V2ray.py
  25. 85
      ProxyFilter/VLESS.py
  26. 99
      ProxyFilter/VMess.py
  27. 110
      ProxyFilter/Xray.py
  28. 6
      ProxyFilter/__init__.py
  29. 301
      ProxyFilter/baseFunc.py
  30. 51
      ProxyFilter/filter.py
  31. 2
      docs/ProxyObject/Brook.md
  32. 4
      docs/ProxyObject/Trojan.md
  33. 4
      docs/ProxyObject/VLESS.md
  34. 20
      docs/ProxyObject/VMess.md

13
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

130
Basis/Filter.py

@ -0,0 +1,130 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
filterObject = {
'optional': {
'type': bool,
'optional': True, # `optional` is not force require
'default': False, # disable `optional` option in default
'allowNone': False, # `optional` couldn't be None
},
'default': {
'type': any, # skip type check
'optional': True, # `default` is not force require
'default': None,
'allowNone': True, # `default` can be None
},
'allowNone': {
'type': bool,
'optional': True, # `allowNone` is not force require
'default': False, # disable `allowNone` option in default
'allowNone': False, # `allowNone` couldn't be None
},
'type': {
'type': [any, type, list, dict],
'optional': False, # `type` is force require
'allowNone': False, # `type` couldn't be None
},
'multiSub': {
'type': bool,
'optional': True, # `multiSub` is not force require
'default': False, # disable `multiSub` option in default
'allowNone': False, # `multiSub` couldn't be None
},
'indexKey': {
'type': str,
'optional': True, # `indexKey` is not force require
'default': 'type',
'allowNone': False, # `indexKey` couldn't be None
},
'format': {
'type': any,
'optional': True, # `format` is not force require
'default': lambda x: x, # don't change anything
'allowNone': False, # `format` couldn't be None
},
'filter': {
'type': any,
'optional': True, # `filter` is not force require
'default': lambda x: True, # always pass filter
'allowNone': False, # `filter` couldn't be None
},
'errMsg': {
'type': str,
'optional': True, # `errMsg` is not force require
'default': 'Filter error',
'allowNone': False, # `errMsg` couldn't be None
}
}
for field in filterObject:
filterObject[field]['errMsg'] = 'Invalid `%s` key' % field
filterObject[field]['format'] = filterObject['format']['default'] # return same value
filterObject[field]['filter'] = filterObject['filter']['default'] # always return True
def Filter(raw: dict, rules: dict) -> dict:
if type(raw) != dict:
raise RuntimeError('Invalid input for filter')
data = {}
raw = copy.deepcopy(raw)
rules = copy.deepcopy(rules)
for key, rule in rules.items():
# pretreatment process (raw --[copy / default value]--> data)
if key not in raw: # key not exist
if not rule['optional']: # force require key not exist
raise RuntimeError('Missing `%s` field' % key)
data[key] = rule['default'] # set default value
else: # key exist
data[key] = raw[key]
# format process (data --[format]--> data)
if data[key] is None: # key content is None
if not rule['allowNone']: # key is not allow None
raise RuntimeError('Field `%s` shouldn\'t be None' % key)
continue # skip following process
try:
data[key] = rule['format'](data[key]) # run format
except:
raise RuntimeError(rule['errMsg']) # format error
# filter process (data --[type check (& filter check)]--> pass / non-pass)
if type(rule['type']) == type: # str / int / bool / ...
rule['type'] = [rule['type']] # str -> [str] / int -> [int] / ...
if type(rule['type']) == list: # [str, int, bool, ...]
if data[key] == any and any in rule['type']: # special case -> skip type filter
pass
elif type(data[key]) not in rule['type']: # type not in allow list
raise RuntimeError('Invalid `%s` field' % key)
elif type(rule['type']) == dict: # check subObject
if type(data[key]) != dict:
raise RuntimeError('Invalid sub object in `%s`' % key) # subObject content should be dict
if not rule['multiSub']: # single subObject
subRules = rule['type']
else: # multi subObject
if rule['indexKey'] not in data[key]: # confirm index key exist
raise RuntimeError('Index key `%s` not found in `%s`' % (rule['indexKey'], key))
subType = data[key][rule['indexKey']].lower()
if subType not in rule['type']: # confirm subObject rule exist
raise RuntimeError('Unknown index `%s` in key `%s`' % (subType, key))
subRules = rule['type'][subType]
try:
data[key] = Filter(data[key], subRules)
except RuntimeError as exp:
raise RuntimeError('%s (in `%s`)' % (exp, key)) # add located info
continue
elif rule['type'] != any: # type == any -> skip type filter
raise RuntimeError('Unknown `type` in rules')
if not rule['filter'](data[key]): # run filter
raise RuntimeError(rule['errMsg'])
return data
def rulesFilter(rules: dict) -> dict:
result = {}
for key, rule in rules.items(): # filter by basic rules
result[key] = Filter(rule, filterObject)
return result
filterObject = rulesFilter(filterObject) # self-format

67
Basis/Functions.py

@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
import time
import uuid
import psutil
@ -10,8 +11,38 @@ from IPy import IP
from Basis.Logger import logging
def isIpAddr(ipAddr: str) -> bool:
try:
if '/' in ipAddr: # filter CIDR
return False
if '.' not in ipAddr and ':' not in ipAddr: # not IPv4 or IPv6
return False
IP(ipAddr) # try to convert to IP address
return True # valid IP address
except:
return False
def isDomain(domain: str) -> bool:
try:
domainRegex = r'^(?=^.{3,255}$)[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62}(\.[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62})+$'
return re.search(domainRegex, domain) is not None # regex matching
except: # unexpected error
return False
def isHost(host: str) -> bool:
return isIpAddr(host) or isDomain(host) # IPv4 / IPv6 / Domain
def isPort(port: int) -> bool:
if type(port) != int:
return False
return port in range(1, 65536) # 1 ~ 65535
def md5Sum(data: str, encode: str = 'utf-8') -> str:
return hashlib.md5(data.encode(encoding = encode)).hexdigest()
return hashlib.md5(data.encode(encoding = encode)).hexdigest() # MD5 hash
def hostFormat(host: str, v6Bracket: bool = False) -> str:
@ -42,6 +73,40 @@ def genUUID() -> str: # generate uuid v5
))
def toInt(raw) -> int:
try:
return int(raw)
except:
raise RuntimeError('Unable convert to int')
def toStr(raw) -> str:
if raw is None:
raise RuntimeError('None could not convert to str')
if isinstance(raw, bytes): # bytes -> str
return str(raw, encoding = 'utf-8')
try:
return str(raw)
except:
raise RuntimeError('Unable convert to str')
def toStrTidy(raw) -> str:
return toStr(raw).strip().lower() # with trim and lower
def toBool(raw) -> bool:
if isinstance(raw, (bool, int, float)):
return bool(raw)
try:
raw = toStr(raw).strip().lower()
if raw in ['true', 'false']:
return True if raw == 'true' else False
return int(raw) != 0
except:
raise RuntimeError('Unable convert to bool')
def getAvailablePort(rangeStart: int = 1024, rangeEnd: int = 65535, waitTime: int = 10) -> int: # get available port
if rangeStart > rangeEnd or rangeStart < 1 or rangeEnd > 65535:
raise RuntimeError('Invalid port range')

123
Dockerfile

@ -6,17 +6,6 @@ ARG GO17_IMG="golang:1.17-alpine3.16"
ARG GO18_IMG="golang:1.18-alpine3.16"
ARG PYTHON_IMG="python:3.10-alpine3.16"
# Compile upx (under gcc10)
FROM ${ALPINE_IMG} AS upx
ENV UPX_VERSION="3.96"
RUN sed -i 's/v3.\d\d/v3.15/' /etc/apk/repositories && \
apk add bash build-base perl ucl-dev zlib-dev
RUN wget https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-src.tar.xz && \
tar xf upx-${UPX_VERSION}-src.tar.xz
WORKDIR ./upx-${UPX_VERSION}-src/
RUN make all && mkdir -p /upx/bin/ && mv ./src/upx.out /upx/bin/upx && \
mkdir -p /upx/lib/ && cd /usr/lib/ && cp -d ./libgcc_s.so* ./libstdc++.so* ./libucl.so* /upx/lib/
# Download build-base
FROM ${ALPINE_IMG} AS build-base
WORKDIR /apk/
@ -24,6 +13,12 @@ RUN apk add build-base | grep -oE 'Installing \S+' | cut -b 12- > ./build-base
RUN chmod +x ./build-base && cat ./build-base | xargs -n1 apk fetch && \
sed -i 's/^/ \/apk\/&/g;s/$/&-*.apk/g;1i\apk add' ./build-base && sed -i ':a;N;s/\n//g;ba' ./build-base
# Compile numpy
FROM ${PYTHON_IMG} AS numpy
WORKDIR /wheels/
COPY --from=build-base /apk/ /apk/
RUN /apk/build-base && pip wheel numpy
# Compile gevent
FROM ${PYTHON_IMG} AS gevent
WORKDIR /wheels/
@ -31,12 +26,6 @@ RUN apk add libffi-dev
COPY --from=build-base /apk/ /apk/
RUN /apk/build-base && pip wheel gevent
# Compile numpy
FROM ${PYTHON_IMG} AS numpy
WORKDIR /wheels/
COPY --from=build-base /apk/ /apk/
RUN /apk/build-base && pip wheel numpy
# Build python wheels
FROM ${PYTHON_IMG} AS wheels
WORKDIR /wheels/
@ -46,6 +35,17 @@ RUN /apk/build-base && pip wheel colorlog flask IPy psutil pysocks requests sals
COPY --from=gevent /wheels/*.whl /wheels/
COPY --from=numpy /wheels/*.whl /wheels/
# Compile upx (under gcc10)
FROM ${ALPINE_IMG} AS upx
ENV UPX_VERSION="3.96"
RUN sed -i 's/v3.\d\d/v3.15/' /etc/apk/repositories && \
apk add bash build-base perl ucl-dev zlib-dev
RUN wget https://github.com/upx/upx/releases/download/v${UPX_VERSION}/upx-${UPX_VERSION}-src.tar.xz && \
tar xf upx-${UPX_VERSION}-src.tar.xz
WORKDIR ./upx-${UPX_VERSION}-src/
RUN make -C ./src/ && mkdir -p /upx/bin/ && mv ./src/upx.out /upx/bin/upx && \
mkdir -p /upx/lib/ && cd /usr/lib/ && cp -d ./libgcc_s.so* ./libstdc++.so* ./libucl.so* /upx/lib/
# Compile shadowsocks-rust
FROM ${RUST_IMG} AS ss-rust
ENV SS_RUST="1.15.0-alpha.8"
@ -59,7 +59,7 @@ RUN cargo build --target-dir ./ --release --bin sslocal --bin ssserver \
mv ./release/sslocal /tmp/ss-rust-local && mv ./release/ssserver /tmp/ss-rust-server && \
strip /tmp/ss-rust-*
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/ss-rust-*
RUN ls /tmp/ss-rust-* | xargs -P0 -n1 upx -9
# Compile shadowsocks-libev
FROM ${ALPINE_IMG} AS ss-libev
@ -93,8 +93,8 @@ RUN sed -i 's/MutableMapping/abc.MutableMapping/' ./shadowsocks/lru_cache.py &&
mv ../../lib/ /packages/ssr-python/
# shadowsocks-python (latest version)
WORKDIR ../shadowsocks/
RUN git checkout master
RUN sed -i 's/if addr is/if addr ==/g' ./shadowsocks/common.py && \
RUN git checkout master && \
sed -i 's/if addr is/if addr ==/g' ./shadowsocks/common.py && \
sed -i 's/and ip is not/and ip !=/g' ./shadowsocks/common.py && \
sed -i 's/if len(block) is/if len(block) ==/g' ./shadowsocks/common.py && \
sed -i 's/MutableMapping/abc.MutableMapping/' ./shadowsocks/lru_cache.py && \
@ -166,15 +166,15 @@ RUN git clone https://github.com/shadowsocks/qtun.git && \
WORKDIR ./simple-obfs/
RUN git submodule update --init --recursive && \
./autogen.sh && ./configure --disable-documentation && make && \
mv ./src//obfs-local ./src//obfs-server /plugins/
mv ./src/obfs-local ./src/obfs-server /plugins/
# Compile qtun
WORKDIR ../qtun/
RUN cargo update
RUN cargo build --target-dir ./ --release && \
mv ./release//qtun-client ./release//qtun-server /plugins/ && \
mv ./release/qtun-client ./release/qtun-server /plugins/ && \
strip /plugins/*
COPY --from=upx /upx/ /usr/
RUN upx -9 /plugins/qtun-*
RUN ls /plugins/qtun-* | xargs -P0 -n1 upx -9
# Compile sip003 plugins (part2 -> go1.16)
FROM ${GO16_IMG} AS plugin-2
@ -232,7 +232,7 @@ RUN go mod download -x
RUN env CGO_ENABLED=0 go build -v -o gun-plugin -trimpath -ldflags "-s -w" ./cmd/sip003/ && \
mv ./gun-plugin /plugins/
COPY --from=upx /upx/ /usr/
RUN upx -9 /plugins/*
RUN ls /plugins/* | xargs -P0 -n1 upx -9
# Compile sip003 plugins (part3 -> go1.17)
FROM ${GO17_IMG} AS plugin-3
@ -260,38 +260,32 @@ RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-X main.version=$(git desc
env CGO_ENABLED=0 go build -v -trimpath -ldflags "-X main.version=$(git describe --tags) -s -w" ./cmd/ck-server && \
mv ./ck-client ./ck-server /plugins/
COPY --from=upx /upx/ /usr/
RUN upx -9 /plugins/*
RUN ls /plugins/* | xargs -P0 -n1 upx -9
# Combine sip003 plugins
FROM ${ALPINE_IMG} AS plugin
COPY --from=plugin-1 /plugins/ /release/
COPY --from=plugin-2 /plugins/ /release/
COPY --from=plugin-3 /plugins/ /release/
COPY --from=plugin-1 /plugins/ /plugins/
COPY --from=plugin-2 /plugins/ /plugins/
COPY --from=plugin-3 /plugins/ /plugins/
# Compile v2fly-core
# Compile xray-core and v2fly-core
FROM ${GO18_IMG} AS v2ray
ENV V2RAY_VERSION="4.45.2"
RUN wget https://github.com/v2fly/v2ray-core/archive/refs/tags/v${V2RAY_VERSION}.tar.gz && \
tar xf v${V2RAY_VERSION}.tar.gz
WORKDIR ./v2ray-core-${V2RAY_VERSION}/
RUN go mod download -x
RUN env CGO_ENABLED=0 go build -v -o v2ray -trimpath -ldflags "-s -w" ./main && \
env CGO_ENABLED=0 go build -v -o v2ctl -trimpath -ldflags "-s -w" -tags confonly ./infra/control/main && \
mv ./v2ctl ./v2ray /tmp/
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/v2*
# Compile xray-core
FROM ${GO18_IMG} AS xray
ENV XRAY_VERSION="1.5.9"
ENV V2FLY_VERSION="4.45.2"
RUN wget https://github.com/XTLS/Xray-core/archive/refs/tags/v${XRAY_VERSION}.tar.gz && \
tar xf v${XRAY_VERSION}.tar.gz
RUN wget https://github.com/v2fly/v2ray-core/archive/refs/tags/v${V2FLY_VERSION}.tar.gz && \
tar xf v${V2FLY_VERSION}.tar.gz
WORKDIR ./Xray-core-${XRAY_VERSION}/
RUN go mod download -x
RUN env CGO_ENABLED=0 go build -v -o xray -trimpath -ldflags "-s -w" ./main && \
mv ./xray /tmp/
WORKDIR ../v2ray-core-${V2FLY_VERSION}/
RUN go mod download -x
RUN env CGO_ENABLED=0 go build -v -o v2ray -trimpath -ldflags "-s -w" ./main && \
mv ./v2ray /tmp/
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/xray
RUN ls /tmp/*ray | xargs -P0 -n1 upx -9
# Compile trojan-go
FROM ${GO17_IMG} AS trojan-go
@ -320,29 +314,23 @@ RUN cmake .. -DENABLE_MYSQL=OFF -DSYSTEMD_SERVICE=OFF && make && \
strip /tmp/trojan
COPY --from=trojan-go /tmp/trojan-go /tmp/
# Compile gost-v3
FROM ${GO18_IMG} AS gost-v3
RUN apk add git
RUN git clone https://github.com/go-gost/gost.git
WORKDIR ./gost/
RUN go mod download -x
RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w" ./cmd/gost && \
mv ./gost /tmp/gost-v3
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/gost-v3
# Compile gost
FROM ${GO17_IMG} AS gost
# Compile gost and gost-v3
FROM ${GO18_IMG} AS gost
ENV GOST_VERSION="2.11.2"
RUN wget https://github.com/ginuerzh/gost/archive/refs/tags/v${GOST_VERSION}.tar.gz && \
RUN apk add git
RUN git clone https://github.com/go-gost/gost.git ./gost-v3/ && \
wget https://github.com/ginuerzh/gost/archive/refs/tags/v${GOST_VERSION}.tar.gz && \
tar xf v${GOST_VERSION}.tar.gz
WORKDIR ./gost-${GOST_VERSION}/
RUN go mod download -x
RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w" ./cmd/gost && \
mv ./gost /tmp/
WORKDIR ../gost-v3/
RUN go mod download -x
RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w" ./cmd/gost && \
mv ./gost /tmp/gost-v3
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/gost
COPY --from=gost-v3 /tmp/gost-v3 /tmp/
RUN ls /tmp/gost* | xargs -P0 -n1 upx -9
# Compile brook
FROM ${GO16_IMG} AS brook
@ -370,14 +358,6 @@ RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags "-s -w \
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/clash
# Compile caddy
FROM ${GO18_IMG} AS caddy
RUN go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
RUN xcaddy build --with github.com/caddyserver/forwardproxy@caddy2=github.com/klzgrad/forwardproxy@naive && \
mv ./caddy /tmp/
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/caddy
# Download naiveproxy
FROM ${ALPINE_IMG} AS naiveproxy
ENV NAIVE_VERSION="v103.0.5060.53-3"
@ -391,7 +371,6 @@ RUN echo -e "while read FILE_NAME;do\nwget https://github.com/klzgrad/naiveproxy
sh naiveproxy.sh
COPY --from=build-base /apk/ /apk/
RUN /apk/build-base && strip /tmp/naive
COPY --from=caddy /tmp/caddy /tmp/
# Compile open-snell
FROM ${GO17_IMG} AS snell
@ -406,7 +385,7 @@ RUN env CGO_ENABLED=0 go build -v -trimpath -ldflags \
"-s -w -X 'github.com/icpz/open-snell/constants.Version=${SNELL_VERSION}'" ./cmd/snell-server && \
mv ./snell-client ./snell-server /tmp/
COPY --from=upx /upx/ /usr/
RUN upx -9 /tmp/snell-*
RUN ls /tmp/snell-* | xargs -P0 -n1 upx -9
# Compile hysteria
FROM ${GO17_IMG} AS hysteria
@ -490,16 +469,14 @@ RUN mkdir -p /asset/usr/local/lib/python${PYTHON}/site-packages/ && \
tar xf /packages.tar.gz -C /asset/usr/local/lib/python${PYTHON}/site-packages/
COPY --from=openssl /tmp/libcrypto.so* /asset/lib/
COPY --from=shadowsocks /release/ /asset/usr/bin/
COPY --from=plugin /release/ /asset/usr/bin/
COPY --from=v2ray /tmp/v2* /asset/usr/bin/
COPY --from=xray /tmp/xray /asset/usr/bin/
COPY --from=plugin /plugins/ /asset/usr/bin/
COPY --from=v2ray /tmp/*ray /asset/usr/bin/
COPY --from=trojan /tmp/trojan* /asset/usr/bin/
COPY --from=gost /tmp/gost* /asset/usr/bin/
COPY --from=brook /tmp/brook /asset/usr/bin/
COPY --from=clash /tmp/clash /asset/usr/bin/
COPY --from=snell /tmp/snell-* /asset/usr/bin/
COPY --from=hysteria /tmp/hysteria /asset/usr/bin/
COPY --from=naiveproxy /tmp/caddy /asset/usr/bin/
COPY --from=naiveproxy /tmp/naive /asset/usr/bin/
COPY --from=relaybaton /tmp/relaybaton /asset/usr/bin/
COPY --from=pingtunnel /tmp/pingtunnel /asset/usr/bin/

111
Filter/Brook.py

@ -0,0 +1,111 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Functions import toInt, toStr, toStrTidy, toBool
secureObject = rulesFilter({
'verify': {
'optional': True,
'default': True,
'type': bool,
'format': toBool,
'errMsg': 'Invalid verify option'
}
})
originObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'origin',
'errMsg': 'Invalid Origin stream type'
},
'uot': {
'optional': True,
'default': False,
'type': bool,
'format': toBool,
'errMsg': 'Invalid UoT option'
}
})
wsObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'ws',
'errMsg': 'Invalid WebSocket stream type'
},
'host': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid WebSocket host'
},
'path': {
'optional': True,
'default': '/ws',
'type': str,
'format': lambda s: toStr(s).strip(),
'errMsg': 'Invalid WebSocket path'
},
'raw': {
'optional': True,
'default': False,
'type': bool,
'format': toBool,
'errMsg': 'Invalid raw option'
},
'secure': {
'optional': True,
'default': None,
'allowNone': True,
'type': secureObject,
'errMsg': 'Invalid secure options'
}
})
brookObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'passwd': {
'type': str,
'format': toStr,
'errMsg': 'Invalid password content'
},
'stream': {
'optional': True,
'default': {
'type': 'origin'
},
'multiSub': True,
'type': {
'origin': originObject,
'ws': wsObject,
},
'errMsg': 'Invalid Brook stream'
}
})
def brookFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
proxyInfo = Filter(proxyInfo, brookObject) # run filter
stream = proxyInfo['stream']
if stream['type'] == 'ws' and stream['host'] == '':
stream['host'] = proxyInfo['server'] # fill host option in WebSocket stream
return proxyInfo

95
Filter/Hysteria.py

@ -0,0 +1,95 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Constant import hysteriaProtocols
from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool
hysteriaObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'protocol': {
'optional': True,
'default': 'udp',
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in hysteriaProtocols,
'errMsg': 'Unknown Hysteria protocol'
},
'obfs': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': toStr,
'errMsg': 'Invalid obfs content'
},
'passwd': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': toStr,
'errMsg': 'Invalid password content'
},
'up': {
'optional': True,
'default': 10,
'type': int,
'format': toInt,
'filter': lambda i: i > 0,
'errMsg': 'Invalid upload speed option'
},
'down': {
'optional': True,
'default': 50,
'type': int,
'format': toInt,
'filter': lambda i: i > 0,
'errMsg': 'Invalid download speed option'
},
'sni': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid SNI content'
},
'alpn': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid alpn option'
},
'verify': {
'optional': True,
'default': True,
'type': bool,
'format': toBool,
'errMsg': 'Invalid verify option'
}
})
def hysteriaFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
proxyInfo = Filter(proxyInfo, hysteriaObject) # run filter
if proxyInfo['sni'] == '' and not isIpAddr(proxyInfo['server']):
proxyInfo['sni'] = proxyInfo['server']
return proxyInfo

58
Filter/Plugin.py

@ -0,0 +1,58 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Basis.Filter import rulesFilter
from Basis.Constant import pluginClients
from Basis.Functions import toStr, toStrTidy
pluginAlias = {
'obfs-local': {'obfs', 'simple-obfs'},
'simple-tls': {'tls', 'simple-tls'},
'v2ray-plugin': {'v2ray'},
'xray-plugin': {'xray'},
'kcptun-client': {'kcptun'},
'gost-plugin': {'gost'},
'ck-client': {'ck', 'cloak'},
'gq-client': {'gq', 'goquiet', 'go-quiet'},
'mtt-client': {'mtt', 'mos-tls-tunnel'},
'rabbit-plugin': {'rabbit', 'rabbit-tcp'},
'qtun-client': {'qtun'},
'gun-plugin': {'gun'},
}
pluginObject = rulesFilter({
'type': {
'type': str,
'format': lambda s: pluginFormat(toStrTidy(s)),
'filter': lambda s: s in pluginClients,
'errMsg': 'Unknown SIP003 plugin'
},
'param': {
'optional': True,
'default': '',
'type': str,
'format': toStr,
'errMsg': 'Invalid SIP003 param'
}
})
def loadAlias() -> None:
for plugin in pluginAlias:
for alias in copy.copy(pluginAlias[plugin]):
pluginAlias[plugin].update({ # better compatibility
alias + '-local', alias + '-plugin',
alias + '-client', alias + '-server',
})
def pluginFormat(pluginName: str) -> str:
pluginName = pluginName.replace('_', '-')
for plugin, alias in pluginAlias.items():
if pluginName in alias:
return plugin
return pluginName # alias not found
loadAlias()

47
Filter/Shadowsocks.py

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Filter.Plugin import pluginObject
from Basis.Constant import ssAllMethods
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Functions import toInt, toStr, toStrTidy
ssObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'method': {
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in ssAllMethods,
'errMsg': 'Unknown Shadowsocks method'
},
'passwd': {
'type': str,
'format': toStr,
'errMsg': 'Invalid password content'
},
'plugin': {
'optional': True,
'default': None,
'allowNone': True,
'type': pluginObject,
'errMsg': 'Invalid plugin options'
}
})
def ssFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
return Filter(proxyInfo, ssObject) # run filter

85
Filter/ShadowsocksR.py

@ -0,0 +1,85 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Functions import toInt, toStr, toStrTidy
from Basis.Constant import ssrMethods, ssrProtocols, ssrObfuscations
def ssrProtocolFormat(protocol: str) -> str:
protocol = toStr(protocol).strip().lower().replace('-', '_')
return 'origin' if protocol == '' else protocol # '' -> origin
def ssrObfsFormat(obfs: str) -> str:
obfs = toStr(obfs).strip().lower().replace('-', '_')
return 'plain' if obfs == '' else obfs # '' -> plain
ssrObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'method': {
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in ssrMethods,
'errMsg': 'Unknown ShadowsocksR method'
},
'passwd': {
'type': str,
'format': toStr,
'errMsg': 'Invalid password content'
},
'protocol': {
'optional': True,
'default': 'origin',
'type': str,
'format': ssrProtocolFormat,
'filter': lambda s: s in ssrProtocols,
'errMsg': 'Unknown ShadowsocksR protocol'
},
'protocolParam': {
'optional': True,
'default': '',
'type': str,
'format': toStr,
'errMsg': 'Invalid ShadowsocksR protocol param'
},
'obfs': {
'optional': True,
'default': 'plain',
'type': str,
'format': ssrObfsFormat,
'filter': lambda s: s in ssrObfuscations,
'errMsg': 'Unknown ShadowsocksR obfuscation'
},
'obfsParam': {
'optional': True,
'default': '',
'type': str,
'format': toStr,
'errMsg': 'Invalid ShadowsocksR obfuscation param'
}
})
def ssrFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
proxyInfo = Filter(proxyInfo, ssrObject) # run filter
if proxyInfo['protocol'] == 'origin': # origin without param
proxyInfo['protocolParam'] = ''
if proxyInfo['obfs'] == 'plain': # plain without param
proxyInfo['obfsParam'] = ''
return proxyInfo

51
Filter/Trojan.py

@ -0,0 +1,51 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Filter import Xray
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Functions import toInt, toStr, toStrTidy
trojanObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'passwd': {
'type': str,
'format': toStr,
'errMsg': 'Invalid password content'
},
'stream': {
'optional': True,
'default': {
'type': 'tcp'
},
'multiSub': True,
'type': {
'tcp': Xray.tcpObject,
'kcp': Xray.kcpObject,
'ws': Xray.wsObject,
'h2': Xray.h2Object,
'quic': Xray.quicObject,
'grpc': Xray.grpcObject,
},
'errMsg': 'Invalid Trojan stream'
}
})
def trojanFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
proxyInfo = Filter(proxyInfo, trojanObject) # run filter
Xray.addSni(proxyInfo) # add SNI option
return proxyInfo

116
Filter/TrojanGo.py

@ -0,0 +1,116 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Filter.Plugin import pluginObject
from Basis.Constant import trojanGoMethods
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool
ssObject = rulesFilter({
'method': {
'optional': True,
'default': 'aes-128-gcm',
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in trojanGoMethods,
'errMsg': 'Unknown Shadowsocks method'
},
'passwd': {
'optional': True,
'default': '',
'type': str,
'format': toStr,
'errMsg': 'Invalid Shadowsocks password'
}
})
wsObject = rulesFilter({
'host': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid WebSocket host'
},
'path': {
'optional': True,
'default': '/',
'type': str,
'format': lambda s: toStr(s).strip(),
'errMsg': 'Invalid WebSocket path'
}
})
trojanGoObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'passwd': {
'type': str,
'format': toStr,
'errMsg': 'Invalid password content'
},
'sni': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid SNI content'
},
'alpn': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space
'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'],
'errMsg': 'Invalid alpn option'
},
'verify': {
'optional': True,
'default': True,
'type': bool,
'format': toBool,
'errMsg': 'Invalid verify option'
},
'ws': {
'optional': True,
'default': None,
'allowNone': True,
'type': wsObject,
'errMsg': 'Invalid WebSocket options'
},
'ss': {
'optional': True,
'default': None,
'allowNone': True,
'type': ssObject,
'errMsg': 'Invalid Shadowsocks options'
},
'plugin': {
'optional': True,
'default': None,
'allowNone': True,
'type': pluginObject,
'errMsg': 'Invalid plugin options'
}
})
def trojanGoFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
proxyInfo = Filter(proxyInfo, trojanGoObject) # run filter
if proxyInfo['sni'] == '' and not isIpAddr(proxyInfo['server']):
proxyInfo['sni'] = proxyInfo['server']
return proxyInfo

256
Filter/V2ray.py

@ -0,0 +1,256 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from Basis.Filter import rulesFilter
from Basis.Constant import quicMethods, udpObfuscations
from Basis.Functions import isIpAddr, toInt, toStr, toStrTidy, toBool
tlsObject = rulesFilter({
'sni': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid SNI content'
},
'alpn': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space
'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'],
'errMsg': 'Invalid alpn option'
},
'verify': {
'optional': True,
'default': True,
'type': bool,
'format': toBool,
'errMsg': 'Invalid verify option'
}
})
obfsObject = rulesFilter({
'host': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid obfs host'
},
'path': {
'optional': True,
'default': '/',
'type': str,
'format': lambda s: toStr(s).strip(),
'errMsg': 'Invalid obfs path'
}
})
tcpObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'tcp',
'errMsg': 'Invalid TCP stream type'
},
'obfs': {
'optional': True,
'default': None,
'allowNone': True,
'type': obfsObject,
'errMsg': 'Invalid obfsObject'
},
'secure': {
'optional': True,
'default': None,
'allowNone': True,
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
})
kcpObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'kcp',
'errMsg': 'Invalid mKCP stream type'
},
'seed': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': toStr,
'errMsg': 'Invalid mKCP seed'
},
'obfs': {
'optional': True,
'default': 'none',
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in udpObfuscations,
'errMsg': 'Unknown mKCP obfs method'
},
'secure': {
'optional': True,
'default': None,
'allowNone': True,
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
})
wsObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'ws',
'errMsg': 'Invalid WebSocket stream type'
},
'host': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid WebSocket host'
},
'path': {
'optional': True,
'default': '/',
'type': str,
'format': lambda s: toStr(s).strip(),
'errMsg': 'Invalid WebSocket path'
},
'ed': {
'optional': True,
'default': None,
'allowNone': True,
'type': int,
'format': toInt,
'filter': lambda i: i > 0,
'errMsg': 'Illegal Max-Early-Data length'
},
'secure': {
'optional': True,
'default': None,
'allowNone': True,
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
})
h2Object = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'h2',
'errMsg': 'Invalid HTTP/2 stream type'
},
'host': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid HTTP/2 host'
},
'path': {
'optional': True,
'default': '/',
'type': str,
'format': lambda s: toStr(s).strip(),
'errMsg': 'Invalid HTTP/2 path'
},
'secure': {
'optional': True,
'default': {},
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
})
quicObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'quic',
'errMsg': 'Invalid QUIC stream type'
},
'method': {
'optional': True,
'default': 'none',
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in quicMethods,
'errMsg': 'Unknown QUIC method'
},
'passwd': {
'optional': True,
'default': '',
'type': str,
'format': toStr,
'errMsg': 'Invalid QUIC password'
},
'obfs': {
'optional': True,
'default': 'none',
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in udpObfuscations,
'errMsg': 'Unknown QUIC obfs method'
},
'secure': {
'optional': True,
'default': {},
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
})
grpcObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'grpc',
'errMsg': 'Invalid gRPC stream type'
},
'service': {
'type': str,
'format': lambda s: toStr(s).strip(),
'errMsg': 'Invalid service content'
},
'mode': {
'optional': True,
'default': 'gun',
'type': str,
'format': toStrTidy,
'filter': lambda s: s in ['gun', 'multi'],
'errMsg': 'Unknown gRPC mode'
},
'secure': {
'optional': True,
'default': None,
'allowNone': True,
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
})
def addSni(proxyInfo: dict) -> None:
stream = proxyInfo['stream']
if stream['secure'] is None or stream['secure']['sni'] != '': # don't need to set SNI
return
if not isIpAddr(proxyInfo['server']):
stream['secure']['sni'] = proxyInfo['server'] # set SNI as server address (domain case)
sniContent = ''
if stream['type'] == 'tcp' and stream['obfs'] is not None: # obfs host in TCP stream
sniContent = stream['obfs']['host'].split(',')[0]
elif stream['type'] == 'ws': # WebSocket host
sniContent = stream['host']
elif stream['type'] == 'h2': # HTTP/2 host
sniContent = stream['host'].split(',')[0]
if sniContent != '':
stream['secure']['sni'] = sniContent # overwrite SNI content

59
Filter/VLESS.py

@ -0,0 +1,59 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Filter import Xray
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Functions import toInt, toStrTidy
vlessObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'method': {
'optional': True,
'default': 'none',
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'none',
'errMsg': 'Unknown VLESS method'
},
'id': {
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid VLESS ID'
},
'stream': {
'optional': True,
'default': {
'type': 'tcp'
},
'multiSub': True,
'type': {
'tcp': Xray.tcpObject,
'kcp': Xray.kcpObject,
'ws': Xray.wsObject,
'h2': Xray.h2Object,
'quic': Xray.quicObject,
'grpc': Xray.grpcObject,
},
'errMsg': 'Invalid VLESS stream'
}
})
def vlessFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
proxyInfo = Filter(proxyInfo, vlessObject) # run filter
Xray.addSni(proxyInfo) # add SNI option
return proxyInfo

68
Filter/VMess.py

@ -0,0 +1,68 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Filter import V2ray
from Basis.Constant import vmessMethods
from Basis.Functions import isHost, isPort
from Basis.Filter import Filter, rulesFilter
from Basis.Functions import toInt, toStrTidy
vmessObject = rulesFilter({
'server': {
'type': str,
'format': toStrTidy,
'filter': isHost,
'errMsg': 'Invalid server address'
},
'port': {
'type': int,
'format': toInt,
'filter': isPort,
'errMsg': 'Invalid port number'
},
'method': {
'optional': True,
'default': 'auto',
'type': str,
'format': lambda s: toStrTidy(s).replace('_', '-'),
'filter': lambda s: s in vmessMethods,
'errMsg': 'Unknown VMess method'
},
'id': {
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid VMess ID'
},
'aid': {
'optional': True,
'default': 0,
'type': int,
'format': toInt,
'filter': lambda i: i in range(0, 65536), # 0 ~ 65535
'errMsg': 'Invalid VMess alter ID'
},
'stream': {
'optional': True,
'default': {
'type': 'tcp'
},
'multiSub': True,
'type': {
'tcp': V2ray.tcpObject,
'kcp': V2ray.kcpObject,
'ws': V2ray.wsObject,
'h2': V2ray.h2Object,
'quic': V2ray.quicObject,
'grpc': V2ray.grpcObject,
},
'errMsg': 'Invalid VMess stream'
}
})
def vmessFilter(proxyInfo: dict) -> dict:
proxyInfo = copy.deepcopy(proxyInfo)
proxyInfo = Filter(proxyInfo, vmessObject) # run filter
V2ray.addSni(proxyInfo) # add SNI option
return proxyInfo

166
Filter/Xray.py

@ -0,0 +1,166 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import copy
from Filter import V2ray
from Basis.Filter import rulesFilter
from Basis.Constant import xtlsFlows
from Basis.Functions import toStrTidy, toBool
def xtlsFlowFormat(flow: str) -> str:
flow = flow.replace('_', '-')
xtlsFlowAlias = {
'xtls-origin': {'origin', 'xtls-rprx-origin'},
'xtls-direct': {'direct', 'xtls-rprx-direct'},
'xtls-splice': {'splice', 'xtls-rprx-splice'},
}
for xtlsFlow, alias in xtlsFlowAlias.items():
if flow in alias:
return xtlsFlow
return flow # alias not found
tlsObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'tls',
'errMsg': 'Invalid TLS secure type'
},
'sni': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid SNI content'
},
'alpn': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space
'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'],
'errMsg': 'Invalid alpn option'
},
'verify': {
'optional': True,
'default': True,
'type': bool,
'format': toBool,
'errMsg': 'Invalid verify option'
}
})
xtlsObject = rulesFilter({
'type': {
'type': str,
'format': toStrTidy,
'filter': lambda s: s == 'xtls',
'errMsg': 'Invalid XTLS secure type'
},
'sni': {
'optional': True,
'default': '',
'type': str,
'format': toStrTidy,
'errMsg': 'Invalid SNI content'
},
'alpn': {
'optional': True,
'default': None,
'allowNone': True,
'type': str,
'format': lambda s: toStrTidy(s).replace(' ', ''), # remove space
'filter': lambda s: s in ['h2', 'http/1.1', 'h2,http/1.1'],
'errMsg': 'Invalid alpn option'
},
'verify': {
'optional': True,
'default': True,
'type': bool,
'format': toBool,
'errMsg': 'Invalid verify option'
},
'flow': {
'optional': True,
'default': 'xtls-direct',
'type': str,
'format': lambda s: xtlsFlowFormat(toStrTidy(s)),
'filter': lambda s: s in xtlsFlows,
'errMsg': 'Unknown XTLS flow'
},
'udp443': {
'optional': True,
'default': False,
'type': bool,
'format': toBool,
'errMsg': 'Invalid udp/443 option'
}
})
secureRule_1 = { # None / tlsObject / xtlsObject
'optional': True,
'default': {
'type': 'tls'
},
'allowNone': True,
'multiSub': True,
'type': {
'tls': tlsObject,
'xtls': xtlsObject,
},
'errMsg': 'Invalid secure options'
}
secureRule_2 = { # None / tlsObject
'optional': True,
'default': {
'type': 'tls'
},
'allowNone': True,
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
secureRule_3 = { # tlsObject
'optional': True,
'default': {
'type': 'tls'
},
'type': tlsObject,
'errMsg': 'Invalid secure options'
}
tcpObject = rulesFilter({
**copy.deepcopy(V2ray.tcpObject),
'secure': secureRule_1 # None / tlsObject / xtlsObject
})
kcpObject = rulesFilter({
**copy.deepcopy(V2ray.kcpObject),
'secure': secureRule_1 # None / tlsObject / xtlsObject
})
wsObject = rulesFilter({
**copy.deepcopy(V2ray.wsObject),
'secure': secureRule_2 # None / tlsObject
})
h2Object = rulesFilter({
**copy.deepcopy(V2ray.h2Object),
'secure': secureRule_3 # tlsObject
})
quicObject = rulesFilter({
**copy.deepcopy(V2ray.quicObject),
'secure': secureRule_3 # tlsObject
})
grpcObject = rulesFilter({
**copy.deepcopy(V2ray.grpcObject),
'secure': secureRule_2 # None / tlsObject
})
addSni = V2ray.addSni

27
Filter/__init__.py

@ -0,0 +1,27 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from Filter.Brook import brookFilter
from Filter.VMess import vmessFilter
from Filter.VLESS import vlessFilter
from Filter.Trojan import trojanFilter
from Filter.Shadowsocks import ssFilter
from Filter.ShadowsocksR import ssrFilter
from Filter.TrojanGo import trojanGoFilter
from Filter.Hysteria import hysteriaFilter
filterEntry = {
'ss': ssFilter,
'ssr': ssrFilter,
'vmess': vmessFilter,
'vless': vlessFilter,
'trojan': trojanFilter,
'trojan-go': trojanGoFilter,
'brook': brookFilter,
'hysteria': hysteriaFilter,
}
def Filter(proxyType: str, proxyInfo: dict) -> dict:
if proxyType not in filterEntry:
raise RuntimeError('Unknown proxy type')
return filterEntry[proxyType](proxyInfo)

93
ProxyFilter/Brook.py

@ -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'

90
ProxyFilter/Hysteria.py

@ -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'

124
ProxyFilter/Plugin.py

@ -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 # 匹配不到时返回原值

135
ProxyFilter/Shadowsocks.py

@ -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'

185
ProxyFilter/ShadowsocksR.py

@ -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'

75
ProxyFilter/Trojan.py

@ -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'

138
ProxyFilter/TrojanGo.py

@ -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'

260
ProxyFilter/V2ray.py

@ -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

85
ProxyFilter/VLESS.py

@ -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'

99
ProxyFilter/VMess.py

@ -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'

110
ProxyFilter/Xray.py

@ -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

6
ProxyFilter/__init__.py

@ -1,6 +0,0 @@
#!/usr/bin/python
# -*- coding:utf-8 -*-
from ProxyFilter.filter import *
__all__ = ['filte']

301
ProxyFilter/baseFunc.py

@ -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}

51
ProxyFilter/filter.py

@ -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'

2
docs/ProxyObject/Brook.md

@ -82,7 +82,7 @@
### raw
+ 类型:*bool*
+ 说明:是否直接传输原始数据(即`--withoutBrookProtocol`)
+ 说明:传输原始数据(即 `--withoutBrookProtocol`
+ 缺省:`False`
+ 限制:无

4
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

4
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

20
docs/ProxyObject/VMess.md

@ -72,7 +72,7 @@
### secure
+ 类型:*None* / [*secureObject*](#secureobject)
+ 类型:*None* / [*tlsObject*](#tlsobject)
+ 说明:TLS加密选项
+ 缺省:`None`
+ 限制:无
@ -104,7 +104,7 @@
### secure
+ 类型:*None* / [*secureObject*](#secureobject)
+ 类型:*None* / [*tlsObject*](#tlsobject)
+ 说明:TLS加密选项
+ 缺省:`None`
+ 限制:无
@ -144,7 +144,7 @@
### secure
+ 类型:*None* / [*secureObject*](#secureobject)
+ 类型:*None* / [*tlsObject*](#tlsobject)
+ 说明:TLS加密选项
+ 缺省:`None`
+ 限制:无
@ -176,9 +176,9 @@
### secure
+ 类型:[*secureObject*](#secureobject)
+ 类型:[*tlsObject*](#tlsobject)
+ 说明:TLS加密选项
+ 缺省:`None`
+ 缺省:`tlsObject`
+ 限制:无
## quicObject
@ -216,9 +216,9 @@
### secure
+ 类型:[*secureObject*](#secureobject)
+ 类型:[*tlsObject*](#tlsobject)
+ 说明:TLS加密选项
+ 缺省:`secureObject`
+ 缺省:`tlsObject`
+ 限制:无
## grpcObject
@ -248,7 +248,7 @@
### secure
+ 类型:*None* / [*secureObject*](#secureobject)
+ 类型:*None* / [*tlsObject*](#tlsobject)
+ 说明:TLS加密选项
+ 缺省:`None`
+ 限制:无
@ -276,7 +276,7 @@
+ 缺省:`/`
+ 限制:无
## secureObject
## tlsObject
```
{
@ -290,7 +290,7 @@
+ 类型:*str*
+ 说明:TLS握手SNI字段
+ 缺省:`obfsObject.host[0]` / `wsObject.host` / `h2Object.host[0]` / `空`
+ 缺省:`空`
+ 限制:无
### alpn

Loading…
Cancel
Save