mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-01 01:59:55 +00:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0f452b540 | ||
|
|
6c9abe16cc | ||
|
|
213d80c1e2 | ||
|
|
1db89da122 | ||
|
|
689c58f661 | ||
|
|
33590c4066 | ||
|
|
60ae9dce56 | ||
|
|
4741ac6702 | ||
|
|
ef3d7e4dd7 | ||
|
|
a1c7881229 | ||
|
|
12e3952b74 | ||
|
|
88419cbd12 | ||
|
|
4ed830330e | ||
|
|
3ed6ff9402 | ||
|
|
34de62d21d | ||
|
|
d2e255f257 | ||
|
|
a0c46bb4b7 | ||
|
|
9e3bf14b1a | ||
|
|
28c387a9b6 | ||
|
|
15eda703b4 | ||
|
|
b1d12a15db | ||
|
|
5a21bf3642 | ||
|
|
199fb8fd5d | ||
|
|
fd959feff2 | ||
|
|
d5a03901d2 | ||
|
|
257fead538 | ||
|
|
c489c5260b | ||
|
|
8f92b1de13 | ||
|
|
9f7a2a36c1 | ||
|
|
a93479124c | ||
|
|
ed42c4feb8 | ||
|
|
608ddb1b44 | ||
|
|
d036d98128 | ||
|
|
d900c71214 | ||
|
|
1672750c47 | ||
|
|
41b57afb3f | ||
|
|
188372cb04 | ||
|
|
a1350d4985 | ||
|
|
dc958e6a39 | ||
|
|
8a5f3b8909 | ||
|
|
c6d7ef8cb8 | ||
|
|
bb8c47d83d | ||
|
|
5cf0f18c29 | ||
|
|
83213d493e | ||
|
|
90ed01ed53 | ||
|
|
f91a586da8 | ||
|
|
266fb03838 | ||
|
|
76e9607fd7 | ||
|
|
23e2d3a132 | ||
|
|
6e35cf9399 | ||
|
|
2116640886 | ||
|
|
a4fcd3af07 | ||
|
|
d22a893060 | ||
|
|
00cceba890 | ||
|
|
2b4726b9ad | ||
|
|
26e6d83f8b | ||
|
|
50d7834e09 | ||
|
|
86c127db8b | ||
|
|
febb6021aa | ||
|
|
9e57b298bf | ||
|
|
791ea5e568 | ||
|
|
7e7016b567 | ||
|
|
b4fe669848 | ||
|
|
cad26ac6a8 | ||
|
|
f328203bc1 | ||
|
|
5c40a6340c | ||
|
|
61d6a9abd6 | ||
|
|
a013ac32a3 | ||
|
|
ee5d77cfd1 | ||
|
|
936df90ace | ||
|
|
f774276896 | ||
|
|
aa51b9faba | ||
|
|
d55b047125 | ||
|
|
efc7abc6e0 | ||
|
|
c2301f66a4 | ||
|
|
468cfc3cc4 | ||
|
|
5dce957755 | ||
|
|
4ecb49b3b9 | ||
|
|
7de4af28d2 | ||
|
|
48d8efb3e9 | ||
|
|
e6e7aa5ae2 | ||
|
|
99aa1b0de1 | ||
|
|
52ad793d11 | ||
|
|
2fb9331211 | ||
|
|
793ce45db0 |
18
.github/release/.fpm_systemd
vendored
Normal file
18
.github/release/.fpm_systemd
vendored
Normal file
@@ -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 <none@example.com>"
|
||||
--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
|
||||
15
.github/release/config.yaml
vendored
Normal file
15
.github/release/config.yaml
vendored
Normal file
@@ -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
|
||||
151
.github/workflows/build.yml
vendored
151
.github/workflows/build.yml
vendored
@@ -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
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .github/release/.fpm_systemd .fpm
|
||||
|
||||
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
|
||||
fpm -t deb \
|
||||
-v "${PackageVersion}" \
|
||||
-p "mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}.deb" \
|
||||
--architecture ${{ matrix.jobs.debian }} \
|
||||
mihomo=/usr/bin/mihomo
|
||||
|
||||
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 <<EOF
|
||||
mixed-port: 7890
|
||||
EOF
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/conffiles <<EOF
|
||||
/etc/mihomo/config.yaml
|
||||
EOF
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
|
||||
Package: mihomo
|
||||
Version: ${PackageVersion}
|
||||
Section:
|
||||
Priority: extra
|
||||
Architecture: ${ARCH}
|
||||
Maintainer: MetaCubeX <none@example.com>
|
||||
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') }}
|
||||
- name: Package RPM
|
||||
if: matrix.jobs.rpm != ''
|
||||
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
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
cp .github/release/.fpm_systemd .fpm
|
||||
|
||||
# - 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
|
||||
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: |
|
||||
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
|
||||
|
||||
@@ -7,9 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -17,7 +15,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 +60,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 +73,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
|
||||
}
|
||||
|
||||
@@ -317,15 +314,7 @@ func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
uintPort, err := strconv.ParseUint(port, 10, 16)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addr = C.Metadata{
|
||||
Host: u.Hostname(),
|
||||
DstIP: netip.Addr{},
|
||||
DstPort: uint16(uintPort),
|
||||
}
|
||||
err = addr.SetRemoteAddress(net.JoinHostPort(u.Hostname(), port))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
@@ -41,23 +40,8 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
// trim FQDN (#737)
|
||||
host = strings.TrimRight(host, ".")
|
||||
|
||||
var uint16Port uint16
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
uint16Port = uint16(port)
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{
|
||||
NetWork: C.TCP,
|
||||
Host: host,
|
||||
DstIP: netip.Addr{},
|
||||
DstPort: uint16Port,
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
metadata := &C.Metadata{}
|
||||
_ = metadata.SetRemoteAddress(net.JoinHostPort(host, port))
|
||||
return metadata
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
@@ -10,13 +9,12 @@ import (
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"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 {
|
||||
@@ -28,24 +26,23 @@ type AnyTLS struct {
|
||||
|
||||
type AnyTLSOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
||||
MinIdleSession int `proxy:"min-idle-session,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
||||
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,23 +50,18 @@ 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) {
|
||||
if err = t.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// create uot on tcp
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
|
||||
}
|
||||
@@ -93,29 +85,6 @@ func (t *AnyTLS) Close() error {
|
||||
|
||||
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||
|
||||
tOption := anytls.ClientConfig{
|
||||
Password: option.Password,
|
||||
Server: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
|
||||
Dialer: singDialer,
|
||||
IdleSessionCheckInterval: time.Duration(option.IdleSessionCheckInterval) * time.Second,
|
||||
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
|
||||
MinIdleSession: option.MinIdleSession,
|
||||
}
|
||||
tlsConfig := &vmess.TLSConfig{
|
||||
Host: option.SNI,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
NextProtos: option.ALPN,
|
||||
FingerPrint: option.Fingerprint,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
}
|
||||
if tlsConfig.Host == "" {
|
||||
tlsConfig.Host = option.Server
|
||||
}
|
||||
tOption.TLSConfig = tlsConfig
|
||||
|
||||
outbound := &AnyTLS{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
@@ -128,10 +97,39 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
client: anytls.NewClient(context.TODO(), tOption),
|
||||
option: &option,
|
||||
dialer: singDialer,
|
||||
}
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer(outbound.DialOptions()...))
|
||||
outbound.dialer = singDialer
|
||||
|
||||
tOption := anytls.ClientConfig{
|
||||
Password: option.Password,
|
||||
Server: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
|
||||
Dialer: singDialer,
|
||||
IdleSessionCheckInterval: time.Duration(option.IdleSessionCheckInterval) * time.Second,
|
||||
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
|
||||
MinIdleSession: option.MinIdleSession,
|
||||
}
|
||||
echConfig, err := option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := &vmess.TLSConfig{
|
||||
Host: option.SNI,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
NextProtos: option.ALPN,
|
||||
FingerPrint: option.Fingerprint,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
ECH: echConfig,
|
||||
}
|
||||
if tlsConfig.Host == "" {
|
||||
tlsConfig.Host = option.Server
|
||||
}
|
||||
tOption.TLSConfig = tlsConfig
|
||||
|
||||
client := anytls.NewClient(context.TODO(), tOption)
|
||||
outbound.client = client
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
@@ -3,22 +3,24 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type ProxyAdapter interface {
|
||||
C.ProxyAdapter
|
||||
DialOptions(opts ...dialer.Option) []dialer.Option
|
||||
DialOptions() []dialer.Option
|
||||
ResolveUDP(ctx context.Context, metadata *C.Metadata) error
|
||||
}
|
||||
|
||||
type Base struct {
|
||||
@@ -59,7 +61,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 +71,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 +130,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))
|
||||
}
|
||||
@@ -160,6 +162,17 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
||||
return opts
|
||||
}
|
||||
|
||||
func (b *Base) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't resolve ip: %w", err)
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Base) Close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -167,8 +180,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
|
||||
}
|
||||
@@ -203,12 +216,21 @@ func NewBase(opt BaseOption) *Base {
|
||||
|
||||
type conn struct {
|
||||
N.ExtendedConn
|
||||
chain C.Chain
|
||||
actualRemoteDestination string
|
||||
chain C.Chain
|
||||
adapterAddr string
|
||||
}
|
||||
|
||||
func (c *conn) RemoteDestination() string {
|
||||
return c.actualRemoteDestination
|
||||
if remoteAddr := c.RemoteAddr(); remoteAddr != nil {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(remoteAddr); err != nil {
|
||||
if m.Valid() {
|
||||
return m.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
host, _, _ := net.SplitHostPort(c.adapterAddr)
|
||||
return host
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
@@ -241,19 +263,25 @@ func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
|
||||
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
|
||||
}
|
||||
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())}
|
||||
return &conn{N.NewExtendedConn(c), []string{a.Name()}, a.Addr()}
|
||||
}
|
||||
|
||||
type packetConn struct {
|
||||
N.EnhancePacketConn
|
||||
chain C.Chain
|
||||
adapterName string
|
||||
connID string
|
||||
actualRemoteDestination string
|
||||
chain C.Chain
|
||||
adapterName string
|
||||
connID string
|
||||
adapterAddr string
|
||||
resolveUDP func(ctx context.Context, metadata *C.Metadata) error
|
||||
}
|
||||
|
||||
func (c *packetConn) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
|
||||
return c.resolveUDP(ctx, metadata)
|
||||
}
|
||||
|
||||
func (c *packetConn) RemoteDestination() string {
|
||||
return c.actualRemoteDestination
|
||||
host, _, _ := net.SplitHostPort(c.adapterAddr)
|
||||
return host
|
||||
}
|
||||
|
||||
// Chains implements C.Connection
|
||||
@@ -287,24 +315,12 @@ func (c *packetConn) AddRef(ref any) {
|
||||
c.EnhancePacketConn = N.NewRefPacketConn(c.EnhancePacketConn, ref) // add ref for autoCloseProxyAdapter
|
||||
}
|
||||
|
||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||
func newPacketConn(pc net.PacketConn, a ProxyAdapter) C.PacketConn {
|
||||
epc := N.NewEnhancePacketConn(pc)
|
||||
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
|
||||
epc = N.NewDeadlineEnhancePacketConn(epc) // most conn from outbound can't handle readDeadline correctly
|
||||
}
|
||||
return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())}
|
||||
}
|
||||
|
||||
func parseRemoteDestination(addr string) string {
|
||||
if dst, _, err := net.SplitHostPort(addr); err == nil {
|
||||
return dst
|
||||
} else {
|
||||
if addrError, ok := err.(*net.AddrError); ok && strings.Contains(addrError.Err, "missing port") {
|
||||
return dst
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return &packetConn{epc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), a.Addr(), a.ResolveUDP}
|
||||
}
|
||||
|
||||
type AddRef interface {
|
||||
@@ -317,8 +333,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 +355,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
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/loopback"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
@@ -20,12 +21,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,25 +35,31 @@ 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
|
||||
}
|
||||
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err := d.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil
|
||||
}
|
||||
|
||||
func (d *Direct) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
|
||||
if (!metadata.Resolved() || resolver.DirectHostResolver != resolver.DefaultResolver) && metadata.Host != "" {
|
||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't resolve ip: %w", err)
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Direct) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
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,15 +23,18 @@ 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())
|
||||
if err := d.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -42,6 +45,13 @@ func (d *Dns) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opt
|
||||
}, d), nil
|
||||
}
|
||||
|
||||
func (d *Dns) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
|
||||
if !metadata.Resolved() {
|
||||
metadata.DstIP = netip.AddrFrom4([4]byte{127, 0, 0, 2})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dnsPacket struct {
|
||||
data []byte
|
||||
put func()
|
||||
|
||||
36
adapter/outbound/ech.go
Normal file
36
adapter/outbound/ech.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
)
|
||||
|
||||
type ECHOptions struct {
|
||||
Enable bool `proxy:"enable,omitempty" obfs:"enable,omitempty"`
|
||||
Config string `proxy:"config,omitempty" obfs:"config,omitempty"`
|
||||
}
|
||||
|
||||
func (o ECHOptions) Parse() (*ech.Config, error) {
|
||||
if !o.Enable {
|
||||
return nil, nil
|
||||
}
|
||||
echConfig := &ech.Config{}
|
||||
if o.Config != "" {
|
||||
list, err := base64.StdEncoding.DecodeString(o.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("base64 decode ech config string failed: %v", err)
|
||||
}
|
||||
echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
|
||||
return list, nil
|
||||
}
|
||||
} else {
|
||||
echConfig.GetEncryptedClientHelloConfigList = func(ctx context.Context, serverName string) ([]byte, error) {
|
||||
return resolver.ResolveECHWithResolver(ctx, serverName, resolver.ProxyServerHostResolver)
|
||||
}
|
||||
}
|
||||
return echConfig, nil
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -10,13 +10,11 @@ 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/ech"
|
||||
"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 +23,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 (
|
||||
@@ -43,10 +45,13 @@ type Hysteria struct {
|
||||
|
||||
option *HysteriaOption
|
||||
client *core.Client
|
||||
|
||||
tlsConfig *tlsC.Config
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
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 +59,23 @@ 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) {
|
||||
if err := h.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 {
|
||||
@@ -78,7 +86,15 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
|
||||
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||
},
|
||||
remoteAddr: func(addr string) (net.Addr, error) {
|
||||
return resolveUDPAddr(ctx, "udp", addr, h.prefer)
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, h.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = h.echConfig.ClientHandle(ctx, h.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return udpAddr, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -92,30 +108,31 @@ func (h *Hysteria) ProxyInfo() C.ProxyInfo {
|
||||
|
||||
type HysteriaOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
Ports string `proxy:"ports,omitempty"`
|
||||
Protocol string `proxy:"protocol,omitempty"`
|
||||
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
|
||||
Up string `proxy:"up"`
|
||||
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
|
||||
Down string `proxy:"down"`
|
||||
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
|
||||
Auth string `proxy:"auth,omitempty"`
|
||||
AuthString string `proxy:"auth-str,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
Ports string `proxy:"ports,omitempty"`
|
||||
Protocol string `proxy:"protocol,omitempty"`
|
||||
ObfsProtocol string `proxy:"obfs-protocol,omitempty"` // compatible with Stash
|
||||
Up string `proxy:"up"`
|
||||
UpSpeed int `proxy:"up-speed,omitempty"` // compatible with Stash
|
||||
Down string `proxy:"down"`
|
||||
DownSpeed int `proxy:"down-speed,omitempty"` // compatible with Stash
|
||||
Auth string `proxy:"auth,omitempty"`
|
||||
AuthString string `proxy:"auth-str,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
||||
}
|
||||
|
||||
func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||
@@ -160,6 +177,13 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
} else {
|
||||
tlsConfig.NextProtos = []string{DefaultALPN}
|
||||
}
|
||||
|
||||
echConfig, err := option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsClientConfig := tlsC.UConfig(tlsConfig)
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||
MaxStreamReceiveWindow: uint64(option.ReceiveWindowConn),
|
||||
@@ -214,7 +238,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, tlsClientConfig, quicConfig, clientTransport, up, down, func(refBPS uint64) congestion.CongestionControl {
|
||||
return hyCongestion.NewBrutalSender(congestion.ByteCount(refBPS))
|
||||
}, obfuscator, hopInterval, option.FastOpen,
|
||||
)
|
||||
@@ -232,8 +256,10 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
option: &option,
|
||||
client: client,
|
||||
option: &option,
|
||||
client: client,
|
||||
tlsConfig: tlsClientConfig,
|
||||
echConfig: echConfig,
|
||||
}
|
||||
|
||||
return outbound, nil
|
||||
|
||||
@@ -14,15 +14,14 @@ 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() {
|
||||
@@ -42,24 +41,25 @@ type Hysteria2 struct {
|
||||
|
||||
type Hysteria2Option struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
Ports string `proxy:"ports,omitempty"`
|
||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
||||
Up string `proxy:"up,omitempty"`
|
||||
Down string `proxy:"down,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
ObfsPassword string `proxy:"obfs-password,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
Ports string `proxy:"ports,omitempty"`
|
||||
HopInterval int `proxy:"hop-interval,omitempty"`
|
||||
Up string `proxy:"up,omitempty"`
|
||||
Down string `proxy:"down,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
Obfs string `proxy:"obfs,omitempty"`
|
||||
ObfsPassword string `proxy:"obfs-password,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
||||
|
||||
// quic-go special config
|
||||
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
|
||||
@@ -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,10 @@ 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) {
|
||||
if err = h.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err := h.client.ListenPacket(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -108,6 +107,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 == "" {
|
||||
@@ -142,6 +157,12 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
}
|
||||
|
||||
tlsClientConfig := tlsC.UConfig(tlsConfig)
|
||||
echConfig, err := option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if option.UdpMTU == 0 {
|
||||
// "1200" from quic-go's MaxDatagramSize
|
||||
// "-3" from quic-go's DatagramFrame.MaxDataLen
|
||||
@@ -155,8 +176,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,41 +184,46 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
ReceiveBPS: StringToBps(option.Down),
|
||||
SalamanderPassword: salamanderPassword,
|
||||
Password: option.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
TLSConfig: tlsClientConfig,
|
||||
QUICConfig: quicConfig,
|
||||
UDPDisabled: false,
|
||||
CWND: option.CWND,
|
||||
UdpMTU: option.UdpMTU,
|
||||
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
|
||||
return resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = echConfig.ClientHandle(ctx, tlsClientConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return udpAddr, nil
|
||||
},
|
||||
}
|
||||
|
||||
var ranges utils.IntRanges[uint16]
|
||||
var serverAddress []string
|
||||
var serverPorts []uint16
|
||||
if option.Ports != "" {
|
||||
ranges, err = utils.NewUnsignedRanges[uint16](option.Ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ranges.Range(func(port uint16) bool {
|
||||
serverAddress = append(serverAddress, net.JoinHostPort(option.Server, strconv.Itoa(int(port))))
|
||||
serverPorts = append(serverPorts, port)
|
||||
return true
|
||||
})
|
||||
if len(serverAddress) > 0 {
|
||||
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
|
||||
return resolveUDPAddr(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
||||
}
|
||||
|
||||
if len(serverPorts) > 0 {
|
||||
if option.HopInterval == 0 {
|
||||
option.HopInterval = defaultHopInterval
|
||||
} else if option.HopInterval < minHopInterval {
|
||||
option.HopInterval = minHopInterval
|
||||
}
|
||||
clientOptions.HopInterval = time.Duration(option.HopInterval) * time.Second
|
||||
clientOptions.ServerPorts = serverPorts
|
||||
}
|
||||
}
|
||||
if option.Port == 0 && len(serverAddress) == 0 {
|
||||
if option.Port == 0 && len(serverPorts) == 0 {
|
||||
return nil, errors.New("invalid port")
|
||||
}
|
||||
|
||||
@@ -207,21 +231,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
|
||||
}
|
||||
|
||||
@@ -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,11 @@ 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.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := m.ensureClientIsRunning(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := m.client.DialContext(ctx, metadata.UDPAddr())
|
||||
@@ -76,7 +79,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 +88,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)
|
||||
|
||||
@@ -13,11 +13,14 @@ import (
|
||||
type RealityOptions struct {
|
||||
PublicKey string `proxy:"public-key"`
|
||||
ShortID string `proxy:"short-id"`
|
||||
|
||||
SupportX25519MLKEM768 bool `proxy:"support-x25519mlkem768"`
|
||||
}
|
||||
|
||||
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
|
||||
if o.PublicKey != "" {
|
||||
config := new(tlsC.RealityConfig)
|
||||
config.SupportX25519MLKEM768 = o.SupportX25519MLKEM768
|
||||
|
||||
const x25519ScalarSize = 32
|
||||
publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey)
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
@@ -21,7 +21,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,10 +29,20 @@ 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) {
|
||||
if err := r.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPacketConn(&nopPacketConn{}, r), nil
|
||||
}
|
||||
|
||||
func (r *Reject) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
|
||||
if !metadata.Resolved() {
|
||||
metadata.DstIP = netip.IPv4Unspecified()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRejectWithOption(option RejectOption) *Reject {
|
||||
return &Reject{
|
||||
Base: &Base{
|
||||
|
||||
@@ -2,7 +2,6 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
gost "github.com/metacubex/mihomo/transport/gost-plugin"
|
||||
"github.com/metacubex/mihomo/transport/restls"
|
||||
@@ -20,9 +18,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 {
|
||||
@@ -64,6 +62,7 @@ type v2rayObfsOption struct {
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
@@ -77,6 +76,7 @@ type gostObfsOption struct {
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
ECHOpts ECHOptions `obfs:"ech-opts,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
@@ -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
|
||||
@@ -199,6 +200,9 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = ss.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -227,15 +231,9 @@ func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if ss.option.UDPOverTCP {
|
||||
// ss uot use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err = ss.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
|
||||
return newPacketConn(N.NewThreadSafePacketConn(uot.NewConn(c, uot.Request{Destination: destination})), ss), nil
|
||||
@@ -302,6 +300,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
v2rayOption.TLS = true
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
v2rayOption.Fingerprint = opts.Fingerprint
|
||||
|
||||
echConfig, err := opts.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize v2ray-plugin error: %w", addr, err)
|
||||
}
|
||||
v2rayOption.ECHConfig = echConfig
|
||||
}
|
||||
} else if option.Plugin == "gost-plugin" {
|
||||
opts := gostObfsOption{Host: "bing.com", Mux: true}
|
||||
@@ -324,6 +328,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
gostOption.TLS = true
|
||||
gostOption.SkipCertVerify = opts.SkipCertVerify
|
||||
gostOption.Fingerprint = opts.Fingerprint
|
||||
|
||||
echConfig, err := opts.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
|
||||
}
|
||||
gostOption.ECHConfig = echConfig
|
||||
}
|
||||
} else if option.Plugin == shadowtls.Mode {
|
||||
obfsMode = shadowtls.Mode
|
||||
@@ -342,6 +352,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{}
|
||||
|
||||
@@ -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
|
||||
@@ -105,6 +105,9 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = ssr.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2,18 +2,16 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
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 +39,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,22 +47,13 @@ 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() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err = s.ProxyAdapter.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc, err := s.client.ListenPacket(ctx, M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -109,7 +96,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,
|
||||
|
||||
@@ -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
|
||||
@@ -127,6 +127,9 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = s.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -207,7 +210,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 {
|
||||
|
||||
@@ -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,14 +101,17 @@ 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 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = ss.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := cDialer.DialContext(ctx, "tcp", ss.addr)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
|
||||
@@ -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, C.Path.ErrNotSafePath(path)
|
||||
}
|
||||
b, err = os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
@@ -32,6 +33,7 @@ type Trojan struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
|
||||
ssCipher core.Cipher
|
||||
}
|
||||
@@ -48,6 +50,7 @@ type TrojanOption struct {
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
@@ -77,6 +80,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
ECHConfig: t.echConfig,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -110,7 +114,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
|
||||
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.echConfig, t.realityConfig)
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
@@ -124,6 +128,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
FingerPrint: t.option.Fingerprint,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
NextProtos: alpn,
|
||||
ECH: t.echConfig,
|
||||
Reality: t.realityConfig,
|
||||
})
|
||||
}
|
||||
@@ -165,10 +170,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 +189,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 +218,15 @@ 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) {
|
||||
if err = t.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 +243,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
|
||||
@@ -245,6 +254,9 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = t.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := dialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
@@ -266,12 +278,6 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
pc := trojan.NewPacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (t *Trojan) SupportUOT() bool {
|
||||
return true
|
||||
@@ -295,6 +301,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,
|
||||
@@ -317,6 +327,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.echConfig, err = option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if option.SSOpts.Enabled {
|
||||
if option.SSOpts.Password == "" {
|
||||
return nil, errors.New("empty password")
|
||||
@@ -334,7 +349,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 {
|
||||
@@ -361,7 +376,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.realityConfig)
|
||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.echConfig, t.realityConfig)
|
||||
|
||||
t.gunTLSConfig = tlsConfig
|
||||
t.gunConfig = &gun.Config{
|
||||
|
||||
@@ -3,7 +3,6 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@@ -12,21 +11,25 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"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 {
|
||||
*Base
|
||||
option *TuicOption
|
||||
client *tuic.PoolClient
|
||||
|
||||
tlsConfig *tlsC.Config
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
type TuicOption struct {
|
||||
@@ -47,26 +50,27 @@ type TuicOption struct {
|
||||
DisableSni bool `proxy:"disable-sni,omitempty"`
|
||||
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
|
||||
|
||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
FastOpen bool `proxy:"fast-open,omitempty"`
|
||||
MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
CustomCA string `proxy:"ca,omitempty"`
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
|
||||
ReceiveWindow int `proxy:"recv-window,omitempty"`
|
||||
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
|
||||
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
|
||||
UDPOverStream bool `proxy:"udp-over-stream,omitempty"`
|
||||
UDPOverStreamVersion int `proxy:"udp-over-stream-version,omitempty"`
|
||||
}
|
||||
|
||||
// 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,12 +83,16 @@ 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
|
||||
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if err = t.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.option.UDPOverStream {
|
||||
uotDestination := uot.RequestDestination(uint8(t.option.UDPOverStreamVersion))
|
||||
uotMetadata := *metadata
|
||||
@@ -96,13 +104,6 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
}
|
||||
|
||||
// tuic uos use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
if t.option.UDPOverStreamVersion == uot.LegacyVersion {
|
||||
@@ -134,6 +135,10 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = t.echConfig.ClientHandle(ctx, t.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
addr = udpAddr
|
||||
var pc net.PacketConn
|
||||
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
|
||||
@@ -248,6 +253,12 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
tlsConfig.InsecureSkipVerify = true // tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config
|
||||
}
|
||||
|
||||
tlsClientConfig := tlsC.UConfig(tlsConfig)
|
||||
echConfig, err := option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.UDPOverStreamVersion {
|
||||
case uot.Version, uot.LegacyVersion:
|
||||
case 0:
|
||||
@@ -267,7 +278,9 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
option: &option,
|
||||
option: &option,
|
||||
tlsConfig: tlsClientConfig,
|
||||
echConfig: echConfig,
|
||||
}
|
||||
|
||||
clientMaxOpenStreams := int64(option.MaxOpenStreams)
|
||||
@@ -284,7 +297,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
if len(option.Token) > 0 {
|
||||
tkn := tuic.GenTKN(option.Token)
|
||||
clientOption := &tuic.ClientOptionV4{
|
||||
TlsConfig: tlsConfig,
|
||||
TlsConfig: tlsClientConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Token: tkn,
|
||||
UdpRelayMode: udpRelayMode,
|
||||
@@ -304,7 +317,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
maxUdpRelayPacketSize = tuic.MaxFragSizeV5
|
||||
}
|
||||
clientOption := &tuic.ClientOptionV5{
|
||||
TlsConfig: tlsConfig,
|
||||
TlsConfig: tlsClientConfig,
|
||||
QuicConfig: quicConfig,
|
||||
Uuid: uuid.FromStringOrNil(option.UUID),
|
||||
Password: option.Password,
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"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/gun"
|
||||
@@ -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 (
|
||||
@@ -46,6 +46,7 @@ type Vless struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
type VlessOption struct {
|
||||
@@ -62,6 +63,7 @@ type VlessOption struct {
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
@@ -88,6 +90,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECHConfig: v.echConfig,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -151,7 +154,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
|
||||
default:
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
@@ -206,6 +209,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
@@ -225,10 +229,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 +248,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,18 +275,13 @@ 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) {
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 +297,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
|
||||
@@ -310,13 +309,8 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
@@ -342,13 +336,8 @@ func (v *Vless) SupportWithDialer() C.NetWork {
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, 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)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if v.option.XUDP {
|
||||
@@ -563,6 +552,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.echConfig, err = v.option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
@@ -571,7 +565,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 {
|
||||
@@ -611,7 +605,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
v.gunTLSConfig = tlsConfig
|
||||
v.gunConfig = gunConfig
|
||||
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"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/ntp"
|
||||
@@ -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")
|
||||
@@ -41,6 +41,7 @@ type Vmess struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
echConfig *ech.Config
|
||||
}
|
||||
|
||||
type VmessOption struct {
|
||||
@@ -58,6 +59,7 @@ type VmessOption struct {
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
ServerName string `proxy:"servername,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||
@@ -109,6 +111,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
V2rayHttpUpgrade: v.option.WSOpts.V2rayHttpUpgrade,
|
||||
V2rayHttpUpgradeFastOpen: v.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECHConfig: v.echConfig,
|
||||
Headers: http.Header{},
|
||||
}
|
||||
|
||||
@@ -146,6 +149,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
@@ -195,7 +199,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
|
||||
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.echConfig, v.realityConfig)
|
||||
default:
|
||||
// handle TLS
|
||||
if v.option.TLS {
|
||||
@@ -205,6 +209,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
ECH: v.echConfig,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
}
|
||||
@@ -280,10 +285,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 +304,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,18 +328,13 @@ 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) {
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
@@ -361,13 +361,8 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := dialer.DialContext(ctx, "tcp", v.addr)
|
||||
@@ -407,13 +402,8 @@ func (v *Vmess) Close() error {
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, 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)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err = v.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pc, ok := c.(net.PacketConn); ok {
|
||||
@@ -474,6 +464,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.echConfig, err = v.option.ECHOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
@@ -482,7 +477,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 {
|
||||
@@ -522,7 +517,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
v.gunTLSConfig = tlsConfig
|
||||
v.gunConfig = gunConfig
|
||||
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.echConfig, v.realityConfig)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -26,9 +25,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 +165,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 +488,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 +498,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,23 +514,13 @@ 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
|
||||
}
|
||||
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
|
||||
r := resolver.DefaultResolver
|
||||
if w.resolver != nil {
|
||||
r = w.resolver
|
||||
}
|
||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
if err = w.ResolveUDP(ctx, metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, metadata.DstPort).Unwrap())
|
||||
if err != nil {
|
||||
@@ -543,6 +532,21 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
return newPacketConn(pc, w), nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) ResolveUDP(ctx context.Context, metadata *C.Metadata) error {
|
||||
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
|
||||
r := resolver.DefaultResolver
|
||||
if w.resolver != nil {
|
||||
r = w.resolver
|
||||
}
|
||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't resolve ip: %w", err)
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsL3Protocol implements C.ProxyAdapter
|
||||
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
return true
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
@@ -32,7 +32,6 @@ type HealthCheck struct {
|
||||
url string
|
||||
extra map[string]*extraOption
|
||||
mu sync.Mutex
|
||||
started atomic.Bool
|
||||
proxies []C.Proxy
|
||||
interval time.Duration
|
||||
lazy bool
|
||||
@@ -43,13 +42,8 @@ type HealthCheck struct {
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) process() {
|
||||
if hc.started.Load() {
|
||||
log.Warnln("Skip start health check timer due to it's started")
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(hc.interval)
|
||||
hc.start()
|
||||
go hc.check()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
@@ -62,13 +56,12 @@ func (hc *HealthCheck) process() {
|
||||
}
|
||||
case <-hc.ctx.Done():
|
||||
ticker.Stop()
|
||||
hc.stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
|
||||
func (hc *HealthCheck) setProxies(proxies []C.Proxy) {
|
||||
hc.proxies = proxies
|
||||
}
|
||||
|
||||
@@ -105,10 +98,6 @@ func (hc *HealthCheck) registerHealthCheckTask(url string, expectedStatus utils.
|
||||
option := &extraOption{filters: map[string]struct{}{}, expectedStatus: expectedStatus}
|
||||
splitAndAddFiltersToExtra(filter, option)
|
||||
hc.extra[url] = option
|
||||
|
||||
if hc.auto() && !hc.started.Load() {
|
||||
go hc.process()
|
||||
}
|
||||
}
|
||||
|
||||
func splitAndAddFiltersToExtra(filter string, option *extraOption) {
|
||||
@@ -131,14 +120,6 @@ func (hc *HealthCheck) touch() {
|
||||
hc.lastTouch.Store(time.Now())
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) start() {
|
||||
hc.started.Store(true)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) stop() {
|
||||
hc.started.Store(false)
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) check() {
|
||||
if len(hc.proxies) == 0 {
|
||||
return
|
||||
@@ -147,7 +128,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 +141,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 +177,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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
var (
|
||||
errVehicleType = errors.New("unsupport vehicle type")
|
||||
errSubPath = errors.New("path is not subpath of home directory")
|
||||
)
|
||||
|
||||
type healthCheckSchema struct {
|
||||
@@ -115,7 +114,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
if schema.Path != "" {
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
return nil, C.Path.ErrNotSafePath(path)
|
||||
}
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)
|
||||
@@ -127,5 +126,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)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,13 @@ func (bp *baseProvider) Version() uint32 {
|
||||
return bp.version
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Initial() error {
|
||||
if bp.healthCheck.auto() {
|
||||
go bp.healthCheck.process()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bp *baseProvider) HealthCheck() {
|
||||
bp.healthCheck.check()
|
||||
}
|
||||
@@ -88,7 +95,7 @@ func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils
|
||||
func (bp *baseProvider) setProxies(proxies []C.Proxy) {
|
||||
bp.proxies = proxies
|
||||
bp.version += 1
|
||||
bp.healthCheck.setProxy(proxies)
|
||||
bp.healthCheck.setProxies(proxies)
|
||||
if bp.healthCheck.auto() {
|
||||
go bp.healthCheck.check()
|
||||
}
|
||||
@@ -133,6 +140,9 @@ func (pp *proxySetProvider) Update() error {
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Initial() error {
|
||||
if err := pp.baseProvider.Initial(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := pp.Fetcher.Initial()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -161,11 +171,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) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, payload []map[string]any, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
pd := &proxySetProvider{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
@@ -174,6 +180,21 @@ 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
|
||||
// direct call setProxies on hc to avoid starting a health check process immediately, it should be done by Initial()
|
||||
hc.setProxies(proxies)
|
||||
}
|
||||
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, pd.setProxies)
|
||||
pd.Fetcher = fetcher
|
||||
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
|
||||
@@ -221,10 +242,6 @@ func (ip *inlineProvider) VehicleType() types.VehicleType {
|
||||
return types.Inline
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Update() error {
|
||||
// make api update happy
|
||||
ip.updateAt = time.Now()
|
||||
@@ -232,10 +249,6 @@ func (ip *inlineProvider) Update() error {
|
||||
}
|
||||
|
||||
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
ps := ProxySchema{Proxies: payload}
|
||||
buf, err := yaml.Marshal(ps)
|
||||
if err != nil {
|
||||
@@ -245,6 +258,8 @@ func NewInlineProvider(name string, payload []map[string]any, parser resource.Pa
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// direct call setProxies on hc to avoid starting a health check process immediately, it should be done by Initial()
|
||||
hc.setProxies(proxies)
|
||||
|
||||
ip := &inlineProvider{
|
||||
baseProvider: baseProvider{
|
||||
@@ -288,13 +303,6 @@ func (cp *compatibleProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Initial() error {
|
||||
if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
|
||||
cp.HealthCheck()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
}
|
||||
@@ -304,10 +312,6 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
||||
return nil, errors.New("provider need one proxy at least")
|
||||
}
|
||||
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &compatibleProvider{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
@@ -20,7 +20,7 @@ type connReadResult struct {
|
||||
type Conn struct {
|
||||
network.ExtendedConn
|
||||
deadline atomic.TypedValue[time.Time]
|
||||
pipeDeadline pipeDeadline
|
||||
pipeDeadline PipeDeadline
|
||||
disablePipe atomic.Bool
|
||||
inRead atomic.Bool
|
||||
resultCh chan *connReadResult
|
||||
@@ -34,7 +34,7 @@ func IsConn(conn any) bool {
|
||||
func NewConn(conn net.Conn) *Conn {
|
||||
c := &Conn{
|
||||
ExtendedConn: bufio.NewExtendedConn(conn),
|
||||
pipeDeadline: makePipeDeadline(),
|
||||
pipeDeadline: MakePipeDeadline(),
|
||||
resultCh: make(chan *connReadResult, 1),
|
||||
}
|
||||
c.resultCh <- nil
|
||||
@@ -58,7 +58,7 @@ func (c *Conn) Read(p []byte) (n int, err error) {
|
||||
c.resultCh <- nil
|
||||
break
|
||||
}
|
||||
case <-c.pipeDeadline.wait():
|
||||
case <-c.pipeDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (c *Conn) ReadBuffer(buffer *buf.Buffer) (err error) {
|
||||
c.resultCh <- nil
|
||||
break
|
||||
}
|
||||
case <-c.pipeDeadline.wait():
|
||||
case <-c.pipeDeadline.Wait():
|
||||
return os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.ExtendedConn.SetReadDeadline(t)
|
||||
}
|
||||
c.deadline.Store(t)
|
||||
c.pipeDeadline.set(t)
|
||||
c.pipeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ type readResult struct {
|
||||
type NetPacketConn struct {
|
||||
net.PacketConn
|
||||
deadline atomic.TypedValue[time.Time]
|
||||
pipeDeadline pipeDeadline
|
||||
pipeDeadline PipeDeadline
|
||||
disablePipe atomic.Bool
|
||||
inRead atomic.Bool
|
||||
resultCh chan any
|
||||
@@ -28,7 +28,7 @@ type NetPacketConn struct {
|
||||
func NewNetPacketConn(pc net.PacketConn) net.PacketConn {
|
||||
npc := &NetPacketConn{
|
||||
PacketConn: pc,
|
||||
pipeDeadline: makePipeDeadline(),
|
||||
pipeDeadline: MakePipeDeadline(),
|
||||
resultCh: make(chan any, 1),
|
||||
}
|
||||
npc.resultCh <- nil
|
||||
@@ -83,7 +83,7 @@ FOR:
|
||||
c.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.pipeDeadline.wait():
|
||||
case <-c.pipeDeadline.Wait():
|
||||
return 0, nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func (c *NetPacketConn) SetReadDeadline(t time.Time) error {
|
||||
return c.PacketConn.SetReadDeadline(t)
|
||||
}
|
||||
c.deadline.Store(t)
|
||||
c.pipeDeadline.set(t)
|
||||
c.pipeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ FOR:
|
||||
c.netPacketConn.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.netPacketConn.pipeDeadline.wait():
|
||||
case <-c.netPacketConn.pipeDeadline.Wait():
|
||||
return nil, nil, nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -69,7 +69,7 @@ FOR:
|
||||
c.netPacketConn.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.netPacketConn.pipeDeadline.wait():
|
||||
case <-c.netPacketConn.pipeDeadline.Wait():
|
||||
return M.Socksaddr{}, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ FOR:
|
||||
c.netPacketConn.resultCh <- nil
|
||||
break FOR
|
||||
}
|
||||
case <-c.netPacketConn.pipeDeadline.wait():
|
||||
case <-c.netPacketConn.pipeDeadline.Wait():
|
||||
return nil, M.Socksaddr{}, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,24 +9,24 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// pipeDeadline is an abstraction for handling timeouts.
|
||||
type pipeDeadline struct {
|
||||
// PipeDeadline is an abstraction for handling timeouts.
|
||||
type PipeDeadline struct {
|
||||
mu sync.Mutex // Guards timer and cancel
|
||||
timer *time.Timer
|
||||
cancel chan struct{} // Must be non-nil
|
||||
}
|
||||
|
||||
func makePipeDeadline() pipeDeadline {
|
||||
return pipeDeadline{cancel: make(chan struct{})}
|
||||
func MakePipeDeadline() PipeDeadline {
|
||||
return PipeDeadline{cancel: make(chan struct{})}
|
||||
}
|
||||
|
||||
// set sets the point in time when the deadline will time out.
|
||||
// Set sets the point in time when the deadline will time out.
|
||||
// A timeout event is signaled by closing the channel returned by waiter.
|
||||
// Once a timeout has occurred, the deadline can be refreshed by specifying a
|
||||
// t value in the future.
|
||||
//
|
||||
// A zero value for t prevents timeout.
|
||||
func (d *pipeDeadline) set(t time.Time) {
|
||||
func (d *PipeDeadline) Set(t time.Time) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
@@ -61,8 +61,8 @@ func (d *pipeDeadline) set(t time.Time) {
|
||||
}
|
||||
}
|
||||
|
||||
// wait returns a channel that is closed when the deadline is exceeded.
|
||||
func (d *pipeDeadline) wait() chan struct{} {
|
||||
// Wait returns a channel that is closed when the deadline is exceeded.
|
||||
func (d *PipeDeadline) Wait() chan struct{} {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return d.cancel
|
||||
|
||||
@@ -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{}
|
||||
@@ -33,8 +33,8 @@ type pipe struct {
|
||||
localDone chan struct{}
|
||||
remoteDone <-chan struct{}
|
||||
|
||||
readDeadline pipeDeadline
|
||||
writeDeadline pipeDeadline
|
||||
readDeadline PipeDeadline
|
||||
writeDeadline PipeDeadline
|
||||
|
||||
readWaitOptions N.ReadWaitOptions
|
||||
}
|
||||
@@ -56,15 +56,15 @@ func Pipe() (net.Conn, net.Conn) {
|
||||
rdRx: cb1, rdTx: cn1,
|
||||
wrTx: cb2, wrRx: cn2,
|
||||
localDone: done1, remoteDone: done2,
|
||||
readDeadline: makePipeDeadline(),
|
||||
writeDeadline: makePipeDeadline(),
|
||||
readDeadline: MakePipeDeadline(),
|
||||
writeDeadline: MakePipeDeadline(),
|
||||
}
|
||||
p2 := &pipe{
|
||||
rdRx: cb2, rdTx: cn2,
|
||||
wrTx: cb1, wrRx: cn1,
|
||||
localDone: done2, remoteDone: done1,
|
||||
readDeadline: makePipeDeadline(),
|
||||
writeDeadline: makePipeDeadline(),
|
||||
readDeadline: MakePipeDeadline(),
|
||||
writeDeadline: MakePipeDeadline(),
|
||||
}
|
||||
return p1, p2
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func (p *pipe) read(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
case isClosedChan(p.remoteDone):
|
||||
return 0, io.EOF
|
||||
case isClosedChan(p.readDeadline.wait()):
|
||||
case isClosedChan(p.readDeadline.Wait()):
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (p *pipe) read(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
case <-p.remoteDone:
|
||||
return 0, io.EOF
|
||||
case <-p.readDeadline.wait():
|
||||
case <-p.readDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (p *pipe) write(b []byte) (n int, err error) {
|
||||
return 0, io.ErrClosedPipe
|
||||
case isClosedChan(p.remoteDone):
|
||||
return 0, io.ErrClosedPipe
|
||||
case isClosedChan(p.writeDeadline.wait()):
|
||||
case isClosedChan(p.writeDeadline.Wait()):
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ func (p *pipe) write(b []byte) (n int, err error) {
|
||||
return n, io.ErrClosedPipe
|
||||
case <-p.remoteDone:
|
||||
return n, io.ErrClosedPipe
|
||||
case <-p.writeDeadline.wait():
|
||||
case <-p.writeDeadline.Wait():
|
||||
return n, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -145,8 +145,8 @@ func (p *pipe) SetDeadline(t time.Time) error {
|
||||
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.readDeadline.set(t)
|
||||
p.writeDeadline.set(t)
|
||||
p.readDeadline.Set(t)
|
||||
p.writeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ func (p *pipe) SetReadDeadline(t time.Time) error {
|
||||
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.readDeadline.set(t)
|
||||
p.readDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func (p *pipe) SetWriteDeadline(t time.Time) error {
|
||||
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.writeDeadline.set(t)
|
||||
p.writeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
return nil, io.ErrClosedPipe
|
||||
case isClosedChan(p.remoteDone):
|
||||
return nil, io.EOF
|
||||
case isClosedChan(p.readDeadline.wait()):
|
||||
case isClosedChan(p.readDeadline.Wait()):
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
select {
|
||||
@@ -211,7 +211,7 @@ func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
return nil, io.ErrClosedPipe
|
||||
case <-p.remoteDone:
|
||||
return nil, io.EOF
|
||||
case <-p.readDeadline.wait():
|
||||
case <-p.readDeadline.Wait():
|
||||
return nil, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
90
common/net/listener.go
Normal file
90
common/net/listener.go
Normal file
@@ -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 conn, err := l.handle(l.ctx, c); err == nil {
|
||||
l.conns <- conn
|
||||
} 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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
102
common/once/oncefunc.go
Normal file
102
common/once/oncefunc.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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, C.Path.ErrNotSafePath(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 {
|
||||
|
||||
44
component/ca/fingerprint.go
Normal file
44
component/ca/fingerprint.go
Normal file
@@ -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[:])
|
||||
}
|
||||
101
component/ca/keypair.go
Normal file
101
component/ca/keypair.go
Normal file
@@ -0,0 +1,101 @@
|
||||
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
|
||||
ErrNotSafePath(path string) error
|
||||
}
|
||||
|
||||
// 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) {
|
||||
loadErr = path.ErrNotSafePath(certificate)
|
||||
} else if !path.IsSafePath(privateKey) {
|
||||
loadErr = path.ErrNotSafePath(privateKey)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -73,7 +73,7 @@ func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.
|
||||
}
|
||||
if opt.interfaceName == "" {
|
||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||
opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
|
||||
opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr().Unmap())
|
||||
}
|
||||
}
|
||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||
|
||||
31
component/ech/ech.go
Normal file
31
component/ech/ech.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ech
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
GetEncryptedClientHelloConfigList func(ctx context.Context, serverName string) ([]byte, error)
|
||||
}
|
||||
|
||||
func (cfg *Config) ClientHandle(ctx context.Context, tlsConfig *tlsC.Config) (err error) {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
echConfigList, err := cfg.GetEncryptedClientHelloConfigList(ctx, tlsConfig.ServerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve ECH config error: %w", err)
|
||||
}
|
||||
|
||||
tlsConfig.EncryptedClientHelloConfigList = echConfigList
|
||||
if tlsConfig.MinVersion != 0 && tlsConfig.MinVersion < tlsC.VersionTLS13 {
|
||||
tlsConfig.MinVersion = tlsC.VersionTLS13
|
||||
}
|
||||
if tlsConfig.MaxVersion != 0 && tlsConfig.MaxVersion < tlsC.VersionTLS13 {
|
||||
tlsConfig.MaxVersion = tlsC.VersionTLS13
|
||||
}
|
||||
return nil
|
||||
}
|
||||
143
component/ech/key.go
Normal file
143
component/ech/key.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package ech
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
)
|
||||
|
||||
const (
|
||||
AEAD_AES_128_GCM = 0x0001
|
||||
AEAD_AES_256_GCM = 0x0002
|
||||
AEAD_ChaCha20Poly1305 = 0x0003
|
||||
)
|
||||
|
||||
const extensionEncryptedClientHello = 0xfe0d
|
||||
const DHKEM_X25519_HKDF_SHA256 = 0x0020
|
||||
const KDF_HKDF_SHA256 = 0x0001
|
||||
|
||||
// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS.
|
||||
// We need this so that when we insert them into ECHConfigs the ordering
|
||||
// is stable.
|
||||
var sortedSupportedAEADs = []uint16{AEAD_AES_128_GCM, AEAD_AES_256_GCM, AEAD_ChaCha20Poly1305}
|
||||
|
||||
func marshalECHConfig(id uint8, pubKey []byte, publicName string, maxNameLen uint8) []byte {
|
||||
builder := cryptobyte.NewBuilder(nil)
|
||||
|
||||
builder.AddUint16(extensionEncryptedClientHello)
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddUint8(id)
|
||||
|
||||
builder.AddUint16(DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(pubKey)
|
||||
})
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
for _, aeadID := range sortedSupportedAEADs {
|
||||
builder.AddUint16(KDF_HKDF_SHA256) // The only KDF we support
|
||||
builder.AddUint16(aeadID)
|
||||
}
|
||||
})
|
||||
builder.AddUint8(maxNameLen)
|
||||
builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes([]byte(publicName))
|
||||
})
|
||||
builder.AddUint16(0) // extensions
|
||||
})
|
||||
|
||||
return builder.BytesOrPanic()
|
||||
}
|
||||
|
||||
func GenECHConfig(publicName string) (configBase64 string, keyPem string, err error) {
|
||||
echKey, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
echConfig := marshalECHConfig(0, echKey.PublicKey().Bytes(), publicName, 0)
|
||||
|
||||
builder := cryptobyte.NewBuilder(nil)
|
||||
builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(echConfig)
|
||||
})
|
||||
echConfigList := builder.BytesOrPanic()
|
||||
|
||||
builder2 := cryptobyte.NewBuilder(nil)
|
||||
builder2.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(echKey.Bytes())
|
||||
})
|
||||
builder2.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) {
|
||||
builder.AddBytes(echConfig)
|
||||
})
|
||||
echConfigKeys := builder2.BytesOrPanic()
|
||||
|
||||
configBase64 = base64.StdEncoding.EncodeToString(echConfigList)
|
||||
keyPem = string(pem.EncodeToMemory(&pem.Block{Type: "ECH KEYS", Bytes: echConfigKeys}))
|
||||
return
|
||||
}
|
||||
|
||||
func UnmarshalECHKeys(raw []byte) ([]tlsC.EncryptedClientHelloKey, error) {
|
||||
var keys []tlsC.EncryptedClientHelloKey
|
||||
rawString := cryptobyte.String(raw)
|
||||
for !rawString.Empty() {
|
||||
var key tlsC.EncryptedClientHelloKey
|
||||
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
|
||||
return nil, errors.New("error parsing private key")
|
||||
}
|
||||
if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
|
||||
return nil, errors.New("error parsing config")
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil, errors.New("empty ECH keys")
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func LoadECHKey(key string, tlsConfig *tlsC.Config, path ca.Path) error {
|
||||
if key == "" {
|
||||
return nil
|
||||
}
|
||||
painTextErr := loadECHKey([]byte(key), tlsConfig)
|
||||
if painTextErr == nil {
|
||||
return nil
|
||||
}
|
||||
key = path.Resolve(key)
|
||||
var loadErr error
|
||||
if !path.IsSafePath(key) {
|
||||
loadErr = path.ErrNotSafePath(key)
|
||||
} else {
|
||||
var echKey []byte
|
||||
echKey, loadErr = os.ReadFile(key)
|
||||
if loadErr == nil {
|
||||
loadErr = loadECHKey(echKey, tlsConfig)
|
||||
}
|
||||
}
|
||||
if loadErr != nil {
|
||||
return fmt.Errorf("parse ECH keys failed, maybe format error:%s, or path error: %s", painTextErr.Error(), loadErr.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadECHKey(echKey []byte, tlsConfig *tlsC.Config) error {
|
||||
block, rest := pem.Decode(echKey)
|
||||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||
return errors.New("invalid ECH keys pem")
|
||||
}
|
||||
echKeys, err := UnmarshalECHKeys(block.Bytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse ECH keys: %w", err)
|
||||
}
|
||||
tlsConfig.EncryptedClientHelloKeys = echKeys
|
||||
return nil
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func Main(args []string) {
|
||||
if len(args) < 1 {
|
||||
panic("Using: generate uuid/reality-keypair/wg-keypair")
|
||||
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair")
|
||||
}
|
||||
switch args[0] {
|
||||
case "uuid":
|
||||
@@ -33,5 +35,15 @@ func Main(args []string) {
|
||||
}
|
||||
fmt.Println("PrivateKey: " + privateKey.String())
|
||||
fmt.Println("PublicKey: " + privateKey.PublicKey().String())
|
||||
case "ech-keypair":
|
||||
if len(args) < 2 {
|
||||
panic("Using: generate ech-keypair <plain_server_name>")
|
||||
}
|
||||
configBase64, keyPem, err := ech.GenECHConfig(args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Config:", configBase64)
|
||||
fmt.Println("Key:", keyPem)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ var (
|
||||
)
|
||||
|
||||
type ifaceCache struct {
|
||||
ifMap map[string]*Interface
|
||||
ifTable bart.Table[*Interface]
|
||||
ifMapByName map[string]*Interface
|
||||
ifMapByAddr map[netip.Addr]*Interface
|
||||
ifTable bart.Table[*Interface]
|
||||
}
|
||||
|
||||
var caches = singledo.NewSingle[*ifaceCache](time.Second * 20)
|
||||
@@ -40,7 +41,8 @@ func getCache() (*ifaceCache, error) {
|
||||
}
|
||||
|
||||
cache := &ifaceCache{
|
||||
ifMap: make(map[string]*Interface),
|
||||
ifMapByName: make(map[string]*Interface),
|
||||
ifMapByAddr: make(map[netip.Addr]*Interface),
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
@@ -78,9 +80,13 @@ func getCache() (*ifaceCache, error) {
|
||||
Flags: iface.Flags,
|
||||
Addresses: ipNets,
|
||||
}
|
||||
cache.ifMap[iface.Name] = ifaceObj
|
||||
cache.ifMapByName[iface.Name] = ifaceObj
|
||||
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue // interface down
|
||||
}
|
||||
for _, prefix := range ipNets {
|
||||
cache.ifMapByAddr[prefix.Addr()] = ifaceObj
|
||||
cache.ifTable.Insert(prefix, ifaceObj)
|
||||
}
|
||||
}
|
||||
@@ -95,7 +101,7 @@ func Interfaces() (map[string]*Interface, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cache.ifMap, nil
|
||||
return cache.ifMapByName, nil
|
||||
}
|
||||
|
||||
func ResolveInterface(name string) (*Interface, error) {
|
||||
@@ -117,6 +123,11 @@ func ResolveInterfaceByAddr(addr netip.Addr) (*Interface, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// maybe two interfaces have the same prefix but different address
|
||||
// so direct check address equal before do a route lookup (longest prefix match)
|
||||
if iface, ok := cache.ifMapByAddr[addr]; ok {
|
||||
return iface, nil
|
||||
}
|
||||
iface, ok := cache.ifTable.Lookup(addr)
|
||||
if !ok {
|
||||
return nil, ErrIfaceNotFound
|
||||
@@ -130,7 +141,8 @@ func IsLocalIp(addr netip.Addr) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return cache.ifTable.Contains(addr), nil
|
||||
_, ok := cache.ifMapByAddr[addr]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
func FlushCache() {
|
||||
|
||||
@@ -2,7 +2,6 @@ package proxydialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
@@ -40,23 +38,22 @@ func (p proxyDialer) DialContext(ctx context.Context, network, address string) (
|
||||
return nil, err
|
||||
}
|
||||
if strings.Contains(network, "udp") { // using in wireguard outbound
|
||||
if !currentMeta.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, currentMeta.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
currentMeta.DstIP = ip
|
||||
}
|
||||
pc, err := p.listenPacket(ctx, currentMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !currentMeta.Resolved() { // should not happen, maybe by a wrongly implemented proxy, but we can handle this (:
|
||||
err = pc.ResolveUDP(ctx, currentMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return N.NewBindPacketConn(pc, currentMeta.UDPAddr()), nil
|
||||
}
|
||||
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 +75,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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -77,7 +77,7 @@ func NewHostValue(value any) (HostValue, error) {
|
||||
isDomain = false
|
||||
for _, str := range valueArr {
|
||||
if ip, err := netip.ParseAddr(str); err == nil {
|
||||
ips = append(ips, ip)
|
||||
ips = append(ips, ip.Unmap())
|
||||
} else {
|
||||
return HostValue{}, err
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func NewHostValue(value any) (HostValue, error) {
|
||||
} else if len(valueArr) == 1 {
|
||||
host := valueArr[0]
|
||||
if ip, err := netip.ParseAddr(host); err == nil {
|
||||
ips = append(ips, ip)
|
||||
ips = append(ips, ip.Unmap())
|
||||
isDomain = false
|
||||
} else {
|
||||
domain = host
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
@@ -49,6 +48,7 @@ type Resolver interface {
|
||||
LookupIP(ctx context.Context, host string) (ips []netip.Addr, err error)
|
||||
LookupIPv4(ctx context.Context, host string) (ips []netip.Addr, err error)
|
||||
LookupIPv6(ctx context.Context, host string) (ips []netip.Addr, err error)
|
||||
ResolveECH(ctx context.Context, host string) ([]byte, error)
|
||||
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
|
||||
Invalid() bool
|
||||
ClearCache()
|
||||
@@ -67,7 +67,8 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
||||
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
if ip.Is4() || ip.Is4In6() {
|
||||
ip = ip.Unmap()
|
||||
if ip.Is4() {
|
||||
return []netip.Addr{ip}, nil
|
||||
}
|
||||
return []netip.Addr{}, ErrIPVersion
|
||||
@@ -116,7 +117,8 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
||||
}
|
||||
|
||||
if ip, err := netip.ParseAddr(host); err == nil {
|
||||
if strings.Contains(host, ":") {
|
||||
ip = ip.Unmap()
|
||||
if ip.Is6() {
|
||||
return []netip.Addr{ip}, nil
|
||||
}
|
||||
return nil, ErrIPVersion
|
||||
@@ -165,6 +167,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
|
||||
}
|
||||
|
||||
if ip, err := netip.ParseAddr(host); err == nil {
|
||||
ip = ip.Unmap()
|
||||
return []netip.Addr{ip}, nil
|
||||
}
|
||||
|
||||
@@ -216,6 +219,17 @@ func ResolveIPPrefer6(ctx context.Context, host string) (netip.Addr, error) {
|
||||
return ResolveIPPrefer6WithResolver(ctx, host, DefaultResolver)
|
||||
}
|
||||
|
||||
func ResolveECHWithResolver(ctx context.Context, host string, r Resolver) ([]byte, error) {
|
||||
if r != nil && r.Invalid() {
|
||||
return r.ResolveECH(ctx, host)
|
||||
}
|
||||
return SystemResolver.ResolveECH(ctx, host)
|
||||
}
|
||||
|
||||
func ResolveECH(ctx context.Context, host string) ([]byte, error) {
|
||||
return ResolveECHWithResolver(ctx, host, DefaultResolver)
|
||||
}
|
||||
|
||||
func ResetConnection() {
|
||||
if DefaultResolver != nil {
|
||||
go DefaultResolver.ResetConnection()
|
||||
|
||||
@@ -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,12 +96,16 @@ 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 {
|
||||
_ = os.Chtimes(f.vehicle.Path(), now, now)
|
||||
}
|
||||
f.updatedAt = now
|
||||
f.backoff.Reset() // no error, reset backoff
|
||||
return lo.Empty[V](), true, nil
|
||||
}
|
||||
|
||||
@@ -109,8 +115,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 +153,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
|
||||
}
|
||||
@@ -202,6 +221,10 @@ func (f *Fetcher[V]) updateWithLog() {
|
||||
|
||||
func NewFetcher[V any](name string, interval time.Duration, vehicle types.Vehicle, parser Parser[V], onUpdate func(V)) *Fetcher[V] {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
minBackoff := 10 * time.Second
|
||||
if interval < minBackoff {
|
||||
minBackoff = interval
|
||||
}
|
||||
return &Fetcher[V]{
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
@@ -210,5 +233,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: minBackoff,
|
||||
Max: interval,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -72,8 +72,16 @@ func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSend
|
||||
overrideDest := config.OverrideDest
|
||||
|
||||
if inWhitelist {
|
||||
replaceDomain := func(metadata *C.Metadata, host string) {
|
||||
if sd.domainCanReplace(host) {
|
||||
replaceDomain(metadata, host, overrideDest)
|
||||
} else {
|
||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||
}
|
||||
}
|
||||
|
||||
if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok {
|
||||
return wrapable.WrapperSender(packetSender, overrideDest)
|
||||
return wrapable.WrapperSender(packetSender, replaceDomain)
|
||||
}
|
||||
|
||||
host, err := current.SniffData(packet.Data())
|
||||
@@ -81,7 +89,7 @@ func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSend
|
||||
continue
|
||||
}
|
||||
|
||||
replaceDomain(metadata, host, overrideDest)
|
||||
replaceDomain(metadata, host)
|
||||
return packetSender
|
||||
}
|
||||
}
|
||||
@@ -128,11 +136,9 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
|
||||
return false
|
||||
}
|
||||
|
||||
for _, matcher := range sd.skipDomain {
|
||||
if matcher.MatchDomain(host) {
|
||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||
return false
|
||||
}
|
||||
if !sd.domainCanReplace(host) {
|
||||
log.Debugln("[Sniffer] Skip sni[%s]", host)
|
||||
return false
|
||||
}
|
||||
|
||||
sd.skipList.Delete(dst)
|
||||
@@ -152,10 +158,20 @@ func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||
metadata.RemoteAddress(),
|
||||
metadata.Host, host)
|
||||
metadata.Host = host
|
||||
metadata.DstIP = netip.Addr{}
|
||||
}
|
||||
metadata.DNSMode = C.DNSNormal
|
||||
}
|
||||
|
||||
func (sd *Dispatcher) domainCanReplace(host string) bool {
|
||||
for _, matcher := range sd.skipDomain {
|
||||
if matcher.MatchDomain(host) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (sd *Dispatcher) Enable() bool {
|
||||
return sd != nil && sd.enable
|
||||
}
|
||||
|
||||
@@ -74,24 +74,25 @@ func (sniffer *QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
return "", ErrorUnsupportedSniffer
|
||||
}
|
||||
|
||||
func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender {
|
||||
func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, replaceDomain sniffer.ReplaceDomain) constant.PacketSender {
|
||||
return &quicPacketSender{
|
||||
sender: packetSender,
|
||||
chClose: make(chan struct{}),
|
||||
override: override,
|
||||
PacketSender: packetSender,
|
||||
replaceDomain: replaceDomain,
|
||||
chClose: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
var _ constant.PacketSender = (*quicPacketSender)(nil)
|
||||
|
||||
type quicPacketSender struct {
|
||||
lock sync.RWMutex
|
||||
ranges utils.IntRanges[uint64]
|
||||
buffer []byte
|
||||
result string
|
||||
override bool
|
||||
lock sync.RWMutex
|
||||
ranges utils.IntRanges[uint64]
|
||||
buffer []byte
|
||||
result *string
|
||||
|
||||
sender constant.PacketSender
|
||||
replaceDomain sniffer.ReplaceDomain
|
||||
|
||||
constant.PacketSender
|
||||
|
||||
chClose chan struct{}
|
||||
closed bool
|
||||
@@ -100,7 +101,7 @@ type quicPacketSender struct {
|
||||
// Send will send PacketAdapter nonblocking
|
||||
// the implement must call UDPPacket.Drop() inside Send
|
||||
func (q *quicPacketSender) Send(current constant.PacketAdapter) {
|
||||
defer q.sender.Send(current)
|
||||
defer q.PacketSender.Send(current)
|
||||
|
||||
q.lock.RLock()
|
||||
if q.closed {
|
||||
@@ -116,29 +117,27 @@ func (q *quicPacketSender) Send(current constant.PacketAdapter) {
|
||||
}
|
||||
}
|
||||
|
||||
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy
|
||||
func (q *quicPacketSender) Process(conn constant.PacketConn, proxy constant.WriteBackProxy) {
|
||||
q.sender.Process(conn, proxy)
|
||||
}
|
||||
|
||||
// ResolveUDP wait sniffer recv all fragments and update the domain
|
||||
func (q *quicPacketSender) ResolveUDP(data *constant.Metadata) error {
|
||||
// DoSniff wait sniffer recv all fragments and update the domain
|
||||
func (q *quicPacketSender) DoSniff(metadata *constant.Metadata) error {
|
||||
select {
|
||||
case <-q.chClose:
|
||||
q.lock.RLock()
|
||||
replaceDomain(data, q.result, q.override)
|
||||
if q.result != nil {
|
||||
host := *q.result
|
||||
q.replaceDomain(metadata, host)
|
||||
}
|
||||
q.lock.RUnlock()
|
||||
break
|
||||
case <-time.After(quicWaitConn):
|
||||
q.close()
|
||||
}
|
||||
|
||||
return q.sender.ResolveUDP(data)
|
||||
return q.PacketSender.DoSniff(metadata)
|
||||
}
|
||||
|
||||
// Close stop the Process loop
|
||||
func (q *quicPacketSender) Close() {
|
||||
q.sender.Close()
|
||||
q.PacketSender.Close()
|
||||
q.close()
|
||||
}
|
||||
|
||||
@@ -433,7 +432,7 @@ func (q *quicPacketSender) tryAssemble() error {
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
q.result = *domain
|
||||
q.result = domain
|
||||
q.closeLocked()
|
||||
q.lock.Unlock()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type fakeSender struct {
|
||||
resultCh chan *constant.Metadata
|
||||
constant.PacketSender
|
||||
}
|
||||
|
||||
var _ constant.PacketSender = (*fakeSender)(nil)
|
||||
@@ -22,18 +22,7 @@ func (e *fakeSender) Send(packet constant.PacketAdapter) {
|
||||
packet.Drop()
|
||||
}
|
||||
|
||||
func (e *fakeSender) Process(constant.PacketConn, constant.WriteBackProxy) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (e *fakeSender) ResolveUDP(metadata *constant.Metadata) error {
|
||||
e.resultCh <- metadata
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *fakeSender) Close() {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (e *fakeSender) DoSniff(metadata *constant.Metadata) error { return nil }
|
||||
|
||||
type fakeUDPPacket struct {
|
||||
data []byte
|
||||
@@ -78,23 +67,28 @@ func asPacket(data string) constant.PacketAdapter {
|
||||
return pktAdp
|
||||
}
|
||||
|
||||
func testQuicSniffer(data []string, async bool) (string, error) {
|
||||
const fakeHost = "fake.host.com"
|
||||
|
||||
func testQuicSniffer(data []string, async bool) (string, string, error) {
|
||||
q, err := NewQuicSniffer(SnifferConfig{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
resultCh := make(chan *constant.Metadata, 1)
|
||||
emptySender := &fakeSender{resultCh: resultCh}
|
||||
emptySender := &fakeSender{}
|
||||
|
||||
sender := q.WrapperSender(emptySender, true)
|
||||
sender := q.WrapperSender(emptySender, func(metadata *constant.Metadata, host string) {
|
||||
replaceDomain(metadata, host, true)
|
||||
})
|
||||
|
||||
go func() {
|
||||
meta := constant.Metadata{}
|
||||
err = sender.ResolveUDP(&meta)
|
||||
meta := constant.Metadata{Host: fakeHost}
|
||||
err := sender.DoSniff(&meta)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
resultCh <- &meta
|
||||
}()
|
||||
|
||||
for _, d := range data {
|
||||
@@ -106,14 +100,15 @@ func testQuicSniffer(data []string, async bool) (string, error) {
|
||||
}
|
||||
|
||||
meta := <-resultCh
|
||||
return meta.SniffHost, nil
|
||||
return meta.SniffHost, meta.Host, nil
|
||||
}
|
||||
|
||||
func TestQuicHeaders(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
input []string
|
||||
domain string
|
||||
input []string
|
||||
domain string
|
||||
invalid bool
|
||||
}{
|
||||
//Normal domain quic sniff
|
||||
{
|
||||
@@ -171,16 +166,31 @@ func TestQuicHeaders(t *testing.T) {
|
||||
},
|
||||
domain: "www.google.com",
|
||||
},
|
||||
// invalid packet
|
||||
{
|
||||
input: []string{"00000000000000000000"},
|
||||
invalid: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range cases {
|
||||
data, err := testQuicSniffer(test.input, true)
|
||||
data, host, err := testQuicSniffer(test.input, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.domain, data)
|
||||
if test.invalid {
|
||||
assert.Equal(t, fakeHost, host)
|
||||
} else {
|
||||
assert.Equal(t, test.domain, host)
|
||||
}
|
||||
|
||||
data, err = testQuicSniffer(test.input, false)
|
||||
data, host, err = testQuicSniffer(test.input, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.domain, data)
|
||||
if test.invalid {
|
||||
assert.Equal(t, fakeHost, host)
|
||||
} else {
|
||||
assert.Equal(t, test.domain, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
70
component/tls/httpserver.go
Normal file
70
component/tls/httpserver.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func extractTlsHandshakeTimeoutFromServer(s *http.Server) time.Duration {
|
||||
var ret time.Duration
|
||||
for _, v := range [...]time.Duration{
|
||||
s.ReadHeaderTimeout,
|
||||
s.ReadTimeout,
|
||||
s.WriteTimeout,
|
||||
} {
|
||||
if v <= 0 {
|
||||
continue
|
||||
}
|
||||
if ret == 0 || v < ret {
|
||||
ret = v
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewListenerForHttps returns a net.Listener for (*http.Server).Serve()
|
||||
// the "func (c *conn) serve(ctx context.Context)" in http\server.go
|
||||
// only do tls handshake and check NegotiatedProtocol with std's *tls.Conn
|
||||
// so we do the same logic to let http2 (not h2c) work fine
|
||||
func NewListenerForHttps(l net.Listener, httpServer *http.Server, tlsConfig *Config) net.Listener {
|
||||
http2Server := &http2.Server{}
|
||||
_ = http2.ConfigureServer(httpServer, http2Server)
|
||||
return N.NewHandleContextListener(context.Background(), l, func(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||
c := Server(conn, tlsConfig)
|
||||
|
||||
tlsTO := extractTlsHandshakeTimeoutFromServer(httpServer)
|
||||
if tlsTO > 0 {
|
||||
dl := time.Now().Add(tlsTO)
|
||||
_ = conn.SetReadDeadline(dl)
|
||||
_ = conn.SetWriteDeadline(dl)
|
||||
}
|
||||
|
||||
err := c.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Restore Conn-level deadlines.
|
||||
if tlsTO > 0 {
|
||||
_ = conn.SetReadDeadline(time.Time{})
|
||||
_ = conn.SetWriteDeadline(time.Time{})
|
||||
}
|
||||
|
||||
if c.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS {
|
||||
http2Server.ServeConn(c, &http2.ServeConnOpts{BaseConfig: httpServer})
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
return c, nil
|
||||
}, func(a any) {
|
||||
stack := debug.Stack()
|
||||
log.Errorln("https server panic: %s\n%s", a, stack)
|
||||
})
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
@@ -35,11 +34,12 @@ const RealityMaxShortIDLen = 8
|
||||
type RealityConfig struct {
|
||||
PublicKey *ecdh.PublicKey
|
||||
ShortID [RealityMaxShortIDLen]byte
|
||||
|
||||
SupportX25519MLKEM768 bool
|
||||
}
|
||||
|
||||
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 *Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||
for retry := 0; ; retry++ {
|
||||
verifier := &realityVerifier{
|
||||
serverName: tlsConfig.ServerName,
|
||||
}
|
||||
@@ -49,39 +49,18 @@ func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string
|
||||
SessionTicketsDisabled: true,
|
||||
VerifyPeerCertificate: verifier.VerifyPeerCertificate,
|
||||
}
|
||||
clientID := utls.ClientHelloID{
|
||||
Client: fingerprint.Client,
|
||||
Version: fingerprint.Version,
|
||||
Seed: fingerprint.Seed,
|
||||
|
||||
if !realityConfig.SupportX25519MLKEM768 && fingerprint == HelloChrome_Auto {
|
||||
fingerprint = HelloChrome_120 // old reality server doesn't work with X25519MLKEM768
|
||||
}
|
||||
uConn := utls.UClient(conn, uConfig, clientID)
|
||||
|
||||
uConn := utls.UClient(conn, uConfig, fingerprint)
|
||||
verifier.UConn = uConn
|
||||
err := uConn.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ------for X25519MLKEM768 does not work properly with reality-------
|
||||
// Iterate over extensions and check
|
||||
for _, extension := range uConn.Extensions {
|
||||
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
|
||||
ce.Curves = slices.DeleteFunc(ce.Curves, func(curveID utls.CurveID) bool {
|
||||
return curveID == utls.X25519MLKEM768
|
||||
})
|
||||
}
|
||||
if ks, ok := extension.(*utls.KeyShareExtension); ok {
|
||||
ks.KeyShares = slices.DeleteFunc(ks.KeyShares, func(share utls.KeyShare) bool {
|
||||
return share.Group == utls.X25519MLKEM768
|
||||
})
|
||||
}
|
||||
}
|
||||
// Rebuild the client hello
|
||||
err = uConn.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
hello := uConn.HandshakeState.Hello
|
||||
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
||||
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
|
||||
@@ -145,13 +124,12 @@ func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string
|
||||
log.Debugln("REALITY Authentication: %v, AEAD: %T", verifier.verified, aeadCipher)
|
||||
|
||||
if !verifier.verified {
|
||||
go realityClientFallback(uConn, uConfig.ServerName, clientID)
|
||||
go realityClientFallback(uConn, uConfig.ServerName, fingerprint)
|
||||
return nil, errors.New("REALITY authentication failed")
|
||||
}
|
||||
|
||||
return uConn, nil
|
||||
}
|
||||
return nil, errors.New("unknown uTLS fingerprint")
|
||||
}
|
||||
|
||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||
|
||||
@@ -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,53 @@ import (
|
||||
"github.com/mroth/weightedrand/v2"
|
||||
)
|
||||
|
||||
type Conn = utls.Conn
|
||||
type UConn = utls.UConn
|
||||
type UClientHelloID = utls.ClientHelloID
|
||||
|
||||
const VersionTLS12 = utls.VersionTLS12
|
||||
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" {
|
||||
func Server(c net.Conn, config *utls.Config) *Conn {
|
||||
return utls.Server(c, config)
|
||||
}
|
||||
|
||||
func NewListener(inner net.Listener, config *Config) net.Listener {
|
||||
return utls.NewListener(inner, config)
|
||||
}
|
||||
|
||||
func GetFingerprint(clientFingerprint string) (UClientHelloID, bool) {
|
||||
if len(clientFingerprint) == 0 {
|
||||
clientFingerprint = globalFingerprint
|
||||
}
|
||||
if len(clientFingerprint) == 0 || clientFingerprint == "none" {
|
||||
return UClientHelloID{}, false
|
||||
}
|
||||
|
||||
if initRandomFingerprint.ClientHelloID == nil {
|
||||
initRandomFingerprint, _ = RollFingerprint()
|
||||
if clientFingerprint == "random" {
|
||||
fingerprint := randomFingerprint()
|
||||
log.Debugln("use initial random HelloID:%s", fingerprint.Client)
|
||||
return fingerprint, true
|
||||
}
|
||||
|
||||
if ClientFingerprint == "random" {
|
||||
log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client)
|
||||
return initRandomFingerprint, 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 +67,34 @@ func RollFingerprint() (UClientHelloID, bool) {
|
||||
)
|
||||
initClient := chooser.Pick()
|
||||
log.Debugln("initial random HelloID:%s", initClient)
|
||||
fingerprint, ok := Fingerprints[initClient]
|
||||
return fingerprint, ok
|
||||
}
|
||||
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": {nil},
|
||||
"randomized": {nil},
|
||||
var HelloChrome_Auto = utls.HelloChrome_Auto
|
||||
var HelloChrome_120 = utls.HelloChrome_120 // special fingerprint for some old protocols doesn't work with HelloChrome_Auto
|
||||
|
||||
var fingerprints = map[string]UClientHelloID{
|
||||
"chrome": utls.HelloChrome_Auto,
|
||||
"firefox": utls.HelloFirefox_Auto,
|
||||
"safari": utls.HelloSafari_Auto,
|
||||
"ios": utls.HelloIOS_Auto,
|
||||
"android": utls.HelloAndroid_11_OkHttp,
|
||||
"edge": utls.HelloEdge_Auto,
|
||||
"360": utls.Hello360_Auto,
|
||||
"qq": utls.HelloQQ_Auto,
|
||||
"random": {},
|
||||
|
||||
// deprecated fingerprints should not be used
|
||||
"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,
|
||||
"randomized": utls.HelloRandomized,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -88,10 +104,12 @@ 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 {
|
||||
type Certificate = utls.Certificate
|
||||
|
||||
func UCertificate(it tls.Certificate) utls.Certificate {
|
||||
return utls.Certificate{
|
||||
Certificate: it.Certificate,
|
||||
PrivateKey: it.PrivateKey,
|
||||
@@ -104,11 +122,15 @@ func UCertificates(it tls.Certificate) utls.Certificate {
|
||||
}
|
||||
}
|
||||
|
||||
type EncryptedClientHelloKey = utls.EncryptedClientHelloKey
|
||||
|
||||
type Config = utls.Config
|
||||
|
||||
func UConfig(config *tls.Config) *utls.Config {
|
||||
return &utls.Config{
|
||||
Rand: config.Rand,
|
||||
Time: config.Time,
|
||||
Certificates: utils.Map(config.Certificates, UCertificates),
|
||||
Certificates: utils.Map(config.Certificates, UCertificate),
|
||||
VerifyPeerCertificate: config.VerifyPeerCertificate,
|
||||
RootCAs: config.RootCAs,
|
||||
NextProtos: config.NextProtos,
|
||||
@@ -152,14 +174,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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -3,6 +3,7 @@ package updater
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -32,6 +33,17 @@ const (
|
||||
typeTarGzip
|
||||
)
|
||||
|
||||
func (t compressionType) String() string {
|
||||
switch t {
|
||||
case typeZip:
|
||||
return "zip"
|
||||
case typeTarGzip:
|
||||
return "tar.gz"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
var DefaultUiUpdater = &UIUpdater{}
|
||||
|
||||
func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater {
|
||||
@@ -99,48 +111,38 @@ func detectFileType(data []byte) compressionType {
|
||||
}
|
||||
|
||||
func (u *UIUpdater) downloadUI() error {
|
||||
err := u.prepareUIPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("prepare UI path failed: %w", err)
|
||||
}
|
||||
|
||||
data, err := downloadForBytes(u.externalUIURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't download file: %w", err)
|
||||
}
|
||||
|
||||
fileType := detectFileType(data)
|
||||
if fileType == typeUnknown {
|
||||
return fmt.Errorf("unknown or unsupported file type")
|
||||
tmpDir := C.Path.Resolve("downloadUI.tmp")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
os.RemoveAll(tmpDir) // cleanup tmp dir before extract
|
||||
log.Debugln("extractedFolder: %s", tmpDir)
|
||||
err = extract(data, tmpDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't extract compressed file: %w", err)
|
||||
}
|
||||
|
||||
ext := ".zip"
|
||||
if fileType == typeTarGzip {
|
||||
ext = ".tgz"
|
||||
}
|
||||
|
||||
saved := path.Join(C.Path.HomeDir(), "download"+ext)
|
||||
log.Debugln("compression Type: %s", ext)
|
||||
if err = saveFile(data, saved); err != nil {
|
||||
return fmt.Errorf("can't save compressed file: %w", err)
|
||||
}
|
||||
defer os.Remove(saved)
|
||||
|
||||
err = cleanup(u.externalUIPath)
|
||||
log.Debugln("cleanupFolder: %s", u.externalUIPath)
|
||||
err = cleanup(u.externalUIPath) // cleanup files in dir don't remove dir itself
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return fmt.Errorf("cleanup exist file error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
extractedFolder, err := extract(saved, C.Path.HomeDir())
|
||||
err = u.prepareUIPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't extract compressed file: %w", err)
|
||||
return fmt.Errorf("prepare UI path failed: %w", err)
|
||||
}
|
||||
|
||||
err = os.Rename(extractedFolder, u.externalUIPath)
|
||||
log.Debugln("moveFolder from %s to %s", tmpDir, u.externalUIPath)
|
||||
err = moveDir(tmpDir, u.externalUIPath) // move files from tmp to target
|
||||
if err != nil {
|
||||
return fmt.Errorf("rename UI folder failed: %w", err)
|
||||
return fmt.Errorf("move UI folder failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -155,228 +157,109 @@ func (u *UIUpdater) prepareUIPath() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unzip(src, dest string) (string, error) {
|
||||
r, err := zip.OpenReader(src)
|
||||
func unzip(data []byte, dest string) error {
|
||||
r, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
// check whether or not only exists singleRoot dir
|
||||
rootDir := ""
|
||||
isSingleRoot := true
|
||||
rootItemCount := 0
|
||||
for _, f := range r.File {
|
||||
parts := strings.Split(strings.Trim(f.Name, "/"), "/")
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
isDir := strings.HasSuffix(f.Name, "/")
|
||||
if !isDir {
|
||||
isSingleRoot = false
|
||||
break
|
||||
}
|
||||
|
||||
if rootDir == "" {
|
||||
rootDir = parts[0]
|
||||
}
|
||||
rootItemCount++
|
||||
}
|
||||
}
|
||||
|
||||
if rootItemCount != 1 {
|
||||
isSingleRoot = false
|
||||
}
|
||||
|
||||
// build the dir of extraction
|
||||
var extractedFolder string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
// if the singleRoot, use it directly
|
||||
log.Debugln("Match the singleRoot")
|
||||
extractedFolder = filepath.Join(dest, rootDir)
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
} else {
|
||||
log.Debugln("Match the multiRoot")
|
||||
// or put the files/dirs into new dir
|
||||
baseName := filepath.Base(src)
|
||||
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
||||
extractedFolder = filepath.Join(dest, baseName)
|
||||
|
||||
for i := 1; ; i++ {
|
||||
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
|
||||
}
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
}
|
||||
|
||||
for _, f := range r.File {
|
||||
var fpath string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
fpath = filepath.Join(dest, f.Name)
|
||||
} else {
|
||||
fpath = filepath.Join(extractedFolder, f.Name)
|
||||
}
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
|
||||
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||
if !inDest(fpath, dest) {
|
||||
return fmt.Errorf("invalid file path: %s", fpath)
|
||||
}
|
||||
if f.FileInfo().IsDir() {
|
||||
info := f.FileInfo()
|
||||
if info.IsDir() {
|
||||
os.MkdirAll(fpath, os.ModePerm)
|
||||
continue
|
||||
}
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
continue // disallow symlink
|
||||
}
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
rc, err := f.Open()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(outFile, rc)
|
||||
outFile.Close()
|
||||
rc.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
}
|
||||
return extractedFolder, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func untgz(src, dest string) (string, error) {
|
||||
file, err := os.Open(src)
|
||||
func untgz(data []byte, dest string) error {
|
||||
gzr, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
rootDir := ""
|
||||
isSingleRoot := true
|
||||
rootItemCount := 0
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parts := strings.Split(cleanTarPath(header.Name), string(os.PathSeparator))
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
isDir := header.Typeflag == tar.TypeDir
|
||||
if !isDir {
|
||||
isSingleRoot = false
|
||||
break
|
||||
}
|
||||
|
||||
if rootDir == "" {
|
||||
rootDir = parts[0]
|
||||
}
|
||||
rootItemCount++
|
||||
}
|
||||
}
|
||||
|
||||
if rootItemCount != 1 {
|
||||
isSingleRoot = false
|
||||
}
|
||||
|
||||
file.Seek(0, 0)
|
||||
gzr, _ = gzip.NewReader(file)
|
||||
_ = gzr.Reset(bytes.NewReader(data))
|
||||
tr = tar.NewReader(gzr)
|
||||
|
||||
var extractedFolder string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
log.Debugln("Match the singleRoot")
|
||||
extractedFolder = filepath.Join(dest, rootDir)
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
} else {
|
||||
log.Debugln("Match the multiRoot")
|
||||
baseName := filepath.Base(src)
|
||||
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
||||
baseName = strings.TrimSuffix(baseName, ".tar")
|
||||
extractedFolder = filepath.Join(dest, baseName)
|
||||
|
||||
for i := 1; ; i++ {
|
||||
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
|
||||
break
|
||||
}
|
||||
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
|
||||
}
|
||||
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||
}
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
var fpath string
|
||||
if isSingleRoot && rootDir != "" {
|
||||
fpath = filepath.Join(dest, cleanTarPath(header.Name))
|
||||
} else {
|
||||
fpath = filepath.Join(extractedFolder, cleanTarPath(header.Name))
|
||||
}
|
||||
fpath := filepath.Join(dest, header.Name)
|
||||
|
||||
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||
if !inDest(fpath, dest) {
|
||||
return fmt.Errorf("invalid file path: %s", fpath)
|
||||
}
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
case tar.TypeReg:
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(outFile, tr); err != nil {
|
||||
outFile.Close()
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
outFile.Close()
|
||||
}
|
||||
}
|
||||
return extractedFolder, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func extract(src, dest string) (string, error) {
|
||||
srcLower := strings.ToLower(src)
|
||||
switch {
|
||||
case strings.HasSuffix(srcLower, ".tar.gz") ||
|
||||
strings.HasSuffix(srcLower, ".tgz"):
|
||||
return untgz(src, dest)
|
||||
case strings.HasSuffix(srcLower, ".zip"):
|
||||
return unzip(src, dest)
|
||||
func extract(data []byte, dest string) error {
|
||||
fileType := detectFileType(data)
|
||||
log.Debugln("compression Type: %s", fileType)
|
||||
switch fileType {
|
||||
case typeZip:
|
||||
return unzip(data, dest)
|
||||
case typeTarGzip:
|
||||
return untgz(data, dest)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported file format: %s", src)
|
||||
return fmt.Errorf("unknown or unsupported file type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,22 +281,49 @@ func cleanTarPath(path string) string {
|
||||
}
|
||||
|
||||
func cleanup(root string) error {
|
||||
if _, err := os.Stat(root); os.IsNotExist(err) {
|
||||
return nil
|
||||
dirEntryList, err := os.ReadDir(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
|
||||
for _, dirEntry := range dirEntryList {
|
||||
err = os.RemoveAll(filepath.Join(root, dirEntry.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func moveDir(src string, dst string) error {
|
||||
dirEntryList, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dirEntryList) == 1 && dirEntryList[0].IsDir() {
|
||||
src = filepath.Join(src, dirEntryList[0].Name())
|
||||
log.Debugln("match the singleRoot: %s", src)
|
||||
dirEntryList, err = os.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, dirEntry := range dirEntryList {
|
||||
err = os.Rename(filepath.Join(src, dirEntry.Name()), filepath.Join(dst, dirEntry.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func inDest(fpath, dest string) bool {
|
||||
if rel, err := filepath.Rel(dest, fpath); err == nil {
|
||||
if filepath.IsLocal(rel) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
_ "unsafe"
|
||||
@@ -174,6 +175,7 @@ type Profile struct {
|
||||
type TLS struct {
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
EchKey string
|
||||
CustomTrustCert []string
|
||||
}
|
||||
|
||||
@@ -360,6 +362,7 @@ type RawSniffingConfig struct {
|
||||
type RawTLS struct {
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||
CustomTrustCert []string `yaml:"custom-certifactes" json:"custom-certifactes"`
|
||||
}
|
||||
|
||||
@@ -754,6 +757,12 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||
}
|
||||
|
||||
func parseController(cfg *RawConfig) (*Controller, error) {
|
||||
if path := cfg.ExternalUI; path != "" && !C.Path.IsSafePath(path) {
|
||||
return nil, C.Path.ErrNotSafePath(path)
|
||||
}
|
||||
if uiName := cfg.ExternalUIName; uiName != "" && !filepath.IsLocal(uiName) {
|
||||
return nil, fmt.Errorf("external UI name is not local: %s", uiName)
|
||||
}
|
||||
return &Controller{
|
||||
ExternalController: cfg.ExternalController,
|
||||
ExternalUI: cfg.ExternalUI,
|
||||
@@ -811,6 +820,7 @@ func parseTLS(cfg *RawConfig) (*TLS, error) {
|
||||
return &TLS{
|
||||
Certificate: cfg.TLS.Certificate,
|
||||
PrivateKey: cfg.TLS.PrivateKey,
|
||||
EchKey: cfg.TLS.EchKey,
|
||||
CustomTrustCert: cfg.TLS.CustomTrustCert,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -92,8 +92,7 @@ type Conn interface {
|
||||
type PacketConn interface {
|
||||
N.EnhancePacketConn
|
||||
Connection
|
||||
// Deprecate WriteWithMetadata because of remote resolve DNS cause TURN failed
|
||||
// WriteWithMetadata(p []byte, metadata *Metadata) (n int, err error)
|
||||
ResolveUDP(ctx context.Context, metadata *Metadata) error
|
||||
}
|
||||
|
||||
type Dialer interface {
|
||||
@@ -134,8 +133,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
|
||||
@@ -319,10 +318,15 @@ type PacketSender interface {
|
||||
Send(PacketAdapter)
|
||||
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy
|
||||
Process(PacketConn, WriteBackProxy)
|
||||
// ResolveUDP do a local resolve UDP dns blocking if metadata is not resolved
|
||||
ResolveUDP(*Metadata) error
|
||||
// Close stop the Process loop
|
||||
Close()
|
||||
// DoSniff will blocking after sniffer work done
|
||||
DoSniff(*Metadata) error
|
||||
// AddMapping add a destination NAT record
|
||||
AddMapping(originMetadata *Metadata, metadata *Metadata)
|
||||
// RestoreReadFrom restore destination NAT for ReadFrom
|
||||
// the implement must ensure returned netip.Add is valid (or just return input addr)
|
||||
RestoreReadFrom(addr netip.Addr) netip.Addr
|
||||
}
|
||||
|
||||
type NatTable interface {
|
||||
|
||||
@@ -261,6 +261,11 @@ func (m *Metadata) Pure() *Metadata {
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Metadata) Clone() *Metadata {
|
||||
copyM := *m
|
||||
return ©M
|
||||
}
|
||||
|
||||
func (m *Metadata) AddrPort() netip.AddrPort {
|
||||
return netip.AddrPortFrom(m.DstIP.Unmap(), m.DstPort)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
P "path"
|
||||
"path/filepath"
|
||||
@@ -37,13 +38,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 +83,37 @@ 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
|
||||
for _, safePath := range p.SafePaths() {
|
||||
if rel, err := filepath.Rel(safePath, path); err == nil {
|
||||
if filepath.IsLocal(rel) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return !strings.Contains(rel, "..")
|
||||
func (p *path) SafePaths() []string {
|
||||
return append([]string{p.homeDir}, p.safePaths...) // add homedir to safePaths
|
||||
}
|
||||
|
||||
func (p *path) ErrNotSafePath(path string) error {
|
||||
return ErrNotSafePath{Path: path, SafePaths: p.SafePaths()}
|
||||
}
|
||||
|
||||
type ErrNotSafePath struct {
|
||||
Path string
|
||||
SafePaths []string
|
||||
}
|
||||
|
||||
func (e ErrNotSafePath) Error() string {
|
||||
return fmt.Sprintf("path is not subpath of home directory or SAFE_PATHS: %s \n allowed paths: %s", e.Path, e.SafePaths)
|
||||
}
|
||||
|
||||
func (p *path) GetPathByHash(prefix, name string) string {
|
||||
|
||||
41
constant/path_test.go
Normal file
41
constant/path_test.go
Normal file
@@ -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"))
|
||||
|
||||
}
|
||||
@@ -10,8 +10,10 @@ type Sniffer interface {
|
||||
SupportPort(port uint16) bool
|
||||
}
|
||||
|
||||
type ReplaceDomain func(metadata *constant.Metadata, host string)
|
||||
|
||||
type MultiPacketSniffer interface {
|
||||
WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender
|
||||
WrapperSender(packetSender constant.PacketSender, replaceDomain ReplaceDomain) constant.PacketSender
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -77,7 +77,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
msg, _, err := c.Client.ExchangeWithConn(m, dConn)
|
||||
|
||||
// Resolvers MUST resend queries over TCP if they receive a truncated UDP response (with TC=1 set)!
|
||||
if msg != nil && msg.Truncated && c.Client.Net == "" {
|
||||
if msg != nil && msg.Truncated && network == "udp" {
|
||||
tcpClient := *c.Client // copy a client
|
||||
tcpClient.Net = "tcp"
|
||||
network = "tcp"
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
16
dns/doh.go
16
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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -127,6 +127,28 @@ func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *Resolver) ResolveECH(ctx context.Context, host string) ([]byte, error) {
|
||||
query := &D.Msg{}
|
||||
query.SetQuestion(D.Fqdn(host), D.TypeHTTPS)
|
||||
|
||||
msg, err := r.ExchangeContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rr := range msg.Answer {
|
||||
switch resource := rr.(type) {
|
||||
case *D.HTTPS:
|
||||
for _, value := range resource.Value {
|
||||
if echConfig, ok := value.(*D.SVCBECHConfig); ok {
|
||||
return echConfig.ECH, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, errors.New("no ECH config found in DNS records")
|
||||
}
|
||||
|
||||
// ExchangeContext a batch of dns request with context.Context, and it use cache
|
||||
func (r *Resolver) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
if len(m.Question) == 0 {
|
||||
@@ -326,7 +348,8 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
|
||||
func (r *Resolver) lookupIP(ctx context.Context, host string, dnsType uint16) (ips []netip.Addr, err error) {
|
||||
ip, err := netip.ParseAddr(host)
|
||||
if err == nil {
|
||||
isIPv4 := ip.Is4() || ip.Is4In6()
|
||||
ip = ip.Unmap()
|
||||
isIPv4 := ip.Is4()
|
||||
if dnsType == D.TypeAAAA && !isIPv4 {
|
||||
return []netip.Addr{ip}, nil
|
||||
} else if dnsType == D.TypeA && isIPv4 {
|
||||
@@ -393,7 +416,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 +429,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) &&
|
||||
|
||||
@@ -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
|
||||
|
||||
124
docs/config.yaml
124
docs/config.yaml
@@ -48,6 +48,13 @@ ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS
|
||||
tls:
|
||||
certificate: string # 证书 PEM 格式,或者 证书的路径
|
||||
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
custom-certifactes:
|
||||
- |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
@@ -427,6 +434,10 @@ proxies: # socks5
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
@@ -448,6 +459,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
|
||||
@@ -526,6 +538,10 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# servername: example.com # priority over wss host
|
||||
# network: ws
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
# ws-opts:
|
||||
# path: /path
|
||||
# headers:
|
||||
@@ -598,6 +614,10 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
|
||||
- name: "vless-vision"
|
||||
type: vless
|
||||
@@ -625,6 +645,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: xxx
|
||||
short-id: xxx # optional
|
||||
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
|
||||
client-fingerprint: chrome # cannot be empty
|
||||
|
||||
- name: "vless-reality-grpc"
|
||||
@@ -644,6 +665,7 @@ proxies: # socks5
|
||||
reality-opts:
|
||||
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
|
||||
short-id: 10f897e26c4b9478
|
||||
support-x25519mlkem768: false # 如果服务端支持可手动设置为true
|
||||
|
||||
- name: "vless-ws"
|
||||
type: vless
|
||||
@@ -682,6 +704,10 @@ proxies: # socks5
|
||||
# enabled: false
|
||||
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
||||
# password: "example"
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
|
||||
- name: trojan-grpc
|
||||
server: server
|
||||
@@ -739,6 +765,10 @@ proxies: # socks5
|
||||
up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||
down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||
# sni: server.com
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
# skip-cert-verify: false
|
||||
# recv-window-conn: 12582912
|
||||
# recv-window: 52428800
|
||||
@@ -762,6 +792,10 @@ proxies: # socks5
|
||||
# obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander
|
||||
# obfs-password: yourpassword
|
||||
# sni: server.com
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
# skip-cert-verify: false
|
||||
# fingerprint: xxxx
|
||||
# alpn:
|
||||
@@ -837,6 +871,10 @@ proxies: # socks5
|
||||
# skip-cert-verify: true
|
||||
# max-open-streams: 20 # default 100, too many open streams may hurt performance
|
||||
# sni: example.com
|
||||
# ech-opts:
|
||||
# enable: true # 必须手动开启
|
||||
# # 如果config为空则通过dns解析,不为空则通过该值指定,格式为经过base64编码的ech参数(dig +short TYPE65 tls-ech.dev)
|
||||
# config: AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA
|
||||
#
|
||||
# meta 和 sing-box 私有扩展,将 ss-uot 用于 udp 中继,开启此选项后 udp-relay-mode 将失效
|
||||
# 警告,与原版 tuic 不兼容!!!
|
||||
@@ -905,6 +943,12 @@ proxies: # socks5
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
- name: "dns-out"
|
||||
type: dns
|
||||
|
||||
# 配置指定 interface-name 和 fwmark 的 DIRECT
|
||||
- name: en1-direct
|
||||
type: direct
|
||||
interface-name: en1
|
||||
routing-mark: 6667
|
||||
proxy-groups:
|
||||
# 代理链,目前 relay 可以支持 udp 的只有 vmess/vless/trojan/ss/ssr/tuic
|
||||
# wireguard 目前不支持在 relay 中使用,请使用 proxy 中的 dialer-proxy 配置项
|
||||
@@ -961,14 +1005,6 @@ proxy-groups:
|
||||
- vmess1
|
||||
- auto
|
||||
|
||||
# 配置指定 interface-name 和 fwmark 的 DIRECT
|
||||
- name: en1
|
||||
type: select
|
||||
interface-name: en1
|
||||
routing-mark: 6667
|
||||
proxies:
|
||||
- DIRECT
|
||||
|
||||
- name: UseProvider
|
||||
type: select
|
||||
filter: "HK|TW" # 正则表达式,过滤 provider1 中节点名包含 HK 或 TW
|
||||
@@ -1128,6 +1164,13 @@ listeners:
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
|
||||
- name: http-in-1
|
||||
type: http
|
||||
@@ -1141,6 +1184,13 @@ listeners:
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
|
||||
- name: mixed-in-1
|
||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||
@@ -1155,6 +1205,13 @@ listeners:
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
|
||||
- name: reidr-in-1
|
||||
type: redir
|
||||
@@ -1179,6 +1236,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
|
||||
@@ -1195,6 +1261,13 @@ listeners:
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||
# reality-config:
|
||||
# dest: test.com:443
|
||||
@@ -1217,6 +1290,13 @@ listeners:
|
||||
# 00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
# congestion-controller: bbr
|
||||
# max-idle-time: 15000
|
||||
# authentication-timeout: 1000
|
||||
@@ -1248,6 +1328,13 @@ listeners:
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||
reality-config:
|
||||
dest: test.com:443
|
||||
@@ -1268,6 +1355,13 @@ listeners:
|
||||
# "certificate" and "private-key" are required
|
||||
certificate: ./server.crt
|
||||
private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
|
||||
|
||||
- name: trojan-in-1
|
||||
@@ -1284,6 +1378,13 @@ listeners:
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
certificate: ./server.crt
|
||||
private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||
# reality-config:
|
||||
# dest: test.com:443
|
||||
@@ -1309,6 +1410,13 @@ listeners:
|
||||
00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
# 如果填写则开启ech(可由 mihomo generate ech-keypair <明文域名> 生成)
|
||||
# ech-key: |
|
||||
# -----BEGIN ECH KEYS-----
|
||||
# ACATwY30o/RKgD6hgeQxwrSiApLaCgU+HKh7B6SUrAHaDwBD/g0APwAAIAAgHjzK
|
||||
# madSJjYQIf9o1N5GXjkW4DEEeb17qMxHdwMdNnwADAABAAEAAQACAAEAAwAIdGVz
|
||||
# dC5jb20AAA==
|
||||
# -----END ECH KEYS-----
|
||||
## up 和 down 均不写或为 0 则使用 BBR 流控
|
||||
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||
|
||||
51
go.mod
51
go.mod
@@ -11,41 +11,41 @@ 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
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||
github.com/metacubex/bart v0.19.0
|
||||
github.com/metacubex/bart v0.20.5
|
||||
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.52.1-0.20250522021943-aef454b9e639
|
||||
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/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||
github.com/metacubex/utls v1.7.0-alpha.1
|
||||
github.com/metacubex/sing v0.5.3
|
||||
github.com/metacubex/sing-mux v0.3.2
|
||||
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
|
||||
github.com/metacubex/sing-shadowsocks v0.2.10
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.4
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c
|
||||
github.com/metacubex/sing-vmess v0.2.2
|
||||
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-20250516165257-e29c16ae41d4
|
||||
github.com/metacubex/utls v1.7.3
|
||||
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 // 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
|
||||
|
||||
93
go.sum
93
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 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
github.com/ebitengine/purego v0.8.3/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=
|
||||
@@ -97,40 +97,49 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||
github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY=
|
||||
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
|
||||
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
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.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
|
||||
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k=
|
||||
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/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/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
|
||||
github.com/metacubex/sing v0.5.3/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-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY=
|
||||
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.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
|
||||
github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
|
||||
github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
|
||||
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-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A=
|
||||
github.com/metacubex/utls v1.7.3/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=
|
||||
|
||||
@@ -113,12 +113,11 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
||||
tunnel.OnInnerLoading()
|
||||
|
||||
initInnerTcp()
|
||||
loadProxyProvider(cfg.Providers)
|
||||
loadProvider(cfg.Providers)
|
||||
updateProfile(cfg)
|
||||
loadRuleProvider(cfg.RuleProviders)
|
||||
loadProvider(cfg.RuleProviders)
|
||||
runtime.GC()
|
||||
tunnel.OnRunning()
|
||||
hcCompatibleProvider(cfg.Providers)
|
||||
updateUpdater(cfg)
|
||||
|
||||
resolver.ResetConnection()
|
||||
@@ -303,81 +302,41 @@ func updateRules(rules []C.Rule, subRules map[string][]C.Rule, ruleProviders map
|
||||
tunnel.UpdateRules(rules, subRules, ruleProviders)
|
||||
}
|
||||
|
||||
func loadProvider(pv provider.Provider) {
|
||||
if pv.VehicleType() == provider.Compatible {
|
||||
return
|
||||
} else {
|
||||
log.Infoln("Start initial provider %s", (pv).Name())
|
||||
}
|
||||
|
||||
if err := pv.Initial(); err != nil {
|
||||
switch pv.Type() {
|
||||
case provider.Proxy:
|
||||
{
|
||||
log.Errorln("initial proxy provider %s error: %v", (pv).Name(), err)
|
||||
}
|
||||
case provider.Rule:
|
||||
{
|
||||
log.Errorln("initial rule provider %s error: %v", (pv).Name(), err)
|
||||
}
|
||||
|
||||
func loadProvider[P provider.Provider](providers map[string]P) {
|
||||
load := func(pv P) {
|
||||
name := pv.Name()
|
||||
if pv.VehicleType() == provider.Compatible {
|
||||
log.Infoln("Start initial compatible provider %s", name)
|
||||
} else {
|
||||
log.Infoln("Start initial provider %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadRuleProvider(ruleProviders map[string]provider.RuleProvider) {
|
||||
wg := sync.WaitGroup{}
|
||||
ch := make(chan struct{}, concurrentCount)
|
||||
for _, ruleProvider := range ruleProviders {
|
||||
ruleProvider := ruleProvider
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
go func() {
|
||||
defer func() { <-ch; wg.Done() }()
|
||||
loadProvider(ruleProvider)
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func loadProxyProvider(proxyProviders map[string]provider.ProxyProvider) {
|
||||
// limit concurrent size
|
||||
wg := sync.WaitGroup{}
|
||||
ch := make(chan struct{}, concurrentCount)
|
||||
for _, proxyProvider := range proxyProviders {
|
||||
proxyProvider := proxyProvider
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
go func() {
|
||||
defer func() { <-ch; wg.Done() }()
|
||||
loadProvider(proxyProvider)
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
func hcCompatibleProvider(proxyProviders map[string]provider.ProxyProvider) {
|
||||
// limit concurrent size
|
||||
wg := sync.WaitGroup{}
|
||||
ch := make(chan struct{}, concurrentCount)
|
||||
for _, proxyProvider := range proxyProviders {
|
||||
proxyProvider := proxyProvider
|
||||
if proxyProvider.VehicleType() == provider.Compatible {
|
||||
log.Infoln("Start initial Compatible provider %s", proxyProvider.Name())
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
go func() {
|
||||
defer func() { <-ch; wg.Done() }()
|
||||
if err := proxyProvider.Initial(); err != nil {
|
||||
log.Errorln("initial Compatible provider %s error: %v", proxyProvider.Name(), err)
|
||||
if err := pv.Initial(); err != nil {
|
||||
switch pv.Type() {
|
||||
case provider.Proxy:
|
||||
{
|
||||
log.Errorln("initial proxy provider %s error: %v", name, err)
|
||||
}
|
||||
}()
|
||||
case provider.Rule:
|
||||
{
|
||||
log.Errorln("initial rule provider %s error: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
ch := make(chan struct{}, concurrentCount)
|
||||
for _, pv := range providers {
|
||||
pv := pv
|
||||
wg.Add(1)
|
||||
ch <- struct{}{}
|
||||
go func() {
|
||||
defer func() { <-ch; wg.Done() }()
|
||||
load(pv)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func updateSniffer(snifferConfig *sniffer.Config) {
|
||||
@@ -455,7 +414,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) {
|
||||
|
||||
@@ -57,6 +57,7 @@ func applyRoute(cfg *config.Config) {
|
||||
Secret: cfg.Controller.Secret,
|
||||
Certificate: cfg.TLS.Certificate,
|
||||
PrivateKey: cfg.TLS.PrivateKey,
|
||||
EchKey: cfg.TLS.EchKey,
|
||||
DohServer: cfg.Controller.ExternalDohServer,
|
||||
IsDebug: cfg.General.LogLevel == log.DEBUG,
|
||||
Cors: route.Cors{
|
||||
|
||||
@@ -371,13 +371,20 @@ func updateConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if req.Path == "" {
|
||||
if req.Path == "" { // default path unneeded any safe check
|
||||
req.Path = C.Path.Config()
|
||||
}
|
||||
if !filepath.IsAbs(req.Path) {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("path is not a absolute path"))
|
||||
return
|
||||
} else {
|
||||
if !filepath.IsAbs(req.Path) {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError("path is not a absolute path"))
|
||||
return
|
||||
}
|
||||
|
||||
if !C.Path.IsSafePath(req.Path) {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.JSON(w, r, newError(C.Path.ErrNotSafePath(req.Path).Error()))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
cfg, err = executor.ParseWithPath(req.Path)
|
||||
|
||||
@@ -3,7 +3,6 @@ package route
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -15,8 +14,10 @@ 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"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
@@ -62,6 +63,7 @@ type Config struct {
|
||||
Secret string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
EchKey string
|
||||
DohServer string
|
||||
IsDebug bool
|
||||
Cors Cors
|
||||
@@ -186,7 +188,7 @@ func startTLS(cfg *Config) {
|
||||
|
||||
// handle tlsAddr
|
||||
if len(cfg.TLSAddr) > 0 {
|
||||
c, err := CN.ParseCert(cfg.Certificate, cfg.PrivateKey, C.Path)
|
||||
cert, err := ca.LoadTLSKeyPair(cfg.Certificate, cfg.PrivateKey, C.Path)
|
||||
if err != nil {
|
||||
log.Errorln("External controller tls listen error: %s", err)
|
||||
return
|
||||
@@ -199,14 +201,22 @@ func startTLS(cfg *Config) {
|
||||
}
|
||||
|
||||
log.Infoln("RESTful API tls listening at: %s", l.Addr().String())
|
||||
tlsConfig := &tlsC.Config{}
|
||||
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
|
||||
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||
|
||||
if cfg.EchKey != "" {
|
||||
err = ech.LoadECHKey(cfg.EchKey, tlsConfig, C.Path)
|
||||
if err != nil {
|
||||
log.Errorln("External controller tls serve error: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
server := &http.Server{
|
||||
Handler: router(cfg.IsDebug, cfg.Secret, cfg.DohServer, cfg.Cors),
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{c},
|
||||
},
|
||||
}
|
||||
tlsServer = server
|
||||
if err = server.ServeTLS(l, "", ""); err != nil {
|
||||
if err = server.Serve(tlsC.NewListenerForHttps(l, server, tlsConfig)); err != nil {
|
||||
log.Errorln("External controller tls serve error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package anytls
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
@@ -12,23 +11,25 @@ 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"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
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"
|
||||
"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 {
|
||||
closed bool
|
||||
config LC.AnyTLSServer
|
||||
listeners []net.Listener
|
||||
tlsConfig *tls.Config
|
||||
tlsConfig *tlsC.Config
|
||||
userMap map[[32]byte]string
|
||||
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||
}
|
||||
@@ -41,13 +42,20 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{}
|
||||
tlsConfig := &tlsC.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
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
tlsConfig.Certificates = []tlsC.Certificate{tlsC.UCertificate(cert)}
|
||||
|
||||
if config.EchKey != "" {
|
||||
err = ech.LoadECHKey(config.EchKey, tlsConfig, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sl = &Listener{
|
||||
@@ -87,7 +95,7 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
return nil, err
|
||||
}
|
||||
if len(tlsConfig.Certificates) > 0 {
|
||||
l = tls.NewListener(l, tlsConfig)
|
||||
l = tlsC.NewListener(l, tlsConfig)
|
||||
} else {
|
||||
return nil, errors.New("disallow using AnyTLS without certificates config")
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ type AnyTLSServer struct {
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
EchKey string `yaml:"ech-key" json:"ech-key"`
|
||||
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -12,5 +12,6 @@ type AuthServer struct {
|
||||
AuthStore auth.AuthStore
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
EchKey string
|
||||
RealityConfig reality.Config
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type Hysteria2Server struct {
|
||||
ObfsPassword string `yaml:"obfs-password" json:"obfs-password,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
EchKey string `yaml:"ech-key" json:"ech-key,omitempty"`
|
||||
MaxIdleTime int `yaml:"max-idle-time" json:"max-idle-time,omitempty"`
|
||||
ALPN []string `yaml:"alpn" json:"alpn,omitempty"`
|
||||
Up string `yaml:"up" json:"up,omitempty"`
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user