From 07d48c773295f71e5fa0ad3e18b5a55d97c58a03 Mon Sep 17 00:00:00 2001 From: mzz2017 <2017@duck.com> Date: Sun, 16 Jun 2024 05:07:00 +0800 Subject: [PATCH] fix: outbound --- component/outbound/dialer/alive_dialer_set.go | 2 +- component/outbound/dialer/annotation.go | 5 + component/outbound/dialer/block.go | 43 +- .../outbound/dialer/connectivity_check.go | 6 +- component/outbound/dialer/dialer.go | 22 +- component/outbound/dialer/direct.go | 21 +- component/outbound/dialer/http/http.go | 115 ------ component/outbound/dialer/juicity/juicity.go | 153 ------- component/outbound/dialer/latencies_n.go | 2 +- component/outbound/dialer/register.go | 69 +--- .../dialer/shadowsocks/shadowsocks.go | 250 ------------ .../dialer/shadowsocksr/shadowsocksr.go | 167 -------- component/outbound/dialer/sockopt.go | 2 +- component/outbound/dialer/socks/socks.go | 104 ----- component/outbound/dialer/socks/socks_test.go | 56 --- component/outbound/dialer/trojan/trojan.go | 194 --------- component/outbound/dialer/tuic/tuic.go | 160 -------- component/outbound/dialer/utils.go | 5 + component/outbound/dialer/v2ray/cipher.go | 26 -- component/outbound/dialer/v2ray/v2ray.go | 384 ------------------ component/outbound/dialer_group.go | 2 +- component/outbound/dialer_group_test.go | 82 ++-- component/outbound/dialer_selection_policy.go | 2 +- component/outbound/filter.go | 37 +- component/outbound/outbound.go | 2 +- .../outbound/transport/simpleobfs/dialer.go | 7 - .../outbound/transport/simpleobfs/http.go | 108 ----- .../transport/simpleobfs/simpleobfs.go | 97 ----- .../outbound/transport/simpleobfs/tls.go | 200 --------- component/outbound/transport/tls/dialer.go | 8 - component/outbound/transport/tls/tls.go | 131 ------ component/outbound/transport/tls/utls.go | 63 --- component/outbound/transport/ws/conn.go | 42 -- component/outbound/transport/ws/dialer.go | 10 - component/outbound/transport/ws/ws.go | 108 ----- control/control_plane.go | 26 +- go.mod | 5 +- go.sum | 2 + 38 files changed, 141 insertions(+), 2577 deletions(-) delete mode 100644 component/outbound/dialer/http/http.go delete mode 100644 component/outbound/dialer/juicity/juicity.go delete mode 100644 component/outbound/dialer/shadowsocks/shadowsocks.go delete mode 100644 component/outbound/dialer/shadowsocksr/shadowsocksr.go delete mode 100644 component/outbound/dialer/socks/socks.go delete mode 100644 component/outbound/dialer/socks/socks_test.go delete mode 100644 component/outbound/dialer/trojan/trojan.go delete mode 100644 component/outbound/dialer/tuic/tuic.go delete mode 100644 component/outbound/dialer/v2ray/cipher.go delete mode 100644 component/outbound/dialer/v2ray/v2ray.go delete mode 100644 component/outbound/transport/simpleobfs/dialer.go delete mode 100644 component/outbound/transport/simpleobfs/http.go delete mode 100644 component/outbound/transport/simpleobfs/simpleobfs.go delete mode 100644 component/outbound/transport/simpleobfs/tls.go delete mode 100644 component/outbound/transport/tls/dialer.go delete mode 100644 component/outbound/transport/tls/tls.go delete mode 100644 component/outbound/transport/tls/utls.go delete mode 100644 component/outbound/transport/ws/conn.go delete mode 100644 component/outbound/transport/ws/dialer.go delete mode 100644 component/outbound/transport/ws/ws.go diff --git a/component/outbound/dialer/alive_dialer_set.go b/component/outbound/dialer/alive_dialer_set.go index 70279f4..85e4e91 100644 --- a/component/outbound/dialer/alive_dialer_set.go +++ b/component/outbound/dialer/alive_dialer_set.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package dialer diff --git a/component/outbound/dialer/annotation.go b/component/outbound/dialer/annotation.go index ef61536..72e2d20 100644 --- a/component/outbound/dialer/annotation.go +++ b/component/outbound/dialer/annotation.go @@ -1,3 +1,8 @@ +/* +* SPDX-License-Identifier: AGPL-3.0-only +* Copyright (c) 2022-2024, daeuniverse Organization + */ + package dialer import ( diff --git a/component/outbound/dialer/block.go b/component/outbound/dialer/block.go index 34035eb..8dc2840 100644 --- a/component/outbound/dialer/block.go +++ b/component/outbound/dialer/block.go @@ -1,50 +1,19 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package dialer import ( - "fmt" - "net" - + D "github.com/daeuniverse/outbound/dialer" "github.com/daeuniverse/outbound/netproxy" ) -type blockDialer struct { - DialCallback func() -} - -func (d *blockDialer) Dial(network, addr string) (c netproxy.Conn, err error) { - magicNetwork, err := netproxy.ParseMagicNetwork(network) - if err != nil { - return nil, err - } - switch magicNetwork.Network { - case "tcp": - return d.DialTcp(addr) - case "udp": - return d.DialUdp(addr) - default: - return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network) - } -} - -func (d *blockDialer) DialTcp(addr string) (c netproxy.Conn, err error) { - d.DialCallback() - return nil, net.ErrClosed -} -func (d *blockDialer) DialUdp(addr string) (c netproxy.PacketConn, err error) { - d.DialCallback() - return nil, net.ErrClosed -} - func NewBlockDialer(option *GlobalOption, dialCallback func()) (netproxy.Dialer, *Property) { - return &blockDialer{DialCallback: dialCallback}, &Property{ - Name: "block", - Address: "", - Protocol: "", - Link: "", + d, _p := D.NewBlockDialer(&option.ExtraOption, dialCallback) + return d, &Property{ + Property: *_p, + SubscriptionTag: "", } } diff --git a/component/outbound/dialer/connectivity_check.go b/component/outbound/dialer/connectivity_check.go index a4e0ace..304f769 100644 --- a/component/outbound/dialer/connectivity_check.go +++ b/component/outbound/dialer/connectivity_check.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package dialer @@ -272,10 +272,10 @@ type CheckOption struct { func (d *Dialer) ActivateCheck() { d.tickerMu.Lock() defer d.tickerMu.Unlock() - if d.InstanceOption.CheckEnabled { + if d.InstanceOption.DisableCheck || d.checkActivated { return } - d.InstanceOption.CheckEnabled = true + d.checkActivated = true go d.aliveBackground() } diff --git a/component/outbound/dialer/dialer.go b/component/outbound/dialer/dialer.go index 9f5b1a2..5d08706 100644 --- a/component/outbound/dialer/dialer.go +++ b/component/outbound/dialer/dialer.go @@ -1,3 +1,8 @@ +/* +* SPDX-License-Identifier: AGPL-3.0-only +* Copyright (c) 2022-2024, daeuniverse Organization + */ + package dialer import ( @@ -6,6 +11,7 @@ import ( "sync" "time" + D "github.com/daeuniverse/outbound/dialer" "github.com/daeuniverse/outbound/netproxy" "github.com/sirupsen/logrus" ) @@ -29,29 +35,26 @@ type Dialer struct { checkCh chan time.Time ctx context.Context cancel context.CancelFunc + + checkActivated bool } type GlobalOption struct { + D.ExtraOption Log *logrus.Logger TcpCheckOptionRaw TcpCheckOptionRaw // Lazy parse CheckDnsOptionRaw CheckDnsOptionRaw // Lazy parse CheckInterval time.Duration CheckTolerance time.Duration CheckDnsTcp bool - AllowInsecure bool - TlsImplementation string - UtlsImitate string } type InstanceOption struct { - CheckEnabled bool + DisableCheck bool } type Property struct { - Name string - Address string - Protocol string - Link string + D.Property SubscriptionTag string } @@ -77,9 +80,6 @@ func NewDialer(dialer netproxy.Dialer, option *GlobalOption, iOption InstanceOpt ctx: ctx, cancel: cancel, } - if iOption.CheckEnabled { - go d.aliveBackground() - } return d } diff --git a/component/outbound/dialer/direct.go b/component/outbound/dialer/direct.go index b008a4a..d8a707c 100644 --- a/component/outbound/dialer/direct.go +++ b/component/outbound/dialer/direct.go @@ -1,20 +1,19 @@ +/* +* SPDX-License-Identifier: AGPL-3.0-only +* Copyright (c) 2022-2024, daeuniverse Organization + */ + package dialer import ( + D "github.com/daeuniverse/outbound/dialer" "github.com/daeuniverse/outbound/netproxy" - softwindDirect "github.com/daeuniverse/outbound/protocol/direct" ) func NewDirectDialer(option *GlobalOption, fullcone bool) (netproxy.Dialer, *Property) { - property := &Property{ - Name: "direct", - Address: "", - Protocol: "", - Link: "", - } - if fullcone { - return softwindDirect.FullconeDirect, property - } else { - return softwindDirect.SymmetricDirect, property + d, _p := D.NewDirectDialer(&option.ExtraOption, fullcone) + return d, &Property{ + Property: *_p, + SubscriptionTag: "", } } diff --git a/component/outbound/dialer/http/http.go b/component/outbound/dialer/http/http.go deleted file mode 100644 index e83e6c8..0000000 --- a/component/outbound/dialer/http/http.go +++ /dev/null @@ -1,115 +0,0 @@ -package http - -import ( - "fmt" - "net" - "net/url" - "strconv" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol/http" -) - -func init() { - dialer.FromLinkRegister("http", NewHTTP) - dialer.FromLinkRegister("https", NewHTTP) -} - -type HTTP struct { - Name string `json:"name"` - Server string `json:"server"` - Port int `json:"port"` - Username string `json:"username"` - Password string `json:"password"` - SNI string `json:"sni"` - Protocol string `json:"protocol"` - AllowInsecure bool `json:"allowInsecure"` -} - -func NewHTTP(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - s, err := ParseHTTPURL(link) - if err != nil { - return nil, nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err) - } - return s.Dialer(option, nextDialer) -} - -func ParseHTTPURL(link string) (data *HTTP, err error) { - u, err := url.Parse(link) - if err != nil || (u.Scheme != "http" && u.Scheme != "https") { - return nil, fmt.Errorf("%w: %v", dialer.InvalidParameterErr, err) - } - pwd, _ := u.User.Password() - strPort := u.Port() - if strPort == "" { - if u.Scheme == "http" { - strPort = "80" - } else if u.Scheme == "https" { - strPort = "443" - } - } - port, err := strconv.Atoi(strPort) - if err != nil { - return nil, fmt.Errorf("error when parsing port: %w", err) - } - allowInsecure, _ := strconv.ParseBool(u.Query().Get("allowInsecure")) - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("allow_insecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("allowinsecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("skipVerify")) - } - return &HTTP{ - Name: u.Fragment, - Server: u.Hostname(), - Port: port, - Username: u.User.Username(), - Password: pwd, - SNI: u.Query().Get("sni"), - Protocol: u.Scheme, - AllowInsecure: allowInsecure, - }, nil -} - -func (s *HTTP) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { - u := s.URL() - d, err := http.NewHTTPProxy(&u, nextDialer) - if err != nil { - return nil, nil, err - } - return d, &dialer.Property{ - Name: s.Name, - Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Protocol: s.Protocol, - Link: u.String(), - }, nil -} - -func (s *HTTP) URL() url.URL { - u := url.URL{ - Scheme: s.Protocol, - Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Fragment: s.Name, - } - if s.SNI != "" { - u.RawQuery = url.Values{"sni": []string{s.SNI}, "allowInsecure": []string{common.BoolToString(s.AllowInsecure)}}.Encode() - } - if s.Username != "" { - if s.Password != "" { - u.User = url.UserPassword(s.Username, s.Password) - } else { - u.User = url.User(s.Username) - } - } - return u -} - -func (s *HTTP) ExportToURL() string { - u := s.URL() - return u.String() -} diff --git a/component/outbound/dialer/juicity/juicity.go b/component/outbound/dialer/juicity/juicity.go deleted file mode 100644 index 5826d24..0000000 --- a/component/outbound/dialer/juicity/juicity.go +++ /dev/null @@ -1,153 +0,0 @@ -package juicity - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/base64" - "encoding/hex" - "fmt" - "net" - "net/url" - "strconv" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol" -) - -func init() { - dialer.FromLinkRegister("juicity", NewJuicity) -} - -type Juicity struct { - Name string - Server string - Port int - User string - Password string - Sni string - AllowInsecure bool - CongestionControl string - PinnedCertchainSha256 string - Protocol string -} - -func NewJuicity(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - s, err := ParseJuicityURL(link) - if err != nil { - return nil, nil, err - } - return s.Dialer(option, nextDialer) -} - -func (s *Juicity) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { - d := nextDialer - var err error - var flags protocol.Flags - tlsConfig := &tls.Config{ - NextProtos: []string{"h3"}, - MinVersion: tls.VersionTLS13, - ServerName: s.Sni, - InsecureSkipVerify: s.AllowInsecure || option.AllowInsecure, - } - if s.PinnedCertchainSha256 != "" { - pinnedHash, err := base64.URLEncoding.DecodeString(s.PinnedCertchainSha256) - if err != nil { - pinnedHash, err = base64.StdEncoding.DecodeString(s.PinnedCertchainSha256) - if err != nil { - pinnedHash, err = hex.DecodeString(s.PinnedCertchainSha256) - if err != nil { - return nil, nil, fmt.Errorf("failed to decode PinnedCertchainSha256") - } - } - } - tlsConfig.InsecureSkipVerify = true - tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - if !bytes.Equal(common.GenerateCertChainHash(rawCerts), pinnedHash) { - return fmt.Errorf("pinned hash of cert chain does not match") - } - return nil - } - } - if d, err = protocol.NewDialer("juicity", d, protocol.Header{ - ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Feature1: s.CongestionControl, - TlsConfig: tlsConfig, - User: s.User, - Password: s.Password, - IsClient: true, - Flags: flags, - }); err != nil { - return nil, nil, err - } - return d, &dialer.Property{ - Name: s.Name, - Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Protocol: s.Protocol, - Link: s.ExportToURL(), - }, nil -} - -func ParseJuicityURL(u string) (data *Juicity, err error) { - //trojan://password@server:port#escape(remarks) - t, err := url.Parse(u) - if err != nil { - err = fmt.Errorf("invalid trojan format") - return - } - allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure")) - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("allow_insecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("allowinsecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("skipVerify")) - } - sni := t.Query().Get("peer") - if sni == "" { - sni = t.Query().Get("sni") - } - if sni == "" { - sni = t.Hostname() - } - port, err := strconv.Atoi(t.Port()) - if err != nil { - return nil, dialer.InvalidParameterErr - } - password, _ := t.User.Password() - data = &Juicity{ - Name: t.Fragment, - Server: t.Hostname(), - Port: port, - User: t.User.Username(), - Password: password, - Sni: sni, - AllowInsecure: allowInsecure, - CongestionControl: t.Query().Get("congestion_control"), - PinnedCertchainSha256: t.Query().Get("pinned_certchain_sha256"), - Protocol: "juicity", - } - return data, nil -} - -func (t *Juicity) ExportToURL() string { - u := &url.URL{ - Scheme: "juicity", - User: url.UserPassword(t.User, t.Password), - Host: net.JoinHostPort(t.Server, strconv.Itoa(t.Port)), - Fragment: t.Name, - } - q := u.Query() - if t.AllowInsecure { - q.Set("allow_insecure", "1") - } - common.SetValue(&q, "sni", t.Sni) - common.SetValue(&q, "congestion_control", t.CongestionControl) - common.SetValue(&q, "pinned_certchain_sha256", t.PinnedCertchainSha256) - u.RawQuery = q.Encode() - return u.String() -} diff --git a/component/outbound/dialer/latencies_n.go b/component/outbound/dialer/latencies_n.go index df6db3c..2b6fb9f 100644 --- a/component/outbound/dialer/latencies_n.go +++ b/component/outbound/dialer/latencies_n.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package dialer diff --git a/component/outbound/dialer/register.go b/component/outbound/dialer/register.go index 9ddbab3..6fdc383 100644 --- a/component/outbound/dialer/register.go +++ b/component/outbound/dialer/register.go @@ -1,74 +1,23 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package dialer import ( - "fmt" - "net/url" - "strings" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/outbound/netproxy" + D "github.com/daeuniverse/outbound/dialer" "github.com/daeuniverse/outbound/protocol/direct" ) -type FromLinkCreator func(gOption *GlobalOption, nextDialer netproxy.Dialer, link string) (dialer netproxy.Dialer, property *Property, err error) - -var fromLinkCreators = make(map[string]FromLinkCreator) - -func FromLinkRegister(name string, creator FromLinkCreator) { - fromLinkCreators[name] = creator -} - func NewFromLink(gOption *GlobalOption, iOption InstanceOption, link string, subscriptionTag string) (*Dialer, error) { - /// Get overwritten name. - overwrittenName, linklike := common.GetTagFromLinkLikePlaintext(link) - links := strings.Split(linklike, "->") - d := direct.SymmetricDirect - p := &Property{ - Name: "", - Address: "", - Protocol: "", - Link: link, + d, _p, err := D.NewNetproxyDialerFromLink(direct.SymmetricDirect, &gOption.ExtraOption, link) + if err != nil { + return nil, err + } + p := Property{ + Property: *_p, SubscriptionTag: subscriptionTag, } - for i := len(links) - 1; i >= 0; i-- { - link := strings.TrimSpace(links[i]) - u, err := url.Parse(link) - if err != nil { - return nil, err - } - creator, ok := fromLinkCreators[u.Scheme] - if !ok { - return nil, fmt.Errorf("unexpected link type: %v", u.Scheme) - } - var _property *Property - d, _property, err = creator(gOption, d, link) - if err != nil { - return nil, fmt.Errorf("create %v: %w", link, err) - } - if p.Name == "" { - p.Name = _property.Name - } else { - p.Name = _property.Name + "->" + p.Name - } - if p.Protocol == "" { - p.Protocol = _property.Protocol - } else { - p.Protocol = _property.Protocol + "->" + p.Protocol - } - if p.Address == "" { - p.Address = _property.Address - } else { - p.Address = _property.Address + "->" + p.Address - } - } - if overwrittenName != "" { - p.Name = overwrittenName - } - node := NewDialer(d, gOption, iOption, p) - return node, nil + return NewDialer(d, gOption, iOption, &p), nil } diff --git a/component/outbound/dialer/shadowsocks/shadowsocks.go b/component/outbound/dialer/shadowsocks/shadowsocks.go deleted file mode 100644 index e6c4a1c..0000000 --- a/component/outbound/dialer/shadowsocks/shadowsocks.go +++ /dev/null @@ -1,250 +0,0 @@ -package shadowsocks - -import ( - "encoding/base64" - "fmt" - "net" - "net/url" - "strconv" - "strings" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/dae/component/outbound/transport/simpleobfs" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol" - "github.com/daeuniverse/outbound/protocol/shadowsocks" -) - -func init() { - // Use random salt by default to decrease the boot time - shadowsocks.DefaultSaltGeneratorType = shadowsocks.RandomSaltGeneratorType - - dialer.FromLinkRegister("shadowsocks", NewShadowsocksFromLink) - dialer.FromLinkRegister("ss", NewShadowsocksFromLink) -} - -type Shadowsocks struct { - Name string `json:"name"` - Server string `json:"server"` - Port int `json:"port"` - Password string `json:"password"` - Cipher string `json:"cipher"` - Plugin Sip003 `json:"plugin"` - UDP bool `json:"udp"` - Protocol string `json:"protocol"` -} - -func NewShadowsocksFromLink(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (npd netproxy.Dialer, property *dialer.Property, err error) { - s, err := ParseSSURL(link) - if err != nil { - return nil, nil, err - } - return s.Dialer(option, nextDialer) -} - -func (s *Shadowsocks) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { - var err error - d := nextDialer - switch s.Plugin.Name { - case "simple-obfs": - switch s.Plugin.Opts.Obfs { - case "http", "tls": - default: - return nil, nil, fmt.Errorf("unsupported obfs %v of plugin %v", s.Plugin.Opts.Obfs, s.Plugin.Name) - } - host := s.Plugin.Opts.Host - if host == "" { - host = "cloudflare.com" - } - path := s.Plugin.Opts.Path - uSimpleObfs := url.URL{ - Scheme: "simple-obfs", - Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - RawQuery: url.Values{ - "obfs": []string{s.Plugin.Opts.Obfs}, - "host": []string{host}, - "uri": []string{path}, - }.Encode(), - } - d, _, err = simpleobfs.NewSimpleObfs(option, d, uSimpleObfs.String()) - if err != nil { - return nil, nil, err - } - default: - } - var nextDialerName string - switch s.Cipher { - case "aes-256-gcm", "aes-128-gcm", "chacha20-poly1305", "chacha20-ietf-poly1305": - nextDialerName = "shadowsocks" - case "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-ofb", "aes-192-ofb", "aes-256-ofb", "des-cfb", "bf-cfb", "cast5-cfb", "rc4-md5", "rc4-md5-6", "chacha20", "chacha20-ietf", "salsa20", "camellia-128-cfb", "camellia-192-cfb", "camellia-256-cfb", "idea-cfb", "rc2-cfb", "seed-cfb", "rc4", "none", "plain": - nextDialerName = "shadowsocks_stream" - default: - return nil, nil, fmt.Errorf("unsupported shadowsocks encryption method: %v", s.Cipher) - } - d, err = protocol.NewDialer(nextDialerName, d, protocol.Header{ - ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Cipher: s.Cipher, - Password: s.Password, - IsClient: true, - }) - if err != nil { - return nil, nil, err - } - return d, &dialer.Property{ - Name: s.Name, - Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Protocol: s.Protocol, - Link: s.ExportToURL(), - }, nil -} - -func ParseSSURL(u string) (data *Shadowsocks, err error) { - // parse attempts to parse ss:// links - parse := func(content string) (v *Shadowsocks, ok bool) { - // try to parse in the format of ss://BASE64(method:password)@server:port/?plugin=xxxx#name - u, err := url.Parse(content) - if err != nil { - return nil, false - } - username := u.User.String() - username, _ = common.Base64UrlDecode(username) - arr := strings.SplitN(username, ":", 2) - if len(arr) != 2 { - return nil, false - } - cipher := arr[0] - password := arr[1] - var sip003 Sip003 - plugin := u.Query().Get("plugin") - if len(plugin) > 0 { - sip003 = ParseSip003(plugin) - } - port, err := strconv.Atoi(u.Port()) - if err != nil { - return nil, false - } - return &Shadowsocks{ - Cipher: strings.ToLower(cipher), - Password: password, - Server: u.Hostname(), - Port: port, - Name: u.Fragment, - Plugin: sip003, - UDP: sip003.Name == "", - Protocol: "shadowsocks", - }, true - } - var ( - v *Shadowsocks - ok bool - ) - content := u - // try to parse the ss:// link, if it fails, base64 decode first - if v, ok = parse(content); !ok { - // 进行base64解码,并unmarshal到VmessInfo上 - t := content[5:] - var l, r string - if ind := strings.Index(t, "#"); ind > -1 { - l = t[:ind] - r = t[ind+1:] - } else { - l = t - } - l, err = common.Base64StdDecode(l) - if err != nil { - l, err = common.Base64UrlDecode(l) - if err != nil { - return - } - } - t = "ss://" + l - if len(r) > 0 { - t += "#" + r - } - v, ok = parse(t) - } - if !ok { - return nil, fmt.Errorf("%w: unrecognized ss address", dialer.InvalidParameterErr) - } - return v, nil -} - -type Sip003 struct { - Name string `json:"name"` - Opts Sip003Opts `json:"opts"` -} -type Sip003Opts struct { - Tls string `json:"tls"` // for v2ray-plugin - Obfs string `json:"obfs"` - Host string `json:"host"` - Path string `json:"uri"` -} - -func ParseSip003Opts(opts string) Sip003Opts { - var sip003Opts Sip003Opts - fields := strings.Split(opts, ";") - for i := range fields { - a := strings.Split(fields[i], "=") - if len(a) == 1 { - // to avoid panic - a = append(a, "") - } - switch a[0] { - case "tls": - sip003Opts.Tls = "tls" - case "obfs", "mode": - sip003Opts.Obfs = a[1] - case "obfs-path", "obfs-uri", "path": - if !strings.HasPrefix(a[1], "/") { - a[1] += "/" - } - sip003Opts.Path = a[1] - case "obfs-host", "host": - sip003Opts.Host = a[1] - } - } - return sip003Opts -} -func ParseSip003(plugin string) Sip003 { - var sip003 Sip003 - fields := strings.SplitN(plugin, ";", 2) - switch fields[0] { - case "obfs-local", "simpleobfs": - sip003.Name = "simple-obfs" - default: - sip003.Name = fields[0] - } - sip003.Opts = ParseSip003Opts(fields[1]) - return sip003 -} - -func (s *Sip003) String() string { - list := []string{s.Name} - if s.Opts.Obfs != "" { - list = append(list, "obfs="+s.Opts.Obfs) - } - if s.Opts.Host != "" { - list = append(list, "obfs-host="+s.Opts.Host) - } - if s.Opts.Path != "" { - list = append(list, "obfs-uri="+s.Opts.Path) - } - return strings.Join(list, ";") -} - -func (s *Shadowsocks) ExportToURL() string { - // sip002 - u := &url.URL{ - Scheme: "ss", - User: url.User(strings.TrimSuffix(base64.URLEncoding.EncodeToString([]byte(s.Cipher+":"+s.Password)), "=")), - Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Fragment: s.Name, - } - if s.Plugin.Name != "" { - q := u.Query() - q.Set("plugin", s.Plugin.String()) - u.RawQuery = q.Encode() - } - return u.String() -} diff --git a/component/outbound/dialer/shadowsocksr/shadowsocksr.go b/component/outbound/dialer/shadowsocksr/shadowsocksr.go deleted file mode 100644 index 6c9c842..0000000 --- a/component/outbound/dialer/shadowsocksr/shadowsocksr.go +++ /dev/null @@ -1,167 +0,0 @@ -package shadowsocksr - -import ( - "encoding/base64" - "fmt" - "net" - "net/url" - "strconv" - "strings" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol" - "github.com/daeuniverse/outbound/protocol/shadowsocks_stream" - "github.com/daeuniverse/outbound/transport/shadowsocksr/obfs" - "github.com/daeuniverse/outbound/transport/shadowsocksr/proto" -) - -func init() { - dialer.FromLinkRegister("shadowsocksr", NewShadowsocksR) - dialer.FromLinkRegister("ssr", NewShadowsocksR) -} - -type ShadowsocksR struct { - Name string `json:"name"` - Server string `json:"server"` - Port int `json:"port"` - Password string `json:"password"` - Cipher string `json:"cipher"` - Proto string `json:"proto"` - ProtoParam string `json:"protoParam"` - Obfs string `json:"obfs"` - ObfsParam string `json:"obfsParam"` - Protocol string `json:"protocol"` -} - -func NewShadowsocksR(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - s, err := ParseSSRURL(link) - if err != nil { - return nil, nil, err - } - return s.Dialer(option, nextDialer) -} - -func (s *ShadowsocksR) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { - d := nextDialer - obfsDialer, err := obfs.NewDialer(d, &obfs.ObfsParam{ - ObfsHost: s.Server, - ObfsPort: uint16(s.Port), - Obfs: s.Obfs, - ObfsParam: s.ObfsParam, - }) - if err != nil { - return nil, nil, err - } - d = obfsDialer - d, err = shadowsocks_stream.NewDialer(d, protocol.Header{ - ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Cipher: s.Cipher, - Password: s.Password, - IsClient: true, - }) - if err != nil { - return nil, nil, err - } - d = &proto.Dialer{ - NextDialer: d, - Protocol: s.Proto, - ProtocolParam: s.ProtoParam, - ObfsOverhead: obfsDialer.ObfsOverhead(), - } - - return d, &dialer.Property{ - Name: s.Name, - Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Protocol: s.Protocol, - Link: s.ExportToURL(), - }, nil -} - -func ParseSSRURL(u string) (data *ShadowsocksR, err error) { - // parse attempts to parse ss:// links - parse := func(content string) (v ShadowsocksR, ok bool) { - arr := strings.Split(content, "/?") - if strings.Contains(content, ":") && len(arr) < 2 { - content += "/?remarks=&protoparam=&obfsparam=" - arr = strings.Split(content, "/?") - } else if len(arr) != 2 { - return v, false - } - pre := strings.Split(arr[0], ":") - if len(pre) > 6 { - //if the length is more than 6, it means that the host contains the characters:, - //re-merge the first few groups into the host - pre[len(pre)-6] = strings.Join(pre[:len(pre)-5], ":") - pre = pre[len(pre)-6:] - } else if len(pre) < 6 { - return v, false - } - q, err := url.ParseQuery(arr[1]) - if err != nil { - return v, false - } - pswd, _ := common.Base64UrlDecode(pre[5]) - add, _ := common.Base64UrlDecode(pre[0]) - remarks, _ := common.Base64UrlDecode(q.Get("remarks")) - protoparam, _ := common.Base64UrlDecode(q.Get("protoparam")) - obfsparam, _ := common.Base64UrlDecode(q.Get("obfsparam")) - port, err := strconv.Atoi(pre[1]) - if err != nil { - return v, false - } - v = ShadowsocksR{ - Name: remarks, - Server: add, - Port: port, - Password: pswd, - Cipher: pre[3], - Proto: pre[2], - ProtoParam: protoparam, - Obfs: pre[4], - ObfsParam: obfsparam, - Protocol: "shadowsocksr", - } - return v, true - } - content := u[6:] - var ( - info ShadowsocksR - ok bool - ) - // try parsing the ssr:// link, if it fails, base64 decode first - if info, ok = parse(content); !ok { - // perform base64 decoding and parse again - content, err = common.Base64StdDecode(content) - if err != nil { - content, err = common.Base64UrlDecode(content) - if err != nil { - return - } - } - info, ok = parse(content) - } - if !ok { - err = fmt.Errorf("%w: unrecognized ssr address", dialer.InvalidParameterErr) - return - } - return &info, nil -} - -func (s *ShadowsocksR) ExportToURL() string { - /* ssr://server:port:proto:method:obfs:URLBASE64(password)/?remarks=URLBASE64(remarks)&protoparam=URLBASE64(protoparam)&obfsparam=URLBASE64(obfsparam)) */ - return fmt.Sprintf("ssr://%v", strings.TrimSuffix(base64.URLEncoding.EncodeToString([]byte( - fmt.Sprintf( - "%v:%v:%v:%v:%v/?remarks=%v&protoparam=%v&obfsparam=%v", - net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - s.Proto, - s.Cipher, - s.Obfs, - base64.URLEncoding.EncodeToString([]byte(s.Password)), - base64.URLEncoding.EncodeToString([]byte(s.Name)), - base64.URLEncoding.EncodeToString([]byte(s.ProtoParam)), - base64.URLEncoding.EncodeToString([]byte(s.ObfsParam)), - ), - )), "=")) -} diff --git a/component/outbound/dialer/sockopt.go b/component/outbound/dialer/sockopt.go index ac9b610..1ebf22a 100644 --- a/component/outbound/dialer/sockopt.go +++ b/component/outbound/dialer/sockopt.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package dialer diff --git a/component/outbound/dialer/socks/socks.go b/component/outbound/dialer/socks/socks.go deleted file mode 100644 index 7fe7ab8..0000000 --- a/component/outbound/dialer/socks/socks.go +++ /dev/null @@ -1,104 +0,0 @@ -package socks - -import ( - "fmt" - - "net" - "net/url" - "strconv" - - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol/socks5" -) - -func init() { - dialer.FromLinkRegister("socks", NewSocks) // socks -> socks5 - //dialer.FromLinkRegister("socks4", NewSocks) - //dialer.FromLinkRegister("socks4a", NewSocks) - dialer.FromLinkRegister("socks5", NewSocks) -} - -type Socks struct { - Name string `json:"name"` - Server string `json:"server"` - Port int `json:"port"` - Username string `json:"username"` - Password string `json:"password"` - Protocol string `json:"protocol"` -} - -func NewSocks(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - s, err := ParseSocksURL(link) - if err != nil { - return nil, nil, dialer.InvalidParameterErr - } - return s.Dialer(option, nextDialer) -} - -func (s *Socks) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { - link := s.ExportToURL() - d := nextDialer - switch s.Protocol { - case "", "socks", "socks5": - d, err := socks5.NewSocks5Dialer(link, d) // Socks5 Proxy supports full-cone. - if err != nil { - return nil, nil, err - } - return d, &dialer.Property{ - Name: s.Name, - Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Protocol: s.Protocol, - Link: link, - }, nil - //case "socks4", "socks4a": - // d, err := socks4.NewSocks4Dialer(link, &proxy.Direct{}) - // if err != nil { - // return nil, err - // } - // return dialer.NewDialer(d, false, s.Name, s.Protocol, link), nil - default: - return nil, nil, fmt.Errorf("unexpected protocol: %v", s.Protocol) - } -} - -func ParseSocksURL(link string) (data *Socks, err error) { - u, err := url.Parse(link) - if err != nil { - return nil, dialer.InvalidParameterErr - } - pwd, _ := u.User.Password() - strPort := u.Port() - port, err := strconv.Atoi(strPort) - if err != nil { - return nil, err - } - // socks -> socks5 - if u.Scheme == "socks" { - u.Scheme = "socks5" - } - return &Socks{ - Name: u.Fragment, - Server: u.Hostname(), - Port: port, - Username: u.User.Username(), - Password: pwd, - Protocol: u.Scheme, - }, nil -} - -func (s *Socks) ExportToURL() string { - var user *url.Userinfo - if s.Password != "" { - user = url.UserPassword(s.Username, s.Password) - } else { - user = url.User(s.Username) - } - u := url.URL{ - Scheme: s.Protocol, - User: user, - Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Fragment: s.Name, - } - return u.String() -} diff --git a/component/outbound/dialer/socks/socks_test.go b/component/outbound/dialer/socks/socks_test.go deleted file mode 100644 index 35701a9..0000000 --- a/component/outbound/dialer/socks/socks_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2023, daeuniverse Organization - */ - -package socks - -import ( - "context" - "net/netip" - "testing" - "time" - - "github.com/daeuniverse/dae/common/netutils" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/protocol/direct" - dnsmessage "github.com/miekg/dns" - "github.com/sirupsen/logrus" -) - -func TestSocks5(t *testing.T) { - c, err := ParseSocksURL("socks5://192.168.31.6:1081") - if err != nil { - t.Fatal(err) - } - log := logrus.StandardLogger() - d, _, err := c.Dialer(&dialer.GlobalOption{ - Log: log, - TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{ - Log: log, - Raw: []string{"http://gstatic.com/generate_204"}, - ResolverNetwork: "udp", - Method: "HEAD", - }, - CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{ - Raw: []string{"dns.google.com:53"}, - ResolverNetwork: "udp", - }, - CheckInterval: 10 * time.Second, - CheckTolerance: 0, - CheckDnsTcp: true, - AllowInsecure: false, - TlsImplementation: "", - UtlsImitate: "", - }, direct.SymmetricDirect) - if err != nil { - t.Fatal(err) - } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - addrs, err := netutils.ResolveNetip(ctx, d, netip.MustParseAddrPort("8.8.8.8:53"), "apple.com", dnsmessage.TypeA, "udp") - if err != nil { - t.Fatal(err) - } - t.Log(addrs) -} diff --git a/component/outbound/dialer/trojan/trojan.go b/component/outbound/dialer/trojan/trojan.go deleted file mode 100644 index ee8988d..0000000 --- a/component/outbound/dialer/trojan/trojan.go +++ /dev/null @@ -1,194 +0,0 @@ -package trojan - -import ( - "fmt" - "net" - "net/url" - "strconv" - "strings" - - "github.com/daeuniverse/dae/component/outbound/transport/tls" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/dae/component/outbound/transport/ws" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol" - "github.com/daeuniverse/outbound/transport/grpc" -) - -func init() { - dialer.FromLinkRegister("trojan", NewTrojan) - dialer.FromLinkRegister("trojan-go", NewTrojan) -} - -type Trojan struct { - Name string `json:"name"` - Server string `json:"server"` - Port int `json:"port"` - Password string `json:"password"` - Sni string `json:"sni"` - Type string `json:"type"` - Encryption string `json:"encryption"` - Host string `json:"host"` - Path string `json:"path"` - ServiceName string `json:"serviceName"` - AllowInsecure bool `json:"allowInsecure"` - Protocol string `json:"protocol"` -} - -func NewTrojan(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - s, err := ParseTrojanURL(link) - if err != nil { - return nil, nil, err - } - return s.Dialer(option, nextDialer) -} - -func (s *Trojan) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { - d := nextDialer - var err error - if s.Type != "grpc" { - // grpc contains tls - u := url.URL{ - Scheme: option.TlsImplementation, - Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - RawQuery: url.Values{ - "sni": []string{s.Sni}, - "allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)}, - "utlsImitate": []string{option.UtlsImitate}, - }.Encode(), - } - if d, _, err = tls.NewTls(option, d, u.String()); err != nil { - return nil, nil, err - } - } - // "tls,ws,ss,trojanc" - switch s.Type { - case "ws": - u := url.URL{ - Scheme: "ws", - Host: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - RawQuery: url.Values{ - "host": []string{s.Host}, - "path": []string{s.Path}, - }.Encode(), - } - if d, _, err = ws.NewWs(option, d, u.String()); err != nil { - return nil, nil, err - } - case "grpc": - serviceName := s.ServiceName - if serviceName == "" { - serviceName = "GunService" - } - d = &grpc.Dialer{ - NextDialer: &netproxy.ContextDialerConverter{Dialer: d}, - ServiceName: serviceName, - ServerName: s.Sni, - AllowInsecure: s.AllowInsecure || option.AllowInsecure, - } - } - if strings.HasPrefix(s.Encryption, "ss;") { - fields := strings.SplitN(s.Encryption, ";", 3) - if d, err = protocol.NewDialer("shadowsocks", d, protocol.Header{ - ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Cipher: fields[1], - Password: fields[2], - IsClient: false, - }); err != nil { - return nil, nil, err - } - } - if d, err = protocol.NewDialer("trojanc", d, protocol.Header{ - ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Password: s.Password, - IsClient: true, - }); err != nil { - return nil, nil, err - } - return d, &dialer.Property{ - Name: s.Name, - Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Protocol: s.Protocol, - Link: s.ExportToURL(), - }, nil -} - -func ParseTrojanURL(u string) (data *Trojan, err error) { - //trojan://password@server:port#escape(remarks) - t, err := url.Parse(u) - if err != nil { - err = fmt.Errorf("invalid trojan format") - return - } - allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure")) - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("allow_insecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("allowinsecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("skipVerify")) - } - sni := t.Query().Get("peer") - if sni == "" { - sni = t.Query().Get("sni") - } - if sni == "" { - sni = t.Hostname() - } - port, err := strconv.Atoi(t.Port()) - if err != nil { - return nil, dialer.InvalidParameterErr - } - data = &Trojan{ - Name: t.Fragment, - Server: t.Hostname(), - Port: port, - Password: t.User.Username(), - Sni: sni, - AllowInsecure: allowInsecure, - Protocol: "trojan", - } - if t.Query().Get("type") != "" { - t.Scheme = "trojan-go" - } - if t.Scheme == "trojan-go" { - data.Protocol = "trojan-go" - data.Encryption = t.Query().Get("encryption") - data.Host = t.Query().Get("host") - data.Path = t.Query().Get("path") - data.Type = t.Query().Get("type") - data.ServiceName = t.Query().Get("serviceName") - if data.Type == "grpc" && data.ServiceName == "" { - data.ServiceName = data.Path - } - } - return data, nil -} - -func (t *Trojan) ExportToURL() string { - u := &url.URL{ - Scheme: "trojan", - User: url.User(t.Password), - Host: net.JoinHostPort(t.Server, strconv.Itoa(t.Port)), - Fragment: t.Name, - } - q := u.Query() - if t.AllowInsecure { - q.Set("allowInsecure", "1") - } - common.SetValue(&q, "sni", t.Sni) - - if t.Protocol == "trojan-go" { - u.Scheme = "trojan-go" - common.SetValue(&q, "host", t.Host) - common.SetValue(&q, "encryption", t.Encryption) - common.SetValue(&q, "type", t.Type) - common.SetValue(&q, "path", t.Path) - } - u.RawQuery = q.Encode() - return u.String() -} diff --git a/component/outbound/dialer/tuic/tuic.go b/component/outbound/dialer/tuic/tuic.go deleted file mode 100644 index 3919b10..0000000 --- a/component/outbound/dialer/tuic/tuic.go +++ /dev/null @@ -1,160 +0,0 @@ -package tuic - -import ( - "crypto/tls" - "fmt" - "net" - "net/url" - "strconv" - "strings" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol" -) - -func init() { - dialer.FromLinkRegister("tuic", NewTuic) -} - -type Tuic struct { - Name string - Server string - Port int - User string - Password string - Sni string - AllowInsecure bool - DisableSni bool - CongestionControl string - Alpn []string - Protocol string - UdpRelayMode string -} - -func NewTuic(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - s, err := ParseTuicURL(link) - if err != nil { - return nil, nil, err - } - return s.Dialer(option, nextDialer) -} - -func (s *Tuic) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { - d := nextDialer - var err error - var flags protocol.Flags - if s.UdpRelayMode == "quic" { - flags |= protocol.Flags_Tuic_UdpRelayModeQuic - } - if d, err = protocol.NewDialer("tuic", d, protocol.Header{ - ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Feature1: s.CongestionControl, - TlsConfig: &tls.Config{ - NextProtos: s.Alpn, - MinVersion: tls.VersionTLS13, - ServerName: s.Sni, - InsecureSkipVerify: s.AllowInsecure || option.AllowInsecure, - }, - User: s.User, - Password: s.Password, - IsClient: true, - Flags: flags, - }); err != nil { - return nil, nil, err - } - return d, &dialer.Property{ - Name: s.Name, - Address: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)), - Protocol: s.Protocol, - Link: s.ExportToURL(), - }, nil -} - -func ParseTuicURL(u string) (data *Tuic, err error) { - //trojan://password@server:port#escape(remarks) - t, err := url.Parse(u) - if err != nil { - err = fmt.Errorf("invalid trojan format") - return - } - var alpn []string - if t.Query().Has("alpn") { - alpn = strings.Split(t.Query().Get("alpn"), ",") - for i := range alpn { - alpn[i] = strings.TrimSpace(alpn[i]) - } - } - allowInsecure, _ := strconv.ParseBool(t.Query().Get("allowInsecure")) - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("allow_insecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("allowinsecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(t.Query().Get("skipVerify")) - } - sni := t.Query().Get("peer") - if sni == "" { - sni = t.Query().Get("sni") - } - if sni == "" { - sni = t.Hostname() - } - disableSni, _ := strconv.ParseBool(t.Query().Get("disable_sni")) - if disableSni { - sni = "" - allowInsecure = true - } - port, err := strconv.Atoi(t.Port()) - if err != nil { - return nil, dialer.InvalidParameterErr - } - password, _ := t.User.Password() - data = &Tuic{ - Name: t.Fragment, - Server: t.Hostname(), - Port: port, - User: t.User.Username(), - Password: password, - Sni: sni, - AllowInsecure: allowInsecure, - DisableSni: disableSni, - CongestionControl: t.Query().Get("congestion_control"), - Alpn: alpn, - UdpRelayMode: strings.ToLower(t.Query().Get("udp_relay_mode")), - Protocol: "tuic", - } - return data, nil -} - -func (t *Tuic) ExportToURL() string { - u := &url.URL{ - Scheme: "tuic", - User: url.UserPassword(t.User, t.Password), - Host: net.JoinHostPort(t.Server, strconv.Itoa(t.Port)), - Fragment: t.Name, - } - q := u.Query() - if t.AllowInsecure { - q.Set("allow_insecure", "1") - } - common.SetValue(&q, "sni", t.Sni) - if t.DisableSni { - common.SetValue(&q, "disable_sni", "1") - } - if t.CongestionControl != "" { - common.SetValue(&q, "congestion_control", t.CongestionControl) - } - if len(t.Alpn) > 0 { - common.SetValue(&q, "alpn", strings.Join(t.Alpn, ",")) - } - if t.UdpRelayMode != "" { - common.SetValue(&q, "udp_relay_mode", t.UdpRelayMode) - } - - u.RawQuery = q.Encode() - return u.String() -} diff --git a/component/outbound/dialer/utils.go b/component/outbound/dialer/utils.go index 0a6f412..4b436d7 100644 --- a/component/outbound/dialer/utils.go +++ b/component/outbound/dialer/utils.go @@ -1,3 +1,8 @@ +/* +* SPDX-License-Identifier: AGPL-3.0-only +* Copyright (c) 2022-2024, daeuniverse Organization + */ + package dialer import "time" diff --git a/component/outbound/dialer/v2ray/cipher.go b/component/outbound/dialer/v2ray/cipher.go deleted file mode 100644 index 17bf985..0000000 --- a/component/outbound/dialer/v2ray/cipher.go +++ /dev/null @@ -1,26 +0,0 @@ -package v2ray - -import ( - "runtime" - - "golang.org/x/sys/cpu" -) - -var ( - hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ - hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL - // Keep in sync with crypto/aes/cipher_s390x.go. - hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && - (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) - - hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 || - runtime.GOARCH == "arm64" && hasGCMAsmARM64 || - runtime.GOARCH == "s390x" && hasGCMAsmS390X -) - -func getAutoCipher() string { - if hasAESGCMHardwareSupport { - return "aes-128-gcm" - } - return "chacha20-ietf-poly1305" -} diff --git a/component/outbound/dialer/v2ray/v2ray.go b/component/outbound/dialer/v2ray/v2ray.go deleted file mode 100644 index e303071..0000000 --- a/component/outbound/dialer/v2ray/v2ray.go +++ /dev/null @@ -1,384 +0,0 @@ -package v2ray - -import ( - "encoding/base64" - "fmt" - "net" - "net/url" - "regexp" - "strings" - - "github.com/daeuniverse/dae/common" - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/dae/component/outbound/transport/tls" - "github.com/daeuniverse/dae/component/outbound/transport/ws" - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/protocol" - "github.com/daeuniverse/outbound/protocol/direct" - "github.com/daeuniverse/outbound/protocol/http" - "github.com/daeuniverse/outbound/transport/grpc" - "github.com/daeuniverse/outbound/transport/meek" - jsoniter "github.com/json-iterator/go" -) - -func init() { - dialer.FromLinkRegister("vmess", NewV2Ray) - dialer.FromLinkRegister("vless", NewV2Ray) -} - -type V2Ray struct { - Ps string `json:"ps"` - Add string `json:"add"` - Port string `json:"port"` - ID string `json:"id"` - Aid string `json:"aid"` - Net string `json:"net"` - Type string `json:"type"` - Host string `json:"host"` - SNI string `json:"sni"` - Path string `json:"path"` - TLS string `json:"tls"` - Flow string `json:"flow,omitempty"` - Alpn string `json:"alpn,omitempty"` - AllowInsecure bool `json:"allowInsecure"` - V string `json:"v"` - Protocol string `json:"protocol"` -} - -func NewV2Ray(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - var ( - s *V2Ray - err error - ) - switch { - case strings.HasPrefix(link, "vmess://"): - s, err = ParseVmessURL(link) - if err != nil { - return nil, nil, err - } - if s.Aid != "0" && s.Aid != "" { - return nil, nil, fmt.Errorf("%w: aid: %v, we only support AEAD encryption", dialer.UnexpectedFieldErr, s.Aid) - } - case strings.HasPrefix(link, "vless://"): - s, err = ParseVlessURL(link) - if err != nil { - return nil, nil, err - } - default: - return nil, nil, dialer.InvalidParameterErr - } - return s.Dialer(option, nextDialer) -} - -func (s *V2Ray) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (npd netproxy.Dialer, property *dialer.Property, err error) { - d := nextDialer - switch s.Protocol { - case "vmess", "vless": - default: - return nil, nil, fmt.Errorf("V2Ray.Dialer: unexpected protocol: %v", s.Protocol) - } - - switch strings.ToLower(s.Net) { - case "ws": - scheme := "ws" - if s.TLS == "tls" || s.TLS == "xtls" { - scheme = "wss" - } - sni := s.SNI - if sni == "" { - sni = s.Host - } - u := url.URL{ - Scheme: scheme, - Host: net.JoinHostPort(s.Add, s.Port), - Path: s.Path, - RawQuery: url.Values{ - "host": []string{s.Host}, - "sni": []string{sni}, - "allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)}, - }.Encode(), - } - d, _, err = ws.NewWs(option, d, u.String()) - if err != nil { - return nil, nil, err - } - case "tcp": - if s.TLS == "tls" || s.TLS == "xtls" { - sni := s.SNI - if sni == "" { - sni = s.Host - } - u := url.URL{ - Scheme: option.TlsImplementation, - Host: net.JoinHostPort(s.Add, s.Port), - RawQuery: url.Values{ - "sni": []string{sni}, - "allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)}, - "utlsImitate": []string{option.UtlsImitate}, - }.Encode(), - } - d, _, err = tls.NewTls(option, d, u.String()) - if err != nil { - return nil, nil, err - } - } - if s.Type != "none" && s.Type != "" { - return nil, nil, fmt.Errorf("%w: type: %v", dialer.UnexpectedFieldErr, s.Type) - } - case "grpc": - sni := s.SNI - if sni == "" { - sni = s.Host - } - serviceName := s.Path - if serviceName == "" { - serviceName = "GunService" - } - d = &grpc.Dialer{ - NextDialer: &netproxy.ContextDialerConverter{Dialer: d}, - ServiceName: serviceName, - ServerName: sni, - AllowInsecure: s.AllowInsecure || option.AllowInsecure, - } - case "http", "http2", "h2": - sni := s.SNI - if sni == "" { - sni = s.Add - } - scheme := "http" - if s.TLS == "tls" { - scheme = "https" - } - u := url.URL{ - Scheme: scheme, - Host: net.JoinHostPort(s.Add, s.Port), - Path: s.Path, - RawQuery: url.Values{ - "sni": []string{sni}, - "allowInsecure": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)}, - "tlsImplementation": []string{option.TlsImplementation}, - "utlsImitate": []string{option.UtlsImitate}, - "host": []string{s.Host}, - "alpn": []string{s.Alpn}, - "transport": []string{"1"}, - }.Encode(), - } - d, err = http.NewHTTPProxy(&u, direct.SymmetricDirect) - if err != nil { - return nil, nil, err - } - case "meek": - if strings.HasPrefix(s.Path, "https://") && s.TLS != "tls" && s.TLS != "utls" { - return nil, nil, fmt.Errorf("%w: meek: tls should be enabled", dialer.InvalidParameterErr) - } - - u := url.URL{ - Scheme: "meek", - Host: net.JoinHostPort(s.Add, s.Port), - RawQuery: url.Values{ - "url": []string{s.Path}, - "alpn": []string{s.Alpn}, - "serverName": []string{s.SNI}, - "skipCertVerify": []string{common.BoolToString(s.AllowInsecure || option.AllowInsecure)}, - }.Encode(), - } - - d, err = meek.NewDialer(u.String(), d) - if err != nil { - return nil, nil, err - } - default: - return nil, nil, fmt.Errorf("%w: network: %v", dialer.UnexpectedFieldErr, s.Net) - } - - if d, err = protocol.NewDialer(s.Protocol, d, protocol.Header{ - ProxyAddress: net.JoinHostPort(s.Add, s.Port), - Cipher: getAutoCipher(), - Password: s.ID, - IsClient: true, - //Flags: protocol.Flags_VMess_UsePacketAddr, - }); err != nil { - return nil, nil, err - } - return d, &dialer.Property{ - Name: s.Ps, - Address: net.JoinHostPort(s.Add, s.Port), - Protocol: s.Protocol, - Link: s.ExportToURL(), - }, nil -} - -func ParseVlessURL(vless string) (data *V2Ray, err error) { - u, err := url.Parse(vless) - if err != nil { - return nil, err - } - data = &V2Ray{ - Ps: u.Fragment, - Add: u.Hostname(), - Port: u.Port(), - ID: u.User.String(), - Net: u.Query().Get("type"), - Type: u.Query().Get("headerType"), - SNI: u.Query().Get("sni"), - Host: u.Query().Get("host"), - Path: u.Query().Get("path"), - TLS: u.Query().Get("security"), - Flow: u.Query().Get("flow"), - Alpn: u.Query().Get("alpn"), - Protocol: "vless", - } - if data.Net == "" { - data.Net = "tcp" - } - if data.Net == "grpc" { - data.Path = u.Query().Get("serviceName") - } - - if data.Net == "meek" { - data.Path = u.Query().Get("url") - } - - if data.Type == "" { - data.Type = "none" - } - if data.TLS == "" { - data.TLS = "none" - } - if data.Flow == "" { - data.Flow = "xtls-rprx-direct" - } - if data.Type == "mkcp" || data.Type == "kcp" { - data.Path = u.Query().Get("seed") - } - return data, nil -} - -func ParseVmessURL(vmess string) (data *V2Ray, err error) { - var info V2Ray - // perform base64 decoding and unmarshal to VmessInfo - raw, err := common.Base64StdDecode(vmess[8:]) - if err != nil { - raw, err = common.Base64UrlDecode(vmess[8:]) - } - if err != nil { - // not in json format, try to resolve as vmess://BASE64(Security:ID@Add:Port)?remarks=Ps&obfsParam=Host&Path=Path&obfs=Net&tls=TLS - var u *url.URL - u, err = url.Parse(vmess) - if err != nil { - return - } - re := regexp.MustCompile(`.*:(.+)@(.+):(\d+)`) - s := strings.Split(vmess[8:], "?")[0] - s, err = common.Base64StdDecode(s) - if err != nil { - s, _ = common.Base64UrlDecode(s) - } - subMatch := re.FindStringSubmatch(s) - if subMatch == nil { - err = fmt.Errorf("unrecognized vmess address") - return - } - q := u.Query() - ps := q.Get("remarks") - if ps == "" { - ps = q.Get("remark") - } - obfs := q.Get("obfs") - obfsParam := q.Get("obfsParam") - path := q.Get("path") - if obfs == "kcp" || obfs == "mkcp" { - m := make(map[string]string) - //cater to v2rayN definition - _ = jsoniter.Unmarshal([]byte(obfsParam), &m) - path = m["seed"] - obfsParam = "" - } - aid := q.Get("alterId") - if aid == "" { - aid = q.Get("aid") - } - sni := q.Get("peer") - info = V2Ray{ - ID: subMatch[1], - Add: subMatch[2], - Port: subMatch[3], - Ps: ps, - Host: jsoniter.Get([]byte(obfsParam), "host").ToString(), - Path: path, - Net: obfs, - Aid: aid, - TLS: map[string]string{"1": "tls"}[q.Get("tls")], - SNI: sni, - AllowInsecure: false, - } - if info.Net == "websocket" { - info.Net = "ws" - } - } else { - err = jsoniter.Unmarshal([]byte(raw), &info) - if err != nil { - return - } - } - // correct the wrong vmess as much as possible - if strings.HasPrefix(info.Host, "/") && info.Path == "" { - info.Path = info.Host - info.Host = "" - } - if info.Aid == "" { - info.Aid = "0" - } - info.Protocol = "vmess" - return &info, nil -} - -func (s *V2Ray) ExportToURL() string { - switch s.Protocol { - case "vless": - // https://github.com/XTLS/Xray-core/issues/91 - var query = make(url.Values) - common.SetValue(&query, "type", s.Net) - common.SetValue(&query, "security", s.TLS) - switch s.Net { - case "websocket", "ws", "http", "h2": - common.SetValue(&query, "path", s.Path) - common.SetValue(&query, "host", s.Host) - case "mkcp", "kcp": - common.SetValue(&query, "headerType", s.Type) - common.SetValue(&query, "seed", s.Path) - case "tcp": - common.SetValue(&query, "headerType", s.Type) - common.SetValue(&query, "host", s.Host) - common.SetValue(&query, "path", s.Path) - case "grpc": - common.SetValue(&query, "serviceName", s.Path) - case "meek": - common.SetValue(&query, "url", s.Host) - } - - //TODO: QUIC - if s.TLS != "none" { - common.SetValue(&query, "sni", s.Host) // FIXME: it may be different from ws's host - common.SetValue(&query, "alpn", s.Alpn) - } - if s.TLS == "xtls" { - common.SetValue(&query, "flow", s.Flow) - } - - U := url.URL{ - Scheme: "vless", - User: url.User(s.ID), - Host: net.JoinHostPort(s.Add, s.Port), - RawQuery: query.Encode(), - Fragment: s.Ps, - } - return U.String() - case "vmess": - s.V = "2" - b, _ := jsoniter.Marshal(s) - return "vmess://" + strings.TrimSuffix(base64.StdEncoding.EncodeToString(b), "=") - } - //log.Warn("unexpected protocol: %v", v.Protocol) - return "" -} diff --git a/component/outbound/dialer_group.go b/component/outbound/dialer_group.go index 0c162cf..c0a6bce 100644 --- a/component/outbound/dialer_group.go +++ b/component/outbound/dialer_group.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package outbound diff --git a/component/outbound/dialer_group_test.go b/component/outbound/dialer_group_test.go index 52222ce..f1fa08c 100644 --- a/component/outbound/dialer_group_test.go +++ b/component/outbound/dialer_group_test.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package outbound @@ -28,6 +28,12 @@ var TestNetworkType = &dialer.NetworkType{ var log = logger.NewLogger("trace", false, nil) +func newDirectDialer(option *dialer.GlobalOption, fullcone bool) *dialer.Dialer { + _d, p := dialer.NewDirectDialer(option, true) + d := dialer.NewDialer(_d, option, dialer.InstanceOption{DisableCheck: false}, p) + return d +} + func TestDialerGroup_Select_Fixed(t *testing.T) { option := &dialer.GlobalOption{ Log: log, @@ -38,14 +44,15 @@ func TestDialerGroup_Select_Fixed(t *testing.T) { CheckDnsTcp: false, } dialers := []*dialer.Dialer{ - dialer.NewDirectDialer(option, true), - dialer.NewDirectDialer(option, false), + newDirectDialer(option, true), + newDirectDialer(option, false), } fixedIndex := 1 - g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{ - Policy: consts.DialerSelectionPolicy_Fixed, - FixedIndex: fixedIndex, - }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) + g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}}, + DialerSelectionPolicy{ + Policy: consts.DialerSelectionPolicy_Fixed, + FixedIndex: fixedIndex, + }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) for i := 0; i < 10; i++ { d, _, err := g.Select(TestNetworkType, false) if err != nil { @@ -78,20 +85,21 @@ func TestDialerGroup_Select_MinLastLatency(t *testing.T) { CheckInterval: 15 * time.Second, } dialers := []*dialer.Dialer{ - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), } - g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{ - Policy: consts.DialerSelectionPolicy_MinLastLatency, - }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) + g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}}, + DialerSelectionPolicy{ + Policy: consts.DialerSelectionPolicy_MinLastLatency, + }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) // Test 1000 times. for i := 0; i < 1000; i++ { @@ -147,15 +155,16 @@ func TestDialerGroup_Select_Random(t *testing.T) { CheckInterval: 15 * time.Second, } dialers := []*dialer.Dialer{ - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), } - g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{ - Policy: consts.DialerSelectionPolicy_Random, - }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) + g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}}, + DialerSelectionPolicy{ + Policy: consts.DialerSelectionPolicy_Random, + }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) count := make([]int, len(dialers)) for i := 0; i < 100; i++ { d, _, err := g.Select(TestNetworkType, false) @@ -186,15 +195,16 @@ func TestDialerGroup_SetAlive(t *testing.T) { CheckInterval: 15 * time.Second, } dialers := []*dialer.Dialer{ - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), - dialer.NewDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), + newDirectDialer(option, false), } - g := NewDialerGroup(option, "test-group", dialers, DialerSelectionPolicy{ - Policy: consts.DialerSelectionPolicy_Random, - }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) + g := NewDialerGroup(option, "test-group", dialers, []*dialer.Annotation{{}}, + DialerSelectionPolicy{ + Policy: consts.DialerSelectionPolicy_Random, + }, func(alive bool, networkType *dialer.NetworkType, isInit bool) {}) zeroTarget := 3 g.MustGetAliveDialerSet(TestNetworkType).NotifyLatencyChange(dialers[zeroTarget], false) count := make([]int, len(dialers)) diff --git a/component/outbound/dialer_selection_policy.go b/component/outbound/dialer_selection_policy.go index 78e2095..30b310c 100644 --- a/component/outbound/dialer_selection_policy.go +++ b/component/outbound/dialer_selection_policy.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package outbound diff --git a/component/outbound/filter.go b/component/outbound/filter.go index 552a1be..f535d93 100644 --- a/component/outbound/filter.go +++ b/component/outbound/filter.go @@ -1,17 +1,18 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package outbound import ( "fmt" - "regexp" "strings" "github.com/daeuniverse/dae/component/outbound/dialer" "github.com/daeuniverse/dae/pkg/config_parser" + "github.com/dlclark/regexp2" + "github.com/sirupsen/logrus" ) const ( @@ -28,20 +29,22 @@ const ( ) type DialerSet struct { + log *logrus.Logger dialers []*dialer.Dialer nodeToTagMap map[*dialer.Dialer]string } func NewDialerSetFromLinks(option *dialer.GlobalOption, tagToNodeList map[string][]string) *DialerSet { s := &DialerSet{ + log: option.Log, dialers: make([]*dialer.Dialer, 0), nodeToTagMap: make(map[*dialer.Dialer]string), } for subscriptionTag, nodes := range tagToNodeList { for _, node := range nodes { - d, err := dialer.NewFromLink(option, dialer.InstanceOption{CheckEnabled: false}, node, subscriptionTag) + d, err := dialer.NewFromLink(option, dialer.InstanceOption{DisableCheck: false}, node, subscriptionTag) if err != nil { - option.Log.Infof("failed to parse node: %v", err) + s.log.Infof("failed to parse node: %v", err) continue } s.dialers = append(s.dialers, d) @@ -69,24 +72,29 @@ func (s *DialerSet) filterHit(dialer *dialer.Dialer, filters []*config_parser.Fu switch filter.Name { case FilterInput_Name: // Or + loop: for _, param := range filter.Params { switch param.Key { case FilterKey_Name_Regex: - matched, _ := regexp.MatchString(param.Val, dialer.Property().Name) + regex, err := regexp2.Compile(param.Val, 0) + if err != nil { + return false, fmt.Errorf("bad regexp in filter %v: %w", filter.String(false, true, true), err) + } + matched, _ := regex.MatchString(dialer.Property().Name) //logrus.Warnln(param.Val, matched, dialer.Name()) if matched { subFilterHit = true - break + break loop } case FilterKey_Name_Keyword: if strings.Contains(dialer.Property().Name, param.Val) { subFilterHit = true - break + break loop } case "": if dialer.Property().Name == param.Val { subFilterHit = true - break + break loop } default: return false, fmt.Errorf(`unsupported filter key "%v" in "filter: %v()"`, param.Key, filter.Name) @@ -94,20 +102,25 @@ func (s *DialerSet) filterHit(dialer *dialer.Dialer, filters []*config_parser.Fu } case FilterInput_SubscriptionTag: // Or + loop2: for _, param := range filter.Params { switch param.Key { case FilterInput_SubscriptionTag_Regex: - matched, _ := regexp.MatchString(param.Val, s.nodeToTagMap[dialer]) - //logrus.Warnln(param.Val, matched, dialer.Name()) + regex, err := regexp2.Compile(param.Val, 0) + if err != nil { + return false, fmt.Errorf("bad regexp in filter %v: %w", filter.String(false, true, true), err) + } + matched, _ := regex.MatchString(s.nodeToTagMap[dialer]) if matched { subFilterHit = true - break + break loop2 } + //logrus.Warnln(param.Val, matched, dialer.Name()) case "": // Full if s.nodeToTagMap[dialer] == param.Val { subFilterHit = true - break + break loop2 } default: return false, fmt.Errorf(`unsupported filter key "%v" in "filter: %v()"`, param.Key, filter.Name) diff --git a/component/outbound/outbound.go b/component/outbound/outbound.go index 357f6e7..32244cf 100644 --- a/component/outbound/outbound.go +++ b/component/outbound/outbound.go @@ -1,6 +1,6 @@ /* * SPDX-License-Identifier: AGPL-3.0-only - * Copyright (c) 2022-2023, daeuniverse Organization + * Copyright (c) 2022-2024, daeuniverse Organization */ package outbound diff --git a/component/outbound/transport/simpleobfs/dialer.go b/component/outbound/transport/simpleobfs/dialer.go deleted file mode 100644 index fc64406..0000000 --- a/component/outbound/transport/simpleobfs/dialer.go +++ /dev/null @@ -1,7 +0,0 @@ -package simpleobfs - -import "github.com/daeuniverse/dae/component/outbound/dialer" - -func init() { - dialer.FromLinkRegister("simpleobfs", NewSimpleObfs) -} diff --git a/component/outbound/transport/simpleobfs/http.go b/component/outbound/transport/simpleobfs/http.go deleted file mode 100644 index e89edc1..0000000 --- a/component/outbound/transport/simpleobfs/http.go +++ /dev/null @@ -1,108 +0,0 @@ -// from https://github.com/Dreamacro/clash/blob/master/component/simple-obfs/http.go - -package simpleobfs - -import ( - "bytes" - "encoding/base64" - "fmt" - "io" - "net/http" - "strings" - "sync" - - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/pkg/fastrand" - "github.com/daeuniverse/outbound/pool" -) - -// HTTPObfs is shadowsocks http simple-obfs implementation -type HTTPObfs struct { - netproxy.Conn - host string - port string - path string - buf []byte - offset int - firstRequest bool - firstResponse bool - wMu sync.Mutex - rMu sync.Mutex -} - -func (ho *HTTPObfs) Read(b []byte) (int, error) { - ho.rMu.Lock() - defer ho.rMu.Unlock() - if ho.buf != nil { - n := copy(b, ho.buf[ho.offset:]) - ho.offset += n - if ho.offset == len(ho.buf) { - pool.Put(ho.buf) - ho.buf = nil - } - return n, nil - } - - if ho.firstResponse { - buf := pool.Get(1 << 15) - n, err := ho.Conn.Read(buf) - if err != nil { - pool.Put(buf) - return 0, err - } - idx := bytes.Index(buf[:n], []byte("\r\n\r\n")) - if idx == -1 { - pool.Put(buf) - return 0, io.EOF - } - ho.firstResponse = false - length := n - (idx + 4) - n = copy(b, buf[idx+4:n]) - if length > n { - ho.buf = buf[:idx+4+length] - ho.offset = idx + 4 + n - } else { - pool.Put(buf) - } - return n, nil - } - return ho.Conn.Read(b) -} - -func (ho *HTTPObfs) Write(b []byte) (int, error) { - ho.wMu.Lock() - defer ho.wMu.Unlock() - if ho.firstRequest { - req, _ := http.NewRequest("GET", fmt.Sprintf("http://%s%s", ho.host, ho.path), bytes.NewBuffer(b[:])) - req.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", fastrand.Rand().Int()%87, fastrand.Rand().Int()%2)) - req.Header.Set("Upgrade", "websocket") - req.Header.Set("Connection", "Upgrade") - if ho.port != "80" { - req.Host = fmt.Sprintf("%s:%s", ho.host, ho.port) - } - randBytes := make([]byte, 16) - fastrand.Read(randBytes) - req.Header.Set("Sec-WebSocket-Key", base64.URLEncoding.EncodeToString(randBytes)) - req.ContentLength = int64(len(b)) - err := req.Write(ho.Conn) - ho.firstRequest = false - return len(b), err - } - - return ho.Conn.Write(b) -} - -// NewHTTPObfs return a HTTPObfs -func NewHTTPObfs(conn netproxy.Conn, host string, port string, path string) netproxy.Conn { - if !strings.HasPrefix(path, "/") { - path = "/" + path - } - return &HTTPObfs{ - Conn: conn, - firstRequest: true, - firstResponse: true, - host: host, - port: port, - path: path, - } -} diff --git a/component/outbound/transport/simpleobfs/simpleobfs.go b/component/outbound/transport/simpleobfs/simpleobfs.go deleted file mode 100644 index 5fb364c..0000000 --- a/component/outbound/transport/simpleobfs/simpleobfs.go +++ /dev/null @@ -1,97 +0,0 @@ -package simpleobfs - -import ( - "fmt" - "net" - "net/url" - "strings" - - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" -) - -type ObfsType int - -const ( - HTTP ObfsType = iota - TLS -) - -// SimpleObfs is a base http-obfs struct -type SimpleObfs struct { - dialer netproxy.Dialer - obfstype ObfsType - addr string - path string - host string -} - -// NewSimpleobfs returns a simpleobfs proxy. -func NewSimpleObfs(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - u, err := url.Parse(link) - if err != nil { - return nil, nil, fmt.Errorf("simpleobfs: %w", err) - } - - t := &SimpleObfs{ - dialer: nextDialer, - addr: u.Host, - } - query := u.Query() - obfstype := query.Get("type") - if obfstype == "" { - obfstype = query.Get("obfs") - } - switch strings.ToLower(obfstype) { - case "http": - t.obfstype = HTTP - case "tls": - t.obfstype = TLS - default: - return nil, nil, fmt.Errorf("unsupported obfs type %v", obfstype) - } - t.host = query.Get("host") - t.path = query.Get("path") - if t.path == "" { - t.path = query.Get("uri") - } - return t, &dialer.Property{ - Name: u.Fragment, - Address: t.addr, - Protocol: "simpleobfs(" + obfstype + ")", - Link: link, - }, nil -} - -func (s *SimpleObfs) Dial(network, addr string) (c netproxy.Conn, err error) { - magicNetwork, err := netproxy.ParseMagicNetwork(network) - if err != nil { - return nil, err - } - switch magicNetwork.Network { - case "tcp": - rc, err := s.dialer.Dial(network, s.addr) - if err != nil { - return nil, fmt.Errorf("[simpleobfs]: dial to %s: %w", s.addr, err) - } - - host, port, err := net.SplitHostPort(s.addr) - if err != nil { - return nil, err - } - if s.host != "" { - host = s.host - } - switch s.obfstype { - case HTTP: - c = NewHTTPObfs(rc, host, port, s.path) - case TLS: - c = NewTLSObfs(rc, host) - } - return c, err - case "udp": - return nil, fmt.Errorf("%w: simpleobfs+udp", netproxy.UnsupportedTunnelTypeError) - default: - return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network) - } -} diff --git a/component/outbound/transport/simpleobfs/tls.go b/component/outbound/transport/simpleobfs/tls.go deleted file mode 100644 index df95e9c..0000000 --- a/component/outbound/transport/simpleobfs/tls.go +++ /dev/null @@ -1,200 +0,0 @@ -// from https://github.com/Dreamacro/clash/blob/master/component/simple-obfs/tls.go - -package simpleobfs - -import ( - "bytes" - "encoding/binary" - "io" - "sync" - "time" - - "github.com/daeuniverse/outbound/netproxy" - "github.com/daeuniverse/outbound/pkg/fastrand" -) - -const ( - chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 -) - -// TLSObfs is shadowsocks tls simple-obfs implementation -type TLSObfs struct { - netproxy.Conn - server string - remain int - firstRequest bool - firstResponse bool - rMu sync.Mutex - wMu sync.Mutex -} - -func (to *TLSObfs) read(b []byte, discardN int) (int, error) { - buf := make([]byte, discardN) - _, err := io.ReadFull(to.Conn, buf) - if err != nil { - return 0, err - } - sizeBuf := make([]byte, 2) - _, err = io.ReadFull(to.Conn, sizeBuf) - if err != nil { - return 0, nil - } - - length := int(binary.BigEndian.Uint16(sizeBuf)) - if length > len(b) { - n, err := to.Conn.Read(b) - if err != nil { - return n, err - } - to.remain = length - n - return n, nil - } - - return io.ReadFull(to.Conn, b[:length]) -} - -func (to *TLSObfs) Read(b []byte) (int, error) { - to.rMu.Lock() - defer to.rMu.Unlock() - if to.remain > 0 { - length := to.remain - if length > len(b) { - length = len(b) - } - - n, err := io.ReadFull(to.Conn, b[:length]) - to.remain -= n - return n, err - } - - if to.firstResponse { - // type + ver + lensize + 91 = 96 - // type + ver + lensize + 1 = 6 - // type + ver = 3 - to.firstResponse = false - return to.read(b, 105) - } - - // type + ver = 3 - return to.read(b, 3) -} -func (to *TLSObfs) Write(b []byte) (int, error) { - to.wMu.Lock() - defer to.wMu.Unlock() - length := len(b) - for i := 0; i < length; i += chunkSize { - end := i + chunkSize - if end > length { - end = length - } - - n, err := to.write(b[i:end]) - if err != nil { - return n, err - } - } - return length, nil -} - -func (to *TLSObfs) write(b []byte) (int, error) { - if to.firstRequest { - helloMsg := makeClientHelloMsg(b, to.server) - _, err := to.Conn.Write(helloMsg) - to.firstRequest = false - return len(b), err - } - - buf := &bytes.Buffer{} - buf.Write([]byte{0x17, 0x03, 0x03}) - binary.Write(buf, binary.BigEndian, uint16(len(b))) - buf.Write(b) - _, err := to.Conn.Write(buf.Bytes()) - return len(b), err -} - -// NewTLSObfs return a SimpleObfs -func NewTLSObfs(conn netproxy.Conn, server string) netproxy.Conn { - return &TLSObfs{ - Conn: conn, - server: server, - firstRequest: true, - firstResponse: true, - } -} - -func makeClientHelloMsg(data []byte, server string) []byte { - random := make([]byte, 28) - sessionID := make([]byte, 32) - fastrand.Read(random) - fastrand.Read(sessionID) - - buf := &bytes.Buffer{} - - // handshake, TLS 1.0 version, length - buf.WriteByte(22) - buf.Write([]byte{0x03, 0x01}) - length := uint16(212 + len(data) + len(server)) - buf.WriteByte(byte(length >> 8)) - buf.WriteByte(byte(length & 0xff)) - - // clientHello, length, TLS 1.2 version - buf.WriteByte(1) - buf.WriteByte(0) - binary.Write(buf, binary.BigEndian, uint16(208+len(data)+len(server))) - buf.Write([]byte{0x03, 0x03}) - - // random with timestamp, sid len, sid - binary.Write(buf, binary.BigEndian, uint32(time.Now().Unix())) - buf.Write(random) - buf.WriteByte(32) - buf.Write(sessionID) - - // cipher suites - buf.Write([]byte{0x00, 0x38}) - buf.Write([]byte{ - 0xc0, 0x2c, 0xc0, 0x30, 0x00, 0x9f, 0xcc, 0xa9, 0xcc, 0xa8, 0xcc, 0xaa, 0xc0, 0x2b, 0xc0, 0x2f, - 0x00, 0x9e, 0xc0, 0x24, 0xc0, 0x28, 0x00, 0x6b, 0xc0, 0x23, 0xc0, 0x27, 0x00, 0x67, 0xc0, 0x0a, - 0xc0, 0x14, 0x00, 0x39, 0xc0, 0x09, 0xc0, 0x13, 0x00, 0x33, 0x00, 0x9d, 0x00, 0x9c, 0x00, 0x3d, - 0x00, 0x3c, 0x00, 0x35, 0x00, 0x2f, 0x00, 0xff, - }) - - // compression - buf.Write([]byte{0x01, 0x00}) - - // extension length - binary.Write(buf, binary.BigEndian, uint16(79+len(data)+len(server))) - - // session ticket - buf.Write([]byte{0x00, 0x23}) - binary.Write(buf, binary.BigEndian, uint16(len(data))) - buf.Write(data) - - // server name - buf.Write([]byte{0x00, 0x00}) - binary.Write(buf, binary.BigEndian, uint16(len(server)+5)) - binary.Write(buf, binary.BigEndian, uint16(len(server)+3)) - buf.WriteByte(0) - binary.Write(buf, binary.BigEndian, uint16(len(server))) - buf.Write([]byte(server)) - - // ec_point - buf.Write([]byte{0x00, 0x0b, 0x00, 0x04, 0x03, 0x01, 0x00, 0x02}) - - // groups - buf.Write([]byte{0x00, 0x0a, 0x00, 0x0a, 0x00, 0x08, 0x00, 0x1d, 0x00, 0x17, 0x00, 0x19, 0x00, 0x18}) - - // signature - buf.Write([]byte{ - 0x00, 0x0d, 0x00, 0x20, 0x00, 0x1e, 0x06, 0x01, 0x06, 0x02, 0x06, 0x03, 0x05, - 0x01, 0x05, 0x02, 0x05, 0x03, 0x04, 0x01, 0x04, 0x02, 0x04, 0x03, 0x03, 0x01, - 0x03, 0x02, 0x03, 0x03, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, - }) - - // encrypt then mac - buf.Write([]byte{0x00, 0x16, 0x00, 0x00}) - - // extended master secret - buf.Write([]byte{0x00, 0x17, 0x00, 0x00}) - - return buf.Bytes() -} diff --git a/component/outbound/transport/tls/dialer.go b/component/outbound/transport/tls/dialer.go deleted file mode 100644 index d09078f..0000000 --- a/component/outbound/transport/tls/dialer.go +++ /dev/null @@ -1,8 +0,0 @@ -package tls - -import "github.com/daeuniverse/dae/component/outbound/dialer" - -func init() { - dialer.FromLinkRegister("tls", NewTls) - dialer.FromLinkRegister("utls", NewTls) -} diff --git a/component/outbound/transport/tls/tls.go b/component/outbound/transport/tls/tls.go deleted file mode 100644 index 98fd170..0000000 --- a/component/outbound/transport/tls/tls.go +++ /dev/null @@ -1,131 +0,0 @@ -package tls - -import ( - "crypto/tls" - "fmt" - "net/url" - "strconv" - "strings" - - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" - utls "github.com/refraction-networking/utls" -) - -// Tls is a base Tls struct -type Tls struct { - dialer netproxy.Dialer - addr string - serverName string - skipVerify bool - tlsImplentation string - utlsImitate string - - tlsConfig *tls.Config -} - -// NewTls returns a Tls infra. -func NewTls(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - u, err := url.Parse(link) - if err != nil { - return nil, nil, fmt.Errorf("NewTls: %w", err) - } - - query := u.Query() - - tlsImplentation := u.Scheme - utlsImitate := query.Get("utlsImitate") - if tlsImplentation == "tls" && option.TlsImplementation != "" { - tlsImplentation = option.TlsImplementation - utlsImitate = option.UtlsImitate - } - t := &Tls{ - dialer: nextDialer, - addr: u.Host, - tlsImplentation: tlsImplentation, - utlsImitate: utlsImitate, - serverName: query.Get("sni"), - } - if t.serverName == "" { - t.serverName = u.Hostname() - } - - // skipVerify - allowInsecure, _ := strconv.ParseBool(u.Query().Get("allowInsecure")) - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("allow_insecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("allowinsecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("skipVerify")) - } - t.skipVerify = allowInsecure || option.AllowInsecure - t.tlsConfig = &tls.Config{ - ServerName: t.serverName, - InsecureSkipVerify: t.skipVerify, - } - if len(query.Get("alpn")) > 0 { - t.tlsConfig.NextProtos = strings.Split(query.Get("alpn"), ",") - } - - return t, &dialer.Property{ - Name: u.Fragment, - Address: t.addr, - Protocol: tlsImplentation, - Link: link, - }, nil -} - -func (s *Tls) Dial(network, addr string) (c netproxy.Conn, err error) { - magicNetwork, err := netproxy.ParseMagicNetwork(network) - if err != nil { - return nil, err - } - switch magicNetwork.Network { - case "tcp": - rc, err := s.dialer.Dial(network, s.addr) - if err != nil { - return nil, fmt.Errorf("[Tls]: dial to %s: %w", s.addr, err) - } - - var tlsConn interface { - netproxy.Conn - Handshake() error - } - - switch s.tlsImplentation { - case "tls": - tlsConn = tls.Client(&netproxy.FakeNetConn{ - Conn: rc, - LAddr: nil, - RAddr: nil, - }, s.tlsConfig) - - case "utls": - clientHelloID, err := nameToUtlsClientHelloID(s.utlsImitate) - if err != nil { - return nil, err - } - - tlsConn = utls.UClient(&netproxy.FakeNetConn{ - Conn: rc, - LAddr: nil, - RAddr: nil, - }, uTLSConfigFromTLSConfig(s.tlsConfig), *clientHelloID) - - default: - return nil, fmt.Errorf("unknown tls implementation: %v", s.tlsImplentation) - } - - if err := tlsConn.Handshake(); err != nil { - return nil, err - } - return tlsConn, err - case "udp": - return nil, fmt.Errorf("%w: tls+udp", netproxy.UnsupportedTunnelTypeError) - default: - return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network) - } -} diff --git a/component/outbound/transport/tls/utls.go b/component/outbound/transport/tls/utls.go deleted file mode 100644 index 76d8542..0000000 --- a/component/outbound/transport/tls/utls.go +++ /dev/null @@ -1,63 +0,0 @@ -package tls - -import ( - "crypto/tls" - "fmt" - - utls "github.com/refraction-networking/utls" -) - -func uTLSConfigFromTLSConfig(config *tls.Config) *utls.Config { - return &utls.Config{ - ServerName: config.ServerName, - InsecureSkipVerify: config.InsecureSkipVerify, - } -} - -var clientHelloIDMap = map[string]*utls.ClientHelloID{ - "randomized": &utls.HelloRandomized, - "randomizedalpn": &utls.HelloRandomizedALPN, - "randomizednoalpn": &utls.HelloRandomizedNoALPN, - "firefox_auto": &utls.HelloFirefox_Auto, - "firefox_55": &utls.HelloFirefox_55, - "firefox_56": &utls.HelloFirefox_56, - "firefox_63": &utls.HelloFirefox_63, - "firefox_65": &utls.HelloFirefox_65, - "firefox_99": &utls.HelloFirefox_99, - "firefox_102": &utls.HelloFirefox_102, - "firefox_105": &utls.HelloFirefox_105, - "chrome_auto": &utls.HelloChrome_Auto, - "chrome_58": &utls.HelloChrome_58, - "chrome_62": &utls.HelloChrome_62, - "chrome_70": &utls.HelloChrome_70, - "chrome_72": &utls.HelloChrome_72, - "chrome_83": &utls.HelloChrome_83, - "chrome_87": &utls.HelloChrome_87, - "chrome_96": &utls.HelloChrome_96, - "chrome_100": &utls.HelloChrome_100, - "chrome_102": &utls.HelloChrome_102, - "ios_auto": &utls.HelloIOS_Auto, - "ios_11_1": &utls.HelloIOS_11_1, - "ios_12_1": &utls.HelloIOS_12_1, - "ios_13": &utls.HelloIOS_13, - "ios_14": &utls.HelloIOS_14, - "android_11_okhttp": &utls.HelloAndroid_11_OkHttp, - "edge_auto": &utls.HelloEdge_Auto, - "edge_85": &utls.HelloEdge_85, - "edge_106": &utls.HelloEdge_106, - "safari_auto": &utls.HelloSafari_Auto, - "safari_16_0": &utls.HelloSafari_16_0, - "360_auto": &utls.Hello360_Auto, - "360_7_5": &utls.Hello360_7_5, - "360_11_0": &utls.Hello360_11_0, - "qq_auto": &utls.HelloQQ_Auto, - "qq_11_1": &utls.HelloQQ_11_1, -} - -func nameToUtlsClientHelloID(name string) (*utls.ClientHelloID, error) { - clientHelloID, ok := clientHelloIDMap[name] - if !ok { - return nil, fmt.Errorf("unknown uTLS Client Hello ID: %s", name) - } - return clientHelloID, nil -} diff --git a/component/outbound/transport/ws/conn.go b/component/outbound/transport/ws/conn.go deleted file mode 100644 index 7137727..0000000 --- a/component/outbound/transport/ws/conn.go +++ /dev/null @@ -1,42 +0,0 @@ -package ws - -import ( - "bytes" - "github.com/gorilla/websocket" - "time" -) - -type conn struct { - *websocket.Conn - readBuffer bytes.Buffer -} - -func newConn(wsc *websocket.Conn) *conn { - return &conn{ - Conn: wsc, - } -} - -func (c *conn) Read(b []byte) (n int, err error) { - if c.readBuffer.Len() > 0 { - return c.readBuffer.Read(b) - } - _, msg, err := c.Conn.ReadMessage() - if err != nil { - return 0, err - } - n = copy(b, msg) - if n < len(msg) { - c.readBuffer.Write(msg[n:]) - } - return n, nil - -} -func (c *conn) Write(b []byte) (n int, err error) { - return len(b), c.Conn.WriteMessage(websocket.BinaryMessage, b) -} - -func (c *conn) SetDeadline(t time.Time) error { - _ = c.Conn.SetReadDeadline(t) - return c.Conn.SetWriteDeadline(t) -} diff --git a/component/outbound/transport/ws/dialer.go b/component/outbound/transport/ws/dialer.go deleted file mode 100644 index 71e3547..0000000 --- a/component/outbound/transport/ws/dialer.go +++ /dev/null @@ -1,10 +0,0 @@ -package ws - -import ( - "github.com/daeuniverse/dae/component/outbound/dialer" -) - -func init() { - dialer.FromLinkRegister("ws", NewWs) - dialer.FromLinkRegister("wss", NewWs) -} diff --git a/component/outbound/transport/ws/ws.go b/component/outbound/transport/ws/ws.go deleted file mode 100644 index de3e755..0000000 --- a/component/outbound/transport/ws/ws.go +++ /dev/null @@ -1,108 +0,0 @@ -package ws - -import ( - "crypto/tls" - "fmt" - "net" - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/daeuniverse/dae/component/outbound/dialer" - "github.com/daeuniverse/outbound/netproxy" - "github.com/gorilla/websocket" -) - -// Ws is a base Ws struct -type Ws struct { - dialer netproxy.Dialer - wsAddr string - header http.Header - tlsClientConfig *tls.Config -} - -// NewWs returns a Ws infra. -func NewWs(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { - u, err := url.Parse(link) - if err != nil { - return nil, nil, fmt.Errorf("NewWs: %w", err) - } - - t := &Ws{ - dialer: nextDialer, - } - - query := u.Query() - host := query.Get("host") - if host == "" { - host = u.Hostname() - } - t.header = http.Header{} - t.header.Set("Host", host) - - wsUrl := url.URL{ - Scheme: u.Scheme, - Host: u.Host, - } - t.wsAddr = wsUrl.String() + u.Path - if u.Scheme == "wss" { - allowInsecure, _ := strconv.ParseBool(u.Query().Get("allowInsecure")) - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("allow_insecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("allowinsecure")) - } - if !allowInsecure { - allowInsecure, _ = strconv.ParseBool(u.Query().Get("skipVerify")) - } - // TODO: utls - t.tlsClientConfig = &tls.Config{ - ServerName: query.Get("sni"), - InsecureSkipVerify: allowInsecure || option.AllowInsecure, - } - if len(query.Get("alpn")) > 0 { - t.tlsClientConfig.NextProtos = strings.Split(query.Get("alpn"), ",") - } - } - return t, &dialer.Property{ - Name: u.Fragment, - Address: wsUrl.Host, - Protocol: u.Scheme, - Link: link, - }, nil -} - -func (s *Ws) Dial(network, addr string) (c netproxy.Conn, err error) { - magicNetwork, err := netproxy.ParseMagicNetwork(network) - if err != nil { - return nil, err - } - switch magicNetwork.Network { - case "tcp": - wsDialer := &websocket.Dialer{ - NetDial: func(_, addr string) (net.Conn, error) { - c, err := s.dialer.Dial(network, addr) - if err != nil { - return nil, err - } - return &netproxy.FakeNetConn{ - Conn: c, - LAddr: nil, - RAddr: nil, - }, nil - }, - TLSClientConfig: s.tlsClientConfig, - } - rc, _, err := wsDialer.Dial(s.wsAddr, s.header) - if err != nil { - return nil, fmt.Errorf("[Ws]: dial to %s: %w", s.wsAddr, err) - } - return newConn(rc), err - case "udp": - return nil, fmt.Errorf("%w: ws+udp", netproxy.UnsupportedTunnelTypeError) - default: - return nil, fmt.Errorf("%w: %v", netproxy.UnsupportedTunnelTypeError, network) - } -} diff --git a/control/control_plane.go b/control/control_plane.go index 8f14329..8f8cda4 100644 --- a/control/control_plane.go +++ b/control/control_plane.go @@ -33,6 +33,7 @@ import ( "github.com/daeuniverse/dae/config" "github.com/daeuniverse/dae/pkg/config_parser" internal "github.com/daeuniverse/dae/pkg/ebpf_internal" + D "github.com/daeuniverse/outbound/dialer" "github.com/daeuniverse/outbound/pool" "github.com/daeuniverse/outbound/protocol/direct" "github.com/daeuniverse/outbound/transport/grpc" @@ -224,24 +225,17 @@ func NewControlPlane( log.Warnln("AllowInsecure is enabled, but it is not recommended. Please make sure you have to turn it on.") } option := &dialer.GlobalOption{ - Log: log, - TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{ - Raw: global.TcpCheckUrl, - Log: log, - ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), - Method: global.TcpCheckHttpMethod, - }, - CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{ - Raw: global.UdpCheckDns, - ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), - Somark: global.SoMarkFromDae, + ExtraOption: D.ExtraOption{ + AllowInsecure: global.AllowInsecure, + TlsImplementation: global.TlsImplementation, + UtlsImitate: global.UtlsImitate, }, + Log: log, + TcpCheckOptionRaw: dialer.TcpCheckOptionRaw{Raw: global.TcpCheckUrl, Log: log, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), Method: global.TcpCheckHttpMethod}, + CheckDnsOptionRaw: dialer.CheckDnsOptionRaw{Raw: global.UdpCheckDns, ResolverNetwork: common.MagicNetwork("udp", global.SoMarkFromDae), Somark: global.SoMarkFromDae}, CheckInterval: global.CheckInterval, CheckTolerance: global.CheckTolerance, CheckDnsTcp: true, - AllowInsecure: global.AllowInsecure, - TlsImplementation: global.TlsImplementation, - UtlsImitate: global.UtlsImitate, } // Dial mode. @@ -255,9 +249,9 @@ func NewControlPlane( } disableKernelAliveCallback := dialMode != consts.DialMode_Ip _direct, directProperty := dialer.NewDirectDialer(option, true) - direct := dialer.NewDialer(_direct, option, dialer.InstanceOption{CheckEnabled: false}, directProperty) + direct := dialer.NewDialer(_direct, option, dialer.InstanceOption{DisableCheck: true}, directProperty) _block, blockProperty := dialer.NewBlockDialer(option, func() { /*Dialer Outbound*/ }) - block := dialer.NewDialer(_block, option, dialer.InstanceOption{CheckEnabled: false}, blockProperty) + block := dialer.NewDialer(_block, option, dialer.InstanceOption{DisableCheck: true}, blockProperty) outbounds := []*outbound.DialerGroup{ outbound.NewDialerGroup(option, consts.OutboundDirect.String(), []*dialer.Dialer{direct}, []*dialer.Annotation{{}}, diff --git a/go.mod b/go.mod index 818f9f3..f5f5077 100644 --- a/go.mod +++ b/go.mod @@ -11,11 +11,11 @@ require ( github.com/cilium/ebpf v0.11.0 github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d github.com/daeuniverse/outbound v0.0.0-20240423150318-97fdbb427e02 + github.com/dlclark/regexp2 v1.11.0 github.com/json-iterator/go v1.1.12 github.com/miekg/dns v1.1.55 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd - github.com/refraction-networking/utls v1.6.4 github.com/safchain/ethtool v0.3.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 @@ -33,7 +33,7 @@ require ( github.com/andybalholm/brotli v1.0.6 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect - github.com/gorilla/websocket v1.5.0 + github.com/gorilla/websocket v1.5.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/stretchr/testify v1.8.1 // indirect @@ -65,6 +65,7 @@ require ( github.com/mzz2017/disk-bloom v1.0.1 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/quic-go/qpack v0.4.0 // indirect + github.com/refraction-networking/utls v1.6.4 // indirect github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/vishvananda/netns v0.0.4 // indirect diff --git a/go.sum b/go.sum index 60b56e7..2fe2dc3 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFP github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 h1:ED31mPIxDJnrLt9W9dH5xgd/6KjzEACKHBVGQ33czc0= github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152/go.mod h1:I9fhc/EvSg88cDxmfQ47v35Ssz9rlFunL/KY0A1JAYI= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521 h1:fBHFH+Y/GPGFGo7LIrErQc3p2MeAhoIQNgaxPWYsSxk= github.com/ebfe/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:ucvhdsUCE3TH0LoLRb6ShHiJl8e39dGlx6A4g/ujlow= github.com/eknkc/basex v1.0.1 h1:TcyAkqh4oJXgV3WYyL4KEfCMk9W8oJCpmx1bo+jVgKY=