diff --git a/ProxyDecoder/Trojan.py b/ProxyDecoder/Trojan.py index a1bf4e3..8845d31 100644 --- a/ProxyDecoder/Trojan.py +++ b/ProxyDecoder/Trojan.py @@ -8,7 +8,7 @@ def __trojanCommonDecode(url: str) -> dict: """ Trojan标准分享链接解码 - FORMAT: trojan://$(UUID)@server:port?{fields}#$(remark) + FORMAT: trojan://$(password)@server:port?{fields}#$(remark) type -> tcp / kcp / ws / http / quic / grpc diff --git a/ProxyDecoder/TrojanGo.py b/ProxyDecoder/TrojanGo.py new file mode 100644 index 0000000..2cf4961 --- /dev/null +++ b/ProxyDecoder/TrojanGo.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding:utf-8 -*- + +import re +from ProxyDecoder import baseFunc + +def __trojanGoCommonDecode(url: str) -> dict: + """ + Trojan-Go标准分享链接解码 + + FORMAT: trojan-go://$(password)@server:port/?{fields}#$(remark) + + sni -> TLS SNI + + type -> original / ws + + host -> WebSocket Host + + path -> WebSocket Path + + encryption -> urlEncode(ss;aes-256-gcm;ss-password) + + plugin -> urlEncode(pluginName;pluginOption) -> officially undefined (refer to SIP002 scheme) + + """ + match = re.search(r'^trojan-go://([\S]+?)(#[\S]*)?$', url) # trojan-go://...#REMARK + remark = baseFunc.urlDecode( + match[2][1:] if match[2] is not None else '' + ) + match = re.search( + r'^([\S]+)@([a-zA-Z0-9.:\-_\[\]]+)/?([\S]*)$', match[1] # $(password)@server[:port]/?... + ) + info = { + 'passwd': baseFunc.urlDecode(match[1]), + 'remark': remark + } + params = baseFunc.paramSplit(match[3]) + match = re.search( + r'^([a-zA-Z0-9.:\-_\[\]]+?)(:([0-9]{1,5}))?$', match[2] # server[:port] + ) + info['server'] = baseFunc.formatHost(match[1]) + info['port'] = int(match[3]) if match[3] is not None else 443 + + if 'sni' in params: + info['sni'] = params['sni'] + + if 'type' in params: + if params['type'] not in ['', 'original', 'ws']: + raise Exception('Unknown Trojan-Go network type') + if params['type'] == 'ws': # WebSocket mode + info['ws'] = {} + if 'host' in params: + info['ws']['host'] = params['host'] + if 'path' in params: + info['ws']['path'] = params['path'] + + if 'encryption' in params and params['encryption'] not in ['', 'none']: # shadowsocks encrypt + match = re.search( + r'^ss;([a-zA-Z0-9\-_]+):([\S]+)$', params['encryption'] + ) + info['ss'] = { + 'method': match[1], + 'passwd': match[2] + } + + if 'plugin' in params and params['plugin'] not in ['', 'none']: # SIP003 plugin + match = re.search( + r'^([\S]+?)(;([\S]+))?$', params['plugin'] + ) + info['plugin'] = { + 'type': match[1], + 'param': match[3] if match[3] is not None else '' + } + return info + +def trojanGoDecode(url: str) -> dict or None: + """ + Trojan-Go分享链接解码 + + 链接合法: + return { + 'type': 'trojan-go', + ... + } + + 链接不合法: + return None + """ + if url[0:12] != 'trojan-go://': + return None + try: + result = __trojanGoCommonDecode(url) # try Trojan-Go common decode + except: + return None + result['type'] = 'trojan-go' + return result diff --git a/ProxyDecoder/decoder.py b/ProxyDecoder/decoder.py index 907cc56..9423775 100644 --- a/ProxyDecoder/decoder.py +++ b/ProxyDecoder/decoder.py @@ -7,6 +7,7 @@ from ProxyDecoder import ShadowsocksR from ProxyDecoder import VMess from ProxyDecoder import VLESS from ProxyDecoder import Trojan +from ProxyDecoder import TrojanGo def decode(url: str) -> dict or None: """ @@ -34,6 +35,8 @@ def decode(url: str) -> dict or None: return VLESS.vlessDecode(url) elif scheme == 'trojan': return Trojan.trojanDecode(url) + elif scheme == 'trojan-go': + return TrojanGo.trojanGoDecode(url) except: pass return None diff --git a/demo.py b/demo.py index 76c1a77..eb9b160 100644 --- a/demo.py +++ b/demo.py @@ -5,32 +5,32 @@ import ProxyDecoder as Decoder import ProxyFilter as Filter import Check as Checker -info = { - 'type': 'trojan-go', - 'server': '127.0.0.1', - 'port': 12345, - 'passwd': 'dnomd343', - 'sni': 'local.343.re', - 'plugin': { - 'type': 'simple-tls', - 'param': 'n=local.343.re;no-verify' - } -} +# info = { +# 'type': 'trojan-go', +# 'server': '127.0.0.1', +# 'port': 12345, +# 'passwd': 'dnomd343', +# 'sni': 'local.343.re', +# 'plugin': { +# 'type': 'simple-tls', +# 'param': 'n=local.343.re;no-verify' +# } +# } +# +# status, ret = Filter.filte(info, isExtra = True) +# print(status) +# print(ret) +# +# data = Checker.proxyTest({ +# 'check': ['http'], +# 'info': ret +# }) +# +# print(data) -status, ret = Filter.filte(info, isExtra = True) -print(status) +url = 'trojan-go://password1234@google.com/?sni=microsoft.com&type=ws&host=youtube.com&path=%2Fgo&encryption=ss%3Baes-256-gcm%3Afuckgfw&plugin=obfs-local%3Bobfs%3Dhttp%3Bobfs-host%3Dwww.bing.com#server%20name' +ret = Decoder.decode(url) print(ret) -data = Checker.proxyTest({ - 'check': ['http'], - 'info': ret -}) - -print(data) - -# status, client = Builder.build(ret, '/tmp/ProxyC') -# print(status) -# print(client) -# -# time.sleep(300) -# Builder.destroy(client) +ret = Filter.filte(ret) +print(ret)