diff --git a/ProxyBuilder/Trojan.py b/ProxyBuilder/Trojan.py new file mode 100644 index 0000000..b816edb --- /dev/null +++ b/ProxyBuilder/Trojan.py @@ -0,0 +1,45 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- + +import json +from ProxyBuilder import Xray + +def load(proxyInfo: dict, socksPort: int, configFile: str) -> tuple[list, str, dict]: + """ + Trojan配置载入 + proxyInfo: 节点信息 + socksPort: 本地通讯端口 + configFile: 配置文件路径 + + return startCommand, fileContent, envVar + """ + flowType = None + if proxyInfo['stream']['secure'] is not None and proxyInfo['stream']['secure']['type'] == 'xtls': + flowType = proxyInfo['stream']['secure']['flow'] + if flowType == 'xtls-origin': + flowType = 'xtls-rprx-origin' + elif flowType == 'xtls-direct': + flowType = 'xtls-rprx-direct' + elif flowType == 'xtls-splice': + flowType = 'xtls-rprx-splice' + else: + raise Exception('Unknown XTLS flow') + if proxyInfo['stream']['secure']['udp443']: + flowType += '-udp443' + outboundConfig = { + 'protocol': 'trojan', + 'settings': { + 'servers': [ + { + 'address': proxyInfo['server'], + 'port': proxyInfo['port'], + 'password': proxyInfo['passwd'], + } + ] + }, + 'streamSettings': Xray.xrayStreamConfig(proxyInfo['stream']) + } + if flowType is not None: # 添加XTLS流控类型 + outboundConfig['settings']['servers'][0]['flow'] = flowType + config = Xray.baseConfig(socksPort, outboundConfig) # Trojan节点配置 + return ['xray', '-c', configFile], json.dumps(config), {} diff --git a/ProxyBuilder/V2ray.py b/ProxyBuilder/V2ray.py index 61700df..f367c76 100644 --- a/ProxyBuilder/V2ray.py +++ b/ProxyBuilder/V2ray.py @@ -101,7 +101,7 @@ def h2Config(streamInfo: dict, secureFunc) -> dict: # HTTP/2传输方式配置 if streamInfo['host'] != '': h2Object['host'] = streamInfo['host'].split(',') return {**{ - 'network': 'h2', + 'network': 'http', 'httpSettings': h2Object }, **secureFunc(streamInfo['secure'])} diff --git a/ProxyBuilder/builder.py b/ProxyBuilder/builder.py index f857a20..775834d 100644 --- a/ProxyBuilder/builder.py +++ b/ProxyBuilder/builder.py @@ -12,6 +12,7 @@ from ProxyBuilder import Shadowsocks from ProxyBuilder import ShadowsocksR from ProxyBuilder import VMess from ProxyBuilder import VLESS +from ProxyBuilder import Trojan libcPaths = [ '/usr/lib64/libc.so.6', # CentOS @@ -104,6 +105,8 @@ def build(proxyInfo: dict, configDir: str, clientObj = VMess elif proxyInfo['type'] == 'vless': # VLESS节点 clientObj = VLESS + elif proxyInfo['type'] == 'trojan': # Trojan节点 + clientObj = Trojan else: # 未知类型 return False, 'Unknown proxy type' diff --git a/ProxyFilter/Trojan.py b/ProxyFilter/Trojan.py new file mode 100644 index 0000000..beb23b8 --- /dev/null +++ b/ProxyFilter/Trojan.py @@ -0,0 +1,84 @@ +#!/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, { + 'type': 'trojan' + }) + if not status: # 节点格式错误 + return False, result + stream = result['stream'] + if stream['secure'] is not None and stream['secure']['sni'] == '': # 未指定SNI + if stream['type'] == 'tcp' and stream['obfs'] is not None: + stream['secure']['sni'] = stream['obfs']['host'].split(',')[0] + elif stream['type'] == 'ws': + stream['secure']['sni'] = stream['host'] + elif stream['type'] == 'h2': + stream['secure']['sni'] = stream['host'].split(',')[0] + return True, result + except: + return False, 'Unknown error' diff --git a/ProxyFilter/filter.py b/ProxyFilter/filter.py index c95e14d..1043674 100644 --- a/ProxyFilter/filter.py +++ b/ProxyFilter/filter.py @@ -5,6 +5,7 @@ from ProxyFilter import Shadowsocks from ProxyFilter import ShadowsocksR from ProxyFilter import VMess from ProxyFilter import VLESS +from ProxyFilter import Trojan def filte(raw: dict, isExtra: bool = False) -> tuple[bool, str or dict]: """ @@ -31,6 +32,8 @@ def filte(raw: dict, isExtra: bool = False) -> tuple[bool, str or dict]: return VMess.vmessFilter(raw, isExtra) elif raw['type'] == 'vless': return VLESS.vlessFilter(raw, isExtra) + elif raw['type'] == 'trojan': + return Trojan.trojanFilter(raw, isExtra) else: return False, 'Unknown proxy type' except: diff --git a/ProxyTester/Trojan.py b/ProxyTester/Trojan.py new file mode 100644 index 0000000..133c34c --- /dev/null +++ b/ProxyTester/Trojan.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- +import json + +from ProxyTester import Xray + +config = {} + +def trojanBasicTest() -> dict: + serverConfig = { + 'run_type': 'server', + 'local_addr': '127.0.0.1', + 'local_port': config['port'], + 'password': [ + config['passwd'] + ], + 'ssl': { + 'cert': config['cert'], + 'key': config['key'] + } + } + return { + 'caption': 'Trojan basic', + 'proxy': { + 'type': 'trojan', + 'server': '127.0.0.1', + 'port': config['port'], + 'passwd': config['passwd'], + 'stream': { + 'type': 'tcp', + 'secure': { + 'type': 'tls', + 'sni': config['host'] + } + } + }, + 'server': { + 'startCommand': ['trojan', '-c', config['file']], + 'fileContent': json.dumps(serverConfig), + 'filePath': config['file'], + 'envVar': {} + }, + 'aider': None + } + +def loadTrojanStream(streamInfo: dict, xtlsFlow: str or None) -> dict: + proxyInfo = { + 'type': 'trojan', + 'server': '127.0.0.1', + 'port': config['port'], + 'passwd': config['passwd'], + 'stream': streamInfo['client'] + } + inboundConfig = { + 'protocol': 'trojan', + 'listen': '127.0.0.1', + 'port': config['port'], + 'settings': { + 'clients': [ + { + 'password': config['passwd'] + } + ] + }, + 'streamSettings': streamInfo['server'] + } + if xtlsFlow is not None: # add XTLS flow option + inboundConfig['settings']['clients'][0]['flow'] = xtlsFlow + return { + 'caption': 'Trojan network ' + streamInfo['caption'], + 'proxy': proxyInfo, + 'server': { + 'startCommand': ['xray', '-c', config['file']], + 'fileContent': Xray.xrayConfig(inboundConfig), + 'filePath': config['file'], + 'envVar': {} + }, + 'aider': None + } + +def trojanTest(trojanConfig: dict) -> list: + result = [] + for key, value in trojanConfig.items(): # trojanConfig -> config + config[key] = value + + result.append(trojanBasicTest()) # basic test + + # TCP stream + streamInfo = Xray.loadTcpStream(False, '', '') + result.append(loadTrojanStream(streamInfo, None)) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + for flow in Xray.xtlsFlowList: + streamInfo = Xray.loadTcpStream(False, '', '') + xtlsFlow, streamInfo = Xray.addXtlsConfig(streamInfo, config['cert'], config['key'], config['host'], flow) + result.append(loadTrojanStream(streamInfo, xtlsFlow)) + + streamInfo = Xray.loadTcpStream(True, config['host'], '/') + result.append(loadTrojanStream(streamInfo, None)) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + + # mKCP stream + for obfs in Xray.udpObfsList: + streamInfo = Xray.loadKcpStream(config['passwd'], obfs) + result.append(loadTrojanStream(streamInfo, None)) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + for flow in Xray.xtlsFlowList: + streamInfo = Xray.loadKcpStream(config['passwd'], obfs) + xtlsFlow, streamInfo = Xray.addXtlsConfig(streamInfo, config['cert'], config['key'], config['host'], flow) + result.append(loadTrojanStream(streamInfo, xtlsFlow)) + + # WebSocket stream + streamInfo = Xray.loadWsStream(config['host'], config['path'], False) + result.append(loadTrojanStream(streamInfo, None)) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + + streamInfo = Xray.loadWsStream(config['host'], config['path'], True) + result.append(loadTrojanStream(streamInfo, None)) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + + # HTTP/2 stream + streamInfo = Xray.loadH2Stream(config['host'], config['path']) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + + # QUIC stream + for method in Xray.quicMethodList: + for obfs in Xray.udpObfsList: + streamInfo = Xray.loadQuicStream(method, config['passwd'], obfs) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + + # GRPC stream + streamInfo = Xray.loadGrpcStream(config['service']) + result.append(loadTrojanStream(streamInfo, None)) + streamInfo = Xray.addTlsConfig(streamInfo, config['cert'], config['key'], config['host']) + result.append(loadTrojanStream(streamInfo, None)) + + return result diff --git a/ProxyTester/V2ray.py b/ProxyTester/V2ray.py index 698aac7..6ef445b 100644 --- a/ProxyTester/V2ray.py +++ b/ProxyTester/V2ray.py @@ -145,7 +145,7 @@ def loadH2Stream(host: str, path: str) -> dict: 'path': path }, 'server': { - 'network': 'h2', + 'network': 'http', 'httpSettings': { 'host': [host], 'path': path diff --git a/ProxyTester/tester.py b/ProxyTester/tester.py index ab45a77..b10422b 100644 --- a/ProxyTester/tester.py +++ b/ProxyTester/tester.py @@ -5,6 +5,7 @@ from ProxyTester import Shadowsocks from ProxyTester import ShadowsocksR from ProxyTester import VMess from ProxyTester import VLESS +from ProxyTester import Trojan def test(key: str, config: dict) -> list: if key in ['ss', 'shadowsocks']: @@ -15,5 +16,7 @@ def test(key: str, config: dict) -> list: return VMess.vmessTest(config) elif key == 'vless': return VLESS.vlessTest(config) + elif key == 'trojan': + return Trojan.trojanTest(config) else: return [] diff --git a/Test.py b/Test.py index 9e484ce..fa103af 100644 --- a/Test.py +++ b/Test.py @@ -52,12 +52,15 @@ def testObject(option: dict) -> None: # test target object }) print(option['caption'], end=' -> ') if not checkResult['success']: # client build error - print('\n----------------------------------------------------------------') + print('\n--------------------------------------------------------------------------------------------------------------------------------') print(option) - print('----------------------------------------------------------------\n') + print('--------------------------------------------------------------------------------------------------------------------------------\n') + testDestroy(option['server'], serverProcess) # destroy and exit + if option['aider'] is not None: + testDestroy(option['aider'], aiderProcess) raise Exception('check error') delay = checkResult['check']['http']['delay'] # get http delay - print(str(delay) + 'ms') + print(format(delay, '.2f') + 'ms') testDestroy(option['server'], serverProcess) # destroy server process if option['aider'] is not None: diff --git a/docs/ProxyObject.md b/docs/ProxyObject.md index aba1119..719c625 100644 --- a/docs/ProxyObject.md +++ b/docs/ProxyObject.md @@ -869,3 +869,361 @@ + 缺省:False + 可选值:不限 + 建议值:False + +## Trojan + +> **remark** +> +> + 类型:*str* +> + 说明:节点备注名称 +> + 缺省:'' +> + 可选值:不限 + +``` +{ + 'type': 'trojan', + 'server': ..., + 'port': ..., + 'passwd': ..., + 'stream': ... +} +``` + +**server** + ++ 类型:*str* ++ 说明:服务器地址 ++ 缺省:必选 ++ 可选值:合法的IP地址或域名 + +**port** + ++ 类型:*int* ++ 说明:服务器端口 ++ 缺省:必选 ++ 可选值:1 ~ 65535 + +**passwd** + ++ 类型:*str* ++ 说明:Trojan连接密码 ++ 缺省:必选 ++ 可选值:不限 + +**stream** + ++ 类型:*tcpObject* / *kcpObject* / *wsObject* / *h2Object* / *quicObject* / *grpcObject* ++ 说明:Trojan底层传输方式 ++ 缺省:tcpObject ++ 可选值:不限 + +### tcpObject + +``` +{ + 'type': 'tcp', + 'obfs': ..., + 'secure': ... +} +``` + +**obfs** + ++ 类型:*None* / *obfsObject* ++ 说明:http伪装 ++ 缺省:None ++ 可选值:不限 + +**secure** + ++ 类型:*None* / *tlsObject* / *xtlsObject* ++ 说明:TLS加密 ++ 缺省:None ++ 可选值:不限 + +### kcpObject + +``` +{ + 'type': 'kcp', + 'seed': ..., + 'obfs': ..., + 'secure': ... +} +``` + +**seed** + ++ 类型:*None* / *str* ++ 说明:mKCP混淆密码 ++ 缺省:None ++ 可选值:不限 + +**obfs** + ++ 类型:*str* ++ 说明:数据包头部伪装类型 ++ 缺省:'none' ++ 可选值:`none`,`srtp`,`utp`,`wechat-video`,`dtls`,`wireguard` + +**secure** + ++ 类型:*None* / *tlsObject* / *xtlsObject* ++ 说明:TLS加密 ++ 缺省:None ++ 可选值:不限 + +### wsObject + +``` +{ + 'type': 'ws', + 'host': ..., + 'path': ..., + 'ed': ..., + 'secure': ... +} +``` + +**host** + ++ 类型:*str* ++ 说明:Websocket连接域名 ++ 缺省:'' ++ 可选值:不限 ++ 建议值:合法域名 + +**path** + ++ 类型:*str* ++ 说明:Websocket连接路径 ++ 缺省:'/' ++ 可选值:不限 ++ 建议值:以`/`开头的合法路径 + +**ed** + ++ 类型:*None* / *int* ++ 说明:`Early Data`长度阈值 ++ 缺省:None ++ 可选值:>0 ++ 建议值:2048 + +**secure** + ++ 类型:*None* / *tlsObject* ++ 说明:TLS加密 ++ 缺省:None ++ 可选值:不限 + +### h2Object + +``` +{ + 'type': 'h2', + 'host': ..., + 'path': ..., + 'secure': ... +} +``` + +**host** + ++ 类型:*str* ++ 说明:HTTP/2通讯域名 ++ 缺省:'' ++ 可选值:不限 ++ 建议值:合法域名列表(逗号隔开) + +**path** + ++ 类型:*str* ++ 说明:HTTP/2通讯路径 ++ 缺省:'/' ++ 可选值:不限 ++ 建议值:以`/`开头的合法路径 + +**secure** + ++ 类型:*tlsObject* ++ 说明:TLS加密 ++ 缺省:None ++ 可选值:不限 + +### quicObject + +``` +{ + 'type': 'quic', + 'method': ..., + 'passwd': ..., + 'obfs': ..., + 'secure': ... +} +``` + +**method** + ++ 类型:*str* ++ 说明:QUIC加密方式 ++ 缺省:'none' ++ 可选值:`none`,`aes-128-gcm`,`chacha20-poly1305` + +**passwd** + ++ 类型:*str* ++ 说明:QUIC连接密码 ++ 缺省:'' ++ 可选值:不限 + +**obfs** + ++ 类型:*str* ++ 说明:数据包头部伪装类型 ++ 缺省:'none' ++ 可选值:`none`,`srtp`,`utp`,`wechat-video`,`dtls`,`wireguard` + +**secure** + ++ 类型:*tlsObject* ++ 说明:TLS加密 ++ 缺省:secureObject ++ 可选值:不限 + +### grpcObject + +``` +{ + 'type': 'grpc', + 'service': ..., + 'secure': ... +} +``` + +**service** + ++ 类型:*str* ++ 说明:gRPC服务名称 ++ 缺省:必选 ++ 可选值:不限 ++ 建议值:英文大小写字母、数字、下划线及英文句号组成 + +**secure** + ++ 类型:*None* / *tlsObject* ++ 说明:TLS加密 ++ 缺省:None ++ 可选值:不限 + +### obfsObject + +``` +{ + 'host': ..., + 'path': ... +} +``` + +**host** + ++ 类型:*str* ++ 说明:http伪装域名 ++ 缺省:'' ++ 可选值:不限 ++ 建议值:合法域名列表(逗号隔开) + +**path** + ++ 类型:*str* ++ 说明:http伪装路径 ++ 缺省:'/' ++ 可选值:不限 ++ 建议值:以`/`开头的合法路径 + +### tlsObject + +``` +{ + 'type': 'tls', + 'sni': ..., + 'alpn': ..., + 'verify': ... +} +``` + +**sni** + ++ 类型:*str* ++ 说明:TLS握手SNI字段 ++ 缺省:obfsObject.host[0] / wsObject.host / h2Object.host[0] / '' ++ 可选值:不限 ++ 建议值:合法域名 + +**alpn** + ++ 类型:*None* / *str* ++ 说明:TLS握手协商协议 ++ 缺省:None ++ 可选值:`h2`,`http/1.1`,`h2,http/1.1` ++ 建议值:'h2,http/1.1' + +**verify** + ++ 类型:*bool* ++ 说明:是否验证服务端证书 ++ 缺省:True ++ 可选值:True / False ++ 建议值:True + +### xtlsObject + +``` +{ + 'type': 'xtls', + 'sni': ..., + 'alpn': ..., + 'verify': ..., + 'flow': ..., + 'udp443': ... +} +``` + +**sni** + ++ 类型:*str* ++ 说明:TLS握手SNI字段 ++ 缺省:obfsObject.host[0] / wsObject.host / h2Object.host[0] / '' ++ 可选值:不限 ++ 建议值:合法域名 + +**alpn** + ++ 类型:*None* / *str* ++ 说明:TLS握手协商协议 ++ 缺省:None ++ 可选值:`h2`,`http/1.1`,`h2,http/1.1` ++ 建议值:'h2,http/1.1' + +**verify** + ++ 类型:*bool* ++ 说明:是否验证服务端证书 ++ 缺省:True ++ 可选值:不限 ++ 建议值:True + +**flow** + ++ 类型:*str* ++ 说明:XTLS流控算法 ++ 缺省:'xtls-direct' ++ 可选值:`xtls-origin`,`xtls-direct`,`xtls-splice` ++ 建议值:'xtls-direct' (Linux平台建议`xtls-splice`) + +**udp443** + ++ 类型:*bool* ++ 说明:是否放行UDP/443端口流量 ++ 缺省:False ++ 可选值:不限 ++ 建议值:False