diff --git a/.github/release.sh b/.github/release.sh new file mode 100644 index 00000000..4757cdbe --- /dev/null +++ b/.github/release.sh @@ -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 \ No newline at end of file diff --git a/.github/rename-cgo.sh b/.github/rename-cgo.sh new file mode 100644 index 00000000..54841712 --- /dev/null +++ b/.github/rename-cgo.sh @@ -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 \ No newline at end of file diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 3e9d3d93..00000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -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 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..66b694de --- /dev/null +++ b/.github/workflows/build.yml @@ -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 }} diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml deleted file mode 100644 index fae58609..00000000 --- a/.github/workflows/docker.yaml +++ /dev/null @@ -1,62 +0,0 @@ -name: Docker - -on: - workflow_dispatch: - 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 }} \ No newline at end of file diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml deleted file mode 100644 index c20efb0b..00000000 --- a/.github/workflows/prerelease.yml +++ /dev/null @@ -1,61 +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: mknejp/delete-release-assets@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - tag_name: Prerelease-${{ github.ref_name }} - assets: | - *.zip - *.gz - - - name: Tag Repo - uses: richardsimko/update-tag@v1.0.6 - with: - tag_name: Prerelease-${{ github.ref_name }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Upload Alpha - uses: softprops/action-gh-release@v1 - if: ${{ success() }} - with: - tag_name: Prerelease-${{ github.ref_name }} - files: bin/* - prerelease: true - generate_release_notes: true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index c74637a6..00000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -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 diff --git a/.gitignore b/.gitignore index e7ee7ab9..d8801262 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ vendor # test suite test/config/cache* /output -/.vscode \ No newline at end of file +.vscode/ +.fleet/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 10f961a3..9c2e44c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 && \ 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/geoip.dat https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat - -COPY . /clash-src -WORKDIR /clash-src -RUN go mod download &&\ - make docker &&\ - mv ./bin/clash.meta-docker /clash - +COPY docker/file-name.sh /clash/file-name.sh +WORKDIR /clash +COPY bin/ bin/ +RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \ + FILE_NAME=`ls bin/ | egrep "$FILE_NAME.*"|awk NR==1` && \ + mv bin/$FILE_NAME clash.gz && gzip -d clash.gz && echo "$FILE_NAME" > /clash-config/test FROM alpine:latest 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/"] COPY --from=builder /clash-config/ /root/.config/clash/ -COPY --from=builder /clash /clash +COPY --from=builder /clash/clash /clash RUN chmod +x /clash ENTRYPOINT [ "/clash" ] diff --git a/Makefile b/Makefile index e23fa2d3..f7a637a9 100644 --- a/Makefile +++ b/Makefile @@ -47,14 +47,17 @@ all:linux-amd64 linux-arm64\ darwin-amd64 darwin-arm64\ windows-amd64 windows-arm64\ + +darwin-all: darwin-amd64 darwin-arm64 + docker: - GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ + GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ darwin-amd64: GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 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: GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ @@ -66,7 +69,7 @@ linux-amd64: GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@ 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: 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 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: 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_CFLAGS := $(CFLAGS) ebpf: - cd component/ebpf/ && go generate ./... \ No newline at end of file + cd component/ebpf/ && go generate ./... diff --git a/README.md b/README.md index 321ce97e..cdfa3505 100644 --- a/README.md +++ b/README.md @@ -29,32 +29,42 @@ - Netfilter TCP redirecting. Deploy Clash on your Internet gateway with `iptables`. - Comprehensive HTTP RESTful API controller -## Getting Started -Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash/wiki). +## 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 You should install [golang](https://go.dev) first. Then get the source code of Clash.Meta: + ```shell git clone https://github.com/MetaCubeX/Clash.Meta.git cd Clash.Meta && go mod download ``` If you can't visit github,you should set proxy first: + ```shell go env -w GOPROXY=https://goproxy.io,direct ``` -So now you can build it: +Now you can build it: + ```shell go build ``` -### DNS configuration +If you need gvisor for tun stack, build with: + +```shell +go build -tags with_gvisor +``` + + + + ### IPTABLES configuration -Work on Linux OS who's supported `iptables` + +Work on Linux OS which supported `iptables` ```yaml # Enable the TPROXY listener @@ -281,17 +290,15 @@ iptables: 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) - -+ Rename executable file to `Clash-Meta` and move to `/usr/local/bin/` - -+ Create folder `/etc/Clash-Meta/` as working directory +- 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 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] WantedBy=multi-user.target ``` + Launch clashd on system startup with: + ```shell $ systemctl enable Clash-Meta ``` + Launch clashd immediately with: ```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`. -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 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) +## Debugging +Check [wiki](https://github.com/MetaCubeX/Clash.Meta/wiki/How-to-use-debug-api) to get an instruction on using debug API. + + ## Credits -* [Dreamacro/clash](https://github.com/Dreamacro/clash) -* [SagerNet/sing-box](https://github.com/SagerNet/sing-box) -* [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) -* [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) -* [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go) -* [yaling888/clash-plus-pro](https://github.com/yaling888/clash) +- [Dreamacro/clash](https://github.com/Dreamacro/clash) +- [SagerNet/sing-box](https://github.com/SagerNet/sing-box) +- [riobard/go-shadowsocks2](https://github.com/riobard/go-shadowsocks2) +- [v2ray/v2ray-core](https://github.com/v2ray/v2ray-core) +- [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go) +- [yaling888/clash-plus-pro](https://github.com/yaling888/clash) ## License diff --git a/adapter/adapter.go b/adapter/adapter.go index feef72be..ffb5ced0 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -92,6 +92,7 @@ func (p *Proxy) MarshalJSON() ([]byte, error) { mapping["history"] = p.DelayHistory() mapping["name"] = p.Name() mapping["udp"] = p.SupportUDP() + mapping["xudp"] = p.SupportXUDP() mapping["tfo"] = p.SupportTFO() return json.Marshal(mapping) } diff --git a/adapter/inbound/http.go b/adapter/inbound/http.go index 94040078..b1b881ce 100644 --- a/adapter/inbound/http.go +++ b/adapter/inbound/http.go @@ -16,11 +16,11 @@ func NewHTTP(target socks5.Addr, source net.Addr, conn net.Conn, additions ...Ad for _, addition := range additions { addition.Apply(metadata) } - if ip, port, err := parseAddr(source.String()); err == nil { + if ip, port, err := parseAddr(source); err == nil { metadata.SrcIP = ip 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.InPort = port } diff --git a/adapter/inbound/https.go b/adapter/inbound/https.go index 3fff2371..485e72bb 100644 --- a/adapter/inbound/https.go +++ b/adapter/inbound/https.go @@ -15,11 +15,11 @@ func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) *cont for _, addition := range additions { 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.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.InPort = port } diff --git a/adapter/inbound/listen.go b/adapter/inbound/listen.go index d481a56e..fa82db92 100644 --- a/adapter/inbound/listen.go +++ b/adapter/inbound/listen.go @@ -4,7 +4,7 @@ import ( "context" "net" - "github.com/database64128/tfo-go/v2" + "github.com/sagernet/tfo-go" ) var ( diff --git a/adapter/inbound/packet.go b/adapter/inbound/packet.go index d1fcb05d..44e5e1a7 100644 --- a/adapter/inbound/packet.go +++ b/adapter/inbound/packet.go @@ -24,12 +24,12 @@ func NewPacket(target socks5.Addr, packet C.UDPPacket, source C.Type, additions for _, addition := range additions { 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.SrcPort = port } 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.InPort = port } diff --git a/adapter/inbound/socket.go b/adapter/inbound/socket.go index 557b22ab..590f64d7 100644 --- a/adapter/inbound/socket.go +++ b/adapter/inbound/socket.go @@ -18,22 +18,13 @@ func NewSocket(target socks5.Addr, conn net.Conn, source C.Type, additions ...Ad addition.Apply(metadata) } - remoteAddr := conn.RemoteAddr() - - // Filter when net.Addr interface is nil - if remoteAddr != nil { - if ip, port, err := parseAddr(remoteAddr.String()); err == nil { - metadata.SrcIP = ip - metadata.SrcPort = port - } + if ip, port, err := parseAddr(conn.RemoteAddr()); err == nil { + metadata.SrcIP = ip + metadata.SrcPort = port } - localAddr := conn.LocalAddr() - // Filter when net.Addr interface is nil - if localAddr != nil { - if ip, port, err := parseAddr(localAddr.String()); err == nil { - metadata.InIP = ip - metadata.InPort = port - } + if ip, port, err := parseAddr(conn.LocalAddr()); err == nil { + metadata.InIP = ip + metadata.InPort = port } return context.NewConnContext(conn, metadata) diff --git a/adapter/inbound/util.go b/adapter/inbound/util.go index cddbcf1f..88e989f9 100644 --- a/adapter/inbound/util.go +++ b/adapter/inbound/util.go @@ -1,13 +1,14 @@ package inbound import ( - "github.com/Dreamacro/clash/common/nnip" + "errors" "net" "net/http" "net/netip" "strconv" "strings" + "github.com/Dreamacro/clash/common/nnip" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) @@ -57,8 +58,19 @@ func parseHTTPAddr(request *http.Request) *C.Metadata { return metadata } -func parseAddr(addr string) (netip.Addr, string, error) { - host, port, err := net.SplitHostPort(addr) +func parseAddr(addr net.Addr) (netip.Addr, string, error) { + // 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 { return netip.Addr{}, "", err } diff --git a/adapter/outbound/base.go b/adapter/outbound/base.go index d7ffec5a..24de7d94 100644 --- a/adapter/outbound/base.go +++ b/adapter/outbound/base.go @@ -4,12 +4,14 @@ import ( "context" "encoding/json" "errors" - "github.com/gofrs/uuid" "net" "strings" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" + + "github.com/gofrs/uuid" ) type Base struct { @@ -18,6 +20,7 @@ type Base struct { iface string tp C.AdapterType udp bool + xudp bool tfo bool rmark int id string @@ -87,6 +90,11 @@ func (b *Base) SupportUDP() bool { return b.udp } +// SupportXUDP implements C.ProxyAdapter +func (b *Base) SupportXUDP() bool { + return b.xudp +} + // SupportTFO implements C.ProxyAdapter func (b *Base) SupportTFO() bool { return b.tfo @@ -146,6 +154,7 @@ type BaseOption struct { Addr string Type C.AdapterType UDP bool + XUDP bool TFO bool Interface string RoutingMark int @@ -158,6 +167,7 @@ func NewBase(opt BaseOption) *Base { addr: opt.Addr, tp: opt.Type, udp: opt.UDP, + xudp: opt.XUDP, tfo: opt.TFO, iface: opt.Interface, rmark: opt.RoutingMark, @@ -166,7 +176,7 @@ func NewBase(opt BaseOption) *Base { } type conn struct { - net.Conn + N.ExtendedConn chain C.Chain actualRemoteDestination string } @@ -185,8 +195,12 @@ func (c *conn) AppendToChains(a C.ProxyAdapter) { 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 { - return &conn{c, []string{a.Name()}, parseRemoteDestination(a.Addr())} + return &conn{N.NewExtendedConn(c), []string{a.Name()}, parseRemoteDestination(a.Addr())} } type packetConn struct { diff --git a/adapter/outbound/http.go b/adapter/outbound/http.go index b734290a..720dc3e1 100644 --- a/adapter/outbound/http.go +++ b/adapter/outbound/http.go @@ -7,7 +7,6 @@ import ( "encoding/base64" "errors" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" "io" "net" "net/http" @@ -15,6 +14,7 @@ import ( "strconv" "github.com/Dreamacro/clash/component/dialer" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" ) @@ -150,7 +150,7 @@ func NewHttp(option HttpOption) (*Http, error) { sni = option.SNI } if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(&tls.Config{ + tlsConfig = tlsC.GetGlobalTLSConfig(&tls.Config{ InsecureSkipVerify: option.SkipCertVerify, ServerName: sni, }) diff --git a/adapter/outbound/hysteria.go b/adapter/outbound/hysteria.go index a1276415..bd75cc3c 100644 --- a/adapter/outbound/hysteria.go +++ b/adapter/outbound/hysteria.go @@ -178,7 +178,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) { return nil, err } } else { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } if len(option.ALPN) > 0 { diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 8df84c7c..54566666 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -223,7 +223,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } if len(shadowTLSOpt.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil { return nil, err diff --git a/adapter/outbound/socks5.go b/adapter/outbound/socks5.go index 28d41180..d40a6bff 100644 --- a/adapter/outbound/socks5.go +++ b/adapter/outbound/socks5.go @@ -5,12 +5,12 @@ import ( "crypto/tls" "errors" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" "io" "net" "strconv" "github.com/Dreamacro/clash/component/dialer" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" ) @@ -167,7 +167,7 @@ func NewSocks5(option Socks5Option) (*Socks5, error) { } if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index e7928b50..2a8cfe47 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -8,6 +8,7 @@ import ( "net/http" "strconv" + N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/component/dialer" tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" @@ -29,20 +30,21 @@ type Trojan struct { type TrojanOption struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - Password string `proxy:"password"` - ALPN []string `proxy:"alpn,omitempty"` - SNI string `proxy:"sni,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` - Fingerprint string `proxy:"fingerprint,omitempty"` - UDP bool `proxy:"udp,omitempty"` - Network string `proxy:"network,omitempty"` - GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` - WSOpts WSOptions `proxy:"ws-opts,omitempty"` - Flow string `proxy:"flow,omitempty"` - FlowShow bool `proxy:"flow-show,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Password string `proxy:"password"` + ALPN []string `proxy:"alpn,omitempty"` + SNI string `proxy:"sni,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Fingerprint string `proxy:"fingerprint,omitempty"` + UDP bool `proxy:"udp,omitempty"` + Network string `proxy:"network,omitempty"` + GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` + WSOpts WSOptions `proxy:"ws-opts,omitempty"` + Flow string `proxy:"flow,omitempty"` + FlowShow bool `proxy:"flow-show,omitempty"` + ClientFingerprint string `proxy:"client-fingerprint,omitempty"` } 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 func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { var err error + + if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 { + t.option.ClientFingerprint = tlsC.GetGlobalFingerprint() + } + if t.transport != nil { c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig) } else { @@ -95,7 +102,7 @@ func (t *Trojan) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) return c, err } err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)) - return c, err + return N.NewExtendedConn(c), err } // DialContext implements C.ProxyAdapter @@ -211,12 +218,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) tOption := &trojan.Option{ - Password: option.Password, - ALPN: option.ALPN, - ServerName: option.Server, - SkipCertVerify: option.SkipCertVerify, - FlowShow: option.FlowShow, - Fingerprint: option.Fingerprint, + Password: option.Password, + ALPN: option.ALPN, + ServerName: option.Server, + SkipCertVerify: option.SkipCertVerify, + FlowShow: option.FlowShow, + Fingerprint: option.Fingerprint, + ClientFingerprint: option.ClientFingerprint, } switch option.Network { @@ -268,7 +276,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { } if len(option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error 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.gunConfig = &gun.Config{ diff --git a/adapter/outbound/tuic.go b/adapter/outbound/tuic.go index fa24ae39..0ca13670 100644 --- a/adapter/outbound/tuic.go +++ b/adapter/outbound/tuic.go @@ -143,7 +143,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { return nil, err } } else { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } if len(option.ALPN) > 0 { @@ -165,7 +165,7 @@ func NewTuic(option TuicOption) (*Tuic, error) { } if option.MaxUdpRelayPacketSize == 0 { - option.MaxUdpRelayPacketSize = 1500 + option.MaxUdpRelayPacketSize = 1252 } if option.MaxOpenStreams == 0 { @@ -216,9 +216,14 @@ func NewTuic(option TuicOption) (*Tuic, error) { prefer: C.NewDNSPrefer(option.IPVersion), }, } - // to avoid tuic's "too many open streams", decrease to 0.9x + 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 { clientMaxOpenStreams = 1 } diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index e9e382c4..e46e245d 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -12,10 +12,6 @@ import ( "strconv" "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/component/dialer" "github.com/Dreamacro/clash/component/resolver" @@ -25,6 +21,10 @@ import ( "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/vless" "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 ( @@ -45,31 +45,37 @@ type Vless struct { type VlessOption struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - UUID string `proxy:"uuid"` - Flow string `proxy:"flow,omitempty"` - FlowShow bool `proxy:"flow-show,omitempty"` - TLS bool `proxy:"tls,omitempty"` - UDP bool `proxy:"udp,omitempty"` - PacketAddr bool `proxy:"packet-addr,omitempty"` - XUDP bool `proxy:"xudp,omitempty"` - PacketEncoding string `proxy:"packet-encoding,omitempty"` - Network string `proxy:"network,omitempty"` - HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` - HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` - GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` - WSOpts WSOptions `proxy:"ws-opts,omitempty"` - WSPath string `proxy:"ws-path,omitempty"` - WSHeaders map[string]string `proxy:"ws-headers,omitempty"` - SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` - Fingerprint string `proxy:"fingerprint,omitempty"` - ServerName string `proxy:"servername,omitempty"` + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + UUID string `proxy:"uuid"` + Flow string `proxy:"flow,omitempty"` + FlowShow bool `proxy:"flow-show,omitempty"` + TLS bool `proxy:"tls,omitempty"` + UDP bool `proxy:"udp,omitempty"` + PacketAddr bool `proxy:"packet-addr,omitempty"` + XUDP bool `proxy:"xudp,omitempty"` + PacketEncoding string `proxy:"packet-encoding,omitempty"` + Network string `proxy:"network,omitempty"` + HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` + HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` + GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"` + WSOpts WSOptions `proxy:"ws-opts,omitempty"` + WSPath string `proxy:"ws-path,omitempty"` + WSHeaders map[string]string `proxy:"ws-headers,omitempty"` + SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` + Fingerprint string `proxy:"fingerprint,omitempty"` + ServerName string `proxy:"servername,omitempty"` + ClientFingerprint string `proxy:"client-fingerprint,omitempty"` } func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { var err error + + if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 { + v.option.ClientFingerprint = tlsC.GetGlobalFingerprint() + } + switch v.option.Network { case "ws": @@ -80,6 +86,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { Path: v.option.WSOpts.Path, MaxEarlyData: v.option.WSOpts.MaxEarlyData, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + ClientFingerprint: v.option.ClientFingerprint, 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 { - wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint) + if err != nil { + return nil, err + } } 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 { tlsOpts := vmess.TLSConfig{ - Host: host, - SkipCertVerify: v.option.SkipCertVerify, - FingerPrint: v.option.Fingerprint, + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + FingerPrint: v.option.Fingerprint, + ClientFingerprint: v.option.ClientFingerprint, } if isH2 { @@ -233,12 +244,15 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta }(c) 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 } // ListenPacketContext implements C.ProxyAdapter func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - // vless use stream-oriented udp with a special address, so we needs a net.UDPAddr + // vless use stream-oriented udp with a special address, so we need a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIP(ctx, metadata.Host) if err != nil { @@ -279,7 +293,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o // ListenPacketWithDialer implements C.ProxyAdapter 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() { ip, err := resolver.ResolveIP(ctx, metadata.Host) 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) if err != nil { return nil, err @@ -485,6 +509,7 @@ func NewVless(option VlessOption) (*Vless, error) { addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), tp: C.Vless, udp: option.UDP, + xudp: option.XUDP, iface: option.Interface, rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), @@ -493,16 +518,6 @@ func NewVless(option VlessOption) (*Vless, error) { 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 { case "h2": if len(option.HTTP2Opts.Host) == 0 { @@ -519,10 +534,11 @@ func NewVless(option VlessOption) (*Vless, error) { } gunConfig := &gun.Config{ - ServiceName: v.option.GrpcOpts.GrpcServiceName, - Host: v.option.ServerName, + ServiceName: v.option.GrpcOpts.GrpcServiceName, + Host: v.option.ServerName, + ClientFingerprint: v.option.ClientFingerprint, } - tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(&tls.Config{ + tlsConfig := tlsC.GetGlobalTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, }) @@ -535,7 +551,9 @@ func NewVless(option VlessOption) (*Vless, error) { v.gunTLSConfig = tlsConfig v.gunConfig = gunConfig - v.transport = gun.NewHTTP2Client(dialFn, tlsConfig) + + v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint) + } return v, nil diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 4d3bb3d7..5da8c8b1 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -5,8 +5,6 @@ import ( "crypto/tls" "errors" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" - vmess "github.com/sagernet/sing-vmess" "net" "net/http" "strconv" @@ -15,10 +13,12 @@ import ( "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/gun" clashVMess "github.com/Dreamacro/clash/transport/vmess" + vmess "github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess/packetaddr" M "github.com/sagernet/sing/common/metadata" ) @@ -59,6 +59,7 @@ type VmessOption struct { PacketEncoding string `proxy:"packet-encoding,omitempty"` GlobalPadding bool `proxy:"global-padding,omitempty"` AuthenticatedLength bool `proxy:"authenticated-length,omitempty"` + ClientFingerprint string `proxy:"client-fingerprint,omitempty"` } type HTTPOptions struct { @@ -86,6 +87,11 @@ type WSOptions struct { // StreamConn implements C.ProxyAdapter func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { var err error + + if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) { + v.option.ClientFingerprint = tlsC.GetGlobalFingerprint() + } + switch v.option.Network { case "ws": @@ -96,6 +102,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { Path: v.option.WSOpts.Path, MaxEarlyData: v.option.WSOpts.MaxEarlyData, EarlyDataHeaderName: v.option.WSOpts.EarlyDataHeaderName, + ClientFingerprint: v.option.ClientFingerprint, 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 { - wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + wsOpts.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil { return nil, err @@ -133,8 +140,9 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { if v.option.TLS { host, _, _ := net.SplitHostPort(v.addr) tlsOpts := &clashVMess.TLSConfig{ - Host: host, - SkipCertVerify: v.option.SkipCertVerify, + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + ClientFingerprint: v.option.ClientFingerprint, } if v.option.ServerName != "" { @@ -159,9 +167,10 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { case "h2": host, _, _ := net.SplitHostPort(v.addr) tlsOpts := clashVMess.TLSConfig{ - Host: host, - SkipCertVerify: v.option.SkipCertVerify, - NextProtos: []string{"h2"}, + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + NextProtos: []string{"h2"}, + ClientFingerprint: v.option.ClientFingerprint, } 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 { host, _, _ := net.SplitHostPort(v.addr) tlsOpts := &clashVMess.TLSConfig{ - Host: host, - SkipCertVerify: v.option.SkipCertVerify, + Host: host, + SkipCertVerify: v.option.SkipCertVerify, + ClientFingerprint: v.option.ClientFingerprint, } if v.option.ServerName != "" { @@ -251,7 +261,7 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta // ListenPacketContext implements C.ProxyAdapter func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { - // vmess use stream-oriented udp with a special address, so we needs a net.UDPAddr + // vmess use stream-oriented udp with a special address, so we need a net.UDPAddr if !metadata.Resolved() { ip, err := resolver.ResolveIP(ctx, metadata.Host) if err != nil { @@ -294,7 +304,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o // ListenPacketWithDialer implements C.ProxyAdapter 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() { ip, err := resolver.ResolveIP(ctx, metadata.Host) if err != nil { @@ -366,7 +376,7 @@ func NewVmess(option VmessOption) (*Vmess, error) { switch option.Network { case "h2", "grpc": 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)), tp: C.Vmess, udp: option.UDP, + xudp: option.XUDP, iface: option.Interface, rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), @@ -400,8 +411,9 @@ func NewVmess(option VmessOption) (*Vmess, error) { } gunConfig := &gun.Config{ - ServiceName: v.option.GrpcOpts.GrpcServiceName, - Host: v.option.ServerName, + ServiceName: v.option.GrpcOpts.GrpcServiceName, + Host: v.option.ServerName, + ClientFingerprint: v.option.ClientFingerprint, } tlsConfig := &tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, @@ -416,7 +428,9 @@ func NewVmess(option VmessOption) (*Vmess, error) { v.gunTLSConfig = tlsConfig v.gunConfig = gunConfig - v.transport = gun.NewHTTP2Client(dialFn, tlsConfig) + + v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint) + } return v, nil } diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index fee24bd6..0a421793 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -165,6 +165,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { for i := range gb.excludeTypeArray { if strings.EqualFold(mType, gb.excludeTypeArray[i]) { flag = true + break } } diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 7dc4d3d3..48bd4994 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -115,11 +115,20 @@ func (lb *LoadBalance) SupportUDP() bool { } func strategyRoundRobin() strategyFn { + flag := true idx := 0 return func(proxies []C.Proxy, metadata *C.Metadata) C.Proxy { length := len(proxies) 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] if proxy.Alive() { return proxy diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index ebae562a..05976c89 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -78,7 +78,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide providersMap[groupName] = pd } else { if groupOption.URL == "" { - groupOption.URL = "http://www.gstatic.com/generate_204" + groupOption.URL = "https://cp.cloudflare.com/generate_204" } if groupOption.Interval == 0 { diff --git a/adapter/parser.go b/adapter/parser.go index 86fe96f9..d9c18694 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -2,6 +2,9 @@ package adapter import ( "fmt" + + tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" @@ -54,6 +57,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { Path: []string{"/"}, }, } + + if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 { + vmessOption.ClientFingerprint = GlobalUtlsClient + } + err = decoder.Decode(mapping, vmessOption) if err != nil { break @@ -61,6 +69,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { proxy, err = outbound.NewVmess(*vmessOption) case "vless": vlessOption := &outbound.VlessOption{} + + if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 { + vlessOption.ClientFingerprint = GlobalUtlsClient + } + err = decoder.Decode(mapping, vlessOption) if err != nil { break @@ -75,6 +88,11 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) { proxy, err = outbound.NewSnell(*snellOption) case "trojan": trojanOption := &outbound.TrojanOption{} + + if GlobalUtlsClient := tlsC.GetGlobalFingerprint(); len(GlobalUtlsClient) != 0 { + trojanOption.ClientFingerprint = GlobalUtlsClient + } + err = decoder.Decode(mapping, trojanOption) if err != nil { break diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 7d7ba977..e8bd7ed1 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -301,6 +301,7 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray for i := range excludeTypeArray { if strings.EqualFold(pType, excludeTypeArray[i]) { flag = true + break } } diff --git a/common/buf/sing.go b/common/buf/sing.go new file mode 100644 index 00000000..b5e015f5 --- /dev/null +++ b/common/buf/sing.go @@ -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 diff --git a/common/convert/converter.go b/common/convert/converter.go index 89b4d95f..7d896d53 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -2,8 +2,10 @@ package convert import ( "bytes" + "encoding/base64" "encoding/json" "fmt" + "github.com/Dreamacro/clash/log" "net/url" "strconv" "strings" @@ -111,6 +113,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { trojan["grpc-opts"] = grpcOpts } + if fingerprint := query.Get("fp"); fingerprint == "" { + trojan["client-fingerprint"] = "chrome" + } else { + trojan["client-fingerprint"] = fingerprint + } + proxies = append(proxies, trojan) case "vless": @@ -120,7 +128,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { } query := urlVLess.Query() 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 != "" { vless["flow"] = strings.ToLower(flow) } @@ -138,7 +150,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { } query := urlVMess.Query() 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["cipher"] = "auto" if encryption := query.Get("encryption"); encryption != "" { @@ -272,16 +288,19 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { cipher string password string ) - + cipher = cipherRaw 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), ":") if !found { continue } - err := VerifyMethod(cipher, password) + err = VerifyMethod(cipher, password) if err != nil { - dcBuf, _ := encRaw.DecodeString(cipherRaw) + dcBuf, _ = encRaw.DecodeString(cipherRaw) cipher, password, found = strings.Cut(string(dcBuf), ":") } } diff --git a/common/convert/v.go b/common/convert/v.go index 29be4a9f..606d8aff 100644 --- a/common/convert/v.go +++ b/common/convert/v.go @@ -1,15 +1,24 @@ package convert import ( + "errors" + "fmt" "net/url" + "strconv" "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 // https://github.com/XTLS/Xray-core/discussions/716 query := url.Query() 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["server"] = url.Hostname() 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")) if strings.HasSuffix(tls, "tls") { proxy["tls"] = true + if fingerprint := query.Get("fp"); fingerprint == "" { + proxy["client-fingerprint"] = "chrome" + } else { + proxy["client-fingerprint"] = fingerprint + } } if sni := query.Get("sni"); 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["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 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") proxy["grpc-opts"] = grpcOpts } + return nil } diff --git a/common/generics/list/list.go b/common/generics/list/list.go index a06a7c61..04d84180 100644 --- a/common/generics/list/list.go +++ b/common/generics/list/list.go @@ -5,10 +5,10 @@ // Package list implements a doubly linked list. // // To iterate over a list (where l is a *List): +// // for e := l.Front(); e != nil; e = e.Next() { // // do something with e.Value // } -// package list // Element is an element of a linked list. diff --git a/common/net/bufconn.go b/common/net/bufconn.go index a50c7f03..ba0ca026 100644 --- a/common/net/bufconn.go +++ b/common/net/bufconn.go @@ -3,18 +3,22 @@ package net import ( "bufio" "net" + + "github.com/Dreamacro/clash/common/buf" ) +var _ ExtendedConn = (*BufferedConn)(nil) + type BufferedConn struct { r *bufio.Reader - net.Conn + ExtendedConn } func NewBufferedConn(c net.Conn) *BufferedConn { if bc, ok := c.(*BufferedConn); ok { return bc } - return &BufferedConn{bufio.NewReader(c), c} + return &BufferedConn{bufio.NewReader(c), NewExtendedConn(c)} } // Reader returns the internal bufio.Reader. @@ -42,3 +46,22 @@ func (c *BufferedConn) UnreadByte() error { func (c *BufferedConn) Buffered() int { 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 +} diff --git a/common/net/refconn.go b/common/net/refconn.go index 6d28a2bf..324e6474 100644 --- a/common/net/refconn.go +++ b/common/net/refconn.go @@ -51,6 +51,10 @@ func (c *refConn) SetWriteDeadline(t time.Time) error { return c.conn.SetWriteDeadline(t) } +func (c *refConn) Upstream() any { + return c.conn +} + func NewRefConn(conn net.Conn, ref any) net.Conn { return &refConn{conn: conn, ref: ref} } diff --git a/common/net/relay.go b/common/net/relay.go index d4edede2..6191e76b 100644 --- a/common/net/relay.go +++ b/common/net/relay.go @@ -1,24 +1,24 @@ package net -import ( - "io" - "net" - "time" -) - -// Relay copies between left and right bidirectionally. -func Relay(leftConn, rightConn net.Conn) { - ch := make(chan error) - - go func() { - // Wrapping to avoid using *net.TCPConn.(ReadFrom) - // See also https://github.com/Dreamacro/clash/pull/1209 - _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) - leftConn.SetReadDeadline(time.Now()) - ch <- err - }() - - _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}) - rightConn.SetReadDeadline(time.Now()) - <-ch -} +//import ( +// "io" +// "net" +// "time" +//) +// +//// Relay copies between left and right bidirectionally. +//func Relay(leftConn, rightConn net.Conn) { +// ch := make(chan error) +// +// go func() { +// // Wrapping to avoid using *net.TCPConn.(ReadFrom) +// // See also https://github.com/Dreamacro/clash/pull/1209 +// _, err := io.Copy(WriteOnlyWriter{Writer: leftConn}, ReadOnlyReader{Reader: rightConn}) +// leftConn.SetReadDeadline(time.Now()) +// ch <- err +// }() +// +// _, _ = io.Copy(WriteOnlyWriter{Writer: rightConn}, ReadOnlyReader{Reader: leftConn}) +// rightConn.SetReadDeadline(time.Now()) +// <-ch +//} diff --git a/common/net/sing.go b/common/net/sing.go new file mode 100644 index 00000000..342f2e95 --- /dev/null +++ b/common/net/sing.go @@ -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) +} diff --git a/common/net/tls.go b/common/net/tls.go index 4f5263b8..5e1c81f2 100644 --- a/common/net/tls.go +++ b/common/net/tls.go @@ -1,11 +1,19 @@ package net import ( + "crypto/rand" + "crypto/rsa" "crypto/tls" + "crypto/x509" + "encoding/pem" "fmt" + "math/big" ) func ParseCert(certificate, privateKey string) (tls.Certificate, error) { + if certificate == "" || privateKey == "" { + return newRandomTLSKeyPair() + } cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) if painTextErr == nil { return cert, nil @@ -17,3 +25,28 @@ func ParseCert(certificate, privateKey string) (tls.Certificate, error) { } 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 +} diff --git a/common/net/websocket.go b/common/net/websocket.go new file mode 100644 index 00000000..b002310a --- /dev/null +++ b/common/net/websocket.go @@ -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 +} diff --git a/component/dialer/bind_darwin.go b/component/dialer/bind_darwin.go index 91d74987..8705a708 100644 --- a/component/dialer/bind_darwin.go +++ b/component/dialer/bind_darwin.go @@ -1,6 +1,7 @@ package dialer import ( + "context" "net" "net/netip" "syscall" @@ -10,16 +11,8 @@ import ( "golang.org/x/sys/unix" ) -type controlFn = func(network, address string, c syscall.RawConn) 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) - } - }() - +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 @@ -49,7 +42,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A return err } - dialer.Control = bindControl(ifaceObj.Index, dialer.Control) + addControlToDialer(dialer, bindControl(ifaceObj.Index)) return nil } @@ -59,6 +52,10 @@ func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address return "", err } - lc.Control = bindControl(ifaceObj.Index, lc.Control) + addControlToListenConfig(lc, bindControl(ifaceObj.Index)) return address, nil } + +func ParseNetwork(network string, addr netip.Addr) string { + return network +} diff --git a/component/dialer/bind_linux.go b/component/dialer/bind_linux.go index ca88cb58..1ec98f3d 100644 --- a/component/dialer/bind_linux.go +++ b/component/dialer/bind_linux.go @@ -1,6 +1,7 @@ package dialer import ( + "context" "net" "net/netip" "syscall" @@ -8,16 +9,8 @@ import ( "golang.org/x/sys/unix" ) -type controlFn = func(network, address string, c syscall.RawConn) 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) - } - }() - +func bindControl(ifaceName string) 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 @@ -37,13 +30,17 @@ func bindControl(ifaceName string, chain controlFn) controlFn { } func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error { - dialer.Control = bindControl(ifaceName, dialer.Control) + addControlToDialer(dialer, bindControl(ifaceName)) return nil } func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) { - lc.Control = bindControl(ifaceName, lc.Control) + addControlToListenConfig(lc, bindControl(ifaceName)) return address, nil } + +func ParseNetwork(network string, addr netip.Addr) string { + return network +} diff --git a/component/dialer/bind_others.go b/component/dialer/bind_others.go index f732b1ce..5cb2fd62 100644 --- a/component/dialer/bind_others.go +++ b/component/dialer/bind_others.go @@ -1,4 +1,4 @@ -//go:build !linux && !darwin +//go:build !linux && !darwin && !windows package dialer @@ -91,3 +91,13 @@ func bindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, add 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 +} diff --git a/component/dialer/bind_windows.go b/component/dialer/bind_windows.go new file mode 100644 index 00000000..b680e90f --- /dev/null +++ b/component/dialer/bind_windows.go @@ -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 +} diff --git a/component/dialer/control.go b/component/dialer/control.go new file mode 100644 index 00000000..26b1db76 --- /dev/null +++ b/component/dialer/control.go @@ -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) + } +} diff --git a/component/dialer/control_go119.go b/component/dialer/control_go119.go new file mode 100644 index 00000000..ec980586 --- /dev/null +++ b/component/dialer/control_go119.go @@ -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) + } +} diff --git a/component/dialer/control_go120.go b/component/dialer/control_go120.go new file mode 100644 index 00000000..65e33f9c --- /dev/null +++ b/component/dialer/control_go120.go @@ -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) + } +} diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index d7c0f3db..e31936e9 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -6,7 +6,6 @@ import ( "fmt" "net" "net/netip" - "runtime" "strings" "sync" @@ -25,17 +24,6 @@ var ( 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 { opt := &option{ interfaceName: DefaultInterface.Load(), diff --git a/component/dialer/mark_linux.go b/component/dialer/mark_linux.go index eaba5cf7..996c3dea 100644 --- a/component/dialer/mark_linux.go +++ b/component/dialer/mark_linux.go @@ -3,26 +3,22 @@ package dialer import ( + "context" "net" "net/netip" "syscall" ) 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) { - lc.Control = bindMarkToControl(mark, lc.Control) + addControlToListenConfig(lc, bindMarkToControl(mark)) } -func bindMarkToControl(mark 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) - } - }() +func bindMarkToControl(mark 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() { diff --git a/component/dialer/reuse_unix.go b/component/dialer/reuse_unix.go index 85fe5e5e..a0cf7388 100644 --- a/component/dialer/reuse_unix.go +++ b/component/dialer/reuse_unix.go @@ -3,6 +3,7 @@ package dialer import ( + "context" "net" "syscall" @@ -10,18 +11,10 @@ import ( ) func addrReuseToListenConfig(lc *net.ListenConfig) { - chain := lc.Control - - lc.Control = func(network, address string, c syscall.RawConn) (err error) { - defer func() { - if err == nil && chain != nil { - err = chain(network, address, c) - } - }() - + addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error { 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_REUSEPORT, 1) }) - } + }) } diff --git a/component/dialer/reuse_windows.go b/component/dialer/reuse_windows.go index 77fcf7ab..b8d0d809 100644 --- a/component/dialer/reuse_windows.go +++ b/component/dialer/reuse_windows.go @@ -1,6 +1,7 @@ package dialer import ( + "context" "net" "syscall" @@ -8,17 +9,9 @@ import ( ) func addrReuseToListenConfig(lc *net.ListenConfig) { - chain := lc.Control - - lc.Control = func(network, address string, c syscall.RawConn) (err error) { - defer func() { - if err == nil && chain != nil { - err = chain(network, address, c) - } - }() - + addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error { return c.Control(func(fd uintptr) { windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1) }) - } + }) } diff --git a/component/ebpf/redir/bpf_bpfeb.go b/component/ebpf/redir/bpf_bpfeb.go index ec0e1a77..57a526e8 100644 --- a/component/ebpf/redir/bpf_bpfeb.go +++ b/component/ebpf/redir/bpf_bpfeb.go @@ -41,9 +41,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) { // // The following types are suitable as obj argument: // -// *bpfObjects -// *bpfPrograms -// *bpfMaps +// *bpfObjects +// *bpfPrograms +// *bpfMaps // // See ebpf.CollectionSpec.LoadAndAssign documentation for details. func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { @@ -134,5 +134,6 @@ func _BpfClose(closers ...io.Closer) error { } // Do not access this directly. +// //go:embed bpf_bpfeb.o var _BpfBytes []byte diff --git a/component/ebpf/redir/bpf_bpfel.go b/component/ebpf/redir/bpf_bpfel.go index ed859e5d..936b84eb 100644 --- a/component/ebpf/redir/bpf_bpfel.go +++ b/component/ebpf/redir/bpf_bpfel.go @@ -41,9 +41,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) { // // The following types are suitable as obj argument: // -// *bpfObjects -// *bpfPrograms -// *bpfMaps +// *bpfObjects +// *bpfPrograms +// *bpfMaps // // See ebpf.CollectionSpec.LoadAndAssign documentation for details. func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { @@ -134,5 +134,6 @@ func _BpfClose(closers ...io.Closer) error { } // Do not access this directly. +// //go:embed bpf_bpfel.o var _BpfBytes []byte diff --git a/component/ebpf/tc/bpf_bpfeb.go b/component/ebpf/tc/bpf_bpfeb.go index 12847538..623986dc 100644 --- a/component/ebpf/tc/bpf_bpfeb.go +++ b/component/ebpf/tc/bpf_bpfeb.go @@ -28,9 +28,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) { // // The following types are suitable as obj argument: // -// *bpfObjects -// *bpfPrograms -// *bpfMaps +// *bpfObjects +// *bpfPrograms +// *bpfMaps // // See ebpf.CollectionSpec.LoadAndAssign documentation for details. func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { @@ -115,5 +115,6 @@ func _BpfClose(closers ...io.Closer) error { } // Do not access this directly. +// //go:embed bpf_bpfeb.o var _BpfBytes []byte diff --git a/component/ebpf/tc/bpf_bpfel.go b/component/ebpf/tc/bpf_bpfel.go index c09e35bb..07daba12 100644 --- a/component/ebpf/tc/bpf_bpfel.go +++ b/component/ebpf/tc/bpf_bpfel.go @@ -28,9 +28,9 @@ func loadBpf() (*ebpf.CollectionSpec, error) { // // The following types are suitable as obj argument: // -// *bpfObjects -// *bpfPrograms -// *bpfMaps +// *bpfObjects +// *bpfPrograms +// *bpfMaps // // See ebpf.CollectionSpec.LoadAndAssign documentation for details. func loadBpfObjects(obj interface{}, opts *ebpf.CollectionOptions) error { @@ -115,5 +115,6 @@ func _BpfClose(closers ...io.Closer) error { } // Do not access this directly. +// //go:embed bpf_bpfel.o var _BpfBytes []byte diff --git a/component/http/http.go b/component/http/http.go index 5f7968f6..54a3daa9 100644 --- a/component/http/http.go +++ b/component/http/http.go @@ -13,7 +13,7 @@ import ( ) const ( - UA = "Clash" + UA = "clash.meta" ) func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) { diff --git a/component/nat/table.go b/component/nat/table.go index fbb16dec..5dcd91ed 100644 --- a/component/nat/table.go +++ b/component/nat/table.go @@ -1,6 +1,7 @@ package nat import ( + "net" "sync" C "github.com/Dreamacro/clash/constant" @@ -10,16 +11,24 @@ type Table struct { mapping sync.Map } -func (t *Table) Set(key string, pc C.PacketConn) { - t.mapping.Store(key, pc) +type Entry struct { + 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 { - item, exist := t.mapping.Load(key) + entry, exist := t.getEntry(key) if !exist { return nil } - return item.(C.PacketConn) + return entry.PacketConn } func (t *Table) GetOrCreateLock(key string) (*sync.Cond, bool) { @@ -31,6 +40,62 @@ func (t *Table) Delete(key string) { 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 func New() *Table { return &Table{} diff --git a/component/process/process.go b/component/process/process.go index fe4c5d2a..76ec2c45 100644 --- a/component/process/process.go +++ b/component/process/process.go @@ -16,14 +16,6 @@ const ( 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) } - -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 -} diff --git a/component/process/process_darwin.go b/component/process/process_darwin.go index 39e56dd2..67d2e833 100644 --- a/component/process/process_darwin.go +++ b/component/process/process_darwin.go @@ -33,11 +33,7 @@ var structSize = func() int { } }() -func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { - return 0, 0, ErrPlatformNotSupport -} - -func findProcessName(network string, ip netip.Addr, port int) (*uint32, string, error) { +func findProcessName(network string, ip netip.Addr, port int) (uint32, string, error) { var spath string switch network { case TCP: @@ -45,14 +41,14 @@ func findProcessName(network string, ip netip.Addr, port int) (*uint32, string, case UDP: spath = "net.inet.udp.pcblist_n" default: - return nil, "", ErrInvalidNetwork + return 0, "", ErrInvalidNetwork } isIPv4 := ip.Is4() value, err := syscall.Sysctl(spath) if err != nil { - return nil, "", err + return 0, "", err } buf := []byte(value) @@ -96,7 +92,7 @@ func findProcessName(network string, ip netip.Addr, port int) (*uint32, string, // xsocket_n.so_last_pid pid := readNativeUint32(buf[so+68 : so+72]) pp, err := getExecPathFromPID(pid) - return nil, pp, err + return 0, pp, err } // 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 != "" { - return nil, fallbackUDPProcess, nil + return 0, fallbackUDPProcess, nil } - return nil, "", ErrNotFound + return 0, "", ErrNotFound } func getExecPathFromPID(pid uint32) (string, error) { diff --git a/component/process/process_freebsd_amd64.go b/component/process/process_freebsd_amd64.go index ffbe1515..709ade3b 100644 --- a/component/process/process_freebsd_amd64.go +++ b/component/process/process_freebsd_amd64.go @@ -21,11 +21,7 @@ var ( once sync.Once ) -func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { - return 0, 0, ErrPlatformNotSupport -} - -func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, string, error) { +func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { once.Do(func() { if err := initSearcher(); err != nil { 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 { - return nil, "", ErrPlatformNotSupport + return 0, "", ErrPlatformNotSupport } var spath string @@ -46,22 +42,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, strin case UDP: spath = "net.inet.udp.pcblist" default: - return nil, "", ErrInvalidNetwork + return 0, "", ErrInvalidNetwork } value, err := syscall.Sysctl(spath) if err != nil { - return nil, "", err + return 0, "", err } buf := []byte(value) pid, err := defaultSearcher.Search(buf, ip, uint16(srcPort), isTCP) if err != nil { - return nil, "", err + return 0, "", err } pp, err := getExecPathFromPID(pid) - return nil, pp, err + return 0, pp, err } func getExecPathFromPID(pid uint32) (string, error) { diff --git a/component/process/process_linux.go b/component/process/process_linux.go index 9b1a4844..f8174495 100644 --- a/component/process/process_linux.go +++ b/component/process/process_linux.go @@ -59,14 +59,14 @@ type inetDiagResponse struct { INode uint32 } -func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, string, error) { - inode, uid, err := resolveSocketByNetlink(network, ip, srcPort) +func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { + uid, inode, err := resolveSocketByNetlink(network, ip, srcPort) if err != nil { - return nil, "", err + return 0, "", err } 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) { @@ -119,7 +119,7 @@ func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, response := (*inetDiagResponse)(unsafe.Pointer(&msg.Data[0])) - return response.INode, response.UID, nil + return response.UID, response.INode, nil } return 0, 0, ErrNotFound diff --git a/component/process/process_other.go b/component/process/process_other.go index 32614b26..eea6e5fd 100644 --- a/component/process/process_other.go +++ b/component/process/process_other.go @@ -4,8 +4,8 @@ package process import "net/netip" -func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, string, error) { - return nil, "", ErrPlatformNotSupport +func findProcessName(network string, ip netip.Addr, srcPort int) (uint32, string, error) { + return 0, "", ErrPlatformNotSupport } func resolveSocketByNetlink(network string, ip netip.Addr, srcPort int) (uint32, uint32, error) { diff --git a/component/process/process_windows.go b/component/process/process_windows.go index 7e073c8e..cce08e30 100644 --- a/component/process/process_windows.go +++ b/component/process/process_windows.go @@ -62,7 +62,7 @@ func initWin32API() error { 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() { err := initWin32API() if err != nil { @@ -86,22 +86,22 @@ func findProcessName(network string, ip netip.Addr, srcPort int) (*uint32, strin fn = getExUDPTable class = udpTablePid default: - return nil, "", ErrInvalidNetwork + return 0, "", ErrInvalidNetwork } buf, err := getTransportTable(fn, family, class) if err != nil { - return nil, "", err + return 0, "", err } s := newSearcher(family == windows.AF_INET, network == TCP) pid, err := s.Search(buf, ip, uint16(srcPort)) if err != nil { - return nil, "", err + return 0, "", err } pp, err := getExecPathFromPID(pid) - return nil, pp, err + return 0, pp, err } type searcher struct { diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index 55f98d3c..fa1e7c02 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -11,6 +11,8 @@ import ( "time" "github.com/Dreamacro/clash/component/trie" + + "github.com/miekg/dns" ) var ( @@ -44,6 +46,7 @@ type Resolver interface { ResolveIP(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) + ExchangeContext(ctx context.Context, m *dns.Msg) (msg *dns.Msg, err error) } // LookupIPv4WithResolver same as LookupIPv4, but with a resolver diff --git a/component/resource/fetcher.go b/component/resource/fetcher.go index df8e9a54..4b905c7f 100644 --- a/component/resource/fetcher.go +++ b/component/resource/fetcher.go @@ -57,7 +57,7 @@ func (f *Fetcher[V]) Initial() (V, error) { f.UpdatedAt = &modTime isLocal = true 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 } } else { @@ -162,7 +162,7 @@ func (f *Fetcher[V]) pullLoop() { case <-f.ticker.C: elm, same, err := f.Update() 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 } diff --git a/component/sniffer/base_sniffer.go b/component/sniffer/base_sniffer.go new file mode 100644 index 00000000..c2958cc6 --- /dev/null +++ b/component/sniffer/base_sniffer.go @@ -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) diff --git a/component/sniffer/dispatcher.go b/component/sniffer/dispatcher.go index fd4fe98e..f4511b97 100644 --- a/component/sniffer/dispatcher.go +++ b/component/sniffer/dispatcher.go @@ -11,7 +11,6 @@ import ( "github.com/Dreamacro/clash/common/cache" N "github.com/Dreamacro/clash/common/net" - "github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/sniffer" @@ -27,16 +26,12 @@ var ( var Dispatcher *SnifferDispatcher type SnifferDispatcher struct { - enable bool - - sniffers []sniffer.Sniffer - - forceDomain *trie.DomainTrie[struct{}] - skipSNI *trie.DomainTrie[struct{}] - portRanges *[]utils.Range[uint16] - skipList *cache.LruCache[string, uint8] - rwMux sync.RWMutex - + enable bool + sniffers map[sniffer.Sniffer]SnifferConfig + forceDomain *trie.DomainTrie[struct{}] + skipSNI *trie.DomainTrie[struct{}] + skipList *cache.LruCache[string, uint8] + rwMux sync.RWMutex forceDnsMapping bool parsePureIp bool } @@ -55,10 +50,14 @@ func (sd *SnifferDispatcher) TCPSniff(conn net.Conn, metadata *C.Metadata) { } inWhitelist := false - for _, portRange := range *sd.portRanges { - if portRange.Contains(uint16(port)) { - inWhitelist = true - break + overrideDest := false + for sniffer, config := range sd.sniffers { + if sniffer.SupportNetwork() == C.TCP || sniffer.SupportNetwork() == C.ALLNet { + 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.rwMux.RUnlock() - sd.replaceDomain(metadata, host) + sd.replaceDomain(metadata, host, overrideDest) } } } -func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string) { - dstIP := "" - if metadata.DstIP.IsValid() { - dstIP = metadata.DstIP.String() +func (sd *SnifferDispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) { + metadata.SniffHost = host + if overrideDest { + 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 + log.Debugln("[Sniffer] Sniff TCP [%s]-->[%s] success, replace domain [%s]-->[%s]", + metadata.SourceDetail(), + metadata.RemoteAddress(), + metadata.Host, host) } 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) { - for _, s := range sd.sniffers { + for s := range sd.sniffers { if s.SupportNetwork() == C.TCP { _ = conn.SetReadDeadline(time.Now().Add(1 * time.Second)) _, err := conn.Peek(1) @@ -182,38 +171,37 @@ func NewCloseSnifferDispatcher() (*SnifferDispatcher, error) { return &dispatcher, nil } -func NewSnifferDispatcher(needSniffer []sniffer.Type, forceDomain *trie.DomainTrie[struct{}], - skipSNI *trie.DomainTrie[struct{}], ports *[]utils.Range[uint16], +func NewSnifferDispatcher(snifferConfig map[sniffer.Type]SnifferConfig, forceDomain *trie.DomainTrie[struct{}], + skipSNI *trie.DomainTrie[struct{}], forceDnsMapping bool, parsePureIp bool) (*SnifferDispatcher, error) { dispatcher := SnifferDispatcher{ enable: true, forceDomain: forceDomain, skipSNI: skipSNI, - portRanges: ports, - skipList: cache.New[string, uint8](cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)), + skipList: cache.New(cache.WithSize[string, uint8](128), cache.WithAge[string, uint8](600)), forceDnsMapping: forceDnsMapping, parsePureIp: parsePureIp, + sniffers: make(map[sniffer.Sniffer]SnifferConfig, 0), } - for _, snifferName := range needSniffer { - s, err := NewSniffer(snifferName) + for snifferName, config := range snifferConfig { + s, err := NewSniffer(snifferName, config) if err != nil { log.Errorln("Sniffer name[%s] is error", snifferName) return &SnifferDispatcher{enable: false}, err } - - dispatcher.sniffers = append(dispatcher.sniffers, s) + dispatcher.sniffers[s] = config } return &dispatcher, nil } -func NewSniffer(name sniffer.Type) (sniffer.Sniffer, error) { +func NewSniffer(name sniffer.Type, snifferConfig SnifferConfig) (sniffer.Sniffer, error) { switch name { case sniffer.TLS: - return &TLSSniffer{}, nil + return NewTLSSniffer(snifferConfig) case sniffer.HTTP: - return &HTTPSniffer{}, nil + return NewHTTPSniffer(snifferConfig) default: return nil, ErrorUnsupportedSniffer } diff --git a/component/sniffer/http_sniffer.go b/component/sniffer/http_sniffer.go index 551b20c8..bfa7ca6e 100644 --- a/component/sniffer/http_sniffer.go +++ b/component/sniffer/http_sniffer.go @@ -7,7 +7,9 @@ import ( "net" "strings" + "github.com/Dreamacro/clash/common/utils" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/sniffer" ) var ( @@ -24,10 +26,25 @@ const ( ) type HTTPSniffer struct { + *BaseSniffer version version 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 { switch http.version { case HTTP1: diff --git a/component/sniffer/tls_sniffer.go b/component/sniffer/tls_sniffer.go index f5a8fd99..0867d0f0 100644 --- a/component/sniffer/tls_sniffer.go +++ b/component/sniffer/tls_sniffer.go @@ -5,7 +5,9 @@ import ( "errors" "strings" + "github.com/Dreamacro/clash/common/utils" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/constant/sniffer" ) var ( @@ -13,7 +15,22 @@ var ( errNotClientHello = errors.New("not client hello") ) +var _ sniffer.Sniffer = (*TLSSniffer)(nil) + 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 { diff --git a/component/tls/config.go b/component/tls/config.go index 4470658f..39d1b1fd 100644 --- a/component/tls/config.go +++ b/component/tls/config.go @@ -6,75 +6,70 @@ import ( "crypto/tls" "crypto/x509" "encoding/hex" + "errors" "fmt" - xtls "github.com/xtls/go" + "strings" "sync" - "time" + + CN "github.com/Dreamacro/clash/common/net" + + xtls "github.com/xtls/go" ) -var globalFingerprints = make([][32]byte, 0, 0) -var mutex sync.Mutex +var tlsCertificates = make([]tls.Certificate, 0) -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 { - if insecureSkipVerify { - return nil - } - - var preErr error + // ssl pining for i := range rawCerts { rawCert := rawCerts[i] cert, err := x509.ParseCertificate(rawCert) if err == nil { - opts := x509.VerifyOptions{ - CurrentTime: time.Now(), - } - - if _, err := cert.Verify(opts); err == nil { + hash := sha256.Sum256(cert.Raw) + if bytes.Equal(fingerprint[:], hash[:]) { return nil - } else { - fingerprint := sha256.Sum256(cert.Raw) - for _, fp := range *fingerprints { - if bytes.Equal(fingerprint[:], fp[:]) { - return nil - } - } - - preErr = err } } } - - return preErr + return errNotMacth } } -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) { + fingerprint = strings.TrimSpace(strings.Replace(fingerprint, ":", "", -1)) fpByte, err := hex.DecodeString(fingerprint) if err != nil { return nil, err } if len(fpByte) != 32 { - return nil, fmt.Errorf("fingerprint string length error,need sha25 fingerprint") + return nil, fmt.Errorf("fingerprint string length error,need sha256 fingerprint") } return (*[32]byte)(fpByte), nil } func GetDefaultTLSConfig() *tls.Config { - return GetGlobalFingerprintTLCConfig(nil) + return GetGlobalTLSConfig(nil) } // GetSpecifiedFingerprintTLSConfig specified fingerprint @@ -82,29 +77,20 @@ func GetSpecifiedFingerprintTLSConfig(tlsConfig *tls.Config, fingerprint string) if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { return nil, err } else { - if tlsConfig == nil { - return &tls.Config{ - InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), - }, nil - } else { - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) - tlsConfig.InsecureSkipVerify = true - return tlsConfig, nil - } + tlsConfig = GetGlobalTLSConfig(tlsConfig) + tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) + tlsConfig.InsecureSkipVerify = true + return tlsConfig, nil } } -func GetGlobalFingerprintTLCConfig(tlsConfig *tls.Config) *tls.Config { +func GetGlobalTLSConfig(tlsConfig *tls.Config) *tls.Config { if tlsConfig == nil { return &tls.Config{ - InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false), + Certificates: tlsCertificates, } } - - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify) - tlsConfig.InsecureSkipVerify = true + tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCertificates...) return tlsConfig } @@ -113,28 +99,37 @@ func GetSpecifiedFingerprintXTLSConfig(tlsConfig *xtls.Config, fingerprint strin if fingerprintBytes, err := convertFingerprint(fingerprint); err != nil { return nil, err } else { - if tlsConfig == nil { - return &xtls.Config{ - InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, false), - }, nil - } else { - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&[][32]byte{*fingerprintBytes}, tlsConfig.InsecureSkipVerify) - tlsConfig.InsecureSkipVerify = true - return tlsConfig, nil - } + tlsConfig = GetGlobalXTLSConfig(tlsConfig) + tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) + 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 { return &xtls.Config{ - InsecureSkipVerify: true, - VerifyPeerCertificate: verifyPeerCertificateAndFingerprints(&globalFingerprints, false), + Certificates: xtlsCerts, } } - tlsConfig.VerifyPeerCertificate = verifyPeerCertificateAndFingerprints(&globalFingerprints, tlsConfig.InsecureSkipVerify) - tlsConfig.InsecureSkipVerify = true + tlsConfig.Certificates = xtlsCerts return tlsConfig } diff --git a/component/tls/utls.go b/component/tls/utls.go new file mode 100644 index 00000000..f965fc64 --- /dev/null +++ b/component/tls/utls.go @@ -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 +} diff --git a/config/config.go b/config/config.go index e2250e8a..d71fc63f 100644 --- a/config/config.go +++ b/config/config.go @@ -4,11 +4,12 @@ import ( "container/list" "errors" "fmt" - P "github.com/Dreamacro/clash/component/process" + "net" "net/netip" "net/url" "os" + "reflect" "runtime" "strconv" "strings" @@ -24,6 +25,9 @@ import ( "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/geodata" "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" C "github.com/Dreamacro/clash/constant" providerTypes "github.com/Dreamacro/clash/constant/provider" @@ -43,19 +47,19 @@ import ( type General struct { Inbound Controller - Mode T.TunnelMode `json:"mode"` - UnifiedDelay bool - LogLevel log.LogLevel `json:"log-level"` - IPv6 bool `json:"ipv6"` - Interface string `json:"interface-name"` - RoutingMark int `json:"-"` - GeodataMode bool `json:"geodata-mode"` - GeodataLoader string `json:"geodata-loader"` - TCPConcurrent bool `json:"tcp-concurrent"` - EnableProcess bool `json:"enable-process"` - FindProcessMode P.FindProcessMode `json:"find-process-mode"` - Sniffing bool `json:"sniffing"` - EBpf EBpf `json:"-"` + Mode T.TunnelMode `json:"mode"` + UnifiedDelay bool + LogLevel log.LogLevel `json:"log-level"` + IPv6 bool `json:"ipv6"` + Interface string `json:"interface-name"` + RoutingMark int `json:"-"` + GeodataMode bool `json:"geodata-mode"` + GeodataLoader string `json:"geodata-loader"` + TCPConcurrent bool `json:"tcp-concurrent"` + FindProcessMode P.FindProcessMode `json:"find-process-mode"` + Sniffing bool `json:"sniffing"` + EBpf EBpf `json:"-"` + GlobalClientFingerprint string `json:"global-client-fingerprint"` } // Inbound config @@ -96,7 +100,7 @@ type DNS struct { DefaultNameserver []dns.NameServer `yaml:"default-nameserver"` FakeIPRange *fakeip.Pool Hosts *trie.DomainTrie[netip.Addr] - NameServerPolicy map[string]dns.NameServer + NameServerPolicy map[string][]dns.NameServer ProxyServerNameserver []dns.NameServer } @@ -116,6 +120,11 @@ type Profile struct { } type TLS struct { + RawCert `yaml:",inline"` + CustomTrustCert []RawCert `yaml:"custom-certifactes"` +} + +type RawCert struct { Certificate string `yaml:"certificate"` PrivateKey string `yaml:"private-key"` } @@ -129,11 +138,10 @@ type IPTables struct { type Sniffer struct { Enable bool - Sniffers []snifferTypes.Type + Sniffers map[snifferTypes.Type]SNIFF.SnifferConfig Reverses *trie.DomainTrie[struct{}] ForceDomain *trie.DomainTrie[struct{}] SkipDomain *trie.DomainTrie[struct{}] - Ports *[]utils.Range[uint16] ForceDnsMapping bool ParsePureIp bool } @@ -176,7 +184,7 @@ type RawDNS struct { FakeIPRange string `yaml:"fake-ip-range"` FakeIPFilter []string `yaml:"fake-ip-filter"` 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"` } @@ -228,33 +236,33 @@ type RawTuicServer struct { } type RawConfig struct { - Port int `yaml:"port"` - SocksPort int `yaml:"socks-port"` - RedirPort int `yaml:"redir-port"` - TProxyPort int `yaml:"tproxy-port"` - MixedPort int `yaml:"mixed-port"` - ShadowSocksConfig string `yaml:"ss-config"` - VmessConfig string `yaml:"vmess-config"` - InboundTfo bool `yaml:"inbound-tfo"` - Authentication []string `yaml:"authentication"` - AllowLan bool `yaml:"allow-lan"` - BindAddress string `yaml:"bind-address"` - Mode T.TunnelMode `yaml:"mode"` - UnifiedDelay bool `yaml:"unified-delay"` - LogLevel log.LogLevel `yaml:"log-level"` - IPv6 bool `yaml:"ipv6"` - ExternalController string `yaml:"external-controller"` - ExternalControllerTLS string `yaml:"external-controller-tls"` - ExternalUI string `yaml:"external-ui"` - Secret string `yaml:"secret"` - Interface string `yaml:"interface-name"` - RoutingMark int `yaml:"routing-mark"` - Tunnels []LC.Tunnel `yaml:"tunnels"` - GeodataMode bool `yaml:"geodata-mode"` - GeodataLoader string `yaml:"geodata-loader"` - 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"` + Port int `yaml:"port"` + SocksPort int `yaml:"socks-port"` + RedirPort int `yaml:"redir-port"` + TProxyPort int `yaml:"tproxy-port"` + MixedPort int `yaml:"mixed-port"` + ShadowSocksConfig string `yaml:"ss-config"` + VmessConfig string `yaml:"vmess-config"` + InboundTfo bool `yaml:"inbound-tfo"` + Authentication []string `yaml:"authentication"` + AllowLan bool `yaml:"allow-lan"` + BindAddress string `yaml:"bind-address"` + Mode T.TunnelMode `yaml:"mode"` + UnifiedDelay bool `yaml:"unified-delay"` + LogLevel log.LogLevel `yaml:"log-level"` + IPv6 bool `yaml:"ipv6"` + ExternalController string `yaml:"external-controller"` + ExternalControllerTLS string `yaml:"external-controller-tls"` + ExternalUI string `yaml:"external-ui"` + Secret string `yaml:"secret"` + Interface string `yaml:"interface-name"` + RoutingMark int `yaml:"routing-mark"` + Tunnels []LC.Tunnel `yaml:"tunnels"` + GeodataMode bool `yaml:"geodata-mode"` + GeodataLoader string `yaml:"geodata-loader"` + TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` + FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` + GlobalClientFingerprint string `yaml:"global-client-fingerprint"` Sniffer RawSniffer `yaml:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` @@ -283,13 +291,20 @@ type RawGeoXUrl struct { } type RawSniffer struct { - Enable bool `yaml:"enable" json:"enable"` - Sniffing []string `yaml:"sniffing" json:"sniffing"` - ForceDomain []string `yaml:"force-domain" json:"force-domain"` - SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` - Ports []string `yaml:"port-whitelist" json:"port-whitelist"` - ForceDnsMapping bool `yaml:"force-dns-mapping" json:"force-dns-mapping"` - ParsePureIp bool `yaml:"parse-pure-ip" json:"parse-pure-ip"` + Enable bool `yaml:"enable" json:"enable"` + OverrideDest bool `yaml:"override-destination" json:"override-destination"` + Sniffing []string `yaml:"sniffing" json:"sniffing"` + ForceDomain []string `yaml:"force-domain" json:"force-domain"` + SkipDomain []string `yaml:"skip-domain" json:"skip-domain"` + Ports []string `yaml:"port-whitelist" json:"port-whitelist"` + 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 @@ -331,7 +346,6 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Proxy: []map[string]any{}, ProxyGroup: []map[string]any{}, TCPConcurrent: false, - EnableProcess: false, FindProcessMode: P.FindProcessStrict, Tun: RawTun{ Enable: false, @@ -399,6 +413,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Ports: []string{}, ForceDnsMapping: true, ParsePureIp: true, + OverrideDest: true, }, Profile: Profile{ StoreSelected: true, @@ -507,6 +522,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { elapsedTime := time.Since(startTime) / time.Millisecond // duration in ms 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 } @@ -540,18 +560,18 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Secret: cfg.Secret, ExternalControllerTLS: cfg.ExternalControllerTLS, }, - UnifiedDelay: cfg.UnifiedDelay, - Mode: cfg.Mode, - LogLevel: cfg.LogLevel, - IPv6: cfg.IPv6, - Interface: cfg.Interface, - RoutingMark: cfg.RoutingMark, - GeodataMode: cfg.GeodataMode, - GeodataLoader: cfg.GeodataLoader, - TCPConcurrent: cfg.TCPConcurrent, - EnableProcess: cfg.EnableProcess, - FindProcessMode: cfg.FindProcessMode, - EBpf: cfg.EBpf, + UnifiedDelay: cfg.UnifiedDelay, + Mode: cfg.Mode, + LogLevel: cfg.LogLevel, + IPv6: cfg.IPv6, + Interface: cfg.Interface, + RoutingMark: cfg.RoutingMark, + GeodataMode: cfg.GeodataMode, + GeodataLoader: cfg.GeodataLoader, + TCPConcurrent: cfg.TCPConcurrent, + FindProcessMode: cfg.FindProcessMode, + EBpf: cfg.EBpf, + GlobalClientFingerprint: cfg.GlobalClientFingerprint, }, nil } @@ -831,17 +851,15 @@ func parseHosts(cfg *RawConfig) (*trie.DomainTrie[netip.Addr], error) { } func hostWithDefaultPort(host string, defPort string) (string, error) { - if !strings.Contains(host, ":") { - host += ":" - } - hostname, port, err := net.SplitHostPort(host) if err != nil { - return "", err - } - - if port == "" { - port = defPort + if !strings.Contains(err.Error(), "missing port in address") { + return "", err + } + host = host + ":" + defPort + if hostname, port, err = net.SplitHostPort(host); err != nil { + return "", err + } } return net.JoinHostPort(hostname, port), nil @@ -851,10 +869,7 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) var nameservers []dns.NameServer for idx, server := range servers { - // parse without scheme .e.g 8.8.8.8:53 - if !strings.Contains(server, "://") { - server = "udp://" + server - } + server = parsePureDNSServer(server) u, err := url.Parse(server) if err != nil { 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") dnsNetType = "tcp-tls" // DNS over TLS case "https": - host := u.Host - proxyAdapter = "" - if _, _, err := net.SplitHostPort(host); err != nil && strings.Contains(err.Error(), "missing port in address") { - host = net.JoinHostPort(host, "443") - } else { - if err != nil { - return nil, err - } - } - clearURL := url.URL{Scheme: "https", Host: host, Path: u.Path} - addr = clearURL.String() - dnsNetType = "https" // DNS over HTTPS - if len(u.Fragment) != 0 { - for _, s := range strings.Split(u.Fragment, "&") { - arr := strings.Split(s, "=") - if len(arr) == 0 { - continue - } else if len(arr) == 1 { - proxyAdapter = arr[0] - } else if len(arr) == 2 { - params[arr[0]] = arr[1] - } else { - params[arr[0]] = strings.Join(arr[1:], "=") + addr, err = hostWithDefaultPort(u.Host, "443") + if err == nil { + proxyAdapter = "" + clearURL := url.URL{Scheme: "https", Host: addr, Path: u.Path} + addr = clearURL.String() + dnsNetType = "https" // DNS over HTTPS + if len(u.Fragment) != 0 { + for _, s := range strings.Split(u.Fragment, "&") { + arr := strings.Split(s, "=") + if len(arr) == 0 { + continue + } else if len(arr) == 1 { + proxyAdapter = arr[0] + } else if len(arr) == 2 { + params[arr[0]] = arr[1] + } else { + params[arr[0]] = strings.Join(arr[1:], "=") + } } } } @@ -930,18 +940,53 @@ func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) return nameservers, nil } -func parseNameServerPolicy(nsPolicy map[string]string, preferH3 bool) (map[string]dns.NameServer, error) { - policy := map[string]dns.NameServer{} +func parsePureDNSServer(server string) string { + 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 { - 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 { return nil, err } if _, valid := trie.ValidAndSplitDomain(domain); !valid { return nil, fmt.Errorf("DNS ResoverRule invalid domain: %s", domain) } - policy[domain] = nameservers[0] + policy[domain] = nameservers } return policy, nil @@ -967,6 +1012,7 @@ func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainM if err := geodata.InitGeoSite(); err != nil { 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 { @@ -1185,55 +1231,60 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) { ForceDnsMapping: snifferRaw.ForceDnsMapping, ParsePureIp: snifferRaw.ParsePureIp, } + loadSniffer := make(map[snifferTypes.Type]SNIFF.SnifferConfig) - var ports []utils.Range[uint16] - if len(snifferRaw.Ports) == 0 { - ports = append(ports, *utils.NewRange[uint16](80, 80)) - ports = append(ports, *utils.NewRange[uint16](443, 443)) - } else { - for _, portRange := range snifferRaw.Ports { - portRaws := strings.Split(portRange, "-") - p, err := strconv.ParseUint(portRaws[0], 10, 16) + if len(snifferRaw.Sniff) != 0 { + for sniffType, sniffConfig := range snifferRaw.Sniff { + find := false + ports, err := parsePortRange(sniffConfig.Ports) if err != nil { - return nil, fmt.Errorf("%s format error", portRange) + return nil, err } - - 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) + overrideDest := snifferRaw.OverrideDest + if sniffConfig.OverrideDest != nil { + overrideDest = *sniffConfig.OverrideDest + } + for _, snifferType := range snifferTypes.List { + if snifferType.String() == strings.ToUpper(sniffType) { + find = true + loadSniffer[snifferType] = SNIFF.SnifferConfig{ + Ports: ports, + OverrideDest: overrideDest, + } } + } - end := uint16(p) - ports = append(ports, *utils.NewRange(start, end)) - } else { - ports = append(ports, *utils.NewRange(start, start)) + if !find { + return nil, fmt.Errorf("not find the sniffer[%s]", sniffType) + } + } + } 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 - - 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.Sniffers = loadSniffer sniffer.ForceDomain = trie.New[struct{}]() for _, domain := range snifferRaw.ForceDomain { err := sniffer.ForceDomain.Insert(domain, struct{}{}) @@ -1254,3 +1305,28 @@ func parseSniffer(snifferRaw RawSniffer) (*Sniffer, error) { 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 +} diff --git a/constant/adapters.go b/constant/adapters.go index c13a85c4..bf5f7fdb 100644 --- a/constant/adapters.go +++ b/constant/adapters.go @@ -5,6 +5,7 @@ import ( "fmt" "net" "net/netip" + "sync" "time" "github.com/Dreamacro/clash/component/dialer" @@ -92,6 +93,7 @@ type ProxyAdapter interface { Type() AdapterType Addr() string SupportUDP() bool + SupportXUDP() bool SupportTFO() bool MarshalJSON() ([]byte, error) @@ -226,3 +228,23 @@ type PacketAdapter interface { UDPPacket 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) +} diff --git a/constant/dns.go b/constant/dns.go index da68753c..3d97d97b 100644 --- a/constant/dns.go +++ b/constant/dns.go @@ -124,4 +124,4 @@ const ( HTTPVersion2 HTTPVersion = "h2" // HTTPVersion3 is HTTP/3. HTTPVersion3 HTTPVersion = "h3" -) \ No newline at end of file +) diff --git a/constant/listener.go b/constant/listener.go index a52c1946..6f9f169b 100644 --- a/constant/listener.go +++ b/constant/listener.go @@ -16,7 +16,7 @@ type MultiAddrListener interface { type InboundListener interface { Name() string - Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter) error + Listen(tcpIn chan<- ConnContext, udpIn chan<- PacketAdapter, natTable NatTable) error Close() error Address() string RawAddress() string diff --git a/constant/metadata.go b/constant/metadata.go index 359329f9..599a6055 100644 --- a/constant/metadata.go +++ b/constant/metadata.go @@ -128,12 +128,14 @@ type Metadata struct { InName string `json:"inboundName"` Host string `json:"host"` DNSMode DNSMode `json:"dnsMode"` - Uid *uint32 `json:"uid"` + Uid uint32 `json:"uid"` Process string `json:"process"` ProcessPath string `json:"processPath"` SpecialProxy string `json:"specialProxy"` SpecialRules string `json:"specialRules"` RemoteDst string `json:"remoteDestination"` + // Only domain rule + SniffHost string `json:"sniffHost"` } func (m *Metadata) RemoteAddress() string { @@ -146,16 +148,17 @@ func (m *Metadata) SourceAddress() string { func (m *Metadata) SourceDetail() string { if m.Type == INNER { - return fmt.Sprintf("[%s]", ClashName) + return fmt.Sprintf("%s", ClashName) } - if m.Process != "" && m.Uid != nil { - return fmt.Sprintf("%s(%s, uid=%d)", m.SourceAddress(), m.Process, *m.Uid) - } else if m.Uid != nil { - return fmt.Sprintf("%s(uid=%d)", m.SourceAddress(), *m.Uid) - } else if m.Process != "" { + switch { + case m.Process != "" && m.Uid != 0: + return fmt.Sprintf("%s(%s, uid=%d)", m.SourceAddress(), m.Process, m.Uid) + case m.Uid != 0: + return fmt.Sprintf("%s(uid=%d)", m.SourceAddress(), m.Uid) + case m.Process != "": return fmt.Sprintf("%s(%s)", m.SourceAddress(), m.Process) - } else { + default: return fmt.Sprintf("%s", m.SourceAddress()) } } @@ -175,6 +178,14 @@ func (m *Metadata) Resolved() bool { 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 // when dialing proxy connection in DNSMapping mode. func (m *Metadata) Pure() *Metadata { diff --git a/constant/provider/interface.go b/constant/provider/interface.go index 8bc3c0fe..b42bbe71 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -102,5 +102,6 @@ type RuleProvider interface { Behavior() RuleType Match(*constant.Metadata) bool ShouldResolveIP() bool + ShouldFindProcess() bool AsRule(adaptor string) constant.Rule } diff --git a/constant/sniffer/sniffer.go b/constant/sniffer/sniffer.go index 8ab2d496..6b20b3f6 100644 --- a/constant/sniffer/sniffer.go +++ b/constant/sniffer/sniffer.go @@ -6,6 +6,7 @@ type Sniffer interface { SupportNetwork() constant.NetWork SniffTCP(bytes []byte) (string, error) Protocol() string + SupportPort(port uint16) bool } const ( diff --git a/constant/version.go b/constant/version.go index 8bc7e2f7..cbb7ab61 100644 --- a/constant/version.go +++ b/constant/version.go @@ -4,5 +4,5 @@ var ( Meta = true Version = "1.10.0" BuildTime = "unknown time" - ClashName = "Clash.Meta" + ClashName = "clash.meta" ) diff --git a/dns/client.go b/dns/client.go index a7bf5eb3..c5a52281 100644 --- a/dns/client.go +++ b/dns/client.go @@ -4,13 +4,14 @@ import ( "context" "crypto/tls" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" - "go.uber.org/atomic" "math/rand" "net" "net/netip" "strings" + tlsC "github.com/Dreamacro/clash/component/tls" + "go.uber.org/atomic" + "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" @@ -24,6 +25,26 @@ type client struct { host string iface *atomic.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) { @@ -77,7 +98,7 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) ch := make(chan result, 1) go func() { 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{ diff --git a/dns/dhcp.go b/dns/dhcp.go index 1efa3bd1..151e4421 100644 --- a/dns/dhcp.go +++ b/dns/dhcp.go @@ -2,12 +2,14 @@ package dns import ( "context" - "go.uber.org/atomic" "net" "net/netip" + "strings" "sync" "time" + "go.uber.org/atomic" + "github.com/Dreamacro/clash/component/dhcp" "github.com/Dreamacro/clash/component/iface" "github.com/Dreamacro/clash/component/resolver" @@ -34,6 +36,17 @@ type dhcpClient struct { 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) { ctx, cancel := context.WithTimeout(context.Background(), resolver.DefaultDNSTimeout) defer cancel() diff --git a/dns/doh.go b/dns/doh.go index 34685578..1e6528d9 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -64,6 +64,7 @@ type dnsOverHTTPS struct { r *Resolver httpVersions []C.HTTPVersion proxyAdapter string + addr string } // type check @@ -83,6 +84,7 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin doh := &dnsOverHTTPS{ url: u, + addr: u.String(), r: r, proxyAdapter: proxyAdapter, 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. -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) { // Quote from https://www.rfc-editor.org/rfc/rfc8484.html: // In order to maximize HTTP cache friendliness, DoH clients using media // 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 // request. + m = m.Copy() id := m.Id m.Id = 0 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, // it returns an HTTP3 transport, otherwise it returns the H1/H2 transport. func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripper, err error) { - tlsConfig := tlsC.GetGlobalFingerprintTLCConfig( + tlsConfig := tlsC.GetGlobalTLSConfig( &tls.Config{ InsecureSkipVerify: false, MinVersion: tls.VersionTLS12, diff --git a/dns/doq.go b/dns/doq.go index 1c5956af..1354f177 100644 --- a/dns/doq.go +++ b/dns/doq.go @@ -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) { // When sending queries over a QUIC connection, the DNS Message ID MUST be // set to zero. + m = m.Copy() id := m.Id m.Id = 0 defer func() { @@ -298,7 +299,7 @@ func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (q // openConnection opens a new QUIC connection. func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) { - tlsConfig := tlsC.GetGlobalFingerprintTLCConfig( + tlsConfig := tlsC.GetGlobalTLSConfig( &tls.Config{ InsecureSkipVerify: false, NextProtos: []string{ diff --git a/dns/enhancer.go b/dns/enhancer.go index 8b3ce282..76d4460e 100644 --- a/dns/enhancer.go +++ b/dns/enhancer.go @@ -109,7 +109,7 @@ func NewEnhancer(cfg Config) *ResolverEnhancer { if cfg.EnhancedMode != C.DNSNormal { 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{ diff --git a/dns/filters.go b/dns/filters.go index 0dbfa317..b51e6402 100644 --- a/dns/filters.go +++ b/dns/filters.go @@ -91,6 +91,17 @@ type geoSiteFilter struct { 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 { for _, matcher := range gsf.matchers { if matcher.ApplyDomain(domain) { diff --git a/dns/middleware.go b/dns/middleware.go index 28ced849..7dc9622d 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -164,7 +164,6 @@ func withResolver(resolver *Resolver) handler { msg.SetRcode(r, msg.Rcode) msg.Authoritative = true - log.Debugln("[DNS] %s --> %s", msgToDomain(r), msgToIP(msg)) return msg, nil } } diff --git a/dns/resolver.go b/dns/resolver.go index d99a465d..ac8917ca 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -4,17 +4,20 @@ import ( "context" "errors" "fmt" - "go.uber.org/atomic" "math/rand" "net/netip" + "strings" "time" + "go.uber.org/atomic" + "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/component/fakeip" "github.com/Dreamacro/clash/component/geodata/router" "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/trie" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" D "github.com/miekg/dns" "golang.org/x/sync/singleflight" @@ -23,6 +26,7 @@ import ( type dnsClient interface { Exchange(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 { @@ -30,6 +34,12 @@ type result struct { Error error } +type geositePolicyRecord struct { + matcher fallbackDomainFilter + policy *Policy + inversedMatching bool +} + type Resolver struct { ipv6 bool hosts *trie.DomainTrie[netip.Addr] @@ -40,6 +50,7 @@ type Resolver struct { group singleflight.Group lruCache *cache.LruCache[string, *D.Msg] policy *trie.DomainTrie[*Policy] + geositePolicy []geositePolicyRecord proxyServer []dnsClient } @@ -272,12 +283,18 @@ func (r *Resolver) matchPolicy(m *D.Msg) []dnsClient { } record := r.policy.Search(domain) - if record == nil { - return nil + if record != nil { + p := record.Data() + return p.GetData() } - p := record.Data() - return p.GetData() + for _, geositeRecord := range r.geositePolicy { + matched := geositeRecord.matcher.Match(domain) + if matched != geositeRecord.inversedMatching { + return geositeRecord.policy.GetData() + } + } + return nil } func (r *Resolver) shouldOnlyQueryFallback(m *D.Msg) bool { @@ -406,19 +423,19 @@ type Config struct { FallbackFilter FallbackFilter Pool *fakeip.Pool Hosts *trie.DomainTrie[netip.Addr] - Policy map[string]NameServer + Policy map[string][]NameServer } func NewResolver(config Config) *Resolver { defaultResolver := &Resolver{ 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{ ipv6: config.IPv6, 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, } @@ -433,7 +450,26 @@ func NewResolver(config Config) *Resolver { if len(config.Policy) != 0 { r.policy = trie.New[*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() } diff --git a/dns/util.go b/dns/util.go index e53abab0..203ab615 100644 --- a/dns/util.go +++ b/dns/util.go @@ -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) { fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout) + domain := msgToDomain(m) for _, client := range clients { r := client fast.Go(func() (*D.Msg, error) { + log.Debugln("[DNS] resolve %s from %s", domain, r.Address()) m, err := r.ExchangeContext(ctx, m) if err != nil { return nil, err } else if m.Rcode == D.RcodeServerFailure || m.Rcode == D.RcodeRefused { return nil, errors.New("server failure") } + log.Debugln("[DNS] %s --> %s, from %s", domain, msgToIP(m), r.Address()) return m, nil }) } @@ -256,7 +259,6 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M } return nil, err } - msg = elm return } diff --git a/docker/file-name.sh b/docker/file-name.sh new file mode 100644 index 00000000..fb87cad0 --- /dev/null +++ b/docker/file-name.sh @@ -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 \ No newline at end of file diff --git a/docs/allocs.svg b/docs/allocs.svg new file mode 100644 index 00000000..57deec50 --- /dev/null +++ b/docs/allocs.svg @@ -0,0 +1,2387 @@ + + + + + + +unnamed + + +cluster_L + + + + +Type: alloc_space + +Type: alloc_space +Time: Jan 30, 2023 at 9:18pm (CST) +Showing nodes accounting for 1777.16MB, 96.71% of 1837.65MB total +Dropped 188 nodes (cum <= 9.19MB) +Dropped 2 edges (freq <= 1.84MB) +Showing top 55 nodes out of 117 +See https://git.io/JfYMW for how to read the graph + + + +N1 + + +quic-go +(*client) +dial +func1 +0 of 1648.40MB (89.70%) + + + + + +N3 + + +quic-go +(*connection) +run +0 of 1648.40MB (89.70%) + + + + + +N1->N3 + + + + + + + 1648.40MB + + + + + +N2 + + +quic-go +(*connection) +sendPacket +0 of 1412.44MB (76.86%) + + + + + +N4 + + +quic-go +(*packetPacker) +PackPacket +30MB (1.63%) +of 956.14MB (52.03%) + + + + + +N2->N4 + + + + + + + 956.14MB + + + + + +N10 + + +quic-go +(*connection) +sendPackedPacket +0 of 456.30MB (24.83%) + + + + + +N2->N10 + + + + + + + 456.30MB + + + + + +N3->N2 + + + + + + + 1412.44MB + + + + + +N36 + + +quic-go +(*connection) +handleUnpackedShortHeaderPacket +0 of 235.46MB (12.81%) + + + + + +N3->N36 + + + + + + + 235.46MB + + + + + +NN4_0 + + + + + +16B..64B + + + + + +N4->NN4_0 + + + + + + + 30MB + + + + + +N6 + + +quic-go +(*packetPacker) +maybeGetShortHeaderPacket +0 of 722.12MB (39.30%) + + + + + +N4->N6 + + + + + + + 722.12MB + + + + + +N13 + + +quic-go +(*packetPacker) +appendPacket +118.51MB (6.45%) +of 204.01MB (11.10%) + + + + + +N4->N13 + + + + + + + 204.01MB + + + + + +N5 + + +quic-go +(*packetPacker) +composeNextPacket +162.51MB (8.84%) +of 446.09MB (24.28%) + + + + + +NN5_0 + + + + + +48B..64B + + + + + +N5->NN5_0 + + + + + + + 93.50MB + + + + + +NN5_1 + + + + + +16B..32B + + + + + +N5->NN5_1 + + + + + + + 69MB + + + + + +N25 + + +quic-go +(*framerI) +AppendStreamFrames +15.50MB (0.84%) +of 157.55MB (8.57%) + + + + + +N5->N25 + + + + + + + 157.55MB + + + + + +N34 + + +ackhandler +(*receivedPacketTracker) +GetAckFrame +0 of 126.04MB (6.86%) + + + + + +N5->N34 + + + + + + + 126.04MB + + + + + +N6->N5 + + + + + + + 446.09MB + + + + + +N8 + + +quic-go +(*packetPacker) +getShortHeader +276.03MB (15.02%) + + + + + +N6->N8 + + + + + + + 276.03MB + + + + + +N7 + + +sync +(*Pool) +Get +0 of 248.65MB (13.53%) + + + + + +N12 + + +ackhandler +glob +func1 +160.01MB (8.71%) + + + + + +N7->N12 + + + + + + + 160.01MB + + + + + +N19 + + +wire +init +0 +func1 +70.60MB (3.84%) + + + + + +N7->N19 + + + + + + + 70.60MB + + + + + +N44 + + +wire +glob +func1 +15MB (0.82%) + + + + + +N7->N44 + + + + + + + 15MB + + + + + +NN8_0 + + + + + +128B + + + + + +N8->NN8_0 + + + + + + + 276.03MB + + + + + +N9 + + +congestion +NewConnectionStateOnSentPacket +236.03MB (12.84%) + + + + + +NN9_0 + + + + + +128B + + + + + +N9->NN9_0 + + + + + + + 236.03MB + + + + + +N10->N7 + + + + + + + 160.01MB + + + + + +N17 + + +ackhandler +(*sentPacketHandler) +SentPacket +0 of 296.29MB (16.12%) + + + + + +N10->N17 + + + + + + + 296.29MB + + + + + +N11 + + +linkedlist +(*List[…]) +insertValue +120MB (6.53%) + + + + + +NN11_0 + + + + + +32B + + + + + +N11->NN11_0 + + + + + + + 113.50MB + + + + + +NN12_0 + + + + + +96B + + + + + +N12->NN12_0 + + + + + + + 160.01MB + + + + + +NN13_0 + + + + + +64B + + + + + +N13->NN13_0 + + + + + + + 118.51MB + + + + + +N18 + + +bytes +NewBuffer +85.50MB (4.65%) + + + + + +N13->N18 + + + + + + + 85.50MB + (inline) + + + + + +N14 + + +ackhandler +(*receivedPacketHistory) +AppendAckRanges +115.54MB (6.29%) + + + + + +NN14_0 + + + + + +512B + + + + + +N14->NN14_0 + + + + + + + 51.53MB + + + + + +NN14_1 + + + + + +256B + + + + + +N14->NN14_1 + + + + + + + 29.51MB + + + + + +NN14_2 + + + + + +128B + + + + + +N14->NN14_2 + + + + + + + 20MB + + + + + +N15 + + +quic-go +(*connection) +handleFrames +0 of 228.96MB (12.46%) + + + + + +N21 + + +wire +(*frameParser) +parseFrame +0 of 109.58MB (5.96%) + + + + + +N15->N21 + + + + + + + 109.58MB + + + + + +N23 + + +quic-go +(*connection) +handleFrame +0 of 119.38MB (6.50%) + + + + + +N15->N23 + + + + + + + 119.38MB + + + + + +N16 + + +quic-go +(*sendStream) +popStreamFrame +111.50MB (6.07%) +of 142.05MB (7.73%) + + + + + +NN16_0 + + + + + +16B + + + + + +N16->NN16_0 + + + + + + + 57MB + + + + + +NN16_1 + + + + + +32B + + + + + +N16->NN16_1 + + + + + + + 54.50MB + + + + + +N29 + + +wire +GetStreamFrame +0 of 70.60MB (3.84%) + + + + + +N16->N29 + + + + + + + 30.54MB + (inline) + + + + + +N17->N9 + + + + + + + 236.03MB + (inline) + + + + + +N17->N11 + + + + + + + 57.50MB + (inline) + + + + + +NN18_0 + + + + + +48B + + + + + +N18->NN18_0 + + + + + + + 85.50MB + + + + + +NN19_0 + + + + + +1.50kB + + + + + +N19->NN19_0 + + + + + + + 70.60MB + + + + + +N20 + + +geodata +LoadGeoSiteMatcher +0 of 74.20MB (4.04%) + + + + + +N33 + + +router +NewMphMatcherGroup +0 of 57.15MB (3.11%) + + + + + +N20->N33 + + + + + + + 57.15MB + + + + + +N40 + + +reflect +New +13MB (0.71%) + + + + + +N20->N40 + + + + + + + 11.50MB + + + + + +N24 + + +wire +parseAckFrame +65.02MB (3.54%) +of 69.52MB (3.78%) + + + + + +N21->N24 + + + + + + + 69.52MB + + + + + +N21->N29 + + + + + + + 40.06MB + (inline) + + + + + +N22 + + +quic-go +(*basicConn) +ReadPacket +35.50MB (1.93%) +of 59.01MB (3.21%) + + + + + +N22->N7 + + + + + + + 2MB + + + + + +NN22_0 + + + + + +96B + + + + + +N22->NN22_0 + + + + + + + 35.50MB + + + + + +N37 + + +net +(*UDPConn) +ReadFrom +18.50MB (1.01%) +of 22MB (1.20%) + + + + + +N22->N37 + + + + + + + 21.50MB + + + + + +N41 + + +ackhandler +(*sentPacketHandler) +ReceivedAck +0 of 90.10MB (4.90%) + + + + + +N23->N41 + + + + + + + 90.10MB + + + + + +N42 + + +quic-go +(*receiveStream) +handleStreamFrameImpl +7MB (0.38%) +of 29.28MB (1.59%) + + + + + +N23->N42 + + + + + + + 29.28MB + + + + + +N24->N7 + + + + + + + 4.50MB + + + + + +NN24_0 + + + + + +512B + + + + + +N24->NN24_0 + + + + + + + 32.52MB + + + + + +NN24_1 + + + + + +256B + + + + + +N24->NN24_1 + + + + + + + 18MB + + + + + +N25->N16 + + + + + + + 142.05MB + + + + + +NN25_0 + + + + + +16B + + + + + +N25->NN25_0 + + + + + + + 15.50MB + + + + + +N26 + + +runtime +main +0 of 79.85MB (4.35%) + + + + + +N52 + + +main +main +0 of 78.85MB (4.29%) + + + + + +N26->N52 + + + + + + + 78.85MB + + + + + +N27 + + +strmatcher +(*MphMatcherGroup) +Build +37.85MB (2.06%) + + + + + +N28 + + +ackhandler +(*sentPacketHandler) +detectLostPackets +func1 +0 of 79.59MB (4.33%) + + + + + +N35 + + +quic-go +(*sendStream) +queueRetransmission +23.59MB (1.28%) + + + + + +N28->N35 + + + + + + + 23.59MB + + + + + +N43 + + +linkedlist +(*List[…]) +InsertAfter +0 of 62.50MB (3.40%) + + + + + +N28->N43 + + + + + + + 56MB + + + + + +N29->N7 + + + + + + + 70.60MB + + + + + +N30 + + +common +NewGEOSITE +0 of 74.20MB (4.04%) + + + + + +N30->N20 + + + + + + + 25.91MB + + + + + +N46 + + +geodata +Verify +0 of 51.44MB (2.80%) + + + + + +N30->N46 + + + + + + + 48.29MB + + + + + +N31 + + +quic-go +(*packetHandlerMap) +listen +0 of 59.01MB (3.21%) + + + + + +N31->N22 + + + + + + + 59.01MB + + + + + +N32 + + +http +HandlerFunc +ServeHTTP +0 of 26.60MB (1.45%) + + + + + +N47 + + +chi +(*Mux) +routeHTTP +0 of 26.60MB (1.45%) + + + + + +N32->N47 + + + + + + + 26.10MB + + + + + +N48 + + +chi +(*Mux) +ServeHTTP +0 of 26.10MB (1.42%) + + + + + +N32->N48 + + + + + + + 25.59MB + + + + + +N33->N27 + + + + + + + 37.85MB + + + + + +N38 + + +strmatcher +(*MphMatcherGroup) +AddFullOrDomainPattern +18.80MB (1.02%) + + + + + +N33->N38 + + + + + + + 18.80MB + (inline) + + + + + +N34->N7 + + + + + + + 10.50MB + + + + + +N34->N14 + + + + + + + 115.54MB + + + + + +N36->N15 + + + + + + + 228.96MB + + + + + +NN37_0 + + + + + +48B + + + + + +N37->NN37_0 + + + + + + + 18.50MB + + + + + +N39 + + +sync +(*poolChain) +pushHead +14.30MB (0.78%) + + + + + +NN40_0 + + + + + +96B + + + + + +N40->NN40_0 + + + + + + + 11.50MB + + + + + +N41->N28 + + + + + + + 79.59MB + + + + + +N41->N39 + + + + + + + 5.51MB + + + + + +NN42_0 + + + + + +16B + + + + + +N42->NN42_0 + + + + + + + 7MB + + + + + +N49 + + +quic-go +(*frameSorter) +push +5.28MB (0.29%) +of 22.28MB (1.21%) + + + + + +N42->N49 + + + + + + + 22.28MB + + + + + +N43->N11 + + + + + + + 62.50MB + (inline) + + + + + +NN44_0 + + + + + +64B + + + + + +N44->NN44_0 + + + + + + + 15MB + + + + + +N45 + + +flate +NewWriter +10.58MB (0.58%) +of 17.16MB (0.93%) + + + + + +NN45_0 + + + + + +648kB + + + + + +N45->NN45_0 + + + + + + + 10.58MB + + + + + +N46->N20 + + + + + + + 48.29MB + + + + + +N47->N32 + + + + + + + 26.60MB + + + + + +N53 + + +pprof +(*Profile) +WriteTo +0 of 22.36MB (1.22%) + + + + + +N47->N53 + + + + + + + 22.36MB + + + + + +N48->N32 + + + + + + + 26.10MB + + + + + +N51 + + +tree +insert[…] +11MB (0.6%) + + + + + +N49->N51 + + + + + + + 11MB + + + + + +N50 + + +http +(*conn) +serve +0 of 24.10MB (1.31%) + + + + + +N50->N48 + + + + + + + 24.10MB + + + + + +NN51_0 + + + + + +48B + + + + + +N51->NN51_0 + + + + + + + 11MB + + + + + +N52->N30 + + + + + + + 74.20MB + + + + + +N54 + + +pprof +writeHeapInternal +0 of 22.36MB (1.22%) + + + + + +N53->N54 + + + + + + + 22.36MB + + + + + +N55 + + +pprof +writeHeapProto +0 of 22.36MB (1.22%) + + + + + +N54->N55 + + + + + + + 22.36MB + + + + + +N55->N45 + + + + + + + 16.28MB + + + + + diff --git a/docs/config.yaml b/docs/config.yaml index 27be2ce7..35e85ecc 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -9,8 +9,25 @@ mixed-port: 10801 # HTTP(S) 和 SOCKS 代理混合端口 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 +#自定义 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 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 experimental: - # 具体配置待定 - # 证书指纹,SHA256格式,补充校验TLS证书 - # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 - fingerprints: - - "8F111FA9AD3CD8E917A118522CAC39EA33741B3BBE73F91CECE548D5CCB0E5E8" # 忽略大小写 + # 类似于 /etc/hosts, 仅支持配置单个 IP hosts: # '*.clash.dev': 127.0.0.1 @@ -84,14 +97,40 @@ ebpf: # 嗅探域名 可选配置 sniffer: 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: - tls - http # 强制对此域名进行嗅探 - force-domain: - - +.v2ex.com + # 仅对白名单中的端口进行嗅探,默认为 443,80 + # 已废弃,若 sniffer.sniff 配置则此项无效 port-whitelist: - "80" - "443" @@ -121,7 +160,7 @@ tunnels: - tcp/udp,127.0.0.1:6553,114.114.114.114:53,proxy - tcp,127.0.0.1:6666,rds.mysql.com:3306,vpn # full yaml config - - network: [ tcp, udp ] + - network: [tcp, udp] address: 127.0.0.1:7777 target: target.com proxy: proxy @@ -201,10 +240,11 @@ dns: # - '+.youtube.com' # 配置查询域名使用的 DNS 服务器 - # nameserver-policy: - # 'www.baidu.com': '114.114.114.114' - # '+.internal.crop.com': '10.0.0.1' - + nameserver-policy: + # 'www.baidu.com': '114.114.114.114' + # '+.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: # Shadowsocks # cipher支持: @@ -219,15 +259,14 @@ proxies: server: server port: 443 cipher: chacha20-ietf-poly1305 - password: - "password" - # udp: true - # udp-over-tcp: false - # ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual - # ipv4:仅使用 IPv4 ipv6:仅使用 IPv6 - # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, - # UDP 则为双栈解析,获取结果中的第一个 IPv4 - # ipv6-prefer 同 ipv4-prefer + password: "password" + # udp: true + # udp-over-tcp: false + # ip-version: ipv4 # 设置节点使用 IP 版本,可选:dual,ipv4,ipv6,ipv4-prefer,ipv6-prefer。默认使用 dual + # ipv4:仅使用 IPv4 ipv6:仅使用 IPv6 + # ipv4-prefer:优先使用 IPv4 对于 TCP 会进行双栈解析,并发链接但是优先使用 IPv4 链接, + # UDP 则为双栈解析,获取结果中的第一个 IPv4 + # ipv6-prefer 同 ipv4-prefer # 现有协议都支持此参数,TCP 效果仅在开启 tcp-concurrent 生效 - name: "ss2" type: ss @@ -250,6 +289,8 @@ proxies: plugin-opts: mode: websocket # no QUIC now # tls: true # wss + # 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取 + # 配置指纹将实现 SSL Pining 效果 # fingerprint: xxxx # skip-cert-verify: true # host: bing.com @@ -281,6 +322,7 @@ proxies: # udp: true # tls: true # 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 # servername: example.com # priority over wss host # network: ws @@ -386,6 +428,7 @@ proxies: server: server port: 443 password: yourpsk + # client-fingerprint: random # Available: "chrome","firefox","safari","random","none" # fingerprint: xxxx # udp: true # sni: example.com # aka server name @@ -445,6 +488,7 @@ proxies: # flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS # skip-cert-verify: true # fingerprint: xxxx + # client-fingerprint: random # Available: "chrome","firefox","safari","random","none" - name: "vless-ws" type: vless @@ -454,6 +498,7 @@ proxies: udp: true tls: true network: ws + # client-fingerprint: random # Available: "chrome","firefox","safari","random","none" servername: example.com # priority over wss host # skip-cert-verify: true # fingerprint: xxxx @@ -497,7 +542,7 @@ proxies: private-key: eCtXsJZ27+4PbhDkHnB923tkUn2Gj59wZw5wFA75MnU= public-key: Cr8hWlKvtDt7nrvf+f0brNQQzabAqrjfBvas9pmowjo= udp: true - + # reserved: 'U4An' - name: tuic server: www.example.com port: 10443 @@ -514,6 +559,7 @@ proxies: # max-udp-relay-packet-size: 1500 # fast-open: true # skip-cert-verify: true + # max-open-streams: 20 # default 100, too many open streams may hurt performance # ShadowsocksR # The supported ciphers (encryption methods): all stream ciphers in ss @@ -555,7 +601,7 @@ proxy-groups: - vmess1 # tolerance: 150 # lazy: true - url: "http://www.gstatic.com/generate_204" + url: "https://cp.cloudflare.com/generate_204" interval: 300 # fallback 将按照 url 测试结果按照节点顺序选择 @@ -565,7 +611,7 @@ proxy-groups: - ss1 - ss2 - vmess1 - url: "http://www.gstatic.com/generate_204" + url: "https://cp.cloudflare.com/generate_204" interval: 300 # load-balance 将按照算法随机选择节点 @@ -575,7 +621,7 @@ proxy-groups: - ss1 - ss2 - vmess1 - url: "http://www.gstatic.com/generate_204" + url: "https://cp.cloudflare.com/generate_204" interval: 300 # strategy: consistent-hashing # 可选 round-robin 和 sticky-sessions @@ -617,14 +663,14 @@ proxy-providers: enable: true interval: 600 # lazy: true - url: http://www.gstatic.com/generate_204 + url: https://cp.cloudflare.com/generate_204 test: type: file path: /test.yaml health-check: enable: true interval: 36000 - url: http://www.gstatic.com/generate_204 + url: https://cp.cloudflare.com/generate_204 rule-providers: rule1: behavior: classical # domain ipcidr @@ -673,6 +719,11 @@ sub-rules: tls: certificate: string # 证书 PEM 格式,或者 证书的路径 private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径 + # 自定义证书验证,将加入 Clash 证书验证中,绝大多数 TLS 相关支持,如:DNS + # 可用于自定义证书的验证 + custom-certificates: + - certificate: string # 证书 PEM 格式,或者 证书的路径 + private-key: string # 证书对应的私钥 PEM 格式,或者私钥路径 # 流量入站 listeners: @@ -757,7 +808,7 @@ listeners: listen: 0.0.0.0 # rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules # proxy: proxy # 如果不为空则直接将该入站流量交由指定proxy处理(当proxy不为空时,这里的proxy名称必须合法,否则会出错) - network: [ tcp, udp ] + network: [tcp, udp] target: target.com - name: tun-in-1 @@ -801,4 +852,3 @@ listeners: # - com.android.chrome # exclude_package: # 排除被路由的 Android 应用包名 # - com.android.captiveportallogin - diff --git a/docs/heap.svg b/docs/heap.svg new file mode 100644 index 00000000..0795977f --- /dev/null +++ b/docs/heap.svg @@ -0,0 +1,2182 @@ + + + + + + +unnamed + + +cluster_L + + + + +Type: inuse_space + +Type: inuse_space +Time: Jan 30, 2023 at 9:18pm (CST) +Showing nodes accounting for 12146.04kB, 100% of 12146.04kB total +Showing top 69 nodes out of 71 +See https://git.io/JfYMW for how to read the graph + + + +N1 + + +runtime +allocm +5637.50kB (46.41%) + + + + + +NN1_0 + + + + + +1kB + + + + + +N1->NN1_0 + + + + + + + 5637.50kB + + + + + +N2 + + +runtime +schedule +0 of 5637.50kB (46.41%) + + + + + +N68 + + +runtime +resetspinning +0 of 5637.50kB (46.41%) + + + + + +N2->N68 + + + + + + + 5637.50kB + + + + + +N3 + + +runtime +mstart +0 of 3587.50kB (29.54%) + + + + + +N62 + + +runtime +mstart0 +0 of 3587.50kB (29.54%) + + + + + +N3->N62 + + + + + + + 3587.50kB + + + + + +N4 + + +runtime +main +0 of 2836.92kB (23.36%) + + + + + +N61 + + +main +main +0 of 2836.92kB (23.36%) + + + + + +N4->N61 + + + + + + + 2836.92kB + + + + + +N5 + + +geodata +LoadGeoSiteMatcher +0 of 2836.92kB (23.36%) + + + + + +N10 + + +router +NewMphMatcherGroup +0 of 1812.91kB (14.93%) + + + + + +N5->N10 + + + + + + + 1812.91kB + + + + + +N27 + + +geodata +(*loader) +LoadGeoSite +0 of 1024.02kB (8.43%) + + + + + +N5->N27 + + + + + + + 1024.02kB + + + + + +N6 + + +strmatcher +(*MphMatcherGroup) +Build +1300.89kB (10.71%) + + + + + +NN6_0 + + + + + +1.14MB + + + + + +N6->NN6_0 + + + + + + + 1300.89kB + + + + + +N7 + + +runtime +mcall +0 of 2050kB (16.88%) + + + + + +N67 + + +runtime +park_m +0 of 2050kB (16.88%) + + + + + +N7->N67 + + + + + + + 2050kB + + + + + +N8 + + +impl +(*MessageInfo) +unmarshalPointer +0 of 1024.02kB (8.43%) + + + + + +N9 + + +impl +consumeStringValidateUTF8 +1024.02kB (8.43%) + + + + + +N8->N9 + + + + + + + 1024.02kB + + + + + +N58 + + +impl +consumeMessageSliceInfo +0 of 1024.02kB (8.43%) + + + + + +N8->N58 + + + + + + + 1024.02kB + + + + + +NN9_0 + + + + + +16B + + + + + +N9->NN9_0 + + + + + + + 1024.02kB + + + + + +N10->N6 + + + + + + + 1300.89kB + + + + + +N31 + + +strmatcher +(*MphMatcherGroup) +AddPattern +0 of 512.01kB (4.22%) + + + + + +N10->N31 + + + + + + + 512.01kB + + + + + +N11 + + +runtime +gcBgMarkWorker +512.02kB (4.22%) + + + + + +NN11_0 + + + + + +32B + + + + + +N11->NN11_0 + + + + + + + 512.02kB + + + + + +N12 + + +congestion +(*ConnectionStates) +Insert +596.16kB (4.91%) + + + + + +NN12_0 + + + + + +160kB + + + + + +N12->NN12_0 + + + + + + + 596.16kB + + + + + +N13 + + +bufio +NewReaderSize +514kB (4.23%) + + + + + +NN13_0 + + + + + +4kB + + + + + +N13->NN13_0 + + + + + + + 514kB + + + + + +N14 + + +quic-go +init +0 +func1 +512.75kB (4.22%) + + + + + +NN14_0 + + + + + +1.50kB + + + + + +N14->NN14_0 + + + + + + + 512.75kB + + + + + +N15 + + +trie +(*Node[…]) +optimize +512.44kB (4.22%) + + + + + +NN15_0 + + + + + +896B + + + + + +N15->NN15_0 + + + + + + + 512.44kB + + + + + +N16 + + +runtime +malg +512.20kB (4.22%) + + + + + +NN16_0 + + + + + +416B + + + + + +N16->NN16_0 + + + + + + + 512.20kB + + + + + +N17 + + +time +NewTicker +512.05kB (4.22%) + + + + + +NN17_0 + + + + + +96B + + + + + +N17->NN17_0 + + + + + + + 512.05kB + + + + + +N18 + + +strmatcher +(*MphMatcherGroup) +AddFullOrDomainPattern +512.01kB (4.22%) + + + + + +NN18_0 + + + + + +24B + + + + + +N18->NN18_0 + + + + + + + 512.01kB + + + + + +N19 + + +quic-go +(*client) +dial +func1 +0 of 596.16kB (4.91%) + + + + + +N49 + + +quic-go +(*connection) +run +0 of 596.16kB (4.91%) + + + + + +N19->N49 + + + + + + + 596.16kB + + + + + +N20 + + +mixed +handleConn +0 of 514kB (4.23%) + + + + + +N26 + + +net +NewBufferedConn +0 of 514kB (4.23%) + + + + + +N20->N26 + + + + + + + 514kB + + + + + +N21 + + +quic-go +(*packetHandlerMap) +listen +0 of 512.75kB (4.22%) + + + + + +N48 + + +quic-go +(*basicConn) +ReadPacket +0 of 512.75kB (4.22%) + + + + + +N21->N48 + + + + + + + 512.75kB + + + + + +N22 + + +executor +loadRuleProvider +func1 +0 of 512.44kB (4.22%) + + + + + +N40 + + +executor +loadProvider +0 of 512.44kB (4.22%) + + + + + +N22->N40 + + + + + + + 512.44kB + + + + + +N23 + + +runtime +systemstack +0 of 512.20kB (4.22%) + + + + + +N65 + + +runtime +newproc +func1 +0 of 512.20kB (4.22%) + + + + + +N23->N65 + + + + + + + 512.20kB + + + + + +N24 + + +statistic +(*Manager) +handle +0 of 512.05kB (4.22%) + + + + + +N24->N17 + + + + + + + 512.05kB + + + + + +N25 + + +bufio +NewReader +0 of 514kB (4.23%) + + + + + +N25->N13 + + + + + + + 514kB + (inline) + + + + + +N26->N25 + + + + + + + 514kB + (inline) + + + + + +N28 + + +geodata +(*loader) +LoadGeoSiteWithAttr +0 of 1024.02kB (8.43%) + + + + + +N27->N28 + + + + + + + 1024.02kB + + + + + +N29 + + +memconservative +(*memConservativeLoader) +LoadSiteByPath +0 of 1024.02kB (8.43%) + + + + + +N28->N29 + + + + + + + 1024.02kB + + + + + +N30 + + +memconservative +GeoSiteCache +Unmarshal +0 of 1024.02kB (8.43%) + + + + + +N29->N30 + + + + + + + 1024.02kB + + + + + +N59 + + +proto +Unmarshal +0 of 1024.02kB (8.43%) + + + + + +N30->N59 + + + + + + + 1024.02kB + + + + + +N31->N18 + + + + + + + 512.01kB + (inline) + + + + + +N32 + + +trie +(*DomainTrie[…]) +Optimize +0 of 512.44kB (4.22%) + + + + + +N32->N15 + + + + + + + 512.44kB + + + + + +N33 + + +config +Parse +0 of 2836.92kB (23.36%) + + + + + +N34 + + +config +ParseRawConfig +0 of 2836.92kB (23.36%) + + + + + +N33->N34 + + + + + + + 2836.92kB + + + + + +N35 + + +config +parseRules +0 of 2836.92kB (23.36%) + + + + + +N34->N35 + + + + + + + 2836.92kB + + + + + +N41 + + +rules +ParseRule +0 of 2836.92kB (23.36%) + + + + + +N35->N41 + + + + + + + 2836.92kB + + + + + +N36 + + +hub +Parse +0 of 2836.92kB (23.36%) + + + + + +N37 + + +executor +Parse +0 of 2836.92kB (23.36%) + + + + + +N36->N37 + + + + + + + 2836.92kB + (inline) + + + + + +N39 + + +executor +ParseWithPath +0 of 2836.92kB (23.36%) + + + + + +N37->N39 + + + + + + + 2836.92kB + + + + + +N38 + + +executor +ParseWithBytes +0 of 2836.92kB (23.36%) + + + + + +N38->N33 + + + + + + + 2836.92kB + + + + + +N39->N38 + + + + + + + 2836.92kB + (inline) + + + + + +N44 + + +provider +(*ruleSetProvider) +Initial +0 of 512.44kB (4.22%) + + + + + +N40->N44 + + + + + + + 512.44kB + + + + + +N42 + + +common +NewGEOSITE +0 of 2836.92kB (23.36%) + + + + + +N41->N42 + + + + + + + 2836.92kB + + + + + +N42->N5 + + + + + + + 2836.92kB + + + + + +N43 + + +provider +(*domainStrategy) +OnUpdate +0 of 512.44kB (4.22%) + + + + + +N43->N32 + + + + + + + 512.44kB + + + + + +N45 + + +provider +NewRuleSetProvider +func1 +0 of 512.44kB (4.22%) + + + + + +N44->N45 + + + + + + + 512.44kB + + + + + +N45->N43 + + + + + + + 512.44kB + + + + + +N46 + + +congestion +(*BandwidthSampler) +OnPacketSent +0 of 596.16kB (4.91%) + + + + + +N46->N12 + + + + + + + 596.16kB + (inline) + + + + + +N47 + + +congestion +(*bbrSender) +OnPacketSent +0 of 596.16kB (4.91%) + + + + + +N47->N46 + + + + + + + 596.16kB + + + + + +N53 + + +quic-go +getPacketBuffer +0 of 512.75kB (4.22%) + + + + + +N48->N53 + + + + + + + 512.75kB + (inline) + + + + + +N52 + + +quic-go +(*connection) +sendPackets +0 of 596.16kB (4.91%) + + + + + +N49->N52 + + + + + + + 596.16kB + + + + + +N50 + + +quic-go +(*connection) +sendPackedPacket +0 of 596.16kB (4.91%) + + + + + +N55 + + +ackhandler +(*sentPacketHandler) +SentPacket +0 of 596.16kB (4.91%) + + + + + +N50->N55 + + + + + + + 596.16kB + + + + + +N51 + + +quic-go +(*connection) +sendPacket +0 of 596.16kB (4.91%) + + + + + +N51->N50 + + + + + + + 596.16kB + + + + + +N52->N51 + + + + + + + 596.16kB + + + + + +N53->N14 + + + + + + + 512.75kB + + + + + +N54 + + +ackhandler +(*ccAdapter) +OnPacketSent +0 of 596.16kB (4.91%) + + + + + +N54->N47 + + + + + + + 596.16kB + + + + + +N56 + + +ackhandler +(*sentPacketHandler) +sentPacketImpl +0 of 596.16kB (4.91%) + + + + + +N55->N56 + + + + + + + 596.16kB + + + + + +N56->N54 + + + + + + + 596.16kB + + + + + +N57 + + +impl +(*MessageInfo) +unmarshal +0 of 1024.02kB (8.43%) + + + + + +N57->N8 + + + + + + + 1024.02kB + + + + + +N58->N8 + + + + + + + 1024.02kB + + + + + +N60 + + +proto +UnmarshalOptions +unmarshal +0 of 1024.02kB (8.43%) + + + + + +N59->N60 + + + + + + + 1024.02kB + + + + + +N60->N57 + + + + + + + 1024.02kB + + + + + +N61->N36 + + + + + + + 2836.92kB + + + + + +N63 + + +runtime +mstart1 +0 of 3587.50kB (29.54%) + + + + + +N62->N63 + + + + + + + 3587.50kB + + + + + +N63->N2 + + + + + + + 3587.50kB + + + + + +N64 + + +runtime +newm +0 of 5637.50kB (46.41%) + + + + + +N64->N1 + + + + + + + 5637.50kB + + + + + +N66 + + +runtime +newproc1 +0 of 512.20kB (4.22%) + + + + + +N65->N66 + + + + + + + 512.20kB + + + + + +N66->N16 + + + + + + + 512.20kB + + + + + +N67->N2 + + + + + + + 2050kB + + + + + +N69 + + +runtime +startm +0 of 5637.50kB (46.41%) + + + + + +N68->N69 + + + + + + + 5637.50kB + + + + + +N69->N64 + + + + + + + 5637.50kB + + + + + diff --git a/go.mod b/go.mod index 3eb1fd91..c2a979dc 100644 --- a/go.mod +++ b/go.mod @@ -6,41 +6,43 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da github.com/cilium/ebpf v0.9.3 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/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/render v1.0.2 github.com/gofrs/uuid v4.3.1+incompatible github.com/google/gopacket v1.1.19 github.com/gorilla/websocket v1.5.0 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/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 - github.com/mdlayher/netlink v1.7.0 - github.com/metacubex/quic-go v0.31.1-0.20221127023445-9f0ce65a734e - github.com/metacubex/sing-shadowsocks v0.1.0 - github.com/metacubex/sing-tun v0.1.0 - github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c + github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7 + github.com/metacubex/quic-go v0.32.0 + github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7 + github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b + github.com/metacubex/sing-wireguard v0.0.0-20230213124601-d04406a109b4 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/refraction-networking/utls v1.2.0 github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 - github.com/sagernet/sing v0.1.0 - github.com/sagernet/sing-vmess v0.1.0 - github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c - github.com/samber/lo v1.35.0 + github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9 + github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb + github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d + 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/stretchr/testify v1.8.1 github.com/xtls/go v0.0.0-20220914232946-0441cf4cf837 go.etcd.io/bbolt v1.3.6 go.uber.org/atomic v1.10.0 go.uber.org/automaxprocs v1.5.1 - golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a - golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 - golang.org/x/net v0.2.1-0.20221117215542-ecf7fda6a59e + golang.org/x/crypto v0.5.0 + golang.org/x/exp v0.0.0-20221205204356-47842c84f3db + golang.org/x/net v0.5.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 gopkg.in/yaml.v3 v3.0.1 lukechampine.com/blake3 v1.1.7 @@ -48,6 +50,7 @@ require ( require ( 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/fsnotify/fsnotify v1.6.0 // 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/go-cmp v0.5.9 // 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/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/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e // indirect github.com/onsi/ginkgo/v2 v2.2.0 // indirect github.com/oschwald/maxminddb-golang v1.10.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/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 - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/mod v0.6.0 // indirect + golang.org/x/text v0.6.0 // indirect golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect - golang.org/x/tools v0.1.12 // indirect - gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect + golang.org/x/tools v0.2.0 // 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 diff --git a/go.sum b/go.sum index cc305000..076442ab 100644 --- a/go.sum +++ b/go.sum @@ -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/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= 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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 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/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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 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.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= +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/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 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/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/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c h1:OCFM4+DXTWfNlyeoddrTwdup/ztkGSyAMR2UGcPckNQ= -github.com/insomniacslk/dhcp v0.0.0-20221001123530-5308ebe5334c/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= -github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= -github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg= +github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE= +github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/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/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 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-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= 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.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE= 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/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/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/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.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= 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.0/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= +github.com/mdlayher/netlink v1.7.2-0.20221213171556-9881fafed8c7 h1:HSkXG1bE/qcRuuPlZ2Jyf0Od8HLxOowi7CzKQqNtWn4= +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-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/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= -github.com/metacubex/gvisor v0.0.0-20221217030112-bdcd835fd60e h1:3PHqNvIAwYbv9cOQbRFIUgzJ+K6fhV1HHj+Vpg8U7g8= -github.com/metacubex/gvisor v0.0.0-20221217030112-bdcd835fd60e/go.mod h1:TIvkJD0sxe8pIob3p6T8IzxXunlp6yfgktvTNp+DGNM= -github.com/metacubex/quic-go v0.31.1-0.20221127023445-9f0ce65a734e h1:RnfC6+sShJ3biU2Q2wuh4FxZ8/3fp1QG+1zAfswVehA= -github.com/metacubex/quic-go v0.31.1-0.20221127023445-9f0ce65a734e/go.mod h1:7NPWVTLiX2Ss9q9gBNZaNHsPqZ3Tg/ApyrXxxUYbl78= -github.com/metacubex/sing-shadowsocks v0.1.0 h1:uGBtNkpy4QFlofaNkJf+iFegeLU11VzTUlkC46FHF8A= -github.com/metacubex/sing-shadowsocks v0.1.0/go.mod h1:8pBSYDKVxTtqUtGZyEh4ZpFJXwP6wBVVKrs6oQiOwmQ= -github.com/metacubex/sing-tun v0.1.0 h1:iQj0+0WjJynSKAtfv87wOZlVKWl3w9RvkOSkVe9zuMg= -github.com/metacubex/sing-tun v0.1.0/go.mod h1:l4JyI6RTrlHLQz5vSakg+wxA+LwGVI0Mz5ZtlOv67dA= -github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c h1:VHtXDny/TNOF7YDT9d9Qkr+x6K1O4cejXLlyPUXDeXQ= -github.com/metacubex/sing-wireguard v0.0.0-20221109114053-16c22adda03c/go.mod h1:fULJ451x1/XlpIhl+Oo+EPGKla9tFZaqT5dKLrZ+NvM= +github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e h1:j4j2dlV2d//FAsQlRUriH6nvv36AEAhECbNy7narf1M= +github.com/metacubex/gvisor v0.0.0-20230213124051-7a16c835d80e/go.mod h1:abc7OdNmWlhcNHz84ECEosd5ND5pnWQmD8W55p/4cuc= +github.com/metacubex/quic-go v0.32.0 h1:dSD8LB4MSeBuD4otd8y1DUZcRdDcEB0Ax5esPOqn2Hw= +github.com/metacubex/quic-go v0.32.0/go.mod h1:yParIzDYUd/t/pzFlDtZKhnvSqbUu0bPChlKEGmJStA= +github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7 h1:MNCGIpXhxXn9ck5bxfm/cW9Nr2FGQ5cakcGK0yKZcak= +github.com/metacubex/sing-shadowsocks v0.1.1-0.20230202072246-e2bef5f088c7/go.mod h1:8pBSYDKVxTtqUtGZyEh4ZpFJXwP6wBVVKrs6oQiOwmQ= +github.com/metacubex/sing-tun v0.1.1-0.20230213124625-28d27a0c236b h1:ZF/oNrSCaxIFoZmFQCiUx67t9aENZjyuqw2n4zw3L2o= +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-20230213124601-d04406a109b4 h1:d96mCF/LYyC9kULd2xwcXfP0Jd8klrOngmRxuUIZg/8= +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/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/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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= @@ -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/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.1.0 h1:FGmaP2BVPYO2IyC/3R1DaQa/zr+kOKHRgWqrmOF+Gu8= -github.com/sagernet/sing v0.1.0/go.mod h1:zvgDYKI+vCAW9RyfyrKTgleI+DOa8lzHMPC7VZo3OL4= -github.com/sagernet/sing-vmess v0.1.0 h1:x0tYBJRbVi7zVXpMEW45eApGpXIDs9ub3raglouAKMo= -github.com/sagernet/sing-vmess v0.1.0/go.mod h1:4lwj6EHrUlgRnKhbmtboGbt+wtl5+tHMv96Ez8LZArw= -github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c h1:qP3ZOHnjZalvqbjundbXiv/YrNlo3HOgrKc+S1QGs0U= -github.com/sagernet/wireguard-go v0.0.0-20221108054404-7c2acadba17c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI= -github.com/samber/lo v1.35.0 h1:GlT8CV1GE+v97Y7MLF1wXvX6mjoxZ+hi61tj/ZcQwY0= -github.com/samber/lo v1.35.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= +github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9 h1:qnXh4RjHsNjdZXkfbqwVqAzYUfc160gfkS5gepmsA+A= +github.com/sagernet/sing v0.1.7-0.20230207063819-27d2950cdbe9/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w= +github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb h1:oyd3w17fXNmWVYFUe17YVHJW5CLW9X2mxJFDP/IWrAM= +github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb/go.mod h1:9KkmnQzTL4Gvv8U2TRAH2BOITCGsGPpHtUPP5sxn5sY= +github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk= +github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g= +github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo= +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/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 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-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA= -github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww= +github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E= 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/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= 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.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/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4= -golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= +golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= +golang.org/x/exp v0.0.0-20221205204356-47842c84f3db h1:D/cFflL63o2KSLJIwjlcIt8PR064j/xsmdEJL/YvY/o= +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/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.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +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-20190404232315-eb5bcb51f2a3/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 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.2.1-0.20221117215542-ecf7fda6a59e/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw= +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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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-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-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-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.2.1-0.20221110211117-d684c6f88669 h1:pvmSpBoSG0gD2LLPAX15QHPig8xsbU0tu1sSAmResqk= -golang.org/x/sys v0.2.1-0.20221110211117-d684c6f88669/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= +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/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k= +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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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.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.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +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-20191011141410-1b5146add898/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/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 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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 55786864..b3e33f98 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -18,7 +18,7 @@ import ( "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/resolver" 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/config" C "github.com/Dreamacro/clash/constant" @@ -137,8 +137,9 @@ func GetGeneral() *config.General { func updateListeners(listeners map[string]C.InboundListener) { tcpIn := tunnel.TCPIn() udpIn := tunnel.UDPIn() + natTable := tunnel.NatTable() - listener.PatchInboundListeners(listeners, tcpIn, udpIn, true) + listener.PatchInboundListeners(listeners, tcpIn, udpIn, natTable, true) } func updateExperimental(c *config.Config) { @@ -146,29 +147,25 @@ func updateExperimental(c *config.Config) { } func preUpdateExperimental(c *config.Config) { - for _, fingerprint := range c.Experimental.Fingerprints { - if err := tls.AddCertFingerprint(fingerprint); err != nil { - log.Warnln("fingerprint[%s] is err, %s", fingerprint, err.Error()) - } + CTLS.AddCertificate(c.TLS.PrivateKey, c.TLS.Certificate) + for _, c := range c.TLS.CustomTrustCert { + CTLS.AddCertificate(c.PrivateKey, c.Certificate) } } func updateDNS(c *config.DNS, generalIPv6 bool) { if !c.Enable { - resolver.DisableIPv6 = !generalIPv6 resolver.DefaultResolver = nil resolver.DefaultHostMapper = nil resolver.DefaultLocalServer = nil dns.ReCreateServer("", nil, nil) return - } else { - resolver.DisableIPv6 = !c.IPv6 } cfg := dns.Config{ Main: c.NameServer, Fallback: c.Fallback, - IPv6: c.IPv6, + IPv6: c.IPv6 && generalIPv6, EnhancedMode: c.EnhancedMode, Pool: c.FakeIPRange, Hosts: c.Hosts, @@ -227,11 +224,11 @@ func loadProvider(pv provider.Provider) { switch pv.Type() { 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: { - 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) { if sniffer.Enable { dispatcher, err := SNI.NewSnifferDispatcher( - sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.Ports, + sniffer.Sniffers, sniffer.ForceDomain, sniffer.SkipDomain, sniffer.ForceDnsMapping, sniffer.ParsePureIp, ) if err != nil { @@ -309,13 +306,12 @@ func updateTunnels(tunnels []LC.Tunnel) { func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) - tunnel.SetFindProcessMode(general.EnableProcess, general.FindProcessMode) + tunnel.SetFindProcessMode(general.FindProcessMode) dialer.DisableIPv6 = !general.IPv6 if !dialer.DisableIPv6 { log.Infoln("Use IPv6") - } else { - resolver.DisableIPv6 = true } + resolver.DisableIPv6 = dialer.DisableIPv6 if general.TCPConcurrent { dialer.SetDial(general.TCPConcurrent) @@ -353,12 +349,13 @@ func updateGeneral(general *config.General, force bool) { tcpIn := tunnel.TCPIn() udpIn := tunnel.UDPIn() + natTable := tunnel.NatTable() listener.ReCreateHTTP(general.Port, tcpIn) 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.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn) + listener.ReCreateTProxy(general.TProxyPort, tcpIn, udpIn, natTable) listener.ReCreateMixed(general.MixedPort, tcpIn, udpIn) listener.ReCreateShadowSocks(general.ShadowSocksConfig, tcpIn, udpIn) listener.ReCreateVmess(general.VmessConfig, tcpIn, udpIn) diff --git a/hub/hub.go b/hub/hub.go index ee18e70a..1e925bfe 100644 --- a/hub/hub.go +++ b/hub/hub.go @@ -42,8 +42,8 @@ func Parse(options ...Option) error { } if cfg.General.ExternalController != "" { - go route.Start(cfg.General.ExternalController,cfg.General.ExternalControllerTLS, - cfg.General.Secret,cfg.TLS.Certificate,cfg.TLS.PrivateKey) + go route.Start(cfg.General.ExternalController, cfg.General.ExternalControllerTLS, + cfg.General.Secret, cfg.TLS.Certificate, cfg.TLS.PrivateKey) } executor.ApplyConfig(cfg, true) diff --git a/hub/route/configs.go b/hub/route/configs.go index 37acac5e..9e630b29 100644 --- a/hub/route/configs.go +++ b/hub/route/configs.go @@ -104,7 +104,6 @@ func pointerOrDefault(p *int, def int) int { if p != nil { return *p } - return def } @@ -210,7 +209,7 @@ func pointerOrDefaultTuicServer(p *tuicServerSchema, def LC.TuicServer) LC.TuicS func patchConfigs(w http.ResponseWriter, r *http.Request) { 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.JSON(w, r, ErrBadRequest) return @@ -240,11 +239,12 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) { tcpIn := tunnel.TCPIn() udpIn := tunnel.UDPIn() + natTable := tunnel.NatTable() P.ReCreateHTTP(pointerOrDefault(general.Port, ports.Port), tcpIn) P.ReCreateSocks(pointerOrDefault(general.SocksPort, ports.SocksPort), tcpIn, udpIn) - P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn) - P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn) + P.ReCreateRedir(pointerOrDefault(general.RedirPort, ports.RedirPort), tcpIn, udpIn, natTable) + P.ReCreateTProxy(pointerOrDefault(general.TProxyPort, ports.TProxyPort), tcpIn, udpIn, natTable) P.ReCreateMixed(pointerOrDefault(general.MixedPort, ports.MixedPort), tcpIn, udpIn) P.ReCreateTun(pointerOrDefaultTun(general.Tun, P.LastTunConf), 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) } -type updateConfigRequest struct { - Path string `json:"path"` - Payload string `json:"payload"` -} - 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 { render.Status(r, http.StatusBadRequest) render.JSON(w, r, ErrBadRequest) diff --git a/hub/route/dns.go b/hub/route/dns.go new file mode 100644 index 00000000..2918b059 --- /dev/null +++ b/hub/route/dns.go @@ -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) +} diff --git a/hub/route/proxies.go b/hub/route/proxies.go index baffb1f5..5bf6eb9c 100644 --- a/hub/route/proxies.go +++ b/hub/route/proxies.go @@ -70,12 +70,10 @@ func getProxy(w http.ResponseWriter, r *http.Request) { render.JSON(w, r, proxy) } -type UpdateProxyRequest struct { - Name string `json:"name"` -} - func updateProxy(w http.ResponseWriter, r *http.Request) { - req := UpdateProxyRequest{} + req := struct { + Name string `json:"name"` + }{} if err := render.DecodeJSON(r.Body, &req); err != nil { render.Status(r, http.StatusBadRequest) render.JSON(w, r, ErrBadRequest) diff --git a/hub/route/server.go b/hub/route/server.go index 2bef92c1..0d6a47ac 100644 --- a/hub/route/server.go +++ b/hub/route/server.go @@ -73,6 +73,7 @@ func Start(addr string, tlsAddr string, secret string, r.Mount("/providers/proxies", proxyProviderRouter()) r.Mount("/providers/rules", ruleProviderRouter()) r.Mount("/cache", cacheRouter()) + r.Mount("/dns", dnsRouter()) }) if uiPath != "" { diff --git a/listener/inbound/base.go b/listener/inbound/base.go index 41be5b10..b132ac6c 100644 --- a/listener/inbound/base.go +++ b/listener/inbound/base.go @@ -61,7 +61,7 @@ func (b *Base) RawAddress() string { } // Listen implements constant.InboundListener -func (*Base) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (*Base) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { return nil } diff --git a/listener/inbound/http.go b/listener/inbound/http.go index b19f0154..a93f9684 100644 --- a/listener/inbound/http.go +++ b/listener/inbound/http.go @@ -42,7 +42,7 @@ func (h *HTTP) Address() string { } // Listen implements constant.InboundListener -func (h *HTTP) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (h *HTTP) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error h.l, err = http.New(h.RawAddress(), tcpIn, h.Additions()...) if err != nil { diff --git a/listener/inbound/mixed.go b/listener/inbound/mixed.go index a2920c69..dbba264c 100644 --- a/listener/inbound/mixed.go +++ b/listener/inbound/mixed.go @@ -50,7 +50,7 @@ func (m *Mixed) Address() string { } // Listen implements constant.InboundListener -func (m *Mixed) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (m *Mixed) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error m.l, err = mixed.New(m.RawAddress(), tcpIn, m.Additions()...) if err != nil { diff --git a/listener/inbound/redir.go b/listener/inbound/redir.go index 7a1685ba..4b88d895 100644 --- a/listener/inbound/redir.go +++ b/listener/inbound/redir.go @@ -42,7 +42,7 @@ func (r *Redir) Address() string { } // Listen implements constant.InboundListener -func (r *Redir) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (r *Redir) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error r.l, err = redir.New(r.RawAddress(), tcpIn, r.Additions()...) if err != nil { diff --git a/listener/inbound/shadowsocks.go b/listener/inbound/shadowsocks.go index e6baa80c..40907485 100644 --- a/listener/inbound/shadowsocks.go +++ b/listener/inbound/shadowsocks.go @@ -57,7 +57,7 @@ func (s *ShadowSocks) Address() string { } // Listen implements constant.InboundListener -func (s *ShadowSocks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (s *ShadowSocks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error s.l, err = sing_shadowsocks.New(s.ss, tcpIn, udpIn, s.Additions()...) if err != nil { diff --git a/listener/inbound/socks.go b/listener/inbound/socks.go index 010d08f9..aac2ee23 100644 --- a/listener/inbound/socks.go +++ b/listener/inbound/socks.go @@ -68,7 +68,7 @@ func (s *Socks) Address() string { } // Listen implements constant.InboundListener -func (s *Socks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (s *Socks) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error if s.stl, err = socks.New(s.RawAddress(), tcpIn, s.Additions()...); err != nil { return err diff --git a/listener/inbound/tproxy.go b/listener/inbound/tproxy.go index 7aa8af8d..fa458d2c 100644 --- a/listener/inbound/tproxy.go +++ b/listener/inbound/tproxy.go @@ -49,7 +49,7 @@ func (t *TProxy) Address() string { } // Listen implements constant.InboundListener -func (t *TProxy) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (t *TProxy) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error t.lTCP, err = tproxy.New(t.RawAddress(), tcpIn, t.Additions()...) if err != nil { @@ -57,7 +57,7 @@ func (t *TProxy) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter } if t.udp { if t.lUDP != nil { - t.lUDP, err = tproxy.NewUDP(t.RawAddress(), udpIn, t.Additions()...) + t.lUDP, err = tproxy.NewUDP(t.RawAddress(), udpIn, natTable, t.Additions()...) if err != nil { return err } diff --git a/listener/inbound/tuic.go b/listener/inbound/tuic.go index f3b8a5f2..f6641500 100644 --- a/listener/inbound/tuic.go +++ b/listener/inbound/tuic.go @@ -12,11 +12,11 @@ type TuicOption struct { Token []string `inbound:"token"` Certificate string `inbound:"certificate"` PrivateKey string `inbound:"private-key"` - CongestionController string `inbound:"congestion-controllerr,omitempty"` - MaxIdleTime int `inbound:"max-idle-timer,omitempty"` - AuthenticationTimeout int `inbound:"authentication-timeoutr,omitempty"` - ALPN []string `inbound:"alpnr,omitempty"` - MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-sizer,omitempty"` + CongestionController string `inbound:"congestion-controller,omitempty"` + MaxIdleTime int `inbound:"max-idle-time,omitempty"` + AuthenticationTimeout int `inbound:"authentication-timeout,omitempty"` + ALPN []string `inbound:"alpn,omitempty"` + MaxUdpRelayPacketSize int `inbound:"max-udp-relay-packet-size,omitempty"` } func (o TuicOption) Equal(config C.InboundConfig) bool { @@ -69,7 +69,7 @@ func (t *Tuic) Address() string { } // Listen implements constant.InboundListener -func (t *Tuic) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (t *Tuic) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error t.l, err = tuic.New(t.ts, tcpIn, udpIn, t.Additions()...) if err != nil { diff --git a/listener/inbound/tun.go b/listener/inbound/tun.go index 997164c2..ad215989 100644 --- a/listener/inbound/tun.go +++ b/listener/inbound/tun.go @@ -111,7 +111,7 @@ func (t *Tun) Address() string { } // Listen implements constant.InboundListener -func (t *Tun) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (t *Tun) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error t.l, err = sing_tun.New(t.tun, tcpIn, udpIn, t.Additions()...) if err != nil { diff --git a/listener/inbound/tunnel.go b/listener/inbound/tunnel.go index 221f4cd6..41d024ef 100644 --- a/listener/inbound/tunnel.go +++ b/listener/inbound/tunnel.go @@ -74,7 +74,7 @@ func (t *Tunnel) Address() string { } // Listen implements constant.InboundListener -func (t *Tunnel) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (t *Tunnel) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error for _, network := range t.config.Network { switch network { diff --git a/listener/inbound/vmess.go b/listener/inbound/vmess.go index 130e17c5..70e840a5 100644 --- a/listener/inbound/vmess.go +++ b/listener/inbound/vmess.go @@ -69,7 +69,7 @@ func (v *Vmess) Address() string { } // Listen implements constant.InboundListener -func (v *Vmess) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) error { +func (v *Vmess) Listen(tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) error { var err error users := make([]LC.VmessUser, len(v.config.Users)) for i, v := range v.config.Users { diff --git a/listener/listener.go b/listener/listener.go index d747d5f5..d8eb5c0c 100644 --- a/listener/listener.go +++ b/listener/listener.go @@ -207,7 +207,7 @@ func ReCreateSocks(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd log.Infoln("SOCKS proxy listening at: %s", socksListener.Address()) } -func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) { redirMux.Lock() defer redirMux.Unlock() @@ -245,7 +245,7 @@ func ReCreateRedir(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAd return } - redirUDPListener, err = tproxy.NewUDP(addr, udpIn) + redirUDPListener, err = tproxy.NewUDP(addr, udpIn, natTable) if err != nil { log.Warnln("Failed to start Redir UDP Listener: %s", err) } @@ -403,7 +403,7 @@ func ReCreateTuic(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- return } -func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter) { +func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable) { tproxyMux.Lock() defer tproxyMux.Unlock() @@ -441,7 +441,7 @@ func ReCreateTProxy(port int, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketA return } - tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn) + tproxyUDPListener, err = tproxy.NewUDP(addr, udpIn, natTable) if err != nil { log.Warnln("Failed to start TProxy UDP Listener: %s", err) } @@ -719,7 +719,7 @@ func PatchTunnel(tunnels []LC.Tunnel, tcpIn chan<- C.ConnContext, udpIn chan<- C } } -func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, dropOld bool) { +func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn chan<- C.ConnContext, udpIn chan<- C.PacketAdapter, natTable C.NatTable, dropOld bool) { inboundMux.Lock() defer inboundMux.Unlock() @@ -731,7 +731,7 @@ func PatchInboundListeners(newListenerMap map[string]C.InboundListener, tcpIn ch continue } } - if err := newListener.Listen(tcpIn, udpIn); err != nil { + if err := newListener.Listen(tcpIn, udpIn, natTable); err != nil { log.Errorln("Listener %s listen err: %s", name, err.Error()) continue } diff --git a/listener/parse.go b/listener/parse.go index 9459b9e1..aa9e39ac 100644 --- a/listener/parse.go +++ b/listener/parse.go @@ -92,6 +92,7 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) { AuthenticationTimeout: 1000, ALPN: []string{"h3"}, MaxUdpRelayPacketSize: 1500, + CongestionController: "bbr", } err = decoder.Decode(mapping, tuicOption) if err != nil { diff --git a/listener/sing/sing.go b/listener/sing/sing.go index e8aafa39..a3e15154 100644 --- a/listener/sing/sing.go +++ b/listener/sing/sing.go @@ -48,6 +48,10 @@ func (c *waitCloseConn) RemoteAddr() net.Addr { return c.rAddr } +func (c *waitCloseConn) Upstream() any { + return c.Conn +} + func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error { additions := h.Additions if ctxAdditions := getAdditions(ctx); len(ctxAdditions) > 0 { @@ -146,7 +150,7 @@ func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { err = errors.New("writeBack to closed connection") return } - err = conn.WritePacket(buff, M.ParseSocksaddr(addr.String())) + err = conn.WritePacket(buff, M.SocksaddrFromNet(addr)) return } @@ -162,3 +166,11 @@ func (c *packet) Drop() { func (c *packet) InAddr() net.Addr { return c.lAddr } + +func (c *packet) SetNatTable(natTable C.NatTable) { + // no need +} + +func (c *packet) SetUdpInChan(in chan<- C.PacketAdapter) { + // no need +} diff --git a/listener/sing_vmess/server.go b/listener/sing_vmess/server.go index 859317f0..bb89ba99 100644 --- a/listener/sing_vmess/server.go +++ b/listener/sing_vmess/server.go @@ -42,7 +42,7 @@ func New(config LC.VmessServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packe Additions: additions, } - service := vmess.NewService[string](h) + service := vmess.NewService[string](h, vmess.ServiceWithDisableHeaderProtection()) err = service.UpdateUsers( common.Map(config.Users, func(it LC.VmessUser) string { return it.Username diff --git a/listener/tproxy/packet.go b/listener/tproxy/packet.go index e86a11ca..2a274f61 100644 --- a/listener/tproxy/packet.go +++ b/listener/tproxy/packet.go @@ -1,16 +1,23 @@ package tproxy import ( + "errors" + "fmt" "net" "net/netip" + "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/common/pool" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" ) type packet struct { - pc net.PacketConn - lAddr netip.AddrPort - buf []byte + pc net.PacketConn + lAddr netip.AddrPort + buf []byte + in chan<- C.PacketAdapter + natTable C.NatTable } func (c *packet) Data() []byte { @@ -19,13 +26,12 @@ func (c *packet) Data() []byte { // WriteBack opens a new socket binding `addr` to write UDP packet back func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { - tc, err := dialUDP("udp", addr.(*net.UDPAddr).AddrPort(), c.lAddr) + tc, err := createOrGetLocalConn(addr, c.LocalAddr(), c.in, c.natTable) if err != nil { n = 0 return } n, err = tc.Write(b) - tc.Close() return } @@ -41,3 +47,74 @@ func (c *packet) Drop() { func (c *packet) InAddr() net.Addr { return c.pc.LocalAddr() } + +// this function listen at rAddr and write to lAddr +// for here, rAddr is the ip/port client want to access +// lAddr is the ip/port client opened +func createOrGetLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natTable C.NatTable) (*net.UDPConn, error) { + remote := rAddr.String() + local := lAddr.String() + localConn := natTable.GetLocalConn(local, remote) + // localConn not exist + if localConn == nil { + lockKey := remote + "-lock" + cond, loaded := natTable.GetOrCreateLockForLocalConn(local, lockKey) + if loaded { + cond.L.Lock() + cond.Wait() + // we should get localConn here + localConn = natTable.GetLocalConn(local, remote) + if localConn == nil { + return nil, fmt.Errorf("localConn is nil, nat entry not exist") + } + cond.L.Unlock() + } else { + if cond == nil { + return nil, fmt.Errorf("cond is nil, nat entry not exist") + } + defer func() { + natTable.DeleteLocalConnMap(local, lockKey) + cond.Broadcast() + }() + conn, err := listenLocalConn(rAddr, lAddr, in, natTable) + if err != nil { + log.Errorln("listenLocalConn failed with error: %s, packet loss", err.Error()) + return nil, err + } + natTable.AddLocalConn(local, remote, conn) + localConn = conn + } + } + return localConn, nil +} + +// this function listen at rAddr +// and send what received to program itself, then send to real remote +func listenLocalConn(rAddr, lAddr net.Addr, in chan<- C.PacketAdapter, natTable C.NatTable) (*net.UDPConn, error) { + additions := []inbound.Addition{ + inbound.WithInName("DEFAULT-TPROXY"), + inbound.WithSpecialRules(""), + } + lc, err := dialUDP("udp", rAddr.(*net.UDPAddr).AddrPort(), lAddr.(*net.UDPAddr).AddrPort()) + if err != nil { + return nil, err + } + go func() { + log.Debugln("TProxy listenLocalConn rAddr=%s lAddr=%s", rAddr.String(), lAddr.String()) + for { + buf := pool.Get(pool.UDPBufferSize) + br, err := lc.Read(buf) + if err != nil { + pool.Put(buf) + if errors.Is(err, net.ErrClosed) { + log.Debugln("TProxy local conn listener exit.. rAddr=%s lAddr=%s", rAddr.String(), lAddr.String()) + return + } + } + // since following localPackets are pass through this socket which listen rAddr + // I choose current listener as packet's packet conn + handlePacketConn(lc, in, natTable, buf[:br], lAddr.(*net.UDPAddr).AddrPort(), rAddr.(*net.UDPAddr).AddrPort(), additions...) + } + }() + return lc, nil +} diff --git a/listener/tproxy/udp.go b/listener/tproxy/udp.go index f85c9ea9..d3727180 100644 --- a/listener/tproxy/udp.go +++ b/listener/tproxy/udp.go @@ -32,7 +32,7 @@ func (l *UDPListener) Close() error { return l.packetConn.Close() } -func NewUDP(addr string, in chan<- C.PacketAdapter, additions ...inbound.Addition) (*UDPListener, error) { +func NewUDP(addr string, in chan<- C.PacketAdapter, natTable C.NatTable, additions ...inbound.Addition) (*UDPListener, error) { if len(additions) == 0 { additions = []inbound.Addition{ inbound.WithInName("DEFAULT-TPROXY"), @@ -83,19 +83,21 @@ func NewUDP(addr string, in chan<- C.PacketAdapter, additions ...inbound.Additio // try to unmap 4in6 address lAddr = netip.AddrPortFrom(lAddr.Addr().Unmap(), lAddr.Port()) } - handlePacketConn(l, in, buf[:n], lAddr, rAddr, additions...) + handlePacketConn(l, in, natTable, buf[:n], lAddr, rAddr, additions...) } }() return rl, nil } -func handlePacketConn(pc net.PacketConn, in chan<- C.PacketAdapter, buf []byte, lAddr, rAddr netip.AddrPort, additions ...inbound.Addition) { +func handlePacketConn(pc net.PacketConn, in chan<- C.PacketAdapter, natTable C.NatTable, buf []byte, lAddr, rAddr netip.AddrPort, additions ...inbound.Addition) { target := socks5.AddrFromStdAddrPort(rAddr) pkt := &packet{ - pc: pc, - lAddr: lAddr, - buf: buf, + pc: pc, + lAddr: lAddr, + buf: buf, + in: in, + natTable: natTable, } select { case in <- inbound.NewPacket(target, pkt, C.TPROXY, additions...): diff --git a/listener/tuic/server.go b/listener/tuic/server.go index 8b928637..a7ad69f6 100644 --- a/listener/tuic/server.go +++ b/listener/tuic/server.go @@ -18,6 +18,8 @@ import ( "github.com/Dreamacro/clash/transport/tuic" ) +const ServerMaxIncomingStreams = (1 << 32) - 1 + type Listener struct { closed bool config LC.TuicServer @@ -47,9 +49,12 @@ func New(config LC.TuicServer, tcpIn chan<- C.ConnContext, udpIn chan<- C.Packet } quicConfig := &quic.Config{ MaxIdleTimeout: time.Duration(config.MaxIdleTime) * time.Millisecond, - MaxIncomingStreams: 1 >> 32, - MaxIncomingUniStreams: 1 >> 32, + MaxIncomingStreams: ServerMaxIncomingStreams, + MaxIncomingUniStreams: ServerMaxIncomingStreams, EnableDatagrams: true, + Allow0RTT: func(addr net.Addr) bool { + return true + }, } quicConfig.InitialStreamReceiveWindow = tuic.DefaultStreamReceiveWindow / 10 quicConfig.MaxStreamReceiveWindow = tuic.DefaultStreamReceiveWindow diff --git a/log/log.go b/log/log.go index f1053f44..acddeaff 100644 --- a/log/log.go +++ b/log/log.go @@ -18,6 +18,10 @@ var ( func init() { log.SetOutput(os.Stdout) log.SetLevel(log.DebugLevel) + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + TimestampFormat: "2006-01-02T15:04:05.999999999Z07:00", + }) } type Event struct { diff --git a/patch/add_debug_api.patch b/patch/add_debug_api.patch new file mode 100644 index 00000000..7134b378 --- /dev/null +++ b/patch/add_debug_api.patch @@ -0,0 +1,53 @@ +Subject: [PATCH] Chore: add debug api +--- +Index: hub/route/debug.go +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/hub/route/debug.go b/hub/route/debug.go +new file mode 100644 +--- /dev/null (revision df1007e2b14f7a526d176410995998bf06054657) ++++ b/hub/route/debug.go (revision df1007e2b14f7a526d176410995998bf06054657) +@@ -0,0 +1,21 @@ ++package route ++ ++import ( ++ "github.com/Dreamacro/clash/log" ++ "github.com/go-chi/chi/v5" ++ "github.com/go-chi/chi/v5/middleware" ++ "net/http" ++ "runtime" ++) ++ ++func debugRouter() http.Handler { ++ handler := middleware.Profiler() ++ r := chi.NewRouter() ++ r.Mount("/", handler) ++ r.Put("/gc", func(writer http.ResponseWriter, request *http.Request) { ++ log.Debugln("trigger GC") ++ runtime.GC() ++ }) ++ ++ return r ++} +Index: hub/route/server.go +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/hub/route/server.go b/hub/route/server.go +--- a/hub/route/server.go (revision f83fd6c690928ca7861196e3ca5af566303f95d5) ++++ b/hub/route/server.go (revision df1007e2b14f7a526d176410995998bf06054657) +@@ -59,6 +59,11 @@ + MaxAge: 300, + }) + r.Use(corsM.Handler) ++ ++ r.Group(func(r chi.Router) { ++ r.Mount("/debug", debugRouter()) ++ }) ++ + r.Group(func(r chi.Router) { + r.Use(authentication) + r.Get("/", hello) diff --git a/rules/common/domain.go b/rules/common/domain.go index 1dc6b250..6b3eba22 100644 --- a/rules/common/domain.go +++ b/rules/common/domain.go @@ -19,7 +19,7 @@ func (d *Domain) RuleType() C.RuleType { } func (d *Domain) Match(metadata *C.Metadata) (bool, string) { - return metadata.Host == d.domain, d.adapter + return metadata.RuleHost() == d.domain, d.adapter } func (d *Domain) Adapter() string { diff --git a/rules/common/domain_keyword.go b/rules/common/domain_keyword.go index 4fff673e..94d2a949 100644 --- a/rules/common/domain_keyword.go +++ b/rules/common/domain_keyword.go @@ -19,7 +19,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType { } func (dk *DomainKeyword) Match(metadata *C.Metadata) (bool, string) { - domain := metadata.Host + domain := metadata.RuleHost() return strings.Contains(domain, dk.keyword), dk.adapter } diff --git a/rules/common/domain_suffix.go b/rules/common/domain_suffix.go index 1e48a236..4bdc2e2e 100644 --- a/rules/common/domain_suffix.go +++ b/rules/common/domain_suffix.go @@ -19,7 +19,7 @@ func (ds *DomainSuffix) RuleType() C.RuleType { } func (ds *DomainSuffix) Match(metadata *C.Metadata) (bool, string) { - domain := metadata.Host + domain := metadata.RuleHost() return strings.HasSuffix(domain, "."+ds.suffix) || domain == ds.suffix, ds.adapter } diff --git a/rules/common/geosite.go b/rules/common/geosite.go index 865b0b1b..e89dc19b 100644 --- a/rules/common/geosite.go +++ b/rules/common/geosite.go @@ -9,7 +9,6 @@ import ( _ "github.com/Dreamacro/clash/component/geodata/standard" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" - "github.com/Dreamacro/clash/transport/socks5" ) type GEOSITE struct { @@ -25,11 +24,10 @@ func (gs *GEOSITE) RuleType() C.RuleType { } func (gs *GEOSITE) Match(metadata *C.Metadata) (bool, string) { - if metadata.AddrType() != socks5.AtypDomainName { + domain := metadata.RuleHost() + if len(domain) == 0 { return false, "" } - - domain := metadata.Host return gs.matcher.ApplyDomain(domain), gs.adapter } diff --git a/rules/common/uid.go b/rules/common/uid.go index 1457a3d0..ea275c28 100644 --- a/rules/common/uid.go +++ b/rules/common/uid.go @@ -3,7 +3,6 @@ package common import ( "fmt" "github.com/Dreamacro/clash/common/utils" - "github.com/Dreamacro/clash/component/process" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "runtime" @@ -72,27 +71,14 @@ func (u *Uid) RuleType() C.RuleType { } func (u *Uid) Match(metadata *C.Metadata) (bool, string) { - srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16) - if err != nil { - return false, "" - } - var uid *uint32 - if metadata.Uid != nil { - uid = metadata.Uid - } else if uid, err = process.FindUid(metadata.NetWork.String(), metadata.SrcIP, int(srcPort)); err == nil { - metadata.Uid = uid - } else { - log.Warnln("[UID] could not get uid from %s", metadata.String()) - return false, "" - } - - if uid != nil { - for _, _uid := range u.uids { - if _uid.Contains(*uid) { + if metadata.Uid != 0 { + for _, uid := range u.uids { + if uid.Contains(metadata.Uid) { return true, u.adapter } } } + log.Warnln("[UID] could not get uid from %s", metadata.String()) return false, "" } @@ -103,3 +89,7 @@ func (u *Uid) Adapter() string { func (u *Uid) Payload() string { return u.oUid } + +func (u *Uid) ShouldFindProcess() bool { + return true +} diff --git a/rules/logic/logic.go b/rules/logic/logic.go index 4a0bd7e8..a53503df 100644 --- a/rules/logic/logic.go +++ b/rules/logic/logic.go @@ -247,7 +247,10 @@ func matchSubRules(metadata *C.Metadata, name string, subRules map[string][]C.Ru func (logic *Logic) Match(metadata *C.Metadata) (bool, string) { switch logic.ruleType { case C.SubRules: - return matchSubRules(metadata, logic.adapter, logic.subRules) + if m, _ := logic.rules[0].Match(metadata); m { + return matchSubRules(metadata, logic.adapter, logic.subRules) + } + return false, "" case C.NOT: if m, _ := logic.rules[0].Match(metadata); !m { return true, logic.adapter diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index 73426e85..25360ec7 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -42,6 +42,11 @@ func (c *classicalStrategy) OnUpdate(rules []string) { shouldResolveIP := false for _, rawRule := range rules { ruleType, rule, params := ruleParse(rawRule) + + if ruleType == "PROCESS-NAME" { + c.shouldFindProcess = true + } + r, err := c.parse(ruleType, rule, "", params) if err != nil { log.Warnln("parse rule error:[%s]", err.Error()) diff --git a/rules/provider/domain_strategy.go b/rules/provider/domain_strategy.go index 61fe93a6..add64e76 100644 --- a/rules/provider/domain_strategy.go +++ b/rules/provider/domain_strategy.go @@ -12,8 +12,12 @@ type domainStrategy struct { domainRules *trie.DomainTrie[struct{}] } +func (d *domainStrategy) ShouldFindProcess() bool { + return false +} + func (d *domainStrategy) Match(metadata *C.Metadata) bool { - return d.domainRules != nil && d.domainRules.Search(metadata.Host) != nil + return d.domainRules != nil && d.domainRules.Search(metadata.RuleHost()) != nil } func (d *domainStrategy) Count() int { diff --git a/rules/provider/ipcidr_strategy.go b/rules/provider/ipcidr_strategy.go index bba9ddc5..88228301 100644 --- a/rules/provider/ipcidr_strategy.go +++ b/rules/provider/ipcidr_strategy.go @@ -12,6 +12,10 @@ type ipcidrStrategy struct { trie *trie.IpCidrTrie } +func (i *ipcidrStrategy) ShouldFindProcess() bool { + return false +} + func (i *ipcidrStrategy) Match(metadata *C.Metadata) bool { return i.trie != nil && i.trie.IsContain(metadata.DstIP.AsSlice()) } diff --git a/rules/provider/provider.go b/rules/provider/provider.go index 347bebaa..175917c2 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -37,6 +37,7 @@ type ruleStrategy interface { Match(metadata *C.Metadata) bool Count() int ShouldResolveIP() bool + ShouldFindProcess() bool OnUpdate(rules []string) } @@ -86,6 +87,10 @@ func (rp *ruleSetProvider) ShouldResolveIP() bool { return rp.strategy.ShouldResolveIP() } +func (rp *ruleSetProvider) ShouldFindProcess() bool { + return rp.strategy.ShouldFindProcess() +} + func (rp *ruleSetProvider) AsRule(adaptor string) C.Rule { panic("implement me") } diff --git a/rules/provider/rule_set.go b/rules/provider/rule_set.go index 326e3b0d..45cddf6c 100644 --- a/rules/provider/rule_set.go +++ b/rules/provider/rule_set.go @@ -9,14 +9,15 @@ import ( type RuleSet struct { *common.Base - ruleProviderName string - adapter string - ruleProvider P.RuleProvider - noResolveIP bool + ruleProviderName string + adapter string + ruleProvider P.RuleProvider + noResolveIP bool + shouldFindProcess bool } func (rs *RuleSet) ShouldFindProcess() bool { - return false + return rs.shouldFindProcess || rs.getProviders().ShouldFindProcess() } func (rs *RuleSet) RuleType() C.RuleType { diff --git a/transport/gun/gun.go b/transport/gun/gun.go index 66b95517..920e7adc 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -17,8 +17,9 @@ import ( "sync" "time" + "github.com/Dreamacro/clash/common/buf" "github.com/Dreamacro/clash/common/pool" - + tlsC "github.com/Dreamacro/clash/component/tls" "go.uber.org/atomic" "golang.org/x/net/http2" ) @@ -50,8 +51,9 @@ type Conn struct { } type Config struct { - ServiceName string - Host string + ServiceName string + Host string + ClientFingerprint string } func (g *Conn) initRequest() { @@ -121,13 +123,13 @@ func (g *Conn) Read(b []byte) (n int, err error) { func (g *Conn) Write(b []byte) (n int, err error) { protobufHeader := [binary.MaxVarintLen64 + 1]byte{0x0A} varuintSize := binary.PutUvarint(protobufHeader[1:], uint64(len(b))) - grpcHeader := make([]byte, 5) + var grpcHeader [5]byte grpcPayloadLen := uint32(varuintSize + 1 + len(b)) binary.BigEndian.PutUint32(grpcHeader[1:5], grpcPayloadLen) buf := pool.GetBuffer() defer pool.PutBuffer(buf) - buf.Write(grpcHeader) + buf.Write(grpcHeader[:]) buf.Write(protobufHeader[:varuintSize+1]) buf.Write(b) @@ -139,6 +141,28 @@ func (g *Conn) Write(b []byte) (n int, err error) { return len(b), err } +func (g *Conn) WriteBuffer(buffer *buf.Buffer) error { + defer buffer.Release() + dataLen := buffer.Len() + varLen := UVarintLen(uint64(dataLen)) + header := buffer.ExtendHeader(6 + varLen) + header[0] = 0x00 + binary.BigEndian.PutUint32(header[1:5], uint32(1+varLen+dataLen)) + header[5] = 0x0A + binary.PutUvarint(header[6:], uint64(dataLen)) + _, err := g.writer.Write(buffer.Bytes()) + + if err == io.ErrClosedPipe && g.err != nil { + err = g.err + } + + return err +} + +func (g *Conn) FrontHeadroom() int { + return 6 + binary.MaxVarintLen64 +} + func (g *Conn) Close() error { g.close.Store(true) if r := g.response; r != nil { @@ -165,8 +189,9 @@ func (g *Conn) SetDeadline(t time.Time) error { return nil } -func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *TransportWrap { +func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string) *TransportWrap { wrap := TransportWrap{} + dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) { pconn, err := dialFn(network, addr) if err != nil { @@ -174,17 +199,34 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config) *TransportWrap { } wrap.remoteAddr = pconn.RemoteAddr() - cn := tls.Client(pconn, cfg) - if err := cn.HandshakeContext(ctx); err != nil { + + if len(Fingerprint) != 0 { + if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists { + utlsConn := tlsC.UClient(pconn, cfg, fingerprint) + if err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx); err != nil { + pconn.Close() + return nil, err + } + state := utlsConn.(*tlsC.UConn).ConnectionState() + if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { + utlsConn.Close() + return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) + } + return utlsConn, nil + } + } + + conn := tls.Client(pconn, cfg) + if err := conn.HandshakeContext(ctx); err != nil { pconn.Close() return nil, err } - state := cn.ConnectionState() + state := conn.ConnectionState() if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { - cn.Close() + conn.Close() return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) } - return cn, nil + return conn, nil } wrap.Transport = &http2.Transport{ @@ -237,6 +279,6 @@ func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.C return conn, nil } - transport := NewHTTP2Client(dialFn, tlsConfig) + transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint) return StreamGunWithTransport(transport, cfg) } diff --git a/transport/gun/utils.go b/transport/gun/utils.go new file mode 100644 index 00000000..e5f6e019 --- /dev/null +++ b/transport/gun/utils.go @@ -0,0 +1,10 @@ +package gun + +func UVarintLen(x uint64) int { + i := 0 + for x >= 0x80 { + x >>= 7 + i++ + } + return i + 1 +} diff --git a/transport/hysteria/conns/udp/hop.go b/transport/hysteria/conns/udp/hop.go index c31f317f..53830ae4 100644 --- a/transport/hysteria/conns/udp/hop.go +++ b/transport/hysteria/conns/udp/hop.go @@ -212,6 +212,9 @@ func (c *ObfsUDPHopClientPacketConn) ReadFrom(b []byte) (int, net.Addr, error) { func (c *ObfsUDPHopClientPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) { c.connMutex.RLock() defer c.connMutex.RUnlock() + if c.closed { + return 0, net.ErrClosed + } /* // Check if the address is the server address if addr.String() != c.serverAddr.String() { @@ -237,6 +240,7 @@ func (c *ObfsUDPHopClientPacketConn) Close() error { err := c.currentConn.Close() close(c.closeChan) c.closed = true + c.serverAddrs = nil // For GC return err } diff --git a/transport/hysteria/core/client.go b/transport/hysteria/core/client.go index 5465e3d2..1df14242 100644 --- a/transport/hysteria/core/client.go +++ b/transport/hysteria/core/client.go @@ -406,7 +406,7 @@ func (c *quicPktConn) WriteTo(p []byte, addr string) error { _ = struc.Pack(&msgBuf, &msg) err = c.Session.SendMessage(msgBuf.Bytes()) if err != nil { - if errSize, ok := err.(quic.ErrMessageToLarge); ok { + if errSize, ok := err.(quic.ErrMessageTooLarge); ok { // need to frag msg.MsgID = uint16(rand.Intn(0xFFFF)) + 1 // msgID must be > 0 when fragCount > 1 fragMsgs := fragUDPMessage(msg, int(errSize)) diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go index 86a23779..2c0c5946 100644 --- a/transport/shadowtls/shadowtls.go +++ b/transport/shadowtls/shadowtls.go @@ -54,8 +54,8 @@ func (h HashedConn) Read(b []byte) (n int, err error) { } func (s *ShadowTLS) read(b []byte) (int, error) { - buf := pool.Get(tlsHeaderLen) - _, err := io.ReadFull(s.Conn, buf) + var buf [tlsHeaderLen]byte + _, err := io.ReadFull(s.Conn, buf[:]) if err != nil { return 0, fmt.Errorf("shadowtls read failed %w", err) } @@ -63,7 +63,6 @@ func (s *ShadowTLS) read(b []byte) (int, error) { return 0, fmt.Errorf("invalid shadowtls header %v", buf) } length := int(binary.BigEndian.Uint16(buf[3:])) - pool.Put(buf) if length > len(b) { n, err := s.Conn.Read(b) diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 86de2f65..e336d9db 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -46,13 +46,14 @@ const ( ) type Option struct { - Password string - ALPN []string - ServerName string - SkipCertVerify bool - Fingerprint string - Flow string - FlowShow bool + Password string + ALPN []string + ServerName string + SkipCertVerify bool + Fingerprint string + Flow string + FlowShow bool + ClientFingerprint string } type WebsocketOption struct { @@ -82,7 +83,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } if len(t.option.Fingerprint) == 0 { - xtlsConfig = tlsC.GetGlobalFingerprintXTLCConfig(xtlsConfig) + xtlsConfig = tlsC.GetGlobalXTLSConfig(xtlsConfig) } else { var err error if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, t.option.Fingerprint); err != nil { @@ -107,7 +108,7 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } if len(t.option.Fingerprint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint); err != nil { @@ -115,15 +116,26 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } } + if len(t.option.ClientFingerprint) != 0 { + utlsConn, valid := vmess.GetUtlsConnWithClientFingerprint(conn, t.option.ClientFingerprint, tlsConfig) + if valid { + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + + err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx) + return utlsConn, err + + } + } + tlsConn := tls.Client(conn, tlsConfig) + // fix tls handshake not timeout ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) defer cancel() - if err := tlsConn.HandshakeContext(ctx); err != nil { - return nil, err - } - return tlsConn, nil + err := tlsConn.HandshakeContext(ctx) + return tlsConn, err } } @@ -141,12 +153,13 @@ func (t *Trojan) StreamWebsocketConn(conn net.Conn, wsOptions *WebsocketOption) } return vmess.StreamWebsocketConn(conn, &vmess.WebsocketConfig{ - Host: wsOptions.Host, - Port: wsOptions.Port, - Path: wsOptions.Path, - Headers: wsOptions.Headers, - TLS: true, - TLSConfig: tlsConfig, + Host: wsOptions.Host, + Port: wsOptions.Port, + Path: wsOptions.Path, + Headers: wsOptions.Headers, + TLS: true, + TLSConfig: tlsConfig, + ClientFingerprint: t.option.ClientFingerprint, }) } diff --git a/transport/tuic/client.go b/transport/tuic/client.go index 1d1c3d15..d3f511df 100644 --- a/transport/tuic/client.go +++ b/transport/tuic/client.go @@ -361,7 +361,6 @@ func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Met pc := &quicStreamPacketConn{ connId: connId, quicConn: quicConn, - lAddr: quicConn.LocalAddr(), inputConn: N.NewBufferedConn(pipe2), udpRelayMode: t.UdpRelayMode, maxUdpRelayPacketSize: t.MaxUdpRelayPacketSize, diff --git a/transport/tuic/congestion/bbr_sender.go b/transport/tuic/congestion/bbr_sender.go index 1706956b..5adbd0b7 100644 --- a/transport/tuic/congestion/bbr_sender.go +++ b/transport/tuic/congestion/bbr_sender.go @@ -13,16 +13,21 @@ import ( ) const ( - MaxDatagramSize = 1252 - DefaultBBRMaxCongestionWindow congestion.ByteCount = 2000 * MaxDatagramSize - InitialCongestionWindow congestion.ByteCount = 10 * MaxDatagramSize - MinInitialPacketSize = 1200 - InitialPacketSizeIPv4 = 1252 - InitialPacketSizeIPv6 = 1232 + // InitialMaxDatagramSize is the default maximum packet size used in QUIC for congestion window computations in bytes. + InitialMaxDatagramSize = 1252 + InitialPacketSizeIPv4 = 1252 + InitialPacketSizeIPv6 = 1232 + InitialCongestionWindow = 10 + DefaultBBRMaxCongestionWindow = 10000 ) -func GetMaxPacketSize(addr net.Addr) congestion.ByteCount { - maxSize := congestion.ByteCount(MinInitialPacketSize) +const ( + initialMinCongestionWindow = 4 + minInitialPacketSize = 1200 +) + +func GetInitialPacketSize(addr net.Addr) congestion.ByteCount { + maxSize := congestion.ByteCount(minInitialPacketSize) // If this is not a UDP address, we don't know anything about the MTU. // Use the minimum size of an Initial packet as the max packet size. if udpAddr, ok := addr.(*net.UDPAddr); ok { @@ -35,41 +40,11 @@ func GetMaxPacketSize(addr net.Addr) congestion.ByteCount { return maxSize } -func GetMaxOutgoingPacketSize(addr net.Addr) congestion.ByteCount { - maxSize := congestion.ByteCount(MinInitialPacketSize) - // If this is not a UDP address, we don't know anything about the MTU. - // Use the minimum size of an Initial packet as the max packet size. - if udpAddr, ok := addr.(*net.UDPAddr); ok { - - if udpAddr.IP.To4() != nil { - //The maximum packet size of any QUIC packet over IPv4. 1500(Ethernet) - 20(IPv4 header) - 8(UDP header) = 1472. - maxSize = congestion.ByteCount(1472) - } else { - // The maximum outgoing packet size allowed. - // The maximum packet size of any QUIC packet over IPv6, based on ethernet's max - // size, minus the IP and UDP headers. IPv6 has a 40 byte header, UDP adds an - // additional 8 bytes. This is a total overhead of 48 bytes. Ethernet's - // max packet size is 1500 bytes, 1500 - 48 = 1452. - maxSize = congestion.ByteCount(1452) - } - } - return maxSize -} - var ( - // Default maximum packet size used in the Linux TCP implementation. - // Used in QUIC for congestion window computations in bytes. - MaxSegmentSize = MaxDatagramSize - // Default initial rtt used before any samples are received. InitialRtt = 100 * time.Millisecond - // Constants based on TCP defaults. - // The minimum CWND to ensure delayed acks don't reduce bandwidth measurements. - // Does not inflate the pacing rate. - DefaultMinimumCongestionWindow = 4 * MaxDatagramSize - // The gain used for the STARTUP, equal to 2/ln(2). DefaultHighGain = 2.89 @@ -174,9 +149,9 @@ type bbrSender struct { // The initial value of the |congestion_window_|. initialCongestionWindow congestion.ByteCount // The largest value the |congestion_window_| can achieve. - maxCongestionWindow congestion.ByteCount + initialMaxCongestionWindow congestion.ByteCount // The smallest value the |congestion_window_| can achieve. - minCongestionWindow congestion.ByteCount + //minCongestionWindow congestion.ByteCount // The pacing gain applied during the STARTUP phase. highGain float64 // The CWND gain applied during the STARTUP phase. @@ -269,11 +244,14 @@ type bbrSender struct { pacer *pacer maxDatagramSize congestion.ByteCount - - MaxOutgoingPacketSize congestion.ByteCount } -func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, initialMaxOutgoingPacketSize, maxCongestionWindow congestion.ByteCount) *bbrSender { +func NewBBRSender( + clock Clock, + initialMaxDatagramSize, + initialCongestionWindow, + initialMaxCongestionWindow congestion.ByteCount, +) *bbrSender { b := &bbrSender{ mode: STARTUP, clock: clock, @@ -282,8 +260,6 @@ func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, maxAckHeight: NewWindowedFilter(int64(BandwidthWindowSize), MaxFilter), congestionWindow: initialCongestionWindow, initialCongestionWindow: initialCongestionWindow, - maxCongestionWindow: maxCongestionWindow, - minCongestionWindow: congestion.ByteCount(DefaultMinimumCongestionWindow), highGain: DefaultHighGain, highCwndGain: DefaultHighGain, drainGain: 1.0 / DefaultHighGain, @@ -292,15 +268,22 @@ func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, congestionWindowGainConst: DefaultCongestionWindowGainConst, numStartupRtts: RoundTripsWithoutGrowthBeforeExitingStartup, recoveryState: NOT_IN_RECOVERY, - recoveryWindow: maxCongestionWindow, + recoveryWindow: initialMaxCongestionWindow, minRttSinceLastProbeRtt: InfiniteRTT, - MaxOutgoingPacketSize: initialMaxOutgoingPacketSize, maxDatagramSize: initialMaxDatagramSize, } b.pacer = newPacer(b.BandwidthEstimate) return b } +func (b *bbrSender) maxCongestionWindow() congestion.ByteCount { + return b.maxDatagramSize * DefaultBBRMaxCongestionWindow +} + +func (b *bbrSender) minCongestionWindow() congestion.ByteCount { + return b.maxDatagramSize * initialMinCongestionWindow +} + func (b *bbrSender) SetRTTStatsProvider(provider congestion.RTTStatsProvider) { b.rttStats = provider } @@ -323,10 +306,10 @@ func (b *bbrSender) SetMaxDatagramSize(s congestion.ByteCount) { if s < b.maxDatagramSize { panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", b.maxDatagramSize, s)) } - cwndIsMinCwnd := b.congestionWindow == b.minCongestionWindow + cwndIsMinCwnd := b.congestionWindow == b.minCongestionWindow() b.maxDatagramSize = s if cwndIsMinCwnd { - b.congestionWindow = b.minCongestionWindow + b.congestionWindow = b.minCongestionWindow() } b.pacer.SetMaxDatagramSize(s) } @@ -393,6 +376,7 @@ func (b *bbrSender) OnPacketAcked(number congestion.PacketNumber, ackedBytes con b.CalculatePacingRate() b.CalculateCongestionWindow(bytesAcked, excessAcked) b.CalculateRecoveryWindow(bytesAcked, congestion.ByteCount(0)) + } func (b *bbrSender) OnPacketLost(number congestion.PacketNumber, lostBytes congestion.ByteCount, priorInFlight congestion.ByteCount) { @@ -491,8 +475,21 @@ func (b *bbrSender) OnRetransmissionTimeout(packetsRetransmitted bool) { // //} +//func (b *bbrSender) BandwidthEstimate() Bandwidth { +// return Bandwidth(b.maxBandwidth.GetBest()) +//} + +// BandwidthEstimate returns the current bandwidth estimate func (b *bbrSender) BandwidthEstimate() Bandwidth { - return Bandwidth(b.maxBandwidth.GetBest()) + if b.rttStats == nil { + return infBandwidth + } + srtt := b.rttStats.SmoothedRTT() + if srtt == 0 { + // If we haven't measured an rtt, the bandwidth estimate is unknown. + return infBandwidth + } + return BandwidthFromDelta(b.GetCongestionWindow(), srtt) } //func (b *bbrSender) HybridSlowStart() *HybridSlowStart { @@ -740,7 +737,7 @@ func (b *bbrSender) GetTargetCongestionWindow(gain float64) congestion.ByteCount congestionWindow = congestion.ByteCount(gain * float64(b.initialCongestionWindow)) } - return maxByteCount(congestionWindow, b.minCongestionWindow) + return maxByteCount(congestionWindow, b.minCongestionWindow()) } func (b *bbrSender) CheckIfFullBandwidthReached() { @@ -811,7 +808,7 @@ func (b *bbrSender) MaybeEnterOrExitProbeRtt(now time.Time, isRoundStart, minRtt // PROBE_RTT. The CWND during PROBE_RTT is kMinimumCongestionWindow, but // we allow an extra packet since QUIC checks CWND before sending a // packet. - if b.GetBytesInFlight() < b.ProbeRttCongestionWindow()+b.MaxOutgoingPacketSize { + if b.GetBytesInFlight() < b.ProbeRttCongestionWindow()+b.maxDatagramSize { b.exitProbeRttAt = now.Add(ProbeRttTime) b.probeRttRoundPassed = false } @@ -836,7 +833,7 @@ func (b *bbrSender) ProbeRttCongestionWindow() congestion.ByteCount { if b.probeRttBasedOnBdp { return b.GetTargetCongestionWindow(ModerateProbeRttMultiplier) } else { - return b.minCongestionWindow + return b.minCongestionWindow() } } @@ -921,8 +918,8 @@ func (b *bbrSender) CalculateCongestionWindow(ackedBytes, excessAcked congestion } // Enforce the limits on the congestion window. - b.congestionWindow = maxByteCount(b.congestionWindow, b.minCongestionWindow) - b.congestionWindow = minByteCount(b.congestionWindow, b.maxCongestionWindow) + b.congestionWindow = maxByteCount(b.congestionWindow, b.minCongestionWindow()) + b.congestionWindow = minByteCount(b.congestionWindow, b.maxCongestionWindow()) } func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.ByteCount) { @@ -936,7 +933,7 @@ func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.Byt // Set up the initial recovery window. if b.recoveryWindow == 0 { - b.recoveryWindow = maxByteCount(b.GetBytesInFlight()+ackedBytes, b.minCongestionWindow) + b.recoveryWindow = maxByteCount(b.GetBytesInFlight()+ackedBytes, b.minCongestionWindow()) return } @@ -945,7 +942,7 @@ func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.Byt if b.recoveryWindow >= lostBytes { b.recoveryWindow -= lostBytes } else { - b.recoveryWindow = congestion.ByteCount(MaxSegmentSize) + b.recoveryWindow = congestion.ByteCount(b.maxDatagramSize) } // In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH, // release additional |bytes_acked| to achieve a slow-start-like behavior. @@ -955,7 +952,7 @@ func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.Byt // Sanity checks. Ensure that we always allow to send at least an MSS or // |bytes_acked| in response, whichever is larger. b.recoveryWindow = maxByteCount(b.recoveryWindow, b.GetBytesInFlight()+ackedBytes) - b.recoveryWindow = maxByteCount(b.recoveryWindow, b.minCongestionWindow) + b.recoveryWindow = maxByteCount(b.recoveryWindow, b.minCongestionWindow()) } var _ congestion.CongestionControl = &bbrSender{} diff --git a/transport/tuic/conn.go b/transport/tuic/conn.go index 8e6722c0..7ecc3f0d 100644 --- a/transport/tuic/conn.go +++ b/transport/tuic/conn.go @@ -26,7 +26,7 @@ func SetCongestionController(quicConn quic.Connection, cc string) { quicConn.SetCongestionControl( congestion.NewCubicSender( congestion.DefaultClock{}, - congestion.GetMaxPacketSize(quicConn.RemoteAddr()), + congestion.GetInitialPacketSize(quicConn.RemoteAddr()), false, nil, ), @@ -35,7 +35,7 @@ func SetCongestionController(quicConn quic.Connection, cc string) { quicConn.SetCongestionControl( congestion.NewCubicSender( congestion.DefaultClock{}, - congestion.GetMaxPacketSize(quicConn.RemoteAddr()), + congestion.GetInitialPacketSize(quicConn.RemoteAddr()), true, nil, ), @@ -44,10 +44,9 @@ func SetCongestionController(quicConn quic.Connection, cc string) { quicConn.SetCongestionControl( congestion.NewBBRSender( congestion.DefaultClock{}, - congestion.GetMaxPacketSize(quicConn.RemoteAddr()), - congestion.GetMaxOutgoingPacketSize(quicConn.RemoteAddr()), - congestion.InitialCongestionWindow, - congestion.DefaultBBRMaxCongestionWindow, + congestion.GetInitialPacketSize(quicConn.RemoteAddr()), + congestion.InitialCongestionWindow*congestion.InitialMaxDatagramSize, + congestion.DefaultBBRMaxCongestionWindow*congestion.InitialMaxDatagramSize, ), ) } @@ -110,7 +109,6 @@ var _ net.Conn = &quicStreamConn{} type quicStreamPacketConn struct { connId uint32 quicConn quic.Connection - lAddr net.Addr inputConn *N.BufferedConn udpRelayMode string @@ -252,7 +250,7 @@ func (q *quicStreamPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err erro } func (q *quicStreamPacketConn) LocalAddr() net.Addr { - return q.lAddr + return q.quicConn.LocalAddr() } var _ net.PacketConn = &quicStreamPacketConn{} diff --git a/transport/tuic/protocol.go b/transport/tuic/protocol.go index ab696e79..a54c8e54 100644 --- a/transport/tuic/protocol.go +++ b/transport/tuic/protocol.go @@ -454,12 +454,10 @@ func NewAddress(metadata *C.Metadata) Address { switch metadata.AddrType() { case socks5.AtypIPv4: addrType = AtypIPv4 - addr = make([]byte, net.IPv4len) - copy(addr[:], metadata.DstIP.AsSlice()) + addr = metadata.DstIP.AsSlice() case socks5.AtypIPv6: addrType = AtypIPv6 - addr = make([]byte, net.IPv6len) - copy(addr[:], metadata.DstIP.AsSlice()) + addr = metadata.DstIP.AsSlice() case socks5.AtypDomainName: addrType = AtypDomainName addr = make([]byte, len(metadata.Host)+1) @@ -478,18 +476,14 @@ func NewAddress(metadata *C.Metadata) Address { func NewAddressAddrPort(addrPort netip.AddrPort) Address { var addrType byte - var addr []byte if addrPort.Addr().Is4() { addrType = AtypIPv4 - addr = make([]byte, net.IPv4len) } else { addrType = AtypIPv6 - addr = make([]byte, net.IPv6len) } - copy(addr[:], addrPort.Addr().AsSlice()) return Address{ TYPE: addrType, - ADDR: addr, + ADDR: addrPort.Addr().AsSlice(), PORT: addrPort.Port(), } } diff --git a/transport/tuic/server.go b/transport/tuic/server.go index 3f459fd6..2830b324 100644 --- a/transport/tuic/server.go +++ b/transport/tuic/server.go @@ -54,6 +54,7 @@ func (s *Server) Serve() error { if err != nil { return err } + SetCongestionController(conn, s.CongestionController) uuid, err := uuid.NewV4() if err != nil { return err @@ -139,7 +140,6 @@ func (s *serverHandler) parsePacket(packet Packet, udpRelayMode string) (err err pc := &quicStreamPacketConn{ connId: assocId, quicConn: s.quicConn, - lAddr: s.quicConn.LocalAddr(), inputConn: nil, udpRelayMode: udpRelayMode, maxUdpRelayPacketSize: s.MaxUdpRelayPacketSize, @@ -151,12 +151,12 @@ func (s *serverHandler) parsePacket(packet Packet, udpRelayMode string) (err err return s.HandleUdpFn(packet.ADDR.SocksAddr(), &serverUDPPacket{ pc: pc, packet: &packet, - rAddr: s.genServerAssocIdAddr(assocId), + rAddr: s.genServerAssocIdAddr(assocId, s.quicConn.RemoteAddr()), }) } -func (s *serverHandler) genServerAssocIdAddr(assocId uint32) net.Addr { - return ServerAssocIdAddr(fmt.Sprintf("tuic-%s-%d", s.uuid.String(), assocId)) +func (s *serverHandler) genServerAssocIdAddr(assocId uint32, addr net.Addr) net.Addr { + return &ServerAssocIdAddr{assocId: fmt.Sprintf("tuic-%s-%d", s.uuid.String(), assocId), addr: addr} } func (s *serverHandler) handleStream() (err error) { @@ -166,7 +166,6 @@ func (s *serverHandler) handleStream() (err error) { if err != nil { return err } - go func() (err error) { stream := &quicStreamConn{ Stream: quicStream, @@ -274,14 +273,21 @@ func (s *serverHandler) handleUniStream() (err error) { } } -type ServerAssocIdAddr string +type ServerAssocIdAddr struct { + assocId string + addr net.Addr +} func (a ServerAssocIdAddr) Network() string { return "ServerAssocIdAddr" } func (a ServerAssocIdAddr) String() string { - return string(a) + return a.assocId +} + +func (a ServerAssocIdAddr) RawAddr() net.Addr { + return a.addr } type serverUDPPacket struct { diff --git a/transport/v2ray-plugin/websocket.go b/transport/v2ray-plugin/websocket.go index 2a052888..7c2c8a88 100644 --- a/transport/v2ray-plugin/websocket.go +++ b/transport/v2ray-plugin/websocket.go @@ -2,10 +2,10 @@ package obfs import ( "crypto/tls" - tlsC "github.com/Dreamacro/clash/component/tls" "net" "net/http" + tlsC "github.com/Dreamacro/clash/component/tls" "github.com/Dreamacro/clash/transport/vmess" ) @@ -43,7 +43,7 @@ func NewV2rayObfs(conn net.Conn, option *Option) (net.Conn, error) { NextProtos: []string{"http/1.1"}, } if len(option.Fingerprint) == 0 { - config.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + config.TLSConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if config.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, option.Fingerprint); err != nil { diff --git a/transport/vless/conn.go b/transport/vless/conn.go index 5ee69611..aceda463 100644 --- a/transport/vless/conn.go +++ b/transport/vless/conn.go @@ -1,12 +1,16 @@ package vless import ( - "bytes" "encoding/binary" "errors" "fmt" "io" "net" + "sync" + "time" + + "github.com/Dreamacro/clash/common/buf" + N "github.com/Dreamacro/clash/common/net" "github.com/gofrs/uuid" xtls "github.com/xtls/go" @@ -14,93 +18,173 @@ import ( ) type Conn struct { - net.Conn + N.ExtendedConn dst *DstAddr id *uuid.UUID addons *Addons received bool + + handshake chan struct{} + handshakeMutex sync.Mutex + err error } func (vc *Conn) Read(b []byte) (int, error) { if vc.received { - return vc.Conn.Read(b) + return vc.ExtendedConn.Read(b) } if err := vc.recvResponse(); err != nil { return 0, err } vc.received = true - return vc.Conn.Read(b) + return vc.ExtendedConn.Read(b) } -func (vc *Conn) sendRequest() error { - buf := &bytes.Buffer{} - - buf.WriteByte(Version) // protocol version - buf.Write(vc.id.Bytes()) // 16 bytes of uuid - - if vc.addons != nil { - bytes, err := proto.Marshal(vc.addons) - if err != nil { - return err - } - - buf.WriteByte(byte(len(bytes))) - buf.Write(bytes) - } else { - buf.WriteByte(0) // addon data length. 0 means no addon data +func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { + if vc.received { + return vc.ExtendedConn.ReadBuffer(buffer) } + if err := vc.recvResponse(); err != nil { + return err + } + vc.received = true + return vc.ExtendedConn.ReadBuffer(buffer) +} + +func (vc *Conn) Write(p []byte) (int, error) { + select { + case <-vc.handshake: + default: + if vc.sendRequest(p) { + if vc.err != nil { + return 0, vc.err + } + return len(p), vc.err + } + if vc.err != nil { + return 0, vc.err + } + } + return vc.ExtendedConn.Write(p) +} + +func (vc *Conn) WriteBuffer(buffer *buf.Buffer) error { + select { + case <-vc.handshake: + default: + if vc.sendRequest(buffer.Bytes()) { + return vc.err + } + if vc.err != nil { + return vc.err + } + } + return vc.ExtendedConn.WriteBuffer(buffer) +} + +func (vc *Conn) sendRequest(p []byte) bool { + vc.handshakeMutex.Lock() + defer vc.handshakeMutex.Unlock() + + select { + case <-vc.handshake: + // The handshake has been completed before. + // So return false to remind the caller. + return false + default: + } + defer close(vc.handshake) + + requestLen := 1 // protocol version + requestLen += 16 // UUID + requestLen += 1 // addons length + var addonsBytes []byte + if vc.addons != nil { + addonsBytes, vc.err = proto.Marshal(vc.addons) + if vc.err != nil { + return true + } + } + requestLen += len(addonsBytes) + requestLen += 1 // command + if !vc.dst.Mux { + requestLen += 2 // port + requestLen += 1 // addr type + requestLen += len(vc.dst.Addr) + } + requestLen += len(p) + + _buffer := buf.StackNewSize(requestLen) + defer buf.KeepAlive(_buffer) + buffer := buf.Dup(_buffer) + defer buffer.Release() + + buf.Must( + buffer.WriteByte(Version), // protocol version + buf.Error(buffer.Write(vc.id.Bytes())), // 16 bytes of uuid + buffer.WriteByte(byte(len(addonsBytes))), + buf.Error(buffer.Write(addonsBytes)), + ) + if vc.dst.Mux { - buf.WriteByte(CommandMux) + buf.Must(buffer.WriteByte(CommandMux)) } else { if vc.dst.UDP { - buf.WriteByte(CommandUDP) + buf.Must(buffer.WriteByte(CommandUDP)) } else { - buf.WriteByte(CommandTCP) + buf.Must(buffer.WriteByte(CommandTCP)) } - // Port AddrType Addr - binary.Write(buf, binary.BigEndian, vc.dst.Port) - buf.WriteByte(vc.dst.AddrType) - buf.Write(vc.dst.Addr) + binary.BigEndian.PutUint16(buffer.Extend(2), vc.dst.Port) + buf.Must( + buffer.WriteByte(vc.dst.AddrType), + buf.Error(buffer.Write(vc.dst.Addr)), + ) } - _, err := vc.Conn.Write(buf.Bytes()) - return err + buf.Must(buf.Error(buffer.Write(p))) + + _, vc.err = vc.ExtendedConn.Write(buffer.Bytes()) + return true } func (vc *Conn) recvResponse() error { - var err error - buf := make([]byte, 1) - _, err = io.ReadFull(vc.Conn, buf) - if err != nil { - return err + var buf [1]byte + _, vc.err = io.ReadFull(vc.ExtendedConn, buf[:]) + if vc.err != nil { + return vc.err } if buf[0] != Version { return errors.New("unexpected response version") } - _, err = io.ReadFull(vc.Conn, buf) - if err != nil { - return err + _, vc.err = io.ReadFull(vc.ExtendedConn, buf[:]) + if vc.err != nil { + return vc.err } length := int64(buf[0]) if length != 0 { // addon data length > 0 - io.CopyN(io.Discard, vc.Conn, length) // just discard + io.CopyN(io.Discard, vc.ExtendedConn, length) // just discard } return nil } +func (vc *Conn) Upstream() any { + return vc.ExtendedConn +} + // newConn return a Conn instance func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) { c := &Conn{ - Conn: conn, - id: client.uuid, - dst: dst, + ExtendedConn: N.NewExtendedConn(conn), + id: client.uuid, + dst: dst, + handshake: make(chan struct{}), } if !dst.UDP && client.Addons != nil { @@ -124,8 +208,12 @@ func newConn(conn net.Conn, client *Client, dst *DstAddr) (*Conn, error) { } } - if err := c.sendRequest(); err != nil { - return nil, err - } + go func() { + select { + case <-c.handshake: + case <-time.After(200 * time.Millisecond): + c.sendRequest(nil) + } + }() return c, nil } diff --git a/transport/vless/xtls.go b/transport/vless/xtls.go index ab8248af..a1aea44f 100644 --- a/transport/vless/xtls.go +++ b/transport/vless/xtls.go @@ -2,9 +2,9 @@ package vless import ( "context" - tlsC "github.com/Dreamacro/clash/component/tls" "net" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" xtls "github.com/xtls/go" ) @@ -23,7 +23,7 @@ func StreamXTLSConn(conn net.Conn, cfg *XTLSConfig) (net.Conn, error) { NextProtos: cfg.NextProtos, } if len(cfg.Fingerprint) == 0 { - xtlsConfig = tlsC.GetGlobalFingerprintXTLCConfig(xtlsConfig) + xtlsConfig = tlsC.GetGlobalXTLSConfig(xtlsConfig) } else { var err error if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.Fingerprint); err != nil { diff --git a/transport/vmess/tls.go b/transport/vmess/tls.go index 75434095..711c342d 100644 --- a/transport/vmess/tls.go +++ b/transport/vmess/tls.go @@ -3,17 +3,18 @@ package vmess import ( "context" "crypto/tls" - tlsC "github.com/Dreamacro/clash/component/tls" "net" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" ) type TLSConfig struct { - Host string - SkipCertVerify bool - FingerPrint string - NextProtos []string + Host string + SkipCertVerify bool + FingerPrint string + ClientFingerprint string + NextProtos []string } func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { @@ -24,7 +25,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { } if len(cfg.FingerPrint) == 0 { - tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + tlsConfig = tlsC.GetGlobalTLSConfig(tlsConfig) } else { var err error if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, cfg.FingerPrint); err != nil { @@ -32,11 +33,32 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) { } } + if len(cfg.ClientFingerprint) != 0 { + utlsConn, valid := GetUtlsConnWithClientFingerprint(conn, cfg.ClientFingerprint, tlsConfig) + if valid { + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + + err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx) + return utlsConn, err + } + } tlsConn := tls.Client(conn, tlsConfig) - // fix tls handshake not timeout ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) defer cancel() + err := tlsConn.HandshakeContext(ctx) return tlsConn, err } + +func GetUtlsConnWithClientFingerprint(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) { + + if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists { + utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint) + + return utlsConn, true + } + + return nil, false +} diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index b7b369fd..71dabbdd 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -5,9 +5,12 @@ import ( "context" "crypto/tls" "encoding/base64" + "encoding/binary" "errors" "fmt" + "io" + "math/rand" "net" "net/http" "net/url" @@ -16,6 +19,9 @@ import ( "sync" "time" + "github.com/Dreamacro/clash/common/buf" + N "github.com/Dreamacro/clash/common/net" + tlsC "github.com/Dreamacro/clash/component/tls" "github.com/gorilla/websocket" ) @@ -24,6 +30,8 @@ type websocketConn struct { reader io.Reader remoteAddr net.Addr + rawWriter N.ExtendedWriter + // https://godoc.org/github.com/gorilla/websocket#hdr-Concurrency rMux sync.Mutex wMux sync.Mutex @@ -31,6 +39,7 @@ type websocketConn struct { type websocketWithEarlyDataConn struct { net.Conn + wsWriter N.ExtendedWriter underlay net.Conn closed bool dialed chan bool @@ -48,6 +57,7 @@ type WebsocketConfig struct { TLSConfig *tls.Config MaxEarlyData int EarlyDataHeaderName string + ClientFingerprint string } // Read implements net.Conn.Read() @@ -79,16 +89,64 @@ func (wsc *websocketConn) Write(b []byte) (int, error) { return len(b), nil } +func (wsc *websocketConn) WriteBuffer(buffer *buf.Buffer) error { + var payloadBitLength int + dataLen := buffer.Len() + data := buffer.Bytes() + if dataLen < 126 { + payloadBitLength = 1 + } else if dataLen < 65536 { + payloadBitLength = 3 + } else { + payloadBitLength = 9 + } + + var headerLen int + headerLen += 1 // FIN / RSV / OPCODE + headerLen += payloadBitLength + headerLen += 4 // MASK KEY + + header := buffer.ExtendHeader(headerLen) + header[0] = websocket.BinaryMessage | 1<<7 + header[1] = 1 << 7 + + if dataLen < 126 { + header[1] |= byte(dataLen) + } else if dataLen < 65536 { + header[1] |= 126 + binary.BigEndian.PutUint16(header[2:], uint16(dataLen)) + } else { + header[1] |= 127 + binary.BigEndian.PutUint64(header[2:], uint64(dataLen)) + } + + maskKey := rand.Uint32() + binary.LittleEndian.PutUint32(header[1+payloadBitLength:], maskKey) + N.MaskWebSocket(maskKey, data) + + wsc.wMux.Lock() + defer wsc.wMux.Unlock() + return wsc.rawWriter.WriteBuffer(buffer) +} + +func (wsc *websocketConn) FrontHeadroom() int { + return 14 +} + +func (wsc *websocketConn) Upstream() any { + return wsc.conn.UnderlyingConn() +} + func (wsc *websocketConn) Close() error { - var errors []string + var e []string if err := wsc.conn.WriteControl(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), time.Now().Add(time.Second*5)); err != nil { - errors = append(errors, err.Error()) + e = append(e, err.Error()) } if err := wsc.conn.Close(); err != nil { - errors = append(errors, err.Error()) + e = append(e, err.Error()) } - if len(errors) > 0 { - return fmt.Errorf("failed to close connection: %s", strings.Join(errors, ",")) + if len(e) > 0 { + return fmt.Errorf("failed to close connection: %s", strings.Join(e, ",")) } return nil } @@ -149,6 +207,7 @@ func (wsedc *websocketWithEarlyDataConn) Dial(earlyData []byte) error { } wsedc.dialed <- true + wsedc.wsWriter = N.NewExtendedWriter(wsedc.Conn) if earlyDataBuf.Len() != 0 { _, err = wsedc.Conn.Write(earlyDataBuf.Bytes()) } @@ -170,6 +229,20 @@ func (wsedc *websocketWithEarlyDataConn) Write(b []byte) (int, error) { return wsedc.Conn.Write(b) } +func (wsedc *websocketWithEarlyDataConn) WriteBuffer(buffer *buf.Buffer) error { + if wsedc.closed { + return io.ErrClosedPipe + } + if wsedc.Conn == nil { + if err := wsedc.Dial(buffer.Bytes()); err != nil { + return err + } + return nil + } + + return wsedc.wsWriter.WriteBuffer(buffer) +} + func (wsedc *websocketWithEarlyDataConn) Read(b []byte) (int, error) { if wsedc.closed { return 0, io.ErrClosedPipe @@ -228,6 +301,17 @@ func (wsedc *websocketWithEarlyDataConn) SetWriteDeadline(t time.Time) error { return wsedc.Conn.SetWriteDeadline(t) } +func (wsedc *websocketWithEarlyDataConn) LazyHeadroom() bool { + return wsedc.Conn == nil +} + +func (wsedc *websocketWithEarlyDataConn) Upstream() any { + if wsedc.Conn == nil { // ensure return a nil interface not an interface with nil value + return nil + } + return wsedc.Conn +} + func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Conn, error) { ctx, cancel := context.WithCancel(context.Background()) conn = &websocketWithEarlyDataConn{ @@ -241,6 +325,7 @@ func streamWebsocketWithEarlyDataConn(conn net.Conn, c *WebsocketConfig) (net.Co } func streamWebsocketConn(conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buffer) (net.Conn, error) { + dialer := &websocket.Dialer{ NetDial: func(network, addr string) (net.Conn, error) { return conn, nil @@ -254,6 +339,18 @@ func streamWebsocketConn(conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buf if c.TLS { scheme = "wss" dialer.TLSClientConfig = c.TLSConfig + if len(c.ClientFingerprint) != 0 { + if fingerprint, exists := tlsC.GetFingerprint(c.ClientFingerprint); exists { + dialer.NetDialTLSContext = func(_ context.Context, _, addr string) (net.Conn, error) { + utlsConn := tlsC.UClient(conn, c.TLSConfig, fingerprint) + + if err := utlsConn.(*tlsC.UConn).WebsocketHandshake(); err != nil { + return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) + } + return utlsConn, nil + } + } + } } u, err := url.Parse(c.Path) @@ -294,6 +391,7 @@ func streamWebsocketConn(conn net.Conn, c *WebsocketConfig, earlyData *bytes.Buf return &websocketConn{ conn: wsConn, + rawWriter: N.NewExtendedWriter(wsConn.UnderlyingConn()), remoteAddr: conn.RemoteAddr(), }, nil } diff --git a/tunnel/connection.go b/tunnel/connection.go index c63bab78..bd8d1b63 100644 --- a/tunnel/connection.go +++ b/tunnel/connection.go @@ -9,6 +9,7 @@ import ( N "github.com/Dreamacro/clash/common/net" "github.com/Dreamacro/clash/common/pool" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" ) func handleUDPToRemote(packet C.UDPPacket, pc C.PacketConn, metadata *C.Metadata) error { @@ -32,6 +33,7 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, oAddr, buf := pool.Get(pool.UDPBufferSize) defer func() { _ = pc.Close() + closeAllLocalCoon(key) natTable.Delete(key) _ = pool.Put(buf) }() @@ -44,11 +46,12 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, oAddr, } fromUDPAddr := from.(*net.UDPAddr) - if fAddr.IsValid() { - fromAddr, _ := netip.AddrFromSlice(fromUDPAddr.IP) - fromAddr.Unmap() - if oAddr == fromAddr { - fromUDPAddr.IP = fAddr.AsSlice() + fromUDPAddr = &(*fromUDPAddr) // make a copy + if fromAddr, ok := netip.AddrFromSlice(fromUDPAddr.IP); ok { + if fAddr.IsValid() && (oAddr.Unmap() == fromAddr.Unmap()) { + fromUDPAddr.IP = fAddr.Unmap().AsSlice() + } else { + fromUDPAddr.IP = fromAddr.Unmap().AsSlice() } } @@ -59,6 +62,19 @@ func handleUDPToLocal(packet C.UDPPacket, pc net.PacketConn, key string, oAddr, } } +func closeAllLocalCoon(lAddr string) { + natTable.RangeLocalConn(lAddr, func(key, value any) bool { + conn, ok := value.(*net.UDPConn) + if !ok || conn == nil { + log.Debugln("Value %#v unknown value when closing TProxy local conn...", conn) + return true + } + conn.Close() + log.Debugln("Closing TProxy local conn... lAddr=%s rAddr=%s", lAddr, key) + return true + }) +} + func handleSocket(ctx C.ConnContext, outbound net.Conn) { N.Relay(ctx.Conn(), outbound) } diff --git a/tunnel/statistic/tracker.go b/tunnel/statistic/tracker.go index fc627297..97dd7316 100644 --- a/tunnel/statistic/tracker.go +++ b/tunnel/statistic/tracker.go @@ -4,6 +4,8 @@ import ( "net" "time" + "github.com/Dreamacro/clash/common/buf" + N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" "github.com/gofrs/uuid" @@ -29,7 +31,9 @@ type trackerInfo struct { type tcpTracker struct { C.Conn `json:"-"` *trackerInfo - manager *Manager + manager *Manager + extendedReader N.ExtendedReader + extendedWriter N.ExtendedWriter } func (tt *tcpTracker) ID() string { @@ -44,6 +48,14 @@ func (tt *tcpTracker) Read(b []byte) (int, error) { return n, err } +func (tt *tcpTracker) ReadBuffer(buffer *buf.Buffer) (err error) { + err = tt.extendedReader.ReadBuffer(buffer) + download := int64(buffer.Len()) + tt.manager.PushDownloaded(download) + tt.DownloadTotal.Add(download) + return +} + func (tt *tcpTracker) Write(b []byte) (int, error) { n, err := tt.Conn.Write(b) upload := int64(n) @@ -52,11 +64,23 @@ func (tt *tcpTracker) Write(b []byte) (int, error) { return n, err } +func (tt *tcpTracker) WriteBuffer(buffer *buf.Buffer) (err error) { + upload := int64(buffer.Len()) + err = tt.extendedWriter.WriteBuffer(buffer) + tt.manager.PushUploaded(upload) + tt.UploadTotal.Add(upload) + return +} + func (tt *tcpTracker) Close() error { tt.manager.Leave(tt) return tt.Conn.Close() } +func (tt *tcpTracker) Upstream() any { + return tt.Conn +} + func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.Rule) *tcpTracker { uuid, _ := uuid.NewV4() if conn != nil { @@ -79,6 +103,8 @@ func NewTCPTracker(conn C.Conn, manager *Manager, metadata *C.Metadata, rule C.R UploadTotal: atomic.NewInt64(0), DownloadTotal: atomic.NewInt64(0), }, + extendedReader: N.NewExtendedReader(conn), + extendedWriter: N.NewExtendedWriter(conn), } if rule != nil { diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index c5534062..695f2945 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -82,6 +82,11 @@ func UDPIn() chan<- C.PacketAdapter { return udpQueue } +// NatTable return nat table +func NatTable() C.NatTable { + return natTable +} + // Rules return all rules func Rules() []C.Rule { return rules @@ -148,12 +153,8 @@ func SetMode(m TunnelMode) { // SetFindProcessMode replace SetAlwaysFindProcess // always find process info if legacyAlways = true or mode.Always() = true, may be increase many memory -func SetFindProcessMode(legacyAlways bool, mode P.FindProcessMode) { - if legacyAlways { - findProcessMode = P.FindProcessAlways - } else { - findProcessMode = mode - } +func SetFindProcessMode(mode P.FindProcessMode) { + findProcessMode = mode } // processUDP starts a loop to handle udp packet @@ -308,12 +309,12 @@ func handleUDPConn(packet C.PacketAdapter) { log.Warnln( "[UDP] dial %s %s --> %s error: %s", proxy.Name(), - metadata.SourceAddress(), + metadata.SourceDetail(), metadata.RemoteAddress(), err.Error(), ) } else { - log.Warnln("[UDP] dial %s (match %s/%s) %s --> %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.SourceAddress(), metadata.RemoteAddress(), err.Error()) + log.Warnln("[UDP] dial %s (match %s/%s) %s --> %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.SourceDetail(), metadata.RemoteAddress(), err.Error()) } }) if err != nil { @@ -325,7 +326,7 @@ func handleUDPConn(packet C.PacketAdapter) { switch true { case metadata.SpecialProxy != "": - log.Infoln("[UDP] %s --> %s using %s", metadata.SourceAddress(), metadata.RemoteAddress(), metadata.SpecialProxy) + log.Infoln("[UDP] %s --> %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), metadata.SpecialProxy) case rule != nil: if rule.Payload() != "" { log.Infoln("[UDP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)", rule.RuleType().String(), rule.Payload()), rawPc.Chains().String()) @@ -341,9 +342,10 @@ func handleUDPConn(packet C.PacketAdapter) { } oAddr := metadata.DstIP + natTable.Set(key, pc) + go handleUDPToLocal(packet, pc, key, oAddr, fAddr) - natTable.Set(key, pc) handle() }() } @@ -394,12 +396,12 @@ func handleTCPConn(connCtx C.ConnContext) { log.Warnln( "[TCP] dial %s %s --> %s error: %s", proxy.Name(), - metadata.SourceAddress(), + metadata.SourceDetail(), metadata.RemoteAddress(), err.Error(), ) } else { - log.Warnln("[TCP] dial %s (match %s/%s) %s --> %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.SourceAddress(), metadata.RemoteAddress(), err.Error()) + log.Warnln("[TCP] dial %s (match %s/%s) %s --> %s error: %s", proxy.Name(), rule.RuleType().String(), rule.Payload(), metadata.SourceDetail(), metadata.RemoteAddress(), err.Error()) } }) if err != nil { @@ -413,7 +415,7 @@ func handleTCPConn(connCtx C.ConnContext) { switch true { case metadata.SpecialProxy != "": - log.Infoln("[TCP] %s --> %s using %s", metadata.SourceAddress(), metadata.RemoteAddress(), metadata.SpecialProxy) + log.Infoln("[TCP] %s --> %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), metadata.SpecialProxy) case rule != nil: if rule.Payload() != "" { log.Infoln("[TCP] %s --> %s match %s using %s", metadata.SourceDetail(), metadata.RemoteAddress(), fmt.Sprintf("%s(%s)", rule.RuleType().String(), rule.Payload()), remoteConn.Chains().String()) @@ -427,7 +429,7 @@ func handleTCPConn(connCtx C.ConnContext) { default: log.Infoln( "[TCP] %s --> %s doesn't match any rule using DIRECT", - metadata.SourceAddress(), + metadata.SourceDetail(), metadata.RemoteAddress(), ) }