diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08c80e75..1283400c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -142,7 +142,7 @@ jobs: if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }} id: setup-ndk with: - ndk-version: r25b + ndk-version: r26 add-to-path: false local-cache: true @@ -204,7 +204,7 @@ jobs: Upload-Prerelease: permissions: write-all - if: ${{ github.ref_type=='branch' }} + if: ${{ github.ref_type=='branch' && github.event_name != 'pull_request' }} needs: [Build] runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index 9339a935..51cecc2d 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,22 @@ - VMess, Shadowsocks, Trojan, Snell protocol support for remote connections - Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP. - Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes -- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency -- Remote providers, allowing users to get node lists remotely instead of hardcoding in config +- Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node + based off latency +- Remote providers, allowing users to get node lists remotely instead of hard-coding in config - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Comprehensive HTTP RESTful API controller +## Dashboard + +We made an official web dashboard providing first class support for this project, check it out +at [metacubexd](https://github.com/MetaCubeX/metacubexd) + ## Wiki -Configuration examples can be found at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be found [Clash.Meta Wiki](https://clash-meta.wiki). + +Configuration examples can be found +at [/docs/config.yaml](https://github.com/MetaCubeX/Clash.Meta/blob/Alpha/docs/config.yaml), while documentation can be +found [Clash.Meta Wiki](https://clash-meta.wiki). ## Build @@ -43,7 +52,7 @@ git clone https://github.com/MetaCubeX/Clash.Meta.git cd Clash.Meta && go mod download ``` -If you can't visit github,you should set proxy first: +If you can't visit GitHub, you should set proxy first: ```shell go env -w GOPROXY=https://goproxy.io,direct @@ -324,36 +333,27 @@ ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta WantedBy=multi-user.target ``` -Launch clashd on system startup with: +Launch clash-meta daemon on system startup with: ```shell $ systemctl enable Clash-Meta ``` -Launch clashd immediately with: +Launch clash-meta daemon immediately with: ```shell $ systemctl start Clash-Meta ``` -### Display Process name - -Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`. - -To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta). - -### Dashboard - -We also made a custom fork of yacd provide better support for this project, check it out at [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta) - ## Development -If you want to build an application that uses clash as a library, check out the +If you want to build an application that uses clash as a library, check out the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library) ## Debugging -Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API. +Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug +API. ## Credits diff --git a/adapter/adapter.go b/adapter/adapter.go index 6cc79c3a..e9ce59bb 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -18,6 +18,8 @@ import ( "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" + + "github.com/puzpuzpuz/xsync/v2" ) var UnifiedDelay = atomic.NewBool(false) @@ -36,7 +38,7 @@ type Proxy struct { history *queue.Queue[C.DelayHistory] alive *atomic.Bool url string - extra map[string]*extraProxyState + extra *xsync.MapOf[string, *extraProxyState] } // Alive implements C.Proxy @@ -46,10 +48,8 @@ func (p *Proxy) Alive() bool { // AliveForTestUrl implements C.Proxy func (p *Proxy) AliveForTestUrl(url string) bool { - if p.extra != nil { - if state, ok := p.extra[url]; ok { - return state.alive.Load() - } + if state, ok := p.extra.Load(url); ok { + return state.alive.Load() } return p.alive.Load() @@ -88,16 +88,16 @@ func (p *Proxy) DelayHistory() []C.DelayHistory { for _, item := range queueM { histories = append(histories, item) } + return histories } // DelayHistoryForTestUrl implements C.Proxy func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory { var queueM []C.DelayHistory - if p.extra != nil { - if state, ok := p.extra[url]; ok { - queueM = state.history.Copy() - } + + if state, ok := p.extra.Load(url); ok { + queueM = state.history.Copy() } if queueM == nil { @@ -112,19 +112,25 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory { } func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory { - extra := map[string][]C.DelayHistory{} - if p.extra != nil && len(p.extra) != 0 { - for testUrl, option := range p.extra { - histories := []C.DelayHistory{} - queueM := option.history.Copy() - for _, item := range queueM { - histories = append(histories, item) - } + extraHistory := map[string][]C.DelayHistory{} - extra[testUrl] = histories + p.extra.Range(func(k string, v *extraProxyState) bool { + + testUrl := k + state := v + + histories := []C.DelayHistory{} + queueM := state.history.Copy() + + for _, item := range queueM { + histories = append(histories, item) } - } - return extra + + extraHistory[testUrl] = histories + + return true + }) + return extraHistory } // LastDelay return last history record. if proxy is not alive, return the max value of uint16. @@ -149,11 +155,9 @@ func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) { alive := p.alive.Load() history := p.history.Last() - if p.extra != nil { - if state, ok := p.extra[url]; ok { - alive = state.alive.Load() - history = state.history.Last() - } + if state, ok := p.extra.Load(url); ok { + alive = state.alive.Load() + history = state.history.Last() } if !alive { @@ -213,18 +217,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In if alive { record.Delay = t } - - if p.extra == nil { - p.extra = map[string]*extraProxyState{} + p.history.Put(record) + if p.history.Len() > defaultHistoriesNum { + p.history.Pop() } - state, ok := p.extra[url] + state, ok := p.extra.Load(url) if !ok { state = &extraProxyState{ history: queue.New[C.DelayHistory](defaultHistoriesNum), alive: atomic.NewBool(true), } - p.extra[url] = state + p.extra.Store(url, state) } state.alive.Store(alive) @@ -307,7 +311,12 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In } func NewProxy(adapter C.ProxyAdapter) *Proxy { - return &Proxy{adapter, queue.New[C.DelayHistory](defaultHistoriesNum), atomic.NewBool(true), "", map[string]*extraProxyState{}} + return &Proxy{ + ProxyAdapter: adapter, + history: queue.New[C.DelayHistory](defaultHistoriesNum), + alive: atomic.NewBool(true), + url: "", + extra: xsync.NewMapOf[*extraProxyState]()} } func urlToMetadata(rawURL string) (addr C.Metadata, err error) { @@ -350,14 +359,14 @@ func (p *Proxy) determineFinalStoreType(store C.DelayHistoryStoreType, url strin return C.OriginalHistory } - if p.extra == nil { - store = C.ExtraHistory - } else { - if _, ok := p.extra[url]; ok { - store = C.ExtraHistory - } else if len(p.extra) < 2*C.DefaultMaxHealthCheckUrlNum { - store = C.ExtraHistory - } + if p.extra.Size() < 2*C.DefaultMaxHealthCheckUrlNum { + return C.ExtraHistory } + + _, ok := p.extra.Load(url) + if ok { + return C.ExtraHistory + } + return store } diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index 94b59cd0..75e999a6 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -3,6 +3,9 @@ package outbound import ( "context" "errors" + "net/netip" + + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" @@ -12,6 +15,11 @@ type Direct struct { *Base } +type DirectOption struct { + BasicOption + Name string `proxy:"name"` +} + // DialContext implements C.ProxyAdapter func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { opts = append(opts, dialer.WithResolver(resolver.DefaultResolver)) @@ -19,7 +27,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ... if err != nil { return nil, err } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return NewConn(c, d), nil } @@ -33,13 +41,28 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, } metadata.DstIP = ip } - pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", metadata.DstIP), "", d.Base.DialOptions(opts...)...) + pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) if err != nil { return nil, err } return newPacketConn(pc, d), nil } +func NewDirectWithOption(option DirectOption) *Direct { + return &Direct{ + Base: &Base{ + name: option.Name, + tp: C.Direct, + udp: true, + tfo: option.TFO, + mpTcp: option.MPTCP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), + }, + } +} + func NewDirect() *Direct { return &Direct{ Base: &Base{ diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index 0b652ca9..19074bb3 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -7,14 +7,16 @@ import ( "encoding/base64" "errors" "fmt" + "io" "net" "net/http" "strconv" + N "github.com/Dreamacro/clash/common/net" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" - tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" ) @@ -74,7 +76,7 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad if err != nil { return nil, fmt.Errorf("%s connect error: %w", h.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -155,19 +157,13 @@ func NewHttp(option HttpOption) (*Http, error) { if option.SNI != "" { sni = option.SNI } - if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{ - InsecureSkipVerify: option.SkipCertVerify, - ServerName: sni, - }) - } else { - var err error - if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(&tls.Config{ - InsecureSkipVerify: option.SkipCertVerify, - ServerName: sni, - }, option.Fingerprint); err != nil { - return nil, err - } + var err error + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{ + InsecureSkipVerify: option.SkipCertVerify, + ServerName: sni, + }, option.Fingerprint) + if err != nil { + return nil, err } } diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index 7da4975d..8a9d6258 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -2,16 +2,11 @@ package outbound import ( "context" - "crypto/sha256" "crypto/tls" "encoding/base64" - "encoding/hex" - "encoding/pem" "fmt" "net" "net/netip" - "os" - "regexp" "strconv" "time" @@ -19,9 +14,9 @@ import ( "github.com/metacubex/quic-go/congestion" M "github.com/sagernet/sing/common/metadata" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" - tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" hyCongestion "github.com/Dreamacro/clash/transport/hysteria/congestion" @@ -43,8 +38,6 @@ const ( DefaultHopInterval = 10 ) -var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`) - type Hysteria struct { *Base @@ -120,12 +113,12 @@ type HysteriaOption struct { func (c *HysteriaOption) Speed() (uint64, uint64, error) { var up, down uint64 - up = stringToBps(c.Up) + up = StringToBps(c.Up) if up == 0 { return 0, 0, fmt.Errorf("invaild upload speed: %s", c.Up) } - down = stringToBps(c.Down) + down = StringToBps(c.Down) if down == 0 { return 0, 0, fmt.Errorf("invaild download speed: %s", c.Down) } @@ -153,37 +146,10 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { MinVersion: tls.VersionTLS13, } - var bs []byte var err error - if len(option.CustomCA) > 0 { - bs, err = os.ReadFile(option.CustomCA) - if err != nil { - return nil, fmt.Errorf("hysteria %s load ca error: %w", addr, err) - } - } else if option.CustomCAString != "" { - bs = []byte(option.CustomCAString) - } - - if len(bs) > 0 { - block, _ := pem.Decode(bs) - if block == nil { - return nil, fmt.Errorf("CA cert is not PEM") - } - - fpBytes := sha256.Sum256(block.Bytes) - if len(option.Fingerprint) == 0 { - option.Fingerprint = hex.EncodeToString(fpBytes[:]) - } - } - - if len(option.Fingerprint) != 0 { - var err error - tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) - if err != nil { - return nil, err - } - } else { - tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) + tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString) + if err != nil { + return nil, err } if len(option.ALPN) > 0 { @@ -268,42 +234,6 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { }, nil } -func stringToBps(s string) uint64 { - if s == "" { - return 0 - } - - // when have not unit, use Mbps - if v, err := strconv.Atoi(s); err == nil { - return stringToBps(fmt.Sprintf("%d Mbps", v)) - } - - m := rateStringRegexp.FindStringSubmatch(s) - if m == nil { - return 0 - } - var n uint64 - switch m[2] { - case "K": - n = 1 << 10 - case "M": - n = 1 << 20 - case "G": - n = 1 << 30 - case "T": - n = 1 << 40 - default: - n = 1 - } - v, _ := strconv.ParseUint(m[1], 10, 64) - n = v * n - if m[3] == "b" { - // Bits, need to convert to bytes - n = n >> 3 - } - return n -} - type hyPacketConn struct { core.UDPConn } diff --git a/adapter/outbound/hysteria2.go b/adapter/outbound/hysteria2.go new file mode 100644 index 00000000..57c15a12 --- /dev/null +++ b/adapter/outbound/hysteria2.go @@ -0,0 +1,157 @@ +package outbound + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "runtime" + "strconv" + + CN "github.com/Dreamacro/clash/common/net" + "github.com/Dreamacro/clash/component/ca" + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/proxydialer" + C "github.com/Dreamacro/clash/constant" + tuicCommon "github.com/Dreamacro/clash/transport/tuic/common" + + "github.com/metacubex/sing-quic/hysteria2" + + M "github.com/sagernet/sing/common/metadata" +) + +func init() { + hysteria2.SetCongestionController = tuicCommon.SetCongestionController +} + +type Hysteria2 struct { + *Base + + option *Hysteria2Option + client *hysteria2.Client + dialer proxydialer.SingDialer +} + +type Hysteria2Option struct { + BasicOption + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Up string `proxy:"up,omitempty"` + Down string `proxy:"down,omitempty"` + Password string `proxy:"password,omitempty"` + Obfs string `proxy:"obfs,omitempty"` + ObfsPassword string `proxy:"obfs-password,omitempty"` + SNI string `proxy:"sni,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Fingerprint string `proxy:"fingerprint,omitempty"` + ALPN []string `proxy:"alpn,omitempty"` + CustomCA string `proxy:"ca,omitempty"` + CustomCAString string `proxy:"ca-str,omitempty"` + CWND int `proxy:"cwnd,omitempty"` +} + +func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { + options := h.Base.DialOptions(opts...) + h.dialer.SetDialer(dialer.NewDialer(options...)) + c, err := h.client.DialConn(ctx, M.ParseSocksaddr(metadata.RemoteAddress())) + if err != nil { + return nil, err + } + return NewConn(CN.NewRefConn(c, h), h), nil +} + +func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { + options := h.Base.DialOptions(opts...) + h.dialer.SetDialer(dialer.NewDialer(options...)) + pc, err := h.client.ListenPacket(ctx) + if err != nil { + return nil, err + } + if pc == nil { + return nil, errors.New("packetConn is nil") + } + return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil +} + +func closeHysteria2(h *Hysteria2) { + if h.client != nil { + _ = h.client.CloseWithError(errors.New("proxy removed")) + } +} + +func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { + addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + var salamanderPassword string + if len(option.Obfs) > 0 { + if option.ObfsPassword == "" { + return nil, errors.New("missing obfs password") + } + switch option.Obfs { + case hysteria2.ObfsTypeSalamander: + salamanderPassword = option.ObfsPassword + default: + return nil, fmt.Errorf("unknown obfs type: %s", option.Obfs) + } + } + + serverName := option.Server + if option.SNI != "" { + serverName = option.SNI + } + + tlsConfig := &tls.Config{ + ServerName: serverName, + InsecureSkipVerify: option.SkipCertVerify, + MinVersion: tls.VersionTLS13, + } + + var err error + tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString) + if err != nil { + return nil, err + } + + if len(option.ALPN) > 0 { + tlsConfig.NextProtos = option.ALPN + } + + singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()) + + clientOptions := hysteria2.ClientOptions{ + Context: context.TODO(), + Dialer: singDialer, + ServerAddress: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)), + SendBPS: StringToBps(option.Up), + ReceiveBPS: StringToBps(option.Down), + SalamanderPassword: salamanderPassword, + Password: option.Password, + TLSConfig: tlsConfig, + UDPDisabled: false, + CWND: option.CWND, + } + + client, err := hysteria2.NewClient(clientOptions) + if err != nil { + return nil, err + } + + outbound := &Hysteria2{ + Base: &Base{ + name: option.Name, + addr: addr, + tp: C.Hysteria2, + udp: true, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), + }, + option: &option, + client: client, + dialer: singDialer, + } + runtime.SetFinalizer(outbound, closeHysteria2) + + return outbound, nil +} diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index c1481622..f744ec53 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -19,7 +19,7 @@ import ( v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" restlsC "github.com/3andne/restls-client-go" - "github.com/metacubex/sing-shadowsocks2" + shadowsocks "github.com/metacubex/sing-shadowsocks2" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/uot" ) @@ -146,7 +146,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -294,7 +294,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint) - restlsConfig.SessionTicketsDisabled = true if err != nil { return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) } diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index cd6854af..0f03f86d 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -80,7 +80,7 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia if err != nil { return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) diff --git a/adapter/outbound/singmux.go b/adapter/outbound/singmux.go index 9a977318..c9f50ce9 100644 --- a/adapter/outbound/singmux.go +++ b/adapter/outbound/singmux.go @@ -3,7 +3,6 @@ package outbound import ( "context" "errors" - "net" "runtime" CN "github.com/Dreamacro/clash/common/net" @@ -15,14 +14,13 @@ import ( mux "github.com/sagernet/sing-mux" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" ) type SingMux struct { C.ProxyAdapter base ProxyBase client *mux.Client - dialer *muxSingDialer + dialer proxydialer.SingDialer onlyTcp bool } @@ -41,27 +39,9 @@ type ProxyBase interface { DialOptions(opts ...dialer.Option) []dialer.Option } -type muxSingDialer struct { - dialer dialer.Dialer - proxy C.ProxyAdapter - statistic bool -} - -var _ N.Dialer = (*muxSingDialer)(nil) - -func (d *muxSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic) - return cDialer.DialContext(ctx, network, destination.String()) -} - -func (d *muxSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - var cDialer C.Dialer = proxydialer.New(d.proxy, d.dialer, d.statistic) - return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort()) -} - func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { options := s.base.DialOptions(opts...) - s.dialer.dialer = dialer.NewDialer(options...) + s.dialer.SetDialer(dialer.NewDialer(options...)) c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddr(metadata.RemoteAddress())) if err != nil { return nil, err @@ -74,7 +54,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) } options := s.base.DialOptions(opts...) - s.dialer.dialer = dialer.NewDialer(options...) + s.dialer.SetDialer(dialer.NewDialer(options...)) // sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr if !metadata.Resolved() { @@ -114,7 +94,7 @@ func closeSingMux(s *SingMux) { } func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) { - singDialer := &muxSingDialer{dialer: dialer.NewDialer(), proxy: proxy, statistic: option.Statistic} + singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic) client, err := mux.NewClient(mux.Options{ Dialer: singDialer, Protocol: option.Protocol, diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index d0b9e748..16405fcf 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -6,6 +6,7 @@ import ( "net" "strconv" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" @@ -93,7 +94,7 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %w", s.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -121,7 +122,7 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, err } - tcpKeepAlive(c) + N.TCPKeepAlive(c) c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) err = snell.WriteUDPHeader(c, s.version) @@ -207,7 +208,7 @@ func NewSnell(option SnellOption) (*Snell, error) { return nil, err } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil }) } diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index f451cd1a..864500c5 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -9,9 +9,10 @@ import ( "net" "strconv" + N "github.com/Dreamacro/clash/common/net" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" - tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) @@ -80,7 +81,7 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -126,7 +127,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, safeConnClose(c, err) }(c) - tcpKeepAlive(c) + N.TCPKeepAlive(c) var user *socks5.User if ss.user != "" { user = &socks5.User{ @@ -155,7 +156,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, bindUDPAddr.IP = serverAddr.IP } - pc, err := dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", bindUDPAddr.AddrPort().Addr()), "", ss.Base.DialOptions(opts...)...) + pc, err := cDialer.ListenPacket(ctx, "udp", "", bindUDPAddr.AddrPort()) if err != nil { return } @@ -179,13 +180,10 @@ func NewSocks5(option Socks5Option) (*Socks5, error) { ServerName: option.Server, } - if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - var err error - if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { - return nil, err - } + var err error + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) + if err != nil { + return nil, err } } diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index ec420bf3..337f2a38 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -8,6 +8,8 @@ import ( "net/http" "strconv" + N "github.com/Dreamacro/clash/common/net" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" tlsC "github.com/Dreamacro/clash/component/tls" @@ -131,7 +133,7 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) @@ -184,7 +186,7 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me defer func(c net.Conn) { safeConnClose(c, err) }(c) - tcpKeepAlive(c) + N.TCPKeepAlive(c) c, err = t.plainStream(ctx, c) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) @@ -268,7 +270,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return c, nil } @@ -279,13 +281,10 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { ServerName: tOption.ServerName, } - if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - var err error - if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { - return nil, err - } + var err error + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) + if err != nil { + return nil, err } t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig) diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index c10a853a..93e49dc7 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -2,25 +2,25 @@ package outbound import ( "context" - "crypto/sha256" "crypto/tls" - "encoding/hex" - "encoding/pem" + "errors" "fmt" "math" "net" - "os" "strconv" "time" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" - tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/tuic" "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/uot" ) type Tuic struct { @@ -59,6 +59,9 @@ type TuicOption struct { DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"` SNI string `proxy:"sni,omitempty"` + + UDPOverStream bool `proxy:"udp-over-stream,omitempty"` + UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"` } // DialContext implements C.ProxyAdapter @@ -82,6 +85,32 @@ func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, op // ListenPacketWithDialer implements C.ProxyAdapter func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { + if t.option.UDPOverStream { + uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion)) + uotMetadata := *metadata + uotMetadata.Host = uotDestination.Fqdn + uotMetadata.DstPort = uotDestination.Port + c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata) + if err != nil { + return nil, err + } + + // tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr + if !metadata.Resolved() { + ip, err := resolver.ResolveIP(ctx, metadata.Host) + if err != nil { + return nil, errors.New("can't resolve ip") + } + metadata.DstIP = ip + } + + destination := M.SocksaddrFromNet(metadata.UDPAddr()) + if t.option.UDPOverStreamVersion == uot.LegacyVersion { + return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), t), nil + } else { + return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil + } + } pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer) if err != nil { return nil, err @@ -129,37 +158,10 @@ func NewTuic(option TuicOption) (*Tuic, error) { tlsConfig.ServerName = option.SNI } - var bs []byte var err error - if len(option.CustomCA) > 0 { - bs, err = os.ReadFile(option.CustomCA) - if err != nil { - return nil, fmt.Errorf("tuic %s load ca error: %w", addr, err) - } - } else if option.CustomCAString != "" { - bs = []byte(option.CustomCAString) - } - - if len(bs) > 0 { - block, _ := pem.Decode(bs) - if block == nil { - return nil, fmt.Errorf("CA cert is not PEM") - } - - fpBytes := sha256.Sum256(block.Bytes) - if len(option.Fingerprint) == 0 { - option.Fingerprint = hex.EncodeToString(fpBytes[:]) - } - } - - if len(option.Fingerprint) != 0 { - var err error - tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) - if err != nil { - return nil, err - } - } else { - tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) + tlsConfig, err = ca.GetTLSConfig(tlsConfig, option.Fingerprint, option.CustomCA, option.CustomCAString) + if err != nil { + return nil, err } if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array @@ -239,6 +241,14 @@ func NewTuic(option TuicOption) (*Tuic, error) { tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config } + switch option.UDPOverStreamVersion { + case uot.Version, uot.LegacyVersion: + case 0: + option.UDPOverStreamVersion = uot.LegacyVersion + default: + return nil, fmt.Errorf("tuic %s unknown udp over stream protocol version: %d", addr, option.UDPOverStreamVersion) + } + t := &Tuic{ Base: &Base{ name: option.Name, @@ -282,6 +292,10 @@ func NewTuic(option TuicOption) (*Tuic, error) { t.client = tuic.NewPoolClientV4(clientOption) } else { + maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize + if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 { + maxUdpRelayPacketSize = tuic.MaxFragSizeV5 + } clientOption := &tuic.ClientOptionV5{ TlsConfig: tlsConfig, QuicConfig: quicConfig, @@ -290,7 +304,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { UdpRelayMode: udpRelayMode, CongestionController: option.CongestionController, ReduceRtt: option.ReduceRtt, - MaxUdpRelayPacketSize: option.MaxUdpRelayPacketSize, + MaxUdpRelayPacketSize: maxUdpRelayPacketSize, MaxOpenStreams: clientMaxOpenStreams, CWND: option.CWND, } diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index 7f3ec4c3..b048cd8b 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -4,10 +4,12 @@ import ( "bytes" "context" "crypto/tls" + "fmt" "net" "net/netip" + "regexp" + "strconv" "sync" - "time" "github.com/Dreamacro/clash/component/resolver" C "github.com/Dreamacro/clash/constant" @@ -19,13 +21,6 @@ var ( once sync.Once ) -func tcpKeepAlive(c net.Conn) { - if tcp, ok := c.(*net.TCPConn); ok { - _ = tcp.SetKeepAlive(true) - _ = tcp.SetKeepAlivePeriod(30 * time.Second) - } -} - func getClientSessionCache() tls.ClientSessionCache { once.Do(func() { globalClientSessionCache = tls.NewLRUClientSessionCache(128) @@ -128,3 +123,41 @@ func safeConnClose(c net.Conn, err error) { _ = c.Close() } } + +var rateStringRegexp = regexp.MustCompile(`^(\d+)\s*([KMGT]?)([Bb])ps$`) + +func StringToBps(s string) uint64 { + if s == "" { + return 0 + } + + // when have not unit, use Mbps + if v, err := strconv.Atoi(s); err == nil { + return StringToBps(fmt.Sprintf("%d Mbps", v)) + } + + m := rateStringRegexp.FindStringSubmatch(s) + if m == nil { + return 0 + } + var n uint64 + switch m[2] { + case "K": + n = 1 << 10 + case "M": + n = 1 << 20 + case "G": + n = 1 << 30 + case "T": + n = 1 << 40 + default: + n = 1 + } + v, _ := strconv.ParseUint(m[1], 10, 64) + n = v * n + if m[3] == "b" { + // Bits, need to convert to bytes + n = n >> 3 + } + return n +} diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 83ce4e57..037f3367 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -15,6 +15,7 @@ import ( "github.com/Dreamacro/clash/common/convert" N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/utils" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/resolver" @@ -57,6 +58,7 @@ type VlessOption struct { UUID string `proxy:"uuid"` Flow string `proxy:"flow,omitempty"` TLS bool `proxy:"tls,omitempty"` + ALPN []string `proxy:"alpn,omitempty"` UDP bool `proxy:"udp,omitempty"` PacketAddr bool `proxy:"packet-addr,omitempty"` XUDP bool `proxy:"xudp,omitempty"` @@ -109,13 +111,9 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M NextProtos: []string{"http/1.1"}, } - if len(v.option.Fingerprint) == 0 { - wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint) - if err != nil { - return nil, err - } + wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint) + if err != nil { + return nil, err } if v.option.ServerName != "" { @@ -211,6 +209,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne FingerPrint: v.option.Fingerprint, ClientFingerprint: v.option.ClientFingerprint, Reality: v.realityConfig, + NextProtos: v.option.ALPN, } if isH2 { @@ -261,7 +260,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -326,7 +325,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -576,7 +575,7 @@ func NewVless(option VlessOption) (*Vless, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return c, nil } @@ -590,7 +589,7 @@ func NewVless(option VlessOption) (*Vless, error) { } var tlsConfig *tls.Config if option.TLS { - tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{ + tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, }) diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 8a94c082..db654580 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -13,11 +13,13 @@ import ( N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/utils" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/proxydialer" "github.com/Dreamacro/clash/component/resolver" tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/ntp" "github.com/Dreamacro/clash/transport/gun" clashVMess "github.com/Dreamacro/clash/transport/vmess" @@ -52,6 +54,7 @@ type VmessOption struct { UDP bool `proxy:"udp,omitempty"` Network string `proxy:"network,omitempty"` TLS bool `proxy:"tls,omitempty"` + ALPN []string `proxy:"alpn,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"` ServerName string `proxy:"servername,omitempty"` @@ -125,12 +128,9 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M NextProtos: []string{"http/1.1"}, } - if len(v.option.Fingerprint) == 0 { - wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil { - return nil, err - } + wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint) + if err != nil { + return nil, err } if v.option.ServerName != "" { @@ -149,6 +149,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M SkipCertVerify: v.option.SkipCertVerify, ClientFingerprint: v.option.ClientFingerprint, Reality: v.realityConfig, + NextProtos: v.option.ALPN, } if v.option.ServerName != "" { @@ -205,6 +206,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M SkipCertVerify: v.option.SkipCertVerify, ClientFingerprint: v.option.ClientFingerprint, Reality: v.realityConfig, + NextProtos: v.option.ALPN, } if v.option.ServerName != "" { @@ -304,7 +306,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -365,7 +367,7 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) defer func(c net.Conn) { safeConnClose(c, err) }(c) @@ -413,6 +415,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { if option.AuthenticatedLength { options = append(options, vmess.ClientWithAuthenticatedLength()) } + options = append(options, vmess.ClientWithTimeFunc(ntp.Now)) client, err := vmess.NewClient(option.UUID, security, option.AlterID, options...) if err != nil { return nil, err @@ -464,7 +467,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { if err != nil { return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) } - tcpKeepAlive(c) + N.TCPKeepAlive(c) return c, nil } @@ -478,7 +481,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { } var tlsConfig *tls.Config if option.TLS { - tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{ + tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, }) diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index e6738596..6a11a234 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -27,7 +27,6 @@ import ( "github.com/sagernet/sing/common/debug" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/wireguard-go/device" ) @@ -36,7 +35,7 @@ type WireGuard struct { bind *wireguard.ClientBind device *device.Device tunDevice wireguard.Device - dialer *wgSingDialer + dialer proxydialer.SingDialer startOnce sync.Once startErr error resolver *dns.Resolver @@ -70,37 +69,6 @@ type WireGuardPeerOption struct { AllowedIPs []string `proxy:"allowed-ips,omitempty"` } -type wgSingDialer struct { - dialer dialer.Dialer - proxyName string -} - -var _ N.Dialer = (*wgSingDialer)(nil) - -func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - var cDialer C.Dialer = d.dialer - if len(d.proxyName) > 0 { - pd, err := proxydialer.NewByName(d.proxyName, d.dialer) - if err != nil { - return nil, err - } - cDialer = pd - } - return cDialer.DialContext(ctx, network, destination.String()) -} - -func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - var cDialer C.Dialer = d.dialer - if len(d.proxyName) > 0 { - pd, err := proxydialer.NewByName(d.proxyName, d.dialer) - if err != nil { - return nil, err - } - cDialer = pd - } - return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort()) -} - type wgSingErrorHandler struct { name string } @@ -168,7 +136,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, - dialer: &wgSingDialer{dialer: dialer.NewDialer(), proxyName: option.DialerProxy}, + dialer: proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), } runtime.SetFinalizer(outbound, closeWireGuard) @@ -302,7 +270,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { if err != nil { return nil, E.Cause(err, "create WireGuard device") } - outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{ + outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{ Verbosef: func(format string, args ...interface{}) { log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...))) }, @@ -355,7 +323,7 @@ func closeWireGuard(w *WireGuard) { func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { options := w.Base.DialOptions(opts...) - w.dialer.dialer = dialer.NewDialer(options...) + w.dialer.SetDialer(dialer.NewDialer(options...)) var conn net.Conn w.startOnce.Do(func() { w.startErr = w.tunDevice.Start() @@ -387,7 +355,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { options := w.Base.DialOptions(opts...) - w.dialer.dialer = dialer.NewDialer(options...) + w.dialer.SetDialer(dialer.NewDialer(options...)) var pc net.PacketConn w.startOnce.Do(func() { w.startErr = w.tunDevice.Start() diff --git a/adapter/outboundgroup/util.go b/adapter/outboundgroup/util.go index 85373a1f..84216377 100644 --- a/adapter/outboundgroup/util.go +++ b/adapter/outboundgroup/util.go @@ -1,17 +1,5 @@ package outboundgroup -import ( - "net" - "time" -) - -func tcpKeepAlive(c net.Conn) { - if tcp, ok := c.(*net.TCPConn); ok { - _ = tcp.SetKeepAlive(true) - _ = tcp.SetKeepAlivePeriod(30 * time.Second) - } -} - type SelectAble interface { Set(string) error ForceSet(name string) diff --git a/adapter/parser.go b/adapter/parser.go index a561a1ed..eeb0fd59 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -92,6 +92,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { break } proxy, err = outbound.NewHysteria(*hyOption) + case "hysteria2": + hyOption := &outbound.Hysteria2Option{} + err = decoder.Decode(mapping, hyOption) + if err != nil { + break + } + proxy, err = outbound.NewHysteria2(*hyOption) case "wireguard": wgOption := &outbound.WireGuardOption{} err = decoder.Decode(mapping, wgOption) @@ -106,6 +113,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { break } proxy, err = outbound.NewTuic(*tuicOption) + case "direct": + directOption := &outbound.DirectOption{} + err = decoder.Decode(mapping, directOption) + if err != nil { + break + } + proxy = outbound.NewDirectWithOption(*directOption) default: return nil, fmt.Errorf("unsupport proxy type: %s", proxyType) } diff --git a/common/buf/sing.go b/common/buf/sing.go index 4585bf74..d204ba11 100644 --- a/common/buf/sing.go +++ b/common/buf/sing.go @@ -14,13 +14,6 @@ var NewSize = buf.NewSize var With = buf.With var As = buf.As -var KeepAlive = common.KeepAlive - -//go:norace -func Dup[T any](obj T) T { - return common.Dup(obj) -} - var ( Must = common.Must Error = common.Error diff --git a/common/cache/lrucache.go b/common/cache/lrucache.go index 1373b0be..2f9d3e79 100644 --- a/common/cache/lrucache.go +++ b/common/cache/lrucache.go @@ -7,6 +7,8 @@ import ( "time" "github.com/Dreamacro/clash/common/generics/list" + + "github.com/samber/lo" ) // Option is part of Functional Options Pattern @@ -87,7 +89,7 @@ func (c *LruCache[K, V]) Get(key K) (V, bool) { el := c.get(key) if el == nil { - return getZero[V](), false + return lo.Empty[V](), false } value := el.value @@ -119,7 +121,7 @@ func (c *LruCache[K, V]) GetWithExpire(key K) (V, time.Time, bool) { el := c.get(key) if el == nil { - return getZero[V](), time.Time{}, false + return lo.Empty[V](), time.Time{}, false } return el.value, time.Unix(el.expires, 0), true @@ -259,8 +261,3 @@ type entry[K comparable, V any] struct { value V expires int64 } - -func getZero[T any]() T { - var result T - return result -} diff --git a/common/cmd/cmd_test.go b/common/cmd/cmd_test.go index 4bba6def..b124a22d 100644 --- a/common/cmd/cmd_test.go +++ b/common/cmd/cmd_test.go @@ -21,7 +21,7 @@ func TestSplitArgs(t *testing.T) { func TestExecCmd(t *testing.T) { if runtime.GOOS == "windows" { - _, err := ExecCmd("dir") + _, err := ExecCmd("cmd -c 'dir'") assert.Nil(t, err) return } diff --git a/common/convert/converter.go b/common/convert/converter.go index b67918db..5a618f42 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -50,7 +50,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { hysteria["port"] = urlHysteria.Port() hysteria["sni"] = query.Get("peer") hysteria["obfs"] = query.Get("obfs") - hysteria["alpn"] = []string{query.Get("alpn")} + if alpn := query.Get("alpn"); alpn != "" { + hysteria["alpn"] = strings.Split(alpn, ",") + } hysteria["auth_str"] = query.Get("auth") hysteria["protocol"] = query.Get("protocol") up := query.Get("up") @@ -66,6 +68,79 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { hysteria["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure")) proxies = append(proxies, hysteria) + case "hysteria2": + urlHysteria2, err := url.Parse(line) + if err != nil { + continue + } + + query := urlHysteria2.Query() + name := uniqueName(names, urlHysteria2.Fragment) + hysteria2 := make(map[string]any, 20) + + hysteria2["name"] = name + hysteria2["type"] = scheme + hysteria2["server"] = urlHysteria2.Hostname() + if port := urlHysteria2.Port(); port != "" { + hysteria2["port"] = port + } else { + hysteria2["port"] = "443" + } + hysteria2["obfs"] = query.Get("obfs") + hysteria2["obfs-password"] = query.Get("obfs-password") + hysteria2["sni"] = query.Get("sni") + hysteria2["skip-cert-verify"], _ = strconv.ParseBool(query.Get("insecure")) + if alpn := query.Get("alpn"); alpn != "" { + hysteria2["alpn"] = strings.Split(alpn, ",") + } + if auth := urlHysteria2.User.String(); auth != "" { + hysteria2["password"] = auth + } + hysteria2["fingerprint"] = query.Get("pinSHA256") + hysteria2["down"] = query.Get("down") + hysteria2["up"] = query.Get("up") + + proxies = append(proxies, hysteria2) + case "tuic": + // A temporary unofficial TUIC share link standard + // Modified from https://github.com/daeuniverse/dae/discussions/182 + // Changes: + // 1. Support TUICv4, just replace uuid:password with token + // 2. Remove `allow_insecure` field + urlTUIC, err := url.Parse(line) + if err != nil { + continue + } + query := urlTUIC.Query() + + tuic := make(map[string]any, 20) + tuic["name"] = uniqueName(names, urlTUIC.Fragment) + tuic["type"] = scheme + tuic["server"] = urlTUIC.Hostname() + tuic["port"] = urlTUIC.Port() + tuic["udp"] = true + password, v5 := urlTUIC.User.Password() + if v5 { + tuic["uuid"] = urlTUIC.User.Username() + tuic["password"] = password + } else { + tuic["token"] = urlTUIC.User.Username() + } + if cc := query.Get("congestion_control"); cc != "" { + tuic["congestion-controller"] = cc + } + if alpn := query.Get("alpn"); alpn != "" { + tuic["alpn"] = strings.Split(alpn, ",") + } + if sni := query.Get("sni"); sni != "" { + tuic["sni"] = sni + } + if query.Get("disable_sni") == "1" { + tuic["disable-sni"] = true + } + if udpRelayMode := query.Get("udp_relay_mode"); udpRelayMode != "" { + tuic["udp-relay-mode"] = udpRelayMode + } case "trojan": urlTrojan, err := url.Parse(line) @@ -86,10 +161,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { trojan["udp"] = true trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure")) - sni := query.Get("sni") - if sni != "" { + if sni := query.Get("sni"); sni != "" { trojan["sni"] = sni } + if alpn := query.Get("alpn"); alpn != "" { + trojan["alpn"] = strings.Split(alpn, ",") + } network := strings.ToLower(query.Get("type")) if network != "" { @@ -217,6 +294,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if strings.HasSuffix(tls, "tls") { vmess["tls"] = true } + if alpn, ok := values["alpn"].(string); ok { + vmess["alpn"] = strings.Split(alpn, ",") + } } switch network { @@ -332,6 +412,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { } } proxies = append(proxies, ss) + case "ssr": dcBuf, err := encRaw.DecodeString(body) if err != nil { diff --git a/common/convert/converter_test.go b/common/convert/converter_test.go new file mode 100644 index 00000000..83b41c4c --- /dev/null +++ b/common/convert/converter_test.go @@ -0,0 +1,35 @@ +package convert + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// https://v2.hysteria.network/zh/docs/developers/URI-Scheme/ +func TestConvertsV2Ray_normal(t *testing.T) { + hy2test := "hysteria2://letmein@example.com:8443/?insecure=1&obfs=salamander&obfs-password=gawrgura&pinSHA256=deadbeef&sni=real.example.com&up=114&down=514&alpn=h3,h4#hy2test" + + expected := []map[string]interface{}{ + { + "name": "hy2test", + "type": "hysteria2", + "server": "example.com", + "port": "8443", + "sni": "real.example.com", + "obfs": "salamander", + "obfs-password": "gawrgura", + "alpn": []string{"h3", "h4"}, + "password": "letmein", + "up": "114", + "down": "514", + "skip-cert-verify": true, + "fingerprint": "deadbeef", + }, + } + + proxies, err := ConvertsV2Ray([]byte(hy2test)) + + assert.Nil(t, err) + assert.Equal(t, expected, proxies) +} diff --git a/common/convert/v.go b/common/convert/v.go index 23949aab..2d8cf732 100644 --- a/common/convert/v.go +++ b/common/convert/v.go @@ -24,8 +24,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m proxy["port"] = url.Port() proxy["uuid"] = url.User.Username() proxy["udp"] = true - proxy["skip-cert-verify"] = false - proxy["tls"] = false tls := strings.ToLower(query.Get("security")) if strings.HasSuffix(tls, "tls") || tls == "reality" { proxy["tls"] = true @@ -34,6 +32,9 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m } else { proxy["client-fingerprint"] = fingerprint } + if alpn := query.Get("alpn"); alpn != "" { + proxy["alpn"] = strings.Split(alpn, ",") + } } if sni := query.Get("sni"); sni != "" { proxy["servername"] = sni diff --git a/common/net/tcpip.go b/common/net/tcpip.go index a84e7e4c..0499e54c 100644 --- a/common/net/tcpip.go +++ b/common/net/tcpip.go @@ -4,8 +4,11 @@ import ( "fmt" "net" "strings" + "time" ) +var KeepAliveInterval = 15 * time.Second + func SplitNetworkType(s string) (string, string, error) { var ( shecme string @@ -44,3 +47,10 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) { host, port, err = net.SplitHostPort(temp) return } + +func TCPKeepAlive(c net.Conn) { + if tcp, ok := c.(*net.TCPConn); ok { + _ = tcp.SetKeepAlive(true) + _ = tcp.SetKeepAlivePeriod(KeepAliveInterval) + } +} diff --git a/common/picker/picker_test.go b/common/picker/picker_test.go index 17b823cb..4c1c9ebe 100644 --- a/common/picker/picker_test.go +++ b/common/picker/picker_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/samber/lo" "github.com/stretchr/testify/assert" ) @@ -15,7 +16,7 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err case <-timer.C: return input, nil case <-ctx.Done(): - return getZero[T](), ctx.Err() + return lo.Empty[T](), ctx.Err() } } } @@ -35,11 +36,6 @@ func TestPicker_Timeout(t *testing.T) { picker.Go(sleepAndSend(ctx, 20, 1)) number := picker.Wait() - assert.Equal(t, number, getZero[int]()) + assert.Equal(t, number, lo.Empty[int]()) assert.NotNil(t, picker.Error()) } - -func getZero[T any]() T { - var result T - return result -} diff --git a/common/queue/queue.go b/common/queue/queue.go index 4755cb35..cb58e2f5 100644 --- a/common/queue/queue.go +++ b/common/queue/queue.go @@ -2,6 +2,8 @@ package queue import ( "sync" + + "github.com/samber/lo" ) // Queue is a simple concurrent safe queue @@ -24,7 +26,7 @@ func (q *Queue[T]) Put(items ...T) { // Pop returns the head of items. func (q *Queue[T]) Pop() T { if len(q.items) == 0 { - return GetZero[T]() + return lo.Empty[T]() } q.lock.Lock() @@ -37,7 +39,7 @@ func (q *Queue[T]) Pop() T { // Last returns the last of item. func (q *Queue[T]) Last() T { if len(q.items) == 0 { - return GetZero[T]() + return lo.Empty[T]() } q.lock.RLock() @@ -69,8 +71,3 @@ func New[T any](hint int64) *Queue[T] { items: make([]T, 0, hint), } } - -func GetZero[T any]() T { - var result T - return result -} diff --git a/component/auth/auth.go b/component/auth/auth.go index 9d30b927..9b351606 100644 --- a/component/auth/auth.go +++ b/component/auth/auth.go @@ -1,7 +1,7 @@ package auth import ( - "sync" + "github.com/puzpuzpuz/xsync/v2" ) type Authenticator interface { @@ -15,7 +15,7 @@ type AuthUser struct { } type inMemoryAuthenticator struct { - storage *sync.Map + storage *xsync.MapOf[string, string] usernames []string } @@ -31,13 +31,13 @@ func NewAuthenticator(users []AuthUser) Authenticator { return nil } - au := &inMemoryAuthenticator{storage: &sync.Map{}} + au := &inMemoryAuthenticator{storage: xsync.NewMapOf[string]()} for _, user := range users { au.storage.Store(user.User, user.Pass) } usernames := make([]string, 0, len(users)) - au.storage.Range(func(key, value any) bool { - usernames = append(usernames, key.(string)) + au.storage.Range(func(key string, value string) bool { + usernames = append(usernames, key) return true }) au.usernames = usernames diff --git a/component/tls/config.go b/component/ca/config.go similarity index 63% rename from component/tls/config.go rename to component/ca/config.go index d7382f7c..03fb007c 100644 --- a/component/tls/config.go +++ b/component/ca/config.go @@ -1,4 +1,4 @@ -package tls +package ca import ( "bytes" @@ -8,12 +8,13 @@ import ( "encoding/hex" "errors" "fmt" + "os" "strings" "sync" ) var trustCerts []*x509.Certificate -var certPool *x509.CertPool +var globalCertPool *x509.CertPool var mutex sync.RWMutex var errNotMatch = errors.New("certificate fingerprints do not match") @@ -33,12 +34,12 @@ func AddCertificate(certificate string) error { func initializeCertPool() { var err error - certPool, err = x509.SystemCertPool() + globalCertPool, err = x509.SystemCertPool() if err != nil { - certPool = x509.NewCertPool() + globalCertPool = x509.NewCertPool() } for _, cert := range trustCerts { - certPool.AddCert(cert) + globalCertPool.AddCert(cert) } } @@ -53,15 +54,15 @@ func getCertPool() *x509.CertPool { if len(trustCerts) == 0 { return nil } - if certPool == nil { + if globalCertPool == nil { mutex.Lock() defer mutex.Unlock() - if certPool != nil { - return certPool + if globalCertPool != nil { + return globalCertPool } initializeCertPool() } - return certPool + return globalCertPool } func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { @@ -94,29 +95,49 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) { return (*[32]byte)(fpByte), nil } -func GetDefaultTLSConfig() *tls.Config { - return GetGlobalTLSConfig(nil) +// GetTLSConfig specified fingerprint, customCA and customCAString +func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (*tls.Config, error) { + if tlsConfig == nil { + tlsConfig = &tls.Config{} + } + var certificate []byte + var err error + if len(customCA) > 0 { + certificate, err = os.ReadFile(customCA) + if err != nil { + return nil, fmt.Errorf("load ca error: %w", err) + } + } else if customCAString != "" { + certificate = []byte(customCAString) + } + if len(certificate) > 0 { + certPool := x509.NewCertPool() + if !certPool.AppendCertsFromPEM(certificate) { + return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate) + } + tlsConfig.RootCAs = certPool + } else { + tlsConfig.RootCAs = getCertPool() + } + if len(fingerprint) > 0 { + var fingerprintBytes *[32]byte + fingerprintBytes, err = convertFingerprint(fingerprint) + if err != nil { + return nil, err + } + tlsConfig = GetGlobalTLSConfig(tlsConfig) + tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) + tlsConfig.InsecureSkipVerify = true + } + return tlsConfig, nil } // GetSpecifiedFingerprintTLSConfig specified fingerprint func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) (*tls.Config, error) { - if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { - return nil, err - } else { - tlsConfig = GetGlobalTLSConfig(tlsConfig) - tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) - tlsConfig.InsecureSkipVerify = true - return tlsConfig, nil - } + return GetTLSConfig(tlsConfig, fingerprint, "", "") } func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config { - certPool := getCertPool() - if tlsConfig == nil { - return &tls.Config{ - RootCAs: certPool, - } - } - tlsConfig.RootCAs = certPool + tlsConfig, _ = GetTLSConfig(tlsConfig, "", "", "") return tlsConfig } diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 89c7564a..0cfa1b6c 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -162,14 +162,22 @@ func concurrentDualStackDialContext(ctx context.Context, network string, ips []n func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { ipv4s, ipv6s := resolver.SortationAddr(ips) - preferIPVersion := opt.prefer + if len(ipv4s) == 0 && len(ipv6s) == 0 { + return nil, ErrorNoIpAddress + } + preferIPVersion := opt.prefer fallbackTicker := time.NewTicker(fallbackTimeout) defer fallbackTicker.Stop() + results := make(chan dialResult) returned := make(chan struct{}) defer close(returned) + + var wg sync.WaitGroup + racer := func(ips []netip.Addr, isPrimary bool) { + defer wg.Done() result := dialResult{isPrimary: isPrimary} defer func() { select { @@ -182,18 +190,36 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, }() result.Conn, result.error = dialFn(ctx, network, ips, port, opt) } - go racer(ipv4s, preferIPVersion != 6) - go racer(ipv6s, preferIPVersion != 4) + + if len(ipv4s) != 0 { + wg.Add(1) + go racer(ipv4s, preferIPVersion != 6) + } + + if len(ipv6s) != 0 { + wg.Add(1) + go racer(ipv6s, preferIPVersion != 4) + } + + go func() { + wg.Wait() + close(results) + }() + var fallback dialResult var errs []error - for i := 0; i < 2; { + +loop: + for { select { case <-fallbackTicker.C: if fallback.error == nil && fallback.Conn != nil { return fallback.Conn, nil } - case res := <-results: - i++ + case res, ok := <-results: + if !ok { + break loop + } if res.error == nil { if res.isPrimary { return res.Conn, nil @@ -208,6 +234,7 @@ func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, } } } + if fallback.error == nil && fallback.Conn != nil { return fallback.Conn, nil } diff --git a/component/http/http.go b/component/http/http.go index bcede09f..8e682e94 100644 --- a/component/http/http.go +++ b/component/http/http.go @@ -2,6 +2,7 @@ package http import ( "context" + "crypto/tls" "io" "net" "net/http" @@ -9,15 +10,13 @@ import ( "strings" "time" - "github.com/Dreamacro/clash/component/tls" + "github.com/Dreamacro/clash/component/ca" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/listener/inner" ) -const ( - UA = "clash.meta" -) - func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { + UA := C.UA method = strings.ToUpper(method) urlRes, err := URL.Parse(url) if err != nil { @@ -60,7 +59,7 @@ func HttpRequest(ctx context.Context, url, method string, header map[string][]st return d.DialContext(ctx, network, address) } }, - TLSClientConfig: tls.GetDefaultTLSConfig(), + TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}), } client := http.Client{Transport: transport} diff --git a/component/nat/table.go b/component/nat/table.go index adc6eace..df258dc2 100644 --- a/component/nat/table.go +++ b/component/nat/table.go @@ -5,23 +5,28 @@ import ( "sync" C "github.com/Dreamacro/clash/constant" + + "github.com/puzpuzpuz/xsync/v2" ) type Table struct { - mapping sync.Map + mapping *xsync.MapOf[string, *Entry] + lockMap *xsync.MapOf[string, *sync.Cond] } type Entry struct { PacketConn C.PacketConn WriteBackProxy C.WriteBackProxy - LocalUDPConnMap sync.Map + LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn] + LocalLockMap *xsync.MapOf[string, *sync.Cond] } func (t *Table) Set(key string, e C.PacketConn, w C.WriteBackProxy) { t.mapping.Store(key, &Entry{ PacketConn: e, WriteBackProxy: w, - LocalUDPConnMap: sync.Map{}, + LocalUDPConnMap: xsync.NewMapOf[*net.UDPConn](), + LocalLockMap: xsync.NewMapOf[*sync.Cond](), }) } @@ -34,15 +39,19 @@ func (t *Table) Get(key string) (C.PacketConn, C.WriteBackProxy) { } func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { - item, loaded := t.mapping.LoadOrStore(key, sync.NewCond(&sync.Mutex{})) - return item.(*sync.Cond), loaded + item, loaded := t.lockMap.LoadOrCompute(key, makeLock) + return item, loaded } func (t *Table) Delete(key string) { t.mapping.Delete(key) } -func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn { +func (t *Table) DeleteLock(lockKey string) { + t.lockMap.Delete(lockKey) +} + +func (t *Table) GetForLocalConn(lAddr, rAddr string) *net.UDPConn { entry, exist := t.getEntry(lAddr) if !exist { return nil @@ -51,10 +60,10 @@ func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn { if !exist { return nil } - return item.(*net.UDPConn) + return item } -func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool { +func (t *Table) AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool { entry, exist := t.getEntry(lAddr) if !exist { return false @@ -63,7 +72,7 @@ func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool { return true } -func (t *Table) RangeLocalConn(lAddr string, f func(key, value any) bool) { +func (t *Table) RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) { entry, exist := t.getEntry(lAddr) if !exist { return @@ -76,11 +85,11 @@ func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool if !loaded { return nil, false } - item, loaded := entry.LocalUDPConnMap.LoadOrStore(key, sync.NewCond(&sync.Mutex{})) - return item.(*sync.Cond), loaded + item, loaded := entry.LocalLockMap.LoadOrCompute(key, makeLock) + return item, loaded } -func (t *Table) DeleteLocalConnMap(lAddr, key string) { +func (t *Table) DeleteForLocalConn(lAddr, key string) { entry, loaded := t.getEntry(lAddr) if !loaded { return @@ -88,17 +97,26 @@ func (t *Table) DeleteLocalConnMap(lAddr, key string) { entry.LocalUDPConnMap.Delete(key) } -func (t *Table) getEntry(key string) (*Entry, bool) { - item, ok := t.mapping.Load(key) - // This should not happen usually since this function called after PacketConn created - if !ok { - return nil, false +func (t *Table) DeleteLockForLocalConn(lAddr, key string) { + entry, loaded := t.getEntry(lAddr) + if !loaded { + return } - entry, ok := item.(*Entry) - return entry, ok + entry.LocalLockMap.Delete(key) +} + +func (t *Table) getEntry(key string) (*Entry, bool) { + return t.mapping.Load(key) +} + +func makeLock() *sync.Cond { + return sync.NewCond(&sync.Mutex{}) } // New return *Cache func New() *Table { - return &Table{} + return &Table{ + mapping: xsync.NewMapOf[*Entry](), + lockMap: xsync.NewMapOf[*sync.Cond](), + } } diff --git a/component/proxydialer/sing.go b/component/proxydialer/sing.go new file mode 100644 index 00000000..9b116527 --- /dev/null +++ b/component/proxydialer/sing.go @@ -0,0 +1,82 @@ +package proxydialer + +import ( + "context" + "net" + + C "github.com/Dreamacro/clash/constant" + + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type SingDialer interface { + N.Dialer + SetDialer(dialer C.Dialer) +} + +type singDialer proxyDialer + +var _ N.Dialer = (*singDialer)(nil) + +func (d *singDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return (*proxyDialer)(d).DialContext(ctx, network, destination.String()) +} + +func (d *singDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return (*proxyDialer)(d).ListenPacket(ctx, "udp", "", destination.AddrPort()) +} + +func (d *singDialer) SetDialer(dialer C.Dialer) { + (*proxyDialer)(d).dialer = dialer +} + +func NewSingDialer(proxy C.ProxyAdapter, dialer C.Dialer, statistic bool) SingDialer { + return (*singDialer)(&proxyDialer{ + proxy: proxy, + dialer: dialer, + statistic: statistic, + }) +} + +type byNameSingDialer struct { + dialer C.Dialer + proxyName string +} + +var _ N.Dialer = (*byNameSingDialer)(nil) + +func (d *byNameSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + var cDialer C.Dialer = d.dialer + if len(d.proxyName) > 0 { + pd, err := NewByName(d.proxyName, d.dialer) + if err != nil { + return nil, err + } + cDialer = pd + } + return cDialer.DialContext(ctx, network, destination.String()) +} + +func (d *byNameSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + var cDialer C.Dialer = d.dialer + if len(d.proxyName) > 0 { + pd, err := NewByName(d.proxyName, d.dialer) + if err != nil { + return nil, err + } + cDialer = pd + } + return cDialer.ListenPacket(ctx, "udp", "", destination.AddrPort()) +} + +func (d *byNameSingDialer) SetDialer(dialer C.Dialer) { + d.dialer = dialer +} + +func NewByNameSingDialer(proxyName string, dialer C.Dialer) SingDialer { + return &byNameSingDialer{ + dialer: dialer, + proxyName: proxyName, + } +} diff --git a/component/resolver/host.go b/component/resolver/host.go index 3b7e9a37..d6eb5873 100644 --- a/component/resolver/host.go +++ b/component/resolver/host.go @@ -4,6 +4,7 @@ import ( "errors" "net/netip" "strings" + _ "unsafe" "github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/component/trie" @@ -20,28 +21,39 @@ func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts { } } +// lookupStaticHost looks up the addresses and the canonical name for the given host from /etc/hosts. +// +//go:linkname lookupStaticHost net.lookupStaticHost +func lookupStaticHost(host string) ([]string, string) + // Return the search result and whether to match the parameter `isDomain` func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) { - value := h.DomainTrie.Search(domain) - if value == nil { - return nil, false - } - hostValue := value.Data() - for { - if isDomain && hostValue.IsDomain { - return &hostValue, true - } else { - if node := h.DomainTrie.Search(hostValue.Domain); node != nil { - hostValue = node.Data() + if value := h.DomainTrie.Search(domain); value != nil { + hostValue := value.Data() + for { + if isDomain && hostValue.IsDomain { + return &hostValue, true } else { - break + if node := h.DomainTrie.Search(hostValue.Domain); node != nil { + hostValue = node.Data() + } else { + break + } } } + if isDomain == hostValue.IsDomain { + return &hostValue, true + } + + return &hostValue, false } - if isDomain == hostValue.IsDomain { - return &hostValue, true + if !isDomain { + addr, _ := lookupStaticHost(domain) + if hostValue, err := NewHostValue(addr); err == nil { + return &hostValue, true + } } - return &hostValue, false + return nil, false } type HostValue struct { diff --git a/component/resolver/host_windows.go b/component/resolver/host_windows.go new file mode 100644 index 00000000..669f9547 --- /dev/null +++ b/component/resolver/host_windows.go @@ -0,0 +1,19 @@ +//go:build !go1.22 + +// a simple standard lib fix from: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc + +package resolver + +import ( + "golang.org/x/sys/windows" + _ "unsafe" +) + +//go:linkname testHookHostsPath net.testHookHostsPath +var testHookHostsPath string + +func init() { + if dir, err := windows.GetSystemDirectory(); err == nil { + testHookHostsPath = dir + "/Drivers/etc/hosts" + } +} diff --git a/component/resource/fetcher.go b/component/resource/fetcher.go index 4b905c7f..c92687b1 100644 --- a/component/resource/fetcher.go +++ b/component/resource/fetcher.go @@ -9,6 +9,8 @@ import ( types "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/log" + + "github.com/samber/lo" ) var ( @@ -65,7 +67,7 @@ func (f *Fetcher[V]) Initial() (V, error) { } if err != nil { - return getZero[V](), err + return lo.Empty[V](), err } var contents V @@ -85,18 +87,18 @@ func (f *Fetcher[V]) Initial() (V, error) { if err != nil { if !isLocal { - return getZero[V](), err + return lo.Empty[V](), err } // parse local file error, fallback to remote buf, err = f.vehicle.Read() if err != nil { - return getZero[V](), err + return lo.Empty[V](), err } contents, err = f.parser(buf) if err != nil { - return getZero[V](), err + return lo.Empty[V](), err } isLocal = false @@ -104,7 +106,7 @@ func (f *Fetcher[V]) Initial() (V, error) { if f.vehicle.Type() != types.File && !isLocal { if err := safeWrite(f.vehicle.Path(), buf); err != nil { - return getZero[V](), err + return lo.Empty[V](), err } } @@ -121,7 +123,7 @@ func (f *Fetcher[V]) Initial() (V, error) { func (f *Fetcher[V]) Update() (V, bool, error) { buf, err := f.vehicle.Read() if err != nil { - return getZero[V](), false, err + return lo.Empty[V](), false, err } now := time.Now() @@ -129,17 +131,17 @@ func (f *Fetcher[V]) Update() (V, bool, error) { if bytes.Equal(f.hash[:], hash[:]) { f.UpdatedAt = &now _ = os.Chtimes(f.vehicle.Path(), now, now) - return getZero[V](), true, nil + return lo.Empty[V](), true, nil } contents, err := f.parser(buf) if err != nil { - return getZero[V](), false, err + return lo.Empty[V](), false, err } if f.vehicle.Type() != types.File { if err := safeWrite(f.vehicle.Path(), buf); err != nil { - return getZero[V](), false, err + return lo.Empty[V](), false, err } } @@ -210,8 +212,3 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl interval: interval, } } - -func getZero[V any]() V { - var result V - return result -} diff --git a/component/sniffer/dispatcher.go b/component/sniffer/dispatcher.go index f813eec2..a1c8a93f 100644 --- a/component/sniffer/dispatcher.go +++ b/component/sniffer/dispatcher.go @@ -35,7 +35,8 @@ type SnifferDispatcher struct { parsePureIp bool } -func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) { +// TCPSniff returns true if the connection is sniffed to have a domain +func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool { if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) { inWhitelist := false overrideDest := false @@ -50,7 +51,7 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata } if !inWhitelist { - return + return false } sd.rwMux.RLock() @@ -58,18 +59,18 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata if count, ok := sd.skipList.Get(dst); ok && count > 5 { log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst) defer sd.rwMux.RUnlock() - return + return false } sd.rwMux.RUnlock() if host, err := sd.sniffDomain(conn, metadata); err != nil { sd.cacheSniffFailed(metadata) log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort) - return + return false } else { if sd.skipSNI.Has(host) { log.Debugln("[Sniffer] Skip sni[%s]", host) - return + return false } sd.rwMux.RLock() @@ -77,20 +78,23 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata sd.rwMux.RUnlock() sd.replaceDomain(metadata, host, overrideDest) + return true } } + return false } func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) { + // show log early, since the following code may mutate `metadata.Host` + log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]", + metadata.SourceDetail(), + metadata.RemoteAddress(), + metadata.Host, host) metadata.SniffHost = host if overrideDest { metadata.Host = host } metadata.DNSMode = C.DNSNormal - log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]", - metadata.SourceDetail(), - metadata.RemoteAddress(), - metadata.Host, host) } func (sd *SnifferDispatcher) Enable() bool { diff --git a/component/tls/reality.go b/component/tls/reality.go index 265c584e..c995af0a 100644 --- a/component/tls/reality.go +++ b/component/tls/reality.go @@ -22,6 +22,7 @@ import ( "github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/ntp" utls "github.com/sagernet/utls" "github.com/zhangyunhao116/fastrand" @@ -70,7 +71,7 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string rawSessionID[i] = 0 } - binary.BigEndian.PutUint64(hello.SessionId, uint64(time.Now().Unix())) + binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix())) copy(hello.SessionId[8:], realityConfig.ShortID[:]) hello.SessionId[0] = 1 diff --git a/config/config.go b/config/config.go index cb30999b..e240f9b9 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( "net/netip" "net/url" "os" + "path" "regexp" "strings" "time" @@ -16,6 +17,7 @@ import ( "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/adapter/provider" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/component/auth" "github.com/Dreamacro/clash/component/dialer" @@ -59,6 +61,7 @@ type General struct { Sniffing bool `json:"sniffing"` EBpf EBpf `json:"-"` GlobalClientFingerprint string `json:"global-client-fingerprint"` + GlobalUA string `json:"global-ua"` } // Inbound config @@ -87,6 +90,16 @@ type Controller struct { Secret string `json:"-"` } +// NTP config +type NTP struct { + Enable bool `yaml:"enable"` + Server string `yaml:"server"` + Port int `yaml:"port"` + Interval int `yaml:"interval"` + DialerProxy string `yaml:"dialer-proxy"` + WriteToSystem bool `yaml:"write-to-system"` +} + // DNS config type DNS struct { Enable bool `yaml:"enable"` @@ -144,13 +157,15 @@ type Sniffer struct { // Experimental config type Experimental struct { - Fingerprints []string `yaml:"fingerprints"` + Fingerprints []string `yaml:"fingerprints"` + QUICGoDisableGSO bool `yaml:"quic-go-disable-gso"` } // Config is clash config manager type Config struct { General *General IPTables *IPTables + NTP *NTP DNS *DNS Experimental *Experimental Hosts *trie.DomainTrie[resolver.HostValue] @@ -167,6 +182,15 @@ type Config struct { TLS *TLS } +type RawNTP struct { + Enable bool `yaml:"enable"` + Server string `yaml:"server"` + ServerPort int `yaml:"server-port"` + Interval int `yaml:"interval"` + DialerProxy string `yaml:"dialer-proxy"` + WriteToSystem bool `yaml:"write-to-system"` +} + type RawDNS struct { Enable bool `yaml:"enable"` PreferH3 bool `yaml:"prefer-h3"` @@ -255,6 +279,8 @@ type RawConfig struct { ExternalController string `yaml:"external-controller"` ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalUI string `yaml:"external-ui"` + ExternalUIURL string `yaml:"external-ui-url" json:"external-ui-url"` + ExternalUIName string `yaml:"external-ui-name" json:"external-ui-name"` Secret string `yaml:"secret"` Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` @@ -264,11 +290,14 @@ type RawConfig struct { TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` GlobalClientFingerprint string `yaml:"global-client-fingerprint"` + GlobalUA string `yaml:"global-ua"` + KeepAliveInterval int `yaml:"keep-alive-interval"` Sniffer RawSniffer `yaml:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` RuleProvider map[string]map[string]any `yaml:"rule-providers"` Hosts map[string]any `yaml:"hosts"` + NTP RawNTP `yaml:"ntp"` DNS RawDNS `yaml:"dns"` Tun RawTun `yaml:"tun"` TuicServer RawTuicServer `yaml:"tuic-server"` @@ -348,6 +377,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { ProxyGroup: []map[string]any{}, TCPConcurrent: false, FindProcessMode: P.FindProcessStrict, + GlobalUA: "clash.meta", Tun: RawTun{ Enable: false, Device: "", @@ -379,6 +409,13 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { InboundInterface: "lo", Bypass: []string{}, }, + NTP: RawNTP{ + Enable: false, + WriteToSystem: false, + Server: "time.apple.com", + ServerPort: 123, + Interval: 30, + }, DNS: RawDNS{ Enable: false, IPv6: false, @@ -426,6 +463,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { GeoIp: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.dat", GeoSite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat", }, + ExternalUIURL: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip", } if err := yaml.Unmarshal(buf, rawCfg); err != nil { @@ -493,6 +531,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } config.Hosts = hosts + ntpCfg := paresNTP(rawCfg) + config.NTP = ntpCfg + dnsCfg, err := parseDNS(rawCfg, hosts, rules, ruleProviders) if err != nil { return nil, err @@ -533,19 +574,40 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { } func parseGeneral(cfg *RawConfig) (*General, error) { - externalUI := cfg.ExternalUI geodata.SetLoader(cfg.GeodataLoader) C.GeoIpUrl = cfg.GeoXUrl.GeoIp C.GeoSiteUrl = cfg.GeoXUrl.GeoSite C.MmdbUrl = cfg.GeoXUrl.Mmdb C.GeodataMode = cfg.GeodataMode + C.UA = cfg.GlobalUA + if cfg.KeepAliveInterval != 0 { + N.KeepAliveInterval = time.Duration(cfg.KeepAliveInterval) * time.Second + } + + ExternalUIPath = cfg.ExternalUI // checkout externalUI exist - if externalUI != "" { - externalUI = C.Path.Resolve(externalUI) - if _, err := os.Stat(externalUI); os.IsNotExist(err) { - return nil, fmt.Errorf("external-ui: %s not exist", externalUI) + if ExternalUIPath != "" { + ExternalUIPath = C.Path.Resolve(ExternalUIPath) + if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { + defaultUIpath := path.Join(C.Path.HomeDir(), "ui") + log.Warnln("external-ui: %s does not exist, creating folder in %s", ExternalUIPath, defaultUIpath) + if err := os.MkdirAll(defaultUIpath, os.ModePerm); err != nil { + return nil, err + } + ExternalUIPath = defaultUIpath + cfg.ExternalUI = defaultUIpath } } + // checkout UIpath/name exist + if cfg.ExternalUIName != "" { + ExternalUIName = cfg.ExternalUIName + } else { + ExternalUIFolder = ExternalUIPath + } + if cfg.ExternalUIURL != "" { + ExternalUIURL = cfg.ExternalUIURL + } + cfg.Tun.RedirectToTun = cfg.EBpf.RedirectToTun return &General{ Inbound: Inbound{ @@ -580,6 +642,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { FindProcessMode: cfg.FindProcessMode, EBpf: cfg.EBpf, GlobalClientFingerprint: cfg.GlobalClientFingerprint, + GlobalUA: cfg.GlobalUA, }, nil } @@ -1132,6 +1195,19 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM return sites, nil } +func paresNTP(rawCfg *RawConfig) *NTP { + cfg := rawCfg.NTP + ntpCfg := &NTP{ + Enable: cfg.Enable, + Server: cfg.Server, + Port: cfg.ServerPort, + Interval: cfg.Interval, + DialerProxy: cfg.DialerProxy, + WriteToSystem: cfg.WriteToSystem, + } + return ntpCfg +} + func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rules []C.Rule, ruleProviders map[string]providerTypes.RuleProvider) (*DNS, error) { cfg := rawCfg.DNS if cfg.Enable && len(cfg.NameServer) == 0 { diff --git a/config/updateGeo.go b/config/update_geo.go similarity index 75% rename from config/updateGeo.go rename to config/update_geo.go index b75d3184..07f211e4 100644 --- a/config/updateGeo.go +++ b/config/update_geo.go @@ -1,17 +1,11 @@ package config import ( - "context" "fmt" - "io" - "net/http" - "os" "runtime" - "time" "github.com/Dreamacro/clash/component/geodata" _ "github.com/Dreamacro/clash/component/geodata/standard" - clashHttp "github.com/Dreamacro/clash/component/http" C "github.com/Dreamacro/clash/constant" "github.com/oschwald/maxminddb-golang" @@ -72,19 +66,3 @@ func UpdateGeoDatabases() error { return nil } - -func downloadForBytes(url string) ([]byte, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) - defer cancel() - resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - return io.ReadAll(resp.Body) -} - -func saveFile(bytes []byte, path string) error { - return os.WriteFile(path, bytes, 0o644) -} diff --git a/config/update_ui.go b/config/update_ui.go new file mode 100644 index 00000000..27e0f382 --- /dev/null +++ b/config/update_ui.go @@ -0,0 +1,145 @@ +package config + +import ( + "archive/zip" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "strings" + "sync" + + C "github.com/Dreamacro/clash/constant" +) + +var ( + ExternalUIURL string + ExternalUIPath string + ExternalUIFolder string + ExternalUIName string +) +var ( + ErrIncompleteConf = errors.New("ExternalUI configure incomplete") +) +var xdMutex sync.Mutex + +func UpdateUI() error { + xdMutex.Lock() + defer xdMutex.Unlock() + + err := prepare() + if err != nil { + return err + } + + data, err := downloadForBytes(ExternalUIURL) + if err != nil { + return fmt.Errorf("can't download file: %w", err) + } + + saved := path.Join(C.Path.HomeDir(), "download.zip") + if saveFile(data, saved) != nil { + return fmt.Errorf("can't save zip file: %w", err) + } + defer os.Remove(saved) + + err = cleanup(ExternalUIFolder) + if err != nil { + if !os.IsNotExist(err) { + return fmt.Errorf("cleanup exist file error: %w", err) + } + } + + unzipFolder, err := unzip(saved, C.Path.HomeDir()) + if err != nil { + return fmt.Errorf("can't extract zip file: %w", err) + } + + err = os.Rename(unzipFolder, ExternalUIFolder) + if err != nil { + return fmt.Errorf("can't rename folder: %w", err) + } + return nil +} + +func prepare() error { + if ExternalUIPath == "" || ExternalUIURL == "" { + return ErrIncompleteConf + } + + if ExternalUIName != "" { + ExternalUIFolder = filepath.Clean(path.Join(ExternalUIPath, ExternalUIName)) + if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) { + if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil { + return err + } + } + } else { + ExternalUIFolder = ExternalUIPath + } + + return nil +} + +func unzip(src, dest string) (string, error) { + r, err := zip.OpenReader(src) + if err != nil { + return "", err + } + defer r.Close() + var extractedFolder string + for _, f := range r.File { + fpath := filepath.Join(dest, f.Name) + if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) { + return "", fmt.Errorf("invalid file path: %s", fpath) + } + if f.FileInfo().IsDir() { + os.MkdirAll(fpath, os.ModePerm) + continue + } + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return "", err + } + outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return "", err + } + rc, err := f.Open() + if err != nil { + return "", err + } + _, err = io.Copy(outFile, rc) + outFile.Close() + rc.Close() + if err != nil { + return "", err + } + if extractedFolder == "" { + extractedFolder = filepath.Dir(fpath) + } + } + return extractedFolder, nil +} + +func cleanup(root string) error { + if _, err := os.Stat(root); os.IsNotExist(err) { + return nil + } + return filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + if err := os.RemoveAll(path); err != nil { + return err + } + } else { + if err := os.Remove(path); err != nil { + return err + } + } + return nil + }) +} diff --git a/config/utils.go b/config/utils.go index 799082c4..1fa54634 100644 --- a/config/utils.go +++ b/config/utils.go @@ -1,15 +1,37 @@ package config import ( + "context" "fmt" + "io" "net" + "net/http" "net/netip" + "os" "strings" + "time" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/common/structure" + clashHttp "github.com/Dreamacro/clash/component/http" ) +func downloadForBytes(url string) ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*90) + defer cancel() + resp, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"clash"}}, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return io.ReadAll(resp.Body) +} + +func saveFile(bytes []byte, path string) error { + return os.WriteFile(path, bytes, 0o644) +} + func trimArr(arr []string) (r []string) { for _, e := range arr { r = append(r, strings.Trim(e, " ")) diff --git a/constant/adapters.go b/constant/adapters.go index a3796ef7..33b9a44f 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -36,6 +36,7 @@ const ( Vless Trojan Hysteria + Hysteria2 WireGuard Tuic ) @@ -200,6 +201,8 @@ func (at AdapterType) String() string { return "Trojan" case Hysteria: return "Hysteria" + case Hysteria2: + return "Hysteria2" case WireGuard: return "WireGuard" case Tuic: @@ -267,13 +270,17 @@ type NatTable interface { Delete(key string) - GetLocalConn(lAddr, rAddr string) *net.UDPConn + DeleteLock(key string) - AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool + GetForLocalConn(lAddr, rAddr string) *net.UDPConn - RangeLocalConn(lAddr string, f func(key, value any) bool) + AddForLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool - GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool) + RangeForLocalConn(lAddr string, f func(key string, value *net.UDPConn) bool) - DeleteLocalConnMap(lAddr, key string) + GetOrCreateLockForLocalConn(lAddr string, key string) (*sync.Cond, bool) + + DeleteForLocalConn(lAddr, key string) + + DeleteLockForLocalConn(lAddr, key string) } diff --git a/constant/http.go b/constant/http.go new file mode 100644 index 00000000..8e321f6b --- /dev/null +++ b/constant/http.go @@ -0,0 +1,5 @@ +package constant + +var ( + UA string +) diff --git a/constant/metadata.go b/constant/metadata.go index dbd31fd8..70478911 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -30,6 +30,7 @@ const ( TUNNEL TUN TUIC + HYSTERIA2 INNER ) @@ -78,6 +79,8 @@ func (t Type) String() string { return "Tun" case TUIC: return "Tuic" + case HYSTERIA2: + return "Hysteria2" case INNER: return "Inner" default: @@ -110,6 +113,8 @@ func ParseType(t string) (*Type, error) { res = TUN case "TUIC": res = TUIC + case "HYSTERIA2": + res = HYSTERIA2 case "INNER": res = INNER default: diff --git a/dns/client.go b/dns/client.go index ba83412b..56f55668 100644 --- a/dns/client.go +++ b/dns/client.go @@ -9,9 +9,9 @@ import ( "strings" "github.com/Dreamacro/clash/common/atomic" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" - tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" D "github.com/miekg/dns" @@ -99,7 +99,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) ch := make(chan result, 1) go func() { if strings.HasSuffix(c.Client.Net, "tls") { - conn = tls.Client(conn, tlsC.GetGlobalTLSConfig(c.Client.TLSConfig)) + conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig)) } msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{ diff --git a/dns/doh.go b/dns/doh.go index 49e502fd..0d84fc4f 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -15,7 +15,7 @@ import ( "sync" "time" - tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/Dreamacro/clash/component/ca" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/metacubex/quic-go" @@ -382,7 +382,7 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error) // HTTP3 is enabled in the upstream options). If this attempt is successful, // it returns an HTTP3 transport, otherwise it returns the H1/H2 transport. func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) { - tlsConfig := tlsC.GetGlobalTLSConfig( + tlsConfig := ca.GetGlobalTLSConfig( &tls.Config{ InsecureSkipVerify: false, MinVersion: tls.VersionTLS12, diff --git a/dns/doq.go b/dns/doq.go index f0016d79..afa8259a 100644 --- a/dns/doq.go +++ b/dns/doq.go @@ -12,7 +12,7 @@ import ( "sync" "time" - tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/Dreamacro/clash/component/ca" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/metacubex/quic-go" @@ -330,7 +330,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio return nil, err } - tlsConfig := tlsC.GetGlobalTLSConfig( + tlsConfig := ca.GetGlobalTLSConfig( &tls.Config{ ServerName: host, InsecureSkipVerify: false, diff --git a/dns/resolver.go b/dns/resolver.go index 8f41a44e..89e36214 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -200,6 +200,7 @@ func (r *Resolver) exchangeWithoutCache(ctx context.Context, m *D.Msg) (msg *D.M isIPReq := isIPRequest(q) if isIPReq { + cache=true return r.ipExchange(ctx, m) } diff --git a/dns/util.go b/dns/util.go index 77f677cb..29de4e2a 100644 --- a/dns/util.go +++ b/dns/util.go @@ -30,9 +30,13 @@ const ( ) func minimalTTL(records []D.RR) uint32 { - return lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool { + minObj := lo.MinBy(records, func(r1 D.RR, r2 D.RR) bool { return r1.Header().Ttl < r2.Header().Ttl - }).Header().Ttl + }) + if minObj != nil { + return minObj.Header().Ttl + } + return 0 } func updateTTL(records []D.RR, ttl uint32) { @@ -46,27 +50,27 @@ func updateTTL(records []D.RR, ttl uint32) { } func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) { - // skip dns cache for acme challenge - if len(msg.Question) != 0 { - if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge") { - log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) + putMsgToCacheWithExpire(c, key, msg, 0) +} + +func putMsgToCacheWithExpire(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg, sec uint32) { + if sec == 0 { + if sec = minimalTTL(msg.Answer); sec == 0 { + if sec = minimalTTL(msg.Ns); sec == 0 { + sec = minimalTTL(msg.Extra) + } + } + if sec == 0 { return } - } - var ttl uint32 - switch { - case len(msg.Answer) != 0: - ttl = minimalTTL(msg.Answer) - case len(msg.Ns) != 0: - ttl = minimalTTL(msg.Ns) - case len(msg.Extra) != 0: - ttl = minimalTTL(msg.Extra) - default: - log.Debugln("[DNS] response msg empty: %#v", msg) - return + + if sec > 120 { + sec = 120 // at least 2 minutes to cache + } + } - c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Second*time.Duration(ttl))) + c.SetWithExpire(key, msg.Copy(), time.Now().Add(time.Duration(sec)*time.Second)) } func setMsgTTL(msg *D.Msg, ttl uint32) { @@ -286,7 +290,7 @@ func listenPacket(ctx context.Context, proxyAdapter C.ProxyAdapter, proxyName st DstPort: uint16(uintPort), } if proxyAdapter == nil { - return dialer.ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", opts...) + return dialer.NewDialer(opts...).ListenPacket(ctx, dialer.ParseNetwork(network, dstIP), "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort)) } if !proxyAdapter.SupportUDP() { diff --git a/docs/config.yaml b/docs/config.yaml index 6ae3910e..c2bdc263 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -41,7 +41,11 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要 # secret: "123456" # `Authorization:Bearer ${secret}` # tcp-concurrent: true # TCP 并发连接所有 IP, 将使用最快握手的 TCP -external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 + +# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问 +external-ui: /path/to/ui/folder/ +external-ui-name: xd +external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip" # interface-name: en0 # 设置出口网卡 @@ -50,8 +54,18 @@ external-ui: /path/to/ui/folder # 配置 WEB UI 目录,使用 http://{{externa # Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan. global-client-fingerprint: chrome +# TCP keep alive interval +keep-alive-interval: 15 + # routing-mark:6666 # 配置 fwmark 仅用于 Linux experimental: + # Disable quic-go GSO support. This may result in reduced performance on Linux. + # This is not recommended for most users. + # Only users encountering issues with quic-go's internal implementation should enable this, + # and they should disable it as soon as the issue is resolved. + # This field will be removed when quic-go fixes all their issues in GSO. + # This equivalent to the environment variable QUIC_GO_DISABLE_GSO=1. + #quic-go-disable-gso: true # 类似于 /etc/hosts, 仅支持配置单个 IP hosts: @@ -628,6 +642,25 @@ proxies: # socks5 # fingerprint: xxxx # fast-open: true # 支持 TCP 快速打开,默认为 false + #hysteria2 + - name: "hysteria2" + type: hysteria2 + server: server.com + port: 443 + # up和down均不写或为0则使用BBR流控 + # up: "30 Mbps" # 若不写单位,默认为 Mbps + # down: "200 Mbps" # 若不写单位,默认为 Mbps + password: yourpassword + # obfs: salamander # 默认为空,如果填写则开启obfs,目前仅支持salamander + # obfs-password: yourpassword + # sni: server.com + # skip-cert-verify: false + # fingerprint: xxxx + # alpn: + # - h3 + # ca: "./my.ca" + # ca-str: "xyz" + # wireguard - name: "wg" type: wireguard @@ -681,6 +714,11 @@ proxies: # socks5 # skip-cert-verify: true # max-open-streams: 20 # default 100, too many open streams may hurt performance # sni: example.com + # + # meta和sing-box私有扩展,将ss-uot用于udp中继,开启此选项后udp-relay-mode将失效 + # 警告,与原版tuic不兼容!!! + # udp-over-stream: false + # udp-over-stream-version: 1 # ShadowsocksR # The supported ciphers (encryption methods): all stream ciphers in ss diff --git a/go.mod b/go.mod index 3167d9ea..6c67a38a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Dreamacro/clash go 1.20 require ( - github.com/3andne/restls-client-go v0.1.4 + github.com/3andne/restls-client-go v0.1.6 github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/cilium/ebpf v0.11.0 github.com/coreos/go-iptables v0.7.0 @@ -13,41 +13,43 @@ require ( github.com/go-chi/render v1.0.3 github.com/gofrs/uuid/v5 v5.0.0 github.com/gorilla/websocket v1.5.0 - github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c + github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a github.com/jpillora/backoff v1.0.0 github.com/klauspost/cpuid/v2 v2.2.5 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/mdlayher/netlink v1.7.2 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86 - github.com/metacubex/sing-shadowsocks v0.2.4 - github.com/metacubex/sing-shadowsocks2 v0.1.3 - github.com/metacubex/sing-tun v0.1.11 - github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8 + github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf + github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 + github.com/metacubex/sing-shadowsocks v0.2.5 + github.com/metacubex/sing-shadowsocks2 v0.1.4 + github.com/metacubex/sing-tun v0.1.12 + github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 - github.com/miekg/dns v1.1.55 - github.com/mroth/weightedrand/v2 v2.0.2 + github.com/miekg/dns v1.1.56 + github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 + github.com/puzpuzpuz/xsync/v2 v2.5.0 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.2.9 - github.com/sagernet/sing-mux v0.1.2 + github.com/sagernet/sing v0.2.11 + github.com/sagernet/sing-mux v0.1.3 github.com/sagernet/sing-shadowtls v0.1.4 - github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 + github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 - github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 + github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f github.com/samber/lo v1.38.1 - github.com/shirou/gopsutil/v3 v3.23.7 + github.com/shirou/gopsutil/v3 v3.23.8 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 github.com/zhangyunhao116/fastrand v0.3.0 go.etcd.io/bbolt v1.3.7 go.uber.org/automaxprocs v1.5.3 - golang.org/x/crypto v0.12.0 - golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b - golang.org/x/net v0.14.0 + golang.org/x/crypto v0.13.0 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/net v0.15.0 golang.org/x/sync v0.3.0 - golang.org/x/sys v0.11.0 + golang.org/x/sys v0.12.0 google.golang.org/protobuf v1.31.0 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.2.1 @@ -64,7 +66,8 @@ require ( github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gaukas/godicttls v0.0.4 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/btree v1.1.2 // indirect @@ -72,7 +75,7 @@ require ( github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/josharian/native v1.1.0 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.16.7 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect @@ -82,7 +85,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.1 // indirect + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect @@ -90,16 +93,16 @@ require ( github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect - github.com/tklauser/go-sysconf v0.3.11 // indirect - github.com/tklauser/numcpus v0.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect - golang.org/x/mod v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.13.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7 +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 diff --git a/go.sum b/go.sum index a90f4dba..0105d57d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU= -github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I= +github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08= +github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY= github.com/RyuaNerin/go-krypto v1.0.2 h1:9KiZrrBs+tDrQ66dNy4nrX6SzntKtSKdm0wKHhdB4WM= github.com/RyuaNerin/go-krypto v1.0.2/go.mod h1:17LzMeJCgzGTkPH3TmfzRnEJ/yA7ErhTPp9sxIqONtA= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= @@ -35,6 +35,8 @@ github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtB github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= +github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= @@ -42,8 +44,9 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= @@ -67,15 +70,15 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs= -github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= +github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ= +github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -92,24 +95,26 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww= github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg= -github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86 h1:qGExcB3lYk51LPEJh5HTdQplbZmuTn+tkcuhuas1LC8= -github.com/metacubex/quic-go v0.37.4-0.20230809092428-5acf8eb2de86/go.mod h1:HhHoyskMk4kzfLPKcm7EF7pGXF89KRVwjbGrEaN6lIU= -github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7 h1:XY3Y6nPL45XuN/k3rDXJ1TJknLo8rTo1SVuDOmOEf4E= -github.com/metacubex/sing v0.0.0-20230714010500-e24664dc75a7/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= -github.com/metacubex/sing-shadowsocks v0.2.4 h1:Gc99Z17JVif1PKKq1pjqhSmc2kvHUgk+AqxOstCzhQ0= -github.com/metacubex/sing-shadowsocks v0.2.4/go.mod h1:w9qoEZSh9aKeXSLXHe0DGbG2UE9/2VlLGwukzQZ7byI= -github.com/metacubex/sing-shadowsocks2 v0.1.3 h1:nZvH+4jQXZ92NeNdR9fXaUGTPNJPt6u0nkcuh/NEt5Y= -github.com/metacubex/sing-shadowsocks2 v0.1.3/go.mod h1:5Mt93RlmRlIcDmvtapkhQJ8YTRGLFhHciLYopJjs7j8= -github.com/metacubex/sing-tun v0.1.11 h1:B8meDewklvKkeUfjqR2ViuYLam0/m4IgkTi3qcJIOuc= -github.com/metacubex/sing-tun v0.1.11/go.mod h1:vbki176Y5sxXC1DWXucrPh3q5j8cKai1D87y8m8rjQc= -github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8 h1:AqqZCr9gOeKdO6oIzFh4b2puOUFcw8MdpmGHWRehyX8= -github.com/metacubex/sing-vmess v0.1.8-0.20230801054944-603005461ff8/go.mod h1:tyJg7b4s8NrSztl/Y1ajA7X0sJLlIsEJWkgRVocjmgY= +github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8= +github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8= +github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140 h1:qiTekhMDwY2vXARJx1D9EIEdtllbL7+ZBzHX9DQpWs4= +github.com/metacubex/sing v0.0.0-20230921160249-edb949c9c140/go.mod h1:GQ673iPfUnkbK/dIPkfd1Xh1MjOGo36gkl/mkiHY7Jg= +github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81 h1:6g+ohVa8FQLXz/ATmped/4kWuK0HKvhy1hwzQXyF0EI= +github.com/metacubex/sing-quic v0.0.0-20230921160948-82175eb07a81/go.mod h1:oGpQmqe5tj3sPdPWCNLbBoUSwqd+Z6SqVO7TlMNVnH4= +github.com/metacubex/sing-shadowsocks v0.2.5 h1:O2RRSHlKGEpAVG/OHJQxyHqDy8uvvdCW/oW2TDBOIhc= +github.com/metacubex/sing-shadowsocks v0.2.5/go.mod h1:Xz2uW9BEYGEoA8B4XEpoxt7ERHClFCwsMAvWaruoyMo= +github.com/metacubex/sing-shadowsocks2 v0.1.4 h1:OOCf8lgsVcpTOJUeaFAMzyKVebaQOBnKirDdUdBoKIE= +github.com/metacubex/sing-shadowsocks2 v0.1.4/go.mod h1:Qz028sLfdY3qxGRm9FDI+IM2Ae3ty2wR7HIzD/56h/k= +github.com/metacubex/sing-tun v0.1.12 h1:Jgmz0k3ddRiJ8zfS4X7j6B/iSy6GnOdDEU0nhqiZcK4= +github.com/metacubex/sing-tun v0.1.12/go.mod h1:X2P/H1HqXwqGcguGXWDVDhSS1GmDxVi13OmbtDedZ2M= +github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 h1:FtupiyFkaVjFvRa7B/uDtRWg5BNsoyPC9MTev3sDasY= +github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74/go.mod h1:8EWBZpc+qNvf5gmvjAtMHK1/DpcWqzfcBL842K00BsM= github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 h1:mXFpxfR/1nADh+GoT8maWEvc6LO6uatPsARD8WzUDMA= github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28/go.mod h1:KrDPq/dE793jGIJw9kcIvjA/proAfU0IeU7WlMXW7rs= -github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= -github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= -github.com/mroth/weightedrand/v2 v2.0.2 h1:A8wJRUBcfguGl6oOQHI8fy5P4ViGRT9hdQdlG/7RiXo= -github.com/mroth/weightedrand/v2 v2.0.2/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= +github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= +github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= @@ -130,33 +135,35 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c= +github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg= -github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g= -github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= +github.com/sagernet/sing-mux v0.1.3 h1:fAf7PZa2A55mCeh0KKM02f1k2Y4vEmxuZZ/51ahkkLA= +github.com/sagernet/sing-mux v0.1.3/go.mod h1:wGeIeiiFLx4HUM5LAg65wrNZ/X1muOimqK0PEhNbPi0= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0= -github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE= -github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg= +github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q= +github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM= -github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo= -github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0= +github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho= +github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= -github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= -github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= +github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= +github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -179,10 +186,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= -github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= -github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= -github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= @@ -203,20 +210,20 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= -golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= @@ -235,25 +242,25 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index f4cda47a..1831584f 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -2,16 +2,22 @@ package executor import ( "fmt" + "net" "net/netip" "os" "runtime" + "strconv" "strings" "sync" + "time" + + "github.com/Dreamacro/clash/ntp" "github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/component/auth" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/component/dialer" G "github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/iface" @@ -19,7 +25,6 @@ import ( "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/resolver" SNI "github.com/Dreamacro/clash/component/sniffer" - CTLS "github.com/Dreamacro/clash/component/tls" "github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/config" C "github.com/Dreamacro/clash/constant" @@ -79,9 +84,9 @@ func ApplyConfig(cfg *config.Config, force bool) { tunnel.OnSuspend() - CTLS.ResetCertificate() + ca.ResetCertificate() for _, c := range cfg.TLS.CustomTrustCert { - if err := CTLS.AddCertificate(c); err != nil { + if err := ca.AddCertificate(c); err != nil { log.Warnln("%s\nadd error: %s", c, err.Error()) } } @@ -92,6 +97,7 @@ func ApplyConfig(cfg *config.Config, force bool) { updateSniffer(cfg.Sniffer) updateHosts(cfg.Hosts) updateGeneral(cfg.General) + updateNTP(cfg.NTP) updateDNS(cfg.DNS, cfg.RuleProviders, cfg.General.IPv6) updateListeners(cfg.General, cfg.Listeners, force) updateIPTables(cfg) @@ -137,6 +143,7 @@ func GetGeneral() *config.General { AllowLan: listener.AllowLan(), BindAddress: listener.BindAddress(), }, + Controller: config.Controller{}, Mode: tunnel.Mode(), LogLevel: log.Level(), IPv6: !resolver.DisableIPv6, @@ -176,6 +183,20 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList } func updateExperimental(c *config.Config) { + if c.Experimental.QUICGoDisableGSO { + _ = os.Setenv("QUIC_GO_DISABLE_GSO", "1") + } +} + +func updateNTP(c *config.NTP) { + if c.Enable { + ntp.ReCreateNTPService( + net.JoinHostPort(c.Server, strconv.Itoa(c.Port)), + time.Duration(c.Interval), + c.DialerProxy, + c.WriteToSystem, + ) + } } func updateDNS(c *config.DNS, ruleProvider map[string]provider.RuleProvider, generalIPv6 bool) { @@ -479,7 +500,7 @@ func updateIPTables(cfg *config.Config) { } func Shutdown() { - listener.Cleanup(false) + listener.Cleanup() tproxy.CleanupTProxyIPTables() resolver.StoreFakePoolState() diff --git a/hub/route/groups.go b/hub/route/groups.go index e5b61fb5..c82207f0 100644 --- a/hub/route/groups.go +++ b/hub/route/groups.go @@ -9,6 +9,7 @@ import ( "time" "github.com/Dreamacro/clash/adapter" + "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/common/utils" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/tunnel" @@ -57,6 +58,11 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) { return } + if proxy.(*adapter.Proxy).Type() == C.URLTest { + URLTestGroup := proxy.(*adapter.Proxy).ProxyAdapter.(*outboundgroup.URLTest) + URLTestGroup.ForceSet("") + } + query := r.URL.Query() url := query.Get("url") timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32) @@ -77,7 +83,6 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) { defer cancel() dm, err := group.URLTest(ctx, url, expectedStatus) - if err != nil { render.Status(r, http.StatusGatewayTimeout) render.JSON(w, r, newError(err.Error())) diff --git a/hub/route/provider.go b/hub/route/provider.go index 1ba0d32c..c050a9f1 100644 --- a/hub/route/provider.go +++ b/hub/route/provider.go @@ -4,22 +4,35 @@ import ( "context" "net/http" + C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/tunnel" "github.com/go-chi/chi/v5" "github.com/go-chi/render" + "github.com/samber/lo" ) func proxyProviderRouter() http.Handler { r := chi.NewRouter() r.Get("/", getProviders) - r.Route("/{name}", func(r chi.Router) { + r.Route("/{providerName}", func(r chi.Router) { r.Use(parseProviderName, findProviderByName) r.Get("/", getProvider) r.Put("/", updateProvider) r.Get("/healthcheck", healthCheckProvider) + r.Mount("/", proxyProviderProxyRouter()) + }) + return r +} + +func proxyProviderProxyRouter() http.Handler { + r := chi.NewRouter() + r.Route("/{name}", func(r chi.Router) { + r.Use(parseProxyName, findProviderProxyByName) + r.Get("/", getProxy) + r.Get("/healthcheck", getProxyDelay) }) return r } @@ -54,7 +67,7 @@ func healthCheckProvider(w http.ResponseWriter, r *http.Request) { func parseProviderName(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - name := getEscapeParam(r, "name") + name := getEscapeParam(r, "providerName") ctx := context.WithValue(r.Context(), CtxKeyProviderName, name) next.ServeHTTP(w, r.WithContext(ctx)) }) @@ -76,6 +89,27 @@ func findProviderByName(next http.Handler) http.Handler { }) } +func findProviderProxyByName(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ( + name = r.Context().Value(CtxKeyProxyName).(string) + pd = r.Context().Value(CtxKeyProvider).(provider.ProxyProvider) + ) + proxy, exist := lo.Find(pd.Proxies(), func(proxy C.Proxy) bool { + return proxy.Name() == name + }) + + if !exist { + render.Status(r, http.StatusNotFound) + render.JSON(w, r, ErrNotFound) + return + } + + ctx := context.WithValue(r.Context(), CtxKeyProxy, proxy) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + func ruleProviderRouter() http.Handler { r := chi.NewRouter() r.Get("/", getRuleProviders) diff --git a/hub/route/proxies.go b/hub/route/proxies.go index 36c9d1b1..c1e30b21 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -46,7 +46,7 @@ func parseProxyName(next http.Handler) http.Handler { func findProxyByName(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { name := r.Context().Value(CtxKeyProxyName).(string) - proxies := tunnel.Proxies() + proxies := tunnel.ProxiesWithProviders() proxy, exist := proxies[name] if !exist { render.Status(r, http.StatusNotFound) @@ -60,7 +60,7 @@ func findProxyByName(next http.Handler) http.Handler { } func getProxies(w http.ResponseWriter, r *http.Request) { - proxies := tunnel.Proxies() + proxies := tunnel.ProxiesWithProviders() render.JSON(w, r, render.M{ "proxies": proxies, }) diff --git a/hub/route/restart.go b/hub/route/restart.go index 6c3f27f3..a907021f 100644 --- a/hub/route/restart.go +++ b/hub/route/restart.go @@ -8,7 +8,7 @@ import ( "runtime" "syscall" - "github.com/Dreamacro/clash/listener" + "github.com/Dreamacro/clash/hub/executor" "github.com/Dreamacro/clash/log" "github.com/go-chi/chi/v5" @@ -39,12 +39,12 @@ func restart(w http.ResponseWriter, r *http.Request) { // The background context is used because the underlying functions wrap it // with timeout and shut down the server, which handles current request. It // also should be done in a separate goroutine for the same reason. - go runRestart(execPath) + go restartExecutable(execPath) } -func runRestart(execPath string) { +func restartExecutable(execPath string) { var err error - listener.Cleanup(false) + executor.Shutdown() if runtime.GOOS == "windows" { cmd := exec.Command(execPath, os.Args[1:]...) log.Infoln("restarting: %q %q", execPath, os.Args[1:]) diff --git a/hub/route/upgrade.go b/hub/route/upgrade.go index a58f224c..7b486ee3 100644 --- a/hub/route/upgrade.go +++ b/hub/route/upgrade.go @@ -1,10 +1,12 @@ package route import ( + "errors" "fmt" "net/http" "os" + "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/hub/updater" "github.com/Dreamacro/clash/log" @@ -14,11 +16,12 @@ import ( func upgradeRouter() http.Handler { r := chi.NewRouter() - r.Post("/", upgrade) + r.Post("/", upgradeCore) + r.Post("/ui", updateUI) return r } -func upgrade(w http.ResponseWriter, r *http.Request) { +func upgradeCore(w http.ResponseWriter, r *http.Request) { // modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/home/controlupdate.go#L108 log.Infoln("start update") execPath, err := os.Executable() @@ -41,5 +44,26 @@ func upgrade(w http.ResponseWriter, r *http.Request) { f.Flush() } - go runRestart(execPath) + go restartExecutable(execPath) +} + +func updateUI(w http.ResponseWriter, r *http.Request) { + err := config.UpdateUI() + if err != nil { + if errors.Is(err, config.ErrIncompleteConf) { + log.Warnln("%s", err) + render.Status(r, http.StatusNotImplemented) + render.JSON(w, r, newError(fmt.Sprintf("%s", err))) + } else { + log.Warnln("%s", err) + render.Status(r, http.StatusInternalServerError) + render.JSON(w, r, newError(fmt.Sprintf("%s", err))) + } + return + } + + render.JSON(w, r, render.M{"status": "ok"}) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } } diff --git a/hub/updater/updater.go b/hub/updater/updater.go index 90b14c27..1a930c03 100644 --- a/hub/updater/updater.go +++ b/hub/updater/updater.go @@ -32,7 +32,7 @@ var ( workDir string // mu protects all fields below. - mu sync.RWMutex + mu sync.Mutex currentExeName string // 当前可执行文件 updateDir string // 更新目录 diff --git a/listener/autoredir/tcp.go b/listener/autoredir/tcp.go index 854d31d6..c390d89a 100644 --- a/listener/autoredir/tcp.go +++ b/listener/autoredir/tcp.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/transport/socks5" @@ -55,7 +56,7 @@ func (l *Listener) handleRedir(conn net.Conn, in chan<- C.ConnContext) { return } - _ = conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) in <- inbound.NewSocket(target, conn, C.REDIR, l.additions...) } diff --git a/listener/config/hysteria2.go b/listener/config/hysteria2.go new file mode 100644 index 00000000..5520babc --- /dev/null +++ b/listener/config/hysteria2.go @@ -0,0 +1,25 @@ +package config + +import "encoding/json" + +type Hysteria2Server struct { + Enable bool `yaml:"enable" json:"enable"` + Listen string `yaml:"listen" json:"listen"` + Users map[string]string `yaml:"users" json:"users,omitempty"` + Obfs string `yaml:"obfs" json:"obfs,omitempty"` + ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"` + Certificate string `yaml:"certificate" json:"certificate"` + PrivateKey string `yaml:"private-key" json:"private-key"` + MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"` + ALPN []string `yaml:"alpn" json:"alpn,omitempty"` + Up string `yaml:"up" json:"up,omitempty"` + Down string `yaml:"down" json:"down,omitempty"` + IgnoreClientBandwidth bool `yaml:"ignore-client-bandwidth" json:"ignore-client-bandwidth,omitempty"` + Masquerade string `yaml:"masquerade" json:"masquerade,omitempty"` + CWND int `yaml:"cwnd" json:"cwnd,omitempty"` +} + +func (h Hysteria2Server) String() string { + b, _ := json.Marshal(h) + return string(b) +} diff --git a/listener/inbound/hysteria2.go b/listener/inbound/hysteria2.go new file mode 100644 index 00000000..430d0e68 --- /dev/null +++ b/listener/inbound/hysteria2.go @@ -0,0 +1,95 @@ +package inbound + +import ( + C "github.com/Dreamacro/clash/constant" + LC "github.com/Dreamacro/clash/listener/config" + "github.com/Dreamacro/clash/listener/sing_hysteria2" + "github.com/Dreamacro/clash/log" +) + +type Hysteria2Option struct { + BaseOption + Users map[string]string `inbound:"users,omitempty"` + Obfs string `inbound:"obfs,omitempty"` + ObfsPassword string `inbound:"obfs-password,omitempty"` + Certificate string `inbound:"certificate"` + PrivateKey string `inbound:"private-key"` + MaxIdleTime int `inbound:"max-idle-time,omitempty"` + ALPN []string `inbound:"alpn,omitempty"` + Up string `inbound:"up,omitempty"` + Down string `inbound:"down,omitempty"` + IgnoreClientBandwidth bool `inbound:"ignore-client-bandwidth,omitempty"` + Masquerade string `inbound:"masquerade,omitempty"` + CWND int `inbound:"cwnd,omitempty"` +} + +func (o Hysteria2Option) Equal(config C.InboundConfig) bool { + return optionToString(o) == optionToString(config) +} + +type Hysteria2 struct { + *Base + config *Hysteria2Option + l *sing_hysteria2.Listener + ts LC.Hysteria2Server +} + +func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) { + base, err := NewBase(&options.BaseOption) + if err != nil { + return nil, err + } + return &Hysteria2{ + Base: base, + config: options, + ts: LC.Hysteria2Server{ + Enable: true, + Listen: base.RawAddress(), + Users: options.Users, + Obfs: options.Obfs, + ObfsPassword: options.ObfsPassword, + Certificate: options.Certificate, + PrivateKey: options.PrivateKey, + MaxIdleTime: options.MaxIdleTime, + ALPN: options.ALPN, + Up: options.Up, + Down: options.Down, + IgnoreClientBandwidth: options.IgnoreClientBandwidth, + Masquerade: options.Masquerade, + CWND: options.CWND, + }, + }, nil +} + +// Config implements constant.InboundListener +func (t *Hysteria2) Config() C.InboundConfig { + return t.config +} + +// Address implements constant.InboundListener +func (t *Hysteria2) Address() string { + if t.l != nil { + for _, addr := range t.l.AddrList() { + return addr.String() + } + } + return "" +} + +// Listen implements constant.InboundListener +func (t *Hysteria2) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { + var err error + t.l, err = sing_hysteria2.New(t.ts, tcpIn, udpIn, t.Additions()...) + if err != nil { + return err + } + log.Infoln("Hysteria2[%s] proxy listening at: %s", t.Name(), t.Address()) + return nil +} + +// Close implements constant.InboundListener +func (t *Hysteria2) Close() error { + return t.l.Close() +} + +var _ C.InboundListener = (*Hysteria2)(nil) diff --git a/listener/listener.go b/listener/listener.go index 8f0088db..b1d59d49 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -84,9 +84,7 @@ type Ports struct { func GetTunConf() LC.Tun { if tunLister == nil { - return LC.Tun{ - Enable: false, - } + return LastTunConf } return tunLister.Config() } @@ -516,7 +514,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack defer func() { if err != nil { log.Errorln("Start TUN listening error: %s", err.Error()) - Cleanup(false) + tunConf.Enable = false } }() @@ -527,7 +525,7 @@ func ReCreateTun(tunConf LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.Pack return } - Cleanup(true) + closeTunListener() if !tunConf.Enable { return @@ -897,10 +895,13 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { return false } -func Cleanup(wait bool) { +func closeTunListener() { if tunLister != nil { tunLister.Close() tunLister = nil } - LastTunConf = LC.Tun{} +} + +func Cleanup() { + closeTunListener() } diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go index e8385873..7241927d 100644 --- a/listener/mixed/mixed.go +++ b/listener/mixed/mixed.go @@ -1,9 +1,9 @@ package mixed import ( - "github.com/Dreamacro/clash/adapter/inbound" "net" + "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/common/cache" N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" @@ -70,7 +70,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } func handleConn(conn net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], additions ...inbound.Addition) { - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) diff --git a/listener/parse.go b/listener/parse.go index c8e1ddf7..b0fac86a 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -86,6 +86,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { return nil, err } listener, err = IN.NewVmess(vmessOption) + case "hysteria2": + hysteria2Option := &IN.Hysteria2Option{} + err = decoder.Decode(mapping, hysteria2Option) + if err != nil { + return nil, err + } + listener, err = IN.NewHysteria2(hysteria2Option) case "tuic": tuicOption := &IN.TuicOption{ MaxIdleTime: 15000, diff --git a/listener/redir/tcp.go b/listener/redir/tcp.go index ad4a91bc..9a843af8 100644 --- a/listener/redir/tcp.go +++ b/listener/redir/tcp.go @@ -4,6 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" ) @@ -66,6 +67,6 @@ func handleRedir(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Ad conn.Close() return } - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) in <- inbound.NewSocket(target, conn, C.REDIR, additions...) } diff --git a/listener/shadowsocks/tcp.go b/listener/shadowsocks/tcp.go index febf87c3..2d0958a0 100644 --- a/listener/shadowsocks/tcp.go +++ b/listener/shadowsocks/tcp.go @@ -59,7 +59,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C } continue } - _ = c.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(c) go sl.HandleConn(c, tcpIn) } }() diff --git a/listener/sing/sing.go b/listener/sing/sing.go index f59fd613..d5731bbf 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -72,7 +72,27 @@ func UpstreamMetadata(metadata M.Metadata) M.Metadata { } } -func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { +func ConvertMetadata(metadata *C.Metadata) M.Metadata { + return M.Metadata{ + Protocol: metadata.Type.String(), + Source: M.SocksaddrFrom(metadata.SrcIP, metadata.SrcPort), + Destination: M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort), + } +} + +func (h *ListenerHandler) IsSpecialFqdn(fqdn string) bool { + switch fqdn { + case mux.Destination.Fqdn: + case vmess.MuxDestination.Fqdn: + case uot.MagicAddress: + case uot.LegacyMagicAddress: + default: + return false + } + return true +} + +func (h *ListenerHandler) ParseSpecialFqdn(ctx context.Context, conn net.Conn, metadata M.Metadata) error { switch metadata.Destination.Fqdn { case mux.Destination.Fqdn: return mux.HandleConnection(ctx, h, log.SingLogger, conn, UpstreamMetadata(metadata)) @@ -89,6 +109,13 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()} return h.NewPacketConnection(ctx, uot.NewConn(conn, uot.Request{}), metadata) } + return errors.New("not special fqdn") +} + +func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + if h.IsSpecialFqdn(metadata.Destination.Fqdn) { + return h.ParseSpecialFqdn(ctx, conn, metadata) + } target := socks5.ParseAddr(metadata.Destination.String()) wg := &sync.WaitGroup{} defer wg.Wait() // this goroutine must exit after conn.Close() diff --git a/listener/sing_hysteria2/server.go b/listener/sing_hysteria2/server.go new file mode 100644 index 00000000..4e0a7c07 --- /dev/null +++ b/listener/sing_hysteria2/server.go @@ -0,0 +1,181 @@ +package sing_hysteria2 + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/Dreamacro/clash/adapter/inbound" + "github.com/Dreamacro/clash/adapter/outbound" + CN "github.com/Dreamacro/clash/common/net" + "github.com/Dreamacro/clash/common/sockopt" + C "github.com/Dreamacro/clash/constant" + LC "github.com/Dreamacro/clash/listener/config" + "github.com/Dreamacro/clash/listener/sing" + "github.com/Dreamacro/clash/log" + + "github.com/metacubex/sing-quic/hysteria2" + + E "github.com/sagernet/sing/common/exceptions" +) + +type Listener struct { + closed bool + config LC.Hysteria2Server + udpListeners []net.PacketConn + services []*hysteria2.Service[string] +} + +func New(config LC.Hysteria2Server, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, additions ...inbound.Addition) (*Listener, error) { + var sl *Listener + var err error + if len(additions) == 0 { + additions = []inbound.Addition{ + inbound.WithInName("DEFAULT-HYSTERIA2"), + inbound.WithSpecialRules(""), + } + } + + h := &sing.ListenerHandler{ + TcpIn: tcpIn, + UdpIn: udpIn, + Type: C.HYSTERIA2, + Additions: additions, + } + + sl = &Listener{false, config, nil, nil} + + cert, err := CN.ParseCert(config.Certificate, config.PrivateKey) + if err != nil { + return nil, err + } + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS13, + Certificates: []tls.Certificate{cert}, + } + if len(config.ALPN) > 0 { + tlsConfig.NextProtos = config.ALPN + } else { + tlsConfig.NextProtos = []string{"h3"} + } + + var salamanderPassword string + if len(config.Obfs) > 0 { + if config.ObfsPassword == "" { + return nil, errors.New("missing obfs password") + } + switch config.Obfs { + case hysteria2.ObfsTypeSalamander: + salamanderPassword = config.ObfsPassword + default: + return nil, fmt.Errorf("unknown obfs type: %s", config.Obfs) + } + } + var masqueradeHandler http.Handler + if config.Masquerade != "" { + masqueradeURL, err := url.Parse(config.Masquerade) + if err != nil { + return nil, E.Cause(err, "parse masquerade URL") + } + switch masqueradeURL.Scheme { + case "file": + masqueradeHandler = http.FileServer(http.Dir(masqueradeURL.Path)) + case "http", "https": + masqueradeHandler = &httputil.ReverseProxy{ + Rewrite: func(r *httputil.ProxyRequest) { + r.SetURL(masqueradeURL) + r.Out.Host = r.In.Host + }, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + w.WriteHeader(http.StatusBadGateway) + }, + } + default: + return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) + } + } + + service, err := hysteria2.NewService[string](hysteria2.ServiceOptions{ + Context: context.Background(), + Logger: log.SingLogger, + SendBPS: outbound.StringToBps(config.Up), + ReceiveBPS: outbound.StringToBps(config.Down), + SalamanderPassword: salamanderPassword, + TLSConfig: tlsConfig, + IgnoreClientBandwidth: config.IgnoreClientBandwidth, + Handler: h, + MasqueradeHandler: masqueradeHandler, + CWND: config.CWND, + }) + if err != nil { + return nil, err + } + + userNameList := make([]string, 0, len(config.Users)) + userPasswordList := make([]string, 0, len(config.Users)) + for name, password := range config.Users { + userNameList = append(userNameList, name) + userPasswordList = append(userPasswordList, password) + } + service.UpdateUsers(userNameList, userPasswordList) + + for _, addr := range strings.Split(config.Listen, ",") { + addr := addr + _service := *service + service := &_service // make a copy + + ul, err := net.ListenPacket("udp", addr) + if err != nil { + return nil, err + } + + err = sockopt.UDPReuseaddr(ul.(*net.UDPConn)) + if err != nil { + log.Warnln("Failed to Reuse UDP Address: %s", err) + } + + sl.udpListeners = append(sl.udpListeners, ul) + sl.services = append(sl.services, service) + + go func() { + _ = service.Start(ul) + }() + } + + return sl, nil +} + +func (l *Listener) Close() error { + l.closed = true + var retErr error + for _, service := range l.services { + err := service.Close() + if err != nil { + retErr = err + } + } + for _, lis := range l.udpListeners { + err := lis.Close() + if err != nil { + retErr = err + } + } + return retErr +} + +func (l *Listener) Config() string { + return l.config.String() +} + +func (l *Listener) AddrList() (addrList []net.Addr) { + for _, lis := range l.udpListeners { + addrList = append(addrList, lis.LocalAddr()) + } + return +} diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index 1fa9a3ba..d0e137a7 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -5,15 +5,16 @@ import ( "fmt" "net" "strings" - "time" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/sockopt" C "github.com/Dreamacro/clash/constant" LC "github.com/Dreamacro/clash/listener/config" embedSS "github.com/Dreamacro/clash/listener/shadowsocks" "github.com/Dreamacro/clash/listener/sing" "github.com/Dreamacro/clash/log" + "github.com/Dreamacro/clash/ntp" shadowsocks "github.com/metacubex/sing-shadowsocks" "github.com/metacubex/sing-shadowsocks/shadowaead" @@ -64,7 +65,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C case common.Contains(shadowaead.List, config.Cipher): sl.service, err = shadowaead.NewService(config.Cipher, nil, config.Password, udpTimeout, h) case common.Contains(shadowaead_2022.List, config.Cipher): - sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h, time.Now) + sl.service, err = shadowaead_2022.NewServiceWithPassword(config.Cipher, config.Password, udpTimeout, h, ntp.Now) default: err = fmt.Errorf("shadowsocks: unsupported method: %s", config.Cipher) return embedSS.New(config, tcpIn, udpIn) @@ -145,7 +146,7 @@ func New(config LC.ShadowsocksServer, tcpIn chan<- C.ConnContext, udpIn chan<- C } continue } - _ = c.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(c) go sl.HandleConn(c, tcpIn) } diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index 084c701b..66fe8cd5 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -172,7 +172,7 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte } }() - networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(handler) + networkUpdateMonitor, err := tun.NewNetworkUpdateMonitor(log.SingLogger) if err != nil { err = E.Cause(err, "create NetworkUpdateMonitor") return @@ -184,15 +184,14 @@ func New(options LC.Tun, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapte return } - defaultInterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, tun.DefaultInterfaceMonitorOptions{OverrideAndroidVPN: true}) + defaultInterfaceMonitor, err := tun.NewDefaultInterfaceMonitor(networkUpdateMonitor, log.SingLogger, tun.DefaultInterfaceMonitorOptions{OverrideAndroidVPN: true}) if err != nil { err = E.Cause(err, "create DefaultInterfaceMonitor") return } l.defaultInterfaceMonitor = defaultInterfaceMonitor - defaultInterfaceMonitor.RegisterCallback(func(event int) error { + defaultInterfaceMonitor.RegisterCallback(func(event int) { l.FlushDefaultInterface() - return nil }) err = defaultInterfaceMonitor.Start() if err != nil { diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index f6a279c1..06f3e051 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -7,9 +7,11 @@ import ( "strings" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" LC "github.com/Dreamacro/clash/listener/config" "github.com/Dreamacro/clash/listener/sing" + "github.com/Dreamacro/clash/ntp" vmess "github.com/metacubex/sing-vmess" "github.com/sagernet/sing/common" @@ -42,7 +44,7 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe Additions: additions, } - service := vmess.NewService[string](h, vmess.ServiceWithDisableHeaderProtection()) + service := vmess.NewService[string](h, vmess.ServiceWithDisableHeaderProtection(), vmess.ServiceWithTimeFunc(ntp.Now)) err = service.UpdateUsers( common.Map(config.Users, func(it LC.VmessUser) string { return it.Username @@ -83,7 +85,7 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe } continue } - _ = c.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(c) go sl.HandleConn(c, tcpIn) } diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index cbaac987..2fd252a3 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -67,7 +67,7 @@ func New(addr string, in chan<- C.ConnContext, additions ...inbound.Addition) (* } func handleSocks(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) bufConn := N.NewBufferedConn(conn) head, err := bufConn.Peek(1) if err != nil { diff --git a/listener/tproxy/packet.go b/listener/tproxy/packet.go index 2966fd2e..b73339a1 100644 --- a/listener/tproxy/packet.go +++ b/listener/tproxy/packet.go @@ -55,16 +55,15 @@ func (c *packet) InAddr() net.Addr { func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natTable C.NatTable) (*net.UDPConn, error) { remote := rAddr.String() local := lAddr.String() - localConn := natTable.GetLocalConn(local, remote) + localConn := natTable.GetForLocalConn(local, remote) // localConn not exist if localConn == nil { - lockKey := remote + "-lock" - cond, loaded := natTable.GetOrCreateLockForLocalConn(local, lockKey) + cond, loaded := natTable.GetOrCreateLockForLocalConn(local, remote) if loaded { cond.L.Lock() cond.Wait() // we should get localConn here - localConn = natTable.GetLocalConn(local, remote) + localConn = natTable.GetForLocalConn(local, remote) if localConn == nil { return nil, fmt.Errorf("localConn is nil, nat entry not exist") } @@ -74,7 +73,7 @@ func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natT return nil, fmt.Errorf("cond is nil, nat entry not exist") } defer func() { - natTable.DeleteLocalConnMap(local, lockKey) + natTable.DeleteLockForLocalConn(local, remote) cond.Broadcast() }() conn, err := listenLocalConn(rAddr, lAddr, in, natTable) @@ -82,7 +81,7 @@ func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natT log.Errorln("listenLocalConn failed with error: %s, packet loss (rAddr[%T]=%s lAddr[%T]=%s)", err.Error(), rAddr, remote, lAddr, local) return nil, err } - natTable.AddLocalConn(local, remote, conn) + natTable.AddForLocalConn(local, remote, conn) localConn = conn } } diff --git a/listener/tproxy/tproxy.go b/listener/tproxy/tproxy.go index 198481f7..8c868609 100644 --- a/listener/tproxy/tproxy.go +++ b/listener/tproxy/tproxy.go @@ -4,6 +4,7 @@ import ( "net" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) @@ -32,7 +33,7 @@ func (l *Listener) Close() error { func (l *Listener) handleTProxy(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { target := socks5.ParseAddrToSocksAddr(conn.LocalAddr()) - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) in <- inbound.NewSocket(target, conn, C.TPROXY, additions...) } diff --git a/listener/tuic/server.go b/listener/tuic/server.go index 76996b27..125c53e1 100644 --- a/listener/tuic/server.go +++ b/listener/tuic/server.go @@ -1,6 +1,7 @@ package tuic import ( + "context" "crypto/tls" "net" "strings" @@ -11,6 +12,7 @@ import ( "github.com/Dreamacro/clash/common/sockopt" C "github.com/Dreamacro/clash/constant" LC "github.com/Dreamacro/clash/listener/config" + "github.com/Dreamacro/clash/listener/sing" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/tuic" @@ -36,6 +38,12 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet inbound.WithSpecialRules(""), } } + h := &sing.ListenerHandler{ + TcpIn: tcpIn, + UdpIn: udpIn, + Type: C.TUIC, + Additions: additions, + } cert, err := CN.ParseCert(config.Certificate, config.PrivateKey) if err != nil { return nil, err @@ -86,7 +94,19 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet newAdditions = slices.Clone(additions) newAdditions = append(newAdditions, _additions...) } - tcpIn <- inbound.NewSocket(addr, conn, C.TUIC, newAdditions...) + connCtx := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...) + metadata := sing.ConvertMetadata(connCtx.Metadata()) + if h.IsSpecialFqdn(metadata.Destination.Fqdn) { + go func() { // ParseSpecialFqdn will block, so open a new goroutine + _ = h.ParseSpecialFqdn( + sing.WithAdditions(context.Background(), newAdditions...), + conn, + metadata, + ) + }() + return nil + } + tcpIn <- connCtx return nil } handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error { diff --git a/listener/tunnel/tcp.go b/listener/tunnel/tcp.go index c1d896ad..d660d2b8 100644 --- a/listener/tunnel/tcp.go +++ b/listener/tunnel/tcp.go @@ -5,6 +5,7 @@ import ( "net" "github.com/Dreamacro/clash/adapter/inbound" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) @@ -34,7 +35,7 @@ func (l *Listener) Close() error { } func (l *Listener) handleTCP(conn net.Conn, in chan<- C.ConnContext, additions ...inbound.Addition) { - conn.(*net.TCPConn).SetKeepAlive(true) + N.TCPKeepAlive(conn) ctx := inbound.NewSocket(l.target, conn, C.TUNNEL, additions...) ctx.Metadata().SpecialProxy = l.proxy in <- ctx diff --git a/ntp/service.go b/ntp/service.go new file mode 100644 index 00000000..c5506197 --- /dev/null +++ b/ntp/service.go @@ -0,0 +1,124 @@ +package ntp + +import ( + "context" + "sync" + "time" + + "github.com/Dreamacro/clash/component/dialer" + "github.com/Dreamacro/clash/component/proxydialer" + "github.com/Dreamacro/clash/log" + + M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/ntp" +) + +var offset time.Duration +var service *Service + +type Service struct { + server M.Socksaddr + dialer proxydialer.SingDialer + ticker *time.Ticker + ctx context.Context + cancel context.CancelFunc + mu sync.Mutex + syncSystemTime bool + running bool +} + +func ReCreateNTPService(server string, interval time.Duration, dialerProxy string, syncSystemTime bool) { + if service != nil { + service.Stop() + } + ctx, cancel := context.WithCancel(context.Background()) + service = &Service{ + server: M.ParseSocksaddr(server), + dialer: proxydialer.NewByNameSingDialer(dialerProxy, dialer.NewDialer()), + ticker: time.NewTicker(interval * time.Minute), + ctx: ctx, + cancel: cancel, + syncSystemTime: syncSystemTime, + } + service.Start() +} + +func (srv *Service) Start() { + srv.mu.Lock() + defer srv.mu.Unlock() + log.Infoln("NTP service start, sync system time is %t", srv.syncSystemTime) + service.running = true + srv.update() + go srv.loopUpdate() +} + +func (srv *Service) Stop() { + srv.mu.Lock() + defer srv.mu.Unlock() + if service.running { + srv.ticker.Stop() + srv.cancel() + service.running = false + } +} + +func (srv *Service) Running() bool { + if srv == nil { + return false + } + srv.mu.Lock() + defer srv.mu.Unlock() + return srv.running +} + +func (srv *Service) update() { + var response *ntp.Response + var err error + for i := 0; i < 3; i++ { + response, err = ntp.Exchange(context.Background(), srv.dialer, srv.server) + if err != nil { + if i == 2 { + log.Errorln("Initialize NTP time failed: %s", err) + return + } + time.Sleep(time.Second * 2) // wait for 2 seconds before the next try + continue + } + break + } + offset = response.ClockOffset + if offset > time.Duration(0) { + log.Infoln("System clock is ahead of NTP time by %s", offset) + } else if offset < time.Duration(0) { + log.Infoln("System clock is behind NTP time by %s", -offset) + } + if srv.syncSystemTime { + timeNow := response.Time + err = setSystemTime(timeNow) + if err == nil { + log.Infoln("Sync system time success: %s", timeNow.Local().Format(ntp.TimeLayout)) + } else { + log.Errorln("Write time to system: %s", err) + srv.syncSystemTime = false + } + } +} + +func (srv *Service) loopUpdate() { + for { + select { + case <-srv.ctx.Done(): + return + case <-srv.ticker.C: + } + srv.update() + } +} + +func Now() time.Time { + now := time.Now() + if service.Running() && offset.Abs() > 0 { + now = now.Add(offset) + } + return now +} diff --git a/ntp/time_stub.go b/ntp/time_stub.go new file mode 100644 index 00000000..12050983 --- /dev/null +++ b/ntp/time_stub.go @@ -0,0 +1,12 @@ +//go:build !(windows || linux || darwin) + +package ntp + +import ( + "os" + "time" +) + +func setSystemTime(nowTime time.Time) error { + return os.ErrInvalid +} diff --git a/ntp/time_unix.go b/ntp/time_unix.go new file mode 100644 index 00000000..9e819473 --- /dev/null +++ b/ntp/time_unix.go @@ -0,0 +1,14 @@ +//go:build linux || darwin + +package ntp + +import ( + "time" + + "golang.org/x/sys/unix" +) + +func setSystemTime(nowTime time.Time) error { + timeVal := unix.NsecToTimeval(nowTime.UnixNano()) + return unix.Settimeofday(&timeVal) +} diff --git a/ntp/time_windows.go b/ntp/time_windows.go new file mode 100644 index 00000000..8ef29b1b --- /dev/null +++ b/ntp/time_windows.go @@ -0,0 +1,32 @@ +package ntp + +import ( + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +func setSystemTime(nowTime time.Time) error { + var systemTime windows.Systemtime + systemTime.Year = uint16(nowTime.Year()) + systemTime.Month = uint16(nowTime.Month()) + systemTime.Day = uint16(nowTime.Day()) + systemTime.Hour = uint16(nowTime.Hour()) + systemTime.Minute = uint16(nowTime.Minute()) + systemTime.Second = uint16(nowTime.Second()) + systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000) + + dllKernel32 := windows.NewLazySystemDLL("kernel32.dll") + proc := dllKernel32.NewProc("SetSystemTime") + + _, _, err := proc.Call( + uintptr(unsafe.Pointer(&systemTime)), + ) + + if err != nil && err.Error() != "The operation completed successfully." { + return err + } + + return nil +} diff --git a/test/go.mod b/test/go.mod index 80f958a5..5582dd04 100644 --- a/test/go.mod +++ b/test/go.mod @@ -8,13 +8,13 @@ require ( github.com/docker/go-connections v0.4.0 github.com/miekg/dns v1.1.55 github.com/stretchr/testify v1.8.4 - golang.org/x/net v0.14.0 + golang.org/x/net v0.15.0 ) replace github.com/Dreamacro/clash => ../ require ( - github.com/3andne/restls-client-go v0.1.4 // indirect + github.com/3andne/restls-client-go v0.1.6 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect github.com/RyuaNerin/go-krypto v1.0.2 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect @@ -31,6 +31,7 @@ require ( github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gofrs/uuid/v5 v5.0.0 // indirect @@ -41,10 +42,10 @@ require ( github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c // indirect + github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a // indirect github.com/josharian/native v1.1.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect - github.com/klauspost/compress v1.15.15 // indirect + github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect @@ -52,7 +53,7 @@ require ( github.com/mdlayher/socket v0.4.1 // indirect github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 // indirect github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 // indirect - github.com/metacubex/quic-go v0.37.4-0.20230806014204-ef9b221eec12 // indirect + github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf // indirect github.com/metacubex/sing-shadowsocks v0.2.4 // indirect github.com/metacubex/sing-shadowsocks2 v0.1.3 // indirect github.com/metacubex/sing-tun v0.1.11 // indirect @@ -60,7 +61,7 @@ require ( github.com/metacubex/sing-wireguard v0.0.0-20230611155257-1498ae315a28 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/mroth/weightedrand/v2 v2.0.2 // indirect + github.com/mroth/weightedrand/v2 v2.1.0 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/openacid/low v0.1.21 // indirect @@ -71,41 +72,42 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/puzpuzpuz/xsync/v2 v2.5.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.3.1 // indirect + github.com/quic-go/qtls-go1-20 v0.3.3 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect - github.com/sagernet/sing v0.2.9 // indirect - github.com/sagernet/sing-mux v0.1.2 // indirect + github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a // indirect + github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect - github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect + github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 // indirect github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 // indirect - github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 // indirect + github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f // indirect github.com/samber/lo v1.38.1 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect - github.com/shirou/gopsutil/v3 v3.23.7 // indirect + github.com/shirou/gopsutil/v3 v3.23.8 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/tklauser/go-sysconf v0.3.11 // indirect - github.com/tklauser/numcpus v0.6.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zhangyunhao116/fastrand v0.3.0 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.12.0 // indirect - golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect - golang.org/x/mod v0.11.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect diff --git a/test/go.sum b/test/go.sum index fbf635c6..609d2fcb 100644 --- a/test/go.sum +++ b/test/go.sum @@ -1,5 +1,5 @@ -github.com/3andne/restls-client-go v0.1.4 h1:kLNC2aSRHPlEVYmTj6EOqJoorCpobEe2toMRSfBF7FU= -github.com/3andne/restls-client-go v0.1.4/go.mod h1:04CGbRk1BwBiEDles8b5mlKgTqIwE5MqF7JDloJV47I= +github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08= +github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= @@ -44,6 +44,8 @@ github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtB github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= +github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -72,8 +74,8 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c h1:P/3mFnHCv1A/ej4m8pF5EB6FUt9qEL2Q9lfrcUNwCYs= -github.com/insomniacslk/dhcp v0.0.0-20230731140434-0f9eb93a696c/go.mod h1:7474bZ1YNCvarT6WFKie4kEET6J0KYRDC4XJqqXzQW4= +github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a h1:S33o3djA1nPRd+d/bf7jbbXytXuK/EoXow7+aa76grQ= +github.com/insomniacslk/dhcp v0.0.0-20230908212754-65c27093e38a/go.mod h1:zmdm3sTSDP3vOOX3CEWRkkRHtKr1DxBx+J1OQFoDQQs= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= @@ -81,8 +83,8 @@ github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2E github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= -github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -99,8 +101,8 @@ github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvO github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475 h1:qSEOvPPaMrWggFyFhFYGyMR8i1HKyhXjdi1QYUAa2ww= github.com/metacubex/gvisor v0.0.0-20230611153922-78842f086475/go.mod h1:wehEpqiogdeyncfhckJP5gD2LtBgJW0wnDC24mJ+8Jg= -github.com/metacubex/quic-go v0.37.4-0.20230806014204-ef9b221eec12 h1:18tcXxLgwjUjs38QM1E1a+AAh4j+Mo/mKcJTmqHrN9c= -github.com/metacubex/quic-go v0.37.4-0.20230806014204-ef9b221eec12/go.mod h1:HhHoyskMk4kzfLPKcm7EF7pGXF89KRVwjbGrEaN6lIU= +github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf h1:hflzPbb2M+3uUOZEVO72MKd2R62xEermoVaNhJOzBR8= +github.com/metacubex/quic-go v0.38.1-0.20230909013832-033f6a2115cf/go.mod h1:7RCcKJJk1DMeNQQNnYKS+7FqftqPfG031oP8jrYRMw8= github.com/metacubex/sing-shadowsocks v0.2.4 h1:Gc99Z17JVif1PKKq1pjqhSmc2kvHUgk+AqxOstCzhQ0= github.com/metacubex/sing-shadowsocks v0.2.4/go.mod h1:w9qoEZSh9aKeXSLXHe0DGbG2UE9/2VlLGwukzQZ7byI= github.com/metacubex/sing-shadowsocks2 v0.1.3 h1:nZvH+4jQXZ92NeNdR9fXaUGTPNJPt6u0nkcuh/NEt5Y= @@ -117,8 +119,8 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mroth/weightedrand/v2 v2.0.2 h1:A8wJRUBcfguGl6oOQHI8fy5P4ViGRT9hdQdlG/7RiXo= -github.com/mroth/weightedrand/v2 v2.0.2/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= +github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= +github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4= github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs= github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= @@ -143,10 +145,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/puzpuzpuz/xsync/v2 v2.5.0 h1:2k4qrO/orvmEXZ3hmtHqIy9XaQtPTwzMZk1+iErpE8c= +github.com/puzpuzpuz/xsync/v2 v2.5.0/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.3.1 h1:O4BLOM3hwfVF3AcktIylQXyl7Yi2iBNVy5QsV+ySxbg= -github.com/quic-go/qtls-go1-20 v0.3.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= +github.com/quic-go/qtls-go1-20 v0.3.3 h1:17/glZSLI9P9fDAeyCHBFSWSqJcwx1byhLwP5eUIDCM= +github.com/quic-go/qtls-go1-20 v0.3.3/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA= github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms= @@ -154,26 +158,26 @@ github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6E github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk= -github.com/sagernet/sing v0.2.9 h1:3wsTz+JG5Wzy65eZnh6AuCrD2QqcRF6Iq6f7ttmJsAo= -github.com/sagernet/sing v0.2.9/go.mod h1:Ta8nHnDLAwqySzKhGoKk4ZIB+vJ3GTKj7UPrWYvM+4w= -github.com/sagernet/sing-mux v0.1.2 h1:av2/m6e+Gh+ECTuJZqYCjJz55BNkot0VyRMkREqyF/g= -github.com/sagernet/sing-mux v0.1.2/go.mod h1:r2V8AlOzXaRCHXK7fILCUGzuI2iILweTaG8C5xlpHxo= +github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a h1:b89t6Mjgk4rJ5lrNMnCzy1/J116XkhgdB3YNd9FHyF4= +github.com/sagernet/sing v0.2.10-0.20230807080248-4db0062caa0a/go.mod h1:9uOZwWkhT2Z2WldolLxX34s+1svAX4i4vvz5hy8u1MA= +github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c h1:35/FowAvt3Z62mck0TXzVc4jS5R5CWq62qcV2P1cp0I= +github.com/sagernet/sing-mux v0.1.3-0.20230811111955-dc1639b5204c/go.mod h1:TKxqIvfQQgd36jp2tzsPavGjYTVZilV+atip1cssjIY= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as= github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37/go.mod h1:3skNSftZDJWTGVtVaM2jfbce8qHnmH/AGDRe62iNOg0= -github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 h1:2ItpW1nMNkPzmBTxV0/eClCklHrFSQMnUGcpUmJxVeE= -github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9/go.mod h1:FUyTEc5ye5NjKnDTDMuiLF2M6T4BE6y6KZuax//UCEg= +github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6 h1:Px+hN4Vzgx+iCGVnWH5A8eR7JhNnIV3rGQmBxA7cw6Q= +github.com/sagernet/tfo-go v0.0.0-20230816093905-5a5c285d44a6/go.mod h1:zovq6vTvEM6ECiqE3Eeb9rpIylPpamPcmrJ9tv0Bt0M= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2 h1:kDUqhc9Vsk5HJuhfIATJ8oQwBmpOZJuozQG7Vk88lL4= github.com/sagernet/utls v0.0.0-20230309024959-6732c2ab36f2/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM= -github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77 h1:g6QtRWQ2dKX7EQP++1JLNtw4C2TNxd4/ov8YUpOPOSo= -github.com/sagernet/wireguard-go v0.0.0-20230420044414-a7bac1754e77/go.mod h1:pJDdXzZIwJ+2vmnT0TKzmf8meeum+e2mTDSehw79eE0= +github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f h1:Kvo8w8Y9lzFGB/7z09MJ3TR99TFtfI/IuY87Ygcycho= +github.com/sagernet/wireguard-go v0.0.0-20230807125731-5d4a7ef2dc5f/go.mod h1:mySs0abhpc/gLlvhoq7HP1RzOaRmIXVeZGCh++zoApk= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 h1:rc/CcqLH3lh8n+csdOuDfP+NuykE0U6AeYSJJHKDgSg= github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9/go.mod h1:a/83NAfUXvEuLpmxDssAXxgUgrEy12MId3Wd7OTs76s= -github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4= -github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4= +github.com/shirou/gopsutil/v3 v3.23.8 h1:xnATPiybo6GgdRoC4YoGnxXZFRc3dqQTGi73oLvvBrE= +github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -196,10 +200,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= -github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= -github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= -github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= @@ -221,24 +225,24 @@ go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= -golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -261,16 +265,16 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -279,8 +283,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/transport/gun/utils.go b/transport/gun/utils.go index e5f6e019..e4a66315 100644 --- a/transport/gun/utils.go +++ b/transport/gun/utils.go @@ -1,10 +1,26 @@ package gun func UVarintLen(x uint64) int { - i := 0 - for x >= 0x80 { - x >>= 7 - i++ + switch { + case x < 1<<(7*1): + return 1 + case x < 1<<(7*2): + return 2 + case x < 1<<(7*3): + return 3 + case x < 1<<(7*4): + return 4 + case x < 1<<(7*5): + return 5 + case x < 1<<(7*6): + return 6 + case x < 1<<(7*7): + return 7 + case x < 1<<(7*8): + return 8 + case x < 1<<(7*9): + return 9 + default: + return 10 } - return i + 1 } diff --git a/transport/hysteria/pmtud_fix/avail.go b/transport/hysteria/pmtud_fix/avail.go index 2f2bce83..af248f5c 100644 --- a/transport/hysteria/pmtud_fix/avail.go +++ b/transport/hysteria/pmtud_fix/avail.go @@ -1,5 +1,4 @@ -//go:build linux || windows -// +build linux windows +//go:build linux || windows || darwin package pmtud_fix diff --git a/transport/hysteria/pmtud_fix/unavail.go b/transport/hysteria/pmtud_fix/unavail.go index 0eeb83df..35b849d2 100644 --- a/transport/hysteria/pmtud_fix/unavail.go +++ b/transport/hysteria/pmtud_fix/unavail.go @@ -1,5 +1,4 @@ -//go:build !linux && !windows -// +build !linux,!windows +//go:build !linux && !windows && !darwin package pmtud_fix diff --git a/transport/sing-shadowtls/shadowtls.go b/transport/sing-shadowtls/shadowtls.go index 0e1e95c0..6d731ae6 100644 --- a/transport/sing-shadowtls/shadowtls.go +++ b/transport/sing-shadowtls/shadowtls.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "net" + "github.com/Dreamacro/clash/component/ca" tlsC "github.com/Dreamacro/clash/component/tls" "github.com/Dreamacro/clash/log" @@ -39,12 +40,9 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) ( } var err error - if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { - return nil, err - } + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) + if err != nil { + return nil, err } tlsHandshake := shadowtls.DefaultTLSHandshakeFunc(option.Password, tlsConfig) diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 710905ad..6dfcfe11 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -14,6 +14,7 @@ import ( N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/component/ca" tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" @@ -77,13 +78,10 @@ func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error ServerName: t.option.ServerName, } - if len(t.option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - var err error - if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint); err != nil { - return nil, err - } + var err error + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint) + if err != nil { + return nil, err } if len(t.option.ClientFingerprint) != 0 { @@ -112,7 +110,7 @@ func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) defer cancel() - err := tlsConn.HandshakeContext(ctx) + err = tlsConn.HandshakeContext(ctx) return tlsConn, err } diff --git a/transport/tuic/common/congestion.go b/transport/tuic/common/congestion.go index 8b8018b5..36ee01a1 100644 --- a/transport/tuic/common/congestion.go +++ b/transport/tuic/common/congestion.go @@ -13,7 +13,9 @@ const ( ) func SetCongestionController(quicConn quic.Connection, cc string, cwnd int) { - CWND := c.ByteCount(cwnd) + if cwnd == 0 { + cwnd = 32 + } switch cc { case "cubic": quicConn.SetCongestionControl( @@ -38,7 +40,7 @@ func SetCongestionController(quicConn quic.Connection, cc string, cwnd int) { congestion.NewBBRSender( congestion.DefaultClock{}, congestion.GetInitialPacketSize(quicConn.RemoteAddr()), - CWND*congestion.InitialMaxDatagramSize, + c.ByteCount(cwnd)*congestion.InitialMaxDatagramSize, congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize, ), ) diff --git a/transport/tuic/congestion/minmax.go b/transport/tuic/congestion/minmax.go index ed75072e..0a8f4ad4 100644 --- a/transport/tuic/congestion/minmax.go +++ b/transport/tuic/congestion/minmax.go @@ -3,27 +3,11 @@ package congestion import ( "math" "time" - - "golang.org/x/exp/constraints" ) // InfDuration is a duration of infinite length const InfDuration = time.Duration(math.MaxInt64) -func Max[T constraints.Ordered](a, b T) T { - if a < b { - return b - } - return a -} - -func Min[T constraints.Ordered](a, b T) T { - if a < b { - return a - } - return b -} - // MinNonZeroDuration return the minimum duration that's not zero. func MinNonZeroDuration(a, b time.Duration) time.Duration { if a == 0 { diff --git a/transport/tuic/congestion/minmax_go120.go b/transport/tuic/congestion/minmax_go120.go new file mode 100644 index 00000000..1266edbc --- /dev/null +++ b/transport/tuic/congestion/minmax_go120.go @@ -0,0 +1,19 @@ +//go:build !go1.21 + +package congestion + +import "golang.org/x/exp/constraints" + +func Max[T constraints.Ordered](a, b T) T { + if a < b { + return b + } + return a +} + +func Min[T constraints.Ordered](a, b T) T { + if a < b { + return a + } + return b +} diff --git a/transport/tuic/congestion/minmax_go121.go b/transport/tuic/congestion/minmax_go121.go new file mode 100644 index 00000000..65b06726 --- /dev/null +++ b/transport/tuic/congestion/minmax_go121.go @@ -0,0 +1,13 @@ +//go:build go1.21 + +package congestion + +import "cmp" + +func Max[T cmp.Ordered](a, b T) T { + return max(a, b) +} + +func Min[T cmp.Ordered](a, b T) T { + return min(a, b) +} diff --git a/transport/tuic/server.go b/transport/tuic/server.go index a6f91b88..cabc04e0 100644 --- a/transport/tuic/server.go +++ b/transport/tuic/server.go @@ -223,6 +223,10 @@ func NewServer(option *ServerOption, pc net.PacketConn) (*Server, error) { } } if len(option.Users) > 0 { + maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize + if maxUdpRelayPacketSize > MaxFragSizeV5 { + maxUdpRelayPacketSize = MaxFragSizeV5 + } server.optionV5 = &v5.ServerOption{ HandleTcpFn: option.HandleTcpFn, HandleUdpFn: option.HandleUdpFn, diff --git a/transport/tuic/tuic.go b/transport/tuic/tuic.go index 8832ef91..387a152c 100644 --- a/transport/tuic/tuic.go +++ b/transport/tuic/tuic.go @@ -30,6 +30,7 @@ const DefaultConnectionReceiveWindow = common.DefaultConnectionReceiveWindow var GenTKN = v4.GenTKN var PacketOverHeadV4 = v4.PacketOverHead var PacketOverHeadV5 = v5.PacketOverHead +var MaxFragSizeV5 = v5.MaxFragSize type UdpRelayMode = common.UdpRelayMode diff --git a/transport/tuic/v4/client.go b/transport/tuic/v4/client.go index fd3bf54a..ce33b72b 100644 --- a/transport/tuic/v4/client.go +++ b/transport/tuic/v4/client.go @@ -22,6 +22,7 @@ import ( "github.com/Dreamacro/clash/transport/tuic/common" "github.com/metacubex/quic-go" + "github.com/puzpuzpuz/xsync/v2" "github.com/zhangyunhao116/fastrand" ) @@ -49,7 +50,7 @@ type clientImpl struct { openStreams atomic.Int64 closed atomic.Bool - udpInputMap sync.Map + udpInputMap *xsync.MapOf[uint32, net.Conn] // only ready for PoolClient dialerRef C.Dialer @@ -263,11 +264,10 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) { if quicConn != nil { _ = quicConn.CloseWithError(ProtocolError, errStr) } - udpInputMap := &t.udpInputMap - udpInputMap.Range(func(key, value any) bool { - if conn, ok := value.(net.Conn); ok { - _ = conn.Close() - } + udpInputMap := t.udpInputMap + udpInputMap.Range(func(key uint32, value net.Conn) bool { + conn := value + _ = conn.Close() udpInputMap.Delete(key) return true }) @@ -469,6 +469,7 @@ func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client ClientOption: clientOption, udp: udp, dialerRef: dialerRef, + udpInputMap: xsync.NewIntegerMapOf[uint32, net.Conn](), } c := &Client{ci} runtime.SetFinalizer(c, closeClient) diff --git a/transport/tuic/v4/server.go b/transport/tuic/v4/server.go index b0012d96..56133fea 100644 --- a/transport/tuic/v4/server.go +++ b/transport/tuic/v4/server.go @@ -17,6 +17,7 @@ import ( "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" + "github.com/puzpuzpuz/xsync/v2" ) type ServerOption struct { @@ -33,6 +34,7 @@ func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid quicConn: quicConn, uuid: uuid, authCh: make(chan struct{}), + udpInputMap: xsync.NewIntegerMapOf[uint32, *atomic.Bool](), } } @@ -45,7 +47,7 @@ type serverHandler struct { authOk atomic.Bool authOnce sync.Once - udpInputMap sync.Map + udpInputMap *xsync.MapOf[uint32, *atomic.Bool] } func (s *serverHandler) AuthOk() bool { @@ -78,8 +80,7 @@ func (s *serverHandler) parsePacket(packet *Packet, udpRelayMode common.UdpRelay assocId = packet.ASSOC_ID - v, _ := s.udpInputMap.LoadOrStore(assocId, &atomic.Bool{}) - writeClosed := v.(*atomic.Bool) + writeClosed, _ := s.udpInputMap.LoadOrCompute(assocId, func() *atomic.Bool { return &atomic.Bool{} }) if writeClosed.Load() { return nil } @@ -173,8 +174,7 @@ func (s *serverHandler) HandleUniStream(reader *bufio.Reader) (err error) { if err != nil { return } - if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded { - writeClosed := v.(*atomic.Bool) + if writeClosed, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded { writeClosed.Store(true) } case HeartbeatType: diff --git a/transport/tuic/v5/client.go b/transport/tuic/v5/client.go index 74dfd581..c4ac25d4 100644 --- a/transport/tuic/v5/client.go +++ b/transport/tuic/v5/client.go @@ -20,6 +20,7 @@ import ( "github.com/Dreamacro/clash/transport/tuic/common" "github.com/metacubex/quic-go" + "github.com/puzpuzpuz/xsync/v2" "github.com/zhangyunhao116/fastrand" ) @@ -46,7 +47,7 @@ type clientImpl struct { openStreams atomic.Int64 closed atomic.Bool - udpInputMap sync.Map + udpInputMap xsync.MapOf[uint16, net.Conn] // only ready for PoolClient dialerRef C.Dialer @@ -270,10 +271,9 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) { _ = quicConn.CloseWithError(ProtocolError, errStr) } udpInputMap := &t.udpInputMap - udpInputMap.Range(func(key, value any) bool { - if conn, ok := value.(net.Conn); ok { - _ = conn.Close() - } + udpInputMap.Range(func(key uint16, value net.Conn) bool { + conn := value + _ = conn.Close() udpInputMap.Delete(key) return true }) @@ -406,6 +406,7 @@ func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client ClientOption: clientOption, udp: udp, dialerRef: dialerRef, + udpInputMap: *xsync.NewIntegerMapOf[uint16, net.Conn](), } c := &Client{ci} runtime.SetFinalizer(c, closeClient) diff --git a/transport/tuic/v5/frag.go b/transport/tuic/v5/frag.go index ae9dbf10..8df9f785 100644 --- a/transport/tuic/v5/frag.go +++ b/transport/tuic/v5/frag.go @@ -9,6 +9,11 @@ import ( "github.com/metacubex/quic-go" ) +// MaxFragSize is a safe udp relay packet size +// because tuicv5 support udp fragment so we unneeded to do a magic modify for quic-go to increase MaxDatagramFrameSize +// it may not work fine in some platform +var MaxFragSize = 1200 - PacketOverHead + func fragWriteNative(quicConn quic.Connection, packet Packet, buf *bytes.Buffer, fragSize int) (err error) { fullPayload := packet.DATA off := 0 diff --git a/transport/tuic/v5/packet.go b/transport/tuic/v5/packet.go index cd3ed12b..efbe0bb9 100644 --- a/transport/tuic/v5/packet.go +++ b/transport/tuic/v5/packet.go @@ -96,10 +96,10 @@ func (q *quicStreamPacketConn) SetWriteDeadline(t time.Time) error { } func (q *quicStreamPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) { - if q.inputConn != nil { + if inputConn := q.inputConn; inputConn != nil { // copy inputConn avoid be nil in for loop for { var packet Packet - packet, err = ReadPacket(q.inputConn) + packet, err = ReadPacket(inputConn) if err != nil { return } @@ -116,10 +116,10 @@ func (q *quicStreamPacketConn) ReadFrom(p []byte) (n int, addr net.Addr, err err } func (q *quicStreamPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) { - if q.inputConn != nil { + if inputConn := q.inputConn; inputConn != nil { // copy inputConn avoid be nil in for loop for { var packet Packet - packet, err = ReadPacket(q.inputConn) + packet, err = ReadPacket(inputConn) if err != nil { return } diff --git a/transport/tuic/v5/server.go b/transport/tuic/v5/server.go index 30259583..10003a9d 100644 --- a/transport/tuic/v5/server.go +++ b/transport/tuic/v5/server.go @@ -16,6 +16,7 @@ import ( "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" + "github.com/puzpuzpuz/xsync/v2" ) type ServerOption struct { @@ -32,6 +33,7 @@ func NewServerHandler(option *ServerOption, quicConn quic.EarlyConnection, uuid quicConn: quicConn, uuid: uuid, authCh: make(chan struct{}), + udpInputMap: xsync.NewIntegerMapOf[uint16, *serverUDPInput](), } } @@ -45,7 +47,7 @@ type serverHandler struct { authUUID atomic.TypedValue[string] authOnce sync.Once - udpInputMap sync.Map + udpInputMap *xsync.MapOf[uint16, *serverUDPInput] } func (s *serverHandler) AuthOk() bool { @@ -94,8 +96,7 @@ func (s *serverHandler) parsePacket(packet *Packet, udpRelayMode common.UdpRelay assocId = packet.ASSOC_ID - v, _ := s.udpInputMap.LoadOrStore(assocId, &serverUDPInput{}) - input := v.(*serverUDPInput) + input, _ := s.udpInputMap.LoadOrCompute(assocId, func() *serverUDPInput { return &serverUDPInput{} }) if input.writeClosed.Load() { return nil } @@ -186,8 +187,7 @@ func (s *serverHandler) HandleUniStream(reader *bufio.Reader) (err error) { if err != nil { return } - if v, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded { - input := v.(*serverUDPInput) + if input, loaded := s.udpInputMap.LoadAndDelete(disassociate.ASSOC_ID); loaded { input.writeClosed.Store(true) } } diff --git a/transport/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go index 25483670..066a3e2a 100644 --- a/transport/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -6,7 +6,7 @@ import ( "net" "net/http" - tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/Dreamacro/clash/component/ca" "github.com/Dreamacro/clash/transport/vmess" ) @@ -43,13 +43,10 @@ func NewV2rayObfs(ctx context.Context, conn net.Conn, option *Option) (net.Conn, InsecureSkipVerify: option.SkipCertVerify, NextProtos: []string{"http/1.1"}, } - if len(option.Fingerprint) == 0 { - config.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - var err error - if config.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { - return nil, err - } + var err error + config.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) + if err != nil { + return nil, err } if host := config.Headers.Get("Host"); host != "" { diff --git a/transport/vmess/tls.go b/transport/vmess/tls.go index 54813029..8bcb6513 100644 --- a/transport/vmess/tls.go +++ b/transport/vmess/tls.go @@ -6,6 +6,7 @@ import ( "errors" "net" + "github.com/Dreamacro/clash/component/ca" tlsC "github.com/Dreamacro/clash/component/tls" ) @@ -25,13 +26,10 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn NextProtos: cfg.NextProtos, } - if len(cfg.FingerPrint) == 0 { - tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) - } else { - var err error - if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, cfg.FingerPrint); err != nil { - return nil, err - } + var err error + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, cfg.FingerPrint) + if err != nil { + return nil, err } if len(cfg.ClientFingerprint) != 0 { @@ -51,7 +49,7 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn tlsConn := tls.Client(conn, tlsConfig) - err := tlsConn.HandshakeContext(ctx) + err = tlsConn.HandshakeContext(ctx) return tlsConn, err } diff --git a/tunnel/connection.go b/tunnel/connection.go index 2e76b86b..9fc4f405 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -73,12 +73,9 @@ func handleUDPToLocal(writeBack C.WriteBack, pc N.EnhancePacketConn, key string, } func closeAllLocalCoon(lAddr string) { - natTable.RangeLocalConn(lAddr, func(key, value any) bool { - conn, ok := value.(*net.UDPConn) - if !ok || conn == nil { - log.Debugln("Value %#v unknown value when closing TProxy local conn...", conn) - return true - } + natTable.RangeForLocalConn(lAddr, func(key string, value *net.UDPConn) bool { + conn := value + conn.Close() log.Debugln("Closing TProxy local conn... lAddr=%s rAddr=%s", lAddr, key) return true diff --git a/tunnel/statistic/manager.go b/tunnel/statistic/manager.go index 2358d0bd..19ce58d9 100644 --- a/tunnel/statistic/manager.go +++ b/tunnel/statistic/manager.go @@ -2,11 +2,11 @@ package statistic import ( "os" - "sync" "time" "github.com/Dreamacro/clash/common/atomic" + "github.com/puzpuzpuz/xsync/v2" "github.com/shirou/gopsutil/v3/process" ) @@ -14,6 +14,7 @@ var DefaultManager *Manager func init() { DefaultManager = &Manager{ + connections: xsync.NewMapOf[Tracker](), uploadTemp: atomic.NewInt64(0), downloadTemp: atomic.NewInt64(0), uploadBlip: atomic.NewInt64(0), @@ -27,7 +28,7 @@ func init() { } type Manager struct { - connections sync.Map + connections *xsync.MapOf[string, Tracker] uploadTemp *atomic.Int64 downloadTemp *atomic.Int64 uploadBlip *atomic.Int64 @@ -48,14 +49,14 @@ func (m *Manager) Leave(c Tracker) { func (m *Manager) Get(id string) (c Tracker) { if value, ok := m.connections.Load(id); ok { - c = value.(Tracker) + c = value } return } func (m *Manager) Range(f func(c Tracker) bool) { - m.connections.Range(func(key, value any) bool { - return f(value.(Tracker)) + m.connections.Range(func(key string, value Tracker) bool { + return f(value) }) } diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index d4c15a87..ff64915a 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -127,6 +127,20 @@ func Proxies() map[string]C.Proxy { return proxies } +func ProxiesWithProviders() map[string]C.Proxy { + allProxies := make(map[string]C.Proxy) + for name, proxy := range proxies { + allProxies[name] = proxy + } + for _, p := range providers { + for _, proxy := range p.Proxies() { + name := proxy.Name() + allProxies[name] = proxy + } + } + return allProxies +} + // Providers return all compatible providers func Providers() map[string]provider.ProxyProvider { return providers @@ -318,8 +332,7 @@ func handleUDPConn(packet C.PacketAdapter) { return } - lockKey := key + "-lock" - cond, loaded := natTable.GetOrCreateLock(lockKey) + cond, loaded := natTable.GetOrCreateLock(key) go func() { defer packet.Drop() @@ -333,7 +346,7 @@ func handleUDPConn(packet C.PacketAdapter) { } defer func() { - natTable.Delete(lockKey) + natTable.DeleteLock(key) cond.Broadcast() }() @@ -406,15 +419,28 @@ func handleTCPConn(connCtx C.ConnContext) { return } + preHandleFailed := false if err := preHandleMetadata(metadata); err != nil { log.Debugln("[Metadata PreHandle] error: %s", err) - return + preHandleFailed = true } conn := connCtx.Conn() conn.ResetPeeked() // reset before sniffer if sniffer.Dispatcher.Enable() && sniffingEnable { - sniffer.Dispatcher.TCPSniff(conn, metadata) + // Try to sniff a domain when `preHandleMetadata` failed, this is usually + // caused by a "Fake DNS record missing" error when enhanced-mode is fake-ip. + if sniffer.Dispatcher.TCPSniff(conn, metadata) { + // we now have a domain name + preHandleFailed = false + } + } + + // If both trials have failed, we can do nothing but give up + if preHandleFailed { + log.Debugln("[Metadata PreHandle] failed to sniff a domain for connection %s --> %s, give up", + metadata.SourceDetail(), metadata.RemoteAddress()) + return } peekMutex := sync.Mutex{}