diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34c80619..f9e04574 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,11 +15,11 @@ on: - Alpha concurrency: - group: ${{ github.ref }}-${{ github.workflow }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: - REGISTRY: ghcr.io + REGISTRY: docker.io jobs: Build: permissions: write-all @@ -48,7 +48,7 @@ jobs: target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat", id: "4", } - - { type: "WithoutCGO", target: "linux-386 linux-riscv64", id: "5" } + - { type: "WithoutCGO", target: "linux-386 linux-riscv64 linux-loong64", id: "5" } - { type: "WithoutCGO", target: "freebsd-386 freebsd-amd64 freebsd-arm64", @@ -75,17 +75,17 @@ jobs: - { type: "WithoutCGO-GO120", target: "windows-amd64-compatible windows-amd64 windows-386",id: "2" } # Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later. - { type: "WithoutCGO-GO120", target: "darwin-amd64 darwin-arm64 android-arm64",id: "3" } - - { type: "WithCGO", target: "windows/*", id: "1" } - - { type: "WithCGO", target: "linux/386", id: "2" } - - { type: "WithCGO", target: "linux/amd64", id: "3" } - - { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" } - - { type: "WithCGO", target: "linux/arm,", id: "5" } - - { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" } - - { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" } - - { type: "WithCGO", target: "linux/mips64", id: "8" } - - { type: "WithCGO", target: "linux/mips64le", id: "9" } - - { type: "WithCGO", target: "darwin-10.16/*", id: "10" } - - { type: "WithCGO", target: "android", id: "11" } +# - { type: "WithCGO", target: "windows/*", id: "1" } +# - { type: "WithCGO", target: "linux/386", id: "2" } +# - { type: "WithCGO", target: "linux/amd64", id: "3" } +# - { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" } +# - { type: "WithCGO", target: "linux/arm,", id: "5" } +# - { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" } +# - { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" } +# - { type: "WithCGO", target: "linux/mips64", id: "8" } +# - { type: "WithCGO", target: "linux/mips64le", id: "9" } +# - { type: "WithCGO", target: "darwin-10.16/*", id: "10" } +# - { type: "WithCGO", target: "android", id: "11" } steps: - name: Check out code into the Go module directory @@ -118,17 +118,14 @@ jobs: - name: Set ENV run: | sudo timedatectl set-timezone "Asia/Shanghai" - echo "NAME=mihomo" >> $GITHUB_ENV - echo "REPO=${{ github.repository }}" >> $GITHUB_ENV - echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV echo "BUILDTIME=$(date)" >> $GITHUB_ENV - echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV shell: bash - name: Set ENV run: | echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV echo "LDFLAGS=-X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV + echo "GOTOOLCHAIN=local" >> $GITHUB_ENV shell: bash - name: Setup Go @@ -233,7 +230,7 @@ jobs: Upload-Prerelease: permissions: write-all - if: ${{ github.ref_type=='branch' && github.event_name != 'pull_request' }} + if: ${{ github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }} needs: [Build] runs-on: ubuntu-latest steps: @@ -309,7 +306,7 @@ jobs: generate_release_notes: true Docker: - if: ${{ github.event_name != 'pull_request' }} + if: ${{ !startsWith(github.event_name, 'pull_request') }} permissions: write-all needs: [Build] runs-on: ubuntu-latest @@ -349,12 +346,12 @@ jobs: ls . ls bin/ - - name: login to ghcr.io + - name: login to docker REGISTRY uses: docker/login-action@v3 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} # Build and push Docker image with Buildx (don't push on PR) # https://github.com/docker/build-push-action diff --git a/adapter/adapter.go b/adapter/adapter.go index ae9584be..da6171f1 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -26,17 +26,16 @@ const ( defaultHistoriesNum = 10 ) -type extraProxyState struct { - history *queue.Queue[C.DelayHistory] +type internalProxyState struct { alive atomic.Bool + history *queue.Queue[C.DelayHistory] } type Proxy struct { C.ProxyAdapter - history *queue.Queue[C.DelayHistory] alive atomic.Bool - url string - extra *xsync.MapOf[string, *extraProxyState] + history *queue.Queue[C.DelayHistory] + extra *xsync.MapOf[string, *internalProxyState] } // AliveForTestUrl implements C.Proxy @@ -81,7 +80,6 @@ func (p *Proxy) DelayHistory() []C.DelayHistory { for _, item := range queueM { histories = append(histories, item) } - return histories } @@ -92,11 +90,6 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory { if state, ok := p.extra.Load(url); ok { queueM = state.history.Copy() } - - if queueM == nil { - queueM = p.history.Copy() - } - histories := []C.DelayHistory{} for _, item := range queueM { histories = append(histories, item) @@ -104,61 +97,46 @@ func (p *Proxy) DelayHistoryForTestUrl(url string) []C.DelayHistory { return histories } -func (p *Proxy) ExtraDelayHistory() map[string][]C.DelayHistory { - extraHistory := map[string][]C.DelayHistory{} - - p.extra.Range(func(k string, v *extraProxyState) bool { +// ExtraDelayHistories return all delay histories for each test URL +// implements C.Proxy +func (p *Proxy) ExtraDelayHistories() map[string]C.ProxyState { + histories := map[string]C.ProxyState{} + p.extra.Range(func(k string, v *internalProxyState) bool { testUrl := k state := v - histories := []C.DelayHistory{} queueM := state.history.Copy() + var history []C.DelayHistory for _, item := range queueM { - histories = append(histories, item) + history = append(history, item) } - extraHistory[testUrl] = histories - + histories[testUrl] = C.ProxyState{ + Alive: state.alive.Load(), + History: history, + } return true }) - return extraHistory + return histories } -// LastDelay return last history record. if proxy is not alive, return the max value of uint16. +// LastDelayForTestUrl return last history record of the specified URL. if proxy is not alive, return the max value of uint16. // implements C.Proxy -func (p *Proxy) LastDelay() (delay uint16) { - var max uint16 = 0xffff - if !p.alive.Load() { - return max - } - - history := p.history.Last() - if history.Delay == 0 { - return max - } - return history.Delay -} - -// LastDelayForTestUrl implements C.Proxy func (p *Proxy) LastDelayForTestUrl(url string) (delay uint16) { - var max uint16 = 0xffff + var maxDelay uint16 = 0xffff - alive := p.alive.Load() - history := p.history.Last() + alive := false + var history C.DelayHistory if state, ok := p.extra.Load(url); ok { alive = state.alive.Load() history = state.history.Last() } - if !alive { - return max - } - - if history.Delay == 0 { - return max + if !alive || history.Delay == 0 { + return maxDelay } return history.Delay } @@ -173,8 +151,8 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { mapping := map[string]any{} _ = json.Unmarshal(inner, &mapping) mapping["history"] = p.DelayHistory() - mapping["extra"] = p.ExtraDelayHistory() - mapping["alive"] = p.AliveForTestUrl(p.url) + mapping["extra"] = p.ExtraDelayHistories() + mapping["alive"] = p.alive.Load() mapping["name"] = p.Name() mapping["udp"] = p.SupportUDP() mapping["xudp"] = p.SupportXUDP() @@ -187,47 +165,32 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (t uint16, err error) { defer func() { alive := err == nil - - if len(p.url) == 0 || url == p.url { - p.alive.Store(alive) - record := C.DelayHistory{Time: time.Now()} - if alive { - record.Delay = t - } - p.history.Put(record) - if p.history.Len() > defaultHistoriesNum { - p.history.Pop() - } - - // test URL configured by the proxy provider - if len(p.url) == 0 { - p.url = url - } - } else { - record := C.DelayHistory{Time: time.Now()} - if alive { - record.Delay = t - } - p.history.Put(record) - if p.history.Len() > defaultHistoriesNum { - p.history.Pop() - } - - state, ok := p.extra.Load(url) - if !ok { - state = &extraProxyState{ - history: queue.New[C.DelayHistory](defaultHistoriesNum), - alive: atomic.NewBool(true), - } - p.extra.Store(url, state) - } - - state.alive.Store(alive) - state.history.Put(record) - if state.history.Len() > defaultHistoriesNum { - state.history.Pop() - } + record := C.DelayHistory{Time: time.Now()} + if alive { + record.Delay = t } + + p.alive.Store(alive) + p.history.Put(record) + if p.history.Len() > defaultHistoriesNum { + p.history.Pop() + } + + state, ok := p.extra.Load(url) + if !ok { + state = &internalProxyState{ + history: queue.New[C.DelayHistory](defaultHistoriesNum), + alive: atomic.NewBool(true), + } + p.extra.Store(url, state) + } + + state.alive.Store(alive) + state.history.Put(record) + if state.history.Len() > defaultHistoriesNum { + state.history.Pop() + } + }() unifiedDelay := UnifiedDelay.Load() @@ -304,8 +267,7 @@ func NewProxy(adapter C.ProxyAdapter) *Proxy { ProxyAdapter: adapter, history: queue.New[C.DelayHistory](defaultHistoriesNum), alive: atomic.NewBool(true), - url: "", - extra: xsync.NewMapOf[string, *extraProxyState]()} + extra: xsync.NewMapOf[string, *internalProxyState]()} } func urlToMetadata(rawURL string) (addr C.Metadata, err error) { diff --git a/adapter/inbound/ipfilter.go b/adapter/inbound/ipfilter.go new file mode 100644 index 00000000..7fa218c1 --- /dev/null +++ b/adapter/inbound/ipfilter.go @@ -0,0 +1,57 @@ +package inbound + +import ( + "net" + "net/netip" + + C "github.com/metacubex/mihomo/constant" +) + +var lanAllowedIPs []netip.Prefix +var lanDisAllowedIPs []netip.Prefix + +func SetAllowedIPs(prefixes []netip.Prefix) { + lanAllowedIPs = prefixes +} + +func SetDisAllowedIPs(prefixes []netip.Prefix) { + lanDisAllowedIPs = prefixes +} + +func AllowedIPs() []netip.Prefix { + return lanAllowedIPs +} + +func DisAllowedIPs() []netip.Prefix { + return lanDisAllowedIPs +} + +func IsRemoteAddrDisAllowed(addr net.Addr) bool { + m := C.Metadata{} + if err := m.SetRemoteAddr(addr); err != nil { + return false + } + return isAllowed(m.AddrPort().Addr().Unmap()) && !isDisAllowed(m.AddrPort().Addr().Unmap()) +} + +func isAllowed(addr netip.Addr) bool { + if addr.IsValid() { + for _, prefix := range lanAllowedIPs { + if prefix.Contains(addr) { + return true + } + } + } + return false +} + +func isDisAllowed(addr netip.Addr) bool { + if addr.IsValid() { + for _, prefix := range lanDisAllowedIPs { + if prefix.Contains(addr) { + return true + } + } + } + return false +} diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index b9b9fefc..7def7b20 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -3,6 +3,7 @@ package outbound import ( "context" "errors" + "fmt" "net/netip" N "github.com/metacubex/mihomo/common/net" @@ -13,6 +14,7 @@ import ( type Direct struct { *Base + loopBack *loopBackDetector } type DirectOption struct { @@ -22,17 +24,23 @@ type DirectOption struct { // DialContext implements C.ProxyAdapter func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { + if d.loopBack.CheckConn(metadata.SourceAddrPort()) { + return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) + } opts = append(opts, dialer.WithResolver(resolver.DefaultResolver)) c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) if err != nil { return nil, err } N.TCPKeepAlive(c) - return NewConn(c, d), nil + return d.loopBack.NewConn(NewConn(c, d)), nil } // ListenPacketContext implements C.ProxyAdapter func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { + if d.loopBack.CheckPacketConn(metadata.SourceAddrPort()) { + return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress()) + } // net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver) @@ -45,7 +53,7 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, if err != nil { return nil, err } - return newPacketConn(pc, d), nil + return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil } func NewDirectWithOption(option DirectOption) *Direct { @@ -60,6 +68,7 @@ func NewDirectWithOption(option DirectOption) *Direct { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, + loopBack: newLoopBackDetector(), } } @@ -71,6 +80,7 @@ func NewDirect() *Direct { udp: true, prefer: C.DualStack, }, + loopBack: newLoopBackDetector(), } } @@ -82,5 +92,6 @@ func NewCompatible() *Direct { udp: true, prefer: C.DualStack, }, + loopBack: newLoopBackDetector(), } } diff --git a/adapter/outbound/direct_loopback_detect.go b/adapter/outbound/direct_loopback_detect.go new file mode 100644 index 00000000..410d5a2f --- /dev/null +++ b/adapter/outbound/direct_loopback_detect.go @@ -0,0 +1,68 @@ +package outbound + +import ( + "net/netip" + + "github.com/metacubex/mihomo/common/callback" + C "github.com/metacubex/mihomo/constant" + + "github.com/puzpuzpuz/xsync/v3" +) + +type loopBackDetector struct { + connMap *xsync.MapOf[netip.AddrPort, struct{}] + packetConnMap *xsync.MapOf[netip.AddrPort, struct{}] +} + +func newLoopBackDetector() *loopBackDetector { + return &loopBackDetector{ + connMap: xsync.NewMapOf[netip.AddrPort, struct{}](), + packetConnMap: xsync.NewMapOf[netip.AddrPort, struct{}](), + } +} + +func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn { + metadata := C.Metadata{} + if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { + return conn + } + connAddr := metadata.AddrPort() + if !connAddr.IsValid() { + return conn + } + l.connMap.Store(connAddr, struct{}{}) + return callback.NewCloseCallbackConn(conn, func() { + l.connMap.Delete(connAddr) + }) +} + +func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn { + metadata := C.Metadata{} + if metadata.SetRemoteAddr(conn.LocalAddr()) != nil { + return conn + } + connAddr := metadata.AddrPort() + if !connAddr.IsValid() { + return conn + } + l.packetConnMap.Store(connAddr, struct{}{}) + return callback.NewCloseCallbackPacketConn(conn, func() { + l.packetConnMap.Delete(connAddr) + }) +} + +func (l *loopBackDetector) CheckConn(connAddr netip.AddrPort) bool { + if !connAddr.IsValid() { + return false + } + _, ok := l.connMap.Load(connAddr) + return ok +} + +func (l *loopBackDetector) CheckPacketConn(connAddr netip.AddrPort) bool { + if !connAddr.IsValid() { + return false + } + _, ok := l.packetConnMap.Load(connAddr) + return ok +} diff --git a/adapter/outbound/reality.go b/adapter/outbound/reality.go index 766138da..6711f378 100644 --- a/adapter/outbound/reality.go +++ b/adapter/outbound/reality.go @@ -1,13 +1,13 @@ package outbound import ( + "crypto/ecdh" "encoding/base64" "encoding/hex" "errors" + "fmt" tlsC "github.com/metacubex/mihomo/component/tls" - - "golang.org/x/crypto/curve25519" ) type RealityOptions struct { @@ -19,10 +19,16 @@ func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) { if o.PublicKey != "" { config := new(tlsC.RealityConfig) - n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey)) - if err != nil || n != curve25519.ScalarSize { + const x25519ScalarSize = 32 + var publicKey [x25519ScalarSize]byte + n, err := base64.RawURLEncoding.Decode(publicKey[:], []byte(o.PublicKey)) + if err != nil || n != x25519ScalarSize { return nil, errors.New("invalid REALITY public key") } + config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey[:]) + if err != nil { + return nil, fmt.Errorf("fail to create REALITY public key: %w", err) + } n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID)) if err != nil || n > tlsC.RealityMaxShortIDLen { diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index b564e28d..62f4aaa5 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -30,9 +30,6 @@ func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ... // ListenPacketContext implements C.ProxyAdapter func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - if r.drop { - return newPacketConn(&dropPacketConn{}, r), nil - } return newPacketConn(&nopPacketConn{}, r), nil } @@ -129,22 +126,3 @@ func (rw dropConn) RemoteAddr() net.Addr { return nil } func (rw dropConn) SetDeadline(time.Time) error { return nil } func (rw dropConn) SetReadDeadline(time.Time) error { return nil } func (rw dropConn) SetWriteDeadline(time.Time) error { return nil } - -type dropPacketConn struct{} - -func (npc dropPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { - time.Sleep(C.DefaultDropTime) - return len(b), nil -} -func (npc dropPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { - time.Sleep(C.DefaultDropTime) - return 0, nil, io.EOF -} -func (npc dropPacketConn) WaitReadFrom() ([]byte, func(), net.Addr, error) { - return nil, nil, nil, io.EOF -} -func (npc dropPacketConn) Close() error { return nil } -func (npc dropPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified } -func (npc dropPacketConn) SetDeadline(time.Time) error { return nil } -func (npc dropPacketConn) SetReadDeadline(time.Time) error { return nil } -func (npc dropPacketConn) SetWriteDeadline(time.Time) error { return nil } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 859a10d6..98932f0c 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -20,6 +20,7 @@ import ( restlsC "github.com/3andne/restls-client-go" shadowsocks "github.com/metacubex/sing-shadowsocks2" + "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/uot" ) @@ -187,7 +188,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial if err != nil { return nil, err } - pc = ss.method.DialPacketConn(N.NewBindPacketConn(pc, addr)) + pc = ss.method.DialPacketConn(bufio.NewBindPacketConn(pc, addr)) return newPacketConn(pc, ss), nil } @@ -210,9 +211,9 @@ func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, destination := M.SocksaddrFromNet(metadata.UDPAddr()) if ss.option.UDPOverTCPVersion == uot.LegacyVersion { - return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil + return newPacketConn(N.NewThreadSafePacketConn(uot.NewConn(c, uot.Request{Destination: destination})), ss), nil } else { - return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil + return newPacketConn(N.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), ss), nil } } return nil, C.ErrNotSupport diff --git a/adapter/outbound/util.go b/adapter/outbound/util.go index ce9e5f65..2c85c7c8 100644 --- a/adapter/outbound/util.go +++ b/adapter/outbound/util.go @@ -140,24 +140,25 @@ func StringToBps(s string) uint64 { if m == nil { return 0 } - var n uint64 + var n uint64 = 1 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 + n *= 1000 + fallthrough + case "G": + n *= 1000 + fallthrough + case "M": + n *= 1000 + fallthrough + case "K": + n *= 1000 } v, _ := strconv.ParseUint(m[1], 10, 64) - n = v * n + n *= v if m[3] == "b" { // Bits, need to convert to bytes - n = n >> 3 + n /= 8 } return n } diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 50427e53..488c2d34 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -89,6 +89,7 @@ func (f *Fallback) MarshalJSON() ([]byte, error) { "all": all, "testUrl": f.testUrl, "expectedStatus": f.expectedStatus, + "fixed": f.selected, }) } diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 7fbc91a7..79b18948 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -150,7 +150,6 @@ func strategyRoundRobin(url string) strategyFn { for ; i < length; i++ { id := (idx + i) % length proxy := proxies[id] - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { i++ return proxy @@ -169,7 +168,6 @@ func strategyConsistentHashing(url string) strategyFn { for i := 0; i < maxRetry; i, key = i+1, key+1 { idx := jumpHash(key, buckets) proxy := proxies[idx] - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { return proxy } @@ -177,7 +175,6 @@ func strategyConsistentHashing(url string) strategyFn { // when availability is poor, traverse the entire list to get the available nodes for _, proxy := range proxies { - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { return proxy } @@ -204,7 +201,6 @@ func strategyStickySessions(url string) strategyFn { nowIdx := idx for i := 1; i < maxRetry; i++ { proxy := proxies[nowIdx] - // if proxy.Alive() { if proxy.AliveForTestUrl(url) { if nowIdx != idx { lruCache.Delete(key) diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index 422349fe..3f7f9770 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -34,10 +34,12 @@ type GroupCommonOption struct { ExcludeFilter string `group:"exclude-filter,omitempty"` ExcludeType string `group:"exclude-type,omitempty"` ExpectedStatus string `group:"expected-status,omitempty"` + IncludeAll bool `group:"include-all,omitempty"` + IncludeAllProxies bool `group:"include-all-proxies,omitempty"` IncludeAllProviders bool `group:"include-all-providers,omitempty"` } -func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { +func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) { decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) groupOption := &GroupCommonOption{ @@ -55,18 +57,24 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide providers := []types.ProxyProvider{} + if groupOption.IncludeAll { + groupOption.IncludeAllProviders = true + groupOption.IncludeAllProxies = true + } var GroupUse []string - visited := make(map[string]bool) + var GroupProxies []string if groupOption.IncludeAllProviders { - for name := range provider.ProxyProviderName { - GroupUse = append(GroupUse, name) - visited[name] = true - } + GroupUse = append(GroupUse, AllProviders...) } else { GroupUse = groupOption.Use } + if groupOption.IncludeAllProxies { + GroupProxies = append(groupOption.Proxies, AllProxies...) + } else { + GroupProxies = groupOption.Proxies + } - if len(groupOption.Proxies) == 0 && len(GroupUse) == 0 { + if len(GroupProxies) == 0 && len(GroupUse) == 0 { return nil, fmt.Errorf("%s: %w", groupName, errMissProxy) } @@ -82,8 +90,13 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide groupOption.ExpectedStatus = status testUrl := groupOption.URL - if len(groupOption.Proxies) != 0 { - ps, err := getProxies(proxyMap, groupOption.Proxies) + if groupOption.URL == "" { + groupOption.URL = C.DefaultTestURL + testUrl = groupOption.URL + } + + if len(GroupProxies) != 0 { + ps, err := getProxies(proxyMap, GroupProxies) if err != nil { return nil, fmt.Errorf("%s: %w", groupName, err) } @@ -94,17 +107,12 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide // select don't need health check if groupOption.Type != "select" && groupOption.Type != "relay" { - if groupOption.URL == "" { - groupOption.URL = C.DefaultTestURL - testUrl = groupOption.URL - } - if groupOption.Interval == 0 { groupOption.Interval = 300 } } - hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), true, expectedStatus) + hc := provider.NewHealthCheck(ps, testUrl, uint(groupOption.Interval), groupOption.Lazy, expectedStatus) pd, err := provider.NewCompatibleProvider(groupName, ps, hc) if err != nil { diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index bdac909f..2f9456fa 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -4,12 +4,15 @@ import ( "context" "encoding/json" "errors" + "fmt" + "sync" "time" "github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/common/callback" N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/singledo" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/provider" @@ -113,7 +116,7 @@ func (u *URLTest) fast(touch bool) C.Proxy { elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) { fast := proxies[0] - min := fast.LastDelayForTestUrl(u.testUrl) + minDelay := fast.LastDelayForTestUrl(u.testUrl) fastNotExist := true for _, proxy := range proxies[1:] { @@ -126,9 +129,9 @@ func (u *URLTest) fast(touch bool) C.Proxy { } delay := proxy.LastDelayForTestUrl(u.testUrl) - if delay < min { + if delay < minDelay { fast = proxy - min = delay + minDelay = delay } } @@ -170,9 +173,38 @@ func (u *URLTest) MarshalJSON() ([]byte, error) { "all": all, "testUrl": u.testUrl, "expectedStatus": u.expectedStatus, + "fixed": u.selected, }) } +func (u *URLTest) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) { + var wg sync.WaitGroup + var lock sync.Mutex + mp := map[string]uint16{} + proxies := u.GetProxies(false) + for _, proxy := range proxies { + proxy := proxy + wg.Add(1) + go func() { + delay, err := proxy.URLTest(ctx, u.testUrl, expectedStatus) + if err == nil { + lock.Lock() + mp[proxy.Name()] = delay + lock.Unlock() + } + + wg.Done() + }() + } + wg.Wait() + + if len(mp) == 0 { + return mp, fmt.Errorf("get delay: all proxies timeout") + } else { + return mp, nil + } +} + func parseURLTestOption(config map[string]any) []urlTestOption { opts := []urlTestOption{} diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index d8e56192..12e1df42 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -213,9 +213,8 @@ func (hc *HealthCheck) close() { } func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck { - if len(url) == 0 { - interval = 0 - expectedStatus = nil + if url == "" { + // expectedStatus = nil url = C.DefaultTestURL } diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index a22492d2..c78a9d39 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -32,6 +32,9 @@ type OverrideSchema struct { Down *string `provider:"down,omitempty"` DialerProxy *string `provider:"dialer-proxy,omitempty"` SkipCertVerify *bool `provider:"skip-cert-verify,omitempty"` + Interface *string `provider:"interface-name,omitempty"` + RoutingMark *int `provider:"routing-mark,omitempty"` + IPVersion *string `provider:"ip-version,omitempty"` } type proxyProviderSchema struct { @@ -67,6 +70,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide var hcInterval uint if schema.HealthCheck.Enable { + if schema.HealthCheck.Interval == 0 { + schema.HealthCheck.Interval = 300 + } hcInterval = uint(schema.HealthCheck.Interval) } hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, hcInterval, schema.HealthCheck.Lazy, expectedStatus) diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 01ae44ee..d710d3a4 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -24,8 +24,6 @@ import ( "gopkg.in/yaml.v3" ) -var ProxyProviderName = make(map[string]struct{}) - const ( ReservedName = "default" ) @@ -201,7 +199,6 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd)) pd.Fetcher = fetcher - ProxyProviderName[name] = struct{}{} wrapper := &ProxySetProvider{pd} runtime.SetFinalizer(wrapper, stopProxyProvider) return wrapper, nil @@ -252,6 +249,9 @@ func (cp *compatibleProvider) Update() error { } func (cp *compatibleProvider) Initial() error { + if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" { + cp.HealthCheck() + } return nil } @@ -390,6 +390,15 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray if override.SkipCertVerify != nil { mapping["skip-cert-verify"] = *override.SkipCertVerify } + if override.Interface != nil { + mapping["interface-name"] = *override.Interface + } + if override.RoutingMark != nil { + mapping["routing-mark"] = *override.RoutingMark + } + if override.IPVersion != nil { + mapping["ip-version"] = *override.IPVersion + } proxy, err := adapter.ParseProxy(mapping) if err != nil { diff --git a/common/arc/arc.go b/common/arc/arc.go index 8e62c906..da78b1c1 100644 --- a/common/arc/arc.go +++ b/common/arc/arc.go @@ -20,16 +20,15 @@ func WithSize[K comparable, V any](maxSize int) Option[K, V] { } type ARC[K comparable, V any] struct { - p int - c int - t1 *list.List[*entry[K, V]] - b1 *list.List[*entry[K, V]] - t2 *list.List[*entry[K, V]] - b2 *list.List[*entry[K, V]] - mutex sync.Mutex - len int - cache map[K]*entry[K, V] - staleReturn bool + p int + c int + t1 *list.List[*entry[K, V]] + b1 *list.List[*entry[K, V]] + t2 *list.List[*entry[K, V]] + b2 *list.List[*entry[K, V]] + mutex sync.Mutex + len int + cache map[K]*entry[K, V] } // New returns a new Adaptive Replacement Cache (ARC). @@ -74,20 +73,22 @@ func (a *ARC[K, V]) SetWithExpire(key K, value V, expires time.Time) { func (a *ARC[K, V]) setWithExpire(key K, value V, expires time.Time) { ent, ok := a.cache[key] - if ok != true { + if !ok { a.len++ ent := &entry[K, V]{key: key, value: value, ghost: false, expires: expires.Unix()} a.req(ent) a.cache[key] = ent - } else { - if ent.ghost { - a.len++ - } - ent.value = value - ent.ghost = false - ent.expires = expires.Unix() - a.req(ent) + return } + + if ent.ghost { + a.len++ + } + + ent.value = value + ent.ghost = false + ent.expires = expires.Unix() + a.req(ent) } // Get retrieves a previously via Set inserted entry. @@ -97,25 +98,25 @@ func (a *ARC[K, V]) Get(key K) (value V, ok bool) { defer a.mutex.Unlock() ent, ok := a.get(key) - if ok { - return ent.value, true + if !ok { + return lo.Empty[V](), false } - return lo.Empty[V](), false + return ent.value, true } func (a *ARC[K, V]) get(key K) (e *entry[K, V], ok bool) { ent, ok := a.cache[key] - if ok { - a.req(ent) - return ent, !ent.ghost + if !ok { + return ent, false } - return ent, false + a.req(ent) + return ent, !ent.ghost } // GetWithExpire returns any representation of a cached response, // a time.Time Give expected expires, // and a bool set to true if the key was found. -// This method will NOT check the maxAge of element and will NOT update the expires. +// This method will NOT update the expires. func (a *ARC[K, V]) GetWithExpire(key K) (V, time.Time, bool) { a.mutex.Lock() defer a.mutex.Unlock() @@ -138,10 +139,11 @@ func (a *ARC[K, V]) Len() int { } func (a *ARC[K, V]) req(ent *entry[K, V]) { - if ent.ll == a.t1 || ent.ll == a.t2 { + switch { + case ent.ll == a.t1 || ent.ll == a.t2: // Case I ent.setMRU(a.t2) - } else if ent.ll == a.b1 { + case ent.ll == a.b1: // Case II // Cache Miss in t1 and t2 @@ -152,16 +154,11 @@ func (a *ARC[K, V]) req(ent *entry[K, V]) { } else { d = a.b2.Len() / a.b1.Len() } - - // a.p = min(a.p+d, a.c) - a.p = a.p + d - if a.c < a.p { - a.p = a.c - } + a.p = min(a.p+d, a.c) a.replace(ent) ent.setMRU(a.t2) - } else if ent.ll == a.b2 { + case ent.ll == a.b2: // Case III // Cache Miss in t1 and t2 @@ -172,35 +169,30 @@ func (a *ARC[K, V]) req(ent *entry[K, V]) { } else { d = a.b1.Len() / a.b2.Len() } - //a.p = max(a.p-d, 0) - a.p = a.p - d - if a.p < 0 { - a.p = 0 - } + a.p = max(a.p-d, 0) a.replace(ent) ent.setMRU(a.t2) - } else if ent.ll == nil { - // Case IV - - if a.t1.Len()+a.b1.Len() == a.c { - // Case A - if a.t1.Len() < a.c { - a.delLRU(a.b1) - a.replace(ent) - } else { - a.delLRU(a.t1) - } - } else if a.t1.Len()+a.b1.Len() < a.c { - // Case B - if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c { - if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c { - a.delLRU(a.b2) - } - a.replace(ent) - } + case ent.ll == nil && a.t1.Len()+a.b1.Len() == a.c: + // Case IV A + if a.t1.Len() < a.c { + a.delLRU(a.b1) + a.replace(ent) + } else { + a.delLRU(a.t1) } - + ent.setMRU(a.t1) + case ent.ll == nil && a.t1.Len()+a.b1.Len() < a.c: + // Case IV B + if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() >= a.c { + if a.t1.Len()+a.t2.Len()+a.b1.Len()+a.b2.Len() == 2*a.c { + a.delLRU(a.b2) + } + a.replace(ent) + } + ent.setMRU(a.t1) + case ent.ll == nil: + // Case IV, not A nor B ent.setMRU(a.t1) } } @@ -227,3 +219,17 @@ func (a *ARC[K, V]) replace(ent *entry[K, V]) { lru.setMRU(a.b2) } } + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a int, b int) int { + if a < b { + return b + } + return a +} diff --git a/common/arc/arc_test.go b/common/arc/arc_test.go index cb9835a3..a9d8a0c1 100644 --- a/common/arc/arc_test.go +++ b/common/arc/arc_test.go @@ -1,59 +1,105 @@ package arc -import "testing" +import ( + "testing" +) -func TestBasic(t *testing.T) { +func TestInsertion(t *testing.T) { cache := New[string, string](WithSize[string, string](3)) - if cache.Len() != 0 { - t.Error("Empty cache should have length 0") + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) } - cache.Set("Hello", "World") - if cache.Len() != 1 { - t.Error("Cache should have length 1") + const ( + k1 = "Hello" + k2 = "Hallo" + k3 = "Ciao" + k4 = "Salut" + + v1 = "World" + v2 = "Worlds" + v3 = "Welt" + ) + + // Insert the first value + cache.Set(k1, v1) + if got, want := cache.Len(), 1; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v1 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v1) } - var val interface{} - var ok bool - - if val, ok = cache.Get("Hello"); val != "World" || ok != true { - t.Error("Didn't set \"Hello\" to \"World\"") + // Replace existing value for a given key + cache.Set(k1, v2) + if got, want := cache.Len(), 1; got != want { + t.Errorf("re-insertion: cache.Len(): got %d want %d", cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v2 { + t.Errorf("re-insertion: cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2) } - cache.Set("Hello", "World1") - if cache.Len() != 1 { - t.Error("Inserting the same entry multiple times shouldn't increase cache size") + // Add a second different key + cache.Set(k2, v3) + if got, want := cache.Len(), 2; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + if got, ok := cache.Get(k1); !ok || got != v2 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k1, got, ok, v2) + } + if got, ok := cache.Get(k2); !ok || got != v3 { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", k2, got, ok, v3) } - if val, ok = cache.Get("Hello"); val != "World1" || ok != true { - t.Error("Didn't update \"Hello\" to \"World1\"") + // Fill cache + cache.Set(k3, v1) + if got, want := cache.Len(), 3; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) } - cache.Set("Hallo", "Welt") - if cache.Len() != 2 { - t.Error("Inserting two different entries should result into lenght=2") - } - - if val, ok = cache.Get("Hallo"); val != "Welt" || ok != true { - t.Error("Didn't set \"Hallo\" to \"Welt\"") + // Exceed size, this should not exceed size: + cache.Set(k4, v1) + if got, want := cache.Len(), 3; got != want { + t.Errorf("insertion of key out of size: cache.Len(): got %d want %d", cache.Len(), want) } } -func TestBasicReplace(t *testing.T) { - cache := New[string, string](WithSize[string, string](3)) - - cache.Set("Hello", "Hallo") - cache.Set("World", "Welt") - cache.Get("World") - cache.Set("Cache", "Cache") - cache.Set("Replace", "Ersetzen") - - value, ok := cache.Get("World") - if !ok || value != "Welt" { - t.Error("ARC should have replaced \"Hello\"") +func TestEviction(t *testing.T) { + size := 3 + cache := New[string, string](WithSize[string, string](size)) + if got, want := cache.Len(), 0; got != want { + t.Errorf("empty cache.Len(): got %d want %d", cache.Len(), want) } - if cache.Len() != 3 { - t.Error("ARC should have a maximum size of 3") + tests := []struct { + k, v string + }{ + {"k1", "v1"}, + {"k2", "v2"}, + {"k3", "v3"}, + {"k4", "v4"}, + } + for i, tt := range tests[:size] { + cache.Set(tt.k, tt.v) + if got, want := cache.Len(), i+1; got != want { + t.Errorf("insertion of key #%d: cache.Len(): got %d want %d", want, cache.Len(), want) + } + } + + // Exceed size and check we don't outgrow it: + cache.Set(tests[size].k, tests[size].v) + if got := cache.Len(); got != size { + t.Errorf("insertion of overflow key #%d: cache.Len(): got %d want %d", 4, cache.Len(), size) + } + + // Check that LRU got evicted: + if got, ok := cache.Get(tests[0].k); ok || got != "" { + t.Errorf("cache.Get(%v): got (%v,%t) want (,true)", tests[0].k, got, ok) + } + + for _, tt := range tests[1:] { + if got, ok := cache.Get(tt.k); !ok || got != tt.v { + t.Errorf("cache.Get(%v): got (%v,%t) want (%v,true)", tt.k, got, ok, tt.v) + } } } diff --git a/common/callback/close_callback.go b/common/callback/close_callback.go new file mode 100644 index 00000000..630ee5d7 --- /dev/null +++ b/common/callback/close_callback.go @@ -0,0 +1,61 @@ +package callback + +import ( + "sync" + + C "github.com/metacubex/mihomo/constant" +) + +type closeCallbackConn struct { + C.Conn + closeFunc func() + closeOnce sync.Once +} + +func (w *closeCallbackConn) Close() error { + w.closeOnce.Do(w.closeFunc) + return w.Conn.Close() +} + +func (w *closeCallbackConn) ReaderReplaceable() bool { + return true +} + +func (w *closeCallbackConn) WriterReplaceable() bool { + return true +} + +func (w *closeCallbackConn) Upstream() any { + return w.Conn +} + +func NewCloseCallbackConn(conn C.Conn, callback func()) C.Conn { + return &closeCallbackConn{Conn: conn, closeFunc: callback} +} + +type closeCallbackPacketConn struct { + C.PacketConn + closeFunc func() + closeOnce sync.Once +} + +func (w *closeCallbackPacketConn) Close() error { + w.closeOnce.Do(w.closeFunc) + return w.PacketConn.Close() +} + +func (w *closeCallbackPacketConn) ReaderReplaceable() bool { + return true +} + +func (w *closeCallbackPacketConn) WriterReplaceable() bool { + return true +} + +func (w *closeCallbackPacketConn) Upstream() any { + return w.PacketConn +} + +func NewCloseCallbackPacketConn(conn C.PacketConn, callback func()) C.PacketConn { + return &closeCallbackPacketConn{PacketConn: conn, closeFunc: callback} +} diff --git a/common/lru/lrucache.go b/common/lru/lrucache.go index d23277c2..6f32ed18 100644 --- a/common/lru/lrucache.go +++ b/common/lru/lrucache.go @@ -80,7 +80,7 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] { return lc } -// Get returns the any representation of a cached response and a bool +// Get returns any representation of a cached response and a bool // set to true if the key was found. func (c *LruCache[K, V]) Get(key K) (V, bool) { c.mu.Lock() @@ -110,7 +110,7 @@ func (c *LruCache[K, V]) GetOrStore(key K, constructor func() V) (V, bool) { return value, true } -// GetWithExpire returns the any representation of a cached response, +// GetWithExpire returns any representation of a cached response, // a time.Time Give expected expires, // and a bool set to true if the key was found. // This method will NOT check the maxAge of element and will NOT update the expires. @@ -135,7 +135,7 @@ func (c *LruCache[K, V]) Exist(key K) bool { return ok } -// Set stores the any representation of a response for a given key. +// Set stores any representation of a response for a given key. func (c *LruCache[K, V]) Set(key K, value V) { c.mu.Lock() defer c.mu.Unlock() @@ -151,7 +151,7 @@ func (c *LruCache[K, V]) set(key K, value V) { c.setWithExpire(key, value, time.Unix(expires, 0)) } -// SetWithExpire stores the any representation of a response for a given key and given expires. +// SetWithExpire stores any representation of a response for a given key and given expires. // The expires time will round to second. func (c *LruCache[K, V]) SetWithExpire(key K, value V, expires time.Time) { c.mu.Lock() diff --git a/common/net/deadline/conn.go b/common/net/deadline/conn.go new file mode 100644 index 00000000..e8446ce2 --- /dev/null +++ b/common/net/deadline/conn.go @@ -0,0 +1,149 @@ +package deadline + +import ( + "net" + "os" + "time" + + "github.com/metacubex/mihomo/common/atomic" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/bufio" + "github.com/sagernet/sing/common/network" +) + +type connReadResult struct { + buffer []byte + err error +} + +type Conn struct { + network.ExtendedConn + deadline atomic.TypedValue[time.Time] + pipeDeadline pipeDeadline + disablePipe atomic.Bool + inRead atomic.Bool + resultCh chan *connReadResult +} + +func NewConn(conn net.Conn) *Conn { + c := &Conn{ + ExtendedConn: bufio.NewExtendedConn(conn), + pipeDeadline: makePipeDeadline(), + resultCh: make(chan *connReadResult, 1), + } + c.resultCh <- nil + return c +} + +func (c *Conn) Read(p []byte) (n int, err error) { + select { + case result := <-c.resultCh: + if result != nil { + n = copy(p, result.buffer) + err = result.err + if n >= len(result.buffer) { + c.resultCh <- nil // finish cache read + } else { + result.buffer = result.buffer[n:] + c.resultCh <- result // push back for next call + } + return + } else { + c.resultCh <- nil + break + } + case <-c.pipeDeadline.wait(): + return 0, os.ErrDeadlineExceeded + } + + if c.disablePipe.Load() { + return c.ExtendedConn.Read(p) + } else if c.deadline.Load().IsZero() { + c.inRead.Store(true) + defer c.inRead.Store(false) + return c.ExtendedConn.Read(p) + } + + <-c.resultCh + go c.pipeRead(len(p)) + + return c.Read(p) +} + +func (c *Conn) pipeRead(size int) { + buffer := make([]byte, size) + n, err := c.ExtendedConn.Read(buffer) + buffer = buffer[:n] + c.resultCh <- &connReadResult{ + buffer: buffer, + err: err, + } +} + +func (c *Conn) ReadBuffer(buffer *buf.Buffer) (err error) { + select { + case result := <-c.resultCh: + if result != nil { + n, _ := buffer.Write(result.buffer) + err = result.err + + if n >= len(result.buffer) { + c.resultCh <- nil // finish cache read + } else { + result.buffer = result.buffer[n:] + c.resultCh <- result // push back for next call + } + return + } else { + c.resultCh <- nil + break + } + case <-c.pipeDeadline.wait(): + return os.ErrDeadlineExceeded + } + + if c.disablePipe.Load() { + return c.ExtendedConn.ReadBuffer(buffer) + } else if c.deadline.Load().IsZero() { + c.inRead.Store(true) + defer c.inRead.Store(false) + return c.ExtendedConn.ReadBuffer(buffer) + } + + <-c.resultCh + go c.pipeRead(buffer.FreeLen()) + + return c.ReadBuffer(buffer) +} + +func (c *Conn) SetReadDeadline(t time.Time) error { + if c.disablePipe.Load() { + return c.ExtendedConn.SetReadDeadline(t) + } else if c.inRead.Load() { + c.disablePipe.Store(true) + return c.ExtendedConn.SetReadDeadline(t) + } + c.deadline.Store(t) + c.pipeDeadline.set(t) + return nil +} + +func (c *Conn) ReaderReplaceable() bool { + select { + case result := <-c.resultCh: + c.resultCh <- result + if result != nil { + return false // cache reading + } else { + break + } + default: + return false // pipe reading + } + return c.disablePipe.Load() || c.deadline.Load().IsZero() +} + +func (c *Conn) Upstream() any { + return c.ExtendedConn +} diff --git a/common/net/deadline/packet_sing.go b/common/net/deadline/packet_sing.go index 65db1b8f..d54748b0 100644 --- a/common/net/deadline/packet_sing.go +++ b/common/net/deadline/packet_sing.go @@ -5,6 +5,7 @@ import ( "runtime" "github.com/metacubex/mihomo/common/net/packet" + "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" @@ -121,17 +122,18 @@ type singPacketReadWaiter struct { type singWaitReadResult singReadResult -func (c *singPacketReadWaiter) InitializeReadWaiter(newBuffer func() *buf.Buffer) { - c.packetReadWaiter.InitializeReadWaiter(newBuffer) +func (c *singPacketReadWaiter) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) { + return c.packetReadWaiter.InitializeReadWaiter(options) } -func (c *singPacketReadWaiter) WaitReadPacket() (destination M.Socksaddr, err error) { +func (c *singPacketReadWaiter) WaitReadPacket() (buffer *buf.Buffer, destination M.Socksaddr, err error) { FOR: for { select { case result := <-c.netPacketConn.resultCh: if result != nil { if result, ok := result.(*singWaitReadResult); ok { + buffer = result.buffer destination = result.destination err = result.err c.netPacketConn.resultCh <- nil // finish cache read @@ -145,7 +147,7 @@ FOR: break FOR } case <-c.netPacketConn.pipeDeadline.wait(): - return M.Socksaddr{}, os.ErrDeadlineExceeded + return nil, M.Socksaddr{}, os.ErrDeadlineExceeded } } @@ -154,8 +156,7 @@ FOR: } else if c.netPacketConn.deadline.Load().IsZero() { c.netPacketConn.inRead.Store(true) defer c.netPacketConn.inRead.Store(false) - destination, err = c.packetReadWaiter.WaitReadPacket() - return + return c.packetReadWaiter.WaitReadPacket() } <-c.netPacketConn.resultCh @@ -165,8 +166,9 @@ FOR: } func (c *singPacketReadWaiter) pipeWaitReadPacket() { - destination, err := c.packetReadWaiter.WaitReadPacket() + buffer, destination, err := c.packetReadWaiter.WaitReadPacket() result := &singWaitReadResult{} + result.buffer = buffer result.destination = destination result.err = err c.netPacketConn.resultCh <- result diff --git a/common/net/packet/packet_sing.go b/common/net/packet/packet_sing.go index cfcf5ed0..6e25eb4d 100644 --- a/common/net/packet/packet_sing.go +++ b/common/net/packet/packet_sing.go @@ -24,16 +24,16 @@ type enhanceSingPacketConn struct { func (c *enhanceSingPacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) { var buff *buf.Buffer var dest M.Socksaddr - newBuffer := func() *buf.Buffer { - buff = buf.NewPacket() // do not use stack buffer - return buff - } + rwOptions := N.ReadWaitOptions{} if c.packetReadWaiter != nil { - c.packetReadWaiter.InitializeReadWaiter(newBuffer) - defer c.packetReadWaiter.InitializeReadWaiter(nil) - dest, err = c.packetReadWaiter.WaitReadPacket() + c.packetReadWaiter.InitializeReadWaiter(rwOptions) + buff, dest, err = c.packetReadWaiter.WaitReadPacket() } else { - dest, err = c.SingPacketConn.ReadPacket(newBuffer()) + buff = rwOptions.NewPacketBuffer() + dest, err = c.SingPacketConn.ReadPacket(buff) + if buff != nil { + rwOptions.PostReturn(buff) + } } if dest.IsFqdn() { addr = dest @@ -41,9 +41,7 @@ func (c *enhanceSingPacketConn) WaitReadFrom() (data []byte, put func(), addr ne addr = dest.UDPAddr() } if err != nil { - if buff != nil { - buff.Release() - } + buff.Release() return } if buff == nil { diff --git a/common/net/packet/packet_windows.go b/common/net/packet/packet_windows.go index cb4c518b..3c467c6d 100644 --- a/common/net/packet/packet_windows.go +++ b/common/net/packet/packet_windows.go @@ -4,12 +4,72 @@ package packet import ( "net" + "strconv" + "syscall" + + "github.com/metacubex/mihomo/common/pool" + + "golang.org/x/sys/windows" ) type enhanceUDPConn struct { *net.UDPConn + rawConn syscall.RawConn } func (c *enhanceUDPConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err error) { - return waitReadFrom(c.UDPConn) + if c.rawConn == nil { + c.rawConn, _ = c.UDPConn.SyscallConn() + } + var readErr error + hasData := false + err = c.rawConn.Read(func(fd uintptr) (done bool) { + if !hasData { + hasData = true + // golang's internal/poll.FD.RawRead will Use a zero-byte read as a way to get notified when this + // socket is readable if we return false. So the `recvfrom` syscall will not block the system thread. + return false + } + readBuf := pool.Get(pool.UDPBufferSize) + put = func() { + _ = pool.Put(readBuf) + } + var readFrom windows.Sockaddr + var readN int + readN, readFrom, readErr = windows.Recvfrom(windows.Handle(fd), readBuf, 0) + if readN > 0 { + data = readBuf[:readN] + } else { + put() + put = nil + data = nil + } + if readErr == windows.WSAEWOULDBLOCK { + return false + } + if readFrom != nil { + switch from := readFrom.(type) { + case *windows.SockaddrInet4: + ip := from.Addr // copy from.Addr; ip escapes, so this line allocates 4 bytes + addr = &net.UDPAddr{IP: ip[:], Port: from.Port} + case *windows.SockaddrInet6: + ip := from.Addr // copy from.Addr; ip escapes, so this line allocates 16 bytes + addr = &net.UDPAddr{IP: ip[:], Port: from.Port, Zone: strconv.FormatInt(int64(from.ZoneId), 10)} + } + } + // udp should not convert readN == 0 to io.EOF + //if readN == 0 { + // readErr = io.EOF + //} + hasData = false + return true + }) + if err != nil { + return + } + if readErr != nil { + err = readErr + return + } + return } diff --git a/common/net/sing.go b/common/net/sing.go index c92008ba..f8698620 100644 --- a/common/net/sing.go +++ b/common/net/sing.go @@ -5,9 +5,10 @@ import ( "net" "runtime" + "github.com/metacubex/mihomo/common/net/deadline" + "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/bufio/deadline" "github.com/sagernet/sing/common/network" ) @@ -19,8 +20,10 @@ type ExtendedConn = network.ExtendedConn type ExtendedWriter = network.ExtendedWriter type ExtendedReader = network.ExtendedReader +var WriteBuffer = bufio.WriteBuffer + func NewDeadlineConn(conn net.Conn) ExtendedConn { - return deadline.NewFallbackConn(conn) + return deadline.NewConn(conn) } func NeedHandshake(conn any) bool { diff --git a/component/ebpf/byteorder/byteorder_littleendian.go b/component/ebpf/byteorder/byteorder_littleendian.go index 216a5e5a..d40f3517 100644 --- a/component/ebpf/byteorder/byteorder_littleendian.go +++ b/component/ebpf/byteorder/byteorder_littleendian.go @@ -1,4 +1,4 @@ -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64 package byteorder diff --git a/component/ebpf/redir/bpf_bpfel.go b/component/ebpf/redir/bpf_bpfel.go index 936b84eb..1fe3454a 100644 --- a/component/ebpf/redir/bpf_bpfel.go +++ b/component/ebpf/redir/bpf_bpfel.go @@ -1,6 +1,6 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64 +// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64 package redir diff --git a/component/ebpf/tc/bpf_bpfel.go b/component/ebpf/tc/bpf_bpfel.go index 07daba12..3bfa1655 100644 --- a/component/ebpf/tc/bpf_bpfel.go +++ b/component/ebpf/tc/bpf_bpfel.go @@ -1,6 +1,6 @@ -// Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 -// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 +// +//go:build 386 || amd64 || amd64p32 || arm || arm64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 || loong64 +// +build 386 amd64 amd64p32 arm arm64 mips64le mips64p32le mipsle ppc64le riscv64 loong64 package tc diff --git a/component/geodata/router/condition.go b/component/geodata/router/condition.go index 156614ae..73bc88d4 100644 --- a/component/geodata/router/condition.go +++ b/component/geodata/router/condition.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/metacubex/mihomo/component/geodata/strmatcher" + "github.com/metacubex/mihomo/component/trie" ) var matcherTypeMap = map[Domain_Type]strmatcher.Type{ @@ -31,12 +32,69 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) { return matcher, nil } -type DomainMatcher struct { +type DomainMatcher interface { + ApplyDomain(string) bool +} + +type succinctDomainMatcher struct { + set *trie.DomainSet + otherMatchers []strmatcher.Matcher + not bool +} + +func (m *succinctDomainMatcher) ApplyDomain(domain string) bool { + isMatched := m.set.Has(domain) + if !isMatched { + for _, matcher := range m.otherMatchers { + isMatched = matcher.Match(domain) + if isMatched { + break + } + } + } + if m.not { + isMatched = !isMatched + } + return isMatched +} + +func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) { + t := trie.New[struct{}]() + m := &succinctDomainMatcher{ + not: not, + } + for _, d := range domains { + switch d.Type { + case Domain_Plain, Domain_Regex: + matcher, err := matcherTypeMap[d.Type].New(d.Value) + if err != nil { + return nil, err + } + m.otherMatchers = append(m.otherMatchers, matcher) + + case Domain_Domain: + err := t.Insert("+."+d.Value, struct{}{}) + if err != nil { + return nil, err + } + + case Domain_Full: + err := t.Insert(d.Value, struct{}{}) + if err != nil { + return nil, err + } + } + } + m.set = t.NewDomainSet() + return m, nil +} + +type v2rayDomainMatcher struct { matchers strmatcher.IndexMatcher not bool } -func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) { +func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) { g := strmatcher.NewMphMatcherGroup() for _, d := range domains { matcherType, f := matcherTypeMap[d.Type] @@ -49,30 +107,13 @@ func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) { } } g.Build() - return &DomainMatcher{ + return &v2rayDomainMatcher{ matchers: g, not: not, }, nil } -// NewDomainMatcher new domain matcher. -func NewDomainMatcher(domains []*Domain, not bool) (*DomainMatcher, error) { - g := new(strmatcher.MatcherGroup) - for _, d := range domains { - m, err := domainToMatcher(d) - if err != nil { - return nil, err - } - g.Add(m) - } - - return &DomainMatcher{ - matchers: g, - not: not, - }, nil -} - -func (m *DomainMatcher) ApplyDomain(domain string) bool { +func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool { isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0 if m.not { isMatched = !isMatched diff --git a/component/geodata/strmatcher/ac_automaton_matcher.go b/component/geodata/strmatcher/ac_automaton_matcher.go index a21a83a2..fd02d511 100644 --- a/component/geodata/strmatcher/ac_automaton_matcher.go +++ b/component/geodata/strmatcher/ac_automaton_matcher.go @@ -39,7 +39,7 @@ func newNode() [validCharCount]Edge { return s } -var char2Index = []int{ +var char2Index = [...]int{ 'A': 0, 'a': 0, 'B': 1, diff --git a/component/geodata/strmatcher/domain_matcher.go b/component/geodata/strmatcher/domain_matcher.go deleted file mode 100644 index ae8e65bc..00000000 --- a/component/geodata/strmatcher/domain_matcher.go +++ /dev/null @@ -1,98 +0,0 @@ -package strmatcher - -import "strings" - -func breakDomain(domain string) []string { - return strings.Split(domain, ".") -} - -type node struct { - values []uint32 - sub map[string]*node -} - -// DomainMatcherGroup is a IndexMatcher for a large set of Domain matchers. -// Visible for testing only. -type DomainMatcherGroup struct { - root *node -} - -func (g *DomainMatcherGroup) Add(domain string, value uint32) { - if g.root == nil { - g.root = new(node) - } - - current := g.root - parts := breakDomain(domain) - for i := len(parts) - 1; i >= 0; i-- { - part := parts[i] - if current.sub == nil { - current.sub = make(map[string]*node) - } - next := current.sub[part] - if next == nil { - next = new(node) - current.sub[part] = next - } - current = next - } - - current.values = append(current.values, value) -} - -func (g *DomainMatcherGroup) addMatcher(m domainMatcher, value uint32) { - g.Add(string(m), value) -} - -func (g *DomainMatcherGroup) Match(domain string) []uint32 { - if domain == "" { - return nil - } - - current := g.root - if current == nil { - return nil - } - - nextPart := func(idx int) int { - for i := idx - 1; i >= 0; i-- { - if domain[i] == '.' { - return i - } - } - return -1 - } - - matches := [][]uint32{} - idx := len(domain) - for { - if idx == -1 || current.sub == nil { - break - } - - nidx := nextPart(idx) - part := domain[nidx+1 : idx] - next := current.sub[part] - if next == nil { - break - } - current = next - idx = nidx - if len(current.values) > 0 { - matches = append(matches, current.values) - } - } - switch len(matches) { - case 0: - return nil - case 1: - return matches[0] - default: - result := []uint32{} - for idx := range matches { - // Insert reversely, the subdomain that matches further ranks higher - result = append(result, matches[len(matches)-1-idx]...) - } - return result - } -} diff --git a/component/geodata/strmatcher/full_matcher.go b/component/geodata/strmatcher/full_matcher.go deleted file mode 100644 index e00d02aa..00000000 --- a/component/geodata/strmatcher/full_matcher.go +++ /dev/null @@ -1,25 +0,0 @@ -package strmatcher - -type FullMatcherGroup struct { - matchers map[string][]uint32 -} - -func (g *FullMatcherGroup) Add(domain string, value uint32) { - if g.matchers == nil { - g.matchers = make(map[string][]uint32) - } - - g.matchers[domain] = append(g.matchers[domain], value) -} - -func (g *FullMatcherGroup) addMatcher(m fullMatcher, value uint32) { - g.Add(string(m), value) -} - -func (g *FullMatcherGroup) Match(str string) []uint32 { - if g.matchers == nil { - return nil - } - - return g.matchers[str] -} diff --git a/component/geodata/strmatcher/mph_matcher.go b/component/geodata/strmatcher/mph_matcher.go index 8d8b0508..7c1b4062 100644 --- a/component/geodata/strmatcher/mph_matcher.go +++ b/component/geodata/strmatcher/mph_matcher.go @@ -236,25 +236,25 @@ tail: h ^= uint64(*(*byte)(p)) h ^= uint64(*(*byte)(unsafe.Add(p, s>>1))) << 8 h ^= uint64(*(*byte)(unsafe.Add(p, s-1))) << 16 - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 case s <= 8: h ^= uint64(readUnaligned32(p)) h ^= uint64(readUnaligned32(unsafe.Add(p, s-4))) << 32 - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 case s <= 16: h ^= readUnaligned64(p) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, s-8)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 case s <= 32: h ^= readUnaligned64(p) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, 8)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, s-16)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 h ^= readUnaligned64(unsafe.Add(p, s-8)) - h = rotl31(h*m1) * m2 + h = bits.RotateLeft64(h*m1, 31) * m2 default: v1 := h v2 := uint64(seed * hashkey[1]) @@ -262,16 +262,16 @@ tail: v4 := uint64(seed * hashkey[3]) for s >= 32 { v1 ^= readUnaligned64(p) - v1 = rotl31(v1*m1) * m2 + v1 = bits.RotateLeft64(v1*m1, 31) * m2 p = unsafe.Add(p, 8) v2 ^= readUnaligned64(p) - v2 = rotl31(v2*m2) * m3 + v2 = bits.RotateLeft64(v2*m2, 31) * m3 p = unsafe.Add(p, 8) v3 ^= readUnaligned64(p) - v3 = rotl31(v3*m3) * m4 + v3 = bits.RotateLeft64(v3*m3, 31) * m4 p = unsafe.Add(p, 8) v4 ^= readUnaligned64(p) - v4 = rotl31(v4*m4) * m1 + v4 = bits.RotateLeft64(v4*m4, 31) * m1 p = unsafe.Add(p, 8) s -= 32 } @@ -290,10 +290,6 @@ func readUnaligned32(p unsafe.Pointer) uint32 { return uint32(q[0]) | uint32(q[1])<<8 | uint32(q[2])<<16 | uint32(q[3])<<24 } -func rotl31(x uint64) uint64 { - return (x << 31) | (x >> (64 - 31)) -} - func readUnaligned64(p unsafe.Pointer) uint64 { q := (*[8]byte)(p) return uint64(q[0]) | uint64(q[1])<<8 | uint64(q[2])<<16 | uint64(q[3])<<24 | uint64(q[4])<<32 | uint64(q[5])<<40 | uint64(q[6])<<48 | uint64(q[7])<<56 diff --git a/component/geodata/strmatcher/strmatcher.go b/component/geodata/strmatcher/strmatcher.go index 294e6e73..6bdb8b97 100644 --- a/component/geodata/strmatcher/strmatcher.go +++ b/component/geodata/strmatcher/strmatcher.go @@ -58,50 +58,3 @@ type matcherEntry struct { m Matcher id uint32 } - -// MatcherGroup is an implementation of IndexMatcher. -// Empty initialization works. -type MatcherGroup struct { - count uint32 - fullMatcher FullMatcherGroup - domainMatcher DomainMatcherGroup - otherMatchers []matcherEntry -} - -// Add adds a new Matcher into the MatcherGroup, and returns its index. The index will never be 0. -func (g *MatcherGroup) Add(m Matcher) uint32 { - g.count++ - c := g.count - - switch tm := m.(type) { - case fullMatcher: - g.fullMatcher.addMatcher(tm, c) - case domainMatcher: - g.domainMatcher.addMatcher(tm, c) - default: - g.otherMatchers = append(g.otherMatchers, matcherEntry{ - m: m, - id: c, - }) - } - - return c -} - -// Match implements IndexMatcher.Match. -func (g *MatcherGroup) Match(pattern string) []uint32 { - result := []uint32{} - result = append(result, g.fullMatcher.Match(pattern)...) - result = append(result, g.domainMatcher.Match(pattern)...) - for _, e := range g.otherMatchers { - if e.m.Match(pattern) { - result = append(result, e.id) - } - } - return result -} - -// Size returns the number of matchers in the MatcherGroup. -func (g *MatcherGroup) Size() uint32 { - return g.count -} diff --git a/component/geodata/utils.go b/component/geodata/utils.go index 4716ccbd..a4002aeb 100644 --- a/component/geodata/utils.go +++ b/component/geodata/utils.go @@ -12,6 +12,7 @@ import ( ) var geoLoaderName = "memconservative" +var geoSiteMatcher = "succinct" // geoLoaderName = "standard" @@ -19,6 +20,10 @@ func LoaderName() string { return geoLoaderName } +func SiteMatcherName() string { + return geoSiteMatcher +} + func SetLoader(newLoader string) { if newLoader == "memc" { newLoader = "memconservative" @@ -26,6 +31,15 @@ func SetLoader(newLoader string) { geoLoaderName = newLoader } +func SetSiteMatcher(newMatcher string) { + switch newMatcher { + case "mph", "hybrid": + geoSiteMatcher = "mph" + default: + geoSiteMatcher = "succinct" + } +} + func Verify(name string) error { switch name { case C.GeositeName: @@ -41,8 +55,8 @@ func Verify(name string) error { var loadGeoSiteMatcherSF = singleflight.Group{} -func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) { - if len(countryCode) == 0 { +func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) { + if countryCode == "" { return nil, 0, fmt.Errorf("country code could not be empty") } @@ -60,7 +74,7 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) listName := strings.TrimSpace(parts[0]) attrVal := parts[1:] - if len(listName) == 0 { + if listName == "" { return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode) } @@ -104,7 +118,12 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) matcher, err := router.NewDomainMatcher(domains) mph:minimal perfect hash algorithm */ - matcher, err := router.NewMphMatcherGroup(domains, not) + var matcher router.DomainMatcher + if geoSiteMatcher == "mph" { + matcher, err = router.NewMphMatcherGroup(domains, not) + } else { + matcher, err = router.NewSuccinctMatcherGroup(domains, not) + } if err != nil { return nil, 0, err } diff --git a/component/process/process_windows.go b/component/process/process_windows.go index d43c78c6..f8cd00d8 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -218,7 +218,7 @@ func getExecPathFromPID(pid uint32) (string, error) { r1, _, err := syscall.SyscallN( queryProcName, uintptr(h), - uintptr(1), + uintptr(0), uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&size)), ) diff --git a/component/tls/reality.go b/component/tls/reality.go index 250dc4d0..687ef1ef 100644 --- a/component/tls/reality.go +++ b/component/tls/reality.go @@ -5,6 +5,7 @@ import ( "context" "crypto/aes" "crypto/cipher" + "crypto/ecdh" "crypto/ed25519" "crypto/hmac" "crypto/sha256" @@ -27,7 +28,6 @@ import ( utls "github.com/sagernet/utls" "github.com/zhangyunhao116/fastrand" "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/curve25519" "golang.org/x/crypto/hkdf" "golang.org/x/net/http2" ) @@ -35,7 +35,7 @@ import ( const RealityMaxShortIDLen = 8 type RealityConfig struct { - PublicKey [curve25519.ScalarSize]byte + PublicKey *ecdh.PublicKey ShortID [RealityMaxShortIDLen]byte } @@ -81,15 +81,18 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string //log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16]) - ecdheParams := uConn.HandshakeState.State13.EcdheParams - if ecdheParams == nil { + ecdheKey := uConn.HandshakeState.State13.EcdheKey + if ecdheKey == nil { // WTF??? if retry > 2 { - return nil, errors.New("nil ecdheParams") + return nil, errors.New("nil ecdheKey") } continue // retry } - authKey := ecdheParams.SharedKey(realityConfig.PublicKey[:]) + authKey, err := ecdheKey.ECDH(realityConfig.PublicKey) + if err != nil { + return nil, err + } if authKey == nil { return nil, errors.New("nil auth_key") } diff --git a/component/tls/utls.go b/component/tls/utls.go index 787f6fad..3063fc55 100644 --- a/component/tls/utls.go +++ b/component/tls/utls.go @@ -68,16 +68,21 @@ func RollFingerprint() (UClientHelloID, bool) { } var Fingerprints = map[string]UClientHelloID{ - "chrome": {&utls.HelloChrome_Auto}, - "firefox": {&utls.HelloFirefox_Auto}, - "safari": {&utls.HelloSafari_Auto}, - "ios": {&utls.HelloIOS_Auto}, - "android": {&utls.HelloAndroid_11_OkHttp}, - "edge": {&utls.HelloEdge_Auto}, - "360": {&utls.Hello360_Auto}, - "qq": {&utls.HelloQQ_Auto}, - "random": {nil}, - "randomized": {nil}, + "chrome": {&utls.HelloChrome_Auto}, + "chrome_psk": {&utls.HelloChrome_100_PSK}, + "chrome_psk_shuffle": {&utls.HelloChrome_106_Shuffle}, + "chrome_padding_psk_shuffle": {&utls.HelloChrome_114_Padding_PSK_Shuf}, + "chrome_pq": {&utls.HelloChrome_115_PQ}, + "chrome_pq_psk": {&utls.HelloChrome_115_PQ_PSK}, + "firefox": {&utls.HelloFirefox_Auto}, + "safari": {&utls.HelloSafari_Auto}, + "ios": {&utls.HelloIOS_Auto}, + "android": {&utls.HelloAndroid_11_OkHttp}, + "edge": {&utls.HelloEdge_Auto}, + "360": {&utls.Hello360_Auto}, + "qq": {&utls.HelloQQ_Auto}, + "random": {nil}, + "randomized": {nil}, } func init() { diff --git a/config/config.go b/config/config.go index 9cba5d09..bb503b49 100644 --- a/config/config.go +++ b/config/config.go @@ -55,8 +55,11 @@ type General struct { Interface string `json:"interface-name"` RoutingMark int `json:"-"` GeoXUrl GeoXUrl `json:"geox-url"` + GeoAutoUpdate bool `json:"geo-auto-update"` + GeoUpdateInterval int `json:"geo-update-interval"` GeodataMode bool `json:"geodata-mode"` GeodataLoader string `json:"geodata-loader"` + GeositeMatcher string `json:"geosite-matcher"` TCPConcurrent bool `json:"tcp-concurrent"` FindProcessMode P.FindProcessMode `json:"find-process-mode"` Sniffing bool `json:"sniffing"` @@ -78,6 +81,8 @@ type Inbound struct { VmessConfig string `json:"vmess-config"` Authentication []string `json:"authentication"` SkipAuthPrefixes []netip.Prefix `json:"skip-auth-prefixes"` + LanAllowedIPs []netip.Prefix `json:"lan-allowed-ips"` + LanDisAllowedIPs []netip.Prefix `json:"lan-disallowed-ips"` AllowLan bool `json:"allow-lan"` BindAddress string `json:"bind-address"` InboundTfo bool `json:"inbound-tfo"` @@ -123,11 +128,11 @@ type DNS struct { // FallbackFilter config type FallbackFilter struct { - GeoIP bool `yaml:"geoip"` - GeoIPCode string `yaml:"geoip-code"` - IPCIDR []netip.Prefix `yaml:"ipcidr"` - Domain []string `yaml:"domain"` - GeoSite []*router.DomainMatcher `yaml:"geosite"` + GeoIP bool `yaml:"geoip"` + GeoIPCode string `yaml:"geoip-code"` + IPCIDR []netip.Prefix `yaml:"ipcidr"` + Domain []string `yaml:"domain"` + GeoSite []router.DomainMatcher `yaml:"geosite"` } // Profile config @@ -236,7 +241,9 @@ type RawTun struct { AutoDetectInterface bool `yaml:"auto-detect-interface"` RedirectToTun []string `yaml:"-" json:"-"` - MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + GSO bool `yaml:"gso" json:"gso,omitempty"` + GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` //Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4_address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6_address,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict_route,omitempty"` @@ -244,6 +251,8 @@ type RawTun struct { Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6_route_address,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4_route_exclude_address,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6_route_exclude_address,omitempty"` + IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include_uid,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include_uid_range,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude_uid,omitempty"` @@ -283,6 +292,8 @@ type RawConfig struct { InboundMPTCP bool `yaml:"inbound-mptcp"` Authentication []string `yaml:"authentication" json:"authentication"` SkipAuthPrefixes []netip.Prefix `yaml:"skip-auth-prefixes"` + LanAllowedIPs []netip.Prefix `yaml:"lan-allowed-ips"` + LanDisAllowedIPs []netip.Prefix `yaml:"lan-disallowed-ips"` AllowLan bool `yaml:"allow-lan" json:"allow-lan"` BindAddress string `yaml:"bind-address" json:"bind-address"` Mode T.TunnelMode `yaml:"mode" json:"mode"` @@ -298,8 +309,11 @@ type RawConfig struct { Interface string `yaml:"interface-name"` RoutingMark int `yaml:"routing-mark"` Tunnels []LC.Tunnel `yaml:"tunnels"` + GeoAutoUpdate bool `yaml:"geo-auto-update" json:"geo-auto-update"` + GeoUpdateInterval int `yaml:"geo-update-interval" json:"geo-update-interval"` GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"` GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"` + GeositeMatcher string `yaml:"geosite-matcher" json:"geosite-matcher"` 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"` @@ -377,22 +391,25 @@ func Parse(buf []byte) (*Config, error) { func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with default value rawCfg := &RawConfig{ - AllowLan: false, - BindAddress: "*", - IPv6: true, - Mode: T.Rule, - GeodataMode: C.GeodataMode, - GeodataLoader: "memconservative", - UnifiedDelay: false, - Authentication: []string{}, - LogLevel: log.INFO, - Hosts: map[string]any{}, - Rule: []string{}, - Proxy: []map[string]any{}, - ProxyGroup: []map[string]any{}, - TCPConcurrent: false, - FindProcessMode: P.FindProcessStrict, - GlobalUA: "clash.meta", + AllowLan: false, + BindAddress: "*", + LanAllowedIPs: []netip.Prefix{netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0")}, + IPv6: true, + Mode: T.Rule, + GeoAutoUpdate: false, + GeoUpdateInterval: 24, + GeodataMode: C.GeodataMode, + GeodataLoader: "memconservative", + UnifiedDelay: false, + Authentication: []string{}, + LogLevel: log.INFO, + Hosts: map[string]any{}, + Rule: []string{}, + Proxy: []map[string]any{}, + ProxyGroup: []map[string]any{}, + TCPConcurrent: false, + FindProcessMode: P.FindProcessStrict, + GlobalUA: "clash.meta", Tun: RawTun{ Enable: false, Device: "", @@ -522,6 +539,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.Listeners = listener log.Infoln("Geodata Loader mode: %s", geodata.LoaderName()) + log.Infoln("Geosite Matcher implementation: %s", geodata.SiteMatcherName()) ruleProviders, err := parseRuleProviders(rawCfg) if err != nil { return nil, err @@ -590,6 +608,9 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { func parseGeneral(cfg *RawConfig) (*General, error) { geodata.SetLoader(cfg.GeodataLoader) + geodata.SetSiteMatcher(cfg.GeositeMatcher) + C.GeoAutoUpdate = cfg.GeoAutoUpdate + C.GeoUpdateInterval = cfg.GeoUpdateInterval C.GeoIpUrl = cfg.GeoXUrl.GeoIp C.GeoSiteUrl = cfg.GeoXUrl.GeoSite C.MmdbUrl = cfg.GeoXUrl.Mmdb @@ -635,6 +656,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) { VmessConfig: cfg.VmessConfig, AllowLan: cfg.AllowLan, SkipAuthPrefixes: cfg.SkipAuthPrefixes, + LanAllowedIPs: cfg.LanAllowedIPs, + LanDisAllowedIPs: cfg.LanDisAllowedIPs, BindAddress: cfg.BindAddress, InboundTfo: cfg.InboundTfo, InboundMPTCP: cfg.InboundMPTCP, @@ -652,6 +675,8 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Interface: cfg.Interface, RoutingMark: cfg.RoutingMark, GeoXUrl: cfg.GeoXUrl, + GeoAutoUpdate: cfg.GeoAutoUpdate, + GeoUpdateInterval: cfg.GeoUpdateInterval, GeodataMode: cfg.GeodataMode, GeodataLoader: cfg.GeodataLoader, TCPConcurrent: cfg.TCPConcurrent, @@ -670,6 +695,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ providersConfig := cfg.ProxyProvider var proxyList []string + var AllProxies []string proxiesList := list.New() groupsList := list.New() @@ -692,6 +718,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ } proxies[proxy.Name()] = proxy proxyList = append(proxyList, proxy.Name()) + AllProxies = append(AllProxies, proxy.Name()) proxiesList.PushBack(mapping) } @@ -710,6 +737,7 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ return nil, nil, err } + var AllProviders []string // parse and initial providers for name, mapping := range providersConfig { if name == provider.ReservedName { @@ -722,11 +750,12 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ } providersMap[name] = pd + AllProviders = append(AllProviders, name) } // parse proxy group for idx, mapping := range groupsConfig { - group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap) + group, err := outboundgroup.ParseProxyGroup(mapping, proxies, providersMap, AllProxies, AllProviders) if err != nil { return nil, nil, fmt.Errorf("proxy group[%d]: %w", idx, err) } @@ -1104,7 +1133,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro for pair := nsPolicy.Oldest(); pair != nil; pair = pair.Next() { k, v := pair.Key, pair.Value - if strings.Contains(k, ",") { + if strings.Contains(strings.ToLower(k), ",") { if strings.Contains(k, "geosite:") { subkeys := strings.Split(k, ":") subkeys = subkeys[1:] @@ -1113,7 +1142,7 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro newKey := "geosite:" + subkey updatedPolicy.Store(newKey, v) } - } else if strings.Contains(k, "rule-set:") { + } else if strings.Contains(strings.ToLower(k), "rule-set:") { subkeys := strings.Split(k, ":") subkeys = subkeys[1:] subkeys = strings.Split(subkeys[0], ",") @@ -1128,6 +1157,11 @@ func parseNameServerPolicy(nsPolicy *orderedmap.OrderedMap[string, any], rulePro } } } else { + if strings.Contains(strings.ToLower(k), "geosite:") { + updatedPolicy.Store("geosite:"+k[8:], v) + } else if strings.Contains(strings.ToLower(k), "rule-set:") { + updatedPolicy.Store("rule-set:"+k[9:], v) + } updatedPolicy.Store(k, v) } } @@ -1178,8 +1212,8 @@ func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) { return ipNets, nil } -func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) { - var sites []*router.DomainMatcher +func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]router.DomainMatcher, error) { + var sites []router.DomainMatcher if len(countries) > 0 { if err := geodata.InitGeoSite(); err != nil { return nil, fmt.Errorf("can't initial GeoSite: %s", err) @@ -1241,7 +1275,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul EnhancedMode: cfg.EnhancedMode, FallbackFilter: FallbackFilter{ IPCIDR: []netip.Prefix{}, - GeoSite: []*router.DomainMatcher{}, + GeoSite: []router.DomainMatcher{}, }, } var err error @@ -1384,6 +1418,8 @@ func parseTun(rawTun RawTun, general *General) error { RedirectToTun: rawTun.RedirectToTun, MTU: rawTun.MTU, + GSO: rawTun.GSO, + GSOMaxSize: rawTun.GSOMaxSize, Inet4Address: []netip.Prefix{tunAddressPrefix}, Inet6Address: rawTun.Inet6Address, StrictRoute: rawTun.StrictRoute, @@ -1391,6 +1427,8 @@ func parseTun(rawTun RawTun, general *General) error { Inet6RouteAddress: rawTun.Inet6RouteAddress, Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress, Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress, + IncludeInterface: rawTun.IncludeInterface, + ExcludeInterface: rawTun.ExcludeInterface, IncludeUID: rawTun.IncludeUID, IncludeUIDRange: rawTun.IncludeUIDRange, ExcludeUID: rawTun.ExcludeUID, diff --git a/config/update_geo.go b/config/update_geo.go index 718c2d07..2cde47a1 100644 --- a/config/update_geo.go +++ b/config/update_geo.go @@ -28,7 +28,7 @@ func UpdateGeoDatabases() error { return fmt.Errorf("invalid GeoIP database file: %s", err) } - if saveFile(data, C.Path.GeoIP()) != nil { + if err = saveFile(data, C.Path.GeoIP()); err != nil { return fmt.Errorf("can't save GeoIP database file: %w", err) } @@ -43,8 +43,7 @@ func UpdateGeoDatabases() error { return fmt.Errorf("invalid MMDB database file: %s", err) } _ = instance.Close() - - if saveFile(data, C.Path.MMDB()) != nil { + if err = saveFile(data, C.Path.MMDB()); err != nil { return fmt.Errorf("can't save MMDB database file: %w", err) } } @@ -58,7 +57,7 @@ func UpdateGeoDatabases() error { return fmt.Errorf("invalid GeoSite database file: %s", err) } - if saveFile(data, C.Path.GeoSite()) != nil { + if err = saveFile(data, C.Path.GeoSite()); err != nil { return fmt.Errorf("can't save GeoSite database file: %w", err) } diff --git a/config/update_ui.go b/config/update_ui.go index e5596597..cff1d6d7 100644 --- a/config/update_ui.go +++ b/config/update_ui.go @@ -40,7 +40,7 @@ func UpdateUI() error { } saved := path.Join(C.Path.HomeDir(), "download.zip") - if saveFile(data, saved) != nil { + if err = saveFile(data, saved); err != nil { return fmt.Errorf("can't save zip file: %w", err) } defer os.Remove(saved) diff --git a/constant/adapters.go b/constant/adapters.go index 68d4aa4e..9752de55 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -47,7 +47,7 @@ const ( DefaultDropTime = 12 * DefaultTCPTimeout DefaultUDPTimeout = DefaultTCPTimeout DefaultTLSTimeout = DefaultTCPTimeout - DefaultTestURL = "https://cp.cloudflare.com/generate_204" + DefaultTestURL = "https://www.gstatic.com/generate_204" ) var ErrNotSupport = errors.New("no support") @@ -147,13 +147,18 @@ type DelayHistory struct { Delay uint16 `json:"delay"` } +type ProxyState struct { + Alive bool `json:"alive"` + History []DelayHistory `json:"history"` +} + type DelayHistoryStoreType int type Proxy interface { ProxyAdapter AliveForTestUrl(url string) bool DelayHistory() []DelayHistory - ExtraDelayHistory() map[string][]DelayHistory + ExtraDelayHistories() map[string]ProxyState LastDelayForTestUrl(url string) uint16 URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (uint16, error) diff --git a/constant/geodata.go b/constant/geodata.go index 72452270..e93d56b3 100644 --- a/constant/geodata.go +++ b/constant/geodata.go @@ -1,8 +1,10 @@ package constant var ( - GeodataMode bool - GeoIpUrl string - MmdbUrl string - GeoSiteUrl string + GeodataMode bool + GeoAutoUpdate bool + GeoUpdateInterval int + GeoIpUrl string + MmdbUrl string + GeoSiteUrl string ) diff --git a/constant/metadata.go b/constant/metadata.go index 09a2f152..3c712909 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -148,8 +148,8 @@ type Metadata struct { SpecialRules string `json:"specialRules"` RemoteDst string `json:"remoteDestination"` - RawSrcAddr net.Addr `json:"-"` - RawDstAddr net.Addr `json:"-"` + RawSrcAddr net.Addr `json:"-"` + RawDstAddr net.Addr `json:"-"` // Only domain rule SniffHost string `json:"sniffHost"` } @@ -162,6 +162,10 @@ func (m *Metadata) SourceAddress() string { return net.JoinHostPort(m.SrcIP.String(), strconv.FormatUint(uint64(m.SrcPort), 10)) } +func (m *Metadata) SourceAddrPort() netip.AddrPort { + return netip.AddrPortFrom(m.SrcIP.Unmap(), m.SrcPort) +} + func (m *Metadata) SourceDetail() string { if m.Type == INNER { return fmt.Sprintf("%s", MihomoName) diff --git a/constant/rule_extra.go b/constant/rule_extra.go index 62dc1cc3..b4ba65d9 100644 --- a/constant/rule_extra.go +++ b/constant/rule_extra.go @@ -5,7 +5,7 @@ import ( ) type RuleGeoSite interface { - GetDomainMatcher() *router.DomainMatcher + GetDomainMatcher() router.DomainMatcher } type RuleGeoIP interface { diff --git a/constant/tun.go b/constant/tun.go index 5e2841bc..f6c0e011 100644 --- a/constant/tun.go +++ b/constant/tun.go @@ -9,14 +9,12 @@ import ( var StackTypeMapping = map[string]TUNStack{ strings.ToLower(TunGvisor.String()): TunGvisor, strings.ToLower(TunSystem.String()): TunSystem, - strings.ToLower(TunLWIP.String()): TunLWIP, strings.ToLower(TunMixed.String()): TunMixed, } const ( TunGvisor TUNStack = iota TunSystem - TunLWIP TunMixed ) @@ -64,8 +62,6 @@ func (e TUNStack) String() string { return "gVisor" case TunSystem: return "System" - case TunLWIP: - return "LWIP" case TunMixed: return "Mixed" default: diff --git a/dns/filters.go b/dns/filters.go index 8eb1e48e..46244c35 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -74,7 +74,7 @@ func (df *domainFilter) Match(domain string) bool { } type geoSiteFilter struct { - matchers []*router.DomainMatcher + matchers []router.DomainMatcher } func NewGeoSite(group string) (fallbackDomainFilter, error) { @@ -87,7 +87,7 @@ func NewGeoSite(group string) (fallbackDomainFilter, error) { return nil, err } filter := &geoSiteFilter{ - matchers: []*router.DomainMatcher{matcher}, + matchers: []router.DomainMatcher{matcher}, } return filter, nil } diff --git a/dns/resolver.go b/dns/resolver.go index e1ca95dd..8ea68ed7 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -400,7 +400,7 @@ type FallbackFilter struct { GeoIPCode string IPCIDR []netip.Prefix Domain []string - GeoSite []*router.DomainMatcher + GeoSite []router.DomainMatcher } type Config struct { @@ -483,32 +483,34 @@ func NewResolver(config Config) *Resolver { r.policy = make([]dnsPolicy, 0) var triePolicy *trie.DomainTrie[[]dnsClient] - insertTriePolicy := func() { + insertPolicy := func(policy dnsPolicy) { if triePolicy != nil { triePolicy.Optimize() r.policy = append(r.policy, domainTriePolicy{triePolicy}) triePolicy = nil } + if policy != nil { + r.policy = append(r.policy, policy) + } } for pair := config.Policy.Oldest(); pair != nil; pair = pair.Next() { domain, nameserver := pair.Key, pair.Value - domain = strings.ToLower(domain) if temp := strings.Split(domain, ":"); len(temp) == 2 { prefix := temp[0] key := temp[1] - switch strings.ToLower(prefix) { + switch prefix { case "rule-set": if p, ok := config.RuleProviders[key]; ok { - insertTriePolicy() - r.policy = append(r.policy, domainSetPolicy{ + log.Debugln("Adding rule-set policy: %s ", key) + insertPolicy(domainSetPolicy{ domainSetProvider: p, dnsClients: cacheTransform(nameserver), }) continue } else { - log.Warnln("can't found ruleset policy: %s", key) + log.Warnln("Can't found ruleset policy: %s", key) } case "geosite": inverse := false @@ -516,14 +518,13 @@ func NewResolver(config Config) *Resolver { inverse = true key = key[1:] } - log.Debugln("adding geosite policy: %s inversed %t", key, inverse) + log.Debugln("Adding geosite policy: %s inversed %t", key, inverse) matcher, err := NewGeoSite(key) if err != nil { log.Warnln("adding geosite policy %s error: %s", key, err) continue } - insertTriePolicy() - r.policy = append(r.policy, geositePolicy{ + insertPolicy(geositePolicy{ matcher: matcher, inverse: inverse, dnsClients: cacheTransform(nameserver), @@ -536,7 +537,7 @@ func NewResolver(config Config) *Resolver { } _ = triePolicy.Insert(domain, cacheTransform(nameserver)) } - insertTriePolicy() + insertPolicy(nil) } fallbackIPFilters := []fallbackIPFilter{} diff --git a/docs/config.yaml b/docs/config.yaml index bdb822f7..2a44a993 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -13,6 +13,11 @@ authentication: # http,socks入口的验证用户名,密码 skip-auth-prefixes: # 设置跳过验证的IP段 - 127.0.0.1/8 - ::1/128 +lan-allowed-ips: # 允许连接的 IP 地址段,仅作用于 allow-lan 为 true, 默认值为0.0.0.0/0和::/0 + - 0.0.0.0/0 + - ::/0 +lan-disallowed-ips: # 禁止连接的 IP 地址段, 黑名单优先级高于白名单, 默认值为空 + - 192.168.0.3/32 # find-process-mode has 3 values:always, strict, off # - always, 开启,强制匹配所有进程 @@ -28,6 +33,14 @@ geox-url: geosite: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite.dat" mmdb: "https://fastly.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip.metadb" +geo-auto-update: false # 是否自动更新 geodata +geo-update-interval: 24 # 更新间隔,单位:小时 + +# Matcher implementation used by GeoSite, available implementations: +# - succinct (default, same as rule-set) +# - mph (from V2Ray, also `hybrid` in Xray) +# geosite-matcher: succinct + log-level: debug # 日志等级 silent/error/warning/info/debug ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录 @@ -90,28 +103,34 @@ profile: # 存储 select 选择记录 # Tun 配置 tun: enable: false - stack: system # gvisor / lwip + stack: system # gvisor/mixed dns-hijack: - 0.0.0.0:53 # 需要劫持的 DNS # auto-detect-interface: true # 自动识别出口网卡 # auto-route: true # 配置路由表 # mtu: 9000 # 最大传输单元 + # gso: false # 启用通用分段卸载, 仅支持 Linux + # gso-max-size: 65536 # 通用分段卸载包的最大大小 # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 - inet4-route-address: # 启用 auto_route 时使用自定义路由而不是默认路由 + inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - 0.0.0.0/1 - 128.0.0.0/1 - inet6-route-address: # 启用 auto_route 时使用自定义路由而不是默认路由 + inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 - "::/1" - "8000::/1" # endpoint-independent-nat: false # 启用独立于端点的 NAT - # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route + # include-interface: # 限制被路由的接口。默认不限制, 与 `exclude-interface` 冲突 + # - "lan0" + # exclude-interface: # 排除路由的接口, 与 `include-interface` 冲突 + # - "lan1" + # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route # - 0 # include-uid-range: # 限制被路由的的用户范围 - # - 1000-99999 + # - 1000:9999 # exclude-uid: # 排除路由的的用户 #- 1000 # exclude-uid-range: # 排除路由的的用户范围 - # - 1000-99999 + # - 1000:9999 # Android 用户和应用规则仅在 Android 下被支持 # 并且需要 auto-route @@ -632,9 +651,8 @@ proxies: # socks5 type: hysteria server: server.com port: 443 - # ports: 1000,2000-3000,5000 # port 不可省略, - auth_str: yourpassword # 将会在未来某个时候删除 - # auth-str: yourpassword + # ports: 1000,2000-3000,5000 # port 不可省略 + auth-str: yourpassword # obfs: obfs_str # alpn: # - h3 @@ -643,14 +661,11 @@ proxies: # socks5 down: "200 Mbps" # 若不写单位,默认为 Mbps # sni: server.com # skip-cert-verify: false - # recv_window_conn: 12582912 # 将会在未来某个时候删除 # recv-window-conn: 12582912 - # recv_window: 52428800 # 将会在未来某个时候删除 # recv-window: 52428800 # ca: "./my.ca" - # ca_str: "xyz" # 将会在未来某个时候删除 # ca-str: "xyz" - # disable_mtu_discovery: false + # disable-mtu-discovery: false # fingerprint: xxxx # fast-open: true # 支持 TCP 快速打开,默认为 false @@ -691,7 +706,7 @@ proxies: # socks5 # dialer-proxy: "ss1" # remote-dns-resolve: true # 强制dns远程解析,默认值为false # dns: [ 1.1.1.1, 8.8.8.8 ] # 仅在remote-dns-resolve为true时生效 - # 如果peers不为空,该段落中的allowed_ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 + # 如果peers不为空,该段落中的allowed-ips不可为空;前面段落的server,port,ip,ipv6,public-key,pre-shared-key均会被忽略,但private-key会被保留且只能在顶层指定 # peers: # - server: 162.159.192.1 # port: 2480 @@ -699,7 +714,7 @@ proxies: # socks5 # ipv6: fd01:5ca1:ab1e:80fa:ab85:6eea:213f:f4a5 # public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= # # pre-shared-key: 31aIhAPwktDGpH4JDhA8GNvjFXEf/a6+UaQRyOAiyfM= - # allowed_ips: ['0.0.0.0/0'] + # allowed-ips: ['0.0.0.0/0'] # reserved: [209,98,59] # tuic @@ -737,7 +752,7 @@ proxies: # socks5 # The supported obfses: # plain http_simple http_post # random_head tls1.2_ticket_auth tls1.2_ticket_fastauth - # The supported supported protocols: + # The supported protocols: # origin auth_sha1_v4 auth_aes128_md5 # auth_aes128_sha1 auth_chain_a auth_chain_b - name: "ssr" @@ -836,6 +851,15 @@ proxy-providers: interval: 600 # lazy: true url: https://cp.cloudflare.com/generate_204 + override: # 覆写节点加载时的一些配置项 + skip-cert-verify: true + udp: true + # down: "50 Mbps" + # up: "10 Mbps" + # dialer-proxy: proxy + # interface-name: tailscale0 + # routing-mark: 233 + # ip-version: ipv4-prefer test: type: file path: /test.yaml @@ -986,42 +1010,42 @@ listeners: type: tun # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) - stack: system # gvisor / lwip + stack: system # gvisor / mixed dns-hijack: - - 0.0.0.0:53 # 需要劫持的 DNS + - 0.0.0.0:53 # 需要劫持的 DNS # auto-detect-interface: false # 自动识别出口网卡 # auto-route: false # 配置路由表 # mtu: 9000 # 最大传输单元 inet4-address: # 必须手动设置ipv4地址段 - - 198.19.0.1/30 + - 198.19.0.1/30 inet6-address: # 必须手动设置ipv6地址段 - - "fdfe:dcba:9877::1/126" - # strict_route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 - # inet4_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由 - # - 0.0.0.0/1 - # - 128.0.0.0/1 - # inet6_route_address: # 启用 auto_route 时使用自定义路由而不是默认路由 - # - "::/1" - # - "8000::/1" - # endpoint_independent_nat: false # 启用独立于端点的 NAT - # include_uid: # UID 规则仅在 Linux 下被支持,并且需要 auto_route + - "fdfe:dcba:9877::1/126" + # strict-route: true # 将所有连接路由到tun来防止泄漏,但你的设备将无法其他设备被访问 + # inet4-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 + # - 0.0.0.0/1 + # - 128.0.0.0/1 + # inet6-route-address: # 启用 auto-route 时使用自定义路由而不是默认路由 + # - "::/1" + # - "8000::/1" + # endpoint-independent-nat: false # 启用独立于端点的 NAT + # include-uid: # UID 规则仅在 Linux 下被支持,并且需要 auto-route # - 0 - # include_uid_range: # 限制被路由的的用户范围 - # - 1000-99999 - # exclude_uid: # 排除路由的的用户 + # include-uid-range: # 限制被路由的的用户范围 + # - 1000:99999 + # exclude-uid: # 排除路由的的用户 # - 1000 - # exclude_uid_range: # 排除路由的的用户范围 - # - 1000-99999 + # exclude-uid-range: # 排除路由的的用户范围 + # - 1000:99999 # Android 用户和应用规则仅在 Android 下被支持 - # 并且需要 auto_route + # 并且需要 auto-route - # include_android_user: # 限制被路由的 Android 用户 + # include-android-user: # 限制被路由的 Android 用户 # - 0 # - 10 - # include_package: # 限制被路由的 Android 应用包名 + # include-package: # 限制被路由的 Android 应用包名 # - com.android.chrome - # exclude_package: # 排除被路由的 Android 应用包名 + # exclude-package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin # 入口配置与 Listener 等价,传入流量将和 socks,mixed 等入口一样按照 mode 所指定的方式进行匹配处理 # shadowsocks,vmess 入口配置(传入流量将和socks,mixed等入口一样按照mode所指定的方式进行匹配处理) diff --git a/go.mod b/go.mod index b4462c5c..866ac035 100644 --- a/go.mod +++ b/go.mod @@ -14,19 +14,19 @@ require ( github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.3.1 github.com/gofrs/uuid/v5 v5.0.0 - github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c + github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 github.com/jpillora/backoff v1.0.0 github.com/klauspost/cpuid/v2 v2.2.6 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.40.1-0.20231130135418-0c1b47cf9394 - github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b - github.com/metacubex/sing-shadowsocks v0.2.5 - github.com/metacubex/sing-shadowsocks2 v0.1.4 - github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd - github.com/metacubex/sing-vmess v0.1.9-0.20230921005247-a0488d7dac74 - github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170 + github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796 + github.com/metacubex/sing-shadowsocks v0.2.6 + github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1 + github.com/metacubex/sing-tun v0.2.0-beta.4 + github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f + github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 github.com/miekg/dns v1.1.57 github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 @@ -34,22 +34,22 @@ require ( github.com/puzpuzpuz/xsync/v3 v3.0.2 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.2.18-0.20231108041402-4fbbd193203c - github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07 + github.com/sagernet/sing v0.3.0-rc.3 + github.com/sagernet/sing-mux v0.1.6-beta.1 github.com/sagernet/sing-shadowtls v0.1.4 - 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-20230807125731-5d4a7ef2dc5f - github.com/samber/lo v1.38.1 - github.com/shirou/gopsutil/v3 v3.23.10 + github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 + github.com/sagernet/utls v1.5.4 + github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e + github.com/samber/lo v1.39.0 + github.com/shirou/gopsutil/v3 v3.23.11 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.8.4 github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/zhangyunhao116/fastrand v0.3.0 go.uber.org/automaxprocs v1.5.3 golang.org/x/crypto v0.16.0 - golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa - golang.org/x/net v0.18.0 + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb + golang.org/x/net v0.19.0 golang.org/x/sync v0.5.0 golang.org/x/sys v0.15.0 google.golang.org/protobuf v1.31.0 @@ -58,11 +58,12 @@ require ( ) require ( - github.com/RyuaNerin/go-krypto v1.0.2 // indirect + github.com/RyuaNerin/go-krypto v1.2.4 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/andybalholm/brotli v1.0.5 // indirect + github.com/andybalholm/brotli v1.0.6 // indirect github.com/buger/jsonparser v1.1.1 // indirect + github.com/cloudflare/circl v1.3.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect @@ -79,11 +80,11 @@ 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.16.7 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/socket v0.4.1 // indirect - github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 // indirect + github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect github.com/onsi/ginkgo/v2 v2.9.5 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect @@ -91,8 +92,7 @@ require ( 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.4.1 // 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/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect @@ -105,11 +105,11 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.uber.org/mock v0.3.0 // indirect - go4.org/netipx v0.0.0-20230824141953-6213f710f925 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.15.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.16.0 // indirect ) -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231118023733-957d84f17d2c +replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f diff --git a/go.sum b/go.sum index 0e0ab68c..3f80c92d 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,16 @@ 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/RyuaNerin/elliptic2 v1.0.0/go.mod h1:wWB8fWrJI/6EPJkyV/r1Rj0hxUgrusmqSj8JN6yNf/A= +github.com/RyuaNerin/go-krypto v1.2.4 h1:mXuNdK6M317aPV0llW6Xpjbo4moOlPF7Yxz4tb4b4Go= +github.com/RyuaNerin/go-krypto v1.2.4/go.mod h1:QqCYkoutU3yInyD9INt2PGolVRsc3W4oraQadVGXJ/8= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 h1:cDVUiFo+npB0ZASqnw4q90ylaVAbnYyx0JYqK4YcGok= github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= -github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= @@ -20,6 +21,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4= github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM= +github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= +github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8= github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -77,16 +80,16 @@ github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I= 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-20231016090811-6a2c8fbdcc1c h1:PgxFEySCI41sH0mB7/2XswdXbUykQsRUGod8Rn+NubM= -github.com/insomniacslk/dhcp v0.0.0-20231016090811-6a2c8fbdcc1c/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= +github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= +github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 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.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -103,24 +106,24 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= -github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8 h1:npBvaPAT145UY8682AzpUMWpdIxJti/WPLjy7gCiYYs= -github.com/metacubex/gvisor v0.0.0-20231001104248-0f672c3fb8d8/go.mod h1:ZR6Gas7P1GcADCVBc1uOrA0bLQqDDyp70+63fD/BE2c= +github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc h1:+yTZ6q2EeQCAJNpKNEu5j32Pm23ShD38ElIa635wTrk= +github.com/metacubex/gvisor v0.0.0-20231209122014-3e43224c7bbc/go.mod h1:rhBU9tD5ktoGPBtXUquhWuGJ4u+8ZZzBMi2cAdv9q8Y= github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394 h1:dIT+KB2hknBCrwVAXPeY9tpzzkOZP5m40yqUteRT6/Y= github.com/metacubex/quic-go v0.40.1-0.20231130135418-0c1b47cf9394/go.mod h1:F/t8VnA47xoia8ABlNA4InkZjssvFJ5p6E6jKdbkgAs= -github.com/metacubex/sing v0.0.0-20231118023733-957d84f17d2c h1:SZwaf42NVCIDaHw5X2HOnNcEiK9ao6yO+N4zYyKfXe8= -github.com/metacubex/sing v0.0.0-20231118023733-957d84f17d2c/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b h1:7XXoEePvxfkQN9b2wB8UXU3uzb9uL8syEFF7A9VAKKQ= -github.com/metacubex/sing-quic v0.0.0-20231130141855-0022295e524b/go.mod h1:Gu5/zqZDd5G1AUtoV2yjAPWOEy7zwbU2DBUjdxJh0Kw= -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.15-0.20231103033938-170591e8d5bd h1:k0+92eARqyTAovGhg2AxdsMWHjUsdiGCnR5NuXF3CQY= -github.com/metacubex/sing-tun v0.1.15-0.20231103033938-170591e8d5bd/go.mod h1:Q7zmpJ+qOvMMXyUoYlxGQuWkqALUpXzFSSqO+KLPyzA= -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-20231001110902-321836559170 h1:DBGA0hmrP4pVIwLiXUONdphjcppED+plmVaKf1oqkwk= -github.com/metacubex/sing-wireguard v0.0.0-20231001110902-321836559170/go.mod h1:/VbJfbdLnANE+SKXyMk/96sTRrD4GdFLh5mkegqqFcY= +github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f h1:T2PuaAiXMSC3mjRRUmIomuiu3jhi7EWSbzXtVIrVUC4= +github.com/metacubex/sing v0.0.0-20231221131356-d73c21c7ea3f/go.mod h1:9pfuAH6mZfgnz/YjP6xu5sxx882rfyjpcrTdUpd6w3g= +github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796 h1:xiCPttMGAaIh4Ad6t85VxUoUv+Sg88eXzzUvYN8gT5w= +github.com/metacubex/sing-quic v0.0.0-20231220152840-85620b446796/go.mod h1:E1e1Uu6YaJddD+c0DtJlSOkfMI0NLdOVhM60KAlcssY= +github.com/metacubex/sing-shadowsocks v0.2.6 h1:6oEB3QcsFYnNiFeoevcXrCwJ3sAablwVSgtE9R3QeFQ= +github.com/metacubex/sing-shadowsocks v0.2.6/go.mod h1:zIkMeSnb8Mbf4hdqhw0pjzkn1d99YJ3JQm/VBg5WMTg= +github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1 h1:ftbpVCK1+n3jxIP7+NMkRYOFEQtGPodV42MizsPey0w= +github.com/metacubex/sing-shadowsocks2 v0.1.6-beta.1/go.mod h1:kUVj2X+2wUh6Z5pAk9WrjDRehPyXolC6nJyFl7ln4V4= +github.com/metacubex/sing-tun v0.2.0-beta.4 h1:42F+uF9zKsaWsiUXNKzZD8aRkyPN9m5SdpF2yZEZar8= +github.com/metacubex/sing-tun v0.2.0-beta.4/go.mod h1:O8wFThUDfiwb6y56I714dQuyaqT8DW9VCD/wvGesyLM= +github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f h1:QjXrHKbTMBip/C+R79bvbfr42xH1gZl3uFb0RELdZiQ= +github.com/metacubex/sing-vmess v0.1.9-0.20231207122118-72303677451f/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY= +github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232 h1:loWjR+k9dxqBSgruGyT5hE8UCRMmCEjxqZbryfY9no4= +github.com/metacubex/sing-wireguard v0.0.0-20231209125515-0594297f7232/go.mod h1:NGCrBZ+fUmp81yaA1kVskcNWBnwl5z4UHxz47A01zm8= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU= @@ -154,28 +157,26 @@ github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= -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.5-0.20231109075101-6b086ed6bb07 h1:ncKb5tVOsCQgCsv6UpsA0jinbNb5OQ5GMPJlyQP3EHM= -github.com/sagernet/sing-mux v0.1.5-0.20231109075101-6b086ed6bb07/go.mod h1:u/MZf32xPG8jEKe3t+xUV67EBnKtDtCaPhsJQOQGUYU= +github.com/sagernet/sing-mux v0.1.6-beta.1 h1:ADs1TgiMfA628Y2qfv21tEvePDZjBRRYddwtNFZiwe8= +github.com/sagernet/sing-mux v0.1.6-beta.1/go.mod h1:WWtRmrwCDgb+g+7Da6o62I9WiMNB0a3w6BJhEpNQlNA= 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-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-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/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= +github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= +github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= +github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= +github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= +github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= +github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e h1:iGH0RMv2FzELOFNFQtvsxH7NPmlo7X5JizEK51UCojo= +github.com/sagernet/wireguard-go v0.0.0-20231209092712-9a439356a62e/go.mod h1:YbL4TKHRR6APYQv3U2RGfwLDpPYSyWz6oUlpISBEzBE= +github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= +github.com/samber/lo v1.39.0/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.10 h1:/N42opWlYzegYaVkWejXWJpbzKv2JDy3mrgGzKsh9hM= -github.com/shirou/gopsutil/v3 v3.23.10/go.mod h1:JIE26kpucQi+innVlAUnIEOSBhBUkirr5b44yr55+WE= +github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ= +github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= 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= @@ -221,22 +222,22 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -go4.org/netipx v0.0.0-20230824141953-6213f710f925 h1:eeQDDVKFkx0g4Hyy8pHgmZaK0EqB4SD6rvKbUdN3ziQ= -go4.org/netipx v0.0.0-20230824141953-6213f710f925/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 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.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= -golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 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.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -255,18 +256,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -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/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8= -golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= +golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 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= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 72d7380e..783da4d3 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -112,6 +112,7 @@ func ApplyConfig(cfg *config.Config, force bool) { loadRuleProvider(cfg.RuleProviders) runtime.GC() tunnel.OnRunning() + hcCompatibleProvider(cfg.Providers) log.SetLevel(cfg.General.LogLevel) } @@ -140,17 +141,20 @@ func GetGeneral() *config.General { VmessConfig: ports.VmessConfig, Authentication: authenticator, SkipAuthPrefixes: inbound.SkipAuthPrefixes(), + LanAllowedIPs: inbound.AllowedIPs(), + LanDisAllowedIPs: inbound.DisAllowedIPs(), AllowLan: listener.AllowLan(), BindAddress: listener.BindAddress(), }, - Controller: config.Controller{}, - Mode: tunnel.Mode(), - LogLevel: log.Level(), - IPv6: !resolver.DisableIPv6, - GeodataLoader: G.LoaderName(), - Interface: dialer.DefaultInterface.Load(), - Sniffing: tunnel.IsSniffing(), - TCPConcurrent: dialer.GetTcpConcurrent(), + Controller: config.Controller{}, + Mode: tunnel.Mode(), + LogLevel: log.Level(), + IPv6: !resolver.DisableIPv6, + GeodataLoader: G.LoaderName(), + GeositeMatcher: G.SiteMatcherName(), + Interface: dialer.DefaultInterface.Load(), + Sniffing: tunnel.IsSniffing(), + TCPConcurrent: dialer.GetTcpConcurrent(), } return general @@ -165,6 +169,8 @@ func updateListeners(general *config.General, listeners map[string]C.InboundList allowLan := general.AllowLan listener.SetAllowLan(allowLan) inbound.SetSkipAuthPrefixes(general.SkipAuthPrefixes) + inbound.SetAllowedIPs(general.LanAllowedIPs) + inbound.SetDisAllowedIPs(general.LanDisAllowedIPs) bindAddress := general.BindAddress listener.SetBindAddress(bindAddress) @@ -265,7 +271,7 @@ func updateRules(rules []C.Rule, subRules map[string][]C.Rule, ruleProviders map func loadProvider(pv provider.Provider) { if pv.VehicleType() == provider.Compatible { - log.Infoln("Start initial compatible provider %s", pv.Name()) + return } else { log.Infoln("Start initial provider %s", (pv).Name()) } @@ -313,15 +319,32 @@ func loadProxyProvider(proxyProviders map[string]provider.ProxyProvider) { go func() { defer func() { <-ch; wg.Done() }() loadProvider(proxyProvider) - if proxyProvider.VehicleType() == provider.Compatible { - go proxyProvider.HealthCheck() - } }() } wg.Wait() } +func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) { + // limit concurrent size + wg := sync.WaitGroup{} + ch := make(chan struct{}, concurrentCount) + for _, proxyProvider := range proxyProviders { + proxyProvider := proxyProvider + if proxyProvider.VehicleType() == provider.Compatible { + log.Infoln("Start initial Compatible provider %s", proxyProvider.Name()) + wg.Add(1) + ch <- struct{}{} + go func() { + defer func() { <-ch; wg.Done() }() + if err := proxyProvider.Initial(); err != nil { + log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err) + } + }() + } + } + +} func updateTun(general *config.General) { if general == nil { return @@ -379,8 +402,8 @@ func updateGeneral(general *config.General) { } iface.FlushCache() - geodataLoader := general.GeodataLoader - G.SetLoader(geodataLoader) + G.SetLoader(general.GeodataLoader) + G.SetSiteMatcher(general.GeositeMatcher) } func updateUsers(users []auth.AuthUser) { diff --git a/hub/route/configs.go b/hub/route/configs.go index 3b5f62b3..ec0b464c 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -10,7 +10,6 @@ import ( "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/config" - "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/hub/executor" P "github.com/metacubex/mihomo/listener" @@ -50,6 +49,8 @@ type configSchema struct { UdptunConfig *string `json:"udptun-config"` AllowLan *bool `json:"allow-lan"` SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"` + LanAllowedIPs *[]netip.Prefix `json:"lan-allowed-ips"` + LanDisAllowedIPs *[]netip.Prefix `json:"lan-disallowed-ips"` BindAddress *string `json:"bind-address"` Mode *tunnel.TunnelMode `json:"mode"` LogLevel *log.LogLevel `json:"log-level"` @@ -68,7 +69,9 @@ type tunSchema struct { AutoDetectInterface *bool `yaml:"auto-detect-interface" json:"auto-detect-interface"` //RedirectToTun []string `yaml:"-" json:"-"` - MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"` + MTU *uint32 `yaml:"mtu" json:"mtu,omitempty"` + GSO *bool `yaml:"gso" json:"gso,omitempty"` + GSOMaxSize *uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` //Inet4Address *[]netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` Inet6Address *[]netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"` @@ -76,6 +79,8 @@ type tunSchema struct { Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` + IncludeInterface *[]string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface *[]string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` IncludeUID *[]uint32 `yaml:"include-uid" json:"include-uid,omitempty"` IncludeUIDRange *[]string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` ExcludeUID *[]uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` @@ -144,6 +149,12 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun { if p.MTU != nil { def.MTU = *p.MTU } + if p.GSO != nil { + def.GSO = *p.GSO + } + if p.GSOMaxSize != nil { + def.GSOMaxSize = *p.GSOMaxSize + } //if p.Inet4Address != nil { // def.Inet4Address = *p.Inet4Address //} @@ -162,6 +173,12 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun { if p.Inet6RouteExcludeAddress != nil { def.Inet6RouteExcludeAddress = *p.Inet6RouteExcludeAddress } + if p.IncludeInterface != nil { + def.IncludeInterface = *p.IncludeInterface + } + if p.ExcludeInterface != nil { + def.ExcludeInterface = *p.ExcludeInterface + } if p.IncludeUID != nil { def.IncludeUID = *p.IncludeUID } @@ -252,6 +269,14 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { inbound.SetSkipAuthPrefixes(*general.SkipAuthPrefixes) } + if general.LanAllowedIPs != nil { + inbound.SetAllowedIPs(*general.LanAllowedIPs) + } + + if general.LanDisAllowedIPs != nil { + inbound.SetDisAllowedIPs(*general.LanDisAllowedIPs) + } + if general.BindAddress != nil { P.SetBindAddress(*general.BindAddress) } @@ -319,7 +344,7 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } } else { if req.Path == "" { - req.Path = constant.Path.Config() + req.Path = C.Path.Config() } if !filepath.IsAbs(req.Path) { render.Status(r, http.StatusBadRequest) @@ -364,7 +389,7 @@ func updateGeoDatabases(w http.ResponseWriter, r *http.Request) { return } - cfg, err := executor.ParseWithPath(constant.Path.Config()) + cfg, err := executor.ParseWithPath(C.Path.Config()) if err != nil { log.Errorln("[REST-API] update GEO databases failed: %v", err) return diff --git a/hub/route/external.go b/hub/route/external.go new file mode 100644 index 00000000..d2f06358 --- /dev/null +++ b/hub/route/external.go @@ -0,0 +1,21 @@ +package route + +import "github.com/go-chi/chi/v5" + +type externalRouter func(r chi.Router) + +var externalRouters = make([]externalRouter, 0) + +func Register(route ...externalRouter) { + externalRouters = append(externalRouters, route...) +} + +func addExternalRouters(r chi.Router) { + if len(externalRouters) == 0 { + return + } + + for _, caller := range externalRouters { + caller(r) + } +} diff --git a/hub/route/groups.go b/hub/route/groups.go index e36b8ab0..18aabf74 100644 --- a/hub/route/groups.go +++ b/hub/route/groups.go @@ -2,15 +2,17 @@ package route import ( "context" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" "net/http" "strconv" "time" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/profile/cachefile" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/tunnel" ) @@ -63,6 +65,10 @@ func getGroupDelay(w http.ResponseWriter, r *http.Request) { URLTestGroup.ForceSet("") } + if proxy.(*adapter.Proxy).Type() != C.Selector { + cachefile.Cache().SetSelected(proxy.Name(), "") + } + query := r.URL.Query() url := query.Get("url") timeout, err := strconv.ParseInt(query.Get("timeout"), 10, 32) diff --git a/hub/route/server.go b/hub/route/server.go index 6ececddb..8e7f225f 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -63,6 +63,7 @@ func Start(addr string, tlsAddr string, secret string, AllowedHeaders: []string{"Content-Type", "Authorization"}, MaxAge: 300, }) + r.Use(setPrivateNetworkAccess) r.Use(corsM.Handler) if isDebug { r.Mount("/debug", func() http.Handler { @@ -89,10 +90,11 @@ func Start(addr string, tlsAddr string, secret string, r.Mount("/connections", connectionRouter()) r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) - r.Mount("/lru", cacheRouter()) + r.Mount("/cache", cacheRouter()) r.Mount("/dns", dnsRouter()) r.Mount("/restart", restartRouter()) r.Mount("/upgrade", upgradeRouter()) + addExternalRouters(r) }) @@ -148,6 +150,15 @@ func Start(addr string, tlsAddr string, secret string, } +func setPrivateNetworkAccess(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { + w.Header().Add("Access-Control-Allow-Private-Network", "true") + } + next.ServeHTTP(w, r) + }) +} + func safeEuqal(a, b string) bool { aBuf := utils.ImmutableBytesFromString(a) bBuf := utils.ImmutableBytesFromString(b) diff --git a/listener/config/tun.go b/listener/config/tun.go index 6db1fd66..1772c6f5 100644 --- a/listener/config/tun.go +++ b/listener/config/tun.go @@ -28,6 +28,8 @@ type Tun struct { RedirectToTun []string `yaml:"-" json:"-"` MTU uint32 `yaml:"mtu" json:"mtu,omitempty"` + GSO bool `yaml:"gso" json:"gso,omitempty"` + GSOMaxSize uint32 `yaml:"gso-max-size" json:"gso-max-size,omitempty"` Inet4Address []netip.Prefix `yaml:"inet4-address" json:"inet4-address,omitempty"` Inet6Address []netip.Prefix `yaml:"inet6-address" json:"inet6-address,omitempty"` StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"` @@ -35,6 +37,8 @@ type Tun struct { Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"` Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"` Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"` + IncludeInterface []string `yaml:"include-interface" json:"include-interface,omitempty"` + ExcludeInterface []string `yaml:"exclude-interface" json:"exclude-interface,omitempty"` IncludeUID []uint32 `yaml:"include-uid" json:"include-uid,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` diff --git a/listener/http/proxy.go b/listener/http/proxy.go index 45f53d6b..4822eabc 100644 --- a/listener/http/proxy.go +++ b/listener/http/proxy.go @@ -1,10 +1,14 @@ package http import ( + "context" "fmt" + "io" "net" "net/http" "strings" + "sync" + _ "unsafe" "github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/common/lru" @@ -14,9 +18,18 @@ import ( "github.com/metacubex/mihomo/log" ) +//go:linkname registerOnHitEOF net/http.registerOnHitEOF +func registerOnHitEOF(rc io.ReadCloser, fn func()) + +//go:linkname requestBodyRemains net/http.requestBodyRemains +func requestBodyRemains(rc io.ReadCloser) bool + func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], additions ...inbound.Addition) { client := newClient(c, tunnel, additions...) defer client.CloseIdleConnections() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + peekMutex := sync.Mutex{} conn := N.NewBufferedConn(c) @@ -24,7 +37,9 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], trusted := cache == nil // disable authenticate if lru is nil for keepAlive { + peekMutex.Lock() request, err := ReadRequest(conn.Reader()) + peekMutex.Unlock() if err != nil { break } @@ -72,6 +87,23 @@ func HandleConn(c net.Conn, tunnel C.Tunnel, cache *lru.LruCache[string, bool], if request.URL.Scheme == "" || request.URL.Host == "" { resp = responseWith(request, http.StatusBadRequest) } else { + request = request.WithContext(ctx) + + startBackgroundRead := func() { + go func() { + peekMutex.Lock() + defer peekMutex.Unlock() + _, err := conn.Peek(1) + if err != nil { + cancel() + } + }() + } + if requestBodyRemains(request.Body) { + registerOnHitEOF(request.Body, startBackgroundRead) + } else { + startBackgroundRead() + } resp, err = client.Do(request) if err != nil { resp = responseWith(request, http.StatusBadGateway) diff --git a/listener/http/server.go b/listener/http/server.go index be397d9a..3ff1679d 100644 --- a/listener/http/server.go +++ b/listener/http/server.go @@ -71,6 +71,12 @@ func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additi t.SetKeepAlive(false) } } + if len(additions) == 0 { // only apply on default listener + if inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) { + _ = conn.Close() + continue + } + } go HandleConn(conn, tunnel, c, additions...) } }() diff --git a/listener/inbound/tun.go b/listener/inbound/tun.go index d1044b8e..a1fdebfa 100644 --- a/listener/inbound/tun.go +++ b/listener/inbound/tun.go @@ -19,6 +19,8 @@ type TunOption struct { AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"` MTU uint32 `inbound:"mtu,omitempty"` + GSO bool `inbound:"gso,omitempty"` + GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"` Inet4Address []string `inbound:"inet4_address,omitempty"` Inet6Address []string `inbound:"inet6_address,omitempty"` StrictRoute bool `inbound:"strict_route,omitempty"` @@ -26,6 +28,8 @@ type TunOption struct { Inet6RouteAddress []string `inbound:"inet6_route_address,omitempty"` Inet4RouteExcludeAddress []string `inbound:"inet4_route_exclude_address,omitempty"` Inet6RouteExcludeAddress []string `inbound:"inet6_route_exclude_address,omitempty"` + IncludeInterface []string `inbound:"include-interface,omitempty"` + ExcludeInterface []string `inbound:"exclude-interface" json:"exclude-interface,omitempty"` IncludeUID []uint32 `inbound:"include_uid,omitempty"` IncludeUIDRange []string `inbound:"include_uid_range,omitempty"` ExcludeUID []uint32 `inbound:"exclude_uid,omitempty"` @@ -93,6 +97,8 @@ func NewTun(options *TunOption) (*Tun, error) { AutoRoute: options.AutoRoute, AutoDetectInterface: options.AutoDetectInterface, MTU: options.MTU, + GSO: options.GSO, + GSOMaxSize: options.GSOMaxSize, Inet4Address: inet4Address, Inet6Address: inet6Address, StrictRoute: options.StrictRoute, @@ -100,6 +106,8 @@ func NewTun(options *TunOption) (*Tun, error) { Inet6RouteAddress: inet6RouteAddress, Inet4RouteExcludeAddress: inet4RouteExcludeAddress, Inet6RouteExcludeAddress: inet6RouteExcludeAddress, + IncludeInterface: options.IncludeInterface, + ExcludeInterface: options.ExcludeInterface, IncludeUID: options.IncludeUID, IncludeUIDRange: options.IncludeUIDRange, ExcludeUID: options.ExcludeUID, diff --git a/listener/listener.go b/listener/listener.go index 903cb64b..ac602971 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -818,6 +818,8 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { LastTunConf.AutoRoute != tunConf.AutoRoute || LastTunConf.AutoDetectInterface != tunConf.AutoDetectInterface || LastTunConf.MTU != tunConf.MTU || + LastTunConf.GSO != tunConf.GSO || + LastTunConf.GSOMaxSize != tunConf.GSOMaxSize || LastTunConf.StrictRoute != tunConf.StrictRoute || LastTunConf.EndpointIndependentNat != tunConf.EndpointIndependentNat || LastTunConf.UDPTimeout != tunConf.UDPTimeout || @@ -857,6 +859,14 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { return tunConf.Inet6RouteExcludeAddress[i].String() < tunConf.Inet6RouteExcludeAddress[j].String() }) + sort.Slice(tunConf.IncludeInterface, func(i, j int) bool { + return tunConf.IncludeInterface[i] < tunConf.IncludeInterface[j] + }) + + sort.Slice(tunConf.ExcludeInterface, func(i, j int) bool { + return tunConf.ExcludeInterface[i] < tunConf.ExcludeInterface[j] + }) + sort.Slice(tunConf.IncludeUID, func(i, j int) bool { return tunConf.IncludeUID[i] < tunConf.IncludeUID[j] }) @@ -892,6 +902,8 @@ func hasTunConfigChange(tunConf *LC.Tun) bool { !slices.Equal(tunConf.Inet6RouteAddress, LastTunConf.Inet6RouteAddress) || !slices.Equal(tunConf.Inet4RouteExcludeAddress, LastTunConf.Inet4RouteExcludeAddress) || !slices.Equal(tunConf.Inet6RouteExcludeAddress, LastTunConf.Inet6RouteExcludeAddress) || + !slices.Equal(tunConf.IncludeInterface, LastTunConf.IncludeInterface) || + !slices.Equal(tunConf.ExcludeInterface, LastTunConf.ExcludeInterface) || !slices.Equal(tunConf.IncludeUID, LastTunConf.IncludeUID) || !slices.Equal(tunConf.IncludeUIDRange, LastTunConf.IncludeUIDRange) || !slices.Equal(tunConf.ExcludeUID, LastTunConf.ExcludeUID) || diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go index 0f1b3aab..94613039 100644 --- a/listener/mixed/mixed.go +++ b/listener/mixed/mixed.go @@ -62,6 +62,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener } continue } + if len(additions) == 0 { // only apply on default listener + if inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { + _ = c.Close() + continue + } + } go handleConn(c, tunnel, ml.cache, additions...) } }() diff --git a/listener/sing/sing.go b/listener/sing/sing.go index 65c42b6a..4e31faeb 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -138,41 +138,36 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta } func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { - if deadline.NeedAdditionalReadDeadline(conn) { - conn = deadline.NewFallbackPacketConn(bufio.NewNetPacketConn(conn)) // conn from sing should check NeedAdditionalReadDeadline - } defer func() { _ = conn.Close() }() mutex := sync.Mutex{} - conn2 := conn // a new interface to set nil in defer + conn2 := bufio.NewNetPacketConn(conn) // a new interface to set nil in defer defer func() { mutex.Lock() // this goroutine must exit after all conn.WritePacket() is not running defer mutex.Unlock() conn2 = nil }() - var buff *buf.Buffer - newBuffer := func() *buf.Buffer { - buff = buf.NewPacket() // do not use stack buffer - return buff - } + rwOptions := network.ReadWaitOptions{} readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) if isReadWaiter { - readWaiter.InitializeReadWaiter(newBuffer) + readWaiter.InitializeReadWaiter(rwOptions) } for { var ( + buff *buf.Buffer dest M.Socksaddr err error ) - buff = nil // clear last loop status, avoid repeat release if isReadWaiter { - dest, err = readWaiter.WaitReadPacket() + buff, dest, err = readWaiter.WaitReadPacket() } else { - dest, err = conn.ReadPacket(newBuffer()) + buff = rwOptions.NewPacketBuffer() + dest, err = conn.ReadPacket(buff) + if buff != nil { + rwOptions.PostReturn(buff) + } } if err != nil { - if buff != nil { - buff.Release() - } + buff.Release() if ShouldIgnorePacketError(err) { break } @@ -212,7 +207,7 @@ func ShouldIgnorePacketError(err error) bool { } type packet struct { - conn *network.PacketConn + conn *network.NetPacketConn mutex *sync.Mutex rAddr net.Addr lAddr net.Addr @@ -238,18 +233,7 @@ func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { return } - buff := buf.NewPacket() - defer buff.Release() - n, err = buff.Write(b) - if err != nil { - return - } - - err = conn.WritePacket(buff, M.SocksaddrFromNet(addr)) - if err != nil { - return - } - return + return conn.WriteTo(b, addr) } // LocalAddr returns the source IP/Port of UDP Packet diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index 1760e43c..bd5002a4 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -23,6 +23,7 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/common/network" ) type Listener struct { @@ -96,30 +97,33 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi go func() { conn := bufio.NewPacketConn(ul) - var buff *buf.Buffer - newBuffer := func() *buf.Buffer { - buff = buf.NewPacket() // do not use stack buffer - return buff + rwOptions := network.ReadWaitOptions{ + FrontHeadroom: network.CalculateFrontHeadroom(sl.service), + RearHeadroom: network.CalculateRearHeadroom(sl.service), + MTU: network.CalculateMTU(conn, sl.service), } readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) if isReadWaiter { - readWaiter.InitializeReadWaiter(newBuffer) + readWaiter.InitializeReadWaiter(rwOptions) } for { var ( + buff *buf.Buffer dest M.Socksaddr err error ) buff = nil // clear last loop status, avoid repeat release if isReadWaiter { - dest, err = readWaiter.WaitReadPacket() + buff, dest, err = readWaiter.WaitReadPacket() } else { - dest, err = conn.ReadPacket(newBuffer()) + buff = rwOptions.NewPacketBuffer() + dest, err = conn.ReadPacket(buff) + if buff != nil { + rwOptions.PostReturn(buff) + } } if err != nil { - if buff != nil { - buff.Release() - } + buff.Release() if sl.closed { break } diff --git a/listener/sing_tun/dns.go b/listener/sing_tun/dns.go index 122f5a32..056c9169 100644 --- a/listener/sing_tun/dns.go +++ b/listener/sing_tun/dns.go @@ -73,7 +73,7 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) defer cancel() inData := buff[:n] - msg, err := RelayDnsPacket(ctx, inData) + msg, err := RelayDnsPacket(ctx, inData, buff) if err != nil { return err } @@ -98,6 +98,8 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta return h.ListenerHandler.NewConnection(ctx, conn, metadata) } +const SafeDnsPacketSize = 2 * 1024 // safe size which is 1232 from https://dnsflagday.net/2020/, so 2048 is enough + func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error { if h.ShouldHijackDns(metadata.Destination.AddrPort()) { log.Debugln("[DNS] hijack udp:%s from %s", metadata.Destination.String(), metadata.Source.String()) @@ -109,72 +111,79 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network. defer mutex.Unlock() conn2 = nil }() - - var buff *buf.Buffer - newBuffer := func() *buf.Buffer { - // safe size which is 1232 from https://dnsflagday.net/2020/. - // so 2048 is enough - buff = buf.NewSize(2 * 1024) - return buff + rwOptions := network.ReadWaitOptions{ + FrontHeadroom: network.CalculateFrontHeadroom(conn), + RearHeadroom: network.CalculateRearHeadroom(conn), + MTU: SafeDnsPacketSize, } readWaiter, isReadWaiter := bufio.CreatePacketReadWaiter(conn) if isReadWaiter { - readWaiter.InitializeReadWaiter(newBuffer) + readWaiter.InitializeReadWaiter(rwOptions) } for { var ( - dest M.Socksaddr - err error + readBuff *buf.Buffer + dest M.Socksaddr + err error ) _ = conn.SetReadDeadline(time.Now().Add(DefaultDnsReadTimeout)) - buff = nil // clear last loop status, avoid repeat release + readBuff = nil // clear last loop status, avoid repeat release if isReadWaiter { - dest, err = readWaiter.WaitReadPacket() + readBuff, dest, err = readWaiter.WaitReadPacket() } else { - dest, err = conn.ReadPacket(newBuffer()) + readBuff = rwOptions.NewPacketBuffer() + dest, err = conn.ReadPacket(readBuff) + if readBuff != nil { + rwOptions.PostReturn(readBuff) + } } if err != nil { - if buff != nil { - buff.Release() + if readBuff != nil { + readBuff.Release() } if sing.ShouldIgnorePacketError(err) { break } return err } - go func(buff *buf.Buffer) { + go func() { ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout) defer cancel() - inData := buff.Bytes() - msg, err := RelayDnsPacket(ctx, inData) + inData := readBuff.Bytes() + writeBuff := readBuff + writeBuff.Resize(writeBuff.Start(), 0) + if len(writeBuff.FreeBytes()) < SafeDnsPacketSize { // only create a new buffer when space don't enough + writeBuff = rwOptions.NewPacketBuffer() + } + msg, err := RelayDnsPacket(ctx, inData, writeBuff.FreeBytes()) + if writeBuff != readBuff { + readBuff.Release() + } if err != nil { - buff.Release() - return - } - buff.Reset() - _, err = buff.Write(msg) - if err != nil { - buff.Release() + writeBuff.Release() return } + writeBuff.Truncate(len(msg)) mutex.Lock() defer mutex.Unlock() conn := conn2 if conn == nil { + writeBuff.Release() return } - err = conn.WritePacket(buff, dest) // WritePacket will release buff + err = conn.WritePacket(writeBuff, dest) // WritePacket will release writeBuff if err != nil { + writeBuff.Release() return } - }(buff) // catch buff at goroutine create, avoid next loop change buff + }() } return nil } return h.ListenerHandler.NewPacketConnection(ctx, conn, metadata) } -func RelayDnsPacket(ctx context.Context, payload []byte) ([]byte, error) { +func RelayDnsPacket(ctx context.Context, payload []byte, target []byte) ([]byte, error) { msg := &D.Msg{} if err := msg.Unpack(payload); err != nil { return nil, err @@ -184,10 +193,10 @@ func RelayDnsPacket(ctx context.Context, payload []byte) ([]byte, error) { if err != nil { m := new(D.Msg) m.SetRcode(msg, D.RcodeServerFailure) - return m.Pack() + return m.PackBuffer(target) } r.SetRcode(msg, r.Rcode) r.Compress = true - return r.Pack() + return r.PackBuffer(target) } diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index 2017e922..cc26d37d 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -94,6 +94,9 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis inbound.WithSpecialRules(""), } } + if options.GSOMaxSize == 0 { + options.GSOMaxSize = 65536 + } tunName := options.Device if tunName == "" || !checkTunName(tunName) { tunName = CalculateInterfaceName(InterfaceName) @@ -204,6 +207,7 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis tunOptions := tun.Options{ Name: tunName, MTU: tunMTU, + GSO: options.GSO, Inet4Address: options.Inet4Address, Inet6Address: options.Inet6Address, AutoRoute: options.AutoRoute, @@ -212,6 +216,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis Inet6RouteAddress: options.Inet6RouteAddress, Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress, Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress, + IncludeInterface: options.IncludeInterface, + ExcludeInterface: options.ExcludeInterface, IncludeUID: includeUID, ExcludeUID: excludeUID, IncludeAndroidUser: options.IncludeAndroidUser, @@ -236,10 +242,7 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis stackOptions := tun.StackOptions{ Context: context.TODO(), Tun: tunIf, - MTU: tunOptions.MTU, - Name: tunOptions.Name, - Inet4Address: tunOptions.Inet4Address, - Inet6Address: tunOptions.Inet6Address, + TunOptions: tunOptions, EndpointIndependentNat: options.EndpointIndependentNat, UDPTimeout: udpTimeout, Handler: handler, @@ -248,7 +251,7 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis if options.FileDescriptor > 0 { if tunName, err := getTunnelName(int32(options.FileDescriptor)); err != nil { - stackOptions.Name = tunName + stackOptions.TunOptions.Name = tunName stackOptions.ForwarderBindInterface = true } } diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index c8c33e7b..8016e958 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -59,6 +59,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener } continue } + if len(additions) == 0 { // only apply on default listener + if inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) { + _ = c.Close() + continue + } + } go handleSocks(c, tunnel, additions...) } }() diff --git a/log/log.go b/log/log.go index d0c9b330..6f565e7c 100644 --- a/log/log.go +++ b/log/log.go @@ -19,9 +19,9 @@ func init() { log.SetOutput(os.Stdout) log.SetLevel(log.DebugLevel) log.SetFormatter(&log.TextFormatter{ - FullTimestamp: true, - TimestampFormat: "2006-01-02T15:04:05.999999999Z07:00", - ForceColors: true, + FullTimestamp: true, + TimestampFormat: "2006-01-02T15:04:05.999999999Z07:00", + EnvironmentOverrideColors: true, }) } diff --git a/main.go b/main.go index 01edee9f..425b4661 100644 --- a/main.go +++ b/main.go @@ -3,16 +3,18 @@ package main import ( "flag" "fmt" - "github.com/metacubex/mihomo/constant/features" "os" "os/signal" "path/filepath" "runtime" "strings" + "sync" "syscall" + "time" "github.com/metacubex/mihomo/config" C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/constant/features" "github.com/metacubex/mihomo/hub" "github.com/metacubex/mihomo/hub/executor" "github.com/metacubex/mihomo/log" @@ -29,6 +31,8 @@ var ( externalUI string externalController string secret string + updateGeoMux sync.Mutex + updatingGeo = false ) func init() { @@ -107,6 +111,17 @@ func main() { log.Fatalln("Parse config error: %s", err.Error()) } + if C.GeoAutoUpdate { + ticker := time.NewTicker(time.Duration(C.GeoUpdateInterval) * time.Hour) + + log.Infoln("[GEO] Start update GEO database every %d hours", C.GeoUpdateInterval) + go func() { + for range ticker.C { + updateGeoDatabases() + } + }() + } + defer executor.Shutdown() termSign := make(chan os.Signal, 1) @@ -126,3 +141,39 @@ func main() { } } } + +func updateGeoDatabases() { + log.Infoln("[GEO] Start updating GEO database") + updateGeoMux.Lock() + + if updatingGeo { + updateGeoMux.Unlock() + log.Infoln("[GEO] GEO database is updating, skip") + return + } + + updatingGeo = true + updateGeoMux.Unlock() + + go func() { + defer func() { + updatingGeo = false + }() + + log.Infoln("[GEO] Updating GEO database") + + if err := config.UpdateGeoDatabases(); err != nil { + log.Errorln("[GEO] update GEO database error: %s", err.Error()) + return + } + + cfg, err := executor.ParseWithPath(C.Path.Config()) + if err != nil { + log.Errorln("[GEO] update GEO database failed: %s", err.Error()) + return + } + + log.Infoln("[GEO] Update GEO database success, apply new config") + executor.ApplyConfig(cfg, false) + }() +} diff --git a/rules/common/geosite.go b/rules/common/geosite.go index e9b19d0e..1e3c1ab5 100644 --- a/rules/common/geosite.go +++ b/rules/common/geosite.go @@ -15,7 +15,7 @@ type GEOSITE struct { *Base country string adapter string - matcher *router.DomainMatcher + matcher router.DomainMatcher recodeSize int } @@ -39,7 +39,7 @@ func (gs *GEOSITE) Payload() string { return gs.country } -func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher { +func (gs *GEOSITE) GetDomainMatcher() router.DomainMatcher { return gs.matcher } diff --git a/transport/tuic/v5/client.go b/transport/tuic/v5/client.go index 8daa0925..8a4d6fb1 100644 --- a/transport/tuic/v5/client.go +++ b/transport/tuic/v5/client.go @@ -47,7 +47,7 @@ type clientImpl struct { openStreams atomic.Int64 closed atomic.Bool - udpInputMap xsync.MapOf[uint16, net.Conn] + udpInputMap *xsync.MapOf[uint16, net.Conn] // only ready for PoolClient dialerRef C.Dialer @@ -270,7 +270,7 @@ func (t *clientImpl) forceClose(quicConn quic.Connection, err error) { if quicConn != nil { _ = quicConn.CloseWithError(ProtocolError, errStr) } - udpInputMap := &t.udpInputMap + udpInputMap := t.udpInputMap udpInputMap.Range(func(key uint16, value net.Conn) bool { conn := value _ = conn.Close() @@ -406,7 +406,7 @@ func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client ClientOption: clientOption, udp: udp, dialerRef: dialerRef, - udpInputMap: *xsync.NewMapOf[uint16, net.Conn](), + udpInputMap: xsync.NewMapOf[uint16, net.Conn](), } c := &Client{ci} runtime.SetFinalizer(c, closeClient) diff --git a/transport/vless/vision/conn.go b/transport/vless/vision/conn.go index 79c77835..5ad28134 100644 --- a/transport/vless/vision/conn.go +++ b/transport/vless/vision/conn.go @@ -157,14 +157,7 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { func (vc *Conn) Write(p []byte) (int, error) { if vc.writeFilterApplicationData { - buffer := buf.New() - defer buffer.Release() - buffer.Write(p) - err := vc.WriteBuffer(buffer) - if err != nil { - return 0, err - } - return len(p), nil + return N.WriteBuffer(vc, buf.As(p)) } return vc.ExtendedWriter.Write(p) } @@ -266,6 +259,10 @@ func (vc *Conn) FrontHeadroom() int { return PaddingHeaderLen - uuid.Size } +func (vc *Conn) RearHeadroom() int { + return 500 + 900 +} + func (vc *Conn) NeedHandshake() bool { return vc.needHandshake } diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index 43faac5a..f6914199 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -65,6 +65,11 @@ type WebsocketConfig struct { // Read implements net.Conn.Read() // modify from gobwas/ws/wsutil.readData func (wsc *websocketConn) Read(b []byte) (n int, err error) { + defer func() { // avoid gobwas/ws pbytes.GetLen panic + if value := recover(); value != nil { + err = fmt.Errorf("websocket error: %s", value) + } + }() var header ws.Header for { n, err = wsc.reader.Read(b) @@ -333,6 +338,10 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, RawQuery: u.RawQuery, } + if !strings.HasPrefix(uri.Path, "/") { + uri.Path = "/" + uri.Path + } + if c.TLS { uri.Scheme = "wss" config := c.TLSConfig diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index c37b0c7d..391fe7c1 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -332,7 +332,6 @@ func handleUDPConn(packet C.PacketAdapter) { if !metadata.Resolved() { ip, err := resolver.ResolveIP(context.Background(), metadata.Host) if err != nil { - packet.Drop() return } metadata.DstIP = ip @@ -375,7 +374,6 @@ func handleUDPConn(packet C.PacketAdapter) { cond.Broadcast() }() - pCtx := icontext.NewPacketConnContext(metadata) proxy, rule, err := resolveMetadata(metadata) if err != nil { log.Warnln("[UDP] Parse metadata failed: %s", err.Error()) @@ -402,7 +400,6 @@ func handleUDPConn(packet C.PacketAdapter) { if err != nil { return } - pCtx.InjectPacketConn(rawPc) pc := statistic.NewUDPTracker(rawPc, statistic.DefaultManager, metadata, rule, 0, 0, true) @@ -412,6 +409,10 @@ func handleUDPConn(packet C.PacketAdapter) { case rule != nil: if rule.Payload() != "" { log.Infoln("[UDP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)", rule.RuleType().String(), rule.Payload()), rawPc.Chains().String()) + if rawPc.Chains().Last() == "REJECT-DROP" { + pc.Close() + return + } } else { log.Infoln("[UDP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), rule.Payload(), rawPc.Chains().String()) }