mirror of
https://github.com/p4gefau1t/trojan-go.git
synced 2026-10-19 16:15:38 +00:00
change url encryption format, fix easy mode json
This commit is contained in:
@@ -4,6 +4,10 @@ draft: false
|
||||
weight: 200
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
- encrpytion 格式修改为 ss;method:password
|
||||
|
||||
## 概述
|
||||
|
||||
感谢 @DuckSoft @StudentMain @phlinhng 对 Trojan-Go URL 方案的讨论和贡献。**目前 URL 方案为草案,需要更多的实践和讨论。**
|
||||
@@ -16,7 +20,7 @@ Trojan-Go**客户端**可以接受URL,以定位服务器资源。原则如下:
|
||||
|
||||
- URL 的作用,是定位 Trojan-Go 节点资源,方便资源分享
|
||||
|
||||
需要注意,基于人类可读性的考虑,禁止将 base64 等编码数据嵌入 URL 中。首先, base64 编码不能保证传输安全,其意义在于在 ASCII 信道传输非 ASCII 数据。其次,如果需要保证分享URL时的传输安全,请对明文URL进行加密,而不是修改URL格式。
|
||||
需要注意,基于人类可读性的考虑,禁止将 base64 等编码数据嵌入 URL 中。首先, base64 编码不能保证传输安全,其意义在于在 ASCII 信道传输非 ASCII 数据。其次,如果需要保证分享 URL 时的传输安全,请对明文 URL 进行加密,而不是修改 URL 格式。
|
||||
|
||||
## 格式
|
||||
|
||||
@@ -42,7 +46,7 @@ trojan-go://
|
||||
例如
|
||||
|
||||
```text
|
||||
trojan-go://password1234@google.com/?sni=microsoft.com&type=ws&host=youtube.com&path=%2Fgo&encryption=ss%3Baes-256-gcm%3Bfuckgfw
|
||||
trojan-go://password1234@google.com/?sni=microsoft.com&type=ws&host=youtube.com&path=%2Fgo&encryption=ss%3Baes-256-gcm%3Afuckgfw
|
||||
```
|
||||
|
||||
由于 Trojan-Go 兼容 Trojan,所以对于 Trojan 的 URL 方案
|
||||
@@ -140,7 +144,7 @@ TLS 认证必须开启。无法使用根CA校验服务器身份的节点,不
|
||||
使用 Shadowsocks 算法进行流量加密时,其格式为:
|
||||
|
||||
```text
|
||||
ss;method;password
|
||||
ss;method:password
|
||||
```
|
||||
|
||||
其中 ss 是固定内容,method 是加密方法,必须为下列之一:
|
||||
|
||||
+80
-42
@@ -1,9 +1,10 @@
|
||||
package easy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/p4gefau1t/trojan-go/option"
|
||||
|
||||
@@ -22,6 +23,31 @@ type easy struct {
|
||||
key *string
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
RunType string `json:"run_type"`
|
||||
LocalAddr string `json:"local_addr"`
|
||||
LocalPort int `json:"local_port"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
Password []string `json:"password"`
|
||||
}
|
||||
|
||||
type TLS struct {
|
||||
SNI string `json:"sni"`
|
||||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
RunType string `json:"run_type"`
|
||||
LocalAddr string `json:"local_addr"`
|
||||
LocalPort int `json:"local_port"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
RemotePort int `json:"remote_port"`
|
||||
Password []string `json:"password"`
|
||||
TLS `json:"ssl"`
|
||||
}
|
||||
|
||||
func (o *easy) Name() string {
|
||||
return "easy"
|
||||
}
|
||||
@@ -35,34 +61,41 @@ func (o *easy) Handle() error {
|
||||
}
|
||||
log.Info("easy mode enabled, trojan-go will NOT use the config file")
|
||||
if *o.client {
|
||||
clientConfigFormat := `
|
||||
{
|
||||
"run_type": "client",
|
||||
"local_addr": "%s",
|
||||
"local_port": %s,
|
||||
"remote_addr": "%s",
|
||||
"remote_port": %s,
|
||||
"password": [
|
||||
"%s"
|
||||
]
|
||||
}
|
||||
`
|
||||
if *o.local == "" {
|
||||
log.Warn("client local addr is unspecified, using 127.0.0.1:1080")
|
||||
*o.local = "127.0.0.1:1080"
|
||||
}
|
||||
localHost, localPort, err := net.SplitHostPort(*o.local)
|
||||
localHost, localPortStr, err := net.SplitHostPort(*o.local)
|
||||
if err != nil {
|
||||
log.Fatal(common.NewError("invalid local addr format:" + *o.local).Base(err))
|
||||
}
|
||||
remoteHost, remotePort, err := net.SplitHostPort(*o.remote)
|
||||
remoteHost, remotePortStr, err := net.SplitHostPort(*o.remote)
|
||||
if err != nil {
|
||||
log.Fatal(common.NewError("invalid remote addr format:" + *o.remote).Base(err))
|
||||
}
|
||||
clientConfigJSON := fmt.Sprintf(clientConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password)
|
||||
log.Info("generated json config:")
|
||||
log.Info(clientConfigJSON)
|
||||
proxy, err := proxy.NewProxyFromConfigData([]byte(clientConfigJSON), true)
|
||||
localPort, err := strconv.Atoi(localPortStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
remotePort, err := strconv.Atoi(remotePortStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
clientConfig := ClientConfig{
|
||||
RunType: "client",
|
||||
LocalAddr: localHost,
|
||||
LocalPort: localPort,
|
||||
RemoteAddr: remoteHost,
|
||||
RemotePort: remotePort,
|
||||
Password: []string{
|
||||
*o.password,
|
||||
},
|
||||
}
|
||||
clientConfigJSON, err := json.Marshal(&clientConfig)
|
||||
common.Must(err)
|
||||
log.Info("generated config:")
|
||||
log.Info(string(clientConfigJSON))
|
||||
proxy, err := proxy.NewProxyFromConfigData(clientConfigJSON, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@@ -71,23 +104,6 @@ func (o *easy) Handle() error {
|
||||
}
|
||||
|
||||
} else if *o.server {
|
||||
serverConfigFormat := `
|
||||
{
|
||||
"run_type": "server",
|
||||
"local_addr": "%s",
|
||||
"local_port": %s,
|
||||
"remote_addr": "%s",
|
||||
"remote_port": %s,
|
||||
"password": [
|
||||
"%s"
|
||||
],
|
||||
"ssl": {
|
||||
"verify_hostname": false,
|
||||
"cert": "%s",
|
||||
"key": "%s"
|
||||
}
|
||||
}
|
||||
`
|
||||
if *o.remote == "" {
|
||||
log.Warn("server remote addr is unspecified, using 127.0.0.1:80")
|
||||
*o.remote = "127.0.0.1:80"
|
||||
@@ -96,19 +112,41 @@ func (o *easy) Handle() error {
|
||||
log.Warn("server local addr is unspecified, using 0.0.0.0:443")
|
||||
*o.local = "0.0.0.0:443"
|
||||
}
|
||||
localHost, localPort, err := net.SplitHostPort(*o.local)
|
||||
localHost, localPortStr, err := net.SplitHostPort(*o.local)
|
||||
if err != nil {
|
||||
log.Fatal(common.NewError("invalid local addr format:" + *o.local).Base(err))
|
||||
}
|
||||
remoteHost, remotePort, err := net.SplitHostPort(*o.remote)
|
||||
remoteHost, remotePortStr, err := net.SplitHostPort(*o.remote)
|
||||
if err != nil {
|
||||
log.Fatal(common.NewError("invalid remote addr format:" + *o.remote).Base(err))
|
||||
}
|
||||
serverConfigJSON := fmt.Sprintf(serverConfigFormat, localHost, localPort, remoteHost, remotePort, *o.password, *o.cert, *o.key)
|
||||
localPort, err := strconv.Atoi(localPortStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
remotePort, err := strconv.Atoi(remotePortStr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
serverConfig := ServerConfig{
|
||||
RunType: "server",
|
||||
LocalAddr: localHost,
|
||||
LocalPort: localPort,
|
||||
RemoteAddr: remoteHost,
|
||||
RemotePort: remotePort,
|
||||
Password: []string{
|
||||
*o.password,
|
||||
},
|
||||
TLS: TLS{
|
||||
Cert: *o.cert,
|
||||
Key: *o.key,
|
||||
},
|
||||
}
|
||||
serverConfigJSON, err := json.Marshal(&serverConfig)
|
||||
common.Must(err)
|
||||
log.Info("generated json config:")
|
||||
|
||||
log.Info(serverConfigJSON)
|
||||
proxy, err := proxy.NewProxyFromConfigData([]byte(serverConfigJSON), true)
|
||||
log.Info(string(serverConfigJSON))
|
||||
proxy, err := proxy.NewProxyFromConfigData(serverConfigJSON, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
+4
-4
@@ -74,12 +74,12 @@ func (u *url) Handle() error {
|
||||
ssMethod := ""
|
||||
if strings.HasPrefix(info.Encryption, "ss;") {
|
||||
ssEnabled = true
|
||||
ssConfig := strings.Split(info.Encryption, ";")
|
||||
if len(ssConfig) != 3 {
|
||||
ssConfig := strings.Split(info.Encryption[3:], ":")
|
||||
if len(ssConfig) != 2 {
|
||||
log.Fatalf("invalid shadowsocks config: %s", info.Encryption)
|
||||
}
|
||||
ssMethod = ssConfig[1]
|
||||
ssPassword = ssConfig[2]
|
||||
ssMethod = ssConfig[0]
|
||||
ssPassword = ssConfig[1]
|
||||
}
|
||||
muxEnabled := false
|
||||
listenHost := "127.0.0.1"
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ func TestUrl_Handle(t *testing.T) {
|
||||
urlCases := []string{
|
||||
"trojan-go://password@server.com",
|
||||
"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath",
|
||||
"trojan-go://password@server.com/?encryption=ss%3Baes-256-gcm%3Bfuckgfw",
|
||||
"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath&encryption=ss%3Baes-256-gcm%3Bfuckgfw",
|
||||
"trojan-go://password@server.com/?encryption=ss%3baes-256-gcm%3afuckgfw",
|
||||
"trojan-go://password@server.com/?type=ws&host=baidu.com&path=%2fwspath&encryption=ss%3Baes-256-gcm%3Afuckgfw",
|
||||
}
|
||||
optionCases := []string{
|
||||
"mux=true;listen=127.0.0.1:0",
|
||||
|
||||
+30
-8
@@ -18,6 +18,10 @@ var validTypes = map[string]struct{}{
|
||||
ShareInfoTypeWebSocket: {},
|
||||
}
|
||||
|
||||
var validEncryptionProviders = map[string]struct{}{
|
||||
"ss": {},
|
||||
}
|
||||
|
||||
var validSSEncryptionMap = map[string]struct{}{
|
||||
"aes-128-gcm": {},
|
||||
"aes-256-gcm": {},
|
||||
@@ -145,19 +149,37 @@ func NewShareInfoFromURL(shareLink string) (info ShareInfo, e error) {
|
||||
e = errors.New("empty encryption")
|
||||
return
|
||||
} else {
|
||||
if strings.HasPrefix(info.Encryption, "ss;") {
|
||||
if parts := strings.SplitN(info.Encryption, ";", 3); len(parts) != 3 {
|
||||
e = errors.New("expected 3 ss encryption params")
|
||||
encryptionParts := strings.SplitN(info.Encryption, ";", 2)
|
||||
encryptionProviderName := encryptionParts[0]
|
||||
|
||||
if _, ok := validEncryptionProviders[encryptionProviderName]; !ok {
|
||||
e = errors.New(fmt.Sprintf("unsupported encryption provider name: %s", encryptionProviderName))
|
||||
return
|
||||
}
|
||||
|
||||
var encryptionParams string
|
||||
if len(encryptionParts) >= 2 {
|
||||
encryptionParams = encryptionParts[1]
|
||||
}
|
||||
|
||||
if encryptionProviderName == "ss" {
|
||||
ssParams := strings.SplitN(encryptionParams, ":", 2)
|
||||
if len(ssParams) < 2 {
|
||||
e = errors.New("missing ss password")
|
||||
return
|
||||
} else if _, ok := validSSEncryptionMap[parts[1]]; !ok {
|
||||
e = errors.New(fmt.Sprintf("unsupported ss encryption method: %s", parts[1]))
|
||||
}
|
||||
|
||||
ssMethod, ssPassword := ssParams[0], ssParams[1]
|
||||
if _, ok := validSSEncryptionMap[ssMethod]; !ok {
|
||||
e = errors.New(fmt.Sprintf("unsupported ss method: %s", ssMethod))
|
||||
return
|
||||
} else if parts[2] == "" {
|
||||
e = errors.New("empty ss encryption key")
|
||||
}
|
||||
|
||||
if ssPassword == "" {
|
||||
e = errors.New("ss password cannot be empty")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// add more encryption support here
|
||||
e = errors.New(fmt.Sprintf("encryption param %s is not supported", info.Encryption))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -222,13 +222,13 @@ func TestNewShareInfoFromURL_Encryption_Unknown(t *testing.T) {
|
||||
func TestNewShareInfoFromURL_Encryption_SS_NotSupportedMethods(t *testing.T) {
|
||||
invalidMethods := []string{"rc4-md5", "rc4", "des-cfb", "table", "salsa20-ctr"}
|
||||
for _, invalidMethod := range invalidMethods {
|
||||
_, e := NewShareInfoFromURL(fmt.Sprintf("trojan-go://a@b.c?encryption=ss%%3B%s%%3Bshabi", invalidMethod))
|
||||
_, e := NewShareInfoFromURL(fmt.Sprintf("trojan-go://a@b.c?encryption=ss%%3B%s%%3Ashabi", invalidMethod))
|
||||
assert.Error(t, e, "encryption %s should not be supported by ss", invalidMethod)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewShareInfoFromURL_Encryption_SS_NoPassword(t *testing.T) {
|
||||
_, e := NewShareInfoFromURL("trojan-go://a@b.c?encryption=ss%3Baes-256-gcm%3B")
|
||||
_, e := NewShareInfoFromURL("trojan-go://a@b.c?encryption=ss%3Baes-256-gcm%3A")
|
||||
assert.Error(t, e, "empty ss password should not be allowed")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user