Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 45 additions & 18 deletions adapter/outbound/trojan.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,24 +37,26 @@ type Trojan struct {

type TrojanOption struct {
BasicOption
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
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"`
Name string `proxy:"name"`
Server string `proxy:"server"`
Port int `proxy:"port"`
Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Certificate string `proxy:"certificate,omitempty"`
PrivateKey string `proxy:"private-key,omitempty"`
UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"`
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
XHTTPOpts XHTTPOptions `proxy:"xhttp-opts,omitempty"`
SplitHTTPOpts SplitHTTPOptions `proxy:"splithttp-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
Expand Down Expand Up @@ -116,6 +118,8 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "grpc":
break // already handle in gun transport
case "xhttp", "splithttp":
break // already handle in dialXHTTPConn
default:
// default tcp network
// handle TLS
Expand Down Expand Up @@ -172,8 +176,31 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C
return err
}

func (t *Trojan) dialXHTTPConn(ctx context.Context) (net.Conn, error) {
cfg, err := buildXHTTPConfig(t.option.Network, t.addr, t.option.SNI, t.option.ALPN, t.option.XHTTPOpts, t.option.SplitHTTPOpts, t.realityConfig != nil)
if err != nil {
return nil, err
}

return dialXHTTPConn(ctx, t.dialer, cfg, xhttpTLSOptions{
Address: t.addr,
TLSEnabled: true,
ServerName: t.option.SNI,
SkipCertVerify: t.option.SkipCertVerify,
Fingerprint: t.option.Fingerprint,
Certificate: t.option.Certificate,
PrivateKey: t.option.PrivateKey,
ClientFingerprint: t.option.ClientFingerprint,
ALPN: cfg.ALPN,
ECH: t.echConfig,
Reality: t.realityConfig,
})
}

func (t *Trojan) dialContext(ctx context.Context) (c net.Conn, err error) {
switch t.option.Network {
case "xhttp", "splithttp":
return t.dialXHTTPConn(ctx)
case "grpc": // gun transport
return t.gunTransport.Dial()
default:
Expand Down
185 changes: 22 additions & 163 deletions adapter/outbound/vless.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ import (
"github.com/metacubex/mihomo/transport/vless"
"github.com/metacubex/mihomo/transport/vless/encryption"
"github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/mihomo/transport/xhttp"

"github.com/metacubex/http"
vmessSing "github.com/metacubex/sing-vmess"
"github.com/metacubex/sing-vmess/packetaddr"
M "github.com/metacubex/sing/common/metadata"
"github.com/metacubex/tls"
"github.com/samber/lo"
)

type Vless struct {
Expand Down Expand Up @@ -63,6 +61,7 @@ type VlessOption struct {
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
XHTTPOpts XHTTPOptions `proxy:"xhttp-opts,omitempty"`
SplitHTTPOpts SplitHTTPOptions `proxy:"splithttp-opts,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"`
Expand All @@ -72,38 +71,6 @@ type VlessOption struct {
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
}

type XHTTPOptions struct {
Path string `proxy:"path,omitempty"`
Host string `proxy:"host,omitempty"`
Mode string `proxy:"mode,omitempty"`
Headers map[string]string `proxy:"headers,omitempty"`
NoGRPCHeader bool `proxy:"no-grpc-header,omitempty"`
XPaddingBytes string `proxy:"x-padding-bytes,omitempty"`
DownloadSettings *XHTTPDownloadSettings `proxy:"download-settings,omitempty"`
}

type XHTTPDownloadSettings struct {
// xhttp part
Path *string `proxy:"path,omitempty"`
Host *string `proxy:"host,omitempty"`
Headers *map[string]string `proxy:"headers,omitempty"`
NoGRPCHeader *bool `proxy:"no-grpc-header,omitempty"`
XPaddingBytes *string `proxy:"x-padding-bytes,omitempty"`
// proxy part
Server *string `proxy:"server,omitempty"`
Port *int `proxy:"port,omitempty"`
TLS *bool `proxy:"tls,omitempty"`
ALPN *[]string `proxy:"alpn,omitempty"`
ECHOpts *ECHOptions `proxy:"ech-opts,omitempty"`
RealityOpts *RealityOptions `proxy:"reality-opts,omitempty"`
SkipCertVerify *bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint *string `proxy:"fingerprint,omitempty"`
Certificate *string `proxy:"certificate,omitempty"`
PrivateKey *string `proxy:"private-key,omitempty"`
ServerName *string `proxy:"servername,omitempty"`
ClientFingerprint *string `proxy:"client-fingerprint,omitempty"`
}

func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch v.option.Network {
case "ws":
Expand Down Expand Up @@ -185,7 +152,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
case "grpc":
break // already handle in gun transport
case "xhttp":
case "xhttp", "splithttp":
break // already handle in dialXHTTPConn
default:
// default tcp network
Expand Down Expand Up @@ -265,139 +232,31 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
return conn, nil
}

func (v *Vless) dialXHTTPConn() (net.Conn, error) {
requestHost := v.option.XHTTPOpts.Host
if requestHost == "" {
if v.option.ServerName != "" {
requestHost = v.option.ServerName
} else {
requestHost = v.option.Server
}
}

cfg := &xhttp.Config{
Host: requestHost,
Path: v.option.XHTTPOpts.Path,
Mode: v.option.XHTTPOpts.Mode,
Headers: v.option.XHTTPOpts.Headers,
NoGRPCHeader: v.option.XHTTPOpts.NoGRPCHeader,
XPaddingBytes: v.option.XHTTPOpts.XPaddingBytes,
}

transport := xhttp.NewTransport(
func(ctx context.Context) (net.Conn, error) {
return v.dialer.DialContext(ctx, "tcp", v.addr)
},
func(ctx context.Context, raw net.Conn, isH2 bool) (net.Conn, error) {
return v.streamTLSConn(ctx, raw, isH2)
},
)
downloadTransport := transport

if ds := v.option.XHTTPOpts.DownloadSettings; ds != nil {
if cfg.Mode == "stream-one" {
return nil, fmt.Errorf(`xhttp mode "stream-one" cannot be used with download-settings`)
}

var err error

downloadServer := lo.FromPtrOr(ds.Server, v.option.Server)
downloadPort := lo.FromPtrOr(ds.Port, v.option.Port)
downloadTLS := lo.FromPtrOr(ds.TLS, v.option.TLS)
downloadALPN := lo.FromPtrOr(ds.ALPN, v.option.ALPN)
downloadEchConfig := v.echConfig
if ds.ECHOpts != nil {
downloadEchConfig, err = ds.ECHOpts.Parse()
if err != nil {
return nil, err
}
}
downloadRealityCfg := v.realityConfig
if ds.RealityOpts != nil {
downloadRealityCfg, err = ds.RealityOpts.Parse()
if err != nil {
return nil, err
}
}
downloadSkipCertVerify := lo.FromPtrOr(ds.SkipCertVerify, v.option.SkipCertVerify)
downloadFingerprint := lo.FromPtrOr(ds.Fingerprint, v.option.Fingerprint)
downloadCertificate := lo.FromPtrOr(ds.Certificate, v.option.Certificate)
downloadPrivateKey := lo.FromPtrOr(ds.PrivateKey, v.option.PrivateKey)
downloadServerName := lo.FromPtrOr(ds.ServerName, v.option.ServerName)
downloadClientFingerprint := lo.FromPtrOr(ds.ClientFingerprint, v.option.ClientFingerprint)

downloadAddr := net.JoinHostPort(downloadServer, strconv.Itoa(downloadPort))

downloadHost := lo.FromPtrOr(ds.Host, v.option.XHTTPOpts.Host)
if downloadHost == "" {
if downloadServerName != "" {
downloadHost = downloadServerName
} else {
downloadHost = downloadServer
}
}

cfg.DownloadConfig = &xhttp.Config{
Host: downloadHost,
Path: lo.FromPtrOr(ds.Path, v.option.XHTTPOpts.Path),
Mode: v.option.XHTTPOpts.Mode,
Headers: lo.FromPtrOr(ds.Headers, v.option.XHTTPOpts.Headers),
NoGRPCHeader: lo.FromPtrOr(ds.NoGRPCHeader, v.option.XHTTPOpts.NoGRPCHeader),
XPaddingBytes: lo.FromPtrOr(ds.XPaddingBytes, v.option.XHTTPOpts.XPaddingBytes),
}

downloadTransport = xhttp.NewTransport(
func(ctx context.Context) (net.Conn, error) {
return v.dialer.DialContext(ctx, "tcp", downloadAddr)
},
func(ctx context.Context, conn net.Conn, isH2 bool) (net.Conn, error) {
if downloadTLS {
host, _, _ := net.SplitHostPort(downloadAddr)

tlsOpts := vmess.TLSConfig{
Host: host,
SkipCertVerify: downloadSkipCertVerify,
FingerPrint: downloadFingerprint,
Certificate: downloadCertificate,
PrivateKey: downloadPrivateKey,
ClientFingerprint: downloadClientFingerprint,
ECH: downloadEchConfig,
Reality: downloadRealityCfg,
NextProtos: downloadALPN,
}

if isH2 {
tlsOpts.NextProtos = []string{"h2"}
}

if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName
}

return vmess.StreamTLSConn(ctx, conn, &tlsOpts)
}

return conn, nil
},
)
func (v *Vless) dialXHTTPConn(ctx context.Context) (net.Conn, error) {
cfg, err := buildXHTTPConfig(v.option.Network, v.addr, v.option.ServerName, v.option.ALPN, v.option.XHTTPOpts, v.option.SplitHTTPOpts, v.realityConfig != nil)
if err != nil {
return nil, err
}

mode := cfg.EffectiveMode(v.realityConfig != nil)
switch mode {
case "stream-one":
return xhttp.DialStreamOne(cfg, transport)
case "stream-up":
return xhttp.DialStreamUp(cfg, transport, downloadTransport)
case "packet-up":
return xhttp.DialPacketUp(cfg, transport)
default:
return nil, fmt.Errorf("xhttp mode %s is not implemented yet", mode)
}
return dialXHTTPConn(ctx, v.dialer, cfg, xhttpTLSOptions{
Address: v.addr,
TLSEnabled: v.option.TLS,
ServerName: v.option.ServerName,
SkipCertVerify: v.option.SkipCertVerify,
Fingerprint: v.option.Fingerprint,
Certificate: v.option.Certificate,
PrivateKey: v.option.PrivateKey,
ClientFingerprint: v.option.ClientFingerprint,
ALPN: cfg.ALPN,
ECH: v.echConfig,
Reality: v.realityConfig,
})
}

func (v *Vless) dialContext(ctx context.Context) (c net.Conn, err error) {
switch v.option.Network {
case "xhttp":
return v.dialXHTTPConn()
case "xhttp", "splithttp":
return v.dialXHTTPConn(ctx)
case "grpc": // gun transport
return v.gunTransport.Dial()
default:
Expand Down
Loading