Compare commits

..

22 Commits

Author SHA1 Message Date
95564f1ae6 Revert "fix #322: add option general.find-process-mode, user can turn off findProcess feature in router" 2023-01-06 12:17:46 +08:00
de6d32a9d5 Merge pull request #324 from chain710/dev-1.14
fix #322: add option general.find-process-mode, user can turn off findProcess feature in router
2023-01-06 11:57:15 +08:00
5616d9dc40 fix #322: add option general.find-process-mode, user can turn off findProcess feature in router
findProcess slow down connection due to repeat call to FindProcessName in router environment
this option has 3 values: always, strict, off
- always, equal to enable-process: true. Just try to merge all process related option into one
- strict, as default value, behavior remains unchanged
- off, turn off findProcess, useful in router environment
2023-01-05 17:01:44 +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
184 changed files with 1659 additions and 9227 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@ -11,11 +11,10 @@ import (
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
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"
shadowsocks "github.com/metacubex/sing-shadowsocks" "github.com/metacubex/sing-shadowsocks"
"github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/metacubex/sing-shadowsocks/shadowimpl"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -28,10 +27,9 @@ type ShadowSocks struct {
option *ShadowSocksOption option *ShadowSocksOption
// obfs // obfs
obfsMode string obfsMode string
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
shadowTLSOption *shadowtls.ShadowTLSOption
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
@ -63,32 +61,8 @@ type v2rayObfsOption struct {
Mux bool `obfs:"mux,omitempty"` Mux bool `obfs:"mux,omitempty"`
} }
type shadowTLSOption struct {
Password string `obfs:"password"`
Host string `obfs:"host"`
Fingerprint string `obfs:"fingerprint,omitempty"`
ClientFingerprint string `obfs:"client-fingerprint,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)
@ -103,9 +77,9 @@ func (ss *ShadowSocks) streamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e
} }
} }
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 +99,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 +157,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var v2rayOption *v2rayObfs.Option var v2rayOption *v2rayObfs.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@ -227,23 +192,6 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
v2rayOption.TLS = true v2rayOption.TLS = true
v2rayOption.SkipCertVerify = opts.SkipCertVerify v2rayOption.SkipCertVerify = opts.SkipCertVerify
} }
} else if option.Plugin == shadowtls.Mode {
obfsMode = shadowtls.Mode
opt := &shadowTLSOption{
Version: 2,
}
if err := decoder.Decode(option.PluginOpts, opt); err != nil {
return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err)
}
shadowTLSOpt = &shadowtls.ShadowTLSOption{
Password: opt.Password,
Host: opt.Host,
Fingerprint: opt.Fingerprint,
ClientFingerprint: opt.ClientFingerprint,
SkipCertVerify: opt.SkipCertVerify,
Version: opt.Version,
}
} }
return &ShadowSocks{ return &ShadowSocks{
@ -252,18 +200,16 @@ 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),
}, },
method: method, method: method,
option: &option, option: &option,
obfsMode: obfsMode, obfsMode: obfsMode,
v2rayOption: v2rayOption, v2rayOption: v2rayOption,
obfsOption: obfsOption, obfsOption: obfsOption,
shadowTLSOption: shadowTLSOpt,
}, 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

@ -4,13 +4,12 @@ import (
"context" "context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"net" "net"
"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"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/gun"
"github.com/Dreamacro/clash/transport/trojan" "github.com/Dreamacro/clash/transport/trojan"
@ -30,21 +29,20 @@ type Trojan struct {
type TrojanOption struct { type TrojanOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
Password string `proxy:"password"` Password string `proxy:"password"`
ALPN []string `proxy:"alpn,omitempty"` ALPN []string `proxy:"alpn,omitempty"`
SNI string `proxy:"sni,omitempty"` SNI string `proxy:"sni,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
Flow string `proxy:"flow,omitempty"` Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"` FlowShow bool `proxy:"flow-show,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) { func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
@ -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
@ -218,25 +211,21 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{ tOption := &trojan.Option{
Password: option.Password, Password: option.Password,
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
FlowShow: option.FlowShow, FlowShow: option.FlowShow,
Fingerprint: option.Fingerprint, Fingerprint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint,
} }
switch option.Network { if option.Network != "ws" && len(option.Flow) >= 16 {
case "", "tcp": option.Flow = option.Flow[:16]
if len(option.Flow) >= 16 { switch option.Flow {
option.Flow = option.Flow[:16] case vless.XRO, vless.XRD, vless.XRS:
switch option.Flow { tOption.Flow = option.Flow
case vless.XRO, vless.XRD, vless.XRS: default:
tOption.Flow = option.Flow return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
default:
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
}
} }
} }
@ -250,7 +239,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 +265,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 +273,11 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint) if t.option.Flow != "" {
t.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
} else {
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

@ -21,10 +21,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 (
@ -45,37 +41,28 @@ type Vless struct {
type VlessOption struct { type VlessOption struct {
BasicOption BasicOption
Name string `proxy:"name"` Name string `proxy:"name"`
Server string `proxy:"server"` Server string `proxy:"server"`
Port int `proxy:"port"` Port int `proxy:"port"`
UUID string `proxy:"uuid"` UUID string `proxy:"uuid"`
Flow string `proxy:"flow,omitempty"` Flow string `proxy:"flow,omitempty"`
FlowShow bool `proxy:"flow-show,omitempty"` FlowShow bool `proxy:"flow-show,omitempty"`
TLS bool `proxy:"tls,omitempty"` TLS bool `proxy:"tls,omitempty"`
UDP bool `proxy:"udp,omitempty"` UDP bool `proxy:"udp,omitempty"`
PacketAddr bool `proxy:"packet-addr,omitempty"` Network string `proxy:"network,omitempty"`
XUDP bool `proxy:"xudp,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
Network string `proxy:"network,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` ServerName string `proxy:"servername,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,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 +73,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 +91,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 != "" {
@ -154,7 +137,11 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c, err = vmess.StreamH2Conn(c, h2Opts) c, err = vmess.StreamH2Conn(c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig) if v.isXTLSEnabled() {
c, err = gun.StreamGunWithXTLSConn(c, v.gunTLSConfig, v.gunConfig)
} else {
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig)
}
default: default:
// default tcp network // default tcp network
// handle TLS And XTLS // handle TLS And XTLS
@ -165,17 +152,21 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
return nil, err return nil, err
} }
return v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) return v.client.StreamConn(c, parseVlessAddr(metadata))
} }
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() {
xtlsOpts := vless.XTLSConfig{ xtlsOpts := vless.XTLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
Fingerprint: v.option.Fingerprint, FingerPrint: v.option.Fingerprint,
}
if isH2 {
xtlsOpts.NextProtos = []string{"h2"}
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -186,10 +177,9 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
} else if v.option.TLS { } else if v.option.TLS {
tlsOpts := vmess.TLSConfig{ tlsOpts := vmess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
FingerPrint: v.option.Fingerprint, FingerPrint: v.option.Fingerprint,
ClientFingerprint: v.option.ClientFingerprint,
} }
if isH2 { if isH2 {
@ -206,8 +196,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
@ -222,7 +212,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -244,15 +234,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 {
@ -272,15 +259,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
if v.option.PacketAddr { c, err = v.client.StreamConn(c, parseVlessAddr(metadata))
packetAddrMetadata := *metadata // make a copy
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
packetAddrMetadata.DstPort = "443"
c, err = v.client.StreamConn(c, parseVlessAddr(&packetAddrMetadata, false))
} else {
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
}
if err != nil { if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err) return nil, fmt.Errorf("new vless client error: %v", err)
@ -293,7 +272,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 {
@ -310,15 +289,7 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
if v.option.PacketAddr { c, err = v.StreamConn(c, metadata)
packetAddrMetadata := *metadata // make a copy
packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress
packetAddrMetadata.DstPort = "443"
c, err = v.StreamConn(c, &packetAddrMetadata)
} else {
c, err = v.StreamConn(c, metadata)
}
if err != nil { if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err) return nil, fmt.Errorf("new vless client error: %v", err)
@ -334,17 +305,6 @@ func (v *Vless) SupportWithDialer() bool {
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if v.option.XUDP {
return newPacketConn(&threadSafePacketConn{
PacketConn: vmessSing.NewXUDPConn(c, M.ParseSocksaddr(metadata.RemoteAddress())),
}, v), nil
} else if v.option.PacketAddr {
return newPacketConn(&threadSafePacketConn{
PacketConn: packetaddr.NewConn(&vlessPacketConn{
Conn: c, rAddr: metadata.UDPAddr(),
}, M.ParseSocksaddr(metadata.RemoteAddress())),
}, v), nil
}
return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil
} }
@ -353,7 +313,7 @@ func (v *Vless) SupportUOT() bool {
return true return true
} }
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr { func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr {
var addrType byte var addrType byte
var addr []byte var addr []byte
switch metadata.AddrType() { switch metadata.AddrType() {
@ -377,8 +337,7 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
UDP: metadata.NetWork == C.UDP, UDP: metadata.NetWork == C.UDP,
AddrType: addrType, AddrType: addrType,
Addr: addr, Addr: addr,
Port: uint16(port), Port: uint(port),
Mux: metadata.NetWork == C.UDP && xudp,
} }
} }
@ -479,7 +438,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 +447,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,10 +458,7 @@ 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,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
client: client, client: client,
@ -535,11 +481,10 @@ func NewVless(option VlessOption) (*Vless, error) {
} }
gunConfig := &gun.Config{ gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName, ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName, Host: v.option.ServerName,
ClientFingerprint: v.option.ClientFingerprint,
} }
tlsConfig := tlsC.GetGlobalTLSConfig(&tls.Config{ tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName, ServerName: v.option.ServerName,
}) })
@ -552,9 +497,11 @@ func NewVless(option VlessOption) (*Vless, error) {
v.gunTLSConfig = tlsConfig v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig v.gunConfig = gunConfig
if v.isXTLSEnabled() {
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint) v.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig)
} else {
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
}
} }
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,8 +114,9 @@ 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 {
var err error
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
} }
@ -140,9 +134,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if v.option.TLS { if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -167,10 +160,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
case "h2": case "h2":
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := clashVMess.TLSConfig{ tlsOpts := clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
NextProtos: []string{"h2"}, NextProtos: []string{"h2"},
ClientFingerprint: v.option.ClientFingerprint,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -195,9 +187,8 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if v.option.TLS { if v.option.TLS {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsOpts := &clashVMess.TLSConfig{ tlsOpts := &clashVMess.TLSConfig{
Host: host, Host: host,
SkipCertVerify: v.option.SkipCertVerify, SkipCertVerify: v.option.SkipCertVerify,
ClientFingerprint: v.option.ClientFingerprint,
} }
if v.option.ServerName != "" { if v.option.ServerName != "" {
@ -213,12 +204,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 +252,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 +280,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 {
@ -299,12 +290,22 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
} }
return v.ListenPacketOnStreamConn(c, metadata) return v.ListenPacketOnStreamConn(c, metadata)
} }
c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.StreamConn(c, metadata)
return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata)
} }
// 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 {
@ -314,18 +315,10 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
} }
c, err := dialer.DialContext(ctx, "tcp", v.addr) c, err := dialer.DialContext(ctx, "tcp", v.addr)
if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
}
tcpKeepAlive(c)
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = v.StreamConn(c, metadata)
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
return v.ListenPacketOnStreamConn(c, metadata) return v.ListenPacketOnStreamConn(c, metadata)
} }
@ -376,7 +369,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 +379,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),
@ -412,9 +403,8 @@ func NewVmess(option VmessOption) (*Vmess, error) {
} }
gunConfig := &gun.Config{ gunConfig := &gun.Config{
ServiceName: v.option.GrpcOpts.GrpcServiceName, ServiceName: v.option.GrpcOpts.GrpcServiceName,
Host: v.option.ServerName, Host: v.option.ServerName,
ClientFingerprint: v.option.ClientFingerprint,
} }
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
@ -429,9 +419,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

@ -4,13 +4,11 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"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"
"time"
) )
type Fallback struct { type Fallback struct {
@ -31,21 +29,11 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...) c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
if err == nil { if err == nil {
c.AppendToChains(f) c.AppendToChains(f)
f.onDialSuccess()
} else { } else {
f.onDialFailed(proxy.Type(), err) f.onDialFailed(proxy.Type(), err)
} }
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
f.onDialSuccess()
} else {
f.onDialFailed(proxy.Type(), err)
}
},
}
return c, err return c, err
} }
@ -144,7 +132,6 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider)
}, },
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType,
providers, providers,
}), }),
disableUDP: option.DisableUDP, disableUDP: option.DisableUDP,

View File

@ -3,26 +3,23 @@ package outboundgroup
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"sync"
"time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/constant/provider"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel"
"github.com/dlclark/regexp2" "github.com/dlclark/regexp2"
"go.uber.org/atomic" "go.uber.org/atomic"
"strings"
"sync"
"time"
) )
type GroupBase struct { type GroupBase struct {
*outbound.Base *outbound.Base
filterRegs []*regexp2.Regexp filterRegs []*regexp2.Regexp
excludeFilterReg *regexp2.Regexp excludeFilterReg *regexp2.Regexp
excludeTypeArray []string
providers []provider.ProxyProvider providers []provider.ProxyProvider
failedTestMux sync.Mutex failedTestMux sync.Mutex
failedTimes int failedTimes int
@ -36,7 +33,6 @@ type GroupBaseOption struct {
outbound.BaseOption outbound.BaseOption
filter string filter string
excludeFilter string excludeFilter string
excludeType string
providers []provider.ProxyProvider providers []provider.ProxyProvider
} }
@ -45,10 +41,6 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
if opt.excludeFilter != "" { if opt.excludeFilter != "" {
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0) excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
} }
var excludeTypeArray []string
if opt.excludeType != "" {
excludeTypeArray = strings.Split(opt.excludeType, "|")
}
var filterRegs []*regexp2.Regexp var filterRegs []*regexp2.Regexp
if opt.filter != "" { if opt.filter != "" {
@ -62,7 +54,6 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
Base: outbound.NewBase(opt.BaseOption), Base: outbound.NewBase(opt.BaseOption),
filterRegs: filterRegs, filterRegs: filterRegs,
excludeFilterReg: excludeFilterReg, excludeFilterReg: excludeFilterReg,
excludeTypeArray: excludeTypeArray,
providers: opt.providers, providers: opt.providers,
failedTesting: atomic.NewBool(false), failedTesting: atomic.NewBool(false),
} }
@ -157,25 +148,6 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
} }
proxies = newProxies proxies = newProxies
} }
if gb.excludeTypeArray != nil {
var newProxies []C.Proxy
for _, p := range proxies {
mType := p.Type().String()
flag := false
for i := range gb.excludeTypeArray {
if strings.EqualFold(mType, gb.excludeTypeArray[i]) {
flag = true
break
}
}
if flag {
continue
}
newProxies = append(newProxies, p)
}
proxies = newProxies
}
if gb.excludeFilterReg != nil { if gb.excludeFilterReg != nil {
var newProxies []C.Proxy var newProxies []C.Proxy

View File

@ -5,12 +5,11 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/common/cache"
"net" "net"
"time" "time"
"github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/adapter/outbound"
"github.com/Dreamacro/clash/common/cache"
"github.com/Dreamacro/clash/common/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)
defer func() {
if err == nil {
c.AppendToChains(lb)
lb.onDialSuccess()
} else {
lb.onDialFailed(proxy.Type(), err)
}
}()
c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...) c, err = proxy.DialContext(ctx, metadata, lb.Base.DialOptions(opts...)...)
if err == nil {
c.AppendToChains(lb)
} else {
lb.onDialFailed(proxy.Type(), err)
}
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
lb.onDialSuccess()
} else {
lb.onDialFailed(proxy.Type(), err)
}
},
}
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
@ -246,7 +229,6 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide
}, },
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType,
providers, providers,
}), }),
strategyFn: strategyFn, strategyFn: strategyFn,

View File

@ -31,7 +31,6 @@ type GroupCommonOption struct {
DisableUDP bool `group:"disable-udp,omitempty"` DisableUDP bool `group:"disable-udp,omitempty"`
Filter string `group:"filter,omitempty"` Filter string `group:"filter,omitempty"`
ExcludeFilter string `group:"exclude-filter,omitempty"` ExcludeFilter string `group:"exclude-filter,omitempty"`
ExcludeType string `group:"exclude-type,omitempty"`
} }
func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) {
@ -78,7 +77,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

@ -191,7 +191,6 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re
}, },
"", "",
"", "",
"",
providers, providers,
}), }),
} }

View File

@ -100,7 +100,6 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider)
}, },
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType,
providers, providers,
}), }),
selected: "COMPATIBLE", selected: "COMPATIBLE",

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)
u.onDialSuccess()
} else { } else {
u.onDialFailed(proxy.Type(), err) u.onDialFailed(proxy.Type(), err)
} }
c = &callback.FirstWriteCallBackConn{
Conn: c,
Callback: func(err error) {
if err == nil {
u.onDialSuccess()
} else {
u.onDialFailed(proxy.Type(), err)
}
},
}
return c, err return c, err
} }
@ -155,7 +144,6 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o
option.Filter, option.Filter,
option.ExcludeFilter, option.ExcludeFilter,
option.ExcludeType,
providers, providers,
}), }),
fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),

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

@ -3,10 +3,10 @@ package provider
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/Dreamacro/clash/component/resource"
"time" "time"
"github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/common/structure"
"github.com/Dreamacro/clash/component/resource"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
) )
@ -27,7 +27,6 @@ type proxyProviderSchema struct {
Interval int `provider:"interval,omitempty"` Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"` Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
} }
@ -64,7 +63,5 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
interval := time.Duration(uint(schema.Interval)) * time.Second interval := time.Duration(uint(schema.Interval)) * time.Second
filter := schema.Filter filter := schema.Filter
excludeFilter := schema.ExcludeFilter excludeFilter := schema.ExcludeFilter
excludeType := schema.ExcludeType return NewProxySetProvider(name, interval, filter, excludeFilter, vehicle, hc)
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, vehicle, hc)
} }

View File

@ -5,6 +5,8 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/dlclark/regexp2"
"gopkg.in/yaml.v3"
"net/http" "net/http"
"runtime" "runtime"
"strings" "strings"
@ -17,9 +19,6 @@ import (
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
types "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"github.com/dlclark/regexp2"
"gopkg.in/yaml.v3"
) )
const ( const (
@ -142,16 +141,11 @@ func stopProxyProvider(pd *ProxySetProvider) {
_ = pd.Fetcher.Destroy() _ = pd.Fetcher.Destroy()
} }
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
} }
var excludeTypeArray []string
if excludeType != "" {
excludeTypeArray = strings.Split(excludeType, "|")
}
var filterRegs []*regexp2.Regexp var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") { for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, 0) filterReg, err := regexp2.Compile(filter, 0)
@ -170,7 +164,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
healthCheck: hc, healthCheck: hc,
} }
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, filterRegs, excludeFilterReg), proxiesOnUpdate(pd))
pd.Fetcher = fetcher pd.Fetcher = fetcher
pd.getSubscriptionInfo() pd.getSubscriptionInfo()
@ -268,7 +262,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
} }
} }
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { func proxiesParseAndFilter(filter string, excludeFilter string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] {
return func(buf []byte) ([]C.Proxy, error) { return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{} schema := &ProxySchema{}
@ -288,28 +282,6 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
proxiesSet := map[string]struct{}{} proxiesSet := map[string]struct{}{}
for _, filterReg := range filterRegs { for _, filterReg := range filterRegs {
for idx, mapping := range schema.Proxies { for idx, mapping := range schema.Proxies {
if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
mType, ok := mapping["type"]
if !ok {
continue
}
pType, ok := mType.(string)
if !ok {
continue
}
flag := false
for i := range excludeTypeArray {
if strings.EqualFold(pType, excludeTypeArray[i]) {
flag = true
break
}
}
if flag {
continue
}
}
mName, ok := mapping["name"] mName, ok := mapping["name"]
if !ok { if !ok {
continue continue

View File

@ -1,20 +0,0 @@
package buf
import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
)
type Buffer = buf.Buffer
var StackNew = buf.StackNew
var StackNewSize = buf.StackNewSize
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,16 +138,20 @@ 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 != "" {
vmess["cipher"] = encryption vmess["cipher"] = encryption
} }
if packetEncoding := query.Get("packetEncoding"); packetEncoding != "" {
switch packetEncoding {
case "packet":
vmess["packet-addr"] = true
case "xudp":
vmess["xudp"] = true
}
}
proxies = append(proxies, vmess) proxies = append(proxies, vmess)
continue continue
} }
@ -170,11 +162,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
if jsonDc.Decode(&values) != nil { if jsonDc.Decode(&values) != nil {
continue continue
} }
tempName, ok := values["ps"].(string)
if !ok { name := uniqueName(names, values["ps"].(string))
continue
}
name := uniqueName(names, tempName)
vmess := make(map[string]any, 20) vmess := make(map[string]any, 20)
vmess["name"] = name vmess["name"] = name
@ -188,7 +177,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["alterId"] = 0 vmess["alterId"] = 0
} }
vmess["udp"] = true vmess["udp"] = true
vmess["xudp"] = true
vmess["tls"] = false vmess["tls"] = false
vmess["skip-cert-verify"] = false vmess["skip-cert-verify"] = false
@ -284,25 +272,19 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
} }
var ( var (
cipherRaw = urlSS.User.Username() cipher = urlSS.User.Username()
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(cipher)
if err != nil { if !strings.Contains(string(dcBuf), "2022-blake3") {
dcBuf, _ = enc.DecodeString(cipherRaw) dcBuf, _ = encRaw.DecodeString(cipher)
} }
cipher, password, found = strings.Cut(string(dcBuf), ":") cipher, password, found = strings.Cut(string(dcBuf), ":")
if !found { if !found {
continue continue
} }
err = VerifyMethod(cipher, password)
if err != nil {
dcBuf, _ = encRaw.DecodeString(cipherRaw)
cipher, password, found = strings.Cut(string(dcBuf), ":")
}
} }
ss := make(map[string]any, 10) ss := make(map[string]any, 10)

View File

@ -2,7 +2,6 @@ package convert
import ( import (
"encoding/base64" "encoding/base64"
"github.com/metacubex/sing-shadowsocks/shadowimpl"
"math/rand" "math/rand"
"net/http" "net/http"
"strings" "strings"
@ -315,8 +314,3 @@ func SetUserAgent(header http.Header) {
userAgent := RandUserAgent() userAgent := RandUserAgent()
header.Set("User-Agent", userAgent) header.Set("User-Agent", userAgent)
} }
func VerifyMethod(cipher, password string) (err error) {
_, err = shadowimpl.FetchMethod(cipher, password)
return
}

View File

@ -1,24 +1,15 @@
package convert package convert
import ( import (
"errors"
"fmt"
"net/url" "net/url"
"strconv"
"strings" "strings"
) )
func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy map[string]any) error { func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy map[string]any) {
// Xray VMessAEAD / VLESS share link standard // Xray VMessAEAD / VLESS share link standard
// https://github.com/XTLS/Xray-core/discussions/716 // https://github.com/XTLS/Xray-core/discussions/716
query := url.Query() query := url.Query()
proxy["name"] = uniqueName(names, url.Fragment) proxy["name"] = uniqueName(names, url.Fragment)
if url.Hostname() == "" {
return errors.New("url.Hostname() is empty")
}
if url.Port() == "" {
return errors.New("url.Port() is empty")
}
proxy["type"] = scheme proxy["type"] = scheme
proxy["server"] = url.Hostname() proxy["server"] = url.Hostname()
proxy["port"] = url.Port() proxy["port"] = url.Port()
@ -29,24 +20,11 @@ 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
} }
switch query.Get("packetEncoding") {
case "none":
case "packet":
proxy["packet-addr"] = true
default:
proxy["xudp"] = true
}
network := strings.ToLower(query.Get("type")) network := strings.ToLower(query.Get("type"))
if network == "" { if network == "" {
network = "tcp" network = "tcp"
@ -101,17 +79,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 +86,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,20 +22,11 @@ func (c *BufferedConn) Reader() *bufio.Reader {
return c.r return c.r
} }
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)
} }
@ -56,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,6 +6,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strings" "strings"
"sync" "sync"
@ -24,6 +25,17 @@ var (
ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel") ErrorDisableIPv6 = errors.New("IPv6 is disabled, dialer cancel")
) )
func ParseNetwork(network string, addr netip.Addr) string {
if runtime.GOOS == "windows" { // fix bindIfaceToListenConfig() in windows force bind to an ipv4 address
if !strings.HasSuffix(network, "4") &&
!strings.HasSuffix(network, "6") &&
addr.Unmap().Is6() {
network += "6"
}
}
return network
}
func applyOptions(options ...Option) *option { func applyOptions(options ...Option) *option {
opt := &option{ opt := &option{
interfaceName: DefaultInterface.Load(), interfaceName: DefaultInterface.Load(),
@ -65,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 != "" {
@ -118,40 +141,7 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
return nil, ErrorDisableIPv6 return nil, ErrorDisableIPv6
} }
address := net.JoinHostPort(destination.String(), port) return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
if opt.tfo {
return dialTFO(ctx, *dialer, network, address)
}
return dialer.DialContext(ctx, network, address)
}
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 {
err = fmt.Errorf("dns resolve failed:%w", err)
return nil, err
}
return dialContext(ctx, network, ip, port, opt)
} }
func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { func dualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
@ -200,7 +190,6 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
} }
} }
if result.error != nil { if result.error != nil {
result.error = fmt.Errorf("dns resolve failed:%w", result.error)
return return
} }
result.resolved = true result.resolved = true
@ -248,6 +237,26 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
return nil, err return nil, err
} }
func concurrentDualStackDialContext(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.LookupIPWithResolver(ctx, host, opt.resolver)
} else {
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
}
if err != nil {
return nil, err
}
return concurrentDialContext(ctx, network, ips, port, opt)
}
func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
returned := make(chan struct{}) returned := make(chan struct{})
defer close(returned) defer close(returned)
@ -359,51 +368,77 @@ func concurrentDialContext(ctx context.Context, network string, ips []netip.Addr
return nil, 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) { 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) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ips []netip.Addr var ips []netip.Addr
switch network { if opt.resolver == nil {
case "tcp4", "udp4": ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
if opt.resolver == nil { } else {
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host) ips, err = resolver.LookupIPv4WithResolver(ctx, host, opt.resolver)
} else {
ips, err = resolver.LookupIPv4WithResolver(ctx, host, opt.resolver)
}
default:
if opt.resolver == nil {
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
} else {
ips, err = resolver.LookupIPv6WithResolver(ctx, host, opt.resolver)
}
} }
if err != nil { if err != nil {
err = fmt.Errorf("dns resolve failed:%w", err)
return nil, err return nil, err
} }
return concurrentDialContext(ctx, network, ips, port, opt) return concurrentDialContext(ctx, network, ips, port, opt)
} }
func concurrentDualStackDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) { func concurrentIPv6DialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ips []netip.Addr var ips []netip.Addr
if opt.resolver != nil { if opt.resolver == nil {
ips, err = resolver.LookupIPWithResolver(ctx, host, opt.resolver) ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
} else { } else {
ips, err = resolver.LookupIPProxyServerHost(ctx, host) ips, err = resolver.LookupIPv6WithResolver(ctx, host, opt.resolver)
} }
if err != nil { if err != nil {
err = fmt.Errorf("dns resolve failed:%w", err)
return nil, err return nil, err
} }

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

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,6 @@ package geodata
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
"io" "io"
@ -10,8 +9,7 @@ import (
"os" "os"
) )
var initGeoSite bool var initFlag bool
var initGeoIP int
func InitGeoSite() error { func InitGeoSite() error {
if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) {
@ -20,9 +18,8 @@ func InitGeoSite() error {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
} }
log.Infoln("Download GeoSite.dat finish") log.Infoln("Download GeoSite.dat finish")
initGeoSite = false
} }
if !initGeoSite { if !initFlag {
if err := Verify(C.GeositeName); err != nil { if err := Verify(C.GeositeName); err != nil {
log.Warnln("GeoSite.dat invalid, remove and download: %s", err) log.Warnln("GeoSite.dat invalid, remove and download: %s", err)
if err := os.Remove(C.Path.GeoSite()); err != nil { if err := os.Remove(C.Path.GeoSite()); err != nil {
@ -32,7 +29,7 @@ func InitGeoSite() error {
return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) return fmt.Errorf("can't download GeoSite.dat: %s", err.Error())
} }
} }
initGeoSite = true initFlag = true
} }
return nil return nil
} }
@ -53,68 +50,3 @@ func downloadGeoSite(path string) (err error) {
return err return err
} }
func downloadGeoIP(path string) (err error) {
resp, err := http.Get(C.GeoIpUrl)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func InitGeoIP() error {
if C.GeodataMode {
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
log.Infoln("Can't find GeoIP.dat, start download")
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
}
log.Infoln("Download GeoIP.dat finish")
initGeoIP = 0
}
if initGeoIP != 1 {
if err := Verify(C.GeoipName); err != nil {
log.Warnln("GeoIP.dat invalid, remove and download: %s", err)
if err := os.Remove(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
}
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
}
}
initGeoIP = 1
}
return nil
}
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download")
if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
}
}
if initGeoIP != 2 {
if !mmdb.Verify() {
log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
}
if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
}
}
initGeoIP = 2
}
return nil
}

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

@ -2,9 +2,6 @@ package mmdb
import ( import (
"github.com/oschwald/geoip2-golang" "github.com/oschwald/geoip2-golang"
"io"
"net/http"
"os"
"sync" "sync"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -45,20 +42,3 @@ func Instance() *geoip2.Reader {
return mmdb return mmdb
} }
func DownloadMMDB(path string) (err error) {
resp, err := http.Get(C.MmdbUrl)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}

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

@ -1,57 +0,0 @@
package process
import (
"encoding/json"
"errors"
"strings"
)
const (
FindProcessAlways = "always"
FindProcessStrict = "strict"
FindProcessOff = "off"
)
var (
validModes = map[string]struct{}{
FindProcessAlways: {},
FindProcessOff: {},
FindProcessStrict: {},
}
)
type FindProcessMode string
func (m FindProcessMode) Always() bool {
return m == FindProcessAlways
}
func (m FindProcessMode) Off() bool {
return m == FindProcessOff
}
func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error {
var tp string
if err := unmarshal(&tp); err != nil {
return err
}
return m.Set(tp)
}
func (m *FindProcessMode) UnmarshalJSON(data []byte) error {
var tp string
if err := json.Unmarshal(data, &tp); err != nil {
return err
}
return m.Set(tp)
}
func (m *FindProcessMode) Set(value string) error {
mode := strings.ToLower(value)
_, exist := validModes[mode]
if !exist {
return errors.New("invalid find process mode")
}
*m = FindProcessMode(mode)
return nil
}

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"
@ -26,17 +27,26 @@ var (
var Dispatcher *SnifferDispatcher var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct { type SnifferDispatcher struct {
enable bool enable bool
sniffers map[sniffer.Sniffer]SnifferConfig
forceDomain *trie.DomainTrie[struct{}] sniffers []sniffer.Sniffer
skipSNI *trie.DomainTrie[struct{}]
skipList *cache.LruCache[string, uint8] forceDomain *trie.DomainTrie[struct{}]
rwMux sync.RWMutex skipSNI *trie.DomainTrie[struct{}]
portRanges *[]utils.Range[uint16]
skipList *cache.LruCache[string, uint8]
rwMux sync.RWMutex
forceDnsMapping bool forceDnsMapping bool
parsePureIp bool parsePureIp bool
} }
func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) { func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
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,14 +55,10 @@ func (sd *SnifferDispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata
} }
inWhitelist := false inWhitelist := false
overrideDest := false for _, portRange := range *sd.portRanges {
for sniffer, config := range sd.sniffers { if portRange.Contains(uint16(port)) {
if sniffer.SupportNetwork() == C.TCP || sniffer.SupportNetwork() == C.ALLNet { inWhitelist = true
inWhitelist = sniffer.SupportPort(uint16(port)) break
if inWhitelist {
overrideDest = config.OverrideDest
break
}
} }
} }
@ -69,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()
} }
originHost := metadata.Host
if originHost != host {
log.Infoln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
metadata.SrcIP, metadata.SrcPort,
dstIP, metadata.DstPort,
metadata.Host, host)
} else {
log.Debugln("[Sniffer] Sniff TCP [%s:%s]-->[%s:%s] success, replace domain [%s]-->[%s]",
metadata.SrcIP, metadata.SrcPort,
dstIP, metadata.DstPort,
metadata.Host, host)
}
metadata.Host = host
metadata.DNSMode = C.DNSNormal metadata.DNSMode = C.DNSNormal
log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]",
metadata.SourceDetail(),
metadata.RemoteAddress(),
metadata.Host, host)
} }
func (sd *SnifferDispatcher) Enable() bool { func (sd *SnifferDispatcher) Enable() bool {
@ -105,7 +121,7 @@ func (sd *SnifferDispatcher) Enable() bool {
} }
func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) { func (sd *SnifferDispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) {
for s := range sd.sniffers { for _, s := range sd.sniffers {
if s.SupportNetwork() == C.TCP { if s.SupportNetwork() == C.TCP {
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second)) _ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
_, err := conn.Peek(1) _, err := conn.Peek(1)
@ -166,37 +182,38 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil return &dispatcher, nil
} }
func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, 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,70 +6,75 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"strings"
"sync"
CN "github.com/Dreamacro/clash/common/net"
xtls "github.com/xtls/go" xtls "github.com/xtls/go"
"sync"
"time"
) )
var tlsCertificates = make([]tls.Certificate, 0) 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(privateKey, certificate string) error {
mutex.Lock()
defer mutex.Unlock()
if cert, err := CN.ParseCert(certificate, privateKey); err != nil {
return err
} else {
tlsCertificates = append(tlsCertificates, cert)
}
return nil
}
func GetCertificates() []tls.Certificate {
mutex.RLock()
defer mutex.RUnlock()
return tlsCertificates
}
func verifyFingerprint(fingerprint *[32]byte) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// ssl pining if insecureSkipVerify {
return nil
}
var preErr error
for i := range rawCerts { for i := range rawCerts {
rawCert := rawCerts[i] rawCert := rawCerts[i]
cert, err := x509.ParseCertificate(rawCert) cert, err := x509.ParseCertificate(rawCert)
if err == nil { if err == nil {
hash := sha256.Sum256(cert.Raw) opts := x509.VerifyOptions{
if bytes.Equal(fingerprint[:], hash[:]) { CurrentTime: time.Now(),
}
if _, err := cert.Verify(opts); err == nil {
return nil return nil
} else {
fingerprint := sha256.Sum256(cert.Raw)
for _, fp := range *fingerprints {
if bytes.Equal(fingerprint[:], fp[:]) {
return nil
}
}
preErr = err
} }
} }
} }
return errNotMacth
return preErr
} }
} }
func AddCertFingerprint(fingerprint string) error {
fpByte, err2 := convertFingerprint(fingerprint)
if err2 != nil {
return err2
}
mutex.Lock()
globalFingerprints = append(globalFingerprints, *fpByte)
mutex.Unlock()
return nil
}
func convertFingerprint(fingerprint string) (*[32]byte, error) { func convertFingerprint(fingerprint string) (*[32]byte, error) {
fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1))
fpByte, err := hex.DecodeString(fingerprint) fpByte, err := hex.DecodeString(fingerprint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(fpByte) != 32 { if len(fpByte) != 32 {
return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint") return nil, fmt.Errorf("fingerprint string length error,need sha25 fingerprint")
} }
return (*[32]byte)(fpByte), nil return (*[32]byte)(fpByte), nil
} }
func GetDefaultTLSConfig() *tls.Config { func GetDefaultTLSConfig() *tls.Config {
return GetGlobalTLSConfig(nil) return GetGlobalFingerprintTLCConfig(nil)
} }
// GetSpecifiedFingerprintTLSConfig specified fingerprint // GetSpecifiedFingerprintTLSConfig specified fingerprint
@ -77,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{
tlsConfig.InsecureSkipVerify = true InsecureSkipVerify: true,
return tlsConfig, nil VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
} }
} }
func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config { func GetGlobalFingerprintTLCConfig(tlsConfig *tls.Config) *tls.Config {
if tlsConfig == nil { if tlsConfig == nil {
return &tls.Config{ return &tls.Config{
Certificates: tlsCertificates, InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCertificates...)
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig return tlsConfig
} }
@ -99,37 +113,28 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin
if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil {
return nil, err return nil, err
} else { } else {
tlsConfig = GetGlobalXTLSConfig(tlsConfig) if tlsConfig == nil {
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) return &xtls.Config{
tlsConfig.InsecureSkipVerify = true InsecureSkipVerify: true,
return tlsConfig, nil VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false),
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
} }
} }
func GetGlobalXTLSConfig(tlsConfig *xtls.Config) *xtls.Config { func GetGlobalFingerprintXTLCConfig(tlsConfig *xtls.Config) *xtls.Config {
xtlsCerts := make([]xtls.Certificate, len(tlsCertificates))
for _, cert := range tlsCertificates {
tlsSsaList := make([]xtls.SignatureScheme, len(cert.SupportedSignatureAlgorithms))
for _, ssa := range cert.SupportedSignatureAlgorithms {
tlsSsa := xtls.SignatureScheme(ssa)
tlsSsaList = append(tlsSsaList, tlsSsa)
}
xtlsCert := xtls.Certificate{
Certificate: cert.Certificate,
PrivateKey: cert.PrivateKey,
OCSPStaple: cert.OCSPStaple,
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
Leaf: cert.Leaf,
SupportedSignatureAlgorithms: tlsSsaList,
}
xtlsCerts = append(xtlsCerts, xtlsCert)
}
if tlsConfig == nil { if tlsConfig == nil {
return &xtls.Config{ return &xtls.Config{
Certificates: xtlsCerts, InsecureSkipVerify: true,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.Certificates = xtlsCerts 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,10 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -25,9 +23,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"
@ -47,19 +42,18 @@ import (
type General struct { type General struct {
Inbound Inbound
Controller Controller
Mode T.TunnelMode `json:"mode"` Mode T.TunnelMode `json:"mode"`
UnifiedDelay bool UnifiedDelay bool
LogLevel log.LogLevel `json:"log-level"` LogLevel log.LogLevel `json:"log-level"`
IPv6 bool `json:"ipv6"` IPv6 bool `json:"ipv6"`
Interface string `json:"interface-name"` Interface string `json:"interface-name"`
RoutingMark int `json:"-"` RoutingMark int `json:"-"`
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"`
FindProcessMode P.FindProcessMode `json:"find-process-mode"` EnableProcess bool `json:"enable-process"`
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 +94,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
} }
@ -120,11 +114,6 @@ type Profile struct {
} }
type TLS struct { type TLS struct {
RawCert `yaml:",inline"`
CustomTrustCert []RawCert `yaml:"custom-certifactes"`
}
type RawCert struct {
Certificate string `yaml:"certificate"` Certificate string `yaml:"certificate"`
PrivateKey string `yaml:"private-key"` PrivateKey string `yaml:"private-key"`
} }
@ -138,10 +127,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
} }
@ -184,7 +174,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"`
} }
@ -236,33 +226,32 @@ type RawTuicServer struct {
} }
type RawConfig struct { type RawConfig struct {
Port int `yaml:"port"` Port int `yaml:"port"`
SocksPort int `yaml:"socks-port"` SocksPort int `yaml:"socks-port"`
RedirPort int `yaml:"redir-port"` RedirPort int `yaml:"redir-port"`
TProxyPort int `yaml:"tproxy-port"` TProxyPort int `yaml:"tproxy-port"`
MixedPort int `yaml:"mixed-port"` MixedPort int `yaml:"mixed-port"`
ShadowSocksConfig string `yaml:"ss-config"` ShadowSocksConfig string `yaml:"ss-config"`
VmessConfig string `yaml:"vmess-config"` VmessConfig string `yaml:"vmess-config"`
InboundTfo bool `yaml:"inbound-tfo"` InboundTfo bool `yaml:"inbound-tfo"`
Authentication []string `yaml:"authentication"` Authentication []string `yaml:"authentication"`
AllowLan bool `yaml:"allow-lan"` AllowLan bool `yaml:"allow-lan"`
BindAddress string `yaml:"bind-address"` BindAddress string `yaml:"bind-address"`
Mode T.TunnelMode `yaml:"mode"` Mode T.TunnelMode `yaml:"mode"`
UnifiedDelay bool `yaml:"unified-delay"` UnifiedDelay bool `yaml:"unified-delay"`
LogLevel log.LogLevel `yaml:"log-level"` LogLevel log.LogLevel `yaml:"log-level"`
IPv6 bool `yaml:"ipv6"` IPv6 bool `yaml:"ipv6"`
ExternalController string `yaml:"external-controller"` ExternalController string `yaml:"external-controller"`
ExternalControllerTLS string `yaml:"external-controller-tls"` ExternalControllerTLS string `yaml:"external-controller-tls"`
ExternalUI string `yaml:"external-ui"` ExternalUI string `yaml:"external-ui"`
Secret string `yaml:"secret"` Secret string `yaml:"secret"`
Interface string `yaml:"interface-name"` Interface string `yaml:"interface-name"`
RoutingMark int `yaml:"routing-mark"` RoutingMark int `yaml:"routing-mark"`
Tunnels []LC.Tunnel `yaml:"tunnels"` Tunnels []LC.Tunnel `yaml:"tunnels"`
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"`
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` EnableProcess bool `yaml:"enable-process" json:"enable-process"`
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"`
@ -291,20 +280,13 @@ 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
@ -332,21 +314,21 @@ func Parse(buf []byte) (*Config, error) {
func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
// config with default value // config with default value
rawCfg := &RawConfig{ rawCfg := &RawConfig{
AllowLan: false, AllowLan: false,
BindAddress: "*", BindAddress: "*",
IPv6: true, IPv6: true,
Mode: T.Rule, Mode: T.Rule,
GeodataMode: C.GeodataMode, GeodataMode: C.GeodataMode,
GeodataLoader: "memconservative", GeodataLoader: "memconservative",
UnifiedDelay: false, UnifiedDelay: false,
Authentication: []string{}, Authentication: []string{},
LogLevel: log.INFO, LogLevel: log.INFO,
Hosts: map[string]string{}, Hosts: map[string]string{},
Rule: []string{}, Rule: []string{},
Proxy: []map[string]any{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
TCPConcurrent: false, TCPConcurrent: false,
FindProcessMode: P.FindProcessStrict, EnableProcess: false,
Tun: RawTun{ Tun: RawTun{
Enable: false, Enable: false,
Device: "", Device: "",
@ -413,7 +395,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,
@ -447,6 +428,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
} }
config.General = general config.General = general
dialer.DefaultInterface.Store(config.General.Interface)
proxies, providers, err := parseProxies(rawCfg) proxies, providers, err := parseProxies(rawCfg)
if err != nil { if err != nil {
return nil, err return nil, err
@ -521,11 +503,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
} }
@ -559,18 +536,17 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
Secret: cfg.Secret, Secret: cfg.Secret,
ExternalControllerTLS: cfg.ExternalControllerTLS, ExternalControllerTLS: cfg.ExternalControllerTLS,
}, },
UnifiedDelay: cfg.UnifiedDelay, UnifiedDelay: cfg.UnifiedDelay,
Mode: cfg.Mode, Mode: cfg.Mode,
LogLevel: cfg.LogLevel, LogLevel: cfg.LogLevel,
IPv6: cfg.IPv6, IPv6: cfg.IPv6,
Interface: cfg.Interface, Interface: cfg.Interface,
RoutingMark: cfg.RoutingMark, RoutingMark: cfg.RoutingMark,
GeodataMode: cfg.GeodataMode, GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent, TCPConcurrent: cfg.TCPConcurrent,
FindProcessMode: cfg.FindProcessMode, EnableProcess: cfg.EnableProcess,
EBpf: cfg.EBpf, EBpf: cfg.EBpf,
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
}, nil }, nil
} }
@ -850,15 +826,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
return "", err }
}
host = host + ":" + defPort if port == "" {
if hostname, port, err = net.SplitHostPort(host); err != nil { port = defPort
return "", err
}
} }
return net.JoinHostPort(hostname, port), nil return net.JoinHostPort(hostname, port), nil
@ -868,7 +846,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())
@ -889,24 +870,29 @@ 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 = "" if _, _, err := net.SplitHostPort(host); err != nil && strings.Contains(err.Error(), "missing port in address") {
clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path} host = net.JoinHostPort(host, "443")
addr = clearURL.String() } else {
dnsNetType = "https" // DNS over HTTPS if err != nil {
if len(u.Fragment) != 0 { return nil, err
for _, s := range strings.Split(u.Fragment, "&") { }
arr := strings.Split(s, "=") }
if len(arr) == 0 { clearURL := url.URL{Scheme: "https", Host: host, Path: u.Path}
continue addr = clearURL.String()
} else if len(arr) == 1 { dnsNetType = "https" // DNS over HTTPS
proxyAdapter = arr[0] if len(u.Fragment) != 0 {
} else if len(arr) == 2 { for _, s := range strings.Split(u.Fragment, "&") {
params[arr[0]] = arr[1] arr := strings.Split(s, "=")
} else { if len(arr) == 0 {
params[arr[0]] = strings.Join(arr[1:], "=") continue
} } else if len(arr) == 1 {
proxyAdapter = arr[0]
} else if len(arr) == 2 {
params[arr[0]] = arr[1]
} else {
params[arr[0]] = strings.Join(arr[1:], "=")
} }
} }
} }
@ -939,53 +925,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
@ -1011,7 +962,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 {
@ -1230,60 +1180,55 @@ 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)
}
for _, snifferName := range snifferRaw.Sniffing {
find := false
for _, snifferType := range snifferTypes.List {
if snifferType.String() == strings.ToUpper(snifferName) {
find = true
loadSniffer[snifferType] = SNIFF.SnifferConfig{
Ports: globalPorts,
OverrideDest: snifferRaw.OverrideDest,
}
}
} }
if !find { start := uint16(p)
return nil, fmt.Errorf("not find the sniffer[%s]", snifferName) 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.Sniffers = loadSniffer sniffer.Ports = &ports
loadSniffer := make(map[snifferTypes.Type]struct{})
for _, snifferName := range snifferRaw.Sniffing {
find := false
for _, snifferType := range snifferTypes.List {
if snifferType.String() == strings.ToUpper(snifferName) {
find = true
loadSniffer[snifferType] = struct{}{}
}
}
if !find {
return nil, fmt.Errorf("not find the sniffer[%s]", snifferName)
}
}
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{}{})
@ -1304,28 +1249,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

@ -3,12 +3,92 @@ package config
import ( import (
"fmt" "fmt"
"github.com/Dreamacro/clash/component/geodata" "github.com/Dreamacro/clash/component/geodata"
"github.com/Dreamacro/clash/component/mmdb"
"io"
"net/http"
"os" "os"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/log"
) )
func downloadMMDB(path string) (err error) {
resp, err := http.Get(C.MmdbUrl)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func downloadGeoIP(path string) (err error) {
resp, err := http.Get(C.GeoIpUrl)
if err != nil {
return
}
defer resp.Body.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, resp.Body)
return err
}
func initGeoIP() error {
if C.GeodataMode {
if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) {
log.Infoln("Can't find GeoIP.dat, start download")
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
}
log.Infoln("Download GeoIP.dat finish")
}
if err := geodata.Verify(C.GeoipName); err != nil {
log.Warnln("GeoIP.dat invalid, remove and download: %s", err)
if err := os.Remove(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error())
}
if err := downloadGeoIP(C.Path.GeoIP()); err != nil {
return fmt.Errorf("can't download GeoIP.dat: %s", err.Error())
}
}
return nil
}
if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) {
log.Infoln("Can't find MMDB, start download")
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
}
}
if !mmdb.Verify() {
log.Warnln("MMDB invalid, remove and download")
if err := os.Remove(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't remove invalid MMDB: %s", err.Error())
}
if err := downloadMMDB(C.Path.MMDB()); err != nil {
return fmt.Errorf("can't download MMDB: %s", err.Error())
}
}
return nil
}
// Init prepare necessary files // Init prepare necessary files
func Init(dir string) error { func Init(dir string) error {
// initial homedir // initial homedir
@ -42,7 +122,7 @@ func Init(dir string) error {
C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite
C.MmdbUrl = rawCfg.GeoXUrl.Mmdb C.MmdbUrl = rawCfg.GeoXUrl.Mmdb
// initial GeoIP // initial GeoIP
if err := geodata.InitGeoIP(); err != nil { if err := initGeoIP(); err != nil {
return fmt.Errorf("can't initial GeoIP: %w", err) return fmt.Errorf("can't initial GeoIP: %w", err)
} }

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{

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