From 1a3403d4e1b07825626f20e1220ecff2374c4427 Mon Sep 17 00:00:00 2001 From: cesaryuan Date: Tue, 11 Mar 2025 18:39:11 +0800 Subject: [PATCH] feat: add gost-plugin which currently only ws and mws are tested --- adapter/outbound/shadowsocks.go | 47 ++++++++++++++++ transport/gost-plugin/websocket.go | 86 ++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 transport/gost-plugin/websocket.go diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 94a7763d..49e638a4 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -13,6 +13,7 @@ import ( "github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" + gost "github.com/metacubex/mihomo/transport/gost-plugin" "github.com/metacubex/mihomo/transport/restls" obfs "github.com/metacubex/mihomo/transport/simple-obfs" shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" @@ -34,6 +35,7 @@ type ShadowSocks struct { obfsMode string obfsOption *simpleObfsOption v2rayOption *v2rayObfs.Option + gostOption *gost.Option shadowTLSOption *shadowtls.ShadowTLSOption restlsConfig *restlsC.Config } @@ -71,6 +73,19 @@ type v2rayObfsOption struct { V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"` } +type gostObfsOption struct { + Mode string `obfs:"mode"` + Host string `obfs:"host,omitempty"` + Path string `obfs:"path,omitempty"` + TLS bool `obfs:"tls,omitempty"` + Fingerprint string `obfs:"fingerprint,omitempty"` + Headers map[string]string `obfs:"headers,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` + Mux bool `obfs:"mux,omitempty"` + V2rayHttpUpgrade bool `obfs:"v2ray-http-upgrade,omitempty"` + V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"` +} + type shadowTLSOption struct { Password string `obfs:"password"` Host string `obfs:"host"` @@ -101,6 +116,12 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } + case gost.ModeWebsocket: + var err error + c, err = gost.NewGostWebsocket(ctx, c, ss.gostOption) + if err != nil { + return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) + } case shadowtls.Mode: var err error c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption) @@ -240,6 +261,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } var v2rayOption *v2rayObfs.Option + var gostOption *gost.Option var obfsOption *simpleObfsOption var shadowTLSOpt *shadowtls.ShadowTLSOption var restlsConfig *restlsC.Config @@ -281,6 +303,30 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.Fingerprint = opts.Fingerprint } + } else if option.Plugin == "gost-plugin" { + opts := gostObfsOption{Host: "bing.com", Mux: true} + if err := decoder.Decode(option.PluginOpts, &opts); err != nil { + return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err) + } + + if opts.Mode != gost.ModeWebsocket { + return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode) + } + obfsMode = opts.Mode + gostOption = &gost.Option{ + Host: opts.Host, + Path: opts.Path, + Headers: opts.Headers, + Mux: opts.Mux, + V2rayHttpUpgrade: opts.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: opts.V2rayHttpUpgradeFastOpen, + } + + if opts.TLS { + gostOption.TLS = true + gostOption.SkipCertVerify = opts.SkipCertVerify + gostOption.Fingerprint = opts.Fingerprint + } } else if option.Plugin == shadowtls.Mode { obfsMode = shadowtls.Mode opt := &shadowTLSOption{ @@ -336,6 +382,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { option: &option, obfsMode: obfsMode, v2rayOption: v2rayOption, + gostOption: gostOption, obfsOption: obfsOption, shadowTLSOption: shadowTLSOpt, restlsConfig: restlsConfig, diff --git a/transport/gost-plugin/websocket.go b/transport/gost-plugin/websocket.go new file mode 100644 index 00000000..bb668f4d --- /dev/null +++ b/transport/gost-plugin/websocket.go @@ -0,0 +1,86 @@ +package gost + +import ( + "context" + "crypto/tls" + "net" + "net/http" + + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/transport/vmess" + smux "github.com/sagernet/smux" +) + +const ( + ModeWebsocket string = "gost-websocket" +) + +// Option is options of gost websocket +type Option struct { + Host string + Port string + Path string + Headers map[string]string + TLS bool + SkipCertVerify bool + Fingerprint string + Mux bool + V2rayHttpUpgrade bool + V2rayHttpUpgradeFastOpen bool +} + +// NewGostWebsocket return a gost websocket +func NewGostWebsocket(ctx context.Context, conn net.Conn, option *Option) (net.Conn, error) { + header := http.Header{} + for k, v := range option.Headers { + header.Add(k, v) + } + + config := &vmess.WebsocketConfig{ + Host: option.Host, + Port: option.Port, + Path: option.Path, + V2rayHttpUpgrade: option.V2rayHttpUpgrade, + V2rayHttpUpgradeFastOpen: option.V2rayHttpUpgradeFastOpen, + Headers: header, + } + + if option.TLS { + config.TLS = true + tlsConfig := &tls.Config{ + ServerName: option.Host, + InsecureSkipVerify: option.SkipCertVerify, + NextProtos: []string{"http/1.1"}, + } + var err error + config.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) + if err != nil { + return nil, err + } + + if host := config.Headers.Get("Host"); host != "" { + config.TLSConfig.ServerName = host + } + } + + var err error + conn, err = vmess.StreamWebsocketConn(ctx, conn, config) + if err != nil { + return nil, err + } + + if option.Mux { + session, err := smux.Client(conn, nil) + if err != nil { + return nil, err + } + + stream, err := session.OpenStream() + if err != nil { + return nil, err + } + + conn = stream + } + return conn, nil +}