Compare commits

..

23 Commits

Author SHA1 Message Date
b9ffc82e53 Merge branch 'Beta' into Meta 2023-01-12 01:33:56 +08:00
78aaea6a45 Merge branch 'Alpha' into Beta 2023-01-12 01:33:16 +08:00
3645fbf161 Merge pull request #327 from Rasphino/Meta
Update flake.nix hash
2023-01-08 00:29:29 +08:00
a1d0f22132 fix: update flake.nix hash 2023-01-07 23:38:32 +08:00
fa73b0f4bf Merge remote-tracking branch 'origin/Beta' into Meta 2023-01-01 19:41:36 +08:00
3b76a8b839 Merge remote-tracking branch 'origin/Alpha' into Beta 2023-01-01 19:40:36 +08:00
667f42dcdc Merge pull request #282 from tdjnodj/Meta
Update README.md
2022-12-03 17:23:51 +08:00
dfbe09860f Update README.md 2022-12-03 17:17:08 +08:00
9e20f9c26a chore: update dependencies 2022-11-28 20:33:10 +08:00
f968d0cb82 chore: update github action 2022-11-26 20:16:12 +08:00
2ad84f4379 Merge branch 'Beta' into Meta 2022-11-02 18:08:22 +08:00
c7aa16426f Merge branch 'Alpha' into Beta 2022-11-02 18:07:29 +08:00
5987f8e3b5 Merge branch 'Beta' into Meta 2022-08-29 13:08:29 +08:00
3a8eb72de2 Merge branch 'Alpha' into Beta 2022-08-29 13:08:22 +08:00
33abbdfd24 Merge pull request #174 from MetaCubeX/Alpha
Alpha
2022-08-29 11:24:07 +08:00
0703d6cbff Merge pull request #173 from MetaCubeX/Alpha
Alpha
2022-08-29 11:22:14 +08:00
10d2d14938 Merge branch 'Beta' into Meta
# Conflicts:
#	rules/provider/classical_strategy.go
2022-07-02 10:41:41 +08:00
691cf1d8d6 Merge pull request #94 from bash99/Meta
Update README.md
2022-06-15 19:15:51 +08:00
d1decb8e58 Update README.md
add permissions for systemctl services
clash-dashboard change to updated one
2022-06-15 14:00:05 +08:00
7d04904109 fix: leak dns when domain in hosts list 2022-06-11 18:51:26 +08:00
a5acd3aa97 refactor: clear linkname,reduce cycle dependencies,transport init geosite function 2022-06-11 18:51:22 +08:00
eea9a12560 fix: 规则匹配默认策略组返回错误 2022-06-09 14:18:35 +08:00
0a4570b55c fix: group filter touch provider 2022-06-09 14:18:29 +08:00
238 changed files with 2507 additions and 11770 deletions

26
.github/release.sh vendored
View File

@ -1,26 +0,0 @@
#!/bin/bash
FILENAMES=$(ls)
for FILENAME in $FILENAMES
do
if [[ ! ($FILENAME =~ ".exe" || $FILENAME =~ ".sh")]];then
gzip -S ".gz" $FILENAME
elif [[ $FILENAME =~ ".exe" ]];then
zip -m ${FILENAME%.*}.zip $FILENAME
else echo "skip $FILENAME"
fi
done
FILENAMES=$(ls)
for FILENAME in $FILENAMES
do
if [[ $FILENAME =~ ".zip" ]];then
echo "rename $FILENAME"
mv $FILENAME ${FILENAME%.*}-${VERSION}.zip
elif [[ $FILENAME =~ ".gz" ]];then
echo "rename $FILENAME"
mv $FILENAME ${FILENAME%.*}-${VERSION}.gz
else
echo "skip $FILENAME"
fi
done

35
.github/rename-cgo.sh vendored
View File

@ -1,35 +0,0 @@
#!/bin/bash
FILENAMES=$(ls)
for FILENAME in $FILENAMES
do
if [[ $FILENAME =~ "darwin-10.16-arm64" ]];then
echo "rename darwin-10.16-arm64 $FILENAME"
mv $FILENAME clash.meta-darwin-arm64-cgo
elif [[ $FILENAME =~ "darwin-10.16-amd64" ]];then
echo "rename darwin-10.16-amd64 $FILENAME"
mv $FILENAME clash.meta-darwin-amd64-cgo
elif [[ $FILENAME =~ "windows-4.0-386" ]];then
echo "rename windows 386 $FILENAME"
mv $FILENAME clash.meta-windows-386-cgo.exe
elif [[ $FILENAME =~ "windows-4.0-amd64" ]];then
echo "rename windows amd64 $FILENAME"
mv $FILENAME clash.meta-windows-amd64-cgo.exe
elif [[ $FILENAME =~ "clash.meta-linux-arm-5" ]];then
echo "rename clash.meta-linux-arm-5 $FILENAME"
mv $FILENAME clash.meta-linux-armv5-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-6" ]];then
echo "rename clash.meta-linux-arm-6 $FILENAME"
mv $FILENAME clash.meta-linux-armv6-cgo
elif [[ $FILENAME =~ "clash.meta-linux-arm-7" ]];then
echo "rename clash.meta-linux-arm-7 $FILENAME"
mv $FILENAME clash.meta-linux-armv7-cgo
elif [[ $FILENAME =~ "linux" ]];then
echo "rename linux $FILENAME"
mv $FILENAME $FILENAME-cgo
elif [[ $FILENAME =~ "android" ]];then
echo "rename android $FILENAME"
mv $FILENAME $FILENAME-cgo
else echo "skip $FILENAME"
fi
done

22
.github/workflows/build.yaml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Build All
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
check-latest: true
cache: true
- name: Build
run: make all
- name: Release
uses: softprops/action-gh-release@v1
with:
files: bin/*
draft: true

View File

@ -1,369 +0,0 @@
name: Build
on:
workflow_dispatch:
push:
paths-ignore:
- "docs/**"
- "README.md"
branches:
- Alpha
- Beta
- Meta
tags:
- "v*"
pull_request_target:
branches:
- Alpha
- Beta
- Meta
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
env:
REGISTRY: docker.io
jobs:
Build:
permissions: write-all
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
job:
- {
type: "WithoutCGO",
target: "linux-amd64 linux-amd64-compatible",
id: "1",
}
- {
type: "WithoutCGO",
target: "linux-armv5 linux-armv6 linux-armv7",
id: "2",
}
- {
type: "WithoutCGO",
target: "linux-arm64 linux-mips64 linux-mips64le",
id: "3",
}
- {
type: "WithoutCGO",
target: "linux-mips-softfloat linux-mips-hardfloat linux-mipsle-softfloat linux-mipsle-hardfloat",
id: "4",
}
- { type: "WithoutCGO", target: "linux-386 linux-riscv64", id: "5" }
- {
type: "WithoutCGO",
target: "freebsd-386 freebsd-amd64 freebsd-arm64",
id: "6",
}
- {
type: "WithoutCGO",
target: "windows-amd64-compatible windows-amd64 windows-386",
id: "7",
}
- {
type: "WithoutCGO",
target: "windows-arm64 windows-arm32v7",
id: "8",
}
- {
type: "WithoutCGO",
target: "darwin-amd64 darwin-arm64 android-arm64",
id: "9",
}
- { type: "WithCGO", target: "windows/*", id: "1" }
- { type: "WithCGO", target: "linux/386", id: "2" }
- { type: "WithCGO", target: "linux/amd64", id: "3" }
- { type: "WithCGO", target: "linux/arm64,linux/riscv64", id: "4" }
- { type: "WithCGO", target: "linux/arm,", id: "5" }
- { type: "WithCGO", target: "linux/arm-6,linux/arm-7", id: "6" }
- { type: "WithCGO", target: "linux/mips,linux/mipsle", id: "7" }
- { type: "WithCGO", target: "linux/mips64", id: "8" }
- { type: "WithCGO", target: "linux/mips64le", id: "9" }
- { type: "WithCGO", target: "darwin-10.16/*", id: "10" }
- { type: "WithCGO", target: "android", id: "11" }
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Set variables
run: echo "VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='Alpha'}}
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='Beta'}}
run: echo "VERSION=beta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='Meta'}}
run: echo "VERSION=meta-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set variables
if: ${{github.ref_name=='' || github.ref_type=='tag'}}
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
shell: bash
- name: Set ENV
run: |
sudo timedatectl set-timezone "Asia/Shanghai"
echo "NAME=clash.meta" >> $GITHUB_ENV
echo "REPO=${{ github.repository }}" >> $GITHUB_ENV
echo "ShortSHA=$(git rev-parse --short ${{ github.sha }})" >> $GITHUB_ENV
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_ENV
shell: bash
- name: Set ENV
run: |
echo "TAGS=with_gvisor,with_lwip" >> $GITHUB_ENV
echo "LDFLAGS=-X 'github.com/Dreamacro/clash/constant.Version=${VERSION}' -X 'github.com/Dreamacro/clash/constant.BuildTime=${BUILDTIME}' -w -s -buildid=" >> $GITHUB_ENV
shell: bash
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: "1.20"
check-latest: true
- name: Test
if: ${{ matrix.job.id=='1' && matrix.job.type=='WithoutCGO' }}
run: |
go test ./...
- name: Build WithoutCGO
if: ${{ matrix.job.type=='WithoutCGO' }}
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j$(($(nproc) + 1)) ${{ matrix.job.target }}
- uses: nttld/setup-ndk@v1
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
id: setup-ndk
with:
ndk-version: r25b
add-to-path: false
local-cache: true
- name: Build Android
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target=='android' }}
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
mkdir bin
CC=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang
CGO_ENABLED=1 CC=${CC} GOARCH=arm64 GOOS=android go build -tags ${TAGS} -trimpath -ldflags "${LDFLAGS}" -o bin/${NAME}-android-arm64
- name: Set up xgo
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
run: |
docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest
- name: Build by xgo
if: ${{ matrix.job.type=='WithCGO' && matrix.job.target!='android' }}
env:
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
mkdir bin
xgo --targets="${{ matrix.job.target }}" --tags="${TAGS}" -ldflags="${LDFLAGS}" --out bin/${NAME} ./
- name: Rename
if: ${{ matrix.job.type=='WithCGO' }}
run: |
cd bin
ls -la
cp ../.github/rename-cgo.sh ./
bash ./rename-cgo.sh
rm ./rename-cgo.sh
ls -la
cd ..
- name: Zip
if: ${{ success() }}
run: |
cd bin
ls -la
chmod +x *
cp ../.github/release.sh ./
bash ./release.sh
rm ./release.sh
ls -la
cd ..
- name: Save version
run: echo ${VERSION} > bin/version.txt
shell: bash
- uses: actions/upload-artifact@v3
if: ${{ success() }}
with:
name: artifact
path: bin/
Upload-Prerelease:
permissions: write-all
if: ${{ github.ref_type=='branch' }}
needs: [Build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Delete current release assets
uses: andreaswilli/delete-release-assets-action@v2.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: Prerelease-${{ github.ref_name }}
deleteOnlyFromDrafts: false
- name: Set Env
run: |
echo "BUILDTIME=$(TZ=Asia/Shanghai date)" >> $GITHUB_ENV
shell: bash
- name: Tag Repo
uses: richardsimko/update-tag@v1.0.6
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
cat > release.txt << 'EOF'
Release created at ${{ env.BUILDTIME }}
Synchronize ${{ github.ref_name }} branch code updates, keeping only the latest version
<br>
### release version
`default(not specified in file name)`: compiled with GOAMD64=v3
`cgo`: support lwip tun stack, compiled with GOAMD64=v1
`compatible`: compiled with GOAMD64=v1
Check details between different architectural levels [here](https://github.com/golang/go/wiki/MinimumRequirements#amd64).
EOF
- name: Upload Prerelease
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag: ${{ github.ref_name }}
tag_name: Prerelease-${{ github.ref_name }}
files: |
bin/*
prerelease: true
generate_release_notes: true
body_path: release.txt
- name: Git push assets to "release" branch
run: |
cd bin || exit 1
git init
git config --local user.name "github-actions[bot]"
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b release
git add .
git commit -m "${{ env.BUILDTIME }}"
git remote add origin "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}"
git push -f -u origin release
Upload-Release:
permissions: write-all
if: ${{ github.ref_type=='tag' }}
needs: [Build]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
files: bin/*
generate_release_notes: true
Docker:
permissions: write-all
needs: [Build]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/download-artifact@v3
with:
name: artifact
path: bin/
- name: Display structure of downloaded files
run: ls -R
working-directory: bin
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
- name: Show files
run: |
ls .
ls bin/
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: |
linux/386
linux/amd64
linux/arm64/v8
linux/arm/v7
# linux/riscv64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

61
.github/workflows/docker.yaml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Docker
on:
push:
branches:
- Beta
tags:
- "v*"
env:
REGISTRY: docker.io
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ secrets.DOCKERHUB_ACCOUNT }}/${{secrets.DOCKERHUB_REPO}}
- name: Log into registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: ${{ github.event_name != 'pull_request' }}
platforms: |
linux/386
linux/amd64
linux/arm64/v8
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

61
.github/workflows/prerelease.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Prerelease
on:
workflow_dispatch:
push:
branches:
- Alpha
- Beta
pull_request:
branches:
- Alpha
- Beta
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
check-latest: true
cache: true
- name: Test
if: ${{github.ref_name=='Beta'}}
run: |
go test ./...
- name: Build
if: success()
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j$(($(nproc) + 1)) releases
- name: Delete current release assets
uses: mknejp/delete-release-assets@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag_name: Prerelease-${{ github.ref_name }}
assets: |
*.zip
*.gz
- name: Tag Repo
uses: richardsimko/update-tag@v1.0.6
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Alpha
uses: softprops/action-gh-release@v1
if: ${{ success() }}
with:
tag_name: Prerelease-${{ github.ref_name }}
files: bin/*
prerelease: true
generate_release_notes: true

36
.github/workflows/release.yaml vendored Normal file
View File

@ -0,0 +1,36 @@
name: Release
on:
push:
tags:
- "v*"
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
check-latest: true
cache: true
- name: Test
run: |
go test ./...
- name: Build
if: success()
env:
NAME: Clash.Meta
BINDIR: bin
run: make -j$(($(nproc) + 1)) releases
- name: Upload Release
uses: softprops/action-gh-release@v1
if: ${{ success() && startsWith(github.ref, 'refs/tags/')}}
with:
tag: ${{ github.ref }}
files: bin/*
generate_release_notes: true

3
.gitignore vendored
View File

@ -24,5 +24,4 @@ vendor
# test suite # test suite
test/config/cache* test/config/cache*
/output /output
.vscode/ /.vscode
.fleet/

View File

@ -1,19 +1,18 @@
FROM alpine:latest as builder FROM golang:alpine as builder
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
RUN apk add --no-cache gzip && \ RUN apk add --no-cache make git && \
mkdir /clash-config && \ mkdir /clash-config && \
wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \ wget -O /clash-config/Country.mmdb https://raw.githubusercontent.com/Loyalsoldier/geoip/release/Country.mmdb && \
wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \ wget -O /clash-config/geosite.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat wget -O /clash-config/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
COPY docker/file-name.sh /clash/file-name.sh
WORKDIR /clash COPY . /clash-src
COPY bin/ bin/ WORKDIR /clash-src
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \ RUN go mod download &&\
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && echo $FILE_NAME && \ make docker &&\
mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test mv ./bin/Clash.Meta-docker /clash
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta" LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/Clash.Meta"
@ -22,6 +21,6 @@ RUN apk add --no-cache ca-certificates tzdata iptables
VOLUME ["/root/.config/clash/"] VOLUME ["/root/.config/clash/"]
COPY --from=builder /clash-config/ /root/.config/clash/ COPY --from=builder /clash-config/ /root/.config/clash/
COPY --from=builder /clash/clash /clash COPY --from=builder /clash /clash
RUN chmod +x /clash RUN chmod +x /clash
ENTRYPOINT [ "/clash" ] ENTRYPOINT [ "/clash" ]

View File

@ -47,17 +47,14 @@ all:linux-amd64 linux-arm64\
darwin-amd64 darwin-arm64\ darwin-amd64 darwin-arm64\
windows-amd64 windows-arm64\ windows-amd64 windows-arm64\
darwin-all: darwin-amd64 darwin-arm64
docker: docker:
GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64: darwin-amd64:
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-amd64-compatible: darwin-amd64-compatible:
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64: darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -69,7 +66,7 @@ linux-amd64:
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-amd64-compatible: linux-amd64-compatible:
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64: linux-arm64:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -101,9 +98,6 @@ linux-mips64:
linux-mips64le: linux-mips64le:
GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=mips64le GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-riscv64:
GOARCH=riscv64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
android-arm64: android-arm64:
GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=arm64 GOOS=android $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -123,7 +117,7 @@ windows-amd64:
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-amd64-compatible: windows-amd64-compatible:
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
windows-arm64: windows-arm64:
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe

View File

@ -29,42 +29,32 @@
- Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`.
- Comprehensive HTTP RESTful API controller - Comprehensive HTTP RESTful API controller
## Wiki ## Getting Started
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
Documentation and configuring examples are available on [Clash.Meta Wiki](https://clash-meta.wiki). ## Advanced usage for this branch
## Build ## Build
You should install [golang](https://go.dev) first. You should install [golang](https://go.dev) first.
Then get the source code of Clash.Meta: Then get the source code of Clash.Meta:
```shell ```shell
git clone https://github.com/MetaCubeX/Clash.Meta.git git clone https://github.com/MetaCubeX/Clash.Meta.git
cd Clash.Meta && go mod download cd Clash.Meta && go mod download
``` ```
If you can't visit github,you should set proxy first: If you can't visit github,you should set proxy first:
```shell ```shell
go env -w GOPROXY=https://goproxy.io,direct go env -w GOPROXY=https://goproxy.io,direct
``` ```
Now you can build it: So now you can build it:
```shell ```shell
go build go build
``` ```
If you need gvisor for tun stack, build with: ### DNS configuration
```shell
go build -tags with_gvisor
```
<!-- ## Advanced usage of this fork -->
<!-- ### DNS configuration
Support `geosite` with `fallback-filter`. Support `geosite` with `fallback-filter`.
@ -74,6 +64,7 @@ Support resolve ip with a `Proxy Tunnel`.
```yaml ```yaml
proxy-groups: proxy-groups:
- name: DNS - name: DNS
type: url-test type: url-test
use: use:
@ -82,7 +73,6 @@ proxy-groups:
interval: 180 interval: 180
lazy: true lazy: true
``` ```
```yaml ```yaml
dns: dns:
enable: true enable: true
@ -98,12 +88,12 @@ dns:
- https://doh.pub/dns-query - https://doh.pub/dns-query
- tls://223.5.5.5:853 - tls://223.5.5.5:853
fallback: fallback:
- "https://1.0.0.1/dns-query#DNS" # append the proxy adapter name or group name to the end of DNS URL with '#' prefix. - 'https://1.0.0.1/dns-query#DNS' # append the proxy adapter name or group name to the end of DNS URL with '#' prefix.
- "tls://8.8.4.4:853#DNS" - 'tls://8.8.4.4:853#DNS'
fallback-filter: fallback-filter:
geoip: false geoip: false
geosite: geosite:
- gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers. - gfw # `geosite` filter only use fallback server to resolve ip, prevent DNS leaks to unsafe DNS providers.
domain: domain:
- +.example.com - +.example.com
ipcidr: ipcidr:
@ -120,23 +110,21 @@ Built-in [Wintun](https://www.wintun.net) driver.
# Enable the TUN listener # Enable the TUN listener
tun: tun:
enable: true enable: true
stack: system # system/gvisor stack: gvisor # only gvisor
dns-hijack: dns-hijack:
- 0.0.0.0:53 # additional dns server listen on TUN - 0.0.0.0:53 # additional dns server listen on TUN
auto-route: true # auto set global route auto-route: true # auto set global route
``` ```
### Rules configuration ### Rules configuration
- Support rule `GEOSITE`. - Support rule `GEOSITE`.
- Support rule-providers `RULE-SET`. - Support rule-providers `RULE-SET`.
- Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`. - Support `multiport` condition for rule `SRC-PORT` and `DST-PORT`.
- Support `network` condition for all rules. - Support `network` condition for all rules.
- Support source IPCIDR condition for all rules, just append to the end. - Support source IPCIDR condition for all rules, just append to the end.
- The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat. - The `GEOSITE` databases via https://github.com/Loyalsoldier/v2ray-rules-dat.
```yaml ```yaml
rules: rules:
# network(tcp/udp) condition for all rules # network(tcp/udp) condition for all rules
- DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp - DOMAIN-SUFFIX,bilibili.com,DIRECT,tcp
- DOMAIN-SUFFIX,bilibili.com,REJECT,udp - DOMAIN-SUFFIX,bilibili.com,REJECT,udp
@ -165,6 +153,7 @@ rules:
- MATCH,PROXY - MATCH,PROXY
``` ```
### Proxies configuration ### Proxies configuration
Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node) Active health detection `urltest / fallback` (based on tcp handshake, multiple failures within a limited time will actively trigger health detection to use the node)
@ -173,17 +162,18 @@ Support `Policy Group Filter`
```yaml ```yaml
proxy-groups: proxy-groups:
- name: 🚀 HK Group - name: 🚀 HK Group
type: select type: select
use: use:
- ALL - ALL
filter: "HK" filter: 'HK'
- name: 🚀 US Group - name: 🚀 US Group
type: select type: select
use: use:
- ALL - ALL
filter: "US" filter: 'US'
proxy-providers: proxy-providers:
ALL: ALL:
@ -195,12 +185,14 @@ proxy-providers:
enable: true enable: true
interval: 600 interval: 600
url: http://www.gstatic.com/generate_204 url: http://www.gstatic.com/generate_204
``` ```
Support outbound transport protocol `VLESS`. Support outbound transport protocol `VLESS`.
The XTLS support (TCP/UDP) transport by the XRAY-CORE. The XTLS support (TCP/UDP) transport by the XRAY-CORE.
```yaml ```yaml
proxies: proxies:
- name: "vless" - name: "vless"
@ -240,8 +232,8 @@ proxies:
grpc-service-name: grpcname grpc-service-name: grpcname
``` ```
Support outbound transport protocol `Wireguard`
Support outbound transport protocol `Wireguard`
```yaml ```yaml
proxies: proxies:
- name: "wg" - name: "wg"
@ -256,7 +248,6 @@ proxies:
``` ```
Support outbound transport protocol `Tuic` Support outbound transport protocol `Tuic`
```yaml ```yaml
proxies: proxies:
- name: "tuic" - name: "tuic"
@ -275,11 +266,11 @@ proxies:
# max-udp-relay-packet-size: 1500 # max-udp-relay-packet-size: 1500
# fast-open: true # fast-open: true
# skip-cert-verify: true # skip-cert-verify: true
``` -->
```
### IPTABLES configuration ### IPTABLES configuration
Work on Linux OS who's supported `iptables`
Work on Linux OS which supported `iptables`
```yaml ```yaml
# Enable the TPROXY listener # Enable the TPROXY listener
@ -290,15 +281,17 @@ iptables:
inbound-interface: eth0 # detect the inbound interface, default is 'lo' inbound-interface: eth0 # detect the inbound interface, default is 'lo'
``` ```
### General installation guide for Linux ### General installation guide for Linux
+ Create user given name `clash-meta`
- Create user given name `clash-meta` + Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases)
- Download and decompress pre-built binaries from [releases](https://github.com/MetaCubeX/Clash.Meta/releases) + Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
+ Create folder `/etc/Clash-Meta/` as working directory
- Rename executable file to `Clash-Meta` and move to `/usr/local/bin/`
- Create folder `/etc/Clash-Meta/` as working directory
Run Meta Kernel by user `clash-meta` as a daemon. Run Meta Kernel by user `clash-meta` as a daemon.
@ -324,13 +317,10 @@ ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
Launch clashd on system startup with: Launch clashd on system startup with:
```shell ```shell
$ systemctl enable Clash-Meta $ systemctl enable Clash-Meta
``` ```
Launch clashd immediately with: Launch clashd immediately with:
```shell ```shell
@ -341,29 +331,23 @@ $ systemctl start Clash-Meta
Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`. Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`.
To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta). To display process name in GUI please use [Dashboard For Meta](https://github.com/MetaCubeX/clash-dashboard).
### Dashboard ![img.png](https://github.com/Clash-Mini/Dashboard/raw/master/View/Dashboard-Process.png)
We also made a custom fork of yacd provide better support for this project, check it out at [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta)
## Development ## Development
If you want to build an application that uses clash as a library, check out the If you want to build an application that uses clash as a library, check out the
the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library) the [GitHub Wiki](https://github.com/Dreamacro/clash/wiki/use-clash-as-a-library)
## Debugging
Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API.
## Credits ## Credits
- [Dreamacro/clash](https://github.com/Dreamacro/clash) * [Dreamacro/clash](https://github.com/Dreamacro/clash)
- [SagerNet/sing-box](https://github.com/SagerNet/sing-box) * [SagerNet/sing-box](https://github.com/SagerNet/sing-box)
- [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) * [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2)
- [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) * [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core)
- [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go) * [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go)
- [yaling888/clash-plus-pro](https://github.com/yaling888/clash) * [yaling888/clash-plus-pro](https://github.com/yaling888/clash)
## License ## License

View File

@ -92,7 +92,6 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
mapping["history"] = p.DelayHistory() mapping["history"] = p.DelayHistory()
mapping["name"] = p.Name() mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP() mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP()
mapping["tfo"] = p.SupportTFO() mapping["tfo"] = p.SupportTFO()
return json.Marshal(mapping) return json.Marshal(mapping)
} }

View File

@ -16,11 +16,11 @@ func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Ad
for _, addition := range additions { for _, addition := range additions {
addition.Apply(metadata) addition.Apply(metadata)
} }
if ip, port, err := parseAddr(source); err == nil { if ip, port, err := parseAddr(source.String()); err == nil {
metadata.SrcIP = ip metadata.SrcIP = ip
metadata.SrcPort = port metadata.SrcPort = port
} }
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil { if ip, port, err := parseAddr(conn.LocalAddr().String()); err == nil {
metadata.InIP = ip metadata.InIP = ip
metadata.InPort = port metadata.InPort = port
} }

View File

@ -15,11 +15,11 @@ func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *cont
for _, addition := range additions { for _, addition := range additions {
addition.Apply(metadata) addition.Apply(metadata)
} }
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil { if ip, port, err := parseAddr(conn.RemoteAddr().String()); err == nil {
metadata.SrcIP = ip metadata.SrcIP = ip
metadata.SrcPort = port metadata.SrcPort = port
} }
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil { if ip, port, err := parseAddr(conn.LocalAddr().String()); err == nil {
metadata.InIP = ip metadata.InIP = ip
metadata.InPort = port metadata.InPort = port
} }

View File

@ -4,7 +4,7 @@ import (
"context" "context"
"net" "net"
"github.com/sagernet/tfo-go" "github.com/database64128/tfo-go/v2"
) )
var ( var (

View File

@ -24,12 +24,12 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions
for _, addition := range additions { for _, addition := range additions {
addition.Apply(metadata) addition.Apply(metadata)
} }
if ip, port, err := parseAddr(packet.LocalAddr()); err == nil { if ip, port, err := parseAddr(packet.LocalAddr().String()); err == nil {
metadata.SrcIP = ip metadata.SrcIP = ip
metadata.SrcPort = port metadata.SrcPort = port
} }
if p, ok := packet.(C.UDPPacketInAddr); ok { if p, ok := packet.(C.UDPPacketInAddr); ok {
if ip, port, err := parseAddr(p.InAddr()); err == nil { if ip, port, err := parseAddr(p.InAddr().String()); err == nil {
metadata.InIP = ip metadata.InIP = ip
metadata.InPort = port metadata.InPort = port
} }

View File

@ -18,13 +18,22 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Ad
addition.Apply(metadata) addition.Apply(metadata)
} }
if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil { remoteAddr := conn.RemoteAddr()
metadata.SrcIP = ip
metadata.SrcPort = port // Filter when net.Addr interface is nil
if remoteAddr != nil {
if ip, port, err := parseAddr(remoteAddr.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
} }
if ip, port, err := parseAddr(conn.LocalAddr()); err == nil { localAddr := conn.LocalAddr()
metadata.InIP = ip // Filter when net.Addr interface is nil
metadata.InPort = port if localAddr != nil {
if ip, port, err := parseAddr(localAddr.String()); err == nil {
metadata.InIP = ip
metadata.InPort = port
}
} }
return context.NewConnContext(conn, metadata) return context.NewConnContext(conn, metadata)
@ -34,7 +43,7 @@ func NewInner(conn net.Conn, dst string, host string) *context.ConnContext {
metadata := &C.Metadata{} metadata := &C.Metadata{}
metadata.NetWork = C.TCP metadata.NetWork = C.TCP
metadata.Type = C.INNER metadata.Type = C.INNER
metadata.DNSMode = C.DNSNormal metadata.DNSMode = C.DNSMapping
metadata.Host = host metadata.Host = host
metadata.Process = C.ClashName metadata.Process = C.ClashName
if h, port, err := net.SplitHostPort(dst); err == nil { if h, port, err := net.SplitHostPort(dst); err == nil {

View File

@ -1,14 +1,13 @@
package inbound package inbound
import ( import (
"errors" "github.com/Dreamacro/clash/common/nnip"
"net" "net"
"net/http" "net/http"
"net/netip" "net/netip"
"strconv" "strconv"
"strings" "strings"
"github.com/Dreamacro/clash/common/nnip"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
@ -58,19 +57,8 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata return metadata
} }
func parseAddr(addr net.Addr) (netip.Addr, string, error) { func parseAddr(addr string) (netip.Addr, string, error) {
// Filter when net.Addr interface is nil host, port, err := net.SplitHostPort(addr)
if addr == nil {
return netip.Addr{}, "", errors.New("nil addr")
}
if rawAddr, ok := addr.(interface{ RawAddr() net.Addr }); ok {
ip, port, err := parseAddr(rawAddr.RawAddr())
if err == nil {
return ip, port, err
}
}
addrStr := addr.String()
host, port, err := net.SplitHostPort(addrStr)
if err != nil { if err != nil {
return netip.Addr{}, "", err return netip.Addr{}, "", err
} }

View File

@ -4,11 +4,10 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/gofrs/uuid"
"net" "net"
"strings" "strings"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -19,7 +18,6 @@ type Base struct {
iface string iface string
tp C.AdapterType tp C.AdapterType
udp bool udp bool
xudp bool
tfo bool tfo bool
rmark int rmark int
id string id string
@ -34,7 +32,12 @@ func (b *Base) Name() string {
// Id implements C.ProxyAdapter // Id implements C.ProxyAdapter
func (b *Base) Id() string { func (b *Base) Id() string {
if b.id == "" { if b.id == "" {
b.id = utils.NewUUIDV6().String() id, err := uuid.NewV6()
if err != nil {
b.id = b.name
} else {
b.id = id.String()
}
} }
return b.id return b.id
@ -84,11 +87,6 @@ func (b *Base) SupportUDP() bool {
return b.udp return b.udp
} }
// SupportXUDP implements C.ProxyAdapter
func (b *Base) SupportXUDP() bool {
return b.xudp
}
// SupportTFO implements C.ProxyAdapter // SupportTFO implements C.ProxyAdapter
func (b *Base) SupportTFO() bool { func (b *Base) SupportTFO() bool {
return b.tfo return b.tfo
@ -134,15 +132,10 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
default: default:
} }
if b.tfo {
opts = append(opts, dialer.WithTFO(true))
}
return opts return opts
} }
type BasicOption struct { type BasicOption struct {
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"` Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"` RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"` IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
@ -153,7 +146,6 @@ type BaseOption struct {
Addr string Addr string
Type C.AdapterType Type C.AdapterType
UDP bool UDP bool
XUDP bool
TFO bool TFO bool
Interface string Interface string
RoutingMark int RoutingMark int
@ -166,7 +158,6 @@ func NewBase(opt BaseOption) *Base {
addr: opt.Addr, addr: opt.Addr,
tp: opt.Type, tp: opt.Type,
udp: opt.UDP, udp: opt.UDP,
xudp: opt.XUDP,
tfo: opt.TFO, tfo: opt.TFO,
iface: opt.Interface, iface: opt.Interface,
rmark: opt.RoutingMark, rmark: opt.RoutingMark,
@ -175,7 +166,7 @@ func NewBase(opt BaseOption) *Base {
} }
type conn struct { type conn struct {
N.ExtendedConn net.Conn
chain C.Chain chain C.Chain
actualRemoteDestination string actualRemoteDestination string
} }
@ -194,19 +185,13 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name()) c.chain = append(c.chain, a.Name())
} }
func (c *conn) Upstream() any {
return c.ExtendedConn
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())} return &conn{c, []string{a.Name()}, parseRemoteDestination(a.Addr())}
} }
type packetConn struct { type packetConn struct {
net.PacketConn net.PacketConn
chain C.Chain chain C.Chain
adapterName string
connID string
actualRemoteDestination string actualRemoteDestination string
} }
@ -224,13 +209,8 @@ func (c *packetConn) AppendToChains(a C.ProxyAdapter) {
c.chain = append(c.chain, a.Name()) c.chain = append(c.chain, a.Name())
} }
func (c *packetConn) LocalAddr() net.Addr {
lAddr := c.PacketConn.LocalAddr()
return N.NewCustomAddr(c.adapterName, c.connID, lAddr) // make quic-go's connMultiplexer happy
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
return &packetConn{pc, []string{a.Name()}, a.Name(), utils.NewUUIDV4().String(), parseRemoteDestination(a.Addr())} return &packetConn{pc, []string{a.Name()}, parseRemoteDestination(a.Addr())}
} }
func parseRemoteDestination(addr string) string { func parseRemoteDestination(addr string) string {

View File

@ -7,6 +7,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"io" "io"
"net" "net"
"net/http" "net/http"
@ -14,7 +15,6 @@ import (
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -150,7 +150,7 @@ func NewHttp(option HttpOption) (*Http, error) {
sni = option.SNI sni = option.SNI
} }
if len(option.Fingerprint) == 0 { if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{ tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(&tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni, ServerName: sni,
}) })
@ -170,7 +170,6 @@ func NewHttp(option HttpOption) (*Http, error) {
name: option.Name, name: option.Name,
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Http, tp: C.Http,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@ -178,7 +178,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
return nil, err return nil, err
} }
} else { } else {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
} }
if len(option.ALPN) > 0 { if len(option.ALPN) > 0 {

View File

@ -1,35 +0,0 @@
package outbound
import (
"encoding/base64"
"encoding/hex"
"errors"
tlsC "github.com/Dreamacro/clash/component/tls"
"golang.org/x/crypto/curve25519"
)
type RealityOptions struct {
PublicKey string `proxy:"public-key"`
ShortID string `proxy:"short-id"`
}
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
if o.PublicKey != "" {
config := new(tlsC.RealityConfig)
n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey))
if err != nil || n != curve25519.ScalarSize {
return nil, errors.New("invalid REALITY public key")
}
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
if err != nil || n > tlsC.RealityMaxShortIDLen {
return nil, errors.New("invalid REALITY short ID")
}
return config, nil
}
return nil, nil
}

View File

@ -6,7 +6,6 @@ import (
"net" "net"
"time" "time"
"github.com/Dreamacro/clash/common/buf"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
) )
@ -17,12 +16,12 @@ type Reject struct {
// DialContext implements C.ProxyAdapter // 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, opts ...dialer.Option) (C.Conn, error) {
return NewConn(nopConn{}, r), nil return NewConn(&nopConn{}, r), nil
} }
// ListenPacketContext implements C.ProxyAdapter // 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, opts ...dialer.Option) (C.PacketConn, error) {
return newPacketConn(nopPacketConn{}, r), nil return newPacketConn(&nopPacketConn{}, r), nil
} }
func NewReject() *Reject { func NewReject() *Reject {
@ -49,37 +48,27 @@ func NewPass() *Reject {
type nopConn struct{} type nopConn struct{}
func (rw nopConn) Read(b []byte) (int, error) { func (rw *nopConn) Read(b []byte) (int, error) {
return 0, io.EOF return 0, io.EOF
} }
func (rw nopConn) ReadBuffer(buffer *buf.Buffer) error { func (rw *nopConn) Write(b []byte) (int, error) {
return io.EOF
}
func (rw nopConn) Write(b []byte) (int, error) {
return 0, io.EOF return 0, io.EOF
} }
func (rw nopConn) WriteBuffer(buffer *buf.Buffer) error { func (rw *nopConn) Close() error { return nil }
return io.EOF func (rw *nopConn) LocalAddr() net.Addr { return nil }
} func (rw *nopConn) RemoteAddr() net.Addr { return nil }
func (rw *nopConn) SetDeadline(time.Time) error { return nil }
func (rw nopConn) Close() error { return nil } func (rw *nopConn) SetReadDeadline(time.Time) error { return nil }
func (rw nopConn) LocalAddr() net.Addr { return nil } func (rw *nopConn) SetWriteDeadline(time.Time) error { return nil }
func (rw nopConn) RemoteAddr() net.Addr { return nil }
func (rw nopConn) SetDeadline(time.Time) error { return nil }
func (rw nopConn) SetReadDeadline(time.Time) error { return nil }
func (rw nopConn) SetWriteDeadline(time.Time) error { return nil }
var udpAddrIPv4Unspecified = &net.UDPAddr{IP: net.IPv4zero, Port: 0}
type nopPacketConn struct{} type nopPacketConn struct{}
func (npc nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil } func (npc *nopPacketConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { return len(b), nil }
func (npc nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF } func (npc *nopPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { return 0, nil, io.EOF }
func (npc nopPacketConn) Close() error { return nil } func (npc *nopPacketConn) Close() error { return nil }
func (npc nopPacketConn) LocalAddr() net.Addr { return udpAddrIPv4Unspecified } func (npc *nopPacketConn) LocalAddr() net.Addr { return &net.UDPAddr{IP: net.IPv4zero, Port: 0} }
func (npc nopPacketConn) SetDeadline(time.Time) error { return nil } func (npc *nopPacketConn) SetDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetReadDeadline(time.Time) error { return nil } func (npc *nopPacketConn) SetReadDeadline(time.Time) error { return nil }
func (npc nopPacketConn) SetWriteDeadline(time.Time) error { return nil } func (npc *nopPacketConn) SetWriteDeadline(time.Time) error { return nil }

View File

@ -2,23 +2,21 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"time"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/restls" "github.com/Dreamacro/clash/transport/shadowtls"
obfs "github.com/Dreamacro/clash/transport/simple-obfs" obfs "github.com/Dreamacro/clash/transport/simple-obfs"
shadowtls "github.com/Dreamacro/clash/transport/sing-shadowtls"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks" shadowsocks "github.com/metacubex/sing-shadowsocks"
"github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@ -35,23 +33,21 @@ type ShadowSocks struct {
obfsMode string obfsMode string
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowTLSOption
restlsConfig *restlsC.Config tlsConfig *tls.Config
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Plugin string `proxy:"plugin,omitempty"` Plugin string `proxy:"plugin,omitempty"`
PluginOpts map[string]any `proxy:"plugin-opts,omitempty"` PluginOpts map[string]any `proxy:"plugin-opts,omitempty"`
UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"` UDPOverTCP bool `proxy:"udp-over-tcp,omitempty"`
UDPOverTCPVersion int `proxy:"udp-over-tcp-version,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
type simpleObfsOption struct { type simpleObfsOption struct {
@ -75,26 +71,10 @@ type shadowTLSOption struct {
Host string `obfs:"host"` Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,omitempty"`
}
type restlsOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
VersionHint string `obfs:"version-hint"`
RestlsScript string `obfs:"restls-script,omitempty"`
} }
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
return ss.StreamConnContext(ctx, c, metadata)
}
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
useEarly := false
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host) c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
@ -108,34 +88,12 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
case shadowtls.Mode: case shadowtls.Mode:
var err error c = shadowtls.NewShadowTLS(c, ss.shadowTLSOption.Password, ss.tlsConfig)
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
useEarly = true
case restls.Mode:
var err error
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
if err != nil {
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
}
useEarly = true
} }
useEarly = useEarly || N.NeedHandshake(c)
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion)) return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
if useEarly {
return ss.method.DialEarlyConn(c, uotDestination), nil
} else {
return ss.method.DialConn(c, uotDestination)
}
}
if useEarly {
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else {
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -155,7 +113,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = ss.StreamConnContext(ctx, c, metadata) c, err = ss.StreamConn(c, metadata)
return NewConn(c, ss), err return NewConn(c, ss), err
} }
@ -171,12 +129,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
if err != nil { if err != nil {
return nil, err return nil, err
} }
destination := M.ParseSocksaddr(metadata.RemoteAddress()) return newPacketConn(uot.NewClientConn(tcpConn), ss), nil
if ss.option.UDPOverTCPVersion == 1 {
return newPacketConn(uot.NewConn(tcpConn, uot.Request{Destination: destination}), ss), nil
} else {
return newPacketConn(uot.NewLazyConn(tcpConn, uot.Request{Destination: destination}), ss), nil
}
} }
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer) addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
if err != nil { if err != nil {
@ -199,12 +152,7 @@ func (ss *ShadowSocks) SupportWithDialer() bool {
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (ss *ShadowSocks) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP { if ss.option.UDPOverTCP {
destination := M.ParseSocksaddr(metadata.RemoteAddress()) return newPacketConn(uot.NewClientConn(c), ss), nil
if ss.option.UDPOverTCPVersion == uot.LegacyVersion {
return newPacketConn(uot.NewConn(c, uot.Request{Destination: destination}), ss), nil
} else {
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), ss), nil
}
} }
return nil, errors.New("no support") return nil, errors.New("no support")
} }
@ -216,15 +164,15 @@ func (ss *ShadowSocks) SupportUOT() bool {
func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
method, err := shadowimpl.FetchMethod(option.Cipher, option.Password, time.Now) method, err := shadowimpl.FetchMethod(option.Cipher, option.Password)
if err != nil { if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err) return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
} }
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowTLSOption
var restlsConfig *restlsC.Config var tlsConfig *tls.Config
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@ -262,41 +210,25 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
} else if option.Plugin == shadowtls.Mode { } else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode obfsMode = shadowtls.Mode
opt := &shadowTLSOption{ shadowTLSOpt = &shadowTLSOption{}
Version: 2, if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil {
}
if err := decoder.Decode(option.PluginOpts, opt); err != nil {
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err) return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
} }
shadowTLSOpt = &shadowtls.ShadowTLSOption{ tlsConfig = &tls.Config{
Password: opt.Password, NextProtos: shadowtls.DefaultALPN,
Host: opt.Host, MinVersion: tls.VersionTLS12,
Fingerprint: opt.Fingerprint, InsecureSkipVerify: shadowTLSOpt.SkipCertVerify,
ClientFingerprint: option.ClientFingerprint, ServerName: shadowTLSOpt.Host,
SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version,
}
} else if option.Plugin == restls.Mode {
obfsMode = restls.Mode
restlsOpt := &restlsOption{}
if err := decoder.Decode(option.PluginOpts, restlsOpt); err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
} }
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint) if len(shadowTLSOpt.Fingerprint) == 0 {
restlsConfig.SessionTicketsDisabled = true tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
if err != nil { } else {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil {
return nil, err
}
} }
}
switch option.UDPOverTCPVersion {
case uot.Version, uot.LegacyVersion:
case 0:
option.UDPOverTCPVersion = uot.Version
default:
return nil, fmt.Errorf("ss %s unknown udp over tcp protocol version: %d", addr, option.UDPOverTCPVersion)
} }
return &ShadowSocks{ return &ShadowSocks{
@ -305,7 +237,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
addr: addr, addr: addr,
tp: C.Shadowsocks, tp: C.Shadowsocks,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@ -317,7 +248,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt, shadowTLSOption: shadowTLSOpt,
restlsConfig: restlsConfig, tlsConfig: tlsConfig,
}, nil }, nil
} }

View File

@ -163,7 +163,6 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
addr: addr, addr: addr,
tp: C.ShadowsocksR, tp: C.ShadowsocksR,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@ -167,7 +167,6 @@ func NewSnell(option SnellOption) (*Snell, error) {
addr: addr, addr: addr,
tp: C.Snell, tp: C.Snell,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@ -5,12 +5,12 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"io" "io"
"net" "net"
"strconv" "strconv"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
) )
@ -167,7 +167,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
} }
if len(option.Fingerprint) == 0 { if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
} else { } else {
var err error var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
@ -182,7 +182,6 @@ func NewSocks5(option Socks5Option) (*Socks5, error) {
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Socks5, tp: C.Socks5,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),

View File

@ -8,7 +8,6 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -26,28 +25,24 @@ type Trojan struct {
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *gun.TransportWrap transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
} }
type TrojanOption struct { type TrojanOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"` ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` Flow string `proxy:"flow,omitempty"`
Flow string `proxy:"flow,omitempty"` FlowShow bool `proxy:"flow-show,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) { func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
@ -80,13 +75,8 @@ func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
if t.transport != nil { if t.transport != nil {
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig) c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
} else { } else {
c, err = t.plainStream(c) c, err = t.plainStream(c)
} }
@ -105,7 +95,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error)
return c, err return c, err
} }
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return N.NewExtendedConn(c), err return c, err
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -221,13 +211,12 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{ tOption := &trojan.Option{
Password: option.Password, Password: option.Password,
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
FlowShow: option.FlowShow, FlowShow: option.FlowShow,
Fingerprint: option.Fingerprint, Fingerprint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint,
} }
switch option.Network { switch option.Network {
@ -253,7 +242,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
addr: addr, addr: addr,
tp: C.Trojan, tp: C.Trojan,
udp: option.UDP, udp: option.UDP,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@ -262,13 +250,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
option: &option, option: &option,
} }
var err error
t.realityConfig, err = option.RealityOpts.Parse()
if err != nil {
return nil, err
}
tOption.Reality = t.realityConfig
if option.Network == "grpc" { if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(network, addr string) (net.Conn, error) {
c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...) c, err := dialer.DialContext(context.Background(), "tcp", t.addr, t.Base.DialOptions()...)
@ -287,7 +268,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
if len(option.Fingerprint) == 0 { if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
} else { } else {
var err error var err error
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil {
@ -295,7 +276,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig) t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
t.gunTLSConfig = tlsConfig t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{ t.gunConfig = &gun.Config{

View File

@ -42,17 +42,15 @@ type TuicOption struct {
DisableSni bool `proxy:"disable-sni,omitempty"` DisableSni bool `proxy:"disable-sni,omitempty"`
MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"` MaxUdpRelayPacketSize int `proxy:"max-udp-relay-packet-size,omitempty"`
FastOpen bool `proxy:"fast-open,omitempty"` FastOpen bool `proxy:"fast-open,omitempty"`
MaxOpenStreams int `proxy:"max-open-streams,omitempty"` MaxOpenStreams int `proxy:"max-open-streams,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
CustomCA string `proxy:"ca,omitempty"` CustomCA string `proxy:"ca,omitempty"`
CustomCAString string `proxy:"ca-str,omitempty"` CustomCAString string `proxy:"ca-str,omitempty"`
ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"` ReceiveWindowConn int `proxy:"recv-window-conn,omitempty"`
ReceiveWindow int `proxy:"recv-window,omitempty"` ReceiveWindow int `proxy:"recv-window,omitempty"`
DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"` DisableMTUDiscovery bool `proxy:"disable-mtu-discovery,omitempty"`
MaxDatagramFrameSize int `proxy:"max-datagram-frame-size,omitempty"`
SNI string `proxy:"sni,omitempty"`
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -108,14 +106,12 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (pc net.Pack
func NewTuic(option TuicOption) (*Tuic, error) { func NewTuic(option TuicOption) (*Tuic, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
serverName := option.Server serverName := option.Server
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
ServerName: serverName, ServerName: serverName,
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
MinVersion: tls.VersionTLS13, MinVersion: tls.VersionTLS13,
} }
if option.SNI != "" {
tlsConfig.ServerName = option.SNI
}
var bs []byte var bs []byte
var err error var err error
@ -147,7 +143,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
return nil, err return nil, err
} }
} else { } else {
tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
} }
if len(option.ALPN) > 0 { if len(option.ALPN) > 0 {
@ -169,22 +165,13 @@ func NewTuic(option TuicOption) (*Tuic, error) {
} }
if option.MaxUdpRelayPacketSize == 0 { if option.MaxUdpRelayPacketSize == 0 {
option.MaxUdpRelayPacketSize = 1252 option.MaxUdpRelayPacketSize = 1500
} }
if option.MaxOpenStreams == 0 { if option.MaxOpenStreams == 0 {
option.MaxOpenStreams = 100 option.MaxOpenStreams = 100
} }
if option.MaxDatagramFrameSize == 0 {
option.MaxDatagramFrameSize = option.MaxUdpRelayPacketSize + tuic.PacketOverHead
}
if option.MaxDatagramFrameSize > 1400 {
option.MaxDatagramFrameSize = 1400
}
option.MaxUdpRelayPacketSize = option.MaxDatagramFrameSize - tuic.PacketOverHead
// ensure server's incoming stream can handle correctly, increase to 1.1x // ensure server's incoming stream can handle correctly, increase to 1.1x
quicMaxOpenStreams := int64(option.MaxOpenStreams) quicMaxOpenStreams := int64(option.MaxOpenStreams)
quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0)) quicMaxOpenStreams = quicMaxOpenStreams + int64(math.Ceil(float64(quicMaxOpenStreams)/10.0))
@ -197,7 +184,6 @@ func NewTuic(option TuicOption) (*Tuic, error) {
MaxIncomingUniStreams: quicMaxOpenStreams, MaxIncomingUniStreams: quicMaxOpenStreams,
KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond, KeepAlivePeriod: time.Duration(option.HeartbeatInterval) * time.Millisecond,
DisablePathMTUDiscovery: option.DisableMTUDiscovery, DisablePathMTUDiscovery: option.DisableMTUDiscovery,
MaxDatagramFrameSize: int64(option.MaxDatagramFrameSize),
EnableDatagrams: true, EnableDatagrams: true,
} }
if option.ReceiveWindowConn == 0 { if option.ReceiveWindowConn == 0 {
@ -227,18 +213,12 @@ func NewTuic(option TuicOption) (*Tuic, error) {
udp: true, udp: true,
tfo: option.FastOpen, tfo: option.FastOpen,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
} }
clientMaxOpenStreams := int64(option.MaxOpenStreams)
// to avoid tuic's "too many open streams", decrease to 0.9x // to avoid tuic's "too many open streams", decrease to 0.9x
if clientMaxOpenStreams == 100 { clientMaxOpenStreams := int64(option.MaxOpenStreams)
clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0)) clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0))
}
if clientMaxOpenStreams < 1 { if clientMaxOpenStreams < 1 {
clientMaxOpenStreams = 1 clientMaxOpenStreams = 1
} }

View File

@ -12,20 +12,19 @@ import (
"strconv" "strconv"
"sync" "sync"
vmessSing "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/socks5"
"github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vless"
"github.com/Dreamacro/clash/transport/vmess" "github.com/Dreamacro/clash/transport/vmess"
vmessSing "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata"
) )
const ( const (
@ -42,46 +41,38 @@ type Vless struct {
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *gun.TransportWrap transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
} }
type VlessOption struct { type VlessOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
Flow string `proxy:"flow,omitempty"` Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"` FlowShow bool `proxy:"flow-show,omitempty"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
PacketAddr bool `proxy:"packet-addr,omitempty"` PacketAddr bool `proxy:"packet-addr,omitempty"`
XUDP bool `proxy:"xudp,omitempty"` XUDP bool `proxy:"xudp,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"` PacketEncoding string `proxy:"packet-encoding,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` ServerName string `proxy:"servername,omitempty"`
ServerName string `proxy:"servername,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
host, port, _ := net.SplitHostPort(v.addr) host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &vmess.WebsocketConfig{ wsOpts := &vmess.WebsocketConfig{
Host: host, Host: host,
@ -89,7 +80,6 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
Path: v.option.WSOpts.Path, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{}, Headers: http.Header{},
} }
@ -108,12 +98,9 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
} }
if len(v.option.Fingerprint) == 0 { if len(v.option.Fingerprint) == 0 {
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
} else { } else {
wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint) wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint)
if err != nil {
return nil, err
}
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -157,7 +144,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err = vmess.StreamH2Conn(c, h2Opts) c, err = vmess.StreamH2Conn(c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
default: default:
// default tcp network // default tcp network
// handle TLS And XTLS // handle TLS And XTLS
@ -174,7 +161,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) { func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
if v.isLegacyXTLSEnabled() && !isH2 { if v.isXTLSEnabled() && !isH2 {
xtlsOpts := vless.XTLSConfig{ xtlsOpts := vless.XTLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
@ -189,11 +176,9 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
} else if v.option.TLS { } else if v.option.TLS {
tlsOpts := vmess.TLSConfig{ tlsOpts := vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint, FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
} }
if isH2 { if isH2 {
@ -210,8 +195,8 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
return conn, nil return conn, nil
} }
func (v *Vless) isLegacyXTLSEnabled() bool { func (v *Vless) isXTLSEnabled() bool {
return v.client.Addons != nil && v.client.Addons.Flow != vless.XRV return v.client.Addons != nil
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -248,15 +233,12 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
}(c) }(c)
c, err = v.StreamConn(c, metadata) c, err = v.StreamConn(c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
return NewConn(c, v), err return NewConn(c, v), err
} }
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host) ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil { if err != nil {
@ -297,7 +279,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vless use stream-oriented udp with a special address, so we need a net.UDPAddr // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host) ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil { if err != nil {
@ -483,9 +465,6 @@ func NewVless(option VlessOption) (*Vless, error) {
if option.Network != "ws" && len(option.Flow) >= 16 { if option.Network != "ws" && len(option.Flow) >= 16 {
option.Flow = option.Flow[:16] option.Flow = option.Flow[:16]
switch option.Flow { switch option.Flow {
case vless.XRV:
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
fallthrough
case vless.XRO, vless.XRD, vless.XRS: case vless.XRO, vless.XRD, vless.XRS:
addons = &vless.Addons{ addons = &vless.Addons{
Flow: option.Flow, Flow: option.Flow,
@ -495,16 +474,6 @@ func NewVless(option VlessOption) (*Vless, error) {
} }
} }
switch option.PacketEncoding {
case "packetaddr", "packet":
option.PacketAddr = true
option.XUDP = false
default: // https://github.com/XTLS/Xray-core/pull/1567#issuecomment-1407305458
if !option.PacketAddr {
option.XUDP = true
}
}
client, err := vless.NewClient(option.UUID, addons, option.FlowShow) client, err := vless.NewClient(option.UUID, addons, option.FlowShow)
if err != nil { if err != nil {
return nil, err return nil, err
@ -516,8 +485,6 @@ func NewVless(option VlessOption) (*Vless, error) {
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vless, tp: C.Vless,
udp: option.UDP, udp: option.UDP,
xudp: option.XUDP,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@ -526,9 +493,14 @@ func NewVless(option VlessOption) (*Vless, error) {
option: &option, option: &option,
} }
v.realityConfig, err = v.option.RealityOpts.Parse() switch option.PacketEncoding {
if err != nil { case "packetaddr", "packet":
return nil, err option.PacketAddr = true
case "xudp":
option.XUDP = true
}
if option.XUDP {
option.PacketAddr = false
} }
switch option.Network { switch option.Network {
@ -547,11 +519,10 @@ func NewVless(option VlessOption) (*Vless, error) {
} }
gunConfig := &gun.Config{ gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName, ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName, Host: v.option.ServerName,
ClientFingerprint: v.option.ClientFingerprint,
} }
tlsConfig := tlsC.GetGlobalTLSConfig(&tls.Config{ tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName, ServerName: v.option.ServerName,
}) })
@ -564,8 +535,7 @@ func NewVless(option VlessOption) (*Vless, error) {
v.gunTLSConfig = tlsConfig v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
} }
return v, nil return v, nil

View File

@ -5,21 +5,20 @@ import (
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
vmess "github.com/sagernet/sing-vmess"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
clashVMess "github.com/Dreamacro/clash/transport/vmess" clashVMess "github.com/Dreamacro/clash/transport/vmess"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing-vmess/packetaddr" "github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
) )
@ -35,35 +34,31 @@ type Vmess struct {
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
gunConfig *gun.Config gunConfig *gun.Config
transport *gun.TransportWrap transport *gun.TransportWrap
realityConfig *tlsC.RealityConfig
} }
type VmessOption struct { type VmessOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
AlterID int `proxy:"alterId"` AlterID int `proxy:"alterId"`
Cipher string `proxy:"cipher"` Cipher string `proxy:"cipher"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
ServerName string `proxy:"servername,omitempty"` ServerName string `proxy:"servername,omitempty"`
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` PacketAddr bool `proxy:"packet-addr,omitempty"`
PacketAddr bool `proxy:"packet-addr,omitempty"` XUDP bool `proxy:"xudp,omitempty"`
XUDP bool `proxy:"xudp,omitempty"` PacketEncoding string `proxy:"packet-encoding,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"` GlobalPadding bool `proxy:"global-padding,omitempty"`
GlobalPadding bool `proxy:"global-padding,omitempty"` AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
type HTTPOptions struct { type HTTPOptions struct {
@ -91,13 +86,9 @@ type WSOptions struct {
// StreamConn implements C.ProxyAdapter // StreamConn implements C.ProxyAdapter
func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
host, port, _ := net.SplitHostPort(v.addr) host, port, _ := net.SplitHostPort(v.addr)
wsOpts := &clashVMess.WebsocketConfig{ wsOpts := &clashVMess.WebsocketConfig{
Host: host, Host: host,
@ -105,7 +96,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
Path: v.option.WSOpts.Path, Path: v.option.WSOpts.Path,
MaxEarlyData: v.option.WSOpts.MaxEarlyData, MaxEarlyData: v.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName,
ClientFingerprint: v.option.ClientFingerprint,
Headers: http.Header{}, Headers: http.Header{},
} }
@ -124,7 +114,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
} }
if len(v.option.Fingerprint) == 0 { if len(v.option.Fingerprint) == 0 {
wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
} else { } else {
if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil { if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil {
return nil, err return nil, err
@ -143,15 +133,14 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if v.option.TLS { if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
tlsOpts.Host = v.option.ServerName tlsOpts.Host = v.option.ServerName
} }
c, err = clashVMess.StreamTLSConn(c, tlsOpts) c, err = clashVMess.StreamTLSConn(c, tlsOpts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -170,11 +159,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
case "h2": case "h2":
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := clashVMess.TLSConfig{ tlsOpts := clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -193,16 +180,14 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err = clashVMess.StreamH2Conn(c, h2Opts) c, err = clashVMess.StreamH2Conn(c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
default: default:
// handle TLS // handle TLS
if v.option.TLS { if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
Reality: v.realityConfig,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -218,24 +203,12 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
} }
if metadata.NetWork == C.UDP { if metadata.NetWork == C.UDP {
if v.option.XUDP { if v.option.XUDP {
if N.NeedHandshake(c) { return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
return v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else {
return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
} else { } else {
if N.NeedHandshake(c) { return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
return v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else {
return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
} }
} else { } else {
if N.NeedHandshake(c) { return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
return v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil
} else {
return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
} }
} }
@ -278,7 +251,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
// ListenPacketContext implements C.ProxyAdapter // ListenPacketContext implements C.ProxyAdapter
func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host) ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil { if err != nil {
@ -306,17 +279,9 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
}(c) }(c)
if v.option.XUDP { if v.option.XUDP {
if N.NeedHandshake(c) { c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
c = v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} else {
c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
} else { } else {
if N.NeedHandshake(c) { c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
c = v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} else {
c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
}
} }
if err != nil { if err != nil {
@ -329,7 +294,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr
if !metadata.Resolved() { if !metadata.Resolved() {
ip, err := resolver.ResolveIP(ctx, metadata.Host) ip, err := resolver.ResolveIP(ctx, metadata.Host)
if err != nil { if err != nil {
@ -401,7 +366,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
switch option.Network { switch option.Network {
case "h2", "grpc": case "h2", "grpc":
if !option.TLS { if !option.TLS {
option.TLS = true return nil, fmt.Errorf("TLS must be true with h2/grpc network")
} }
} }
@ -411,8 +376,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
tp: C.Vmess, tp: C.Vmess,
udp: option.UDP, udp: option.UDP,
xudp: option.XUDP,
tfo: option.TFO,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@ -437,9 +400,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
} }
gunConfig := &gun.Config{ gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName, ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName, Host: v.option.ServerName,
ClientFingerprint: v.option.ClientFingerprint,
} }
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
@ -454,15 +416,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
v.gunTLSConfig = tlsConfig v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
} }
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
return v, nil return v, nil
} }

View File

@ -17,7 +17,7 @@ import (
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/listener/sing"
wireguard "github.com/metacubex/sing-wireguard" wireguard "github.com/metacubex/sing-wireguard"
@ -34,7 +34,7 @@ type WireGuard struct {
bind *wireguard.ClientBind bind *wireguard.ClientBind
device *device.Device device *device.Device
tunDevice wireguard.Device tunDevice wireguard.Device
dialer *wgSingDialer dialer *wgDialer
startOnce sync.Once startOnce sync.Once
startErr error startErr error
} }
@ -56,28 +56,16 @@ type WireGuardOption struct {
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"` PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
} }
type wgSingDialer struct { type wgDialer struct {
dialer dialer.Dialer options []dialer.Option
} }
var _ N.Dialer = &wgSingDialer{} func (d *wgDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return dialer.DialContext(ctx, network, destination.String(), d.options...)
func (d *wgSingDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return d.dialer.DialContext(ctx, network, destination.String())
} }
func (d *wgSingDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *wgDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return d.dialer.ListenPacket(ctx, "udp", "", destination.AddrPort()) return dialer.ListenPacket(ctx, dialer.ParseNetwork("udp", destination.Addr), "", d.options...)
}
type wgNetDialer struct {
tunDevice wireguard.Device
}
var _ dialer.NetDialer = &wgNetDialer{}
func (d wgNetDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return d.tunDevice.DialContext(ctx, network, M.ParseSocksaddr(address).Unwrap())
} }
func NewWireGuard(option WireGuardOption) (*WireGuard, error) { func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
@ -91,7 +79,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
dialer: &wgSingDialer{dialer: dialer.NewDialer()}, dialer: &wgDialer{},
} }
runtime.SetFinalizer(outbound, closeWireGuard) runtime.SetFinalizer(outbound, closeWireGuard)
@ -186,14 +174,14 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
} }
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{ outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, &device.Logger{
Verbosef: func(format string, args ...interface{}) { Verbosef: func(format string, args ...interface{}) {
log.SingLogger.Debug(fmt.Sprintf(strings.ToLower(format), args...)) sing.Logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
}, },
Errorf: func(format string, args ...interface{}) { Errorf: func(format string, args ...interface{}) {
log.SingLogger.Error(fmt.Sprintf(strings.ToLower(format), args...)) sing.Logger.Error(fmt.Sprintf(strings.ToLower(format), args...))
}, },
}, option.Workers) }, option.Workers)
if debug.Enabled { if debug.Enabled {
log.SingLogger.Trace("created wireguard ipc conf: \n", ipcConf) sing.Logger.Trace("created wireguard ipc conf: \n", ipcConf)
} }
err = outbound.device.IpcSet(ipcConf) err = outbound.device.IpcSet(ipcConf)
if err != nil { if err != nil {
@ -211,8 +199,7 @@ func closeWireGuard(w *WireGuard) {
} }
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := w.Base.DialOptions(opts...) w.dialer.options = opts
w.dialer.dialer = dialer.NewDialer(options...)
var conn net.Conn var conn net.Conn
w.startOnce.Do(func() { w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start() w.startErr = w.tunDevice.Start()
@ -221,12 +208,15 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
return nil, w.startErr return nil, w.startErr
} }
if !metadata.Resolved() { if !metadata.Resolved() {
options = append(options, dialer.WithResolver(resolver.DefaultResolver)) var addrs []netip.Addr
options = append(options, dialer.WithNetDialer(wgNetDialer{tunDevice: w.tunDevice})) addrs, err = resolver.LookupIP(ctx, metadata.Host)
conn, err = dialer.NewDialer(options...).DialContext(ctx, "tcp", metadata.RemoteAddress()) if err != nil {
return nil, err
}
conn, err = N.DialSerial(ctx, w.tunDevice, "tcp", M.ParseSocksaddr(metadata.RemoteAddress()), addrs)
} else { } else {
port, _ := strconv.Atoi(metadata.DstPort) port, _ := strconv.Atoi(metadata.DstPort)
conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap()) conn, err = w.tunDevice.DialContext(ctx, "tcp", M.SocksaddrFrom(metadata.DstIP, uint16(port)))
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -238,8 +228,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
} }
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
options := w.Base.DialOptions(opts...) w.dialer.options = opts
w.dialer.dialer = dialer.NewDialer(options...)
var pc net.PacketConn var pc net.PacketConn
w.startOnce.Do(func() { w.startOnce.Do(func() {
w.startErr = w.tunDevice.Start() w.startErr = w.tunDevice.Start()
@ -258,7 +247,7 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
metadata.DstIP = ip metadata.DstIP = ip
} }
port, _ := strconv.Atoi(metadata.DstPort) port, _ := strconv.Atoi(metadata.DstPort)
pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)).Unwrap()) pc, err = w.tunDevice.ListenPacket(ctx, M.SocksaddrFrom(metadata.DstIP, uint16(port)))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -7,8 +7,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
@ -32,23 +30,11 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...) c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
c.AppendToChains(f) c.AppendToChains(f)
f.onDialSuccess()
} else { } else {
f.onDialFailed(proxy.Type(), err) f.onDialFailed(proxy.Type(), err)
} }
if N.NeedHandshake(c) {
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
f.onDialSuccess()
} else {
f.onDialFailed(proxy.Type(), err)
}
},
}
}
return c, err return c, err
} }

View File

@ -165,7 +165,6 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
for i := range gb.excludeTypeArray { for i := range gb.excludeTypeArray {
if strings.EqualFold(mType, gb.excludeTypeArray[i]) { if strings.EqualFold(mType, gb.excludeTypeArray[i]) {
flag = true flag = true
break
} }
} }

View File

@ -6,14 +6,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"sync"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/callback"
"github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/common/murmur3"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
@ -21,7 +18,7 @@ import (
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
) )
type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy type strategyFn = func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy
type LoadBalance struct { type LoadBalance struct {
*GroupBase *GroupBase
@ -86,27 +83,17 @@ func jumpHash(key uint64, buckets int32) int32 {
// DialContext implements C.ProxyAdapter // 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, opts ...dialer.Option) (c C.Conn, err error) {
proxy := lb.Unwrap(metadata, true) proxy := lb.Unwrap(metadata, true)
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
if err == nil { defer func() {
c.AppendToChains(lb) if err == nil {
} else { c.AppendToChains(lb)
lb.onDialFailed(proxy.Type(), err) lb.onDialSuccess()
} } else {
lb.onDialFailed(proxy.Type(), err)
if N.NeedHandshake(c) {
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
lb.onDialSuccess()
} else {
lb.onDialFailed(proxy.Type(), err)
}
},
} }
} }()
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
return return
} }
@ -129,25 +116,12 @@ func (lb *LoadBalance) SupportUDP() bool {
func strategyRoundRobin() strategyFn { func strategyRoundRobin() strategyFn {
idx := 0 idx := 0
idxMutex := sync.Mutex{} return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy {
idxMutex.Lock()
defer idxMutex.Unlock()
i := 0
length := len(proxies) length := len(proxies)
for i := 0; i < length; i++ {
if touch { idx = (idx + 1) % length
defer func() { proxy := proxies[idx]
idx = (idx + i) % length
}()
}
for ; i < length; i++ {
id := (idx + i) % length
proxy := proxies[id]
if proxy.Alive() { if proxy.Alive() {
i++
return proxy return proxy
} }
} }
@ -158,7 +132,7 @@ func strategyRoundRobin() strategyFn {
func strategyConsistentHashing() strategyFn { func strategyConsistentHashing() strategyFn {
maxRetry := 5 maxRetry := 5
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy { return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKey(metadata)))) key := uint64(murmur3.Sum32([]byte(getKey(metadata))))
buckets := int32(len(proxies)) buckets := int32(len(proxies))
for i := 0; i < maxRetry; i, key = i+1, key+1 { for i := 0; i < maxRetry; i, key = i+1, key+1 {
@ -186,7 +160,7 @@ func strategyStickySessions() strategyFn {
lruCache := cache.New[uint64, int]( lruCache := cache.New[uint64, int](
cache.WithAge[uint64, int](int64(ttl.Seconds())), cache.WithAge[uint64, int](int64(ttl.Seconds())),
cache.WithSize[uint64, int](1000)) cache.WithSize[uint64, int](1000))
return func(proxies []C.Proxy, metadata *C.Metadata, touch bool) C.Proxy { return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata)))) key := uint64(murmur3.Sum32([]byte(getKeyWithSrcAndDst(metadata))))
length := len(proxies) length := len(proxies)
idx, has := lruCache.Get(key) idx, has := lruCache.Get(key)
@ -218,7 +192,7 @@ func strategyStickySessions() strategyFn {
// Unwrap implements C.ProxyAdapter // Unwrap implements C.ProxyAdapter
func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy { func (lb *LoadBalance) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
proxies := lb.GetProxies(touch) proxies := lb.GetProxies(touch)
return lb.strategyFn(proxies, metadata, touch) return lb.strategyFn(proxies, metadata)
} }
// MarshalJSON implements C.ProxyAdapter // MarshalJSON implements C.ProxyAdapter

View File

@ -78,7 +78,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
providersMap[groupName] = pd providersMap[groupName] = pd
} else { } else {
if groupOption.URL == "" { if groupOption.URL == "" {
groupOption.URL = "https://cp.cloudflare.com/generate_204" groupOption.URL = "http://www.gstatic.com/generate_204"
} }
if groupOption.Interval == 0 { if groupOption.Interval == 0 {

View File

@ -176,7 +176,7 @@ func (r *Relay) proxies(metadata *C.Metadata, touch bool) ([]C.Proxy, []C.Proxy)
} }
func (r *Relay) Addr() string { func (r *Relay) Addr() string {
proxies, _ := r.proxies(nil, false) proxies, _ := r.proxies(nil, true)
return proxies[len(proxies)-1].Addr() return proxies[len(proxies)-1].Addr()
} }

View File

@ -6,8 +6,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -40,23 +38,10 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
c.AppendToChains(u) c.AppendToChains(u)
u.onDialSuccess()
} else { } else {
u.onDialFailed(proxy.Type(), err) u.onDialFailed(proxy.Type(), err)
} }
if N.NeedHandshake(c) {
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
u.onDialSuccess()
} else {
u.onDialFailed(proxy.Type(), err)
}
},
}
}
return c, err return c, err
} }

View File

@ -2,9 +2,6 @@ package adapter
import ( import (
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -23,7 +20,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
) )
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption) err = decoder.Decode(mapping, ssOption)
if err != nil { if err != nil {
break break
@ -56,16 +53,14 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
Method: "GET", Method: "GET",
Path: []string{"/"}, Path: []string{"/"},
}, },
ClientFingerprint: tlsC.GetGlobalFingerprint(),
} }
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
if err != nil { if err != nil {
break break
} }
proxy, err = outbound.NewVmess(*vmessOption) proxy, err = outbound.NewVmess(*vmessOption)
case "vless": case "vless":
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} vlessOption := &outbound.VlessOption{}
err = decoder.Decode(mapping, vlessOption) err = decoder.Decode(mapping, vlessOption)
if err != nil { if err != nil {
break break
@ -79,7 +74,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
} }
proxy, err = outbound.NewSnell(*snellOption) proxy, err = outbound.NewSnell(*snellOption)
case "trojan": case "trojan":
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break

View File

@ -6,10 +6,10 @@ import (
"github.com/Dreamacro/clash/common/batch" "github.com/Dreamacro/clash/common/batch"
"github.com/Dreamacro/clash/common/singledo" "github.com/Dreamacro/clash/common/singledo"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/gofrs/uuid"
"go.uber.org/atomic" "go.uber.org/atomic"
) )
@ -34,15 +34,16 @@ type HealthCheck struct {
func (hc *HealthCheck) process() { func (hc *HealthCheck) process() {
ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) ticker := time.NewTicker(time.Duration(hc.interval) * time.Second)
go func() {
time.Sleep(30 * time.Second)
hc.lazyCheck()
}()
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
now := time.Now().Unix() hc.lazyCheck()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
hc.check()
} else {
log.Debugln("Skip once health check because we are lazy")
}
case <-hc.done: case <-hc.done:
ticker.Stop() ticker.Stop()
return return
@ -50,6 +51,17 @@ func (hc *HealthCheck) process() {
} }
} }
func (hc *HealthCheck) lazyCheck() bool {
now := time.Now().Unix()
if !hc.lazy || now-hc.lastTouch.Load() < int64(hc.interval) {
hc.check()
return true
} else {
log.Debugln("Skip once health check because we are lazy")
return false
}
}
func (hc *HealthCheck) setProxy(proxies []C.Proxy) { func (hc *HealthCheck) setProxy(proxies []C.Proxy) {
hc.proxies = proxies hc.proxies = proxies
} }
@ -64,7 +76,10 @@ func (hc *HealthCheck) touch() {
func (hc *HealthCheck) check() { func (hc *HealthCheck) check() {
_, _, _ = hc.singleDo.Do(func() (struct{}, error) { _, _, _ = hc.singleDo.Do(func() (struct{}, error) {
id := utils.NewUUIDV4().String() id := ""
if uid, err := uuid.NewV4(); err == nil {
id = uid.String()
}
log.Debugln("Start New Health Checking {%s}", id) log.Debugln("Start New Health Checking {%s}", id)
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10)) b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
for _, proxy := range hc.proxies { for _, proxy := range hc.proxies {

View File

@ -80,7 +80,6 @@ func (pp *proxySetProvider) Initial() error {
return err return err
} }
pp.OnUpdate(elm) pp.OnUpdate(elm)
pp.getSubscriptionInfo()
return nil return nil
} }
@ -100,7 +99,7 @@ func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
pp.proxies = proxies pp.proxies = proxies
pp.healthCheck.setProxy(proxies) pp.healthCheck.setProxy(proxies)
if pp.healthCheck.auto() { if pp.healthCheck.auto() {
go pp.healthCheck.check() defer func() { go pp.healthCheck.lazyCheck() }()
} }
} }
@ -173,6 +172,8 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd))
pd.Fetcher = fetcher pd.Fetcher = fetcher
pd.getSubscriptionInfo()
wrapper := &ProxySetProvider{pd} wrapper := &ProxySetProvider{pd}
runtime.SetFinalizer(wrapper, stopProxyProvider) runtime.SetFinalizer(wrapper, stopProxyProvider)
return wrapper, nil return wrapper, nil
@ -300,7 +301,6 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
for i := range excludeTypeArray { for i := range excludeTypeArray {
if strings.EqualFold(pType, excludeTypeArray[i]) { if strings.EqualFold(pType, excludeTypeArray[i]) {
flag = true flag = true
break
} }
} }

View File

@ -1,25 +0,0 @@
package buf
import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
)
const BufferSize = buf.BufferSize
type Buffer = buf.Buffer
var New = buf.New
var StackNew = buf.StackNew
var StackNewSize = buf.StackNewSize
var With = buf.With
var KeepAlive = common.KeepAlive
//go:norace
func Dup[T any](obj T) T {
return common.Dup(obj)
}
var Must = common.Must
var Error = common.Error

View File

@ -1,25 +0,0 @@
package callback
import (
C "github.com/Dreamacro/clash/constant"
)
type FirstWriteCallBackConn struct {
C.Conn
Callback func(error)
written bool
}
func (c *FirstWriteCallBackConn) Write(b []byte) (n int, err error) {
defer func() {
if !c.written {
c.written = true
c.Callback(err)
}
}()
return c.Conn.Write(b)
}
func (c *FirstWriteCallBackConn) Upstream() any {
return c.Conn
}

View File

@ -2,10 +2,8 @@ package convert
import ( import (
"bytes" "bytes"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/Dreamacro/clash/log"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
@ -83,7 +81,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
trojan["port"] = urlTrojan.Port() trojan["port"] = urlTrojan.Port()
trojan["password"] = urlTrojan.User.Username() trojan["password"] = urlTrojan.User.Username()
trojan["udp"] = true trojan["udp"] = true
trojan["skip-cert-verify"], _ = strconv.ParseBool(query.Get("allowInsecure")) trojan["skip-cert-verify"] = false
sni := query.Get("sni") sni := query.Get("sni")
if sni != "" { if sni != "" {
@ -113,12 +111,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
trojan["grpc-opts"] = grpcOpts trojan["grpc-opts"] = grpcOpts
} }
if fingerprint := query.Get("fp"); fingerprint == "" {
trojan["client-fingerprint"] = "chrome"
} else {
trojan["client-fingerprint"] = fingerprint
}
proxies = append(proxies, trojan) proxies = append(proxies, trojan)
case "vless": case "vless":
@ -128,11 +120,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
query := urlVLess.Query() query := urlVLess.Query()
vless := make(map[string]any, 20) vless := make(map[string]any, 20)
err = handleVShareLink(names, urlVLess, scheme, vless) handleVShareLink(names, urlVLess, scheme, vless)
if err != nil {
log.Warnln("error:%s line:%s", err.Error(), line)
continue
}
if flow := query.Get("flow"); flow != "" { if flow := query.Get("flow"); flow != "" {
vless["flow"] = strings.ToLower(flow) vless["flow"] = strings.ToLower(flow)
} }
@ -150,11 +138,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
query := urlVMess.Query() query := urlVMess.Query()
vmess := make(map[string]any, 20) vmess := make(map[string]any, 20)
err = handleVShareLink(names, urlVMess, scheme, vmess) handleVShareLink(names, urlVMess, scheme, vmess)
if err != nil {
log.Warnln("error:%s line:%s", err.Error(), line)
continue
}
vmess["alterId"] = 0 vmess["alterId"] = 0
vmess["cipher"] = "auto" vmess["cipher"] = "auto"
if encryption := query.Get("encryption"); encryption != "" { if encryption := query.Get("encryption"); encryption != "" {
@ -288,19 +272,16 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
cipher string cipher string
password string password string
) )
cipher = cipherRaw
if password, found = urlSS.User.Password(); !found { if password, found = urlSS.User.Password(); !found {
dcBuf, err := base64.RawURLEncoding.DecodeString(cipherRaw) dcBuf, _ := enc.DecodeString(cipherRaw)
if err != nil {
dcBuf, _ = enc.DecodeString(cipherRaw)
}
cipher, password, found = strings.Cut(string(dcBuf), ":") cipher, password, found = strings.Cut(string(dcBuf), ":")
if !found { if !found {
continue continue
} }
err = VerifyMethod(cipher, password) err := VerifyMethod(cipher, password)
if err != nil { if err != nil {
dcBuf, _ = encRaw.DecodeString(cipherRaw) dcBuf, _ := encRaw.DecodeString(cipherRaw)
cipher, password, found = strings.Cut(string(dcBuf), ":") cipher, password, found = strings.Cut(string(dcBuf), ":")
} }
} }

View File

@ -2,14 +2,12 @@ package convert
import ( import (
"encoding/base64" "encoding/base64"
"github.com/metacubex/sing-shadowsocks/shadowimpl"
"math/rand"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/Dreamacro/clash/common/utils" "github.com/gofrs/uuid"
"github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/zhangyunhao116/fastrand"
) )
var hostsSuffix = []string{ var hostsSuffix = []string{
@ -294,7 +292,8 @@ var (
) )
func RandHost() string { func RandHost() string {
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(utils.NewUUIDV4().Bytes())) id, _ := uuid.NewV4()
base := strings.ToLower(base64.RawURLEncoding.EncodeToString(id.Bytes()))
base = strings.ReplaceAll(base, "-", "") base = strings.ReplaceAll(base, "-", "")
base = strings.ReplaceAll(base, "_", "") base = strings.ReplaceAll(base, "_", "")
buf := []byte(base) buf := []byte(base)
@ -302,11 +301,11 @@ func RandHost() string {
prefix += string(buf[6:8]) + "-" prefix += string(buf[6:8]) + "-"
prefix += string(buf[len(buf)-8:]) prefix += string(buf[len(buf)-8:])
return prefix + hostsSuffix[fastrand.Intn(hostsLen)] return prefix + hostsSuffix[rand.Intn(hostsLen)]
} }
func RandUserAgent() string { func RandUserAgent() string {
return userAgents[fastrand.Intn(uaLen)] return userAgents[rand.Intn(uaLen)]
} }
func SetUserAgent(header http.Header) { func SetUserAgent(header http.Header) {
@ -318,6 +317,6 @@ func SetUserAgent(header http.Header) {
} }
func VerifyMethod(cipher, password string) (err error) { func VerifyMethod(cipher, password string) (err error) {
_, err = shadowimpl.FetchMethod(cipher, password, time.Now) _, err = shadowimpl.FetchMethod(cipher, password)
return return
} }

View File

@ -1,24 +1,15 @@
package convert package convert
import ( import (
"errors"
"fmt"
"net/url" "net/url"
"strconv"
"strings" "strings"
) )
func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy map[string]any) error { func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy map[string]any) {
// Xray VMessAEAD / VLESS share link standard // Xray VMessAEAD / VLESS share link standard
// https://github.com/XTLS/Xray-core/discussions/716 // https://github.com/XTLS/Xray-core/discussions/716
query := url.Query() query := url.Query()
proxy["name"] = uniqueName(names, url.Fragment) proxy["name"] = uniqueName(names, url.Fragment)
if url.Hostname() == "" {
return errors.New("url.Hostname() is empty")
}
if url.Port() == "" {
return errors.New("url.Port() is empty")
}
proxy["type"] = scheme proxy["type"] = scheme
proxy["server"] = url.Hostname() proxy["server"] = url.Hostname()
proxy["port"] = url.Port() proxy["port"] = url.Port()
@ -27,23 +18,12 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
proxy["skip-cert-verify"] = false proxy["skip-cert-verify"] = false
proxy["tls"] = false proxy["tls"] = false
tls := strings.ToLower(query.Get("security")) tls := strings.ToLower(query.Get("security"))
if strings.HasSuffix(tls, "tls") || tls == "reality" { if strings.HasSuffix(tls, "tls") {
proxy["tls"] = true proxy["tls"] = true
if fingerprint := query.Get("fp"); fingerprint == "" {
proxy["client-fingerprint"] = "chrome"
} else {
proxy["client-fingerprint"] = fingerprint
}
} }
if sni := query.Get("sni"); sni != "" { if sni := query.Get("sni"); sni != "" {
proxy["servername"] = sni proxy["servername"] = sni
} }
if realityPublicKey := query.Get("pbk"); realityPublicKey != "" {
proxy["reality-opts"] = map[string]any{
"public-key": realityPublicKey,
"short-id": query.Get("sid"),
}
}
switch query.Get("packetEncoding") { switch query.Get("packetEncoding") {
case "none": case "none":
@ -107,17 +87,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
wsOpts["path"] = query.Get("path") wsOpts["path"] = query.Get("path")
wsOpts["headers"] = headers wsOpts["headers"] = headers
if earlyData := query.Get("ed"); earlyData != "" {
med, err := strconv.Atoi(earlyData)
if err != nil {
return fmt.Errorf("bad WebSocket max early data size: %v", err)
}
wsOpts["max-early-data"] = med
}
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
wsOpts["early-data-header-name"] = earlyDataHeader
}
proxy["ws-opts"] = wsOpts proxy["ws-opts"] = wsOpts
case "grpc": case "grpc":
@ -125,5 +94,4 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
grpcOpts["grpc-service-name"] = query.Get("serviceName") grpcOpts["grpc-service-name"] = query.Get("serviceName")
proxy["grpc-opts"] = grpcOpts proxy["grpc-opts"] = grpcOpts
} }
return nil
} }

View File

@ -5,10 +5,10 @@
// Package list implements a doubly linked list. // Package list implements a doubly linked list.
// //
// To iterate over a list (where l is a *List): // To iterate over a list (where l is a *List):
//
// for e := l.Front(); e != nil; e = e.Next() { // for e := l.Front(); e != nil; e = e.Next() {
// // do something with e.Value // // do something with e.Value
// } // }
//
package list package list
// Element is an element of a linked list. // Element is an element of a linked list.

View File

@ -1,36 +0,0 @@
package net
import (
"net"
)
type CustomAddr interface {
net.Addr
RawAddr() net.Addr
}
type customAddr struct {
networkStr string
addrStr string
rawAddr net.Addr
}
func (a customAddr) Network() string {
return a.networkStr
}
func (a customAddr) String() string {
return a.addrStr
}
func (a customAddr) RawAddr() net.Addr {
return a.rawAddr
}
func NewCustomAddr(networkStr string, addrStr string, rawAddr net.Addr) CustomAddr {
return customAddr{
networkStr: networkStr,
addrStr: addrStr,
rawAddr: rawAddr,
}
}

View File

@ -3,23 +3,18 @@ package net
import ( import (
"bufio" "bufio"
"net" "net"
"github.com/Dreamacro/clash/common/buf"
) )
var _ ExtendedConn = (*BufferedConn)(nil)
type BufferedConn struct { type BufferedConn struct {
r *bufio.Reader r *bufio.Reader
ExtendedConn net.Conn
peeked bool
} }
func NewBufferedConn(c net.Conn) *BufferedConn { func NewBufferedConn(c net.Conn) *BufferedConn {
if bc, ok := c.(*BufferedConn); ok { if bc, ok := c.(*BufferedConn); ok {
return bc return bc
} }
return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c), false} return &BufferedConn{bufio.NewReader(c), c}
} }
// Reader returns the internal bufio.Reader. // Reader returns the internal bufio.Reader.
@ -27,24 +22,11 @@ func (c *BufferedConn) Reader() *bufio.Reader {
return c.r return c.r
} }
func (c *BufferedConn) ResetPeeked() {
c.peeked = false
}
func (c *BufferedConn) Peeked() bool {
return c.peeked
}
// Peek returns the next n bytes without advancing the reader. // Peek returns the next n bytes without advancing the reader.
func (c *BufferedConn) Peek(n int) ([]byte, error) { func (c *BufferedConn) Peek(n int) ([]byte, error) {
c.peeked = true
return c.r.Peek(n) return c.r.Peek(n)
} }
func (c *BufferedConn) Discard(n int) (discarded int, err error) {
return c.r.Discard(n)
}
func (c *BufferedConn) Read(p []byte) (int, error) { func (c *BufferedConn) Read(p []byte) (int, error) {
return c.r.Read(p) return c.r.Read(p)
} }
@ -60,22 +42,3 @@ func (c *BufferedConn) UnreadByte() error {
func (c *BufferedConn) Buffered() int { func (c *BufferedConn) Buffered() int {
return c.r.Buffered() return c.r.Buffered()
} }
func (c *BufferedConn) ReadBuffer(buffer *buf.Buffer) (err error) {
if c.r.Buffered() > 0 {
_, err = buffer.ReadOnceFrom(c.r)
return
}
return c.ExtendedConn.ReadBuffer(buffer)
}
func (c *BufferedConn) Upstream() any {
return c.ExtendedConn
}
func (c *BufferedConn) ReaderReplaceable() bool {
if c.r.Buffered() > 0 {
return false
}
return true
}

View File

@ -51,10 +51,6 @@ func (c *refConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t) return c.conn.SetWriteDeadline(t)
} }
func (c *refConn) Upstream() any {
return c.conn
}
func NewRefConn(conn net.Conn, ref any) net.Conn { func NewRefConn(conn net.Conn, ref any) net.Conn {
return &refConn{conn: conn, ref: ref} return &refConn{conn: conn, ref: ref}
} }

View File

@ -1,24 +1,24 @@
package net package net
//import ( import (
// "io" "io"
// "net" "net"
// "time" "time"
//) )
//
//// Relay copies between left and right bidirectionally. // Relay copies between left and right bidirectionally.
//func Relay(leftConn, rightConn net.Conn) { func Relay(leftConn, rightConn net.Conn) {
// ch := make(chan error) ch := make(chan error)
//
// go func() { go func() {
// // Wrapping to avoid using *net.TCPConn.(ReadFrom) // Wrapping to avoid using *net.TCPConn.(ReadFrom)
// // See also https://github.com/Dreamacro/clash/pull/1209 // See also https://github.com/Dreamacro/clash/pull/1209
// _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn})
// leftConn.SetReadDeadline(time.Now()) leftConn.SetReadDeadline(time.Now())
// ch <- err ch <- err
// }() }()
//
// _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}) _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn})
// rightConn.SetReadDeadline(time.Now()) rightConn.SetReadDeadline(time.Now())
// <-ch <-ch
//} }

View File

@ -1,30 +0,0 @@
package net
import (
"context"
"net"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/network"
)
var NewExtendedConn = bufio.NewExtendedConn
var NewExtendedWriter = bufio.NewExtendedWriter
var NewExtendedReader = bufio.NewExtendedReader
type ExtendedConn = network.ExtendedConn
type ExtendedWriter = network.ExtendedWriter
type ExtendedReader = network.ExtendedReader
func NeedHandshake(conn any) bool {
if earlyConn, isEarlyConn := common.Cast[network.EarlyConn](conn); isEarlyConn && earlyConn.NeedHandshake() {
return true
}
return false
}
// Relay copies between left and right bidirectionally.
func Relay(leftConn, rightConn net.Conn) {
_ = bufio.CopyConn(context.TODO(), leftConn, rightConn)
}

View File

@ -1,19 +1,11 @@
package net package net
import ( import (
"crypto/rand"
"crypto/rsa"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt" "fmt"
"math/big"
) )
func ParseCert(certificate, privateKey string) (tls.Certificate, error) { func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
if certificate == "" || privateKey == "" {
return newRandomTLSKeyPair()
}
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
if painTextErr == nil { if painTextErr == nil {
return cert, nil return cert, nil
@ -25,28 +17,3 @@ func ParseCert(certificate, privateKey string) (tls.Certificate, error) {
} }
return cert, nil return cert, nil
} }
func newRandomTLSKeyPair() (tls.Certificate, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return tls.Certificate{}, err
}
template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate(
rand.Reader,
&template,
&template,
&key.PublicKey,
key)
if err != nil {
return tls.Certificate{}, err
}
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return tls.Certificate{}, err
}
return tlsCert, nil
}

View File

@ -1,131 +0,0 @@
package net
import (
"encoding/binary"
"math/bits"
)
// kanged from https://github.com/nhooyr/websocket/blob/master/frame.go
// License: MIT
// MaskWebSocket applies the WebSocket masking algorithm to p
// with the given key.
// See https://tools.ietf.org/html/rfc6455#section-5.3
//
// The returned value is the correctly rotated key to
// to continue to mask/unmask the message.
//
// It is optimized for LittleEndian and expects the key
// to be in little endian.
//
// See https://github.com/golang/go/issues/31586
func MaskWebSocket(key uint32, b []byte) uint32 {
if len(b) >= 8 {
key64 := uint64(key)<<32 | uint64(key)
// At some point in the future we can clean these unrolled loops up.
// See https://github.com/golang/go/issues/31586#issuecomment-487436401
// Then we xor until b is less than 128 bytes.
for len(b) >= 128 {
v := binary.LittleEndian.Uint64(b)
binary.LittleEndian.PutUint64(b, v^key64)
v = binary.LittleEndian.Uint64(b[8:16])
binary.LittleEndian.PutUint64(b[8:16], v^key64)
v = binary.LittleEndian.Uint64(b[16:24])
binary.LittleEndian.PutUint64(b[16:24], v^key64)
v = binary.LittleEndian.Uint64(b[24:32])
binary.LittleEndian.PutUint64(b[24:32], v^key64)
v = binary.LittleEndian.Uint64(b[32:40])
binary.LittleEndian.PutUint64(b[32:40], v^key64)
v = binary.LittleEndian.Uint64(b[40:48])
binary.LittleEndian.PutUint64(b[40:48], v^key64)
v = binary.LittleEndian.Uint64(b[48:56])
binary.LittleEndian.PutUint64(b[48:56], v^key64)
v = binary.LittleEndian.Uint64(b[56:64])
binary.LittleEndian.PutUint64(b[56:64], v^key64)
v = binary.LittleEndian.Uint64(b[64:72])
binary.LittleEndian.PutUint64(b[64:72], v^key64)
v = binary.LittleEndian.Uint64(b[72:80])
binary.LittleEndian.PutUint64(b[72:80], v^key64)
v = binary.LittleEndian.Uint64(b[80:88])
binary.LittleEndian.PutUint64(b[80:88], v^key64)
v = binary.LittleEndian.Uint64(b[88:96])
binary.LittleEndian.PutUint64(b[88:96], v^key64)
v = binary.LittleEndian.Uint64(b[96:104])
binary.LittleEndian.PutUint64(b[96:104], v^key64)
v = binary.LittleEndian.Uint64(b[104:112])
binary.LittleEndian.PutUint64(b[104:112], v^key64)
v = binary.LittleEndian.Uint64(b[112:120])
binary.LittleEndian.PutUint64(b[112:120], v^key64)
v = binary.LittleEndian.Uint64(b[120:128])
binary.LittleEndian.PutUint64(b[120:128], v^key64)
b = b[128:]
}
// Then we xor until b is less than 64 bytes.
for len(b) >= 64 {
v := binary.LittleEndian.Uint64(b)
binary.LittleEndian.PutUint64(b, v^key64)
v = binary.LittleEndian.Uint64(b[8:16])
binary.LittleEndian.PutUint64(b[8:16], v^key64)
v = binary.LittleEndian.Uint64(b[16:24])
binary.LittleEndian.PutUint64(b[16:24], v^key64)
v = binary.LittleEndian.Uint64(b[24:32])
binary.LittleEndian.PutUint64(b[24:32], v^key64)
v = binary.LittleEndian.Uint64(b[32:40])
binary.LittleEndian.PutUint64(b[32:40], v^key64)
v = binary.LittleEndian.Uint64(b[40:48])
binary.LittleEndian.PutUint64(b[40:48], v^key64)
v = binary.LittleEndian.Uint64(b[48:56])
binary.LittleEndian.PutUint64(b[48:56], v^key64)
v = binary.LittleEndian.Uint64(b[56:64])
binary.LittleEndian.PutUint64(b[56:64], v^key64)
b = b[64:]
}
// Then we xor until b is less than 32 bytes.
for len(b) >= 32 {
v := binary.LittleEndian.Uint64(b)
binary.LittleEndian.PutUint64(b, v^key64)
v = binary.LittleEndian.Uint64(b[8:16])
binary.LittleEndian.PutUint64(b[8:16], v^key64)
v = binary.LittleEndian.Uint64(b[16:24])
binary.LittleEndian.PutUint64(b[16:24], v^key64)
v = binary.LittleEndian.Uint64(b[24:32])
binary.LittleEndian.PutUint64(b[24:32], v^key64)
b = b[32:]
}
// Then we xor until b is less than 16 bytes.
for len(b) >= 16 {
v := binary.LittleEndian.Uint64(b)
binary.LittleEndian.PutUint64(b, v^key64)
v = binary.LittleEndian.Uint64(b[8:16])
binary.LittleEndian.PutUint64(b[8:16], v^key64)
b = b[16:]
}
// Then we xor until b is less than 8 bytes.
for len(b) >= 8 {
v := binary.LittleEndian.Uint64(b)
binary.LittleEndian.PutUint64(b, v^key64)
b = b[8:]
}
}
// Then we xor until b is less than 4 bytes.
for len(b) >= 4 {
v := binary.LittleEndian.Uint32(b)
binary.LittleEndian.PutUint32(b, v^key)
b = b[4:]
}
// xor remaining bytes.
for i := range b {
b[i] ^= byte(key)
key = bits.RotateLeft32(key, -8)
}
return key
}

View File

@ -1,10 +1,10 @@
package pool package pool
import ( import (
"math/rand"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/zhangyunhao116/fastrand"
) )
func TestAllocGet(t *testing.T) { func TestAllocGet(t *testing.T) {
@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
func BenchmarkMSB(b *testing.B) { func BenchmarkMSB(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
msb(fastrand.Int()) msb(rand.Int())
} }
} }

View File

@ -1,15 +0,0 @@
//go:build with_low_memory
package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 16 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 8 * 1024
)

View File

@ -1,15 +0,0 @@
//go:build !with_low_memory
package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024
)

View File

@ -1,5 +1,17 @@
package pool package pool
const (
// io.Copy default buffer size is 32 KiB
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
RelayBufferSize = 20 * 1024
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
// set to 9000, so the UDP Buffer size set to 16Kib
UDPBufferSize = 16 * 1024
)
func Get(size int) []byte { func Get(size int) []byte {
return defaultAllocator.Get(size) return defaultAllocator.Get(size)
} }

View File

@ -1,8 +0,0 @@
package utils
func MustOK[T any](result T, ok bool) T {
if ok {
return result
}
panic("operation failed")
}

View File

@ -1,34 +0,0 @@
package utils
import (
"errors"
"fmt"
"reflect"
)
func Filter[T comparable](tSlice []T, filter func(t T) bool) []T {
result := make([]T, 0)
for _, t := range tSlice {
if filter(t) {
result = append(result, t)
}
}
return result
}
func ToStringSlice(value any) ([]string, error) {
strArr := make([]string, 0)
switch reflect.TypeOf(value).Kind() {
case reflect.Slice, reflect.Array:
origin := reflect.ValueOf(value)
for i := 0; i < origin.Len(); i++ {
item := fmt.Sprintf("%v", origin.Index(i))
strArr = append(strArr, item)
}
case reflect.String:
strArr = append(strArr, fmt.Sprintf("%v", value))
default:
return nil, errors.New("value format error, must be string or array")
}
return strArr, nil
}

View File

@ -1,9 +0,0 @@
package utils
func Reverse(s string) string {
a := []rune(s)
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}
return string(a)
}

View File

@ -2,50 +2,15 @@ package utils
import ( import (
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/zhangyunhao116/fastrand"
) )
type fastRandReader struct{} var uuidNamespace, _ = uuid.FromString("00000000-0000-0000-0000-000000000000")
func (r fastRandReader) Read(p []byte) (int, error) {
return fastrand.Read(p)
}
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
func NewUUIDV1() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV1() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
return UnsafeUUIDGenerator.NewV3(ns, name)
}
func NewUUIDV4() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV4() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
return UnsafeUUIDGenerator.NewV5(ns, name)
}
func NewUUIDV6() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV6() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
func NewUUIDV7() uuid.UUID {
u, _ := UnsafeUUIDGenerator.NewV7() // fastrand.Read wouldn't cause error, so ignore err is safe
return u
}
// UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090 // UUIDMap https://github.com/XTLS/Xray-core/issues/158#issue-783294090
func UUIDMap(str string) (uuid.UUID, error) { func UUIDMap(str string) (uuid.UUID, error) {
u, err := uuid.FromString(str) u, err := uuid.FromString(str)
if err != nil { if err != nil {
return NewUUIDV5(uuid.Nil, str), nil return uuid.NewV5(uuidNamespace, str), nil
} }
return u, nil return u, nil
} }

View File

@ -1,7 +1,6 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
@ -11,8 +10,16 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func bindControl(ifaceIdx int) controlFn { type controlFn = func(network, address string, c syscall.RawConn) error
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
func bindControl(ifaceIdx int, chain controlFn) controlFn {
return func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
addrPort, err := netip.ParseAddrPort(address) addrPort, err := netip.ParseAddrPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() { if err == nil && !addrPort.Addr().IsGlobalUnicast() {
return return
@ -42,7 +49,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
return err return err
} }
addControlToDialer(dialer, bindControl(ifaceObj.Index)) dialer.Control = bindControl(ifaceObj.Index, dialer.Control)
return nil return nil
} }
@ -52,10 +59,6 @@ func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address
return "", err return "", err
} }
addControlToListenConfig(lc, bindControl(ifaceObj.Index)) lc.Control = bindControl(ifaceObj.Index, lc.Control)
return address, nil return address, nil
} }
func ParseNetwork(network string, addr netip.Addr) string {
return network
}

View File

@ -1,7 +1,6 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
@ -9,8 +8,16 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func bindControl(ifaceName string) controlFn { type controlFn = func(network, address string, c syscall.RawConn) error
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
func bindControl(ifaceName string, chain controlFn) controlFn {
return func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
addrPort, err := netip.ParseAddrPort(address) addrPort, err := netip.ParseAddrPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() { if err == nil && !addrPort.Addr().IsGlobalUnicast() {
return return
@ -30,17 +37,13 @@ func bindControl(ifaceName string) controlFn {
} }
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
addControlToDialer(dialer, bindControl(ifaceName)) dialer.Control = bindControl(ifaceName, dialer.Control)
return nil return nil
} }
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
addControlToListenConfig(lc, bindControl(ifaceName)) lc.Control = bindControl(ifaceName, lc.Control)
return address, nil return address, nil
} }
func ParseNetwork(network string, addr netip.Addr) string {
return network
}

View File

@ -1,4 +1,4 @@
//go:build !linux && !darwin && !windows //go:build !linux && !darwin
package dialer package dialer
@ -91,13 +91,3 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add
return addr.String(), nil return addr.String(), nil
} }
func ParseNetwork(network string, addr netip.Addr) string {
// fix bindIfaceToListenConfig() force bind to an ipv4 address
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
return network
}

View File

@ -1,92 +0,0 @@
package dialer
import (
"context"
"encoding/binary"
"net"
"net/netip"
"syscall"
"unsafe"
"github.com/Dreamacro/clash/component/iface"
)
const (
IP_UNICAST_IF = 31
IPV6_UNICAST_IF = 31
)
func bind4(handle syscall.Handle, ifaceIdx int) error {
var bytes [4]byte
binary.BigEndian.PutUint32(bytes[:], uint32(ifaceIdx))
idx := *(*uint32)(unsafe.Pointer(&bytes[0]))
return syscall.SetsockoptInt(handle, syscall.IPPROTO_IP, IP_UNICAST_IF, int(idx))
}
func bind6(handle syscall.Handle, ifaceIdx int) error {
return syscall.SetsockoptInt(handle, syscall.IPPROTO_IPV6, IPV6_UNICAST_IF, ifaceIdx)
}
func bindControl(ifaceIdx int) controlFn {
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
addrPort, err := netip.ParseAddrPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
return
}
var innerErr error
err = c.Control(func(fd uintptr) {
handle := syscall.Handle(fd)
bind6err := bind6(handle, ifaceIdx)
bind4err := bind4(handle, ifaceIdx)
switch network {
case "ip6", "tcp6":
innerErr = bind6err
case "ip4", "tcp4", "udp4":
innerErr = bind4err
case "udp6":
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
if bind4err != nil {
innerErr = bind6err
} else {
innerErr = bind4err
}
} else {
innerErr = bind6err
}
}
})
if innerErr != nil {
err = innerErr
}
return
}
}
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return err
}
addControlToDialer(dialer, bindControl(ifaceObj.Index))
return nil
}
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
ifaceObj, err := iface.ResolveInterface(ifaceName)
if err != nil {
return "", err
}
addControlToListenConfig(lc, bindControl(ifaceObj.Index))
return address, nil
}
func ParseNetwork(network string, addr netip.Addr) string {
return network
}

View File

@ -1,22 +0,0 @@
package dialer
import (
"context"
"net"
"syscall"
)
type controlFn = func(ctx context.Context, network, address string, c syscall.RawConn) error
func addControlToListenConfig(lc *net.ListenConfig, fn controlFn) {
llc := *lc
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
switch {
case llc.Control != nil:
if err = llc.Control(network, address, c); err != nil {
return
}
}
return fn(context.Background(), network, address, c)
}
}

View File

@ -1,22 +0,0 @@
//go:build !go1.20
package dialer
import (
"context"
"net"
"syscall"
)
func addControlToDialer(d *net.Dialer, fn controlFn) {
ld := *d
d.Control = func(network, address string, c syscall.RawConn) (err error) {
switch {
case ld.Control != nil:
if err = ld.Control(network, address, c); err != nil {
return
}
}
return fn(context.Background(), network, address, c)
}
}

View File

@ -1,26 +0,0 @@
//go:build go1.20
package dialer
import (
"context"
"net"
"syscall"
)
func addControlToDialer(d *net.Dialer, fn controlFn) {
ld := *d
d.ControlContext = func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
switch {
case ld.ControlContext != nil:
if err = ld.ControlContext(ctx, network, address, c); err != nil {
return
}
case ld.Control != nil:
if err = ld.Control(network, address, c); err != nil {
return
}
}
return fn(ctx, network, address, c)
}
}

View File

@ -2,27 +2,40 @@ package dialer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"os" "runtime"
"strings" "strings"
"sync" "sync"
"time"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
)
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) "go.uber.org/atomic"
)
var ( var (
dialMux sync.Mutex dialMux sync.Mutex
actualSingleStackDialContext = serialSingleStackDialContext actualSingleDialContext = singleDialContext
actualDualStackDialContext = serialDualStackDialContext actualDualStackDialContext = dualStackDialContext
tcpConcurrent = false tcpConcurrent = false
fallbackTimeout = 300 * time.Millisecond DisableIPv6 = false
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel")
) )
func ParseNetwork(network string, addr netip.Addr) string {
if runtime.GOOS == "windows" { // fix bindIfaceToListenConfig() in windows force bind to an ipv4 address
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
}
return network
}
func applyOptions(options ...Option) *option { func applyOptions(options ...Option) *option {
opt := &option{ opt := &option{
interfaceName: DefaultInterface.Load(), interfaceName: DefaultInterface.Load(),
@ -53,23 +66,29 @@ func DialContext(ctx context.Context, network, address string, options ...Option
network = fmt.Sprintf("%s%d", network, opt.network) network = fmt.Sprintf("%s%d", network, opt.network)
} }
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
if err != nil {
return nil, err
}
switch network { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
return actualSingleStackDialContext(ctx, network, ips, port, opt) return actualSingleDialContext(ctx, network, address, opt)
case "tcp", "udp": case "tcp", "udp":
return actualDualStackDialContext(ctx, network, ips, port, opt) return actualDualStackDialContext(ctx, network, address, opt)
default: default:
return nil, ErrorInvalidedNetworkStack return nil, ErrorInvalidedNetworkStack
} }
} }
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) { func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
cfg := applyOptions(options...) cfg := &option{
interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
}
for _, o := range DefaultOptions {
o(cfg)
}
for _, o := range options {
o(cfg)
}
lc := &net.ListenConfig{} lc := &net.ListenConfig{}
if cfg.interfaceName != "" { if cfg.interfaceName != "" {
@ -89,40 +108,26 @@ func ListenPacket(ctx context.Context, network, address string, options ...Optio
return lc.ListenPacket(ctx, network, address) return lc.ListenPacket(ctx, network, address)
} }
func SetTcpConcurrent(concurrent bool) { func SetDial(concurrent bool) {
dialMux.Lock() dialMux.Lock()
defer dialMux.Unlock()
tcpConcurrent = concurrent tcpConcurrent = concurrent
if concurrent { if concurrent {
actualSingleStackDialContext = concurrentSingleStackDialContext actualSingleDialContext = concurrentSingleDialContext
actualDualStackDialContext = concurrentDualStackDialContext actualDualStackDialContext = concurrentDualStackDialContext
} else { } else {
actualSingleStackDialContext = serialSingleStackDialContext actualSingleDialContext = singleDialContext
actualDualStackDialContext = serialDualStackDialContext actualDualStackDialContext = dualStackDialContext
} }
dialMux.Unlock()
} }
func GetTcpConcurrent() bool { func GetDial() bool {
dialMux.Lock()
defer dialMux.Unlock()
return tcpConcurrent return tcpConcurrent
} }
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
address := net.JoinHostPort(destination.String(), port) dialer := &net.Dialer{}
netDialer := opt.netDialer
switch netDialer.(type) {
case nil:
netDialer = &net.Dialer{}
case *net.Dialer:
_netDialer := *netDialer.(*net.Dialer)
netDialer = &_netDialer // make a copy
default:
return netDialer.DialContext(ctx, network, address)
}
dialer := netDialer.(*net.Dialer)
if opt.interfaceName != "" { if opt.interfaceName != "" {
if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil { if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err return nil, err
@ -131,213 +136,328 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
if opt.routingMark != 0 { if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination) bindMarkToDialer(opt.routingMark, dialer, network, destination)
} }
if opt.tfo {
return dialTFO(ctx, *dialer, network, address) if DisableIPv6 && destination.Is6() {
return nil, ErrorDisableIPv6
} }
return dialer.DialContext(ctx, network, address)
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
} }
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
return serialDialContext(ctx, network, ips, port, opt) host, port, err := net.SplitHostPort(address)
} if err != nil {
return nil, err
func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
}
func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
return parallelDialContext(ctx, network, ips, port, opt)
}
func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
if opt.prefer != 4 && opt.prefer != 6 {
return parallelDialContext(ctx, network, ips, port, opt)
} }
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
}
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
ipv4s, ipv6s := sortationAddr(ips)
preferIPVersion := opt.prefer
fallbackTicker := time.NewTicker(fallbackTimeout)
defer fallbackTicker.Stop()
results := make(chan dialResult)
returned := make(chan struct{}) returned := make(chan struct{})
defer close(returned) defer close(returned)
racer := func(ips []netip.Addr, isPrimary bool) {
result := dialResult{isPrimary: isPrimary} type dialResult struct {
net.Conn
error
resolved bool
ipv6 bool
done bool
}
results := make(chan dialResult)
var primary, fallback dialResult
startRacer := func(ctx context.Context, network, host string, r resolver.Resolver, ipv6 bool) {
result := dialResult{ipv6: ipv6, done: true}
defer func() { defer func() {
select { select {
case results <- result: case results <- result:
case <-returned: case <-returned:
if result.Conn != nil && result.error == nil { if result.Conn != nil {
_ = result.Conn.Close() _ = result.Conn.Close()
} }
} }
}() }()
result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
} var ip netip.Addr
go racer(ipv4s, preferIPVersion != 6) if ipv6 {
go racer(ipv6s, preferIPVersion != 4) if r == nil {
var fallback dialResult ip, result.error = resolver.ResolveIPv6ProxyServerHost(ctx, host)
var errs []error
for i := 0; i < 2; {
select {
case <-fallbackTicker.C:
if fallback.error == nil && fallback.Conn != nil {
return fallback.Conn, nil
}
case res := <-results:
i++
if res.error == nil {
if res.isPrimary {
return res.Conn, nil
}
fallback = res
} else { } else {
if res.isPrimary { ip, result.error = resolver.ResolveIPv6WithResolver(ctx, host, r)
errs = append([]error{fmt.Errorf("connect failed: %w", res.error)}, errs...) }
} else { } else {
errs = append(errs, fmt.Errorf("connect failed: %w", res.error)) if r == nil {
} ip, result.error = resolver.ResolveIPv4ProxyServerHost(ctx, host)
} else {
ip, result.error = resolver.ResolveIPv4WithResolver(ctx, host, r)
} }
} }
if result.error != nil {
return
}
result.resolved = true
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
} }
if fallback.error == nil && fallback.Conn != nil {
return fallback.Conn, nil go startRacer(ctx, network+"4", host, opt.resolver, false)
go startRacer(ctx, network+"6", host, opt.resolver, true)
count := 2
for i := 0; i < count; i++ {
select {
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
if !res.ipv6 {
primary = res
} else {
fallback = res
}
if primary.done && fallback.done {
if primary.resolved {
return nil, primary.error
} else if fallback.resolved {
return nil, fallback.error
} else {
return nil, primary.error
}
}
case <-ctx.Done():
err = ctx.Err()
break
}
} }
return nil, errorsJoin(errs...)
if err == nil {
err = fmt.Errorf("dual stack dial failed")
} else {
err = fmt.Errorf("dual stack dial failed:%w", err)
}
return nil, err
} }
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
if len(ips) == 0 { host, port, err := net.SplitHostPort(address)
return nil, ErrorNoIpAddress if err != nil {
return nil, err
} }
results := make(chan dialResult)
var ips []netip.Addr
if opt.resolver != nil {
ips, err = resolver.LookupIPWithResolver(ctx, host, opt.resolver)
} else {
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
}
if err != nil {
return nil, err
}
return concurrentDialContext(ctx, network, ips, port, opt)
}
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
returned := make(chan struct{}) returned := make(chan struct{})
defer close(returned) defer close(returned)
racer := func(ctx context.Context, ip netip.Addr) {
result := dialResult{isPrimary: true, ip: ip} type dialResult struct {
ip netip.Addr
net.Conn
error
isPrimary bool
done bool
}
preferCount := atomic.NewInt32(0)
results := make(chan dialResult)
tcpRacer := func(ctx context.Context, ip netip.Addr) {
result := dialResult{ip: ip, done: true}
defer func() { defer func() {
select { select {
case results <- result: case results <- result:
case <-returned: case <-returned:
if result.Conn != nil && result.error == nil { if result.Conn != nil {
_ = result.Conn.Close() _ = result.Conn.Close()
} }
} }
}() }()
if strings.Contains(network, "tcp") {
network = "tcp"
} else {
network = "udp"
}
if ip.Is6() {
network += "6"
if opt.prefer != 4 {
result.isPrimary = true
}
}
if ip.Is4() {
network += "4"
if opt.prefer != 6 {
result.isPrimary = true
}
}
if result.isPrimary {
preferCount.Add(1)
}
result.Conn, result.error = dialContext(ctx, network, ip, port, opt) result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
} }
for _, ip := range ips { for _, ip := range ips {
go racer(ctx, ip) go tcpRacer(ctx, ip)
}
var errs []error
for i := 0; i < len(ips); i++ {
res := <-results
if res.error == nil {
return res.Conn, nil
}
errs = append(errs, res.error)
} }
if len(errs) > 0 { connCount := len(ips)
return nil, errorsJoin(errs...) var fallback dialResult
} var primaryError error
return nil, os.ErrDeadlineExceeded var finalError error
} for i := 0; i < connCount; i++ {
select {
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { case res := <-results:
if len(ips) == 0 { if res.error == nil {
return nil, ErrorNoIpAddress if res.isPrimary {
} return res.Conn, nil
var errs []error } else {
for _, ip := range ips { if !fallback.done || fallback.error != nil {
if conn, err := dialContext(ctx, network, ip, port, opt); err == nil { fallback = res
return conn, nil }
} else { }
errs = append(errs, err) } else {
if res.isPrimary {
primaryError = res.error
preferCount.Add(-1)
if preferCount.Load() == 0 && fallback.done && fallback.error == nil {
return fallback.Conn, nil
}
}
}
case <-ctx.Done():
if fallback.done && fallback.error == nil {
return fallback.Conn, nil
}
finalError = ctx.Err()
break
} }
} }
return nil, errorsJoin(errs...)
if fallback.done && fallback.error == nil {
return fallback.Conn, nil
}
if primaryError != nil {
return nil, primaryError
}
if fallback.error != nil {
return nil, fallback.error
}
if finalError == nil {
finalError = fmt.Errorf("all ips %v tcp shake hands failed", ips)
} else {
finalError = fmt.Errorf("concurrent dial failed:%w", finalError)
}
return nil, finalError
} }
type dialResult struct { func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
ip netip.Addr
net.Conn
error
isPrimary bool
}
func parseAddr(ctx context.Context, network, address string, preferResolver resolver.Resolver) ([]netip.Addr, string, error) {
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, "-1", err return nil, err
}
var ip netip.Addr
switch network {
case "tcp4", "udp4":
if opt.resolver == nil {
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
} else {
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, opt.resolver)
}
default:
if opt.resolver == nil {
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
} else {
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, opt.resolver)
}
}
if err != nil {
return nil, err
}
return dialContext(ctx, network, ip, port, opt)
}
func concurrentSingleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
switch network {
case "tcp4", "udp4":
return concurrentIPv4DialContext(ctx, network, address, opt)
default:
return concurrentIPv6DialContext(ctx, network, address, opt)
}
}
func concurrentIPv4DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
} }
var ips []netip.Addr var ips []netip.Addr
switch network { if opt.resolver == nil {
case "tcp4", "udp4": ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
if preferResolver == nil { } else {
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host) ips, err = resolver.LookupIPv4WithResolver(ctx, host, opt.resolver)
} else {
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
}
case "tcp6", "udp6":
if preferResolver == nil {
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
}
default:
if preferResolver == nil {
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
}
} }
if err != nil { if err != nil {
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err) return nil, err
} }
for i, ip := range ips {
if ip.Is4In6() { return concurrentDialContext(ctx, network, ips, port, opt)
ips[i] = ip.Unmap()
}
}
return ips, port, nil
} }
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) { func concurrentIPv6DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
for _, v := range ips { host, port, err := net.SplitHostPort(address)
if v.Is4() { // 4in6 parse was in parseAddr if err != nil {
ipv4s = append(ipv4s, v) return nil, err
} else {
ipv6s = append(ipv6s, v)
}
} }
return
var ips []netip.Addr
if opt.resolver == nil {
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv6WithResolver(ctx, host, opt.resolver)
}
if err != nil {
return nil, err
}
return concurrentDialContext(ctx, network, ips, port, opt)
} }
type Dialer struct { type Dialer struct {
opt option Opt option
} }
func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
return DialContext(ctx, network, address, WithOption(d.opt)) return DialContext(ctx, network, address, WithOption(d.Opt))
} }
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) { func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
opt := WithOption(d.opt) return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, WithOption(d.Opt))
if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context."
opt = WithInterface("")
}
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt)
} }
func NewDialer(options ...Option) Dialer { func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...) opt := applyOptions(options...)
return Dialer{opt: *opt} return Dialer{Opt: *opt}
} }

View File

@ -1,18 +0,0 @@
package dialer
import (
"errors"
E "github.com/sagernet/sing/common/exceptions"
)
var (
ErrorNoIpAddress = errors.New("no ip address")
ErrorInvalidedNetworkStack = errors.New("invalided network stack")
)
func errorsJoin(errs ...error) error {
// compatibility with golang<1.20
// maybe use errors.Join(errs...) is better after we drop the old version's support
return E.Errors(errs...)
}

View File

@ -3,22 +3,26 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
) )
func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) { func bindMarkToDialer(mark int, dialer *net.Dialer, _ string, _ netip.Addr) {
addControlToDialer(dialer, bindMarkToControl(mark)) dialer.Control = bindMarkToControl(mark, dialer.Control)
} }
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) { func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) {
addControlToListenConfig(lc, bindMarkToControl(mark)) lc.Control = bindMarkToControl(mark, lc.Control)
} }
func bindMarkToControl(mark int) controlFn { func bindMarkToControl(mark int, chain controlFn) controlFn {
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) { return func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
addrPort, err := netip.ParseAddrPort(address) addrPort, err := netip.ParseAddrPort(address)
if err == nil && !addrPort.Addr().IsGlobalUnicast() { if err == nil && !addrPort.Addr().IsGlobalUnicast() {

View File

@ -1,9 +1,6 @@
package dialer package dialer
import ( import (
"context"
"net"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"go.uber.org/atomic" "go.uber.org/atomic"
@ -15,19 +12,13 @@ var (
DefaultRoutingMark = atomic.NewInt32(0) DefaultRoutingMark = atomic.NewInt32(0)
) )
type NetDialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
type option struct { type option struct {
interfaceName string interfaceName string
addrReuse bool addrReuse bool
routingMark int routingMark int
network int network int
prefer int prefer int
tfo bool
resolver resolver.Resolver resolver resolver.Resolver
netDialer NetDialer
} }
type Option func(opt *option) type Option func(opt *option)
@ -78,18 +69,6 @@ func WithOnlySingleStack(isIPv4 bool) Option {
} }
} }
func WithTFO(tfo bool) Option {
return func(opt *option) {
opt.tfo = tfo
}
}
func WithNetDialer(netDialer NetDialer) Option {
return func(opt *option) {
opt.netDialer = netDialer
}
}
func WithOption(o option) Option { func WithOption(o option) Option {
return func(opt *option) { return func(opt *option) {
*opt = o *opt = o

View File

@ -3,7 +3,6 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"syscall" "syscall"
@ -11,10 +10,18 @@ import (
) )
func addrReuseToListenConfig(lc *net.ListenConfig) { func addrReuseToListenConfig(lc *net.ListenConfig) {
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error { chain := lc.Control
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
}) })
}) }
} }

View File

@ -1,7 +1,6 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"syscall" "syscall"
@ -9,9 +8,17 @@ import (
) )
func addrReuseToListenConfig(lc *net.ListenConfig) { func addrReuseToListenConfig(lc *net.ListenConfig) {
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error { chain := lc.Control
lc.Control = func(network, address string, c syscall.RawConn) (err error) {
defer func() {
if err == nil && chain != nil {
err = chain(network, address, c)
}
}()
return c.Control(func(fd uintptr) { return c.Control(func(fd uintptr) {
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
}) })
}) }
} }

View File

@ -1,123 +0,0 @@
package dialer
import (
"context"
"github.com/sagernet/tfo-go"
"io"
"net"
"time"
)
type tfoConn struct {
net.Conn
closed bool
dialed chan bool
cancel context.CancelFunc
ctx context.Context
dialFn func(ctx context.Context, earlyData []byte) (net.Conn, error)
}
func (c *tfoConn) Dial(earlyData []byte) (err error) {
c.Conn, err = c.dialFn(c.ctx, earlyData)
if err != nil {
return
}
c.dialed <- true
return err
}
func (c *tfoConn) Read(b []byte) (n int, err error) {
if c.closed {
return 0, io.ErrClosedPipe
}
if c.Conn == nil {
select {
case <-c.ctx.Done():
return 0, io.ErrUnexpectedEOF
case <-c.dialed:
}
}
return c.Conn.Read(b)
}
func (c *tfoConn) Write(b []byte) (n int, err error) {
if c.closed {
return 0, io.ErrClosedPipe
}
if c.Conn == nil {
if err := c.Dial(b); err != nil {
return 0, err
}
return len(b), nil
}
return c.Conn.Write(b)
}
func (c *tfoConn) Close() error {
c.closed = true
c.cancel()
if c.Conn == nil {
return nil
}
return c.Conn.Close()
}
func (c *tfoConn) LocalAddr() net.Addr {
if c.Conn == nil {
return nil
}
return c.Conn.LocalAddr()
}
func (c *tfoConn) RemoteAddr() net.Addr {
if c.Conn == nil {
return nil
}
return c.Conn.RemoteAddr()
}
func (c *tfoConn) SetDeadline(t time.Time) error {
if err := c.SetReadDeadline(t); err != nil {
return err
}
return c.SetWriteDeadline(t)
}
func (c *tfoConn) SetReadDeadline(t time.Time) error {
if c.Conn == nil {
return nil
}
return c.Conn.SetReadDeadline(t)
}
func (c *tfoConn) SetWriteDeadline(t time.Time) error {
if c.Conn == nil {
return nil
}
return c.Conn.SetWriteDeadline(t)
}
func (c *tfoConn) Upstream() any {
if c.Conn == nil { // ensure return a nil interface not an interface with nil value
return nil
}
return c.Conn
}
func (c *tfoConn) NeedHandshake() bool {
return c.Conn == nil
}
func dialTFO(ctx context.Context, netDialer net.Dialer, network, address string) (net.Conn, error) {
ctx, cancel := context.WithCancel(ctx)
dialer := tfo.Dialer{Dialer: netDialer, DisableTFO: false}
return &tfoConn{
dialed: make(chan bool, 1),
cancel: cancel,
ctx: ctx,
dialFn: func(ctx context.Context, earlyData []byte) (net.Conn, error) {
return dialer.DialContext(ctx, network, address, earlyData)
},
}, nil
}

View File

@ -41,9 +41,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) {
// //
// The following types are suitable as obj argument: // The following types are suitable as obj argument:
// //
// *bpfObjects // *bpfObjects
// *bpfPrograms // *bpfPrograms
// *bpfMaps // *bpfMaps
// //
// See ebpf.CollectionSpec.LoadAndAssign documentation for details. // See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
@ -134,6 +134,5 @@ func _BpfClose(closers ...io.Closer) error {
} }
// Do not access this directly. // Do not access this directly.
//
//go:embed bpf_bpfeb.o //go:embed bpf_bpfeb.o
var _BpfBytes []byte var _BpfBytes []byte

View File

@ -41,9 +41,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) {
// //
// The following types are suitable as obj argument: // The following types are suitable as obj argument:
// //
// *bpfObjects // *bpfObjects
// *bpfPrograms // *bpfPrograms
// *bpfMaps // *bpfMaps
// //
// See ebpf.CollectionSpec.LoadAndAssign documentation for details. // See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
@ -134,6 +134,5 @@ func _BpfClose(closers ...io.Closer) error {
} }
// Do not access this directly. // Do not access this directly.
//
//go:embed bpf_bpfel.o //go:embed bpf_bpfel.o
var _BpfBytes []byte var _BpfBytes []byte

View File

@ -28,9 +28,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) {
// //
// The following types are suitable as obj argument: // The following types are suitable as obj argument:
// //
// *bpfObjects // *bpfObjects
// *bpfPrograms // *bpfPrograms
// *bpfMaps // *bpfMaps
// //
// See ebpf.CollectionSpec.LoadAndAssign documentation for details. // See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
@ -115,6 +115,5 @@ func _BpfClose(closers ...io.Closer) error {
} }
// Do not access this directly. // Do not access this directly.
//
//go:embed bpf_bpfeb.o //go:embed bpf_bpfeb.o
var _BpfBytes []byte var _BpfBytes []byte

View File

@ -28,9 +28,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) {
// //
// The following types are suitable as obj argument: // The following types are suitable as obj argument:
// //
// *bpfObjects // *bpfObjects
// *bpfPrograms // *bpfPrograms
// *bpfMaps // *bpfMaps
// //
// See ebpf.CollectionSpec.LoadAndAssign documentation for details. // See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error {
@ -115,6 +115,5 @@ func _BpfClose(closers ...io.Closer) error {
} }
// Do not access this directly. // Do not access this directly.
//
//go:embed bpf_bpfel.o //go:embed bpf_bpfel.o
var _BpfBytes []byte var _BpfBytes []byte

View File

@ -1,10 +1,13 @@
package geodata package geodata
import ( import (
"errors"
"fmt" "fmt"
C "github.com/Dreamacro/clash/constant"
"strings"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log"
) )
type loader struct { type loader struct {
@ -12,7 +15,47 @@ type loader struct {
} }
func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) { func (l *loader) LoadGeoSite(list string) ([]*router.Domain, error) {
return l.LoadSiteByPath(C.GeositeName, list) return l.LoadGeoSiteWithAttr(C.GeositeName, list)
}
func (l *loader) LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
parts := strings.Split(siteWithAttr, "@")
if len(parts) == 0 {
return nil, errors.New("empty rule")
}
list := strings.TrimSpace(parts[0])
attrVal := parts[1:]
if len(list) == 0 {
return nil, fmt.Errorf("empty listname in rule: %s", siteWithAttr)
}
domains, err := l.LoadSiteByPath(file, list)
if err != nil {
return nil, err
}
attrs := parseAttrs(attrVal)
if attrs.IsEmpty() {
if strings.Contains(siteWithAttr, "@") {
log.Warnln("empty attribute list: %s", siteWithAttr)
}
return domains, nil
}
filteredDomains := make([]*router.Domain, 0, len(domains))
hasAttrMatched := false
for _, domain := range domains {
if attrs.Match(domain) {
hasAttrMatched = true
filteredDomains = append(filteredDomains, domain)
}
}
if !hasAttrMatched {
log.Warnln("attribute match no rule: geosite: %s", siteWithAttr)
}
return filteredDomains, nil
} }
func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) { func (l *loader) LoadGeoIP(country string) ([]*router.CIDR, error) {

View File

@ -14,5 +14,6 @@ type LoaderImplementation interface {
type Loader interface { type Loader interface {
LoaderImplementation LoaderImplementation
LoadGeoSite(list string) ([]*router.Domain, error) LoadGeoSite(list string) ([]*router.Domain, error)
LoadGeoSiteWithAttr(file string, siteWithAttr string) ([]*router.Domain, error)
LoadGeoIP(country string) ([]*router.CIDR, error) LoadGeoIP(country string) ([]*router.CIDR, error)
} }

View File

@ -118,7 +118,7 @@ func (g GeoSiteCache) Unmarshal(filename, code string) (*router.GeoSite, error)
case errFailedToReadBytes, errFailedToReadExpectedLenBytes, case errFailedToReadBytes, errFailedToReadExpectedLenBytes,
errInvalidGeodataFile, errInvalidGeodataVarintLength: errInvalidGeodataFile, errInvalidGeodataVarintLength:
log.Warnln("failed to decode geosite file: %s%s", filename, ", fallback to the original ReadFile method") log.Warnln("failed to decode geoip file: %s%s", filename, ", fallback to the original ReadFile method")
geositeBytes, err = os.ReadFile(asset) geositeBytes, err = os.ReadFile(asset)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,14 +1,9 @@
package geodata package geodata
import ( import (
"errors"
"fmt" "fmt"
"golang.org/x/sync/singleflight"
"strings"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
) )
var geoLoaderName = "memconservative" var geoLoaderName = "memconservative"
@ -39,8 +34,6 @@ func Verify(name string) error {
} }
} }
var loadGeoSiteMatcherSF = singleflight.Group{}
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) { func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
if len(countryCode) == 0 { if len(countryCode) == 0 {
return nil, 0, fmt.Errorf("country code could not be empty") return nil, 0, fmt.Errorf("country code could not be empty")
@ -51,52 +44,15 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
not = true not = true
countryCode = countryCode[1:] countryCode = countryCode[1:]
} }
countryCode = strings.ToLower(countryCode)
parts := strings.Split(countryCode, "@") geoLoader, err := GetGeoDataLoader(geoLoaderName)
if len(parts) == 0 {
return nil, 0, errors.New("empty rule")
}
listName := strings.TrimSpace(parts[0])
attrVal := parts[1:]
if len(listName) == 0 {
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
}
v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) {
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, err
}
return geoLoader.LoadGeoSite(listName)
})
if err != nil { if err != nil {
if !shared {
loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
}
return nil, 0, err return nil, 0, err
} }
domains := v.([]*router.Domain)
attrs := parseAttrs(attrVal) domains, err := geoLoader.LoadGeoSite(countryCode)
if attrs.IsEmpty() { if err != nil {
if strings.Contains(countryCode, "@") { return nil, 0, err
log.Warnln("empty attribute list: %s", countryCode)
}
} else {
filteredDomains := make([]*router.Domain, 0, len(domains))
hasAttrMatched := false
for _, domain := range domains {
if attrs.Match(domain) {
hasAttrMatched = true
filteredDomains = append(filteredDomains, domain)
}
}
if !hasAttrMatched {
log.Warnln("attribute match no rule: geosite: %s", countryCode)
}
domains = filteredDomains
} }
/** /**
@ -112,34 +68,25 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
return matcher, len(domains), nil return matcher, len(domains), nil
} }
var loadGeoIPMatcherSF = singleflight.Group{}
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) { func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
if len(country) == 0 { if len(country) == 0 {
return nil, 0, fmt.Errorf("country code could not be empty") return nil, 0, fmt.Errorf("country code could not be empty")
} }
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, 0, err
}
not := false not := false
if country[0] == '!' { if country[0] == '!' {
not = true not = true
country = country[1:] country = country[1:]
} }
country = strings.ToLower(country)
v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) { records, err := geoLoader.LoadGeoIP(country)
geoLoader, err := GetGeoDataLoader(geoLoaderName)
if err != nil {
return nil, err
}
return geoLoader.LoadGeoIP(country)
})
if err != nil { if err != nil {
if !shared {
loadGeoIPMatcherSF.Forget(country) // don't store the error result
}
return nil, 0, err return nil, 0, err
} }
records := v.([]*router.CIDR)
geoIP := &router.GeoIP{ geoIP := &router.GeoIP{
CountryCode: country, CountryCode: country,
@ -151,10 +98,6 @@ func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
return matcher, len(records), nil return matcher, len(records), nil
} }
func ClearCache() {
loadGeoSiteMatcherSF = singleflight.Group{}
loadGeoIPMatcherSF = singleflight.Group{}
}

View File

@ -2,19 +2,18 @@ package http
import ( import (
"context" "context"
"github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/listener/inner"
"io" "io"
"net" "net"
"net/http" "net/http"
URL "net/url" URL "net/url"
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/listener/inner"
) )
const ( const (
UA = "clash.meta" UA = "Clash"
) )
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {

View File

@ -1,7 +1,6 @@
package nat package nat
import ( import (
"net"
"sync" "sync"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -11,24 +10,16 @@ type Table struct {
mapping sync.Map mapping sync.Map
} }
type Entry struct { func (t *Table) Set(key string, pc C.PacketConn) {
PacketConn C.PacketConn t.mapping.Store(key, pc)
LocalUDPConnMap sync.Map
}
func (t *Table) Set(key string, e C.PacketConn) {
t.mapping.Store(key, &Entry{
PacketConn: e,
LocalUDPConnMap: sync.Map{},
})
} }
func (t *Table) Get(key string) C.PacketConn { func (t *Table) Get(key string) C.PacketConn {
entry, exist := t.getEntry(key) item, exist := t.mapping.Load(key)
if !exist { if !exist {
return nil return nil
} }
return entry.PacketConn return item.(C.PacketConn)
} }
func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) {
@ -40,62 +31,6 @@ func (t *Table) Delete(key string) {
t.mapping.Delete(key) t.mapping.Delete(key)
} }
func (t *Table) GetLocalConn(lAddr, rAddr string) *net.UDPConn {
entry, exist := t.getEntry(lAddr)
if !exist {
return nil
}
item, exist := entry.LocalUDPConnMap.Load(rAddr)
if !exist {
return nil
}
return item.(*net.UDPConn)
}
func (t *Table) AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool {
entry, exist := t.getEntry(lAddr)
if !exist {
return false
}
entry.LocalUDPConnMap.Store(rAddr, conn)
return true
}
func (t *Table) RangeLocalConn(lAddr string, f func(key, value any) bool) {
entry, exist := t.getEntry(lAddr)
if !exist {
return
}
entry.LocalUDPConnMap.Range(f)
}
func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool) {
entry, loaded := t.getEntry(lAddr)
if !loaded {
return nil, false
}
item, loaded := entry.LocalUDPConnMap.LoadOrStore(key, sync.NewCond(&sync.Mutex{}))
return item.(*sync.Cond), loaded
}
func (t *Table) DeleteLocalConnMap(lAddr, key string) {
entry, loaded := t.getEntry(lAddr)
if !loaded {
return
}
entry.LocalUDPConnMap.Delete(key)
}
func (t *Table) getEntry(key string) (*Entry, bool) {
item, ok := t.mapping.Load(key)
// This should not happen usually since this function called after PacketConn created
if !ok {
return nil, false
}
entry, ok := item.(*Entry)
return entry, ok
}
// New return *Cache // New return *Cache
func New() *Table { func New() *Table {
return &Table{} return &Table{}

View File

@ -16,6 +16,14 @@ const (
UDP = "udp" UDP = "udp"
) )
func FindProcessName(network string, srcIP netip.Addr, srcPort int) (uint32, string, error) { func FindProcessName(network string, srcIP netip.Addr, srcPort int) (*uint32, string, error) {
return findProcessName(network, srcIP, srcPort) return findProcessName(network, srcIP, srcPort)
} }
func FindUid(network string, srcIP netip.Addr, srcPort int) (*uint32, error) {
_, uid, err := resolveSocketByNetlink(network, srcIP, srcPort)
if err != nil {
return nil, err
}
return &uid, nil
}

View File

@ -33,7 +33,11 @@ var structSize = func() int {
} }
}() }()
func findProcessName(network string, ip netip.Addr, port int) (uint32, string, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) {
return 0, 0, ErrPlatformNotSupport
}
func findProcessName(network string, ip netip.Addr, port int) (*uint32, string, error) {
var spath string var spath string
switch network { switch network {
case TCP: case TCP:
@ -41,14 +45,14 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
case UDP: case UDP:
spath = "net.inet.udp.pcblist_n" spath = "net.inet.udp.pcblist_n"
default: default:
return 0, "", ErrInvalidNetwork return nil, "", ErrInvalidNetwork
} }
isIPv4 := ip.Is4() isIPv4 := ip.Is4()
value, err := syscall.Sysctl(spath) value, err := syscall.Sysctl(spath)
if err != nil { if err != nil {
return 0, "", err return nil, "", err
} }
buf := []byte(value) buf := []byte(value)
@ -92,7 +96,7 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
// xsocket_n.so_last_pid // xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72]) pid := readNativeUint32(buf[so+68 : so+72])
pp, err := getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return 0, pp, err return nil, pp, err
} }
// udp packet connection may be not equal with srcIP // udp packet connection may be not equal with srcIP
@ -102,10 +106,10 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
} }
if network == UDP && fallbackUDPProcess != "" { if network == UDP && fallbackUDPProcess != "" {
return 0, fallbackUDPProcess, nil return nil, fallbackUDPProcess, nil
} }
return 0, "", ErrNotFound return nil, "", ErrNotFound
} }
func getExecPathFromPID(pid uint32) (string, error) { func getExecPathFromPID(pid uint32) (string, error) {

View File

@ -21,7 +21,11 @@ var (
once sync.Once once sync.Once
) )
func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) {
return 0, 0, ErrPlatformNotSupport
}
func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, string, error) {
once.Do(func() { once.Do(func() {
if err := initSearcher(); err != nil { if err := initSearcher(); err != nil {
log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error()) log.Errorln("Initialize PROCESS-NAME failed: %s", err.Error())
@ -31,7 +35,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
}) })
if defaultSearcher == nil { if defaultSearcher == nil {
return 0, "", ErrPlatformNotSupport return nil, "", ErrPlatformNotSupport
} }
var spath string var spath string
@ -42,22 +46,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
case UDP: case UDP:
spath = "net.inet.udp.pcblist" spath = "net.inet.udp.pcblist"
default: default:
return 0, "", ErrInvalidNetwork return nil, "", ErrInvalidNetwork
} }
value, err := syscall.Sysctl(spath) value, err := syscall.Sysctl(spath)
if err != nil { if err != nil {
return 0, "", err return nil, "", err
} }
buf := []byte(value) buf := []byte(value)
pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP) pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP)
if err != nil { if err != nil {
return 0, "", err return nil, "", err
} }
pp, err := getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return 0, pp, err return nil, pp, err
} }
func getExecPathFromPID(pid uint32) (string, error) { func getExecPathFromPID(pid uint32) (string, error) {

View File

@ -59,14 +59,14 @@ type inetDiagResponse struct {
INode uint32 INode uint32
} }
func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, string, error) {
uid, inode, err := resolveSocketByNetlink(network, ip, srcPort) inode, uid, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil { if err != nil {
return 0, "", err return nil, "", err
} }
pp, err := resolveProcessNameByProcSearch(inode, uid) pp, err := resolveProcessNameByProcSearch(inode, uid)
return uid, pp, err return &uid, pp, err
} }
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) {
@ -119,7 +119,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32,
response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0])) response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0]))
return response.UID, response.INode, nil return response.INode, response.UID, nil
} }
return 0, 0, ErrNotFound return 0, 0, ErrNotFound

View File

@ -4,8 +4,8 @@ package process
import "net/netip" import "net/netip"
func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, string, error) {
return 0, "", ErrPlatformNotSupport return nil, "", ErrPlatformNotSupport
} }
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) {

View File

@ -62,7 +62,7 @@ func initWin32API() error {
return nil return nil
} }
func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, string, error) {
once.Do(func() { once.Do(func() {
err := initWin32API() err := initWin32API()
if err != nil { if err != nil {
@ -86,22 +86,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string
fn = getExUDPTable fn = getExUDPTable
class = udpTablePid class = udpTablePid
default: default:
return 0, "", ErrInvalidNetwork return nil, "", ErrInvalidNetwork
} }
buf, err := getTransportTable(fn, family, class) buf, err := getTransportTable(fn, family, class)
if err != nil { if err != nil {
return 0, "", err return nil, "", err
} }
s := newSearcher(family == windows.AF_INET, network == TCP) s := newSearcher(family == windows.AF_INET, network == TCP)
pid, err := s.Search(buf, ip, uint16(srcPort)) pid, err := s.Search(buf, ip, uint16(srcPort))
if err != nil { if err != nil {
return 0, "", err return nil, "", err
} }
pp, err := getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return 0, pp, err return nil, pp, err
} }
type searcher struct { type searcher struct {

View File

@ -1,113 +0,0 @@
package resolver
import (
"errors"
"net/netip"
"strings"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie"
"github.com/zhangyunhao116/fastrand"
)
type Hosts struct {
*trie.DomainTrie[HostValue]
}
func NewHosts(hosts *trie.DomainTrie[HostValue]) Hosts {
return Hosts{
hosts,
}
}
// Return the search result and whether to match the parameter `isDomain`
func (h *Hosts) Search(domain string, isDomain bool) (*HostValue, bool) {
value := h.DomainTrie.Search(domain)
if value == nil {
return nil, false
}
hostValue := value.Data()
for {
if isDomain && hostValue.IsDomain {
return &hostValue, true
} else {
if node := h.DomainTrie.Search(hostValue.Domain); node != nil {
hostValue = node.Data()
} else {
break
}
}
}
if isDomain == hostValue.IsDomain {
return &hostValue, true
}
return &hostValue, false
}
type HostValue struct {
IsDomain bool
IPs []netip.Addr
Domain string
}
func NewHostValue(value any) (HostValue, error) {
isDomain := true
ips := make([]netip.Addr, 0)
domain := ""
if valueArr, err := utils.ToStringSlice(value); err != nil {
return HostValue{}, err
} else {
if len(valueArr) > 1 {
isDomain = false
for _, str := range valueArr {
if ip, err := netip.ParseAddr(str); err == nil {
ips = append(ips, ip)
} else {
return HostValue{}, err
}
}
} else if len(valueArr) == 1 {
host := valueArr[0]
if ip, err := netip.ParseAddr(host); err == nil {
ips = append(ips, ip)
isDomain = false
} else {
domain = host
}
}
}
if isDomain {
return NewHostValueByDomain(domain)
} else {
return NewHostValueByIPs(ips)
}
}
func NewHostValueByIPs(ips []netip.Addr) (HostValue, error) {
if len(ips) == 0 {
return HostValue{}, errors.New("ip list is empty")
}
return HostValue{
IsDomain: false,
IPs: ips,
}, nil
}
func NewHostValueByDomain(domain string) (HostValue, error) {
domain = strings.Trim(domain, ".")
item := strings.Split(domain, ".")
if len(item) < 2 {
return HostValue{}, errors.New("invaild domain")
}
return HostValue{
IsDomain: true,
Domain: domain,
}, nil
}
func (hv HostValue) RandIP() (netip.Addr, error) {
if hv.IsDomain {
return netip.Addr{}, errors.New("value type is error")
}
return hv.IPs[fastrand.Intn(len(hv.IPs))], nil
}

View File

@ -4,16 +4,13 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math/rand"
"net" "net"
"net/netip" "net/netip"
"strings" "strings"
"time" "time"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/miekg/dns"
"github.com/zhangyunhao116/fastrand"
) )
var ( var (
@ -28,7 +25,7 @@ var (
DisableIPv6 = true DisableIPv6 = true
// DefaultHosts aim to resolve hosts // DefaultHosts aim to resolve hosts
DefaultHosts = NewHosts(trie.New[HostValue]()) DefaultHosts = trie.New[netip.Addr]()
// DefaultDNSTimeout defined the default dns request timeout // DefaultDNSTimeout defined the default dns request timeout
DefaultDNSTimeout = time.Second * 5 DefaultDNSTimeout = time.Second * 5
@ -47,16 +44,13 @@ type Resolver interface {
ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error) ResolveIP(ctx context.Context, host string) (ip netip.Addr, err error)
ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error) ResolveIPv4(ctx context.Context, host string) (ip netip.Addr, err error)
ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error) ResolveIPv6(ctx context.Context, host string) (ip netip.Addr, err error)
ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error)
} }
// LookupIPv4WithResolver same as LookupIPv4, but with a resolver // LookupIPv4WithResolver same as LookupIPv4, but with a resolver
func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) { func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
if node, ok := DefaultHosts.Search(host, false); ok { if node := DefaultHosts.Search(host); node != nil {
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool { if ip := node.Data(); ip.Is4() {
return ip.Is4() return []netip.Addr{node.Data()}, nil
}); len(addrs) > 0 {
return addrs, nil
} }
} }
@ -72,6 +66,10 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
return r.LookupIPv4(ctx, host) return r.LookupIPv4(ctx, host)
} }
if DefaultResolver != nil {
return DefaultResolver.LookupIPv4(ctx, host)
}
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host) ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
if err != nil { if err != nil {
return nil, err return nil, err
@ -95,7 +93,7 @@ func ResolveIPv4WithResolver(ctx context.Context, host string, r Resolver) (neti
} else if len(ips) == 0 { } else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host) return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
} }
return ips[fastrand.Intn(len(ips))], nil return ips[rand.Intn(len(ips))], nil
} }
// ResolveIPv4 with a host, return ipv4 // ResolveIPv4 with a host, return ipv4
@ -109,11 +107,9 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
return nil, ErrIPv6Disabled return nil, ErrIPv6Disabled
} }
if node, ok := DefaultHosts.Search(host, false); ok { if node := DefaultHosts.Search(host); node != nil {
if addrs := utils.Filter(node.IPs, func(ip netip.Addr) bool { if ip := node.Data(); ip.Is6() {
return ip.Is6() return []netip.Addr{ip}, nil
}); len(addrs) > 0 {
return addrs, nil
} }
} }
@ -127,6 +123,9 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
if r != nil { if r != nil {
return r.LookupIPv6(ctx, host) return r.LookupIPv6(ctx, host)
} }
if DefaultResolver != nil {
return DefaultResolver.LookupIPv6(ctx, host)
}
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host) ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host)
if err != nil { if err != nil {
@ -151,7 +150,7 @@ func ResolveIPv6WithResolver(ctx context.Context, host string, r Resolver) (neti
} else if len(ips) == 0 { } else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host) return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
} }
return ips[fastrand.Intn(len(ips))], nil return ips[rand.Intn(len(ips))], nil
} }
func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) { func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
@ -160,8 +159,8 @@ func ResolveIPv6(ctx context.Context, host string) (netip.Addr, error) {
// LookupIPWithResolver same as LookupIP, but with a resolver // LookupIPWithResolver same as LookupIP, but with a resolver
func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) { func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip.Addr, error) {
if node, ok := DefaultHosts.Search(host, false); ok { if node := DefaultHosts.Search(host); node != nil {
return node.IPs, nil return []netip.Addr{node.Data()}, nil
} }
if r != nil { if r != nil {
@ -170,7 +169,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
} }
return r.LookupIP(ctx, host) return r.LookupIP(ctx, host)
} else if DisableIPv6 { } else if DisableIPv6 {
return LookupIPv4WithResolver(ctx, host, r) return LookupIPv4(ctx, host)
} }
if ip, err := netip.ParseAddr(host); err == nil { if ip, err := netip.ParseAddr(host); err == nil {
@ -200,7 +199,7 @@ func ResolveIPWithResolver(ctx context.Context, host string, r Resolver) (netip.
} else if len(ips) == 0 { } else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host) return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
} }
return ips[fastrand.Intn(len(ips))], nil return ips[rand.Intn(len(ips))], nil
} }
// ResolveIP with a host, return ip // ResolveIP with a host, return ip

View File

@ -57,7 +57,7 @@ func (f *Fetcher[V]) Initial() (V, error) {
f.UpdatedAt = &modTime f.UpdatedAt = &modTime
isLocal = true isLocal = true
if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) { if f.interval != 0 && modTime.Add(f.interval).Before(time.Now()) {
log.Warnln("[Provider] %s not updated for a long time, force refresh", f.Name()) log.Infoln("[Provider] %s not updated for a long time, force refresh", f.Name())
forceUpdate = true forceUpdate = true
} }
} else { } else {
@ -162,7 +162,7 @@ func (f *Fetcher[V]) pullLoop() {
case <-f.ticker.C: case <-f.ticker.C:
elm, same, err := f.Update() elm, same, err := f.Update()
if err != nil { if err != nil {
log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error()) log.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error())
continue continue
} }

View File

@ -1,53 +0,0 @@
package sniffer
import (
"errors"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/sniffer"
)
type SnifferConfig struct {
OverrideDest bool
Ports []utils.Range[uint16]
}
type BaseSniffer struct {
ports []utils.Range[uint16]
supportNetworkType constant.NetWork
}
// Protocol implements sniffer.Sniffer
func (*BaseSniffer) Protocol() string {
return "unknown"
}
// SniffTCP implements sniffer.Sniffer
func (*BaseSniffer) SniffTCP(bytes []byte) (string, error) {
return "", errors.New("TODO")
}
// SupportNetwork implements sniffer.Sniffer
func (bs *BaseSniffer) SupportNetwork() constant.NetWork {
return bs.supportNetworkType
}
// SupportPort implements sniffer.Sniffer
func (bs *BaseSniffer) SupportPort(port uint16) bool {
for _, portRange := range bs.ports {
if portRange.Contains(port) {
return true
}
}
return false
}
func NewBaseSniffer(ports []utils.Range[uint16], networkType constant.NetWork) *BaseSniffer {
return &BaseSniffer{
ports: ports,
supportNetworkType: networkType,
}
}
var _ sniffer.Sniffer = (*BaseSniffer)(nil)

View File

@ -11,6 +11,7 @@ import (
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/sniffer" "github.com/Dreamacro/clash/constant/sniffer"
@ -26,18 +27,27 @@ var (
var Dispatcher *SnifferDispatcher var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct { type SnifferDispatcher struct {
enable bool enable bool
sniffers map[sniffer.Sniffer]SnifferConfig
forceDomain *trie.DomainSet sniffers []sniffer.Sniffer
skipSNI *trie.DomainSet
skipList *cache.LruCache[string, uint8] forceDomain *trie.DomainTrie[struct{}]
rwMux sync.RWMutex skipSNI *trie.DomainTrie[struct{}]
portRanges *[]utils.Range[uint16]
skipList *cache.LruCache[string, uint8]
rwMux sync.RWMutex
forceDnsMapping bool forceDnsMapping bool
parsePureIp bool parsePureIp bool
} }
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) { func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Has(metadata.Host) || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) { bufConn, ok := conn.(*N.BufferedConn)
if !ok {
return
}
if (metadata.Host == "" && sd.parsePureIp) || sd.forceDomain.Search(metadata.Host) != nil || (metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping) {
port, err := strconv.ParseUint(metadata.DstPort, 10, 16) port, err := strconv.ParseUint(metadata.DstPort, 10, 16)
if err != nil { if err != nil {
log.Debugln("[Sniffer] Dst port is error") log.Debugln("[Sniffer] Dst port is error")
@ -45,14 +55,10 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
} }
inWhitelist := false inWhitelist := false
overrideDest := false for _, portRange := range *sd.portRanges {
for sniffer, config := range sd.sniffers { if portRange.Contains(uint16(port)) {
if sniffer.SupportNetwork() == C.TCP || sniffer.SupportNetwork() == C.ALLNet { inWhitelist = true
inWhitelist = sniffer.SupportPort(uint16(port)) break
if inWhitelist {
overrideDest = config.OverrideDest
break
}
} }
} }
@ -69,12 +75,12 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
} }
sd.rwMux.RUnlock() sd.rwMux.RUnlock()
if host, err := sd.sniffDomain(conn, metadata); err != nil { if host, err := sd.sniffDomain(bufConn, metadata); err != nil {
sd.cacheSniffFailed(metadata) sd.cacheSniffFailed(metadata)
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort) log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%s] to [%s:%s]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
return return
} else { } else {
if sd.skipSNI.Has(host) { if sd.skipSNI.Search(host) != nil {
log.Debugln("[Sniffer] Skip sni[%s]", host) log.Debugln("[Sniffer] Skip sni[%s]", host)
return return
} }
@ -83,21 +89,31 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
sd.skipList.Delete(dst) sd.skipList.Delete(dst)
sd.rwMux.RUnlock() sd.rwMux.RUnlock()
sd.replaceDomain(metadata, host, overrideDest) sd.replaceDomain(metadata, host)
} }
} }
} }
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) { func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) {
metadata.SniffHost = host dstIP := ""
if overrideDest { if metadata.DstIP.IsValid() {
metadata.Host = host dstIP = metadata.DstIP.String()
} }
originHost := metadata.Host
if originHost != host {
log.Infoln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
metadata.SrcIP, metadata.SrcPort,
dstIP, metadata.DstPort,
metadata.Host, host)
} else {
log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
metadata.SrcIP, metadata.SrcPort,
dstIP, metadata.DstPort,
metadata.Host, host)
}
metadata.Host = host
metadata.DNSMode = C.DNSNormal metadata.DNSMode = C.DNSNormal
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
metadata.SourceDetail(),
metadata.RemoteAddress(),
metadata.Host, host)
} }
func (sd *SnifferDispatcher) Enable() bool { func (sd *SnifferDispatcher) Enable() bool {
@ -105,7 +121,7 @@ func (sd *SnifferDispatcher) Enable() bool {
} }
func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) { func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) {
for s := range sd.sniffers { for _, s := range sd.sniffers {
if s.SupportNetwork() == C.TCP { if s.SupportNetwork() == C.TCP {
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second)) _ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
_, err := conn.Peek(1) _, err := conn.Peek(1)
@ -166,37 +182,38 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil return &dispatcher, nil
} }
func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[struct{}],
forceDomain *trie.DomainSet, skipSNI *trie.DomainSet, skipSNI *trie.DomainTrie[struct{}], ports *[]utils.Range[uint16],
forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) { forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) {
dispatcher := SnifferDispatcher{ dispatcher := SnifferDispatcher{
enable: true, enable: true,
forceDomain: forceDomain, forceDomain: forceDomain,
skipSNI: skipSNI, skipSNI: skipSNI,
skipList: cache.New(cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)), portRanges: ports,
skipList: cache.New[string, uint8](cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)),
forceDnsMapping: forceDnsMapping, forceDnsMapping: forceDnsMapping,
parsePureIp: parsePureIp, parsePureIp: parsePureIp,
sniffers: make(map[sniffer.Sniffer]SnifferConfig, 0),
} }
for snifferName, config := range snifferConfig { for _, snifferName := range needSniffer {
s, err := NewSniffer(snifferName, config) s, err := NewSniffer(snifferName)
if err != nil { if err != nil {
log.Errorln("Sniffer name[%s] is error", snifferName) log.Errorln("Sniffer name[%s] is error", snifferName)
return &SnifferDispatcher{enable: false}, err return &SnifferDispatcher{enable: false}, err
} }
dispatcher.sniffers[s] = config
dispatcher.sniffers = append(dispatcher.sniffers, s)
} }
return &dispatcher, nil return &dispatcher, nil
} }
func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer, error) { func NewSniffer(name sniffer.Type) (sniffer.Sniffer, error) {
switch name { switch name {
case sniffer.TLS: case sniffer.TLS:
return NewTLSSniffer(snifferConfig) return &TLSSniffer{}, nil
case sniffer.HTTP: case sniffer.HTTP:
return NewHTTPSniffer(snifferConfig) return &HTTPSniffer{}, nil
default: default:
return nil, ErrorUnsupportedSniffer return nil, ErrorUnsupportedSniffer
} }

View File

@ -7,9 +7,7 @@ import (
"net" "net"
"strings" "strings"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/sniffer"
) )
var ( var (
@ -26,25 +24,10 @@ const (
) )
type HTTPSniffer struct { type HTTPSniffer struct {
*BaseSniffer
version version version version
host string host string
} }
var _ sniffer.Sniffer = (*HTTPSniffer)(nil)
func NewHTTPSniffer(snifferConfig SnifferConfig) (*HTTPSniffer, error) {
ports := make([]utils.Range[uint16], 0)
if len(snifferConfig.Ports) == 0 {
ports = append(ports, *utils.NewRange[uint16](80, 80))
} else {
ports = append(ports, snifferConfig.Ports...)
}
return &HTTPSniffer{
BaseSniffer: NewBaseSniffer(ports, C.TCP),
}, nil
}
func (http *HTTPSniffer) Protocol() string { func (http *HTTPSniffer) Protocol() string {
switch http.version { switch http.version {
case HTTP1: case HTTP1:

View File

@ -5,9 +5,7 @@ import (
"errors" "errors"
"strings" "strings"
"github.com/Dreamacro/clash/common/utils"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/sniffer"
) )
var ( var (
@ -15,22 +13,7 @@ var (
errNotClientHello = errors.New("not client hello") errNotClientHello = errors.New("not client hello")
) )
var _ sniffer.Sniffer = (*TLSSniffer)(nil)
type TLSSniffer struct { type TLSSniffer struct {
*BaseSniffer
}
func NewTLSSniffer(snifferConfig SnifferConfig) (*TLSSniffer, error) {
ports := make([]utils.Range[uint16], 0)
if len(snifferConfig.Ports) == 0 {
ports = append(ports, *utils.NewRange[uint16](443, 443))
} else {
ports = append(ports, snifferConfig.Ports...)
}
return &TLSSniffer{
BaseSniffer: NewBaseSniffer(ports, C.TCP),
}, nil
} }
func (tls *TLSSniffer) Protocol() string { func (tls *TLSSniffer) Protocol() string {

View File

@ -6,91 +6,75 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"strings"
"sync"
xtls "github.com/xtls/go" xtls "github.com/xtls/go"
"sync"
"time"
) )
var trustCerts []*x509.Certificate var globalFingerprints = make([][32]byte, 0, 0)
var certPool *x509.CertPool var mutex sync.Mutex
var mutex sync.RWMutex
var errNotMacth error = errors.New("certificate fingerprints do not match")
func AddCertificate(certificate string) error { func verifyPeerCertificateAndFingerprints(fingerprints *[][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
mutex.Lock()
defer mutex.Unlock()
if certificate == "" {
return fmt.Errorf("certificate is empty")
}
if cert, err := x509.ParseCertificate([]byte(certificate)); err == nil {
trustCerts = append(trustCerts, cert)
return nil
} else {
return fmt.Errorf("add certificate failed")
}
}
func ResetCertificate() {
mutex.Lock()
defer mutex.Unlock()
trustCerts = nil
}
func getCertPool() *x509.CertPool {
if len(trustCerts) == 0 {
return nil
}
if certPool == nil {
mutex.Lock()
defer mutex.Unlock()
if certPool != nil {
return certPool
}
certPool, err := x509.SystemCertPool()
if err == nil {
for _, cert := range trustCerts {
certPool.AddCert(cert)
}
}
}
return certPool
}
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// ssl pining if insecureSkipVerify {
return nil
}
var preErr error
for i := range rawCerts { for i := range rawCerts {
rawCert := rawCerts[i] rawCert := rawCerts[i]
cert, err := x509.ParseCertificate(rawCert) cert, err := x509.ParseCertificate(rawCert)
if err == nil { if err == nil {
hash := sha256.Sum256(cert.Raw) opts := x509.VerifyOptions{
if bytes.Equal(fingerprint[:], hash[:]) { CurrentTime: time.Now(),
}
if _, err := cert.Verify(opts); err == nil {
return nil return nil
} else {
fingerprint := sha256.Sum256(cert.Raw)
for _, fp := range *fingerprints {
if bytes.Equal(fingerprint[:], fp[:]) {
return nil
}
}
preErr = err
} }
} }
} }
return errNotMacth
return preErr
} }
} }
func AddCertFingerprint(fingerprint string) error {
fpByte, err2 := convertFingerprint(fingerprint)
if err2 != nil {
return err2
}
mutex.Lock()
globalFingerprints = append(globalFingerprints, *fpByte)
mutex.Unlock()
return nil
}
func convertFingerprint(fingerprint string) (*[32]byte, error) { func convertFingerprint(fingerprint string) (*[32]byte, error) {
fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1))
fpByte, err := hex.DecodeString(fingerprint) fpByte, err := hex.DecodeString(fingerprint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(fpByte) != 32 { if len(fpByte) != 32 {
return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint") return nil, fmt.Errorf("fingerprint string length error,need sha25 fingerprint")
} }
return (*[32]byte)(fpByte), nil return (*[32]byte)(fpByte), nil
} }
func GetDefaultTLSConfig() *tls.Config { func GetDefaultTLSConfig() *tls.Config {
return GetGlobalTLSConfig(nil) return GetGlobalFingerprintTLCConfig(nil)
} }
// GetSpecifiedFingerprintTLSConfig specified fingerprint // GetSpecifiedFingerprintTLSConfig specified fingerprint
@ -98,21 +82,29 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string)
if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil {
return nil, err return nil, err
} else { } else {
tlsConfig = GetGlobalTLSConfig(tlsConfig) if tlsConfig == nil {
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) return &tls.Config{
tlsConfig.InsecureSkipVerify = true InsecureSkipVerify: true,
return tlsConfig, nil VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
} }
} }
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config { func GetGlobalFingerprintTLCConfig(tlsConfig *tls.Config) *tls.Config {
certPool := getCertPool()
if tlsConfig == nil { if tlsConfig == nil {
return &tls.Config{ return &tls.Config{
RootCAs: certPool, InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.RootCAs = certPool
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig return tlsConfig
} }
@ -121,21 +113,28 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin
if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil {
return nil, err return nil, err
} else { } else {
tlsConfig = GetGlobalXTLSConfig(tlsConfig) if tlsConfig == nil {
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) return &xtls.Config{
tlsConfig.InsecureSkipVerify = true InsecureSkipVerify: true,
return tlsConfig, nil VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
} }
} }
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config { func GetGlobalFingerprintXTLCConfig(tlsConfig *xtls.Config) *xtls.Config {
certPool := getCertPool()
if tlsConfig == nil { if tlsConfig == nil {
return &xtls.Config{ return &xtls.Config{
RootCAs: certPool, InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.RootCAs = certPool tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig return tlsConfig
} }

Some files were not shown because too many files have changed in this diff Show More