diff --git a/.github/release/.fpm_systemd b/.github/release/.fpm_systemd new file mode 100644 index 0000000000..85ec6c7e14 --- /dev/null +++ b/.github/release/.fpm_systemd @@ -0,0 +1,18 @@ +-s dir +--name mihomo +--category net +--license GPL-3.0-or-later +--description "The universal proxy platform." +--url "https://wiki.metacubex.one/" +--maintainer "MetaCubeX " +--deb-field "Bug: https://github.com/MetaCubeX/mihomo/issues" +--no-deb-generate-changes +--config-files /etc/mihomo/config.yaml + +.github/release/config.yaml=/etc/mihomo/config.yaml + +.github/release/mihomo.service=/usr/lib/systemd/system/mihomo.service +.github/release/mihomo@.service=/usr/lib/systemd/system/mihomo@.service + + +LICENSE=/usr/share/licenses/mihomo/LICENSE \ No newline at end of file diff --git a/.github/release/config.yaml b/.github/release/config.yaml new file mode 100644 index 0000000000..9864130fc3 --- /dev/null +++ b/.github/release/config.yaml @@ -0,0 +1,15 @@ +mixed-port: 7890 + +dns: + enable: true + ipv6: true + enhanced-mode: fake-ip + fake-ip-filter: + - "*" + - "+.lan" + - "+.local" + nameserver: + - system + +rules: + - MATCH,DIRECT \ No newline at end of file diff --git a/.github/mihomo.service b/.github/release/mihomo.service similarity index 100% rename from .github/mihomo.service rename to .github/release/mihomo.service diff --git a/.github/mihomo@.service b/.github/release/mihomo@.service similarity index 100% rename from .github/mihomo@.service rename to .github/release/mihomo@.service diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d2af487bf1..a1caf2cebc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,23 +33,25 @@ jobs: - { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible } - { goos: darwin, goarch: amd64, goamd64: v3, output: amd64 } - - { goos: linux, goarch: '386', output: '386' } + - { goos: linux, goarch: '386', go386: sse2, output: '386', debian: i386, rpm: i386} + - { goos: linux, goarch: '386', go386: softfloat, output: '386-softfloat' } - { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test } - - { goos: linux, goarch: amd64, goamd64: v3, output: amd64 } - - { goos: linux, goarch: arm64, output: arm64 } + - { goos: linux, goarch: amd64, goamd64: v3, output: amd64, debian: amd64, rpm: x86_64, pacman: x86_64} + - { goos: linux, goarch: arm64, output: arm64, debian: arm64, rpm: aarch64, pacman: aarch64} - { goos: linux, goarch: arm, goarm: '5', output: armv5 } - - { goos: linux, goarch: arm, goarm: '6', output: armv6 } - - { goos: linux, goarch: arm, goarm: '7', output: armv7 } + - { goos: linux, goarch: arm, goarm: '6', output: armv6, debian: armel, rpm: armv6hl} + - { goos: linux, goarch: arm, goarm: '7', output: armv7, debian: armhf, rpm: armv7hl, pacman: armv7hl} - { goos: linux, goarch: mips, gomips: hardfloat, output: mips-hardfloat } - { goos: linux, goarch: mips, gomips: softfloat, output: mips-softfloat } - { goos: linux, goarch: mipsle, gomips: hardfloat, output: mipsle-hardfloat } - { goos: linux, goarch: mipsle, gomips: softfloat, output: mipsle-softfloat } - { goos: linux, goarch: mips64, output: mips64 } - - { goos: linux, goarch: mips64le, output: mips64le } - - { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1' } - - { goos: linux, goarch: loong64, output: loong64-abi2, abi: '2' } - - { goos: linux, goarch: riscv64, output: riscv64 } - - { goos: linux, goarch: s390x, output: s390x } + - { goos: linux, goarch: mips64le, output: mips64le, debian: mips64el, rpm: mips64el } + - { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1', debian: loongarch64, rpm: loongarch64 } + - { goos: linux, goarch: loong64, output: loong64-abi2, abi: '2', debian: loong64, rpm: loong64 } + - { goos: linux, goarch: riscv64, output: riscv64, debian: riscv64, rpm: riscv64 } + - { goos: linux, goarch: s390x, output: s390x, debian: s390x, rpm: s390x } + - { goos: linux, goarch: ppc64le, output: ppc64le, debian: ppc64el, rpm: ppc64le } - { goos: windows, goarch: '386', output: '386' } - { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible } @@ -125,11 +127,11 @@ jobs: with: go-version: ${{ matrix.jobs.goversion }} - - name: Set up Go1.23 loongarch abi1 + - name: Set up Go1.24 loongarch abi1 if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }} run: | - wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.23.0/go1.23.0.linux-amd64-abi1.tar.gz - sudo tar zxf go1.23.0.linux-amd64-abi1.tar.gz -C /usr/local + wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.24.0/go1.24.0.linux-amd64-abi1.tar.gz + sudo tar zxf go1.24.0.linux-amd64-abi1.tar.gz -C /usr/local echo "/usr/local/go/bin" >> $GITHUB_PATH # modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557 @@ -194,17 +196,16 @@ jobs: curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1 - name: Set variables - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }} - run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV - shell: bash - - - name: Set variables - if: ${{ github.event_name != 'workflow_dispatch' && github.ref_name == 'Alpha' }} - run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV - shell: bash - - - name: Set Time Variable run: | + VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)" + PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}" + if [ -n "${{ github.event.inputs.version }}" ]; then + VERSION=${{ github.event.inputs.version }} + PackageVersion="${VERSION#v}" >> $GITHUB_ENV + fi + echo "VERSION=${VERSION}" >> $GITHUB_ENV + echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV + echo "BUILDTIME=$(date)" >> $GITHUB_ENV echo "CGO_ENABLED=0" >> $GITHUB_ENV echo "BUILDTAG=-extldflags --static" >> $GITHUB_ENV @@ -215,7 +216,7 @@ jobs: uses: nttld/setup-ndk@v1 id: setup-ndk with: - ndk-version: r28-beta1 + ndk-version: r29-beta1 - name: Set NDK path if: ${{ matrix.jobs.goos == 'android' }} @@ -233,7 +234,7 @@ jobs: - name: Update CA run: | - sudo apt-get install ca-certificates + sudo apt-get update && sudo apt-get install ca-certificates sudo update-ca-certificates cp -f /etc/ssl/certs/ca-certificates.crt component/ca/ca-certificates.crt @@ -242,6 +243,7 @@ jobs: GOOS: ${{matrix.jobs.goos}} GOARCH: ${{matrix.jobs.goarch}} GOAMD64: ${{matrix.jobs.goamd64}} + GO386: ${{matrix.jobs.go386}} GOARM: ${{matrix.jobs.goarm}} GOMIPS: ${{matrix.jobs.gomips}} run: | @@ -256,79 +258,51 @@ jobs: rm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}} fi - - name: Create DEB package - if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }} + - name: Package DEB + if: matrix.jobs.debian != '' run: | - sudo apt-get install dpkg - if [ "${{matrix.jobs.abi}}" = "1" ]; then - ARCH=loongarch64 - elif [ "${{matrix.jobs.goarm}}" = "7" ]; then - ARCH=armhf - elif [ "${{matrix.jobs.goarch}}" = "arm" ]; then - ARCH=armel - else - ARCH=${{matrix.jobs.goarch}} - fi - PackageVersion=$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | grep -o '"tag_name": "[^"]*' | grep -o '[^"]*$' | sed 's/v//g' ) - if [ $(git branch | awk -F ' ' '{print $2}') = "Alpha" ]; then - PackageVersion="$(echo "${PackageVersion}" | awk -F '.' '{$NF = $NF + 1; print}' OFS='.')-${VERSION}" - fi - - mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN - mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin - mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo - mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo - mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system - - cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/ - cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/ - cp .github/{mihomo.service,mihomo@.service} mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system/ - - cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml < mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/conffiles < mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control < - Homepage: https://wiki.metacubex.one/ - Description: The universal proxy platform. - EOF - - dpkg-deb -Z gzip --build mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION} - - - name: Convert DEB to RPM - if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') }} + set -xeuo pipefail + sudo gem install fpm + cp .github/release/.fpm_systemd .fpm + + fpm -t deb \ + -v "${PackageVersion}" \ + -p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb" \ + --architecture ${{ matrix.jobs.debian }} \ + mihomo=/usr/bin/mihomo + + - name: Package RPM + if: matrix.jobs.rpm != '' + run: | + set -xeuo pipefail + sudo gem install fpm + cp .github/release/.fpm_systemd .fpm + + fpm -t rpm \ + -v "${PackageVersion}" \ + -p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.rpm" \ + --architecture ${{ matrix.jobs.rpm }} \ + mihomo=/usr/bin/mihomo + + - name: Package Pacman + if: matrix.jobs.pacman != '' run: | - sudo apt-get install -y alien - alien --to-rpm --scripts mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb - mv mihomo*.rpm mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.rpm - - # - name: Convert DEB to PKG - # if: ${{ matrix.jobs.goos == 'linux' && !contains(matrix.jobs.goarch, 'mips') && !contains(matrix.jobs.goarch, 'loong64') }} - # run: | - # docker pull archlinux - # docker run --rm -v ./:/mnt archlinux bash -c " - # pacman -Syu pkgfile base-devel --noconfirm - # curl -L https://github.com/helixarch/debtap/raw/master/debtap > /usr/bin/debtap - # chmod 755 /usr/bin/debtap - # debtap -u - # debtap -Q /mnt/mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb - # " - # mv mihomo*.pkg.tar.zst mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.pkg.tar.zst + set -xeuo pipefail + sudo gem install fpm + sudo apt-get update && sudo apt-get install -y libarchive-tools + cp .github/release/.fpm_systemd .fpm + + fpm -t pacman \ + -v "${PackageVersion}" \ + -p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.pkg.tar.zst" \ + --architecture ${{ matrix.jobs.pacman }} \ + mihomo=/usr/bin/mihomo - name: Save version run: | echo ${VERSION} > version.txt shell: bash + - name: Archive production artifacts uses: actions/upload-artifact@v4 with: @@ -337,6 +311,7 @@ jobs: mihomo*.gz mihomo*.deb mihomo*.rpm + mihomo*.pkg.tar.zst mihomo*.zip version.txt checksums.txt diff --git a/adapter/adapter.go b/adapter/adapter.go index 4320702d9a..5e89e8bf5d 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -17,7 +17,6 @@ import ( "github.com/metacubex/mihomo/common/queue" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/ca" - "github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" "github.com/puzpuzpuz/xsync/v3" @@ -63,8 +62,8 @@ func (p *Proxy) Dial(metadata *C.Metadata) (C.Conn, error) { } // DialContext implements C.ProxyAdapter -func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - conn, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) +func (p *Proxy) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + conn, err := p.ProxyAdapter.DialContext(ctx, metadata) return conn, err } @@ -76,8 +75,8 @@ func (p *Proxy) DialUDP(metadata *C.Metadata) (C.PacketConn, error) { } // ListenPacketContext implements C.ProxyAdapter -func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) +func (p *Proxy) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata) return pc, err } diff --git a/adapter/outbound/anytls.go b/adapter/outbound/anytls.go index 7fb1d9c646..0e3b07de39 100644 --- a/adapter/outbound/anytls.go +++ b/adapter/outbound/anytls.go @@ -15,8 +15,8 @@ import ( "github.com/metacubex/mihomo/transport/anytls" "github.com/metacubex/mihomo/transport/vmess" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/uot" + M "github.com/metacubex/sing/common/metadata" + "github.com/metacubex/sing/common/uot" ) type AnyTLS struct { @@ -43,9 +43,7 @@ type AnyTLSOption struct { MinIdleSession int `proxy:"min-idle-session,omitempty"` } -func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - options := t.Base.DialOptions(opts...) - t.dialer.SetDialer(dialer.NewDialer(options...)) +func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { c, err := t.client.CreateProxy(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, err @@ -53,10 +51,8 @@ func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ... return NewConn(c, t), nil } -func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { +func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { // create tcp - options := t.Base.DialOptions(opts...) - t.dialer.SetDialer(dialer.NewDialer(options...)) c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2)) if err != nil { return nil, err @@ -93,8 +89,23 @@ func (t *AnyTLS) Close() error { func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + outbound := &AnyTLS{ + Base: &Base{ + name: option.Name, + addr: addr, + tp: C.AnyTLS, + udp: option.UDP, + tfo: option.TFO, + mpTcp: option.MPTCP, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), + }, + option: &option, + } - singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()) + singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...)) + outbound.dialer = singDialer tOption := anytls.ClientConfig{ Password: option.Password, @@ -116,22 +127,8 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) { } tOption.TLSConfig = tlsConfig - outbound := &AnyTLS{ - Base: &Base{ - name: option.Name, - addr: addr, - tp: C.AnyTLS, - udp: option.UDP, - tfo: option.TFO, - mpTcp: option.MPTCP, - iface: option.Interface, - rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), - }, - client: anytls.NewClient(context.TODO(), tOption), - option: &option, - dialer: singDialer, - } + client := anytls.NewClient(context.TODO(), tOption) + outbound.client = client return outbound, nil } diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index b2f1ac562c..7b4b93ab34 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -18,7 +18,7 @@ import ( type ProxyAdapter interface { C.ProxyAdapter - DialOptions(opts ...dialer.Option) []dialer.Option + DialOptions() []dialer.Option } type Base struct { @@ -59,7 +59,7 @@ func (b *Base) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me return c, C.ErrNotSupport } -func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { +func (b *Base) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { return nil, C.ErrNotSupport } @@ -69,7 +69,7 @@ func (b *Base) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad } // ListenPacketContext implements C.ProxyAdapter -func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { +func (b *Base) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { return nil, C.ErrNotSupport } @@ -128,7 +128,7 @@ func (b *Base) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { } // DialOptions return []dialer.Option from struct -func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option { +func (b *Base) DialOptions() (opts []dialer.Option) { if b.iface != "" { opts = append(opts, dialer.WithInterface(b.iface)) } @@ -167,8 +167,8 @@ func (b *Base) Close() error { type BasicOption struct { TFO bool `proxy:"tfo,omitempty"` MPTCP bool `proxy:"mptcp,omitempty"` - Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` - RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"` + Interface string `proxy:"interface-name,omitempty"` + RoutingMark int `proxy:"routing-mark,omitempty"` IPVersion string `proxy:"ip-version,omitempty"` DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy } @@ -317,8 +317,8 @@ type autoCloseProxyAdapter struct { closeErr error } -func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - c, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...) +func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + c, err := p.ProxyAdapter.DialContext(ctx, metadata) if err != nil { return nil, err } @@ -339,8 +339,8 @@ func (p *autoCloseProxyAdapter) DialContextWithDialer(ctx context.Context, diale return c, nil } -func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) +func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { + pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata) if err != nil { return nil, err } diff --git a/adapter/outbound/direct.go b/adapter/outbound/direct.go index dbde55933b..788a4b933e 100644 --- a/adapter/outbound/direct.go +++ b/adapter/outbound/direct.go @@ -20,12 +20,13 @@ type DirectOption struct { } // DialContext implements C.ProxyAdapter -func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { +func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { if err := d.loopBack.CheckConn(metadata); err != nil { return nil, err } + opts := d.DialOptions() opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver)) - c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...) + c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), opts...) if err != nil { return nil, err } @@ -33,7 +34,7 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ... } // ListenPacketContext implements C.ProxyAdapter -func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { +func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { if err := d.loopBack.CheckPacketConn(metadata); err != nil { return nil, err } @@ -45,7 +46,7 @@ func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, } metadata.DstIP = ip } - pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", metadata.AddrPort()) + pc, err := dialer.NewDialer(d.DialOptions()...).ListenPacket(ctx, "udp", "", metadata.AddrPort()) if err != nil { return nil, err } diff --git a/adapter/outbound/dns.go b/adapter/outbound/dns.go index 8686b288e2..40e70f2590 100644 --- a/adapter/outbound/dns.go +++ b/adapter/outbound/dns.go @@ -7,7 +7,6 @@ import ( N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/pool" - "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/resolver" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" @@ -23,14 +22,14 @@ type DnsOption struct { } // DialContext implements C.ProxyAdapter -func (d *Dns) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { +func (d *Dns) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { left, right := N.Pipe() go resolver.RelayDnsConn(context.Background(), right, 0) return NewConn(left, d), nil } // ListenPacketContext implements C.ProxyAdapter -func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { +func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { log.Debugln("[DNS] hijack udp:%s from %s", metadata.RemoteAddress(), metadata.SourceAddrPort()) ctx, cancel := context.WithCancel(context.Background()) diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index 543e48a1bd..f02308b920 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -58,8 +58,8 @@ func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me } // DialContext implements C.ProxyAdapter -func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - return h.DialContextWithDialer(ctx, dialer.NewDialer(h.Base.DialOptions(opts...)...), metadata) +func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + return h.DialContextWithDialer(ctx, dialer.NewDialer(h.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index f475680be6..55caf58b83 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -10,13 +10,10 @@ import ( "strconv" "time" - "github.com/metacubex/quic-go" - "github.com/metacubex/quic-go/congestion" - M "github.com/sagernet/sing/common/metadata" - "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" hyCongestion "github.com/metacubex/mihomo/transport/hysteria/congestion" @@ -25,6 +22,10 @@ import ( "github.com/metacubex/mihomo/transport/hysteria/pmtud_fix" "github.com/metacubex/mihomo/transport/hysteria/transport" "github.com/metacubex/mihomo/transport/hysteria/utils" + + "github.com/metacubex/quic-go" + "github.com/metacubex/quic-go/congestion" + M "github.com/metacubex/sing/common/metadata" ) const ( @@ -45,8 +46,8 @@ type Hysteria struct { client *core.Client } -func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx, opts...)) +func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + tcpConn, err := h.client.DialTCP(metadata.String(), metadata.DstPort, h.genHdc(ctx)) if err != nil { return nil, err } @@ -54,20 +55,20 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts . return NewConn(tcpConn, h), nil } -func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - udpConn, err := h.client.DialUDP(h.genHdc(ctx, opts...)) +func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + udpConn, err := h.client.DialUDP(h.genHdc(ctx)) if err != nil { return nil, err } return newPacketConn(&hyPacketConn{udpConn}, h), nil } -func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer { +func (h *Hysteria) genHdc(ctx context.Context) utils.PacketDialer { return &hyDialerWithContext{ ctx: context.Background(), hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) { var err error - var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...) + var cDialer C.Dialer = dialer.NewDialer(h.DialOptions()...) if len(h.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(h.option.DialerProxy, cDialer) if err != nil { @@ -214,7 +215,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { down = uint64(option.DownSpeed * mbpsToBps) } client, err := core.NewClient( - addr, ports, option.Protocol, auth, tlsConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl { + addr, ports, option.Protocol, auth, tlsC.UConfig(tlsConfig), quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl { return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS)) }, obfuscator, hopInterval, option.FastOpen, ) diff --git a/adapter/outbound/hysteria2.go b/adapter/outbound/hysteria2.go index 9f4f171882..131a8b3c03 100644 --- a/adapter/outbound/hysteria2.go +++ b/adapter/outbound/hysteria2.go @@ -14,15 +14,15 @@ import ( "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" tuicCommon "github.com/metacubex/mihomo/transport/tuic/common" - "github.com/metacubex/sing-quic/hysteria2" - "github.com/metacubex/quic-go" "github.com/metacubex/randv2" - M "github.com/sagernet/sing/common/metadata" + "github.com/metacubex/sing-quic/hysteria2" + M "github.com/metacubex/sing/common/metadata" ) func init() { @@ -68,9 +68,7 @@ type Hysteria2Option struct { MaxConnectionReceiveWindow uint64 `proxy:"max-connection-receive-window,omitempty"` } -func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - options := h.Base.DialOptions(opts...) - h.dialer.SetDialer(dialer.NewDialer(options...)) +func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { c, err := h.client.DialConn(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, err @@ -78,9 +76,7 @@ func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts return NewConn(c, h), nil } -func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - options := h.Base.DialOptions(opts...) - h.dialer.SetDialer(dialer.NewDialer(options...)) +func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { pc, err := h.client.ListenPacket(ctx) if err != nil { return nil, err @@ -108,6 +104,22 @@ func (h *Hysteria2) ProxyInfo() C.ProxyInfo { func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + outbound := &Hysteria2{ + Base: &Base{ + name: option.Name, + addr: addr, + tp: C.Hysteria2, + udp: true, + iface: option.Interface, + rmark: option.RoutingMark, + prefer: C.NewDNSPrefer(option.IPVersion), + }, + option: &option, + } + + singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...)) + outbound.dialer = singDialer + var salamanderPassword string if len(option.Obfs) > 0 { if option.ObfsPassword == "" { @@ -155,8 +167,6 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { MaxConnectionReceiveWindow: option.MaxConnectionReceiveWindow, } - singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()) - clientOptions := hysteria2.ClientOptions{ Context: context.TODO(), Dialer: singDialer, @@ -165,7 +175,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { ReceiveBPS: StringToBps(option.Down), SalamanderPassword: salamanderPassword, Password: option.Password, - TLSConfig: tlsConfig, + TLSConfig: tlsC.UConfig(tlsConfig), QUICConfig: quicConfig, UDPDisabled: false, CWND: option.CWND, @@ -207,21 +217,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) { if err != nil { return nil, err } - - outbound := &Hysteria2{ - Base: &Base{ - name: option.Name, - addr: addr, - tp: C.Hysteria2, - udp: true, - iface: option.Interface, - rmark: option.RoutingMark, - prefer: C.NewDNSPrefer(option.IPVersion), - }, - option: &option, - client: client, - dialer: singDialer, - } + outbound.client = client return outbound, nil } diff --git a/adapter/outbound/mieru.go b/adapter/outbound/mieru.go index b0f24323c6..1d8c78f751 100644 --- a/adapter/outbound/mieru.go +++ b/adapter/outbound/mieru.go @@ -40,8 +40,8 @@ type MieruOption struct { } // DialContext implements C.ProxyAdapter -func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - if err := m.ensureClientIsRunning(opts...); err != nil { +func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + if err := m.ensureClientIsRunning(); err != nil { return nil, err } addr := metadataToMieruNetAddrSpec(metadata) @@ -53,8 +53,8 @@ func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d } // ListenPacketContext implements C.ProxyAdapter -func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - if err := m.ensureClientIsRunning(opts...); err != nil { +func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { + if err := m.ensureClientIsRunning(); err != nil { return nil, err } c, err := m.client.DialContext(ctx, metadata.UDPAddr()) @@ -76,7 +76,7 @@ func (m *Mieru) ProxyInfo() C.ProxyInfo { return info } -func (m *Mieru) ensureClientIsRunning(opts ...dialer.Option) error { +func (m *Mieru) ensureClientIsRunning() error { m.mu.Lock() defer m.mu.Unlock() @@ -85,7 +85,7 @@ func (m *Mieru) ensureClientIsRunning(opts ...dialer.Option) error { } // Create a dialer and add it to the client config, before starting the client. - var dialer C.Dialer = dialer.NewDialer(m.Base.DialOptions(opts...)...) + var dialer C.Dialer = dialer.NewDialer(m.DialOptions()...) var err error if len(m.option.DialerProxy) > 0 { dialer, err = proxydialer.NewByName(m.option.DialerProxy, dialer) diff --git a/adapter/outbound/reject.go b/adapter/outbound/reject.go index a8e2100d1a..da3a4e3c60 100644 --- a/adapter/outbound/reject.go +++ b/adapter/outbound/reject.go @@ -7,7 +7,6 @@ import ( "time" "github.com/metacubex/mihomo/common/buf" - "github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant" ) @@ -21,7 +20,7 @@ type RejectOption struct { } // DialContext implements C.ProxyAdapter -func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { +func (r *Reject) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { if r.drop { return NewConn(dropConn{}, r), nil } @@ -29,7 +28,7 @@ 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) { +func (r *Reject) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { return newPacketConn(&nopPacketConn{}, r), nil } diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 80d113c719..0b215ca45f 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -20,9 +20,9 @@ import ( v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin" 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" + "github.com/metacubex/sing/common/bufio" + M "github.com/metacubex/sing/common/metadata" + "github.com/metacubex/sing/common/uot" ) type ShadowSocks struct { @@ -84,11 +84,12 @@ type gostObfsOption struct { } type shadowTLSOption struct { - Password string `obfs:"password"` - Host string `obfs:"host"` - Fingerprint string `obfs:"fingerprint,omitempty"` - SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` - Version int `obfs:"version,omitempty"` + Password string `obfs:"password,omitempty"` + Host string `obfs:"host"` + Fingerprint string `obfs:"fingerprint,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` + Version int `obfs:"version,omitempty"` + ALPN []string `obfs:"alpn,omitempty"` } type restlsOption struct { @@ -154,8 +155,8 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada } // DialContext implements C.ProxyAdapter -func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata) +func (ss *ShadowSocks) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -180,8 +181,8 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale } // ListenPacketContext implements C.ProxyAdapter -func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - return ss.ListenPacketWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata) +func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + return ss.ListenPacketWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata) } // ListenPacketWithDialer implements C.ProxyAdapter @@ -342,6 +343,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { SkipCertVerify: opt.SkipCertVerify, Version: opt.Version, } + + if opt.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array + shadowTLSOpt.ALPN = opt.ALPN + } else { + shadowTLSOpt.ALPN = shadowtls.DefaultALPN + } } else if option.Plugin == restls.Mode { obfsMode = restls.Mode restlsOpt := &restlsOption{} diff --git a/adapter/outbound/shadowsocksr.go b/adapter/outbound/shadowsocksr.go index 7957d6cd4e..d7b932e6a6 100644 --- a/adapter/outbound/shadowsocksr.go +++ b/adapter/outbound/shadowsocksr.go @@ -67,8 +67,8 @@ func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, meta } // DialContext implements C.ProxyAdapter -func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata) +func (ssr *ShadowSocksR) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + return ssr.DialContextWithDialer(ctx, dialer.NewDialer(ssr.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -93,8 +93,8 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia } // ListenPacketContext implements C.ProxyAdapter -func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.Base.DialOptions(opts...)...), metadata) +func (ssr *ShadowSocksR) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + return ssr.ListenPacketWithDialer(ctx, dialer.NewDialer(ssr.DialOptions()...), metadata) } // ListenPacketWithDialer implements C.ProxyAdapter diff --git a/adapter/outbound/singmux.go b/adapter/outbound/singmux.go index 13f9f66c19..819f23877c 100644 --- a/adapter/outbound/singmux.go +++ b/adapter/outbound/singmux.go @@ -11,9 +11,9 @@ import ( C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" - mux "github.com/sagernet/sing-mux" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" + mux "github.com/metacubex/sing-mux" + E "github.com/metacubex/sing/common/exceptions" + M "github.com/metacubex/sing/common/metadata" ) type SingMux struct { @@ -41,9 +41,7 @@ type BrutalOption struct { Down string `proxy:"down,omitempty"` } -func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - options := s.ProxyAdapter.DialOptions(opts...) - s.dialer.SetDialer(dialer.NewDialer(options...)) +func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) if err != nil { return nil, err @@ -51,12 +49,10 @@ func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts .. return NewConn(c, s), err } -func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { +func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { if s.onlyTcp { - return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) + return s.ProxyAdapter.ListenPacketContext(ctx, metadata) } - options := s.ProxyAdapter.DialOptions(opts...) - s.dialer.SetDialer(dialer.NewDialer(options...)) // sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr if !metadata.Resolved() { @@ -109,7 +105,7 @@ func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error) // TODO // "TCP Brutal is only supported on Linux-based systems" - singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(), option.Statistic) + singDialer := proxydialer.NewSingDialer(proxy, dialer.NewDialer(proxy.DialOptions()...), option.Statistic) client, err := mux.NewClient(mux.Options{ Dialer: singDialer, Logger: log.SingLogger, diff --git a/adapter/outbound/snell.go b/adapter/outbound/snell.go index cf3cdcb3c6..275f726369 100644 --- a/adapter/outbound/snell.go +++ b/adapter/outbound/snell.go @@ -75,8 +75,8 @@ func (s *Snell) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C. } // DialContext implements C.ProxyAdapter -func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - if s.version == snell.Version2 && dialer.IsZeroOptions(opts) { +func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + if s.version == snell.Version2 { c, err := s.pool.Get() if err != nil { return nil, err @@ -89,7 +89,7 @@ func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d return NewConn(c, s), err } - return s.DialContextWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata) + return s.DialContextWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -114,8 +114,8 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta } // ListenPacketContext implements C.ProxyAdapter -func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.Base.DialOptions(opts...)...), metadata) +func (s *Snell) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + return s.ListenPacketWithDialer(ctx, dialer.NewDialer(s.DialOptions()...), metadata) } // ListenPacketWithDialer implements C.ProxyAdapter @@ -207,7 +207,7 @@ func NewSnell(option SnellOption) (*Snell, error) { if option.Version == snell.Version2 { s.pool = snell.NewPool(func(ctx context.Context) (*snell.Snell, error) { var err error - var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions()...) + var cDialer C.Dialer = dialer.NewDialer(s.DialOptions()...) if len(s.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer) if err != nil { diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index f42011d769..4bb774b93d 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -66,8 +66,8 @@ func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C } // DialContext implements C.ProxyAdapter -func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.Base.DialOptions(opts...)...), metadata) +func (ss *Socks5) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + return ss.DialContextWithDialer(ctx, dialer.NewDialer(ss.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -101,8 +101,8 @@ func (ss *Socks5) SupportWithDialer() C.NetWork { } // ListenPacketContext implements C.ProxyAdapter -func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - var cDialer C.Dialer = dialer.NewDialer(ss.Base.DialOptions(opts...)...) +func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { + var cDialer C.Dialer = dialer.NewDialer(ss.DialOptions()...) if len(ss.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(ss.option.DialerProxy, cDialer) if err != nil { diff --git a/adapter/outbound/ssh.go b/adapter/outbound/ssh.go index 2f08bdbccf..861999ed69 100644 --- a/adapter/outbound/ssh.go +++ b/adapter/outbound/ssh.go @@ -43,8 +43,8 @@ type SshOption struct { HostKeyAlgorithms []string `proxy:"host-key-algorithms,omitempty"` } -func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - var cDialer C.Dialer = dialer.NewDialer(s.Base.DialOptions(opts...)...) +func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { + var cDialer C.Dialer = dialer.NewDialer(s.DialOptions()...) if len(s.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(s.option.DialerProxy, cDialer) if err != nil { @@ -136,7 +136,11 @@ func NewSsh(option SshOption) (*Ssh, error) { if strings.Contains(option.PrivateKey, "PRIVATE KEY") { b = []byte(option.PrivateKey) } else { - b, err = os.ReadFile(C.Path.Resolve(option.PrivateKey)) + path := C.Path.Resolve(option.PrivateKey) + if !C.Path.IsSafePath(path) { + return nil, fmt.Errorf("path is not subpath of home directory: %s", path) + } + b, err = os.ReadFile(path) if err != nil { return nil, err } diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index d6ca43794c..a321caf0e6 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -165,10 +165,10 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C } // DialContext implements C.ProxyAdapter -func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { +func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { var c net.Conn // gun transport - if t.transport != nil && dialer.IsZeroOptions(opts) { + if t.transport != nil { c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { return nil, err @@ -184,7 +184,7 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ... return NewConn(c, t), nil } - return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata) + return t.DialContextWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -213,11 +213,11 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met } // ListenPacketContext implements C.ProxyAdapter -func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { +func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { var c net.Conn // grpc transport - if t.transport != nil && dialer.IsZeroOptions(opts) { + if t.transport != nil { c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) if err != nil { return nil, fmt.Errorf("%s connect error: %w", t.addr, err) @@ -234,7 +234,7 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata, pc := trojan.NewPacketConn(c) return newPacketConn(pc, t), err } - return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata) + return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata) } // ListenPacketWithDialer implements C.ProxyAdapter @@ -295,6 +295,10 @@ func (t *Trojan) Close() error { func NewTrojan(option TrojanOption) (*Trojan, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) + if option.SNI == "" { + option.SNI = option.Server + } + t := &Trojan{ Base: &Base{ name: option.Name, @@ -334,7 +338,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { if option.Network == "grpc" { dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) { var err error - var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...) + var cDialer C.Dialer = dialer.NewDialer(t.DialOptions()...) if len(t.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(t.option.DialerProxy, cDialer) if err != nil { diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index 6616c652bd..f062f83027 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -14,13 +14,14 @@ import ( "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/resolver" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/transport/tuic" "github.com/gofrs/uuid/v5" "github.com/metacubex/quic-go" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/uot" + M "github.com/metacubex/sing/common/metadata" + "github.com/metacubex/sing/common/uot" ) type Tuic struct { @@ -65,8 +66,8 @@ type TuicOption struct { } // DialContext implements C.ProxyAdapter -func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - return t.DialContextWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata) +func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + return t.DialContextWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -79,8 +80,8 @@ func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad } // ListenPacketContext implements C.ProxyAdapter -func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata) +func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { + return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata) } // ListenPacketWithDialer implements C.ProxyAdapter @@ -284,7 +285,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { if len(option.Token) > 0 { tkn := tuic.GenTKN(option.Token) clientOption := &tuic.ClientOptionV4{ - TlsConfig: tlsConfig, + TlsConfig: tlsC.UConfig(tlsConfig), QuicConfig: quicConfig, Token: tkn, UdpRelayMode: udpRelayMode, @@ -304,7 +305,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { maxUdpRelayPacketSize = tuic.MaxFragSizeV5 } clientOption := &tuic.ClientOptionV5{ - TlsConfig: tlsConfig, + TlsConfig: tlsC.UConfig(tlsConfig), QuicConfig: quicConfig, Uuid: uuid.FromStringOrNil(option.UUID), Password: option.Password, diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 4d1a23b8e1..b075e7199b 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -27,7 +27,7 @@ import ( vmessSing "github.com/metacubex/sing-vmess" "github.com/metacubex/sing-vmess/packetaddr" - M "github.com/sagernet/sing/common/metadata" + M "github.com/metacubex/sing/common/metadata" ) const ( @@ -225,10 +225,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne } // DialContext implements C.ProxyAdapter -func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { +func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { var c net.Conn // gun transport - if v.transport != nil && dialer.IsZeroOptions(opts) { + if v.transport != nil { c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -244,7 +244,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d return NewConn(c, v), nil } - return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) + return v.DialContextWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -271,7 +271,7 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta } // ListenPacketContext implements C.ProxyAdapter -func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { +func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { // vless use stream-oriented udp with a special address, so we need a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIP(ctx, metadata.Host) @@ -282,7 +282,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } var c net.Conn // gun transport - if v.transport != nil && dialer.IsZeroOptions(opts) { + if v.transport != nil { c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -298,7 +298,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o return v.ListenPacketOnStreamConn(ctx, c, metadata) } - return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) + return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata) } // ListenPacketWithDialer implements C.ProxyAdapter @@ -571,7 +571,7 @@ func NewVless(option VlessOption) (*Vless, error) { case "grpc": dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) { var err error - var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...) + var cDialer C.Dialer = dialer.NewDialer(v.DialOptions()...) if len(v.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer) if err != nil { diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index fddef0e1b1..23da3bebf7 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -25,7 +25,7 @@ import ( vmess "github.com/metacubex/sing-vmess" "github.com/metacubex/sing-vmess/packetaddr" - M "github.com/sagernet/sing/common/metadata" + M "github.com/metacubex/sing/common/metadata" ) var ErrUDPRemoteAddrMismatch = errors.New("udp packet dropped due to mismatched remote address") @@ -280,10 +280,10 @@ func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M } // DialContext implements C.ProxyAdapter -func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { +func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { var c net.Conn // gun transport - if v.transport != nil && dialer.IsZeroOptions(opts) { + if v.transport != nil { c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -299,7 +299,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d return NewConn(c, v), nil } - return v.DialContextWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) + return v.DialContextWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata) } // DialContextWithDialer implements C.ProxyAdapter @@ -323,7 +323,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta } // ListenPacketContext implements C.ProxyAdapter -func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { +func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { // vmess use stream-oriented udp with a special address, so we need a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIP(ctx, metadata.Host) @@ -334,7 +334,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } var c net.Conn // gun transport - if v.transport != nil && dialer.IsZeroOptions(opts) { + if v.transport != nil { c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) if err != nil { return nil, err @@ -349,7 +349,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } return v.ListenPacketOnStreamConn(ctx, c, metadata) } - return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) + return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.DialOptions()...), metadata) } // ListenPacketWithDialer implements C.ProxyAdapter @@ -482,7 +482,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { case "grpc": dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) { var err error - var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...) + var cDialer C.Dialer = dialer.NewDialer(v.DialOptions()...) if len(v.option.DialerProxy) > 0 { cDialer, err = proxydialer.NewByName(v.option.DialerProxy, cDialer) if err != nil { diff --git a/adapter/outbound/wireguard.go b/adapter/outbound/wireguard.go index 06d67f8738..db0ef95af4 100644 --- a/adapter/outbound/wireguard.go +++ b/adapter/outbound/wireguard.go @@ -26,9 +26,9 @@ import ( wireguard "github.com/metacubex/sing-wireguard" "github.com/metacubex/wireguard-go/device" - "github.com/sagernet/sing/common/debug" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" + "github.com/metacubex/sing/common/debug" + E "github.com/metacubex/sing/common/exceptions" + M "github.com/metacubex/sing/common/metadata" ) type wireguardGoDevice interface { @@ -166,8 +166,9 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) { rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, - dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()), } + singDialer := proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...)), slowdown.New()) + outbound.dialer = singDialer var reserved [3]uint8 if len(option.Reserved) > 0 { @@ -488,9 +489,7 @@ func (w *WireGuard) Close() error { return nil } -func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { - options := w.Base.DialOptions(opts...) - w.dialer.SetDialer(dialer.NewDialer(options...)) +func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Conn, err error) { var conn net.Conn if err = w.init(ctx); err != nil { return nil, err @@ -500,6 +499,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts if w.resolver != nil { r = w.resolver } + options := w.DialOptions() options = append(options, dialer.WithResolver(r)) options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice})) conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress()) @@ -515,9 +515,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts return NewConn(conn, w), nil } -func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - options := w.Base.DialOptions(opts...) - w.dialer.SetDialer(dialer.NewDialer(options...)) +func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { var pc net.PacketConn if err = w.init(ctx); err != nil { return nil, err diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index b8bb458f05..8f8842a1c4 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -6,11 +6,9 @@ import ( "errors" "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/utils" - "github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/provider" ) @@ -31,9 +29,9 @@ func (f *Fallback) Now() string { } // DialContext implements C.ProxyAdapter -func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { +func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { proxy := f.findAliveProxy(true) - c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...) + c, err := proxy.DialContext(ctx, metadata) if err == nil { c.AppendToChains(f) } else { @@ -54,9 +52,9 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts . } // ListenPacketContext implements C.ProxyAdapter -func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { +func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { proxy := f.findAliveProxy(true) - pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...) + pc, err := proxy.ListenPacketContext(ctx, metadata) if err == nil { pc.AppendToChains(f) } @@ -155,18 +153,14 @@ func (f *Fallback) ForceSet(name string) { func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback { return &Fallback{ GroupBase: NewGroupBase(GroupBaseOption{ - outbound.BaseOption{ - Name: option.Name, - Type: C.Fallback, - Interface: option.Interface, - RoutingMark: option.RoutingMark, - }, - option.Filter, - option.ExcludeFilter, - option.ExcludeType, - option.TestTimeout, - option.MaxFailedTimes, - providers, + Name: option.Name, + Type: C.Fallback, + Filter: option.Filter, + ExcludeFilter: option.ExcludeFilter, + ExcludeType: option.ExcludeType, + TestTimeout: option.TestTimeout, + MaxFailedTimes: option.MaxFailedTimes, + Providers: providers, }), disableUDP: option.DisableUDP, testUrl: option.URL, diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index f891016876..dc66976bba 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -41,53 +41,47 @@ type GroupBase struct { } type GroupBaseOption struct { - outbound.BaseOption - filter string - excludeFilter string - excludeType string + Name string + Type C.AdapterType + Filter string + ExcludeFilter string + ExcludeType string TestTimeout int - maxFailedTimes int - providers []provider.ProxyProvider + MaxFailedTimes int + Providers []provider.ProxyProvider } func NewGroupBase(opt GroupBaseOption) *GroupBase { - if opt.RoutingMark != 0 { - log.Warnln("The group [%s] with routing-mark configuration is deprecated, please set it directly on the proxy instead", opt.Name) - } - if opt.Interface != "" { - log.Warnln("The group [%s] with interface-name configuration is deprecated, please set it directly on the proxy instead", opt.Name) - } - var excludeTypeArray []string - if opt.excludeType != "" { - excludeTypeArray = strings.Split(opt.excludeType, "|") + if opt.ExcludeType != "" { + excludeTypeArray = strings.Split(opt.ExcludeType, "|") } var excludeFilterRegs []*regexp2.Regexp - if opt.excludeFilter != "" { - for _, excludeFilter := range strings.Split(opt.excludeFilter, "`") { + if opt.ExcludeFilter != "" { + for _, excludeFilter := range strings.Split(opt.ExcludeFilter, "`") { excludeFilterReg := regexp2.MustCompile(excludeFilter, regexp2.None) excludeFilterRegs = append(excludeFilterRegs, excludeFilterReg) } } var filterRegs []*regexp2.Regexp - if opt.filter != "" { - for _, filter := range strings.Split(opt.filter, "`") { + if opt.Filter != "" { + for _, filter := range strings.Split(opt.Filter, "`") { filterReg := regexp2.MustCompile(filter, regexp2.None) filterRegs = append(filterRegs, filterReg) } } gb := &GroupBase{ - Base: outbound.NewBase(opt.BaseOption), + Base: outbound.NewBase(outbound.BaseOption{Name: opt.Name, Type: opt.Type}), filterRegs: filterRegs, excludeFilterRegs: excludeFilterRegs, excludeTypeArray: excludeTypeArray, - providers: opt.providers, + providers: opt.Providers, failedTesting: atomic.NewBool(false), TestTimeout: opt.TestTimeout, - maxFailedTimes: opt.maxFailedTimes, + maxFailedTimes: opt.MaxFailedTimes, } if gb.TestTimeout == 0 { diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index c3222b3a49..99e11c46fc 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -9,12 +9,10 @@ import ( "sync" "time" - "github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/common/callback" "github.com/metacubex/mihomo/common/lru" N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/utils" - "github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/provider" @@ -88,9 +86,9 @@ func jumpHash(key uint64, buckets int32) int32 { } // DialContext implements C.ProxyAdapter -func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { +func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { proxy := lb.Unwrap(metadata, true) - c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...) + c, err = proxy.DialContext(ctx, metadata) if err == nil { c.AppendToChains(lb) @@ -112,7 +110,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op } // ListenPacketContext implements C.ProxyAdapter -func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (pc C.PacketConn, err error) { +func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (pc C.PacketConn, err error) { defer func() { if err == nil { pc.AppendToChains(lb) @@ -120,7 +118,7 @@ func (lb *LoadBalance) ListenPacketContext(ctx context.Context, metadata *C.Meta }() proxy := lb.Unwrap(metadata, true) - return proxy.ListenPacketContext(ctx, metadata, lb.Base.DialOptions(opts...)...) + return proxy.ListenPacketContext(ctx, metadata) } // SupportUDP implements C.ProxyAdapter @@ -255,18 +253,14 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide } return &LoadBalance{ GroupBase: NewGroupBase(GroupBaseOption{ - outbound.BaseOption{ - Name: option.Name, - Type: C.LoadBalance, - Interface: option.Interface, - RoutingMark: option.RoutingMark, - }, - option.Filter, - option.ExcludeFilter, - option.ExcludeType, - option.TestTimeout, - option.MaxFailedTimes, - providers, + Name: option.Name, + Type: C.LoadBalance, + Filter: option.Filter, + ExcludeFilter: option.ExcludeFilter, + ExcludeType: option.ExcludeType, + TestTimeout: option.TestTimeout, + MaxFailedTimes: option.MaxFailedTimes, + Providers: providers, }), strategyFn: strategyFn, disableUDP: option.DisableUDP, diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index b073c4bba7..9c09a0f4a2 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -7,12 +7,12 @@ import ( "github.com/dlclark/regexp2" - "github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/utils" C "github.com/metacubex/mihomo/constant" types "github.com/metacubex/mihomo/constant/provider" + "github.com/metacubex/mihomo/log" ) var ( @@ -23,7 +23,6 @@ var ( ) type GroupCommonOption struct { - outbound.BasicOption Name string `group:"name"` Type string `group:"type"` Proxies []string `group:"proxies,omitempty"` @@ -43,6 +42,10 @@ type GroupCommonOption struct { IncludeAllProviders bool `group:"include-all-providers,omitempty"` Hidden bool `group:"hidden,omitempty"` Icon string `group:"icon,omitempty"` + + // removed configs, only for error logging + Interface string `group:"interface-name,omitempty"` + RoutingMark int `group:"routing-mark,omitempty"` } func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) { @@ -59,6 +62,13 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide return nil, errFormat } + if groupOption.RoutingMark != 0 { + log.Errorln("The group [%s] with routing-mark configuration was removed, please set it directly on the proxy instead", groupOption.Name) + } + if groupOption.Interface != "" { + log.Errorln("The group [%s] with interface-name configuration was removed, please set it directly on the proxy instead", groupOption.Name) + } + groupName := groupOption.Name providers := []types.ProxyProvider{} diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 29aa9c6a74..77a2f03b8e 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -19,17 +19,17 @@ type Relay struct { } // DialContext implements C.ProxyAdapter -func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { +func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { proxies, chainProxies := r.proxies(metadata, true) switch len(proxies) { case 0: - return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) + return outbound.NewDirect().DialContext(ctx, metadata) case 1: - return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...) + return proxies[0].DialContext(ctx, metadata) } var d C.Dialer - d = dialer.NewDialer(r.Base.DialOptions(opts...)...) + d = dialer.NewDialer() for _, proxy := range proxies[:len(proxies)-1] { d = proxydialer.New(proxy, d, false) } @@ -49,18 +49,18 @@ func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d } // ListenPacketContext implements C.ProxyAdapter -func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { +func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) { proxies, chainProxies := r.proxies(metadata, true) switch len(proxies) { case 0: - return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...) + return outbound.NewDirect().ListenPacketContext(ctx, metadata) case 1: - return proxies[0].ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...) + return proxies[0].ListenPacketContext(ctx, metadata) } var d C.Dialer - d = dialer.NewDialer(r.Base.DialOptions(opts...)...) + d = dialer.NewDialer() for _, proxy := range proxies[:len(proxies)-1] { d = proxydialer.New(proxy, d, false) } @@ -153,18 +153,9 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re log.Warnln("The group [%s] with relay type is deprecated, please using dialer-proxy instead", option.Name) return &Relay{ GroupBase: NewGroupBase(GroupBaseOption{ - outbound.BaseOption{ - Name: option.Name, - Type: C.Relay, - Interface: option.Interface, - RoutingMark: option.RoutingMark, - }, - "", - "", - "", - 5000, - 5, - providers, + Name: option.Name, + Type: C.Relay, + Providers: providers, }), Hidden: option.Hidden, Icon: option.Icon, diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 20eca70ffd..03ee192c9e 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -5,8 +5,6 @@ import ( "encoding/json" "errors" - "github.com/metacubex/mihomo/adapter/outbound" - "github.com/metacubex/mihomo/component/dialer" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/constant/provider" ) @@ -15,13 +13,14 @@ type Selector struct { *GroupBase disableUDP bool selected string + testUrl string Hidden bool Icon string } // DialContext implements C.ProxyAdapter -func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { - c, err := s.selectedProxy(true).DialContext(ctx, metadata, s.Base.DialOptions(opts...)...) +func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) { + c, err := s.selectedProxy(true).DialContext(ctx, metadata) if err == nil { c.AppendToChains(s) } @@ -29,8 +28,8 @@ func (s *Selector) DialContext(ctx context.Context, metadata *C.Metadata, opts . } // ListenPacketContext implements C.ProxyAdapter -func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { - pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata, s.Base.DialOptions(opts...)...) +func (s *Selector) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { + pc, err := s.selectedProxy(true).ListenPacketContext(ctx, metadata) if err == nil { pc.AppendToChains(s) } @@ -57,13 +56,20 @@ func (s *Selector) MarshalJSON() ([]byte, error) { for _, proxy := range s.GetProxies(false) { all = append(all, proxy.Name()) } + // When testurl is the default value + // do not append a value to ensure that the web dashboard follows the settings of the dashboard + var url string + if s.testUrl != C.DefaultTestURL { + url = s.testUrl + } return json.Marshal(map[string]any{ - "type": s.Type().String(), - "now": s.Now(), - "all": all, - "hidden": s.Hidden, - "icon": s.Icon, + "type": s.Type().String(), + "now": s.Now(), + "all": all, + "testUrl": url, + "hidden": s.Hidden, + "icon": s.Icon, }) } @@ -105,21 +111,18 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy { func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { return &Selector{ GroupBase: NewGroupBase(GroupBaseOption{ - outbound.BaseOption{ - Name: option.Name, - Type: C.Selector, - Interface: option.Interface, - RoutingMark: option.RoutingMark, - }, - option.Filter, - option.ExcludeFilter, - option.ExcludeType, - option.TestTimeout, - option.MaxFailedTimes, - providers, + Name: option.Name, + Type: C.Selector, + Filter: option.Filter, + ExcludeFilter: option.ExcludeFilter, + ExcludeType: option.ExcludeType, + TestTimeout: option.TestTimeout, + MaxFailedTimes: option.MaxFailedTimes, + Providers: providers, }), selected: "COMPATIBLE", disableUDP: option.DisableUDP, + testUrl: option.URL, Hidden: option.Hidden, Icon: option.Icon, } diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index bdb15734cc..5dc620547e 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -6,12 +6,10 @@ import ( "errors" "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" ) @@ -62,9 +60,9 @@ func (u *URLTest) ForceSet(name string) { } // DialContext implements C.ProxyAdapter -func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { +func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata) (c C.Conn, err error) { proxy := u.fast(true) - c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) + c, err = proxy.DialContext(ctx, metadata) if err == nil { c.AppendToChains(u) } else { @@ -85,9 +83,9 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts .. } // ListenPacketContext implements C.ProxyAdapter -func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { +func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (C.PacketConn, error) { proxy := u.fast(true) - pc, err := proxy.ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...) + pc, err := proxy.ListenPacketContext(ctx, metadata) if err == nil { pc.AppendToChains(u) } else { @@ -207,19 +205,14 @@ func parseURLTestOption(config map[string]any) []urlTestOption { func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { urlTest := &URLTest{ GroupBase: NewGroupBase(GroupBaseOption{ - outbound.BaseOption{ - Name: option.Name, - Type: C.URLTest, - Interface: option.Interface, - RoutingMark: option.RoutingMark, - }, - - option.Filter, - option.ExcludeFilter, - option.ExcludeType, - option.TestTimeout, - option.MaxFailedTimes, - providers, + Name: option.Name, + Type: C.URLTest, + Filter: option.Filter, + ExcludeFilter: option.ExcludeFilter, + ExcludeType: option.ExcludeType, + TestTimeout: option.TestTimeout, + MaxFailedTimes: option.MaxFailedTimes, + Providers: providers, }), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), disableUDP: option.DisableUDP, diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index 8737ff96ae..2bddd8e79e 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -7,13 +7,13 @@ import ( "time" "github.com/metacubex/mihomo/common/atomic" - "github.com/metacubex/mihomo/common/batch" "github.com/metacubex/mihomo/common/singledo" "github.com/metacubex/mihomo/common/utils" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" "github.com/dlclark/regexp2" + "golang.org/x/sync/errgroup" ) type HealthCheckOption struct { @@ -147,7 +147,8 @@ func (hc *HealthCheck) check() { _, _, _ = hc.singleDo.Do(func() (struct{}, error) { id := utils.NewUUIDV4().String() log.Debugln("Start New Health Checking {%s}", id) - b, _ := batch.New[bool](hc.ctx, batch.WithConcurrencyNum[bool](10)) + b := new(errgroup.Group) + b.SetLimit(10) // execute default health check option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus} @@ -159,13 +160,13 @@ func (hc *HealthCheck) check() { hc.execute(b, url, id, option) } } - b.Wait() + _ = b.Wait() log.Debugln("Finish A Health Checking {%s}", id) return struct{}{}, nil }) } -func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *extraOption) { +func (hc *HealthCheck) execute(b *errgroup.Group, url, uid string, option *extraOption) { url = strings.TrimSpace(url) if len(url) == 0 { log.Debugln("Health Check has been skipped due to testUrl is empty, {%s}", uid) @@ -195,13 +196,13 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex } p := proxy - b.Go(p.Name(), func() (bool, error) { + b.Go(func() error { ctx, cancel := context.WithTimeout(hc.ctx, hc.timeout) defer cancel() log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid) _, _ = p.URLTest(ctx, url, expectedStatus) log.Debugln("Health Checked, proxy: %s, url: %s, alive: %t, delay: %d ms uid: {%s}", p.Name(), url, p.AliveForTestUrl(url), p.LastDelayForTestUrl(url), uid) - return false, nil + return nil }) } } diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index cd5c3ff7b6..2eaa2a3e94 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -127,5 +127,5 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide interval := time.Duration(uint(schema.Interval)) * time.Second - return NewProxySetProvider(name, interval, parser, vehicle, hc) + return NewProxySetProvider(name, interval, schema.Payload, parser, vehicle, hc) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 3dccefea5f..b1934bc7e6 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -161,7 +161,7 @@ func (pp *proxySetProvider) Close() error { return pp.Fetcher.Close() } -func NewProxySetProvider(name string, interval time.Duration, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { +func NewProxySetProvider(name string, interval time.Duration, payload []map[string]any, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { if hc.auto() { go hc.process() } @@ -174,6 +174,19 @@ func NewProxySetProvider(name string, interval time.Duration, parser resource.Pa }, } + if len(payload) > 0 { // using as fallback proxies + ps := ProxySchema{Proxies: payload} + buf, err := yaml.Marshal(ps) + if err != nil { + return nil, err + } + proxies, err := parser(buf) + if err != nil { + return nil, err + } + pd.proxies = proxies + } + fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, pd.setProxies) pd.Fetcher = fetcher if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok { diff --git a/common/buf/sing.go b/common/buf/sing.go index 0907a95cee..59c650adeb 100644 --- a/common/buf/sing.go +++ b/common/buf/sing.go @@ -1,8 +1,8 @@ package buf import ( - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" + "github.com/metacubex/sing/common" + "github.com/metacubex/sing/common/buf" ) const BufferSize = buf.BufferSize diff --git a/common/net/deadline/conn.go b/common/net/deadline/conn.go index fdf9334fd6..03ee7f6a6a 100644 --- a/common/net/deadline/conn.go +++ b/common/net/deadline/conn.go @@ -7,9 +7,9 @@ import ( "github.com/metacubex/mihomo/common/atomic" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/network" + "github.com/metacubex/sing/common/buf" + "github.com/metacubex/sing/common/bufio" + "github.com/metacubex/sing/common/network" ) type connReadResult struct { diff --git a/common/net/deadline/packet_sing.go b/common/net/deadline/packet_sing.go index d54748b022..71a1c51515 100644 --- a/common/net/deadline/packet_sing.go +++ b/common/net/deadline/packet_sing.go @@ -6,10 +6,10 @@ import ( "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" - N "github.com/sagernet/sing/common/network" + "github.com/metacubex/sing/common/buf" + "github.com/metacubex/sing/common/bufio" + M "github.com/metacubex/sing/common/metadata" + N "github.com/metacubex/sing/common/network" ) type SingPacketConn struct { diff --git a/common/net/deadline/pipe_sing.go b/common/net/deadline/pipe_sing.go index 0f6d378da7..e39bde7519 100644 --- a/common/net/deadline/pipe_sing.go +++ b/common/net/deadline/pipe_sing.go @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/sagernet/sing/common/buf" - N "github.com/sagernet/sing/common/network" + "github.com/metacubex/sing/common/buf" + N "github.com/metacubex/sing/common/network" ) type pipeAddr struct{} diff --git a/common/net/listener.go b/common/net/listener.go new file mode 100644 index 0000000000..0621b6c221 --- /dev/null +++ b/common/net/listener.go @@ -0,0 +1,90 @@ +package net + +import ( + "context" + "net" + "sync" +) + +type handleContextListener struct { + net.Listener + ctx context.Context + cancel context.CancelFunc + conns chan net.Conn + err error + once sync.Once + handle func(context.Context, net.Conn) (net.Conn, error) + panicLog func(any) +} + +func (l *handleContextListener) init() { + go func() { + for { + c, err := l.Listener.Accept() + if err != nil { + l.err = err + close(l.conns) + return + } + go func() { + defer func() { + if r := recover(); r != nil { + if l.panicLog != nil { + l.panicLog(r) + } + } + }() + if c, err := l.handle(l.ctx, c); err == nil { + l.conns <- c + } else { + // handle failed, close the underlying connection. + _ = c.Close() + } + }() + } + }() +} + +func (l *handleContextListener) Accept() (net.Conn, error) { + l.once.Do(l.init) + if c, ok := <-l.conns; ok { + return c, nil + } + return nil, l.err +} + +func (l *handleContextListener) Close() error { + l.cancel() + l.once.Do(func() { // l.init has not been called yet, so close related resources directly. + l.err = net.ErrClosed + close(l.conns) + }) + defer func() { + // at here, listener has been closed, so we should close all connections in the channel + for c := range l.conns { + go func(c net.Conn) { + defer func() { + if r := recover(); r != nil { + if l.panicLog != nil { + l.panicLog(r) + } + } + }() + _ = c.Close() + }(c) + } + }() + return l.Listener.Close() +} + +func NewHandleContextListener(ctx context.Context, l net.Listener, handle func(context.Context, net.Conn) (net.Conn, error), panicLog func(any)) net.Listener { + ctx, cancel := context.WithCancel(ctx) + return &handleContextListener{ + Listener: l, + ctx: ctx, + cancel: cancel, + conns: make(chan net.Conn), + handle: handle, + panicLog: panicLog, + } +} diff --git a/common/net/packet/packet_sing.go b/common/net/packet/packet_sing.go index 6e25eb4d86..fac7c0d9a2 100644 --- a/common/net/packet/packet_sing.go +++ b/common/net/packet/packet_sing.go @@ -3,10 +3,10 @@ package packet import ( "net" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" + "github.com/metacubex/sing/common/buf" + "github.com/metacubex/sing/common/bufio" + M "github.com/metacubex/sing/common/metadata" + N "github.com/metacubex/sing/common/network" ) type SingPacketConn = N.NetPacketConn diff --git a/common/net/packet/ref_sing.go b/common/net/packet/ref_sing.go index 2ca955fa89..851694b289 100644 --- a/common/net/packet/ref_sing.go +++ b/common/net/packet/ref_sing.go @@ -3,9 +3,9 @@ package packet import ( "runtime" - "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" + "github.com/metacubex/sing/common/buf" + M "github.com/metacubex/sing/common/metadata" + N "github.com/metacubex/sing/common/network" ) type refSingPacketConn struct { diff --git a/common/net/packet/thread_sing.go b/common/net/packet/thread_sing.go index 0869a5124c..53e7a48de2 100644 --- a/common/net/packet/thread_sing.go +++ b/common/net/packet/thread_sing.go @@ -1,9 +1,9 @@ package packet import ( - "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" + "github.com/metacubex/sing/common/buf" + M "github.com/metacubex/sing/common/metadata" + N "github.com/metacubex/sing/common/network" ) type threadSafeSingPacketConn struct { diff --git a/common/net/sing.go b/common/net/sing.go index d726f4409d..2cd1d726af 100644 --- a/common/net/sing.go +++ b/common/net/sing.go @@ -7,9 +7,9 @@ import ( "github.com/metacubex/mihomo/common/net/deadline" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/network" + "github.com/metacubex/sing/common" + "github.com/metacubex/sing/common/bufio" + "github.com/metacubex/sing/common/network" ) var NewExtendedConn = bufio.NewExtendedConn diff --git a/common/net/tls.go b/common/net/tls.go deleted file mode 100644 index 77c0d7ca39..0000000000 --- a/common/net/tls.go +++ /dev/null @@ -1,65 +0,0 @@ -package net - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "encoding/hex" - "encoding/pem" - "fmt" - "math/big" -) - -type Path interface { - Resolve(path string) string -} - -func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) { - if certificate == "" && privateKey == "" { - var err error - certificate, privateKey, _, err = NewRandomTLSKeyPair() - if err != nil { - return tls.Certificate{}, err - } - } - cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) - if painTextErr == nil { - return cert, nil - } - - certificate = path.Resolve(certificate) - privateKey = path.Resolve(privateKey) - cert, loadErr := tls.LoadX509KeyPair(certificate, privateKey) - if loadErr != nil { - return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) - } - return cert, nil -} - -func NewRandomTLSKeyPair() (certificate string, privateKey string, fingerprint string, err error) { - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return - } - template := x509.Certificate{SerialNumber: big.NewInt(1)} - certDER, err := x509.CreateCertificate( - rand.Reader, - &template, - &template, - &key.PublicKey, - key) - if err != nil { - return - } - cert, err := x509.ParseCertificate(certDER) - if err != nil { - return - } - hash := sha256.Sum256(cert.Raw) - fingerprint = hex.EncodeToString(hash[:]) - privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})) - certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})) - return -} diff --git a/common/once/oncefunc.go b/common/once/oncefunc.go new file mode 100644 index 0000000000..80c00f8802 --- /dev/null +++ b/common/once/oncefunc.go @@ -0,0 +1,102 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package once + +import "sync" + +// OnceFunc returns a function that invokes f only once. The returned function +// may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceFunc(f func()) func() { + var ( + once sync.Once + valid bool + p any + ) + // Construct the inner closure just once to reduce costs on the fast path. + g := func() { + defer func() { + p = recover() + if !valid { + // Re-panic immediately so on the first call the user gets a + // complete stack trace into f. + panic(p) + } + }() + f() + f = nil // Do not keep f alive after invoking it. + valid = true // Set only if f does not panic. + } + return func() { + once.Do(g) + if !valid { + panic(p) + } + } +} + +// OnceValue returns a function that invokes f only once and returns the value +// returned by f. The returned function may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceValue[T any](f func() T) func() T { + var ( + once sync.Once + valid bool + p any + result T + ) + g := func() { + defer func() { + p = recover() + if !valid { + panic(p) + } + }() + result = f() + f = nil + valid = true + } + return func() T { + once.Do(g) + if !valid { + panic(p) + } + return result + } +} + +// OnceValues returns a function that invokes f only once and returns the values +// returned by f. The returned function may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { + var ( + once sync.Once + valid bool + p any + r1 T1 + r2 T2 + ) + g := func() { + defer func() { + p = recover() + if !valid { + panic(p) + } + }() + r1, r2 = f() + f = nil + valid = true + } + return func() (T1, T2) { + once.Do(g) + if !valid { + panic(p) + } + return r1, r2 + } +} diff --git a/common/pool/sing.go b/common/pool/sing.go index c246ae9f94..a2c2b060b3 100644 --- a/common/pool/sing.go +++ b/common/pool/sing.go @@ -1,6 +1,6 @@ package pool -import "github.com/sagernet/sing/common/buf" +import "github.com/metacubex/sing/common/buf" func init() { buf.DefaultAllocator = defaultAllocator diff --git a/component/ca/config.go b/component/ca/config.go index 7ff3533434..4b37f7624d 100644 --- a/component/ca/config.go +++ b/component/ca/config.go @@ -1,17 +1,13 @@ package ca import ( - "bytes" - "crypto/sha256" "crypto/tls" "crypto/x509" _ "embed" - "encoding/hex" "errors" "fmt" "os" "strconv" - "strings" "sync" C "github.com/metacubex/mihomo/constant" @@ -81,41 +77,15 @@ func getCertPool() *x509.CertPool { return globalCertPool } -func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - // ssl pining - for i := range rawCerts { - rawCert := rawCerts[i] - cert, err := x509.ParseCertificate(rawCert) - if err == nil { - hash := sha256.Sum256(cert.Raw) - if bytes.Equal(fingerprint[:], hash[:]) { - return nil - } - } - } - return errNotMatch - } -} - -func convertFingerprint(fingerprint string) (*[32]byte, error) { - fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1)) - fpByte, err := hex.DecodeString(fingerprint) - if err != nil { - return nil, err - } - - if len(fpByte) != 32 { - return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint") - } - return (*[32]byte)(fpByte), nil -} - func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) { var certificate []byte var err error if len(customCA) > 0 { - certificate, err = os.ReadFile(C.Path.Resolve(customCA)) + path := C.Path.Resolve(customCA) + if !C.Path.IsSafePath(path) { + return nil, fmt.Errorf("path is not subpath of home directory: %s", path) + } + certificate, err = os.ReadFile(path) if err != nil { return nil, fmt.Errorf("load ca error: %w", err) } @@ -133,14 +103,6 @@ func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) } } -func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) { - fingerprintBytes, err := convertFingerprint(fingerprint) - if err != nil { - return nil, err - } - return verifyFingerprint(fingerprintBytes), nil -} - // GetTLSConfig specified fingerprint, customCA and customCAString func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (_ *tls.Config, err error) { if tlsConfig == nil { diff --git a/component/ca/fingerprint.go b/component/ca/fingerprint.go new file mode 100644 index 0000000000..5c44a31f32 --- /dev/null +++ b/component/ca/fingerprint.go @@ -0,0 +1,44 @@ +package ca + +import ( + "bytes" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "fmt" + "strings" +) + +// NewFingerprintVerifier returns a function that verifies whether a certificate's SHA-256 fingerprint matches the given one. +func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) { + switch fingerprint { + case "chrome", "firefox", "safari", "ios", "android", "edge", "360", "qq", "random", "randomized": // WTF??? + return nil, fmt.Errorf("`fingerprint` is used for TLS certificate pinning. If you need to specify the browser fingerprint, use `client-fingerprint`") + } + fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1)) + fpByte, err := hex.DecodeString(fingerprint) + if err != nil { + return nil, fmt.Errorf("fingerprint string decode error: %w", err) + } + + if len(fpByte) != 32 { + return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint") + } + + return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + // ssl pining + for _, rawCert := range rawCerts { + hash := sha256.Sum256(rawCert) + if bytes.Equal(fpByte, hash[:]) { + return nil + } + } + return errNotMatch + }, nil +} + +// CalculateFingerprint computes the SHA-256 fingerprint of the given DER-encoded certificate and returns it as a hex string. +func CalculateFingerprint(certDER []byte) string { + hash := sha256.Sum256(certDER) + return hex.EncodeToString(hash[:]) +} diff --git a/component/ca/keypair.go b/component/ca/keypair.go new file mode 100644 index 0000000000..51555f6e35 --- /dev/null +++ b/component/ca/keypair.go @@ -0,0 +1,98 @@ +package ca + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "math/big" +) + +type Path interface { + Resolve(path string) string + IsSafePath(path string) bool +} + +// LoadTLSKeyPair loads a TLS key pair from the provided certificate and private key data or file paths, supporting fallback resolution. +// Returns a tls.Certificate and an error, where the error indicates issues during parsing or file loading. +// If both certificate and privateKey are empty, generates a random TLS RSA key pair. +// Accepts a Path interface for resolving file paths when necessary. +func LoadTLSKeyPair(certificate, privateKey string, path Path) (tls.Certificate, error) { + if certificate == "" && privateKey == "" { + var err error + certificate, privateKey, _, err = NewRandomTLSKeyPair(KeyPairTypeRSA) + if err != nil { + return tls.Certificate{}, err + } + } + cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) + if painTextErr == nil { + return cert, nil + } + if path == nil { + return tls.Certificate{}, painTextErr + } + + certificate = path.Resolve(certificate) + privateKey = path.Resolve(privateKey) + var loadErr error + if path.IsSafePath(certificate) && path.IsSafePath(privateKey) { + cert, loadErr = tls.LoadX509KeyPair(certificate, privateKey) + } else { + loadErr = fmt.Errorf("path is not subpath of home directory") + } + if loadErr != nil { + return tls.Certificate{}, fmt.Errorf("parse certificate failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error()) + } + return cert, nil +} + +type KeyPairType string + +const ( + KeyPairTypeRSA KeyPairType = "rsa" + KeyPairTypeP256 KeyPairType = "p256" + KeyPairTypeP384 KeyPairType = "p384" + KeyPairTypeEd25519 KeyPairType = "ed25519" +) + +// NewRandomTLSKeyPair generates a random TLS key pair based on the specified KeyPairType and returns it with a SHA256 fingerprint. +// Note: Most browsers do not support KeyPairTypeEd25519 type of certificate, and utls.UConn will also reject this type of certificate. +func NewRandomTLSKeyPair(keyPairType KeyPairType) (certificate string, privateKey string, fingerprint string, err error) { + var key crypto.Signer + switch keyPairType { + case KeyPairTypeRSA: + key, err = rsa.GenerateKey(rand.Reader, 2048) + case KeyPairTypeP256: + key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + case KeyPairTypeP384: + key, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + case KeyPairTypeEd25519: + _, key, err = ed25519.GenerateKey(rand.Reader) + default: // fallback to KeyPairTypeRSA + key, err = rsa.GenerateKey(rand.Reader, 2048) + } + if err != nil { + return + } + + template := x509.Certificate{SerialNumber: big.NewInt(1)} + certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key) + if err != nil { + return + } + privBytes, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return + } + fingerprint = CalculateFingerprint(certDER) + privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})) + certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})) + return +} diff --git a/component/iface/iface.go b/component/iface/iface.go index 62a46f1f8d..92e0ccf3aa 100644 --- a/component/iface/iface.go +++ b/component/iface/iface.go @@ -80,6 +80,9 @@ func getCache() (*ifaceCache, error) { } cache.ifMap[iface.Name] = ifaceObj + if iface.Flags&net.FlagUp == 0 { + continue // interface down + } for _, prefix := range ipNets { cache.ifTable.Insert(prefix, ifaceObj) } diff --git a/component/proxydialer/proxydialer.go b/component/proxydialer/proxydialer.go index 71a658b861..e1a3cda0ae 100644 --- a/component/proxydialer/proxydialer.go +++ b/component/proxydialer/proxydialer.go @@ -55,8 +55,8 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) ( } var conn C.Conn var err error - if d, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work - conn, err = p.proxy.DialContext(ctx, currentMeta, dialer.WithOption(d.Opt)) + if _, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work + conn, err = p.proxy.DialContext(ctx, currentMeta) } else { conn, err = p.proxy.DialContextWithDialer(ctx, p.dialer, currentMeta) } @@ -78,8 +78,8 @@ func (p proxyDialer) listenPacket(ctx context.Context, currentMeta *C.Metadata) var pc C.PacketConn var err error currentMeta.NetWork = C.UDP - if d, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work - pc, err = p.proxy.ListenPacketContext(ctx, currentMeta, dialer.WithOption(d.Opt)) + if _, ok := p.dialer.(dialer.Dialer); ok { // first using old function to let mux work + pc, err = p.proxy.ListenPacketContext(ctx, currentMeta) } else { pc, err = p.proxy.ListenPacketWithDialer(ctx, p.dialer, currentMeta) } diff --git a/component/proxydialer/sing.go b/component/proxydialer/sing.go index 71180c016d..51d685e8db 100644 --- a/component/proxydialer/sing.go +++ b/component/proxydialer/sing.go @@ -6,8 +6,8 @@ import ( C "github.com/metacubex/mihomo/constant" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" + M "github.com/metacubex/sing/common/metadata" + N "github.com/metacubex/sing/common/network" ) type SingDialer interface { diff --git a/component/proxydialer/slowdown_sing.go b/component/proxydialer/slowdown_sing.go index cc3a46aaf9..c944267073 100644 --- a/component/proxydialer/slowdown_sing.go +++ b/component/proxydialer/slowdown_sing.go @@ -5,7 +5,7 @@ import ( "net" "github.com/metacubex/mihomo/component/slowdown" - M "github.com/sagernet/sing/common/metadata" + M "github.com/metacubex/sing/common/metadata" ) type SlowDownSingDialer struct { diff --git a/component/resource/fetcher.go b/component/resource/fetcher.go index 39beee856a..3658e1a18b 100644 --- a/component/resource/fetcher.go +++ b/component/resource/fetcher.go @@ -3,13 +3,15 @@ package resource import ( "context" "os" + "sync" "time" "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/slowdown" types "github.com/metacubex/mihomo/constant/provider" "github.com/metacubex/mihomo/log" - "github.com/sagernet/fswatch" + "github.com/metacubex/fswatch" "github.com/samber/lo" ) @@ -27,6 +29,8 @@ type Fetcher[V any] struct { interval time.Duration onUpdate func(V) watcher *fswatch.Watcher + loadBufMutex sync.Mutex + backoff slowdown.Backoff } func (f *Fetcher[V]) Name() string { @@ -46,17 +50,11 @@ func (f *Fetcher[V]) UpdatedAt() time.Time { } func (f *Fetcher[V]) Initial() (V, error) { - var ( - buf []byte - contents V - err error - ) - if stat, fErr := os.Stat(f.vehicle.Path()); fErr == nil { // local file exists, use it first - buf, err = os.ReadFile(f.vehicle.Path()) + buf, err := os.ReadFile(f.vehicle.Path()) modTime := stat.ModTime() - contents, _, err = f.loadBuf(buf, utils.MakeHash(buf), false) + contents, _, err := f.loadBuf(buf, utils.MakeHash(buf), false) f.updatedAt = modTime // reset updatedAt to file's modTime if err == nil { @@ -69,21 +67,25 @@ func (f *Fetcher[V]) Initial() (V, error) { } // parse local file error, fallback to remote - contents, _, err = f.Update() + contents, _, updateErr := f.Update() + // start the pull loop even if f.Update() failed + err := f.startPullLoop(false) if err != nil { return lo.Empty[V](), err } - err = f.startPullLoop(false) - if err != nil { - return lo.Empty[V](), err + + if updateErr != nil { + return lo.Empty[V](), updateErr } + return contents, nil } func (f *Fetcher[V]) Update() (V, bool, error) { buf, hash, err := f.vehicle.Read(f.ctx, f.hash) if err != nil { + f.backoff.AddAttempt() // add a failed attempt to backoff return lo.Empty[V](), false, err } return f.loadBuf(buf, hash, f.vehicle.Type() != types.File) @@ -94,6 +96,9 @@ func (f *Fetcher[V]) SideUpdate(buf []byte) (V, bool, error) { } func (f *Fetcher[V]) loadBuf(buf []byte, hash utils.HashType, updateFile bool) (V, bool, error) { + f.loadBufMutex.Lock() + defer f.loadBufMutex.Unlock() + now := time.Now() if f.hash.Equal(hash) { if updateFile { @@ -109,8 +114,10 @@ func (f *Fetcher[V]) loadBuf(buf []byte, hash utils.HashType, updateFile bool) ( contents, err := f.parser(buf) if err != nil { + f.backoff.AddAttempt() // add a failed attempt to backoff return lo.Empty[V](), false, err } + f.backoff.Reset() // no error, reset backoff if updateFile { if err = f.vehicle.Write(buf); err != nil { @@ -145,14 +152,25 @@ func (f *Fetcher[V]) pullLoop(forceUpdate bool) { log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name()) f.updateWithLog() } + if attempt := f.backoff.Attempt(); attempt > 0 { // f.Update() was failed, decrease the interval from backoff to achieve fast retry + if duration := f.backoff.ForAttempt(attempt); duration < initialInterval { + initialInterval = duration + } + } timer := time.NewTimer(initialInterval) defer timer.Stop() for { select { case <-timer.C: - timer.Reset(f.interval) f.updateWithLog() + interval := f.interval + if attempt := f.backoff.Attempt(); attempt > 0 { // f.Update() was failed, decrease the interval from backoff to achieve fast retry + if duration := f.backoff.ForAttempt(attempt); duration < interval { + interval = duration + } + } + timer.Reset(interval) case <-f.ctx.Done(): return } @@ -210,5 +228,11 @@ func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicl parser: parser, onUpdate: onUpdate, interval: interval, + backoff: slowdown.Backoff{ + Factor: 2, + Jitter: false, + Min: time.Second, + Max: interval, + }, } } diff --git a/component/slowdown/backoff.go b/component/slowdown/backoff.go index a5a7251c96..1868763498 100644 --- a/component/slowdown/backoff.go +++ b/component/slowdown/backoff.go @@ -4,9 +4,10 @@ package slowdown import ( "math" - "math/rand" "sync/atomic" "time" + + "github.com/metacubex/randv2" ) // Backoff is a time.Duration counter, starting at Min. After every call to @@ -63,7 +64,7 @@ func (b *Backoff) ForAttempt(attempt float64) time.Duration { minf := float64(min) durf := minf * math.Pow(factor, attempt) if b.Jitter { - durf = rand.Float64()*(durf-minf) + minf + durf = randv2.Float64()*(durf-minf) + minf } //ensure float64 wont overflow int64 if durf > maxInt64 { @@ -90,6 +91,11 @@ func (b *Backoff) Attempt() float64 { return float64(b.attempt.Load()) } +// AddAttempt adds one to the attempt counter. +func (b *Backoff) AddAttempt() { + b.attempt.Add(1) +} + // Copy returns a backoff with equals constraints as the original func (b *Backoff) Copy() *Backoff { return &Backoff{ diff --git a/component/tls/reality.go b/component/tls/reality.go index eee37384a8..6a5cdc5fe1 100644 --- a/component/tls/reality.go +++ b/component/tls/reality.go @@ -37,9 +37,8 @@ type RealityConfig struct { ShortID [RealityMaxShortIDLen]byte } -func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { - retry := 0 - for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ { +func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHelloID, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { + for retry := 0; ; retry++ { verifier := &realityVerifier{ serverName: tlsConfig.ServerName, } @@ -151,7 +150,6 @@ func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string return uConn, nil } - return nil, errors.New("unknown uTLS fingerprint") } func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { diff --git a/component/tls/utls.go b/component/tls/utls.go index b38849e515..80b37f38a8 100644 --- a/component/tls/utls.go +++ b/component/tls/utls.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "net" + "github.com/metacubex/mihomo/common/once" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/log" @@ -11,46 +12,44 @@ import ( "github.com/mroth/weightedrand/v2" ) +type Conn = utls.Conn type UConn = utls.UConn +type UClientHelloID = utls.ClientHelloID const VersionTLS13 = utls.VersionTLS13 -type UClientHelloID struct { - *utls.ClientHelloID +func Client(c net.Conn, config *utls.Config) *Conn { + return utls.Client(c, config) } -var initRandomFingerprint UClientHelloID -var initUtlsClient string - func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn { - return utls.UClient(c, config, *fingerprint.ClientHelloID) + return utls.UClient(c, config, fingerprint) } -func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) { - if ClientFingerprint == "none" { - return UClientHelloID{}, false +func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) { + if len(clientFingerprint) == 0 { + clientFingerprint = globalFingerprint } - - if initRandomFingerprint.ClientHelloID == nil { - initRandomFingerprint, _ = RollFingerprint() + if len(clientFingerprint) == 0 || clientFingerprint == "none" { + return UClientHelloID{}, false } - if ClientFingerprint == "random" { - log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client) - return initRandomFingerprint, true + if clientFingerprint == "random" { + fingerprint := randomFingerprint() + log.Debugln("use initial random HelloID:%s", fingerprint.Client) + return fingerprint, true } - fingerprint, ok := Fingerprints[ClientFingerprint] - if ok { + if fingerprint, ok := fingerprints[clientFingerprint]; ok { log.Debugln("use specified fingerprint:%s", fingerprint.Client) - return fingerprint, ok + return fingerprint, true } else { - log.Warnln("wrong ClientFingerprint:%s", ClientFingerprint) + log.Warnln("wrong clientFingerprint:%s", clientFingerprint) return UClientHelloID{}, false } } -func RollFingerprint() (UClientHelloID, bool) { +var randomFingerprint = once.OnceValue(func() UClientHelloID { chooser, _ := weightedrand.NewChooser( weightedrand.NewChoice("chrome", 6), weightedrand.NewChoice("safari", 3), @@ -59,26 +58,29 @@ func RollFingerprint() (UClientHelloID, bool) { ) initClient := chooser.Pick() log.Debugln("initial random HelloID:%s", initClient) - fingerprint, ok := Fingerprints[initClient] - return fingerprint, ok -} - -var Fingerprints = map[string]UClientHelloID{ - "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}, + fingerprint, ok := fingerprints[initClient] + if !ok { + log.Warnln("error in initial random HelloID:%s", initClient) + } + return fingerprint +}) + +var fingerprints = map[string]UClientHelloID{ + "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": {}, + "randomized": utls.HelloRandomized, } func init() { @@ -88,7 +90,7 @@ func init() { randomized := utls.HelloRandomized randomized.Seed, _ = utls.NewPRNGSeed() randomized.Weights = &weights - Fingerprints["randomized"] = UClientHelloID{&randomized} + fingerprints["randomized"] = randomized } func UCertificates(it tls.Certificate) utls.Certificate { @@ -104,6 +106,8 @@ func UCertificates(it tls.Certificate) utls.Certificate { } } +type Config = utls.Config + func UConfig(config *tls.Config) *utls.Config { return &utls.Config{ Rand: config.Rand, @@ -152,14 +156,12 @@ func BuildWebsocketHandshakeState(c *UConn) error { return nil } -func SetGlobalUtlsClient(Client string) { - initUtlsClient = Client -} +var globalFingerprint string -func HaveGlobalFingerprint() bool { - return len(initUtlsClient) != 0 && initUtlsClient != "none" +func SetGlobalFingerprint(fingerprint string) { + globalFingerprint = fingerprint } func GetGlobalFingerprint() string { - return initUtlsClient + return globalFingerprint } diff --git a/component/updater/update_geo.go b/component/updater/update_geo.go index 719a521515..0778087af0 100644 --- a/component/updater/update_geo.go +++ b/component/updater/update_geo.go @@ -9,7 +9,6 @@ import ( "time" "github.com/metacubex/mihomo/common/atomic" - "github.com/metacubex/mihomo/common/batch" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/geodata" _ "github.com/metacubex/mihomo/component/geodata/standard" @@ -19,6 +18,7 @@ import ( "github.com/metacubex/mihomo/log" "github.com/oschwald/maxminddb-golang" + "golang.org/x/sync/errgroup" ) var ( @@ -169,41 +169,25 @@ func UpdateGeoSite() (err error) { func updateGeoDatabases() error { defer runtime.GC() - b, _ := batch.New[interface{}](context.Background()) + b := errgroup.Group{} if geodata.GeoIpEnable() { if geodata.GeodataMode() { - b.Go("UpdateGeoIp", func() (_ interface{}, err error) { - err = UpdateGeoIp() - return - }) + b.Go(UpdateGeoIp) } else { - b.Go("UpdateMMDB", func() (_ interface{}, err error) { - err = UpdateMMDB() - return - }) + b.Go(UpdateMMDB) } } if geodata.ASNEnable() { - b.Go("UpdateASN", func() (_ interface{}, err error) { - err = UpdateASN() - return - }) + b.Go(UpdateASN) } if geodata.GeoSiteEnable() { - b.Go("UpdateGeoSite", func() (_ interface{}, err error) { - err = UpdateGeoSite() - return - }) + b.Go(UpdateGeoSite) } - if e := b.Wait(); e != nil { - return e.Err - } - - return nil + return b.Wait() } var ErrGetDatabaseUpdateSkip = errors.New("GEO database is updating, skip") diff --git a/config/config.go b/config/config.go index 165c8d705f..f6b193d451 100644 --- a/config/config.go +++ b/config/config.go @@ -754,6 +754,9 @@ func parseGeneral(cfg *RawConfig) (*General, error) { } func parseController(cfg *RawConfig) (*Controller, error) { + if path := cfg.ExternalUI; path != "" && !C.Path.IsSafePath(path) { + return nil, fmt.Errorf("path is not subpath of home directory: %s", path) + } return &Controller{ ExternalController: cfg.ExternalController, ExternalUI: cfg.ExternalUI, diff --git a/constant/adapters.go b/constant/adapters.go index 4289dfa73e..280552ec33 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -134,8 +134,8 @@ type ProxyAdapter interface { // DialContext return a C.Conn with protocol which // contains multiplexing-related reuse logic (if any) - DialContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (Conn, error) - ListenPacketContext(ctx context.Context, metadata *Metadata, opts ...dialer.Option) (PacketConn, error) + DialContext(ctx context.Context, metadata *Metadata) (Conn, error) + ListenPacketContext(ctx context.Context, metadata *Metadata) (PacketConn, error) // SupportUOT return UDP over TCP support SupportUOT() bool diff --git a/constant/path.go b/constant/path.go index 1594441c24..909e18f61f 100644 --- a/constant/path.go +++ b/constant/path.go @@ -37,13 +37,23 @@ var Path = func() *path { } } - return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath} + var safePaths []string + for _, safePath := range filepath.SplitList(os.Getenv("SAFE_PATHS")) { + safePath = strings.TrimSpace(safePath) + if len(safePath) == 0 { + continue + } + safePaths = append(safePaths, safePath) + } + + return &path{homeDir: homeDir, configFile: "config.yaml", allowUnsafePath: allowUnsafePath, safePaths: safePaths} }() type path struct { homeDir string configFile string allowUnsafePath bool + safePaths []string } // SetHomeDir is used to set the configuration path @@ -72,19 +82,22 @@ func (p *path) Resolve(path string) string { return path } -// IsSafePath return true if path is a subpath of homedir +// IsSafePath return true if path is a subpath of homedir (or in the SAFE_PATHS environment variable) func (p *path) IsSafePath(path string) bool { if p.allowUnsafePath || features.CMFA { return true } homedir := p.HomeDir() path = p.Resolve(path) - rel, err := filepath.Rel(homedir, path) - if err != nil { - return false + safePaths := append([]string{homedir}, p.safePaths...) // add homedir to safePaths + for _, safePath := range safePaths { + if rel, err := filepath.Rel(safePath, path); err == nil { + if filepath.IsLocal(rel) { + return true + } + } } - - return !strings.Contains(rel, "..") + return false } func (p *path) GetPathByHash(prefix, name string) string { diff --git a/constant/path_test.go b/constant/path_test.go new file mode 100644 index 0000000000..82c6b2c563 --- /dev/null +++ b/constant/path_test.go @@ -0,0 +1,41 @@ +package constant + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestPath(t *testing.T) { + assert.False(t, (&path{}).IsSafePath("/usr/share/metacubexd/")) + assert.True(t, (&path{ + safePaths: []string{"/usr/share/metacubexd"}, + }).IsSafePath("/usr/share/metacubexd/")) + + assert.False(t, (&path{}).IsSafePath("../metacubexd/")) + assert.True(t, (&path{ + homeDir: "/usr/share/mihomo", + safePaths: []string{"/usr/share/metacubexd"}, + }).IsSafePath("../metacubexd/")) + assert.False(t, (&path{ + homeDir: "/usr/share/mihomo", + safePaths: []string{"/usr/share/ycad"}, + }).IsSafePath("../metacubexd/")) + + assert.False(t, (&path{}).IsSafePath("/opt/mykeys/key1.key")) + assert.True(t, (&path{ + safePaths: []string{"/opt/mykeys"}, + }).IsSafePath("/opt/mykeys/key1.key")) + assert.True(t, (&path{ + safePaths: []string{"/opt/mykeys/"}, + }).IsSafePath("/opt/mykeys/key1.key")) + assert.True(t, (&path{ + safePaths: []string{"/opt/mykeys/key1.key"}, + }).IsSafePath("/opt/mykeys/key1.key")) + + assert.True(t, (&path{}).IsSafePath("key1.key")) + assert.True(t, (&path{}).IsSafePath("./key1.key")) + assert.True(t, (&path{}).IsSafePath("./mykey/key1.key")) + assert.True(t, (&path{}).IsSafePath("./mykey/../key1.key")) + assert.False(t, (&path{}).IsSafePath("./mykey/../../key1.key")) + +} diff --git a/dns/dhcp.go b/dns/dhcp.go index e3829b7c2c..fdd9b2f188 100644 --- a/dns/dhcp.go +++ b/dns/dhcp.go @@ -82,7 +82,7 @@ func (d *dhcpClient) resolve(ctx context.Context) ([]dnsClient, error) { for _, item := range dns { nameserver = append(nameserver, NameServer{ Addr: net.JoinHostPort(item.String(), "53"), - Interface: d.ifaceName, + ProxyName: d.ifaceName, }) } diff --git a/dns/doh.go b/dns/doh.go index 50c7210932..c6f7c67b5d 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -17,8 +17,10 @@ import ( "time" "github.com/metacubex/mihomo/component/ca" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" + "github.com/metacubex/quic-go" "github.com/metacubex/quic-go/http3" D "github.com/miekg/dns" @@ -550,23 +552,23 @@ func (doh *dnsOverHTTPS) createTransportH3( Dial: func( ctx context.Context, - // Ignore the address and always connect to the one that we got - // from the bootstrapper. + // Ignore the address and always connect to the one that we got + // from the bootstrapper. _ string, - tlsCfg *tls.Config, + tlsCfg *tlsC.Config, cfg *quic.Config, ) (c quic.EarlyConnection, err error) { return doh.dialQuic(ctx, addr, tlsCfg, cfg) }, DisableCompression: true, - TLSClientConfig: tlsConfig, + TLSClientConfig: tlsC.UConfig(tlsConfig), QUICConfig: doh.getQUICConfig(), } return &http3Transport{baseTransport: rt}, nil } -func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { +func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tlsC.Config, cfg *quic.Config) (quic.EarlyConnection, error) { ip, port, err := net.SplitHostPort(addr) if err != nil { return nil, err @@ -635,7 +637,7 @@ func (doh *dnsOverHTTPS) probeH3( // Run probeQUIC and probeTLS in parallel and see which one is faster. chQuic := make(chan error, 1) chTLS := make(chan error, 1) - go doh.probeQUIC(ctx, addr, probeTLSCfg, chQuic) + go doh.probeQUIC(ctx, addr, tlsC.UConfig(probeTLSCfg), chQuic) go doh.probeTLS(ctx, probeTLSCfg, chTLS) select { @@ -660,7 +662,7 @@ func (doh *dnsOverHTTPS) probeH3( // probeQUIC attempts to establish a QUIC connection to the specified address. // We run probeQUIC and probeTLS in parallel and see which one is faster. -func (doh *dnsOverHTTPS) probeQUIC(ctx context.Context, addr string, tlsConfig *tls.Config, ch chan error) { +func (doh *dnsOverHTTPS) probeQUIC(ctx context.Context, addr string, tlsConfig *tlsC.Config, ch chan error) { startTime := time.Now() conn, err := doh.dialQuic(ctx, addr, tlsConfig, doh.getQUICConfig()) if err != nil { diff --git a/dns/doq.go b/dns/doq.go index 29fdd00660..af0a0d4601 100644 --- a/dns/doq.go +++ b/dns/doq.go @@ -13,10 +13,11 @@ import ( "time" "github.com/metacubex/mihomo/component/ca" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" - "github.com/metacubex/quic-go" + "github.com/metacubex/quic-go" D "github.com/miekg/dns" ) @@ -338,7 +339,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio transport := quic.Transport{Conn: udp} transport.SetCreatedConn(true) // auto close conn transport.SetSingleUse(true) // auto close transport - conn, err = transport.Dial(ctx, &udpAddr, tlsConfig, doq.getQUICConfig()) + conn, err = transport.Dial(ctx, &udpAddr, tlsC.UConfig(tlsConfig), doq.getQUICConfig()) if err != nil { return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err) } diff --git a/dns/resolver.go b/dns/resolver.go index 9f7e28f38c..0dfeadd28d 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -393,7 +393,6 @@ func (r *Resolver) ResetConnection() { type NameServer struct { Net string Addr string - Interface string ProxyAdapter C.ProxyAdapter ProxyName string Params map[string]string @@ -407,7 +406,6 @@ func (ns NameServer) Equal(ns2 NameServer) bool { }() if ns.Net == ns2.Net && ns.Addr == ns2.Addr && - ns.Interface == ns2.Interface && ns.ProxyAdapter == ns2.ProxyAdapter && ns.ProxyName == ns2.ProxyName && maps.Equal(ns.Params, ns2.Params) && diff --git a/dns/util.go b/dns/util.go index ee04d24e1d..a4ca98d4f1 100644 --- a/dns/util.go +++ b/dns/util.go @@ -11,7 +11,6 @@ import ( "time" "github.com/metacubex/mihomo/common/picker" - "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/log" @@ -115,11 +114,6 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { continue } - var options []dialer.Option - if s.Interface != "" { - options = append(options, dialer.WithInterface(s.Interface)) - } - host, port, _ := net.SplitHostPort(s.Addr) ret = append(ret, &client{ Client: &D.Client{ @@ -132,7 +126,7 @@ func transform(servers []NameServer, resolver *Resolver) []dnsClient { }, port: port, host: host, - dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName, options...), + dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName), }) } return ret diff --git a/docs/config.yaml b/docs/config.yaml index be55e0efb1..af6e3a6161 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -448,6 +448,7 @@ proxies: # socks5 host: "cloud.tencent.com" password: "shadow_tls_password" version: 2 # support 1/2/3 + # alpn: ["h2","http/1.1"] - name: "ss5" type: ss @@ -1179,6 +1180,15 @@ listeners: # proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错) password: vlmpIPSyHH6f4S8WVPdRIHIlzmB+GIRfoH3aNJ/t9Gg= cipher: 2022-blake3-aes-256-gcm + # shadow-tls: + # enable: false # 设置为true时开启 + # version: 3 # 支持v1/v2/v3 + # password: password # v2设置项 + # users: # v3设置项 + # - name: 1 + # password: password + # handshake: + # dest: test.com:443 - name: vmess-in-1 type: vmess diff --git a/go.mod b/go.mod index 1af1accd9a..0f06ecd21d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 - github.com/gofrs/uuid/v5 v5.3.1 + github.com/gofrs/uuid/v5 v5.3.2 github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20 github.com/klauspost/cpuid/v2 v2.2.9 // lastest version compatible with golang1.20 @@ -21,31 +21,31 @@ require ( github.com/metacubex/bart v0.19.0 github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 github.com/metacubex/chacha v0.1.2 + github.com/metacubex/fswatch v0.1.1 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 - github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 + github.com/metacubex/quic-go v0.51.1-0.20250423035655-e3948b36ce14 github.com/metacubex/randv2 v0.2.0 - github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c - github.com/metacubex/sing-shadowsocks v0.2.8 - github.com/metacubex/sing-shadowsocks2 v0.2.2 - github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 - github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 - github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 - github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 + github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 + github.com/metacubex/sing-mux v0.3.2 + github.com/metacubex/sing-quic v0.0.0-20250504030450-1e678cb3d50b + github.com/metacubex/sing-shadowsocks v0.2.9 + github.com/metacubex/sing-shadowsocks2 v0.2.3 + github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 + github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 + github.com/metacubex/sing-vmess v0.2.1 + github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f + github.com/metacubex/smux v0.0.0-20250503055512-501391591dee github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 - github.com/metacubex/utls v1.7.0-alpha.1 + github.com/metacubex/utls v1.7.0-alpha.2 github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 - github.com/miekg/dns v1.1.63 + github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20 github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 github.com/puzpuzpuz/xsync/v3 v3.5.1 github.com/sagernet/cors v1.2.1 - github.com/sagernet/fswatch v0.1.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a - github.com/sagernet/sing v0.5.2 - github.com/sagernet/sing-mux v0.2.1 - github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 - github.com/samber/lo v1.49.1 + github.com/samber/lo v1.50.0 github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 @@ -57,6 +57,7 @@ require ( golang.org/x/crypto v0.33.0 // lastest version compatible with golang1.20 golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20 golang.org/x/net v0.35.0 // lastest version compatible with golang1.20 + golang.org/x/sync v0.11.0 // lastest version compatible with golang1.20 golang.org/x/sys v0.30.0 // lastest version compatible with golang1.20 google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20 gopkg.in/yaml.v3 v3.0.1 @@ -64,19 +65,19 @@ require ( ) require ( - github.com/RyuaNerin/go-krypto v1.2.4 // indirect + github.com/RyuaNerin/go-krypto v1.3.0 // 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.6 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ebitengine/purego v0.8.2 // indirect - github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect + github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 // indirect + github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect @@ -91,14 +92,13 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect + github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // 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 github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/quic-go/qpack v0.4.0 // indirect - github.com/quic-go/qtls-go1-20 v0.4.1 // indirect - github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect @@ -111,10 +111,7 @@ require ( gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/sync v0.11.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.24.0 // indirect ) - -replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a diff --git a/go.sum b/go.sum index 4b7965733b..8fe9af8986 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ 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/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/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg= +github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM= +github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss= 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/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= @@ -26,12 +26,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I= -github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615 h1:W7mpP4uiOAbBOdDnRXT9EUdauFv7bz+ERT5rPIord00= +github.com/ebitengine/purego v0.8.3-0.20250507171810-1638563e3615/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98= github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= -github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8= -github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= +github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= +github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391/go.mod h1:K2R7GhgxrlJzHw2qiPWsCZXf/kXEJN9PLnQK73Ll0po= github.com/ericlagergren/saferand v0.0.0-20220206064634-960a4dd2bc5c h1:RUzBDdZ+e/HEe2Nh8lYsduiPAZygUfVXJn0Ncj5sHMg= @@ -39,8 +39,8 @@ github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 h1:tlDMEdcPRQKBE github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1/go.mod h1:4RfsapbGx2j/vU5xC/5/9qB3kn9Awp1YDiEnN43QrJ4= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010 h1:fuGucgPk5dN6wzfnxl3D0D3rVLw4v2SbBT9jb4VnxzA= github.com/ericlagergren/subtle v0.0.0-20220507045147-890d697da010/go.mod h1:JtBcj7sBuTTRupn7c2bFspMDIObMJsVK8TeUvpShPok= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= @@ -59,8 +59,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= -github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0= -github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= +github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -103,34 +103,43 @@ github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFb github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc= github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= +github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU= +github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI= 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-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI= github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU= -github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/jBDS/kCYjz/x+0BCOKfd2VODYevyeIt+Ds= -github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY= +github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo= +github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA= +github.com/metacubex/quic-go v0.51.1-0.20250423035655-e3948b36ce14 h1:vhB4KEgiN89xXtLlyYWczu3AxgN2T1lp0kIDYT4Faag= +github.com/metacubex/quic-go v0.51.1-0.20250423035655-e3948b36ce14/go.mod h1:9R1NOzCgTcWsdWvOMlmtMuF0uKzuOpsfvEf7U3I8zM0= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= -github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00= -github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= -github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0= -github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= -github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4= -github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= -github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo= -github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= -github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0= -github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63/go.mod h1:eDZ2JpkSkewGmUlCoLSn2MRFn1D0jKPIys/6aogFx7U= -github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 h1:hcsz5e5lqhBxn3iQQDIF60FLZ8PQT542GTQZ+1wcIGo= -github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0= -github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8= -github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ= -github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg= -github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc= +github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= +github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7 h1:m4nSxvw46JEgxMzzmnXams+ebwabcry4Ydep/zNiesQ= +github.com/metacubex/sing v0.5.3-0.20250504031621-1f99e54c15b7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w= +github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw= +github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw= +github.com/metacubex/sing-quic v0.0.0-20250504030450-1e678cb3d50b h1:JKx0yY/eXU7U5tHiAxANytFtkfEjzOte19qLlc+pFeY= +github.com/metacubex/sing-quic v0.0.0-20250504030450-1e678cb3d50b/go.mod h1:mqtr9bgM9eLvLKQqiLOi5I6AJHkvqAw2a61ABZcLuoE= +github.com/metacubex/sing-shadowsocks v0.2.9 h1:2e++13WNN7EGjGtvrGLUzW1xrCdQbW2gIFpgw5GEw00= +github.com/metacubex/sing-shadowsocks v0.2.9/go.mod h1:CJSEGO4FWQAWe+ZiLZxCweGdjRR60A61SIoVjdjQeBA= +github.com/metacubex/sing-shadowsocks2 v0.2.3 h1:v3rNS/5Ywh0NIZ6VU/NmdERQIN5RePzyxCFeQsU4Cx0= +github.com/metacubex/sing-shadowsocks2 v0.2.3/go.mod h1:/WNy/Q8ahLCoPRriWuFZFD0Jy+JNp1MEQl28Zw6SaF8= +github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI= +github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E= +github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6 h1:TAwL91XPa6x1QK55CRm+VTzPvLPUfEr/uFDnOZArqEU= +github.com/metacubex/sing-tun v0.4.6-0.20250503065609-efb9f0beb6f6/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U= +github.com/metacubex/sing-vmess v0.2.1 h1:I6gM3VUjtvJ15D805EUbNH+SRBuqzJeFnuIbKYUsWZ0= +github.com/metacubex/sing-vmess v0.2.1/go.mod h1:DsODWItJtOMZNna8Qhheg8r3tUivrcO3vWgaTYKnfTo= +github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU= +github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80= +github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo= +github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= -github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4= -github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= +github.com/metacubex/utls v1.7.0-alpha.2 h1:kLRg6zDV12R1uclL5qW9Tx4RD6ztGIIrTZWY5zrJXCg= +github.com/metacubex/utls v1.7.0-alpha.2/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= @@ -162,22 +171,12 @@ github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++ github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= -github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= -github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= -github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= -github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= -github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= -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/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY= +github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc= github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs= github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI= github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index d046223e72..dd5f0912ff 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -375,9 +375,8 @@ func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) { } }() } - } - + wg.Wait() } func updateSniffer(snifferConfig *sniffer.Config) { @@ -455,7 +454,7 @@ func updateGeneral(general *config.General, logging bool) { mihomoHttp.SetUA(general.GlobalUA) resource.SetETag(general.ETagSupport) - tlsC.SetGlobalUtlsClient(general.GlobalClientFingerprint) + tlsC.SetGlobalFingerprint(general.GlobalClientFingerprint) } func updateUsers(users []auth.AuthUser) { diff --git a/hub/route/configs.go b/hub/route/configs.go index b23a35a52a..56a9a53d6a 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -373,10 +373,9 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) { } else { if req.Path == "" { req.Path = C.Path.Config() - } - if !filepath.IsAbs(req.Path) { + } else if !filepath.IsLocal(req.Path) { render.Status(r, http.StatusBadRequest) - render.JSON(w, r, newError("path is not a absolute path")) + render.JSON(w, r, newError("path is not a valid absolute path")) return } diff --git a/hub/route/server.go b/hub/route/server.go index 3c3e5ca47f..2ccd8596d7 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -15,8 +15,8 @@ import ( "time" "github.com/metacubex/mihomo/adapter/inbound" - CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/tunnel/statistic" @@ -186,7 +186,7 @@ func startTLS(cfg *Config) { // handle tlsAddr if len(cfg.TLSAddr) > 0 { - c, err := CN.ParseCert(cfg.Certificate, cfg.PrivateKey, C.Path) + c, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path) if err != nil { log.Errorln("External controller tls listen error: %s", err) return diff --git a/listener/anytls/server.go b/listener/anytls/server.go index 2293f7c924..db0a5503fd 100644 --- a/listener/anytls/server.go +++ b/listener/anytls/server.go @@ -12,16 +12,16 @@ import ( "github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/buf" - N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/transport/anytls/padding" "github.com/metacubex/mihomo/transport/anytls/session" - "github.com/sagernet/sing/common/auth" - "github.com/sagernet/sing/common/bufio" - M "github.com/sagernet/sing/common/metadata" + "github.com/metacubex/sing/common/auth" + "github.com/metacubex/sing/common/bufio" + M "github.com/metacubex/sing/common/metadata" ) type Listener struct { @@ -43,7 +43,7 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition) tlsConfig := &tls.Config{} if config.Certificate != "" && config.PrivateKey != "" { - cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/config/shadowsocks.go b/listener/config/shadowsocks.go index c5c60f1095..442743ef78 100644 --- a/listener/config/shadowsocks.go +++ b/listener/config/shadowsocks.go @@ -13,6 +13,7 @@ type ShadowsocksServer struct { Cipher string Udp bool MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"` + ShadowTLS ShadowTLS `yaml:"shadow-tls" json:"shadow-tls,omitempty"` } func (t ShadowsocksServer) String() string { diff --git a/listener/config/shadowtls.go b/listener/config/shadowtls.go new file mode 100644 index 0000000000..b427ad6283 --- /dev/null +++ b/listener/config/shadowtls.go @@ -0,0 +1,22 @@ +package config + +type ShadowTLS struct { + Enable bool + Version int + Password string + Users []ShadowTLSUser + Handshake ShadowTLSHandshakeOptions + HandshakeForServerName map[string]ShadowTLSHandshakeOptions + StrictMode bool + WildcardSNI string +} + +type ShadowTLSUser struct { + Name string + Password string +} + +type ShadowTLSHandshakeOptions struct { + Dest string + Proxy string +} diff --git a/listener/http/server.go b/listener/http/server.go index 3c1eacdd51..52483081eb 100644 --- a/listener/http/server.go +++ b/listener/http/server.go @@ -6,7 +6,7 @@ import ( "net" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant" authStore "github.com/metacubex/mihomo/listener/auth" LC "github.com/metacubex/mihomo/listener/config" @@ -68,7 +68,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A var realityBuilder *reality.Builder if config.Certificate != "" && config.PrivateKey != "" { - cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/inbound/common_test.go b/listener/inbound/common_test.go index 18a6eefa53..7c5718dc06 100644 --- a/listener/inbound/common_test.go +++ b/listener/inbound/common_test.go @@ -17,6 +17,7 @@ import ( N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/generater" C "github.com/metacubex/mihomo/constant" @@ -29,13 +30,14 @@ var httpPath = "/inbound_test" var httpData = make([]byte, 10240) var remoteAddr = netip.MustParseAddr("1.2.3.4") var userUUID = utils.NewUUIDV4().String() -var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = N.NewRandomTLSKeyPair() +var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256) var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey)) var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}} var tlsClientConfig, _ = ca.GetTLSConfig(nil, tlsFingerprint, "", "") var realityPrivateKey, realityPublickey string var realityDest = "itunes.apple.com" var realityShortid = "10f897e26c4b9478" +var realityRealDial = false func init() { rand.Read(httpData) @@ -205,6 +207,14 @@ func NewHttpTestTunnel() *TestTunnel { if metadata.DstPort == 443 { tlsConn := tls.Server(c, tlsConfig.Clone()) if metadata.Host == realityDest { // ignore the tls handshake error for realityDest + if realityRealDial { + rconn, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress()) + if err != nil { + panic(err) + } + N.Relay(rconn, tlsConn) + return + } ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout) defer cancel() if err := tlsConn.HandshakeContext(ctx); err != nil { diff --git a/listener/inbound/shadowsocks.go b/listener/inbound/shadowsocks.go index 51592ab35f..994f4c597c 100644 --- a/listener/inbound/shadowsocks.go +++ b/listener/inbound/shadowsocks.go @@ -15,6 +15,7 @@ type ShadowSocksOption struct { Cipher string `inbound:"cipher"` UDP bool `inbound:"udp,omitempty"` MuxOption MuxOption `inbound:"mux-option,omitempty"` + ShadowTLS ShadowTLS `inbound:"shadow-tls,omitempty"` } func (o ShadowSocksOption) Equal(config C.InboundConfig) bool { @@ -43,6 +44,7 @@ func NewShadowSocks(options *ShadowSocksOption) (*ShadowSocks, error) { Cipher: options.Cipher, Udp: options.UDP, MuxOption: options.MuxOption.Build(), + ShadowTLS: options.ShadowTLS.Build(), }, }, nil } diff --git a/listener/inbound/shadowsocks_test.go b/listener/inbound/shadowsocks_test.go index cf72c55c88..7a26eecaf4 100644 --- a/listener/inbound/shadowsocks_test.go +++ b/listener/inbound/shadowsocks_test.go @@ -3,12 +3,14 @@ package inbound_test import ( "crypto/rand" "encoding/base64" + "net" "net/netip" "strings" "testing" "github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/listener/inbound" + shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" shadowsocks "github.com/metacubex/sing-shadowsocks" "github.com/metacubex/sing-shadowsocks/shadowaead" @@ -17,34 +19,36 @@ import ( "github.com/stretchr/testify/assert" ) -var shadowsocksCipherList = []string{shadowsocks.MethodNone} +var noneList = []string{shadowsocks.MethodNone} +var shadowsocksCipherLists = [][]string{noneList, shadowaead.List, shadowaead_2022.List, shadowstream.List} +var shadowsocksCipherShortLists = [][]string{noneList, shadowaead.List[:5]} // for test shadowTLS var shadowsocksPassword32 string var shadowsocksPassword16 string func init() { - shadowsocksCipherList = append(shadowsocksCipherList, shadowaead.List...) - shadowsocksCipherList = append(shadowsocksCipherList, shadowaead_2022.List...) - shadowsocksCipherList = append(shadowsocksCipherList, shadowstream.List...) passwordBytes := make([]byte, 32) rand.Read(passwordBytes) shadowsocksPassword32 = base64.StdEncoding.EncodeToString(passwordBytes) shadowsocksPassword16 = base64.StdEncoding.EncodeToString(passwordBytes[:16]) } -func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) { +func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption, cipherLists [][]string) { t.Parallel() - for _, cipher := range shadowsocksCipherList { - cipher := cipher - t.Run(cipher, func(t *testing.T) { - inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value - inboundOptions.Cipher = cipher - outboundOptions.Cipher = cipher - testInboundShadowSocks0(t, inboundOptions, outboundOptions) - }) + for _, cipherList := range cipherLists { + for i, cipher := range cipherList { + enableSingMux := i == 0 + cipher := cipher + t.Run(cipher, func(t *testing.T) { + inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value + inboundOptions.Cipher = cipher + outboundOptions.Cipher = cipher + testInboundShadowSocks0(t, inboundOptions, outboundOptions, enableSingMux) + }) + } } } -func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) { +func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption, enableSingMux bool) { t.Parallel() password := shadowsocksPassword32 if strings.Contains(inboundOptions.Cipher, "-128-") { @@ -88,11 +92,74 @@ func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOpt tunnel.DoTest(t, out) - testSingMux(t, tunnel, out) + if enableSingMux { + testSingMux(t, tunnel, out) + } } func TestInboundShadowSocks_Basic(t *testing.T) { inboundOptions := inbound.ShadowSocksOption{} outboundOptions := outbound.ShadowSocksOption{} - testInboundShadowSocks(t, inboundOptions, outboundOptions) + testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherLists) +} + +func testInboundShadowSocksShadowTls(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) { + t.Parallel() + t.Run("Conn", func(t *testing.T) { + inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value + testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists) + }) + t.Run("UConn", func(t *testing.T) { + inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value + outboundOptions.ClientFingerprint = "chrome" + testInboundShadowSocks(t, inboundOptions, outboundOptions, shadowsocksCipherShortLists) + }) +} + +func TestInboundShadowSocks_ShadowTlsv1(t *testing.T) { + inboundOptions := inbound.ShadowSocksOption{ + ShadowTLS: inbound.ShadowTLS{ + Enable: true, + Version: 1, + Handshake: inbound.ShadowTLSHandshakeOptions{Dest: net.JoinHostPort(realityDest, "443")}, + }, + } + outboundOptions := outbound.ShadowSocksOption{ + Plugin: shadowtls.Mode, + PluginOpts: map[string]any{"host": realityDest, "fingerprint": tlsFingerprint, "version": 1}, + } + testInboundShadowSocksShadowTls(t, inboundOptions, outboundOptions) +} + +func TestInboundShadowSocks_ShadowTlsv2(t *testing.T) { + inboundOptions := inbound.ShadowSocksOption{ + ShadowTLS: inbound.ShadowTLS{ + Enable: true, + Version: 2, + Password: shadowsocksPassword16, + Handshake: inbound.ShadowTLSHandshakeOptions{Dest: net.JoinHostPort(realityDest, "443")}, + }, + } + outboundOptions := outbound.ShadowSocksOption{ + Plugin: shadowtls.Mode, + PluginOpts: map[string]any{"host": realityDest, "password": shadowsocksPassword16, "fingerprint": tlsFingerprint, "version": 2}, + } + outboundOptions.PluginOpts["alpn"] = []string{"http/1.1"} // shadowtls v2 work confuse with http/2 server, so we set alpn to http/1.1 to pass the test + testInboundShadowSocksShadowTls(t, inboundOptions, outboundOptions) +} + +func TestInboundShadowSocks_ShadowTlsv3(t *testing.T) { + inboundOptions := inbound.ShadowSocksOption{ + ShadowTLS: inbound.ShadowTLS{ + Enable: true, + Version: 3, + Users: []inbound.ShadowTLSUser{{Name: "test", Password: shadowsocksPassword16}}, + Handshake: inbound.ShadowTLSHandshakeOptions{Dest: net.JoinHostPort(realityDest, "443")}, + }, + } + outboundOptions := outbound.ShadowSocksOption{ + Plugin: shadowtls.Mode, + PluginOpts: map[string]any{"host": realityDest, "password": shadowsocksPassword16, "fingerprint": tlsFingerprint, "version": 3}, + } + testInboundShadowSocksShadowTls(t, inboundOptions, outboundOptions) } diff --git a/listener/inbound/shadowtls.go b/listener/inbound/shadowtls.go new file mode 100644 index 0000000000..d71adbf0e4 --- /dev/null +++ b/listener/inbound/shadowtls.go @@ -0,0 +1,58 @@ +package inbound + +import ( + "github.com/metacubex/mihomo/common/utils" + LC "github.com/metacubex/mihomo/listener/config" +) + +type ShadowTLS struct { + Enable bool `inbound:"enable"` + Version int `inbound:"version,omitempty"` + Password string `inbound:"password,omitempty"` + Users []ShadowTLSUser `inbound:"users,omitempty"` + Handshake ShadowTLSHandshakeOptions `inbound:"handshake,omitempty"` + HandshakeForServerName map[string]ShadowTLSHandshakeOptions `inbound:"handshake-for-server-name,omitempty"` + StrictMode bool `inbound:"strict-mode,omitempty"` + WildcardSNI string `inbound:"wildcard-sni,omitempty"` +} + +type ShadowTLSUser struct { + Name string `inbound:"name,omitempty"` + Password string `inbound:"password,omitempty"` +} + +type ShadowTLSHandshakeOptions struct { + Dest string `inbound:"dest"` + Proxy string `inbound:"proxy,omitempty"` +} + +func (c ShadowTLS) Build() LC.ShadowTLS { + handshakeForServerName := make(map[string]LC.ShadowTLSHandshakeOptions) + for k, v := range c.HandshakeForServerName { + handshakeForServerName[k] = v.Build() + } + return LC.ShadowTLS{ + Enable: c.Enable, + Version: c.Version, + Password: c.Password, + Users: utils.Map(c.Users, ShadowTLSUser.Build), + Handshake: c.Handshake.Build(), + HandshakeForServerName: handshakeForServerName, + StrictMode: c.StrictMode, + WildcardSNI: c.WildcardSNI, + } +} + +func (c ShadowTLSUser) Build() LC.ShadowTLSUser { + return LC.ShadowTLSUser{ + Name: c.Name, + Password: c.Password, + } +} + +func (c ShadowTLSHandshakeOptions) Build() LC.ShadowTLSHandshakeOptions { + return LC.ShadowTLSHandshakeOptions{ + Dest: c.Dest, + Proxy: c.Proxy, + } +} diff --git a/listener/mixed/mixed.go b/listener/mixed/mixed.go index 0ffdb02a3d..6893bb5a15 100644 --- a/listener/mixed/mixed.go +++ b/listener/mixed/mixed.go @@ -8,6 +8,7 @@ import ( "github.com/metacubex/mihomo/adapter/inbound" N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/auth" + "github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant" authStore "github.com/metacubex/mihomo/listener/auth" LC "github.com/metacubex/mihomo/listener/config" @@ -63,7 +64,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A var realityBuilder *reality.Builder if config.Certificate != "" && config.PrivateKey != "" { - cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/reality/reality.go b/listener/reality/reality.go index 16ccc01c10..036bcf2828 100644 --- a/listener/reality/reality.go +++ b/listener/reality/reality.go @@ -9,6 +9,7 @@ import ( "net" "time" + N "github.com/metacubex/mihomo/common/net" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/listener/inner" "github.com/metacubex/mihomo/log" @@ -79,11 +80,17 @@ type Builder struct { } func (b Builder) NewListener(l net.Listener) net.Listener { - l = utls.NewRealityListener(l, b.realityConfig) - // Due to low implementation quality, the reality server intercepted half close and caused memory leaks. - // We fixed it by calling Close() directly. - l = realityListenerWrapper{l} - return l + return N.NewHandleContextListener(context.Background(), l, func(ctx context.Context, conn net.Conn) (net.Conn, error) { + c, err := utls.RealityServer(ctx, conn, b.realityConfig) + if err != nil { + return nil, err + } + // Due to low implementation quality, the reality server intercepted half-close and caused memory leaks. + // We fixed it by calling Close() directly. + return realityConnWrapper{c}, nil + }, func(a any) { + log.Errorln("reality server panic: %s", a) + }) } type realityConnWrapper struct { @@ -97,15 +104,3 @@ func (c realityConnWrapper) Upstream() any { func (c realityConnWrapper) CloseWrite() error { return c.Close() } - -type realityListenerWrapper struct { - net.Listener -} - -func (l realityListenerWrapper) Accept() (net.Conn, error) { - c, err := l.Listener.Accept() - if err != nil { - return nil, err - } - return realityConnWrapper{c.(*utls.Conn)}, nil -} diff --git a/listener/sing/context.go b/listener/sing/context.go index e1e8b452bd..0193cb881f 100644 --- a/listener/sing/context.go +++ b/listener/sing/context.go @@ -6,7 +6,7 @@ import ( "github.com/metacubex/mihomo/adapter/inbound" - "github.com/sagernet/sing/common/auth" + "github.com/metacubex/sing/common/auth" ) type contextKey string diff --git a/listener/sing/dialer.go b/listener/sing/dialer.go new file mode 100644 index 0000000000..d3998d6e9c --- /dev/null +++ b/listener/sing/dialer.go @@ -0,0 +1,35 @@ +package sing + +import ( + "context" + "fmt" + "net" + + C "github.com/metacubex/mihomo/constant" + "github.com/metacubex/mihomo/listener/inner" + + M "github.com/metacubex/sing/common/metadata" + N "github.com/metacubex/sing/common/network" +) + +type Dialer struct { + t C.Tunnel + proxy string +} + +func (d Dialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + if network != "tcp" && network != "tcp4" && network != "tcp6" { + return nil, fmt.Errorf("unsupported network %s", network) + } + return inner.HandleTcp(d.t, destination.String(), d.proxy) +} + +func (d Dialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, fmt.Errorf("unsupported ListenPacket") +} + +var _ N.Dialer = (*Dialer)(nil) + +func NewDialer(t C.Tunnel, proxy string) (d *Dialer) { + return &Dialer{t, proxy} +} diff --git a/listener/sing/sing.go b/listener/sing/sing.go index af7cdbc907..a2ce9b0acc 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -14,15 +14,15 @@ import ( C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" + mux "github.com/metacubex/sing-mux" vmess "github.com/metacubex/sing-vmess" - mux "github.com/sagernet/sing-mux" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/bufio" - "github.com/sagernet/sing/common/bufio/deadline" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/uot" + "github.com/metacubex/sing/common/buf" + "github.com/metacubex/sing/common/bufio" + "github.com/metacubex/sing/common/bufio/deadline" + E "github.com/metacubex/sing/common/exceptions" + M "github.com/metacubex/sing/common/metadata" + "github.com/metacubex/sing/common/network" + "github.com/metacubex/sing/common/uot" ) const UDPTimeout = 5 * time.Minute diff --git a/listener/sing_hysteria2/server.go b/listener/sing_hysteria2/server.go index a415256f95..0090926aba 100644 --- a/listener/sing_hysteria2/server.go +++ b/listener/sing_hysteria2/server.go @@ -13,8 +13,9 @@ import ( "github.com/metacubex/mihomo/adapter/inbound" "github.com/metacubex/mihomo/adapter/outbound" - CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/sockopt" + "github.com/metacubex/mihomo/component/ca" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/listener/sing" @@ -23,7 +24,7 @@ import ( "github.com/metacubex/sing-quic/hysteria2" "github.com/metacubex/quic-go" - E "github.com/sagernet/sing/common/exceptions" + E "github.com/metacubex/sing/common/exceptions" ) type Listener struct { @@ -55,7 +56,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi sl = &Listener{false, config, nil, nil} - cert, err := CN.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } @@ -124,9 +125,10 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi SendBPS: outbound.StringToBps(config.Up), ReceiveBPS: outbound.StringToBps(config.Down), SalamanderPassword: salamanderPassword, - TLSConfig: tlsConfig, + TLSConfig: tlsC.UConfig(tlsConfig), QUICConfig: quicConfig, IgnoreClientBandwidth: config.IgnoreClientBandwidth, + UDPTimeout: sing.UDPTimeout, Handler: h, MasqueradeHandler: masqueradeHandler, CWND: config.CWND, diff --git a/listener/sing_shadowsocks/server.go b/listener/sing_shadowsocks/server.go index fe934ee6bd..52b062ed02 100644 --- a/listener/sing_shadowsocks/server.go +++ b/listener/sing_shadowsocks/server.go @@ -18,11 +18,12 @@ import ( shadowsocks "github.com/metacubex/sing-shadowsocks" "github.com/metacubex/sing-shadowsocks/shadowaead" "github.com/metacubex/sing-shadowsocks/shadowaead_2022" - "github.com/sagernet/sing/common" - "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" + shadowtls "github.com/metacubex/sing-shadowtls" + "github.com/metacubex/sing/common" + "github.com/metacubex/sing/common/buf" + "github.com/metacubex/sing/common/bufio" + M "github.com/metacubex/sing/common/metadata" + "github.com/metacubex/sing/common/network" ) type Listener struct { @@ -31,10 +32,24 @@ type Listener struct { listeners []net.Listener udpListeners []net.PacketConn service shadowsocks.Service + shadowTLS *shadowtls.Service } var _listener *Listener +// shadowTLSService is a wrapper for shadowsocks.Service to support shadowTLS. +type shadowTLSService struct { + shadowsocks.Service + shadowTLS *shadowtls.Service +} + +func (s *shadowTLSService) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { + if s.shadowTLS != nil { + return s.shadowTLS.NewConnection(ctx, conn, metadata) + } + return s.Service.NewConnection(ctx, conn, metadata) +} + func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addition) (C.MultiAddrListener, error) { var sl *Listener var err error @@ -60,7 +75,8 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi return nil, err } - sl = &Listener{false, config, nil, nil, nil} + sl = &Listener{} + sl.config = config switch { case config.Cipher == shadowsocks.MethodNone: @@ -77,6 +93,51 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi return nil, err } + if config.ShadowTLS.Enable { + buildHandshake := func(handshake LC.ShadowTLSHandshakeOptions) (handshakeConfig shadowtls.HandshakeConfig) { + handshakeConfig.Server = M.ParseSocksaddr(handshake.Dest) + handshakeConfig.Dialer = sing.NewDialer(tunnel, handshake.Proxy) + return + } + var handshakeForServerName map[string]shadowtls.HandshakeConfig + if config.ShadowTLS.Version > 1 { + handshakeForServerName = make(map[string]shadowtls.HandshakeConfig) + for serverName, serverOptions := range config.ShadowTLS.HandshakeForServerName { + handshakeForServerName[serverName] = buildHandshake(serverOptions) + } + } + var wildcardSNI shadowtls.WildcardSNI + switch config.ShadowTLS.WildcardSNI { + case "authed": + wildcardSNI = shadowtls.WildcardSNIAuthed + case "all": + wildcardSNI = shadowtls.WildcardSNIAll + default: + wildcardSNI = shadowtls.WildcardSNIOff + } + var shadowTLS *shadowtls.Service + shadowTLS, err = shadowtls.NewService(shadowtls.ServiceConfig{ + Version: config.ShadowTLS.Version, + Password: config.ShadowTLS.Password, + Users: common.Map(config.ShadowTLS.Users, func(it LC.ShadowTLSUser) shadowtls.User { + return shadowtls.User{Name: it.Name, Password: it.Password} + }), + Handshake: buildHandshake(config.ShadowTLS.Handshake), + HandshakeForServerName: handshakeForServerName, + StrictMode: config.ShadowTLS.StrictMode, + WildcardSNI: wildcardSNI, + Handler: sl.service, + Logger: log.SingLogger, + }) + if err != nil { + return nil, err + } + sl.service = &shadowTLSService{ + Service: sl.service, + shadowTLS: shadowTLS, + } + } + for _, addr := range strings.Split(config.Listen, ",") { addr := addr diff --git a/listener/sing_tun/dns.go b/listener/sing_tun/dns.go index 505f16acc9..ab56584808 100644 --- a/listener/sing_tun/dns.go +++ b/listener/sing_tun/dns.go @@ -12,10 +12,10 @@ import ( "github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/log" - "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" + "github.com/metacubex/sing/common/buf" + "github.com/metacubex/sing/common/bufio" + M "github.com/metacubex/sing/common/metadata" + "github.com/metacubex/sing/common/network" ) type ListenerHandler struct { diff --git a/listener/sing_tun/iface.go b/listener/sing_tun/iface.go index 3551a853bb..ddb6c8410c 100644 --- a/listener/sing_tun/iface.go +++ b/listener/sing_tun/iface.go @@ -6,7 +6,7 @@ import ( "github.com/metacubex/mihomo/component/iface" - "github.com/metacubex/sing-tun/control" + "github.com/metacubex/sing/common/control" ) type defaultInterfaceFinder struct{} diff --git a/listener/sing_tun/server.go b/listener/sing_tun/server.go index df5ea0c5c2..6998f0c1d2 100644 --- a/listener/sing_tun/server.go +++ b/listener/sing_tun/server.go @@ -24,11 +24,11 @@ import ( "golang.org/x/exp/constraints" tun "github.com/metacubex/sing-tun" - "github.com/metacubex/sing-tun/control" - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - F "github.com/sagernet/sing/common/format" - "github.com/sagernet/sing/common/ranges" + "github.com/metacubex/sing/common" + "github.com/metacubex/sing/common/control" + E "github.com/metacubex/sing/common/exceptions" + F "github.com/metacubex/sing/common/format" + "github.com/metacubex/sing/common/ranges" "go4.org/netipx" "golang.org/x/exp/maps" diff --git a/listener/sing_vless/server.go b/listener/sing_vless/server.go index c16bc97bf3..97a62fe491 100644 --- a/listener/sing_vless/server.go +++ b/listener/sing_vless/server.go @@ -11,7 +11,7 @@ import ( "unsafe" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" @@ -22,8 +22,8 @@ import ( mihomoVMess "github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/sing-vmess/vless" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/metadata" + "github.com/metacubex/sing/common" + "github.com/metacubex/sing/common/metadata" ) func init() { @@ -87,7 +87,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) var httpHandler http.Handler if config.Certificate != "" && config.PrivateKey != "" { - cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index dc80e8cb8f..b5a9378fd6 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -10,7 +10,7 @@ import ( "strings" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/listener/reality" @@ -20,8 +20,8 @@ import ( mihomoVMess "github.com/metacubex/mihomo/transport/vmess" vmess "github.com/metacubex/sing-vmess" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/metadata" + "github.com/metacubex/sing/common" + "github.com/metacubex/sing/common/metadata" ) type Listener struct { @@ -80,7 +80,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition) var httpHandler http.Handler if config.Certificate != "" && config.PrivateKey != "" { - cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/socks/tcp.go b/listener/socks/tcp.go index 9e42a62505..ab4086a38b 100644 --- a/listener/socks/tcp.go +++ b/listener/socks/tcp.go @@ -9,6 +9,7 @@ import ( "github.com/metacubex/mihomo/adapter/inbound" N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/component/auth" + "github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant" authStore "github.com/metacubex/mihomo/listener/auth" LC "github.com/metacubex/mihomo/listener/config" @@ -62,7 +63,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A var realityBuilder *reality.Builder if config.Certificate != "" && config.PrivateKey != "" { - cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/trojan/server.go b/listener/trojan/server.go index 299575a562..d3ca98d783 100644 --- a/listener/trojan/server.go +++ b/listener/trojan/server.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/metacubex/mihomo/adapter/inbound" - N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/listener/reality" @@ -20,7 +20,7 @@ import ( "github.com/metacubex/mihomo/transport/trojan" mihomoVMess "github.com/metacubex/mihomo/transport/vmess" - "github.com/sagernet/smux" + "github.com/metacubex/smux" ) type Listener struct { @@ -74,7 +74,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition) var httpHandler http.Handler if config.Certificate != "" && config.PrivateKey != "" { - cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } diff --git a/listener/tuic/server.go b/listener/tuic/server.go index ea4aed8f45..75b39a5bf8 100644 --- a/listener/tuic/server.go +++ b/listener/tuic/server.go @@ -7,8 +7,9 @@ import ( "time" "github.com/metacubex/mihomo/adapter/inbound" - CN "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/sockopt" + "github.com/metacubex/mihomo/component/ca" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" LC "github.com/metacubex/mihomo/listener/config" "github.com/metacubex/mihomo/listener/sing" @@ -47,7 +48,7 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) ( return nil, err } - cert, err := CN.ParseCert(config.Certificate, config.PrivateKey, C.Path) + cert, err := ca.LoadTLSKeyPair(config.Certificate, config.PrivateKey, C.Path) if err != nil { return nil, err } @@ -123,7 +124,7 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) ( option := &tuic.ServerOption{ HandleTcpFn: handleTcpFn, HandleUdpFn: handleUdpFn, - TlsConfig: tlsConfig, + TlsConfig: tlsC.UConfig(tlsConfig), QuicConfig: quicConfig, CongestionController: config.CongestionController, AuthenticationTimeout: time.Duration(config.AuthenticationTimeout) * time.Millisecond, diff --git a/log/sing.go b/log/sing.go index 7fdd3f1254..854b7b874a 100644 --- a/log/sing.go +++ b/log/sing.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - L "github.com/sagernet/sing/common/logger" + L "github.com/metacubex/sing/common/logger" ) type singLogger struct{} diff --git a/ntp/service.go b/ntp/service.go index d4582c9910..6fb7453e29 100644 --- a/ntp/service.go +++ b/ntp/service.go @@ -9,8 +9,8 @@ import ( "github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/log" - M "github.com/sagernet/sing/common/metadata" - "github.com/sagernet/sing/common/ntp" + M "github.com/metacubex/sing/common/metadata" + "github.com/metacubex/sing/common/ntp" ) var offset time.Duration diff --git a/rules/provider/parse.go b/rules/provider/parse.go index 97d513fc1b..4683aa3865 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -63,5 +63,7 @@ func ParseRuleProvider(name string, mapping map[string]any, parse common.ParseRu return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) } - return NewRuleSetProvider(name, behavior, format, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil + interval := time.Duration(uint(schema.Interval)) * time.Second + + return NewRuleSetProvider(name, behavior, format, interval, vehicle, schema.Payload, parse), nil } diff --git a/rules/provider/provider.go b/rules/provider/provider.go index 5c456832b6..23a5781f04 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -133,7 +133,7 @@ func (rp *RuleSetProvider) Close() error { return rp.ruleSetProvider.Close() } -func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle, parse common.ParseRuleFunc) P.RuleProvider { +func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleFormat, interval time.Duration, vehicle P.Vehicle, payload []string, parse common.ParseRuleFunc) P.RuleProvider { rp := &ruleSetProvider{ baseProvider: baseProvider{ behavior: behavior, @@ -147,6 +147,9 @@ func NewRuleSetProvider(name string, behavior P.RuleBehavior, format P.RuleForma } rp.strategy = newStrategy(behavior, parse) + if len(payload) > 0 { // using as fallback rules + rp.strategy = rulesParseInline(payload, rp.strategy) + } rp.Fetcher = resource.NewFetcher(name, interval, vehicle, func(bytes []byte) (ruleStrategy, error) { return rulesParse(bytes, newStrategy(behavior, parse), format) }, onUpdate) diff --git a/transport/anytls/client.go b/transport/anytls/client.go index abd8364a0c..44633ae345 100644 --- a/transport/anytls/client.go +++ b/transport/anytls/client.go @@ -13,8 +13,8 @@ import ( "github.com/metacubex/mihomo/transport/anytls/session" "github.com/metacubex/mihomo/transport/vmess" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" + M "github.com/metacubex/sing/common/metadata" + N "github.com/metacubex/sing/common/network" ) type ClientConfig struct { diff --git a/transport/gost-plugin/websocket.go b/transport/gost-plugin/websocket.go index e60af59550..23d06b9462 100644 --- a/transport/gost-plugin/websocket.go +++ b/transport/gost-plugin/websocket.go @@ -8,7 +8,7 @@ import ( "github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/transport/vmess" - smux "github.com/sagernet/smux" + smux "github.com/metacubex/smux" ) // Option is options of gost websocket diff --git a/transport/gun/gun.go b/transport/gun/gun.go index 68f4b2d9a0..13d4046d75 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -237,25 +237,19 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri return pconn, nil } - clientFingerprint := clientFingerprint - if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 { - clientFingerprint = tlsC.GetGlobalFingerprint() - } - if len(clientFingerprint) != 0 { + if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok { if realityConfig == nil { - if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - utlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), fingerprint) - if err := utlsConn.HandshakeContext(ctx); err != nil { - pconn.Close() - return nil, err - } - state := utlsConn.ConnectionState() - if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { - utlsConn.Close() - return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) - } - return utlsConn, nil + tlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), clientFingerprint) + if err := tlsConn.HandshakeContext(ctx); err != nil { + pconn.Close() + return nil, err + } + state := tlsConn.ConnectionState() + if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { + tlsConn.Close() + return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) } + return tlsConn, nil } else { realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig) if err != nil { diff --git a/transport/hysteria/core/client.go b/transport/hysteria/core/client.go index 782948c018..03acb943ee 100644 --- a/transport/hysteria/core/client.go +++ b/transport/hysteria/core/client.go @@ -3,7 +3,6 @@ package core import ( "bytes" "context" - "crypto/tls" "errors" "fmt" "net" @@ -11,6 +10,7 @@ import ( "sync" "time" + tlsC "github.com/metacubex/mihomo/component/tls" "github.com/metacubex/mihomo/transport/hysteria/obfs" "github.com/metacubex/mihomo/transport/hysteria/pmtud_fix" "github.com/metacubex/mihomo/transport/hysteria/transport" @@ -38,7 +38,7 @@ type Client struct { congestionFactory CongestionFactory obfuscator obfs.Obfuscator - tlsConfig *tls.Config + tlsConfig *tlsC.Config quicConfig *quic.Config quicSession quic.Connection @@ -52,7 +52,7 @@ type Client struct { fastOpen bool } -func NewClient(serverAddr string, serverPorts string, protocol string, auth []byte, tlsConfig *tls.Config, quicConfig *quic.Config, +func NewClient(serverAddr string, serverPorts string, protocol string, auth []byte, tlsConfig *tlsC.Config, quicConfig *quic.Config, transport *transport.ClientTransport, sendBPS uint64, recvBPS uint64, congestionFactory CongestionFactory, obfuscator obfs.Obfuscator, hopInterval time.Duration, fastOpen bool) (*Client, error) { quicConfig.DisablePathMTUDiscovery = quicConfig.DisablePathMTUDiscovery || pmtud_fix.DisablePathMTUDiscovery diff --git a/transport/hysteria/transport/client.go b/transport/hysteria/transport/client.go index 91876ea9f4..7fcc08e126 100644 --- a/transport/hysteria/transport/client.go +++ b/transport/hysteria/transport/client.go @@ -1,18 +1,18 @@ package transport import ( - "crypto/tls" "fmt" "net" "time" - "github.com/metacubex/quic-go" - + tlsC "github.com/metacubex/mihomo/component/tls" "github.com/metacubex/mihomo/transport/hysteria/conns/faketcp" "github.com/metacubex/mihomo/transport/hysteria/conns/udp" "github.com/metacubex/mihomo/transport/hysteria/conns/wechat" obfsPkg "github.com/metacubex/mihomo/transport/hysteria/obfs" "github.com/metacubex/mihomo/transport/hysteria/utils" + + "github.com/metacubex/quic-go" ) type ClientTransport struct{} @@ -62,7 +62,7 @@ func (ct *ClientTransport) quicPacketConn(proto string, rAddr net.Addr, serverPo } } -func (ct *ClientTransport) QUICDial(proto string, server string, serverPorts string, tlsConfig *tls.Config, quicConfig *quic.Config, obfs obfsPkg.Obfuscator, hopInterval time.Duration, dialer utils.PacketDialer) (quic.Connection, error) { +func (ct *ClientTransport) QUICDial(proto string, server string, serverPorts string, tlsConfig *tlsC.Config, quicConfig *quic.Config, obfs obfsPkg.Obfuscator, hopInterval time.Duration, dialer utils.PacketDialer) (quic.Connection, error) { serverUDPAddr, err := dialer.RemoteAddr(server) if err != nil { return nil, err diff --git a/transport/sing-shadowtls/shadowtls.go b/transport/sing-shadowtls/shadowtls.go index 2f634bcbb0..904bcd6311 100644 --- a/transport/sing-shadowtls/shadowtls.go +++ b/transport/sing-shadowtls/shadowtls.go @@ -10,7 +10,7 @@ import ( "github.com/metacubex/mihomo/log" "github.com/metacubex/sing-shadowtls" - utls "github.com/metacubex/utls" + "golang.org/x/exp/slices" ) const ( @@ -19,6 +19,7 @@ const ( var ( DefaultALPN = []string{"h2", "http/1.1"} + WsALPN = []string{"http/1.1"} ) type ShadowTLSOption struct { @@ -28,15 +29,19 @@ type ShadowTLSOption struct { ClientFingerprint string SkipCertVerify bool Version int + ALPN []string } func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) (net.Conn, error) { tlsConfig := &tls.Config{ - NextProtos: DefaultALPN, + NextProtos: option.ALPN, MinVersion: tls.VersionTLS12, InsecureSkipVerify: option.SkipCertVerify, ServerName: option.Host, } + if option.Version == 1 { + tlsConfig.MaxVersion = tls.VersionTLS12 // ShadowTLS v1 only support TLS 1.2 + } var err error tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint) @@ -61,17 +66,21 @@ func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) shadowtls.T return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error { tlsConfig := tlsC.UConfig(config) tlsConfig.SessionIDGenerator = sessionIDGenerator - clientFingerprint := clientFingerprint - if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 { - clientFingerprint = tlsC.GetGlobalFingerprint() + if config.MaxVersion == tls.VersionTLS12 { // for ShadowTLS v1 + tlsConn := tlsC.Client(conn, tlsConfig) + return tlsConn.HandshakeContext(ctx) } - if len(clientFingerprint) != 0 { - if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - tlsConn := tlsC.UClient(conn, tlsConfig, fingerprint) - return tlsConn.HandshakeContext(ctx) + if clientFingerprint, ok := tlsC.GetFingerprint(clientFingerprint); ok { + tlsConn := tlsC.UClient(conn, tlsConfig, clientFingerprint) + if slices.Equal(tlsConfig.NextProtos, WsALPN) { + err := tlsC.BuildWebsocketHandshakeState(tlsConn) + if err != nil { + return err + } } + return tlsConn.HandshakeContext(ctx) } - tlsConn := utls.Client(conn, tlsConfig) + tlsConn := tlsC.Client(conn, tlsConfig) return tlsConn.HandshakeContext(ctx) } } diff --git a/transport/tuic/server.go b/transport/tuic/server.go index 354533aa16..d31a8625d5 100644 --- a/transport/tuic/server.go +++ b/transport/tuic/server.go @@ -3,13 +3,13 @@ package tuic import ( "bufio" "context" - "crypto/tls" "net" "time" "github.com/metacubex/mihomo/adapter/inbound" N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/utils" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/tuic/common" @@ -24,7 +24,7 @@ type ServerOption struct { HandleTcpFn func(conn net.Conn, addr socks5.Addr, additions ...inbound.Addition) error HandleUdpFn func(addr socks5.Addr, packet C.UDPPacket, additions ...inbound.Addition) error - TlsConfig *tls.Config + TlsConfig *tlsC.Config QuicConfig *quic.Config Tokens [][32]byte // V4 special Users map[[16]byte]string // V5 special diff --git a/transport/tuic/v4/client.go b/transport/tuic/v4/client.go index 62b419b7a5..b5b3291746 100644 --- a/transport/tuic/v4/client.go +++ b/transport/tuic/v4/client.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "context" - "crypto/tls" "errors" "net" "runtime" @@ -15,6 +14,7 @@ import ( atomic2 "github.com/metacubex/mihomo/common/atomic" N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/pool" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/transport/tuic/common" @@ -25,7 +25,7 @@ import ( ) type ClientOption struct { - TlsConfig *tls.Config + TlsConfig *tlsC.Config QuicConfig *quic.Config Token [32]byte UdpRelayMode common.UdpRelayMode diff --git a/transport/tuic/v5/client.go b/transport/tuic/v5/client.go index a3c13d2b29..d8660cad99 100644 --- a/transport/tuic/v5/client.go +++ b/transport/tuic/v5/client.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "context" - "crypto/tls" "errors" "net" "runtime" @@ -15,6 +14,7 @@ import ( atomic2 "github.com/metacubex/mihomo/common/atomic" N "github.com/metacubex/mihomo/common/net" "github.com/metacubex/mihomo/common/pool" + tlsC "github.com/metacubex/mihomo/component/tls" C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/transport/tuic/common" @@ -25,7 +25,7 @@ import ( ) type ClientOption struct { - TlsConfig *tls.Config + TlsConfig *tlsC.Config QuicConfig *quic.Config Uuid [16]byte Password string diff --git a/transport/vless/vision/vision.go b/transport/vless/vision/vision.go index ac0b05345c..28fd2fceaf 100644 --- a/transport/vless/vision/vision.go +++ b/transport/vless/vision/vision.go @@ -14,7 +14,7 @@ import ( tlsC "github.com/metacubex/mihomo/component/tls" "github.com/gofrs/uuid/v5" - "github.com/sagernet/sing/common" + "github.com/metacubex/sing/common" ) var ErrNotTLS13 = errors.New("XTLS Vision based on TLS 1.3 outer connection") diff --git a/transport/vmess/tls.go b/transport/vmess/tls.go index 69871bb8e0..588c159aa8 100644 --- a/transport/vmess/tls.go +++ b/transport/vmess/tls.go @@ -32,20 +32,14 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn return nil, err } - clientFingerprint := cfg.ClientFingerprint - if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 { - clientFingerprint = tlsC.GetGlobalFingerprint() - } - if len(clientFingerprint) != 0 { + if clientFingerprint, ok := tlsC.GetFingerprint(cfg.ClientFingerprint); ok { if cfg.Reality == nil { - if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - utlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), fingerprint) - err = utlsConn.HandshakeContext(ctx) - if err != nil { - return nil, err - } - return utlsConn, nil + tlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), clientFingerprint) + err = tlsConn.HandshakeContext(ctx) + if err != nil { + return nil, err } + return tlsConn, nil } else { return tlsC.GetRealityConn(ctx, conn, clientFingerprint, tlsConfig, cfg.Reality) } diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index 6a8963fd4e..7e8886b6b7 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -351,31 +351,26 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, } if config.ServerName == "" && !config.InsecureSkipVerify { // users must set either ServerName or InsecureSkipVerify in the config. config = config.Clone() - config.ServerName = uri.Host + config.ServerName = c.Host } - clientFingerprint := c.ClientFingerprint - if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 { - clientFingerprint = tlsC.GetGlobalFingerprint() - } - if len(clientFingerprint) != 0 { - if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - utlsConn := tlsC.UClient(conn, tlsC.UConfig(config), fingerprint) - if err = tlsC.BuildWebsocketHandshakeState(utlsConn); err != nil { - return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) - } - conn = utlsConn + if clientFingerprint, ok := tlsC.GetFingerprint(c.ClientFingerprint); ok { + tlsConn := tlsC.UClient(conn, tlsC.UConfig(config), clientFingerprint) + if err = tlsC.BuildWebsocketHandshakeState(tlsConn); err != nil { + return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) + } + err = tlsConn.HandshakeContext(ctx) + if err != nil { + return nil, err } + conn = tlsConn } else { - conn = tls.Client(conn, config) - } - - if tlsConn, ok := conn.(interface { - HandshakeContext(ctx context.Context) error - }); ok { - if err = tlsConn.HandshakeContext(ctx); err != nil { + tlsConn := tls.Client(conn, config) + err = tlsConn.HandshakeContext(ctx) + if err != nil { return nil, err } + conn = tlsConn } } diff --git a/tunnel/dns_dialer.go b/tunnel/dns_dialer.go index 1839869b4a..c141d96648 100644 --- a/tunnel/dns_dialer.go +++ b/tunnel/dns_dialer.go @@ -21,18 +21,17 @@ type DNSDialer struct { r resolver.Resolver proxyAdapter C.ProxyAdapter proxyName string - opts []dialer.Option } -func NewDNSDialer(r resolver.Resolver, proxyAdapter C.ProxyAdapter, proxyName string, opts ...dialer.Option) *DNSDialer { - return &DNSDialer{r: r, proxyAdapter: proxyAdapter, proxyName: proxyName, opts: opts} +func NewDNSDialer(r resolver.Resolver, proxyAdapter C.ProxyAdapter, proxyName string) *DNSDialer { + return &DNSDialer{r: r, proxyAdapter: proxyAdapter, proxyName: proxyName} } func (d *DNSDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { r := d.r proxyName := d.proxyName proxyAdapter := d.proxyAdapter - opts := d.opts + var opts []dialer.Option var rule C.Rule metadata := &C.Metadata{ NetWork: C.TCP, @@ -94,7 +93,7 @@ func (d *DNSDialer) DialContext(ctx context.Context, network, addr string) (net. metadata.Host = "" // clear host to avoid double resolve in proxy } - conn, err := proxyAdapter.DialContext(ctx, metadata, opts...) + conn, err := proxyAdapter.DialContext(ctx, metadata) if err != nil { logMetadataErr(metadata, rule, proxyAdapter, err) return nil, err @@ -113,7 +112,7 @@ func (d *DNSDialer) DialContext(ctx context.Context, network, addr string) (net. return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter) } - packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...) + packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata) if err != nil { logMetadataErr(metadata, rule, proxyAdapter, err) return nil, err @@ -131,7 +130,7 @@ func (d *DNSDialer) ListenPacket(ctx context.Context, network, addr string) (net r := d.r proxyAdapter := d.proxyAdapter proxyName := d.proxyName - opts := d.opts + var opts []dialer.Option metadata := &C.Metadata{ NetWork: C.UDP, Type: C.INNER, @@ -173,7 +172,7 @@ func (d *DNSDialer) ListenPacket(ctx context.Context, network, addr string) (net return nil, fmt.Errorf("proxy adapter [%s] UDP is not supported", proxyAdapter) } - packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata, opts...) + packetConn, err := proxyAdapter.ListenPacketContext(ctx, metadata) if err != nil { logMetadataErr(metadata, rule, proxyAdapter, err) return nil, err