diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index 4e4efc72..ea859c42 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -1,7 +1,7 @@ -name: Alpha +name: alpha on: [push] jobs: - Feature-build: + Build: if: ${{ !contains(github.event.head_commit.message, '[Skip CI]') }} runs-on: ubuntu-latest steps: @@ -57,15 +57,15 @@ jobs: files: bin/* prerelease: true - - name: send telegram message on push - uses: appleboy/telegram-action@master - with: - to: ${{ secrets.TTELEGRAM_CHAT_ID }} - token: ${{ secrets.TELEGRAM_TOKEN }} - message: | - ${{ github.actor }} created commit: - Commit message: ${{ github.event.commits[0].message }} - - Repository: ${{ github.repository }} - - See changes: https://github.com/${{ github.repository }}/commit/${{github.sha}} \ No newline at end of file +# - name: send telegram message on push +# uses: appleboy/telegram-action@master +# with: +# to: ${{ secrets.TTELEGRAM_CHAT_ID }} +# token: ${{ secrets.TELEGRAM_TOKEN }} +# message: | +# ${{ github.actor }} created commit: +# Commit message: ${{ github.event.commits[0].message }} +# +# Repository: ${{ github.repository }} +# +# See changes: https://github.com/${{ github.repository }}/commit/${{github.sha}} \ No newline at end of file diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index e9119415..6b5c61f6 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -2,9 +2,12 @@ package outbound import ( "context" + "crypto/sha1" + "encoding/hex" "encoding/json" "errors" "net" + "regexp" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" @@ -136,3 +139,28 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) { func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { return &packetConn{pc, []string{a.Name()}} } + +func uuidMap(str string) string { + match, _ := regexp.MatchString(`[\da-f]{8}(-[\da-f]{4}){3}-[\da-f]{12}$`, str) + if !match { + var Nil [16]byte + h := sha1.New() + h.Write(Nil[:]) + h.Write([]byte(str)) + u := h.Sum(nil)[:16] + u[6] = (u[6] & 0x0f) | (5 << 4) + u[8] = u[8]&(0xff>>2) | (0x02 << 6) + buf := make([]byte, 36) + hex.Encode(buf[0:8], u[0:4]) + buf[8] = '-' + hex.Encode(buf[9:13], u[4:6]) + buf[13] = '-' + hex.Encode(buf[14:18], u[6:8]) + buf[18] = '-' + hex.Encode(buf[19:23], u[8:10]) + buf[23] = '-' + hex.Encode(buf[24:], u[10:]) + return string(buf) + } + return str +} diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index d8e8e18f..1cea9ab5 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -386,7 +386,7 @@ func NewVless(option VlessOption) (*Vless, error) { } } - client, err := vless.NewClient(option.UUID, addons, option.FlowShow) + client, err := vless.NewClient(uuidMap(option.UUID), addons, option.FlowShow) if err != nil { return nil, err } diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index b32349d0..be3def96 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -266,7 +266,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o func NewVmess(option VmessOption) (*Vmess, error) { security := strings.ToLower(option.Cipher) client, err := vmess.NewClient(vmess.Config{ - UUID: option.UUID, + UUID: uuidMap(option.UUID), AlterID: uint16(option.AlterID), Security: security, HostName: option.Server, diff --git a/common/net/relay.go b/common/net/relay.go new file mode 100644 index 00000000..e7157639 --- /dev/null +++ b/common/net/relay.go @@ -0,0 +1,30 @@ +package net + +import ( + "io" + "net" + "time" + + "github.com/Dreamacro/clash/common/pool" +) + +// Relay copies between left and right bidirectionally. +func Relay(leftConn, rightConn net.Conn) { + ch := make(chan error) + + go func() { + buf := pool.Get(pool.RelayBufferSize) + // Wrapping to avoid using *net.TCPConn.(ReadFrom) + // See also https://github.com/Dreamacro/clash/pull/1209 + _, err := io.CopyBuffer(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}, buf) + pool.Put(buf) + leftConn.SetReadDeadline(time.Now()) + ch <- err + }() + + buf := pool.Get(pool.RelayBufferSize) + io.CopyBuffer(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}, buf) + pool.Put(buf) + rightConn.SetReadDeadline(time.Now()) + <-ch +} diff --git a/common/structure/structure.go b/common/structure/structure.go index 31b07024..6b038bd2 100644 --- a/common/structure/structure.go +++ b/common/structure/structure.go @@ -31,7 +31,7 @@ func NewDecoder(option Option) *Decoder { // Decode transform a map[string]any to a struct func (d *Decoder) Decode(src map[string]any, dst any) error { if reflect.TypeOf(dst).Kind() != reflect.Ptr { - return fmt.Errorf("Decode must recive a ptr struct") + return fmt.Errorf("decode must recive a ptr struct") } t := reflect.TypeOf(dst).Elem() v := reflect.ValueOf(dst).Elem() @@ -291,7 +291,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e field reflect.StructField val reflect.Value } - fields := []field{} + var fields []field for len(structs) > 0 { structVal := structs[0] structs = structs[1:] diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 1766f4ad..5b2027fc 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -4,14 +4,19 @@ import ( "context" "errors" "fmt" - "github.com/Dreamacro/clash/log" "net" "net/netip" + "sync" "github.com/Dreamacro/clash/component/resolver" ) -var DisableIPv6 = false +var ( + dialMux sync.Mutex + actualSingleDialContext = singleDialContext + actualDualStackDialContext = dualStackDialContext + DisableIPv6 = false +) func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { opt := &option{ @@ -29,37 +34,9 @@ func DialContext(ctx context.Context, network, address string, options ...Option switch network { case "tcp4", "tcp6", "udp4", "udp6": - host, port, err := net.SplitHostPort(address) - if err != nil { - return nil, err - } - - var ip netip.Addr - switch network { - case "tcp4", "udp4": - if !opt.direct { - ip, err = resolver.ResolveIPv4ProxyServerHost(host) - } else { - ip, err = resolver.ResolveIPv4(host) - } - default: - if !opt.direct { - ip, err = resolver.ResolveIPv6ProxyServerHost(host) - } else { - ip, err = resolver.ResolveIPv6(host) - } - } - if err != nil { - return nil, err - } - - return dialContext(ctx, network, ip, port, opt) + return actualSingleDialContext(ctx, network, address, opt) case "tcp", "udp": - if TCPConcurrent { - return concurrentDialContext(ctx, network, address, opt) - } else { - return dualStackDialContext(ctx, network, address, opt) - } + return actualDualStackDialContext(ctx, network, address, opt) default: return nil, errors.New("network invalid") } @@ -97,6 +74,19 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio return lc.ListenPacket(ctx, network, address) } +func SetDial(concurrent bool) { + dialMux.Lock() + if concurrent { + actualSingleDialContext = concurrentSingleDialContext + actualDualStackDialContext = concurrentDualStackDialContext + } else { + actualSingleDialContext = singleDialContext + actualDualStackDialContext = concurrentDualStackDialContext + } + + dialMux.Unlock() +} + func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { dialer := &net.Dialer{} if opt.interfaceName != "" { @@ -196,12 +186,24 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt return nil, errors.New("never touched") } -func concurrentDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { +func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { host, port, err := net.SplitHostPort(address) if err != nil { return nil, err } + var ips []netip.Addr + + if opt.direct { + ips, err = resolver.ResolveAllIP(host) + } else { + ips, err = resolver.ResolveAllIPProxyServerHost(host) + } + + return concurrentDialContext(ctx, network, ips, port, opt) +} + +func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { returned := make(chan struct{}) defer close(returned) @@ -213,13 +215,6 @@ func concurrentDialContext(ctx context.Context, network, address string, opt *op } results := make(chan dialResult) - var ips []netip.Addr - - if opt.direct { - ips, err = resolver.ResolveAllIP(host) - } else { - ips, err = resolver.ResolveAllIPProxyServerHost(host) - } tcpRacer := func(ctx context.Context, ip netip.Addr) { result := dialResult{ip: ip} @@ -239,7 +234,6 @@ func concurrentDialContext(ctx context.Context, network, address string, opt *op v = "6" } - log.Debugln("[%s] try use [%s] connected", host, ip.String()) result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt) } @@ -251,16 +245,70 @@ func concurrentDialContext(ctx context.Context, network, address string, opt *op for res := range results { connCount-- if res.error == nil { - log.Debugln("[%s] used [%s] connected", host, res.ip.String()) return res.Conn, nil } - log.Errorln("connect error:%v", res.error) if connCount == 0 { - log.Errorln("connect [%s] all ip failed", host) break } } - return nil, errors.New("all ip tcp shakeHands failed") + return nil, errors.New("all ip tcp shake hands failed") +} + +func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + var ip netip.Addr + switch network { + case "tcp4", "udp4": + if !opt.direct { + ip, err = resolver.ResolveIPv4ProxyServerHost(host) + } else { + ip, err = resolver.ResolveIPv4(host) + } + default: + if !opt.direct { + ip, err = resolver.ResolveIPv6ProxyServerHost(host) + } else { + ip, err = resolver.ResolveIPv6(host) + } + } + if err != nil { + return nil, err + } + + return dialContext(ctx, network, ip, port, opt) +} + +func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) { + host, port, err := net.SplitHostPort(address) + if err != nil { + return nil, err + } + + var ips []netip.Addr + switch network { + case "tcp4", "udp4": + if !opt.direct { + ips, err = resolver.ResolveAllIPv4ProxyServerHost(host) + } else { + ips, err = resolver.ResolveAllIPv4(host) + } + default: + if !opt.direct { + ips, err = resolver.ResolveAllIPv6ProxyServerHost(host) + } else { + ips, err = resolver.ResolveAllIPv6(host) + } + } + + if err != nil { + return nil, err + } + + return concurrentDialContext(ctx, network, ips, port, opt) } diff --git a/component/dialer/options.go b/component/dialer/options.go index bde81a09..2985dc7b 100644 --- a/component/dialer/options.go +++ b/component/dialer/options.go @@ -6,7 +6,6 @@ var ( DefaultOptions []Option DefaultInterface = atomic.NewString("") DefaultRoutingMark = atomic.NewInt32(0) - TCPConcurrent = false ) type option struct { diff --git a/component/sniffer/dispatcher.go b/component/sniffer/dispatcher.go index a712c1f0..9d47d36f 100644 --- a/component/sniffer/dispatcher.go +++ b/component/sniffer/dispatcher.go @@ -23,15 +23,17 @@ var ( var Dispatcher SnifferDispatcher -type SnifferDispatcher struct { - enable bool +type ( + SnifferDispatcher struct { + enable bool - sniffers []C.Sniffer + sniffers []C.Sniffer - foreDomain *trie.DomainTrie[bool] - skipSNI *trie.DomainTrie[bool] - portRanges *[]utils.Range[uint16] -} + foreDomain *trie.DomainTrie[bool] + skipSNI *trie.DomainTrie[bool] + portRanges *[]utils.Range[uint16] + } +) func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { bufConn, ok := conn.(*CN.BufferedConn) @@ -101,7 +103,6 @@ func (sd *SnifferDispatcher) sniffDomain(conn *CN.BufferedConn, metadata *C.Meta log.Errorln("[Sniffer] [%s] Maybe read timeout, Consider adding skip", metadata.DstIP.String()) conn.Close() } - log.Errorln("[Sniffer] %v", err) return "", err } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 75476527..565d61cd 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -254,8 +254,8 @@ func updateGeneral(general *config.General, force bool) { resolver.DisableIPv6 = true } - dialer.TCPConcurrent = general.TCPConcurrent - if dialer.TCPConcurrent { + if general.TCPConcurrent { + dialer.SetDial(general.TCPConcurrent) log.Infoln("Use tcp concurrent") } diff --git a/listener/http/proxy.go b/listener/http/proxy.go index e8a805a9..b57ff4f3 100644 --- a/listener/http/proxy.go +++ b/listener/http/proxy.go @@ -19,12 +19,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string, client := newClient(c.RemoteAddr(), in) defer client.CloseIdleConnections() - var conn *N.BufferedConn - if bufConn, ok := c.(*N.BufferedConn); ok { - conn = bufConn - } else { - conn = N.NewBufferedConn(c) - } + conn := N.NewBufferedConn(c) keepAlive := true trusted := cache == nil // disable authenticate if cache is nil @@ -66,6 +61,12 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string, request.RequestURI = "" + if isUpgradeRequest(request) { + if resp = handleUpgrade(conn, conn.RemoteAddr(), request, in); resp == nil { + return // hijack connection + } + } + removeHopByHopHeaders(request.Header) removeExtraHTTPHostPort(request) @@ -95,7 +96,7 @@ func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache[string, } } - conn.Close() + _ = conn.Close() } func authenticate(request *http.Request, cache *cache.Cache[string, bool]) *http.Response { diff --git a/listener/http/upgrade.go b/listener/http/upgrade.go new file mode 100644 index 00000000..4203e17a --- /dev/null +++ b/listener/http/upgrade.go @@ -0,0 +1,95 @@ +package http + +import ( + "context" + "crypto/tls" + "net" + "net/http" + "strings" + "time" + + "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" +) + +func isUpgradeRequest(req *http.Request) bool { + return strings.EqualFold(req.Header.Get("Connection"), "Upgrade") +} + +func handleUpgrade(localConn net.Conn, source net.Addr, request *http.Request, in chan<- C.ConnContext) (resp *http.Response) { + removeProxyHeaders(request.Header) + removeExtraHTTPHostPort(request) + + address := request.Host + if _, _, err := net.SplitHostPort(address); err != nil { + port := "80" + if request.TLS != nil { + port = "443" + } + address = net.JoinHostPort(address, port) + } + + dstAddr := socks5.ParseAddr(address) + if dstAddr == nil { + return + } + + left, right := net.Pipe() + + in <- inbound.NewHTTP(dstAddr, source, right) + + var remoteServer *N.BufferedConn + if request.TLS != nil { + tlsConn := tls.Client(left, &tls.Config{ + ServerName: request.URL.Hostname(), + }) + + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + if tlsConn.HandshakeContext(ctx) != nil { + _ = localConn.Close() + _ = left.Close() + return + } + + remoteServer = N.NewBufferedConn(tlsConn) + } else { + remoteServer = N.NewBufferedConn(left) + } + defer func() { + _ = remoteServer.Close() + }() + + err := request.Write(remoteServer) + if err != nil { + _ = localConn.Close() + return + } + + resp, err = http.ReadResponse(remoteServer.Reader(), request) + if err != nil { + _ = localConn.Close() + return + } + + if resp.StatusCode == http.StatusSwitchingProtocols { + removeProxyHeaders(resp.Header) + + err = localConn.SetReadDeadline(time.Time{}) // set to not time out + if err != nil { + return + } + + err = resp.Write(localConn) + if err != nil { + return + } + + N.Relay(remoteServer, localConn) // blocking here + _ = localConn.Close() + resp = nil + } + return +} diff --git a/listener/http/utils.go b/listener/http/utils.go index bcee60f0..63726d51 100644 --- a/listener/http/utils.go +++ b/listener/http/utils.go @@ -8,15 +8,21 @@ import ( "strings" ) +// removeHopByHopHeaders remove Proxy-* headers +func removeProxyHeaders(header http.Header) { + header.Del("Proxy-Connection") + header.Del("Proxy-Authenticate") + header.Del("Proxy-Authorization") +} + // removeHopByHopHeaders remove hop-by-hop header func removeHopByHopHeaders(header http.Header) { // Strip hop-by-hop header based on RFC: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1 // https://www.mnot.net/blog/2011/07/11/what_proxies_must_do - header.Del("Proxy-Connection") - header.Del("Proxy-Authenticate") - header.Del("Proxy-Authorization") + removeProxyHeaders(header) + header.Del("TE") header.Del("Trailers") header.Del("Transfer-Encoding") diff --git a/listener/tun/ipstack/system/mars/nat/nat.go b/listener/tun/ipstack/system/mars/nat/nat.go index 9f6f57d2..b8b197c5 100644 --- a/listener/tun/ipstack/system/mars/nat/nat.go +++ b/listener/tun/ipstack/system/mars/nat/nat.go @@ -1,6 +1,7 @@ package nat import ( + log "github.com/sirupsen/logrus" "io" "net" "net/netip" @@ -43,7 +44,8 @@ func Start(device io.ReadWriter, gateway, portal, broadcast netip.Addr) (*TCP, * for { n, err := device.Read(buf) if err != nil { - return + log.Warnln("system error:%s", err.Error()) + continue } raw := buf[:n] diff --git a/listener/tun/ipstack/system/mars/nat/table.go b/listener/tun/ipstack/system/mars/nat/table.go index 9c1b32cd..38b7d6c6 100644 --- a/listener/tun/ipstack/system/mars/nat/table.go +++ b/listener/tun/ipstack/system/mars/nat/table.go @@ -2,7 +2,6 @@ package nat import ( "net/netip" - "sync" "github.com/Dreamacro/clash/common/generics/list" ) @@ -25,7 +24,6 @@ type binding struct { } type table struct { - mu sync.Mutex tuples map[tuple]*list.Element[*binding] ports [portLength]*list.Element[*binding] available *list.List[*binding] @@ -39,13 +37,13 @@ func (t *table) tupleOf(port uint16) tuple { elm := t.ports[offset] + t.available.MoveToFront(elm) + return elm.Value.tuple } func (t *table) portOf(tuple tuple) uint16 { - t.mu.Lock() elm := t.tuples[tuple] - t.mu.Unlock() if elm == nil { return 0 } @@ -59,11 +57,8 @@ func (t *table) newConn(tuple tuple) uint16 { elm := t.available.Back() b := elm.Value - t.mu.Lock() delete(t.tuples, b.tuple) t.tuples[tuple] = elm - t.mu.Unlock() - b.tuple = tuple t.available.MoveToFront(elm) @@ -71,19 +66,6 @@ func (t *table) newConn(tuple tuple) uint16 { return portBegin + b.offset } -func (t *table) delete(tup tuple) { - t.mu.Lock() - elm := t.tuples[tup] - if elm == nil { - t.mu.Unlock() - return - } - delete(t.tuples, tup) - t.mu.Unlock() - - t.available.MoveToBack(elm) -} - func newTable() *table { result := &table{ tuples: make(map[tuple]*list.Element[*binding], portLength), diff --git a/listener/tun/ipstack/system/mars/nat/tcp.go b/listener/tun/ipstack/system/mars/nat/tcp.go index 48ad3e43..cc0abe7d 100644 --- a/listener/tun/ipstack/system/mars/nat/tcp.go +++ b/listener/tun/ipstack/system/mars/nat/tcp.go @@ -16,8 +16,6 @@ type conn struct { net.Conn tuple tuple - - close func(tuple tuple) } func (t *TCP) Accept() (net.Conn, error) { @@ -39,9 +37,6 @@ func (t *TCP) Accept() (net.Conn, error) { return &conn{ Conn: c, tuple: tup, - close: func(tuple tuple) { - t.table.delete(tuple) - }, }, nil } @@ -57,11 +52,6 @@ func (t *TCP) SetDeadline(time time.Time) error { return t.listener.SetDeadline(time) } -func (c *conn) Close() error { - c.close(c.tuple) - return c.Conn.Close() -} - func (c *conn) LocalAddr() net.Addr { return &net.TCPAddr{ IP: c.tuple.SourceAddr.Addr().AsSlice(), diff --git a/tunnel/connection.go b/tunnel/connection.go index 82443c35..0384e805 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -2,7 +2,6 @@ package tunnel import ( "errors" - "io" "net" "time" @@ -63,35 +62,5 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, fAddr n } func handleSocket(ctx C.ConnContext, outbound net.Conn) { - relay(ctx.Conn(), outbound) -} - -// relay copies between left and right bidirectionally. -func relay(leftConn, rightConn net.Conn) { - ch := make(chan error) - - tcpKeepAlive(leftConn) - tcpKeepAlive(rightConn) - - go func() { - buf := pool.Get(pool.RelayBufferSize) - // Wrapping to avoid using *net.TCPConn.(ReadFrom) - // See also https://github.com/Dreamacro/clash/pull/1209 - _, err := io.CopyBuffer(N.WriteOnlyWriter{Writer: leftConn}, N.ReadOnlyReader{Reader: rightConn}, buf) - pool.Put(buf) - leftConn.SetReadDeadline(time.Now()) - ch <- err - }() - - buf := pool.Get(pool.RelayBufferSize) - io.CopyBuffer(N.WriteOnlyWriter{Writer: rightConn}, N.ReadOnlyReader{Reader: leftConn}, buf) - pool.Put(buf) - rightConn.SetReadDeadline(time.Now()) - <-ch -} - -func tcpKeepAlive(c net.Conn) { - if tcp, ok := c.(*net.TCPConn); ok { - tcp.SetKeepAlive(true) - } + N.Relay(ctx.Conn(), outbound) }