Merge branch 'Alpha' into Beta

This commit is contained in:
wwqgtxx 2023-02-19 01:25:01 +08:00
commit 0ba415866e
152 changed files with 7599 additions and 1227 deletions

26
.github/release.sh vendored Normal file
View file

@ -0,0 +1,26 @@
#!/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 Normal file
View file

@ -0,0 +1,26 @@
#!/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

View file

@ -1,22 +0,0 @@
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

325
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,325 @@
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 }}

View file

@ -1,61 +0,0 @@
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 }}

View file

@ -1,60 +0,0 @@
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: 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
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: ${{ github.ref_name }}
tag_name: Prerelease-${{ github.ref_name }}
files: bin/*
prerelease: true
generate_release_notes: true

View file

@ -1,36 +0,0 @@
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,4 +24,5 @@ vendor
# test suite # test suite
test/config/cache* test/config/cache*
/output /output
/.vscode .vscode/
.fleet/

View file

@ -1,18 +1,17 @@
FROM golang:alpine as builder FROM alpine:latest as builder
RUN apk add --no-cache make git && \ RUN apk add --no-cache gzip && \
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
COPY . /clash-src WORKDIR /clash
WORKDIR /clash-src COPY bin/ bin/
RUN go mod download &&\ RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
make docker &&\ FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && \
mv ./bin/clash.meta-docker /clash mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test
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"
@ -21,6 +20,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 COPY --from=builder /clash/clash /clash
RUN chmod +x /clash RUN chmod +x /clash
ENTRYPOINT [ "/clash" ] ENTRYPOINT [ "/clash" ]

View file

@ -47,14 +47,17 @@ 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=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOAMD64=v1 $(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=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
darwin-arm64: darwin-arm64:
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -66,7 +69,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=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
linux-arm64: linux-arm64:
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
@ -117,7 +120,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=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe GOARCH=amd64 GOOS=windows GOAMD64=v1 $(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
@ -154,4 +157,4 @@ CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
ebpf: export BPF_CLANG := $(CLANG) ebpf: export BPF_CLANG := $(CLANG)
ebpf: export BPF_CFLAGS := $(CFLAGS) ebpf: export BPF_CFLAGS := $(CFLAGS)
ebpf: ebpf:
cd component/ebpf/ && go generate ./... cd component/ebpf/ && go generate ./...

104
README.md
View file

@ -29,32 +29,42 @@
- 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
## Getting Started ## Wiki
Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki).
## Advanced usage for this branch Documentation and configuring examples are available on [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki) and [Clash.Meta Wiki](https://docs.metacubex.one/).
## 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
``` ```
So now you can build it: Now you can build it:
```shell ```shell
go build go build
``` ```
### DNS configuration If you need gvisor for tun stack, build with:
```shell
go build -tags with_gvisor
```
<!-- ## Advanced usage of this fork -->
<!-- ### DNS configuration
Support `geosite` with `fallback-filter`. Support `geosite` with `fallback-filter`.
@ -64,7 +74,6 @@ 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:
@ -73,6 +82,7 @@ proxy-groups:
interval: 180 interval: 180
lazy: true lazy: true
``` ```
```yaml ```yaml
dns: dns:
enable: true enable: true
@ -88,12 +98,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:
@ -110,28 +120,30 @@ Built-in [Wintun](https://www.wintun.net) driver.
# Enable the TUN listener # Enable the TUN listener
tun: tun:
enable: true enable: true
stack: gvisor # only gvisor stack: system # system/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
# multiport condition for rules SRC-PORT and DST-PORT # multiport condition for rules SRC-PORT and DST-PORT
- DST-PORT,123/136/137-139,DIRECT,udp - DST-PORT,123/136/137-139,DIRECT,udp
# rule GEOSITE # rule GEOSITE
- GEOSITE,category-ads-all,REJECT - GEOSITE,category-ads-all,REJECT
- GEOSITE,icloud@cn,DIRECT - GEOSITE,icloud@cn,DIRECT
@ -142,18 +154,17 @@ rules:
- GEOSITE,youtube,PROXY - GEOSITE,youtube,PROXY
- GEOSITE,geolocation-cn,DIRECT - GEOSITE,geolocation-cn,DIRECT
- GEOSITE,geolocation-!cn,PROXY - GEOSITE,geolocation-!cn,PROXY
# source IPCIDR condition for all rules in gateway proxy # source IPCIDR condition for all rules in gateway proxy
#- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32 #- GEOSITE,geolocation-!cn,REJECT,192.168.1.88/32,192.168.1.99/32
- GEOIP,telegram,PROXY,no-resolve - GEOIP,telegram,PROXY,no-resolve
- GEOIP,private,DIRECT,no-resolve - GEOIP,private,DIRECT,no-resolve
- GEOIP,cn,DIRECT - GEOIP,cn,DIRECT
- 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)
@ -162,18 +173,17 @@ 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:
@ -185,14 +195,12 @@ 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"
@ -203,7 +211,7 @@ proxies:
servername: example.com # AKA SNI servername: example.com # AKA SNI
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS # flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true # skip-cert-verify: true
- name: "vless-ws" - name: "vless-ws"
type: vless type: vless
server: server server: server
@ -228,12 +236,12 @@ proxies:
network: grpc network: grpc
servername: example.com # priority over wss host servername: example.com # priority over wss host
# skip-cert-verify: true # skip-cert-verify: true
grpc-opts: grpc-opts:
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"
@ -248,6 +256,7 @@ proxies:
``` ```
Support outbound transport protocol `Tuic` Support outbound transport protocol `Tuic`
```yaml ```yaml
proxies: proxies:
- name: "tuic" - name: "tuic"
@ -266,11 +275,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
@ -281,17 +290,15 @@ 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.
@ -317,10 +324,13 @@ 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
@ -331,23 +341,29 @@ $ 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 [Dashboard For Meta](https://github.com/MetaCubeX/clash-dashboard). To display process name in GUI please use [Razord-meta](https://github.com/MetaCubeX/Razord-meta).
![img.png](https://github.com/Clash-Mini/Dashboard/raw/master/View/Dashboard-Process.png) ### Dashboard
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,6 +92,7 @@ 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.String()); err == nil { if ip, port, err := parseAddr(source); err == nil {
metadata.SrcIP = ip metadata.SrcIP = ip
metadata.SrcPort = port metadata.SrcPort = port
} }
if ip, port, err := parseAddr(conn.LocalAddr().String()); err == nil { if ip, port, err := parseAddr(conn.LocalAddr()); 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().String()); err == nil { if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
metadata.SrcIP = ip metadata.SrcIP = ip
metadata.SrcPort = port metadata.SrcPort = port
} }
if ip, port, err := parseAddr(conn.LocalAddr().String()); err == nil { if ip, port, err := parseAddr(conn.LocalAddr()); 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/database64128/tfo-go/v2" "github.com/sagernet/tfo-go"
) )
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().String()); err == nil { if ip, port, err := parseAddr(packet.LocalAddr()); 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().String()); err == nil { if ip, port, err := parseAddr(p.InAddr()); err == nil {
metadata.InIP = ip metadata.InIP = ip
metadata.InPort = port metadata.InPort = port
} }

View file

@ -18,22 +18,13 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Ad
addition.Apply(metadata) addition.Apply(metadata)
} }
remoteAddr := conn.RemoteAddr() if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil {
metadata.SrcIP = ip
// Filter when net.Addr interface is nil metadata.SrcPort = port
if remoteAddr != nil {
if ip, port, err := parseAddr(remoteAddr.String()); err == nil {
metadata.SrcIP = ip
metadata.SrcPort = port
}
} }
localAddr := conn.LocalAddr() if ip, port, err := parseAddr(conn.LocalAddr()); err == nil {
// Filter when net.Addr interface is nil metadata.InIP = ip
if localAddr != nil { metadata.InPort = port
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)

View file

@ -1,13 +1,14 @@
package inbound package inbound
import ( import (
"github.com/Dreamacro/clash/common/nnip" "errors"
"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"
) )
@ -57,8 +58,19 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata return metadata
} }
func parseAddr(addr string) (netip.Addr, string, error) { func parseAddr(addr net.Addr) (netip.Addr, string, error) {
host, port, err := net.SplitHostPort(addr) // Filter when net.Addr interface is nil
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,12 +4,14 @@ 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 {
@ -18,6 +20,7 @@ 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
@ -87,6 +90,11 @@ 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
@ -146,6 +154,7 @@ 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
@ -158,6 +167,7 @@ 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,
@ -166,7 +176,7 @@ func NewBase(opt BaseOption) *Base {
} }
type conn struct { type conn struct {
net.Conn N.ExtendedConn
chain C.Chain chain C.Chain
actualRemoteDestination string actualRemoteDestination string
} }
@ -185,8 +195,12 @@ 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{c, []string{a.Name()}, parseRemoteDestination(a.Addr())} return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())}
} }
type packetConn struct { type packetConn struct {

View file

@ -7,7 +7,6 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
tlsC "github.com/Dreamacro/clash/component/tls"
"io" "io"
"net" "net"
"net/http" "net/http"
@ -15,6 +14,7 @@ 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.GetGlobalFingerprintTLCConfig(&tls.Config{ tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: option.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ServerName: sni, ServerName: sni,
}) })

View file

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

View file

@ -223,7 +223,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
} }
if len(shadowTLSOpt.Fingerprint) == 0 { if len(shadowTLSOpt.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else { } else {
if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil { if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil {
return nil, err return nil, err

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.GetGlobalFingerprintTLCConfig(tlsConfig) tlsConfig = tlsC.GetGlobalTLSConfig(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 {

View file

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
tlsC "github.com/Dreamacro/clash/component/tls" tlsC "github.com/Dreamacro/clash/component/tls"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -29,20 +30,21 @@ 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) {
@ -75,6 +77,11 @@ 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 {
@ -95,7 +102,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 c, err return N.NewExtendedConn(c), err
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
@ -211,12 +218,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{ tOption := &trojan.Option{
Password: option.Password, Password: option.Password,
ALPN: option.ALPN, ALPN: option.ALPN,
ServerName: option.Server, ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify, SkipCertVerify: option.SkipCertVerify,
FlowShow: option.FlowShow, FlowShow: option.FlowShow,
Fingerprint: option.Fingerprint, Fingerprint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint,
} }
switch option.Network { switch option.Network {
@ -268,7 +276,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
if len(option.Fingerprint) == 0 { if len(option.Fingerprint) == 0 {
tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) tlsConfig = tlsC.GetGlobalTLSConfig(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 {
@ -276,7 +284,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint)
t.gunTLSConfig = tlsConfig t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{ t.gunConfig = &gun.Config{

View file

@ -143,7 +143,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
return nil, err return nil, err
} }
} else { } else {
tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} }
if len(option.ALPN) > 0 { if len(option.ALPN) > 0 {
@ -165,7 +165,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
} }
if option.MaxUdpRelayPacketSize == 0 { if option.MaxUdpRelayPacketSize == 0 {
option.MaxUdpRelayPacketSize = 1500 option.MaxUdpRelayPacketSize = 1252
} }
if option.MaxOpenStreams == 0 { if option.MaxOpenStreams == 0 {
@ -216,9 +216,14 @@ func NewTuic(option TuicOption) (*Tuic, error) {
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
} }
// to avoid tuic's "too many open streams", decrease to 0.9x
clientMaxOpenStreams := int64(option.MaxOpenStreams) clientMaxOpenStreams := int64(option.MaxOpenStreams)
clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0))
// to avoid tuic's "too many open streams", decrease to 0.9x
if clientMaxOpenStreams == 100 {
clientMaxOpenStreams = clientMaxOpenStreams - int64(math.Ceil(float64(clientMaxOpenStreams)/10.0))
}
if clientMaxOpenStreams < 1 { if clientMaxOpenStreams < 1 {
clientMaxOpenStreams = 1 clientMaxOpenStreams = 1
} }

View file

@ -12,10 +12,6 @@ import (
"strconv" "strconv"
"sync" "sync"
vmessSing "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing-vmess/packetaddr"
M "github.com/sagernet/sing/common/metadata"
"github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/common/convert"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
@ -25,6 +21,10 @@ 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,31 +45,37 @@ 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"` PacketAddr bool `proxy:"packet-addr,omitempty"`
XUDP bool `proxy:"xudp,omitempty"` XUDP bool `proxy:"xudp,omitempty"`
PacketEncoding string `proxy:"packet-encoding,omitempty"` PacketEncoding string `proxy:"packet-encoding,omitempty"`
Network string `proxy:"network,omitempty"` Network string `proxy:"network,omitempty"`
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
WSOpts WSOptions `proxy:"ws-opts,omitempty"` WSOpts WSOptions `proxy:"ws-opts,omitempty"`
WSPath string `proxy:"ws-path,omitempty"` WSPath string `proxy:"ws-path,omitempty"`
WSHeaders map[string]string `proxy:"ws-headers,omitempty"` WSHeaders map[string]string `proxy:"ws-headers,omitempty"`
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
Fingerprint string `proxy:"fingerprint,omitempty"` Fingerprint string `proxy:"fingerprint,omitempty"`
ServerName string `proxy:"servername,omitempty"` ServerName string `proxy:"servername,omitempty"`
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error var err error
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
@ -80,6 +86,7 @@ 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{},
} }
@ -98,9 +105,12 @@ 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.GetGlobalFingerprintTLCConfig(tlsConfig) wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(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 != "" {
@ -176,9 +186,10 @@ 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 {
@ -233,12 +244,15 @@ 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 needs a net.UDPAddr // vless use stream-oriented udp with a special address, so we need 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 {
@ -279,7 +293,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 needs a net.UDPAddr // vless use stream-oriented udp with a special address, so we need 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 {
@ -474,6 +488,16 @@ 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
@ -485,6 +509,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,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@ -493,16 +518,6 @@ func NewVless(option VlessOption) (*Vless, error) {
option: &option, option: &option,
} }
switch option.PacketEncoding {
case "packetaddr", "packet":
option.PacketAddr = true
case "xudp":
option.XUDP = true
}
if option.XUDP {
option.PacketAddr = false
}
switch option.Network { switch option.Network {
case "h2": case "h2":
if len(option.HTTP2Opts.Host) == 0 { if len(option.HTTP2Opts.Host) == 0 {
@ -519,10 +534,11 @@ 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.GetGlobalFingerprintTLCConfig(&tls.Config{ tlsConfig := tlsC.GetGlobalTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName, ServerName: v.option.ServerName,
}) })
@ -535,7 +551,9 @@ func NewVless(option VlessOption) (*Vless, error) {
v.gunTLSConfig = tlsConfig v.gunTLSConfig = tlsConfig
v.gunConfig = gunConfig v.gunConfig = gunConfig
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig)
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
} }
return v, nil return v, nil

View file

@ -5,8 +5,6 @@ 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"
@ -15,10 +13,12 @@ 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,6 +59,7 @@ 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 {
@ -86,6 +87,11 @@ 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":
@ -96,6 +102,7 @@ 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{},
} }
@ -114,7 +121,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
} }
if len(v.option.Fingerprint) == 0 { if len(v.option.Fingerprint) == 0 {
wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig)
} else { } else {
if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil { if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil {
return nil, err return nil, err
@ -133,8 +140,9 @@ 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 != "" {
@ -159,9 +167,10 @@ 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 != "" {
@ -186,8 +195,9 @@ 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 != "" {
@ -251,7 +261,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 needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we need 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 {
@ -294,7 +304,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
// ListenPacketWithDialer implements C.ProxyAdapter // ListenPacketWithDialer implements C.ProxyAdapter
func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr // vmess use stream-oriented udp with a special address, so we need 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 {
@ -366,7 +376,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 {
return nil, fmt.Errorf("TLS must be true with h2/grpc network") option.TLS = true
} }
} }
@ -376,6 +386,7 @@ 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,
iface: option.Interface, iface: option.Interface,
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
@ -400,8 +411,9 @@ 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,
@ -416,7 +428,9 @@ 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

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

View file

@ -115,11 +115,20 @@ 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++ {
idx = (idx + 1) % length flag = !flag
if flag {
idx = (idx - 1) % length
} else {
idx = (idx + 2) % length
}
if idx < 0 {
idx = idx + length
}
proxy := proxies[idx] proxy := proxies[idx]
if proxy.Alive() { if proxy.Alive() {
return proxy return proxy

View file

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

View file

@ -2,6 +2,9 @@ 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"
@ -54,6 +57,11 @@ 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
@ -61,6 +69,11 @@ 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
@ -75,6 +88,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
proxy, err = outbound.NewSnell(*snellOption) proxy, err = outbound.NewSnell(*snellOption)
case "trojan": case "trojan":
trojanOption := &outbound.TrojanOption{} trojanOption := &outbound.TrojanOption{}
if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 {
trojanOption.ClientFingerprint = GlobalUtlsClient
}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break

View file

@ -301,6 +301,7 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
for i := range excludeTypeArray { for i := range excludeTypeArray {
if strings.EqualFold(pType, excludeTypeArray[i]) { if strings.EqualFold(pType, excludeTypeArray[i]) {
flag = true flag = true
break
} }
} }

19
common/buf/sing.go Normal file
View file

@ -0,0 +1,19 @@
package buf
import (
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
)
type Buffer = buf.Buffer
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

@ -2,8 +2,10 @@ 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"
@ -111,6 +113,12 @@ 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":
@ -120,7 +128,11 @@ 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)
handleVShareLink(names, urlVLess, scheme, vless) err = 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)
} }
@ -138,7 +150,11 @@ 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)
handleVShareLink(names, urlVMess, scheme, vmess) err = 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 != "" {
@ -272,16 +288,19 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
cipher string cipher string
password string password string
) )
cipher = cipherRaw
if password, found = urlSS.User.Password(); !found { if password, found = urlSS.User.Password(); !found {
dcBuf, _ := enc.DecodeString(cipherRaw) dcBuf, err := base64.RawURLEncoding.DecodeString(cipherRaw)
if err != nil {
dcBuf, _ = enc.DecodeString(cipherRaw)
}
cipher, password, found = strings.Cut(string(dcBuf), ":") cipher, password, found = strings.Cut(string(dcBuf), ":")
if !found { if !found {
continue continue
} }
err := VerifyMethod(cipher, password) err = VerifyMethod(cipher, password)
if err != nil { if err != nil {
dcBuf, _ := encRaw.DecodeString(cipherRaw) dcBuf, _ = encRaw.DecodeString(cipherRaw)
cipher, password, found = strings.Cut(string(dcBuf), ":") cipher, password, found = strings.Cut(string(dcBuf), ":")
} }
} }

View file

@ -1,15 +1,24 @@
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) { func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy map[string]any) error {
// 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()
@ -20,6 +29,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
@ -87,6 +101,17 @@ 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":
@ -94,4 +119,5 @@ 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,18 +3,22 @@ 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
net.Conn ExtendedConn
} }
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), c} return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c)}
} }
// Reader returns the internal bufio.Reader. // Reader returns the internal bufio.Reader.
@ -42,3 +46,22 @@ 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,6 +51,10 @@ 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
} //}

22
common/net/sing.go Normal file
View file

@ -0,0 +1,22 @@
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,11 +1,19 @@
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
@ -17,3 +25,28 @@ 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
}

131
common/net/websocket.go Normal file
View file

@ -0,0 +1,131 @@
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,6 +1,7 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
@ -10,16 +11,8 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
type controlFn = func(network, address string, c syscall.RawConn) error func bindControl(ifaceIdx int) controlFn {
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
@ -49,7 +42,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
return err return err
} }
dialer.Control = bindControl(ifaceObj.Index, dialer.Control) addControlToDialer(dialer, bindControl(ifaceObj.Index))
return nil return nil
} }
@ -59,6 +52,10 @@ func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address
return "", err return "", err
} }
lc.Control = bindControl(ifaceObj.Index, lc.Control) addControlToListenConfig(lc, bindControl(ifaceObj.Index))
return address, nil return address, nil
} }
func ParseNetwork(network string, addr netip.Addr) string {
return network
}

View file

@ -1,6 +1,7 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
@ -8,16 +9,8 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
type controlFn = func(network, address string, c syscall.RawConn) error func bindControl(ifaceName string) controlFn {
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
@ -37,13 +30,17 @@ func bindControl(ifaceName string, chain controlFn) controlFn {
} }
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error { func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
dialer.Control = bindControl(ifaceName, dialer.Control) addControlToDialer(dialer, bindControl(ifaceName))
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) {
lc.Control = bindControl(ifaceName, lc.Control) addControlToListenConfig(lc, bindControl(ifaceName))
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 //go:build !linux && !darwin && !windows
package dialer package dialer
@ -91,3 +91,13 @@ 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

@ -0,0 +1,81 @@
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)
switch network {
case "tcp6", "udp6":
innerErr = bind6(handle, ifaceIdx)
_ = bind4(handle, ifaceIdx)
default:
innerErr = bind4(handle, ifaceIdx)
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
_ = bind6(handle, ifaceIdx)
}
})
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

@ -0,0 +1,22 @@
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

@ -0,0 +1,22 @@
//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

@ -0,0 +1,26 @@
//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,7 +6,6 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strings" "strings"
"sync" "sync"
@ -25,17 +24,6 @@ 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(),

View file

@ -3,26 +3,22 @@
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) {
dialer.Control = bindMarkToControl(mark, dialer.Control) addControlToDialer(dialer, bindMarkToControl(mark))
} }
func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) { func bindMarkToListenConfig(mark int, lc *net.ListenConfig, _, _ string) {
lc.Control = bindMarkToControl(mark, lc.Control) addControlToListenConfig(lc, bindMarkToControl(mark))
} }
func bindMarkToControl(mark int, chain controlFn) controlFn { func bindMarkToControl(mark int) controlFn {
return func(network, address string, c syscall.RawConn) (err error) { return func(ctx context.Context, 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

@ -3,6 +3,7 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"syscall" "syscall"
@ -10,18 +11,10 @@ import (
) )
func addrReuseToListenConfig(lc *net.ListenConfig) { func addrReuseToListenConfig(lc *net.ListenConfig) {
chain := lc.Control addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
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,6 +1,7 @@
package dialer package dialer
import ( import (
"context"
"net" "net"
"syscall" "syscall"
@ -8,17 +9,9 @@ import (
) )
func addrReuseToListenConfig(lc *net.ListenConfig) { func addrReuseToListenConfig(lc *net.ListenConfig) {
chain := lc.Control addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
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

@ -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,5 +134,6 @@ 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,5 +134,6 @@ 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,5 +115,6 @@ 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,5 +115,6 @@ func _BpfClose(closers ...io.Closer) error {
} }
// Do not access this directly. // Do not access this directly.
//
//go:embed bpf_bpfel.o //go:embed bpf_bpfel.o
var _BpfBytes []byte var _BpfBytes []byte

View file

@ -13,7 +13,7 @@ import (
) )
const ( const (
UA = "Clash" UA = "clash.meta"
) )
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {

View file

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

View file

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

View file

@ -21,11 +21,7 @@ var (
once sync.Once once sync.Once
) )
func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, 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())
@ -35,7 +31,7 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, strin
}) })
if defaultSearcher == nil { if defaultSearcher == nil {
return nil, "", ErrPlatformNotSupport return 0, "", ErrPlatformNotSupport
} }
var spath string var spath string
@ -46,22 +42,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, strin
case UDP: case UDP:
spath = "net.inet.udp.pcblist" spath = "net.inet.udp.pcblist"
default: default:
return nil, "", ErrInvalidNetwork return 0, "", ErrInvalidNetwork
} }
value, err := syscall.Sysctl(spath) value, err := syscall.Sysctl(spath)
if err != nil { if err != nil {
return nil, "", err return 0, "", 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 nil, "", err return 0, "", err
} }
pp, err := getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return nil, pp, err return 0, 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) {
inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) uid, inode, err := resolveSocketByNetlink(network, ip, srcPort)
if err != nil { if err != nil {
return nil, "", err return 0, "", 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.INode, response.UID, nil return response.UID, response.INode, 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 nil, "", ErrPlatformNotSupport return 0, "", 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, strin
fn = getExUDPTable fn = getExUDPTable
class = udpTablePid class = udpTablePid
default: default:
return nil, "", ErrInvalidNetwork return 0, "", ErrInvalidNetwork
} }
buf, err := getTransportTable(fn, family, class) buf, err := getTransportTable(fn, family, class)
if err != nil { if err != nil {
return nil, "", err return 0, "", 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 nil, "", err return 0, "", err
} }
pp, err := getExecPathFromPID(pid) pp, err := getExecPathFromPID(pid)
return nil, pp, err return 0, pp, err
} }
type searcher struct { type searcher struct {

View file

@ -11,6 +11,8 @@ import (
"time" "time"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/miekg/dns"
) )
var ( var (
@ -44,6 +46,7 @@ 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.Infoln("[Provider] %s not updated for a long time, force refresh", f.Name()) log.Warnln("[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.Warnln("[Provider] %s pull error: %s", f.Name(), err.Error()) log.Errorln("[Provider] %s pull error: %s", f.Name(), err.Error())
continue continue
} }

View file

@ -0,0 +1,53 @@
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,7 +11,6 @@ import (
"github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/cache"
N "github.com/Dreamacro/clash/common/net" N "github.com/Dreamacro/clash/common/net"
"github.com/Dreamacro/clash/common/utils"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/constant/sniffer" "github.com/Dreamacro/clash/constant/sniffer"
@ -27,16 +26,12 @@ var (
var Dispatcher *SnifferDispatcher var Dispatcher *SnifferDispatcher
type SnifferDispatcher struct { type SnifferDispatcher struct {
enable bool enable bool
sniffers map[sniffer.Sniffer]SnifferConfig
sniffers []sniffer.Sniffer forceDomain *trie.DomainTrie[struct{}]
skipSNI *trie.DomainTrie[struct{}]
forceDomain *trie.DomainTrie[struct{}] skipList *cache.LruCache[string, uint8]
skipSNI *trie.DomainTrie[struct{}] rwMux sync.RWMutex
portRanges *[]utils.Range[uint16]
skipList *cache.LruCache[string, uint8]
rwMux sync.RWMutex
forceDnsMapping bool forceDnsMapping bool
parsePureIp bool parsePureIp bool
} }
@ -55,10 +50,14 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
} }
inWhitelist := false inWhitelist := false
for _, portRange := range *sd.portRanges { overrideDest := false
if portRange.Contains(uint16(port)) { for sniffer, config := range sd.sniffers {
inWhitelist = true if sniffer.SupportNetwork() == C.TCP || sniffer.SupportNetwork() == C.ALLNet {
break inWhitelist = sniffer.SupportPort(uint16(port))
if inWhitelist {
overrideDest = config.OverrideDest
break
}
} }
} }
@ -89,31 +88,21 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) {
sd.skipList.Delete(dst) sd.skipList.Delete(dst)
sd.rwMux.RUnlock() sd.rwMux.RUnlock()
sd.replaceDomain(metadata, host) sd.replaceDomain(metadata, host, overrideDest)
} }
} }
} }
func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) { func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
dstIP := "" metadata.SniffHost = host
if metadata.DstIP.IsValid() { if overrideDest {
dstIP = metadata.DstIP.String() metadata.Host = host
} }
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 {
@ -121,7 +110,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)
@ -182,38 +171,37 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) {
return &dispatcher, nil return &dispatcher, nil
} }
func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[struct{}], func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, forceDomain *trie.DomainTrie[struct{}],
skipSNI *trie.DomainTrie[struct{}], ports *[]utils.Range[uint16], skipSNI *trie.DomainTrie[struct{}],
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,
portRanges: ports, skipList: cache.New(cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)),
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 := range needSniffer { for snifferName, config := range snifferConfig {
s, err := NewSniffer(snifferName) s, err := NewSniffer(snifferName, config)
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) (sniffer.Sniffer, error) { func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer, error) {
switch name { switch name {
case sniffer.TLS: case sniffer.TLS:
return &TLSSniffer{}, nil return NewTLSSniffer(snifferConfig)
case sniffer.HTTP: case sniffer.HTTP:
return &HTTPSniffer{}, nil return NewHTTPSniffer(snifferConfig)
default: default:
return nil, ErrorUnsupportedSniffer return nil, ErrorUnsupportedSniffer
} }

View file

@ -7,7 +7,9 @@ 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 (
@ -24,10 +26,25 @@ 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,7 +5,9 @@ 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 (
@ -13,7 +15,22 @@ 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,75 +6,70 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
xtls "github.com/xtls/go" "strings"
"sync" "sync"
"time"
CN "github.com/Dreamacro/clash/common/net"
xtls "github.com/xtls/go"
) )
var globalFingerprints = make([][32]byte, 0, 0) var tlsCertificates = make([]tls.Certificate, 0)
var mutex sync.Mutex
func verifyPeerCertificateAndFingerprints(fingerprints *[][32]byte, insecureSkipVerify bool) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { var mutex sync.RWMutex
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 {
if insecureSkipVerify { // ssl pining
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 {
opts := x509.VerifyOptions{ hash := sha256.Sum256(cert.Raw)
CurrentTime: time.Now(), if bytes.Equal(fingerprint[:], hash[:]) {
}
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 sha25 fingerprint") return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint")
} }
return (*[32]byte)(fpByte), nil return (*[32]byte)(fpByte), nil
} }
func GetDefaultTLSConfig() *tls.Config { func GetDefaultTLSConfig() *tls.Config {
return GetGlobalFingerprintTLCConfig(nil) return GetGlobalTLSConfig(nil)
} }
// GetSpecifiedFingerprintTLSConfig specified fingerprint // GetSpecifiedFingerprintTLSConfig specified fingerprint
@ -82,29 +77,20 @@ 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 {
if tlsConfig == nil { tlsConfig = GetGlobalTLSConfig(tlsConfig)
return &tls.Config{ tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
InsecureSkipVerify: true, tlsConfig.InsecureSkipVerify = true
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), return tlsConfig, nil
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
} }
} }
func GetGlobalFingerprintTLCConfig(tlsConfig *tls.Config) *tls.Config { func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config {
if tlsConfig == nil { if tlsConfig == nil {
return &tls.Config{ return &tls.Config{
InsecureSkipVerify: true, Certificates: tlsCertificates,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCertificates...)
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig return tlsConfig
} }
@ -113,28 +99,37 @@ 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 {
if tlsConfig == nil { tlsConfig = GetGlobalXTLSConfig(tlsConfig)
return &xtls.Config{ tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
InsecureSkipVerify: true, tlsConfig.InsecureSkipVerify = true
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), return tlsConfig, nil
}, nil
} else {
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify)
tlsConfig.InsecureSkipVerify = true
return tlsConfig, nil
}
} }
} }
func GetGlobalFingerprintXTLCConfig(tlsConfig *xtls.Config) *xtls.Config { func GetGlobalXTLSConfig(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{
InsecureSkipVerify: true, Certificates: xtlsCerts,
VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false),
} }
} }
tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify) tlsConfig.Certificates = xtlsCerts
tlsConfig.InsecureSkipVerify = true
return tlsConfig return tlsConfig
} }

123
component/tls/utls.go Normal file
View file

@ -0,0 +1,123 @@
package tls
import (
"crypto/tls"
"net"
"github.com/Dreamacro/clash/log"
"github.com/mroth/weightedrand/v2"
utls "github.com/refraction-networking/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,11 +4,12 @@ import (
"container/list" "container/list"
"errors" "errors"
"fmt" "fmt"
P "github.com/Dreamacro/clash/component/process"
"net" "net"
"net/netip" "net/netip"
"net/url" "net/url"
"os" "os"
"reflect"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@ -24,6 +25,9 @@ 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"
@ -43,19 +47,19 @@ 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"`
EnableProcess bool `json:"enable-process"` FindProcessMode P.FindProcessMode `json:"find-process-mode"`
FindProcessMode P.FindProcessMode `json:"find-process-mode"` Sniffing bool `json:"sniffing"`
Sniffing bool `json:"sniffing"` EBpf EBpf `json:"-"`
EBpf EBpf `json:"-"` GlobalClientFingerprint string `json:"global-client-fingerprint"`
} }
// Inbound config // Inbound config
@ -96,7 +100,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
} }
@ -116,6 +120,11 @@ 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"`
} }
@ -129,11 +138,10 @@ type IPTables struct {
type Sniffer struct { type Sniffer struct {
Enable bool Enable bool
Sniffers []snifferTypes.Type Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig
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
} }
@ -176,7 +184,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]string `yaml:"nameserver-policy"` NameServerPolicy map[string]any `yaml:"nameserver-policy"`
ProxyServerNameserver []string `yaml:"proxy-server-nameserver"` ProxyServerNameserver []string `yaml:"proxy-server-nameserver"`
} }
@ -228,33 +236,33 @@ 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"`
EnableProcess bool `yaml:"enable-process" json:"enable-process"` FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
Sniffer RawSniffer `yaml:"sniffer"` Sniffer RawSniffer `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
@ -283,13 +291,20 @@ type RawGeoXUrl struct {
} }
type RawSniffer struct { type RawSniffer struct {
Enable bool `yaml:"enable" json:"enable"` Enable bool `yaml:"enable" json:"enable"`
Sniffing []string `yaml:"sniffing" json:"sniffing"` OverrideDest bool `yaml:"override-destination" json:"override-destination"`
ForceDomain []string `yaml:"force-domain" json:"force-domain"` Sniffing []string `yaml:"sniffing" json:"sniffing"`
SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` ForceDomain []string `yaml:"force-domain" json:"force-domain"`
Ports []string `yaml:"port-whitelist" json:"port-whitelist"` SkipDomain []string `yaml:"skip-domain" json:"skip-domain"`
ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"` Ports []string `yaml:"port-whitelist" json:"port-whitelist"`
ParsePureIp bool `yaml:"parse-pure-ip" json:"parse-pure-ip"` ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"`
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
@ -331,7 +346,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Proxy: []map[string]any{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
TCPConcurrent: false, TCPConcurrent: false,
EnableProcess: false,
FindProcessMode: P.FindProcessStrict, FindProcessMode: P.FindProcessStrict,
Tun: RawTun{ Tun: RawTun{
Enable: false, Enable: false,
@ -399,6 +413,7 @@ 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,
@ -507,6 +522,11 @@ 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
} }
@ -540,18 +560,18 @@ 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,
EnableProcess: cfg.EnableProcess, FindProcessMode: cfg.FindProcessMode,
FindProcessMode: cfg.FindProcessMode, EBpf: cfg.EBpf,
EBpf: cfg.EBpf, GlobalClientFingerprint: cfg.GlobalClientFingerprint,
}, nil }, nil
} }
@ -831,17 +851,15 @@ 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 {
return "", err if !strings.Contains(err.Error(), "missing port in address") {
} return "", err
}
if port == "" { host = host + ":" + defPort
port = defPort if hostname, port, err = net.SplitHostPort(host); err != nil {
return "", err
}
} }
return net.JoinHostPort(hostname, port), nil return net.JoinHostPort(hostname, port), nil
@ -851,10 +869,7 @@ 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 {
// parse without scheme .e.g 8.8.8.8:53 server = parsePureDNSServer(server)
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())
@ -875,29 +890,24 @@ 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":
host := u.Host addr, err = hostWithDefaultPort(u.Host, "443")
proxyAdapter = "" if err == nil {
if _, _, err := net.SplitHostPort(host); err != nil && strings.Contains(err.Error(), "missing port in address") { proxyAdapter = ""
host = net.JoinHostPort(host, "443") clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path}
} else { addr = clearURL.String()
if err != nil { dnsNetType = "https" // DNS over HTTPS
return nil, err if len(u.Fragment) != 0 {
} for _, s := range strings.Split(u.Fragment, "&") {
} arr := strings.Split(s, "=")
clearURL := url.URL{Scheme: "https", Host: host, Path: u.Path} if len(arr) == 0 {
addr = clearURL.String() continue
dnsNetType = "https" // DNS over HTTPS } else if len(arr) == 1 {
if len(u.Fragment) != 0 { proxyAdapter = arr[0]
for _, s := range strings.Split(u.Fragment, "&") { } else if len(arr) == 2 {
arr := strings.Split(s, "=") params[arr[0]] = arr[1]
if len(arr) == 0 { } else {
continue params[arr[0]] = strings.Join(arr[1:], "=")
} 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:], "=")
} }
} }
} }
@ -930,18 +940,53 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error)
return nameservers, nil return nameservers, nil
} }
func parseNameServerPolicy(nsPolicy map[string]string, preferH3 bool) (map[string]dns.NameServer, error) { func parsePureDNSServer(server string) string {
policy := map[string]dns.NameServer{} addPre := func(server string) string {
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 {
nameservers, err := parseNameServer([]string{server}, preferH3) var (
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[0] policy[domain] = nameservers
} }
return policy, nil return policy, nil
@ -967,6 +1012,7 @@ 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 {
@ -1185,55 +1231,60 @@ 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)
var ports []utils.Range[uint16] if len(snifferRaw.Sniff) != 0 {
if len(snifferRaw.Ports) == 0 { for sniffType, sniffConfig := range snifferRaw.Sniff {
ports = append(ports, *utils.NewRange[uint16](80, 80)) find := false
ports = append(ports, *utils.NewRange[uint16](443, 443)) ports, err := parsePortRange(sniffConfig.Ports)
} else {
for _, portRange := range snifferRaw.Ports {
portRaws := strings.Split(portRange, "-")
p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s format error", portRange) return nil, err
} }
overrideDest := snifferRaw.OverrideDest
start := uint16(p) if sniffConfig.OverrideDest != nil {
if len(portRaws) > 1 { overrideDest = *sniffConfig.OverrideDest
p, err = strconv.ParseUint(portRaws[1], 10, 16) }
if err != nil { for _, snifferType := range snifferTypes.List {
return nil, fmt.Errorf("%s format error", portRange) if snifferType.String() == strings.ToUpper(sniffType) {
find = true
loadSniffer[snifferType] = SNIFF.SnifferConfig{
Ports: ports,
OverrideDest: overrideDest,
}
} }
}
end := uint16(p) if !find {
ports = append(ports, *utils.NewRange(start, end)) return nil, fmt.Errorf("not find the sniffer[%s]", sniffType)
} else { }
ports = append(ports, *utils.NewRange(start, start)) }
} else {
// Deprecated: Use Sniff instead
log.Warnln("Deprecated: Use Sniff instead")
globalPorts, err := parsePortRange(snifferRaw.Ports)
if err != nil {
return nil, err
}
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 {
return nil, fmt.Errorf("not find the sniffer[%s]", snifferName)
} }
} }
} }
sniffer.Ports = &ports sniffer.Sniffers = loadSniffer
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{}{})
@ -1254,3 +1305,28 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) {
return sniffer, nil return sniffer, nil
} }
func parsePortRange(portRanges []string) ([]utils.Range[uint16], error) {
ports := make([]utils.Range[uint16], 0)
for _, portRange := range portRanges {
portRaws := strings.Split(portRange, "-")
p, err := strconv.ParseUint(portRaws[0], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
start := uint16(p)
if len(portRaws) > 1 {
p, err = strconv.ParseUint(portRaws[1], 10, 16)
if err != nil {
return nil, fmt.Errorf("%s format error", portRange)
}
end := uint16(p)
ports = append(ports, *utils.NewRange(start, end))
} else {
ports = append(ports, *utils.NewRange(start, start))
}
}
return ports, nil
}

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"sync"
"time" "time"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
@ -92,6 +93,7 @@ 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)
@ -226,3 +228,23 @@ 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

@ -124,4 +124,4 @@ const (
HTTPVersion2 HTTPVersion = "h2" HTTPVersion2 HTTPVersion = "h2"
// HTTPVersion3 is HTTP/3. // HTTPVersion3 is HTTP/3.
HTTPVersion3 HTTPVersion = "h3" HTTPVersion3 HTTPVersion = "h3"
) )

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) error Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter, natTable NatTable) error
Close() error Close() error
Address() string Address() string
RawAddress() string RawAddress() string

View file

@ -128,12 +128,14 @@ 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 {
@ -146,16 +148,17 @@ 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)
} }
if m.Process != "" && m.Uid != nil { switch {
return fmt.Sprintf("%s(%s, uid=%d)", m.SourceAddress(), m.Process, *m.Uid) case m.Process != "" && m.Uid != 0:
} else if m.Uid != nil { return fmt.Sprintf("%s(%s, uid=%d)", m.SourceAddress(), m.Process, m.Uid)
return fmt.Sprintf("%s(uid=%d)", m.SourceAddress(), *m.Uid) case m.Uid != 0:
} else if m.Process != "" { return fmt.Sprintf("%s(uid=%d)", m.SourceAddress(), m.Uid)
case m.Process != "":
return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process) return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process)
} else { default:
return fmt.Sprintf("%s", m.SourceAddress()) return fmt.Sprintf("%s", m.SourceAddress())
} }
} }
@ -175,6 +178,14 @@ 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,5 +102,6 @@ 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,6 +6,7 @@ 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

@ -4,13 +4,14 @@ 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"
@ -24,6 +25,26 @@ 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) {
@ -77,7 +98,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.GetGlobalFingerprintTLCConfig(c.Client.TLSConfig)) conn = tls.Client(conn, tlsC.GetGlobalTLSConfig(c.Client.TLSConfig))
} }
msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{ msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

26
docker/file-name.sh Normal file
View file

@ -0,0 +1,26 @@
#!/bin/sh
os="clash.meta-linux-"
arch=`uname -m`
case $arch in
"x86_64")
arch="amd64-compatible"
;;
"x86")
arch="386-cgo"
;;
"aarch64")
arch="arm64"
;;
"armv7l")
arch="armv7"
;;
"riscv64")
arch="riscv64-cgo"
;;
*)
echo "Unknown architecture"
exit 1
;;
esac
file_name="$os$arch"
echo $file_name

2387
docs/allocs.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 127 KiB

View file

@ -9,8 +9,25 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口
allow-lan: true # 允许局域网连接 allow-lan: true # 允许局域网连接
bind-address: "*" # 绑定IP地址仅作用于 allow-lan 为 true'*'表示所有地址 bind-address: "*" # 绑定IP地址仅作用于 allow-lan 为 true'*'表示所有地址
# find-process-mode has 3 values: always, strict, off
# - always, 开启,强制匹配所有进程
# - strict, 默认由clash判断是否开启
# - off, 不匹配进程,推荐在路由器上使用此模式
find-process-mode: strict
# global-client-fingerprint:全局TLS指纹,优先低于proxy内的 client-fingerprint
# accepts "chrome","firefox","safari","ios","random","none" options.
# Utls is currently support TLS transport in TCP/grpc/WS/HTTP for VLESS/Vmess and trojan.
global-client-fingerprint: chrome
mode: rule mode: rule
#自定义 geox-url
geox-url:
geoip: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat"
geosite: "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat"
mmdb: "https://cdn.jsdelivr.net/gh/Loyalsoldier/geoip@release/Country.mmdb"
log-level: debug # 日志等级 silent/error/warning/info/debug log-level: debug # 日志等级 silent/error/warning/info/debug
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录 ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
@ -26,11 +43,7 @@ external-ui: /path/to/ui/folder # 配置WEB UI目录使用http://{{external-c
# routing-mark: 6666 # 配置 fwmark 仅用于Linux # routing-mark: 6666 # 配置 fwmark 仅用于Linux
experimental: experimental:
# 具体配置待定
# 证书指纹,SHA256格式,补充校验TLS证书
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
fingerprints:
- "8F111FA9AD3CD8E917A118522CAC39EA33741B3BBE73F91CECE548D5CCB0E5E8" # 忽略大小写
# 类似于 /etc/hosts, 仅支持配置单个 IP # 类似于 /etc/hosts, 仅支持配置单个 IP
hosts: hosts:
# '*.clash.dev': 127.0.0.1 # '*.clash.dev': 127.0.0.1
@ -84,14 +97,40 @@ ebpf:
# 嗅探域名 可选配置 # 嗅探域名 可选配置
sniffer: sniffer:
enable: false enable: false
## 对 redir-host 类型识别的流量进行强制嗅探
## 如Tun、Redir 和 TProxy 并 DNS 为 redir-host 皆属于
# force-dns-mapping: false
## 对所有未获取到域名的流量进行强制嗅探
# parse-pure-ip: false
# 是否使用嗅探结果作为实际访问,默认 true
# 全局配置,优先级低于 sniffer.sniff 实际配置
override-destination: false
sniff:
# TLS 默认如果不配置 ports 默认嗅探 443
TLS:
# ports: [443, 8443]
# 默认嗅探 80
HTTP:
# 需要嗅探的端口
ports: [80, 8080-8880]
# 可覆盖 sniffer.override-destination
override-destination: true
force-domain:
- +.v2ex.com
## 对嗅探结果进行跳过
# skip-domain:
# - Mijia Cloud
# 需要嗅探协议 # 需要嗅探协议
# 已废弃,若 sniffer.sniff 配置则此项无效
sniffing: sniffing:
- tls - tls
- http - http
# 强制对此域名进行嗅探 # 强制对此域名进行嗅探
force-domain:
- +.v2ex.com
# 仅对白名单中的端口进行嗅探,默认为 44380 # 仅对白名单中的端口进行嗅探,默认为 44380
# 已废弃,若 sniffer.sniff 配置则此项无效
port-whitelist: port-whitelist:
- "80" - "80"
- "443" - "443"
@ -121,7 +160,7 @@ tunnels:
- tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy - tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy
- tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn - tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn
# full yaml config # full yaml config
- network: [ tcp, udp ] - network: [tcp, udp]
address: 127.0.0.1:7777 address: 127.0.0.1:7777
target: target.com target: target.com
proxy: proxy proxy: proxy
@ -201,10 +240,11 @@ dns:
# - '+.youtube.com' # - '+.youtube.com'
# 配置查询域名使用的 DNS 服务器 # 配置查询域名使用的 DNS 服务器
# nameserver-policy: nameserver-policy:
# 'www.baidu.com': '114.114.114.114' # 'www.baidu.com': '114.114.114.114'
# '+.internal.crop.com': '10.0.0.1' # '+.internal.crop.com': '10.0.0.1'
"geosite:cn": "https://doh.pub/dns-query"
"www.baidu.com": [https://doh.pub/dns-query,https://dns.alidns.com/dns-query]
proxies: proxies:
# Shadowsocks # Shadowsocks
# cipher支持: # cipher支持:
@ -219,15 +259,14 @@ proxies:
server: server server: server
port: 443 port: 443
cipher: chacha20-ietf-poly1305 cipher: chacha20-ietf-poly1305
password: password: "password"
"password" # udp: true
# udp: true # udp-over-tcp: false
# udp-over-tcp: false # ip-version: ipv4 # 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer。默认使用 dual
# ip-version: ipv4 # 设置节点使用 IP 版本可选dualipv4ipv6ipv4-preferipv6-prefer。默认使用 dual # ipv4仅使用 IPv4 ipv6仅使用 IPv6
# ipv4仅使用 IPv4 ipv6仅使用 IPv6 # ipv4-prefer优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接,
# ipv4-prefer优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, # UDP 则为双栈解析,获取结果中的第一个 IPv4
# UDP 则为双栈解析,获取结果中的第一个 IPv4 # ipv6-prefer 同 ipv4-prefer
# ipv6-prefer 同 ipv4-prefer
# 现有协议都支持此参数TCP 效果仅在开启 tcp-concurrent 生效 # 现有协议都支持此参数TCP 效果仅在开启 tcp-concurrent 生效
- name: "ss2" - name: "ss2"
type: ss type: ss
@ -250,6 +289,8 @@ proxies:
plugin-opts: plugin-opts:
mode: websocket # no QUIC now mode: websocket # no QUIC now
# tls: true # wss # tls: true # wss
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
# 配置指纹将实现 SSL Pining 效果
# fingerprint: xxxx # fingerprint: xxxx
# skip-cert-verify: true # skip-cert-verify: true
# host: bing.com # host: bing.com
@ -281,6 +322,7 @@ proxies:
# udp: true # udp: true
# tls: true # tls: true
# fingerprint: xxxx # fingerprint: xxxx
# client-fingerprint: chrome # Available: "chrome","firefox","safari","ios","random", currently only support TLS transport in TCP/GRPC/WS/HTTP for VLESS/Vmess and trojan.
# skip-cert-verify: true # skip-cert-verify: true
# servername: example.com # priority over wss host # servername: example.com # priority over wss host
# network: ws # network: ws
@ -386,6 +428,7 @@ proxies:
server: server server: server
port: 443 port: 443
password: yourpsk password: yourpsk
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
# fingerprint: xxxx # fingerprint: xxxx
# udp: true # udp: true
# sni: example.com # aka server name # sni: example.com # aka server name
@ -445,6 +488,7 @@ proxies:
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS # flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
# skip-cert-verify: true # skip-cert-verify: true
# fingerprint: xxxx # fingerprint: xxxx
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
- name: "vless-ws" - name: "vless-ws"
type: vless type: vless
@ -454,6 +498,7 @@ proxies:
udp: true udp: true
tls: true tls: true
network: ws network: ws
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
servername: example.com # priority over wss host servername: example.com # priority over wss host
# skip-cert-verify: true # skip-cert-verify: true
# fingerprint: xxxx # fingerprint: xxxx
@ -497,7 +542,7 @@ proxies:
private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU= private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU=
public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo=
udp: true udp: true
# reserved: 'U4An'
- name: tuic - name: tuic
server: www.example.com server: www.example.com
port: 10443 port: 10443
@ -514,6 +559,7 @@ 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
# max-open-streams: 20 # default 100, too many open streams may hurt performance
# ShadowsocksR # ShadowsocksR
# The supported ciphers (encryption methods): all stream ciphers in ss # The supported ciphers (encryption methods): all stream ciphers in ss
@ -555,7 +601,7 @@ proxy-groups:
- vmess1 - vmess1
# tolerance: 150 # tolerance: 150
# lazy: true # lazy: true
url: "http://www.gstatic.com/generate_204" url: "https://cp.cloudflare.com/generate_204"
interval: 300 interval: 300
# fallback 将按照 url 测试结果按照节点顺序选择 # fallback 将按照 url 测试结果按照节点顺序选择
@ -565,7 +611,7 @@ proxy-groups:
- ss1 - ss1
- ss2 - ss2
- vmess1 - vmess1
url: "http://www.gstatic.com/generate_204" url: "https://cp.cloudflare.com/generate_204"
interval: 300 interval: 300
# load-balance 将按照算法随机选择节点 # load-balance 将按照算法随机选择节点
@ -575,7 +621,7 @@ proxy-groups:
- ss1 - ss1
- ss2 - ss2
- vmess1 - vmess1
url: "http://www.gstatic.com/generate_204" url: "https://cp.cloudflare.com/generate_204"
interval: 300 interval: 300
# strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions # strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions
@ -617,14 +663,14 @@ proxy-providers:
enable: true enable: true
interval: 600 interval: 600
# lazy: true # lazy: true
url: http://www.gstatic.com/generate_204 url: https://cp.cloudflare.com/generate_204
test: test:
type: file type: file
path: /test.yaml path: /test.yaml
health-check: health-check:
enable: true enable: true
interval: 36000 interval: 36000
url: http://www.gstatic.com/generate_204 url: https://cp.cloudflare.com/generate_204
rule-providers: rule-providers:
rule1: rule1:
behavior: classical # domain ipcidr behavior: classical # domain ipcidr
@ -673,6 +719,11 @@ sub-rules:
tls: tls:
certificate: string # 证书 PEM 格式,或者 证书的路径 certificate: string # 证书 PEM 格式,或者 证书的路径
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径 private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
# 自定义证书验证,将加入 Clash 证书验证中,绝大多数 TLS 相关支持DNS
# 可用于自定义证书的验证
custom-certificates:
- certificate: string # 证书 PEM 格式,或者 证书的路径
private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径
# 流量入站 # 流量入站
listeners: listeners:
@ -757,7 +808,7 @@ listeners:
listen: 0.0.0.0 listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错) # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时这里的proxy名称必须合法否则会出错)
network: [ tcp, udp ] network: [tcp, udp]
target: target.com target: target.com
- name: tun-in-1 - name: tun-in-1
@ -801,4 +852,3 @@ listeners:
# - com.android.chrome # - com.android.chrome
# exclude_package: # 排除被路由的 Android 应用包名 # exclude_package: # 排除被路由的 Android 应用包名
# - com.android.captiveportallogin # - com.android.captiveportallogin

2182
docs/heap.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 116 KiB

57
go.mod
View file

@ -6,41 +6,43 @@ require (
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da
github.com/cilium/ebpf v0.9.3 github.com/cilium/ebpf v0.9.3
github.com/coreos/go-iptables v0.6.0 github.com/coreos/go-iptables v0.6.0
github.com/database64128/tfo-go/v2 v2.0.2
github.com/dlclark/regexp2 v1.7.0 github.com/dlclark/regexp2 v1.7.0
github.com/go-chi/chi/v5 v5.0.7 github.com/go-chi/chi/v5 v5.0.8
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-chi/render v1.0.2 github.com/go-chi/render v1.0.2
github.com/gofrs/uuid v4.3.1+incompatible github.com/gofrs/uuid v4.3.1+incompatible
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.5.4
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
github.com/jpillora/backoff v1.0.0 github.com/jpillora/backoff v1.0.0
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.0 github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7
github.com/metacubex/quic-go v0.31.1-0.20221127023445-9f0ce65a734e github.com/metacubex/quic-go v0.32.0
github.com/metacubex/sing-shadowsocks v0.1.0 github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7
github.com/metacubex/sing-tun v0.1.0 github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b
github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4
github.com/miekg/dns v1.1.50 github.com/miekg/dns v1.1.50
github.com/mroth/weightedrand/v2 v2.0.0
github.com/oschwald/geoip2-golang v1.8.0 github.com/oschwald/geoip2-golang v1.8.0
github.com/refraction-networking/utls v1.2.0
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97
github.com/sagernet/sing v0.1.0 github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9
github.com/sagernet/sing-vmess v0.1.0 github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb
github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
github.com/samber/lo v1.35.0 github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
github.com/samber/lo v1.37.0
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837
go.etcd.io/bbolt v1.3.6 go.etcd.io/bbolt v1.3.6
go.uber.org/atomic v1.10.0 go.uber.org/atomic v1.10.0
go.uber.org/automaxprocs v1.5.1 go.uber.org/automaxprocs v1.5.1
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a golang.org/x/crypto v0.5.0
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 golang.org/x/exp v0.0.0-20221205204356-47842c84f3db
golang.org/x/net v0.2.1-0.20221117215542-ecf7fda6a59e golang.org/x/net v0.5.0
golang.org/x/sync v0.1.0 golang.org/x/sync v0.1.0
golang.org/x/sys v0.2.1-0.20221110211117-d684c6f88669 golang.org/x/sys v0.4.0
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.1.7 lukechampine.com/blake3 v1.1.7
@ -48,6 +50,7 @@ require (
require ( require (
github.com/ajg/form v1.5.1 // indirect github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
@ -55,24 +58,26 @@ require (
github.com/google/btree v1.0.1 // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/josharian/native v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/marten-seemann/qpack v0.3.0 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.3 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.1 // indirect
github.com/mdlayher/socket v0.4.0 // indirect github.com/mdlayher/socket v0.4.0 // indirect
github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/oschwald/maxminddb-golang v1.10.0 // indirect github.com/oschwald/maxminddb-golang v1.10.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
github.com/quic-go/qtls-go1-19 v0.2.0 // indirect
github.com/quic-go/qtls-go1-20 v0.1.0 // indirect
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e // indirect
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.6.0 // indirect
golang.org/x/text v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.2.0 // indirect
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
) )
replace gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c => github.com/metacubex/gvisor v0.0.0-20221217030112-bdcd835fd60e replace go.uber.org/atomic v1.10.0 => github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370

121
go.sum
View file

@ -2,6 +2,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -9,8 +11,6 @@ github.com/cilium/ebpf v0.9.3 h1:5KtxXZU+scyERvkJMEm16TbScVvuuMrlhPly78ZMbSc=
github.com/cilium/ebpf v0.9.3/go.mod h1:w27N4UjpaQ9X/DGrSugxUG+H+NhgntDuPb5lCzxCn8A= github.com/cilium/ebpf v0.9.3/go.mod h1:w27N4UjpaQ9X/DGrSugxUG+H+NhgntDuPb5lCzxCn8A=
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/database64128/tfo-go/v2 v2.0.2 h1:5rGgkJeLEKlNaqredfrPQNLnctn1b+1fq/8tdKdOzJg=
github.com/database64128/tfo-go/v2 v2.0.2/go.mod h1:FDdt4JaAsRU66wsYHxSVytYimPkKIHupVsxM+5DhvjY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -20,8 +20,8 @@ github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:Pjfxu
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss= github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg= github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
@ -55,10 +55,11 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ= github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg=
github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
@ -66,6 +67,8 @@ github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGu
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok= github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM=
github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
@ -73,36 +76,33 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
github.com/marten-seemann/qpack v0.3.0 h1:UiWstOgT8+znlkDPOg2+3rIuYXJ2CnGDkGUXN6ki6hE=
github.com/marten-seemann/qpack v0.3.0/go.mod h1:cGfKPBiP4a9EQdxCwEwI/GEeWAsjSekBvx/X8mh58+g=
github.com/marten-seemann/qtls-go1-18 v0.1.3 h1:R4H2Ks8P6pAtUagjFty2p7BVHn3XiwDAl7TTQf5h7TI=
github.com/marten-seemann/qtls-go1-18 v0.1.3/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.1 h1:mnbxeq3oEyQxQXwI4ReCgW9DPoPR94sNlqWoDZnjRIE=
github.com/marten-seemann/qtls-go1-19 v0.1.1/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y= github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA= github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o= github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/netlink v1.7.0 h1:ZNGI4V7i1fJ94DPYtWhI/R85i/Q7ZxnuhUJQcJMoodI= github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7 h1:HSkXG1bE/qcRuuPlZ2Jyf0Od8HLxOowi7CzKQqNtWn4=
github.com/mdlayher/netlink v1.7.0/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7/go.mod h1:1ztDZHGbU5MjN5lNZpkpG8ygndjjWzcojp/H7r6l6QQ=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg= github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
github.com/metacubex/gvisor v0.0.0-20221217030112-bdcd835fd60e h1:3PHqNvIAwYbv9cOQbRFIUgzJ+K6fhV1HHj+Vpg8U7g8= github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e h1:j4j2dlV2d//FAsQlRUriH6nvv36AEAhECbNy7narf1M=
github.com/metacubex/gvisor v0.0.0-20221217030112-bdcd835fd60e/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e/go.mod h1:abc7OdNmWlhcNHz84ECEosd5ND5pnWQmD8W55p/4cuc=
github.com/metacubex/quic-go v0.31.1-0.20221127023445-9f0ce65a734e h1:RnfC6+sShJ3biU2Q2wuh4FxZ8/3fp1QG+1zAfswVehA= github.com/metacubex/quic-go v0.32.0 h1:dSD8LB4MSeBuD4otd8y1DUZcRdDcEB0Ax5esPOqn2Hw=
github.com/metacubex/quic-go v0.31.1-0.20221127023445-9f0ce65a734e/go.mod h1:7NPWVTLiX2Ss9q9gBNZaNHsPqZ3Tg/ApyrXxxUYbl78= github.com/metacubex/quic-go v0.32.0/go.mod h1:yParIzDYUd/t/pzFlDtZKhnvSqbUu0bPChlKEGmJStA=
github.com/metacubex/sing-shadowsocks v0.1.0 h1:uGBtNkpy4QFlofaNkJf+iFegeLU11VzTUlkC46FHF8A= github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7 h1:MNCGIpXhxXn9ck5bxfm/cW9Nr2FGQ5cakcGK0yKZcak=
github.com/metacubex/sing-shadowsocks v0.1.0/go.mod h1:8pBSYDKVxTtqUtGZyEh4ZpFJXwP6wBVVKrs6oQiOwmQ= github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7/go.mod h1:8pBSYDKVxTtqUtGZyEh4ZpFJXwP6wBVVKrs6oQiOwmQ=
github.com/metacubex/sing-tun v0.1.0 h1:iQj0+0WjJynSKAtfv87wOZlVKWl3w9RvkOSkVe9zuMg= github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b h1:ZF/oNrSCaxIFoZmFQCiUx67t9aENZjyuqw2n4zw3L2o=
github.com/metacubex/sing-tun v0.1.0/go.mod h1:l4JyI6RTrlHLQz5vSakg+wxA+LwGVI0Mz5ZtlOv67dA= github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b/go.mod h1:TjuaYuR/g1MaY3um89xTfRNt61FJ2IcI/m5zD8QBxw4=
github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c h1:VHtXDny/TNOF7YDT9d9Qkr+x6K1O4cejXLlyPUXDeXQ= github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4 h1:d96mCF/LYyC9kULd2xwcXfP0Jd8klrOngmRxuUIZg/8=
github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c/go.mod h1:fULJ451x1/XlpIhl+Oo+EPGKla9tFZaqT5dKLrZ+NvM= github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4/go.mod h1:p2VpJuxRefgVMxc8cmatMGSFNvYbjMYMsXJOe7qFstw=
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370 h1:UkViS4DCESAUEYgbIEQdD02hyMacFt6Dny+1MOJtNIo=
github.com/metacubex/uber-atomic v0.0.0-20230202125923-feb10b770370/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/mroth/weightedrand/v2 v2.0.0 h1:ADehnByWbliEDIazDAKFdBHoqgHSXAkgyKqM/9YsPoo=
github.com/mroth/weightedrand/v2 v2.0.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q= github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
@ -113,6 +113,16 @@ github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYx
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-18 v0.2.0 h1:5ViXqBZ90wpUcZS0ge79rf029yx0dYB0McyPJwqqj7U=
github.com/quic-go/qtls-go1-18 v0.2.0/go.mod h1:moGulGHK7o6O8lSPSZNoOwcLvJKJ85vVNc7oJFD65bc=
github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4nttk=
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/refraction-networking/utls v1.2.0 h1:U5f8wkij2NVinfLuJdFP3gCMwIHs+EzvhxmYdXgiapo=
github.com/refraction-networking/utls v1.2.0/go.mod h1:NPq+cVqzH7D1BeOkmOcb5O/8iVewAsiVt2x1/eO0hgQ=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34= github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e h1:5CFRo8FJbCuf5s/eTBdZpmMbn8Fe2eSMLNAYfKanA34=
github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g= github.com/sagernet/abx-go v0.0.0-20220819185957-dba1257d738e/go.mod h1:qbt0dWObotCfcjAJJ9AxtFPNSDUfZF+6dCpgKEOBn/g=
@ -121,14 +131,16 @@ github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY= github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
github.com/sagernet/sing v0.1.0 h1:FGmaP2BVPYO2IyC/3R1DaQa/zr+kOKHRgWqrmOF+Gu8= github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9 h1:qnXh4RjHsNjdZXkfbqwVqAzYUfc160gfkS5gepmsA+A=
github.com/sagernet/sing v0.1.0/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4= github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w=
github.com/sagernet/sing-vmess v0.1.0 h1:x0tYBJRbVi7zVXpMEW45eApGpXIDs9ub3raglouAKMo= github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb h1:oyd3w17fXNmWVYFUe17YVHJW5CLW9X2mxJFDP/IWrAM=
github.com/sagernet/sing-vmess v0.1.0/go.mod h1:4lwj6EHrUlgRnKhbmtboGbt+wtl5+tHMv96Ez8LZArw= github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb/go.mod h1:9KkmnQzTL4Gvv8U2TRAH2BOITCGsGPpHtUPP5sxn5sY=
github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c h1:qP3ZOHnjZalvqbjundbXiv/YrNlo3HOgrKc+S1QGs0U= github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI= github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
github.com/samber/lo v1.35.0 h1:GlT8CV1GE+v97Y7MLF1wXvX6mjoxZ+hi61tj/ZcQwY0= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
github.com/samber/lo v1.35.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@ -143,9 +155,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/thoas/go-funk v0.9.1 h1:O549iLZqPpTUQ10ykd26sZhzD+rmR5pWhuElrhbC20M= github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M= github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 h1:AHhUwwFJGl27E46OpdJHplZkK09m7aETNBNzhT6t15M=
@ -153,22 +164,20 @@ github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837/go.mod h1:YJTRELIWrGxR1s8x
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk= go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a h1:diz9pEYuTIuLMJLs3rGDkeaTsNyRs6duYdFyPAxzE/U= golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o=
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/exp v0.0.0-20221205204356-47842c84f3db/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -181,8 +190,8 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.1-0.20221117215542-ecf7fda6a59e h1:IVOjWZQH/57UDcpX19vSmMz8w3ohroOMWohn8qWpRkg= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.2.1-0.20221117215542-ecf7fda6a59e/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
@ -206,19 +215,19 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.1-0.20221110211117-d684c6f88669 h1:pvmSpBoSG0gD2LLPAX15QHPig8xsbU0tu1sSAmResqk= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.2.1-0.20221110211117-d684c6f88669/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -227,8 +236,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -237,7 +246,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -18,7 +18,7 @@ import (
"github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/profile/cachefile"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
SNI "github.com/Dreamacro/clash/component/sniffer" SNI "github.com/Dreamacro/clash/component/sniffer"
"github.com/Dreamacro/clash/component/tls" CTLS "github.com/Dreamacro/clash/component/tls"
"github.com/Dreamacro/clash/component/trie" "github.com/Dreamacro/clash/component/trie"
"github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/config"
C "github.com/Dreamacro/clash/constant" C "github.com/Dreamacro/clash/constant"
@ -137,8 +137,9 @@ func GetGeneral() *config.General {
func updateListeners(listeners map[string]C.InboundListener) { func updateListeners(listeners map[string]C.InboundListener) {
tcpIn := tunnel.TCPIn() tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn() udpIn := tunnel.UDPIn()
natTable := tunnel.NatTable()
listener.PatchInboundListeners(listeners, tcpIn, udpIn, true) listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true)
} }
func updateExperimental(c *config.Config) { func updateExperimental(c *config.Config) {
@ -146,29 +147,25 @@ func updateExperimental(c *config.Config) {
} }
func preUpdateExperimental(c *config.Config) { func preUpdateExperimental(c *config.Config) {
for _, fingerprint := range c.Experimental.Fingerprints { CTLS.AddCertificate(c.TLS.PrivateKey, c.TLS.Certificate)
if err := tls.AddCertFingerprint(fingerprint); err != nil { for _, c := range c.TLS.CustomTrustCert {
log.Warnln("fingerprint[%s] is err, %s", fingerprint, err.Error()) CTLS.AddCertificate(c.PrivateKey, c.Certificate)
}
} }
} }
func updateDNS(c *config.DNS, generalIPv6 bool) { func updateDNS(c *config.DNS, generalIPv6 bool) {
if !c.Enable { if !c.Enable {
resolver.DisableIPv6 = !generalIPv6
resolver.DefaultResolver = nil resolver.DefaultResolver = nil
resolver.DefaultHostMapper = nil resolver.DefaultHostMapper = nil
resolver.DefaultLocalServer = nil resolver.DefaultLocalServer = nil
dns.ReCreateServer("", nil, nil) dns.ReCreateServer("", nil, nil)
return return
} else {
resolver.DisableIPv6 = !c.IPv6
} }
cfg := dns.Config{ cfg := dns.Config{
Main: c.NameServer, Main: c.NameServer,
Fallback: c.Fallback, Fallback: c.Fallback,
IPv6: c.IPv6, IPv6: c.IPv6 && generalIPv6,
EnhancedMode: c.EnhancedMode, EnhancedMode: c.EnhancedMode,
Pool: c.FakeIPRange, Pool: c.FakeIPRange,
Hosts: c.Hosts, Hosts: c.Hosts,
@ -227,11 +224,11 @@ func loadProvider(pv provider.Provider) {
switch pv.Type() { switch pv.Type() {
case provider.Proxy: case provider.Proxy:
{ {
log.Warnln("initial proxy provider %s error: %v", (pv).Name(), err) log.Errorln("initial proxy provider %s error: %v", (pv).Name(), err)
} }
case provider.Rule: case provider.Rule:
{ {
log.Warnln("initial rule provider %s error: %v", (pv).Name(), err) log.Errorln("initial rule provider %s error: %v", (pv).Name(), err)
} }
} }
@ -283,7 +280,7 @@ func updateTun(general *config.General) {
func updateSniffer(sniffer *config.Sniffer) { func updateSniffer(sniffer *config.Sniffer) {
if sniffer.Enable { if sniffer.Enable {
dispatcher, err := SNI.NewSnifferDispatcher( dispatcher, err := SNI.NewSnifferDispatcher(
sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.Ports, sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain,
sniffer.ForceDnsMapping, sniffer.ParsePureIp, sniffer.ForceDnsMapping, sniffer.ParsePureIp,
) )
if err != nil { if err != nil {
@ -309,13 +306,12 @@ func updateTunnels(tunnels []LC.Tunnel) {
func updateGeneral(general *config.General, force bool) { func updateGeneral(general *config.General, force bool) {
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
tunnel.SetFindProcessMode(general.EnableProcess, general.FindProcessMode) tunnel.SetFindProcessMode(general.FindProcessMode)
dialer.DisableIPv6 = !general.IPv6 dialer.DisableIPv6 = !general.IPv6
if !dialer.DisableIPv6 { if !dialer.DisableIPv6 {
log.Infoln("Use IPv6") log.Infoln("Use IPv6")
} else {
resolver.DisableIPv6 = true
} }
resolver.DisableIPv6 = dialer.DisableIPv6
if general.TCPConcurrent { if general.TCPConcurrent {
dialer.SetDial(general.TCPConcurrent) dialer.SetDial(general.TCPConcurrent)
@ -353,12 +349,13 @@ func updateGeneral(general *config.General, force bool) {
tcpIn := tunnel.TCPIn() tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn() udpIn := tunnel.UDPIn()
natTable := tunnel.NatTable()
listener.ReCreateHTTP(general.Port, tcpIn) listener.ReCreateHTTP(general.Port, tcpIn)
listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn) listener.ReCreateSocks(general.SocksPort, tcpIn, udpIn)
listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn) listener.ReCreateRedir(general.RedirPort, tcpIn, udpIn, natTable)
listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn) listener.ReCreateAutoRedir(general.EBpf.AutoRedir, tcpIn, udpIn)
listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn) listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable)
listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn) listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn)
listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn) listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn)
listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn) listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn)

View file

@ -42,8 +42,8 @@ func Parse(options ...Option) error {
} }
if cfg.General.ExternalController != "" { if cfg.General.ExternalController != "" {
go route.Start(cfg.General.ExternalController,cfg.General.ExternalControllerTLS, go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS,
cfg.General.Secret,cfg.TLS.Certificate,cfg.TLS.PrivateKey) cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey)
} }
executor.ApplyConfig(cfg, true) executor.ApplyConfig(cfg, true)

View file

@ -104,7 +104,6 @@ func pointerOrDefault(p *int, def int) int {
if p != nil { if p != nil {
return *p return *p
} }
return def return def
} }
@ -210,7 +209,7 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS
func patchConfigs(w http.ResponseWriter, r *http.Request) { func patchConfigs(w http.ResponseWriter, r *http.Request) {
general := &configSchema{} general := &configSchema{}
if err := render.DecodeJSON(r.Body, general); err != nil { if err := render.DecodeJSON(r.Body, &general); err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)
return return
@ -240,11 +239,12 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
tcpIn := tunnel.TCPIn() tcpIn := tunnel.TCPIn()
udpIn := tunnel.UDPIn() udpIn := tunnel.UDPIn()
natTable := tunnel.NatTable()
P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn) P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn)
P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn)
P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn) P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn, natTable)
P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn) P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn, natTable)
P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn) P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn)
P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tcpIn, udpIn) P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), tcpIn, udpIn)
P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tcpIn, udpIn) P.ReCreateShadowSocks(pointerOrDefaultString(general.ShadowSocksConfig, ports.ShadowSocksConfig), tcpIn, udpIn)
@ -266,13 +266,11 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
render.NoContent(w, r) render.NoContent(w, r)
} }
type updateConfigRequest struct {
Path string `json:"path"`
Payload string `json:"payload"`
}
func updateConfigs(w http.ResponseWriter, r *http.Request) { func updateConfigs(w http.ResponseWriter, r *http.Request) {
req := updateConfigRequest{} req := struct {
Path string `json:"path"`
Payload string `json:"payload"`
}{}
if err := render.DecodeJSON(r.Body, &req); err != nil { if err := render.DecodeJSON(r.Body, &req); err != nil {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, ErrBadRequest) render.JSON(w, r, ErrBadRequest)

82
hub/route/dns.go Normal file
View file

@ -0,0 +1,82 @@
package route
import (
"context"
"math"
"net/http"
"github.com/Dreamacro/clash/component/resolver"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/miekg/dns"
"github.com/samber/lo"
)
func dnsRouter() http.Handler {
r := chi.NewRouter()
r.Get("/query", queryDNS)
return r
}
func queryDNS(w http.ResponseWriter, r *http.Request) {
if resolver.DefaultResolver == nil {
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError("DNS section is disabled"))
return
}
name := r.URL.Query().Get("name")
qTypeStr, _ := lo.Coalesce(r.URL.Query().Get("type"), "A")
qType, exist := dns.StringToType[qTypeStr]
if !exist {
render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("invalid query type"))
return
}
ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout)
defer cancel()
msg := dns.Msg{}
msg.SetQuestion(dns.Fqdn(name), qType)
resp, err := resolver.DefaultResolver.ExchangeContext(ctx, &msg)
if err != nil {
render.Status(r, http.StatusInternalServerError)
render.JSON(w, r, newError(err.Error()))
return
}
responseData := render.M{
"Status": resp.Rcode,
"Question": resp.Question,
"TC": resp.Truncated,
"RD": resp.RecursionDesired,
"RA": resp.RecursionAvailable,
"AD": resp.AuthenticatedData,
"CD": resp.CheckingDisabled,
}
rr2Json := func(rr dns.RR, _ int) render.M {
header := rr.Header()
return render.M{
"name": header.Name,
"type": header.Rrtype,
"TTL": header.Ttl,
"data": lo.Substring(rr.String(), len(header.String()), math.MaxUint),
}
}
if len(resp.Answer) > 0 {
responseData["Answer"] = lo.Map(resp.Answer, rr2Json)
}
if len(resp.Ns) > 0 {
responseData["Authority"] = lo.Map(resp.Ns, rr2Json)
}
if len(resp.Extra) > 0 {
responseData["Additional"] = lo.Map(resp.Extra, rr2Json)
}
render.JSON(w, r, responseData)
}

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