diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index b14761a49..938a88583 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -3,6 +3,7 @@ package outbound import ( "context" "crypto/tls" + "errors" "fmt" "net" "net/http" @@ -15,6 +16,7 @@ import ( tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/transport/gun" + "github.com/metacubex/mihomo/transport/shadowsocks/core" "github.com/metacubex/mihomo/transport/trojan" ) @@ -29,6 +31,8 @@ type Trojan struct { transport *gun.TransportWrap realityConfig *tlsC.RealityConfig + + ssCipher core.Cipher } type TrojanOption struct { @@ -46,9 +50,17 @@ type TrojanOption struct { RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"` + SSOpts TrojanSSOption `proxy:"ss-opts,omitempty"` ClientFingerprint string `proxy:"client-fingerprint,omitempty"` } +// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5 +type TrojanSSOption struct { + Enabled bool `proxy:"enabled,omitempty"` + Method string `proxy:"method,omitempty"` + Password string `proxy:"password,omitempty"` +} + func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) { if t.option.Network == "ws" { host, port, _ := net.SplitHostPort(t.addr) @@ -95,6 +107,10 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C. return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } + if t.ssCipher != nil { + c = t.ssCipher.StreamConn(c) + } + if metadata.NetWork == C.UDP { err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) return c, err @@ -112,6 +128,10 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ... return nil, err } + if t.ssCipher != nil { + c = t.ssCipher.StreamConn(c) + } + if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil { c.Close() return nil, err @@ -161,6 +181,11 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, defer func(c net.Conn) { safeConnClose(c, err) }(c) + + if t.ssCipher != nil { + c = t.ssCipher.StreamConn(c) + } + err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) if err != nil { return nil, err @@ -193,6 +218,10 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } + if t.ssCipher != nil { + c = t.ssCipher.StreamConn(c) + } + err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata)) if err != nil { return nil, err @@ -257,6 +286,20 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { } tOption.Reality = t.realityConfig + if option.SSOpts.Enabled { + if option.SSOpts.Password == "" { + return nil, errors.New("empty password") + } + if option.SSOpts.Method == "" { + option.SSOpts.Method = "AES-128-GCM" + } + ciph, err := core.PickCipher(option.SSOpts.Method, nil, option.SSOpts.Password) + if err != nil { + return nil, err + } + t.ssCipher = ciph + } + if option.Network == "grpc" { dialFn := func(network, addr string) (net.Conn, error) { var err error diff --git a/docs/config.yaml b/docs/config.yaml index 987491e34..fe8501636 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -611,6 +611,10 @@ proxies: # socks5 # - h2 # - http/1.1 # skip-cert-verify: true + # ss-opts: # like trojan-go's `shadowsocks` config + # enabled: false + # method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305 + # password: "example" - name: trojan-grpc server: server