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
171 changed files with 1871 additions and 8997 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

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

@ -1,26 +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 =~ "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,325 +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
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: "freebsd-386 freebsd-amd64 freebsd-arm64",
id: "5",
}
- {
type: "WithoutCGO",
target: "windows-amd64-compatible windows-amd64 windows-386",
id: "6",
}
- {
type: "WithoutCGO",
target: "windows-arm64 windows-arm32v7",
id: "7",
}
- {
type: "WithoutCGO",
target: "darwin-amd64 darwin-arm64 android-arm64",
id: "8",
}
- { 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: |
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 -u)" >> $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 ..
- 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: Tag Repo
uses: richardsimko/update-tag@v1.0.6
with:
tag_name: Prerelease-${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- 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
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
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,17 +1,18 @@
FROM alpine:latest as builder FROM golang:alpine as builder
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` && \ 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"
@ -20,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)-$@
@ -120,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 [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki) and [Clash.Meta Wiki](https://docs.metacubex.one/). ## 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,8 +88,8 @@ 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:
@ -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,14 +18,23 @@ 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()
// Filter when net.Addr interface is nil
if remoteAddr != nil {
if ip, port, err := parseAddr(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 { }
localAddr := conn.LocalAddr()
// Filter when net.Addr interface is nil
if localAddr != nil {
if ip, port, err := parseAddr(localAddr.String()); err == nil {
metadata.InIP = ip metadata.InIP = ip
metadata.InPort = port 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,14 +4,12 @@ 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/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/gofrs/uuid"
) )
type Base struct { type Base struct {
@ -20,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
@ -90,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
@ -140,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"`
@ -159,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
@ -172,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,
@ -181,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
} }
@ -200,12 +185,8 @@ 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 {

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

@ -53,9 +53,6 @@ func (rw *nopConn) Read(b []byte) (int, error) {
} }
func (rw *nopConn) Write(b []byte) (int, error) { func (rw *nopConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
return 0, io.EOF return 0, io.EOF
} }

View File

@ -2,6 +2,7 @@ package outbound
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -9,9 +10,10 @@ import (
"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/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"
@ -31,7 +33,8 @@ type ShadowSocks struct {
obfsMode string obfsMode string
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowTLSOption
tlsConfig *tls.Config
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
@ -67,28 +70,11 @@ type shadowTLSOption struct {
Password string `obfs:"password"` Password string `obfs:"password"`
Host string `obfs:"host"` Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"` Fingerprint string `obfs:"fingerprint,omitempty"`
ClientFingerprint string `obfs:"client-fingerprint,omitempty"`
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
Version int `obfs:"version,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) {
switch ss.obfsMode {
case shadowtls.Mode:
// fix tls handshake not timeout
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
defer cancel()
var err error
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
}
return ss.streamConn(c, metadata)
}
func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, ss.obfsOption.Host) c = obfs.NewTLSObfs(c, ss.obfsOption.Host)
@ -101,11 +87,13 @@ func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
if err != nil { if err != nil {
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:
c = shadowtls.NewShadowTLS(c, ss.shadowTLSOption.Password, ss.tlsConfig)
} }
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")), nil return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443"))
} }
return ss.method.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil return ss.method.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -125,15 +113,7 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
switch ss.obfsMode { c, err = ss.StreamConn(c, metadata)
case shadowtls.Mode:
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil {
return nil, err
}
}
c, err = ss.streamConn(c, metadata)
return NewConn(c, ss), err return NewConn(c, ss), err
} }
@ -191,7 +171,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowTLSOption
var tlsConfig *tls.Config
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@ -229,20 +210,24 @@ 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: opt.ClientFingerprint, ServerName: shadowTLSOpt.Host,
SkipCertVerify: opt.SkipCertVerify, }
Version: opt.Version,
if len(shadowTLSOpt.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig)
} else {
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil {
return nil, err
}
} }
} }
@ -252,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),
@ -264,6 +248,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt, shadowTLSOption: shadowTLSOpt,
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"
@ -44,7 +43,6 @@ type TrojanOption struct {
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) {
@ -77,11 +75,6 @@ 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) c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig)
} else { } else {
@ -102,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
@ -224,7 +217,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
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 {
@ -250,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),
@ -277,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 {
@ -285,7 +276,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint) t.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
t.gunTLSConfig = tlsConfig t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{ t.gunConfig = &gun.Config{

View File

@ -51,7 +51,6 @@ type TuicOption struct {
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"`
SNI string `proxy:"sni,omitempty"`
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -107,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
@ -146,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 {
@ -168,7 +165,7 @@ 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 {
@ -216,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,6 +12,10 @@ 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"
@ -21,10 +25,6 @@ import (
"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 (
@ -66,16 +66,10 @@ type VlessOption struct {
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":
@ -86,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{},
} }
@ -105,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 != "" {
@ -171,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,7 +179,6 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint, FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint,
} }
if isH2 { if isH2 {
@ -206,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
@ -244,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 {
@ -293,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 {
@ -479,7 +465,7 @@ 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.XRO, vless.XRD, vless.XRS, vless.XRV: case vless.XRO, vless.XRD, vless.XRS:
addons = &vless.Addons{ addons = &vless.Addons{
Flow: option.Flow, Flow: option.Flow,
} }
@ -488,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
@ -509,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),
@ -519,6 +493,16 @@ func NewVless(option VlessOption) (*Vless, error) {
option: &option, option: &option,
} }
switch option.PacketEncoding {
case "packetaddr", "packet":
option.PacketAddr = true
case "xudp":
option.XUDP = true
}
if option.XUDP {
option.PacketAddr = false
}
switch option.Network { switch option.Network {
case "h2": case "h2":
if len(option.HTTP2Opts.Host) == 0 { if len(option.HTTP2Opts.Host) == 0 {
@ -537,9 +521,8 @@ 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,
}) })
@ -552,9 +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)
} }
return v, nil return v, nil

View File

@ -5,6 +5,8 @@ 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"
@ -13,12 +15,10 @@ 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"
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"
) )
@ -59,7 +59,6 @@ type VmessOption struct {
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 {
@ -87,11 +86,6 @@ 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":
@ -102,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{},
} }
@ -121,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
@ -142,7 +135,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -170,7 +162,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -197,7 +188,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -213,12 +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 {
return v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil return v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} else { } else {
return v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil return v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
} else { } else {
return v.client.DialEarlyConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), nil return v.client.DialConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
} }
@ -261,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 {
@ -289,9 +279,9 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
}(c) }(c)
if v.option.XUDP { if v.option.XUDP {
c = v.client.DialEarlyXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) c, err = v.client.DialXUDPPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} else { } else {
c = v.client.DialEarlyPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress())) c, err = v.client.DialPacketConn(c, M.ParseSocksaddr(metadata.RemoteAddress()))
} }
if err != nil { if err != nil {
@ -304,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 {
@ -376,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")
} }
} }
@ -386,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),
@ -414,7 +402,6 @@ 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,
@ -429,9 +416,7 @@ 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)
} }
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"
@ -174,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 {

View File

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
"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"
@ -31,20 +30,10 @@ 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)
} else {
f.onDialFailed(proxy.Type(), err)
}
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
f.onDialSuccess() f.onDialSuccess()
} else { } else {
f.onDialFailed(proxy.Type(), err) 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

@ -10,7 +10,6 @@ import (
"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"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -84,24 +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...)...)
defer func() {
if err == nil { if err == nil {
c.AppendToChains(lb) c.AppendToChains(lb)
} else {
lb.onDialFailed(proxy.Type(), err)
}
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
lb.onDialSuccess() lb.onDialSuccess()
} else { } else {
lb.onDialFailed(proxy.Type(), err) lb.onDialFailed(proxy.Type(), err)
} }
}, }()
}
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
return return
} }
@ -123,20 +115,11 @@ func (lb *LoadBalance) SupportUDP() bool {
} }
func strategyRoundRobin() strategyFn { func strategyRoundRobin() strategyFn {
flag := true
idx := 0 idx := 0
return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy { return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy {
length := len(proxies) length := len(proxies)
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
flag = !flag idx = (idx + 1) % length
if flag {
idx = (idx - 1) % length
} else {
idx = (idx + 2) % length
}
if idx < 0 {
idx = idx + length
}
proxy := proxies[idx] proxy := proxies[idx]
if proxy.Alive() { if proxy.Alive() {
return proxy return proxy

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

@ -6,7 +6,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/callback"
"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"
@ -39,20 +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)
} else {
u.onDialFailed(proxy.Type(), err)
}
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
u.onDialSuccess() u.onDialSuccess()
} else { } else {
u.onDialFailed(proxy.Type(), err) 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"
@ -57,11 +54,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
Path: []string{"/"}, Path: []string{"/"},
}, },
} }
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
vmessOption.ClientFingerprint = GlobalUtlsClient
}
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
if err != nil { if err != nil {
break break
@ -69,11 +61,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
proxy, err = outbound.NewVmess(*vmessOption) proxy, err = outbound.NewVmess(*vmessOption)
case "vless": case "vless":
vlessOption := &outbound.VlessOption{} vlessOption := &outbound.VlessOption{}
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
vlessOption.ClientFingerprint = GlobalUtlsClient
}
err = decoder.Decode(mapping, vlessOption) err = decoder.Decode(mapping, vlessOption)
if err != nil { if err != nil {
break break
@ -88,11 +75,6 @@ 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{} trojanOption := &outbound.TrojanOption{}
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
trojanOption.ClientFingerprint = GlobalUtlsClient
}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break

View File

@ -301,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"
@ -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

@ -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()
@ -29,11 +20,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
tls := strings.ToLower(query.Get("security")) tls := strings.ToLower(query.Get("security"))
if strings.HasSuffix(tls, "tls") { 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
@ -101,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":
@ -119,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

@ -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,22 +0,0 @@
package net
import (
"context"
"net"
"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
// 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,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

@ -6,23 +6,36 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strings" "strings"
"sync" "sync"
"time"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"go.uber.org/atomic"
) )
var ( var (
dialMux sync.Mutex dialMux sync.Mutex
actualSingleStackDialContext = serialSingleStackDialContext actualSingleDialContext = singleDialContext
actualDualStackDialContext = serialDualStackDialContext actualDualStackDialContext = dualStackDialContext
tcpConcurrent = false tcpConcurrent = false
DisableIPv6 = false
ErrorInvalidedNetworkStack = errors.New("invalided network stack") ErrorInvalidedNetworkStack = errors.New("invalided network stack")
ErrorConnTimeout = errors.New("connect timeout") ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel")
fallbackTimeout = 300 * time.Millisecond
) )
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(),
@ -55,7 +68,7 @@ func DialContext(ctx context.Context, network, address string, options ...Option
switch network { switch network {
case "tcp4", "tcp6", "udp4", "udp6": case "tcp4", "tcp6", "udp4", "udp6":
return actualSingleStackDialContext(ctx, network, address, opt) return actualSingleDialContext(ctx, network, address, opt)
case "tcp", "udp": case "tcp", "udp":
return actualDualStackDialContext(ctx, network, address, opt) return actualDualStackDialContext(ctx, network, address, opt)
default: default:
@ -64,7 +77,18 @@ func DialContext(ctx context.Context, network, address string, options ...Option
} }
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 != "" {
@ -88,11 +112,11 @@ func SetDial(concurrent bool) {
dialMux.Lock() dialMux.Lock()
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() dialMux.Unlock()
@ -113,64 +137,312 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
bindMarkToDialer(opt.routingMark, dialer, network, destination) bindMarkToDialer(opt.routingMark, dialer, network, destination)
} }
address := net.JoinHostPort(destination.String(), port) if DisableIPv6 && destination.Is6() {
if opt.tfo { return nil, ErrorDisableIPv6
return dialTFO(ctx, *dialer, network, address)
} }
return dialer.DialContext(ctx, network, address)
return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
} }
func serialSingleStackDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) { func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
ips, port, err := parseAddr(ctx, network, address, opt.resolver) host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
return serialDialContext(ctx, network, ips, port, opt)
}
func serialDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
if err != nil {
return nil, err
}
return dualStackDialContext(
ctx,
func(ctx context.Context) (net.Conn, error) { return serialDialContext(ctx, network, ips, port, opt) },
func(ctx context.Context) (net.Conn, error) { return serialDialContext(ctx, network, ips, port, opt) },
opt.prefer == 4)
}
func concurrentSingleStackDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
ips, port, err := parseAddr(ctx, network, address, opt.resolver)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if conn, err := parallelDialContext(ctx, network, ips, port, opt); err != nil { returned := make(chan struct{})
return nil, err defer close(returned)
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() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
_ = result.Conn.Close()
}
}
}()
var ip netip.Addr
if ipv6 {
if r == nil {
ip, result.error = resolver.ResolveIPv6ProxyServerHost(ctx, host)
} else { } else {
return conn, nil ip, result.error = resolver.ResolveIPv6WithResolver(ctx, host, r)
} }
} else {
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)
}
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
}
}
if err == nil {
err = fmt.Errorf("dual stack dial failed")
} else {
err = fmt.Errorf("dual stack dial failed:%w", err)
}
return nil, err
} }
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
ips, port, err := parseAddr(ctx, network, address, opt.resolver) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if opt.prefer != 4 && opt.prefer != 6 {
return parallelDialContext(ctx, network, ips, port, opt) var ips []netip.Addr
if opt.resolver != nil {
ips, err = resolver.LookupIPWithResolver(ctx, host, opt.resolver)
} else {
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
} }
ipv4s, ipv6s := sortationAddr(ips)
return dualStackDialContext( if err != nil {
ctx, return nil, err
func(ctx context.Context) (net.Conn, error) { }
return parallelDialContext(ctx, network, ipv4s, port, opt)
}, return concurrentDialContext(ctx, network, ips, port, opt)
func(ctx context.Context) (net.Conn, error) { }
return parallelDialContext(ctx, network, ipv6s, port, opt)
}, func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
opt.prefer == 4) returned := make(chan struct{})
defer close(returned)
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() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
_ = 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)
}
for _, ip := range ips {
go tcpRacer(ctx, ip)
}
connCount := len(ips)
var fallback dialResult
var primaryError error
var finalError error
for i := 0; i < connCount; i++ {
select {
case res := <-results:
if res.error == nil {
if res.isPrimary {
return res.Conn, nil
} else {
if !fallback.done || fallback.error != nil {
fallback = res
}
}
} 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
}
}
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
}
func singleDialContext(ctx context.Context, network string, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ip netip.Addr
switch network {
case "tcp4", "udp4":
if opt.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
if opt.resolver == nil {
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv4WithResolver(ctx, host, opt.resolver)
}
if err != nil {
return nil, err
}
return concurrentDialContext(ctx, network, ips, port, opt)
}
func concurrentIPv6DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
var ips []netip.Addr
if opt.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 {
@ -189,162 +461,3 @@ func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...) opt := applyOptions(options...)
return Dialer{Opt: *opt} return Dialer{Opt: *opt}
} }
func dualStackDialContext(
ctx context.Context,
ipv4DialFn func(ctx context.Context) (net.Conn, error),
ipv6DialFn func(ctx context.Context) (net.Conn, error),
preferIPv4 bool) (net.Conn, error) {
fallbackTicker := time.NewTicker(fallbackTimeout)
defer fallbackTicker.Stop()
results := make(chan dialResult)
returned := make(chan struct{})
defer close(returned)
racer := func(dial func(ctx context.Context) (net.Conn, error), isPrimary bool) {
result := dialResult{isPrimary: isPrimary}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
_ = result.Conn.Close()
}
}
}()
result.Conn, result.error = dial(ctx)
}
go racer(ipv4DialFn, preferIPv4)
go racer(ipv6DialFn, !preferIPv4)
var fallback dialResult
var err error
for {
select {
case <-ctx.Done():
if fallback.error == nil && fallback.Conn != nil {
return fallback.Conn, nil
}
return nil, fmt.Errorf("dual stack connect failed: %w", err)
case <-fallbackTicker.C:
if fallback.error == nil && fallback.Conn != nil {
return fallback.Conn, nil
}
case res := <-results:
if res.error == nil {
if res.isPrimary {
return res.Conn, nil
}
fallback = res
}
err = res.error
}
}
}
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
results := make(chan dialResult)
returned := make(chan struct{})
defer close(returned)
tcpRacer := func(ctx context.Context, ip netip.Addr, port string) {
result := dialResult{isPrimary: true}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
_ = result.Conn.Close()
}
}
}()
result.ip = ip
result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
}
for _, ip := range ips {
go tcpRacer(ctx, ip, port)
}
var err error
for {
select {
case <-ctx.Done():
if err != nil {
return nil, err
}
if ctx.Err() == context.DeadlineExceeded {
return nil, ErrorConnTimeout
}
return nil, ctx.Err()
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
err = res.error
}
}
}
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
var (
conn net.Conn
err error
errs []error
)
for _, ip := range ips {
if conn, err = dialContext(ctx, network, ip, port, opt); err == nil {
return conn, nil
} else {
errs = append(errs, err)
}
}
return nil, errors.Join(errs...)
}
type dialResult struct {
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)
if err != nil {
return nil, "-1", err
}
var ips []netip.Addr
switch network {
case "tcp4", "udp4":
if preferResolver == nil {
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
} 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.LookupIP(ctx, host)
} else {
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
}
}
if err != nil {
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
}
return ips, port, nil
}
func sortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
for _, v := range ips {
if v.Is4() || v.Is4In6() {
ipv4s = append(ipv4s, v)
} else {
ipv6s = append(ipv6s, v)
}
}
return
}

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

@ -18,7 +18,6 @@ type option struct {
routingMark int routingMark int
network int network int
prefer int prefer int
tfo bool
resolver resolver.Resolver resolver resolver.Resolver
} }
@ -70,12 +69,6 @@ func WithOnlySingleStack(isIPv4 bool) Option {
} }
} }
func WithTFO(tfo bool) Option {
return func(opt *option) {
opt.tfo = tfo
}
}
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,119 +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 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

@ -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

@ -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

@ -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

@ -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

@ -13,7 +13,7 @@ import (
) )
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

@ -11,8 +11,6 @@ import (
"time" "time"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/miekg/dns"
) )
var ( var (
@ -46,7 +44,6 @@ 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

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"
@ -27,16 +28,25 @@ var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct { type SnifferDispatcher struct {
enable bool enable bool
sniffers map[sniffer.Sniffer]SnifferConfig
sniffers []sniffer.Sniffer
forceDomain *trie.DomainTrie[struct{}] forceDomain *trie.DomainTrie[struct{}]
skipSNI *trie.DomainTrie[struct{}] skipSNI *trie.DomainTrie[struct{}]
portRanges *[]utils.Range[uint16]
skipList *cache.LruCache[string, uint8] skipList *cache.LruCache[string, uint8]
rwMux sync.RWMutex 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) {
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) { 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 {
@ -45,16 +55,12 @@ 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))
if inWhitelist {
overrideDest = config.OverrideDest
break break
} }
} }
}
if !inWhitelist { if !inWhitelist {
return return
@ -69,7 +75,7 @@ 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
@ -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()
} }
metadata.DNSMode = C.DNSNormal originHost := metadata.Host
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]", if originHost != host {
metadata.SourceDetail(), log.Infoln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
metadata.RemoteAddress(), metadata.SrcIP, metadata.SrcPort,
dstIP, metadata.DstPort,
metadata.Host, host) 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
} }
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, forceDomain *trie.DomainTrie[struct{}], func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[struct{}],
skipSNI *trie.DomainTrie[struct{}], 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,69 +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 trustCert,_ = x509.SystemCertPool() var globalFingerprints = make([][32]byte, 0, 0)
var mutex sync.Mutex
var mutex sync.RWMutex func verifyPeerCertificateAndFingerprints(fingerprints *[][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
var errNotMacth error = errors.New("certificate fingerprints do not match")
func AddCertificate(certificate string) error {
mutex.Lock()
defer mutex.Unlock()
if certificate == "" {
return fmt.Errorf("certificate is empty")
}
if ok := trustCert.AppendCertsFromPEM([]byte(certificate)); !ok {
return fmt.Errorf("add certificate failed")
}
return nil
}
func ResetCertificate(){
mutex.Lock()
defer mutex.Unlock()
trustCert,_=x509.SystemCertPool()
}
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
} else {
fingerprint := sha256.Sum256(cert.Raw)
for _, fp := range *fingerprints {
if bytes.Equal(fingerprint[:], fp[:]) {
return nil 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
@ -76,20 +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{
InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil return tlsConfig, nil
} }
}
} }
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config { func GetGlobalFingerprintTLCConfig(tlsConfig *tls.Config) *tls.Config {
if tlsConfig == nil { if tlsConfig == nil {
return &tls.Config{ return &tls.Config{
RootCAs: trustCert, InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.RootCAs = trustCert
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig return tlsConfig
} }
@ -98,20 +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{
InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil return tlsConfig, nil
} }
}
} }
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config { func GetGlobalFingerprintXTLCConfig(tlsConfig *xtls.Config) *xtls.Config {
if tlsConfig == nil { if tlsConfig == nil {
return &xtls.Config{ return &xtls.Config{
RootCAs: trustCert, InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.RootCAs = trustCert tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig return tlsConfig
} }

View File

@ -1,123 +0,0 @@
package tls
import (
"crypto/tls"
"net"
"github.com/Dreamacro/clash/log"
"github.com/mroth/weightedrand/v2"
utls "github.com/sagernet/utls"
)
type UConn struct {
*utls.UConn
}
type UClientHelloID struct {
*utls.ClientHelloID
}
var initRandomFingerprint UClientHelloID
var initUtlsClient string
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) net.Conn {
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{
Client: fingerprint.Client,
Version: fingerprint.Version,
Seed: fingerprint.Seed,
})
return &UConn{UConn: utlsConn}
}
func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) {
if ClientFingerprint == "none" {
return UClientHelloID{}, false
}
if initRandomFingerprint.ClientHelloID == nil {
initRandomFingerprint, _ = RollFingerprint()
}
if ClientFingerprint == "random" {
log.Debugln("use initial random HelloID:%s", initRandomFingerprint.Client)
return initRandomFingerprint, true
}
fingerprint, ok := Fingerprints[ClientFingerprint]
log.Debugln("use specified fingerprint:%s", fingerprint.Client)
return fingerprint, ok
}
func RollFingerprint() (UClientHelloID, bool) {
chooser, _ := weightedrand.NewChooser(
weightedrand.NewChoice("chrome", 6),
weightedrand.NewChoice("safari", 3),
weightedrand.NewChoice("ios", 2),
weightedrand.NewChoice("firefox", 1),
)
initClient := chooser.Pick()
log.Debugln("initial random HelloID:%s", initClient)
fingerprint, ok := Fingerprints[initClient]
return fingerprint, ok
}
var Fingerprints = map[string]UClientHelloID{
"chrome": {&utls.HelloChrome_Auto},
"firefox": {&utls.HelloFirefox_Auto},
"safari": {&utls.HelloSafari_Auto},
"ios": {&utls.HelloIOS_Auto},
"randomized": {&utls.HelloRandomized},
}
func copyConfig(c *tls.Config) *utls.Config {
return &utls.Config{
RootCAs: c.RootCAs,
ServerName: c.ServerName,
InsecureSkipVerify: c.InsecureSkipVerify,
VerifyPeerCertificate: c.VerifyPeerCertificate,
}
}
// WebsocketHandshake basically calls UConn.Handshake inside it but it will only send
// http/1.1 in its ALPN.
// Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go
func (c *UConn) WebsocketHandshake() error {
// Build the handshake state. This will apply every variable of the TLS of the
// fingerprint in the UConn
if err := c.BuildHandshakeState(); err != nil {
return err
}
// Iterate over extensions and check for utls.ALPNExtension
hasALPNExtension := false
for _, extension := range c.Extensions {
if alpn, ok := extension.(*utls.ALPNExtension); ok {
hasALPNExtension = true
alpn.AlpnProtocols = []string{"http/1.1"}
break
}
}
if !hasALPNExtension { // Append extension if doesn't exists
c.Extensions = append(c.Extensions, &utls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}})
}
// Rebuild the client hello and do the handshake
if err := c.BuildHandshakeState(); err != nil {
return err
}
return c.Handshake()
}
func SetGlobalUtlsClient(Client string) {
initUtlsClient = Client
}
func HaveGlobalFingerprint() bool {
if len(initUtlsClient) != 0 && initUtlsClient != "none" {
return true
}
return false
}
func GetGlobalFingerprint() string {
return initUtlsClient
}

View File

@ -4,12 +4,11 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
P "github.com/Dreamacro/clash/component/process"
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -25,9 +24,6 @@ import (
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
P "github.com/Dreamacro/clash/component/process"
SNIFF "github.com/Dreamacro/clash/component/sniffer"
tlsC "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
providerTypes "github.com/Dreamacro/clash/constant/provider" providerTypes "github.com/Dreamacro/clash/constant/provider"
@ -56,10 +52,10 @@ type General struct {
GeodataMode bool `json:"geodata-mode"` GeodataMode bool `json:"geodata-mode"`
GeodataLoader string `json:"geodata-loader"` GeodataLoader string `json:"geodata-loader"`
TCPConcurrent bool `json:"tcp-concurrent"` TCPConcurrent bool `json:"tcp-concurrent"`
EnableProcess bool `json:"enable-process"`
FindProcessMode P.FindProcessMode `json:"find-process-mode"` FindProcessMode P.FindProcessMode `json:"find-process-mode"`
Sniffing bool `json:"sniffing"` Sniffing bool `json:"sniffing"`
EBpf EBpf `json:"-"` EBpf EBpf `json:"-"`
GlobalClientFingerprint string `json:"global-client-fingerprint"`
} }
// Inbound config // Inbound config
@ -100,7 +96,7 @@ type DNS struct {
DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` DefaultNameserver []dns.NameServer `yaml:"default-nameserver"`
FakeIPRange *fakeip.Pool FakeIPRange *fakeip.Pool
Hosts *trie.DomainTrie[netip.Addr] Hosts *trie.DomainTrie[netip.Addr]
NameServerPolicy map[string][]dns.NameServer NameServerPolicy map[string]dns.NameServer
ProxyServerNameserver []dns.NameServer ProxyServerNameserver []dns.NameServer
} }
@ -122,7 +118,6 @@ type Profile struct {
type TLS struct { type TLS struct {
Certificate string `yaml:"certificate"` Certificate string `yaml:"certificate"`
PrivateKey string `yaml:"private-key"` PrivateKey string `yaml:"private-key"`
CustomTrustCert []string `yaml:"custom-certifactes"`
} }
// IPTables config // IPTables config
@ -134,10 +129,11 @@ type IPTables struct {
type Sniffer struct { type Sniffer struct {
Enable bool Enable bool
Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig Sniffers []snifferTypes.Type
Reverses *trie.DomainTrie[struct{}] Reverses *trie.DomainTrie[struct{}]
ForceDomain *trie.DomainTrie[struct{}] ForceDomain *trie.DomainTrie[struct{}]
SkipDomain *trie.DomainTrie[struct{}] SkipDomain *trie.DomainTrie[struct{}]
Ports *[]utils.Range[uint16]
ForceDnsMapping bool ForceDnsMapping bool
ParsePureIp bool ParsePureIp bool
} }
@ -180,7 +176,7 @@ type RawDNS struct {
FakeIPRange string `yaml:"fake-ip-range"` FakeIPRange string `yaml:"fake-ip-range"`
FakeIPFilter []string `yaml:"fake-ip-filter"` FakeIPFilter []string `yaml:"fake-ip-filter"`
DefaultNameserver []string `yaml:"default-nameserver"` DefaultNameserver []string `yaml:"default-nameserver"`
NameServerPolicy map[string]any `yaml:"nameserver-policy"` NameServerPolicy map[string]string `yaml:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"` ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
} }
@ -257,8 +253,8 @@ type RawConfig struct {
GeodataMode bool `yaml:"geodata-mode"` GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"` GeodataLoader string `yaml:"geodata-loader"`
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
EnableProcess bool `yaml:"enable-process" json:"enable-process"`
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
Sniffer RawSniffer `yaml:"sniffer"` Sniffer RawSniffer `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
@ -288,19 +284,12 @@ type RawGeoXUrl struct {
type RawSniffer struct { type RawSniffer struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
OverrideDest bool `yaml:"override-destination" json:"override-destination"`
Sniffing []string `yaml:"sniffing" json:"sniffing"` Sniffing []string `yaml:"sniffing" json:"sniffing"`
ForceDomain []string `yaml:"force-domain" json:"force-domain"` ForceDomain []string `yaml:"force-domain" json:"force-domain"`
SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` SkipDomain []string `yaml:"skip-domain" json:"skip-domain"`
Ports []string `yaml:"port-whitelist" json:"port-whitelist"` Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"` ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"`
ParsePureIp bool `yaml:"parse-pure-ip" json:"parse-pure-ip"` ParsePureIp bool `yaml:"parse-pure-ip" json:"parse-pure-ip"`
Sniff map[string]RawSniffingConfig `yaml:"sniff" json:"sniff"`
}
type RawSniffingConfig struct {
Ports []string `yaml:"ports" json:"ports"`
OverrideDest *bool `yaml:"override-destination" json:"override-destination"`
} }
// EBpf config // EBpf config
@ -342,6 +331,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Proxy: []map[string]any{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
TCPConcurrent: false, TCPConcurrent: false,
EnableProcess: false,
FindProcessMode: P.FindProcessStrict, FindProcessMode: P.FindProcessStrict,
Tun: RawTun{ Tun: RawTun{
Enable: false, Enable: false,
@ -409,7 +399,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Ports: []string{}, Ports: []string{},
ForceDnsMapping: true, ForceDnsMapping: true,
ParsePureIp: true, ParsePureIp: true,
OverrideDest: true,
}, },
Profile: Profile{ Profile: Profile{
StoreSelected: true, StoreSelected: true,
@ -518,11 +507,6 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms
log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm log.Infoln("Initial configuration complete, total time: %dms", elapsedTime) //Segment finished in xxm
if len(config.General.GlobalClientFingerprint) != 0 {
log.Debugln("GlobalClientFingerprint:%s", config.General.GlobalClientFingerprint)
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
}
return config, nil return config, nil
} }
@ -565,9 +549,9 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
GeodataMode: cfg.GeodataMode, GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent, TCPConcurrent: cfg.TCPConcurrent,
EnableProcess: cfg.EnableProcess,
FindProcessMode: cfg.FindProcessMode, FindProcessMode: cfg.FindProcessMode,
EBpf: cfg.EBpf, EBpf: cfg.EBpf,
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
}, nil }, nil
} }
@ -847,15 +831,17 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) {
} }
func hostWithDefaultPort(host string, defPort string) (string, error) { func hostWithDefaultPort(host string, defPort string) (string, error) {
if !strings.Contains(host, ":") {
host += ":"
}
hostname, port, err := net.SplitHostPort(host) hostname, port, err := net.SplitHostPort(host)
if err != nil { if err != nil {
if !strings.Contains(err.Error(), "missing port in address") {
return "", err
}
host = host + ":" + defPort
if hostname, port, err = net.SplitHostPort(host); err != nil {
return "", err return "", err
} }
if port == "" {
port = defPort
} }
return net.JoinHostPort(hostname, port), nil return net.JoinHostPort(hostname, port), nil
@ -865,7 +851,10 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
var nameservers []dns.NameServer var nameservers []dns.NameServer
for idx, server := range servers { for idx, server := range servers {
server = parsePureDNSServer(server) // parse without scheme .e.g 8.8.8.8:53
if !strings.Contains(server, "://") {
server = "udp://" + server
}
u, err := url.Parse(server) u, err := url.Parse(server)
if err != nil { if err != nil {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
@ -886,10 +875,16 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
addr, err = hostWithDefaultPort(u.Host, "853") addr, err = hostWithDefaultPort(u.Host, "853")
dnsNetType = "tcp-tls" // DNS over TLS dnsNetType = "tcp-tls" // DNS over TLS
case "https": case "https":
addr, err = hostWithDefaultPort(u.Host, "443") host := u.Host
if err == nil {
proxyAdapter = "" proxyAdapter = ""
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path} if _, _, err := net.SplitHostPort(host); err != nil && strings.Contains(err.Error(), "missing port in address") {
host = net.JoinHostPort(host, "443")
} else {
if err != nil {
return nil, err
}
}
clearURL := url.URL{Scheme: "https", Host: host, Path: u.Path}
addr = clearURL.String() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS dnsNetType = "https" // DNS over HTTPS
if len(u.Fragment) != 0 { if len(u.Fragment) != 0 {
@ -906,7 +901,6 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
} }
} }
} }
}
case "dhcp": case "dhcp":
addr = u.Host addr = u.Host
dnsNetType = "dhcp" // UDP from DHCP dnsNetType = "dhcp" // UDP from DHCP
@ -936,53 +930,18 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
return nameservers, nil return nameservers, nil
} }
func parsePureDNSServer(server string) string { func parseNameServerPolicy(nsPolicy map[string]string, preferH3 bool) (map[string]dns.NameServer, error) {
addPre := func(server string) string { policy := map[string]dns.NameServer{}
return "udp://" + server
}
if ip, err := netip.ParseAddr(server); err != nil {
if strings.Contains(server, "://") {
return server
}
return addPre(server)
} else {
if ip.Is4() {
return addPre(server)
} else {
return addPre("[" + server + "]")
}
}
}
func parseNameServerPolicy(nsPolicy map[string]any, preferH3 bool) (map[string][]dns.NameServer, error) {
policy := map[string][]dns.NameServer{}
for domain, server := range nsPolicy { for domain, server := range nsPolicy {
var ( nameservers, err := parseNameServer([]string{server}, preferH3)
nameservers []dns.NameServer
err error
)
switch reflect.TypeOf(server).Kind() {
case reflect.Slice, reflect.Array:
origin := reflect.ValueOf(server)
servers := make([]string, 0)
for i := 0; i < origin.Len(); i++ {
servers = append(servers, fmt.Sprintf("%v", origin.Index(i)))
}
nameservers, err = parseNameServer(servers, preferH3)
case reflect.String:
nameservers, err = parseNameServer([]string{fmt.Sprintf("%v", server)}, preferH3)
default:
return nil, errors.New("server format error, must be string or array")
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, valid := trie.ValidAndSplitDomain(domain); !valid { if _, valid := trie.ValidAndSplitDomain(domain); !valid {
return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain) return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain)
} }
policy[domain] = nameservers policy[domain] = nameservers[0]
} }
return policy, nil return policy, nil
@ -1008,7 +967,6 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM
if err := geodata.InitGeoSite(); err != nil { if err := geodata.InitGeoSite(); err != nil {
return nil, fmt.Errorf("can't initial GeoSite: %s", err) return nil, fmt.Errorf("can't initial GeoSite: %s", err)
} }
log.Warnln("replace fallback-filter.geosite with nameserver-policy, it will be removed in the future")
} }
for _, country := range countries { for _, country := range countries {
@ -1227,50 +1185,44 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
ForceDnsMapping: snifferRaw.ForceDnsMapping, ForceDnsMapping: snifferRaw.ForceDnsMapping,
ParsePureIp: snifferRaw.ParsePureIp, ParsePureIp: snifferRaw.ParsePureIp,
} }
loadSniffer := make(map[snifferTypes.Type]SNIFF.SnifferConfig)
if len(snifferRaw.Sniff) != 0 { var ports []utils.Range[uint16]
for sniffType, sniffConfig := range snifferRaw.Sniff { if len(snifferRaw.Ports) == 0 {
find := false ports = append(ports, *utils.NewRange[uint16](80, 80))
ports, err := parsePortRange(sniffConfig.Ports) ports = append(ports, *utils.NewRange[uint16](443, 443))
if err != nil {
return nil, err
}
overrideDest := snifferRaw.OverrideDest
if sniffConfig.OverrideDest != nil {
overrideDest = *sniffConfig.OverrideDest
}
for _, snifferType := range snifferTypes.List {
if snifferType.String() == strings.ToUpper(sniffType) {
find = true
loadSniffer[snifferType] = SNIFF.SnifferConfig{
Ports: ports,
OverrideDest: overrideDest,
}
}
}
if !find {
return nil, fmt.Errorf("not find the sniffer[%s]", sniffType)
}
}
} else { } else {
// Deprecated: Use Sniff instead for _, portRange := range snifferRaw.Ports {
log.Warnln("Deprecated: Use Sniff instead") portRaws := strings.Split(portRange, "-")
globalPorts, err := parsePortRange(snifferRaw.Ports) p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("%s format error", portRange)
} }
start := uint16(p)
if len(portRaws) > 1 {
p, err = strconv.ParseUint(portRaws[1], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
end := uint16(p)
ports = append(ports, *utils.NewRange(start, end))
} else {
ports = append(ports, *utils.NewRange(start, start))
}
}
}
sniffer.Ports = &ports
loadSniffer := make(map[snifferTypes.Type]struct{})
for _, snifferName := range snifferRaw.Sniffing { for _, snifferName := range snifferRaw.Sniffing {
find := false find := false
for _, snifferType := range snifferTypes.List { for _, snifferType := range snifferTypes.List {
if snifferType.String() == strings.ToUpper(snifferName) { if snifferType.String() == strings.ToUpper(snifferName) {
find = true find = true
loadSniffer[snifferType] = SNIFF.SnifferConfig{ loadSniffer[snifferType] = struct{}{}
Ports: globalPorts,
OverrideDest: snifferRaw.OverrideDest,
}
} }
} }
@ -1278,9 +1230,10 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
return nil, fmt.Errorf("not find the sniffer[%s]", snifferName) return nil, fmt.Errorf("not find the sniffer[%s]", snifferName)
} }
} }
}
sniffer.Sniffers = loadSniffer for st := range loadSniffer {
sniffer.Sniffers = append(sniffer.Sniffers, st)
}
sniffer.ForceDomain = trie.New[struct{}]() sniffer.ForceDomain = trie.New[struct{}]()
for _, domain := range snifferRaw.ForceDomain { for _, domain := range snifferRaw.ForceDomain {
err := sniffer.ForceDomain.Insert(domain, struct{}{}) err := sniffer.ForceDomain.Insert(domain, struct{}{})
@ -1301,28 +1254,3 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
return sniffer, nil return sniffer, nil
} }
func parsePortRange(portRanges []string) ([]utils.Range[uint16], error) {
ports := make([]utils.Range[uint16], 0)
for _, portRange := range portRanges {
portRaws := strings.Split(portRange, "-")
p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
start := uint16(p)
if len(portRaws) > 1 {
p, err = strconv.ParseUint(portRaws[1], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
end := uint16(p)
ports = append(ports, *utils.NewRange(start, end))
} else {
ports = append(ports, *utils.NewRange(start, start))
}
}
return ports, nil
}

View File

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"sync"
"time" "time"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@ -93,7 +92,6 @@ type ProxyAdapter interface {
Type() AdapterType Type() AdapterType
Addr() string Addr() string
SupportUDP() bool SupportUDP() bool
SupportXUDP() bool
SupportTFO() bool SupportTFO() bool
MarshalJSON() ([]byte, error) MarshalJSON() ([]byte, error)
@ -228,23 +226,3 @@ type PacketAdapter interface {
UDPPacket UDPPacket
Metadata() *Metadata Metadata() *Metadata
} }
type NatTable interface {
Set(key string, e PacketConn)
Get(key string) PacketConn
GetOrCreateLock(key string) (*sync.Cond, bool)
Delete(key string)
GetLocalConn(lAddr, rAddr string) *net.UDPConn
AddLocalConn(lAddr, rAddr string, conn *net.UDPConn) bool
RangeLocalConn(lAddr string, f func(key, value any) bool)
GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool)
DeleteLocalConnMap(lAddr, key string)
}

View File

@ -3,8 +3,6 @@ package constant
import ( import (
"net" "net"
N "github.com/Dreamacro/clash/common/net"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
) )
@ -15,7 +13,7 @@ type PlainContext interface {
type ConnContext interface { type ConnContext interface {
PlainContext PlainContext
Metadata() *Metadata Metadata() *Metadata
Conn() *N.BufferedConn Conn() net.Conn
} }
type PacketConnContext interface { type PacketConnContext interface {

View File

@ -16,7 +16,7 @@ type MultiAddrListener interface {
type InboundListener interface { type InboundListener interface {
Name() string Name() string
Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter, natTable NatTable) error Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter) error
Close() error Close() error
Address() string Address() string
RawAddress() string RawAddress() string

View File

@ -128,14 +128,12 @@ type Metadata struct {
InName string `json:"inboundName"` InName string `json:"inboundName"`
Host string `json:"host"` Host string `json:"host"`
DNSMode DNSMode `json:"dnsMode"` DNSMode DNSMode `json:"dnsMode"`
Uid uint32 `json:"uid"` Uid *uint32 `json:"uid"`
Process string `json:"process"` Process string `json:"process"`
ProcessPath string `json:"processPath"` ProcessPath string `json:"processPath"`
SpecialProxy string `json:"specialProxy"` SpecialProxy string `json:"specialProxy"`
SpecialRules string `json:"specialRules"` SpecialRules string `json:"specialRules"`
RemoteDst string `json:"remoteDestination"` RemoteDst string `json:"remoteDestination"`
// Only domain rule
SniffHost string `json:"sniffHost"`
} }
func (m *Metadata) RemoteAddress() string { func (m *Metadata) RemoteAddress() string {
@ -148,17 +146,16 @@ func (m *Metadata) SourceAddress() string {
func (m *Metadata) SourceDetail() string { func (m *Metadata) SourceDetail() string {
if m.Type == INNER { if m.Type == INNER {
return fmt.Sprintf("%s", ClashName) return fmt.Sprintf("[%s]", ClashName)
} }
switch { if m.Process != "" && m.Uid != nil {
case m.Process != "" && m.Uid != 0: return fmt.Sprintf("%s(%s, uid=%d)", m.SourceAddress(), m.Process, *m.Uid)
return fmt.Sprintf("%s(%s, uid=%d)", m.SourceAddress(), m.Process, m.Uid) } else if m.Uid != nil {
case m.Uid != 0: return fmt.Sprintf("%s(uid=%d)", m.SourceAddress(), *m.Uid)
return fmt.Sprintf("%s(uid=%d)", m.SourceAddress(), m.Uid) } else if m.Process != "" {
case m.Process != "":
return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process) return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process)
default: } else {
return fmt.Sprintf("%s", m.SourceAddress()) return fmt.Sprintf("%s", m.SourceAddress())
} }
} }
@ -178,14 +175,6 @@ func (m *Metadata) Resolved() bool {
return m.DstIP.IsValid() return m.DstIP.IsValid()
} }
func (m *Metadata) RuleHost() string {
if len(m.SniffHost) == 0 {
return m.Host
} else {
return m.SniffHost
}
}
// Pure is used to solve unexpected behavior // Pure is used to solve unexpected behavior
// when dialing proxy connection in DNSMapping mode. // when dialing proxy connection in DNSMapping mode.
func (m *Metadata) Pure() *Metadata { func (m *Metadata) Pure() *Metadata {

View File

@ -102,6 +102,5 @@ type RuleProvider interface {
Behavior() RuleType Behavior() RuleType
Match(*constant.Metadata) bool Match(*constant.Metadata) bool
ShouldResolveIP() bool ShouldResolveIP() bool
ShouldFindProcess() bool
AsRule(adaptor string) constant.Rule AsRule(adaptor string) constant.Rule
} }

View File

@ -6,7 +6,6 @@ type Sniffer interface {
SupportNetwork() constant.NetWork SupportNetwork() constant.NetWork
SniffTCP(bytes []byte) (string, error) SniffTCP(bytes []byte) (string, error)
Protocol() string Protocol() string
SupportPort(port uint16) bool
} }
const ( const (

View File

@ -4,5 +4,5 @@ var (
Meta = true Meta = true
Version = "1.10.0" Version = "1.10.0"
BuildTime = "unknown time" BuildTime = "unknown time"
ClashName = "clash.meta" ClashName = "Clash.Meta"
) )

View File

@ -12,7 +12,7 @@ import (
type ConnContext struct { type ConnContext struct {
id uuid.UUID id uuid.UUID
metadata *C.Metadata metadata *C.Metadata
conn *N.BufferedConn conn net.Conn
} }
func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext { func NewConnContext(conn net.Conn, metadata *C.Metadata) *ConnContext {
@ -36,6 +36,6 @@ func (c *ConnContext) Metadata() *C.Metadata {
} }
// Conn implement C.ConnContext Conn // Conn implement C.ConnContext Conn
func (c *ConnContext) Conn() *N.BufferedConn { func (c *ConnContext) Conn() net.Conn {
return c.conn return c.conn
} }

View File

@ -4,14 +4,13 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"go.uber.org/atomic"
"math/rand" "math/rand"
"net" "net"
"net/netip" "net/netip"
"strings" "strings"
tlsC "github.com/Dreamacro/clash/component/tls"
"go.uber.org/atomic"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@ -25,26 +24,6 @@ type client struct {
host string host string
iface *atomic.String iface *atomic.String
proxyAdapter string proxyAdapter string
addr string
}
var _ dnsClient = (*client)(nil)
// Address implements dnsClient
func (c *client) Address() string {
if len(c.addr) != 0 {
return c.addr
}
schema := "udp"
if strings.HasPrefix(c.Client.Net, "tcp") {
schema = "tcp"
if strings.HasSuffix(c.Client.Net, "tls") {
schema = "tls"
}
}
c.addr = fmt.Sprintf("%s://%s", schema, net.JoinHostPort(c.host, c.port))
return c.addr
} }
func (c *client) Exchange(m *D.Msg) (*D.Msg, error) { func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
@ -98,7 +77,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
ch := make(chan result, 1) ch := make(chan result, 1)
go func() { go func() {
if strings.HasSuffix(c.Client.Net, "tls") { if strings.HasSuffix(c.Client.Net, "tls") {
conn = tls.Client(conn, tlsC.GetGlobalTLSConfig(c.Client.TLSConfig)) conn = tls.Client(conn, tlsC.GetGlobalFingerprintTLCConfig(c.Client.TLSConfig))
} }
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{ msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{

View File

@ -2,14 +2,12 @@ package dns
import ( import (
"context" "context"
"go.uber.org/atomic"
"net" "net"
"net/netip" "net/netip"
"strings"
"sync" "sync"
"time" "time"
"go.uber.org/atomic"
"github.com/Dreamacro/clash/component/dhcp" "github.com/Dreamacro/clash/component/dhcp"
"github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/iface"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@ -36,17 +34,6 @@ type dhcpClient struct {
err error err error
} }
var _ dnsClient = (*dhcpClient)(nil)
// Address implements dnsClient
func (d *dhcpClient) Address() string {
addrs := make([]string, 0)
for _, c := range d.clients {
addrs = append(addrs, c.Address())
}
return strings.Join(addrs, ",")
}
func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) { func (d *dhcpClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel() defer cancel()

View File

@ -64,7 +64,6 @@ type dnsOverHTTPS struct {
r *Resolver r *Resolver
httpVersions []C.HTTPVersion httpVersions []C.HTTPVersion
proxyAdapter string proxyAdapter string
addr string
} }
// type check // type check
@ -84,7 +83,6 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
doh := &dnsOverHTTPS{ doh := &dnsOverHTTPS{
url: u, url: u,
addr: u.String(),
r: r, r: r,
proxyAdapter: proxyAdapter, proxyAdapter: proxyAdapter,
quicConfig: &quic.Config{ quicConfig: &quic.Config{
@ -100,16 +98,13 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
} }
// Address implements the Upstream interface for *dnsOverHTTPS. // Address implements the Upstream interface for *dnsOverHTTPS.
func (doh *dnsOverHTTPS) Address() string { func (doh *dnsOverHTTPS) Address() string { return doh.url.String() }
return doh.addr
}
func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
// Quote from https://www.rfc-editor.org/rfc/rfc8484.html: // Quote from https://www.rfc-editor.org/rfc/rfc8484.html:
// In order to maximize HTTP cache friendliness, DoH clients using media // In order to maximize HTTP cache friendliness, DoH clients using media
// formats that include the ID field from the DNS message header, such // formats that include the ID field from the DNS message header, such
// as "application/dns-message", SHOULD use a DNS ID of 0 in every DNS // as "application/dns-message", SHOULD use a DNS ID of 0 in every DNS
// request. // request.
m = m.Copy()
id := m.Id id := m.Id
m.Id = 0 m.Id = 0
defer func() { defer func() {
@ -379,7 +374,7 @@ func (doh *dnsOverHTTPS) createClient(ctx context.Context) (*http.Client, error)
// HTTP3 is enabled in the upstream options). If this attempt is successful, // HTTP3 is enabled in the upstream options). If this attempt is successful,
// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport. // it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) { func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) {
tlsConfig := tlsC.GetGlobalTLSConfig( tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
&tls.Config{ &tls.Config{
InsecureSkipVerify: false, InsecureSkipVerify: false,
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,

View File

@ -89,7 +89,6 @@ func (doq *dnsOverQUIC) Address() string { return doq.addr }
func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) { func (doq *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
// When sending queries over a QUIC connection, the DNS Message ID MUST be // When sending queries over a QUIC connection, the DNS Message ID MUST be
// set to zero. // set to zero.
m = m.Copy()
id := m.Id id := m.Id
m.Id = 0 m.Id = 0
defer func() { defer func() {
@ -299,7 +298,7 @@ func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (q
// openConnection opens a new QUIC connection. // openConnection opens a new QUIC connection.
func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) { func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) {
tlsConfig := tlsC.GetGlobalTLSConfig( tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
&tls.Config{ &tls.Config{
InsecureSkipVerify: false, InsecureSkipVerify: false,
NextProtos: []string{ NextProtos: []string{

View File

@ -109,7 +109,7 @@ func NewEnhancer(cfg Config) *ResolverEnhancer {
if cfg.EnhancedMode != C.DNSNormal { if cfg.EnhancedMode != C.DNSNormal {
fakePool = cfg.Pool fakePool = cfg.Pool
mapping = cache.New(cache.WithSize[netip.Addr, string](4096), cache.WithStale[netip.Addr, string](true)) mapping = cache.New[netip.Addr, string](cache.WithSize[netip.Addr, string](4096), cache.WithStale[netip.Addr, string](true))
} }
return &ResolverEnhancer{ return &ResolverEnhancer{

View File

@ -91,17 +91,6 @@ type geoSiteFilter struct {
matchers []*router.DomainMatcher matchers []*router.DomainMatcher
} }
func NewGeoSite(group string) (fallbackDomainFilter, error) {
matcher, _, err := geodata.LoadGeoSiteMatcher(group)
if err != nil {
return nil, err
}
filter := &geoSiteFilter{
matchers: []*router.DomainMatcher{matcher},
}
return filter, nil
}
func (gsf *geoSiteFilter) Match(domain string) bool { func (gsf *geoSiteFilter) Match(domain string) bool {
for _, matcher := range gsf.matchers { for _, matcher := range gsf.matchers {
if matcher.ApplyDomain(domain) { if matcher.ApplyDomain(domain) {

View File

@ -164,6 +164,7 @@ func withResolver(resolver *Resolver) handler {
msg.SetRcode(r, msg.Rcode) msg.SetRcode(r, msg.Rcode)
msg.Authoritative = true msg.Authoritative = true
log.Debugln("[DNS] %s --> %s", msgToDomain(r), msgToIP(msg))
return msg, nil return msg, nil
} }
} }

View File

@ -4,20 +4,17 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"go.uber.org/atomic"
"math/rand" "math/rand"
"net/netip" "net/netip"
"strings"
"time" "time"
"go.uber.org/atomic"
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/fakeip"
"github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/geodata/router"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
"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/log"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
@ -26,7 +23,6 @@ import (
type dnsClient interface { type dnsClient interface {
Exchange(m *D.Msg) (msg *D.Msg, err error) Exchange(m *D.Msg) (msg *D.Msg, err error)
ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error)
Address() string
} }
type result struct { type result struct {
@ -34,12 +30,6 @@ type result struct {
Error error Error error
} }
type geositePolicyRecord struct {
matcher fallbackDomainFilter
policy *Policy
inversedMatching bool
}
type Resolver struct { type Resolver struct {
ipv6 bool ipv6 bool
hosts *trie.DomainTrie[netip.Addr] hosts *trie.DomainTrie[netip.Addr]
@ -50,7 +40,6 @@ type Resolver struct {
group singleflight.Group group singleflight.Group
lruCache *cache.LruCache[string, *D.Msg] lruCache *cache.LruCache[string, *D.Msg]
policy *trie.DomainTrie[*Policy] policy *trie.DomainTrie[*Policy]
geositePolicy []geositePolicyRecord
proxyServer []dnsClient proxyServer []dnsClient
} }
@ -283,18 +272,12 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient {
} }
record := r.policy.Search(domain) record := r.policy.Search(domain)
if record != nil { if record == nil {
p := record.Data() return nil
return p.GetData()
} }
for _, geositeRecord := range r.geositePolicy { p := record.Data()
matched := geositeRecord.matcher.Match(domain) return p.GetData()
if matched != geositeRecord.inversedMatching {
return geositeRecord.policy.GetData()
}
}
return nil
} }
func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool {
@ -423,19 +406,19 @@ type Config struct {
FallbackFilter FallbackFilter FallbackFilter FallbackFilter
Pool *fakeip.Pool Pool *fakeip.Pool
Hosts *trie.DomainTrie[netip.Addr] Hosts *trie.DomainTrie[netip.Addr]
Policy map[string][]NameServer Policy map[string]NameServer
} }
func NewResolver(config Config) *Resolver { func NewResolver(config Config) *Resolver {
defaultResolver := &Resolver{ defaultResolver := &Resolver{
main: transform(config.Default, nil), main: transform(config.Default, nil),
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)), lruCache: cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
} }
r := &Resolver{ r := &Resolver{
ipv6: config.IPv6, ipv6: config.IPv6,
main: transform(config.Main, defaultResolver), main: transform(config.Main, defaultResolver),
lruCache: cache.New(cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)), lruCache: cache.New[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
hosts: config.Hosts, hosts: config.Hosts,
} }
@ -450,26 +433,7 @@ func NewResolver(config Config) *Resolver {
if len(config.Policy) != 0 { if len(config.Policy) != 0 {
r.policy = trie.New[*Policy]() r.policy = trie.New[*Policy]()
for domain, nameserver := range config.Policy { for domain, nameserver := range config.Policy {
if strings.HasPrefix(strings.ToLower(domain), "geosite:") { _ = r.policy.Insert(domain, NewPolicy(transform([]NameServer{nameserver}, defaultResolver)))
groupname := domain[8:]
inverse := false
if strings.HasPrefix(groupname, "!") {
inverse = true
groupname = groupname[1:]
}
log.Debugln("adding geosite policy: %s inversed %t", groupname, inverse)
matcher, err := NewGeoSite(groupname)
if err != nil {
continue
}
r.geositePolicy = append(r.geositePolicy, geositePolicyRecord{
matcher: matcher,
policy: NewPolicy(transform(nameserver, defaultResolver)),
inversedMatching: inverse,
})
} else {
_ = r.policy.Insert(domain, NewPolicy(transform(nameserver, defaultResolver)))
}
} }
r.policy.Optimize() r.policy.Optimize()
} }

View File

@ -235,18 +235,15 @@ func listenPacket(ctx context.Context, proxyAdapter string, network string, addr
func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) { func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.Msg, err error) {
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
domain := msgToDomain(m)
for _, client := range clients { for _, client := range clients {
r := client r := client
fast.Go(func() (*D.Msg, error) { fast.Go(func() (*D.Msg, error) {
log.Debugln("[DNS] resolve %s from %s", domain, r.Address())
m, err := r.ExchangeContext(ctx, m) m, err := r.ExchangeContext(ctx, m)
if err != nil { if err != nil {
return nil, err return nil, err
} else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused { } else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused {
return nil, errors.New("server failure") return nil, errors.New("server failure")
} }
log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), r.Address())
return m, nil return m, nil
}) })
} }
@ -259,6 +256,7 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
} }
return nil, err return nil, err
} }
msg = elm msg = elm
return return
} }

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